Skip to content

Commit 5d120c0

Browse files
committed
add type into autocomplete
1 parent 1770b21 commit 5d120c0

File tree

2 files changed

+246
-1
lines changed

2 files changed

+246
-1
lines changed

src/services/ghost/GhostInlineCompletionProvider.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,36 @@ export class GhostInlineCompletionProvider implements vscode.InlineCompletionIte
5454
const suggestions = this.suggestionsHistory[i]
5555
const fillInAtCursor = suggestions?.getFillInAtCursor()
5656

57-
if (fillInAtCursor && prefix === fillInAtCursor.prefix && suffix === fillInAtCursor.suffix) {
57+
if (!fillInAtCursor) {
58+
continue
59+
}
60+
61+
// First, try exact prefix/suffix match
62+
if (prefix === fillInAtCursor.prefix && suffix === fillInAtCursor.suffix) {
5863
const item: vscode.InlineCompletionItem = {
5964
insertText: fillInAtCursor.text,
6065
range: new vscode.Range(position, position),
6166
}
6267
return [item]
6368
}
69+
70+
// If no exact match, check for partial typing
71+
// The user may have started typing the suggested text
72+
if (prefix.startsWith(fillInAtCursor.prefix) && suffix === fillInAtCursor.suffix) {
73+
// Extract what the user has typed between the original prefix and current position
74+
const typedContent = prefix.substring(fillInAtCursor.prefix.length)
75+
76+
// Check if the typed content matches the beginning of the suggestion
77+
if (fillInAtCursor.text.startsWith(typedContent)) {
78+
// Return the remaining part of the suggestion (with already-typed portion removed)
79+
const remainingText = fillInAtCursor.text.substring(typedContent.length)
80+
const item: vscode.InlineCompletionItem = {
81+
insertText: remainingText,
82+
range: new vscode.Range(position, position),
83+
}
84+
return [item]
85+
}
86+
}
6487
}
6588

6689
return []

