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 | |