From bd32153557c579a2510d557de173bd9b57af3fd4 Mon Sep 17 00:00:00 2001 From: tonydero Date: Thu, 19 Sep 2024 19:03:06 -0600 Subject: [PATCH] repo fix first commit --- .gitattributes | 2 + .gitignore | 19 + addons/godot-vim/godot-vim.gd | 1702 ++++++++++++++++++++++ addons/godot-vim/icon.svg | 179 +++ addons/godot-vim/icon.svg.import | 37 + addons/godot-vim/plugin.cfg | 7 + addons/godot_vim/command_line.gd | 93 ++ addons/godot_vim/commands/delmarks.gd | 14 + addons/godot_vim/commands/find.gd | 16 + addons/godot_vim/commands/goto.gd | 7 + addons/godot_vim/commands/marks.gd | 61 + addons/godot_vim/commands/movecolumn.gd | 2 + addons/godot_vim/commands/moveline.gd | 2 + addons/godot_vim/commands/reload.gd | 4 + addons/godot_vim/commands/remap.gd | 9 + addons/godot_vim/commands/w.gd | 17 + addons/godot_vim/commands/wa.gd | 17 + addons/godot_vim/constants.gd | 19 + addons/godot_vim/cursor.gd | 1135 +++++++++++++++ addons/godot_vim/dispatcher.gd | 39 + addons/godot_vim/hack_regular.ttf | Bin 0 -> 309408 bytes addons/godot_vim/hack_regular.ttf.import | 34 + addons/godot_vim/key_map.gd | 741 ++++++++++ addons/godot_vim/plugin.cfg | 7 + addons/godot_vim/plugin.gd | 354 +++++ addons/godot_vim/remap.gd | 178 +++ addons/godot_vim/status_bar.gd | 88 ++ icon.svg | 1 + icon.svg.import | 37 + project.godot | 28 + scenes/character.tscn | 56 + scenes/mapTile.tscn | 6 + scenes/npc.tscn | 12 + scenes/testScene.tscn | 67 + scripts/character.gd | 12 + scripts/equipmentGen.gd | 17 + scripts/gear.gd | 22 + scripts/gearRand.gd | 39 + scripts/item.gd | 10 + scripts/map_tile.gd | 12 + scripts/npc.gd | 37 + scripts/test_scene.gd | 33 + 42 files changed, 5172 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 addons/godot-vim/godot-vim.gd create mode 100644 addons/godot-vim/icon.svg create mode 100644 addons/godot-vim/icon.svg.import create mode 100644 addons/godot-vim/plugin.cfg create mode 100644 addons/godot_vim/command_line.gd create mode 100644 addons/godot_vim/commands/delmarks.gd create mode 100644 addons/godot_vim/commands/find.gd create mode 100644 addons/godot_vim/commands/goto.gd create mode 100644 addons/godot_vim/commands/marks.gd create mode 100644 addons/godot_vim/commands/movecolumn.gd create mode 100644 addons/godot_vim/commands/moveline.gd create mode 100644 addons/godot_vim/commands/reload.gd create mode 100644 addons/godot_vim/commands/remap.gd create mode 100644 addons/godot_vim/commands/w.gd create mode 100644 addons/godot_vim/commands/wa.gd create mode 100644 addons/godot_vim/constants.gd create mode 100644 addons/godot_vim/cursor.gd create mode 100644 addons/godot_vim/dispatcher.gd create mode 100644 addons/godot_vim/hack_regular.ttf create mode 100644 addons/godot_vim/hack_regular.ttf.import create mode 100644 addons/godot_vim/key_map.gd create mode 100644 addons/godot_vim/plugin.cfg create mode 100644 addons/godot_vim/plugin.gd create mode 100644 addons/godot_vim/remap.gd create mode 100644 addons/godot_vim/status_bar.gd create mode 100644 icon.svg create mode 100644 icon.svg.import create mode 100644 project.godot create mode 100644 scenes/character.tscn create mode 100644 scenes/mapTile.tscn create mode 100644 scenes/npc.tscn create mode 100644 scenes/testScene.tscn create mode 100644 scripts/character.gd create mode 100644 scripts/equipmentGen.gd create mode 100644 scripts/gear.gd create mode 100644 scripts/gearRand.gd create mode 100644 scripts/item.gd create mode 100644 scripts/map_tile.gd create mode 100644 scripts/npc.gd create mode 100644 scripts/test_scene.gd diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58c1eec --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# ---> Godot +# Godot 4+ specific ignores +.godot/ +/android/ + +# Godot-specific ignores +.import/ +export.cfg +export_presets.cfg + +# Imported translations (automatically generated from CSV files) +*.translation + +# Mono-specific ignores +.mono/ +data_*/ +mono_crash.*.json + + diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd new file mode 100644 index 0000000..f6733d5 --- /dev/null +++ b/addons/godot-vim/godot-vim.gd @@ -0,0 +1,1702 @@ +@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" }, + { "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": ["Apostrophe", "{char}"], "type": MOTION, "motion": "go_to_bookmark", "motion_args": {} }, + { "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", "operator_args": { "maintain_position": true } }, + { "keys": ["Shift+Y"], "type": OPERATOR_MOTION, "operator": "yank", "motion": "move_to_end_of_line", "motion_args": { "inclusive": true }, "operator_args": { "maintain_position": true } }, + { "keys": ["C"], "type": OPERATOR, "operator": "change" }, + { "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": "change", "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": ["G", "U"], "type": OPERATOR, "operator": "change_case", "operator_args": { "lower": true }, "context": Context.VISUAL }, + { "keys": ["G", "Shift+U"], "type": OPERATOR, "operator": "change_case", "operator_args": { "lower": false }, "context": Context.VISUAL }, + { "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": ["Ctrl+BracketRight"], "type": ACTION, "action": "go_to_definition", "action_args": {} }, +] + + +# 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, + "Ctrl+I": 1, + "Ctrl+R": 1, + "Ctrl+BracketRight": 1, +} + + +var editor_interface : EditorInterface +var the_ed := EditorAdaptor.new() # The current editor adaptor +var the_vim := Vim.new() +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 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) + + 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) + + 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) + else: + col = min(col, ed.last_column(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 i := ed.find_first_non_white_space_character(ed.curr_line()) + return Position.new(cur.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 + + 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) + +### OPERATORS + + static func delete(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: + var text := ed.selected_text() + var line_wise = args.get("line_wise", false) + vim.register.set_text(text, line_wise) + + ed.begin_complex_operation() + ed.delete_selection() + + # For linewise delete, we want to delete one more line + if line_wise: + ed.select(ed.curr_line(), -1, ed.curr_line()+1, -1) + ed.delete_selection() + ed.end_complex_operation() + + 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)) + + vim.current.enter_insert_mode(); + ed.delete_selection() + + 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)) + + + ### 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() + + ed.begin_complex_operation() + if vim.current.visual_mode: + ed.delete_selection() + else: + if line_wise: + if after: + text = "\n" + text + col = len(ed.line_text(line)) + else: + text = text + "\n" + col = 0 + else: + col += 1 if after else 0 + + ed.set_curr_column(col) + + ed.insert_text(text) + ed.end_complex_operation() + + 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) + 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_definition(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: + var pos_before := ed.curr_position() + + ed.go_to_definition() + + await ed.code_editor.get_tree().process_frame + var pos_after := ed.curr_position() + if not pos_before.equals(pos_after): + vim.current.jump_list.add(pos_before, pos_after) + + ### 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 + ed.set_block_caret(true) + + visual_start_pos = ed.curr_position() + + if line_wise: + ed.select(visual_start_pos.line, 0, visual_start_pos.line, INF_COL) + else: + ed.select_by_pos2(visual_start_pos, 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 + + 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: + return code_editor.get_caret_column() + + 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) + + if line < first_visible_line(): + code_editor.set_line_as_first_visible(max(0, line-8)) + elif line > last_visible_line(): + code_editor.set_line_as_last_visible(min(last_line(), line+8)) + code_editor.set_caret_line(line) + code_editor.set_caret_column(col) + + 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 go_to_definition() -> void: + var symbol := code_editor.get_word_under_caret() + code_editor.symbol_lookup.emit(symbol, curr_line(), curr_column()) + + 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 deselect() -> void: + code_editor.deselect() + + 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 we try to select backward pass the first line, select the first char + if to_line < 0: + to_line = 0 + to_col = 0 + # If we try to select pass the last line, select till the last char + elif to_line > last_line(): + to_line = last_line() + to_col = INF_COL + + # Our select() is inclusive, e.g. ed.select(0, 0, 0, 0) selects the first character; + # while CodeEdit's select() is exlusvie on the right hand side, e.g. code_editor.select(0, 0, 0, 1) selects the first character. + # We do the translation here: + if from_line < to_line or (from_line == to_line and from_col <= to_col): # Selecting forward + to_col += 1 + else: + from_col += 1 + + if DEBUGGING: + print(" Selecting from (%d,%d) to (%d,%d)" % [from_line, from_col, to_line, to_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.new(code_editor.get_selection_from_line(), code_editor.get_selection_from_column()) + var to := Position.new(code_editor.get_selection_to_line(), code_editor.get_selection_to_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].duplicate(true) + 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 + + # We respecte selection start position if we are in visual mod + var start := vim_session.visual_start_pos if vim_session.visual_mode else 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", {}) + operator_args.original_pos = start + + 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) + + # Handle the motion and get the new cursor position + var new_pos = process_motion(command.motion, motion_args, ed, vim) + if new_pos == null: + return true + + # In some cases (text object), we need to override the start position + if new_pos is TextRange: + start = new_pos.from + new_pos = new_pos.to + + var inclusive : bool = motion_args.get("inclusive", false) + var jump_forward := start.compares_to(new_pos) < 0 + + if vim_session.visual_mode: # Visual mode + if vim_session.visual_line: + if jump_forward: + ed.select(start.line, 0, new_pos.line, INF_COL) + else: + ed.select(start.line, INF_COL, new_pos.line, -1) + else: + ed.select_by_pos2(start, new_pos) + else: # Normal mode + if input_state.operator.is_empty(): # Motion only + ed.jump_to(new_pos.line, new_pos.column) + else: # Operator motion + # Check if we need to exlude the last character in selection + if not inclusive: + if jump_forward: + new_pos = new_pos.left() + else: + start = start.left() + + ed.select_by_pos2(start, 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", {}) + operator_args.original_pos = start + + if vim.current.visual_mode: + operator_args.line_wise = vim.current.visual_line + process_operator(command.operator, operator_args, ed, vim) + vim.current.enter_normal_mode() + return true + + # Otherwise, we are in normal mode + if 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 + + # Line wise operation + if input_state.operator == command.operator: + operator_args.line_wise = true + var new_pos : Position = process_motion("expand_to_line", {}, ed, vim) + ed.select(start.line, 0, new_pos.line, new_pos.column) + 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) + + if operator_args.get("maintain_position", false): + var original_pos = operator_args.get("original_pos") + ed.jump_to(original_pos.line, original_pos.column) + + 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()) + + # In Godot 4.3, CodeEdit.select moves cursor as well. If we select forward, the cursor will be positioned at the next column of the last selected column. + # But for VIM in the same case, the cursor position is exactly the last selected column. So we move back by one column when we considering the current position. + if vim.current.visual_mode: + if ed.code_editor.get_selection_origin_column() < ed.code_editor.get_caret_column(): + cur.column -= 1 + + # 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/addons/godot-vim/icon.svg b/addons/godot-vim/icon.svg new file mode 100644 index 0000000..9831b04 --- /dev/null +++ b/addons/godot-vim/icon.svg @@ -0,0 +1,179 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/godot-vim/icon.svg.import b/addons/godot-vim/icon.svg.import new file mode 100644 index 0000000..7f441b3 --- /dev/null +++ b/addons/godot-vim/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kinglqst816b" +path="res://.godot/imported/icon.svg-5744b51718b6a64145ec5798797f7631.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/godot-vim/icon.svg" +dest_files=["res://.godot/imported/icon.svg-5744b51718b6a64145ec5798797f7631.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/godot-vim/plugin.cfg b/addons/godot-vim/plugin.cfg new file mode 100644 index 0000000..9a7f5d3 --- /dev/null +++ b/addons/godot-vim/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="godot-vim" +description="VIM bindings for godot4" +author="Original: Josh N; Forked by Wenqiang Wang" +version="4.3.0" +script="godot-vim.gd" diff --git a/addons/godot_vim/command_line.gd b/addons/godot_vim/command_line.gd new file mode 100644 index 0000000..75e0f71 --- /dev/null +++ b/addons/godot_vim/command_line.gd @@ -0,0 +1,93 @@ +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 MODE = Constants.Mode + +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 is_paused: bool = false +var search_pattern: String = "" + + +func _ready(): + 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 + # Update search + 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.center_viewport_to_caret(1) # why no work, eh? + + code_edit.scroll_vertical = ( + code_edit.get_scroll_pos_for_line(pos.y) - code_edit.get_visible_line_count() / 2 + ) + + +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 globals.vim_plugin.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) + + +# Wait for user input +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/addons/godot_vim/commands/delmarks.gd b/addons/godot_vim/commands/delmarks.gd new file mode 100644 index 0000000..e98df76 --- /dev/null +++ b/addons/godot_vim/commands/delmarks.gd @@ -0,0 +1,14 @@ +const Constants = preload("res://addons/godot_vim/constants.gd") +const StatusBar = preload("res://addons/godot_vim/status_bar.gd") +const MODE = Constants.Mode + +const LINE_START_IDX: int = 8 +const COL_START_IDX: int = 16 +const FILE_START_IDX: int = 25 + + +func execute(api: Dictionary, _args): + api.marks = {} + + api.status_bar.display_text("Deleted all marks") + api.cursor.set_mode(MODE.NORMAL) diff --git a/addons/godot_vim/commands/find.gd b/addons/godot_vim/commands/find.gd new file mode 100644 index 0000000..bfbc5fb --- /dev/null +++ b/addons/godot_vim/commands/find.gd @@ -0,0 +1,16 @@ +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) + # api.code_edit.center_viewport_to_caret() + else: + api.status_bar.display_error('Pattern not found: "%s"' % [api.command_line.search_pattern]) + api.cursor.set_mode(MODE.NORMAL) diff --git a/addons/godot_vim/commands/goto.gd b/addons/godot_vim/commands/goto.gd new file mode 100644 index 0000000..9794bda --- /dev/null +++ b/addons/godot_vim/commands/goto.gd @@ -0,0 +1,7 @@ +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() - 1, 0) + api.cursor.set_mode(MODE.NORMAL) diff --git a/addons/godot_vim/commands/marks.gd b/addons/godot_vim/commands/marks.gd new file mode 100644 index 0000000..cf3c1c0 --- /dev/null +++ b/addons/godot_vim/commands/marks.gd @@ -0,0 +1,61 @@ +const Constants = preload("res://addons/godot_vim/constants.gd") +const StatusBar = preload("res://addons/godot_vim/status_bar.gd") +const MODE = Constants.Mode + +const LINE_START_IDX: int = 8 +const COL_START_IDX: int = 16 +const FILE_START_IDX: int = 25 + + +## Display format: +## (1) LINE_START_IDX +## (2) COL_START_IDX +## (3) FILE_START_IDX +## +## List of all marks: +## (1) (2) (3) +## | | | +## mark line col file +## a 123 456 res://some_file +func row_string(mark: String, line: String, col: String, file: String) -> String: + var text: String = mark + text += " ".repeat(LINE_START_IDX - mark.length()) + line + text += " ".repeat(COL_START_IDX - text.length()) + col + text += " ".repeat(FILE_START_IDX - text.length()) + file + return text + + +func mark_string(key: String, m: Dictionary) -> String: + var pos: Vector2i = m.get("pos", Vector2i()) + var file: String = m.get("file", "") + return row_string(key, str(pos.y), str(pos.x), file) + + +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 text: String = "[color=%s]List of all marks[/color]" % StatusBar.SPECIAL_COLOR + text += "\n" + row_string("mark", "line", "col", "file") + + # Display user-defined marks first (alphabet) + for key in marks.keys(): + if !is_key_alphabet(key): + continue + text += "\n" + mark_string(key, marks[key]) + + # Then display 'number' marks + for key in marks.keys(): + if is_key_alphabet(key) or key == "-1": + continue + text += "\n" + mark_string(key, marks[key]) + + api.status_bar.display_text(text) + + +func is_key_alphabet(key: String) -> bool: + var unicode: int = key.unicode_at(0) + return (unicode >= 65 and unicode <= 90) or (unicode >= 97 and unicode <= 122) diff --git a/addons/godot_vim/commands/movecolumn.gd b/addons/godot_vim/commands/movecolumn.gd new file mode 100644 index 0000000..4d0915d --- /dev/null +++ b/addons/godot_vim/commands/movecolumn.gd @@ -0,0 +1,2 @@ +func execute(api, args): + api.cursor.move_column(int(args)) diff --git a/addons/godot_vim/commands/moveline.gd b/addons/godot_vim/commands/moveline.gd new file mode 100644 index 0000000..23c20a0 --- /dev/null +++ b/addons/godot_vim/commands/moveline.gd @@ -0,0 +1,2 @@ +func execute(api, args): + api.cursor.move_line(int(args)) diff --git a/addons/godot_vim/commands/reload.gd b/addons/godot_vim/commands/reload.gd new file mode 100644 index 0000000..8728787 --- /dev/null +++ b/addons/godot_vim/commands/reload.gd @@ -0,0 +1,4 @@ +func execute(api: Dictionary, _args): + print("[Godot VIM] Reloading...") + api.status_bar.display_text("Reloading plugin...") + api.vim_plugin.initialize(true) diff --git a/addons/godot_vim/commands/remap.gd b/addons/godot_vim/commands/remap.gd new file mode 100644 index 0000000..13f9c2b --- /dev/null +++ b/addons/godot_vim/commands/remap.gd @@ -0,0 +1,9 @@ +const Constants = preload("res://addons/godot_vim/constants.gd") +const StatusBar = preload("res://addons/godot_vim/status_bar.gd") + + +func execute(api: Dictionary, _args): + print("[Godot VIM] Please run :reload in the command line after changing your keybinds") + var script: Script = api.key_map.get_script() + # Line 45 is where KeyMap::map() is + EditorInterface.edit_script(script, 40, 0, false) diff --git a/addons/godot_vim/commands/w.gd b/addons/godot_vim/commands/w.gd new file mode 100644 index 0000000..505b554 --- /dev/null +++ b/addons/godot_vim/commands/w.gd @@ -0,0 +1,17 @@ +const Constants = preload("res://addons/godot_vim/constants.gd") +const MODE = Constants.Mode + + +func execute(api, _args: String): + #EditorInterface.save_scene() + press_save_shortcut() + api.cursor.set_mode(MODE.NORMAL) + + +func press_save_shortcut(): + var a = InputEventKey.new() + a.keycode = KEY_S + a.ctrl_pressed = true + a.alt_pressed = true + a.pressed = true + Input.parse_input_event(a) diff --git a/addons/godot_vim/commands/wa.gd b/addons/godot_vim/commands/wa.gd new file mode 100644 index 0000000..5329b08 --- /dev/null +++ b/addons/godot_vim/commands/wa.gd @@ -0,0 +1,17 @@ +const Constants = preload("res://addons/godot_vim/constants.gd") +const MODE = Constants.Mode + + +func execute(api, _args: String): + #EditorInterface.save_scene() + press_save_shortcut() + api.cursor.set_mode(MODE.NORMAL) + + +func press_save_shortcut(): + var a = InputEventKey.new() + a.keycode = KEY_S + a.shift_pressed = true + a.alt_pressed = true + a.pressed = true + Input.parse_input_event(a) diff --git a/addons/godot_vim/constants.gd b/addons/godot_vim/constants.gd new file mode 100644 index 0000000..c3e6101 --- /dev/null +++ b/addons/godot_vim/constants.gd @@ -0,0 +1,19 @@ +enum Mode { NORMAL = 0, INSERT, VISUAL, VISUAL_LINE, COMMAND } + +enum Language { + GDSCRIPT, + SHADER, +} + +const KEYWORDS: String = ".,\"'-=+!@#$%^&*()[]{}?~/\\<>:;`" +const DIGITS: String = "0123456789" +const SPACES: String = " \t" +const PAIRS: Dictionary = { + '"': '"', + "'": "'", + "`": "`", + "(": ")", + "[": "]", + "{": "}", +} +const BRACES: String = "([{}])" diff --git a/addons/godot_vim/cursor.gd b/addons/godot_vim/cursor.gd new file mode 100644 index 0000000..1fec6d6 --- /dev/null +++ b/addons/godot_vim/cursor.gd @@ -0,0 +1,1135 @@ +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 KEYWORDS = Constants.KEYWORDS +const SPACES = Constants.SPACES +const LANGUAGE = Constants.Language + +var code_edit: CodeEdit +var language: LANGUAGE = LANGUAGE.GDSCRIPT +var command_line: CommandLine +var status_bar: StatusBar +var key_map: KeyMap + +var mode: Mode = Mode.NORMAL +# For visual modes: +# `selection_from` is the origin point of the selection +# code_edit's caret pos is the end point of the selection (the one the user can move) +var selection_from: Vector2i = Vector2i() + +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(): + set_mode(Mode.NORMAL) + selection_from = Vector2i.ZERO + set_column(code_edit.get_caret_column()) + + +func _input(event: InputEvent): + if Input.is_key_pressed(KEY_ESCAPE): + reset_normal() + status_bar.clear() + return + + draw_cursor() + if !has_focus() and mode != Mode.INSERT: + return + if !event is InputEventKey: + return + if !event.pressed: + return + if mode == Mode.COMMAND: + return + + # See KeyMap.key_map, KeyMap.register_event() + var registered_cmd: Dictionary = key_map.register_event(event, mode) + + # Display keys in status bar + if mode == Mode.NORMAL or is_mode_visual(mode): + status_bar.set_keys_text(key_map.get_input_stream_as_string()) + else: + status_bar.clear_keys() + + if KeyMap.is_cmd_valid(registered_cmd): + code_edit.cancel_code_completion() + get_viewport().set_input_as_handled() + + +# Mostly used for commands like "w", "b", and "e" +func get_word_edge_pos( + from_line: int, from_col: int, forward: bool, word_end: bool, big_word: bool +) -> Vector2i: + var search_dir: int = int(forward) - int(!forward) # 1 if forward else -1 + var line: int = from_line + # Think of `col` as the place in between the two chars we're testing + var col: int = from_col + search_dir + int(word_end) # Also nudge it once if checking word ends ("e" or "ge") + # Char groups: 0 = char is normal char, 1 = char is keyword, 2 = char is space + # Cancel 1st bit (keywords) if big word so that keywords and normal chars are treated the same + var big_word_mask: int = 0b10 if big_word else 0b11 + + var text: String = get_line_text(line) + while line >= 0 and line < code_edit.get_line_count(): + while col >= 0 and col <= text.length(): + # Get "group" of chars to the left and right of `col` + var left_char: String = " " if col == 0 else text[col - 1] + var right_char: String = " " if col == text.length() else text[col] # ' ' if eol; else, the char to the right + var lg: int = ( + (int(KEYWORDS.contains(left_char)) | (int(SPACES.contains(left_char)) << 1)) + & big_word_mask + ) + var rg: int = ( + (int(KEYWORDS.contains(right_char)) | (int(SPACES.contains(right_char)) << 1)) + & big_word_mask + ) + + # Same as: if lg != rg and (lg if word_end else rg) != 2 but without branching + # (is different group) and (spaces don't count in the wrong direction) + if lg != rg and lg * int(word_end) + rg * int(!word_end) != 0b10: + return Vector2i(col - int(word_end), line) + + col += search_dir + line += search_dir + text = get_line_text(line) + col = (text.length() - 1) * int(search_dir < 0) + + return Vector2i(from_col, from_line) + + +""" Rough explanation: +forward and end -> criteria = current_empty and !previous_empty, no offset +!forward and end -> criteria = !current_empty and previous_empty, +1 offset +forward and !end -> criteria = !current_empty and previous_empty, -1 offset +!forward and !end -> criteria = current_empty and !previous_empty, no offset + +criteria = (current_empty and !previous_empty) + if forward == end + else ( !(current_empty and !previous_empty) - search_dir ) +""" + + +## Get the 'edge' or a paragraph (like with { or } motions) +func get_paragraph_edge_pos(from_line: int, forward: bool, paragraph_end: bool) -> int: + var search_dir: int = int(forward) - int(!forward) # 1 if forward else -1 + var line: int = from_line + var prev_empty: bool = code_edit.get_line(line).strip_edges().is_empty() + var f_eq_end: bool = forward == paragraph_end + line += search_dir + + while line >= 0 and line < code_edit.get_line_count(): + var current_empty: bool = code_edit.get_line(line).strip_edges().is_empty() + if f_eq_end: + if current_empty and !prev_empty: + return line + elif !current_empty and prev_empty: + return line - search_dir + + prev_empty = current_empty + line += search_dir + return line + + +# Get the 'edge' or a section (like with [[ or ]] motions) +# See is_line_section() +func get_section_edge_pos(from_line: int, forward: bool) -> Vector2i: + var search_dir: int = int(forward) - int(!forward) + var line: int = from_line + var is_prev_section: bool = is_line_section(code_edit.get_line(line)) + + line += search_dir + while line >= 0 and line < code_edit.get_line_count(): + var text: String = code_edit.get_line(line) + if is_line_section(text) and !is_prev_section: + return Vector2i(text.length(), line) + + is_prev_section = is_line_section(code_edit.get_line(line)) + line += search_dir + return Vector2i(0, line) + + +## Finds the next -- or previous is `forward = false` -- occurence of `char` in the line `line`, +## starting from col `from_col` +## Additionally, it can stop before the occurence with `stop_before = true` +func find_char_in_line( + line: int, from_col: int, forward: bool, stop_before: bool, char: String +) -> int: + var text: String = get_line_text(line) + + # Search char + var col: int = text.find(char, from_col + 1) if forward else text.rfind(char, from_col - 1) + if col == -1: # Not found + return -1 + + # col + offset + # where offset = ( int(!forward) - int(forward) ) * int(stop_before) + # = 1 if forward, -1 if !forward, 0 otherwise + return col + (int(!forward) - int(forward)) * int(stop_before) + + +## Finds the next -- or previous if `forward = false` -- occurence of any character in `chars` +## if `chars` has only one character, it will look for that one +func find_next_occurence_of_chars( + from_line: int, + from_col: int, + chars: String, + forward: bool, +) -> Vector2i: + var p: Vector2i = Vector2i(from_col, from_line) + var line_count: int = code_edit.get_line_count() + var search_dir: int = int(forward) - int(!forward) # 1 if forward, -1 if backwards + + var text: String = get_line_text(p.y) + while p.y >= 0 and p.y < line_count: + while p.x >= 0 and p.x < text.length(): + if chars.contains(text[p.x]): + return p + p.x += search_dir + p.y += search_dir + text = get_line_text(p.y) + p.x = (text.length() - 1) * int(!forward) # 0 if forwards, EOL if backwards + # Not found + # i want optional typing to be in godot so bad + return Vector2i(-1, -1) + + +## Finds the next / previous brace specified by `brace` and its closing `counterpart` +## `force_inline` forces to look only in the line `from_line` (See constants.gd::INLINE_BRACKETS) +## E.g. +## brace = "(", counterpart = ")", forward = false, from_line and from_col = in between the brackets +## will find the start of the set of parantheses the cursor is inside of +func find_brace( + from_line: int, + from_col: int, + brace: String, + counterpart: String, + forward: bool, + force_inline: bool = false, +) -> Vector2i: + var line_count: int = code_edit.get_line_count() + var d: int = int(forward) - int(!forward) + + var p: Vector2i = Vector2i(from_col + d, from_line) + var stack: int = 0 + + var text: String = get_line_text(p.y) + while p.y >= 0 and p.y < line_count: + while p.x >= 0 and p.x < text.length(): + var char: String = text[p.x] + + if char == counterpart: + if stack == 0: + return p + stack -= 1 + elif char == brace: + stack += 1 + p.x += d + + if force_inline: + return Vector2i(-1, -1) + + p.y += d + text = get_line_text(p.y) + p.x = (text.length() - 1) * int(!forward) # 0 if forwards, EOL if backwards + + # i want optional typing to be in godot so bad rn + return Vector2i(-1, -1) + + +func get_comment_char() -> String: + match language: + LANGUAGE.SHADER: + return "//" + LANGUAGE.GDSCRIPT: + return "#" + _: + return "#" + + +func set_line_commented(line: int, is_commented: bool): + var text: String = get_line_text(line) + # Don't comment if empty + if text.strip_edges().is_empty(): + return + + var ind: int = code_edit.get_first_non_whitespace_column(line) + if is_commented: + code_edit.set_line(line, text.insert(ind, get_comment_char() + " ")) + return + # We use get_word_edge_pos() in case there's multiple '#'s + var start_col: int = get_word_edge_pos(line, ind, true, false, true).x + code_edit.select(line, ind, line, start_col) + code_edit.delete_selection() + + +func is_line_commented(line: int) -> bool: + var text: String = get_line_text(line).strip_edges(true, false) + return text.begins_with(get_comment_char()) + + +func set_mode(m: int): + var old_mode: int = mode + mode = m + command_line.close() + + match mode: + Mode.NORMAL: + code_edit.call_deferred("cancel_code_completion") + key_map.clear() + + code_edit.remove_secondary_carets() # Secondary carets are used when searching with '/' (See command_line.gd) + code_edit.release_focus() + code_edit.deselect() + self.grab_focus() + status_bar.set_mode_text(Mode.NORMAL) + + # Insert -> Normal + if old_mode == Mode.INSERT: + # code_edit.end_complex_operation() # See Mode.INSERT match arm below + move_column(-1) + + Mode.VISUAL: + if old_mode != Mode.VISUAL_LINE: + selection_from = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) + status_bar.set_mode_text(Mode.VISUAL) + update_visual_selection() + + Mode.VISUAL_LINE: + if old_mode != Mode.VISUAL: + selection_from = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) + status_bar.set_mode_text(Mode.VISUAL_LINE) + update_visual_selection() + + 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) + + # if old_mode == Mode.NORMAL: + # Complex operation so that entire insert mode actions can be undone + # with one undo + # code_edit.begin_complex_operation() + + _: + push_error("[vim::cursor::set_mode()] Unknown mode %s" % mode) + + +func move_line(offset: int): + set_line(get_line() + offset) + + +func get_line() -> int: + 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_char_at(line: int, col: int) -> String: + var text: String = code_edit.get_line(line) + if col > 0 and col < text.length(): + return text[col] + return "" + + +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): + code_edit.set_caret_line(min(position, code_edit.get_line_count() - 1)) + + if is_mode_visual(mode): + update_visual_selection() + + +func move_column(offset: int): + set_column(get_column() + offset) + + +func get_column(): + return code_edit.get_caret_column() + + +func set_column(position: int): + code_edit.set_caret_column(min(get_line_length(), position)) + + if is_mode_visual(mode): + update_visual_selection() + + +func select(from_line: int, from_col: int, to_line: int, to_col: int): + code_edit.select(from_line, from_col, to_line, to_col + 1) + selection_from = Vector2i(from_col, from_line) + set_caret_pos(to_line, to_col) + # status_bar.set_mode_text(Mode.VISUAL) + + +func update_visual_selection(): + var selection_to: Vector2i = Vector2i(code_edit.get_caret_column(), code_edit.get_caret_line()) + if mode == Mode.VISUAL: + var backwards: bool = ( + selection_to.x < selection_from.x + if selection_to.y == selection_from.y + else selection_to.y < selection_from.y + ) + code_edit.select( + selection_from.y, + selection_from.x + int(backwards), + selection_to.y, + selection_to.x + int(!backwards) + ) + 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 is_line_section(text: String) -> bool: + var t: String = text.strip_edges() + + match language: + LANGUAGE.SHADER: + return t.ends_with("{") and !SPACES.contains(text.left(1)) + LANGUAGE.GDSCRIPT: + return ( + t.begins_with("func") + or t.begins_with("static func") + or t.begins_with("class") + or t.begins_with("#region") + ) + _: + return false + + +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() and code_edit.get_selected_text() != "": + selection_from = Vector2i( + code_edit.get_selection_from_column(), code_edit.get_selection_from_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) + + +#region COMMANDS + +#region MOTIONS +# Motion commands must return a Vector2i with the cursor's new position + + +## Moves the cursor horizontally +## Args: +## - "move_by": int +## How many characters to move by +func cmd_move_by_chars(args: Dictionary) -> Vector2i: + return Vector2i(get_column() + args.get("move_by", 0), get_line()) + + +## Moves the cursor vertically +## Args: +## - "move_by": int +## How many lines to move by +func cmd_move_by_lines(args: Dictionary) -> Vector2i: + return Vector2i(get_column(), get_line() + args.get("move_by", 0)) + + +## Moves the cursor vertically by a certain percentage of the screen / page +## Args: +## - "percentage": float +## How much to move by +## E.g. percentage = 0.5 will move down half a screen, +## percentage = -1.0 will move up a full screen +func cmd_move_by_screen(args: Dictionary) -> Vector2i: + var h: int = code_edit.get_visible_line_count() + var amt: int = int(h * args.get("percentage", 0.0)) + return Vector2i(get_column(), get_line() + amt) + + +## Moves the cursor by word +## Args: +## - "forward": bool +## Whether to move forwards (right) or backwards (left) +## - "word_end": bool +## Whether to move to the end of a word +## - "big_word": bool +## Whether to ignore keywords like ";", ",", "." (See KEYWORDS in constants.gd) +func cmd_move_by_word(args: Dictionary) -> Vector2i: + return get_word_edge_pos( + get_line(), + get_column(), + args.get("forward", true), + args.get("word_end", false), + args.get("big_word", false) + ) + + +## Moves the cursor by paragraph +## Args: +## - "forward": bool +## Whether to move forward (down) or backward (up) +func cmd_move_by_paragraph(args: Dictionary) -> Vector2i: + var forward: bool = args.get("forward", false) + var line: int = get_paragraph_edge_pos(get_line(), forward, forward) + return Vector2i(0, line) + + +## Moves the cursor to the start of the line +## This is the VIM equivalent of "0" +func cmd_move_to_bol(_args: Dictionary) -> Vector2i: + return Vector2i(0, get_line()) + + +## Moves the cursor to the end of the line +## This is the VIM equivalent of "$" +func cmd_move_to_eol(args: Dictionary) -> Vector2i: + return Vector2i(get_line_length(), get_line()) + + +## Moves the cursor to the first non-whitespace character in the current line +## This is the VIM equivalent of "^" +func cmd_move_to_first_non_whitespace_char(args: Dictionary) -> Vector2i: + return Vector2i(code_edit.get_first_non_whitespace_column(get_line()), get_line()) + + +## Moves the cursor to the start of the file +## This is the VIM equivalent of "gg" +func cmd_move_to_bof(args: Dictionary) -> Vector2i: + return Vector2i(0, 0) + + +## Moves the cursor to the end of the file +## This is the VIM equivalent of "G" +func cmd_move_to_eof(args: Dictionary) -> Vector2i: + return Vector2i(0, code_edit.get_line_count()) + + +func cmd_move_to_center_of_line(_args: Dictionary) -> Vector2i: + var l: int = get_line() + return Vector2i(get_line_length(l) / 2, l) + + +## Repeats the last '/' search +## This is the VIM equivalent of "n" and "N" +## Args: +## - "forward": bool +## Whether to search down (true) or up (false) +func cmd_find_again(args: Dictionary) -> Vector2i: + if command_line.search_pattern.is_empty(): + return get_caret_pos() + + var rmatch: RegExMatch + if args.get("forward", false): + rmatch = globals.vim_plugin.search_regex( + code_edit, command_line.search_pattern, get_caret_pos() + Vector2i.RIGHT + ) + else: + rmatch = globals.vim_plugin.search_regex_backwards( + code_edit, command_line.search_pattern, get_caret_pos() + Vector2i.LEFT + ) + + if rmatch == null: + return get_caret_pos() + return globals.vim_plugin.idx_to_pos(code_edit, rmatch.get_start()) + + +## Jumps to a character in the current line +## This is the VIM equivalent of f, F, t, ant T +## Args: +## - "selected_char": String +## The character to look for +## - "forward": bool +## Whether to search right (true) or left (false) +## - "stop_before": bool +## Whether to stop before [selected_char] +func cmd_find_in_line(args: Dictionary) -> Vector2i: + var line: int = get_line() + var col: int = find_char_in_line( + line, + get_column(), + args.get("forward", false), + args.get("stop_before", false), + args.get("selected_char", "") + ) + + globals.last_search = args + + if col >= 0: + return Vector2i(col, line) + return Vector2i(get_column(), line) + + +## Repeats the last inline search +## This is the VIM equivalent of ";" and "," +## Args: +## - "invert": bool +## Whether search in the opposite direction of the last search +func cmd_find_in_line_again(args_mut: Dictionary) -> Vector2i: + # 'mut' ('mutable') because 'args' will be changed + # The reason for that is because the arg 'inclusive' is dependant on the last search + # and will be used with Operators + if !globals.has("last_search"): + return get_caret_pos() + + var last_search: Dictionary = globals.last_search + var line: int = get_line() + var col: int = find_char_in_line( + line, + get_column(), + last_search.get("forward", false) != args_mut.get("invert", false), # Invert 'forward' if necessary (xor) + last_search.get("stop_before", false), + last_search.get("selected_char", "") + ) + + args_mut.inclusive = globals.last_search.get("inclusive", false) + if col >= 0: + return Vector2i(col, line) + return Vector2i(get_column(), line) + + +## Moves the cursor by section (VIM equivalent of [[ and ]]) +## Sections are defined by the following keywords: +## - "func" +## - "class" +## - "#region" +## See also is_line_section() +## +## Args: +## - "forward": bool +## Whether to move forward (down) or backward (up) +func cmd_move_by_section(args: Dictionary) -> Vector2i: + var section_edge: Vector2i = get_section_edge_pos(get_line(), args.get("forward", false)) + return section_edge + + +## Corresponds to the % motion in VIM +func cmd_jump_to_next_brace_pair(_args: Dictionary) -> Vector2i: + const PAIRS = Constants.PAIRS + var p: Vector2i = get_caret_pos() + + var p0: Vector2i = find_next_occurence_of_chars(p.y, p.x, Constants.BRACES, true) + # Not found + if p0.x < 0 or p0.y < 0: + return p + + var brace: String = code_edit.get_line(p0.y)[p0.x] + # Whether this brace is an opening or closing brace. i.e. ([{ or }]) + var is_closing_brace: bool = PAIRS.values().has(brace) + var closing_counterpart: String = "" + if is_closing_brace: + var idx: int = PAIRS.values().find(brace) + if idx != -1: + closing_counterpart = brace + brace = PAIRS.keys()[idx] + else: + closing_counterpart = PAIRS.get(brace, "") + if closing_counterpart.is_empty(): + push_error("[GodotVIM] Failed to get counterpart for brace: ", brace) + return p + + var p1: Vector2i = ( + find_brace(p0.y, p0.x, closing_counterpart, brace, false) + if is_closing_brace + else find_brace(p0.y, p0.x, brace, closing_counterpart, true) + ) + + if p1 == Vector2i(-1, -1): + return p0 + + return p1 + + +#region TEXT OBJECTS +# Text Object commands must return two Vector2is with the cursor start and end position + + +## Get the bounds of the text object specified in args +## Args: +## - "object": String +## The text object to select. Should ideally be a key in constants.gd::PAIRS but doesn't have to +## If "object" is not in constants.gd::PAIRS, then "counterpart" must be specified +## - "counterpart": String (optional) +## The end key of the text object +## - "inline": bool (default = false) +## Forces the search to occur only in the current line +## - "around": bool (default = false) +## Whether to select around (e.g. ab, aB, a[, a] in VIM) +func cmd_text_object(args: Dictionary) -> Array[Vector2i]: + var p: Vector2i = get_caret_pos() + + # Get start and end keys + if !args.has("object"): + push_error("[GodotVim] Error on cmd_text_object: No object selected") + return [p, p] + + var obj: String = args.object + var counterpart: String + if args.has("counterpart"): + counterpart = args.counterpart + elif Constants.PAIRS.has(obj): + counterpart = Constants.PAIRS[obj] + else: + push_error( + '[GodotVim] Error on cmd_text_object: Invalid brace pair: "', + obj, + '". You can specify an end key with the argument `counterpart: String`' + ) + return [p, p] + + var inline: bool = args.get("inline", false) + + # Deal with edge case where the cursor is already on the end + var p0x = p.x - 1 if get_char_at(p.y, p.x) == counterpart else p.x + # Look backwards to find start + var p0: Vector2i = find_brace(p.y, p0x, counterpart, obj, false, inline) + + # Not found; try to look forward then + if p0.x == -1: + if inline: + var col: int = find_char_in_line(p.y, p0x, true, false, obj) + p0 = Vector2i(col, p.y) + else: + p0 = find_next_occurence_of_chars(p.y, p0x, obj, true) + + if p0.x == -1: + return [p, p] + + # Look forwards to find end + var p1: Vector2i = find_brace(p0.y, p0.x, obj, counterpart, true, inline) + + if p1 == Vector2i(-1, -1): + return [p, p] + + if args.get("around", false): + return [p0, p1] + return [p0 + Vector2i.RIGHT, p1 + Vector2i.LEFT] + + +## Corresponds to the iw, iW, aw, aW motions in regular VIM +## Args: +## - "around": bool (default = false) +## Whether to select around words (aw, aW in VIM) +## - "big_word": bool (default = false) +## Whether this is a big word motion (iW, aW in VIM) +func cmd_text_object_word(args: Dictionary) -> Array[Vector2i]: + var is_big_word: bool = args.get("big_word", false) + + var p: Vector2i = get_caret_pos() + var p0 = get_word_edge_pos(p.y, p.x + 1, false, false, is_big_word) + var p1 = get_word_edge_pos(p.y, p.x - 1, true, true, is_big_word) + + if !args.get("around", false): + # Inner word (iw, iW) + return [p0, p1] + + # Around word (aw, aW) + var text: String = get_line_text(p.y) + # Whether char to the RIGHT is a space + var next_char_is_space: bool = SPACES.contains(text[mini(p1.x + 1, text.length() - 1)]) + if next_char_is_space: + p1.x = get_word_edge_pos(p1.y, p1.x, true, false, false).x - 1 + return [p0, p1] + + # Whether char to the LEFT is a space + next_char_is_space = SPACES.contains(text[maxi(p0.x - 1, 0)]) + if next_char_is_space: + p0.x = get_word_edge_pos(p0.y, p0.x, false, true, false).x + 1 + return [p0, p1] + + return [p0, p1] + + +## Warning: changes the current mode to VISUAL_LINE +## Args: +## - "around": bool (default = false) +## Whether to select around paragraphs (ap in VIM) +func cmd_text_object_paragraph(args: Dictionary) -> Array[Vector2i]: + var p: Vector2i = get_caret_pos() + var p0: Vector2i = Vector2i(0, get_paragraph_edge_pos(p.y, false, false) + 1) + var p1: Vector2i = Vector2i(0, get_paragraph_edge_pos(p.y, true, true) - 1) + + if !args.get("around", false): + # Inner paragraph (ip) + set_mode(Mode.VISUAL_LINE) + return [p0, p1] + + # Extend downwards + if p1.y < code_edit.get_line_count() - 1: + p1.y = get_paragraph_edge_pos(p1.y, true, false) + # Extend upwards + elif p0.y > 0: + p0.y = get_paragraph_edge_pos(p0.y - 1, false, true) + + set_mode(Mode.VISUAL_LINE) + return [p0, p1] + + +#endregion TEXT OBJECTS + +#endregion MOTIONS + +#region ACTIONS + + +## Enters Insert mode +## Args: +## - (optional) "offset": String +## Either of: +## "after": Enter insert mode after the selected character (VIM equivalent: a) +## "bol": Enter insert mode at the beginning of this line (VIM equivalent: I) +## "eol": Enter insert mode at the end of this line (VIM equivalent: A) +## "new_line_below": Insert at a new line below (VIM equivalent: o) +## "new_line_above": Insert at a new line above (VIM equivalent: O) +## defaults to "in_place": Enter insert mode before the selected character (VIM equivalent: i) +func cmd_insert(args: Dictionary): + set_mode(Mode.INSERT) + var offset: String = args.get("offset", "in_place") + + if offset == "after": + move_column(1) + elif offset == "bol": + set_column(code_edit.get_first_non_whitespace_column(get_line())) + elif offset == "eol": + set_column(get_line_length()) + elif offset == "new_line_below": + var line: int = code_edit.get_caret_line() + var ind: int = ( + code_edit.get_first_non_whitespace_column(line) + + int(code_edit.get_line(line).ends_with(":")) + ) + 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) + elif offset == "new_line_above": + var ind: int = code_edit.get_first_non_whitespace_column(code_edit.get_caret_line()) + code_edit.insert_line_at(code_edit.get_caret_line(), "\t".repeat(ind)) + move_line(-1) + set_column(ind) + set_mode(Mode.INSERT) + + +## Switches to Normal mode +## Args: +## - (optional) "backspaces" : int +## Number of times to backspace (e.g. once with 'jk') +## - (optional) "offset" : int +## How many colums to move the caret +func cmd_normal(args: Dictionary): + for __ in args.get("backspaces", 0): + code_edit.backspace() + reset_normal() + if args.has("offset"): + move_column(args.offset) + + +## Switches to Visual mode +## if "line_wise": bool (optional) is true, it will switch to VisualLine instead +func cmd_visual(args: Dictionary): + if args.get("line_wise", false): + set_mode(Mode.VISUAL_LINE) + else: + set_mode(Mode.VISUAL) + + +## Switches the current mode to COMMAND mode +## Args: +## - Empty -> Enter command mode normally +## - { "command" : "[cmd]" } -> Enter command mode with the command "[cmd]" already typed in +func cmd_command(args: Dictionary): + set_mode(Mode.COMMAND) + if args.has("command"): + command_line.set_command(args.command) + else: + command_line.set_command(":") + + +func cmd_undo(_args: Dictionary): + code_edit.undo() + set_mode(Mode.NORMAL) + + +func cmd_redo(_args: Dictionary): + code_edit.redo() + if mode != Mode.NORMAL: + set_mode(Mode.NORMAL) + + +## Join the current line with the next one +func cmd_join(_args: Dictionary): + var line: int = code_edit.get_caret_line() + code_edit.begin_complex_operation() + code_edit.select( + line, get_line_length(), line + 1, code_edit.get_first_non_whitespace_column(line + 1) + ) + code_edit.delete_selection() + code_edit.deselect() + code_edit.insert_text_at_caret(" ") + code_edit.end_complex_operation() + + +## Centers the cursor on the screen +func cmd_center_caret(_args: Dictionary): + code_edit.center_viewport_to_caret() + + +## Replace the current character with [selected_char] +## Args: +## - "selected_char": String +## as is processed in KeyMap::event_to_string() +func cmd_replace(args: Dictionary): + var char: String = args.get("selected_char", "") + if char.begins_with(""): + char = "\n" + elif char.begins_with(""): + char = "\t" + + code_edit.begin_complex_operation() + code_edit.delete_selection() + code_edit.insert_text_at_caret(char) + move_column(-1) + code_edit.end_complex_operation() + + +## For now, all marks are global +func cmd_mark(args: Dictionary): + if !args.has("selected_char"): + push_error("[GodotVIM] Error on cmd_mark(): No char selected") + return + + if !globals.has("marks"): + globals.marks = {} + var m: String = args.selected_char + var unicode: int = m.unicode_at(0) + if (unicode < 65 or unicode > 90) and (unicode < 97 or unicode > 122): + # We use call_deferred because otherwise, the error gets overwritten at the end of _input() + status_bar.call_deferred(&"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": get_caret_pos() + } + status_bar.call_deferred(&"display_text", 'Mark "%s" set' % m) + + +func cmd_jump_to_mark(args: Dictionary): + if !args.has("selected_char"): + push_error("[GodotVIM] Error on cmd_jump_to_mark(): No char selected") + return + if !globals.has("marks"): + globals.marks = {} + + var m: String = args.selected_char + if !globals.marks.has(m): + status_bar.display_error('Mark "%s" not set' % m) + return + var mark: Dictionary = globals.marks[m] + globals.vim_plugin.edit_script(mark.file, mark.pos + Vector2i(0, 1)) + code_edit.call_deferred(&"center_viewport_to_caret") + + +#endregion ACTIONS + +#region OPERATIONS + + +## Delete a selection +## Corresponds to "d" in regular VIM +func cmd_delete(args: Dictionary): + if args.get("line_wise", false): + var l0: int = code_edit.get_selection_from_line() + var l1: int = code_edit.get_selection_to_line() + code_edit.select(l0 - 1, get_line_length(l0 - 1), l1, get_line_length(l1)) + call_deferred(&"move_line", +1) + + code_edit.cut() + + if mode != Mode.NORMAL: + set_mode(Mode.NORMAL) + + +## Copies (yanks) a selection +## Corresponds to "y" in regular VIM +func cmd_yank(args: Dictionary): + if args.get("line_wise", false): + var l0: int = code_edit.get_selection_from_line() + var l1: int = code_edit.get_selection_to_line() + code_edit.select(l0 - 1, get_line_length(l0 - 1), l1, get_line_length(l1)) + + code_edit.copy() + code_edit.deselect() + if mode != Mode.NORMAL: + set_mode(Mode.NORMAL) + + +## Changes a selection +## Corresponds to "c" in regular VIM +func cmd_change(args: Dictionary): + if args.get("line_wise", false): + var l0: int = code_edit.get_selection_from_line() + var l1: int = code_edit.get_selection_to_line() + + code_edit.select(l0, code_edit.get_first_non_whitespace_column(l0), l1, get_line_length(l1)) + + code_edit.cut() + set_mode(Mode.INSERT) + + +func cmd_paste(_args: Dictionary): + code_edit.begin_complex_operation() + + if !is_mode_visual(mode): + 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) + + +## Indents or unindents the selected line(s) by 1 level +## Corresponds to >> or << in regular VIM +## Args: +## - (optional) "forward": whether to indent *in*. Defaults to false +func cmd_indent(args: Dictionary): + if args.get("forward", false): + code_edit.indent_lines() + else: + code_edit.unindent_lines() + set_mode(Mode.NORMAL) + + +## Toggles whether the selected line(s) are commented +func cmd_comment(_args: Dictionary): + var l0: int = code_edit.get_selection_from_line() + var l1: int = code_edit.get_selection_to_line() + var do_comment: bool = !is_line_commented(mini(l0, l1)) + + code_edit.begin_complex_operation() + for line in range(mini(l0, l1), maxi(l0, l1) + 1): + set_line_commented(line, do_comment) + code_edit.end_complex_operation() + + set_mode(Mode.NORMAL) + + +## Sets the selected text to uppercase or lowercase +## Args: +## - "uppercase": bool (default: false) +## Whether to toggle uppercase or lowercase +func cmd_set_uppercase(args: Dictionary): + var text: String = code_edit.get_selected_text() + if args.get("uppercase", false): + code_edit.insert_text_at_caret(text.to_upper()) + else: + code_edit.insert_text_at_caret(text.to_lower()) + + set_mode(Mode.NORMAL) + + +## Toggles the case of the selectex text +func cmd_toggle_uppercase(_args: Dictionary): + var text: String = code_edit.get_selected_text() + for i in text.length(): + var char: String = text[i] + if is_uppercase(char): + text[i] = char.to_lower() + else: + text[i] = char.to_upper() + code_edit.insert_text_at_caret(text) + + set_mode(Mode.NORMAL) + + +#endregion OPERATIONS + + +## Corresponds to 'o' in Visual mode in regular Vim +func cmd_visual_jump_to_other_end(args: Dictionary): + if !is_mode_visual(mode): + push_warning( + "[GodotVim] Attempting to jump to other end of selection while not in VISUAL mode. Ignoring..." + ) + return + + var p: Vector2i = selection_from + selection_from = get_caret_pos() + set_caret_pos(p.y, p.x) +#endregion COMMANDS diff --git a/addons/godot_vim/dispatcher.gd b/addons/godot_vim/dispatcher.gd new file mode 100644 index 0000000..a8c3a63 --- /dev/null +++ b/addons/godot_vim/dispatcher.gd @@ -0,0 +1,39 @@ +extends Object + +var handlers: Dictionary = { + "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"), + "delmarks": preload("res://addons/godot_vim/commands/delmarks.gd"), + "moveline": preload("res://addons/godot_vim/commands/moveline.gd"), + "movecolumn": preload("res://addons/godot_vim/commands/movecolumn.gd"), + "w": preload("res://addons/godot_vim/commands/w.gd"), + "wa": preload("res://addons/godot_vim/commands/wa.gd"), + # GodotVIM speficic commands: + "reload": preload("res://addons/godot_vim/commands/reload.gd"), + "remap": preload("res://addons/godot_vim/commands/remap.gd"), +} + +var aliases: Dictionary = {"delm": ":delmarks"} + +var globals: Dictionary + + +## Returns [enum @GlobalScope.Error] +func dispatch(command: String, do_allow_aliases: bool = true) -> int: + var command_idx_end: int = command.find(" ", 1) + if command_idx_end == -1: + command_idx_end = command.length() + var handler_name: String = command.substr(1, command_idx_end - 1) + + if do_allow_aliases and aliases.has(handler_name): + return dispatch(aliases[handler_name], false) + + if not handlers.has(handler_name): + return ERR_DOES_NOT_EXIST + + var handler = handlers.get(handler_name) + var handler_instance = handler.new() + var args: String = command.substr(command_idx_end, command.length()) + handler_instance.execute(globals, args) + return OK diff --git a/addons/godot_vim/hack_regular.ttf b/addons/godot_vim/hack_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..92a90cb06e0535afa79f6cba26ceece15a7f7959 GIT binary patch literal 309408 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

