[cm6] Only swallow text in autocomplete insertion if node is well-formed (#12562)

* [cm6] Only swallow text in autocomplete insertion if node is well-formed

* [cm6] Use longest common prefix heuristic for autocompletion in ill-formed nodes

GitOrigin-RevId: 5adf3dc0314d86b267e2142a1250dece3ab29ef8
This commit is contained in:
Mathias Jakobsen 2023-04-14 09:58:55 +01:00 committed by Copybot
parent 73191f56e1
commit 7d237d0103

View file

@ -7,6 +7,7 @@ import {
} from '@codemirror/autocomplete'
import { EditorView } from '@codemirror/view'
import { prepareSnippetTemplate } from '../snippets'
import { ancestorNodeOfType } from '../../../utils/tree-query'
// from https://github.com/codemirror/autocomplete/blob/main/src/closebrackets.ts
export const nextChar = (doc: Text, pos: number) => {
@ -39,11 +40,31 @@ export const createCommandApplier =
view.dispatch(insertCompletionText(view.state, text, from, to))
}
const longestCommonPrefix = (...strs: string[]) => {
if (strs.length === 0) {
return 0
}
const minLength = Math.min(...strs.map(str => str.length))
for (let i = 0; i < minLength; ++i) {
for (let j = 1; j < strs.length; ++j) {
if (strs[j][i] !== strs[0][i]) {
return i
}
}
}
return minLength
}
// apply a completed required parameter, adding a closing brace and extending the range if needed
export const createRequiredParameterApplier =
(text: string) =>
(view: EditorView, completion: Completion, from: number, to: number) => {
const { doc } = view.state
const argumentNode = ancestorNodeOfType(view.state, from, '$Argument')
const isWellFormedArgumentNode =
argumentNode &&
argumentNode.getChild('OpenBrace') &&
argumentNode.getChild('CloseBrace')
// add a closing brace if needed
if (nextChar(doc, to) !== '}') {
@ -51,11 +72,22 @@ export const createRequiredParameterApplier =
text += '}'
}
if (isWellFormedArgumentNode) {
// extend over subsequent text that isn't a brace, space, or comma
const match = doc.sliceString(to, doc.lineAt(from).to).match(/^[^}\s,]+/)
const match = doc
.sliceString(to, Math.min(doc.lineAt(from).to, argumentNode.to))
.match(/^[^}\s,]+/)
if (match) {
to += match[0].length
}
} else {
// Ensure we don't swallow a closing brace
const restOfLine = doc
.sliceString(to, Math.min(doc.lineAt(from).to, from + text.length))
.split('}')[0]
to += longestCommonPrefix(text.slice(to - from), restOfLine)
}
}
view.dispatch(insertCompletionText(view.state, text, from, to))