From 39f7408ccbebe3f1126a4d3b7d44208c646934d7 Mon Sep 17 00:00:00 2001
From: Francois Belair <razoric480@gmail.com>
Date: Sat, 10 Apr 2021 15:10:29 -0400
Subject: Implement LSP didDeleteFiles & make parser aware of sub-nodes

---
 .../language_server/gdscript_extend_parser.cpp     |  88 ++++++++++++----
 .../language_server/gdscript_workspace.cpp         |  11 ++
 .../gdscript/language_server/gdscript_workspace.h  |   1 +
 modules/gdscript/language_server/lsp.hpp           | 114 +++++++++++++++++++++
 4 files changed, 195 insertions(+), 19 deletions(-)

(limited to 'modules/gdscript/language_server')

diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index e63b6ab20e..15236d900d 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -49,8 +49,9 @@ void ExtendGDScriptParser::update_diagnostics() {
 		diagnostic.code = -1;
 		lsp::Range range;
 		lsp::Position pos;
-		int line = LINE_NUMBER_TO_INDEX(error.line);
-		const String &line_text = get_lines()[line];
+		const PackedStringArray lines = get_lines();
+		int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, lines.size() - 1);
+		const String &line_text = lines[line];
 		pos.line = line;
 		pos.character = line_text.length() - line_text.strip_edges(true, false).length();
 		range.start = pos;
@@ -361,24 +362,73 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
 		r_symbol.detail += " -> " + p_func->get_datatype().to_string();
 	}
 
-	for (int i = 0; i < p_func->body->locals.size(); i++) {
-		const SuiteNode::Local &local = p_func->body->locals[i];
-		lsp::DocumentSymbol symbol;
-		symbol.name = local.name;
-		symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable;
-		symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line);
-		symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column);
-		symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line);
-		symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column);
-		symbol.uri = uri;
-		symbol.script_path = path;
-		symbol.detail = SuiteNode::Local::CONSTANT ? "const " : "var ";
-		symbol.detail += symbol.name;
-		if (local.get_datatype().is_hard_type()) {
-			symbol.detail += ": " + local.get_datatype().to_string();
+	List<GDScriptParser::SuiteNode *> function_nodes;
+
+	List<GDScriptParser::Node *> node_stack;
+	node_stack.push_back(p_func->body);
+
+	while (!node_stack.is_empty()) {
+		GDScriptParser::Node *node = node_stack[0];
+		node_stack.pop_front();
+
+		switch (node->type) {
+			case GDScriptParser::TypeNode::IF: {
+				GDScriptParser::IfNode *if_node = (GDScriptParser::IfNode *)node;
+				node_stack.push_back(if_node->true_block);
+				if (if_node->false_block) {
+					node_stack.push_back(if_node->false_block);
+				}
+			} break;
+
+			case GDScriptParser::TypeNode::FOR: {
+				GDScriptParser::ForNode *for_node = (GDScriptParser::ForNode *)node;
+				node_stack.push_back(for_node->loop);
+			} break;
+
+			case GDScriptParser::TypeNode::WHILE: {
+				GDScriptParser::WhileNode *while_node = (GDScriptParser::WhileNode *)node;
+				node_stack.push_back(while_node->loop);
+			} break;
+
+			case GDScriptParser::TypeNode::MATCH_BRANCH: {
+				GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node;
+				node_stack.push_back(match_node->block);
+			} break;
+
+			case GDScriptParser::TypeNode::SUITE: {
+				GDScriptParser::SuiteNode *suite_node = (GDScriptParser::SuiteNode *)node;
+				function_nodes.push_back(suite_node);
+				for (int i = 0; i < suite_node->statements.size(); ++i) {
+					node_stack.push_back(suite_node->statements[i]);
+				}
+			} break;
+
+			default:
+				continue;
+		}
+	}
+
+	for (List<GDScriptParser::SuiteNode *>::Element *N = function_nodes.front(); N; N = N->next()) {
+		const GDScriptParser::SuiteNode *suite_node = N->get();
+		for (int i = 0; i < suite_node->locals.size(); i++) {
+			const SuiteNode::Local &local = suite_node->locals[i];
+			lsp::DocumentSymbol symbol;
+			symbol.name = local.name;
+			symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable;
+			symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line);
+			symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column);
+			symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line);
+			symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column);
+			symbol.uri = uri;
+			symbol.script_path = path;
+			symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var ";
+			symbol.detail += symbol.name;
+			if (local.get_datatype().is_hard_type()) {
+				symbol.detail += ": " + local.get_datatype().to_string();
+			}
+			symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line));
+			r_symbol.children.push_back(symbol);
 		}
