Compare commits

...

24 Commits
v1.1 ... main

Author SHA1 Message Date
teebarjunk
25d4f161cf 1.12 2022-02-03 07:34:34 -05:00
teebarjunk
4225903a92 1.11 2021-12-19 10:43:24 -05:00
teebarjunk
dfe30486d2 1.10 2021-11-27 05:35:18 -05:00
teebarjunk
40beef4690 1.9 2021-11-24 11:09:48 -05:00
teebarjunk
498fb79e89 1.8 2021-11-20 08:53:02 -05:00
teebarjunk
baeeb0a808 1.7 2021-10-24 11:28:40 -04:00
teebarjunk
06e8d176c4 1.7 2021-10-24 11:27:27 -04:00
teebarjunk
4f09def7ef 1.6 2021-10-23 00:00:42 -04:00
teebarjunk
96c064c2b9 1.6 2021-10-22 23:51:34 -04:00
teebarjunk
c8ce103df4 1.5 2021-10-18 19:52:40 -04:00
teebarjunk
5bcd5a47b0 1.5 2021-10-18 19:50:21 -04:00
teebarjunk
f2ae9db115 1.4.1 2021-10-18 15:29:57 -04:00
teebarjunk
19cb94742d 1.4 2021-10-18 15:21:53 -04:00
teebarjunk
8fd5a31874 1.4 2021-10-18 15:20:19 -04:00
teebarjunk
ba5e9148cb 1.2 2021-10-14 14:52:00 -04:00
teebarjunk
2f3dfacf47 1.2 2021-10-14 14:49:03 -04:00
teebarjunk
326ddf3299 1.2 2021-10-14 06:47:23 -04:00
teebarjunk
4747dd2887 1.2 2021-10-14 06:45:59 -04:00
teebarjunk
726e266c63 removed stray prints 2021-10-13 14:03:19 -04:00
teebarjunk
fad24f7a82 checks fix 2021-10-13 13:57:41 -04:00
teebarjunk
3d51620408 checks fix 2021-10-13 13:55:07 -04:00
teebarjunk
dcdab0b9c1 gdignore fix 2021-10-13 13:52:58 -04:00
teebarjunk
01facc81ad 1.1 2021-10-13 13:47:08 -04:00
teebarjunk
948f3db8d4 1.1 2021-10-13 13:06:48 -04:00
50 changed files with 3871 additions and 1134 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ test_files/
.trash/
.trash.json
.text_editor_state.json
word_skip_list.txt

View File

