Merge pull request #115767 from DexterFstone/Add-a-script-editor-keyboard-shortcut-to-show-the-documentation-tooltip-for-the-word-the-caret-is-on

Add a script editor keyboard shortcut to show the documentation tooltip for the word the caret is on
This commit is contained in:
Thaddeus Crews 2026-02-09 15:07:03 -06:00
commit b6b7f5a9de
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
4 changed files with 52 additions and 21 deletions

View file

@ -4640,6 +4640,20 @@ void EditorHelpBitTooltip::_target_gui_input(const Ref<InputEvent> &p_event) {
break;
}
}
const Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
queue_free();
}
}
void EditorHelpBitTooltip::_shortcut_pressed(Control *p_target) {
TextEdit *tx = Object::cast_to<TextEdit>(p_target);
if (tx) {
popup_under_position(
tx->get_global_position() + tx->get_caret_draw_pos() +
Point2(0, tx->get_line_height()) + tx->get_window()->get_position_with_decorations());
}
}
void EditorHelpBitTooltip::_notification(int p_what) {
@ -4659,7 +4673,7 @@ void EditorHelpBitTooltip::_notification(int p_what) {
_is_mouse_inside_tooltip = false;
_start_timer();
break;
case NOTIFICATION_INTERNAL_PROCESS:
case NOTIFICATION_INTERNAL_PROCESS: {
// A workaround to hide the tooltip since the window does not receive keyboard events
// with `FLAG_POPUP` and `FLAG_NO_FOCUS` flags, so we can't use `_input_from_window()`.
if (is_inside_tree()) {
@ -4667,9 +4681,11 @@ void EditorHelpBitTooltip::_notification(int p_what) {
queue_free();
get_parent_viewport()->set_input_as_handled();
} else if (Input::get_singleton()->is_any_key_pressed()) {
queue_free();
if (!_is_shortcut_pressed) {
queue_free();
}
} else if (!Input::get_singleton()->get_mouse_button_mask().is_empty()) {
if (!_is_mouse_inside_tooltip) {
if (!_is_mouse_inside_tooltip && !_is_shortcut_pressed) {
queue_free();
}
} else if (!Input::get_singleton()->get_last_mouse_velocity().is_zero_approx()) {
@ -4678,38 +4694,41 @@ void EditorHelpBitTooltip::_notification(int p_what) {
}
}
}
break;
} break;
}
}
Control *EditorHelpBitTooltip::make_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue, bool p_use_class_prefix) {
Control *EditorHelpBitTooltip::make_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue, bool p_use_class_prefix, bool p_shortcut) {
ERR_FAIL_NULL_V(p_target, _make_invisible_control());
// Show the custom tooltip only if it is not already visible.
// The viewport will retrigger `make_custom_tooltip()` every few seconds
// because the return control is not visible even if the custom tooltip is displayed.
if (_is_tooltip_visible || Input::get_singleton()->is_anything_pressed()) {
if (_is_tooltip_visible || (!p_shortcut && Input::get_singleton()->is_anything_pressed())) {
return _make_invisible_control();
}
EditorHelpBit *help_bit = memnew(EditorHelpBit(p_symbol, p_prologue, p_use_class_prefix, false, true));
EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target));
EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target, p_shortcut));
help_bit->connect("request_hide", callable_mp(static_cast<Node *>(tooltip), &Node::queue_free));
tooltip->add_child(help_bit);
p_target->add_child(tooltip);
help_bit->update_content_height();
tooltip->popup_under_cursor();
if (tooltip->is_shortcut_pressed()) {
tooltip->_shortcut_pressed(p_target);
} else {
tooltip->popup_under_position(tooltip->get_mouse_position());
}
return _make_invisible_control();
}
// Copy-paste from `Viewport::_gui_show_tooltip()`.
void EditorHelpBitTooltip::popup_under_cursor() {
Point2 mouse_pos = get_mouse_position();
void EditorHelpBitTooltip::popup_under_position(const Point2 &p_point) {
Point2 tooltip_offset = GLOBAL_GET_CACHED(Point2, "display/mouse_cursor/tooltip_position_offset");
Rect2 r(mouse_pos + tooltip_offset, get_contents_minimum_size());
Rect2 r(p_point + tooltip_offset, get_contents_minimum_size());
r.size = r.size.min(get_max_size());
Window *window = get_parent_visible_window();
@ -4723,7 +4742,7 @@ void EditorHelpBitTooltip::popup_under_cursor() {
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SELF_FITTING_WINDOWS) || is_embedded()) {
if (r.size.x + r.position.x > vr.size.x + vr.position.x) {
// Place it in the opposite direction. If it fails, just hug the border.
r.position.x = mouse_pos.x - r.size.x - tooltip_offset.x;
r.position.x = p_point.x - r.size.x - tooltip_offset.x;
if (r.position.x < vr.position.x) {
r.position.x = vr.position.x + vr.size.x - r.size.x;
@ -4734,7 +4753,7 @@ void EditorHelpBitTooltip::popup_under_cursor() {
if (r.size.y + r.position.y > vr.size.y + vr.position.y) {
// Same as above.
r.position.y = mouse_pos.y - r.size.y - tooltip_offset.y;
r.position.y = p_point.y - r.size.y - tooltip_offset.y;
if (r.position.y < vr.position.y) {
r.position.y = vr.position.y + vr.size.y - r.size.y;
@ -4751,9 +4770,11 @@ void EditorHelpBitTooltip::popup_under_cursor() {
popup(r);
}
EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) {
EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target, bool p_shortcut) {
ERR_FAIL_NULL(p_target);
_is_shortcut_pressed = p_shortcut;
set_theme_type_variation("TooltipPanel");
timer = memnew(Timer);

View file

@ -369,22 +369,26 @@ class EditorHelpBitTooltip : public PopupPanel {
Timer *timer = nullptr;
uint64_t _enter_tree_time = 0;
bool _is_mouse_inside_tooltip = false;
bool _is_shortcut_pressed = false;
static Control *_make_invisible_control();
void _start_timer();
void _target_gui_input(const Ref<InputEvent> &p_event);
void _shortcut_pressed(Control *p_target);
protected:
void _notification(int p_what);
public:
// The returned control is an orphan node, which is to make the standard tooltip invisible.
[[nodiscard]] static Control *make_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue = String(), bool p_use_class_prefix = false);
[[nodiscard]] static Control *make_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue = String(), bool p_use_class_prefix = false, bool p_shortcut = false);
void popup_under_cursor();
void popup_under_position(const Point2 &p_point);
EditorHelpBitTooltip(Control *p_target);
bool is_shortcut_pressed() const { return _is_shortcut_pressed; }
EditorHelpBitTooltip(Control *p_target, bool p_shortcut = false);
};
class EditorSyntaxHighlighter;

View file

@ -184,6 +184,7 @@ ScriptTextEditor::EditMenusSTE::EditMenusSTE() {
edit_menu_convert_indent->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent"), EDIT_AUTO_INDENT);
search_menu->get_popup()->add_separator();
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/show_tooltip"), SHOW_TOOLTIP_AT_CARET);
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL);
breakpoints_menu = memnew(PopupMenu);
@ -1300,7 +1301,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
}
}
void ScriptTextEditor::_show_symbol_tooltip(const String &p_symbol, int p_row, int p_column) {
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;
}
@ -1418,7 +1419,7 @@ void ScriptTextEditor::_show_symbol_tooltip(const String &p_symbol, int p_row, i
}
if (!doc_symbol.is_empty() || !debug_value.is_empty()) {
Control *tmp = EditorHelpBitTooltip::make_tooltip(code_editor->get_text_editor(), doc_symbol, debug_value, true);
Control *tmp = EditorHelpBitTooltip::make_tooltip(code_editor->get_text_editor(), doc_symbol, debug_value, true, p_shortcut);
memdelete(tmp);
}
}
@ -1793,6 +1794,9 @@ bool ScriptTextEditor::_edit_option(int p_op) {
}
code_editor->goto_line_centered(bpoints[bpoint_idx]);
} break;
case SHOW_TOOLTIP_AT_CARET: {
_show_symbol_tooltip(tx->get_word_under_caret(), tx->get_caret_line(), tx->get_caret_column(), true);
} break;
case HELP_CONTEXTUAL: {
String text = tx->get_selected_text(0);
if (text.is_empty()) {
@ -2565,6 +2569,7 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT("script_text_editor/replace_in_files", TTRC("Replace in Files..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R);
ED_SHORTCUT("script_text_editor/show_tooltip", TTRC("Show Tooltip"), KeyModifierMask::ALT | Key::SLASH, true);
ED_SHORTCUT("script_text_editor/contextual_help", TTRC("Contextual Help"), KeyModifierMask::ALT | Key::F1);
ED_SHORTCUT_OVERRIDE("script_text_editor/contextual_help", "macos", KeyModifierMask::ALT | KeyModifierMask::SHIFT | Key::SPACE);
@ -2598,7 +2603,7 @@ void ScriptTextEditor::_enable_code_editor() {
code_editor->connect("show_errors_panel", callable_mp(this, &ScriptTextEditor::_show_errors_panel));
code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));
code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol));
code_editor->get_text_editor()->connect("symbol_hovered", callable_mp(this, &ScriptTextEditor::_show_symbol_tooltip));
code_editor->get_text_editor()->connect("symbol_hovered", callable_mp(this, &ScriptTextEditor::_show_symbol_tooltip).bind(false));
code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol));
code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes));
code_editor->get_text_editor()->connect("gutter_removed", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes));

View file

@ -115,6 +115,7 @@ class ScriptTextEditor : public CodeEditorBase {
DEBUG_GOTO_NEXT_BREAKPOINT,
DEBUG_GOTO_PREV_BREAKPOINT,
SHOW_TOOLTIP_AT_CARET,
HELP_CONTEXTUAL,
LOOKUP_SYMBOL,
};
@ -191,7 +192,7 @@ protected:
void _lookup_symbol(const String &p_symbol, int p_row, int p_column);
void _validate_symbol(const String &p_symbol);
void _show_symbol_tooltip(const String &p_symbol, int p_row, int p_column);
void _show_symbol_tooltip(const String &p_symbol, int p_row, int p_column, bool p_shortcut = false);
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;