src/services/ghost/__tests__/GhostInlineCompletionProvider.test.ts

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,228 @@ describe("GhostInlineCompletionProvider", () => {
314314

315315
expect(result[0].insertText).toBe("console.log('test')")
316316
})
317+
318+
describe("partial typing support", () => {
319+
it("should return remaining suggestion when user has partially typed the suggestion", () => {
320+
// Set up a suggestion
321+
const suggestions = new GhostSuggestionsState()
322+
suggestions.setFillInAtCursor({
323+
text: "console.log('Hello, World!');",
324+
prefix: "const x = 1",
325+
suffix: "\nconst y = 2",
326+
})
327+
provider.updateSuggestions(suggestions)
328+
329+
// Simulate user typing "cons" after the prefix
330+
const partialDocument = new MockTextDocument(
331+
vscode.Uri.file("/test.ts"),
332+
"const x = 1cons\nconst y = 2",
333+
)
334+
const partialPosition = new vscode.Position(0, 15) // After "const x = 1cons"
335+
336+
const result = provider.provideInlineCompletionItems(
337+
partialDocument,
338+
partialPosition,
339+
mockContext,
340+
mockToken,
341+
) as vscode.InlineCompletionItem[]
342+
343+
expect(result).toHaveLength(1)
344+
// Should return the remaining part after "cons"
345+
expect(result[0].insertText).toBe("ole.log('Hello, World!');")
346+
})
347+
348+
it("should return full suggestion when user has typed nothing after prefix", () => {
349+
const suggestions = new GhostSuggestionsState()
350+
suggestions.setFillInAtCursor({
351+
text: "console.log('test');",
352+
prefix: "const x = 1",
353+
suffix: "\nconst y = 2",
354+
})
355+
provider.updateSuggestions(suggestions)
356+
357+
// User is at exact prefix position (no partial typing)
358+
const result = provider.provideInlineCompletionItems(
359+
mockDocument,
360+
mockPosition,
361+
mockContext,
362+
mockToken,
363+
) as vscode.InlineCompletionItem[]
364+
365+
expect(result).toHaveLength(1)
366+
expect(result[0].insertText).toBe("console.log('test');")
367+
})
368+
369+
it("should return empty when partially typed content does not match suggestion", () => {
370+
const suggestions = new GhostSuggestionsState()
371+
suggestions.setFillInAtCursor({
372+
text: "console.log('test');",
373+
prefix: "const x = 1",
374+
suffix: "\nconst y = 2",
375+
})
376+
provider.updateSuggestions(suggestions)
377+
378+
// User typed "xyz" which doesn't match the suggestion
379+
const mismatchDocument = new MockTextDocument(
380+
vscode.Uri.file("/test.ts"),
381+
"const x = 1xyz\nconst y = 2",
382+
)
383+
const mismatchPosition = new vscode.Position(0, 14)
384+
385+
const result = provider.provideInlineCompletionItems(
386+
mismatchDocument,
387+
mismatchPosition,
388+
mockContext,
389+
mockToken,
390+
)
391+
392+
expect(result).toEqual([])
393+
})
394+
395+
it("should return empty string when user has typed entire suggestion", () => {
396+
const suggestions = new GhostSuggestionsState()
397+
suggestions.setFillInAtCursor({
398+
text: "console.log('test');",
399+
prefix: "const x = 1",
400+
suffix: "\nconst y = 2",
401+
})
402+
provider.updateSuggestions(suggestions)
403+
404+
// User has typed the entire suggestion - cursor is at the end of typed text
405+
// Position 31 is right after the semicolon, before the newline
406+
const completeDocument = new MockTextDocument(
407+
vscode.Uri.file("/test.ts"),
408+
"const x = 1console.log('test');\nconst y = 2",
409+
)
410+
const completePosition = new vscode.Position(0, 31) // After the semicolon, before newline
411+
412+
const result = provider.provideInlineCompletionItems(
413+
completeDocument,
414+
completePosition,
415+
mockContext,
416+
mockToken,
417+
) as vscode.InlineCompletionItem[]
418+
419+
expect(result).toHaveLength(1)
420+
// Should return empty string since everything is typed
421+
expect(result[0].insertText).toBe("")
422+
})
423+
424+
it("should not match when suffix has changed", () => {
425+
const suggestions = new GhostSuggestionsState()
426+
suggestions.setFillInAtCursor({
427+
text: "console.log('test');",
428+
prefix: "const x = 1",
429+
suffix: "\nconst y = 2",
430+
})
431+
provider.updateSuggestions(suggestions)
432+
433+
// User typed partial content but suffix changed
434+
const changedSuffixDocument = new MockTextDocument(
435+
vscode.Uri.file("/test.ts"),
436+
"const x = 1cons\nconst y = 3",
437+
)
438+
const changedSuffixPosition = new vscode.Position(0, 15)
439+
440+
const result = provider.provideInlineCompletionItems(
441+
changedSuffixDocument,
442+
changedSuffixPosition,
443+
mockContext,
444+
mockToken,
445+
)
446+
447+
expect(result).toEqual([])
448+
})
449+
450+
it("should prefer exact match over partial match", () => {
451+
// Add a suggestion that would match partially
452+
const suggestions1 = new GhostSuggestionsState()
453+
suggestions1.setFillInAtCursor({
454+
text: "console.log('partial');",
455+
prefix: "const x = 1",
456+
suffix: "\nconst y = 2",
457+
})
458+
provider.updateSuggestions(suggestions1)
459+
460+
// Add a suggestion with exact match (more recent)
461+
const suggestions2 = new GhostSuggestionsState()
462+
suggestions2.setFillInAtCursor({
463+
text: "exact match",
464+
prefix: "const x = 1cons",
465+
suffix: "\nconst y = 2",
466+
})
467+
provider.updateSuggestions(suggestions2)
468+
469+
// User is at position that matches exact prefix of second suggestion
470+
const document = new MockTextDocument(vscode.Uri.file("/test.ts"), "const x = 1cons\nconst y = 2")
471+
const position = new vscode.Position(0, 15)
472+
473+
const result = provider.provideInlineCompletionItems(
474+
document,
475+
position,
476+
mockContext,
477+
mockToken,
478+
) as vscode.InlineCompletionItem[]
479+
480+
expect(result).toHaveLength(1)
481+
// Should return exact match (most recent), not partial
482+
expect(result[0].insertText).toBe("exact match")
483+
})
484+
485+
it("should handle multi-character partial typing", () => {
486+
const suggestions = new GhostSuggestionsState()
487+
suggestions.setFillInAtCursor({
488+
text: "function test() { return 42; }",
489+
prefix: "const x = 1",
490+
suffix: "\nconst y = 2",
491+
})
492+
provider.updateSuggestions(suggestions)
493+
494+
// User typed "function te"
495+
const partialDocument = new MockTextDocument(
496+
vscode.Uri.file("/test.ts"),
497+
"const x = 1function te\nconst y = 2",
498+
)
499+
const partialPosition = new vscode.Position(0, 22)
500+
501+
const result = provider.provideInlineCompletionItems(
502+
partialDocument,
503+
partialPosition,
504+
mockContext,
505+
mockToken,
506+
) as vscode.InlineCompletionItem[]
507+
508+
expect(result).toHaveLength(1)
509+
expect(result[0].insertText).toBe("st() { return 42; }")
510+
})
511+
512+
it("should handle case-sensitive partial matching", () => {
513+
const suggestions = new GhostSuggestionsState()
514+
suggestions.setFillInAtCursor({
515+
text: "Console.log('test');",
516+
prefix: "const x = 1",
517+
suffix: "\nconst y = 2",
518+
})
519+
provider.updateSuggestions(suggestions)
520+
521+
// User typed "cons" (lowercase) but suggestion starts with "Console" (uppercase)
522+
const partialDocument = new MockTextDocument(
523+
vscode.Uri.file("/test.ts"),
524+
"const x = 1cons\nconst y = 2",
525+
)
526+
const partialPosition = new vscode.Position(0, 15)
527+
528+
const result = provider.provideInlineCompletionItems(
529+
partialDocument,
530+
partialPosition,
531+
mockContext,
532+
mockToken,
533+
)
534+
535+
// Should not match due to case difference
536+
expect(result).toEqual([])
537+
})
538+
})
317539
})
318540

319541
describe("updateSuggestions", () => {

0 commit comments

Comments
 (0)