Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
25d4f161cf | ||
|
4225903a92 | ||
|
dfe30486d2 | ||
|
40beef4690 | ||
|
498fb79e89 | ||
|
baeeb0a808 | ||
|
06e8d176c4 | ||
|
4f09def7ef | ||
|
96c064c2b9 | ||
|
c8ce103df4 | ||
|
5bcd5a47b0 | ||
|
f2ae9db115 | ||
|
19cb94742d | ||
|
8fd5a31874 | ||
|
ba5e9148cb | ||
|
2f3dfacf47 | ||
|
326ddf3299 | ||
|
4747dd2887 | ||
|
726e266c63 | ||
|
fad24f7a82 | ||
|
3d51620408 | ||
|
dcdab0b9c1 | ||
|
01facc81ad | ||
|
948f3db8d4 |
1
.gitignore
vendored
@ -16,3 +16,4 @@ test_files/
|
||||
.trash/
|
||||
.trash.json
|
||||
.text_editor_state.json
|
||||
word_skip_list.txt
|
||||
|
147
CHANGES.md
@ -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: `` 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:
|
||||

|
||||
- `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
@ -1,48 +1,62 @@
|
||||
# Text Editor
|
||||
Version 1.1
|
||||
Version `1.12`
|
||||
|
||||

|
||||
|
||||
***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: `` 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
After Width: | Height: | Size: 12 KiB |
BIN
README/icon.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 97 KiB |
@ -1 +0,0 @@
|
||||
|
17
addons/text_editor/TE_Console.gd
Normal 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()
|
@ -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:
|
||||
|
1214
addons/text_editor/TE_Editor.gd
Normal 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)
|
||||
|
||||
|
101
addons/text_editor/TE_FileInfoLabel.gd
Normal 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]
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
39
addons/text_editor/TE_MetaTabs.gd
Normal 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)
|
@ -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]
|
||||
|
244
addons/text_editor/TE_ScriptInfo.gd
Normal 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()
|
163
addons/text_editor/TE_Search.gd
Normal 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
|
3
addons/text_editor/TE_StopWords.gd
Normal 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"))
|
||||
|
@ -1,7 +1,7 @@
|
||||
tool
|
||||
extends TabContainer
|
||||
|
||||
onready var editor:TextEditor = owner
|
||||
onready var editor:TE_Editor = owner
|
||||
|
||||
var mouse_over:bool = false
|
||||
|
||||
|
@ -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(" "))
|
||||
|
@ -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()
|
@ -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)
|
||||
#
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
50
addons/text_editor/ext/ext_rpy.gd
Normal 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
|
2
addons/text_editor/ext/ext_txt.gd
Normal file
@ -0,0 +1,2 @@
|
||||
tool
|
||||
extends TE_ExtensionHelper
|
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
BIN
addons/text_editor/icons/icon_blue.png
Normal file
After Width: | Height: | Size: 478 B |
35
addons/text_editor/icons/icon_blue.png.import
Normal 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
|
BIN
addons/text_editor/icons/icon_green.png
Normal file
After Width: | Height: | Size: 478 B |
35
addons/text_editor/icons/icon_green.png.import
Normal 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
|
BIN
addons/text_editor/icons/icon_red.png
Normal file
After Width: | Height: | Size: 478 B |
35
addons/text_editor/icons/icon_red.png.import
Normal 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
|
BIN
addons/text_editor/icons/icon_yellow.png
Normal file
After Width: | Height: | Size: 478 B |
35
addons/text_editor/icons/icon_yellow.png.import
Normal 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
|
@ -3,5 +3,5 @@
|
||||
name="TextEditor"
|
||||
description="A text editor for Godot."
|
||||
author="teebar"
|
||||
version="1.0"
|
||||
version="1.12"
|
||||
script="plugin.gd"
|
||||
|
3
addons/text_editor/te_empty_style.tres
Normal file
@ -0,0 +1,3 @@
|
||||
[gd_resource type="StyleBoxEmpty" format=2]
|
||||
|
||||
[resource]
|
3
default_bus_layout.tres
Normal file
@ -0,0 +1,3 @@
|
||||
[gd_resource type="AudioBusLayout" format=2]
|
||||
|
||||
[resource]
|