@ -1,6 +1,149 @@
# 1.12
- Changed icon.
- Added tabs popup menu:
- Options for closing many tabs.
- Options for selecting tabs.
- Console is hidden on start.
- Fixed `yaml` tabs not working.
- Fixed `tab` + `shift + tab`ing when multiple lines are selected.
# 1.11
- Toggled `Low Processor Mode` to keep cpu/gpu usage down.
- Simplified *File List* filter display.
- *File List* filter now scans closed folders as well.
- Folder icon in *File List* changes when open/closed.
- *File Editor* now saves states of:
- Cursor position.
- Selection.
- Scroll position.
- Enabled hiding in *File Editor*.
- *Tag List* tags are sorted.
- Added `.rpy` *Renpy* file support.
- Added tab/spaces toggle.
- Fixed files with `.` in their name not showing up.
- Fixed error caused by clicking *File List* symbol for unopened file.
# 1.10
- Added cursor panel at bottom of Text Editor.
- Word counter.
- Symbol path.
- Added `insert` menu, for inserting Date.
- `ctrl + shift + u` and `ctrl + shift + l` will toggle uppercase and lowercase.
- `ctrl + shift + o` and `ctrl + shift + p` will toggle capitalize and variablize.
- Fixed `ctrl + f` not bringing up search pannel.
- Fixed error when creating new file.
- Removed Text Editor hints.
- Color tweaks.
# 1.9
- Tag Viewer now shows all tags regardless of whether the file is open or not.
- File View can show symbols. Toggle with `ctrl` click.
- File View filter will scan symbols as well.
- File List dims characters `0123456789-_`.
- Image Preview on `ctrl + click` in Markdown: `![](icon.png)` will display `"res://icon.png"`
- Tab title ignores leading numbers: "001_character" = "character"
- Directories are highlighted if they have a file with a selected tag.
- Holding `ctrl` while selecting a tag allows multiple to be selected.
- Added tab icon based on folder color.
- Fixed Markdown symbol generator including `#` inside code.
- Fixed meta container resizing.
# 1.8
- Added filter to symbols list.
- Added filter to file list.
- Added `.md` highlighting for `{}`. (Not official Markdown.)
- Fixed unsaved files asking for a save path if no text entered.
- Fixed file wiping if hitting undo after loading a file.
- Fixed *no word_skip_list.txt* error.
- Folders colorized in file list.
- Display version at top right.
# 1.7
- Added option to view `Extensionless` files.
- Added Symbol path heirarchy to hint popup so you know where you are in big files:
![](README/changes_hint_toc.png)
- `ctrl + shift +`
- `U` Make selection uppercase.
- `L` Make selection lowercase.
- `O` Make selection capitalized.
- `P` Make selection variable: `My text -> my_text`
- Select file shorctut:
- `ctrl + shift + 0-9` Remember file.
- `ctrl + 0-9` Swap to file.
- Selected Symbol is now highlighted.
- Improved meta data for `.md` files.
- `search` will autoselect term when clicked.
- `search` `all` toggle added to allow only searching in the selected file.
- `search` `case` toggle added to allow searching based on upper/lower case.
- `sys` panel shows unique word list.
- `sys` panel shows time since modified.
- Can create a `word_skip_list.txt` in main folder for ignoring certain words from showing in `sys` word list.
- File List panel hint paths are localized.
- Removed `.md` function color.
- Fixed `trash` not working in exported binaries.
- Fixed dragging files into directory bug.
- Fixed temporary files not closing properly.
- Fixed close non existing tab bug.
- Fixed symbol list not redrawing after file closed.
- Fixed symbol list not redrawing after file type changed.
- Fixed focus not being grabbed when tab selected.
# 1.6
- Added `Uppercase` `Lowercase` and `Capitalize` option to popup menu for selected text.
- `ctrl + click` in Symbol View selects entire "chapter" and sub "chapters". `ctrl + shift + click` selects only one "chapter".
- `ctrl + click` in editor will auto scroll symbol view.
- Folders can be tinted.
- `word_wrap` state is saved/loaded.
- Fixed error that occured when folder containing binary was moved.
- Markdown can have a `progress` field in meta data which can be sorted in `sys`.
- Markdown meta info taken into account for `sys`
- Markdown meta info colourized.
- Markdown code color based on variable color.
- JSON comments like YAML: `"#": "comment"`
- JSON color tweaks.
# 1.5
- Added `Ctrl + N` to immediately create new file without defining path.
- New unsaved file will have contents remembered.
- Added `Ctrl + Shift + Mouse wheel` to change ui font size.
- Added word wrap toggle.
- Fixed sorting error in `sys`.
- Fixed font size save/load.
- `sys` shows chapter count.
- Preserves symbol view scroll value when tabbing.
- Can access full filesystem.
- Fixed "New File" dialog not gaining focus.
# 1.4
- Added `sys` info tab.
- Added `console` info tab. (wip)
- Changing extension updates colors.
- Fixed exported build not styling things properly.
- Fixed symbols/tags not showing when first booting editor.
- Tweaked colors.
- Internal rewriting.
# 1.3
- Basic search implemented. `Ctrl + F`
- Can create links inside `()` which makes markdown links clickable.: `Ctrl + Click`
# 1.2
- Can unrecycle now. (Make sure `view/Directories/.trash` is toggled, then press arrow.
- Added folder recycle option
- Added folder move/drag.
- Empty directories properly hide if they have no subdirectories.
- Fixed hide/show file type not updating list.
- Settings are saved more frequently.
- Fixed file dragging.
- Fixed meta table not resizing.
- Tweaked symbol colorizer to emphasize depth.
- Bug fixes.
# 1.1
- Fixed files and directories not being sorted.
- Added `addons` folder hider option.
- Preserve folder open/close state.
- Fixed directories with `.gdignore` not hiding.
- Fixed files and directories not being sorted.
- Fixed "failed to load settings" error.
- Tweaked syntax coloring.
- Got rid of accidental test file.
- Got rid of accidental test file.

154
README.md
View File

@ -1,48 +1,62 @@
# Text Editor
Version 1.1
Version `1.12`
![](README/readme_preview.png)
***Warning: Use at your own risk. Backup your files before testing.***
# Features
- Tabs with scroll
- File filtering
- Highlighting for common file formats (`md` `json`...)
- Tag filtering system
- Multi file tab system.
- File browser filtering.
- Highlighting for common formats (`md` `json` `ini`...)
- Tag [System](#mini-features-tags).
- File Management:
- Creation
- Renaming
- Recycling
- Auto save/load settings
- Many little *Ease of life* functions:
- Folder open/close
- Comment toggling for:
- `.md`: `<!-- -->`
- `.json`: `/* */`
- `.ini`: `; `
- `.cfg`: `; `
- `.yaml`: `# `
- Creation.
- Renaming.
- Recycling.
- Moving.
- Search files.
- Image previews.
- Auto save/load settings.
- Many little *Ease of Life* [features](#mini-features).
# Controls
- `ctrl + W` Close file
- `ctrl + shift + W` Open last closed file
- `ctrl + tab` Select next open file
- `ctrl + shift + tab` Select last open file
- `ctrl + mouse wheel` Adjust font size
- `ctrl + up` `ctrl + down` Move selected lines
- `ctrl + /` Toggle line comments
- `ctrl + M` Toggle file meta info
- `ctrl + N` New file.
- `ctrl + W` Close file.
- `ctrl + shift + W` Open last closed file .
- `ctrl + tab` Select next open file.
- `ctrl + shift + tab` Select last open file.
- `ctrl + mouse wheel` Adjust font size.
- `ctrl + shift + mouse wheel` Adjust ui font size.
- `ctrl + up` & `ctrl + down` Move selected lines.
- `ctrl + /` Toggle line comments.
- `ctrl + M` Toggle file meta info.
- `ctrl + F` Search for text in all files.
- `ctrl + shift + 0-9` Create hotkey for selected file.
- `ctrl + 0-9` Load hotkeyed file.
## Symbol View
- `ctrl + click` Select entire block + children.
- `ctrl + shift + click` Select block without children.
## Editor View
- `ctrl + click` anywhere: Scroll to nearest symbol in symbol view.
- `ctrl + click` inside brackets: Goto local file.
- `ctrl + shift +`
- `U` Make selection uppercase.
- `L` Make selection lowercase.
- `O` Make selection capitalized.
- `P` Make selection variable: `My text -> my_text`
# Symbols and Tags
To make it easier to find stuff there is a *Symbol* viewer.
*Symbols* are like *Table of Contents* for a file.
- `Markdown` uses headings `# Heading`
- `JSON` uses Dictionaries `"object": {`
- `YAML` uses Dictionaries `object: `
- `ini` `cfg` use headings `[heading]`
Symbols can have tags. Tags are added with comments.
Symbols can have *Tags*. Tags are added with comments.
- `Markdown` uses `<!-- #tag1 #tag2 -->`
- `JSON` uses `"#": "#tag1 #tag2"`
@ -57,9 +71,83 @@ Click them to toggle on and off.\
This will then highlight *Files* and *Symbols* that have that tag.
# Todo
- [ ] Preserve which folders are open/closed.
- [ ] Search
- [ ] Find and replace
- [ ] Meta data based on format.
- [ ] Recycle folder
- [ ] Undo recycle
- [x] `1.1` Preserve folders open/close state.
- [x] `1.3` Search all files.
- [x] `1.7` Search file.
- [ ] Find and replace.
- [x] `1.7` Improve meta data based on format.
- [x] `1.2` Recycle folders.
- [x] `1.2` Unrecylce. (Toggle `view/directories/.trash` and press green arrow.)
- [ ] JSON formatting.
- [ ] JSON error testing.
- [ ] Color themes.
# Mini features
## File List
### Colorize Folder
You can colorize files in a folder for easier identification. Right click a folder and select a color.
The `tab colors` toggle at the top will toggle tabs colorized by folder.
### Content Preview
You can preview the contents of a file by `ctrl + click`ing it.
The list is clickable, so you can go straight to a section of the file.
When using the filter, contents will be scanned.
## Content List (Symbols)
### Selecting Sections
`ctrl + click`ing on a symbol will select all lines contained in it, and it's childrens.
`ctrl + shift + click` a symbol will only select it's lines, not it's childrens.
## File Editor
### Follow Link
You can follow Markdown links by `ctrl + click`ing on them.
## Tags
The tag list displays all tags throughout the files.
To add a tag to a file, include a comment, with a hashtag:
- `.md`: `<!-- #tag1 #tag2 -->`
- `.json`: `{ "#": "#tag1 #tag2 }`
- `.ini` `.cfg`: `; #tag1 #tag2`
- `.yaml`: `# #tag1 #tag2`
`click` a tag to select it.
All files in the File List and symbols in the Symbol List containing the tag, will be highlighted.
`ctrl + click` to select multiple tags at once.
## Meta Panel
Toggle the meta panel with `ctrl + M`.
### Meta
The meta tab updates whenever you make a save.
It lists some information on the contents of your file.
Currently it mostly only works for Markdown.
### Search
todo
### System
Hitting refrsh will list all files in a table with sortable columns.
Select a column to sort on:
- Chapter count.
- Word count.
- Unique words.
- Progress.
- Time since modified.
### Image
In Markdown files (`.md`) you can `ctrl + click` an image to preview it.
Images look like: `![](image_url.png)` in Markdown.
# Icon credit
<a href="https://www.flaticon.com/free-icons/files-and-folders" title="files and folders icons">Files and folders icons created by Uniconlabs - Flaticon</a>

BIN
README/changes_hint_toc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
README/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -1 +0,0 @@

View File

@ -0,0 +1,17 @@
tool
extends "res://addons/text_editor/TE_RichTextLabel.gd"
func _ready():
clear()
func msg(msg):
append_bbcode(str(msg))
newline()
func err(err):
append_bbcode(clr(err, Color.tomato))
newline()
func info(info):
append_bbcode(clr(info, Color.aquamarine))
newline()

View File

@ -1,16 +1,27 @@
tool
extends RichTextLabel
var editor:TextEditor
var editor:TE_Editor
var click_pos:Vector2
func _init(text):
set_bbcode(text)
visible = false
func _ready():
add_font_override("normal_font", editor.FONT_R)
add_font_override("bold_font", editor.FONT_B)
add_font_override("italics_font", editor.FONT_I)
add_font_override("bold_italics_font", editor.FONT_BI)
click_pos = get_global_mouse_position()
# 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)
rect_size = editor.FONT_R.get_string_size(text)
rect_size += Vector2(16, 16)
func _process(_delta):
set_global_position(get_global_mouse_position())
var mp = get_global_mouse_position()
set_visible(mp.distance_to(click_pos) > 16.0)
set_global_position(mp + Vector2(16, 8))
func _input(e):
if e is InputEventMouseButton:

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
tool
extends TextEdit
var editor:TextEditor
var editor:TE_Editor
var _hscroll:HScrollBar
var _vscroll:VScrollBar
@ -9,6 +9,7 @@ var helper:TE_ExtensionHelper
var temporary:bool = false setget set_temporary
var modified:bool = false
var file_path:String = ""
var mouse_inside:bool = false
var symbols:Dictionary = {}
var tags:Dictionary = {}
@ -17,30 +18,130 @@ var last_shift:bool
var last_selected:bool
var last_selection:Array = [0, 0, 0, 0]
var color:String = ""
var hscroll:int = 0
var vscroll:int = 0
var in_focus:bool = false
func _ready():
# prefab?
if name == "file_editor":
set_process(false)
set_process_input(false)
return
var _e
if not editor:
editor = owner
_e = editor.connect("save_files", self, "save_file")
_e = editor.connect("file_selected", self, "_file_selected")
_e = editor.connect("file_renamed", self, "_file_renamed")
_e = editor.connect("dir_tint_changed", self, "_dir_tint_changed")
_e = connect("text_changed", self, "text_changed")
_e = connect("focus_entered", self, "set", ["in_focus", true])
_e = connect("focus_exited", self, "set", ["in_focus", false])
_e = connect("focus_entered", self, "_focus_entered")
_e = connect("focus_exited", self, "_focus_exited")
_e = connect("mouse_entered", self, "set", ["mouse_inside", true])
_e = connect("mouse_exited", self, "set", ["mouse_inside", false])
# _e = connect("cursor_changed", self, "_cursor_changed")
if get_parent() is TabContainer:
get_parent().connect("tab_changed", self, "_tab_changed")
get_parent().connect("tab_selected", self, "_tab_changed")
# if get_parent() is TabContainer:
# get_parent().connect("tab_changed", self, "_tab_changed")
# get_parent().connect("tab_selected", self, "_tab_changed")
add_font_override("font", editor.FONT)
get_menu().add_font_override("font", editor.FONT)
var popup = get_menu()
popup.add_font_override("font", editor.FONT)
popup.add_separator()
popup.add_item("Uppercase", 1000)
# var sc = ShortCut.new()
# sc.shortcut = InputEventKey.new()
# sc.shortcut.shift = true
# sc.shortcut.control = true
# sc.shortcut.scancode = KEY_U
# popup.add_item_shortcut(sc, 1000)
popup.add_item("Lowercase")
popup.add_item("Capitalize")
popup.add_item("Variable")
# popup.add_shortcut()
_e = popup.connect("index_pressed", self, "_popup_menu")
# hint
theme = Theme.new()
theme.set_font("font", "TooltipLabel", editor.FONT_R)
# var sb = StyleBoxFlat.new()
# sb.bg_color.a = .75
# theme.set_color("font_color", "TooltipLabel", Color(0,0,0,.5))
# theme.set_stylebox("panel", "TooltipPanel", sb)
# print(theme.get_color_list("TooltipLabel"))
# print(theme.get_color_types())
TE_Util.dig(self, self, "_node")
func set_visible(v):
prints(file_path, v)
.set_visible(v)
func _focus_entered():
if not in_focus:
in_focus = true
func _focus_exited():
if in_focus:
in_focus = false
func set_cursor_state(s:Array):
cursor_set_line(s[0], s[1])
if len(s) > 2:
select(s[2], s[3], s[4], s[5])
func get_cursor_state() -> Array:
if is_selection_active():
return [
cursor_get_line(),
cursor_get_column(),
get_selection_from_line(),
get_selection_from_column(),
get_selection_to_line(),
get_selection_to_column()]
else:
return [
cursor_get_line(),
cursor_get_column()]
func _dir_tint_changed(dir:String):
if file_path.get_base_dir() == dir:
update_name()
func _popup_menu(index:int):
match get_menu().get_item_text(index):
"Uppercase": selection_uppercase()
"Lowercase": selection_lowercase()
"Capitalize": selection_capitalize()
"Variable": selection_variable()
func selection_uppercase():
var s = get_cursor_state()
insert_text_at_cursor(get_selection_text().to_upper())
set_cursor_state(s)
func selection_lowercase():
var s = get_cursor_state()
insert_text_at_cursor(get_selection_text().to_lower())
set_cursor_state(s)
func selection_variable():
var s = get_cursor_state()
insert_text_at_cursor(TE_Util.to_var(get_selection_text()))
set_cursor_state(s)
func selection_capitalize():
var s = get_cursor_state()
insert_text_at_cursor(get_selection_text().capitalize())
set_cursor_state(s)
func _node(n):
var _e
if n is HScrollBar:
@ -56,61 +157,108 @@ func _scroll_h(h:HScrollBar):
func _scroll_v(v:VScrollBar):
vscroll = v.value
func _tab_changed(index:int):
var myindex = get_index()
if index == myindex and visible:
grab_focus()
grab_click_focus()
yield(get_tree(), "idle_frame")
set_h_scroll(hscroll)
set_v_scroll(vscroll)
func get_state() -> Dictionary:
return {
var state = {
hscroll=scroll_horizontal,
vscroll=scroll_vertical
vscroll=scroll_vertical,
cursor=get_cursor_state()
}
# unsaved
if file_path == "":
state.text = text
return state
func set_state(state:Dictionary):
yield(get_tree(), "idle_frame")
hscroll = state.hscroll
vscroll = state.vscroll
set_h_scroll(state.hscroll)
set_v_scroll(state.vscroll)
if "hscroll" in state:
hscroll = state.hscroll
vscroll = state.vscroll
set_h_scroll(state.hscroll)
set_v_scroll(state.vscroll)
if "text" in state:
if state.text.strip_edges():
text = state.text
else:
editor._close_file(file_path)
if "cursor" in state:
set_cursor_state(state.cursor)
func _file_renamed(old_path:String, new_path:String):
if old_path == file_path:
file_path = new_path
update_name()
func _update_selected_line():
var l = cursor_get_line()
editor.select_symbol_line(0)
for i in len(symbols):
var sindex = clamp(i, 0, len(symbols))
var symbol = symbols.values()[sindex]
if i == len(symbols)-1 or symbols.keys()[i+1] > l:
editor.select_symbol_line(sindex)
break
func get_line_symbols(line:int) -> PoolStringArray:
var depth:PoolStringArray = PoolStringArray()
for i in len(symbols):
var sindex = clamp(i, 0, len(symbols))
var symbol = symbols.values()[sindex]
while len(depth) <= symbol.deep:
depth.append("")
depth[symbol.deep] = symbol.name
if i == len(symbols)-1 or symbols.keys()[i+1] > line:
depth.resize(symbol.deep+1)
break
return depth
func _input(e):
if not editor.is_plugin_active():
return
if not visible or not in_focus:
# custom tab system
if visible and in_focus and e is InputEventKey and e.pressed and e.scancode == KEY_TAB:
if is_selection_active():
var a = get_selection_from_line()
var b = get_selection_to_line()
var lines = get_selection_text().split("\n")
if e.shift:
for i in len(lines):
lines[i] = lines[i].trim_prefix(helper.get_tab())
else:
for i in len(lines):
lines[i] = helper.get_tab() + lines[i]
insert_text_at_cursor(lines.join("\n"))
select(a, 0, b, len(get_line(b)))
else:
insert_text_at_cursor(helper.get_tab())
get_tree().set_input_as_handled()
if not visible or not in_focus or not mouse_inside:
return
if e is InputEventKey and e.pressed and e.control:
# tab to next
if e.scancode == KEY_TAB:
get_tree().set_input_as_handled()
if e.shift:
get_parent().prev()
else:
get_parent().next()
# show current position in heirarchy as editor hint
if e is InputEventMouseButton and not e.pressed:
_update_selected_line()
if e is InputEventMouseButton and not e.pressed and e.control:
var line:String = get_line(cursor_get_line())
# save files
elif e.scancode == KEY_S:
get_tree().set_input_as_handled()
editor.save_files()
# close file
elif e.scancode == KEY_W:
get_tree().set_input_as_handled()
if e.shift:
editor.open_last_file()
else:
close()
# click link
var ca = line.find("(")
var cb = line.find_last(")")
if ca != -1 and cb != -1:
var a:int = cursor_get_column()
var b:int = cursor_get_column()
if ca < a and cb >= b:
while a > 0 and not line[a] in "(": a -= 1
while b <= len(line) and not line[b] in ")": b += 1
var file = line.substr(a+1, b-a-1)
var link = file_path.get_base_dir().plus_file(file)
editor.open_file(link)
editor.select_file(link)
# remember last selection
if e is InputEventKey and e.pressed:
@ -152,6 +300,30 @@ func _input(e):
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)
if e.scancode == KEY_U:
if get_selection_text() == get_selection_text().to_upper():
selection_lowercase()
else:
selection_uppercase()
if e.scancode == KEY_L:
if get_selection_text() == get_selection_text().to_lower():
selection_uppercase()
else:
selection_lowercase()
if e.scancode == KEY_O:
if get_selection_text() == get_selection_text().capitalize():
selection_variable()
else:
selection_capitalize()
if e.scancode == KEY_P:
if get_selection_text() == TE_Util.to_var(get_selection_text()):
selection_capitalize()
else:
selection_variable()
func _unhandled_key_input(e):
if not visible:
@ -162,15 +334,35 @@ func _unhandled_key_input(e):
helper.toggle_comment(self)
get_tree().set_input_as_handled()
func _file_renamed(old_path:String, new_path:String):
if old_path == file_path:
file_path = new_path
update_name()
update_colors()
func _file_selected(p:String):
if not p:
if p != file_path:
return
if p == file_path:
grab_focus()
grab_click_focus()
update_symbols()
update_heading()
update_symbols()
update_heading()
# yield(get_tree(), "idle_frame")
#
# grab_focus()
# grab_click_focus()
func goto_symbol(index:int):
var syms = symbols.keys()
if syms and index >= 0 and index < len(syms):
goto_line(syms[index])
func goto_line(line:int, bottom:bool=true):
# force scroll to bottom so selected line will be at top
if bottom:
cursor_set_line(get_line_count())
cursor_set_line(line)
_update_selected_line()
func text_changed():
if last_selected:
@ -189,7 +381,7 @@ func text_changed():
insert_text_at_cursor("`%s`" % get_selection_text())
_:
print(last_key)
pass
if not modified:
if temporary:
@ -202,6 +394,8 @@ func set_temporary(t):
update_name()
func update_symbols():
update_helper()
symbols.clear()
tags.clear()
@ -217,55 +411,98 @@ func update_symbols():
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()
if not editor.popup_unsaved.visible:
var _e
_e = editor.popup_unsaved.connect("confirmed", self, "_popup", ["confirm_close"], CONNECT_ONESHOT)
_e = editor.popup_unsaved.connect("custom_action", self, "_popup", [], CONNECT_ONESHOT)
# _e = editor.popup_unsaved.connect("hide", self, "_popup", ["cancel"], CONNECT_ONESHOT)
editor.popup_unsaved.show()
else:
editor._close_file(file_path)
func _popup(msg):
match msg:
"close":
"confirm_close":
editor._close_file(file_path)
"save_and_close":
save_file()
editor._close_file(file_path)
editor.popup_unsaved.disconnect("confirmed", self, "_popup")
editor.popup_unsaved.disconnect("custom_action", self, "_popup")
func load_file(path:String):
file_path = path
text = TE_Util.load_text(path)
if path != "":
text = editor.load_file(path)
clear_undo_history()
update_colors()
update_name()
# update colors
clear_colors()
helper = TextEditor.get_extension_helper(file_path)
func update_helper():
helper = editor.get_extension_helper(file_path)
helper.apply_colors(editor, self)
func update_colors():
clear_colors()
update_helper()
func _created_nonexisting(fp:String):
file_path = fp
modified = false
update_name()
update_symbols()
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()
if file_path == "" and text:
editor.popup_create_file(editor.current_directory, text, funcref(self, "_created_nonexisting"))
else:
if modified:
if not file_path.begins_with(editor.current_directory):
var err_msg = "can't save to %s" % file_path
push_error(err_msg)
editor.console.err(err_msg)
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
var n:String
if file_path == "":
n = "UNSAVED"
else:
n = file_path.get_file().split(".", true, 1)[0]
if editor.ignore_head_numbers:
n = editor._split_header(n)[1]
if temporary: n = "?" + n
if modified: n = "*" + n
if len(n) > 12:
n = n.substr(0, 9) + "..."
if editor.show_tab_color:
var color = editor.get_file_tint_name(file_path)
var path = "res://addons/text_editor/icons/icon_%s.png" % color
if File.new().file_exists(path):
editor.tab_parent.set_tab_icon(get_index(), load(path))
else:
editor.tab_parent.set_tab_icon(get_index(), null)
else:
editor.tab_parent.set_tab_icon(get_index(), null)
editor.tab_parent.set_tab_title(get_index(), n)
update_heading()
name = n
func update_heading():
if Engine.editor_hint:
@ -282,5 +519,5 @@ func update_heading():
OS.set_window_title("%s - Text Editor" % f)
func needs_save() -> bool:
return modified or not File.new().file_exists(file_path)
return modified or (not File.new().file_exists(file_path) and text)

View File

@ -0,0 +1,101 @@
tool
extends Node
const DIM:Color = Color.webgray
const CLR:Color = Color.white
onready var editor:TE_Editor = owner
var tab:TextEdit = null
var typed:String = ""
var word_count:int = 0
func _ready():
var _e
yield(get_tree(), "idle_frame")
_e = editor.tab_parent.connect("tab_changed", self, "_tab_changed")
editor.override_fonts($l)
editor.override_fonts($m)
editor.override_fonts($r)
func _tab_changed(index:int):
if tab and is_instance_valid(tab) and not tab.is_queued_for_deletion():
tab.disconnect("cursor_changed", self, "_cursor_changed")
tab = null
var new_tab = editor.tab_parent.get_child(index)
if tab == new_tab:
return
tab = new_tab
var _e
_e = tab.connect("cursor_changed", self, "_cursor_changed")
# _e = tab.connect("text_changed", self, "_text_changed")
#
#func _text_changed():
## print("text changed")
# pass
func _input(event):
if event is InputEventKey and event.pressed and tab and is_instance_valid(tab) and tab.has_focus():
if not event.scancode == KEY_BACKSPACE:
if char(event.scancode) in " .?!-":
word_count += 1
typed = ""
else:
typed += char(event.scancode)
_cursor_changed()
func _cursor_changed():
var l_lines:PoolStringArray = PoolStringArray()
var m_lines:PoolStringArray = PoolStringArray()
var r_lines:PoolStringArray = PoolStringArray()
var l:RichTextLabel
if tab.is_selection_active():
var seltext:String = tab.get_selection_text()
var words = {}
var word_count:int = TE_Util.count_words(seltext, words, null, false)
m_lines.append(kv("chars", len(seltext)))
m_lines.append(kv("words", word_count))
var l1 = tab.get_selection_from_line() + 1
var l2 = tab.get_selection_to_line() + 1
var c1 = tab.get_selection_from_column()
var c2 = tab.get_selection_to_column()
if l1 == l2:
l_lines.append(kv("line", l1))
l_lines.append(kv("char", "%s - %s" % [c1, c2]))
else:
l_lines.append(clr("line: ", DIM) + clr(str(l1), CLR) + clr(":", DIM) + clr(str(c1), CLR))
l_lines.append(clr("->", Color.webgray))
l_lines.append(clr("line: ", DIM) + clr(str(l2), CLR) + clr(":", DIM) + clr(str(c2), CLR))
m_lines.append(kv("lines", abs(l2 - l1) + 1))
else:
l_lines.append(kv("line", tab.cursor_get_line() + 1))
l_lines.append(kv("char", tab.cursor_get_column()))
var depth = tab.get_line_symbols(tab.cursor_get_line())
for i in len(depth):
depth[i] = b(depth[i])
r_lines.append(depth.join(clr("/", DIM)))
m_lines.append(kv("typed", word_count))
$l.set_bbcode(l_lines.join(" "))
$m.set_bbcode("[center]" + m_lines.join(" "))
$r.set_bbcode("[right]" +r_lines.join(" "))
func kv(k:String, v) -> String:
var clr2 = Color.white
if v is int:
v = TE_Util.commas(v)
return clr(k + ": ", DIM) + clr(str(v), clr2)
func b(t:String) -> String: return "[b]%s[/b]" % t
func i(t:String) -> String: return "[i]%s[/i]" % t
func u(t:String) -> String: return "[u]%s[/u]" % t
func clr(t:String, c:Color) -> String: return "[color=#%s]%s[/color]" % [c.to_html(), t]

View File

@ -1,12 +1,82 @@
extends TabContainer
onready var editor:TextEditor = owner
onready var editor:TE_Editor = owner
var mouse:bool = false
var last_tab_index:int = -1
var tab_menu:PopupMenu
func _ready():
if not editor.is_plugin_active():
return
var _e
_e = connect("mouse_entered", self, "set", ["mouse", true])
_e = connect("mouse_exited", self, "set", ["mouse", false])
_e = connect("tab_changed", self, "_tab_changed")
_e = connect("pre_popup_pressed", self, "update_popup")
add_font_override("font", editor.FONT_R)
tab_menu = owner.get_node("popup_tab_menu")
tab_menu.connect("index_pressed", self, "_popup_selected")
func _tab_changed(index):
var tab = get_child(index)
tab.grab_focus()
last_tab_index = index
func _popup_selected(index:int):
var tindex := tab_menu.get_item_id(index)
if tindex >= 100:
current_tab = tindex - 100
return
match tindex:
0: # close
get_child(hovered_tab_index).close()
1: # close others
var all_tabs = owner.get_tabs()
var hovered = get_child(hovered_tab_index)
for tab in all_tabs:
if tab != hovered:
tab.close()
2: # close left
var all_tabs = owner.get_tabs()
for i in range(0, hovered_tab_index):
all_tabs[i].close()
current_tab = 0
3: # close right
var all_tabs = owner.get_tabs()
for i in range(hovered_tab_index+1, len(all_tabs)):
all_tabs[i].close()
var hovered_tab_index:int
func update_popup(index:int=current_tab):
var all_tabs = owner.get_tabs()
hovered_tab_index = index
tab_menu.clear()
tab_menu.rect_size = Vector2.ZERO
tab_menu.add_item("Close", 0)
tab_menu.add_item("Close others", 1)
if index > 0:
tab_menu.add_item("Close all to left", 2)
if index < len(all_tabs)-1:
tab_menu.add_item("Close all to right", 3)
tab_menu.add_separator()
var i = 0
for tab in owner.get_tabs():
tab_menu.add_item(tab.name, 100+i)
i += 1
func _input(e):
if not editor.is_plugin_active():
@ -20,6 +90,14 @@ func _input(e):
elif e.button_index == BUTTON_WHEEL_UP:
next()
get_tree().set_input_as_handled()
elif e.button_index == BUTTON_RIGHT:
var index := get_tab_idx_at_point(get_local_mouse_position())
if index != -1:
update_popup(index)
tab_menu.rect_global_position = get_global_mouse_position()
tab_menu.popup()
get_tree().set_input_as_handled()
if e is InputEventKey and e.pressed and e.control and e.scancode == KEY_TAB:
if e.shift:

View File

@ -1,18 +1,16 @@
tool
extends RichTextLabel
extends "res://addons/text_editor/TE_RichTextLabel.gd"
onready var editor:TextEditor = owner
onready var file_popup:PopupMenu = $file_popup
onready var dir_popup:PopupMenu = $dir_popup
const DragLabel = preload("res://addons/text_editor/TE_DragLabel.gd")
var drag_label:RichTextLabel
var files:Array = []
var dirs:Array = []
var selected
var hovered:String = ""
var dragging:String = ""
export var p_filter:NodePath
var filter:String = ""
var selected:Array = []
var dragging:Array = []
var drag_start:Vector2
func _ready():
@ -23,12 +21,10 @@ func _ready():
_e = editor.connect("file_closed", self, "_file_closed")
_e = editor.connect("file_selected", self, "_file_selected")
_e = editor.connect("file_renamed", self, "_file_renamed")
_e = connect("meta_hover_started", self, "_meta_entered")
_e = connect("meta_hover_ended", self, "_meta_exited")
# hint
theme = Theme.new()
theme.set_font("font", "TooltipLabel", editor.FONT_R)
var le:LineEdit = get_node(p_filter)
_e = le.connect("text_changed", self, "_filter_changed")
le.add_font_override("font", editor.FONT_R)
# file popup
file_popup.clear()
@ -37,34 +33,71 @@ func _ready():
file_popup.add_separator()
file_popup.add_item("Remove")
_e = file_popup.connect("index_pressed", self, "_file_popup")
file_popup.add_font_override("font", TextEditor.FONT)
file_popup.add_font_override("font", editor.FONT)
# dir popup
dir_popup.clear()
dir_popup.rect_size = Vector2.ZERO
dir_popup.add_item("New File")
dir_popup.add_item("New Folder")
dir_popup.add_separator()
dir_popup.add_item("Remove")
dir_popup.add_separator()
dir_popup.add_item("Tint Yellow")
dir_popup.add_item("Tint Red")
dir_popup.add_item("Tint Blue")
dir_popup.add_item("Tint Green")
dir_popup.add_item("Reset Tint")
_e = dir_popup.connect("index_pressed", self, "_dir_popup")
dir_popup.add_font_override("font", TextEditor.FONT)
dir_popup.add_font_override("font", editor.FONT)
add_font_override("normal_font", editor.FONT_R)
add_font_override("bold_font", editor.FONT_B)
add_font_override("italics_font", editor.FONT_I)
add_font_override("bold_italics_font", editor.FONT_BI)
func _filter_changed(t:String):
filter = t
_redraw()
func _dir_popup(index:int):
var p = _meta_to_file(selected)
var type = p[0]
var file = p[1]
var type = selected[0]
var file = selected[1]
if type == "d":
file = file.file_path
match dir_popup.get_item_text(index):
"New File": editor.popup_create_file(file)
"New Folder": editor.popup_create_dir(file)
"New File":
editor.popup_create_file(file)
"Remove":
editor.recycle(file, type == "f")
"Tint Yellow":
selected[1].tint = "yellow"#Color.gold
editor.emit_signal("dir_tint_changed", selected[1].file_path)
_redraw()
"Tint Red":
selected[1].tint = "red"#Color.tomato
editor.emit_signal("dir_tint_changed", selected[1].file_path)
_redraw()
"Tint Blue":
selected[1].tint = "blue"#Color.deepskyblue
editor.emit_signal("dir_tint_changed", selected[1].file_path)
_redraw()
"Tint Green":
selected[1].tint = "green"#Color.chartreuse
editor.emit_signal("dir_tint_changed", selected[1].file_path)
_redraw()
"Reset Tint":
selected[1].tint = ""#Color.white
editor.emit_signal("dir_tint_changed", selected[1].file_path)
_redraw()
func _file_popup(index:int):
var p = _meta_to_file(selected)
var type = p[0]
var file = p[1]
var type = selected[0]
var file = selected[1]
if type == "d":
file = file.file_path
match file_popup.get_item_text(index):
"Rename":
@ -75,70 +108,98 @@ func _file_popup(index:int):
"Remove":
if type == "f":
editor.recycle_file(file)
editor.recycle(file, true)
_:
selected = {}
selected = []
func _renamed(new_file:String):
var p = _meta_to_file(selected)
var type = p[0]
var file = p[1]
var type = selected[0]
var file = selected[1]
var old_path:String = file
var old_file:String = old_path.get_file()
if new_file != old_file:
var new_path:String = old_path.get_base_dir().plus_file(new_file)
editor.rename_file(old_path, new_path)
selected = {}
selected = []
func _input(e:InputEvent):
if not editor.is_plugin_active():
return
if e is InputEventMouseButton and hovered:
var p = _meta_to_file(hovered)
var type = p[0]
var file = p[1]
if e is InputEventMouseButton and meta_hovered:
var type = meta_hovered[0]
var file = meta_hovered[1]
if e.button_index == BUTTON_LEFT:
if e.pressed:
dragging = hovered
if type == "f":
drag_label = DragLabel.new()
drag_label.editor = editor
drag_label.set_bbcode(file.get_file())
editor.add_child(drag_label)
if type in ["f", "d"]:
if type == "f" and Input.is_key_pressed(KEY_CONTROL):
editor.file_data[file].open = not editor.file_data[file].open
_redraw()
else:
var file_path = file if type == "f" else file.file_path
# can't move recycling
if editor.is_trash_path(file_path):
return
# select for drag
else:
dragging = meta_hovered
drag_label = DragLabel.new(file_path.get_file())
drag_label.editor = editor
editor.add_child(drag_label)
else:
if dragging and dragging != hovered:
var p2 = _meta_to_file(dragging)
var drag_type = p[0]
var drag_file = p[1]
if drag_type == "f" and type == "d":
var dir:String = file
var old_path:String = drag_file
var new_path:String = dir.plus_file(old_path.get_file())
editor.rename_file(old_path, new_path)
if type == "f" and Input.is_key_pressed(KEY_CONTROL):
pass
else:
match type:
# toggle directory
"d":
p[2].open = not p[2].open
_redraw()
if dragging and dragging != meta_hovered:
var drag_type = dragging[0]
var drag_file = dragging[1]
# select
"f":
editor.select_file(file)
# dragged onto directory?
if type == "d":
var dir:String = file.file_path
var old_path:String = drag_file if drag_type == "f" else drag_file.file_path
var new_path:String = dir.plus_file(old_path.get_file())
editor.rename_file(old_path, new_path)
dragging = []
else:
match type:
# toggle directory
"d":
file.open = not file.open
_redraw()
# unrecycle
"unrecycle":
editor.unrecycle(file.file_path)
# select
"f":
editor.select_file(file)
# select file symbol
"fs":
editor.select_file(file)
var tab = editor.get_selected_tab()
yield(get_tree(), "idle_frame")
tab.goto_symbol(meta_hovered[2])
get_tree().set_input_as_handled()
elif e.button_index == BUTTON_RIGHT:
if e.pressed:
selected = hovered
selected = meta_hovered
match type:
"d":
dir_popup.set_global_position(get_global_mouse_position())
@ -149,26 +210,6 @@ func _input(e:InputEvent):
file_popup.popup()
get_tree().set_input_as_handled()
func _meta_to_file(m:String):
var p = m.split(":", true, 1)
var type = p[0]
var index = int(p[1])
match type:
"d":
return [type, dirs[index].file_path, dirs[index]]
"f":
return [type, files[index]]
func _meta_entered(m):
hovered = m
var f = _meta_to_file(m)
match f[0]:
"f", "d": hint_tooltip = f[1]
func _meta_exited(_m):
hovered = ""
hint_tooltip = ""
func _file_opened(_file_path:String): _redraw()
func _file_closed(_file_path:String): _redraw()
func _file_selected(_file_path:String): _redraw()
@ -178,76 +219,139 @@ var lines:PoolStringArray = PoolStringArray()
func _redraw():
lines = PoolStringArray()
dirs.clear()
files.clear()
_draw_dir(editor.file_list[""], 0)
lines.append_array(_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]
func _dull_nonwords(s:String, clr:Color, dull:Color) -> String:
var on = false
var parts = []
for c in s:
on = c in "0123456789_-"
if not parts or parts[-1][0] != on:
parts.append([on, ""])
parts[-1][1] += c
var out = ""
for p in parts:
out += clr(p[1], dull if p[0] else clr)
return out
const FOLDER:String = "🗀" # not visible in godot
func _draw_dir(dir:Dictionary, deep:int):
const FOLDER_CLOSED:String = "🗀" # not visible in Godot.
const FOLDER_OPEN:String = "🗁" # not visible in Godot.
func _draw_dir(dir:Dictionary, deep:int) -> Array:
var is_tagging = editor.is_tagging()
var dimmest:float = .5 if is_tagging else 0.0
var tint = editor.get_tint_color(dir.tint)
var dull = Color.white.darkened(.65)
var dull_tint = tint.darkened(.5)
var space = clr("".repeat(deep), Color.white.darkened(.8))
var space = ""
var file:String = dir.file_path
var name:String = b(file.get_file())
var head:String = "" if dir.open else ""
var dir_index:int = len(dirs)
var link:String = url(space+clr(FOLDER+head, Color.white.darkened(.5))+" "+name, "d:%s" % dir_index)
lines.append(clr(link, Color.white.darkened(dimmest)))
dirs.append(dir)
space = clr("".repeat(deep), Color.white.darkened(.8))
var head:String = "" if dir.open else ""
var fold:String = FOLDER_OPEN if dir.open else FOLDER_CLOSED
var dname = b(_dull_nonwords(file.get_file(), tint.darkened(0 if editor.is_dir_tagged(dir) else 0.5), dull))
dname = clr("", dull) + dname + clr("", dull)
head = clr(space+fold, Color.gold) + clr(head, Color.white.darkened(.5))
head += " %s" % dname
var link:String = meta(head, ["d", dir], editor.get_localized_path(file))
if editor.is_trash_path(file):
link += " " + meta(clr("", Color.yellowgreen), ["unrecycle", dir], file)
var out = []
var sel = editor.get_selected_tab()
sel = sel.file_path if sel else ""
if dir.open:
if dir.open or filter:
# draw dirs
for path in dir.dirs:
var file_path = dir.all[path]
if file_path is Dictionary:
_draw_dir(file_path, deep+1)
if file_path is Dictionary and (file_path.show or filter):
out.append_array(_draw_dir(file_path, deep+1))
# draw files
var last = len(dir.files)-1
for i in len(dir.files):
var file_path = dir.files[i]
file = file_path.get_file()
var p = file.split(".", true, 1)
var fname = file_path.get_file()
file = fname
var p = [file, ""] if not "." in file else file.split(".", true, 1)
file = p[0]
var ext = p[1]
var is_selected = file_path == sel
var is_opened = editor.is_opened(file_path)
var color = Color.white
head = "┣╸" if i != last else "┗╸"
var color = tint
head = "" if filter else "┣╸" if i != last else "┗╸"
if "readme" in file.to_lower():
var fname_lower = fname.to_lower()
if "readme" in fname_lower:
head = "🛈"
if is_selected:
head = ""
elif is_opened:
head = ""
head = clr(head, Color.white.darkened(.5 if is_opened else .75))
var bold = false
if is_tagging:
if editor.is_tagged(file_path):
file = b(file)
bold = true
else:
color = color.darkened(dimmest)
else:
pass
file = clr(file, color)
ext = clr("." + ext, Color.white.darkened(.65))
var line = space + head + file + ext
lines.append(url(line, "f:%s" % len(files)))
files.append(file_path)
file = _dull_nonwords(file, color, dull)
if bold:
file = b(file)
ext = "" if not ext else clr("." + ext, dull)
var line = file + ext
if is_selected:
line = u(line)
var hint_path = editor.get_localized_path(file_path)
var file_lines = []
if not editor._scanning:
var fdata = editor.get_file_data(file_path)
var symbols = fdata.symbols.values()
for j in range(1, len(symbols)):
if fdata.open or filter:
var sdata:Dictionary = symbols[j]
var sname:String = sdata.name
var index = sname.to_lower().find(filter)
if filter and index == -1:
continue
var sname_highlighted = TE_Util.highlight(sname, index, len(filter), TE_Util.hue_shift(dull_tint, -.075), Color.white)
var s = clr(" @ " + "-".repeat(sdata.deep), dull) + sname_highlighted
var h = hint_path + " @" + sname
file_lines.append(meta(space + s, ["fs", file_path, j], h))
if filter:
var index = fname_lower.find(filter)
if index != -1:
line = TE_Util.highlight(fname, index, len(filter), dull_tint, Color.white)
# show file name if not filtering, or if
if not filter or file_lines:
file_lines.push_front(meta(space + head + line, ["f", file_path], hint_path))
# file_lines.push_front(space + head + line)
out.append_array(file_lines)
# show folder name if not filtering, or if filter catches a sub file or subsymbol
if not filter or out:
out.push_front(link)
return out

View File

@ -1,7 +1,7 @@
tool
extends LineEdit
onready var editor:TextEditor = owner
onready var editor:TE_Editor = owner
var fr:FuncRef
func _ready():
@ -9,16 +9,20 @@ func _ready():
_e = connect("text_entered", self, "_enter")
_e = connect("focus_exited", self, "_lost_focus")
add_font_override("font", TextEditor.FONT_R)
add_font_override("font", editor.FONT_R)
func _unhandled_key_input(e):
if e.scancode == KEY_ESCAPE and e.pressed:
if not editor.is_plugin_active():
return
if visible and 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
select_all()
fr = funcref(obj, fname)
show()
call_deferred("grab_focus")
@ -28,5 +32,6 @@ func _lost_focus():
hide()
func _enter(t:String):
fr.call_func(t)
if fr:
fr.call_func(t)
hide()

View File

@ -1,16 +1,13 @@
tool
extends TE_RichTextLabel
onready var editor:TextEditor = owner
extends "res://addons/text_editor/TE_RichTextLabel.gd"
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 _resized():
# add_constant_override("table_hseparation", int(rect_size.x / 6.0))
func _file_selected(_file_path:String):
yield(get_tree(), "idle_frame")
@ -20,6 +17,9 @@ func _file_saved(_file_path:String):
_redraw()
func _redraw():
if not visible:
return
var tab = editor.get_selected_tab()
if tab:
tab.helper.generate_meta(tab, self)

View File

@ -0,0 +1,39 @@
tool
extends TabContainer
onready var editor:TE_Editor = owner
func _ready():
if not editor.is_plugin_active():
return
set_visible(false)
add_font_override("font", editor.FONT_R)
func _unhandled_key_input(e):
if not editor.is_plugin_active():
return
if e.control and e.pressed:
match e.scancode:
# show this menu
KEY_M:
set_visible(not get_parent().visible)
get_tree().set_input_as_handled()
# find menu
KEY_F:
set_visible(true)
select_tab($search)
$search/rte.select()
func set_visible(v:bool):
get_parent().visible = v
func select_tab(tab:Node):
current_tab = tab.get_index()
func show_image(file_path:String):
get_parent().visible = true
select_tab($image)
$image/image.texture = TE_Util.load_image(file_path)

View File

@ -1,12 +1,87 @@
extends RichTextLabel
class_name TE_RichTextLabel
onready var editor:TE_Editor = owner
var meta_items:Array = []
var meta_hovered:Array = []
class Table:
var table_id:String
var heading:Array = []
var columns:Array = []
var _sort_index:int
var _sort_reverse:bool
func _init(id:String):
table_id = id
func sort(index:int, reverse:bool):
_sort_index = index
_sort_reverse = reverse
columns.sort_custom(self, "_sort")
func output(rte:RichTextLabel):
rte.push_table(len(heading))
for i in len(heading):
rte.push_cell()
rte.push_bold()
rte.push_meta("table|%s|%s" % [table_id, i])
rte.add_text(heading[i])
rte.pop()
rte.pop()
rte.pop()
for i in len(columns):
rte.push_cell()
rte.add_text(str(columns[i]))
rte.pop()
rte.pop()
class RTE:
var rte
var s:String
func start(st:String):
s = st
return self
func clr(c:Color):
s = "[color=#%s]%s[/color]" % [c.to_html(), s]
return self
func meta(type:String, meta, args=null):
var index:int = len(rte.meta_items)
rte.meta_items.append(meta)
s = "[url=%s|%s]%s[/url]" % [type, index, s]
return self
func out():
rte.append_bbcode(s)
func _ready():
# hint
theme = Theme.new()
theme.set_font("font", "TooltipLabel", editor.FONT_R)
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)
var _e
_e = connect("resized", self, "_resized")
_e = connect("meta_clicked", self, "_meta_clicked")
_e = connect("meta_hover_started", self, "_meta_hover_started")
_e = connect("meta_hover_ended", self, "_meta_hover_ended")
func _resized():
pass
func _clicked(_data):
pass
func clear():
.clear()
meta_items.clear()
func table(rows) -> String:
var cells = ""
@ -19,3 +94,34 @@ func table(rows) -> String:
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]
func b(t:String) -> String: return "[b]%s[/b]" % t
func i(t:String) -> String: return "[i]%s[/i]" % t
func u(t:String) -> String: return "[u]%s[/u]" % t
func clr(t:String, c:Color) -> String: return "[color=#%s]%s[/color]" % [c.to_html(), t]
func center(t:String): return "[center]%s[/center]" % t
func _meta_hover_started(meta):
var info = meta_items[int(meta)]
var hint = info[1]
meta_hovered = info[0]
if hint:
hint_tooltip = hint
func _meta_hover_ended(_meta):
meta_hovered = []
hint_tooltip = ""
func _meta_clicked(meta):
var info = meta_items[int(meta)]
if info[0]:
_clicked(info[0])
func add_meta(args:Array, hint:String) -> int:
var index:int = len(meta_items)
meta_items.append([args, hint])
return index
func meta(t:String, args:Array=[], hint:String="") -> String:
var index:int = add_meta(args, hint)
return "[url=%s]%s[/url]" % [index, t]

View File

@ -0,0 +1,244 @@
tool
extends "res://addons/text_editor/TE_RichTextLabel.gd"
var chapter_info:Array = []
var sort_on:String = "words"
var sort_on_index:int = 1
var sort_reverse:Dictionary = { id=true, chaps=true, words=true, uwords=true, "%":true, modified=true }
var skip_words:PoolStringArray
func _ready():
var btn = get_parent().get_node("update")
btn.add_font_override("font", editor.FONT_R)
var _e = btn.connect("pressed", self, "_update")
func _update():
chapter_info.clear()
# load block list
var skip_list = editor.current_directory.plus_file("word_skip_list.txt")
if File.new().file_exists(skip_list):
skip_words = TE_Util.load_text(skip_list).replace("\n", " ").strip_edges().to_lower().split(" ")
else:
skip_words = PoolStringArray()
for path in editor.file_paths:
var file = path.get_file()
var ext = file.get_extension()
match ext:
"md": _process_md(path)
# clear empty
for i in range(len(chapter_info)-1, -1, -1):
var info = chapter_info[i]
if not info.words:
chapter_info.remove(i)
_sort()
_redraw()
const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
const MONTHS = ["Januaray", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
const TIMES:Dictionary = {
"second": 60,
"minute": 60,
"hour": 24,
"day": INF
}
func get_time(t:int) -> String:
for k in TIMES:
if t < TIMES[k]:
return "%s %s ago" % [t, k + ("" if t == 1 else "s")]
t /= TIMES[k]
return "???"
func _process_md(path:String):
var lines = TE_Util.load_text(path).split("\n")
var file_time = File.new().get_modified_time(path)
var curr_time = OS.get_unix_time()
var diff_time = curr_time - file_time
var time_nice = get_time(diff_time)
if false and diff_time > 9999999:
time_nice = OS.get_datetime_from_unix_time(file_time)
time_nice.weekday = WEEKDAYS[time_nice.weekday-1].substr(0, 3).to_lower()
time_nice.month = MONTHS[time_nice.month-1].substr(0, 3).to_lower()
time_nice.hour12 = str(time_nice.hour % 12)
time_nice.ampm = "am" if time_nice.hour > 12 else "pm"
time_nice = "{weekday} {month} {day}, {year} {hour12}:{minute}:{second}{ampm}".format(time_nice)
var out = { path=path, line=0, id=editor.get_localized_path(path), modified=file_time, time_nice=time_nice, words=0, uwords={}, chaps=0, "%":0.0 }
chapter_info.append(out)
var i = 0
while i < len(lines):
# skip head meta
if i == 0 and lines[i].begins_with("---"):
i += 1
while i < len(lines) and not lines[i].begins_with("---"):
if ":" in lines[i]:
var p = lines[i].split(":", true, 1)
var k = p[0].strip_edges()
var v = p[1].strip_edges()
match k:
"name":
out.id = v
"prog", "progress":
out["%"] = float(v.replace("%", ""))
i += 1
# skip comments
elif "<!--" in lines[i]:
pass
# skip code blocks
elif lines[i].begins_with("~~~") or lines[i].begins_with("```"):
var head = lines[i].substr(0, 3)
i += 1
while i < len(lines) and not lines[i].begins_with(head):
i += 1
# heading
elif lines[i].begins_with("#"):
var p = lines[i].split(" ", true, 1)
var id = lines[i].split(" ", true, 1)
var deep = len(id[0])
id = "???" if len(id) == 1 else id[1].strip_edges()
out.chaps += 1
else:
out.words += TE_Util.count_words(lines[i], out.uwords, skip_words)
i += 1
# sort word counts
TE_Util.sort_vals(out.uwords)
var words = PoolStringArray(out.uwords.keys())
var words_top = words
if len(words_top) > 16:
words_top.resize(16)
out.uwords = words_top.join(" ")
var word_lines = [""]
for word in words:
if len(word_lines[-1]) >= 64:
word_lines.append("")
if word_lines[-1]:
word_lines[-1] += " "
word_lines[-1] += word
out.uwords_all = PoolStringArray(word_lines).join("\n")
func _clicked(args):
match args[0]:
"sort_table":
var key = args[1]
if sort_on != key:
sort_on = key
sort_on_index = sort_reverse.keys().find(sort_on)
else:
sort_reverse[key] = not sort_reverse[key]
_sort()
_redraw()
"goto":
var tab = editor.open_file(args[1])
editor.select_file(args[1])
tab.goto_line(args[2])
func _sort():
if sort_reverse[sort_on]:
chapter_info.sort_custom(self, "_sort_chapters_r")
else:
chapter_info.sort_custom(self, "_sort_chapters")
func _sort_chapters(a, b):
return a[sort_on] < b[sort_on]
func _sort_chapters_r(a, b):
return a[sort_on] > b[sort_on]
func _redraw():
clear()
var c1 = Color.white.darkened(.4)
var c2 = Color.white.darkened(.3)
var ch1 = lerp(c1, Color.yellowgreen, .5)
var ch2 = lerp(c2, Color.yellowgreen, .5)
var cols = ["id", "chaps", "words", "uwords", "%", "modified"]
push_align(RichTextLabel.ALIGN_CENTER)
push_table(len(cols))
add_constant_override("table_hseparation", 8)
for id in cols:
push_cell()
push_bold()
push_meta(add_meta(["sort_table", id], "sort on %s" % id))
add_text(id)
if sort_on == id:
push_color(Color.greenyellow.darkened(.25))
add_text("" if sort_reverse[id] else "")
pop()
else:
push_color(Color.white.darkened(.7))
add_text("" if sort_reverse[id] else "")
pop()
pop()
pop()
pop()
for i in len(chapter_info):
var item = chapter_info[i]
var clr = c1 if i%2==0 else c2
var clrh = ch1 if i%2==0 else ch2
# id
push_cell()
push_color(clrh if sort_on_index == 0 else clr)
push_meta(add_meta(["goto", item.path, item.line], item.path + "\n" + item.uwords_all))
add_text(item.id)
pop()
pop()
pop()
# chapters
push_cell()
push_color(clrh if sort_on_index == 1 else clr)
add_text(TE_Util.commas(item.chaps))
pop()
pop()
# word cound
push_cell()
push_color(clrh if sort_on_index == 2 else clr)
add_text(TE_Util.commas(item.words))
pop()
pop()
# unique words
push_cell()
push_color(clrh if sort_on_index == 3 else clr)
add_text(item.uwords)
pop()
pop()
# percent
push_cell()
push_color(clrh if sort_on_index == 4 else clr)
add_text(str(int(item["%"])))
pop()
pop()
# time
push_cell()
push_color(clrh if sort_on_index == 5 else clr)
add_text(item.time_nice)
pop()
pop()
pop()
pop()

View File

@ -0,0 +1,163 @@
tool
extends "res://addons/text_editor/TE_RichTextLabel.gd"
onready var line_edit:LineEdit = get_parent().get_node("c/le")
onready var all_toggle:CheckBox = get_parent().get_node("c/all")
onready var case_toggle:CheckBox = get_parent().get_node("c/case")
var console_urls:Array = []
var last_search:String = ""
func _ready():
var _e
_e = line_edit.connect("text_entered", self, "_text_entered")
# fix fonts
line_edit.add_font_override("font", editor.FONT_R)
all_toggle.add_font_override("font", editor.FONT_R)
case_toggle.add_font_override("font", editor.FONT_R)
func select():
line_edit.grab_focus()
line_edit.grab_click_focus()
line_edit.select_all()
func _clicked(args):
match args[0]:
"goto":
var tab:TextEdit = editor.open_file(args[1])
editor.select_file(args[1])
yield(get_tree(), "idle_frame")
# goto line
var hfrom = int(args[2])
var line = int(args[3])
tab.goto_line(hfrom)
tab.goto_line(line, false)
# select area
var from = int(args[4])
var lenn = int(args[5])
tab.select(line, from, line, from + lenn)
tab.cursor_set_line(line)
tab.cursor_set_column(from)
func _text_entered(search_for:String):
last_search = search_for
clear()
var found:Dictionary = _search(search_for)
var bbcode:PoolStringArray = PoolStringArray()
var fpaths = found.keys()
for k in len(fpaths):
if k != 0:
bbcode.append("")
var file_path:String = fpaths[k]
var l = clr("%s/%s " % [k+1, len(fpaths)], Color.orange) + clr(file_path.get_file(), Color.yellow)
l = meta(l, ["goto", file_path, 0, 0, 0], file_path)
bbcode.append(l)
var all_found = found[file_path]
for j in len(all_found):
var result = found[file_path][j]
var got_lines = result[0]
var highlight_from = result[1]
var line_index = result[2]
var char_index = result[3]
bbcode.append(clr(" %s/%s" % [j+1, len(all_found)], Color.orange))
for i in len(got_lines):
l = ""
var highlight = got_lines[i][0]
var lindex = got_lines[i][1]
var ltext = got_lines[i][2]
if highlight:
# var line:String = ltext
# var head:String = line.substr(0, char_index)
# var midd:String = line.substr(char_index, len(search_for))
# var tail:String = line.substr(char_index+len(search_for))
# head = clr(head, Color.white.darkened(.25))
# midd = b(clr(midd, Color.white))
# tail = clr(tail, Color.white.darkened(.25))
var h = TE_Util.highlight(ltext, char_index, len(search_for), Color.white.darkened(.25), Color.white)
l = "\t" + clr(str(lindex) + ": ", Color.white.darkened(.25)) + h
else:
l = "\t" + clr(str(lindex) + ": ", Color.white.darkened(.65)) + clr(ltext, Color.white.darkened(.5))
if l:
l = meta(l, ["goto", file_path, highlight_from, line_index, char_index, len(search_for)])
bbcode.append(l)
set_bbcode(bbcode.join("\n"))
# get a list of files containging lines
func _search(search_for:String) -> Dictionary:
var out = {}
var search_for_l:String
if case_toggle.pressed:
search_for_l = search_for
else:
search_for_l = search_for.to_lower()
var paths:Array
# search all
if all_toggle.pressed:
paths = editor.file_paths
# only search selected
else:
var sel = editor.get_selected_file()
if not sel:
var err_msg = "no file open to search"
editor.console.err(err_msg)
push_error(err_msg)
return out
else:
paths = [sel]
for path in paths:
var lines = TE_Util.load_text(path).split("\n")
for line_index in len(lines):
var line:String = lines[line_index]
# make lowercase, if case doesn't matter
if not case_toggle.pressed:
line = line.to_lower()
# find index where result is found
var char_index:int = line.find(search_for_l)
if char_index != -1:
if not path in out:
out[path] = []
var preview_lines = [[true, line_index, lines[line_index]]]
var highlight_from:int = line_index
# previous few lines before a blank
for i in range(1, 3):
if line_index-i >= 0:
if not lines[line_index-i].strip_edges():
break
highlight_from = line_index-i
preview_lines.push_front([false, line_index-i, lines[line_index-i]])
# next few lines before a blank
for i in range(1, 3):
if line_index+i < len(lines):
if not lines[line_index+i].strip_edges():
break
preview_lines.push_back([false, line_index+i, lines[line_index+i]])
# lines, index in file, index in line
out[path].append([preview_lines, highlight_from, line_index, char_index])
return out

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,26 @@
tool
extends RichTextLabel
extends "res://addons/text_editor/TE_RichTextLabel.gd"
onready var editor:TextEditor = owner
var hscrolls:Dictionary = {}
var selected_line:int = 0
var current_file:String = ""
var filter:String = ""
export var p_filter:NodePath
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")
_e = editor.connect("file_selected", self, "_file_selected")
_e = editor.connect("file_closed", self, "_file_closed")
_e = editor.connect("file_renamed", self, "_file_renamed")
_e = editor.connect("selected_symbol_line", self, "_selected_symbol_line")
_e = get_v_scroll().connect("value_changed", self, "_scrolling")
var le:LineEdit = get_node(p_filter)
_e = le.connect("text_changed", self, "_filter_changed")
le.add_font_override("font", editor.FONT_R)
add_font_override("normal_font", editor.FONT_R)
add_font_override("bold_font", editor.FONT_B)
@ -17,21 +29,76 @@ func _ready():
call_deferred("_redraw")
func _hovered(_id):
pass
func _filter_changed(t:String):
filter = t.to_lower()
_redraw()
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 _selected_symbol_line(line:int):
selected_line = clamp(line, 0, get_line_count())
scroll_to_line(clamp(line-1, 0, get_line_count()-1))
_redraw()
func _file_selected(file_path:String):
current_file = file_path
yield(get_tree(), "idle_frame")
get_v_scroll().value = hscrolls.get(file_path, 0)
func _file_renamed(old:String, new:String):
current_file = new
yield(get_tree(), "idle_frame")
_redraw()
func _file_closed(file_path:String):
if file_path == current_file:
current_file = ""
_redraw()
func _scrolling(v):
hscrolls[editor.get_selected_file()] = get_v_scroll().value
func _clicked(args:Array):
var te:TextEdit = editor.get_selected_tab()
# select entire symbol block?
if Input.is_key_pressed(KEY_CONTROL):
var tab = editor.get_selected_tab()
var symbols = {} if not tab else tab.symbols
var line_index:int = args[1]
var symbol_index:int = symbols.keys().find(line_index)
var next_line:int
# select sub symbol blocks?
if not Input.is_key_pressed(KEY_SHIFT):
var deep = symbols[line_index].deep
while symbol_index < len(symbols)-1 and symbols.values()[symbol_index+1].deep > deep:
symbol_index += 1
if symbol_index == len(symbols)-1:
next_line = tab.get_line_count()-1
else:
next_line = symbols.keys()[symbol_index+1]-1
tab.select(line_index, 0, next_line, len(tab.get_line(next_line)))
te.goto_line(line_index)
else:
te.goto_line(args[1])
func _redraw():
var tab = editor.get_selected_tab()
var symbols = {} if not tab else tab.symbols
var spaces = PoolStringArray([
"- ",
" - ",
" - "
])
var colors = PoolColorArray([
Color.white,
Color.white.darkened(.25),
Color.white.darkened(.5)
])
# no symbols
if not symbols or len(symbols) == 1:
@ -39,14 +106,37 @@ func _redraw():
else:
var t = PoolStringArray()
var i = -1
for line_index in symbols:
i += 1
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])
var deep = symbol_info.deep
var space = "" if not deep else clr("-".repeat(deep), Color.white.darkened(.75))
var cl = Color.white
if filter and not filter in symbol_info.name.to_lower():
continue
if symbol_info.name.begins_with("*") and symbol_info.name.ends_with("*"):
cl = editor.get_symbol_color(deep, -.33)
elif symbol_info.name.begins_with('"') and symbol_info.name.ends_with('"'):
cl = editor.get_symbol_color(deep, .33)
else:
cl = editor.get_symbol_color(deep)
if not editor.is_tagged_or_visible(symbol_info.tags):
cl = cl.darkened(.7)
var tags = "" if not symbol_info.tags else PoolStringArray(symbol_info.tags).join(", ")
var text = clr(meta(space + symbol_info.name, [symbol_info, line_index], tags), cl)
if i == selected_line:
text = b(u(text))
t.append(text)
set_bbcode(t.join("\n"))

