clangd / vscode-clangd

Visual Studio Code extension for clangd
https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd
MIT License
631 stars 110 forks source link

How to add new Feature to clangd by other extension #668

Open wss29 opened 2 months ago

wss29 commented 2 months ago

Note: we mostly implement features in the clangd language server, and rely on Microsoft's LSP client framework to expose these in VSCode. Features requiring a lot of VSCode-specific work are unlikely to be implemented. Hi,

  1. As we know there is some feature added inside clangd image
  2. How could I do if I want to add my custom feature to reuse clangd in my extension?
  3. I try to create my clanged context like clangd, but when I install my extension and vscode-clangd I will get Error: command 'clangd.applyFix' already exists. is it possible to install these two extension meanwhile?

    export class MyClangdContext implements vscode.Disposable {
    subscriptions: vscode.Disposable[] = [];
    client!: MyClangdLanguageClient;
    
    async activate(globalStoragePath: string, outputChannel: vscode.OutputChannel) {
        const clangdPath = vscode.workspace.getConfiguration('clangd').get<string>('path');
        if (!clangdPath) {
            return;
        }
    
        const clangd: vscodelc.Executable = {
            command: clangdPath,
            args: await config.get<string[]>('arguments'),
            options: { cwd: vscode.workspace.rootPath || process.cwd() },
        };
        const traceFile = config.get<string>('trace');
        if (!!traceFile) {
            const trace = { CLANGD_TRACE: traceFile };
            clangd.options = { env: { ...process.env, ...trace } };
        }
        const serverOptions: vscodelc.ServerOptions = clangd;
    
        const clientOptions: vscodelc.LanguageClientOptions = {
            // Register the server for c-family and cuda files.
            documentSelector: clangdDocumentSelector,
            initializationOptions: {
                clangdFileStatus: true,
                fallbackFlags: config.get<string[]>('fallbackFlags'),
            },
            outputChannel: outputChannel,
            // Do not switch to output window when clangd returns output.
            revealOutputChannelOn: vscodelc.RevealOutputChannelOn.Never,
        };
    
        this.client = new MyClangdLanguageClient('my Clang Language Server', serverOptions, clientOptions);
        this.client.clientOptions.errorHandler = this.client.createDefaultErrorHandler(
            // max restart count
            config.get<boolean>('restartAfterCrash') ? /*default*/ 4 : 0
        );
        myfeature.activate(this);
        this.client.start();
        console.log('my Clang Language Server is now active!');
    }

    image

HighCommander4 commented 2 months ago

Can you give an example of a feature you have in mind?

Is your feature's implementation going to be talking to the clangd server? If so, are you using a forked/modified clangd server that e.g. supports a new kind of LSP request?

wss29 commented 2 months ago

Hi @HighCommander4 my feature needs to extract functions in the c/c++ file, so I need to talk to the clangd server, just like the internal ast feature but need some custom operation after getting ast node. currently, I just use the original clangd server, do not have new LSP request. maybe in the future, I need new LSP request

HighCommander4 commented 2 months ago

Can you use the extension API shown here (see also this file) to ask clangd for an AST and then do your processing on it and provide the user feature you're looking for?

wss29 commented 2 months ago

hi @HighCommander4 thanks for your replay. It seems that should work, but how could I use that API, I do not find a package of @clangd/vscode-clangd in npm. Should I wrap the files in the API folder into a module myself?

HighCommander4 commented 2 months ago

I do not find a package of @clangd/vscode-clangd in npm

Could you file an issue about publishing that package please?

In the meantime, you'll need to check out the vscode-clangd repo and refer to the package using a local path the way I do in this example repo.

wss29 commented 2 months ago

Hi @HighCommander4 I have published an issue on 670, I have used your API it is ok except for a little problem with ast node. I use following code to get ast of a c file,

  const ast: ASTNode | undefined = await api.languageClient.sendRequest(ASTType, { textDocument: { uri: args } });

the problem is that the above code only works when the file has opened in vscode, if I directly call and do not open the text document in vscode, I will get 'ResponseError: trying to get AST for non-added document ' is it possible not open file and get ast node of a c file just by pass uri directly?

HighCommander4 commented 2 months ago

is it possible not open file and get ast node of a c file just by pass uri directly?

Clangd does require that the file be opened on the server with textDocument/didOpen before any operations (such as requesting an AST) are performed on it.

However, opening the file on the server doesn't necessarily require opening it in the editor; I think you could use the client object exposed in the extension API to send the didOpen notification from your extension's code.

wss29 commented 2 months ago

hi @HighCommander4 I indeed try 'textDocument/didOpen', but when I send a request of 'textDocument/didOpen', I get error 'method not found' image does that mean @clangd/vscode-clangd not support 'textDocument/didOpen'? and what is the value of the version should I set in TextDocumentItem

wss29 commented 2 months ago

HI @HighCommander4 After fixing two problems. it can open file and get ast node of a c file just by pass uri directly

  1. invoking 'textDocument/didOpen' should use sendNotification not sendRequest
  2. the version field is a type of number, give a string value '1' not work correct, should give the number 1, thanks very much for your replay
    const clangdExtension = vscode.extensions.getExtension<ClangdExtension>(CLANGD_EXTENSION);
            if (!clangdExtension) {
                return undefined;
            }
            if (!clangdExtension.isActive) {
                await clangdExtension.activate();
            }
            try {
                const api = clangdExtension.exports.getApi(CLANGD_API_VERSION);
                const file = vscode.Uri.parse(args).fsPath;
                await fs.promises.access(file, fs.constants.F_OK);
                const textContent = await fs.promises.readFile(file, 'utf8');
                await api.languageClient.sendNotification('textDocument/didOpen', {
                    textDocument: { uri: args, languageId: 'c', version: 1, text: textContent },
                });
                const ast: ASTNode | undefined = await api.languageClient.sendRequest(ASTType, { textDocument: { uri: args } });
                if (!ast || !ast.children) {
                    return [];
                }
                const ret = ast.children
                    .filter(o => o.kind === 'Function' || o.kind === 'Method')
                    .map(o =>
                        JSON.stringify({
                            role: o.role,
                            kind: o.kind,
                            detail: o.detail,
                            range: o.range,
                        })
                    );
                return ret;
            } catch (error) {
                console.log(error);
            }
HighCommander4 commented 2 months ago

Glad you figured it out. For reference, the standard Language Server Protocol messages and notifications (such as didOpen) are documented at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/.