星期一, 十一月 30, 2020

VIM学习笔记 定时器(timer)

自8.0版本起,包含+timers特性的Vim提供了定时器功能。利用定时器,可以在指定延时之后触发指定操作,也可以按照固定的时间间隔来重复执行任务。

启动定时器

使用timer_start()函数,可以启动定时器并返回定时器ID:

timer_start({time}, {callback} [, {options}])

其中:

  • time,指定时间间隔,单位为毫秒(milliseconds);
  • callback,指定需要触发的回调函数;
  • options,是包含多个键值的字典选项,用于配置和控制定时器的行为

定时器选项

使用"repeat"选项,可以控制执行回调函数的次数。缺省为执行"1"次。如果希望无限循环执行,那么可以将此值设置为"-1"。

使用以下命令,设置在状态行中显示时间:

set laststatus=2
if has("win32")
    set statusline=%{strftime(\"%I:%M:%S\ \%p,\ %a\ %b\ %d,\ %Y\")}
else
    set statusline=%{strftime(\"%l:%M:%S\ \%p,\ %a\ %b\ %d,\ %Y\")}
endif

自定义以下函数,用于更新状态行:

function! UpdateStatusBar(timer)
    execute 'let &ro = &ro'
endfunction

使用以下命令启动定时器,回调函数将持续更新状态行的时间显示:

:let timer = timer_start(3000, 'UpdateStatusBar',{'repeat':-1})

定时器信息

使用不带参数的timer_info()函数,可以获取所有定时器的信息:

:echo timer_info()

timer_info_all

使用带有ID参数的timer_info()函数,可以获取指定定时器的信息:

:echo timer_info(timer)

函数返回包含详细信息的字典:

项目描述
id该定时器的 ID
repeat定时器还需要重复执行的次数,无限执行则返回 -1
remaining距定时器启动还剩余的毫秒数
time计时器时间间隔(毫秒数)
paused暂停状态时返回 1,否则返回 0
callback回调函数

暂停定时器

使用timer_pause()函数,并指定第二个参数为非0数值或非空字符串,可以暂停定时器:

:call timer_pause(timer,1)

使用timer_pause()函数,并指定第二个参数为数值0或空字符串,可以对定时器取消暂停:

:call timer_pause(timer,0)

取消定时器

假设启动以下定时器,将在指定延时之后强制退出Vim:

:let timer_id = timer_start(10000, {id -> execute('quit!')})

使用timer_stop()函数,可以取消指定定时器:

:call timer_stop(timer_id)

使用timer_stopall()函数,可以取消所有定时器:

:call timer_stopall()

定时器实例

假设当前脚本文件中包含以下代码:

let s:timeouts = [5000, 10000, 30000, 1000, 1000, 3200, 500, 700]

function! s:noop(timer_id)
    let s:timeouts = insert(s:timeouts, remove(s:timeouts, len(s:timeouts) - 1))
    normal dd
   call timer_start(s:timeouts[0], function('<SID>noop'))
endfunction

call s:noop(0)

使用以下命令执行当前脚本,将从当前行开始,按照设定的时间间隔逐行删除文本:

:so %

使用以下命令,可以查看关于定时器的帮助信息:

:help timer

函数小结
timer_start()新建定时器
timer_info()定时器信息
timer_pause()暂停或继续定时器
timer_stop()停止定时器
timer_stopall()停止所有定时器

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期一, 十一月 23, 2020

VIM学习笔记 作业(job)

在传统的单线程模式下,运行外部命令时,将中断用户当前的编辑操作,并等待命令完成才可以返回vim。

自8.0版本起,包含+channel和+job特性的Vim将可以支持异步操作。Vim使用作业(job)来启动进程,并利用通道(channel)和其他进程通信。

利用异步支持,可以在后台进行复杂耗时的操作(比如使用外部grep工具查找文本),不必等待外部命令结束即可返回Vim,而不中断前台的正常编辑。在外部命令结束运行时,可以通过回调函数来处理输出结果。

job_start_version

例如使用:!ls外部命令,将在屏幕底部列示目录内容,并等待用户按回车键以返回常规模式,此时用户无法进行其它操作:

shell_cmd_ls

启动作业

使用job_start()函数,可以异步执行命令,其格式为:

job_start({command} [, {options}])

其中:command,用于指定需要运行的外部系统命令;options,是包含多个键值的字典选项,用于配置和控制作业的行为。

使用以下命令,可以启动作业以执行外部命令,并立刻返回常规模式,不影响用户的后续操作:

:call job_start('ls')

作业选项

作业采用管道(pipe)将外部命令与vim联接起来,并与标准输入(stdin)、标准输出(stdout)和标准错误(stderr)输出进行交互。

在使用job_start()函数启动作业时,可以注册一个或多个回调函数来处理特定的信息。如果希望静默执行命令,而不关心输出信息,那么也可以不注册任何回调函数。

使用"callback"选项,可以同时捕获标准输出与标准错误输出;也可以分别使用"out_cb"或"err_cb"选项,来单独捕获标准输出或标准错误输出。

首先定义用于捕获输出的回调函数:

func! MyHandler (channel, msg)
    echomsg a:msg
endfunc

通过"callback"选项,指定回调函数来处理stdout和stderr的内容:

let job = job_start ('ls', {"callback": "MyHandler"})

通过"out_cb"选项,指定回调函数来处理stdout的内容:

let job = job_start ('ls', {"out_cb": "MyHandler"})

通过"err_cb"选项,指定回调函数来处理stderr的内容:

let job = job_start ('ls', {"err_cb": "ErrHandler"})

在以上回调函数中使用了:echomsg命令,因此随后可以使用:message命令来查看信息历史,以确认命令执行结果:

job_start_ls_messages

使用"in_io"、"out_io"或"err_io"选项,可以将作业管道重定向到文件或缓冲区。

使用以下命令,可以将标准输出重定向至指定缓冲区:

:let job = job_start('ls', {'out_io': 'buffer', 'out_name': 'mybuffer'})

使用以下命令,则可以打开缓冲区查看输出信息:

:sbuf mybuffer

请注意,首行包含了“Reading from channel output...”的说明文字:

job_start_ls_out_io

使用以下任一命令,均可以将标准输出重定向至指定文件:

:let job = job_start('ls -al', {'out_io': 'file', 'out_name': '/tmp/file.txt'})

:call job_start(["/bin/sh", "-c", "ls -al > /tmp/file.txt"])

使用以下命令,可以查看作业选项的帮助信息:

:help job-options

作业状态

使用job_status()函数,可以返回指定作业的状态:

:echo job_status(job)

状态描述
run作业运行中
fail作业无法启动
dead作业启动后结束或被终止

使用job_info()函数,则可返回指定作业的详细信息:

:echo job_info(job)

job_info

函数将返回包含详细信息的字典:

描述
statusjob_status()返回值
cmd启动作业的命令行参数列表
stoponexitVim结束时给作业发信号(缺省是 "term")
channeljob_getchannel()返回值
process进程ID
tty_in终端输入名,如果没有则为空
tty_out终端输出名,如果没有则为空
exitval"status" 为 "dead" 时才有效
exit_cb退出时调用的函数
termsig终止程序的信号(仅用于Unix)
仅当"status"为"dead"时才有意义
tty_type使用的虚拟控制台类型(仅用于MS-Windows)
可选值是"winpty"或"conpty"

停止作业

使用job_stop()函数,可以停止指定的作业:

:call job_stop(job)

作业实例

实例1:启动Apache HTTP Server服务

:let job = job_start('apachectl start')

使用以下命令载入首页内容,以确认服务启动成功:

:silent r ! curl localhost

实例2:在位置列表中显示命令输出

function! s:on_find(chan, msg)
      lgetexpr split(a:msg, '')
endfunction

call job_start('find . -print0', {
      \ 'out_mode': 'raw',
      \ 'callback': function('<SID>on_find')
      \ })

使用以下命令,执行包含以上命令的脚本:

:so %

使用以下命令,将在位置列表(location list)中显示外部find命令输出的文件列表:

:lopen

job_start_out_mode_raw

使用以下命令,可以查看关于作业的帮助信息:

:help job

函数小结
job_start()启动作业
job_status()显示作业状态
job_info()显示作业的详细信息
job_stop()停止作业

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期五, 十一月 13, 2020

VIM学习笔记 静默执行命令(silent)

通常在使用!运行外部Shell命令时,将显示提示信息“Press ENTER or type command to continue”,需要用户点击回车键才可以返回常规模式。

shell_cmd_msg

使用silent静默执行命令

如果不希望显示提示信息,那么可以使用:silent命令:

:silent !echo 'Hello World'

如果需要清除命令本身及其输出信息,那么可以使用Ctrl-L快捷键或:redraw!命令来重画屏幕。

您可以自定义命令,来合并以上两步操作:

:command! -nargs=1 Silent execute ':silent !'.<q-args> | execute ':redraw!'

使用以下自定义命令,将首先执行外部命令,然后重画屏幕:

:Silent echo 'Hello World'

通过结合:execute命令,可以生成并执行较复杂的命令:

:silent exec "!command"

定义以下快捷键,在Linux下静默执行命令。比如使用eSpeak将文字转换为语音:

:nnoremap <leader>es :silent exec '!espeak "hello world" &'<CR>

定义以下快捷键,在Windows下使用默认程序打开当前文件。比如使用默认浏览器,打开当前编辑的HTML文档:

:nmap <Leader>x :silent ! start "1" "%:p"<CR>

后台执行命令

使用以下命令,可以利用Shell后台执行命令和重定向的能力:

:silent exec "!(ping www.vim.org >ping.out >2&1) &"

  • >ping.out,即1>ping.out,表示将命令的标准输出(stdout)重定向到名为“ping.out”的文件;因为默认值为1,所以可以省略;
  • >2&1,表示将“2”代表的标准错误(stderr)也重定向至“1”代表的标准输出(stdout);即标准输出和标准错误都输出至名为“ping.out”的文件;
  • &,表示在后台执行命令。

如果不希望外部命令输出任何信息,那么可以将标准输出指向空设备文件“/dev/null”:

:silent exec "!(ping www.vim.org >/dev/null >2&1) &"

也可以静默执行外部命令,并在新建标签页(Tab)内显示命令输出:

:silent exec "!(echo 'Hello World') > test.txt" | :tabedit test.txt

如果希望在分割窗口内显示命令输出,那么可以使用:split命令:

:silent exec "!(echo 'Hello World') > test.txt" | :sp test.txt

后台打开应用窗口

使用以下命令,将打开与当前文件同名的PDF文档。由于Zathura窗口在前台显示,所以无法在Vim窗口中继续进行编辑;关闭zathura窗口之后,也需要在Vim中点击回车键以返回常规模式:

:!zathura %:r.pdf

zathura_foreground

使用以下命令,将打开与当前文件同名的PDF文档。由于zathura窗口在前台显示,所以无法在Vim窗口中继续进行编辑;关闭zathura窗口之后,不需要在Vim中点击回车键即可返回常规模式:

:silent !zathura %:r.pdf

使用以下命令,将在后台打开与当前文件同名的PDF文档。由于zathura窗口在后台显示,所以无需关闭zathura窗口,也无需点击回车键,即可以在Vim窗口中继续进行编辑:

:silent exec '!zathura '.expand("%:r").'.pdf &'

实例:静默压缩文件

使用以下命令,可以使用Zip压缩当前文件:

:!zip test.zip %:p

屏幕将显示以下信息,并等待用户按回车键以返回常规模式:

shell_cmd_zip_msg

使用以下命令,则屏幕不会显示任何信息,并且自动返回常规模式:

:silent !zip test.zip %:p

使用以下命令,可以批量压缩所有打开的文件:

:silent bufdo !zip test.zip %:p

实例:静默载入视图

如果希望记忆光标位置和手动折叠(Fold),以便在重新打开文件时恢复到之前的编辑状态。那么可以在vimrc配置文件中,增加以下自动命令

set viewdir=$HOME/vimfiles/views/
autocmd BufWinLeave * mkview
autocmd BufWinEnter * silent loadview

使用system()函数静默执行命令

通过调用system()函数,也可静默执行命令:

:call system('espeak "hello world" &')

函数system()!命令都可以调用外部命令,但system()函数不会切到shell终端,而是仍停留在vim界面。所调用外部命令的输出将会被system()函数捕获,可以将其保存在VimL变量中以供后续使用。

使用以下命令,可以查看更多帮助信息:

:help :silent

:help system()

关于本文中使用的第三方工具,请参阅以下网址:

  • eSpeak, text to speech
  • Zathura, document viewer
  • Zip, compression and file packaging/archive utility

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期二, 十一月 03, 2020

VIM学习笔记 全局命令-实例(Global Command-Examples)

执行单条命令

将所有未标志为“DONE”的行,都在行尾标注“TODO”:

:g!/DONE/s/$/ TODO/

:v/DONE/s/$/ TODO/

从第5行到第10行,在每一行下插入空行:

:5,10g/^/pu _

通过调用:sort命令,可以对指定范围内的文本进行排序。例如以下命令,将对“{}”包围的CSS属性按字母排序:

:g/{/ .+1,/}/-1 sort

g_cmd_range_sort

同理,使用以下命令,可以针对“{}”包围的文本增加缩进

:g/{/ .+1,/}/-1 >

执行多条命令

使用以下命令,可以为“Chapter”开头的标题行增加分隔线:

:g/^Chapter/t.|s/./-/g

Chapter 1
---------
Chapter 2
---------

从以上命令可以看到,通过使用“|”可以组合执行多条命令。首先使用:t命令,复制行内容;然后执行:s命令,将文本替换为横线。

使用以下命令,可以进行多次替换。对于包含“apples”或“cherries”的行,进行两次替换:首先将“apples”替换为“bananas”,然后将“cherries”替换为“oranges”:

:g /apples\|cherries/ substitute /apples/bananas/g | substitute /cherries/oranges/g

g_cmd_sub_multi

调用函数

首先在vimrc配置文件中,新增以下自定义函数:

" Delete duplicate lines
function! DelDupLine()
  if getline(".") == getline(line(".") - 1)
    norm dd
  endif
endfunction

然后使用以下命令,调用函数来删除文件中重复的行:

:g/^/ call Del()

读取和写入文件

将所有不是以2个Tab制表符开头的行,增加到文件“top2levels.otl”的末尾:

:g!/^\t\t/.w>>top2levels.otl

首先移动到文件开头,然后执行以下命令,可以搜集相对于指定字符偏移位置的行:

:g /^Chapter/ .+2w >> begin

包含“Chapter”字符的章节号、名称和描述,分处在连续的行中。模式^Chapter将查找以“Chapter”字符开头的行;.+2将定位到匹配处之后的第2行;然后使用:w命令将其追加到名为“begin”的文件中。

g_cmd_range_lines

拆分当前文件,将每一行内容,分别保存为一个文本文件(1.txt,2.txt,3,txt依次类推):

:g/^/exe ".w ".line(".").".txt"

将字符串“MARK”替换为文件“tmp.txt”的内容:

:g/^MARK$/r tmp.txt | -d

读取和写入寄存器

假设需要从以下代码中,提取所有以“TODO”开头的注释文字:

if ( m && m[2] )
    // TODO: something1
    return [ m[1].length + m[2].length ];
else {
    // TODO: something2
    return [ 1, "`" ];
}

首先,使用qaq命令来清除寄存器a的内容,做为存储提取内容的中介位置;

然后,使用以下命令将包含“TODO”的行添加到寄存器a;请注意,使用大写的寄存器编号“A”以表示向寄存器添加内容(而小写字母则表示覆盖内容);

:g/TODO/yank A

如果希望将文本同时放入指定寄存器和系统剪切板,那么可以使用以下命令:

:g/TODO/yank A | :let @+=@a

请注意,在Mac和Windows下,寄存器*和+都用于访问系统剪切板;在Linux下,寄存器+用于访问系统剪切板。

使用以下命令,可以看到内容已经被放入寄存器和剪切板:

:registers a+*

g_cmd_register_append

请注意,vim使用“^J”符号表示换行。

使用"ap命令,即可以将寄存器a中存储的TODO信息粘贴出来:

    // TODO: something1
    // TODO: something2

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>

星期一, 十一月 02, 2020

VIM学习笔记 全局命令(Global Command)

:global全局命令,通常简写为:g,可以针对匹配模式的行执行编辑命令;通过与正则表达式、寄存器和标记等功能相配合,可以高效地实现复杂的操作。

使用:g命令,可以针对所有匹配模式的行执行操作。其命令格式为:

:[range]g/{pattern}/[command]

即针对在[range]范围内,所有匹配{pattern}模式的行,执行[command]命令。

命令:g!及其同义词:v,则可以针对所有不匹配模式的行执行操作。其命令格式为:

:[range]g!/{pattern}/[command]

即针对在[range]范围内,所有不匹配{pattern}条件的行,执行[command]命令。

如果没有指定[range],则针对文件中的所有行执行命令。也可以使用行地址,把全局搜索限定在指定的行或行范围内。

如果没有指定[command],则执行:print命令来显示行内容。

使用以下命令,可以查看全局命令的帮助信息:

:h :g

全局查找

查找并显示文件中所有包含模式pattern的行,并移动到最后一个匹配处:

:g/pattern

查找并显示文件中所有包含模式pattern的行:

:g/pattern/p

查找并显示文件中所有精确匹配单词pattern的行:

:g/\<pattern\>/p

查找并显示第20到40行之间所有包含模式pattern的行:

:20,40g/pattern/p

查找并显示文件中所有不包行模式pattern的行,并显示这些行号:

:g!/pattern/nu

全局删除

删除包含模式patternn的行:

:g/pattern/d

删除不包含模式pattern的行:

:g!/pattern/d

删除所有空行:

:g/^$/d

删除所有空行以及仅包含空格和Tab制表符的行:

:g/^[ tab]*$/d

删除指定范围内的文本,例如以下文本中的“DESCRIPTION”部分:

:g/DESCRIPTION/,/PARAMETERS/-1d

g_cmd_range_del

全局替换

利用全局命令,可以仅针对符合查询条件的行,进行替换操作。例如使用以下命令,将包含“microsoft antitrust”的行中的“judgment”替换为“ripoff”:

:g/microsoft antitrust/s/judgment/ripoff/

可以在命令中指定查找的范围。比如以下命令,将在包含“microsoft antitrust”的前两行及后两行中进行替换:

:g/microsoft antitrust/-2,/microsoft antitrust/+2s/judgment/ripoff/c

g_cmd_range

以上命令末尾的c参数,用于提示用户对每一个替换操作进行确认。

假设希望在以“end”结尾的行中,将第1部分文字替换为“The greatest of times;”:

the best of times; the worst of times: end    

由于会尽可能多(as much text as possible)的匹配,以下命令将替换至第2个“of”处:

:g/end$/s/.*of/The greatest of/

The greatest of times: end                    

为了只替换第1部分,则需要更精确的匹配条件:

:g/end$/s/.*of times;/The greatest of times;/

The greatest of times; the worst of times: end

全局移动

将所有的行按相反的顺序排列。其中,查找模式.*将匹配所有行,m0命令将每一行移动到0行之后:

:g/.*/m0

将指定标记(Mark)之间的行按相反的顺序排列:

:'a,'bg/^/m'b

g_cmd_mark_move

将以下文本中的“DESCRIPTION”部分,上移到“SYNTAX”之前:

:g /SYNTAX/.,/DESCRIPTION/-1 move /PARAMETERS/-1

首先匹配从包含“SYNTAX”的行到包含“DESCRIPTION”的上一行;然后将这些行移动到包含“PARAMETERS”的上一行。

g_cmd_range_move

以下两条命令均可以将所有不是以数字开头的行,移动到文件末尾:

:g!/^[[:digit:]]/m$

:g/^[^[:digit:]]/m$

全局复制

使用以下命令,可以重复每一行。其中:t:copy为复制命令:

:g/^/t.

将包含模式pattern的行,复制到文件末尾:

:g/pattern/t$

重复每一行,并以“print ''”包围:

:g/./yank|put|-1s/'/"/g|s/.*/Print '&'/

Chapter 1
Print 'Chapter 1'

Ver: 2.0 | YYQ<上一篇 | 目录 下一篇>