diff --git a/src/language/definition.ts b/src/language/definition.ts index 99a03430e..360b3dcb8 100644 --- a/src/language/definition.ts +++ b/src/language/definition.ts @@ -40,52 +40,89 @@ export class DefinitionProvider implements vscode.DefinitionProvider { return } - async provideDefinition(document: vscode.TextDocument, position: vscode.Position): Promise { + /** + * VSCode hook to provide definitions of the symbol at `position`. + * In LW these can be custom commands, labels, citations, glossary entries, and file names. + * + * Also provides the exact range of the found symbol (`originSelectionRange`), + * as different symbol types support different characters in LaTeX (esp. regarding `[:-]`) + * + * @param document The document to be scanned. + * @param position The position to be scanned at. + * + * @returns {DefinitionLink[]} linking `originSelectionRange` to `targetUri`/`targetRange` + */ + async provideDefinition(document: vscode.TextDocument, position: vscode.Position): Promise { if (document.uri.scheme !== 'file') { - return + return [] } - const token = tokenizer(document, position) - if (token === undefined) { - return + const tokenRange = tokenizer(document, position) + if (tokenRange === undefined) { + return [] } + const token = document.getText(tokenRange) if (token.startsWith('\\')) { const macro = lw.completion.macro.getData().definedCmds.get(token.slice(1)) if (macro) { - return macro.location + return [{ + targetUri: macro.location.uri, + targetRange: macro.location.range, + originSelectionRange: tokenRange + }] } - return + return [] } const ref = lw.completion.reference.getItem(token) if (ref) { - return new vscode.Location(vscode.Uri.file(ref.file), ref.position) + return [{ + targetUri: vscode.Uri.file(ref.file), + targetRange: new vscode.Range(ref.position, ref.position), + originSelectionRange: tokenRange + }] } const cite = lw.completion.citation.getItem(token) if (cite) { - return new vscode.Location(vscode.Uri.file(cite.file), cite.position) + return [{ + targetUri: vscode.Uri.file(cite.file), + targetRange: new vscode.Range(cite.position, cite.position), + originSelectionRange: tokenRange + }] } const glossary = lw.completion.glossary.getItem(token) if (glossary) { - return new vscode.Location(vscode.Uri.file(glossary.filePath), glossary.position) + return [{ + targetUri: vscode.Uri.file(glossary.filePath), + targetRange: new vscode.Range(glossary.position, glossary.position), + originSelectionRange: tokenRange + }] } if (vscode.window.activeTextEditor && token.includes('.')) { // We skip graphics files const graphicsExtensions = ['.pdf', '.eps', '.jpg', '.jpeg', '.JPG', '.JPEG', '.gif', '.png'] const ext = path.extname(token) if (graphicsExtensions.includes(ext)) { - return + return [] } const absolutePath = path.resolve(path.dirname(vscode.window.activeTextEditor.document.fileName), token) if (fs.existsSync(absolutePath)) { - return new vscode.Location( vscode.Uri.file(absolutePath), new vscode.Position(0, 0) ) + return [{ + targetUri: vscode.Uri.file(absolutePath), + targetRange: new vscode.Range(0, 0, 0, 0), + originSelectionRange: tokenRange + }] } } const filename = await this.onAFilename(document, position, token) if (filename) { - return new vscode.Location( vscode.Uri.file(filename), new vscode.Position(0, 0) ) + return [{ + targetUri: vscode.Uri.file(filename), + targetRange: new vscode.Range(0, 0, 0, 0), + originSelectionRange: tokenRange + }] } - return + return [] } } diff --git a/src/preview/hover.ts b/src/preview/hover.ts index 0b7ba0303..00d454eb0 100644 --- a/src/preview/hover.ts +++ b/src/preview/hover.ts @@ -33,7 +33,7 @@ class HoverProvider implements vscode.HoverProvider { return graphicsHover } } - const token = tokenizer(document, position) + const token = document.getText(tokenizer(document, position)) if (!token) { return } diff --git a/src/utils/tokenizer.ts b/src/utils/tokenizer.ts index bb88c9642..4f6fd39a8 100644 --- a/src/utils/tokenizer.ts +++ b/src/utils/tokenizer.ts @@ -2,13 +2,13 @@ import * as vscode from 'vscode' import * as utils from './utils' /** - * If a string on `position` is like `\macro`, `\macro{` or `\macro[`, - * return `\macro`. + * If the string at `position` is a latex command, e.g., `\macro`, `\macro{` or `\macro[`, + * return the range of the command string (`\macro`). * * @param document The document to be scanned. * @param position The position to be scanned at. */ -function macroTokenizer(document: vscode.TextDocument, position: vscode.Position): string | undefined { +function macroTokenizer(document: vscode.TextDocument, position: vscode.Position): vscode.Range | undefined { let startRegex: RegExp if (document.languageId === 'latex-expl3') { startRegex = /\\(?=[^\\{},[\]]*$)/ @@ -21,26 +21,26 @@ function macroTokenizer(document: vscode.TextDocument, position: vscode.Position } const firstBracket = document.getText(new vscode.Range(position, new vscode.Position(position.line, 65535))).match(/[[{]/) if (firstBracket && firstBracket.index !== undefined && firstBracket.index > 0) { - return document.getText(new vscode.Range( + return new vscode.Range( new vscode.Position(position.line, startResult.index), new vscode.Position(position.line, position.character + firstBracket.index) - )).trim() + ) } const wordRange = document.getWordRangeAtPosition(position) if (wordRange) { - return document.getText(wordRange.with(new vscode.Position(position.line, startResult.index))).trim() + return wordRange.with(new vscode.Position(position.line, startResult.index)) } return } /** - * If a string on `position` surround by `{...}` or `[...]`, - * return the string inside brackets. + * If the string at `position` is surround by `{...}` or `[...]`, + * return the range for the argument at `position` inside the brackets. * * @param document The document to be scanned. * @param position The position to be scanned at. */ -function argTokenizer(document: vscode.TextDocument, position: vscode.Position): string | undefined { +function argTokenizer(document: vscode.TextDocument, position: vscode.Position): vscode.Range | undefined { const startResult = document.getText(new vscode.Range(new vscode.Position(position.line, 0), position)).match(/[{,[](?=[^{},[\]]*$)/) if (startResult === null || startResult.index === undefined || startResult.index < 0) { return @@ -49,22 +49,22 @@ function argTokenizer(document: vscode.TextDocument, position: vscode.Position): if (endResult === null || endResult.index === undefined || endResult.index < 0) { return } - return document.getText(new vscode.Range( + return new vscode.Range( new vscode.Position(position.line, startResult.index + 1), new vscode.Position(position.line, position.character + endResult.index) - )).trim() + ) } /** - * If a string on `position` is like `\macro{` or `\macro[`, then - * returns the `\macro`. If it is like `{...}` or `[...]`, then - * returns the string inside brackets. + * If the string at `position` is like `\macro{` or `\macro[`, then + * returns the range for `\macro`. If it is like `{...}` or `[...]`, then + * returns the range the argument inside brackets. * * @param document The document to be scanned. * @param position The position to be scanned at. */ -export function tokenizer(document: vscode.TextDocument, position: vscode.Position): string | undefined { +export function tokenizer(document: vscode.TextDocument, position: vscode.Position): vscode.Range | undefined { // \macro case const macroToken = macroTokenizer(document, position) if (macroToken) {