View File

@ -1,7 +1,7 @@
tool
extends TabContainer
onready var editor:TextEditor = owner
onready var editor:TE_Editor = owner
var mouse_over:bool = false

View File

@ -1,15 +1,8 @@
tool
extends RichTextLabel
onready var editor:TextEditor = owner
var tag_indices:Array = [] # safer to use int in [url=] than str.
extends "res://addons/text_editor/TE_RichTextLabel.gd"
func _ready():
var _e
_e = connect("meta_hover_started", self, "_hovered")
_e = connect("meta_hover_ended", self, "_unhover")
_e = connect("meta_clicked", self, "_clicked")
_e = editor.connect("symbols_updated", self, "_redraw")
_e = editor.connect("tags_updated", self, "_redraw")
@ -24,39 +17,36 @@ func _ready():
call_deferred("_redraw")
func _hovered(index):
var tag = tag_indices[int(index)]
var count = editor.tag_counts[tag]
hint_tooltip = "%s x%s" % [tag, count]
func _unhover(t):
hint_tooltip = ""
func _clicked(id):
var tag = tag_indices[int(id)]
editor.enable_tag(tag, not editor.is_tag_enabled(tag))
func sort_tags(tags:Dictionary):
var sorter:Array = []
for tag in tags:
sorter.append([tag, tags[tag]])
func _clicked(args:Array):
var tag = args[0]
var was_enabled = editor.is_tag_enabled(tag)
sorter.sort_custom(self, "_sort_tags")
if not Input.is_key_pressed(KEY_CONTROL):
editor.tags_enabled.clear()
tags.clear()
for item in sorter:
tags[item[0]] = item[1]
return tags
editor.enable_tag(tag, not was_enabled)
func _sort_tags(a, b):
return a[0] < b[0]
#func sort_tags(tags:Dictionary):
# var sorter:Array = []
# for tag in tags:
# sorter.append([tag, tags[tag]])
#
# sorter.sort_custom(self, "_sort_tags")
#
# tags.clear()
# for item in sorter:
# tags[item[0]] = item[1]
# return tags
#
#func _sort_tags(a, b):
# return a[0] < b[0]
func _redraw():
var tab = editor.get_selected_tab()
var tags = editor.tag_counts
var tags = editor.tags
var tab_tags = {} if not tab else tab.tags
sort_tags(tags)
# sort_tags(tags)
if not tags:
set_bbcode("[color=#%s][i][center]*No tags*" % [Color.webgray.to_html()])
@ -66,30 +56,23 @@ func _redraw():
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 count = editor.tags[tag]
var enabled = editor.is_tag_enabled(tag)
var x = tag
# 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 = editor.color_text
var dim = 0.75
if tag in tab_tags:
color = editor.color_symbol
x = "[b]%s[/b]" % x
color = editor.color_tag
x = b(x)
dim = 0.6
if enabled:
x = x
else:
x = "[color=#%s]%s[/color]" % [color.darkened(dim).to_html(), x]
x = clr(x, color.darkened(dim))
x = "[url=%s]%s[/url]" % [len(tag_indices), x]
t.append(x)
tag_indices.append(tag)
t.append(meta(x, [tag], "%s x%s" % [tag, count] ))
set_bbcode("[center]" + t.join(" "))

