This commit is contained in:
teebarjunk 2021-10-10 23:10:22 -04:00
commit af2b69645f
34 changed files with 2013 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# Godot-specific ignores
.import/
export.cfg
export_presets.cfg
# Imported translations (automatically generated from CSV files)
*.translation
# Mono-specific ignores
.mono/
data_*/
# Text editor related
test_files/
.trash/
.trash_info.json

255
TextEditor.tscn Normal file
View File

@ -0,0 +1,255 @@
[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/text_edit.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
}

View File

@ -0,0 +1,21 @@
extends RichTextLabel
class_name TE_RichTextLabel
func _ready():
add_font_override("normal_font", owner.FONT_R)
add_font_override("bold_font", owner.FONT_B)
add_font_override("italics_font", owner.FONT_I)
add_font_override("bold_italics_font", owner.FONT_BI)
func table(rows) -> String:
var cells = ""
var clr = Color.white.darkened(.5).to_html()
for i in len(rows):
if i == 0:
for item in rows[i]:
cells += "[cell][b]%s[/b][/cell][/color]" % item
else:
for item in rows[i]:
cells += "[cell][color=#%s]%s[/color][/cell]" % [clr, item]
return "[center][table=%s]%s[/table][/center]" % [len(rows[0]), cells]

View File

@ -0,0 +1,139 @@
class_name TE_Util
static func load_json(path:String) -> Dictionary:
var f:File = File.new()
if f.file_exists(path):
f.open(path, File.READ)
var out = JSON.parse(f.get_as_text()).result
f.close()
return out
return {}
static func save_json(path:String, data:Dictionary):
var f:File = File.new()
f.open(path, File.WRITE)
f.store_string(JSON.print(data, "\t"))
f.close()
static func is_wrapped(t:String, head:String, tail:String) -> bool:
t = t.strip_edges()
return t.begins_with(head) and t.ends_with(tail)
static func unwrap(t:String, head:String, tail:String, keep_white:bool=false) -> String:
var stripped = t.strip_edges()
stripped = stripped.substr(len(head), len(stripped)-len(head)-len(tail))
if keep_white:
var whead = get_whitespace_head(t)
var wtail = get_whitespace_tail(t)
return whead + stripped + wtail
else:
return t.substr(len(head), len(t)-len(head)-len(tail))
static func wrap(t:String, head:String, tail:String, keep_white:bool=false) -> String:
if keep_white:
var whead = get_whitespace_head(t)
var wtail = get_whitespace_tail(t)
return whead + head + t.strip_edges() + tail + wtail
else:
return head + t + tail
static func get_whitespace_head(t:String):
var length = len(t) - len(t.strip_edges(true, false))
return t.substr(0, length)
static func get_whitespace_tail(t:String):
var length = len(t) - len(t.strip_edges(false, true))
return t.substr(len(t)-length)
static func dig(d, obj:Object, fname:String):
var f = funcref(obj, fname)
if d is Dictionary:
_dig_dict(d, f)
elif d is Node:
_dig_node(d, f)
static func _dig_dict(d:Dictionary, f:FuncRef):
f.call_func(d)
for k in d:
if d[k] is Dictionary:
_dig_dict(d[k], f)
static func _dig_node(d:Node, f:FuncRef):
f.call_func(d)
for i in d.get_child_count():
_dig_node(d.get_child(i), f)
static func sort(d:Dictionary, reverse:bool=false) -> Dictionary:
return Dict.new(d).sort(reverse)
static func sort_value(d:Dictionary, reverse:bool=false) -> Dictionary:
return Dict.new(d).sort_value(reverse)
static func sort_on_ext(d:Dictionary, reverse:bool=false) -> Dictionary:
return Dict.new(d).sort_ext(reverse)
static func split_many(s:String, spliton:String, allow_empty:bool=true) -> PoolStringArray:
var parts := PoolStringArray()
var start := 0
var i := 0
while i < len(s):
if s[i] in spliton:
if allow_empty or start < i:
parts.append(s.substr(start, i - start))
start = i + 1
i += 1
if allow_empty or start < i:
parts.append(s.substr(start, i - start))
return parts
static func commas(number) -> String:
number = str(number)
var mod = len(number) % 3
var out = ""
for i in len(number):
if i and i % 3 == mod:
out += ","
out += number[i]
return out
class Dict:
var d:Dictionary
var a:Array = []
var i:int = 0
func _init(dict:Dictionary):
d = dict
func _pop():
for k in d: a.append([k, d[k]])
func _unpop() -> Dictionary:
d.clear()
for i in a: d[i[0]] = i[1]
return d
func sort(reverse:bool=false) -> Dictionary:
_pop()
a.sort_custom(self, "_sort_reverse" if reverse else "_sort")
return _unpop()
func sort_value(reverse:bool=false) -> Dictionary:
_pop()
i = 1
a.sort_custom(self, "_sort_reverse" if reverse else "_sort")
return _unpop()
func sort_ext(reverse:bool=false) -> Dictionary:
for k in d:
if "." in k:
var p = k.split(".", true, 1)
p = p[1] + p[0]
a.append([k, d[k], p + "." + k])
else:
a.append([k, d[k], "." + k])
i = 2
a.sort_custom(self, "_sort_reverse" if reverse else "_sort")
return _unpop()
func _sort(a, b): return a[i] > b[i]
func _sort_reverse(a, b): return a[i] < b[i]

