Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plug support of CCDB in serve-d #271

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions protocol/source/served/lsp/uri.d
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ private void assertEquals(T)(T a, T b)

DocumentUri uriFromFile(scope const(char)[] file)
{
import std.ascii : isAlpha, toLower;
import std.uri : encodeComponent;

if ((!isAbsolute(file) && !file.startsWith("/"))
|| !file.length)
throw new Exception(text("Tried to pass relative path '", file, "' to uriFromFile"));
file = file.buildNormalizedPath.replace("\\", "/");
file = file.buildNormalizedPath().replace("\\", "/");
assert(file.length);
if (file.ptr[0] != '/')
file = '/' ~ file; // always triple slash at start but never quad slash
if (file.length >= 2 && file[0 .. 2] == "//") // Shares (\\share\bob) are different somehow
if (file.length >= 2 && file[0].isAlpha && file[1] == ':')
file = file[0].toLower ~ file[1 .. $];
else if (file.length >= 2 && file[0 .. 2] == "//") // Shares (\\share\bob) are different somehow
file = file[2 .. $];
else if (file.ptr[0] != '/')
file = '/' ~ file; // always triple slash at start but never quad slash

return text("file://", file.encodeComponent.replace("%2F", "/"));
}

Expand All @@ -34,7 +38,7 @@ unittest

