Skip to content

Commit

Permalink
cache ListSymbols results
Browse files Browse the repository at this point in the history
Improves performance with workspace/symbol requests.
  • Loading branch information
Prince781 committed Nov 27, 2021
1 parent a1ef671 commit 50c739d
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 73 deletions.
36 changes: 21 additions & 15 deletions src/analysis/codestyleanalyzer.vala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class Vls.CodeStyleAnalyzer : CodeVisitor, CodeAnalyzer {
}
}

public CodeStyleAnalyzer (Vala.SourceFile source_file) {
this.visit_source_file (source_file);
}

public override void visit_source_file (SourceFile source_file) {
current_file = source_file;
source_file.accept_children (this);
Expand Down Expand Up @@ -72,13 +76,17 @@ class Vls.CodeStyleAnalyzer : CodeVisitor, CodeAnalyzer {
st.accept_children (this);
}

public override void visit_delegate (Delegate d) {
if (d.source_reference == null || d.source_reference.file != current_file ||
d.source_reference.begin.pos == null)
return;
private void analyze_callable (Callable callable) {
_num_callable++;

unowned string text = (string)d.source_reference.end.pos;
// because we allow content to be temporarily inconsistent with the
// parse tree (to allow for fast code completion), we have to use
// [last_fresh_content]
unowned var content = (current_file is TextDocument) ?
((TextDocument)current_file).last_fresh_content : current_file.content;
var sr = callable.source_reference;
var zero_idx = (long) Util.get_string_pos (content, sr.end.line - 1, sr.end.column);
unowned string text = content.offset (zero_idx);
var spaces = 0;
unichar c = '\0';
for (var i = 0; text.get_next_char (ref i, out c) && c != '(' && !(c == '\r' || c == '\n');)
Expand All @@ -88,18 +96,16 @@ class Vls.CodeStyleAnalyzer : CodeVisitor, CodeAnalyzer {
_total_spacing += spaces;
}

public override void visit_delegate (Delegate d) {
if (d.source_reference == null || d.source_reference.file != current_file ||
d.source_reference.begin.pos == null)
return;
analyze_callable (d);
}

public override void visit_method (Method m) {
if (m.source_reference == null || m.source_reference.begin.pos == null)
return;
_num_callable++;

unowned string text = (string)m.source_reference.end.pos;
var spaces = 0;
unichar c = '\0';
for (var i = 0; text.get_next_char (ref i, out c) && c != '(' && !(c == '\r' || c == '\n');)
spaces++;
if (c == '\r' || c == '\n')
spaces = 1;
_total_spacing += spaces;
analyze_callable (m);
}
}
29 changes: 23 additions & 6 deletions src/list_symbols.vala → src/analysis/symbolenumerator.vala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* list_symbols.vala
/* symbolenumerator.vala
*
* Copyright 2020 Princeton Ferro <[email protected]>
*
Expand All @@ -21,34 +21,51 @@ using Lsp;
/**
* Used to list all symbols defined in a document, usually for outlining.
*/
class Vls.ListSymbols : Vala.CodeVisitor {
class Vls.SymbolEnumerator : Vala.CodeVisitor, CodeAnalyzer {
private Vala.SourceFile file;
private Gee.Deque<DocumentSymbol> containers;
private Gee.List<DocumentSymbol> top_level_syms;
private Gee.TreeMap<Range, DocumentSymbol> syms_flat;
private Gee.List<DocumentSymbol> all_syms;
private Gee.List<SymbolInformation>? all_sym_infos;
private Gee.HashMap<string, DocumentSymbol> ns_name_to_dsym;
Vala.TypeSymbol? str_sym;
string uri;

public ListSymbols (Vala.SourceFile file) {
public DateTime last_updated { get; set; }

public SymbolEnumerator (Vala.SourceFile file) {
this.file = file;
this.top_level_syms = new Gee.LinkedList<DocumentSymbol> ();
this.containers = new Gee.LinkedList<DocumentSymbol> ();
this.syms_flat = new Gee.TreeMap<Range, DocumentSymbol> ();
this.all_syms = new Gee.LinkedList<DocumentSymbol> ();
this.ns_name_to_dsym = new Gee.HashMap<string, DocumentSymbol> ();

str_sym = file.context.root.scope.lookup ("string") as Vala.TypeSymbol;
try {
uri = Filename.to_uri (file.filename);
} catch (Error e) {
warning ("%s is not a URI!", file.filename);
return;
}

str_sym = file.context.root.scope.lookup ("string") as Vala.TypeSymbol;
this.visit_source_file (file);
str_sym = null;
}

public Gee.Iterator<DocumentSymbol> iterator () {
return top_level_syms.iterator ();
}

public Gee.Iterable<DocumentSymbol> flattened () {
return all_syms;
public Gee.Iterable<SymbolInformation> flattened () {
if (all_sym_infos == null) {
all_sym_infos = new Gee.LinkedList<SymbolInformation> ();
all_sym_infos.add_all_iterator (
all_syms
.map<SymbolInformation> (s => new SymbolInformation.from_document_symbol (s, uri)));
}
return all_sym_infos;
}

public DocumentSymbol? add_symbol (Vala.Symbol sym, SymbolKind kind, bool adding_parent = false) {
Expand Down
12 changes: 9 additions & 3 deletions src/codehelp/codehelp.vala
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,21 @@ namespace Vls.CodeHelp {
if (sr == null)
return @"(error - $(expr.type_name) does not have source ref!)";
var file = sr.file;
unowned string content;
if (file.content == null)
file.content = (string) file.get_mapped_contents ();
var from = (long) Util.get_string_pos (file.content, sr.begin.line-1, sr.begin.column-1);
var to = (long) Util.get_string_pos (file.content, sr.end.line-1, sr.end.column);
if (sr.file is TextDocument) {
content = ((TextDocument)sr.file).last_fresh_content;
} else {
content = file.content;
}
var from = (long) Util.get_string_pos (content, sr.begin.line-1, sr.begin.column-1);
var to = (long) Util.get_string_pos (content, sr.end.line-1, sr.end.column);
if (from > to) {
warning ("expression %s has bad source reference %s", expr.to_string (), expr.source_reference.to_string ());
return expr.to_string ();
}
return file.content [from:to];
return file.content[from:to];
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/codehelp/completionengine.vala
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ namespace Vls.CompletionEngine {
Set<CompletionItem> completions,
bool in_oce) {
string method = "textDocument/completion";
var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (doc);
var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (doc) as CodeStyleAnalyzer;
bool in_instance = false;
bool inside_static_or_class_construct_block = false;
var seen_props = new HashSet<string> ();
Expand Down Expand Up @@ -415,7 +415,7 @@ namespace Vls.CompletionEngine {
Vala.SourceFile doc, Vala.TypeSymbol type_symbol, Vala.Scope scope,
Gee.List<Pair<Vala.DataType?, Vala.Symbol>> missing_symbols,
Set<CompletionItem> completions) {
var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (doc);
var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (doc) as CodeStyleAnalyzer;
string spaces = " ";

if (code_style != null)
Expand Down Expand Up @@ -745,7 +745,7 @@ namespace Vls.CompletionEngine {
Vala.CodeNode result, Vala.Scope? scope, Set<CompletionItem> completions,
bool retry_inner = true) {
string method = "textDocument/completion";
var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (doc);
var code_style = compilation.get_analysis_for_file<CodeStyleAnalyzer> (doc) as CodeStyleAnalyzer;
Vala.Scope current_scope = scope ?? CodeHelp.get_scope_containing_node (result);
Vala.DataType? data_type = null;
Vala.Symbol? symbol = null;
Expand Down
2 changes: 1 addition & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
vls_src = files([
'analysis/codeanalyzer.vala',
'analysis/codestyleanalyzer.vala',
'list_symbols.vala',
'analysis/symbolenumerator.vala',
'codehelp/codehelp.vala',
'codehelp/codelensengine.vala',
'codehelp/completionengine.vala',
Expand Down
58 changes: 34 additions & 24 deletions src/projects/compilation.vala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Vls.Compilation : BuildTarget {
/**
* The analyses for each project source.
*/
private HashMap<TextDocument, CodeAnalyzer> _source_analyzers = new HashMap<TextDocument, CodeAnalyzer> ();
private HashMap<Vala.SourceFile, HashMap<Type, CodeAnalyzer>> _source_analyzers = new HashMap<Vala.SourceFile, HashMap<Type, CodeAnalyzer>> ();

public Vala.CodeContext code_context { get; private set; default = new Vala.CodeContext (); }

Expand Down Expand Up @@ -378,26 +378,16 @@ class Vls.Compilation : BuildTarget {
source_file.accept (new CNameMapper (cname_to_sym));
}

// update the code analyses
var removed_sources = new HashSet<TextDocument> ();
foreach (var entry in _source_analyzers)
removed_sources.add (entry.key);

// remove analyses for sources that are no longer a part of the code context
var removed_sources = new Vala.HashSet<Vala.SourceFile> ();
removed_sources.add_all (code_context.get_source_files ());
foreach (var entry in _project_sources) {
var source = entry.value;
if (!_source_analyzers.has_key (source) ||
_source_analyzers[source].last_updated.compare (source.last_updated) < 0) {
var analyzer = new CodeStyleAnalyzer ();
analyzer.visit_source_file (source);
analyzer.last_updated = source.last_updated;
_source_analyzers[source] = analyzer;
}
removed_sources.remove (source);
var text_document = entry.value;
text_document.last_fresh_content = text_document.content;
removed_sources.remove (entry.value);
}

// remove source analyzers for files that have been removed
foreach (var removed_source in removed_sources)
_source_analyzers.unset (removed_source, null);
foreach (var source in removed_sources)
_source_analyzers.unset (source, null);

last_updated = new DateTime.now ();
_completed_first_compile = true;
Expand Down Expand Up @@ -434,13 +424,33 @@ class Vls.Compilation : BuildTarget {
/**
* Get the analysis for the source file
*/
public T? get_analysis_for_file<T> (Vala.SourceFile source) {
var document = source as TextDocument;
public CodeAnalyzer? get_analysis_for_file<T> (Vala.SourceFile source) {
var analyses = _source_analyzers[source];
if (analyses == null) {
analyses = new HashMap<Type, CodeAnalyzer> ();
_source_analyzers[source] = analyses;
}

if (document == null)
return null;
// generate the analysis on demand if it doesn't exist or it is stale
CodeAnalyzer? analysis = null;
if (!analyses.has_key (typeof (T)) || analyses[typeof (T)].last_updated.compare (last_updated) < 0) {
Vala.CodeContext.push (code_context);
if (typeof (T) == typeof (CodeStyleAnalyzer)) {
analysis = new CodeStyleAnalyzer (source);
} else if (typeof (T) == typeof (SymbolEnumerator)) {
analysis = new SymbolEnumerator (source);
}

if (analysis != null) {
analysis.last_updated = new DateTime.now ();
analyses[typeof (T)] = analysis;
}
Vala.CodeContext.pop ();
} else {
analysis = analyses[typeof (T)];
}

return _source_analyzers[document];
return analysis;
}

public bool lookup_input_source_file (File file, out Vala.SourceFile input_source) {
Expand Down
7 changes: 4 additions & 3 deletions src/projects/project.vala
Original file line number Diff line number Diff line change
Expand Up @@ -374,12 +374,13 @@ abstract class Vls.Project : Object {
/**
* Get all source files used in this project.
*/
public Collection<Vala.SourceFile> get_project_source_files () {
var results = new ArrayList<Vala.SourceFile> ();
public Iterable<Map.Entry<Vala.SourceFile, Compilation>> get_project_source_files () {
var results = new HashMap<Vala.SourceFile, Compilation> ();
foreach (var btarget in build_targets) {
if (!(btarget is Compilation))
continue;
results.add_all (((Compilation)btarget).get_project_files ());
foreach (var file in ((Compilation)btarget).get_project_files ())
results[file] = (Compilation)btarget;
}
return results;
}
Expand Down
16 changes: 16 additions & 0 deletions src/projects/textdocument.vala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ class Vls.TextDocument : SourceFile {
}
}

private string? _last_fresh_content = null;

/**
* The contents at the time of compilation.
*/
public string last_fresh_content {
get {
if (_last_fresh_content == null)
return this.content;
return _last_fresh_content;
}
set {
_last_fresh_content = value;
}
}

public TextDocument (CodeContext context, File file, string? content = null, bool cmdline = false) throws FileError {
string? cont = content;
string uri = file.get_uri ();
Expand Down
38 changes: 20 additions & 18 deletions src/server.vala
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class Vls.Server : Object {
call_handlers["textDocument/references"] = this.textDocumentReferences;
call_handlers["textDocument/documentHighlight"] = this.textDocumentReferences;
call_handlers["textDocument/implementation"] = this.textDocumentImplementation;
call_handlers["workspace/symbol"] = this.workspaceSymbol;
call_handlers["workspace/symbol"] = this.handle_workspace_symbol_request;
call_handlers["textDocument/rename"] = this.textDocumentRename;
call_handlers["textDocument/prepareRename"] = this.textDocumentPrepareRename;
call_handlers["textDocument/codeLens"] = this.handle_code_lens_request;
Expand Down Expand Up @@ -1026,7 +1026,7 @@ class Vls.Server : Object {
Vala.CodeContext.push (compilation.code_context);

var array = new Json.Array ();
var syms = new ListSymbols (file);
var syms = compilation.get_analysis_for_file<SymbolEnumerator> (file) as SymbolEnumerator;
if (init_params.capabilities.textDocument.documentSymbol.hierarchicalDocumentSymbolSupport)
foreach (var dsym in syms) {
// debug(@"found $(dsym.name)");
Expand All @@ -1035,7 +1035,7 @@ class Vls.Server : Object {
else {
foreach (var dsym in syms.flattened ()) {
// debug(@"found $(dsym.name)");
array.add_element (Json.gobject_serialize (new SymbolInformation.from_document_symbol (dsym, p.textDocument.uri)));
array.add_element (Json.gobject_serialize (dsym));
}
}

Expand Down Expand Up @@ -1586,8 +1586,7 @@ class Vls.Server : Object {
});
}

// TODO: avoid recreating SymbolInformation unless the compilation has changed?
void workspaceSymbol (Jsonrpc.Server self, Jsonrpc.Client client, string method, Variant id, Variant @params) {
void handle_workspace_symbol_request (Jsonrpc.Server self, Jsonrpc.Client client, string method, Variant id, Variant @params) {
var query = (string) @params.lookup_value ("query", VariantType.STRING);

wait_for_context_update (id, request_cancelled => {
Expand All @@ -1600,19 +1599,22 @@ class Vls.Server : Object {
Project[] all_projects = projects.get_keys_as_array ();
all_projects += default_project;
foreach (var project in all_projects) {
foreach (var text_document in project.get_project_source_files ()) {
Vala.CodeContext.push (text_document.context);
new ListSymbols (text_document)
.flattened ()
// NOTE: if introspection for g_str_match_string () / string.match_string ()
// is fixed, this will have to be changed to `dsym.name.match_sting (query, true)`
.filter (dsym => query.match_string (dsym.name, true))
.foreach (dsym => {
var si = new SymbolInformation.from_document_symbol (dsym,
File.new_for_commandline_arg_and_cwd (text_document.filename, project.root_path).get_uri ());
json_array.add_element (Json.gobject_serialize (si));
return true;
});
foreach (var source_pair in project.get_project_source_files ()) {
var text_document = source_pair.key;
var compilation = source_pair.value;
Vala.CodeContext.push (compilation.code_context);
var symbol_enumerator = compilation.get_analysis_for_file<SymbolEnumerator> (text_document) as SymbolEnumerator;
if (symbol_enumerator != null) {
symbol_enumerator
.flattened ()
// NOTE: if introspection for g_str_match_string () / string.match_string ()
// is fixed, this will have to be changed to `dsym.name.match_sting (query, true)`
.filter (dsym => query.match_string (dsym.name, true))
.foreach (dsym => {
json_array.add_element (Json.gobject_serialize (dsym));
return true;
});
}
Vala.CodeContext.pop ();
}
}
Expand Down

0 comments on commit 50c739d

Please sign in to comment.