View File

@ -0,0 +1,442 @@
extends Node
class_name TextEditor
const FONT:DynamicFont = preload("res://addons/text_editor/fonts/font.tres")
const FONT_R:DynamicFont = preload("res://addons/text_editor/fonts/font_r.tres")
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 SHOW_EXT:PoolStringArray = PoolStringArray([
".txt", ".md", ".json", ".csv", ".ini", ".cfg", ".yaml"
])
const FILE_FILTERS:PoolStringArray = PoolStringArray([
"*.txt ; Text",
"*.md ; Markdown",
"*.json ; JSON",
"*.csv ; Comma Seperated Values",
"*.cfg ; Config",
"*.ini ; Config",
"*.yaml ; YAML"
])
var color_text:Color = Color.white
var color_comment:Color = Color.darkolivegreen
var color_symbol:Color = Color.white.darkened(.5)
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 popup:ConfirmationDialog = $popup
onready var popup_unsaved:ConfirmationDialog = $popup_unsaved
onready var file_dialog:FileDialog = $file_dialog
onready var menu_file:MenuButton = $c/c/c/file_button
onready var line_edit:LineEdit = $c/c3/c/c/line_edit
signal updated_file_list()
signal file_opened(file_path)
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 dirs:Array = []
var file_list:Dictionary = {}
var extensions:Dictionary = {}
var symbols:Dictionary = {}
var tags:Array = []
var tags_enabled:Dictionary = {}
var tag_counts:Dictionary = {}
var opened:Array = []
var closed:Array = []
func _ready():
var _e
_e = test_button.connect("pressed", self, "_debug_pressed")
# popup unsaved
popup_unsaved.get_ok().text = "Ok"
popup_unsaved.get_cancel().text = "Cancel"
var btn = popup_unsaved.add_button("Save and Close", false, "save_and_close")
btn.modulate = Color.yellowgreen
btn.connect("pressed", popup_unsaved, "hide")
TE_Util.dig(popup_unsaved, self, "_apply_fonts")
# menu
var p = menu_file.get_popup()
p.add_font_override("font", FONT_R)
p.add_item("New File")
_e = p.connect("index_pressed", self, "_menu_file")
# file dialog
_e = file_dialog.connect("file_selected", self, "_file_dialog_file")
file_dialog.add_font_override("title_font", FONT_R)
TE_Util.dig(file_dialog, self, "_apply_fonts")
# 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)
set_directory()
func _input(e):
if e is InputEventMouseButton and e.control:
if e.button_index == BUTTON_WHEEL_DOWN:
FONT.size = int(max(8, FONT.size - 1))
get_tree().set_input_as_handled()
elif e.button_index == BUTTON_WHEEL_UP:
FONT.size = int(min(64, FONT.size + 1))
get_tree().set_input_as_handled()
func _apply_fonts(n:Node):
if n is Control:
if n.has_font("font"):
n.add_font_override("font", FONT_R)
func _menu_file(a):
match menu_file.get_popup().items[a]:
"New File": popup_create_file()
func _file_dialog_file(file_path:String):
match file_dialog.get_meta("mode"):
"create": create_file(file_path)
var tab_index:int = -1
func _tab_changed(index:int):
tab_index = index
var node = tab_parent.get_child(index)
if node:
_selected_file_changed(get_selected_file())
else:
_selected_file_changed("")
var last_selected_file:String = ""
func _selected_file_changed(file_path:String):
if file_path != last_selected_file:
last_selected_file = file_path
emit_signal("file_selected", last_selected_file)
func is_tag_enabled(tag:String) -> bool:
return tags_enabled[tag]
func enable_tag(tag:String, enabled:bool=true):
tags_enabled[tag] = enabled
tags.clear()
for t in tags_enabled:
if tags_enabled[t]:
tags.append(t)
emit_signal("tags_updated")
func is_tagged_or_visible(file_tags:Array) -> bool:
if not len(tags):
return true
for t in tags:
if not t in file_tags:
return false
return true
func is_tagged(file_path:String) -> bool:
if not len(tags):
return true
var tab = get_tab(file_path)
if tab:
return is_tagged_or_visible(tab.tags.keys())
return false
func popup_create_file(dir:String="res://"):
file_dialog.set_meta("mode", "create")
file_dialog.current_dir = dir
file_dialog.current_path = "new_file.txt"
file_dialog.window_title = "Create File"
file_dialog.mode = FileDialog.MODE_SAVE_FILE
file_dialog.filters = FILE_FILTERS
file_dialog.show()
func create_file(file_path:String):
var f:File = File.new()
if f.open(file_path, File.WRITE) == OK:
f.store_string("")
f.close()
refresh_files()
open_file(file_path)
select_file(file_path)
else:
push_error("couldnt create %s" % file_path)
func _debug_pressed():
set_directory()
func _unhandled_key_input(e:InputEventKey):
if not e.pressed:
return
if e.control:
# save
if e.scancode == KEY_S:
emit_signal("save_files")
# 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:
sort_files()
else:
return
get_tree().set_input_as_handled()
func sort_files():
TE_Util.dig(file_list, self, "_sort_files")
emit_signal("updated_file_list")
func _sort_files(d:Dictionary):
return TE_Util.sort_on_ext(d)
func get_selected_file() -> String:
var node = get_selected_tab()
return node.file_path if node else ""
func get_tab(file_path:String) -> TextEdit:
for child in tab_parent.get_children():
if child.file_path == file_path:
return child
return null
func get_selected_tab() -> TextEdit:
var i = tab_parent.current_tab
if i >= 0 and i < tab_parent.get_child_count():
return tab_parent.get_child(i) as TextEdit
return null
func get_temporary_tab() -> TextEdit:
for child in tab_parent.get_children():
if child.temporary:
return child
return null
func save_file(file_path:String, text:String):
var f:File = File.new()
var _err = f.open(file_path, File.WRITE)
f.store_string(text)
f.close()
emit_signal("file_saved", file_path)
func open_last_file():
if closed:
closed.pop_back()
func close_selected():
var file = get_selected_file()
if file:
close_file(file)
func close_file(file_path:String):
var tab = get_tab(file_path)
if tab:
tab.close()
func _close_file(file_path, remember:bool=true):
if remember:
closed.append(opened.pop_back())
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)
if tab:
return tab
else:
tab = tab_prefab.duplicate()
tab_parent.add_child(tab)
tab.set_owner(self)
tab.load_file(file_path)
if temporary:
tab.temporary = true
else:
opened.append(file_path)
emit_signal("file_opened", file_path)
return tab
func is_opened(file_path:String) -> bool:
return get_tab(file_path) != null
func is_selected(file_path:String) -> bool:
return get_selected_file() == file_path
func recycle_file(file_path:String):
var old_base:String = file_path.substr(len("res://")).get_base_dir()
var p = file_path.get_file().split(".", true, 1)
var old_name:String = p[0]
var old_ext:String = p[1]
var tab = get_tab(file_path)
var new_file = "%s_%s.%s" % [old_name, OS.get_system_time_secs(), old_ext]
var new_path:String = "res://.trash".plus_file(old_base).plus_file(new_file)
# create directory
var new_dir = new_path.get_base_dir()
if Directory.new().make_dir_recursive(new_dir) != OK:
print("couldn't remove %s" % file_path)
return
# save recovery information
var trash_info = TE_Util.load_json("res://.trash_info.json")
trash_info[new_path] = file_path
TE_Util.save_json("res://.trash_info.json", trash_info)
# remove by renaming
rename_file(file_path, new_path)
print("Send to " + new_path)
if tab:
tab_parent.remove_child(tab)
tab.queue_free()
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
if File.new().file_exists(new_path):
push_error("can't rename %s to %s. file already exists." % [old_path, new_path])
return
var selected = get_selected_file()
if Directory.new().rename(old_path, new_path) == OK:
refresh_files()
if selected == old_path:
_selected_file_changed(new_path)
emit_signal("file_renamed", old_path, new_path)
else:
push_error("couldn't rename %s to %s." % [old_path, new_path])
func select_file(file_path:String):
var temp = get_temporary_tab()
if temp:
if temp.file_path == file_path:
temp.temporary = false
else:
temp.close()
if not is_opened(file_path):
open_file(file_path, true)
# select current tab
tab_parent.current_tab = get_tab(file_path).get_index()
_selected_file_changed(file_path)
func set_directory(path:String="res://test_files"):
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()
func _file_symbols_updated(file_path:String):
var tg = get_tab(file_path).tags
for tag in tg:
if not tag in tags_enabled:
tags_enabled[tag] = false
tag_counts.clear()
for child in get_all_tabs():
for t in child.tags:
if not t in tag_counts:
tag_counts[t] = child.tags[t]
else:
tag_counts[t] += child.tags[t]
emit_signal("symbols_updated")
func get_all_tabs() -> Array:
return tab_parent.get_children()
func refresh_files():
extensions.clear()
dirs.clear()
file_list.clear()
var dir = Directory.new()
if dir.open(current_directory) == OK:
_scan_dir("", current_directory, dir, file_list)
else:
push_error("error trying to load %s." % current_directory)
sort_files()
func _scan_dir(id:String, path:String, dir:Directory, list:Dictionary):
var _e = dir.list_dir_begin(true, false)
dirs.append(path)
var files = {}
list[id] = { file_path=path, files=files, open=true }
var fname = dir.get_next()
while fname:
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"):
files[fname] = file_path
var ext = get_extension(file_path)
if not ext in extensions:
extensions[ext] = 1
else:
extensions[ext] += 1
fname = dir.get_next()
dir.list_dir_end()
static func get_extension(file_path:String) -> String:
var file = file_path.get_file()
if "." in file:
return file.split(".", true, 1)[1]
return ""
static func get_extension_helper(file_path:String) -> TE_ExtensionHelper:
var ext:String = get_extension(file_path).replace(".", "_")
var ext_path:String = "res://addons/text_editor/ext/ext_%s.gd" % ext
if File.new().file_exists(ext_path):
return load(ext_path).new()
return load("res://addons/text_editor/ext/TE_ExtensionHelper.gd").new()

