diff --git a/addons/Todo_Manager/ColourPicker.gd b/addons/Todo_Manager/ColourPicker.gd new file mode 100644 index 0000000..984c983 --- /dev/null +++ b/addons/Todo_Manager/ColourPicker.gd @@ -0,0 +1,16 @@ +tool +extends HBoxContainer + +var colour : Color +var title : String setget set_title +var index : int + +onready var colour_picker := $TODOColourPickerButton + +func _ready() -> void: + $TODOColourPickerButton.color = colour + $Label.text = title + +func set_title(value: String) -> void: + title = value + $Label.text = value diff --git a/addons/Todo_Manager/Current.gd b/addons/Todo_Manager/Current.gd new file mode 100644 index 0000000..d4fced9 --- /dev/null +++ b/addons/Todo_Manager/Current.gd @@ -0,0 +1,45 @@ +tool +extends Panel + +signal tree_built # used for debugging + +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") +const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd") + +var sort_alphabetical := true + +onready var tree := $Tree as Tree + +func build_tree(todo_item : TodoItem, patterns : Array) -> void: + tree.clear() + var root := tree.create_item() + root.set_text(0, "Scripts") + var script := tree.create_item(root) + script.set_text(0, todo_item.get_short_path() + " -------") + script.set_metadata(0, todo_item) + for todo in todo_item.todos: + var item := tree.create_item(script) + var content_header : String = todo.content + if "\n" in todo.content: + content_header = content_header.split("\n")[0] + "..." + item.set_text(0, "(%0) - %1".format([todo.line_number, content_header], "%_")) + item.set_tooltip(0, todo.content) + item.set_metadata(0, todo) + for pattern in patterns: + if pattern[0] == todo.pattern: + item.set_custom_color(0, pattern[1]) + emit_signal("tree_built") + + +func sort_alphabetical(a, b) -> bool: + if a.script_path > b.script_path: + return true + else: + return false + +func sort_backwards(a, b) -> bool: + if a.script_path < b.script_path: + return true + else: + return false + diff --git a/addons/Todo_Manager/Dock.gd b/addons/Todo_Manager/Dock.gd new file mode 100644 index 0000000..d3d16be --- /dev/null +++ b/addons/Todo_Manager/Dock.gd @@ -0,0 +1,250 @@ +tool +extends Control + +#signal tree_built # used for debugging + +const Project := preload("res://addons/Todo_Manager/Project.gd") +const Current := preload("res://addons/Todo_Manager/Current.gd") + +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") +const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd") +const ColourPicker := preload("res://addons/Todo_Manager/UI/ColourPicker.tscn") +const Pattern := preload("res://addons/Todo_Manager/UI/Pattern.tscn") +const DEFAULT_PATTERNS := [["\\bTODO\\b", Color("96f1ad")], ["\\bHACK\\b", Color("d5bc70")], ["\\bFIXME\\b", Color("d57070")]] +const DEFAULT_SCRIPT_COLOUR := Color("ccced3") +const DEFAULT_SCRIPT_NAME := false +const DEFAULT_SORT := true + +var plugin : EditorPlugin + +var todo_items : Array + +var script_colour := Color("ccced3") +var ignore_paths := [] +var full_path := false +var sort_alphabetical := true +var auto_refresh := true + +var patterns := [["\\bTODO\\b", Color("96f1ad")], ["\\bHACK\\b", Color("d5bc70")], ["\\bFIXME\\b", Color("d57070")]] + +onready var tabs := $VBoxContainer/TabContainer as TabContainer +onready var project := $VBoxContainer/TabContainer/Project as Project +onready var current := $VBoxContainer/TabContainer/Current as Current +onready var project_tree := $VBoxContainer/TabContainer/Project/Tree as Tree +onready var current_tree := $VBoxContainer/TabContainer/Current/Tree as Tree +onready var settings_panel := $VBoxContainer/TabContainer/Settings as Panel +onready var colours_container := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3/Colours as VBoxContainer +onready var pattern_container := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns as VBoxContainer +onready var ignore_textbox := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths/TextEdit as LineEdit + +func _ready() -> void: + load_config() + populate_settings() + + +func build_tree() -> void: + if tabs: + match tabs.current_tab: + 0: + project.build_tree(todo_items, ignore_paths, patterns, sort_alphabetical, full_path) + create_config_file() + 1: + current.build_tree(get_active_script(), patterns) + create_config_file() + 2: + pass + _: + pass + + +func get_active_script() -> TodoItem: + var current_script : Script = plugin.get_editor_interface().get_script_editor().get_current_script() + if current_script: + var script_path = current_script.resource_path + for todo_item in todo_items: + if todo_item.script_path == script_path: + return todo_item + + # nothing found + var todo_item := TodoItem.new() + todo_item.script_path = script_path + return todo_item + else: + # not a script + var todo_item := TodoItem.new() + todo_item.script_path = "res://Documentation" + return todo_item + + +func go_to_script(script_path: String, line_number : int = 0) -> void: + var script := load(script_path) + plugin.get_editor_interface().edit_resource(script) + plugin.get_editor_interface().get_script_editor().goto_line(line_number - 1) + + +func sort_alphabetical(a, b) -> bool: + if a.script_path > b.script_path: + return true + else: + return false + +func sort_backwards(a, b) -> bool: + if a.script_path < b.script_path: + return true + else: + return false + + +func populate_settings() -> void: + for i in patterns.size(): + ## Create Colour Pickers + var colour_picker := ColourPicker.instance() + colour_picker.colour = patterns[i][1] + colour_picker.title = patterns[i][0] + colour_picker.index = i + colours_container.add_child(colour_picker) + colour_picker.colour_picker.connect("color_changed", self, "change_colour", [i]) + + ## Create Patterns + var pattern_edit := Pattern.instance() + pattern_edit.text = patterns[i][0] + pattern_edit.index = i + pattern_container.add_child(pattern_edit) + pattern_edit.line_edit.connect("text_changed", self, "change_pattern", [i, colour_picker]) + pattern_edit.remove_button.connect("pressed", self, "remove_pattern", [i, pattern_edit, colour_picker]) + $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns/AddPatternButton.raise() + + # path filtering + var ignore_paths_field := ignore_textbox + if !ignore_paths_field.is_connected("text_changed", self, "_on_ignore_paths_changed"): + ignore_paths_field.connect("text_changed", self, "_on_ignore_paths_changed") + var ignore_paths_text := "" + for path in ignore_paths: + ignore_paths_text += path + ", " + ignore_paths_text.rstrip(' ').rstrip(',') + ignore_paths_field.text = ignore_paths_text + + +func rebuild_settings() -> void: + for node in colours_container.get_children(): + node.queue_free() + for node in pattern_container.get_children(): + if node is Button: + continue + node.queue_free() + populate_settings() + + +#### CONFIG FILE #### +func create_config_file() -> void: + var config = ConfigFile.new() + config.set_value("scripts", "full_path", full_path) + config.set_value("scripts", "sort_alphabetical", sort_alphabetical) + config.set_value("scripts", "script_colour", script_colour) + config.set_value("scripts", "ignore_paths", ignore_paths) + + config.set_value("patterns", "patterns", patterns) + + config.set_value("config", "auto_refresh", auto_refresh) + + var err = config.save("res://addons/Todo_Manager/todo.cfg") + + +func load_config() -> void: + var config := ConfigFile.new() + if config.load("res://addons/Todo_Manager/todo.cfg") == OK: + full_path = config.get_value("scripts", "full_path", DEFAULT_SCRIPT_NAME) + sort_alphabetical = config.get_value("scripts", "sort_alphabetical", DEFAULT_SORT) + script_colour = config.get_value("scripts", "script_colour", DEFAULT_SCRIPT_COLOUR) + ignore_paths = config.get_value("scripts", "ignore_paths", []) + patterns = config.get_value("patterns", "patterns", DEFAULT_PATTERNS) + auto_refresh = config.get_value("config", "auto_refresh", true) + else: + create_config_file() + + +#### Events #### +func _on_SettingsButton_toggled(button_pressed: bool) -> void: + settings_panel.visible = button_pressed + if button_pressed == false: + create_config_file() +# plugin.find_tokens_from_path(plugin.script_cache) + if auto_refresh: + plugin.rescan_files() + +func _on_Tree_item_activated() -> void: + var item : TreeItem + match tabs.current_tab: + 0: + item = project_tree.get_selected() + 1: + item = current_tree.get_selected() + if item.get_metadata(0) is Todo: + var todo : Todo = item.get_metadata(0) + call_deferred("go_to_script", todo.script_path, todo.line_number) + else: + var todo_item = item.get_metadata(0) + call_deferred("go_to_script", todo_item.script_path) + +func _on_FullPathCheckBox_toggled(button_pressed: bool) -> void: + full_path = button_pressed + +func _on_ScriptColourPickerButton_color_changed(color: Color) -> void: + script_colour = color + +func _on_TODOColourPickerButton_color_changed(color: Color) -> void: + patterns[0][1] = color + +func _on_RescanButton_pressed() -> void: + plugin.rescan_files() + +func change_colour(colour: Color, index: int) -> void: + patterns[index][1] = colour + +func change_pattern(value: String, index: int, this_colour: Node) -> void: + patterns[index][0] = value + this_colour.title = value + +func remove_pattern(index: int, this: Node, this_colour: Node) -> void: + patterns.remove(index) + this.queue_free() + this_colour.queue_free() + +func _on_DefaultButton_pressed() -> void: + patterns = DEFAULT_PATTERNS.duplicate(true) + sort_alphabetical = DEFAULT_SORT + script_colour = DEFAULT_SCRIPT_COLOUR + full_path = DEFAULT_SCRIPT_NAME + rebuild_settings() + +func _on_AlphSortCheckBox_toggled(button_pressed: bool) -> void: + sort_alphabetical = button_pressed + +func _on_AddPatternButton_pressed() -> void: + patterns.append(["\\bplaceholder\\b", Color.white]) + rebuild_settings() + +func _on_RefreshCheckButton_toggled(button_pressed: bool) -> void: + auto_refresh = button_pressed + +func _on_Timer_timeout() -> void: + plugin.refresh_lock = false + +func _on_ignore_paths_changed(new_text: String) -> void: + var text = ignore_textbox.text + var split: Array = text.split(',') + ignore_paths.clear() + for elem in split: + if elem == " " || elem == "": + continue + ignore_paths.push_front(elem.lstrip(' ').rstrip(' ')) + # validate so no empty string slips through (all paths ignored) + var i := 0 + for path in ignore_paths: + if (path == "" || path == " "): + ignore_paths.remove(i) + i += 1 + +func _on_TabContainer_tab_changed(tab: int) -> void: + build_tree() + diff --git a/addons/Todo_Manager/Pattern.gd b/addons/Todo_Manager/Pattern.gd new file mode 100644 index 0000000..932bc9c --- /dev/null +++ b/addons/Todo_Manager/Pattern.gd @@ -0,0 +1,19 @@ +tool +extends HBoxContainer + +var text : String setget set_text +var disabled : bool +var index : int + +onready var line_edit := $LineEdit as LineEdit +onready var remove_button := $RemoveButton as Button + +func _ready() -> void: + line_edit.text = text + remove_button.disabled = disabled + + +func set_text(value: String) -> void: + text = value + if line_edit: + line_edit.text = value diff --git a/addons/Todo_Manager/Project.gd b/addons/Todo_Manager/Project.gd new file mode 100644 index 0000000..2642d5d --- /dev/null +++ b/addons/Todo_Manager/Project.gd @@ -0,0 +1,59 @@ +tool +extends Panel + +signal tree_built # used for debugging + +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") + +var sort_alphabetical := true + +onready var tree := $Tree as Tree + +func build_tree(todo_items : Array, ignore_paths : Array, patterns : Array, sort_alphabetical : bool, full_path : bool) -> void: + tree.clear() + if sort_alphabetical: + todo_items.sort_custom(self, "sort_alphabetical") + else: + todo_items.sort_custom(self, "sort_backwards") + var root := tree.create_item() + root.set_text(0, "Scripts") + for todo_item in todo_items: + var ignore := false + for ignore_path in ignore_paths: + var script_path : String = todo_item.script_path + if script_path.begins_with(ignore_path) or script_path.begins_with("res://" + ignore_path) or script_path.begins_with("res:///" + ignore_path): + ignore = true + break + if ignore: + continue + var script := tree.create_item(root) + if full_path: + script.set_text(0, todo_item.script_path + " -------") + else: + script.set_text(0, todo_item.get_short_path() + " -------") + script.set_metadata(0, todo_item) + for todo in todo_item.todos: + var item := tree.create_item(script) + var content_header : String = todo.content + if "\n" in todo.content: + content_header = content_header.split("\n")[0] + "..." + item.set_text(0, "(%0) - %1".format([todo.line_number, content_header], "%_")) + item.set_tooltip(0, todo.content) + item.set_metadata(0, todo) + for pattern in patterns: + if pattern[0] == todo.pattern: + item.set_custom_color(0, pattern[1]) + emit_signal("tree_built") + + +func sort_alphabetical(a, b) -> bool: + if a.script_path > b.script_path: + return true + else: + return false + +func sort_backwards(a, b) -> bool: + if a.script_path < b.script_path: + return true + else: + return false diff --git a/addons/Todo_Manager/UI/ColourPicker.tscn b/addons/Todo_Manager/UI/ColourPicker.tscn new file mode 100644 index 0000000..400b59f --- /dev/null +++ b/addons/Todo_Manager/UI/ColourPicker.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/Todo_Manager/ColourPicker.gd" type="Script" id=1] + +[node name="TODOColour" type="HBoxContainer"] +margin_right = 156.0 +margin_bottom = 20.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="."] +margin_top = 3.0 +margin_right = 52.0 +margin_bottom = 17.0 +text = "#TODO:" + +[node name="TODOColourPickerButton" type="ColorPickerButton" parent="."] +margin_left = 56.0 +margin_right = 156.0 +margin_bottom = 20.0 +rect_min_size = Vector2( 100, 0 ) +color = Color( 1, 1, 1, 1 ) diff --git a/addons/Todo_Manager/UI/Dock.tscn b/addons/Todo_Manager/UI/Dock.tscn new file mode 100644 index 0000000..b760796 --- /dev/null +++ b/addons/Todo_Manager/UI/Dock.tscn @@ -0,0 +1,425 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://addons/Todo_Manager/Dock.gd" type="Script" id=1] +[ext_resource path="res://addons/Todo_Manager/Project.gd" type="Script" id=2] +[ext_resource path="res://addons/Todo_Manager/Current.gd" type="Script" id=3] + +[sub_resource type="ButtonGroup" id=1] + +[sub_resource type="ButtonGroup" id=2] + +[node name="Dock" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_min_size = Vector2( 0, 200 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Header" type="HBoxContainer" parent="VBoxContainer"] +visible = false +margin_right = 1024.0 +margin_bottom = 20.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HeaderLeft" type="HBoxContainer" parent="VBoxContainer/Header"] +margin_right = 510.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 + +[node name="Title" type="Label" parent="VBoxContainer/Header/HeaderLeft"] +margin_top = 3.0 +margin_right = 71.0 +margin_bottom = 17.0 +text = "Todo Dock:" +align = 1 +valign = 1 + +[node name="HeaderRight" type="HBoxContainer" parent="VBoxContainer/Header"] +margin_left = 514.0 +margin_right = 1024.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +alignment = 2 + +[node name="SettingsButton" type="Button" parent="VBoxContainer/Header/HeaderRight"] +visible = false +margin_left = 447.0 +margin_right = 510.0 +margin_bottom = 20.0 +toggle_mode = true +text = "Settings" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="TabContainer" type="TabContainer" parent="VBoxContainer"] +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_vertical = 3 +tab_align = 0 + +[node name="Project" type="Panel" parent="VBoxContainer/TabContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 32.0 +margin_right = -4.0 +margin_bottom = -4.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Tree" type="Tree" parent="VBoxContainer/TabContainer/Project"] +anchor_right = 1.0 +anchor_bottom = 1.0 +hide_root = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Current" type="Panel" parent="VBoxContainer/TabContainer"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 32.0 +margin_right = -4.0 +margin_bottom = -4.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource( 3 ) + +[node name="Tree" type="Tree" parent="VBoxContainer/TabContainer/Current"] +anchor_right = 1.0 +anchor_bottom = 1.0 +custom_constants/draw_relationship_lines = 0 +hide_folding = true +hide_root = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Settings" type="Panel" parent="VBoxContainer/TabContainer"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 32.0 +margin_right = -4.0 +margin_bottom = -4.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/TabContainer/Settings"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer"] +margin_right = 1016.0 +margin_bottom = 564.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/margin_right = 5 +custom_constants/margin_top = 5 +custom_constants/margin_left = 5 +custom_constants/margin_bottom = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer"] +margin_left = 5.0 +margin_top = 5.0 +margin_right = 1011.0 +margin_bottom = 559.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/separation = 14 + +[node name="Scripts" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_right = 1006.0 +margin_bottom = 14.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Scripts"] +margin_right = 47.0 +margin_bottom = 14.0 +text = "Scripts:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Scripts"] +margin_left = 51.0 +margin_right = 1006.0 +margin_bottom = 14.0 +size_flags_horizontal = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_top = 28.0 +margin_right = 1006.0 +margin_bottom = 132.0 +size_flags_horizontal = 5 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer"] +margin_right = 1006.0 +margin_bottom = 104.0 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2"] +margin_right = 50.0 +margin_bottom = 104.0 +rect_min_size = Vector2( 50, 0 ) + +[node name="Scripts" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2"] +margin_left = 54.0 +margin_right = 545.0 +margin_bottom = 104.0 + +[node name="ScriptName" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +margin_right = 491.0 +margin_bottom = 24.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"] +margin_top = 5.0 +margin_right = 82.0 +margin_bottom = 19.0 +text = "Script Name:" + +[node name="FullPathCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"] +margin_left = 86.0 +margin_right = 169.0 +margin_bottom = 24.0 +group = SubResource( 1 ) +text = "Full path" + +[node name="ShortNameCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"] +margin_left = 173.0 +margin_right = 274.0 +margin_bottom = 24.0 +pressed = true +group = SubResource( 1 ) +text = "Short name" + +[node name="ScriptSort" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +margin_top = 28.0 +margin_right = 491.0 +margin_bottom = 52.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"] +margin_top = 5.0 +margin_right = 70.0 +margin_bottom = 19.0 +text = "Sort Order:" + +[node name="AlphSortCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"] +margin_left = 74.0 +margin_right = 181.0 +margin_bottom = 24.0 +pressed = true +group = SubResource( 2 ) +text = "Alphabetical" + +[node name="RAlphSortCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"] +margin_left = 185.0 +margin_right = 347.0 +margin_bottom = 24.0 +group = SubResource( 2 ) +text = "Reverse Alphabetical" + +[node name="Label2" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"] +margin_left = 351.0 +margin_top = 5.0 +margin_right = 475.0 +margin_bottom = 19.0 +custom_colors/font_color = Color( 0.392157, 0.392157, 0.392157, 1 ) +text = "(Sorted by full path)" + +[node name="ScriptColour" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +margin_top = 56.0 +margin_right = 491.0 +margin_bottom = 76.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour"] +margin_top = 3.0 +margin_right = 85.0 +margin_bottom = 17.0 +text = "Script Colour:" + +[node name="ScriptColourPickerButton" type="ColorPickerButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour"] +margin_left = 89.0 +margin_right = 189.0 +margin_bottom = 20.0 +rect_min_size = Vector2( 100, 0 ) +color = Color( 0.8, 0.807843, 0.827451, 1 ) + +[node name="IgnorePaths" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +margin_top = 80.0 +margin_right = 491.0 +margin_bottom = 104.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"] +margin_top = 5.0 +margin_right = 84.0 +margin_bottom = 19.0 +text = "Ignore Paths:" + +[node name="TextEdit" type="LineEdit" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"] +margin_left = 88.0 +margin_right = 338.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 250, 0 ) + +[node name="Label3" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"] +margin_left = 342.0 +margin_top = 5.0 +margin_right = 491.0 +margin_bottom = 19.0 +custom_colors/font_color = Color( 0.392157, 0.392157, 0.392157, 1 ) +text = "(Separated by commas)" + +[node name="TODOColours" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_top = 146.0 +margin_right = 1006.0 +margin_bottom = 160.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/TODOColours"] +margin_right = 95.0 +margin_bottom = 14.0 +text = "TODO Colours:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/TODOColours"] +margin_left = 99.0 +margin_right = 1006.0 +margin_bottom = 14.0 +size_flags_horizontal = 3 + +[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_top = 174.0 +margin_right = 1006.0 +margin_bottom = 242.0 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3"] +margin_right = 50.0 +margin_bottom = 68.0 +rect_min_size = Vector2( 50, 0 ) + +[node name="Colours" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3"] +margin_left = 54.0 +margin_right = 223.0 +margin_bottom = 68.0 + +[node name="Patterns" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_top = 256.0 +margin_right = 1006.0 +margin_bottom = 270.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Patterns"] +margin_right = 57.0 +margin_bottom = 14.0 +text = "Patterns:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Patterns"] +margin_left = 61.0 +margin_right = 1006.0 +margin_bottom = 14.0 +size_flags_horizontal = 3 + +[node name="HBoxContainer4" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_top = 284.0 +margin_right = 1006.0 +margin_bottom = 388.0 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4"] +margin_right = 50.0 +margin_bottom = 104.0 +rect_min_size = Vector2( 50, 0 ) + +[node name="Patterns" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4"] +margin_left = 54.0 +margin_right = 282.0 +margin_bottom = 104.0 + +[node name="AddPatternButton" type="Button" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns"] +margin_top = 84.0 +margin_right = 37.0 +margin_bottom = 104.0 +size_flags_horizontal = 0 +text = "Add" + +[node name="Config" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_top = 402.0 +margin_right = 1006.0 +margin_bottom = 416.0 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Config"] +margin_right = 43.0 +margin_bottom = 14.0 +text = "Config:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Config"] +margin_left = 47.0 +margin_right = 1006.0 +margin_bottom = 14.0 +size_flags_horizontal = 3 + +[node name="HBoxContainer5" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +margin_top = 430.0 +margin_right = 1006.0 +margin_bottom = 494.0 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5"] +margin_right = 50.0 +margin_bottom = 64.0 +rect_min_size = Vector2( 50, 0 ) + +[node name="Patterns" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5"] +margin_left = 54.0 +margin_right = 216.0 +margin_bottom = 64.0 + +[node name="RefreshCheckButton" type="CheckButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"] +margin_right = 162.0 +margin_bottom = 40.0 +pressed = true +text = "Auto Refresh" + +[node name="DefaultButton" type="Button" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"] +margin_top = 44.0 +margin_right = 113.0 +margin_bottom = 64.0 +size_flags_horizontal = 0 +text = "Reset to default" + +[node name="Timer" type="Timer" parent="."] +one_shot = true + +[node name="RescanButton" type="Button" parent="."] +anchor_left = 1.0 +anchor_right = 1.0 +margin_left = -97.0 +margin_right = -6.0 +margin_bottom = 20.0 +text = "Rescan Files" +[connection signal="toggled" from="VBoxContainer/Header/HeaderRight/SettingsButton" to="." method="_on_SettingsButton_toggled"] +[connection signal="tab_changed" from="VBoxContainer/TabContainer" to="." method="_on_TabContainer_tab_changed"] +[connection signal="item_activated" from="VBoxContainer/TabContainer/Project/Tree" to="." method="_on_Tree_item_activated"] +[connection signal="item_activated" from="VBoxContainer/TabContainer/Current/Tree" to="." method="_on_Tree_item_activated"] +[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName/FullPathCheckBox" to="." method="_on_FullPathCheckBox_toggled"] +[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort/AlphSortCheckBox" to="." method="_on_AlphSortCheckBox_toggled"] +[connection signal="color_changed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour/ScriptColourPickerButton" to="." method="_on_ScriptColourPickerButton_color_changed"] +[connection signal="pressed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns/AddPatternButton" to="." method="_on_AddPatternButton_pressed"] +[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/RefreshCheckButton" to="." method="_on_RefreshCheckButton_toggled"] +[connection signal="pressed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/DefaultButton" to="." method="_on_DefaultButton_pressed"] +[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] +[connection signal="pressed" from="RescanButton" to="." method="_on_RescanButton_pressed"] diff --git a/addons/Todo_Manager/UI/Pattern.tscn b/addons/Todo_Manager/UI/Pattern.tscn new file mode 100644 index 0000000..d7ab839 --- /dev/null +++ b/addons/Todo_Manager/UI/Pattern.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/Todo_Manager/Pattern.gd" type="Script" id=1] + +[node name="Pattern" type="HBoxContainer"] +margin_right = 228.0 +margin_bottom = 24.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="LineEdit" type="LineEdit" parent="."] +margin_right = 200.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 200, 0 ) +size_flags_horizontal = 0 +text = "\\bTODO\\b.*" + +[node name="RemoveButton" type="Button" parent="."] +margin_left = 204.0 +margin_right = 228.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 24, 24 ) +text = "-" diff --git a/addons/Todo_Manager/plugin.cfg b/addons/Todo_Manager/plugin.cfg new file mode 100644 index 0000000..507eb61 --- /dev/null +++ b/addons/Todo_Manager/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Todo Manager" +description="Dock for housing TODO messages." +author="Peter de Vroom" +version="1.1" +script="plugin.gd" diff --git a/addons/Todo_Manager/plugin.gd b/addons/Todo_Manager/plugin.gd new file mode 100644 index 0000000..e541331 --- /dev/null +++ b/addons/Todo_Manager/plugin.gd @@ -0,0 +1,257 @@ +tool +extends EditorPlugin + +const DockScene := preload("res://addons/Todo_Manager/UI/Dock.tscn") +const Dock := preload("res://addons/Todo_Manager/Dock.gd") +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") +const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd") + +var _dockUI : Dock +#var update_thread : Thread = Thread.new() + +var script_cache : Array +var remove_queue : Array +var combined_pattern : String + +var refresh_lock := false # makes sure _on_filesystem_changed only triggers once + +func _enter_tree() -> void: + _dockUI = DockScene.instance() as Control + add_control_to_bottom_panel(_dockUI, "TODO") + connect("resource_saved", self, "check_saved_file") + get_editor_interface().get_resource_filesystem().connect("filesystem_changed", self, "_on_filesystem_changed") + get_editor_interface().get_file_system_dock().connect("file_removed", self, "queue_remove") + get_editor_interface().get_script_editor().connect("editor_script_changed", self, "_on_active_script_changed") + _dockUI.plugin = self + combined_pattern = combine_patterns(_dockUI.patterns) + find_tokens_from_path(find_scripts()) + _dockUI.build_tree() + + +func _exit_tree() -> void: + _dockUI.create_config_file() + remove_control_from_bottom_panel(_dockUI) + _dockUI.queue_free() + + +func queue_remove(file: String): + for i in _dockUI.todo_items.size() - 1: + if _dockUI.todo_items[i].script_path == file: + _dockUI.todo_items.remove(i) + + +func find_tokens_from_path(scripts: Array) -> void: + for script_path in scripts: + var file := File.new() + file.open(script_path, File.READ) + var contents := file.get_as_text() + file.close() + find_tokens(contents, script_path) + +func find_tokens_from_script(script: Resource) -> void: + find_tokens(script.source_code, script.resource_path) + + +func find_tokens(text: String, script_path: String) -> void: + var regex = RegEx.new() +# if regex.compile("#\\s*\\bTODO\\b.*|#\\s*\\bHACK\\b.*") == OK: + if regex.compile(combined_pattern) == OK: + var result : Array = regex.search_all(text) + if result.empty(): + for i in _dockUI.todo_items.size(): + if _dockUI.todo_items[i].script_path == script_path: + _dockUI.todo_items.remove(i) + return # No tokens found + var match_found : bool + var i := 0 + for todo_item in _dockUI.todo_items: + if todo_item.script_path == script_path: + match_found = true + var updated_todo_item := update_todo_item(todo_item, result, text, script_path) + _dockUI.todo_items.remove(i) + _dockUI.todo_items.insert(i, updated_todo_item) + break + i += 1 + if !match_found: + _dockUI.todo_items.append(create_todo_item(result, text, script_path)) + + +func create_todo_item(regex_results: Array, text: String, script_path: String) -> TodoItem: + var todo_item = TodoItem.new() + todo_item.script_path = script_path + var last_line_number := 0 + var lines := text.split("\n") + for r in regex_results: + var new_todo : Todo = create_todo(r.get_string(), script_path) + new_todo.line_number = get_line_number(r.get_string(), text, last_line_number) + # GD Multiline comment + var trailing_line := new_todo.line_number + var should_break = false + while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"): + for other_r in regex_results: + if lines[trailing_line] in other_r.get_string(): + should_break = true + break + if should_break: + break + + new_todo.content += "\n" + lines[trailing_line] + trailing_line += 1 + + last_line_number = new_todo.line_number + todo_item.todos.append(new_todo) + return todo_item + + +func update_todo_item(todo_item: TodoItem, regex_results: Array, text: String, script_path: String) -> TodoItem: + todo_item.todos.clear() + var lines := text.split("\n") + for r in regex_results: + var new_todo : Todo = create_todo(r.get_string(), script_path) + new_todo.line_number = get_line_number(r.get_string(), text) + # GD Multiline comment + var trailing_line := new_todo.line_number + var should_break = false + while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"): + for other_r in regex_results: + if lines[trailing_line] in other_r.get_string(): + should_break = true + break + if should_break: + break + + new_todo.content += "\n" + lines[trailing_line] + trailing_line += 1 + todo_item.todos.append(new_todo) + return todo_item + + +func get_line_number(what: String, from: String, start := 0) -> int: + what = what.split('\n')[0] # Match first line of multiline C# comments + var temp_array := from.split('\n') + var lines := Array(temp_array) + var line_number# = lines.find(what) + 1 + for i in range(start, lines.size()): + if what in lines[i]: + line_number = i + 1 # +1 to account of 0-based array vs 1-based line numbers + break + else: + line_number = 0 # This is an error + return line_number + + +func check_saved_file(script: Resource) -> void: +# print(script) + pass +# if _dockUI.auto_refresh: +# if script is Script: +# find_tokens_from_script(script) +# _dockUI.build_tree() + + +func _on_filesystem_changed() -> void: + if !refresh_lock: + if _dockUI.auto_refresh: + refresh_lock = true + _dockUI.get_node("Timer").start() + rescan_files() + +func find_scripts() -> Array: + var scripts : Array + var directory_queue : Array + var dir : Directory = Directory.new() + ### FIRST PHASE ### + if dir.open("res://") == OK: + get_dir_contents(dir, scripts, directory_queue) + else: + printerr("TODO_Manager: There was an error during find_scripts() ### First Phase ###") + + ### SECOND PHASE ### + while not directory_queue.empty(): + if dir.change_dir(directory_queue[0]) == OK: + get_dir_contents(dir, scripts, directory_queue) + else: + printerr("TODO_Manager: There was an error at: " + directory_queue[0]) + directory_queue.pop_front() + + cache_scripts(scripts) + return scripts + + +func cache_scripts(scripts: Array) -> void: + for script in scripts: + if not script_cache.has(script): + script_cache.append(script) + + +func get_dir_contents(dir: Directory, scripts: Array, directory_queue: Array) -> void: + dir.list_dir_begin(true, true) + var file_name : String = dir.get_next() + + while file_name != "": + if dir.current_is_dir(): + if file_name == ".import" or filename == ".mono": # Skip .import folder which should never have scripts + pass + else: + directory_queue.append(dir.get_current_dir() + "/" + file_name) + else: + if file_name.ends_with(".gd") or file_name.ends_with(".cs"): + if dir.get_current_dir() == "res://": + scripts.append(dir.get_current_dir() + file_name) + else: + scripts.append(dir.get_current_dir() + "/" + file_name) + file_name = dir.get_next() + + +func rescan_files() -> void: + _dockUI.todo_items.clear() + script_cache.clear() + combined_pattern = combine_patterns(_dockUI.patterns) + find_tokens_from_path(find_scripts()) + _dockUI.build_tree() + + +func combine_patterns(patterns: Array) -> String: + if patterns.size() == 1: + return patterns[0][0] + else: +# var pattern_string : String + var pattern_string := "((\\/\\*)|(#|\\/\\/))\\s*(" + for i in range(patterns.size()): + if i == 0: + pattern_string += patterns[i][0] + else: + pattern_string += "|" + patterns[i][0] + pattern_string += ")(?(2)[\\s\\S]*?\\*\\/|.*)" +# if i == 0: +## pattern_string = "#\\s*" + patterns[i][0] + ".*" +# pattern_string = "((\\/\\*)|(#|\\/\\/))\\s*" + patterns[i][0] + ".*" # (?(2)[\\s\\S]*?\\*\\/|.*) +# else: +## pattern_string += "|" + "#\\s*" + patterns[i][0] + ".*" +# pattern_string += "|" + "((\\/\\*)|(#|\\/\\/))\\s*" + patterns[i][0] + ".*" + return pattern_string + + +func create_todo(todo_string: String, script_path: String) -> Todo: + var todo := Todo.new() + var regex = RegEx.new() + for pattern in _dockUI.patterns: + if regex.compile(pattern[0]) == OK: + var result : RegExMatch = regex.search(todo_string) + if result: + todo.pattern = pattern[0] + todo.title = result.strings[0] + else: + continue + else: + printerr("Error compiling " + pattern[0]) + + todo.content = todo_string + todo.script_path = script_path + return todo + + +func _on_active_script_changed(script) -> void: + if _dockUI: + if _dockUI.tabs.current_tab == 1: + _dockUI.build_tree() diff --git a/addons/Todo_Manager/todo.cfg b/addons/Todo_Manager/todo.cfg new file mode 100644 index 0000000..fe226c2 --- /dev/null +++ b/addons/Todo_Manager/todo.cfg @@ -0,0 +1,14 @@ +[scripts] + +full_path=false +sort_alphabetical=true +script_colour=Color( 0.8, 0.807843, 0.827451, 1 ) +ignore_paths=[ ] + +[patterns] + +patterns=[ [ "\\bTODO\\b", Color( 0.588235, 0.945098, 0.678431, 1 ) ], [ "\\bHACK\\b", Color( 0.835294, 0.737255, 0.439216, 1 ) ], [ "\\bFIXME\\b", Color( 0.835294, 0.439216, 0.439216, 1 ) ] ] + +[config] + +auto_refresh=true diff --git a/addons/Todo_Manager/todoItem_class.gd b/addons/Todo_Manager/todoItem_class.gd new file mode 100644 index 0000000..b278264 --- /dev/null +++ b/addons/Todo_Manager/todoItem_class.gd @@ -0,0 +1,14 @@ +tool +extends Reference + +var script_path : String +var todos : Array + +func get_short_path() -> String: + var temp_array := script_path.rsplit('/', false, 1) + var short_path : String + if !temp_array[1]: + short_path = "(!)" + temp_array[0] + else: + short_path = temp_array[1] + return short_path diff --git a/addons/Todo_Manager/todo_class.gd b/addons/Todo_Manager/todo_class.gd new file mode 100644 index 0000000..59f8901 --- /dev/null +++ b/addons/Todo_Manager/todo_class.gd @@ -0,0 +1,8 @@ +tool +extends Reference + +var pattern : String +var title : String +var content : String +var script_path : String +var line_number : int diff --git a/project.godot b/project.godot index 004703d..a5c598c 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,10 @@ _global_script_class_icons={ config/name="Pir-serious-game-ethics" config/icon="res://icon.png" +[editor_plugins] + +enabled=PoolStringArray( "EXP-System-Dialog", "Todo_Manager" ) + [gdnative] singletons=[ "res://git_api.gdnlib" ]