Skip to content

Commit

Permalink
Support API usage when clangd is disabled or failed to initialize pro…
Browse files Browse the repository at this point in the history
…perly (#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.
  • Loading branch information
HighCommander4 authored Nov 18, 2024
1 parent 0cda54a commit 4da3e1e
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 27 deletions.
5 changes: 5 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ const provideHover = async (document: vscode.TextDocument, position: vscode.Posi

if (clangdExtension) {
const api = (await clangdExtension.activate()).getApi(CLANGD_API_VERSION);

// Extension may be disabled or have failed to initialize
if (!api.languageClient) {
return undefined;
}

const textDocument = api.languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document);
const range = api.languageClient.code2ProtocolConverter.asRange(new vscode.Range(position, position));
Expand Down
2 changes: 1 addition & 1 deletion api/vscode-clangd.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface ClangdApiV1 {
// https://microsoft.github.io/language-server-protocol/specifications/specification-current
// clangd custom requests:
// https://clangd.llvm.org/extensions
languageClient: BaseLanguageClient
languageClient: BaseLanguageClient|undefined
}

export interface ClangdExtension {
Expand Down
2 changes: 1 addition & 1 deletion src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {BaseLanguageClient} from 'vscode-languageclient';
import {ClangdApiV1, ClangdExtension} from '../api/vscode-clangd';

export class ClangdExtensionImpl implements ClangdExtension {
constructor(public client: BaseLanguageClient) {}
constructor(public client: BaseLanguageClient|undefined) {}

public getApi(version: 1): ClangdApiV1;
public getApi(version: number): unknown {
Expand Down
24 changes: 17 additions & 7 deletions src/clangd-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,26 @@ class EnableEditsNearCursorFeature implements vscodelc.StaticFeature {

export class ClangdContext implements vscode.Disposable {
subscriptions: vscode.Disposable[] = [];
client!: ClangdLanguageClient;
client: ClangdLanguageClient;

async activate(globalStoragePath: string,
outputChannel: vscode.OutputChannel) {
const clangdPath = await install.activate(this, globalStoragePath);
static async create(globalStoragePath: string,
outputChannel: vscode.OutputChannel):
Promise<ClangdContext|null> {
const subscriptions: vscode.Disposable[] = [];
const clangdPath = await install.activate(subscriptions, globalStoragePath);
if (!clangdPath)
return;
return null;

const clangdArguments = await config.get<string[]>('arguments');

return new ClangdContext(clangdPath, clangdArguments, outputChannel);
}

private constructor(clangdPath: string, clangdArguments: string[],
outputChannel: vscode.OutputChannel) {
const clangd: vscodelc.Executable = {
command: clangdPath,
args: await config.get<string[]>('arguments'),
args: clangdArguments,
options: {cwd: vscode.workspace.rootPath || process.cwd()}
};
const traceFile = config.get<string>('trace');
Expand Down Expand Up @@ -111,7 +120,8 @@ export class ClangdContext implements vscode.Disposable {
let list = await next(document, position, context, token);
if (!config.get<boolean>('serverCompletionRanking'))
return list;
let items = (Array.isArray(list) ? list : list!.items).map(item => {
let items = (!list ? [] : Array.isArray(list) ? list : list.items);
items = items.map(item => {
// Gets the prefix used by VSCode when doing fuzzymatch.
let prefix = document.getText(
new vscode.Range((item.range as vscode.Range).start, position))
Expand Down
22 changes: 14 additions & 8 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export async function activate(context: vscode.ExtensionContext):
const outputChannel = vscode.window.createOutputChannel('clangd');
context.subscriptions.push(outputChannel);

const clangdContext = new ClangdContext;
context.subscriptions.push(clangdContext);
let clangdContext: ClangdContext|null = null;

// An empty place holder for the activate command, otherwise we'll get an
// "command is not registered" error.
Expand All @@ -31,20 +30,27 @@ export async function activate(context: vscode.ExtensionContext):
// stop/start cycle in this situation is pointless, and doesn't work
// anyways because the client can't be stop()-ped when it's still in the
// Starting state).
if (clangdContext.clientIsStarting()) {
if (clangdContext && clangdContext.clientIsStarting()) {
return;
}
await clangdContext.dispose();
await clangdContext.activate(context.globalStoragePath, outputChannel);
if (clangdContext)
clangdContext.dispose();
clangdContext = await ClangdContext.create(context.globalStoragePath,
outputChannel);
if (clangdContext)
context.subscriptions.push(clangdContext);
if (apiInstance) {
apiInstance.client = clangdContext.client;
apiInstance.client = clangdContext?.client;
}
}));

let shouldCheck = false;

if (vscode.workspace.getConfiguration('clangd').get<boolean>('enable')) {
await clangdContext.activate(context.globalStoragePath, outputChannel);
clangdContext =
await ClangdContext.create(context.globalStoragePath, outputChannel);
if (clangdContext)
context.subscriptions.push(clangdContext);

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

apiInstance = new ClangdExtensionImpl(clangdContext.client);
apiInstance = new ClangdExtensionImpl(clangdContext?.client);
return apiInstance;
}
19 changes: 9 additions & 10 deletions src/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import AbortController from 'abort-controller';
import * as path from 'path';
import * as vscode from 'vscode';

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

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

class UI {
constructor(private context: ClangdContext,
constructor(private disposables: vscode.Disposable[],
private globalStoragePath: string) {}

get storagePath(): string { return this.globalStoragePath; }
Expand Down Expand Up @@ -60,8 +60,7 @@ class UI {
error(s: string) { vscode.window.showErrorMessage(s); }
info(s: string) { vscode.window.showInformationMessage(s); }
command(name: string, body: () => any) {
this.context.subscriptions.push(
vscode.commands.registerCommand(name, body));
this.disposables.push(vscode.commands.registerCommand(name, body));
}

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

get clangdPath(): string {
let p = config.get<string>('path')!;
let p = config.get<string>('path');
// Backwards compatibility: if it's a relative path with a slash, interpret
// relative to project root.
if (!path.isAbsolute(p) && p.includes(path.sep) &&
Expand Down

0 comments on commit 4da3e1e

Please sign in to comment.