microsoft / vscode-languageserver-node

Language server protocol implementation for VSCode. This allows implementing language services in JS/TS running on node.js
MIT License
1.41k stars 320 forks source link

Interact with the running vscode Language Server #1493

Open ams1 opened 3 weeks ago

ams1 commented 3 weeks ago

Hi πŸ‘‹,

Big THANK YOU for maintaining our essential tooling! 🫑

Context: Although I love and heavily use commands like Find All References, Show Call Hierarchy etc., OFTEN, for documentation purposes I feel the need to EXTRACT those results I see in the references tab in a way that I can do further processing outside vscode: ex. as from - to pairs (function from invokes function to) - so I could build a graph-like document (using graphviz etc.).

My question: Is there a 'simple' way to access the running vscode server instance attached to my current vscode project and programmatically send it commands/be able to capture some json output - instead of interacting with it from the menu?

By 'simple' I mean for example curl or maybe a short extension.

If there is no simple way, could you please point me to where I should start digging regarding the normal way πŸ˜„ - some examples? At first glance it seems extensions are focused more on the language server side - I don't want to maintain the server itself - the one I have running does a great job already - I just want to be able to interact with it programmatically.

Hope I made a good job describing my problem and hope this is a good place to pose the question. πŸ€—

Thank you.

ams1 commented 3 weeks ago

Hi, me again πŸ˜„

In the meantime I found a way (not sure if it's the best) using an extension and the following commands from https://code.visualstudio.com/api/references/commands:

vscode.prepareCallHierarchy - Prepare call hierarchy at a position inside a document

vscode.provideIncomingCalls - Compute incoming calls for an item

vscode.provideOutgoingCalls - Compute outgoing calls for an item

A working snippet (maybe it helps others).

import * as vscode from 'vscode';

async function logOutgoingCalls(item: vscode.CallHierarchyItem) {
    const outgoingCalls: vscode.CallHierarchyOutgoingCall[] = await vscode.commands.executeCommand('vscode.provideOutgoingCalls', item);

    // iterate over outgoingCalls
    for (const outgoingCall of outgoingCalls) {
        console.log(item.name, '-[:invokes]->', outgoingCall.to.name);

        // recursively log outgoing calls
        // TODO: handle cycles
        await logOutgoingCalls(outgoingCall.to);
    }
}

async function logIncomingCalls(item: vscode.CallHierarchyItem) {
    const incomingCalls: vscode.CallHierarchyIncomingCall[] = await vscode.commands.executeCommand('vscode.provideIncomingCalls', item);

    // iterate over incomingCalls
    for (const incomingCall of incomingCalls) {
        console.log(incomingCall.from.name, '-[:invokes]->', item.name);

        // recursively log incoming calls
        // TODO: handle cycles
        await logIncomingCalls(incomingCall.from);
    }
}

export function activate(context: vscode.ExtensionContext) {
    console.log('Congratulations, your extension is now active!');

    let disposable = vscode.commands.registerCommand('extension.logCallHierarchy', async () => {
        if (vscode.window.activeTextEditor) {
            const position = vscode.window.activeTextEditor.selection.active;
            const uri = vscode.window.activeTextEditor.document.uri;

            const items: vscode.CallHierarchyItem[] = await vscode.commands.executeCommand('vscode.prepareCallHierarchy', uri, position);
            if (items) {
                for (const item of items) {

                    // log incoming calls
                    console.log('Incoming calls of ', item.name);
                    await logIncomingCalls(item);

                    // log outgoing calls
                    console.log('Outgoing calls of ', item.name);
                    await logOutgoingCalls(item);
                }
            }
        }
    });

    context.subscriptions.push(disposable);

    let statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
    statusBarItem.command = 'extension.logCallHierarchy';
    statusBarItem.text = "Log Call Hierarchy";
    statusBarItem.show();
    context.subscriptions.push(statusBarItem);
}

export function deactivate() { }

From my point of view this can be closed - thank you.