From 64f196ca6e877152a771150ee73e01a6129dc9ee Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Mon, 21 Apr 2025 15:18:34 -0700 Subject: [PATCH 1/2] feat(lsp): add handler for openFileDiff notification --- packages/amazonq/src/lsp/chat/messages.ts | 32 +++++++++- packages/core/src/amazonq/index.ts | 1 + packages/core/src/shared/index.ts | 3 +- .../shared/utilities/textDocumentUtilities.ts | 64 ++++++++++++++++--- 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 82bccd68b06..456c5e3fa94 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -39,6 +39,8 @@ import { ShowDocumentRequest, contextCommandsNotificationType, ContextCommandParams, + openFileDiffNotificationType, + OpenFileDiffParams, } from '@aws/language-server-runtimes/protocol' import { v4 as uuidv4 } from 'uuid' import * as vscode from 'vscode' @@ -46,8 +48,16 @@ import { Disposable, LanguageClient, Position, TextDocumentIdentifier } from 'vs import * as jose from 'jose' import { AmazonQChatViewProvider } from './webviewProvider' import { AuthUtil } from 'aws-core-vscode/codewhisperer' -import { AmazonQPromptSettings, messages } from 'aws-core-vscode/shared' -import { DefaultAmazonQAppInitContext, messageDispatcher } from 'aws-core-vscode/amazonq' +import { + amazonQDiffScheme, + AmazonQPromptSettings, + messages, + textDocumentUtil, + amazonQTabSuffix, + disposeOnEditorClose, +} from 'aws-core-vscode/shared' +import { ContentProvider, DefaultAmazonQAppInitContext, messageDispatcher } from 'aws-core-vscode/amazonq' +import path from 'path' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -353,6 +363,24 @@ export function registerMessageListeners( params: params, }) }) + + languageClient.onNotification(openFileDiffNotificationType.method, async (params: OpenFileDiffParams) => { + const uri = vscode.Uri.parse(params.originalFileUri) + const tempFileUri = await textDocumentUtil.createTempFileForDiffWithContent( + uri, + params.fileContent ?? '', + amazonQDiffScheme + ) + const contentProvider = new ContentProvider(tempFileUri) + const disposable = vscode.workspace.registerTextDocumentContentProvider(amazonQDiffScheme, contentProvider) + await vscode.commands.executeCommand( + 'vscode.diff', + uri, + tempFileUri, + `${path.basename(params.originalFileUri)} ${amazonQTabSuffix}` + ) + disposeOnEditorClose(uri, disposable) + }) } function isServerEvent(command: string) { diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts index e38eec98035..0afa76d7962 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -47,6 +47,7 @@ export * as authConnection from '../auth/connection' export * as featureConfig from './webview/generators/featureConfig' export * as messageDispatcher from './webview/messages/messageDispatcher' import { FeatureContext } from '../shared/featureConfig' +export { ContentProvider } from './commons/controllers/contentController' /** * main from createMynahUI is a purely browser dependency. Due to this diff --git a/packages/core/src/shared/index.ts b/packages/core/src/shared/index.ts index 4cda5285f69..833326255dd 100644 --- a/packages/core/src/shared/index.ts +++ b/packages/core/src/shared/index.ts @@ -56,7 +56,7 @@ export * as funcUtil from './utilities/functionUtils' export { fs } from './fs/fs' export * from './handleUninstall' export { CrashMonitoring } from './crashMonitoring' -export { amazonQDiffScheme } from './constants' +export { amazonQDiffScheme, amazonQTabSuffix } from './constants' export * from './featureConfig' export { i18n } from './i18n-helper' export * from './icons' @@ -74,3 +74,4 @@ export * as BaseLspInstaller from './lsp/baseLspInstaller' export * as collectionUtil from './utilities/collectionUtils' export * from './datetime' export * from './performance/marks' +export { disposeOnEditorClose } from './utilities/editorUtilities' diff --git a/packages/core/src/shared/utilities/textDocumentUtilities.ts b/packages/core/src/shared/utilities/textDocumentUtilities.ts index 48a20a6c44b..24126af4530 100644 --- a/packages/core/src/shared/utilities/textDocumentUtilities.ts +++ b/packages/core/src/shared/utilities/textDocumentUtilities.ts @@ -119,20 +119,17 @@ export async function applyChanges(doc: vscode.TextDocument, range: vscode.Range } /** - * Creates a temporary file for diff comparison by cloning the original file - * and applying the proposed changes within the selected range. + * Creates a temporary file for diff comparison by cloning the original file. + * This is the base implementation used by other diff-related functions. * * @param {vscode.Uri} originalFileUri - The URI of the original file. - * @param {any} message - The message object containing the proposed code changes. - * @param {vscode.Selection} selection - The selection range in the document where the changes are applied. - * @returns {Promise} - A promise that resolves to the URI of the temporary file. + * @param {string} scheme - The URI scheme to use for the temporary file. + * @returns {Promise<{tempFileUri: vscode.Uri, doc: vscode.TextDocument}>} - A promise that resolves to the URI of the temporary file and the document. */ -export async function createTempFileForDiff( +async function createTempFileForDiffBase( originalFileUri: vscode.Uri, - message: any, - selection: vscode.Selection, scheme: string -): Promise { +): Promise<{ tempFileUri: vscode.Uri; doc: vscode.TextDocument }> { const errorCode = 'createTempFile' const id = Date.now() const languageId = (await vscode.workspace.openTextDocument(originalFileUri)).languageId @@ -152,7 +149,7 @@ export async function createTempFileForDiff( throw ToolkitError.chain(error, 'Failed to write to temp file', { code: errorCode }) } - // Apply the proposed changes to the temp file + // Open the temp file as a document const doc = await vscode.workspace.openTextDocument(tempFileUri.path) const languageIdStatus = await vscode.languages.setTextDocumentLanguage(doc, languageId) if (languageIdStatus) { @@ -161,6 +158,27 @@ export async function createTempFileForDiff( getLogger().error('Diff: Unable to set languageId for %s to: %s', tempFileUri.fsPath, languageId) } + return { tempFileUri, doc } +} + +/** + * Creates a temporary file for diff comparison by cloning the original file + * and applying the proposed changes within the selected range. + * + * @param {vscode.Uri} originalFileUri - The URI of the original file. + * @param {any} message - The message object containing the proposed code changes. + * @param {vscode.Selection} selection - The selection range in the document where the changes are applied. + * @param {string} scheme - The URI scheme to use for the temporary file. + * @returns {Promise} - A promise that resolves to the URI of the temporary file. + */ +export async function createTempFileForDiff( + originalFileUri: vscode.Uri, + message: any, + selection: vscode.Selection, + scheme: string +): Promise { + const { tempFileUri, doc } = await createTempFileForDiffBase(originalFileUri, scheme) + const code = getIndentedCode(message, doc, selection) const range = getSelectionFromRange(doc, selection) @@ -168,6 +186,32 @@ export async function createTempFileForDiff( return tempFileUri } +/** + * Creates a temporary file for diff comparison by cloning the original file + * and replacing the entire content with the provided content. + * + * @param {vscode.Uri} originalFileUri - The URI of the original file. + * @param {string} content - The content to replace the entire document with. + * @param {string} scheme - The URI scheme to use for the temporary file. + * @returns {Promise} - A promise that resolves to the URI of the temporary file. + */ +export async function createTempFileForDiffWithContent( + originalFileUri: vscode.Uri, + content: string, + scheme: string +): Promise { + const { tempFileUri, doc } = await createTempFileForDiffBase(originalFileUri, scheme) + + // Create a range that covers the entire document + const entireDocumentRange = new vscode.Range( + new vscode.Position(0, 0), + new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length) + ) + + await applyChanges(doc, entireDocumentRange, content) + return tempFileUri +} + /** * Indents the given code based on the current document's indentation at the selection start. * From 1910cbf436055c39e0cc3ad17b288d7fc50ba22e Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Mon, 21 Apr 2025 17:34:41 -0700 Subject: [PATCH 2/2] reuse contentController --- packages/amazonq/src/lsp/chat/messages.ts | 39 +++++------ packages/core/src/amazonq/index.ts | 2 +- packages/core/src/shared/index.ts | 3 +- .../shared/utilities/textDocumentUtilities.ts | 64 +++---------------- 4 files changed, 27 insertions(+), 81 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 456c5e3fa94..4865c1046d4 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -48,16 +48,8 @@ import { Disposable, LanguageClient, Position, TextDocumentIdentifier } from 'vs import * as jose from 'jose' import { AmazonQChatViewProvider } from './webviewProvider' import { AuthUtil } from 'aws-core-vscode/codewhisperer' -import { - amazonQDiffScheme, - AmazonQPromptSettings, - messages, - textDocumentUtil, - amazonQTabSuffix, - disposeOnEditorClose, -} from 'aws-core-vscode/shared' -import { ContentProvider, DefaultAmazonQAppInitContext, messageDispatcher } from 'aws-core-vscode/amazonq' -import path from 'path' +import { AmazonQPromptSettings, messages } from 'aws-core-vscode/shared' +import { DefaultAmazonQAppInitContext, messageDispatcher, EditorContentController } from 'aws-core-vscode/amazonq' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -365,21 +357,20 @@ export function registerMessageListeners( }) languageClient.onNotification(openFileDiffNotificationType.method, async (params: OpenFileDiffParams) => { - const uri = vscode.Uri.parse(params.originalFileUri) - const tempFileUri = await textDocumentUtil.createTempFileForDiffWithContent( - uri, - params.fileContent ?? '', - amazonQDiffScheme - ) - const contentProvider = new ContentProvider(tempFileUri) - const disposable = vscode.workspace.registerTextDocumentContentProvider(amazonQDiffScheme, contentProvider) - await vscode.commands.executeCommand( - 'vscode.diff', - uri, - tempFileUri, - `${path.basename(params.originalFileUri)} ${amazonQTabSuffix}` + const edc = new EditorContentController() + const uri = params.originalFileUri + const doc = await vscode.workspace.openTextDocument(uri) + const entireDocumentSelection = new vscode.Selection( + new vscode.Position(0, 0), + new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length) ) - disposeOnEditorClose(uri, disposable) + await edc.viewDiff({ + context: { + activeFileContext: { filePath: params.originalFileUri }, + focusAreaContext: { selectionInsideExtendedCodeBlock: entireDocumentSelection }, + }, + code: params.fileContent, + }) }) } diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts index 0afa76d7962..8a4815c1577 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -47,7 +47,7 @@ export * as authConnection from '../auth/connection' export * as featureConfig from './webview/generators/featureConfig' export * as messageDispatcher from './webview/messages/messageDispatcher' import { FeatureContext } from '../shared/featureConfig' -export { ContentProvider } from './commons/controllers/contentController' +export { EditorContentController } from './commons/controllers/contentController' /** * main from createMynahUI is a purely browser dependency. Due to this diff --git a/packages/core/src/shared/index.ts b/packages/core/src/shared/index.ts index 833326255dd..4cda5285f69 100644 --- a/packages/core/src/shared/index.ts +++ b/packages/core/src/shared/index.ts @@ -56,7 +56,7 @@ export * as funcUtil from './utilities/functionUtils' export { fs } from './fs/fs' export * from './handleUninstall' export { CrashMonitoring } from './crashMonitoring' -export { amazonQDiffScheme, amazonQTabSuffix } from './constants' +export { amazonQDiffScheme } from './constants' export * from './featureConfig' export { i18n } from './i18n-helper' export * from './icons' @@ -74,4 +74,3 @@ export * as BaseLspInstaller from './lsp/baseLspInstaller' export * as collectionUtil from './utilities/collectionUtils' export * from './datetime' export * from './performance/marks' -export { disposeOnEditorClose } from './utilities/editorUtilities' diff --git a/packages/core/src/shared/utilities/textDocumentUtilities.ts b/packages/core/src/shared/utilities/textDocumentUtilities.ts index 24126af4530..48a20a6c44b 100644 --- a/packages/core/src/shared/utilities/textDocumentUtilities.ts +++ b/packages/core/src/shared/utilities/textDocumentUtilities.ts @@ -119,17 +119,20 @@ export async function applyChanges(doc: vscode.TextDocument, range: vscode.Range } /** - * Creates a temporary file for diff comparison by cloning the original file. - * This is the base implementation used by other diff-related functions. + * Creates a temporary file for diff comparison by cloning the original file + * and applying the proposed changes within the selected range. * * @param {vscode.Uri} originalFileUri - The URI of the original file. - * @param {string} scheme - The URI scheme to use for the temporary file. - * @returns {Promise<{tempFileUri: vscode.Uri, doc: vscode.TextDocument}>} - A promise that resolves to the URI of the temporary file and the document. + * @param {any} message - The message object containing the proposed code changes. + * @param {vscode.Selection} selection - The selection range in the document where the changes are applied. + * @returns {Promise} - A promise that resolves to the URI of the temporary file. */ -async function createTempFileForDiffBase( +export async function createTempFileForDiff( originalFileUri: vscode.Uri, + message: any, + selection: vscode.Selection, scheme: string -): Promise<{ tempFileUri: vscode.Uri; doc: vscode.TextDocument }> { +): Promise { const errorCode = 'createTempFile' const id = Date.now() const languageId = (await vscode.workspace.openTextDocument(originalFileUri)).languageId @@ -149,7 +152,7 @@ async function createTempFileForDiffBase( throw ToolkitError.chain(error, 'Failed to write to temp file', { code: errorCode }) } - // Open the temp file as a document + // Apply the proposed changes to the temp file const doc = await vscode.workspace.openTextDocument(tempFileUri.path) const languageIdStatus = await vscode.languages.setTextDocumentLanguage(doc, languageId) if (languageIdStatus) { @@ -158,27 +161,6 @@ async function createTempFileForDiffBase( getLogger().error('Diff: Unable to set languageId for %s to: %s', tempFileUri.fsPath, languageId) } - return { tempFileUri, doc } -} - -/** - * Creates a temporary file for diff comparison by cloning the original file - * and applying the proposed changes within the selected range. - * - * @param {vscode.Uri} originalFileUri - The URI of the original file. - * @param {any} message - The message object containing the proposed code changes. - * @param {vscode.Selection} selection - The selection range in the document where the changes are applied. - * @param {string} scheme - The URI scheme to use for the temporary file. - * @returns {Promise} - A promise that resolves to the URI of the temporary file. - */ -export async function createTempFileForDiff( - originalFileUri: vscode.Uri, - message: any, - selection: vscode.Selection, - scheme: string -): Promise { - const { tempFileUri, doc } = await createTempFileForDiffBase(originalFileUri, scheme) - const code = getIndentedCode(message, doc, selection) const range = getSelectionFromRange(doc, selection) @@ -186,32 +168,6 @@ export async function createTempFileForDiff( return tempFileUri } -/** - * Creates a temporary file for diff comparison by cloning the original file - * and replacing the entire content with the provided content. - * - * @param {vscode.Uri} originalFileUri - The URI of the original file. - * @param {string} content - The content to replace the entire document with. - * @param {string} scheme - The URI scheme to use for the temporary file. - * @returns {Promise} - A promise that resolves to the URI of the temporary file. - */ -export async function createTempFileForDiffWithContent( - originalFileUri: vscode.Uri, - content: string, - scheme: string -): Promise { - const { tempFileUri, doc } = await createTempFileForDiffBase(originalFileUri, scheme) - - // Create a range that covers the entire document - const entireDocumentRange = new vscode.Range( - new vscode.Position(0, 0), - new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length) - ) - - await applyChanges(doc, entireDocumentRange, content) - return tempFileUri -} - /** * Indents the given code based on the current document's indentation at the selection start. *