├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ └── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── zsh-vi-mode.plugin.zsh └── zsh-vi-mode.zsh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jeffreytse # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: jeffreytse 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: jeffreytse 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: jeffreytse 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug 3 | about: File a bug/issue 4 | title: "[BUG] " 5 | labels: Bug, Needs Triage 6 | assignees: "" 7 | --- 8 | 9 | <!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED --> 10 | 11 | <!-- 12 | TIP: Please search to see if an issue already exists for the bug you encountered. 13 | --> 14 | 15 | <!-- TIP: Hit 'Preview' for a more readable version of this template --> 16 | 17 | ## General information 18 | 19 | <!-- TIP: You can open a new terminal program, and then run the following 20 | command `zvm_system_report | pbcopy` to copy straight to clipboard, then 21 | paste here --> 22 | 23 | <!-- 24 | Please report the following information as possible as you can: 25 | 26 | - Terminal program: (e.g. iTerm 3.0.0, Xfce 4.16, xterm 327, other?) 27 | - Operating system: (e.g. Manjaro Linux 5.4.105-1, macOS Mojave 10.13.1, other?) 28 | - ZSH framework: (e.g. oh-my-zsh, prezto, antigen, antibody, zplug, other?) 29 | - ZSH version: 5.x.x 30 | - ZVM version: 0.x.x 31 | --> 32 | 33 | ## Basic examination 34 | 35 | <!-- Check all that apply [x] --> 36 | 37 | - [ ] I have read through the [README](https://github.com/jeffreytse/zsh-vi-mode) page 38 | - [ ] I have the latest version of zsh-vi-mode 39 | - [ ] I have tested with another terminal program <!-- e.g. Xfce4, iTerm, etc. --> 40 | 41 | ## Problem description 42 | 43 | <!-- This is a summary description on the problem --> 44 | 45 | ## Reproduction steps 46 | 47 | 1. 48 | 2. 49 | 3. 50 | 51 | ## Expected behavior 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Jeffrey Tse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <div align="center"> 2 | <a href="https://github.com/jeffreytse/zsh-vi-mode"> 3 | <img alt="vi-mode →~ zsh" src="https://user-images.githubusercontent.com/9413601/103399068-46bfcb80-4b7a-11eb-8741-86cff3d85a69.png" width="600"> 4 | </a> 5 | <p> 💻 A better and friendly vi(vim) mode plugin for ZSH. </p> 6 | 7 | <br> <h1>⚒️ Zsh Vi Mode ⚒️</h1> 8 | 9 | </div> 10 | 11 | 12 | 13 | <h4 align="center"> 14 | <a href="https://www.zsh.org/" target="_blank"><code>ZSH</code></a> plugin for Agnosticism. 15 | </h4> 16 | 17 | <p align="center"> 18 | 19 | <a href="https://github.com/sponsors/jeffreytse"> 20 | <img src="https://img.shields.io/static/v1?label=sponsor&message=%E2%9D%A4&logo=GitHub&link=&color=greygreen" 21 | alt="Donate (GitHub Sponsor)" /> 22 | </a> 23 | 24 | <a href="https://github.com/jeffreytse/zsh-vi-mode/releases"> 25 | <img src="https://img.shields.io/github/v/release/jeffreytse/zsh-vi-mode?color=brightgreen" 26 | alt="Release Version" /> 27 | </a> 28 | 29 | <a href="https://opensource.org/licenses/MIT"> 30 | <img src="https://img.shields.io/badge/License-MIT-brightgreen.svg" 31 | alt="License: MIT" /> 32 | </a> 33 | 34 | <a href="https://liberapay.com/jeffreytse"> 35 | <img src="http://img.shields.io/liberapay/goal/jeffreytse.svg?logo=liberapay" 36 | alt="Donate (Liberapay)" /> 37 | </a> 38 | 39 | <a href="https://patreon.com/jeffreytse"> 40 | <img src="https://img.shields.io/badge/support-patreon-F96854.svg?style=flat-square" 41 | alt="Donate (Patreon)" /> 42 | </a> 43 | 44 | <a href="https://ko-fi.com/jeffreytse"> 45 | <img height="20" src="https://www.ko-fi.com/img/githubbutton_sm.svg" 46 | alt="Donate (Ko-fi)" /> 47 | </a> 48 | 49 | </p> 50 | 51 | <div align="center"> 52 | <h4> 53 | <a href="#-features">Features</a> | 54 | <a href="#%EF%B8%8F-installation">Install</a> | 55 | <a href="#-usage">Usage</a> | 56 | <a href="#-credits">Credits</a> | 57 | <a href="#-license">License</a> 58 | </h4> 59 | </div> 60 | 61 | <div align="center"> 62 | <sub>Built with ❤︎ by 63 | <a href="https://jeffreytse.net">jeffreytse</a> and 64 | <a href="https://github.com/jeffreytse/zsh-vi-mode/graphs/contributors">contributors </a> 65 | </div> 66 | <br> 67 | 68 | <img alt="Zsh Vi-mode Demo" src="https://user-images.githubusercontent.com/9413601/105746868-f3734a00-5f7a-11eb-8db5-22fcf50a171b.gif" /> 69 | 70 | ## 🤔 Why ZVM? 71 | 72 | Maybe you have experienced the default Vi mode in Zsh, after turning on 73 | the default Vi mode, you gradually found that it had many problems, some 74 | features were not perfect or non-existent, and some behaviors even were 75 | different from the native Vi(Vim) mode. 76 | 77 | Although the default Vi mode was a bit embarrassing and unpleasant, you 78 | kept on using it and gradually lost your interest on it after using for 79 | a period of time. Eventually, you disappointedly gave up. 80 | 81 | You never think of the Vi mode for a long time, one day you accidentally 82 | discovered this plugin, you read here and realize that this plugin is to 83 | solve the above problems and make you fall in love to Vi mode again. A 84 | smile suddenly appeared on your face like regaining a good life. 85 | 86 | > If winter comes, can spring be far behind? 87 | 88 | 89 | ## ✨ Features 90 | 91 | - 🌟 Pure Zsh's script without any third-party dependencies. 92 | - 🎉 Better experience with the near-native vi(vim) mode. 93 | - ⌛ Lower delay and better response (Mode switching speed, etc.). 94 | - ✏️ Mode indication with different cursor styles. 95 | - 🧮 Cursor movement (Navigation). 96 | - 📝 Insert & Replace (Insert mode). 97 | - 💡 Text Objects (A word, inner word, etc.). 98 | - 🔎 Searching history. 99 | - ❇️ Undo, Redo, Cut, Copy, Paste, and Delete. 100 | - 🪐 Better surrounds functionality (Add, Replace, Delete, Move Around, and Highlight). 101 | - 🧽 Switch keywords (Increase/Decrease Number, Boolean, Weekday, Month, etc.). 102 | - ⚙️ Better functionality in command mode (**In progress**). 103 | - 🪀 Repeating command such as `10p` and `4fa` (**In progress**). 104 | - 📒 System clipboard (**In progress**). 105 | 106 | ## 💼 Requirements 107 | 108 | ZSH: >= 5.1.0 109 | 110 | ## 🛠️ Installation 111 | 112 | #### Using [Antigen](https://github.com/zsh-users/antigen) 113 | 114 | Bundle `zsh-vi-mode` in your `.zshrc` 115 | 116 | ```shell 117 | antigen bundle jeffreytse/zsh-vi-mode 118 | ``` 119 | 120 | #### Using [zplug](https://github.com/b4b4r07/zplug) 121 | Load `zsh-vi-mode` as a plugin in your `.zshrc` 122 | 123 | ```shell 124 | zplug "jeffreytse/zsh-vi-mode" 125 | ``` 126 | 127 | #### Using [zgen](https://github.com/tarjoilija/zgen) 128 | 129 | Include the load command in your `.zshrc` 130 | 131 | ```shell 132 | zgen load jeffreytse/zsh-vi-mode 133 | ``` 134 | 135 | #### Using [zinit](https://github.com/zdharma-continuum/zinit) 136 | 137 | Include the load command in your `.zshrc` 138 | 139 | ```shell 140 | zinit ice depth=1 141 | zinit light jeffreytse/zsh-vi-mode 142 | ``` 143 | 144 | Note: the use of `depth=1` ice is optional, other types of ice are neither 145 | recommended nor officially supported by this plugin. 146 | 147 | #### As an [Oh My Zsh!](https://github.com/robbyrussell/oh-my-zsh) custom plugin 148 | 149 | Clone `zsh-vi-mode` into your custom plugins repo 150 | 151 | ```shell 152 | git clone https://github.com/jeffreytse/zsh-vi-mode \ 153 | $ZSH_CUSTOM/plugins/zsh-vi-mode 154 | ``` 155 | Then load as a plugin in your `.zshrc` 156 | 157 | ```shell 158 | plugins+=(zsh-vi-mode) 159 | ``` 160 | 161 | Keep in mind that plugins need to be added before `oh-my-zsh.sh` is sourced. 162 | 163 | #### Using [Antibody](https://getantibody.github.io/) 164 | 165 | Add `zsh-vi-mode` to your plugins file (e.g. `~/.zsh_plugins.txt`) 166 | 167 | ```shell 168 | jeffreytse/zsh-vi-mode 169 | ``` 170 | 171 | #### Using [Zap](https://github.com/zap-zsh/zap) 172 | 173 | Load `zsh-vi-mode` as a plugin in your `.zshrc` 174 | 175 | ```shell 176 | plug "jeffreytse/zsh-vi-mode" 177 | ``` 178 | 179 | #### Using [Zim](https://github.com/zimfw/zimfw) 180 | 181 | Load `zsh-vi-mode` as a plugin in your `.zimrc` 182 | 183 | ```shell 184 | zmodule jeffreytse/zsh-vi-mode 185 | ``` 186 | 187 | #### Using [Homebrew](https://brew.sh/) 188 | 189 | For Homebrew users, you can install it through the following command 190 | 191 | ```shell 192 | brew install zsh-vi-mode 193 | ``` 194 | 195 | Then source it in your `.zshrc` (or `.bashrc`) 196 | 197 | ```shell 198 | source $(brew --prefix)/opt/zsh-vi-mode/share/zsh-vi-mode/zsh-vi-mode.plugin.zsh 199 | ``` 200 | 201 | #### Arch Linux (AUR) 202 | 203 | For Arch Linux users, you can install it through the following command 204 | 205 | ```shell 206 | yay -S zsh-vi-mode 207 | ``` 208 | 209 | or the latest update (unstable) 210 | 211 | ```shell 212 | yay -S zsh-vi-mode-git 213 | ``` 214 | 215 | Then source it in your `.zshrc` (or `.bashrc`) 216 | 217 | ```shell 218 | source /usr/share/zsh/plugins/zsh-vi-mode/zsh-vi-mode.plugin.zsh 219 | ``` 220 | 221 | #### Nix 222 | 223 | For users of Nix, as of [e7e3480530b34a9fe8cb52963ec2cf66e6707e15](https://github.com/NixOS/nixpkgs/commit/e7e3480530b34a9fe8cb52963ec2cf66e6707e15) you can source the plugin through the following configuration 224 | 225 | ```nix 226 | programs = { 227 | zsh = { 228 | interactiveShellInit = '' 229 | source ${pkgs.zsh-vi-mode}/share/zsh-vi-mode/zsh-vi-mode.plugin.zsh 230 | ''; 231 | }; 232 | }; 233 | ``` 234 | 235 | Or if you prefer `home-manager`: 236 | 237 | ```nix 238 | home-manager.users.[your username] = { pkgs, ... }: { 239 | programs = { 240 | zsh = { 241 | initExtra = '' 242 | source ${pkgs.zsh-vi-mode}/share/zsh-vi-mode/zsh-vi-mode.plugin.zsh 243 | ''; 244 | }; 245 | }; 246 | }; 247 | ``` 248 | 249 | You can also use `home-manager`'s built-in "plugin" feature: 250 | 251 | ```nix 252 | home-manager.users.[your username] = { pkgs, ... }: { 253 | programs = { 254 | zsh = { 255 | plugins = [ 256 | { 257 | name = "vi-mode"; 258 | src = pkgs.zsh-vi-mode; 259 | file = "share/zsh-vi-mode/zsh-vi-mode.plugin.zsh"; 260 | } 261 | ]; 262 | }; 263 | }; 264 | }; 265 | ``` 266 | 267 | #### Using [Fig](https://fig.io) 268 | 269 | Fig adds apps, shortcuts, and autocomplete to your existing terminal. 270 | 271 | Install `zsh-vi-mode` in just one click. 272 | 273 | <a href="https://fig.io/plugins/other/zsh-vi-mode" target="_blank"><img src="https://fig.io/badges/install-with-fig.svg" /></a> 274 | 275 | #### Gentoo Linux 276 | 277 | Available in [dm9pZCAq overlay](https://github.com/gentoo-mirror/dm9pZCAq) 278 | 279 | ```shell 280 | eselect repository enable dm9pZCAq 281 | emerge --sync dm9pZCAq 282 | emerge app-shells/zsh-vi-mode 283 | ``` 284 | 285 | Then source it in your `.zshrc` (or `.bashrc`) 286 | 287 | ```shell 288 | source /usr/share/zsh/site-contrib/zsh-vi-mode/zsh-vi-mode.plugin.zsh 289 | ``` 290 | 291 | #### Manually 292 | 293 | Clone this repository somewhere (`$HOME/.zsh-vi-mode` for example) 294 | 295 | ```shell 296 | git clone https://github.com/jeffreytse/zsh-vi-mode.git $HOME/.zsh-vi-mode 297 | ``` 298 | Then source it in your `.zshrc` (or `.bashrc`) 299 | 300 | ```shell 301 | source $HOME/.zsh-vi-mode/zsh-vi-mode.plugin.zsh 302 | ``` 303 | 304 | ## Packaging Status 305 | 306 | [![Packaging status](https://repology.org/badge/vertical-allrepos/zsh-vi-mode.svg)](https://repology.org/project/zsh-vi-mode/versions) 307 | 308 | ## 📚 Usage 309 | 310 | Use `ESC` or `CTRL-[` to enter `Normal mode`. 311 | 312 | But some people may like the custom escape key such as `jj`, `jk` and so on, 313 | if you want to custom the escape key, you can learn more from [here](#custom-escape-key). 314 | 315 | History 316 | ------- 317 | 318 | - `ctrl-p` : Previous command in history 319 | - `ctrl-n` : Next command in history 320 | - `/` : Search backward in history 321 | - `n` : Repeat the last `/` 322 | 323 | 324 | Mode indicators 325 | --------------- 326 | 327 | `Normal mode` is indicated with block style cursor, and `Insert mode` with 328 | beam style cursor by default. 329 | 330 | Vim edition 331 | ----------- 332 | 333 | In `Normal mode` you can use `vv` to edit current command line in an editor 334 | (e.g. `vi`/`vim`/`nvim`...), because it is bound to the `Visual mode`. 335 | 336 | You can change the editor by `ZVM_VI_EDITOR` option, by default it is 337 | `$EDITOR`. 338 | 339 | Movement 340 | -------- 341 | 342 | - `$` : To the end of the line 343 | - `^` : To the first non-blank character of the line 344 | - `0` : To the first character of the line 345 | - `w` : [count] words forward 346 | - `W` : [count] WORDS forward 347 | - `e` : Forward to the end of word [count] inclusive 348 | - `E` : Forward to the end of WORD [count] inclusive 349 | - `b` : [count] words backward 350 | - `B` : [count] WORDS backward 351 | - `t{char}` : Till before [count]'th occurrence of {char} to the right 352 | - `T{char}` : Till before [count]'th occurrence of {char} to the left 353 | - `f{char}` : To [count]'th occurrence of {char} to the right 354 | - `F{char}` : To [count]'th occurrence of {char} to the left 355 | - `;` : Repeat latest f, t, F or T [count] times 356 | - `,` : Repeat latest f, t, F or T in opposite direction 357 | 358 | 359 | Insertion 360 | --------- 361 | 362 | - `i` : Insert text before the cursor 363 | - `I` : Insert text before the first character in the line 364 | - `a` : Append text after the cursor 365 | - `A` : Append text at the end of the line 366 | - `o` : Insert new command line below the current one 367 | - `O` : Insert new command line above the current one 368 | 369 | Surround 370 | -------- 371 | 372 | There are 2 kinds of keybinding mode for surround operating, default is 373 | `classic` mode, you can choose the mode by setting `ZVM_VI_SURROUND_BINDKEY` 374 | option. 375 | 376 | 1. `classic` mode (verb->s->surround) 377 | 378 | - `S"` : Add `"` for visual selection 379 | - `ys"` : Add `"` for visual selection 380 | - `cs"'` : Change `"` to `'` 381 | - `ds"` : Delete `"` 382 | 383 | 2. `s-prefix` mode (s->verb->surround) 384 | - `sa"` : Add `"` for visual selection 385 | - `sd"` : Delete `"` 386 | - `sr"'` : Change `"` to `'` 387 | 388 | Note that key sequences must be pressed in fairly quick succession to avoid a timeout. You may extend this timeout with the [`ZVM_KEYTIMEOUT` option](#readkey-engine). 389 | 390 | #### How to select surround text object? 391 | 392 | - `vi"` : Select the text object inside the quotes 393 | - `va(` : Select the text object including the brackets 394 | 395 | Then you can do any operation for the selection: 396 | 397 | 1. Add surrounds for text object 398 | 399 | - `vi"` -> `S[` or `sa[` => `"object"` -> `"[object]"` 400 | - `va"` -> `S[` or `sa[` => `"object"` -> `["object"]` 401 | 402 | 2. Delete/Yank/Change text object 403 | 404 | - `di(` or `vi(` -> `d` 405 | - `ca(` or `va(` -> `c` 406 | - `yi(` or `vi(` -> `y` 407 | 408 | Increment and Decrement 409 | -------- 410 | 411 | In normal mode, typing `ctrl-a` will increase to the next keyword, and typing 412 | `ctrl-x` will decrease to the next keyword. The keyword can be at the cursor, 413 | or to the right of the cursor (on the same line). The keyword could be as 414 | below: 415 | 416 | - Number (Decimal, Hexadecimal, Binary...) 417 | - Boolean (True or False, Yes or No, On or Off...) 418 | - Weekday (Sunday, Monday, Tuesday, Wednesday...) 419 | - Month (January, February, March, April, May...) 420 | - Operator (&&, ||, ++, --, ==, !==, and, or...) 421 | - ... 422 | 423 | For example: 424 | 425 | 1. Increment 426 | 427 | - `9` => `10` 428 | - `aa99bb` => `aa100bb` 429 | - `aa100bc` => `aa101bc` 430 | - `0xDe` => `0xdf` 431 | - `0Xdf` => `0Xe0` 432 | - `0b101` => `0b110` 433 | - `0B11` => `0B101` 434 | - `true` => `false` 435 | - `yes` => `no` 436 | - `on` => `off` 437 | - `T` => `F` 438 | - `Fri` => `Sat` 439 | - `Oct` => `Nov` 440 | - `Monday` => `Tuesday` 441 | - `January` => `February` 442 | - `+` => `-` 443 | - `++` => `--` 444 | - `==` => `!=` 445 | - `!==` => `===` 446 | - `&&` => `||` 447 | - `and` => `or` 448 | - ... 449 | 450 | 2. Decrement: 451 | 452 | - `100` => `99` 453 | - `aa100bb` => `aa99bb` 454 | - `0` => `-1` 455 | - `0xdE0` => `0xDDF` 456 | - `0xffFf0` => `0xfffef` 457 | - `0xfffF0` => `0xFFFEF` 458 | - `0x0` => `0xffffffffffffffff` 459 | - `0Xf` => `0Xe` 460 | - `0b100` => `0b010` 461 | - `0B100` => `0B011` 462 | - `True` => `False` 463 | - `On` => `Off` 464 | - `Sun` => `Sat` 465 | - `Jan` => `Dec` 466 | - `Monday` => `Sunday` 467 | - `August` => `July` 468 | - `/` => `*` 469 | - `++` => `--` 470 | - `==` => `!=` 471 | - `!==` => `===` 472 | - `||` => `&&` 473 | - `or` => `and` 474 | - ... 475 | 476 | Custom Escape Key 477 | -------- 478 | 479 | You can use below options to custom the escape key which could better match 480 | your flavor, such as `jj` or `jk` and so on. 481 | 482 | - `ZVM_VI_ESCAPE_BINDKEY`: The vi escape key in all modes (default is `^[` 483 | => `ESC`) 484 | - `ZVM_VI_INSERT_ESCAPE_BINDKEY`: The vi escape key in insert mode (default 485 | is `$ZVM_VI_ESCAPE_BINDKEY`) 486 | - `ZVM_VI_VISUAL_ESCAPE_BINDKEY`: The vi escape key in visual mode (default 487 | is `$ZVM_VI_ESCAPE_BINDKEY`) 488 | - `ZVM_VI_OPPEND_ESCAPE_BINDKEY`: The vi escape key in operator pending mode 489 | (default is `$ZVM_VI_ESCAPE_BINDKEY`) 490 | 491 | For example: 492 | 493 | ```zsh 494 | # Only changing the escape key to `jk` in insert mode, we still 495 | # keep using the default keybindings `^[` in other modes 496 | ZVM_VI_INSERT_ESCAPE_BINDKEY=jk 497 | ``` 498 | 499 | Readkey Engine 500 | -------- 501 | 502 | This plugin has supported to choose the readkey engine for reading and 503 | processing the key events. It easy to do by the `ZVM_READKEY_ENGINE`option, 504 | currently the below engines are supported: 505 | 506 | - `ZVM_READKEY_ENGINE_NEX`: It is a better readkey engine to replace ZLE (Beta). 507 | - `ZVM_READKEY_ENGINE_ZLE`: It is Zsh's default readkey engine (ZLE). 508 | - `ZVM_READKEY_ENGINE_DEFAULT`: It is the default engine of this plugin 509 | (It's the NEX engine now). 510 | 511 | The NEX is a better engine for reading and handling the key events than the 512 | Zsh's ZLE engine, currently the NEX engine is still at beta stage, you can 513 | change back to Zsh's ZLE engine if you want. 514 | 515 | For example: 516 | 517 | ```zsh 518 | # Change to Zsh's default readkey engine 519 | ZVM_READKEY_ENGINE=$ZVM_READKEY_ENGINE_ZLE 520 | ``` 521 | 522 | You can use `ZVM_KEYTIMEOUT` option to adjust the key input timeout for 523 | waiting for next key, default is `0.4` seconds. 524 | 525 | The escape key is a special case, it can be used standalone. NEX engine 526 | waits for a period after receiving the escape character, to determine 527 | whether it is standalone or part of an escape sequence. While waiting, 528 | additional key presses make the escape key behave as a meta key. If no 529 | other key presses come in, it is handled as a standalone escape. 530 | 531 | For the NEX engine, we can use `ZVM_ESCAPE_KEYTIMEOUT` option to adjust 532 | the waiting timeout for the escape key, default is `0.03` seconds. 533 | 534 | Configuration Function 535 | -------- 536 | 537 | Since there are some config options relied to some variables defined in 538 | the plugin, however, some not. We need to provide an unified config entry 539 | function. The name of entry function is stored in an option called 540 | `ZVM_CONFIG_FUNC` and default value is `zvm_config`, you can change to 541 | others for fitting your flavor. 542 | 543 | If this config function exists, it will be called automatically, you can 544 | do some configurations in this aspect before you source this plugin. For 545 | example: 546 | 547 | ```zsh 548 | function zvm_config() { 549 | ZVM_LINE_INIT_MODE=$ZVM_MODE_INSERT 550 | ZVM_VI_INSERT_ESCAPE_BINDKEY=jk 551 | } 552 | 553 | source ~/zsh-vi-mode.zsh 554 | ``` 555 | 556 | Execute Extra Commands 557 | -------- 558 | 559 | This plugin has provided a mechanism to execute extra commands, and now 560 | you have the below aspects for executing something: 561 | 562 | ```zsh 563 | zvm_before_init_commands=() 564 | zvm_after_init_commands=() 565 | zvm_before_select_vi_mode_commands=() 566 | zvm_after_select_vi_mode_commands=() 567 | zvm_before_lazy_keybindings_commands=() 568 | zvm_after_lazy_keybindings_commands=() 569 | ``` 570 | 571 | Since the default [initialization mode](#initialization-mode), this plugin 572 | will overwrite the previous key bindings, this causes the key bindings of 573 | other plugins (i.e. `fzf`, `zsh-autocomplete`, etc.) to fail. 574 | 575 | You can solve the compatibility issue as below: 576 | 577 | ```zsh 578 | # Append a command directly 579 | zvm_after_init_commands+=('[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh') 580 | ``` 581 | 582 | or 583 | 584 | ```zsh 585 | # Define an init function and append to zvm_after_init_commands 586 | function my_init() { 587 | [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh 588 | } 589 | zvm_after_init_commands+=(my_init) 590 | ``` 591 | 592 | or 593 | 594 | ```zsh 595 | # The plugin will auto execute this zvm_after_init function 596 | function zvm_after_init() { 597 | [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh 598 | } 599 | ``` 600 | 601 | or if you are using the `zinit`: 602 | 603 | ```zsh 604 | # For postponing loading `fzf` 605 | zinit ice lucid wait 606 | zinit snippet OMZP::fzf 607 | ``` 608 | 609 | By default, [the lazy keybindings feature](#lazy-keybindings) is enabled, all 610 | the keybindings of `normal` and `visual` mode should be executed by the 611 | `zvm_after_lazy_keybindings_commands`. For example: 612 | 613 | ```zsh 614 | # The plugin will auto execute this zvm_after_lazy_keybindings function 615 | function zvm_after_lazy_keybindings() { 616 | bindkey -M vicmd 's' your_normal_widget 617 | bindkey -M visual 'n' your_visual_widget 618 | } 619 | ``` 620 | 621 | Custom widgets and keybindings 622 | -------- 623 | 624 | This plugin has two functions for you to define custom widgets and keybindings. 625 | In case of unnecessary problems, it is better to use them, especially when you 626 | meet the key conflicts. 627 | 628 | To define a custom widget, you should: 629 | 630 | ```zsh 631 | # If [your_custom_widget] were ignored, it will be the same with <your_custom_widget> 632 | zvm_define_widget <your_custom_widget> [your_custom_function] 633 | ``` 634 | 635 | To define a keybinding, you should: 636 | 637 | ```zsh 638 | zvm_bindkey <keymap> <keys> <widget> 639 | ``` 640 | 641 | For example: 642 | 643 | ```zsh 644 | # Your custom widget 645 | function my_custom_widget() { 646 | echo 'Hello, ZSH!' 647 | } 648 | 649 | # The plugin will auto execute this zvm_after_lazy_keybindings function 650 | function zvm_after_lazy_keybindings() { 651 | # Here we define the custom widget 652 | zvm_define_widget my_custom_widget 653 | 654 | # In normal mode, press Ctrl-E to invoke this widget 655 | zvm_bindkey vicmd '^E' my_custom_widget 656 | } 657 | ``` 658 | 659 | Vi Mode Indicator 660 | -------- 661 | 662 | This plugin has provided a `ZVM_MODE` variable for you to retrieve 663 | current vi mode and better show the indicator. 664 | 665 | And currently the below modes are supported: 666 | 667 | ```zsh 668 | ZVM_MODE_NORMAL 669 | ZVM_MODE_INSERT 670 | ZVM_MODE_VISUAL 671 | ZVM_MODE_VISUAL_LINE 672 | ZVM_MODE_REPLACE 673 | ``` 674 | 675 | For updating the vi mode indicator, we should add our commands to 676 | `zvm_after_select_vi_mode_commands`. For example: 677 | 678 | ```zsh 679 | # The plugin will auto execute this zvm_after_select_vi_mode function 680 | function zvm_after_select_vi_mode() { 681 | case $ZVM_MODE in 682 | $ZVM_MODE_NORMAL) 683 | # Something you want to do... 684 | ;; 685 | $ZVM_MODE_INSERT) 686 | # Something you want to do... 687 | ;; 688 | $ZVM_MODE_VISUAL) 689 | # Something you want to do... 690 | ;; 691 | $ZVM_MODE_VISUAL_LINE) 692 | # Something you want to do... 693 | ;; 694 | $ZVM_MODE_REPLACE) 695 | # Something you want to do... 696 | ;; 697 | esac 698 | } 699 | ``` 700 | 701 | Custom Cursor Style 702 | -------- 703 | 704 | This plugin has provided some options for users to custom the cursor 705 | style for better terminal compatibility. 706 | 707 | - You can disable this feature by the `ZVM_CURSOR_STYLE_ENABLED` 708 | option (Default is `true`) 709 | 710 | ```zsh 711 | # Disable the cursor style feature 712 | ZVM_CURSOR_STYLE_ENABLED=false 713 | ``` 714 | 715 | - You can set your cursor style for different vi mode: 716 | 717 | ```zsh 718 | # The prompt cursor in normal mode 719 | ZVM_NORMAL_MODE_CURSOR 720 | 721 | # The prompt cursor in insert mode 722 | ZVM_INSERT_MODE_CURSOR 723 | 724 | # The prompt cursor in visual mode 725 | ZVM_VISUAL_MODE_CURSOR 726 | 727 | # The prompt cursor in visual line mode 728 | ZVM_VISUAL_LINE_MODE_CURSOR 729 | 730 | # The prompt cursor in operator pending mode 731 | ZVM_OPPEND_MODE_CURSOR 732 | ``` 733 | 734 | - And the below cursor styles are supported: 735 | 736 | ```zsh 737 | ZVM_CURSOR_USER_DEFAULT 738 | ZVM_CURSOR_BLOCK 739 | ZVM_CURSOR_UNDERLINE 740 | ZVM_CURSOR_BEAM 741 | ZVM_CURSOR_BLINKING_BLOCK 742 | ZVM_CURSOR_BLINKING_UNDERLINE 743 | ZVM_CURSOR_BLINKING_BEAM 744 | ``` 745 | 746 | - Custom your cursor style is easy as below: 747 | 748 | ```zsh 749 | ZVM_INSERT_MODE_CURSOR=$ZVM_CURSOR_BEAM 750 | ZVM_NORMAL_MODE_CURSOR=$ZVM_CURSOR_BLOCK 751 | ZVM_OPPEND_MODE_CURSOR=$ZVM_CURSOR_UNDERLINE 752 | ``` 753 | 754 | - Also, custom your colorful cursor style as below: 755 | 756 | ```zsh 757 | # The plugin will auto execute this zvm_config function 758 | zvm_config() { 759 | # Retrieve default cursor styles 760 | local ncur=$(zvm_cursor_style $ZVM_NORMAL_MODE_CURSOR) 761 | local icur=$(zvm_cursor_style $ZVM_INSERT_MODE_CURSOR) 762 | 763 | # Append your custom color for your cursor 764 | ZVM_INSERT_MODE_CURSOR=$icur'\e\e]12;red\a' 765 | ZVM_NORMAL_MODE_CURSOR=$ncur'\e\e]12;#008800\a' 766 | } 767 | ``` 768 | 769 | We can use `ZVM_TERM` option to set the term type for plugin to handle 770 | terminal escape sequences, default is `$TERM`. It could be `xterm-256color`, 771 | `alacritty-256color`, `st-256color`, etc. It's important for some 772 | terminal emulators to show cursor properly. 773 | 774 | Highlight Behavior 775 | -------- 776 | 777 | You can use `ZVM_VI_HIGHLIGHT_BACKGROUND`, `ZVM_VI_HIGHLIGHT_FOREGROUND` 778 | and `ZVM_VI_HIGHLIGHT_EXTRASTYLE` to change the highlight behaviors ( 779 | surrounds, visual-line, etc.), the color value could be _a color name_ or 780 | _a hex color value_. 781 | 782 | For example: 783 | 784 | ```zsh 785 | ZVM_VI_HIGHLIGHT_FOREGROUND=green # Color name 786 | ZVM_VI_HIGHLIGHT_FOREGROUND=#008800 # Hex value 787 | ZVM_VI_HIGHLIGHT_BACKGROUND=red # Color name 788 | ZVM_VI_HIGHLIGHT_BACKGROUND=#ff0000 # Hex value 789 | ZVM_VI_HIGHLIGHT_EXTRASTYLE=bold,underline # bold and underline 790 | ``` 791 | 792 | Command Line Initial Mode 793 | -------- 794 | 795 | You can set the command line initial mode by the `ZVM_LINE_INIT_MODE` 796 | option. 797 | 798 | Currently the below modes are supported: 799 | 800 | - `ZVM_MODE_LAST` : Starting with last mode (Default). 801 | - `ZVM_MODE_INSERT` : Starting with insert mode. 802 | - `ZVM_MODE_NORMAL` : Starting with normal mode. 803 | 804 | For example: 805 | 806 | ```zsh 807 | # Always starting with insert mode for each command line 808 | ZVM_LINE_INIT_MODE=$ZVM_MODE_INSERT 809 | ``` 810 | 811 | Lazy Keybindings 812 | -------- 813 | 814 | This plugin has supported the lazy keybindings feature, and it is enabled 815 | by default. To disable it, you can set the option `ZVM_LAZY_KEYBINDINGS` 816 | to `false` before this plugin is loaded. This feature will postpone all 817 | the keybindings of `normal` and `visual` mode to the first time you enter 818 | the normal mode. 819 | 820 | It can greatly improve the startup speed, especially you open the terminal 821 | and just want to execute a simple command. 822 | 823 | Initialization Mode 824 | -------- 825 | 826 | In order to prevent various problems related to keybindings caused by the 827 | plugin sourcing sequence, and also keep the same functionality for this 828 | plugin, the initialization of this plugin was postponed to the first 829 | command line starting. 830 | 831 | However, almost all plugins are initialized when the script is sourced. 832 | Therefore, this plugin provides an option `ZVM_INIT_MODE` to change the 833 | initialization mode. 834 | 835 | For example: 836 | 837 | ```zsh 838 | # Do the initialization when the script is sourced (i.e. Initialize instantly) 839 | ZVM_INIT_MODE=sourcing 840 | ``` 841 | 842 | ## 💎 Credits 843 | 844 | - [Zsh](https://www.zsh.org/) - A powerful shell that operates as both an interactive shell and as a scripting language interpreter. 845 | - [Oh-My-Zsh](https://github.com/ohmyzsh/ohmyzsh) - A delightful, open source, community-driven framework for managing your ZSH configuration. 846 | - [vim-surround](https://github.com/tpope/vim-surround) - A vim plugin that all about "surroundings": parentheses, brackets, quotes, XML tags, and more. 847 | - [vim-sandwich](https://github.com/machakann/vim-sandwich) - A set of operator and textobject plugins to add/delete/replace surroundings of a sandwiched textobject. 848 | 849 | ## 🔫 Contributing 850 | 851 | Issues and Pull Requests are greatly appreciated. If you've never contributed to an open source project before I'm more than happy to walk you through how to create a pull request. 852 | 853 | You can start by [opening an issue](https://github.com/jeffreytse/zsh-vi-mode/issues/new) describing the problem that you're looking to resolve and we'll go from there. 854 | 855 | ## 🌈 License 856 | 857 | This theme is licensed under the [MIT license](https://opensource.org/licenses/mit-license.php) © Jeffrey Tse. 858 | -------------------------------------------------------------------------------- /zsh-vi-mode.plugin.zsh: -------------------------------------------------------------------------------- 1 | # According to the standard: 2 | # https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html 3 | 0="${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}" 4 | 0="${${(M)0:#/*}:-$PWD/$0}" 5 | 6 | source ${0:h}/zsh-vi-mode.zsh 7 | -------------------------------------------------------------------------------- /zsh-vi-mode.zsh: -------------------------------------------------------------------------------- 1 | # zsh-vi-mode.zsh -- A better and friendly vi(vim) mode for Zsh 2 | # https://github.com/jeffreytse/zsh-vi-mode 3 | # 4 | # Copyright (c) 2020 Jeffrey Tse 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | # All Settings 25 | # Some of these variables should be set before sourcing this file. 26 | # 27 | # ZVM_CONFIG_FUNC 28 | # the config function (default is `zvm_config`), if this config function 29 | # exists, it will be called automatically, you can do some configurations 30 | # in this aspect before you source this plugin. 31 | # 32 | # For example: 33 | # 34 | # ```zsh 35 | # function zvm_config() { 36 | # ZVM_LINE_INIT_MODE=$ZVM_MODE_INSERT 37 | # ZVM_VI_INSERT_ESCAPE_BINDKEY=jk 38 | # } 39 | # 40 | # source ~/zsh-vi-mode.zsh 41 | # ``` 42 | # 43 | # ZVM_INIT_MODE 44 | # the plugin initial mode (default is doing the initialization when the first 45 | # new command line is starting. For doing the initialization instantly, you 46 | # can set it to `sourcing`. 47 | # 48 | # ZVM_VI_ESCAPE_BINDKEY 49 | # the vi escape key for all modes (default is ^[ => <ESC>), you can set it 50 | # to whatever you like, such as `jj`, `jk` and so on. 51 | # 52 | # ZVM_VI_INSERT_ESCAPE_BINDKEY 53 | # the vi escape key of insert mode (default is $ZVM_VI_ESCAPE_BINDKEY), you 54 | # can set it to whatever, such as `jj`, `jk` and so on. 55 | # 56 | # ZVM_VI_VISUAL_ESCAPE_BINDKEY 57 | # the vi escape key of visual mode (default is $ZVM_VI_ESCAPE_BINDKEY), you 58 | # can set it to whatever, such as `jj`, `jk` and so on. 59 | # 60 | # ZVM_VI_OPPEND_ESCAPE_BINDKEY 61 | # the vi escape key of operator pendding mode (default is 62 | # $ZVM_VI_ESCAPE_BINDKEY), you can set it to whatever, such as `jj`, `jk` 63 | # and so on. 64 | # 65 | # ZVM_VI_INSERT_MODE_LEGACY_UNDO: 66 | # using legacy undo behavior in vi insert mode 67 | # 68 | # ZVM_VI_HIGHLIGHT_FOREGROUND: 69 | # the behavior of highlight foreground (surrounds, visual-line, etc) in vi mode 70 | # 71 | # ZVM_VI_HIGHLIGHT_BACKGROUND: 72 | # the behavior of highlight background (surrounds, visual-line, etc) in vi mode 73 | # 74 | # ZVM_VI_HIGHLIGHT_EXTRASTYLE: 75 | # the behavior of highlight extra style (i.e. bold, underline) in vi mode 76 | # 77 | # For example: 78 | # ZVM_VI_HIGHLIGHT_FOREGROUND=green # Color name 79 | # ZVM_VI_HIGHLIGHT_FOREGROUND=#008800 # Hex value 80 | # ZVM_VI_HIGHLIGHT_BACKGROUND=red # Color name 81 | # ZVM_VI_HIGHLIGHT_BACKGROUND=#ff0000 # Hex value 82 | # ZVM_VI_HIGHLIGHT_EXTRASTYLE=bold,underline # bold and underline 83 | # 84 | # ZVM_VI_SURROUND_BINDKEY 85 | # the key binding mode for surround operating (default is 'classic') 86 | # 87 | # 1. 'classic' mode (verb->s->surround): 88 | # S" Add " for visual selection 89 | # ys" Add " for visual selection 90 | # cs"' Change " to ' 91 | # ds" Delete " 92 | # 93 | # 2. 's-prefix' mode (s->verb->surround): 94 | # sa" Add " for visual selection 95 | # sd" Delete " 96 | # sr"' Change " to ' 97 | # 98 | # How to select surround text object? 99 | # vi" Select the text object inside the quotes 100 | # va( Select the text object including the brackets 101 | # 102 | # Then you can do any operation for the selection: 103 | # 104 | # 1. Add surrounds for text object 105 | # vi" -> S[ or sa[ => "object" -> "[object]" 106 | # 107 | # 2. Delete/Yank/Change text object 108 | # di( or vi( -> d 109 | # ca( or va( -> c 110 | # yi( or vi( -> y 111 | # 112 | # ZVM_READKEY_ENGINE 113 | # the readkey engine for reading and processing the key events, and the 114 | # below engines are supported: 115 | # ZVM_READKEY_ENGINE_NEX (Default) 116 | # ZVM_READKEY_ENGINE_ZLE 117 | # 118 | # the NEX is a better engine for reading and handling the key events than 119 | # the Zsh's ZLE engine, currently the NEX engine is at beta stage, and 120 | # you can change to Zsh's ZLE engine if you want. 121 | # 122 | # ZVM_KEYTIMEOUT: 123 | # the key input timeout for waiting for next key (default is 0.4 seconds) 124 | # 125 | # ZVM_ESCAPE_KEYTIMEOUT: 126 | # the key input timeout for waiting for next key if it is beginning with 127 | # an escape character (default is 0.03 seconds), and this option is just 128 | # available for the NEX readkey engine 129 | # 130 | # ZVM_LINE_INIT_MODE 131 | # the setting for init mode of command line (default is empty), empty will 132 | # keep the last command mode, for the first command line it will be insert 133 | # mode, you can also set it to a specific vi mode to alway keep the mode 134 | # for each command line 135 | # 136 | # For example: 137 | # ZVM_LINE_INIT_MODE=$ZVM_MODE_INSERT 138 | # ZVM_LINE_INIT_MODE=$ZVM_MODE_NORMAL 139 | # 140 | # ZVM_LAZY_KEYBINDINGS: 141 | # the setting for lazy keybindings (default is true), and lazy keybindings 142 | # will postpone the keybindings of vicmd and visual keymaps to the first 143 | # time entering normal mode 144 | # 145 | # ZVM_NORMAL_MODE_CURSOR: 146 | # the prompt cursor in normal mode 147 | # 148 | # ZVM_INSERT_MODE_CURSOR: 149 | # the prompt cursor in insert mode 150 | # 151 | # ZVM_VISUAL_MODE_CURSOR: 152 | # the prompt cursor in visual mode 153 | # 154 | # ZVM_VISUAL_LINE_MODE_CURSOR: 155 | # the prompt cursor in visual line mode 156 | # 157 | # ZVM_OPPEND_MODE_CURSOR: 158 | # the prompt cursor in operator pending mode 159 | # 160 | # You can change the cursor style by below: 161 | # ZVM_INSERT_MODE_CURSOR=$ZVM_CURSOR_BLOCK 162 | # 163 | # and the below cursor style are supported: 164 | # ZVM_CURSOR_USER_DEFAULT 165 | # ZVM_CURSOR_BLOCK 166 | # ZVM_CURSOR_UNDERLINE 167 | # ZVM_CURSOR_BEAM 168 | # ZVM_CURSOR_BLINKING_BLOCK 169 | # ZVM_CURSOR_BLINKING_UNDERLINE 170 | # ZVM_CURSOR_BLINKING_BEAM 171 | # 172 | # ZVM_VI_EDITOR 173 | # the editor to edit your command line (default is $EDITOR) 174 | # 175 | # ZVM_TMPDIR 176 | # the temporary directory (default is $TMPDIR, otherwise it's /tmp) 177 | # 178 | # ZVM_TERM 179 | # the term for handling terminal sequences, it's important for some 180 | # terminal emulators to show cursor properly (default is $TERM) 181 | # 182 | # ZVM_CURSOR_STYLE_ENABLED 183 | # enable the cursor style feature (default is true) 184 | # 185 | 186 | # Avoid sourcing plugin multiple times 187 | command -v 'zvm_version' >/dev/null && return 188 | 189 | # Plugin information 190 | typeset -gr ZVM_NAME='zsh-vi-mode' 191 | typeset -gr ZVM_DESCRIPTION='💻 A better and friendly vi(vim) mode plugin for ZSH.' 192 | typeset -gr ZVM_REPOSITORY='https://github.com/jeffreytse/zsh-vi-mode' 193 | typeset -gr ZVM_VERSION='0.11.0' 194 | 195 | # Plugin initial status 196 | ZVM_INIT_DONE=false 197 | 198 | # Postpone reset prompt (i.e. postpone the widget `reset-prompt`) 199 | # -1 (No postponing) 200 | # >=0 (Postponing, the decimal value stands for calling times of `reset-prompt`) 201 | ZVM_POSTPONE_RESET_PROMPT=-1 202 | 203 | # Disable reset prompt (i.e. postpone the widget `reset-prompt`) 204 | ZVM_RESET_PROMPT_DISABLED=false 205 | 206 | # Operator pending mode 207 | ZVM_OPPEND_MODE=false 208 | 209 | # Insert mode could be 210 | # `i` (insert) 211 | # `a` (append) 212 | # `I` (insert at the non-blank beginning of current line) 213 | # `A` (append at the end of current line) 214 | ZVM_INSERT_MODE='i' 215 | 216 | # The mode could be the below value: 217 | # `n` (normal) 218 | # `i` (insert) 219 | # `v` (visual) 220 | # `vl` (visual-line) 221 | ZVM_MODE='' 222 | 223 | # The keys typed to invoke this widget, as a literal string 224 | ZVM_KEYS='' 225 | 226 | # The region hilight information 227 | ZVM_REGION_HIGHLIGHT=() 228 | 229 | # Default zvm readkey engines 230 | ZVM_READKEY_ENGINE_NEX='nex' 231 | ZVM_READKEY_ENGINE_ZLE='zle' 232 | ZVM_READKEY_ENGINE_DEFAULT=$ZVM_READKEY_ENGINE_NEX 233 | 234 | # Default alternative character for escape characters 235 | ZVM_ESCAPE_SPACE='\s' 236 | ZVM_ESCAPE_NEWLINE='^J' 237 | 238 | # Default vi modes 239 | ZVM_MODE_LAST='' 240 | ZVM_MODE_NORMAL='n' 241 | ZVM_MODE_INSERT='i' 242 | ZVM_MODE_VISUAL='v' 243 | ZVM_MODE_VISUAL_LINE='vl' 244 | ZVM_MODE_REPLACE='r' 245 | 246 | # Default cursor styles 247 | ZVM_CURSOR_USER_DEFAULT='ud' 248 | ZVM_CURSOR_BLOCK='bl' 249 | ZVM_CURSOR_UNDERLINE='ul' 250 | ZVM_CURSOR_BEAM='be' 251 | ZVM_CURSOR_BLINKING_BLOCK='bbl' 252 | ZVM_CURSOR_BLINKING_UNDERLINE='bul' 253 | ZVM_CURSOR_BLINKING_BEAM='bbe' 254 | 255 | # The commands need to be repeated 256 | ZVM_REPEAT_MODE=false 257 | ZVM_REPEAT_RESET=false 258 | ZVM_REPEAT_COMMANDS=($ZVM_MODE_NORMAL i) 259 | 260 | ########################################## 261 | # Initial all default settings 262 | 263 | # Default config function 264 | : ${ZVM_CONFIG_FUNC:='zvm_config'} 265 | 266 | # Set the readkey engine (default is NEX engine) 267 | : ${ZVM_READKEY_ENGINE:=$ZVM_READKEY_ENGINE_DEFAULT} 268 | 269 | # Set key input timeout (default is 0.4 seconds) 270 | : ${ZVM_KEYTIMEOUT:=0.4} 271 | 272 | # Set the escape key timeout (default is 0.03 seconds) 273 | : ${ZVM_ESCAPE_KEYTIMEOUT:=0.03} 274 | 275 | # Set keybindings mode (default is true) 276 | # The lazy keybindings will post the keybindings of vicmd and visual 277 | # keymaps to the first time entering the normal mode 278 | : ${ZVM_LAZY_KEYBINDINGS:=true} 279 | 280 | # All keybindings for lazy loading 281 | if $ZVM_LAZY_KEYBINDINGS; then 282 | ZVM_LAZY_KEYBINDINGS_LIST=() 283 | fi 284 | 285 | # Set the cursor style in defferent vi modes, the value you could use 286 | # the predefined value, such as $ZVM_CURSOR_BLOCK, $ZVM_CURSOR_BEAM, 287 | # $ZVM_CURSOR_BLINKING_BLOCK and so on. 288 | : ${ZVM_INSERT_MODE_CURSOR:=$ZVM_CURSOR_BEAM} 289 | : ${ZVM_NORMAL_MODE_CURSOR:=$ZVM_CURSOR_BLOCK} 290 | : ${ZVM_VISUAL_MODE_CURSOR:=$ZVM_CURSOR_BLOCK} 291 | : ${ZVM_VISUAL_LINE_MODE_CURSOR:=$ZVM_CURSOR_BLOCK} 292 | 293 | # Operator pending mode cursor style (default is underscore) 294 | : ${ZVM_OPPEND_MODE_CURSOR:=$ZVM_CURSOR_UNDERLINE} 295 | 296 | # Set the vi escape key (default is ^[ => <ESC>) 297 | : ${ZVM_VI_ESCAPE_BINDKEY:=^[} 298 | : ${ZVM_VI_INSERT_ESCAPE_BINDKEY:=$ZVM_VI_ESCAPE_BINDKEY} 299 | : ${ZVM_VI_VISUAL_ESCAPE_BINDKEY:=$ZVM_VI_ESCAPE_BINDKEY} 300 | : ${ZVM_VI_OPPEND_ESCAPE_BINDKEY:=$ZVM_VI_ESCAPE_BINDKEY} 301 | 302 | # Set the line init mode (empty will keep the last mode) 303 | # you can also set it to others, such as $ZVM_MODE_INSERT. 304 | : ${ZVM_LINE_INIT_MODE:=$ZVM_MODE_LAST} 305 | 306 | : ${ZVM_VI_INSERT_MODE_LEGACY_UNDO:=false} 307 | : ${ZVM_VI_SURROUND_BINDKEY:=classic} 308 | : ${ZVM_VI_HIGHLIGHT_BACKGROUND:=#cc0000} 309 | : ${ZVM_VI_HIGHLIGHT_FOREGROUND:=#eeeeee} 310 | : ${ZVM_VI_HIGHLIGHT_EXTRASTYLE:=default} 311 | : ${ZVM_VI_EDITOR:=${EDITOR:-vim}} 312 | : ${ZVM_TMPDIR:=${TMPDIR:-/tmp}} 313 | 314 | # Set the term for handling terminal sequences, it's important for some 315 | # terminal emulators to show cursor properly (default is $TERM) 316 | : ${ZVM_TERM:=${TERM:-xterm-256color}} 317 | 318 | # Enable the cursor style feature 319 | : ${ZVM_CURSOR_STYLE_ENABLED:=true} 320 | 321 | # All the extra commands 322 | commands_array_names=( 323 | zvm_before_init_commands 324 | zvm_after_init_commands 325 | zvm_before_select_vi_mode_commands 326 | zvm_after_select_vi_mode_commands 327 | zvm_before_lazy_keybindings_commands 328 | zvm_after_lazy_keybindings_commands 329 | ) 330 | for commands_array_name in $commands_array_names; do 331 | # Ensure commands set to an empty array, if not already set. 332 | if [[ -z "${(P)commands_array_name}" ]]; then 333 | typeset -g -a $commands_array_name 334 | fi 335 | done 336 | 337 | # All the handlers for switching keyword 338 | zvm_switch_keyword_handlers=( 339 | zvm_switch_number 340 | zvm_switch_boolean 341 | zvm_switch_operator 342 | zvm_switch_weekday 343 | zvm_switch_month 344 | ) 345 | 346 | # History for switching keyword 347 | zvm_switch_keyword_history=() 348 | 349 | # Display version information 350 | function zvm_version() { 351 | local git_info=$(git show -s --format="(%h, %ci)" 2>/dev/null) 352 | echo -e "$ZVM_NAME $ZVM_VERSION $git_info" 353 | echo -e "\e[4m$ZVM_REPOSITORY\e[0m" 354 | echo -e "$ZVM_DESCRIPTION" 355 | } 356 | 357 | # The widget wrapper 358 | function zvm_widget_wrapper() { 359 | local rawfunc=$1; 360 | local func=$2; 361 | local called=$3; 362 | local -i retval 363 | $called || { $rawfunc "${@:4}" } 364 | $func "${@:4}" 365 | return retval 366 | } 367 | 368 | # Define widget function 369 | function zvm_define_widget() { 370 | local widget=$1 371 | local func=$2 || $1 372 | local result=($(zle -l -L "${widget}")) 373 | 374 | # Check if existing the same name 375 | if [[ ${#result[@]} == 4 ]]; then 376 | local rawfunc=${result[4]} 377 | local wrapper="zvm_${widget}-wrapper" 378 | 379 | # To avoid double calling, we need to check if the raw function 380 | # has been called already in the custom widget function 381 | local rawcode=$(declare -f $func 2>/dev/null) 382 | local called=false 383 | [[ "$rawcode" == *"\$rawfunc"* ]] && { called=true } 384 | 385 | eval "$wrapper() { zvm_widget_wrapper $rawfunc $func $called \"\$@\" }" 386 | func=$wrapper 387 | fi 388 | 389 | zle -N $widget $func 390 | } 391 | 392 | # Get the keys typed to invoke this widget, as a literal string 393 | function zvm_keys() { 394 | local keys=${ZVM_KEYS:-$KEYS} 395 | 396 | # Append the prefix of keys if it is visual or visual-line mode 397 | case "${ZVM_MODE}" in 398 | $ZVM_MODE_VISUAL) 399 | if [[ "$keys" != v* ]]; then 400 | keys="v${keys}" 401 | fi 402 | ;; 403 | $ZVM_MODE_VISUAL_LINE) 404 | if [[ "$keys" != V* ]]; then 405 | keys="V${keys}" 406 | fi 407 | ;; 408 | esac 409 | 410 | # Escape the newline and space characters, otherwise, we can't 411 | # get the output from subshell correctly. 412 | keys=${keys//$'\n'/$ZVM_ESCAPE_NEWLINE} 413 | keys=${keys// /$ZVM_ESCAPE_SPACE} 414 | 415 | echo $keys 416 | } 417 | 418 | # Find the widget on a specified bindkey 419 | function zvm_find_bindkey_widget() { 420 | local keymap=$1 421 | local keys=$2 422 | local prefix_mode=${3:-false} 423 | retval=() 424 | 425 | if $prefix_mode; then 426 | local pos=0 427 | local spos=3 428 | local prefix_keys= 429 | 430 | # Get the prefix keys 431 | if [[ $prefix_keys ]]; then 432 | prefix_keys=${prefix_keys:0:-1} 433 | 434 | # If the last key is an escape key (e.g. \", \`, \\) we still 435 | # need to remove the escape backslash `\` 436 | if [[ ${prefix_keys: -1} == '\' ]]; then 437 | prefix_keys=${prefix_keys:0:-1} 438 | fi 439 | fi 440 | 441 | local result=$(bindkey -M ${keymap} -p "$prefix_keys")$'\n' 442 | 443 | # Split string to array by newline 444 | for ((i=$spos;i<$#result;i++)); do 445 | 446 | # Save the last whitespace character of the line 447 | # and continue continue handling while meeting `\n` 448 | case "${result:$i:1}" in 449 | ' ') spos=$i; i=$i+1; continue;; 450 | [$'\n']);; 451 | *) continue;; 452 | esac 453 | 454 | # Check if it has the same prefix keys and retrieve the widgets 455 | if [[ "${result:$((pos+1)):$#keys}" == "$keys" ]]; then 456 | 457 | # Get the binding keys 458 | local k=${result:$((pos+1)):$((spos-pos-2))} 459 | 460 | # Escape spaces in key bindings (space -> $ZVM_ESCAPE_SPACE) 461 | k=${k// /$ZVM_ESCAPE_SPACE} 462 | retval+=($k ${result:$((spos+1)):$((i-spos-1))}) 463 | fi 464 | 465 | # Save as new position 466 | pos=$i+1 467 | 468 | # Skip 3 characters 469 | # One key and quotes at least (i.e \n"_" ) 470 | i=$i+3 471 | done 472 | else 473 | local result=$(bindkey -M ${keymap} "$keys") 474 | if [[ "${result: -14}" == ' undefined-key' ]]; then 475 | return 476 | fi 477 | 478 | # Escape spaces in key bindings (space -> $ZVM_ESCAPE_SPACE) 479 | for ((i=$#result;i>=0;i--)); do 480 | 481 | # Backward find the first whitespace character 482 | [[ "${result:$i:1}" == ' ' ]] || continue 483 | 484 | # Retrieve the keys and widget 485 | local k=${result:1:$i-2} 486 | 487 | # Escape spaces in key bindings (space -> $ZVM_ESCAPE_SPACE) 488 | k=${k// /$ZVM_ESCAPE_SPACE} 489 | retval+=($k ${result:$i+1}) 490 | 491 | break 492 | done 493 | fi 494 | } 495 | 496 | # Read keys for retrieving widget 497 | function zvm_readkeys() { 498 | local keymap=$1 499 | local key=${2:-$(zvm_keys)} 500 | local keys= 501 | local widget= 502 | local result= 503 | local pattern= 504 | local timeout= 505 | 506 | while :; do 507 | # Keep reading key for escape character 508 | if [[ "$key" == $'\e' ]]; then 509 | while :; do 510 | local k= 511 | read -t $ZVM_ESCAPE_KEYTIMEOUT -k 1 k || break 512 | key="${key}${k}" 513 | done 514 | fi 515 | 516 | keys="${keys}${key}" 517 | 518 | # Handle the pattern 519 | if [[ -n "$key" ]]; then 520 | # Transform the non-printed characters 521 | local k=$(zvm_escape_non_printed_characters "${key}") 522 | 523 | # Escape keys 524 | # " -> \" It's a special character in bash syntax 525 | # ` -> \` It's a special character in bash syntax 526 | # <space> -> ` ` It's a special character in bash syntax 527 | k=${k//\"/\\\"} 528 | k=${k//\`/\\\`} 529 | k=${k//$ZVM_ESCAPE_SPACE/ } 530 | 531 | pattern="${pattern}${k}" 532 | fi 533 | 534 | # Find out widgets that match this key pattern 535 | zvm_find_bindkey_widget $keymap "$pattern" true 536 | result=(${retval[@]}) 537 | 538 | # Exit key input if there is only one widget matched 539 | # or no more widget matched. 540 | case ${#result[@]} in 541 | 2) key=; widget=${result[2]}; break;; 542 | 0) break;; 543 | esac 544 | 545 | # Evaluate the readkey timeout 546 | # Special timeout for the escape sequence 547 | if [[ "${keys}" == $'\e' ]]; then 548 | timeout=$ZVM_ESCAPE_KEYTIMEOUT 549 | # Check if there is any one custom escape sequence 550 | for ((i=1; i<=${#result[@]}; i=i+2)); do 551 | if [[ "${result[$i]}" =~ '^\^\[\[?[A-Z0-9]*~?\^\[' ]]; then 552 | timeout=$ZVM_KEYTIMEOUT 553 | break 554 | fi 555 | done 556 | else 557 | timeout=$ZVM_KEYTIMEOUT 558 | fi 559 | 560 | # Wait for reading next key, and we should save the widget 561 | # as the final widget if it is full matching 562 | key= 563 | if [[ "${result[1]}" == "${pattern}" ]]; then 564 | widget=${result[2]} 565 | # Get current widget as final widget when reading key timeout 566 | read -t $timeout -k 1 key || break 567 | else 568 | zvm_enter_oppend_mode 569 | read -k 1 key 570 | fi 571 | done 572 | 573 | # Exit operator pending mode 574 | if $ZVM_OPPEND_MODE; then 575 | zvm_exit_oppend_mode 576 | fi 577 | 578 | if [[ -z "$key" ]]; then 579 | retval=(${keys} $widget) 580 | else 581 | retval=(${keys:0:-$#key} $widget $key) 582 | fi 583 | } 584 | 585 | # Add key bindings 586 | function zvm_bindkey() { 587 | local keymap=$1 588 | local keys=$2 589 | local widget=$3 590 | local params=$4 591 | local key= 592 | 593 | # We should bind keys with an existing widget 594 | [[ -z $widget ]] && return 595 | 596 | # If lazy keybindings is enabled, we need to add to the lazy list 597 | if [[ ${ZVM_LAZY_KEYBINDINGS_LIST+x} && ${keymap} != viins ]]; then 598 | keys=${keys//\"/\\\"} 599 | keys=${keys//\`/\\\`} 600 | ZVM_LAZY_KEYBINDINGS_LIST+=( 601 | "${keymap} \"${keys}\" ${widget} \"${params}\"" 602 | ) 603 | return 604 | fi 605 | 606 | # Hanle the keybinding of NEX readkey engine 607 | if [[ $ZVM_READKEY_ENGINE == $ZVM_READKEY_ENGINE_NEX ]]; then 608 | # Get the first key (especially check if ctrl characters) 609 | if [[ $#keys -gt 1 && "${keys:0:1}" == '^' ]]; then 610 | key=${keys:0:2} 611 | else 612 | key=${keys:0:1} 613 | 614 | # As any character that is not bound to one of the history control 615 | # related functions, or self-insert or self-insert-unmeta, will 616 | # cause the mode to be exited To prevent history search, so that 617 | # we need to bind keys explicitly. 618 | if [[ "$keymap" == "viins" ]]; then 619 | bindkey -M isearch "${key}" self-insert 620 | fi 621 | fi 622 | bindkey -M $keymap "${key}" zvm_readkeys_handler 623 | fi 624 | 625 | # Wrap params to a new widget 626 | if [[ -n $params ]]; then 627 | local suffix=$(zvm_string_to_hex $params) 628 | eval "$widget:$suffix() { $widget $params }" 629 | widget="$widget:$suffix" 630 | zvm_define_widget $widget 631 | fi 632 | 633 | # Bind keys with with a widget 634 | bindkey -M $keymap "${keys}" $widget 635 | } 636 | 637 | # Convert string to hexadecimal 638 | function zvm_string_to_hex() { 639 | local str= 640 | for ((i=1;i<=$#1;i++)); do 641 | str+=$(printf '%x' "'${1[$i]}") 642 | done 643 | echo "$str" 644 | } 645 | 646 | # Escape non-printed characters 647 | function zvm_escape_non_printed_characters() { 648 | local str= 649 | for ((i=0;i<$#1;i++)); do 650 | local c=${1:$i:1} 651 | if [[ "$c" < ' ' ]]; then 652 | local ord=$(($(printf '%d' "'$c")+64)) 653 | c=$(printf \\$(printf '%03o' $ord)) 654 | str="${str}^${c}" 655 | elif [[ "$c" == '' ]]; then 656 | str="${str}^?" 657 | elif [[ "$c" == '�' ]]; then 658 | str="${str}^@" 659 | else 660 | str="${str}${c}" 661 | fi 662 | done 663 | 664 | # Escape the newline and space characters, otherwise, we can't 665 | # get the output from subshell correctly. 666 | str=${str// /$ZVM_ESCAPE_SPACE} 667 | str=${str//$'\n'/$ZVM_ESCAPE_NEWLINE} 668 | 669 | echo -n $str 670 | } 671 | 672 | # Backward remove characters of an emacs region in the line 673 | function zvm_backward_kill_region() { 674 | local bpos=$CURSOR-1 epos=$CURSOR 675 | 676 | # Backward search the boundary of current region 677 | for ((; bpos >= 0; bpos--)); do 678 | # Break when cursor is at the beginning of line 679 | [[ "${BUFFER:$bpos:1}" == $'\n' ]] && break 680 | 681 | # Break when cursor is at the boundary of a word region 682 | [[ "${BUFFER:$bpos:2}" =~ ^\ [^\ $'\n']$ ]] && break 683 | done 684 | 685 | bpos=$bpos+1 686 | CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))} 687 | BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}" 688 | CURSOR=$bpos 689 | } 690 | 691 | # Remove all characters between the cursor position and the 692 | # beginning of the line. 693 | function zvm_backward_kill_line() { 694 | BUFFER=${BUFFER:$CURSOR:$#BUFFER} 695 | CURSOR=0 696 | } 697 | 698 | # Remove all characters between the cursor position and the 699 | # end of the line. 700 | function zvm_forward_kill_line() { 701 | BUFFER=${BUFFER:0:$CURSOR} 702 | } 703 | 704 | # Remove all characters of the line. 705 | function zvm_kill_line() { 706 | local ret=($(zvm_calc_selection $ZVM_MODE_VISUAL_LINE)) 707 | local bpos=${ret[1]} epos=${ret[2]} 708 | CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}$'\n' 709 | BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}" 710 | CURSOR=$bpos 711 | } 712 | 713 | # Remove all characters of the whole line. 714 | function zvm_kill_whole_line() { 715 | local ret=($(zvm_calc_selection $ZVM_MODE_VISUAL_LINE)) 716 | local bpos=$ret[1] epos=$ret[2] cpos=$ret[3] 717 | CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))}$'\n' 718 | 719 | # Adjust region range of deletion 720 | if (( $epos < $#BUFFER )); then 721 | epos=$epos+1 722 | fi 723 | 724 | BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}" 725 | CURSOR=$cpos 726 | } 727 | 728 | # Exchange the point and mark 729 | function zvm_exchange_point_and_mark() { 730 | cursor=$MARK 731 | MARK=$CURSOR CURSOR=$cursor 732 | zvm_highlight 733 | } 734 | 735 | # Open line below 736 | function zvm_open_line_below() { 737 | local i=$CURSOR 738 | 739 | # If there is a completion suffix, we should break at the 740 | # postion of suffix begin, otherwise, it should break when 741 | # forward finding out the first newline character. 742 | for ((; i<$#BUFFER; i++)); do 743 | if ((SUFFIX_ACTIVE == 1)) && ((i >= SUFFIX_BEGIN)); then 744 | break 745 | fi 746 | if [[ "${BUFFER[$i]}" == $'\n' ]]; then 747 | i=$((i-1)) 748 | break 749 | fi 750 | done 751 | 752 | CURSOR=$i 753 | LBUFFER+=$'\n' 754 | 755 | zvm_reset_repeat_commands $ZVM_MODE_NORMAL o 756 | zvm_select_vi_mode $ZVM_MODE_INSERT 757 | } 758 | 759 | # Open line above 760 | function zvm_open_line_above() { 761 | local i=$CURSOR 762 | 763 | # Break when backward finding out the first newline character. 764 | for ((; i>0; i--)); do 765 | if [[ "${BUFFER[$i]}" == $'\n' ]]; then 766 | break 767 | fi 768 | done 769 | 770 | CURSOR=$i 771 | LBUFFER+=$'\n' 772 | CURSOR=$((CURSOR-1)) 773 | 774 | zvm_reset_repeat_commands $ZVM_MODE_NORMAL O 775 | zvm_select_vi_mode $ZVM_MODE_INSERT 776 | } 777 | 778 | # Replace characters one by one (Replacing mode) 779 | function zvm_vi_replace() { 780 | if [[ $ZVM_MODE == $ZVM_MODE_NORMAL ]]; then 781 | local cursor=$CURSOR 782 | local cache=() 783 | local cmds=() 784 | local key= 785 | 786 | zvm_select_vi_mode $ZVM_MODE_REPLACE 787 | 788 | while :; do 789 | # Read a character for replacing 790 | zvm_update_cursor 791 | 792 | # Redisplay the command line, this is to be called from within 793 | # a user-defined widget to allow changes to become visible 794 | zle -R 795 | 796 | read -k 1 key 797 | 798 | # Escape key will break the replacing process, and enter key 799 | # will repace with a newline character. 800 | case $(zvm_escape_non_printed_characters $key) in 801 | '^['|$ZVM_VI_OPPEND_ESCAPE_BINDKEY) break;; 802 | '^M') key=$'\n';; 803 | esac 804 | 805 | # If the key is backspace, we should move backward the cursor 806 | if [[ $key == '' ]]; then 807 | # Cursor position should not be less than zero 808 | if ((cursor > 0)); then 809 | cursor=$((cursor-1)) 810 | fi 811 | 812 | # We should recover the character when cache size is not zero 813 | if ((${#cache[@]} > 0)); then 814 | key=${cache[-1]} 815 | 816 | if [[ $key == '<I>' ]]; then 817 | key= 818 | fi 819 | 820 | cache=(${cache[@]:0:-1}) 821 | BUFFER[$cursor+1]=$key 822 | 823 | # Remove from commands 824 | cmds=(${cmds[@]:0:-1}) 825 | fi 826 | else 827 | # If the key or the character at cursor is a newline character, 828 | # or the cursor is at the end of buffer, we should insert the 829 | # key instead of replacing with the key. 830 | if [[ $key == $'\n' || 831 | $BUFFER[$cursor+1] == $'\n' || 832 | $BUFFER[$cursor+1] == '' 833 | ]]; then 834 | cache+=('<I>') 835 | LBUFFER+=$key 836 | else 837 | cache+=(${BUFFER[$cursor+1]}) 838 | BUFFER[$cursor+1]=$key 839 | fi 840 | 841 | cursor=$((cursor+1)) 842 | 843 | # Push to commands 844 | cmds+=($key) 845 | fi 846 | 847 | # Update next cursor position 848 | CURSOR=$cursor 849 | 850 | zle redisplay 851 | done 852 | 853 | # The cursor position should go back one character after 854 | # exiting the replace mode 855 | zle vi-backward-char 856 | 857 | zvm_select_vi_mode $ZVM_MODE_NORMAL 858 | zvm_reset_repeat_commands $ZVM_MODE R $cmds 859 | elif [[ $ZVM_MODE == $ZVM_MODE_VISUAL ]]; then 860 | zvm_enter_visual_mode V 861 | zvm_vi_change 862 | elif [[ $ZVM_MODE == $ZVM_MODE_VISUAL_LINE ]]; then 863 | zvm_vi_change 864 | fi 865 | } 866 | 867 | # Replace characters in one time 868 | function zvm_vi_replace_chars() { 869 | local cmds=() 870 | local key= 871 | 872 | # Read a character for replacing 873 | zvm_enter_oppend_mode 874 | 875 | # Redisplay the command line, this is to be called from within 876 | # a user-defined widget to allow changes to become visible 877 | zle redisplay 878 | zle -R 879 | 880 | read -k 1 key 881 | 882 | zvm_exit_oppend_mode 883 | 884 | # Escape key will break the replacing process 885 | case $(zvm_escape_non_printed_characters $key) in 886 | $ZVM_VI_OPPEND_ESCAPE_BINDKEY) 887 | zvm_exit_visual_mode 888 | return 889 | esac 890 | 891 | if [[ $ZVM_MODE == $ZVM_MODE_NORMAL ]]; then 892 | cmds+=($key) 893 | BUFFER[$CURSOR+1]=$key 894 | else 895 | local ret=($(zvm_calc_selection)) 896 | local bpos=${ret[1]} epos=${ret[2]} 897 | for ((bpos=bpos+1; bpos<=epos; bpos++)); do 898 | # Newline character is no need to be replaced 899 | if [[ $BUFFER[$bpos] == $'\n' ]]; then 900 | cmds+=($'\n') 901 | continue 902 | fi 903 | 904 | cmds+=($key) 905 | BUFFER[$bpos]=$key 906 | done 907 | zvm_exit_visual_mode 908 | fi 909 | 910 | # Reset the repeat commands 911 | zvm_reset_repeat_commands $ZVM_MODE r $cmds 912 | } 913 | 914 | # Substitute characters of selection 915 | function zvm_vi_substitute() { 916 | # Substitute one character in normal mode 917 | if [[ $ZVM_MODE == $ZVM_MODE_NORMAL ]]; then 918 | BUFFER="${BUFFER:0:$CURSOR}${BUFFER:$((CURSOR+1))}" 919 | zvm_reset_repeat_commands $ZVM_MODE c 0 1 920 | zvm_select_vi_mode $ZVM_MODE_INSERT 921 | else 922 | zvm_vi_change 923 | fi 924 | } 925 | 926 | # Substitute all characters of a line 927 | function zvm_vi_substitute_whole_line() { 928 | zvm_select_vi_mode $ZVM_MODE_VISUAL_LINE false 929 | zvm_vi_substitute 930 | } 931 | 932 | # Check if cursor is at an empty line 933 | function zvm_is_empty_line() { 934 | local cursor=${1:-$CURSOR} 935 | if [[ ${BUFFER:$cursor:1} == $'\n' && 936 | ${BUFFER:$((cursor-1)):1} == $'\n' ]]; then 937 | return 938 | fi 939 | return 1 940 | } 941 | 942 | # Get the beginning and end position of selection 943 | function zvm_selection() { 944 | local bpos= epos= 945 | if (( MARK > CURSOR )) ; then 946 | bpos=$CURSOR epos=$((MARK+1)) 947 | else 948 | bpos=$MARK epos=$((CURSOR+1)) 949 | fi 950 | echo $bpos $epos 951 | } 952 | 953 | # Calculate the region of selection 954 | function zvm_calc_selection() { 955 | local ret=($(zvm_selection)) 956 | local bpos=${ret[1]} epos=${ret[2]} cpos= 957 | 958 | # Save the current cursor position 959 | cpos=$bpos 960 | 961 | # Check if it is visual-line mode 962 | if [[ "${1:-$ZVM_MODE}" == $ZVM_MODE_VISUAL_LINE ]]; then 963 | 964 | # Extend the selection to whole line 965 | for ((bpos=$bpos-1; $bpos>0; bpos--)); do 966 | if [[ "${BUFFER:$bpos:1}" == $'\n' ]]; then 967 | bpos=$((bpos+1)) 968 | break 969 | fi 970 | done 971 | for ((epos=$epos-1; $epos<$#BUFFER; epos++)); do 972 | if [[ "${BUFFER:$epos:1}" == $'\n' ]]; then 973 | break 974 | fi 975 | done 976 | 977 | # The begin position must not be less than zero 978 | if (( bpos < 0 )); then 979 | bpos=0 980 | fi 981 | 982 | ########################################### 983 | # Calculate the new cursor position, here we consider that 984 | # the selection will be delected. 985 | 986 | # Calculate the indent of current cursor line 987 | for ((cpos=$((CURSOR-1)); $cpos>=0; cpos--)); do 988 | [[ "${BUFFER:$cpos:1}" == $'\n' ]] && break 989 | done 990 | 991 | local indent=$((CURSOR-cpos-1)) 992 | 993 | # If the selection includes the last line, the cursor 994 | # will move up to above line. Otherwise the cursor will 995 | # keep in the same line. 996 | 997 | local hpos= # Line head position 998 | local rpos= # Reference position 999 | 1000 | if (( $epos < $#BUFFER )); then 1001 | # Get the head position of next line 1002 | hpos=$((epos+1)) 1003 | rpos=$bpos 1004 | else 1005 | # Get the head position of above line 1006 | for ((hpos=$((bpos-2)); $hpos>0; hpos--)); do 1007 | if [[ "${BUFFER:$hpos:1}" == $'\n' ]]; then 1008 | break 1009 | fi 1010 | done 1011 | if (( $hpos < -1 )); then 1012 | hpos=-1 1013 | fi 1014 | hpos=$((hpos+1)) 1015 | rpos=$hpos 1016 | fi 1017 | 1018 | # Calculate the cursor postion, the indent must be 1019 | # less than the line characters. 1020 | for ((cpos=$hpos; $cpos<$#BUFFER; cpos++)); do 1021 | if [[ "${BUFFER:$cpos:1}" == $'\n' ]]; then 1022 | break 1023 | fi 1024 | if (( $hpos + $indent <= $cpos )); then 1025 | break 1026 | fi 1027 | done 1028 | 1029 | cpos=$((rpos+cpos-hpos)) 1030 | fi 1031 | 1032 | echo $bpos $epos $cpos 1033 | } 1034 | 1035 | # Yank characters of the marked region 1036 | function zvm_yank() { 1037 | local ret=($(zvm_calc_selection $1)) 1038 | local bpos=$ret[1] epos=$ret[2] cpos=$ret[3] 1039 | CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))} 1040 | if [[ ${1:-$ZVM_MODE} == $ZVM_MODE_VISUAL_LINE ]]; then 1041 | CUTBUFFER=${CUTBUFFER}$'\n' 1042 | fi 1043 | CURSOR=$bpos MARK=$epos 1044 | } 1045 | 1046 | # Up case of the visual selection 1047 | function zvm_vi_up_case() { 1048 | local ret=($(zvm_selection)) 1049 | local bpos=${ret[1]} epos=${ret[2]} 1050 | local content=${BUFFER:$bpos:$((epos-bpos))} 1051 | BUFFER="${BUFFER:0:$bpos}${(U)content}${BUFFER:$epos}" 1052 | zvm_exit_visual_mode 1053 | } 1054 | 1055 | # Down case of the visual selection 1056 | function zvm_vi_down_case() { 1057 | local ret=($(zvm_selection)) 1058 | local bpos=${ret[1]} epos=${ret[2]} 1059 | local content=${BUFFER:$bpos:$((epos-bpos))} 1060 | BUFFER="${BUFFER:0:$bpos}${(L)content}${BUFFER:$epos}" 1061 | zvm_exit_visual_mode 1062 | } 1063 | 1064 | # Opposite case of the visual selection 1065 | function zvm_vi_opp_case() { 1066 | local ret=($(zvm_selection)) 1067 | local bpos=${ret[1]} epos=${ret[2]} 1068 | local content=${BUFFER:$bpos:$((epos-bpos))} 1069 | for ((i=1; i<=$#content; i++)); do 1070 | if [[ ${content[i]} =~ [A-Z] ]]; then 1071 | content[i]=${(L)content[i]} 1072 | elif [[ ${content[i]} =~ [a-z] ]]; then 1073 | content[i]=${(U)content[i]} 1074 | fi 1075 | done 1076 | BUFFER="${BUFFER:0:$bpos}${content}${BUFFER:$epos}" 1077 | zvm_exit_visual_mode 1078 | } 1079 | 1080 | # Yank characters of the visual selection 1081 | function zvm_vi_yank() { 1082 | zvm_yank 1083 | zvm_exit_visual_mode ${1:-true} 1084 | } 1085 | 1086 | # Put cutbuffer after the cursor 1087 | function zvm_vi_put_after() { 1088 | local head= foot= 1089 | local content=${CUTBUFFER} 1090 | local offset=1 1091 | 1092 | if [[ ${content: -1} == $'\n' ]]; then 1093 | local pos=${CURSOR} 1094 | 1095 | # Find the end of current line 1096 | for ((; $pos<$#BUFFER; pos++)); do 1097 | if [[ ${BUFFER:$pos:1} == $'\n' ]]; then 1098 | pos=$pos+1 1099 | break 1100 | fi 1101 | done 1102 | 1103 | # Special handling if cursor at an empty line 1104 | if zvm_is_empty_line; then 1105 | head=${BUFFER:0:$pos} 1106 | foot=${BUFFER:$pos} 1107 | else 1108 | head=${BUFFER:0:$pos} 1109 | foot=${BUFFER:$pos} 1110 | if [[ $pos == $#BUFFER ]]; then 1111 | content=$'\n'${content:0:-1} 1112 | pos=$pos+1 1113 | fi 1114 | fi 1115 | 1116 | offset=0 1117 | BUFFER="${head}${content}${foot}" 1118 | CURSOR=$pos 1119 | else 1120 | # Special handling if cursor at an empty line 1121 | if zvm_is_empty_line; then 1122 | head="${BUFFER:0:$((CURSOR-1))}" 1123 | foot="${BUFFER:$CURSOR}" 1124 | else 1125 | head="${BUFFER:0:$CURSOR}" 1126 | foot="${BUFFER:$((CURSOR+1))}" 1127 | fi 1128 | 1129 | BUFFER="${head}${BUFFER:$CURSOR:1}${content}${foot}" 1130 | CURSOR=$CURSOR+$#content 1131 | fi 1132 | 1133 | # Reresh display and highlight buffer 1134 | zvm_highlight clear 1135 | zvm_highlight custom $(($#head+$offset)) $(($#head+$#content+$offset)) 1136 | } 1137 | 1138 | # Put cutbuffer before the cursor 1139 | function zvm_vi_put_before() { 1140 | local head= foot= 1141 | local content=${CUTBUFFER} 1142 | 1143 | if [[ ${content: -1} == $'\n' ]]; then 1144 | local pos=$CURSOR 1145 | 1146 | # Find the beginning of current line 1147 | for ((; $pos>0; pos--)); do 1148 | if [[ "${BUFFER:$pos:1}" == $'\n' ]]; then 1149 | pos=$pos+1 1150 | break 1151 | fi 1152 | done 1153 | 1154 | # Check if it is an empty line 1155 | if zvm_is_empty_line; then 1156 | head=${BUFFER:0:$((pos-1))} 1157 | foot=$'\n'${BUFFER:$pos} 1158 | pos=$((pos-1)) 1159 | else 1160 | head=${BUFFER:0:$pos} 1161 | foot=${BUFFER:$pos} 1162 | fi 1163 | 1164 | BUFFER="${head}${content}${foot}" 1165 | CURSOR=$pos 1166 | else 1167 | head="${BUFFER:0:$CURSOR}" 1168 | foot="${BUFFER:$((CURSOR+1))}" 1169 | BUFFER="${head}${content}${BUFFER:$CURSOR:1}${foot}" 1170 | CURSOR=$CURSOR+$#content 1171 | CURSOR=$((CURSOR-1)) 1172 | fi 1173 | 1174 | # Reresh display and highlight buffer 1175 | zvm_highlight clear 1176 | zvm_highlight custom $#head $(($#head+$#content)) 1177 | } 1178 | 1179 | # Replace a selection 1180 | function zvm_replace_selection() { 1181 | local ret=($(zvm_calc_selection)) 1182 | local bpos=$ret[1] epos=$ret[2] cpos=$ret[3] 1183 | local cutbuf=$1 1184 | 1185 | # If there's a replacement, we need to calculate cursor position 1186 | if (( $#cutbuf > 0 )); then 1187 | cpos=$(($bpos + $#cutbuf - 1)) 1188 | fi 1189 | 1190 | CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))} 1191 | 1192 | # Check if it is visual line mode 1193 | if [[ $ZVM_MODE == $ZVM_MODE_VISUAL_LINE ]]; then 1194 | if (( $epos < $#BUFFER )); then 1195 | epos=$epos+1 1196 | elif (( $bpos > 0 )); then 1197 | bpos=$bpos-1 1198 | fi 1199 | CUTBUFFER=${CUTBUFFER}$'\n' 1200 | fi 1201 | 1202 | BUFFER="${BUFFER:0:$bpos}${cutbuf}${BUFFER:$epos}" 1203 | CURSOR=$cpos 1204 | } 1205 | 1206 | # Replace characters of the visual selection 1207 | function zvm_vi_replace_selection() { 1208 | zvm_replace_selection $CUTBUFFER 1209 | zvm_exit_visual_mode ${1:-true} 1210 | } 1211 | 1212 | # Delete characters of the visual selection 1213 | function zvm_vi_delete() { 1214 | zvm_replace_selection 1215 | zvm_exit_visual_mode ${1:-true} 1216 | } 1217 | 1218 | # Yank characters of the visual selection 1219 | function zvm_vi_change() { 1220 | local ret=($(zvm_calc_selection)) 1221 | local bpos=$ret[1] epos=$ret[2] 1222 | 1223 | CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))} 1224 | 1225 | # Check if it is visual line mode 1226 | if [[ $ZVM_MODE == $ZVM_MODE_VISUAL_LINE ]]; then 1227 | CUTBUFFER=${CUTBUFFER}$'\n' 1228 | fi 1229 | 1230 | BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}" 1231 | CURSOR=$bpos 1232 | 1233 | # Return when it's repeating mode 1234 | $ZVM_REPEAT_MODE && return 1235 | 1236 | # Reset the repeat commands 1237 | if [[ $ZVM_MODE != $ZVM_MODE_NORMAL ]]; then 1238 | local npos=0 ncount=0 ccount=0 1239 | # Count the amount of newline character and the amount of 1240 | # characters after the last newline character. 1241 | while :; do 1242 | # Forward find the last newline character's position 1243 | npos=$(zvm_substr_pos $CUTBUFFER $'\n' $npos) 1244 | if [[ $npos == -1 ]]; then 1245 | if (($ncount == 0)); then 1246 | ccount=$#CUTBUFFER 1247 | fi 1248 | break 1249 | fi 1250 | npos=$((npos+1)) 1251 | ncount=$(($ncount + 1)) 1252 | ccount=$(($#CUTBUFFER - $npos)) 1253 | done 1254 | zvm_reset_repeat_commands $ZVM_MODE c $ncount $ccount 1255 | fi 1256 | 1257 | zvm_exit_visual_mode false 1258 | zvm_select_vi_mode $ZVM_MODE_INSERT ${1:-true} 1259 | } 1260 | 1261 | # Change characters from cursor to the end of current line 1262 | function zvm_vi_change_eol() { 1263 | local bpos=$CURSOR epos=$CURSOR 1264 | 1265 | # Find the end of current line 1266 | for ((; $epos<$#BUFFER; epos++)); do 1267 | if [[ "${BUFFER:$epos:1}" == $'\n' ]]; then 1268 | break 1269 | fi 1270 | done 1271 | 1272 | CUTBUFFER=${BUFFER:$bpos:$((epos-bpos))} 1273 | BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}" 1274 | 1275 | zvm_reset_repeat_commands $ZVM_MODE c 0 $#CUTBUFFER 1276 | zvm_select_vi_mode $ZVM_MODE_INSERT 1277 | } 1278 | 1279 | # Default handler for unhandled key events 1280 | function zvm_default_handler() { 1281 | local keys=$(zvm_keys) 1282 | local extra_keys=$1 1283 | 1284 | # Exit vi mode if keys is the escape keys 1285 | case $(zvm_escape_non_printed_characters "$keys") in 1286 | '^['|$ZVM_VI_INSERT_ESCAPE_BINDKEY) 1287 | zvm_exit_insert_mode false 1288 | zvm_reset_prompt 1289 | ZVM_KEYS=${extra_keys} 1290 | return 1291 | ;; 1292 | [vV]'^['|[vV]$ZVM_VI_VISUAL_ESCAPE_BINDKEY) 1293 | zvm_exit_visual_mode false 1294 | zvm_reset_prompt 1295 | ZVM_KEYS=${extra_keys} 1296 | return 1297 | ;; 1298 | esac 1299 | 1300 | case "$KEYMAP" in 1301 | vicmd) 1302 | case "$keys" in 1303 | [vV]c) zvm_vi_change false;; 1304 | [vV]d) zvm_vi_delete false;; 1305 | [vV]y) zvm_vi_yank false;; 1306 | [vV]S) zvm_change_surround S;; 1307 | [cdyvV]*) 1308 | # We must loop util we meet a valid range action 1309 | while :; do 1310 | zvm_range_handler "${keys}${extra_keys}" 1311 | case $? in 1312 | 0) break;; 1313 | 1) 1314 | # Continue to ask to provide the action when we're 1315 | # still in visual mode 1316 | keys='v'; extra_keys= 1317 | ;; 1318 | 2) 1319 | # Pushe the keys onto the input stack of ZLE, it's 1320 | # handled in zvm_readkeys_handler function 1321 | zvm_exit_visual_mode false 1322 | zvm_reset_prompt 1323 | return 1324 | ;; 1325 | 3) 1326 | zvm_exit_visual_mode false 1327 | zvm_reset_prompt 1328 | break 1329 | ;; 1330 | esac 1331 | done 1332 | ;; 1333 | *) 1334 | for ((i=0;i<$#keys;i++)) do 1335 | zvm_navigation_handler ${keys:$i:1} 1336 | zvm_highlight 1337 | done 1338 | ;; 1339 | esac 1340 | ;; 1341 | viins|main) 1342 | if [[ "${keys:0:1}" =~ [a-zA-Z0-9\ ] ]]; then 1343 | zvm_self_insert "${keys:0:1}" 1344 | zle redisplay 1345 | ZVM_KEYS="${keys:1}${extra_keys}" 1346 | return 1347 | elif [[ "${keys:0:1}" == $'\e' ]]; then 1348 | zvm_exit_insert_mode false 1349 | ZVM_KEYS="${keys:1}${extra_keys}" 1350 | return 1351 | fi 1352 | ;; 1353 | visual) 1354 | ;; 1355 | esac 1356 | 1357 | ZVM_KEYS= 1358 | } 1359 | 1360 | # Read keys for retrieving and executing a widget 1361 | function zvm_readkeys_handler() { 1362 | local keymap=${1} 1363 | local keys=${2:-$KEYS} 1364 | local key= 1365 | local widget= 1366 | 1367 | # Get the keymap if keymap is empty 1368 | if [[ -z $keymap ]]; then 1369 | case "$ZVM_MODE" in 1370 | $ZVM_MODE_INSERT) keymap=viins;; 1371 | $ZVM_MODE_NORMAL) keymap=vicmd;; 1372 | $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE) keymap=visual;; 1373 | esac 1374 | fi 1375 | 1376 | # Read keys and retrieve the widget 1377 | zvm_readkeys $keymap $keys 1378 | keys=${retval[1]} 1379 | widget=${retval[2]} 1380 | key=${retval[3]} 1381 | 1382 | # Escape space in keys 1383 | keys=${keys//$ZVM_ESCAPE_SPACE/ } 1384 | key=${key//$ZVM_ESCAPE_SPACE/ } 1385 | 1386 | ZVM_KEYS="${keys}" 1387 | 1388 | # If the widget is current handler, we should call the default handler 1389 | if [[ "${widget}" == "${funcstack[1]}" ]]; then 1390 | widget= 1391 | fi 1392 | 1393 | # If the widget isn't matched, we should call the default handler 1394 | if [[ -z ${widget} ]]; then 1395 | # Disable reset prompt action, as multiple calling this function 1396 | # will cause potential line eaten issue. 1397 | ZVM_RESET_PROMPT_DISABLED=true 1398 | 1399 | zle zvm_default_handler "$key" 1400 | 1401 | ZVM_RESET_PROMPT_DISABLED=false 1402 | 1403 | # Push back to the key input stack, and postpone reset prompt 1404 | if [[ -n "$ZVM_KEYS" ]]; then 1405 | # To prevent ZLE from error "not enough arguments for -U", the 1406 | # parameter should be put after `--` symbols. 1407 | zle -U -- "${ZVM_KEYS}" 1408 | else 1409 | # If there is any reset prompt, we need to execute for 1410 | # prompt resetting. 1411 | zvm_postpone_reset_prompt false 1412 | fi 1413 | else 1414 | zle $widget 1415 | fi 1416 | 1417 | ZVM_KEYS= 1418 | } 1419 | 1420 | # Find and move cursor to next character 1421 | function zvm_find_and_move_cursor() { 1422 | local char=$1 1423 | local count=${2:-1} 1424 | local forward=${3:-true} 1425 | local skip=${4:-false} 1426 | local cursor=$CURSOR 1427 | 1428 | [[ -z $char ]] && return 1 1429 | 1430 | # Find the specific character 1431 | while :; do 1432 | if $forward; then 1433 | cursor=$((cursor+1)) 1434 | ((cursor > $#BUFFER)) && break 1435 | else 1436 | cursor=$((cursor-1)) 1437 | ((cursor < 0)) && break 1438 | fi 1439 | if [[ ${BUFFER[$cursor+1]} == $char ]]; then 1440 | count=$((count-1)) 1441 | fi 1442 | ((count == 0)) && break 1443 | done 1444 | 1445 | [[ $count > 0 ]] && return 1 1446 | 1447 | # Skip the character 1448 | if $skip; then 1449 | if $forward; then 1450 | cursor=$((cursor-1)) 1451 | else 1452 | cursor=$((cursor+1)) 1453 | fi 1454 | fi 1455 | 1456 | CURSOR=$cursor 1457 | } 1458 | 1459 | # Handle the navigation action 1460 | function zvm_navigation_handler() { 1461 | # Return if no keys provided 1462 | [[ -z $1 ]] && return 1 1463 | 1464 | local keys=$1 1465 | local count= 1466 | local cmd= 1467 | 1468 | # Retrieve the calling command 1469 | if [[ $keys =~ '^([1-9][0-9]*)?([fFtT].?)$' ]]; then 1470 | count=${match[1]:-1} 1471 | 1472 | # The length of keys must be 2 1473 | if (( ${#match[2]} < 2)); then 1474 | zvm_enter_oppend_mode 1475 | 1476 | read -k 1 cmd 1477 | keys+=$cmd 1478 | 1479 | case "$(zvm_escape_non_printed_characters ${keys[-1]})" in 1480 | $ZVM_VI_OPPEND_ESCAPE_BINDKEY) return 1;; 1481 | esac 1482 | 1483 | zvm_exit_oppend_mode 1484 | fi 1485 | 1486 | local forward=true 1487 | local skip=false 1488 | 1489 | [[ ${keys[-2]} =~ '[FT]' ]] && forward=false 1490 | [[ ${keys[-2]} =~ '[tT]' ]] && skip=true 1491 | 1492 | # Escape special characters (e.g. ', ", `, ~, ^, |, &, <space>) 1493 | local key=${keys[-1]} 1494 | if [[ $key =~ "['\\\"\`\~\^\|\#\&\*\;\}\(\)\<\>\ ]" ]]; then 1495 | key=\\${key} 1496 | fi 1497 | 1498 | cmd=(zvm_find_and_move_cursor $key $count $forward $skip) 1499 | count=1 1500 | else 1501 | count=${keys:0:-1} 1502 | case ${keys: -1} in 1503 | '^') cmd=(zle vi-first-non-blank);; 1504 | '$') cmd=(zle vi-end-of-line);; 1505 | ' ') cmd=(zle vi-forward-char);; 1506 | '0') cmd=(zle vi-digit-or-beginning-of-line);; 1507 | 'h') cmd=(zle vi-backward-char);; 1508 | 'j') cmd=(zle down-line-or-history);; 1509 | 'k') cmd=(zle up-line-or-history);; 1510 | 'l') cmd=(zle vi-forward-char);; 1511 | 'w') cmd=(zle vi-forward-word);; 1512 | 'W') cmd=(zle vi-forward-blank-word);; 1513 | 'e') cmd=(zle vi-forward-word-end);; 1514 | 'E') cmd=(zle vi-forward-blank-word-end);; 1515 | 'b') cmd=(zle vi-backward-word);; 1516 | 'B') cmd=(zle vi-backward-blank-word);; 1517 | esac 1518 | fi 1519 | 1520 | # Check widget if the widget is empty 1521 | if [[ -z $cmd ]]; then 1522 | return 0 1523 | fi 1524 | 1525 | # Check if keys includes the count 1526 | if [[ ! $count =~ ^[0-9]+$ ]]; then 1527 | count=1 1528 | fi 1529 | 1530 | zvm_repeat_command "$cmd" $count 1531 | local exit_code=$? 1532 | 1533 | if [[ $exit_code == 0 ]]; then 1534 | retval=$keys 1535 | fi 1536 | 1537 | return $exit_code 1538 | } 1539 | 1540 | # Handle a range of characters 1541 | function zvm_range_handler() { 1542 | local keys=$1 1543 | local cursor=$CURSOR 1544 | local key= 1545 | local mode= 1546 | local cmds=($ZVM_MODE) 1547 | local count=1 1548 | local exit_code=0 1549 | 1550 | # Enter operator pending mode 1551 | zvm_enter_oppend_mode false 1552 | 1553 | # If the keys is less than 2 keys, we should read more 1554 | # keys (e.g. d, c, y, etc.) 1555 | while (( ${#keys} < 2 )); do 1556 | zvm_update_cursor 1557 | read -k 1 key 1558 | keys="${keys}${key}" 1559 | done 1560 | 1561 | # If the keys ends in numbers, we should read more 1562 | # keys (e.g. d2, c3, y10, etc.) 1563 | while [[ ${keys: 1} =~ ^[1-9][0-9]*$ ]]; do 1564 | zvm_update_cursor 1565 | read -k 1 key 1566 | keys="${keys}${key}" 1567 | done 1568 | 1569 | # If the 2nd character is `i` or `a`, we should read 1570 | # one more key 1571 | if [[ ${keys} =~ '^.[ia]$' ]]; then 1572 | zvm_update_cursor 1573 | read -k 1 key 1574 | keys="${keys}${key}" 1575 | fi 1576 | 1577 | # Exit operator pending mode 1578 | zvm_exit_oppend_mode 1579 | 1580 | # Handle escape in operator pending mode 1581 | # escape non-printed characters (e.g. ^[) 1582 | if [[ $(zvm_escape_non_printed_characters "$keys") =~ 1583 | ${ZVM_VI_OPPEND_ESCAPE_BINDKEY/\^\[/\\^\\[} ]]; then 1584 | return 1 1585 | fi 1586 | 1587 | # Enter visual mode or visual line mode 1588 | if [[ $ZVM_MODE != $ZVM_MODE_VISUAL && 1589 | $ZVM_MODE != $ZVM_MODE_VISUAL_LINE ]]; then 1590 | case "${keys}" in 1591 | [cdy][jk]) mode=$ZVM_MODE_VISUAL_LINE;; 1592 | cc|dd|yy) mode=$ZVM_MODE_VISUAL_LINE;; 1593 | *) mode=$ZVM_MODE_VISUAL;; 1594 | esac 1595 | # Select the mode 1596 | if [[ ! -z $mode ]]; then 1597 | zvm_select_vi_mode $mode false 1598 | fi 1599 | fi 1600 | 1601 | ####################################### 1602 | # Selection Cases: 1603 | # 1604 | # 1. SAMPLE: `word1 word2 w`, CURSOR: at `w` of `word1` 1605 | # 1606 | # c[we] -> `word1` 1607 | # c2[we] -> `word1 word2` 1608 | # ve -> `word1` 1609 | # v2e -> `word1 word2` 1610 | # vw -> `word1 w` 1611 | # v2w -> `word1 word2 w` 1612 | # [dy]e -> `word1` 1613 | # [dy]2e -> `word1 word2` 1614 | # [dy]w -> `word1 ` 1615 | # [dy]2w -> `word1 word2 ` 1616 | # [cdyv]iw -> `word1` 1617 | # [cdyv]aw -> `word1 ` 1618 | # [cdyv]2iw -> `word1 ` 1619 | # [cdyv]2aw -> `word1 word2 ` 1620 | # 1621 | # 2. SAMPLE: `a bb c dd`, CURSOR: at `a` 1622 | # 1623 | # cw -> `a` 1624 | # c2w -> `a bb` 1625 | # ce -> `a bb` 1626 | # c2e -> `a bb c` 1627 | # 1628 | # 3. SAMPLE: ` .foo. bar. baz.`, CURSOR: at `f` 1629 | # 1630 | # c[WE] -> `foo.` 1631 | # c2[WE] -> `foo. bar.` 1632 | # vE -> `foo.` 1633 | # v2E -> `foo. bar.` 1634 | # vW -> `foo. b` 1635 | # v2W -> `foo. bar. b` 1636 | # d2W -> `foo. bar. b` 1637 | # [dy]E -> `foo.` 1638 | # [dy]2E -> `foo. bar.` 1639 | # [dy]W -> `foo. ` 1640 | # [dy]2W -> `foo. bar. ` 1641 | # [cdyv]iW -> `.foo.` 1642 | # [cdyv]aW -> `.foo. ` 1643 | # [cdyv]2iW -> `.foo. ` 1644 | # [cdyv]2aW -> `.foo. bar. ` 1645 | # 1646 | # 4. SAMPLE: ` .foo.bar.baz.`, CURSOR: at `r` 1647 | # 1648 | # [cdy]b -> `ba` 1649 | # [cdy]B -> `.foo.ba` 1650 | # vb -> `bar` 1651 | # vB -> `.foo.bar` 1652 | # vFf -> `foo.bar` 1653 | # vTf -> `oo.bar` 1654 | # [cdyv]fz -> `r.baz` 1655 | # [cdy]Ff -> `foo.ba` 1656 | # [cdyv]tz -> `r.ba` 1657 | # [cdy]Tf -> `oo.ba` 1658 | # 1659 | 1660 | # Pre navigation handling 1661 | local navkey= 1662 | 1663 | if [[ $keys =~ '^c([1-9][0-9]*)?[ia][wW]$' ]]; then 1664 | count=${match[1]:-1} 1665 | navkey=${keys: -2} 1666 | elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?[ia][eE]$' ]]; then 1667 | navkey= 1668 | elif [[ $keys =~ '^c([1-9][0-9]*)?[eEwW]$' ]]; then 1669 | count=${match[1]:-1} 1670 | navkey=c${keys: -1} 1671 | elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?[bB]$' ]]; then 1672 | MARK=$((MARK-1)) 1673 | count=${match[1]:-1} 1674 | navkey=${keys: -1} 1675 | elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?([FT].?)$' ]]; then 1676 | MARK=$((MARK-1)) 1677 | count=${match[1]:-1} 1678 | navkey=${match[2]} 1679 | elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?j$' ]]; then 1680 | # Exit if there is no line below 1681 | count=${match[1]:-1} 1682 | for ((i=$((CURSOR+1)); i<=$#BUFFER; i++)); do 1683 | [[ ${BUFFER[$i]} == $'\n' ]] && navkey='j' 1684 | done 1685 | elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?k$' ]]; then 1686 | # Exit if there is no line above 1687 | count=${match[1]:-1} 1688 | for ((i=$((CURSOR+1)); i>0; i--)); do 1689 | [[ ${BUFFER[$i]} == $'\n' ]] && navkey='k' 1690 | done 1691 | elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?[\^h0]$' ]]; then 1692 | MARK=$((MARK-1)) 1693 | count=${match[1]:-1} 1694 | navkey=${keys: -1} 1695 | 1696 | # Exit if the cursor is at the beginning of a line 1697 | if ((MARK < 0)); then 1698 | navkey= 1699 | elif [[ ${BUFFER[$MARK+1]} == $'\n' ]]; then 1700 | navkey= 1701 | fi 1702 | elif [[ $keys =~ '^[cdy]([1-9][0-9]*)?l$' ]]; then 1703 | count=${match[1]:-1} 1704 | count=$((count-1)) 1705 | navkey=${count}l 1706 | elif [[ $keys =~ '^.([1-9][0-9]*)?([^0-9]+)$' ]]; then 1707 | count=${match[1]:-1} 1708 | navkey=${match[2]} 1709 | else 1710 | navkey= 1711 | fi 1712 | 1713 | # Handle navigation 1714 | case $navkey in 1715 | '') exit_code=1;; 1716 | *[ia]?) 1717 | # At least 1 time 1718 | if [[ -z $count ]]; then 1719 | count=1 1720 | fi 1721 | 1722 | # Retrieve the widget 1723 | cmd= 1724 | case ${navkey: -2} in 1725 | iw) cmd=(zle select-in-word);; 1726 | aw) cmd=(zle select-a-word);; 1727 | iW) cmd=(zle select-in-blank-word);; 1728 | aW) cmd=(zle select-a-blank-word);; 1729 | esac 1730 | 1731 | if [[ -n "$cmd" ]]; then 1732 | zvm_repeat_command "$cmd" $count 1733 | elif [[ -n "$(zvm_match_surround "${keys[-1]}")" ]]; then 1734 | ZVM_KEYS="${keys}" 1735 | exit_code=2 1736 | elif [[ "${keys[1]}" == 'v' ]]; then 1737 | exit_code=1 1738 | else 1739 | exit_code=3 1740 | fi 1741 | ;; 1742 | c[eEwW]) 1743 | ####################################### 1744 | # Selection Cases: 1745 | # 1746 | # 1. SAMPLE: `word1 word2 w`, CURSOR: at `1` of `word1` 1747 | # 1748 | # c[weWE] -> `1` 1749 | # c2[weWE] -> `1 word2` 1750 | # 1751 | # 2. SAMPLE: `word1 word2 w`, CURSOR: at ` ` after `word1` 1752 | # 1753 | # cw -> ` ` 1754 | # c2w -> ` word2 ` 1755 | # ce -> ` word2` 1756 | # c2e -> ` word2 w` 1757 | # 1758 | 1759 | if [[ "${BUFFER[$((CURSOR + 1))]}" == ' ' ]]; then 1760 | case ${navkey: -1} in 1761 | w) cmd=(zle vi-forward-word);; 1762 | W) cmd=(zle vi-forward-blank-word);; 1763 | e) cmd=(zle vi-forward-word-end);; 1764 | E) cmd=(zle vi-forward-blank-word-end);; 1765 | esac 1766 | 1767 | zvm_repeat_command "$cmd" $count 1768 | 1769 | case ${navkey: -1} in 1770 | w|W) CURSOR=$((CURSOR-1));; 1771 | esac 1772 | else 1773 | if [[ "${BUFFER[$((CURSOR + 2))]}" == ' ' ]]; then 1774 | count=$((count - 1)) 1775 | fi 1776 | 1777 | case ${navkey: -1} in 1778 | e|w) cmd=(zle vi-forward-word-end);; 1779 | E|W) cmd=(zle vi-forward-blank-word-end);; 1780 | esac 1781 | 1782 | zvm_repeat_command "$cmd" $count 1783 | fi 1784 | ;; 1785 | *) 1786 | local retval= 1787 | 1788 | # Prevent some actions(e.g. w, e) from affecting the auto 1789 | # suggestion suffix 1790 | BUFFER+=$'\0' 1791 | 1792 | if zvm_navigation_handler "${count}${navkey}"; then 1793 | keys="${keys[1]}${retval}" 1794 | else 1795 | exit_code=1 1796 | fi 1797 | 1798 | BUFFER[-1]='' 1799 | ;; 1800 | esac 1801 | 1802 | # Check if there is no range selected 1803 | # For the exit code: 1804 | # 1) Loop in visual mode 1805 | # 2) Loop by ZVM_KEYS 1806 | # 3) Exit loop 1807 | if [[ $exit_code != 0 ]]; then 1808 | return $exit_code 1809 | fi 1810 | 1811 | # Post navigation handling 1812 | if [[ $keys =~ '^[cdy]([1-9][0-9]*)?[ia][wW]$' ]]; then 1813 | cursor=$MARK 1814 | elif [[ $keys =~ '[dy]([1-9][0-9]*)?[wW]' ]]; then 1815 | CURSOR=$((CURSOR-1)) 1816 | # If the CURSOR is at the newline character, we should 1817 | # move backward a character 1818 | if [[ "${BUFFER:$CURSOR:1}" == $'\n' ]]; then 1819 | CURSOR=$((CURSOR-1)) 1820 | fi 1821 | else 1822 | cursor=$CURSOR 1823 | fi 1824 | 1825 | # Handle operation 1826 | case "${keys}" in 1827 | c*) zvm_vi_change false; cursor=;; 1828 | d*) zvm_vi_delete false; cursor=;; 1829 | y*) zvm_vi_yank false; cursor=;; 1830 | [vV]*) cursor=;; 1831 | esac 1832 | 1833 | # Reset the repeat commands when it's changing or deleting 1834 | if $ZVM_REPEAT_MODE; then 1835 | zvm_exit_visual_mode false 1836 | elif [[ $keys =~ '^[cd].*' ]]; then 1837 | cmds+=($keys) 1838 | zvm_reset_repeat_commands $cmds 1839 | fi 1840 | 1841 | # Change the cursor position if the cursor is not null 1842 | if [[ ! -z $cursor ]]; then 1843 | CURSOR=$cursor 1844 | fi 1845 | } 1846 | 1847 | # Repeat executing command 1848 | function zvm_repeat_command { 1849 | local cmd=$1 1850 | local count=${2:-1} 1851 | 1852 | # check if it's a zle command 1853 | local is_zle_cmd=false 1854 | if [[ ${cmd} =~ '^zle .*' ]]; then 1855 | is_zle_cmd=true 1856 | fi 1857 | 1858 | # Execute the command for `count` times. We can not use 1859 | # variable `i`, since some widgets will affect the variable 1860 | # `i`, and it will cause an infinite loop. 1861 | local init_cursor=$CURSOR 1862 | local last_cursor=$CURSOR 1863 | local exit_code=0 1864 | for ((c=0; c<count; c++)); do 1865 | eval $cmd 1866 | 1867 | exit_code=$? 1868 | 1869 | if $is_zle_cmd; then 1870 | exit_code=0 1871 | elif [[ $exit_code != 0 ]]; then 1872 | CURSOR=$init_cursor 1873 | break 1874 | fi 1875 | 1876 | # If the cursor position is no change, we can break 1877 | # the loop and no need to loop so many times, thus 1878 | # when the count is quite large, it will not be 1879 | # stuck for a long time. 1880 | [[ $last_cursor == $CURSOR ]] && break 1881 | 1882 | last_cursor=$CURSOR 1883 | done 1884 | 1885 | return $exit_code 1886 | } 1887 | 1888 | # Edit command line in EDITOR 1889 | function zvm_vi_edit_command_line() { 1890 | # Create a temporary file and save the BUFFER to it 1891 | local tmp_file=$(mktemp ${ZVM_TMPDIR}/zshXXXXXX) 1892 | 1893 | # Some users may config the noclobber option to prevent from 1894 | # overwriting existing files with the > operator, we should 1895 | # use >! operator to ignore the noclobber. 1896 | echo "$BUFFER" >! "$tmp_file" 1897 | 1898 | # Edit the file with the specific editor, in case of 1899 | # the warning about input not from a terminal (e.g. 1900 | # vim), we should tell the editor input is from the 1901 | # terminal and not from standard input. 1902 | "${(@Q)${(z)${ZVM_VI_EDITOR}}}" $tmp_file </dev/tty 1903 | 1904 | # Reload the content to the BUFFER from the temporary 1905 | # file after editing, and delete the temporary file. 1906 | BUFFER=$(cat $tmp_file) 1907 | rm "$tmp_file" 1908 | 1909 | # Exit the visual mode 1910 | case $ZVM_MODE in 1911 | $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE) 1912 | zvm_exit_visual_mode 1913 | ;; 1914 | esac 1915 | } 1916 | 1917 | # Get the substr position in a string 1918 | function zvm_substr_pos() { 1919 | local pos=-1 1920 | local len=${#1} 1921 | local slen=${#2} 1922 | local i=${3:-0} 1923 | local forward=${4:-true} 1924 | local init=${i:-$($forward && echo "$i" || echo "i=$len-1")} 1925 | local condition=$($forward && echo "i<$len" || echo "i>=0") 1926 | local step=$($forward && echo 'i++' || echo 'i--') 1927 | for (($init;$condition;$step)); do 1928 | if [[ ${1:$i:$slen} == "$2" ]]; then 1929 | pos=$i 1930 | break 1931 | fi 1932 | done 1933 | echo $pos 1934 | } 1935 | 1936 | # Parse surround from keys 1937 | function zvm_parse_surround_keys() { 1938 | local keys=${1:-${$(zvm_keys)//$ZVM_ESCAPE_SPACE/ }} 1939 | local action= 1940 | local surround= 1941 | case "${keys}" in 1942 | vS*) action=S; surround=${keys:2};; 1943 | vsa*) action=a; surround=${keys:3};; 1944 | vys*) action=y; surround=${keys:3};; 1945 | s[dr]*) action=${keys:1:1}; surround=${keys:2};; 1946 | [acd]s*) action=${keys:0:1}; surround=${keys:2};; 1947 | [cdvy][ia]*) action=${keys:0:2}; surround=${keys:2};; 1948 | esac 1949 | echo $action ${surround// /$ZVM_ESCAPE_SPACE} 1950 | } 1951 | 1952 | # Move around code structure (e.g. (..), {..}) 1953 | function zvm_move_around_surround() { 1954 | local slen= 1955 | local bpos=-1 1956 | local epos=-1 1957 | for ((i=$CURSOR;i>=0;i--)); do 1958 | # Check if it's one of the surrounds 1959 | for s in {\',\",\`,\(,\[,\{,\<}; do 1960 | slen=${#s} 1961 | if [[ ${BUFFER:$i:$slen} == "$s" ]]; then 1962 | bpos=$i 1963 | break 1964 | fi 1965 | done 1966 | if (($bpos == -1)); then 1967 | continue 1968 | fi 1969 | # Search the nearest surround 1970 | local ret=($(zvm_search_surround "$s")) 1971 | if [[ -z ${ret[@]} ]]; then 1972 | continue 1973 | fi 1974 | bpos=${ret[1]} 1975 | epos=${ret[2]} 1976 | # Move between the openning and close surrounds 1977 | if (( $CURSOR > $((bpos-1)) )) && (( $CURSOR < $((bpos+slen)) )); then 1978 | CURSOR=$epos 1979 | else 1980 | CURSOR=$bpos 1981 | fi 1982 | break 1983 | done 1984 | } 1985 | 1986 | # Match the surround pair from the part 1987 | function zvm_match_surround() { 1988 | local bchar=${1// /$ZVM_ESCAPE_SPACE} 1989 | local echar=$bchar 1990 | case $bchar in 1991 | '(') echar=')';; 1992 | '[') echar=']';; 1993 | '{') echar='}';; 1994 | '<') echar='>';; 1995 | ')') bchar='(';echar=')';; 1996 | ']') bchar='[';echar=']';; 1997 | '}') bchar='{';echar='}';; 1998 | '>') bchar='<';echar='>';; 1999 | "'") ;; 2000 | '"') ;; 2001 | '`') ;; 2002 | *) return;; 2003 | esac 2004 | echo $bchar $echar 2005 | } 2006 | 2007 | # Search surround from the string 2008 | function zvm_search_surround() { 2009 | local ret=($(zvm_match_surround "$1")) 2010 | local bchar=${${ret[1]//$ZVM_ESCAPE_SPACE/ }:- } 2011 | local echar=${${ret[2]//$ZVM_ESCAPE_SPACE/ }:- } 2012 | local bpos=$(zvm_substr_pos $BUFFER $bchar $CURSOR false) 2013 | local epos=$(zvm_substr_pos $BUFFER $echar $CURSOR true) 2014 | if [[ $bpos == $epos ]]; then 2015 | epos=$(zvm_substr_pos $BUFFER $echar $((CURSOR+1)) true) 2016 | if [[ $epos == -1 ]]; then 2017 | epos=$(zvm_substr_pos $BUFFER $echar $((CURSOR-1)) false) 2018 | if [[ $epos != -1 ]]; then 2019 | local tmp=$epos; epos=$bpos; bpos=$tmp 2020 | fi 2021 | fi 2022 | fi 2023 | if [[ $bpos == -1 ]] || [[ $epos == -1 ]]; then 2024 | return 2025 | fi 2026 | echo $bpos $epos $bchar $echar 2027 | } 2028 | 2029 | # Select surround and highlight it in visual mode 2030 | function zvm_select_surround() { 2031 | local ret=($(zvm_parse_surround_keys)) 2032 | local action=${1:-${ret[1]}} 2033 | local surround=${2:-${ret[2]//$ZVM_ESCAPE_SPACE/ }} 2034 | ret=($(zvm_search_surround ${surround})) 2035 | if [[ ${#ret[@]} == 0 ]]; then 2036 | zvm_exit_visual_mode 2037 | return 2038 | fi 2039 | local bpos=${ret[1]} 2040 | local epos=${ret[2]} 2041 | if [[ ${action:1:1} == 'i' ]]; then 2042 | ((bpos++)) 2043 | else 2044 | ((epos++)) 2045 | fi 2046 | MARK=$bpos; CURSOR=$epos-1 2047 | 2048 | # refresh for highlight redraw 2049 | zle redisplay 2050 | } 2051 | 2052 | # Change surround in vicmd or visual mode 2053 | function zvm_change_surround() { 2054 | local ret=($(zvm_parse_surround_keys)) 2055 | local action=${1:-${ret[1]}} 2056 | local surround=${2:-${ret[2]//$ZVM_ESCAPE_SPACE/ }} 2057 | local bpos=${3} epos=${4} 2058 | local is_appending=false 2059 | case $action in 2060 | S|y|a) is_appending=true;; 2061 | esac 2062 | if $is_appending; then 2063 | if [[ -z $bpos && -z $epos ]]; then 2064 | ret=($(zvm_selection)) 2065 | bpos=${ret[1]} epos=${ret[2]} 2066 | fi 2067 | else 2068 | ret=($(zvm_search_surround "$surround")) 2069 | (( ${#ret[@]} )) || return 2070 | bpos=${ret[1]} epos=${ret[2]} 2071 | zvm_highlight custom $bpos $(($bpos+1)) 2072 | zvm_highlight custom $epos $(($epos+1)) 2073 | fi 2074 | local key= 2075 | case $action in 2076 | c|r) 2077 | zvm_enter_oppend_mode 2078 | read -k 1 key 2079 | zvm_exit_oppend_mode 2080 | ;; 2081 | S|y|a) 2082 | if [[ -z $surround ]]; then 2083 | zvm_enter_oppend_mode 2084 | read -k 1 key 2085 | zvm_exit_oppend_mode 2086 | else 2087 | key=$surround 2088 | fi 2089 | if [[ $ZVM_MODE == $ZVM_MODE_VISUAL ]]; then 2090 | zle visual-mode 2091 | fi 2092 | ;; 2093 | esac 2094 | 2095 | # Check if it is ESCAPE key (<ESC> or ZVM_VI_ESCAPE_BINDKEY) 2096 | case "$key" in 2097 | $'\e'|"${ZVM_VI_ESCAPE_BINDKEY//\^\[/$'\e'}") 2098 | zvm_highlight clear 2099 | return 2100 | esac 2101 | 2102 | # Start changing surround 2103 | ret=($(zvm_match_surround "$key")) 2104 | local bchar=${${ret[1]//$ZVM_ESCAPE_SPACE/ }:-$key} 2105 | local echar=${${ret[2]//$ZVM_ESCAPE_SPACE/ }:-$key} 2106 | local value=$($is_appending && echo 0 || echo 1 ) 2107 | local head=${BUFFER:0:$bpos} 2108 | local body=${BUFFER:$((bpos+value)):$((epos-(bpos+value)))} 2109 | local foot=${BUFFER:$((epos+value))} 2110 | BUFFER="${head}${bchar}${body}${echar}${foot}" 2111 | 2112 | # Clear highliht 2113 | zvm_highlight clear 2114 | 2115 | case $action in 2116 | S|y|a) zvm_select_vi_mode $ZVM_MODE_NORMAL;; 2117 | esac 2118 | } 2119 | 2120 | # Change surround text object 2121 | function zvm_change_surround_text_object() { 2122 | local ret=($(zvm_parse_surround_keys)) 2123 | local action=${1:-${ret[1]}} 2124 | local surround=${2:-${ret[2]//$ZVM_ESCAPE_SPACE/ }} 2125 | ret=($(zvm_search_surround "${surround}")) 2126 | if [[ ${#ret[@]} == 0 ]]; then 2127 | zvm_select_vi_mode $ZVM_MODE_NORMAL 2128 | return 2129 | fi 2130 | local bpos=${ret[1]} 2131 | local epos=${ret[2]} 2132 | if [[ ${action:1:1} == 'i' ]]; then 2133 | ((bpos++)) 2134 | else 2135 | ((epos++)) 2136 | fi 2137 | CUTBUFFER=${BUFFER:$bpos:$(($epos-$bpos))} 2138 | case ${action:0:1} in 2139 | c) 2140 | BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}" 2141 | CURSOR=$bpos 2142 | zvm_select_vi_mode $ZVM_MODE_INSERT 2143 | ;; 2144 | d) 2145 | BUFFER="${BUFFER:0:$bpos}${BUFFER:$epos}" 2146 | CURSOR=$bpos 2147 | ;; 2148 | esac 2149 | } 2150 | 2151 | # Repeat last change 2152 | function zvm_repeat_change() { 2153 | ZVM_REPEAT_MODE=true 2154 | ZVM_RESET_PROMPT_DISABLED=true 2155 | 2156 | local cmd=${ZVM_REPEAT_COMMANDS[2]} 2157 | 2158 | # Handle repeat command 2159 | case $cmd in 2160 | [aioAIO]) zvm_repeat_insert;; 2161 | c) zvm_repeat_vi_change;; 2162 | [cd]*) zvm_repeat_range_change;; 2163 | R) zvm_repeat_replace;; 2164 | r) zvm_repeat_replace_chars;; 2165 | *) zle vi-repeat-change;; 2166 | esac 2167 | 2168 | zle redisplay 2169 | 2170 | ZVM_RESET_PROMPT_DISABLED=false 2171 | ZVM_REPEAT_MODE=false 2172 | } 2173 | 2174 | # Repeat inserting characters 2175 | function zvm_repeat_insert() { 2176 | local cmd=${ZVM_REPEAT_COMMANDS[2]} 2177 | local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]}) 2178 | 2179 | # Pre-handle the command 2180 | case $cmd in 2181 | a) CURSOR+=1;; 2182 | o) 2183 | zle vi-backward-char 2184 | zle vi-end-of-line 2185 | LBUFFER+=$'\n' 2186 | ;; 2187 | A) 2188 | zle vi-end-of-line 2189 | CURSOR=$((CURSOR+1)) 2190 | ;; 2191 | I) zle vi-first-non-blank;; 2192 | O) 2193 | zle vi-digit-or-beginning-of-line 2194 | LBUFFER+=$'\n' 2195 | CURSOR=$((CURSOR-1)) 2196 | ;; 2197 | esac 2198 | 2199 | # Insert characters 2200 | for ((i=1; i<=${#cmds[@]}; i++)); do 2201 | cmd="${cmds[$i]}" 2202 | 2203 | # Hanlde the backspace command 2204 | if [[ $cmd == '' ]]; then 2205 | if (($#LBUFFER > 0)); then 2206 | LBUFFER=${LBUFFER:0:-1} 2207 | fi 2208 | continue 2209 | fi 2210 | 2211 | # The length of character should be 1 2212 | if (($#cmd == 1)); then 2213 | LBUFFER+=$cmd 2214 | fi 2215 | done 2216 | } 2217 | 2218 | # Repeat changing visual characters 2219 | function zvm_repeat_vi_change() { 2220 | local mode=${ZVM_REPEAT_COMMANDS[1]} 2221 | local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]}) 2222 | 2223 | # Backward move cursor to the beginning of line 2224 | if [[ $mode == $ZVM_MODE_VISUAL_LINE ]]; then 2225 | zle vi-digit-or-beginning-of-line 2226 | fi 2227 | 2228 | local ncount=${cmds[1]} 2229 | local ccount=${cmds[2]} 2230 | local pos=$CURSOR epos=$CURSOR 2231 | 2232 | # Forward expand the characters to the Nth newline character 2233 | for ((i=0; i<$ncount; i++)); do 2234 | pos=$(zvm_substr_pos $BUFFER $'\n' $pos) 2235 | if [[ $pos == -1 ]]; then 2236 | epos=$#BUFFER 2237 | break 2238 | fi 2239 | pos=$((pos+1)) 2240 | epos=$pos 2241 | done 2242 | 2243 | # Forward expand the remaining characters 2244 | for ((i=0; i<$ccount; i++)); do 2245 | local char=${BUFFER[$epos+i]} 2246 | if [[ $char == $'\n' || $char == '' ]]; then 2247 | ccount=$i 2248 | break 2249 | fi 2250 | done 2251 | 2252 | epos=$((epos+ccount)) 2253 | RBUFFER=${RBUFFER:$((epos-CURSOR))} 2254 | } 2255 | 2256 | # Repeat changing a range of characters 2257 | function zvm_repeat_range_change() { 2258 | local cmd=${ZVM_REPEAT_COMMANDS[2]} 2259 | 2260 | # Remove characters 2261 | zvm_range_handler $cmd 2262 | 2263 | # Insert characters 2264 | zvm_repeat_insert 2265 | } 2266 | 2267 | # Repeat replacing 2268 | function zvm_repeat_replace() { 2269 | local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]}) 2270 | local cmd= 2271 | local cursor=$CURSOR 2272 | 2273 | for ((i=1; i<=${#cmds[@]}; i++)); do 2274 | cmd="${cmds[$i]}" 2275 | 2276 | # If the cmd or the character at cursor is a newline character, 2277 | # or the cursor is at the end of buffer, we should insert the 2278 | # cmd instead of replacing with the cmd. 2279 | if [[ $cmd == $'\n' || 2280 | $BUFFER[$cursor+1] == $'\n' || 2281 | $BUFFER[$cursor+1] == '' 2282 | ]]; then 2283 | LBUFFER+=$cmd 2284 | else 2285 | BUFFER[$cursor+1]=$cmd 2286 | fi 2287 | 2288 | cursor=$((cursor+1)) 2289 | CURSOR=$cursor 2290 | done 2291 | 2292 | # The cursor position should go back one character after 2293 | # exiting the replace mode 2294 | zle vi-backward-char 2295 | } 2296 | 2297 | # Repeat replacing characters 2298 | function zvm_repeat_replace_chars() { 2299 | local mode=${ZVM_REPEAT_COMMANDS[1]} 2300 | local cmds=(${ZVM_REPEAT_COMMANDS[3,-1]}) 2301 | local cmd= 2302 | 2303 | # Replacment of visual mode should move backward cursor to the 2304 | # begin of current line, and replacing to the end of last line. 2305 | if [[ $mode == $ZVM_MODE_VISUAL_LINE ]]; then 2306 | zle vi-digit-or-beginning-of-line 2307 | cmds+=($'\n') 2308 | fi 2309 | 2310 | local cursor=$((CURSOR+1)) 2311 | 2312 | for ((i=1; i<=${#cmds[@]}; i++)); do 2313 | cmd="${cmds[$i]}" 2314 | 2315 | # If we meet a newline character in the buffer, we should keep 2316 | # stop replacing, util we meet next newline character command. 2317 | if [[ ${BUFFER[$cursor]} == $'\n' ]]; then 2318 | if [[ $cmd == $'\n' ]]; then 2319 | cursor=$((cursor+1)) 2320 | fi 2321 | continue 2322 | fi 2323 | 2324 | # A newline character command should keep replacing with last 2325 | # character, until we meet a newline character in the buffer, 2326 | # then we use next command. 2327 | if [[ $cmd == $'\n' ]]; then 2328 | i=$((i-1)) 2329 | cmd="${cmds[$i]}" 2330 | fi 2331 | 2332 | # The length of character should be 1 2333 | if (($#cmd == 1)); then 2334 | BUFFER[$cursor]="${cmd}" 2335 | fi 2336 | 2337 | cursor=$((cursor+1)) 2338 | 2339 | # Break when it reaches the end 2340 | if ((cursor > $#BUFFER)); then 2341 | break 2342 | fi 2343 | done 2344 | } 2345 | 2346 | # Select a word under the cursor 2347 | function zvm_select_in_word() { 2348 | local cursor=${1:-$CURSOR} 2349 | local buffer=${2:-$BUFFER} 2350 | local bpos=$cursor epos=$cursor 2351 | local pattern='[0-9a-zA-Z_]' 2352 | 2353 | if ! [[ "${buffer:$cursor:1}" =~ $pattern ]]; then 2354 | pattern="[^${pattern:1:-1} ]" 2355 | fi 2356 | 2357 | for ((; $bpos>=0; bpos--)); do 2358 | [[ "${buffer:$bpos:1}" =~ $pattern ]] || break 2359 | done 2360 | for ((; $epos<$#buffer; epos++)); do 2361 | [[ "${buffer:$epos:1}" =~ $pattern ]] || break 2362 | done 2363 | 2364 | bpos=$((bpos+1)) 2365 | 2366 | # The ending position must be greater than 0 2367 | if (( epos > 0 )); then 2368 | epos=$((epos-1)) 2369 | fi 2370 | 2371 | echo $bpos $epos 2372 | } 2373 | 2374 | # Switch keyword 2375 | function zvm_switch_keyword() { 2376 | local bpos= epos= cpos=$CURSOR 2377 | 2378 | # Cursor position cases: 2379 | # 2380 | # 1. Cursor on symbol: 2381 | # 2+2 => + 2382 | # 2-2 => - 2383 | # 2 + 2 => + 2384 | # 2 +2 => +2 2385 | # 2 -2 => -2 2386 | # 2 -a => -a 2387 | # 2388 | # 2. Cursor on number or alpha: 2389 | # 2+2 => +2 2390 | # 2-2 => -2 2391 | # 2 + 2 => 2 2392 | # 2 +2 => +2 2393 | # 2 -2 => -2 2394 | # 2 -a => -a 2395 | 2396 | # If cursor is on the `+` or `-`, we need to check if it is a 2397 | # number with a sign or an operator, only the number needs to 2398 | # forward the cursor. 2399 | if [[ ${BUFFER:$cpos:2} =~ [+-][0-9] ]]; then 2400 | if [[ $cpos == 0 || ${BUFFER:$((cpos-1)):1} =~ [^0-9] ]]; then 2401 | cpos=$((cpos+1)) 2402 | fi 2403 | 2404 | # If cursor is on the `+` or `-`, we need to check if it is a 2405 | # short option, only the short option needs to forward the cursor. 2406 | elif [[ ${BUFFER:$cpos:2} =~ [+-][a-zA-Z] ]]; then 2407 | if [[ $cpos == 0 || ${BUFFER:$((cpos-1)):1} == ' ' ]]; then 2408 | cpos=$((cpos+1)) 2409 | fi 2410 | fi 2411 | 2412 | local result=($(zvm_select_in_word $cpos)) 2413 | bpos=${result[1]} epos=$((${result[2]}+1)) 2414 | 2415 | # Move backward the cursor 2416 | if [[ $bpos != 0 && ${BUFFER:$((bpos-1)):1} == [+-] ]]; then 2417 | bpos=$((bpos-1)) 2418 | fi 2419 | 2420 | local word=${BUFFER:$bpos:$((epos-bpos))} 2421 | local keys=$(zvm_keys) 2422 | 2423 | if [[ $keys == '' ]]; then 2424 | local increase=true 2425 | else 2426 | local increase=false 2427 | fi 2428 | 2429 | # Execute extra commands 2430 | for handler in $zvm_switch_keyword_handlers; do 2431 | if ! zvm_exist_command ${handler}; then 2432 | continue 2433 | fi 2434 | 2435 | result=($($handler $word $increase)); 2436 | 2437 | if (( $#result == 0 )); then 2438 | continue 2439 | fi 2440 | 2441 | epos=$(( bpos + ${result[3]} )) 2442 | bpos=$(( bpos + ${result[2]} )) 2443 | 2444 | if (( cpos < bpos )) || (( cpos >= epos )); then 2445 | continue 2446 | fi 2447 | 2448 | # Save to history and only keep some recent records 2449 | zvm_switch_keyword_history+=("${handler}:${word}") 2450 | zvm_switch_keyword_history=("${zvm_switch_keyword_history[@]: -10}") 2451 | 2452 | BUFFER="${BUFFER:0:$bpos}${result[1]}${BUFFER:$epos}" 2453 | CURSOR=$((bpos + ${#result[1]} - 1)) 2454 | 2455 | zle reset-prompt 2456 | return 2457 | done 2458 | } 2459 | 2460 | # Switch number keyword 2461 | function zvm_switch_number { 2462 | local word=$1 2463 | local increase=${2:-true} 2464 | local result= bpos= epos= 2465 | 2466 | # Hexadecimal 2467 | if [[ $word =~ [^0-9]?(0[xX][0-9a-fA-F]*) ]]; then 2468 | local number=${match[1]} 2469 | local prefix=${number:0:2} 2470 | bpos=$((mbegin-1)) epos=$mend 2471 | 2472 | # Hexadecimal cases: 2473 | # 2474 | # 1. Increment: 2475 | # 0xDe => 0xdf 2476 | # 0xdE => 0xDF 2477 | # 0xde0 => 0xddf 2478 | # 0xffffffffffffffff => 0x0000000000000000 2479 | # 0X9 => 0XA 2480 | # 0Xdf => 0Xe0 2481 | # 2482 | # 2. Decrement: 2483 | # 0xdE0 => 0xDDF 2484 | # 0xffFf0 => 0xfffef 2485 | # 0xfffF0 => 0xFFFEF 2486 | # 0x0 => 0xffffffffffffffff 2487 | # 0X0 => 0XFFFFFFFFFFFFFFFF 2488 | # 0Xf => 0Xe 2489 | 2490 | local lower=true 2491 | if [[ $number =~ [A-Z][0-9]*$ ]]; then 2492 | lower=false 2493 | fi 2494 | 2495 | # Fix the number truncated after 15 digits issue 2496 | if (( $#number > 17 )); then 2497 | local d=$(($#number - 15)) 2498 | local h=${number:0:$d} 2499 | number="0x${number:$d}" 2500 | fi 2501 | 2502 | local p=$(($#number - 2)) 2503 | 2504 | if $increase; then 2505 | if (( $number == 0x${(l:15::f:)} )); then 2506 | h=$(([##16]$h+1)) 2507 | h=${h: -1} 2508 | number=${(l:15::0:)} 2509 | else 2510 | h=${h:2} 2511 | number=$(([##16]$number + 1)) 2512 | fi 2513 | else 2514 | if (( $number == 0 )); then 2515 | if (( ${h:-0} == 0 )); then 2516 | h=f 2517 | else 2518 | h=$(([##16]$h-1)) 2519 | h=${h: -1} 2520 | fi 2521 | number=${(l:15::f:)} 2522 | else 2523 | h=${h:2} 2524 | number=$(([##16]$number - 1)) 2525 | fi 2526 | fi 2527 | 2528 | # Padding with zero 2529 | if (( $#number < $p )); then 2530 | number=${(l:$p::0:)number} 2531 | fi 2532 | 2533 | result="${h}${number}" 2534 | 2535 | # Transform the case 2536 | if $lower; then 2537 | result="${(L)result}" 2538 | fi 2539 | 2540 | result="${prefix}${result}" 2541 | 2542 | # Binary 2543 | elif [[ $word =~ [^0-9]?(0[bB][01]*) ]]; then 2544 | # Binary cases: 2545 | # 2546 | # 1. Increment: 2547 | # 0b1 => 0b10 2548 | # 0x1111111111111111111111111111111111111111111111111111111111111111 => 2549 | # 0x0000000000000000000000000000000000000000000000000000000000000000 2550 | # 0B0 => 0B1 2551 | # 2552 | # 2. Decrement: 2553 | # 0b1 => 0b0 2554 | # 0b100 => 0b011 2555 | # 0B010 => 0B001 2556 | # 0b0 => 2557 | # 0x1111111111111111111111111111111111111111111111111111111111111111 2558 | 2559 | local number=${match[1]} 2560 | local prefix=${number:0:2} 2561 | bpos=$((mbegin-1)) epos=$mend 2562 | 2563 | # Fix the number truncated after 63 digits issue 2564 | if (( $#number > 65 )); then 2565 | local d=$(($#number - 63)) 2566 | local h=${number:0:$d} 2567 | number="0b${number:$d}" 2568 | fi 2569 | 2570 | local p=$(($#number - 2)) 2571 | 2572 | if $increase; then 2573 | if (( $number == 0b${(l:63::1:)} )); then 2574 | h=$(([##2]$h+1)) 2575 | h=${h: -1} 2576 | number=${(l:63::0:)} 2577 | else 2578 | h=${h:2} 2579 | number=$(([##2]$number + 1)) 2580 | fi 2581 | else 2582 | if (( $number == 0b0 )); then 2583 | if (( ${h:-0} == 0 )); then 2584 | h=1 2585 | else 2586 | h=$(([##2]$h-1)) 2587 | h=${h: -1} 2588 | fi 2589 | number=${(l:63::1:)} 2590 | else 2591 | h=${h:2} 2592 | number=$(([##2]$number - 1)) 2593 | fi 2594 | fi 2595 | 2596 | # Padding with zero 2597 | if (( $#number < $p )); then 2598 | number=${(l:$p::0:)number} 2599 | fi 2600 | 2601 | result="${prefix}${number}" 2602 | 2603 | # Decimal 2604 | elif [[ $word =~ ([-+]?[0-9]+) ]]; then 2605 | # Decimal cases: 2606 | # 2607 | # 1. Increment: 2608 | # 0 => 1 2609 | # 99 => 100 2610 | # 2611 | # 2. Decrement: 2612 | # 0 => -1 2613 | # 10 => 9 2614 | # aa1230xa => aa1231xa 2615 | # aa1230bb => aa1231bb 2616 | # aa123a0bb => aa124a0bb 2617 | 2618 | local number=${match[1]} 2619 | bpos=$((mbegin-1)) epos=$mend 2620 | 2621 | if $increase; then 2622 | result=$(($number + 1)) 2623 | else 2624 | result=$(($number - 1)) 2625 | fi 2626 | 2627 | # Check if need the plus sign prefix 2628 | if [[ ${word:$bpos:1} == '+' ]]; then 2629 | result="+${result}" 2630 | fi 2631 | fi 2632 | 2633 | if [[ $result ]]; then 2634 | echo $result $bpos $epos 2635 | fi 2636 | } 2637 | 2638 | # Switch boolean keyword 2639 | function zvm_switch_boolean() { 2640 | local word=$1 2641 | local increase=$2 2642 | local result= 2643 | local bpos=0 epos=$#word 2644 | 2645 | # Remove option prefix 2646 | if [[ $word =~ (^[+-]{0,2}) ]]; then 2647 | local prefix=${match[1]} 2648 | bpos=$mend 2649 | word=${word:$bpos} 2650 | fi 2651 | 2652 | case ${(L)word} in 2653 | true) result=false;; 2654 | false) result=true;; 2655 | yes) result=no;; 2656 | no) result=yes;; 2657 | on) result=off;; 2658 | off) result=on;; 2659 | y) result=n;; 2660 | n) result=y;; 2661 | t) result=f;; 2662 | f) result=t;; 2663 | *) return;; 2664 | esac 2665 | 2666 | # Transform the case 2667 | if [[ $word =~ ^[A-Z]+$ ]]; then 2668 | result=${(U)result} 2669 | elif [[ $word =~ ^[A-Z] ]]; then 2670 | result=${(U)result:0:1}${result:1} 2671 | fi 2672 | 2673 | echo $result $bpos $epos 2674 | } 2675 | 2676 | # Switch weekday keyword 2677 | function zvm_switch_weekday() { 2678 | local word=$1 2679 | local increase=$2 2680 | local result=${(L)word} 2681 | local weekdays=( 2682 | sunday 2683 | monday 2684 | tuesday 2685 | wednesday 2686 | thursday 2687 | friday 2688 | saturday 2689 | ) 2690 | 2691 | local i=1 2692 | 2693 | for ((; i<=${#weekdays[@]}; i++)); do 2694 | if [[ ${weekdays[i]:0:$#result} == ${result} ]]; then 2695 | result=${weekdays[i]} 2696 | break 2697 | fi 2698 | done 2699 | 2700 | # Return if no match 2701 | if (( i > ${#weekdays[@]} )); then 2702 | return 2703 | fi 2704 | 2705 | if $increase; then 2706 | if (( i == ${#weekdays[@]} )); then 2707 | i=1 2708 | else 2709 | i=$((i+1)) 2710 | fi 2711 | else 2712 | if (( i == 1 )); then 2713 | i=${#weekdays[@]} 2714 | else 2715 | i=$((i-1)) 2716 | fi 2717 | fi 2718 | 2719 | # Abbreviation 2720 | if (( $#result == $#word )); then 2721 | result=${weekdays[i]} 2722 | else 2723 | result=${weekdays[i]:0:$#word} 2724 | fi 2725 | 2726 | # Transform the case 2727 | if [[ $word =~ ^[A-Z]+$ ]]; then 2728 | result=${(U)result} 2729 | elif [[ $word =~ ^[A-Z] ]]; then 2730 | result=${(U)result:0:1}${result:1} 2731 | fi 2732 | 2733 | echo $result 0 $#word 2734 | } 2735 | 2736 | # Switch operator keyword 2737 | function zvm_switch_operator() { 2738 | local word=$1 2739 | local increase=$2 2740 | local result= 2741 | 2742 | case ${(L)word} in 2743 | '&&') result='||';; 2744 | '||') result='&&';; 2745 | '++') result='--';; 2746 | '--') result='++';; 2747 | '==') result='!=';; 2748 | '!=') result='==';; 2749 | '===') result='!==';; 2750 | '!==') result='===';; 2751 | '+') result='-';; 2752 | '-') result='*';; 2753 | '*') result='/';; 2754 | '/') result='+';; 2755 | 'and') result='or';; 2756 | 'or') result='and';; 2757 | *) return;; 2758 | esac 2759 | 2760 | # Transform the case 2761 | if [[ $word =~ ^[A-Z]+$ ]]; then 2762 | result=${(U)result} 2763 | elif [[ $word =~ ^[A-Z] ]]; then 2764 | result=${(U)result:0:1}${result:1} 2765 | fi 2766 | 2767 | # Since the `echo` command can not print the character 2768 | # `-`, here we use `printf` command alternatively. 2769 | printf "%s 0 $#word" "${result}" 2770 | } 2771 | 2772 | # Switch month keyword 2773 | function zvm_switch_month() { 2774 | local word=$1 2775 | local increase=$2 2776 | local result=${(L)word} 2777 | local months=( 2778 | january 2779 | february 2780 | march 2781 | april 2782 | may 2783 | june 2784 | july 2785 | august 2786 | september 2787 | october 2788 | november 2789 | december 2790 | ) 2791 | 2792 | local i=1 2793 | 2794 | for ((; i<=${#months[@]}; i++)); do 2795 | if [[ ${months[i]:0:$#result} == ${result} ]]; then 2796 | result=${months[i]} 2797 | break 2798 | fi 2799 | done 2800 | 2801 | # Return if no match 2802 | if (( i > ${#months[@]} )); then 2803 | return 2804 | fi 2805 | 2806 | if $increase; then 2807 | if (( i == ${#months[@]} )); then 2808 | i=1 2809 | else 2810 | i=$((i+1)) 2811 | fi 2812 | else 2813 | if (( i == 1 )); then 2814 | i=${#months[@]} 2815 | else 2816 | i=$((i-1)) 2817 | fi 2818 | fi 2819 | 2820 | ##################### 2821 | # Abbreviation 2822 | local lastlen=0 2823 | local last="${zvm_switch_keyword_history[-1]}" 2824 | local funcmark="${funcstack[1]}:" 2825 | if [[ "$last" =~ "^${funcmark}" ]]; then 2826 | lastlen=$(($#last - $#funcmark)) 2827 | fi 2828 | 2829 | # Use cases: 2830 | # 2831 | # May -> June 2832 | # Apr -> May -> Jun 2833 | # April -> May -> June 2834 | # January -> Feb(Munual changing) -> Mar 2835 | # Jan -> February(Munual changing) -> March 2836 | # 2837 | if [[ "$result" == "may" ]]; then 2838 | if (($lastlen == 3)); then 2839 | result=${months[i]:0:3} 2840 | else 2841 | result=${months[i]} 2842 | fi 2843 | else 2844 | if (($#word == 3)); then 2845 | result=${months[i]:0:3} 2846 | else 2847 | result=${months[i]} 2848 | fi 2849 | fi 2850 | 2851 | ##################### 2852 | # Transform the case 2853 | if [[ $word =~ ^[A-Z]+$ ]]; then 2854 | result=${(U)result} 2855 | elif [[ $word =~ ^[A-Z] ]]; then 2856 | result=${(U)result:0:1}${result:1} 2857 | fi 2858 | 2859 | echo $result 0 $#word 2860 | } 2861 | 2862 | # Highlight content 2863 | function zvm_highlight() { 2864 | local opt=${1:-mode} 2865 | local region=() 2866 | local redraw=false 2867 | 2868 | # Hanlde region by the option 2869 | case "$opt" in 2870 | mode) 2871 | case "$ZVM_MODE" in 2872 | $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE) 2873 | local ret=($(zvm_calc_selection)) 2874 | local bpos=$((ret[1])) epos=$((ret[2])) 2875 | local bg=$ZVM_VI_HIGHLIGHT_BACKGROUND 2876 | local fg=$ZVM_VI_HIGHLIGHT_FOREGROUND 2877 | local es=$ZVM_VI_HIGHLIGHT_EXTRASTYLE 2878 | region=("$bpos $epos fg=$fg,bg=$bg,$es") 2879 | ;; 2880 | esac 2881 | redraw=true 2882 | ;; 2883 | custom) 2884 | local bpos=$2 epos=$3 2885 | local bg=${4:-$ZVM_VI_HIGHLIGHT_BACKGROUND} 2886 | local fg=${5:-$ZVM_VI_HIGHLIGHT_FOREGROUND} 2887 | local es=${6:-$ZVM_VI_HIGHLIGHT_EXTRASTYLE} 2888 | region=("${ZVM_REGION_HIGHLIGHT[@]}") 2889 | region+=("$bpos $epos fg=$fg,bg=$bg,$es") 2890 | redraw=true 2891 | ;; 2892 | clear) 2893 | zle redisplay 2894 | redraw=true 2895 | ;; 2896 | redraw) redraw=true;; 2897 | esac 2898 | 2899 | # Update region highlight 2900 | if (( $#region > 0 )) || [[ "$opt" == 'clear' ]]; then 2901 | 2902 | # Remove old region highlight 2903 | local rawhighlight=() 2904 | for ((i=1; i<=${#region_highlight[@]}; i++)); do 2905 | local raw=true 2906 | local spl=(${(@s/ /)region_highlight[i]}) 2907 | local pat="${spl[1]} ${spl[2]}" 2908 | for ((j=1; j<=${#ZVM_REGION_HIGHLIGHT[@]}; j++)); do 2909 | if [[ "$pat" == "${ZVM_REGION_HIGHLIGHT[j]:0:$#pat}" ]]; then 2910 | raw=false 2911 | break 2912 | fi 2913 | done 2914 | if $raw; then 2915 | rawhighlight+=("${region_highlight[i]}") 2916 | fi 2917 | done 2918 | 2919 | # Assign new region highlight 2920 | ZVM_REGION_HIGHLIGHT=("${region[@]}") 2921 | region_highlight=("${rawhighlight[@]}" "${ZVM_REGION_HIGHLIGHT[@]}") 2922 | fi 2923 | 2924 | # Check if we need to refresh the region highlight 2925 | if $redraw; then 2926 | zle -R 2927 | fi 2928 | } 2929 | 2930 | # Enter the visual mode 2931 | function zvm_enter_visual_mode() { 2932 | local mode= 2933 | local last_mode=$ZVM_MODE 2934 | local last_region= 2935 | 2936 | # Exit the visual mode 2937 | case $last_mode in 2938 | $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE) 2939 | last_region=($MARK $CURSOR) 2940 | zvm_exit_visual_mode 2941 | ;; 2942 | esac 2943 | 2944 | case "${1:-$(zvm_keys)}" in 2945 | v) mode=$ZVM_MODE_VISUAL;; 2946 | V) mode=$ZVM_MODE_VISUAL_LINE;; 2947 | *) mode=$last_mode;; 2948 | esac 2949 | 2950 | # We should just exit the visual mdoe if current mode 2951 | # is the same with last visual mode 2952 | if [[ $last_mode == $mode ]]; then 2953 | return 2954 | fi 2955 | 2956 | zvm_select_vi_mode $mode 2957 | 2958 | # Recover the region when changing to another visual mode 2959 | if [[ -n $last_region ]]; then 2960 | MARK=$last_region[1] 2961 | CURSOR=$last_region[2] 2962 | zle redisplay 2963 | fi 2964 | } 2965 | 2966 | # Exit the visual mode 2967 | function zvm_exit_visual_mode() { 2968 | case "$ZVM_MODE" in 2969 | $ZVM_MODE_VISUAL) zle visual-mode;; 2970 | $ZVM_MODE_VISUAL_LINE) zle visual-line-mode;; 2971 | esac 2972 | zvm_highlight clear 2973 | zvm_select_vi_mode $ZVM_MODE_NORMAL ${1:-true} 2974 | } 2975 | 2976 | # Enter the vi insert mode 2977 | function zvm_enter_insert_mode() { 2978 | local keys=${1:-$(zvm_keys)} 2979 | 2980 | if [[ $keys == 'i' ]]; then 2981 | ZVM_INSERT_MODE='i' 2982 | elif [[ $keys == 'a' ]]; then 2983 | ZVM_INSERT_MODE='a' 2984 | if ! zvm_is_empty_line; then 2985 | CURSOR=$((CURSOR+1)) 2986 | fi 2987 | fi 2988 | 2989 | zvm_reset_repeat_commands $ZVM_MODE_NORMAL $ZVM_INSERT_MODE 2990 | zvm_select_vi_mode $ZVM_MODE_INSERT 2991 | } 2992 | 2993 | # Exit the vi insert mode 2994 | function zvm_exit_insert_mode() { 2995 | ZVM_INSERT_MODE= 2996 | zvm_select_vi_mode $ZVM_MODE_NORMAL ${1:-true} 2997 | } 2998 | 2999 | # Enter the vi operator pending mode 3000 | function zvm_enter_oppend_mode() { 3001 | ZVM_OPPEND_MODE=true 3002 | ${1:-true} && zvm_update_cursor 3003 | } 3004 | 3005 | # Exit the vi operator pending mode 3006 | function zvm_exit_oppend_mode() { 3007 | ZVM_OPPEND_MODE=false 3008 | ${1:-true} && zvm_update_cursor 3009 | } 3010 | 3011 | # Insert at the beginning of the line 3012 | function zvm_insert_bol() { 3013 | ZVM_INSERT_MODE='I' 3014 | zle vi-first-non-blank 3015 | zvm_select_vi_mode $ZVM_MODE_INSERT 3016 | zvm_reset_repeat_commands $ZVM_MODE_NORMAL $ZVM_INSERT_MODE 3017 | } 3018 | 3019 | # Append at the end of the line 3020 | function zvm_append_eol() { 3021 | ZVM_INSERT_MODE='A' 3022 | zle vi-end-of-line 3023 | CURSOR=$((CURSOR+1)) 3024 | zvm_select_vi_mode $ZVM_MODE_INSERT 3025 | zvm_reset_repeat_commands $ZVM_MODE_NORMAL $ZVM_INSERT_MODE 3026 | } 3027 | 3028 | # Self insert content to cursor position 3029 | function zvm_self_insert() { 3030 | local keys=${1:-$KEYS} 3031 | 3032 | # Update the autosuggestion 3033 | if [[ ${POSTDISPLAY:0:$#keys} == $keys ]]; then 3034 | POSTDISPLAY=${POSTDISPLAY:$#keys} 3035 | else 3036 | POSTDISPLAY= 3037 | fi 3038 | 3039 | LBUFFER+=${keys} 3040 | } 3041 | 3042 | # Reset the repeat commands 3043 | function zvm_reset_repeat_commands() { 3044 | ZVM_REPEAT_RESET=true 3045 | ZVM_REPEAT_COMMANDS=($@) 3046 | } 3047 | 3048 | # Select vi mode 3049 | function zvm_select_vi_mode() { 3050 | local mode=$1 3051 | local reset_prompt=${2:-true} 3052 | 3053 | # Check if current mode is the same with the new mode 3054 | if [[ $mode == "$ZVM_MODE" ]]; then 3055 | zvm_update_cursor 3056 | return 3057 | fi 3058 | 3059 | zvm_exec_commands 'before_select_vi_mode' 3060 | 3061 | # Some plugins would reset the prompt when we select the 3062 | # keymap, so here we postpone executing reset-prompt. 3063 | zvm_postpone_reset_prompt true 3064 | 3065 | # Exit operator pending mode 3066 | if $ZVM_OPPEND_MODE; then 3067 | zvm_exit_oppend_mode false 3068 | fi 3069 | 3070 | case $mode in 3071 | $ZVM_MODE_NORMAL) 3072 | ZVM_MODE=$ZVM_MODE_NORMAL 3073 | zvm_update_cursor 3074 | zle vi-cmd-mode 3075 | ;; 3076 | $ZVM_MODE_INSERT) 3077 | ZVM_MODE=$ZVM_MODE_INSERT 3078 | zvm_update_cursor 3079 | zle vi-insert 3080 | ;; 3081 | $ZVM_MODE_VISUAL) 3082 | ZVM_MODE=$ZVM_MODE_VISUAL 3083 | zvm_update_cursor 3084 | zle visual-mode 3085 | ;; 3086 | $ZVM_MODE_VISUAL_LINE) 3087 | ZVM_MODE=$ZVM_MODE_VISUAL_LINE 3088 | zvm_update_cursor 3089 | zle visual-line-mode 3090 | ;; 3091 | $ZVM_MODE_REPLACE) 3092 | ZVM_MODE=$ZVM_MODE_REPLACE 3093 | zvm_enter_oppend_mode 3094 | ;; 3095 | esac 3096 | 3097 | # This aspect provides you a moment to do something, such as 3098 | # update the cursor, prompt and so on. 3099 | zvm_exec_commands 'after_select_vi_mode' 3100 | 3101 | # Stop and trigger reset-prompt 3102 | $reset_prompt && zvm_postpone_reset_prompt false true 3103 | 3104 | # Start the lazy keybindings when the first time entering the 3105 | # normal mode, when the mode is the same as last mode, we get 3106 | # empty value for $mode. 3107 | if [[ $mode == $ZVM_MODE_NORMAL ]] && 3108 | (( $#ZVM_LAZY_KEYBINDINGS_LIST > 0 )); then 3109 | 3110 | zvm_exec_commands 'before_lazy_keybindings' 3111 | 3112 | # Here we should unset the list for normal keybindings 3113 | local list=("${ZVM_LAZY_KEYBINDINGS_LIST[@]}") 3114 | unset ZVM_LAZY_KEYBINDINGS_LIST 3115 | 3116 | for r in "${list[@]}"; do 3117 | eval "zvm_bindkey ${r}" 3118 | done 3119 | 3120 | zvm_exec_commands 'after_lazy_keybindings' 3121 | fi 3122 | } 3123 | 3124 | # Postpone reset prompt 3125 | function zvm_postpone_reset_prompt() { 3126 | local toggle=$1 3127 | local force=${2:-false} 3128 | 3129 | if $force; then 3130 | ZVM_POSTPONE_RESET_PROMPT=1 3131 | fi 3132 | 3133 | if $toggle; then 3134 | ZVM_POSTPONE_RESET_PROMPT=0 3135 | else 3136 | if (($ZVM_POSTPONE_RESET_PROMPT > 0)); then 3137 | ZVM_POSTPONE_RESET_PROMPT=-1 3138 | zle reset-prompt 3139 | else 3140 | ZVM_POSTPONE_RESET_PROMPT=-1 3141 | fi 3142 | fi 3143 | } 3144 | 3145 | # Reset prompt 3146 | function zvm_reset_prompt() { 3147 | # Return if postponing is enabled 3148 | if (($ZVM_POSTPONE_RESET_PROMPT >= 0)); then 3149 | ZVM_POSTPONE_RESET_PROMPT=$(($ZVM_POSTPONE_RESET_PROMPT + 1)) 3150 | return 3151 | fi 3152 | 3153 | # Return if reset prompt is disabled 3154 | if [[ $ZVM_RESET_PROMPT_DISABLED == true ]]; then 3155 | return 3156 | fi 3157 | 3158 | local -i retval 3159 | if [[ -z "$rawfunc" ]]; then 3160 | zle .reset-prompt -- "$@" 3161 | else 3162 | $rawfunc -- "$@" 3163 | fi 3164 | 3165 | return retval 3166 | } 3167 | 3168 | # Undo action in vi insert mode 3169 | # 3170 | # CTRL-U Remove all characters between the cursor position and 3171 | # the beginning of the line. Previous versions of vim 3172 | # deleted all characters on the line. 3173 | function zvm_viins_undo() { 3174 | if [[ $ZVM_VI_INS_LEGACY_UNDO ]]; then 3175 | zvm_kill_line 3176 | else 3177 | zvm_backward_kill_line 3178 | fi 3179 | } 3180 | 3181 | function zvm_set_cursor() { 3182 | # Term of vim isn't supported 3183 | if [[ -n $VIMRUNTIME ]]; then 3184 | return 3185 | fi 3186 | 3187 | echo -ne "$1" 3188 | } 3189 | 3190 | # Get the escape sequence of cursor style 3191 | function zvm_cursor_style() { 3192 | local style=${(L)1} 3193 | local term=${2:-$ZVM_TERM} 3194 | 3195 | case $term in 3196 | # For xterm and rxvt and their derivatives use the same escape 3197 | # sequences as the VT520 terminal. And screen, konsole, alacritty, 3198 | # st and foot implement a superset of VT100 and VT100, they support 3199 | # 256 colors the same way xterm does. 3200 | xterm*|rxvt*|screen*|tmux*|konsole*|alacritty*|st*|foot*|wezterm) 3201 | case $style in 3202 | $ZVM_CURSOR_BLOCK) style='\e[2 q';; 3203 | $ZVM_CURSOR_UNDERLINE) style='\e[4 q';; 3204 | $ZVM_CURSOR_BEAM) style='\e[6 q';; 3205 | $ZVM_CURSOR_BLINKING_BLOCK) style='\e[1 q';; 3206 | $ZVM_CURSOR_BLINKING_UNDERLINE) style='\e[3 q';; 3207 | $ZVM_CURSOR_BLINKING_BEAM) style='\e[5 q';; 3208 | $ZVM_CURSOR_USER_DEFAULT) style='\e[0 q';; 3209 | esac 3210 | ;; 3211 | *) style='\e[0 q';; 3212 | esac 3213 | 3214 | # Restore default cursor color 3215 | if [[ $style == '\e[0 q' ]]; then 3216 | local old_style= 3217 | 3218 | case $ZVM_MODE in 3219 | $ZVM_MODE_INSERT) old_style=$ZVM_INSERT_MODE_CURSOR;; 3220 | $ZVM_MODE_NORMAL) old_style=$ZVM_NORMAL_MODE_CURSOR;; 3221 | $ZVM_MODE_OPPEND) old_style=$ZVM_OPPEND_MODE_CURSOR;; 3222 | esac 3223 | 3224 | if [[ $old_style =~ '\e\][0-9]+;.+\a' ]]; then 3225 | style=$style'\e\e]112\a' 3226 | fi 3227 | fi 3228 | 3229 | echo $style 3230 | } 3231 | 3232 | # Update the cursor according current vi mode 3233 | function zvm_update_cursor() { 3234 | 3235 | # Check if we need to update the cursor style 3236 | $ZVM_CURSOR_STYLE_ENABLED || return 3237 | 3238 | local mode=$1 3239 | local shape= 3240 | 3241 | # Check if it is operator pending mode 3242 | if $ZVM_OPPEND_MODE; then 3243 | mode=opp 3244 | shape=$(zvm_cursor_style $ZVM_OPPEND_MODE_CURSOR) 3245 | fi 3246 | 3247 | # Get cursor shape by the mode 3248 | case "${mode:-$ZVM_MODE}" in 3249 | $ZVM_MODE_NORMAL) 3250 | shape=$(zvm_cursor_style $ZVM_NORMAL_MODE_CURSOR) 3251 | ;; 3252 | $ZVM_MODE_INSERT) 3253 | shape=$(zvm_cursor_style $ZVM_INSERT_MODE_CURSOR) 3254 | ;; 3255 | $ZVM_MODE_VISUAL) 3256 | shape=$(zvm_cursor_style $ZVM_VISUAL_MODE_CURSOR) 3257 | ;; 3258 | $ZVM_MODE_VISUAL_LINE) 3259 | shape=$(zvm_cursor_style $ZVM_VISUAL_LINE_MODE_CURSOR) 3260 | ;; 3261 | esac 3262 | 3263 | if [[ $shape ]]; then 3264 | zvm_set_cursor $shape 3265 | fi 3266 | } 3267 | 3268 | # Updates highlight region 3269 | function zvm_update_highlight() { 3270 | case "$ZVM_MODE" in 3271 | $ZVM_MODE_VISUAL|$ZVM_MODE_VISUAL_LINE) 3272 | zvm_highlight 3273 | ;; 3274 | esac 3275 | } 3276 | 3277 | # Updates repeat commands 3278 | function zvm_update_repeat_commands() { 3279 | # We don't need to update the repeat commands if current 3280 | # mode is already the repeat mode. 3281 | $ZVM_REPEAT_MODE && return 3282 | 3283 | # We don't need to update the repeat commands if it is 3284 | # reseting the repeat commands. 3285 | if $ZVM_REPEAT_RESET; then 3286 | ZVM_REPEAT_RESET=false 3287 | return 3288 | fi 3289 | 3290 | # We update the repeat commands when it's the insert mode 3291 | [[ $ZVM_MODE == $ZVM_MODE_INSERT ]] || return 3292 | 3293 | local char=$KEYS 3294 | 3295 | # If current key is an arrow key, we should do something 3296 | if [[ "$KEYS" =~ '\[[ABCD]' ]]; then 3297 | # If last key is also an arrow key, we just replace it 3298 | if [[ ${ZVM_REPEAT_COMMANDS[-1]} =~ '\[[ABCD]' ]]; then 3299 | ZVM_REPEAT_COMMANDS=(${ZVM_REPEAT_COMMANDS[@]:0:-1}) 3300 | fi 3301 | else 3302 | # If last command is arrow key movement, we should reset 3303 | # the repeat commands with i(nsert) command 3304 | if [[ ${ZVM_REPEAT_COMMANDS[-1]} =~ '\[[ABCD]' ]]; then 3305 | zvm_reset_repeat_commands $ZVM_MODE_NORMAL i 3306 | fi 3307 | char=${BUFFER[$CURSOR]} 3308 | fi 3309 | 3310 | # If current key is backspace key, we should remove last 3311 | # one, until it has only the mode and inital command 3312 | if [[ "$KEYS" == '' ]]; then 3313 | if ((${#ZVM_REPEAT_COMMANDS[@]} > 2)) && 3314 | [[ ${ZVM_REPEAT_COMMANDS[-1]} != '' ]]; then 3315 | ZVM_REPEAT_COMMANDS=(${ZVM_REPEAT_COMMANDS[@]:0:-1}) 3316 | elif (($#LBUFFER > 0)); then 3317 | ZVM_REPEAT_COMMANDS+=($KEYS) 3318 | fi 3319 | else 3320 | ZVM_REPEAT_COMMANDS+=($char) 3321 | fi 3322 | } 3323 | 3324 | # Updates editor information when line pre redraw 3325 | function zvm_zle-line-pre-redraw() { 3326 | # Fix cursor style is not updated in tmux environment, when 3327 | # there are one more panel in the same window, the program 3328 | # in other panel could change the cursor shape, we need to 3329 | # update cursor style when line is redrawing. 3330 | if [[ -n $TMUX ]]; then 3331 | zvm_update_cursor 3332 | # Fix display is not updated in the terminal of IntelliJ IDE. 3333 | [[ "$TERMINAL_EMULATOR" == "JetBrains-JediTerm" ]] && zle redisplay 3334 | fi 3335 | zvm_update_highlight 3336 | zvm_update_repeat_commands 3337 | } 3338 | 3339 | # Start every prompt in the correct vi mode 3340 | function zvm_zle-line-init() { 3341 | # Save last mode 3342 | local mode=${ZVM_MODE:-$ZVM_MODE_INSERT} 3343 | 3344 | # It's neccessary to set to insert mode when line init 3345 | # and we don't need to reset prompt. 3346 | zvm_select_vi_mode $ZVM_MODE_INSERT false 3347 | 3348 | # Select line init mode and reset prompt 3349 | case ${ZVM_LINE_INIT_MODE:-$mode} in 3350 | $ZVM_MODE_INSERT) zvm_select_vi_mode $ZVM_MODE_INSERT;; 3351 | *) zvm_select_vi_mode $ZVM_MODE_NORMAL;; 3352 | esac 3353 | } 3354 | 3355 | # Restore the user default cursor style after prompt finish 3356 | function zvm_zle-line-finish() { 3357 | # When we start a program (e.g. vim, bash, etc.) from the 3358 | # command line, the cursor style is inherited by other 3359 | # programs, so that we need to reset the cursor style to 3360 | # default before executing a command and set the custom 3361 | # style again when the command exits. This way makes any 3362 | # other interactive CLI application would not be affected 3363 | # by it. 3364 | local shape=$(zvm_cursor_style $ZVM_CURSOR_USER_DEFAULT) 3365 | zvm_set_cursor $shape 3366 | zvm_switch_keyword_history=() 3367 | } 3368 | 3369 | # Initialize vi-mode for widgets, keybindings, etc. 3370 | function zvm_init() { 3371 | # Check if it has been initalized 3372 | if $ZVM_INIT_DONE; then 3373 | return; 3374 | fi 3375 | 3376 | # Mark plugin initial status 3377 | ZVM_INIT_DONE=true 3378 | 3379 | zvm_exec_commands 'before_init' 3380 | 3381 | # Correct the readkey engine 3382 | case $ZVM_READKEY_ENGINE in 3383 | $ZVM_READKEY_ENGINE_NEX|$ZVM_READKEY_ENGINE_ZLE);; 3384 | *) 3385 | echo -n "Warning: Unsupported readkey engine! " 3386 | echo "ZVM_READKEY_ENGINE=$ZVM_READKEY_ENGINE" 3387 | ZVM_READKEY_ENGINE=$ZVM_READKEY_ENGINE_DEFAULT 3388 | ;; 3389 | esac 3390 | 3391 | # Reduce ESC delay (zle default is 0.4 seconds) 3392 | # Set to 0.01 second delay for taking over the key input processing 3393 | case $ZVM_READKEY_ENGINE in 3394 | $ZVM_READKEY_ENGINE_NEX) KEYTIMEOUT=1;; 3395 | $ZVM_READKEY_ENGINE_ZLE) KEYTIMEOUT=$(($ZVM_KEYTIMEOUT*100));; 3396 | esac 3397 | 3398 | # Create User-defined widgets 3399 | zvm_define_widget zvm_default_handler 3400 | zvm_define_widget zvm_readkeys_handler 3401 | zvm_define_widget zvm_backward_kill_region 3402 | zvm_define_widget zvm_backward_kill_line 3403 | zvm_define_widget zvm_forward_kill_line 3404 | zvm_define_widget zvm_kill_line 3405 | zvm_define_widget zvm_viins_undo 3406 | zvm_define_widget zvm_select_surround 3407 | zvm_define_widget zvm_change_surround 3408 | zvm_define_widget zvm_move_around_surround 3409 | zvm_define_widget zvm_change_surround_text_object 3410 | zvm_define_widget zvm_enter_insert_mode 3411 | zvm_define_widget zvm_exit_insert_mode 3412 | zvm_define_widget zvm_enter_visual_mode 3413 | zvm_define_widget zvm_exit_visual_mode 3414 | zvm_define_widget zvm_enter_oppend_mode 3415 | zvm_define_widget zvm_exit_oppend_mode 3416 | zvm_define_widget zvm_exchange_point_and_mark 3417 | zvm_define_widget zvm_open_line_below 3418 | zvm_define_widget zvm_open_line_above 3419 | zvm_define_widget zvm_insert_bol 3420 | zvm_define_widget zvm_append_eol 3421 | zvm_define_widget zvm_self_insert 3422 | zvm_define_widget zvm_vi_replace 3423 | zvm_define_widget zvm_vi_replace_chars 3424 | zvm_define_widget zvm_vi_substitute 3425 | zvm_define_widget zvm_vi_substitute_whole_line 3426 | zvm_define_widget zvm_vi_change 3427 | zvm_define_widget zvm_vi_change_eol 3428 | zvm_define_widget zvm_vi_delete 3429 | zvm_define_widget zvm_vi_yank 3430 | zvm_define_widget zvm_vi_put_after 3431 | zvm_define_widget zvm_vi_put_before 3432 | zvm_define_widget zvm_vi_replace_selection 3433 | zvm_define_widget zvm_vi_up_case 3434 | zvm_define_widget zvm_vi_down_case 3435 | zvm_define_widget zvm_vi_opp_case 3436 | zvm_define_widget zvm_vi_edit_command_line 3437 | zvm_define_widget zvm_repeat_change 3438 | zvm_define_widget zvm_switch_keyword 3439 | 3440 | # Override standard widgets 3441 | zvm_define_widget zle-line-pre-redraw zvm_zle-line-pre-redraw 3442 | 3443 | # Ensure the correct cursor style when an interactive program 3444 | # (e.g. vim, bash, etc.) starts and exits 3445 | zvm_define_widget zle-line-init zvm_zle-line-init 3446 | zvm_define_widget zle-line-finish zvm_zle-line-finish 3447 | 3448 | # Override reset-prompt widget 3449 | zvm_define_widget reset-prompt zvm_reset_prompt 3450 | 3451 | # All Key bindings 3452 | # Emacs-like bindings 3453 | # Normal editing 3454 | zvm_bindkey viins '^A' beginning-of-line 3455 | zvm_bindkey viins '^E' end-of-line 3456 | zvm_bindkey viins '^B' backward-char 3457 | zvm_bindkey viins '^F' forward-char 3458 | zvm_bindkey viins '^K' zvm_forward_kill_line 3459 | zvm_bindkey viins '^W' backward-kill-word 3460 | zvm_bindkey viins '^U' zvm_viins_undo 3461 | zvm_bindkey viins '^Y' yank 3462 | zvm_bindkey viins '^_' undo 3463 | 3464 | # Mode agnostic editing 3465 | zvm_bindkey viins '^[[H' beginning-of-line 3466 | zvm_bindkey vicmd '^[[H' beginning-of-line 3467 | zvm_bindkey viins '^[[F' end-of-line 3468 | zvm_bindkey vicmd '^[[F' end-of-line 3469 | zvm_bindkey viins '^[[3~' delete-char 3470 | zvm_bindkey vicmd '^[[3~' delete-char 3471 | 3472 | # History search 3473 | zvm_bindkey viins '^R' history-incremental-search-backward 3474 | zvm_bindkey viins '^S' history-incremental-search-forward 3475 | zvm_bindkey viins '^P' up-line-or-history 3476 | zvm_bindkey viins '^N' down-line-or-history 3477 | 3478 | # Insert mode 3479 | zvm_bindkey vicmd 'i' zvm_enter_insert_mode 3480 | zvm_bindkey vicmd 'a' zvm_enter_insert_mode 3481 | zvm_bindkey vicmd 'I' zvm_insert_bol 3482 | zvm_bindkey vicmd 'A' zvm_append_eol 3483 | 3484 | # Other key bindings 3485 | zvm_bindkey vicmd 'v' zvm_enter_visual_mode 3486 | zvm_bindkey vicmd 'V' zvm_enter_visual_mode 3487 | zvm_bindkey visual 'o' zvm_exchange_point_and_mark 3488 | zvm_bindkey vicmd 'o' zvm_open_line_below 3489 | zvm_bindkey vicmd 'O' zvm_open_line_above 3490 | zvm_bindkey vicmd 'r' zvm_vi_replace_chars 3491 | zvm_bindkey vicmd 'R' zvm_vi_replace 3492 | zvm_bindkey vicmd 's' zvm_vi_substitute 3493 | zvm_bindkey vicmd 'S' zvm_vi_substitute_whole_line 3494 | zvm_bindkey vicmd 'C' zvm_vi_change_eol 3495 | zvm_bindkey visual 'c' zvm_vi_change 3496 | zvm_bindkey visual 'd' zvm_vi_delete 3497 | zvm_bindkey visual 'x' zvm_vi_delete 3498 | zvm_bindkey visual 'y' zvm_vi_yank 3499 | zvm_bindkey vicmd 'p' zvm_vi_put_after 3500 | zvm_bindkey vicmd 'P' zvm_vi_put_before 3501 | zvm_bindkey visual 'p' zvm_vi_replace_selection 3502 | zvm_bindkey visual 'P' zvm_vi_replace_selection 3503 | zvm_bindkey visual 'U' zvm_vi_up_case 3504 | zvm_bindkey visual 'u' zvm_vi_down_case 3505 | zvm_bindkey visual '~' zvm_vi_opp_case 3506 | zvm_bindkey visual 'v' zvm_vi_edit_command_line 3507 | zvm_bindkey vicmd '.' zvm_repeat_change 3508 | 3509 | zvm_bindkey vicmd '^A' zvm_switch_keyword 3510 | zvm_bindkey vicmd '^X' zvm_switch_keyword 3511 | 3512 | # Keybindings for escape key and some specials 3513 | local exit_oppend_mode_widget= 3514 | local exit_insert_mode_widget= 3515 | local exit_visual_mode_widget= 3516 | local default_handler_widget= 3517 | 3518 | case $ZVM_READKEY_ENGINE in 3519 | $ZVM_READKEY_ENGINE_NEX) 3520 | exit_oppend_mode_widget=zvm_readkeys_handler 3521 | exit_insert_mode_widget=zvm_readkeys_handler 3522 | exit_visual_mode_widget=zvm_readkeys_handler 3523 | ;; 3524 | $ZVM_READKEY_ENGINE_ZLE) 3525 | exit_insert_mode_widget=zvm_exit_insert_mode 3526 | exit_visual_mode_widget=zvm_exit_visual_mode 3527 | default_handler_widget=zvm_default_handler 3528 | ;; 3529 | esac 3530 | 3531 | # Bind custom escape key 3532 | zvm_bindkey vicmd "$ZVM_VI_OPPEND_ESCAPE_BINDKEY" $exit_oppend_mode_widget 3533 | zvm_bindkey viins "$ZVM_VI_INSERT_ESCAPE_BINDKEY" $exit_insert_mode_widget 3534 | zvm_bindkey visual "$ZVM_VI_VISUAL_ESCAPE_BINDKEY" $exit_visual_mode_widget 3535 | 3536 | # Bind the default escape key if the escape key is not the default 3537 | case "$ZVM_VI_OPPEND_ESCAPE_BINDKEY" in 3538 | '^['|'\e') ;; 3539 | *) zvm_bindkey vicmd '^[' $exit_oppend_mode_widget;; 3540 | esac 3541 | case "$ZVM_VI_INSERT_ESCAPE_BINDKEY" in 3542 | '^['|'\e') ;; 3543 | *) zvm_bindkey viins '^[' $exit_insert_mode_widget;; 3544 | esac 3545 | case "$ZVM_VI_VISUAL_ESCAPE_BINDKEY" in 3546 | '^['|'\e') ;; 3547 | *) zvm_bindkey visual '^[' $exit_visual_mode_widget;; 3548 | esac 3549 | 3550 | # Bind and overwrite original y/d/c of vicmd 3551 | for c in {y,d,c}; do 3552 | zvm_bindkey vicmd "$c" $default_handler_widget 3553 | done 3554 | 3555 | # Surround text-object 3556 | # Enable surround text-objects (quotes, brackets) 3557 | local surrounds=() 3558 | 3559 | # Append brackets 3560 | for s in ${(s..)^:-'()[]{}<>'}; do 3561 | surrounds+=($s) 3562 | done 3563 | 3564 | # Append quotes 3565 | for s in {\',\",\`,\ ,'^['}; do 3566 | surrounds+=($s) 3567 | done 3568 | 3569 | # Append for escaping visual mode 3570 | if $is_custom_escape_key; then 3571 | surrounds+=("$ZVM_VI_ESCAPE_BINDKEY") 3572 | fi 3573 | 3574 | # Surround key bindings 3575 | for s in $surrounds; do 3576 | for c in {a,i}${s}; do 3577 | zvm_bindkey visual "$c" zvm_select_surround 3578 | done 3579 | for c in {c,d,y}{a,i}${s}; do 3580 | zvm_bindkey vicmd "$c" zvm_change_surround_text_object 3581 | done 3582 | if [[ $ZVM_VI_SURROUND_BINDKEY == 's-prefix' ]]; then 3583 | for c in s{d,r}${s}; do 3584 | zvm_bindkey vicmd "$c" zvm_change_surround 3585 | done 3586 | for c in sa${s}; do 3587 | zvm_bindkey visual "$c" zvm_change_surround 3588 | done 3589 | else 3590 | for c in {d,c}s${s}; do 3591 | zvm_bindkey vicmd "$c" zvm_change_surround 3592 | done 3593 | for c in {S,ys}${s}; do 3594 | zvm_bindkey visual "$c" zvm_change_surround 3595 | done 3596 | fi 3597 | done 3598 | 3599 | # Moving around surrounds 3600 | zvm_bindkey vicmd '%' zvm_move_around_surround 3601 | 3602 | # Fix BACKSPACE was stuck in zsh 3603 | # Since normally '^?' (backspace) is bound to vi-backward-delete-char 3604 | zvm_bindkey viins '^?' backward-delete-char 3605 | 3606 | # Initialize ZVM_MODE value 3607 | case ${ZVM_LINE_INIT_MODE:-$ZVM_MODE_INSERT} in 3608 | $ZVM_MODE_INSERT) ZVM_MODE=$ZVM_MODE_INSERT;; 3609 | *) ZVM_MODE=$ZVM_MODE_NORMAL;; 3610 | esac 3611 | 3612 | # Enable vi keymap 3613 | bindkey -v 3614 | 3615 | zvm_exec_commands 'after_init' 3616 | } 3617 | 3618 | # Check if a command is existed 3619 | function zvm_exist_command() { 3620 | command -v "$1" >/dev/null 3621 | } 3622 | 3623 | # Execute commands 3624 | function zvm_exec_commands() { 3625 | local commands="zvm_${1}_commands" 3626 | commands=(${(P)commands}) 3627 | 3628 | # Execute the default command 3629 | if zvm_exist_command "zvm_$1"; then 3630 | eval "zvm_$1" ${@:2} 3631 | fi 3632 | 3633 | # Execute extra commands 3634 | for cmd in $commands; do 3635 | if zvm_exist_command ${cmd}; then 3636 | cmd="$cmd ${@:2}" 3637 | fi 3638 | eval $cmd 3639 | done 3640 | } 3641 | 3642 | # Generate system report 3643 | function zvm_system_report() { 3644 | # OS 3645 | local os_info= 3646 | case "$(uname -s)" in 3647 | Darwin) 3648 | local product="$(sw_vers -productName)" 3649 | local version="$(sw_vers -productVersion) ($(sw_vers -buildVersion))" 3650 | os_info="${product} ${version}" 3651 | ;; 3652 | *) os_info="$(uname -s) ($(uname -r) $(uname -v) $(uname -m) $(uname -o))";; 3653 | esac 3654 | 3655 | # Terminal Program 3656 | local term_info="${TERM_PROGRAM:-unknown} ${TERM_PROGRAM_VERSION:-unknown}" 3657 | term_info="${term_info} (${TERM})" 3658 | 3659 | # ZSH Frameworks 3660 | local zsh_frameworks=() 3661 | 3662 | if zvm_exist_command "omz"; then 3663 | zsh_framworks+=("oh-my-zsh $(omz version)") 3664 | fi 3665 | 3666 | if zvm_exist_command "starship"; then 3667 | zsh_framworks+=("$(starship --version | head -n 1)") 3668 | fi 3669 | 3670 | if zvm_exist_command "antigen"; then 3671 | zsh_framworks+=("$(antigen version | head -n 1)") 3672 | fi 3673 | 3674 | if zvm_exist_command "zplug"; then 3675 | zsh_framworks+=("zplug $(zplug --version | head -n 1)") 3676 | fi 3677 | 3678 | if zvm_exist_command "zinit"; then 3679 | # As `zinit version` information includes term style, in order 3680 | # to acquire the pure text, we need to elimindate all the escape 3681 | # sequences. 3682 | local version=$(zinit version \ 3683 | | head -n 1 \ 3684 | | sed -E $'s/(\033\[[a-zA-Z0-9;]+ ?m)//g') 3685 | zsh_framworks+=("${version}") 3686 | fi 3687 | 3688 | # Shell 3689 | local shell=$SHELL 3690 | if [[ -z $shell ]]; then 3691 | shell=zsh 3692 | fi 3693 | 3694 | ################# 3695 | # System Report 3696 | ################# 3697 | print - "- Terminal program: ${term_info}" 3698 | print - "- Operating system: ${os_info}" 3699 | print - "- ZSH framework: ${(j:, :)zsh_framworks}" 3700 | print - "- ZSH version: $($shell --version)" 3701 | print - "- ZVM version: $(zvm_version | head -n 1)" 3702 | } 3703 | 3704 | # Load config by calling the config function 3705 | if zvm_exist_command "$ZVM_CONFIG_FUNC"; then 3706 | $ZVM_CONFIG_FUNC 3707 | fi 3708 | 3709 | # Initialize this plugin according to the mode 3710 | case $ZVM_INIT_MODE in 3711 | sourcing) zvm_init;; 3712 | *) precmd_functions+=(zvm_init);; 3713 | esac 3714 | 3715 | --------------------------------------------------------------------------------