No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

plugin.gd 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. tool
  2. extends EditorPlugin
  3. const DockScene := preload("res://addons/Todo_Manager/UI/Dock.tscn")
  4. const Dock := preload("res://addons/Todo_Manager/Dock.gd")
  5. const Todo := preload("res://addons/Todo_Manager/todo_class.gd")
  6. const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd")
  7. var _dockUI : Dock
  8. #var update_thread : Thread = Thread.new()
  9. var script_cache : Array
  10. var remove_queue : Array
  11. var combined_pattern : String
  12. var refresh_lock := false # makes sure _on_filesystem_changed only triggers once
  13. func _enter_tree() -> void:
  14. _dockUI = DockScene.instance() as Control
  15. add_control_to_bottom_panel(_dockUI, "TODO")
  16. connect("resource_saved", self, "check_saved_file")
  17. get_editor_interface().get_resource_filesystem().connect("filesystem_changed", self, "_on_filesystem_changed")
  18. get_editor_interface().get_file_system_dock().connect("file_removed", self, "queue_remove")
  19. get_editor_interface().get_script_editor().connect("editor_script_changed", self, "_on_active_script_changed")
  20. _dockUI.plugin = self
  21. combined_pattern = combine_patterns(_dockUI.patterns)
  22. find_tokens_from_path(find_scripts())
  23. _dockUI.build_tree()
  24. func _exit_tree() -> void:
  25. _dockUI.create_config_file()
  26. remove_control_from_bottom_panel(_dockUI)
  27. _dockUI.queue_free()
  28. func queue_remove(file: String):
  29. for i in _dockUI.todo_items.size() - 1:
  30. if _dockUI.todo_items[i].script_path == file:
  31. _dockUI.todo_items.remove(i)
  32. func find_tokens_from_path(scripts: Array) -> void:
  33. for script_path in scripts:
  34. var file := File.new()
  35. file.open(script_path, File.READ)
  36. var contents := file.get_as_text()
  37. file.close()
  38. find_tokens(contents, script_path)
  39. func find_tokens_from_script(script: Resource) -> void:
  40. find_tokens(script.source_code, script.resource_path)
  41. func find_tokens(text: String, script_path: String) -> void:
  42. var regex = RegEx.new()
  43. # if regex.compile("#\\s*\\bTODO\\b.*|#\\s*\\bHACK\\b.*") == OK:
  44. if regex.compile(combined_pattern) == OK:
  45. var result : Array = regex.search_all(text)
  46. if result.empty():
  47. for i in _dockUI.todo_items.size():
  48. if _dockUI.todo_items[i].script_path == script_path:
  49. _dockUI.todo_items.remove(i)
  50. return # No tokens found
  51. var match_found : bool
  52. var i := 0
  53. for todo_item in _dockUI.todo_items:
  54. if todo_item.script_path == script_path:
  55. match_found = true
  56. var updated_todo_item := update_todo_item(todo_item, result, text, script_path)
  57. _dockUI.todo_items.remove(i)
  58. _dockUI.todo_items.insert(i, updated_todo_item)
  59. break
  60. i += 1
  61. if !match_found:
  62. _dockUI.todo_items.append(create_todo_item(result, text, script_path))
  63. func create_todo_item(regex_results: Array, text: String, script_path: String) -> TodoItem:
  64. var todo_item = TodoItem.new()
  65. todo_item.script_path = script_path
  66. var last_line_number := 0
  67. var lines := text.split("\n")
  68. for r in regex_results:
  69. var new_todo : Todo = create_todo(r.get_string(), script_path)
  70. new_todo.line_number = get_line_number(r.get_string(), text, last_line_number)
  71. # GD Multiline comment
  72. var trailing_line := new_todo.line_number
  73. var should_break = false
  74. while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"):
  75. for other_r in regex_results:
  76. if lines[trailing_line] in other_r.get_string():
  77. should_break = true
  78. break
  79. if should_break:
  80. break
  81. new_todo.content += "\n" + lines[trailing_line]
  82. trailing_line += 1
  83. last_line_number = new_todo.line_number
  84. todo_item.todos.append(new_todo)
  85. return todo_item
  86. func update_todo_item(todo_item: TodoItem, regex_results: Array, text: String, script_path: String) -> TodoItem:
  87. todo_item.todos.clear()
  88. var lines := text.split("\n")
  89. for r in regex_results:
  90. var new_todo : Todo = create_todo(r.get_string(), script_path)
  91. new_todo.line_number = get_line_number(r.get_string(), text)
  92. # GD Multiline comment
  93. var trailing_line := new_todo.line_number
  94. var should_break = false
  95. while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"):
  96. for other_r in regex_results:
  97. if lines[trailing_line] in other_r.get_string():
  98. should_break = true
  99. break
  100. if should_break:
  101. break
  102. new_todo.content += "\n" + lines[trailing_line]
  103. trailing_line += 1
  104. todo_item.todos.append(new_todo)
  105. return todo_item
  106. func get_line_number(what: String, from: String, start := 0) -> int:
  107. what = what.split('\n')[0] # Match first line of multiline C# comments
  108. var temp_array := from.split('\n')
  109. var lines := Array(temp_array)
  110. var line_number# = lines.find(what) + 1
  111. for i in range(start, lines.size()):
  112. if what in lines[i]:
  113. line_number = i + 1 # +1 to account of 0-based array vs 1-based line numbers
  114. break
  115. else:
  116. line_number = 0 # This is an error
  117. return line_number
  118. func check_saved_file(script: Resource) -> void:
  119. # print(script)
  120. pass
  121. # if _dockUI.auto_refresh:
  122. # if script is Script:
  123. # find_tokens_from_script(script)
  124. # _dockUI.build_tree()
  125. func _on_filesystem_changed() -> void:
  126. if !refresh_lock:
  127. if _dockUI.auto_refresh:
  128. refresh_lock = true
  129. _dockUI.get_node("Timer").start()
  130. rescan_files()
  131. func find_scripts() -> Array:
  132. var scripts : Array
  133. var directory_queue : Array
  134. var dir : Directory = Directory.new()
  135. ### FIRST PHASE ###
  136. if dir.open("res://") == OK:
  137. get_dir_contents(dir, scripts, directory_queue)
  138. else:
  139. printerr("TODO_Manager: There was an error during find_scripts() ### First Phase ###")
  140. ### SECOND PHASE ###
  141. while not directory_queue.empty():
  142. if dir.change_dir(directory_queue[0]) == OK:
  143. get_dir_contents(dir, scripts, directory_queue)
  144. else:
  145. printerr("TODO_Manager: There was an error at: " + directory_queue[0])
  146. directory_queue.pop_front()
  147. cache_scripts(scripts)
  148. return scripts
  149. func cache_scripts(scripts: Array) -> void:
  150. for script in scripts:
  151. if not script_cache.has(script):
  152. script_cache.append(script)
  153. func get_dir_contents(dir: Directory, scripts: Array, directory_queue: Array) -> void:
  154. dir.list_dir_begin(true, true)
  155. var file_name : String = dir.get_next()
  156. while file_name != "":
  157. if dir.current_is_dir():
  158. if file_name == ".import" or filename == ".mono": # Skip .import folder which should never have scripts
  159. pass
  160. else:
  161. directory_queue.append(dir.get_current_dir() + "/" + file_name)
  162. else:
  163. if file_name.ends_with(".gd") or file_name.ends_with(".cs"):
  164. if dir.get_current_dir() == "res://":
  165. scripts.append(dir.get_current_dir() + file_name)
  166. else:
  167. scripts.append(dir.get_current_dir() + "/" + file_name)
  168. file_name = dir.get_next()
  169. func rescan_files() -> void:
  170. _dockUI.todo_items.clear()
  171. script_cache.clear()
  172. combined_pattern = combine_patterns(_dockUI.patterns)
  173. find_tokens_from_path(find_scripts())
  174. _dockUI.build_tree()
  175. func combine_patterns(patterns: Array) -> String:
  176. if patterns.size() == 1:
  177. return patterns[0][0]
  178. else:
  179. # var pattern_string : String
  180. var pattern_string := "((\\/\\*)|(#|\\/\\/))\\s*("
  181. for i in range(patterns.size()):
  182. if i == 0:
  183. pattern_string += patterns[i][0]
  184. else:
  185. pattern_string += "|" + patterns[i][0]
  186. pattern_string += ")(?(2)[\\s\\S]*?\\*\\/|.*)"
  187. # if i == 0:
  188. ## pattern_string = "#\\s*" + patterns[i][0] + ".*"
  189. # pattern_string = "((\\/\\*)|(#|\\/\\/))\\s*" + patterns[i][0] + ".*" # (?(2)[\\s\\S]*?\\*\\/|.*)
  190. # else:
  191. ## pattern_string += "|" + "#\\s*" + patterns[i][0] + ".*"
  192. # pattern_string += "|" + "((\\/\\*)|(#|\\/\\/))\\s*" + patterns[i][0] + ".*"
  193. return pattern_string
  194. func create_todo(todo_string: String, script_path: String) -> Todo:
  195. var todo := Todo.new()
  196. var regex = RegEx.new()
  197. for pattern in _dockUI.patterns:
  198. if regex.compile(pattern[0]) == OK:
  199. var result : RegExMatch = regex.search(todo_string)
  200. if result:
  201. todo.pattern = pattern[0]
  202. todo.title = result.strings[0]
  203. else:
  204. continue
  205. else:
  206. printerr("Error compiling " + pattern[0])
  207. todo.content = todo_string
  208. todo.script_path = script_path
  209. return todo
  210. func _on_active_script_changed(script) -> void:
  211. if _dockUI:
  212. if _dockUI.tabs.current_tab == 1:
  213. _dockUI.build_tree()