// ***************************************************************************** // Copyright (C) 2025 Lonti.com Pty Ltd. // // 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 { AIVariableContext, AIVariableResolutionRequest, AIVariableResolver, ResolvedAIVariable } from '@theia/ai-core'; import { FrontendVariableContribution, FrontendVariableService } from '@theia/ai-core/lib/browser'; import { MaybePromise, PreferenceService } from '@theia/core'; import { inject, injectable } from '@theia/core/shared/inversify'; import { PREF_AI_INLINE_COMPLETION_MAX_CONTEXT_LINES } from '../common/ai-code-completion-preference'; import { CodeCompletionVariableContext } from './code-completion-variable-context'; import { FILE, LANGUAGE, PREFIX, SUFFIX } from './code-completion-variables'; @injectable() export class CodeCompletionVariableContribution implements FrontendVariableContribution, AIVariableResolver { @inject(PreferenceService) protected preferences: PreferenceService; registerVariables(service: FrontendVariableService): void { [ FILE, PREFIX, SUFFIX, LANGUAGE ].forEach(variable => { service.registerResolver(variable, this); }); } canResolve(_request: AIVariableResolutionRequest, context: AIVariableContext): MaybePromise { return CodeCompletionVariableContext.is(context) ? 1 : 0; } async resolve(request: AIVariableResolutionRequest, context: AIVariableContext): Promise { if (!CodeCompletionVariableContext.is(context)) { return Promise.resolve(undefined); } switch (request.variable.id) { case FILE.id: return this.resolveFile(context); case LANGUAGE.id: return this.resolveLanguage(context); case PREFIX.id: return this.resolvePrefix(context); case SUFFIX.id: return this.resolveSuffix(context); default: return undefined; } } protected resolvePrefix(context: CodeCompletionVariableContext): ResolvedAIVariable | undefined { const position = context.position; const model = context.model; const maxContextLines = this.preferences.get(PREF_AI_INLINE_COMPLETION_MAX_CONTEXT_LINES, -1); let prefixStartLine = 1; if (maxContextLines === 0) { // Only the cursor line prefixStartLine = position.lineNumber; } else if (maxContextLines > 0) { const linesBeforeCursor = position.lineNumber - 1; // Allocate one more line to the prefix in case of an odd maxContextLines const prefixLines = Math.min( Math.ceil(maxContextLines / 2), linesBeforeCursor ); prefixStartLine = Math.max(1, position.lineNumber - prefixLines); } const prefix = model.getValueInRange({ startLineNumber: prefixStartLine, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column, }); return { variable: PREFIX, value: prefix }; } protected resolveSuffix(context: CodeCompletionVariableContext): ResolvedAIVariable | undefined { const position = context.position; const model = context.model; const maxContextLines = this.preferences.get(PREF_AI_INLINE_COMPLETION_MAX_CONTEXT_LINES, -1); let suffixEndLine = model.getLineCount(); if (maxContextLines === 0) { suffixEndLine = position.lineNumber; } else if (maxContextLines > 0) { const linesAfterCursor = model.getLineCount() - position.lineNumber; const suffixLines = Math.min( Math.floor(maxContextLines / 2), linesAfterCursor ); suffixEndLine = Math.min(model.getLineCount(), position.lineNumber + suffixLines); } const suffix = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: position.column, endLineNumber: suffixEndLine, endColumn: model.getLineMaxColumn(suffixEndLine), }); return { variable: SUFFIX, value: suffix }; } protected resolveLanguage(context: CodeCompletionVariableContext): ResolvedAIVariable | undefined { return { variable: LANGUAGE, value: context.model.getLanguageId() }; } protected resolveFile(context: CodeCompletionVariableContext): ResolvedAIVariable | undefined { return { variable: FILE, value: context.model.uri.toString(false) }; } }