Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 151 additions & 3 deletions editor/script/script_text_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "editor/editor_string_names.h"
#include "editor/gui/editor_toaster.h"
#include "editor/inspector/editor_context_menu_plugin.h"
#include "editor/docks/scene_tree_dock.h"
#include "editor/inspector/editor_inspector.h"
#include "editor/inspector/multi_node_edit.h"
#include "editor/script/syntax_highlighters.h"
Expand All @@ -55,6 +56,7 @@
#include "editor/themes/editor_scale.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/item_list.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/split_container.h"
#include "scene/main/scene_tree.h"
Expand Down Expand Up @@ -866,7 +868,7 @@ void ScriptTextEditor::_validate_script() {
if (errors.size() > 0) {
code_editor->set_error_pos(errors.front()->get().line - 1, errors.front()->get().column - 1);
const String message = errors.front()->get().message.replace("[", "[lb]");
const Point2i display_pos = code_editor->get_pos_for_display(code_editor->get_error_pos());
const Point2i display_pos = code_editor->get_error_pos() + Point2i(1, 1);
const String error_text = vformat(TTR("Error at ([hint=Line %d, column %d]%d, %d[/hint]):"), display_pos.x, display_pos.y, display_pos.x, display_pos.y) + " " + message;
code_editor->set_error(error_text);
}
Expand Down Expand Up @@ -1188,6 +1190,12 @@ void ScriptTextEditor::_on_caret_moved() {
}

void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_column) {
CodeEdit *text_edit = code_editor->get_text_editor();
String nodepath = text_edit->get_nodepath_at_pos(p_row, p_column);
if (!nodepath.is_empty() && _navigate_nodepath(p_row, p_column)) {
return;
}

Ref<Script> script = edited_res;
Node *base = get_tree()->get_edited_scene_root();
if (base) {
Expand Down Expand Up @@ -1293,7 +1301,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
// Check for Autoload scenes.
const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(p_symbol);
if (info.is_singleton) {
EditorNode::get_singleton()->open_scene(info.path);
EditorNode::get_singleton()->load_scene(info.path);
}
} else if (p_symbol.is_relative_path()) {
// Every symbol other than absolute path is relative path so keep this condition at last.
Expand All @@ -1308,7 +1316,28 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
CodeEdit *text_edit = code_editor->get_text_editor();

Ref<Script> script = edited_res;
Node *base = get_tree()->get_edited_scene_root();
Node *scene_root = get_tree()->get_edited_scene_root();
if (scene_root) {
NodePath np(p_symbol);
if (!np.is_empty()) {
if (np.is_absolute()) {
if (scene_root->get_node_or_null(np)) {
text_edit->set_symbol_lookup_word_as_valid(true);
return;
}
} else {
Vector<Node *> script_nodes = _find_all_node_for_script(scene_root, scene_root, script);
for (Node *owner : script_nodes) {
if (owner->get_node_or_null(np)) {
text_edit->set_symbol_lookup_word_as_valid(true);
return;
}
}
}
}
}

Node *base = scene_root;
if (base) {
base = _find_node_for_script(base, base, script);
}
Expand All @@ -1331,6 +1360,104 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
}
}