View File

@ -0,0 +1,67 @@
extends Resource
class_name TE_ExtensionHelper
var symbols:Dictionary = {}
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))
r.set_bbcode(r.table([
["chars", "words", "lines"],
[chars, words, lines]
]))
func toggle_comment(t:TextEdit, head:String="", tail:String=""):
var wasnt_selected:bool = false
var cursor_l
var cursor_c
if not t.is_selection_active():
var l = t.cursor_get_line()
var lt = t.get_line(l)
wasnt_selected = lt.strip_edges() == ""
cursor_l = t.cursor_get_line()
cursor_c = t.cursor_get_column()
var s = len(lt) - len(lt.strip_edges(true, false))
t.select(l, s, l, len(t.get_line(l)))
var l1 = t.get_selection_from_line()
var c1 = t.get_selection_from_column()
var old = t.get_selection_text()
var new
if TE_Util.is_wrapped(old, head, tail):
new = TE_Util.unwrap(old, head, tail)
else:
new = TE_Util.wrap(old, head, tail)
t.insert_text_at_cursor(new)
if wasnt_selected:
t.deselect()
t.cursor_set_line(cursor_l)
t.cursor_set_column(cursor_c+len(head))
else:
var l = new.split("\n")
var l2 = l1 + len(l)-1
var c2 = c1 + len(l[-1])
t.select(l1, c1, l2, c2)
return [old, new]
func add_symbol(line:int=-1, deep:int=0, name:String="") -> Dictionary:
var symbol = { deep=deep, name=name, tags=[] }
symbols[line] = symbol
return symbol
func get_symbols(t:String) -> Dictionary:
symbols = {}
return symbols
func apply_colors(e, t:TextEdit):
t.add_color_override("font_color", e.color_text)
t.add_color_override("number_color", e.color_var)
t.add_color_override("member_variable_color", e.color_var)

