Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix link highlighting in \ref and \input for non-word characters #4448

Merged
merged 1 commit into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 51 additions & 14 deletions src/language/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,52 +40,89 @@ export class DefinitionProvider implements vscode.DefinitionProvider {
return
}

async provideDefinition(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.Location | undefined> {
/**
* 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<vscode.DefinitionLink[]> {
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 []
}

}
2 changes: 1 addition & 1 deletion src/preview/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
30 changes: 15 additions & 15 deletions src/utils/tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = /\\(?=[^\\{},[\]]*$)/
Expand All @@ -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
Expand All @@ -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) {
Expand Down
Loading