How update extensions after creating ?

Hello marijn,
I tried to update keyAction.of to stop default events in some situations. ( useEffect )
but I can’t find an ability to do it correctly. reconfigured - remove other settings / append - just one time is adding this settings , so I cant toggle it every time when useEffect is run.
Could you give me a link or example - because i tried a lot of variants in documentation without results (
Thank you.

I don’t know what keyAction.of is, or how useEffect is related to this. I think you’re going to have to explain the problem in a little more detail.

sorry - my fault
i was saying about keymap.of
when we run EditorState.create({ extensions: [ dummyKeymap(params) ] })
we create default attributes for dummyKeymap function where we have keymap.of
and i try to understand - what i should do to change the param inside this function after creating.
the main reason for this - i want to have a single line and multiple lines of codemirror 6 .
if i change some params - i want to update dummyKeymap with keymap.of and change event of completionKeymap… because i want to save data after press on Enter in a single line mode.
i tried appendConfig but it doesn`t help. and reconfigured - it changed every settings.
https://codemirror.net/6/docs/ref/#state.StateEffect^appendConfig
https://codemirror.net/6/docs/ref/#state.StateEffect^reconfigure


 const dummyKeymap = (isOpen: boolean) =>
    keymap.of([ {
        key: 'Enter',
        preventDefault: true,
        run() { return isOpen ? defaultEvent : saveEvent },
      },
    ]);

I mean - this code is working with reconfigure all settings - but i believe that is some for updating only selective option, not all settings.
also, I need to add all previous data inside.

view.dispatch({
    effects: StateEffect.reconfigure.of(dummyKeymap(isCodeEditorOpen)),
  });

If you are trying to just update a single extension, what you probably need is Compartment. See the example on Compartment at CodeMirror Configuration Example

thank you for your answer.
i checked that example but still can`t change keymap.of if it is outside EditorState.create extension - just one way working with StateEffect.reconfigure.of( but it reconfigure all settings.
if i use let keyAction = new Compartment; outside ( because i have state outside ) i get keyAction.get(view.state); as undefined ( view is codemirror.current )

The only thing I can think of is that you are using ‘Enter’ and the default keybinding has a higher precedence than yours. If you are using ‘Enter’ key, can you try a different key say ‘ctrl-s’ or try putting your keymap extension before any other extension or set a higher precedence.

My theory is that you are using ‘Enter’ but the default keybinding has a higher precedence so your keybinding is not called. when you call reconfigure, it will remove all other extension and your keybinding now has a chance to run.

If this is not the case I am out of ideas. Perhaps you could share a small working example

BTW, here is a simple working example I created that will reconfigure the Enter key after 5 seconds to do something else.


let keyAction = new Compartment();
let saveOnEnter = keymap.of([{
  key: 'Enter',
  run: () => {
    alert('Saving...');
    return true;
  },
  preventDefault:true
}]);

let state = EditorState.create({
  doc: 'console.log("Hello ??world")', extensions: [
    keyAction.of(saveOnEnter),
    basicSetup,
    javascript(),
  ]
});

let view = new EditorView({state, parent: document.querySelector("#editor")!});

setTimeout(() => {
  let preventSaveOnEnter = keymap.of([{
    key: 'Enter',
    run: () => {
      alert('Sorry you cannot save at this moment!!');
      return true;
    },
    preventDefault: true,
  }]);
  view.dispatch({
    effects: [keyAction.reconfigure([preventSaveOnEnter])],
  })
}, 5000);

Thank you a lot for your idea.
I also tried to change position and key, but the main problem is present - you are using static data, but I need to inject data into these events.
Also, i have previously used effects, not as an array.


      console.log(isCodeEditorOpen);  // it is true or false and it is working correct..
      const preventSaveOnEnter = (isOpen: boolean) => {
        console.log(isOpen);  // it is true or false and it is working correct..

        return keymap.of([
          {
            key: 'Enter',
            run: () => {
              console.log(isOpen); // but this data is saved from the first run
              return true;
            },
            preventDefault: true,
          },
        ]);

// or tihs way -
      console.log(isOpen, 'isOpen'); // here data is changed .. 
      return isOpen
        ? keymap.of([
            {
              key: 'Enter',
              run: () => {
                console.log('true'); 
                return true;
              },
              preventDefault: true,
            },
          ])
        : keymap.of([
            {
              key: 'Enter',
              run: () => {   // but this function still using old data - the first data that was running .
                console.log('false');
                return true;
              },
              preventDefault: true,
            },
          ]);
    };
      };
      
      
      view.dispatch({
        effects: [keyAction.reconfigure([preventSaveOnEnter(isCodeEditorOpen)])],
      });

all variables are saving function stack inside function and don`t update it.

In the last example, are you dispatching the keyAction.reconfigure effect whenever the isCodeEditorOpen changes?

BTW are you using React hooks and useEffects ? If so, the below code, especially the importance of [ isCodeEditorOpen] should make immediate sense, if not that’s probably the source of the problem. in that case i recommend the following article A Complete Guide to useEffect — Overreacted

//IF YOU ARE NOT USING REACT & REACT HOOKS IGNORE THIS
useEffect(()=>{
  view.dispatch({
        effects: [keyAction.reconfigure([preventSaveOnEnter(isCodeEditorOpen)])],
      });
}, [ isCodeEditorOpen]) 

A complete complete code snippet or a codesandbox would probably be useful.

Hello - thank you for your help and example - i am using the same.
My code is a real problem inside the keymap, I also tried to create a new object and catch data…
but it saves just first init data.


  const keyAction = new Compartment();
  const dummyKeymap = (isOpen: boolean) => {
    console.log('isOpen', isOpen); // here is working correct 
    return keymap.of(
      isOpen
        ? [
            {
              key: 'Escape',
              preventDefault: true,
              run(view) {
                console.log('isEscape', isOpen);  // here is first data after init / can reload default data.
                view.contentDOM.blur();
                return true;
              },
            },
          ]
        : [
            {
              key: 'Escape',
              preventDefault: true,
              run(view) {
                console.log('isEscape', isOpen);
                view.contentDOM.blur();
                return true;
              },
            },
          ],
    );
  };

  useEffect(() => {
    let doc: string = '';

    const state = EditorState.create({
      doc,
      extensions: [
        keyAction.of(dummyKeymap(isCodeEditorOpen)),  // here can be default data. 

        defaultTheme,
        basicSetup,
        myHighlightStyle,
        underlineKeymap,
        CLearLines(),
        // deleted ability to select a lot of positions with ctrl + click
        EditorView.clickAddsSelectionRange.of(() => false),
        EditorState.allowMultipleSelections.of(true),

        autocompletion({
          override: [myFunction, widgetAutocomplete],
          maxRenderedOptions: 8,
          defaultKeymap: true,
        }),
      ],
    });
    console.log('run code mirror6');
    const codemirror: EditorView = new EditorView({state, parent: editorElement.current});
    codeMirror.current = codemirror;

    const setMouseClick = (e: MouseEvent) => mouseClick(e, codemirror);
    editorElement.current?.addEventListener('click', setMouseClick);

    return () => {
      codemirror.destroy();
      editorElement.current?.removeEventListener('click', setMouseClick);
    };
  }, [activeWidget?.id]);

  useEffect(() => {
    const view = codeMirror.current;
    view.dispatch({
      effects: keyAction.reconfigure(dummyKeymap(isCodeEditorOpen)),
    });
  }, [isCodeEditorOpen]);

It seems that you have the keyAction comparment inside a React function (**I couldn’t be 100% sure from the code since it is incomplete. **), and since that function is called on every render you get a new instance of a compartment. Can you check if this is the case.

For example in the following code, if you click on the editor, the isOpen changes and you get the desired result. Try placing the new Comparment code inside and you will see this won’t work.

import {useEffect, useRef, useState} from "react";
import {basicSetup, EditorState, EditorView} from "@codemirror/basic-setup"
import {Compartment} from "@codemirror/state";
import {keymap} from "@codemirror/view";

const keyAction = new Compartment();

export const CodemirrorEditor=()=>{
  const ref = useRef<HTMLDivElement>(null);
  const viewRef = useRef<EditorView>();
  const [isOpen, setOpen] = useState(false);

  const dummyKeymap = (isOpen: boolean) => {
    return keymap.of(
      isOpen ? [{
            key: 'Escape',
            preventDefault: true,
            run(view) {
              console.log('Keymap1. Escape Key pressed ', isOpen);  // here is first data after init / can reload default data.
              view.contentDOM.blur();
              return true;
            },
          },
        ]
        : [{
            key: 'Escape',
            preventDefault: true,
            run(view) {
              console.log('Keymap2. Escape key Pressed', isOpen);
              view.contentDOM.blur();
              return true;
            },
          },
        ],
    );
  };

  useEffect(() => {
    viewRef.current = new EditorView({
      state: EditorState.create({
        doc: 'hello world',
        extensions: [
          basicSetup,
          keyAction.of(dummyKeymap(isOpen)),
        ]
      }),
      parent: ref.current!,
    });
  }, []);

  useEffect(() => {
    console.log(`isOpen changed to ${isOpen}. Dispatching reconfigure of keyAction compartment`);
    viewRef.current?.dispatch({effects: keyAction.reconfigure(dummyKeymap(isOpen))});
  }, [isOpen]);

  return <div ref={ref} style={{width: '100vw', height: '100vh'}} onClick={() => setOpen(o => !o)}/>
}

1 Like

You are right. i missed it and now i am feeling like shit…
thank you a lot for your attentiveness!

Thank you very much

Hello, I would like to ask how I can dynamically update the table name of the prompt?

{
        schema: {
          test: ['test'],
        },
        tables: [
          {
            label: 'si_db_table_attr',
            apply: 'si_db_table_attr',
          },
          {
            label: 'si_sv_service_sql',
            apply: 'si_sv_service_sql',
          },
        ],
      }
<template>
  <div class="sql-model" ref="codeRef"></div>
</template>

<script lang="ts" setup>
// "@codemirror/commands": "^6.0.0",
// "@codemirror/lang-json": "^6.0.0",
// "@codemirror/lang-sql": "^6.0.0",
// "@codemirror/state": "^6.0.0",
// "@codemirror/theme-one-dark": "^6.0.0",
// "@codemirror/view": "^6.0.0",
import { onMounted, ref } from 'vue';
import { EditorView } from '@codemirror/view';
import { basicSetup } from 'codemirror';
import { sql } from '@codemirror/lang-sql';
import { EditorState } from '@codemirror/state';

const codeRef = ref<HTMLElement>();
const view = ref<EditorView>();
const doc = ref<string>('');

onMounted(() => {
  // 自定义主题
  const theme = EditorView.theme({
    '&': {
      fontSize: '12.5pt',
      border: '1px solid #c0c0c0',
    },
    '.cm-content': {
      fontFamily: 'Lucida Console, Menlo, Monaco,  monospace',
      minHeight: '200px',
    },
    '.cm-gutters': {
      minHeight: '200px',
    },
    '.cm-scroller': {
      overflow: 'auto',
      maxHeight: '380px',
    },
  });

  const state = EditorState.create({
    doc: doc.value,
    extensions: [
      basicSetup,
      sql({
        schema: {
          test: ['test'],
        },
        tables: [
          {
            label: 'si_db_table_attr',
            apply: 'si_db_table_attr',
          },
          {
            label: 'si_sv_service_sql',
            apply: 'si_sv_service_sql',
          },
        ],
      }),
      theme,
    ],
  });

  view.value = new EditorView({
    state,
    parent: codeRef.value,
  });
});
</script>

<style scoped lang="scss">
.sql-model {
  height: 300px;
  width: 100%;
  border-top-right-radius: 4px;
  border-top-left-radius: 4px;

  :deep(.cm-editor) {
    height: 100%;

    &.cm-focused .cm-selectionBackground,
    &.cm-selectionBackground {
      background: #439AF15F;
    }
  }

  :deep(.cm-scroller) {
    overflow: auto;
  }
}
</style>