星期六, 七月 26, 2025

VIM学习笔记 网页验证(W3C Validation)

符合标准的网页开发,可谓是不作恶的底线了。通过生产具有可读性的高质量代码,促成在不同浏览器中达成一致的显示效果,并且关照不同用户群体的可访问性(accessibility)。

可以使用W3C Markup Validation Service,在线验证网页是否符合标准;也可以使用Html Validator插件,在浏览器中验证网页。而更高效的方式,则是使用Nu Html Checker (v.Nu)开源工具,它可以在命令行中离线批量验证多个文件。

vnu_github

安装运行

可以直接从GitHub下载各平台下的可运行程序。

如果您已经安装了Java运行环境,那么可以下载 vnu.jar文件直接运行。

如果您没有安装过Java运行环境,那么可以下载相应操作系统的预编译包,比如vnu.windows.zip,其中内置了的Java系统。

将vnu所在目录,增加到path环境变量中:

vnu_windows_path

在命令行中,执行以下命令,可以显示帮助信息即说明安装成功。

$ vnu --help

vnu_cmd_help

在Mac操作系统下,可以通过Homebrew进行安装:

$ brew install vnu

验证网页

使用以下命令,可以验证在线网页:

$ vnu https://yyq123.github.io/learn-vim/learn-vi-00-00-TOC.html

使用以下命令,可以验证指定文件:

$ vnu filename.html

使用以下命令,可以验证多个文件文件:

$ vnu filename1.html filename2.html

使用以下命令,可以验证当前目录下所有网页文件:

$ vnu --asciiquotes --skip-non-html .

--asciiquotes选项,指定在输出信息中使用ASCII编码的引号(替代默认的Unicode编码的引号);

--skip-non-html选项,仅验证所有扩展名为html、htm、xhtml或xht的网页文件。

默认情况下,命令按照GNU error format格式输出。也可以使用--format选项,指定、xml、json或text格式。

vnu_cmd_out

使用--errors-only选项,将仅显示错误信息,而忽略info和warning信息:

$ vnu --errors-only filename.html

在Vim中调用vnu

在Vim中,可以直接调用vnu来验证当前文件,将在弹出的操作系统命令行中显示输出信息,需要点击回车键来关闭命令行窗口并返回到Vim:

:!vnu --asciiquotes %

vnu_vim_shell

使用vnu进行编译

请将以下代码,保存至~/.vim/after/compiler/w3c-html-validator.vim

let current_compiler = "w3c-html-validator"
CompilerSet makeprg=vnu\ $*\ --asciiquotes\ '%'
CompilerSet errorformat=\"file:%f\":%[0-9]%#\.%c-%l\.%[0-9]%#:\ %m

vimrc配置文件中,指定新建的编译器:

autocmd FileType html,xhtml compiler w3c-html-validator

再次打开HTML文件,使用以下命令即可使用vnu进行验证,并在QuickFix中显示输出信息:

:make

如果已经打开了多个文件,那么使用以下命令则可以对参数(Arguments)列表中的所有文件中进行验证:

:make ##

vnu_make_quickfix

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

星期一, 七月 21, 2025

VIM学习笔记 快捷键速查表(vim-which-key)

 Vim提供了一种不依赖鼠标的快速高效的编辑体验,通过大量的键盘快捷键,可以快速地进行文本移动、编辑、修改等操作。然而如何记忆和使用种类繁多的快捷键,也形成了另一种挑战。

打开vimrc配置文件,然后使用以下命令,可以列示所有F功能键的当前定义:

:for i in range(1, 12) | execute("map <F".i.">") | endfor

map_list_FunKey

使用以下命令,可以将键盘映射(和修改过的选项),写入到当前目录下的_exrc文件:

:mk

map_list_mk_exrc

更高效的方式,则是使用vim-which-key插件,可以在弹出窗口(popup)中列示当前所有快捷键的定义,大大减轻了记忆各种快捷键的负担。

安装配置