View File

@ -0,0 +1,2 @@
extends TE_ExtensionHelper

View File

@ -0,0 +1,34 @@
extends TE_ExtensionHelper
func apply_colors(e:TextEditor, t:TextEdit):
.apply_colors(e, t)
# symbols
t.add_color_region("[", "]", e.color_symbol, false)
# string
t.add_color_region('"', '"', e.color_var, false)
# comment
t.add_color_region(';', '', e.color_comment, true)
func get_symbols(t:String) -> Dictionary:
var out = .get_symbols(t)
var last = add_symbol()
var lines = t.split("\n")
var i = 0
while i < len(lines):
# symbols
if lines[i].begins_with("["):
var name = lines[i].split("[", true, 1)[1].split("]", true, 1)[0]
last = add_symbol(i, 0, name)
# tags
elif lines[i].begins_with(";") and "#" in lines[i]:
for t in lines[i].substr(1).split("#"):
if t:
last.tags.append(t)
i += 1
return out

View File

@ -0,0 +1,41 @@
extends TE_ExtensionHelper
func toggle_comment(t:TextEdit, head:String="/*", tail:String="*/"):
return .toggle_comment(t, head, tail)
func get_symbols(t:String):
var out = .get_symbols(t)
var last = add_symbol()
var lines = t.split("\n")
var i = 0
while i < len(lines):
# symbols
if "\": {" in lines[i]:
var key = lines[i].split("\": {", true, 1)[0].rsplit("\"", true, 0)[1]
var deep = max(0, len(lines[i]) - len(lines[i].strip_edges(true, false)) - 1)
last = add_symbol(i, deep, key)
# tags
elif "/* #" in lines[i]:
for tag in lines[i].split("/* #", true, 1)[1].split("*/", true, 1)[0].split("#"):
tag = tag.strip_edges()
if tag:
last.tags.append(tag)
i += 1
return out
func apply_colors(e:TextEditor, t:TextEdit):
.apply_colors(e, t)
# vars
t.add_color_region(' "', '"', e.color_var)
t.add_color_region('"', '"', e.color_varname)
t.add_keyword_color("true", e.color_var)
t.add_keyword_color("false", e.color_var)
# comments
t.add_color_region("/*", "*/", e.color_comment)
t.add_color_region("//", "", e.color_comment, true)

