feat: implement base autocomplete implementation for codemirror along with preRequest autocompletion
This commit is contained in:
@@ -8,6 +8,7 @@ import "codemirror/mode/javascript/javascript"
|
|||||||
import { ref, watch } from "@nuxtjs/composition-api"
|
import { ref, watch } from "@nuxtjs/composition-api"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
import { LinterDefinition } from "~/helpers/editor/linting/linter"
|
import { LinterDefinition } from "~/helpers/editor/linting/linter"
|
||||||
|
import { Completer } from "~/helpers/editor/completion"
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -16,11 +17,13 @@ const props = withDefaults(
|
|||||||
placeholder?: string
|
placeholder?: string
|
||||||
wrap?: boolean
|
wrap?: boolean
|
||||||
linter: LinterDefinition | null
|
linter: LinterDefinition | null
|
||||||
|
completer: Completer | null
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
wrap: true,
|
wrap: true,
|
||||||
linter: null as any,
|
linter: null as any,
|
||||||
|
completer: null as any,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,5 +48,6 @@ useCodemirror(editor, value, {
|
|||||||
lineWrapping: props.wrap,
|
lineWrapping: props.wrap,
|
||||||
},
|
},
|
||||||
linter: props.linter,
|
linter: props.linter,
|
||||||
|
completer: props.completer,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import "codemirror/theme/3024-night.css"
|
|||||||
import "codemirror/lib/codemirror.css"
|
import "codemirror/lib/codemirror.css"
|
||||||
import "codemirror/addon/lint/lint.css"
|
import "codemirror/addon/lint/lint.css"
|
||||||
import "codemirror/addon/dialog/dialog.css"
|
import "codemirror/addon/dialog/dialog.css"
|
||||||
|
import "codemirror/addon/hint/show-hint.css"
|
||||||
|
|
||||||
import "codemirror/addon/fold/foldgutter.css"
|
import "codemirror/addon/fold/foldgutter.css"
|
||||||
import "codemirror/addon/fold/foldgutter"
|
import "codemirror/addon/fold/foldgutter"
|
||||||
@@ -15,6 +16,7 @@ import "codemirror/addon/fold/comment-fold"
|
|||||||
import "codemirror/addon/fold/indent-fold"
|
import "codemirror/addon/fold/indent-fold"
|
||||||
import "codemirror/addon/display/autorefresh"
|
import "codemirror/addon/display/autorefresh"
|
||||||
import "codemirror/addon/lint/lint"
|
import "codemirror/addon/lint/lint"
|
||||||
|
import "codemirror/addon/hint/show-hint"
|
||||||
import "codemirror/addon/display/placeholder"
|
import "codemirror/addon/display/placeholder"
|
||||||
import "codemirror/addon/edit/closebrackets"
|
import "codemirror/addon/edit/closebrackets"
|
||||||
import "codemirror/addon/search/search"
|
import "codemirror/addon/search/search"
|
||||||
@@ -24,10 +26,12 @@ import "codemirror/addon/dialog/dialog"
|
|||||||
|
|
||||||
import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api"
|
import { watch, onMounted, ref, Ref, useContext } from "@nuxtjs/composition-api"
|
||||||
import { LinterDefinition } from "./linting/linter"
|
import { LinterDefinition } from "./linting/linter"
|
||||||
|
import { Completer } from "./completion"
|
||||||
|
|
||||||
type CodeMirrorOptions = {
|
type CodeMirrorOptions = {
|
||||||
extendedEditorConfig: Omit<CodeMirror.EditorConfiguration, "value">
|
extendedEditorConfig: Omit<CodeMirror.EditorConfiguration, "value">
|
||||||
linter: LinterDefinition | null
|
linter: LinterDefinition | null
|
||||||
|
completer: Completer | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = {
|
const DEFAULT_EDITOR_CONFIG: CodeMirror.EditorConfiguration = {
|
||||||
@@ -76,6 +80,31 @@ export function useCodemirror(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateCompleterConfig = () => {
|
||||||
|
if (options.completer) {
|
||||||
|
cm.value?.setOption("hintOptions", {
|
||||||
|
completeSingle: false,
|
||||||
|
hint: async (editor: CodeMirror.Editor) => {
|
||||||
|
const pos = editor.getCursor()
|
||||||
|
const text = editor.getValue()
|
||||||
|
|
||||||
|
const result = await options.completer!(text, pos)
|
||||||
|
|
||||||
|
console.log("complete!")
|
||||||
|
console.log(result)
|
||||||
|
|
||||||
|
return <CodeMirror.Hints>{
|
||||||
|
from: result.start,
|
||||||
|
to: result.end,
|
||||||
|
list: result.completions
|
||||||
|
.sort((a, b) => a.score - b.score)
|
||||||
|
.map((x) => x.text),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Boot-up CodeMirror, set the value and listeners
|
// Boot-up CodeMirror, set the value and listeners
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG)
|
cm.value = CodeMirror(el.value!, DEFAULT_EDITOR_CONFIG)
|
||||||
@@ -83,6 +112,7 @@ export function useCodemirror(
|
|||||||
setTheme()
|
setTheme()
|
||||||
updateEditorConfig()
|
updateEditorConfig()
|
||||||
updateLinterConfig()
|
updateLinterConfig()
|
||||||
|
updateCompleterConfig()
|
||||||
|
|
||||||
cm.value.on("change", (instance) => {
|
cm.value.on("change", (instance) => {
|
||||||
// External update propagation (via watchers) should be ignored
|
// External update propagation (via watchers) should be ignored
|
||||||
@@ -90,6 +120,13 @@ export function useCodemirror(
|
|||||||
value.value = instance.getValue()
|
value.value = instance.getValue()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* TODO: Show autocomplete on typing (this is just for testing) */
|
||||||
|
cm.value.on("keyup", (instance, event) => {
|
||||||
|
if (!instance.state.completionActive && event.key !== "Enter") {
|
||||||
|
instance.showHint()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const setTheme = () => {
|
const setTheme = () => {
|
||||||
@@ -120,6 +157,7 @@ export function useCodemirror(
|
|||||||
deep: true,
|
deep: true,
|
||||||
})
|
})
|
||||||
watch(() => options.linter, updateLinterConfig, { immediate: true })
|
watch(() => options.linter, updateLinterConfig, { immediate: true })
|
||||||
|
watch(() => options.completer, updateCompleterConfig, { immediate: true })
|
||||||
|
|
||||||
// Watch value updates
|
// Watch value updates
|
||||||
watch(value, (newVal) => {
|
watch(value, (newVal) => {
|
||||||
|
|||||||
33
helpers/editor/completion/index.ts
Normal file
33
helpers/editor/completion/index.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export type CompletionEntry = {
|
||||||
|
text: string
|
||||||
|
meta: string
|
||||||
|
score: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompleterResult = {
|
||||||
|
/**
|
||||||
|
* List of completions to display
|
||||||
|
*/
|
||||||
|
completions: CompletionEntry[]
|
||||||
|
/**
|
||||||
|
* Start of the completion position
|
||||||
|
* (on completion the start..end region is replaced)
|
||||||
|
*/
|
||||||
|
start: { line: number; ch: number }
|
||||||
|
/**
|
||||||
|
* End of the completion position
|
||||||
|
* (on completion the start..end region is replaced)
|
||||||
|
*/
|
||||||
|
end: { line: number; ch: number }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Completer = (
|
||||||
|
/**
|
||||||
|
* The contents of the editor
|
||||||
|
*/
|
||||||
|
text: string,
|
||||||
|
/**
|
||||||
|
* Position where the completer is fired
|
||||||
|
*/
|
||||||
|
completePos: { line: number; ch: number }
|
||||||
|
) => Promise<CompleterResult>
|
||||||
30
helpers/editor/completion/preRequest.ts
Normal file
30
helpers/editor/completion/preRequest.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { convertIndexToLineCh } from "../utils"
|
||||||
|
import { Completer, CompletionEntry } from "."
|
||||||
|
import { getPreRequestScriptCompletions } from "~/helpers/tern"
|
||||||
|
|
||||||
|
const completer: Completer = async (text, completePos) => {
|
||||||
|
const results = await getPreRequestScriptCompletions(
|
||||||
|
text,
|
||||||
|
completePos.line,
|
||||||
|
completePos.ch
|
||||||
|
)
|
||||||
|
|
||||||
|
const start = convertIndexToLineCh(text, results.start)
|
||||||
|
const end = convertIndexToLineCh(text, results.end)
|
||||||
|
|
||||||
|
const completions = results.completions.map((completion: any, i: number) => {
|
||||||
|
return <CompletionEntry>{
|
||||||
|
text: completion.name,
|
||||||
|
meta: completion.isKeyword ? "keyword" : completion.type,
|
||||||
|
score: results.completions.length - i,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
completions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default completer
|
||||||
Reference in New Issue
Block a user