commit 86f3a3fc22db00c72d613c98c6c8ecccdd0d9c21 Author: Victorhck Date: Tue Sep 8 22:33:03 2020 +0200 primer commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e945deb --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Created by https://www.toptal.com/developers/gitignore/api/vim +# Edit at https://www.toptal.com/developers/gitignore?templates=vim + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/vim diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7cdbe0b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,438 @@ +Attribution-NonCommercial-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..81b1145 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Aprende Vim (de la manera más inteligente) + +## ¿De qué trata esto? +*Aprende Vim (de la manera más inteligente)* es un libro para aprender las partes más interesantes del editor Vim. Es una traducción del libro original en inglés que puedes encontrar en: [https://github.com/iggredible/Learn-Vim](https://github.com/iggredible/Learn-Vim) + +La idea es trauducir el libro del inglés al español para difundir y dar a conocer este potente editor de texto que es Vim. Traducciones, correcciones y mejoras son bienvenidas. No dudes en hacer un PR. + +## Índice de contenidos (En progreso. Se irán traduciendo los diferentes capítulos) +- Ch 0 - Learn Vim the Smart Way +- Ch 1 - Starting Out +- [Ch 2 - Buffers, Windows, and Tabs](./ch02_buffers_windows_tabs.md) +- [Ch 3 - Opening and Searching Files](./ch03_opening_and_searching_files.md) +- [Ch 4 - Vim Grammar](./ch04_vim_grammar.md) +- [Ch 5 - Moving in a File](./ch05_moving_in_file.md) +- [Ch 6 - Insert Mode](./ch06_insert_mode.md) +- [Ch 7 - The Dot command](./ch07_the_dot_command.md) +- [Ch 8 - Registers](./ch08_registers.md) +- [Ch 9 - Macros](./ch09_macros.md) +- [Ch 10 - Undo](./ch10_undo.md) +- [Ch 11 - Visual Mode](./ch11_visual_mode.md) +- [Ch 12 - Search and Substitute](./ch12_search_and_substitute.md) +- [Ch 13 - The Global Command](./ch13_the_global_command.md) +- [Ch 14 - External Commands](./ch14_external_commands.md) +- Ch 15 - Command Line Mode +- Ch 16 - Tags +- Ch 17 - Git +- Ch 18 - Fold +- Ch 19 - Compile + +# Licencia y derechos de autor +El materia original en iglés es autoría de ©2020 Igor Irianto. La traducción en español, será autoría de los diferentes colaboradores (si los hubiera). Al colaborar en este repositorio estás de acuerdo en compartir tu trabajo con la licencia utilizada para el repositorio completo. + +Creative Commons License
+ +Este trabajo está publicado bajo una licencia Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International. + diff --git a/ch02_buffers_windows_tabs.md b/ch02_buffers_windows_tabs.md new file mode 100644 index 0000000..252833f --- /dev/null +++ b/ch02_buffers_windows_tabs.md @@ -0,0 +1,186 @@ +# Buffers, Windows, and Tabs + +If you have used a modern text editor, you are probably familiar with windows and tabs. Vim has three abstractions instead of two: buffers, windows, and tabs. + +In this chapter, I will explain how buffers, windows, and tabs work in Vim. + +Before you start, make sure you have `set hidden` option in your `vimrc`. Without this, whenever you switch buffers, Vim will prompt you to save the file (you don't want that if you want to move quickly between buffers). For more information, check out `:h hidden`. + +# Buffers + +A buffer is an in-memory space where you can write and edit some text. When you are opening a new file in vim its content will be bound to a new buffer: + 1. From within vim, open a new buffer `:new` (create a new empty buffer) + 2. From your terminal, open a new file `file1.js` (create a new buffer with `file1.js` bound to it) + +If your buffer isn't bound to a file yet but you want to save its content, you can save it with `:w `. + +``` +vim file1.js +``` + +![one buffer displayed with highlight](./img/screen-one-buffer-file1-highlighted.png) + +What you are seeing is `file1.js` *buffer*. Whenever we open a new file, Vim creates a new buffer. + +Exit Vim. This time, open two new files: + +``` +vim file1.js file2.js +``` +![one buffer displayed.png](./img/screen-one-buffer.png) + +Vim displays `file1.js` buffer, but it actually creates two buffers: `file1.js` buffer and `file2.js` buffer. You can see all buffers with `:buffers` (alternatively, you can use `:ls` or `:files` too). + +![buffers command showing 2 buffers](./img/screen-one-buffer-buffers-command.png) + +There are several ways you can traverse buffers: +1. `:bnext` to go to the next buffer (`:bprevious` to go to the previous buffer). +2. `:buffer` + filename. Vim can autocomplete filename with `tab`. +3. `:buffer` + `n`, where `n` is the buffer number. For example, typing `:buffer 2` will take you to buffer #2. +4. Jump to the older position in jump list with `Ctrl-o` and to the newer position with `Ctrl-i`. These are not buffer specific methods, but they can be used to jump between different buffers. I will talk more about jumps in Chapter 5. +5. Go to the previously edited buffer with `Ctrl-^`. + + +Once Vim creates a a buffer, it will remain in your buffers list. To remove it, you can type `:bdelete`. It accepts either a buffer number (`:bdelete 3` to delete buffer #3) or a filename (`:bdelete` then use `tab` to autocomplete). + +The hardest thing for me when learning about buffer was visualizing how it worked. One way to think of it is by imagining my buffers as a deck of cards. If I have 3 buffers, I have a stack of 3 cards. If I see `file1.js` buffer displayed, then the `file1.js` card is at the top of the deck. I can't see `file2.js` and `file3.js` cards, but I know they are in the deck. If I switch to `file3.js` buffer, I am putting `file1.js` card in the deck and putting `file3.js` card to the top of the deck. + +If you haven't used Vim before, this is a new concept. Take your time to understand it. When you're ready, let's move on to windows. + +# Windows + +A window is a viewport on a buffer. You can have multiple windows. Most text editors have the ability to display multiple windows. Below you see a VSCode with 3 windows. + +![buffers command showing 2 buffers](./img/screen-vscode-3-windows.png) + +Let's open `file1.js` from the terminal again: +``` +vim file1.js +``` +![one buffer displayed.png](./img/screen-one-buffer.png) + +Earlier I said that you're looking at `file1.js` buffer. While that was correct, it was incomplete. You are looking at `file1.js` buffer displayed through **a window**. A window is what you are seeing a buffer through. + +Don't quit vim yet. Run: +``` +:split file2.js +``` + +![split window horizontally](./img/screen-split-window.png) + +Now you are looking at two buffers through **two windows**. The top window displays `file2.js` buffer. The bottom window displays `file1.js` buffer. + +By the way, if you want to navigate between windows, you can use these commands: + +``` +Ctrl-W h Moves the cursor to the left window +Ctrl-W j Moves the cursor to the window below +Ctrl-W k Moves the cursor to the window upper +Ctrl-W l Moves the cursor to the right window +``` + + +Now run: +``` +:vsplit file3.js +``` + +![split window vertically and horizontally](./img/screen-split-window-vertically-and-horizontally.png) + + +You are now seeing three windows displaying three buffers. The top left window displays `file3.js` buffer, the top right window displays `file2.js` buffer, and the bottom window displays `file1.js` buffer. + +You can have multiple windows displaying the same buffer. While you're on the top left window, type: +``` +:buffer file2.js +``` +![split window vertically and horizontally with two file2.js](./img/screen-split-window-vertically-and-horizontally-two-file2.png) + + +Now both top left and top right windows are displaying `file2.js` buffer. If you start typing on the top left, you'll see that the content on both top left and top right window are changing in real time. + +To close the current window, you can run `Ctrl-W c` or type `:quit`. When you close a window, the buffer will still be there (again, to view your buffers, you can use `:buffers, :ls, :files`). + +Here are some useful normal mode window commands: +``` +Ctrl-W v Opens a new vertical split +Ctrl-W s Opens a new horizontal split +Ctrl-W c Closes a window +Ctrl-W o Makes the current window the only one on screen and closes other windows +``` +And here is a list of useful window Ex commands: +``` +:vsplit filename Split window vertically +:split filename Split window horizontally +:new filename Create new window +``` + +For more, check out `:h window`. Take your time to understand them. + +# Tabs + +A tab is a collection of windows. Think of it like a layout for windows. In most modern text editors (and modern internet browsers), a tab means an open file/ page and when you close it, that file/page goes away. In Vim, a tab does not represent an open file. When you close a tab in Vim, you are not closing a file. Remember, Vim stores files in-memory via buffers. Closing a tab (or a window) does not make that file disappear from the buffers. + +Let's see Vim tabs in action. Open `file1.js`: +``` +vim file1.js +``` + +To open `file2.js` in a new tab: + +``` +:tabnew file2.js +``` +![screen displays tab 2](./img/screen-tab2.png) + +You can also let Vim autocomplete the file you want to open in a *new tab* by pressing `tab` (no pun intended). + + +Below is a list of useful tab navigations: +``` +:tabnew file.txt open file.txt in a new tab +:tabclose Close the current tab +:tabnext Go to next tab +:tabprevious Go to previous tab +:tablast Go to last tab +:tabfirst Go to first tab +``` +You can also run `gt` to go to next tab page. You can pass count as argument to `gt`, where count is tab number. To go to the third tab, do `3gt`. + +One advantage of having multiple tabs is you can have different window arrangements in different tabs. Maybe you want your first tab to have 3 vertical windows and second tab to have a mixed horizontal and horizontal windows layout. Tab is the perfect tool for the job! + +![first tab with multiple windows](./img/tabs-file1js.png) + +![second tab with multiple windows](./img/tabs-file2js.png) + +To start Vim with multiple tabs, you can do this from the terminal: +``` +vim -p file1.js file2.js file3.js +``` +# Moving in 3D + +Moving between windows is like traveling two-dimensionally along X-Y axis in a Cartesian coordinate. You can move to the top, right, bottom, and left window with `Ctrl-W h/j/k/l`. + +![cartesian movement in x and y axis](./img/cartesian-xy.png) + +Moving between buffers is like traveling across the Z axis in a Cartesian coordinate. Imagine your buffer files are lined up across the Z axis. You can traverse the Z axis one buffer at a time with `:bnext` and `:bprevious`. You can jump to any coordinate in Z axis with `:buffer filename/buffernumber`. + +![cartesian movement in z axis](./img/cartesian-z.png) + +You can move in *three-dimensional space* by combining window and buffer movements. You can move to the top, right, bottom, or left window (X-Y navigations) with window navigations. Since each window contains buffers, you can move forward and backward (Z navigations) with buffer movements. + +![cartesian movement in x, y, and z axis](./img/cartesian-xyz.png) + + +# Using Buffers, Windows, and Tabs the Smart Way + +You have learned how buffers, windows, and tabs work in Vim. To use them efficiently, you need to understand what they are designed for and apply them in your own workflow. Everyone has a different workflow, here is one example. + +I would use buffers to open up all required files to get the current task done. This task may require opening seven or seven times seven buffer files, but it doesn't matter because Vim can handle many buffers before it starts slowing down. Plus having many buffers opened won't crowd my screen. I am only seeing one buffer (assuming I have only one window) at any time and I can quickly fly to any buffer I wish. + +I would use multiple windows to view multiple buffers at once, usually when diffing files, comparing codes, or following a code flow. I usually don't keep more than three windows opened at the same time because my screen will get crowded. When I am done, I would close any extra windows. I prefer having as few opened windows as possible. + +I would use a tab to handle client-side codes and a separate tab for back-end codes. Personally, I do not use tabs in my workflow. I use [tmux](https://github.com/tmux/tmux/wiki) windows as substitute for Vim tabs. My tmux workflow is similar to Vim tabs workflow. Instead of having multiple **Vim tabs** opened for each context, I would have multiple **tmux windows** (one tmux window for client-side codes and another window for back-end codes). + +My workflow may look different than yours based on your editing style and that's fine. Experiment around to discover your own flow and find what works for you best. + diff --git a/ch03_opening_and_searching_files.md b/ch03_opening_and_searching_files.md new file mode 100644 index 0000000..83f7956 --- /dev/null +++ b/ch03_opening_and_searching_files.md @@ -0,0 +1,342 @@ +# Opening and Searching Files + +The goal of this chapter is to introduce you to opening and searching files in Vim. Being able to search quickly is a great way to jump-start your Vim productivity. One reason it took me a long time to get onboard with Vim is because I didn't know how to find things quickly like many popular text editors. + +This chapter is divided into two parts. In the first part, I will show you how to open and search files without plugins. In the second part, I will show you how to open and search files with [FZF.vim](https://github.com/junegunn/fzf.vim) plugin. Feel free to jump to whichever section you need to learn. However, I highly recommend you to go through everything. With that said, let's get started! + +# Opening and Editing Files with `:edit` +`:edit` is the simplest way to open a file in Vim. + +``` +:edit file.txt +``` +- If `file.txt` exists, it opens `file.txt` buffer. +- If `file.txt` doesn't exist, it creates a new buffer for `file.txt`. + + +Autocomplete (`tab`) works with `:edit`. For example, if your file is inside a [Rails](https://rubyonrails.org/) controller directory `./app/controllers/users_controllers.rb`, you can use `tab` to expand the terms quickly: +``` +:edit acu +``` + +`:edit` accepts wildcards arguments. `*` matches any file in the current directory. If you are only looking for files with `.yml` extension in the current directory, you can run: + +``` +:edit *.yml +``` +After pressing tab, Vim will give you a list of all `.yml` files in the current directory to choose from. + +You can use `**` to search recursively. If you want to look for all `*.md` files in your project, but you are not sure in which directories, you can do this: +``` +:edit **/*.md +``` +`:edit` can be used to run `netrw`, Vim's built-in file explorer (I will cover this later in this chapter). To do that, give `:edit` a directory argument instead of file. For example: +``` +:edit . +:edit test/unit/ +``` +`:edit` also accepts an Ex command as an argument (I will cover more on Ex commands in later chapter) when followed by `+`. Here are some examples: + +- To go to line number 5 (`:5`): `:edit +5 /test/unit/helper.spec.js` +- To go to the first line containing `"const"` (`/const`): `:edit +/const /test/unit/helper.spec.js` +- To delete all empty lines (`:g/^$/d`): `:edit +g/^$/d test/unit/helper.spec.js` + +# Searching Files with `:find` +You can find files with `:find`. For example: + +``` +:find package.json +:find app/controllers/users_controller.rb +``` + +Autocomplete also works with `:find`: + +``` +:find p to find package.json +:find acu to find app/controllers/users_controller.rb +``` + +You may notice that `:find` syntax and behavior look like `:edit`'s. The difference is that `:find` finds file in `path`, `:edit` doesn't. + +Let's learn a little bit about this `path`. Once you learn how to modify your paths, `:find` can become a powerful searching tool. To check what your paths are, do: +``` +:set path? +``` + +By default, yours probably look like this: + +``` +path=.,/usr/include,, +``` + +Here are what they mean: +- `.` means to search relative to the directory of the current file +- `,` means to search in the current directory +- `/usr/include` is a [directory](https://askubuntu.com/questions/191611/what-is-the-use-of-usr-include-directory). + +The take-home here is that you can modify your own paths. Let's assume this is your project structure: +``` +▾ app/ + ▸ assets/ + ▾ controllers/ + application_controller.rb + comments_controller.rb + users_controller.rb + ... +``` + +If you want to go to `users_controller.rb` from root directory, you have to go through several directories (and pressing a considerable amount of tabs). Sometimes you only care about `controllers/` directory, so you want to search immediately inside that directory without going through `app/` and `controllers/` each time you find a file. `path` can shorten that journey. + +You can add the `controllers/` directory to `path` with: + +``` +:set path+=app/controllers/ +``` + +Now that your path is updated, when you type `:find u`, Vim will also search for `app/controllers/` directory for files starting with "u". + +If you have a nested `controllers/` directory, like `app/controllers/account/users_controller.rb`, Vim won't find `users_controllers`. You need to instead add `:set path+=app/controllers/**` so autocomplete will find `users_controller.rb`. + +You might be thinking to add the entire project directories so when you press `tab`, Vim will search everywhere for that file, like this: +``` +:set path+=$PWD/** +``` + +`$PWD` is the current working directory. If you try to add your entire project to `path` so all files are reachable upon a `tab` press, although this may work for a small project, doing this may slow down your search significantly if you have many files in your project. I recommend adding only the `path` of your most visited files / directories. + +Updating `path` takes only a few seconds and doing this will save you a lot of time. + +# Searching in Files with `:grep` + +If you need to find in files, you can use grep. Vim has two ways of doing that: +- Internal grep (`:vim`. Yes, it is spelled `:vim`. It is short for `:vimgrep`). +- External grep (`:grep`). + +Let's go through internal grep first. `:vim` has the following syntax: + +``` +:vim /pattern/ file +``` + +- `/pattern/` is a regex pattern of your search term. +- `file` is the file(s) argument. Just like `:find`, you can pass it `*` and `**` wildcards. + +For example, to look for all occurrences of "foo" string inside all ruby files (`.rb`) inside `app/controllers/` directory: + +``` +:vim /foo/ app/controllers/**/*.rb +``` + +After running that command, you will be redirected to the first result. Vim's `vim` search command uses `quickfix` operation. To see all search results, run `:copen`. This opens a `quickfix` window. Here are some useful quickfix commands to get you productive immediately: + +``` +:copen Open the quickfix window +:cclose Close the quickfix window +:cnext Go to the next error +:cprevious Go to the previous error +:colder Go to the older error list +:cnewer Go to the newer error list +``` + +I won't cover quickfix too deep here. To learn more about quickfix, check out `:h quickfix`. + +You may notice that running internal grep (`:vim`) can get slow if you have a large number of matches. This is because it reads them into memory. Vim loads each matching files as if they are being edited. + +Let's talk about external grep. By default, it uses `grep` terminal command. To search for "foo" inside a ruby file inside `app/controllers/` directory, you can do this: + +``` +:grep "foo" app/controllers/**/*.rb +``` + +Just like `:vim`, `:grep` accepts `*` and `**` wildcards. It also displays all matches using `quickfix`. + +Vim uses `grepprg` variable to determine which external program to run when running `:grep` so you don't have to always use the terminal `grep` command. Later in this article, I will show you how to change default the external command. + +# Browsing Files with `netrw` + +`netrw` is Vim's built-in file explorer. It is useful to see a project's structural hierarchy. To run `netrw`, you need these two settings in your `.vimrc`: + +``` +set nocp +filetype plugin on +``` + +I will only cover the basic use of `netrw` here, but it should be enough to get you started. You can start `netrw` when you launch Vim and passing it a directory instead of a file. For example: + +``` +vim . +vim src/client/ +vim app/controllers/ +``` + +To launch `netrw` from inside Vim, you can use `:edit` and pass it a directory instead of a filename: + +``` +:edit . +:edit src/client/ +:edit app/controllers/ +``` + +There are other ways to launch `netrw` window without passing a directory: + +``` +:Explore Starts netrw on current file +:Sexplore Not kidding. Starts netrw on split top half of the screen +:Vexplore Starts netrw on split left half of the screen +``` + +You can navigate `netrw` with Vim motions (I will cover these on chapter 5). If you need to create, delete, and rename file/directory, here is a list of useful `netrw` commands: + +``` +% Create a new file +d Create a new directory +R Rename a file or directory +D Delete a file or directory +``` + +`:h netrw` is very comprehensive. Check it out if you have time. + +If you find `netrw` too bland and need more flavor, [vim-vinegar](https://github.com/tpope/vim-vinegar) is a good plugin to improve `netrw`. If you're looking for a different file explorer, [NERDTree](https://github.com/preservim/nerdtree) is a good alternative. Check them out! + +# FZF + +Now that you've learned how to open and search files in Vim with built-in tools, it's time to use plugins to level up your search game. + +One thing that modern text editors got right that Vim didn't is how easy it is to find files and to find in files . In this second half of the chapter, I will show you how to use [FZF.vim](https://github.com/junegunn/fzf.vim) to make searching in Vim easy and powerful. + +# Setup + +But first, make sure you have [FZF](https://github.com/junegunn/fzf) and [ripgrep](https://github.com/BurntSushi/ripgrep) download. Follow the instruction on their github repo. The commands `fzf` and `rg` should now be available after successful installs. + +Ripgrep is a search tool much like grep (hence the name). It is generally faster than grep and has many useful features. FZF is a general-purpose command-line fuzzy finder. You can use it with any commands, including ripgrep. Together, they make a powerful search tool combination. + +FZF does not use ripgrep by default, so we need to tell FZF to use ripgrep with `FZF_DEFAULT_COMMAND` variable. In my `.zshrc` (`.bashrc` if you use bash), I have these: +``` +if type rg &> /dev/null; then + export FZF_DEFAULT_COMMAND='rg --files' + export FZF_DEFAULT_OPTS='-m' +fi +``` + + +Pay attention to `-m` in `FZF_DEFAULT_OPTS`. This option allows us to make multiple selections with `tab` or `shift-tab`. You don't have to have this line to make FZF work with Vim, but I think it is a useful option to have. It will come in handy when you want to perform search and replace in multiple files which I'll cover in just a little bit. `FZF` accepts more options, to learn more, check out [fzf's repo](https://github.com/junegunn/fzf#usage) or `man fzf`. At minimum you should have `export FZF_DEFAULT_COMMAND='rg'`. + +After installing FZF and ripgrep, let's set up FZF plugin. I am using [vim-plug](https://github.com/junegunn/vim-plug) plugin manager in this example, but you can use any plugin managers. + +Add these inside your `.vimrc` plugins. You need to use [FZF.vim](https://github.com/junegunn/fzf.vim) plugin (created by the same FZF author). +``` +Plug 'junegunn/fzf.vim' +Plug 'junegunn/fzf', { 'do': { -> fzf#install() } } +``` + +For more info about this plugin, you can check out [FZF.vim repo](https://github.com/junegunn/fzf/blob/master/README-VIM.md). + +# FZF Syntax + +To be able to use FZF efficiently, you should learn some basic FZF syntax. Fortunately, the list is short: + +- `^` is a prefix exact match. To search for a phrase starting with "welcome": `^welcome`. +- `$` is a suffix exact match. To search for a phrase ending with "my friends": `friends$`. +- `'` is an exact match. To search for the phrase "welcome my friends": `'welcome my friends`. +- `|` is an "or" match. To search for either "friends" or "foes": `friends | foes`. +- `!` is an inverse match. To search for phrase containing "welcome" and not "friends": `welcome !friends` + +You can mix and match these options. For example, `^hello | ^welcome friends$` will search for the phrase starting with either "welcome" or "hello" and ending with "friends". + +# Finding Files + +To search for files inside Vim using FZF.vim plugin, you can use the `:Files` method. Run `:Files` from Vim and you will be prompted with FZF search prompt. + +

