11import React , { useMemo , useRef } from "react" ;
22import { defaultKeymap , indentWithTab } from "@codemirror/commands" ;
33import { foldKeymap } from "@codemirror/language" ;
4- import { EditorState , Extension } from "@codemirror/state" ;
4+ import { EditorState , Extension , Compartment } from "@codemirror/state" ;
55import { DOMEventHandlers , EditorView , KeyBinding , keymap , Rect , ViewUpdate } from "@codemirror/view" ;
66import { minimalSetup } from "codemirror" ;
77
@@ -30,7 +30,7 @@ import {
3030 adaptedHighlightSpecialChars ,
3131 adaptedLineNumbers ,
3232 adaptedLintGutter ,
33- adaptedPlaceholder ,
33+ adaptedPlaceholder , compartment ,
3434} from "./tests/codemirrorTestHelper" ;
3535import { ExtensionCreator } from "./types" ;
3636
@@ -53,6 +53,7 @@ export interface CodeEditorProps extends TestableComponent {
5353 /**
5454 * Handler method to receive onChange events.
5555 * As input the new value is given.
56+ * @deprecated (v25) use `(v: string) => void` in future
5657 */
5758 onChange ?: ( v : any ) => void ;
5859 /**
@@ -74,7 +75,7 @@ export interface CodeEditorProps extends TestableComponent {
7475 /**
7576 * Called when the cursor position changes
7677 */
77- onCursorChange ?: ( pos : number , coords : Rect , scrollinfo : HTMLElement , cm : EditorView ) => any ;
78+ onCursorChange ?: ( pos : number , coords : Rect , scrollinfo : HTMLElement , cm : EditorView ) => void ;
7879
7980 /**
8081 * Syntax mode of the code editor.
@@ -83,7 +84,7 @@ export interface CodeEditorProps extends TestableComponent {
8384 /**
8485 * Default value used first when the editor is instanciated.
8586 */
86- defaultValue ?: any ;
87+ defaultValue ?: string ;
8788 /**
8889 * If enabled the code editor won't show numbers before each line.
8990 */
@@ -169,7 +170,7 @@ export interface CodeEditorProps extends TestableComponent {
169170}
170171
171172const addExtensionsFor = ( flag : boolean , ...extensions : Extension [ ] ) => ( flag ? [ ...extensions ] : [ ] ) ;
172- const addToKeyMapConfigFor = ( flag : boolean , ...keys : any ) => ( flag ? [ ...keys ] : [ ] ) ;
173+ const addToKeyMapConfigFor = ( flag : boolean , ...keys : KeyBinding [ ] ) => ( flag ? [ ...keys ] : [ ] ) ;
173174const addHandlersFor = ( flag : boolean , handlerName : string , handler : any ) =>
174175 flag ? ( { [ handlerName ] : handler } as DOMEventHandlers < any > ) : { } ;
175176
@@ -221,7 +222,24 @@ export const CodeEditor = ({
221222} : CodeEditorProps ) => {
222223 const parent = useRef < any > ( undefined ) ;
223224 const [ view , setView ] = React . useState < EditorView | undefined > ( ) ;
225+ const currentView = React . useRef < EditorView > ( )
226+ currentView . current = view
227+ const currentReadOnly = React . useRef ( readOnly )
228+ currentReadOnly . current = readOnly
224229 const [ showPreview , setShowPreview ] = React . useState < boolean > ( false ) ;
230+ // CodeMirror Compartments in order to allow for re-configuration after initialization
231+ const readOnlyCompartment = React . useRef < Compartment > ( compartment ( ) )
232+ const wrapLinesCompartment = React . useRef < Compartment > ( compartment ( ) )
233+ const preventLineNumbersCompartment = React . useRef < Compartment > ( compartment ( ) )
234+ const shouldHaveMinimalSetupCompartment = React . useRef < Compartment > ( compartment ( ) )
235+ const placeholderCompartment = React . useRef < Compartment > ( compartment ( ) )
236+ const modeCompartment = React . useRef < Compartment > ( compartment ( ) )
237+ const keyMapConfigsCompartment = React . useRef < Compartment > ( compartment ( ) )
238+ const tabIntentSizeCompartment = React . useRef < Compartment > ( compartment ( ) )
239+ const disabledCompartment = React . useRef < Compartment > ( compartment ( ) )
240+ const supportCodeFoldingCompartment = React . useRef < Compartment > ( compartment ( ) )
241+ const useLintingCompartment = React . useRef < Compartment > ( compartment ( ) )
242+ const shouldHighlightActiveLineCompartment = React . useRef < Compartment > ( compartment ( ) )
225243
226244 const linters = useMemo ( ( ) => {
227245 if ( ! mode ) {
@@ -240,17 +258,15 @@ export const CodeEditor = ({
240258
241259 const onKeyDownHandler = ( event : KeyboardEvent , view : EditorView ) => {
242260 if ( onKeyDown && ! onKeyDown ( event ) ) {
243- if ( event . key === "Enter" ) {
261+ if ( event . key === "Enter" && ! currentReadOnly . current ) {
244262 const cursor = view . state . selection . main . head ;
245- const cursorLine = view . state . doc . lineAt ( cursor ) . number ;
246- const offsetFromFirstLine = view . state . doc . line ( cursorLine ) . to ;
247263 view . dispatch ( {
248264 changes : {
249- from : offsetFromFirstLine ,
265+ from : cursor ,
250266 insert : "\n" ,
251267 } ,
252268 selection : {
253- anchor : offsetFromFirstLine + 1 ,
269+ anchor : cursor + 1 ,
254270 } ,
255271 } ) ;
256272 }
@@ -265,14 +281,17 @@ export const CodeEditor = ({
265281 return false ;
266282 } ;
267283
268- React . useEffect ( ( ) => {
284+ const createKeyMapConfigs = ( ) => {
269285 const tabIndent =
270286 ! ! ( tabIntentStyle === "tab" && mode && ! ( tabForceSpaceForModes ?? [ ] ) . includes ( mode ) ) || enableTab ;
271- const keyMapConfigs = [
287+ return [
272288 defaultKeymap as KeyBinding ,
273- ...addToKeyMapConfigFor ( supportCodeFolding , foldKeymap ) ,
289+ ...addToKeyMapConfigFor ( supportCodeFolding , ... foldKeymap ) ,
274290 ...addToKeyMapConfigFor ( tabIndent , indentWithTab ) ,
275291 ] ;
292+ }
293+
294+ React . useEffect ( ( ) => {
276295 const domEventHandlers = {
277296 ...addHandlersFor ( ! ! onScroll , "scroll" , onScroll ) ,
278297 ...addHandlersFor (
@@ -286,13 +305,13 @@ export const CodeEditor = ({
286305 } as DOMEventHandlers < any > ;
287306 const extensions = [
288307 markField ,
289- adaptedPlaceholder ( placeholder ) ,
308+ placeholderCompartment . current . of ( adaptedPlaceholder ( placeholder ) ) ,
290309 adaptedHighlightSpecialChars ( ) ,
291- useCodeMirrorModeExtension ( mode ) ,
292- keymap ?. of ( keyMapConfigs ) ,
293- EditorState ?. tabSize . of ( tabIntentSize ) ,
294- EditorState ?. readOnly . of ( readOnly ) ,
295- EditorView ?. editable . of ( ! disabled ) ,
310+ modeCompartment . current . of ( useCodeMirrorModeExtension ( mode ) ) ,
311+ keyMapConfigsCompartment . current . of ( keymap ?. of ( createKeyMapConfigs ( ) ) ) ,
312+ tabIntentSizeCompartment . current . of ( EditorState ?. tabSize . of ( tabIntentSize ) ) ,
313+ readOnlyCompartment . current . of ( EditorState ?. readOnly . of ( readOnly ) ) ,
314+ disabledCompartment . current . of ( EditorView ?. editable . of ( ! disabled ) ) ,
296315 AdaptedEditorViewDomEventHandlers ( domEventHandlers ) as Extension ,
297316 EditorView ?. updateListener . of ( ( v : ViewUpdate ) => {
298317 if ( disabled ) return ;
@@ -328,12 +347,12 @@ export const CodeEditor = ({
328347 }
329348 }
330349 } ) ,
331- addExtensionsFor ( shouldHaveMinimalSetup , minimalSetup ) ,
332- addExtensionsFor ( ! preventLineNumbers , adaptedLineNumbers ( ) ) ,
333- addExtensionsFor ( shouldHighlightActiveLine , adaptedHighlightActiveLine ( ) ) ,
334- addExtensionsFor ( wrapLines , EditorView ?. lineWrapping ) ,
335- addExtensionsFor ( supportCodeFolding , adaptedFoldGutter ( ) , adaptedCodeFolding ( ) ) ,
336- addExtensionsFor ( useLinting , ...linters ) ,
350+ shouldHaveMinimalSetupCompartment . current . of ( addExtensionsFor ( shouldHaveMinimalSetup , minimalSetup ) ) ,
351+ preventLineNumbersCompartment . current . of ( addExtensionsFor ( ! preventLineNumbers , adaptedLineNumbers ( ) ) ) ,
352+ shouldHighlightActiveLineCompartment . current . of ( addExtensionsFor ( shouldHighlightActiveLine , adaptedHighlightActiveLine ( ) ) ) ,
353+ wrapLinesCompartment . current . of ( addExtensionsFor ( wrapLines , EditorView ?. lineWrapping ) ) ,
354+ supportCodeFoldingCompartment . current . of ( addExtensionsFor ( supportCodeFolding , adaptedFoldGutter ( ) , adaptedCodeFolding ( ) ) ) ,
355+ useLintingCompartment . current . of ( addExtensionsFor ( useLinting , ...linters ) ) ,
337356 additionalExtensions ,
338357 ] ;
339358
@@ -375,7 +394,64 @@ export const CodeEditor = ({
375394 setView ( undefined ) ;
376395 }
377396 } ;
378- } , [ parent . current , mode , preventLineNumbers , wrapLines ] ) ;
397+ } , [ parent . current ] ) ;
398+
399+ // Updates an extension for a specific parameter that has changed after the initialization
400+ const updateExtension = ( extension : Extension | undefined , parameterCompartment : Compartment ) : void => {
401+ if ( extension ) {
402+ currentView . current ?. dispatch ( {
403+ effects : parameterCompartment . reconfigure ( extension )
404+ } )
405+ }
406+ }
407+
408+ React . useEffect ( ( ) => {
409+ updateExtension ( EditorState ?. readOnly . of ( readOnly ! ) , readOnlyCompartment . current )
410+ } , [ readOnly ] )
411+
412+ React . useEffect ( ( ) => {
413+ updateExtension ( adaptedPlaceholder ( placeholder ) , placeholderCompartment . current )
414+ } , [ placeholder ] )
415+
416+ React . useEffect ( ( ) => {
417+ updateExtension ( useCodeMirrorModeExtension ( mode ) , modeCompartment . current )
418+ } , [ mode ] )
419+
420+ React . useEffect ( ( ) => {
421+ updateExtension ( keymap ?. of ( createKeyMapConfigs ( ) ) , keyMapConfigsCompartment . current )
422+ } , [ supportCodeFolding , mode , tabIntentStyle , ( tabForceSpaceForModes ?? [ ] ) . join ( ", " ) , enableTab ] )
423+
424+ React . useEffect ( ( ) => {
425+ updateExtension ( EditorState ?. tabSize . of ( tabIntentSize ?? 2 ) , tabIntentSizeCompartment . current )
426+ } , [ tabIntentSize ] )
427+
428+ React . useEffect ( ( ) => {
429+ updateExtension ( EditorView ?. editable . of ( ! disabled ) , disabledCompartment . current )
430+ } , [ disabled ] )
431+
432+ React . useEffect ( ( ) => {
433+ updateExtension ( addExtensionsFor ( shouldHaveMinimalSetup ?? true , minimalSetup ) , shouldHaveMinimalSetupCompartment . current )
434+ } , [ shouldHaveMinimalSetup ] )
435+
436+ React . useEffect ( ( ) => {
437+ updateExtension ( addExtensionsFor ( ! preventLineNumbers , adaptedLineNumbers ( ) ) , preventLineNumbersCompartment . current )
438+ } , [ preventLineNumbers ] )
439+
440+ React . useEffect ( ( ) => {
441+ updateExtension ( addExtensionsFor ( shouldHighlightActiveLine ?? false , adaptedHighlightActiveLine ( ) ) , shouldHighlightActiveLineCompartment . current )
442+ } , [ shouldHighlightActiveLine ] )
443+
444+ React . useEffect ( ( ) => {
445+ updateExtension ( addExtensionsFor ( wrapLines ?? false , EditorView ?. lineWrapping ) , wrapLinesCompartment . current )
446+ } , [ wrapLines ] )
447+
448+ React . useEffect ( ( ) => {
449+ updateExtension ( addExtensionsFor ( supportCodeFolding ?? false , adaptedFoldGutter ( ) , adaptedCodeFolding ( ) ) , supportCodeFoldingCompartment . current )
450+ } , [ supportCodeFolding ] )
451+
452+ React . useEffect ( ( ) => {
453+ updateExtension ( addExtensionsFor ( useLinting ?? false , ...linters ) , useLintingCompartment . current )
454+ } , [ mode , useLinting ] )
379455
380456 const hasToolbarSupport = mode && ModeToolbarSupport . indexOf ( mode ) > - 1 && useToolbar ;
381457
0 commit comments