diff --git a/client/src/language/middlewareCompletion.ts b/client/src/language/middlewareCompletion.ts index fa4d768a..9f79aaa0 100644 --- a/client/src/language/middlewareCompletion.ts +++ b/client/src/language/middlewareCompletion.ts @@ -3,18 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import { type CompletionList, Uri, commands } from 'vscode' +import { type CompletionList, Uri, commands, Range } from 'vscode' import { type CompletionMiddleware } from 'vscode-languageclient/node' import { requestsManager } from './RequestManager' -import { getEmbeddedLanguageDocPosition } from './utils' +import { getEmbeddedLanguageDocPosition, getOriginalDocRange } from './utils' +import { getFileContent } from '../lib/src/utils/files' export const middlewareProvideCompletion: CompletionMiddleware['provideCompletionItem'] = async (document, position, context, token, next) => { const embeddedLanguageDocInfos = await requestsManager.getEmbeddedLanguageDocInfos(document.uri.toString(), position) if (embeddedLanguageDocInfos === undefined || embeddedLanguageDocInfos === null) { return await next(document, position, context, token) } - const adjustedPosition = await getEmbeddedLanguageDocPosition(document, embeddedLanguageDocInfos, position) + const embeddedLanguageDocContent = await getFileContent(Uri.parse(embeddedLanguageDocInfos.uri).fsPath) + if (embeddedLanguageDocContent === undefined) { + return + } + const adjustedPosition = getEmbeddedLanguageDocPosition( + document, + embeddedLanguageDocContent, + embeddedLanguageDocInfos.characterIndexes, + position + ) const vdocUri = Uri.parse(embeddedLanguageDocInfos.uri) const result = await commands.executeCommand( 'vscode.executeCompletionItemProvider', @@ -22,5 +32,19 @@ export const middlewareProvideCompletion: CompletionMiddleware['provideCompletio adjustedPosition, context.triggerCharacter ) + result.items.forEach((item) => { + if (item.range === undefined) { + // pass + } else if (item.range instanceof Range) { + item.range = getOriginalDocRange(document, embeddedLanguageDocContent, embeddedLanguageDocInfos.characterIndexes, item.range) + } else { + const inserting = getOriginalDocRange(document, embeddedLanguageDocContent, embeddedLanguageDocInfos.characterIndexes, item.range.inserting) + const replacing = getOriginalDocRange(document, embeddedLanguageDocContent, embeddedLanguageDocInfos.characterIndexes, item.range.replacing) + if (inserting === undefined || replacing === undefined) { + return + } + item.range = { inserting, replacing } + } + }) return result } diff --git a/client/src/language/middlewareHover.ts b/client/src/language/middlewareHover.ts index 51d21fe5..e237383f 100644 --- a/client/src/language/middlewareHover.ts +++ b/client/src/language/middlewareHover.ts @@ -8,13 +8,23 @@ import { type Hover, Uri, commands } from 'vscode' import { requestsManager } from './RequestManager' import { getEmbeddedLanguageDocPosition } from './utils' +import { getFileContent } from '../lib/src/utils/files' export const middlewareProvideHover: HoverMiddleware['provideHover'] = async (document, position, token, next) => { const embeddedLanguageDocInfos = await requestsManager.getEmbeddedLanguageDocInfos(document.uri.toString(), position) if (embeddedLanguageDocInfos === undefined || embeddedLanguageDocInfos === null) { return await next(document, position, token) } - const adjustedPosition = await getEmbeddedLanguageDocPosition(document, embeddedLanguageDocInfos, position) + const embeddedLanguageDocContent = await getFileContent(Uri.parse(embeddedLanguageDocInfos.uri).fsPath) + if (embeddedLanguageDocContent === undefined) { + return + } + const adjustedPosition = getEmbeddedLanguageDocPosition( + document, + embeddedLanguageDocContent, + embeddedLanguageDocInfos.characterIndexes, + position + ) const vdocUri = Uri.parse(embeddedLanguageDocInfos.uri) const result = await commands.executeCommand( 'vscode.executeHoverProvider', diff --git a/client/src/language/utils.ts b/client/src/language/utils.ts index 5fbc27f9..71bb265a 100644 --- a/client/src/language/utils.ts +++ b/client/src/language/utils.ts @@ -3,31 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import fs from 'fs' +import { Position, Range, type TextDocument } from 'vscode' -import { type TextDocument, Position } from 'vscode' +export const getOriginalDocRange = ( + originalTextDocument: TextDocument, + embeddedLanguageDocContent: string, + characterIndexes: number[], + embeddedRange: Range +): Range | undefined => { + const start = getOriginalDocPosition(originalTextDocument, embeddedLanguageDocContent, characterIndexes, embeddedRange.start) + const end = getOriginalDocPosition(originalTextDocument, embeddedLanguageDocContent, characterIndexes, embeddedRange.end) + if (start === undefined || end === undefined) { + return + } + return new Range(start, end) +} -import { type EmbeddedLanguageDocInfos } from '../lib/src/types/embedded-languages' -import { logger } from '../lib/src/utils/OutputLogger' +const getOriginalDocPosition = ( + originalTextDocument: TextDocument, + embeddedLanguageDocContent: string, + characterIndexes: number[], + embeddedPosition: Position +): Position | undefined => { + const embeddedLanguageOffset = getOffset(embeddedLanguageDocContent, embeddedPosition) + const originalOffset = characterIndexes.findIndex(index => index === embeddedLanguageOffset) + if (originalOffset === -1) { + return + } + return originalTextDocument.positionAt(originalOffset) +} -export const getEmbeddedLanguageDocPosition = async ( +export const getEmbeddedLanguageDocPosition = ( originalTextDocument: TextDocument, - embeddedLanguageDocInfos: EmbeddedLanguageDocInfos, + embeddedLanguageDocContent: string, + characterIndexes: number[], originalPosition: Position -): Promise => { +): Position => { const originalOffset = originalTextDocument.offsetAt(originalPosition) - const embeddedLanguageDocOffset = embeddedLanguageDocInfos.characterIndexes[originalOffset] - try { - const embeddedLanguageDocContent = await new Promise((resolve, reject) => { - fs.readFile(embeddedLanguageDocInfos.uri.replace('file://', ''), { encoding: 'utf-8' }, - (error, data) => { error !== null ? reject(error) : resolve(data) } - ) - }) - return getPosition(embeddedLanguageDocContent, embeddedLanguageDocOffset) - } catch (error) { - logger.error(`Failed to get embedded language document position: ${error as any}`) - return undefined - } + const embeddedLanguageDocOffset = characterIndexes[originalOffset] + return getPosition(embeddedLanguageDocContent, embeddedLanguageDocOffset) } const getPosition = (documentContent: string, offset: number): Position => { @@ -43,3 +57,12 @@ const getPosition = (documentContent: string, offset: number): Position => { } return new Position(line, character) } + +const getOffset = (documentContent: string, position: Position): number => { + let offset = 0 + for (let i = 0; i < position.line; i++) { + offset = documentContent.indexOf('\n', offset) + 1 + } + offset += position.character + return offset +} diff --git a/client/src/lib/src/utils/files.ts b/client/src/lib/src/utils/files.ts new file mode 100644 index 00000000..8b3f10ad --- /dev/null +++ b/client/src/lib/src/utils/files.ts @@ -0,0 +1,20 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2023 Savoir-faire Linux. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import fs from 'fs' + +import { logger } from './OutputLogger' + +export const getFileContent = async (path: string): Promise => { + const fileContent = await new Promise((resolve, reject) => { + fs.readFile(path, { encoding: 'utf-8' }, + (error, data) => { error !== null ? reject(error) : resolve(data) } + ) + }).catch(err => { + logger.error(`Could not open file: ${err}`) + return undefined + }) + return fileContent +}