Skip to content

Commit

Permalink
Add settings to ignore backticks in AI code completion
Browse files Browse the repository at this point in the history
fixed #14461
  • Loading branch information
JonasHelming committed Nov 16, 2024
1 parent be43deb commit b7dd888
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { ILogger } from '@theia/core';
import { ContainerModule } from '@theia/core/shared/inversify';
import { CodeCompletionAgent, CodeCompletionAgentImpl } from '../common/code-completion-agent';
import { CodeCompletionAgent, CodeCompletionAgentImpl } from './code-completion-agent';
import { AIFrontendApplicationContribution } from './ai-code-frontend-application-contribution';
import { FrontendApplicationContribution, KeybindingContribution, PreferenceContribution } from '@theia/core/lib/browser';
import { Agent } from '@theia/ai-core';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AI_CORE_PREFERENCES_TITLE } from '@theia/ai-core/lib/browser/ai-core-pr
export const PREF_AI_INLINE_COMPLETION_ENABLE = 'ai-features.codeCompletion.enableCodeCompletion';
export const PREF_AI_INLINE_COMPLETION_AUTOMATIC_ENABLE = 'ai-features.codeCompletion.automaticCodeCompletion';
export const PREF_AI_INLINE_COMPLETION_EXCLUDED_EXTENSIONS = 'ai-features.codeCompletion.excludedFileExtensions';
export const PREF_AI_INLINE_COMPLETION_STRIP_BACKTICKS = 'ai-features.codeCompletion.stripBackticks';

export const AICodeCompletionPreferencesSchema: PreferenceSchema = {
type: 'object',
Expand All @@ -40,6 +41,14 @@ export const AICodeCompletionPreferencesSchema: PreferenceSchema = {
type: 'string'
},
default: []
},
[PREF_AI_INLINE_COMPLETION_STRIP_BACKTICKS]: {
title: 'Strip Backticks from Inline Completions',
type: 'boolean',
description: 'Remove surrounding backticks from the code returned by some LLMs. If a backtick is detected, all content after the closing\
backtick is stripped as well. This setting helps ensure plain code is returned when language models use markdown-like formatting.',
default: true
}

}
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as monaco from '@theia/monaco-editor-core';

import { inject, injectable } from '@theia/core/shared/inversify';
import { CodeCompletionAgent } from '../common/code-completion-agent';
import { CodeCompletionAgent } from './code-completion-agent';
import { AgentService } from '@theia/ai-core';

@injectable()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { expect } from 'chai';
import { CodeCompletionAgentImpl } from './code-completion-agent';

describe('CodeCompletionAgentImpl', () => {
class TestableCodeCompletionAgent extends CodeCompletionAgentImpl {
public stripBackticksForTest(text: string): string {
return this.stripBackticks(text);
}
}
const agent = new TestableCodeCompletionAgent();

describe('stripBackticks', () => {

it('should remove surrounding backticks and language (TypeScript)', () => {
const input = '```TypeScript\nconsole.log(\"Hello, World!\");```';
const output = agent.stripBackticksForTest(input);
expect(output).to.equal('console.log("Hello, World!");');
});

it('should remove surrounding backticks and language (md)', () => {
const input = '```md\nconsole.log(\"Hello, World!\");```';
const output = agent.stripBackticksForTest(input);
expect(output).to.equal('console.log("Hello, World!");');
});

it('should remove all text after second occurrence of backticks', () => {
const input = '```js\nlet x = 10;\n```\nTrailing text should be removed```';
const output = agent.stripBackticksForTest(input);
expect(output).to.equal('let x = 10;');
});

it('should return the text unchanged if no surrounding backticks', () => {
const input = 'console.log(\"Hello, World!\");';
const output = agent.stripBackticksForTest(input);
expect(output).to.equal('console.log("Hello, World!");');
});

it('should remove surrounding backticks without language', () => {
const input = '```\nconsole.log(\"Hello, World!\");```';
const output = agent.stripBackticksForTest(input);
expect(output).to.equal('console.log("Hello, World!");');
});

it('should handle text starting with backticks but no second delimiter', () => {
const input = '```python\nprint(\"Hello, World!\")';
const output = agent.stripBackticksForTest(input);
expect(output).to.equal('print("Hello, World!")');
});

});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
import { generateUuid, ILogger } from '@theia/core';
import { inject, injectable, named } from '@theia/core/shared/inversify';
import * as monaco from '@theia/monaco-editor-core';
import { PREF_AI_INLINE_COMPLETION_STRIP_BACKTICKS } from './ai-code-completion-preference';
import { PreferenceService } from '@theia/core/lib/browser';

export const CodeCompletionAgent = Symbol('CodeCompletionAgent');
export interface CodeCompletionAgent extends Agent {
Expand All @@ -30,6 +32,10 @@ export interface CodeCompletionAgent extends Agent {

@injectable()
export class CodeCompletionAgentImpl implements CodeCompletionAgent {

@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;

async provideInlineCompletions(
model: monaco.editor.ITextModel,
position: monaco.Position,
Expand Down Expand Up @@ -97,23 +103,39 @@ export class CodeCompletionAgentImpl implements CodeCompletionAgent {
if (token.isCancellationRequested) {
return undefined;
}
const completionText = await getTextOfResponse(response);
let completionText = await getTextOfResponse(response);

if (token.isCancellationRequested) {
return undefined;
}

this.recordingService.recordResponse({
agentId: this.id,
sessionId,
requestId,
response: completionText,
});

if (this.preferenceService.get<boolean>(PREF_AI_INLINE_COMPLETION_STRIP_BACKTICKS, true)) {
completionText = this.stripBackticks(completionText);
}

return {
items: [{ insertText: completionText }],
enableForwardStability: true,
};
}

protected stripBackticks(text: string): string {
if (text.startsWith('```')) {
// Remove the first backticks and any language identifier
const startRemoved = text.slice(3).replace(/^\w*\n/, '');
const secondBacktickIndex = startRemoved.indexOf('```');
return secondBacktickIndex !== -1 ? startRemoved.slice(0, secondBacktickIndex).trim() : startRemoved.trim();
}
return text;
}

@inject(ILogger)
@named('code-completion-agent')
protected logger: ILogger;
Expand Down

0 comments on commit b7dd888

Please sign in to comment.