Skip to content

Instantly share code, notes, and snippets.

@aktau
Created March 5, 2014 12:11
Show Gist options
  • Save aktau/9366015 to your computer and use it in GitHub Desktop.
Save aktau/9366015 to your computer and use it in GitHub Desktop.
vim_source_analysis.md

Vim refactoring

Analyse how f_system works (the VimL system call).

eval.c: f_system()

infile = NULL if arg[1].v_type is not VAR_UNKNOWN:

  • this is an input string
  • look for a temp file name (vim_tempname, horrible and complex). infile
  • fopen/fwrite/fclose
  • res = get_cmd_output(get_tv_string(&arg[0]), infile, kShellOptSilent | kShellOptCooked)
  • translate line endings if necessary
  • os_remove(infile)

misc1.c: get_cmd_output(cmd, infile, flags) used by: f_system(infile), find_locales(NULL), expand_backtick(NULL)

// get the stdout of an external command, returns alloced string or NULL

  • tempname = look for a temp file name (vim_tempname)
  • cmd = make_filter_cmd(cmd, infile, tempname)
  • call_shell(cmd, kShellOptDoOut | kShellOptExpand | flags, NULL)
  • read tempname into memory (fopen, ftell, fread, fclose, os_remove)
  • change NULs into SOHs, NUL-terminate
  • return output

ex_cmds.c: make_filter_cmd(cmd, itmp, otmp)

// create a shell command from a cmd string, input redirection file and // output redirection file. Returns an alloced string with the shell // command

  • (optionally) put braces around command
  • (optionally itmp != NULL) add file input: " < "
  • (optionally) special casing for non-UNIX shells that need " < " on the first command and not at the end of a pipe
  • (optionally otmp != NULL) append_redir()
  • return buf

ex_cmds.c: append_redir(buf, buflen, opt, fname)

// append output redirection for file fname to the end of string buffer, // works with the shellredir and shellpipe options // NOTE: shellredir = EXTERN p_srr = usually > I suppose... // NOTE: shellpipe = EXTERN p_sp = "| tee" (for unix), ">" otherwise

shell.c: os_call_shell(cmd, opts, extra_shell_args)

typedef void (shell_read_cb)(char_u buf, size_t cnt); proposed: os_call_shell(cmd, opts, extra_shell_args, write_buf, write_buf_size, shell_read_cb read_cb);

Vimscript internals

Functions are not (ex) commands

Functions and commands are defined differently, an example:

function! s:MyFunc(myParam)
    do something
endfunction

command! -nargs=1 MyCommand call s:MyFunc(<f-args>)

User-defined commands doc.

There's buffer-local commands as well. All user-defined commands must stat with a capital letter. User-defined commands can be abbreviated, but it's not recommended. They can be undefined with :delcommand.

Important globals

  • hash/func_hashtab
  • ga_array/functions: ordered list of default functions, ordered because it needs to be able to do lookup of partial strings (e.g.: "function" can also be "fun")
  • ga_array/script_items: store sourced scripts (an array of scriptitem_S)
  • ga_array/ga_scripts: array that holds a dictionary for each script (each ) with the script-local variables

Structures

typedef struct exarg {
  char_u      *arg;             /* argument of the command */
  char_u      *nextcmd;         /* next command (NULL if none) */
  char_u      *cmd;             /* the name of the command (except for :make) */
  char_u      **cmdlinep;       /* pointer to pointer of allocated cmdline */
  cmdidx_T cmdidx;              /* the index for the command */
  long argt;                    /* flags for the command */
  int skip;                     /* don't execute the command, only parse it */
  int forceit;                  /* TRUE if ! present */
  int addr_count;               /* the number of addresses given */
  linenr_T line1;               /* the first line number */
  linenr_T line2;               /* the second line number or count */
  int flags;                    /* extra flags after count: EXFLAG_ */
  char_u      *do_ecmd_cmd;     /* +command arg to be used in edited file */
  linenr_T do_ecmd_lnum;        /* the line number in an edited file */
  int append;                   /* TRUE with ":w >>file" command */
  int usefilter;                /* TRUE with ":w !command" and ":r!command" */
  int amount;                   /* number of '>' or '<' for shift command */
  int regname;                  /* register name (NUL if none) */
  int force_bin;                /* 0, FORCE_BIN or FORCE_NOBIN */
  int read_edit;                /* ++edit argument */
  int force_ff;                 /* ++ff= argument (index in cmd[]) */
  int force_enc;                /* ++enc= argument (index in cmd[]) */
  int bad_char;                 /* BAD_KEEP, BAD_DROP or replacement byte */
  int useridx;                  /* user command index */
  char_u      *errmsg;          /* returned error message */
  char_u      *(*getline)(int, void *, int);
  void        *cookie;          /* argument for getline() */
  struct condstack *cstack;     /* condition stack for ":if" etc. */
} exarg_T;

