Skip to content

Commit

Permalink
Merge pull request #88 from arshad-yaseen/fix/completion-not-showing-…
Browse files Browse the repository at this point in the history
…on-insert-mode

Fix completion not showing if the mode is insert
  • Loading branch information
arshad-yaseen authored Dec 6, 2024
2 parents 1d2a7ed + a16ae11 commit 3dc83e1
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 127 deletions.
201 changes: 75 additions & 126 deletions src/classes/completion-range.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -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;
}
}
7 changes: 6 additions & 1 deletion src/core/completion/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
});
}
Expand Down
3 changes: 3 additions & 0 deletions tests/ui/src/app/api/complete/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}

0 comments on commit 3dc83e1

Please sign in to comment.