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

add symbol index component #285

Merged
merged 40 commits into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8fdd391
add symbol index component
WebFreak001 Dec 30, 2022
3840f8a
index whole project, use for symbol_search
WebFreak001 Dec 30, 2022
2bf4078
DCD, D-Scanner and libdparse update
WebFreak001 Dec 30, 2022
fa9ab6b
make symbol search use fuzzy search
WebFreak001 Dec 30, 2022
7f267ee
improve method naming
WebFreak001 Dec 31, 2022
166edf7
remove dead code
WebFreak001 Dec 31, 2022
8e58aa4
add auto-import suggestions to completion, fix #83
WebFreak001 Dec 31, 2022
62389d1
make import completion suggestions nicer
WebFreak001 Dec 31, 2022
e8081b1
index stdlib files
WebFreak001 Dec 31, 2022
2c0d024
fix dscanner test typo and test runner
WebFreak001 Jan 4, 2023
7cf367e
implement index caching, improve performance
WebFreak001 Jan 4, 2023
1b03380
save index when calling autoIndexSources
WebFreak001 Jan 4, 2023
f3e8938
make saving index explicit
WebFreak001 Jan 4, 2023
54d6fcb
fix synchronization issues
WebFreak001 Jan 4, 2023
55e9d21
fix possible duplicates with insertSet
WebFreak001 Jan 4, 2023
24bc701
fix & improve index build benchmarking
WebFreak001 Jan 4, 2023
596510e
index storage & building optimizations
WebFreak001 Jan 4, 2023
9361556
Merge branch 'master' into references
WebFreak001 Jan 4, 2023
3f0aa48
add basic grep-based find-references
WebFreak001 Jan 4, 2023
967cab6
Merge branch 'master' into references
WebFreak001 Jan 5, 2023
bbe3527
make collecting references streaming
WebFreak001 Jan 5, 2023
0ccd449
bump to DCD 0.15.2 to fix msgpack-d / dub upgrade
WebFreak001 Jan 6, 2023
cc51672
fix lifetime warning
WebFreak001 Jan 6, 2023
dc64920
make symbol index optional
WebFreak001 Jan 6, 2023
0a842ff
fix tc_integrated_dscanner
WebFreak001 Jan 6, 2023
bfc330b
fix windows import issue
WebFreak001 Jan 6, 2023
f1e34ea
use symbol index instead in diagnostics
WebFreak001 Jan 6, 2023
ca9bf94
Merge branch 'master' into references
WebFreak001 Jan 6, 2023
1b1af94
make completion consistent with code actions
WebFreak001 Jan 6, 2023
449b3da
windows symbol fixes
WebFreak001 Jan 6, 2023
d72a64e
Merge branch 'master' into references
WebFreak001 Jan 6, 2023
01ea409
fix auto-complete with existing imports
WebFreak001 Jan 6, 2023
77f46a4
Merge branch 'master' into references
WebFreak001 Jan 9, 2023
12d00f9
bump DCD version
WebFreak001 Jan 12, 2023
d4b3138
fix some D-Scanner definition list edgecases
WebFreak001 Jan 13, 2023
8bd39b5
Index flag for incomplete modules / having mixins
WebFreak001 Jan 13, 2023
b20e4ad
also search through incomplete modules
WebFreak001 Jan 13, 2023
2ae604d
fix return type
WebFreak001 Jan 14, 2023
3a37995
iterate through public imports for find references
WebFreak001 Jan 14, 2023
d44f2fa
improve auto-import suggestions
WebFreak001 Jan 15, 2023
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
2 changes: 1 addition & 1 deletion dcd/dub.sdl
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name "dcd"

