Search code examples
typescriptvisual-studio-codevscode-extensionslanguage-server-protocol

How to get current text/symbol when implementing Go To Definition


I'm working on my first vscode extension using Language Server Protocol, I need to get the text were the Right click -> Go to definition was triggered

enter image description here

My current onDefinition method receives only the textPosition

export default class DefinitionFinder extends Handler {
    constructor(
        protected connection: Connection,
        private refManager: ReferenceManager)
    {
        super();

        this.connection.onDefinition(async (textPosition) => {
            return this.handleErrors(
                this.getDefinition(textPosition), undefined) as Promise<Definition>;
        });
    }
    
private async getDefinition(textPosition: TextDocumentPositionParams): Promise<Location[]> {
    const text = "funABC";

    // instead of hardcoded value I need to get the text/symbol 
    // that is going to be use to go to definition 

    return this.refManager.getDefinitionLocations(text);
}

The TextDocumentPositionParams only contains the documentUri, line(number) and character(number)

Does that mean that on each call to onDefinition I need to open the document, go to the line and character and get the current word ?

export interface TextDocumentPositionParams {
    /**
     * The text document.
     */
    textDocument: TextDocumentIdentifier;
    /**
     * The position inside the text document.
     */
    position: Position;
}

export interface TextDocumentIdentifier {
    /**
     * The text document's uri. (string)
     */
    uri: DocumentUri;
}

export declare namespace Position {
    /**
     * Creates a new Position literal from the given line and character.
     * @param line The position's line.
     * @param character The position's character.
     */

Solution

  • This is how I ended up implementing the logic to get the word/symbol when "Go To Definition" is requested on a Language Server

    I decided to return a LocationLink which also appears as a hyperlink over the word/symbol

    this.connection.onDefinition(async (textPosition) => {
        return this.handleErrors(
            this.getDefinitionLink(textPosition), undefined) as Promise<LocationLink[]>;
    });
    

    This method get the symbol from the document and then look its definition in the refManager

    private async getDefinitionLink(textPosition: TextDocumentPositionParams): Promise<LocationLink[]> {
        const symbol = this.getSymbolAtPosition(textPosition.position, textPosition.textDocument.uri);
    
        return this.refManager.getDefinitionLink(symbol);
    }
    

    This is the actual logic to extract the current word no matter if it was click in the middle of it.

    
    let TokenSeparatorsXML = /[\t= <>"]/;
    
    public getSymbolAtPosition(position: Position, documentUri: string): string {
        const range = {
            start: { line: position.line, character: 0},
            end: { line: position.line, character: Number.MAX_VALUE  }
        };
        //get the whole line 
        const txtDoc = this.refManager.getDocument(documentUri)!;
    
        const context = txtDoc.getText(range);
        const offset = position.character ;
    
        let start = offset-1;
    
        while ((start > 0) && !context[start].match(this.TokenSeparatorsXML))
        {
            start--;
        }
    
        let end = offset;
    
        while ((end < context.length) && !context[end].match(this.TokenSeparatorsXML))
        {
            end++;
        }
    
        const symbol = context.substr(start + 1, end - start - 1);
    
        console.log(`${start}->${end}- symbol: ${symbol}`);
    
        return symbol;
    }
    

    In my case the token separators are very narrow but you should consider using the word pattern https://code.visualstudio.com/api/language-extensions/language-configuration-guide#word-pattern

    The full code of the extension can be found here - https://github.com/mauriciogracia/petrel-xml