Post

Vim Command Line Area

In Vim, the bottom line where you type commands like :w, :q, or /pattern is called Command-line area. I use it everyday, but never know its exact syntax and how the interpreter works. This post is for this purpose.

Grammar

The top-level function for Command-line interpreter is function do_cmdline. I copied this whole function to Claude, and Claude told me “The function is quite sophisticated”. Nevertheless, it happily generated an ebnf grammar for me.

👉 Command-line ebnf grammar 👈
(* Main Program Structure *)
program ::= command_sequence
command_sequence ::= command_line (newline command_line)*
command_line ::= command ('|' command)*

(* Basic Command Structure *)
command ::= simple_command 
          | control_structure
          | exception_structure
          | loop_structure
          | conditional_structure

simple_command ::= command_name arguments?

(* Control Structures *)
control_structure ::= if_structure
                    | while_structure  
                    | for_structure
                    | try_structure
                    | function_definition

(* Conditional Structures *)
if_structure ::= 'if' condition newline
                 command_sequence
                 ('elseif' condition newline command_sequence)*
                 ('else' newline command_sequence)?
                 'endif'

conditional_structure ::= if_structure

(* Loop Structures *)
while_structure ::= 'while' condition newline
                    command_sequence
                    'endwhile'

for_structure ::= 'for' variable 'in' iterable newline
                  command_sequence
                  'endfor'

loop_structure ::= while_structure | for_structure

(* Exception Handling *)
try_structure ::= 'try' newline
                  command_sequence
                  ('catch' pattern? newline command_sequence)*
                  ('finally' newline command_sequence)?
                  'endtry'

exception_structure ::= try_structure

(* Function Definition *)
function_definition ::= 'function' function_name '(' parameter_list? ')' function_attributes? newline
                       command_sequence
                       'endfunction'

(* Loop Control *)
loop_control ::= 'continue' | 'break'

(* Function Control *)
function_control ::= 'return' expression?

(* File Control *)
file_control ::= 'finish'

(* Terminals and Primitives *)
command_name ::= identifier range_prefix?

range_prefix ::= range ':'?

range ::= line_specifier (',' line_specifier)?

line_specifier ::= number
                 | '.'
                 | '$'
                 | '%'
                 | "'" mark
                 | '/' pattern '/'
                 | '?' pattern '?'
                 | line_specifier ('+' | '-') number?

condition ::= expression

expression ::= (* Complex expression grammar - simplified *)
             | variable
             | literal
             | function_call
             | binary_operation
             | unary_operation

variable ::= identifier

literal ::= string_literal | number_literal

string_literal ::= '"' string_content '"'
                 | "'" string_content "'"

number_literal ::= digit+ ('.' digit+)?

function_call ::= function_name '(' argument_list? ')'

argument_list ::= expression (',' expression)*

parameter_list ::= parameter (',' parameter)*

parameter ::= identifier

function_attributes ::= attribute*

attribute ::= 'range' | 'abort' | 'dict' | 'closure'

binary_operation ::= expression operator expression

unary_operation ::= operator expression

operator ::= '+' | '-' | '*' | '/' | '%' | '==' | '!=' | '<' | '>' | '<=' | '>=' 
           | '&&' | '||' | '=~' | '!~' | 'is' | 'isnot'

arguments ::= argument*

argument ::= expression | flag | option_setting

flag ::= '+' | '-' | '!'

option_setting ::= identifier '=' expression

identifier ::= letter (letter | digit | '_')*

letter ::= 'a'..'z' | 'A'..'Z'

digit ::= '0'..'9'

mark ::= letter

pattern ::= (* Regular expression pattern *)

iterable ::= expression

newline ::= '\n' | '\r\n'

string_content ::= (* Any characters except closing quote *)

function_name ::= identifier

(* Comments *)
comment ::= '"' comment_content

comment_content ::= (* Any characters to end of line *)

It is fairly complicated! You can write if, for and while control flow statements in the Command-line area. And can also use | to pipe multiple commands into one.

do_cmdline sets up the skeleton of the interpreter. The actual implementation for each semantic keyword/structure is scattered in different places. For example, while’s implementation is here. You can search void ex_ in file ex_eval.c, ex_cmds.c and ex_docmd.c to find out what commands Vim has defined and their implementations.

:!

The Bang command is very powerful. The implementation is here.

There are three mainly use cases.

  1. :!<cmd>: run cmd and show the output in command-line area.
  2. :r!<cmd>: run cmd and insert the output to the current cursor location. This is actually a compound command. r means read which is another vim command, and pattern r! has a special code path. Therefore, :r!date is shorthand of :read!date.
  3. %<range>!<cmd>: use line range as input to the command, and use the output to overwrite this range. This is useful for reformatting code.

:normal

normal is another commonly used command. For example, how to insert a character at the end of each line in a range? One way is :<range>normal A<char>.

The implementation is here. Basically, it runs the same command for each line in the range selection.

:luado

This command executes a chunk of Lua code that acts on a range of lines in the current buffer. Whatever string is returned from the chunk is used to determine what each line should be replaced with. Two implicit line and linenr variables are also provided. line is the text of the line being iterated upon whereas linenr is its number.

Examples:

  1. :<range>luado return line:upper() converts selected lines to upper case.
  2. :<range>luado return line .. ';' adds semicolon at the end of each line.
  3. :<range>luado return '"' + line + '"' adds double quotes around each line.

Source code here. You can see that it generates a function function(line, linenr) and pushes it to stack. Then it executes this function for each selected line. If the function returns a string, then it uses this string to replace existing buffer content:

1
2
ml_replace(l, new_line_transformed, false);
inserted_bytes(l, 0, old_line_len, (int)new_line_len);
This post is licensed under CC BY 4.0 by the author.