dependency "dcd:common" version="~>0.15.0"
dependency "dcd:common" version="~>0.15.2"
8 changes: 4 additions & 4 deletions dub.selections.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"automem": "0.6.8",
"botan": "1.12.19",
"botan-math": "1.0.3",
"cachetools": "0.4.0",
"dcd": "0.15.0",
"cachetools": "0.4.1",
"dcd": "0.15.2",
"dfmt": "0.15.0-beta.1",
"diet-complete": "0.0.3",
"diet-ng": "1.8.1",
Expand All @@ -17,14 +17,14 @@
"isfreedesktop": "0.1.1",
"libasync": "0.8.6",
"libddoc": "0.8.0",
"libdparse": "0.20.0",
"libdparse": "0.21.1",
"memutils": "1.0.4",
"mir-algorithm": "3.18.4",
"mir-core": "1.3.15",
"mir-cpuid": "1.2.10",
"mir-ion": "2.1.3",
"mir-linux-kernel": "1.0.1",
"msgpack-d": "1.0.1",
"msgpack-d": "1.0.4",
"openssl": "3.2.2",
"requests": "2.0.9",
"rm-rf": "0.1.0",
Expand Down
2 changes: 1 addition & 1 deletion http/dub.selections.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"fileVersion": 1,
"versions": {
"automem": "0.6.8",
"cachetools": "0.4.0",
"cachetools": "0.4.1",
"requests": "2.0.9",
"test_allocator": "0.3.3",
"unit-threaded": "0.10.8"
Expand Down
57 changes: 57 additions & 0 deletions lsp/source/served/lsp/textdocumentmanager.d
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,40 @@ struct Document
return text;
}

/// Same as rawText, but only return a slice, not the whole text.
const(char)[] sliceRawText(TextRange r) const scope return
{
return sliceRawText(textRangeToByteRange(r));
}

/// ditto
const(char)[] sliceRawText(size_t[2] bytes) const scope return
{
if (bytes[1] > text.length)
bytes[1] = text.length;
if (bytes[0] > bytes[1])
bytes[0] = bytes[1];

return rawText[bytes[0] .. bytes[1]];
}

/// ditto
string sliceRawText(TextRange r) immutable
{
return sliceRawText(textRangeToByteRange(r));
}

/// ditto
string sliceRawText(size_t[2] bytes) immutable
{
if (bytes[1] > text.length)
bytes[1] = text.length;
if (bytes[0] > bytes[1])
bytes[0] = bytes[1];

return text[bytes[0] .. bytes[1]];
}

/// Returns the text length.
size_t length() const @property
{
Expand Down Expand Up @@ -464,6 +498,29 @@ struct Document
]);
}

/// Converts a byte range to an LSP position-based text range.
/// See_Also: $(LREF textRangeToByteRange)
TextRange byteRangeToTextRange(size_t[2] range) const
{
Position cachePos;
size_t cacheBytes;

auto start = nextPositionBytes(cachePos, cacheBytes, range[0]);
auto end = nextPositionBytes(cachePos, cacheBytes, range[1]);

return TextRange(start, end);
}

/// Converts an LSP position-based text range to a byte range.
/// See_Also: $(LREF byteRangeToTextRange)
size_t[2] textRangeToByteRange(TextRange range) const
{
return [
positionToBytes(range.start),
positionToBytes(range.end),
];
}

