Skip to content

Commit 4da3e1e

Browse files
Support API usage when clangd is disabled or failed to initialize properly (#728)
As part of this change, uses of the non-null assertion operator (`!`) are removed. To facilitate doing this for `ClangdContext.client`, the code is reorganized slightly so that in the cases where this field would have been null, a ClangdContext object is not created in the first place.
1 parent 0cda54a commit 4da3e1e

File tree

6 files changed

+47
-27
lines changed

6 files changed

+47
-27
lines changed

api/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ const provideHover = async (document: vscode.TextDocument, position: vscode.Posi
1717

1818
if (clangdExtension) {
1919
const api = (await clangdExtension.activate()).getApi(CLANGD_API_VERSION);
20+
21+
// Extension may be disabled or have failed to initialize
22+
if (!api.languageClient) {
23+
return undefined;
24+
}
2025

2126
const textDocument = api.languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document);
2227
const range = api.languageClient.code2ProtocolConverter.asRange(new vscode.Range(position, position));

api/vscode-clangd.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface ClangdApiV1 {
88
// https://microsoft.github.io/language-server-protocol/specifications/specification-current
99
// clangd custom requests:
1010
// https://clangd.llvm.org/extensions
11-
languageClient: BaseLanguageClient
11+
languageClient: BaseLanguageClient|undefined
1212
}
1313

1414
export interface ClangdExtension {

src/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {BaseLanguageClient} from 'vscode-languageclient';
33
import {ClangdApiV1, ClangdExtension} from '../api/vscode-clangd';
44

55
export class ClangdExtensionImpl implements ClangdExtension {
6-
constructor(public client: BaseLanguageClient) {}
6+
constructor(public client: BaseLanguageClient|undefined) {}
77

88
public getApi(version: 1): ClangdApiV1;
99
public getApi(version: number): unknown {

src/clangd-context.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,26 @@ class EnableEditsNearCursorFeature implements vscodelc.StaticFeature {
5858

5959
export class ClangdContext implements vscode.Disposable {
6060
subscriptions: vscode.Disposable[] = [];
61-
client!: ClangdLanguageClient;
61+
client: ClangdLanguageClient;
6262

63-
async activate(globalStoragePath: string,
64-
outputChannel: vscode.OutputChannel) {
65-
const clangdPath = await install.activate(this, globalStoragePath);
63+
static async create(globalStoragePath: string,
64+
outputChannel: vscode.OutputChannel):
65+
Promise<ClangdContext|null> {
66+
const subscriptions: vscode.Disposable[] = [];
67+
const clangdPath = await install.activate(subscriptions, globalStoragePath);
6668
if (!clangdPath)
67-
return;
69+
return null;
6870

71+
const clangdArguments = await config.get<string[]>('arguments');
72+
73+
return new ClangdContext(clangdPath, clangdArguments, outputChannel);
74+
}
75+
76+
private constructor(clangdPath: string, clangdArguments: string[],
77+
outputChannel: vscode.OutputChannel) {
6978
const clangd: vscodelc.Executable = {
7079
command: clangdPath,
71-
args: await config.get<string[]>('arguments'),
80+
args: clangdArguments,
7281
options: {cwd: vscode.workspace.rootPath || process.cwd()}
7382
};
7483
const traceFile = config.get<string>('trace');
@@ -111,7 +120,8 @@ export class ClangdContext implements vscode.Disposable {
111120
let list = await next(document, position, context, token);
112121
if (!config.get<boolean>('serverCompletionRanking'))
113122
return list;
114-
let items = (Array.isArray(list) ? list : list!.items).map(item => {
123+
let items = (!list ? [] : Array.isArray(list) ? list : list.items);
124+
items = items.map(item => {
115125
// Gets the prefix used by VSCode when doing fuzzymatch.
116126
let prefix = document.getText(
117127
new vscode.Range((item.range as vscode.Range).start, position))

src/extension.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ export async function activate(context: vscode.ExtensionContext):
1616
const outputChannel = vscode.window.createOutputChannel('clangd');
1717
context.subscriptions.push(outputChannel);
1818

19-
const clangdContext = new ClangdContext;
20-
context.subscriptions.push(clangdContext);
19+
let clangdContext: ClangdContext|null = null;
2120

2221
// An empty place holder for the activate command, otherwise we'll get an
2322
// "command is not registered" error.
@@ -31,20 +30,27 @@ export async function activate(context: vscode.ExtensionContext):
3130
// stop/start cycle in this situation is pointless, and doesn't work
3231
// anyways because the client can't be stop()-ped when it's still in the
3332
// Starting state).
34-
if (clangdContext.clientIsStarting()) {
33+
if (clangdContext && clangdContext.clientIsStarting()) {
3534
return;
3635
}
37-
await clangdContext.dispose();
38-
await clangdContext.activate(context.globalStoragePath, outputChannel);
36+
if (clangdContext)
37+
clangdContext.dispose();
38+
clangdContext = await ClangdContext.create(context.globalStoragePath,
39+
outputChannel);
40+
if (clangdContext)
41+
context.subscriptions.push(clangdContext);
3942
if (apiInstance) {
40-
apiInstance.client = clangdContext.client;
43+
apiInstance.client = clangdContext?.client;
4144
}
4245
}));
4346

4447
let shouldCheck = false;
4548

4649
if (vscode.workspace.getConfiguration('clangd').get<boolean>('enable')) {
47-
await clangdContext.activate(context.globalStoragePath, outputChannel);
50+
clangdContext =
51+
await ClangdContext.create(context.globalStoragePath, outputChannel);
52+
if (clangdContext)
53+
context.subscriptions.push(clangdContext);
4854

4955
shouldCheck = vscode.workspace.getConfiguration('clangd').get<boolean>(
5056
'detectExtensionConflicts') ??
@@ -83,6 +89,6 @@ export async function activate(context: vscode.ExtensionContext):
8389
}, 5000);
8490
}
8591

86-
apiInstance = new ClangdExtensionImpl(clangdContext.client);
92+
apiInstance = new ClangdExtensionImpl(clangdContext?.client);
8793
return apiInstance;
8894
}

src/install.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ import AbortController from 'abort-controller';
66
import * as path from 'path';
77
import * as vscode from 'vscode';
88

9-
import {ClangdContext} from './clangd-context';
109
import * as config from './config';
1110

1211
// Returns the clangd path to be used, or null if clangd is not installed.
13-
export async function activate(
14-
context: ClangdContext, globalStoragePath: string): Promise<string|null> {
15-
const ui = new UI(context, globalStoragePath);
16-
context.subscriptions.push(vscode.commands.registerCommand(
12+
export async function activate(disposables: vscode.Disposable[],
13+
globalStoragePath: string):
14+
Promise<string|null> {
15+
const ui = new UI(disposables, globalStoragePath);
16+
disposables.push(vscode.commands.registerCommand(
1717
'clangd.install', async () => common.installLatest(ui)));
18-
context.subscriptions.push(vscode.commands.registerCommand(
18+
disposables.push(vscode.commands.registerCommand(
1919
'clangd.update', async () => common.checkUpdates(true, ui)));
2020
const status = await common.prepare(ui, config.get<boolean>('checkUpdates'));
2121
return status.clangdPath;
2222
}
2323

2424
class UI {
25-
constructor(private context: ClangdContext,
25+
constructor(private disposables: vscode.Disposable[],
2626
private globalStoragePath: string) {}
2727

2828
get storagePath(): string { return this.globalStoragePath; }
@@ -60,8 +60,7 @@ class UI {
6060
error(s: string) { vscode.window.showErrorMessage(s); }
6161
info(s: string) { vscode.window.showInformationMessage(s); }
6262
command(name: string, body: () => any) {
63-
this.context.subscriptions.push(
64-
vscode.commands.registerCommand(name, body));
63+
this.disposables.push(vscode.commands.registerCommand(name, body));
6564
}
6665

6766
async shouldReuse(release: string): Promise<boolean|undefined> {
@@ -121,7 +120,7 @@ class UI {
121120
}
122121

123122
get clangdPath(): string {
124-
let p = config.get<string>('path')!;
123+
let p = config.get<string>('path');
125124
// Backwards compatibility: if it's a relative path with a slash, interpret
126125
// relative to project root.
127126
if (!path.isAbsolute(p) && p.includes(path.sep) &&

0 commit comments

Comments
 (0)