Skip to content

Commit 09c5172

Browse files
committed
Quick Search implementation
1 parent 1b61b13 commit 09c5172

File tree

3 files changed

+298
-5
lines changed

3 files changed

+298
-5
lines changed

addons/script-ide/plugin.gd

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ const SCRIPT_IDE: StringName = &"plugin/script_ide/"
1111
const OUTLINE_POSITION_RIGHT: StringName = SCRIPT_IDE + &"outline_position_right"
1212
## Editor setting to control whether private members (annotated with '_' should be hidden or not)
1313
const HIDE_PRIVATE_MEMBERS: StringName = SCRIPT_IDE + &"hide_private_members"
14-
## Editor setting to control whether we want to auto navigate to the script in the filesystem when selected
15-
const AUTO_NAVIGATE_IN_FS: StringName = SCRIPT_IDE + &"auto_navigate_in_fs"
14+
## Editor setting to control whether we want to auto navigate to the script
15+
## in the filesystem (dock) when selected
16+
const AUTO_NAVIGATE_IN_FS: StringName = SCRIPT_IDE + &"auto_navigate_in_filesystem_dock"
1617
## Editor setting to control whether the script list should be visible or not
1718
const SCRIPT_LIST_VISIBLE: StringName = SCRIPT_IDE + &"script_list_visible"
1819
## Editor setting for the 'Open Outline Popup' shortcut
1920
const OPEN_OUTLINE_POPUP: StringName = SCRIPT_IDE + &"open_outline_popup"
2021
## Editor setting for the 'Open Scripts Popup' shortcut
2122
const OPEN_SCRIPTS_POPUP: StringName = SCRIPT_IDE + &"open_scripts_popup"
23+
## Editor setting for the 'Open Scripts Popup' shortcut
24+
const OPEN_QUICK_SEARCH_POPUP: StringName = SCRIPT_IDE + &"open_quick_search_popup"
2225

2326
const GETTER: StringName = &"get"
2427
const SETTER: StringName = &"set"
@@ -46,6 +49,7 @@ var hide_private_members: bool = false
4649
var is_auto_navigate_in_fs: bool = true
4750
var open_outline_popup_shc: Shortcut
4851
var open_scripts_popup_shc: Shortcut
52+
var open_quick_search_popup_shc: Shortcut
4953
#endregion
5054

5155
#region Existing controls we modify
@@ -69,6 +73,7 @@ var outline_popup: PopupPanel
6973
var filter_box: HBoxContainer
7074

7175
var scripts_popup: PopupPanel
76+
var quick_open_popup: PopupPanel
7277

7378
var class_btn: Button
7479
var constant_btn: Button
@@ -91,11 +96,14 @@ var last_tab_hovered: int = -1
9196
var sync_script_list: bool = false
9297
var suppress_settings_sync: bool = false
9398

99+
const SHORTCUT_INTERVAL: int = 400
100+
var last_shortcut_time: int = -SHORTCUT_INTERVAL
101+
94102
#region Enter / Exit -> Plugin setup
95103
## Change the Godot script UI and transform into an IDE like UI
96104
func _enter_tree() -> void:
97105
var script_path: String = get_script().get_path().get_base_dir()
98-
106+
99107
keyword_icon = create_editor_texture(load(script_path.path_join("icon/keyword.svg")))
100108
func_icon = create_editor_texture(load(script_path.path_join("icon/func.svg")))
101109
func_get_icon = create_editor_texture(load(script_path.path_join("icon/func_get.svg")))
@@ -144,8 +152,19 @@ func _enter_tree() -> void:
144152
editor_settings.set_setting(OPEN_SCRIPTS_POPUP, shortcut)
145153
editor_settings.set_initial_value(OPEN_SCRIPTS_POPUP, shortcut, false)
146154

