├── .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 |
10 |
11 |
14 |
15 |
16 |
17 | ## General information
18 |
19 |
22 |
23 |
32 |
33 | ## Basic examination
34 |
35 |
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
40 |
41 | ## Problem description
42 |
43 |
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 |
2 |
3 |
4 |
5 |
💻 A better and friendly vi(vim) mode plugin for ZSH.
6 |
7 |
⚒️ Zsh Vi Mode ⚒️
8 |
9 |
10 |
11 |
12 |
13 |
14 | ZSH
plugin for Agnosticism.
15 |
16 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
60 |
61 |
66 |
67 |
68 |
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 |
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 | [](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
632 | zvm_define_widget [your_custom_function]
633 | ```
634 |
635 | To define a keybinding, you should:
636 |
637 | ```zsh
638 | zvm_bindkey
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 ^[ => ), 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 ^[ => )
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 | # -> ` ` 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 == '' ]]; 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+=('')
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. ', ", `, ~, ^, |, &, )
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 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 =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 ( 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 |
--------------------------------------------------------------------------------