feat: updated engine

This commit is contained in:
Sara Gerretsen 2026-07-10 17:04:34 +02:00
parent cbe99774ff
commit f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions

View file

@ -32,97 +32,30 @@
#include "../gdscript.h"
#include "../gdscript_analyzer.h"
#include "editor/settings/editor_settings.h"
#include "gdscript_language_protocol.h"
#include "gdscript_workspace.h"
int get_indent_size() {
if (EditorSettings::get_singleton()) {
return EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
} else {
return 4;
}
}
LSP::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const {
LSP::Position GodotPosition::to_lsp() const {
LSP::Position res;
// Special case: `line = 0` -> root class (range covers everything).
if (line <= 0) {
return res;
}
// Special case: `line = p_lines.size() + 1` -> root class (range covers everything).
if (line >= p_lines.size() + 1) {
res.line = p_lines.size();
return res;
}
res.line = line - 1;
// Special case: `column = 0` -> Starts at beginning of line.
if (column <= 0) {
return res;
}
// Note: character outside of `pos_line.length()-1` is valid.
res.character = column - 1;
String pos_line = p_lines[res.line];
if (pos_line.contains_char('\t')) {
int tab_size = get_indent_size();
int in_col = 1;
int res_char = 0;
while (res_char < pos_line.size() && in_col < column) {
if (pos_line[res_char] == '\t') {
in_col += tab_size;
res_char++;
} else {
in_col++;
res_char++;
}
}
res.character = res_char;
}
return res;
}
GodotPosition GodotPosition::from_lsp(const LSP::Position p_pos, const Vector<String> &p_lines) {
GodotPosition res(p_pos.line + 1, p_pos.character + 1);
// Line outside of actual text is valid (-> pos/cursor at end of text).
if (res.line > p_lines.size()) {
return res;
}
String line = p_lines[p_pos.line];
int tabs_before_char = 0;
for (int i = 0; i < p_pos.character && i < line.length(); i++) {
if (line[i] == '\t') {
tabs_before_char++;
}
}
if (tabs_before_char > 0) {
int tab_size = get_indent_size();
res.column += tabs_before_char * (tab_size - 1);
}
return res;
GodotPosition GodotPosition::from_lsp(const LSP::Position p_pos) {
return GodotPosition(p_pos.line + 1, p_pos.character + 1);
}
LSP::Range GodotRange::to_lsp(const Vector<String> &p_lines) const {
LSP::Range GodotRange::to_lsp() const {
LSP::Range res;
res.start = start.to_lsp(p_lines);
res.end = end.to_lsp(p_lines);
res.start = start.to_lsp();
res.end = end.to_lsp();
return res;
}
GodotRange GodotRange::from_lsp(const LSP::Range &p_range, const Vector<String> &p_lines) {
GodotPosition start = GodotPosition::from_lsp(p_range.start, p_lines);
GodotPosition end = GodotPosition::from_lsp(p_range.end, p_lines);
GodotRange GodotRange::from_lsp(const LSP::Range &p_range) {
GodotPosition start = GodotPosition::from_lsp(p_range.start);
GodotPosition end = GodotPosition::from_lsp(p_range.end);
return GodotRange(start, end);
}
@ -135,18 +68,12 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.severity = LSP::DiagnosticSeverity::Error;
diagnostic.message = error.message;
diagnostic.source = "gdscript";
diagnostic.code = -1;
LSP::Range range;
LSP::Position pos;
const PackedStringArray line_array = get_lines();
int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, line_array.size() - 1);
const String &line_text = line_array[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
range.end = range.start;
range.end.character = line_text.strip_edges(false).length();
diagnostic.range = range;
GodotRange godot_range(
GodotPosition(error.start_line, error.start_column),
GodotPosition(error.end_line, error.end_column));
diagnostic.range = godot_range.to_lsp();
diagnostics.push_back(diagnostic);
}
@ -156,17 +83,12 @@ void ExtendGDScriptParser::update_diagnostics() {
diagnostic.severity = LSP::DiagnosticSeverity::Warning;
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.start_line);
const String &line_text = get_lines()[line];
pos.line = line;
pos.character = line_text.length() - line_text.strip_edges(true, false).length();
range.start = pos;
range.end = pos;
range.end.character = line_text.strip_edges(false).length();
diagnostic.range = range;
GodotRange godot_range(
GodotPosition(warning.start_line, warning.start_column),
GodotPosition(warning.end_line, warning.end_column));
diagnostic.range = godot_range.to_lsp();
diagnostics.push_back(diagnostic);
}
}
@ -217,7 +139,7 @@ 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(scr_path);
link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(lines);
link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp();
document_links.push_back(link);
}
}
@ -228,7 +150,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) {
LSP::Range ExtendGDScriptParser::range_of_node(const GDScriptParser::Node *p_node) const {
GodotPosition start(p_node->start_line, p_node->start_column);
GodotPosition end(p_node->end_line, p_node->end_column);
return GodotRange(start, end).to_lsp(lines);
return GodotRange(start, end).to_lsp();
}
void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, LSP::DocumentSymbol &r_symbol) {
@ -333,8 +255,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
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();
if (m.constant->type_constraint.is_hard_type()) {
symbol.detail += ": " + m.constant->type_constraint.to_string();
}
const Variant &default_value = m.constant->initializer->reduced_value;
@ -391,8 +313,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
param_symbol.uri = uri;
param_symbol.script_path = path;
param_symbol.detail = "var " + param_symbol.name;
if (param->get_datatype().is_hard_type()) {
param_symbol.detail += ": " + param->get_datatype().to_string();
if (param->type_constraint.is_hard_type()) {
param_symbol.detail += ": " + param->type_constraint.to_string();
}
symbol.children.push_back(param_symbol);
}
@ -404,8 +326,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.name = m.enum_value.identifier->name;
symbol.kind = LSP::SymbolKind::EnumMember;
symbol.deprecated = false;
symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.start_column).to_lsp(lines);
symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.end_column).to_lsp(lines);
symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.start_column).to_lsp();
symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.end_column).to_lsp();
symbol.selectionRange = range_of_node(m.enum_value.identifier);
symbol.documentation = m.enum_value.doc_data.description;
symbol.uri = uri;
@ -440,8 +362,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
child.name = value.identifier->name;
child.kind = LSP::SymbolKind::EnumMember;
child.deprecated = false;
child.range.start = GodotPosition(value.line, value.start_column).to_lsp(lines);
child.range.end = GodotPosition(value.line, value.end_column).to_lsp(lines);
child.range.start = GodotPosition(value.line, value.start_column).to_lsp();
child.range.end = GodotPosition(value.line, value.end_column).to_lsp();
child.selectionRange = range_of_node(value.identifier);
child.documentation = value.doc_data.description;
child.uri = uri;
@ -502,8 +424,8 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
parameters += ", ";
}
parameters += String(parameter->identifier->name);
if (parameter->get_datatype().is_hard_type()) {
parameters += ": " + parameter->get_datatype().to_string();
if (parameter->type_constraint.is_hard_type()) {
parameters += ": " + parameter->type_constraint.to_string();
}
if (parameter->initializer != nullptr) {
parameters += " = " + parameter->initializer->reduced_value.to_json_string();
@ -514,11 +436,11 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
parameters += ", ";
}
const ParameterNode *rest_param = p_func->rest_parameter;
parameters += "..." + rest_param->identifier->name + ": " + rest_param->get_datatype().to_string();
parameters += "..." + rest_param->identifier->name + ": " + rest_param->type_constraint.to_string();
}
r_symbol.detail += parameters + ")";
const DataType return_type = p_func->get_datatype();
const DataType return_type = p_func->return_type_constraint;
if (return_type.is_hard_type()) {
if (return_type.kind == DataType::BUILTIN && return_type.builtin_type == Variant::NIL) {
r_symbol.detail += " -> void";
@ -615,8 +537,8 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
break;
default:
// Fallback.
symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp(get_lines());
symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp(get_lines());
symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp();
symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp();
symbol.selectionRange = symbol.range;
break;
}
@ -710,14 +632,29 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const LSP::Position &p_c
return longthing;
}
String ExtendGDScriptParser::get_identifier_under_position(const LSP::Position &p_position, LSP::Range &r_range) const {
String ExtendGDScriptParser::get_symbol_name_under_position(const LSP::Position &p_position, LSP::Range &r_range) const {
r_range = LSP::Range(p_position, p_position); // Default for error macros.
ERR_FAIL_INDEX_V(p_position.line, lines.size(), "");
String line = lines[p_position.line];
if (line.is_empty()) {
return "";
}
// Checks against line.size(), which includes a terminating NUL. This is to allow a cursor after the last character.
ERR_FAIL_INDEX_V(p_position.character, line.size(), "");
LSP::Position pos = p_position;
// Cursor after last character.
if (pos.character >= line.length()) {
pos.character--;
}
// If on the start of an annotation move the position into the identifier part. We account for "@" at the end.
if (line[pos.character] == '@' && pos.character + 1 < line.size() && is_unicode_identifier_start(line[pos.character + 1])) {
pos.character++;
}
// `p_position` cursor is BETWEEN chars, not ON chars.
// ->
// ```gdscript
@ -729,51 +666,54 @@ String ExtendGDScriptParser::get_identifier_under_position(const LSP::Position &
// |
// | cursor on `member`, pos on ` ` (space)
// ```
// -> Move position to previous character if:
// * Position not on valid identifier char.
// * Prev position is valid identifier char.
LSP::Position pos = p_position;
if (
pos.character >= line.length() // Cursor at end of line.
|| (!is_unicode_identifier_continue(line[pos.character]) // Not on valid identifier char.
&& (pos.character > 0 // Not line start -> there is a prev char.
&& is_unicode_identifier_continue(line[pos.character - 1]) // Prev is valid identifier char.
))) {
if (!is_unicode_identifier_continue(line[pos.character])) {
if (pos.character == 0 || !is_unicode_identifier_continue(line[pos.character - 1])) {
// In between two non-identifier chars.
return "";
}
// Move position to previous character if not on valid char and the previous char is valid.
pos.character--;
}
int start_pos = pos.character;
// Iterate forward till we have a start. Symbol starts require lookahead, so we save the latest valid start and track back to it once we can stop looking.
// E.g. ?0nly
// ^
// | could be an identifier start (since 0 can't start identifiers), but if there was another symbol in front of 0 the identifier would be longer.
int last_valid_start_pos = -1;
for (int c = pos.character; c >= 0; c--) {
start_pos = c;
char32_t ch = line[c];
bool valid_char = is_unicode_identifier_continue(ch);
if (!valid_char) {
if (is_unicode_identifier_start(ch)) {
last_valid_start_pos = c;
}
if (!is_unicode_identifier_continue(ch)) {
break;
}
}
int end_pos = pos.character;
// Iterate backwards till we have an end. No lookahead required. Uses +1 since the end of a range is exclusive.
int end_pos = pos.character + 1;
for (int c = pos.character; c < line.length(); c++) {
char32_t ch = line[c];
bool valid_char = is_unicode_identifier_continue(ch);
if (!valid_char) {
if (!is_unicode_identifier_continue(ch)) {
break;
}
end_pos = c;
end_pos = c + 1;
}
if (!is_unicode_identifier_start(line[start_pos + 1])) {
if (last_valid_start_pos == -1) {
return "";
}
if (start_pos < end_pos) {
r_range.start.line = r_range.end.line = pos.line;
r_range.start.character = start_pos + 1;
r_range.end.character = end_pos + 1;
return line.substr(start_pos + 1, end_pos - start_pos);
// For annotations we include the @, since it is included in the symbol name used by other parts of the LSP code.
int start_pos = last_valid_start_pos;
if (start_pos > 0 && line[start_pos - 1] == '@') {
start_pos -= 1;
}
return "";
r_range.start.character = start_pos;
r_range.end.character = end_pos;
return line.substr(start_pos, end_pos - start_pos);
}
String ExtendGDScriptParser::get_uri() const {
@ -870,41 +810,17 @@ const List<LSP::DocumentLink> &ExtendGDScriptParser::get_document_links() const
return document_links;
}
const Array &ExtendGDScriptParser::get_member_completions() {
if (member_completions.is_empty()) {
for (const KeyValue<String, const LSP::DocumentSymbol *> &E : members) {
const LSP::DocumentSymbol *symbol = E.value;
LSP::CompletionItem item = symbol->make_completion_item();
item.data = JOIN_SYMBOLS(path, E.key);
member_completions.push_back(item.to_json());
}
for (const KeyValue<String, ClassMembers> &E : inner_classes) {
const ClassMembers *inner_class = &E.value;
for (const KeyValue<String, const LSP::DocumentSymbol *> &F : *inner_class) {
const LSP::DocumentSymbol *symbol = F.value;
LSP::CompletionItem item = symbol->make_completion_item();
item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(E.key, F.key));
member_completions.push_back(item.to_json());
}
}
}
return member_completions;
}
Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
ERR_FAIL_NULL_V(p_func, Dictionary());
Dictionary func;
func["name"] = p_func->identifier->name;
func["return_type"] = p_func->get_datatype().to_string();
func["return_type"] = p_func->return_type_constraint.to_string();
func["rpc_config"] = p_func->rpc_config;
Array parameters;
for (int i = 0; i < p_func->parameters.size(); i++) {
Dictionary arg;
arg["name"] = p_func->parameters[i]->identifier->name;
arg["type"] = p_func->parameters[i]->get_datatype().to_string();
arg["type"] = p_func->parameters[i]->type_constraint.to_string();
if (p_func->parameters[i]->initializer != nullptr) {
arg["default_value"] = p_func->parameters[i]->initializer->reduced_value;
}
@ -954,7 +870,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
Dictionary api;
api["name"] = m.constant->identifier->name;
api["value"] = m.constant->initializer->reduced_value;
api["data_type"] = m.constant->get_datatype().to_string();
api["data_type"] = m.constant->type_constraint.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;
@ -991,7 +907,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode
case ClassNode::Member::VARIABLE: {
Dictionary api;
api["name"] = m.variable->identifier->name;
api["data_type"] = m.variable->get_datatype().to_string();
api["data_type"] = m.variable->type_constraint.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());