155+
if (!editor_settings.has_setting(OPEN_QUICK_SEARCH_POPUP)):
156+
var shortcut: Shortcut = Shortcut.new()
157+
var event: InputEventKey = InputEventKey.new()
158+
event.device = -1
159+
event.keycode = KEY_SHIFT
160+
161+
shortcut.events = [ event ]
162+
editor_settings.set_setting(OPEN_QUICK_SEARCH_POPUP, shortcut)
163+
editor_settings.set_initial_value(OPEN_QUICK_SEARCH_POPUP, shortcut, false)
164+
147165
open_outline_popup_shc = editor_settings.get_setting(OPEN_OUTLINE_POPUP)
148166
open_scripts_popup_shc = editor_settings.get_setting(OPEN_SCRIPTS_POPUP)
167+
open_quick_search_popup_shc = editor_settings.get_setting(OPEN_QUICK_SEARCH_POPUP)
149168

150169
# Update on filesystem changed (e.g. save operation).
151170
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
@@ -307,6 +326,9 @@ func _exit_tree() -> void:
307326
if (outline_popup != null):
308327
outline_popup.free()
309328

329+
if (quick_open_popup != null):
330+
quick_open_popup.free()
331+
310332
get_editor_settings().settings_changed.disconnect(sync_settings)
311333
#endregion
312334

@@ -317,13 +339,20 @@ func _process(delta: float) -> void:
317339

318340
## Process the user defined shortcuts
319341
func _shortcut_input(event: InputEvent) -> void:
342+
if (open_quick_search_popup_shc.matches_event(event) && event.is_released()):
343+
var old_time: int = last_shortcut_time
344+
last_shortcut_time = Time.get_ticks_msec()
345+
346+
if (last_shortcut_time - old_time <= SHORTCUT_INTERVAL):
347+
get_viewport().set_input_as_handled()
348+
open_quick_search()
349+
return
350+
320351
if (open_outline_popup_shc.matches_event(event)):
321352
get_viewport().set_input_as_handled()
322-
323353
open_outline_popup()
324354
elif (open_scripts_popup_shc.matches_event(event)):
325355
get_viewport().set_input_as_handled()
326-
327356
open_scripts_popup()
328357

329358
## Schedules an update on the next frame
@@ -342,6 +371,15 @@ func update_editor():
342371
update_outline_cache()
343372
update_outline()
344373

