diff options
Diffstat (limited to 'modules/gdscript/language_server')
11 files changed, 1074 insertions, 542 deletions
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 330530be80..49f5303ae6 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,23 +31,25 @@ #include "gdscript_extend_parser.h" #include "../gdscript.h" -#include "core/io/json.h" +#include "../gdscript_analyzer.h" #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" void ExtendGDScriptParser::update_diagnostics() { diagnostics.clear(); - if (has_error()) { + const List<ParserError> &errors = get_errors(); + for (const ParserError &error : errors) { lsp::Diagnostic diagnostic; diagnostic.severity = lsp::DiagnosticSeverity::Error; - diagnostic.message = get_error(); + diagnostic.message = error.message; diagnostic.source = "gdscript"; diagnostic.code = -1; lsp::Range range; lsp::Position pos; - int line = LINE_NUMBER_TO_INDEX(get_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; @@ -58,16 +60,15 @@ 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_message(); + diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message(); diagnostic.source = "gdscript"; diagnostic.code = warning.code; lsp::Range range; lsp::Position pos; - int line = LINE_NUMBER_TO_INDEX(warning.line); + int line = LINE_NUMBER_TO_INDEX(warning.start_line); const String &line_text = get_lines()[line]; pos.line = line; pos.character = line_text.length() - line_text.strip_edges(true, false).length(); @@ -82,7 +83,7 @@ void ExtendGDScriptParser::update_diagnostics() { void ExtendGDScriptParser::update_symbols() { members.clear(); - const GDScriptParser::Node *head = get_parse_tree(); + const GDScriptParser::Node *head = get_tree(); if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { parse_class_symbol(gdclass, class_symbol); @@ -106,15 +107,15 @@ void ExtendGDScriptParser::update_symbols() { void ExtendGDScriptParser::update_document_links(const String &p_code) { document_links.clear(); - GDScriptTokenizerText tokenizer; + GDScriptTokenizer tokenizer; FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES); - tokenizer.set_code(p_code); + tokenizer.set_source_code(p_code); while (true) { - GDScriptTokenizerText::Token token = tokenizer.get_token(); - if (token == GDScriptTokenizer::TK_EOF || token == GDScriptTokenizer::TK_ERROR) { + GDScriptTokenizer::Token token = tokenizer.scan(); + if (token.type == GDScriptTokenizer::Token::TK_EOF) { break; - } else if (token == GDScriptTokenizer::TK_CONSTANT) { - const Variant &const_val = tokenizer.get_token_constant(); + } else if (token.type == GDScriptTokenizer::Token::LITERAL) { + const Variant &const_val = token.literal; if (const_val.get_type() == Variant::STRING) { String path = const_val; bool exists = fs->file_exists(path); @@ -126,15 +127,14 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { String value = const_val; lsp::DocumentLink link; link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path); - link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line()); - link.range.end.line = link.range.start.line; - link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column()); - link.range.start.character = link.range.end.character - value.length(); + link.range.start.line = LINE_NUMBER_TO_INDEX(token.start_line); + link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line); + link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column); + link.range.end.character = LINE_NUMBER_TO_INDEX(token.end_column); document_links.push_back(link); } } } - tokenizer.advance(); } } @@ -144,220 +144,287 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.uri = uri; r_symbol.script_path = path; r_symbol.children.clear(); - r_symbol.name = p_class->name; - if (r_symbol.name.empty()) { + r_symbol.name = p_class->identifier != nullptr ? String(p_class->identifier->name) : String(); + if (r_symbol.name.is_empty()) { r_symbol.name = path.get_file(); } r_symbol.kind = lsp::SymbolKind::Class; r_symbol.deprecated = false; - r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line); - r_symbol.range.start.character = p_class->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; - r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); - - for (int i = 0; i < p_class->variables.size(); ++i) { - const GDScriptParser::ClassNode::Member &m = p_class->variables[i]; - - lsp::DocumentSymbol symbol; - symbol.name = m.identifier; - symbol.kind = lsp::SymbolKind::Variable; - symbol.deprecated = false; - const int line = LINE_NUMBER_TO_INDEX(m.line); - symbol.range.start.line = line; - symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length(); - symbol.range.end.line = line; - symbol.range.end.character = lines[line].length(); - symbol.selectionRange.start.line = symbol.range.start.line; - if (m._export.type != Variant::NIL) { - symbol.detail += "export "; - } - symbol.detail += "var " + m.identifier; - if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) { - symbol.detail += ": " + m.data_type.to_string(); - } - if (m.default_value.get_type() != Variant::NIL) { - symbol.detail += " = " + JSON::print(m.default_value); - } - - symbol.documentation = parse_documentation(line); - symbol.uri = uri; - symbol.script_path = path; - - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->_signals.size(); ++i) { - const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i]; - - lsp::DocumentSymbol symbol; - symbol.name = signal.name; - symbol.kind = lsp::SymbolKind::Event; - symbol.deprecated = false; - const int line = LINE_NUMBER_TO_INDEX(signal.line); - symbol.range.start.line = line; - symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length(); - symbol.range.end.line = symbol.range.start.line; - symbol.range.end.character = lines[line].length(); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(line); - symbol.uri = uri; - symbol.script_path = path; - symbol.detail = "signal " + signal.name + "("; - for (int j = 0; j < signal.arguments.size(); j++) { - if (j > 0) { - symbol.detail += ", "; - } - symbol.detail += signal.arguments[j]; - } - symbol.detail += ")"; - - r_symbol.children.push_back(symbol); - } + r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->start_line), is_root_class); + + for (int i = 0; i < p_class->members.size(); i++) { + const ClassNode::Member &m = p_class->members[i]; + + switch (m.type) { + case ClassNode::Member::VARIABLE: { + lsp::DocumentSymbol symbol; + symbol.name = m.variable->identifier->name; + symbol.kind = m.variable->property == VariableNode::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); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.variable->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.variable->end_column); + symbol.selectionRange.start.line = symbol.range.start.line; + if (m.variable->exported) { + symbol.detail += "@export "; + } + symbol.detail += "var " + m.variable->identifier->name; + if (m.get_datatype().is_hard_type()) { + symbol.detail += ": " + m.get_datatype().to_string(); + } + if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) { + symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string(); + } - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { - lsp::DocumentSymbol symbol; - const GDScriptParser::ClassNode::Constant &c = E->value(); - const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); - ERR_FAIL_COND(!node); - symbol.name = E->key(); - symbol.kind = lsp::SymbolKind::Constant; - symbol.deprecated = false; - const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line); - symbol.range.start.line = line; - symbol.range.start.character = E->get().expression->column; - symbol.range.end.line = symbol.range.start.line; - symbol.range.end.character = lines[line].length(); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(line); - symbol.uri = uri; - symbol.script_path = path; + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); + symbol.uri = uri; + symbol.script_path = path; + + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::CONSTANT: { + lsp::DocumentSymbol symbol; + + symbol.name = m.constant->identifier->name; + symbol.kind = lsp::SymbolKind::Constant; + symbol.deprecated = false; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.constant->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); + symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.constant->start_line)); + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = "const " + symbol.name; + if (m.constant->get_datatype().is_hard_type()) { + symbol.detail += ": " + m.constant->get_datatype().to_string(); + } - symbol.detail = "const " + symbol.name; - if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) { - symbol.detail += ": " + c.type.to_string(); - } + const Variant &default_value = m.constant->initializer->reduced_value; + String value_text; + if (default_value.get_type() == Variant::OBJECT) { + RES res = default_value; + if (res.is_valid() && !res->get_path().is_empty()) { + value_text = "preload(\"" + res->get_path() + "\")"; + if (symbol.documentation.is_empty()) { + if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { + symbol.documentation = S->get()->class_symbol.documentation; + } + } + } else { + value_text = default_value.to_json_string(); + } + } else { + value_text = default_value.to_json_string(); + } + if (!value_text.is_empty()) { + symbol.detail += " = " + value_text; + } - String value_text; - if (node->value.get_type() == Variant::OBJECT) { - RES res = node->value; - if (res.is_valid() && !res->get_path().empty()) { - value_text = "preload(\"" + res->get_path() + "\")"; - if (symbol.documentation.empty()) { - if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) { - symbol.documentation = S->get()->class_symbol.documentation; + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::ENUM_VALUE: { + lsp::DocumentSymbol symbol; + + symbol.name = m.enum_value.identifier->name; + symbol.kind = lsp::SymbolKind::EnumMember; + symbol.deprecated = false; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.enum_value.leftmost_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.enum_value.rightmost_column); + symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.enum_value.line)); + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = symbol.name + " = " + itos(m.enum_value.value); + + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::SIGNAL: { + lsp::DocumentSymbol symbol; + symbol.name = m.signal->identifier->name; + symbol.kind = lsp::SymbolKind::Event; + symbol.deprecated = false; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.signal->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.signal->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.signal->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.signal->end_column); + symbol.selectionRange.start.line = symbol.range.start.line; + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.signal->start_line)); + symbol.uri = uri; + symbol.script_path = path; + symbol.detail = "signal " + String(m.signal->identifier->name) + "("; + for (int j = 0; j < m.signal->parameters.size(); j++) { + if (j > 0) { + symbol.detail += ", "; } + symbol.detail += m.signal->parameters[j]->identifier->name; } - } else { - value_text = JSON::print(node->value); - } - } else { - value_text = JSON::print(node->value); - } - if (!value_text.empty()) { - symbol.detail += " = " + value_text; + symbol.detail += ")"; + + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::ENUM: { + lsp::DocumentSymbol symbol; + symbol.kind = lsp::SymbolKind::Enum; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.m_enum->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.m_enum->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.m_enum->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.m_enum->end_column); + symbol.selectionRange.start.line = symbol.range.start.line; + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.m_enum->start_line)); + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{"; + for (int j = 0; j < m.m_enum->values.size(); j++) { + if (j > 0) { + symbol.detail += ", "; + } + symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value); + } + symbol.detail += "}"; + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::FUNCTION: { + lsp::DocumentSymbol symbol; + parse_function_symbol(m.function, symbol); + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::CLASS: { + lsp::DocumentSymbol symbol; + parse_class_symbol(m.m_class, symbol); + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::UNDEFINED: + break; // Unreachable. } - - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->functions.size(); ++i) { - const GDScriptParser::FunctionNode *func = p_class->functions[i]; - lsp::DocumentSymbol symbol; - parse_function_symbol(func, symbol); - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->static_functions.size(); ++i) { - const GDScriptParser::FunctionNode *func = p_class->static_functions[i]; - lsp::DocumentSymbol symbol; - parse_function_symbol(func, symbol); - r_symbol.children.push_back(symbol); - } - - for (int i = 0; i < p_class->subclasses.size(); ++i) { - const GDScriptParser::ClassNode *subclass = p_class->subclasses[i]; - lsp::DocumentSymbol symbol; - parse_class_symbol(subclass, symbol); - r_symbol.children.push_back(symbol); } } void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) { const String uri = get_uri(); - r_symbol.name = p_func->name; - r_symbol.kind = lsp::SymbolKind::Function; - r_symbol.detail = "func " + p_func->name + "("; + r_symbol.name = p_func->identifier->name; + 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; - const int line = LINE_NUMBER_TO_INDEX(p_func->line); - r_symbol.range.start.line = line; - r_symbol.range.start.character = p_func->column; - r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line); - r_symbol.range.end.character = lines[r_symbol.range.end.line].length(); + r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line); + r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_func->start_column); + r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_func->start_line); + r_symbol.range.end.character = LINE_NUMBER_TO_INDEX(p_func->end_column); r_symbol.selectionRange.start.line = r_symbol.range.start.line; - r_symbol.documentation = parse_documentation(line); + r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_func->start_line)); r_symbol.uri = uri; r_symbol.script_path = path; - String arguments; - for (int i = 0; i < p_func->arguments.size(); i++) { + String parameters; + for (int i = 0; i < p_func->parameters.size(); i++) { + const ParameterNode *parameter = p_func->parameters[i]; lsp::DocumentSymbol symbol; symbol.kind = lsp::SymbolKind::Variable; - symbol.name = p_func->arguments[i]; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line); - symbol.range.start.character = p_func->body->column; - symbol.range.end = symbol.range.start; + symbol.name = parameter->identifier->name; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column); symbol.uri = uri; symbol.script_path = path; r_symbol.children.push_back(symbol); if (i > 0) { - arguments += ", "; + parameters += ", "; } - arguments += String(p_func->arguments[i]); - if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) { - arguments += ": " + p_func->argument_types[i].to_string(); + parameters += String(parameter->identifier->name); + if (parameter->get_datatype().is_hard_type()) { + parameters += ": " + parameter->get_datatype().to_string(); } - int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size()); - if (default_value_idx >= 0) { - const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]); - if (const_node == nullptr) { - const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]); - if (operator_node) { - const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next); - } - } - - if (const_node) { - String value = JSON::print(const_node->value); - arguments += " = " + value; - } + if (parameter->default_value != nullptr) { + parameters += " = " + parameter->default_value->reduced_value.to_json_string(); } } - r_symbol.detail += arguments + ")"; - if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) { - r_symbol.detail += " -> " + p_func->return_type.to_string(); + r_symbol.detail += parameters + ")"; + if (p_func->get_datatype().is_hard_type()) { + r_symbol.detail += " -> " + p_func->get_datatype().to_string(); } - for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) { - lsp::DocumentSymbol symbol; - const GDScriptParser::LocalVarNode *var = E->value(); - symbol.name = E->key(); - symbol.kind = lsp::SymbolKind::Variable; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line); - symbol.range.start.character = E->get()->column; - symbol.range.end.line = symbol.range.start.line; - symbol.range.end.character = lines[symbol.range.end.line].length(); - symbol.uri = uri; - symbol.script_path = path; - symbol.detail = "var " + symbol.name; - if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) { - symbol.detail += ": " + var->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); - r_symbol.children.push_back(symbol); } } @@ -398,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; } @@ -424,7 +491,7 @@ String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_curs return longthing; } -String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const { +String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_required) const { String longthing; int len = lines.size(); for (int i = 0; i < len; i++) { @@ -432,7 +499,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c String line = lines[i]; String first_part = line.substr(0, p_cursor.character); String last_part = line.substr(p_cursor.character + 1, lines[i].length()); - if (!p_symbol.empty()) { + if (!p_symbol.is_empty()) { String left_cursor_text; for (int c = p_cursor.character - 1; c >= 0; c--) { left_cursor_text = line.substr(c, p_cursor.character - c); @@ -446,7 +513,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c longthing += first_part; longthing += String::chr(0xFFFF); //not unicode, represents the cursor - if (p_func_requred) { + if (p_func_required) { longthing += "("; // tell the parser this is a function call } longthing += last_part; @@ -465,12 +532,15 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); String line = lines[p_position.line]; + if (line.is_empty()) { + return ""; + } ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); int start_pos = p_position.character; for (int c = p_position.character; c >= 0; c--) { start_pos = c; - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -479,7 +549,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -531,7 +601,7 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi } while (c >= 0) { - const CharType &character = line[c]; + const char32_t &character = line[c]; if (character == ')') { ++bracket_stack; } else if (character == '(') { @@ -568,7 +638,7 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int } const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const { - if (p_subclass.empty()) { + if (p_subclass.is_empty()) { const lsp::DocumentSymbol *const *ptr = members.getptr(p_name); if (ptr) { return *ptr; @@ -590,7 +660,7 @@ const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const } const Array &ExtendGDScriptParser::get_member_completions() { - if (member_completions.empty()) { + if (member_completions.is_empty()) { const String *name = members.next(nullptr); while (name) { const lsp::DocumentSymbol *symbol = members.get(*name); @@ -624,34 +694,26 @@ const Array &ExtendGDScriptParser::get_member_completions() { Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const { Dictionary func; ERR_FAIL_NULL_V(p_func, func); - func["name"] = p_func->name; - func["return_type"] = p_func->return_type.to_string(); - func["rpc_mode"] = p_func->rpc_mode; - Array arguments; - for (int i = 0; i < p_func->arguments.size(); i++) { + func["name"] = p_func->identifier->name; + func["return_type"] = p_func->get_datatype().to_string(); + 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; - arg["name"] = p_func->arguments[i]; - arg["type"] = p_func->argument_types[i].to_string(); - int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size()); - if (default_value_idx >= 0) { - const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]); - if (const_node == nullptr) { - const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]); - if (operator_node) { - const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next); - } - } - if (const_node) { - arg["default_value"] = const_node->value; - } + arg["name"] = p_func->parameters[i]->identifier->name; + arg["type"] = p_func->parameters[i]->get_datatype().to_string(); + if (p_func->parameters[i]->default_value != nullptr) { + arg["default_value"] = p_func->parameters[i]->default_value->reduced_value; } - arguments.push_back(arg); + parameters.push_back(arg); } - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) { + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->start_line))) { func["signature"] = symbol->detail; func["description"] = symbol->documentation; } - func["arguments"] = arguments; + func["arguments"] = parameters; return func; } @@ -660,91 +722,117 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode ERR_FAIL_NULL_V(p_class, class_api); - class_api["name"] = String(p_class->name); + class_api["name"] = p_class->identifier != nullptr ? String(p_class->identifier->name) : String(); class_api["path"] = path; Array extends_class; - for (int i = 0; i < p_class->extends_class.size(); i++) { - extends_class.append(String(p_class->extends_class[i])); + for (int i = 0; i < p_class->extends.size(); i++) { + extends_class.append(String(p_class->extends[i])); } class_api["extends_class"] = extends_class; - class_api["extends_file"] = String(p_class->extends_file); + class_api["extends_file"] = String(p_class->extends_path); class_api["icon"] = String(p_class->icon_path); - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) { + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->start_line))) { class_api["signature"] = symbol->detail; class_api["description"] = symbol->documentation; } - Array subclasses; - for (int i = 0; i < p_class->subclasses.size(); i++) { - subclasses.push_back(dump_class_api(p_class->subclasses[i])); - } - class_api["sub_classes"] = subclasses; - + Array nested_classes; Array constants; - for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { - const GDScriptParser::ClassNode::Constant &c = E->value(); - const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression); - ERR_FAIL_COND_V(!node, class_api); - - Dictionary api; - api["name"] = E->key(); - api["value"] = node->value; - api["data_type"] = node->datatype.to_string(); - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) { - api["signature"] = symbol->detail; - api["description"] = symbol->documentation; - } - constants.push_back(api); - } - class_api["constants"] = constants; - Array members; - for (int i = 0; i < p_class->variables.size(); ++i) { - const GDScriptParser::ClassNode::Member &m = p_class->variables[i]; - Dictionary api; - api["name"] = m.identifier; - api["data_type"] = m.data_type.to_string(); - api["default_value"] = m.default_value; - api["setter"] = String(m.setter); - api["getter"] = String(m.getter); - api["export"] = m._export.type != Variant::NIL; - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) { - api["signature"] = symbol->detail; - api["description"] = symbol->documentation; - } - members.push_back(api); - } - class_api["members"] = members; - Array signals; - for (int i = 0; i < p_class->_signals.size(); ++i) { - const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i]; - Dictionary api; - api["name"] = signal.name; - Array args; - for (int j = 0; j < signal.arguments.size(); j++) { - args.append(signal.arguments[j]); - } - api["arguments"] = args; - if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) { - api["signature"] = symbol->detail; - api["description"] = symbol->documentation; + Array methods; + Array static_functions; + + for (int i = 0; i < p_class->members.size(); i++) { + const ClassNode::Member &m = p_class->members[i]; + switch (m.type) { + case ClassNode::Member::CLASS: + nested_classes.push_back(dump_class_api(m.m_class)); + break; + case ClassNode::Member::CONSTANT: { + Dictionary api; + api["name"] = m.constant->identifier->name; + api["value"] = m.constant->initializer->reduced_value; + api["data_type"] = m.constant->get_datatype().to_string(); + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.constant->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + constants.push_back(api); + } break; + case ClassNode::Member::ENUM_VALUE: { + Dictionary api; + api["name"] = m.enum_value.identifier->name; + api["value"] = m.enum_value.value; + api["data_type"] = m.get_datatype().to_string(); + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.enum_value.line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + constants.push_back(api); + } break; + case ClassNode::Member::ENUM: { + Dictionary enum_dict; + for (int j = 0; j < m.m_enum->values.size(); j++) { + enum_dict[m.m_enum->values[j].identifier->name] = m.m_enum->values[j].value; + } + + Dictionary api; + api["name"] = m.m_enum->identifier->name; + api["value"] = enum_dict; + api["data_type"] = m.get_datatype().to_string(); + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.m_enum->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + constants.push_back(api); + } break; + case ClassNode::Member::VARIABLE: { + Dictionary api; + api["name"] = m.variable->identifier->name; + api["data_type"] = m.variable->get_datatype().to_string(); + api["default_value"] = m.variable->initializer != nullptr ? m.variable->initializer->reduced_value : Variant(); + api["setter"] = m.variable->setter ? ("@" + String(m.variable->identifier->name) + "_setter") : (m.variable->setter_pointer != nullptr ? String(m.variable->setter_pointer->name) : String()); + api["getter"] = m.variable->getter ? ("@" + String(m.variable->identifier->name) + "_getter") : (m.variable->getter_pointer != nullptr ? String(m.variable->getter_pointer->name) : String()); + api["export"] = m.variable->exported; + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.variable->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + members.push_back(api); + } break; + case ClassNode::Member::SIGNAL: { + Dictionary api; + api["name"] = m.signal->identifier->name; + Array pars; + for (int j = 0; j < m.signal->parameters.size(); j++) { + pars.append(String(m.signal->parameters[i]->identifier->name)); + } + api["arguments"] = pars; + if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) { + api["signature"] = symbol->detail; + api["description"] = symbol->documentation; + } + signals.push_back(api); + } break; + case ClassNode::Member::FUNCTION: { + if (m.function->is_static) { + static_functions.append(dump_function_api(m.function)); + } else { + methods.append(dump_function_api(m.function)); + } + } break; + case ClassNode::Member::UNDEFINED: + break; // Unreachable. } - signals.push_back(api); } - class_api["signals"] = signals; - Array methods; - for (int i = 0; i < p_class->functions.size(); ++i) { - methods.append(dump_function_api(p_class->functions[i])); - } + class_api["sub_classes"] = nested_classes; + class_api["constants"] = constants; + class_api["members"] = members; + class_api["signals"] = signals; class_api["methods"] = methods; - - Array static_functions; - for (int i = 0; i < p_class->static_functions.size(); ++i) { - static_functions.append(dump_function_api(p_class->static_functions[i])); - } class_api["static_functions"] = static_functions; return class_api; @@ -752,7 +840,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode Dictionary ExtendGDScriptParser::generate_api() const { Dictionary api; - const GDScriptParser::Node *head = get_parse_tree(); + const GDScriptParser::Node *head = get_tree(); if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) { api = dump_class_api(gdclass); } @@ -763,7 +851,11 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { path = p_path; lines = p_code.split("\n"); - Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, nullptr, false); + Error err = GDScriptParser::parse(p_code, p_path, false); + if (err == OK) { + GDScriptAnalyzer analyzer(this); + err = analyzer.analyze(); + } update_diagnostics(); update_symbols(); update_document_links(p_code); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 0c031d7883..99b0bf45d0 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,7 +32,7 @@ #define GDSCRIPT_EXTEND_PARSER_H #include "../gdscript_parser.h" -#include "core/variant.h" +#include "core/variant/variant.h" #include "lsp.hpp" #ifndef LINE_NUMBER_TO_INDEX @@ -85,7 +85,7 @@ public: Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const; String get_text_for_completion(const lsp::Position &p_cursor) const; - String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const; + String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const; String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const; String get_uri() const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 8a805196a1..cdddab319d 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,9 +30,8 @@ #include "gdscript_language_protocol.h" -#include "core/io/json.h" -#include "core/os/copymem.h" -#include "core/project_settings.h" +#include "core/config/project_settings.h" +#include "editor/doc_tools.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -95,7 +94,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { // Response String output = GDScriptLanguageProtocol::get_singleton()->process_message(msg); - if (!output.empty()) { + if (!output.is_empty()) { res_queue.push_back(output.utf8()); } } @@ -104,7 +103,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { Error GDScriptLanguageProtocol::LSPeer::send_data() { int sent = 0; - if (!res_queue.empty()) { + if (!res_queue.is_empty()) { CharString c_res = res_queue[0]; if (res_sent < c_res.size()) { Error err = connection->put_partial_data((const uint8_t *)c_res.get_data() + res_sent, c_res.size() - res_sent - 1, sent); @@ -116,7 +115,7 @@ Error GDScriptLanguageProtocol::LSPeer::send_data() { // Response sent if (res_sent >= c_res.size() - 1) { res_sent = 0; - res_queue.remove(0); + res_queue.remove_at(0); } } return OK; @@ -129,18 +128,18 @@ 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) { String ret = process_string(p_text); - if (ret.empty()) { + if (ret.is_empty()) { return ret; } else { return format_output(ret); @@ -162,7 +161,7 @@ void GDScriptLanguageProtocol::_bind_methods() { ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized); ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected); ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected); - ClassDB::bind_method(D_METHOD("notify_client", "method", "params"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("notify_client", "method", "params", "client_id"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled); ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document); ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace); @@ -190,13 +189,11 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { params["path"] = workspace->root; Dictionary request = make_notification("gdscript_client/changeWorkspace", params); - ERR_FAIL_COND_V_MSG(latest_client_id == -1, ret.to_json(), - "GDScriptLanguageProtocol: Can't initialize as no client is connected."); - ERR_FAIL_INDEX_V_MSG((uint64_t)latest_client_id, clients.size(), ret.to_json(), + ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(), 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()); } @@ -214,12 +211,12 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { void GDScriptLanguageProtocol::initialized(const Variant &p_params) { lsp::GodotCapabilities capabilities; - DocData *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { + DocTools *doc = EditorHelp::get_doc_data(); + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { lsp::GodotNativeClassInfo gdclass; - gdclass.name = E->get().name; - gdclass.class_doc = &(E->get()); - if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E->get().name))) { + gdclass.name = E.value.name; + gdclass.class_doc = &(E.value); + if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E.value.name))) { gdclass.class_info = ptr; } capabilities.native_classes.push_back(gdclass); @@ -257,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); } @@ -277,12 +274,29 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia "GDScript LSP: Can't notify client as none was connected."); p_client_id = latest_client_id; } - ERR_FAIL_INDEX((uint64_t)p_client_id, clients.size()); + ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); 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()); +} + +void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) { + if (p_client_id == -1) { + ERR_FAIL_COND_MSG(latest_client_id == -1, + "GDScript LSP: Can't notify client as none was connected."); + p_client_id = latest_client_id; + } + ERR_FAIL_COND(!clients.has(p_client_id)); + Ref<LSPeer> peer = clients.get(p_client_id); + ERR_FAIL_COND(peer == nullptr); + + Dictionary message = make_request(p_method, p_params, next_server_id); + next_server_id++; + String msg = Variant(message).to_json_string(); msg = format_output(msg); peer->res_queue.push_back(msg.utf8()); } @@ -296,10 +310,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 564878313d..0fed8597f9 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -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" // For jsonrpc. +#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,10 +75,12 @@ private: static GDScriptLanguageProtocol *singleton; HashMap<int, Ref<LSPeer>> clients; - Ref<TCP_Server> server; - int latest_client_id = -1; + Ref<TCPServer> server; + int latest_client_id = 0; int next_client_id = 0; + int next_server_id = 0; + Ref<GDScriptTextDocument> text_document; Ref<GDScriptWorkspace> workspace; @@ -97,10 +105,11 @@ 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); + void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); bool is_smart_resolve_enabled() const; bool is_goto_native_symbols_enabled() const; @@ -108,4 +117,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 3387d262f8..33c1c834f1 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -30,18 +30,13 @@ #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" GDScriptLanguageServer::GDScriptLanguageServer() { - thread = nullptr; - thread_running = false; - started = false; - - use_thread = false; - port = 6008; + _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); _EDITOR_DEF("network/language_server/enable_smart_resolve", true); _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false); @@ -62,9 +57,10 @@ void GDScriptLanguageServer::_notification(int p_what) { } } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + String host = String(_EDITOR_GET("network/language_server/remote_host")); int port = (int)_EDITOR_GET("network/language_server/remote_port"); bool use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (port != this->port || use_thread != this->use_thread) { + if (host != this->host || port != this->port || use_thread != this->use_thread) { this->stop(); this->start(); } @@ -82,14 +78,14 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { } void GDScriptLanguageServer::start() { + host = String(_EDITOR_GET("network/language_server/remote_host")); 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(host)) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { - ERR_FAIL_COND(thread != nullptr); thread_running = true; - thread = Thread::create(GDScriptLanguageServer::thread_main, this); + thread.start(GDScriptLanguageServer::thread_main, this); } set_process_internal(!use_thread); started = true; @@ -98,11 +94,9 @@ void GDScriptLanguageServer::start() { void GDScriptLanguageServer::stop() { if (use_thread) { - ERR_FAIL_COND(nullptr == thread); + ERR_FAIL_COND(!thread.is_started()); thread_running = false; - Thread::wait_to_finish(thread); - memdelete(thread); - thread = nullptr; + thread.wait_to_finish(); } protocol.stop(); started = false; @@ -110,7 +104,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_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 228d29bf42..8de72fc9c9 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -40,19 +40,18 @@ class GDScriptLanguageServer : public EditorPlugin { GDScriptLanguageProtocol protocol; - Thread *thread; - bool thread_running; - bool started; - bool use_thread; - int port; + Thread thread; + bool thread_running = false; + bool started = false; + bool use_thread = false; + String host = "127.0.0.1"; + int port = 6005; static void thread_main(void *p_userdata); private: void _notification(int p_what); - void _iteration(); public: - Error parse_script_file(const String &p_path); GDScriptLanguageServer(); void start(); void stop(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index c6fe3169dc..961295b076 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -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; @@ -147,12 +169,11 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { List<ScriptCodeCompletionOption> options; GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options); - if (!options.empty()) { + if (!options.is_empty()) { 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; @@ -196,8 +217,8 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { arr = native_member_completions.duplicate(); - for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) { - ExtendGDScriptParser *script = E->get(); + for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) { + ExtendGDScriptParser *script = E.value; const Array &items = script->get_member_completions(); const int start_size = arr.size(); @@ -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); @@ -257,13 +286,13 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { if ((item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) && !item.label.ends_with("):")) { item.insertText = item.label + "("; - if (symbol && symbol->children.empty()) { + if (symbol && symbol->children.is_empty()) { item.insertText += ")"; } } 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); } } @@ -341,7 +370,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) { params.load(p_params); List<const lsp::DocumentSymbol *> symbols; Array arr = this->find_symbols(params, symbols); - if (arr.empty() && !symbols.empty() && !symbols.front()->get()->native_class.empty()) { // Find a native symbol + if (arr.is_empty() && !symbols.is_empty() && !symbols.front()->get()->native_class.is_empty()) { // Find a native symbol const lsp::DocumentSymbol *symbol = symbols.front()->get(); if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { String id; @@ -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,9 +461,9 @@ 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()) { - if (!s->uri.empty()) { + for (const lsp::DocumentSymbol *&E : list) { + if (const lsp::DocumentSymbol *s = E) { + if (!s->uri.is_empty()) { lsp::Location location; location.uri = s->uri; location.range = s->range; diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index b2fd0c31f9..eb7b2c0240 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,19 +31,21 @@ #ifndef GDSCRIPT_TEXT_DOCUMENT_H #define GDSCRIPT_TEXT_DOCUMENT_H -#include "core/os/file_access.h" -#include "core/reference.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 a203b9bfdb..a944844226 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,8 +32,9 @@ #include "../gdscript.h" #include "../gdscript_parser.h" -#include "core/project_settings.h" -#include "core/script_language.h" +#include "core/config/project_settings.h" +#include "core/object/script_language.h" +#include "editor/doc_tools.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_node.h" @@ -41,6 +42,8 @@ #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { + ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal); + 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); @@ -50,6 +53,68 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } +void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) { + Ref<Script> script = obj->get_script(); + + if (script->get_language()->get_name() != "GDScript") { + return; + } + + String function_signature = "func " + function; + String source = script->get_source_code(); + + if (source.find(function_signature) != -1) { + return; + } + + int first_class = source.find("\nclass "); + int start_line = 0; + if (first_class != -1) { + start_line = source.substr(0, first_class).split("\n").size(); + } else { + start_line = source.split("\n").size(); + } + + String function_body = "\n\n" + function_signature + "("; + for (int i = 0; i < args.size(); ++i) { + function_body += args[i]; + if (i < args.size() - 1) { + function_body += ", "; + } + } + function_body += ")"; + if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) { + function_body += " -> void"; + } + function_body += ":\n\tpass # Replace with function body.\n"; + + lsp::TextEdit text_edit; + + if (first_class != -1) { + function_body += "\n\n"; + } + text_edit.range.end.line = text_edit.range.start.line = start_line; + + text_edit.newText = function_body; + + String uri = get_file_uri(script->get_path()); + + lsp::ApplyWorkspaceEditParams params; + params.edit.add_edit(uri, text_edit); + + GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json()); +} + +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); @@ -79,7 +144,7 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_ if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) { const lsp::DocumentSymbol &class_symbol = E->value(); - if (p_member.empty()) { + if (p_member.is_empty()) { return &class_symbol; } else { for (int i = 0; i < class_symbol.children.size(); i++) { @@ -104,11 +169,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); @@ -118,7 +212,7 @@ void GDScriptWorkspace::reload_all_workspace_scripts() { Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path); String err_msg = "Failed parse script " + path; if (S) { - err_msg += "\n" + S->get()->get_error(); + err_msg += "\n" + S->get()->get_errors()[0].message; } ERR_CONTINUE_MSG(err != OK, err_msg); } @@ -170,13 +264,15 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) Array GDScriptWorkspace::symbol(const Dictionary &p_params) { String query = p_params["query"]; Array arr; - if (!query.empty()) { - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + if (!query.is_empty()) { + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { Vector<lsp::DocumentedSymbolInformation> script_symbols; - E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); + E.value->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()); } } } @@ -189,16 +285,16 @@ Error GDScriptWorkspace::initialize() { return OK; } - DocData *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { - const DocData::ClassDoc &class_data = E->value(); + DocTools *doc = EditorHelp::get_doc_data(); + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { + const DocData::ClassDoc &class_data = E.value; lsp::DocumentSymbol class_symbol; - String class_name = E->key(); + String class_name = E.key; class_symbol.name = class_name; class_symbol.native_class = class_name; class_symbol.kind = lsp::SymbolKind::Class; class_symbol.detail = String("<Native> class ") + class_name; - if (!class_data.inherits.empty()) { + if (!class_data.inherits.is_empty()) { class_symbol.detail += " extends " + class_data.inherits; } class_symbol.documentation = class_data.brief_description + "\n" + class_data.description; @@ -218,18 +314,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 { @@ -239,8 +330,21 @@ 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.constructors); methods_signals.append_array(class_data.methods); + methods_signals.append_array(class_data.operators); const int signal_start_idx = methods_signals.size(); methods_signals.append_array(class_data.signals); @@ -262,7 +366,7 @@ Error GDScriptWorkspace::initialize() { symbol_arg.kind = lsp::SymbolKind::Variable; symbol_arg.detail = arg.type; - if (!arg_default_value_started && !arg.default_value.empty()) { + if (!arg_default_value_started && !arg.default_value.is_empty()) { arg_default_value_started = true; } String arg_str = arg.name + ": " + arg.type; @@ -277,11 +381,11 @@ Error GDScriptWorkspace::initialize() { symbol.children.push_back(symbol_arg); } if (data.qualifiers.find("vararg") != -1) { - params += params.empty() ? "..." : ", ..."; + params += params.is_empty() ? "..." : ", ..."; } String return_type = data.return_type; - if (return_type.empty()) { + if (return_type.is_empty()) { return_type = "void"; } symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type; @@ -295,22 +399,25 @@ Error GDScriptWorkspace::initialize() { reload_all_workspace_scripts(); if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) { + for (const KeyValue<StringName, lsp::DocumentSymbol> &E : native_symbols) { ClassMembers members; - const lsp::DocumentSymbol &class_symbol = E->get(); + const lsp::DocumentSymbol &class_symbol = E.value; for (int i = 0; i < class_symbol.children.size(); i++) { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; members.set(symbol.name, &symbol); } - native_members.set(E->key(), members); + native_members.set(E.key, members); } // cache member completions - for (Map<String, ExtendGDScriptParser *>::Element *S = scripts.front(); S; S = S->next()) { - S->get()->get_member_completions(); + for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) { + S.value->get_member_completions(); } } + EditorNode *editor_node = EditorNode::get_singleton(); + editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal)); + return OK; } @@ -337,6 +444,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); @@ -349,7 +500,7 @@ Error GDScriptWorkspace::parse_local_script(const String &p_path) { String GDScriptWorkspace::get_file_path(const String &p_uri) const { String path = p_uri; path = path.replace(root_uri + "/", "res://"); - path = path.http_unescape(); + path = path.uri_decode(); return path; } @@ -412,7 +563,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; } } @@ -427,15 +578,38 @@ 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); } } } -const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) { +const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) { const lsp::DocumentSymbol *symbol = nullptr; String path = get_file_path(p_doc_pos.textDocument.uri); @@ -447,39 +621,52 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } lsp::Position pos = p_doc_pos.position; - if (symbol_identifier.empty()) { + if (symbol_identifier.is_empty()) { Vector2i offset; symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); pos.character += offset.y; } - if (!symbol_identifier.empty()) { + if (!symbol_identifier.is_empty()) { if (ScriptServer::is_global_class(symbol_identifier)) { String class_path = ScriptServer::get_global_class_path(symbol_identifier); symbol = get_script_symbol(class_path); } else { ScriptLanguage::LookupResult ret; - if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, nullptr, ret)) { + if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" ", "").replace("\t", "").find("new(") > -1) { + symbol_identifier = "_init"; + } + if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) { if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) { 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 { String member = ret.class_member; - if (member.empty() && symbol_identifier != ret.class_name) { + if (member.is_empty() && symbol_identifier != ret.class_name) { member = symbol_identifier; } symbol = get_native_symbol(ret.class_name, member); } } else { symbol = parser->get_member_symbol(symbol_identifier); + + if (!symbol) { + symbol = get_local_symbol(parser, symbol_identifier); + } } } } @@ -504,8 +691,8 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP class_ptr = native_members.next(class_ptr); } - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { - const ExtendGDScriptParser *script = E->get(); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + const ExtendGDScriptParser *script = E.value; const ClassMembers &members = script->get_members(); if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { r_list.push_back(*symbol); @@ -528,7 +715,7 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) { if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) { const lsp::DocumentSymbol &symbol = E->get(); - if (p_params.symbol_name.empty() || p_params.symbol_name == symbol.name) { + if (p_params.symbol_name.is_empty() || p_params.symbol_name == symbol.name) { return &symbol; } @@ -545,8 +732,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); } } } @@ -573,8 +760,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; @@ -606,12 +792,12 @@ GDScriptWorkspace::GDScriptWorkspace() { GDScriptWorkspace::~GDScriptWorkspace() { Set<String> cached_parsers; - for (Map<String, ExtendGDScriptParser *>::Element *E = parse_results.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) { + cached_parsers.insert(E.key); } - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + cached_parsers.insert(E.key); } for (Set<String>::Element *E = cached_parsers.front(); E; E = E->next()) { diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index e45b06747d..ce5bba5f00 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -32,13 +32,13 @@ #define GDSCRIPT_WORKSPACE_H #include "../gdscript_parser.h" -#include "core/variant.h" +#include "core/variant/variant.h" #include "editor/editor_file_system.h" #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(); @@ -60,6 +62,8 @@ protected: void list_script_files(const String &p_root_dir, List<String> &r_files); + void apply_new_signal(Object *obj, String function, PackedStringArray args); + public: String root; String root_uri; @@ -83,12 +87,14 @@ public: void publish_diagnostics(const String &p_path); void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options); - const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); + const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_required = false); void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list); const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params); 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 cf27a1578c..8e503a9df9 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -31,9 +31,9 @@ #ifndef GODOT_LSP_H #define GODOT_LSP_H -#include "core/class_db.h" -#include "core/list.h" -#include "editor/doc_data.h" +#include "core/doc_data.h" +#include "core/object/class_db.h" +#include "core/templates/list.h" namespace lsp { @@ -255,6 +255,72 @@ struct TextEdit { }; /** + * The edits to be applied. + */ +struct WorkspaceEdit { + /** + * Holds changes to existing resources. + */ + Map<String, Vector<TextEdit>> changes; + + _FORCE_INLINE_ void add_edit(const String &uri, const TextEdit &edit) { + if (changes.has(uri)) { + changes[uri].push_back(edit); + } else { + Vector<TextEdit> edits; + edits.push_back(edit); + changes[uri] = edits; + } + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + + Dictionary out_changes; + for (const KeyValue<String, Vector<TextEdit>> &E : changes) { + Array edits; + for (int i = 0; i < E.value.size(); ++i) { + Dictionary text_edit; + text_edit["range"] = E.value[i].range.to_json(); + text_edit["newText"] = E.value[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. @@ -292,21 +358,21 @@ struct Command { namespace TextDocumentSyncKind { /** - * Documents should not be synced at all. - */ + * Documents should not be synced at all. + */ static const int None = 0; /** - * Documents are synced by always sending the full content - * of the document. - */ + * Documents are synced by always sending the full content + * of the document. + */ static const int Full = 1; /** - * Documents are synced by sending the full content on open. - * After that only incremental updates to the document are - * send. - */ + * Documents are synced by sending the full content on open. + * After that only incremental updates to the document are + * send. + */ static const int Incremental = 2; }; // namespace TextDocumentSyncKind @@ -486,7 +552,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 +560,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["save"] = save; + dict["save"] = save.to_json(); return dict; } }; @@ -547,7 +613,7 @@ struct TextDocumentItem { * The version number of this document (it will increase after each * change, including undo/redo). */ - int version; + int version = 0; /** * The content of the opened text document. @@ -584,7 +650,7 @@ struct TextDocumentContentChangeEvent { /** * The length of the range that got replaced. */ - int rangeLength; + int rangeLength = 0; /** * The new text of the range/document. @@ -601,20 +667,20 @@ struct TextDocumentContentChangeEvent { // Use namespace instead of enumeration to follow the LSP specifications namespace DiagnosticSeverity { /** - * Reports an error. - */ + * Reports an error. + */ static const int Error = 1; /** - * Reports a warning. - */ + * Reports a warning. + */ static const int Warning = 2; /** - * Reports an information. - */ + * Reports an information. + */ static const int Information = 3; /** - * Reports a hint. - */ + * Reports a hint. + */ static const int Hint = 4; }; // namespace DiagnosticSeverity @@ -656,12 +722,12 @@ struct Diagnostic { * The diagnostic's severity. Can be omitted. If omitted it is up to the * client to interpret diagnostics as error, warning, info or hint. */ - int severity; + int severity = 0; /** * The diagnostic's code, which might appear in the user interface. */ - int code; + int code = 0; /** * A human-readable string describing the source of this @@ -687,7 +753,7 @@ struct Diagnostic { dict["severity"] = severity; dict["message"] = message; dict["source"] = source; - if (!relatedInformation.empty()) { + if (!relatedInformation.is_empty()) { Array arr; arr.resize(relatedInformation.size()); for (int i = 0; i < relatedInformation.size(); i++) { @@ -766,7 +832,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 +854,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; @@ -805,18 +871,18 @@ static const int TypeParameter = 25; */ namespace InsertTextFormat { /** - * The primary text to be inserted is treated as a plain string. - */ + * The primary text to be inserted is treated as a plain string. + */ static const int PlainText = 1; /** - * The primary text to be inserted is treated as a snippet. - * - * A snippet can define tab stops and placeholders with `$1`, `$2` - * and `${3:foo}`. `$0` defines the final tab stop, it defaults to - * the end of the snippet. Placeholders with equal identifiers are linked, - * that is typing in one will update others too. - */ + * The primary text to be inserted is treated as a snippet. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ static const int Snippet = 2; }; // namespace InsertTextFormat @@ -833,7 +899,7 @@ struct CompletionItem { * an icon is chosen by the editor. The standardized set * of available values is defined in `CompletionItemKind`. */ - int kind; + int kind = 0; /** * A human-readable string with additional information @@ -891,7 +957,7 @@ struct CompletionItem { * The format of the insert text. The format applies to both the `insertText` property * and the `newText` property of a provided `textEdit`. */ - int insertTextFormat; + int insertTextFormat = 0; /** * An edit which is applied to a document when selecting this completion. When an edit is provided the value of @@ -1003,7 +1069,7 @@ struct CompletionList { * This list it not complete. Further typing should result in recomputing * this list. */ - bool isIncomplete; + bool isIncomplete = false; /** * The completion items. @@ -1018,32 +1084,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 /** @@ -1191,7 +1257,7 @@ struct DocumentSymbol { void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { DocumentedSymbolInformation si; - if (p_join_name && !p_container.empty()) { + if (p_join_name && !p_container.is_empty()) { si.name = p_container + ">" + name; } else { si.name = name; @@ -1266,6 +1332,18 @@ struct DocumentSymbol { } }; +struct ApplyWorkspaceEditParams { + WorkspaceEdit edit; + + Dictionary to_json() { + Dictionary dict; + + dict["edit"] = edit.to_json(); + + return dict; + } +}; + struct NativeSymbolInspectParams { String native_class; String symbol_name; @@ -1281,16 +1359,16 @@ struct NativeSymbolInspectParams { */ namespace FoldingRangeKind { /** - * Folding range for a comment - */ + * Folding range for a comment + */ static const String Comment = "comment"; /** - * Folding range for a imports or includes - */ + * Folding range for a imports or includes + */ static const String Imports = "imports"; /** - * Folding range for a region (e.g. `#region`) - */ + * Folding range for a region (e.g. `#region`) + */ static const String Region = "region"; } // namespace FoldingRangeKind @@ -1341,20 +1419,20 @@ struct FoldingRange { */ namespace CompletionTriggerKind { /** - * Completion was triggered by typing an identifier (24x7 code - * complete), manual invocation (e.g Ctrl+Space) or via API. - */ + * Completion was triggered by typing an identifier (24x7 code + * complete), manual invocation (e.g Ctrl+Space) or via API. + */ static const int Invoked = 1; /** - * Completion was triggered by a trigger character specified by - * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. - */ + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + */ static const int TriggerCharacter = 2; /** - * Completion was re-triggered as the current completion list is incomplete. - */ + * Completion was re-triggered as the current completion list is incomplete. + */ static const int TriggerForIncompleteCompletions = 3; } // namespace CompletionTriggerKind @@ -1363,8 +1441,8 @@ static const int TriggerForIncompleteCompletions = 3; */ struct CompletionContext { /** - * How the completion was triggered. - */ + * How the completion was triggered. + */ int triggerKind = CompletionTriggerKind::TriggerCharacter; /** @@ -1528,6 +1606,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 +1776,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`. @@ -1661,7 +1852,7 @@ struct ServerCapabilities { signatureHelpProvider.triggerCharacters.push_back(","); signatureHelpProvider.triggerCharacters.push_back("("); dict["signatureHelpProvider"] = signatureHelpProvider.to_json(); - dict["codeLensProvider"] = false; // codeLensProvider.to_json(); + //dict["codeLensProvider"] = codeLensProvider.to_json(); dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json(); dict["renameProvider"] = renameProvider.to_json(); dict["documentLinkProvider"] = documentLinkProvider.to_json(); @@ -1676,6 +1867,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; @@ -1714,7 +1906,7 @@ struct GodotNativeClassInfo { struct GodotCapabilities { /** * Native class list - */ + */ List<GodotNativeClassInfo> native_classes; Dictionary to_json() { @@ -1781,7 +1973,6 @@ static String marked_documentation(const String &p_bbcode) { } return markdown; } - } // namespace lsp #endif |