From a16ae115552170efca5bc8959d0906568fbbdbb2 Mon Sep 17 00:00:00 2001 From: arshad Date: Fri, 6 Dec 2024 03:05:14 +0300 Subject: [PATCH] fix: completion not showing if the mode is insert --- src/classes/completion-range.ts | 201 +++++++++---------------- src/core/completion/handler.ts | 7 +- tests/ui/src/app/api/complete/route.ts | 3 + 3 files changed, 84 insertions(+), 127 deletions(-) diff --git a/src/classes/completion-range.ts b/src/classes/completion-range.ts index 1f00c5e..3fca617 100644 --- a/src/classes/completion-range.ts +++ b/src/classes/completion-range.ts @@ -1,13 +1,16 @@ import {CursorPosition, EditorModel, EditorRange, Monaco} from '../types'; -import { - getCharAfterCursor, - getTextAfterCursor, - getTextBeforeCursor, -} from '../utils'; export class CompletionRange { constructor(private monaco: Monaco) {} + /** + * Calculates the range where the completion should be inserted in the editor. + * + * @param pos - The current cursor position in the editor. + * @param completion - The text of the completion to be inserted. + * @param mdl - The Monaco editor text model. + * @returns The range where the completion should be inserted. + */ public computeInsertionRange( pos: CursorPosition, completion: string, @@ -23,20 +26,21 @@ export class CompletionRange { ); } - const charAfterCursor = getCharAfterCursor(pos, mdl); - const completionFirstChar = completion[0]; + const startOffset = mdl.getOffsetAt(pos); + const textBeforeCursor = mdl.getValue().substring(0, startOffset); + const textAfterCursor = mdl.getValue().substring(startOffset); - // If there's no non-whitespace character after the cursor, calculate range without overlap - if (!charAfterCursor || completionFirstChar === charAfterCursor) { - return this.calculateRangeWithoutOverlap(pos, completion); - } + let prefixOverlapLength = 0; + let suffixOverlapLength = 0; + let maxOverlapLength = 0; + let startOverlapLength = 0; - const startOffset = mdl.getOffsetAt(pos); - const textBeforeCursor = getTextBeforeCursor(pos, mdl); - const textAfterCursor = getTextAfterCursor(pos, mdl); + const completionLength = completion.length; + const beforeLength = textBeforeCursor.length; + const afterLength = textAfterCursor.length; - // Handle cursor at the end of the document or when there's no text after the cursor - if (startOffset >= mdl.getValue().length || !textAfterCursor.length) { + // Handle cursor at the end of the document + if (startOffset >= mdl.getValue().length) { return new this.monaco.Range( pos.lineNumber, pos.column, @@ -45,17 +49,55 @@ export class CompletionRange { ); } - // Calculate overlaps with text before and after the cursor - const startOverlapLength = this.getSuffixOverlapLength( - completion, - textBeforeCursor, - ); - const maxOverlapLength = this.computeMaxOverlapLength( - completion, - textAfterCursor, - ); + // Handle empty remaining text + if (afterLength === 0) { + return new this.monaco.Range( + pos.lineNumber, + pos.column, + pos.lineNumber, + pos.column, + ); + } + + // Find overlap with text before cursor + const maxBeforeOverlap = Math.min(completionLength, beforeLength); + for (let i = 1; i <= maxBeforeOverlap; i++) { + const completionStart = completion.substring(0, i); + const textEnd = textBeforeCursor.slice(-i); + if (completionStart === textEnd) { + startOverlapLength = i; + } + } + + // Find overlap with text after cursor + const maxAfterOverlap = Math.min(completionLength, afterLength); + + // Find the longest prefix overlap with text after cursor + for (let i = 0; i < maxAfterOverlap; i++) { + if (completion[i] !== textAfterCursor[i]) break; + prefixOverlapLength++; + } + + // Find the longest suffix overlap with text after cursor + for (let i = 1; i <= maxAfterOverlap; i++) { + if (completion.slice(-i) === textAfterCursor.slice(0, i)) { + suffixOverlapLength = i; + } + } - // Calculate start and end positions based on overlaps + maxOverlapLength = Math.max(prefixOverlapLength, suffixOverlapLength); + + // Check for internal overlaps if no prefix or suffix overlap + if (maxOverlapLength === 0) { + for (let i = 1; i < completionLength; i++) { + if (textAfterCursor.startsWith(completion.substring(i))) { + maxOverlapLength = completionLength - i; + break; + } + } + } + + // Calculate start and end positions const startPosition = startOverlapLength > 0 ? mdl.getPositionAt(startOffset - startOverlapLength) @@ -71,8 +113,13 @@ export class CompletionRange { ); } - // Calculates the range when there's no overlap with existing text - private calculateRangeWithoutOverlap( + /** + * Calculates the range for caching when there's no overlap with existing text. + * @param pos - The current cursor position. + * @param completion - The completion text. + * @returns The range for caching the completion. + */ + public computeCacheRange( pos: CursorPosition, completion: string, ): EditorRange { @@ -94,102 +141,4 @@ export class CompletionRange { endColumn, ); } - - // Computes the maximum overlap length with text after the cursor - private computeMaxOverlapLength( - completion: string, - textAfterCursor: string, - ): number { - const prefixOverlapLength = this.getPrefixOverlapLength( - completion, - textAfterCursor, - ); - const suffixOverlapLength = this.getSuffixPrefixOverlapLength( - completion, - textAfterCursor, - ); - let maxOverlapLength = Math.max(prefixOverlapLength, suffixOverlapLength); - - // Check for internal overlaps if no prefix or suffix overlap is found - if (maxOverlapLength === 0) { - maxOverlapLength = this.getInternalOverlapLength( - completion, - textAfterCursor, - ); - } - - return maxOverlapLength; - } - - // Finds overlap where the suffix of 'textBeforeCursor' matches the prefix of 'completion' - private getSuffixOverlapLength( - completion: string, - textBeforeCursor: string, - ): number { - const maxPossibleOverlap = Math.min( - completion.length, - textBeforeCursor.length, - ); - let overlapLength = 0; - - for (let i = 1; i <= maxPossibleOverlap; i++) { - if (completion.substring(0, i) === textBeforeCursor.slice(-i)) { - overlapLength = i; - } - } - - return overlapLength; - } - - // Finds the maximum length where the prefix of 'completion' matches 'textAfterCursor' - private getPrefixOverlapLength( - completion: string, - textAfterCursor: string, - ): number { - const maxPossibleOverlap = Math.min( - completion.length, - textAfterCursor.length, - ); - - for (let i = 0; i < maxPossibleOverlap; i++) { - if (completion[i] !== textAfterCursor[i]) { - return i; - } - } - - return maxPossibleOverlap; - } - - // Finds overlap where the suffix of 'completion' matches the prefix of 'textAfterCursor' - private getSuffixPrefixOverlapLength( - completion: string, - textAfterCursor: string, - ): number { - const maxPossibleOverlap = Math.min( - completion.length, - textAfterCursor.length, - ); - - for (let i = maxPossibleOverlap; i > 0; i--) { - if (completion.slice(-i) === textAfterCursor.slice(0, i)) { - return i; - } - } - - return 0; - } - - // Finds internal overlaps within 'completion' and 'textAfterCursor' - private getInternalOverlapLength( - completion: string, - textAfterCursor: string, - ): number { - for (let i = 1; i < completion.length; i++) { - if (textAfterCursor.startsWith(completion.substring(i))) { - return completion.length - i; - } - } - - return 0; - } } diff --git a/src/core/completion/handler.ts b/src/core/completion/handler.ts index 6c7d91b..b74e651 100644 --- a/src/core/completion/handler.ts +++ b/src/core/completion/handler.ts @@ -132,10 +132,15 @@ const handleInlineCompletions = async ({ mdl, ); + const cacheRange = completionRange.computeCacheRange( + pos, + formattedCompletion, + ); + if (enableCaching) { completionCache.add({ completion: formattedCompletion, - range: completionInsertionRange, + range: cacheRange, textBeforeCursor: getTextBeforeCursor(pos, mdl), }); } diff --git a/tests/ui/src/app/api/complete/route.ts b/tests/ui/src/app/api/complete/route.ts index b547054..9522086 100644 --- a/tests/ui/src/app/api/complete/route.ts +++ b/tests/ui/src/app/api/complete/route.ts @@ -12,10 +12,13 @@ export async function POST(req: Request) { }); if (error) { + console.log(error); // Handle error if needed // ... return Response.json({completion: null, error}, {status: 500}); } + console.log('Got completion'); + return Response.json({completion}); }