Skip to content

Commit

Permalink
Add integration with cmake-tools extension
Browse files Browse the repository at this point in the history
  • Loading branch information
sr-tream committed Nov 17, 2023
1 parent b1113ec commit bf81862
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 1 deletion.
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"sinon": "^15.2.0",
"typescript": "^4.5.5",
"vsce": "^2.7.0",
"vscode-test": "^1.3.0"
"vscode-test": "^1.3.0",
"vscode-cmake-tools": "^1.0.0"
},
"repository": {
"type": "git",
Expand Down
2 changes: 2 additions & 0 deletions src/clangd-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import * as vscodelc from 'vscode-languageclient/node';

import * as ast from './ast';
import * as cmakeTools from './cmake-tools';
import * as config from './config';
import * as configFileWatcher from './config-file-watcher';
import * as fileStatus from './file-status';
Expand Down Expand Up @@ -168,6 +169,7 @@ export class ClangdContext implements vscode.Disposable {
fileStatus.activate(this);
switchSourceHeader.activate(this);
configFileWatcher.activate(this);
cmakeTools.activate(this);
}

get visibleClangdEditors(): vscode.TextEditor[] {
Expand Down
197 changes: 197 additions & 0 deletions src/cmake-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import * as path from 'path';
import * as vscode from 'vscode';
import * as api from 'vscode-cmake-tools';
import * as vscodelc from 'vscode-languageclient/node';

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

export function activate(context: ClangdContext) {
const feature = new CMakeToolsFeature(context);
context.client.registerFeature(feature);
}

namespace protocol {

export interface DidChangeConfigurationClientCapabilities {
dynamicRegistration?: boolean;
}

export interface ClangdCompileCommand {
// Directory
workingDirectory: string;
// Command line
compilationCommand: string[];
}

export interface ConfigurationSettings {
// File -> ClangdCompileCommand
compilationDatabaseChanges: Object;
}

export interface DidChangeConfigurationParams {
settings: ConfigurationSettings;
}

export namespace DidChangeConfigurationRequest {
export const type = new vscodelc.NotificationType<DidChangeConfigurationParams>(
'workspace/didChangeConfiguration');
}

} // namespace protocol

class CMakeToolsFeature implements vscodelc.StaticFeature {
private projectChange: vscode.Disposable = {dispose() {}};
private codeModelChange: vscode.Disposable|undefined;
private cmakeTools: api.CMakeToolsApi|undefined;
private project: api.Project|undefined;
private codeModel: Map<string, protocol.ClangdCompileCommand>|undefined;

constructor(private readonly context: ClangdContext) {
let cmakeTools = api.getCMakeToolsApi(api.Version.v1);
if (cmakeTools === undefined)
return;

cmakeTools.then(api => {
this.cmakeTools = api;
if (this.cmakeTools === undefined)
return;

this.projectChange = this.cmakeTools.onActiveProjectChanged(
this.onActiveProjectChanged, this);
if (vscode.workspace.workspaceFolders !== undefined) {
// FIXME: clangd not supported multi-workspace projects
const projectUri = vscode.workspace.workspaceFolders[0].uri;
this.onActiveProjectChanged(projectUri);
}
});
}

fillClientCapabilities(_capabilities: vscodelc.ClientCapabilities) {}
fillInitializeParams(_params: vscodelc.InitializeParams) {}

initialize(capabilities: vscodelc.ServerCapabilities,
_documentSelector: vscodelc.DocumentSelector|undefined) {}
getState(): vscodelc.FeatureState { return {kind: 'static'}; }
dispose() {
if (this.codeModelChange !== undefined)
this.codeModelChange.dispose();
this.projectChange.dispose();
}

async onActiveProjectChanged(path: vscode.Uri|undefined) {
if (this.codeModelChange !== undefined) {
this.codeModelChange.dispose();
this.codeModelChange = undefined;
}

if (path === undefined)
return;

this.cmakeTools?.getProject(path).then(project => {
this.project = project;
this.codeModelChange =
this.project?.onCodeModelChanged(this.onCodeModelChanged, this);
this.onCodeModelChanged();
});
}

async onCodeModelChanged() {
const content = this.project?.codeModel;
if (content === undefined)
return;

if (content.toolchains === undefined)
return;

const request: protocol.DidChangeConfigurationParams = {
settings: {compilationDatabaseChanges: {}}
};

let codeModelChanges: Map<string, protocol.ClangdCompileCommand> =
new Map();
content.configurations.forEach(configuration => {
configuration.projects.forEach(project => {
let sourceDirectory = project.sourceDirectory;
project.targets.forEach(target => {
if (target.sourceDirectory !== undefined)
sourceDirectory = target.sourceDirectory;
let commandLine: string[] = [];
if (target.sysroot !== undefined)
commandLine.push(`--sysroot=${target.sysroot}`);
target.fileGroups?.forEach(fileGroup => {
if (fileGroup.language === undefined)
return;

const compiler = content.toolchains?.get(fileGroup.language);
if (compiler === undefined)
return;

commandLine.unshift(compiler.path);
if (compiler.target !== undefined)
commandLine.push(`--target=${compiler.target}`);

let compilerName =
compiler.path.substring(compiler.path.lastIndexOf(path.sep) + 1)
.toLowerCase();
if (compilerName.endsWith('.exe'))
compilerName = compilerName.substring(0, compilerName.length - 4);

const ClangCLMode =
compilerName === 'cl' || compilerName === 'clang-cl';
const incFlag = ClangCLMode ? '/I' : '-I';
const defFlag = ClangCLMode ? '/D' : '-D';

fileGroup.compileCommandFragments?.forEach(commands => {
commands.split(/\s/g).forEach(
command => { commandLine.push(command); });
});
fileGroup.includePath?.forEach(
include => { commandLine.push(`${incFlag}${include.path}`); });
fileGroup.defines?.forEach(
define => { commandLine.push(`${defFlag}${define}`); });
fileGroup.sources.forEach(source => {
const file = sourceDirectory.length != 0
? sourceDirectory + path.sep + source
: source;
const command: protocol.ClangdCompileCommand = {
workingDirectory: sourceDirectory,
compilationCommand: commandLine
};
codeModelChanges.set(file, command);
});
});
});
});
});

const codeModel = new Map(codeModelChanges);
this.codeModel?.forEach((cc, file) => {
if (!codeModelChanges.has(file)) {
const command: protocol.ClangdCompileCommand = {
workingDirectory: '',
compilationCommand: []
};
codeModelChanges.set(file, command);
return;
}
const command = codeModelChanges.get(file);
if (command?.workingDirectory === cc.workingDirectory &&
command?.compilationCommand.length === cc.compilationCommand.length &&
command?.compilationCommand.every(
(val, index) => val === cc.compilationCommand[index])) {
codeModelChanges.delete(file);
}
});
this.codeModel = codeModel;

if (codeModelChanges.size === 0)
return;

codeModelChanges.forEach(
(cc, file) => {Object.assign(
request.settings.compilationDatabaseChanges, {[file]: cc})});

this.context.client.sendNotification(
protocol.DidChangeConfigurationRequest.type, request);
}
}

0 comments on commit bf81862

Please sign in to comment.