View File

@ -0,0 +1,84 @@
extends TE_ExtensionHelper
func toggle_comment(t:TextEdit, head:String="<!-- ", tail:String=" -->"):
return .toggle_comment(t, head, tail)
func apply_colors(e:TextEditor, t:TextEdit):
.apply_colors(e, t)
var code:Color = Color.aquamarine.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)
# bold
t.add_color_region("**", "**", Color.tomato, false)
# italic
t.add_color_region("*", "*", Color.tomato.darkened(.3), false)
# quote
t.add_color_region("> ", "", Color.white.darkened(.6), true)
# comment
t.add_color_region("<!--", "-->", e.color_comment, false)
# headings
var head = e.color_symbol
t.add_color_region("# *", "*", Color.yellowgreen, true)
t.add_color_region("# \"", "\"", Color.yellowgreen, true)
t.add_color_region("# ", "", head, true)
t.add_color_region("## ", "", head, true)
t.add_color_region("### ", "", head, true)
t.add_color_region("#### ", "", head, true)
t.add_color_region("##### ", "", head, true)
t.add_color_region("###### ", "", head, true)
# url links
t.add_color_region("[", ")", Color.purple)
# lists
t.add_color_region("- [x", "]", Color.yellowgreen, false)
t.add_color_region("- [", " ]", Color.white.darkened(.6), false)
# code blocks
t.add_color_region("```", "```", code, false)
t.add_color_region("~~~", "~~~", code, false)
# strikeout
t.add_color_region("~~", "~~", Color.tomato, false)
# code
t.add_color_region("`", "`", code, false)
# at/mention
t.add_color_region("@", " ", Color.yellowgreen, false)
t.add_color_region(": ", "", Color.white.lightened(.4), true)
# tables
t.add_color_region("|", "", Color.tan, true)
func get_symbols(t:String) -> Dictionary:
var out = .get_symbols(t)
var last = add_symbol()
var lines = t.split("\n")
var i = 0
while i < len(lines):
# symbols
if lines[i].begins_with("#"):
var p = lines[i].split(" ", true, 1)
var deep = len(p[0])-1
var name = p[1].strip_edges()
last = add_symbol(i, deep, name)
# tags
elif "<!-- #" in lines[i]:
for tag in lines[i].split("<!-- #", true, 1)[1].split("-->", true, 1)[0].split("#"):
tag = tag.strip_edges()
if tag:
last.tags.append(tag)
i += 1
return out

View File

