mirror of
https://github.com/Relintai/Godot-TextEditor.git
synced 2025-01-26 16:59:19 +01:00
filtering
This commit is contained in:
parent
3e482dd5e0
commit
6c09d1d66d
4
.gitignore
vendored
4
.gitignore
vendored
@ -12,5 +12,5 @@ data_*/
|
||||
|
||||
# Text editor related
|
||||
test_files/
|
||||
.trash/
|
||||
.trash_info.json
|
||||
.te_trash/
|
||||
.te_trash_info.json
|
||||
|
3
Node2D.tscn
Normal file
3
Node2D.tscn
Normal file
@ -0,0 +1,3 @@
|
||||
[gd_scene format=2]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
63
README.md
Normal file
63
README.md
Normal file
@ -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 `<!-- #tag1 #tag2 -->`
|
||||
- `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.
|
||||
|
||||
|
||||
|
255
TextEditor.tscn
255
TextEditor.tscn
@ -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
|
||||
}
|
34
addons/text_editor/FileTabs.gd
Normal file
34
addons/text_editor/FileTabs.gd
Normal file
@ -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())
|
||||
|
@ -1,3 +1,4 @@
|
||||
tool
|
||||
extends TE_RichTextLabel
|
||||
|
||||
onready var editor:TextEditor = owner
|
18
addons/text_editor/TE_DragLabel.gd
Normal file
18
addons/text_editor/TE_DragLabel.gd
Normal file
@ -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()
|
@ -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)
|
238
addons/text_editor/TE_FilesList.gd
Normal file
238
addons/text_editor/TE_FilesList.gd
Normal file
@ -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
|
@ -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
|
@ -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(" "))
|
@ -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()
|
@ -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)
|
||||
|
||||
|
313
addons/text_editor/TextEditor.tscn
Normal file
313
addons/text_editor/TextEditor.tscn
Normal file
@ -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
|
||||
}
|
@ -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=""):
|
||||
|
2
addons/text_editor/ext/ext_cfg.gd
Normal file
2
addons/text_editor/ext/ext_cfg.gd
Normal file
@ -0,0 +1,2 @@
|
||||
tool
|
||||
extends "res://addons/text_editor/ext/ext_ini.gd"
|
@ -1,2 +1,3 @@
|
||||
tool
|
||||
extends TE_ExtensionHelper
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
tool
|
||||
extends TE_ExtensionHelper
|
||||
|
||||
func apply_colors(e:TextEditor, t:TextEdit):
|
||||
|
@ -1,3 +1,4 @@
|
||||
tool
|
||||
extends TE_ExtensionHelper
|
||||
|
||||
func toggle_comment(t:TextEdit, head:String="/*", tail:String="*/"):
|
||||
|
@ -1,3 +1,4 @@
|
||||
tool
|
||||
extends TE_ExtensionHelper
|
||||
|
||||
func toggle_comment(t:TextEdit, head:String="<!-- ", tail:String=" -->"):
|
||||
@ -5,17 +6,17 @@ func toggle_comment(t:TextEdit, head:String="<!-- ", tail: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)
|
||||
|
@ -1,3 +1,4 @@
|
||||
tool
|
||||
extends TE_ExtensionHelper
|
||||
|
||||
func _is_commented(lines) -> bool:
|
||||
|
@ -1,3 +1,4 @@
|
||||
tool
|
||||
extends LineEdit
|
||||
|
||||
onready var editor:TextEditor = owner
|
||||
|
@ -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
|
7
addons/text_editor/plugin.cfg
Normal file
7
addons/text_editor/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="TextEditor"
|
||||
description="A text editor for Godot."
|
||||
author="teebar"
|
||||
version="1.0"
|
||||
script="plugin.gd"
|
26
addons/text_editor/plugin.gd
Normal file
26
addons/text_editor/plugin.gd
Normal file
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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]
|
||||
|
Loading…
Reference in New Issue
Block a user