diff --git a/langtons-ant/addons/godot-vim/godot-vim.gd b/langtons-ant/addons/godot-vim/godot-vim.gd deleted file mode 100644 index be6183c..0000000 --- a/langtons-ant/addons/godot-vim/godot-vim.gd +++ /dev/null @@ -1,1685 +0,0 @@ -@tool -extends EditorPlugin - -const INF_COL : int = 99999 -const DEBUGGING : int = 0 # Change to 1 for debugging -const CODE_MACRO_PLAY_END : int = 10000 - -const BREAKERS : Dictionary = { '!': 1, '"': 1, '#': 1, '$': 1, '%': 1, '&': 1, '(': 1, ')': 1, '*': 1, '+': 1, ',': 1, '-': 1, '.': 1, '/': 1, ':': 1, ';': 1, '<': 1, '=': 1, '>': 1, '?': 1, '@': 1, '[': 1, '\\': 1, ']': 1, '^': 1, '`': 1, '\'': 1, '{': 1, '|': 1, '}': 1, '~': 1 } -const WHITESPACE: Dictionary = { ' ': 1, ' ': 1, '\n' : 1 } -const ALPHANUMERIC: Dictionary = { 'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1, 'f': 1, 'g': 1, 'h': 1, 'i': 1, 'j': 1, 'k': 1, 'l': 1, 'm': 1, 'n': 1, 'o': 1, 'p': 1, 'q': 1, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 1, 'w': 1, 'x': 1, 'y': 1, 'z': 1, 'A': 1, 'B': 1, 'C': 1, 'D': 1, 'E': 1, 'F': 1, 'G': 1, 'H': 1, 'I': 1, 'J': 1, 'K': 1, 'L': 1, 'M': 1, 'N': 1, 'O': 1, 'P': 1, 'Q': 1, 'R': 1, 'S': 1, 'T': 1, 'U': 1, 'V': 1, 'W': 1, 'X': 1, 'Y': 1, 'Z': 1, '0': 1, '1': 1, '2': 1, '3': 1, '4': 1, '5': 1, '6': 1, '7': 1, '8': 1, '9': 1, '_': 1 } -const LOWER_ALPHA: Dictionary = { 'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1, 'f': 1, 'g': 1, 'h': 1, 'i': 1, 'j': 1, 'k': 1, 'l': 1, 'm': 1, 'n': 1, 'o': 1, 'p': 1, 'q': 1, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 1, 'w': 1, 'x': 1, 'y': 1, 'z': 1 } -const SYMBOLS = { "(": ")", ")": "(", "[": "]", "]": "[", "{": "}", "}": "{", "<": ">", ">": "<", '"': '"', "'": "'" } - - -enum { - MOTION, - OPERATOR, - OPERATOR_MOTION, - ACTION, -} - - -enum Context { - NORMAL, - VISUAL, -} - - -var the_key_map : Array[Dictionary] = [ - # Move - { "keys": ["H"], "type": MOTION, "motion": "move_by_characters", "motion_args": { "forward": false } }, - { "keys": ["L"], "type": MOTION, "motion": "move_by_characters", "motion_args": { "forward": true } }, - { "keys": ["J"], "type": MOTION, "motion": "move_by_lines", "motion_args": { "forward": true, "line_wise": true } }, - { "keys": ["K"], "type": MOTION, "motion": "move_by_lines", "motion_args": { "forward": false, "line_wise": true } }, - { "keys": ["Shift+Equal"], "type": MOTION, "motion": "move_by_lines", "motion_args": { "forward": true, "to_first_char": true } }, - { "keys": ["Minus"], "type": MOTION, "motion": "move_by_lines", "motion_args": { "forward": false, "to_first_char": true } }, - { "keys": ["Shift+4"], "type": MOTION, "motion": "move_to_end_of_line", "motion_args": { "inclusive": true } }, - { "keys": ["Shift+6"], "type": MOTION, "motion": "move_to_first_non_white_space_character", "motion_args": { "change_line": false } }, - { "keys": ["Shift+Minus"], "type": MOTION, "motion": "move_to_first_non_white_space_character", "motion_args": { "change_line": true } }, - { "keys": ["0"], "type": MOTION, "motion": "move_to_start_of_line" }, - { "keys": ["Shift+H"], "type": MOTION, "motion": "move_to_top_line", "motion_args": { "to_jump_list": true } }, - { "keys": ["Shift+L"], "type": MOTION, "motion": "move_to_bottom_line", "motion_args": { "to_jump_list": true } }, - { "keys": ["Shift+M"], "type": MOTION, "motion": "move_to_middle_line", "motion_args": { "to_jump_list": true } }, - { "keys": ["G", "G"], "type": MOTION, "motion": "move_to_line_or_edge_of_document", "motion_args": { "forward": false, "to_jump_list": true } }, - { "keys": ["Shift+G"], "type": MOTION, "motion": "move_to_line_or_edge_of_document", "motion_args": { "forward": true, "to_jump_list": true } }, - { "keys": ["Ctrl+F"], "type": MOTION, "motion": "move_by_page", "motion_args": { "forward": true } }, - { "keys": ["Ctrl+B"], "type": MOTION, "motion": "move_by_page", "motion_args": { "forward": false } }, - { "keys": ["Ctrl+D"], "type": MOTION, "motion": "move_by_scroll", "motion_args": { "forward": true } }, - { "keys": ["Ctrl+U"], "type": MOTION, "motion": "move_by_scroll", "motion_args": { "forward": false } }, - { "keys": ["Shift+BackSlash"], "type": MOTION, "motion": "move_to_column" }, - { "keys": ["W"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": true, "word_end": false, "big_word": false } }, - { "keys": ["Shift+W"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": true, "word_end": false, "big_word": true } }, - { "keys": ["E"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": true, "word_end": true, "big_word": false, "inclusive": true } }, - { "keys": ["Shift+E"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": true, "word_end": true, "big_word": true, "inclusive": true } }, - { "keys": ["B"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": false, "word_end": false, "big_word": false } }, - { "keys": ["Shift+B"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": false, "word_end": false, "big_word": true } }, - { "keys": ["G", "E"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": false, "word_end": true, "big_word": false } }, - { "keys": ["G", "Shift+E"], "type": MOTION, "motion": "move_by_words", "motion_args": { "forward": false, "word_end": true, "big_word": true } }, - { "keys": ["Shift+5"], "type": MOTION, "motion": "move_to_matched_symbol", "motion_args": { "inclusive": true, "to_jump_list": true } }, - { "keys": ["F", "{char}"], "type": MOTION, "motion": "move_to_next_char", "motion_args": { "forward": true, "inclusive": true } }, - { "keys": ["Shift+F", "{char}"], "type": MOTION, "motion": "move_to_next_char", "motion_args": { "forward": false } }, - { "keys": ["T", "{char}"], "type": MOTION, "motion": "move_to_next_char", "motion_args": { "forward": true, "stop_before": true, "inclusive": true } }, - { "keys": ["Shift+T", "{char}"], "type": MOTION, "motion": "move_to_next_char", "motion_args": { "forward": false, "stop_before": true } }, - { "keys": ["Semicolon"], "type": MOTION, "motion": "repeat_last_char_search", "motion_args": {} }, - { "keys": ["Shift+8"], "type": MOTION, "motion": "find_word_under_caret", "motion_args": { "forward": true, "to_jump_list": true } }, - { "keys": ["Shift+3"], "type": MOTION, "motion": "find_word_under_caret", "motion_args": { "forward": false, "to_jump_list": true } }, - { "keys": ["N"], "type": MOTION, "motion": "find_again", "motion_args": { "forward": true, "to_jump_list": true } }, - { "keys": ["Shift+N"], "type": MOTION, "motion": "find_again", "motion_args": { "forward": false, "to_jump_list": true } }, - { "keys": ["A", "Shift+9"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"(" } }, - { "keys": ["A", "Shift+0"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"(" } }, - { "keys": ["A", "B"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"(" } }, - { "keys": ["A", "BracketLeft"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"[" } }, - { "keys": ["A", "BracketRight"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"[" } }, - { "keys": ["A", "Shift+BracketLeft"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"{" } }, - { "keys": ["A", "Shift+BracketRight"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"{" } }, - { "keys": ["A", "Shift+B"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"{" } }, - { "keys": ["A", "Apostrophe"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":"'" } }, - { "keys": ["A", 'Shift+Apostrophe'], "type": MOTION, "motion": "text_object", "motion_args": { "inner": false, "object":'"' } }, - { "keys": ["I", "Shift+9"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"(" } }, - { "keys": ["I", "Shift+0"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"(" } }, - { "keys": ["I", "B"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"(" } }, - { "keys": ["I", "BracketLeft"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"[" } }, - { "keys": ["I", "BracketRight"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"[" } }, - { "keys": ["I", "Shift+BracketLeft"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"{" } }, - { "keys": ["I", "Shift+BracketRight"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"{" } }, - { "keys": ["I", "Shift+B"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"{" } }, - { "keys": ["I", "Apostrophe"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"'" } }, - { "keys": ["I", 'Shift+Apostrophe'], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":'"' } }, - { "keys": ["I", "W"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"w" } }, - { "keys": ["D"], "type": OPERATOR, "operator": "delete" }, - { "keys": ["Shift+D"], "type": OPERATOR_MOTION, "operator": "delete", "motion": "move_to_end_of_line", "motion_args": { "inclusive": true } }, - { "keys": ["Y"], "type": OPERATOR, "operator": "yank" }, - { "keys": ["Shift+Y"], "type": OPERATOR_MOTION, "operator": "yank", "motion": "move_to_end_of_line", "motion_args": { "inclusive": true } }, - { "keys": ["C"], "type": OPERATOR, "operator": "change", "operator_args": {"normal_mode_after_visual": false}}, - { "keys": ["Shift+C"], "type": OPERATOR_MOTION, "operator": "change", "motion": "move_to_end_of_line", "motion_args": { "inclusive": true } }, - { "keys": ["X"], "type": OPERATOR_MOTION, "operator": "delete", "motion": "move_by_characters", "motion_args": { "forward": true, "one_line": true }, "context": Context.NORMAL }, - { "keys": ["S"], "type": OPERATOR_MOTION, "operator": "delete_and_enter_insert_mode", "motion": "move_by_characters", "motion_args": { "forward": true }, "context": Context.NORMAL }, - { "keys": ["X"], "type": OPERATOR, "operator": "delete", "context": Context.VISUAL }, - { "keys": ["Shift+X"], "type": OPERATOR_MOTION, "operator": "delete", "motion": "move_by_characters", "motion_args": { "forward": false } }, - { "keys": ["U"], "type": OPERATOR, "operator": "change_case", "operator_args": { "lower": true }, "context": Context.VISUAL }, - { "keys": ["Shift+U"], "type": OPERATOR, "operator": "change_case", "operator_args": { "lower": false }, "context": Context.VISUAL }, - { "keys": ["Shift+QuoteLeft"], "type": OPERATOR, "operator": "toggle_case", "operator_args": {}, "context": Context.VISUAL }, - { "keys": ["Shift+QuoteLeft"], "type": OPERATOR_MOTION, "operator": "toggle_case", "motion": "move_by_characters", "motion_args": { "forward": true }, "context": Context.NORMAL }, - { "keys": ["P"], "type": ACTION, "action": "paste", "action_args": { "after": true } }, - { "keys": ["Shift+P"], "type": ACTION, "action": "paste", "action_args": { "after": false } }, - { "keys": ["U"], "type": ACTION, "action": "undo", "action_args": {}, "context": Context.NORMAL }, - { "keys": ["Ctrl+R"], "type": ACTION, "action": "redo", "action_args": {} }, - { "keys": ["R", "{char}"], "type": ACTION, "action": "replace", "action_args": {} }, - { "keys": ["Period"], "type": ACTION, "action": "repeat_last_edit", "action_args": {} }, - { "keys": ["I"], "type": ACTION, "action": "enter_insert_mode", "action_args": { "insert_at": "inplace" }, "context": Context.NORMAL }, - { "keys": ["Shift+I"], "type": ACTION, "action": "enter_insert_mode", "action_args": { "insert_at": "bol" } }, - { "keys": ["A"], "type": ACTION, "action": "enter_insert_mode", "action_args": { "insert_at": "after" }, "context": Context.NORMAL }, - { "keys": ["Shift+A"], "type": ACTION, "action": "enter_insert_mode", "action_args": { "insert_at": "eol" } }, - { "keys": ["O"], "type": ACTION, "action": "enter_insert_mode", "action_args": { "insert_at": "new_line_below" } }, - { "keys": ["Shift+O"], "type": ACTION, "action": "enter_insert_mode", "action_args": { "insert_at": "new_line_above" } }, - { "keys": ["V"], "type": ACTION, "action": "enter_visual_mode", "action_args": { "line_wise": false } }, - { "keys": ["Shift+V"], "type": ACTION, "action": "enter_visual_mode", "action_args": { "line_wise": true } }, - { "keys": ["Slash"], "type": ACTION, "action": "search", "action_args": {} }, - { "keys": ["Ctrl+O"], "type": ACTION, "action": "jump_list_walk", "action_args": { "forward": false } }, - { "keys": ["Ctrl+I"], "type": ACTION, "action": "jump_list_walk", "action_args": { "forward": true } }, - { "keys": ["Z", "A"], "type": ACTION, "action": "toggle_folding", }, - { "keys": ["Z", "Shift+M"], "type": ACTION, "action": "fold_all", }, - { "keys": ["Z", "Shift+R"], "type": ACTION, "action": "unfold_all", }, - { "keys": ["Q", "{char}"], "type": ACTION, "action": "record_macro", "when_not": "is_recording" }, - { "keys": ["Q"], "type": ACTION, "action": "stop_record_macro", "when": "is_recording" }, - { "keys": ["Shift+2", "{char}"], "type": ACTION, "action": "play_macro", }, - { "keys": ["Shift+Comma"], "type": ACTION, "action": "indent", "action_args": { "forward" = false } }, - { "keys": ["Shift+Period"], "type": ACTION, "action": "indent", "action_args": { "forward" = true } }, - { "keys": ["Shift+J"], "type": ACTION, "action": "join_lines", "action_args": {} }, - { "keys": ["M", "{char}"], "type": ACTION, "action": "set_bookmark", "action_args": {} }, - { "keys": ["Apostrophe", "{char}"], "type": MOTION, "motion": "go_to_bookmark", "motion_args": {} }, - { "keys": ["Shift+K"], "type": ACTION, "action": "go_to_doc", "context": Context.NORMAL}, - -] - - -# The list of command keys we handle (other command keys will be handled by Godot) -var command_keys_white_list : Dictionary = { - "Escape": 1, - "Enter": 1, - # "Ctrl+F": 1, # Uncomment if you would like move-forward by page function instead of search on slash - "Ctrl+B": 1, - "Ctrl+U": 1, - "Ctrl+D": 1, - #"Ctrl+O": 1, # Prefer to use as Alt+arrow aliases - #"Ctrl+I": 1, - "Ctrl+R": 1 -} - - -var editor_interface : EditorInterface -var the_vim := Vim.new() -var the_ed := EditorAdaptor.new(the_vim) # The current editor adaptor -var the_dispatcher := CommandDispatcher.new(the_key_map) # The command dispatcher - - -func _enter_tree() -> void: - editor_interface = get_editor_interface() - - var script_editor = editor_interface.get_script_editor() - script_editor.editor_script_changed.connect(on_script_changed) - script_editor.script_close.connect(on_script_closed) - on_script_changed(script_editor.get_current_script()) - - var settings = editor_interface.get_editor_settings() - settings.settings_changed.connect(on_settings_changed) - on_settings_changed() - - var find_bar = find_first_node_of_type(script_editor, 'FindReplaceBar') - var find_bar_line_edit : LineEdit = find_first_node_of_type(find_bar, 'LineEdit') - find_bar_line_edit.text_changed.connect(on_search_text_changed) - - -func _input(event) -> void: - var key = event as InputEventKey - - # Don't process when not a key action - if key == null or !key.is_pressed() or not is_instance_valid(the_ed) or not the_ed.has_focus(): - return - - if key.get_keycode_with_modifiers() == KEY_NONE and key.unicode == CODE_MACRO_PLAY_END: - the_vim.macro_manager.on_macro_finished(the_ed) - get_viewport().set_input_as_handled() - return - - # Check to not block some reserved keys (we only handle unicode keys and the white list) - var key_code = key.as_text_keycode() - if DEBUGGING: - print("Key: %s Buffer: %s" % [key_code, the_vim.current.input_state.key_codes()]) - - # We only process keys in the white list or it is ASCII char or SHIFT+ASCII char - if key.get_keycode_with_modifiers() & (~KEY_MASK_SHIFT) > 128 and key_code not in command_keys_white_list: - return - - if the_dispatcher.dispatch(key, the_vim, the_ed): - get_viewport().set_input_as_handled() - - -func on_script_changed(s: Script) -> void: - the_vim.set_current_session(s, the_ed) - - var script_editor = editor_interface.get_script_editor() - var scrpit_editor_base := script_editor.get_current_editor() - if scrpit_editor_base: - var code_editor := scrpit_editor_base.get_base_editor() as CodeEdit - the_ed.set_code_editor(code_editor) - the_ed.set_block_caret(true) - the_ed.set_caret_blink(false) - - if not code_editor.is_connected("caret_changed", on_caret_changed): - code_editor.caret_changed.connect(on_caret_changed) - if not code_editor.is_connected("lines_edited_from", on_lines_edited_from): - code_editor.lines_edited_from.connect(on_lines_edited_from) - - -func on_script_closed(s: Script) -> void: - the_vim.remove_session(s) - - -func on_settings_changed() -> void: - var settings := editor_interface.get_editor_settings() - the_ed.notify_settings_changed(settings) - - -func on_caret_changed()-> void: - the_ed.set_block_caret(not the_vim.current.insert_mode) - - -func on_lines_edited_from(from: int, to: int) -> void: - the_vim.current.jump_list.on_lines_edited(from, to) - the_vim.current.text_change_number += 1 - the_vim.current.bookmark_manager.on_lines_edited(from, to) - - -func on_search_text_changed(new_search_text: String) -> void: - the_vim.search_buffer = new_search_text - - -static func find_first_node_of_type(p: Node, type: String) -> Node: - if p.get_class() == type: - return p - for c in p.get_children(): - var t := find_first_node_of_type(c, type) - if t: - return t - return null - - -class Command: - - ### MOTIONS - - static func move_by_characters(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var one_line = args.get('one_line', false) - var col : int = cur.column + args.repeat * (1 if args.forward else -1) - var line := cur.line - if col > ed.last_column(line): - if one_line: - col = ed.last_column(line) + 1 - else: - line += 1 - col = 0 - elif col < 0: - if one_line: - col = 0 - else: - line -= 1 - col = ed.last_column(line) - # if vim.current.visual_mode: - # col -= 1 - return Position.new(line, col) - - static func move_by_scroll(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var count = ed.get_visible_line_count(ed.first_visible_line(), ed.last_visible_line()) - return Position.new(ed.next_unfolded_line(cur.line, count / 2, args.forward), cur.column) - - static func move_by_page(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var count = ed.get_visible_line_count(ed.first_visible_line(), ed.last_visible_line()) - return Position.new(ed.next_unfolded_line(cur.line, count, args.forward), cur.column) - - static func move_to_column(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - return Position.new(cur.line, args.repeat - 1) - - static func move_by_lines(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - # Depending what our last motion was, we may want to do different things. - # If our last motion was moving vertically, we want to preserve the column from our - # last horizontal move. If our last motion was going to the end of a line, - # moving vertically we should go to the end of the line, etc. - var col : int = cur.column - match vim.current.last_motion: - "move_by_lines", "move_by_scroll", "move_by_page", "move_to_end_of_line", "move_to_column": - col = vim.current.last_h_pos - _: - vim.current.last_h_pos = col - - var line = ed.next_unfolded_line(cur.line, args.repeat, args.forward) - - if args.get("to_first_char", false): - col = ed.find_first_non_white_space_character(line) - - return Position.new(line, col) - - static func move_to_first_non_white_space_character(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var new_line = cur.line - if args.change_line: - new_line += args.repeat - 1 - var i := ed.find_first_non_white_space_character(new_line) - return Position.new(new_line, i) - - static func move_to_start_of_line(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - return Position.new(cur.line, 0) - - static func move_to_end_of_line(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var line = cur.line - if args.repeat > 1: - line = ed.next_unfolded_line(line, args.repeat - 1) - vim.current.last_h_pos = INF_COL - return Position.new(line, ed.last_column(line)) - - static func move_to_top_line(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - return Position.new(ed.first_visible_line(), cur.column) - - static func move_to_bottom_line(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - return Position.new(ed.last_visible_line(), cur.column) - - static func move_to_middle_line(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var first := ed.first_visible_line() - var count = ed.get_visible_line_count(first, ed.last_visible_line()) - return Position.new(ed.next_unfolded_line(first, count / 2), cur.column) - - static func move_to_line_or_edge_of_document(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var line = ed.last_line() if args.forward else ed.first_line() - if args.repeat_is_explicit: - line = args.repeat + ed.first_line() - 1 - return Position.new(line, ed.find_first_non_white_space_character(line)) - - static func move_by_words(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var start_line := cur.line - var start_col := cur.column - var start_pos := cur.duplicate() - - # If we are beyond line end, move it to line end - if start_col > 0 and start_col == ed.last_column(start_line) + 1: - cur = Position.new(start_line, start_col-1) - - var forward : bool = args.forward - var word_end : bool = args.word_end - var big_word : bool = args.big_word - var repeat : int = args.repeat - var empty_line_is_word := not (forward and word_end) # For 'e', empty lines are not considered words - var one_line := not vim.current.input_state.operator.is_empty() # if there is an operator pending, let it not beyond the line end each time - - if (forward and !word_end) or (not forward and word_end): # w or ge - repeat += 1 - - var words : Array[TextRange] = [] - for i in range(repeat): - var word = _find_word(cur, ed, forward, big_word, empty_line_is_word, one_line) - if word != null: - words.append(word) - cur = Position.new(word.from.line, word.to.column-1 if forward else word.from.column) - else: # eof - words.append(TextRange.new(ed.last_pos(), ed.last_pos()) if forward else TextRange.zero) - break - - var short_circuit : bool = len(words) != repeat - var first_word := words[0] - var last_word : TextRange = words.pop_back() - if forward and not word_end: # w - if vim.current.input_state.operator == "change": # cw need special treatment to not cover whitespaces - if not short_circuit: - last_word = words.pop_back() - return last_word.to - if not short_circuit and not start_pos.equals(first_word.from): - last_word = words.pop_back() # We did not start in the middle of a word. Discard the extra word at the end. - return last_word.from - elif forward and word_end: # e - return last_word.to.left() - elif not forward and word_end: # ge - if not short_circuit and not start_pos.equals(first_word.to.left()): - last_word = words.pop_back() # We did not start in the middle of a word. Discard the extra word at the end. - return last_word.to.left() - else: # b - return last_word.from - - static func move_to_matched_symbol(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - # Get the symbol to match - var symbol := ed.find_forward(cur.line, cur.column, func(c): return c.char in "()[]{}", true) - if symbol == null: # No symbol found in this line after or under caret - return null - - var counter_part : String = SYMBOLS[symbol.char] - - # Two attemps to find the symbol pair: from line start or doc start - for from in [Position.new(symbol.line, 0), Position.new(0, 0)]: - var parser = GDScriptParser.new(ed, from) - if not parser.parse_until(symbol): - continue - - if symbol.char in ")]}": - parser.stack.reverse() - for p in parser.stack: - if p.char == counter_part: - return p - continue - else: - parser.parse_one_char() - return parser.find_matching() - return null - - static func move_to_next_char(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - vim.last_char_search = args - - var forward : bool = args.forward - var stop_before : bool = args.get("stop_before", false) - var to_find = args.selected_character - var repeat : int = args.repeat - - var old_pos := cur.duplicate() - for ch in ed.chars(cur.line, cur.column + (1 if forward else -1), forward, true): - if ch.char == to_find: - repeat -= 1 - if repeat == 0: - return old_pos if stop_before else Position.new(ch.line, ch.column) - old_pos = Position.new(ch.line, ch.column) - return null - - static func repeat_last_char_search(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var last_char_search := vim.last_char_search - if last_char_search.is_empty(): - return null - args.forward = last_char_search.forward - args.selected_character = last_char_search.selected_character - args.stop_before = last_char_search.get("stop_before", false) - args.inclusive = last_char_search.get("inclusive", false) - return move_to_next_char(cur, args, ed, vim) - - static func expand_to_line(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - return Position.new(cur.line + args.repeat - 1, INF_COL) - - static func find_word_under_caret(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var forward : bool = args.forward - var range := ed.get_word_at_pos(cur.line, cur.column) - var text := ed.range_text(range) - var pos := ed.search(text, cur.line, cur.column + (1 if forward else -1), false, true, forward) - vim.last_search_command = "*" if forward else "#" - vim.search_buffer = text - return pos - - static func find_again(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var forward : bool = args.forward - forward = forward == (vim.last_search_command != "#") - var case_sensitive := vim.last_search_command in "*#" - var whole_word := vim.last_search_command in "*#" - cur = cur.next(ed) if forward else cur.prev(ed) - return ed.search(vim.search_buffer, cur.line, cur.column, case_sensitive, whole_word, forward) - - static func text_object(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Variant: - var inner : bool = args.inner - var obj : String = args.object - - if obj == "w" and inner: - return ed.get_word_at_pos(cur.line, cur.column) - - if obj in "([{\"'": - var counter_part : String = SYMBOLS[obj] - for from in [Position.new(cur.line, 0), Position.new(0, 0)]: # Two attemps: from line beginning doc beginning - var parser = GDScriptParser.new(ed, from) - if not parser.parse_until(cur): - continue - - var range = TextRange.zero - if parser.stack_top_char == obj: - range.from = parser.stack.back() - range.to = parser.find_matching() - elif ed.char_at(cur.line, cur.column) == obj: - parser.parse_one_char() - range.from = parser.pos - range.to = parser.find_matching() - else: - continue - - if range.from == null or range.to == null: - continue - - if inner: - range.from = range.from.next(ed) - else: - range.to = range.to.next(ed) - return range - - return null - - -### OPERATORS - - static func delete(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text := ed.selected_text() - vim.register.set_text(text, args.get("line_wise", false)) - ed.delete_selection() - var line := ed.curr_line() - var col := ed.curr_column() - if col > ed.last_column(line): # If after deletion we are beyond the end, move left - ed.set_curr_column(ed.last_column(line)) - - static func yank(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text := ed.selected_text() - ed.deselect() - vim.register.set_text(text, args.get("line_wise", false)) - - static func change(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text := ed.selected_text() - vim.register.set_text(text, args.get("line_wise", false)) - - ed.delete_selection() - vim.current.enter_insert_mode() - - static func change_case(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var lower_case : bool = args.get("lower", false) - var text := ed.selected_text() - ed.replace_selection(text.to_lower() if lower_case else text.to_upper()) - - static func toggle_case(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text := ed.selected_text() - var s := PackedStringArray() - for c in text: - s.append(c.to_lower() if c == c.to_upper() else c.to_upper()) - ed.replace_selection(''.join(s)) - - static func delete_and_enter_insert_mode(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text := ed.selected_text() - vim.register.set_text(text, args.get("line_wise", false)) - ed.delete_selection() - var line := ed.curr_line() - var col := ed.curr_column() - if col > ed.last_column(line): # If after deletion we are beyond the end, move left - ed.set_curr_column(ed.last_column(line)) - vim.current.enter_insert_mode(); - - ### ACTIONS - - static func paste(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var after : bool = args.after - var line_wise := vim.register.line_wise - var clipboard_text := vim.register.text - - var text : String = "" - for i in range(args.repeat): - text += clipboard_text - - var line := ed.curr_line() - var col := ed.curr_column() - - if line_wise: - if after: - text = "\n" + text.substr(0, len(text)-1) - col = len(ed.line_text(line)) - else: - col = 0 - else: - col += 1 if after else 0 - - ed.set_curr_column(col) - ed.insert_text(text) - - static func undo(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - for i in range(args.repeat): - ed.undo() - ed.deselect() - - static func redo(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - for i in range(args.repeat): - ed.redo() - ed.deselect() - - static func replace(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var to_replace = args.selected_character - var line := ed.curr_line() - var col := ed.curr_column() - ed.select(line, col, line, col+1) - ed.replace_selection(to_replace) - - static func enter_insert_mode(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var insert_at : String = args.insert_at - var line = ed.curr_line() - var col = ed.curr_column() - - vim.current.enter_insert_mode() - - match insert_at: - "inplace": - pass - "after": - ed.set_curr_column(col + 1) - "bol": - ed.set_curr_column(ed.find_first_non_white_space_character(line)) - "eol": - ed.set_curr_column(INF_COL) - "new_line_below": - ed.set_curr_column(INF_COL) - ed.simulate_press(KEY_ENTER) - "new_line_above": - ed.set_curr_column(0) - if line == ed.first_line(): - ed.insert_text("\n") - ed.jump_to(0, 0) - else: - ed.jump_to(line - 1, INF_COL) - ed.simulate_press(KEY_ENTER) - - static func enter_visual_mode(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var line_wise : bool = args.get('line_wise', false) - vim.current.enter_visual_mode(line_wise) - - static func search(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - if OS.get_name() == "macOS": - ed.simulate_press(KEY_F, 0, false, false, false, true) - else: - ed.simulate_press(KEY_F, 0, true, false, false, false) - vim.last_search_command = "/" - - static func jump_list_walk(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var offset : int = args.repeat * (1 if args.forward else -1) - var pos : Position = vim.current.jump_list.move(offset) - if pos != null: - if not args.forward: - vim.current.jump_list.set_next(ed.curr_position()) - ed.jump_to(pos.line, pos.column) - - static func toggle_folding(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - ed.toggle_folding(ed.curr_line()) - - static func fold_all(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - ed.fold_all() - - static func unfold_all(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - ed.unfold_all() - - static func repeat_last_edit(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var repeat : int = args.repeat - vim.macro_manager.play_macro(repeat, ".", ed) - - static func record_macro(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var name = args.selected_character - if name in ALPHANUMERIC: - vim.macro_manager.start_record_macro(name) - - static func stop_record_macro(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - vim.macro_manager.stop_record_macro() - - static func play_macro(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var name = args.selected_character - var repeat : int = args.repeat - if name in ALPHANUMERIC: - vim.macro_manager.play_macro(repeat, name, ed) - - static func is_recording(ed: EditorAdaptor, vim: Vim) -> bool: - return vim.macro_manager.is_recording() - - static func indent(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var repeat : int = args.repeat - var forward : bool = args.get("forward", false) - var range = ed.selection() - - if not range.is_single_line() and range.to.column == 0: # Don't select the last empty line - ed.select(range.from.line, range.from.column, range.to.line-1, INF_COL) - - ed.begin_complex_operation() - for i in range(repeat): - if forward: - ed.indent() - else: - ed.unindent() - ed.end_complex_operation() - - static func join_lines(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - if vim.current.normal_mode: - var line := ed.curr_line() - ed.select(line, 0, line + args.repeat, INF_COL) - - var range := ed.selection() - ed.select(range.from.line, 0, range.to.line, INF_COL) - var s := PackedStringArray() - s.append(ed.line_text(range.from.line)) - for line in range(range.from.line + 1, range.to.line + 1): - s.append(ed.line_text(line).lstrip(' \t\n')) - ed.replace_selection(' '.join(s)) - - static func set_bookmark(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var name = args.selected_character - if name in LOWER_ALPHA: - vim.current.bookmark_manager.set_bookmark(name, ed.curr_line()) - - static func go_to_bookmark(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var name = args.selected_character - var line := vim.current.bookmark_manager.get_bookmark(name) - if line < 0: - return null - return Position.new(line, 0) - - static func go_to_doc(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text: String = ed.line_text(ed.curr_line()) - var begin = text.rfind("res://", ed.curr_column()) - var symbol = text[begin-1] - var end = text.find(symbol, begin) - var search_word: String - if ed.curr_column() <= end: - search_word = text.substr(begin, end - begin) - else: - search_word = ed.code_editor.get_word_under_caret() - ed.code_editor.symbol_lookup.emit(search_word, ed.curr_line(), ed.curr_column()) - - - ### HELPER FUNCTIONS - - ## Returns the boundaries of the next word. If the cursor in the middle of the word, then returns the boundaries of the current word, starting at the cursor. - ## If the cursor is at the start/end of a word, and we are going forward/backward, respectively, find the boundaries of the next word. - static func _find_word(cur: Position, ed: EditorAdaptor, forward: bool, big_word: bool, empty_line_is_word: bool, one_line: bool) -> TextRange: - var char_tests := [ func(c): return c in ALPHANUMERIC or c in BREAKERS ] if big_word else [ func(c): return c in ALPHANUMERIC, func(c): return c in BREAKERS ] - - for p in ed.chars(cur.line, cur.column, forward): - if one_line and p.char == '\n': # If we only allow search in one line and we met the line end - return TextRange.from_num3(p.line, p.column, INF_COL) - - if p.line != cur.line and empty_line_is_word and p.line_text.strip_edges() == '': - return TextRange.from_num3(p.line, 0, 0) - - for char_test in char_tests: - if char_test.call(p.char): - var word_start := p.column - var word_end := word_start - for q in ed.chars(p.line, p.column, forward, true): # Advance to end of word. - if not char_test.call(q.char): - break - word_end = q.column - - if p.line == cur.line and word_start == cur.column and word_end == word_start: - continue # We started at the end of a word. Find the next one. - else: - return TextRange.from_num3(p.line, min(word_start, word_end), max(word_start + 1, word_end + 1)) - return null - - -class Position: - var line: int - var column: int - - static var zero :Position: - get: - return Position.new(0, 0) - - func _init(l: int, c: int): - line = l - column = c - - func _to_string() -> String: - return "(%s, %s)" % [line, column] - - func equals(other: Position) -> bool: - return line == other.line and column == other.column - - func compares_to(other: Position) -> int: - if line < other.line: return -1 - if line > other.line: return 1 - if column < other.column: return -1 - if column > other.column: return 1 - return 0 - - func duplicate() -> Position: return Position.new(line, column) - func up() -> Position: return Position.new(line-1, column) - func down() -> Position: return Position.new(line+1, column) - func left() -> Position: return Position.new(line, column-1) - func right() -> Position: return Position.new(line, column+1) - func next(ed: EditorAdaptor) -> Position: return ed.offset_pos(self, 1) - func prev(ed: EditorAdaptor) -> Position: return ed.offset_pos(self, -1) - - -class TextRange: - var from: Position - var to: Position - - static var zero : TextRange: - get: - return TextRange.new(Position.zero, Position.zero) - - static func from_num4(from_line: int, from_column: int, to_line: int, to_column: int): - return TextRange.new(Position.new(from_line, from_column), Position.new(to_line, to_column)) - - static func from_num3(line: int, from_column: int, to_column: int): - return from_num4(line, from_column, line, to_column) - - func _init(f: Position, t: Position): - from = f - to = t - - func _to_string() -> String: - return "[%s - %s]" % [from, to] - - func is_single_line() -> bool: - return from.line == to.line - - func is_empty() -> bool: - return from.line == to.line and from.column == to.column - - -class CharPos extends Position: - var line_text : String - - var char: String: - get: - return line_text[column] if column < len(line_text) else '\n' - - func _init(line_text: String, line: int, col: int): - super(line, col) - self.line_text = line_text - - -class JumpList: - var buffer: Array[Position] - var pointer: int = 0 - - func _init(capacity: int = 20): - buffer = [] - buffer.resize(capacity) - - func add(old_pos: Position, new_pos: Position) -> void: - var current : Position = buffer[pointer] - if current == null or not current.equals(old_pos): - pointer = (pointer + 1) % len(buffer) - buffer[pointer] = old_pos - pointer = (pointer + 1) % len(buffer) - buffer[pointer] = new_pos - - func set_next(pos: Position) -> void: - buffer[(pointer + 1) % len(buffer)] = pos # This overrides next forward position (TODO: an insert might be better) - - func move(offset: int) -> Position: - var t := (pointer + offset) % len(buffer) - var r : Position = buffer[t] - if r != null: - pointer = t - return r - - func on_lines_edited(from: int, to: int) -> void: - for pos in buffer: - if pos != null and pos.line > from: # Unfortunately we don't know which column changed - pos.line += to - from - - -class InputState: - var prefix_repeat: String - var motion_repeat: String - var operator: String - var operator_args: Dictionary - var buffer: Array[InputEventKey] = [] - - func push_key(key: InputEventKey) -> void: - buffer.append(key) - - func push_repeat_digit(d: String) -> void: - if operator.is_empty(): - prefix_repeat += d - else: - motion_repeat += d - - func get_repeat() -> int: - var repeat : int = 0 - if prefix_repeat: - repeat = max(repeat, 1) * int(prefix_repeat) - if motion_repeat: - repeat = max(repeat, 1) * int(motion_repeat) - return repeat - - func key_codes() -> Array[String]: - var r : Array[String] = [] - for k in buffer: - r.append(k.as_text_keycode()) - return r - - func clear() -> void: - prefix_repeat = "" - motion_repeat = "" - operator = "" - buffer.clear() - - -class GDScriptParser: - const open_symbol := { "(": ")", "[": "]", "{": "}", "'": "'", '"': '"' } - const close_symbol := { ")": "(", "]": "[", "}": "{", } - - var stack : Array[CharPos] - var in_comment := false - var escape_count := 0 - var valid: bool = true - var eof : bool = false - var pos: Position - - var stack_top_char : String: - get: - return "" if stack.is_empty() else stack.back().char - - var _it: CharIterator - var _ed : EditorAdaptor - - func _init(ed: EditorAdaptor, from: Position): - _ed = ed - _it = ed.chars(from.line, from.column) - if not _it._iter_init(null): - eof = true - - func parse_until(to: Position) -> bool: - while valid and not eof: - parse_one_char() - if _it.line == to.line and _it.column == to.column: - break - return valid and not eof - - - func find_matching() -> Position: - var depth := len(stack) - while valid and not eof: - parse_one_char() - if len(stack) < depth: - return pos - return null - - func parse_one_char() -> String: # ChatGPT got credit here - if eof or not valid: - return "" - - var p := _it._iter_get(null) - pos = p - - if not _it._iter_next(null): - eof = true - - var char := p.char - var top: String = '' if stack.is_empty() else stack.back().char - if top in "'\"": # in string - if char == top and escape_count % 2 == 0: - stack.pop_back() - escape_count = 0 - return char - escape_count = escape_count + 1 if char == "\\" else 0 - elif in_comment: - if char == "\n": - in_comment = false - elif char == "#": - in_comment = true - elif char in open_symbol: - stack.append(p) - return char - elif char in close_symbol: - if top == close_symbol[char]: - stack.pop_back() - return char - else: - valid = false - return "" - - -class Register: - var line_wise : bool = false - var text : String: - get: - return DisplayServer.clipboard_get() - - func set_text(value: String, line_wise: bool) -> void: - self.line_wise = line_wise - DisplayServer.clipboard_set(value) - - -class BookmarkManager: - var bookmarks : Dictionary - - func on_lines_edited(from: int, to: int) -> void: - for b in bookmarks: - var line : int = bookmarks[b] - if line > from: - bookmarks[b] += to - from - - func set_bookmark(name: String, line: int) -> void: - bookmarks[name] = line - - func get_bookmark(name: String) -> int: - return bookmarks.get(name, -1) - - -class CommandMatchResult: - var full: Array[Dictionary] = [] - var partial: Array[Dictionary] = [] - - -class VimSession: - var ed : EditorAdaptor - - ## Mode insert_mode | visual_mode | visual_line - ## Insert true | false | false - ## Normal false | false | false - ## Visual false | true | false - ## Visual Line false | true | true - var insert_mode : bool = false - var visual_mode : bool = false - var visual_line : bool = false - - var normal_mode: bool: - get: - return not insert_mode and not visual_mode - - ## Pending input - var input_state := InputState.new() - - ## The last motion occurred - var last_motion : String - - ## When using jk for navigation, if you move from a longer line to a shorter line, the cursor may clip to the end of the shorter line. - ## If j is pressed again and cursor goes to the next line, the cursor should go back to its horizontal position on the longer - ## line if it can. This is to keep track of the horizontal position. - var last_h_pos : int = 0 - - ## How many times text are changed - var text_change_number : int - - ## List of positions for C-I and C-O - var jump_list := JumpList.new() - - ## The bookmark manager of the session - var bookmark_manager := BookmarkManager.new() - - ## The start position of visual mode - var visual_start_pos := Position.zero - - func enter_normal_mode() -> void: - if insert_mode: - ed.end_complex_operation() # Wrap up the undo operation when we get out of insert mode - - insert_mode = false - visual_mode = false - visual_line = false - ed.set_block_caret(true) - - func enter_insert_mode() -> void: - insert_mode = true - visual_mode = false - visual_line = false - ed.set_block_caret(false) - ed.begin_complex_operation() - - func enter_visual_mode(line_wise: bool) -> void: - insert_mode = false - visual_mode = true - visual_line = line_wise - - - visual_start_pos = ed.curr_position().right() - if line_wise: - ed.select(visual_start_pos.line + 1, 0, visual_start_pos.line, 0) - else: - ed.select_by_pos2(visual_start_pos.left(), visual_start_pos) - - -class Macro: - var keys : Array[InputEventKey] = [] - var enabled := false - - func _to_string() -> String: - var s := PackedStringArray() - for key in keys: - s.append(key.as_text_keycode()) - return ",".join(s) - - func play(ed: EditorAdaptor) -> void: - for key in keys: - ed.simulate_press_key(key) - ed.simulate_press(KEY_ESCAPE) - - -class MacroManager: - var vim : Vim - var macros : Dictionary = {} - var recording_name : String - var playing_names : Array[String] = [] - var command_buffer: Array[InputEventKey] - - func _init(vim: Vim): - self.vim = vim - - func start_record_macro(name: String): - print('Recording macro "%s"...' % name ) - macros[name] = Macro.new() - recording_name = name - - func stop_record_macro() -> void: - print('Stop recording macro "%s"' % recording_name) - macros[recording_name].enabled = true - recording_name = "" - - func is_recording() -> bool: - return recording_name != "" - - func play_macro(n: int, name: String, ed: EditorAdaptor) -> void: - var macro : Macro = macros.get(name, null) - if (macro == null or not macro.enabled): - return - if name in playing_names: - return # to avoid recursion - - playing_names.append(name) - if len(playing_names) == 1: - ed.begin_complex_operation() - - if DEBUGGING: - print("Playing macro %s: %s" % [name, macro]) - - for i in range(n): - macro.play(ed) - - ed.simulate_press(KEY_NONE, CODE_MACRO_PLAY_END) # This special marks the end of macro play - - func on_macro_finished(ed: EditorAdaptor): - var name : String = playing_names.pop_back() - if playing_names.is_empty(): - ed.end_complex_operation() - - func push_key(key: InputEventKey) -> void: - command_buffer.append(key) - if recording_name: - macros[recording_name].keys.append(key) - - func on_command_processed(command: Dictionary, is_edit: bool) -> void: - if is_edit and command.get('action', '') != "repeat_last_edit": - var macro := Macro.new() - macro.keys = command_buffer.duplicate() - macro.enabled = true - macros["."] = macro - command_buffer.clear() - - -## Global VIM state; has multiple sessions -class Vim: - var sessions : Dictionary - var current: VimSession - var register: Register = Register.new() - var last_char_search: Dictionary = {} # { selected_character, stop_before, forward, inclusive } - var last_search_command: String - var search_buffer: String - var macro_manager := MacroManager.new(self) - - func set_current_session(s: Script, ed: EditorAdaptor): - var session : VimSession = sessions.get(s) - if not session: - session = VimSession.new() - session.ed = ed - sessions[s] = session - current = session - - func remove_session(s: Script): - sessions.erase(s) - - -class CharIterator: - var ed : EditorAdaptor - var line : int - var column : int - var forward : bool - var one_line : bool - var line_text : String - - func _init(ed: EditorAdaptor, line: int, col: int, forward: bool, one_line: bool): - self.ed = ed - self.line = line - self.column = col - self.forward = forward - self.one_line = one_line - - func _ensure_column_valid() -> bool: - if column < 0 or column > len(line_text): - line += 1 if forward else -1 - if one_line or line < 0 or line > ed.last_line(): - return false - line_text = ed.line_text(line) - column = 0 if forward else len(line_text) - return true - - func _iter_init(arg) -> bool: - if line < 0 or line > ed.last_line(): - return false - line_text = ed.line_text(line) - return _ensure_column_valid() - - func _iter_next(arg) -> bool: - column += 1 if forward else -1 - return _ensure_column_valid() - - func _iter_get(arg) -> CharPos: - return CharPos.new(line_text, line, column) - - -class EditorAdaptor: - var code_editor: CodeEdit - var tab_width : int = 4 - var complex_ops : int = 0 - var vim: Vim - const MARGIN_LINES_UP: int = 6 - const MARGIN_LINES_DOWN: int = 4 - - func _init(v: Vim): - vim = v - - func set_code_editor(new_editor: CodeEdit) -> void: - self.code_editor = new_editor - - func notify_settings_changed(settings: EditorSettings) -> void: - tab_width = settings.get_setting("text_editor/behavior/indent/size") as int - - func curr_position() -> Position: - return Position.new(code_editor.get_caret_line(), code_editor.get_caret_column()) - - func curr_line() -> int: - return code_editor.get_caret_line() - - func curr_column() -> int: - var col = code_editor.get_caret_column() - if vim.current.visual_mode: - col -= 1 - return col - - func set_curr_column(col: int) -> void: - code_editor.set_caret_column(col) - - func jump_to(line: int, col: int) -> void: - code_editor.unfold_line(line) - update_margin(line) - code_editor.set_caret_line(line) - code_editor.set_caret_column(col) - - func update_margin(line: int) -> void: - var folded_lines_above = code_editor.get_next_visible_line_offset_from(line, -MARGIN_LINES_UP-1) - MARGIN_LINES_UP-1 - var folded_lines_below = code_editor.get_next_visible_line_offset_from(line, +MARGIN_LINES_DOWN+1) - MARGIN_LINES_DOWN-1 - if line < first_visible_line() + MARGIN_LINES_UP + folded_lines_above: - var lines_to_move = first_visible_line() - (first_visible_line() + MARGIN_LINES_UP) + line - folded_lines_above - code_editor.set_line_as_first_visible(max(0, lines_to_move)) - elif line > last_visible_line() - MARGIN_LINES_DOWN - folded_lines_below: - var lines_to_move = last_visible_line() - (last_visible_line() - MARGIN_LINES_DOWN) + line + folded_lines_below - code_editor.set_line_as_last_visible(min(last_line(), lines_to_move)) - - func first_line() -> int: - return 0 - - func last_line() -> int : - return code_editor.get_line_count() - 1 - - func first_visible_line() -> int: - return code_editor.get_first_visible_line() - - func last_visible_line() -> int: - return code_editor.get_last_full_visible_line() - - func get_visible_line_count(from_line: int, to_line: int) -> int: - return code_editor.get_visible_line_count_in_range(from_line, to_line) - - func next_unfolded_line(line: int, offset: int = 1, forward: bool = true) -> int: - var step : int = 1 if forward else -1 - if line + step > last_line() or line + step < first_line(): - return line - var count := code_editor.get_next_visible_line_offset_from(line + step, offset * step) - return line + count * (1 if forward else -1) - - func last_column(line: int = -1) -> int: - if line == -1: - line = curr_line() - return len(line_text(line)) - 1 - - func last_pos() -> Position: - var line = last_line() - return Position.new(line, last_column(line)) - - func line_text(line: int) -> String: - return code_editor.get_line(line) - - func range_text(range: TextRange) -> String: - var s := PackedStringArray() - for p in chars(range.from.line, range.from.column): - if p.equals(range.to): - break - s.append(p.char) - return "".join(s) - - func char_at(line: int, col: int) -> String: - var s := line_text(line) - return s[col] if col >= 0 and col < len(s) else '' - - func set_block_caret(block: bool) -> void: - if block: - if curr_column() == last_column() + 1: - code_editor.caret_type = TextEdit.CARET_TYPE_LINE - code_editor.add_theme_constant_override("caret_width", 8) - else: - code_editor.caret_type = TextEdit.CARET_TYPE_BLOCK - code_editor.add_theme_constant_override("caret_width", 1) - else: - code_editor.add_theme_constant_override("caret_width", 1) - code_editor.caret_type = TextEdit.CARET_TYPE_LINE - - func set_caret_blink(blink: bool) -> void: - code_editor.caret_blink = blink; - - func deselect() -> void: - code_editor.deselect() - - func select_range(r: TextRange) -> void: - select(r.from.line, r.from.column, r.to.line, r.to.column) - - func select_by_pos2(from: Position, to: Position) -> void: - select(from.line, from.column, to.line, to.column) - - func select(from_line: int, from_col: int, to_line: int, to_col: int) -> void: - if to_line > last_line(): # If we try to select pass the last line, select till the last char - to_line = last_line() - to_col = INF_COL - - code_editor.select(from_line, from_col, to_line, to_col) - - func delete_selection() -> void: - code_editor.delete_selection() - - func selected_text() -> String: - return code_editor.get_selected_text() - - func selection() -> TextRange: - var from : Position - var to : Position - if code_editor.has_selection(): - from = Position.new(code_editor.get_selection_from_line(), code_editor.get_selection_from_column()) - to = Position.new(code_editor.get_selection_to_line(), code_editor.get_selection_to_column()) - else: - from = Position.new(code_editor.get_caret_line(), code_editor.get_caret_column()) - to = Position.new(code_editor.get_caret_line(), code_editor.get_caret_column()) - return TextRange.new(from, to) - - func replace_selection(text: String) -> void: - var col := curr_column() - begin_complex_operation() - delete_selection() - insert_text(text) - end_complex_operation() - set_curr_column(col) - - func toggle_folding(line_or_above: int) -> void: - if code_editor.is_line_folded(line_or_above): - code_editor.unfold_line(line_or_above) - else: - while line_or_above >= 0: - if code_editor.can_fold_line(line_or_above): - code_editor.fold_line(line_or_above) - break - line_or_above -= 1 - - func fold_all() -> void: - code_editor.fold_all_lines() - - func unfold_all() -> void: - code_editor.unfold_all_lines() - - func insert_text(text: String) -> void: - code_editor.insert_text_at_caret(text) - - func offset_pos(pos: Position, offset: int) -> Position: - var count : int = abs(offset) + 1 - for p in chars(pos.line, pos.column, offset > 0): - count -= 1 - if count == 0: - return p - return null - - func undo() -> void: - code_editor.undo() - - func redo() -> void: - code_editor.redo() - - func indent() -> void: - code_editor.indent_lines() - - func unindent() -> void: - code_editor.unindent_lines() - - func simulate_press_key(key: InputEventKey): - for pressed in [true, false]: - var key2 := key.duplicate() - key2.pressed = pressed - Input.parse_input_event(key2) - - func simulate_press(keycode: Key, unicode: int=0, ctrl=false, alt=false, shift=false, meta=false) -> void: - var k = InputEventKey.new() - if ctrl: - k.ctrl_pressed = true - if shift: - k.shift_pressed = true - if alt: - k.alt_pressed = true - if meta: - k.meta_pressed = true - k.keycode = keycode - k.key_label = keycode - k.unicode = unicode - simulate_press_key(k) - - func begin_complex_operation() -> void: - complex_ops += 1 - if complex_ops == 1: - if DEBUGGING: - print("Complex operation begins") - code_editor.begin_complex_operation() - - func end_complex_operation() -> void: - complex_ops -= 1 - if complex_ops == 0: - if DEBUGGING: - print("Complex operation ends") - code_editor.end_complex_operation() - - ## Return the index of the first non whtie space character in string - func find_first_non_white_space_character(line: int) -> int: - var s := line_text(line) - return len(s) - len(s.lstrip(" \t\r\n")) - - ## Return the next (or previous) char from current position and update current position according. Return "" if not more char available - func chars(line: int, col: int, forward: bool = true, one_line: bool = false) -> CharIterator: - return CharIterator.new(self, line, col, forward, one_line) - - func find_forward(line: int, col: int, condition: Callable, one_line: bool = false) -> CharPos: - for p in chars(line, col, true, one_line): - if condition.call(p): - return p - return null - - func find_backforward(line: int, col: int, condition: Callable, one_line: bool = false) -> CharPos: - for p in chars(line, col, false, one_line): - if condition.call(p): - return p - return null - - func get_word_at_pos(line: int, col: int) -> TextRange: - var end := find_forward(line, col, func(p): return p.char not in ALPHANUMERIC, true); - var start := find_backforward(line, col, func(p): return p.char not in ALPHANUMERIC, true); - return TextRange.new(start.right(), end) - - func search(text: String, line: int, col: int, match_case: bool, whole_word: bool, forward: bool) -> Position: - var flags : int = 0 - if match_case: flags |= TextEdit.SEARCH_MATCH_CASE - if whole_word: flags |= TextEdit.SEARCH_WHOLE_WORDS - if not forward: flags |= TextEdit.SEARCH_BACKWARDS - var result = code_editor.search(text, flags, line, col) - if result.x < 0 or result. y < 0: - return null - - code_editor.set_search_text(text) - return Position.new(result.y, result.x) - - func has_focus() -> bool: - return weakref(code_editor).get_ref() and code_editor.has_focus() - - -class CommandDispatcher: - var key_map : Array[Dictionary] - - func _init(km: Array[Dictionary]): - self.key_map = km - - func dispatch(key: InputEventKey, vim: Vim, ed: EditorAdaptor) -> bool: - var key_code := key.as_text_keycode() - var input_state := vim.current.input_state - - vim.macro_manager.push_key(key) - - if key_code == "Escape": - input_state.clear() - vim.macro_manager.on_command_processed({}, vim.current.insert_mode) # From insert mode to normal mode, this marks the end of an edit command - vim.current.enter_normal_mode() - return false # Let godot get the Esc as well to dispose code completion pops, etc - - if vim.current.insert_mode: # We are in insert mode - return false # Let Godot CodeEdit handle it - - if key_code not in ["Shift", "Ctrl", "Alt", "Escape"]: # Don't add these to input buffer - # Handle digits - if key_code.is_valid_int() and input_state.buffer.is_empty(): - input_state.push_repeat_digit(key_code) - if input_state.get_repeat() > 0: # No more handding if it is only repeat digit - return true - - # Save key to buffer - input_state.push_key(key) - - # Match the command - var context = Context.VISUAL if vim.current.visual_mode else Context.NORMAL - var result = match_commands(context, vim.current.input_state, ed, vim) - if not result.full.is_empty(): - var command = result.full[0] - var change_num := vim.current.text_change_number - if process_command(command, ed, vim): - input_state.clear() - if vim.current.normal_mode: - vim.macro_manager.on_command_processed(command, vim.current.text_change_number > change_num) # Notify macro manager about the finished command - elif result.partial.is_empty(): - input_state.clear() - - return true # We handled the input - - func match_commands(context: Context, input_state: InputState, ed: EditorAdaptor, vim: Vim) -> CommandMatchResult: - # Partial matches are not applied. They inform the key handler - # that the current key sequence is a subsequence of a valid key - # sequence, so that the key buffer is not cleared. - var result := CommandMatchResult.new() - var pressed := input_state.key_codes() - - for command in key_map: - if not is_command_available(command, context, ed, vim): - continue - - var mapped : Array = command.keys - if mapped[-1] == "{char}": - if pressed.slice(0, -1) == mapped.slice(0, -1) and len(pressed) == len(mapped): - result.full.append(command) - elif mapped.slice(0, len(pressed)-1) == pressed.slice(0, -1): - result.partial.append(command) - else: - continue - else: - if pressed == mapped: - result.full.append(command) - elif mapped.slice(0, len(pressed)) == pressed: - result.partial.append(command) - else: - continue - - return result - - func is_command_available(command: Dictionary, context: Context, ed: EditorAdaptor, vim: Vim) -> bool: - if command.get("context") not in [null, context]: - return false - - var when : String = command.get("when", '') - if when and not Callable(Command, when).call(ed, vim): - return false - - var when_not: String = command.get("when_not", '') - if when_not and Callable(Command, when_not).call(ed, vim): - return false - - return true - - func process_command(command: Dictionary, ed: EditorAdaptor, vim: Vim) -> bool: - var vim_session := vim.current - var input_state := vim_session.input_state - var start := Position.new(ed.curr_line(), ed.curr_column()) - - # If there is an operator pending, then we do need a motion or operator (for linewise operation) - if not input_state.operator.is_empty() and (command.type != MOTION and command.type != OPERATOR): - return false - - if command.type == ACTION: - var action_args = command.get("action_args", {}) - if command.keys[-1] == "{char}": - action_args.selected_character = char(input_state.buffer.back().unicode) - process_action(command.action, action_args, ed, vim) - return true - elif command.type == MOTION or command.type == OPERATOR_MOTION: - var motion_args = command.get("motion_args", {}) - - if command.type == OPERATOR_MOTION: - var operator_args = command.get("operator_args", {}) - input_state.operator = command.operator - input_state.operator_args = operator_args - - if command.keys[-1] == "{char}": - motion_args.selected_character = char(input_state.buffer.back().unicode) - - var new_pos = process_motion(command.motion, motion_args, ed, vim) - if new_pos == null: - return true - - if vim_session.visual_mode: # Visual mode - start = vim_session.visual_start_pos.left() - if new_pos is TextRange: - start = new_pos.from # In some cases (text object), we need to override the start position - new_pos = new_pos.to - if vim_session.visual_line: - var start_line = start.line - var new_line = new_pos.line - var new_col = 0 - - if new_line == ed.last_line(): - new_col = ed.last_column(new_pos.line) + 1 - elif start.line >= new_pos.line: - start_line += 1 - - ed.select(start_line, 0, new_line, new_col) - else: # visual mode - ed.select_by_pos2(start, new_pos.right()) - elif input_state.operator.is_empty(): # Normal mode motion only - ed.jump_to(new_pos.line, new_pos.column) - else: # Normal mode operator motion - if new_pos is TextRange: - start = new_pos.from # In some cases (text object), we need to override the start position - new_pos = new_pos.to - var inclusive : bool = motion_args.get("inclusive", false) - ed.select_by_pos2(start, new_pos.right() if inclusive else new_pos) - process_operator(input_state.operator, input_state.operator_args, ed, vim) - return true - elif command.type == OPERATOR: - var operator_args = command.get("operator_args", {}) - if vim.current.visual_mode: - operator_args.line_wise = vim.current.visual_line - process_operator(command.operator, operator_args, ed, vim) - if operator_args.get("normal_mode_after_visual", true): - vim.current.enter_normal_mode() - return true - elif input_state.operator.is_empty(): # We are not fully done yet, need to wait for the motion - input_state.operator = command.operator - input_state.operator_args = operator_args - input_state.buffer.clear() - return false - else: - if input_state.operator == command.operator: # Line wise operation - operator_args.line_wise = true - var new_pos : Position = process_motion("expand_to_line", {}, ed, vim) - if new_pos.compares_to(start) > 0: - ed.select(start.line, 0, new_pos.line + 1, 0) - else: - ed.select(new_pos.line, 0, start.line + 1, 0) - process_operator(command.operator, operator_args, ed, vim) - return true - - return false - - func process_action(action: String, action_args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - if DEBUGGING: - print(" Action: %s %s" % [action, action_args]) - - action_args.repeat = max(1, vim.current.input_state.get_repeat()) - Callable(Command, action).call(action_args, ed, vim) - - if vim.current.visual_mode and action != "enter_visual_mode": - vim.current.enter_normal_mode() - - func process_operator(operator: String, operator_args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - if DEBUGGING: - print(" Operator %s %s on %s" % [operator, operator_args, ed.selection()]) - - # Perform operation - Callable(Command, operator).call(operator_args, ed, vim) - - - func process_motion(motion: String, motion_args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Variant: - # Get current position - var cur := Position.new(ed.curr_line(), ed.curr_column()) - - # Prepare motion args - var user_repeat = vim.current.input_state.get_repeat() - if user_repeat > 0: - motion_args.repeat = user_repeat - motion_args.repeat_is_explicit = true - else: - motion_args.repeat = 1 - motion_args.repeat_is_explicit = false - - # Calculate new position - var result = Callable(Command, motion).call(cur, motion_args, ed, vim) - if result is Position: - var new_pos : Position = result - if new_pos.column == INF_COL: # INF_COL means the last column - new_pos.column = ed.last_column(new_pos.line) - - if motion_args.get('to_jump_list', false): - vim.current.jump_list.add(cur, new_pos) - - # Save last motion - vim.current.last_motion = motion - - if DEBUGGING: - print(" Motion: %s %s to %s" % [motion, motion_args, result]) - - return result diff --git a/langtons-ant/addons/godot-vim/godot-vim.gd.uid b/langtons-ant/addons/godot-vim/godot-vim.gd.uid deleted file mode 100644 index f02d030..0000000 --- a/langtons-ant/addons/godot-vim/godot-vim.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://t4imnbu2n5vw diff --git a/langtons-ant/addons/godot-vim/icon.svg b/langtons-ant/addons/godot-vim/icon.svg deleted file mode 100644 index 9831b04..0000000 --- a/langtons-ant/addons/godot-vim/icon.svg +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/langtons-ant/addons/godot-vim/plugin.cfg b/langtons-ant/addons/godot-vim/plugin.cfg deleted file mode 100644 index da5f41c..0000000 --- a/langtons-ant/addons/godot-vim/plugin.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[plugin] - -name="godot-vim" -description="VIM bindings for godot4" -author="Josh N" -version="0.3" -script="godot-vim.gd" diff --git a/langtons-ant/addons/godot_vim/command_line.gd b/langtons-ant/addons/godot_vim/command_line.gd deleted file mode 100644 index a94cbac..0000000 --- a/langtons-ant/addons/godot_vim/command_line.gd +++ /dev/null @@ -1,86 +0,0 @@ -extends LineEdit - -const Cursor = preload("res://addons/godot_vim/cursor.gd") -const StatusBar = preload("res://addons/godot_vim/status_bar.gd") -const Constants = preload("res://addons/godot_vim/constants.gd") -const Dispatcher = preload("res://addons/godot_vim/dispatcher.gd") -const Mode = Constants.Mode - -const Marks = preload("res://addons/godot_vim/commands/marks.gd") -const Goto = preload("res://addons/godot_vim/commands/goto.gd") -const Find = preload("res://addons/godot_vim/commands/find.gd") - -var code_edit: CodeEdit -var cursor: Cursor -var status_bar: StatusBar -var globals: Dictionary -var dispatcher: Dispatcher - -var is_paused: bool = false -var search_pattern: String = '' - -func _ready(): - dispatcher = Dispatcher.new() - dispatcher.globals = globals - placeholder_text = "Enter command..." - show() - - text_submitted.connect(_on_text_submitted) - text_changed.connect(_on_text_changed) - editable = true - -func set_command(cmd: String): - text = cmd - caret_column = text.length() - -func _on_text_changed(cmd: String): - if !cmd.begins_with('/'): return - var pattern: String = cmd.substr(1) - var rmatch: RegExMatch = globals.vim_plugin.search_regex( - code_edit, - pattern, - cursor.get_caret_pos() + Vector2i.RIGHT - ) - if rmatch == null: - code_edit.remove_secondary_carets() - return - var pos: Vector2i = globals.vim_plugin.idx_to_pos(code_edit, rmatch.get_start()) - if code_edit.get_caret_count() < 2: - code_edit.add_caret(pos.y, pos.x) - code_edit.select(pos.y, pos.x, pos.y, pos.x + rmatch.get_string().length(), 1) - code_edit.scroll_vertical = code_edit.get_scroll_pos_for_line(pos.y) - -func handle_command(cmd: String): - if cmd.begins_with('/'): - var find = Find.new() - find.execute(globals, cmd) - return - - if cmd.trim_prefix(':').is_valid_int(): - var goto = Goto.new() - goto.execute(globals, cmd.trim_prefix(':')) - return - - if dispatcher.dispatch(cmd) == OK: - set_paused(true) - return - - status_bar.display_error('Unknown command: "%s"' % [ cmd.trim_prefix(':') ]) - set_paused(true) - -func close(): - hide() - clear() - set_paused(false) - -func set_paused(paused: bool): - is_paused = paused - text = "Press ENTER to continue" if is_paused else "" - editable = !is_paused - -func _on_text_submitted(new_text: String): - if is_paused: - cursor.set_mode(Mode.NORMAL) - status_bar.main_label.text = '' - return - handle_command(new_text) diff --git a/langtons-ant/addons/godot_vim/command_line.gd.uid b/langtons-ant/addons/godot_vim/command_line.gd.uid deleted file mode 100644 index b415696..0000000 --- a/langtons-ant/addons/godot_vim/command_line.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://lk5et75ba1pr diff --git a/langtons-ant/addons/godot_vim/commands/find.gd b/langtons-ant/addons/godot_vim/commands/find.gd deleted file mode 100644 index 9a9cd8a..0000000 --- a/langtons-ant/addons/godot_vim/commands/find.gd +++ /dev/null @@ -1,16 +0,0 @@ -const Constants = preload("res://addons/godot_vim/constants.gd") -const Mode = Constants.Mode - -func execute(api : Dictionary, args: String): - api.command_line.search_pattern = args.substr(1) - var rmatch: RegExMatch = api.vim_plugin.search_regex( - api.code_edit, - api.command_line.search_pattern, - api.cursor.get_caret_pos() + Vector2i.RIGHT - ) - if rmatch != null: - var pos: Vector2i = api.vim_plugin.idx_to_pos(api.code_edit, rmatch.get_start()) - api.cursor.set_caret_pos(pos.y, pos.x) - else: - api.status_bar.display_error('Pattern not found: "%s"' % [api.command_line.search_pattern]) - api.cursor.set_mode(Mode.NORMAL) diff --git a/langtons-ant/addons/godot_vim/commands/find.gd.uid b/langtons-ant/addons/godot_vim/commands/find.gd.uid deleted file mode 100644 index 69a291a..0000000 --- a/langtons-ant/addons/godot_vim/commands/find.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dp7ck72w5b66n diff --git a/langtons-ant/addons/godot_vim/commands/goto.gd b/langtons-ant/addons/godot_vim/commands/goto.gd deleted file mode 100644 index 22a62a1..0000000 --- a/langtons-ant/addons/godot_vim/commands/goto.gd +++ /dev/null @@ -1,6 +0,0 @@ -const Constants = preload("res://addons/godot_vim/constants.gd") -const Mode = Constants.Mode - -func execute(api, args): - api.cursor.set_caret_pos(args.to_int(), 0) - api.cursor.set_mode(Mode.NORMAL) diff --git a/langtons-ant/addons/godot_vim/commands/goto.gd.uid b/langtons-ant/addons/godot_vim/commands/goto.gd.uid deleted file mode 100644 index baeea78..0000000 --- a/langtons-ant/addons/godot_vim/commands/goto.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cu4md7kr2qrp diff --git a/langtons-ant/addons/godot_vim/commands/marks.gd b/langtons-ant/addons/godot_vim/commands/marks.gd deleted file mode 100644 index c6d142a..0000000 --- a/langtons-ant/addons/godot_vim/commands/marks.gd +++ /dev/null @@ -1,29 +0,0 @@ -const Contants = preload("res://addons/godot_vim/constants.gd") -const StatusBar = preload("res://addons/godot_vim/status_bar.gd") -const Mode = Contants.Mode - -func execute(api, _args): - var marks: Dictionary = api.get('marks', {}) - if marks.is_empty(): - api.status_bar.display_error("No marks set") - api.cursor.set_mode(Mode.NORMAL) - return - - var display_mark = func(key: String, m: Dictionary) -> String: - var pos: Vector2i = m.get('pos', Vector2i()) - var file: String = m.get('file', '') - return "\n%s\t\t%s \t%s \t\t %s" % [key, pos.y, pos.x, file] - - var text: String = "[color=%s]List of all marks[/color]\nmark\tline\tcol \t file" % StatusBar.SPECIAL_COLOR - for key in marks.keys(): - var unicode: int = key.unicode_at(0) - if (unicode < 65 or unicode > 90) and (unicode < 97 or unicode > 122): - continue - text += display_mark.call(key, marks[key]) - for key in marks.keys(): - var unicode: int = key.unicode_at(0) - if (unicode >= 65 and unicode <= 90) or (unicode >= 97 and unicode <= 122) or key == "-1": - continue - text += display_mark.call(key, marks[key]) - - api.status_bar.display_text(text, Control.TEXT_DIRECTION_LTR) diff --git a/langtons-ant/addons/godot_vim/commands/marks.gd.uid b/langtons-ant/addons/godot_vim/commands/marks.gd.uid deleted file mode 100644 index b138ae5..0000000 --- a/langtons-ant/addons/godot_vim/commands/marks.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cf1an2q01i3ix diff --git a/langtons-ant/addons/godot_vim/constants.gd b/langtons-ant/addons/godot_vim/constants.gd deleted file mode 100644 index c94d72a..0000000 --- a/langtons-ant/addons/godot_vim/constants.gd +++ /dev/null @@ -1,8 +0,0 @@ -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" diff --git a/langtons-ant/addons/godot_vim/constants.gd.uid b/langtons-ant/addons/godot_vim/constants.gd.uid deleted file mode 100644 index 86c1875..0000000 --- a/langtons-ant/addons/godot_vim/constants.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://d2ro7q1ec5gk0 diff --git a/langtons-ant/addons/godot_vim/cursor.gd b/langtons-ant/addons/godot_vim/cursor.gd deleted file mode 100644 index 80647ad..0000000 --- a/langtons-ant/addons/godot_vim/cursor.gd +++ /dev/null @@ -1,722 +0,0 @@ -extends Control - -const CommandLine = preload("res://addons/godot_vim/command_line.gd") -const StatusBar = preload("res://addons/godot_vim/status_bar.gd") -const Constants = preload("res://addons/godot_vim/constants.gd") -const Mode = Constants.Mode -const WordEdgeMode = Constants.WordEdgeMode -const KEYWORDS = Constants.KEYWORDS -const SPACES = Constants.SPACES - -var code_edit: CodeEdit -var command_line: CommandLine -var status_bar: StatusBar - -var mode: Mode = Mode.NORMAL -var caret: Vector2 -var input_stream: String = "" -var selection_from: Vector2i = Vector2i() # For visual modes -var selection_to: Vector2i = Vector2i() # For visual modes -var globals: Dictionary = {} - -func _init(): - set_focus_mode(FOCUS_ALL) - -func _ready(): - code_edit.connect("focus_entered", focus_entered) - code_edit.connect("caret_changed", cursor_changed) - call_deferred('set_mode', Mode.NORMAL) - -func cursor_changed(): - draw_cursor() - -func focus_entered(): - if mode == Mode.NORMAL: - code_edit.release_focus() - self.grab_focus() - - -func reset_normal(): - code_edit.cancel_code_completion() - input_stream = '' - set_mode(Mode.NORMAL) - selection_from = Vector2i.ZERO - selection_to = Vector2i.ZERO - set_column(code_edit.get_caret_column()) - return - - -func back_to_normal_mode(event, m): - var old_caret_pos = code_edit.get_caret_column() - if Input.is_key_pressed(KEY_ESCAPE): - if m == Mode.INSERT: - handle_input_stream('l') - reset_normal() - return 1 - if m == Mode.INSERT: - var old_time = Time.get_ticks_msec() - if Input.is_key_label_pressed(KEY_J): - old_caret_pos = code_edit.get_caret_column() - if Time.get_ticks_msec() - old_time < 700 and Input.is_key_label_pressed(KEY_K): - code_edit.backspace() - code_edit.cancel_code_completion() - reset_normal() - handle_input_stream('l') - return 1 - return 0 - - -func _input(event): - if back_to_normal_mode(event, mode): return - draw_cursor() - code_edit.cancel_code_completion() - if !has_focus(): return - if !event is InputEventKey: return - if !event.pressed: return - if mode == Mode.INSERT or mode == Mode.COMMAND: return - - if event.keycode == KEY_ESCAPE: - input_stream = '' - return - - - var ch: String - if !event is InputEventMouseMotion and !event is InputEventMouseButton: - ch = char(event.unicode) - - if Input.is_key_pressed(KEY_ENTER): - ch = '' - if Input.is_key_pressed(KEY_TAB): - ch = '' - if Input.is_key_pressed(KEY_CTRL): - if OS.is_keycode_unicode(event.keycode): - var c: String = char(event.keycode) - if !Input.is_key_pressed(KEY_SHIFT): - c = c.to_lower() - ch = '' % c - - input_stream += ch - status_bar.display_text(input_stream) - - var s: int = globals.vim_plugin.get_first_non_digit_idx(input_stream) - if s == -1: return # All digits - - var cmd: String = input_stream.substr(s) - var count: int = maxi( input_stream.left(s).to_int(), 1 ) - for i in count: - input_stream = handle_input_stream(cmd) - - -func handle_input_stream(stream: String) -> String: - # BEHOLD, THE IF STATEMENT HELL!!! MUAHAHAHAHa - if stream == 'h': - move_column(-1) - return '' - if stream == 'j': - move_line(+1) - return '' - if stream == 'k': - move_line(-1) - return '' - if stream == 'l': - move_column(+1) - return '' - if stream.to_lower().begins_with('w'): - var p: Vector2i = get_word_edge_pos(get_line(), get_column(), '' if stream[0] == 'W' else KEYWORDS, WordEdgeMode.WORD) - set_caret_pos(p.y, p.x) - return '' - if stream.to_lower().begins_with('e'): - var p: Vector2i = get_word_edge_pos(get_line(), get_column(), '' if stream[0] == 'E' else KEYWORDS, WordEdgeMode.END) - set_caret_pos(p.y, p.x) - return '' - if stream.to_lower().begins_with('b'): - var p: Vector2i = get_word_edge_pos(get_line(), get_column(), '' if stream[0] == 'B' else KEYWORDS, WordEdgeMode.BEGINNING) - set_caret_pos(p.y, p.x) - return '' - - if stream.to_lower() .begins_with('f') or stream.to_lower() .begins_with('t'): - if stream.length() == 1: return stream - - var char: String = stream[1] # TODO check for , and - globals.last_search = stream.left(2) # First 2 in case it's longer - var col: int = find_char_motion(get_line(), get_column(), stream[0], char) - if col >= 0: - set_column(col) - return '' - if stream.begins_with(';') and globals.has('last_search'): - var cmd: String = globals.last_search[0] - var col: int = find_char_motion(get_line(), get_column(), cmd, globals.last_search[1]) - if col >= 0: - set_column(col) - return '' - if stream.begins_with(',') and globals.has('last_search'): - var cmd: String = globals.last_search[0] - cmd = cmd.to_upper() if is_lowercase(cmd) else cmd.to_lower() - var col: int = find_char_motion(get_line(), get_column(), cmd, globals.last_search[1]) - if col >= 0: - set_column(col) - return '' - - if mode == Mode.VISUAL: # TODO make it work for visual line too - var range: Array = calc_double_motion_region(selection_to, stream) - if range.size() == 1: return stream - if range.size() == 2: - selection_from = range[0] - selection_to = range[1] - update_visual_selection() - - if stream.begins_with('J') and mode == Mode.NORMAL: - code_edit.begin_complex_operation() - code_edit.select( get_line(), get_line_length(), get_line()+1, code_edit.get_first_non_whitespace_column(get_line()+1) ) - code_edit.delete_selection() - code_edit.deselect() - code_edit.insert_text_at_caret(' ') - code_edit.end_complex_operation() - globals.last_command = stream - return '' - if stream.begins_with('d'): - if is_mode_visual(mode): - DisplayServer.clipboard_set( '\r' + code_edit.get_selected_text() ) - code_edit.delete_selection() - move_line(+1) - set_mode(Mode.NORMAL) - return '' - - if stream.begins_with('dd') and mode == Mode.NORMAL: - code_edit.select( get_line()-1, get_line_length(get_line()-1), get_line(), get_line_length() ) - DisplayServer.clipboard_set( '\r' + code_edit.get_selected_text() ) - code_edit.delete_selection() - move_line(+1) - globals.last_command = stream - return '' - - var range: Array = calc_double_motion_region(get_caret_pos(), stream, 1) - if range.size() == 0: return '' - if range.size() == 1: return stream - if range.size() == 2: - code_edit.select(range[0].y, range[0].x, range[1].y, range[1].x + 1) - code_edit.cut() - globals.last_command = stream - return '' - - if mode == Mode.NORMAL and stream.begins_with('D'): - code_edit.select( get_line(), code_edit.get_caret_column(), get_line(), get_line_length() ) - code_edit.cut() - globals.last_command = stream - return '' - if stream.begins_with('p'): - code_edit.begin_complex_operation() - if is_mode_visual(mode): - code_edit.delete_selection() - if DisplayServer.clipboard_get().begins_with('\r\n'): - set_column(get_line_length()) - else: - move_column(+1) - code_edit.deselect() - code_edit.paste() - move_column(-1) - code_edit.end_complex_operation() - set_mode(Mode.NORMAL) - globals.last_command = stream - return '' - if stream.begins_with('P'): - status_bar.display_error("Unimplemented command: P") - return '' - if stream.begins_with('$'): - set_column(get_line_length()) - return '' - if stream.begins_with('^'): - set_column( code_edit.get_first_non_whitespace_column(get_line()) ) - return '' - if stream == 'G': - set_line(code_edit.get_line_count()) - return '' - if stream.begins_with('g'): - if stream.begins_with('gg'): - set_line(0) - return '' - - if stream.begins_with('gc') and is_mode_visual(mode): - code_edit.begin_complex_operation() - for line in range( min(selection_from.y, selection_to.y), max(selection_from.y, selection_to.y)+1 ): - toggle_comment(line) - code_edit.end_complex_operation() - set_mode(Mode.NORMAL) - return '' - if stream.begins_with('gcc') and mode == Mode.NORMAL: - toggle_comment(get_line()) - globals.last_command = stream - return '' - return stream - - if stream == '0': - set_column(0) - return '' - if stream == 'i' and mode == Mode.NORMAL: - set_mode(Mode.INSERT) - return '' - if stream == 'a' and mode == Mode.NORMAL: - set_mode(Mode.INSERT) - move_column(+1) - return '' - if stream == 'I' and mode == Mode.NORMAL: - set_column(code_edit.get_first_non_whitespace_column(get_line())) - set_mode(Mode.INSERT) - return '' - if stream.begins_with('A') and mode == Mode.NORMAL: - set_mode(Mode.INSERT) - set_column(get_line_length()) - return '' - if stream == 'v': - set_mode(Mode.VISUAL) - return '' - if stream == 'V': - set_mode(Mode.VISUAL_LINE) - return '' - if stream.begins_with('o'): - if is_mode_visual(mode): - var tmp: Vector2i = selection_from - selection_from = selection_to - selection_to = tmp - return '' - - var ind: int = code_edit.get_first_non_whitespace_column(get_line()) - if code_edit.get_line(get_line()).ends_with(':'): - ind += 1 - var line: int = code_edit.get_caret_line() - code_edit.insert_line_at(line + int(line < code_edit.get_line_count() - 1), "\t".repeat(ind)) - move_line(+1) - set_column(ind) - set_mode(Mode.INSERT) - globals.last_command = stream - return '' - if stream.begins_with('O') and mode == Mode.NORMAL: - var ind: int = code_edit.get_first_non_whitespace_column(get_line()) - code_edit.insert_line_at(get_line(), "\t".repeat(ind)) - move_line(-1) - set_column(ind) - set_mode(Mode.INSERT) - globals.last_command = stream - return '' - - if stream == 'x': - code_edit.copy() - code_edit.delete_selection() - globals.last_command = stream - return '' - if stream.begins_with('s'): - code_edit.cut() - set_mode(Mode.INSERT) - return '' - if stream == 'u': - code_edit.undo() - set_mode(Mode.NORMAL) - return '' - if stream.begins_with(''): - code_edit.redo() - return '' - if stream.begins_with('r') and mode == Mode.NORMAL: - if stream.length() < 2: return stream - code_edit.begin_complex_operation() - code_edit.delete_selection() - var ch: String = stream[1] - if stream.substr(1).begins_with(''): - ch = '\n' - elif stream.substr(1).begins_with(''): - ch = '\t' - code_edit.insert_text_at_caret(ch) - move_column(-1) - code_edit.end_complex_operation() - globals.last_command = stream - return '' - if stream.begins_with('y'): - if is_mode_visual(mode): - code_edit.copy() - set_mode(Mode.NORMAL) - return '' - - if stream.length() == 1: return stream - if stream.begins_with('yy') and mode == Mode.NORMAL: - code_edit.select(code_edit.get_caret_line(), 0, code_edit.get_caret_line(), get_line_length()) - DisplayServer.clipboard_set( '\r\n' + code_edit.get_selected_text() ) - move_column(0) - code_edit.deselect() - - var range: Array = calc_double_motion_region(get_caret_pos(), stream, 1) - if range.size() == 0: return '' - if range.size() == 1: return stream - if range.size() == 2: - code_edit.select(range[0].y, range[0].x, range[1].y, range[1].x + 1) - code_edit.copy() - code_edit.deselect() - return '' - - if stream == '.': - if globals.has('last_command'): - handle_input_stream(globals.last_command) - call_deferred(&'set_mode', Mode.NORMAL) - return '' - - if stream.begins_with(':') and mode == Mode.NORMAL: # Could make this work with visual too ig - set_mode(Mode.COMMAND) - command_line.set_command(':') - return '' - if stream.begins_with('/') and mode == Mode.NORMAL: - set_mode(Mode.COMMAND) - command_line.set_command('/') - return '' - if stream.begins_with('n'): - var rmatch: RegExMatch = globals.vim_plugin.search_regex( - code_edit, - command_line.search_pattern, - get_caret_pos() + Vector2i.RIGHT - ) - if rmatch != null: - var pos: Vector2i = globals.vim_plugin.idx_to_pos(code_edit,rmatch.get_start()) - set_caret_pos(pos.y, pos.x) - return '' - if stream.begins_with('N'): - var rmatch: RegExMatch = globals.vim_plugin.search_regex_backwards( - code_edit, - command_line.search_pattern, - get_caret_pos() + Vector2i.LEFT - ) - if rmatch != null: - var pos: Vector2i = globals.vim_plugin.idx_to_pos(code_edit,rmatch.get_start()) - set_caret_pos(pos.y, pos.x) - return '' - - if stream.begins_with('c'): - if mode == Mode.VISUAL: - code_edit.cut() - set_mode(Mode.INSERT) - return '' - - if stream.begins_with('cc') and mode == Mode.NORMAL: - code_edit.begin_complex_operation() - var l: int = get_line() - var ind: int = code_edit.get_first_non_whitespace_column(l) - code_edit.select( l-1, get_line_length(l-1), l, get_line_length(l) ) - code_edit.cut() - code_edit.insert_line_at(get_line()+1, "\t".repeat(ind)) - code_edit.end_complex_operation() - move_line(+1) - set_mode(Mode.INSERT) - globals.last_command = stream - return '' - - var range: Array = calc_double_motion_region(get_caret_pos(), stream, 1) - if range.size() == 0: return '' - if range.size() == 1: return stream - if range.size() == 2: - code_edit.select(range[0].y, range[0].x, range[1].y, range[1].x + 1) - code_edit.cut() - set_mode(Mode.INSERT) - globals.last_command = stream - return '' - if mode == Mode.NORMAL and stream.begins_with('C'): - code_edit.select( get_line(), code_edit.get_caret_column(), get_line(), get_line_length() ) - code_edit.cut() - set_mode(Mode.INSERT) - globals.last_command = stream - return '' - if stream.begins_with('z'): - if stream.begins_with('zz') and mode == Mode.NORMAL: - code_edit.center_viewport_to_caret() - return '' - return stream - - if stream.begins_with('>'): - if is_mode_visual(mode) and stream.length() == 1: - code_edit.indent_lines() - return '' - if stream.length() == 1: return stream - if stream.begins_with('>>') and mode == Mode.NORMAL: - code_edit.indent_lines() - globals.last_command = stream - return '' - if stream.begins_with('<'): - if is_mode_visual(mode) and stream.length() == 1: - code_edit.unindent_lines() - return '' - if stream.length() == 1: return stream - if stream.begins_with('<<') and mode == Mode.NORMAL: - code_edit.unindent_lines() - globals.last_command = stream - return '' - - if stream.begins_with('}'): - var para_edge: Vector2i = get_paragraph_edge_pos( get_line(), 1 ) - set_caret_pos(para_edge.y, para_edge.x) - return '' - - if stream.begins_with('{'): - var para_edge: Vector2i = get_paragraph_edge_pos( get_line(), -1 ) - set_caret_pos(para_edge.y, para_edge.x) - return '' - - if stream.begins_with('m') and mode == Mode.NORMAL: - if stream.length() < 2: return stream - if !globals.has('marks'): globals.marks = {} - var m: String = stream[1] - var unicode: int = m.unicode_at(0) - if (unicode < 65 or unicode > 90) and (unicode < 97 or unicode > 122): - status_bar.display_error('Marks must be between a-z or A-Z') - return '' - globals.marks[m] = { - 'file' : globals.script_editor.get_current_script().resource_path, - 'pos' : Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) - } - status_bar.display_text('Mark "%s" set' % m, TEXT_DIRECTION_LTR) - return '' - if stream.begins_with('`'): - if stream.length() < 2: return stream - if !globals.has('marks'): globals.marks = {} - if !globals.marks.has(stream[1]): - status_bar.display_error('Mark "%s" not set' % [ stream[1] ]) - return '' - var mark: Dictionary = globals.marks[stream[1]] - globals.vim_plugin.edit_script(mark.file, mark.pos) - return '' - return '' - - -# Mostly used for commands like "w", "b", and "e" -# delims is the keywords / characters used as delimiters. Usually, it's the constant KEYWORDS -func get_word_edge_pos(from_line: int, from_col: int, delims: String, mode: WordEdgeMode) -> Vector2i: - var search_dir: int = -1 if mode == WordEdgeMode.BEGINNING else 1 - var char_offset: int = 1 if mode == WordEdgeMode.END else -1 - var line: int = from_line - var col: int = from_col + search_dir - var text: String = get_line_text(line) - - while line >= 0 and line < code_edit.get_line_count(): - while col >= 0 and col < text.length(): - var char: String = text[col] - if SPACES.contains(char): - col += search_dir - continue - # Please don't question this lmao. It just works, alight? - var other_char: String = ' ' if col == (text.length()-1) * int(char_offset > 0) else text[col + char_offset] - - if SPACES.contains(other_char): - return Vector2i(col, line) - if delims.contains(char) != delims.contains(other_char): - return Vector2i(col, line) - col += search_dir - line += search_dir - text = get_line_text(line) - col = (text.length() - 1) * int(search_dir < 0 and char_offset < 0) - return Vector2i(from_col, from_line) - -func get_paragraph_edge_pos(from_line: int, search_dir: int): - var line: int = from_line - var prev_empty: bool = code_edit.get_line(line) .strip_edges().is_empty() - line += search_dir - while line >= 0 and line < code_edit.get_line_count(): - var text: String = code_edit.get_line(line) .strip_edges() - if text.is_empty() and !prev_empty: - return Vector2i(text.length(), line) - prev_empty = text.is_empty() - line += search_dir - return Vector2i(0, line) - -# motion: command like "f", "t", "F", or "T" -func find_char_motion(in_line: int, from_col: int, motion: String, char: String) -> int: - var search_dir: int = 1 if is_lowercase(motion) else -1 - var offset: int = int(motion == 'T') - int(motion == 't') # 1 if T, -1 if t, 0 otherwise - var text: String = get_line_text(in_line) - - var col: int = -1 - if motion == 'f' or motion == 't': - col = text.find(char, from_col + search_dir) - elif motion == 'F' or motion == 'T': - col = text.rfind(char, from_col + search_dir) - if col == -1: - return -1 - return col + offset - -# returns: [ Vector2i from_pos, Vector2i to_pos ] -func calc_double_motion_region(from_pos: Vector2i, stream: String, from_char: int = 0) -> Array[Vector2i]: - var primary: String = get_stream_char(stream, from_char) - var secondary: String = get_stream_char(stream, from_char + 1) - if primary == '': - return [from_pos] # Incomplete - - if primary.to_lower() == 'w': - var p1: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x, '' if primary == 'W' else KEYWORDS, WordEdgeMode.WORD) - return [from_pos, p1 + Vector2i.LEFT] - if primary.to_lower() == 'b': - var p0: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x, '' if primary == 'B' else KEYWORDS, WordEdgeMode.BEGINNING) - return [p0, from_pos + Vector2i.LEFT] - if primary.to_lower() == 'e': - var p1: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x, '' if primary == 'E' else KEYWORDS, WordEdgeMode.END) - return [from_pos, p1] - - if primary == '$': - var p1: Vector2i = Vector2i(get_line_length(from_pos.y), from_pos.y) - return [from_pos, p1] - if primary == '^': - var p0: Vector2i = Vector2i(code_edit.get_first_non_whitespace_column(from_pos.y), from_pos.y) - return [p0, from_pos + Vector2i.LEFT] - - if primary != 'i' and primary != 'a': - return [] # Invalid - if secondary == '': - return [from_pos] # Incomplete - - if primary == 'i' and secondary.to_lower() == 'w': - var p0: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x + 1, '' if secondary == 'W' else KEYWORDS, WordEdgeMode.BEGINNING) - var p1: Vector2i = get_word_edge_pos(from_pos.y, from_pos.x - 1, '' if secondary == 'W' else KEYWORDS, WordEdgeMode.END) - return [ p0, p1 ] - - if primary == 'i' and secondary == 'p': - var p0: Vector2i = get_paragraph_edge_pos(from_pos.y + 1, -1) + Vector2i.DOWN - var p1: Vector2i = get_paragraph_edge_pos(from_pos.y - 1, 1) - return [ p0, p1 ] - - return [] # Unknown combination - -func toggle_comment(line: int): - var ind: int = code_edit.get_first_non_whitespace_column(line) - var text: String = get_line_text(line) - # Comment line - if text[ind] != '#': - code_edit.set_line(line, text.insert(ind, '# ')) - return - # Uncomment line - var start_col: int = get_word_edge_pos(line, ind, KEYWORDS, WordEdgeMode.WORD).x - code_edit.select(line, ind, line, start_col) - code_edit.delete_selection() - -func set_mode(m: int): - var old_mode: int = mode - mode = m - command_line.close() - match mode: - Mode.NORMAL: - code_edit.remove_secondary_carets() - code_edit.deselect() - code_edit.release_focus() - code_edit.deselect() - self.grab_focus() - status_bar.set_mode_text(Mode.NORMAL) - if old_mode == Mode.INSERT: - move_column(-1) - Mode.VISUAL: - if old_mode != Mode.VISUAL_LINE: - selection_from = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) - selection_to = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) - set_caret_pos(selection_to.y, selection_to.x) - status_bar.set_mode_text(Mode.VISUAL) - Mode.VISUAL_LINE: - if old_mode != Mode.VISUAL: - selection_from = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) - selection_to = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) - set_caret_pos(selection_to.y, selection_to.x) - status_bar.set_mode_text(Mode.VISUAL_LINE) - Mode.COMMAND: - command_line.show() - command_line.call_deferred("grab_focus") - status_bar.set_mode_text(Mode.COMMAND) - Mode.INSERT: - code_edit.call_deferred("grab_focus") - status_bar.set_mode_text(Mode.INSERT) - _: - pass - -func move_line(offset:int): - set_line(get_line() + offset) - -func get_line() -> int: - if is_mode_visual(mode): - return selection_to.y - return code_edit.get_caret_line() - -func get_line_text(line: int = -1) -> String: - if line == -1: - return code_edit.get_line(get_line()) - return code_edit.get_line(line) - -func get_line_length(line: int = -1) -> int: - return get_line_text(line).length() - -func set_caret_pos(line: int, column: int): - set_line(line) # line has to be set before column - set_column(column) - -func get_caret_pos() -> Vector2i: - return Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) - -func set_line(position:int): - if !is_mode_visual(mode): - code_edit.set_caret_line(min(position, code_edit.get_line_count()-1)) - return - - selection_to = Vector2i( clampi(selection_to.x, 0, get_line_length(position)), clampi(position, 0, code_edit.get_line_count()) ) - update_visual_selection() - - -func move_column(offset: int): - set_column(get_column()+offset) - -func get_column(): - if is_mode_visual(mode): - return selection_to.x - return code_edit.get_caret_column() - -func set_column(position: int): - if !is_mode_visual(mode): - var line: String = code_edit.get_line(code_edit.get_caret_line()) - code_edit.set_caret_column(min(line.length(), position)) - return - - selection_to = Vector2i( clampi(position, 0, get_line_length(selection_to.y)), clampi(selection_to.y, 0, code_edit.get_line_count()) ) - update_visual_selection() - -func update_visual_selection(): - if mode == Mode.VISUAL: - var to_right: bool = selection_to.x >= selection_from.x or selection_to.y > selection_from.y - code_edit.select( selection_from.y, selection_from.x + int(!to_right), selection_to.y, selection_to.x + int(to_right) ) - elif mode == Mode.VISUAL_LINE: - var f: int = mini(selection_from.y, selection_to.y) - 1 - var t: int = maxi(selection_from.y, selection_to.y) - code_edit.select(f, get_line_length(f), t, get_line_length(t)) - -func is_mode_visual(m: int) -> bool: - return m == Mode.VISUAL or m == Mode.VISUAL_LINE - -func is_lowercase(text: String) -> bool: - return text == text.to_lower() - -func is_uppercase(text: String) -> bool: - return text == text.to_upper() - -func get_stream_char(stream: String, idx: int) -> String: - return stream[idx] if stream.length() > idx else '' - -func draw_cursor(): - if code_edit.is_dragging_cursor(): - selection_from = Vector2i(code_edit.get_selection_from_column(), code_edit.get_selection_from_line()) - selection_to = Vector2i(code_edit.get_selection_to_column(), code_edit.get_selection_to_line()) - - if code_edit.get_selected_text(0).length() > 1 and !is_mode_visual(mode): - code_edit.release_focus() - self.grab_focus() - set_mode(Mode.VISUAL) - - if mode == Mode.INSERT: - if code_edit.has_selection(0): - code_edit.deselect(0) - return - - if mode != Mode.NORMAL: - return - - var line: int = code_edit.get_caret_line() - var column: int = code_edit.get_caret_column() - if column >= code_edit.get_line(line).length(): - column -= 1 - code_edit.set_caret_column(column) - - code_edit.select(line, column, line, column+1) diff --git a/langtons-ant/addons/godot_vim/cursor.gd.uid b/langtons-ant/addons/godot_vim/cursor.gd.uid deleted file mode 100644 index 4e346ea..0000000 --- a/langtons-ant/addons/godot_vim/cursor.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bmkia3drpjivh diff --git a/langtons-ant/addons/godot_vim/dispatcher.gd b/langtons-ant/addons/godot_vim/dispatcher.gd deleted file mode 100644 index f099971..0000000 --- a/langtons-ant/addons/godot_vim/dispatcher.gd +++ /dev/null @@ -1,22 +0,0 @@ -extends Object - -var handlers = { - "goto": preload("res://addons/godot_vim/commands/goto.gd"), - "find": preload("res://addons/godot_vim/commands/find.gd"), - "marks": preload("res://addons/godot_vim/commands/marks.gd") -} - -var globals - -func dispatch(command : String): - var command_idx_end = command.find(' ', 1) - if command_idx_end == -1: command_idx_end = command.length() - var handler_name = command.substr(1, command_idx_end-1) - if not handlers.has(handler_name): - return ERR_DOES_NOT_EXIST - - var handler = handlers.get(handler_name) - var handler_instance = handler.new() - var args = command.substr(command_idx_end, command.length()) - handler_instance.execute(globals, args) - return OK diff --git a/langtons-ant/addons/godot_vim/dispatcher.gd.uid b/langtons-ant/addons/godot_vim/dispatcher.gd.uid deleted file mode 100644 index 6b36fcf..0000000 --- a/langtons-ant/addons/godot_vim/dispatcher.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dvfyfj3wtqfds diff --git a/langtons-ant/addons/godot_vim/hack_regular.ttf b/langtons-ant/addons/godot_vim/hack_regular.ttf deleted file mode 100644 index 92a90cb..0000000 Binary files a/langtons-ant/addons/godot_vim/hack_regular.ttf and /dev/null differ diff --git a/langtons-ant/addons/godot_vim/plugin.cfg b/langtons-ant/addons/godot_vim/plugin.cfg deleted file mode 100644 index 5726e85..0000000 --- a/langtons-ant/addons/godot_vim/plugin.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[plugin] - -name="GodotVim" -description="" -author="Bernardo Bruning" -version="0.1" -script="plugin.gd" diff --git a/langtons-ant/addons/godot_vim/plugin.gd b/langtons-ant/addons/godot_vim/plugin.gd deleted file mode 100644 index 35d10fa..0000000 --- a/langtons-ant/addons/godot_vim/plugin.gd +++ /dev/null @@ -1,164 +0,0 @@ -@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 - diff --git a/langtons-ant/addons/godot_vim/plugin.gd.uid b/langtons-ant/addons/godot_vim/plugin.gd.uid deleted file mode 100644 index fad9375..0000000 --- a/langtons-ant/addons/godot_vim/plugin.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://duaf0qcswso06 diff --git a/langtons-ant/addons/godot_vim/status_bar.gd b/langtons-ant/addons/godot_vim/status_bar.gd deleted file mode 100644 index 7160c14..0000000 --- a/langtons-ant/addons/godot_vim/status_bar.gd +++ /dev/null @@ -1,68 +0,0 @@ -extends HBoxContainer -const ERROR_COLOR: String = "#ff8866" -const SPECIAL_COLOR: String = "#fcba03" - -const Constants = preload("res://addons/godot_vim/constants.gd") -const Mode = Constants.Mode - -var mode_label: Label -var main_label: RichTextLabel - -func _ready(): - var font = load("res://addons/godot_vim/hack_regular.ttf") - - mode_label = Label.new() - - mode_label.text = '' - mode_label.add_theme_color_override(&"font_color", Color.BLACK) - var stylebox: StyleBoxFlat = StyleBoxFlat.new() - stylebox.bg_color = Color.GOLD - stylebox.content_margin_left = 4.0 - stylebox.content_margin_right = 4.0 - stylebox.content_margin_top = 2.0 - stylebox.content_margin_bottom = 2.0 - mode_label.add_theme_stylebox_override(&"normal", stylebox) - mode_label.add_theme_font_override(&"font", font) - add_child(mode_label) - - main_label = RichTextLabel.new() - main_label.bbcode_enabled = true - main_label.text = '' - main_label.fit_content = true - main_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL - main_label.text_direction = Control.TEXT_DIRECTION_RTL - main_label.add_theme_font_override(&"normal_font", font) - add_child(main_label) - -func display_text(text: String, text_direction: Control.TextDirection = TEXT_DIRECTION_RTL): - main_label.text = text - main_label.text_direction = text_direction - -func display_error(text: String): - main_label.text = '[color=%s]%s' % [ERROR_COLOR, text] - main_label.text_direction = Control.TEXT_DIRECTION_LTR - -func display_special(text: String): - main_label.text = '[color=%s]%s' % [SPECIAL_COLOR, text] - main_label.text_direction = Control.TEXT_DIRECTION_LTR - -func set_mode_text(mode: Mode): - var stylebox: StyleBoxFlat = mode_label.get_theme_stylebox(&"normal") - match mode: - Mode.NORMAL: - mode_label.text = 'NORMAL' - stylebox.bg_color = Color.LIGHT_SALMON - Mode.INSERT: - mode_label.text = 'INSERT' - stylebox.bg_color = Color.POWDER_BLUE - Mode.VISUAL: - mode_label.text = 'VISUAL' - stylebox.bg_color = Color.PLUM - Mode.VISUAL_LINE: - mode_label.text = 'VISUAL LINE' - stylebox.bg_color = Color.PLUM - Mode.COMMAND: - mode_label.text = 'COMMAND' - stylebox.bg_color = Color.TOMATO - _: - pass diff --git a/langtons-ant/addons/godot_vim/status_bar.gd.uid b/langtons-ant/addons/godot_vim/status_bar.gd.uid deleted file mode 100644 index 1d9aeb6..0000000 --- a/langtons-ant/addons/godot_vim/status_bar.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bsixy362mx4r7