/// Returns the word range at a given line/column position.
TextRange wordRangeAt(Position position) const
{
Expand Down
2 changes: 1 addition & 1 deletion serverbase/source/served/utils/events.d
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct protocolNotification

struct EventProcessorConfig
{
string[] allowedDuplicateMethods = ["object", "served", "std", "io", "workspaced", "fs"];
string[] allowedDuplicateMethods = ["object", "served", "std", "core", "etc", "io", "workspaced", "fs"];
}

/// Hooks into initialization, possibly manipulating the InitializeResponse.
Expand Down
199 changes: 107 additions & 92 deletions source/served/commands/code_actions.d
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import served.utils.fibermanager;
import workspaced.api;
import workspaced.com.dcdext;
import workspaced.com.importer;
import workspaced.com.index;
import workspaced.coms;

import served.commands.format : generateDfmtArgs;
Expand All @@ -20,7 +21,7 @@ import std.conv : to;
import std.experimental.logger;
import std.format : format;
import std.path : buildNormalizedPath, isAbsolute;
import std.regex : matchFirst, regex, replaceAll;
import std.regex : Captures, matchFirst, regex, replaceAll;
import std.string : indexOf, indexOfAny, join, replace, strip;

import fs = std.file;
Expand Down Expand Up @@ -51,19 +52,20 @@ CodeAction[] provideCodeActions(CodeActionParams params)
if (instance.has!DCDComponent)
instance.get!DCDComponent();

auto startBytes = document.positionToBytes(params.range.start);

CodeAction[] ret;
if (instance.has!DCDExtComponent) // check if extends
{
scope codeText = document.rawText.idup;
auto startIndex = document.positionToBytes(params.range.start);
ptrdiff_t idx = min(cast(ptrdiff_t) startIndex, cast(ptrdiff_t) codeText.length - 1);
ptrdiff_t idx = min(cast(ptrdiff_t) startBytes, cast(ptrdiff_t) codeText.length - 1);
while (idx > 0)
{
if (codeText[idx] == ':')
{
// probably extends
if (instance.get!DCDExtComponent.implementAll(codeText,
cast(int) startIndex).getYield.length > 0)
cast(int) startBytes).getYield.length > 0)
{
Command cmd = {
title: "Implement base classes/interfaces",
Expand All @@ -81,13 +83,11 @@ CodeAction[] provideCodeActions(CodeActionParams params)
idx--;
}
}

addDubDiagnostics(ret, instance, document, params, startBytes);
foreach (diagnostic; params.context.diagnostics)
{
if (diagnostic.source == DubDiagnosticSource)
{
addDubDiagnostics(ret, instance, document, diagnostic, params);
}
else if (diagnostic.source == DScannerDiagnosticSource)
if (diagnostic.source == DScannerDiagnosticSource)
{
addDScannerDiagnostics(ret, instance, document, diagnostic, params);
}
Expand All @@ -100,105 +100,120 @@ CodeAction[] provideCodeActions(CodeActionParams params)
}

void addDubDiagnostics(ref CodeAction[] ret, WorkspaceD.Instance instance,
Document document, Diagnostic diagnostic, CodeActionParams params)
Document document, CodeActionParams params, size_t rangeStartBytes)
{
auto match = diagnostic.message.matchFirst(importRegex);
if (diagnostic.message.canFind("import ") && match)
{
ret ~= CodeAction(Command("Import " ~ match[1], "code-d.addImport",
[
JsonValue(match[1]),
JsonValue(document.positionToOffset(params.range[0]))
]));
}
if (cast(bool)(match = diagnostic.message.matchFirst(undefinedIdentifier))
|| cast(bool)(match = diagnostic.message.matchFirst(undefinedTemplate))
|| cast(bool)(match = diagnostic.message.matchFirst(noProperty)))
auto diagnostics = params.context.diagnostics;

if (instance.has!IndexComponent)
{
string[] files;
string[] modules;
int lineNo;
joinAll({
files ~= instance.get!DscannerComponent.findSymbol(match[1])
.getYield.map!"a.file".array;
}, {
if (instance.has!DCDComponent)
files ~= instance.get!DCDComponent.searchSymbol(match[1]).getYield.map!"a.file".array;
} /*, {
struct Symbol
size_t[2][] undefinedIndices;
string[] undefinedIdentifiers;
Captures!string match;
foreach (diagnostic; diagnostics)
{
if (cast(bool)(match = diagnostic.message.matchFirst(undefinedIdentifier))
|| cast(bool)(match = diagnostic.message.matchFirst(undefinedTemplate))
|| cast(bool)(match = diagnostic.message.matchFirst(noProperty)))
{
string project, package_;
undefinedIndices ~= document.textRangeToByteRange(diagnostic.range);
undefinedIdentifiers ~= match[1];
break;
}
}

StopWatch sw;
bool got;
Symbol[] symbols;
sw.start();
info("asking the interwebs for ", match[1]);
new Thread({
import std.net.curl : get;
import std.uri : encodeComponent;

auto str = get(
"https://symbols.webfreak.org/symbols?limit=60&identifier=" ~ encodeComponent(match[1]));
foreach (symbol; parseJSON(str).array)
symbols ~= Symbol(symbol["project"].str, symbol["package"].str);
got = true;
}).start();
while (sw.peek < 3.seconds && !got)
Fiber.yield();
foreach (v; symbols.sort!"a.project < b.project"
.uniq!"a.project == b.project")
ret ~= Command("Import " ~ v.package_ ~ " from dub package " ~ v.project);
}*/
);
info("Files: ", files);
foreach (file; files.sort().uniq)
if (!undefinedIdentifiers.length)
{
if (!isAbsolute(file))
file = buildNormalizedPath(instance.cwd, file);
if (!fs.exists(file))
continue;
lineNo = 0;
foreach (line; io.File(file).byLine)
auto startRange = document.wordRangeAt(rangeStartBytes);
if (params.range.start != params.range.end)
startRange = document.textRangeToByteRange(params.range);
auto identifier = document.sliceRawText(startRange);

if (isValidDIdentifier(identifier))
{
if (++lineNo >= 100)
break;
auto match2 = line.matchFirst(moduleRegex);
if (match2)
undefinedIndices ~= startRange;
undefinedIdentifiers ~= identifier.idup;
}
}

// assumptions:
// if [1] is non-empty, then [0] is used just for sorting
// if [1] is empty, [0] is assumed to be the file path to read the module from
string[2][] importModuleSuggestsions;
foreach (i, identifier; undefinedIdentifiers)
{
auto range = undefinedIndices[i];
CompletionItem[] availableSymbols;
provideAutoImports(TextDocumentPositionParams.init, instance,
document, availableSymbols, cast(int)range[0], identifier, true);

foreach (sym; availableSymbols)
{
if (!sym.insertText.isNone)
{
modules ~= match2[1].replaceAll(whitespace, "").idup;
break;
string replacement = sym.insertText.deref;
TextEditCollection[DocumentUri] changes;
changes[document.uri] = [
TextEdit(document.byteRangeToTextRange(range), replacement)
];
ret ~= CodeAction("Change to `" ~ replacement ~ "`", WorkspaceEdit(changes));
}
else
{
auto data = sym.data.deref.get!(StringMap!JsonValue);
string[2] sortAndMod;
sortAndMod[0] = sym.sortText.deref;
sortAndMod[1] = data["moduleName"].get!string;
importModuleSuggestsions ~= sortAndMod;
}
}
}
importModuleSuggestsions.sort();
foreach (mod; importModuleSuggestsions.map!"a[1]".uniq)
if (mod.length)
ret ~= CodeAction(Command("Import " ~ mod, "code-d.addImport", [
JsonValue(mod),
JsonValue(document.positionToOffset(params.range[0]))
]));
}
else
{
foreach (diagnostic; diagnostics)
{
auto match = diagnostic.message.matchFirst(importRegex);
if (diagnostic.message.canFind("import ") && match)
{
ret ~= CodeAction(Command("Import " ~ match[1], "code-d.addImport",
[
JsonValue(match[1]),
JsonValue(document.positionToOffset(diagnostic.range.start))
]));
}
}
foreach (mod; modules.sort().uniq)
ret ~= CodeAction(Command("Import " ~ mod, "code-d.addImport", [
JsonValue(mod),
JsonValue(document.positionToOffset(params.range[0]))
]));
}

if (diagnostic.message.startsWith("use `is` instead of `==`",
"use `!is` instead of `!=`")
&& diagnostic.range.end.character - diagnostic.range.start.character == 2)
foreach (diagnostic; diagnostics)
{
auto b = document.positionToBytes(diagnostic.range.start);
auto text = document.rawText[b .. $];
if (diagnostic.message.startsWith("use `is` instead of `==`",
"use `!is` instead of `!=`")
&& diagnostic.range.end.character - diagnostic.range.start.character == 2)
{
auto b = document.positionToBytes(diagnostic.range.start);
auto text = document.rawText[b .. $];

string target = diagnostic.message[5] == '!' ? "!=" : "==";
string replacement = diagnostic.message[5] == '!' ? "!is" : "is";
string target = diagnostic.message[5] == '!' ? "!=" : "==";
string replacement = diagnostic.message[5] == '!' ? "!is" : "is";

if (text.startsWith(target))
{
string title = format!"Change '%s' to '%s'"(target, replacement);
TextEditCollection[DocumentUri] changes;
changes[document.uri] = [TextEdit(diagnostic.range, replacement)];
auto action = CodeAction(title, WorkspaceEdit(changes));
action.isPreferred = true;
action.diagnostics = [diagnostic];
action.kind = CodeActionKind.quickfix;
ret ~= action;
if (text.startsWith(target))
{
string title = format!"Change '%s' to '%s'"(target, replacement);
TextEditCollection[DocumentUri] changes;
changes[document.uri] = [TextEdit(diagnostic.range, replacement)];
auto action = CodeAction(title, WorkspaceEdit(changes));
action.isPreferred = true;
action.diagnostics = [diagnostic];
action.kind = CodeActionKind.quickfix;
ret ~= action;
}
}
}
}
Expand Down
Loading