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模式下都可以轻松对齐了。

参考文章