@ -0,0 +1,91 @@
extends TE_ExtensionHelper
func _is_commented(lines) -> bool:
for i in len(lines):
if not lines[i].strip_edges():
continue
if not lines[i].strip_edges(true, false).begins_with("# "):
return false
return true
func toggle_comment(t:TextEdit, head:String="", tail:String=""):
if not t.is_selection_active():
var l = t.cursor_get_line()
var lt = t.get_line(l)
var s = len(lt) - len(lt.strip_edges(true, false))
t.select(l, s, l, len(t.get_line(l)))
var l1 = t.get_selection_from_line()
var c1 = t.get_selection_from_column()
var old = t.get_selection_text()
var new = old.split("\n")
if _is_commented(new):
for i in len(new):
if "# " in new[i]:
var p = new[i].split("# ", true, 1)
new[i] = p[0] + p[1]
else:
for i in len(new):
if not new[i].strip_edges():
continue
var space = TE_Util.get_whitespace_head(new[i])
new[i] = space + "# " + new[i].strip_edges(true, false)
new = new.join("\n")
t.insert_text_at_cursor(new)
var l = new.split("\n")
var l2 = l1 + len(l)-1
var c2 = c1 + len(l[-1])
t.select(l1, c1, l2, c2)
return [old, new]
func apply_colors(e:TextEditor, t:TextEdit):
.apply_colors(e, t)
# strings
t.add_color_region('"', '"', e.color_var)
# bools
t.add_keyword_color("true", e.color_var)
t.add_keyword_color("false", e.color_var)
# array element
t.add_color_region("- ", "", Color.webgray, true)
# comments
t.add_color_region("#", "", e.color_comment, true)
func get_symbols(t:String) -> Dictionary:
var out = .get_symbols(t)
var last = add_symbol()
var lines = t.split("\n")
var i = 0
while i < len(lines):
# find objects to use as symbols
if ":" in lines[i]:
var p = lines[i].split(":", true, 1)
var r = p[1].strip_edges()
if not r or r.begins_with("{") or r.begins_with("#"):
var name = p[0].strip_edges()
var deep = max(0, len(lines[i]) - len(lines[i].strip_edges(true, false)))
last = add_symbol(i, deep, name)
# find tags inside comments
if "# " in lines[i]:
var p = lines[i].split("# ", true, 1)
if p[0].count("\"") % 2 != 0:
pass
elif "#" in p[1]:
for tag in p[1].split("#", true, 1)[1].split("#"):
tag = tag.strip_edges()
if tag:
last.tags.append(tag)
i += 1
return out

View File

@ -0,0 +1,16 @@
extends Node
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,10 @@
[gd_resource type="DynamicFont" load_steps=4 format=2]
[ext_resource path="res://addons/text_editor/fonts/RobotoMono-Regular.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://addons/text_editor/fonts/unifont_upper-13.0.01.ttf" type="DynamicFontData" id=2]
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -0,0 +1,10 @@
[gd_resource type="DynamicFont" load_steps=4 format=2]
[ext_resource path="res://addons/text_editor/fonts/RobotoMono-Bold.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://addons/text_editor/fonts/unifont_upper-13.0.01.ttf" type="DynamicFontData" id=2]
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -0,0 +1,10 @@
[gd_resource type="DynamicFont" load_steps=4 format=2]
[ext_resource path="res://addons/text_editor/fonts/RobotoMono-BoldItalic.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://addons/text_editor/fonts/unifont_upper-13.0.01.ttf" type="DynamicFontData" id=2]
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -0,0 +1,10 @@
[gd_resource type="DynamicFont" load_steps=4 format=2]
[ext_resource path="res://addons/text_editor/fonts/RobotoMono-Italic.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://addons/text_editor/fonts/unifont_upper-13.0.01.ttf" type="DynamicFontData" id=2]
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -0,0 +1,10 @@
[gd_resource type="DynamicFont" load_steps=4 format=2]
[ext_resource path="res://addons/text_editor/fonts/RobotoMono-Regular.ttf" type="DynamicFontData" id=1]
[ext_resource path="res://addons/text_editor/fonts/unifont_upper-13.0.01.ttf" type="DynamicFontData" id=2]
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,32 @@
extends LineEdit
onready var editor:TextEditor = owner
var fr:FuncRef
func _ready():
var _e
_e = connect("text_entered", self, "_enter")
_e = connect("focus_exited", self, "_lost_focus")
add_font_override("font", TextEditor.FONT_R)
func _unhandled_key_input(e):
if e.scancode == KEY_ESCAPE and e.pressed:
fr = null
hide()
get_tree().set_input_as_handled()
func display(t:String, obj:Object, fname:String):
text = t
fr = funcref(obj, fname)
show()
call_deferred("grab_focus")
func _lost_focus():
print("lost focus")
fr = null
hide()
func _enter(t:String):
fr.call_func(t)
hide()

View File

@ -0,0 +1,267 @@
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

View File

@ -0,0 +1,49 @@
extends RichTextLabel
onready var editor:TextEditor = owner
func _ready():
var _e
_e = connect("meta_hover_started", self, "_hovered")
_e = connect("meta_clicked", self, "_clicked")
_e = editor.connect("symbols_updated", self, "_redraw")
_e = editor.connect("tags_updated", self, "_redraw")
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 _hovered(_id):
pass
func _clicked(id):
var p = id.split(":", true, 1)
var i = int(p[1])
match p[0]:
"l":
var te:TextEdit = editor.get_selected_tab()
te.cursor_set_line(te.get_line_count()) # force scroll to bottom so selected line will be at top
te.cursor_set_line(i)
func _redraw():
var tab = editor.get_selected_tab()
var symbols = {} if not tab else tab.symbols
# no symbols
if not symbols or len(symbols) == 1:
set_bbcode("[color=#%s][i][center]*No symbols*" % [Color.webgray.to_html()])
else:
var t = PoolStringArray()
for line_index in symbols:
if line_index == -1:
continue # special file chapter
var symbol_info = symbols[line_index]
var space = "" if not symbol_info.deep else " ".repeat(symbol_info.deep)
var tagged = editor.is_tagged_or_visible(symbol_info.tags)
var clr = Color.white.darkened(0.0 if tagged else 0.75).to_html()
t.append(space + "[color=#%s][url=l:%s]%s[/url][/color]" % [clr, line_index, symbol_info.name])
set_bbcode(t.join("\n"))