version (Windows)
{

assertEquals(uriFromFile(`C:\Home\foo\bar.d`), `file://c%3A/Home/foo/bar.d`);
}
else
{
Expand Down Expand Up @@ -283,7 +287,7 @@ unittest
{
assertEquals(uriNormalize(`b/../a.d`), `a.d`);
assertEquals(uriNormalize(`b/../../a.d`), `../a.d`);

foreach (prefix; ["file:///", "file://", "", "/", "//"])
{
assertEquals(uriNormalize(prefix ~ `foo/bar/./a.d`), prefix ~ `foo/bar/a.d`);
Expand Down
84 changes: 84 additions & 0 deletions source/served/commands/ccdb.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/// Clang compilation database (aka. compile_commands.json) related functions
module served.commands.ccdb;

import served.io.nothrow_fs;
import served.lsp.protocol;
import served.lsp.uri;
import served.types;
import served.utils.events;

import std.experimental.logger;
import fs = std.file;
import std.path;

import workspaced.api;
import workspaced.coms;

string discoverCcdb(string root)
{
import std.algorithm : count, map, sort;
import std.array : array;

trace("discovering CCDB in ", root);

if (fs.exists(chainPath(root, "compile_commands.json")))
return buildNormalizedPath(root, "compile_commands.json");

string[] dbs = tryDirEntries(root, "compile_commands.json", fs.SpanMode.breadth)
.map!(e => buildNormalizedPath(e.name))
.array;

// using in priority:
// - those which have fewer directory depth
// - lexical order
dbs.sort!((a, b) {
const depthA = count(a, dirSeparator);
const depthB = count(b, dirSeparator);
if (depthA != depthB)
return depthA < depthB;
return a < b;
});

tracef("discovered following CCDB:%-(\n - %s%)", dbs);

return dbs.length ? dbs[0] : null;
}

@protocolNotification("workspace/didChangeWatchedFiles")
void onCcdbFileChange(DidChangeWatchedFilesParams params)
{
import std.algorithm : endsWith, map;

foreach (c; params.changes)
{
trace("watched file did change: ", c);

if (!c.uri.endsWith("compile_commands.json"))
continue;

string filename = c.uri.uriToFile;

auto inst = backend.getBestInstance!ClangCompilationDatabaseComponent(filename);
if (!inst)
continue;

string ccdbPath = inst.get!ClangCompilationDatabaseComponent.getDbPath();
if (!ccdbPath)
continue;

filename = filename.buildNormalizedPath();

if (filename == ccdbPath)
{
if (c.type == FileChangeType.deleted)
{
filename = discoverCcdb(inst.cwd);
tracef("CCDB file deleted. Switching from %s to %s", ccdbPath, filename ? filename
: "(null)");
}

tracef("will (re)load %s", filename);
inst.get!ClangCompilationDatabaseComponent.setDbPath(filename);
}
}
}
134 changes: 106 additions & 28 deletions source/served/extension.d
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import workspaced.coms;

// list of all commands for auto dispatch
public import served.commands.calltips;
public import served.commands.ccdb;
public import served.commands.code_actions;
public import served.commands.code_lens;
public import served.commands.color;
Expand Down Expand Up @@ -112,6 +113,14 @@ void changedConfig(ConfigWorkspace target, string[] paths, served.types.Configur
backend.get!DCDComponent(workspaceFs)
.addImports(config.d.projectImportPaths.map!(a => a.userPath).array);
break;
case "d.ccdbPath":
if (config.d.ccdbPath.length &&
backend.has!ClangCompilationDatabaseComponent(workspaceFs))
{
const ccdbPath = config.d.ccdbPath.userPath.buildNormalizedPath();
backend.get!ClangCompilationDatabaseComponent(workspaceFs).setDbPath(ccdbPath);
}
break;
case "d.dubConfiguration":
if (backend.has!DubComponent(workspaceFs))
{
Expand Down Expand Up @@ -171,9 +180,11 @@ void changedConfig(ConfigWorkspace target, string[] paths, served.types.Configur
{
import served.linters.dscanner : clear1 = clear;
import served.linters.dub : clear2 = clear;
import served.linters.ccdb : clear3 = clear;

clear1();
clear2();
clear3();
}
break;
case "d.enableStaticLinting":
Expand All @@ -192,6 +203,14 @@ void changedConfig(ConfigWorkspace target, string[] paths, served.types.Configur
clear();
}
break;
case "d.enableCcdbLinting":
if (!config.d.enableCcdbLinting)
{
import served.linters.ccdb : clear;

clear();
}
break;
default:
break;
}
Expand All @@ -215,7 +234,7 @@ string[] getPossibleSourceRoots(string workspaceFolder)
import std.file;

auto confPaths = config(workspaceFolder.uriFromFile, false).d.projectImportPaths.map!(
a => a.isAbsolute ? a : buildNormalizedPath(workspaceRoot, a));
a => a.isAbsolute ? a : buildNormalizedPath(workspaceRoot, a));
if (!confPaths.empty)
return confPaths.array;
auto a = buildNormalizedPath(workspaceFolder, "source");
Expand Down Expand Up @@ -390,6 +409,8 @@ void doGlobalStartup(UserConfiguration config)
backend.register!DubComponent(false);
trace("Registering fsworkspace");
backend.register!FSWorkspaceComponent(false);
trace("Registering ccdb");
backend.register!ClangCompilationDatabaseComponent;
rtbo marked this conversation as resolved.
Show resolved Hide resolved
trace("Registering dcd");
backend.register!DCDComponent;
trace("Registering dcdext");
Expand Down Expand Up @@ -762,6 +783,19 @@ void delayedProjectActivation(WorkspaceD.Instance instance, string workspaceRoot

emitExtensionEvent!onAddingProject(instance, workspaceRoot, workspaceUri);

scope (success)
{
trace("Started files provider for root ", root);

trace("Loaded Components for ", instance.cwd, ": ",
instance.instanceComponents.map!"a.info.name");

emitExtensionEvent!onAddedProject(instance, workspaceRoot, workspaceUri);

rootTimer.stop();
info("Root ", root, " initialized in ", rootTimer.peek);
}

bool disableDub = proj.config.d.neverUseDub || !root.useDub;
bool loadedDub;
Exception err;
Expand Down Expand Up @@ -852,44 +886,69 @@ void delayedProjectActivation(WorkspaceD.Instance instance, string workspaceRoot
}

if (!loadedDub)
{
error("Exception starting dub: ", err);
proj.startupError(workspaceRoot, translate!"d.ext.dubFail"(instance.cwd, err ? err.msg
: ""));
}
else
trace("Started dub with root dependencies ", instance.get!DubComponent.rootDependencies);
trace("Started dub with root dependencies ", instance
.get!DubComponent.rootDependencies);
}
if (!loadedDub)

if (loadedDub)
didLoadDubProject();

string ccdbPath = proj.config.d.ccdbPath;
if (!ccdbPath.length && !loadedDub && !proj.config.d.neverUseCcdb)
ccdbPath = discoverCcdb(workspaceRoot);
bool loadedCcdb;
if (ccdbPath.length)
{
if (!disableDub)
trace("starting CCDB with ", ccdbPath);

try
{
error("Failed starting dub in ", root, " - falling back to fsworkspace");
proj.startupError(workspaceRoot, translate!"d.ext.dubFail"(instance.cwd, err ? err.msg : ""));
if (backend.attachEager(instance, "ccdb", err))
{
instance.get!ClangCompilationDatabaseComponent.setDbPath(ccdbPath);
loadedCcdb = true;
}
}
try
catch (Exception ex)
{
trace("Starting fsworkspace...");

instance.config.set("fsworkspace", "additionalPaths",
getPossibleSourceRoots(workspaceRoot));
if (!backend.attachEager(instance, "fsworkspace", err))
throw new Exception("Attach returned failure: " ~ err.msg);
err = ex;
}
catch (Exception e)
if (!loadedCcdb)
{
error(e);
proj.startupError(workspaceRoot, translate!"d.ext.fsworkspaceFail"(instance.cwd));

error("Exception loading CCDB: ", err);
proj.startupError(workspaceRoot, translate!"d.ext.ccdbFail"(instance.cwd, err ? err.msg : ""));
}
else
trace("Initialized CCDB with import paths ", instance
.get!ClangCompilationDatabaseComponent.importPaths);
}
else
didLoadDubProject();

trace("Started files provider for root ", root);
if (loadedDub || loadedCcdb)
return;

trace("Loaded Components for ", instance.cwd, ": ",
instance.instanceComponents.map!"a.info.name");
error("Failed starting dub or CCDB in ", root, " - falling back to fsworkspace");

emitExtensionEvent!onAddedProject(instance, workspaceRoot, workspaceUri);
try
rtbo marked this conversation as resolved.
Show resolved Hide resolved
{
trace("Starting fsworkspace...");

rootTimer.stop();
info("Root ", root, " initialized in ", rootTimer.peek);
instance.config.set("fsworkspace", "additionalPaths",
getPossibleSourceRoots(workspaceRoot));
if (!backend.attachEager(instance, "fsworkspace", err))
throw new Exception("Attach returned failure: " ~ err.msg);
}
catch (Exception e)
{
error(e);
proj.startupError(workspaceRoot, translate!"d.ext.fsworkspaceFail"(instance.cwd));
}
}

void notifySkippedRoots()
Expand Down Expand Up @@ -1086,16 +1145,29 @@ void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params)
@protocolNotification("textDocument/didOpen")
void onDidOpenDocument(DidOpenTextDocumentParams params)
{
string lintSetting = config(params.textDocument.uri).d.lintOnFileOpen;
auto config = workspace(params.textDocument.uri).config;
auto document = documents[params.textDocument.uri];

string lintSetting = config.d.lintOnFileOpen;
bool shouldLint;
if (lintSetting == "always")
shouldLint = true;
else if (lintSetting == "project")
shouldLint = workspaceIndex(params.textDocument.uri) != size_t.max;

if (shouldLint)
{
onDidChangeDocument(DidChangeTextDocumentParams(
VersionedTextDocumentIdentifier(params.textDocument.uri, params.textDocument.version_)));
VersionedTextDocumentIdentifier(
params.textDocument.uri, params.textDocument.version_)));

if (config.d.enableCcdbLinting && document.languageId == "d")
{
import served.linters.ccdb;

lint(document);
}
}
}

@protocolNotification("textDocument/didClose")
Expand Down Expand Up @@ -1288,10 +1360,8 @@ ServedInfoResponse getServedInfo(ServedInfoParams params)
@protocolNotification("textDocument/didSave")
void onDidSaveDocument(DidSaveTextDocumentParams params)
{
auto workspaceRoot = workspaceRootFor(params.textDocument.uri);
auto config = workspace(params.textDocument.uri).config;
auto document = documents[params.textDocument.uri];
auto fileName = params.textDocument.uri.uriToFile.baseName;

if (document.getLanguageId == "d" || document.getLanguageId == "diet")
{
Expand All @@ -1310,6 +1380,13 @@ void onDidSaveDocument(DidSaveTextDocumentParams params)
{
import served.linters.dub;

lint(document);
}
}, {
if (config.d.enableCcdbLinting && document.languageId == "d")
{
import served.linters.ccdb;

lint(document);
}
});
Expand Down Expand Up @@ -1362,6 +1439,7 @@ shared static ~this()
//dfmt off
alias memberModules = AliasSeq!(
served.commands.calltips,
served.commands.ccdb,
served.commands.code_actions,
served.commands.code_lens,
served.commands.color,
Expand Down
Loading
Loading