Skip to content

Commit 20aa224

Browse files
committed
Fix snippet insertion, Kusto lookup, and duplicate-destroy in unified editor
- String.replace() treats a literal "$" in the replacement as a special pattern (e.g. "$&", "$1"), so inserting selected text containing a "$" into a snippet silently mangled it. Pass a replacer function instead. - Look up code languages via milkdownCodeLanguages (which includes Kusto) instead of the unregistered @codemirror/language-data list. - destroyPrevious() is called fire-and-forget from a sync constructor, so this.crepe may still be null while initialize() is pending. Await this.ready first so we always destroy the Crepe instance instead of leaving initialize() to mount into an element a newer instance already took over.
1 parent 77d90bb commit 20aa224

1 file changed

Lines changed: 18 additions & 6 deletions

File tree

ui/src/pages/unified_editor.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Crepe } from '@milkdown/crepe';
22
import { editorViewCtx, parserCtx, serializerCtx } from '@milkdown/kit/core';
33
import { Slice } from '@milkdown/kit/prose/model';
44
import { autocompletion } from '@codemirror/autocomplete';
5-
import { languages } from '@codemirror/language-data';
65
import { Compartment, EditorState } from '@codemirror/state';
76
import { EditorView, keymap, placeholder as placeholderExtension } from '@codemirror/view';
87
import { basicSetup } from 'codemirror';
@@ -76,7 +75,7 @@ function normalizeKey(key) {
7675

7776
function findLanguage(mode) {
7877
const name = String(mode || '').split('/').pop().toLowerCase();
79-
return languages.find((description) => {
78+
return milkdownCodeLanguages.find((description) => {
8079
if (description.name.toLowerCase() === name) {
8180
return true;
8281
}
@@ -302,8 +301,8 @@ class CodeEditor {
302301
const selection = this.view.state.selection.main;
303302
const selected = this.view.state.sliceDoc(selection.from, selection.to);
304303
const insert = String(snippet || '')
305-
.replace(/\$\{1:\$SELECTION\}/g, selected)
306-
.replace(/\$SELECTION/g, selected);
304+
.replace(/\$\{1:\$SELECTION\}/g, () => selected)
305+
.replace(/\$SELECTION/g, () => selected);
307306
this.view.dispatch({
308307
changes: { from: selection.from, to: selection.to, insert },
309308
selection: { anchor: selection.from + insert.length },
@@ -482,8 +481,8 @@ class MilkdownEditor {
482481
const { from, to } = view.state.selection;
483482
const selected = view.state.doc.textBetween(from, to, '\n');
484483
const insert = String(snippet || '')
485-
.replace(/\$\{1:\$SELECTION\}/g, selected)
486-
.replace(/\$SELECTION/g, selected);
484+
.replace(/\$\{1:\$SELECTION\}/g, () => selected)
485+
.replace(/\$SELECTION/g, () => selected);
487486
view.dispatch(view.state.tr.insertText(insert, from, to));
488487
});
489488
return this;
@@ -503,6 +502,19 @@ class MilkdownEditor {
503502
}
504503

505504
async destroy() {
505+
// destroyPrevious() calls this fire-and-forget from a sync
506+
// constructor, so `this.ready` (the pending initialize()) may not
507+
// have resolved yet -- `this.crepe` would still be null. Awaiting it
508+
// first means we always end up destroying the Crepe instance instead
509+
// of silently no-oping and leaving initialize() to mount its editor
510+
// into an element a newer instance already took over, unremoved.
511+
if (this.ready) {
512+
try {
513+
await this.ready;
514+
} catch {
515+
return;
516+
}
517+
}
506518
if (this.crepe) {
507519
try {
508520
await this.crepe.destroy();

0 commit comments

Comments
 (0)