1 | /**************************************************************************/ |
2 | /* text_editor.cpp */ |
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 | #include "text_editor.h" |
32 | |
33 | #include "core/io/json.h" |
34 | #include "core/os/keyboard.h" |
35 | #include "editor/editor_node.h" |
36 | #include "editor/editor_settings.h" |
37 | #include "scene/gui/menu_button.h" |
38 | |
39 | void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { |
40 | ERR_FAIL_COND(p_highlighter.is_null()); |
41 | |
42 | highlighters[p_highlighter->_get_name()] = p_highlighter; |
43 | highlighter_menu->add_radio_check_item(p_highlighter->_get_name()); |
44 | } |
45 | |
46 | void TextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { |
47 | ERR_FAIL_COND(p_highlighter.is_null()); |
48 | |
49 | HashMap<String, Ref<EditorSyntaxHighlighter>>::Iterator el = highlighters.begin(); |
50 | while (el) { |
51 | int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key); |
52 | highlighter_menu->set_item_checked(highlighter_index, el->value == p_highlighter); |
53 | ++el; |
54 | } |
55 | |
56 | CodeEdit *te = code_editor->get_text_editor(); |
57 | te->set_syntax_highlighter(p_highlighter); |
58 | } |
59 | |
60 | void TextEditor::_change_syntax_highlighter(int p_idx) { |
61 | set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]); |
62 | } |
63 | |
64 | void TextEditor::_load_theme_settings() { |
65 | code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); |
66 | } |
67 | |
68 | String TextEditor::get_name() { |
69 | String name; |
70 | |
71 | name = edited_res->get_path().get_file(); |
72 | if (name.is_empty()) { |
73 | // This appears for newly created built-in text_files before saving the scene. |
74 | name = TTR("[unsaved]" ); |
75 | } else if (edited_res->is_built_in()) { |
76 | const String &text_file_name = edited_res->get_name(); |
77 | if (!text_file_name.is_empty()) { |
78 | // If the built-in text_file has a custom resource name defined, |
79 | // display the built-in text_file name as follows: `ResourceName (scene_file.tscn)` |
80 | name = vformat("%s (%s)" , text_file_name, name.get_slice("::" , 0)); |
81 | } |
82 | } |
83 | |
84 | if (is_unsaved()) { |
85 | name += "(*)" ; |
86 | } |
87 | |
88 | return name; |
89 | } |
90 | |
91 | Ref<Texture2D> TextEditor::get_theme_icon() { |
92 | return EditorNode::get_singleton()->get_object_icon(edited_res.ptr(), "TextFile" ); |
93 | } |
94 | |
95 | Ref<Resource> TextEditor::get_edited_resource() const { |
96 | return edited_res; |
97 | } |
98 | |
99 | void TextEditor::set_edited_resource(const Ref<Resource> &p_res) { |
100 | ERR_FAIL_COND(edited_res.is_valid()); |
101 | ERR_FAIL_COND(p_res.is_null()); |
102 | |
103 | edited_res = p_res; |
104 | |
105 | Ref<TextFile> text_file = edited_res; |
106 | if (text_file != nullptr) { |
107 | code_editor->get_text_editor()->set_text(text_file->get_text()); |
108 | } |
109 | |
110 | Ref<JSON> json_file = edited_res; |
111 | if (json_file != nullptr) { |
112 | code_editor->get_text_editor()->set_text(json_file->get_parsed_text()); |
113 | } |
114 | |
115 | code_editor->get_text_editor()->clear_undo_history(); |
116 | code_editor->get_text_editor()->tag_saved_version(); |
117 | |
118 | emit_signal(SNAME("name_changed" )); |
119 | code_editor->update_line_and_column(); |
120 | } |
121 | |
122 | void TextEditor::enable_editor(Control *p_shortcut_context) { |
123 | if (editor_enabled) { |
124 | return; |
125 | } |
126 | |
127 | editor_enabled = true; |
128 | |
129 | _load_theme_settings(); |
130 | |
131 | _validate_script(); |
132 | |
133 | if (p_shortcut_context) { |
134 | for (int i = 0; i < edit_hb->get_child_count(); ++i) { |
135 | Control *c = cast_to<Control>(edit_hb->get_child(i)); |
136 | if (c) { |
137 | c->set_shortcut_context(p_shortcut_context); |
138 | } |
139 | } |
140 | } |
141 | } |
142 | |
143 | void TextEditor::add_callback(const String &p_function, PackedStringArray p_args) { |
144 | } |
145 | |
146 | void TextEditor::set_debugger_active(bool p_active) { |
147 | } |
148 | |
149 | Control *TextEditor::get_base_editor() const { |
150 | return code_editor->get_text_editor(); |
151 | } |
152 | |
153 | PackedInt32Array TextEditor::get_breakpoints() { |
154 | return PackedInt32Array(); |
155 | } |
156 | |
157 | void TextEditor::reload_text() { |
158 | ERR_FAIL_COND(edited_res.is_null()); |
159 | |
160 | CodeEdit *te = code_editor->get_text_editor(); |
161 | int column = te->get_caret_column(); |
162 | int row = te->get_caret_line(); |
163 | int h = te->get_h_scroll(); |
164 | int v = te->get_v_scroll(); |
165 | |
166 | Ref<TextFile> text_file = edited_res; |
167 | if (text_file != nullptr) { |
168 | te->set_text(text_file->get_text()); |
169 | } |
170 | |
171 | Ref<JSON> json_file = edited_res; |
172 | if (json_file != nullptr) { |
173 | te->set_text(json_file->get_parsed_text()); |
174 | } |
175 | |
176 | te->set_caret_line(row); |
177 | te->set_caret_column(column); |
178 | te->set_h_scroll(h); |
179 | te->set_v_scroll(v); |
180 | |
181 | te->tag_saved_version(); |
182 | |
183 | code_editor->update_line_and_column(); |
184 | _validate_script(); |
185 | } |
186 | |
187 | void TextEditor::_validate_script() { |
188 | emit_signal(SNAME("name_changed" )); |
189 | emit_signal(SNAME("edited_script_changed" )); |
190 | |
191 | Ref<JSON> json_file = edited_res; |
192 | if (json_file != nullptr) { |
193 | CodeEdit *te = code_editor->get_text_editor(); |
194 | |
195 | te->set_line_background_color(code_editor->get_error_pos().x, Color(0, 0, 0, 0)); |
196 | code_editor->set_error("" ); |
197 | |
198 | if (json_file->parse(te->get_text(), true) != OK) { |
199 | code_editor->set_error(json_file->get_error_message()); |
200 | code_editor->set_error_pos(json_file->get_error_line(), 0); |
201 | te->set_line_background_color(code_editor->get_error_pos().x, EDITOR_GET("text_editor/theme/highlighting/mark_color" )); |
202 | } |
203 | } |
204 | } |
205 | |
206 | void TextEditor::_update_bookmark_list() { |
207 | bookmarks_menu->clear(); |
208 | |
209 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark" ), BOOKMARK_TOGGLE); |
210 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks" ), BOOKMARK_REMOVE_ALL); |
211 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark" ), BOOKMARK_GOTO_NEXT); |
212 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark" ), BOOKMARK_GOTO_PREV); |
213 | |
214 | PackedInt32Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines(); |
215 | if (bookmark_list.size() == 0) { |
216 | return; |
217 | } |
218 | |
219 | bookmarks_menu->add_separator(); |
220 | |
221 | for (int i = 0; i < bookmark_list.size(); i++) { |
222 | String line = code_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges(); |
223 | // Limit the size of the line if too big. |
224 | if (line.length() > 50) { |
225 | line = line.substr(0, 50); |
226 | } |
227 | |
228 | bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\"" ); |
229 | bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); |
230 | } |
231 | } |
232 | |
233 | void TextEditor::_bookmark_item_pressed(int p_idx) { |
234 | if (p_idx < 4) { // Any item before the separator. |
235 | _edit_option(bookmarks_menu->get_item_id(p_idx)); |
236 | } else { |
237 | code_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); |
238 | } |
239 | } |
240 | |
241 | void TextEditor::apply_code() { |
242 | Ref<TextFile> text_file = edited_res; |
243 | if (text_file != nullptr) { |
244 | text_file->set_text(code_editor->get_text_editor()->get_text()); |
245 | } |
246 | |
247 | Ref<JSON> json_file = edited_res; |
248 | if (json_file != nullptr) { |
249 | json_file->parse(code_editor->get_text_editor()->get_text(), true); |
250 | } |
251 | code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); |
252 | } |
253 | |
254 | bool TextEditor::is_unsaved() { |
255 | const bool unsaved = |
256 | code_editor->get_text_editor()->get_version() != code_editor->get_text_editor()->get_saved_version() || |
257 | edited_res->get_path().is_empty(); // In memory. |
258 | return unsaved; |
259 | } |
260 | |
261 | Variant TextEditor::get_edit_state() { |
262 | return code_editor->get_edit_state(); |
263 | } |
264 | |
265 | void TextEditor::set_edit_state(const Variant &p_state) { |
266 | code_editor->set_edit_state(p_state); |
267 | |
268 | Dictionary state = p_state; |
269 | if (state.has("syntax_highlighter" )) { |
270 | int idx = highlighter_menu->get_item_idx_from_text(state["syntax_highlighter" ]); |
271 | if (idx >= 0) { |
272 | _change_syntax_highlighter(idx); |
273 | } |
274 | } |
275 | |
276 | ensure_focus(); |
277 | } |
278 | |
279 | Variant TextEditor::get_navigation_state() { |
280 | return code_editor->get_navigation_state(); |
281 | } |
282 | |
283 | void TextEditor::trim_trailing_whitespace() { |
284 | code_editor->trim_trailing_whitespace(); |
285 | } |
286 | |
287 | void TextEditor::insert_final_newline() { |
288 | code_editor->insert_final_newline(); |
289 | } |
290 | |
291 | void TextEditor::convert_indent() { |
292 | code_editor->get_text_editor()->convert_indent(); |
293 | } |
294 | |
295 | void TextEditor::tag_saved_version() { |
296 | code_editor->get_text_editor()->tag_saved_version(); |
297 | } |
298 | |
299 | void TextEditor::goto_line(int p_line, bool p_with_error) { |
300 | code_editor->goto_line(p_line); |
301 | } |
302 | |
303 | void TextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { |
304 | code_editor->goto_line_selection(p_line, p_begin, p_end); |
305 | } |
306 | |
307 | void TextEditor::set_executing_line(int p_line) { |
308 | code_editor->set_executing_line(p_line); |
309 | } |
310 | |
311 | void TextEditor::clear_executing_line() { |
312 | code_editor->clear_executing_line(); |
313 | } |
314 | |
315 | void TextEditor::ensure_focus() { |
316 | code_editor->get_text_editor()->grab_focus(); |
317 | } |
318 | |
319 | Vector<String> TextEditor::get_functions() { |
320 | return Vector<String>(); |
321 | } |
322 | |
323 | bool TextEditor::show_members_overview() { |
324 | return true; |
325 | } |
326 | |
327 | void TextEditor::update_settings() { |
328 | code_editor->update_editor_settings(); |
329 | } |
330 | |
331 | void TextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { |
332 | Variant args[1] = { this }; |
333 | const Variant *argp[] = { &args[0] }; |
334 | code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); |
335 | } |
336 | |
337 | Control *TextEditor::() { |
338 | return edit_hb; |
339 | } |
340 | |
341 | void TextEditor::() { |
342 | memdelete(edit_hb); |
343 | } |
344 | |
345 | void TextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { |
346 | code_editor->set_find_replace_bar(p_bar); |
347 | } |
348 | |
349 | void TextEditor::_edit_option(int p_op) { |
350 | CodeEdit *tx = code_editor->get_text_editor(); |
351 | |
352 | switch (p_op) { |
353 | case EDIT_UNDO: { |
354 | tx->undo(); |
355 | tx->call_deferred(SNAME("grab_focus" )); |
356 | } break; |
357 | case EDIT_REDO: { |
358 | tx->redo(); |
359 | tx->call_deferred(SNAME("grab_focus" )); |
360 | } break; |
361 | case EDIT_CUT: { |
362 | tx->cut(); |
363 | tx->call_deferred(SNAME("grab_focus" )); |
364 | } break; |
365 | case EDIT_COPY: { |
366 | tx->copy(); |
367 | tx->call_deferred(SNAME("grab_focus" )); |
368 | } break; |
369 | case EDIT_PASTE: { |
370 | tx->paste(); |
371 | tx->call_deferred(SNAME("grab_focus" )); |
372 | } break; |
373 | case EDIT_SELECT_ALL: { |
374 | tx->select_all(); |
375 | tx->call_deferred(SNAME("grab_focus" )); |
376 | } break; |
377 | case EDIT_MOVE_LINE_UP: { |
378 | code_editor->move_lines_up(); |
379 | } break; |
380 | case EDIT_MOVE_LINE_DOWN: { |
381 | code_editor->move_lines_down(); |
382 | } break; |
383 | case EDIT_INDENT: { |
384 | tx->indent_lines(); |
385 | } break; |
386 | case EDIT_UNINDENT: { |
387 | tx->unindent_lines(); |
388 | } break; |
389 | case EDIT_DELETE_LINE: { |
390 | code_editor->delete_lines(); |
391 | } break; |
392 | case EDIT_DUPLICATE_SELECTION: { |
393 | code_editor->duplicate_selection(); |
394 | } break; |
395 | case EDIT_TOGGLE_FOLD_LINE: { |
396 | int previous_line = -1; |
397 | for (int caret_idx : tx->get_caret_index_edit_order()) { |
398 | int line_idx = tx->get_caret_line(caret_idx); |
399 | if (line_idx != previous_line) { |
400 | tx->toggle_foldable_line(line_idx); |
401 | previous_line = line_idx; |
402 | } |
403 | } |
404 | tx->queue_redraw(); |
405 | } break; |
406 | case EDIT_FOLD_ALL_LINES: { |
407 | tx->fold_all_lines(); |
408 | tx->queue_redraw(); |
409 | } break; |
410 | case EDIT_UNFOLD_ALL_LINES: { |
411 | tx->unfold_all_lines(); |
412 | tx->queue_redraw(); |
413 | } break; |
414 | case EDIT_TRIM_TRAILING_WHITESAPCE: { |
415 | trim_trailing_whitespace(); |
416 | } break; |
417 | case EDIT_CONVERT_INDENT_TO_SPACES: { |
418 | tx->set_indent_using_spaces(true); |
419 | convert_indent(); |
420 | } break; |
421 | case EDIT_CONVERT_INDENT_TO_TABS: { |
422 | tx->set_indent_using_spaces(false); |
423 | convert_indent(); |
424 | } break; |
425 | case EDIT_TO_UPPERCASE: { |
426 | _convert_case(CodeTextEditor::UPPER); |
427 | } break; |
428 | case EDIT_TO_LOWERCASE: { |
429 | _convert_case(CodeTextEditor::LOWER); |
430 | } break; |
431 | case EDIT_CAPITALIZE: { |
432 | _convert_case(CodeTextEditor::CAPITALIZE); |
433 | } break; |
434 | case EDIT_TOGGLE_WORD_WRAP: { |
435 | TextEdit::LineWrappingMode wrap = code_editor->get_text_editor()->get_line_wrapping_mode(); |
436 | code_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY); |
437 | } break; |
438 | case SEARCH_FIND: { |
439 | code_editor->get_find_replace_bar()->popup_search(); |
440 | } break; |
441 | case SEARCH_FIND_NEXT: { |
442 | code_editor->get_find_replace_bar()->search_next(); |
443 | } break; |
444 | case SEARCH_FIND_PREV: { |
445 | code_editor->get_find_replace_bar()->search_prev(); |
446 | } break; |
447 | case SEARCH_REPLACE: { |
448 | code_editor->get_find_replace_bar()->popup_replace(); |
449 | } break; |
450 | case SEARCH_IN_FILES: { |
451 | String selected_text = code_editor->get_text_editor()->get_selected_text(); |
452 | |
453 | // Yep, because it doesn't make sense to instance this dialog for every single script open... |
454 | // So this will be delegated to the ScriptEditor. |
455 | emit_signal(SNAME("search_in_files_requested" ), selected_text); |
456 | } break; |
457 | case REPLACE_IN_FILES: { |
458 | String selected_text = code_editor->get_text_editor()->get_selected_text(); |
459 | |
460 | emit_signal(SNAME("replace_in_files_requested" ), selected_text); |
461 | } break; |
462 | case SEARCH_GOTO_LINE: { |
463 | goto_line_dialog->popup_find_line(tx); |
464 | } break; |
465 | case BOOKMARK_TOGGLE: { |
466 | code_editor->toggle_bookmark(); |
467 | } break; |
468 | case BOOKMARK_GOTO_NEXT: { |
469 | code_editor->goto_next_bookmark(); |
470 | } break; |
471 | case BOOKMARK_GOTO_PREV: { |
472 | code_editor->goto_prev_bookmark(); |
473 | } break; |
474 | case BOOKMARK_REMOVE_ALL: { |
475 | code_editor->remove_all_bookmarks(); |
476 | } break; |
477 | } |
478 | } |
479 | |
480 | void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { |
481 | code_editor->convert_case(p_case); |
482 | } |
483 | |
484 | ScriptEditorBase *TextEditor::create_editor(const Ref<Resource> &p_resource) { |
485 | if (Object::cast_to<TextFile>(*p_resource) || Object::cast_to<JSON>(*p_resource)) { |
486 | return memnew(TextEditor); |
487 | } |
488 | return nullptr; |
489 | } |
490 | |
491 | void TextEditor::register_editor() { |
492 | ScriptEditor::register_create_script_editor_function(create_editor); |
493 | } |
494 | |
495 | void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { |
496 | Ref<InputEventMouseButton> mb = ev; |
497 | |
498 | if (mb.is_valid()) { |
499 | if (mb->get_button_index() == MouseButton::RIGHT) { |
500 | CodeEdit *tx = code_editor->get_text_editor(); |
501 | |
502 | Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); |
503 | int row = pos.y; |
504 | int col = pos.x; |
505 | |
506 | tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click" )); |
507 | bool can_fold = tx->can_fold_line(row); |
508 | bool is_folded = tx->is_line_folded(row); |
509 | |
510 | if (tx->is_move_caret_on_right_click_enabled()) { |
511 | tx->remove_secondary_carets(); |
512 | if (tx->has_selection()) { |
513 | int from_line = tx->get_selection_from_line(); |
514 | int to_line = tx->get_selection_to_line(); |
515 | int from_column = tx->get_selection_from_column(); |
516 | int to_column = tx->get_selection_to_column(); |
517 | |
518 | if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { |
519 | // Right click is outside the selected text. |
520 | tx->deselect(); |
521 | } |
522 | } |
523 | if (!tx->has_selection()) { |
524 | tx->set_caret_line(row, true, false); |
525 | tx->set_caret_column(col); |
526 | } |
527 | } |
528 | |
529 | if (!mb->is_pressed()) { |
530 | _make_context_menu(tx->has_selection(), can_fold, is_folded, get_local_mouse_position()); |
531 | } |
532 | } |
533 | } |
534 | |
535 | Ref<InputEventKey> k = ev; |
536 | if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu" , true)) { |
537 | CodeEdit *tx = code_editor->get_text_editor(); |
538 | int line = tx->get_caret_line(0); |
539 | tx->adjust_viewport_to_caret(0); |
540 | _make_context_menu(tx->has_selection(0), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos(0))); |
541 | context_menu->grab_focus(); |
542 | } |
543 | } |
544 | |
545 | void TextEditor::() { |
546 | const CodeEdit *tx = code_editor->get_text_editor(); |
547 | PopupMenu * = edit_menu->get_popup(); |
548 | popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo()); |
549 | popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo()); |
550 | } |
551 | |
552 | void TextEditor::(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position) { |
553 | context_menu->clear(); |
554 | if (p_selection) { |
555 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut" ), EDIT_CUT); |
556 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy" ), EDIT_COPY); |
557 | } |
558 | |
559 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste" ), EDIT_PASTE); |
560 | context_menu->add_separator(); |
561 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all" ), EDIT_SELECT_ALL); |
562 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo" ), EDIT_UNDO); |
563 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo" ), EDIT_REDO); |
564 | context_menu->add_separator(); |
565 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent" ), EDIT_INDENT); |
566 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent" ), EDIT_UNINDENT); |
567 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark" ), BOOKMARK_TOGGLE); |
568 | |
569 | if (p_selection) { |
570 | context_menu->add_separator(); |
571 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase" ), EDIT_TO_UPPERCASE); |
572 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase" ), EDIT_TO_LOWERCASE); |
573 | } |
574 | if (p_can_fold || p_is_folded) { |
575 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line" ), EDIT_TOGGLE_FOLD_LINE); |
576 | } |
577 | |
578 | const CodeEdit *tx = code_editor->get_text_editor(); |
579 | context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); |
580 | context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); |
581 | |
582 | context_menu->set_position(get_screen_position() + p_position); |
583 | context_menu->reset_size(); |
584 | context_menu->popup(); |
585 | } |
586 | |
587 | void TextEditor::update_toggle_scripts_button() { |
588 | code_editor->update_toggle_scripts_button(); |
589 | } |
590 | |
591 | TextEditor::TextEditor() { |
592 | code_editor = memnew(CodeTextEditor); |
593 | add_child(code_editor); |
594 | code_editor->add_theme_constant_override("separation" , 0); |
595 | code_editor->connect("load_theme_settings" , callable_mp(this, &TextEditor::_load_theme_settings)); |
596 | code_editor->connect("validate_script" , callable_mp(this, &TextEditor::_validate_script)); |
597 | code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); |
598 | code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
599 | code_editor->show_toggle_scripts_button(); |
600 | |
601 | update_settings(); |
602 | |
603 | code_editor->get_text_editor()->set_context_menu_enabled(false); |
604 | code_editor->get_text_editor()->connect("gui_input" , callable_mp(this, &TextEditor::_text_edit_gui_input)); |
605 | |
606 | context_menu = memnew(PopupMenu); |
607 | add_child(context_menu); |
608 | context_menu->connect("id_pressed" , callable_mp(this, &TextEditor::_edit_option)); |
609 | |
610 | edit_hb = memnew(HBoxContainer); |
611 | |
612 | search_menu = memnew(MenuButton); |
613 | search_menu->set_shortcut_context(this); |
614 | edit_hb->add_child(search_menu); |
615 | search_menu->set_text(TTR("Search" )); |
616 | search_menu->set_switch_on_hover(true); |
617 | search_menu->get_popup()->connect("id_pressed" , callable_mp(this, &TextEditor::_edit_option)); |
618 | |
619 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find" ), SEARCH_FIND); |
620 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next" ), SEARCH_FIND_NEXT); |
621 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous" ), SEARCH_FIND_PREV); |
622 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace" ), SEARCH_REPLACE); |
623 | search_menu->get_popup()->add_separator(); |
624 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files" ), SEARCH_IN_FILES); |
625 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files" ), REPLACE_IN_FILES); |
626 | |
627 | edit_menu = memnew(MenuButton); |
628 | edit_menu->set_shortcut_context(this); |
629 | edit_hb->add_child(edit_menu); |
630 | edit_menu->set_text(TTR("Edit" )); |
631 | edit_menu->set_switch_on_hover(true); |
632 | edit_menu->connect("about_to_popup" , callable_mp(this, &TextEditor::_prepare_edit_menu)); |
633 | edit_menu->get_popup()->connect("id_pressed" , callable_mp(this, &TextEditor::_edit_option)); |
634 | |
635 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo" ), EDIT_UNDO); |
636 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo" ), EDIT_REDO); |
637 | edit_menu->get_popup()->add_separator(); |
638 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut" ), EDIT_CUT); |
639 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy" ), EDIT_COPY); |
640 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste" ), EDIT_PASTE); |
641 | edit_menu->get_popup()->add_separator(); |
642 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all" ), EDIT_SELECT_ALL); |
643 | edit_menu->get_popup()->add_separator(); |
644 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up" ), EDIT_MOVE_LINE_UP); |
645 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down" ), EDIT_MOVE_LINE_DOWN); |
646 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent" ), EDIT_INDENT); |
647 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent" ), EDIT_UNINDENT); |
648 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line" ), EDIT_DELETE_LINE); |
649 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line" ), EDIT_TOGGLE_FOLD_LINE); |
650 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines" ), EDIT_FOLD_ALL_LINES); |
651 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines" ), EDIT_UNFOLD_ALL_LINES); |
652 | edit_menu->get_popup()->add_separator(); |
653 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection" ), EDIT_DUPLICATE_SELECTION); |
654 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap" ), EDIT_TOGGLE_WORD_WRAP); |
655 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace" ), EDIT_TRIM_TRAILING_WHITESAPCE); |
656 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces" ), EDIT_CONVERT_INDENT_TO_SPACES); |
657 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs" ), EDIT_CONVERT_INDENT_TO_TABS); |
658 | |
659 | edit_menu->get_popup()->add_separator(); |
660 | PopupMenu *convert_case = memnew(PopupMenu); |
661 | convert_case->set_name("convert_case" ); |
662 | edit_menu->get_popup()->add_child(convert_case); |
663 | edit_menu->get_popup()->add_submenu_item(TTR("Convert Case" ), "convert_case" ); |
664 | convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase" , TTR("Uppercase" )), EDIT_TO_UPPERCASE); |
665 | convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase" , TTR("Lowercase" )), EDIT_TO_LOWERCASE); |
666 | convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize" , TTR("Capitalize" )), EDIT_CAPITALIZE); |
667 | convert_case->connect("id_pressed" , callable_mp(this, &TextEditor::_edit_option)); |
668 | |
669 | highlighter_menu = memnew(PopupMenu); |
670 | highlighter_menu->set_name("highlighter_menu" ); |
671 | edit_menu->get_popup()->add_child(highlighter_menu); |
672 | edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter" ), "highlighter_menu" ); |
673 | highlighter_menu->connect("id_pressed" , callable_mp(this, &TextEditor::_change_syntax_highlighter)); |
674 | |
675 | Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter; |
676 | plain_highlighter.instantiate(); |
677 | add_syntax_highlighter(plain_highlighter); |
678 | |
679 | Ref<EditorStandardSyntaxHighlighter> highlighter; |
680 | highlighter.instantiate(); |
681 | add_syntax_highlighter(highlighter); |
682 | set_syntax_highlighter(plain_highlighter); |
683 | |
684 | MenuButton * = memnew(MenuButton); |
685 | goto_menu->set_shortcut_context(this); |
686 | edit_hb->add_child(goto_menu); |
687 | goto_menu->set_text(TTR("Go To" )); |
688 | goto_menu->set_switch_on_hover(true); |
689 | goto_menu->get_popup()->connect("id_pressed" , callable_mp(this, &TextEditor::_edit_option)); |
690 | |
691 | goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line" ), SEARCH_GOTO_LINE); |
692 | goto_menu->get_popup()->add_separator(); |
693 | |
694 | bookmarks_menu = memnew(PopupMenu); |
695 | bookmarks_menu->set_name("Bookmarks" ); |
696 | goto_menu->get_popup()->add_child(bookmarks_menu); |
697 | goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks" ), "Bookmarks" ); |
698 | _update_bookmark_list(); |
699 | bookmarks_menu->connect("about_to_popup" , callable_mp(this, &TextEditor::_update_bookmark_list)); |
700 | bookmarks_menu->connect("index_pressed" , callable_mp(this, &TextEditor::_bookmark_item_pressed)); |
701 | |
702 | goto_line_dialog = memnew(GotoLineDialog); |
703 | add_child(goto_line_dialog); |
704 | } |
705 | |
706 | TextEditor::~TextEditor() { |
707 | highlighters.clear(); |
708 | } |
709 | |
710 | void TextEditor::validate() { |
711 | this->code_editor->validate_script(); |
712 | } |
713 | |