Skip to content

Commit 703a872

Browse files
committed
rewrite kit-codemirror: performance, reliability, easier customization
- made `useCodeMirror` easier to reuse, now accepting an object of options and manages the editor in a stateless approach which only guarantees that the `editorRef` is updated for any `useLayoutEffect`/useEffect` which is defined after the hook was called. it now manages extensions in a single compartment, keeping the editors state when extensions are reconfigured - removed `effects` prop and system, instead directly modify extensions or use `editor` directly via `onViewLifecycle`, `onSetup` or from `useCodeMirror`. - reworked `useExtension` to automatically add and reconfigure an extension, now can only be used together with `useCodeMirror`, must be called directly after it - `CodeMirror` replaced prop `classNamesContent: string[]` with `classNameContent: string` - removed `useEditorClasses` helper hook, now included in `CodeMirror` - changed internal effects and state flow, for performance and reliability - added `onSetup` prop to `CodeMirror`, reactively attach event listener without extra state overhead or needing to use the `useCodeMirror` hook - added `Transaction.remote` annotation to the default `onExternalChange` handler - added `isRemoteChange` util to check for the remote annotation
1 parent 5df22b5 commit 703a872

File tree

19 files changed

+311
-298
lines changed

19 files changed

+311
-298
lines changed

babel.config.json

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{
22
"presets": [
3-
"@babel/preset-react",
3+
[
4+
"@babel/preset-react",
5+
{
6+
"runtime": "automatic"
7+
}
8+
],
49
"@babel/preset-typescript"
510
],
611
"plugins": [
@@ -19,7 +24,12 @@
1924
"cjs": {
2025
"presets": [
2126
"@babel/preset-env",
22-
"@babel/preset-react",
27+
[
28+
"@babel/preset-react",
29+
{
30+
"runtime": "automatic"
31+
}
32+
],
2333
"@babel/preset-typescript"
2434
],
2535
"plugins": [
@@ -36,7 +46,12 @@
3646
"test": {
3747
"presets": [
3848
"@babel/preset-env",
39-
"@babel/preset-react",
49+
[
50+
"@babel/preset-react",
51+
{
52+
"runtime": "automatic"
53+
}
54+
],
4055
"@babel/preset-typescript"
4156
]
4257
},

package-lock.json

Lines changed: 4 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"@codemirror/language": "^6.1.0",
3030
"@ui-schema/dictionary": "~0.0.13",
3131
"@ui-schema/ds-material": "~0.4.3",
32-
"@ui-schema/kit-codemirror": "~0.2.0",
32+
"@ui-schema/kit-codemirror": "^1.0.0-alpha.0",
3333
"@ui-schema/material-code": "~0.4.7",
3434
"@ui-schema/ui-schema": "~0.4.7",
3535
"immutable": "^5.0.0",
Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import React from 'react'
1+
import Box from '@mui/material/Box'
2+
import { useCodeMirror } from '@ui-schema/kit-codemirror'
3+
import React, { useCallback } from 'react'
24
import {
35
lineNumbers, highlightActiveLineGutter, highlightSpecialChars,
46
drawSelection, dropCursor,
@@ -11,36 +13,25 @@ import { history, defaultKeymap, historyKeymap, indentWithTab } from '@codemirro
1113
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
1214
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete'
1315
import { lintKeymap } from '@codemirror/lint'
14-
import { Compartment, EditorState, Extension } from '@codemirror/state'
16+
import { Compartment, EditorState, Extension, Prec } from '@codemirror/state'
1517
import { useEditorTheme } from '@ui-schema/material-code/useEditorTheme'
1618
import { useHighlightStyle } from '@ui-schema/material-code/useHighlightStyle'
17-
import { CodeMirrorComponentProps, CodeMirror, CodeMirrorProps } from '@ui-schema/kit-codemirror/CodeMirror'
19+
import { CodeMirrorComponentProps } from '@ui-schema/kit-codemirror/CodeMirror'
1820
import { useExtension } from '@ui-schema/kit-codemirror/useExtension'
1921
import { MuiCodeMirrorStyleProps } from '@ui-schema/material-code'
2022

2123
export const CustomCodeMirror: React.FC<CodeMirrorComponentProps & MuiCodeMirrorStyleProps & { minHeight?: number }> = (
2224
{
23-
// values we want to override in this component
24-
value, extensions, effects,
25+
value, extensions,
2526
dense, variant,
26-
// custom prop by this `demo` package:
27+
onChange,
28+
style, classNameContent,
2729
minHeight,
28-
// everything else is just passed down
2930
...props
3031
},
3132
) => {
32-
const {onChange} = props
3333
const theme = useEditorTheme(typeof onChange === 'undefined', dense, variant)
3434
const highlightStyle = useHighlightStyle()
35-
const {init: initHighlightExt, effects: effectsHighlightExt} = useExtension(
36-
() => syntaxHighlighting(highlightStyle || defaultHighlightStyle, {fallback: true}),
37-
[highlightStyle],
38-
)
39-
const {init: initThemeExt, effects: effectsThemeExt} = useExtension(
40-
() => theme,
41-
[theme],
42-
)
43-
const effectsRef = React.useRef<((editor: EditorView) => void)[]>(effects || [])
4435

4536
const extensionsAll: Extension[] = React.useMemo(() => [
4637
lineNumbers(),
@@ -71,43 +62,50 @@ export const CustomCodeMirror: React.FC<CodeMirrorComponentProps & MuiCodeMirror
7162
...lintKeymap,
7263
indentWithTab,
7364
]),
74-
initHighlightExt(),
75-
initThemeExt(),
7665
...(extensions || []),
77-
], [extensions, initHighlightExt, initThemeExt])
78-
79-
// attach parent plugin effects first
80-
React.useMemo(() => {
81-
if(!effects) return
82-
effectsRef.current.push(...effects)
83-
}, [effects])
66+
], [extensions])
8467

85-
// attach each plugin effect separately (thus only the one which changes get reconfigured)
86-
React.useMemo(() => {
87-
if(!effectsHighlightExt) return
88-
effectsRef.current.push(...effectsHighlightExt)
89-
}, [effectsHighlightExt])
90-
React.useMemo(() => {
91-
if(!effectsThemeExt) return
92-
effectsRef.current.push(...effectsThemeExt)
93-
}, [effectsThemeExt])
68+
const containerRef = React.useRef<HTMLDivElement | null>(null)
69+
const [editorRef] = useCodeMirror({
70+
onChange,
71+
value: value || '',
72+
extensions: extensionsAll,
73+
containerRef,
74+
onViewLifecycle: React.useCallback((view) => {
75+
console.log('on-view-lifecycle', view)
76+
}, []),
77+
})
9478

95-
const onViewLifecycle: CodeMirrorProps['onViewLifecycle'] = React.useCallback((view) => {
96-
console.log('on-view-lifecycle', view)
97-
}, [])
79+
useExtension(
80+
useCallback(
81+
() => syntaxHighlighting(highlightStyle || defaultHighlightStyle, {fallback: true}),
82+
[highlightStyle],
83+
),
84+
editorRef,
85+
)
86+
useExtension(
87+
useCallback(
88+
() => theme,
89+
[theme],
90+
),
91+
editorRef,
92+
)
93+
useExtension(
94+
useCallback(() => {
95+
return Prec.lowest(EditorView.editorAttributes.of({class: classNameContent || ''}))
96+
}, [classNameContent]),
97+
editorRef,
98+
)
9899

99-
return <CodeMirror
100-
value={value || ''}
101-
extensions={extensionsAll}
102-
onViewLifecycle={onViewLifecycle}
103-
effects={effectsRef.current.splice(0, effectsRef.current.length)}
100+
return <Box
101+
ref={containerRef}
104102
{...props}
105103

106104
// use this to force any min height:
107105
style={minHeight ? {
106+
...style,
108107
display: 'flex',
109108
minHeight: minHeight,
110-
} : undefined}
111-
// className={className}
109+
} : style}
112110
/>
113111
}

packages/demo/src/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import React from 'react'
1+
import { Profiler } from 'react'
22
import { createRoot } from 'react-dom/client'
33
import { App } from './App'
44

55
createRoot(document.querySelector('#root')!)
66
.render(
7-
<React.Profiler id="App" onRender={() => null}>
7+
<Profiler id="App" onRender={() => null}>
88
<App/>
9-
</React.Profiler>,
9+
</Profiler>,
1010
)

0 commit comments

Comments
 (0)