diff --git a/doc/classes/ScriptEditorBase.xml b/doc/classes/ScriptEditorBase.xml
index 50ee8110c0..48e6a0ab1d 100644
--- a/doc/classes/ScriptEditorBase.xml
+++ b/doc/classes/ScriptEditorBase.xml
@@ -68,13 +68,13 @@
- Emitted when the user contextual goto and the item is in the same script.
+ Emitted when the editor requests to save a new history navigation point.
- Emitted when the user changes current script or moves caret by 10 or more columns within the same script.
+ Emitted when the editor requests an update to its navigation point.
diff --git a/editor/doc/editor_help.cpp b/editor/doc/editor_help.cpp
index 8cf2408b4f..60c8bca8d0 100644
--- a/editor/doc/editor_help.cpp
+++ b/editor/doc/editor_help.cpp
@@ -54,6 +54,7 @@
#include "editor/file_system/editor_paths.h"
#include "editor/gui/editor_toaster.h"
#include "editor/inspector/editor_property_name_processor.h"
+#include "editor/script/script_editor_navigation_marker.h"
#include "editor/script/script_editor_plugin.h"
#include "editor/script/syntax_highlighters.h"
#include "editor/settings/editor_settings.h"
@@ -270,13 +271,14 @@ void EditorHelp::_search(bool p_search_previous) {
void EditorHelp::_class_desc_finished() {
if (scroll_to >= 0) {
- class_desc->connect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph).bind(scroll_to), CONNECT_ONE_SHOT | CONNECT_DEFERRED);
+ class_desc->connect(SceneStringName(draw), callable_mp(this, &EditorHelp::_class_desc_scroll_to_paragraph).bind(scroll_to, need_save_new_history), CONNECT_ONE_SHOT | CONNECT_DEFERRED);
}
scroll_to = -1;
+ need_save_new_history = false;
}
void EditorHelp::_class_list_select(const String &p_select) {
- _goto_desc(p_select);
+ _goto_desc(p_select, true);
}
void EditorHelp::_class_desc_select(const String &p_select) {
@@ -344,12 +346,14 @@ void EditorHelp::_class_desc_select(const String &p_select) {
// Case order is important here to correctly handle edge cases like `Variant.Type` in `@GlobalScope`.
if (table->has(link)) {
// Found in the current page.
+ ScriptEditorNavigationMarker::get_singleton()->locate_begin();
if (class_desc->is_finished()) {
- emit_signal(SNAME("request_save_history"));
- class_desc->scroll_to_paragraph((*table)[link]);
+ _class_desc_scroll_to_paragraph((*table)[link], _need_save_new_history());
} else {
scroll_to = (*table)[link];
+ need_save_new_history = _need_save_new_history();
}
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
} else {
// Look for link in `@GlobalScope`.
if (topic == "class_enum") {
@@ -726,7 +730,7 @@ void EditorHelp::_pop_code_font() {
class_desc->pop(); // font
}
-Error EditorHelp::_goto_desc(const String &p_class) {
+Error EditorHelp::_goto_desc(const String &p_class, bool p_can_trigger_save_history) {
if (!doc->class_list.has(p_class)) {
return ERR_DOES_NOT_EXIST;
}
@@ -738,11 +742,17 @@ Error EditorHelp::_goto_desc(const String &p_class) {
description_line = 0;
if (p_class == edited_class) {
+ if (p_can_trigger_save_history) {
+ trigger_history_save_on_navigate();
+ }
return OK; // Already there.
}
edited_class = p_class;
_update_doc();
+ if (p_can_trigger_save_history) {
+ trigger_history_save_on_navigate();
+ }
return OK;
}
@@ -2344,7 +2354,7 @@ void EditorHelp::_update_doc() {
}
void EditorHelp::_request_help(const String &p_string) {
- Error err = _goto_desc(p_string);
+ Error err = _goto_desc(p_string, false);
if (err == OK) {
EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
@@ -2432,12 +2442,36 @@ void EditorHelp::_help_callback(const String &p_topic) {
}
if (class_desc->is_finished()) {
- class_desc->scroll_to_paragraph(line);
+ _class_desc_scroll_to_paragraph(line, _need_save_new_history());
} else {
scroll_to = line;
+ need_save_new_history = _need_save_new_history();
}
}
+void EditorHelp::_class_desc_scroll_to_paragraph(int p_line, bool p_save_history) {
+ // Save history before scrolling.
+ if (p_save_history) {
+ Dictionary state = get_state();
+ // Row 0 is not a state worth saving as a previous state to history.
+ if (int(state["row"]) > 0) {
+ emit_signal(SNAME("_request_save_new_history"), state);
+ }
+ }
+ class_desc->scroll_to_paragraph(p_line);
+ // Save history after scrolling.
+ if (p_save_history) {
+ emit_signal(SNAME("_request_save_new_history"), get_state());
+ if (ScriptEditorNavigationMarker::get_singleton()->is_locating()) {
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
+ }
+ }
+}
+
+bool EditorHelp::_need_save_new_history() const {
+ return !ScriptEditorNavigationMarker::get_singleton()->is_initializing() && ScriptEditorNavigationMarker::get_singleton()->is_locating();
+}
+
static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) {
bool is_native = false;
{
@@ -3352,7 +3386,7 @@ void EditorHelp::go_to_help(const String &p_help) {
void EditorHelp::go_to_class(const String &p_class) {
_wait_for_thread();
- _goto_desc(p_class);
+ _goto_desc(p_class, true);
}
void EditorHelp::update_doc() {
@@ -3362,6 +3396,21 @@ void EditorHelp::update_doc() {
_update_doc();
}
+void EditorHelp::trigger_history_save_on_navigate() {
+ if (_need_save_new_history()) {
+ emit_signal(SNAME("_request_save_new_history"), get_state());
+ if (ScriptEditorNavigationMarker::get_singleton()->is_locating()) {
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
+ }
+ }
+}
+
+Dictionary EditorHelp::get_state() {
+ Dictionary state;
+ state["row"] = get_scroll(); // Use "row" to simplify the logic when processing history list.
+ return state;
+}
+
void EditorHelp::cleanup_doc() {
_wait_for_thread();
memdelete(doc);
@@ -3380,12 +3429,15 @@ Vector> EditorHelp::get_sections() {
void EditorHelp::scroll_to_section(int p_section_index) {
_wait_for_thread();
+ ScriptEditorNavigationMarker::get_singleton()->locate_begin();
int line = section_line[p_section_index].second;
if (class_desc->is_finished()) {
- class_desc->scroll_to_paragraph(line);
+ _class_desc_scroll_to_paragraph(line, _need_save_new_history());
} else {
scroll_to = line;
+ need_save_new_history = _need_save_new_history();
}
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
}
void EditorHelp::popup_search() {
@@ -3425,7 +3477,7 @@ void EditorHelp::_bind_methods() {
ClassDB::bind_method("_help_callback", &EditorHelp::_help_callback);
ADD_SIGNAL(MethodInfo("go_to_help"));
- ADD_SIGNAL(MethodInfo("request_save_history"));
+ ADD_SIGNAL(MethodInfo("_request_save_new_history", PropertyInfo(Variant::DICTIONARY, "state")));
}
void EditorHelp::init_gdext_pointers() {
diff --git a/editor/doc/editor_help.h b/editor/doc/editor_help.h
index 8b5786a6b5..4848b728dd 100644
--- a/editor/doc/editor_help.h
+++ b/editor/doc/editor_help.h
@@ -148,8 +148,11 @@ class EditorHelp : public VBoxContainer {
} theme_cache;
int scroll_to = -1;
+ bool need_save_new_history = false;
void _help_callback(const String &p_topic);
+ void _class_desc_scroll_to_paragraph(int p_line, bool p_save_history);
+ bool _need_save_new_history() const;
void _add_text(const String &p_bbcode);
bool scroll_locked = false;
@@ -175,7 +178,7 @@ class EditorHelp : public VBoxContainer {
void _class_desc_resized(bool p_force_update_theme);
int display_margin = 0;
- Error _goto_desc(const String &p_class);
+ Error _goto_desc(const String &p_class, bool p_can_trigger_save_history);
//void _update_history_buttons();
void _update_method_list(MethodType p_method_type, const Vector &p_methods);
void _update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector &p_methods);
@@ -249,6 +252,8 @@ public:
void go_to_help(const String &p_help);
void go_to_class(const String &p_class);
void update_doc();
+ void trigger_history_save_on_navigate();
+ Dictionary get_state();
Vector> get_sections();
void scroll_to_section(int p_section_index);
diff --git a/editor/gui/code_editor.cpp b/editor/gui/code_editor.cpp
index 2d4891b7cf..06893d9126 100644
--- a/editor/gui/code_editor.cpp
+++ b/editor/gui/code_editor.cpp
@@ -37,6 +37,7 @@
#include "core/string/string_builder.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
+#include "editor/script/script_editor_navigation_marker.h"
#include "editor/script/syntax_highlighters.h"
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
@@ -88,7 +89,9 @@ void GotoLinePopup::_goto_line() {
}
void GotoLinePopup::_submit() {
+ ScriptEditorNavigationMarker::get_singleton()->locate_begin();
_goto_line();
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
hide();
}
@@ -1397,12 +1400,21 @@ void CodeTextEditor::center_viewport_to_caret_if_line_invisible(int p_line) {
text_editor->call_on_all_layout_pending_finished(callable_mp(this, &CodeTextEditor::center_viewport_to_caret_if_line_invisible).bind(0));
return;
}
- if (!text_editor->is_line_in_viewport(p_line)) {
+ if (!text_editor->is_line_in_viewport(CLAMP(p_line, 0, text_editor->get_line_count() - 1))) {
text_editor->center_viewport_to_caret();
}
}
-void CodeTextEditor::goto_line(int p_line, int p_column) {
+void CodeTextEditor::trigger_history_save_on_navigate() {
+ if (!ScriptEditorNavigationMarker::get_singleton()->is_initializing() && ScriptEditorNavigationMarker::get_singleton()->is_locating()) {
+ call_on_all_layout_pending_finished(callable_mp(this, &CodeTextEditor::_emit_request_save_new_history));
+ if (ScriptEditorNavigationMarker::get_singleton()->is_locating()) {
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
+ }
+ }
+}
+
+void CodeTextEditor::goto_line_without_history(int p_line, int p_column) {
text_editor->remove_secondary_carets();
text_editor->deselect();
text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
@@ -1413,6 +1425,11 @@ void CodeTextEditor::goto_line(int p_line, int p_column) {
adjust_viewport_to_caret();
}
+void CodeTextEditor::goto_line(int p_line, int p_column) {
+ goto_line_without_history(p_line, p_column);
+ trigger_history_save_on_navigate();
+}
+
void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
text_editor->remove_secondary_carets();
text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
@@ -1420,6 +1437,7 @@ void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
text_editor->set_code_hint("");
text_editor->cancel_code_completion();
adjust_viewport_to_caret();
+ trigger_history_save_on_navigate();
}
void CodeTextEditor::goto_line_centered(int p_line, int p_column) {
@@ -1431,6 +1449,15 @@ void CodeTextEditor::goto_line_centered(int p_line, int p_column) {
text_editor->set_code_hint("");
text_editor->cancel_code_completion();
center_viewport_to_caret();
+ trigger_history_save_on_navigate();
+}
+
+void CodeTextEditor::goto_line_and_center_if_necessary(int p_line, int p_column) {
+ if (!text_editor->is_line_in_viewport(CLAMP(p_line, 0, text_editor->get_line_count() - 1))) {
+ goto_line_centered(p_line, p_column);
+ } else {
+ goto_line(p_line, p_column);
+ }
}
void CodeTextEditor::set_executing_line(int p_line) {
@@ -1441,7 +1468,7 @@ void CodeTextEditor::clear_executing_line() {
text_editor->clear_executing_lines();
}
-Variant CodeTextEditor::get_edit_state() {
+Dictionary CodeTextEditor::get_edit_state() {
Dictionary state;
state.merge(get_navigation_state());
@@ -1455,7 +1482,7 @@ Variant CodeTextEditor::get_edit_state() {
return state;
}
-Variant CodeTextEditor::get_previous_state() {
+Dictionary CodeTextEditor::get_previous_state() {
return previous_state;
}
@@ -1477,7 +1504,7 @@ void CodeTextEditor::set_preview_navigation_change(bool p_preview) {
}
}
-void CodeTextEditor::set_edit_state(const Variant &p_state) {
+void CodeTextEditor::set_edit_state(const Dictionary &p_state) {
Dictionary state = p_state;
/* update the row first as it sets the column to 0 */
@@ -1488,17 +1515,17 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) {
} else {
text_editor->set_caret_line(state["row"]);
text_editor->set_caret_column(state["column"]);
- if (int(state["scroll_position"]) == -1) {
- // Special case for previous state.
- center_viewport_to_caret();
- } else {
- text_editor->set_v_scroll(state["scroll_position"]);
- }
+ text_editor->set_v_scroll(state["scroll_position"]);
text_editor->set_h_scroll(state["h_scroll_position"]);
}
if (state.get("selection", false)) {
- text_editor->select(state["selection_from_line"], state["selection_from_column"], state["selection_to_line"], state["selection_to_column"]);
+ // Selection can be set in two ways, from->to or to->from, so we need to check the order to set it correctly.
+ if (state["row"] < state["selection_to_line"] || state["column"] < state["selection_to_column"]) {
+ text_editor->select(state["selection_to_line"], state["selection_to_column"], state["selection_from_line"], state["selection_from_column"]);
+ } else {
+ text_editor->select(state["selection_from_line"], state["selection_from_column"], state["selection_to_line"], state["selection_to_column"]);
+ }
} else {
text_editor->deselect();
}
@@ -1535,7 +1562,7 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) {
}
}
-Variant CodeTextEditor::get_navigation_state() {
+Dictionary CodeTextEditor::get_navigation_state() {
Dictionary state;
state["scroll_position"] = text_editor->get_v_scroll();
@@ -1918,6 +1945,10 @@ void CodeTextEditor::_show_goto_popup_request() {
emit_signal("show_goto_popup");
}
+void CodeTextEditor::_emit_request_save_new_history() {
+ emit_signal(SNAME("_request_save_new_history"));
+}
+
void CodeTextEditor::_bind_methods() {
ADD_SIGNAL(MethodInfo("validate_script"));
ADD_SIGNAL(MethodInfo("load_theme_settings"));
@@ -1926,6 +1957,7 @@ void CodeTextEditor::_bind_methods() {
ADD_SIGNAL(MethodInfo("show_goto_popup"));
ADD_SIGNAL(MethodInfo("navigation_preview_ended"));
ADD_SIGNAL(MethodInfo("zoomed", PropertyInfo(Variant::FLOAT, "zoom_factor")));
+ ADD_SIGNAL(MethodInfo("_request_save_new_history", PropertyInfo(Variant::DICTIONARY, "state")));
}
void CodeTextEditor::set_code_complete_func(CodeTextEditorCodeCompleteFunc p_code_complete_func, void *p_ud) {
diff --git a/editor/gui/code_editor.h b/editor/gui/code_editor.h
index a5bc8fad11..fa1c776103 100644
--- a/editor/gui/code_editor.h
+++ b/editor/gui/code_editor.h
@@ -211,6 +211,7 @@ class CodeTextEditor : public VBoxContainer {
void _zoom_to(float p_zoom_factor);
void _show_goto_popup_request();
+ void _emit_request_save_new_history();
void _update_error_content_height();
@@ -260,17 +261,20 @@ public:
void adjust_viewport_to_caret();
void center_viewport_to_caret();
void center_viewport_to_caret_if_line_invisible(int p_line);
+ void trigger_history_save_on_navigate();
+ void goto_line_without_history(int p_line, int p_column = 0);
void goto_line(int p_line, int p_column = 0);
void goto_line_selection(int p_line, int p_begin, int p_end);
void goto_line_centered(int p_line, int p_column = 0);
+ void goto_line_and_center_if_necessary(int p_line, int p_column = 0);
void set_executing_line(int p_line);
void clear_executing_line();
- Variant get_edit_state();
- void set_edit_state(const Variant &p_state);
- Variant get_navigation_state();
- Variant get_previous_state();
+ Dictionary get_edit_state();
+ void set_edit_state(const Dictionary &p_state);
+ Dictionary get_navigation_state();
+ Dictionary get_previous_state();
void store_previous_state();
bool is_previewing_navigation_change() const;
diff --git a/editor/script/script_editor_base.cpp b/editor/script/script_editor_base.cpp
index 01da19977b..77d8988e46 100644
--- a/editor/script/script_editor_base.cpp
+++ b/editor/script/script_editor_base.cpp
@@ -34,6 +34,7 @@
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "editor/editor_node.h"
+#include "editor/script/script_editor_navigation_marker.h"
#include "editor/script/script_editor_plugin.h"
#include "editor/script/syntax_highlighters.h"
#include "editor/settings/editor_settings.h"
@@ -53,10 +54,11 @@ void ScriptEditorBase::_bind_methods() {
// First use in ScriptTextEditor.
ADD_SIGNAL(MethodInfo("request_save_history"));
+ ADD_SIGNAL(MethodInfo("_request_save_new_history", PropertyInfo(Variant::DICTIONARY, "state")));
+ ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::DICTIONARY, "state")));
ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic")));
ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what")));
- ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::DICTIONARY, "state")));
ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text")));
ADD_SIGNAL(MethodInfo("go_to_method", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::STRING, "method")));
}
@@ -174,7 +176,9 @@ void TextEditorBase::EditMenus::_bookmark_item_pressed(int p_idx) {
if (p_idx < 4) { // Any item before the separator.
script_text_editor->_edit_option(bookmarks_menu->get_item_id(p_idx));
} else {
+ ScriptEditorNavigationMarker::get_singleton()->locate_begin();
script_text_editor->code_editor->goto_line_centered(bookmarks_menu->get_item_metadata(p_idx));
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
}
}
@@ -488,10 +492,14 @@ bool TextEditorBase::_edit_option(int p_op) {
code_editor->toggle_bookmark();
} break;
case BOOKMARK_GOTO_NEXT: {
+ ScriptEditorNavigationMarker::get_singleton()->locate_begin();
code_editor->goto_next_bookmark();
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
} break;
case BOOKMARK_GOTO_PREV: {
+ ScriptEditorNavigationMarker::get_singleton()->locate_begin();
code_editor->goto_prev_bookmark();
+ ScriptEditorNavigationMarker::get_singleton()->locate_end();
} break;
case BOOKMARK_REMOVE_ALL: {
code_editor->remove_all_bookmarks();
@@ -524,6 +532,20 @@ void TextEditorBase::_validate_script() {
emit_signal(SNAME("edited_script_changed"));
}
+void TextEditorBase::_emit_request_save_new_history() {
+ Dictionary state = get_edit_state();
+ state["ensure_caret_visible"] = true;
+ previous_history_line = state["row"];
+ emit_signal(SNAME("_request_save_new_history"), state);
+}
+
+void TextEditorBase::_emit_request_save_previous_state() {
+ Dictionary state = get_edit_state();
+ state["ensure_caret_visible"] = true;
+ previous_history_line = state["row"];
+ emit_signal(SNAME("request_save_previous_state"), state);
+}
+
void TextEditorBase::add_syntax_highlighter(Ref p_highlighter) {
ERR_FAIL_COND(p_highlighter.is_null());
@@ -631,6 +653,9 @@ void TextEditorBase::set_edit_state(const Variant &p_state) {
code_editor->set_edit_state(p_state);
Dictionary state = p_state;
+ if (state.has("row")) {
+ previous_history_line = state["row"];
+ }
if (state.has("syntax_highlighter")) {
for (const Ref &highlighter : highlighters) {
if (highlighter->_get_name() == String(state["syntax_highlighter"])) {
@@ -653,6 +678,7 @@ TextEditorBase::TextEditorBase() {
code_editor->connect("validate_script", callable_mp(this, &TextEditorBase::_validate_script));
code_editor->connect("load_theme_settings", callable_mp(this, &TextEditorBase::_load_theme_settings));
code_editor->connect("show_goto_popup", callable_mp(this, &TextEditorBase::_edit_option).bind(SEARCH_GOTO_LINE));
+ code_editor->connect("_request_save_new_history", callable_mp(this, &TextEditorBase::_emit_request_save_new_history));
context_menu = memnew(PopupMenu);
context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &TextEditorBase::_edit_option));
diff --git a/editor/script/script_editor_base.h b/editor/script/script_editor_base.h
index 7ecf5b8c85..f0dc6ec846 100644
--- a/editor/script/script_editor_base.h
+++ b/editor/script/script_editor_base.h
@@ -182,6 +182,10 @@ protected:
void _convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); }
+ int previous_history_line = -1;
+ void _emit_request_save_new_history();
+ void _emit_request_save_previous_state();
+
public:
virtual void add_syntax_highlighter(Ref p_highlighter) override;
virtual void set_syntax_highlighter(Ref p_highlighter);
@@ -208,9 +212,12 @@ public:
virtual void trim_final_newlines() { code_editor->trim_final_newlines(); }
virtual void insert_final_newline() { code_editor->insert_final_newline(); }
+ virtual void goto_line_without_history(int p_line, int p_column = 0) { code_editor->goto_line_without_history(p_line, p_column); }
virtual void goto_line(int p_line, int p_column = 0) { code_editor->goto_line(p_line, p_column); }
virtual void goto_line_selection(int p_line, int p_begin, int p_end) { code_editor->goto_line_selection(p_line, p_begin, p_end); }
virtual void goto_line_centered(int p_line, int p_column = 0) { code_editor->goto_line_centered(p_line, p_column); }
+ virtual void goto_line_and_center_if_necessary(int p_line, int p_column = 0) { code_editor->goto_line_and_center_if_necessary(p_line, p_column); }
+ virtual void trigger_history_save_on_navigate() { code_editor->trigger_history_save_on_navigate(); }
virtual void set_executing_line(int p_line) { code_editor->set_executing_line(p_line); }
virtual void clear_executing_line() { code_editor->clear_executing_line(); }
diff --git a/editor/script/script_editor_navigation_marker.cpp b/editor/script/script_editor_navigation_marker.cpp
new file mode 100644
index 0000000000..292ea767fc
--- /dev/null
+++ b/editor/script/script_editor_navigation_marker.cpp
@@ -0,0 +1,105 @@
+/**************************************************************************/
+/* script_editor_navigation_marker.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "script_editor_navigation_marker.h"
+
+#include "core/config/engine.h"
+#include "core/os/memory.h"
+
+ScriptEditorNavigationMarker *ScriptEditorNavigationMarker::get_singleton() {
+ if (!singleton) {
+ singleton = memnew(ScriptEditorNavigationMarker);
+ }
+ return singleton;
+}
+
+void ScriptEditorNavigationMarker::release_singleton() {
+ if (!singleton) {
+ return;
+ }
+ memdelete(singleton);
+ singleton = nullptr;
+}
+
+void ScriptEditorNavigationMarker::init_begin() {
+ init_in_progress = true;
+}
+
+void ScriptEditorNavigationMarker::init_end() {
+ init_in_progress = false;
+}
+
+void ScriptEditorNavigationMarker::locate_begin() {
+ locate_in_progress = true;
+}
+
+void ScriptEditorNavigationMarker::locate_end() {
+ locate_in_progress = false;
+ locate_end_physics_frame = Engine::get_singleton()->get_physics_frames();
+ locate_end_process_frame = Engine::get_singleton()->get_process_frames();
+}
+
+void ScriptEditorNavigationMarker::traverse_begin() {
+ traverse_in_progress = true;
+}
+
+void ScriptEditorNavigationMarker::traverse_end() {
+ traverse_in_progress = false;
+ traverse_end_physics_frame = Engine::get_singleton()->get_physics_frames();
+ traverse_end_process_frame = Engine::get_singleton()->get_process_frames();
+}
+
+bool ScriptEditorNavigationMarker::is_initializing() const {
+ return init_in_progress;
+}
+
+bool ScriptEditorNavigationMarker::is_locating() const {
+ return locate_in_progress;
+}
+
+bool ScriptEditorNavigationMarker::is_traversing() const {
+ return traverse_in_progress;
+}
+
+bool ScriptEditorNavigationMarker::is_locate_just_occured() const {
+ if (Engine::get_singleton()->is_in_physics_frame()) {
+ return locate_end_physics_frame == Engine::get_singleton()->get_physics_frames() || locate_end_physics_frame == Engine::get_singleton()->get_physics_frames() - 1;
+ } else {
+ return locate_end_process_frame == Engine::get_singleton()->get_process_frames();
+ }
+}
+
+bool ScriptEditorNavigationMarker::is_traverse_just_occured() const {
+ if (Engine::get_singleton()->is_in_physics_frame()) {
+ return traverse_end_physics_frame == Engine::get_singleton()->get_physics_frames() || traverse_end_physics_frame == Engine::get_singleton()->get_physics_frames() - 1;
+ } else {
+ return traverse_end_process_frame == Engine::get_singleton()->get_process_frames();
+ }
+}
diff --git a/editor/script/script_editor_navigation_marker.h b/editor/script/script_editor_navigation_marker.h
new file mode 100644
index 0000000000..dab689d272
--- /dev/null
+++ b/editor/script/script_editor_navigation_marker.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* script_editor_navigation_marker.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#pragma once
+
+#include
+
+class ScriptEditorNavigationMarker {
+ static inline ScriptEditorNavigationMarker *singleton = nullptr;
+
+private:
+ bool init_in_progress = false;
+
+ bool locate_in_progress = false;
+ uint64_t locate_end_physics_frame = 0;
+ uint64_t locate_end_process_frame = 0;
+
+ bool traverse_in_progress = false;
+ uint64_t traverse_end_physics_frame = 0;
+ uint64_t traverse_end_process_frame = 0;
+
+public:
+ static ScriptEditorNavigationMarker *get_singleton();
+ static void release_singleton();
+
+ void init_begin();
+ void init_end();
+ void locate_begin();
+ void locate_end();
+ void traverse_begin();
+ void traverse_end();
+
+ bool is_initializing() const;
+ bool is_locating() const;
+ bool is_traversing() const;
+
+ bool is_locate_just_occured() const;
+ bool is_traverse_just_occured() const;
+};
diff --git a/editor/script/script_editor_plugin.cpp b/editor/script/script_editor_plugin.cpp
index d41f874d00..7333547b6c 100644
--- a/editor/script/script_editor_plugin.cpp
+++ b/editor/script/script_editor_plugin.cpp
@@ -65,6 +65,7 @@
#include "editor/run/editor_run_bar.h"
#include "editor/scene/editor_scene_tabs.h"
#include "editor/script/find_in_files.h"
+#include "editor/script/script_editor_navigation_marker.h"
#include "editor/script/script_text_editor.h"
#include "editor/script/syntax_highlighters.h"
#include "editor/script/text_editor.h"
@@ -196,7 +197,9 @@ void ScriptEditor::_script_created(Ref