Parsing vs. executing

How doe eval_1, eval_2, et cetera intertwine with do_source and the like? Surely do_source must somehow call a parsing function. How does the parse call back? Is it just used as a verifier that everything is ok?

Builtin EX commands vs user-defined commands

These are NOT functions. Mostly around ex_docmd.c.

  • Builtin commands are defined in a pre-defined array: cmdnames[]
  • User-defined commands are executed via do_ucmd()

Structure/call graph

| `-> do_source (parse every line in file as an EX command)

ex_eval.c

functions for Ex command line for the +eval feature.

Seems to contains a few control structures, function prototype void ex_KEYWORD(exarg_T *eap)

These commands often adjust of exarg_T.cstack (struct condstack), all conditional branches are on a stack. The maximum stacksize is 50 deep.

  • try/catch/finally:
  • throw: throw (do_throw called by ex_throw)
  • if/else: if, else
  • while/for: while
  • endwhile/endfor: endwhile
  • continue: continue
  • break: break

ex_cmds_defs.h

Depending on what's #define'd, it declares a series of enums or an array of structs with builtin commands.

ex_docmd.c

A couple of very important functions are defined here:

  • do_one_command: execute one EX command
    1. skip comment lines and leading space
    1. handle command modifiers
    1. parse range
    1. parse command
    1. parse arguments
    1. switch on command name
  • lots of ex commands:
  • ex_command (:command)
  • ex_tabclose (:tabclose)
  • ex_cd (:cd, :chdir, ...)
  • do_sub (:s/.../.../) ...

ex_docmd.c/ex_cmds.c/ex_eval.c/... (what?)

Notes

What's the difference between:

  • builtin functions, f_*, e.g.: f_function)
  • ex commands, ex_*, e.g.: ex_echo, ex_execute, ex_return
  • do commands, do_*, e.g.: do_ascii (:ascii), do_bang (only caller of do_filter) -> it appears "do" is mostly synonymous to "ex" in this case (cruft)

IBM Vimscript guide: Note, however, that, unlike C or Perl, Vimscript does not allow you to throw away the return value of a function without using it. So, if you intend to use the function as a procedure or subroutine and ignore its return value, you must prefix the invocation with the call command. Otherwise, Vimscript will assume that the function call is actually a built-in Vim command and will most likely complain that no such command exists. We'll look at the difference between functions and commands in a future article in this series.

Line ranges:

You can invoke any standard Vim command—including call—with a preliminary line range, which causes the command to be repeated once for every line in the range:

"Delete every line from the current line (.) to the end-of-file ($)... :.,$delete

"Replace "foo" with "bar" everywhere in lines 1 to 10 :1,10s/foo/bar/

And there's a crazy special-special case:

Internalizing function line ranges This call-the-function-repeatedly-for-each-line behavior is a convenient default. However, sometimes you might prefer to specify a range but then have the function called only once, and then handle the range semantics within the function itself. That's also easy in Vimscript. You simply append a special modifier (range) to the function declaration:

function DeAmperfyAll() range"Step through each line in the range...
    for linenum in range(a:firstline, a:lastline)
    ...

then the function is invoked only once, and two special arguments, a:firstline and a:lastline, are set to the first and last line numbers in the range. If no range is specified, both a:firstline and a:lastline are set to the current line number.

How about '|'?

Some commands handle '|' themselves! (mentioned in ex_docmd.c:1970)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment