注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

从选择word开始看vim一些操作实现  

2012-06-17 23:16:17|  分类: 源代码 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、vim操作
vim最早是在真正的没有鼠标的终端上运行的,所以所有的操作都是使用键盘来完成的,即使在现在,有些时候只能通过远程终端来连接,连接之后的操作也通常不能使用鼠标。使用vim其实比较更加像一个非常简陋的一个dos系统,它所有的操作都是通过指令来完成。对于一个经常修改文件的人来说,例如程序员,可能把双手尽可能的固定在键盘上效率会更高,因为在键盘和鼠标之前切换的时候还是非常浪费时间和影响思路的,可以想象之前的程序员在写程序的时候双手是一直在键盘之上的。
其实对于很多看似生僻的操作,当真正在使用vim做开发的时候都可能会用到,例如如何删除光标之下的单词?熟悉vim的同学可能脱口而出使用 dw指令,但是dw只是删除从光标开始到单词的结束,而不是删除整个单词,事实上正确的指令应该是diw,其中中间的修饰符‘i’是inner的缩写,大家也可以使用中间的‘a’修饰符,之间有一些简单的语义差别。其中的i和a都是vim的单独操作指令,分别为insert和append指令,所以感觉vim的指令系统还是比较复杂的,那么就简单看一下vim的内部是如何实现这种复杂指令编码系统的。
二、操作次数
大部分的vim命令都可以在开始首先指明接下来的操作循环的次数,这一点和C语言比较相似,这是一个词法的分析机制,也就是通过数字开始的指令都是表示重复次数,有特殊的意义,这一点次数本身的特征就比较明显,可以区分开来其它的单词类指令。关于数字的操作就是放在对于一个对指令读取的大函数中完成,而这个数值将会进一步作为一个参数传递给即将执行的命令,相应代码为:
normal_cmd(oap, toplevel)
    /*
     * Handle a count before a command and compute ca.count0.
     * Note that '0' is a command and not the start of a count, but it's
     * part of a count after other digits.
     */
    while (    (c >= '1' && c <= '9')
        || (ca.count0 != 0 && (c == K_DEL || c == K_KDEL || c == '0')))
    {
        if (c == K_DEL || c == K_KDEL)
        {
        ca.count0 /= 10;
#ifdef FEAT_CMDL_INFO
        del_from_showcmd(4);    /* delete the digit and ~@% */
#endif
        }
        else
        ca.count0 = ca.count0 * 10 + (c - '0');
        if (ca.count0 < 0)        /* got too large! */
        ca.count0 = 999999999L;
……
        c = plain_vgetc();这里将会做特殊处理,不断的读入用户输入字符,直到遇到一个非数字结束,所以对于接下来的命名解析来说,是不用担心数字的处理的,而只用接收并处理命令的次数即可
}
……
if (ca.opcount != 0) 在分支中将 2d3w转换为d6w
    {
    /*
     * If we're in the middle of an operator (including after entering a
     * yank buffer with '"') AND we had a count before the operator, then
     * that count overrides the current value of ca.count0.
     * What this means effectively, is that commands like "3dw" get turned
     * into "d3w" which makes things fall into place pretty neatly.
     * If you give a count before AND after the operator, they are
     * multiplied.
     */
    if (ca.count0)
        ca.count0 *= ca.opcount;
    else
        ca.count0 = ca.opcount;
    }
三、叠字操作
主要是指dd、yy之类的以行为单位的操作,这些比较特殊,它们通常操作的单位都是一行,而这个也正是很多情况下需要处理的一种情况。现在的问题是这个行是单个命令来各自处理(例如d判断自己的接下来操作内容是否为d)还是由框架统一为大家处理?响应的代码位于
/*
 * Handle an operator command.
 * The actual work is done by do_pending_operator().
 */
    static void