推荐使用minpac等插件管理器,来安装GitHub上的vim-which-key插件。(请参阅官方安装文档

假设使用逗号键,作为前缀键(leader)

let mapleader=","

vimrc配置文件中设置timeoutlen选项,指定在按下leader键后,如果在约定时间后没有进一步的按键操作,则会在弹出窗口中显示快捷键绑定信息:

set timeoutlen=500
nnoremap <silent> <leader> :<c-u>WhichKey '<Space>'<CR>
nnoremap <silent> <localleader> :<c-u>WhichKey  ','<CR>

定义快捷键字典

首先,需要创建一个空字典:

let g:which_key_map = {}

vim-which-key插件能够自动识别已经存在的leader快捷键,你可以为其添加文字描述;使用'name'关键字,可以定义二级字典组,以便于为捷键分类显示。

let g:which_key_map.s = { 'name' : '+source' }
let g:which_key_map.s.v = 'Source-vimrc'
let g:which_key_map.t = { 'name' : '+tab' }
let g:which_key_map.t.v = 'Tab-vimrc'

对于尚不存在的快捷键,也可以直接定义命令和描述:

let g:which_key_map.f = {
      \ 'name' : '+file' ,
      \ 'n' : ['fn' , 'File Name to Clipboard'] ,
      \ 'p' : ['fp' , 'Path Name to Clipboard'] ,
      \ }

更多vim-which-key插件配置代码,请参阅实例

显示快捷键列表

在常规模式下点击前缀键(leader),将在弹出窗口(popup)中显示一级菜单,其中“+”开头的菜单项表示包含子菜单:

Popup

点击“+”后的首字母,比如",b"将显示缓冲区(buffer)子菜单下的快捷键信息:

Group

点击退格键<BACKSPACE>,将返回上一级菜单;点击<ESC>键,将关闭弹出窗口并返回常规模式。

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

星期一, 六月 16, 2025

VIM学习笔记 工具箱-PDF查看器zathura

zathura是一款高度可定制的简约文档查看器,适用于大多数Linux发行版。zathura是由PWMT社区开发的开源软件,它有以下两大特点:

  • 以键盘为中心,使用vim习惯的键位绑定,类似vim的命令行模式,沉浸式的vim操作体验;
  • 以文档为中心,极简的用户界面将最大化的屏幕空间留给文档本身,提供更专注的阅读体验。

zathura提供以下主要特性:

  • 支持PDF、Djvu、PostScript、Comic Book等多种文件格式;
  • 支持书签(bookmark);
  • 类似vim的命令行/搜索行/状态栏;
  • 使用鼠标双击进行链接跳转;
  • 使用鼠标选择文本将自动拷贝至剪贴板;
  • 支持SyncTeX技术,允许输入和输出同步,便于编辑LaTex文档

安装

以Debian为例,使用以下命令安装zathura:

$ sudo apt install zathura zathura-pdf-poppler zathura-djvu zathura-ps

针对其它平台的安装,请参阅官方文档

使用

在操作系统命令行环境下,使用以下命令即可查看文件:

$ zathura filename.pdf

zathura main window

在默认阅读模式下,使用F11键切换至全屏幕模式;使用F5键切换至演示模式;使用Tab键切换至目录模式。

zathura mode index

配置

通过编辑~/.config/zathura/zathurarc配置文件,可以自定义系统选项:

# show status bar and vertical scrollbar
set guioptions sv
# allowing to change the colors
set recolor true
set recolor-lightcolor "#000000"
set recolor-darkcolor "#E0E0E0"
set recolor-keephue true
# zoom and scroll step size
set zoom-step 20
set scroll-step 80
# copy selection to system clipboardset
set selection-clipboard clipboard
# enable incremental search
set incremental-search true
# shortcut - zoom
map <C-i> zoom in
map <C-o> zoom out

更多配置选项,请参阅官方文档

快捷键

Document Navigation文档导航
J向下移动一行 Move the document display window one line down.
K向上移动一行 Move the document display window one line up.
L向右移动一列 Move the document display window one column to the right.
H向左移动一列 Move the document display window one column to the left.
Ctrl + D向下移动半页 Move the document display window half a page down.
Ctrl + U向上移动半页 Move the document display window half a page up.
Ctrl + Y向右移动半页 Move the document display window half a page to the right.
Ctrl + T向左移动半页 Move the document display window half a page to the left.
Ctrl + B向下移动一页 Move the document display window a full page down.
Ctrl + F向上移动一页 Move the document display window a full page up.
Y向右移动一页 Move the document display window a full page to the right.
T向左移动一页 Move the document display window a full page to the left.
GG移动到第一页 Go to the first page of the current document.
Shift + G移动到最后一页 Go to the last page of the current document.
5, then Shift + G移动到第5页 Go to the fifth page of the current document.
Shift + H移动到当前页首 Go to the top of the currently selected page.
Shift + L移动到当前页尾 Go to the bottom of the currently selected page.
/向前搜索 Open forward search prompt.
?向后搜索 Open reverse search prompt.
N移动到下一匹配处 Go to the next occurrence of the current search term.
Shift + N移动到上一匹配处 Go back to the previous occurrence of the current search term.
Interface Navigation界面导航
A适合窗口高度 Fit the height of the current page to the display window.
S适合窗口宽度 Fit the width of the current page to the display window.
R顺时针旋转90度 Rotate the current document by 90 degrees clockwise.
Ctrl + N显示/隐藏 状态栏 Toggle status bar.
+放大 Zoom in
-缩小 Zoom out
=恢复缩放比例 Revert the zoom level of the document at the currently selected page.
60 + =缩放至指定比例 Set the current zoom level of the document to 60%.
Ctrl + R反转显示颜色 Invert the colors.
Shift + R重画屏幕 Refresh and redraw.
F5演示模式 presentation mode.
F11全屏幕模式 fullscreen mode.
Q退出/结束当前进程 Terminate the current Zathura session.
Linking and Indexing链接和目录
Shift + F高亮显示链接 Show all link hints.
F跳转到指定链接 Load a link hint’s page to the document viewer.
C复制链接地址 Copy a link hint’s page to the system clipboard.
Tab进入目录模式 Display the document’s content index.
J下一目录项 Scroll one item down in the document’s content index.
K上一目录项 Scroll one item up in the document’s content index.
L展开当前目录 Display the current index item’s subitems.
H折叠当前目录 Hide the current index item’s subitems.
Shift + L展开所有目录 Display all the subitems in the document’s content index.
Shift + H折叠所有目录 Hide all the subitems in the document’s content index.
Enter跳转到目录指引的页 Load the page of the currently selected index entry.
Presentation Mode演示模式
Space下一页 Go to the next document slide.
Shift + Space上一页 Go back to the previous document slide.
F5退出演示模式 Go back to the default Zathura mode.
Fullscreen Mode全屏幕模式
Shift + J下一页 Move the document display window a full page down.
Shift + K上一页 Move the document display window a full page up.
ZI放大 Zoom in
ZO缩小 Zoom out
Z0恢复缩放 Reset the current zoom level.
60 + =缩放至指定比例 Set the zoom level of the document to 60%.
F11退出全屏幕模式 Go back to the default Zathura mode.
Command Mode命令模式
:进入命令行 Open Zathura’s command prompt.
:close关闭当前文档 Exit the current document without exiting Zathura.
:open {filename}打开指定文档 Open a new document on the current Zathura window.
:blist列示书签 List all the available bookmarks for the current session.
:bmark {name}新增书签 Add the current page to the session’s bookmark index.
:bjump {name}跳转至指定书签 Jump to a bookmark.
:bdelete {name}删除书签 Delete bookmark
:exec执行外部命令 Run an external shell command on the current document.
:info显示文档信息 Print the document’s internal properties.
:print打印文档 Send the current document to the machine’s print spool.

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

星期六, 六月 07, 2025

VIM学习笔记 编译文档(Compiling Latex)

LaTeX是一种用于生成高印刷质量的科技和数学类文档的排版系统,可以生成包含复杂表格和数学公式的复杂文档。

LaTeX文档是一个以.tex扩展名的文本文件,可以使用任意文本编辑器(比如Vim)编辑;在编辑完成后,可以编译为更容易阅读的文档格式(比如PDF)。

TeX Live

TeX Live是一种免费的跨平台TeX文档制作系统。它包括所有与TeX相关的主要程序、宏包和字体,支持全球多种语言。许多Unix/GNU/Linux操作系统通过自己的包管理器提供TeX Live,但强烈推荐严格按照官方文档进行手动安装。(另:参考

在安装之后,增加以下PATH路径:

export PATH=$PATH:/usr/local/texlive/2025/bin/i386-linux
export INFOPATH=$INFOPATH:/usr/local/texlive/2025/texmf-dist/doc/info
export MANPATH=$MANPATH:/usr/local/texlive/2025/texmf-dist/doc/man

同理,使用以下命令为sudo增加安全路径:

$ sudo visudo

Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2025/bin/i386-linux"

使用tlmgr更新Tex Live系统以及安装的Packages:

$ sudo tlmgr update --self

$ sudo tlmgr update --all

如果在使用过程中,仍然出现类似以下缺失包的报错信息:

! LaTeX Error: File `gb4e.sty' not found.

那么可以使用以下命令,手动安装缺失的包:

$ sudo tlmgr install gb4e

编辑Latex文档

Vim内置了Latex语法高亮度,可以很方便地编辑文档。

Latex-Syntax

编译Latex文档

在Vim命令行中执行pdfTeX命令,可以将Latex源文件编译为更容易阅读的PDF文件:

:!pdflatex sample.tex

默认情况下,将显示类似以下输出信息,并等待用户按回车键以返回到Vim:

Output written on sample.pdf (1 page, 66919 bytes).
Transcript written on sample.log.
Press ENTER or type command to continue

查看Latex输出文档

命令执行成功后,将在同一目录下,生成一个同名的PDF文件。同时还会生成一些辅助文件,如sample.log,其中包含有关编译过程的详细信息。

使用以下命令,启动PDF查看器验证生成的输出结果:

:zathura sample.pdf

Latex-View-PDF

自动化Latex编译和输出

在vimrc启动文件中自定义以下命令:

:command Latex execute "silent !pdflatex % > /dev/null && zathura %:r.pdf > /dev/null 2>&1 &" | redraw!

  • silent,将避免显示诸如 “Press ENTER to continue” 等命令执行过程中的提示信息;
  • pdlatex,将编译当前文件, % > 将标准输出重定向到/dev/null而不是显示在屏幕上;
  • zathura,用于查看生成的PDF文件,即以%:r指定的当前文件名,以.pdf指定的后缀扩展名;
  • redraw,重画屏幕,以保证正常显示;

在命令行中执行以下自定义命令,将自动化使用pdflatex来编译文档,并使用Zathura来显示生成的PDF文件:

:Latex

您也可以在vimrc启动文件中自定义以下快捷键:

:map <F2> :Latex <CR>

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

星期二, 四月 29, 2025

VIM学习笔记 使用Git Bash Shell环境

Git Bash是将Git版本控制系统和Bash Shell集成至单一界面的工具,允许用户在Windows系统的命令行环境中使用Git命令,为熟悉Linux系统的开发人员提供无缝体验;同时还能利用Bash强大的脚本和命令行功能。

集成Git Bash和ConEmu

如果您已经安装了GitConEmu,那么通过以下配置将可以在虚拟终端和Vim中调用Git Bash。

在ConEmu中,点击Win+Alt+P快捷键以打开设置对话框。

Git Bash

  1. 在侧边栏中,选择Startup下的Tasks
  2. 点击+按钮,新增项目;
  3. 输入"Bash::git bash"作为项目名称;
  4. Task Parameters编辑框中,指定启动目录和图标文件:
    /single /Dir "C:\" /icon "C:\Program Files\Git\mingw64\share\git\git-for-windows.ico"
  5. Commands编辑框中,指定Git Bash命令:
    "%ProgramFiles%\Git\bin\sh.exe" --login -i
  6. 点击Save Settings按钮,保存设置。

在ConEmu中,选择新建Git Bash终端窗口:

New Console

您可以在命令行环境下管理Git库:

Command

除了Git命令,Git Bash也提供一系列Linux经典命令工具,您可以使用$ help命令列出命令列表:

Help

配置Vim使用Git Bash

通过以下命令,可以查询到Vim在Windows下,默认使用命令提示符 "Command Prompt":

:set shell?

shell=C:\WINDOWS\system32\cmd.exe

vimrc配置文件中设置以下函数:

fun! ShellGitBash()
	let $CHERE_INVOKING=1
	set shell=C:\PROGRA~1\Git\bin\sh.exe
	set shellcmdflag=--login\ -c
	set shellxquote=\"
	set shellslash
	let $PATH .= ';C:\PROGRA~1\Git\bin'
endfun

通过以下命令,则可切换至Git Bash的Shell环境:

:call ShellGitBash()

在Vim中使用:terminal命令,即可打开Git Bash终端窗口:

terminal

你也可以在Vim命令行模式下,使用:!:shell命令,来执行bash命令

Ver 25.04.28  | YYQ<上一篇 | 目录 下一篇>

VIM学习笔记 插件管理器(minpac)

Vim8引入了内置的packages软件包功能。而minpac则是构建于packages软件包功能之上的轻量化插件管理器。

安装配置

首先在操作系统中,为minpac创建安装目录:

  • Linux:
    $ mkdir -p ~/.vim/pack/minpac/opt
  • Windows:
    $ mkdir -p ~/vimfiles/pack/minpac/opt

使用以下命令,将minpac作为手动载入的插件,安装在opt目录中:

$ cd ~/vimfiles/pack/minpac/opt
$ git clone https://github.com/k-takata/minpac.git

在vimrc配置文件中,添加以下命令:

packadd minpac
call minpac#init()

安装插件

minpac支持源码托管在GitHub的插件,你可以在github.com/vim-scripts/上找到vim官网里所有插件的镜像,也可以直接引用github.com的网址。

如果希望安装新插件,可以在vimrc配置文件中,添加以下minpac#add()命令:

call minpac#add('lilydjwg/colorizer')
call minpac#add('kshenoy/vim-signature')
call minpac#add('yyq123/HTML-Editor')

默认情况下,插件将被作为自动载入插件安装至start目录。

通过在函数中指定'type': 'opt'选项,将minpac自身作为手动载入插件进行注册。

call minpac#add('k-takata/minpac', {'type':'opt'})

然后使用以下命令,重新载入vimrc配置文件,并安装插件:

:source ~/.vimrc
:call minpac#update()

安装的插件将存放在pack子目录中,目录结构如下:

Directory

卸载插件

如果希望卸载插件,请先在vimrc文件中删除相关call minpac#add('')命令;然后重新载入vimrc文件,并执行以下命令:

:call minpac#clean()

更新插件

建议在vimrc文件中,增加以下命令映射以便于后续操作:

" minpac commands:
" 安装和更新插件
command! PackUpdate call minpac#update()
" 卸载插件
command! PackClean call minpac#clean()
" 查看插件状态
command! PackStatus call minpac#status()

然后就可以使用以下命令,批量更新所有已安装的插件:

:PackUpdate

帮助信息

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

:help minpac

Ver 25.4.22 | YYQ<上一篇 | 目录 下一篇>

星期日, 四月 20, 2025

VIM学习笔记 软件包(packages)

Vim8引入了内置的packages软件包功能。Vim软件包将创建一个或多个目录,用于安装和更新插件。和普通插件管理器相比,它主要有以下优点:

  • 软件包可作为归档下载,并可解压到单独的目录。这样文件就不会和其它插件的文件混杂。也方便后续更新和删除。
  • 软件包可以使用git等版本库进行更新。
  • 软件包可包含相互依赖的多个插件。
  • 软件包可包含启动时自动载入的插件和通过:packadd手动载入的插件。

任何位于~/.vim/pack之下的目录,都将被视为软件包。一个软件包,可以将插件存储在startopt子目录中:

  • ~/.vim/pack/*/start/{name}
    存储自动载入的插件
  • ~/.vim/pack/*/opt/{name}
    存储通过:packadd {name}命令手动载入的插件

其中,*可以为任意名称。

例如使用以下命令,创建以“plugins”命名的目录结构:

mkdir -p ~/.vim/pack/plugins/start

mkdir -p ~/.vim/pack/plugins/opt

安装配置

可以clone或者使用子模块(Submodules)功能,将git库同步到本地软件包目录。这也意味着,您可以在Git库里集中管理.vimrc配置文件和.vim目录结构,并同步到不同的计算机里。

Git子模块允许你将一个Git仓库作为另一个Git仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。

pack目录创建git库:

cd ~/.vim/pack
git init
git add *
git commit -m "Initial commit"
git submodule init
git remote add origin https://github.com/username/reponame.git
git push -u origin master

目录结构如下,之后安装的插件将存放在~/.vim/pack子目录中:

packages Directory

安装插件

软件包支持源码托管在GitHub的插件,你可以在github.com/vim-scripts/上找到vim官网里所有插件的镜像,也可以直接引用github.com的网址。

如果希望安装新插件,可以执行以下命令安装指定的插件:

$ git submodule add https://github.com/chrisbra/unicode.vim plugins/start/unicode

packages-git-submodule-add

然后使用以下命令,来提交变更:

$ git commit -m "Added submodules"
$ git push origin master

使用以下命令,可以安装用于演示Vim8.2新功能的killersheep小游戏。

$ git submodule add https://github.com/vim/killersheep plugins/opt/killersheep
$ git commit -m "Added killersheep"
$ git push origin master

使用以下命令,手动载入插件:

:packadd killersheep

使用以下命令,启动游戏:

:Kill

packages-killersheep

卸载插件

如果希望卸载插件,请先执行以下命令来移除git submodule:

git submodule deinit -f -- ~/.vim/pack/plugins/opt/killersheep

然后使用以下命令,删除插件文件目录:

rm -rf ~/.vim/pack/plugins/opt/killersheep

最后使用以下命令,从远处Git库中删除插件:

git rm -rf ~/.vim/pack/plugins/opt/

更新插件

使用以下命令,可以更新指定插件'unicode':

$ cd ~/.vim/pack/plugins/start/unicode
$ git submodule update --remote --merge

使用以下命令,可以批量更新所有已安装的插件:

$ cd ~/.vim/pack
$ git submodule foreach git pull origin master

请注意,如果有任何插件更新,请使用以下命令提交更新至Git repository:

$ cd ~/.vim/pack
$ git commit -am "Updated plugins."

帮助信息

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

:help packages

请注意,对于Vim内置的软件包功能:

  • 仅仅负责载入插件,而需要用户自行下载、安装和更新插件,用户友好度欠佳;
  • 调用git的clone和submodule,对于普通用户的门槛过高;
  • 需要自行生成帮助文档(Help tags),例如以下命令将生成指定目录下的帮助文档:
    :helptags ~/.vim/pack/plugins/start/reponame
    而以下命令则将生成所有帮助文档:
    :helptags ALL

如果您同时使用多台电脑,那么结合packages和git功能即可以实现插件配置的同步。当然,这过程中牵扯了诸多手工操作和git知识,平衡取舍还请各位自行斟酌。

$ git clone https://github.com/username/reponame.git ~/.vim
$ cd ~/.vim
$ git submodule update --init
$ git commit -am "Updated plugins."
$ git submodule foreach git pull origin master
$ git add .
$ git commit -m "update submodules"
命令小结
:packadd>安装插件

Ver 25.4.20 | YYQ<上一篇 | 目录 下一篇>

星期日, 十月 24, 2021

VIM学习笔记 选择模式 (Select Mode)

选择模式,可以理解为另一种可视化模式。在选择模式下,可以对选中的文本进行快速操作。比如先高亮选中文本,然后用Backspace来删除这段文本;或者先高亮选中文本,然后用输入的内容来替换这些文本。执行操作之后,Vim将自动进入插入模式(Insert Mode)

选择模式与可视化模式的主要区别在于:在可视化模式下,可以选中文本,然后执行命令操作。也就是说,要用命令来结束可视化模式。而在选择模式下,命令仅限于Backspace(用于删除操作)和可打印的字符(用于替换操作)。因为不需要输入命令,所以操作变得更为简单,但同时也存在很多限制。

进入选择模式

在常规模式下,可以使用以下命令进入选择模式。如果已经启用'showmode'选项,那么将在屏幕底部显示相应的选择模式名称:

命令描述模式名称
gh进入字符选择模式-- SELECT --
gH进入行选择模式-- SELECT LINE --
gCTRL-H进入块选择模式-- SELECT BLOCK --

可视化模式(Visual Mode)下,可以使用Ctrl + g快捷键,进入选择模式。Vim将根据当前所处的可视化模式的类型,自动进入与之一致的字符/行/块选择模式。再次点击ctrl-G键,将返回到可视化模式。

可以使用ctrl-O命令从选择模式切换到可视化模式。

选择模式下的操作

在选择模式下移动光标比在正常模式下要困难一些。因为输入任何可打印字符和回车符,Vim就会删掉已选中的文本并进插入状态。所以要选择文本就只能使用方向键、CTRL以及功能键。

如果希望使用Shift键来扩展选择,那么需要在'keymodel'选项中包含"startsel":

:set keymodel=startsel,stopsel

如果要在选择模式下用鼠标来选择文本,需要设置以下选项:

:set selectmode=mouse

退出选择模式

使用EscCtrl-[键,可以退出选择模式。

帮助信息

:help select-mode

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

星期五, 十月 22, 2021

VIM学习笔记 脚本-字典-实例(Script-Dictionary-Example)

可以将字典(Dictionary),理解为存储了关于键-值的成对的二元数组。以下将演示在脚本中利用字典数据结构的实例。

删除重复行

文本排序(sort)章节中,使用以下Vim内置的排序命令,可以去除文件中的重复行,同时原始行的顺序也将随之改变。对于重复的多行,将仅仅保留第一行,而其它的行将被删除。

:%sort u

创建自定义函数

以下将自定义Uniq()函数,利用字典数据结构来去除重复行,同时保留原始的行顺序。

Script-UDF-Uniq
Source code: Uniq.vim

首先,新建一个空字典"have_already_seen"用于遍历指定区域内的所有行;而不重复的行将被加入到字典"unique_lines"中。

因为字典结构不会存储键为空的项目,所以在循环语句中,将为每行内容新增前导字符'>',以确保行不为空。

函数将检查行是否已经作为键存在于字典"have_already_seen"中。如果已经存在,就忽略此行;如果不存在,则被加入字典"unique_lines"中。最后,字典"unique_lines"将仅仅包含所有唯一的行,并以原始的顺序排列;而重复的行,则会被删除。

调用自定义函数

将以上代码添加到vimrc配置文件之后,使用以下命令,可以针对整个文件执行函数:

:%call Uniq()

也可以创建快捷键,针对指定的行范围(如可视化模式下选中的行)执行函数:

vmap u :call Uniq()<CR>

Script-UDF-Uniq

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

星期四, 九月 09, 2021

VIM学习笔记 脚本-字典(Script-Dictionary)

可以将字典(Dictionary),理解为存储了关于键-值的成对的二元数组。如果你知道了键,就能快速地查找它对应的值。

创建字典

使用以花括号包含逗号分隔的键-值列表,来创建字典。每个项目包含以冒号分隔的键和值。

let flavor = {
\ '01': 'guava',
\ '02': 'mangosteen',
\ '03': 'mango',
\ '04': 'banana',
\ '05': 'coconut',
\ '06': 'passionfruit',
\ '07': 'watermelon',
\ '08': 'papya'
}

"创建空字典
let emptydict = {}

访问字典

可以使用以方括号包围的索引编号,来访问字典的值;而更直观的方式,是使用键的名称:

let Dx = flavor['09']

如果键只包含字母、数字和下划线,那么可以使用以点标记的形式进行访问。这种类似“表名.列名”的书写形式,非常类似于数据库的记录格式,显得更简洁也更易读:

let user = {}
let user.name = 'Bram'
let user.acct = 123007

如果键不存在,那么将显示以下报错信息:

E716: Key not present in Dictionary: 09

使用get()函数来访问字典的值,第一个参数指定字典,第二个参数指定需要查找的键。如果该键存在,将返回对应的值,如果指定的键不存在,则返回0。如果指定了第三个参数,那么在指定的键不存在时,则返回此参数值。

echo get(flavor, '08')          "papya
echo get(flavor, '09')          "0
echo get(flavor, '09', 'None')  "None

新增项目

使用以下命令为新的键赋值,即可为字典新增项目:

let diagnosis = {'1':'item1'}

使用extend()函数,可以为字典新增多个项目:

call extend(diagnosis, {'2':'item2', '3':'item3'})
" '1': 'item1', '2': 'item2', '3': 'item3'

使用extend()函数,也可以合并两个字典:

let new_diagnosis = {'0':'new'}
call extend(diagnosis, new_diagnosis)
" '0': 'new', '1': 'item1', '2': 'item2', '3': 'item3'

删除项目

使用remove()函数,可以删除字典中的项目:

let removed_value = remove(flavor, '09')

当需要删除多个项目时,使用filter()函数将更加高效。 与过滤列表项目类似,您可以使用v:val和v:key进行操作:

" 删除所有以0开头键值的项目
call filter(flavor, 'v:key[0] != "0"') 
" 删除所有不包含指定内容的项目
call filter(flavor, 'v:val =~ "mango"') 
" 删除所有键与值相同的项目
call filter(diagnosis, 'v:key != v:val')

字典和列表

使用以下命令,可以获取字典中的键列表,值列表,和键-值列表:

let dict = {'key1':'value1', 'key2':'value2', 'key3':'value3'}
let keylist = keys(dict)
" ['key1', 'key2', 'key3']
let valuelist =	values(dict)
" ['value1', 'value2', 'value3']
let pairlist = items(dict)
" [['key1':'value1'], ['key2':'value2'], ['key3':'value3']]

大多数关于列表的函数,同样也适用于字典:

let is_empty =	empty(dict)           " 测试字典是否为空
let entry_count = len(dict)           " 返回字典中包含项目的总数量	
let occurrences = count(dict, str)    " 返回字典中值为str的项目的数量
let greatest = max(dict)              " 返回最大值
let least = min(dict)                 " 返回最小值

使用map()函数操作数据,可以将其中的字符串格式化为首字母大写:

let names = {'u1': 'TOM', 'u2': 'jerry', 'u3': 'alEX'}
call map( names, 'toupper(v:val[0]) . tolower(v:val[1:])' )
" {'u1': 'Tom', 'u2': 'Jerry', 'u3': 'ALex'}

关于相关函数的使用说明,请参阅列表(List)章节。

帮助信息

使用以下命令,可以查看字典相关的帮助信息:

:help Dictionary
:help Dictionary-function

函数小结
len()字典的项目总数
empty()检查字典是否为空
remove()删除字典里一或多个项目
copy()建立字典的浅备份
deepcopy()建立字典的完整备份
filter()过滤指定的字典项目
map()改变每个字典项目
max()字典项目的最大值
min()字典项目的最小值
count()计算字典里某值的出现次数

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

星期三, 一月 20, 2021

VIM学习笔记 脚本-列表(Script-List)

列表(List),是一组由逗号分隔的项目的有序序列。它与其它编程语言中的数组(Array)概念非常相似。可以使用索引号来访问列表项目。也可以在序列的任何位置上增加或者删除项目。

请注意下文中引号后的文字为命令执行的结果,以演示各个函数的功能。

创建列表

可以将一组由逗号分隔的项目放置在方括号之内,以创建一个列表。列表中的项目索引从0开始。可以使用[n]形式,来引用特定的列表项目。

let data = [1,2,3,4,5,6,"seven"]
echo data[0]                            " 1
let data[1] = 42                        " [1,42,3,4,5,6,"seven"]
let data[2] += 99                       " [1,42,102,4,5,6,"seven"]
let data[6] .= ' samurai'               " [1,42,102,4,5,6,"seven samurai"]

使用[m:n]形式,可以引用指定范围的列表项目。

let data = [-1,0,1,2,3,4,5]
let positive = data[2:6]                 " [1,2,3,4,5]

如果忽略索引的起始位置,那么将默认从列表首个项目开始;如果忽略索引的结束位置,那么将默认至列表最后一个项目。

let middle = len(data)/2                 " middle = 3
let first_half = data[: middle-1]        " data[0 : middle-1]
echo first_half                          " [-1,0,1]
let second_half	= data[middle :]         " data[middle : len(data)-1]
echo first_half                          " [2,3,4,5]

使用range()函数,可以生成一个整数值的列表。 range(max)将生成从0到max-1的列表;range(min, max)将生成包含min和max在内的连续值列表;range(min, max, step)将从min到max,按照step指定的步长来生成列表。

let seq_of_ints = range(5)               " [0,1,2,3,4]
let seq_of_ints = range(1,5)             " [1,2,3,4,5]
let seq_of_ints = range(1,10,2)          " [1,3,5,7,9]

列表嵌套

除了数值和字符串之外,列表中也可以包含嵌套的列表。

let pow = [
\   [ 1, 0, 0, 0  ],
\   [ 1, 1, 1, 1  ],
\   [ 1, 3, 9, 27 ]
\]

echo pow[2][3]     " 27

" [2],指第3个嵌套列表
" [3],指嵌套列表中的第4个项目 

引用列表

将变量赋值为列表时,实际上是将变量指向列表;如果再次将该变量赋给其它变量,那么这两个变量都将指向同一个列表。也就是说,对于实际列表值的变更,将同时影响所有指向它的变量。

let old_suffixes = ['.c', '.h', '.py']
let new_suffixes = old_suffixes
let new_suffixes[2] = '.js'
echo old_suffixes      " ['.c', '.h', '.js']
echo new_suffixes      " ['.c', '.h', '.js']

复制列表

使用copy()函数复制列表,就可以使用不同的变量,来保存不同状态下的列表值。

let old_suffixes = ['.c', '.h', '.py']
let new_suffixes = copy(old_suffixes)
let new_suffixes[2] = '.js'
echo old_suffixes      " ['.c', '.h', '.py']
echo new_suffixes      " ['.c', '.h', '.js']

请注意,copy()函数只会复制最顶层的列表,即列表的浅备份。如果顶层列表包含嵌套列表,那么嵌套的子列表,将仅仅被作为指向实际子列表的指针被复制。也就是说,对于实际子列表的更改,将同时影响所有指向它的变量。

let pedantic_pow = copy(pow)
echo pow               " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

let pedantic_pow[0][0] = 'vague'
echo pow               " [['vague', 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [['vague', 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

" also changes pow[0][0] due to shared nested list

使用deepcopy()函数,则可以复制顶层列表及其包含的嵌套列表,即列表的完整备份。

let pedantic_pow = deepcopy(pow)
echo pow               " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

let pedantic_pow[0][0] = 'vague'
echo pow               " [[1, 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]
echo pedantic_pow      " [['vague', 0, 0, 0], [1, 1, 1, 1], [1, 3, 9, 27]]

" pow[0][0] now unaffected; no nested list is shared

拆分列表

使用split()函数,可以将字符串拆分为列表:

let words = split("one two three")          " 以空格为分隔符
echo words                                  " ['one', 'two', 'three']

let words = split("one:two three", ":")     " 以指定字符为分隔符
echo words                                  " ['one', 'two three']

合并列表

使用join()函数,可以将列表中的项目合并为字符串:

let list = ['one', 'two', 'three']
let str = join(list)                        " 使用空格连结列表项目
echo str                                    " one two three
let str = join(list, ';')                   " 使用指定字符连结列表项目
echo str                                    " one;two;three

列表长度和位置

使用以下函数,可以计算列表的长度,以及在列表中所处的位置:

let list = [1, 2, 3]
let list_length   = len(list)             " 列表的项目总数
echo list_length                          " 3
let greatest_elem = max(list)             " 列表项目的最大值
echo greatest_elem                        " 3
let least_elem    = min(list)             " 列表项目的最小值
echo least_elem                           " 1
let list_is_empty = empty(list)           " 将列表置为空
echo list_is_empty                        " 0

let week = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat','Sun']
let value_found   = index(week, 'Sun')    " 第一次出现指定值的索引位置
echo value_found                          " 0
let value_found   = index(week, 'sun')    " 如果没有找到匹配值(区分大小写)将返回-1
echo value_found                          " -1
let value_count   = count(week, 'Sun')    " 出现指定值的次数
echo value_count                          " 2

增加列表项目

call insert(list, newval)          " 在列表开头增加新项目
call insert(list, newval, idx)     " 在列表指定位置之前增加新项目
call    add(list, newval)          " 在列表末尾增加新项目

删除列表项目

call remove(list, idx)             " 删除指定位置的项目
call remove(list, from, to)        " 删除指定范围的项目

排序列表项目

let list = [3, 2, 1]
call sort(list)                   " 为列表排序
echo list                         " [1, 2, 3]
call reverse(list)                " 反转列表项目的排序
call list                         " [3, 2, 1]

过滤列表项目

使用filter({expr1}, {expr2})函数,可以对{expr1}指定的列表中的每个项目计算{expr2}表达式,以过滤掉符合指定模式的项目。

let data = [-1,0,1,2,3,4,5]
let positive = filter(copy(data), 'v:val >= 0')       " 过滤掉负数
echo positive                                         " [0,1,2,3,4,5]

let words = ['Linux', 'Unix', 'Mac']
let nnix = filter(copy(words), 'v:val !~ ".*nix"')    " 过滤包含nix的字符串
echo nnix                                             " ['Linux', 'Mac']

call filter(words, 0)                                 " 过滤掉所有项目
echo words                                            " []

修改列表项目

使用map({expr1}, {expr2})函数,可以将{expr1}指定的列表中的每个项目替换为{expr2}表达式的的计算结果。

let data = [-1,0,1,2,3,4,5]
let inc = map(copy(data), 'v:val + 1')                " 为每个成员+1
echo inc                                              " [0,1,2,3,4,5,6]

let words = ['Linux', 'Unix', 'Mac']
let cap = map(copy(words), 'toupper(v:val)')          " 将每个成员转换为大写
echo cap                                              " ['LINUX', 'UNIX', 'MAC']

连结列表

使用++=操作符,可以连结多个列表。

let activities = ['sleep', 'eat'] + ['drink']         " ['sleep', 'eat', 'drink']
let activities += ['code']                            " ['sleep', 'eat', 'drink', 'code']

请注意,操作符两侧必须均为列表。如果将列表与其它类型的数据连结,将会报错:

let activities += 'code'                              " E734: Wrong variable type for +=

常见问题

请注意,所有列表相关的函数都将修改后的列表作为返回结果,同时参数中的列表也将被修改。而通常,我们会希望返回修改后的列表,但保持原始列表不变。因此,建议使用copy()函数来复制原始列表作为参数,以避免其被修改。

let new_values = map(values, 'v:val * v:val')             " values和new_values均被修改
let new_values = map(copy(values), 'v:val * v:val')       " values保持不变

let sorted_list = reverse(sort(unsorted_list))            " unsorted_list和sorted_list均被修改
let sorted_list = reverse(sort(copy(unsorted_list)))      " unsorted_list保持不变

帮助信息

使用以下命令,可以查看列表相关的帮助信息:

:help list
:help list-functions

函数小结
len()列表的项目总数
empty()检查列表是否为空
insert()在列表某处插入项目
add()在列表后附加项目
remove()删除列表里一或多个项目
copy()建立列表的浅备份
deepcopy()建立列表的完整备份
filter()过滤指定的列表项目
map()改变每个列表项目
sort()为列表项目排序
reverse()反转列表项目的顺序
split()分割字符串成为列表
join()合并列表项目成为字符串
range()返回数值序列的列表
index()列表里某值的索引
max()列表项目的最大值
min()列表项目的最小值
count()计算列表里某值的出现次数

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

星期四, 一月 14, 2021

VIM学习笔记 通道(channel)

假设使用以下命令,连续开启两个异步作业

:call job_start('cd ~/.vim/')
:call job_start('ls')

这些作业之间,将是相互独立的。也就是说,连续执行这两条命令,并不能进入指定目录并列示文件。第一条命令,开启一个后台作业并使用'cd'命令进入目录;第二条命令,开启另一个独立的后台作业并使用'ls'命令列示当前目录的文件。

通道概念

利用Vim内置终端功能,可以改变当前目录并列示文件:

:terminal
$ cd ~/.vim
$ ls

也就是说,terminal命令开启了一个异步作业(即shell进程),它持续等待用户的输入,并解释执行键入的shell命令。vim利用通道(channel)来与后台异步作业进行通讯。借由此机制,vim可以获取外部命令的输出和状态,并执行回调函数进行响应。而随着外部命令的结束,通道也会自动关闭。

开启通道

使用ch_open({address} [, {options}])函数,可以开启通道:

:let channel = ch_open('localhost:8765', {'callback': "MyHandler"})

在通道选项{options}中,模式"mode"规定了通讯的消息格式(即传输和读写字符串的格式)。共支持四种模式:

  • NL,利用换行符(newline)来分隔消息。使用job_start()函数启动的作业,默认使用此模式;
  • JSONjson数据交换格式。使用ch_open()函数开启的通道,默认使用json模式;
  • JS,JavaScript风格的信息格式,效率比json更高;
  • RAW,原始格式,完全由用户在回调函数中进行处理。

至于应该使用何种模式的通道,则取决于另一端程序所提供的服务。对于简单通讯可以使用 NL 模式,而复杂的服务则推荐 JSON 模式。

模式的选择,也将影响"callback"回调函数的定义。一般形式为:

func MyHandler(channel, msg)
   echo "from the handler: " . a:msg
endfunc
  • channel参数,是通道ID,即ch_open()的返回值,代表某个特定的通道;
  • msg参数,即消息内容。如果是JSON或JS模式,将会自动解码为VimL数据类型,比如嵌套的字典或列表结构等;如果是NL模式,则将其转换为去除换行符的字符串;如果是RAW模式,则保留原始信息,其中的换行符也需要用户在回调函数中自行处理。

通道交互

开启通道并与另一端的程序建立连接之后,vim可以向对方发送请求,并等待回应,以此来协同工作。

针对不同模式的通道,需要使用不同的方式来发送信息:

JSON / JSNL / RAW描述
call ch_sendexpr(channel,{expr})call ch_sendraw(channel,{string})异步发送消息
不等待响应
call ch_sendexpr(channel,{expr},
     {'callback':MyHandler})
call ch_sendraw(channel,{string},
     {'callback':'MyHandler'})
异步发送消息
指定回调函数来响应
let response =
    ch_evalexpr(channel,{expr})
let response =
    ch_evalraw(channel,{string})
同步发送消息
并等待对方响应
  • channel参数,即用于识别通道的唯一编号;
  • expr参数,指定将要发送的VimL数值或数据结构,并交由vim编码成json或js风格的字符串;
  • string参数,必须是字符串,而不能是其他复杂的VimL数据结构。

Vim实际发送的消息,为[{channel},{expr}]组成的一个二元列表;通道彼端接收消息并进行处理之后,也将由通道返回[{channel},{response}]组成的二元列表;在同一请求回应中,通道编号是相同的,据此将返回值分发到对应的回调函数。如果在发送消息时没有指定回调函数,那么将使用在ch_open()中指定的回调函数。

同步发送消息,存在阻塞的风险,但其优点是程序逻辑简单,不必使用回调函数。如果另一端的服务程序运行在本地机器,并且执行的操作耗时较短时,可以考虑使用同步消息方式。根据通道选项"timeout"键的默认设定,阻塞时间超过2000毫秒 (即2秒)时,Vim将自动终止操作。在超时或出错时,ch_evalexpr()函数将返回空字符串。

请注意,JSON和JS模式的通道也可以使用ch_sendraw()和ch_evalraw()函数,但是需要调用json_encode()和json_decode()函数来自行处理编码和解码。

通道状态

使用ch_status()函数,可以返回指定通道的状态:

:echo ch_status(channel)

状态描述
fail通道打开失败
open通道可用
buffered通道已关闭,但还有待读的数据
closed通道已关闭

使用ch_info()函数,可以返回指定通道的详细信息:

:echo ch_info(channel)

{'status': 'open', 'id': 1, 'port': 8765, 'hostname': 'localhost', 'sock_io': 'socket', 'sock_mode': 'JSON', 'sock_timeout': 2000, 'sock_status': 'open'}

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

描述
statusch_status()返回值
id通道号
port地址的端口号
hostname地址的机器名
sock_io"socket"
sock_mode"NL"、"RAW"、"JSON" 或 "JS"
sock_timeout以毫秒为单位的超时
sock_status"open" 或 "closed"

关闭通道

使用ch_close()函数,可以关闭指定的通道:

:call ch_close(channel)

使用套接字(socket)时,将关闭双向的套接字;使用管道 (stdin/stdout/stderr)时,将关闭所有的管道。

通道实例

在操作系统的终端中,运行Vim自带的 $VIMRUNTIME/tools/demoserver.py 演示程序,服务开始监听指定端口:

Server loop running in thread:  Thread-1
Listening on port 8765

在Vim中开启通道,连接到演示服务器:

:let channel = ch_open('localhost:8765')

此时操作系统终端中,将显示通讯开放:

=== socket opened ===

在Vim中使用以下命令,向通道彼端发送消息:

:call ch_sendexpr(channel, 'hello!')

因为没有指定回调函数,所以Vim不会显示任何回显;运行在外部终端的监听服务,将显示以下信息:

received: [1,"hello!"]
sending [1, "got it"]

在Vim中使用以下命令,向通道彼端发送消息并指定上文中定义的回调函数:

:call ch_sendexpr(channel, 'hello!', {'callback': "MyHandler"})

Vim将执行指定的回调函数,并显示以下信息:

from the handler: got it

同时运行在外部终端的监听服务,将显示以下信息:

received: [2,"hello!"]
sending [2, "got it"]

在操作系统终端中的服务程序内,输入以下命令向另一端的Vim发送消息:

["ex","echo 'hi there'"]

在Vim屏幕底部,将显示以下消息:

hi there

channel_demo

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

:help channel

命令小结
ch_open()打开通道
ch_sendexpr()发送消息
ch_evalexpr()
ch_sendraw()
ch_evalraw()
ch_status()通道状态
ch_info()通道信息
ch_close()关闭通道

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

星期一, 一月 04, 2021

VIM学习笔记 使用rot13加密

ROT13算法

ROT13(回转13位,英语:rotate by 13 places,有时也记为ROT-13)是一种简易的替换式密码。ROT13是一种在英文网络论坛用作隐藏八卦(spoiler)、妙句、谜题解答以及某些脏话的工具,目的是逃过版主或管理员的匆匆一瞥。ROT13被描述成“杂志字谜上下颠倒解答的Usenet点对点体”。ROT13 也是过去在古罗马开发的凯撒加密的一种变体。ROT13是它自己本身的逆反;也就是说,要还原ROT13,套用加密同样的算法即可得,故同样的操作可用再加密与解密。该算法并没有提供真正的密码学上的保全,故它不应该被套用在需要保全的用途上。它常常被当作弱加密示例的典型。

ROT13_table_with_example
Source: zh.wikipedia.org/zh-cn/ROT13

g?命令

使用 g?{motion} 命令,可以使用Rot13对{motion}跨越的文本进行编码。例如以下命令,将对当前行进行ROT13转换:

g??

使用以下命令,将对从当前行到文件末尾的文本进行ROT13转换:

:normal VGg?

使用以下命令,将对从指定行到文件末尾的文本进行ROT13转换:

:normal 10GVGg?

假设需要针对以下id属性值进行ROT13转换:

<li id="lorem">foo</li>

那么可以在g?命令中指定文本对象

g?i"

<li id="yberz">foo</li>

以下英文笑话,精华句为ROT13所隐匿:

How can you tell an extrovert from an
introvert at NSA? Va gur ryringbef,
gur rkgebireg ybbxf ng gur BGURE thl'f fubrf.

使用以下命令,透过ROT13表格转换整片文字,该笑话的解答揭露如下:

ggVGg?

Ubj pna lbh gryy na rkgebireg sebz na
vagebireg ng AFN? In the elevators,
the extrovert looks at the OTHER guy's shoes.

再次执行该命令,将重新对文本进行解密。以此类推,可以反复加密和解密整个文件。

定义以下快捷键,也可以对整个文件进行ROT13转换:

:map <F3> ggg?G

使用以下命令,可以查看相关帮助信息:

:help g?

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

星期五, 十二月 18, 2020

The Clothed/Naked Maja

 

The Clothed/Naked Maja

星期四, 十二月 17, 2020

星期一, 十一月 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<上一篇 | 目录 下一篇>