| 1 | /**************************************************************************/ |
| 2 | /* gdscript_extend_parser.h */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #ifndef GDSCRIPT_EXTEND_PARSER_H |
| 32 | #define GDSCRIPT_EXTEND_PARSER_H |
| 33 | |
| 34 | #include "../gdscript_parser.h" |
| 35 | #include "godot_lsp.h" |
| 36 | |
| 37 | #include "core/variant/variant.h" |
| 38 | |
| 39 | #ifndef LINE_NUMBER_TO_INDEX |
| 40 | #define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) |
| 41 | #endif |
| 42 | #ifndef COLUMN_NUMBER_TO_INDEX |
| 43 | #define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1) |
| 44 | #endif |
| 45 | |
| 46 | #ifndef SYMBOL_SEPERATOR |
| 47 | #define SYMBOL_SEPERATOR "::" |
| 48 | #endif |
| 49 | |
| 50 | #ifndef JOIN_SYMBOLS |
| 51 | #define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPERATOR + (name)) |
| 52 | #endif |
| 53 | |
| 54 | typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers; |
| 55 | |
| 56 | /** |
| 57 | * Represents a Position as used by GDScript Parser. Used for conversion to and from `lsp::Position`. |
| 58 | * |
| 59 | * Difference to `lsp::Position`: |
| 60 | * * Line & Char/column: 1-based |
| 61 | * * LSP: both 0-based |
| 62 | * * Tabs are expanded to columns using tab size (`text_editor/behavior/indent/size`). |
| 63 | * * LSP: tab is single char |
| 64 | * |
| 65 | * Example: |
| 66 | * ```gdscript |
| 67 | * →→var my_value = 42 |
| 68 | * ``` |
| 69 | * `_` is at: |
| 70 | * * Godot: `column=12` |
| 71 | * * using `indent/size=4` |
| 72 | * * Note: counting starts at `1` |
| 73 | * * LSP: `character=8` |
| 74 | * * Note: counting starts at `0` |
| 75 | */ |
| 76 | struct GodotPosition { |
| 77 | int line; |
| 78 | int column; |
| 79 | |
| 80 | GodotPosition(int p_line, int p_column) : |
| 81 | line(p_line), column(p_column) {} |
| 82 | |
| 83 | lsp::Position to_lsp(const Vector<String> &p_lines) const; |
| 84 | static GodotPosition from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines); |
| 85 | |
| 86 | bool operator==(const GodotPosition &p_other) const { |
| 87 | return line == p_other.line && column == p_other.column; |
| 88 | } |
| 89 | |
| 90 | String to_string() const { |
| 91 | return vformat("(%d,%d)" , line, column); |
| 92 | } |
| 93 | }; |
| 94 | |
| 95 | struct GodotRange { |
| 96 | GodotPosition start; |
| 97 | GodotPosition end; |
| 98 | |
| 99 | GodotRange(GodotPosition p_start, GodotPosition p_end) : |
| 100 | start(p_start), end(p_end) {} |
| 101 | |
| 102 | lsp::Range to_lsp(const Vector<String> &p_lines) const; |
| 103 | static GodotRange from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines); |
| 104 | |
| 105 | bool operator==(const GodotRange &p_other) const { |
| 106 | return start == p_other.start && end == p_other.end; |
| 107 | } |
| 108 | |
| 109 | String to_string() const { |
| 110 | return vformat("[%s:%s]" , start.to_string(), end.to_string()); |
| 111 | } |
| 112 | }; |
| 113 | |
| 114 | class ExtendGDScriptParser : public GDScriptParser { |
| 115 | String path; |
| 116 | Vector<String> lines; |
| 117 | |
| 118 | lsp::DocumentSymbol class_symbol; |
| 119 | Vector<lsp::Diagnostic> diagnostics; |
| 120 | List<lsp::DocumentLink> document_links; |
| 121 | ClassMembers members; |
| 122 | HashMap<String, ClassMembers> inner_classes; |
| 123 | |
| 124 | lsp::Range range_of_node(const GDScriptParser::Node *p_node) const; |
| 125 | |
| 126 | void update_diagnostics(); |
| 127 | |
| 128 | void update_symbols(); |
| 129 | void update_document_links(const String &p_code); |
| 130 | void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol); |
| 131 | void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol); |
| 132 | |
| 133 | Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const; |
| 134 | Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const; |
| 135 | |
| 136 | const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name = "" ) const; |
| 137 | |
| 138 | Array member_completions; |
| 139 | |
| 140 | public: |
| 141 | _FORCE_INLINE_ const String &get_path() const { return path; } |
| 142 | _FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; } |
| 143 | _FORCE_INLINE_ const lsp::DocumentSymbol &get_symbols() const { return class_symbol; } |
| 144 | _FORCE_INLINE_ const Vector<lsp::Diagnostic> &get_diagnostics() const { return diagnostics; } |
| 145 | _FORCE_INLINE_ const ClassMembers &get_members() const { return members; } |
| 146 | _FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; } |
| 147 | |
| 148 | Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const; |
| 149 | |
| 150 | String get_text_for_completion(const lsp::Position &p_cursor) const; |
| 151 | String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "" , bool p_func_required = false) const; |
| 152 | String get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const; |
| 153 | String get_uri() const; |
| 154 | |
| 155 | /** |
| 156 | * `p_symbol_name` gets ignored if empty. Otherwise symbol must match passed in named. |
| 157 | * |
| 158 | * Necessary when multiple symbols at same line for example with `func`: |
| 159 | * `func handle_arg(arg: int):` |
| 160 | * -> Without `p_symbol_name`: returns `handle_arg`. Even if parameter (`arg`) is wanted. |
| 161 | * With `p_symbol_name`: symbol name MUST match `p_symbol_name`: returns `arg`. |
| 162 | */ |
| 163 | const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line, const String &p_symbol_name = "" ) const; |
| 164 | const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "" ) const; |
| 165 | const List<lsp::DocumentLink> &get_document_links() const; |
| 166 | |
| 167 | const Array &get_member_completions(); |
| 168 | Dictionary generate_api() const; |
| 169 | |
| 170 | Error parse(const String &p_code, const String &p_path); |
| 171 | }; |
| 172 | |
| 173 | #endif // GDSCRIPT_EXTEND_PARSER_H |
| 174 | |