View File

@ -0,0 +1,67 @@
extends RichTextLabel
onready var editor:TextEditor = owner
var tag_indices:Array = [] # safer to use int in [url=] than str.
func _ready():
var _e
_e = connect("meta_hover_started", self, "_hovered")
_e = connect("meta_clicked", self, "_clicked")
_e = editor.connect("symbols_updated", self, "_redraw")
_e = editor.connect("tags_updated", self, "_redraw")
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 _hovered(_id):
pass
func _clicked(id):
var tag = tag_indices[int(id)]
editor.enable_tag(tag, not editor.is_tag_enabled(tag))
func _redraw():
var tab = editor.get_selected_tab()
var tags = editor.tag_counts
var tab_tags = {} if not tab else tab.tags
TE_Util.sort_value(tags)
if not tags:
set_bbcode("[color=#%s][i][center]*No tags*" % [Color.webgray.to_html()])
else:
var t:PoolStringArray = PoolStringArray()
var count_color1 = Color.tomato.to_html()
var count_color2 = Color.tomato.darkened(.75).to_html()
for tag in tags:
var count = editor.tag_counts[tag]
var enabled = editor.is_tag_enabled(tag)
var x
if count > 1:
x = "[color=#%s][i]%s[/i][/color]%s" % [count_color1 if enabled else count_color2, count, tag]
else:
x = tag
var color = Color.white
var dim = 0.75
if tag in tab_tags:
color = Color.greenyellow
x = "[b]%s[/b]" % x
dim = 0.6
if enabled:
x = x
else:
x = "[color=#%s]%s[/color]" % [color.darkened(dim).to_html(), x]
x = "[url=%s]%s[/url]" % [len(tag_indices), x]
t.append(x)
tag_indices.append(tag)
set_bbcode(t.join(" "))

View File

@ -0,0 +1,23 @@
extends TE_RichTextLabel
onready var editor:TextEditor = owner
func _ready():
var _e
_e = editor.connect("file_selected", self, "_file_selected")
_e = editor.connect("file_saved", self, "_file_saved")
func _unhandled_key_input(e):
if e.scancode == KEY_M and e.pressed:
visible = not visible
func _file_selected(_file_path:String):
yield(get_tree(), "idle_frame")
_redraw()
func _file_saved(_file_path:String):
_redraw()
func _redraw():
var tab = editor.get_selected_tab()
tab.helper.generate_meta(tab, self)

View File

@ -0,0 +1,30 @@
extends TabContainer
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 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())

View File

