diff --git a/core/object/script_language.h b/core/object/script_language.h index 41e35442fc..374d31da97 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -222,7 +222,9 @@ public: /* EDITOR FUNCTIONS */ struct Warning { + /// One-based. int start_line = 0; + /// One-based. int end_line = 0; int code; String string_code; @@ -231,7 +233,9 @@ public: struct ScriptError { String path; + /// One-based. int line = -1; + /// One-based. int column = -1; String message; }; diff --git a/editor/gui/code_editor.cpp b/editor/gui/code_editor.cpp index 2d4891b7cf..8f80bcc6bd 100644 --- a/editor/gui/code_editor.cpp +++ b/editor/gui/code_editor.cpp @@ -988,21 +988,11 @@ void CodeTextEditor::_line_col_changed() { code_complete_timer->stop(); } - String line = text_editor->get_line(text_editor->get_caret_line()); - - int positional_column = 0; - for (int i = 0; i < text_editor->get_caret_column(); i++) { - if (line[i] == '\t') { - positional_column += text_editor->get_indent_size(); // Tab size - } else { - positional_column += 1; - } - } - + Point2i display_position = get_pos_for_display(Point2i(text_editor->get_caret_line(), text_editor->get_caret_column())); StringBuilder sb; - sb.append(itos(text_editor->get_caret_line() + 1).lpad(4)); + sb.append(itos(display_position.x).lpad(4)); sb.append(" : "); - sb.append(itos(positional_column + 1).lpad(3)); + sb.append(itos(display_position.y).lpad(3)); line_and_col_button->set_text(sb.as_string()); @@ -1595,18 +1585,25 @@ Point2i CodeTextEditor::get_error_pos() const { return Point2i(error_line, error_column); } +Point2i CodeTextEditor::get_pos_for_display(Point2i p_internal_position) const { + const String line_text = text_editor->get_line(p_internal_position.x); + const int indent_size = text_editor->get_indent_size(); + + int corrected_column = 0; + for (int i = 0; i < p_internal_position.y; i++) { + if (line_text[i] == '\t') { + corrected_column += indent_size - (corrected_column % indent_size); + } else { + corrected_column += 1; + } + } + + return Point2(p_internal_position.x + 1, corrected_column + 1); +} + void CodeTextEditor::goto_error() { if (!error->get_text().is_empty()) { - int corrected_column = error_column; - - const String line_text = text_editor->get_line(error_line); - const int indent_size = text_editor->get_indent_size(); - if (indent_size > 1) { - const int tab_count = line_text.length() - line_text.lstrip("\t").length(); - corrected_column -= tab_count * (indent_size - 1); - } - - goto_line_centered(error_line, corrected_column); + goto_line_centered(error_line, error_column); } } diff --git a/editor/gui/code_editor.h b/editor/gui/code_editor.h index a5bc8fad11..d6240990a1 100644 --- a/editor/gui/code_editor.h +++ b/editor/gui/code_editor.h @@ -283,6 +283,8 @@ public: void set_error(const String &p_error); void set_error_pos(int p_line, int p_column); Point2i get_error_pos() const; + /// Convert internal position into a user readable format. This means 1 is the first position and the column counts tabs with their tab width. + Point2i get_pos_for_display(Point2i p_internal_position) const; void update_line_and_column() { _line_col_changed(); } CodeEdit *get_text_editor() { return text_editor; } FindReplaceBar *get_find_replace_bar() { return find_replace_bar; } diff --git a/editor/script/script_text_editor.cpp b/editor/script/script_text_editor.cpp index bca7adb364..d83e4c78ac 100644 --- a/editor/script/script_text_editor.cpp +++ b/editor/script/script_text_editor.cpp @@ -382,7 +382,7 @@ bool ScriptTextEditor::_warning_clicked(const Variant &p_line) { return true; } else if (p_line.get_type() == Variant::DICTIONARY) { Dictionary meta = p_line.operator Dictionary(); - const int line = meta["line"].operator int64_t() - 1; + const int line = meta["line"].operator int64_t(); const String code = meta["code"].operator String(); const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; @@ -424,16 +424,7 @@ void ScriptTextEditor::_error_clicked(const Variant &p_line) { if (scr.is_null()) { EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); } else { - int corrected_column = column; - - const String line_text = code_editor->get_text_editor()->get_line(line); - const int indent_size = code_editor->get_text_editor()->get_indent_size(); - if (indent_size > 1) { - const int tab_count = line_text.length() - line_text.lstrip("\t").length(); - corrected_column -= tab_count * (indent_size - 1); - } - - ScriptEditor::get_singleton()->edit(scr, line, corrected_column); + ScriptEditor::get_singleton()->edit(scr, line, column); } } } @@ -873,12 +864,11 @@ void ScriptTextEditor::_validate_script() { } if (errors.size() > 0) { - const int line = errors.front()->get().line; - const int column = errors.front()->get().column; + code_editor->set_error_pos(errors.front()->get().line - 1, errors.front()->get().column - 1); const String message = errors.front()->get().message.replace("[", "[lb]"); - const String error_text = vformat(TTR("Error at ([hint=Line %d, column %d]%d, %d[/hint]):"), line, column, line, column) + " " + message; + const Point2i display_pos = code_editor->get_pos_for_display(code_editor->get_error_pos()); + const String error_text = vformat(TTR("Error at ([hint=Line %d, column %d]%d, %d[/hint]):"), display_pos.x, display_pos.y, display_pos.x, display_pos.y) + " " + message; code_editor->set_error(error_text); - code_editor->set_error_pos(line - 1, column - 1); } script_is_valid = false; } else { @@ -945,7 +935,7 @@ void ScriptTextEditor::_update_warnings() { warnings_panel->push_table(3); for (const ScriptLanguage::Warning &w : warnings) { Dictionary ignore_meta; - ignore_meta["line"] = w.start_line; + ignore_meta["line"] = w.start_line - 1; ignore_meta["code"] = w.string_code.to_lower(); warnings_panel->push_cell(); warnings_panel->push_meta(ignore_meta); @@ -978,10 +968,6 @@ void ScriptTextEditor::_update_errors() { errors_panel->clear(); errors_panel->push_table(2); for (const ScriptLanguage::ScriptError &err : errors) { - Dictionary click_meta; - click_meta["line"] = err.line; - click_meta["column"] = err.column; - errors_panel->push_cell(); errors_panel->push_meta(err.line - 1); errors_panel->push_color(warnings_panel->get_theme_color(SNAME("error_color"), EditorStringName(Editor))); @@ -998,13 +984,13 @@ void ScriptTextEditor::_update_errors() { errors_panel->pop(); // Table for (const KeyValue> &KV : depended_errors) { - Dictionary click_meta; - click_meta["path"] = KV.key; - click_meta["line"] = 1; + Dictionary click_meta_script; + click_meta_script["path"] = KV.key; + click_meta_script["line"] = 0; errors_panel->add_newline(); errors_panel->add_newline(); - errors_panel->push_meta(click_meta); + errors_panel->push_meta(click_meta_script); errors_panel->add_text(vformat(R"(%s:)", KV.key)); errors_panel->pop(); // Meta goto. errors_panel->add_newline(); @@ -1013,8 +999,10 @@ void ScriptTextEditor::_update_errors() { errors_panel->push_table(2); String filename = KV.key.get_file(); for (const ScriptLanguage::ScriptError &err : KV.value) { - click_meta["line"] = err.line; - click_meta["column"] = err.column; + Dictionary click_meta; + click_meta["path"] = KV.key; + click_meta["line"] = err.line - 1; + click_meta["column"] = err.column - 1; errors_panel->push_cell(); errors_panel->push_meta(click_meta); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 4777ff8c28..9037df620a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -44,10 +44,6 @@ #include "servers/text/text_server.h" #endif -#ifdef TOOLS_ENABLED -#include "editor/settings/editor_settings.h" -#endif - // This function is used to determine that a type is "built-in" as opposed to native // and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded: // `Variant::NIL` - `null` is literal, not a type. @@ -448,13 +444,6 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ for_completion = p_for_completion; parse_body = p_parse_body; - int tab_size = 4; -#ifdef TOOLS_ENABLED - if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); - } -#endif // TOOLS_ENABLED - if (p_for_completion) { // Remove cursor sentinel char. const Vector lines = p_source_code.split("\n"); @@ -467,8 +456,6 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ if (line[j] == char32_t(0xFFFF)) { found = true; break; - } else if (line[j] == '\t') { - cursor_column += tab_size - 1; } cursor_column++; } @@ -510,7 +497,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ // Create a dummy Node for the warning, pointing to the very beginning of the file Node *nd = alloc_node(); nd->start_line = 1; - nd->start_column = 0; + nd->start_column = 1; nd->end_line = 1; push_warning(nd, GDScriptWarning::EMPTY_FILE); } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 3d1b654928..f93c77fbe6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -271,10 +271,10 @@ public: // }; // Type type = NO_ERROR; String message; - int start_line = 0; - int start_column = 0; - int end_line = 0; - int end_column = 0; + int start_line; + int start_column; + int end_line; + int end_column; }; #ifdef TOOLS_ENABLED @@ -342,10 +342,11 @@ public: }; Type type = NONE; - int start_line = 0; - int start_column = 0; - int end_line = 0; - int end_column = 0; + // Negative values indicate a node which was used for sentence level recovery. + int start_line = -1; + int start_column = -1; + int end_line = -1; + int end_column = -1; Node *next = nullptr; List annotations; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index ad1e359f5f..bd080c9076 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -1164,8 +1164,8 @@ void GDScriptTokenizerText::check_indent() { while (!_is_at_end()) { char32_t space = _peek(); if (space == '\t') { - // Consider individual tab columns. - column += tab_size - 1; + // Counting tab indent with the configured tab size, allows error tolerant parsing in the editor, + // so that autocompletion still is functional when a file contains mixed indentation. indent_count += tab_size; } else if (space == ' ') { indent_count += 1; @@ -1307,12 +1307,8 @@ void GDScriptTokenizerText::_skip_whitespace() { char32_t c = _peek(); switch (c) { case ' ': - _advance(); - break; case '\t': _advance(); - // Consider individual tab columns. - column += tab_size - 1; break; case '\r': _advance(); // Consume either way. diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 4133db8ff0..b9d6c0fd76 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -165,10 +165,13 @@ public: Type type = EMPTY; Variant literal; - int start_line = 0; - int start_column = 0; - int end_line = 0; - int end_column = 0; + // The parser positions errors based on the previous token. This needs to + // be default initialized so that errors on the first token don't access + // uninitialized memory. + int start_line = 1; + int start_column = 1; + int end_line = 1; + int end_column = 1; CursorPlace cursor_place = CURSOR_NONE; String source; @@ -227,16 +230,16 @@ class GDScriptTokenizerText : public GDScriptTokenizer { String source; const char32_t *_source = nullptr; const char32_t *_current = nullptr; - int line = 0; - int column = 0; + int line = 1; + int column = 1; int cursor_line = -1; int cursor_column = -1; int tab_size = 4; // Keep track of multichar tokens. const char32_t *_start = nullptr; - int start_line = 0; - int start_column = 0; + int start_line = 1; + int start_column = 1; // Info cache. bool line_continuation = false; // Whether this line is a continuation of the previous, like when using '\'. diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp index 37d2638083..01ea731e3a 100644 --- a/modules/gdscript/gdscript_tokenizer_buffer.cpp +++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp @@ -447,6 +447,8 @@ GDScriptTokenizer::Token GDScriptTokenizerBuffer::scan() { } Token eof; eof.type = Token::TK_EOF; + eof.start_line = current_line; + eof.end_line = current_line; return eof; }; diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 9a03b82108..be2d891c88 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -161,10 +161,10 @@ public: static_assert(std_size(default_warning_levels) == WARNING_MAX, "Amount of default levels does not match the amount of warnings."); Code code = WARNING_MAX; - int start_line = 0; - int start_column = 0; - int end_line = 0; - int end_column = 0; + int start_line; + int start_column; + int end_line; + int end_column; Vector symbols; String get_name() const; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 9d6333d4e7..a1ee3eb3b7 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -35,95 +35,27 @@ #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" -#include "editor/settings/editor_settings.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 &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 &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 &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 &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); } @@ -141,7 +73,7 @@ void ExtendGDScriptParser::update_diagnostics() { GodotPosition(error.start_line, error.start_column), GodotPosition(error.end_line, error.end_column)); - diagnostic.range = godot_range.to_lsp(get_lines()); + diagnostic.range = godot_range.to_lsp(); diagnostics.push_back(diagnostic); } @@ -156,7 +88,7 @@ void ExtendGDScriptParser::update_diagnostics() { GodotPosition(warning.start_line, warning.start_column), GodotPosition(warning.end_line, warning.end_column)); - diagnostic.range = godot_range.to_lsp(get_lines()); + diagnostic.range = godot_range.to_lsp(); diagnostics.push_back(diagnostic); } } @@ -207,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); } } @@ -218,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) { @@ -394,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; @@ -430,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; @@ -605,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; } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index ecc8b26492..8ee79c547a 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -71,8 +71,8 @@ struct GodotPosition { GodotPosition(int p_line, int p_column) : line(p_line), column(p_column) {} - LSP::Position to_lsp(const Vector &p_lines) const; - static GodotPosition from_lsp(const LSP::Position p_pos, const Vector &p_lines); + LSP::Position to_lsp() const; + static GodotPosition from_lsp(const LSP::Position p_pos); bool operator==(const GodotPosition &p_other) const { return line == p_other.line && column == p_other.column; @@ -90,8 +90,8 @@ struct GodotRange { GodotRange(GodotPosition p_start, GodotPosition p_end) : start(p_start), end(p_end) {} - LSP::Range to_lsp(const Vector &p_lines) const; - static GodotRange from_lsp(const LSP::Range &p_range, const Vector &p_lines); + LSP::Range to_lsp() const; + static GodotRange from_lsp(const LSP::Range &p_range); bool operator==(const GodotRange &p_other) const { return start == p_other.start && end == p_other.end; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index a5538ddafb..d880ede4e5 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -546,8 +546,6 @@ Array GDScriptLanguageProtocol::lsp_completion(const Dictionary &p_params) { List options; get_workspace()->completion(params, &options); - const Vector &lines = get_parse_result(workspace->get_file_path(params.textDocument.uri))->get_lines(); - if (!options.is_empty()) { int i = 0; arr.resize(options.size()); @@ -573,7 +571,7 @@ Array GDScriptLanguageProtocol::lsp_completion(const Dictionary &p_params) { if (option.text_edit.is_set()) { GodotRange range(GodotPosition(option.text_edit.start_line, option.text_edit.start_column), GodotPosition(option.text_edit.end_line, option.text_edit.end_column)); item.textEdit.newText = option.text_edit.new_text; - item.textEdit.range = range.to_lsp(lines); + item.textEdit.range = range.to_lsp(); } switch (option.kind) { diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index ea2c66588b..145135bb22 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -65,19 +65,19 @@ static void test_tokenizer(const String &p_code, const Vector &p_lines) StringBuilder token; token += " --> "; // Padding for line number. + // Files without trailing newline can produce dedent tokens on non-existent lines. + const String &start_line = current.start_line <= p_lines.size() ? p_lines[current.start_line - 1] : String(); + const String &end_line = current.start_line <= p_lines.size() ? p_lines[current.start_line - 1] : String(); + if (current.start_line != current.end_line) { // Print "vvvvvv" to point at the token. StringBuilder pointer; pointer += " "; // Padding for line number. - int line_width = 0; - if (current.start_line - 1 >= 0 && current.start_line - 1 < p_lines.size()) { - line_width = p_lines[current.start_line - 1].replace("\t", tab).length(); - } - - const int offset = MAX(0, current.start_column - 1); - const int width = MAX(0, line_width - current.start_column + 1); - pointer += String::chr(' ').repeat(offset) + String::chr('v').repeat(width); + const int line_width = start_line.length() + start_line.count("\t") + (tab_size - 1); + const int start_offset = current.start_column - 1 + (current.start_column > 1 ? start_line.count("\t", 0, current.start_column - 1) * (tab_size - 1) : 0); + const int width = line_width - start_offset; + pointer += String::chr(' ').repeat(start_offset) + String::chr('v').repeat(width); print_line(pointer.as_string()); } @@ -92,12 +92,13 @@ static void test_tokenizer(const String &p_code, const Vector &p_lines) pointer += " "; // Padding for line number. if (current.start_line == current.end_line) { - const int offset = MAX(0, current.start_column - 1); - const int width = MAX(0, current.end_column - current.start_column); - pointer += String::chr(' ').repeat(offset) + String::chr('^').repeat(width); + const int start_offset = current.start_column - 1 + (current.start_column > 1 ? start_line.count("\t", 0, current.start_column - 1) * (tab_size - 1) : 0); + const int end_offset = current.end_column - 1 + (current.end_column > 1 ? end_line.count("\t", 0, current.end_column - 1) * (tab_size - 1) : 0); + const int width = end_offset - start_offset; + pointer += String::chr(' ').repeat(start_offset) + String::chr('^').repeat(width); } else { - const int width = MAX(0, current.end_column - 1); - pointer += String::chr('^').repeat(width); + const int end_offset = current.end_column - 1 + (current.end_column > 1 ? end_line.count("\t", 0, current.end_column - 1) * (tab_size - 1) : 0); + pointer += String::chr('^').repeat(end_offset); } print_line(pointer.as_string()); diff --git a/modules/gdscript/tests/test_lsp.h b/modules/gdscript/tests/test_lsp.h index 33de0fac8f..74516e7d84 100644 --- a/modules/gdscript/tests/test_lsp.h +++ b/modules/gdscript/tests/test_lsp.h @@ -320,20 +320,6 @@ void assert_no_errors_in(const String &p_path) { REQUIRE_MESSAGE(err == OK, vformat("Errors while analyzing '%s'", p_path)); } -inline LSP::Position lsp_pos(int line, int character) { - LSP::Position p; - p.line = line; - p.character = character; - return p; -} - -void test_position_roundtrip(LSP::Position p_lsp, GodotPosition p_gd, const PackedStringArray &p_lines) { - GodotPosition actual_gd = GodotPosition::from_lsp(p_lsp, p_lines); - CHECK_EQ(p_gd, actual_gd); - LSP::Position actual_lsp = p_gd.to_lsp(p_lines); - CHECK_EQ(p_lsp, actual_lsp); -} - // Note: // * Cursor is BETWEEN chars // * `va|r` -> cursor between `a`&`r` @@ -344,75 +330,6 @@ void test_position_roundtrip(LSP::Position p_lsp, GodotPosition p_gd, const Pack // * LSP: both 0-based // * Godot: both 1-based TEST_SUITE("[Modules][GDScript][LSP][Editor]") { - TEST_CASE("Can convert positions to and from Godot") { - String code = R"(extends Node - -var member := 42 - -func f(): - var value := 42 - return value + member)"; - PackedStringArray lines = code.split("\n"); - - SUBCASE("line after end") { - LSP::Position lsp = lsp_pos(7, 0); - GodotPosition gd(8, 1); - test_position_roundtrip(lsp, gd, lines); - } - SUBCASE("first char in first line") { - LSP::Position lsp = lsp_pos(0, 0); - GodotPosition gd(1, 1); - test_position_roundtrip(lsp, gd, lines); - } - - SUBCASE("with tabs") { - // On `v` in `value` in `var value := ...`. - LSP::Position lsp = lsp_pos(5, 6); - GodotPosition gd(6, 13); - test_position_roundtrip(lsp, gd, lines); - } - - SUBCASE("doesn't fail with column outside of character length") { - LSP::Position lsp = lsp_pos(2, 100); - GodotPosition::from_lsp(lsp, lines); - - GodotPosition gd(3, 100); - gd.to_lsp(lines); - } - - SUBCASE("doesn't fail with line outside of line length") { - LSP::Position lsp = lsp_pos(200, 100); - GodotPosition::from_lsp(lsp, lines); - - GodotPosition gd(300, 100); - gd.to_lsp(lines); - } - - SUBCASE("special case: zero column for root class") { - GodotPosition gd(1, 0); - LSP::Position expected = lsp_pos(0, 0); - LSP::Position actual = gd.to_lsp(lines); - CHECK_EQ(actual, expected); - } - SUBCASE("special case: zero line and column for root class") { - GodotPosition gd(0, 0); - LSP::Position expected = lsp_pos(0, 0); - LSP::Position actual = gd.to_lsp(lines); - CHECK_EQ(actual, expected); - } - SUBCASE("special case: negative line for root class") { - GodotPosition gd(-1, 0); - LSP::Position expected = lsp_pos(0, 0); - LSP::Position actual = gd.to_lsp(lines); - CHECK_EQ(actual, expected); - } - SUBCASE("special case: lines.length() + 1 for root class") { - GodotPosition gd(lines.size() + 1, 0); - LSP::Position expected = lsp_pos(lines.size(), 0); - LSP::Position actual = gd.to_lsp(lines); - CHECK_EQ(actual, expected); - } - } TEST_CASE("[workspace][resolve_symbol]") { EditorFileSystem *efs = memnew(EditorFileSystem); GDScriptLanguageProtocol *proto = initialize(root);