forked from jahd2602/godot-todo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dock.gd
427 lines (314 loc) · 11.4 KB
/
dock.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# Copyright (c) Jahn Johansen
# Modifications Copyright (c) Ivan Skodje in accordance with:
# MIT license
tool
extends EditorPlugin
# Dock Title
var dock_title = "TODOs"
# Dock
var dock = null
# Underscore added to dismiss from TO_DO list
# TO_DO Icon Texture
var texture_todo = null
# FIX_ME Icon Texture
var texture_fixme = null
# A regular expression matcher
var regex = RegEx.new()
# Refresh Time in seconds
var refresh_time = 5 # (subject to change, need more testing)
# Current time between refresh time
var time = 0
# Content Tree
var content_tree = null
# Progress bar
var progress_bar = null
# progress thread
var thread = Thread.new()
var progress_value = 0
var auto_refresh = false
# Called when the node enters the SceneTree
func _enter_tree():
# Compiles and assign the regular expression pattern to use
regex.compile("(\\#[\\s]*(TODO|FIXME))(.*)")
# Create instance of our Dock
dock = preload("scenes/todo_list.tscn").instance()
# Set Dock title
dock.set_name(dock_title)
# Get the content_tree
content_tree = dock.get_node("contents")
# Get progress bar
progress_bar = dock.get_node("progress")
# Create TODO Icon Texture
texture_todo = global.resources.get_cached_resource("res://addons/godot-todo/images/todo.png")
# Create FIXME Icon Texture
texture_fixme = global.resources.get_cached_resource("res://addons/godot-todo/images/fixme.png")
# Add the control to the lower right dock slot
add_control_to_dock(EditorPlugin.DOCK_SLOT_RIGHT_BL, dock)
# Setup Signals
dock.get_node("tool_bar/refresh").connect("pressed", self, "start_thread")
dock.get_node("tool_bar/todo").connect("pressed", self, "start_thread", ["TODO"])
dock.get_node("tool_bar/fixme").connect("pressed", self, "start_thread", ["FIXME"])
# Setup signal with item_activated (triggers when double clicking an item)
content_tree.connect("item_activated", self, "item_activated")
progress_bar.visible = false
set_process(false)
# Runs to draw progress bar
func _process(delta):
if progress_bar and progress_value != progress_bar.value:
progress_bar.value = progress_value
# Called when the node leaves the SceneTree
func _exit_tree():
# Tool needs this in order to properly get node
content_tree = dock.get_node("contents")
# Disconnect signals
dock.get_node("tool_bar/refresh").disconnect("pressed", self, "start_thread")
dock.get_node("tool_bar/todo").disconnect("pressed", self, "start_thread")
dock.get_node("tool_bar/fixme").disconnect("pressed", self, "start_thread")
# Remove the control to the lower right dock slot
remove_control_from_docks(dock)
# Start TODO thread
func start_thread(data = null):
if not (thread.is_active()):
set_process(true)
progress_bar.visible = true
progress_bar.value = 0
thread.start(self, "populate_tree", data)
# Opens the script of selected item
func item_activated():
# Get meta data
var file = content_tree.get_selected().get_metadata(0)
# Edit the given resource (script)
get_editor_interface().edit_resource(load(file))
# Populates the content tree with TODO's and FIXME's
func populate_tree(type_filter = null):
# Tool needs this in order to properly get node - Will
# output SCRIPT ERROR otherwise.
if(dock != null):
content_tree = dock.get_node("contents")
else:
return
# Clear any existing content (to allow us to repopulate without having to do extra work)
content_tree.clear()
# Create the first (root) item
var root = content_tree.create_item()
# Enable the "Expand" flag of Control.
content_tree.set_column_expand(0, true)
# Hide the root (only display children)
content_tree.set_hide_root(true)
# Get all todos and fixme's
var files = find_all_todos()
# Filter to only get results containing TODO and FIXME
if(type_filter == "TODO"):
files = filter_results(files, "TODO")
elif(type_filter == "FIXME"):
files = filter_results(files, "FIXME")
# For each file
var file_number = 0
for file in files:
yield(get_tree(), "idle_frame")
var where = file["file"]
var todos = file["todos"]
file_number += 1
progress_value = (file_number/float(files.size()))*100
# progress_bar.get_node("progress_label").text = file["file"]
# If we have todo items in the todos array
if(!todos.empty()):
# Create item that will act as our folder for TODO's/FIXME's
# that may appear inside it
var file_node = content_tree.create_item(root)
# Metadata is used in order to double click
# the item and open the file in the editor
file_node.set_metadata(0, file["file"])
# The text/name that is displayed in the content tree
file_node.set_text(0, where)
# For each TODO/FIXME
for todo in todos:
# Create an todo item, as a child of the file we
# currently are in
var todo_node = content_tree.create_item(file_node)
# Metadata is used in order to double click
# the item and open the file in the editor
todo_node.set_metadata(0, file["file"])
# The text/name that is displayed in the content tree
todo_node.set_text(0, "%03d: %s" % [todo["line"], todo["text"]])
# Tooltip message when hovering over the item
todo_node.set_tooltip(0, todo["text"])
# Setup Icons depending on type
if(todo["type"] == "TODO"):
todo_node.set_icon(0, texture_todo)
elif(todo["type"] == "FIXME"):
todo_node.set_icon(0, texture_fixme)
progress_bar.value = 100
set_process(false)
progress_bar.visible = false
thread.wait_to_finish()
# Filter results that matches type
func filter_results(unfiltered_results, type):
# Empty array to insert our results
var results = []
# For each file in the unfiltered results
for file in unfiltered_results:
# Prepare dictionary
var filtered = {}
# Set filtered file to be equal to existing file (path)
filtered["file"] = file["file"]
# Create an empty array for the todos key
filtered["todos"] = []
# For each unfiltered TODOs
for todo in file["todos"]:
# If we match the type (TODO, FIXME, etc.)
if todo["type"] == type:
# Append into filtered todos
filtered["todos"].append(todo)
# Append filtered to results
results.append(filtered)
# Return an array with filtered todos/fixme results
return results
# Locate all files within directory, containing specific extensions and optionally look recursively
func find_files(directory, extensions, recur = false):
# Create empty array that will store our results
var results = []
# Create directory variable
var dir = Directory.new()
# Attempt to open directory
if(dir.open(directory) != OK):
return results # Failed to open directory, return empty array
# Initialise the stream used to list all files and directories
dir.list_dir_begin()
# Get our first file (which may also be a sub-directory)
var file = dir.get_next()
# While we have files
while(file != ""):
# Location
var location = ""
# Set full path to file. If the directory is "res://", do not add an additional '/'
if(dir.get_current_dir() != "res://"):
location = dir.get_current_dir() + "/" + file
else:
location = dir.get_current_dir() + "" + file
# If file is equal to '.' or '..', get next file and start over
if (file in [".", ".."]):
file = dir.get_next()
continue
# If we have recursive enabled and dir is a directory
if (recur && dir.current_is_dir()):
# Run recursively and look through subfiles, then append subfile(s) to results
for subfile in find_files(location, extensions, true):
results.append(subfile)
# If we are dealing with a file, and the extension matches our specific extensions
if(!dir.current_is_dir() && file.get_extension().to_lower() in extensions):
# Append file to results
results.append(location)
# Move on to next dir file
file = dir.get_next()
# Close the current stream
dir.list_dir_end()
# Return the results containing full paths to relevant files
return results
# Find all scripts inside the node recursively
func get_all_scripts(node):
# Empty array for scripts
var scripts = []
# Get script from node
var script = node.get_script()
# If script is not null, append script
if(script != null):
scripts.append(script)
# Recursively look through each children for scripts
for child in node.get_children():
for script in get_all_scripts(child):
scripts.append(script)
# Return all scripts
return scripts
# Look for all TODO/FIXMEs
func find_all_todos():
# Get an array of all files matching expressions,
# looking recursively within the res:// folder
var files = find_files("res://", ["gd", "tscn", "xscn", "scn"], true)
# Empty array of checked files
var checked = []
# Empty array of todo files
var todos = []
# Look through each file in files
for file in files:
# If the file is a GDScript (.gd)
if (file.get_extension().to_lower() == "gd"):
# If we have previously checked this file, skip it
if(file in checked):
continue
# Create a dictionary with file path and todo's
var file_todos = {"file": file, "todos": []}
# For each todo in the file
for todo in todos_in_file(file):
# Append todo to todos array
file_todos["todos"].append(todo)
# Append file todos to todos
todos.append(file_todos)
# Append file to checked
checked.append(file)
# # If the file is a scene (.tscn, .xscn, .scn), look for built-in scripts
elif (file.get_extension().to_lower() in ["tscn", "xscn", "scn"]):
# Load instance of the scene
var scene = global.resources.get_cached_resource(file).instance()
# Load all scripts from that scene
var scripts = get_all_scripts(scene)
# For each script, get todos
for script in scripts:
# If we have previously checked this file, skip it
if (script.get_path() in checked):
continue
# Create dictionary
var file_todos = {"file": script.get_path(), "todos": []}
# For each todo in file
for todo in todos_in_string(script.get_source_code()):
# Appen todo to dictionary's todos array
file_todos["todos"].append(todo)
# Append the file todos into todos
todos.append(file_todos)
# Put file path into checked
checked.append(script.get_path())
# Return todos
return todos
# Search for TODOs in a file location and return them as an array
func todos_in_file(location):
# Empty array of todos
var todos = []
# Create file
var file = File.new()
# Attempt to open file stream
file.open(location, File.READ)
# Get each todo in an string array
todos = todos_in_string(file.get_as_text())
# Close file stream
file.close()
# Return todo array
return todos
# Search for TODOs in text and return them as an array
func todos_in_string(string):
# Get each line into an array from string
string = string.split("\n")
# Empty array of todos
var todos = []
# Keep track of lines in order to iterate
var line_count = 0
# While our string array is not empty
while (string.size()):
# Get line
var line = string[0]
# Remove line from string array
string.remove(0)
# Increment line count
line_count += 1
# Try to find pattern, return position if found
var regexMatch = regex.search(line, 0)
# If pos has been found
if regexMatch:
# Append dictionary to todos array
todos.append({
"line": line_count, # Line Number we found the match
"type": regexMatch.get_string(2), # Type match (TODO, FIXME, etc.)
"text" : regexMatch.get_string(3)
})
# Return the todos array, containing each dictionary with data needed in order
# to view line number, type of comment, and the message
return todos