@ -0,0 +1,191 @@
extends TextEdit
onready var tabs:TabContainer = get_parent()
onready var editor:TextEditor = get_parent().owner
var helper:TE_ExtensionHelper
var temporary:bool = false setget set_temporary
var modified:bool = false
var file_path:String = ""
var symbols:Dictionary = {}
var tags:Dictionary = {}
var last_key:int
var last_shift:bool
var last_selected:bool
var last_selection:Array = [0, 0, 0, 0]
func _ready():
var _e
_e = editor.connect("save_files", self, "save_file")
_e = editor.connect("file_selected", self, "_file_selected")
_e = editor.connect("file_renamed", self, "_file_renamed")
_e = connect("text_changed", self, "text_changed")
add_font_override("font", editor.FONT)
get_menu().add_font_override("font", editor.FONT)
func _file_renamed(old_path:String, new_path:String):
if old_path == file_path:
file_path = new_path
update_name()
func _input(e):
if not visible:
return
if e is InputEventKey and e.pressed:
last_key = e.scancode
last_shift = e.shift
if is_selection_active():
last_selected = true
last_selection[0] = get_selection_from_line()
last_selection[1] = get_selection_from_column()
last_selection[2] = get_selection_to_line()
last_selection[3] = get_selection_to_column()
else:
last_selected = false
if e is InputEventKey and e.control and e.shift and e.pressed:
var f
var t
if is_selection_active():
f = get_selection_from_line()
t = get_selection_to_line()
else:
f = cursor_get_line()
t = cursor_get_line()
# move selected text up or down
if e.scancode == KEY_UP and f > 0:
var lines = []
for i in range(f-1, t+1): lines.append(get_line(i))
lines.push_back(lines.pop_front())
for i in len(lines): set_line(f-1+i, lines[i])
select(f-1, 0, t-1, len(get_line(t-1)))
cursor_set_line(cursor_get_line()-1, false)
if e.scancode == KEY_DOWN and t < get_line_count()-1:
var lines = []
for i in range(f, t+2): lines.append(get_line(i))
lines.push_front(lines.pop_back())
for i in len(lines): set_line(f+i, lines[i])
select(f+1, 0, t+1, len(get_line(t+1)))
cursor_set_line(cursor_get_line()+1, false)
func _unhandled_key_input(e):
if not visible:
return
# comment code
if e.scancode == KEY_SLASH and e.control and e.pressed:
helper.toggle_comment(self)
get_tree().set_input_as_handled()
func _file_selected(p:String):
if p and p == file_path:
grab_focus()
update_symbols()
func text_changed():
if last_selected:
match last_key:
KEY_APOSTROPHE:
undo()
select(last_selection[0], last_selection[1], last_selection[2], last_selection[3])
if last_shift:
insert_text_at_cursor("\"%s\"" % get_selection_text())
else:
insert_text_at_cursor("'%s'" % get_selection_text())
KEY_QUOTELEFT:
undo()
select(last_selection[0], last_selection[1], last_selection[2], last_selection[3])
insert_text_at_cursor("`%s`" % get_selection_text())
_:
print(last_key)
if not modified:
if temporary:
temporary = false
modified = true
update_name()
func set_temporary(t):
temporary = t
update_name()
func update_symbols():
symbols.clear()
tags.clear()
# symbol getter
symbols = helper.get_symbols(text)
# collect tags
for line_index in symbols:
var line_info = symbols[line_index]
for tag in line_info.tags:
if not tag in tags:
tags[tag] = 1
else:
tags[tag] += 1
var _e = TE_Util.sort(tags, true)
editor._file_symbols_updated(file_path)
func close():
if modified:
var _e
_e = editor.popup_unsaved.connect("confirmed", self, "_popup", ["close"], CONNECT_ONESHOT)
_e = editor.popup_unsaved.connect("custom_action", self, "_popup", [], CONNECT_ONESHOT)
editor.popup_unsaved.show()
else:
editor._close_file(file_path)
func _popup(msg):
match msg:
"close":
editor._close_file(file_path)
"save_and_close":
save_file()
editor._close_file(file_path)
func load_file(path:String):
file_path = path
var f:File = File.new()
var _err = f.open(path, File.READ)
text = f.get_as_text()
f.close()
update_name()
# update colors
clear_colors()
helper = TextEditor.get_extension_helper(file_path)
helper.apply_colors(editor, self)
print("helper ", helper)
func save_file():
if modified:
if not file_path.begins_with("res://"):
push_error("can't save to %s" % file_path)
return
modified = false
editor.save_file(file_path, text)
update_name()
update_symbols()
func update_name():
var n = file_path.get_file().split(".", true, 1)[0]
if temporary: n = "?" + n
if modified: n = "*" + n
tabs.set_tab_title(get_index(), n)
func needs_save() -> bool:
return modified or not File.new().file_exists(file_path)

7
default_env.tres Normal file
View File

@ -0,0 +1,7 @@
[gd_resource type="Environment" load_steps=2 format=2]
[sub_resource type="ProceduralSky" id=1]
[resource]
background_mode = 2
background_sky = SubResource( 1 )

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

35
icon.png.import Normal file
View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.png"
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

54
project.godot Normal file
View File

@ -0,0 +1,54 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=4
_global_script_classes=[ {
"base": "Resource",
"class": "TE_ExtensionHelper",
"language": "GDScript",
"path": "res://addons/text_editor/ext/TE_ExtensionHelper.gd"
}, {
"base": "RichTextLabel",
"class": "TE_RichTextLabel",
"language": "GDScript",
"path": "res://addons/text_editor/TE_RichTextLabel.gd"
}, {
"base": "Reference",
"class": "TE_Util",
"language": "GDScript",
"path": "res://addons/text_editor/TE_Util.gd"
}, {
"base": "Node",
"class": "TextEditor",
"language": "GDScript",
"path": "res://addons/text_editor/TextEditor.gd"
} ]
_global_script_class_icons={
"TE_ExtensionHelper": "",
"TE_RichTextLabel": "",
"TE_Util": "",
"TextEditor": ""
}
[application]
config/name="TextEdit"
run/main_scene="res://TextEditor.tscn"
config/icon="res://icon.png"
[physics]
common/enable_pause_aware_picking=true
[rendering]
quality/driver/driver_name="GLES2"
vram_compression/import_etc=true
vram_compression/import_etc2=false
environment/default_environment="res://default_env.tres"