-		symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line));
-		r_symbol.children.push_back(symbol);
 	}
 }
 
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index 69cad1a335..9b7b2b36b4 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -42,6 +42,7 @@
 #include "scene/resources/packed_scene.h"
 
 void GDScriptWorkspace::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files);
 	ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol);
 	ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script);
 	ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script);
@@ -51,6 +52,16 @@ void GDScriptWorkspace::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api);
 }
 
+void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) {
+	Array files = p_params["files"];
+	for (int i = 0; i < files.size(); ++i) {
+		Dictionary file = files[i];
+		String uri = file["uri"];
+		String path = get_file_path(uri);
+		parse_script(path, "");
+	}
+}
+
 void GDScriptWorkspace::remove_cache_parser(const String &p_path) {
 	Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path);
 	Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path);
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
index 7fd8bfcf20..27616a2989 100644
--- a/modules/gdscript/language_server/gdscript_workspace.h
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -89,6 +89,7 @@ public:
 	void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
 	Dictionary generate_script_api(const String &p_path);
 	Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature);
+	void did_delete_files(const Dictionary &p_params);
 
 	GDScriptWorkspace();
 	~GDScriptWorkspace();
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
index 6635098be2..47bcfeaefc 100644
--- a/modules/gdscript/language_server/lsp.hpp
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -1528,6 +1528,114 @@ struct SignatureHelp {
 	}
 };
 
+/**
+ * A pattern to describe in which file operation requests or notifications
+ * the server is interested in.
+ */
+struct FileOperationPattern {
+	/**
+	 * The glob pattern to match.
+	 */
+	String glob = "**/*.gd";
+
+	/**
+	 * Whether to match `file`s or `folder`s with this pattern.
+	 *
+	 * Matches both if undefined.
+	 */
+	String matches = "file";
+
+	Dictionary to_json() const {
+		Dictionary dict;
+
+		dict["glob"] = glob;
+		dict["matches"] = matches;
+
+		return dict;
+	}
+};
+
+/**
+ * A filter to describe in which file operation requests or notifications
+ * the server is interested in.
+ */
+struct FileOperationFilter {
+	/**
+	 * The actual file operation pattern.
+	 */
+	FileOperationPattern pattern;
+
+	Dictionary to_json() const {
+		Dictionary dict;
+
+		dict["pattern"] = pattern.to_json();
+
+		return dict;
+	}
+};
+
+/**
+ * The options to register for file operations.
+ */
+struct FileOperationRegistrationOptions {
+	/**
+	 * The actual filters.
+	 */
+	Vector<FileOperationFilter> filters;
+
+	FileOperationRegistrationOptions() {
+		filters.push_back(FileOperationFilter());
+	}
+
+	Dictionary to_json() const {
+		Dictionary dict;
+
+		Array filts;
+		for (int i = 0; i < filters.size(); i++) {
+			filts.push_back(filters[i].to_json());
+		}
+		dict["filters"] = filts;
+
+		return dict;
+	}
+};
+
+/**
+ * The server is interested in file notifications/requests.
+ */
+struct FileOperations {
+	/**
+	 * The server is interested in receiving didDeleteFiles file notifications.
+	 */
+	FileOperationRegistrationOptions didDelete;
+
+	Dictionary to_json() const {
+		Dictionary dict;
+
+		dict["didDelete"] = didDelete.to_json();
+
+		return dict;
+	}
+};
+
+/**
+ * Workspace specific server capabilities
+ */
+struct Workspace {
+	/**
+	 * The server is interested in file notifications/requests.
+	 */
+	FileOperations fileOperations;
+
+	Dictionary to_json() const {
+		Dictionary dict;
+
+		dict["fileOperations"] = fileOperations.to_json();
+
+		return dict;
+	}
+};
+
 struct ServerCapabilities {
 	/**
 	 * Defines how text documents are synced. Is either a detailed structure defining each notification or
@@ -1589,6 +1697,11 @@ struct ServerCapabilities {
 	 */
 	bool workspaceSymbolProvider = true;
 
+	/**
+	 * The server supports workspace folder.
+	 */
+	Workspace workspace;
+
 	/**
 	 * The server provides code actions. The `CodeActionOptions` return type is only
 	 * valid if the client signals code action literal support via the property
@@ -1676,6 +1789,7 @@ struct ServerCapabilities {
 		dict["documentHighlightProvider"] = documentHighlightProvider;
 		dict["documentSymbolProvider"] = documentSymbolProvider;
 		dict["workspaceSymbolProvider"] = workspaceSymbolProvider;
+		dict["workspace"] = workspace.to_json();
 		dict["codeActionProvider"] = codeActionProvider;
 		dict["documentFormattingProvider"] = documentFormattingProvider;
 		dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider;
-- 
cgit v1.2.3