Remenber to use :h <arg>
for help
Ref: https://learnvimscriptthehardway.stevelosh.com/
Basic
Echo
:echo "hello"
:echom "hello"
区别在于echom会将消息放入消息队列中:messages
Setting option
:set <name>=<value>
:set <name>?
可以查看设置的情况
Abbreviations
iabbrev adn and
在输入模式下输入adn,然后空格会发现变成了and
Buffer-Local Options
- local mapping
- like
:map <buffer> D dd
, the<buffer>
told vim only consider that mapping when we are in the buffer where we dedine it
- like
- local leader
<localleader>
instead of<leader>
- setting
setlocal <name>
- shadowing
1
2:nnoremap <buffer> Q x
:nnoremap Q dd- first mapping is more specific, so the second not work
Autocommands
1 | :autocmd BufNewFile * :write |
:help autocmd-events
Autocommands Group
It is possible to create duplicate autocommands, so if you try running the following command::autocmd BufWrite * :sleep 200m
three times. Vim will sleep 600m.
Use augroup
is the same
- Clearing Groups
autocmd!
1 | :augroup testgroup |
:help autocmd-groups
Operator-Pending Mapping
- like
:onoremap p i(
, when we rundp
it was like saying “delete parameters”, which vim translates to “delete inside parentheses”
Status Lines
:set statusline=%f\ -\ FileType:\ %y
:set statusline+=
1 | :set statusline=%l " Current line |
- width and padding
:set statusline=Current:\ %4l\ Total:\ %4L
, like thisCurrent: 12 Total: 223
:set statusline=Current:\ %-4l\ Total:\ %-4L
Current: 12 Total: 223
Programming Language
- set up folding for Vimscript files:
set foldmethod=<value>
Add lines before and after that autocommands group so that it look like thik:
1 | " Vimscript file settings ---------------------- {{{ |
In normal mode, type za
. Vim will fold/unfold the lines starting at the one containting 3{
and ending at 3}
:h foldlevelstart
Multiple-Line Statements
like
1 | :augroup testgroup |
You can write this as three separate lines in a Vimscript file. Or you can separate each line with a pipe character |
. Like this.
1 | :echom "foo" | echom "bar" |
Variables
let
to init or assignment a variable(call it var below)unlet
to delete a varunlet
to delete a var and ignore warnning, if var did not exist
You can add a prefix before :var to define its scope, like this
Options as Variables
1 | :set textwidth=80 |
Using an ampersand in front of a name tells Vim that you’re referring to the option, not a variable that happens to have the same name
Registers as Variables
1 | :let @a = "hello!" |
:echo @"
, unnamed register, which is where text you yank:echo @/
, echo search pattern you just use
Variables Scoping
1 | g:var # as global |
:h internal-variables
Conditionals
Any decision making you do in your coding will be done with if
s.
Basic If
1 | :if 1 |
Vim dose not necessarily treat a non-empty string as “truthly”.
try this
1 | :echom "hello" + 10 |
- Vim will try to coerce variables (and literals) when necessary. When 10 + “20foo” is evaluated Vim will convert “20foo” to an integer (which results in 20) and then add it to 10.
- Strings that start with a number are coerced to that number, otherwise they’re coerced to 0.
- Vim will execute the body of an if statement when its condition evaluates to a non-zero integer, after all coercion takes place.
Comparisons
Vim is case senitive so "foo" != "FOO"
. But if you :set ignorecase
, vim is case insenitive.
So to be code defensively you can nevertrust the ==
comparison. A bare ==
should never appear in you plugins’ code. Vim has two extra sets of comparison operators to deal with this.
==?
is the “case-insenitive”, no matter what user has set==#
is the “case-senitive”, no matter what user has set
String comparison
<string> == <string>
equal<string> != <string>
not equal<string> =~ <pattern>
matching pattern<string> !~ <pattern>
not matching pattern<operator>#
matching with case<operator>?
matching with ignorecase
<string>.<string>
to connect string
Function
Vimscript functions must start with a capital letter if they are unscoped!
Use function
keywork to define a function, use function!
to overwire a function. Be attention, function need to begin with capital letter.
:echo Function()
if Function has no return, vim will implicit return 0:delfunction <function>
to delete a function:call <function>
to call a function
Function Arguments
1 | function DisplayName(name) |
Notice the a:
in the name of the variable represents a variable scope. And it tell vim that they’re in the argument scope.
Varargs
Vimscript call take variable-length argument lists.
1 | function Varg(...) |
a:0
will be set to the number of extra arguments you were givena:1
will be set to the first extra argumenta:000
will be set to a list containing all the extra arguments
Assignment
Vimscript not allow you to reassign argument variables.Try running the follow commands, vim will throw an error. And you need a temp var.
1 | :function Assign(foo) |
String
Vimscritp use .
to connect strings. Use \
to escape sequences.
Using single quotes tell vim that you want string exactly as-is, with no escape sequences. And two single quotes in a row will produce one single quote. :echom 'That''s enough.'
String Functions
- Length
strlen("foo")
len("foo")
- Splitting: splits a String into a List
split("String", separator)
, separator is “whitespace” by default
- Joining: join a List into a String
join(List, connector)
- Case
tolower("String")
toupper("String")
Execute
The execute
command is user to evaluate a string as if it were a Vimscripte command.
Normal
Try run
1 | :nnoremap G dd |
Vim will delete the current line. The normal
command will take into account any mapping that exist.
Vim has a normal!
command to avoid user mapping. So write Vimscript should always use normal!
.
The problem is that normal!
dosen’t parse special character sequences like <cr>
. Combining normal!
with execute
fixes that problem. Like this:
1 | :execute "normal! gg/print\<cr>" |
Demos
Grep Operation, Part One
:nnoremap <leader>g :grep -R '<cWORD>' %<cr>
Escaping Shell Command Argument
Try the mapping on the word that's
. It won’t work, because the single quote inside the word interferes with the quotes in the grep command!
To get around this we can use Vim’s shellescape
function. :h shellescape()
and :h escape
The problem is that Vim performed the shellescape()
call before it expended out special string like <cword>
. So :echom shellescape("<cWORD>")
is literally output '<cword>'
To fix this we’ll use the expand()
function to force th expansion of <cword>
into the actual string before it gets passed to shellescape
.
try following over the word like that's
:
1 | :echom expand("<cWORD>") |
Finally, try :nnoremap <leader>g :silent execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr>
:h copen
,- Open a window to show the current list of errors
:h silent
- The
silent
command just runs the command that follows it while hiding any messages it would normally display
- The
Grep Operator, Part Two
- Create a File
- Inside
.vim/plugin
create file namedgrep-operato.vim
- This is where you’ll place the code.
- Inside
- Skeleton
- To create a new Vim operator you’ll start with two components:a function and a mapping
1
2
3
4
5nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
function! GrepOperator(type)
echom "Test"
endfunction - we set the
operatorfunc
option to our function - then we run
g@
which calls this function as an operator- try type
g@
in normal mode
- try type
- To create a new Vim operator you’ll start with two components:a function and a mapping
- Visual Mode
- Add another mapping
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
<c-u>
to say “delete from the cursor to the bebginning of the line”visualmode()
is a built-in Vim function that return a one-character string representing the last type of visual mode. “v、V and Crtl-v”
- Add another mapping
Motion type
1 | nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@ |
- Pressing
viw<leader>g
echoesv
because we were in characterwise visual mode. - Pressing
Vjj<leader>g
echoesV
because we were in linewise visual mode. - Pressing
<leader>giw
echoeschar
because we used a characterwise motion with the operator. - Pressing
<leader>gG
echoesline
because we used a linewise motion with the operator.
Copying the Text
Edit a function look like this:
1 | nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@ |
==#
case-sensitive comparisonnormal!
dose two things:- Visually select the range of text we want by:
- Moving to mark at the beginning of the range.
- Entering characterwise visual mode.
- Moving to the mark at the end of the range.
- Yanking the visually selected text.
- Visually select the range of text we want by:
@@
is the “unnamed” register: the one that Vim places text into then yank or delete by default
Grep Operator, Part Three
Namespacing
Our script create a function named GrepOperator
in the global namespace.
We can avoid polluting the global namespace by tweaking a couple of lines in our code.
1 | nnoremap <leader>g :set operatorfunc=<SID>GrepOperator<cr>g@ |
- modified the function name to start with
s:
which places it in the current script’s namespace - modified the mapping and prepended the
GrepOperator
function name with<SID>
so they could find the function.- If we hadn’t done this they would have tried to find the function in the global namespace, which wouldn’t have worked
:h <SID>
List
Vim List is much like python.
- Lists
:echo ['foo', 3, 'bar']
:echo ['foo', [3, 'bar']]
of course can be nested
- Indexing
:echo [0, [1, 2]][1]
will display[1, 2]
:echo [0, [1, 2]][-2]
will display0
- Slicing
:echo ['a', 'b', 'c', 'd', 'e'][0:2]
:echo ['a', 'b', 'c', 'd', 'e'][:2]
- Concatenation
:echo ['a', 'b'] + ['c']
- Built-in List Function
:call add(list, 'b')
appendb
:echo len(list)
:echo get(foo, 100, 'default')
- The
get
function will get the item at the given index from the given list, or return the given default value if the index is out of range in the list.
- The
:echo index(foo, 'b')
- return the first index of
b
, or-1
if not in list
- return the first index of
:echo join(list, '--')
join item in list together into a string:call reverse(list)
:h functions
Looping
- For Loops
1
2for i in [1, 2, 3]
endfor - While Loops
1
2
3
4let c = 1
while c <= 4
let c += 1
endfor
Dictionaries
- Dictionaries
:echo {'a': 1, 100: 'foo'}
- Indexing
:echo {'a': 1, 100: 'foo',}['a']
- Or Javascritp-style “dot”
:echo {'a': 1, 100: 'foo',}.a
- Assigning and Adding
:let foo = {'a': 1}
- Assigning:
:let foo.a = 100
- Add:
:let foo.b = 200
- Assigning:
- Removing Entries
:let test = remove(foo, 'a')
- Remove the entry with the given key
- And return the removed value
unlet foo.b
- Also removes entries, but you can’t use the value
- Dictionary Functions
:echom get({'a': 100}, 'b', 'default')
- just like get function for lists
:echom has_key({'a': 100}, 'a')
- return
0
as falsy
- return
:echo items({'a': 100, 'b': 200})
- You can pull the key-value pairs out of a dict with
item
- display like
[['a', 100], ['b', 200]]
- You can pull the key-value pairs out of a dict with
Toggling
For boolean options we can use set someoption!
to “toggle” the option.If we want to toggle a non-boolean option we’ll need to do a bit more work.
Like this. Remember Vim treats the 0 as falsy and any other number as truthy
1 | function! FoldColumnToggle() |
:h wincmd
:h winnr()
Functional Programming
Immutable Data Structures
Vim dosen’t have any immutable collections, but by create some helper functions we can fake it to some degree.
Like this:
1 | function! Sorted(l) |
Vim’s sort()
sorts the list in place, so we create a full copy so original list won’t changed.
Functions as Variables
Vimscript supports using variables to store functions, but the syntax is a bit obtuse.
If a Vimscript variable refers to a function it must start with a capital letter.
Higher-Order Functions
1 | function! Mapped(fn, l) |
Paths
- Relative Paths
:echom expand('%')
.%
means the file you’re editing.
- Absolute Paths
:echom expand('%:p')
. The:p
in the string tells Vim that you want the absolute path.:echo fnamemodify('foo.txt', ':p')
did the same.fnamemodify()
is a Vim function that’s more flexible thanexpand()
in that you can specify any file name, not just special stirngs.
- Listing Files
:echo split(globpath('.', '*'), '\n')
, list of files in a specific directory(here is all files of current dir).- You can recursively list file with
**
::echo split(globpath('.', '**'), '\n')
Creating a Full Plugin
Basic Layout
- Files in
~/.vim/colors
are treated as color schemes - Files in
~/.vim/plugin
will each be run once every time Vim starts - Files in
~/.vim/ftdetect
will also be run every time you start Vimftdetect
stands for “filetype detection”- The file in this dir should set up autocommmands that detect and set the
filetype
of files
- Files in
~/.vim/ftplugin
. When Vim sets a buffer’sfiletype
to a value it then looks for a file(or dir) inftplugin
that matches and run it.- The naming of these files matters!
- Because these files are run every time a buffer’s filetype is set they must only set buffer-local options! If they set options globally they would overwrite them for all open buffers!
- Files in
~/.vim/indent
are a lot likeftplugin
files. They get loaded based on their names. - Files in
~/.vim/compiler
work exactly likeindent
files.- They should set compiler-related options in the current buffer based on their names
- Files in
~/.vim/after
Files in this dir will be loaded every time Vim start, but after the file in~/.vim/plugin
- Files in
~/.vim/autoload
- Files in
~/.vim/syntax
When Vim sets a buffer’sfiletype
to a value it then looks for a file(or dir) insyntax
that matches and run it. - Files in
~/.vim/doc
is where you can add doc for your plugin
Runtimepath
Much like PATH
on Linux/Unix/BSD
systems, Vim has the runtimepath setting which tells it where to find files to load.
:set runtimepath?
So a plugin manager automatically add paths to your runtimepath
when you load vim
Detecting Filetype
Vim dosen’t what a .type
file is yet.
Create ftdetect/TYPE.vim
au BufNewFile,BufRead *.TYPE set filetype=TYPE
Basic Syntax Highlighting
:help syn-keyword
:help iskeyword
:help group-name
:help syntax
Create a syntax/potion.vim
file and set you buffer filetype to potion :set filetype=potion
. The to
, times
and if
words will be highlighted as keywords.
1 | syntax keyword potionKeyword to times |
These two lines show the basic structure of simple sytax highlight in Vim.
- You first define a “chunk” of syntax using
syntax keyword
or a related command (which we’ll talk about later) - You then link “chunks” to highlighting groups. A highlighting group is something you define in a color scheme, for example “function names should be blue”.
Highlighting Functions
Another standard Vim highlighting group is Function
.
1 | syntax keyword potionFunction print join string |
Advanced Syntax Highlighting
Because some character not in iskeyword
we need to use a regular expression to match it. We’ll do this with syntax match
instead of syntax keyword
.
1 | syntax match potionComment "\v#.*$" |
We use syntax match
which tells Vim to match regexes instead of literal keywords.
Highlighting Operators
Another part we need to regexes to highlight is operators. :h group-name
1 | syntax match potionOperator "\v-\=" |
Highlighting Strings
:help syn-region
To highlight strings, we’ll use the syntax region
command.
1 | syntax region potionString start=/\v"/ skip=/\v\\./ end=/\v"/ |
You’ll see the string between “” is highlighted!
- Regions hava a “start” pattern and an “end” pattern that specify where they start and end
- The
skip
argument tells Vim to ignore this matching region- The “skip” argument to
syntax region
allow us to handle string escapes like"she said: \"hello\"!"
- The “skip” argument to
Basic Folding
:help usr_28
:help fold-XXX
- Manual
- You create the folds by hand and they stored in RAM. When you close Vim they go away.
- Marker
- Vim folds your code based on characters in the actual text.
- Usually these characters put in comments (like
3{
), but in some languages you can get away with using something in the language’s syntax itself, like{
in javascript file. - It may seem ugly to clutter up your code with comments, but it let you hand-craft folds for a specific file.
- Diff
- A special folding mode used when diff’ing files.
- Expr
- This lets you use a custom piece of Vimscript to define where folds occur.
- Indent
- Vim uses your code’s indentation to determine folds.
setlocal foldmethod=<method>
and play around with zR
, zM
, za
, zf
…
Advanced Folding
- Folding Theory
- Each line of code in a file has a “foldlevel”. This is always either zero or a positive interger.
- Lines with a foldlevel of zero are never include in any fold
- Adjacant line with the same foldlevel are folded togerther
- If a fold level X is closed, any subsequent lines with a foldlevel greater then or equal to X are folded along with it until you reach a line with a level less than X.
Expr Folding
1 | setlocal foldmethod=expr |
- first line tells Vim to use
expr
folding - second line defines the expression Vim should use to get the foldlevel of a line
- When Vim runs the expression it will set
v:lnum
to the line number of the ling it wants to know about.
- When Vim runs the expression it will set
- The expr function return a String and not an Integer
Our funciont return '0'
for every lines, so Vim won’t fold andthing at all.
Blank Lines
Modify the GetFold
function to look like this:
1 | function! GetPotionFold(lnum) |
- Use
getline(a:lnum)
to get the content of the current line as a String.- This regex
\v^\s*$
will match “beginning of line, any number of whitespace characters, end of line”
- This regex
Special Foldlevels
Your custom folding expression can return one of a few “special” strings that tell Vim how to fold the line.
-1
is one of these special strings. It tells Vim that the level of this line is “underfined”, which means “the foldlevel of this line is equal to the foldlevel of the line above or below it”
An Indentation Level Helper
To tackle non-blank lines we’ll need to know their indentation level.
1 | function! IndentLevel(lnum) |
And run :echom IndentLevel(1)
to see line 1 indent level.
Section Movement Theory
If you’ve never used Vim’s section movement commands(like: [[
, ]]
, []
, ][
) take a second and read :help section
Nroff Files
The four “section movement” commands are conceptually meant to move around between “sections” of a file.
These commands are designed to work with nroff files by default.
External Commands
The :!
command (pronounced “bang”) in Vim runs external commands and display their output on the screen.
Vim doesn’t pass any input to the command then run this way: :!cat
To run an external command without the Press ENTER or type command to continue
prompt, use :silent !
. Note that this command is :silent !
and not :silent!
system()
The system()
Vim function takes a command string as a parameter and returns the output of that command as String.
You can pass a second string as an argument to system()
. Run the following command: :echom system("wc -c", "abcdefg")
If you pass a second argument like this, Vim will write it to a temporary file and pipe it into the command on standard input.
Scratch Splits
:split BufferName
create an new splite for a buffer named BufferName
append()
The append()
Vim function takes two arguments: a line number to append after, and a list of Strings to append as lines. For example:
1 | call append(3, ["foo", "bar"]) |
:help append()
split()
split()
takes a String to split and regular expression to find the split points.
:help split()
Autoloading
:help autoload
Autoload lets you delay loading code until it’s actually needed.
Look at the following command:
1 | :call somefile#Hello() |
If this function has already been loaded, Vim will simply call it normally. Otherwise Vim will look for a file called autoload/somefile.vim
.
If this file exists, Vim will load/source that file. It will then try to call the function normally.
Inside this file, the function should be defined like this:
1 | function somefile#Hello() |
You can use multiple #
characters in the function name like :call myplugin#somefile#Hello()
. And it will look for autoload/myplugin/somefile.vim
If a autoload function like somefile#Hello()
has been loaded, Vim doesn’t need to reload the file to call it.
If somefile#A()
has been load and somefile#B()
doesn’t load. At that time, you rewrite function A. Without closing Vim and call funcion B, Vim will reload this file and your modify in function A will be update.
Documentetion
How Documentetion Works
A documentation filetype=help
.
Create a file called doc/somefile.txt
in you plugin repo. This is where we’ll write help for our plugin.
Open this file in Vim and run :set filetype=help
so you can see the syntax highlighting as you type.
Help Header
The format of help files is a matter of personal taste, but better to be popular with the modern Vimscript community.
- The first line of the file should contain the filename of the help file.
*myhelp.txt* functionality for the potion programming language
- Surrounding a word witd asterisks in a help file create a “tag” that can be jumped to.
- Run
:Helptags
to rebuild the index of help tags, and then open a new Vim window and run:help myhelp.txt
. Vim will open your help document like any other one.
- Run
- Title, description, authors and something
- The
~
characters at the end of the lines ensure that Vim dosen’t try to highlight or hide individual characters inside the art.
- The
Table of Contents
1 | ==================================================== |
- The line of
=
character will be syntax highlight. Use to divide.- You can also use line of
-
- You can also use line of
- The
*PotionContents*
will create another tag, so run:help PotionContents
to go directly to the table of contents. - Each of the word surrounded by
|
create a ling to a tag- Users can press
<c-]>
in the file file to jump to the tag, Or click with mouse
- Users can press
The Command Command
Read :help user-commands
Some plugin hava commands like :Gdiff
and leave it up to the user to decide how to call them.
Commands like this are create with the :command
command.
Omnicomplete
:help ins-completion
:help omnifunc
:help compl-omni
Vim offer a number of different ways to complete text. The most powerful of them is “omnicomplete” which let you call a custom Vimscirpt function to determin completions.
Compiler Support
Read :help quickfix.txt
Vim offer much deeper support for interacting with compilers, including parsing compile error.