Skip to content

Commit 36c940e

Browse files
committed
Fix itemFrom miscalculation which lead to wrongly filtered snippets
1 parent 2349d21 commit 36c940e

File tree

1 file changed

+41
-23
lines changed

1 file changed

+41
-23
lines changed

packages/editor-codemirror/src/behaviors/completion.ts

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -110,34 +110,52 @@ const compareBySortText = (a: CompletionItem, b: CompletionItem) => {
110110
}
111111
};
112112

113-
// compute from
114-
const itemFrom = (item: CompletionItem, contextPos: number) => {
115-
// compute from
116-
return item.textEdit
117-
? InsertReplaceEdit.is(item.textEdit)
118-
? contextPos - (item.textEdit.insert.end.character - item.textEdit.insert.start.character)
119-
: TextEdit.is(item.textEdit)
120-
? contextPos - (item.textEdit.range.end.character - item.textEdit.range.start.character)
121-
: contextPos
122-
: contextPos;
113+
/**
114+
* returns the offset into the document of the beginning of a completion item.
115+
*/
116+
const itemFrom = (item: CompletionItem, cvContext: CodeViewCompletionContext, contextPos: number) => {
117+
if (item.textEdit !== undefined) {
118+
if (InsertReplaceEdit.is(item.textEdit)) {
119+
const endLine = cvContext.code[item.textEdit.insert.end.line];
120+
// we have to "snap" (Math.min) the end of the insertion to the end of the line
121+
// because completions give positions past the end of the line.
122+
// e.g. typing `lib` gives completion `library` with start.character = 0, end.character = 7
123+
// but we only expect it to replace characters 0 thru 3.
124+
const end = Math.min(item.textEdit.insert.end.character, endLine.length);
125+
126+
const replaceLength = end - item.textEdit.insert.start.character;
127+
return contextPos - replaceLength;
128+
}
129+
if (TextEdit.is(item.textEdit)) {
130+
const endLine = cvContext.code[item.textEdit.range.end.line];
131+
// see comment above for an explanation of why we use Math.min here.
132+
const end = Math.min(item.textEdit.range.end.character, endLine.length);
133+
134+
const replaceLength = end - item.textEdit.range.start.character;
135+
136+
return contextPos - replaceLength;
137+
}
138+
}
139+
return contextPos;
123140
};
124141

125142
/**
126143
* replaceText for a given CompletionItem is the text that is already in the document
127144
* that that CompletionItem will replace.
128145
*
146+
*
129147
* Example 1: if you are typing `lib` and get the completion `library`, then this function
130148
* will give `lib`.
131149
* Example 2: if you are typing `os.a` and get the completion `abc`, then this function
132150
* will give `a`.
133151
*/
134-
const getReplaceText = (context: CompletionContext, item: CompletionItem) =>
135-
context.state.sliceDoc(itemFrom(item, context.pos), context.pos);
152+
const getReplaceText = (context: CompletionContext, cvContext: CodeViewCompletionContext, item: CompletionItem) =>
153+
context.state.sliceDoc(itemFrom(item, cvContext, context.pos), context.pos);
136154

137-
const makeCompletionItemApplier = (item: CompletionItem, context: CompletionContext) =>
155+
const makeCompletionItemApplier = (item: CompletionItem, cvContext: CodeViewCompletionContext, context: CompletionContext) =>
138156
(view: EditorView, completion: Completion) => {
139157
// compute from
140-
const from = itemFrom(item, context.pos);
158+
const from = itemFrom(item, cvContext, context.pos);
141159

142160
// handle snippets
143161
const insertText = item.textEdit?.newText ?? (item.insertText || item.label);
@@ -156,11 +174,11 @@ const makeCompletionItemApplier = (item: CompletionItem, context: CompletionCont
156174
}
157175
};
158176

159-
const sortTextItemsBoostScore = (context: CompletionContext, items: CompletionItem[], index: number) => {
177+
const sortTextItemsBoostScore = (context: CompletionContext, cvContext: CodeViewCompletionContext, items: CompletionItem[], index: number) => {
160178
const total = items.length;
161179
const item = items[index];
162180
// compute replaceText
163-
const replaceText = getReplaceText(context, item);
181+
const replaceText = getReplaceText(context, cvContext, item);
164182

165183
// if the replaceText doesn't start with "." then bury items that do
166184
if (!replaceText.startsWith(".") && item.label.startsWith(".")) {
@@ -177,14 +195,14 @@ const sortTextItemsBoostScore = (context: CompletionContext, items: CompletionIt
177195
}
178196
};
179197

180-
const defaultBoostScore = (context: CompletionContext, items: CompletionItem[], index: number) => {
198+
const defaultBoostScore = (context: CompletionContext, cvContext: CodeViewCompletionContext, items: CompletionItem[], index: number) => {
181199
const item = items[index];
182200

183-
const replaceText = getReplaceText(context, item);
201+
const replaceText = getReplaceText(context, cvContext, item);
184202

185203
// if you haven't typed into the completions yet (for example after a `.`) then
186-
// score items starting with non-alphabetic characters -1, everything else 0.
187-
if (replaceText.length === 0) return isLetter(item.label[0]) ? 0 : -1;
204+
// score items starting with non-alphabetic characters -100, everything else 0.
205+
if (replaceText.length === 0) return isLetter(item.label[0]) ? 0 : -100;
188206

189207
// We filter items by replaceText inclusion before scoring,
190208
// so i is garaunteed to be an index into `item.label`...
@@ -233,7 +251,7 @@ async function getCompletions(
233251
if (item.textEdit === undefined && token) return false;
234252

235253
// require at least inclusion
236-
const replaceText = getReplaceText(context, item).toLowerCase();
254+
const replaceText = getReplaceText(context, cvContext, item).toLowerCase();
237255
return item.label.toLowerCase().includes(replaceText) ||
238256
item.insertText?.toLowerCase().includes(replaceText);
239257
});
@@ -249,8 +267,8 @@ async function getCompletions(
249267
detail: !item.documentation ? item.detail : undefined,
250268
type: vsKindToType(item.kind),
251269
info: () => infoNodeForItem(item),
252-
apply: makeCompletionItemApplier(item, context),
253-
boost: boostScore(context, filteredItems, index)
270+
apply: makeCompletionItemApplier(item, cvContext, context),
271+
boost: boostScore(context, cvContext, filteredItems, index)
254272
};
255273
});
256274

0 commit comments

Comments
 (0)