lipX z89%t)L1Zn7w#;-}USgg#E*&nk6g|(G1Qmus3tNsjIhevgp16@5CCac&oFQ%eMM}nZ z{mnz>s34v3aMz*q^t7z0vk!G1GW!Jt2ASxcw2bWTp8MFWsimbQCDW&tl$Dk-0g1n8B}ozJZoF0r$e^J`a*sp<@okRTsO%H2(}g3TTkA_r zY_P>jVzWUR3KUay?+f%NC1?ezWXd19?3;y;D2) z=Jd@SxaBCD7@yRdoI1@I*B<-wy!p$P-g4A5y{O1h(7URyq%^-^5%4=S<)}GLXGT&? zP*KLzTkhzZ9`LuniN0E|5dVh^Rxh7GQJxUnGxL_)=k$BaC)IDL^7E%p&b9}+_DF?A zGiGGlx&2!bzLjrRQ-8rQ!6d{9w;c=hLS+CVTojp^4u~Lz7RS90h8bLbuiryo}eM%KoVvP^fjleHU^+=q5A;2uIP{IMn8hjx?CUO%x=LY*K$e zUzje{LKq99L~=Fia#OW;19`##VP1~Mrtvh>B0Gh6=r^B#@tfaImET5%z7r=71y6sR z1)+KIKbbhmHDqK(Opkw#A(lJ1ckkf2b32FbV2>ybjO9me1|^F}-I933OjHUpo!ybB za2SP5ev0ZSLtRNH0zA}Iy~GRPK;d0)CZZjOFGZkHpcnj=aBX`konu{0C@R1fXWJ6u zt>zd5de9(qgvu{hvPyqiP=X*?9QK49o6V7vmy2IXc%isb1#ifGsWofO;PWB)4-5)1 z;7{NZNdd%*kTmtr&*fqJ!&%FTzlevj)`WLeZu_9MsiDqXpU^aM&*aJB?87X%2GSC~ z2D0Q+hRKs}Y;8)eG1oRU8MQ2k#V^zZ`IMHcL&f>K|5&>xBqaImm<(OqoH=jI_YU>b z>SAO4B%e^+h{dN=?PS~(gl+~lg! zO4x6Syg#q!XWP@GGQ%@7XD^FdKFsu40i{{Jdo!~$as#PyNTj_fx=|f«q(At`*`NbkV(;WAZRz)Jn7&p-cM>U1p@ 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 D9ti__ literal 0 HcmV?d00001 diff --git a/addons/godot_vim/hack_regular.ttf.import b/addons/godot_vim/hack_regular.ttf.import new file mode 100644 index 0000000..dae8cc6 --- /dev/null +++ b/addons/godot_vim/hack_regular.ttf.import @@ -0,0 +1,34 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://4q1f672e7ux2" +path="res://.godot/imported/hack_regular.ttf-0fe21890026c2274f233cdc75cf86ba7.fontdata" + +[deps] + +source_file="res://addons/godot_vim/hack_regular.ttf" +dest_files=["res://.godot/imported/hack_regular.ttf-0fe21890026c2274f233cdc75cf86ba7.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/addons/godot_vim/key_map.gd b/addons/godot_vim/key_map.gd new file mode 100644 index 0000000..79486e7 --- /dev/null +++ b/addons/godot_vim/key_map.gd @@ -0,0 +1,741 @@ +class_name KeyMap extends RefCounted +## Hanldes input stream and key mapping +## +## You may also set your keybindings in the [method map] function + + +## * SET YOUR KEYBINDINGS HERE * +## Also see the "COMMANDS" section at the bottom of cursor.gd +## E.g. the command for +## KeyRemap.new(...) .motion("foo", { "bar": 1 }) +## is handled in Cursor::cmd_foo(args: Dictionary) +## where `args` is `{ "type": "foo", "bar": 1 }` +## Example: +## [codeblock] +## return [ +## # Move 5 characters to the right with "L" +## KeyRemap.new([ "L" ]) +## .motion("move_by_chars", { "move_by": 5 }), +## +## # Let's remove "d" (the delete operator) and replace it with "q" +## # You may additionally specify the type and context of the cmd to remove +## # using .operator() (or .motion() or .action() etc...) and .with_context() +## KeyRemap.new([ "d" ]) +## .remove(), +## # "q" is now the new delete operator +## KeyRemap.new([ "q" ]) +## .operator("delete"), +## +## # Delete this line along with the next two with "Z" +## # .operator() and .motion() automatically merge together +## KeyRemap.new([ "Z" ]) +## .operator("delete") +## .motion("move_by_lines", { "move_by": 2, "line_wise": true }), +## +## # In Insert mode, return to Normal mode with "jk" +## KeyRemap.new([ "j", "k" ]) +## .action("normal", { "backspaces": 1, "offset": 1 }) +## .with_context(Mode.INSERT), +## ] +## [/codeblock] +static func map() -> Array[KeyRemap]: + # Example: + return [ + # In Insert mode, return to Normal mode with "jk" + # KeyRemap.new([ "j", "k" ]) + # .action("normal", { "backspaces": 1, "offset": 0 }) + # .with_context(Mode.INSERT), + # Make "/" search in case insensitive mode + # KeyRemap.new([ "/" ]) + # .action("command", { "command": "/(?i)" }) + # .replace(), + # In Insert mode, return to Normal mode with "Ctrl-[" + # KeyRemap.new([ "" ]) + # .action("normal") + # .with_context(Mode.INSERT), + ] + + +const Constants = preload("res://addons/godot_vim/constants.gd") +const Mode = Constants.Mode + +const INSERT_MODE_TIMEOUT_MS: int = 700 + +enum { + ## Moves the cursor. Can be used in tandem with Operator + Motion, + ## Operators (like delete, change, yank) work on selections + ## In Normal mode, they need a Motion or another Operator bound to them (e.g. dj, yy) + Operator, + ## Operator but with a motion already bound to it + ## Can only be executed in Normal mode + OperatorMotion, + ## A single action (e.g. i, o, v, J, u) + ## Cannot be executed in Visual mode unless specified with "context": Mode.VISUAL + Action, + Incomplete, ## Incomplete command + NotFound, ## Command not found +} + +#region key_map + +# Also see the "COMMANDS" section at the bottom of cursor.gd +# Command for { "type": "foo", ... } is handled in Cursor::cmd_foo(args: Dictionary) +# where `args` is ^^^^^ this Dict ^^^^^^ +var key_map: Array[Dictionary] = [ + # MOTIONS + {"keys": ["h"], "type": Motion, "motion": {"type": "move_by_chars", "move_by": -1}}, + {"keys": ["l"], "type": Motion, "motion": {"type": "move_by_chars", "move_by": 1}}, + { + "keys": ["j"], + "type": Motion, + "motion": {"type": "move_by_lines", "move_by": 1, "line_wise": true} + }, + { + "keys": ["k"], + "type": Motion, + "motion": {"type": "move_by_lines", "move_by": -1, "line_wise": true} + }, + # About motions: the argument `inclusive` is used with Operators (see execute_operator_motion()) + { + "keys": ["w"], + "type": Motion, + "motion": {"type": "move_by_word", "forward": true, "word_end": false} + }, + { + "keys": ["e"], + "type": Motion, + "motion": {"type": "move_by_word", "forward": true, "word_end": true, "inclusive": true} + }, + { + "keys": ["b"], + "type": Motion, + "motion": {"type": "move_by_word", "forward": false, "word_end": false} + }, + { + "keys": ["g", "e"], + "type": Motion, + "motion": {"type": "move_by_word", "forward": false, "word_end": true} + }, + { + "keys": ["W"], + "type": Motion, + "motion": {"type": "move_by_word", "forward": true, "word_end": false, "big_word": true} + }, + { + "keys": ["E"], + "type": Motion, + "motion": + { + "type": "move_by_word", + "forward": true, + "word_end": true, + "big_word": true, + "inclusive": true + } + }, + { + "keys": ["B"], + "type": Motion, + "motion": {"type": "move_by_word", "forward": false, "word_end": false, "big_word": true} + }, + { + "keys": ["g", "E"], + "type": Motion, + "motion": {"type": "move_by_word", "forward": false, "word_end": true, "big_word": true} + }, + # Find & search + { + "keys": ["f", "{char}"], + "type": Motion, + "motion": {"type": "find_in_line", "forward": true, "inclusive": true} + }, + { + "keys": ["t", "{char}"], + "type": Motion, + "motion": {"type": "find_in_line", "forward": true, "stop_before": true, "inclusive": true} + }, + {"keys": ["F", "{char}"], "type": Motion, "motion": {"type": "find_in_line", "forward": false}}, + { + "keys": ["T", "{char}"], + "type": Motion, + "motion": {"type": "find_in_line", "forward": false, "stop_before": true} + }, + {"keys": [";"], "type": Motion, "motion": {"type": "find_in_line_again", "invert": false}}, + {"keys": [","], "type": Motion, "motion": {"type": "find_in_line_again", "invert": true}}, + {"keys": ["n"], "type": Motion, "motion": {"type": "find_again", "forward": true}}, + {"keys": ["N"], "type": Motion, "motion": {"type": "find_again", "forward": false}}, + {"keys": ["0"], "type": Motion, "motion": {"type": "move_to_bol"}}, + {"keys": ["$"], "type": Motion, "motion": {"type": "move_to_eol"}}, + {"keys": ["^"], "type": Motion, "motion": {"type": "move_to_first_non_whitespace_char"}}, + { + "keys": ["{"], + "type": Motion, + "motion": {"type": "move_by_paragraph", "forward": false, "line_wise": true} + }, + { + "keys": ["}"], + "type": Motion, + "motion": {"type": "move_by_paragraph", "forward": true, "line_wise": true} + }, + { + "keys": ["[", "["], + "type": Motion, + "motion": {"type": "move_by_section", "forward": false, "line_wise": true} + }, + { + "keys": ["]", "]"], + "type": Motion, + "motion": {"type": "move_by_section", "forward": true, "line_wise": true} + }, + {"keys": ["g", "g"], "type": Motion, "motion": {"type": "move_to_bof", "line_wise": true}}, + {"keys": ["G"], "type": Motion, "motion": {"type": "move_to_eof", "line_wise": true}}, + {"keys": ["g", "m"], "type": Motion, "motion": {"type": "move_to_center_of_line"}}, + { + "keys": [""], + "type": Motion, + "motion": {"type": "move_by_screen", "percentage": -0.5, "line_wise": true} + }, + { + "keys": [""], + "type": Motion, + "motion": {"type": "move_by_screen", "percentage": 0.5, "line_wise": true} + }, + {"keys": ["%"], "type": Motion, "motion": {"type": "jump_to_next_brace_pair"}}, + # TEXT OBJECTS + { + "keys": ["a", "w"], + "type": Motion, + "motion": {"type": "text_object_word", "around": true, "inclusive": true} + }, + { + "keys": ["a", "W"], + "type": Motion, + "motion": {"type": "text_object_word", "around": true, "inclusive": true} + }, + {"keys": ["i", "w"], "type": Motion, "motion": {"type": "text_object_word", "inclusive": true}}, + { + "keys": ["i", "W"], + "type": Motion, + "motion": {"type": "text_object_word", "big_word": true, "inclusive": true} + }, + { + "keys": ["i", "p"], + "type": Motion, + "motion": {"type": "text_object_paragraph", "line_wise": true} + }, + { + "keys": ["a", "p"], + "type": Motion, + "motion": {"type": "text_object_paragraph", "around": true, "line_wise": true} + }, + { + "keys": ["i", '"'], + "type": Motion, + "motion": {"type": "text_object", "object": '"', "inclusive": true, "inline": true} + }, + { + "keys": ["a", '"'], + "type": Motion, + "motion": + {"type": "text_object", "object": '"', "around": true, "inclusive": true, "inline": true} + }, + { + "keys": ["i", "'"], + "type": Motion, + "motion": {"type": "text_object", "object": "'", "inclusive": true, "inline": true} + }, + { + "keys": ["a", "'"], + "type": Motion, + "motion": + {"type": "text_object", "object": "'", "around": true, "inclusive": true, "inline": true} + }, + { + "keys": ["i", "`"], + "type": Motion, + "motion": {"type": "text_object", "object": "`", "inclusive": true, "inline": true} + }, + { + "keys": ["a", "`"], + "type": Motion, + "motion": + {"type": "text_object", "object": "`", "around": true, "inclusive": true, "inline": true} + }, + # "i" + any of "(", ")", or "b" + { + "keys": ["i", ["(", ")", "b"]], + "type": Motion, + "motion": {"type": "text_object", "object": "(", "inclusive": true} + }, + { + "keys": ["a", ["(", ")", "b"]], + "type": Motion, + "motion": {"type": "text_object", "object": "(", "around": true, "inclusive": true} + }, + { + "keys": ["i", ["[", "]"]], + "type": Motion, + "motion": {"type": "text_object", "object": "[", "inclusive": true} + }, + { + "keys": ["a", ["[", "]"]], + "type": Motion, + "motion": {"type": "text_object", "object": "[", "around": true, "inclusive": true} + }, + { + "keys": ["i", ["{", "}", "B"]], + "type": Motion, + "motion": {"type": "text_object", "object": "{", "inclusive": true} + }, + { + "keys": ["a", ["{", "}", "B"]], + "type": Motion, + "motion": {"type": "text_object", "object": "{", "around": true, "inclusive": true} + }, + # OPERATORS + {"keys": ["d"], "type": Operator, "operator": {"type": "delete"}}, + { + "keys": ["D"], + "type": OperatorMotion, + "operator": {"type": "delete"}, + "motion": {"type": "move_to_eol"} + }, + { + "keys": ["x"], + "type": OperatorMotion, + "operator": {"type": "delete"}, + "motion": {"type": "move_by_chars", "move_by": 1} + }, + {"keys": ["x"], "type": Operator, "context": Mode.VISUAL, "operator": {"type": "delete"}}, + {"keys": ["y"], "type": Operator, "operator": {"type": "yank"}}, + { + "keys": ["Y"], + "type": OperatorMotion, + "operator": {"type": "yank", "line_wise": true}, # No motion. Same as yy + }, + {"keys": ["c"], "type": Operator, "operator": {"type": "change"}}, + { + "keys": ["C"], + "type": OperatorMotion, + "operator": {"type": "change"}, + "motion": {"type": "move_to_eol"} + }, + { + "keys": ["s"], + "type": OperatorMotion, + "operator": {"type": "change"}, + "motion": {"type": "move_by_chars", "move_by": 1} + }, + {"keys": ["s"], "type": Operator, "context": Mode.VISUAL, "operator": {"type": "change"}}, + { + "keys": ["p"], + "type": OperatorMotion, + "operator": {"type": "paste"}, + "motion": {"type": "move_by_chars", "move_by": 1} + }, + {"keys": ["p"], "type": Operator, "context": Mode.VISUAL, "operator": {"type": "paste"}}, + {"keys": ["p"], "type": Operator, "context": Mode.VISUAL_LINE, "operator": {"type": "paste"}}, + {"keys": [">"], "type": Operator, "operator": {"type": "indent", "forward": true}}, + {"keys": ["<"], "type": Operator, "operator": {"type": "indent", "forward": false}}, + { + "keys": ["g", "c", "c"], + "type": OperatorMotion, + "operator": {"type": "comment"}, + "motion": {"type": "move_by_chars", "move_by": 1} + }, + {"keys": ["g", "c"], "type": Operator, "operator": {"type": "comment"}}, + { + "keys": ["~"], + "type": OperatorMotion, + "operator": {"type": "toggle_uppercase"}, + "motion": {"type": "move_by_chars", "move_by": 1} + }, + { + "keys": ["~"], + "type": Operator, + "context": Mode.VISUAL, + "operator": {"type": "toggle_uppercase"} + }, + { + "keys": ["u"], + "type": Operator, + "context": Mode.VISUAL, + "operator": {"type": "set_uppercase", "uppercase": false} + }, + { + "keys": ["U"], + "type": Operator, + "context": Mode.VISUAL, + "operator": {"type": "set_uppercase", "uppercase": true} + }, + { + "keys": ["V"], + "type": Operator, + "context": Mode.VISUAL, + "operator": {"type": "visual", "line_wise": true} + }, + { + "keys": ["v"], + "type": Operator, + "context": Mode.VISUAL_LINE, + "operator": {"type": "visual", "line_wise": false} + }, + # ACTIONS + {"keys": ["i"], "type": Action, "action": {"type": "insert"}}, + {"keys": ["a"], "type": Action, "action": {"type": "insert", "offset": "after"}}, + {"keys": ["I"], "type": Action, "action": {"type": "insert", "offset": "bol"}}, + {"keys": ["A"], "type": Action, "action": {"type": "insert", "offset": "eol"}}, + {"keys": ["o"], "type": Action, "action": {"type": "insert", "offset": "new_line_below"}}, + {"keys": ["O"], "type": Action, "action": {"type": "insert", "offset": "new_line_above"}}, + {"keys": ["v"], "type": Action, "action": {"type": "visual"}}, + {"keys": ["V"], "type": Action, "action": {"type": "visual", "line_wise": true}}, + {"keys": ["u"], "type": Action, "action": {"type": "undo"}}, + {"keys": [""], "type": Action, "action": {"type": "redo"}}, + {"keys": ["r", "{char}"], "type": Action, "action": {"type": "replace"}}, + {"keys": [":"], "type": Action, "action": {"type": "command"}}, + {"keys": ["/"], "type": Action, "action": {"type": "command", "command": "/"}}, + {"keys": ["J"], "type": Action, "action": {"type": "join"}}, + {"keys": ["z", "z"], "type": Action, "action": {"type": "center_caret"}}, + {"keys": ["m", "{char}"], "type": Action, "action": {"type": "mark"}}, + {"keys": ["`", "{char}"], "type": Action, "action": {"type": "jump_to_mark"}}, + # MISCELLANEOUS + { + "keys": ["o"], + "type": Operator, + "context": Mode.VISUAL, + "operator": {"type": "visual_jump_to_other_end"} + }, + { + "keys": ["o"], + "type": Operator, + "context": Mode.VISUAL_LINE, + "operator": {"type": "visual_jump_to_other_end"} + }, +] + +#endregion key_map + +# Keys we won't handle +const BLACKLIST: Array[String] = [ + "", # Save + "", # Bookmark +] + +enum KeyMatch { + None = 0, # Keys don't match + Partial = 1, # Keys match partially + Full = 2, # Keys match totally +} + +var input_stream: Array[String] = [] +var cursor: Control +var last_insert_mode_input_ms: int = 0 + + +func _init(cursor_: Control): + cursor = cursor_ + apply_remaps(KeyMap.map()) + + +## Returns: Dictionary with the found command: { "type": Motion or Operator or OperatorMotion or Action or Incomplete or NotFound, ... } +## Warning: the returned Dict can be empty in if the event wasn't processed +func register_event(event: InputEventKey, with_context: Mode) -> Dictionary: + # Stringify event + var ch: String = event_to_string(event) + if ch.is_empty(): + return {} # Invalid + if BLACKLIST.has(ch): + return {} + + # Handle Insert mode timeout + if with_context == Mode.INSERT: + if handle_insert_mode_timeout(): + clear() + return {} + + # Process input stream + # print("[KeyMap::register_event()] ch = ", ch) # DEBUG + input_stream.append(ch) + var cmd: Dictionary = parse_keys(input_stream, with_context) + if !is_cmd_valid(cmd): + return {"type": NotFound} + + execute(cmd) + return cmd + + +func parse_keys(keys: Array[String], with_context: Mode) -> Dictionary: + var blacklist: Array = get_blacklist_types_in_context(with_context) + var cmd: Dictionary = find_cmd(keys, with_context, blacklist) + if cmd.is_empty() or cmd.type == NotFound: + call_deferred(&"clear") + return cmd + if cmd.type == Incomplete: + # print(cmd) + return cmd + + # Execute the operation as-is if in VISUAL mode + # If in NORMAL mode, await further input + if cmd.type == Operator and with_context == Mode.NORMAL: + var op_args: Array[String] = keys.slice(cmd.keys.size()) # Get the rest of keys for motion + if op_args.is_empty(): # Incomplete; await further input + return {"type": Incomplete} + + var next: Dictionary = find_cmd(op_args, with_context, [Action, OperatorMotion]) + + if next.is_empty() or next.type == NotFound: # Invalid sequence + call_deferred(&"clear") + return {"type": NotFound} + elif next.type == Incomplete: + return {"type": Incomplete} + + cmd.modifier = next + + call_deferred(&"clear") + return cmd + + +## The returned cmd will always have a 'type' key +# TODO use bitmask instead of Array? +func find_cmd(keys: Array[String], with_context: Mode, blacklist: Array = []) -> Dictionary: + var partial: bool = false # In case none were found + var is_visual: bool = with_context == Mode.VISUAL or with_context == Mode.VISUAL_LINE + + for cmd in key_map: + # FILTERS + # Don't allow anything in Insert mode unless specified + if with_context == Mode.INSERT and cmd.get("context", -1) != Mode.INSERT: + continue + + if blacklist.has(cmd.type): + continue + + # Skip if contexts don't match + if cmd.has("context") and with_context != cmd.context: + continue + + # CHECK KEYS + var m: KeyMatch = match_keys(cmd.keys, keys) + partial = partial or m == KeyMatch.Partial # Set/keep partial = true if it was a partial match + + if m != KeyMatch.Full: + continue + + var cmd_mut: Dictionary = cmd.duplicate(true) # 'mut' ('mutable') because key_map is read-only + # Keep track of selected character, which will later be copied into the fucntion call for the command + # (See execute() where we check if cmd.has('selected_char')) + if cmd.keys[-1] is String and cmd.keys[-1] == "{char}": + cmd_mut.selected_char = keys.back() + return cmd_mut + + return {"type": Incomplete if partial else NotFound} + + +# TODO use bitmask instead of Array? +func get_blacklist_types_in_context(context: Mode) -> Array: + match context: + Mode.VISUAL, Mode.VISUAL_LINE: + return [OperatorMotion, Action] + _: + return [] + + +func execute_operator_motion(cmd: Dictionary): + if cmd.has("motion"): + if cmd.has("selected_char"): + cmd.motion.selected_char = cmd.selected_char + operator_motion(cmd.operator, cmd.motion) + else: + call_cmd(cmd.operator) + + +func execute_operator(cmd: Dictionary): + # print("[KeyMay::execute()] op: ", cmd) # DEBUG + if !cmd.has("modifier"): # Execute as-is + call_cmd(cmd.operator) + return + + var mod: Dictionary = cmd.modifier + # Execute with motion + if mod.type == Motion: + if mod.has("selected_char"): + mod.motion.selected_char = mod.selected_char + operator_motion(cmd.operator, mod.motion) + + # Execute with `line_wise = true` if repeating operations (e.g. dd, yy) + elif mod.type == Operator and mod.operator.type == cmd.operator.type: + var op_cmd: Dictionary = cmd.operator.duplicate() + op_cmd.line_wise = true + call_cmd(op_cmd) + + +func execute_action(cmd: Dictionary): + if cmd.has("selected_char"): + cmd.action.selected_char = cmd.selected_char + call_cmd(cmd.action) + + +func execute_motion(cmd: Dictionary): + if cmd.has("selected_char"): + cmd.motion.selected_char = cmd.selected_char + var pos = call_cmd(cmd.motion) # Vector2i for normal motion, or [Vector2i, Vector2i] for text object + + if pos is Vector2i: + cursor.set_caret_pos(pos.y, pos.x) + elif pos is Array: + assert(pos.size() == 2) + # print("[execute_motion() -> text obj] pos = ", pos) + cursor.select(pos[0].y, pos[0].x, pos[1].y, pos[1].x) + + +func execute(cmd: Dictionary): + if !is_cmd_valid(cmd): + return + + match cmd.type: + Motion: + execute_motion(cmd) + OperatorMotion: + execute_operator_motion(cmd) + Operator: + execute_operator(cmd) + Action: + execute_action(cmd) + _: + push_error("[KeyMap::execute()] Unknown command type: %s" % cmd.type) + + +func operator_motion(operator: Dictionary, motion: Dictionary): + # print("[KeyMay::execute_operator_motion()] op = ", operator, ", motion = ", motion) # DEBUG + + # Execute motion before operation + var p = call_cmd(motion) # Vector2i for normal motion, or [Vector2i, Vector2i] for text object + if p is Vector2i: + var p0: Vector2i = cursor.get_caret_pos() + if motion.get("inclusive", false): + p.x += 1 + cursor.code_edit.select(p0.y, p0.x, p.y, p.x) + elif p is Array: + assert(p.size() == 2) + if motion.get("inclusive", false): + p[1].x += 1 + cursor.code_edit.select(p[0].y, p[0].x, p[1].y, p[1].x) + + # Add line_wise flag if line wise motion + var op: Dictionary = operator.duplicate() + op.line_wise = motion.get("line_wise", false) + call_cmd(op) + + +## Unsafe: does not check if the function exists +func call_cmd(cmd: Dictionary) -> Variant: + var func_name: StringName = StringName("cmd_" + cmd.type) + return cursor.call(func_name, cmd) + + +static func is_cmd_valid(cmd: Dictionary): + return !cmd.is_empty() and cmd.type != Incomplete and cmd.type != NotFound + + +static func event_to_string(event: InputEventKey) -> String: + # Special chars + if event.keycode == KEY_ENTER: + return "" + if event.keycode == KEY_TAB: + return "" + if event.keycode == KEY_ESCAPE: + return "" + + # Ctrl + key + if event.is_command_or_control_pressed(): + if !OS.is_keycode_unicode(event.keycode): + return "" + var c: String = char(event.keycode) + return "" % [c if event.shift_pressed else c.to_lower()] + + # You're not special. + return char(event.unicode) + + +## Matches single command keys +## expected_keys: Array[key: String] or Array[any_of_these_keys: Array[key: String]] +static func match_keys(expected_keys: Array, input_keys: Array) -> KeyMatch: + var in_size: int = input_keys.size() + var ex_size: int = expected_keys.size() + + if expected_keys[-1] is String and expected_keys[-1] == "{char}": + # If everything + {char} matches + if _do_keys_match(input_keys.slice(0, -1), expected_keys.slice(0, -1)): + return KeyMatch.Full + + # If everything up until {char} matches + elif _do_keys_match(input_keys, expected_keys.slice(0, in_size)): + return KeyMatch.Partial + + else: + # Check for full match + if _do_keys_match(input_keys, expected_keys): + return KeyMatch.Full + # Check for incomplete command (e.g. "ge", "gcc") + elif _do_keys_match(input_keys, expected_keys.slice(0, in_size)): + return KeyMatch.Partial + # Cases with operators like "dj", "ce" + elif _do_keys_match(input_keys.slice(0, ex_size), expected_keys) and in_size > ex_size: + return KeyMatch.Full + + return KeyMatch.None + + +## input_keys: Array[key: String] +## expected_keys: Array[key: String] or Array[any_of_these_keys: Array[key: String]] +static func _do_keys_match(input_keys: Array, match_keys: Array) -> bool: + if match_keys.size() != input_keys.size(): + return false + + for i in input_keys.size(): + var key: String = input_keys[i] + if match_keys[i] is String: + if !match_keys[i] == key: + return false + elif match_keys[i] is Array: + if !match_keys[i].has(key): + return false + else: + push_error("expected String or Array[String], found ", match_keys[i]) + return false + + return true + + +## Clears the input stream +func clear(): + input_stream = [] + + +func get_input_stream_as_string() -> String: + return "".join(PackedStringArray(input_stream)) + + +## Returns whether the Insert mode input has timed out, in which case we +## don't want to process it +func handle_insert_mode_timeout() -> bool: + var current_tick_ms: int = Time.get_ticks_msec() + + if input_stream.is_empty(): + last_insert_mode_input_ms = current_tick_ms + return false + + if current_tick_ms - last_insert_mode_input_ms > INSERT_MODE_TIMEOUT_MS: + last_insert_mode_input_ms = current_tick_ms + return true + last_insert_mode_input_ms = current_tick_ms + return false + + +func apply_remaps(map: Array[KeyRemap]): + if map.is_empty(): + return + print("[Godot VIM] Applying keybind remaps...") + for remap in map: + remap.apply(key_map) diff --git a/addons/godot_vim/plugin.cfg b/addons/godot_vim/plugin.cfg new file mode 100644 index 0000000..5726e85 --- /dev/null +++ b/addons/godot_vim/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="GodotVim" +description="" +author="Bernardo Bruning" +version="0.1" +script="plugin.gd" diff --git a/addons/godot_vim/plugin.gd b/addons/godot_vim/plugin.gd new file mode 100644 index 0000000..9f44115 --- /dev/null +++ b/addons/godot_vim/plugin.gd @@ -0,0 +1,354 @@ +@tool +extends EditorPlugin + +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") +const Dispatcher = preload("res://addons/godot_vim/dispatcher.gd") + +const Constants = preload("res://addons/godot_vim/constants.gd") +const DIGITS = Constants.DIGITS +const LANGUAGE = Constants.Language + +var cursor: Cursor +var key_map: KeyMap +var command_line: CommandLine +var status_bar: StatusBar +var globals: Dictionary = {} +var dispatcher: Dispatcher + + +func _enter_tree(): + EditorInterface.get_script_editor().connect("editor_script_changed", _on_script_changed) + + var shader_tabcontainer = get_shader_tabcontainer() as TabContainer + if shader_tabcontainer != null: + shader_tabcontainer.tab_changed.connect(_on_shader_tab_changed) + shader_tabcontainer.visibility_changed.connect(_on_shader_tab_visibility_changed) + else: + push_error( + "[Godot VIM] Failed to get shader editor's TabContainer. Vim will be disabled in the shader editor" + ) + + globals = {} + initialize(true) + + +func initialize(forced: bool = false): + _load(forced) + + print("[Godot VIM] Initialized.") + print(" If you wish to set keybindings, please run :remap in the command line") + + +func _on_script_changed(script: Script): + if !script: + return + + mark_recent_file(script.resource_path) + + _load() + + +func _on_shader_tab_changed(_tab: int): + call_deferred(&"_load") + + +func _on_shader_tab_visibility_changed(): + call_deferred(&"_load") + + +func mark_recent_file(path: String): + if !globals.has("marks"): + globals.marks = {} + var marks: Dictionary = globals.marks + + # Check if path is already in the recent files (stored in start_index) + # This is to avoid flooding the recent files list with the same files + var start_index: int = 0 + while start_index <= 9: + var m: String = str(start_index) + if !marks.has(m) or marks[m].file == path: # Found + break + start_index += 1 + + # Shift all files from start_index down one + for i in range(start_index, -1, -1): + var m: String = str(i) + var prev_m: String = str(i - 1) + if !marks.has(prev_m): + continue + marks[m] = marks[prev_m] + + # Mark "-1" won't be accessible to the user + # It's just the current file, and will be indexed next time the + # loop above ^^^ is called + marks["-1"] = {"file": path, "pos": Vector2i(-1, 0)} + + +func edit_script(path: String, pos: Vector2i): + var script = load(path) + if script == null: + status_bar.display_error('Could not open file "%s"' % path) + return "" + EditorInterface.edit_script(script, pos.y, pos.x) + + +#region LOAD + + +func _init_cursor(code_edit: CodeEdit, language: LANGUAGE): + if cursor != null: + cursor.queue_free() + + cursor = Cursor.new() + 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.language = language + cursor.globals = globals + + +func _init_command_line(code_edit: CodeEdit): + 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() + + +func _init_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 + + +func _load(forced: bool = false): + if globals == null: + globals = {} + + var result: Dictionary = find_code_edit() + if result.is_empty(): + return + var code_edit: CodeEdit = result.code_edit + var language: LANGUAGE = result.language + + _init_cursor(code_edit, language) + _init_command_line(code_edit) + _init_status_bar() + + # KeyMap + if key_map == null or forced: + key_map = KeyMap.new(cursor) + else: + key_map.cursor = cursor + cursor.key_map = key_map + + var script_editor = EditorInterface.get_script_editor() + if script_editor == null: + return + var script_editor_base = script_editor.get_current_editor() + if script_editor_base == null: + return + + 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 + globals.key_map = key_map + + dispatcher = Dispatcher.new() + dispatcher.globals = globals + + # Add nodes + if language != LANGUAGE.SHADER: + script_editor_base.add_child(cursor) + script_editor_base.add_child(status_bar) + script_editor_base.add_child(command_line) + return + + # Get shader editor VBoxContainer + var shaders_container = code_edit + for i in 3: + shaders_container = shaders_container.get_parent() + if shaders_container == null: + # We do not print an error here because for this to fail, + # get_shader_code_edit() (through find_code_edit()) must have + # already failed + return + + shaders_container.add_child(cursor) + shaders_container.add_child(status_bar) + shaders_container.add_child(command_line) + + +#endregion LOAD + + +func dispatch(command: String): + return dispatcher.dispatch(command) + + +## Finds whatever CodeEdit is open +func find_code_edit() -> Dictionary: + var code_edit: CodeEdit = get_shader_code_edit() + var language: LANGUAGE = LANGUAGE.SHADER + # Shader panel not open; normal gdscript code edit + if code_edit == null: + code_edit = get_regular_code_edit() + language = LANGUAGE.GDSCRIPT + if code_edit == null: + return {} + + return { + "code_edit": code_edit, + "language": language, + } + + +## Gets the regular GDScript CodeEdit +func get_regular_code_edit(): + var editor = EditorInterface.get_script_editor().get_current_editor() + return _select(editor, ["VSplitContainer", "CodeTextEditor", "CodeEdit"]) + + +# FIXME Handle cases where the shader editor is its own floating window +## Gets the shader editor's CodeEdit +## Returns Option (aka CodeEdit or null) +func get_shader_code_edit(): + var container = get_shader_tabcontainer() + if container == null: + push_error( + "[Godot VIM] Failed to get shader editor's TabContainer. Vim will be disabled in the shader editor" + ) + return null + + # Panel not open + if !container.is_visible_in_tree(): + return null + + var editors = container.get_children(false) + for tse in editors: + if !tse.visible: # Not open + continue + + var code_edit = _select( + tse, ["VBoxContainer", "VSplitContainer", "ShaderTextEditor", "CodeEdit"] + ) + + if code_edit == null: + push_error( + "[Godot Vim] Failed to get shader editor's CodeEdit. Vim will be disabled in the shader editor" + ) + return null + + return code_edit + + +## Returns Option (aka either TabContainer or null if it fails) +func get_shader_tabcontainer(): + # Get the VSplitContainer containing the script editor and bottom panels + var container = EditorInterface.get_script_editor() + for i in 6: + container = container.get_parent() + if container == null: + # We don't print an error here, let us handle this exception elsewhere + return null + + # Get code edit + container = _select( + container, + ["PanelContainer", "VBoxContainer", "WindowWrapper", "HSplitContainer", "TabContainer"] + ) + return container + + +func _select(obj: Node, types: Array[String]): + if not obj: + return null + 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 + + +## Repeat the function `f` and accumulate the result. A bit like Array::reduce() +## f: func(T) -> T where T is the previous output +func repeat_accum(count: int, inital_value: Variant, f: Callable) -> Variant: + var value: Variant = inital_value + for _index in count: + value = f.call(value) + return value diff --git a/addons/godot_vim/remap.gd b/addons/godot_vim/remap.gd new file mode 100644 index 0000000..deaab57 --- /dev/null +++ b/addons/godot_vim/remap.gd @@ -0,0 +1,178 @@ +class_name KeyRemap extends RefCounted + +enum ApplyMode { + ## Append this keybind to the end of the list + APPEND, + ## Insert this keybind at the start of the list + PREPEND, + ## Insert this keybind at the specified index + INSERT, + ## Remove the specified keybind + REMOVE, + ## Replace a keybind with this one + REPLACE, +} + +const Constants = preload("res://addons/godot_vim/constants.gd") +const MODE = Constants.Mode + +# Inner cmd +var inner: Dictionary = {} +var options: Dictionary = {"apply_mode": ApplyMode.APPEND} + + +func _init(keys: Array[String]): + assert(!keys.is_empty(), "cmd_keys cannot be empty") + inner = {"keys": keys} + + +## Returns self +func motion(motion_type: String, args: Dictionary = {}) -> KeyRemap: + var m: Dictionary = {"type": motion_type} + m.merge(args, true) + inner.motion = m + + # Operator + Motion = OperatorMotion + if inner.get("type") == KeyMap.Operator: + inner.type = KeyMap.OperatorMotion + else: + inner.type = KeyMap.Motion + return self + + +## Returns self +func operator(operator_type: String, args: Dictionary = {}) -> KeyRemap: + var o: Dictionary = {"type": operator_type} + o.merge(args, true) + inner.operator = o + + # Motion + Operator = OperatorMotion + if inner.get("type") == KeyMap.Motion: + inner.type = KeyMap.OperatorMotion + else: + inner.type = KeyMap.Operator + return self + + +## Returns self +func action(action_type: String, args: Dictionary = {}) -> KeyRemap: + var a: Dictionary = {"type": action_type} + a.merge(args, true) + inner.action = a + inner.type = KeyMap.Action + return self + + +## Returns self +func with_context(mode: MODE) -> KeyRemap: + inner["context"] = mode + return self + + +# `key_map` = KeyMap::key_map +func apply(key_map: Array[Dictionary]): + match options.get("apply_mode", ApplyMode.APPEND): + ApplyMode.APPEND: + key_map.append(inner) + + ApplyMode.PREPEND: + var err: int = key_map.insert(0, inner) + if err != OK: + push_error("[Godot VIM] Failed to prepend keybind: %s" % error_string(err)) + + ApplyMode.INSERT: + var index: int = options.get("index", 0) + var err: int = key_map.insert(index, inner) + if err != OK: + push_error( + ( + "[Godot VIM] Failed to insert keybind at index %s: %s" + % [index, error_string(err)] + ) + ) + + ApplyMode.REMOVE: + var index: int = _find(key_map, inner) + if index == -1: + return + key_map.remove_at(index) + + ApplyMode.REPLACE: + var constraints: Dictionary = {"keys": inner.get("keys", [])} + var index: int = _find(key_map, constraints) + if index == -1: + return + + # print('replacing at index ', index) + key_map[index] = inner + + +#region Apply options + + +## Append this keybind to the end of the list +## Returns self +func append() -> KeyRemap: + options = {"apply_mode": ApplyMode.APPEND} + return self + + +## Insert this keybind at the start of the list +## Returns self +func prepend() -> KeyRemap: + options = {"apply_mode": ApplyMode.PREPEND} + return self + + +## Insert this keybind at the specified index +func insert_at(index: int) -> KeyRemap: + options = { + "apply_mode": ApplyMode.INSERT, + "index": index, + } + return self + + +## Removes the keybind from the list +## Returns self +func remove() -> KeyRemap: + options = {"apply_mode": ApplyMode.REMOVE} + return self + + +## Replaces the keybind from the list with this new one +## Returns self +func replace(): + options = {"apply_mode": ApplyMode.REPLACE} + return self + + +#endregion + + +func _find(key_map: Array[Dictionary], constraints: Dictionary) -> int: + var keys: Array[String] = constraints.get("keys") + if keys == null: + push_error("[Godot VIM::KeyRemap::_find()] Failed to find keybind: keys not specified") + return -1 + if keys.is_empty(): + push_error("[Godot VIM::KeyRemap::_find()] Failed to find keybind: keys cannot be empty") + return -1 + + for i in key_map.size(): + var cmd: Dictionary = key_map[i] + # Check keys + var m: KeyMap.KeyMatch = KeyMap.match_keys(cmd.keys, keys) + if m != KeyMap.KeyMatch.Full: + continue + + # If types DON'T match (if specified, ofc), skip + if constraints.has("type") and constraints.type != cmd.type: + continue + + # If contexts DON'T match (if specified, ofc), skip + if constraints.get("context", -1) != cmd.get("context", -1): + continue + + return i + return -1 diff --git a/addons/godot_vim/status_bar.gd b/addons/godot_vim/status_bar.gd new file mode 100644 index 0000000..fd52a55 --- /dev/null +++ b/addons/godot_vim/status_bar.gd @@ -0,0 +1,88 @@ +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 +var key_label: Label + + +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.add_theme_font_override(&"normal_font", font) + add_child(main_label) + + key_label = Label.new() + key_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT + key_label.text = "" + key_label.add_theme_font_override(&"font", font) + key_label.custom_minimum_size.x = 120 + add_child(key_label) + + +func display_text(text: String): + main_label.text = text + + +func display_error(text: String): + main_label.text = "[color=%s]%s" % [ERROR_COLOR, text] + + +func display_special(text: String): + main_label.text = "[color=%s]%s" % [SPECIAL_COLOR, text] + + +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 + + +func set_keys_text(text: String): + key_label.text = text + + +func clear(): + main_label.text = "" + + +func clear_keys(): + key_label.text = "" diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..9d8b7fa --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..06fdaad --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b10c1776j6j60" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..d95ad36 --- /dev/null +++ b/project.godot @@ -0,0 +1,28 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Semi-Idle ARPG" +run/main_scene="res://scenes/testScene.tscn" +config/features=PackedStringArray("4.3", "Forward Plus") +config/icon="res://icon.svg" + +[display] + +window/stretch/scale=3.0 + +[dotnet] + +project/assembly_name="Semi-Idle ARPG" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/godot-vim/plugin.cfg") diff --git a/scenes/character.tscn b/scenes/character.tscn new file mode 100644 index 0000000..58ebc42 --- /dev/null +++ b/scenes/character.tscn @@ -0,0 +1,56 @@ +[gd_scene load_steps=2 format=3 uid="uid://ba27ufs8eak0b"] + +[ext_resource type="Script" path="res://scripts/character.gd" id="2_bft53"] + +[node name="Character" type="Control"] +layout_mode = 3 +anchors_preset = 0 +script = ExtResource("2_bft53") + +[node name="CharacterPanel" type="PanelContainer" parent="."] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = 567.0 +offset_top = 300.0 +offset_right = 675.0 +offset_bottom = 350.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="CharacterPanel"] +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="CharacterName" type="Label" parent="CharacterPanel/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +text = "Name" +horizontal_alignment = 1 + +[node name="HBoxContainer" type="HBoxContainer" parent="CharacterPanel/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 + +[node name="CharacterCurrHealth" type="Label" parent="CharacterPanel/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +text = "Current" + +[node name="CharacterHealthSep" type="Label" parent="CharacterPanel/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "/" + +[node name="CharacterMaxHealth" type="Label" parent="CharacterPanel/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +text = "Max +" diff --git a/scenes/mapTile.tscn b/scenes/mapTile.tscn new file mode 100644 index 0000000..b2a833d --- /dev/null +++ b/scenes/mapTile.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bavjvxaourccu"] + +[ext_resource type="Script" path="res://scripts/map_tile.gd" id="1_jtqny"] + +[node name="MapTile" type="Node2D"] +script = ExtResource("1_jtqny") diff --git a/scenes/npc.tscn b/scenes/npc.tscn new file mode 100644 index 0000000..6f5901c --- /dev/null +++ b/scenes/npc.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://cdu3sqa0k8dgs"] + +[ext_resource type="Script" path="res://scripts/npc.gd" id="2_a7mo0"] + +[node name="NPC" type="Control"] +layout_mode = 3 +anchors_preset = 0 +offset_left = 669.0 +offset_top = 330.0 +offset_right = 669.0 +offset_bottom = 330.0 +script = ExtResource("2_a7mo0") diff --git a/scenes/testScene.tscn b/scenes/testScene.tscn new file mode 100644 index 0000000..b455ad4 --- /dev/null +++ b/scenes/testScene.tscn @@ -0,0 +1,67 @@ +[gd_scene load_steps=3 format=3 uid="uid://dhvk3terpgsp3"] + +[ext_resource type="Script" path="res://scripts/test_scene.gd" id="1_mj8nf"] + +[sub_resource type="LabelSettings" id="LabelSettings_ppp5s"] + +[node name="TestScene" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_mj8nf") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 +size_flags_vertical = 8 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="OutputLabels" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 + +[node name="TestMaxHealth" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/OutputLabels"] +layout_mode = 2 +size_flags_horizontal = 8 +text = "Max Health:" +horizontal_alignment = 2 + +[node name="Output" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 + +[node name="TestMaxHealthVal" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/Output"] +layout_mode = 2 +size_flags_vertical = 3 +text = "Value" +label_settings = SubResource("LabelSettings_ppp5s") + +[node name="Controls" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 8 + +[node name="TestButton" type="Button" parent="MarginContainer/VBoxContainer/Controls"] +layout_mode = 2 +text = "Test" + +[node name="ExitButton" type="Button" parent="MarginContainer/VBoxContainer/Controls"] +layout_mode = 2 +text = "Exit" + +[connection signal="pressed" from="MarginContainer/VBoxContainer/Controls/TestButton" to="." method="_on_test_button_pressed"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/Controls/ExitButton" to="." method="_on_exit_button_pressed"] diff --git a/scripts/character.gd b/scripts/character.gd new file mode 100644 index 0000000..7b5572a --- /dev/null +++ b/scripts/character.gd @@ -0,0 +1,12 @@ +class_name Character +extends Control + + +@export var charName := "Character" +@export var maxHealth := 10 +@export var dmgTaken := 0 + +func _init() -> void: + %CharacterName.text = self.charName + %CharacterMaxHealth.text = str(self.maxHealth) + %CharacterCurrHealth.text = str(self.maxHealth - self.dmgTaken) diff --git a/scripts/equipmentGen.gd b/scripts/equipmentGen.gd new file mode 100644 index 0000000..0014d44 --- /dev/null +++ b/scripts/equipmentGen.gd @@ -0,0 +1,17 @@ +# this script handles everything related to equipment generation +extends SceneTree + +var gearItem = load("gearRand.gd") + + +# func genPiece(tier: int, quality: int): +# # gernarate a single piece of equipment of tier and quality +# var gearPiece = gearItem +# +# return statValues + + +func _init() -> void: + var gearPiece = gearItem.new(tier=2, quality=4) + print (gearPiece.statValues) + quit( diff --git a/scripts/gear.gd b/scripts/gear.gd new file mode 100644 index 0000000..03c2e1e --- /dev/null +++ b/scripts/gear.gd @@ -0,0 +1,22 @@ +class_name Gear +extends "item.gd" + + +# the basic stats that occur on gear +const STATLIST = [ + "str", "dex", "int", + "con", "res", "spd", + "dot", "crd", "crc" + ] + + +var statValues = {} +var tier: int = 0 +var slot: int = 0 +var skill: int = 0 + + +func _init(): + for stat in STATLIST: + statValues[stat] = 0 + diff --git a/scripts/gearRand.gd b/scripts/gearRand.gd new file mode 100644 index 0000000..cf0dbe0 --- /dev/null +++ b/scripts/gearRand.gd @@ -0,0 +1,39 @@ +class_name GearRandom extends "gear.gd" + + +func _init(): + # generate random stats piece of gear with tier and quality + randomize() + var randStatValues = {} + # max tier of 10, max stats of 1000 + var randStatMax = tier*100 + match quality: + 0: + # white, common + randStatValues["con"] = randi_range(0,tier*100) + 1: + # green, uncommon + var statListQual1 = STATLIST.slice(0, 4) + for stat in statListQual1: + randStatValues[stat] = randi_range(0, randStatMax) + 2: + # blue, rare + for stat in STATLIST: + randStatValues[stat] = randi_range(0, randStatMax) + 3: + # purple, epic + for stat in STATLIST: + randStatValues[stat] = randi_range(0.3*randStatMax, randStatMax) + 4: + # orange?, legendary + for stat in STATLIST: + randStatValues[stat] = randi_range(0.6*randStatMax, randStatMax) + 5: + # yellow or red?, artifact + for stat in STATLIST: + randStatValues[stat] = randi_range(0.9*randStatMax, randStatMax) + _: + # should never occur, but just in case... + print("Unknown Equipment Quality") + + self.statValues = randStatValues diff --git a/scripts/item.gd b/scripts/item.gd new file mode 100644 index 0000000..85d02e2 --- /dev/null +++ b/scripts/item.gd @@ -0,0 +1,10 @@ +class_name Item +extends Object + + +var quality: int = 0 + +func _init() -> void: + # simple random integer from 0 to 5 for the different item qualities + randomize() + self.quality = randi_range(0, 5) diff --git a/scripts/map_tile.gd b/scripts/map_tile.gd new file mode 100644 index 0000000..63be33d --- /dev/null +++ b/scripts/map_tile.gd @@ -0,0 +1,12 @@ +extends Node2D + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass + + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass diff --git a/scripts/npc.gd b/scripts/npc.gd new file mode 100644 index 0000000..1886b07 --- /dev/null +++ b/scripts/npc.gd @@ -0,0 +1,37 @@ +class_name NPC +extends Character + + +enum npcDifficulties { MINION, NORMAL, MINIBOSS, BOSS, ELITEBOSS, BBEG } + +@export var npcDifficulty: npcDifficulties +@export var tier: int + +func _random_mod_health() -> void: + randomize() + # noise factor + var noise_factor = randf_range(7, 10) + self.maxHealth *= noise_factor + # difficulty factor + match self.npcDifficulty: + npcDifficulties.MINION: + self.maxHealth /= 2 + npcDifficulties.MINIBOSS: + self.maxHealth *= 2 + npcDifficulties.BOSS: + self.maxHealth *= 4 + npcDifficulties.ELITEBOSS: + self.maxHealth *= 8 + npcDifficulties.BBEG: + self.maxHealth *= 16 + # tier factor + self.maxHealth *= exp(self.tier) + + # fun factor (just additional factor to tweak) + self.maxHealth *= 10 + + +func _init(npcDifficulty: npcDifficulties = npcDifficulties.NORMAL, tier: int = 0) -> void: + self.npcDifficulty = npcDifficulty + self.tier = tier + _random_mod_health() diff --git a/scripts/test_scene.gd b/scripts/test_scene.gd new file mode 100644 index 0000000..178e5a5 --- /dev/null +++ b/scripts/test_scene.gd @@ -0,0 +1,33 @@ +extends Control + + +func _on_exit_button_pressed() -> void: + get_tree().quit() + + +func _on_test_button_pressed() -> void: + var rand_difficulty = NPC.npcDifficulties.values().pick_random() + var rand_tier = randi_range(0, 10) + var anNPC = NPC.new(rand_difficulty, rand_tier) + var TestMaxHealthVal = $MarginContainer/HBoxContainer/Output/TestMaxHealthVal + TestMaxHealthVal.text = str(anNPC.maxHealth) + #TestMaxHealthVal.label_settings = LabelSettings.new() + TestMaxHealthVal.label_settings.outline_size = 4 + TestMaxHealthVal.label_settings.outline_color = Color("#1D2021") + match anNPC.npcDifficulty: + NPC.npcDifficulties.MINION: + TestMaxHealthVal.label_settings.font_color = Color.LIGHT_GRAY + NPC.npcDifficulties.NORMAL: + TestMaxHealthVal.label_settings.font_color = Color.SEA_GREEN + NPC.npcDifficulties.MINIBOSS: + TestMaxHealthVal.label_settings.font_color = Color.ROYAL_BLUE + NPC.npcDifficulties.BOSS: + TestMaxHealthVal.label_settings.font_color = Color.PURPLE + NPC.npcDifficulties.ELITEBOSS: + TestMaxHealthVal.label_settings.font_color = Color.ORANGE + NPC.npcDifficulties.BBEG: + TestMaxHealthVal.label_settings.font_color = Color.GOLD + TestMaxHealthVal.label_settings.outline_size = 8 + TestMaxHealthVal.label_settings.outline_color = Color.DARK_RED + #anNPC.position = Vector2(64, 64) + #add_child(anNPC)