| import * as vscode from 'vscode'; |
| import * as vscodelc from 'vscode-languageclient'; |
| import * as semanticHighlighting from './semantic-highlighting'; |
| |
| /** |
| * Method to get workspace configuration option |
| * @param option name of the option (e.g. for clangd.path should be path) |
| * @param defaultValue default value to return if option is not set |
| */ |
| function getConfig<T>(option: string, defaultValue?: any): T { |
| const config = vscode.workspace.getConfiguration('clangd'); |
| return config.get<T>(option, defaultValue); |
| } |
| |
| namespace SwitchSourceHeaderRequest { |
| export const type = |
| new vscodelc.RequestType<vscodelc.TextDocumentIdentifier, string|undefined, |
| void, void>('textDocument/switchSourceHeader'); |
| } |
| |
| class FileStatus { |
| private statuses = new Map<string, any>(); |
| private readonly statusBarItem = |
| vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); |
| |
| onFileUpdated(fileStatus: any) { |
| const filePath = vscode.Uri.parse(fileStatus.uri); |
| this.statuses.set(filePath.fsPath, fileStatus); |
| this.updateStatus(); |
| } |
| |
| updateStatus() { |
| const path = vscode.window.activeTextEditor.document.fileName; |
| const status = this.statuses.get(path); |
| if (!status) { |
| this.statusBarItem.hide(); |
| return; |
| } |
| this.statusBarItem.text = `clangd: ` + status.state; |
| this.statusBarItem.show(); |
| } |
| |
| clear() { |
| this.statuses.clear(); |
| this.statusBarItem.hide(); |
| } |
| |
| dispose() { this.statusBarItem.dispose(); } |
| } |
| |
| class ClangdLanguageClient extends vscodelc.LanguageClient { |
| // Override the default implementation for failed requests. The default |
| // behavior is just to log failures in the output panel, however output panel |
| // is designed for extension debugging purpose, normal users will not open it, |
| // thus when the failure occurs, normal users doesn't know that. |
| // |
| // For user-interactive operations (e.g. applyFixIt, applyTweaks), we will |
| // prompt up the failure to users. |
| logFailedRequest(rpcReply: vscodelc.RPCMessageType, error: any) { |
| if (error instanceof vscodelc.ResponseError && |
| rpcReply.method === "workspace/executeCommand") |
| vscode.window.showErrorMessage(error.message); |
| // Call default implementation. |
| super.logFailedRequest(rpcReply, error); |
| } |
| } |
| |
| /** |
| * this method is called when your extension is activate |
| * your extension is activated the very first time the command is executed |
| */ |
| export function activate(context: vscode.ExtensionContext) { |
| const syncFileEvents = getConfig<boolean>('syncFileEvents', true); |
| |
| const clangd: vscodelc.Executable = { |
| command : getConfig<string>('path'), |
| args : getConfig<string[]>('arguments') |
| }; |
| const traceFile = getConfig<string>('trace'); |
| if (!!traceFile) { |
| const trace = {CLANGD_TRACE : traceFile}; |
| clangd.options = {env : {...process.env, ...trace}}; |
| } |
| const serverOptions: vscodelc.ServerOptions = clangd; |
| |
| // Note that CUDA ('.cu') files are special. When opening files of all other |
| // extensions, VSCode would load clangd automatically. This is achieved by |
| // having a corresponding 'onLanguage:...' activation event in package.json. |
| // However, VSCode does not have CUDA as a supported language yet, so we |
| // cannot add a corresponding activationEvent for CUDA files and clangd will |
| // *not* load itself automatically on '.cu' files. |
| const cudaFilePattern: string = '**/*.{' + [ 'cu' ].join() + '}'; |
| const clientOptions: vscodelc.LanguageClientOptions = { |
| // Register the server for c-family and cuda files. |
| documentSelector: [ |
| { scheme: 'file', language: 'c' }, |
| { scheme: 'file', language: 'cpp' }, |
| { scheme: 'file', language: 'objective-c'}, |
| { scheme: 'file', language: 'objective-cpp'}, |
| { scheme: 'file', pattern: cudaFilePattern }, |
| ], |
| synchronize: !syncFileEvents ? undefined : { |
| // FIXME: send sync file events when clangd provides implemenatations. |
| }, |
| initializationOptions: { clangdFileStatus: true }, |
| // Do not switch to output window when clangd returns output |
| revealOutputChannelOn: vscodelc.RevealOutputChannelOn.Never |
| }; |
| |
| const clangdClient = new ClangdLanguageClient('Clang Language Server', |
| serverOptions, clientOptions); |
| const semanticHighlightingFeature = |
| new semanticHighlighting.SemanticHighlightingFeature(clangdClient, |
| context); |
| context.subscriptions.push( |
| vscode.Disposable.from(semanticHighlightingFeature)); |
| clangdClient.registerFeature(semanticHighlightingFeature); |
| console.log('Clang Language Server is now active!'); |
| context.subscriptions.push(clangdClient.start()); |
| context.subscriptions.push(vscode.commands.registerCommand( |
| 'clangd-vscode.switchheadersource', async () => { |
| const uri = |
| vscode.Uri.file(vscode.window.activeTextEditor.document.fileName); |
| if (!uri) { |
| return; |
| } |
| const docIdentifier = |
| vscodelc.TextDocumentIdentifier.create(uri.toString()); |
| const sourceUri = await clangdClient.sendRequest( |
| SwitchSourceHeaderRequest.type, docIdentifier); |
| if (!sourceUri) { |
| return; |
| } |
| const doc = await vscode.workspace.openTextDocument( |
| vscode.Uri.parse(sourceUri)); |
| vscode.window.showTextDocument(doc); |
| })); |
| const status = new FileStatus(); |
| context.subscriptions.push(vscode.Disposable.from(status)); |
| context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor( |
| () => { status.updateStatus(); })); |
| context.subscriptions.push(clangdClient.onDidChangeState(({newState}) => { |
| if (newState == vscodelc.State.Running) { |
| // clangd starts or restarts after crash. |
| clangdClient.onNotification( |
| 'textDocument/clangd.fileStatus', |
| (fileStatus) => { status.onFileUpdated(fileStatus); }); |
| } else if (newState == vscodelc.State.Stopped) { |
| // Clear all cached statuses when clangd crashes. |
| status.clear(); |
| } |
| })); |
| // An empty place holder for the activate command, otherwise we'll get an |
| // "command is not registered" error. |
| context.subscriptions.push(vscode.commands.registerCommand( |
| 'clangd-vscode.activate', async () => {})); |
| } |