logue / vue-codemirror6

⌨️ @codemirror 6 component for @vuejs. Vue2 & Vue3 both supported.
https://logue.dev/vue-codemirror6/
MIT License
123 stars 16 forks source link

Implicit autocompletion not working #12

Open richardjharris opened 1 year ago

richardjharris commented 1 year ago

If you visit https://codemirror.net/try/ and type this in

import {basicSetup, EditorView} from "codemirror";
import {autocompletion, completeFromList} from "@codemirror/autocomplete";

function findAutoWords(context) {
  console.log("autocomplete called");
  const word = context.matchBefore(/#\w*/);
  if (word == null) return null;
  if (word.from == word.to && !context.explicit) return null;
  return {
    from: word.from,
    options: [{ label: "#match" }, { label: "#hello" }, { label: "#magic" }],
    // If user pressed Ctrl+Space, return all possible tags rather than those matching
    // the typed prefix.
    filter: !context.explicit,
  };
}

new EditorView({
  doc: "console.log('hello')\n",
  extensions: [autocompletion({
    icons: false,
    override: [findAutoWords],
  })],
  parent: document.body
})

... autocompletion automatically triggers when # is typed.

I tried to create the same code using vue-codemirror6 and the autocompletion only works when Ctrl+Space is pressed, e.g.

https://playcode.io/1085925

<script setup lang="ts">
import { autocompletion, CompletionContext } from "@codemirror/autocomplete";
import type { Extension } from "@codemirror/state";
import CodeMirror from "vue-codemirror6";
import { ref, watch } from "vue";

const diaryText = ref("");

function diaryAutocomplete(context: CompletionContext) {
  console.log("diaryAutocomplete called");
  const word = context.matchBefore(/#\w*/);
  if (word == null) return null;
  if (word.from == word.to && !context.explicit) return null;
  return {
    from: word.from,
    options: [{ label: "#match" }, { label: "#hello" }, { label: "#magic" }],
    // If user pressed Ctrl+Space, return all possible tags rather than those matching
    // the typed prefix.
    filter: !context.explicit,
  };
}

const extensions: Extension[] = [
  autocompletion({
    icons: false,
    override: [diaryAutocomplete],
  }),
];
</script>

<template>
  <div class="diary-editor">
    <code-mirror v-model="diaryText" :extensions="extensions" />
  </div>
</template>

<style>
/* start 150px and grow as user types */
.cm-content,
.cm-gutter {
  min-height: 150px;
}

.cm-gutters {
  margin: 1px;
}

.cm-scroller {
  overflow: auto;
}

.cm-wrap {
  border: 1px solid silver;
}

.diary-editor {
  border: 1px solid #cbd5e0;
  border-radius: 0.5rem;
}
</style>
logue commented 1 year ago

The possible cause at the moment is because the implementation is to register the extension by the method described in the address below.

https://codemirror.net/examples/config/

StateEffect.reconfigure.of(extensions)

With this method, it seems that some states and other values cannot be reflected, so there may be some ingenuity.

https://discuss.codemirror.net/t/how-update-extensions-after-creating/4064

logue commented 1 year ago

Although it is palliative, please try to reload the extension by executing the forceReconfigure() function that has been available since 1.19.

g992 commented 1 year ago

Hi, how can i trigger showing autocomplete after every keypress, not only after ctrl + space?

logue commented 1 year ago

According to the CodeMirror specifications, there are patterns that do not work unless the extension is registered at initialization, and patterns that work even if the extension is registered later with the reconfigure instruction, so it is difficult to isolate the problem.

In vue-codemirror6, I think that this problem occurred because it was processed with reconfigure at once.

As a temporary measure, I prepared the forceReconfigure() method. This is the process of canceling all registered extensions and then registering them.

It's a rough implementation, but please try to execute the forceReconfigure function on the component when the extensions are updated as shown below.

<script setup>
import { watch } from 'vue';
import CodeMirror from 'vue-codemirror6";

const editor = ref();

// some function

watch(
  () => extnetions.value, 
  () => editor.value?.forceReconfigure()); 
);
</script>

<template>
  <codemirror v-model="value" ref="editor" />
</template>