nv_operator(cap)
    cmdarg_T    *cap;
{
    int        op_type;

    op_type = get_op_type(cap->cmdchar, cap->nchar);

    if (op_type == cap->oap->op_type)        /* double operator works on lines */
    nv_lineop(cap);
也就是说,对于叠字类的操作都是由更上层作为一个基础功能提供给系统中的各种操作来使用,而不用各个命令来单独处理。事实上,dd这个命令也无法由‘d’操作符来处理,因为d并不是一个位移指令,所以无法按照通常的语义解释。这也说明另一个问题,所有的操作符都可以通过叠字来完成对所在行的操作,这是一个演绎过程。
四、“操作+位移”模式解析
通常的指令都是操作加上一个移动(motion)来完成,而移动也可以单独完成,例如‘w’转到一个单词的结束,显然地,操作和移动是可以相互独立或者说是任意组合的,并且操作也遵守移动的语义的一致性,那么这个操作和移动是如何耦合起来的呢?我们看一下对于操作符的执行动作,同样是刚才看到的操作符例子:
nv_operator(cap)
   else if (!checkclearop(cap->oap))
    {
    cap->oap->start = curwin->w_cursor;这里的操作比较重要的操作,它主要的功能是记录了执行发出时系统中光标的当前位置,这是一个记忆性的定界操作,之后的操作区间(内的操作内容)是以这个为起点进行的
    cap->oap->op_type = op_type;
#ifdef FEAT_EVAL
    set_op_var(op_type);
#endif
    }
当命令操作符确定之后,用户会指定动作指令,例如10w,$等指令,此时这些位移指令就会按照最为基础和原始的语义来进行移动,而移动的核心工作就是来对全局的curwin->w_cursor变量进行更新,以我们最为常见的单词移动为例
nv_wordcmd--->>>fwd_word--->>>inc_cursor
/*
 * Increment the cursor position.  See inc() for return values.
 */
    int
inc_cursor()
{
    return inc(&curwin->w_cursor); 在inc函数中将会完成对于光标的当前逻辑行和逻辑列的更新,当移动指令完成的时候,当前光标就确定了一个结束区间,然后操作指令就可以通过之前保存的那个cap->oap->start来确定指令操作执行的操作对象
}
五、’i‘和’a‘模式的特殊处理
normal_cmd(oap, toplevel)函数中
    /*
     * Get an additional character if we need one.
     */
    if ((nv_cmds[idx].cmd_flags & NV_NCH)
        && (((nv_cmds[idx].cmd_flags & NV_NCH_NOP) == NV_NCH_NOP
            && oap->op_type == OP_NOP)
        || (nv_cmds[idx].cmd_flags & NV_NCH_ALW) == NV_NCH_ALW
        || (ca.cmdchar == 'q'
            && oap->op_type == OP_NOP
            && !Recording
            && !Exec_reg)
        || ((ca.cmdchar == 'a' || ca.cmdchar == 'i')
            && (oap->op_type != OP_NOP
#ifdef FEAT_VISUAL
            || VIsual_active
#endif
               ))))
真正的处理,这里有很多有用的操作,例如单词,一个双引号,小括号内的内容等各种取内容,感觉比较方便,大家之后可以经常使用一下。
nv_edit--->>nv_object
switch (cap->nchar)
    {
    case 'w': /* "aw" = a word */
        flag = current_word(cap->oap, cap->count1, include, FALSE);
        break;
    case 'W': /* "aW" = a WORD */
        flag = current_word(cap->oap, cap->count1, include, TRUE);
        break;
    case 'b': /* "ab" = a braces block */
    case '(':
    case ')':
        flag = current_block(cap->oap, cap->count1, include, '(', ')');
        break;
    case 'B': /* "aB" = a Brackets block */
    case '{':
    case '}':
        flag = current_block(cap->oap, cap->count1, include, '{', '}');
        break;
    case '[': /* "a[" = a [] block */
    case ']':
        flag = current_block(cap->oap, cap->count1, include, '[', ']');
        break;
    case '<': /* "a<" = a <> block */
    case '>':
        flag = current_block(cap->oap, cap->count1, include, '<', '>');
        break;
    case 't': /* "at" = a tag block (xml and html) */
        flag = current_tagblock(cap->oap, cap->count1, include);
        break;
    case 'p': /* "ap" = a paragraph */
        flag = current_par(cap->oap, cap->count1, include, 'p');
        break;
    case 's': /* "as" = a sentence */
        flag = current_sent(cap->oap, cap->count1, include);
        break;
    case '"': /* "a"" = a double quoted string */
    case '\'': /* "a'" = a single quoted string */
    case '`': /* "a`" = a backtick quoted string */
        flag = current_quote(cap->oap, cap->count1, include,
                                  cap->nchar);
        break;
#if 0    /* TODO */
    case 'S': /* "aS" = a section */
    case 'f': /* "af" = a filename */
    case 'u': /* "au" = a URL */
#endif
    default:
        flag = FAIL;
        break;
    }
六、多字母指令处理
/* Values for cmd_flags. */
#define NV_NCH        0x01      /* may need to get a second char */
#define NV_NCH_NOP  (0x02|NV_NCH) /* get second char when no operator pending */
#define NV_NCH_ALW  (0x04|NV_NCH) /* always get a second char */
    {'g',    nv_g_cmd,    NV_NCH_ALW,        FALSE},

在normal_cmd(oap, toplevel)函数中
    /*
     * Get an additional character if we need one.
     */
    if ((nv_cmds[idx].cmd_flags & NV_NCH)
        && (((nv_cmds[idx].cmd_flags & NV_NCH_NOP) == NV_NCH_NOP
            && oap->op_type == OP_NOP)
        || (nv_cmds[idx].cmd_flags & NV_NCH_ALW) == NV_NCH_ALW  对于’g‘命令将会满足这个条件,所以会始终读入一个额外参数
        || (ca.cmdchar == 'q'
            && oap->op_type == OP_NOP
            && !Recording
            && !Exec_reg)
        || ((ca.cmdchar == 'a' || ca.cmdchar == 'i')
            && (oap->op_type != OP_NOP
#ifdef FEAT_VISUAL
            || VIsual_active
#endif
               ))))
然后在对应的nv_g_cmd函数来说
    static void
nv_g_cmd(cap)
    cmdarg_T    *cap;
{
    switch (cap->nchar)
    {
#ifdef MEM_PROFILE
    /*
     * "g^A": dump log of used memory.
     */
    case Ctrl_A:
    vim_mem_profile_dump();
    break;
#endif
……
   /*
     * ge and gE: go back to end of word
     */
    case 'e':
    case 'E':
    oap->motion_type = MCHAR;
    curwin->w_set_curswant = TRUE;
    oap->inclusive = TRUE;
    if (bckend_word(cap->count1, cap->nchar == 'E', FALSE) == FAIL)
        clearopbeep(oap);
    break;
……
  /*
     * "gg": Goto the first line in file.  With a count it goes to
     * that line number like for "G". -- webb

     */
    case 'g':
    cap->arg = FALSE;
    nv_goto(cap);
    break;
  评论这张
 
阅读(916)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017