bool ScriptTextEditor::_navigate_nodepath(int p_row, int p_column) {
CodeEdit *text_edit = code_editor->get_text_editor();
String nodepath = text_edit->get_nodepath_at_pos(p_row, p_column);
if (nodepath.is_empty()) {
return false;
}

Node *scene_root = get_tree()->get_edited_scene_root();
if (!scene_root) {
return false;
}

Ref<Script> script = edited_res;
Vector<Node *> script_nodes = _find_all_node_for_script(scene_root, scene_root, script);
if (script_nodes.is_empty()) {
EditorToaster::get_singleton()->popup_str(
vformat(TTR("NodePath '%s': script is not attached to any node in the current scene."), nodepath),
EditorToaster::SEVERITY_WARNING
);
return true;
}

NodePath np(nodepath);
if (np.is_absolute()) {
Node *target = scene_root->get_node_or_null(np);
if (target) {
_navigate_to_scene_node(target);
return true;
}

EditorToaster::get_singleton()->popup_str(
vformat(TTR("NodePath '%s' could not be resolved in the current scene."), nodepath),
EditorToaster::SEVERITY_WARNING
);
return true;
}

Vector<Node *> resolved;
HashSet<Node *> unique_nodes;
for (Node *owner : script_nodes) {
Node *target = owner->get_node_or_null(np);
if (target && !unique_nodes.has(target)) {
unique_nodes.insert(target);
resolved.push_back(target);
}
}

if (resolved.is_empty()) {
EditorToaster::get_singleton()->popup_str(
vformat(TTR("NodePath '%s' could not be resolved in the current scene."), nodepath),
EditorToaster::SEVERITY_WARNING
);
return true;
}

if (resolved.size() == 1) {
_navigate_to_scene_node(resolved[0]);
return true;
}

_pending_nodepath_nodes.clear();
nodepath_select_list->clear();
for (Node *node : resolved) {
NodePath scene_path = scene_root->get_path_to(node);
int item = nodepath_select_list->add_item(vformat(TTR("%s (%s)"), String(scene_path), node->get_class()));
nodepath_select_list->set_item_tooltip(item, vformat(TTR("Type: %s"), node->get_class()));
_pending_nodepath_nodes.push_back(node);
}

nodepath_select_dialog->set_title(vformat(TTR("Select target for NodePath: %s"), nodepath));
nodepath_select_label->set_text(vformat(TTR("%d matches for NodePath '%s'. Select the target node:"), resolved.size(), nodepath));
nodepath_select_list->select(0);
nodepath_select_list->grab_focus();
nodepath_select_dialog->popup_centered(Size2(420, 320) * EDSCALE);
return true;
}

void ScriptTextEditor::_navigate_to_scene_node(Node *p_node) {
SceneTreeDock::get_singleton()->set_selected(p_node, true);
}

void ScriptTextEditor::_nodepath_select_confirmed() {
PackedInt32Array selected = nodepath_select_list->get_selected_items();
if (selected.is_empty()) {
return;
}

int idx = selected[0];
if (idx < 0 || idx >= _pending_nodepath_nodes.size()) {
return;
}

Node *node = _pending_nodepath_nodes[idx];
if (node) {
_navigate_to_scene_node(node);
}
}

void ScriptTextEditor::_show_symbol_tooltip(const String &p_symbol, int p_row, int p_column, bool p_shortcut) {
if (!EDITOR_GET("text_editor/behavior/documentation/enable_tooltips").booleanize()) {
return;
Expand Down Expand Up @@ -2705,6 +2832,27 @@ void ScriptTextEditor::_enable_code_editor() {
add_child(quick_open);

add_child(connection_info_dialog);

nodepath_select_dialog = memnew(AcceptDialog);
nodepath_select_dialog->set_ok_button_text(TTR("Navigate"));
nodepath_select_label = memnew(Label);
nodepath_select_label->set_text(TTR("Multiple nodes found. Select the target node:"));
nodepath_select_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
nodepath_select_list = memnew(ItemList);
nodepath_select_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
nodepath_select_list->set_select_mode(ItemList::SELECT_SINGLE);
nodepath_select_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
nodepath_select_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);

VBoxContainer *np_vbc = memnew(VBoxContainer);
np_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
np_vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
np_vbc->add_child(nodepath_select_label);
np_vbc->add_child(nodepath_select_list);
nodepath_select_dialog->add_child(np_vbc);

nodepath_select_dialog->connect("confirmed", callable_mp(this, &ScriptTextEditor::_nodepath_select_confirmed));
add_child(nodepath_select_dialog);
}

ScriptTextEditor::ScriptTextEditor() {
Expand Down
10 changes: 10 additions & 0 deletions editor/script/script_text_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
#include "scene/gui/tree.h"

class RichTextLabel;
class ItemList;
class Node;

class ConnectionInfoDialog : public AcceptDialog {
GDCLASS(ConnectionInfoDialog, AcceptDialog);
Expand Down Expand Up @@ -81,6 +83,10 @@ class ScriptTextEditor : public CodeEditorBase {

ScriptEditorQuickOpen *quick_open = nullptr;
ConnectionInfoDialog *connection_info_dialog = nullptr;
AcceptDialog *nodepath_select_dialog = nullptr;
Label *nodepath_select_label = nullptr;
ItemList *nodepath_select_list = nullptr;
Vector<Node *> _pending_nodepath_nodes;

int connection_gutter = -1;
void _gutter_clicked(int p_line, int p_gutter);
Expand Down Expand Up @@ -195,6 +201,10 @@ class ScriptTextEditor : public CodeEditorBase {

void _show_symbol_tooltip(const String &p_symbol, int p_row, int p_column, bool p_shortcut = false);

bool _navigate_nodepath(int p_row, int p_column);
void _navigate_to_scene_node(Node *p_node);
void _nodepath_select_confirmed();

Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
Expand Down
75 changes: 75 additions & 0 deletions scene/gui/code_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2713,6 +2713,75 @@ String CodeEdit::get_text_with_cursor_char(int p_line, int p_column) const {
return result.as_string();
}

String CodeEdit::get_nodepath_at_pos(int p_line, int p_column) const {
if (p_line < 0 || p_column < 0) {
return String();
}
const String &line = get_line(p_line);
if (line.is_empty() || p_column < 0 || p_column > line.length()) {
return String();
}

auto is_nodepath_char = [](char32_t c) {
return is_ascii_identifier_char(c) || c == '/' || c == '.';
};

// NodePath after $:
int left = p_column;
bool quoted = false;
while (left > 0 && (is_nodepath_char(line[left - 1]) || line[left - 1] == '"')) {
left--;
}

if (left > 0 && line[left - 1] == '$') {
if (left < line.length() && line[left] == '"') {
quoted = true;
left++;
}

int right = p_column;
while (right < line.length() && is_nodepath_char(line[right])) {
right++;
}
if (quoted && right < line.length() && line[right] == '"') {
quoted = false;
}
if (right > left && !quoted) {
return line.substr(left, right - left);
}
}

// NodePath from get_node("..."):
left = p_column;
while (left > 0 && is_nodepath_char(line[left - 1])) {
left--;
}

int start = left;
if (start > 0 && line[start - 1] == '"') {
start--;
} else {
return String();
}
if (start > 0 && line[start - 1] == '^') {
start--;
}

static const String get_node_prefix = "get_node(";
const int prefix_len = get_node_prefix.length();
if (start >= prefix_len && line.substr(start - prefix_len, prefix_len) == get_node_prefix) {
int right = p_column;
while (right < line.length() && is_nodepath_char(line[right])) {
right++;
}
if (right > left) {
return line.substr(left, right - left);
}
}

return String();
}

String CodeEdit::get_lookup_word(int p_line, int p_column) const {
if (p_line < 0 || p_column < 0) {
return String();
Expand All @@ -2729,6 +2798,12 @@ String CodeEdit::get_lookup_word(int p_line, int p_column) const {
return get_line(start_line).substr(start_column, end_column - start_column - 1);
}
}

String nodepath = get_nodepath_at_pos(p_line, p_column);
if (!nodepath.is_empty()) {
return nodepath;
}

return get_word(p_line, p_column);
}

Expand Down
1 change: 1 addition & 0 deletions scene/gui/code_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ class CodeEdit : public TextEdit {
String get_text_for_symbol_lookup() const;
String get_text_with_cursor_char(int p_line, int p_column) const;
String get_lookup_word(int p_line, int p_column) const;
String get_nodepath_at_pos(int p_line, int p_column) const;

void set_symbol_lookup_word_as_valid(bool p_valid);

Expand Down
Loading
Loading