añadir capítulos 15, 16 y 17 y actualizar README
This commit is contained in:
parent
addde9dd9c
commit
0296ea5eec
4 changed files with 876 additions and 5 deletions
11
README.md
11
README.md
|
@ -21,11 +21,12 @@ La idea es trauducir el libro del inglés al español para difundir y dar a cono
|
|||
- [Ch 12 - Search and Substitute](./ch12_search_and_substitute.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- [Ch 13 - The Global Command](./ch13_the_global_command.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- [Ch 14 - External Commands](./ch14_external_commands.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- Ch 15 - Command Line Mode ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- Ch 16 - Tags ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- Ch 17 - Git ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- Ch 18 - Fold ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- Ch 19 - Compile ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- [Ch 15 - Command-line Mode](./ch15_command-line_mode.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- [Ch 16 - Tags](./ch16_tags.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- [Ch 17 - Fold](./ch17_fold.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- [Ch 18 - Git](./ch18_git.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- [Ch 19 - Compile](./ch19_compile.md) ......... En progreso [ ] / Traducido [ ] / Revisado [ ]
|
||||
- Ch 20 - Sessions
|
||||
|
||||
# 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.
|
||||
|
|
99
ch15_command-line_mode.md
Normal file
99
ch15_command-line_mode.md
Normal file
|
@ -0,0 +1,99 @@
|
|||
# Command-line Mode
|
||||
In the last three chapters, you learned how to use the search commands (`/`, `?`), substitute command (`:s`), global command (`:g`), and external command (`!`). These are examples of command-line mode commands.
|
||||
|
||||
In this chapter, you will learn various tips and tricks for the command-line mode.
|
||||
|
||||
# Entering and Exiting the Command-line Mode
|
||||
The command-line mode is a mode in itself, just like normal mode, insert mode, and visual mode. When you are in this mode, the cursor goes to the bottom of the screen where you can type in different commands.
|
||||
|
||||
There are 4 different commands you can use to enter the command-line mode:
|
||||
- Search patterns (`/`, `?`)
|
||||
- Command-line commands (`:`)
|
||||
- External commands (`!`)
|
||||
|
||||
You can enter the command-line mode from the normal mode or the visual mode.
|
||||
|
||||
To leave the command-line mode, you can use `<esc>`, `Ctrl-C, or Ctrl-[`.
|
||||
|
||||
*Sometimes other literatures might refer the "Command-line command" as "Ex command" and the "External command" as "filter command" or "bang operator".*
|
||||
|
||||
# Repeating the Previous Command
|
||||
You can repeat the previous command-line command or external command with `@:`.
|
||||
|
||||
If you just ran `:s/foo/bar/g`, running `@:` repeats that substitution.
|
||||
|
||||
If you just ran `:.!tr '[a-z]' '[A-Z]'`, running `@:` repeats the last external command translation filter.
|
||||
|
||||
# Command-line Mode Shortcuts
|
||||
|
||||
While in the command-line mode, you can move to the left or to the right, one character at a time, with the `Left` or `Right` arrow.
|
||||
|
||||
If you need to move word-wise, use `Shift-Left` or `Shift-Right` (in some OS, you might have to use `Ctrl` instead of `Shift`).
|
||||
|
||||
To go to the start of the line, use `Ctrl-B`. To go to the end of the line, use `Ctrl-E`.
|
||||
|
||||
Similar to the insert mode, inside the command-line mode, you have three ways to delete characters:
|
||||
|
||||
```
|
||||
Ctrl-H Delete one character
|
||||
Ctrl-W Delete one word
|
||||
Ctrl-U Delete the entire line
|
||||
```
|
||||
|
||||
# Register and Autocomplete
|
||||
|
||||
When programming, whenever possible, do not repeat if you can autocomplete it. This mindset will not only save you time but reduces the chances of typing the wrong characters.
|
||||
|
||||
You can insert texts from Vim register with `Ctrl-R` (the same way as the insert mode). If you have the string "foo" saved in the register "a" (`"a`), you can insert it by running `Ctrl-R a`. Everything that you can get from the register in the insert mode, you can do the same from the command-line mode.
|
||||
|
||||
You can also autocomplete commands. To autocomplete the `echo` command, while in the command-line mode, type "ec", then press `<Tab>`. You should see on the bottom left Vim commands starting with "ec" (example: `echo echoerr echohl echomsg econ`). To go to the next option, press either `<Tab>` or `Ctrl-N`. To go the previous option, press either `<Shift-Tab>` or `Ctrl-P`.
|
||||
|
||||
Some command-line commands accept file names as arguments. One example is `edit`. After typing the command, `:e ` (don't forget the space), press `<Tab>`. Vim will list all the relevant file names.
|
||||
|
||||
# History Window
|
||||
|
||||
You can view the histoy of command-line commands and search terms (make sure that your Vim build has `+cmdline_hist` when you run `vim --version`).
|
||||
|
||||
To open the command-line history, run `:his :`:
|
||||
|
||||
```
|
||||
# cmd history
|
||||
2 e file1.txt
|
||||
3 g/foo/d
|
||||
4 s/foo/bar/g
|
||||
```
|
||||
|
||||
Vim lists the history of all the `:` commands you run. By default, Vim stores the last 50 commands. To change the amount of the entries that Vim remembers to 100, you can run `:set history=100`.
|
||||
|
||||
When you are in the command-line mode, you can traverse this history list by pressing `Up` and `Down` button. Suppose you had the command-line command history that looks like:
|
||||
```
|
||||
51 s/foo/bar/g
|
||||
52 s/foo/baz/g
|
||||
53 s/foo//g
|
||||
```
|
||||
|
||||
If you press `:` then press `Up` once, you'll see `:s/foo//g`. Press `Up` one more time to see `:s/foo/baz/g`. Vim goes up the history list.
|
||||
|
||||
Similarly, to view the search history, run `:his /`. You can also traverse the history stack by pressing `Up` or `Down` after running the history command `/`.
|
||||
|
||||
Vim is smart enough to distinguish different histories. If you press `Up` or `Down` after pressing `:`, Vim automatically the command history. If you press `Up` or `Down` after pressing `/`, Vim automatically searches the search history.
|
||||
|
||||
# Command-line Window
|
||||
|
||||
The history window displays the list of previously used command-line commands, but you can't execute the command from the history window. To execute a command while browsing, use the *command-line window*. There are three different command-line windows:
|
||||
|
||||
```
|
||||
q: Command-line window
|
||||
q/ Forward search window
|
||||
q? Backward search window
|
||||
```
|
||||
|
||||
Run `q:` to launch the command-line window for command-line commands. Vim will launch a new window at the bottom of the screen. You can traverse upward with the `Up` or `Ctrl-P` keys and traverse downward with the `Down` or `Ctrl-N` keys. If you press `<Return>`, Vim executes that command. To quit the command-line window, either press `Ctrl-C`, `Ctrl-W C`, or type `:quit`.
|
||||
|
||||
Similarly, to launch the command-line window for search, run `q/` to search forward and `q?` to search backward.
|
||||
|
||||
# Learn Command-line Mode the Smart Way
|
||||
|
||||
Compared to the other three modes, the command-line mode is like the Swiss Army knife of text editing. You can edit text, modify files, and execute commands, just to name a few. This chapter is a collection of odds and ends of the command-line mode. It also brings Vim modes into closure. Now that you know how to use the normal, insert, visual, and command-line mode you can edit text with Vim faster than ever.
|
||||
|
||||
It's time to move away from Vim modes and learn how to do a faster navigation with Vim tags.
|
390
ch16_tags.md
Normal file
390
ch16_tags.md
Normal file
|
@ -0,0 +1,390 @@
|
|||
# Tags
|
||||
|
||||
One useful feature in text editing is being able to go to any definition quickly. In this chapter, you will learn how to use Vim tags to do that.
|
||||
|
||||
# Tag Overview
|
||||
|
||||
Suppose someone handed you a new codebase:
|
||||
|
||||
```
|
||||
one = One.new
|
||||
one.donut
|
||||
```
|
||||
|
||||
`One`? `donut`? Well, these might have been obvious to the developers writing the code way back then, but now those developers are no longer here and it is up to you to understand these obscure lines of codes. One way to help understand this is to follow the source code where `One` and `donut` are defined.
|
||||
|
||||
You can search for them with either `fzf` or `grep`, but it is faster to use tags.
|
||||
|
||||
Think of tags like an address book:
|
||||
|
||||
```
|
||||
Name Address
|
||||
Iggy1 1234 Cool St, 11111
|
||||
Iggy2 9876 Awesome Ave, 2222
|
||||
```
|
||||
|
||||
Instead of having a name-address pair, tags store definitions paired with addresses.
|
||||
|
||||
Let's assume that you have these two Ruby files inside the same directory:
|
||||
|
||||
```
|
||||
# one.rb
|
||||
class One
|
||||
def initialize
|
||||
puts "Initialized"
|
||||
end
|
||||
|
||||
def donut
|
||||
puts "Bar"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```
|
||||
# two.rb
|
||||
require './one'
|
||||
|
||||
one = One.new
|
||||
one.donut
|
||||
```
|
||||
|
||||
To jump to a definition, you can use `Ctrl-]` in the normal mode. Inside `two.rb`, go to the line where `one.donut` is and move the cursor over `donut`. Press `Ctrl-]`.
|
||||
|
||||
Whoops, Vim could not find the tag file. You need to generate the tag file first.
|
||||
|
||||
# Tag Generator
|
||||
|
||||
Modern Vim does not come with tag generator, so you will have to download an external tag generator. There are several options to choose:
|
||||
|
||||
- ctags = C only. Available almost everywhere.
|
||||
- exuberant ctags = One of the most popular ones. Has many language support.
|
||||
- universal ctags = Similar to exuberant ctags, but newer.
|
||||
- etags = For Emacs. Hmm...
|
||||
- JTags = Java
|
||||
- ptags.py = Python
|
||||
- ptags = Perl
|
||||
- gnatxref = Ada
|
||||
|
||||
If you look at Vim tutorials online, many will recommend [exuberant ctags](http://ctags.sourceforge.net/). It supports [41 programming languages](http://ctags.sourceforge.net/languages.html). I used it and it worked great. However, because it has not been maintained since 2009, Universal ctags would be a better choice. It works similar to exuberant ctags and is currently being maintained.
|
||||
|
||||
I won't go into details on how to install the universal ctags. Check out the [universal ctags](https://github.com/universal-ctags/ctags) repository for more instructions. Once you have the universal ctags installed, run `ctags --version`. It should show:
|
||||
|
||||
```
|
||||
Universal Ctags 0.0.0(b43eb39), Copyright (C) 2015 Universal Ctags Team
|
||||
Universal Ctags is derived from Exuberant Ctags.
|
||||
Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert
|
||||
```
|
||||
|
||||
Make sure that you see "`Universal Ctags`".
|
||||
|
||||
Next, let's generate a basic tag file. Run:
|
||||
|
||||
```
|
||||
ctags -R .
|
||||
```
|
||||
|
||||
The `R` option tells `ctags` to run a recursive scan from your current location (`.`). You should see a `tags` file in your current directory. Inside you will see something like this:
|
||||
|
||||
```
|
||||
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
||||
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
||||
!_TAG_OUTPUT_FILESEP slash /slash or backslash/
|
||||
!_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/
|
||||
!_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/
|
||||
!_TAG_PROGRAM_AUTHOR Universal Ctags Team //
|
||||
!_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
|
||||
!_TAG_PROGRAM_URL <https://ctags.io/> /official site/
|
||||
!_TAG_PROGRAM_VERSION 0.0.0 /b43eb39/
|
||||
One one.rb /^class One$/;" c
|
||||
donut one.rb /^ def donut$/;" f class:One
|
||||
initialize one.rb /^ def initialize$/;" f class:One
|
||||
```
|
||||
|
||||
Yours might look a little different depending on your Vim setting and the ctags generator. A tag file is composed of two parts: the tag metadata and the tag list. These metadata (`!TAG_FILE...`) are usually controlled by the ctags generator. I won't discuss it here, but feel free to check the documentation!
|
||||
|
||||
Now go to `two.rb`, put the cursor on `donut`, and type `Ctrl-]`. Vim will take you to the file `one.rb` on the line where `def donut` is. Success! But how did Vim do this?
|
||||
|
||||
# Tags Anatomy
|
||||
|
||||
Let's look at the `donut` tag item:
|
||||
|
||||
```
|
||||
donut one.rb /^ def donut$/;" f class:One
|
||||
```
|
||||
|
||||
The above tag item is composed of four components: a `tagname`, a `tagfile`, a `tagaddress`, and tag options.
|
||||
|
||||
- `donut` is the `tagname`. When your cursor is on "donut", Vim searches the tag file for a line that has the "donut" string.
|
||||
- `one.rb` is the `tagfile`. Vim looks for a file `one.rb`.
|
||||
- `/^ def donut$/` is the `tagaddress`. `/.../` is a pattern indicator. `^` is a pattern for the first element on a line. It is followed by two spaces , then the string `def donut`. Finally, `$` is a pattern for the last element on a line.
|
||||
- `f class:One` is the tag option that tells Vim that the function `donut` is a function (`f`) and is part of the `One` class.
|
||||
|
||||
Let's look at another item in the tag list:
|
||||
|
||||
```
|
||||
One one.rb /^class One$/;" c
|
||||
```
|
||||
|
||||
This line works the same way as the `donut` pattern:
|
||||
|
||||
- `One` is the `tagname`. Note that with tags, the first scan is case sensitive. If you have `One` and `one` on the list, Vim will prioritize `One` over `one`.
|
||||
- `one.rb` is the `tagfile`. Vim looks for a file `one.rb`.
|
||||
- `/^class One$/` is the `tagaddress` pattern. Vim looks for a line that starts with (`^`) `class` and ends with (`$`) `One`.
|
||||
- `c` is one of the possible tag options. Since `One` is a ruby class and not a procedure, it marks it with a `c`.
|
||||
|
||||
Depending on which tag generator you use, the content of your tag file may look different. At minimum, a tag file must have either one of these formats:
|
||||
|
||||
```
|
||||
1. {tagname} {TAB} {tagfile} {TAB} {tagaddress}
|
||||
2. {tagname} {TAB} {tagfile} {TAB} {tagaddress} {term} {field} ..
|
||||
```
|
||||
|
||||
# The Tag File
|
||||
|
||||
You have learned that a new file, `tags`, is created after running `ctags -R .`. How does Vim know where to look for the tag file?
|
||||
|
||||
If you run `:set tags?`, you might see `tags=./tags,tags` (depending on your Vim settings, it might be different). Here Vim looks for all tags in the path of the current file in the case of `./tags` and the current directory (your project root) in the case of `tags`.
|
||||
|
||||
Also in the case of `./tags`, Vim will first look for a tag file inside the path of your current file regardless how nested it is, then it will look for a tag file of the current directory (project root). Vim stops after it finds the first match.
|
||||
|
||||
If your `'tags'` file had said `tags=./tags,tags,/user/iggy/mytags/tags`, then Vim will also look at the `/user/iggy/mytags` directory for a tag file after Vim finishes searching `./tags` and `tags` directory. You don't have to store your tag file inside your project, you can keep them separate.
|
||||
|
||||
To add to a tag file location, just run:
|
||||
|
||||
```
|
||||
:set tags+=path/to/my/tags/file
|
||||
```
|
||||
|
||||
# Generating Tags For a Large Project.
|
||||
|
||||
If you tried to run ctags in a large project, it may take a long time because Vim also looks inside every nested directories. If you are a Javascript developer, you know that `node_modules` can be very large. Imagine if you have a five sub-projects and each contains its own `node_modules` directory. If you run `ctags -R .`, ctags will try to scan through all 5 `node_modules`. You probably don't need to run ctags on `node_modules`.
|
||||
|
||||
To run ctags excluding the `node_modules`, run:
|
||||
|
||||
```
|
||||
ctags -R --exclude=node_modules .
|
||||
```
|
||||
|
||||
This time it should take less than a second. By the way, you can use the `exclude` option multiple times:
|
||||
|
||||
```
|
||||
ctags -R --exclude=.git --exclude=vendor --exclude=node_modules --exclude=db --exclude=log .
|
||||
```
|
||||
|
||||
# Tags Navigation
|
||||
|
||||
You can get good milage using only `Ctrl-]`, but let's learn a few more tricks. The tag jump key `Ctrl-]` has an command-line mode alternative: `:tag my-tag`. If you run:
|
||||
|
||||
```
|
||||
:tag donut
|
||||
```
|
||||
|
||||
Vim will jump to the `donut` method, just like doing `Ctrl-]` on "donut" string. You can autocomplete the argument too, with `<Tab>`:
|
||||
|
||||
```
|
||||
:tag d<Tab>
|
||||
```
|
||||
|
||||
Vim lists all tags that starts with "d". In this case, "donut".
|
||||
|
||||
In a real project, you may encounter multiple methods with the same name. Let's update the two files. Inside `one.rb`:
|
||||
|
||||
```
|
||||
# one.rb
|
||||
class One
|
||||
def initialize
|
||||
puts "Initialized"
|
||||
end
|
||||
|
||||
def donut
|
||||
puts "one donut"
|
||||
end
|
||||
|
||||
def pancake
|
||||
puts "one pancake"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
And `two.rb`:
|
||||
|
||||
```
|
||||
# two.rb
|
||||
require './one.rb'
|
||||
|
||||
def pancake
|
||||
"Two pancakes"
|
||||
end
|
||||
|
||||
one = One.new
|
||||
one.donut
|
||||
puts pancake
|
||||
```
|
||||
|
||||
If you are coding along, don't forget to run `ctags -R .` again since you now have several new procedures. You have two instances of the `pancake` procedure. If you are inside `two.rb` and you pressed `Ctrl-]`, what would happen?
|
||||
|
||||
Vim will jump to `def pancake` inside `two.rb`, not the `def pancake` inside `one.rb`. This is because Vim sees the `pancake` procedure inside `two.rb` as having a higher priority than the other `pancake` procedure.
|
||||
|
||||
# Tag Priority
|
||||
|
||||
Not all tags are equal. Some tags have higher priorities. If Vim is presented with duplicate item names, Vim checks the priority of the keyword. The order is:
|
||||
|
||||
1. A fully matched static tag in the current file.
|
||||
2. A fully matched global tag in the current file.
|
||||
3. A fully matched global tag in a different file.
|
||||
4. A fully matched static tag in another file.
|
||||
5. A case-insensitively matched static tag in the current file.
|
||||
6. A case-insensitively matched global tag in the current file.
|
||||
7. A case-insensitively matched global tag in the a different file.
|
||||
8. A case-insensitively matched static tag in the current file.
|
||||
|
||||
According to the priority list, Vim prioritizes the exact match found on the same file. That's why Vim chooses the `pancake` procedure inside `two.rb` over the `pancake` procedure inside `one.rb`. There are some exceptions to the priority list above depending on your `'tagcase'`, `'ignorecase'`, and `'smartcase'` settings, but I will not discuss them here. If you are interested, check out `:h tag-priority`.
|
||||
|
||||
# Selective Tag Jumps
|
||||
|
||||
It would be nice if you can choose which tag items to jump to instead of always going to the highest priority tag item. Maybe you actually need to jump to the `pancake` method in `one.rb` and not the one in `two.rb`. To do that, you can use `:tselect`. Run:
|
||||
|
||||
```
|
||||
:tselect pancake
|
||||
```
|
||||
|
||||
You will see, on the bottom of the screen:
|
||||
|
||||
```
|
||||
# pri kind tag file
|
||||
1 F C f pancake two.rb
|
||||
def pancake
|
||||
2 F f pancake one.rb
|
||||
class:One
|
||||
def pancake
|
||||
```
|
||||
|
||||
If you type `2` then `<Return>`, Vim will jump to the procedure in `one.rb`. If you type `1` then `<Return>`, Vim will jump to the procedure in `two.rb`.
|
||||
|
||||
Pay attention to the `pri` column. You have `F C` on the first match and `F` on the second match. This is what Vim uses to determine the tag priotity. `F C` means a fully-matched (`F`) global tag in the current (`C`) file. `F` means only a fully-matched (`F`) global tag. `F C` always have a higher priority than `F`.
|
||||
|
||||
If you run `:tselect donut`, Vim also prompts you to select which tag item to jump to, even though there is only one option to choose from. Is there a way for Vim to prompt the tag list only if there are multiple matches and to jump immediately if there is only one tag found?
|
||||
|
||||
Of course! Vim has a `:tjump` method. Run:
|
||||
|
||||
```
|
||||
:tjump donut
|
||||
```
|
||||
|
||||
Vim will immediately jump to the `donut` procedure in `one.rb`, much like running `:tag donut`. Now run:
|
||||
|
||||
```
|
||||
:tjump pancake
|
||||
```
|
||||
|
||||
Vim will prompt you tag options to choose from, much like running `:tselect pancake`. With `tjump` You get the best of both methods.
|
||||
|
||||
Vim has a normal mode key for `tjump`: `g Ctrl-]`. I personally like `g Ctrl-]` better than `Ctrl-]`.
|
||||
|
||||
# Autocompletion With Tags
|
||||
|
||||
Tags can assist autocompletions. Recall from chapter 6, Insert Mode, that you can use `Ctrl-X` sub-mode to do various autocompletions. One autocompletion sub-mode that I did not mention was `Ctrl-]`. If you do `Ctrl-X Ctrl-]` while in the insert mode, Vim will use the tag file for autocompletion.
|
||||
|
||||
If you go into the insert mode and type `Ctrl-X Ctrl-]`, you will see:
|
||||
|
||||
```
|
||||
One
|
||||
donut
|
||||
initialize
|
||||
pancake
|
||||
```
|
||||
|
||||
# Tag Stack
|
||||
|
||||
Vim keeps a list of all the tags you have jumped to and from in a tag stack. You can see this stack with `:tags`. If you had first tag-jumped to `pancake`, followed by `donut`, and run `:tags`, you will see:
|
||||
|
||||
```
|
||||
# TO tag FROM line in file/text
|
||||
1 1 pancake 10 ch16_tags/two.rb
|
||||
2 1 donut 9 ch16_tags/two.rb
|
||||
>
|
||||
```
|
||||
|
||||
Note the `>` symbol above. It shows your current position in the stack. To "pop" the stack to go back to one previous stack, you can run `:pop`. Try it, then run `:tags` again:
|
||||
|
||||
```
|
||||
# TO tag FROM line in file/text
|
||||
1 1 pancake 10 puts pancake
|
||||
> 2 1 donut 9 one.donut
|
||||
|
||||
```
|
||||
|
||||
Note that the `>` symbol is now on line two, where the `donut` is. `pop` one more time, then run `:tags` again:
|
||||
|
||||
```
|
||||
# TO tag FROM line in file/text
|
||||
> 1 1 pancake 10 puts pancake
|
||||
2 1 donut 9 one.donut
|
||||
```
|
||||
|
||||
In normal mode, you can run `Ctrl-T` to achieve the same effect as `:pop`.
|
||||
|
||||
# Automatic Tag Generation
|
||||
|
||||
One of the biggest drawbacks of Vim tags is that each time you make a significant change, you have to regenerate the tag file. If you recently renamed the `pancake` procedure to the `waffle` procedure, the tag file did not know that the `pancake` procedure had been renamed. It still stored `pancake` in the list of tags. You have to run `ctags -R .` to create an updated tag file. Recreating a new tag file can be cumbersome.
|
||||
|
||||
Luckily there are several methods you can employ to generate tags automatically. My goal in this section is not to create a foolproof process, but to suggest some ideas so you can expand them.
|
||||
|
||||
## Generate a Tag on Save
|
||||
|
||||
Vim has an autocommand (`autocmd`) method to execute any command on an event trigger. You can use this to generate tags on each save. Run:
|
||||
|
||||
```
|
||||
:autocmd BufWritePost *.rb silent !ctags -R .
|
||||
```
|
||||
|
||||
Breakdown:
|
||||
|
||||
- `autocmd` is Vim's autocommand method. It accepts an event name, file pattern, and a command.
|
||||
- `BufWritePost` is an event for saving a buffer. Each time you save a file, you trigger a `BufWritePost` event.
|
||||
- `.rb` is a file pattern for ruby (`rb`) files.
|
||||
- `silent` is actually part of the command you are passing. Without this, Vim will display `press ENTER or type command to continue` each time you trigger the autocommand.
|
||||
- `!ctags -R .` is the command to execute. Recall that `!cmd` from inside Vim executes terminal command.
|
||||
|
||||
Now each time you save from inside a ruby file, Vim will run `ctags -R .`.
|
||||
|
||||
Add a new procedure in `two.rb`:
|
||||
|
||||
```
|
||||
def waffle
|
||||
"Two waffles"
|
||||
end
|
||||
```
|
||||
|
||||
Save the file. If you check the tag file, you will see `waffle` as part of the tags. Success!
|
||||
|
||||
## Using Plugins
|
||||
|
||||
There are several plugins to generate ctags automatically:
|
||||
|
||||
- [vim-gutentags](https://github.com/ludovicchabant/vim-gutentags)
|
||||
- [vim-tags](https://github.com/szw/vim-tags)
|
||||
- [vim-easytags](https://github.com/xolox/vim-easytags)
|
||||
- [vim-autotag](https://github.com/craigemery/vim-autotag)
|
||||
|
||||
I use vim-gutentags. If you use a Vim plugin manager ([vim-plug](https://github.com/junegunn/vim-plug), [vundle](https://github.com/VundleVim/Vundle.vim), [dein.vim](https://github.com/Shougo/dein.vim), etc), just install the plugin and it will work right ouf of the box!
|
||||
|
||||
## Ctags and Git Hooks
|
||||
|
||||
Tim Pope, author of many great Vim plugins, wrote a blog suggesting to use git hooks. [Check it out](https://tbaggery.com/2011/08/08/effortless-ctags-with-git.html).
|
||||
|
||||
# Learn Tags the Smart Way
|
||||
|
||||
A tag is useful once configured properly. If you are like me and you forget things easily, tags can help you visualize a project.
|
||||
|
||||
Suppose you are faced with a new codebase and you want to understand what `functionFood` does, you can easily read it by jumping to its definition. Inside it, you learn that it also calls `functionBreakfast`. You follow it and you learn that it calls `functionPancake`. Your function call graph looks something like this:
|
||||
|
||||
```
|
||||
functionFood -> functionBreakfast -> functionPancake
|
||||
```
|
||||
|
||||
This gives you insight that this code flow is related to having a pancake for breakfast.
|
||||
|
||||
To learn more about tags, check out `:h tags`. Now that you know how to use tags, let's explore a different feature: folding.
|
381
ch17_fold.md
Normal file
381
ch17_fold.md
Normal file
|
@ -0,0 +1,381 @@
|
|||
# Fold
|
||||
|
||||
When you read a file, often there are many irrelevant text that hinders you from understanding what that file does. To hide this unnecessary information, you can use Vim fold.
|
||||
|
||||
In this chapter, you will learn how to use different folding methods.
|
||||
|
||||
# Manual Fold
|
||||
|
||||
Imagine that you are folding a sheet of paper to cover some text. The actual text does not go away, it is still there. Vim fold works the same way. It *folds* a range of text, hiding it from display without actually deleting it.
|
||||
|
||||
The fold operator is `z`. When you fold a paper, the fold looks like the letter "z" too.
|
||||
|
||||
Suppose you have this text:
|
||||
|
||||
```
|
||||
Fold me
|
||||
Hold me
|
||||
```
|
||||
|
||||
Type `zfj`. Vim folds both lines into one. You should see something like this:
|
||||
|
||||
```
|
||||
+-- 2 lines: Fold me -----
|
||||
```
|
||||
|
||||
Here is the breakdown:
|
||||
|
||||
- `zf` `zf` is the fold operator.
|
||||
- `j` is the motion for the fold operator.
|
||||
|
||||
You can open a folded text with `zo`. To close the fold, use `zc`.
|
||||
|
||||
Vim fold follows the grammar rule. You can pass the fold operator with a motion or text object. To fold an outer paragraph, run `zfap`. To fold to the end of a file, run `zfG`. To fold the texts between `{` and `}`, run `zfa{`.
|
||||
|
||||
You can fold from the visual mode. Highlight the area you want to fold (`v`, `V`, or `Ctrl-V`), then run `zf`.
|
||||
|
||||
A Vim operator is not complete without the command-line mode version. You can execute a fold from the command-line mode with the `:fold` command. To fold the current line and the line after it, run:
|
||||
|
||||
```
|
||||
:,+1fold
|
||||
```
|
||||
|
||||
`,+1` is the range. If you don't pass parameters to the range, it defaults to the current line. `+1` is the range indicator for the next line. To fold the lines 5 to 10, run `:5,10fold`. To fold from the current line to the end of the line, run `:,$fold`.
|
||||
|
||||
There are many other fold and unfold commands. I find them too many to remember when starting out. The most useful ones are:
|
||||
- `zR` to open all folds.
|
||||
- `zM` to close all folds.
|
||||
- `za` toggle a fold.
|
||||
|
||||
You can run `zR` and `zM` on any line, but `za` only works when you are on a folded / unfolded line. To learn more folding commands, check out `:h fold-commands`.
|
||||
|
||||
# Different Fold Methods
|
||||
|
||||
The section above covers Vim's manual fold. There are six different folding methods in Vim:
|
||||
1. Manual
|
||||
2. Indent
|
||||
3. Expression
|
||||
4. Syntax
|
||||
5. Diff
|
||||
6. Marker
|
||||
|
||||
To see which folding method you are currently using, run `:set foldmethod?`. By default, Vim uses the `manual` method.
|
||||
|
||||
In the rest of the chapter, you will learn the other five folding methods. Let's get started with the indent fold.
|
||||
|
||||
# Indent Fold
|
||||
|
||||
To use an indent fold, change the `'foldmethod'` to indent:
|
||||
|
||||
```
|
||||
:set foldmethod=indent
|
||||
```
|
||||
|
||||
Suppose that you have the text:
|
||||
|
||||
```
|
||||
One
|
||||
Two
|
||||
Two again
|
||||
```
|
||||
|
||||
If you run `:set foldmethod=indent`, you will see:
|
||||
|
||||
```
|
||||
One
|
||||
+-- 2 lines: Two -----
|
||||
```
|
||||
|
||||
With indent fold, Vim looks at how many spaces each line has at the beginning and compares it with the `'shiftwidth'` option to determine its foldability. `'shiftwidth'` returns the number of spaces required for each step of the indent. If you run:
|
||||
|
||||
```
|
||||
:set shiftwidth?
|
||||
```
|
||||
|
||||
Vim's default `'shiftwidth'` value is 2. On the text above, there are two spaces between the start of the line and the text "Two" and "Two again". When Vim sees the number of spaces *and* that the `'shiftwidth'` value is 2, Vim considers that line to have an indent fold level of one.
|
||||
|
||||
Suppose this time you only one space between the start of the line and the text:
|
||||
|
||||
```
|
||||
One
|
||||
Two
|
||||
Two again
|
||||
```
|
||||
|
||||
Right now if you run `:set foldmethod=indent`, Vim does not fold the indented line because there isn't sufficient space on each line. However, if you change the `'shiftwidth'` to 1:
|
||||
|
||||
```
|
||||
:set shiftwidth=1
|
||||
```
|
||||
|
||||
The text is now foldable. Restore the shiftwidth back to two and the spaces between the texts to two again. In addition, add:
|
||||
|
||||
```
|
||||
One
|
||||
Two
|
||||
Two again
|
||||
Three
|
||||
Three again
|
||||
```
|
||||
|
||||
Run fold (`zM`), you will see:
|
||||
|
||||
```
|
||||
One
|
||||
+-- 4 lines: Two -----
|
||||
```
|
||||
|
||||
Unfold the folded lines (`zR`), then put your cursor on "Three" and toggle the text's folding state (`za`):
|
||||
|
||||
```
|
||||
One
|
||||
Two
|
||||
Two again
|
||||
+-- 2 lines: Three -----
|
||||
```
|
||||
|
||||
What's this? A fold within a fold?
|
||||
|
||||
You can have nested folds. The text "Two" and "Two again" have fold level of one. The text "Three" and "Three again" have fold level of two. If you have a foldable text with a higher fold level within a foldable text, you can have multiple fold layers.
|
||||
|
||||
# Marker Fold
|
||||
|
||||
To use a marker fold, run:
|
||||
|
||||
```
|
||||
:set foldmethod=marker
|
||||
```
|
||||
|
||||
Suppose you have the text:
|
||||
|
||||
```
|
||||
Hello
|
||||
|
||||
{{{
|
||||
world
|
||||
vim
|
||||
}}}
|
||||
```
|
||||
|
||||
Run `zM`, you will see:
|
||||
|
||||
```
|
||||
hello
|
||||
|
||||
+-- 4 lines: -----
|
||||
```
|
||||
|
||||
Vim sees `{{{` and `}}}` as fold indicators and folds the texts between them. With the marker fold, Vim looks for special markers, defined by `'foldmarker'` option, to mark folding areas. To see what markers Vim uses, run:
|
||||
|
||||
```
|
||||
:set foldmarker?
|
||||
```
|
||||
|
||||
By default, Vim uses `{{{` and `}}}` as indicators. If you want to change the indicator to another texts, like "foo1" and "foo2":
|
||||
|
||||
```
|
||||
:set foldmarker=foo1,foo2
|
||||
```
|
||||
|
||||
If you have the text:
|
||||
|
||||
```
|
||||
hello
|
||||
|
||||
foo1
|
||||
world
|
||||
vim
|
||||
foo2
|
||||
```
|
||||
|
||||
Now Vim uses `foo1` and `foo2` as the new folding markers. As a side note, an indicator must be a literal string and cannot be a regex.
|
||||
|
||||
# Syntax Fold
|
||||
|
||||
Vim has a syntax system to customize the text syntax (highlight, weight, color, etc). This chapter won't discuss how the syntax system works, but you can use this to indicate which text to fold. To use a syntax fold, run:
|
||||
|
||||
```
|
||||
:set foldmethod=syntax
|
||||
```
|
||||
|
||||
Suppose you have this text and you want to fold everything between the square brackets:
|
||||
|
||||
```
|
||||
[
|
||||
"one",
|
||||
"two",
|
||||
"three"
|
||||
]
|
||||
```
|
||||
|
||||
You need to define the proper syntax definition to capture the characters between the square brackets:
|
||||
|
||||
```
|
||||
:syn region testFold start="\\[" end="\\]" transparent fold
|
||||
```
|
||||
|
||||
You should see:
|
||||
|
||||
```
|
||||
+-- 5 lines: [ -----
|
||||
```
|
||||
|
||||
Here is the breakdown:
|
||||
- `:syn` is the syntax command.
|
||||
- `region` constructs a syntax region that can span several lines. For more info, check out `:h syntax.txt`
|
||||
- `start="\\[" end="\\]"` defines the starting and ending of a region. You have to escape (`\\`) the square-brackets because they are considered special characters.
|
||||
- `transparent` to prevent highlights.
|
||||
- `fold` increases the fold level when the syntax matches the starting and ending characters.
|
||||
|
||||
# Expression Fold
|
||||
|
||||
Expression folding allows you to define an expression to match for a fold. After you define the fold expressions, Vim scans each line for the value of `'foldexpr'`. This is the variable that you have to configure to return the appropriate value. If the `'foldexpr'` returns 0, then the line is not folded. If it returns 1, then that line has a fold level of 1. If it returns 2, then that line has a fold level of 2. There are more values other than integers, but I won't go over them. If you are curious, check out `:h fold-expr`.
|
||||
|
||||
First, let's change the foldmethod:
|
||||
|
||||
```
|
||||
:set foldmethod=expr
|
||||
```
|
||||
|
||||
Suppose you have a list of breakfast foods and you want to fold all breakfast items starting with "p":
|
||||
|
||||
```
|
||||
donut
|
||||
pancake
|
||||
pop-tarts
|
||||
protein bar
|
||||
salmon
|
||||
scrambled eggs
|
||||
```
|
||||
|
||||
Next, change the `foldexpr` to capture the expressions starting with "p":
|
||||
|
||||
```
|
||||
:set foldexpr=getline(v:lnum)[0]==\\"p\\"
|
||||
```
|
||||
|
||||
The expression above looks intimidating. Let's break it down:
|
||||
|
||||
- `:set foldexpr` sets up the `'foldexpr'` option to accept a custom expression.
|
||||
- `getline()` is a Vimscript function that returns the content of any given line. If you run `:echo getline(5)`, it will return the content of line 5.
|
||||
- `v:lnum` is Vim's special variable for the `'foldexpr'` expression. Vim scans each line and at that moment stores each line's number in `v:lnum` variable.
|
||||
- `[0]` in the context of `getline(v:lnum)[0]` is the first character of each line. When Vim scans a line, `getline(v:lnum)` returns the content of each line. `getline(v:lnum)[0]` returns the first character of each line. On the first line of our list, "donut", `getline(v:lnum)[0]` returns "d". On the second line of our list, "pancake", `getline(v:lnum)[0]` returns "p".
|
||||
- `==\\"s\\"` is the second half of an equality expression. It checks if the expression you just evaluated is equal to "s". If it is true, it returns 1. If it is false, it returns 0. In Vim, 1 is truthy and 0 is falsy. So on the lines that start with an "s", it returns 1. Recall at the beginning of this section, if a `'foldexpr'` has a value of 1, then it has a fold level of 1.
|
||||
|
||||
After running this expression, you should see:
|
||||
|
||||
```
|
||||
donut
|
||||
+-- 3 lines: pancake -----
|
||||
salmon
|
||||
scrambled eggs
|
||||
```
|
||||
|
||||
# Diff Fold
|
||||
|
||||
Vim can do a diff procedure to compare two or more files.
|
||||
|
||||
If you have `file1.txt`:
|
||||
|
||||
```
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
```
|
||||
|
||||
And `file2.txt`:
|
||||
|
||||
```
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
emacs is ok
|
||||
```
|
||||
|
||||
Run `vimdiff file1.txt file2.txt`:
|
||||
|
||||
```
|
||||
+-- 3 lines: vim is awesome -----
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
vim is awesome
|
||||
[vim is awesome] / [emacs is ok]
|
||||
```
|
||||
|
||||
Vim automatically folds some of the identical lines. When you are running the `vimdiff` command, Vim automatically uses `foldmethod=diff`. If you run `:set foldmethod?`, it will return `diff`.
|
||||
|
||||
# Persisting Fold
|
||||
|
||||
You loses all fold information when you close the Vim session. If you have this file, `count.txt`:
|
||||
|
||||
```
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
```
|
||||
|
||||
Then do a manual fold from line "three" down (`:3,$fold`):
|
||||
|
||||
```
|
||||
one
|
||||
two
|
||||
+-- 3 lines: three ---
|
||||
```
|
||||
|
||||
When you exit Vim and reopen `count.txt`, the folds are no longer there!
|
||||
|
||||
To preserve the folds, after folding, run:
|
||||
|
||||
```
|
||||
:mkview
|
||||
```
|
||||
|
||||
Then when you open up `count.txt`, run:
|
||||
|
||||
```
|
||||
:loadview
|
||||
```
|
||||
|
||||
Your folds are restored. However, you have to manually run `mkview` and `loadview`. I know that one of these days, I will forget to run `mkview` before closing the file and I will lose all the folds. Wouldn't it be nice if you can automate this?
|
||||
|
||||
Definitely! To automatically run `mkview` when you close a `.txt` file and run `loadview` when you open a `.txt` file, add this in your vimrc:
|
||||
|
||||
```
|
||||
autocmd BufWinLeave *.txt mkview
|
||||
autocmd BufWinEnter *.txt silent loadview
|
||||
```
|
||||
|
||||
You have seen the `autocommand` from the previous chapter. It is used to execute a command on an event trigger. There are two events to accomplish this:
|
||||
|
||||
- `BufWinLeave` for when you remove a buffer from a window.
|
||||
- `BufWinEnter` for when you load a buffer in a window.
|
||||
|
||||
Now after you fold inside a `.txt` file and exit Vim, the next time you open that file, your fold information will be restored.
|
||||
|
||||
By default, Vim saves the fold information when running `mkview` inside `~/.vim/view` for the Unix system. For more information, check out `:h 'viewdir'`.
|
||||
|
||||
# Learn Fold the Smart Way
|
||||
|
||||
When I first started Vim, I would skip learning Vim fold because I didn't think it was useful. However, the longer I code, the more useful I find folding is. Strategically placed folds can give you a better overview of the text structure, like a book's *table of content*.
|
||||
|
||||
When you learn fold, start with the manual fold because that can be used on-the-go. Then gradually learn different tricks to do indent and marker folds. Finally, learn how to do syntax and expression folds. You can even use the latter two to write your own Vim plugins.
|
||||
|
||||
Now that you know how to do fold, let's learn something different: version control with git.
|
Loading…
Reference in a new issue