Skip to content

Commit

Permalink
chore: improve prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
arshad-yaseen committed Feb 15, 2025
1 parent 5ef73a1 commit 73e1028
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 134 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
*.log
yalc.lock

.vitepress/cache

.vercel/
.turbo/
.next/
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const DEFAULT_COPILOT_TEMPERATURE = 0.1 as const;
export const DEFAULT_COPILOT_MAX_TOKENS = 64 as const;
export const DEFAULT_COPILOT_TEMPERATURE = 0.5 as const;
export const DEFAULT_COPILOT_MAX_TOKENS = 4096 as const;
62 changes: 27 additions & 35 deletions packages/monacopilot/src/classes/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {getTextBeforeCursor} from '../utils/editor';
import {Queue} from './queue';

export class CompletionCache {
private static readonly MAX_CACHE_SIZE = 10;
private static readonly MAX_CACHE_SIZE = 20;
private cache: Queue<CompletionCacheItem>;

constructor() {
Expand All @@ -23,6 +23,9 @@ export class CompletionCache {
}

public add(cacheItem: Readonly<CompletionCacheItem>): void {
if (!cacheItem.completion.trim()) {
return;
}
this.cache.enqueue(cacheItem);
}

Expand All @@ -38,57 +41,46 @@ export class CompletionCache {
const currentRangeValue = mdl.getValueInRange(cacheItem.range);
const textBeforeCursor = getTextBeforeCursor(pos, mdl);

// Ensure the current text before cursor hasn't become shorter than the cached prefix
// and that it still starts with the same prefix.
if (
textBeforeCursor.length < cacheItem.textBeforeCursor.length ||
!textBeforeCursor.startsWith(cacheItem.textBeforeCursor)
) {
if (!textBeforeCursor.includes(cacheItem.textBeforeCursor)) {
return false;
}

// Then validate the cursor position and typed code against the stored completion range
return this.isPositionValid(cacheItem, pos, currentRangeValue);
if (!this.isPartialMatch(currentRangeValue, cacheItem.completion)) {
return false;
}

return this.isPositionValid(cacheItem, pos);
}

private isPartialMatch(current: string, cached: string): boolean {
return cached.startsWith(current) || current.startsWith(cached);
}

private isPositionValid(
cacheItem: Readonly<CompletionCacheItem>,
pos: Readonly<CursorPosition>,
currentRangeValue: string,
): boolean {
const {range, completion} = cacheItem;
const {range} = cacheItem;
const {startLineNumber, startColumn, endLineNumber, endColumn} = range;
const {lineNumber, column} = pos;

// Check if the user's current typed text (in the range) is still a valid prefix of the completion.
if (!completion.startsWith(currentRangeValue)) {
const isInLineRange =
lineNumber >= startLineNumber && lineNumber <= endLineNumber;
if (!isInLineRange) {
return false;
}

// Check if cursor is at the start of the completion range
const isAtStartOfRange =
lineNumber === startLineNumber && column === startColumn;

// For single-line completion
if (startLineNumber === endLineNumber) {
// Cursor must be at start of the range or within the snippet's boundaries on the same line
return (
isAtStartOfRange ||
(lineNumber === startLineNumber &&
column >= startColumn &&
column <= endColumn)
);
if (lineNumber === startLineNumber && lineNumber === endLineNumber) {
return column >= startColumn - 1 && column <= endColumn + 1;
}

// For multi-line completion, the cursor must be at start of the range or within
// the vertical bounds. Additionally, if it's on the first or last line, it should
// be within the horizontal bounds; otherwise, any column is acceptable.
const isWithinMultiLineRange =
lineNumber > startLineNumber && lineNumber < endLineNumber
? true
: (lineNumber === startLineNumber && column >= startColumn) ||
(lineNumber === endLineNumber && column <= endColumn);
if (lineNumber === startLineNumber) {
return column >= startColumn - 1;
}
if (lineNumber === endLineNumber) {
return column <= endColumn + 1;
}

return isAtStartOfRange || isWithinMultiLineRange;
return true;
}
}
34 changes: 21 additions & 13 deletions packages/monacopilot/src/classes/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,14 @@ export class CompletionFormatter {
}

public indentByColumn(): CompletionFormatter {
// Split completion into lines
const lines = this.formattedCompletion.split('\n');

// Skip indentation if there's only one line or if there's text before cursor in the line
if (lines.length <= 1 || this.textBeforeCursorInLine.trim() !== '') {
return this;
}

// Create indentation string based on current column position
const indentation = ' '.repeat(this.currentColumn - 1);

// Keep first line as is, indent all subsequent lines
this.formattedCompletion =
lines[0] +
'\n' +
Expand All @@ -55,16 +51,28 @@ export class CompletionFormatter {
}

private removeMarkdownCodeBlocks(text: string): string {
const codeBlockRegex = /```[\s\S]*?```/g;
let result = text;
let match: RegExpExecArray | null;

while ((match = codeBlockRegex.exec(text)) !== null) {
const codeBlock = match[0];
const codeContent = codeBlock.split('\n').slice(1, -1).join('\n');
result = result.replace(codeBlock, codeContent);
const lines = text.split('\n');
const result: string[] = [];
let inCodeBlock = false;

for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const isCodeBlockStart = line.trim().startsWith('```');

if (isCodeBlockStart && !inCodeBlock) {
inCodeBlock = true;
continue;
}

if (isCodeBlockStart && inCodeBlock) {
inCodeBlock = false;
continue;
}

result.push(line);
}
return result;

return result.join('\n');
}

public removeExcessiveNewlines(): CompletionFormatter {
Expand Down
99 changes: 27 additions & 72 deletions packages/monacopilot/src/classes/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,43 @@ import type {
EditorRange,
Monaco,
} from '../types/monaco';
import {TextOverlapCalculator} from './text-overlap-calculator';

export class CompletionRange {
constructor(private monaco: Monaco) {}
private textOverlapCalculator: TextOverlapCalculator;

constructor(private monaco: Monaco) {
this.textOverlapCalculator = new TextOverlapCalculator();
}

public computeInsertionRange(
pos: CursorPosition,
completion: string,
mdl: EditorModel,
): EditorRange {
// Handle empty completion
if (!completion) {
return new this.monaco.Range(
pos.lineNumber,
pos.column,
pos.lineNumber,
pos.column,
);
return this.createEmptyRange(pos);
}

const startOffset = mdl.getOffsetAt(pos);
const textBeforeCursor = mdl.getValue().substring(0, startOffset);
const textAfterCursor = mdl.getValue().substring(startOffset);

let prefixOverlapLength = 0;
let suffixOverlapLength = 0;
let maxOverlapLength = 0;
let startOverlapLength = 0;

const completionLength = completion.length;
const beforeLength = textBeforeCursor.length;
const afterLength = textAfterCursor.length;

// Handle cursor at the end of the document
if (startOffset >= mdl.getValue().length) {
return new this.monaco.Range(
pos.lineNumber,
pos.column,
pos.lineNumber,
pos.column,
);
return this.createEmptyRange(pos);
}

// 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;
}
if (textAfterCursor.length === 0) {
return this.createEmptyRange(pos);
}

// 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;
}
}

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;
}
}
}
const {startOverlapLength, maxOverlapLength} =
this.textOverlapCalculator.findOverlaps(
completion,
textBeforeCursor,
textAfterCursor,
);

// Calculate start and end positions
const startPosition =
startOverlapLength > 0
? mdl.getPositionAt(startOffset - startOverlapLength)
Expand Down Expand Up @@ -122,8 +68,8 @@ export class CompletionRange {
const endLineNumber = startLineNumber + lastLineIndex;
const endColumn =
lastLineIndex === 0
? startColumn + completionLines[0].length // Single-line completion
: completionLines[lastLineIndex].length + 1; // Multi-line completion
? startColumn + completionLines[0].length
: completionLines[lastLineIndex].length + 1;

return new this.monaco.Range(
startLineNumber,
Expand All @@ -132,4 +78,13 @@ export class CompletionRange {
endColumn,
);
}

private createEmptyRange(pos: CursorPosition): EditorRange {
return new this.monaco.Range(
pos.lineNumber,
pos.column,
pos.lineNumber,
pos.column,
);
}
}
66 changes: 66 additions & 0 deletions packages/monacopilot/src/classes/text-overlap-calculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
export class TextOverlapCalculator {
public findOverlaps(
completion: string,
textBeforeCursor: string,
textAfterCursor: string,
): {
startOverlapLength: number;
maxOverlapLength: number;
} {
if (!completion) {
return {
startOverlapLength: 0,
maxOverlapLength: 0,
};
}

const completionLength = completion.length;
const beforeLength = textBeforeCursor.length;
const afterLength = textAfterCursor.length;

let prefixOverlapLength = 0;
let suffixOverlapLength = 0;
let startOverlapLength = 0;

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;
}
}

const maxAfterOverlap = Math.min(completionLength, afterLength);

for (let i = 0; i < maxAfterOverlap; i++) {
if (completion[i] !== textAfterCursor[i]) break;
prefixOverlapLength++;
}

for (let i = 1; i <= maxAfterOverlap; i++) {
if (completion.slice(-i) === textAfterCursor.slice(0, i)) {
suffixOverlapLength = i;
}
}

let maxOverlapLength = Math.max(
prefixOverlapLength,
suffixOverlapLength,
);

if (maxOverlapLength === 0) {
for (let i = 1; i < completionLength; i++) {
if (textAfterCursor.startsWith(completion.substring(i))) {
maxOverlapLength = completionLength - i;
break;
}
}
}

return {
startOverlapLength,
maxOverlapLength,
};
}
}
Loading

0 comments on commit 73e1028

Please sign in to comment.