Skip to content

Commit

Permalink
fix: dynamically reconfigure codemirror instead of recreate on rerend…
Browse files Browse the repository at this point in the history
…er (#1125)

* reproduction of decorator updates

* reconfigure extensions rather than recreate
  • Loading branch information
danieldelcore committed Apr 26, 2024
1 parent f394fad commit a41d1a0
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 89 deletions.
53 changes: 52 additions & 1 deletion sandpack-react/src/components/CodeEditor/CodeMirror.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ stories.add("ShowLineNumber", () => (
#header {
width: @width;
height: @height;
}
}
`}
fileType="less"
initMode="immediate"
Expand Down Expand Up @@ -138,3 +138,54 @@ export default function List() {
/>
</SandpackProvider>
));

type Decorators = Array<{ className: string; line: number }>;

stories.add("DecoratorsDynamic", () => {
const [decorators, setDecorators] = React.useState<Decorators>([]);
const [file, setFile] = React.useState(`const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}];
export default function List() {
const [text, setText] = useState("")
const listItems = people.map(person =>
<li>{person}</li>
);
return <ul>{listItems}</ul>;
}`);

React.useEffect(() => {
const lines = file.split("\n");

setDecorators([
{
className: "highlight",
line: Math.floor(Math.random() * lines.length),
},
]);
}, [file]);

return (
<SandpackProvider>
<style>
{`.highlight, .widget {
background: red;
}`}
</style>
<CodeEditor
code={file}
decorators={decorators}
fileType="jsx"
initMode="immediate"
onCodeUpdate={(newCode) => setFile(newCode)}
/>
</SandpackProvider>
);
});
186 changes: 98 additions & 88 deletions sandpack-react/src/components/CodeEditor/CodeMirror.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,91 +186,6 @@ export const CodeMirror = React.forwardRef<CodeMirrorRef, CodeMirrorProps>(
React.useEffect(() => {
if (!wrapper.current || !shouldInitEditor) return;

const customCommandsKeymap: KeyBinding[] = [
{
key: "Tab",
run: (view): boolean => {
indentMore(view);

const customKey = extensionsKeymap.find(({ key }) => key === "Tab");

return customKey?.run?.(view) ?? true;
},
},
{
key: "Shift-Tab",
run: ({ state, dispatch }): boolean => {
indentLess({ state, dispatch });

const customKey = extensionsKeymap.find(
({ key }) => key === "Shift-Tab"
);

return customKey?.run?.(view) ?? true;
},
},
{
key: "Escape",
run: (): boolean => {
if (readOnly) return true;

if (wrapper.current) {
wrapper.current.focus();
}

return true;
},
},
{
key: "mod-Backspace",
run: deleteGroupBackward,
},
];

const extensionList = [
highlightSpecialChars(),
history(),
closeBrackets(),

...extensions,

keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...historyKeymap,
...customCommandsKeymap,
...extensionsKeymap,
] as KeyBinding[]),
langSupport,

getEditorTheme(),
syntaxHighlighting(highlightTheme),
];

if (readOnly) {
extensionList.push(EditorState.readOnly.of(true));
extensionList.push(EditorView.editable.of(false));
} else {
extensionList.push(bracketMatching());
extensionList.push(highlightActiveLine());
}

if (sortedDecorators) {
extensionList.push(highlightDecorators(sortedDecorators));
}

if (wrapContent) {
extensionList.push(EditorView.lineWrapping);
}

if (showLineNumbers) {
extensionList.push(lineNumbers());
}

if (showInlineErrors) {
extensionList.push(highlightInlineError());
}

const parentDiv = wrapper.current;
const existingPlaceholder = parentDiv.querySelector(
".sp-pre-placeholder"
Expand All @@ -281,7 +196,7 @@ export const CodeMirror = React.forwardRef<CodeMirrorRef, CodeMirrorProps>(

const view = new EditorView({
doc: code,
extensions: extensionList,
extensions: [],
parent: parentDiv,
dispatch: (tr): void => {
view.update([tr]);
Expand Down Expand Up @@ -313,14 +228,109 @@ export const CodeMirror = React.forwardRef<CodeMirrorRef, CodeMirrorProps>(
return (): void => {
cmView.current?.destroy();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [shouldInitEditor]);

React.useEffect(() => {
if (cmView.current) {
const customCommandsKeymap: KeyBinding[] = [
{
key: "Tab",
run: (view): boolean => {
indentMore(view);

const customKey = extensionsKeymap.find(
({ key }) => key === "Tab"
);

return customKey?.run?.(view) ?? true;
},
},
{
key: "Shift-Tab",
run: (view): boolean => {
indentLess({ state: view.state, dispatch: view.dispatch });

const customKey = extensionsKeymap.find(
({ key }) => key === "Shift-Tab"
);

return customKey?.run?.(view) ?? true;
},
},
{
key: "Escape",
run: (): boolean => {
if (readOnly) return true;

if (wrapper.current) {
wrapper.current.focus();
}

return true;
},
},
{
key: "mod-Backspace",
run: deleteGroupBackward,
},
];

const extensionList = [
highlightSpecialChars(),
history(),
closeBrackets(),

...extensions,

keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...historyKeymap,
...customCommandsKeymap,
...extensionsKeymap,
] as KeyBinding[]),
langSupport,

getEditorTheme(),
syntaxHighlighting(highlightTheme),
];

if (readOnly) {
extensionList.push(EditorState.readOnly.of(true));
extensionList.push(EditorView.editable.of(false));
} else {
extensionList.push(bracketMatching());
extensionList.push(highlightActiveLine());
}

if (sortedDecorators) {
extensionList.push(highlightDecorators(sortedDecorators));
}

if (wrapContent) {
extensionList.push(EditorView.lineWrapping);
}

if (showLineNumbers) {
extensionList.push(lineNumbers());
}

if (showInlineErrors) {
extensionList.push(highlightInlineError());
}

// Add new hightlight decorators
cmView.current.dispatch({
effects: StateEffect.reconfigure.of(extensionList),
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
shouldInitEditor,
sortedDecorators,
showLineNumbers,
wrapContent,
themeId,
sortedDecorators,
readOnly,
autoReload,
]);
Expand Down

0 comments on commit a41d1a0

Please sign in to comment.