mirror of
https://github.com/Relintai/Godot-TextEditor.git
synced 2025-02-04 19:15:54 +01:00
1.0
This commit is contained in:
commit
af2b69645f
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
255
TextEditor.tscn
Normal 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
|
||||
}
|
21
addons/text_editor/TE_RichTextLabel.gd
Normal file
21
addons/text_editor/TE_RichTextLabel.gd
Normal 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]
|
139
addons/text_editor/TE_Util.gd
Normal file
139
addons/text_editor/TE_Util.gd
Normal 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]
|
442
addons/text_editor/TextEditor.gd
Normal file
442
addons/text_editor/TextEditor.gd
Normal 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()
|
67
addons/text_editor/ext/TE_ExtensionHelper.gd
Normal file
67
addons/text_editor/ext/TE_ExtensionHelper.gd
Normal 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)
|
2
addons/text_editor/ext/ext_csv.gd
Normal file
2
addons/text_editor/ext/ext_csv.gd
Normal file
@ -0,0 +1,2 @@
|
||||
extends TE_ExtensionHelper
|
||||
|
34
addons/text_editor/ext/ext_ini.gd
Normal file
34
addons/text_editor/ext/ext_ini.gd
Normal 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
|
41
addons/text_editor/ext/ext_json.gd
Normal file
41
addons/text_editor/ext/ext_json.gd
Normal 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)
|
84
addons/text_editor/ext/ext_md.gd
Normal file
84
addons/text_editor/ext/ext_md.gd
Normal 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
|
91
addons/text_editor/ext/ext_yaml.gd
Normal file
91
addons/text_editor/ext/ext_yaml.gd
Normal 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
|
16
addons/text_editor/file_buttons.gd
Normal file
16
addons/text_editor/file_buttons.gd
Normal 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
|
BIN
addons/text_editor/fonts/RobotoMono-Bold.ttf
Normal file
BIN
addons/text_editor/fonts/RobotoMono-Bold.ttf
Normal file
Binary file not shown.
BIN
addons/text_editor/fonts/RobotoMono-BoldItalic.ttf
Normal file
BIN
addons/text_editor/fonts/RobotoMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
addons/text_editor/fonts/RobotoMono-Italic.ttf
Normal file
BIN
addons/text_editor/fonts/RobotoMono-Italic.ttf
Normal file
Binary file not shown.
BIN
addons/text_editor/fonts/RobotoMono-Regular.ttf
Normal file
BIN
addons/text_editor/fonts/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
10
addons/text_editor/fonts/font.tres
Normal file
10
addons/text_editor/fonts/font.tres
Normal 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 )
|
10
addons/text_editor/fonts/font_b.tres
Normal file
10
addons/text_editor/fonts/font_b.tres
Normal 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 )
|
10
addons/text_editor/fonts/font_bi.tres
Normal file
10
addons/text_editor/fonts/font_bi.tres
Normal 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 )
|
10
addons/text_editor/fonts/font_i.tres
Normal file
10
addons/text_editor/fonts/font_i.tres
Normal 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 )
|
10
addons/text_editor/fonts/font_r.tres
Normal file
10
addons/text_editor/fonts/font_r.tres
Normal 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 )
|
BIN
addons/text_editor/fonts/unifont-13.0.01.ttf
Normal file
BIN
addons/text_editor/fonts/unifont-13.0.01.ttf
Normal file
Binary file not shown.
BIN
addons/text_editor/fonts/unifont_upper-13.0.01.ttf
Normal file
BIN
addons/text_editor/fonts/unifont_upper-13.0.01.ttf
Normal file
Binary file not shown.
32
addons/text_editor/line_edit.gd
Normal file
32
addons/text_editor/line_edit.gd
Normal 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()
|
267
addons/text_editor/list_files.gd
Normal file
267
addons/text_editor/list_files.gd
Normal 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
|
49
addons/text_editor/list_symbols.gd
Normal file
49
addons/text_editor/list_symbols.gd
Normal 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"))
|
67
addons/text_editor/list_tags.gd
Normal file
67
addons/text_editor/list_tags.gd
Normal 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(" "))
|
23
addons/text_editor/meta_panel.gd
Normal file
23
addons/text_editor/meta_panel.gd
Normal 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)
|
30
addons/text_editor/tab_scroll.gd
Normal file
30
addons/text_editor/tab_scroll.gd
Normal 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())
|
||||
|
191
addons/text_editor/text_edit.gd
Normal file
191
addons/text_editor/text_edit.gd
Normal 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
7
default_env.tres
Normal 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 )
|
35
icon.png.import
Normal file
35
icon.png.import
Normal 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
54
project.godot
Normal 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"
|
Loading…
Reference in New Issue
Block a user