Commit 7f610ff0 authored by Tim Zallmann's avatar Tim Zallmann 💬
Browse files

feat: Check for completion feasibility

Checking if content is already in the min length of 10 characters and cursor at end of line

Cancels suggestion calling if we are not at the end of the line

Specs for different prompts

Apply 1 suggestion(s) to 1 file(s)

Improved Debug Message
parent 0b4ef92d
Loading
Loading
Loading
Loading
+79 −2
Original line number Diff line number Diff line
@@ -143,13 +143,14 @@ describe('CodeSuggestionsProvider', () => {
      }
      return 'after';
    },
    lineAt: () => ({ text: mockPrompt }) as vscode.TextLine,
    fileName: 'test.js',
    languageId: 'javascript',
  } as vscode.TextDocument;
  } as unknown as vscode.TextDocument;

  const position = {
    line: 1,
    character: 1,
    character: mockPrompt.length,
  } as vscode.Position;

  describe('getCompletions', () => {
@@ -243,6 +244,82 @@ describe('CodeSuggestionsProvider', () => {
      expect(inputBody.project_id).toBe(undefined);
      expect(inputBody.project_path).toBe(undefined);
    });

    describe('input prompt', () => {
      const fetchFromApiMock = jest.fn();
      const glcp: CodeSuggestionsProvider = new CodeSuggestionsProvider({
        manager: createManager(project, fetchFromApiMock),
        legacyApiFallbackConfig: createLegacyApiFallbackConfig(false),
        stateManager,
      });

      let testPrompt = '';

      const inputTestDocument = {
        getText(range: vscode.Range): string {
          if (range.start.character === 0 && range.start.line === 0) {
            return testPrompt;
          }
          return '';
        },
        lineAt: () => ({ text: testPrompt }) as vscode.TextLine,
      } as unknown as vscode.TextDocument;

      describe('doesnt request a code suggestion', () => {
        it('if content length is too short ', async () => {
          testPrompt = 'const';

          const inpMockPosition = {
            line: 0,
            character: testPrompt.length,
          } as vscode.Position;

          await glcp.getCompletions({
            document: inputTestDocument,
            position: inpMockPosition,
            cancellationToken,
          });

          expect(codeSuggestionMonolithCalls(fetchFromApiMock)).toHaveLength(0);
        });

        it('if non-ignorable chars after cursor', async () => {
          testPrompt = 'const testValue = "test"';

          const inpMockPosition = {
            line: 0,
            character: testPrompt.length - 4,
          } as vscode.Position;

          await glcp.getCompletions({
            document: inputTestDocument,
            position: inpMockPosition,
            cancellationToken,
          });

          expect(codeSuggestionMonolithCalls(fetchFromApiMock)).toHaveLength(0);
        });
      });

      describe('request a code suggestion', () => {
        it('if special characters are past the cursor', async () => {
          testPrompt = 'const newFunctionForValidatingEMail  = (inp) => {}';

          const inpMockPosition = {
            line: 0,
            character: testPrompt.length - 1,
          } as vscode.Position;

          await glcp.getCompletions({
            document: inputTestDocument,
            position: inpMockPosition,
            cancellationToken,
          });

          expect(codeSuggestionMonolithCalls(fetchFromApiMock)).toHaveLength(1);
        });
      });
    });
  });

  describe('provideInlineCompletionItems', () => {
+28 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import {
import {
  MODEL_GATEWAY_AI_ASSISTED_CODE_SUGGESTIONS_API_URL,
  GITLAB_AI_ASSISTED_CODE_SUGGESTIONS_API_PATH,
  CODE_SUGGESTIONS_MIN_LENGTH,
} from './constants';
import { prettyJson } from '../utils/json';
import { COMMAND_CODE_SUGGESTION_ACCEPTED } from './commands/code_suggestion_accepted';
@@ -117,7 +118,7 @@ export class CodeSuggestionsProvider implements vscode.InlineCompletionItemProvi
    document: vscode.TextDocument,
    position: vscode.Position,
    project: GitLabProject | undefined,
  ): CodeSuggestionPrompt {
  ): CodeSuggestionPrompt | undefined {
    const contentBeforeCursor = document.getText(
      new vscode.Range(0, 0, position.line, position.character),
    );
@@ -126,6 +127,28 @@ export class CodeSuggestionsProvider implements vscode.InlineCompletionItemProvi
      new vscode.Range(position.line, position.character, document.lineCount, 0),
    );

    const contentLength = contentBeforeCursor.length + contentAfterCursor.length;
    if (contentLength < CODE_SUGGESTIONS_MIN_LENGTH) {
      log.debug(
        `Code suggestion: Cancelling Prompt building as content length (${contentLength}) is less than ${CODE_SUGGESTIONS_MIN_LENGTH}`,
      );
      return undefined;
    }

    // Check if we are at the end of the line or only special characters after
    const currentLine = document.lineAt(position);
    const lineSuffix = currentLine.text.substring(position.character).trim();
    if (lineSuffix.length > 0) {
      const allowedCharactersPastCursorRegex = /^\s*[)}\]"'`]*\s*[:{;,]?\s*$/;
      if (!allowedCharactersPastCursorRegex.test(lineSuffix)) {
        log.debug(
          `Code suggestion: Cancelling Prompt building, as characters after the cursor in that line are not ignorable`,
        );

        return undefined;
      }
    }

    const projectInfo =
      project && isSaasProject(project)
        ? { project_id: project.restId, project_path: project.namespaceWithPath }
@@ -185,6 +208,10 @@ export class CodeSuggestionsProvider implements vscode.InlineCompletionItemProvi

    const prompt = CodeSuggestionsProvider.#getPrompt(document, position, project);

    if (!prompt) {
      return [];
    }

    log.debug(
      `AI Assist: fetching completions ... (telemetry: ${prettyJson(
        codeSuggestionsTelemetry.toArray(),
+2 −0
Original line number Diff line number Diff line
@@ -35,3 +35,5 @@ export const AI_ASSISTED_CODE_SUGGESTIONS_LANGUAGES = [
  'terraform',
  'terragrunt',
];

export const CODE_SUGGESTIONS_MIN_LENGTH = 10;