View File

@ -1,734 +0,0 @@
tool
extends Control
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 PATH_TRASH:String = "res://.trash"
const PATH_TRASH_INFO:String = "res://.trash.json"
const PATH_STATE:String = "res://.text_editor_state.json"
const MAIN_EXTENSIONS:PoolStringArray = PoolStringArray([
"txt", "md", "json", "csv", "cfg", "ini", "yaml"
])
const INTERNAL_EXTENSIONS:PoolStringArray = PoolStringArray([
"gd", "tres", "tscn", "import", "gdignore", "gitignore"
])
const FILE_FILTERS:PoolStringArray = PoolStringArray([
"*.txt ; Text",
"*.md ; Markdown",
"*.json ; JSON",
"*.csv ; Comma Seperated Values",
"*.cfg ; Config",
"*.ini ; Config",
"*.yaml ; YAML",
])
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 symbols_updated()
signal tags_updated()
signal save_files()
signal state_saved()
signal state_loaded()
var plugin = null
var plugin_hint:bool = false
var show:Dictionary = {
dir={
empty=true,
hidden=true,
gdignore=true,
addons=false,
git=false,
import=false,
trash=false
},
file={
hidden=false
}
}
var color_text:Color = Color.white
var color_comment:Color = Color.white.darkened(.6)
var color_symbol:Color = Color.deepskyblue
var color_var:Color = Color.orange
var color_varname:Color = Color.white.darkened(.25)
onready var test_button:Node = $c/c/c/test
onready var tab_parent:TabContainer = $c/div1/div2/c/tab_container
onready var tab_prefab:Node = $file_editor
onready var popup:ConfirmationDialog = $popup
onready var popup_unsaved:ConfirmationDialog = $popup_unsaved
onready var file_dialog:FileDialog = $file_dialog
onready var line_edit:LineEdit = $c/div1/div2/c/line_edit
onready var menu_file:MenuButton = $c/c/c/file_button
onready var menu_view:MenuButton = $c/c/c/view_button
var popup_file:PopupMenu
var popup_view:PopupMenu
var popup_view_dir:PopupMenu = PopupMenu.new()
var popup_view_file:PopupMenu = PopupMenu.new()
var current_directory:String = "res://"
var file_list:Dictionary = {}
var symbols:Dictionary = {}
var tags:Array = []
var tags_enabled:Dictionary = {}
var tag_counts:Dictionary = {}
var exts_enabled:Array = []
var opened:Array = []
var closed:Array = []
func _ready():
if not is_plugin_active():
return
load_state()
# not needed when editor plugin
# get_tree().set_auto_accept_quit(false)
var _e
_e = test_button.connect("pressed", self, "_debug_pressed")
test_button.add_font_override("font", FONT_R)
# popup unsaved
popup_unsaved.get_ok().text = "Ok"
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
menu_file.add_font_override("font", FONT_R)
popup_file = menu_file.get_popup()
popup_file.clear()
popup_file.add_font_override("font", FONT_R)
popup_file.add_item("New File", 100)
popup_file.add_item("New Folder", 200)
popup_file.add_separator()
popup_file.add_item("Open last closed", 300)
_e = popup_file.connect("id_pressed", self, "_menu_file")
# view
menu_view.add_font_override("font", FONT_R)
popup_view = menu_view.get_popup()
popup_view.clear()
popup_view.add_font_override("font", FONT_R)
# view/dir
popup_view_dir.clear()
popup_view_dir.set_name("Directories")
popup_view_dir.add_font_override("font", FONT_R)
popup_view_dir.add_check_item("Hidden", hash("Hidden"))
popup_view_dir.add_check_item("Empty", hash("Empty"))
popup_view_dir.add_check_item(".gdignore", hash(".gdignore"))
popup_view_dir.set_item_checked(0, show.dir.hidden)
popup_view_dir.set_item_checked(1, show.dir.empty)
popup_view_dir.set_item_checked(2, show.dir.gdignore)
popup_view_dir.add_separator()
popup_view_dir.add_check_item("addons/", hash("addons/"))
popup_view_dir.add_check_item(".import/", hash(".import/"))
popup_view_dir.add_check_item(".git/", hash(".git/"))
popup_view_dir.add_check_item(".trash/", hash(".trash/"))
popup_view.add_child(popup_view_dir)
popup_view.add_submenu_item("Directories", "Directories")
_e = popup_view_dir.connect("index_pressed", self, "_menu_view_dir")
# view/file
popup_view_file.clear()
popup_view_file.set_name("Files")
popup_view_file.add_font_override("font", FONT_R)
popup_view_file.add_check_item("Hidden", 0)
popup_view_file.set_item_checked(0, show.file.hidden)
popup_view_file.add_separator()
for i in len(MAIN_EXTENSIONS):
var ext = MAIN_EXTENSIONS[i]
popup_view_file.add_check_item("*." + ext, i+2)
popup_view_file.set_item_checked(i+2, true)
exts_enabled.append(ext)
popup_view_file.add_separator()
for i in len(INTERNAL_EXTENSIONS):
var ext = INTERNAL_EXTENSIONS[i]
var id = i+len(MAIN_EXTENSIONS)+3
popup_view_file.add_check_item("*." + ext, id)
popup_view_file.set_item_checked(id, false)
popup_view.add_child(popup_view_file)
popup_view.add_submenu_item("Files", "Files")
_e = popup_view_file.connect("index_pressed", self, "_menu_view_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.add_font_override("font", FONT_R)
set_directory()
func load_state():
var state:Dictionary = TE_Util.load_json(PATH_STATE)
if not state:
return
var selected
for file_path in state.tabs:
var tab = _open_file(file_path)
tab.set_state(state.tabs[file_path])
if file_path == state.selected:
selected = tab
tab_parent.current_tab = selected.get_index()
current_directory = state.current
for k in state.show.dir:
show.dir[k] = state.show.dir[k]
for k in state.show.file:
show.file[k] = state.show.file[k]
tag_counts = state.tag_counts
tags_enabled = state.tags_enabled
exts_enabled = state.exts_enabled
$c/div1.split_offset = state.div1
$c/div1/div2.split_offset = state.div2
emit_signal("state_loaded")
func save_state():
var state:Dictionary = {
"save_version": "1",
"current": current_directory,
"font_size": FONT.size,
"tabs": {},
"selected": get_selected_file(),
"show": show,
"tags": tags,
"tag_counts": tag_counts,
"tags_enabled": tags_enabled,
"exts_enabled": exts_enabled,
"div1": $c/div1.split_offset,
"div2": $c/div1/div2.split_offset
}
for tab in get_all_tabs():
state.tabs[tab.file_path] = tab.get_state()
TE_Util.save_json(PATH_STATE, state)
emit_signal("state_saved")
func _exit_tree():
save_state()
# not needed when an editor plugin
#func _notification(what):
# match what:
# MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
# for tab in get_all_tabs():
# if tab.modified:
# popup.show()
# return
# get_tree().quit()
func is_plugin_active():
if not Engine.editor_hint:
return true
return plugin_hint and visible
func _input(e):
if not is_plugin_active():
return
if e is InputEventMouseButton and e.control:
if e.button_index == BUTTON_WHEEL_DOWN:
FONT.size = int(max(8, FONT.size - 1))
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(id):
match id:
100: popup_create_file() # "New File"
200: popup_create_dir() # "New Folder"
300: open_last_file() # "Open last closed"
func _menu_view_dir(index:int):
var text = popup_view_dir.get_item_text(index)
print(text)
match text:
"Hidden":
show.dir.hidden = not show.dir.hidden
popup_view_dir.set_item_checked(index, show.dir.hidden)
"Empty":
show.dir.empty = not show.dir.empty
popup_view_dir.set_item_checked(index, show.dir.empty)
".gdignore":
show.dir.gdignore = not show.dir.gdignore
popup_view_dir.set_item_checked(index, show.dir.gdignore)
"addons/":
show.dir.addons = not show.dir.addons
popup_view_dir.set_item_checked(index, show.dir.addons)
".import/":
show.dir.import = not show.dir.import
popup_view_dir.set_item_checked(index, show.dir.import)
".git/":
show.dir.git = not show.dir.git
popup_view_dir.set_item_checked(index, show.dir.git)
".trash/":
show.dir.trash = not show.dir.trash
popup_view_dir.set_item_checked(index, show.dir.trash)
refresh_files()
func _menu_view_file(index:int):
# hidden files
if index == 0:
show.file.hidden = not show.file.hidden
popup_view_file.set_item_checked(index, show.file.hidden)
# main extensions
elif index-2 < len(MAIN_EXTENSIONS):
var ext = MAIN_EXTENSIONS[index-2]
var toggled = ext in exts_enabled
if toggled:
exts_enabled.erase(ext)
elif not ext in exts_enabled:
exts_enabled.append(ext)
popup_view_file.set_item_checked(index, not toggled)
refresh_files()
# internal extensions
elif index-3-len(MAIN_EXTENSIONS) < len(INTERNAL_EXTENSIONS):
var ext = INTERNAL_EXTENSIONS[index-3-len(MAIN_EXTENSIONS)]
var toggled = ext in exts_enabled
if toggled:
exts_enabled.erase(ext)
elif not ext in exts_enabled:
exts_enabled.append(ext)
popup_view_file.set_item_checked(index, not toggled)
refresh_files()
func _file_dialog_file(file_path:String):
match file_dialog.get_meta("mode"):
"create_file": create_file(file_path)
"create_dir": create_dir(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.get(tag, false)
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:
var tab = get_tab(file_path)
if tab:
return is_tagged_or_visible(tab.tags.keys())
return false
func is_tagging() -> bool:
return len(tags) > 0
func popup_create_file(dir:String="res://"):
file_dialog.mode = FileDialog.MODE_SAVE_FILE
file_dialog.current_dir = dir
file_dialog.window_title = "Create File"
file_dialog.current_path = "new_file.txt"
file_dialog.filters = FILE_FILTERS
file_dialog.set_meta("mode", "create_file")
file_dialog.show()
func popup_create_dir(dir:String="res://"):
file_dialog.mode = FileDialog.MODE_OPEN_DIR
file_dialog.current_dir = dir
file_dialog.window_title = "Create Folder"
file_dialog.current_path = "New Folder"
file_dialog.set_meta("mode", "create_dir")
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 create_dir(file_path:String):
var d:Directory = Directory.new()
if file_path and file_path.begins_with("res://") and not d.file_exists(file_path):
print("creating folder \"%s\"" % file_path)
d.make_dir(file_path)
refresh_files()
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:
# open_last_file()
# else:
# close_selected()
#
# elif e.scancode == KEY_R:
# sort_files()
#
# else:
# return
#
# get_tree().set_input_as_handled()
func save_files():
emit_signal("save_files")
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:
var file_path = closed.pop_back()
open_file(file_path)
select_file(file_path)
func close_selected():
var tab = get_selected_tab()
if tab:
tab.close()
else:
print("cant close")
func close_file(file_path:String):
var tab = get_tab(file_path)
if tab:
tab.close()
func _close_file(file_path, remember:bool=true):
if remember:
closed.append(file_path)
var tab = get_tab(file_path)
tab_parent.remove_child(tab)
tab.queue_free()
emit_signal("file_closed", file_path)
func _open_file(file_path:String):
var tab = tab_prefab.duplicate()
tab.visible = true
tab.editor = self
tab_parent.add_child(tab)
tab.set_owner(self)
tab.load_file(file_path)
return tab
func open_file(file_path:String, temporary:bool=false):
var tab = get_tab(file_path)
if tab:
return tab
else:
tab = _open_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 = PATH_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(PATH_TRASH_INFO)
trash_info[new_path] = file_path
TE_Util.save_json(PATH_TRASH_INFO, trash_info)
# remove by renaming
rename_file(file_path, 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=current_directory):
var gpath = ProjectSettings.globalize_path(path)
var dname = gpath.get_file()
current_directory = path
file_dialog.current_dir = path
refresh_files()
func _file_symbols_updated(file_path:String):
var tg = get_tab(file_path).tags
tags_enabled.clear()
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():
# ext_counts.clear()
# dirs.clear()
file_list.clear()
var dir = Directory.new()
if dir.open(current_directory) == OK:
_scan_dir("", current_directory, dir, file_list)
emit_signal("updated_file_list")
else:
push_error("error trying to load %s." % current_directory)
func show_dir(fname:String, base_dir:String) -> bool:
if not show.dir.gdignore and File.new().file_exists(base_dir.plus_file(".gdignore")):
return false
if fname.begins_with("."):
if not show.dir.hidden: return false
if not show.dir.import and fname == ".import": return false
if not show.dir.git and fname == ".git": return false
if not show.dir.trash and fname == ".trash": return false
else:
if not show.dir.addons and fname == "addons": return false
return true
func show_file(fname:String) -> bool:
if fname.begins_with("."):
if not show.file.hidden: return false
var ext = get_extension(fname)
return ext in exts_enabled
func _scan_dir(id:String, path:String, dir:Directory, last_dir:Dictionary):
var _e = dir.list_dir_begin(true, false)
var a_dirs_and_files = {}
var a_files = []
var a_dirs = []
var info = { file_path=path, all=a_dirs_and_files, files=a_files, dirs=a_dirs, open=true }
var fname = dir.get_next()
while fname:
var file_path = dir.get_current_dir().plus_file(fname)
if dir.current_is_dir():
if show_dir(fname, file_path.get_base_dir()):
var sub_dir = Directory.new()
sub_dir.open(file_path)
_scan_dir(fname, file_path, sub_dir, a_dirs_and_files)
else:
if show_file(fname):
a_dirs_and_files[fname] = file_path
fname = dir.get_next()
dir.list_dir_end()
# is empty? ignore
if id and not (show.dir.empty or a_dirs_and_files):
return
# add to last
last_dir[id] = info
for p in a_dirs_and_files:
if a_dirs_and_files[p] is Dictionary:
a_dirs.append(p)
else:
a_files.append(a_dirs_and_files[p])
sort_on_ext(a_dirs)
sort_on_ext(a_files)
func sort_on_ext(items:Array):
var sorted = []
for a in items:
var k = a.get_file()
if "." in k:
k = k.split(".", true, 1)
k = k[1] + k[0]
sorted.append([k, a])
sorted.sort_custom(self, "_sort_on_ext")
for i in len(items):
items[i] = sorted[i][1]
return items
func _sort_on_ext(a, b):
return a[0] < b[0]
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

@ -1,5 +1,77 @@
class_name TE_Util
class Sorter:
var d:Dictionary
var a:Array = []
func _init(dict:Dictionary):
d = dict
for k in d:
a.append([k, d[k]])
func on_keys(reverse:bool=false):
a.sort_custom(self, "_sort_keys_rev" if reverse else "_sort_keys")
return _out()
func on_vals(reverse:bool=false):
a.sort_custom(self, "_sort_vals_rev" if reverse else "_sort_vals")
return _out()
func _sort_keys(a, b): return a[0] > b[0]
func _sort_keys_rev(a, b): return a[0] < b[0]
func _sort_vals(a, b): return a[1] > b[1]
func _sort_vals_rev(a, b): return a[1] < b[1]
func _out() -> Dictionary:
d.clear()
for item in a:
d[item[0]] = item[1]
return d
static func sort_keys(d:Dictionary, reverse:bool=false): return Sorter.new(d).on_keys(reverse)
static func sort_vals(d:Dictionary, reverse:bool=false): return Sorter.new(d).on_vals(reverse)
static func count_words(text:String, counter:Dictionary, skip_words=null, stop_words:bool=true):
var word_count:int = 0
for sentence in text.split("."):
for word in sentence.split(" "):
word = _sanitize_word(word)
if not word: continue
if stop_words and word in TE_StopWords.STOP_WORDS: continue
if skip_words and word in skip_words: continue
word_count += 1
if not word in counter:
counter[word] = 1
else:
counter[word] += 1
return word_count
static func _sanitize_word(word:String):
var out = ""
var has_letter = false
for c in word.to_lower():
if c in "abcdefghijklmnopqrstuvwxyz":
out += c
has_letter = true
elif c in "-'0123456789":
out += c
if not has_letter:
return ""
if out.ends_with("'s"):
return out.substr(0, len(out)-2)
return out
static func to_var(s:String) -> String:
return s.to_lower().replace(" ", "_")
static func load_text(path:String) -> String:
var f:File = File.new()
if f.file_exists(path):
@ -21,6 +93,16 @@ static func load_json(path:String, loud:bool=false) -> Dictionary:
push_error("no json at \"%s\"" % path)
return {}
static func load_image(path:String) -> ImageTexture:
var f:File = File.new()
if f.file_exists(path):
var image:Image = Image.new()
image.load(path)
var texture:ImageTexture = ImageTexture.new()
texture.create_from_image(image)
return texture
return null
static func save_json(path:String, data:Dictionary):
var f:File = File.new()
f.open(path, File.WRITE)
@ -57,23 +139,55 @@ 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)
const _dig = {depth=0}
static func _dig_dict(d:Dictionary, f:FuncRef):
static func get_dig_depth() -> int:
return _dig.depth
static func dig_for(d, property:String, value):
var depth:int = 0
if d is Dictionary:
return _dig_for_dict(d, property, value, depth)
# elif d is Node:
# return _dig_for_node(d, propert, value, depth)
return null
static func _dig_for_dict(d:Dictionary, property:String, value, depth:int):
_dig.depth = depth
if property in d and d[property] == value:
return d
for k in d:
if d[k] is Dictionary:
var got = _dig_for_dict(d[k], property, value, depth+1)
if got != null:
return got
return null
#static func _dig_for_node(d:Node, f:FuncRef, depth:int):
# _dig.depth = depth
# f.call_func(d)
# for i in d.get_child_count():
# _dig_node(d.get_child(i), f, depth+1)
static func dig(d, obj:Object, fname:String):
var f:FuncRef = funcref(obj, fname)
var depth:int = 0
if d is Dictionary:
_dig_dict(d, f, depth)
elif d is Node:
_dig_node(d, f, depth)
static func _dig_dict(d:Dictionary, f:FuncRef, depth:int):
_dig.depth = depth
f.call_func(d)
for k in d:
if d[k] is Dictionary:
_dig_dict(d[k], f)
_dig_dict(d[k], f, depth+1)
static func _dig_node(d:Node, f:FuncRef):
static func _dig_node(d:Node, f:FuncRef, depth:int):
_dig.depth = depth
f.call_func(d)
for i in d.get_child_count():
_dig_node(d.get_child(i), f)
_dig_node(d.get_child(i), f, depth+1)
static func file_size(path:String) -> String:
var f:File = File.new()
@ -83,6 +197,24 @@ static func file_size(path:String) -> String:
return String.humanize_size(bytes)
return "-1"
static func hue_shift(c:Color, h:float) -> Color:
return c.from_hsv(wrapf(c.h + h, 0.0, 1.0), c.s, c.v, c.a)
static func highlight(line:String, start:int, length:int, default_color:Color, highlight_color:Color) -> String:
var head:String = line.substr(0, start)
var midd:String = line.substr(start, length)
var tail:String = line.substr(start + length)
head = clr(head, default_color)
midd = b(clr(midd, highlight_color))
tail = clr(tail, default_color)
return head + midd + tail
static func b(t:String) -> String: return "[b]%s[/b]" % t
static func clr(t:String, c:Color) -> String: return "[color=#%s]%s[/color]" % [c.to_html(), t]
#static func saturate(c:Color, s:float=1.0, v:float=1.0) -> Color:
# return c.from_hsv(c.h, c.s * s, c.v * v, c.a)
#static func sort(d, reverse:bool=false):
# return Dict.new(d).sort(reverse)
#

View File

@ -1,6 +1,7 @@
[gd_scene load_steps=16 format=2]
[gd_scene load_steps=29 format=2]
[ext_resource path="res://addons/text_editor/TE_TextEditor.gd" type="Script" id=2]
[ext_resource path="res://addons/text_editor/TE_Console.gd" type="Script" id=1]
[ext_resource path="res://addons/text_editor/TE_Editor.gd" type="Script" id=2]
[ext_resource path="res://addons/text_editor/TE_FilesList.gd" type="Script" id=3]
[ext_resource path="res://addons/text_editor/TE_FileEditor.gd" type="Script" id=4]
[ext_resource path="res://addons/text_editor/TE_SymbolsList.gd" type="Script" id=5]
@ -13,14 +14,37 @@
[ext_resource path="res://addons/text_editor/fonts/font_r.tres" type="DynamicFont" id=12]
[ext_resource path="res://addons/text_editor/fonts/font_bi.tres" type="DynamicFont" id=13]
[ext_resource path="res://addons/text_editor/fonts/font.tres" type="DynamicFont" id=14]
[ext_resource path="res://addons/text_editor/TE_Search.gd" type="Script" id=15]
[ext_resource path="res://addons/text_editor/TE_MetaTabs.gd" type="Script" id=16]
[ext_resource path="res://addons/text_editor/TE_ScriptInfo.gd" type="Script" id=17]
[ext_resource path="res://addons/text_editor/TE_FileInfoLabel.gd" type="Script" id=18]
[ext_resource path="res://addons/text_editor/TE_RichTextLabel.gd" type="Script" id=19]
[ext_resource path="res://addons/text_editor/te_empty_style.tres" type="StyleBox" id=20]
[sub_resource type="Theme" id=1]
[sub_resource type="Theme" id=8]
[sub_resource type="Theme" id=9]
TooltipLabel/fonts/font = ExtResource( 12 )
[sub_resource type="Theme" id=2]
[sub_resource type="Theme" id=10]
TooltipLabel/fonts/font = ExtResource( 12 )
[node name="text_editor" type="Control"]
[sub_resource type="Theme" id=11]
TooltipLabel/fonts/font = ExtResource( 12 )
[sub_resource type="Theme" id=12]
TooltipLabel/fonts/font = ExtResource( 12 )
[sub_resource type="Theme" id=13]
TooltipLabel/fonts/font = ExtResource( 12 )
[sub_resource type="Theme" id=14]
TooltipLabel/fonts/font = ExtResource( 12 )
[sub_resource type="Theme" id=15]
TooltipLabel/fonts/font = ExtResource( 12 )
[node name="editor" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
@ -29,6 +53,13 @@ script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
p_tab_parent = NodePath("c/div1/div2/c/c/c2/tab_container")
p_tab_prefab = NodePath("file_editor")
p_console = NodePath("c/div1/div2/c/c/c/meta_tabs/console")
p_progress_bar = NodePath("c/c/c/progress")
p_menu_file = NodePath("c/c/c/file")
p_menu_view = NodePath("c/c/c/view")
p_menu_insert = NodePath("c/c/c/insert")
[node name="file_editor" type="TextEdit" parent="."]
visible = false
@ -40,6 +71,7 @@ margin_right = 214.0
margin_bottom = 30.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource( 8 )
custom_fonts/font = ExtResource( 14 )
highlight_current_line = true
syntax_highlighting = true
@ -47,6 +79,7 @@ show_line_numbers = true
draw_tabs = true
fold_gutter = true
highlight_all_occurrences = true
hiding_enabled = true
minimap_draw = true
script = ExtResource( 4 )
@ -60,33 +93,33 @@ __meta__ = {
[node name="c" type="PanelContainer" parent="c"]
margin_right = 1024.0
margin_bottom = 34.0
margin_bottom = 38.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
margin_bottom = 31.0
[node name="test" type="Button" parent="c/c/c"]
margin_right = 12.0
margin_bottom = 20.0
margin_bottom = 24.0
text = "⟳"
[node name="file_button" type="MenuButton" parent="c/c/c"]
[node name="file" type="MenuButton" parent="c/c/c"]
margin_left = 16.0
margin_right = 48.0
margin_bottom = 20.0
margin_bottom = 24.0
text = "file"
items = [ "New File", null, 0, false, false, 0, 0, null, "", false, "Extensions", null, 0, false, false, 1, 0, null, "Extensions", false, "New File", null, 0, false, false, 2, 0, null, "", false, "Extensions", null, 0, false, false, 3, 0, null, "Extensions", false ]
__meta__ = {
"_edit_use_anchors_": false
}
[node name="view_button" type="MenuButton" parent="c/c/c"]
[node name="view" type="MenuButton" parent="c/c/c"]
margin_left = 52.0
margin_right = 93.0
margin_bottom = 20.0
margin_bottom = 24.0
focus_mode = 2
text = "view"
items = [ "New File", null, 0, false, false, 0, 0, null, "", false, "Extensions", null, 0, false, false, 1, 0, null, "Extensions", false, "New File", null, 0, false, false, 2, 0, null, "", false, "Extensions", null, 0, false, false, 3, 0, null, "Extensions", false ]
@ -94,8 +127,56 @@ __meta__ = {
"_edit_use_anchors_": false
}
[node name="insert" type="MenuButton" parent="c/c/c"]
margin_left = 97.0
margin_right = 146.0
margin_bottom = 24.0
focus_mode = 2
text = "insert"
items = [ "New File", null, 0, false, false, 0, 0, null, "", false, "Extensions", null, 0, false, false, 1, 0, null, "Extensions", false, "New File", null, 0, false, false, 2, 0, null, "", false, "Extensions", null, 0, false, false, 3, 0, null, "Extensions", false ]
__meta__ = {
"_edit_use_anchors_": false
}
[node name="word_wrap" type="CheckBox" parent="c/c/c"]
margin_left = 150.0
margin_right = 208.0
margin_bottom = 24.0
text = "wrap"
[node name="tab_colors" type="CheckBox" parent="c/c/c"]
margin_left = 212.0
margin_right = 303.0
margin_bottom = 24.0
text = "tab colors"
[node name="space" type="Control" parent="c/c/c"]
margin_left = 307.0
margin_right = 971.0
margin_bottom = 24.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="progress" type="ProgressBar" parent="c/c/c"]
visible = false
margin_left = 878.0
margin_right = 978.0
margin_bottom = 24.0
rect_min_size = Vector2( 100, 0 )
size_flags_vertical = 3
[node name="version" type="Label" parent="c/c/c"]
modulate = Color( 1, 1, 1, 0.521569 )
margin_left = 975.0
margin_top = 3.0
margin_right = 1010.0
margin_bottom = 20.0
custom_fonts/font = ExtResource( 12 )
text = "v1.12"
align = 2
[node name="div1" type="HSplitContainer" parent="c"]
margin_top = 34.0
margin_top = 38.0
margin_right = 1024.0
margin_bottom = 600.0
size_flags_vertical = 3
@ -106,7 +187,7 @@ __meta__ = {
[node name="c2" type="PanelContainer" parent="c/div1"]
margin_right = 206.0
margin_bottom = 566.0
margin_bottom = 562.0
rect_min_size = Vector2( 200, 0 )
size_flags_horizontal = 3
size_flags_vertical = 3
@ -115,16 +196,31 @@ size_flags_vertical = 3
margin_left = 7.0
margin_top = 7.0
margin_right = 199.0
margin_bottom = 559.0
margin_bottom = 555.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="list_files" type="RichTextLabel" parent="c/div1/c2/c"]
[node name="c" type="VBoxContainer" parent="c/div1/c2/c"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="filter" type="LineEdit" parent="c/div1/c2/c/c"]
margin_right = 192.0
margin_bottom = 27.0
custom_fonts/font = ExtResource( 12 )
clear_button_enabled = true
placeholder_text = "Filter"
[node name="list_files" type="RichTextLabel" parent="c/div1/c2/c/c"]
margin_top = 31.0
margin_right = 192.0
margin_bottom = 548.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource( 1 )
theme = SubResource( 9 )
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
@ -135,23 +231,24 @@ script = ExtResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}
p_filter = NodePath("../filter")
[node name="file_popup" type="PopupMenu" parent="c/div1/c2/c/list_files"]
[node name="file_popup" type="PopupMenu" parent="c/div1/c2/c/c/list_files"]
margin_right = 20.0
margin_bottom = 20.0
custom_fonts/font = ExtResource( 14 )
items = [ "Rename", null, 0, false, false, 0, 0, null, "", false, "", null, 0, false, false, -1, 0, null, "", true, "Remove", null, 0, false, false, 2, 0, null, "", false ]
[node name="dir_popup" type="PopupMenu" parent="c/div1/c2/c/list_files"]
[node name="dir_popup" type="PopupMenu" parent="c/div1/c2/c/c/list_files"]
margin_right = 20.0
margin_bottom = 20.0
custom_fonts/font = ExtResource( 14 )
items = [ "New File", null, 0, false, false, 0, 0, null, "", false, "New Folder", null, 0, false, false, 1, 0, null, "", false ]
items = [ "New File", null, 0, false, false, 0, 0, null, "", false, "", null, 0, false, false, -1, 0, null, "", true, "Remove", null, 0, false, false, 2, 0, null, "", false, "", null, 0, false, false, -1, 0, null, "", true, "Tint Yellow", null, 0, false, false, 4, 0, null, "", false, "Tint Red", null, 0, false, false, 5, 0, null, "", false, "Tint Blue", null, 0, false, false, 6, 0, null, "", false, "Tint Green", null, 0, false, false, 7, 0, null, "", false, "Reset Tint", null, 0, false, false, 8, 0, null, "", false ]
[node name="div2" type="HSplitContainer" parent="c/div1"]
margin_left = 218.0
margin_right = 1024.0
margin_bottom = 566.0
margin_bottom = 562.0
size_flags_horizontal = 3
size_flags_vertical = 3
split_offset = -80
@ -161,22 +258,37 @@ __meta__ = {
[node name="c" type="VBoxContainer" parent="c/div1/div2"]
margin_right = 614.0
margin_bottom = 566.0
margin_bottom = 562.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="line_edit" type="LineEdit" parent="c/div1/div2/c"]
visible = false
margin_right = 614.0
margin_bottom = 24.0
margin_bottom = 32.0
custom_fonts/font = ExtResource( 12 )
script = ExtResource( 8 )
[node name="tab_container" type="TabContainer" parent="c/div1/div2/c"]
[node name="c" type="VSplitContainer" parent="c/div1/div2/c"]
margin_right = 614.0
margin_bottom = 539.0
margin_bottom = 562.0
size_flags_horizontal = 3
size_flags_vertical = 3
split_offset = 100
[node name="c2" type="VBoxContainer" parent="c/div1/div2/c/c"]
margin_right = 614.0
margin_bottom = 375.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="tab_container" type="TabContainer" parent="c/div1/div2/c/c/c2"]
margin_right = 614.0
margin_bottom = 353.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_constants/top_margin = 0
custom_constants/side_margin = 0
custom_fonts/font = ExtResource( 12 )
tab_align = 0
drag_to_rearrange_enabled = true
@ -185,22 +297,220 @@ __meta__ = {
"_edit_use_anchors_": false
}
[node name="meta" type="RichTextLabel" parent="c/div1/div2/c"]
margin_top = 543.0
[node name="c" type="HBoxContainer" parent="c/div1/div2/c/c/c2"]
margin_top = 357.0
margin_right = 614.0
margin_bottom = 566.0
margin_bottom = 375.0
size_flags_horizontal = 3
script = ExtResource( 18 )
[node name="l" type="RichTextLabel" parent="c/div1/div2/c/c/c2/c"]
margin_right = 202.0
margin_bottom = 18.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
custom_fonts/normal_font = ExtResource( 12 )
custom_styles/focus = ExtResource( 20 )
custom_styles/normal = ExtResource( 20 )
bbcode_enabled = true
fit_content_height = true
script = ExtResource( 19 )
[node name="m" type="RichTextLabel" parent="c/div1/div2/c/c/c2/c"]
margin_left = 206.0
margin_right = 408.0
margin_bottom = 18.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
custom_fonts/normal_font = ExtResource( 12 )
custom_styles/focus = ExtResource( 20 )
custom_styles/normal = ExtResource( 20 )
bbcode_enabled = true
fit_content_height = true
script = ExtResource( 19 )
[node name="r" type="RichTextLabel" parent="c/div1/div2/c/c/c2/c"]
margin_left = 412.0
margin_right = 614.0
margin_bottom = 18.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
custom_fonts/normal_font = ExtResource( 12 )
custom_styles/focus = ExtResource( 20 )
custom_styles/normal = ExtResource( 20 )
bbcode_enabled = true
fit_content_height = true
script = ExtResource( 19 )
[node name="c" type="Control" parent="c/div1/div2/c/c"]
margin_top = 387.0
margin_right = 614.0
margin_bottom = 562.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="meta_tabs" type="TabContainer" parent="c/div1/div2/c/c/c"]
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource( 16 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="console" type="RichTextLabel" parent="c/div1/div2/c/c/c/meta_tabs"]
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
theme = SubResource( 10 )
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
custom_fonts/normal_font = ExtResource( 12 )
bbcode_enabled = true
meta_underlined = false
text = "active: False
"
script = ExtResource( 1 )
[node name="meta" type="RichTextLabel" parent="c/div1/div2/c/c/c/meta_tabs"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 4.0
margin_top = 32.0
margin_right = -4.0
margin_bottom = -4.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource( 11 )
custom_constants/table_hseparation = 16
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
custom_fonts/normal_font = ExtResource( 12 )
bbcode_enabled = true
fit_content_height = true
script = ExtResource( 9 )
[node name="search" type="VBoxContainer" parent="c/div1/div2/c/c/c/meta_tabs"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 4.0
margin_top = 32.0
margin_right = -4.0
margin_bottom = -4.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="c" type="HBoxContainer" parent="c/div1/div2/c/c/c/meta_tabs/search"]
margin_right = 606.0
margin_bottom = 27.0
[node name="le" type="LineEdit" parent="c/div1/div2/c/c/c/meta_tabs/search/c"]
margin_right = 409.0
margin_bottom = 27.0
size_flags_horizontal = 3
custom_fonts/font = ExtResource( 12 )
[node name="all" type="CheckBox" parent="c/div1/div2/c/c/c/meta_tabs/search/c"]
margin_left = 413.0
margin_right = 504.0
margin_bottom = 27.0
custom_fonts/font = ExtResource( 12 )
text = "all files"
[node name="case" type="CheckBox" parent="c/div1/div2/c/c/c/meta_tabs/search/c"]
margin_left = 508.0
margin_right = 606.0
margin_bottom = 27.0
custom_fonts/font = ExtResource( 12 )
text = "match case"
[node name="rte" type="RichTextLabel" parent="c/div1/div2/c/c/c/meta_tabs/search"]
margin_top = 31.0
margin_right = 606.0
margin_bottom = 139.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource( 12 )
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
custom_fonts/normal_font = ExtResource( 12 )
bbcode_enabled = true
meta_underlined = false
script = ExtResource( 15 )
[node name="sys" type="VBoxContainer" parent="c/div1/div2/c/c/c/meta_tabs"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 4.0
margin_top = 32.0
margin_right = -4.0
margin_bottom = -4.0
[node name="update" type="Button" parent="c/div1/div2/c/c/c/meta_tabs/sys"]
margin_right = 606.0
margin_bottom = 23.0
size_flags_horizontal = 3
custom_fonts/font = ExtResource( 12 )
text = "⟳"
[node name="sys" type="RichTextLabel" parent="c/div1/div2/c/c/c/meta_tabs/sys"]
margin_top = 27.0
margin_right = 606.0
margin_bottom = 239.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource( 13 )
custom_constants/table_hseparation = 101
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
custom_fonts/normal_font = ExtResource( 12 )
bbcode_enabled = true
meta_underlined = false
text = "idwords ⯆unique"
script = ExtResource( 17 )
[node name="image" type="VBoxContainer" parent="c/div1/div2/c/c/c/meta_tabs"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 4.0
margin_top = 32.0
margin_right = -4.0
margin_bottom = -4.0
[node name="image" type="TextureRect" parent="c/div1/div2/c/c/c/meta_tabs/image"]
margin_right = 606.0
margin_bottom = 239.0
size_flags_horizontal = 3
size_flags_vertical = 3
expand = true
stretch_mode = 6
[node name="c2" type="PanelContainer" parent="c/div1/div2"]
margin_left = 626.0
margin_right = 806.0
margin_bottom = 566.0
margin_bottom = 562.0
rect_min_size = Vector2( 100, 0 )
size_flags_vertical = 3
@ -208,19 +518,38 @@ size_flags_vertical = 3
margin_left = 7.0
margin_top = 7.0
margin_right = 173.0
margin_bottom = 559.0
margin_bottom = 555.0
custom_constants/autohide = 0
split_offset = 180
[node name="c" type="Panel" parent="c/div1/div2/c2/c"]
margin_right = 166.0
margin_bottom = 270.0
margin_bottom = 448.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="list_symbols" type="RichTextLabel" parent="c/div1/div2/c2/c/c"]
[node name="c" type="VBoxContainer" parent="c/div1/div2/c2/c/c"]
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="filter" type="LineEdit" parent="c/div1/div2/c2/c/c/c"]
margin_right = 166.0
margin_bottom = 27.0
custom_fonts/font = ExtResource( 12 )
clear_button_enabled = true
placeholder_text = "Symbol Filter"
[node name="list_symbols" type="RichTextLabel" parent="c/div1/div2/c2/c/c/c"]
margin_top = 31.0
margin_right = 166.0
margin_bottom = 448.0
size_flags_vertical = 3
theme = SubResource( 14 )
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
@ -233,11 +562,12 @@ script = ExtResource( 5 )
__meta__ = {
"_edit_use_anchors_": false
}
p_filter = NodePath("../filter")
[node name="c2" type="Panel" parent="c/div1/div2/c2/c"]
margin_top = 282.0
margin_top = 460.0
margin_right = 166.0
margin_bottom = 552.0
margin_bottom = 548.0
size_flags_horizontal = 3
size_flags_vertical = 3
@ -246,7 +576,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource( 2 )
theme = SubResource( 15 )
custom_fonts/bold_italics_font = ExtResource( 13 )
custom_fonts/italics_font = ExtResource( 10 )
custom_fonts/bold_font = ExtResource( 11 )
@ -296,3 +626,7 @@ current_path = "res://test_files/"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="popup_tab_menu" type="PopupMenu" parent="."]
margin_right = 124.0
margin_bottom = 112.0

View File

@ -4,12 +4,15 @@ class_name TE_ExtensionHelper
var symbols:Dictionary = {}
func generate_meta(t:TextEdit, r:TE_RichTextLabel):
func get_tab() -> String:
return " "
func generate_meta(t:TextEdit, r: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)))
var bytes = TE_Util.file_size(t.file_path)
r.add_constant_override("table_hseparation", int(r.rect_size.x / 5.0))
r.set_bbcode(r.table([
["chars", "words", "lines", "bytes"],
[chars, words, lines, bytes]
@ -68,6 +71,23 @@ func get_symbols(t:String) -> Dictionary:
symbols = {}
return symbols
#func get_symbol_names(s:Dictionary):
# var out = []
# for k in s:
# if k != -1:
# out.append(s[k].name)
# return out
func get_tag_counts(s:Dictionary) -> Dictionary:
var out = {}
for k in s:
for tag in s[k].tags:
if not tag in out:
out[tag] = 1
else:
out[tag] += 1
return out
func apply_colors(e, t:TextEdit):
t.add_color_override("font_color", e.color_text)
t.add_color_override("number_color", e.color_var)

View File

@ -1,7 +1,7 @@
tool
extends TE_ExtensionHelper
func apply_colors(e:TextEditor, t:TextEdit):
func apply_colors(e:TE_Editor, t:TextEdit):
.apply_colors(e, t)
# symbols
t.add_color_region("[", "]", e.color_symbol, false)
@ -27,6 +27,7 @@ func get_symbols(t:String) -> Dictionary:
# tags
elif lines[i].begins_with(";") and "#" in lines[i]:
for t in lines[i].substr(1).split("#"):
t = t.strip_edges()
if t:
last.tags.append(t)

View File

@ -17,33 +17,32 @@ func get_symbols(t:String):
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)
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)
elif '"tags": "' in lines[i]:
for tag in lines[i].split('"tags": "', 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):
func apply_colors(e:TE_Editor, t:TextEdit):
.apply_colors(e, t)
# vars
t.add_color_region(' "', '"', e.color_var)
t.add_color_region(' "', '"', e.color_varname)
t.add_color_region('"', '"', e.color_varname)
t.add_keyword_color("true", e.color_var)
t.add_keyword_color("false", e.color_var)
t.add_keyword_color("null", e.color_var)
# comments
t.add_color_region("/*", "*/", e.color_comment)
t.add_color_region("//", "", e.color_comment, true)
# t.add_color_region("/*", "*/", e.color_comment)
t.add_color_region('\t"#"', ",", e.color_comment, false)

View File

@ -1,16 +1,137 @@
tool
extends TE_ExtensionHelper
func generate_meta(t:TextEdit, r:RichTextLabel):
.generate_meta(t, r)
var i = 0
var meta = {}
var words = {}
var word_count = 0
var chaps = [{i=0, id="???", words={}, word_count=0 }]
while i < t.get_line_count():
var line = t.get_line(i)
# get meta
if i == 0 and line.begins_with("---"):
i += 1
while i < t.get_line_count() and not t.get_line(i).begins_with("---"):
if ":" in t.get_line(i):
var p = t.get_line(i).split(":", true, 1)
var k = p[0].strip_edges()
var v = p[1].strip_edges()
meta[k] = v
if k == "name":
chaps[-1].id = v
i += 1
# ignore comments
elif "<!--" in line:
pass
# ignore tables
elif "|" in line:
pass
# ignore code
elif line.begins_with("```") or line.begins_with("~~~"):
var head = line.substr(0, 3)
i += 1
while i < t.get_line_count() and not t.get_line(i).begins_with(head):
i += 1
# get chapter info
elif line.begins_with("#"):
var id = line.split(" ", true, 1)[1].strip_edges()
chaps.append({i=i, id=id, words={}, word_count=0 })
else:
var last = chaps[-1]
last.word_count += TE_Util.count_words(line, last.words)
i += 1
# total words
for chap in chaps:
word_count += chap.word_count
for word in chap.words:
if not word in words:
words[word] = chap.words[word]
else:
words[word] += chap.words[word]
# sort
TE_Util.sort_vals(chap.words)
r.push_align(RichTextLabel.ALIGN_CENTER)
r.push_table(4)
for x in ["#", "id", "word %s" % word_count, "words"]:
r.push_cell()
r.push_bold()
r.add_text(x)
r.pop()
r.pop()
var index:int = 0
for chap in chaps:
if chap.id == "???" and not chap.word_count:
continue
index += 1
r.push_cell()
r.push_color(Color.webgray)
r.add_text(str(index))
r.pop()
r.pop()
r.push_cell()
r.push_color(Color.webgray)
r.add_text(chap.id)
r.pop()
r.pop()
var div = 0 if not chap.word_count or not word_count else chap.word_count / float(word_count)
div *= 100.0
div = "%" + str(stepify(div, .1))
r.push_cell()
r.push_color(Color.webgray)
r.add_text(str(chap.word_count))
r.pop()
r.push_color(Color.gray)
r.add_text(" %s" % div)
r.pop()
r.pop()
r.push_cell()
r.push_color(Color.webgray)
r.add_text(PoolStringArray(chap.words.keys()).join(" "))
r.pop()
r.pop()
r.pop()
r.pop()
func _sort(a, b):
return a[1] > b[1]
func toggle_comment(t:TextEdit, head:String="<!-- ", tail:String=" -->"):
return .toggle_comment(t, head, tail)
func apply_colors(e:TextEditor, t:TextEdit):
func apply_colors(e:TE_Editor, t:TextEdit):
.apply_colors(e, t)
var code:Color = lerp(e.color_text.darkened(.5), Color.yellowgreen, .5)
var code:Color = lerp(Color.white.darkened(.5), Color.deepskyblue, .333)
var quote:Color = lerp(e.color_text, e.color_symbol, .5)
t.add_keyword_color("true", e.color_var)
t.add_keyword_color("false", e.color_var)
t.add_color_override("function_color", e.color_text)
t.add_color_override("number_color", e.color_text)
# t.add_keyword_color("true", e.color_var)
# t.add_keyword_color("false", e.color_var)
# bold italic
t.add_color_region("***", "***", Color.tomato.darkened(.3), false)
@ -20,24 +141,31 @@ func apply_colors(e:TextEditor, t:TextEdit):
t.add_color_region("*", "*", Color.tomato.lightened(.3), false)
# quote
t.add_color_region("> ", "", Color.white.darkened(.6), true)
t.add_color_region("> ", "", quote, true)
# comment
t.add_color_region("<!--", "-->", e.color_comment, false)
# headings
var head = e.color_symbol
t.add_color_region("# *", "*", head.darkened(.5), true)
t.add_color_region("# \"", "\"", head.lightened(.5), 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)
# non official markdown:
# formatted
t.add_color_region("{", "}", lerp(e.color_text, e.color_var, .5).darkened(.25), false)
# t.add_color_region("[", "]", lerp(e.color_text, e.color_var, .5).darkened(.25), false)
# t.add_color_region("(", ")", lerp(e.color_text, e.color_var, .5).darkened(.25), false)
if false:
# quote
t.add_color_region('"', '"', quote, false)
# brackets
t.add_color_region('(', ')', quote, false)
else:
# url links
t.add_color_region("![", ")", e.color_var.lightened(.5))
# url links
t.add_color_region("[", ")", e.color_var.lightened(.5))
# headings
for i in range(1, 7):
var h = "#".repeat(i)
t.add_color_region("%s *" % h, "*", e.get_symbol_color(i-1, -.33), true)
t.add_color_region("%s \"" % h, "\"", e.get_symbol_color(i-1, .33), true)
t.add_color_region("%s " % h, "*", e.get_symbol_color(i-1), true)
# lists
t.add_color_region("- [x", "]", Color.yellowgreen, false)
@ -46,6 +174,7 @@ func apply_colors(e:TextEditor, t:TextEdit):
# code blocks
t.add_color_region("```", "```", code, false)
t.add_color_region("~~~", "~~~", code, false)
t.add_color_region("---", "---", code, false)
# strikeout
t.add_color_region("~~", "~~", Color.tomato, false)
@ -56,12 +185,9 @@ func apply_colors(e:TextEditor, t:TextEdit):
# at/mention
t.add_color_region("@", " ", Color.yellowgreen, false)
t.add_color_region(": ", "", e.color_text.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()
@ -69,11 +195,33 @@ func get_symbols(t:String) -> Dictionary:
var i = 0
while i < len(lines):
# initial meta data
if i == 0 and lines[i].begins_with("---"):
i += 1
while i < len(lines) and not lines[i].begins_with("---"):
if "tags: " in lines[i]:
for tag in lines[i].split("tags: ", true, 1)[1].split("#"):
tag = tag.strip_edges()
if tag:
last.tags.append(tag)
# elif "name: " in lines[i]:
# last.name = lines[i].split("name: ", true, 1)[1]
i += 1
# i += 1
elif lines[i].begins_with("```") or lines[i].begins_with("~~~"):
var head = lines[i].substr(0, 3)
i += 1
while i < len(lines) and not lines[i].begins_with(head):
i += 1
# symbols
if lines[i].begins_with("#"):
elif lines[i].begins_with("#"):
var p = lines[i].split(" ", true, 1)
var deep = len(p[0])-1
var name = p[1].strip_edges()
var name = "???" if len(p) == 1 else p[1].strip_edges()
last = add_symbol(i, deep, name)
# tags

View File

@ -0,0 +1,50 @@
tool
extends TE_ExtensionHelper
func get_tab() -> String:
return " "
func apply_colors(e:TE_Editor, t:TextEdit):
.apply_colors(e, t)
for k in "label menu define default scene show with play return jump call".split(" "):
t.add_keyword_color(k, e.color_symbol)
# 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)
# comments
t.add_color_region("#", "", e.color_comment, true)
t.add_color_region("$ ", "", e.color_comment, true)
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 lines[i].begins_with("label "):
var key = lines[i].substr(len("label ")).strip_edges()
key = key.substr(0, len(key)-1)
last = add_symbol(i, 0, key)
elif lines[i].begins_with("menu "):
var key = lines[i].substr(len("menu ")).strip_edges()
key = key.substr(0, len(key)-1)
last = add_symbol(i, 0, key)
# tags
elif "#" in lines[i]:
var p = lines[i].rsplit("#", true, 1)[1]
if "#" in p:
for tag in p.split("#"):
last.tags.append(tag)
i += 1
return out

View File

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

View File

@ -1,6 +1,9 @@
tool
extends TE_ExtensionHelper
func get_tab() -> String:
return " "
func _is_commented(lines) -> bool:
for i in len(lines):
if not lines[i].strip_edges():
@ -43,7 +46,7 @@ func toggle_comment(t:TextEdit, head:String="", tail:String=""):
return [old, new]
func apply_colors(e:TextEditor, t:TextEdit):
func apply_colors(e:TE_Editor, t:TextEdit):
.apply_colors(e, t)
# strings

View File

@ -5,6 +5,8 @@
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
size = 12
use_filter = true
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -5,6 +5,8 @@
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
size = 12
use_filter = true
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -5,6 +5,8 @@
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
size = 12
use_filter = true
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -5,6 +5,8 @@
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
size = 12
use_filter = true
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

View File

@ -5,6 +5,8 @@
[ext_resource path="res://addons/text_editor/fonts/unifont-13.0.01.ttf" type="DynamicFontData" id=3]
[resource]
size = 12
use_filter = true
font_data = ExtResource( 1 )
fallback/0 = ExtResource( 3 )
fallback/1 = ExtResource( 2 )

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon_blue.png-4b6f1e67dbe59cc990b970ce70a743ca.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/text_editor/icons/icon_blue.png"
dest_files=[ "res://.import/icon_blue.png-4b6f1e67dbe59cc990b970ce70a743ca.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon_green.png-01008ffea524815bdbefdafa2b021148.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/text_editor/icons/icon_green.png"
dest_files=[ "res://.import/icon_green.png-01008ffea524815bdbefdafa2b021148.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon_red.png-68ce187cb535e9b040383f41e156a86c.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/text_editor/icons/icon_red.png"
dest_files=[ "res://.import/icon_red.png-68ce187cb535e9b040383f41e156a86c.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/icon_yellow.png-4fd0044497c78a10c7a9305f6c12f846.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/text_editor/icons/icon_yellow.png"
dest_files=[ "res://.import/icon_yellow.png-4fd0044497c78a10c7a9305f6c12f846.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

View File

@ -3,5 +3,5 @@
name="TextEditor"
description="A text editor for Godot."
author="teebar"
version="1.0"
version="1.12"
script="plugin.gd"

View File

@ -0,0 +1,3 @@
[gd_resource type="StyleBoxEmpty" format=2]
[resource]

3
default_bus_layout.tres Normal file
View File

@ -0,0 +1,3 @@
[gd_resource type="AudioBusLayout" format=2]
[resource]