// ***************************************************************************** // Copyright (C) 2025 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 { FrontendApplicationContribution } from '@theia/core/lib/browser'; import { CommandService } from '@theia/core/lib/common/command'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { inject, injectable } from '@theia/core/shared/inversify'; import * as monaco from '@theia/monaco-editor-core'; import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service'; import { AIActivationService } from '@theia/ai-core/lib/browser/ai-activation-service'; import { nls } from '@theia/core'; export const AI_EDITOR_SEND_TO_CHAT = { id: 'ai-editor.sendToChat', }; @injectable() export class AICodeActionProvider implements FrontendApplicationContribution { @inject(CommandService) protected readonly commandService: CommandService; @inject(MonacoEditorService) protected readonly monacoEditorService: MonacoEditorService; @inject(AIActivationService) protected readonly activationService: AIActivationService; protected readonly toDispose = new DisposableCollection(); onStart(): void { this.registerCodeActionProvider(); // Listen to AI activation changes and re-register the provider this.activationService.onDidChangeCanRun(() => { this.toDispose.dispose(); this.registerCodeActionProvider(); }); } dispose(): void { this.toDispose.dispose(); } protected registerCodeActionProvider(): void { if (!this.activationService.canRun) { // AI is disabled, don't register the provider return; } const disposable = monaco.languages.registerCodeActionProvider('*', { provideCodeActions: (model, range, context, token) => { // Double-check activation status in the provider if (!this.activationService.canRun) { return { actions: [], dispose: () => { } }; } // Filter for error markers only const errorMarkers = context.markers.filter(marker => marker.severity === monaco.MarkerSeverity.Error); if (errorMarkers.length === 0) { return { actions: [], dispose: () => { } }; } const actions: monaco.languages.CodeAction[] = []; // Create code actions for each error marker: Fix with AI and Explain with AI errorMarkers.forEach(marker => { actions.push({ title: nls.localizeByDefault('Fix with AI'), diagnostics: [marker], isAI: true, kind: 'quickfix', command: { id: AI_EDITOR_SEND_TO_CHAT.id, title: nls.localizeByDefault('Fix with AI'), arguments: [{ prompt: `@Coder ${nls.localize('theia/ai/editor/fixWithAI/prompt', 'Help to fix this error')}: "${marker.message}"` }] } }); actions.push({ title: nls.localize('theia/ai/editor/explainWithAI/title', 'Explain with AI'), diagnostics: [marker], kind: 'quickfix', isAI: true, command: { id: AI_EDITOR_SEND_TO_CHAT.id, title: nls.localize('theia/ai/editor/explainWithAI/title', 'Explain with AI'), arguments: [{ prompt: `@Architect ${nls.localize('theia/ai/editor/explainWithAI/prompt', 'Explain this error')}: "${marker.message}"` }] } }); }); return { actions: actions, dispose: () => { } }; } }); this.toDispose.push(disposable); } }