[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:
committed by
Copybot
parent
73191f56e1
commit
7d237d0103
+36
-4
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '@codemirror/autocomplete'
|
} from '@codemirror/autocomplete'
|
||||||
import { EditorView } from '@codemirror/view'
|
import { EditorView } from '@codemirror/view'
|
||||||
import { prepareSnippetTemplate } from '../snippets'
|
import { prepareSnippetTemplate } from '../snippets'
|
||||||
|
import { ancestorNodeOfType } from '../../../utils/tree-query'
|
||||||
|
|
||||||
// from https://github.com/codemirror/autocomplete/blob/main/src/closebrackets.ts
|
// from https://github.com/codemirror/autocomplete/blob/main/src/closebrackets.ts
|
||||||
export const nextChar = (doc: Text, pos: number) => {
|
export const nextChar = (doc: Text, pos: number) => {
|
||||||
@@ -39,11 +40,31 @@ export const createCommandApplier =
|
|||||||
view.dispatch(insertCompletionText(view.state, text, from, to))
|
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
|
// apply a completed required parameter, adding a closing brace and extending the range if needed
|
||||||
export const createRequiredParameterApplier =
|
export const createRequiredParameterApplier =
|
||||||
(text: string) =>
|
(text: string) =>
|
||||||
(view: EditorView, completion: Completion, from: number, to: number) => {
|
(view: EditorView, completion: Completion, from: number, to: number) => {
|
||||||
const { doc } = view.state
|
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
|
// add a closing brace if needed
|
||||||
if (nextChar(doc, to) !== '}') {
|
if (nextChar(doc, to) !== '}') {
|
||||||
@@ -51,10 +72,21 @@ export const createRequiredParameterApplier =
|
|||||||
text += '}'
|
text += '}'
|
||||||
}
|
}
|
||||||
|
|
||||||
// extend over subsequent text that isn't a brace, space, or comma
|
if (isWellFormedArgumentNode) {
|
||||||
const match = doc.sliceString(to, doc.lineAt(from).to).match(/^[^}\s,]+/)
|
// extend over subsequent text that isn't a brace, space, or comma
|
||||||
if (match) {
|
const match = doc
|
||||||
to += match[0].length
|
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user