blob: 330cf7ac262ebc4788d605dc5be69f8bb56d2420 [file] [log] [blame]
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);
if (getConfig<boolean>('semanticHighlighting')) {
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 () => {}));
}