1import * as vscode from 'vscode'; 2import * as vscodelc from 'vscode-languageclient'; 3import * as semanticHighlighting from './semantic-highlighting'; 4 5/** 6 * Method to get workspace configuration option 7 * @param option name of the option (e.g. for clangd.path should be path) 8 * @param defaultValue default value to return if option is not set 9 */ 10function getConfig<T>(option: string, defaultValue?: any): T { 11 const config = vscode.workspace.getConfiguration('clangd'); 12 return config.get<T>(option, defaultValue); 13} 14 15namespace SwitchSourceHeaderRequest { 16export const type = 17 new vscodelc.RequestType<vscodelc.TextDocumentIdentifier, string|undefined, 18 void, void>('textDocument/switchSourceHeader'); 19} 20 21class FileStatus { 22 private statuses = new Map<string, any>(); 23 private readonly statusBarItem = 24 vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10); 25 26 onFileUpdated(fileStatus: any) { 27 const filePath = vscode.Uri.parse(fileStatus.uri); 28 this.statuses.set(filePath.fsPath, fileStatus); 29 this.updateStatus(); 30 } 31 32 updateStatus() { 33 const path = vscode.window.activeTextEditor.document.fileName; 34 const status = this.statuses.get(path); 35 if (!status) { 36 this.statusBarItem.hide(); 37 return; 38 } 39 this.statusBarItem.text = `clangd: ` + status.state; 40 this.statusBarItem.show(); 41 } 42 43 clear() { 44 this.statuses.clear(); 45 this.statusBarItem.hide(); 46 } 47 48 dispose() { this.statusBarItem.dispose(); } 49} 50 51class ClangdLanguageClient extends vscodelc.LanguageClient { 52 // Override the default implementation for failed requests. The default 53 // behavior is just to log failures in the output panel, however output panel 54 // is designed for extension debugging purpose, normal users will not open it, 55 // thus when the failure occurs, normal users doesn't know that. 56 // 57 // For user-interactive operations (e.g. applyFixIt, applyTweaks), we will 58 // prompt up the failure to users. 59 logFailedRequest(rpcReply: vscodelc.RPCMessageType, error: any) { 60 if (error instanceof vscodelc.ResponseError && 61 rpcReply.method === "workspace/executeCommand") 62 vscode.window.showErrorMessage(error.message); 63 // Call default implementation. 64 super.logFailedRequest(rpcReply, error); 65 } 66} 67 68/** 69 * this method is called when your extension is activate 70 * your extension is activated the very first time the command is executed 71 */ 72export function activate(context: vscode.ExtensionContext) { 73 const syncFileEvents = getConfig<boolean>('syncFileEvents', true); 74 75 const clangd: vscodelc.Executable = { 76 command : getConfig<string>('path'), 77 args : getConfig<string[]>('arguments') 78 }; 79 const traceFile = getConfig<string>('trace'); 80 if (!!traceFile) { 81 const trace = {CLANGD_TRACE : traceFile}; 82 clangd.options = {env : {...process.env, ...trace}}; 83 } 84 const serverOptions: vscodelc.ServerOptions = clangd; 85 86 const clientOptions: vscodelc.LanguageClientOptions = { 87 // Register the server for c-family and cuda files. 88 documentSelector: [ 89 { scheme: 'file', language: 'c' }, 90 { scheme: 'file', language: 'cpp' }, 91 // cuda is not supported by vscode, but our extension does. 92 { scheme: 'file', language: 'cuda' }, 93 { scheme: 'file', language: 'objective-c'}, 94 { scheme: 'file', language: 'objective-cpp'} 95 ], 96 synchronize: !syncFileEvents ? undefined : { 97 // FIXME: send sync file events when clangd provides implementations. 98 }, 99 initializationOptions: { clangdFileStatus: true }, 100 // Do not switch to output window when clangd returns output 101 revealOutputChannelOn: vscodelc.RevealOutputChannelOn.Never 102 }; 103 104 const clangdClient = new ClangdLanguageClient('Clang Language Server', 105 serverOptions, clientOptions); 106 if (getConfig<boolean>('semanticHighlighting')) { 107 const semanticHighlightingFeature = 108 new semanticHighlighting.SemanticHighlightingFeature(clangdClient, 109 context); 110 context.subscriptions.push( 111 vscode.Disposable.from(semanticHighlightingFeature)); 112 clangdClient.registerFeature(semanticHighlightingFeature); 113 } 114 console.log('Clang Language Server is now active!'); 115 context.subscriptions.push(clangdClient.start()); 116 context.subscriptions.push(vscode.commands.registerCommand( 117 'clangd-vscode.switchheadersource', async () => { 118 const uri = 119 vscode.Uri.file(vscode.window.activeTextEditor.document.fileName); 120 if (!uri) { 121 return; 122 } 123 const docIdentifier = 124 vscodelc.TextDocumentIdentifier.create(uri.toString()); 125 const sourceUri = await clangdClient.sendRequest( 126 SwitchSourceHeaderRequest.type, docIdentifier); 127 if (!sourceUri) { 128 return; 129 } 130 const doc = await vscode.workspace.openTextDocument( 131 vscode.Uri.parse(sourceUri)); 132 vscode.window.showTextDocument(doc); 133 })); 134 const status = new FileStatus(); 135 context.subscriptions.push(vscode.Disposable.from(status)); 136 context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor( 137 () => { status.updateStatus(); })); 138 context.subscriptions.push(clangdClient.onDidChangeState(({newState}) => { 139 if (newState == vscodelc.State.Running) { 140 // clangd starts or restarts after crash. 141 clangdClient.onNotification( 142 'textDocument/clangd.fileStatus', 143 (fileStatus) => { status.onFileUpdated(fileStatus); }); 144 } else if (newState == vscodelc.State.Stopped) { 145 // Clear all cached statuses when clangd crashes. 146 status.clear(); 147 } 148 })); 149 // An empty place holder for the activate command, otherwise we'll get an 150 // "command is not registered" error. 151 context.subscriptions.push(vscode.commands.registerCommand( 152 'clangd-vscode.activate', async () => {})); 153} 154