11import React , { useMemo , useRef } from "react" ;
22import { defaultKeymap , indentWithTab } from "@codemirror/commands" ;
33import { defaultHighlightStyle , 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
@@ -31,6 +31,7 @@ import {
3131 adaptedLineNumbers ,
3232 adaptedLintGutter ,
3333 adaptedPlaceholder ,
34+ compartment ,
3435 adaptedSyntaxHighlighting ,
3536} from "./tests/codemirrorTestHelper" ;
3637import { ExtensionCreator } from "./types" ;
@@ -55,7 +56,7 @@ export interface CodeEditorProps extends TestableComponent {
5556 * Handler method to receive onChange events.
5657 * As input the new value is given.
5758 */
58- onChange ?: ( v : any ) => void ;
59+ onChange ?: ( v : string ) => void ;
5960 /**
6061 * Called when the focus status changes
6162 */
@@ -75,7 +76,7 @@ export interface CodeEditorProps extends TestableComponent {
7576 /**
7677 * Called when the cursor position changes
7778 */
78- onCursorChange ?: ( pos : number , coords : Rect , scrollinfo : HTMLElement , cm : EditorView ) => any ;
79+ onCursorChange ?: ( pos : number , coords : Rect , scrollinfo : HTMLElement , cm : EditorView ) => void ;
7980
8081 /**
8182 * Syntax mode of the code editor.
@@ -84,7 +85,7 @@ export interface CodeEditorProps extends TestableComponent {
8485 /**
8586 * Default value used first when the editor is instanciated.
8687 */
87- defaultValue ?: any ;
88+ defaultValue ?: string ;
8889 /**
8990 * If enabled the code editor won't show numbers before each line.
9091 */
@@ -170,7 +171,7 @@ export interface CodeEditorProps extends TestableComponent {
170171}
171172
172173const addExtensionsFor = ( flag : boolean , ...extensions : Extension [ ] ) => ( flag ? [ ...extensions ] : [ ] ) ;
173- const addToKeyMapConfigFor = ( flag : boolean , ...keys : any ) => ( flag ? [ ...keys ] : [ ] ) ;
174+ const addToKeyMapConfigFor = ( flag : boolean , ...keys : KeyBinding [ ] ) => ( flag ? [ ...keys ] : [ ] ) ;
174175const addHandlersFor = ( flag : boolean , handlerName : string , handler : any ) =>
175176 flag ? ( { [ handlerName ] : handler } as DOMEventHandlers < any > ) : { } ;
176177
@@ -222,7 +223,24 @@ export const CodeEditor = ({
222223} : CodeEditorProps ) => {
223224 const parent = useRef < any > ( undefined ) ;
224225 const [ view , setView ] = React . useState < EditorView | undefined > ( ) ;
226+ const currentView = React . useRef < EditorView > ( )
227+ currentView . current = view
228+ const currentReadOnly = React . useRef ( readOnly )
229+ currentReadOnly . current = readOnly
225230 const [ showPreview , setShowPreview ] = React . useState < boolean > ( false ) ;
231+ // CodeMirror Compartments in order to allow for re-configuration after initialization
232+ const readOnlyCompartment = React . useRef < Compartment > ( compartment ( ) )
233+ const wrapLinesCompartment = React . useRef < Compartment > ( compartment ( ) )
234+ const preventLineNumbersCompartment = React . useRef < Compartment > ( compartment ( ) )
235+ const shouldHaveMinimalSetupCompartment = React . useRef < Compartment > ( compartment ( ) )
236+ const placeholderCompartment = React . useRef < Compartment > ( compartment ( ) )
237+ const modeCompartment = React . useRef < Compartment > ( compartment ( ) )
238+ const keyMapConfigsCompartment = React . useRef < Compartment > ( compartment ( ) )
239+ const tabIntentSizeCompartment = React . useRef < Compartment > ( compartment ( ) )
240+ const disabledCompartment = React . useRef < Compartment > ( compartment ( ) )
241+ const supportCodeFoldingCompartment = React . useRef < Compartment > ( compartment ( ) )
242+ const useLintingCompartment = React . useRef < Compartment > ( compartment ( ) )
243+ const shouldHighlightActiveLineCompartment = React . useRef < Compartment > ( compartment ( ) )
226244
227245 const linters = useMemo ( ( ) => {
228246 if ( ! mode ) {
@@ -241,17 +259,15 @@ export const CodeEditor = ({
241259
242260 const onKeyDownHandler = ( event : KeyboardEvent , view : EditorView ) => {
243261 if ( onKeyDown && ! onKeyDown ( event ) ) {
244- if ( event . key === "Enter" ) {
262+ if ( event . key === "Enter" && ! currentReadOnly . current ) {
245263 const cursor = view . state . selection . main . head ;
246- const cursorLine = view . state . doc . lineAt ( cursor ) . number ;
247- const offsetFromFirstLine = view . state . doc . line ( cursorLine ) . to ;
248264 view . dispatch ( {
249265 changes : {
250- from : offsetFromFirstLine ,
266+ from : cursor ,
251267 insert : "\n" ,
252268 } ,
253269 selection : {
254- anchor : offsetFromFirstLine + 1 ,
270+ anchor : cursor + 1 ,
255271 } ,
256272 } ) ;
257273 }
@@ -266,14 +282,17 @@ export const CodeEditor = ({
266282 return false ;
267283 } ;
268284
269- React . useEffect ( ( ) => {
285+ const createKeyMapConfigs = ( ) => {
270286 const tabIndent =
271287 ! ! ( tabIntentStyle === "tab" && mode && ! ( tabForceSpaceForModes ?? [ ] ) . includes ( mode ) ) || enableTab ;
272- const keyMapConfigs = [
288+ return [
273289 defaultKeymap as KeyBinding ,
274- ...addToKeyMapConfigFor ( supportCodeFolding , foldKeymap ) ,
290+ ...addToKeyMapConfigFor ( supportCodeFolding , ... foldKeymap ) ,
275291 ...addToKeyMapConfigFor ( tabIndent , indentWithTab ) ,
276292 ] ;
293+ }
294+
295+ React . useEffect ( ( ) => {
277296 const domEventHandlers = {
278297 ...addHandlersFor ( ! ! onScroll , "scroll" , onScroll ) ,
279298 ...addHandlersFor (
@@ -287,13 +306,13 @@ export const CodeEditor = ({
287306 } as DOMEventHandlers < any > ;
288307 const extensions = [
289308 markField ,
290- adaptedPlaceholder ( placeholder ) ,
309+ placeholderCompartment . current . of ( adaptedPlaceholder ( placeholder ) ) ,
291310 adaptedHighlightSpecialChars ( ) ,
292- useCodeMirrorModeExtension ( mode ) ,
293- keymap ?. of ( keyMapConfigs ) ,
294- EditorState ?. tabSize . of ( tabIntentSize ) ,
295- EditorState ?. readOnly . of ( readOnly ) ,
296- EditorView ?. editable . of ( ! disabled ) ,
311+ modeCompartment . current . of ( useCodeMirrorModeExtension ( mode ) ) ,
312+ keyMapConfigsCompartment . current . of ( keymap ?. of ( createKeyMapConfigs ( ) ) ) ,
313+ tabIntentSizeCompartment . current . of ( EditorState ?. tabSize . of ( tabIntentSize ) ) ,
314+ readOnlyCompartment . current . of ( EditorState ?. readOnly . of ( readOnly ) ) ,
315+ disabledCompartment . current . of ( EditorView ?. editable . of ( ! disabled ) ) ,
297316 AdaptedEditorViewDomEventHandlers ( domEventHandlers ) as Extension ,
298317 EditorView ?. updateListener . of ( ( v : ViewUpdate ) => {
299318 if ( disabled ) return ;
@@ -329,12 +348,12 @@ export const CodeEditor = ({
329348 }
330349 }
331350 } ) ,
332- addExtensionsFor ( shouldHaveMinimalSetup , minimalSetup ) ,
333- addExtensionsFor ( ! preventLineNumbers , adaptedLineNumbers ( ) ) ,
334- addExtensionsFor ( shouldHighlightActiveLine , adaptedHighlightActiveLine ( ) ) ,
335- addExtensionsFor ( wrapLines , EditorView ?. lineWrapping ) ,
336- addExtensionsFor ( supportCodeFolding , adaptedFoldGutter ( ) , adaptedCodeFolding ( ) ) ,
337- addExtensionsFor ( useLinting , ...linters ) ,
351+ shouldHaveMinimalSetupCompartment . current . of ( addExtensionsFor ( shouldHaveMinimalSetup , minimalSetup ) ) ,
352+ preventLineNumbersCompartment . current . of ( addExtensionsFor ( ! preventLineNumbers , adaptedLineNumbers ( ) ) ) ,
353+ shouldHighlightActiveLineCompartment . current . of ( addExtensionsFor ( shouldHighlightActiveLine , adaptedHighlightActiveLine ( ) ) ) ,
354+ wrapLinesCompartment . current . of ( addExtensionsFor ( wrapLines , EditorView ?. lineWrapping ) ) ,
355+ supportCodeFoldingCompartment . current . of ( addExtensionsFor ( supportCodeFolding , adaptedFoldGutter ( ) , adaptedCodeFolding ( ) ) ) ,
356+ useLintingCompartment . current . of ( addExtensionsFor ( useLinting , ...linters ) ) ,
338357 adaptedSyntaxHighlighting ( defaultHighlightStyle ) ,
339358 additionalExtensions ,
340359 ] ;
@@ -377,7 +396,64 @@ export const CodeEditor = ({
377396 setView ( undefined ) ;
378397 }
379398 } ;
380- } , [ parent . current , mode , preventLineNumbers , wrapLines ] ) ;
399+ } , [ parent . current ] ) ;
400+
401+ // Updates an extension for a specific parameter that has changed after the initialization
402+ const updateExtension = ( extension : Extension | undefined , parameterCompartment : Compartment ) : void => {
403+ if ( extension ) {
404+ currentView . current ?. dispatch ( {
405+ effects : parameterCompartment . reconfigure ( extension )
406+ } )
407+ }
408+ }
409+
410+ React . useEffect ( ( ) => {
411+ updateExtension ( EditorState ?. readOnly . of ( readOnly ! ) , readOnlyCompartment . current )
412+ } , [ readOnly ] )
413+
414+ React . useEffect ( ( ) => {
415+ updateExtension ( adaptedPlaceholder ( placeholder ) , placeholderCompartment . current )
416+ } , [ placeholder ] )
417+
418+ React . useEffect ( ( ) => {
419+ updateExtension ( useCodeMirrorModeExtension ( mode ) , modeCompartment . current )
420+ } , [ mode ] )
421+
422+ React . useEffect ( ( ) => {
423+ updateExtension ( keymap ?. of ( createKeyMapConfigs ( ) ) , keyMapConfigsCompartment . current )
424+ } , [ supportCodeFolding , mode , tabIntentStyle , ( tabForceSpaceForModes ?? [ ] ) . join ( ", " ) , enableTab ] )
425+
426+ React . useEffect ( ( ) => {
427+ updateExtension ( EditorState ?. tabSize . of ( tabIntentSize ?? 2 ) , tabIntentSizeCompartment . current )
428+ } , [ tabIntentSize ] )
429+
430+ React . useEffect ( ( ) => {
431+ updateExtension ( EditorView ?. editable . of ( ! disabled ) , disabledCompartment . current )
432+ } , [ disabled ] )
433+
434+ React . useEffect ( ( ) => {
435+ updateExtension ( addExtensionsFor ( shouldHaveMinimalSetup ?? true , minimalSetup ) , shouldHaveMinimalSetupCompartment . current )
436+ } , [ shouldHaveMinimalSetup ] )
437+
438+ React . useEffect ( ( ) => {
439+ updateExtension ( addExtensionsFor ( ! preventLineNumbers , adaptedLineNumbers ( ) ) , preventLineNumbersCompartment . current )
440+ } , [ preventLineNumbers ] )
441+
442+ React . useEffect ( ( ) => {
443+ updateExtension ( addExtensionsFor ( shouldHighlightActiveLine ?? false , adaptedHighlightActiveLine ( ) ) , shouldHighlightActiveLineCompartment . current )
444+ } , [ shouldHighlightActiveLine ] )
445+
446+ React . useEffect ( ( ) => {
447+ updateExtension ( addExtensionsFor ( wrapLines ?? false , EditorView ?. lineWrapping ) , wrapLinesCompartment . current )
448+ } , [ wrapLines ] )
449+
450+ React . useEffect ( ( ) => {
451+ updateExtension ( addExtensionsFor ( supportCodeFolding ?? false , adaptedFoldGutter ( ) , adaptedCodeFolding ( ) ) , supportCodeFoldingCompartment . current )
452+ } , [ supportCodeFolding ] )
453+
454+ React . useEffect ( ( ) => {
455+ updateExtension ( addExtensionsFor ( useLinting ?? false , ...linters ) , useLintingCompartment . current )
456+ } , [ mode , useLinting ] )
381457
382458 const hasToolbarSupport = mode && ModeToolbarSupport . indexOf ( mode ) > - 1 && useToolbar ;
383459
0 commit comments