CodeMirror 6 editor provider for JupyterLab with comprehensive language support, themes, extensions, and collaborative editing capabilities
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
The extension system provides a comprehensive registry and configuration management for 25+ configurable extensions that customize editor behavior and appearance.
Main registry for managing editor extensions and their configurations.
/**
* CodeMirror extensions registry
* Manages 25+ configurable extensions for editor customization
*/
class EditorExtensionRegistry implements IEditorExtensionRegistry {
constructor();
/**
* Base editor configuration (default config optionally modified by user)
*/
baseConfiguration: Record<string, any>;
/**
* Default editor configuration as defined when extensions are registered
*/
readonly defaultConfiguration: Record<string, any>;
/**
* Editor configuration JSON schema for validation
*/
readonly settingsSchema: ReadonlyJSONObject;
/**
* Add a default editor extension
*/
addExtension<T>(factory: IEditorExtensionFactory<T>): void;
/**
* Create a new extensions handler for an editor
*/
createNew(options: IEditorHandlerOptions): IExtensionsHandler;
}
interface IEditorExtensionRegistry {
readonly baseConfiguration: Record<string, any>;
addExtension<T>(factory: IEditorExtensionFactory<T>): void;
createNew(options: IEditorHandlerOptions): IExtensionsHandler;
}Usage Examples:
import { EditorExtensionRegistry } from "@jupyterlab/codemirror";
// Create extension registry
const registry = new EditorExtensionRegistry();
// Add custom extension
registry.addExtension({
name: 'myExtension',
factory: (options) => ({
instance: (value) => myCustomExtension(value),
reconfigure: (value) => myExtension.reconfigure(value)
}),
default: true,
schema: {
type: 'boolean',
description: 'Enable my custom extension'
}
});
// Create extensions handler for editor
const handler = registry.createNew({
baseConfiguration: { lineNumbers: true },
config: { myExtension: true }
});Manages individual editor configuration and extension lifecycle.
/**
* Editor configuration handler
* Stores editor configuration and manages extension reconfiguration
*/
class ExtensionsHandler implements IExtensionsHandler, IObservableDisposable {
constructor(options?: IEditorHandlerOptions);
/**
* Signal triggered when editor configuration changes
*/
readonly configChanged: ISignal<this, Record<string, any>>;
/**
* Signal emitted when object is disposed
*/
readonly disposed: ISignal<this, void>;
/**
* Whether the object is disposed
*/
readonly isDisposed: boolean;
// Lifecycle
dispose(): void;
// Configuration management
getOption(option: string): unknown;
hasOption(option: string): boolean;
setOption(option: string, value: unknown): void;
setBaseOptions(options: Record<string, any>): void;
setOptions(options: Record<string, any>): void;
// Extension management
reconfigureExtension<T>(view: EditorView, key: string, value: T): void;
reconfigureExtensions(view: EditorView, configuration: Record<string, any>): void;
injectExtension(view: EditorView, extension: Extension): void;
getInitialExtensions(): Extension[];
}
interface IEditorHandlerOptions {
baseConfiguration?: Record<string, any>;
config?: Record<string, any>;
defaultExtensions?: [string, IConfigurableExtension<any>][];
}Usage Examples:
import { ExtensionsHandler } from "@jupyterlab/codemirror";
import { EditorView } from "@codemirror/view";
// Create handler with configuration
const handler = new ExtensionsHandler({
baseConfiguration: {
lineNumbers: true,
tabSize: 4
},
config: {
lineWrap: true,
theme: 'dark'
}
});
// Listen for configuration changes
handler.configChanged.connect((sender, changes) => {
console.log('Configuration changed:', changes);
});
// Update single option
handler.setOption('fontSize', 14);
// Update multiple options
handler.setOptions({
lineHeight: 1.5,
fontFamily: 'Monaco'
});
// Get current option value
const lineNumbers = handler.getOption('lineNumbers'); // true
// Reconfigure extensions on editor view
const view = new EditorView({...});
handler.reconfigureExtensions(view, {
lineNumbers: false,
lineWrap: false
});The registry comes with 25+ built-in extensions for comprehensive editor customization.
/**
* Get default editor extensions
* Returns array of 25+ configurable extension factories
*/
static getDefaultExtensions(options?: {
themes?: IEditorThemeRegistry;
translator?: ITranslator | null;
}): ReadonlyArray<Readonly<IEditorExtensionFactory<any>>>;Built-in Extensions:
Usage Examples:
import { EditorExtensionRegistry } from "@jupyterlab/codemirror";
// Get default extensions
const extensions = EditorExtensionRegistry.getDefaultExtensions();
// Get extensions with theme and translation support
const extensionsWithThemes = EditorExtensionRegistry.getDefaultExtensions({
themes: themeRegistry,
translator: app.translator
});
// Create registry with default extensions
const registry = new EditorExtensionRegistry();
extensions.forEach(extension => registry.addExtension(extension));Interface for creating configurable extensions.
/**
* Editor extension factory interface
*/
interface IEditorExtensionFactory<T> {
/**
* Extension unique identifier
*/
readonly name: string;
/**
* Extension factory function
*/
readonly factory: (options: IEditorExtensionFactory.IOptions) => IConfigurableExtension<T> | null;
/**
* Extension default value
*/
readonly default?: T;
/**
* JSON schema for configurable option
*/
readonly schema?: ReadonlyJSONObject;
}
interface IEditorExtensionFactory.IOptions {
inline: boolean;
model: CodeEditor.IModel;
}
/**
* Dynamically configurable editor extension
*/
interface IConfigurableExtension<T> {
/**
* Create an editor extension for the provided value
*/
instance(value: T): Extension;
/**
* Reconfigure an editor extension with new value
*/
reconfigure(value: T): StateEffect<T> | null;
}Helper functions for creating different types of extensions.
/**
* Creates a dynamically configurable editor extension
*/
static createConfigurableExtension<T>(
builder: (value: T) => Extension
): IConfigurableExtension<T>;
/**
* Creates a configurable extension returning one of two extensions
* based on a boolean value
*/
static createConditionalExtension(
truthy: Extension,
falsy?: Extension
): IConfigurableExtension<boolean>;
/**
* Creates an immutable extension (cannot be reconfigured)
*/
static createImmutableExtension(
extension: Extension
): IConfigurableExtension<undefined>;Usage Examples:
import { EditorExtensionRegistry } from "@jupyterlab/codemirror";
import { lineNumbers } from "@codemirror/view";
import { keymap } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";
// Create configurable extension
const configurableLineNumbers = EditorExtensionRegistry.createConfigurableExtension<boolean>(
(show) => show ? lineNumbers() : []
);
// Create conditional extension
const conditionalKeymap = EditorExtensionRegistry.createConditionalExtension(
keymap.of(defaultKeymap),
[] // No keymap when false
);
// Create immutable extension
const immutableTheme = EditorExtensionRegistry.createImmutableExtension(
EditorView.theme({
'.cm-editor': { fontSize: '14px' }
})
);
// Add to registry
registry.addExtension({
name: 'myLineNumbers',
factory: () => configurableLineNumbers,
default: true
});Complex configuration scenarios and extension interactions.
// Create handler with complex configuration
const handler = new ExtensionsHandler({
baseConfiguration: {
// Base settings applied to all editors
tabSize: 4,
indentUnit: ' ',
lineNumbers: true
},
config: {
// Specific settings for this editor
theme: 'jupyterlab',
lineWrap: true,
rulers: [80, 120]
},
defaultExtensions: [
// Custom extensions specific to this handler
['myCustomExtension', myCustomExtensionFactory.factory()]
]
});
// Dynamic reconfiguration based on context
handler.configChanged.connect((sender, changes) => {
if ('theme' in changes) {
// React to theme changes
updateEditorStyling(changes.theme);
}
if ('language' in changes) {
// Update language-specific extensions
handler.setOptions({
pythonBuiltin: changes.language === 'python',
jsxHighlighting: changes.language === 'jsx'
});
}
});
// Conditional extension loading
if (isCollaborativeMode) {
handler.setOptions({
yBinding: true,
collaborativeCursors: true,
conflictResolution: 'merge'
});
}interface IExtensionsHandler extends IDisposable {
readonly configChanged: ISignal<this, Record<string, any>>;
getOption(option: string): unknown;
hasOption(option: string): boolean;
setOption(option: string, value: unknown): void;
setBaseOptions(options: Record<string, any>): void;
setOptions(options: Record<string, any>): void;
reconfigureExtension<T>(view: EditorView, key: string, value: T): void;
reconfigureExtensions(view: EditorView, configuration: Record<string, any>): void;
injectExtension(view: EditorView, extension: Extension): void;
getInitialExtensions(): Extension[];
}
interface IEditorHandlerOptions {
baseConfiguration?: Record<string, any>;
config?: Record<string, any>;
defaultExtensions?: [string, IConfigurableExtension<any>][];
}