Commit bda65ec3 authored by Enrique Alcántara's avatar Enrique Alcántara 3️⃣ Committed by Tristan Read
Browse files

feat: Support using web ide in the language server

Fix two issues that prevent using the language
server in the Web IDE

- Send serializable range and position objects to the
language server
- Provide a baseAssetsUrl to load tree-sitter WASM files
parent 49e65804
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ import { COMMAND_CODE_SUGGESTION_STREAM_ACCEPTED } from '../code_suggestions/com
import { CompletionStream, getStreamContextId } from './completion_stream';
import { FeatureFlag, isEnabled } from '../feature_flags';
import { CONFIG_NAMESPACE } from '../constants';
import { serializeInlineCompletionContext, serializePosition } from './serialization_utils';

// We need to wait just a bit after cancellation, otherwise the loading icon flickers while someone types
const CANCELLATION_DELAY = 150;
@@ -106,7 +107,10 @@ export class LanguageClientMiddleware implements Middleware {
      .then(() => []);

    try {
      return await Promise.race([shortCircuit, next(document, position, context, token)]);
      return await Promise.race([
        shortCircuit,
        next(document, position, serializeInlineCompletionContext(context), token),
      ]);
    } finally {
      this.#stateManager.setLoading(false);
    }
@@ -248,7 +252,7 @@ export class LanguageClientMiddleware implements Middleware {
    try {
      intentTypeResponse = await this.#client?.sendRequest(GET_COMPLETION_INTENT_REQUEST, {
        documentUri: document.uri.toString(),
        position,
        position: serializePosition(position),
      });
    } catch (error) {
      log.warn(`Failed to detect completion intent`, error);
+12 −1
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ describe('registerLanguageServer', () => {
    jest.mocked(LanguageClientWrapper).mockReturnValue(clientWrapper);
    client = createFakePartial<BaseLanguageClient>({});
    languageClientFactory = createFakePartial<LanguageClientFactory>({
      createLanguageClient: () => client,
      createLanguageClient: jest.fn(() => client),
    });
    gitlabPlatformManager = createFakePartial<GitLabPlatformManager>({
      onAccountChange: jest.fn(),
@@ -50,6 +50,17 @@ describe('registerLanguageServer', () => {
    await registerLanguageServer(context, languageClientFactory, gitlabPlatformManager);
  });

  it('creates a language client and provides a baseAssetsUrl', () => {
    expect(languageClientFactory.createLanguageClient).toHaveBeenCalledWith(
      context,
      expect.objectContaining({
        initializationOptions: expect.objectContaining({
          baseAssetsUrl: 'https://localhost/assets/language-server/',
        }),
      }),
    );
  });

  it('initializes the language client wrapper', async () => {
    expect(clientWrapper.initAndStart).toHaveBeenCalled();
  });
+8 −1
Original line number Diff line number Diff line
@@ -34,10 +34,17 @@ export const registerLanguageServer = async (
  const statusBarItem = new CodeSuggestionsStatusBarItem(stateManager);
  const gutterIcon = new CodeSuggestionsGutterIcon(context, stateManager);
  const middleware = new LanguageClientMiddleware(stateManager);
  const baseAssetsUrl = vscode.Uri.joinPath(
    context.extensionUri,
    './assets/language-server/',
  ).toString();

  const client = clientFactory.createLanguageClient(context, {
    ...LANGUAGE_CLIENT_OPTIONS,
    initializationOptions: getClientContext(),
    initializationOptions: {
      ...getClientContext(),
      baseAssetsUrl,
    },
    middleware,
  });
  middleware.client = client;
+49 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';
import {
  serializeInlineCompletionContext,
  serializePosition,
  serializeRange,
} from './serialization_utils';

describe('serializationUtils', () => {
  describe('serializePosition', () => {
    it('converts a vscode.Position object into a plain js object', () => {
      const result = serializePosition(new vscode.Position(1, 1));

      expect(result).not.toBeInstanceOf(vscode.Position);
      expect(result).toEqual({ line: 1, character: 1 });
    });
  });

  describe('serializeRange', () => {
    it('converts a vscode.Range object into a plain js object', () => {
      const result = serializeRange(
        new vscode.Range(new vscode.Position(1, 1), new vscode.Position(1, 2)),
      );

      expect(result).not.toBeInstanceOf(vscode.Range);
      expect(result).toEqual({ start: { line: 1, character: 1 }, end: { line: 1, character: 2 } });
    });
  });

  describe('serializeInlineCompletionContext', () => {
    it('transforms the selectedCompletionInfo range object into a plain js object', () => {
      const result = serializeInlineCompletionContext({
        triggerKind: vscode.InlineCompletionTriggerKind.Automatic,
        selectedCompletionInfo: {
          range: new vscode.Range(new vscode.Position(1, 1), new vscode.Position(1, 2)),
          text: 'text',
        },
      });

      expect(result.selectedCompletionInfo?.range).not.toBeInstanceOf(vscode.Range);
      expect(result).toEqual({
        triggerKind: vscode.InlineCompletionTriggerKind.Automatic,
        selectedCompletionInfo: {
          range: { start: { line: 1, character: 1 }, end: { line: 1, character: 2 } },
          text: 'text',
        },
      });
    });
  });
});
+35 −0
Original line number Diff line number Diff line
import * as vscode from 'vscode';

export const serializePosition = (position: vscode.Position): vscode.Position =>
  ({
    line: position.line,
    character: position.character,
  }) as vscode.Position;

export const serializeRange = (range: vscode.Range): vscode.Range =>
  ({
    start: serializePosition(range.start),
    end: serializePosition(range.end),
  }) as vscode.Range;

const serializeSelectedCompletionInfo = (
  selectedCompletionInfo?: vscode.SelectedCompletionInfo,
): vscode.SelectedCompletionInfo | undefined => {
  if (!selectedCompletionInfo) {
    return undefined;
  }

  return {
    text: selectedCompletionInfo.text,
    range: serializeRange(selectedCompletionInfo.range),
  } as unknown as vscode.SelectedCompletionInfo;
};

export const serializeInlineCompletionContext = (
  inlineCompletionContext: vscode.InlineCompletionContext,
): vscode.InlineCompletionContext => ({
  triggerKind: inlineCompletionContext.triggerKind,
  selectedCompletionInfo: serializeSelectedCompletionInfo(
    inlineCompletionContext.selectedCompletionInfo,
  ),
});