The new CodeMirror 6 setup of “everything’s an extension” is taking some time to get used to.
Say I want to provide editor settings to a user, such as:
- The language used
- Show/hide line numbers
- Enable/disable line wrapping
- Change font family/size/line height
I understand creating compartments and reconfiguring those compartments, but what’s the best way to trigger reconfiguration based on outside user input?
I’ve poured through the docs, and tried several approaches. One big hurdle is the async nature of some of the changes. For example: loading languages through @codemirror/language-data
.
The best approach I’ve found so far is this:
export const editorSettingsUpdateEffect = StateEffect.define();
export function makeEditorSettingExtension(onChange) {
const compartment = new Compartment();
const updateCompartment = ViewPlugin.fromClass(
class {
update(u) {
u.transactions.map(async (tr) => {
for (let e of tr.effects) {
if (e.is(editorSettingsUpdateEffect)) {
let newValue = await onChange(e.value, tr);
if (newValue !== undefined) {
u.view.dispatch({
effects: compartment.reconfigure(newValue)
});
}
}
}
});
}
}
);
return [compartment.of([]), updateCompartment];
}
In Use:
const lineNumbersExtension = makeEditorSettingExtension(
function (editorSettings) {
const value = editorSettings.lineNumbers;
if (value !== undefined) {
return value ? lineNumbers() : [];
}
}
});
const view = new EditorView({
parent: document.body,
state: EditorState.create({
doc: '1\n2\n3\n4',
extensions: [lineNumbersExtension]
})
});
// Immediately sending this `editorSettingsUpdateEffect`
// but it could be called later when user changes a setting
view.dispatch({
effects: editorSettingsUpdateEffect.of({
lineNumbers: true
})
});
Is there a better way to dispatch an update to a compartment based on an outside change?
I don’t like using ViewPlugin
in this way, but that’s the only way I’ve found that I can both listen to an effect and asynchronously send a view.dispatch
. Seems like this is pretty inefficient for a dozen settings.
EditorState.transactionExtender
doesn’t allow async
, the effect change has to be done right away. It also doesn’t work for multiple settings at once. It only seems to allow one transactionExtender
to change the effects
, overriding all the others
I’d sure love a way to just “listen” to a particular effect being dispatched and trigger another view.dispatch
as necessary, or some other more direct way for an extension/compartment to dynamically reconfigure itself.
References: