notSpore/langtons-ant/addons/godot_vim/plugin.gd

165 lines
4.8 KiB
GDScript

@tool
extends EditorPlugin
enum Mode { NORMAL, INSERT, VISUAL, VISUAL_LINE, COMMAND }
# Used for commands like "w" "b" and "e" respectively
enum WordEdgeMode { WORD, BEGINNING, END }
const SPACES: String = " \t"
const KEYWORDS: String = ".,\"'-=+!@#$%^&*()[]{}?~/\\<>:;"
const DIGITS: String = "0123456789"
const StatusBar = preload("res://addons/godot_vim/status_bar.gd")
const CommandLine = preload("res://addons/godot_vim/command_line.gd")
const Cursor = preload("res://addons/godot_vim/cursor.gd")
var cursor: Cursor
var command_line: CommandLine
var status_bar: StatusBar
var globals: Dictionary = {}
func _enter_tree():
globals = {}
if get_code_edit() != null:
_load()
get_editor_interface().get_script_editor().connect("editor_script_changed", _script_changed)
func _script_changed(script: Script):
# Add to recent files
var path: String = script.resource_path
var marks: Dictionary = globals.get('marks', {})
for i in range(9, -1, -1):
var m: String = str(i)
var pm: String = str(i - 1)
if !marks.has(pm):
continue
marks[m] = marks[pm]
marks['-1'] = { 'file' : path, 'pos' : Vector2i(-1, 0) }
_load()
func edit_script(path: String, pos: Vector2i):
var script = load(path)
var editor_interface: EditorInterface = globals.editor_interface
if script == null:
status_bar.display_error('Could not open file "%s"' % path)
return ''
editor_interface.edit_script(script)
cursor.call_deferred('set_caret_pos', pos.y, pos.x)
func _load():
if globals == null:
globals = {}
# Cursor
if cursor != null:
cursor.queue_free()
cursor = Cursor.new()
var code_edit = get_code_edit()
code_edit.select(code_edit.get_caret_line(), code_edit.get_caret_column(), code_edit.get_caret_line(), code_edit.get_caret_column()+1)
cursor.code_edit = code_edit
cursor.globals = globals
# Command line
if command_line != null:
command_line.queue_free()
command_line = CommandLine.new()
command_line.code_edit = code_edit
cursor.command_line = command_line
command_line.cursor = cursor
command_line.globals = globals
command_line.hide()
# Status bar
if status_bar != null:
status_bar.queue_free()
status_bar = StatusBar.new()
cursor.status_bar = status_bar
command_line.status_bar = status_bar
var editor_interface = get_editor_interface()
if editor_interface == null: return
var script_editor = editor_interface.get_script_editor()
if script_editor == null: return
var script_editor_base = script_editor.get_current_editor()
if script_editor_base == null: return
globals.editor_interface = editor_interface
globals.command_line = command_line
globals.status_bar = status_bar
globals.code_edit = code_edit
globals.cursor = cursor
globals.script_editor = script_editor
globals.vim_plugin = self
script_editor_base.add_child(cursor)
script_editor_base.add_child(status_bar)
script_editor_base.add_child(command_line)
func get_code_edit():
var editor = get_editor_interface().get_script_editor().get_current_editor()
return _select(editor, ['VSplitContainer', 'CodeTextEditor', 'CodeEdit'])
func _select(obj: Node, types: Array[String]): # ???
for type in types:
for child in obj.get_children():
if child.is_class(type):
obj = child
continue
return obj
func _exit_tree():
if cursor != null:
cursor.queue_free()
if command_line != null:
command_line.queue_free()
if status_bar != null:
status_bar.queue_free()
# -------------------------------------------------------------
# ** UTIL **
# -------------------------------------------------------------
func search_regex(text_edit: TextEdit, pattern: String, from_pos: Vector2i) -> RegExMatch:
var regex: RegEx = RegEx.new()
var err: int = regex.compile(pattern)
var idx: int = pos_to_idx(text_edit, from_pos)
var res: RegExMatch = regex.search(text_edit.text, idx)
if res == null:
return regex.search(text_edit.text, 0)
return res
func search_regex_backwards(text_edit: TextEdit, pattern: String, from_pos: Vector2i) -> RegExMatch:
var regex: RegEx = RegEx.new()
var err: int = regex.compile(pattern)
var idx: int = pos_to_idx(text_edit, from_pos)
# We use pop_back() so it doesn't print an error
var res: RegExMatch = regex.search_all(text_edit.text, 0, idx).pop_back()
if res == null:
return regex.search_all(text_edit.text).pop_back()
return res
func pos_to_idx(text_edit: TextEdit, pos: Vector2i) -> int:
text_edit.select(0, 0, pos.y, pos.x)
var len: int = text_edit.get_selected_text().length()
text_edit.deselect()
return len
func idx_to_pos(text_edit: TextEdit, idx: int) -> Vector2i:
var line: int = text_edit.text .count('\n', 0, idx)
var col: int = idx - text_edit.text .rfind('\n', idx) - 1
return Vector2i(col, line)
func get_first_non_digit_idx(str: String) -> int:
if str.is_empty(): return -1
if str[0] == '0': return 0 # '0...' is an exception
for i in str.length():
if !DIGITS.contains(str[i]):
return i
return -1 # All digits