├── LICENSE.md ├── README.md └── godot_vim ├── SCsub ├── action.cpp ├── action.h ├── command.cpp ├── command.h ├── config.py ├── godot_vim.cpp ├── godot_vim.h ├── motion.cpp ├── motion.h ├── operation.cpp ├── operation.h ├── register_types.cpp ├── register_types.h ├── util.cpp └── util.h /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Darlan P. de Campos 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GodotVim 2 | 3 | This project aims to bring Vim keybindings to Godot's script editor. 4 | 5 | ## Status 6 | 7 | Development has stopped. Please feel free to use it as a starting point for further development or for understanding how the script editor works (or worked). 8 | 9 | ## Getting Started 10 | 11 | The module in its current state is a work in progress and much has to be done in order to make it more usable. If you want to try it out, just follow the steps below. 12 | 13 | ### Installing 14 | 15 | Because it is written as a C++ module, GodotVim needs Godot to be compiled from source in order to work. 16 | 17 | First, clone Godot to your local (and set up your favorite C++ editor if you want to tinker with the code). The plugin currently only works with Godot 2.1. 18 | 19 | ``` 20 | git clone -b 2.1 git@github.com:godotengine/godot.git 21 | ``` 22 | 23 | Clone godot-vim: 24 | 25 | ``` 26 | git clone git@github.com:dcampos/godot-vim.git 27 | ``` 28 | 29 | Copy or symlink the folder godot_vim of the plugin into the modules folder of the godot repository. Then compile Godot as per the [official documentation](http://docs.godotengine.org/en/stable/development/compiling/index.html). 30 | 31 | ## License 32 | 33 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 34 | 35 | -------------------------------------------------------------------------------- /godot_vim/SCsub: -------------------------------------------------------------------------------- 1 | # SCsub 2 | Import('env') 3 | 4 | env.add_source_files(env.modules_sources,"*.cpp") # just add all cpp files to the build 5 | -------------------------------------------------------------------------------- /godot_vim/action.cpp: -------------------------------------------------------------------------------- 1 | #include "action.h" 2 | #include "util.h" 3 | #include "godot_vim.h" 4 | 5 | void Action::enter_insert_mode() { 6 | vim->_set_vim_mode(GodotVim::INSERT); 7 | } 8 | 9 | void Action::enter_insert_mode_append() { 10 | vim->_set_vim_mode(GodotVim::INSERT); 11 | vim->_cursor_set_column(vim->_cursor_get_column() + 1); 12 | } 13 | 14 | void Action::enter_insert_mode_after_eol() { 15 | vim->_set_vim_mode(GodotVim::INSERT); 16 | vim->_cursor_set_column(vim->_get_current_line_length()); 17 | } 18 | 19 | void Action::enter_insert_mode_after_selection() { 20 | vim->_cursor_set_column(vim->get_selection().to.col); 21 | vim->_set_vim_mode(GodotVim::INSERT); 22 | } 23 | 24 | void Action::enter_insert_mode_first_non_blank() { 25 | vim->_set_vim_mode(GodotVim::INSERT); 26 | vim->_cursor_set_column(_find_first_non_blank(vim->_get_current_line())); 27 | } 28 | 29 | void Action::enter_insert_mode_before_selection() { 30 | vim->_cursor_set_column(0); 31 | vim->_cursor_set_line(vim->get_selection().from.row); 32 | vim->_set_vim_mode(GodotVim::INSERT); 33 | } 34 | 35 | void Action::enter_ex() { 36 | vim->get_command_line()->show(); 37 | vim->get_command_line()->set_text(":"); 38 | vim->get_command_line()->set_cursor_pos(1); 39 | vim->get_command_line()->grab_focus(); 40 | } 41 | 42 | void Action::undo() { 43 | vim->get_text_edit()->undo(); 44 | } 45 | 46 | void Action::redo() { 47 | vim->get_text_edit()->redo(); 48 | } 49 | 50 | void Action::toggle_visual_mode() { 51 | if (vim->_get_vim_mode() == GodotVim::VISUAL) { 52 | vim->_set_vim_mode(GodotVim::NORMAL); 53 | } else { 54 | vim->_set_visual_type(GodotVim::SELECT_CHARS); 55 | vim->_set_vim_mode(GodotVim::VISUAL); 56 | } 57 | } 58 | 59 | void Action::replace_char() { 60 | ERR_FAIL_COND(vim->get_input_state().input_char.size() == 0) 61 | 62 | String line = vim->_get_current_line(); 63 | line[vim->_cursor_get_column()] = vim->get_input_state().input_char[0]; 64 | vim->get_text_edit()->begin_complex_operation(); 65 | vim->set_line(vim->_cursor_get_line(), line); 66 | vim->get_text_edit()->end_complex_operation(); 67 | } 68 | 69 | void Action::delete_char() { 70 | String line = vim->_get_current_line(); 71 | line.remove(vim->_cursor_get_column()); 72 | vim->get_text_edit()->begin_complex_operation(); 73 | vim->set_line(vim->_cursor_get_line(), line); 74 | vim->get_text_edit()->end_complex_operation(); 75 | } 76 | 77 | void Action::delete_previous_char() { 78 | String line = vim->_get_current_line(); 79 | line.remove(vim->_cursor_get_column() - 1); 80 | vim->get_text_edit()->begin_complex_operation(); 81 | vim->set_line(vim->_cursor_get_line(), line); 82 | vim->_cursor_set_column(vim->_cursor_get_column() - 1); 83 | vim->get_text_edit()->end_complex_operation(); 84 | } 85 | 86 | 87 | void Action::open_line_below() { 88 | vim->_cursor_set_column(vim->_get_current_line_length()); 89 | vim->get_text_edit()->insert_text_at_cursor("\n"); 90 | vim->_set_vim_mode(GodotVim::INSERT); 91 | } 92 | 93 | void Action::open_line_above() { 94 | vim->_cursor_set_column(0); 95 | vim->get_text_edit()->insert_text_at_cursor("\n"); 96 | vim->_cursor_set_line(vim->_cursor_get_line() - 1); 97 | vim->_set_vim_mode(GodotVim::INSERT); 98 | } 99 | 100 | void Action::toggle_visual_mode_line() { 101 | if (vim->_get_vim_mode() == GodotVim::VISUAL && vim->_get_visual_type() == GodotVim::SELECT_LINES) { 102 | vim->_set_vim_mode(GodotVim::NORMAL); 103 | } else { 104 | vim->_set_visual_type(GodotVim::SELECT_LINES); 105 | vim->_set_vim_mode(GodotVim::VISUAL); 106 | } 107 | } 108 | 109 | void Action::delete_to_eol() { 110 | int sc = vim->_cursor_get_column(); 111 | int sl = vim->_cursor_get_line(); 112 | int ec = vim->_get_current_line_length(); 113 | int el = sl; 114 | 115 | vim->get_text_edit()->select(sl, sc, el, ec); 116 | vim->get_text_edit()->cut(); 117 | } 118 | 119 | void Action::change_to_eol() { 120 | int sc = vim->_cursor_get_column(); 121 | int sl = vim->_cursor_get_line(); 122 | int ec = vim->_get_current_line_length(); 123 | int el = sl; 124 | 125 | vim->get_text_edit()->select(sl, sc, el, ec); 126 | vim->get_text_edit()->cut(); 127 | vim->_set_vim_mode(GodotVim::INSERT); 128 | } 129 | 130 | void Action::scroll_to_center() { 131 | vim->get_text_edit()->center_viewport_to_cursor(); 132 | } 133 | 134 | void Action::paste() { 135 | Vector lines = vim->get_registry().lines; 136 | String text = _join_lines(lines); 137 | 138 | vim->_cursor_set_column(vim->_cursor_get_column() + 1); 139 | 140 | if (vim->get_registry().linewise) { 141 | vim->get_text_edit()->insert_at(text, vim->_cursor_get_line() + 1); 142 | } else { 143 | vim->get_text_edit()->insert_text_at_cursor(text); 144 | } 145 | } 146 | 147 | void Action::paste_before() { 148 | Vector lines = vim->get_registry().lines; 149 | String text = _join_lines(lines); 150 | 151 | if (vim->get_registry().linewise) { 152 | vim->get_text_edit()->insert_at(text, vim->_cursor_get_line()); 153 | } else { 154 | vim->get_text_edit()->insert_text_at_cursor(text); 155 | } 156 | } 157 | 158 | void Action::run() { 159 | (this->*fcn)(); 160 | } 161 | 162 | void Action::_open_line(bool above) { 163 | if (above) vim->_cursor_set_line(vim->_cursor_get_line() - 1); 164 | vim->_cursor_set_column(vim->_get_current_line_length()); 165 | vim->get_text_edit()->insert_text_at_cursor("\n"); 166 | } 167 | 168 | Action::Action(GodotVim *vim, int flags, Action::actionFunction fcn, int cmd_flags) : Command(vim, cmd_flags), flags(flags), fcn(fcn) { 169 | } 170 | 171 | Action * Action::create_action(GodotVim *vim, int flags, Action::actionFunction fcn, int cmd_flags) { 172 | return memnew(Action(vim, flags, fcn, cmd_flags)); 173 | } 174 | -------------------------------------------------------------------------------- /godot_vim/action.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTION_H 2 | #define ACTION_H 3 | 4 | #include "command.h" 5 | 6 | class Action : public Command { 7 | public: 8 | typedef void (Action::* actionFunction) (); 9 | 10 | private: 11 | int flags; 12 | actionFunction fcn; 13 | 14 | Action(GodotVim *vim, int flags, actionFunction fcn, int cmd_flags); 15 | 16 | void _open_line(bool above); 17 | 18 | public: 19 | 20 | void enter_insert_mode(); 21 | void enter_insert_mode_append(); 22 | 23 | void enter_insert_mode_after_eol(); 24 | void enter_insert_mode_after_selection(); 25 | void enter_insert_mode_first_non_blank(); 26 | void enter_insert_mode_before_selection(); 27 | 28 | void enter_ex(); 29 | 30 | void open_line_above(); 31 | void open_line_below(); 32 | 33 | void undo(); 34 | void redo(); 35 | 36 | void paste(); 37 | void paste_before(); 38 | 39 | void toggle_visual_mode_line(); 40 | void toggle_visual_mode(); 41 | 42 | void next_tab(); 43 | void previous_tab(); 44 | 45 | void replace_char(); 46 | 47 | void delete_char(); 48 | void delete_previous_char(); 49 | 50 | void delete_to_eol(); 51 | void change_to_eol(); 52 | 53 | void scroll_to_center(); 54 | void scroll_to_center_first_non_blank(); 55 | 56 | void run(); 57 | 58 | bool is_action() { return true; } 59 | 60 | Action *get_action() { return this; } 61 | 62 | static Action *create_action(GodotVim *vim, int flags, actionFunction fcn, int cmd_flags = 0); 63 | }; 64 | 65 | #endif // ACTION_H 66 | -------------------------------------------------------------------------------- /godot_vim/command.cpp: -------------------------------------------------------------------------------- 1 | #include "command.h" 2 | 3 | bool Command::is_edit() { 4 | return cmd_flags & IS_EDIT; 5 | } 6 | 7 | bool Command::needs_char() { 8 | return cmd_flags & NEEDS_CHAR; 9 | } 10 | 11 | void Command::set_context(Context context) { 12 | this->context = context; 13 | } 14 | 15 | Command::Command(GodotVim * vim, int cmd_flags, Context context) : vim(vim), cmd_flags(cmd_flags), context(context) { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /godot_vim/command.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMAND_H 2 | #define COMMAND_H 3 | 4 | #include "reference.h" 5 | 6 | class GodotVim; 7 | class Motion; 8 | class Action; 9 | class Operation; 10 | 11 | enum Context { 12 | CONTEXT_NORMAL, 13 | CONTEXT_VISUAL, 14 | CONTEXT_OPERATION, 15 | CONTEXT_NONE 16 | }; 17 | 18 | class Command : public Reference { 19 | 20 | int cmd_flags; 21 | Context context; 22 | 23 | public: 24 | 25 | enum { 26 | NEEDS_CHAR = 1, 27 | IS_EDIT = 1 << 1, 28 | }; 29 | 30 | protected: 31 | GodotVim* vim; 32 | 33 | public: 34 | 35 | bool is_edit(); 36 | 37 | bool needs_char(); 38 | 39 | void set_context(Context context); 40 | 41 | virtual Motion *get_motion() { return NULL; } 42 | 43 | virtual Action *get_action() { return NULL; } 44 | 45 | virtual Operation *get_operation() { return NULL; } 46 | 47 | virtual bool is_motion() { return false; } 48 | 49 | virtual bool is_operation() { return false; } 50 | 51 | virtual bool is_action() { return false; } 52 | 53 | virtual void run() = 0; 54 | 55 | Command(GodotVim *vim, int cmd_flags = 0, Context context = CONTEXT_NONE); 56 | 57 | }; 58 | 59 | #endif // COMMAND_H 60 | -------------------------------------------------------------------------------- /godot_vim/config.py: -------------------------------------------------------------------------------- 1 | # config.py 2 | 3 | def can_build(platform): 4 | return True 5 | 6 | def configure(env): 7 | pass 8 | -------------------------------------------------------------------------------- /godot_vim/godot_vim.cpp: -------------------------------------------------------------------------------- 1 | #include "godot_vim.h" 2 | #include "map.h" 3 | #include "os/keyboard.h" 4 | #include "os/input_event.h" 5 | #include "editor/editor_node.h" 6 | #include "motion.h" 7 | #include "action.h" 8 | #include "operation.h" 9 | 10 | void GodotVim::_clear_state() { 11 | input_state.current_command = NULL; 12 | input_state.operator_command = NULL; 13 | input_state.input_string.clear(); 14 | input_state.repeat_count = 0; 15 | input_state.operator_count = 0; 16 | input_state.input_char.clear(); 17 | } 18 | 19 | void GodotVim::_editor_input(const InputEvent &p_event) { 20 | if (p_event.type == InputEvent::KEY) { 21 | const InputEventKey &ke = p_event.key; 22 | 23 | if (!ke.pressed) return; 24 | 25 | if (ke.scancode == KEY_ESCAPE) { 26 | if (vim_mode == INSERT) { 27 | _cursor_set_column(_cursor_get_column() - 1); 28 | } else if (vim_mode == NORMAL) { 29 | input_state.repeat_count = 0; 30 | input_state.input_string.clear(); 31 | } 32 | _set_vim_mode(NORMAL); 33 | } else { 34 | print_line("parsing command..."); 35 | _parse_command_input(ke); 36 | } 37 | } 38 | } 39 | 40 | void GodotVim::_parse_command_input(const InputEventKey &p_event) { 41 | String character = _get_character(p_event); 42 | 43 | if (input_state.current_command && input_state.current_command->needs_char()) { 44 | 45 | input_state.input_char = character; 46 | 47 | } else { 48 | input_state.input_string += character; 49 | 50 | Command *cmd = _find_command(input_state.input_string); 51 | 52 | if (cmd) { 53 | 54 | input_state.current_command = cmd; 55 | 56 | if (cmd->needs_char()) { 57 | return; 58 | } 59 | } 60 | } 61 | 62 | if (vim_mode == NORMAL && input_state.current_command) { 63 | 64 | _run_normal_command(input_state.current_command); 65 | 66 | } else if (vim_mode == VISUAL && input_state.current_command) { 67 | 68 | _run_visual_command(input_state.current_command); 69 | 70 | } else if (input_state.input_string.is_numeric()) { 71 | int n = input_state.input_string.to_int(); 72 | input_state.repeat_count = input_state.repeat_count * 10 + n; 73 | input_state.input_string.clear(); 74 | } else { 75 | 76 | if (!_map_contains_key(input_state.input_string, command_map)) 77 | _clear_state(); 78 | } 79 | } 80 | 81 | void GodotVim::_run_normal_command(Command *p_cmd) { 82 | if (p_cmd->is_operation() && input_state.operator_command) { 83 | 84 | if (input_state.operator_command == p_cmd) { 85 | print_line("same operator"); 86 | } 87 | _clear_state(); 88 | 89 | } else if (p_cmd->is_operation()) { 90 | 91 | input_state.operator_command = p_cmd; 92 | input_state.current_command = NULL; 93 | input_state.operator_count = input_state.repeat_count ? input_state.repeat_count : 1; 94 | input_state.repeat_count = 0; 95 | input_state.input_string.clear(); 96 | 97 | } else if (p_cmd->is_motion() && input_state.operator_command) { 98 | 99 | Command *op_cmd = input_state.operator_command; 100 | 101 | if (op_cmd->is_operation()) { 102 | op_cmd->get_operation()->apply_with_motion(p_cmd->get_motion()); 103 | } 104 | _clear_state(); 105 | } else { 106 | //print_line("running simple command!"); 107 | _run_command(p_cmd); 108 | _clear_state(); 109 | } 110 | } 111 | 112 | void GodotVim::_run_visual_command(Command *p_cmd) { 113 | int sl = text_edit->get_selection_from_line(); 114 | int sc = text_edit->get_selection_from_column(); 115 | 116 | if (sl < 0) sl = _cursor_get_line(); 117 | if (sc < 0) sc = _cursor_get_column(); 118 | 119 | print_line("running visual command!"); 120 | 121 | _run_command(p_cmd); 122 | 123 | if (p_cmd->is_operation()) 124 | _set_vim_mode(NORMAL); 125 | 126 | if (vim_mode == VISUAL) { 127 | _update_visual_selection(); 128 | } else { 129 | text_edit->deselect(); 130 | } 131 | _clear_state(); 132 | } 133 | 134 | void GodotVim::_update_visual_selection() { 135 | int l1 = _cursor_get_line(); 136 | int c1 = _cursor_get_column(); 137 | 138 | Motion::Range range; 139 | 140 | if (l1 < visual_start.y || (l1 == visual_start.y && c1 <= visual_start.x)) { 141 | if (visual_type == SELECT_LINES) { 142 | range = Motion::Range(l1, 0, visual_start.y, _get_line(visual_start.y).length()); 143 | range.linewise = true; 144 | } else { 145 | range = Motion::Range(l1, c1, visual_start.y, visual_start.x + 1); 146 | range.linewise = false; 147 | } 148 | } else { 149 | if (visual_type == SELECT_LINES) { 150 | range = Motion::Range(visual_start.y, 0, l1, _get_line(l1).length()); 151 | range.linewise = true; 152 | } else { 153 | range = Motion::Range(visual_start.y, visual_start.x, l1, c1 + 1); 154 | range.linewise = false; 155 | } 156 | } 157 | 158 | select_range(range); 159 | } 160 | 161 | const GodotVim::InputState GodotVim::get_input_state() { 162 | return input_state; 163 | } 164 | 165 | LineEdit *GodotVim::get_command_line() { 166 | return command_line; 167 | } 168 | 169 | bool GodotVim::_map_contains_key(const String &input, Map map) { 170 | 171 | for (Map::Element *e = map.front(); e; e = e->next()) { 172 | String k = e->key(); 173 | if (k.begins_with(input)) { 174 | return true; 175 | } 176 | } 177 | 178 | return false; 179 | } 180 | 181 | bool GodotVim::_is_command(const String &binding) { 182 | return command_map.has(binding) 183 | || (vim_mode == VISUAL && visual_command_map.has(binding)) 184 | || (vim_mode == NORMAL && normal_command_map.has(binding)); 185 | } 186 | 187 | Command * GodotVim::_find_command(String binding) { 188 | if (!_is_command(binding)) return NULL; 189 | 190 | if (vim_mode == NORMAL && normal_command_map.has(binding)) { 191 | return normal_command_map.find(binding)->get(); 192 | } else if (vim_mode == VISUAL && visual_command_map.has(binding)) { 193 | return visual_command_map.find(binding)->get(); 194 | } 195 | return command_map.find(binding)->get(); 196 | } 197 | 198 | bool GodotVim::_is_normal_command(const String &input) { 199 | return command_map.has(input) || normal_command_map.has(input); 200 | } 201 | 202 | bool GodotVim::_is_visual_command(const String &input) { 203 | return command_map.has(input) || visual_command_map.has(input); 204 | } 205 | 206 | bool GodotVim::_is_char_needed_command(const String &input) { 207 | if (input.empty()) return false; 208 | 209 | Command *cmd = _find_command(input); 210 | 211 | return cmd ? cmd->needs_char() : false; 212 | } 213 | 214 | void GodotVim::_run_command(Command *cmd) { 215 | print_line("_run_command"); 216 | //if (input_state.repeat_count == 0) input_state.repeat_count++; 217 | cmd->run(); 218 | } 219 | 220 | String GodotVim::_get_character(const InputEventKey &p_event) { 221 | String character = String::chr(wchar_t(p_event.unicode)); 222 | if (p_event.mod.control) 223 | character = ""; 224 | return character; 225 | } 226 | 227 | int GodotVim::_find_forward(const String &p_string) { 228 | int n = _get_current_line().find(p_string, _cursor_get_column()); 229 | if (n >= 0) { 230 | _cursor_set_column(n); 231 | } 232 | } 233 | 234 | int GodotVim::_find_backward(const String &p_string) { 235 | int n = _get_current_line().substr(0, _cursor_get_column()).find(p_string); 236 | if (n >= 0) { 237 | _cursor_set_column(n); 238 | } 239 | } 240 | 241 | void GodotVim::_cursor_set(int line, int col) { 242 | text_edit->cursor_set_line(line); 243 | text_edit->cursor_set_column(col); 244 | } 245 | 246 | void GodotVim::_cursor_set_line(int line) { 247 | text_edit->cursor_set_line(line); 248 | } 249 | 250 | void GodotVim::_cursor_set_column(int column) { 251 | text_edit->cursor_set_column(column); 252 | } 253 | 254 | int GodotVim::_cursor_get_line() { 255 | text_edit->cursor_get_line(); 256 | } 257 | 258 | int GodotVim::_cursor_get_column() { 259 | text_edit->cursor_get_column(); 260 | } 261 | 262 | Motion::Range GodotVim::get_selection() { 263 | return selection_range; 264 | } 265 | 266 | void GodotVim::select_range(Motion::Range range) { 267 | if (range.linewise) { 268 | int count = _get_line(range.to.row).length(); 269 | range.from.col = 0; 270 | range.to.col = count; 271 | } 272 | print_line("selecting from " + itos(range.from.row) + "," + itos(range.from.col)); 273 | print_line(" to " + itos(range.to.row) + "," + itos(range.to.col)); 274 | get_text_edit()->select(range.from.row, range.from.col, range.to.row, range.to.col); 275 | selection_range = range; 276 | } 277 | 278 | void GodotVim::yank_range(Motion::Range range) { 279 | registry.lines.clear(); 280 | for (int i = range.from.row; i <= range.to.row; i++) { 281 | String line = _get_line(i); 282 | int col1 = 0; 283 | int col2 = line.length(); 284 | 285 | if (i == range.from.row) col1 = range.from.col; 286 | if (i == range.to.row) col2 = range.to.col; 287 | 288 | registry.lines.push_back(line.substr(col1, col2 - col1)); 289 | 290 | registry.linewise = range.linewise; 291 | 292 | print_line(line.substr(col1, col2)); 293 | } 294 | } 295 | 296 | Registry GodotVim::get_registry() { 297 | return registry; 298 | } 299 | 300 | int GodotVim::_get_current_line_length() { 301 | return text_edit->get_line(text_edit->cursor_get_line()).length(); 302 | } 303 | 304 | String GodotVim::_get_current_line() { 305 | return _get_line(_cursor_get_line()); 306 | } 307 | 308 | String GodotVim::_get_line(int line) { 309 | return text_edit->get_line(line); 310 | } 311 | 312 | int GodotVim::_get_line_count() { 313 | return text_edit->get_line_count(); 314 | } 315 | 316 | void GodotVim::set_line(int line, String text) { 317 | text_edit->set_line(line, text); 318 | } 319 | 320 | 321 | TextEdit * GodotVim::get_text_edit() { 322 | return text_edit; 323 | } 324 | 325 | void GodotVim::_goto_next_tab() { 326 | TabContainer *tab_container = text_edit->get_parent()->get_parent()->cast_to(); 327 | tab_container->set_current_tab(tab_container->get_current_tab()+1); 328 | } 329 | 330 | void GodotVim::_goto_previous_tab() { 331 | 332 | } 333 | 334 | void GodotVim::_move_by_columns(int cols) { 335 | int col = CLAMP(text_edit->cursor_get_column() + cols, 0, _get_current_line_length() - 1); 336 | text_edit->cursor_set_column(col); 337 | virtual_column = col; 338 | } 339 | 340 | void GodotVim::_move_by_lines(int lines) { 341 | int line = CLAMP(text_edit->cursor_get_line() + lines, 0, text_edit->get_line_count() - 1); 342 | text_edit->cursor_set_line(line); 343 | //_check_virtual_column(); 344 | } 345 | 346 | void GodotVim::_check_virtual_column() { 347 | int line_length = _get_current_line_length(); 348 | if (text_edit->cursor_get_column() >= line_length) { 349 | text_edit->cursor_set_column(line_length - 1); 350 | } else if (virtual_column < line_length) { 351 | text_edit->cursor_set_column(virtual_column); 352 | } 353 | } 354 | 355 | GodotVim::VimMode GodotVim::_get_vim_mode() { 356 | return vim_mode; 357 | } 358 | 359 | void GodotVim::_set_vim_mode(VimMode mode) { 360 | switch (mode) { 361 | 362 | case NORMAL: 363 | text_edit->cursor_set_block_mode(true); 364 | text_edit->set_readonly(true); 365 | text_edit->deselect(); 366 | break; 367 | 368 | case INSERT: 369 | text_edit->cursor_set_block_mode(false); 370 | text_edit->set_readonly(false); 371 | text_edit->deselect(); 372 | break; 373 | 374 | case VISUAL: 375 | if (vim_mode != VISUAL) { 376 | visual_start.x = _cursor_get_column(); 377 | visual_start.y = _cursor_get_line(); 378 | } 379 | _update_visual_selection(); 380 | break; 381 | 382 | default: 383 | break; 384 | } 385 | 386 | vim_mode = mode; 387 | } 388 | 389 | GodotVim::VisualType GodotVim::_get_visual_type() { 390 | return visual_type; 391 | } 392 | 393 | void GodotVim::_set_visual_type(GodotVim::VisualType vtype) { 394 | visual_type = vtype; 395 | } 396 | 397 | void GodotVim::_setup_editor() { 398 | if (vim_mode == NORMAL) { 399 | text_edit->cursor_set_block_mode(true); 400 | text_edit->set_readonly(true); 401 | } 402 | 403 | _clear_state(); 404 | } 405 | 406 | void GodotVim::_setup_commands() { 407 | if (!command_map.empty()) return; 408 | 409 | _create_command("h", Motion::create_motion(this, 0, &Motion::move_left)); 410 | _create_command("j", Motion::create_motion(this, Motion::LINEWISE, &Motion::move_down)); 411 | _create_command("k", Motion::create_motion(this, Motion::LINEWISE, &Motion::move_up)); 412 | _create_command("l", Motion::create_motion(this, 0, &Motion::move_right)); 413 | _create_command("0", Motion::create_motion(this, 0, &Motion::move_to_line_start)); 414 | _create_command("$", Motion::create_motion(this, 0, &Motion::move_to_line_end)); 415 | _create_command("^", Motion::create_motion(this, 0, &Motion::move_to_first_non_blank)); 416 | _create_command("gg", Motion::create_motion(this, 0, &Motion::move_to_beginning_of_first_line)); 417 | _create_command("G", Motion::create_motion(this, 0, &Motion::move_to_beginning_of_last_line)); 418 | _create_command("-", Motion::create_motion(this, 0, &Motion::move_to_beginning_of_previous_line)); 419 | _create_command("+", Motion::create_motion(this, 0, &Motion::move_to_beginning_of_next_line)); 420 | _create_command(";", Motion::create_motion(this, 0, &Motion::move_to_last_searched_char)); 421 | _create_command(",", Motion::create_motion(this, 0, &Motion::move_to_last_searched_char_backward)); 422 | _create_command("|", Motion::create_motion(this, 0, &Motion::move_to_column)); 423 | _create_command("w", Motion::create_motion(this, 0, &Motion::move_word_right)); 424 | _create_command("W", Motion::create_motion(this, 0, &Motion::move_word_right_big)); 425 | _create_command("e", Motion::create_motion(this, Motion::INCLUSIVE, &Motion::move_word_end)); 426 | _create_command("E", Motion::create_motion(this, Motion::INCLUSIVE, &Motion::move_word_end_big)); 427 | _create_command("ge", Motion::create_motion(this, 0, &Motion::move_word_end_backward)); 428 | _create_command("gE", Motion::create_motion(this, 0, &Motion::move_word_end_big_backward)); 429 | _create_command("b", Motion::create_motion(this, 0, &Motion::move_word_beginning)); 430 | _create_command("B", Motion::create_motion(this, 0, &Motion::move_word_beginning_big)); 431 | _create_command("}", Motion::create_motion(this, Motion::LINEWISE, &Motion::move_paragraph_down)); 432 | _create_command("{", Motion::create_motion(this, Motion::LINEWISE, &Motion::move_paragraph_up)); 433 | _create_command("%", Motion::create_motion(this, 0, &Motion::move_to_matching_pair)); 434 | _create_command("n", Motion::create_motion(this, 0, &Motion::find_next)); 435 | _create_command("N", Motion::create_motion(this, 0, &Motion::find_previous)); 436 | 437 | _create_command("f", Motion::create_motion(this, Motion::INCLUSIVE, &Motion::find_char, Command::NEEDS_CHAR)); 438 | _create_command("F", Motion::create_motion(this, Motion::INCLUSIVE, &Motion::find_char_backward, Command::NEEDS_CHAR)); 439 | _create_command("t", Motion::create_motion(this, 0, &Motion::find_char, Command::NEEDS_CHAR)); 440 | _create_command("T", Motion::create_motion(this, 0, &Motion::find_char_backward, Command::NEEDS_CHAR)); 441 | 442 | // ACTIONS 443 | _create_command("i", Action::create_action(this, 0, &Action::enter_insert_mode)); 444 | _create_command("a", Action::create_action(this, 0, &Action::enter_insert_mode_append)); 445 | _create_command("A", Action::create_action(this, 0, &Action::enter_insert_mode_after_eol)); 446 | _create_command("A", Action::create_action(this, 0, &Action::enter_insert_mode_after_selection), VISUAL); 447 | _create_command("I", Action::create_action(this, 0, &Action::enter_insert_mode_first_non_blank)); 448 | _create_command("I", Action::create_action(this, 0, &Action::enter_insert_mode_before_selection), VISUAL); 449 | _create_command("p", Action::create_action(this, 0, &Action::paste)); 450 | _create_command("P", Action::create_action(this, 0, &Action::paste_before)); 451 | _create_command("u", Action::create_action(this, 0, &Action::undo)); 452 | _create_command("", Action::create_action(this, 0, &Action::redo)); 453 | _create_command("v", Action::create_action(this, 0, &Action::toggle_visual_mode)); 454 | _create_command("V", Action::create_action(this, 0, &Action::toggle_visual_mode_line)); 455 | _create_command("o", Action::create_action(this, 0, &Action::open_line_below)); 456 | _create_command("O", Action::create_action(this, 0, &Action::open_line_above)); 457 | 458 | _create_command("D", Action::create_action(this, 0, &Action::delete_to_eol), NORMAL); 459 | _create_command("C", Action::create_action(this, 0, &Action::change_to_eol), NORMAL); 460 | _create_command("zz", Action::create_action(this, 0, &Action::scroll_to_center)); 461 | 462 | _create_command("r", Action::create_action(this, 0, &Action::replace_char, Command::NEEDS_CHAR)); 463 | 464 | _create_command(":", Action::create_action(this, 0, &Action::enter_ex)); 465 | 466 | _create_command("x", Action::create_action(this, 0, &Action::delete_char)); 467 | _create_command("X", Action::create_action(this, 0, &Action::delete_previous_char)); 468 | 469 | // SEARCH 470 | _create_command("*", Motion::create_motion(this, 0, &Motion::search_word_under_cursor)); 471 | _create_command("#", Motion::create_motion(this, 0, &Motion::search_word_under_cursor_backward)); 472 | 473 | // OPERATIONS 474 | _create_command("d", Operation::create_operation(this, 0, &Operation::delete_text)); 475 | _create_command("D", Operation::create_operation(this, 0, &Operation::delete_text), VISUAL); 476 | 477 | _create_command("y", Operation::create_operation(this, 0, &Operation::yank)); 478 | _create_command("c", Operation::create_operation(this, 0, &Operation::change_text)); 479 | _create_command("C", Operation::create_operation(this, 0, &Operation::change_lines), VISUAL); 480 | 481 | _create_command(">", Operation::create_operation(this, 0, &Operation::indent_text)); 482 | _create_command("<", Operation::create_operation(this, 0, &Operation::unindent_text)); 483 | _create_command("=", Operation::create_operation(this, 0, &Operation::reindent_text)); 484 | _create_command("g~", Operation::create_operation(this, 0, &Operation::toggle_case)); 485 | _create_command("gu", Operation::create_operation(this, 0, &Operation::to_lower)); 486 | _create_command("u", Operation::create_operation(this, 0, &Operation::to_lower), VISUAL); 487 | _create_command("gU", Operation::create_operation(this, 0, &Operation::to_upper)); 488 | _create_command("U", Operation::create_operation(this, 0, &Operation::to_upper), VISUAL); 489 | 490 | // Some unimplemented commands 491 | //_create_command("", &GodotVim::_toggle_visual_mode_block, ACTION); 492 | 493 | // ~ 494 | //_create_command("gt", &GodotVim::_goto_next_tab, ACTION); 495 | //_create_command("gT", &GodotVim::_goto_previous_tab, ACTION); 496 | 497 | //_create_command("J", &GodotVim::_join_lines, ACTION); 498 | 499 | //_create_command("z.", &GodotVim::_scroll_to_center_first_non_blank, ACTION); 500 | //_create_command("zt", &GodotVim::_scroll_to_top, ACTION); 501 | //_create_command("z", &GodotVim::_scroll_to_top_first_non_blank, ACTION); 502 | //_create_command("z-", &GodotVim::_scroll_to_bottom_, ACTION); 503 | //_create_command("zb", &GodotVim::_scroll_to_bottom_first_non_blank, ACTION); 504 | //_create_command(".", &GodotVim::_repeat_last_edit, ACTION); 505 | // 506 | // 507 | // 508 | 509 | //_create_command("a", &GodotVim::_text_object, MOTION); 510 | //_create_command("i", &GodotVim::_text_object_inner, MOTION); 511 | 512 | //_create_command("/", &GodotVim::_start_search, SEARCH); 513 | //_create_command("?", &GodotVim::_start_search_backward, SEARCH); 514 | // g* 515 | // g# 516 | 517 | } 518 | 519 | void GodotVim::_create_command(String binding, Command *cmd, VimMode context) { 520 | if (context == NONE) { 521 | command_map.insert(binding, cmd); 522 | } else if (context == NORMAL) { 523 | normal_command_map.insert(binding, cmd); 524 | } else if (context == VISUAL) { 525 | visual_command_map.insert(binding, cmd); 526 | } 527 | } 528 | 529 | void GodotVim::disconnect_all() { 530 | text_edit->disconnect("input_event", this, "_editor_input"); 531 | text_edit->disconnect("focus_enter", this, "_editor_focus_enter"); 532 | } 533 | 534 | void GodotVim::_editor_focus_enter() { 535 | _setup_editor(); 536 | } 537 | 538 | void GodotVim::_command_input(const String &p_input) { 539 | command_line->hide(); 540 | text_edit->grab_focus(); 541 | if (p_input == ":w") { 542 | save_script(); 543 | } 544 | } 545 | 546 | void GodotVim::_bind_methods() { 547 | ObjectTypeDB::bind_method("_editor_input", &GodotVim::_editor_input); 548 | ObjectTypeDB::bind_method("_editor_focus_enter", &GodotVim::_editor_focus_enter); 549 | ObjectTypeDB::bind_method("_command_input", &GodotVim::_command_input); 550 | } 551 | 552 | void GodotVim::save_script() { 553 | print_line("Saving resource..."); 554 | editor_node->save_resource(editor->cast_to()->get_edited_script()); 555 | } 556 | 557 | void GodotVim::set_editor(CodeTextEditor *editor) { 558 | this->editor = editor; 559 | this->text_edit = editor->get_text_edit(); 560 | 561 | text_edit->connect("input_event", this, "_editor_input"); 562 | text_edit->connect("focus_enter", this, "_editor_focus_enter"); 563 | 564 | // TEST 565 | 566 | command_line = memnew(LineEdit); 567 | editor->add_child(command_line); 568 | editor->move_child(command_line, text_edit->get_index() + 1); 569 | command_line->connect("text_entered", this, "_command_input"); 570 | command_line->hide(); 571 | 572 | // END 573 | 574 | _setup_editor(); 575 | } 576 | 577 | GodotVim::GodotVim(EditorNode *p_editor_node) { 578 | editor_node = p_editor_node; 579 | vim_mode = NORMAL; 580 | virtual_column = 0; 581 | visual_start = Vector2(); 582 | _setup_commands(); 583 | } 584 | 585 | GodotVim::~GodotVim() { 586 | } 587 | 588 | void GodotVimPlugin::_node_removed(Node * p_node) { 589 | } 590 | 591 | void GodotVimPlugin::_tree_changed() { 592 | print_line("tree changed"); 593 | 594 | if (plugged) return; 595 | 596 | Node *editor = get_editor_viewport(); 597 | 598 | if (editor == NULL) return; 599 | 600 | Node *script_editor = _get_node_by_type(editor, "ScriptEditor"); 601 | 602 | if (!script_editor) return; 603 | 604 | Node *split = _get_node_by_type(script_editor, "HSplitContainer"); 605 | 606 | if (!split) return; 607 | 608 | Node *tc = _get_node_by_type(split, "TabContainer"); 609 | 610 | Node *ls = _get_node_by_type(split, "VSplitContainer"); 611 | 612 | Node *sl = _get_node_by_type(ls, "ItemList"); 613 | 614 | if (!tc || !sl) return; 615 | 616 | plugged = true; 617 | 618 | tab_container = tc->cast_to(); 619 | 620 | tab_container->connect("tab_changed", this, "_tabs_changed"); 621 | 622 | for (int i = 0; i < tab_container->get_tab_count(); i++) { 623 | if (!tab_container->get_tab_control(i)->is_type("CodeTextEditor")) 624 | continue; 625 | 626 | CodeTextEditor *code_editor = tab_container->get_tab_control(i)->cast_to(); 627 | 628 | if (code_editor->has_node(NodePath("GodotVim"))) { 629 | continue; 630 | } 631 | 632 | _plug_editor(code_editor); 633 | } 634 | 635 | } 636 | 637 | Node *GodotVimPlugin::_get_node_by_type(Node *node, String type) { 638 | for (int i=0; i < node->get_child_count(); i++) { 639 | Node * child = node->get_child(i); 640 | if (child->is_type(type)) { 641 | return child; 642 | } 643 | } 644 | 645 | return NULL; 646 | } 647 | 648 | void GodotVimPlugin::_plug_editor(CodeTextEditor *editor) { 649 | print_line("plugging..."); 650 | GodotVim *godot_vim = memnew(GodotVim(editor_node)); 651 | godot_vim->set_name("GodotVim"); 652 | godot_vim->set_editor(editor); 653 | editor->add_child(godot_vim); 654 | } 655 | 656 | void GodotVimPlugin::_notification(int p_what) { 657 | if (p_what == NOTIFICATION_ENTER_TREE) { 658 | print_line("entered tree"); 659 | get_tree()->connect("tree_changed", this, "_tree_changed"); 660 | get_tree()->connect("node_removed", this, "_node_removed"); 661 | } else if (p_what == NOTIFICATION_EXIT_TREE) { 662 | print_line("exiting tree"); 663 | get_tree()->disconnect("node_removed", this, "_node_removed"); 664 | get_tree()->disconnect("tree_changed", this, "_tree_changed"); 665 | } 666 | } 667 | 668 | void GodotVimPlugin::_tabs_changed(int current) { 669 | print_line("tabs changed"); 670 | 671 | Control *control = tab_container->get_tab_control(current); 672 | 673 | if (!control->is_type("CodeTextEditor") || control->has_node(NodePath("GodotVim"))) 674 | return; 675 | 676 | _plug_editor(control->cast_to()); 677 | } 678 | 679 | void GodotVimPlugin::_bind_methods() { 680 | ObjectTypeDB::bind_method("_tree_changed", &GodotVimPlugin::_tree_changed); 681 | ObjectTypeDB::bind_method("_node_removed", &GodotVimPlugin::_node_removed); 682 | ObjectTypeDB::bind_method("_tabs_changed", &GodotVimPlugin::_tabs_changed); 683 | } 684 | 685 | GodotVimPlugin::GodotVimPlugin(EditorNode *p_node) { 686 | print_line("plugin created"); 687 | editor_node = p_node; 688 | plugged = false; 689 | } 690 | 691 | GodotVimPlugin::~GodotVimPlugin() { 692 | 693 | //if (tab_container) 694 | //tab_container->disconnect("tab_changed", this, "_tabs_changed"); 695 | } 696 | -------------------------------------------------------------------------------- /godot_vim/godot_vim.h: -------------------------------------------------------------------------------- 1 | #ifndef GODOT_VIM_H 2 | #define GODOT_VIM_H 3 | 4 | #include "reference.h" 5 | #include "editor/editor_plugin.h" 6 | #include "editor/plugins/script_editor_plugin.h" 7 | #include "motion.h" 8 | 9 | class GodotVimPlugin; 10 | 11 | struct Registry { 12 | bool linewise; 13 | Vector lines; 14 | }; 15 | 16 | class GodotVim : public Node { 17 | public: 18 | enum VimMode { 19 | NORMAL, 20 | INSERT, 21 | VISUAL, 22 | NONE 23 | }; 24 | 25 | enum VisualType { 26 | SELECT_CHARS, 27 | SELECT_LINES, 28 | SELECT_BLOCK 29 | }; 30 | 31 | private: 32 | OBJ_TYPE(GodotVim, Node); 33 | 34 | typedef void (GodotVim::* cmdFunction) (); 35 | typedef bool (* matchFunction) (int, String); 36 | 37 | VimMode vim_mode; 38 | 39 | VisualType visual_type; 40 | 41 | enum CommandType { 42 | MOTION, 43 | OPERATOR, 44 | ACTION, 45 | SEARCH, 46 | EX 47 | }; 48 | 49 | struct VimCommand { 50 | String binding; 51 | cmdFunction function; 52 | CommandType type; 53 | VimMode context; 54 | VimCommand(String binding, cmdFunction function, CommandType type, VimMode context) 55 | : binding(binding), function(function), type(type), context(context) {} 56 | VimCommand() {} 57 | }; 58 | 59 | struct MotionCommand : VimCommand { 60 | bool linewise; 61 | MotionCommand() : linewise(true) {} 62 | }; 63 | 64 | struct InputState { 65 | Command *operator_command; 66 | Command *current_command; 67 | int operator_count; 68 | int repeat_count; 69 | String input_string; 70 | String input_char; 71 | } input_state; 72 | 73 | Registry registry; 74 | Motion::Range selection_range; 75 | 76 | Vector command_bindings; 77 | Map command_map; 78 | Map normal_command_map; 79 | Map visual_command_map; 80 | 81 | Vector2 visual_start; 82 | int virtual_column; 83 | 84 | GodotVimPlugin *plugin; 85 | EditorNode *editor_node; 86 | TextEdit *text_edit; 87 | CodeTextEditor *editor; 88 | LineEdit *command_line; 89 | 90 | void _setup_editor(); 91 | void _clear_state(); 92 | void _editor_focus_enter(); 93 | void _editor_input(const InputEvent &p_event); 94 | void _command_input(const String &p_input); 95 | 96 | // Helper methods 97 | void _parse_command_input(const InputEventKey &p_event); 98 | void _run_normal_command(Command *p_cmd); 99 | void _run_visual_command(Command *p_cmd); 100 | 101 | Command * _find_command(String binding); 102 | 103 | void _setup_commands(); 104 | 105 | bool _is_command(const String &binding); 106 | 107 | bool _is_normal_command(const String &input); 108 | bool _is_visual_command(const String &input); 109 | 110 | bool _is_char_needed_command(const String &input); 111 | 112 | bool _map_contains_key(const String &input, Map map); 113 | 114 | void _run_command(Command *cmd); 115 | 116 | String _get_character(const InputEventKey &p_event); 117 | 118 | void _enter_insert_mode(); 119 | void _enter_insert_mode_append(); 120 | 121 | void _toggle_visual_mode(); 122 | void _toggle_visual_mode_line(); 123 | 124 | void _open_line_above(); 125 | void _open_line_below(); 126 | 127 | void _goto_next_tab(); 128 | void _goto_previous_tab(); 129 | 130 | // Utility functions 131 | int _find_forward(const String &p_string); 132 | int _find_backward(const String &p_string); 133 | 134 | void _move_backward(matchFunction function); 135 | void _move_forward(matchFunction function); 136 | void _move_to_first_non_blank(int line); 137 | void _move_by_columns(int cols); 138 | void _move_by_lines(int lines); 139 | void _open_line(int line); 140 | Point2 _find_next_word(); 141 | 142 | void _check_virtual_column(); 143 | 144 | static void _add_command(VimCommand *command); 145 | 146 | protected: 147 | static void _bind_methods(); 148 | void disconnect_all(); 149 | 150 | public: 151 | void set_editor(CodeTextEditor *editor); 152 | 153 | void _create_command(String binding, Command *cmd, VimMode context = NONE); 154 | 155 | void _set_vim_mode(VimMode mode); 156 | VimMode _get_vim_mode(); 157 | void _set_visual_type(VisualType vtype); 158 | VisualType _get_visual_type(); 159 | 160 | void _update_visual_selection(); 161 | 162 | void _undo(); 163 | void _redo(); 164 | 165 | String _get_current_line(); 166 | String _get_line(int line); 167 | int _get_current_line_length(); 168 | int _get_line_count(); 169 | 170 | void _cursor_set(int line, int col); 171 | void _cursor_set_line(int line); 172 | void _cursor_set_column(int column); 173 | int _cursor_get_line(); 174 | int _cursor_get_column(); 175 | 176 | Motion::Range get_selection(); 177 | 178 | void select_range(Motion::Range range); 179 | void yank_range(Motion::Range range); 180 | Registry get_registry(); 181 | 182 | void set_line(int line, String text); 183 | 184 | void save_script(); 185 | 186 | const InputState get_input_state(); 187 | 188 | TextEdit *get_text_edit(); 189 | LineEdit *get_command_line(); 190 | 191 | GodotVim(EditorNode *editor_node); 192 | ~GodotVim(); 193 | 194 | static bool _is_text_char(CharType c); 195 | static bool _is_symbol(CharType c); 196 | static bool _is_char(CharType c); 197 | static bool _is_number(CharType c); 198 | static bool _is_hex_symbol(CharType c); 199 | static bool _is_pair_right_symbol(CharType c); 200 | static bool _is_pair_left_symbol(CharType c); 201 | static bool _is_pair_symbol(CharType c); 202 | static CharType _get_right_pair_symbol(CharType c); 203 | static CharType _get_left_pair_symbol(CharType c); 204 | static bool _is_space(CharType c); 205 | }; 206 | 207 | class GodotVimPlugin : public EditorPlugin { 208 | OBJ_TYPE(GodotVimPlugin, EditorPlugin); 209 | 210 | static Map editor_registry; 211 | 212 | TabContainer *tab_container; 213 | 214 | void _plug_editor(CodeTextEditor *editor); 215 | 216 | EditorNode *editor_node; 217 | 218 | bool plugged; 219 | 220 | protected: 221 | static void _bind_methods(); 222 | 223 | public: 224 | void _notification(int p_what); 225 | void _tree_changed(); 226 | void _node_removed(Node *p_node); 227 | void _tabs_changed(int current); 228 | 229 | Node *_get_node_by_type(Node *node, String type); 230 | 231 | GodotVimPlugin(EditorNode *p_node); 232 | ~GodotVimPlugin(); 233 | }; 234 | 235 | #endif // GODOT_VIM_H 236 | -------------------------------------------------------------------------------- /godot_vim/motion.cpp: -------------------------------------------------------------------------------- 1 | #include "motion.h" 2 | #include "util.h" 3 | #include "godot_vim.h" 4 | 5 | Motion::Pos Motion::_move_forward(Motion::matchFunction function, bool accept_empty) { 6 | int next_cc = vim->_cursor_get_column() + 1; 7 | int next_cl = vim->_cursor_get_line(); 8 | 9 | String line_text = vim->_get_line(next_cl); 10 | 11 | for (int i = next_cl; i < vim->_get_line_count(); i++) { 12 | line_text = vim->_get_line(i); 13 | 14 | if (line_text.length() == 0 || next_cc >= line_text.length()) { 15 | if (line_text.length() == 0 && accept_empty && i > vim->_cursor_get_line()) return Pos(i, 0); 16 | next_cc = 0; 17 | continue; 18 | } 19 | 20 | for (int j = next_cc; j < line_text.length(); j++) { 21 | if ((function)(j, line_text)) { 22 | print_line("setting cursor to " + itos(j) + ", " + itos(i)); 23 | return Pos(i, j); 24 | } 25 | } 26 | } 27 | 28 | return Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 29 | } 30 | 31 | Motion::Pos Motion::_move_backward(Motion::matchFunction function, bool accept_empty) { 32 | int next_cc = vim->_cursor_get_column() - 1; 33 | int next_cl = vim->_cursor_get_line(); 34 | 35 | String line_text = vim->_get_line(next_cl); 36 | 37 | bool needs_cc = false; 38 | 39 | for (int i = next_cl; i >= 0; i--) { 40 | line_text = vim->_get_line(i); 41 | 42 | if (needs_cc) next_cc = line_text.length() - 1; 43 | 44 | if (line_text.length() == 0) { 45 | if (accept_empty && i < vim->_cursor_get_line()) return Pos(i, 0); 46 | 47 | needs_cc = true; 48 | continue; 49 | } 50 | 51 | for (int j = next_cc; j >= 0; j--) { 52 | if ((function)(j, line_text)) { 53 | print_line("setting cursor to " + itos(j) + ", " + itos(i)); 54 | vim->_cursor_set_line(i); 55 | vim->_cursor_set_column(j); 56 | return Pos(i, j); 57 | } 58 | } 59 | 60 | needs_cc = true; 61 | } 62 | 63 | return Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 64 | } 65 | 66 | Motion::Pos Motion::_move_to_first_non_blank(int line) { 67 | String line_text = vim->_get_line(line); 68 | if (vim->_cursor_get_line() != line) 69 | vim->_cursor_set_line(line); 70 | for (int i = 0; i < line_text.length(); i++) { 71 | CharType c = line_text[i]; 72 | if (!_is_space(c)) { 73 | vim->_cursor_set_column(i); 74 | return Pos(line, i); 75 | } 76 | } 77 | 78 | return Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 79 | } 80 | 81 | Motion::Pos Motion::_move_by_columns(int cols) { 82 | int col = CLAMP(vim->_cursor_get_column() + cols, 0, vim->_get_current_line_length() - 1); 83 | return Pos(vim->_cursor_get_line(), col); 84 | } 85 | 86 | Motion::Pos Motion::_move_by_lines(int lines) { 87 | int line = CLAMP(vim->_cursor_get_line() + lines, 0, vim->_get_line_count() - 1); 88 | return Pos(line, vim->_cursor_get_column()); 89 | } 90 | 91 | Motion::Pos Motion::move_left() { 92 | return _move_by_columns(-1); 93 | } 94 | 95 | Motion::Pos Motion::move_right() { 96 | return _move_by_columns(1); 97 | } 98 | 99 | Motion::Pos Motion::move_down() { 100 | return _move_by_lines(1); 101 | } 102 | 103 | Motion::Pos Motion::move_up() { 104 | return _move_by_lines(-1); 105 | } 106 | 107 | Motion::Pos Motion::move_to_line_start() { 108 | return Pos(vim->_cursor_get_line(), 0); 109 | } 110 | 111 | Motion::Pos Motion::move_to_line_end() { 112 | return Pos(vim->_cursor_get_line(), vim->_get_current_line_length() - 1); 113 | } 114 | 115 | Motion::Pos Motion::move_word_right() { 116 | return _move_forward(&_is_beginning_of_word, true); 117 | } 118 | 119 | Motion::Pos Motion::move_word_right_big() { 120 | return _move_forward(&_is_beginning_of_big_word, true); 121 | } 122 | 123 | Motion::Pos Motion::move_word_end() { 124 | return _move_forward(&_is_end_of_word); 125 | } 126 | 127 | Motion::Pos Motion::move_word_end_big() { 128 | return _move_forward(&_is_end_of_big_word); 129 | } 130 | 131 | Motion::Pos Motion::move_word_end_backward() { 132 | return _move_backward(&_is_end_of_word); 133 | } 134 | 135 | Motion::Pos Motion::move_word_end_big_backward() { 136 | return _move_backward(&_is_end_of_big_word); 137 | } 138 | 139 | Motion::Pos Motion::move_word_beginning() { 140 | return _move_backward(&_is_beginning_of_word, true); 141 | } 142 | 143 | Motion::Pos Motion::move_word_beginning_big() { 144 | return _move_backward(&_is_beginning_of_big_word, true); 145 | } 146 | 147 | Motion::Pos Motion::move_paragraph_up() { 148 | int next_cl = vim->_cursor_get_line(); 149 | 150 | bool has_text = false; 151 | 152 | for (int i = next_cl; i >= 0; i--) { 153 | String line_text = vim->_get_line(i); 154 | if (line_text.size() == 0) { 155 | if (has_text) { 156 | return Pos(i, 0); 157 | } 158 | } else { 159 | has_text = true; 160 | } 161 | } 162 | 163 | return Pos(0, 0); 164 | } 165 | 166 | Motion::Pos Motion::move_paragraph_down() { 167 | int next_cl = vim->_cursor_get_line(); 168 | 169 | bool has_text = false; 170 | 171 | for (int i = next_cl; i < vim->_get_line_count(); i++) { 172 | String line_text = vim->_get_line(i); 173 | if (line_text.size() == 0) { 174 | if (has_text) { 175 | return Pos(i, 0); 176 | } 177 | } else { 178 | has_text = true; 179 | } 180 | } 181 | 182 | int last_line = vim->_get_line_count() - 1; 183 | return Pos(last_line, vim->_get_line(last_line).length() - 1); 184 | } 185 | 186 | Motion::Pos Motion::move_to_matching_pair() { 187 | String line_text = vim->_get_line(vim->_cursor_get_line()); 188 | int col = vim->_cursor_get_column(); 189 | CharType c = line_text[col]; 190 | if (_is_pair_left_symbol(c)) { 191 | CharType m = _get_right_pair_symbol(c); 192 | 193 | col++; 194 | int count = 1; 195 | 196 | for (int i = vim->_cursor_get_line(); i < vim->_get_line_count(); i++) { 197 | line_text = vim->_get_line(i); 198 | for (int j = col; j < line_text.length(); j++) { 199 | CharType cur = line_text[j]; 200 | if (cur == c) { 201 | count++; 202 | } else if (cur == m) { 203 | count--; 204 | if (count == 0) { 205 | return Pos(i, j); 206 | } 207 | } 208 | } 209 | col = 0; 210 | } 211 | } else if (_is_pair_right_symbol(c)) { 212 | CharType m = _get_left_pair_symbol(c); 213 | 214 | col--; 215 | int count = 1; 216 | bool needs_col = false; 217 | 218 | for (int i = vim->_cursor_get_line(); i >= 0; i--) { 219 | line_text = vim->_get_line(i); 220 | 221 | if (needs_col) col = line_text.length(); 222 | 223 | for (int j = col; j >= 0; j--) { 224 | CharType cur = line_text[j]; 225 | if (cur == c) { 226 | count++; 227 | } else if (cur == m) { 228 | count--; 229 | if (count == 0) { 230 | return Pos(i, j); 231 | } 232 | } 233 | } 234 | needs_col = true; 235 | } 236 | } 237 | 238 | return Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 239 | } 240 | 241 | Motion::Pos Motion::move_to_first_non_blank() { 242 | return _move_to_first_non_blank(vim->_cursor_get_line()); 243 | } 244 | 245 | 246 | Motion::Pos Motion::move_to_beginning_of_last_line() { 247 | return _move_to_first_non_blank(vim->_get_line_count() - 1); 248 | } 249 | 250 | Motion::Pos Motion::move_to_beginning_of_first_line() { 251 | return _move_to_first_non_blank(0); 252 | } 253 | 254 | Motion::Pos Motion::move_to_beginning_of_previous_line() { 255 | if (vim->_cursor_get_line() > 0) { 256 | return _move_to_first_non_blank(vim->_cursor_get_line() - 1); 257 | } 258 | return move_to_first_non_blank(); 259 | } 260 | 261 | Motion::Pos Motion::move_to_beginning_of_next_line() { 262 | if (vim->_cursor_get_line() < vim->_get_line_count() - 1) { 263 | _move_to_first_non_blank(vim->_cursor_get_line() + 1); 264 | } 265 | return move_to_first_non_blank(); 266 | } 267 | 268 | Motion::Pos Motion::move_to_last_searched_char() { 269 | 270 | } 271 | 272 | Motion::Pos Motion::move_to_last_searched_char_backward() { 273 | 274 | } 275 | 276 | Motion::Pos Motion::move_to_column() { 277 | String line_text = vim->_get_current_line(); 278 | if (vim->get_input_state().repeat_count - 1 < line_text.length()) { 279 | vim->_cursor_set_column(vim->get_input_state().repeat_count - 1); 280 | } 281 | 282 | return Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 283 | } 284 | 285 | Motion::Pos Motion::search_word_under_cursor() { 286 | String word = vim->get_text_edit()->get_word_under_cursor(); 287 | int r_l = vim->_cursor_get_line(); 288 | int r_c = vim->_cursor_get_column(); 289 | 290 | if (vim->get_text_edit()->search(word, 0, vim->_cursor_get_line(), vim->_cursor_get_column(), r_l, r_c)) { 291 | print_line("word found"); 292 | return Pos(r_l, r_c); 293 | } 294 | return Pos(r_l, r_c); 295 | } 296 | 297 | Motion::Pos Motion::search_word_under_cursor_backward() { 298 | 299 | } 300 | 301 | Motion::Pos Motion::find_next() { 302 | } 303 | 304 | Motion::Pos Motion::find_previous() { 305 | } 306 | 307 | Motion::Pos Motion::find_char() { 308 | int n = find_in_line(vim->_get_current_line(), vim->get_input_state().input_char, vim->_cursor_get_column() + 1); 309 | 310 | if (n >= 0) { 311 | return Pos(vim->_cursor_get_line(), n); 312 | } 313 | 314 | return Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 315 | } 316 | 317 | Motion::Pos Motion::find_char_backward() { 318 | int n = find_in_line(vim->_get_current_line().substr(0, vim->_cursor_get_column()), vim->get_input_state().input_char); 319 | 320 | if (n >= 0) { 321 | return Pos(vim->_cursor_get_line(), n); 322 | } 323 | 324 | return Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 325 | } 326 | 327 | bool Motion::is_inclusive() { 328 | return flags & INCLUSIVE; 329 | } 330 | 331 | bool Motion::is_linewise() { 332 | return flags & LINEWISE; 333 | } 334 | 335 | bool Motion::is_text_object() { 336 | return flags & TEXT_OBJECT; 337 | } 338 | 339 | /** 340 | * Applies a motion taking repeat count into consideration. 341 | * 342 | * @brief Motion::apply 343 | * @return 344 | */ 345 | Motion::Pos Motion::apply() { 346 | Pos pos = Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 347 | int repeat = vim->get_input_state().repeat_count; 348 | if (repeat < 1) repeat = 1; 349 | for (int i = 0; i < repeat; i++) { 350 | vim->_cursor_set_line(pos.row); 351 | vim->_cursor_set_column(pos.col); 352 | pos = (this->*fcn)(); 353 | } 354 | return pos; 355 | } 356 | 357 | Motion::Range Motion::get_range() { 358 | Pos cur_pos = Pos(vim->_cursor_get_line(), vim->_cursor_get_column()); 359 | Pos pos = apply(); 360 | Range range = pos < cur_pos ? Range(pos, cur_pos) : Range(cur_pos, pos); 361 | range.inclusive = is_inclusive(); 362 | range.linewise = is_linewise(); 363 | range.text_object = is_text_object(); 364 | return range; 365 | } 366 | 367 | void Motion::run() { 368 | Pos pos = apply(); 369 | vim->_cursor_set_line(pos.row); 370 | vim->_cursor_set_column(pos.col); 371 | } 372 | 373 | Motion::Motion(GodotVim *vim, int flags, motionFcn fcn, int cmd_flags) : Command(vim, cmd_flags), flags(flags), fcn(fcn) { 374 | } 375 | 376 | Motion * Motion::create_motion(GodotVim *vim, int flags, Motion::motionFcn fcn, int cmd_flags) { 377 | Motion *motion = memnew(Motion(vim, flags, fcn, cmd_flags)); 378 | return motion; 379 | } 380 | -------------------------------------------------------------------------------- /godot_vim/motion.h: -------------------------------------------------------------------------------- 1 | #ifndef MOTION_H 2 | #define MOTION_H 3 | 4 | #include "command.h" 5 | 6 | class GodotVim; 7 | 8 | class Motion : public Command { 9 | public: 10 | 11 | enum { 12 | INCLUSIVE = 1, 13 | LINEWISE = 1 << 1, 14 | TEXT_OBJECT = 1 << 2, 15 | }; 16 | 17 | struct Pos { 18 | int row; 19 | int col; 20 | 21 | operator String() const { 22 | return "[" + itos(row) + ", " + itos(col) + "]"; 23 | } 24 | 25 | bool operator < (const Pos &p_other) const { 26 | return (p_other.row == row) ? (col < p_other.col) : (row < p_other.row); 27 | } 28 | 29 | bool operator > (const Pos &p_other) const { 30 | return (p_other.row == row) ? (col > p_other.col) : (row > p_other.row); 31 | } 32 | 33 | bool operator == (const Pos &p_other) const { 34 | return (row == p_other.row) && (col == p_other.col); 35 | } 36 | 37 | Pos() {} 38 | Pos(int row, int col) : row(row), col(col) { } 39 | }; 40 | 41 | struct Range { 42 | Pos from; 43 | Pos to; 44 | bool linewise; 45 | bool inclusive; 46 | bool text_object; 47 | 48 | Range() {} 49 | 50 | Range(int from_line, int from_col, int to_line, int to_col) : 51 | from(from_line, from_col), 52 | to(to_line, to_col) {} 53 | 54 | Range(Pos from, Pos to) : from(from), to(to) {} 55 | }; 56 | 57 | typedef Pos (Motion::* motionFcn) (); 58 | typedef bool (* matchFunction) (int, String); 59 | 60 | private: 61 | 62 | int flags; 63 | motionFcn fcn; 64 | 65 | Pos _move_backward(matchFunction function, bool accept_empty=false); 66 | Pos _move_forward(matchFunction function, bool accept_empty=false); 67 | Pos _move_to_first_non_blank(int line); 68 | Pos _move_by_columns(int cols); 69 | Pos _move_by_lines(int lines); 70 | 71 | Motion(GodotVim *vim, int flags, motionFcn fcn, int cmd_flags); 72 | 73 | public: 74 | 75 | Pos move_left(); 76 | Pos move_right(); 77 | Pos move_down(); 78 | Pos move_up(); 79 | Pos move_to_first_non_blank(); 80 | Pos move_to_line_end(); 81 | Pos move_to_line_start(); 82 | Pos move_word_right(); 83 | Pos move_word_right_big(); 84 | Pos move_word_beginning(); 85 | Pos move_word_beginning_big(); 86 | Pos move_word_end(); 87 | Pos move_word_end_big(); 88 | Pos move_word_end_backward(); 89 | Pos move_word_end_big_backward(); 90 | Pos move_paragraph_up(); 91 | Pos move_paragraph_down(); 92 | Pos move_to_matching_pair(); 93 | Pos move_to_beginning_of_last_line(); 94 | Pos move_to_beginning_of_first_line(); 95 | Pos move_to_beginning_of_previous_line(); 96 | Pos move_to_beginning_of_next_line(); 97 | Pos move_to_last_searched_char(); 98 | Pos move_to_last_searched_char_backward(); 99 | Pos move_to_column(); 100 | 101 | Pos search_word_under_cursor(); 102 | Pos search_word_under_cursor_backward(); 103 | 104 | Pos find_next(); 105 | Pos find_previous(); 106 | 107 | Pos find_char(); 108 | Pos find_char_backward(); 109 | 110 | bool is_inclusive(); 111 | bool is_text_object(); 112 | bool is_linewise(); 113 | 114 | Pos apply(); 115 | 116 | Range get_range(); 117 | 118 | void run(); 119 | 120 | Motion *get_motion() { return this; } 121 | 122 | bool is_motion() { return true; } 123 | 124 | static Motion *create_motion(GodotVim *vim, int flags, motionFcn fcn, int cmd_flags = 0); 125 | }; 126 | 127 | #endif // MOTION_H 128 | -------------------------------------------------------------------------------- /godot_vim/operation.cpp: -------------------------------------------------------------------------------- 1 | #include "operation.h" 2 | #include "godot_vim.h" 3 | 4 | void Operation::_change_case(CaseOperation option) { 5 | Motion::Range sel = vim->get_selection(); 6 | 7 | vim->get_text_edit()->begin_complex_operation(); 8 | for (int i = sel.from.row; i <= sel.to.row; i++) { 9 | String line_text = vim->_get_line(i); 10 | for (int j = (i == sel.from.row ? sel.from.col : 0); j < (i == sel.from.row ? sel.to.col : line_text.length()); j++) { 11 | switch (option) { 12 | 13 | case TO_LOWER: 14 | line_text[j] = String::char_lowercase(line_text[j]); 15 | break; 16 | 17 | case TO_UPPER: 18 | line_text[j] = String::char_uppercase(line_text[j]); 19 | break; 20 | 21 | default: 22 | if (line_text.substr(j, 1).match("[a-z]")) 23 | line_text[j] = String::char_uppercase(line_text[j]); 24 | else 25 | line_text[j] = String::char_lowercase(line_text[j]); 26 | break; 27 | } 28 | } 29 | vim->set_line(i, line_text); 30 | } 31 | vim->get_text_edit()->end_complex_operation(); 32 | } 33 | 34 | void Operation::delete_text() { 35 | vim->get_text_edit()->cut(); 36 | } 37 | 38 | void Operation::delete_lines() { 39 | 40 | } 41 | 42 | void Operation::yank() { 43 | Motion::Range range = vim->get_selection(); 44 | vim->yank_range(range); 45 | } 46 | 47 | void Operation::change_text() { 48 | vim->get_text_edit()->cut(); 49 | vim->_set_vim_mode(GodotVim::INSERT); 50 | } 51 | 52 | void Operation::change_lines() { 53 | 54 | } 55 | 56 | void Operation::toggle_case() { 57 | _change_case(INVERT); 58 | } 59 | 60 | void Operation::to_lower() { 61 | _change_case(TO_LOWER); 62 | } 63 | 64 | void Operation::to_upper() { 65 | _change_case(TO_UPPER); 66 | } 67 | 68 | void Operation::indent_text() { 69 | int line = vim->_cursor_get_line(); 70 | String text_line = vim->_get_line(line); 71 | text_line = '\t' + text_line; 72 | vim->set_line(line, text_line); 73 | } 74 | 75 | void Operation::unindent_text() { 76 | int line = vim->_cursor_get_line(); 77 | String text_line = vim->_get_line(line); 78 | if (text_line.begins_with("\t")) { 79 | text_line.erase(0, 1); 80 | vim->set_line(line, text_line); 81 | } 82 | } 83 | 84 | void Operation::reindent_text() { 85 | 86 | } 87 | 88 | void Operation::apply_with_motion(Motion *motion) { 89 | Motion::Range range = motion->get_range(); 90 | if (!range.linewise && range.inclusive) { 91 | range.to.col++; 92 | } 93 | vim->select_range(range); 94 | run(); 95 | vim->get_text_edit()->deselect(); 96 | } 97 | 98 | void Operation::run() { 99 | (this->*fcn)(); 100 | } 101 | 102 | Operation::Operation(GodotVim *vim, int flags, Operation::operationFunction fcn) : Command(vim), flags(flags), fcn(fcn) { 103 | } 104 | 105 | Operation * Operation::create_operation(GodotVim *vim, int flags, Operation::operationFunction fcn) { 106 | return memnew(Operation(vim, flags, fcn)); 107 | } 108 | -------------------------------------------------------------------------------- /godot_vim/operation.h: -------------------------------------------------------------------------------- 1 | #ifndef OPERATION_H 2 | #define OPERATION_H 3 | 4 | #include "command.h" 5 | 6 | class Operation : public Command { 7 | public: 8 | typedef void (Operation::* operationFunction) (); 9 | 10 | private: 11 | enum CaseOperation { 12 | TO_LOWER, 13 | TO_UPPER, 14 | INVERT 15 | }; 16 | 17 | int flags; 18 | operationFunction fcn; 19 | 20 | Operation(GodotVim *vim, int flags, operationFunction fcn); 21 | 22 | void _change_case(CaseOperation option); 23 | 24 | public: 25 | 26 | void delete_text(); 27 | void delete_lines(); 28 | 29 | void yank(); 30 | 31 | void to_upper(); 32 | void to_lower(); 33 | void toggle_case(); 34 | 35 | void change_text(); 36 | void change_lines(); 37 | 38 | void indent_text(); 39 | void unindent_text(); 40 | void reindent_text(); 41 | 42 | void apply_with_motion(Motion *motion); 43 | 44 | void run(); 45 | 46 | bool is_operation() { return true; } 47 | 48 | Operation *get_operation() { return this; } 49 | 50 | static Operation *create_operation(GodotVim *vim, int flags, operationFunction fcn); 51 | }; 52 | 53 | #endif // OPERATION_H 54 | -------------------------------------------------------------------------------- /godot_vim/register_types.cpp: -------------------------------------------------------------------------------- 1 | #include "register_types.h" 2 | #include "object_type_db.h" 3 | #include "godot_vim.h" 4 | 5 | void register_godot_vim_types() { 6 | EditorPlugins::add_by_type(); 7 | // ObjectTypeDB::register_type(); 8 | } 9 | 10 | void unregister_godot_vim_types() { 11 | //nothing to do here 12 | } 13 | -------------------------------------------------------------------------------- /godot_vim/register_types.h: -------------------------------------------------------------------------------- 1 | void register_godot_vim_types(); 2 | void unregister_godot_vim_types(); 3 | 4 | -------------------------------------------------------------------------------- /godot_vim/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | #include "ustring.h" 3 | 4 | bool _is_space(CharType c) { 5 | return (c == ' ' || c == '\t'); 6 | } 7 | 8 | bool _is_text_char(CharType c) { 9 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; 10 | } 11 | 12 | bool _is_symbol(CharType c) { 13 | return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~')); 14 | } 15 | 16 | bool _is_char(CharType c) { 17 | 18 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; 19 | } 20 | 21 | bool _is_number(CharType c) { 22 | return (c >= '0' && c <= '9'); 23 | } 24 | 25 | bool _is_hex_symbol(CharType c) { 26 | return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); 27 | } 28 | 29 | bool _is_pair_right_symbol(CharType c) { 30 | return c == '"' || 31 | c == '\'' || 32 | c == ')' || 33 | c == ']' || 34 | c == '}'; 35 | } 36 | 37 | bool _is_pair_left_symbol(CharType c) { 38 | return c == '"' || 39 | c == '\'' || 40 | c == '(' || 41 | c == '[' || 42 | c == '{'; 43 | } 44 | 45 | bool _is_pair_symbol(CharType c) { 46 | return _is_pair_left_symbol(c) || _is_pair_right_symbol(c); 47 | } 48 | 49 | CharType _get_right_pair_symbol(CharType c) { 50 | if (c == '"') 51 | return '"'; 52 | if (c == '\'') 53 | return '\''; 54 | if (c == '(') 55 | return ')'; 56 | if (c == '[') 57 | return ']'; 58 | if (c == '{') 59 | return '}'; 60 | return 0; 61 | } 62 | 63 | CharType _get_left_pair_symbol(CharType c) { 64 | if (c == '"') 65 | return '"'; 66 | if (c == '\'') 67 | return '\''; 68 | if (c == ')') 69 | return '('; 70 | if (c == ']') 71 | return '['; 72 | if (c == '}') 73 | return '{'; 74 | return 0; 75 | } 76 | 77 | bool _is_same_word(CharType a, CharType b) { 78 | return ((_is_text_char(a) && _is_text_char(b)) 79 | || (_is_symbol(a) && b == a)); 80 | } 81 | 82 | bool _is_end_of_word(int col, String line) { 83 | if (line.length() == 0) return false; 84 | 85 | CharType char1 = line[col]; 86 | 87 | if ((!_is_space(char1)) && col >= line.length() - 1) return true; 88 | 89 | CharType char2 = line[col + 1]; 90 | 91 | return ((_is_text_char(char1) && !_is_text_char(char2)) || (_is_symbol(char1) && char1 != char2)); 92 | } 93 | 94 | bool _is_end_of_big_word(int col, String line) { 95 | if (line.length() == 0) return false; 96 | 97 | CharType char1 = line[col]; 98 | 99 | if ((!_is_space(char1)) && col >= line.length() - 1) return true; 100 | 101 | CharType char2 = line[col + 1]; 102 | 103 | return ((_is_text_char(char1) || _is_symbol(char1)) && _is_space(char2)); 104 | } 105 | 106 | 107 | bool _is_beginning_of_word(int col, String line) { 108 | if (line.length() == 0) return true; 109 | if (_is_space(line[col])) return false; 110 | if (col == 0) return true; 111 | 112 | CharType char1 = line[col - 1]; 113 | CharType char2 = line[col]; 114 | 115 | return (!_is_text_char(char1) && _is_text_char(char2)) || (_is_symbol(char2) && char2 != char1); 116 | } 117 | 118 | bool _is_beginning_of_big_word(int col, String line) { 119 | if (line.length() == 0) return false; 120 | if (_is_space(line[col])) return false; 121 | if (col == 0) return true; 122 | 123 | CharType char1 = line[col - 1]; 124 | CharType char2 = line[col]; 125 | 126 | return _is_space(char1) && (_is_text_char(char2) || _is_symbol(char2)); 127 | } 128 | 129 | int _find_first_non_blank(String line) { 130 | 131 | for (int i = 0; i < line.length(); i++) { 132 | CharType c = line[i]; 133 | if (!_is_space(c)) { 134 | return i; 135 | } 136 | } 137 | 138 | return 0; 139 | } 140 | 141 | String _join_lines(Vector lines) { 142 | String ret; 143 | 144 | for (int i = 0; i < lines.size(); i++) { 145 | if (i > 0) ret += "\n"; 146 | ret += lines[i]; 147 | } 148 | 149 | return ret; 150 | } 151 | 152 | int find_in_line(String line, String c, int from) { 153 | return line.find(c, from); 154 | } 155 | -------------------------------------------------------------------------------- /godot_vim/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include "ustring.h" 5 | 6 | bool _is_space(CharType c); 7 | 8 | bool _is_text_char(CharType c); 9 | 10 | bool _is_symbol(CharType c); 11 | 12 | bool _is_char(CharType c); 13 | 14 | bool _is_number(CharType c); 15 | 16 | bool _is_hex_symbol(CharType c); 17 | 18 | bool _is_pair_right_symbol(CharType c); 19 | 20 | bool _is_pair_left_symbol(CharType c); 21 | 22 | bool _is_pair_symbol(CharType c); 23 | 24 | CharType _get_right_pair_symbol(CharType c); 25 | 26 | CharType _get_left_pair_symbol(CharType c); 27 | 28 | bool _is_same_word(CharType a, CharType b); 29 | 30 | bool _is_end_of_word(int col, String line); 31 | 32 | bool _is_end_of_big_word(int col, String line); 33 | 34 | bool _is_beginning_of_word(int col, String line); 35 | 36 | bool _is_beginning_of_big_word(int col, String line); 37 | 38 | int _find_first_non_blank(String line); 39 | 40 | int find_in_line(String line, String c, int from = 0); 41 | 42 | String _join_lines(Vector lines); 43 | 44 | #endif // UTIL_H 45 | --------------------------------------------------------------------------------