374+
func open_quick_search():
375+
if (quick_open_popup == null):
376+
var script_path: String = get_script().get_path().get_base_dir()
377+
quick_open_popup = load(script_path.path_join("quickopen/quick_open_panel.tscn")).instantiate()
378+
379+
if (quick_open_popup.get_parent() != null):
380+
quick_open_popup.get_parent().remove_child(quick_open_popup)
381+
quick_open_popup.popup_exclusive_on_parent(EditorInterface.get_script_editor(), get_center_editor_rect())
382+
345383
func create_set_scripts_popup():
346384
panel_container = scripts_item_list.get_parent().get_parent()
347385

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
@tool
2+
extends PopupPanel
3+
4+
@onready var files_list: ItemList = %FilesList
5+
6+
@onready var all_btn: Button = %AllBtn
7+
@onready var scene_btn: Button = %SceneBtn
8+
@onready var gd_script_btn: Button = %GdScriptBtn
9+
@onready var resource_btn: Button = %ResourceBtn
10+
@onready var other_btn: Button = %OtherBtn
11+
12+
@onready var search_option_btn: OptionButton = %SearchOptionBtn
13+
@onready var filter_txt: LineEdit = %FilterTxt
14+
15+
var scenes: Array[FileData]
16+
var scripts: Array[FileData]
17+
var resources: Array[FileData]
18+
var others: Array[FileData]
19+
20+
var is_rebuild_cache: bool = true
21+
22+
func _ready() -> void:
23+
files_list.item_selected.connect(open_file)
24+
search_option_btn.item_selected.connect(rebuild_cache_and_ui.unbind(1))
25+
filter_txt.right_icon = EditorInterface.get_base_control().get_theme_icon(&"Search", &"EditorIcons")
26+
filter_txt.text_changed.connect(fill_files_list.unbind(1))
27+
28+
all_btn.toggled.connect(fill_files_list_if_toggled)
29+
scene_btn.toggled.connect(fill_files_list_if_toggled)
30+
gd_script_btn.toggled.connect(fill_files_list_if_toggled)
31+
resource_btn.toggled.connect(fill_files_list_if_toggled)
32+
other_btn.toggled.connect(fill_files_list_if_toggled)
33+
34+
about_to_popup.connect(on_show)
35+
36+
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
37+
file_system.filesystem_changed.connect(schedule_rebuild)
38+
39+
func open_file(index: int):
40+
var file: String = files_list.get_item_metadata(index)
41+
EditorInterface.edit_resource(load(file))
42+
43+
func schedule_rebuild():
44+
is_rebuild_cache = true
45+
46+
func on_show():
47+
if (search_option_btn.selected != 0):
48+
search_option_btn.selected = 0
49+
50+
is_rebuild_cache = true
51+
52+
var rebuild_ui: bool = false
53+
var all_btn_not_pressed: bool = all_btn.button_pressed != true
54+
rebuild_ui = is_rebuild_cache || all_btn_not_pressed
55+
56+
if (is_rebuild_cache):
57+
rebuild_cache()
58+
59+
if (rebuild_ui):
60+
if (all_btn_not_pressed):
61+
# Triggers the ui update.
62+
all_btn.button_pressed = true
63+
else:
64+
fill_files_list()
65+
66+
filter_txt.select_all()
67+
filter_txt.grab_focus()
68+
69+
func rebuild_cache():
70+
scenes.clear()
71+
scripts.clear()
72+
resources.clear()
73+
others.clear()
74+
75+
build_file_cache()
76+
77+
func rebuild_cache_and_ui():
78+
rebuild_cache()
79+
fill_files_list()
80+
81+
func build_file_cache():
82+
var dir: EditorFileSystemDirectory = EditorInterface.get_resource_filesystem().get_filesystem()
83+
build_file_cache_dir(dir)
84+
85+
func build_file_cache_dir(dir: EditorFileSystemDirectory):
86+
for index: int in dir.get_subdir_count():
87+
build_file_cache_dir(dir.get_subdir(index))
88+
89+
for index: int in dir.get_file_count():
90+
var file: String = dir.get_file_path(index)
91+
if (search_option_btn.get_selected_id() == 0 && file.begins_with("res://addons")):
92+
continue
93+
94+
var last_delim: int = file.rfind("/")
95+
96+
var file_name: String = file.substr(last_delim + 1)
97+
var file_structure: String = &" - ."
98+
if (file_name.length() + 6 != file.length()):
99+
file_structure = " - " + file.substr(6, last_delim - 6) + ""
100+
101+
file_name = file_name + file_structure
102+
103+
var file_data: FileData = FileData.new()
104+
file_data.file = file
105+
file_data.file_name = file_name
106+
file_data.file_type = dir.get_file_type(index)
107+
108+
if (file_data.file_type == &"Resource"):
109+
file_data.file_type = &"Object"
110+
111+
match (file.get_extension()):
112+
&"tscn": scenes.append(file_data)
113+
&"gd": scripts.append(file_data)
114+
&"tres": resources.append(file_data)
115+
&"gdshader": resources.append(file_data)
116+
_: others.append(file_data)
117+
118+
func fill_files_list_if_toggled(is_toggled: bool):
119+
if (is_toggled):
120+
fill_files_list()
121+
122+
func fill_files_list():
123+
files_list.clear()
124+
125+
if (all_btn.button_pressed):
126+
fill_files_list_with(scenes)
127+
fill_files_list_with(scripts)
128+
fill_files_list_with(resources)
129+
fill_files_list_with(others)
130+
elif (scene_btn.button_pressed):
131+
fill_files_list_with(scenes)
132+
elif (gd_script_btn.button_pressed):
133+
fill_files_list_with(scripts)
134+
elif (resource_btn.button_pressed):
135+
fill_files_list_with(resources)
136+
elif (other_btn.button_pressed):
137+
fill_files_list_with(others)
138+
139+
func fill_files_list_with(files: Array[FileData]):
140+
var filter_text: String = filter_txt.text
141+
142+
for file_data: FileData in files:
143+
var file: String = file_data.file
144+
if (filter_text.is_empty() || filter_text.is_subsequence_ofn(file)):
145+
var icon: Texture2D = EditorInterface.get_base_control().get_theme_icon(file_data.file_type, &"EditorIcons")
146+
147+
files_list.add_item(file_data.file_name, icon)
148+
files_list.set_item_metadata(files_list.item_count - 1, file)
149+
150+
class FileData:
151+
var file: String
152+
var file_name: String
153+
var file_type: StringName
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
[gd_scene load_steps=3 format=3 uid="uid://d2pttchmj3n7q"]
2+
3+
[ext_resource type="Script" path="res://addons/script-ide/quickopen/quick_open_panel.gd" id="1_3tl1s"]
4+
5+
[sub_resource type="ButtonGroup" id="ButtonGroup_8s5oe"]
6+
resource_local_to_scene = false
7+
8+
[node name="QuickOpenPanel" type="PopupPanel"]
9+
script = ExtResource("1_3tl1s")
10+
11+
[node name="PanelContainer" type="PanelContainer" parent="."]
12+
anchors_preset = 15
13+
anchor_right = 1.0
14+
anchor_bottom = 1.0
15+
grow_horizontal = 2
16+
grow_vertical = 2
17+
size_flags_horizontal = 3
18+
size_flags_vertical = 3
19+
20+
[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
21+
layout_mode = 2
22+
theme_override_constants/margin_left = 5
23+
theme_override_constants/margin_top = 5
24+
theme_override_constants/margin_right = 5
25+
theme_override_constants/margin_bottom = 5
26+
27+
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"]
28+
layout_mode = 2
29+
theme_override_constants/separation = 5
30+
31+
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"]
32+
layout_mode = 2
33+
theme_override_constants/separation = 4
34+
35+
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
36+
layout_mode = 2
37+
size_flags_horizontal = 3
38+
theme_override_constants/separation = 2
39+
40+
[node name="AllBtn" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
41+
unique_name_in_owner = true
42+
layout_mode = 2
43+
size_flags_horizontal = 3
44+
tooltip_text = "All files"
45+
toggle_mode = true
46+
button_group = SubResource("ButtonGroup_8s5oe")
47+
text = "All"
48+
49+
[node name="SceneBtn" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
50+
unique_name_in_owner = true
51+
layout_mode = 2
52+
size_flags_horizontal = 3
53+
tooltip_text = "Scene files"
54+
toggle_mode = true
55+
button_group = SubResource("ButtonGroup_8s5oe")
56+
text = "Scene"
57+
58+
[node name="GdScriptBtn" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
59+
unique_name_in_owner = true
60+
layout_mode = 2
61+
size_flags_horizontal = 3
62+
tooltip_text = "GDScript files"
63+
toggle_mode = true
64+
button_group = SubResource("ButtonGroup_8s5oe")
65+
text = "GDScript"
66+
67+
[node name="ResourceBtn" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
68+
unique_name_in_owner = true
69+
layout_mode = 2
70+
size_flags_horizontal = 3
71+
tooltip_text = "Resource files"
72+
toggle_mode = true
73+
button_group = SubResource("ButtonGroup_8s5oe")
74+
text = "Resource"
75+
76+
[node name="OtherBtn" type="Button" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer/HBoxContainer"]
77+
unique_name_in_owner = true
78+
layout_mode = 2
79+
size_flags_horizontal = 3
80+
tooltip_text = "Other files"
81+
toggle_mode = true
82+
button_group = SubResource("ButtonGroup_8s5oe")
83+
text = "Other"
84+
85+
[node name="SearchOptionBtn" type="OptionButton" parent="PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
86+
unique_name_in_owner = true
87+
layout_mode = 2
88+
selected = 0
89+
item_count = 2
90+
popup/item_0/text = "Project"
91+
popup/item_1/text = "Project+Addons"
92+
popup/item_1/id = 1
93+
94+
[node name="FilterTxt" type="LineEdit" parent="PanelContainer/MarginContainer/VBoxContainer"]
95+
unique_name_in_owner = true
96+
layout_mode = 2
97+
placeholder_text = "Filter files"
98+
99+
[node name="FilesList" type="ItemList" parent="PanelContainer/MarginContainer/VBoxContainer"]
100+
unique_name_in_owner = true
101+
layout_mode = 2
102+
size_flags_vertical = 3

0 commit comments

Comments
 (0)