From f1cb17282b04217a47aa09d8527bbc618921eac9 Mon Sep 17 00:00:00 2001 From: Manyu Lakhotia Date: Tue, 5 Dec 2023 13:00:48 -0600 Subject: [PATCH 01/11] Add s command --- addons/godot-vim/godot-vim.gd | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index 724971c..b3f56c6 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -94,6 +94,7 @@ var the_key_map : Array[Dictionary] = [ { "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": "delete_and_enter_insert_mode", "motion": "move_by_characters", "motion_args": { "forward": true, "one_line": true }, "action_args": { "insert_at": "inplace" }, "context": Context.NORMAL }, { "keys": ["X"], "type": OPERATOR, "operator": "delete", "context": Context.VISUAL }, { "keys": ["Shift+X"], "type": OPERATOR_MOTION, "operator": "delete", "motion": "move_by_characters", "motion_args": { "forward": false } }, { "keys": ["U"], "type": OPERATOR, "operator": "change_case", "operator_args": { "lower": true }, "context": Context.VISUAL }, @@ -517,6 +518,15 @@ class Command: s.append(c.to_lower() if c == c.to_upper() else c.to_upper()) ed.replace_selection(''.join(s)) + static func delete_and_enter_insert_mode(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: + var text := ed.selected_text() + vim.register.set_text(text, args.get("line_wise", false)) + ed.delete_selection() + var line := ed.curr_line() + var col := ed.curr_column() + if col > ed.last_column(line): # If after deletion we are beyond the end, move left + ed.set_curr_column(ed.last_column(line)) + vim.current.enter_insert_mode(); ### ACTIONS From 5835ac7670b0e53bfdc13599fd523efbe690e9c6 Mon Sep 17 00:00:00 2001 From: Manyu Lakhotia Date: Tue, 5 Dec 2023 14:43:39 -0600 Subject: [PATCH 02/11] +g based visual mode case change commands --- addons/godot-vim/godot-vim.gd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index b3f56c6..3b65d77 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -97,6 +97,8 @@ var the_key_map : Array[Dictionary] = [ { "keys": ["S"], "type": OPERATOR_MOTION, "operator": "delete_and_enter_insert_mode", "motion": "move_by_characters", "motion_args": { "forward": true, "one_line": true }, "action_args": { "insert_at": "inplace" }, "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 }, From c6ba18bd213488ecb6768cf625a83c48ea791d9e Mon Sep 17 00:00:00 2001 From: Manyu Lakhotia Date: Mon, 5 Feb 2024 20:46:37 -0600 Subject: [PATCH 03/11] Simplify s command based on PR comments --- addons/godot-vim/godot-vim.gd | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index b3f56c6..2925d43 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -94,7 +94,7 @@ var the_key_map : Array[Dictionary] = [ { "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": "delete_and_enter_insert_mode", "motion": "move_by_characters", "motion_args": { "forward": true, "one_line": true }, "action_args": { "insert_at": "inplace" }, "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": ["U"], "type": OPERATOR, "operator": "change_case", "operator_args": { "lower": true }, "context": Context.VISUAL }, @@ -518,16 +518,6 @@ class Command: s.append(c.to_lower() if c == c.to_upper() else c.to_upper()) ed.replace_selection(''.join(s)) - static func delete_and_enter_insert_mode(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text := ed.selected_text() - vim.register.set_text(text, args.get("line_wise", false)) - ed.delete_selection() - var line := ed.curr_line() - var col := ed.curr_column() - if col > ed.last_column(line): # If after deletion we are beyond the end, move left - ed.set_curr_column(ed.last_column(line)) - vim.current.enter_insert_mode(); - ### ACTIONS static func paste(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: From 807b6ad0ecf9615bf37b85fcfec43f0d6d1b2b81 Mon Sep 17 00:00:00 2001 From: Manyu Lakhotia Date: Mon, 5 Feb 2024 20:46:37 -0600 Subject: [PATCH 04/11] Simplify s command based on PR comments --- addons/godot-vim/godot-vim.gd | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index b3f56c6..162e339 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -94,7 +94,7 @@ var the_key_map : Array[Dictionary] = [ { "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": "delete_and_enter_insert_mode", "motion": "move_by_characters", "motion_args": { "forward": true, "one_line": true }, "action_args": { "insert_at": "inplace" }, "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": ["U"], "type": OPERATOR, "operator": "change_case", "operator_args": { "lower": true }, "context": Context.VISUAL }, @@ -518,15 +518,6 @@ class Command: s.append(c.to_lower() if c == c.to_upper() else c.to_upper()) ed.replace_selection(''.join(s)) - static func delete_and_enter_insert_mode(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: - var text := ed.selected_text() - vim.register.set_text(text, args.get("line_wise", false)) - ed.delete_selection() - var line := ed.curr_line() - var col := ed.curr_column() - if col > ed.last_column(line): # If after deletion we are beyond the end, move left - ed.set_curr_column(ed.last_column(line)) - vim.current.enter_insert_mode(); ### ACTIONS From 176a4143c2452104f2ab13dda2ff30746f3effdc Mon Sep 17 00:00:00 2001 From: Manyu Lakhotia Date: Tue, 13 Feb 2024 12:35:54 -0600 Subject: [PATCH 05/11] Check if the_ed is null before focus check --- addons/godot-vim/godot-vim.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index 376b1fd..fab67b5 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -175,7 +175,7 @@ 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.has_focus(): + 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: From f9da071084b820a5280cd9db4d923dcc26113bb2 Mon Sep 17 00:00:00 2001 From: Manyu Lakhotia Date: Tue, 13 Feb 2024 12:40:49 -0600 Subject: [PATCH 06/11] Check if code_editor exists in has focus() --- addons/godot-vim/godot-vim.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index fab67b5..9717c9e 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -1408,7 +1408,7 @@ class EditorAdaptor: return Position.new(result.y, result.x) func has_focus() -> bool: - return code_editor.has_focus() + return weakref(code_editor).get_ref() and code_editor.has_focus() class CommandDispatcher: From 5bef0f3b1b0f7e11d0546abcfb333a0a6ce2abf4 Mon Sep 17 00:00:00 2001 From: Wenqiang Date: Sun, 8 Sep 2024 12:10:20 -0700 Subject: [PATCH 07/11] Fix selection issues to make it work in Godot 4.3 --- addons/godot-vim/godot-vim.gd | 158 +++++++++++++++++++++++----------- 1 file changed, 106 insertions(+), 52 deletions(-) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index 9717c9e..194d576 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -3,7 +3,7 @@ extends EditorPlugin const INF_COL : int = 99999 -const DEBUGGING : int = 0 # Change to 1 for debugging +const DEBUGGING : int = 1 # 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 } @@ -87,10 +87,10 @@ var the_key_map : Array[Dictionary] = [ { "keys": ["I", "Apostrophe"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"'" } }, { "keys": ["I", 'Shift+Apostrophe'], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":'"' } }, { "keys": ["I", "W"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"w" } }, - { "keys": ["D"], "type": OPERATOR, "operator": "delete" }, + { "keys": ["D"], "type": OPERATOR, "operator": "delete", "operator_args": { "line_wise_to_next_line": true } }, { "keys": ["Shift+D"], "type": OPERATOR_MOTION, "operator": "delete", "motion": "move_to_end_of_line", "motion_args": { "inclusive": true } }, - { "keys": ["Y"], "type": OPERATOR, "operator": "yank" }, - { "keys": ["Shift+Y"], "type": OPERATOR_MOTION, "operator": "yank", "motion": "move_to_end_of_line", "motion_args": { "inclusive": true } }, + { "keys": ["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 }, @@ -156,7 +156,6 @@ var the_dispatcher := CommandDispatcher.new(the_key_map) # The command dispatche 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) @@ -200,6 +199,7 @@ 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 @@ -265,6 +265,7 @@ class Command: 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: @@ -291,8 +292,11 @@ class Command: 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) @@ -388,7 +392,7 @@ class Command: 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 + continue if symbol.char in ")]}": parser.stack.reverse() @@ -429,7 +433,11 @@ class Command: 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) + var to_next_line = args.get("to_next_line", false) + if to_next_line: + return Position.new(cur.line + args.repeat, -1) + else: + 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 @@ -489,8 +497,13 @@ class Command: static func delete(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: var text := ed.selected_text() - vim.register.set_text(text, args.get("line_wise", false)) + var line_wise = args.get("line_wise", false) + vim.register.set_text(text, line_wise) + + + ed.delete_selection() + var line := ed.curr_line() var col := ed.curr_column() if col > ed.last_column(line): # If after deletion we are beyond the end, move left @@ -537,13 +550,14 @@ class Command: if line_wise: if after: - text = "\n" + text.substr(0, len(text)-1) + 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) @@ -1025,10 +1039,11 @@ class VimSession: 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 + 1, 0) + ed.select(visual_start_pos.line, 0, visual_start_pos.line, INF_COL) else: - ed.select_by_pos2(visual_start_pos, visual_start_pos.right()) + ed.select_by_pos2(visual_start_pos, visual_start_pos) class Macro: @@ -1221,6 +1236,7 @@ class EditorAdaptor: 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) @@ -1263,17 +1279,30 @@ class EditorAdaptor: func deselect() -> void: code_editor.deselect() - func select_range(r: TextRange) -> void: - select(r.from.line, r.from.column, r.to.line, r.to.column) - func select_by_pos2(from: Position, to: Position) -> void: select(from.line, from.column, to.line, to.column) func select(from_line: int, from_col: int, to_line: int, to_col: int) -> void: - if to_line > last_line(): # If we try to select pass the last line, select till the last char + # 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: @@ -1446,7 +1475,7 @@ class CommandDispatcher: var context = Context.VISUAL if vim.current.visual_mode else Context.NORMAL var result = match_commands(context, vim.current.input_state, ed, vim) if not result.full.is_empty(): - var command = result.full[0] + var command = result.full[0].duplicate(true) var change_num := vim.current.text_change_number if process_command(command, ed, vim): input_state.clear() @@ -1503,7 +1532,9 @@ class CommandDispatcher: func process_command(command: Dictionary, ed: EditorAdaptor, vim: Vim) -> bool: var vim_session := vim.current var input_state := vim_session.input_state - var start := Position.new(ed.curr_line(), ed.curr_column()) + + # 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): @@ -1520,61 +1551,76 @@ class CommandDispatcher: 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 - start = vim_session.visual_start_pos - if new_pos is TextRange: - start = new_pos.from # In some cases (text object), we need to override the start position - new_pos = new_pos.to - ed.jump_to(new_pos.line, new_pos.column) - if start.compares_to(new_pos) > 0: # swap - start = new_pos - new_pos = vim_session.visual_start_pos if vim_session.visual_line: - ed.select(start.line, 0, new_pos.line + 1, 0) + if jump_forward: + ed.select(start.line, 0, new_pos.line, INF_COL) + else: + ed.select(start.line, INF_COL, new_pos.line, 0) else: - ed.select_by_pos2(start, new_pos.right()) - elif input_state.operator.is_empty(): # Normal mode motion only - ed.jump_to(new_pos.line, new_pos.column) - else: # Normal mode operator motion - if new_pos is TextRange: - start = new_pos.from # In some cases (text object), we need to override the start position - new_pos = new_pos.to - var inclusive : bool = motion_args.get("inclusive", false) - ed.select_by_pos2(start, new_pos.right() if inclusive else new_pos) - process_operator(input_state.operator, input_state.operator_args, ed, vim) + 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 - elif input_state.operator.is_empty(): # We are not fully done yet, need to wait for the motion + + # 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 - else: - if input_state.operator == command.operator: # Line wise operation - operator_args.line_wise = true - var new_pos : Position = process_motion("expand_to_line", {}, ed, vim) - if new_pos.compares_to(start) > 0: - ed.select(start.line, 0, new_pos.line + 1, 0) - else: - ed.select(new_pos.line, 0, start.line + 1, 0) - process_operator(command.operator, operator_args, ed, vim) - return true + + # Line wise operation + if input_state.operator == command.operator: + operator_args.line_wise = true + # For operator like 'd', we need to expand selection to the beginning of the next line + var expand_to_next_line : bool = operator_args.get('line_wise_to_next_line', false) + var new_pos : Position = process_motion("expand_to_line", { "to_next_line": expand_to_next_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 @@ -1594,11 +1640,20 @@ class CommandDispatcher: # 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() @@ -1626,4 +1681,3 @@ class CommandDispatcher: print(" Motion: %s %s to %s" % [motion, motion_args, result]) return result - From d046cfc6414c1d0c8871d669d84326616797378f Mon Sep 17 00:00:00 2001 From: Wenqiang Date: Sun, 8 Sep 2024 12:41:40 -0700 Subject: [PATCH 08/11] Updated readme, disabled DEBUGGIN --- README.md | 47 ++++++++++++++++++++++++------------- addons/godot-vim/plugin.cfg | 4 ++-- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2eb7d3e..24d2e06 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,27 @@ -VIM bindings for Godot 4 +# VIM emulator for Godot 4 (version 4.3.0) -recently improved thanks to wenqiangwang -If you would like ctrl+F to be move-forward by page then uncomment the following line +## What is this? +This is a Godot 4 plugin to emulates VIM-like editor behavior witin Godot editor itself (i.e. after installed and enabled, one could edit (kind of) like in VIM. -#"Ctrl+F": 1, ## Uncomment if you want Ctrl+F for move forward by page +## How to install? +1. Clone or download the source of the repo, and copy the `addons` folder to the root of your project. +2. Go to `Project` -> `Project Settings` -> `Plugins` tab and check the check box of the plugin named `godot-vim` -### Supported Mode +## What's new? +- Changed version schema to godot_major_version.godot_minir_version.vim_plugin_version, so that it is easier to find the correct version of plugin for specific version of Godot editors +- Created a new branch `4.2` for Godot Editors 4.0 to 4.2 +- Fixed selection issues due to CodeEdit.select() behavior change + + +## VIM features supprted +#### Mode - Normal mode - Insert mode - Visual mode - Visual line mode -### Supported motions +#### Motions h, l, j, k, +, - ^, 0, $, | @@ -25,14 +34,14 @@ If you would like ctrl+F to be move-forward by page then uncomment the following aw, a(, a{, a[, a", a' iw, i(, i{, i[, i", i' -### Supported operator +#### Operator c, C, d, D, x, X, y, Y, u, U, ~ -### Supported actions +#### Actions p, u, c-r, @@ -42,14 +51,20 @@ If you would like ctrl+F to be move-forward by page then uncomment the following >, < m, ' -### Override Default Godot Shortcuts with `godot-vim`'s ones - -Note that all non-ascii character mappings that are already mapped in the default Godot editor have to be unmapped from the Editor settings (Editor >> Editor Settings >> Shorcuts) before being usable with `godot-vim`. +## FAQ +1. How to override default Godot shortcuts with `godot-vim`'s ones -This currently goes for: + Note that all non-ascii character mappings that are already mapped in the default Godot editor have to be unmapped from the Editor settings (Editor >> Editor Settings >> Shorcuts) before being usable with `godot-vim`. + + This currently goes for: + + - `Ctrl+R` + - `Ctrl+U` + - `Ctrl+D` + + See the full list of non-ascii shortucts that may already be mapped by Godot and thus wouldn't work in `godot-vim` before releasing them in Godot settings: https://github.com/joshnajera/godot-vim/blob/main/addons/godot-vim/godot-vim.gd#L135 -- `Ctrl+R` -- `Ctrl+U` -- `Ctrl+D` +2. I found a problem, what should I do? -See the full list of non-ascii shortucts that may already be mapped by Godot and thus wouldn't work in `godot-vim` before releasing them in Godot settings: https://github.com/joshnajera/godot-vim/blob/main/addons/godot-vim/godot-vim.gd#L135 + - You could debug by yourself, changing the value of `DEBUGGING` variable to 1 to see logs + - Or report the issue by providing a) the content of original text editing, b) the cursor position and c) key sequence that repros the issue diff --git a/addons/godot-vim/plugin.cfg b/addons/godot-vim/plugin.cfg index da5f41c..9a7f5d3 100644 --- a/addons/godot-vim/plugin.cfg +++ b/addons/godot-vim/plugin.cfg @@ -2,6 +2,6 @@ name="godot-vim" description="VIM bindings for godot4" -author="Josh N" -version="0.3" +author="Original: Josh N; Forked by Wenqiang Wang" +version="4.3.0" script="godot-vim.gd" From 3b26426548fb451c0dcd7989d9db723b32d08850 Mon Sep 17 00:00:00 2001 From: wenqiangwang Date: Sun, 8 Sep 2024 13:01:24 -0700 Subject: [PATCH 09/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24d2e06..650232b 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ This is a Godot 4 plugin to emulates VIM-like editor behavior witin Godot editor - `Ctrl+U` - `Ctrl+D` - See the full list of non-ascii shortucts that may already be mapped by Godot and thus wouldn't work in `godot-vim` before releasing them in Godot settings: https://github.com/joshnajera/godot-vim/blob/main/addons/godot-vim/godot-vim.gd#L135 + See the full list of non-ascii shortucts that may already be mapped by Godot and thus wouldn't work in `godot-vim` before releasing them in Godot settings: https://github.com/wenqiangwang/godot-vim/blob/main/addons/godot-vim/godot-vim.gd#L138 2. I found a problem, what should I do? From f52df00e5d0b180307983219c5f8d28e6ea81768 Mon Sep 17 00:00:00 2001 From: Wenqiang Date: Sun, 8 Sep 2024 13:52:03 -0700 Subject: [PATCH 10/11] Fixed issue with pasting after dd and fixed r action --- addons/godot-vim/godot-vim.gd | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index 194d576..9816030 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -3,7 +3,7 @@ extends EditorPlugin const INF_COL : int = 99999 -const DEBUGGING : int = 1 # Change to 1 for debugging +const DEBUGGING : int = 1# 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 } @@ -87,7 +87,7 @@ var the_key_map : Array[Dictionary] = [ { "keys": ["I", "Apostrophe"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"'" } }, { "keys": ["I", 'Shift+Apostrophe'], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":'"' } }, { "keys": ["I", "W"], "type": MOTION, "motion": "text_object", "motion_args": { "inner": true, "object":"w" } }, - { "keys": ["D"], "type": OPERATOR, "operator": "delete", "operator_args": { "line_wise_to_next_line": true } }, + { "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 } }, @@ -433,11 +433,7 @@ class Command: return move_to_next_char(cur, args, ed, vim) static func expand_to_line(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var to_next_line = args.get("to_next_line", false) - if to_next_line: - return Position.new(cur.line + args.repeat, -1) - else: - return Position.new(cur.line + args.repeat - 1, INF_COL) + 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 @@ -499,10 +495,13 @@ class Command: var text := ed.selected_text() var line_wise = args.get("line_wise", false) vim.register.set_text(text, line_wise) - - 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() var line := ed.curr_line() var col := ed.curr_column() @@ -575,7 +574,7 @@ class Command: var to_replace = args.selected_character var line := ed.curr_line() var col := ed.curr_column() - ed.select(line, col, line, col+1) + ed.select(line, col, line, col) ed.replace_selection(to_replace) static func enter_insert_mode(args: Dictionary, ed: EditorAdaptor, vim: Vim) -> void: @@ -1577,7 +1576,7 @@ class CommandDispatcher: if jump_forward: ed.select(start.line, 0, new_pos.line, INF_COL) else: - ed.select(start.line, INF_COL, new_pos.line, 0) + ed.select(start.line, INF_COL, new_pos.line, -1) else: ed.select_by_pos2(start, new_pos) else: # Normal mode @@ -1614,9 +1613,7 @@ class CommandDispatcher: # Line wise operation if input_state.operator == command.operator: operator_args.line_wise = true - # For operator like 'd', we need to expand selection to the beginning of the next line - var expand_to_next_line : bool = operator_args.get('line_wise_to_next_line', false) - var new_pos : Position = process_motion("expand_to_line", { "to_next_line": expand_to_next_line }, ed, vim) + 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) From 2e40cf8740e50339a5ccca097ca6212b92ed95c5 Mon Sep 17 00:00:00 2001 From: Wenqiang Date: Sun, 8 Sep 2024 22:48:51 -0700 Subject: [PATCH 11/11] Added Go to definition and fixed minor issues --- addons/godot-vim/godot-vim.gd | 68 +++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/addons/godot-vim/godot-vim.gd b/addons/godot-vim/godot-vim.gd index 9816030..f6733d5 100644 --- a/addons/godot-vim/godot-vim.gd +++ b/addons/godot-vim/godot-vim.gd @@ -3,9 +3,10 @@ extends EditorPlugin const INF_COL : int = 99999 -const DEBUGGING : int = 1# Change to 1 for debugging +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 } @@ -87,6 +88,7 @@ var the_key_map : Array[Dictionary] = [ { "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 } }, @@ -97,8 +99,8 @@ var the_key_map : Array[Dictionary] = [ { "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": ["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 }, @@ -130,7 +132,7 @@ var the_key_map : Array[Dictionary] = [ { "keys": ["Shift+Period"], "type": ACTION, "action": "indent", "action_args": { "forward" = true } }, { "keys": ["Shift+J"], "type": ACTION, "action": "join_lines", "action_args": {} }, { "keys": ["M", "{char}"], "type": ACTION, "action": "set_bookmark", "action_args": {} }, - { "keys": ["Apostrophe", "{char}"], "type": MOTION, "motion": "go_to_bookmark", "motion_args": {} }, + { "keys": ["Ctrl+BracketRight"], "type": ACTION, "action": "go_to_definition", "action_args": {} }, ] @@ -144,7 +146,8 @@ var command_keys_white_list : Dictionary = { "Ctrl+D": 1, "Ctrl+O": 1, "Ctrl+I": 1, - "Ctrl+R": 1 + "Ctrl+R": 1, + "Ctrl+BracketRight": 1, } @@ -488,6 +491,12 @@ class Command: 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 @@ -496,12 +505,14 @@ class Command: 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() @@ -547,18 +558,24 @@ class Command: var line := ed.curr_line() var col := ed.curr_column() - if line_wise: - if after: - text = "\n" + text - col = len(ed.line_text(line)) - else: - text = text + "\n" - col = 0 + ed.begin_complex_operation() + if vim.current.visual_mode: + ed.delete_selection() else: - col += 1 if after else 0 + 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.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): @@ -687,14 +704,16 @@ class Command: var name = args.selected_character if name in LOWER_ALPHA: vim.current.bookmark_manager.set_bookmark(name, ed.curr_line()) - - static func go_to_bookmark(cur: Position, args: Dictionary, ed: EditorAdaptor, vim: Vim) -> Position: - var name = args.selected_character - var line := vim.current.bookmark_manager.get_bookmark(name) - if line < 0: - return null - return Position.new(line, 0) - + + static func go_to_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 @@ -1215,7 +1234,6 @@ class EditorAdaptor: code_editor.set_caret_line(line) code_editor.set_caret_column(col) - func first_line() -> int: return 0 @@ -1262,6 +1280,10 @@ class EditorAdaptor: 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: