Skip to content

Commit

Permalink
analyzer, server: partial symbol, import, and semantic analysis (#363)
Browse files Browse the repository at this point in the history
This PR improves analysis efficiency by only analyzing the affected AST nodes through its line number. Import analysis is now executed if the affected node is an import statement and semantic analysis is executed if its otherwise.

This will hopefully alleviate concerns surrounding #360.

* analyzer: add option for tree cursor to select node based on starting line number

* server: add last_modified_line

* server: determine last first affected line for analysis

* server: skip analyzing imports when affected node is an import decl

* server: fix analyze_file on did_open

* analyzer/semantic_analyzer, server: add support for partial analysis

* analyzer: add position heuristic in register_symbol

* analyzer: fix failing tests
  • Loading branch information
nedpals committed Jul 5, 2022
1 parent 0611e45 commit d5db5b7
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 34 deletions.
4 changes: 2 additions & 2 deletions analyzer/semantic_analyzer.v
Original file line number Diff line number Diff line change
Expand Up @@ -1131,12 +1131,12 @@ pub fn (mut an SemanticAnalyzer) analyze_from_cursor(mut cursor TreeCursor) {
}

// analyze analyzes the given tree
pub fn (mut store Store) analyze(tree &ast.Tree, src_text tree_sitter.SourceText) {
pub fn (mut store Store) analyze(tree &ast.Tree, src_text tree_sitter.SourceText, cfg NewTreeCursorConfig) {
mut an := SemanticAnalyzer{
store: unsafe { store }
src_text: src_text
}

mut cursor := new_tree_cursor(tree.root_node())
mut cursor := new_tree_cursor(tree.root_node(), cfg)
an.analyze_from_cursor(mut cursor)
}
8 changes: 5 additions & 3 deletions analyzer/store.v
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,12 @@ pub fn (mut ss Store) register_symbol(mut info Symbol) ?&Symbol {
}

// Remove this?
if existing_sym.kind !in analyzer.container_symbol_kinds {
if (existing_sym.kind != .placeholder && existing_sym.kind == info.kind)
if existing_sym.kind !in analyzer.container_symbol_kinds {
if existing_sym.kind != .placeholder
&& (info.range.start_point.row > existing_sym.range.start_point.row
|| (existing_sym.kind == info.kind
&& (existing_sym.file_path == info.file_path
&& existing_sym.file_version >= info.file_version) {
&& existing_sym.file_version >= info.file_version))) {
return report_error('Symbol already exists. (idx=$existing_idx) (name="$existing_sym.name")',
info.range)
}
Expand Down
4 changes: 2 additions & 2 deletions analyzer/symbol_registration.v
Original file line number Diff line number Diff line change
Expand Up @@ -924,9 +924,9 @@ pub fn (mut sr SymbolAnalyzer) analyze_from_cursor(mut cursor TreeCursor) []&Sym
}

// register_symbols_from_tree scans and registers all the symbols based on the given tree
pub fn (mut store Store) register_symbols_from_tree(tree &ast.Tree, src_text ts.SourceText, is_import bool) {
pub fn (mut store Store) register_symbols_from_tree(tree &ast.Tree, src_text ts.SourceText, is_import bool, cfg NewTreeCursorConfig) {
mut sr := new_symbol_analyzer(store, src_text, is_import)
mut cursor := new_tree_cursor(tree.root_node())
mut cursor := new_tree_cursor(tree.root_node(), cfg)
sr.analyze_from_cursor(mut cursor)
}

Expand Down
11 changes: 9 additions & 2 deletions analyzer/tree_cursor.v
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ast

struct TreeCursor {
mut:
start_line_nr u32
cur_child_idx int = -1
named_only bool = true
child_count int [required]
Expand All @@ -22,7 +23,7 @@ pub fn (mut tc TreeCursor) next() ?ast.Node {

tc.cur_child_idx++
if cur_node := tc.current_node() {
if tc.named_only && (cur_node.is_named() && !cur_node.is_extra()) {
if tc.named_only && cur_node.raw_node.start_point().row >= tc.start_line_nr && (cur_node.is_named() && !cur_node.is_extra()) {
return cur_node
}
}
Expand Down Expand Up @@ -54,9 +55,15 @@ pub fn (tc &TreeCursor) free() {
}
}

pub fn new_tree_cursor(root_node ast.Node) TreeCursor {
[params]
pub struct NewTreeCursorConfig {
start_line_nr u32
}

pub fn new_tree_cursor(root_node ast.Node, cfg NewTreeCursorConfig) TreeCursor {
return TreeCursor{
child_count: int(root_node.child_count())
cursor: root_node.tree_cursor()
start_line_nr: cfg.start_line_nr
}
}
41 changes: 32 additions & 9 deletions server/text_synchronization.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@ import lsp
import os
import analyzer
import ropes
import tree_sitter_v as v

fn (mut ls Vls) analyze_file(file File) {
fn (mut ls Vls) analyze_file(file File, affected_node_type v.NodeType, affected_line u32) {
if Feature.v_diagnostics in ls.enabled_features {
ls.reporter.clear(file.uri)
}

is_import := affected_node_type == .import_declaration
file_path := file.uri.path()
ls.store.set_active_file_path(file_path, file.version)
ls.store.import_modules_from_tree(file.tree, file.source, os.join_path(file.uri.dir_path(),
'modules'), ls.root_uri.path(), os.dir(os.dir(file_path)))

ls.store.register_symbols_from_tree(file.tree, file.source, false)
ls.store.cleanup_imports()
if Feature.analyzer_diagnostics in ls.enabled_features {
ls.store.analyze(file.tree, file.source)
// skip analyzing imports when affected is not an import declaration
if is_import || affected_line == 0 {
ls.store.import_modules_from_tree(file.tree, file.source, os.join_path(file.uri.dir_path(),
'modules'), ls.root_uri.path(), os.dir(os.dir(file_path)))

ls.store.cleanup_imports()
}

ls.store.register_symbols_from_tree(file.tree, file.source, false, start_line_nr: affected_line)
if !is_import && Feature.analyzer_diagnostics in ls.enabled_features {
ls.store.analyze(file.tree, file.source, start_line_nr: affected_line)
}

ls.reporter.publish(mut ls.writer, file.uri)
}

Expand Down Expand Up @@ -80,7 +89,7 @@ pub fn (mut ls Vls) did_open(params lsp.DidOpenTextDocumentParams, mut wr Respon

// Analyze only if both source and tree exists
if should_be_analyzed {
ls.analyze_file(ls.files[file_uri])
ls.analyze_file(ls.files[file_uri], .unknown, 0)
}

// wr.log_message('$file_uri | has_file: $has_file | should_be_analyzed: $should_be_analyzed',
Expand Down Expand Up @@ -109,8 +118,9 @@ pub fn (mut ls Vls) did_change(params lsp.DidChangeTextDocumentParams, mut wr Re

mut new_src := ls.files[uri].source
mut new_tree := ls.files[uri].tree.raw_tree.copy()
mut first_affected_start_offset := u32(0)

for content_change in params.content_changes {
for change_i, content_change in params.content_changes {
change_text := content_change.text
start_idx := compute_offset(new_src, content_change.range.start.line, content_change.range.start.character)
old_end_idx := compute_offset(new_src, content_change.range.end.line, content_change.range.end.character)
Expand All @@ -119,6 +129,10 @@ pub fn (mut ls Vls) did_change(params lsp.DidChangeTextDocumentParams, mut wr Re
old_end_pos := content_change.range.end
new_end_pos := compute_position(new_src, new_end_idx)

if change_i == 0 {
first_affected_start_offset = u32(start_idx)
}

old_len := new_src.len()
new_len := old_len - (old_end_idx - start_idx) + change_text.len
diff := new_len - old_len
Expand Down Expand Up @@ -157,6 +171,15 @@ pub fn (mut ls Vls) did_change(params lsp.DidChangeTextDocumentParams, mut wr Re
ls.files[uri].source = new_src
ls.files[uri].version = params.text_document.version

// record last first content change line for partial analysis
if first_affected_direct_node := ls.files[uri].tree.root_node().first_named_child_for_byte(first_affected_start_offset) {
ls.last_modified_line = first_affected_direct_node.raw_node.start_point().row
ls.last_affected_node = first_affected_direct_node.type_name
} else {
ls.last_modified_line = u32(params.content_changes[0].range.start.line)
ls.last_affected_node = .unknown
}

if Feature.v_diagnostics !in ls.enabled_features {
ls.reporter.clear_from_range(
uri,
Expand Down
36 changes: 20 additions & 16 deletions server/vls.v
Original file line number Diff line number Diff line change
Expand Up @@ -108,23 +108,25 @@ mut:

struct Vls {
mut:
vroot_path string
parser &tree_sitter.Parser<v.NodeType>
store analyzer.Store
status ServerStatus = .off
root_uri lsp.DocumentUri
is_typing bool
typing_ch chan int
enabled_features []Feature = server.default_features_list
capabilities lsp.ServerCapabilities
panic_count int
shutdown_timeout time.Duration = 5 * time.minute
client_pid int
vroot_path string
parser &tree_sitter.Parser<v.NodeType>
store analyzer.Store
status ServerStatus = .off
root_uri lsp.DocumentUri
last_modified_line u32 // for did_change
last_affected_node v.NodeType = v.NodeType.unknown
is_typing bool
typing_ch chan int
enabled_features []Feature = server.default_features_list
capabilities lsp.ServerCapabilities
panic_count int
shutdown_timeout time.Duration = 5 * time.minute
client_pid int
// client_capabilities lsp.ClientCapabilities
reporter &DiagnosticReporter
writer &ResponseWriter = &ResponseWriter(0)
reporter &DiagnosticReporter
writer &ResponseWriter = &ResponseWriter(0)
pub mut:
files map[string]File
files map[string]File
}

pub fn new() &Vls {
Expand Down Expand Up @@ -380,7 +382,9 @@ pub fn monitor_changes(mut ls Vls, mut resp_wr ResponseWriter) {
}

uri := lsp.document_uri_from_path(ls.store.cur_file_path)
ls.analyze_file(ls.files[uri])
ls.analyze_file(ls.files[uri], ls.last_affected_node, ls.last_modified_line)
ls.last_modified_line = 0
ls.last_affected_node = .unknown
ls.is_typing = false
}
}
Expand Down

0 comments on commit d5db5b7

Please sign in to comment.