diff options
-rw-r--r-- | doc/classes/CodeEdit.xml | 166 | ||||
-rw-r--r-- | doc/classes/TextEdit.xml | 30 | ||||
-rw-r--r-- | editor/code_editor.cpp | 25 | ||||
-rw-r--r-- | editor/dependency_editor.cpp | 4 | ||||
-rw-r--r-- | editor/plugins/script_text_editor.cpp | 29 | ||||
-rw-r--r-- | editor/plugins/shader_editor_plugin.cpp | 6 | ||||
-rw-r--r-- | editor/plugins/visual_shader_editor_plugin.cpp | 4 | ||||
-rw-r--r-- | scene/gui/code_edit.cpp | 479 | ||||
-rw-r--r-- | scene/gui/code_edit.h | 55 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 376 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 37 | ||||
-rw-r--r-- | scene/resources/default_theme/default_theme.cpp | 3 |
12 files changed, 738 insertions, 476 deletions
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index 6a3f38f51e..0b060ec277 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CodeEdit" inherits="TextEdit" version="4.0"> <brief_description> + Multiline text control intended for editing code. </brief_description> <description> + CodeEdit is a specialised [TextEdit] designed for editing plain text code files. It contains a bunch of features commonly found in code editors such as line numbers, line folding, code completion, indent management and string / comment management. [b]Note[/b]: By default [CodeEdit] always use left-to-right text direction to correctly display source code. </description> <tutorials> @@ -30,6 +32,15 @@ Override this method to define what happens when the user requests code completion. If [code]force[/code] is true, any checks should be bypassed. </description> </method> + <method name="add_auto_brace_completion_pair"> + <return type="void" /> + <argument index="0" name="start_key" type="String" /> + <argument index="1" name="end_key" type="String" /> + <description> + Adds a brace pair. + Both the start and end keys must be symbols. Only the start key has to be unique. + </description> + </method> <method name="add_code_completion_option"> <return type="void" /> <argument index="0" name="type" type="int" enum="CodeEdit.CodeCompletionKind" /> @@ -81,11 +92,13 @@ <method name="clear_bookmarked_lines"> <return type="void" /> <description> + Clears all bookmarked lines. </description> </method> <method name="clear_breakpointed_lines"> <return type="void" /> <description> + Clears all breakpointed lines. </description> </method> <method name="clear_comment_delimiters"> @@ -97,6 +110,7 @@ <method name="clear_executing_lines"> <return type="void" /> <description> + Clears all executed lines. </description> </method> <method name="clear_string_delimiters"> @@ -137,14 +151,23 @@ Folds the given line, if possible (see [method can_fold_line]). </description> </method> + <method name="get_auto_brace_completion_close_key" qualifiers="const"> + <return type="String" /> + <argument index="0" name="open_key" type="String" /> + <description> + Gets the matching auto brace close key for [code]open_key[/code]. + </description> + </method> <method name="get_bookmarked_lines" qualifiers="const"> <return type="Array" /> <description> + Gets all bookmarked lines. </description> </method> <method name="get_breakpointed_lines" qualifiers="const"> <return type="Array" /> <description> + Gets all breakpointed lines. </description> </method> <method name="get_code_completion_option" qualifiers="const"> @@ -205,6 +228,7 @@ <method name="get_executing_lines" qualifiers="const"> <return type="Array" /> <description> + Gets all executing lines. </description> </method> <method name="get_folded_lines" qualifiers="const"> @@ -219,6 +243,26 @@ Returns the full text with char [code]0xFFFF[/code] at the caret location. </description> </method> + <method name="get_text_for_symbol_lookup"> + <return type="String" /> + <description> + Returns the full text with char [code]0xFFFF[/code] at the cursor location. + </description> + </method> + <method name="has_auto_brace_completion_close_key" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="close_key" type="String" /> + <description> + Returns [code]true[/code] if close key [code]close_key[/code] exists. + </description> + </method> + <method name="has_auto_brace_completion_open_key" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="open_key" type="String" /> + <description> + Returns [code]true[/code] if open key [code]open_key[/code] exists. + </description> + </method> <method name="has_comment_delimiter" qualifiers="const"> <return type="bool" /> <argument index="0" name="start_key" type="String" /> @@ -259,18 +303,21 @@ <return type="bool" /> <argument index="0" name="line" type="int" /> <description> + Returns whether the line at the specified index is bookmarked or not. </description> </method> <method name="is_line_breakpointed" qualifiers="const"> <return type="bool" /> <argument index="0" name="line" type="int" /> <description> + Returns whether the line at the specified index is breakpointed or not. </description> </method> <method name="is_line_executing" qualifiers="const"> <return type="bool" /> <argument index="0" name="line" type="int" /> <description> + Returns whether the line at the specified index is marked as executing or not. </description> </method> <method name="is_line_folded" qualifiers="const"> @@ -327,6 +374,7 @@ <argument index="0" name="line" type="int" /> <argument index="1" name="bookmarked" type="bool" /> <description> + Sets the line as bookmarked. </description> </method> <method name="set_line_as_breakpoint"> @@ -334,6 +382,7 @@ <argument index="0" name="line" type="int" /> <argument index="1" name="breakpointed" type="bool" /> <description> + Sets the line as breakpointed. </description> </method> <method name="set_line_as_executing"> @@ -341,6 +390,14 @@ <argument index="0" name="line" type="int" /> <argument index="1" name="executing" type="bool" /> <description> + Sets the line as executing. + </description> + </method> + <method name="set_symbol_lookup_word_as_valid"> + <return type="void" /> + <argument index="0" name="valid" type="bool" /> + <description> + Sets the symbol emitted by [signal symbol_validate] as a valid lookup. </description> </method> <method name="toggle_foldable_line"> @@ -353,6 +410,7 @@ <method name="unfold_all_lines"> <return type="void" /> <description> + Unfolds all lines, folded or not. </description> </method> <method name="unfold_line"> @@ -378,6 +436,15 @@ </method> </methods> <members> + <member name="auto_brace_completion_enabled" type="bool" setter="set_auto_brace_completion_enabled" getter="is_auto_brace_completion_enabled" default="false"> + Sets whether brace pairs should be autocompleted. + </member> + <member name="auto_brace_completion_highlight_matching" type="bool" setter="set_highlight_matching_braces_enabled" getter="is_highlight_matching_braces_enabled" default="false"> + Highlight mismatching brace pairs. + </member> + <member name="auto_brace_completion_pairs" type="Dictionary" setter="set_auto_brace_completion_pairs" getter="get_auto_brace_completion_pairs" default="{"\"": "\"","'": "'","(": ")","[": "]","{": "}"}"> + Sets the brace pairs to be autocompleted. + </member> <member name="code_completion_enabled" type="bool" setter="set_code_completion_enabled" getter="is_code_completion_enabled" default="false"> Sets whether code completion is allowed. </member> @@ -387,18 +454,26 @@ <member name="delimiter_comments" type="String[]" setter="set_comment_delimiters" getter="get_comment_delimiters" default="[]"> Sets the comment delimiters. All existing comment delimiters will be removed. </member> - <member name="delimiter_strings" type="String[]" setter="set_string_delimiters" getter="get_string_delimiters" default="[]"> + <member name="delimiter_strings" type="String[]" setter="set_string_delimiters" getter="get_string_delimiters" default="["' '", "\" \""]"> Sets the string delimiters. All existing string delimiters will be removed. </member> - <member name="draw_bookmarks" type="bool" setter="set_draw_bookmarks_gutter" getter="is_drawing_bookmarks_gutter" default="false"> + <member name="gutters_draw_bookmarks" type="bool" setter="set_draw_bookmarks_gutter" getter="is_drawing_bookmarks_gutter" default="false"> + Sets if bookmarked should be drawn in the gutter. This gutter is shared with breakpoints and executing lines. </member> - <member name="draw_breakpoints_gutter" type="bool" setter="set_draw_breakpoints_gutter" getter="is_drawing_breakpoints_gutter" default="false"> + <member name="gutters_draw_breakpoints_gutter" type="bool" setter="set_draw_breakpoints_gutter" getter="is_drawing_breakpoints_gutter" default="false"> + Sets if breakpoints should be drawn in the gutter. This gutter is shared with bookmarks and executing lines. </member> - <member name="draw_executing_lines" type="bool" setter="set_draw_executing_lines_gutter" getter="is_drawing_executing_lines_gutter" default="false"> + <member name="gutters_draw_executing_lines" type="bool" setter="set_draw_executing_lines_gutter" getter="is_drawing_executing_lines_gutter" default="false"> + Sets if executing lines should be marked in the gutter. This gutter is shared with breakpoints and bookmarks lines. </member> - <member name="draw_fold_gutter" type="bool" setter="set_draw_fold_gutter" getter="is_drawing_fold_gutter" default="false"> + <member name="gutters_draw_fold_gutter" type="bool" setter="set_draw_fold_gutter" getter="is_drawing_fold_gutter" default="false"> + Sets if foldable lines icons should be drawn in the gutter. </member> - <member name="draw_line_numbers" type="bool" setter="set_draw_line_numbers" getter="is_draw_line_numbers_enabled" default="false"> + <member name="gutters_draw_line_numbers" type="bool" setter="set_draw_line_numbers" getter="is_draw_line_numbers_enabled" default="false"> + Sets if line numbers should be drawn in the gutter. + </member> + <member name="gutters_zero_pad_line_numbers" type="bool" setter="set_line_numbers_zero_padded" getter="is_line_numbers_zero_padded" default="false"> + Sets if line numbers drawn in the gutter are zero padded. </member> <member name="indent_automatic" type="bool" setter="set_auto_indent_enabled" getter="is_auto_indent_enabled" default="false"> Sets whether automatic indent are enabled, this will add an extra indent if a prefix or brace is found. @@ -413,18 +488,23 @@ Use spaces instead of tabs for indentation. </member> <member name="layout_direction" type="int" setter="set_layout_direction" getter="get_layout_direction" override="true" enum="Control.LayoutDirection" default="2" /> - <member name="line_folding" type="bool" setter="set_line_folding_enabled" getter="is_line_folding_enabled" default="true"> + <member name="line_folding" type="bool" setter="set_line_folding_enabled" getter="is_line_folding_enabled" default="false"> Sets whether line folding is allowed. </member> + <member name="line_length_guidelines" type="int[]" setter="set_line_length_guidelines" getter="get_line_length_guidelines" default="[]"> + Draws vertical lines at the provided columns. The first entry is considered a main hard guideline and is draw more prominently + </member> <member name="structured_text_bidi_override_options" type="Array" setter="set_structured_text_bidi_override_options" getter="get_structured_text_bidi_override_options" override="true" default="[]" /> - <member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" override="true" enum="Control.TextDirection" default="1" /> - <member name="zero_pad_line_numbers" type="bool" setter="set_line_numbers_zero_padded" getter="is_line_numbers_zero_padded" default="false"> + <member name="symbol_lookup_on_click" type="bool" setter="set_symbol_lookup_on_click_enabled" getter="is_symbol_lookup_on_click_enabled" default="false"> + Set when a validated word from [signal symbol_validate] is clicked, the [signal symbol_lookup] should be emitted. </member> + <member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" override="true" enum="Control.TextDirection" default="1" /> </members> <signals> <signal name="breakpoint_toggled"> <argument index="0" name="line" type="int" /> <description> + Emitted when a breakpoint is added or removed from a line. If the line is moved via backspace a removed is emitted at the old line. </description> </signal> <signal name="request_code_completion"> @@ -432,114 +512,176 @@ Emitted when the user requests code completion. </description> </signal> + <signal name="symbol_lookup"> + <argument index="0" name="symbol" type="String" /> + <argument index="1" name="line" type="int" /> + <argument index="2" name="column" type="int" /> + <description> + Emitted when the user has clicked on a valid symbol. + </description> + </signal> + <signal name="symbol_validate"> + <argument index="0" name="symbol" type="String" /> + <description> + Emitted when the user hovers over a symbol. The symbol should be validated and responded to, by calling [method set_symbol_lookup_word_as_valid]. + </description> + </signal> </signals> <constants> <constant name="KIND_CLASS" value="0" enum="CodeCompletionKind"> + Marks the option as a class. </constant> <constant name="KIND_FUNCTION" value="1" enum="CodeCompletionKind"> + Marks the option as a function. </constant> <constant name="KIND_SIGNAL" value="2" enum="CodeCompletionKind"> + Marks the option as a Godot signal. </constant> <constant name="KIND_VARIABLE" value="3" enum="CodeCompletionKind"> + Marks the option as a variable. </constant> <constant name="KIND_MEMBER" value="4" enum="CodeCompletionKind"> + Marks the option as a member. </constant> <constant name="KIND_ENUM" value="5" enum="CodeCompletionKind"> + Marks the option as a enum entry. </constant> <constant name="KIND_CONSTANT" value="6" enum="CodeCompletionKind"> + Marks the option as a constant. </constant> <constant name="KIND_NODE_PATH" value="7" enum="CodeCompletionKind"> + Marks the option as a Godot node path. </constant> <constant name="KIND_FILE_PATH" value="8" enum="CodeCompletionKind"> + Marks the option as a file path. </constant> <constant name="KIND_PLAIN_TEXT" value="9" enum="CodeCompletionKind"> + Marks the option as unclassified or plain text. </constant> </constants> <theme_items> <theme_item name="background_color" type="Color" default="Color(0, 0, 0, 0)"> + Sets the background [Color]. </theme_item> <theme_item name="bookmark" type="Texture2D"> + Sets a custom [Texture2D] to draw in the bookmark gutter for bookmarked lines. </theme_item> <theme_item name="bookmark_color" type="Color" default="Color(0.5, 0.64, 1, 0.8)"> + [Color] of the bookmark icon for bookmarked lines. </theme_item> <theme_item name="brace_mismatch_color" type="Color" default="Color(1, 0.2, 0.2, 1)"> + [Color] of the text to highlight mismatched braces. </theme_item> <theme_item name="breakpoint" type="Texture2D"> + Sets a custom [Texture2D] to draw in the breakpoint gutter for breakpointed lines. </theme_item> <theme_item name="breakpoint_color" type="Color" default="Color(0.9, 0.29, 0.3, 1)"> + [Color] of the breakpoint icon for bookmarked lines. </theme_item> <theme_item name="can_fold" type="Texture2D"> + Sets a custom [Texture2D] to draw in the line folding gutter when a line can be folded. </theme_item> <theme_item name="caret_background_color" type="Color" default="Color(0, 0, 0, 1)"> + [Color] of the text behind the caret when block caret is enabled. </theme_item> <theme_item name="caret_color" type="Color" default="Color(0.88, 0.88, 0.88, 1)"> + [Color] of the caret. </theme_item> <theme_item name="code_folding_color" type="Color" default="Color(0.8, 0.8, 0.8, 0.8)"> + [Color] for all icons related to line folding. </theme_item> <theme_item name="completion" type="StyleBox"> + [StyleBox] for the code completion popup. </theme_item> <theme_item name="completion_background_color" type="Color" default="Color(0.17, 0.16, 0.2, 1)"> + Sets the background [Color] for the code completion popup. </theme_item> <theme_item name="completion_existing_color" type="Color" default="Color(0.87, 0.87, 0.87, 0.13)"> + Background highlight [Color] for matching text in code completion options. </theme_item> <theme_item name="completion_font_color" type="Color" default="Color(0.67, 0.67, 0.67, 1)"> + Font [Color] for the code completion popup. </theme_item> <theme_item name="completion_lines" type="int" default="7"> + Max number of options to display in the code completion popup at any one time. </theme_item> <theme_item name="completion_max_width" type="int" default="50"> + Max width of options in the code completion popup. Options longer then this will be cut off. </theme_item> <theme_item name="completion_scroll_color" type="Color" default="Color(1, 1, 1, 1)"> + [Color] of the scrollbar in the code completion popup. </theme_item> <theme_item name="completion_scroll_width" type="int" default="3"> + Width of the scrollbar in the code completion popup. </theme_item> <theme_item name="completion_selected_color" type="Color" default="Color(0.26, 0.26, 0.27, 1)"> + Background highlight [Color] for the current selected option item in the code completion popup. </theme_item> <theme_item name="current_line_color" type="Color" default="Color(0.25, 0.25, 0.26, 0.8)"> + Background [Color] of the line containing the caret. </theme_item> <theme_item name="executing_line" type="Texture2D"> + Icon to draw in the executing gutter for executing lines. </theme_item> <theme_item name="executing_line_color" type="Color" default="Color(0.98, 0.89, 0.27, 1)"> + [Color] of the executing icon for executing lines. </theme_item> <theme_item name="focus" type="StyleBox"> + Sets the [StyleBox] when in focus. </theme_item> <theme_item name="folded" type="Texture2D"> + Sets a custom [Texture2D] to draw in the line folding gutter when a line is folded and can be unfolded. </theme_item> <theme_item name="folded_eol_icon" type="Texture2D"> + Sets a custom [Texture2D] to draw at the end of a folded line. </theme_item> <theme_item name="font" type="Font"> + Sets the default [Font]. </theme_item> <theme_item name="font_color" type="Color" default="Color(0.88, 0.88, 0.88, 1)"> + Sets the font [Color]. </theme_item> <theme_item name="font_outline_color" type="Color" default="Color(1, 1, 1, 1)"> The tint of text outline of the [CodeEdit]. </theme_item> <theme_item name="font_readonly_color" type="Color" default="Color(0.88, 0.88, 0.88, 0.5)"> + Sets the font [Color] when [member readonly] is enabled. </theme_item> <theme_item name="font_selected_color" type="Color" default="Color(0, 0, 0, 1)"> + Sets the [Color] of the selected text. [member override_selected_font_color] has to be enabled. </theme_item> <theme_item name="font_size" type="int"> - Font size of the [CodeEdit]'s text. + Sets default font size. + </theme_item> + <theme_item name="line_length_guideline_color" type="Color" default="Color(0.3, 0.5, 0.8, 0.1)"> + [Color] of the main line length guideline, secondary guidelines will have 50% alpha applied. </theme_item> <theme_item name="line_number_color" type="Color" default="Color(0.67, 0.67, 0.67, 0.4)"> + Sets the [Color] of line numbers. </theme_item> <theme_item name="line_spacing" type="int" default="4"> + Sets the spacing between the lines. </theme_item> <theme_item name="normal" type="StyleBox"> + Sets the [StyleBox]. </theme_item> <theme_item name="outline_size" type="int" default="0"> The size of the text outline. </theme_item> <theme_item name="read_only" type="StyleBox"> - </theme_item> - <theme_item name="safe_line_number_color" type="Color" default="Color(0.67, 0.78, 0.67, 0.6)"> + Sets the [StyleBox] when [member readonly] is enabled. </theme_item> <theme_item name="selection_color" type="Color" default="Color(0.49, 0.49, 0.49, 1)"> + Sets the highlight [Color] of text selections. </theme_item> <theme_item name="space" type="Texture2D"> + Sets a custom [Texture2D] for space text characters. </theme_item> <theme_item name="tab" type="Texture2D"> + Sets a custom [Texture2D] for tab text characters. </theme_item> <theme_item name="word_highlighted_color" type="Color" default="Color(0.8, 0.9, 0.9, 0.15)"> + Sets the highlight [Color] of multiple occurrences. [member highlight_all_occurrences] has to be enabled. </theme_item> </theme_items> </class> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 5444721e10..5d62050a94 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -16,6 +16,12 @@ A virtual method that is called whenever backspace is triggered. </description> </method> + <method name="_handle_unicode_input" qualifiers="virtual"> + <return type="void" /> + <argument index="0" name="unicode" type="int" /> + <description> + </description> + </method> <method name="add_gutter"> <return type="void" /> <argument index="0" name="at" type="int" default="-1" /> @@ -256,6 +262,11 @@ Returns the [TextEdit]'s' tab size. </description> </method> + <method name="get_total_gutter_width" qualifiers="const"> + <return type="int" /> + <description> + </description> + </method> <method name="get_visible_line_count" qualifiers="const"> <return type="int" /> <description> @@ -281,6 +292,11 @@ Returns [code]true[/code] if the caret is visible on the screen. </description> </method> + <method name="is_dragging_cursor" qualifiers="const"> + <return type="bool" /> + <description> + </description> + </method> <method name="is_gutter_clickable" qualifiers="const"> <return type="bool" /> <argument index="0" name="gutter" type="int" /> @@ -654,18 +670,6 @@ <description> </description> </signal> - <signal name="symbol_lookup"> - <argument index="0" name="symbol" type="String" /> - <argument index="1" name="row" type="int" /> - <argument index="2" name="column" type="int" /> - <description> - </description> - </signal> - <signal name="symbol_validate"> - <argument index="0" name="symbol" type="String" /> - <description> - </description> - </signal> <signal name="text_changed"> <description> Emitted when the text changes. @@ -790,8 +794,6 @@ <theme_item name="background_color" type="Color" default="Color(0, 0, 0, 0)"> Sets the background [Color] of this [TextEdit]. </theme_item> - <theme_item name="brace_mismatch_color" type="Color" default="Color(1, 0.2, 0.2, 1)"> - </theme_item> <theme_item name="caret_background_color" type="Color" default="Color(0, 0, 0, 1)"> </theme_item> <theme_item name="caret_color" type="Color" default="Color(0.88, 0.88, 0.88, 1)"> diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 285084a72b..eeb99b3677 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -951,14 +951,20 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_line_folding_enabled(EditorSettings::get_singleton()->get("text_editor/appearance/code_folding")); text_editor->set_draw_fold_gutter(EditorSettings::get_singleton()->get("text_editor/appearance/code_folding")); text_editor->set_wrap_enabled(EditorSettings::get_singleton()->get("text_editor/appearance/word_wrap")); - text_editor->set_show_line_length_guidelines(EditorSettings::get_singleton()->get("text_editor/appearance/show_line_length_guidelines")); - text_editor->set_line_length_guideline_soft_column(EditorSettings::get_singleton()->get("text_editor/appearance/line_length_guideline_soft_column")); - text_editor->set_line_length_guideline_hard_column(EditorSettings::get_singleton()->get("text_editor/appearance/line_length_guideline_hard_column")); text_editor->set_scroll_pass_end_of_file(EditorSettings::get_singleton()->get("text_editor/cursor/scroll_past_end_of_file")); text_editor->cursor_set_block_mode(EditorSettings::get_singleton()->get("text_editor/cursor/block_caret")); text_editor->cursor_set_blink_enabled(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink")); text_editor->cursor_set_blink_speed(EditorSettings::get_singleton()->get("text_editor/cursor/caret_blink_speed")); - text_editor->set_auto_brace_completion(EditorSettings::get_singleton()->get("text_editor/completion/auto_brace_complete")); + text_editor->set_auto_brace_completion_enabled(EditorSettings::get_singleton()->get("text_editor/completion/auto_brace_complete")); + + if (EditorSettings::get_singleton()->get("text_editor/appearance/show_line_length_guidelines")) { + TypedArray<int> guideline_cols; + guideline_cols.append(EditorSettings::get_singleton()->get("text_editor/appearance/line_length_guideline_hard_column")); + if (EditorSettings::get_singleton()->get("text_editor/appearance/line_length_guideline_soft_column") != guideline_cols[0]) { + guideline_cols.append(EditorSettings::get_singleton()->get("text_editor/appearance/line_length_guideline_soft_column")); + } + text_editor->set_line_length_guidelines(guideline_cols); + } } void CodeTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { @@ -1609,16 +1615,9 @@ void CodeTextEditor::_apply_settings_change() { } break; } - // Auto brace completion. - text_editor->set_auto_brace_completion( - EDITOR_GET("text_editor/completion/auto_brace_complete")); - - code_complete_timer->set_wait_time( - EDITOR_GET("text_editor/completion/code_complete_delay")); - - // Call hint settings. text_editor->set_code_hint_draw_below(EDITOR_GET("text_editor/completion/put_callhint_tooltip_below_current_line")); + code_complete_timer->set_wait_time(EDITOR_GET("text_editor/completion/code_complete_delay")); idle->set_wait_time(EDITOR_GET("text_editor/completion/idle_parse_delay")); } @@ -1832,7 +1831,7 @@ CodeTextEditor::CodeTextEditor() { } text_editor->set_draw_line_numbers(true); - text_editor->set_brace_matching(true); + text_editor->set_highlight_matching_braces_enabled(true); text_editor->set_auto_indent_enabled(true); status_bar = memnew(HBoxContainer); diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index 069ae2c7f2..a9d8cb219c 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -463,13 +463,13 @@ void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector< removed_deps.sort(); if (removed_deps.is_empty()) { owners->hide(); - text->set_text(TTR("Remove selected files from the project? (no undo)\nYou can find the removed files in the system trash to restore them.")); + text->set_text(TTR("Remove the selected files from the project? (Cannot be undone.)\nDepending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently.")); set_size(Size2()); popup_centered(); } else { _build_removed_dependency_tree(removed_deps); owners->show(); - text->set_text(TTR("The files being removed are required by other resources in order for them to work.\nRemove them anyway? (no undo)\nYou can find the removed files in the system trash to restore them.")); + text->set_text(TTR("The files being removed are required by other resources in order for them to work.\nRemove them anyway? (Cannot be undone.)\nDepending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently.")); popup_centered(Size2(500, 350)); } EditorFileSystem::get_singleton()->scan_changes(); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 65459d159d..5bbe56a800 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -204,7 +204,13 @@ void ScriptTextEditor::_set_theme_for_script() { for (const String &string : strings) { String beg = string.get_slice(" ", 0); String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); - text_edit->add_string_delimiter(beg, end, end == ""); + if (!text_edit->has_string_delimiter(beg)) { + text_edit->add_string_delimiter(beg, end, end == ""); + } + + if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) { + text_edit->add_auto_brace_completion_pair(beg, end); + } } List<String> comments; @@ -214,6 +220,10 @@ void ScriptTextEditor::_set_theme_for_script() { String beg = comment.get_slice(" ", 0); String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); text_edit->add_comment_delimiter(beg, end, end == ""); + + if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) { + text_edit->add_auto_brace_completion_pair(beg, end); + } } } @@ -742,7 +752,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c EditorNode::get_singleton()->load_resource(p_symbol); } - } else if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_lookup_completion(), p_symbol, script->get_path(), base, result) == OK) { + } else if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK) { _goto_line(p_row); result.class_name = result.class_name.trim_prefix("_"); @@ -845,18 +855,17 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) { } ScriptLanguage::LookupResult result; - if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_lookup_completion(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) { - text_edit->set_highlighted_word(p_symbol); + if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) { + text_edit->set_symbol_lookup_word_as_valid(true); } else if (p_symbol.is_rel_path()) { String path = _get_absolute_path(p_symbol); if (FileAccess::exists(path)) { - text_edit->set_highlighted_word(p_symbol); + text_edit->set_symbol_lookup_word_as_valid(true); } else { - text_edit->set_highlighted_word(String()); + text_edit->set_symbol_lookup_word_as_valid(false); } - } else { - text_edit->set_highlighted_word(String()); + text_edit->set_symbol_lookup_word_as_valid(false); } } @@ -1544,7 +1553,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { base = _find_node_for_script(base, base, script); } ScriptLanguage::LookupResult result; - if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_lookup_completion(), word_at_pos, script->get_path(), base, result) == OK) { + if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) { open_docs = true; } } @@ -1829,7 +1838,7 @@ ScriptTextEditor::ScriptTextEditor() { code_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); - code_editor->get_text_editor()->set_select_identifiers_on_hover(true); + code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); code_editor->get_text_editor()->set_context_menu_enabled(false); context_menu = memnew(PopupMenu); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 2c45b96106..95973f9dfd 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -158,6 +158,10 @@ void ShaderTextEditor::_load_theme_settings() { text_editor->add_comment_delimiter("/*", "*/", false); text_editor->add_comment_delimiter("//", "", true); + if (!text_editor->has_auto_brace_completion_open_key("/*")) { + text_editor->add_auto_brace_completion_pair("/*", "*/"); + } + if (warnings_panel) { // Warnings panel warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); @@ -659,7 +663,7 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); - shader_editor->get_text_editor()->set_select_identifiers_on_hover(true); + shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); shader_editor->get_text_editor()->set_context_menu_enabled(false); shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &ShaderEditor::_text_edit_gui_input)); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 4dc4f71006..b35782ed17 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -920,6 +920,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expression_box->add_comment_delimiter("/*", "*/", false); expression_box->add_comment_delimiter("//", "", true); + if (!expression_box->has_auto_brace_completion_open_key("/*")) { + expression_box->add_auto_brace_completion_pair("/*", "*/"); + } + expression_box->set_text(expression); expression_box->set_context_menu_enabled(false); expression_box->set_draw_line_numbers(true); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index be5e0bf4e5..45d74c631a 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -72,13 +72,34 @@ void CodeEdit::_notification(int p_what) { code_completion_background_color = get_theme_color(SNAME("completion_background_color")); code_completion_selected_color = get_theme_color(SNAME("completion_selected_color")); code_completion_existing_color = get_theme_color(SNAME("completion_existing_color")); + + line_length_guideline_color = get_theme_color(SNAME("line_length_guideline_color")); } break; case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); + const Size2 size = get_size(); const bool caret_visible = is_caret_visible(); const bool rtl = is_layout_rtl(); const int row_height = get_row_height(); + if (line_length_guideline_columns.size() > 0) { + const int xmargin_beg = cache.style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width(); + const int xmargin_end = size.width - cache.style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0); + const int char_size = (int)cache.font->get_char_size('0', 0, cache.font_size).width; + + for (int i = 0; i < line_length_guideline_columns.size(); i++) { + const int xoffset = xmargin_beg + char_size * (int)line_length_guideline_columns[i] - get_h_scroll(); + if (xoffset > xmargin_beg && xoffset < xmargin_end) { + Color guideline_color = (i == 0) ? line_length_guideline_color : line_length_guideline_color * Color(1, 1, 1, 0.5); + if (rtl) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - xoffset, 0), Point2(size.width - xoffset, size.height), guideline_color); + continue; + } + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(xoffset, 0), Point2(xoffset, size.height), guideline_color); + } + } + } + bool code_completion_below = false; if (caret_visible && code_completion_active && code_completion_options.size() > 0) { Ref<StyleBox> csb = get_theme_stylebox(SNAME("completion")); @@ -278,6 +299,39 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } } + } else { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_command_pressed() && symbol_lookup_word != String()) { + Vector2i mpos = mb->get_position(); + if (is_layout_rtl()) { + mpos.x = get_size().x - mpos.x; + } + int line, col; + _get_mouse_pos(Point2i(mpos.x, mpos.y), line, col); + + emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col); + return; + } + } + } + } + + Ref<InputEventMouseMotion> mm = p_gui_input; + if (mm.is_valid()) { + Vector2i mpos = mm->get_position(); + if (is_layout_rtl()) { + mpos.x = get_size().x - mpos.x; + } + + if (symbol_lookup_on_click_enabled) { + if (mm->is_command_pressed() && mm->get_button_mask() == 0 && !is_dragging_cursor()) { + symbol_lookup_new_word = get_word_at_pos(mpos); + if (symbol_lookup_new_word != symbol_lookup_word) { + emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word); + } + } else { + set_symbol_lookup_word_as_valid(false); + } } } @@ -288,6 +342,25 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } + /* Ctrl + Hover symbols */ +#ifdef OSX_ENABLED + if (k->get_keycode() == KEY_META) { +#else + if (k->get_keycode() == KEY_CTRL) { +#endif + if (symbol_lookup_on_click_enabled) { + if (k->is_pressed() && !is_dragging_cursor()) { + symbol_lookup_new_word = get_word_at_pos(_get_local_mouse_pos()); + if (symbol_lookup_new_word != symbol_lookup_word) { + emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word); + } + } else { + set_symbol_lookup_word_as_valid(false); + } + } + return; + } + /* If a modifier has been pressed, and nothing else, return. */ if (!k->is_pressed() || k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { return; @@ -437,7 +510,12 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } +/* General overrides */ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { + if (symbol_lookup_word != String()) { + return CURSOR_POINTING_HAND; + } + if ((code_completion_active && code_completion_rect.has_point(p_pos)) || (is_readonly() && (!is_selecting_enabled() || get_line_count() == 0))) { return CURSOR_ARROW; } @@ -459,6 +537,58 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return TextEdit::get_cursor_shape(p_pos); } +void CodeEdit::handle_unicode_input(uint32_t p_unicode) { + bool had_selection = is_selection_active(); + if (had_selection) { + begin_complex_operation(); + delete_selection(); + } + + // Remove the old character if in insert mode and no selection. + if (is_insert_mode() && !had_selection) { + begin_complex_operation(); + + // Make sure we don't try and remove empty space. + if (cursor_get_column() < get_line(cursor_get_line()).length()) { + _remove_text(cursor_get_line(), cursor_get_column(), cursor_get_line(), cursor_get_column() + 1); + } + } + + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + + if (auto_brace_completion_enabled) { + int cl = cursor_get_line(); + int cc = cursor_get_column(); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_cursor(chr); + } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { + insert_text_at_cursor(chr); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_cursor(chr); + } else { + insert_text_at_cursor(chr); + + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_cursor(auto_brace_completion_pairs[pre_brace_pair].close_key); + } + } + cursor_set_column(cc + caret_move_offset); + } else { + insert_text_at_cursor(chr); + } + + if ((is_insert_mode() && !had_selection) || (had_selection)) { + end_complex_operation(); + } +} + /* Indent management */ void CodeEdit::set_indent_size(const int p_size) { ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0."); @@ -527,13 +657,13 @@ void CodeEdit::do_indent() { } if (!indent_using_spaces) { - _insert_text_at_cursor("\t"); + insert_text_at_cursor("\t"); return; } int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor_get_column()); if (spaces_to_add > 0) { - _insert_text_at_cursor(String(" ").repeat(spaces_to_add)); + insert_text_at_cursor(String(" ").repeat(spaces_to_add)); } } @@ -713,34 +843,6 @@ int CodeEdit::_calculate_spaces_till_next_right_indent(int p_column) const { return indent_size - p_column % indent_size; } -/* TODO: remove once brace completion is refactored. */ -static char32_t _get_right_pair_symbol(char32_t c) { - if (c == '"') { - return '"'; - } - if (c == '\'') { - return '\''; - } - if (c == '(') { - return ')'; - } - if (c == '[') { - return ']'; - } - if (c == '{') { - return '}'; - } - return 0; -} - -static bool _is_pair_left_symbol(char32_t c) { - return c == '"' || - c == '\'' || - c == '(' || - c == '[' || - c == '{'; -} - void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { if (is_readonly()) { return; @@ -803,9 +905,8 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { if (should_indent) { ins += indent_text; - /* TODO: Change when brace completion is refactored. */ - char32_t closing_char = _get_right_pair_symbol(indent_char); - if (closing_char != 0 && closing_char == line[cc]) { + String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char)); + if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) { /* No need to move the brace below if we are not taking the text with us. */ if (p_split_current_line) { brace_indent = true; @@ -873,12 +974,20 @@ void CodeEdit::backspace() { merge_gutters(cl, prev_line); - /* TODO: Change when brace completion is refactored. */ - if (auto_brace_completion_enabled && cc > 0 && _is_pair_left_symbol(get_line(cl)[cc - 1])) { - _consume_backspace_for_pair_symbol(prev_line, prev_column); - cursor_set_line(prev_line, false, true); - cursor_set_column(prev_column); - return; + if (auto_brace_completion_enabled && cc > 0) { + int idx = _get_auto_brace_pair_open_at_pos(cl, cc); + if (idx != -1) { + prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + + if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { + _remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); + } else { + _remove_text(prev_line, prev_column, cl, cc); + } + cursor_set_line(prev_line, false, true); + cursor_set_column(prev_column); + return; + } } /* For space indentation we need to do a simple unindent if there are no chars to the left, acting in the */ @@ -896,6 +1005,93 @@ void CodeEdit::backspace() { cursor_set_column(prev_column); } +/* Auto brace completion */ +void CodeEdit::set_auto_brace_completion_enabled(bool p_enabled) { + auto_brace_completion_enabled = p_enabled; +} + +bool CodeEdit::is_auto_brace_completion_enabled() const { + return auto_brace_completion_enabled; +} + +void CodeEdit::set_highlight_matching_braces_enabled(bool p_enabled) { + highlight_matching_braces_enabled = p_enabled; + update(); +} + +bool CodeEdit::is_highlight_matching_braces_enabled() const { + return highlight_matching_braces_enabled; +} + +void CodeEdit::add_auto_brace_completion_pair(const String &p_open_key, const String &p_close_key) { + ERR_FAIL_COND_MSG(p_open_key.is_empty(), "auto brace completion open key cannot be empty"); + ERR_FAIL_COND_MSG(p_close_key.is_empty(), "auto brace completion close key cannot be empty"); + + for (int i = 0; i < p_open_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_open_key[i]), "auto brace completion open key must be a symbol"); + } + for (int i = 0; i < p_close_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_close_key[i]), "auto brace completion close key must be a symbol"); + } + + int at = 0; + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + ERR_FAIL_COND_MSG(auto_brace_completion_pairs[i].open_key == p_open_key, "auto brace completion open key '" + p_open_key + "' already exists."); + if (p_open_key.length() < auto_brace_completion_pairs[i].open_key.length()) { + at++; + } + } + + BracePair brace_pair; + brace_pair.open_key = p_open_key; + brace_pair.close_key = p_close_key; + auto_brace_completion_pairs.insert(at, brace_pair); +} + +void CodeEdit::set_auto_brace_completion_pairs(const Dictionary &p_auto_brace_completion_pairs) { + auto_brace_completion_pairs.clear(); + + Array keys = p_auto_brace_completion_pairs.keys(); + for (int i = 0; i < keys.size(); i++) { + add_auto_brace_completion_pair(keys[i], p_auto_brace_completion_pairs[keys[i]]); + } +} + +Dictionary CodeEdit::get_auto_brace_completion_pairs() const { + Dictionary brace_pairs; + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + brace_pairs[auto_brace_completion_pairs[i].open_key] = auto_brace_completion_pairs[i].close_key; + } + return brace_pairs; +} + +bool CodeEdit::has_auto_brace_completion_open_key(const String &p_open_key) const { + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (auto_brace_completion_pairs[i].open_key == p_open_key) { + return true; + } + } + return false; +} + +bool CodeEdit::has_auto_brace_completion_close_key(const String &p_close_key) const { + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (auto_brace_completion_pairs[i].close_key == p_close_key) { + return true; + } + } + return false; +} + +String CodeEdit::get_auto_brace_completion_close_key(const String &p_open_key) const { + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (auto_brace_completion_pairs[i].open_key == p_open_key) { + return auto_brace_completion_pairs[i].close_key; + } + } + return String(); +} + /* Main Gutter */ void CodeEdit::_update_draw_main_gutter() { set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines); @@ -1700,35 +1896,40 @@ void CodeEdit::confirm_code_completion(bool p_replace) { insert_text_at_cursor(insert_text.substr(matching_chars)); } - /* TODO: merge with autobrace completion, when in CodeEdit. */ /* Handle merging of symbols eg strings, brackets. */ const String line = get_line(caret_line); char32_t next_char = line[cursor_get_column()]; char32_t last_completion_char = insert_text[insert_text.length() - 1]; char32_t last_completion_char_display = display_text[display_text.length() - 1]; - if ((last_completion_char == '"' || last_completion_char == '\'') && (last_completion_char == next_char || last_completion_char_display == next_char)) { + int pre_brace_pair = cursor_get_column() > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, cursor_get_column()) : -1; + int post_brace_pair = cursor_get_column() < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, cursor_get_column()) : -1; + + if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) { _remove_text(caret_line, cursor_get_column(), caret_line, cursor_get_column() + 1); } - if (last_completion_char == '(') { - if (next_char == last_completion_char) { - _remove_text(caret_line, cursor_get_column() - 1, caret_line, cursor_get_column()); - } else if (auto_brace_completion_enabled) { - insert_text_at_cursor(")"); - cursor_set_column(cursor_get_column() - 1); - } - } else if (last_completion_char == ')' && next_char == '(') { - _remove_text(caret_line, cursor_get_column() - 2, caret_line, cursor_get_column()); - if (line[cursor_get_column() + 1] != ')') { - cursor_set_column(cursor_get_column() - 1); + if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) { + _remove_text(caret_line, cursor_get_column(), caret_line, cursor_get_column() + 1); + } else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) { + insert_text_at_cursor(auto_brace_completion_pairs[pre_brace_pair].close_key); + cursor_set_column(cursor_get_column() - auto_brace_completion_pairs[pre_brace_pair].close_key.length()); + } + + if (pre_brace_pair == -1 && post_brace_pair == -1 && cursor_get_column() > 0 && cursor_get_column() < get_line(caret_line).length()) { + pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, cursor_get_column() + 1); + if (pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, cursor_get_column() - 1)) { + _remove_text(caret_line, cursor_get_column() - 2, caret_line, cursor_get_column()); + if (_get_auto_brace_pair_close_at_pos(caret_line, cursor_get_column() - 1) != pre_brace_pair) { + cursor_set_column(cursor_get_column() - 1); + } } } end_complex_operation(); cancel_code_completion(); - if (last_completion_char == '(') { + if (code_completion_prefixes.has(String::chr(last_completion_char))) { request_code_completion(); } } @@ -1742,6 +1943,58 @@ void CodeEdit::cancel_code_completion() { update(); } +/* Line length guidelines */ +void CodeEdit::set_line_length_guidelines(TypedArray<int> p_guideline_columns) { + line_length_guideline_columns = p_guideline_columns; + update(); +} + +TypedArray<int> CodeEdit::get_line_length_guidelines() const { + return line_length_guideline_columns; +} + +/* Symbol lookup */ +void CodeEdit::set_symbol_lookup_on_click_enabled(bool p_enabled) { + symbol_lookup_on_click_enabled = p_enabled; + set_symbol_lookup_word_as_valid(false); +} + +bool CodeEdit::is_symbol_lookup_on_click_enabled() const { + return symbol_lookup_on_click_enabled; +} + +String CodeEdit::get_text_for_symbol_lookup() { + int line, col; + Point2i mp = _get_local_mouse_pos(); + _get_mouse_pos(mp, line, col); + + StringBuilder lookup_text; + const int text_size = get_line_count(); + for (int i = 0; i < text_size; i++) { + String text = get_line(i); + + if (i == line) { + lookup_text += text.substr(0, col); + /* Not unicode, represents the cursor. */ + lookup_text += String::chr(0xFFFF); + lookup_text += text.substr(col, text.size()); + } else { + lookup_text += text; + } + + if (i != text_size - 1) { + lookup_text += "\n"; + } + } + return lookup_text.as_string(); +} + +void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) { + symbol_lookup_word = p_valid ? symbol_lookup_new_word : ""; + symbol_lookup_new_word = ""; + _set_symbol_lookup_word(symbol_lookup_word); +} + void CodeEdit::_bind_methods() { /* Indent management */ ClassDB::bind_method(D_METHOD("set_indent_size", "size"), &CodeEdit::set_indent_size); @@ -1762,6 +2015,22 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines); ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines); + /* Auto brace completion */ + ClassDB::bind_method(D_METHOD("set_auto_brace_completion_enabled", "enable"), &CodeEdit::set_auto_brace_completion_enabled); + ClassDB::bind_method(D_METHOD("is_auto_brace_completion_enabled"), &CodeEdit::is_auto_brace_completion_enabled); + + ClassDB::bind_method(D_METHOD("set_highlight_matching_braces_enabled", "enable"), &CodeEdit::set_highlight_matching_braces_enabled); + ClassDB::bind_method(D_METHOD("is_highlight_matching_braces_enabled"), &CodeEdit::is_highlight_matching_braces_enabled); + + ClassDB::bind_method(D_METHOD("add_auto_brace_completion_pair", "start_key", "end_key"), &CodeEdit::add_auto_brace_completion_pair); + ClassDB::bind_method(D_METHOD("set_auto_brace_completion_pairs", "pairs"), &CodeEdit::set_auto_brace_completion_pairs); + ClassDB::bind_method(D_METHOD("get_auto_brace_completion_pairs"), &CodeEdit::get_auto_brace_completion_pairs); + + ClassDB::bind_method(D_METHOD("has_auto_brace_completion_open_key", "open_key"), &CodeEdit::has_auto_brace_completion_open_key); + ClassDB::bind_method(D_METHOD("has_auto_brace_completion_close_key", "close_key"), &CodeEdit::has_auto_brace_completion_close_key); + + ClassDB::bind_method(D_METHOD("get_auto_brace_completion_close_key", "open_key"), &CodeEdit::get_auto_brace_completion_close_key); + /* Main Gutter */ ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback); @@ -1890,19 +2159,35 @@ void CodeEdit::_bind_methods() { BIND_VMETHOD(MethodInfo("_request_code_completion", PropertyInfo(Variant::BOOL, "force"))); BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_filter_code_completion_candidates", PropertyInfo(Variant::ARRAY, "candidates"))); + /* Line length guidelines */ + ClassDB::bind_method(D_METHOD("set_line_length_guidelines", "guideline_columns"), &CodeEdit::set_line_length_guidelines); + ClassDB::bind_method(D_METHOD("get_line_length_guidelines"), &CodeEdit::get_line_length_guidelines); + + /* Symbol lookup */ + ClassDB::bind_method(D_METHOD("set_symbol_lookup_on_click_enabled", "enable"), &CodeEdit::set_symbol_lookup_on_click_enabled); + ClassDB::bind_method(D_METHOD("is_symbol_lookup_on_click_enabled"), &CodeEdit::is_symbol_lookup_on_click_enabled); + + ClassDB::bind_method(D_METHOD("get_text_for_symbol_lookup"), &CodeEdit::get_text_for_symbol_lookup); + + ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid); + /* Inspector */ - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symbol_lookup_on_click"), "set_symbol_lookup_on_click_enabled", "is_symbol_lookup_on_click_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "line_length_guidelines"), "set_line_length_guidelines", "get_line_length_guidelines"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_executing_lines"), "set_draw_executing_lines_gutter", "is_drawing_executing_lines_gutter"); + ADD_GROUP("Gutters", "gutters_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_line_numbers"), "set_draw_line_numbers", "is_draw_line_numbers_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_pad_line_numbers"), "set_line_numbers_zero_padded", "is_line_numbers_zero_padded"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_executing_lines"), "set_draw_executing_lines_gutter", "is_drawing_executing_lines_gutter"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_line_numbers"), "set_draw_line_numbers", "is_draw_line_numbers_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_zero_pad_line_numbers"), "set_line_numbers_zero_padded", "is_line_numbers_zero_padded"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); ADD_GROUP("Delimiters", "delimiter_"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_strings"), "set_string_delimiters", "get_string_delimiters"); @@ -1918,11 +2203,74 @@ void CodeEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes"); + ADD_GROUP("Auto brace completion", "auto_brace_completion_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_enabled"), "set_auto_brace_completion_enabled", "is_auto_brace_completion_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_highlight_matching"), "set_highlight_matching_braces_enabled", "is_highlight_matching_braces_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "auto_brace_completion_pairs"), "set_auto_brace_completion_pairs", "get_auto_brace_completion_pairs"); + /* Signals */ + /* Gutters */ ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line"))); + + /* Code Completion */ ADD_SIGNAL(MethodInfo("request_code_completion")); + + /* Symbol lookup */ + ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "column"))); + ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol"))); } +/* Auto brace completion */ +int CodeEdit::_get_auto_brace_pair_open_at_pos(int p_line, int p_col) { + const String &line = get_line(p_line); + + /* Should be fast enough, expecting low amount of pairs... */ + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + const String &open_key = auto_brace_completion_pairs[i].open_key; + if (p_col - open_key.length() < 0) { + continue; + } + + bool is_match = true; + for (int j = 0; j < open_key.length(); j++) { + if (line[(p_col - 1) - j] != open_key[(open_key.length() - 1) - j]) { + is_match = false; + break; + } + } + + if (is_match) { + return i; + } + } + return -1; +} + +int CodeEdit::_get_auto_brace_pair_close_at_pos(int p_line, int p_col) { + const String &line = get_line(p_line); + + /* Should be fast enough, expecting low amount of pairs... */ + for (int i = 0; i < auto_brace_completion_pairs.size(); i++) { + if (p_col + auto_brace_completion_pairs[i].close_key.length() > line.length()) { + continue; + } + + bool is_match = true; + for (int j = 0; j < auto_brace_completion_pairs[i].close_key.length(); j++) { + if (line[p_col + j] != auto_brace_completion_pairs[i].close_key[j]) { + is_match = false; + break; + } + } + + if (is_match) { + return i; + } + } + return -1; +} + +/* Gutters */ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { if (p_gutter == main_gutter) { if (draw_breakpoints) { @@ -2547,6 +2895,17 @@ CodeEdit::CodeEdit() { auto_indent_prefixes.insert('['); auto_indent_prefixes.insert('('); + /* Auto brace completion */ + add_auto_brace_completion_pair("(", ")"); + add_auto_brace_completion_pair("{", "}"); + add_auto_brace_completion_pair("[", "]"); + add_auto_brace_completion_pair("\"", "\""); + add_auto_brace_completion_pair("\'", "\'"); + + /* Delimiter traking */ + add_string_delimiter("\"", "\"", false); + add_string_delimiter("\'", "\'", false); + /* Text Direction */ set_layout_direction(LAYOUT_DIRECTION_LTR); set_text_direction(TEXT_DIRECTION_LTR); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 25b518402b..72fdc6e787 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -66,6 +66,19 @@ private: void _new_line(bool p_split_current_line = true, bool p_above = false); + /* Auto brace completion */ + bool auto_brace_completion_enabled = false; + + /* BracePair open_key must be uniquie and ordered by length. */ + struct BracePair { + String open_key = ""; + String close_key = ""; + }; + Vector<BracePair> auto_brace_completion_pairs; + + int _get_auto_brace_pair_open_at_pos(int p_line, int p_col); + int _get_auto_brace_pair_close_at_pos(int p_line, int p_col); + /* Main Gutter */ enum MainGutterType { MAIN_GUTTER_BREAKPOINT = 0x01, @@ -112,7 +125,7 @@ private: void _update_gutter_indexes(); /* Line Folding */ - bool line_folding_enabled = true; + bool line_folding_enabled = false; /* Delimiters */ enum DelimiterType { @@ -210,6 +223,16 @@ private: void _lines_edited_from(int p_from_line, int p_to_line); + /* Line length guidelines */ + TypedArray<int> line_length_guideline_columns; + Color line_length_guideline_color; + + /* Symbol lookup */ + bool symbol_lookup_on_click_enabled = false; + + String symbol_lookup_new_word = ""; + String symbol_lookup_word = ""; + protected: void _gui_input(const Ref<InputEvent> &p_gui_input) override; void _notification(int p_what); @@ -217,7 +240,9 @@ protected: static void _bind_methods(); public: + /* General overrides */ virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + virtual void handle_unicode_input(uint32_t p_unicode) override; /* Indent management */ void set_indent_size(const int p_size); @@ -240,6 +265,22 @@ public: virtual void backspace() override; + /* Auto brace completion */ + void set_auto_brace_completion_enabled(bool p_enabled); + bool is_auto_brace_completion_enabled() const; + + void set_highlight_matching_braces_enabled(bool p_enabled); + bool is_highlight_matching_braces_enabled() const; + + void add_auto_brace_completion_pair(const String &p_open_key, const String &p_close_key); + void set_auto_brace_completion_pairs(const Dictionary &p_auto_brace_completion_pairs); + Dictionary get_auto_brace_completion_pairs() const; + + bool has_auto_brace_completion_open_key(const String &p_open_key) const; + bool has_auto_brace_completion_close_key(const String &p_close_key) const; + + String get_auto_brace_completion_close_key(const String &p_open_key) const; + /* Main Gutter */ void set_draw_breakpoints_gutter(bool p_draw); bool is_drawing_breakpoints_gutter() const; @@ -347,6 +388,18 @@ public: void confirm_code_completion(bool p_replace = false); void cancel_code_completion(); + /* Line length guidelines */ + void set_line_length_guidelines(TypedArray<int> p_guideline_columns); + TypedArray<int> get_line_length_guidelines() const; + + /* Symbol lookup */ + void set_symbol_lookup_on_click_enabled(bool p_enabled); + bool is_symbol_lookup_on_click_enabled() const; + + String get_text_for_symbol_lookup(); + + void set_symbol_lookup_word_as_valid(bool p_valid); + CodeEdit(); ~CodeEdit(); }; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index be3edccc99..1b3935dd25 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -63,45 +63,6 @@ static bool _is_char(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -static bool _is_pair_right_symbol(char32_t c) { - return c == '"' || - c == '\'' || - c == ')' || - c == ']' || - c == '}'; -} - -static bool _is_pair_left_symbol(char32_t c) { - return c == '"' || - c == '\'' || - c == '(' || - c == '[' || - c == '{'; -} - -static bool _is_pair_symbol(char32_t c) { - return _is_pair_left_symbol(c) || _is_pair_right_symbol(c); -} - -static char32_t _get_right_pair_symbol(char32_t c) { - if (c == '"') { - return '"'; - } - if (c == '\'') { - return '\''; - } - if (c == '(') { - return ')'; - } - if (c == '[') { - return ']'; - } - if (c == '{') { - return '}'; - } - return 0; -} - /////////////////////////////////////////////////////////////////////////////// void TextEdit::Text::set_font(const Ref<Font> &p_font) { @@ -633,29 +594,6 @@ void TextEdit::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), cache.background_color); } - if (line_length_guidelines) { - const int hard_x = xmargin_beg + (int)cache.font->get_char_size('0', 0, cache.font_size).width * line_length_guideline_hard_col - cursor.x_ofs; - if (hard_x > xmargin_beg && hard_x < xmargin_end) { - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - hard_x, 0), Point2(size.width - hard_x, size.height), cache.line_length_guideline_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(hard_x, 0), Point2(hard_x, size.height), cache.line_length_guideline_color); - } - } - - // Draw a "Soft" line length guideline, less visible than the hard line length guideline. - // It's usually set to a lower column compared to the hard line length guideline. - // Only drawn if its column differs from the hard line length guideline. - const int soft_x = xmargin_beg + (int)cache.font->get_char_size('0', 0, cache.font_size).width * line_length_guideline_soft_col - cursor.x_ofs; - if (hard_x != soft_x && soft_x > xmargin_beg && soft_x < xmargin_end) { - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - soft_x, 0), Point2(size.width - soft_x, size.height), cache.line_length_guideline_color * Color(1, 1, 1, 0.5)); - } else { - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(soft_x, 0), Point2(soft_x, size.height), cache.line_length_guideline_color * Color(1, 1, 1, 0.5)); - } - } - } - int brace_open_match_line = -1; int brace_open_match_column = -1; bool brace_open_matching = false; @@ -665,7 +603,7 @@ void TextEdit::_notification(int p_what) { bool brace_close_matching = false; bool brace_close_mismatch = false; - if (brace_matching_enabled && cursor.line >= 0 && cursor.line < text.size() && cursor.column >= 0) { + if (highlight_matching_braces_enabled && cursor.line >= 0 && cursor.line < text.size() && cursor.column >= 0) { if (cursor.column < text[cursor.line].length()) { // Check for open. char32_t c = text[cursor.line][cursor.column]; @@ -1239,11 +1177,11 @@ void TextEdit::_notification(int p_what) { } } - if (!clipped && select_identifiers_enabled && highlighted_word.length() != 0) { // Highlight word - if (_is_char(highlighted_word[0]) || highlighted_word[0] == '.') { - int highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word + if (_is_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '.') { + int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); while (highlighted_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + highlighted_word.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1260,7 +1198,7 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, cache.font_selected_color); } - highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); + highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); } } } @@ -1308,7 +1246,7 @@ void TextEdit::_notification(int p_what) { int char_pos = char_ofs + char_margin + ofs_x; if (char_pos >= xmargin_beg) { - if (brace_matching_enabled) { + if (highlight_matching_braces_enabled) { if ((brace_open_match_line == line && brace_open_match_column == glyphs[j].start) || (cursor.column == glyphs[j].start && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) { if (brace_open_mismatch) { @@ -1567,130 +1505,6 @@ void TextEdit::_notification(int p_what) { } } -void TextEdit::_consume_pair_symbol(char32_t ch) { - int cursor_position_to_move = cursor_get_column() + 1; - - char32_t ch_single[2] = { ch, 0 }; - char32_t ch_single_pair[2] = { _get_right_pair_symbol(ch), 0 }; - char32_t ch_pair[3] = { ch, _get_right_pair_symbol(ch), 0 }; - - if (is_selection_active()) { - int new_column, new_line; - - begin_complex_operation(); - _insert_text(get_selection_from_line(), get_selection_from_column(), - ch_single, - &new_line, &new_column); - - int to_col_offset = 0; - if (get_selection_from_line() == get_selection_to_line()) { - to_col_offset = 1; - } - - _insert_text(get_selection_to_line(), - get_selection_to_column() + to_col_offset, - ch_single_pair, - &new_line, &new_column); - end_complex_operation(); - - cursor_set_line(get_selection_to_line()); - cursor_set_column(get_selection_to_column() + to_col_offset); - - deselect(); - update(); - return; - } - - if ((ch == '\'' || ch == '"') && - cursor_get_column() > 0 && _is_text_char(text[cursor.line][cursor_get_column() - 1]) && !_is_pair_right_symbol(text[cursor.line][cursor_get_column()])) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - return; - } - - if (cursor_get_column() < text[cursor.line].length()) { - if (_is_text_char(text[cursor.line][cursor_get_column()])) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - return; - } - if (_is_pair_right_symbol(ch) && - text[cursor.line][cursor_get_column()] == ch) { - cursor_set_column(cursor_position_to_move); - return; - } - } - - String line = text[cursor.line]; - - bool in_single_quote = false; - bool in_double_quote = false; - bool found_comment = false; - - int c = 0; - while (c < line.length()) { - if (line[c] == '\\') { - c++; // Skip quoted anything. - - if (cursor.column == c) { - break; - } - } else if (!in_single_quote && !in_double_quote && line[c] == '#') { - found_comment = true; - break; - } else { - if (line[c] == '\'' && !in_double_quote) { - in_single_quote = !in_single_quote; - } else if (line[c] == '"' && !in_single_quote) { - in_double_quote = !in_double_quote; - } - } - - c++; - - if (cursor.column == c) { - break; - } - } - - // Do not need to duplicate quotes while in comments - if (found_comment) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - - return; - } - - // Disallow inserting duplicated quotes while already in string - if ((in_single_quote || in_double_quote) && (ch == '"' || ch == '\'')) { - insert_text_at_cursor(ch_single); - cursor_set_column(cursor_position_to_move); - - return; - } - - insert_text_at_cursor(ch_pair); - cursor_set_column(cursor_position_to_move); -} - -void TextEdit::_consume_backspace_for_pair_symbol(int prev_line, int prev_column) { - bool remove_right_symbol = false; - - if (cursor.column < text[cursor.line].length() && cursor.column > 0) { - char32_t left_char = text[cursor.line][cursor.column - 1]; - char32_t right_char = text[cursor.line][cursor.column]; - - if (right_char == _get_right_pair_symbol(left_char)) { - remove_right_symbol = true; - } - } - if (remove_right_symbol) { - _remove_text(prev_line, prev_column, cursor.line, cursor.column + 1); - } else { - _remove_text(prev_line, prev_column, cursor.line, cursor.column); - } -} - void TextEdit::backspace() { ScriptInstance *si = get_script_instance(); if (si && si->has_method("_backspace")) { @@ -1719,14 +1533,7 @@ void TextEdit::backspace() { if (is_line_hidden(cursor.line)) { set_line_as_hidden(prev_line, true); } - - if (auto_brace_completion_enabled && - cursor.column > 0 && - _is_pair_left_symbol(text[cursor.line][cursor.column - 1])) { - _consume_backspace_for_pair_symbol(prev_line, prev_column); - } else { - _remove_text(prev_line, prev_column, cursor.line, cursor.column); - } + _remove_text(prev_line, prev_column, cursor.line, cursor.column); cursor_set_line(prev_line, false, true); cursor_set_column(prev_column); @@ -2101,6 +1908,7 @@ void TextEdit::delete_selection() { } selection.active = false; + selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); cursor_set_line(selection.from_line, false, false); cursor_set_column(selection.from_column); @@ -2137,13 +1945,21 @@ void TextEdit::_move_cursor_document_end(bool p_select) { } } -void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection) { - if (p_had_selection) { +void TextEdit::handle_unicode_input(uint32_t p_unicode) { + ScriptInstance *si = get_script_instance(); + if (si && si->has_method("_handle_unicode_input")) { + si->call("_handle_unicode_input", p_unicode); + return; + } + + bool had_selection = selection.active; + if (had_selection) { + begin_complex_operation(); delete_selection(); } // Remove the old character if in insert mode and no selection. - if (insert_mode && !p_had_selection) { + if (insert_mode && !had_selection) { begin_complex_operation(); // Make sure we don't try and remove empty space. @@ -2152,15 +1968,10 @@ void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection) } } - const char32_t chr[2] = { (char32_t)unicode, 0 }; + const char32_t chr[2] = { (char32_t)p_unicode, 0 }; + insert_text_at_cursor(chr); - if (auto_brace_completion_enabled && _is_pair_symbol(chr[0])) { - _consume_pair_symbol(chr[0]); - } else { - _insert_text_at_cursor(chr); - } - - if ((insert_mode && !p_had_selection) || (selection.active != p_had_selection)) { + if ((insert_mode && !had_selection) || (had_selection)) { end_complex_operation(); } } @@ -2301,6 +2112,10 @@ void TextEdit::_get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const r_row = row; } +bool TextEdit::is_dragging_cursor() const { + return dragging_selection || dragging_minimap; +} + void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); @@ -2478,14 +2293,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } else { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (mb->is_command_pressed() && highlighted_word != String()) { - int row, col; - _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col); - - emit_signal(SNAME("symbol_lookup"), highlighted_word, row, col); - return; - } - dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; @@ -2520,18 +2327,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (is_layout_rtl()) { mpos.x = get_size().x - mpos.x; } - if (select_identifiers_enabled) { - if (!dragging_minimap && !dragging_selection && mm->is_command_pressed() && mm->get_button_mask() == 0) { - String new_word = get_word_at_pos(mpos); - if (new_word != highlighted_word) { - emit_signal(SNAME("symbol_validate"), new_word); - } - } else { - if (highlighted_word != String()) { - set_highlighted_word(String()); - } - } - } if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); @@ -2566,23 +2361,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { - // Ctrl + Hover symbols -#ifdef OSX_ENABLED - if (k->get_keycode() == KEY_META) { -#else - if (k->get_keycode() == KEY_CTRL) { -#endif - if (select_identifiers_enabled) { - if (k->is_pressed() && !dragging_minimap && !dragging_selection) { - Point2 mp = _get_local_mouse_pos(); - emit_signal(SNAME("symbol_validate"), get_word_at_pos(mp)); - } else { - set_highlighted_word(String()); - } - } - return; - } - if (!k->is_pressed()) { return; } @@ -2598,9 +2376,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { // * No Modifiers are pressed (except shift) bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); - // Save here for insert mode, just in case it is cleared in the following section. - bool had_selection = selection.active; - selection.selecting_text = false; // Check and handle all built in shortcuts. @@ -2806,9 +2581,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } + // Handle Unicode (if no modifiers active). if (allow_unicode_handling && !readonly && k->get_unicode() >= 32) { - // Handle Unicode (if no modifiers active). - _handle_unicode_character(k->get_unicode(), had_selection); + handle_unicode_input(k->get_unicode()); accept_event(); return; } @@ -3151,16 +2926,6 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i current_op = op; } -void TextEdit::_insert_text_at_cursor(const String &p_text) { - int new_column, new_line; - _insert_text(cursor.line, cursor.column, p_text, &new_line, &new_column); - _update_scrollbars(); - cursor_set_line(new_line, false); - cursor_set_column(new_column); - - update(); -} - int TextEdit::get_char_count() { int totalsize = 0; @@ -3704,23 +3469,19 @@ int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { void TextEdit::insert_text_at_cursor(const String &p_text) { if (selection.active) { - cursor_set_line(selection.from_line, false); - cursor_set_column(selection.from_column); - - _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - selection.active = false; - selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + delete_selection(); } - _insert_text_at_cursor(p_text); + int new_column, new_line; + _insert_text(cursor.line, cursor.column, p_text, &new_line, &new_column); + _update_scrollbars(); + + cursor_set_line(new_line, false); + cursor_set_column(new_column); update(); } Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { - if (highlighted_word != String()) { - return CURSOR_POINTING_HAND; - } - int row, col; _get_mouse_pos(p_pos, row, col); @@ -3753,7 +3514,7 @@ void TextEdit::set_text(String p_text) { setting_text = true; if (!undo_enabled) { _clear(); - _insert_text_at_cursor(p_text); + insert_text_at_cursor(p_text); } if (undo_enabled) { @@ -3762,7 +3523,7 @@ void TextEdit::set_text(String p_text) { begin_complex_operation(); _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); - _insert_text_at_cursor(p_text); + insert_text_at_cursor(p_text); end_complex_operation(); selection.active = false; } @@ -3903,30 +3664,6 @@ bool TextEdit::get_draw_control_chars() const { return draw_control_chars; } -String TextEdit::get_text_for_lookup_completion() { - int row, col; - Point2i mp = _get_local_mouse_pos(); - _get_mouse_pos(mp, row, col); - - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - if (i == row) { - longthing += text[i].substr(0, col); - longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. - longthing += text[i].substr(col, text[i].size()); - } else { - longthing += text[i]; - } - - if (i != len - 1) { - longthing += "\n"; - } - } - - return longthing; -} - String TextEdit::get_line(int line) const { if (line < 0 || line >= text.size()) { return ""; @@ -4015,9 +3752,8 @@ void TextEdit::_update_caches() { cache.font_readonly_color = get_theme_color(SNAME("font_readonly_color")); cache.selection_color = get_theme_color(SNAME("selection_color")); cache.current_line_color = get_theme_color(SNAME("current_line_color")); - cache.line_length_guideline_color = get_theme_color(SNAME("line_length_guideline_color")); cache.code_folding_color = get_theme_color(SNAME("code_folding_color"), SNAME("CodeEdit")); - cache.brace_mismatch_color = get_theme_color(SNAME("brace_mismatch_color")); + cache.brace_mismatch_color = get_theme_color(SNAME("brace_mismatch_color"), SNAME("CodeEdit")); cache.word_highlighted_color = get_theme_color(SNAME("word_highlighted_color")); cache.search_result_color = get_theme_color(SNAME("search_result_color")); cache.search_result_border_color = get_theme_color(SNAME("search_result_border_color")); @@ -4364,7 +4100,7 @@ void TextEdit::paste() { clipboard += ins; } - _insert_text_at_cursor(clipboard); + insert_text_at_cursor(clipboard); end_complex_operation(); update(); @@ -5324,21 +5060,6 @@ void TextEdit::insert_at(const String &p_text, int at) { } } -void TextEdit::set_show_line_length_guidelines(bool p_show) { - line_length_guidelines = p_show; - update(); -} - -void TextEdit::set_line_length_guideline_soft_column(int p_column) { - line_length_guideline_soft_col = p_column; - update(); -} - -void TextEdit::set_line_length_guideline_hard_column(int p_column) { - line_length_guideline_hard_col = p_column; - update(); -} - void TextEdit::set_draw_minimap(bool p_draw) { if (draw_minimap != p_draw) { draw_minimap = p_draw; @@ -5515,19 +5236,11 @@ void TextEdit::menu_option(int p_option) { } } -void TextEdit::set_highlighted_word(const String &new_word) { - highlighted_word = new_word; +void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { + lookup_symbol_word = p_symbol; update(); } -void TextEdit::set_select_identifiers_on_hover(bool p_enable) { - select_identifiers_enabled = p_enable; -} - -bool TextEdit::is_selecting_identifiers_on_hover_enabled() const { - return select_identifiers_enabled; -} - void TextEdit::set_context_menu_enabled(bool p_enable) { context_menu_enabled = p_enable; } @@ -5719,6 +5432,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace); BIND_VMETHOD(MethodInfo("_backspace")); + BIND_VMETHOD(MethodInfo("_handle_unicode_input", PropertyInfo(Variant::INT, "unicode"))) ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut); ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); @@ -5727,6 +5441,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column"), &TextEdit::select); ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); ClassDB::bind_method(D_METHOD("deselect"), &TextEdit::deselect); + ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor); ClassDB::bind_method(D_METHOD("is_selection_active"), &TextEdit::is_selection_active); ClassDB::bind_method(D_METHOD("get_selection_from_line"), &TextEdit::get_selection_from_line); @@ -5777,6 +5492,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable); ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters); ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw); + ClassDB::bind_method(D_METHOD("get_total_gutter_width"), &TextEdit::get_total_gutter_width); // Line gutters. ClassDB::bind_method(D_METHOD("set_line_gutter_metadata", "line", "gutter", "metadata"), &TextEdit::set_line_gutter_metadata); @@ -5858,8 +5574,6 @@ void TextEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("gutter_clicked", PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "gutter"))); ADD_SIGNAL(MethodInfo("gutter_added")); ADD_SIGNAL(MethodInfo("gutter_removed")); - ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::INT, "column"))); - ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol"))); BIND_ENUM_CONSTANT(MENU_CUT); BIND_ENUM_CONSTANT(MENU_COPY); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 62d576b48a..9e6dedb267 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -280,9 +280,6 @@ private: bool cursor_changed_dirty = false; bool text_changed_dirty = false; bool undo_enabled = true; - bool line_length_guidelines = false; - int line_length_guideline_soft_col = 80; - int line_length_guideline_hard_col = 100; bool hiding_enabled = false; bool draw_minimap = false; int minimap_width = 80; @@ -291,7 +288,6 @@ private: bool highlight_all_occurrences = false; bool scroll_past_end_of_file_enabled = false; - bool brace_matching_enabled = false; bool highlight_current_line = false; String cut_copy_line; @@ -309,7 +305,7 @@ private: float target_v_scroll = 0.0; float v_scroll_speed = 80.0; - String highlighted_word; + String lookup_symbol_word; uint64_t last_dblclk = 0; @@ -386,7 +382,6 @@ private: Size2 get_minimum_size() const override; int _get_control_height() const; - Point2 _get_local_mouse_pos() const; int _get_menu_action_accelerator(const String &p_action); void _reset_caret_blink_timer(); @@ -431,10 +426,9 @@ private: void _delete(bool p_word = false, bool p_all_to_right = false); void _move_cursor_document_start(bool p_select); void _move_cursor_document_end(bool p_select); - void _handle_unicode_character(uint32_t unicode, bool p_had_selection); protected: - bool auto_brace_completion_enabled = false; + bool highlight_matching_braces_enabled = false; struct Cache { Ref<Texture2D> tab_icon; @@ -455,7 +449,6 @@ protected: Color selection_color; Color code_folding_color; Color current_line_color; - Color line_length_guideline_color; Color brace_mismatch_color; Color word_highlighted_color; Color search_result_color; @@ -470,19 +463,17 @@ protected: void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr); void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); - void _insert_text_at_cursor(const String &p_text); virtual void _gui_input(const Ref<InputEvent> &p_gui_input); void _notification(int p_what); - void _consume_pair_symbol(char32_t ch); - void _consume_backspace_for_pair_symbol(int prev_line, int prev_column); - static void _bind_methods(); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + void _set_symbol_lookup_word(const String &p_symbol); + public: /* Syntax Highlighting. */ Ref<SyntaxHighlighter> get_syntax_highlighter(); @@ -577,8 +568,10 @@ public: virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + Point2 _get_local_mouse_pos() const; void _get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const; void _get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const; + bool is_dragging_cursor() const; //void delete_char(); //void delete_line(); @@ -607,7 +600,6 @@ public: void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; - void set_highlighted_word(const String &new_word); void set_text(String p_text); void insert_text_at_cursor(const String &p_text); void insert_at(const String &p_text, int at); @@ -635,13 +627,6 @@ public: scroll_past_end_of_file_enabled = p_enabled; update(); } - inline void set_auto_brace_completion(bool p_enabled) { - auto_brace_completion_enabled = p_enabled; - } - inline void set_brace_matching(bool p_enabled) { - brace_matching_enabled = p_enabled; - update(); - } void center_viewport_to_cursor(); @@ -686,6 +671,7 @@ public: void delete_selection(); + virtual void handle_unicode_input(uint32_t p_unicode); virtual void backspace(); void cut(); void copy(); @@ -751,10 +737,6 @@ public: void set_highlight_current_line(bool p_enabled); bool is_highlight_current_line_enabled() const; - void set_show_line_length_guidelines(bool p_show); - void set_line_length_guideline_soft_column(int p_column); - void set_line_length_guideline_hard_column(int p_column); - void set_draw_minimap(bool p_draw); bool is_drawing_minimap() const; @@ -766,9 +748,6 @@ public: void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata); - void set_select_identifiers_on_hover(bool p_enable); - bool is_selecting_identifiers_on_hover_enabled() const; - void set_context_menu_enabled(bool p_enable); bool is_context_menu_enabled(); @@ -784,8 +763,6 @@ public: bool is_menu_visible() const; PopupMenu *get_menu() const; - String get_text_for_lookup_completion(); - virtual bool is_text_field() const override; TextEdit(); ~TextEdit(); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index a4e126fcc6..d0dee2b5e3 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -457,7 +457,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("current_line_color", "TextEdit", Color(0.25, 0.25, 0.26, 0.8)); theme->set_color("caret_color", "TextEdit", control_font_color); theme->set_color("caret_background_color", "TextEdit", Color(0, 0, 0)); - theme->set_color("brace_mismatch_color", "TextEdit", Color(1, 0.2, 0.2)); theme->set_color("word_highlighted_color", "TextEdit", Color(0.8, 0.9, 0.9, 0.15)); theme->set_constant("line_spacing", "TextEdit", 4 * scale); @@ -502,8 +501,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("caret_background_color", "CodeEdit", Color(0, 0, 0)); theme->set_color("brace_mismatch_color", "CodeEdit", Color(1, 0.2, 0.2)); theme->set_color("line_number_color", "CodeEdit", Color(0.67, 0.67, 0.67, 0.4)); - theme->set_color("safe_line_number_color", "CodeEdit", Color(0.67, 0.78, 0.67, 0.6)); theme->set_color("word_highlighted_color", "CodeEdit", Color(0.8, 0.9, 0.9, 0.15)); + theme->set_color("line_length_guideline_color", "CodeEdit", Color(0.3, 0.5, 0.8, 0.1)); theme->set_constant("completion_lines", "CodeEdit", 7); theme->set_constant("completion_max_width", "CodeEdit", 50); |