From 6c09d1d66d727eccc275a877a3dc69e535854d62 Mon Sep 17 00:00:00 2001 From: teebarjunk Date: Mon, 11 Oct 2021 12:41:26 -0400 Subject: [PATCH] filtering --- .gitignore | 4 +- Node2D.tscn | 3 + README.md | 63 ++++ TextEditor.tscn | 255 -------------- addons/text_editor/FileTabs.gd | 34 ++ .../{meta_panel.gd => MetaInfo.gd} | 1 + addons/text_editor/TE_DragLabel.gd | 18 + .../{FileEditorTab.gd => TE_FileEditor.gd} | 27 +- addons/text_editor/TE_FilesList.gd | 238 +++++++++++++ .../{list_symbols.gd => TE_SymbolsList.gd} | 3 + .../{list_tags.gd => TE_TagsPanel.gd} | 5 +- .../{TextEditor.gd => TE_TextEditor.gd} | 273 +++++++++++---- addons/text_editor/TE_Util.gd | 8 + addons/text_editor/TextEditor.tscn | 313 ++++++++++++++++++ addons/text_editor/ext/TE_ExtensionHelper.gd | 8 +- addons/text_editor/ext/ext_cfg.gd | 2 + addons/text_editor/ext/ext_csv.gd | 1 + addons/text_editor/ext/ext_ini.gd | 1 + addons/text_editor/ext/ext_json.gd | 1 + addons/text_editor/ext/ext_md.gd | 7 +- addons/text_editor/ext/ext_yaml.gd | 1 + addons/text_editor/line_edit.gd | 1 + addons/text_editor/list_files.gd | 267 --------------- addons/text_editor/plugin.cfg | 7 + addons/text_editor/plugin.gd | 26 ++ addons/text_editor/tab_scroll.gd | 6 + project.godot | 6 +- 27 files changed, 973 insertions(+), 606 deletions(-) create mode 100644 Node2D.tscn create mode 100644 README.md delete mode 100644 TextEditor.tscn create mode 100644 addons/text_editor/FileTabs.gd rename addons/text_editor/{meta_panel.gd => MetaInfo.gd} (99%) create mode 100644 addons/text_editor/TE_DragLabel.gd rename addons/text_editor/{FileEditorTab.gd => TE_FileEditor.gd} (90%) create mode 100644 addons/text_editor/TE_FilesList.gd rename addons/text_editor/{list_symbols.gd => TE_SymbolsList.gd} (97%) rename addons/text_editor/{list_tags.gd => TE_TagsPanel.gd} (95%) rename addons/text_editor/{TextEditor.gd => TE_TextEditor.gd} (65%) create mode 100644 addons/text_editor/TextEditor.tscn create mode 100644 addons/text_editor/ext/ext_cfg.gd delete mode 100644 addons/text_editor/list_files.gd create mode 100644 addons/text_editor/plugin.cfg create mode 100644 addons/text_editor/plugin.gd diff --git a/.gitignore b/.gitignore index acdfd84..8c441ff 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,5 @@ data_*/ # Text editor related test_files/ -.trash/ -.trash_info.json +.te_trash/ +.te_trash_info.json diff --git a/Node2D.tscn b/Node2D.tscn new file mode 100644 index 0000000..b171e8d --- /dev/null +++ b/Node2D.tscn @@ -0,0 +1,3 @@ +[gd_scene format=2] + +[node name="Node2D" type="Node2D"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1cd19f --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Text Editor +Version 1.0 + +***Warning: Use at your own risk. Backup your files before testing.*** + +# Features +- Tabs with scroll +- File filtering +- Highlighting for common file formats (`md` `json`...) +- Tag filtering system +- File Management: + - Creation + - Renaming + - Recycling +- Many little *Ease of life* functions: + - Folder open/close + - Comment toggling for: + - `.md`: `` + - `.json`: `/* */` + - `.ini`: `; ` + - `.cfg`: `; ` + - `.yaml`: `# ` + - Table of Contents (Symbols) + +# Controls +- `ctrl + W` Close file +- `ctrl + shift + W` Open last closed file +- `ctrl + tab` Select next open file +- `ctrl + shift + tab` Select last open file +- `ctrl + mouse wheel` Adjust font size +- `ctrl + up` `ctrl + down` Move selected lines +- `ctrl + /` Toggle line comments +- `ctrl + M` Toggle file meta info + +# Symbols and Tags +To make it easier to find stuff there is a *Symbol* viewer. + +- `Markdown` uses headings `# Heading` +- `JSON` uses Dictionaries `"object": {` +- `YAML` uses Dictionaries `object: ` +- `ini` `cfg` use headings `[heading]` + +Symbols can have tags. Tags are added with comments. + +- `Markdown` uses `` +- `JSON` uses `/* #tag1 #tag2 */` or `"#": "#tag1 #tag2"` +- `YAML` uses `# #tag1 #tag2` or `"#": "#tag1 #tag2"` +- `ini` `cfg` uses `; #tag1 #tag2` + +Symbols are per file, tags are shared across files. + +When a file is opened with tags, they show up in bottom right *Tag Container*. + +Click them to toggle on and off.\ +This will then highlight *Files* and *Symbols* that have that tag. + +# Todo +- [ ] Search +- [ ] Find and replace +- [ ] Meta data based on format. + + + diff --git a/TextEditor.tscn b/TextEditor.tscn deleted file mode 100644 index 8fe4614..0000000 --- a/TextEditor.tscn +++ /dev/null @@ -1,255 +0,0 @@ -[gd_scene load_steps=10 format=2] - -[ext_resource path="res://addons/text_editor/file_buttons.gd" type="Script" id=1] -[ext_resource path="res://addons/text_editor/TextEditor.gd" type="Script" id=2] -[ext_resource path="res://addons/text_editor/list_files.gd" type="Script" id=3] -[ext_resource path="res://addons/text_editor/FileEditorTab.gd" type="Script" id=4] -[ext_resource path="res://addons/text_editor/list_symbols.gd" type="Script" id=5] -[ext_resource path="res://addons/text_editor/tab_scroll.gd" type="Script" id=6] -[ext_resource path="res://addons/text_editor/list_tags.gd" type="Script" id=7] -[ext_resource path="res://addons/text_editor/line_edit.gd" type="Script" id=8] -[ext_resource path="res://addons/text_editor/meta_panel.gd" type="Script" id=9] - -[node name="text_editor" type="Control"] -anchor_right = 1.0 -anchor_bottom = 1.0 -script = ExtResource( 2 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="c" type="VBoxContainer" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -custom_constants/separation = 0 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="c" type="PanelContainer" parent="c"] -margin_right = 1024.0 -margin_bottom = 34.0 - -[node name="c" type="HBoxContainer" parent="c/c"] -margin_left = 7.0 -margin_top = 7.0 -margin_right = 1017.0 -margin_bottom = 27.0 -script = ExtResource( 1 ) - -[node name="test" type="Button" parent="c/c/c"] -margin_right = 56.0 -margin_bottom = 20.0 -text = "update" - -[node name="file_button" type="MenuButton" parent="c/c/c"] -margin_left = 60.0 -margin_right = 92.0 -margin_bottom = 20.0 -text = "file" -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="c3" type="HSplitContainer" parent="c"] -margin_top = 34.0 -margin_right = 1024.0 -margin_bottom = 600.0 -size_flags_vertical = 3 -split_offset = -300 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="c2" type="PanelContainer" parent="c/c3"] -margin_right = 206.0 -margin_bottom = 566.0 -rect_min_size = Vector2( 200, 0 ) -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="c" type="Panel" parent="c/c3/c2"] -margin_left = 7.0 -margin_top = 7.0 -margin_right = 199.0 -margin_bottom = 559.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="list_files" type="RichTextLabel" parent="c/c3/c2/c"] -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -bbcode_enabled = true -meta_underlined = false -script = ExtResource( 3 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="popup" type="PopupMenu" parent="c/c3/c2/c/list_files"] -margin_right = 69.0 -margin_bottom = 68.0 - -[node name="drag_label" type="RichTextLabel" parent="c/c3/c2/c/list_files"] -visible = false -anchor_right = 1.0 -anchor_bottom = 1.0 -mouse_filter = 2 -bbcode_enabled = true -fit_content_height = true - -[node name="c" type="HSplitContainer" parent="c/c3"] -margin_left = 218.0 -margin_right = 1024.0 -margin_bottom = 566.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -split_offset = -80 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="c" type="VBoxContainer" parent="c/c3/c"] -margin_right = 614.0 -margin_bottom = 566.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="line_edit" type="LineEdit" parent="c/c3/c/c"] -visible = false -margin_right = 614.0 -margin_bottom = 24.0 -script = ExtResource( 8 ) - -[node name="tab_container" type="TabContainer" parent="c/c3/c/c"] -margin_right = 614.0 -margin_bottom = 547.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -tab_align = 0 -drag_to_rearrange_enabled = true -script = ExtResource( 6 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="tab_prefab" type="TextEdit" parent="c/c3/c/c/tab_container"] -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 -highlight_current_line = true -syntax_highlighting = true -show_line_numbers = true -draw_tabs = true -breakpoint_gutter = true -fold_gutter = true -highlight_all_occurrences = true -minimap_draw = true -script = ExtResource( 4 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="meta" type="RichTextLabel" parent="c/c3/c/c"] -margin_top = 551.0 -margin_right = 614.0 -margin_bottom = 566.0 -bbcode_enabled = true -fit_content_height = true -script = ExtResource( 9 ) - -[node name="c2" type="PanelContainer" parent="c/c3/c"] -margin_left = 626.0 -margin_right = 806.0 -margin_bottom = 566.0 -rect_min_size = Vector2( 100, 0 ) -size_flags_vertical = 3 - -[node name="c" type="VSplitContainer" parent="c/c3/c/c2"] -margin_left = 7.0 -margin_top = 7.0 -margin_right = 173.0 -margin_bottom = 559.0 -custom_constants/autohide = 0 - -[node name="c" type="Panel" parent="c/c3/c/c2/c"] -margin_right = 166.0 -margin_bottom = 270.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="list_symbols" type="RichTextLabel" parent="c/c3/c/c2/c/c"] -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_vertical = 3 -bbcode_enabled = true -bbcode_text = "tags" -meta_underlined = false -text = "tags" -script = ExtResource( 5 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="c2" type="Panel" parent="c/c3/c/c2/c"] -margin_top = 282.0 -margin_right = 166.0 -margin_bottom = 552.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 - -[node name="list_tags" type="RichTextLabel" parent="c/c3/c/c2/c/c2"] -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -bbcode_enabled = true -bbcode_text = "tags" -meta_underlined = false -text = "tags" -script = ExtResource( 7 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="popup" type="ConfirmationDialog" parent="."] -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -margin_left = -100.0 -margin_top = -35.0 -margin_right = 100.0 -margin_bottom = 35.0 - -[node name="popup_unsaved" type="ConfirmationDialog" parent="."] -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -margin_left = -100.0 -margin_top = -35.0 -margin_right = 100.0 -margin_bottom = 35.0 -window_title = "Warning" -dialog_text = "Unsaved data will be lost." - -[node name="file_dialog" type="FileDialog" parent="."] -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -margin_left = -297.5 -margin_top = -157.0 -margin_right = 297.5 -margin_bottom = 157.0 -__meta__ = { -"_edit_use_anchors_": false -} diff --git a/addons/text_editor/FileTabs.gd b/addons/text_editor/FileTabs.gd new file mode 100644 index 0000000..423d764 --- /dev/null +++ b/addons/text_editor/FileTabs.gd @@ -0,0 +1,34 @@ +extends TabContainer + +onready var editor:TextEditor = owner +var mouse:bool = false + +func _ready(): + var _e + _e = connect("mouse_entered", self, "set", ["mouse", true]) + _e = connect("mouse_exited", self, "set", ["mouse", false]) + +func _input(e): + if not editor.is_plugin_active(): + return + + if mouse and e is InputEventMouseButton and e.pressed: + if e.button_index == BUTTON_WHEEL_DOWN: + prev() + get_tree().set_input_as_handled() + + elif e.button_index == BUTTON_WHEEL_UP: + next() + get_tree().set_input_as_handled() + + if e is InputEventKey and e.pressed and e.control and e.scancode == KEY_TAB: + if e.shift: + prev() + get_tree().set_input_as_handled() + else: + next() + get_tree().set_input_as_handled() + +func prev(): current_tab = wrapi(current_tab - 1, 0, get_child_count()) +func next(): current_tab = wrapi(current_tab + 1, 0, get_child_count()) + diff --git a/addons/text_editor/meta_panel.gd b/addons/text_editor/MetaInfo.gd similarity index 99% rename from addons/text_editor/meta_panel.gd rename to addons/text_editor/MetaInfo.gd index 0b5f312..59cb341 100644 --- a/addons/text_editor/meta_panel.gd +++ b/addons/text_editor/MetaInfo.gd @@ -1,3 +1,4 @@ +tool extends TE_RichTextLabel onready var editor:TextEditor = owner diff --git a/addons/text_editor/TE_DragLabel.gd b/addons/text_editor/TE_DragLabel.gd new file mode 100644 index 0000000..4d28c5d --- /dev/null +++ b/addons/text_editor/TE_DragLabel.gd @@ -0,0 +1,18 @@ +tool +extends RichTextLabel + +var editor:TextEditor + +func _ready(): + add_font_override("normal_font", editor.FONT_R) + add_font_override("bold_font", editor.FONT_B) + add_font_override("italics_font", editor.FONT_I) + add_font_override("bold_italics_font", editor.FONT_BI) + +func _process(_delta): + set_global_position(get_global_mouse_position()) + +func _input(e): + if e is InputEventMouseButton: + if (e.button_index == BUTTON_LEFT and not e.pressed) or (e.button_index == BUTTON_RIGHT and e.pressed): + queue_free() diff --git a/addons/text_editor/FileEditorTab.gd b/addons/text_editor/TE_FileEditor.gd similarity index 90% rename from addons/text_editor/FileEditorTab.gd rename to addons/text_editor/TE_FileEditor.gd index c7cdb64..4e05a1f 100644 --- a/addons/text_editor/FileEditorTab.gd +++ b/addons/text_editor/TE_FileEditor.gd @@ -1,7 +1,7 @@ +tool extends TextEdit -onready var tabs:TabContainer = get_parent() -onready var editor:TextEditor = get_parent().owner +var editor:TextEditor var helper:TE_ExtensionHelper var temporary:bool = false setget set_temporary @@ -17,6 +17,8 @@ var last_selection:Array = [0, 0, 0, 0] func _ready(): var _e + if not editor: + editor = owner _e = editor.connect("save_files", self, "save_file") _e = editor.connect("file_selected", self, "_file_selected") _e = editor.connect("file_renamed", self, "_file_renamed") @@ -30,9 +32,13 @@ func _file_renamed(old_path:String, new_path:String): update_name() func _input(e): + if not editor.is_plugin_active(): + return + if not visible: return + # remember last selection if e is InputEventKey and e.pressed: last_key = e.scancode last_shift = e.shift @@ -45,6 +51,7 @@ func _input(e): else: last_selected = false + # move lines up/down if e is InputEventKey and e.control and e.shift and e.pressed: var f var t @@ -84,7 +91,9 @@ func _unhandled_key_input(e): func _file_selected(p:String): if p and p == file_path: grab_focus() + grab_click_focus() update_symbols() + update_heading() func text_changed(): if last_selected: @@ -182,7 +191,19 @@ func update_name(): if temporary: n = "?" + n if modified: n = "*" + n - tabs.set_tab_title(get_index(), n) + editor.tab_parent.set_tab_title(get_index(), n) + update_heading() + +func update_heading(): + # set window "file (directory)" + var f = file_path.get_file() + if modified: + f = "*" + f + var d = file_path.get_base_dir().get_file() + if d: + OS.set_window_title("%s (%s)" % [f, d]) + else: + OS.set_window_title(f) func needs_save() -> bool: return modified or not File.new().file_exists(file_path) diff --git a/addons/text_editor/TE_FilesList.gd b/addons/text_editor/TE_FilesList.gd new file mode 100644 index 0000000..66f4521 --- /dev/null +++ b/addons/text_editor/TE_FilesList.gd @@ -0,0 +1,238 @@ +tool +extends RichTextLabel + +onready var editor:TextEditor = owner +onready var file_popup:PopupMenu = $file_popup +onready var dir_popup:PopupMenu = $dir_popup + +const DragLabel = preload("res://addons/text_editor/TE_DragLabel.gd") +var drag_label:RichTextLabel + +var files:Array = [] +var dirs:Array = [] +var selected +var hovered:String = "" +var dragging:String = "" +var drag_start:Vector2 + +func _ready(): + var _e + _e = editor.connect("updated_file_list", self, "_redraw") + _e = editor.connect("tags_updated", self, "_redraw") + _e = editor.connect("file_opened", self, "_file_opened") + _e = editor.connect("file_closed", self, "_file_closed") + _e = editor.connect("file_selected", self, "_file_selected") + _e = editor.connect("file_renamed", self, "_file_renamed") + _e = connect("meta_hover_started", self, "_meta_entered") + _e = connect("meta_hover_ended", self, "_meta_exited") + + # hint + theme = Theme.new() + theme.set_font("font", "TooltipLabel", editor.FONT_R) + + # file popup + file_popup.clear() + file_popup.rect_size = Vector2.ZERO + file_popup.add_item("Rename") + file_popup.add_separator() + file_popup.add_item("Remove") + _e = file_popup.connect("index_pressed", self, "_file_popup") + file_popup.add_font_override("font", TextEditor.FONT) + + # dir popup + dir_popup.clear() + dir_popup.rect_size = Vector2.ZERO + dir_popup.add_item("Create new file") + _e = dir_popup.connect("index_pressed", self, "_dir_popup") + dir_popup.add_font_override("font", TextEditor.FONT) + + add_font_override("normal_font", editor.FONT_R) + add_font_override("bold_font", editor.FONT_B) + add_font_override("italics_font", editor.FONT_I) + add_font_override("bold_italics_font", editor.FONT_BI) + +func _dir_popup(index:int): + var p = _meta_to_file(selected) + var type = p[0] + var file = p[1] + + match dir_popup.get_item_text(index): + "Create new file": + editor.popup_create_file(file) + +func _file_popup(index:int): + var p = _meta_to_file(selected) + var type = p[0] + var file = p[1] + + match file_popup.get_item_text(index): + "Rename": + var fname:String = file.get_file() + var i:int = fname.find(".") + editor.line_edit.display(fname, self, "_renamed") + editor.line_edit.select(0, i) + + "Remove": + if type == "f": + editor.recycle_file(file) + + _: + selected = {} + +func _renamed(new_file:String): + var p = _meta_to_file(selected) + var type = p[0] + var file = p[1] + + var old_path:String = file + var old_file:String = old_path.get_file() + if new_file != old_file: + var new_path:String = old_path.get_base_dir().plus_file(new_file) + editor.rename_file(old_path, new_path) + selected = {} + +func _input(e:InputEvent): + if not editor.is_plugin_active(): + return + + if e is InputEventMouseButton and hovered: + var p = _meta_to_file(hovered) + var type = p[0] + var file = p[1] + + if e.button_index == BUTTON_LEFT: + + if e.pressed: + dragging = hovered + + if type == "f": + drag_label = DragLabel.new() + drag_label.editor = editor + drag_label.set_bbcode(file.get_file()) + editor.add_child(drag_label) + + else: + if dragging and dragging != hovered: + var p2 = _meta_to_file(dragging) + var drag_type = p[0] + var drag_file = p[1] + if drag_type == "f" and type == "d": + var dir:String = file + var old_path:String = drag_file + var new_path:String = dir.plus_file(old_path.get_file()) + editor.rename_file(old_path, new_path) + + else: + match type: + # toggle directory + "d": + p[2].open = not p[2].open + _redraw() + + # select + "f": + editor.select_file(file) + + get_tree().set_input_as_handled() + + elif e.button_index == BUTTON_RIGHT: + if e.pressed: + selected = hovered + match type: + "d": + dir_popup.set_global_position(get_global_mouse_position()) + dir_popup.popup() + + "f": + file_popup.set_global_position(get_global_mouse_position()) + file_popup.popup() + get_tree().set_input_as_handled() + +func _meta_to_file(m:String): + var p = m.split(":", true, 1) + var type = p[0] + var index = int(p[1]) + match type: + "d": + return [type, dirs[index].file_path, dirs[index]] + "f": + return [type, files[index]] + +func _meta_entered(m): + hovered = m + var f = _meta_to_file(m) + match f[0]: + "f", "d": hint_tooltip = f[1] + +func _meta_exited(_m): + hovered = "" + hint_tooltip = "" + +func _file_opened(_file_path:String): _redraw() +func _file_closed(_file_path:String): _redraw() +func _file_selected(_file_path:String): _redraw() +func _file_renamed(_op:String, _np:String): _redraw() + +var lines:PoolStringArray = PoolStringArray() + +func _redraw(): + lines = PoolStringArray() + dirs.clear() + files.clear() + _draw_dir(editor.file_list[""], 0) + set_bbcode(lines.join("\n")) + +func clr(s:String, c:Color) -> String: return "[color=#%s]%s[/color]" % [c.to_html(), s] +func i(s:String) -> String: return "[i]%s[/i]" % s +func b(s:String) -> String: return "[b]%s[/b]" % s +func url(s:String, url:String) -> String: return "[url=%s]%s[/url]" % [url, s] + +const FOLDER:String = "🗀" # not visible in godot +func _draw_dir(dir:Dictionary, deep:int): + var space = clr("┃ ".repeat(deep), Color.white.darkened(.8)) + var file:String = dir.file_path + var name:String = b(file.get_file()) + var head:String = "▼" if dir.open else "▶" + var dir_index:int = len(dirs) + var link:String = url(space+FOLDER+head+" "+name, "d:%s" % dir_index) + lines.append(clr(link, Color.white.darkened(.7))) + dirs.append(dir) + + var sel = editor.get_selected_tab() + sel = sel.file_path if sel else "" + + if dir.open: + var i = 0 + var last = len(dir.all)-1 + for path in dir.all: + var file_path = dir.all[path] + # dir + if file_path is Dictionary: + _draw_dir(file_path, deep+1) + + # file + else: + file = path.get_file() + var is_selected = file_path == sel + head = "┣╸" if i != last else "┗╸" + if is_selected: + head = clr(head, Color.white.darkened(.5)) + else: + head = clr(head, Color.white.darkened(.8)) + var p = file.split(".", true, 1) + file = p[0] + + var color = Color.white if editor.is_tagged(file_path) else Color.white.darkened(.5) + + if editor.is_selected(file_path): + file = clr(file, color) + elif editor.is_opened(file_path): + file = clr(file, color.darkened(.5)) + else: + file = i(clr(file, color.darkened(.75))) + + var ext = clr("." + p[1], Color.white.darkened(.75)) + var line = space + head + file + ext + lines.append(url(line, "f:%s" % len(files))) + files.append(file_path) + i += 1 diff --git a/addons/text_editor/list_symbols.gd b/addons/text_editor/TE_SymbolsList.gd similarity index 97% rename from addons/text_editor/list_symbols.gd rename to addons/text_editor/TE_SymbolsList.gd index 3d4919c..8ebe19e 100644 --- a/addons/text_editor/list_symbols.gd +++ b/addons/text_editor/TE_SymbolsList.gd @@ -1,3 +1,4 @@ +tool extends RichTextLabel onready var editor:TextEditor = owner @@ -13,6 +14,8 @@ func _ready(): add_font_override("bold_font", editor.FONT_B) add_font_override("italics_font", editor.FONT_I) add_font_override("bold_italics_font", editor.FONT_BI) + + call_deferred("_redraw") func _hovered(_id): pass diff --git a/addons/text_editor/list_tags.gd b/addons/text_editor/TE_TagsPanel.gd similarity index 95% rename from addons/text_editor/list_tags.gd rename to addons/text_editor/TE_TagsPanel.gd index 89def28..51a1f72 100644 --- a/addons/text_editor/list_tags.gd +++ b/addons/text_editor/TE_TagsPanel.gd @@ -1,3 +1,4 @@ +tool extends RichTextLabel onready var editor:TextEditor = owner @@ -15,6 +16,8 @@ func _ready(): add_font_override("bold_font", editor.FONT_B) add_font_override("italics_font", editor.FONT_I) add_font_override("bold_italics_font", editor.FONT_BI) + + call_deferred("_redraw") func _hovered(_id): pass @@ -64,4 +67,4 @@ func _redraw(): t.append(x) tag_indices.append(tag) - set_bbcode(t.join(" ")) + set_bbcode("[center]" + t.join(" ")) diff --git a/addons/text_editor/TextEditor.gd b/addons/text_editor/TE_TextEditor.gd similarity index 65% rename from addons/text_editor/TextEditor.gd rename to addons/text_editor/TE_TextEditor.gd index 8aae544..6898747 100644 --- a/addons/text_editor/TextEditor.gd +++ b/addons/text_editor/TE_TextEditor.gd @@ -1,4 +1,5 @@ -extends Node +tool +extends Control class_name TextEditor const FONT:DynamicFont = preload("res://addons/text_editor/fonts/font.tres") @@ -8,9 +9,12 @@ const FONT_B:DynamicFont = preload("res://addons/text_editor/fonts/font_b.tres") const FONT_I:DynamicFont = preload("res://addons/text_editor/fonts/font_i.tres") const FONT_BI:DynamicFont = preload("res://addons/text_editor/fonts/font_bi.tres") -const EXTENSIONS:PoolStringArray = PoolStringArray([ +const MAIN_EXTENSIONS:PoolStringArray = PoolStringArray([ "txt", "md", "json", "csv", "cfg", "ini", "yaml" ]) +const INTERNAL_EXTENSIONS:PoolStringArray = PoolStringArray([ + "gd", "import", "gdignore", "gitignore" +]) const FILE_FILTERS:PoolStringArray = PoolStringArray([ "*.txt ; Text", "*.md ; Markdown", @@ -18,25 +22,39 @@ const FILE_FILTERS:PoolStringArray = PoolStringArray([ "*.csv ; Comma Seperated Values", "*.cfg ; Config", "*.ini ; Config", - "*.yaml ; YAML" + "*.yaml ; YAML", ]) +var plugin = null +var plugin_hint:bool = false + +# hide dirs +var show_dir_empty:bool = true +var show_dir_hidden:bool = true +var show_dir_gdignore:bool = true +# +var show_dir_git:bool = false +var show_dir_import:bool = false +var show_dir_trash:bool = false +# hide files +var show_file_hidden:bool = true + var color_text:Color = Color.white -var color_comment:Color = Color.darkolivegreen -var color_symbol:Color = Color.white.darkened(.5) +var color_comment:Color = Color.white.darkened(.6) +var color_symbol:Color = Color.deepskyblue var color_var:Color = Color.orange var color_varname:Color = Color.white.darkened(.25) - onready var test_button:Node = $c/c/c/test onready var tab_parent:TabContainer = $c/c3/c/c/tab_container -onready var tab_prefab:Node = $c/c3/c/c/tab_container/tab_prefab +onready var tab_prefab:Node = $file_editor onready var popup:ConfirmationDialog = $popup onready var popup_unsaved:ConfirmationDialog = $popup_unsaved onready var file_dialog:FileDialog = $file_dialog onready var line_edit:LineEdit = $c/c3/c/c/line_edit onready var menu_file:MenuButton = $c/c/c/file_button var ext_menu:PopupMenu = PopupMenu.new() +var hide_menu:PopupMenu = PopupMenu.new() signal updated_file_list() signal file_opened(file_path) @@ -44,15 +62,14 @@ signal file_closed(file_path) signal file_selected(file_path) signal file_saved(file_path) signal file_renamed(old_path, new_path) -#signal file_symbols_updated(file_path) signal symbols_updated() signal tags_updated() signal save_files() -var current_directory:String = "" +var current_directory:String = "res://" var dirs:Array = [] var file_list:Dictionary = {} -var ext_counts:Dictionary = {} +#var ext_counts:Dictionary = {} var symbols:Dictionary = {} var tags:Array = [] var tags_enabled:Dictionary = {} @@ -63,11 +80,15 @@ var opened:Array = [] var closed:Array = [] func _ready(): + if not is_plugin_active(): + return + # not needed when editor plugin # get_tree().set_auto_accept_quit(false) var _e _e = test_button.connect("pressed", self, "_debug_pressed") + test_button.add_font_override("font", FONT_R) # popup unsaved popup_unsaved.get_ok().text = "Ok" @@ -78,21 +99,55 @@ func _ready(): TE_Util.dig(popup_unsaved, self, "_apply_fonts") # menu - var p = menu_file.get_popup() + menu_file.add_font_override("font", FONT_R) + var p:PopupMenu = menu_file.get_popup() + p.clear() p.add_font_override("font", FONT_R) p.add_item("New File") _e = p.connect("index_pressed", self, "_menu_file") - # extensions - ext_menu.set_name("Extensions") + # hide + hide_menu.clear() + hide_menu.set_name("Directories") + hide_menu.add_font_override("font", FONT_R) + hide_menu.add_check_item("Hidden", 0) + hide_menu.add_check_item("Empty", 1) + hide_menu.add_check_item(".gdignore", 2) + hide_menu.set_item_checked(0, show_dir_hidden) + hide_menu.set_item_checked(1, show_dir_gdignore) + hide_menu.set_item_checked(2, show_dir_empty) + hide_menu.add_separator() + hide_menu.add_check_item(".ignore/", 4) + hide_menu.add_check_item(".git/", 5) + hide_menu.add_check_item(".trash/", 6) + + p.add_child(hide_menu) + p.add_submenu_item("Directories", "Directories") + _e = hide_menu.connect("index_pressed", self, "_menu_hide") + + # hide extensions + ext_menu.clear() + ext_menu.set_name("Files") ext_menu.add_font_override("font", FONT_R) - for i in len(EXTENSIONS): - var ext = EXTENSIONS[i] - ext_menu.add_check_item(ext, i) - ext_menu.set_item_checked(i, true) + ext_menu.add_check_item("Hidden", 0) + ext_menu.set_item_checked(0, show_file_hidden) + + ext_menu.add_separator() + for i in len(MAIN_EXTENSIONS): + var ext = MAIN_EXTENSIONS[i] + ext_menu.add_check_item("*." + ext, i+2) + ext_menu.set_item_checked(i+2, true) exts_enabled.append(ext) + + ext_menu.add_separator() + for i in len(INTERNAL_EXTENSIONS): + var ext = INTERNAL_EXTENSIONS[i] + var id = i+len(MAIN_EXTENSIONS)+3 + ext_menu.add_check_item("*." + ext, id) + ext_menu.set_item_checked(id, false) + p.add_child(ext_menu) - p.add_submenu_item("Extensions", "Extensions") + p.add_submenu_item("Files", "Files") _e = ext_menu.connect("index_pressed", self, "_menu_extension") # file dialog @@ -102,7 +157,6 @@ func _ready(): # tab control _e = tab_parent.connect("tab_changed", self, "_tab_changed") - tab_parent.remove_child(tab_prefab) # tab_parent.add_font_override("font", FONT_R) @@ -119,7 +173,16 @@ func _ready(): # return # get_tree().quit() +func is_plugin_active(): + if not Engine.editor_hint: + return true + + return plugin_hint and visible + func _input(e): + if not is_plugin_active(): + return + if e is InputEventMouseButton and e.control: if e.button_index == BUTTON_WHEEL_DOWN: FONT.size = int(max(8, FONT.size - 1)) @@ -138,16 +201,58 @@ func _menu_file(a): match menu_file.get_popup().items[a]: "New File": popup_create_file() -func _menu_extension(index:int): - var ext = EXTENSIONS[index] - var toggled = ext in exts_enabled - if toggled: - exts_enabled.erase(ext) - elif not ext in exts_enabled: - exts_enabled.append(ext) - ext_menu.set_item_checked(index, not toggled) +func _menu_hide(index:int): + match index: + 0: + show_dir_hidden = not show_dir_hidden + hide_menu.set_item_checked(index, show_dir_hidden) + 1: + show_dir_empty = not show_dir_empty + hide_menu.set_item_checked(index, show_dir_empty) + 2: + show_dir_gdignore = not show_dir_gdignore + hide_menu.set_item_checked(index, show_dir_gdignore) + + 4: + show_dir_import = not show_dir_import + hide_menu.set_item_checked(index, show_dir_import) + 4: + show_dir_git = not show_dir_git + hide_menu.set_item_checked(index, show_dir_git) + 5: + show_dir_trash = not show_dir_trash + hide_menu.set_item_checked(index, show_dir_trash) + refresh_files() +func _menu_extension(index:int): + # hidden files + if index == 0: + show_file_hidden = not show_file_hidden + ext_menu.set_item_checked(index, show_file_hidden) + + # main extensions + elif index-2 < len(MAIN_EXTENSIONS): + var ext = MAIN_EXTENSIONS[index-2] + var toggled = ext in exts_enabled + if toggled: + exts_enabled.erase(ext) + elif not ext in exts_enabled: + exts_enabled.append(ext) + ext_menu.set_item_checked(index, not toggled) + refresh_files() + + # internal extensions + elif index-3-len(MAIN_EXTENSIONS) < len(INTERNAL_EXTENSIONS): + var ext = INTERNAL_EXTENSIONS[index-3-len(MAIN_EXTENSIONS)] + var toggled = ext in exts_enabled + if toggled: + exts_enabled.erase(ext) + elif not ext in exts_enabled: + exts_enabled.append(ext) + ext_menu.set_item_checked(index, not toggled) + refresh_files() + func _file_dialog_file(file_path:String): match file_dialog.get_meta("mode"): "create": create_file(file_path) @@ -229,10 +334,8 @@ func _unhandled_key_input(e:InputEventKey): # close/unclose tab elif e.scancode == KEY_W: if e.shift: - print("open last tab") open_last_file() else: - print("close tab ") close_selected() elif e.scancode == KEY_R: @@ -244,11 +347,11 @@ func _unhandled_key_input(e:InputEventKey): get_tree().set_input_as_handled() func sort_files(): - TE_Util.dig(file_list, self, "_sort_files") + TE_Util.dig(file_list, self, "_sort") emit_signal("updated_file_list") -func _sort_files(d:Dictionary): - return TE_Util.sort_on_ext(d) +func _sort(dir:Dictionary): + return TE_Util.sort_on_ext(dir) func get_selected_file() -> String: var node = get_selected_tab() @@ -280,13 +383,20 @@ func save_file(file_path:String, text:String): emit_signal("file_saved", file_path) func open_last_file(): + print("open last closed") + print(closed) + print(opened) if closed: - closed.pop_back() + var file_path = closed.pop_back() + open_file(file_path) + select_file(file_path) func close_selected(): - var file = get_selected_file() - if file: - close_file(file) + var tab = get_selected_tab() + if tab: + tab.close() + else: + print("cant close") func close_file(file_path:String): var tab = get_tab(file_path) @@ -295,15 +405,12 @@ func close_file(file_path:String): func _close_file(file_path, remember:bool=true): if remember: - closed.append(opened.pop_back()) + closed.append(file_path) var tab = get_tab(file_path) tab_parent.remove_child(tab) tab.queue_free() emit_signal("file_closed", file_path) - - if opened: - select_file(opened[-1]) func open_file(file_path:String, temporary:bool=false): var tab = get_tab(file_path) @@ -312,6 +419,8 @@ func open_file(file_path:String, temporary:bool=false): else: tab = tab_prefab.duplicate() + tab.visible = true + tab.editor = self tab_parent.add_child(tab) tab.set_owner(self) tab.load_file(file_path) @@ -360,7 +469,6 @@ func recycle_file(file_path:String): if opened: select_file(opened[-1]) - func rename_file(old_path:String, new_path:String): if old_path == new_path or not old_path or not new_path: return @@ -394,10 +502,9 @@ func select_file(file_path:String): tab_parent.current_tab = get_tab(file_path).get_index() _selected_file_changed(file_path) -func set_directory(path:String="res://test_files"): +func set_directory(path:String=current_directory): var gpath = ProjectSettings.globalize_path(path) var dname = gpath.get_file() - OS.set_window_title("%s (%s)" % [dname, gpath]) current_directory = path file_dialog.current_dir = path refresh_files() @@ -422,7 +529,7 @@ func get_all_tabs() -> Array: return tab_parent.get_children() func refresh_files(): - ext_counts.clear() +# ext_counts.clear() dirs.clear() file_list.clear() var dir = Directory.new() @@ -433,39 +540,67 @@ func refresh_files(): sort_files() -func _scan_dir(id:String, path:String, dir:Directory, list:Dictionary): +func show_dir(fname:String) -> bool: + if not show_dir_gdignore and File.new().file_exists(fname.plus_file(".gdignore")): + return false + + if fname.begins_with("."): + if not show_dir_hidden: return false + if not show_dir_import and fname == ".import": return false + if not show_dir_git and fname == ".git": return false + if not show_dir_trash and fname == ".trash": return false + + return true + +func show_file(fname:String) -> bool: + if fname.begins_with("."): + if not show_file_hidden: return false + + var ext = get_extension(fname) + return ext in exts_enabled + +func _scan_dir(id:String, path:String, dir:Directory, last_dir:Dictionary): var _e = dir.list_dir_begin(true, false) dirs.append(path) - var files = {} - list[id] = { file_path=path, files=files, open=true } + var a_dirs_and_files = {} + var a_files = [] + var a_dirs = [] + var info = { file_path=path, all=a_dirs_and_files, files=a_files, dirs=a_dirs, open=true } var fname = dir.get_next() while fname: - if not fname.begins_with("."): - var file_path = dir.get_current_dir().plus_file(fname) - - if dir.current_is_dir(): - # ignore folders with a .gdignore file. - if not fname == ".import" and not File.new().file_exists(file_path.plus_file(".gdignore")): - var sub_dir = Directory.new() - sub_dir.open(file_path) - _scan_dir(fname, file_path, sub_dir, files) - - else: - # ignore .import files - if not file_path.ends_with(".import"): - var ext = get_extension(file_path) - if ext in exts_enabled: - files[fname] = file_path - - if not ext in ext_counts: - ext_counts[ext] = 1 - else: - ext_counts[ext] += 1 - + var file_path = dir.get_current_dir().plus_file(fname) + + if dir.current_is_dir(): + if show_dir(fname): + var sub_dir = Directory.new() + sub_dir.open(file_path) + _scan_dir(fname, file_path, sub_dir, a_dirs_and_files) + + else: + if show_file(fname): + a_dirs_and_files[fname] = file_path + fname = dir.get_next() + dir.list_dir_end() + + # is empty? ignore + if id and not (show_dir_empty or a_dirs_and_files): + return + + # add to last + last_dir[id] = info + + if path == current_directory: + print(JSON.print(info, "\t")) + + for p in a_dirs_and_files: + if a_dirs_and_files[p] is Dictionary: + a_dirs.append(p) + else: + a_files.append(p) static func get_extension(file_path:String) -> String: var file = file_path.get_file() diff --git a/addons/text_editor/TE_Util.gd b/addons/text_editor/TE_Util.gd index c6a575a..d0a6c7a 100644 --- a/addons/text_editor/TE_Util.gd +++ b/addons/text_editor/TE_Util.gd @@ -63,6 +63,14 @@ static func _dig_node(d:Node, f:FuncRef): for i in d.get_child_count(): _dig_node(d.get_child(i), f) +static func file_size(path:String) -> String: + var f:File = File.new() + if f.open(path, File.READ) == OK: + var bytes = f.get_len() + f.close() + return String.humanize_size(bytes) + return "-1" + static func sort(d:Dictionary, reverse:bool=false) -> Dictionary: return Dict.new(d).sort(reverse) diff --git a/addons/text_editor/TextEditor.tscn b/addons/text_editor/TextEditor.tscn new file mode 100644 index 0000000..8f39179 --- /dev/null +++ b/addons/text_editor/TextEditor.tscn @@ -0,0 +1,313 @@ +[gd_scene load_steps=16 format=2] + +[ext_resource path="res://addons/text_editor/file_buttons.gd" type="Script" id=1] +[ext_resource path="res://addons/text_editor/TE_TextEditor.gd" type="Script" id=2] +[ext_resource path="res://addons/text_editor/TE_FilesList.gd" type="Script" id=3] +[ext_resource path="res://addons/text_editor/TE_FileEditor.gd" type="Script" id=4] +[ext_resource path="res://addons/text_editor/TE_SymbolsList.gd" type="Script" id=5] +[ext_resource path="res://addons/text_editor/FileTabs.gd" type="Script" id=6] +[ext_resource path="res://addons/text_editor/TE_TagsPanel.gd" type="Script" id=7] +[ext_resource path="res://addons/text_editor/line_edit.gd" type="Script" id=8] +[ext_resource path="res://addons/text_editor/MetaInfo.gd" type="Script" id=9] +[ext_resource path="res://addons/text_editor/fonts/font_i.tres" type="DynamicFont" id=10] +[ext_resource path="res://addons/text_editor/fonts/font_b.tres" type="DynamicFont" id=11] +[ext_resource path="res://addons/text_editor/fonts/font_r.tres" type="DynamicFont" id=12] +[ext_resource path="res://addons/text_editor/fonts/font_bi.tres" type="DynamicFont" id=13] +[ext_resource path="res://addons/text_editor/fonts/font.tres" type="DynamicFont" id=14] + +[sub_resource type="Theme" id=1] +TooltipLabel/fonts/font = ExtResource( 12 ) + +[node name="text_editor" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="file_editor" type="TextEdit" parent="."] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 222.0 +margin_top = 74.0 +margin_right = 214.0 +margin_bottom = 30.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_fonts/font = ExtResource( 14 ) +highlight_current_line = true +syntax_highlighting = true +show_line_numbers = true +draw_tabs = true +fold_gutter = true +highlight_all_occurrences = true +minimap_draw = true +script = ExtResource( 4 ) + +[node name="c" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +custom_constants/separation = 0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="c" type="PanelContainer" parent="c"] +margin_right = 1024.0 +margin_bottom = 34.0 + +[node name="c" type="HBoxContainer" parent="c/c"] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 1017.0 +margin_bottom = 27.0 +script = ExtResource( 1 ) + +[node name="test" type="Button" parent="c/c/c"] +margin_right = 56.0 +margin_bottom = 20.0 +text = "update" + +[node name="file_button" type="MenuButton" parent="c/c/c"] +margin_left = 60.0 +margin_right = 92.0 +margin_bottom = 20.0 +text = "file" +items = [ "New File", null, 0, false, false, 0, 0, null, "", false, "Extensions", null, 0, false, false, 1, 0, null, "Extensions", false, "New File", null, 0, false, false, 2, 0, null, "", false, "Extensions", null, 0, false, false, 3, 0, null, "Extensions", false ] +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="c3" type="HSplitContainer" parent="c"] +margin_top = 34.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_vertical = 3 +split_offset = -300 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="c2" type="PanelContainer" parent="c/c3"] +margin_right = 206.0 +margin_bottom = 566.0 +rect_min_size = Vector2( 200, 0 ) +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="c" type="Panel" parent="c/c3/c2"] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 199.0 +margin_bottom = 559.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="list_files" type="RichTextLabel" parent="c/c3/c2/c"] +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme = SubResource( 1 ) +custom_fonts/bold_italics_font = ExtResource( 13 ) +custom_fonts/italics_font = ExtResource( 10 ) +custom_fonts/bold_font = ExtResource( 11 ) +custom_fonts/normal_font = ExtResource( 12 ) +bbcode_enabled = true +bbcode_text = "[url=add_file:0][color=#ff00ff00]+[/color][/url] +[color=#ff4d4d4d][url=d:0][color=#ff333333][/color]🗀▼[b]test_files[/b][/url][/color] +[url=f:0][color=#ff333333][/color][color=#ff333333]┣╸[/color][i][color=#ff404040]questish[/color][/i].[color=#ff666666]yaml[/color][/url] +[url=f:1][color=#ff333333][/color][color=#ff333333]┣╸[/color][i][color=#ff404040]garoyle[/color][/i].[color=#ff666666]md[/color][/url] +[url=f:2][color=#ff333333][/color][color=#ff333333]┣╸[/color][i][color=#ff404040]json[/color][/i].[color=#ff666666]json[/color][/url] +[url=f:3][color=#ff333333][/color][color=#ff333333]┣╸[/color][i][color=#ff404040]config[/color][/i].[color=#ff666666]ini[/color][/url] +[url=f:4][color=#ff333333][/color][color=#ff333333]┣╸[/color][i][color=#ff404040]acc[/color][/i].[color=#ff666666]csv[/color][/url] +[color=#ff4d4d4d][url=d:1][color=#ff333333]┃ [/color]🗀▼[b]afolder[/b][/url][/color] +[url=f:5][color=#ff333333]┃ [/color][color=#ff333333]┣╸[/color][i][color=#ff404040]new_file[/color][/i].[color=#ff666666]yaml[/color][/url] +[url=f:6][color=#ff333333]┃ [/color][color=#ff333333]┣╸[/color][i][color=#ff404040]ass2[/color][/i].[color=#ff666666]yaml[/color][/url] +[url=f:7][color=#ff333333]┃ [/color][color=#ff333333]┣╸[/color][i][color=#ff404040]todo[/color][/i].[color=#ff666666]md[/color][/url] +[url=f:8][color=#ff333333]┃ [/color][color=#ff333333]┣╸[/color][i][color=#ff404040]my_fileok[/color][/i].[color=#ff666666]md[/color][/url] +[color=#ff4d4d4d][url=d:2][color=#ff333333]┃ ┃ [/color]🗀▼[b]New Folder[/b][/url][/color] +[url=f:9][color=#ff333333]┃ ┃ [/color][color=#ff333333]┗╸[/color][i][color=#ff404040]mor[/color][/i].[color=#ff666666]md[/color][/url]" +meta_underlined = false +text = "+ +🗀▼test_files +┣╸questish.yaml +┣╸garoyle.md +┣╸json.json +┣╸config.ini +┣╸acc.csv +┃ 🗀▼afolder +┃ ┣╸new_file.yaml +┃ ┣╸ass2.yaml +┃ ┣╸todo.md +┃ ┣╸my_fileok.md +┃ ┃ 🗀▼New Folder +┃ ┃ ┗╸mor.md" +script = ExtResource( 3 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="file_popup" type="PopupMenu" parent="c/c3/c2/c/list_files"] +margin_right = 20.0 +margin_bottom = 20.0 +custom_fonts/font = ExtResource( 14 ) +items = [ "Rename", null, 0, false, false, 0, 0, null, "", false, "", null, 0, false, false, -1, 0, null, "", true, "Remove", null, 0, false, false, 2, 0, null, "", false ] + +[node name="dir_popup" type="PopupMenu" parent="c/c3/c2/c/list_files"] +margin_right = 20.0 +margin_bottom = 20.0 +custom_fonts/font = ExtResource( 14 ) +items = [ "Create new file", null, 0, false, false, 0, 0, null, "", false ] + +[node name="c" type="HSplitContainer" parent="c/c3"] +margin_left = 218.0 +margin_right = 1024.0 +margin_bottom = 566.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +split_offset = -80 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="c" type="VBoxContainer" parent="c/c3/c"] +margin_right = 614.0 +margin_bottom = 566.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="line_edit" type="LineEdit" parent="c/c3/c/c"] +visible = false +margin_right = 614.0 +margin_bottom = 24.0 +custom_fonts/font = ExtResource( 12 ) +script = ExtResource( 8 ) + +[node name="tab_container" type="TabContainer" parent="c/c3/c/c"] +margin_right = 614.0 +margin_bottom = 539.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_fonts/font = ExtResource( 12 ) +tab_align = 0 +drag_to_rearrange_enabled = true +script = ExtResource( 6 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="meta" type="RichTextLabel" parent="c/c3/c/c"] +margin_top = 543.0 +margin_right = 614.0 +margin_bottom = 566.0 +custom_fonts/bold_italics_font = ExtResource( 13 ) +custom_fonts/italics_font = ExtResource( 10 ) +custom_fonts/bold_font = ExtResource( 11 ) +custom_fonts/normal_font = ExtResource( 12 ) +bbcode_enabled = true +fit_content_height = true +script = ExtResource( 9 ) + +[node name="c2" type="PanelContainer" parent="c/c3/c"] +margin_left = 626.0 +margin_right = 806.0 +margin_bottom = 566.0 +rect_min_size = Vector2( 100, 0 ) +size_flags_vertical = 3 + +[node name="c" type="VSplitContainer" parent="c/c3/c/c2"] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 173.0 +margin_bottom = 559.0 +custom_constants/autohide = 0 + +[node name="c" type="Panel" parent="c/c3/c/c2/c"] +margin_right = 166.0 +margin_bottom = 270.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="list_symbols" type="RichTextLabel" parent="c/c3/c/c2/c/c"] +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_vertical = 3 +custom_fonts/bold_italics_font = ExtResource( 13 ) +custom_fonts/italics_font = ExtResource( 10 ) +custom_fonts/bold_font = ExtResource( 11 ) +custom_fonts/normal_font = ExtResource( 12 ) +bbcode_enabled = true +bbcode_text = "[color=#ff808080][i][center]*No symbols*" +meta_underlined = false +text = "*No symbols*" +script = ExtResource( 5 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="c2" type="Panel" parent="c/c3/c/c2/c"] +margin_top = 282.0 +margin_right = 166.0 +margin_bottom = 552.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="list_tags" type="RichTextLabel" parent="c/c3/c/c2/c/c2"] +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_fonts/bold_italics_font = ExtResource( 13 ) +custom_fonts/italics_font = ExtResource( 10 ) +custom_fonts/bold_font = ExtResource( 11 ) +custom_fonts/normal_font = ExtResource( 12 ) +bbcode_enabled = true +bbcode_text = "[color=#ff808080][i][center]*No tags*" +meta_underlined = false +text = "*No tags*" +script = ExtResource( 7 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="popup" type="ConfirmationDialog" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -100.0 +margin_top = -35.0 +margin_right = 100.0 +margin_bottom = 35.0 + +[node name="popup_unsaved" type="ConfirmationDialog" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -100.0 +margin_top = -35.0 +margin_right = 100.0 +margin_bottom = 35.0 +window_title = "Warning" +dialog_text = "Unsaved data will be lost." + +[node name="file_dialog" type="FileDialog" parent="."] +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -297.5 +margin_top = -157.0 +margin_right = 297.5 +margin_bottom = 157.0 +current_dir = "res://test_files" +current_path = "res://test_files/" +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/addons/text_editor/ext/TE_ExtensionHelper.gd b/addons/text_editor/ext/TE_ExtensionHelper.gd index 3fb0b52..d59ce58 100644 --- a/addons/text_editor/ext/TE_ExtensionHelper.gd +++ b/addons/text_editor/ext/TE_ExtensionHelper.gd @@ -1,3 +1,4 @@ +tool extends Resource class_name TE_ExtensionHelper @@ -7,10 +8,11 @@ func generate_meta(t:TextEdit, r:TE_RichTextLabel): var chars = TE_Util.commas(len(t.text)) var words = TE_Util.commas(len(t.text.split(" ", false))) var lines = TE_Util.commas(len(TE_Util.split_many(t.text, ".?!\n", false))) - r.add_constant_override("table_hseparation", int(r.rect_size.x / 4.0)) + var bytes = TE_Util.file_size(t.file_path) + r.add_constant_override("table_hseparation", int(r.rect_size.x / 5.0)) r.set_bbcode(r.table([ - ["chars", "words", "lines"], - [chars, words, lines] + ["chars", "words", "lines", "bytes"], + [chars, words, lines, bytes] ])) func toggle_comment(t:TextEdit, head:String="", tail:String=""): diff --git a/addons/text_editor/ext/ext_cfg.gd b/addons/text_editor/ext/ext_cfg.gd new file mode 100644 index 0000000..aa48d5e --- /dev/null +++ b/addons/text_editor/ext/ext_cfg.gd @@ -0,0 +1,2 @@ +tool +extends "res://addons/text_editor/ext/ext_ini.gd" diff --git a/addons/text_editor/ext/ext_csv.gd b/addons/text_editor/ext/ext_csv.gd index 4616724..829dc86 100644 --- a/addons/text_editor/ext/ext_csv.gd +++ b/addons/text_editor/ext/ext_csv.gd @@ -1,2 +1,3 @@ +tool extends TE_ExtensionHelper diff --git a/addons/text_editor/ext/ext_ini.gd b/addons/text_editor/ext/ext_ini.gd index 7e75de3..b727345 100644 --- a/addons/text_editor/ext/ext_ini.gd +++ b/addons/text_editor/ext/ext_ini.gd @@ -1,3 +1,4 @@ +tool extends TE_ExtensionHelper func apply_colors(e:TextEditor, t:TextEdit): diff --git a/addons/text_editor/ext/ext_json.gd b/addons/text_editor/ext/ext_json.gd index ab97f89..dfd8dab 100644 --- a/addons/text_editor/ext/ext_json.gd +++ b/addons/text_editor/ext/ext_json.gd @@ -1,3 +1,4 @@ +tool extends TE_ExtensionHelper func toggle_comment(t:TextEdit, head:String="/*", tail:String="*/"): diff --git a/addons/text_editor/ext/ext_md.gd b/addons/text_editor/ext/ext_md.gd index 2db9a5e..d91a094 100644 --- a/addons/text_editor/ext/ext_md.gd +++ b/addons/text_editor/ext/ext_md.gd @@ -1,3 +1,4 @@ +tool extends TE_ExtensionHelper func toggle_comment(t:TextEdit, head:String=""): @@ -5,17 +6,17 @@ func toggle_comment(t:TextEdit, head:String=""): func apply_colors(e:TextEditor, t:TextEdit): .apply_colors(e, t) - var code:Color = Color.aquamarine.darkened(.5) + var code:Color = e.color_text.darkened(.5) t.add_keyword_color("true", e.color_var) t.add_keyword_color("false", e.color_var) # bold italic - t.add_color_region("***", "***", Color.tomato.lightened(.3), false) + t.add_color_region("***", "***", Color.tomato.darkened(.3), false) # bold t.add_color_region("**", "**", Color.tomato, false) # italic - t.add_color_region("*", "*", Color.tomato.darkened(.3), false) + t.add_color_region("*", "*", Color.tomato.lightened(.3), false) # quote t.add_color_region("> ", "", Color.white.darkened(.6), true) diff --git a/addons/text_editor/ext/ext_yaml.gd b/addons/text_editor/ext/ext_yaml.gd index 1360e2d..1a42892 100644 --- a/addons/text_editor/ext/ext_yaml.gd +++ b/addons/text_editor/ext/ext_yaml.gd @@ -1,3 +1,4 @@ +tool extends TE_ExtensionHelper func _is_commented(lines) -> bool: diff --git a/addons/text_editor/line_edit.gd b/addons/text_editor/line_edit.gd index 01e28db..77e087e 100644 --- a/addons/text_editor/line_edit.gd +++ b/addons/text_editor/line_edit.gd @@ -1,3 +1,4 @@ +tool extends LineEdit onready var editor:TextEditor = owner diff --git a/addons/text_editor/list_files.gd b/addons/text_editor/list_files.gd deleted file mode 100644 index b24856e..0000000 --- a/addons/text_editor/list_files.gd +++ /dev/null @@ -1,267 +0,0 @@ -extends RichTextLabel - -onready var editor:TextEditor = owner -onready var popup:PopupMenu = $popup -onready var drag_label:RichTextLabel = $drag_label - -var files:Array = [] -var dirs:Array = [] -var selected -var hovered:String = "" -var dragging:String = "" -var drag_start:Vector2 - -func _ready(): - var _e - _e = editor.connect("updated_file_list", self, "_redraw") - _e = editor.connect("tags_updated", self, "_redraw") - _e = editor.connect("file_opened", self, "_file_opened") - _e = editor.connect("file_closed", self, "_file_closed") - _e = editor.connect("file_selected", self, "_file_selected") - _e = editor.connect("file_renamed", self, "_file_renamed") -# _e = connect("meta_clicked", self, "_clicked") - _e = connect("meta_hover_started", self, "_meta_entered") - _e = connect("meta_hover_ended", self, "_meta_exited") - - # popup - popup.add_item("Rename") - popup.add_separator() - popup.add_item("Remove") - _e = popup.connect("index_pressed", self, "_popup") - popup.add_font_override("font", TextEditor.FONT) - - for n in [self, drag_label]: - n.add_font_override("normal_font", editor.FONT_R) - n.add_font_override("bold_font", editor.FONT_B) - n.add_font_override("italics_font", editor.FONT_I) - n.add_font_override("bold_italics_font", editor.FONT_BI) - - set_process(false) - -func _popup(index:int): - var p = selected.split(":", true, 1) - var type = p[0] - var file = files[int(p[1])] if type == "f" else dirs[int(p[1])] if type == "d" else "" - - match popup.get_item_text(index): - "Rename": - var fname:String = selected.file_path.get_file() - var i:int = fname.find(".") - editor.line_edit.display(fname, self, "_renamed") - editor.line_edit.select(0, i) - - "Remove": - if type == "f": - editor.recycle_file(file) - - _: - selected = {} - -func _renamed(new_file:String): - var old_path:String = selected.file_path - var old_file:String = old_path.get_file() - if new_file != old_file: - var new_path:String = old_path.get_base_dir().plus_file(new_file) - editor.rename_file(old_path, new_path) - selected = {} - -func _process(_delta): - var mp = get_global_mouse_position() - if mp.distance_to(drag_start) > 16: - drag_label.visible = true - drag_label.set_global_position(mp) - -func end_drag(): - dragging = "" - drag_label.visible = false - set_process(false) - -func _input(e:InputEvent): - if e is InputEventMouseButton and hovered: - var m = hovered.split(":", true, 1) - var type = m[0] - var index = int(m[1]) - - if e.button_index == BUTTON_LEFT: - if e.pressed: - dragging = hovered - - if type == "f": - drag_label.set_bbcode(files[index].get_file()) -# drag_label.visible = true - drag_start = get_global_mouse_position() - set_process(true) - - else: - if dragging and dragging != hovered: - m = dragging.split(":", true, 1) - var drag_type = m[0] - var drag_index = int(m[1]) - if drag_type == "f" and type == "d": - var dir:String = dirs[index].file_path - var old_path:String = files[drag_index] - var new_path:String = dir.plus_file(old_path.get_file()) - editor.rename_file(old_path, new_path) - - else: - if type == "d": - dirs[index].open = not dirs[index].open - _redraw() - - elif type == "f": - editor.select_file(files[index]) - - end_drag() - get_tree().set_input_as_handled() - return - - elif e.button_index == BUTTON_RIGHT: - if e.pressed: - selected = hovered - popup.set_global_position(get_global_mouse_position()) - popup.popup() - get_tree().set_input_as_handled() - return - - if e is InputEventMouseButton: - if dragging and (e.button_index == BUTTON_LEFT and not e.pressed) or (e.button_index == BUTTON_RIGHT): - end_drag() - get_tree().set_input_as_handled() - return - - -func _meta_entered(m): hovered = m - -func _meta_exited(_m): hovered = "" - -func _file_opened(_file_path:String): _redraw() -func _file_closed(_file_path:String): _redraw() -func _file_selected(_file_path:String): _redraw() -func _file_renamed(_op:String, _np:String): _redraw() - -#func updated_file_list(): -# items.clear() -# _updated_file_list(editor.file_list, 0) -# redraw() - -var lines:PoolStringArray = PoolStringArray() - -func _redraw(): - lines = PoolStringArray() - lines.append("[url=add_file:0][color=#%s]+[/color][/url]" % [Color.green.to_html()]) - dirs.clear() - _draw_dir(editor.file_list[""], 0) - set_bbcode(lines.join("\n")) - -func clr(s:String, c:Color) -> String: return "[color=#%s]%s[/color]" % [c.to_html(), s] -func i(s:String) -> String: return "[i]%s[/i]" % s -func b(s:String) -> String: return "[b]%s[/b]" % s -func url(s:String, url:String) -> String: return "[url=%s]%s[/url]" % [url, s] - -const FOLDER:String = "🗀" # not visible in godot -func _draw_dir(dir:Dictionary, deep:int): - var space = clr("┃ ".repeat(deep), Color.white.darkened(.8)) - var file:String = dir.file_path - var name:String = b(file.get_file()) - var head:String = "▼" if dir.open else "▶" - var link:String = url("%s%s%s%s" % [space, FOLDER, head, name], "d:%s" % len(dirs)) - lines.append(clr(link, Color.white.darkened(.7))) - dirs.append(dir) -# var add = "[url=add_file:%s][color=#%s]+[/color][/url]" % [dindex, Color.green.to_html()] -# name = "[color=#%s]%s[/color] %s" % [Color.darkslategray.to_html(), item.name, add] - - var sel = editor.get_selected_tab() - sel = sel.file_path if sel else "" - - if dir.open: - var i = 0 - var last = len(dir.files)-1 - for path in dir.files: - var file_path = dir.files[path] - # dir - if file_path is Dictionary: - _draw_dir(file_path, deep+1) - - # file - else: - file = path.get_file() - var is_selected = file_path == sel - head = "┣╸" if i != last else "┗╸" - if is_selected: - head = clr(head, Color.white.darkened(.5)) - else: - head = clr(head, Color.white.darkened(.8)) - var p = file.split(".", true, 1) - file = p[0] - - var color = Color.white if editor.is_tagged(file_path) else Color.white.darkened(.5) - - if editor.is_selected(file_path): - file = clr(file, color) - elif editor.is_opened(file_path): - file = clr(file, color.darkened(.5)) - else: - file = i(clr(file, color.darkened(.75))) - - var ext = clr(p[1], Color.white.darkened(.6)) - var line = space + head + file + "." + ext - lines.append(url(line, "f:%s" % len(files))) - files.append(file_path) - i += 1 - - - - # file -# else: -# var p = item.name.split(".", true, 1) -# name = p[0] -# var ext = p[1] -# var clr = Color.white -# var dim = .75 -# -# if editor.is_selected(item.file_path): -# dim = 0.0 -# -# elif editor.is_open(item.file_path): -# dim = .5 -# -# name = "[color=#%s]%s[/color]" % [clr.darkened(dim).to_html(), name] -# name += "[color=#%s].%s[/color]" % [clr.darkened(.75).to_html(), ext] -# -# var tab = editor.get_tab(item.file_path) -# if tab and editor.is_tagged_or_visible(tab.tags.keys()): -# name = "[b]%s[/b]" % name -# -# var space = "" -# if item.deep: -# wide += item.deep * 2 -# -# if item.deep > 1: -# space += "┃ " + " ".repeat(int(max(0, item.deep-2))) -# else: -# space += " ".repeat(int(max(0, item.deep-1))) -# -# if item.last: -# space += "┗╸" -# else: -# space += "┣╸" -# -# space = "[color=#%s]%s[/color]" % [Color.darkslategray.to_html(), space] -# -# # add extra space to make clicking easier -# var extra = max(0, 16 - wide) -# name += " ".repeat(extra) -# text.append("%s[url=f:%s]%s[/url]" % [space, i, name]) -# -# set_bbcode(text.join("\n")) - -#func _updated_file_list(data:Dictionary, deep:int): -# var total = len(data) -# var i = 0 -# for k in data: -# if data[k] is Dictionary: -# items.append({ last=i==total-1, type="D", name=k, file_path=data[k].file_path, deep=deep, open=true }) -# _updated_file_list(data[k].files, deep+1) -# else: -# items.append({ last=i==total-1, type="F", name=k, file_path=data[k], deep=deep }) -# i += 1 diff --git a/addons/text_editor/plugin.cfg b/addons/text_editor/plugin.cfg new file mode 100644 index 0000000..2cb74b0 --- /dev/null +++ b/addons/text_editor/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="TextEditor" +description="A text editor for Godot." +author="teebar" +version="1.0" +script="plugin.gd" diff --git a/addons/text_editor/plugin.gd b/addons/text_editor/plugin.gd new file mode 100644 index 0000000..032d640 --- /dev/null +++ b/addons/text_editor/plugin.gd @@ -0,0 +1,26 @@ +tool +extends EditorPlugin + +const TEPanel:PackedScene = preload("res://addons/text_editor/TextEditor.tscn") +var panel:Node + +func get_plugin_name(): return "Text" +func get_plugin_icon(): return get_editor_interface().get_base_control().get_icon("Font", "EditorIcons") +func has_main_screen(): return true + +func _enter_tree(): + panel = TEPanel.instance() + panel.plugin = self + panel.plugin_hint = true + get_editor_interface().get_editor_viewport().add_child(panel) + make_visible(false) + +func _exit_tree(): + if panel: + panel.queue_free() + +func make_visible(visible): + if panel: + panel.visible = visible + + diff --git a/addons/text_editor/tab_scroll.gd b/addons/text_editor/tab_scroll.gd index 8f34840..d0832e5 100644 --- a/addons/text_editor/tab_scroll.gd +++ b/addons/text_editor/tab_scroll.gd @@ -1,5 +1,8 @@ +tool extends TabContainer +onready var editor:TextEditor = owner + var mouse:bool = false func _ready(): @@ -8,6 +11,9 @@ func _ready(): _e = connect("mouse_exited", self, "set", ["mouse", false]) func _input(e): + if not editor.is_plugin_active(): + return + if mouse and e is InputEventMouseButton and e.pressed: if e.button_index == BUTTON_WHEEL_DOWN: prev() diff --git a/project.godot b/project.godot index 98ce9ed..77b3eb0 100644 --- a/project.godot +++ b/project.godot @@ -24,10 +24,10 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/text_editor/TE_Util.gd" }, { -"base": "Node", +"base": "Control", "class": "TextEditor", "language": "GDScript", -"path": "res://addons/text_editor/TextEditor.gd" +"path": "res://addons/text_editor/TE_TextEditor.gd" } ] _global_script_class_icons={ "TE_ExtensionHelper": "", @@ -39,7 +39,7 @@ _global_script_class_icons={ [application] config/name="TextEdit" -run/main_scene="res://TextEditor.tscn" +run/main_scene="res://addons/text_editor/TextEditor.tscn" config/icon="res://icon.png" [physics]