diff options
Diffstat (limited to 'modules/gdscript/language_server')
9 files changed, 512 insertions, 121 deletions
| diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index e63b6ab20e..d106b3b541 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -32,7 +32,6 @@  #include "../gdscript.h"  #include "../gdscript_analyzer.h" -#include "core/io/json.h"  #include "gdscript_language_protocol.h"  #include "gdscript_workspace.h" @@ -40,8 +39,7 @@ void ExtendGDScriptParser::update_diagnostics() {  	diagnostics.clear();  	const List<ParserError> &errors = get_errors(); -	for (const List<ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { -		const ParserError &error = E->get(); +	for (const ParserError &error : errors) {  		lsp::Diagnostic diagnostic;  		diagnostic.severity = lsp::DiagnosticSeverity::Error;  		diagnostic.message = error.message; @@ -49,8 +47,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; @@ -61,8 +60,7 @@ void ExtendGDScriptParser::update_diagnostics() {  	}  	const List<GDScriptWarning> &warnings = get_warnings(); -	for (const List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { -		const GDScriptWarning &warning = E->get(); +	for (const GDScriptWarning &warning : warnings) {  		lsp::Diagnostic diagnostic;  		diagnostic.severity = lsp::DiagnosticSeverity::Warning;  		diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message(); @@ -152,9 +150,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p  	}  	r_symbol.kind = lsp::SymbolKind::Class;  	r_symbol.deprecated = false; -	r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->start_line); -	r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_class->start_column); -	r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line); +	r_symbol.range.start.line = p_class->start_line; +	r_symbol.range.start.character = p_class->start_column; +	r_symbol.range.end.line = lines.size();  	r_symbol.selectionRange.start.line = r_symbol.range.start.line;  	r_symbol.detail = "class " + r_symbol.name;  	bool is_root_class = &r_symbol == &class_symbol; @@ -167,7 +165,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p  			case ClassNode::Member::VARIABLE: {  				lsp::DocumentSymbol symbol;  				symbol.name = m.variable->identifier->name; -				symbol.kind = lsp::SymbolKind::Variable; +				symbol.kind = m.variable->property == VariableNode::PropertyStyle::PROP_NONE ? lsp::SymbolKind::Variable : lsp::SymbolKind::Property;  				symbol.deprecated = false;  				symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line);  				symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column); @@ -182,7 +180,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p  					symbol.detail += ": " + m.get_datatype().to_string();  				}  				if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) { -					symbol.detail += " = " + JSON::print(m.variable->initializer->reduced_value); +					symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string();  				}  				symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); @@ -223,10 +221,10 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p  							}  						}  					} else { -						value_text = JSON::print(default_value); +						value_text = default_value.to_json_string();  					}  				} else { -					value_text = JSON::print(default_value); +					value_text = default_value.to_json_string();  				}  				if (!value_text.is_empty()) {  					symbol.detail += " = " + value_text; @@ -319,7 +317,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN  	const String uri = get_uri();  	r_symbol.name = p_func->identifier->name; -	r_symbol.kind = lsp::SymbolKind::Function; +	r_symbol.kind = p_func->is_static ? lsp::SymbolKind::Function : lsp::SymbolKind::Method;  	r_symbol.detail = "func " + String(p_func->identifier->name) + "(";  	r_symbol.deprecated = false;  	r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line); @@ -352,8 +350,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN  			parameters += ": " + parameter->get_datatype().to_string();  		}  		if (parameter->default_value != nullptr) { -			String value = JSON::print(parameter->default_value->reduced_value); -			parameters += " = " + value; +			parameters += " = " + parameter->default_value->reduced_value.to_json_string();  		}  	}  	r_symbol.detail += parameters + ")"; @@ -361,24 +358,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);  	}  } @@ -419,8 +465,8 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {  	}  	String doc; -	for (List<String>::Element *E = doc_lines.front(); E; E = E->next()) { -		doc += E->get() + "\n"; +	for (const String &E : doc_lines) { +		doc += E + "\n";  	}  	return doc;  } @@ -647,7 +693,9 @@ Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::Functio  	ERR_FAIL_NULL_V(p_func, func);  	func["name"] = p_func->identifier->name;  	func["return_type"] = p_func->get_datatype().to_string(); -	func["rpc_mode"] = p_func->rpc_mode; +	func["rpc_mode"] = p_func->rpc_config.rpc_mode; +	func["rpc_transfer_mode"] = p_func->rpc_config.transfer_mode; +	func["rpc_transfer_channel"] = p_func->rpc_config.channel;  	Array parameters;  	for (int i = 0; i < p_func->parameters.size(); i++) {  		Dictionary arg; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 912c9a174e..b6c48468f5 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -31,8 +31,6 @@  #include "gdscript_language_protocol.h"  #include "core/config/project_settings.h" -#include "core/io/json.h" -#include "core/os/copymem.h"  #include "editor/doc_tools.h"  #include "editor/editor_log.h"  #include "editor/editor_node.h" @@ -130,13 +128,13 @@ Error GDScriptLanguageProtocol::on_client_connected() {  	peer->connection = tcp_peer;  	clients.set(next_client_id, peer);  	next_client_id++; -	EditorNode::get_log()->add_message("Connection Taken", EditorLog::MSG_TYPE_EDITOR); +	EditorNode::get_log()->add_message("[LSP] Connection Taken", EditorLog::MSG_TYPE_EDITOR);  	return OK;  }  void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) {  	clients.erase(p_client_id); -	EditorNode::get_log()->add_message("Disconnected", EditorLog::MSG_TYPE_EDITOR); +	EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR);  }  String GDScriptLanguageProtocol::process_message(const String &p_text) { @@ -195,7 +193,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {  				vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id));  		Ref<LSPeer> peer = clients.get(latest_client_id);  		if (peer != nullptr) { -			String msg = JSON::print(request); +			String msg = Variant(request).to_json_string();  			msg = format_output(msg);  			(*peer)->res_queue.push_back(msg.utf8());  		} @@ -256,7 +254,7 @@ void GDScriptLanguageProtocol::poll() {  	}  } -Error GDScriptLanguageProtocol::start(int p_port, const IP_Address &p_bind_ip) { +Error GDScriptLanguageProtocol::start(int p_port, const IPAddress &p_bind_ip) {  	return server->listen(p_port, p_bind_ip);  } @@ -281,7 +279,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia  	ERR_FAIL_COND(peer == nullptr);  	Dictionary message = make_notification(p_method, p_params); -	String msg = JSON::print(message); +	String msg = Variant(message).to_json_string();  	msg = format_output(msg);  	peer->res_queue.push_back(msg.utf8());  } @@ -295,10 +293,10 @@ bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const {  }  GDScriptLanguageProtocol::GDScriptLanguageProtocol() { -	server.instance(); +	server.instantiate();  	singleton = this; -	workspace.instance(); -	text_document.instance(); +	workspace.instantiate(); +	text_document.instantiate();  	set_scope("textDocument", text_document.ptr());  	set_scope("completionItem", text_document.ptr());  	set_scope("workspace", workspace.ptr()); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index 8b08ae0655..5a2dd55c46 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -28,8 +28,8 @@  /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */  /*************************************************************************/ -#ifndef GDSCRIPT_PROTOCAL_SERVER_H -#define GDSCRIPT_PROTOCAL_SERVER_H +#ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H +#define GDSCRIPT_LANGUAGE_PROTOCOL_H  #include "core/io/stream_peer.h"  #include "core/io/stream_peer_tcp.h" @@ -37,7 +37,13 @@  #include "gdscript_text_document.h"  #include "gdscript_workspace.h"  #include "lsp.hpp" + +#include "modules/modules_enabled.gen.h" +#ifdef MODULE_JSONRPC_ENABLED  #include "modules/jsonrpc/jsonrpc.h" +#else +#error "Can't build GDScript LSP without JSONRPC module." +#endif  #define LSP_MAX_BUFFER_SIZE 4194304  #define LSP_MAX_CLIENTS 8 @@ -46,7 +52,7 @@ class GDScriptLanguageProtocol : public JSONRPC {  	GDCLASS(GDScriptLanguageProtocol, JSONRPC)  private: -	struct LSPeer : Reference { +	struct LSPeer : RefCounted {  		Ref<StreamPeerTCP> connection;  		uint8_t req_buf[LSP_MAX_BUFFER_SIZE]; @@ -69,7 +75,7 @@ private:  	static GDScriptLanguageProtocol *singleton;  	HashMap<int, Ref<LSPeer>> clients; -	Ref<TCP_Server> server; +	Ref<TCPServer> server;  	int latest_client_id = 0;  	int next_client_id = 0; @@ -97,7 +103,7 @@ public:  	_FORCE_INLINE_ bool is_initialized() const { return _initialized; }  	void poll(); -	Error start(int p_port, const IP_Address &p_bind_ip); +	Error start(int p_port, const IPAddress &p_bind_ip);  	void stop();  	void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); @@ -108,4 +114,4 @@ public:  	GDScriptLanguageProtocol();  }; -#endif +#endif // GDSCRIPT_LANGUAGE_PROTOCOL_H diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 98ada9de4d..c47164d95b 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -30,7 +30,7 @@  #include "gdscript_language_server.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h"  #include "core/os/os.h"  #include "editor/editor_log.h"  #include "editor/editor_node.h" @@ -78,7 +78,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) {  void GDScriptLanguageServer::start() {  	port = (int)_EDITOR_GET("network/language_server/remote_port");  	use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); -	if (protocol.start(port, IP_Address("127.0.0.1")) == OK) { +	if (protocol.start(port, IPAddress("127.0.0.1")) == OK) {  		EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR);  		if (use_thread) {  			thread_running = true; @@ -101,7 +101,7 @@ void GDScriptLanguageServer::stop() {  }  void register_lsp_types() { -	ClassDB::register_class<GDScriptLanguageProtocol>(); -	ClassDB::register_class<GDScriptTextDocument>(); -	ClassDB::register_class<GDScriptWorkspace>(); +	GDREGISTER_CLASS(GDScriptLanguageProtocol); +	GDREGISTER_CLASS(GDScriptTextDocument); +	GDREGISTER_CLASS(GDScriptWorkspace);  } diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 9f2373bf56..03b1e3fa44 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -40,11 +40,14 @@  void GDScriptTextDocument::_bind_methods() {  	ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); +	ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose);  	ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); +	ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave);  	ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);  	ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);  	ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);  	ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); +	ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename);  	ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);  	ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);  	ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink); @@ -61,6 +64,11 @@ void GDScriptTextDocument::didOpen(const Variant &p_param) {  	sync_script_content(doc.uri, doc.text);  } +void GDScriptTextDocument::didClose(const Variant &p_param) { +	// Left empty on purpose. Godot does nothing special on closing a document, +	// but it satisfies LSP clients that require didClose be implemented. +} +  void GDScriptTextDocument::didChange(const Variant &p_param) {  	lsp::TextDocumentItem doc = load_document_item(p_param);  	Dictionary dict = p_param; @@ -73,6 +81,20 @@ void GDScriptTextDocument::didChange(const Variant &p_param) {  	sync_script_content(doc.uri, doc.text);  } +void GDScriptTextDocument::didSave(const Variant &p_param) { +	lsp::TextDocumentItem doc = load_document_item(p_param); +	Dictionary dict = p_param; +	String text = dict["text"]; + +	sync_script_content(doc.uri, text); + +	/*String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri); + +	Ref<GDScript> script = ResourceLoader::load(path); +	script->load_source_code(path); +	script->reload(true);*/ +} +  lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {  	lsp::TextDocumentItem doc;  	Dictionary params = p_param; @@ -151,8 +173,7 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) {  		int i = 0;  		arr.resize(options.size()); -		for (const List<ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { -			const ScriptCodeCompletionOption &option = E->get(); +		for (const ScriptCodeCompletionOption &option : options) {  			lsp::CompletionItem item;  			item.label = option.display;  			item.data = request_data; @@ -210,6 +231,14 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) {  	return arr;  } +Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) { +	lsp::TextDocumentPositionParams params; +	params.load(p_params); +	String new_name = p_params["newName"]; + +	return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name); +} +  Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {  	lsp::CompletionItem item;  	item.load(p_params); @@ -262,8 +291,8 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {  		}  	} else if (item.kind == lsp::CompletionItemKind::Event) {  		if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) { -			const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; -			item.insertText = quote_style + item.label + quote_style; +			const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; +			item.insertText = item.label.quote(quote_style);  		}  	} @@ -288,8 +317,8 @@ Array GDScriptTextDocument::documentLink(const Dictionary &p_params) {  	List<lsp::DocumentLink> links;  	GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links); -	for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { -		ret.push_back(E->get().to_json()); +	for (const lsp::DocumentLink &E : links) { +		ret.push_back(E.to_json());  	}  	return ret;  } @@ -316,8 +345,8 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) {  		Array contents;  		List<const lsp::DocumentSymbol *> list;  		GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list); -		for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { -			if (const lsp::DocumentSymbol *s = E->get()) { +		for (const lsp::DocumentSymbol *&E : list) { +			if (const lsp::DocumentSymbol *s = E) {  				contents.push_back(s->render().value);  			}  		} @@ -367,7 +396,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) {  					id = "class_global:" + symbol->native_class + ":" + symbol->name;  					break;  			} -			call_deferred("show_native_symbol_in_editor", id); +			call_deferred(SNAME("show_native_symbol_in_editor"), id);  		} else {  			notify_client_show_symbol(symbol);  		} @@ -400,10 +429,19 @@ GDScriptTextDocument::~GDScriptTextDocument() {  void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {  	String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);  	GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); + +	EditorFileSystem::get_singleton()->update_file(path); +	Error error; +	Ref<GDScript> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error); +	if (error == OK) { +		if (script->load_source_code(path) == OK) { +			script->reload(true); +		} +	}  }  void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { -	ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id); +	ScriptEditor::get_singleton()->call_deferred(SNAME("_help_class_goto"), p_symbol_id);  	DisplayServer::get_singleton()->window_move_to_foreground();  } @@ -423,8 +461,8 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &  	} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {  		List<const lsp::DocumentSymbol *> list;  		GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(p_location, list); -		for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { -			if (const lsp::DocumentSymbol *s = E->get()) { +		for (const lsp::DocumentSymbol *&E : list) { +			if (const lsp::DocumentSymbol *s = E) {  				if (!s->uri.is_empty()) {  					lsp::Location location;  					location.uri = s->uri; diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 792e601bc1..9021c84a3f 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -31,19 +31,21 @@  #ifndef GDSCRIPT_TEXT_DOCUMENT_H  #define GDSCRIPT_TEXT_DOCUMENT_H -#include "core/object/reference.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h"  #include "lsp.hpp" -class GDScriptTextDocument : public Reference { -	GDCLASS(GDScriptTextDocument, Reference) +class GDScriptTextDocument : public RefCounted { +	GDCLASS(GDScriptTextDocument, RefCounted)  protected:  	static void _bind_methods();  	FileAccess *file_checker;  	void didOpen(const Variant &p_param); +	void didClose(const Variant &p_param);  	void didChange(const Variant &p_param); +	void didSave(const Variant &p_param);  	void sync_script_content(const String &p_path, const String &p_content);  	void show_native_symbol_in_editor(const String &p_symbol_id); @@ -60,6 +62,7 @@ public:  	Array documentSymbol(const Dictionary &p_params);  	Array completion(const Dictionary &p_params);  	Dictionary resolve(const Dictionary &p_params); +	Dictionary rename(const Dictionary &p_params);  	Array foldingRange(const Dictionary &p_params);  	Array codeLens(const Dictionary &p_params);  	Array documentLink(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 69cad1a335..1512b4bb89 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); @@ -105,11 +116,40 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_  	return nullptr;  } +const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier) { +	for (int i = 0; i < p_parent->children.size(); ++i) { +		const lsp::DocumentSymbol *parameter_symbol = &p_parent->children[i]; +		if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) { +			return parameter_symbol; +		} +	} + +	return nullptr; +} + +const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier) { +	const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols(); + +	for (int i = 0; i < class_symbol->children.size(); ++i) { +		if (class_symbol->children[i].kind == lsp::SymbolKind::Function || class_symbol->children[i].kind == lsp::SymbolKind::Class) { +			const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + +			for (int l = 0; l < function_symbol->children.size(); ++l) { +				const lsp::DocumentSymbol *local = &function_symbol->children[l]; +				if (!local->detail.is_empty() && local->name == p_symbol_identifier) { +					return local; +				} +			} +		} +	} + +	return nullptr; +} +  void GDScriptWorkspace::reload_all_workspace_scripts() {  	List<String> paths;  	list_script_files("res://", paths); -	for (List<String>::Element *E = paths.front(); E; E = E->next()) { -		const String &path = E->get(); +	for (const String &path : paths) {  		Error err;  		String content = FileAccess::get_file_as_string(path, &err);  		ERR_CONTINUE(err != OK); @@ -177,7 +217,9 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) {  			E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols);  			for (int i = 0; i < script_symbols.size(); ++i) {  				if (query.is_subsequence_ofi(script_symbols[i].name)) { -					arr.push_back(script_symbols[i].to_json()); +					lsp::DocumentedSymbolInformation symbol = script_symbols[i]; +					symbol.location.uri = get_file_uri(symbol.location.uri); +					arr.push_back(symbol.to_json());  				}  			}  		} @@ -219,18 +261,13 @@ Error GDScriptWorkspace::initialize() {  			class_symbol.children.push_back(symbol);  		} -		Vector<DocData::PropertyDoc> properties; -		properties.append_array(class_data.properties); -		const int theme_prop_start_idx = properties.size(); -		properties.append_array(class_data.theme_properties); -  		for (int i = 0; i < class_data.properties.size(); i++) {  			const DocData::PropertyDoc &data = class_data.properties[i];  			lsp::DocumentSymbol symbol;  			symbol.name = data.name;  			symbol.native_class = class_name;  			symbol.kind = lsp::SymbolKind::Property; -			symbol.detail = String(i >= theme_prop_start_idx ? "<Theme> var" : "var") + " " + class_name + "." + data.name; +			symbol.detail = "var " + class_name + "." + data.name;  			if (data.enumeration.length()) {  				symbol.detail += ": " + data.enumeration;  			} else { @@ -240,6 +277,17 @@ Error GDScriptWorkspace::initialize() {  			class_symbol.children.push_back(symbol);  		} +		for (int i = 0; i < class_data.theme_properties.size(); i++) { +			const DocData::ThemeItemDoc &data = class_data.theme_properties[i]; +			lsp::DocumentSymbol symbol; +			symbol.name = data.name; +			symbol.native_class = class_name; +			symbol.kind = lsp::SymbolKind::Property; +			symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type; +			symbol.documentation = data.description; +			class_symbol.children.push_back(symbol); +		} +  		Vector<DocData::MethodDoc> methods_signals;  		methods_signals.append_array(class_data.methods);  		const int signal_start_idx = methods_signals.size(); @@ -338,6 +386,50 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont  	return err;  } +Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { +	Error err; +	String path = get_file_path(p_doc_pos.textDocument.uri); + +	lsp::WorkspaceEdit edit; + +	List<String> paths; +	list_script_files("res://", paths); + +	const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); +	if (reference_symbol) { +		String identifier = reference_symbol->name; + +		for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { +			PackedStringArray content = FileAccess::get_file_as_string(PE->get(), &err).split("\n"); +			for (int i = 0; i < content.size(); ++i) { +				String line = content[i]; + +				int character = line.find(identifier); +				while (character > -1) { +					lsp::TextDocumentPositionParams params; + +					lsp::TextDocumentIdentifier text_doc; +					text_doc.uri = get_file_uri(PE->get()); + +					params.textDocument = text_doc; +					params.position.line = i; +					params.position.character = character; + +					const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + +					if (other_symbol == reference_symbol) { +						edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name); +					} + +					character = line.find(identifier, character + 1); +				} +			} +		} +	} + +	return edit.to_json(); +} +  Error GDScriptWorkspace::parse_local_script(const String &p_path) {  	Error err;  	String content = FileAccess::get_file_as_string(p_path, &err); @@ -413,7 +505,7 @@ Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {  		RES owner_res = ResourceLoader::load(owner_path);  		if (Object::cast_to<PackedScene>(owner_res.ptr())) {  			Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res)); -			owner_scene_node = owner_packed_scene->instance(); +			owner_scene_node = owner_packed_scene->instantiate();  			break;  		}  	} @@ -428,8 +520,31 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S  	if (const ExtendGDScriptParser *parser = get_parse_result(path)) {  		Node *owner_scene_node = _get_owner_scene_node(path); + +		Array stack; +		Node *current = nullptr; +		if (owner_scene_node != nullptr) { +			stack.push_back(owner_scene_node); + +			while (!stack.is_empty()) { +				current = stack.pop_back(); +				Ref<GDScript> script = current->get_script(); +				if (script.is_valid() && script->get_path() == path) { +					break; +				} +				for (int i = 0; i < current->get_child_count(); ++i) { +					stack.push_back(current->get_child(i)); +				} +			} + +			Ref<GDScript> script = current->get_script(); +			if (!script.is_valid() || script->get_path() != path) { +				current = owner_scene_node; +			} +		} +  		String code = parser->get_text_for_completion(p_params.position); -		GDScriptLanguage::get_singleton()->complete_code(code, path, owner_scene_node, r_options, forced, call_hint); +		GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);  		if (owner_scene_node) {  			memdelete(owner_scene_node);  		} @@ -466,10 +581,16 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu  						String target_script_path = path;  						if (!ret.script.is_null()) {  							target_script_path = ret.script->get_path(); +						} else if (!ret.class_path.is_empty()) { +							target_script_path = ret.class_path;  						}  						if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {  							symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); + +							if (symbol && symbol->kind == lsp::SymbolKind::Function && symbol->name != symbol_identifier) { +								symbol = get_parameter_symbol(symbol, symbol_identifier); +							}  						}  					} else { @@ -481,6 +602,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu  					}  				} else {  					symbol = parser->get_member_symbol(symbol_identifier); + +					if (!symbol) { +						symbol = get_local_symbol(parser, symbol_identifier); +					}  				}  			}  		} @@ -546,8 +671,8 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::N  void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) {  	if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) {  		const List<lsp::DocumentLink> &links = parser->get_document_links(); -		for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { -			r_list.push_back(E->get()); +		for (const lsp::DocumentLink &E : links) { +			r_list.push_back(E);  		}  	}  } @@ -574,8 +699,7 @@ Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams  				GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols);  			} -			for (List<const lsp::DocumentSymbol *>::Element *E = symbols.front(); E; E = E->next()) { -				const lsp::DocumentSymbol *symbol = E->get(); +			for (const lsp::DocumentSymbol *const &symbol : symbols) {  				if (symbol->kind == lsp::SymbolKind::Method || symbol->kind == lsp::SymbolKind::Function) {  					lsp::SignatureInformation signature_info;  					signature_info.label = symbol->detail; diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 7fd8bfcf20..9496677449 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -37,8 +37,8 @@  #include "gdscript_extend_parser.h"  #include "lsp.hpp" -class GDScriptWorkspace : public Reference { -	GDCLASS(GDScriptWorkspace, Reference); +class GDScriptWorkspace : public RefCounted { +	GDCLASS(GDScriptWorkspace, RefCounted);  private:  	void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners); @@ -52,6 +52,8 @@ protected:  	const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const;  	const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const; +	const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier); +	const lsp::DocumentSymbol *get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier);  	void reload_all_workspace_scripts(); @@ -89,6 +91,8 @@ 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); +	Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name);  	GDScriptWorkspace();  	~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 6635098be2..9ac6c6bd4e 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -255,6 +255,62 @@ struct TextEdit {  };  /** + * The edits to be applied. + */ +struct WorkspaceEdit { +	/** +	 * Holds changes to existing resources. +	 */ +	Map<String, Vector<TextEdit>> changes; + +	_FORCE_INLINE_ Dictionary to_json() const { +		Dictionary dict; + +		Dictionary out_changes; +		for (Map<String, Vector<TextEdit>>::Element *E = changes.front(); E; E = E->next()) { +			Array edits; +			for (int i = 0; i < E->get().size(); ++i) { +				Dictionary text_edit; +				text_edit["range"] = E->get()[i].range.to_json(); +				text_edit["newText"] = E->get()[i].newText; +				edits.push_back(text_edit); +			} +			out_changes[E->key()] = edits; +		} +		dict["changes"] = out_changes; + +		return dict; +	} + +	_FORCE_INLINE_ void add_change(const String &uri, const int &line, const int &start_character, const int &end_character, const String &new_text) { +		if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) { +			Vector<TextEdit> edit_list = E->value(); +			for (int i = 0; i < edit_list.size(); ++i) { +				TextEdit edit = edit_list[i]; +				if (edit.range.start.character == start_character) { +					return; +				} +			} +		} + +		TextEdit new_edit; +		new_edit.newText = new_text; +		new_edit.range.start.line = line; +		new_edit.range.start.character = start_character; +		new_edit.range.end.line = line; +		new_edit.range.end.character = end_character; + +		if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) { +			E->value().push_back(new_edit); +		} else { +			Vector<TextEdit> edit_list; +			edit_list.push_back(new_edit); +			changes.insert(uri, edit_list); +		} +	} +}; + +/**   * Represents a reference to a command.   * Provides a title which will be used to represent a command in the UI.   * Commands are identified by a string identifier. @@ -486,7 +542,7 @@ struct TextDocumentSyncOptions {  	 * If present save notifications are sent to the server. If omitted the notification should not be  	 * sent.  	 */ -	bool save = false; +	SaveOptions save;  	Dictionary to_json() {  		Dictionary dict; @@ -494,7 +550,7 @@ struct TextDocumentSyncOptions {  		dict["willSave"] = willSave;  		dict["openClose"] = openClose;  		dict["change"] = change; -		dict["save"] = save; +		dict["save"] = save.to_json();  		return dict;  	}  }; @@ -766,7 +822,7 @@ struct MarkupContent {  // Use namespace instead of enumeration to follow the LSP specifications  // lsp::EnumName::EnumValue is OK but lsp::EnumValue is not -// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc. +// And here C++ compilers are unhappy with our enumeration name like Color, File, RefCounted etc.  /**   * The kind of a completion entry.   */ @@ -788,7 +844,7 @@ static const int Keyword = 14;  static const int Snippet = 15;  static const int Color = 16;  static const int File = 17; -static const int Reference = 18; +static const int RefCounted = 18;  static const int Folder = 19;  static const int EnumMember = 20;  static const int Constant = 21; @@ -1018,32 +1074,32 @@ struct CompletionList {   * A symbol kind.   */  namespace SymbolKind { -static const int File = 0; -static const int Module = 1; -static const int Namespace = 2; -static const int Package = 3; -static const int Class = 4; -static const int Method = 5; -static const int Property = 6; -static const int Field = 7; -static const int Constructor = 8; -static const int Enum = 9; -static const int Interface = 10; -static const int Function = 11; -static const int Variable = 12; -static const int Constant = 13; -static const int String = 14; -static const int Number = 15; -static const int Boolean = 16; -static const int Array = 17; -static const int Object = 18; -static const int Key = 19; -static const int Null = 20; -static const int EnumMember = 21; -static const int Struct = 22; -static const int Event = 23; -static const int Operator = 24; -static const int TypeParameter = 25; +static const int File = 1; +static const int Module = 2; +static const int Namespace = 3; +static const int Package = 4; +static const int Class = 5; +static const int Method = 6; +static const int Property = 7; +static const int Field = 8; +static const int Constructor = 9; +static const int Enum = 10; +static const int Interface = 11; +static const int Function = 12; +static const int Variable = 13; +static const int Constant = 14; +static const int String = 15; +static const int Number = 16; +static const int Boolean = 17; +static const int Array = 18; +static const int Object = 19; +static const int Key = 20; +static const int Null = 21; +static const int EnumMember = 22; +static const int Struct = 23; +static const int Event = 24; +static const int Operator = 25; +static const int TypeParameter = 26;  }; // namespace SymbolKind  /** @@ -1528,6 +1584,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 @@ -1590,6 +1754,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  	 * `textDocument.codeAction.codeActionLiteralSupport`. @@ -1676,6 +1845,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; |