+ Finding files in FZF +

+ + +Since you will be using this command frequently, it is good to have this mapped. I map mine with `Ctrl-f`. In my `.vimrc`, I have this: + +``` +nnoremap :Files +``` + +# Finding in Files +To search inside files, you can use the `:Rg` command. + +

+ FInding in Files in FZF +

+ + +Again, since you will probably use this frequently, let's map it. I map mine with `f`. + +``` +nnoremap f :Rg +``` + +# Other Searches + +FZF.vim provides many other search commands. I won't go through each one of them here, but you can check them out [here](https://github.com/junegunn/fzf.vim#commands). + +Here's what my FZF mappings look like. Feel free to borrow from mine to create your own powerful set of mappings! +``` +nnoremap b :Buffers +nnoremap :Files +nnoremap f :Rg +nnoremap / :BLines +nnoremap ' :Marks +nnoremap g :Commits +nnoremap H :Helptags +nnoremap hh :History +nnoremap h: :History: +nnoremap h/ :History/ +``` + +# Replacing `grep` with `rg` + +As I mentioned earlier, Vim has two ways to search in files: `:vim` and `:grep`. `:grep` uses external search tool that you can reassign using `grepprg` keyword. I will show you how to configure Vim to use ripgrep instead of terminal grep when running the `:grep` command. + +Now let's setup `grepprg` so `:grep` uses ripgrep. Add this in your `vimrc`. +``` +set grepprg=rg\ --vimgrep\ --smart-case\ --follow +``` +Feel free to modify some of the options above! For more information what the options above mean, check out `man rg`. + +After you update `grepprg`, now when you run `:grep`, it will actually run `rg --vimgrep --smart-case --follow` instead of running `grep`. If you want to search for "foo" using ripgrep, you can now run a more succinct command `:grep "foo"` instead of `:grep "foo" . -R` (in addition, ripgrep searches faster than grep). + +Just like the old `:grep`, this new `:grep` still uses quickfix to display results. + +You might wonder, "Well, this is nice but I never used `:grep` in Vim, plus can't I just use `:Rg` to find phrases in files? When will I ever need to use `:grep`? + +That is a very good question. You may need to use `:grep` in Vim to do search and replace in multiple files, which I will cover next. + +# Search and Replace in Multiple Files + +Modern text editors like VSCode make it very easy to search and replace a string across multiple files. In my early Vim days, when I had to search and replace a string in multiple files, I would use [Atom](https://atom.io/) because I couldn't do it easily in Vim. In this section, I will show you two different methods to easily do that in Vim. + +The first method is to replace *all* matching phrases in your project. You will need to use `:grep`. If you want to replace all instances of "pizza" with "donut", here's what you do: + +``` +:grep "pizza" +:cfdo %s/pizza/donut/g | update +``` + +That's it? Yup! Let me break down the steps: + +1. `:grep pizza` uses ripgrep to succinctly search for all instances of "pizza" (by the way, this would still work even if you didn't reassign `grepprg` to use ripgrep. You would have to do `:grep "pizza" . -R` instead of `:grep "pizza"`). I prefer ripgrep for this task because of its concise syntax. +2. `:cfdo` executes any command you pass to all files in your quickfix list. In this case, your command is the substitution command `%s/pizza/donut/g`. The pipe (`|`) is a chain operator. You need to run `update` to save each file after you substitute it. I will cover substitute command in depth in later chapter. + +The second method is to search and replace in select files. With this method, you can manually choose which files you want to perform select and replace on. Here is what you do: + +1. Clear your buffers first. It is imperative that your buffer list contains only the files you need. You can clear it with `%bd | e# | bd#` (or restart Vim). +2. Run `:Files`. +3. Select all files you want to perform search and replace on. To select multiple files, use `tab` / `shift+tab`. This is only possible if you have `-m` option in `FZF_DEFAULT_OPTS` (refer to earlier FZF setup section for the `-m` option). +4. Run `:bufdo %s/pizza/donut/g | update`. The command `:bufdo %s/pizza/donut/g | update` looks similar to the earlier `:cfdo %s/pizza/donut/g | update` command. That's because they are. The difference is instead of substituting all quickfix entries (`:cfdo`), you are substituting all buffer entries (`:bufdo`). + +# Learn Search the Smart Way + +Searching is the bread-and-butter of text editing. Learning how to search well in Vim will help your text editing workflow. + +FZF.vim is a game-changer. I can't imagine using Vim without it. I think it is very important to have a good search tool when starting Vim. I've seen people struggling to transition to Vim because it is missing critical features modern text editors have, like a powerful and easy search. I was one. I hope this chapter addresses one of the issues and help to make the transition to Vim easier. To improve your searching prowess even more, I suggest to check out [fzf repo](https://github.com/junegunn/fzf). + +You also just saw Vim's extensibility in action - the ability to extend search functionality with a plugin and / or an external program. In the future, keep in mind of what other features you wish to extend in Vim. Chances are, someone has created a plugin or there is a program for it already. + +Next, let's talk about a very important topic in Vim: grammar. diff --git a/ch04_vim_grammar.md b/ch04_vim_grammar.md new file mode 100644 index 0000000..3542734 --- /dev/null +++ b/ch04_vim_grammar.md @@ -0,0 +1,204 @@ +# Vim Grammar + +It is easy to get intimidated by the complexity of many Vim commands. If you see a Vim user doing `gUfV` or `1GdG`, you may not immediately know what these commands do. In this chapter, I will break down the general structure of Vim commands into a simple grammar rule. + +This is the most important chapter in the entire book. Once you understand Vim commands' grammar-like structure, you will be able to "speak" to Vim. By the way, when I say *Vim language* in this chapter, I am not talking about Vimscript (the built-in programming language to customize and to create Vim plugins). Here it means the general pattern of normal mode commands. + +# How to learn a language + +I am not a native English speaker. I learned English when I was 13 when I moved to the US. I had to do three things to build up linguistic proficiency: + +1. Learn grammar rules +2. Increase my vocabulary +3. Practice, practice, practice. + +Likewise, to speak Vim language, you need to learn the grammar rules, increase your vocabulary, and practice until you can run the commands without thinking. + +# Grammar Rule + +You only need to know one grammar rule to speak Vim language: + +``` +verb + noun +``` + +That's it! + +This is equivalent to saying these English phrases: + +- *"Eat (verb) a donut (noun)"* +- *"Kick (verb) a ball (noun)"* +- *"Learn (verb) the Vim editor (noun)"* + +Now you need to build up your vocabulary with basic Vim verbs and nouns. + +# Vocabulary +## Nouns (Motions) + +Let's talk about motions as nouns. Motions are used to move around in Vim. They are also Vim nouns. Below you'll see some motion examples : + +``` +h Left +j Down +k Up +l Right +w Move forward to the beginning of the next word +} Jump to the next paragraph +$ Go to the end of the line +``` + +You will learn more about motions in the next chapter, so don't worry too much if you don't understand some of them. + +## Verbs (Operators) + +According to `:h operator`, Vim has 16 operators. However, in my experience, learning these 3 operators is enough for 80% of my editing needs: + +``` +y Yank text (copy) +d Delete text and save to register +c Delete text, save to register, and start insert mode +``` + +Now that you know basic nouns and verbs, let's apply our grammar rule! Suppose you have this expression: + +``` +const learn = "vim"; +``` +- To yank everything from your current location to the end of the line: `y$`. +- To delete from your current location to the beginning of the next word: `dw`. +- To change from your current location to the end of the current paragraph, say `c}`. + +Motions also accept count number as arguments *(I will discuss this further in the next chapter)*. If you need to go up 3 lines, instead of pressing `k` 3 times, you can do `3k`. Count works with Vim grammar. + +- To yank two characters to the left: `y2h`. +- To delete the next two words: `d2w`. +- To change the next two lines: `c2j`. + +Right now, you may have to think long and hard to do even a simple command. You're not alone. When I first started, I had similar struggles but I got faster in time. So will you. + +As a side note, linewise operations are common operations in text editing, so Vim allows you to perform linewise operation by typing the operator command twice. For example, `dd`, `yy`, and `cc` perform **deletion**, **yank**, and **change** on the entire line. Try this with other operators! + +I hope everything starts to make sense. But I am not quite done yet. Vim has one more type of noun: text objects. + +## More Nouns (Text Objects) + +Imagine you are somewhere inside a pair of parentheses like `(hello vim)` and you need to delete the entire phrase inside the parentheses. How can you quickly do it? Is there a way to delete the "group" you are inside of? + +The answer is yes. Texts often come structured. They are often put inside parentheses, quotes, brackets, braces, and so on. Vim has a way to capture this structure with text objects. + +Text objects are used with operators. There are two types of text objects: + +``` +i + object Inner text object +a + object Outer text object +``` +Inner text object selects the object inside *without* the white space or the surrounding objects. Outer text object selects the object inside *including* the white space or the surrounding objects. Outer text object always selects more text than inner text object. So if your cursor is somewhere inside the parentheses in the expression `(hello vim)`: + +- To delete the text inside the parentheses without deleting the parentheses: `di(`. +- To delete the parentheses and the text inside: `da(`. + +Let's look at a different example. Suppose you have this Javascript function and your cursor is on "Hello": + +``` +const hello = function() { + console.log("Hello Vim"); + return true; +} +``` + +- To delete the entire "Hello Vim": `di(`. +- To delete the content of function (surrounded by `{}`): `di{`. +- To delete the "Hello" string: `diw`. + +Text objects are powerful because you can target different objects from one location. You can delete the objects inside the pair of parentheses, the function block, or the whole word. Moreover, when you see `di(`, `di{`, and `diw`, you get a pretty good idea what text objects they represent (a pair of parentheses, a pair of braces, and a word). + +Let's look at one last example. Suppose you have these HTML tags: +``` +
+

Header1

+

Paragraph1

+

Paragraph2

+
+``` +If your cursor is on "Header1" text: +- To delete "Header1": `dit`. +- To delete `

Header1

`: `dat`. + +If your cursor is on "div": +- To delete `h1` and both `p` lines: `dit`. +- To delete everything: `dat`. +- To delete "div": `di<`. + +Below is a list of common text objects: + +``` +w A word +p A paragraph +s A sentence +( or ) A pair of ( ) +{ or } A pair of { } +[ or ] A pair of [ ] +< or > A pair of < > +t XML tags +" A pair of " " +' A Pair of ' ' +` A pair of ` ` +``` +To learn more, check out `:h text-objects`. + +# Composability and Grammar + +After learning Vim grammar, let's discuss composability in Vim and why this is a great feature to have in a text editor. + +Composability means having a set of general commands that can be combined (composed) to perform more complex commands. Just like in programming where you can create more complex abstractions from simpler abstractions, in Vim you can execute complex commands from simpler commands. Vim grammar is the manifestation of Vim's composable nature. + +The true power of Vim's composability shines when it integrates with external programs. Vim has a filter operator (`!`) to use external programs as filters for our texts. Suppose you have this messy text below and you want to tabularize it: +``` +Id|Name|Cuteness +01|Puppy|Very +02|Kitten|Ok +03|Bunny|Ok +``` +This cannot be easily done with Vim commands, but you can get it done quickly with `column` terminal command. With your cursor on "Id", run `!}column -t -s "|"`. Voila! Now you have this pretty tabular data: +``` +Id Name Cuteness +01 Puppy Very +02 Kitten Ok +03 Bunny Ok +``` + +Let's break down the command. The verb was `!` (filter operator) and the noun was `}` (go to next paragraph). The filter operator `!` accepted another argument, a terminal command, so I gave it `column -t -s "|"`. I won't go through how `column` worked, but in short, it tabularized the text. + +Suppose you want to not only tabularize your text, but to display only the rows with "Ok". You know that `awk` can do the job easily. You can do this instead: +``` +!}column -t -s "|" | awk 'NR > 1 && /Ok/ {print $0}' +``` +Result: +``` +02 Kitten Ok +03 Bunny Ok +``` + +Great! Even piping works from inside Vim. + +This is the power of Vim's composability. The more you know your operators, motions, and terminal commands, your ability to compose complex actions is *multiplied*. + +Let me elaborate. Suppose you only know four motions: `w, $, }, G` and the delete (`d`) operator. You can do 8 things: move 4 different ways (`w, $, }, G`) and delete 4 different targets (`dw, d$, d}, dG`). Then one day you learn about the uppercase (`gU`) operator. You have added not just one new ability to your Vim tool belt, but *four*: `gUw, gU$, gU}, gUG`. Now you have 12 tools in your Vim tool belt. Each new knowledge is a multiplier to your current abilities. If you know 10 motions and 5 operators, now you have 60 moves (50 operations + 10 motions) in your arsenal. Moreover, the line number motion (`nG`) gives you `n` motions, where `n` is how many lines you have in your file (example: to go to line 5, `5G`). The search motion (`/`) practically gives you near unlimited number motion because you can search for anything. External command operator (`!`) gives you as many filtering tools as the number of terminal commands you know. Using a composable tool like Vim, everything you know can be connected together to do more complex operations. The more you know, the more powerful you become. + +This composable behavior echoes Unix philosophy: *do one thing well*. A motion has one job: go to X. An operator has one job: do Y. By combining an operator with a motion, you get YX: do Y on X. + +Even better, motions and operators are extendable. You can create custom motions and operators to add to your Vim toolbelt. [`vim-textobj-user`](https://github.com/kana/vim-textobj-user) has a [list](https://github.com/kana/vim-textobj-user/wiki) of custom text objects. + +By the way, it's okay if you don't know `column` or `awk` commands I just did. The point is that Vim integrates very well with terminal commands. + +# Learn Grammar the Smart Way + +You just learned about Vim grammar's only rule: +``` +verb + noun +``` +One of my biggest Vim "AHA!" moments was when I had just learned about the uppercase (`gU`) operator and wanted to uppercase a word, I instinctively ran `gUiw` and it worked! The word I was on was uppercased. I finally began to understand Vim. My hope is that you will have your own "AHA!" moment soon, if not already. + +The goal is this chapter is to show you the `verb + noun` pattern in Vim so you will approach learning Vim like learning a new language instead of memorizing every command combinations. + +Learn the pattern and understand the implications. That's the smart way to learn. diff --git a/ch05_moving_in_file.md b/ch05_moving_in_file.md new file mode 100644 index 0000000..c22f5b8 --- /dev/null +++ b/ch05_moving_in_file.md @@ -0,0 +1,351 @@ +# Moving in a File + +In the beginning, moving with a keyboard will feel awkward and incredibly slow, but don't give up! Once you get used to it, you can go anywhere in a file faster than a mouse. + +In this chapter, you will learn essential motions and how to use them efficiently. Keep in mind that this is **not** the entire motion that Vim has. The goal here is to introduce useful motions to become productive quickly. If you need to learn more, check out `:h motion.txt`. + +# Character Navigation + +The most basic motion unit is moving one character left, down, up, and right. + +``` +h Left +j Down +k Up +l Right +``` + +You can also move with directional arrows. If you are just starting, feel free to use any method you're most comfortable with. + +I prefer `hjkl` because my right hand can stay in home row. Doing this gives me shorter reach to surrounding keys. +To get used to it, I actually disabled the arrow buttons when starting out by adding these in `~/.vimrc`: + +``` +noremap +noremap +noremap +noremap +``` + +There are also plugins to help break this bad habit. One of them is [vim-hardtime](https://github.com/takac/vim-hardtime). +To my surprise, it only took a few days to get used to using `hjkl`. + +*By the way, if you wonder why Vim uses `hjkl` to move, this is because Lear-Siegler ADM-3A terminal where Bill Joy wrote Vi, didn't have arrow keys and used `hjkl` as left/down/up/right.* + +If I want to go somewhere close by, like moving from one part of a word to another part of the same word, I would use `h` or `l`. If I want to go up or down a few lines within displayed window, I would use `j` or `k`. If I want to go somewhere farther, I would use a different motion. + +# Relative Numbering + +I think it is helpful to have `number` and `relativenumber` set. You can do it by having this on `.vimrc`: + +``` +set relativenumber number +``` + +This displays my current line number and relative line numbers. + +Why is this useful? This allows me to quickly see how many lines I am away from my target. With this, I can easily see that my target is 12 lines below me so I can do `12j`. Otherwise, if I'm on line 69 and my target is on line 81, I have to do mental calculation (81 - 69 = 12). That takes too much mental resources. The less I have to think about where I need to go, the better. + +This is 100% personal preference. Experiment with `relativenumber` / `norelativenumber`, `number` / `nonumber` and use whatever you find most useful! + +# Count Your Move + +One more thing, let's talk about "count" argument. Motions accept a preceding numerical argument. I mentioned above that you can go down 12 lines with `12j`. The 12 in `12j` is the count number. + +The syntax to use count with your motion is: + +``` +[count] + motion +``` + +You can apply this to all motions. If you want to move 9 characters to the right, instead of pressing `l` 9 times, you can do `9l`. As you learn more motions, try to give them count argument. + +# Word Navigation + +Let's move to a larger motion unit: *word*. You can move to the beginning of the next word (`w`), to the end of the next word (`e`), to the beginning of the previous word (`b`), and to the end of the previous word (`ge`). + +In addition, there is *WORD*, distinct from word. You can move to the beginning of the next WORD (`W`), to the end of the next WORD (`E`), to the beginning of the previous WORD (`B`), and to the end of the previous WORD (`gE`). To make it easy to remember, WORD uses the same letters as word, except they are uppercased. + +``` +w Move forward to the beginning of the next word +W Move forward to the beginning of the next WORD +e Move forward one word to the end of the next word +E Move forward one word to the end of the next WORD +b Move backward to beginning of the previous word +B Move backward to beginning of the previous WORD +ge Move backward to end of the previous word +gE Move backward to end of the previous WORD +``` + +So what are the similarities and differences between a word and a WORD? Both word and WORD are separated by non-blank characters. A word is a sequence of characters containing only `a-zA-Z0-9_`. A WORD is a sequence of all characters except white space (a white space means either space, tab, and EOL). To learn more, check out `:h word` and `:h WORD`. + +For example, suppose you have: +``` +const hello = "world"; +``` + +With your cursor at the start of the line, to go to the end of the line with `l`, it will take you 21 key presses. Using `w`, it will take 6. Using `W`, it will only take 4. Both word and WORD are good options to travel short distance. + +However, you can get from "c" to ";" in one keystroke with current line navigation. + +# Current Line Navigation + +When editing, you often need to navigate horizontally in a line. To jump to the first character in current line, use `0`. To go to the last character in the current line, use `$`. Additionally, you can use `^` to go to the first non-blank character in the current line and `g_` to go to the last non-blank character in the current line. If you want to go to the column `n` in the current line, you can use `n|`. + +``` +0 Go to the first character in the current line +^ Go to the first nonblank char in the current line +g_ Go to the last non-blank char in the current line +$ Go to the last char in the current line +n| Go the column n in the current line +``` + +You can also perform a current line search with `f` and `t`. The difference between `f` and `t` is that `f` takes you to the first letter of the match and `t` takes you till (right before) the first letter of the match. So if you want to search for and land on "h", use `fh`. If you want to search for first "h" and land right before the match, use `th`. If you want to go to the *next* occurrence of the last current line search, use `;`. To go to the previous occurrence of the last current line match, use `,`. + +To search backwards for "h", use `Fh`. To keep searching for "h" in the same direction, use `;`. Notice that `;` does not always search forward. `;` repeats the last search direction. If you used `F`, `;` will search backward while `,` searches forward. If you used `f`, `;` will search forward and `,` backward. + +``` +f Search forward for a match in the same line +F Search backward for a match in the same line +t Search forward for a match in the same line, stopping before match +T Search backward for a match in the same line, stopping before match +; Repeat the last search in the same line +, Repeat the last search in the same line backwards +``` + +Back at the previous example: + +``` +const hello = "world"; +``` + +With your cursor at the start of the line, you can go to the last character in current line (";") with one key press: `$`. If you want to go to "w" in "world", you can use `fw`. A good tip to go anywhere in a line is to look for least-common-letters like "j", "x", "z" near your target. + +# Sentence and Paragraph Navigation + +Next two navigation units are sentence and paragraph. + +Let's talk about what a sentence is first. A sentence ends with either `. ! ?` followed by an end-of-line, a space, or a tab. You can jump to the next sentence with `)` and the previous sentence with `(`. +``` +( Jump to the previous sentence +) Jump to the next sentence +``` + +Let's look at some examples. Which phrases do you think are sentences and which aren't? Try navigating with `(` and `)` in Vim! + +``` +I am a sentence. I am another sentence because I end with a period. I am still a sentence when ending with an exclamation point! What about question mark? I am not quite a sentence because of the hyphen - and neither semicolon ; nor colon : + +There is an empty line above me. +``` +By the way, if you're having a problem with Vim not counting a sentence for phrases separated by `.` followed by a single line, you might be in `'compatible'` mode. Running `:set nocompatible` will fix it. In Vi, a sentence is a `.` followed by **two** spaces. You should have `nocompatible` set at all times. + +Next, let's talk about what a paragraph is. A paragraph begins after each empty line and also at each set of a paragraph macro specified by the pairs of characters in paragraphs option. + +``` +{ Jump to the previous paragraph +} Jump to the next paragraph + +``` + +If you're not sure what a paragraph macro is, do not worry. The important thing is that a paragraph begins and ends after an empty line. This should be true most of the time. + +Let's look at this example. Try navigating around with `}` and `{` (also, play around with sentence navigations `( )` to move around too!) + +``` +Hello. How are you? I am great, thanks! +Vim is awesome. +It may not easy to learn it at first...- but we are in this together. Good luck! + +Hello again. + +Try to move around with ), (, }, and {. Feel how they work. +You got this. +``` + +Check out `:h sentence` and `:h paragraph` to learn more. + +# Match Navigation + +Programmers often edit files containing codes. It may contain many parentheses, braces, and brackets and it can get confusing to know which parentheses you're inside of. +Many programming languages use parentheses, braces, and brackets and you can get lost in them. If you're inside one of them, you can jump to the other pair (if it exists) with `%`. You can also use this to find out whether you have matching parentheses, braces, and brackets. + +``` +% Navigate to another match, usually works for (), [], {} +``` + +Let's look at a Scheme code example because it uses parentheses extensively. Move around with `%` inside different parentheses. +``` +(define (fib n) + (cond ((= n 0) 0) + ((= n 1) 1) + (else + (+ (fib (- n 1)) (fib (- n 2))) + ))) +``` +I personally like to complement `%` using visual indicators plugins like [vim-rainbow](https://github.com/frazrepo/vim-rainbow). For more, check out `:h %`. + +# Line Number Navigation + +You can jump to line number `n` with `nG`. For example, if you want to jump to line 7, use `7G`. To jump to the first line, use either `1G` or `gg`. To jump to the last line, use `G`. + +Often you don't know exactly which line you are targeting, but you know it's approximately at 70% of the whole file. In this case, you can do `70%`. To jump halfway through the file, you can do `50%`. + +``` +gg Go to the first line +G Go to the last line +nG Go to line n +n% Go to n% in file +``` + +By the way, if you want to see total lines in a file, you can use `CTRL-G`. + +# Window Navigation + +To quickly go to the top, middle, or bottom of your *window*, you can use `H`, `M`, and `L`. + +You can also pass a count to `H` and `L`. If you use `10H`, you will go to 10 lines below the top of window. If you use `3L`, you will go to 3 lines above the last line of window. +``` +H Go to top of screen +M Go to medium screen +L Go to bottom of screen +nH Go n line from top +nL Go n line from bottom +``` + +# Scrolling + +To scroll, you have 3 speed increments: full-screen (`CTRL-F/CTRL-B`), half-screen (`CTRL-D/CTRL-U`), and line (`CTRL-E/CTRL-Y`). + +``` +Ctrl-e Scroll down a line +Ctrl-d Scroll down half screen +Ctrl-f Scroll down whole screen +Ctrl-y Scroll up a line +Ctrl-u Scroll up half screen +Ctrl-b Scroll up whole screen +``` +You can also scroll relatively to the current line: +``` +zt Bring the current line near the top of your screen +zh Bring the current line to the middle (half) of your screen +zb Bring the current line near the bottom of your screen +``` + +# Search Navigation + +Very often you know that a phrase exists inside a file. You can use search navigation to very quickly reach your target. To search for a phrase, you can use `/` to search forward and `?` to search backward. To repeat the last search you can use `n`. To repeat the last search going opposite direction, you can use `N`. + +``` +/ Search forward for a match +? Search backward for a match +n Repeat last search (same direction of previous search) +N Repeat last search (opposite direction of previous search) +``` + +Suppose you have this text: + +``` +let one = 1; +let two = 2; +one = "01"; +one = "one"; +let onetwo = 12; +``` + +If you are searching for "let", you can do `/let`. To quickly search for "let" again, you can just do `n`. To search for "let" again in opposite direction, you can do `N`. If you used `?let` to search, it will search for it backwards. If you use `n`, it will also search backwards, the same direction as `?let` (`N` will search for "let" forwards now). + +You can enable search highlight with `:set hlsearch`. Now when you search for `/let`, it will highlight *all* matching phrases in the file. In addition, you can set incremental search with `:set incsearch`. This will highlight the pattern as you're still typing it. By default, your matching phrases will remain highlighted until you search for another phrase. This can quickly turn into an annoyance. To disable highlight, you can run `:nohlsearch`. Because I use this no-highlight feature frequently, I created a mapping: + +``` +nnoremap :noh +``` + +You can quickly search for the text under the cursor with `*` to search forward and `#` to search backward. If your cursor is on the string "one", pressing `*` will be the same as if you had done `/\`. + +Both `\<` and `\>` in `/\` mean whole word search. It does not match "one" if it is a part of a bigger word. It will match for the word "one" but not "onetwo". If your cursor is over "one" and you want to search forward to match whole or partial words like "one" and "onetwo", you need to use `g*` instead of `*`. + +``` +* Search for whole word under cursor forward +# Search for whole word under cursor backward +g* Search for word under cursor forward +g# Search for word under cursor backward + +``` +# Marking Position + +You can use marks to save your current position and return to this position later. It's like a bookmark for text editing. You can set a mark with `mx`, where `x` can be any alphabetical letter `a-zA-Z`. There are two ways to return to mark: exact (line and column) with ```x`` and linewise (`'x`). + +``` +ma Mark position with mark "a" +`a Jump to line and column "a" +'a Jump to line "a" +``` + +There is a difference between marking with lowercase letters (a-z) and uppercase letters (A-Z). Lowercase alphabets are local marks and uppercase alphabets are global marks (sometimes known as file marks). + +Let's talk about local marks. Each buffer can have its own set of local marks. If I have two files opened, I can set a mark "a" (`ma`) in the first file and another mark "a" (`ma)` in the second file. + +Unlike local marks where you can have a set of marks in each buffer, you only get one set of global marks. If you set `mA` inside `myFile.txt`, the next time you set `mA` in a different file, it will overwrite the "A" mark. One advantage of global marks is you can jump to any global mark even if you are inside a completely different project. Global marks can travel across files. + +To view all marks, use `:marks`. You may notice from the marks list there are more marks other than `a-zA-Z`. Some of them are: + +``` +'' Jump back to the last line in current buffer before jump +`` Jump back to the last position in current buffer before jump +`[ Jump to beginning of previously changed / yanked text +`] Jump to the ending of previously changed / yanked text +`< Jump to the beginning of last visual selection +`> Jump to the ending of last visual selection +`0 Jump back to the last edited file when exiting vim +``` + +There are more marks than the ones listed above. I won't cover them here because I think they are rarely used, but if you're curious, check out `:h marks`. + +# Jump + +Lastly, let's talk about jumps in Vim. In Vim, you can "jump" to a different file or different part of a file with certain motions. Not all motions count as a jump, though. Going down with `j` does not count as a jump, even if you go 10 steps down with `10j`. Going to line 10 with `10G` counts as a jump. + +Here are the commands Vim consider as "jump" commands: + +``` +' Go to the marked line +` Go to the marked position +G Go to the line +/ Search forward +? Search backward +n Repeat the last search, same direction +N Repeat the last search, opposite direction +% Find match +( Go to the last sentence +) Go to the next sentence +{ Go to the last paragraph +} Go to the next paragraph +L Go to the the last line of displayed window +M Go to the middle line of displayed window +H Go to the top line of displayed window +[[ Go to the previous section +]] Go to the next section +:s Substitute +:tag Jump to tag definition +``` + +I don't recommend memorizing this list. A good rule of thumb is, any motion that moves farther than a word and current line navigation is probably a jump. Vim keeps track of where you've been when you move around and you can see this list inside `:jumps`. For more, check out `:h jump-motions`. + +Why are jumps useful? Because you can navigate the jump list with `Ctrl-o` to move up the jump list and `Ctrl-i` to move down the jump list. You can jump across different files, which I will discuss more in the next part. + +# Learn Navigation the Smart Way + +If you are new to Vim, this is a lot to learn. I do not expect anyone to remember everything immediately. It takes time before you can execute them without thinking. + +I think the best way to get started is to memorize a few essential motions. I recommend starting out with `h, j, k, l, w, b, G, /, ?, n`. It should not take long to learn 10 motions and be comfortable with them. + + +To get better at navigation, I can offer two suggestions: + +1. Watch for repeated actions. If you find yourself doing `l` repeatedly, look for a motion that will take you forward faster. You will find that you can use `w` to move between words. If you catch yourself repeatedly doing `w`, look if there is a motion that will take you to the end of the line immediately. You will find that you can use `$`. If you can describe your need verbally, there is a good chance Vim has a way to do it. +2. Whenever you learn a new move, spend a considerable amount of time until you can do it without thinking. + +Finally, you do not need to know every single Vim command to be productive. Most Vim users don't. I don't. Learn the commands that will help you accomplish your task at that moment. + +Take your time. Navigation skill is a very important skill in Vim. Learn one small thing every day and learn it well. diff --git a/ch06_insert_mode.md b/ch06_insert_mode.md new file mode 100644 index 0000000..8101295 --- /dev/null +++ b/ch06_insert_mode.md @@ -0,0 +1,152 @@ +# Insert Mode + +Insert mode is the default mode of many text editors. In this mode, what you type is what you get. + +In this chapter, you will learn how to use features in Vim insert mode to improve your typing efficiency. + +# Ways to go to Insert Mode + +There are many ways to get into insert mode from the normal mode. Here are some of them: +``` +i Insert text before the cursor +I Insert text before the first non-blank character of the line. +a Append text after the cursor +A Append text at the end of line +o Starts a new line below the cursor and insert text +O Starts a new line above the cursor and insert text +s Delete the character under the cursor and insert text +S Delete the current line and insert text +gi Insert text in same position where the last insert mode was stopped in current buffer +gI Insert text at the start of line (column 1) +``` + +Notice the lowercase / uppercase pattern. For every lowercase command, there is an uppercase counterpart. If you are new, don't worry if you don't remember the whole list above. Start with `i` and `a`. They should be enough to get you started. Slowly add more command into your memory. + +# Different Ways to Exit Insert Mode + +There is a few different ways to return to the normal mode while in the insert mode: +``` + Exits insert mode and go to normal mode +Ctrl-[ Exits insert mode and go to normal mode +Ctrl-c Like Ctrl-[ and , but does not check for abbreviation +``` + +I find `esc` key too far to reach, so I map my computer `caps lock` to behave like `esc`. If you search for Bill Joy's ADM-3A keyboard (Vi creator), you will see that `esc` key is not located on far top left like modern keyboards, but to the left of `q` key. This is why I think it makes sense to map `caps lock` to `esc`. + +Another common convention I have seen Vim users do is to map `esc` to `jj` or `jk` in insert mode. + +``` +inoremap jj +inoremap jk +``` +# Repeating Insert Mode + +You can pass a count parameter before entering insert mode. For example: +``` +10i +``` + +If you type "hello world!" and exit insert mode, Vim will repeat the text 10 times. This will work with any insert mode method (ex: `10I`, `11a`, `12o`). + +# Deleting Chunks in Insert Mode + +When you make a typing mistake, it can be cumbersome to type `backspace` repeatedly. It may make more sense to go to normal mode and delete (`d`) your mistake. Alternatively, you can delete one or more characters at a time while in insert mode: + +``` +Ctrl-h Delete one character +Ctrl-w Delete one word +Ctrl-u Delete the entire line +``` + +By the way, these shortcuts also work in command-line mode and Ex mode (I will cover command-line and Ex mode in later chapters). + +# Insert From Register + +Registers are like in-memory scratchpads that store and retrieve texts. To insert a text from any named register while in insert mode, type `Ctrl-r` plus the register symbol. There are many symbols you can use, but for this section, just know that you can use named registers (a-z). + +To see it in action, first you need to yank a word to register a. You can do this with: +``` +"ayiw +``` +- `"a` tells Vim that the target of your next action will go to register a. +- `yiw` yanks inner word. Review the chapter on Vim grammar. + +Register "a" now contains the word you just yanked. While in insert mode, to paste the text stored in register "a": + +``` +Ctrl-r a +``` + +There are multiple types of registers in Vim. I will cover them in greater detail in the next chapter. + +# Scrolling + +Did you know that you can scroll while inside insert mode? While in insert mode, if you go to `Ctrl-x` sub-mode, you can do additional operations. Scrolling is one of them. + +``` +Ctrl-x Ctrl-y Scroll up +Ctrl-x Ctrl-e Scroll down +``` + +# Autocompletion + +Vim has a built-in autocompletion mechanism using `Ctrl-x` sub-mode (like scrolling). Although it is not as good as [intellisense](https://code.visualstudio.com/docs/editor/intellisense) or any other Language Server Protocol (LSP), but for something that is available right out-of-the-box, it is a very capable feature. + +Here are some useful autocomplete commands to get started: +``` +Ctrl-x Ctrl-l Insert a whole line +Ctrl-x Ctrl-n Insert a text from current file +Ctrl-x Ctrl-i Insert a text from included files +Ctrl-x Ctrl-f Insert a file name +``` + +When you trigger autocompletion, Vim will display a pop-up window. To navigate up and down the pop-up window, use `Ctrl-n` and `Ctrl-p`. + +Vim also has two autocompletions that don't use `Ctrl-x` sub-mode: + +``` +Ctrl-n Find the next word match +Ctrl-p Find the previous word match +``` + +In general, Vim looks at the text in all available buffers for autocompletion source. If you have an open buffer with a line that says "Chocolate donuts are the best": +- When you type "Choco" and do `Ctrl-x Ctrl-l`, it will match and print the entire line. +- When you type "Choco" and do `Ctrl-p`, it will match and print the word "Chocolate". + +Autocomplete is a vast topic in Vim. This is just the tip of the iceberg. To learn more, check out `:h ins-completion`. + +# Executing a Normal Mode Command + +Did you know Vim can execute a normal mode command while inside insert mode? + +While in insert mode, if you press `Ctrl-o`, you'll be in `insert-normal` sub-mode. If you look at the mode indicator on bottom left, normally you will see `-- INSERT --`, but pressing `Ctrl-o` changes it to `-- (insert) --`. In this mode, you can do *one* normal mode command. Some things you can do: + +**Centering and jumping** +``` +Ctrl-o zz Center window +Ctrl-o H/M/L Jump to top/middle/bottom window +Ctrl-o 'a Jump to mark 'a' +``` + +**Repeating text** +``` +Ctrl-o 100ihello Insert "hello" 100 times +``` + +**Executing terminal commands** +``` +Ctrl-o !! curl https://google.com Run curl +Ctrl-o !! pwd Run pwd +``` + +**Deleting faster** +``` +Ctrl-o dtz Delete from current location till the letter "z" +Ctrl-o D Delete from current location to the end of the line +``` + +# Learn Insert Mode the Smart Way + +If you are like me and you come from another text editor, it can be tempting to stay in insert mode. However, staying in insert mode when you're not entering a text is an anti-pattern. Develop a habit to go to normal mode when your fingers aren't typing new texts. + +When you need to insert a text, first ask yourself if that text already exists. If it does, try to yank or move that text instead of typing it. Should you have to enter insert mode, see if you can autocomplete that text as much as possible. Avoid typing the same word more than once if you can. diff --git a/ch07_the_dot_command.md b/ch07_the_dot_command.md new file mode 100644 index 0000000..50797e5 --- /dev/null +++ b/ch07_the_dot_command.md @@ -0,0 +1,109 @@ +# El comando del punto + +Cuando estás editando un texto, evita todo lo que puedas, volver a hacer lo que ya hiciste. En este capítulo, aprenderás a utilizar el comando del punto para volver a realizar los cambios previos de una manera sencilla. Es el comando más simple y más versátil para reducir las repeticiones. + +# Uso + +Tal como sugiere su nombre, puedes utilizar el comando del punto, simplemente presionando la tecla del signo del punto (`.`). + +Por ejemplo, si quieres reemplazar todo el texto "let" con "const" en el siguiente código: +``` +let one = "1"; +let two = "2"; +let three = "3"; +``` + +Primero, utiliza `/let` para colocarte encima de la primera coincidencia que encuentre Vim. Segundo, utiliza `cwconst` para reemplazar "let" con "const" . Tercero, utiliza `n` para encontrar la siguiente coincidencia en el texto, utilizando la búsqueda que hemos realizado previamente. Finalmente, repite la acción de sustitución que realizaste con el comando del punto (`.`). Continua haciendo `n . n .` hasta que reemplaces todas las coincidencias que quieras. + +En este caso el comando del punto, repite la secuencia `cwconst`. Esto te ahorra el tener que realizar ocho pulsaciones de teclas en cada cambio teniendo que pulsar solo una tecla, el punto. + +# ¿Qué es un cambio? + +Si buscas en Vim la definición del comando del punto (`:h .`), verás que menciona que el comando repite el último cambio. ¿Qué es un cambio? + +Cada vez que realizas una actualización del contenido del _buffer_ actual (añadir, modificar o eliminar) utilizando cualquier comando del *modo normal*, estás realizando un cambio. Las excepciones son actualizaciones realizadas mediante comandos ejecutados en la línea de comandos (los comando que comienzan con `:`), esos no cuentan como cambios. + +En el primer ejemplo, comprobaste que `cwconst` fue el cambio. Ahora supongamos que tenemos esta frase: + +``` +tarta, patatas, zumo de frutas, +``` + +Ahora vamos a borrar, el texto desde el comienzo de la línea hasta donde encontremos la primera coma. Puedes realizar esto mediante `df,`. Repite la acción con `.` dos veces más hasta que borres todas las palabras. + +Probemos otro ejemplo +``` +tarta, patatas, zumo de frutas, +``` + +Esta vez solo necesitas borrar la coma, no la palabra que la precede. Con el cursor en el inicio de la frase, ve hasta la primera coma de la frase mediante el comando `f,`. Elimina el signo de puntuación bajo el cursor con el comando `x`. Repite mediante `.` la acción un par de veces más. ¿Sencillo, verdad? Espera, ¡no funcionó! ¿Por qué? + +En Vim, los cambios no incluyen los movimientos, porque estos no actualizan el contenido del *buffer*. Cuando ejecutamos `f,x`, realizamos dos acciones diferentes: el comando `f,` mueve el cursor y `x` actualiza el *buffer*. Solo este último realiza un cambio. A diferencia que con `df,` del ejemplo anterior. En este, `f,` indica al operador de borrado donde eliminar el contenido. Es una parte de todo la operación de borrado, `df,`. + +Finalicemos la última tarea. Después de ejecutar `f,` seguido por `x`, ve a la siguiente coma mediante `;` para repetir la última búsqueda realizada con `f`. Después utiliza `.` para eliminar el caracter bajo el cursor. Repite la secuencia `; . ; .` hasta que todo este eliminado. El comando completo sería `f,x;.;.`. + +Probemos otro ejemplo: + +``` +tarta +patatas +zumo de frutas +``` + +Vamos a añadir una coma al final de cada línea. Empezando por la primera línea, vamos a ejecutar lo siguiente `A,j`. A estas alturas, ya te das cuenta que `j` no realiza ningún cambio. El cambio es solo realizado con `A,`. Te puedes mover y repetir el cambio con `j . j .`. El comando completo sería `A,j.j.`. + +Cada acción desde el momento que presionas el operador del comando de insertar (`A`) hasta que lo abandonas con el comando (``) es considerado un cambio. Vim te permite controlar no solo que texto añadir, también *donde* añadirlo. También puedes añadirlo antes del cursor (`i`), después del cursor (`a`), en una nueva línea debajo del cursor (`o`), en una línea sobre el cursor (`O`), al final de la línea actual (`A`) o al comienzo de la línea actual (`I`). Para refrescar estos conceptos, echa un vistazo al capítulo [Insert Mode](./ch6_insert_mode.md) chapter. + + +# Repeat on Multiple Lines + +Suppose you have this text: +``` +let one = "1"; +let two = "2"; +let three = "3"; +const foo = "bar'; +let four = "4"; +let five = "5"; +let six = "6"; +let seven = "7"; +let eight = "8"; +let nine = "9"; +``` +Your goal is to delete all lines except the "foo" line. First, delete the first three lines with `d2j`. Then go over past the "foo" line. On the next line, use the dot command twice. The full command is `d2jj..`. + +Here the change was `d2j`. `2j` was not a motion, but a part of the delete operator. + +Let's look at another example: +``` +zlet zzone = "1"; +zlet zztwo = "2"; +zlet zzthree = "3"; +let four = "4"; +``` + +Let's remove all the z's. First, visually select the only the first z from the first three lines with blockwise-visual mode (`Ctrl-vjj`). If you're not familiar with blockwise visual mode, I will cover them in a later chapter. Once you have the three z's visually selected, delete them with the delete operator (`d`). Then move to the next word (`w`) to the next z. Repeat the change two more times (`..`). The full command is `Ctrl-vjjdw..`. + +When you deleted a column of three z's (`Ctrl-vjjd`), it was counted as a change. Visual mode selection can be used to target multiple lines as part of a change. + +# Including a Motion in a Change + +Let's revisit the first example in this chapter. Recall that the command `/letcwconst` followed by `n . n .` replaced all "let" with "const" in the following expressions: +``` +let one = "1"; +let two = "2"; +let three = "3"; +``` +There is a faster way to accomplish this. When deleting, instead of using the `w` as a noun, use `gn`. + +`gn` is a motion that searches forward for the last search pattern (in this case, `/let`) and automatically does a visual mode selection on the match. To replace the next occurrence, you no longer have to move and repeat the change ( `n . n .`), but only repeat (`. .`). You do not have to move anymore because searching the next match is now part of the change! + +When you are editing, always be on the lookout for a motion that can do several things at once like `gn` whenever possible. + +# Learn the Dot Command the Smart Way + +The dot command's power comes from exchanging several keystrokes for one. It is probably not a profitable exchange to use the dot command for one-keyed-operations like `x`. If your last change requires a complex operation like `cgnconst`, the dot command reduces nine keypresses into one, a very good trade-off. + +When editing, ask if the action you are about to do is repeatable. For example, if I need to remove the next three words, is it more economical to use `d3w` or to do `dw` then `.` two times? Will you be deleting a word again? If so, then it makes sense to use `dw` and repeat it several times instead of `d3w` because `dw` is more reusable than `d3w`. Keep a "change-driven" mindset while editing. + +The dot command is an easy and versatile command to start automating simple tasks. In the later chapter, you will learn how to automate more complex actions with Vim macros. But first, let's learn about registers to store and retrieve texts. diff --git a/ch08_registers.md b/ch08_registers.md new file mode 100644 index 0000000..92d769d --- /dev/null +++ b/ch08_registers.md @@ -0,0 +1,256 @@ +# Registers + +Learning Vim registers is like learning algebra for the first time. You don't think you need them until you learn them. + +You've probably used Vim registers when you yanked or deleted a text then pasted it with `p` or `P`. However, did you know that Vim has 10 different types of registers? + +In this chapter, I will go over all Vim register types and how to use them efficiently. + +# The Ten Register Types + +Here are the 10 register types Vim has: + +1. The unnamed register (`""`). +2. The numbered registers (`"0-9`). +3. The small delete register (`"-`). +4. The named registers (`"a-z`). +5. The read-only registers (`":`, `".`,and `"%`). +6. The alternate buffer register (`"#`). +7. The expression register (`"=`). +8. The selection registers (`"*` and `"+`). +9. The black hole register (`"_`). +10. The last search pattern register (`"/`). + + +# Register Operators + +Here are some operators that store values to registers: +``` +y Yank (copy) +c Delete text and start insert mode +d Delete text +``` + +There are more operators (like `s` or `x`), but these are the common ones. The rule of thumb is, if an operator removes texts, it probably saves the texts to registers. + +To put (paste) texts from registers, you can use: + +``` +p Put the text after the cursor +P Put the text before the cursor +``` + +Both `p` and `P` accept a count and a register symbol as arguments. For example, to put the recently yanked text ten times, do `10p`. To put the text from register "a", do `"ap`. To put the text from register "a" ten times, do `10"ap`. + +The general syntax to get the content from a specific register is `"x`, where `x` is the register symbol. + + +# Calling Registers from Insert Mode + +Everything you learn in this chapter can also be executed in insert mode. To get the text from register "a", normally you do `"ap`. But if you are in insert mode, run `Ctrl-r a`. The syntax to call registers from insert mode is: +``` +Ctrl-r x +``` +Where `x` is the register symbol. Now that you know how to store and retrieve registers, let's dive in! + + +# The Unnamed Register (`""`) + +To get the text from the unnamed register, do `""p`. It stores the last text you yanked, changed, or deleted. If you do another yank, change, or deletion, Vim will automatically replace the text. The unnamed register is like a computer's standard copy / paste operation. + +By default, `p` (or `P`) is connected to the unnamed register (from now on I will refer to the unnamed register with `p` instead of `""p`). + +# The Numbered Registers (`"0-9`) + +Numbered registers automatically fill themselves up in ascending order. There are 2 different numbered registers: the yanked register (`0`) and the numbered registers (`1-9`). Let's discuss the yanked register first. + +## The Yanked Register (`"0`) + +If you yank an entire line of text (`yy`), Vim actually saves that text in two registers: + +1. The unnamed register (`p`). +2. The yanked register (`"0p`). + +When you yank a different text, Vim will replace both the yanked register and the unnamed register. Any other operations will not be stored in register 0. This can be used to your advantage, because unless you do another yank, the yanked text will always be there, no matter how many changes and deletions you do. + +For example, if you: +1. Yank a line (`yy`) +2. Delete a line (`dd`) +3. Delete another line (`dd`) + +The yanked register will have the text from step one. + +If you: +1. Yank a line (`yy`) +2. Delete a line (`dd`) +3. Yank another line (`yy`) + +The yanked register will have the text from step three. + +One last tip, while in insert mode, you can quickly paste the text you just yanked using `Ctrl-r 0`. + +## The Numbered Registers (`"1-9`) + +When you change or delete a text that is at least one line long, that text will be stored in the numbered registers 1-9 sorted by the most recent. + +For example, if you have these lines: +``` +line three +line two +line one +``` + +With your cursor on "line three", delete them one by one with `dd`. Once all lines are deleted, register 1 should contain "line one" (the most recent text), register two "line two" (the second most recent text), and register three "line three" (the latest deleted text). To get the content from register one, "line one", do `"1p`. + + +The numbered registers are automatically incremented when using the dot command. If your numbered register one (`"1`) contains "line one", register two (`"2`) "line two", and register three (`"3`) "line three", you can paste them sequentially with this trick: +- Do `"1P` to paste the content from the numbered register one. +- Do `.` to paste the content from the numbered register two (`"2`). +- Do `.` to paste the content from the numbered register three (`"3`). + +During each sequential dot command call, Vim automatically increments the numbered registers. This trick works with any numbered register. If you started with `"5P`, `.` would do `"6P`, `.` again would do `"7P`, and so on. + +Small deletions like a word deletion (`dw`) or word change (`cw`) do not get stored in the numbered registers. They are stored in the small delete register (`"-`), which I will discuss next. + +# The Small Delete Register (`"-`) + +Changes or deletions less than one line are not stored in the numbered registers 0-9, but in the small delete register (`"-`). + +For example: +1. Delete a word (`diw`) +2. Delete a line (`dd`) +3. Delete a line (`dd`) + +`"-p` gives you the deleted word from step one. + +Another example: +1. I delete a word (`diw`) +2. I delete a line (`dd`) +3. I delete a word (`diw`) + +`"-p` gives you the deleted word from step three. Likewise, `"1p` gives you the deleted line from step two. Unfortunately, there is no way to retrieve the deleted word from step one because the small delete register only stores one item. However, if you want to preserve the text from step one, you can do it with the named registers. + +# The Named Register (`"a-z`) + +The named registers are Vim's most versatile register. It can store yanked, changed, and deleted texts into registers a-z. Unlike the previous 3 register types you've seen which automatically stores texts into registers, you have to explicitly tell Vim to use the named register, giving you full control. + +To yank a word into register "a", you can do it with `"ayiw`. +- `"a` tells vim that the next action (delete / change / yank) will be stored in register "a". +- `yiw` yanks the word. + +To get the text from register "a", run `"ap`. You can use all twenty-six alphabetical characters to store twenty-six different texts with named registers. + +Sometimes you may want to add to your existing named register. In this case, you can append your text instead of starting all over. To do that, you can use the uppercase version of that register. For example, suppose you have the word "Hello " already stored in register "a". If you want to add "world" into register "a", you can find the text "world" and yank it using "A" register (`"Aiw`). + +# The Read-Only Registers (`":`, `".`, `"%`) + +Vim has three read-only registers: `.`, `:`, and `%`. They are pretty simple to use: +``` +. Stores the last inserted text +: Stores the last executed command-line +% Stores the name of current file +``` + +If you write "Hello Vim", running `".p` will print out the text "Hello Vim". If you want to get the name of current file, run `"%p`. If you run `:s/foo/bar/g` command, running `":p` will print out the literal text "s/foo/bar/g". + +# The Alternate File Register (`"#`) + +In Vim, `#` usually represents the alternate file. An alternative file is the last file you opened. To insert the name of the alternate file, you can use `"#p`. + +# The Expression Register (`"=`) + +Vim has an expression register, `"=`, to evaluate expressions. Expression is a vast topic in Vim, so I will only cover the basics here. I will address expressions in greater details in the later chapters. + +You can evaluate mathematical expressions `1 + 1` with: + +``` +"=1+1p +``` + +Here, you are telling Vim that you are using the expression register with `"=`. Your expression is (`1 + 1`). Then you need to type `p` to get the result. As mentioned earlier, you can also access the register from insert mode. To evaluate mathematical expression from insert mode, you can do: + +``` +Ctrl-r =1+1 +``` + +You can get the values from any register using the expression register with `@`. If you wish to get the text from register "a": + +``` +"=@a +``` +Then press ``, then `p`. Similarly, to get values from register "a" while in insert mode: + +``` +Ctrl-r =@a +``` + +You can also evaluate Vim scripts with the expression register. If you define a variable `i` by running `:let i = 1`, you can get it with `"=i`, press return, then `p`. To get it while in insert mode, run `Ctrl-r=i`. + +Suppose you have a function: +``` +function! HelloFunc() + return "Hello Vim Script!" +endfunction +``` + +You can retrieve its value by calling it. To call it from normal mode, you can do: `"=HelloFunc()`, press return, then `p`. From insert mode `Ctrl-r =HelloFunc()`. + +# The Selection registers (`"*`, `"+`) + +Don't you sometimes wish that you can copy a text from external programs and paste it locally in Vim, and vice versa? With Vim's selection registers, you can. Vim has two selection registers: `quotestar` (`"*`) and `quoteplus` (`"+`). You can use them to access copied text from external programs. + +If you are on an external program (like Chrome browser) and you copy a block of text with `Ctrl-c` (or `Cmd-c`, depending on your OS), normally you wouldn't be able to use `p` to paste the text in Vim. However, both Vim's `"+` and `"*` are connected to your clipboard, so you can actually paste the text with `"+p` or `"*p`. Conversely, if you yank a word from Vim with `"+yiw` or `"*yiw`, you can paste that text in the external program with `Ctrl-v` (or `Cmd-v`). Note that this only works if your Vim program comes with `+clipboard` option. Check it out by running `vim --version` from the terminal. If you see a `-clipboard`, you have to install a Vim build with clipboard support on. + +You may wonder if `"*` and `"+` do the same thing, why does Vim have two different registers? Some machines use X11 window system. This system has 3 types of selections: primary, secondary, and clipboard. If your machine uses X11, Vim uses X11's *primary* selection with the `quotestar` (`"*`) register and X11's *clipboard* selection with the `quoteplus` (`"+`) register. This is only applicable if you have `xterm_clipboard` option available in your Vim build (`+xterm_clipboard` in `vim --version`). If your Vim doesn't have `xterm_clipboard`, it's not a big deal. It just means that both `quotestar` and `quoteplus` are interchangeable. + +I find doing `=*p` or `=+p` to be cumbersome. To make Vim to paste copied text from the external program with just `p`, you can add this in your `vimrc`: + +``` +set clipboard=unnamed + ``` + +Now when I copy a text from an external program, I can paste it with the unnamed register, `p`. I can also copy a text from Vim and paste it to an external program with `Ctrl-v`. If you have `+xterm_clipboard` on, you may want to use both `unnamed` and `unnamedplus` clipboard options. + +# The Black Hole Register (`"_`) + +Everytime you delete or change a text, that text is stored in Vim register automatically. Sometimes you just don't want to save anything into the register. How can you do that? + +You can use the black hole register (`"_`). To delete a line and not have Vim store the deleted line into any register, use `"_dd`. It’s the `/dev/null` of registers. + +# The Last Search Pattern Register (`"/`) + +To paste your last search (`/` or `?`) query, you can use the last search pattern register (`"/`). To paste the last search term, use `"/p`. + + +# Viewing the Registers + +To view all your registers, use the `:register` command. To view only registers "a", "1", and "-", use `:register a 1 -`. + +There is a plugin called [vim-peekaboo](https://github.com/junegunn/vim-peekaboo) that lets you to peek into the contents of the registers when you hit `"` or `@` in normal mode and `Ctrl-r` in insert mode. I find this plugin very useful because most times, I can't remember the content in my registers. Give it a try! + +# Executing a Register + +The named registers are not just for storing texts. They can also be used to execute macros with `@`. I will go over macros in the next chapter. If you store the text "Hello Vim" in register "a", and you later record a macro in the same register (`qa{macro-commands}q`), that macro will overwrite your "Hello Vim" text stored earlier (you can execute the macro stored in register "a" with `@a`). + +# Clearing a Register + +Technically, there is no need to clear any register because the next register you store under the same name will overwrite it. However, you can quickly clear any named register by recording an empty macro. For example, if you run `qaq`, Vim will record an empty macro in the register "a". Another alternative is to run the command `:call setreg('a', '')` where "a" is the register "a". One more way to clear register is to set the content of "a" register to an empty string with the expression `:let @a = ''`. + +# Putting the Content of a Register + +You can use the `:put` command to paste the content of any one register. For example, if you run `:put a`, Vim will print the content of register "a". This behaves much like `"ap`, with the difference that the normal mode command `p` prints the register content after the cursor and the command `:put` prints the register content at newline. + +# Learning Registers the Smart Way + +You made it to the end. Congratulations! That was a lot to take. If you are feeling overwhelmed by the sheer information, you are not alone. I was too, when I first started learning about Vim registers. + +I don't think you should memorize everything right away. To become productive, you can start by using only these 3 registers: +1. The unnamed register (`""`). +2. The named registers (`"a-z`). +3. The numbered Registers (`"0-9`). + +Since the unnamed register defaults to `p` or `P`, you only have to learn two registers: the named registers and the numbered registers. Gradually learn more when you need it. Take your time. + +The average human has a limited short-term memory capacity, about seven items at once. That is why in my everyday editing, I only use about three to seven named registers. There is no way I can remember all twenty-six in my head. I normally start with register "a", then "b", ascending the alphabetical order. Try it and experiment around to see what technique works best for you. + +Vim registers are powerful. Used strategically, it can save you from typing countless repeated texts. But now, it's time to learn about macros. diff --git a/ch09_macros.md b/ch09_macros.md new file mode 100644 index 0000000..bedd60f --- /dev/null +++ b/ch09_macros.md @@ -0,0 +1,264 @@ +# Macros + +When editing files, you may find yourself repeating the same actions. Wouldn't it be nice if you can do those actions once and replay them whenever you need it? With Vim macros, you can record actions and store them inside Vim registers. + +In this chapter, you will learn how to use macros to automate mundane tasks (plus it looks cool to watch your file edit itself). + +# Basic Macros + +Here is the basic syntax of a Vim macro: + +``` +qa Start recording a macro in register a +q (while recording) Stop recording macro +``` + +You can use any lowercase letters (a-z) to store macros. Here is how you can execute a macro: + +``` +@a Execute macro from register a +@@ Execute the last executed macros +``` + +Suppose you have this text and you want to uppercase everything in each line: + +``` +hello +vim +macros +are +awesome +``` + +With your cursor at the start of the line "hello", run: +``` +qa0gU$jq +``` + +Here is the breakdown of the command above: + +- `qa` starts recording a macro in the "a" register. +- `0` goes to beginning of the line. +- `gU$` uppercases the text from your current location to the end of the line. +- `j` goes down one line. +- `q` stops recording. + +To replay that macro, run `@a`. Just like many other Vim commands, you can pass a count argument to macros. For example, you can run `3@a` to execute "a" macro three times. You can run `3@@` to execute the last executed macro three times. + +# Safety Guard + +Macro execution automatically ends when it encounters an error. Suppose you have this text: + +``` +a. chocolate donut +b. mochi donut +c. powdered sugar donut +d. plain donut +``` + +If you want to uppercase the first word on each line, this macro should work: + +``` +qa0W~jq +``` + +Here's the breakdown of the command above: +- `qa` starts recording a macro in the "a" register. +- `0` goes to the beginning of the line. +- `W` goes to the next WORD. +- `~` toggles the case of the character under the cursor. +- `j` goes down one line. +- `q` stops recording. + + +I like to overcount my macro calls, so I usually would call it ninety-nine times (`99@a`). With this command, Vim does not actually run this macro ninety-nine times. When Vim reaches the last line and runs `j` action, it finds no more lines to go down to, sees an error, and stops the macro execution. + +The fact that macro execution stops upon the first error encounter is a good feature, otherwise Vim will continue to execute this macro ninety-nine times even though it already reaches the end of the line. + +# Command Line Macro + +Running `@a` in normal mode is not the only way you can execute macros in Vim. You can also run `:normal @a` command line. `:normal` allows the user to execute any normal mode command given as argument. By passing it `@a`, it is the same as running `@a` from normal mode. + +The `:normal` command accepts range as arguments. You can use this to run macro in select ranges. If you want to execute your "a" macro between lines 2 and 3, you can run `:2,3 normal @a`. I will go over command line commands in a later chapter. + +# Executing a Macro Across Multiple Files + +Suppose you have multiple `.txt` files, each containing different lists. Moreover, you need to uppercase the first word only on lines containing the word "donut". How can we execute macros across multiple files on select lines? + +First file: +``` +# savory.txt +a. cheddar jalapeno donut +b. mac n cheese donut +c. fried dumpling +``` + +Second file: +``` +# sweet.txt +a. chocolate donut +b. chocolate pancake +c. powdered sugar donut +``` + +Third file: +``` +# plain.txt +a. wheat bread +b. plain donut +``` +Here is how you can do it: +- `:args *.txt` to find all `.txt` files in your current directory. +- `:argdo g/donut/normal @a` executes the global command `g/donut/normal @a` on each file inside `:args`. +- `:argdo update` executes `update` command to save each file inside `:args` when the buffer has been modified. + +If you are not familiar with the global command `:g/donut/normal @a`, it executes the command you give (`normal @a`) on lines that match the pattern (`/donut/`). I will go over the global command in a later chapter. + +# Recursive Macro + +You can recursively execute a macro by calling the same macro register while recording that macro. Suppose you have this list again and you need to toggle the case of the first word: + +``` +a. chocolate donut +b. mochi donut +c. powdered sugar donut +d. plain donut + +``` +To do it recursively, run: +``` +qaqqa0W~j@aq +``` + +Here is the breakdown of the steps: +- `qaq` records an empty macro "a". It is necessary to record an empty macro in the same register name because when you execute the "a" macro later, you don't want that register to contain anything. +- `qa` starts recording on register "a". +- `0` goes to the first character in the current line. +- `W` goes to the next WORD. +- `~` toggles the case of the character under the cursor. +- `j` goes down one line. +- `@a` executes macro "a". When recording this, `@a` should be empty because you had just called `qaq`. +- `q` stops recording. + +Now you can just run `@a` and watch Vim execute the macro recursively. + +How does the macro know when to stop? When the macro is on the last line, it tries to run `j`, finds no extra line to go to, and stops the macro execution. + +# Appending a Macro + +If you need to add more actions to an existing macro, instead of redoing it, you can append actions to it. In the register chapter, you learned that you can append a named register by using its uppercased symbol. To append actions to a macro in register "a", use register "A". Suppose in addition to toggling the case of the first word, you also want to add a dot at the end of the line. + +Assume you have the following actions stored as a macro in register "a": + +``` +0W~ +``` +This is how you can do it: +``` +qAA.q +``` +The breakdown: +- `qA` starts recording the macro in register "A". +- `A.` inserts a dot (".") at the end of the line (`A`), then exits insert mode (``). +- `q` stops recording macro. + +Now when you execute `@a`, it goes to the first character in the line (`0`), goes to the next WORD (`W`), toggles the case of the character under the cursor (`~`), goes to insert mode at the end of the line (`A`), writes a dot ("."), and exits insert mode (``). + +# Amending a Macro + +Appending is great technique to add new actions at the end of your existing macros, but what if you need to add new actions in the middle of it? In this section, I will show you how to amend a macro. + +Suppose between uppercasing the first word and adding a period at the end of the line, you need to add the word "deep fried" right before the word "donut" *(because the only thing better than regular donuts are deep fried donuts)*. + +I will reuse the text from earlier section: +``` +a. chocolate donut +b. mochi donut +c. powdered sugar donut +d. plain donut +``` + +First, let's call the existing macro (assume you have kept the macro from the previous section in register "a") with `:put a`: + +``` +0W~A.^[ +``` + +What is this `^[`? Didn't you do `0W~A.`? `^[` is Vim's *internal code* representation of ``. With certain special keys, Vim prints the representation of those keys in the form of internal codes. Some common keys that have internal code representations are ``, ``, and ``. There are more special keys, but they are not within the scope of this chapter. + +Back to the macro, right after the toggle case operator (`~`), let's add the instructions to go to the end of the line (`$`), go back one word (`b`), go to the insert mode (`i`), type "deep fried " (don't forget the space after "fried "), and exit insert mode (``). + +Here is what you will end up with: + +``` +0W~$bideep fried A.^[ +``` + +There is a small problem. Vim does not understand ``. You will have to write the internal code representation for the `` you just added. While in insert mode, you press `Ctrl-v` followed by ``. Vim will print `^[`.` Ctrl-v` is an insert mode operator to insert the next non-digit character *literally*. Your macro code should look like this now: + +``` +0W~$bideep fried ^[A.^[ +``` + +To add the amended instruction into register "a", you can do it the same way as adding a new entry into a named register. At the start of the line, run `"ay$`. This tells Vim that you're using the named register "a" (`"a`) to store the yanked text from the current position to the end of the line (`y$`). + +Now when you execute `@a`, your macro will toggle the case of the first word, add "deep fried " before "donut", and add a "." at the end of the line. + +An alternative way to amend a macro is to use a command line expression. Do `:let @a="`, then do `Ctrl-r Ctrl-r a`, this will literally paste the content of register "a". Finally, don't forget to close the double quotes (`"`). If you need to insert special characters using internal codes while editing a command line expression, you can use `Ctrl-v`. + +# Macro Redundancy + +You can easily duplicate macros from one register to another. For example, to duplicate a macro in register "a" to register "z", you can do `:let @z = @a`. `@a` represents the content of register "a". Now if you run `@z`, it does the exact same actions as `@a`. + +I find creating a redundancy useful on my most frequently used macros. In my workflow, I usually record macros in the first seven alphabetical letters (a-g) and I often replace them without much thought. If I move the useful macros towards the end of the alphabets, I can preserve them without worrying that I might accidentally replace them. + +# Series vs Parallel Macro + +Vim can execute macros in series and parallel. Suppose you have this text: + +``` +import { FUNC1 } from "library1"; +import { FUNC2 } from "library2"; +import { FUNC3 } from "library3"; +import { FUNC4 } from "library4"; +import { FUNC5 } from "library5"; +``` + +If you want to record a macro to lowercase all the uppercased "FUNC", this macro should work: + +``` +qa0f{gui{jq +``` + +Here is the breakdown: +- `qa` starts recording in register "a". +- `0` goes to first line. +- `f{` finds the first instance of "{". +- `gui{` lowercases (`gu`) the text inside the bracket text-object (`i{`). +- `j` goes down one line. +- `q` stops macro recording. + +Now you can run `99@a` to execute it on the remaining lines. However, what if you have this import expression inside your file? + +``` +import { FUNC1 } from "library1"; +import { FUNC2 } from "library2"; +import { FUNC3 } from "library3"; +import foo from "bar"; +import { FUNC4 } from "library4"; +import { FUNC5 } from "library5"; +``` + +Running `99@a`, only executes the macro three times. It does not execute the macro on last two lines because the execution fails to run `f{` on the "foo" line. This is expected when running the macro in series. You can always go to the next line where "FUNC4" is and replay that macro again. But what if you want to get everything done in one go? You can run the macro in parallel. + +Recall from earlier section that macros can be executed using the command line command `:normal` (ex: `:3,5 normal @a` to execute macro "a" on lines 3-5). If you run `:1,$ normal @a`, you will see that the macro is being executed on all lines except the "foo" line. It works! + +Although internally Vim does not actually run the macros in parallel, outwardly, it behaves like such. Vim executes `@a` *independently* on each line from the first line to the last line (`1,$`). Since Vim executes these macros independently, each line does not know that one of the macro executions had failed on the "foo" line. + +# Learn Macros the Smart Way + +Many things you do in editing are repetitive. To get better at editing, get into the habit of detecting repetitive actions. Use macros (or dot command) so you don't have to perform the same action twice. Almost everything that you can do in Vim can be done with macros. + +In the beginning, I find it very awkward to write macros, but don't give up. With enough practice, you will get into the habit of automating everything. + +You might find it helpful to use mnemonics to help remember your macros. If you have a macro that creates a function, use the "f" register (`qf`). If you have a macro for numerical operations, the "n" register may be a good fit (`qn`). Name it with the *first named register* that comes to your mind when you think of that operation. I also find that "q" register makes a good default macro register because `qq` does not require much brain power to use. Lastly, I like to increment my macros in alphabetical orders, like `qa`, then `qb`, then `qc`, and so on. Find a method that works best for you. diff --git a/ch10_undo.md b/ch10_undo.md new file mode 100644 index 0000000..88f1236 --- /dev/null +++ b/ch10_undo.md @@ -0,0 +1,266 @@ +# Undo + +Undo is an essential feature in any modern software. Vim's undo system is not only capable of undoing and redoing mistakes, but allows you to manipulate and retrieve text across time. In this chapter, you will learn how to undo and redo your text, navigate an undo branch, persist undo, and travel through time. + +# Undo, Redo, and UNDO + +To perform a basic undo, you can use `u` or run `:undo`. + +If you have this text: +``` +one + +``` + +Add another text: +``` +one +two +``` + +If you do `u`, Vim undoes the text "two". + +How does Vim know how much to undo? Vim undoes a single "change" at a time, similar to a dot command's change (unlike the dot command, command-line commands also count as change). + +To redo the last change, run `Ctrl-R` or `:redo`. After you undo the text above to remove "two", you can run `Ctrl-R` to get the removed text back. + +Vim also has UNDO that you can run with `U`. It undoes all latest changes. + +How is `U` different from `u`? First, `U` removes *all* the changes on the latest changed line, while `u` only removes one change at a time. Second, while doing `u` does not count as a change, doing `U` counts as a change. + +Back to this example: +``` +one +two +``` + +Change the second line with "three" (`ciwthree`): + +``` +one +three +``` + +Change the second line again and replace it with "four" (`ciwfour`): + +``` +one +four +``` + +If you press `u`, you will see "three". If you press `u` again, you'll see "two". +However, if instead of pressing `u` when you still had the text "four", you had pressed `U`, you will see: + +``` +one + +``` + +`U` bypasses all the intermediary changes and goes to the original state when you started (the empty line below the text "one"). In addition, since UNDO actually creates a new change in Vim, you can UNDO your UNDO. `U` followed by `U` will undo itself. You can press `U`, then `U`, then `U`, forever, and you will see the same two texts toggled back and forth. + +I personally do not use `U` because it is hard to remember the original state (I seldom ever need it). The most I have ever used `U` is when I accidentally pressed `Shift-u`. + +Vim sets a maximum number of how many times you can undo in `undolevels` option variable. You can check it with `:echo &undolevels`. I have mine set to be 1000. To change yours to 1000, run `:set undolevels=1000`. Feel free to set it to any number you like. + +# Breaking the Blocks + +I mentioned earlier that `u` undoes a single "change" similar to the dot command's change. Any text entered between entering the insert mode and exiting is counted as a change. + +If you do `ione two three` then press `u`, Vim removes the entire "one two three" text because it is considered a change. This would have been acceptable if you have a short text, but what if you have written several paragraphs under one insert mode session without exiting and later you realized you made a mistake? If you press `u`, everything you had written would be removed. Wouldn't it be useful if you can press `u` to remove only a section of your text? + +Luckily, you can break the undo blocks. When you are typing in insert mode, pressing `Ctrl-G u` creates an undo breakpoint. For example, if you do `ione two three`, then press `u`, you will only lose the text "three". Press `u` one more time to remove "two". When you write a long text, use `Ctrl-G u` strategically. The end of each sentence, between two paragraphs, or after each line of code are good locations to add undo breakpoints to make it easier to undo your mistakes if you ever make one. + +It is also useful to create an undo breakpoint when deleting chunks in insert mode with `Ctrl-W` (delete the word before the cursor) and `Ctrl-U` (delete all text before the cursor). A friend suggested to use the following mappings: +``` +inoremap u +inoremap u +``` +With these, you can easily recover the deleted texts. + +# Undo Tree + +Vim stores every change ever written in an undo tree. If you start a new empty file: + +``` + +``` + +Add a new text: + +``` +one + +``` + +Add a new text: + +``` +one +two +``` + +Undo once: + +``` +one + +``` + +Add a different text: + +``` +one +three +``` + +Undo again: +``` +one + +``` + +And add another different text: +``` +one +four +``` + + +Now if you undo, you will lose the text "four" you just added: +``` +one + +``` + +If you undo one more time: + +``` + +``` + +You will lose the text "one". In most text editor, getting the texts "two" and "three" back would have been impossible, but not with Vim. Run `g+`, you'll get your text "one" back: + +``` +one + +``` + +Type `g+` again and you will see an old friend: +``` +one +two +``` + +Let's keep going. Press `g+` again: +``` +one +three +``` + +Press `g+` one more time: +``` +one +four +``` + +In Vim, every time you press `u` and then make a different change, Vim stores the previous state's text by creating an "undo branch". In this example, after you typed "two", then pressed `u`, then typed "three", you created an undo leaf branch that stores the state containing the text "two". At that moment, the undo tree contained at least two leaf nodes: the main node containing the text "three" (most recent) and the undo branch node containing the text "two". If you had done another undo and typed the text "four", you now have at least three nodes: a main node containing the text "four" and two nodes containing the texts "three" and "two". + +To traverse each node in the undo tree, you can use `g+` to go to a newer state and `g-` to go to an older state. The difference between `u`, `Ctrl-R`, `g+`, and `g-` is that both `u` and `Ctrl-R` traverse only the *main* nodes in undo tree while `g+` and `g-` traverse *all* nodes in the undo tree. + +Undo tree is not easy to visualize. I find [vim-mundo](https://github.com/simnalamburt/vim-mundo) plugin to be very useful to help visualize Vim's undo tree. Give it some time to play around with it. + +# Persistent Undo + +If you start Vim, open a new file, and immediately press `u`, Vim will probably display "*Already at oldest change*" warning. Vim can preserve your undo history with an undo file with `:wundo`. + +Create a file `mynumbers.txt`. Type: + +``` +one +``` + +Then type another line (make sure you either exit insert mode first or create an undo block): + +``` +one +two +``` + +Type another line: +``` +one +two +three +``` + +Now create your undo file. The syntax is `:wundo myundofile`. If you need to overwrite an existing undo file, you can add `!` after `wundo`. +``` +:wundo! mynumbers.undo +``` + +Then exit Vim. + +By now you should have `mynumbers.txt` and `mynumbers.undo` files in your directory. Open up `mynumbers.txt` again and try pressing `u`. You can't. You haven't made any changes since you opened the file. Now load your undo history by reading the undo file with `:rundo`: + +``` +:rundo mynumbers.undo +``` + +Now if you press `u`, Vim removes "three". Press `u` again to remove "two". It is like you never even closed Vim! + +If you want to have an automatic undo persistence, one way to do it is by adding these lines in your vimrc: + +``` +set undodir=~/.vim/undo_dir +set undofile +``` + +I think it's better to put all the undofiles in one centralized directory, in this case, inside the `~/.vim` directory. The name `undo_dir` is arbitrary. `set undofile` tells Vim to turn on `undofile` feature because it is off by default. Now whenever you save, Vim automatically creates and updates the relevant file inside the `undo_dir` directory (make sure that you create the actual `undo_dir` directory inside `~/.vim` directory before running this). + +# Time Travel + +Who says that time travel doesn't exist? Vim can travel to a text state in the past with `:earlier` command-line command. + +If you have this text: +``` +one + +``` +Then later you write another line: +``` +one +two +``` + +If you had typed "two" less than ten seconds ago, you can go back to the state where "two" didn't exist ten seconds ago with: +``` +:earlier 10s +``` + +You can use `:undolist` to see when the last change was made. `:earlier` also accepts minutes (`m`), hours (`h`), and days (`d`) as arguments. + +``` +:earlier 10s go to the state 10 seconds before +:earlier 10m go to the state 10 minutes before +:earlier 10h go to the state 10 hours before +:earlier 10d go to the state 10 days before +``` + +In addition, it also accepts a regular `count` as argument to tell Vim to go to the older state `count` times. For example, if you do `:earlier 2`, Vim will go back to an older text state two changes ago. It is the same as doing `g-` twice. Lastly, you can also tell `:earlier` to go to the older text state 10 saves ago with `:earlier 10f`. + +The same set of arguments work with `:earlier` counterpart: `:later`. +``` +:later 10s go to the state 10 seconds later +:later 10m go to the state 10 minutes later +:later 10h go to the state 10 hours later +:later 10d go to the state 10 days later +:later 10 go to the newer state 10 times +:later 10f go to the state 10 saves later +``` + +# Learn Undo the Smart Way + +`u` and `Ctrl-R` are two indispensable Vim commands. Learn them first. I do not use UNDO in my workflow, however I think it's good to be aware that it exists. Next, learn how to use `:earlier` and `:later` using the time argumentsfirst. After that, take your time to understand the undo tree. The [vim-mundo](https://github.com/simnalamburt/vim-mundo) plugin helped me a lot. Type along the texts in this chapter and check the undo tree as you make each change. Once you grasp it, you will never see undo system the same way again. + +Prior to this chapter, you learned how to find any text in a project space, with undo, you can now find any text in a time dimension. You are now able to search for any text by its location and time written. You have achieved Vim-omnipresence. + diff --git a/ch11_visual_mode.md b/ch11_visual_mode.md new file mode 100644 index 0000000..30b28a4 --- /dev/null +++ b/ch11_visual_mode.md @@ -0,0 +1,306 @@ +# Visual Mode + +You probably know that you can highlight a block of text and apply changes to it. Vim can too, with visual mode. Vim has three different visual modes to use. In this chapter, you will learn how to use each visual mode to manipulate blocks of texts efficiently. + +# The Three Types of Visual Modes +The three modes are: + +``` +v Character-wise visual mode +V Line-wise visual mode +Ctrl-v Block-wise visual mode +``` + +If you have the text: +``` +one +two +three +``` + +Character-wise visual mode is used to select individual characters. Press `v` on the first character on the first line. Then go down to the next line with `j`. It highlights all texts from "one" up to your cursor location. Now if you press `gU`, Vim uppercases the highlighted characters. + +Line-wise visual mode works with line units. Press `V` and watch Vim selects the entire line your cursor is on. Just like character-wise visual mode, if you run `gU`, Vim uppercases the highlighted characters. + +Block-wise visual mode works with rows and columns. It gives you more freedom to move around than the other two modes. Press `Ctrl-V`. Vim highlights the character under the cursor just like character-wise visual mode, except instead of highlighting each character until the end of the line before going to the next line, it can go to the next line without highlighting the entire character on the current line. Try moving around with `h/j/k/l` and watch the cursor movements. + +On the bottom left of your Vim window, you will see either `-- VISUAL --`, `-- VISUAL LINE --`, or `-- VISUAL BLOCK --` displayed to indicate which visual mode you are in. + +While you are inside a visual mode, you can switch to another visual mode by pressing either `v`, `V`, or `Ctrl-V`. For example, if you are in line-wise visual mode and you want to switch to block-wise visual mode, run `Ctrl-V`. Try it! + +There are three ways to exit the visual mode: `esc`, `Ctrl-C`, and the same key as your current visual mode. + +What the latter one means is if you are currently in the line-wise visual mode (`V`), you can exit it by pressing `V` again. If you are in the character-wise visual mode, you can exit it by pressing `v`. If you are in the block-wise visual mode, press `Ctrl-V`. + +There is actually one more way to enter the visual mode: +``` +gv Go to the previous visual mode +``` + +It will start the same visual mode on the same highlighted text block as you did last time. + +# Visual mode navigation + +While in a visual mode, you can expand the highlighted text block with Vim motions. + +Let's use the same text you used earlier: + +``` +one +two +three +``` + +This time let's start from the line "two". Press `v` to go to the character-wise visual mode: + +``` +one +[t]wo +three +``` + +Press `j` and Vim will highlight all the text from the line "two" down to the first character of the line "three". + +``` +one +[two +t]hree +``` + +Suppose you just realized that you also need to highlight the line "one" too, so you press `k`. To your dismay, it now excludes "three". Pressing `k` actually reduces the highlight, not expands it. + +``` +one +[t]wo +three +``` + +Is there a way to freely expand visual selection to go to any direction you want? + +The answer is yes. Let's back up a little bit to where you have the line "two" and "three" highlighted. + +``` +one +[two +t]hree <-- cursor +``` + +Visual highlight follows the cursor movement. If you want to expand it upward to line "one", you need to move the cursor up when the cursor is on the letter "two", not "three". Right now your cursor is on the line "three". To move it, toggle the cursor location with either `o` or `O`. + +``` +one +[two <-- cursor +t]hree +``` + +Now when you press `k`, it no longer reduces the selection, but expands it upward. + +``` +[one +two +t]hree +``` + +# Visual Mode Grammar + +Visual mode is one of Vim's modes. Being a mode means that the same key may work differently than in another mode. Luckily, visual mode shares many common keys with normal mode. + +For example, if you have the text: + +``` +one +two +three +``` + +Highlight the lines "one" and "two" with the line-wise visual mode (`V`): +``` +[one +two] +three +``` + +Pressing `d` will delete the selection, similar to normal mode. Notice the grammar rule from normal mode, verb + noun, does not apply. The same verb is still there (`d`), but there is no noun in visual mode. The grammar rule in visual mode is noun + verb, where noun is the highlighted text. Select the text block first, then operate. + +In normal mode, there are some commands that do not require a motion, like `x` to delete a single character under the cursor and `rx` to replace the character under the cursor with "x". In visual mode, these commands are now being applied to the entire highlighted text instead of a single character. Back at the highlighted text: +``` +[one +two] +three +``` + +Running `x` deletes all highlighted texts. + +You can use this behavior to quickly create a header in markdown text. Suppose you have a text in a markdown file: +``` +Chapter One +``` + +You need to quickly turn this header into a header. First you copy the text with `yy`, then paste it with `p`: +``` +Chapter One +Chapter One +``` + +Now go to the second line, select it with line-wise visual mode: +``` +Chapter One +[Chapter One] +``` + +In markdown you can create a header by adding a series of `=` below a text, so you will replace the entire highlighted text by running `r=`: + +``` +Chapter One +=========== +``` + +To learn more about operators in visual mode, check out `:h visual-operators`. + + +# Visual Mode and Ex Commands + +You can selectively apply Ex commands on a highlighted text block. If you have these expressions: + +``` +const one = "one"; +const two = "two"; +const three = "three"; +``` + +You need to substitute only the first two lines of "const" with "let". Highlight the first two lines with *any* visual mode and run the substitute command `:s/const/let/g`: +``` +let one = "one"; +let two = "two"; +const three = "three"; +``` +Notice I said you can do this with *any* visual mode. You do not have to highlight the entire line to run Ex command on that line. As long as you select at least a character on each line, the Ex command will be applied. + +# Editing Across Multiple Lines + +You can edit text across multiple lines in Vim using the block-wise visual mode. If you need to add a semicolon at the end of each line: + +``` +const one = "one" +const two = "two" +const three = "three" +``` + +With your cursor on the first line: +- Run block-wise visual mode and go down two lines (`Ctrl-V jj`). +- Highlight to the end of the line (`$`). +- Append (`A`) then type ";". +- Exit visual mode (`esc`). + +You should see the appended ";" on each line. By the way, while in block-wise visual mode, to enter the insert mode, you can use either `A` to enter the text after the cursor or `I` to enter the text before the cursor. Do not confuse them with `A` and `I` from normal mode. + +Alternatively, you can also use the `:normal` command: + +- Highlight all 3 lines (`vjj`). +- Type `:normal! A;`. + +Remember, `:normal` command executes normal mode commands. You can instruct it to run `A;` to append text ";" at the end of the line. + +# Incrementing numbers + +Vim has `Ctrl-X` and `Ctrl-A` commands to decrement and increment numbers. When used with visual mode, you can increment numbers across multiple lines. + +If you have these HTML elements: +``` +
+
+
+
+
+``` + +It is a bad practice to have several ids having the same name, so let's increment them to make them unique: +- Move your cursor to the *second* "1". +- Start block-wise visual mode and go down 3 lines (`Ctrl-V 3j`). This highlights the remaining "1"s. +- Run `g Ctrl-A`. + +You should see this result: +``` +
+
+
+
+
+``` + +`g Ctrl-A` increments numbers on multiple lines. `Ctrl-X/Ctrl-A` can increment letters too. If you run: + +``` +:set nrformats+=alpha +``` + +The `nrformats` option instructs Vim which bases are considered as "numbers" for `Ctrl-A` and `Ctrl-X` to increment and decrement. By adding `alpha`, an alphabetical character is now considered as a number. If you have the following HTML elements: +``` +
+
+
+
+
+``` + +Put your cursor on the second "app-a". Use the same technique as above (`Ctrl-V 3j` then `g Ctrl-A`) to increment the ids. +``` +
+
+
+
+
+``` +# Selecting the Last Visual Mode Area + +You learned that `gv` can quickly highlight the last visual mode highlight. You can also go to the location of the start and the end of the last visual mode with these two special marks: + +``` +`< Go to the last place of the previous visual mode highlight +`> Go to the first place of the previous visual mode highlight +``` + +I want you to observe something. Earlier, I mentioned that you can selectively execute Ex commands on a highlighted text, like `:s/const/let/g`. When you did that, you should see this: +``` +:`<,`>s/const/let/g +``` + +You were actually executing `s/const/let/g` command using marks as range. You can always edit these marks anytime you wish. If instead you needed to substitute from the start of the highlighted text to the end of the file, you just change the command line to: +``` +:`<,$s/const/let/g +``` + +# Entering Visual Mode from Insert Mode + +You can also enter visual mode from the insert mode. To go to character-wise visual mode while you are in insert mode: + +``` +Ctrl-O v +``` + +Recall that running `Ctrl-O` while in the insert mode lets you to execute a normal mode command. While in this normal-mode-command-pending mode, run `v` to enter character-wise visual mode. Notice that on the bottom left of the screen, it says `--(insert) VISUAL--`. This trick works with any visual mode operator: `v`, `V`, and `Ctrl-V`. + +# Select Mode + +Vim has a mode similar to visual mode called the *select mode*. Like visual mode, it also has three different modes: +``` +gh Character-wise select mode +gH Line-wise select mode +gCtrl-h Block-wise select mode +``` + +Select mode emulates a regular editor's text highlighting behavior closer than Vim's visual mode does. + +In a regular editor, after you highlight a text block and type a letter, say the letter "y", it will delete the highlighted text and insert the letter "y". + +If you highlight a line of text with line-wise select mode (`gH`) and type "y", it will delete the highlighted text and insert the letter "y", much like the regular text editor. + +Contrast this behavior with visual mode: if you highlight a line of text with line-wise visual mode (`V`) and type "y", the highlighted text will not be deleted and replaced by the literal letter "y". It will only be yanked and stored in the yanked register `"0`. + +I personally never used select mode, but it's good to know that it exists. + +# Learn Visual Mode the Smart Way + +The visual mode is Vim's representation of the text highlighting procedure. + +If you find yourself using visual mode operation far more often than normal mode operations, be careful. I think this is an anti-pattern. It takes more keystrokes to run a visual mode operation than its normal mode counterpart. If you need to delete an inner word, why use four keystrokes, `viwd` (visually highlight an inner word then delete), if you can accomplish it with just three keystrokes (`diw`)? The latter is more direct and concise. Of course, there will be times when visual modes are appropriate, but in general, favor a more direct approach. diff --git a/ch12_search_and_substitute.md b/ch12_search_and_substitute.md new file mode 100644 index 0000000..da773c3 --- /dev/null +++ b/ch12_search_and_substitute.md @@ -0,0 +1,709 @@ +# Search and Substitute +This chapter covers two separate but related concepts: search and substitute. Many times, the texts that you are searching for are not straightforward and you must search for a common pattern. By learning how to use meaningful patterns in search and substitute instead of literal strings, you will be able to target any text quickly. + +As a side note, in this chapter, I will mainly use `/` when talking about search. Everything you can do with `/` can also be done with `?`. + +# Smart Case Sensitivity + +It can be tricky trying to match the case of the search term. If you are searching for the text "Learn Vim", you can easily mistype the case of one letter and get a false search result. Wouldn't it be easier and safer if you can match any case? This is where the option `ignorecase` shines. Just add `set ignorecase` in your vimrc and all your search terms become case insensitive. Now you don't have to do `/Learn Vim` anymore. `/learn vim` will work. + +However, there are times when you need to search for a case specific phrase. One way to do that is to turn off `ignorecase` option with `set noignorecase`, but that is a lot of work to turn on and off each time you need to search for a case sensitive phrase. + +Is there a setting that allows you to do case insensitive search most of the time, but also know to do case sensitive search when you need it? Turns out there is a way. + +Vim has a `smartcase` option to override `ignorecase` if the search pattern *contains at least one uppercase character*. You can combine both `ignorecase` and `smartcase` to perform a case insensitive search when you enter all lowercase characters and a case sensitive search when you enter one or more uppercase characters. + +Inside your vimrc, add: + +``` +set ignorecase smartcase +``` + +If you have these texts: +``` +hello +HELLO +Hello +``` + +You can control case insensitivity with the case of your search phrase: +- `/hello` matches "hello", "HELLO", and "Hello". +- `/HELLO` matches only "HELLO". +- `/Hello` matches only "Hello" + +There is one downside. What if you need to search for only a lowercase string? When you do `/hello`, Vim will always match its uppercase variants. What if you don't want to match them? You can use `\c` pattern in front of your search term to tell Vim that the subsequent search term will be case sensitive. If you do `/\chello`, it will strictly match "hello", not "HELLO" or "Hello". + +# First and Last Character in a Line + +You can use `^` to match the first character in a line and `$` to match the last character in a line. + +If you have this text: + +``` +hello hello +``` + +You can target the first "hello" with `/^hello`. The character that follows `^` must be the first character in a line. To target the last "hello", run `/hello$`. The character before `$` must be the last character in a line. + +If you have this text: + +``` +hello hello friend +``` + +Running `/hello$` will match anything because "friend" is the last term in that line, not "hello". + +# Repeating Search + +You can repeat the previous search with `//`. If you have just searched for `/hello`, running `//` is equivalent to running `/hello`. This shortcut can save you some keystrokes especially if you just did a long search term. Also recall that you can also use `n` and `N` to repeat the last search with the same direction and opposite direction, respectively. + +What if you want to quickly recall *n* last search term? You can quickly traverse the search history by first pressing `/`, then press `up`/`down` arrow keys (or `Ctrl-N`/`Ctrl-P`) until you find the search term you need. To see all your search history, you can run `:history /`. + +When you reach the end of a file while searching, Vim throws an error: `"Search hit the BOTTOM without match for: "`. Sometimes this can be a good safeguard from oversearching, but other times you want to cycle the search back to the top again. You can use the `set wrapscan` option to make Vim to search back at the top of the file when you reach the end of the file. To turn this feature off, do `set nowrapscan`. + +# Searching for Alternative Words + +It is common to search for multiple words at once. If you need to search for *either* "hello vim" or "hola vim", but not "salve vim" or "bonjour vim", you can use the `|` pipe alternative syntax. + +Given this text: + +``` +hello vim +hola vim +salve vim +bonjour vim +``` + +To match both "hello" and "hola", you can do `/hello\|hola`. You have to escape (`\`) the pipe (`|`) operator, otherwise Vim will literally search for the string "|". + +If you don't want to type `\|` every time, you can use the `magic` syntax (`\v`) at the start of the search: `/\vhello|hola`. I will not cover `magic` in this chapter, but with `\v`, you don't have to escape special characters anymore. To learn more about `\v`, feel free to check out `:h \v`. + +# Setting the Start and End of a Match + +Maybe you need to search for a text that is a part of a compound word. If you have these texts: + +``` +11vim22 +vim22 +11vim +vim +``` + +If you need to select "vim" but only when it starts with "11" and ends with "22", you can use `\zs` (starting match) and `\ze` (ending match) operators. Run: + +``` +/11\zsvim\ze22 +``` + +Vim still has to match the entire pattern "11vim22", but only highlights the pattern sandwiched between `\zs` and `\ze`. Another example: + +``` +foobar +foobaz +``` + +If you need to search for the "foo" in "foobaz" but not in "foobar", run: + +``` +/foo\zebaz +``` + +# Searching Character Ranges + +All your search terms up to this point have been a literal word search. In real life, you may have to use a general pattern to find your text. The most basic pattern is the character range, `[ ]`. + +If you need to search for any digit, you probably don't want to type `/0\|1\|2\|3\|4\|5\|6\|7\|8\|9\|0` every single time. Instead, use `/[0-9]` to match for a single digit. The `0-9` expression represents a range of numbers 0-9 that Vim will try to match, so if you are looking for digits between 1 to 5 instead, use `/[1-5]`. + +Digits are not the only data types Vim can look up. You can also do `/[a-z]` to search for lowercase alphas and `/[A-Z]` to search for uppercase alphas. + +You can combine these ranges together. If you need to search for digits 0-9 and both lowercase and uppercase alphas from a to f (a hex), you can do `/[0-9a-fA-F]`. + +To do a negative search, you can add `^` inside the character range brackets. To search for a non-digit, run `/[^0-9]`. Vim will match any character as long as it is not a digit. Beware that the caret (`^`) inside the range brackets is different from the beginning-of-a-line caret (ex: `/^hello`). If a caret is outside of a pair of brackets and is the first character in the search term, it means "the first character in a line". If a caret is inside a pair of brackets and it is the first character inside the brackets, it means a negative search operator. `/^abc` matches the first "abc" in a line and `/[^abc]` matches any character except for an "a", "b", or "c". + +# Searching for Repeating Characters + +If you need to search for double digits in this text: + +``` +1aa +11a +111 +``` + +You can use `/[0-9][0-9]` to match a two-digit character, but this method is unscalable. What if you need to match twenty digits? Typing `[0-9]` twenty times is not a fun experience. That's why you need a `count` argument. + +You can pass `count` to your search. It has the following syntax: + +``` +{n,m} +``` + +By the way, these `count` braces need to be escaped when you use them in Vim. The `count` operator is placed after a single character you want to increment. + +Here are the four different variations of the `count` syntax: +- `{n}` is an exact match. `/[0-9]\{2\}` matches the two digit numbers: "11" and the "11" in "111". +- `{n,m}` is a range match. `/[0-9]\{2,3\}` matches between 2 and 3 digit numbers: "11" and "111". +- `{,m}` is an up-to match. `/[0-9]\{,3\}` matches up to 3 digit numbers: "1", "11", and "111". +- `{n,}` is an at-least match. `/[0-9]\{2,\}` matches at least a 2 or more digit numbers: "11" and "111". + +The count arguments `\{0,\}` (zero or more) and `\{1,\}` (one or more) are common search patterns and Vim has special operators for them: `*` and `+` (`+` needs to be escaped while `*` works fine without the escape). If you do `/[0-9]*`, it is the same as `/[0-9]\{0,\}`. It searches for zero or more digits. It will match "", "1", "123". By the way, it will also match non-digits like "a", because there is technically zero digit in the letter "a". Think carefully before using `*`. If you do `/[0-9]\+`, it is the same as `/[0-9]\{1,\}`. It searches for one or more digits. It will match "1" and "12". + +# Predefined Ranges + +Vim has predefined ranges for common characters like digits and alphas. I will not go through every single one here, but you can find the full list inside `:h /character-classes`. Here are the useful ones: + +``` +\d Digit [0-9] +\D Non-digit [^0-9] +\s Whitespace character (space and tab) +\S Non-whitespace character (everything except space and tab) +\w Word character [0-9A-Za-z_] +\l Lowercase alphas [a-z] +\u Uppercase character [A-Z] +``` + +You can use them like you would use character ranges. To search for any single digit, instead of using `/[0-9]`, you can use `/\d` for a more concise syntax. + +# More Search Examples +## Capturing a Text Between a Pair of Similar Characters + +If you want to search for a phrase surrounded by a pair of double quotes: + +``` +"Vim is awesome!" +``` + +Run this: + + +`/"[^"]\+"` + + +Let's break it down: +- `"` is a literal double quote. It matches the first double quote. +- `[^"]` means any character except for a double quote. It matches any alphanumeric and whitespace character as long as it is not a double quote. +- `\+` means one or more. Since it is preceded by `[^"]`, Vim looks for one or more character that is not a double quote. +- `"` is a literal double quote. IT matches the closing double quote. + +When sees the first `"`, it begins the pattern capture. The moment Vim sees the second double quote in a line, it matches the second `"` pattern and stops the pattern capture. Meanwhile, all non-`"` characters between the two `"` are captured by the `[^"]\+` pattern, in this case, the phrase `Vim is awesome!`. This is a common pattern to capture a phrase surrounded by a pair of similar delimiters: to capture a phrase surrounded by a single quote, you can use `/'[^']\+'`. + +## Capturing a Phone Number + +If you want to match a US phone number separated by a hyphen (`-`), like `123-456-7890`, you can use: +``` +/\v\d\{3\}-\d\{3\}-\d\{4\} +``` + +US Phone number consists of a set of three digit number, followed by another three digits, and finally by four digits. Let's break it down: +- `\d\{3\}` matches a digit repeated exactly three times +- `-` is a literal hyphen + +This pattern is also useful to capture any repeating digits, such as IP addresses and zip codes. + +That covers the search part of this chapter. Now let's move to substitution. + +# Basic Substitution + +Vim's substitute command is a useful command to quickly find and replace any pattern. The substitution syntax is: + +``` +:s/old-pattern/new-pattern/ +``` + +Let's start with a basic usage. If you have this text: + +``` +vim is good +``` + +Let's substitute "good" with "awesome" because Vim is awesome. Run `:s/good/awesome/.` You should see: + +``` +vim is awesome +``` + +# Repeating the Last Substitution + +You can repeat the last substitute command with either the normal command `&` or by running `:s`. If you have just run `:s/good/awesome/`, running either `&` or `:s` will repeat it. + +Also, earlier in this chapter I mentioned that you can use `//` to repeat the previous search pattern. This trick works with the substitution command. If `/good` was done recently and you leave the first substitute pattern argument blank, like in `:s//awesome/`, it is the same as running `:s/good/awesome/`. + +# Substitution Range + +Just like many Ex commands, you can pass a range argument into the substitute command. The syntax is: + +``` +:[range]s/old/new/ +``` + +If you have these expressions: + +``` +let one = 1; +let two = 2; +let three = 3; +let four = 4; +let five = 5; +``` + +To substitute the "let" into "const" on lines three to five, you can do: + +``` +:3,5s/let/const/ +``` + +The substitute command's range syntax is similar to the count syntax in search (`{n,m}`), with minor differences. Here are some variations to pass the range: + +- `:,3/let/const/` - if nothing is given before the comma, it represents the current line. Substitute from current line to line 3. +- `:1,s/let/const/` - if nothing is given after the comma, it also represents the current line. Substitute from line 1 to current line. +- `:3s/let/const/` - if only one value is given as range (no comma), it does substitution on that line only. + +In Vim, `%` usually means the entire file. If you run `:%s/let/const/`, it will do substitution on all lines. + +# Pattern Matching + +The next few sections will cover basic regular expressions. A strong pattern knowledge is essential to master the substitute command. + +If you have the following expressions: + +``` +let one = 1; +let two = 2; +let three = 3; +let four = 4; +let five = 5; +``` + +To add a pair of double quotes around the digits: + +``` +:%s/\d/"\0"/ +``` + +The result: +``` +let one = "1"; +let two = "2"; +let three = "3"; +let four = "4"; +let five = "5"; +``` + +Let's break down the command: +- `:%s` targets the entire file to perform substitution. +- `\d` is Vim's predefined range for digits (`[0-9]`). +- `"\0"` the double quotes are literal double quotes. `\0` is a special character representing "the whole matched pattern". The matched pattern here is a single digit number, `\d`. On line one, `\0` has the value of "1". On line two, value of "2". On line three, value of "3", and so on. + +Alternatively, `&` also represents "the whole matched pattern" like `\0`. `:s/\d/"&"/` would have also worked. + +Let's consider another example. Given these expressions: +``` +one let = "1"; +two let = "2"; +three let = "3"; +four let = "4"; +five let = "5"; +``` +You need to swap all the "let" with the variable names. To do that, run: + +``` +:%s/\(\w\+\) \(\w\+\)/\2 \1/ +``` + +The command above contains too many backslashes and is hard to read. It is more convenient to use the `\v` operator: + +``` +:%s/\v(\w+) (\w+)/\2 \1/ +``` + +The result: +``` +let one = "1"; +let two = "2"; +let three = "3"; +let four = "4"; +let five = "5"; +``` + +Great! Let's break down that command: +- `:%s` targets all the lines in the file +- `(\w+) (\w+) `groups the patterns. `\w` is one of Vim's predefined ranges for a word character (`[0-9A-Za-z_]`). The `( )` surrounding it captures a word character match in a group. Notice the space between the two groupings. `(\w+) (\w+)` captures in two groups. On the first line, the first group captures "one" and the second group captures "two". +- `\2 \1` returns the captured group in a reversed order. `\2` contains the captured string "let" and `\1` the string "one". Having `\2 \1` returns the string "let one". + +Recall that `\0` represents the entire matched pattern. You can break the matched string into smaller groups with `( )`. Each group is represented by `\1`, `\2`, `\3`, etc. + +Let's do one more example to solidify this matched group concept. If you have these numbers: + +``` +123 +456 +789 +``` + +To reverse the order, run: +``` +:%s/\v(\d)(\d)(\d)/\3\2\1/ +``` + +The result is: + +``` +321 +654 +987 +``` + +Each `(\d)` matches and groups each digit. On the first line, the first `(\d)` has a value of "1", the second `(\d)` "2", and the third `(\d)` "3". They are stored in the variables `\1`, `\2`, and `\3`. In the second half of your substitution, the new pattern `\3\2\1` results in the "321" value on line one. + +If you had run this instead: +``` +:%s/\v(\d\d)(\d)/\2\1/ +``` +You would have gotten a different result: +``` +312 +645 +978 +``` + +This is because you now only have two groups. The first group,captured by `(\d\d)`, is stored within `\1` and has the value of "12". The second group, captured by `(\d)`, is stored inside `\2` and has the value of "3". `\2\1` then, returns "312". + +# Substitution Flags + +If you have the sentence: + +``` +chocolate pancake, strawberry pancake, blueberry pancake +``` + +To substitute all the pancakes into donuts, you cannot just run: + +``` +:s/pancake/donut +``` + +The command above will only substitute the first match, giving you: + +``` +chocolate donut, strawberry pancake, blueberry pancake +``` + +There are two ways to solve this. First, you can run the substitute command twice more. Second, you can pass it a global (`g`) flag to substitute all of the matches in a line. + +Let's talk about the global flag. Run: + +``` +:s/pancake/donut/g +``` + +Vim substitutes all pancakes with donuts in one swift command. The global command is one of the several flags the substitute command accepts. You pass flags at the end of the substitute command. Here is a list of useful flags: + +``` +& Reuse the flags from the previous substitute command. Must be passed as the first flag. +g Replace all matches in the line. +c Ask for substitution confirmation. +e Prevent error message from displaying when substitution fails. +i Perform case insensitive substitution +I Perform case sensitive substitution +``` + +There are more flags that I do not list above. To read about all the flags, check out `:h s_flags`. + +By the way, the repeat-substitution commands (`&` and `:s`) do not retain the flags. Running `&` will only repeat `:s/pancake/donut/` without `g`. To quickly repeat the last substitute command with all the flags, run `:&&`. + +# Changing the Delimiter + +If you need to replace a URL with a long path: + +``` +https://mysite.com/a/b/c/d/e +``` + +To substitute it with the word "hello", run: +``` +:s/https:\/\/mysite.com\/a\/b\/c\/d\/e/hello/ +``` + +However, it is hard to tell which forward slashes (`/`) are part of the substitution pattern and which ones are the delimiters. You can change the delimiter with any single-byte characters (except for alphabets, numbers, or `"`, `|`, and `\`). Let's replace them with `+`. The substitution command above then can be rewritten as: + +``` +:s+https:\/\/mysite.com\/a\/b\/c\/d\/e+hello+ +``` + +It is now easier to see where the delimiters are. + +# Special Replace + +You can also modify the case of the text you are substituting. Given the following expressions: + +``` +let one = "1"; +let two = "2"; +let three = "3"; +let four = "4"; +let five = "5"; + +``` +To uppercase the variables "one", "two", "three", etc., run: + +``` +%s/\v(\w+) (\w+)/\1 \U\2/ + +``` +You will get: + +``` +let ONE = "1"; +let TWO = "2"; +let THREE = "3"; +let FOUR = "4"; +let FIVE = "5"; +``` + +Here is the breakdown of that command: +- `(\w+) (\w+)` captures the first two matched groups, such as "let" and "one". +- `\1` returns the value of the first group, "let" +- `\U\2` uppercases (`\U`) the second group (`\2`). + +The trick of this command is the expression `\U\2`. `\U` instructs the following character to be uppercased. + +Let's do one more example. Suppose you are writing a Vim book and you need to capitalize the first letter of each word in a line. + +``` +vim is the greatest text editor in the whole galaxy +``` + +You can run: + +``` +:s/\<./\u&/g +``` + +The result: + +``` +Vim Is The Greatest Text Editor In The Whole Galaxy +``` + +Here is the breakdowns: +- `:s` substitutes the current line +- `\<.` is comprised of two parts: `\<` to match the start of a word and `.` to match any character. `\<` operator makes the following character to be the first character of a word. Since `.` is the next character, it will match the first character of any word. +- `\u&` uppercases the subsequent symbol, `&`. Recall that `&` (or `\0`) represents the whole match. It matches the first character of nay word. +- `g` the global flag. Without it, this command only substitutes the first match. You need to substitute every match on this line. + +To learn more of substitution's special replace symbols like `\u` and `\U`, check out `:h sub-replace-special`. + +# Alternative Patterns + +Sometimes you need to match multiple patterns simultaneously. If you have the following greetings: + +``` +hello vim +hola vim +salve vim +bonjour vim +``` + +You need to substitute the word "vim" with "friend" but only on the lines containing the word "hello" or "hola". Run: + +``` +:%s/\v(hello|hola) vim)/\1 friend/g +``` + +The result: +``` +hello friend +hola friend +salve vim +bonjour vim +``` + +Here is the breakdown: +- `%s` runs the substitute command on each line in a file. +- `(hello|hola)` Matches *either* "hello" or "hola" and consider it as a group. +- `vim` is the literal word "vim". +- `\1` is the first match group, which is either the text "hello" or "hola". +- `friend` is the literal word "friend". + +# Substituting the Start and the End of a Pattern + +Recall that you can use `\zs` and `\ze` to define the start and the end of a match. This technique works in substitution too. If you have: + +``` +chocolate pancake +strawberry sweetcake +blueberry hotcake +``` + +To substitute the "cake" in "hotcake" with "dog" to get a "hotdog": + +``` +:%s/hot\zscake/dog/g +``` + +Result: + +``` +chocolate pancake +strawberry sweetcake +blueberry hotdog +``` + +You can also substitute the nth match in a line with this trick: + +``` +One Mississippi, two Mississippi, three Mississippi, four Mississippi, five Mississippi. +``` + +To substitute the third "Mississippi" with "Arkansas", run: + +``` +:s/\v(.{-}\zsMississippi){3}/Arkansas/g +``` + +The breakdown: +- `:s/` the substitute command. +- `\v` is the magic keyword so you don't have to escape special keywords. +- `.` matches any single character +- `{-}` performs non-greedy match of 0 or more of the preceding atom. +- `\zsMississippi` makes "Mississippi" the start of the match. +- `(...){3}` looks for the third match. + +You have seen the `{3}` syntax before. It is a type of `{n,m}`. In this case, you have `{3}` which will match exactly the third match. The new trick here is `{-}`. It is a non-greedy match. It finds the shortest match of the given pattern. In this case, `(.{-}Mississippi)` matches the least amount of "Mississippi" preceded by any character. Contrast this with `(.*Mississippi)` where it finds the longest match of the given pattern. + +If you use `(.{-}Mississippi)`, you get five matches: "One Mississippi", "Two Mississippi", etc. If you use `(.*Mississippi)`, you get one match: the last "Mississippi". To learn more check out `:h /\{-` and `:h non-greedy`. + +Let's do a simpler example. If you have the string: + +``` +abc1de1 +``` + +You can match "abc1de1" (greedy) with: + +``` +/a.*1 +``` + +You can match "abc1" (non-greedy) with: + +``` +/a.\{-}1 +``` + +So if you need to uppercase the longest match (greedy), run: + +``` +:s/a.*1/\U&/g +``` + +To get: + +``` +ABC1DEFG1 +``` + +If you need to uppercase the shortest match (non-greedy), run: + +``` +:s/a.\{-}1/\U&/g +``` + +To get: + +``` +ABC1defg1 +``` + +# Substituting Across Multiple Files + +Finally, let's learn how to substitute phrases across multiple files. For this section, assume that you have two files: `food.txt` and `animal.txt`. + +Inside `food.txt`: + +``` +corndog +hotdog +chilidog +``` + +Inside `animal.txt`: + +``` +large dog +medium dog +small dog +``` + +Assume your directory structure looks like this: + +``` +├── food.txt +├── animal.txt +``` + +First, capture both `food.txt` and `animal.txt` inside `:args`. Recall from earlier chapters that `:args` can be used to create a list of file names. There are several ways to do this from inside Vim: + +``` +:args *.txt captures all txt files in current location +:args food.txt animal.txt captures only index and server js files +:args **/*.txt captures every txt files +:args ** captures everything +``` + +You can also run the commands above from outside Vim, passing the files as *arguments* for Vim (hence it is called the "args" command). From the terminal, run + +``` +vim food.txt animal.txt +``` + +When Vim starts, you will find `food.txt` and `animal.txt` inside `:args`. + +Either way, when you run `:args`, you should see: + +``` +[food.txt] animal.txt +``` + +To go to the next or previous argument on the list, type `:next` or `:previous`. Now that all the relevant files are stored inside the argument list, you can perform a multi-file substitution with the `:argdo` command. Run: + +``` +:argdo %s/dog/chicken/ +``` + +This performs substitution against the all files inside the `:args` list. Finally, save the changed files with: + +``` +:argdo update +``` + +`:args` and `:argdo` are useful tools to apply command line commands across multiple files. Try it with other commands! + +# Substituting Across Multiple Files with Macros + +Alternatively, you can also run the substitute command across multiple files with macros. Let's start by getting the relevant files into the args list. Run: + +``` +:args animal.txt food.txt +qq +:%s/dog/chicken/g +:wnext +q +99@q +``` + +Here is the breakdown of the steps: +- `:args animal.txt food.txt` lists the relevant files into the `:args` list. +- `qq` starts the macro in the "q" register. +- `:%s/dog/chicken/g` substitutes "dog" with "chicken on all lines in the current file. +- `:wnext` writes (saves) the file then go to the next file on the `args` list. It's like running `:w` and `:next` at the same time. +- `q` stops the macro recording. +- `99@q` executes the macro ninety-nine times. Vim will stop the macro execution after it encounters the first error, so Vim won't actually execute the macro ninety-nine times. + +# Learning Search and Substitution the Smart Way + +The ability to do search well is a necessary skill in editing. Mastering the search lets you to utilize the flexibility of regular expressions to search for any pattern in a file. Take your time to learn these. Actually do the searches and substitutions in this chapter yourself. I once read a book about regular expression without actually doing it and I forgot almost everything I read afterwards. Active coding is the best way to master any skill. + +A good way to improve your pattern matching skill is whenever you need to search for a pattern (like "hello 123"), instead of querying for the literal search term (`/hello 123`), try to come up with a pattern for it (`/\v(\l+) (\d+)`). Many of these regular expression concepts are also applicable in general programming, not only when using Vim. + +Now that you learned about advanced search and substitution in Vim, let's learn one of the most versatile commands, the global command. diff --git a/ch13_the_global_command.md b/ch13_the_global_command.md new file mode 100644 index 0000000..05fc7ae --- /dev/null +++ b/ch13_the_global_command.md @@ -0,0 +1,561 @@ +# The Global Command + +So far you have learned how to repeat the last change with the dot command (`.`), to replay actions with macros (`q`), and to store texts in the registers (`"`). + +In this chapter, you will learn how to repeat a command-line command with the global command. Run once, apply everywhere (in a buffer). + +# Global Command Overview + +Vim's global command is used to running a command-line command on multiple lines simultaneously. + +By the way, you may have heard of the term "Ex Commands" before. In this book, I refer them as command-line commands, but both Ex commands and command-line commands are the same. They are the commands that start with a colon (`:`). In the last chapter, you learned about the substitute command. It was an example of an Ex command. They are called Ex because they originally came from the Ex text editor. I will continue to refer to them as command-line commands in this book. For a full list of Ex commands, check out `:h ex-cmd-index`. + +The global command has the following syntax: + +``` +:g/pattern/command +``` + +The `pattern` matches all lines containing that pattern, similar to the pattern in the substitute command. The `command` can be any command-line command. The global command works by executing `command` against each line that matches the `pattern`. + +If you have the following expressions: + +``` +const one = 1; +console.log("one: ", one); + +const two = 2; +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); +``` + +To remove all lines containing "console", you can run: + +``` +:g/console/d +``` + +Result: + +``` +const one = 1; + +const two = 2; + +const three = 3; +``` + +The global command executes the delete command (`d`) on all lines that match the "console" pattern. + +When running the `g` command, Vim makes two scans across the file. On the first run, it scans each line and marks the line that matches the `/console/` pattern. Once all the matching lines are marked, it makes the second run, where it executes the `d` command on the marked lines. + +If you want to delete all lines containing "const" instead, run: + +``` +:g/const/d +``` + +Result: + +``` +console.log("one: ", one); + +console.log("two: ", two); + +console.log("three: ", three); +``` + +# Inverse Match + +To run the global command on non-matching lines, you can run: + +``` +:g!/{pattern}/{command} +``` + +or + +``` +:v/{pattern}/{command} +``` + +If you run `:v/console/d`, it will delete all lines *not* containing "console". + +# Pattern + +The global command uses the same pattern system as the substitute command, so this section will serve as a refresher. Feel free to skip to the next section or read along! + +If you have these expressions: + +``` +const one = 1; +console.log("one: ", one); + +const two = 2; +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); +``` + +To delete the lines containing either "one" or "two", run: + +``` +:g/one\|two/d +``` + +To delete the lines containing any single digits, run either: + +``` +:g/[0-9]/d +``` + +or + +``` +:g/\d/d +``` + +If you have the expression: + +``` +const oneMillion = 1000000; +const oneThousand = 1000; +const one = 1; +``` + +To match the lines containing between three to six zeroes, run: + +``` +:g/0\{3,6\}/d +``` + +# Passing a Range + +You can pass a range before the `g` command. Here are some ways you can do it: + +- `:1,5/g/console/d` matches the string "console" between lines 1 and 5 and deletes them. +- `:,5/g/console/d` if there is no address before the comma, then it starts from the current line. It looks for the string "console" between the current line and line 5 and deletes them. +- `:3,/g/console/d` if there is no address after the comma, then it ends at the current line. It looks for the string "console" between line 3 and the current line and deletes them. +- `:3g/console/d` if you only pass one address without a comma, it executes the command only on line 3. It looks on line 3 and deletes it if has the string "console". + +In addition to numbers, you can also use these symbols as range: +- `.` means the current line. A range of `.,3` means between the current line and line 3. +- `$` means the last line in the file. `3,$` range means between line 3 and the last line. +- `+n` means n lines after the current line. You can use it with `.` or without. `3,+1` or `3,.+1` means between line 3 and the line after the current line. + +If you don't give it any range, by default it affects the entire file. This is actually not the norm. Most of Vim's command-line commands run on only the current line if you don't pass it any range. The two notable exceptions are the global (`:g`) and the save (`:w`) commands. + +# Normal Command + +You can run a normal command with the global command with `:normal` command-line command. + +If you have this text: +``` +const one = 1; +console.log("one: ", one); + +const two = 2; +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); + +``` +To add a ";" to the end of each line, run: +``` +:g/./normal A; +``` + +Let's break it down: +- `:g` is the global command. +- `/./` is a pattern for "non-empty lines". Recall that the dot (`.`) in regex represents *any character*. It matches the lines with at least one character, so it matches the lines with "const" and "console". It does not match empty lines. +- `normal A;` runs the `:normal` command-line command. `A;` is the normal mode command to insert a ";" at the end of the line. + +# Executing a Macro + +You can also execute a macro with the global command. A macro is just a normal mode operation, so it is possible to execute a macro with `:normal`. If you have the expressions: + +``` +const one = 1 +console.log("one: ", one); + +const two = 2 +console.log("two: ", two); + +const three = 3 +console.log("three: ", three); +``` + +Notice that the lines with "const" do not have semi-colons. Let's create a macro to add a comma to the end of those lines in the register "a": + +``` +qa0A;q +``` + +If you need a refresher, check out the chapter on macro. Now run: + +``` +:g/const/normal @a +``` + +Now all lines with "const" will have a ";" at the end. + +``` +const one = 1; +console.log("one: ", one); + +const two = 2; +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); +``` + +# Recursive Global Command + +The global command itself is a type of a command-line command, so you can technically run the global command inside a global command. + +Given the expressions: + +``` +const one = 1; +console.log("one: ", one); + +const two = 2; +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); +``` + +If you run: + +``` +:g/console/g/two/d +``` + +First, `g` will look for the lines containing the pattern "console" and find 3 matches. Then the second `g` will look for the line containing the pattern "two" from those three matches. Finally, it will delete that match. + +You can also combine `g` with `v` to find positive and negative patterns. For example: + +``` +:g/console/v/two/d +``` + +Instead of looking for the line containing the pattern "two", it will look for the lines *not* containing the pattern "two". + +# Changing the Delimiter + +You can change the global command's delimiter like the substitute command. The rules are the same: you can use any single byte character except for alphabets, numbers, `"`, `|`, and `\`. + +To delete the lines containing "console": + +``` +:g@console@d +``` + +If you are using the substitute command with the global command, you can have two different delimiters: + +``` +g@one@s+const+let+g +``` + +Here the global command will look for all lines containing "one". The substitute command will substitute, from those matches, the string "const" with "let". + +# The Default Command + +What happens if you don't specify any command-line command in the global command? + +The global command will use the print (`:p`) command to print the current line's text. If you run: + +``` +:g/console +``` + +It will print at the bottom of the screen all the lines containing "console". + +By the way, here is one interesting fact. Because the default command used by the global command is `p`, this makes the `g` syntax to be: + +``` +:g/re/p +``` + +- `g` = the global command +- `re` = the regex pattern +- `p` = the print command + +It spells *"grep"*, the same `grep` from the command line. This is **not** a coincidence. The `g/re/p` command originally came from the Ed Editor, one of the first line text editors. The `grep` command got its name from Ed. + +Your computer probably still has the Ed editor. Run `ed` from the terminal (hint: to quit, type `q`). + +# More examples + +## Reverse the Entire Buffer + +To reverse the entire file, run: + +``` +:g/^/m 0 +``` + +`^` is a pattern for the "beginning of a line". Use `^` to match all lines, including empty lines. + +If you need to reverse only a few lines, pass it a range. To reverse the lines between line five to line ten, run: + +``` +:5,10g/^/m 0 +``` + +To learn more about the move command, check out `:h :move`. + +## Aggregating All TODOs + +When I code, sometimes I think of a random brilliant ideas. Not wanting to lose concentration, I usually write them down in the file I am editing, for example: + +``` +const one = 1; +console.log("one: ", one); +// TODO: feed the puppy + +const two = 2; +// TODO: feed the puppy automatically +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); +// TODO: create a startup selling an automatic puppy feeder +``` + +It can be hard to keep track of all the created TODOs. Vim has a `:t` (copy) method to copy all matches to an address. To learn more about the copy method, check out `:h :copy`. + +To copy all TODOs to the end of the file for easier introspection, run: + +``` +:g/TODO/t $ +``` + +Result: + +``` +const one = 1; +console.log("one: ", one); +// TODO: feed the puppy + +const two = 2; +// TODO: feed the puppy automatically +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); +// TODO: create a startup selling an automatic puppy feeder + +// TODO: feed the puppy +// TODO: feed the puppy automatically +// TODO: create a startup selling an automatic puppy feeder +``` + +Now I can review all the TODOs I created, find a time to do them or delegate them to someone else, and continue to work on my next task. + +Another alternative is to use `m`: + +``` +:g/TODO/m $ +``` + +Result: + +``` +const one = 1; +console.log("one: ", one); + +const two = 2; +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); + +// TODO: feed the puppy +// TODO: feed the puppy automatically +// TODO: create a startup selling an automatic puppy feeder +``` + +I can just delete the list once I decided what to do with it. + +## Black Hole Delete + +Recall from the register chapter that deleted texts are stored inside the numbered registers (granted they are sufficiently large ). Whenever you run `:g/console/d`, Vim stores the deleted lines in the numbered registers. If you delete many lines, you can quickly fill up all the numbered registers. To avoid this, you can always use the black hole register (`"_`) to *not* store your deleted lines into the registers. Run: + +``` +:g/console/d _ +``` + +By passing `_` after `d`, Vim won't save the deleted lines into any registers. + +## Reduce Multiple Empty Lines to One Empty Line + +If you have a file with multiple empty lines like the following: + +``` +const one = 1; +console.log("one: ", one); + + +const two = 2; +console.log("two: ", two); + + + + + +const three = 3; +console.log("three: ", three); +``` + +You can quickly reduce each the long empty lines to one empty line. Run: + +``` +:g/^$/,/./-1j +``` + +Result: + +``` +const one = 1; +console.log("one: ", one); + +const two = 2; +console.log("two: ", two); + +const three = 3; +console.log("three: ", three); +``` + +Let's break it down: +- `:g` is the global command +- `/^$/` is the pattern for an empty line. Recall that `^` means the beginning of the line and `$` the end of the line. `^$` matches an empty line (a line with zero characters long). +- `,/./-1` is the range for the `j` command. Since you don't pass a value for the starting range, it starts from the current line. You just learned earlier that `/./` is a pattern for a non-empty line. `,/./` is a range from the current line to the next non-empty line. The global command's range, `/^$/`, takes you to the first match on the line below `console.log("one: ", one);`. This is the current line. `/./` matches the first non-empty line, the line `const two = 2;`. Finally, `-1` offsets that by one line. The effective range for the first match is the empty line below the `console.log("one: ", one);` and the empty line above the `const two = 2;`. +- `j` is the join command `:j`. You can join all the lines given as its range. For example, `:1,5j` joins lines one through five. + +Notice that you are passing a range (`,/./-1`) before the `j` command. Just because you are using a command-line command with the global command, does not mean you cannot give it a range. In this code, you are passing to the `j` command its own range to execute on. You can pass a range to any command while executing the global command. + +By the way, if you want to reduce multiple empty lines into no lines, instead of using `,/./-1` as the range for `j` command, just use `,/./` as the range instead: + +``` +:g/^$/,/./j +``` + +Or simpler: + +``` +:g/^$/-j +``` + +Your text is now reduced to: + +``` +const one = 1; +console.log("one: ", one); +const two = 2; +console.log("two: ", two); +const three = 3; +console.log("three: ", three); +``` + +## Advanced sort + +Vim has a `:sort` command to sort the lines within a range. For example: + +``` +d +b +a +e +c +``` + +You can sort them by running `:sort`. If you give it a range, it will sort only the lines within that range. For example, `:3,5sort` sorts only between lines three and five. + +If you have the following expressions: + +``` +const arrayB = [ + "i", + "g", + "h", + "b", + "f", + "d", + "e", + "c", + "a", +] + +const arrayA = [ + "h", + "b", + "f", + "d", + "e", + "a", + "c", +] +``` + +If you need to sort the elements inside the arrays, but not the arrays themselves, you can run this: + +``` +:g/\[/+1,/\]/-1sort +``` + +Result: +``` +const arrayB = [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", +] + +const arrayA = [ + "a" + "b", + "c", + "d", + "e", + "f", + "h", +] +``` + +This is great! But the command looks complicated. Let's break it down. The command consists of three main parts: the global command pattern, the sort command range, and the sort command. + +`:g/\[/` is the global command pattern. +- `:g` is the global command. +- `/\[/` is the pattern used by the global command. `\[` looks for a literal "[" string. + +`+1,/\]/-1` is the range for the sort command. +- A range can have a starting and an ending addresses. In this case, `+1` is the starting address and `/\]/-1` is the ending address. +- `+1` represents the line after the current line, which is the line that matches the pattern "[" from the global command. `+1` offsets the current line by one line. So in the first match, the range actually starts one line *after* the `const arrayB = [` text. +- `/\]/-1` is the ending address. `\]` represents a literal closing square bracket "]". `-1` offsets it by one line. The ending address is the line above the "]". + +`sort` is the sort command-line command. It sorts everything within the given range. Everything after the "[" to the line above "]" is getting sorted. + +If you are still confused by the command, do not worry. It took me a long time to grasp it. Take a break, leave the screen, and come back again with a fresh mind. + +# Learn the Global Command the Smart Way + +The global command executes the command-line command against all matching lines. With it, you only need to run a command once and Vim will do the rest for you. To become proficient at the global command, two things are required: a good vocabulary of command-line commands and a knowledge of regular expressions. As you spend more time using Vim, you will naturally learn more command-line commands. A regular expression knowledge will require a more active approach. But once you become comfortable with regular expressions, you will be ahead of many. + +Some of the examples here are complicated. Do not be intimidated. Really take your time to understand them. Learn to read the patterns. Make sure you know what each letter in each command represent. Do not give up. + +Whenever you need to apply a command in several locations, pause and see if you can use the `g` command. Look for the best command for the job and write a pattern to target as many things at once. Then repeat it until you can do it without thinking. The next time, see if there is even a faster and more efficient way to do it. + +Now that you know how powerful the global command is, let's learn how to use the external commands to increase your tool arsenals. diff --git a/ch14_external_commands.md b/ch14_external_commands.md new file mode 100644 index 0000000..ff10c48 --- /dev/null +++ b/ch14_external_commands.md @@ -0,0 +1,215 @@ +# External Commands + +Inside the Unix system, you will find many small, hyper-specialized commands where each does one thing well. You can chain these commands to work together to solve a complex problem. Wouldn't it be great if you can use these commands from inside Vim? + +In this chapter, you will learn how extend Vim to work seamlessly with external commands. + +# The Bang Command + +Vim has a bang (`!`) command that can do three things: + +1. Read the STDOUT of an external command into the current buffer. +2. Write the content of your buffer as the STDIN to an external command. +3. Execute an external command from inside Vim. + + +# Reading the STDOUT of a Command Into Vim + +The syntax to read the STDOUT of an external command into the current buffer is: + +``` +:r !{cmd} +``` + +`:r` is Vim's read command. If you use it without `!`, you can use it to get the content of a file. If you have a file `file1.txt` in the current directory and you run: + +``` +:r file1.txt +``` + +Vim will put the content of `file1.txt` into the current buffer. + +If you run the `:r` command followed by a `!` and an external command, the output of that commmand will be inserted into the current buffer. To get the result of the `ls` command, run: + +``` +:r !ls +``` + +It returns something like: + +``` +file1.txt +file2.txt +file3.txt +``` + +You can read the data from the `curl` command: + +``` +:r !curl -s 'https://jsonplaceholder.typicode.com/todos/1' +``` + +The `r` command also accepts an address: + +``` +:10r !cat file1.txt +``` + +Now the STDOUT from running `cat file.txt` will be inserted after line 10. + +# Writing the Buffer Content Into an External Command + +In addition to saving a file, you can also use the write command (`:w`) to pass the text in the current buffer as the STDIN for an external command. The syntax is: + +``` +:w !cmd +``` + +If you have these expressions: + +``` +console.log("Hello Vim"); +console.log("Vim is awesome"); +``` + +Make sure you have [node](https://nodejs.org/en/) installed in your machine, then run: + +``` +:w !node +``` + +Vim will use `node` to execute the Javascript expressions to print "Hello Vim" and "Vim is awesome". + +When using the `:w` command, Vim uses all texts in the current buffer, similar to the global command (most command-line commands, if you don't pass it a range, only executes the command against the current line). If you pass `:w` a specific address: + +``` +:2w !node +``` + +Vim only uses the text from the second line into the `node` interpreter. + +There is a subtle but significant difference between `:w !node` and `:w! node`. With `:w !node`, you are "writing" the text in the current buffer into the external command `node`. With `:w! node`, you are force-saving a file and naming the file "node". + +# Executing an External Command + +You can execute an external command from inside Vim with the bang command. The syntax is: + +``` +:!cmd +``` + +To see the content of the current directory in the long format, run: + +``` +:!ls -ls +``` + +To kill a process that is running on PID 3456, you can run: + +``` +:!kill -9 3456 +``` + +You can run any external command without leaving Vim so you can stay focused on your task. + +# Filtering Texts + +If you give `!` a range, it can be used to filter texts. Suppose you have this: + +``` +hello vim +hello vim +``` + +Let's uppercase the current line using the `tr` (translate) command. Run: + +``` +:.!tr '[:lower:]' '[:upper:]' +``` + +The result: + +``` +HELLO VIM +hello vim +``` + +The breakdown: +- `.!` executes the filter command on the current line. +- `!tr '[:lower:]' '[:upper:]'` calls the `tr` command to replace all lowercase characters with uppercase ones. + +It is imperative to pass a range to run the external command as a filter. If you try running the command above without the `.` (`:!tr '[:lower:]' '[:upper:]'`), you will see an error. + +Let's assume that you need to remove the second column on both lines with the `awk` command: + +``` +:%!awk "{print $1}" +``` + +The result: + +``` +hello +hello +``` + +The breakdown: +- `:%!` executes the filter command on all lines (`%`). +- `awk "{print $1}"` prints only the first column of the match. In this case, the word "hello". + +You can chain multiple commands with the chain operator (`|`) just like in the terminal. Let's say you have a file with these delicious breakfast items: + +``` +name price +chocolate pancake 10 +buttermilk pancake 9 +blueberry pancake 12 +``` + +If you need to sort them based on the price and display only the menu with an even spacing, you can run: + +``` +:%!awk 'NR > 1' | sort -nk 3 | column -t +``` + +The result: +``` +buttermilk pancake 9 +chocolate pancake 10 +blueberry pancake 12 +``` + +The breakdown: +- `:%!` applies the filter to all lines (`%`). +- `awk 'NR > 1'` displays the texts only from row number two onwards. +- `|` chains the next command. +- `sort -nk 3` sorts numerically (`n`) using the values from column 3 (`k 3`). +- `column -t` organizes the text with even spacing. + +# Normal mode command + +Vim has a filter operator (`!`) in the normal mode. If you have the following greetings: + +``` +hello vim +hola vim +bonjour vim +salve vim +``` + +To uppercase the current line and the line below, you can run: +``` +!jtr '[a-z]' '[A-Z]' +``` + +The breakdown: +- `!j` runs the normal command filter operator (`!`) targetting the current line and the line below it. Recall that because it is a normal mode operator, the grammar rule `verb + noun` applies. +- `tr '[a-z]' '[A-Z]'` replaces the lowercase letters with the uppercase letters. + +The filter normal command only works on motions / text objects that are at least one line or longer. If you had tried running `!iwtr '[a-z]' '[A-Z]'` (execute `tr` on inner word), you will find that it applies the `tr` command on the entire line, not the word your cursor is on. + +# Learn External Commands the Smart Way + +Vim is not an IDE. It is a lightweight modal editor that is highly extensible by design. Because of this extensibility, you have easy access to any external command in your system. With this, Vim is one step closer from becoming an IDE. Someone said that the Unix system is the first IDE ever. + +The bang command is as useful as how many external commands you know. Don't worry if your external command knowledge is limited. I still have a lot to learn too. Take this as a motivation for continuous learning. Whenever you need to filter a text, look if there is an external command that can solve your problem. Don't worry about mastering everything about a particular command. Just learn the ones you need to complete the current task. diff --git a/img/cartesian-xy.png b/img/cartesian-xy.png new file mode 100644 index 0000000..4e5b83d Binary files /dev/null and b/img/cartesian-xy.png differ diff --git a/img/cartesian-xyz.png b/img/cartesian-xyz.png new file mode 100644 index 0000000..47d5285 Binary files /dev/null and b/img/cartesian-xyz.png differ diff --git a/img/cartesian-z.png b/img/cartesian-z.png new file mode 100644 index 0000000..388c25c Binary files /dev/null and b/img/cartesian-z.png differ diff --git a/img/fzf-files.gif b/img/fzf-files.gif new file mode 100644 index 0000000..5aa784b Binary files /dev/null and b/img/fzf-files.gif differ diff --git a/img/fzf-in-files.gif b/img/fzf-in-files.gif new file mode 100644 index 0000000..4e91842 Binary files /dev/null and b/img/fzf-in-files.gif differ diff --git a/img/screen-one-buffer-buffers-command.png b/img/screen-one-buffer-buffers-command.png new file mode 100644 index 0000000..e0b0440 Binary files /dev/null and b/img/screen-one-buffer-buffers-command.png differ diff --git a/img/screen-one-buffer-file1-highlighted.png b/img/screen-one-buffer-file1-highlighted.png new file mode 100644 index 0000000..27ee28e Binary files /dev/null and b/img/screen-one-buffer-file1-highlighted.png differ diff --git a/img/screen-one-buffer.png b/img/screen-one-buffer.png new file mode 100644 index 0000000..40fbbdd Binary files /dev/null and b/img/screen-one-buffer.png differ diff --git a/img/screen-split-window-vertically-and-horizontally-two-file2.png b/img/screen-split-window-vertically-and-horizontally-two-file2.png new file mode 100644 index 0000000..4ea0f6e Binary files /dev/null and b/img/screen-split-window-vertically-and-horizontally-two-file2.png differ diff --git a/img/screen-split-window-vertically-and-horizontally.png b/img/screen-split-window-vertically-and-horizontally.png new file mode 100644 index 0000000..49a6ccf Binary files /dev/null and b/img/screen-split-window-vertically-and-horizontally.png differ diff --git a/img/screen-split-window.png b/img/screen-split-window.png new file mode 100644 index 0000000..9d693cf Binary files /dev/null and b/img/screen-split-window.png differ diff --git a/img/screen-tab2.png b/img/screen-tab2.png new file mode 100644 index 0000000..de00920 Binary files /dev/null and b/img/screen-tab2.png differ diff --git a/img/screen-vscode-3-windows.png b/img/screen-vscode-3-windows.png new file mode 100644 index 0000000..590b509 Binary files /dev/null and b/img/screen-vscode-3-windows.png differ diff --git a/img/tabs-file1js.png b/img/tabs-file1js.png new file mode 100644 index 0000000..f4ce550 Binary files /dev/null and b/img/tabs-file1js.png differ diff --git a/img/tabs-file2js.png b/img/tabs-file2js.png new file mode 100644 index 0000000..3ac2a70 Binary files /dev/null and b/img/tabs-file2js.png differ