使用python编写vim脚本 对齐赋值语句

- hikerpig
#Vim

JSer们,不管是前后端,文件头的dependency部分堆积了一群犬牙交错的require语句的时候,内心有没有过一个冲动把它们全都捋一遍全对齐了!各种foramtter给平日的眼净心静贡献了不少力量,不过我印象中ST,VIM,Webstorm好像都没有这么个插件,干脆自己写一个吧。

需求

  • 以等号对齐require语句

附加需求

  • 以等号或冒号对齐语句

平时VIM用的比较多,先下手这个。

VIM有自己强大的DSL插件语言vimscript, 不过各种东西的学习曲线真的是... ( %>_<% ),折腾了一下发现最关键的正则模块我没搞清楚。

想想这个需求很简单,也不需要跟编辑器做很多交互,所以还是用一个顺手的语言实现吧。

VIM具有lua, tcl, perl, ruby, python的编程接口,我就决定用python了,具体接口内容可以看文档:

:help if_pyth.txt

比较关键的几个对象是:

  • vim.current.buffer 当前缓冲区(也可以理解是存在内存里的当前编辑文件内容)
  • vim.current.buffer.mark 获取当前缓冲区的某个mark信息, 下面我使用的mark('<')和mark('>')是比较特殊的,上一次visual selection的起止位置
  • vim.current.window.cursor 当前窗口下输入光标所在位置
if exists("g:loaded_require_formatter")
  finish
endif
let g:loaded_require_formatter = 1

"Function: :format
"Desc: align the require statement
"
func! s:format()
python << EOF

import vim
import re

# prepare
buffer = vim.current.buffer
require_pattern = re.compile(r'(?P<left>\s*[\w\d_]+\s?)=\s*require(?P<right>[\w\d\"\'\s\(\)\-\/]+)')
assign_pattern = re.compile(r'(?P<left>\s*[\w\d_]+\s?)[=:]\s*(?P<right>[\w\d\"\'\s\(\)\-\/]+)')
g_pattern = require_pattern
g_matches = []
g_seperator = '='

vst = 0
vend = 0
start_mark = buffer.mark('<')
end_mark = buffer.mark('>')
if start_mark:
  vst = start_mark[0] - 1
if end_mark:
  vst = end_mark[0]
cursor = vim.current.window.cursor
cend = cursor[0]
lines = buffer[0:]
g_start_line = 0
if vst and vend:
  if vend == cend:
    lines = buffer[vst:vend]
    g_start_line = vst
    g_pattern = assign_pattern
    g_seperator = re.compile('[=:]')
    #print 'vstart is', vst
    #print 'vend is', vend
    #print lines

def get_formated_line(text, left_len, seperator='='):
    """
    :text: {str}
    :left_len: {int}
    :returns: {str}

    """
    if hasattr(seperator, 'match'):
      match = seperator.search(text)
      if match:
        epos = match.start()
      else:
        return text
    else:
      epos = text.find(seperator)
    left_str = text[0:epos]
    remained = text[epos:]
    short_of_len = left_len - len(left_str)
    if short_of_len > 0:
        to_append = []
        for i in range(0, short_of_len):
            to_append.append(' ')
        to_append = ''.join(to_append)
        text = left_str + to_append + remained

    return text

def start(lines):
    max_left_len = 0
    matched_linenos = []
    for i, line in enumerate(lines):
        matches = g_pattern.match(line)
        if matches:
            matched_linenos.append(i)
            g_matches.append(matches)
            gp_dict = matches.groupdict()
            left = gp_dict.get('left')
            if not left[-1] == ' ':
              left += ' '
            left_len = len(left)
            max_left_len = max(max_left_len, left_len)

    for i in matched_linenos:
        line = lines[i]
        fl = get_formated_line(line, max_left_len, seperator=g_seperator)
        #print "formed_line is ", fl

        # replace the line
        real_lineno = i + g_start_line
        del buffer[real_lineno]
        buffer.append(fl, real_lineno)

# start
try:
  start(lines)
except Exception as exp:
  print exp

EOF
endfunc

" change this map if it conflicts with others
map <C-e> :echo <SID>format()<CR>

" 处于visual模式的时候会报range not allowed的错,
" vmap的时候先退出v模式"
vmap <C-e> <Esc>:echo <SID>format()<CR>

这样在normal和visual模式下都可以轻松对齐了。

参考文章