Neovim 智能对话伙伴 chat.nvim

安装及配置 chat.nvim 使用操作界面 picker.nvim 集成 Usage 实际上也在代码块里面是,但是由于 markdown 内还有代码块,导致解析展示出问题。 正是由于这个原因,我制作了 Neovim AI 聊天插件 chat.nvim, 我需要以纯文本展示请求结果的完整内容。 安装及配置 chat.nvim local deepseek_api_key = 'xxxxxxxxxxx' local free_chatgpt_api_key = 'xxxxxxxxxxxxxxx' require('plug').add({ { 'wsdjeg/chat.nvim', opt = { api_key = { deepseek = deepseek_api_key, free_chatgpt = free_chatgpt_api_key, }, provider = 'free_chatgpt', model = 'gpt-4o-mini', border = 'double', }, }, }) 使用操作界面 picker.nvim 集成 :Picker chat - 搜索历史对话 :Picker chat_provider - 搜索并切换 provider :Picker chat_model - 搜索并切换当前 provider 提供的模型

2026/2/3
articleCard.readMore

Neovim buffer 删除插件 bufdel.nvim

为什么再写一个 bufdel 插件 bufdel.nvim 的核心设计 删除之后,切到哪个 buffer 用户命令Bdelete/Bwipeout 一个 Vim 本身的限制说明 自定义用户事件 和其他插件的简单对比 写在最后 bufdelete.nvim、nvim-bufdel、mini.bufremove,以及 snacks.bufdelete。 但是在我自己长期使用过程中,总觉得缺少了我需要的功能。 于是,我写了一个新的插件:bufdel.nvim。 这篇文章简单聊聊它解决了什么问题、有哪些设计取舍,以及它和现有方案的区别。 为什么再写一个 bufdel 插件 按照 buffer name 删除 按照正则表达式匹配到的 buffer 名称删除 按照特定的条件函数删除(比如:未修改、已列出、非当前 buffer) bufnr('#')。但我更希望能明确指定下一个 buffer 是哪个或者通过函数,完全自定义切换逻辑。 bufdel.nvim 的核心设计 delete 和 wipeout,其实完全可以合并,通过 opt 内一个选项区分。 require('bufdel').delete(buffers, opt) 单个 buffer number buffer 名称(字符串) Vim 正则(匹配 buffer name) table(混合 bufnr / bufname / regex) 函数过滤器 require('bufdel').delete(function(buf) return not vim.bo[buf].modified and vim.bo[buf].buflisted and buf ~= vim.api.nvim_get_current_buf() end, { wipe = true }) require('bufdel').delete('.txt$', { wipe = true }) 删除之后,切到哪个 buffer require('bufdel').delete(filter, { wipe = true, switch = function(deleted_buf) return vim.fn.bufnr('#') -- 切换到 alternate buffer end, }) switch = 'alt' alt:alternate buffer(#) current:保持当前 buffer lastused:最近使用的 buffer next / prev:下一个 / 上一个 buffer switch = 3 用户命令Bdelete/Bwipeout :Bdelete :Bwipeout :bdelete / :bwipeout 一致,但不会改变窗口布局。 示例: :Bdelete :Bdelete 3 :Bdelete 2 5 7 :3,6Bdelete 一个 Vim 本身的限制说明 :bdelete 一样: 纯数字的 buffer 名称不能作为用户命令参数使用。 比如: :e 123 :Bdelete 123 自定义用户事件 User BufDelPre User BufDelPost vim.api.nvim_create_autocmd('User', { pattern = 'BufDelPost', callback = function(ev) -- 被删除的 bufnr 在 ev.data.buf 中 end, }) BufDelPost 不会触发。 和其他插件的简单对比 Feature / Plugin bufdel.nvim bufdelete.nvim nvim-bufdel snacks.bufdelete mini.bufremove Preserve window layout ✓ ✓ ✓ ✓ ✓ Delete by bufnr ✓ ✓ ✓ ✓ ✓ Delete by bufname ✓ ✓ ✓ ✓ ✗ User Command ✓ ✓ ✓ ✗ ✗ Lua filter function ✓ ✗ ✗ ✓ ✗ Regex buffer matching ✓ ✗ ✗ ✗ ✗ Post-delete buffer switch ✓ ✓ ✓ ✗ ✗ User autocmd hooks ✓ ✓ ✗ ✗ ✗ 写在最后 功能边界清晰 API 简单 逻辑可控 经常清理 buffer 在意窗口布局 希望对 buffer 删除过程有更多掌控 wsdjeg/budel.nvim 如果你觉得有用,欢迎 star ⭐

2026/1/28
articleCard.readMore

Neovim 日历插件 calendar.nvim

插件安装 基本使用 记录一些坑 最终效果图 calendar.nvim,功能目前还是非常简单的,只是一个简单的日历月视图。 这算是 2026 年我的第一个 Neovim 插件,这篇文字主要介绍 calendar.nvim 插件的安装使用以及制作这一插件遇到的一些问题。 插件安装 nvim-plug require('plug').add({ { 'wsdjeg/calendar.nvim', }, }) 基本使用 require('calendar').setup({ mark_icon = '•', keymap = { next_month = 'L', -- 下个月 previous_month = 'H', -- 上个月 next_day = 'l', -- 后一天 previous_day = 'h', -- 前一天 next_week = 'j', -- 下一周 previous_week = 'k', -- 前一周 today = 't', -- 跳到今天 }, highlights = { current = 'Visual', today = 'Todo', mark = 'Todo', }, }) 记录一些坑 nvim_buf_set_extmark 函数中 col 等参数指的并不是屏幕 column 列表,而是字符串的字节, local hls = { highlights.mark } if is_totay() then table.insert(hls, highlights.today) end if is_current() then table.insert(hls, highlights.current) end vim.api.nvim_buf_set_extmark(buf, ns, col, { virt_text = { { mark_icon, hls } }, }) 最终效果图 local zk_ext = {} function zk_ext.get(year, month) local notes = require('zettelkasten.browser').get_notes() local marks = {} for _, note in ipairs(notes) do local t = vim.split(note.id, '-') if tonumber(t[1]) == year and tonumber(t[2]) == month then table.insert( marks, { year = tonumber(t[1]), month = tonumber(t[2]), day = tonumber(t[3]), } ) end end return marks end require('calendar.extensions').register(zk_ext) 最终的效果图如下:

2026/1/4
articleCard.readMore

文件路径大小写敏感导致 Lua 模块重载

事情起因 寻找原因 如何修复? 总结 事情起因 plugins/chineselinter.lua return { 'wsdjeg/ChineseLinter.nvim', dev = true, opts = { ignored_errors = { 'E015', 'E013', 'E020', 'E021' }, }, cmds = { 'CheckChinese' }, desc = 'Chinese Document Language Standards Checking Tool', } 寻找原因 vim.opt.runtimepath:append("D:/wsdjeg/job.nvim") vim.opt.runtimepath:append("D:/wsdjeg/logger.nvim") vim.opt.runtimepath:append("D:/wsdjeg/nvim-plug") require('plug').setup({ bundle_dir = 'D:/bundle_dir', raw_plugin_dir = 'D:/bundle_dir/raw_plugin', -- ui = 'notify', http_proxy = 'http://127.0.0.1:7890', https_proxy = 'http://127.0.0.1:7890', enable_priority = false, enable_luarocks = true, max_processes = 16, dev_path = 'D:/wsdjeg', }) require("plug").add({ { "wsdjeg/ChineseLinter.nvim", dev = true, opts = { ignored_errors = { "E015", "E013", "E020", "E021" }, }, cmds = { "CheckChinese" }, desc = "Chinese Document Language Standards Checking Tool", }, }) [ 23:35:32:449 ] [ Info ] [ cnlint ] module is loaded [ 23:35:32:450 ] [ Info ] [ cnlint ] setup function is called [ 23:35:32:450 ] [ Info ] [ plug ] load plug: ChineseLinter.nvim in 4.3624ms [ 23:35:32:451 ] [ Info ] [ cnlint ] module is loaded [ 23:35:32:451 ] [ Info ] [ cnlint ] check function is called plugin/ChineseLinter.lua 内定义的: vim.api.nvim_create_user_command("CheckChinese", function(opt) require("ChineseLinter").check() end, { nargs = "*" }) require('ChineseLinter') 不应该再次载入模块文件,因为前面 nvim-plug 已经执行过一次了,正常情况下 package.loaded 内会缓存模块。 看一下 nvim-plug 载入 Lua 插件的逻辑,它会给 plugSpec 自动设置一个模块名称, 以便于自动执行 require(plugSpec.module).setup(plugSpec.opts)。 问题就在于这个 module 名称生成函数原先是: local function get_default_module(name) return name :lower() :gsub('[%.%-]lua$', '') :gsub('^n?vim-', '') :gsub('[%.%-]n?vim', '') end require('chineselinter'),这在 Windows 系统下, 因为文件 lua/ChineseLinter/init.lua 已存在,那么上述 require 函数就会读取这个模块。 而 :CheckChinese 命令实际上调用的模块是 require('ChineseLinter')。 因为 Lua 的模块名称实际上是大小写敏感的,就会再次去寻找模块文件以载入。 如何修复? lower() 函数: lazy.nvim ---@param name string ---@return string function M.normname(name) local ret = name:lower():gsub("^n?vim%-", ""):gsub("%.n?vim$", ""):gsub("[%.%-]lua", ""):gsub("[^a-z]+", "") return ret end diff --git a/lua/plug/loader.lua b/lua/plug/loader.lua index d0fc7b6..957fcb7 100644 --- a/lua/plug/loader.lua +++ b/lua/plug/loader.lua @@ -68,8 +68,7 @@ end --- @param name string --- @return string local function get_default_module(name) - return name:lower() - :gsub('[%.%-]lua$', '') + return name:gsub('[%.%-]lua$', '') :gsub('^n?vim-', '') :gsub('[%.%-]n?vim', '') end @@ -94,6 +93,13 @@ function M.parser(plugSpec) plugSpec.name = check_name(plugSpec) if not plugSpec.module then plugSpec.module = get_default_module(plugSpec.name) + log.info( + string.format( + 'set %s default module name to %s', + plugSpec.name, + plugSpec.module + ) + ) end if #plugSpec.name == 0 then plugSpec.enabled = false Shift 键这么难按,我将插件的名称以及其内模块的名称都改成了小写,修改后插件的安装方式: return { 'wsdjeg/chineselinter.nvim', dev = true, opts = { ignored_errors = { 'E015', 'E013', 'E020', 'E021' }, }, cmds = { 'CheckChinese' }, desc = 'Chinese Document Language Standards Checking Tool', } 总结 package.load[key],这里的 key 是大小写敏感的。 而发现缓存不存在时,依照 key 去载入文件时,在 Windows 系统下路劲又是不敏感的, 会导致同一个模块被不同的大小写模块名称多次载入。

2025/12/28
articleCard.readMore

Neovim 悬浮滚动条 scrollbar.nvim

scrollbar.nvim 简介 安装 scrollbar.nvim 插件的配置 scrollbar.nvim 简介 悬浮侧栏插件 scrollbar.vim, 前段时间该插件使用 Lua 进行了重写并改名称为 scrollbar.nvim, 重写后的插件只支持 Neovim。 scrollbar.nvim 会在当前窗口的右侧使用浮窗绘制一个滚动条,其位置依据当前窗口显示的内容在整个文件中所在的行数, 并且随着鼠标移动、滚屏等操作上下移动。 安装 scrollbar.nvim require('plug').add({ { 'wsdjeg/scrollbar.nvim' } }) luarocks install scrollbar.nvim 插件的配置 require('scrollbar').setup({ max_size = 10, min_size = 5, width = 1, right_offset = 1, excluded_filetypes = { 'startify', 'git-commit', 'leaderf', 'NvimTree', 'tagbar', 'defx', 'neo-tree', 'qf', }, shape = { head = '▲', body = '█', tail = '▼', }, highlight = { head = 'Normal', body = 'Normal', tail = 'Normal', }, debug = false, })

2025/12/25
articleCard.readMore

如何正确地使用 ftplugin 目录

前面再阅读一些插件源码时,发现一个问题,很多插件的使用了 ftplugin 这个目录,其内的脚本文件中直接使用了 setlocal xx=xx 这样的语法。 在早期的 Neovim 或者 Vim 版本中这样确实没有问题,但是随着 Neovim 功能特性增加。这样写就会容易出错。 实际上,直到目前为止 Neovim 和 Vim 的官方文档 :h ftplugin 内的示例还是: " Only do this when not done yet for this buffer if exists("b:did_ftplugin") finish endif let b:did_ftplugin = 1 setlocal textwidth=70 augroup filetypeplugin au FileType * call s:LoadFTPlugin() func! s:LoadFTPlugin() if exists("b:undo_ftplugin") exe b:undo_ftplugin unlet! b:undo_ftplugin b:did_ftplugin endif let s = expand("<amatch>") if s != "" if &cpo =~# "S" && exists("b:did_ftplugin") " In compatible mode options are reset to the global values, need to " set the local values also when a plugin was already used. unlet b:did_ftplugin endif " When there is a dot it is used to separate filetype names. Thus for " "aaa.bbb" load "aaa" and then "bbb". for name in split(s, '\.') " Load Lua ftplugins after Vim ftplugins _per directory_ " TODO(clason): use nvim__get_runtime when supports globs and modeline " XXX: "[.]" in the first pattern makes it a wildcard on Windows exe $'runtime! ftplugin/{name}[.] ftplugin/{name}_*. ftplugin/{name}/*.' endfor endif endfunc augroup END FileType 这个事件,然后根据 expand('<amatch>') 的值来执行 :runtime 命令。 但是,随着 Neovim 和 Vim 增加了设置非当前 buffer 的 option 这一功能后。就会出现这样问题,当 FileType 事件触发时,触发的 buffer 并非是当前 buffer。 那么在 ftplugin 内如果使用了 setlocal 这样的命令,有可能会设置错了缓冲区。 test_ft.lua local log = require("logger").derive("ft") log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) log.info("-----------------------------------------------------") local real_current_win = vim.api.nvim_get_current_win() local newbuf = vim.api.nvim_create_buf(true, false) local events = {} for _, v in ipairs(vim.fn.getcompletion("", "event")) do if not vim.endswith(v, "Cmd") then table.insert(events, v) end end local id = vim.api.nvim_create_autocmd(events, { group = vim.api.nvim_create_augroup("test_ft", { clear = true }), pattern = { "*" }, callback = function(ev) log.info("-----------------------------------------------------") log.info("event is " .. ev.event) log.info("ev.buf is " .. ev.buf) log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) log.info("real_current_win's buf is" .. vim.api.nvim_win_get_buf(real_current_win)) end, }) vim.api.nvim_open_win(newbuf, false, { split = "above" }) vim.api.nvim_set_option_value("filetype", "test123", { buf = newbuf }) vim.api.nvim_del_autocmd(id) log.info("-----------------------------------------------------") log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) [ 23:50:19:932 ] [ Info ] [ ft ] nvim_get_current_buf() is 7 [ 23:50:19:932 ] [ Info ] [ ft ] nvim_get_current_win() is 1000 [ 23:50:19:932 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:50:19:933 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:50:19:933 ] [ Info ] [ ft ] event is WinNew [ 23:50:19:933 ] [ Info ] [ ft ] ev.buf is 7 [ 23:50:19:933 ] [ Info ] [ ft ] nvim_get_current_buf() is 7 [ 23:50:19:933 ] [ Info ] [ ft ] nvim_get_current_win() is 1008 [ 23:50:19:933 ] [ Info ] [ ft ] real_current_win's buf is7 [ 23:50:19:934 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:50:19:934 ] [ Info ] [ ft ] event is BufWinEnter [ 23:50:19:934 ] [ Info ] [ ft ] ev.buf is 9 [ 23:50:19:934 ] [ Info ] [ ft ] nvim_get_current_buf() is 9 [ 23:50:19:934 ] [ Info ] [ ft ] nvim_get_current_win() is 1008 [ 23:50:19:934 ] [ Info ] [ ft ] real_current_win's buf is7 [ 23:50:19:953 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:50:19:953 ] [ Info ] [ ft ] event is Syntax [ 23:50:19:953 ] [ Info ] [ ft ] ev.buf is 9 [ 23:50:19:953 ] [ Info ] [ ft ] nvim_get_current_buf() is 9 [ 23:50:19:953 ] [ Info ] [ ft ] nvim_get_current_win() is 1008 [ 23:50:19:953 ] [ Info ] [ ft ] real_current_win's buf is7 [ 23:50:19:954 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:50:19:954 ] [ Info ] [ ft ] event is FileType [ 23:50:19:954 ] [ Info ] [ ft ] ev.buf is 9 [ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_buf() is 9 [ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_win() is 1008 [ 23:50:19:954 ] [ Info ] [ ft ] real_current_win's buf is7 [ 23:50:19:954 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:50:19:954 ] [ Info ] [ ft ] event is OptionSet [ 23:50:19:954 ] [ Info ] [ ft ] ev.buf is 0 [ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_buf() is 9 [ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_win() is 1008 [ 23:50:19:954 ] [ Info ] [ ft ] real_current_win's buf is7 [ 23:50:19:954 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_buf() is 7 [ 23:50:19:954 ] [ Info ] [ ft ] nvim_get_current_win() is 1000 local log = require("logger").derive("ft") log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) log.info("-----------------------------------------------------") local real_current_win = vim.api.nvim_get_current_win() local newbuf = vim.api.nvim_create_buf(true, false) local events = {} for _, v in ipairs(vim.fn.getcompletion("", "event")) do if not vim.endswith(v, "Cmd") then table.insert(events, v) end end local id = vim.api.nvim_create_autocmd(events, { group = vim.api.nvim_create_augroup("test_ft", { clear = true }), pattern = { "*" }, callback = function(ev) log.info("-----------------------------------------------------") log.info("event is " .. ev.event) log.info("ev.buf is " .. ev.buf) log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) log.info("real_current_win's buf is" .. vim.api.nvim_win_get_buf(real_current_win)) end, }) -- vim.api.nvim_open_win(newbuf, false, { split = "above" }) vim.api.nvim_set_option_value("filetype", "test123", { buf = newbuf }) vim.api.nvim_del_autocmd(id) log.info("-----------------------------------------------------") log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) [ 23:53:49:058 ] [ Info ] [ ft ] nvim_get_current_buf() is 10 [ 23:53:49:058 ] [ Info ] [ ft ] nvim_get_current_win() is 1000 [ 23:53:49:058 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:53:49:078 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:53:49:078 ] [ Info ] [ ft ] event is Syntax [ 23:53:49:078 ] [ Info ] [ ft ] ev.buf is 12 [ 23:53:49:078 ] [ Info ] [ ft ] nvim_get_current_buf() is 12 [ 23:53:49:078 ] [ Info ] [ ft ] nvim_get_current_win() is 1001 [ 23:53:49:078 ] [ Info ] [ ft ] real_current_win's buf is10 [ 23:53:49:079 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:53:49:079 ] [ Info ] [ ft ] event is FileType [ 23:53:49:079 ] [ Info ] [ ft ] ev.buf is 12 [ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_buf() is 12 [ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_win() is 1001 [ 23:53:49:079 ] [ Info ] [ ft ] real_current_win's buf is10 [ 23:53:49:079 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:53:49:079 ] [ Info ] [ ft ] event is OptionSet [ 23:53:49:079 ] [ Info ] [ ft ] ev.buf is 0 [ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_buf() is 12 [ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_win() is 1001 [ 23:53:49:079 ] [ Info ] [ ft ] real_current_win's buf is10 [ 23:53:49:079 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_buf() is 10 [ 23:53:49:079 ] [ Info ] [ ft ] nvim_get_current_win() is 1000 local log = require("logger").derive("ft") log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) log.info("-----------------------------------------------------") local real_current_win = vim.api.nvim_get_current_win() local newbuf = vim.api.nvim_create_buf(true, false) local events = {} for _, v in ipairs(vim.fn.getcompletion("", "event")) do if not vim.endswith(v, "Cmd") then table.insert(events, v) end end local id = vim.api.nvim_create_autocmd(events, { group = vim.api.nvim_create_augroup("test_ft", { clear = true }), pattern = { "*" }, callback = function(ev) log.info("-----------------------------------------------------") log.info("event is " .. ev.event) log.info("ev.buf is " .. ev.buf) log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) log.info('win count is ' .. vim.fn.winnr('$')) log.info('winconfig is ' .. vim.inspect(vim.api.nvim_win_get_config(vim.api.nvim_get_current_win()))) log.info("real_current_win's buf is" .. vim.api.nvim_win_get_buf(real_current_win)) end, }) -- vim.api.nvim_open_win(newbuf, false, { split = "above" }) vim.api.nvim_set_option_value("filetype", "test123", { buf = newbuf }) vim.api.nvim_del_autocmd(id) log.info("-----------------------------------------------------") log.info("nvim_get_current_buf() is " .. vim.api.nvim_get_current_buf()) log.info("nvim_get_current_win() is " .. vim.api.nvim_get_current_win()) [ 23:57:49:249 ] [ Info ] [ ft ] nvim_get_current_buf() is 9 [ 23:57:49:249 ] [ Info ] [ ft ] nvim_get_current_win() is 1000 [ 23:57:49:249 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:57:49:268 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:57:49:268 ] [ Info ] [ ft ] event is Syntax [ 23:57:49:268 ] [ Info ] [ ft ] ev.buf is 13 [ 23:57:49:268 ] [ Info ] [ ft ] nvim_get_current_buf() is 13 [ 23:57:49:268 ] [ Info ] [ ft ] nvim_get_current_win() is 1001 [ 23:57:49:268 ] [ Info ] [ ft ] win count is 2 [ 23:57:49:268 ] [ Info ] [ ft ] winconfig is { anchor = "NW", col = 0, external = false, focusable = false, height = 5, hide = false, mouse = false, relative = "editor", row = 0, width = 168, zindex = 50 } [ 23:57:49:268 ] [ Info ] [ ft ] real_current_win's buf is9 [ 23:57:49:269 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:57:49:269 ] [ Info ] [ ft ] event is FileType [ 23:57:49:269 ] [ Info ] [ ft ] ev.buf is 13 [ 23:57:49:269 ] [ Info ] [ ft ] nvim_get_current_buf() is 13 [ 23:57:49:269 ] [ Info ] [ ft ] nvim_get_current_win() is 1001 [ 23:57:49:269 ] [ Info ] [ ft ] win count is 2 [ 23:57:49:270 ] [ Info ] [ ft ] winconfig is { anchor = "NW", col = 0, external = false, focusable = false, height = 5, hide = false, mouse = false, relative = "editor", row = 0, width = 168, zindex = 50 } [ 23:57:49:270 ] [ Info ] [ ft ] real_current_win's buf is9 [ 23:57:49:270 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:57:49:270 ] [ Info ] [ ft ] event is OptionSet [ 23:57:49:270 ] [ Info ] [ ft ] ev.buf is 0 [ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_buf() is 13 [ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_win() is 1001 [ 23:57:49:270 ] [ Info ] [ ft ] win count is 2 [ 23:57:49:270 ] [ Info ] [ ft ] winconfig is { anchor = "NW", col = 0, external = false, focusable = false, height = 5, hide = false, mouse = false, relative = "editor", row = 0, width = 168, zindex = 50 } [ 23:57:49:270 ] [ Info ] [ ft ] real_current_win's buf is9 [ 23:57:49:270 ] [ Info ] [ ft ] ----------------------------------------------------- [ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_buf() is 9 [ 23:57:49:270 ] [ Info ] [ ft ] nvim_get_current_win() is 1000 这里说明一下,即便是 nvim_open_win 没有执行,Neovim 也会新建一个 autocmd windows,使用 win_gettype() 函数可以获取值为 “autocmd”。

2025/12/23
articleCard.readMore

本地化管理 Github secrets

安装 github.nvim 使用 github.nvim 访问仓库 secrts 安装 github.nvim github.nvim 是一个 GitHub REST API 的 Lua 实现,用于在 Neovim 中访问 Github API。 早些年,我使用 Vim Script 写过类似的 Vim 插件 GitHub.vim, 而 github.nvim 算是 github.vim 的 Lua 重新实现版本,目前也仅仅实现了部分 API,会更具个人使用需要,陆续再实现其他的 API。 可以使用任意 Neovim 插件管理器安装,比如 nvim-plug, require('plug').add({ 'wsdjeg/github.nvim', }) luarocks install github.nvim 使用 github.nvim 访问仓库 secrts libsodium-1.0.20-stable-msvc.zip,解压后, 需要将 libsodium\x64\Release\v143\dynamic 目录加入到环境变量 PATH 内。 vim.env.PATH = vim.env.PATH .. ';' .. [[D:\Downloads\libsodium-1.0.20-stable-msvc\libsodium\x64\Release\v143\dynamic]] luarocks install luasodium SODIUM_INCDIR=D:\Downloads\libsodium-1.0.20-stable-msvc\libsodium\include SODIUM_DIR=D:\Downloads\libsodium-1.0.20-stable-msvc\libsodium\x64\Release\v143\dynamic local luasodium_ffi = require'luasodium' -- ok local luasodium_ffi = require'luasodium.ffi' -- uses the FFI API (in a C module) ok local luasodium_c = require'luasodium.core' -- uses the C API ok local luasodium_pureffi = require'luasodium.pureffi' -- 失败,因为他使用 `require('ffi').load('sodium')`, 应该是 libsodium require(‘ffi’).load(‘sodium’) – 在luasodium 修复之前,可以临时如下操作 – 将 dynamic 目录里面的 libsodium.dll 改名为 sodium.dll 我也给 luasodium 提交了一个 PR 来解决这个问题。 确保上述 libsodium、luasodium、github.nvim 都安装好了之后,就可以使用以下脚本了: local secrts = { { name = 'DOCKER_API_KEY', value = '12jdksjdiiwkdjsskkdj', }, { name = 'LUAROCKS_API_KEY', value = 'ijnuhbygvtfcrdxesz', }, } local repos = { 'picker.nvim', 'format.nvim', 'tasks.nvim', } for _, repo in ipairs(repos) do for _, secrt in ipairs(secrts) do require('github.secrets').update_repository_secret('wsdjeg', repo, secrt) end end 通过上述脚本,就给批量给自己的 Github 仓库设定 secrets,后期如果 API_KEY 修改了, 只需要修改脚本后再执行一次即可。

2025/12/4
articleCard.readMore

发现 Git 仓库中幽灵文件

使用 git-ghosts 拓展 参考链接 src 的文件列表: git log --diff-filter=D --summary | rg delete | rg src require('code-runner').setup({ runners = { lua = { exe = 'nvim', opt = { '-l', '-' }, usestdin = true }, ps = { exe = 'powershell.exe', opt = { '-Command', '-' }, usestdin = true }, }, }) [Running] powershell.exe -Command - STDIN -------------------- delete mode 100644 src/test/hello.c delete mode 100644 src/example/delete.c [Done] exited with code=0, single=0 in 0.916431 seconds :Git log -1 -- src/test/hello.c 使用 git-ghosts 拓展 :Git show <commit_hash> 参考链接 https://www.linux88.com/restore-a-deleted-document-in-git/

2025/11/29
articleCard.readMore

Neovim 中使用 luarocks

安装 luarocks 在 Neovim 内使用 luarocks 使用 nvim-plug 下载 rocks luarocks 的限制 将插件发布到 LuaRocks 模块载入问题 排除问题 Neovim 终端中使用 安装 luarocks scoop install luarocks scoop uninstall lua scoop install lua51 luarocks config | rg deploy deploy_bin_dir = "D:\\Scoop\\apps\\luarocks\\current\\rocks\\bin" deploy_lib_dir = "D:\\Scoop\\apps\\luarocks\\current\\rocks\\lib\\lua\\5.1" deploy_lua_dir = "D:\\Scoop\\apps\\luarocks\\current\\rocks\\share\\lua\\5.1" 在 Neovim 内使用 luarocks :lua 命令或者使用 lua 开发 Neovim 插件时, 若想要使用 luarocks 安装的包,其原理就是将 luarocks 所安装的包位置加入到 package.path 和 package.cpath: nvim-plug 中实现这一步骤的逻辑如下: lua/plug/rocks/init.lua function M.enable() if enabled then return end local ok, _ = pcall(function() local luarocks_config = vim.json.decode( vim.system({ 'luarocks', 'config', '--json' }):wait().stdout ) package.path = package.path .. ';' .. luarocks_config.deploy_lua_dir .. [[\?.lua]] .. ';' .. luarocks_config.deploy_lua_dir .. [[\?\init.lua]] .. ';' package.cpath = package.cpath .. ';' .. luarocks_config.deploy_lib_dir .. '\\?.' .. luarocks_config.external_lib_extension -- 此处,还可以将 luarcoks bin 目录加入到 PATH vim.env.PATH = vim.env.PATH .. ';' .. luarocks_config.deploy_bin_dir end) if ok then enabled = true end end 使用 nvim-plug 下载 rocks type = 'rocks',比如: plugins/mru.lua return { 'wsdjeg/mru.nvim', events = { 'UIEnter' }, opts = { enable_cache = true, ignore_path_regexs = { '/.git/', '/nvim/runtime/doc/', '.mp3$', '.mp4$', '.png$', '.jpg$', '.exe$', 'nvim-mru.json$', 'tags$', }, enable_logger = true, sort_by = 'lastenter', }, type = 'rocks', desc = 'mru(most recently used) files', } luarocks install plugin_name 这一命令。 luarocks 的限制 Error: command ‘install’ requires exclusive write access。 解决的办法是为 luarocks 实现单独的 tasks 序列,逐一执行,这样的话插件的安装会非常慢。一个是单线程,一个是 16 线程 (max_processes = 16)。 { 'wsdjeg/mru.nvim' }, 默认 type 是 git,我是可以获取到该插件默认的 runtimepath 值为 plug.config.bundle_dir .. '/' .. 'wsdjeg/mru.nvim', 此时就可以根据这个目录是否存在来判断插件是否已安装。 但是,这样一个 plugSpec: return { 'wsdjeg/mru.nvim', type = 'rocks', } D:/Scoop/apps/luarocks/current/rocks/lib/luarocks/rocks-5.1/mru.nvim/1.4.0-1 分析 luarocks list 命令的输出内容,返回一个类似与这样的 lua table: return { ['mru.nvim'] = { rtp = 'D:/Scoop/apps/luarocks/current/rocks/lib/luarocks/rocks-5.1/mru.nvim/1.4.0-1', }, ['rooter.nvim'] = { rtp = 'D:/Scoop/apps/luarocks/current/rocks/lib/luarocks/rocks-5.1/rooter.nvim/1.3.0-1', }, } 将插件发布到 LuaRocks googleapis/release-please-action nvim-neorocks/luarocks-tag-release googleapis/release-please-action 来自动打 tag 并且新建 GitHub release,可以参考之前的文章《Github 仓库自动 release》。 使用 nvim-neorocks/luarocks-tag-release GitHub action 自动将 tag 上传到 luarocks.org。 在仓库根目录新建文件 .github/workflows/luarocks.yml: name: Push to Luarocks on: push: tags: # Will upload to luarocks.org when a tag is pushed - "*" pull_request: # Will test a local install without uploading to luarocks.org workflow_dispatch: jobs: luarocks-upload: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: LuaRocks Upload uses: nvim-neorocks/luarocks-tag-release@v7 env: LUAROCKS_API_KEY: $ luarocks/issues/1266 代码逻辑 src/luarocks/build.lua#L344-L363 模块载入问题 D:\wsdjeg\my-blog>luarocks list | rg file -A 2 luafilesystem 1.8.0-1 (installed) - D:\Scoop\apps\luarocks\current\rocks\lib\luarocks\rocks-5.1 :lua require('lfs') 时,报错: E5108: Error executing lua error loading module 'lfs' from file 'D:\Scoop\apps\luarocks\current\rocks\lib\lua\5.1\lfs.dll': 找不到指定的模块。 stack traceback: [C]: at 0x7ff83ac1bdb0 [C]: in function 'require' [string ":lua"]:1: in main chunk D:\Scoop\apps\luarocks\current\rocks\lib\lua\5.1>ls lfs.dll 排除问题 dependencies, scoop install dependencies D:\Scoop\apps\lua51\current>ls Microsoft.VC80.CRT install.json lua5.1.dll.manifest lua51.dll manifest.json bin2c5.1.exe liblua5.1.a lua5.1.exe lua51.dll.manifest wlua5.1.exe include lua5.1.dll lua5.1.exe.manifest luac5.1.exe wlua5.1.exe.manifest :lua 调用的是: D:\Scoop\apps\neovim\current\bin>ls dbghelp.dll lua51.dll nvim.exe platforms win32yank.exe xxd.exe scoop install luajit D:\Scoop\apps\luajit/..   2.1.1762795099-1   current ➛ 2.1.1762795099-1   bin  lua51.dll  luajit  luajit-2.1.1762795099.exe  luajit.exe   include/luajit-2.1  lauxlib.h  lua.h  lua.hpp  luaconf.h  luajit.h  lualib.h   lib   share  install.json  manifest.json D:\Scoop\apps\luarocks\current\config.lua 为: lua_interpreter = "D:/Scoop/apps/luajit/current/bin/luajit.exe" lua_version = "5.1" rocks_trees = { "D:/Scoop/apps/luarocks/current/rocks" } variables = { LUA = "D:/Scoop/apps/luajit/current/bin/luajit.exe", LUA_BINDIR = "D:/Scoop/apps/luajit/current/bin", LUA_INCDIR = "D:/Scoop/apps/luajit/current/include/luajit-2.1", LUA_DIR = "D:/Scoop/apps/luajit/current/bin" } luarocks install luafilesystem --force :=require('lfs') 就会看到: { _COPYRIGHT = "Copyright (C) 2003-2017 Kepler Project", _DESCRIPTION = "LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution", _VERSION = "LuaFileSystem 1.8.0", attributes = <function 1>, chdir = <function 2>, currentdir = <function 3>, dir = <function 4>, link = <function 5>, lock = <function 6>, lock_dir = <function 7>, mkdir = <function 8>, rmdir = <function 9>, setmode = <function 10>, symlinkattributes = <function 11>, touch = <function 12>, unlock = <function 13> } Neovim 终端中使用 diff --git a/lua/plug/rocks/init.lua b/lua/plug/rocks/init.lua index e336791..58f391d 100644 --- a/lua/plug/rocks/init.lua +++ b/lua/plug/rocks/init.lua @@ -71,6 +71,8 @@ function M.enable() .. luarocks_config.deploy_lib_dir .. '\\?.' .. luarocks_config.external_lib_extension + vim.env.LUA_PATH = package.path + vim.env.LUA_CPATH = package.cpath end) if ok then enabled = true D:\wsdjeg\my-blog>lua Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio > print(require('lfs')) table: 00000000004DCAC0 > D:\wsdjeg\my-blog>luajit LuaJIT 2.1.1762795099 -- Copyright (C) 2005-2025 Mike Pall. https://luajit.org/ JIT: ON SSE3 SSE4.1 BMI2 fold cse dce fwd dse narrow loop abc sink fuse > print(require('lfs')) table: 0x01f72937bc70 > D:\wsdjeg\my-blog>echo print(require("lfs")) | nvim -l - table: 0x01dcba84a148

2025/11/27
articleCard.readMore

Neovim 窗口 API 参数 noautocmd 测试

最近在修改 notify.nvim 插件源码的时候有这么一段: local win_config = { -- ..... } if not vim.api.nvim_buf_is_valid(buf) then buf = vim.api.nvim_create_buf(false, true) end if not vim.api.nvim_win_is_valid(win) then win_config.noautocmd = true win = vim.api.nvim_open_win(buf, false, win_config) else vim.api.nvim_win_set_config(win, win_config) end nvim_open_win 和 nvim_win_set_config 两个函数所接受的 win_opt 选项是有区别的,已存在的窗口使用后者设置时不能够传入 noautocmd 选项(neovim#36409)。 'noautocmd' cannot be used with existing windows noautocmd 到底禁用了哪些事件,以及禁用的时机时什么呢? 以下为测试脚本: local buf = vim.api.nvim_create_buf(true, false) local log = require('logger').derive('t_no') local aug = vim.api.nvim_create_augroup('test_noautocmd', { clear = true }) vim.api.nvim_create_autocmd( { 'WinEnter', 'BufWinEnter', 'BufEnter', 'WinLeave', 'TextChangedI' }, { pattern = { '*' }, group = aug, callback = function(ev) log.info(ev.event) end, } ) vim.api.nvim_open_win(buf, true, { split = 'above', noautocmd = true }) -- [ 20:43:20:664 ] [ Info ] [ t_no ] TextChangedI -- [ 20:43:23:092 ] [ Info ] [ t_no ] WinLeave -- [ 20:43:23:093 ] [ Info ] [ t_no ] WinEnter -- [ 20:43:23:094 ] [ Info ] [ t_no ] BufEnter local buf = vim.api.nvim_create_buf(true, false) local log = require('logger').derive('t_no') local aug = vim.api.nvim_create_augroup('test_noautocmd', { clear = true }) vim.api.nvim_create_autocmd( { 'WinEnter', 'BufWinEnter', 'BufEnter', 'WinLeave', 'TextChangedI' }, { pattern = { '*' }, group = aug, callback = function(ev) log.info(ev.event) end, } ) vim.api.nvim_open_win(buf, true, { split = 'above', noautocmd = false }) -- [ 20:44:50:454 ] [ Info ] [ t_no ] WinLeave -- [ 20:44:50:455 ] [ Info ] [ t_no ] WinEnter -- [ 20:44:50:456 ] [ Info ] [ t_no ] BufEnter -- [ 20:44:50:456 ] [ Info ] [ t_no ] BufWinEnter -- [ 20:44:51:279 ] [ Info ] [ t_no ] TextChangedI -- [ 20:44:52:045 ] [ Info ] [ t_no ] WinLeave -- [ 20:44:52:046 ] [ Info ] [ t_no ] WinEnter -- [ 20:44:52:048 ] [ Info ] [ t_no ] BufEnter Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Error *err) FUNC_API_SINCE(6) FUNC_API_TEXTLOCK_ALLOW_CMDWIN { #define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key) buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { return 0; } if ((cmdwin_type != 0 && enter) || buf == cmdwin_buf) { api_set_error(err, kErrorTypeException, "%s", e_cmdwin); return 0; } WinConfig fconfig = WIN_CONFIG_INIT; if (!parse_win_config(NULL, config, &fconfig, false, err)) { return 0; } bool is_split = HAS_KEY_X(config, split) || HAS_KEY_X(config, vertical); Window rv = 0; if (fconfig.noautocmd) { block_autocmds(); } win_T *wp = NULL; tabpage_T *tp = curtab; assert(curwin != NULL); win_T *parent = config->win == 0 ? curwin : NULL; if (config->win > 0) { parent = find_window_by_handle(fconfig.window, err); if (!parent) { // find_window_by_handle has already set the error goto cleanup; } else if (is_split && parent->w_floating) { api_set_error(err, kErrorTypeException, "Cannot split a floating window"); goto cleanup; } tp = win_find_tabpage(parent); } if (is_split) { if (!check_split_disallowed_err(parent ? parent : curwin, err)) { goto cleanup; // error already set } if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) { if (config->vertical) { fconfig.split = p_spr ? kWinSplitRight : kWinSplitLeft; } else { fconfig.split = p_sb ? kWinSplitBelow : kWinSplitAbove; } } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; int size = (flags & WSP_VERT) ? fconfig.width : fconfig.height; TRY_WRAP(err, { if (parent == NULL || parent == curwin) { wp = win_split_ins(size, flags, NULL, 0, NULL); } else { switchwin_T switchwin; // `parent` is valid in `tp`, so switch_win should not fail. const int result = switch_win(&switchwin, parent, tp, true); assert(result == OK); (void)result; wp = win_split_ins(size, flags, NULL, 0, NULL); restore_win(&switchwin, true); } }); if (wp) { wp->w_config = fconfig; if (size > 0) { // Without room for the requested size, window sizes may have been equalized instead. // If the size differs from what was requested, try to set it again now. if ((flags & WSP_VERT) && wp->w_width != size) { win_setwidth_win(size, wp); } else if (!(flags & WSP_VERT) && wp->w_height != size) { win_setheight_win(size, wp); } } } } else { if (!check_split_disallowed_err(curwin, err)) { goto cleanup; // error already set } wp = win_new_float(NULL, false, fconfig, err); } if (!wp) { if (!ERROR_SET(err)) { api_set_error(err, kErrorTypeException, "Failed to create window"); } goto cleanup; } if (fconfig._cmdline_offset < INT_MAX) { cmdline_win = wp; } // Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each // event. In each case, `wp` should already be valid in `tp`, so switch_win should not fail. // Also, autocommands may free the `buf` to switch to, so store a bufref to check. bufref_T bufref; set_bufref(&bufref, buf); if (!fconfig.noautocmd) { switchwin_T switchwin; const int result = switch_win_noblock(&switchwin, wp, tp, true); assert(result == OK); (void)result; if (apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf)) { tp = win_find_tabpage(wp); } restore_win_noblock(&switchwin, true); } if (tp && enter) { goto_tabpage_win(tp, wp); tp = win_find_tabpage(wp); } if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) { // win_set_buf temporarily makes `wp` the curwin to set the buffer. // If not entering `wp`, block Enter and Leave events. (cringe) const bool au_no_enter_leave = curwin != wp && !fconfig.noautocmd; if (au_no_enter_leave) { autocmd_no_enter++; autocmd_no_leave++; } win_set_buf(wp, buf, err); if (!fconfig.noautocmd) { tp = win_find_tabpage(wp); } if (au_no_enter_leave) { autocmd_no_enter--; autocmd_no_leave--; } } if (!tp) { api_set_error(err, kErrorTypeException, "Window was closed immediately"); goto cleanup; } if (fconfig.style == kWinStyleMinimal) { win_set_minimal_style(wp); didset_window_options(wp, true); } rv = wp->handle; cleanup: if (fconfig.noautocmd) { unblock_autocmds(); } return rv; #undef HAS_KEY_X } nvim_open_win 这个函数调用内起作用,在最后的时候使用 unblock_autocmds 又恢复的事件的响应。

2025/11/12
articleCard.readMore

Neovim 模糊搜索插件 picker.nvim

安装和配置 基本使用 内置 source 其他插件 source 如何自定义拓展 picker.nvim 安装和配置 nvim-plug: require('plug').add({ { 'wsdjeg/picker.nvim', config = function() require('picker').setup({ filter = { ignorecase = false, -- ignorecase (boolean): defaults to false }, window = { width = 0.8, -- set picker screen width, default is 0.8 * vim.o.columns height = 0.8, col = 0.1, row = 0.1, current_icon = '>', current_icon_hl = 'CursorLine', enable_preview = false, preview_timeout = 500, }, highlight = { matched = 'Tag', }, prompt = { position = 'bottom', -- set prompt position, bottom or top icon = '>', icon_hl = 'Error', insert_timeout = 100, title = true, -- display/hide source name }, mappings = { close = '<Esc>', next_item = '<Tab>', previous_item = '<S-Tab>', open_item = '<Enter>', toggle_preview = '<C-p>', }, }) end, }, }) nvim-config/plugins/picker.lua, 在我的配置里,我使用 picker.nvim 接管了 Neovim 默认的 vim.ui.select 函数。 基本使用 :Picker 命令看看目前支持的源(sources) :Picker source_name 指定打开某个源进行匹配搜索。 --input 指定默认初始化输入的内容 --input=<cword> 指定以光标下的词作为默认输入内容。 key binding description Tab next item S-Tab previous item Enter default action Esc close picker action() 函数返回定义。 内置 source source description buffers listed buffers buftags ctags outline for current buffer cmd_history results from :history : colorscheme all colorschemes files files in current dir help_tags neovim help tags source highlights highlight group source jumps jump list lines lines in current buffer loclist location list source lsp_document_symbols document symbols result from lsp client lsp_references lsp references lsp_workspace_symbols workspace symbols marks marks list picker_config picker config source qflist quickfix source registers registers context 其他插件 source source description mru most recent used files, need mru.nvim project project history, need rooter.nvim bookmarks all bookmarks, need bookmarks.nvim zettelkasten zettelkasten notes source from zettelkasten.nvim zettelkasten_tags zettelkasten tags source from zettelkasten.nvim git-branch git branch source from git.nvim music-player music-player source form music-player.nvim plug plugins source for nvim-plug async_files async files source, require job.nvim 如何自定义拓展 local source = {} ---@return PickerItem[] function source.get() end ---@param entry PickerItem function source.default_action(entry) end --- 只有需要使用到预览窗口,才需要定义 preview 函数。 source.preview_win = true function source.preview(entry, win, buf) end

2025/10/31
articleCard.readMore

Github 仓库自动 release

因为仓库比较多(比较懒),不想每次都手动发布版本,写项目版本更新的内容,因此给自己的每一个仓库引入了 googleapis/release-please-action 在仓库的根目录新建文件:.github/workflows/release-please.yml on: push: branches: - master permissions: issues: write contents: write pull-requests: write name: release-please jobs: release-please: runs-on: ubuntu-latest steps: - uses: googleapis/release-please-action@v4 with: # this assumes that you have created a personal access token # (PAT) and configured it as a GitHub action secret named # `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important). token: $ # this is a built-in strategy in release-please, see "Action Inputs" # for more options release-type: simple Conventional Commits。

2025/9/22
articleCard.readMore

为什么有那么多人讨厌清朝?

最近有点犯书荒,在搜索历史类小说的时候发现隋、唐、宋、明等朝代的小说较多,相关影视剧也是这样。相反元、清朝代的就非常少, 记得还是上学期间有一些清朝宫廷剧,往后几乎很少看到。 可能在汉人的眼里,清朝实际上是侵略者统治的朝代,和日本当年类似。不同的是现在的中国是满族也在期内而且人口还不少。为了民族统一,国务院甚至发文禁止使用满清一词。 知乎上有这样一个问题,为什么有那么多人讨厌清朝?其中一个答案中提到了很多满清犯下恶。 畿南之屠、潼关之屠、扬州十日、嘉定三屠、昆山之屠、嘉兴之屠、江阴八十一日、常熟之屠、四川之屠、金华之屠、南昌之屠、湘潭之屠、南雄之屠、汾州之屠、大同之屠、广州之屠、潮州之屠。凡此种种,皆为历史记载有据可查之大屠也。 1644年,是中国历史上天翻地覆的一年,就在这一年清军入主中原,无数汉人的噩梦就此开始。清军入住中原后,强迫汉人剃发易服 ,这对于汉人来说以一种莫大的侮辱,各地人民纷纷起义反抗清朝,清朝对此采取屠城政策来强行推行“剃发易服”政策,于是乎一场场惨无人道大屠杀在中华大地上轮番上演。 1.畿南之屠 清朝的建立,源于一场惨绝人寰的大屠杀,清军入关就是外族入侵,为了巩固自己的地位与文化,就通过杀戮屠城的方式威慑全国,清军入关前后不断的屠杀汉人,前后屠虐了一百多年,死亡人数在四千万至八千万之间,史称为满清大屠杀。 顺治元年 (1644年)五月,清朝睿亲王多尔衮 在北直隶三河、昌平、良乡等地进行的屠城,五千人被残杀。 2.潼关之屠 明崇祯十七年(公元1644年正月十三日)满清“豫亲王”爱新觉罗多铎在陕西潼关进行惨绝人寰的大规模屠城,手无寸铁的士兵被多铎尽数屠杀,清军并大肆屠城,7000汉人被清军杀死,在历史上有一句对这件事的说法“潼关之屠七千“(就是屠夫多铎在潼关杀害那么多无辜的汉人) 顺治元年(1644年)正月十三日,清朝“豫亲王”多铎在陕西潼关地进行的屠城,七千人被残杀。 3.扬州十日 清军入关之后,满族人为了统治中国,就对汉人颁布了毫无人性的剃发令、文字狱,以及各个城镇的威慑屠杀,最惨绝人寰的要数“扬州十日”以及“嘉定三屠”,其中扬州十日是最为惨痛的,清军破城后整整屠杀了十天,被屠杀的汉人达到了80万,要比南京大屠杀多50万人。 顺治二年、南明弘光元年(1645年5月20日)。清军攻破扬州,开始屠杀城内的平民百姓。史载:“诸妇女长索系颈,累累如贯珠,一步一跌,遍身泥土;满地皆婴儿,或衬马蹄,或藉人足,肝脑涂地,泣声盈野。”“初四日,天始霁。道路积尸既经积雨暴涨,而青皮如蒙鼓,血肉内溃。秽臭逼人,复经日炙,其气愈甚。前后左右,处处焚灼。室中氤氲,结成如雾,腥闻百里。”后来由城内僧人收殓的尸体就超过了八十万具。 4.嘉定三屠 “嘉定三屠”是指一场大屠杀,一场灭绝人性的全副武装的清兵对手无寸铁的汉人进行的屠杀,清朝顺治二年时清军攻破嘉定,并颁布剃发令但是当地的百姓违抗命令拒绝剃发,此事激怒了清军于是下令屠城屠城的命令,共下达了三次,屠城进行了三回,所以称“三屠”,而死亡人数更是达到了10万之众。 顺治二年、南明弘光元年(1645年)在清军攻破嘉定后,清军将领李成栋三次下令对城中平民进行大屠杀。在屠城过程中,城中百姓或者悬梁自杀或者投井和跳河,被砍断手和脚的百姓在地上挣扎着。清军将一大部分逃生的百姓赶到河边,然后将他们赶进河中纷纷淹死,河里的水都不能流动了。 5.昆山之屠 顺治二年、南明弘光元年(1645年)昆山县人民杀该县清委知县阎茂才,起兵反清。七月初六,清军破城,随即屠城,士民死难者达数万人。清军屠昆山的时候,有妇女千人藏匿在昆山顶上。有个小孩忍不住哭出声来,被清军发现,于是大肆屠杀这些妇女。 6.嘉兴之屠 顺治二年、南明弘光元年(1645年)闰六月二十六日,浙江布政使司隶嘉兴府为反抗清军暴行,嘉兴民众揭竿而起,乡的明翰林学士屠象美、明兵科给事中李毓新主其事,时降清的明嘉兴总兵陈梧反时任大将军指挥义师,前吏部郎中钱棅助饷。二十六日城陷,逃不出的居民除大批年轻妇女被清军掳掠和一些僧人幸免外,几乎全遭屠杀。按当时人口来推,可能约五十万余人遇难。 7.江阴八十一日 顺治二年、南明弘光元年(1645年)清朝颁布剃发令后,江阴人民举行了反清起义,进行反清斗争。清廷先后调动24万军队攻城,江阴人民浴血奋战,守城八十一天,击毙清三王十八将,清军死伤过十万。但终因力量悬殊,粮食罄尽,守城者全部壮烈牺牲。城破后遭到清军血腥屠杀,繁华的街市尽为废墟。 8.四川大屠杀 从顺治三年(1646年)至康熙初期,清军开始侵入四川,在四川各地进行大屠杀。顺治四年(1647年)多尔衮、孝庄采取了彻底屠杀的办法作为报复,公开发布告示,宣称:民贼相混,玉石难分,或屠全城,或屠男而留女。前后十多年里共有500多万人被清军屠杀。 9.常熟之屠 顺治二年、南明弘光元年(1645年)八月到九月,清军先后占领苏州和南直隶常熟之后的纵兵焚烧杀掠。在常熟大屠杀中被屠杀的百姓无法计算,沿河沿岸都是人头。 10.金华之屠 顺治三年、南明隆武二年(1646年)七月十六日,清朝“贝勒”博洛在攻占浙江金华府进行的开始大规模屠城。大约有五万人被屠杀。 11.泾县之屠 顺治三年、南明隆武二年(1646年)八月十七日,清朝“提督”张天禄、“池州总兵”于永绶在南直隶泾县、徽州、绩溪县等地进行的一次大规模屠城。大约有五万人被屠杀。 12.赣州之屠 顺治三年、南明隆武二年(1646年)十月初四日,清朝“江西提督”金声桓、“总兵”柯永盛在江西赣州府进行的一次大规模屠城。大约有二十万人被屠杀。 13.同安之屠 顺治四年(1647年)清军攻克福建厦门和同安县,然后屠城。福建同安县屠城死难5万余人,梵天寺住持无疑和尚收尸合葬于寺东北一里之地,建亭“无祠亭”,墓碑上则刻“万善同归所”。 14.平海之屠 顺治四年、南明永历元年(1647年)七月,清军在福建平海卫进行大规模屠城。被屠杀的无辜百姓具体人数不详。 15.南昌之屠 顺治五年(1648年)清军包围南昌。次年三月间,南昌城陷,清军屠城。八旗军把从南昌掠来的妇女分给各营,昼夜不停的轮奸。不久之后南昌被起义军夺了回来,但在顺治六年(1649年)清军再次占领江西南昌,下令进行屠杀。 16.邵武之屠 顺治五年、南明永历二年(1648年)五月,清朝“福建左路总兵”王之纲在福建邵武县进行屠城,被屠杀的无辜百姓人数不详。 17.湘潭之屠 顺治六年(1649年)正月二十一日清军攻入湖南湘潭和沅州(今芷江),南明督师何腾蛟被俘。清郑亲王济尔哈朗下令在湘潭屠城,湘潭城中百姓几乎全被杀光,城中不满百人。 18.同安之屠 顺治五年、南明永历二年(1648年)八月十六日,清朝“靖南将军”陈泰、“浙闽总督”陈锦、“福建提督”赵国祚在福建同安县进行大屠杀,大约有五万人被屠杀。 19.南雄之屠 顺治六年(1649年)农历十二月二十九日,清军抵达南雄,年三十除夕晚上清军放火焚烧鼓楼,趁明军慌乱救火之际,攻入城内。对南雄县民大肆屠杀,“大清平、靖二藩克雄城,民尽屠戮,十存二三。 20.信丰之屠 顺治六年、南明永历三年(1649年)三月初一日,清朝“梅勒章京”胶商攻克广东信丰县,进行大规模屠城。 21.蒲城之屠 顺治六年、南明永历三年(1649年)四月初五日,清朝“固山额真”李国翰攻克陕西蒲城县然后进行大规模屠城。大约有一万人被屠杀。 22.曹州之屠 顺治六年、南明永历三年(1649年)十月初四日,清朝梅勒章京赖恼、沂州总兵佟养量、临清总兵宜永贵等攻克山东州曹州,进行屠城。被屠杀的无辜居民人数不详。 23.汾州之屠 顺治六年、南明永历三年(1649年)九月至十一月,清朝“端重亲王”博洛、“和硕亲王”满达海等在山西汾州、太谷县、泌州、泽州等地进行规模屠城。大约有四十万人被屠杀。 24.大同之屠 顺治六年(1649年)清军在大同屠杀后,全城只剩下5个重案犯。清朝派来的大同知府,上书顺治帝,称既然没有了苦主,就可以释放这5个人了。这份奏折,至今保存在第一历史档案馆。 25.广州大屠杀 顺治七年(1650年)尚可喜打带领清军清军攻广州,围城十个多月后广州城破。清军入城之后开始屠城,史书记载: ”可喜屠广州,孑遗无留;逸出城者,挤之海中。“ 死难十万至七十万人。 26.潮州之屠 顺治十年(1653年)清军占领广东的潮州和南雄,清军之后进行下令屠杀,“纵兵屠掠,遗骸十余万”,“癸巳,郡城破,横尸遍野……收遗骸十余万,作普同塔于葫芦山”。 揭阳县观音堂海德和尚等收尸聚焚于西湖山,将骨灰葬在西湖南岩。福建同安县屠城死难五万余人,梵天寺主持释无疑收尸合葬于寺东北一里之地,建亭“无祠亭“,墓碑上则刻“万善同归所”。南雄县民也遭到大肆屠杀,“大清平、靖二藩克雄城,民尽屠戮,十存二三。” “家家燕子巢空林,伏尸如山莽充斥……死者无头生被掳,有头还与无头伍。血泚焦土掩红颜,孤孩尚探娘怀乳。” 27.永昌之屠 顺治十六年、南明永历十三年(1659年)闰三月,清朝“征南将军”赵布泰、“提督”线国安等在云南永昌府等地进行的一次大规模屠城。 经过清军的大屠杀后,明朝年间有5000多万人,到来清朝康熙年间只剩1063万,少了2000万人。这其中虽然也有死于瘟疫和明朝内战的,但大部分还是还是被清军所杀。 以史为鉴,可知兴替,可警醒世人,不再重蹈覆辙,再遭如此国耻国难! 再看看我大清: 首先,清朝自己发布过大量屠杀文告,其中最著名的,是清朝官方资料《清世祖实录》 卷十七 顺治二年六月丙寅中的记录: “自今布告之后,京城内外,直隶各省,限旬日尽行剃完。若规避惜发,巧词争辩,决不轻贷”。 并宣称:“所过州县地方,有能削发投顺,开城纳款,即与爵禄,世守富贵。如有抗拒不遵,大兵一到,玉石俱焚,尽行屠戮。” 清朝在四川1649年的另一份文告,口气也非常类似:“民贼相混,玉石难分。或屠全城,或屠男而留女”。 清朝另一份官方史料,《东华录》 卷五顺治元年条,则记载:“不随本朝制度剃发易衣冠者,杀无赦。” 镇江知府告示则是:“一人不剃发全家斩,一家不剃全村斩”,并将反抗者的人头,集中起来恐吓人民。 顺治二年(1645年),江宁巡抚土国宝宣布:“剃发、改装是新朝第一严令,通行天下,法在必行者,不论绅士军民人等,留头不留发,留发不留头!南山可移,此令不可动! ” 在以上清朝官方的宣告中,充斥了大量“屠全城”、“尽行屠戮”、“杀无赦”、“全家斩”、“全村斩”的凶恶威胁。而下面列举的资料,则证明,清朝的公开威胁,绝不仅仅停留在口头。 辽东屠杀 天命九年正月,努尔哈赤下九次汗谕,清查所谓“无谷之人”,并谕令八旗官兵“应将无谷之人视为仇敌”,“捕之送来”,最后于正月二十七日下令:“杀了从各处查出送来之无谷之尼堪”。 天命十年十月初三日,努尔哈赤指责汉民“窝藏奸细,接受札付,叛逃不绝”,命令八旗贝勒和总兵官以下备御以上官将,带领士卒对村庄的汉人, “分路去,逢村堡,即下马斩杀”。 “时奴贼既得辽阳,辽东八站军民不乐从胡者,多至江边…… 其后,贼大至,义民不肯剃头者,皆投鸭水(鸭绿江)以死。”朝鲜 《朝鲜王朝实录》 掠夺虐杀汉族奴隶 崇祯十一年冬至十二年春,清军在畿辅 、山东一带掠去汉民四十六万二千三百馀人,崇祯十五年冬至十六年夏,清军又“俘获人民三十六万九千名口”。(《清太宗实录》 )满清入关后,继续劫掠人口。顺治二年八月辛巳日谕兵部“俘获人口,照例给赏登城被伤之人。”(《清世祖实录》) 汉人奴隶遭受非人的虐待,大量自杀。康熙初年,“八旗家丁每岁以自尽报部者不下二千人”(《清史稿》 ),仅自杀的汉人,在满清入关前后几十年间,就不下10万人。被虐待致死的,更不在少数。 由于满清的疯狂虐待,大量汉人奴隶逃亡,“只此数月之间,逃人已几数万。”(《清世祖实录》) 满清统治者为了制止逃往,强化其1626年颁布的《逃人法》 ,顺治帝颁订:“有隐匿逃人者斩,其邻佑及十家长、百家长不行举首,地方官不能觉察者,俱为连坐”。顺治六年又改为“隐匿逃人者免死,流徙”、“再行申饬,自此谕颁发之日为始,凡章奏中再有干涉逃人者,定置重罪,决不轻恕”。(《清世祖实录》) 华北 畿南之屠 畿南之屠指的是公元1644年(明崇祯十七年五月),满清睿亲王多尔衮在北直隶三河、昌平、良乡等地进行的一次大规模屠城。根据《清世祖实录》记载,1644年五月,大顺军西撤。满清军队占领畿南地区,强令汉族剃发易服 。当地汉族居民纷纷揭竿而起,反对满清统治。满清朝廷派出军队弹压,对起义者和居民大肆屠戮,连老幼亦不能幸免。大约有五千人被屠杀。史称畿南之屠。 曹州之屠 曹州之屠指的是公元1649年(南明永历三年十月初四日),满清梅勒章京 赖恼、沂州总兵佟养量、临清总兵宜永贵等在山东州曹州进行的一次大规模屠城。根据《曹州志》和《重修大名府志》等记载,1649年原明东平侯刘泽清 密谋与亲信李化鲸反正归明。李化鲸部义军占领鲁西南州县,后为清军三省大军围剿,战败被俘,刘、李两人遇害。满清军入城后屠杀无遗。被屠杀的无辜居民人数不详。史称曹州之屠。 大同屠杀 大同之屠指的是公元1649年(南明永历三年八月二十九日),满清“皇父摄政王”多尔衮、“英亲王”阿济格 、“敬谨亲王”尼堪、“端重亲王”博洛 、“承泽亲王”硕塞等在山西大同府、朔州、浑源县等地进行的一次大规模屠城。 根据《清世祖实录》记载,1648年(永历二年),满清“大同总兵”姜瓖 反正归明,义军迅速占领晋西北、晋南广大地区,直接威胁满清朝廷。清廷调取华北地区绝大部分可以调派的军队进剿,历时将近一年才复占山西全境。姜瓖被叛徒汉奸杀害,大同城破,全城官吏兵民被屠杀。被屠杀的无辜居民人数不详。史称“大同之屠”。 清军实施大同大屠杀后,全城只剩下5个重案犯。满清派来的大同知府,上书顺治,称既然没有了苦主,就可以释放这5个人了。这份奏折,至今保存在第一历史档案馆。 参考文献:“清人所至,无不狼藉,尸则无完肤,人则无完发,烧杀抢掠,乃贼人之便饭矣。” 朔州之屠 朔州之屠指的是公元1649年(南明永历三年八月二十九日),满清“皇父摄政王”多尔衮、“英亲王”阿济格、“敬谨亲王”尼堪、“端重亲王”博洛、“承泽亲王”硕塞等在山西大同府、朔州、浑源县等地进行的一次大规模屠城。 关于朔州的这次屠城,根据《清世祖实录》记载,“顺治六年,大同、朔州、浑源三城,已经王师屠戮,人民不存”!《朔州志》 :“城破,悉遭屠戮”,“王师致讨,大兵临城,玉石俱焚,家破人亡……荡然一空”。 汾州之屠 汾州之屠指的是公元1649年(南明永历三年九月至十一月),满清“端重亲王”博洛、“和硕亲王”满达海等在山西汾州、太谷县、泌州、泽州等地进行的一次大规模屠城。 根据《清世祖实录》、《泽州志》 和《明清史料》记载,1649年大同失陷后,满清派出军队对晋南地区开展大扫荡,在各地实施大屠杀。明朝巡抚姜建勋 、监军道何守忠等殉国。大约有四十万人被屠杀。史称汾州之屠。 江淮 泾县之屠 泾县之屠是顺治二年(1645)原明代泾县县令尹民兴流寓泾县,与本县县城诸生赵初浣等率众拒清,据城坚守。清军提督张天禄 于八月十六日黎明,统铁骑百余,据东山发炮,城中屋瓦皆震,尹民兴走脱,赵初浣等战死。城陷,男女少长多罹难,仅遗民九十余人。县治官厅公署悉毁于兵火。史称泾县之屠或者乙酉之难。 泾县之屠指的是公元1646年(南明隆武二年八月十七日前后),满清“提督”张天禄、“池州总兵”于永绶 在南直隶泾县、徽州、绩溪县等地进行的一次大规模屠城。 根据《明清史料》等记载,1646年皖南人民不能容忍满清政权的剃发易服而发动起义,反抗满清统治。义军攻略皖南诸县后,因兵力不足,为满清军队所败。满清军在皖南地区展开疯狂的报复性大屠杀,其中以泾县特为尤甚。大约有五万人被屠杀。史称泾县之屠或乙酉之难。 江南 扬州十日 南明弘光元年,清朝顺治二年(1645年)发生在清军攻破扬州城后对城中平民进行大屠杀的事件。由于当时南明将领史可法 对清军的殊死抵抗,在同年四月廿五日(5月20日),清军攻占扬州后,当时大雨倾盆,多铎宣布在扬州城内进行了屠杀。当时的幸存者王秀楚 所著《扬州十日记》中记载屠杀共持续十日,故名“扬州十日”。 清军攻破扬州城后进行了为期十天的大肆屠杀,史载:“诸妇女长索系颈,累累如贯珠,一步一跌,遍身泥土;满地皆婴儿,或衬马蹄,或藉人足,肝脑涂地,泣声盈野。”“初四日,天始霁。道路积尸既经积雨暴涨,而青皮如蒙鼓,血肉内溃。秽臭逼人,复经日炙,其气愈甚。前后左右,处处焚灼。室中氤氲,结成如雾,腥闻百里。”后来由城内僧人收殓的尸体就超过了80万具。 嘉兴大屠杀 南明弘光元年(1645年)闰六月二十六日,浙江布政使司 隶嘉兴府为反抗清军暴行,嘉兴民众揭竿而起,乡的明翰林学士屠象美、明兵科给事中李毓新主其事,降清的明嘉兴总兵陈梧 反正任大将军指挥义师,前吏部郎中钱棅助饷。二十六日城陷,逃不出的居民除大批年轻妇女被清军掳掠和一些僧人幸免外,几乎全遭屠杀。按当时人口来推,可能约500,000余人遇难。 江阴八十一日 江阴八十一日是指1645年夏江阴人民为抵制剃发令,在江阴典史:阎应元 、陈明遇、冯厚敦等人领导下进行的斗争。因为前后长达81天之久,故被称为“江阴八十一日”。(参考百家讲坛纪连海 先生的江阴八十一日讲座) 此役,10万江阴百姓面对24万清军铁骑,两百多门红衣大炮,血战孤城,抗清81日,击毙清军7万5千余人,亲王3名,大将军18名,最后城破,屠城,无一人降,城内死者九万七千余人,城外死者七万五千余人。阎应元、陈明遇、冯厚敦被后世称为“江阴抗清三公”。后世传纪对江阴的评价:“有明之季,士林无羞恶之心。居高官、享重名者,以蒙面乞降为得意;而封疆大帅,无不反戈内向。独阎、陈二典史乃于一城见义。向使守京口如是,则江南不至拱手献人矣。”此言甚当,沧江横流方显英雄本色,在各地望风披靡之时,阎应元以微末下吏凭借江阴百姓的支持,面对强敌,临危不惧,坚持了近三个月,击杀清寇数万人,重挫了清军锐气,钳制了清寇主力南下,推动了各地的抗清斗争。在城破以后,仍拚死巷战,“竟无一人降者”。 清初,扬州、嘉定、江阴等城,发源于老百姓的英勇抵抗,宁为玉碎不为瓦全,在华夏反侵略史上留下光彩夺目的一页。有章服之美称之华,有礼仪之大谓之夏。誓不与鞑子同流合污。 参考文献:“满城杀尽,然后封刀。……城中所存无几,躲在寺观塔上隐僻处及僧印白等,共计大小五十三人。是役也,守城八十一日,城内死者九万七千馀人,城外死者七万五千馀人。”《江阴城守纪》 嘉定三屠 嘉定三屠,指1645年(南明弘光元年,清朝顺治二年)清军攻破嘉定后,清军将领李成栋 三次下令对城中平民进行大屠杀的事件。 清军颁布剃发令,嘉定百姓拒不从命。乡绅侯峒曾 带领嘉定绅民起义反清,清吴淞 总兵李成栋立即领兵五千来攻。 嘉定城城破,李成栋下令屠城,市民之中,悬梁者,投井者,投河者,血面者,断肢者,被砍未死手足犹动者,骨肉狼籍。妇女们惨遭强奸。如遇抵抗,军队就用长钉把抵抗妇女的双手钉在门板上,然后再肆行奸淫。大屠杀持续了一天,直到尸体堵塞了河流,大约有三万多人遇害。李成栋率军离开嘉定城。 但嘉定城的劫难仍然没有结束。李成栋大屠杀后的三四天,侥幸逃脱的嘉定的幸存者开始溜回城里。他们回城后在一个叫做朱瑛 的义士领导下,重新集结起来,共两千多人。朱瑛领导着幸存者们在这座残破的城市展开了一场反屠杀运动,处死了归降清军的汉奸和清军委派的官吏。 李成栋又领着军士直杀入城里,把许多还在睡梦中的居民杀个精光,积尸成丘,然后放火焚尸。清军杀得兴起,嘉定又惨遭“二屠”。 二十多天后,原来南明的一个名叫吴之番 的将军率余部猛攻嘉定城,周边民众也纷纷响应,杀得城内清兵大溃出逃。不久,李成栋整军反扑,把吴之番数百士兵砍杀殆尽,顺带又屠杀了近二万刚刚到嘉定避乱的民众,血流成渠,是为著名的“嘉定三屠”。 参考文献:“市民之中,悬梁者,投井者,投河者,血面者,断肢者,被砍未死手足犹动者,骨肉狼籍。” 清兵“悉从屋上奔驰,通行无阻。城内难民因街上砖石阻塞,不得逃生,皆纷纷投河死,水为之不流。”“日昼街坊当众奸淫。”有不从者,“用长钉钉其两手于板,仍逼淫之。”,“兵丁每遇一人,辄呼蛮子献宝,其入悉取腰缠奉之,意满方释。遇他兵,勒取如前。所献不多,辄砍三刀。至物尽则杀。”(《嘉定乙酉纪事》 ) 常熟大屠杀 “通衢小巷,桥畔河干,败屋眢井,皆积尸累累,通记不下五千馀人,而男女之被掳去者不计焉。”“沿塘树木,人头悬累累,皆全发乡民也。”(《海角遗编》) 昆山大屠杀 昆山之屠 的时间是是公元1645年(南明弘光元年七月初六日),昆山县人民杀该县清委知县阎茂才,起兵反清,引起满清“吴淞总兵”李成栋、“刑部侍郎”李延龄 在南直隶昆山县进行的一次大规模屠城。 根据《归庄年谱》、《顾炎武 年谱》和《研堂见闻杂记》记载,1645年满清军队进攻昆山县,昆山县绅民在原郧阳抚院王永祚 等人倡义下,起兵反清。顾炎武、归庄等爱国志士都积极参与义举。七月初六清军攻陷昆山后,大肆屠城。大约有四万人被屠杀。史称昆山之屠。 参考文献:“总计城中人被屠戮者十之四,沉河堕井投缳者十之二,被俘者十之二,以逸者十之一,藏匿幸免者十之一。”(《昆新两县续修合志》 卷五一兵纪),“杀戮一空,其逃出城门践溺死者,妇女、婴孩无算。昆山顶上僧寮中,匿妇女千人,小儿一声,搜戮殆尽,血流奔泻,如涧水暴下”!((研堂见闻杂记)) 金华之屠 金华之屠指的是公元1646年(南明隆武二年七月十六日),满清“贝勒”博洛在浙江金华府进行的一次大规模屠城。 根据《浙东记略》、《临安旬制记》、《金华府志》和《金华县志 》等记载,1646年五月,满清军队攻占浙东府县,南明钱塘江防线沦陷,鲁监国放弃绍兴,转进至海上。兴国公王之仁、大学士张国维 等义士杀身殉国。金华人民在督师大学士朱大典带领下据城而战,誓死不降。满清军队攻陷金华后,借口民不顺命,屠城。大约有五万人被屠杀。史称金华之屠。 赣州之屠 赣州之屠指的是公元1646年(南明隆武二年十月初四日),满清“江西提督”金声桓 、“总兵”柯永盛在江西赣州府进行的一次大规模屠城。 根据《赣州府志》 、《赣县志》、《行朝录》和《仿指南录》记载,1646年三月,满清军队从南昌南下,击破沿路明军。赣州府防守力量由各地援军组成,缺乏协同作战能力,不久后沦陷。满清军攻占赣州后,江西总督万元吉、武英殿大学士杨廷麟等与六千守城将士殉国,满清军屠城。大约有二十万人被屠杀。史称赣州之屠。 平海之屠 平海之屠指的是公元1647年(南明永历元年七月),清军在福建平海卫 进行的一次大规模屠城。 根据《明清史料》、《南明史》 等记载,1647年南明绍宗皇帝殉国后,遵奉鲁监国的义师在闽浙两地依然相当活跃。是年七月,南明同安伯杨耿领兵一度收复平海卫。满清援军赶到后,杨耿兵被迫撤退,平海卫百姓惨遭屠杀。被屠杀的无辜百姓具体人数不详。史称平海之屠。 邵武之屠 邵武之屠指的是公元1648年(南明永历二年四月),满清“福建左路总兵”王之纲 在福建邵武县进行的一次大规模屠城。 根据《郑成功档案史料选集》等记载,1648年江西金声桓、王得仁 反正归明后,福建义民举兵响应。义军攻占邵武县城后,复为满清兵所败,城中起而响应的绅民惨遭屠戮。被屠杀的无辜百姓人数不详。史称邵武之屠。 同安之屠 同安之屠指的是公元1648年(南明永历二年八月十六日),满清“靖南将军”陈泰、“浙闽总督”陈锦、“福建提督”赵国祚 在福建同安县进行的一次大规模屠城。 根据《郑成功档案史料选集》等记载,1648年李成栋反正后,闽南地区复归明朝旗下,但闽系将领多为排斥。是年七月满清军队进攻同安,郑成功援军因风向不利受阻。同安城破后,守城将士悉数杀身成仁,满清军队屠城,血流沟渠。大约有五万人被屠杀。史称同安之屠。 湘潭大屠杀 1649年正月二十一日清军攻入湘潭,南明督师何腾蛟 被俘。清郑亲王济尔哈朗下令屠城,“屠至二十六日封刀,二十九日方止”。湘潭城中百姓几乎全被杀光,城中不满百人。”(康熙三年《湘潭县志》 ) 南昌大屠杀 1648年,金声桓、王得仁在江西起兵抗清,七月初十清军包围南昌。次年三月间,南昌城陷,清军屠城。 “妇女各旗分取之,同营者迭嬲无昼夜。三伏溽炎,或旬月不得一盥拭。除所杀及道死、水死、自经死,而在营者亦十余万,所食牛豕皆沸汤微 ?集而已。饱食湿卧,自愿在营而死者,亦十七八。而先至之兵已各私载卤获连轲而下,所掠男女一并斤卖。其初有不愿死者,望城破或胜,庶几生还;至是知见掠转卖,长与乡里辞也,莫不悲号动天,奋身决赴。浮尸蔽江,天为厉霾。”(徐世溥《江变纪略》 ) 华南 信丰之屠 信丰之屠指的是公元1649年(南明永历三年三月初一日),满清“梅勒章京”胶商在广东信丰县进行的一次大规模屠城。 根据《西江志》等记载,1649年李成栋二次入赣,为满清军所败,退守信丰。满清军攻破城池,李成栋仓促撤退,在回师过程中阵亡。满清军入城后对城中居民滥加屠杀。被屠杀的无辜居民人数不详。史称信丰之屠。 南雄大屠杀 雄之屠指的是公元1649年(南明永历三年十二月三十日),满清“平南王”尚可喜 、“靖南王”耿继茂在广东南雄府进行的一次大规模屠城。 根据《岭表纪年》 、《南雄府志》记载,1649年清军从江西南下进攻广东,偷渡梅岭后,派遣间谍进入南雄放火并打开城门,满清军主力拥入。守城六千余名将士死战殉国,城内居民被屠戮殆尽。大约有两万人被屠杀。史称南雄之屠。 1649年农历十二月二十九日,清军抵达南雄,年三十除夕晚上清军放火焚烧鼓楼,趁明军慌乱救火之际,攻入城内。对南雄县民大肆屠杀,“大清平、靖二藩克雄城,民尽屠戮,十存二三。”(乾隆十八年《南雄县志》) 参考文献:“ 家家燕子巢空林,伏尸如山莽充斥。….死者无头生被掳,有头还与无头伍。血泚焦土掩红颜,孤孩尚探娘怀乳。(清军文书陈殿桂,《雄州店家歌》 ) 庚寅之劫(又称广州大屠杀) 庚寅之劫,指1650年(清顺治七年,南明永历四年,庚寅年)11月24日到12月5日清朝军队在广州的屠城暴行。 当年公历11月24日,清朝平南王尚可喜与靖南王耿继茂指挥的清军(汉军镶蓝旗)在围城近十个月后,经过惨烈的战斗,包括筑垒相逼,以楼车攻城,及动用荷兰炮手,终于攻破广州城,随后对据城死守的广州居民进行了长达十二天的大屠杀,据清代官方史载,这场屠城,斩“兵民万余”,又“追剿余众至海滨,溺死者无算”,不论男女老幼,一律残酷地杀死,死亡人数根据收尸的和尚统计至七十万。 广州市社会科学研究所认为“七十万人”显然不可信,因为明末广州府 十三县人口总共才40万人。另有意见认为当时广州人口约40万,而死难者约十万人或超过十万人。 参考文献:“甲申更姓,七年讨殛。何辜生民,再遭六极。血溅天街,蝼蚁聚食。饥鸟啄肠,飞上城北。北风牛溲,堆积髑髅。或如宝塔,或如山邱。五行共尽,无智无愚,无贵无贱,同为一区。”(《祭共冢文》 王鸣雷),“可喜屠广州,孑遗无留;逸出城者,挤之海中。”(倪在田《续明纪事本末》)《广州市宗教志》:“清顺治七年(1650年),清军攻广州,‘死难10万至70万人。’在东郊乌龙冈 真修和尚雇人收拾尸骸,‘聚而殓之,埋其馀烬’合葬立碑”。 意大利籍耶酥会士卫匡国(Martin Martini ,1614~1661)在《鞑靼战纪》中记述:“大屠杀从11月24日一直进行到12月5日。他们不论男女老幼一 律残酷地杀死,他们不说别的,只说:‘杀!杀死这些反叛的蛮子!”荷兰使臣约翰纽霍夫(John Nieuhoff)在其《在联合省的东印度公司出师中国鞑靼大汗皇帝朝廷》一书记述:“鞑靼全军入城之后,全城顿时是一片凄惨景象,每个士兵开始破坏,抢走一切可以到手的东西;妇女、儿童和老人哭声震天;从11月26日到12月15日,各处街道所听到的,全是拷打、杀戮反叛蛮子的声音;全城到处是哀号、屠杀、劫掠;凡有足够财力者,都不惜代价以赎命,然后逃脱这些惨无人道的屠夫之手。” 潮州之屠 潮州之屠指的是公元1653年(南明永历七年九月十四日),满清“靖南将军”哈哈木在广东潮州府进行的一次大规模屠城。 根据《平南王元功垂范》等记载,1653年九月,是年满清“潮州总兵”郝尚久 反正归明,响应李定国大军。李定国兵败西撤后,郝尚久势单力薄。满清军队在包围潮州一月有余之后,攻陷府城,郝尚久自杀殉国。满清军屠城,斩杀无算。大约有十万人被杀,史称潮州之屠。 参考文献:“纵兵屠掠,遗骸十余万”,揭阳县观音堂海德和尚等收尸聚焚于西湖山,将骨灰葬在西湖南岩。福建同安县屠城死难5万余人,梵天寺主持释无疑收尸合葬于寺东北一里之地,建亭“无祠亭”,墓碑上则刻“万善同归所 ”。 西南 四川大屠杀 清军入关后的1647年在四川公开发布告示,宣称:全城尽屠,或屠男而留女。把四川人杀光了以后,就把罪恶全部推给也杀了一点人的张献忠,还编造出张献忠杀人6个亿的历史第一大谎言!根据近年历史学者的研究,四川被害者不下300万,而被张献忠杀害的至多只有14万人,连同张献忠统制地区其它非正常死亡,最多只有30-40万人。更重要的是,在清军开始长达十几年的四川大屠杀前,张献忠已经死了。 南明隆武二年( 1646 年)至永历十九年( 1665年)十一月,张献忠阵亡,满清开始侵入四川。同年满清政府贴出公告:“民贼相混,玉石难分。或屠全城,或屠男而留女”。面对四川人民顽强不屈的抵抗,此后满清用了近20年时间始占领四川。1647年清将张德胜 攻入成都被杀后,相继攻伐四川的清军有高民瞻、吴三桂、李化龙等部。1660年,成都沦陷;1663年,重庆沦陷;1665年,南明下川东战役失利,全蜀沦陷。四川的反清活动被残忍地镇压后,满清“叔父摄政王”多尔衮、孝庄采取了彻底屠杀的办法作为报复,约5,400,000人遇难。最后到了“弥望千里,绝无人烟”的地步。后来清廷不得不迁移湖广的人口至四川(即有名的以湖广填四川 )。 永昌之屠 永昌之屠指的是公元1659年(南明永历十三年闰三月),满清“征南将军”赵布泰 、“提督”线国安等在云南永昌府等地进行的一次大规模屠城。 根据《清世祖实录》和《明清档案》 记载,1659年满清军队进入云南,分路攻打南明防线。满清军队沿途烧杀抢掠无恶不作,永昌一带周围百里无人烟。被屠杀的无辜居民人数不详。史称永昌之屠。 西北 潼关之屠 潼关之屠指的是公元1644年(明崇祯十七年正月十三日),满清“豫亲王”多铎在陕西潼关进行的一次大规模屠城。 根据《清世祖实录》和《潼关志》 记载,1644年正月十二日,李自成从潼关回援西安。留守潼关的大顺“巫山伯”马世耀 向满清军队诈降,但密信为满清所获。次日,多铎诈称举行宴会,将马部官兵解除武装,手无寸铁的战俘被尽数屠杀。 满清大屠杀 扬州十日 扬州十日记·译文 [明·王秀楚] 其他 禁止私塾 满洲国流亡政府 参考资料 2019年,公安部端掉了满遗复辟分裂组织“满洲复兴协和会”,抓获了18名妄图复辟清朝的骨干分子。也就是这件事情,让塔意识到所谓“清宫文化”和“辫子戏”的危害性有多大,从19年开始,广电也开始有意识的减少甚至封杀清宫剧的拍摄,曾经霸占市场二十余年的辫子戏到了人人喊打的地步,电视上可以看到很早之前拍摄的《雍正王朝》《康熙王朝》等古早电视剧,但19年之后几乎没有啥新出的电视剧了,最新的一个是2022年的《天下长河》 作者:上帝的左手 链接:https://www.zhihu.com/question/593861268/answer/1903376346786272890 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2025/9/3
articleCard.readMore

Lua 可变参数

参考文章 local function add(...) local l = 0 for k, v in ipairs({...}) do l = l + v end return l end print(add(1, 2, 3, 4)) -- 输出: -- 10 local function add(...) local l = 0 for k, v in ipairs({...}) do l = l + v end return l end print(add(1, 2, nil, 4)) -- 输出: -- 3 local function add(...) local l = 0 for i = 1, select('#', ...), 1 do l = l + (select(i, ...) or 0) end return l end print(add(1, 2, nil, 4)) -- 输出: -- 7 参考文章 Lua 正确处理可变参数

2025/8/24
articleCard.readMore

高效处理 Markdown 中的代码块

执行代码 格式化代码 context_filetype.vim code-runner.nvim format.nvim 执行代码 ~/.confg/nvim/ftplugin/markdown.lua vim.keymap.set('n', '<leader>lr', function() local cf = vim.fn['context_filetype#get']() if cf.filetype ~= 'markdown' then local runner = require('code-runner').get(cf.filetype) runner['usestdin'] = true runner['range'] = { cf['range'][1][1], cf['range'][2][1] } require('code-runner').open(runner) end end, { silent = true, buffer = true }) 格式化代码 vim.keymap.set('n', '<leader>bf', function() local cf = vim.fn['context_filetype#get']() if cf.filetype ~= 'markdown' then local command = vim.fn.printf( '%s,%sFormat! %s', cf.range[1][1], cf.range[2][1], cf.filetype ) vim.cmd(command) end end, { silent = true, buffer = true })

2025/8/24
articleCard.readMore

为什么停止维护 SpaceVim

项目的初衷 遇到的问题 停止维护的原因 归档即是新的开始 插件列表 插件管理器 nvim-plug 项目管理 rooter.nvim 异步代码检索 flygrep.nvim 历史文件管理 mru.nvim 代码格式化 format.nvim 版本控制 git.nvim 代码运行 code-runner.nvim 书签管理 bookmarks.nvim tags 管理 ctags.nvim 滚动侧栏 scrollbar.nvim 笔记插件 zettelkasten.nvim 日历插件 calendar.nvim 异步job管理 job.nvim 模糊搜索插件 picker.nvim 项目文件跳转 altf.nvim 按键弹窗 record-key.nvim 录屏 record-screen.nvim 项目的初衷 遇到的问题 git log 的话,不难发现, 很大一部分内容是做速度的优化。 甚至,我也使用 Lua 重写的相当一部分的内置插件,包括状态栏、标签栏、任务管理、项目管理、多光标、快捷键导航等等。 但是即便是使用 Lua 进行了重写,但是因为要兼容老版本的 Neovim 以及兼容 Vim,因此整体总的框架使用的是 Vim Script, 调用 Lua 插件的时候还是使用 VimL 去调用 Lua 的代码,例如: func! SpaceVim#test(...) lua require('task').start(require('spacevim').eval('a:000')) endf 停止维护的原因 想使用纯 Lua 来写 Neovim 配置(nvim-config),不再兼容 Vim,因为我也很少再使用 Vim 了。 不想做 Neovim 老版本的兼容支持 单个功能插件独立维护,便于直接使用,我把原先 spacevim 中内置的很多功能, 归档即是新的开始 插件列表 nvim-config。 以下是我自己制作并且日常使用的 Neovim 插件,欢迎尝试。 插件管理器 nvim-plug wsdjeg/nvim-plug 这是我参考 dein.nvim 以及 Vundle.vim 写的一个异步的插件管理器,已经实现了日常我对于插件管理器的要求: clone、pull、build 等操作都是多线程异步执行; 支持通过 autocmd、command、function 等方式触发延迟加载; 支持依赖管理、import 目录载入插件; 支持 luarocks; Vundle.vim, 以前使用 dein.nvim 时候,dein.nvim 是默认没有 UI 界面的,当时我写了一个 dein-ui.vim 用来展示插件安装的进度。 项目管理 rooter.nvim rooter.nvim 在使用 Vim 编辑代码文件时,比较常用的一个功能是根据当前文件的路径自动将当前目录切换到项目的根目录。 这个功能以前我使用的是 vim-rooter 这个插件。 而 rooter.nvim 正是使用 Lua 重新实现该功能的 Neovim 插件, 不同的是,该插件额外提供了一些模糊搜索插件拓展,可以模糊搜索历史打开过的项目。 此外,这个插件还支持设置 callback 函数,当切换项目时自动执行。 异步代码检索 flygrep.nvim flygrep.nvim 日常阅读代码过程中还是经常需要搜索代码的,flygrep。插件会异步调用rg,并实时展示搜索结果。 历史文件管理 mru.nvim mru.nvim 类似于 v:oldfiles,解决的问题是Windows系统下路径格式问题,同时支持各种筛选选项。此外也提供了一个 telescope 拓展。 代码格式化 format.nvim format.nvim 很早以前,我使用的代码格式化插件是 neoformat,这是使用 VimScript 开发的插件,为了更快的速度体验,我使用 Lua 重新写了一个格式化插件,插件的基本逻辑参考了 neoformat。也支持 markdown 内代码块的格式化。 版本控制 git.nvim git.nvim 可以这么说,这应该是我日常使用最多的插件,比如 :Git add,:Git commit,等等。 代码运行 code-runner.nvim code-runner.nvim vscode 里面有一个非常著名的插件,叫做CodeRunner,就是快速运行当前文件,并在下面分屏显示运行结果。code-runner.nvim 实现了想类似的功能,不仅仅可以运行当前文件,还可以快速运行 markdown 文件内当前代码块。 书签管理 bookmarks.nvim bookmarks.nvim 可以标记当前行进入书签列表,支持添加备注内容,支持使用quickfix列表展示书签,也提供了一个 telescope 拓展以便于模糊搜索书签。 tags 管理 ctags.nvim ctags.nvim 自动为当前项目生成ctags数据库,同时更新 &tags 选项,以便于使用ctrl-]跳转到光标tags定义位置。 滚动侧栏 scrollbar.nvim scrollbar.nvim 自动在当前窗口右侧绘制一个滚动条,提示当前文档滚动页大致的位置。 笔记插件 zettelkasten.nvim zettelkasten.nvim 这是一个做笔记的插件,支持标签内链,相互索引。 日历插件 calendar.nvim calendar.nvim 记笔或者待办事项的时候,很多时候需要一个日历视图,来快速看下哪一天有记录。而 calendar.nvim 就是这样一个支持拓展的简易日历视图,可以方便的查阅日历,配合拓展还可以在特定日期执行某个拓展操作。 我目前最常用的是配合 zettelkasten.nvim 插件来记录和查阅每日笔记。 异步job管理 job.nvim job.nvim 这是模拟 jobstart 的工具,基于libuv,可以用于异步执行命令。我的很多插件都是依赖这个job.nvim,比如插件管理器等等,需要异步执行外部命令的。 模糊搜索插件 picker.nvim picker.nvim 早期在使用 Vim 时,使用过 ctrl-p、unite.vim、denite.nvim 等等插件。从 Neovim 浮窗功能出来后,逐步切换到了 telescope.nvim。 但是近期发现这个插件似乎不怎么在维护了,再加上自己也有想法写一个模糊搜索插件,于是就有了 picker.nvim。 项目文件跳转 altf.nvim altf.nvim 这个插件提供了一个命令 :A,可以使用这个命令在头文件和C文件、或者源文件和测试文件等之间快速跳转。 按键弹窗 record-key.nvim record-key.nvim 有时候制作录屏的时候,需要展示自己按键的顺序,这个插件以弹窗的形式,在右下方展示按键的顺序。 录屏 record-screen.nvim record-screen.nvim 屏幕录制的软件其实挺多的,这个插件通过后台调用 ffmpeg,支持录制声音、话筒、摄像头等等。

2025/5/3
articleCard.readMore

解决 Windows 系统下输入法问题

遇到的问题 实际原因 解决方法 使用 Neovim autocmd 自动执行 im-select 更好的解决方法 遇到的问题 "+p 快捷键就可以了,但是由于当前输入法变成了中文模式, 就变成输入了 “+, Normal 模式下 “ 无用,+ 直接给换到下一行。p 按键不会直接输入到终端,反而会弹出输入法候选框。 此外,由于自动切换到了中文输入,当想执行 Neovim 命令时,比如 :w<Cr> 的时候,同样的按键顺序,就会变成了:w, 未保存,同时相当于 Normal 模式下执行了一次 w 光标向前移动一个词位置。 实际原因 解决方法 im-select 进行键盘切换了。 打开命令行窗口:分别在两个键盘模式下执行一次下载的 im-select.exe 可以看到输出如下: 关于这个命令的说明: 直接不带参数执行,获取当前键盘值,比如上图中,我分别先后在美式键盘和微软拼音下单独执行命令,获取到两个键盘值 1033 和 2052。 执行命令,并带上键盘值参数,切换至指定键盘,比如 im-select.exe 1033 切换至美式键盘,im-select.exe 2052 切换至微软拼音。 使用 Neovim autocmd 自动执行 im-select local augroup = vim.api.nvim_create_augroup('wsdjeg', { clear = true }) local create_autocmd = vim.api.nvim_create_autocmd -- 这里指定下载的 im-select.exe 的绝对路径, -- 当然,你也可以将这个命令丢 PATH 里,个人不喜欢污染 PATH local imselect = 'C:\\Users\\wsdjeg\\Downloads\\im-select.exe' create_autocmd({ 'InsertLeave', 'FocusGained', 'CmdlineLeave', 'VimEnter' }, { pattern = { '*' }, group = augroup, callback = function(_) -- 切换至美式键盘 vim.system({ imselect, '1033' }) end, }) create_autocmd({ 'FocusLost' }, { pattern = { '*' }, group = augroup, callback = function(_) -- 切换至微软拼音 vim.system({ imselect, '2052' }) end, }) 更好的解决方法 im-select-mspy,对应的 Neovim 配置如下: local augroup = vim.api.nvim_create_augroup('wsdjeg', { clear = true }) -- download im-select-mspy from https://github.com/daipeihust/im-select/raw/refs/heads/master/win-mspy/out/x64/im-select-mspy.exe local imselect = 'C:\\Users\\wsdjeg\\Downloads\\im-select-mspy.exe' local function imselect_cn() vim.system({ imselect, '-k=ctrl+space', '中文模式' }) end local function imselect_en() vim.system({ imselect, '-k=ctrl+space', '英语模式' }) end -- 使用 table 存储不同 buffer 的 Insert 模式下的输入法 local buffer_im = {} create_autocmd({ 'InsertLeave' }, { pattern = { '*' }, group = augroup, callback = function(ev) vim.system({ imselect }, { text = true }, function(o) -- 这里说明下,再 Windows Terminal 内执行该命令输出的内容默认编码是 `cp936`, -- 需要转码成 utf-8,同时,输出内容尾部有换行符,使用 trim 函数去除。 buffer_im[ev.buf] = vim.trim(vim.iconv(o.stdout, 'cp936', 'utf-8')) end) imselect_en() end, }) create_autocmd({ 'InsertEnter' }, { pattern = { '*' }, group = augroup, callback = function(ev) local c = '英语模式' if buffer_im[ev.buf] and buffer_im[ev.buf] ~= '英语模式' then -- 此处设置快捷键,可以在输入法按键设置里面查看,我选择的是使用 ctrl-space 切换中英文 -- 默认我记得是 shift,同时这个命令默认也是 `-k=shift` vim.system({ imselect, '-k=ctrl+space', buffer_im[ev.buf] }) end end, }) create_autocmd({ 'FocusGained', 'CmdlineLeave', 'VimEnter' }, { pattern = { '*' }, group = augroup, callback = function(_) imselect_en() end, }) create_autocmd({ 'FocusLost' }, { pattern = { '*' }, group = augroup, callback = function(_) imselect_cn() end, })

2025/4/23
articleCard.readMore

Neovim 历史使用文件插件 mru.nvim

起因 mru.nvim 的特点 安装与配置 LeaderF 拓展 起因 mru.nvim, 用于管理历史访问过的文件列表。有人说,这样简单的功能 Neovim 本身就有。 没错,Neovim 和 Vim 一样,支持使用 v:oldfiles 获取最近浏览的文件名称列表。 但是 Neovim 这一变量在 Windows 系统下面存在文件名称格式不一致的问题(neovim#25033)。 比如,一些使用 v:oldfiles 的插件,如 telescope.nvim、dashboard.nvim 等,其列表内会出现一些重复的文件。 D:/hello.txt D:\hello.txt d:/hello.txt d:\hello.txt :h v:oldfiles 可以看到该变量虽然支持修改,但是并不会影响 shada 文件中存储的内容。 因此,在 Neovim 重启后,之前对 v:oldfiles 这一变量的修改就会无效了。 mru.nvim 的特点 D:/hello.txt,可以避免重复显示。 require('mru').setup({ ignore_path_regexs = { '/.git/', '/nvim/runtime/doc/' }, }) require('mru').setup({ ignore_path_regexs = { '/.git/', '.mp3$', '.png$' }, }) :Telescope mru 打开。 :Picker mru 打开。 安装与配置 nvim-plug 插件管理器进行安装: require('plug').add({ { 'wsdjeg/mru.nvim', config = function() require('mru').setup({ enable_cache = true, mru_cache_file = vim.fn.stdpath('data') .. '/nvim-mru.json', ignore_path_regexs = { '/.git/' }, enable_logger = true, -- require wsdjeg/logger.nvim -- sort file by last modified time or last enter time -- `lastmod`, `lastread` or `lastenter`, default is `lastenter` sort_by = 'lastenter', }) end, }, }) LeaderF 拓展 require('mru').get() 获取历史记录文件名称列表。以 leaderf 为例: function! s:nmru(...) abort return v:lua.require('mru').get() endfunction function! s:nmru_acp(line, args) abort exe 'e' a:line endfunction " function() wrapper if v:version > 703 || v:version == 703 && has('patch1170') function! s:_SID() abort return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') endfunction let s:_s = '<SNR>' . s:_SID() . '_' function! s:_function(fstr, ...) abort if a:0 > 1 return function(substitute(a:fstr, 's:', s:_s, 'g')) else return function(a:fstr) endif endfunction else function! s:_SID() abort return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$') endfunction let s:_s = '<SNR>' . s:_SID() . '_' function! s:_function(fstr) abort return function(substitute(a:fstr, 's:', s:_s, 'g')) endfunction endif let g:Lf_Extensions = { \ 'nmru': { \ 'source': string(s:_function('s:nmru', 1))[10:-3], \ 'accept': string(s:_function('s:nmru_acp', 1))[10:-3], \ 'supports_name_only': 1, \ 'supports_multi': 0, \ }, \} local function mru() return require('mru').get() end local function mru_acp(line, args) vim.cmd('e ' .. line) end vim.g.Lf_Extensions = { nvimmru = { source = mru, accept = mru_acp, supports_name_only = true, supports_multi = false, }, }

2025/4/13
articleCard.readMore

Neovim 录制按键及屏幕

按键提示 录屏插件 硬件设备检查 仅录制屏幕 录制屏幕、麦克风、扬声器 安装配置 参考阅读 按键提示 vim.on_key,这个函数在按键按下后会触发。借助这个函数及 Neovim 的悬浮窗口, 实现了一个按键弹窗提示的效果插件 record-key.nvim。 默认的着色是 Normal 高亮组,如果需要突出显示,可以设置为: require('record-key').setup({ timeout = 3000, max_count = 7, winhighlight = 'NormalFloat:Todo,FloatBorder:WinSeparator', }) 录屏插件 record-screen.nvim 是一个 Neovim 屏幕录制的插件, 借助 ffmpeg 这个命令和 Neovim 的异步机制。 硬件设备检查 ffmpeg -list_devices true -f dshow -i dummy 命令获取设备列表: ffmpeg version 7.1.1-full_build-www.gyan.dev Copyright (c) 2000-2025 the FFmpeg developers built with gcc 14.2.0 (Rev1, Built by MSYS2 project) configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-lcms2 --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-libdvdnav --enable-libdvdread --enable-sdl2 --enable-libaribb24 --enable-libaribcaption --enable-libdav1d --enable-libdavs2 --enable-libopenjpeg --enable-libquirc --enable-libuavs3d --enable-libxevd --enable-libzvbi --enable-libqrencode --enable-librav1e --enable-libsvtav1 --enable-libvvenc --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxeve --enable-libxvid --enable-libaom --enable-libjxl --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libcodec2 --enable-libilbc --enable-libgsm --enable-liblc3 --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint libavutil 59. 39.100 / 59. 39.100 libavcodec 61. 19.101 / 61. 19.101 libavformat 61. 7.100 / 61. 7.100 libavdevice 61. 3.100 / 61. 3.100 libavfilter 10. 4.100 / 10. 4.100 libswscale 8. 3.100 / 8. 3.100 libswresample 5. 3.100 / 5. 3.100 libpostproc 58. 3.100 / 58. 3.100 [dshow @ 000001f024fc6d40] "Integrated Camera" (video) [dshow @ 000001f024fc6d40] Alternative name "@device_pnp_\\?\usb#vid_13d3&pid_5419&mi_00#7&17d116b8&1&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" [dshow @ 000001f024fc6d40] "麦克风阵列 (Realtek(R) Audio)" (audio) [dshow @ 000001f024fc6d40] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{253B9838-6E9C-47DB-A420-848E63B3931C}" [in#0 @ 000001f024fad380] Error opening input: Immediate exit requested Error opening input file dummy. Integrated Camera和麦克风麦克风阵列 (Realtek(R) Audio),并没有扬声器,解决方法如下: 一、鼠标右键点击桌面右下角音量图标 二、打开更多音量设置 三、点击录制、右击立体声混声启用 这时候再执行 ffmpeg -list_devices true -f dshow -i dummy 输出为: ffmpeg version 7.1.1-full_build-www.gyan.dev Copyright (c) 2000-2025 the FFmpeg developers built with gcc 14.2.0 (Rev1, Built by MSYS2 project) configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-lcms2 --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-libdvdnav --enable-libdvdread --enable-sdl2 --enable-libaribb24 --enable-libaribcaption --enable-libdav1d --enable-libdavs2 --enable-libopenjpeg --enable-libquirc --enable-libuavs3d --enable-libxevd --enable-libzvbi --enable-libqrencode --enable-librav1e --enable-libsvtav1 --enable-libvvenc --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxeve --enable-libxvid --enable-libaom --enable-libjxl --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libcodec2 --enable-libilbc --enable-libgsm --enable-liblc3 --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint libavutil 59. 39.100 / 59. 39.100 libavcodec 61. 19.101 / 61. 19.101 libavformat 61. 7.100 / 61. 7.100 libavdevice 61. 3.100 / 61. 3.100 libavfilter 10. 4.100 / 10. 4.100 libswscale 8. 3.100 / 8. 3.100 libswresample 5. 3.100 / 5. 3.100 libpostproc 58. 3.100 / 58. 3.100 [dshow @ 0000020942ff6cc0] "Integrated Camera" (video) [dshow @ 0000020942ff6cc0] Alternative name "@device_pnp_\\?\usb#vid_13d3&pid_5419&mi_00#7&17d116b8&1&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" [dshow @ 0000020942ff6cc0] "麦克风阵列 (Realtek(R) Audio)" (audio) [dshow @ 0000020942ff6cc0] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{253B9838-6E9C-47DB-A420-848E63B3931C}" [dshow @ 0000020942ff6cc0] "立体声混音 (Realtek(R) Audio)" (audio) [dshow @ 0000020942ff6cc0] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{A04F0465-843E-4E64-891D-31A28192D215}" [in#0 @ 0000020942fdd340] Error opening input: Immediate exit requested Error opening input file dummy. Integrated Camera 麦克风阵列 (Realtek(R) Audio) 立体声混音 (Realtek(R) Audio) rdp/screen-capture-recorder-to-video-windows-free, 安装完成后,`` 命令输出如下: ffmpeg version 7.1.1-full_build-www.gyan.dev Copyright (c) 2000-2025 the FFmpeg developers built with gcc 14.2.0 (Rev1, Built by MSYS2 project) configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-lcms2 --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-libdvdnav --enable-libdvdread --enable-sdl2 --enable-libaribb24 --enable-libaribcaption --enable-libdav1d --enable-libdavs2 --enable-libopenjpeg --enable-libquirc --enable-libuavs3d --enable-libxevd --enable-libzvbi --enable-libqrencode --enable-librav1e --enable-libsvtav1 --enable-libvvenc --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxeve --enable-libxvid --enable-libaom --enable-libjxl --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libcodec2 --enable-libilbc --enable-libgsm --enable-liblc3 --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint libavutil 59. 39.100 / 59. 39.100 libavcodec 61. 19.101 / 61. 19.101 libavformat 61. 7.100 / 61. 7.100 libavdevice 61. 3.100 / 61. 3.100 libavfilter 10. 4.100 / 10. 4.100 libswscale 8. 3.100 / 8. 3.100 libswresample 5. 3.100 / 5. 3.100 libpostproc 58. 3.100 / 58. 3.100 [dshow @ 0000020d0d8f6d40] "Integrated Camera" (video) [dshow @ 0000020d0d8f6d40] Alternative name "@device_pnp_\\?\usb#vid_13d3&pid_5419&mi_00#7&17d116b8&1&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" [dshow @ 0000020d0d8f6d40] "screen-capture-recorder" (video) [dshow @ 0000020d0d8f6d40] Alternative name "@device_sw_{860BB310-5D01-11D0-BD3B-00A0C911CE86}\{4EA69364-2C8A-4AE6-A561-56E4B5044439}" [dshow @ 0000020d0d8f6d40] "立体声混音 (Realtek(R) Audio)" (audio) [dshow @ 0000020d0d8f6d40] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{A04F0465-843E-4E64-891D-31A28192D215}" [dshow @ 0000020d0d8f6d40] "virtual-audio-capturer" (audio) [dshow @ 0000020d0d8f6d40] Alternative name "@device_sw_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\{8E146464-DB61-4309-AFA1-3578E927E935}" [dshow @ 0000020d0d8f6d40] "麦克风阵列 (Realtek(R) Audio)" (audio) [dshow @ 0000020d0d8f6d40] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{253B9838-6E9C-47DB-A420-848E63B3931C}" [in#0 @ 0000020d0d8dd380] Error opening input: Immediate exit requested Error opening input file dummy. screen-capture-recorder virtual-audio-capturer 仅录制屏幕 require('record-screen').setup({ command = 'ffmpeg', argvs = { '-f', 'gdigrab', '-i', 'desktop', '-pix_fmt', 'yuv420p', '-f', 'mp4', }, }) 录制屏幕、麦克风、扬声器 require('record-screen').setup({ cmd = 'ffmpeg', argvs = { '-f', 'dshow', '-i', 'audio=麦克风阵列 (Realtek(R) Audio)', '-f', 'dshow', '-i', 'audio=立体声混音 (Realtek(R) Audio)', '-filter_complex', 'amix=inputs=2:duration=first:dropout_transition=2', '-f', 'gdigrab', '-r', '60', '-draw_mouse', '1', '-i', 'desktop', '-pix_fmt', 'yuv420p', '-f', 'mp4', }, }) real-time buffer frame dropped 这样的错误提示, 参考issue 136 提到的解决方案,增加参数 -rtbufsize 1500M 安装配置 nvim-plug 插件管理器: require('plug').add({ { 'wsdjeg/record-key.nvim', cmds = { 'RecordKeyToggle' }, config_before = function() vim.keymap.set( 'n', '<leader>rk', '<cmd>RecordKeyToggle<cr>', { silent = true } ) end, }, { 'wsdjeg/record-screen.nvim', depends = { { 'wsdjeg/job.nvim' }, { 'wsdjeg/notify.nvim' }, }, config = function() require('plugins.record-screen') end, }, }) nvim-config/lua/plugins/record-screen.lua 通过这个配置文件,我新建了一个用户自定义命令 :RecordScreen,支持如下参数: -camera: 录制摄像头 -speaker: 录制扬声器 --microphone: 录制麦克风 :RecordScreen stop 停止录屏。 参考阅读 抛弃又贵又难用的录屏软件,3 分钟入门 FFmpeg

2025/4/12
articleCard.readMore

Neovim 自动切换至项目根目录

安装 rooter.nvim 插件配置 Telescope 拓展 插件运行日志 设置 callback 函数 rooter.nvim。 安装 rooter.nvim nvim-plug require('plug').add({ { 'wsdjeg/rooter.nvim', config = function() require('rooter').setup({}) end, }, }) 插件配置 require('rooter').setup({ root_patterns = { '.git/' }, outermost = true, enable_cache = true, project_non_root = '', -- this can be '', 'home' or 'current' enable_logger = true, -- enable runtime log via logger.nvim }) project_non_root: 配置打开非项目文件时的行为 outermost: 若设为 true,那么通过 root_patterns 检索到的多个目录时,取最外层目录。 Telescope 拓展 :Telescope project 列出过往打开过的项目。 插件运行日志 logger.nvim 进行查看。 如果有这一需求,那么在安装 rooter.nvim 时,需要添加相应的依赖插件。 require('plug').add({ { 'wsdjeg/rooter.nvim', config = function() require('rooter').setup({}) end, depends = { { 'wsdjeg/logger.nvim', }, }, }, }) 设置 callback 函数 reg_callback 可以设置 callback 函数,该函数会在项目切换时被调用。 local function update_ctags_option() local project_root = vim.fn.getcwd() local dir = require('util').unify_path(require('tags').cache_dir) .. require('util').path_to_fname(project_root) table.insert(tags, dir .. '/tags') vim.o.tags = table.concat(tags, ',') end require('rooter').reg_callback(update_gtags_option)

2025/3/22
articleCard.readMore

Neovim 日志插件 logger.nvim

安装 在插件中使用 logger.nvim 提供了一个基础的日志框架,不同的插件可以共用一个日志系统。 安装 nvim-plug安装: require('plug').add({ 'wsdjeg/logger.nvim', config = function() require('logger').setup({ -- the level only can be: -- 0 : log debug, info, warn, error messages -- 1 : log info, warn, error messages -- 2 : log warn, error messages -- 3 : log error messages level = 0, }) end, }) 在插件中使用 fyz.nvim,此时可以添加一个文件 lua/fyz/log.lua: local M = {} local logger function M.info(msg) if not logger then pcall(function() logger = require('logger').derive('fyz') logger.info('hello world') end) else logger.info('hello world') end end return M local log = require('fyz.log') log.info('this is log from fyz.nvim') logger.viewRuntimeLog() 查看所有的日志输出,其中就会有如下一行: [ fyz ] [23:22:50:576] [ Info ] this is log from fyz.nvim

2025/3/16
articleCard.readMore

Neovim 任务管理插件 tasks.nvim

起因 安装及配置 tasks.nvim 常用命令 起因 增加了 Tasks 支持,参考的是 Vscode Tasks Manager 的实现。 最早的版本使用 Vim Script 实现的,大约在 2023 年的时候增加了 Lua 实现版本, 不过这些都是在 SpaceVim 内置的插件。 现在,SpaceVim 已经不再维护,而这些常用的功能,我也会陆续剥离出来单独形成插件,这篇文章主要介绍 tasks.nvim 安装及配置 tasks.nvim require('plug').add({ { 'wsdjeg/tasks.nvim', depends = { { 'wsdjeg/code-runner.nvim', }, }, config = function() require('tasks').setup({ global_tasks = '~/.tasks.toml', local_tasks = '.tasks.toml', provider = { 'npm' }, }) end, }, }) 常用命令 tasks.nvim 提供了三个常用命令: :TasksEdit:用于打开 tasks 配置文件,默认打开的是项目配置文件,加上感叹号(:TasksEdit!)则打开全局配置文件。 :TasksList:使用分屏列出所有 tasks :TasksSelect:选择某个 task 并执行 telescope.nvim 那么,可以使用 :Telescope tasks 模糊搜索可用的 tasks.

2025/3/1
articleCard.readMore

Neovim 代码执行插件 code-runner.nvim

安装及配置 自动更新 runner 参数 Code Runner,我曾经也给 SpaceVim 添加了这么一个功能。 现在将这一功能剥离出来形成一个单独独立的 neovim 插件:code-runner.nvim 安装及配置 require('plug').add({ { 'wsdjeg/code-runner.nvim', config = function() require('code-runner').setup({ runners = { lua = { exe = 'lua', opt = { '-' }, usestdin = true }, }, enter_win = false, }) end, }, }) 自动更新 runner 参数 rooter.nvim 插件,可以在切换项目时,读取 .clang 文件内容,并且更新 c 语言的 runner。具体代码实现: local c_runner = { exe = 'gcc', targetopt = '-o', usestdin = true, opt = { '-std=c11', '-xc', '-' }, } require('code-runner').setup({ runners = { c = { c_runner, '#TEMP#' }, }, }) vim.keymap.set( 'n', '<leader>lr', '<cmd>lua require("code-runner").open()<cr>', { silent = true } ) -- make sure rooter.nvim plugin is loaded before code-runner local function update_clang_flag() if vim.fn.filereadable('.clang') == 1 then local flags = vim.fn.readfile('.clang') local opt = { '-std=c11' } for _, v in ipairs(flags) do table.insert(opt, v) end table.insert(opt, '-xc') table.insert(opt, '-') c_runner.opt = opt end end require('rooter').reg_callback(update_clang_flag)

2025/2/28
articleCard.readMore

Neovim 插件管理器 nvim-plug

项目简介 功能特点 完全异步调用命令 支持多种懒加载模式 UI 是可替换的 支持多种插件类型 安装方式 自动安装(推荐) 使用 LuaRocks 基本配置 添加插件 常用命令 nvim-plug 的原因其实很简单,我前面写过一篇插件管理器的运行机制《Neovim 和 Vim 插件管理器的实现逻辑》 。 我想练习一下插件管理器的实现,之前看过 dein.nvim 的一些源码,里面的懒加载实现感觉很有趣。 我想要一个完全可控、异步、并且不限制 UI 表现形式的插件管理器。 项目地址: wsdjeg/nvim-plug 项目简介 插件安装和更新必须是异步的 插件管理过程和 UI 界面分离 支持常用的插件懒加载模式 功能特点 完全异步调用命令 支持多种懒加载模式 事件: events 命令: cmds 文件类型: on_ft 按键: on_map Vim 函数: on_func UI 是可替换的 默认是分屏 UI,参考的是 vundle.vim 可以切换为 notify 风格的浮动通知 也可以完全自己实现 UI 回调 支持多种插件类型 raw 脚本 LuaRocks 包 本地开发路径 安装方式 自动安装(推荐) local dir = vim.fn.stdpath('data') .. '/repos/' local function bootstrap(repo) if vim.fn.isdirectory(dir .. repo) == 0 then vim.fn.system({ 'git', 'clone', '--depth', '1', 'https://github.com/' .. repo .. '.git', dir .. repo, }) end vim.opt.runtimepath:append(dir .. repo) end bootstrap('wsdjeg/job.nvim') bootstrap('wsdjeg/logger.nvim') bootstrap('wsdjeg/nvim-plug') 使用 LuaRocks luarocks install nvim-plug 基本配置 require('plug').setup({ bundle_dir = vim.fn.stdpath('data') .. '/repos', raw_plugin_dir = vim.fn.stdpath('data') .. '/repos/raw_plugin', max_processes = 5, base_url = 'https://github.com', ui = 'default', clone_depth = 1, }) 添加插件 require('plug').add({ { 'wsdjeg/scrollbar.vim', events = { 'VimEnter' }, }, { 'wsdjeg/flygrep.nvim', cmds = { 'FlyGrep' }, config = function() require('flygrep').setup() end, }, { type = 'raw', url = 'https://gist.githubusercontent.com/.../markdown.vim', script_type = 'after/syntax', }, }) 常用命令 :Plug install :Plug update

2025/2/9
articleCard.readMore

从 VimScipt 切换至 Lua

为什么选择 Lua 学习 Lua 配置文件结构 初始化文件 ftplugin Options 事件自动命令 用户自定义命令 设置快捷键 变量类型转换 vim.g 访问全局变量的弊端 OS:Windows 11 Neovim:v0.10.0 Terminal: Windows Terminal 为什么选择 Lua Vim9Script 与 Lua 的速度比较 使用 Lua 重写 SpaceVim 内置插件 学习 Lua 《学习 Lua 脚本语言》。 配置文件结构 初始化文件 ~\AppData\Local\nvim\init.lua,Linux 系统是 ~/.config/nvim/init.lua。Neovim 启动时会自动读取并执行该文件内容。 ftplugin filetype 。比如,打开 Test.java 文件,此时 ftplugin/java.lua 就会被调用执行。 Options 使用 Neovim API: Neovim 提供了设置 Option 的 API 函数:nvim_set_option_value({name}, {value}, {opts}),opts 是一个 Lua table,支持的 key 包括: scope:grobal 或者 local,类似于 :setglobal 和 :setlocal win: 设置指定 window-ID 的 local option buf:设置指定 buffer number 的 local option 使用 vim.o: 这类似于直接使用 :set 命令,默认是设置 global option。 vim.o.number = false -- 禁用行号,启用行号可以设置为 true vim.o.relativenumber = false -- 禁用相对行号 使用 vim.bo、vim.wo: 这类似于使用 :setlocal 命令,但是还是有些区别的,按照 :h vim.bo 描述,其使用的格式是:vim.bo[{bufnr}].opt_name, 如果设置的 option 不是 local to buffer 就会报错,比如执行 :lua vim.bo.number = true 就会报错: E5108: Error executing lua [string ":lua"]:1: 'buf' cannot be passed for window-local option 'number' stack traceback: [C]: in function '__newindex' [string ":lua"]:1: in main chunk vim.wo 时也会有类似的问题,因此在使用这两个方式设置 option 时,需要判断 option 是 local to window 还是 local to buffer, 一般在 help 文档里面都有。 比如 :h 'number': 'number' 'nu' boolean (default off) local to window nvim_get_option_info2({name}, {opts}) 返回值是一个 lua table,其中 scope key 值就可以用来判断,其值可以是 global、win、buf。 实际上,如果去看 Neovim 的源码,你会发现不管是 vim.o 还是 vim.bo 等 只不是 API 的 wrap 而已: neovim’s options.lua#L242: vim.o = setmetatable({}, { __index = function(_, k) return api.nvim_get_option_value(k, {}) end, __newindex = function(_, k, v) return api.nvim_set_option_value(k, v, {}) end, }) local function set_local(winid, bufnr, opts) for k, v in pairs(opts) do if vim.api.nvim_get_option_info2(k, {}).scope == 'win' then vim.api.nvim_set_option_value(k, v, { win = winid }) elseif vim.api.nvim_get_option_info2(k, {}).scope == 'buf' then vim.api.nvim_set_option_value(k, v, { buf = bufnr }) else -- skip global opt end end end local winid, bufnr = open_plugin_float_win() set_local(winid, bufnr, { number = false, relativenumber = false, filetype = 'java', bufhidden = 'wipe', }) 事件自动命令 augroup test_augroup_vim autocmd! autocmd WinEnter * call s:test1() autocmd WinEnter * call s:test2() augroup END augroup text_augroup_vim autocmd! augroup END nvim_create_autocmd 和 nvim_del_autocmd,演示如下: local function test1() print(1) end local function test2() print(2) end local group = vim.api.nvim_create_augroup('test_augroup_neovim', { clear = true }) local autocmd_id1 = vim.api.nvim_create_autocmd({ 'WinEnter' }, { group = group, pattern = { '*' }, callback = test1, }) local autocmd_id2 = vim.api.nvim_create_autocmd({ 'WinEnter' }, { group = group, pattern = { '*' }, callback = test2, }) vim.api.nvim_del_autocmd(autocmd_id2) 用户自定义命令 vim.api.nvim_create_user_command({name}, {command}, {opts}) API 来新建命令。 这个 API 函数接受三个参数,第一个顾名思义就是新建的命令的具体名称, 第二个 command 可以是一个 string 也可以是一个 lua function。如果是 string, 当执行这个命令时,这段字符串会被直接执行。如果是 Lua function,则会被传入一个 table 参数调用。 第三个参数是设定命令的一些参数,类似于 :h command-attributes 比如: vim.api.nvim_create_user_command('SayHello', function(opt) print('hello') end, { nargs = '*', bang = true, }) { "bang", "reg", "range", "args", "mods", "line1", "smods", "fargs", "line2", "count", "name" } bang: 如果定义命令时,第三个参数 bang = true,那么可以在执行命令时带上感叹号。 vim.api.nvim_create_user_command('Test', function(opt) print(opt.bang) end, { nargs = '*', bang = true, }) :Test 时打印 false,执行 :Test! 时打印 true,也可以在函数内判断 if opt.bang 为命令是否带感叹号赋予不一样的意义。 有一些插件的命令支持两个感叹号,是如何实现的呢?比如 vim-gina Single command. Users do not need to remember tons of commands :Gina {command} will execute a gina command or a git raw command asynchronously :Gina! {command} will execute a git raw command asynchronously :Gina!! {command} will execute a git raw command in a shell (mainly for :Gina!! add -p or :Gina!! rebase -i) vim.api.nvim_create_user_command('Gina', function(opt) if not opt.bang then print('it is Gina') elseif opt.bang and opt.fargs[1] == '!' then print('it is Gina!!') else print('it is Gina!') end end, { nargs = '*', bang = true, }) vim.api.nvim_create_user_command('Test1', function(opt) end, { nargs = 0, }) vim.api.nvim_create_user_command('Test2', function(opt) vim.print(opt.args) vim.print(opt.fargs) end, { nargs = 1, }) vim.api.nvim_create_user_command('Test3', function(opt) vim.print(opt.args) vim.print(opt.fargs) end, { nargs = '*', }) vim.api.nvim_create_user_command('Test4', function(opt) vim.print(opt.args) vim.print(opt.fargs) end, { nargs = '+', }) vim.api.nvim_create_user_command('Test5', function(opt) vim.print(opt.args) vim.print(opt.fargs) end, { nargs = '?', }) :Test1 foo 报错 E488: Trailing characters: foo。 nargs 为 1 时,如果执行 :Test2 foo zaa xss 就会发现输出内容为: foo zaa xss { "foo zaa xss" } * 时,如果执行 :Test3 foo zaa xss 就会发现输出内容为: foo zaa xss { "foo", "zaa", "xss" } + 时,如果执行 :Test4 foo zaa xss 就会发现输出内容为: foo zaa xss { "foo", "zaa", "xss" } :Test4 不带参数,则会报错:E471: Argument required nargs 为 ? 时,如果执行 :Test5 foo zaa xss 就会发现输出内容为: foo zaa xss { "foo zaa xss" } :Test5 可以不带参数直接执行 设置快捷键 提供了 vim.keymap.set() 函数来设置快捷键, 如果阅读源码的话,也可以发现,实际上这个函数也不过是包装了 Neovim API 函数 vim.api.nvim_buf_set_keymap 和 vim.api.nvim_set_keymap。 但是原生的 API nvim_set_keymap 和 nvim_buf_set_keymap 使用起来太麻烦了,对于一些特殊按键还需要使用 nvim_replace_termcodes() 去转换。 而 vim.keymap.set({mode}, {lhs}, {rhs}, {opts}) 的优势是: differences compared to regular set_keymap: - remap is used as opposite of noremap. By default it's true for <Plug> keymaps and false for others. - rhs can be lua function. - mode can be a list of modes. - replace_keycodes option for lua function expr maps. (Default: true) - handles buffer specific keymaps commit 移除了 <Plug> kemaps 检测, 这里是与 Vim 不一致的地方。 feat: ignore nore on <Plug> maps feat(lua): add support for lua keymaps vim.keymap.set('i', '<Esc>', function() vim.cmd('noautocmd stopinsert') vim.api.nvim_win_close(prompt_winid, true) vim.api.nvim_win_close(result_winid, true) end, { buffer = prompt_bufid }) 变量类型转换 vim.fn 原因。 比如以下这段 VimScipt: let s:a = [1, 2] call add(s:a, 3) echo s:a [1, 2, 3]。 换成 Lua: local a = {1, 2} vim.fn.add(a, 3) vim.print(a) --- 输出是 {1, 2} vim.fn 的源码: -- vim.fn.{func}(...) ---@nodoc vim.fn = setmetatable({}, { --- @param t table<string,function> --- @param key string --- @return function __index = function(t, key) local _fn --- @type function if vim.api[key] ~= nil then _fn = function() error(string.format('Tried to call API function with vim.fn: use vim.api.%s instead', key)) end else _fn = function(...) return vim.call(key, ...) end end t[key] = _fn return _fn end, }) vim.call(key, ...) 因此应当尽量避免使用 vim.fn 调用 VimScipt 函数操作 Lua 变量,前面的示例应该改为: local a = { 1, 2 } table.insert(a, 3) vim.print(a) --- 输出是 {1, 2, 3} vim.g 访问全局变量的弊端 vim.g 的源码, 不难发现,实际上是通过 vim._getvar 和 vim._setvar 来分别映射 __index 和 __newindex。 可以理解为,每一次访问 __index 时,调用一个 vim._getvar 获取 viml 变量,并转化为一个新的 Lua 变量。最简单的测试: let g:wsd = { 'a' : 'a' } print(vim.g.wsd) print(vim.g.wsd) -- 执行两次,打印的结果不一样 -- table: 0x0215fa040d28 -- table: 0x0215fb557f10 vim.cmd([[ let g:wsd = {'a' : 'a'} ]]) local a = vim.g.wsd vim.cmd([[ let g:wsd.a = 'b' ]]) vim.print(a.a) -- 任然时 a vim.g 获取到的字典变量的变化,也只有在 __newindex 函数被执行时,才会同步到 VimL 的变量。 vim.g.wsd = { a = 'a' } vim.cmd('echo g:wsd') -- {'a' : 'a'} vim.g.wsd.a = 'b' vim.cmd('echo g:wsd') -- 任然是 {'a' : 'a'}

2025/2/1
articleCard.readMore

启用 Git Commit 签名

起因 Git Commit 签名 Windows 安装 GPG 生成 GPG 密钥 设置 Git 签名程序 起因 《震惊!竟然有人在 GitHub 上冒充我的身份!》, 大致看了下 Github 网站对于 Commit 的归属的判断规则。 只需要 Commit 的邮箱在某人的账号设置的邮箱列表内。那么就会将此次提交显示为是某人的归属。这就意味着,任何一个人都可以使用 git config user.email "YOUR_EMAIL" 这一命令设置成别人的邮箱,伪装成他人进行提交。 Git Commit 签名 Windows 安装 GPG scoop 包管理器,通过 scoop install gpg 就可以安装好了。 使用 gpg --version 看一下版本信息。 D:\wsdjeg>gpg --version gpg (GnuPG) 2.4.7 libgcrypt 1.11.0 Copyright (C) 2024 g10 Code GmbH License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Home: D:\Scoop\apps\gpg\current\home Supported algorithms: Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256 Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224 Compression: Uncompressed, ZIP, ZLIB, BZIP2 生成 GPG 密钥 生成并上传 GPG 密钥, 上传完成后使用 https:/github.com/{username}.gpg 格式链接就可以访问你的公钥。 例如我的:wsdjeg.gpg 设置 Git 签名程序 where gpg 查看 gpg 命令的绝对路径: d:\wsdjeg>where gpg D:\Scoop\apps\gpg\current\bin\gpg.exe git config --global --unset gpg.format git commit -S 一直提示没有私钥,找了很久原因才知道,原来需要设置 Git 的签名方式及程序,可能是原来设置过。 git config --global gpg.program "D:\Scoop\apps\gpg\current\bin\gpg.exe" gpg --list-secret-keys --keyid-format=long 列出 GPG 密钥: d:\wsdjeg\my-blog>gpg --list-secret-keys --keyid-format=long D:\Scoop\apps\gpg\current\home\pubring.kbx ------------------------------------------ sec rsa2048/41BB7053E835C848 2021-09-21 [SC] 9C957B574786F570AC69625041BB7053E835C848 uid [ unknown] Shidong Wang (Shidong's GPG key) <wsdjeg@outlook.com> ssb rsa2048/D3E3902EF4E8074C 2021-09-21 [E] git config --global user.signingkey 41BB7053E835C848 git config --global commit.gpgsign true

2025/1/24
articleCard.readMore

从零开始制作实时搜索插件

窗口界面 输入响应 按键映射 窗口内颜色高亮 输入框美化 FlyGrep.vim。 最开始的实现是使用 :split 命令分屏展示搜索结果,使用 :echo 命令配合 while true getchar() 在 cmdline 内模拟输入框。 但是 :split 命令分屏时,总是带动整个界面其他窗口内容的移动。随着 Neovim 增加悬浮窗口这一特性, 我把 Flygrep 搜索结果窗口及底部提示状态都使用浮窗来实现,这就不会受到原先窗口界面布局的影响了。但是目前 Flygrep 的浮窗还是底部半屏窗口。 现在大多数浮窗插件都是在屏幕中间打开窗口,下面就从零开始一步一步实现一个简单的实时代码检索插件。 最终效果图如下: 窗口界面 -- 窗口位置 -- 宽度: columns 的 80% local screen_width = math.floor(vim.o.columns * 0.8) -- 起始位位置: lines * 10%, columns * 10% local start_col = math.floor(vim.o.columns * 0.1) local start_row = math.floor(vim.o.lines * 0.1) -- 整体高度:lines 的 80% local screen_height = math.floor(vim.o.lines * 0.8) local prompt_bufid = vim.api.nvim_create_buf(false, true) local prompt_winid = vim.api.nvim_open_win(prompt_bufid, true, { relative = 'editor', width = screen_width, height = 1, col = start_col, row = start_row + screen_height - 3, focusable = true, border = 'rounded', title = 'Input', title_pos = 'center', -- noautocmd = true, }) local result_bufid = vim.api.nvim_create_buf(false, true) local result_winid = vim.api.nvim_open_win(result_bufid, false, { relative = 'editor', width = screen_width, height = screen_height - 5, col = start_col, row = start_row, focusable = false, border = 'rounded', title = 'Result', title_pos = 'center', -- noautocmd = true, }) 输入响应 TextChangeI 这一事件,在事件 callback 函数内调用搜索命令。 local job = require('spacevim.api.job') local augroup = vim.api.nvim_create_augroup('floatgrep', { clear = true, }) vim.api.nvim_create_autocmd({ 'TextChangedI' }, { group = augroup, buffer = prompt_bufid, callback = function(ev) local text = vim.api.nvim_buf_get_lines(prompt_bufid, 0, 1, false)[1] if text ~= '' then local grep_cmd = { 'rg', '--no-heading', '--color=never', '--with-filename', '--line-number', '--column', '-g', '!.git', '-e', text, '.', } job.start(grep_cmd, { on_stdout = function(id, data) if vim.fn.getbufline(result_bufid, 1)[1] == '' then vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, data) else vim.api.nvim_buf_set_lines(result_bufid, -1, -1, false, data) end end, }) else vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, {}) end end, }) spacevim job API,其实,我也考虑过使用 vim.system() 函数,但是异步搜索完全不调用, 可能是写法有误, 使用 vim.system() 写法如下(无效): vim.api.nvim_create_autocmd({ 'TextChangedI' }, { group = augroup, buffer = prompt_bufid, callback = function(ev) local text = vim.api.nvim_buf_get_lines(prompt_bufid, 0, 1, false)[1] if text ~= '' then local grep_cmd = { 'rg', '--no-heading', '--color=never', '--with-filename', '--line-number', '--column', '-g', '!.git', '-e', text, '.', } vim.system(grep_cmd, { stdout = function(err, data) vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, vim.split(data, '\n')) end, }) end end, }) 按键映射 -- 使用 Esc 关闭整个界面 vim.keymap.set('i', '<Esc>', function() vim.cmd('noautocmd stopinsert') vim.api.nvim_win_close(prompt_winid, true) vim.api.nvim_win_close(result_winid, true) end, { buffer = prompt_bufid }) -- 搜索结果行转换成文件名、光标位置 local function get_file_pos(line) local filename = vim.fn.fnameescape(vim.fn.split(line, [[:\d\+:]])[1]) local linenr = vim.fn.str2nr(string.sub(vim.fn.matchstr(line, [[:\d\+:]]), 2, -2)) local colum = vim.fn.str2nr( string.sub(vim.fn.matchstr(line, [[\(:\d\+\)\@<=:\d\+:]]), 2, -2) ) return filename, linenr, colum end -- 使用回车键打开光标所在的搜索结果,同时关闭界面 vim.keymap.set('i', '<Enter>', function() vim.cmd('noautocmd stopinsert') -- 获取搜索结果光表行 local line_number = vim.api.nvim_win_get_cursor(result_winid)[1] local filename, linenr, colum = get_file_pos( vim.api.nvim_buf_get_lines( result_bufid, line_number - 1, line_number, false )[1] ) vim.api.nvim_win_close(prompt_winid, true) vim.api.nvim_win_close(result_winid, true) vim.cmd('edit ' .. filename) vim.api.nvim_win_set_cursor(0, { linenr, colum }) end, { buffer = prompt_bufid }) -- 使用 Tab/Shift-Tab 上下移动搜素结果 vim.keymap.set('i', '<Tab>', function() local line_number = vim.api.nvim_win_get_cursor(result_winid)[1] vim.api.nvim_win_set_cursor(result_winid, { line_number + 1, 0 }) end, { buffer = prompt_bufid }) vim.keymap.set('i', '<S-Tab>', function() local line_number = vim.api.nvim_win_get_cursor(result_winid)[1] vim.api.nvim_win_set_cursor(result_winid, { line_number - 1, 0 }) end, { buffer = prompt_bufid }) 窗口内颜色高亮 -- 高亮文件名及位置 vim.fn.matchadd( 'Comment', [[\([A-Z]:\)\?[^:]*:\d\+:\(\d\+:\)\?]], 10, -1, { window = result_winid } ) 输入框美化 > 符号,禁用行号。 vim.api.nvim_set_option_value('number', false, { win = prompt_winid }) vim.api.nvim_set_option_value('relativenumber', false, { win = prompt_winid }) local extns = vim.api.nvim_create_namespace('floatgrep_ext') vim.api.nvim_buf_set_extmark(prompt_bufid, extns, 0, 0, { sign_text = '>', sign_hl_group = 'Error', }) simple_float_grep

2025/1/23
articleCard.readMore

Neovim extmarks 功能介绍

什么是 extmark 相关函数 nvim_buf_set_extmark id end_row 和 end_col hi_group hl_eol virt_text virt_text_pos virt_text_win_col virt_text_hide hl_mode virt_lins virt_lines_above virt_lines_leftcol right_gravity 和 end_right_gravity 什么是 extmark :h extmark 描述 Extended marks (extmarks) 是跟踪缓冲区文本变化的的特定位置的注释。位置从 0 开始,位于第一个字符的前方。 f o o b a r line contents 0 1 2 3 4 5 character positions (0-based) 0 1 2 3 4 5 6 extmark positions (0-based) 相关函数 nvim_buf_get_extmark 和 nvim_buf_set_extmark。 nvim_buf_set_extmark nvim_buf_set_extmark 的函数签名是: nvim_buf_set_extmarks({buffer}, {ns_id}, {start}, {col}, {opts}) buffer、ns_id、start、col、opts。 buffer 指的是设置 extmarks 对应的缓冲区 ID,ns_id 全称是 name space id, 可以由 nvim_create_namespace 新建。 其中 opts 是一个 Lua table,比如: vim.api.nvim_buf_set_extmark( 0, vim.api.nvim_create_namespace('test_extmark'), 28, 3, { end_row = 28, end_col = 5, hl_group = 'TODO' } ) opts 支持的 Key 值及其含义包括: id end_row 和 end_col hi_group hl_eol virt_text [text, highlight]。默认是添加在 extmark 开始的行行尾。 示例代码: vim.api.nvim_buf_set_extmark(4, vim.api.nvim_create_namespace("test_extmark"), 47, 3, { end_row = 47, end_col = 5, hl_group = "TODO", virt_text = { { "This is ", "Comment" }, { "hello", "Number" }, { " extmarks", "TODO" } }, }) virt_text_pos eol:行尾最后一个字符右边 overlay: 在 extmark 起始位置显示虚拟文本,覆盖的字符不右移 right_align: 在窗口最右侧显示 inline: 在 extmark 起始位置显示虚拟文本,覆盖的字符右移 virt_text_win_col virt_text_hide hl_mode replace: 默认,只显示 virtual text 颜色 combine: combine with background text color blend: blend with background text color virt_lins [text, highlight] 组成的列表。 virt_lines_above true,可以在上方显示。 virt_lines_leftcol right_gravity 和 end_right_gravity 控制在 extmark 左右侧末端添加字符时 extmark 的位置扩展行为

2025/1/19
articleCard.readMore

Neovim winbar 设置

什么是 winbar 使用 lua 设置 winbar redraw_winbar 函数实现 存在的一些问题 什么是 winbar 'winbar' 这一选项。winbar 实际上类似于状态栏(statusline),只不过 winbar 是显示在每一个窗口的顶部。 它的设置格式与状态栏也完全一致。 这里做了一个简单的示例效果图: " 使用全局状态栏,状态栏只在底部显示,水平分割窗口将不再显示状态栏。 set laststatus=3 " 隐藏顶部标签栏 set showtabline=0 使用 lua 设置 winbar wsdjeg/winbar.nvim 实现的逻辑也比较简单,监控指定的 Neovim 事件,在 callback 函数内调用 redraw_winbar 函数。 local augroup = vim.api.nvim_create_augroup('winbar.nvim', { clear = true, }) vim.api.nvim_create_autocmd({ 'BufWinEnter' }, { group = augroup, pattern = '*', callback = function(e) redraw_winbar() end, }) fgheng/winbar.nvim 插件的代码,最后一次更新时间是 2022 年七月,在他的插件里 setup 函数直接这样写: function M.setup(opts) config.set_options(opts) local winbar = require('winbar.winbar') winbar.init() if opts.enabled == true then vim.api.nvim_create_autocmd({ 'DirChanged', 'CursorMoved', 'BufWinEnter', 'BufFilePost', 'InsertEnter', 'BufWritePost' }, { callback = function() winbar.show_winbar() end }) end end redraw_winbar 函数实现 local function redraw_winbar() local file_name = vim.fn.expand('%:t') if file_name == '' then return end local value = '%#SpaceVim_winbar#' .. file_name .. ' %#SpaceVim_winbar_Normal#' .. default_conf.winbar_seperator .. '%#Normal#' vim.api.nvim_set_option_value('winbar', value, { scope = 'local' }) end 排除 filetype local file_name = vim.fn.expand('%:t') local ft = vim.filetype.match({ filename = file_name }) for _, v in ipairs(excluded_filetypes) do if v == ft then return end end 文件名按正则表达式排除 local excluded_regex = [[\.txt$]] local re = vim.regex(excluded_regex) local file_name = vim.fn.expand('%:t') for _, v in ipairs(excluded_filetypes) do if re:match(file_name) then return end end 存在的一些问题 callback 函数内无法获取到触发这一事件的 windows ID,因为说白了,winbar 是一个 local to window 的选项,通过指定 winid 去修改其值才是最保险的。

2025/1/14
articleCard.readMore

自定义 quickfix 窗口格式

老旧方法 使用 quickfixtextfunc <filename>|<lnum> col <col>|<text> qftf 选项实现的方式。 老旧方法 BufReadPost 事件 function! QuickFixFormat() let qflist = map(getqflist(), \ 'extend(v:val, {"filename" : bufname(v:val.bufnr)})') let prefix_len = 2 + max(map(copy(qflist), \ 'strchars(v:val.filename . v:val.lnum)')) let fmt = '%-' . prefix_len . 's' . '%s' setlocal modifiable call setline('1', map(qflist, \ 'printf(fmt, v:val.filename . ":" . v:val.lnum, "| " . v:val.text)')) setlocal nomodifiable nomodified endfunction augroup QuickFixFormat autocmd! autocmd BufReadPost quickfix call QuickFixFormat() augroup END | 修改成占据整行的竖线 │,以实现完整分割线效果。 完成窗口内容修改后,还存在一个问题,QuickFix 窗口里内容的高亮是通过 Vim 默认的 syntax/qf.vim 文件实现的,在这里需要覆盖默认的 qf FileType 的语法高亮: Vim 可以新建 ~/.vim/syntax/qf.vim 文件,Neovim 是 ~/.config/nvim/syntax/qf.vim。 if exists('b:current_syntax') finish endif syn match qfFileName "^[^│]*" contains=qfLineNr syn match qfSeparator "│" syn match qfLineNr ":\d*" contained " The default highlighting. hi def link qfFileName Directory hi def link qfLineNr LineNr hi def link qfSeparator VertSplit let b:current_syntax = 'qf' 使用 quickfixtextfunc quickfixtextfunc 选项, 可以通过设定这一选项来实现格式化 QuickFix 窗口,修改第一段代码为: function! QuickFixFormat(info) let qflist = getqflist({'id' : a:info.id, 'items' : 1}).items let qflist = map(qflist, \ 'extend(v:val, {"filename" : bufname(v:val.bufnr)})') let prefix_len = 2 + max(map(copy(qflist), \ 'strchars(v:val.filename . v:val.lnum)')) let fmt = '%-' . prefix_len . 's' . '%s' return map(qflist, \ 'printf(fmt, v:val.filename . ":" . v:val.lnum, "│ " . v:val.text)') endfunction set quickfixtextfunc=QuickFixFormat if exists('&quickfixtextfunc') 来检测当前 Vim 或者 Neovim 是否支持 quickfixtextfunc 选项。

2025/1/10
articleCard.readMore

《误杀3》观后感

关于剧情 剧中人物 关于剧情 剧中人物 郑炳睿 一开始确实没想到他原来居然是人贩子,求生其实是生命的本能。人类虽然是高等动物,通过思想可以客服本能,但是还是会有人没办法克服,会依靠本能去做一些决定。 为了能活下去而参与贩卖人口等等,后来还是为了能活下去选择按下女儿那边炸弹的遥控器。虽然几个镜头也极力体现他对女儿的疼爱,比如看到女儿被伤害,哭的眼泪口水哒哒的。 关于这个角色心理的选择,我觉得有一个大的转变,从一开始怕死,而选择按下女儿那边炸弹遥控器,到后来奋不顾身地为女儿挡枪。 但是上面图片中的介绍真是扯淡, 郑炳睿出手爆杀人贩???他才是人贩子。图片剧情简介也完全是跟剧情实际情况截然相反。 李慧萍 电影开头第一眼,这女孩丢失八成跟她有关系,肯定是人贩子。后来证实下来确实是她参与的,但是没想到的是她原来也是是为了复仇。前后出镜截然两种不同风格人物,演员在演演员。 张景贤 误解了,一开始福利院收娃的长头发我以为是他。加上半夜出来杀郑炳睿,我还以为他是达蒙的人。后来看到原来他的妻子是梁素娥,才知道他也是要复仇的。 雅英 我脸盲,看了好几次才把她跟丢失娃的母亲对上号。 吴达毅 最后杀他的是谁? 施福安和Tinaya 没啥感触,死得其所。 阿卢 第一个镜头,就看到仇恨的眼光,审判室里的大笑也没看懂,直到知道他也是丢失儿童的家属,一切才显得那么理所当然。 达蒙 最后啥他的人是谁? 门多萨 一开始以为他是一无是处的领导。 梁素娥 最有感触的还是梁素娥的剧情,感觉词穷了,不知道该怎么写。第一次出镜的时候,感觉挺好看,一张混血儿脸,心善,“我也是孩子母亲,我一定会找到真相”,台词可能我记错了,但是我想她所想表达的意思以及她所坚持的事情一定是这样的,也为此付出了生命,也包括还没出生的孩子。 阿佐 开第一枪的男警察? 阿婕 不记得谁了,女警察? 郑婷婷 最后开枪的是谁?

2025/1/8
articleCard.readMore

Neovim quickfix 窗口内的一些快捷键

删除单行或者多行 结果过滤(Filter) 删除单行或者多行 dd 和 Visual 模式下的 d 快捷键是不可用的,会提示: E21: Cannot make changes, 'modifiable' is off dd 删除光标下的结果,Visual 模式下 d 删除选中行的结果。 local group = vim.api.nvim_create_augroup('quickfix_mapping', { clear = true, }) vim.api.nvim_create_autocmd({ 'FileType' }, { pattern = 'qf', group = group, callback = function(ev) vim.keymap.set('n', 'dd', function() local qflist = vim.fn.getqflist() local line = vim.fn.line('.') table.remove(qflist, line) vim.fn.setqflist(qflist) vim.cmd(tostring(line)) end, { buffer = ev.buf }) end, }) local function table_remove(t, from, to) local rst = {} for i = 1, #t, 1 do if i < from or i > to then rst[#rst + 1] = t[i] -- else -- i = to + 1 end end return rst end vim.api.nvim_create_autocmd({ 'FileType' }, { pattern = 'qf', group = group, callback = function(ev) vim.keymap.set('v', 'd', function() local esc = vim.api.nvim_replace_termcodes('<esc>', true, false, true) vim.api.nvim_feedkeys(esc, 'x', false) local qflist = vim.fn.getqflist() local from = vim.fn.getpos("'<")[2] local to = vim.fn.getpos("'>")[2] if from > to then from, to = to, from end qflist = table_remove(qflist, from, to) vim.fn.setqflist(qflist) vim.cmd(tostring(from)) end, { buffer = ev.buf }) end, }) 结果过滤(Filter) local group = vim.api.nvim_create_augroup('quickfix_mapping', { clear = true, }) vim.api.nvim_create_autocmd({ 'FileType' }, { pattern = 'qf', group = group, callback = function(ev) vim.keymap.set('n', 'c', function() local input_pattern = vim.fn.input('filter pattern:') -- vim.cmd('noautocmd normal! :') local re = vim.regex(input_pattern) local qf = {} for _, item in ipairs(vim.fn.getqflist()) do if not re:match_str(vim.fn.bufname(item.bufnr)) then table.insert(qf, item) end end vim.fn.setqflist(qf) end, { buffer = ev.buf }) end, }) c, 按下快捷键后会弹出一个出入提示,输入一段字符串,可以是 Vim 正则,回车后会移除 quickfix 窗口中文件名称匹配字符串的结果。 如果需要保留文件名匹配字符串的结果,可以增加一个新的快捷键,比如 C,代码如下: local group = vim.api.nvim_create_augroup('quickfix_mapping', { clear = true, }) vim.api.nvim_create_autocmd({ 'FileType' }, { pattern = 'qf', group = group, callback = function(ev) vim.keymap.set('n', 'C', function() local input_pattern = vim.fn.input('filter pattern:') -- vim.cmd('noautocmd normal! :') local re = vim.regex(input_pattern) local qf = {} for _, item in ipairs(vim.fn.getqflist()) do if re:match_str(vim.fn.bufname(item.bufnr)) then table.insert(qf, item) end end vim.fn.setqflist(qf) end, { buffer = ev.buf }) end, }) quickfix.nvim.

2024/12/30
articleCard.readMore

Vim/Neovim 中使用正则表达式

元字符 替换变量 函数式 与 Perl 正则表达式的区别 贪婪模式和非贪婪模式 Neovim 中使用正则表达式 《Vim 中使用正则表达式》。 随着 CSDN 账号的注销,这篇文章已经无法在更新,于是将文章迁移至个人博客,并加以更新,增加 Neovim 相关内容。 经常在网上看到有人抱怨 Vim 的正则表达式太奇怪,无法接受。我倒是觉得 Vim 的正则表达式比较容易理解。 可能是因为我最早接触的正则表达式就是 Vim 的正则表达式吧,正好借此机会整理下 Vim 的正则表达式相关的内容。 首先,在哪些情况下会用到正则表达式? 使用正则表达式的命令最常见的就是 / 和 ? 命令。其格式如下: /正则表达式 ?正则表达式 :s[ubstitute](替换)命令,将第一个//之间的正则表达式替换成第二个//之间的字符串。 :s/正则表达式/替换字符串/选项 / 命令来练习。 元字符 元字符 说明 . 匹配任意字符 [abc] 匹配方括号中的任意一个字符。可以使用 - 表示字符范围,如[a-z0-9]匹 配小写字母和阿拉伯数字。 \d 匹配阿拉伯数字,等同于[0-9]。 [^abc] 在方括号内开头使用^符号,表示匹配除方括号中字符之外的任意字符。 \d 匹配阿拉伯数字,等同于[0-9]。 \D 匹配阿拉伯数字之外的任意字符,等同于[^0-9]。 \x 匹配十六进制数字,等同于[0-9A-Fa-f]。 \X 匹配十六进制数字之外的任意字符,等同于[^0-9A-Fa-f]。 \w 匹配单词字母,等同于[0-9A-Za-z_]。 \W 匹配单词字母之外的任意字符,等同于[^0-9A-Za-z_]。 \t 匹配字符。 \s 匹配空白字符,等同于[ \t]。 \S 匹配非空白字符,等同于[^ \t]。 *、.、/ 等,可以在这些字符前面添加 \,表示这些不是元字符,而是普通字符。比如:\/d 匹配的是 /d这两个字符,而不是匹配任意数字。 表示数量的元字符 元字符 说明 * 匹配0-任意个 \+ 匹配1-任意个 \? 匹配0-1个 \{n,m} 匹配n-m个 \{n} 匹配n个 \{n,} 匹配n-任意个 \{,m} 匹配0-m个 表示位置的符号 元字符 说明 $ 匹配行尾 ^ 匹配行首 \< 匹配单词词首 \> 匹配单词词尾 使用示例 命令 描述 /char\s\+[A-Za-z_]\w*; 查找所有以char开头,之后是一个以上的空白,最后是一个标识符和分号 /\d\d:\d\d:\d\d 查找如 17:37:01 格式的时间字符 :g/^\s*$/d 删除只有空白的行 :s/\<four\>/4/g 将所有的 four 替换成 4,但是 fourteen 中的 four 不替换 替换变量 \( 和 \) 符号括起正规表达式,即可在后面使用\1、\2 等变量来访问 \(和 \) 中的内容。 使用示例 命令 描述 /\(a\+\)[^a]\+\1 查找开头和结尾处a的个数相同的字符串,如 aabbbaa,aaacccaaa,但是不匹配 abbbaa :s/\(http:\/\/[-a-z\._~\+%\/]\+\)/<a href="\1">\1<\/a>/ 将 url 替换为http://url的格式 :s/\(\w\+\)\s\+\(\w\+\)/\2\t\1 将 data1 data2 修改为 data2 data1 函数式 :s/{pattern}/{string}/[flags] 中可以使用函数表达式来书写替换内容,格式为 :s/替换字符串/\=函数式 submatch(1)、submatch(2) 等来引用 \1、\2 等的内容,而submatch(0)可以引用匹配的整个内容。 使用例 :%s/\<id\>/\=line(".") :%s/^\<\w\+\>/\=(line(".")-10) .".". submatch(1) 与 Perl 正则表达式的区别 Vim语法 Perl语法 含义 \+ + 1-任意个 \? ? 0-1个 \{n,m} {n,m} n-m个 \( 和 \) ( 和 ) 分组 贪婪模式和非贪婪模式 a.*b 会尽可能多滴匹配字符,在 ahdbjkbkls 中匹配 ahdbjkb 而不是 ahdb。 \{-} 代替 *,即 a.\{-}b 匹配 ahdb 而不是 ahdbjkb。 Neovim 中使用正则表达式 =~# 和 =~ 比较符。但是 Neovim 提供了一个 vim.regex Lua 模块。 vim.regex 模块常见的使用方式: if vim.regex('http[s]*://'):match_str(str) then -- do something end local str = 'abc12332324def' local r1 = vim.regex('\\d\\+') print(r1:match_str(str)) print(str:sub(r1:match_str(str))) local a, b = r1:match_str(str) print(str:sub(a + 1, b))

2024/12/29
articleCard.readMore

Neovim 状态栏及标签栏点击事件

什么是 tablineat 测试标签栏点击事件 多次点击存在的问题 什么是 tablineat Buffer 切换的功能, 但是似乎直到现在,Vim 都未增加该功能。 2016 年,Neovim 增加了 tablineat 特性,使得用户可以在自定义状态栏和标签栏时, 指定某个区域设定对应的点击事件回调函数,以此实现监控状态栏和标签栏的点击事件。可以使用 has('tablineat') 检测当前 Neovim 是否支持该特性。 以下内容摘自 :h statusline @ N Start of execute function label. Use %X or %T to end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this label runs the specified function: in the example when clicking once using left mouse button on "foo.c", a `SwitchBuffer(10, 1, 'l', ' ')` expression will be run. The specified function receives the following arguments in order: 1. minwid field value or zero if no N was specified 2. number of mouse clicks to detect multiple clicks 3. mouse button used: "l", "r" or "m" for left, right or middle button respectively; one should not rely on third argument being only "l", "r" or "m": any other non-empty string value that contains only ASCII lower case letters may be expected for other mouse buttons 4. modifiers pressed: string which contains "s" if shift modifier was pressed, "c" for control, "a" for alt and "m" for meta; currently if modifier is not pressed string contains space instead, but one should not rely on presence of spaces or specific order of modifiers: use |stridx()| to test whether some modifier is present; string is guaranteed to contain only ASCII letters and spaces, one letter per modifier; "?" modifier may also be present, but its presence is a bug that denotes that new mouse button recognition was added without modifying code that reacts on mouse clicks on this label. Use |getmousepos()|.winid in the specified function to get the corresponding window id of the clicked item. 测试标签栏点击事件 %N@function_name@text%X 来指定区域监控点击事件, 回调函数的名称为两个 @ 标记之间的字符串,% 与 @ 之间的数字作为第一个参数。 写一个测试函数,看一看点击事件的具体执行效果: function! OnClick(a, b, c, d) abort echom a:a echom a:b echom a:c echom '>' . a:d . '<' endfunction set tabline=xxxxxxx%1@OnClick@11111%X%2@OnClick@22222%X :so % 后,状态栏变成了 xxxxxxx1111122222, 鼠标左键单击 11111 区域,看到如下输出。 1 1 l > < 第一个参数是 % 与 @ 之间的数字 第二个参数是鼠标点击的次数 第三个参数是鼠标键位, 可以是左键(l)、右键(r)或者中键(m) 第四个参数对应的时按下的组合键,Ctrl 键(c)、Shift 键(s)、Alt 键(a)、Meta 键(m) 最后一个参数是对应的 shift/alt/ctrl。 多次点击存在的问题 function! OnClick(a, b, c, d) abort echom '点击次数:' .. a:b endfunction set tabline=xxxxxxx%1@OnClick@11111%X%2@OnClick@22222%X 点击次数:1 点击次数:2 点击次数:3 点击次数:4 点击次数:1 点击次数:2 点击次数:3 点击次数:4 点击次数:1 点击次数:2 点击次数:1 点击次数:2 点击次数:3 点击次数:4 点击次数:1 点击次数:2 mousetime 时,点击次数计数归零, 间隔时间可以查阅 :h mousetime, 默认为 500 毫秒。

2024/12/28
articleCard.readMore

代码格式化插件 format.nvim

前因 插件的安装 自定义 formatter 插件的使用 Markdown 代码块格式化 前因 Reddit 上面分享了一个新的代码格式化的插件 format.nvim, 回复大多是是在问为什么要做这个插件以及跟现有插件的区别。 在 SpaceVim v2.3.0 之前,一直使用的代码格式化插件是 neoformat, 这个插件是使用 Vim 脚本写的,同时支持 Neovim 和 Vim。 但是美中不足的地方是这个插件执行格式化命令是调用 Vim 的 system() 函数,当命令执行消耗时间很长时,就会卡住界面无法进行下一步操作。 我也尝试过给 neoformat 增加异步支持,但是最终还是决定重新开发一个插件, 主要市考虑到以下几个原因: Lua 尤其是 luajit 速度相较于 Vim 脚本要快很多,相关比较文章可以查看我前面写的使用 Lua 重写 SpaceVim 内置插件和Vim9Script 与 Lua 的速度比较 做一个格式化插件的框架,尽可能减少默认的 formatter,提供接口自行定义。 不需要太过复杂的功能,仅仅是异步执行指定命令并更新 Neovim 缓冲区。 format.nvim。 这个插件使用了 SpaceVim 的 job api 可以异步执行格式化命令,使得 Neovim 的操作更加顺畅。 插件的安装 nvim-plug来进行安装: require('plug').add({ { 'wsdjeg/format.nvim', cmds = { 'Format' }, depends = { { 'wsdjeg/job.nvim' }, { 'wsdjeg/notify.nvim' }, }, }, }) [[layers]] name = 'format' format_method = 'format.nvim' 自定义 formatter require('format').setup({ custom_formatters = { lua = { exe = 'stylua', args = { '-' }, stdin = true, }, }, }) 插件的使用 格式化整个文件 :Fromat, 执行该命令时就可以根据当前文件的文件类型选择对应的 formatter 对整个 Buffer 进行格式化。 选中区域进行格式化 :Format 命令支持指定区域进行格式化,因此可以在 Neovim 中选中几行代码进行格式化。 指定文件类型 :Format 命令时,会读取当前 Buffer 的 &filetype 选项,但是如果需要指定其他文件类型,比如 java,可以使用如下格式执行命令: :Format! <filetype> 指定格式化工具 :Format prettier 同时指定文件类型、格式化工具,比如选中 markdown 内一段代码块进行格式化: :Format! <filetype> <formatter> Markdown 代码块格式化 :Format 命令支持指定区域格式,这里需要借助一个插件 context_filetype.vim。 通过这个插件获取到代码块的区域和文件类型,传给 Format 命令。 以下示例使用 <Leader>f 格式化光标所在的代码块: function! s:format_code_block() abort let cf = context_filetype#get() if cf.filetype !=# 'markdown' let command = printf('%s,%sFormat! %s', cf.range[0][0], cf.range[1][0], cf.filetype) exe command endif endfunction nnoremap <silent> <Leader>f <cmd><sid>format_code_block()<cr>

2024/12/25
articleCard.readMore

在 SpaceVim 中使用卢曼卡片盒笔记法做笔记

起因 基于 Neovim 的 zettelkasten 插件 基于 Tags 的 ZkBrowser 使用 Telescope 检索 Tags 使用 Telescope 检索标题 补全笔记 ID 笔记模板的筛选 Vim 的支持 参考文章: 起因 flygrep 插件根据记忆的关键字倒是可以找到相关的内容, 但是随着笔记越来越多,关键字筛选已经不足以快速定位了。 于是就想找一个更好的方式来组织管理日常的笔记,后来就了解到了《卢曼卡片盒笔记法》 基于 Neovim 的 zettelkasten 插件 vim-zettelkasten。 基于 Tags 的 ZkBrowser 快捷键 功能描述 q 退出 ZkBrowser 窗口 <LeftRelease> 鼠标左键点击 Tag,筛选包含该 Tag 的笔记 gf 打开光标 ID 下的笔记 Ctrl-l 清除 Tags 筛选,列出所有笔记 Ctrl-] 使用 preview-window 窗口预览笔记 [I 使用 quickfix-window 列出 References F2 打开 Tags 列表侧栏 使用 Telescope 检索 Tags SPC m z g。 使用 Telescope 检索标题 SPC m z f。 补全笔记 ID ctrl-x ctrl-u 来打开补全窗口,补全引用的笔记 ID。 笔记模板的筛选 [[layers]] name = 'zettelkasten' zettel_dir = 'D:\me\zettelkasten' zettel_template_dir = 'D:\me\zettelkasten_template' SPC m z n 快捷键即可,如果需要基于其他模板来新建,可以使用快捷键 SPC m z t 调用 Telescope 检索常用模板。 Vim 的支持 +lua 特性。 参考文章: 什么是 Zettelkasten 卡片盒笔记法? 卢曼:与卡片盒交流 卢曼:学习如何阅读

2024/12/11
articleCard.readMore

《好东西》观后感

关于“善意的谎言” 一些其他想法 关于“善意的谎言” 一些其他想法 其实,说实在的,电影不是特别的好看,很多情节、对话太过于刻意。 就感觉正常的两个人对话,不该是这样,很多时候刻意的制造一些“鸡汤”类的说教, 或者刻意的制造一些搞笑的对话。 但是,其中确实有很多“鸡汤”值得去好好想想,比如: 1、小女孩上台表演前的那段对话,有时候因为害怕别人的眼光、说辞、看法, 而退缩,其实,这个社会上,在意你的人,真的没有你想象的那么多。 可能明天,他就会忘记你所做的事。 2、又比如小孩的作文,我最喜欢的、以及出现了两次的跟幻想相关的话题。 我觉得小孩子还没有这样的思想,更多的是电影呈现给观众的一些观点。 是的,即便是现在,后面的生活,还有很多年,你永远不会知道自己最喜欢的将会是什么,还是要保留一些幻想。

2024/11/25
articleCard.readMore

《重生》:揭穿人性假面

周末跟家人们去看了一部电影,小朋友们选了柯南,我就去看了张家辉主演的《重生》。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; } 剧中角色: 安渡(阮经天 饰) 安渡缉毒组小队长,被其舅舅安佩长期长期压抑不得势再加上自幼经历悲惨,造成了阴暗的性格,被权力、金钱迷失倒也正常。 人性这种东西其实很奇怪,不能去深究其好坏。 张耀(张家辉 饰) 张耀的剧情也算还能理解,想要在监狱里替换掉沙旺,并且通过各种检查,没有安渡他们的安排跟支持几乎不可能。 巴莱(马浴柯 饰) 完全没有想到的是南茜和巴莱也是也是提前安排好的碟中谍角色。

2024/9/8
articleCard.readMore

删除 Telegram 账号

前不久,删除了用了很久的 Telegram 账号 @wsdjeg。 为了避免非本人使用该用户名带来不必要的麻烦,原来的用户名转移给了一个无管理员的公共的 Channel。 个人是非常抵触使用手机号注册和登录的软件,因为手机号实际上运营商是可以二次销售的。 大学期间至今,前面大概有三个手机号已经停机不用了,但是现在搜了下,居然有别人已经用它来注册了微信。 此外,如果提供足够的材料或者“特殊情况”下,运营商可以重新制作一张 SIM 卡,原 SIM 会废弃。 那么新卡就可以接受验证信息,这就意味着,对于账号的所有权,你没有绝对的控制权。 此外,删除 Telegram 另一个原因是在于自己,我想远离不必要的争执。不管是什么原因,有可能是对方问题。 也有可能是自己的问题。但是这些都并不重要,因为这种争执对于自己来说。似乎没有任何用处。只会徒增烦恼、浪费时间。 可能是因为匿名状态下更加肆无忌惮吧。

2024/8/24
articleCard.readMore

Vim Script 的新语法

字符串的连接符 函数的参数 闭包(closure) 字符串的连接符 .. 符号来连接字符串。 let s:foo = 'abc' . 'def' " 可以改成 let s:foo = 'abc' .. 'def' 函数的参数 a:0、 a:000 或者 a:foo 等等。 a: 实际上是一个 dict 变量, 可以看下具体包含哪些 key: function! s:foo(x, y, ...) echom a: endfunction 1,2call s:foo(1, 2, 3) so % 输出如下: {'0': 1, '000': [3], '1': 3, 'y': 2, 'firstline': 1, 'x': 1, 'lastline': 2} {'0': 1, '000': [3], '1': 3, 'y': 2, 'firstline': 1, 'x': 1, 'lastline': 2} a: 字典变量中 key 包括: 0: 可变参数数量, 可变参数是 1 个 3 000: 可变参数组成的列表 x 或者 y: 特定名称的参数 key 值,firstline 和 lastline, 这个是传入的 range, 同时这意味着在定义函数时,参数名称不可以是 firstline 和 lastline。 否则会有如下错误: E125: Illegal argument: firstline optional-function-argument, 具体写法如下: function! s:foo(x, y = 3, ...) echom a: endfunction call s:foo(1) {'0': 0, '000': [], 'y': 3, 'firstline': 1, 'x': 1, 'lastline': 1} s:foo 函数时,虽然只传入了一个参数,但是在函数内部 a: 任然有一个 y key,其值为默认值 3。 闭包(closure) :function 定义增加了 closure 属性。示例如下: function! s:foo(x) function! s:xy(y) closure return pow(a:y, a:x) endfunction return funcref('s:xy') endfunction let s:a = s:foo(2) let s:b = s:foo(3) echo s:a(2) echo s:b(2) closure 关键字,在函数 s:xy 内部是无法调用 a:x 的。 其实,在 Lua 中也可以实现类似的功能: local function foo(x) return function(y) return y^x end end local a = foo(2) local b = foo(3) print(a(2)) print(b(2)) 4.0 8.0

2024/8/21
articleCard.readMore

龙迹之城散人玩家攻略

定期活动 第一天:开区 第八天:狂暴降临 第十五天:末日降临 第二十二天:秒杀直购 第二十九天:神罚套装 第三十六天:炎阳套装 周年庆活动 装备合成技巧 符文提升技巧 龙子提升技巧 异兽提升 兽神出战 定期活动 第一天:开区 第八天:狂暴降临 第十五天:末日降临 第一岛选“元宝专享” 第二岛选“遗迹特权” 第三岛选末日 第四岛选“灵石专享” 第五岛选末日 第六岛选狐狸战灵 第七岛选“魔石专享” 第八岛选末日 第二十二天:秒杀直购 第二十九天:神罚套装 第一岛选“灵符加成” 第二岛选“禁地双倍” 第三岛选神罚 第四岛选“传奇专享” 第五岛选神罚 第六岛选圣光精灵 第七岛选“兽神装备任选” 第八岛选神罚 第三十六天:炎阳套装 周年庆活动 装备合成技巧 武器 属性 匹配 血 吸血 爆裂 (爆 + 攻),全熔炼 爆 暴击几率 怒血 (血 + 攻) 攻 攻击增伤 破天 (血 + 爆) 衣服 属性 匹配 防 物伤减免、魔伤减免 不灭(抗 + 反) 抗 暴击抵抗 金刚 反 反伤 神体 头盔 属性 匹配 防 物伤减免、魔伤减免 天命(护 + 闪) 护 破甲抵抗 玄体 闪 吸血抵抗 龙体 项链 属性 匹配 血 吸血 斩神(破 + 攻),全熔炼 破 无视双防 怒血(血 + 攻) 攻 攻击增伤 弑天(血 + 破) 手镯 属性 匹配 固 暴伤减免 天怒(破 + 闪) 破 无视双防 无双 闪 吸血抵抗 爆破 戒指 属性 匹配 攻 攻击增伤 戮魔(爆 + 狂) 爆 暴击几率 灭神 狂 暴击增伤 破天 鞋子 属性 匹配 防 物伤减免、魔伤减免 破军 御 暴击抵抗 真魂(防 + 护) 护 破甲抵抗 神体 腰带 属性 匹配 防 物伤减免、魔伤减免 天命(御 + 护) 御 暴击抵抗 不灭 护 破甲抵抗 混元 符文提升技巧 试炼之地:乾,坤,离,坎,兑, 禁地:震,艮,巽 龙子提升技巧 禁地:睚眦、蒲牢、霸下 龙眠神殿:其他 6 个 异兽提升 七天登录送:圣兽白虎、雪域狂狮、荆棘地龙、圣兽青龙, 地龙比白虎和狂狮高一阶,青龙比地龙高一阶 任选开:暗殿麒麟 兽神出战 妖狱进地图的条件依赖于出战的兽神级别,优先让最高级出战,点击兽神,会显示兽神装扮是否齐全,提示是否被其他兽神装备了。 出战最高级别兽神后,进入地图,只打最高级别的boss。

2024/8/21
articleCard.readMore

使用 GitHub 进行身份验证时要小心

起因 授权登录时需要的权限 查看 Github 账户已授权网站 国内版 Github? 起因 授权登录时需要的权限 查看 Github 账户已授权网站 Integrations -> Applications -> Authorized OAuth Apps: 如果需要看各自的授权权限点开即可查看: 如果认为该权限太高,可以点击右侧的 Revoke access。 国内版 Github? 写用户的 Followers 做什么?

2024/8/12
articleCard.readMore

请停止复制我的个人网站

今天使用 Google 搜索自己之前发过的一篇文章“停用 v2ex 账号”, 发现搜索结果如下。奇怪的是, 有一个域名为 jbwqfy.com 的网站居然是全站复制了我的个人博客。 打开其首页,我今日刚发的文章他都有,基本上是实时同步了,目前无法查明原因。 whois 查询结果显示如下,并没有什么有效信息。

2024/8/11
articleCard.readMore

从 GitHub Page 切换至 Cloudflare Page

新建 Cloudflare Page 使用最新版 Jekyll 及 插件 自定义域名 Github Page 服务切换到了 Cloudflare Page。 一开始切换的原因是因为 Github 强制用户使用两步验证才能登录,失去了对 Github 账号的访问权限, 因此将网站移到了 Cloudflare Page 服务上。 新建 Cloudflare Page Workers & Pages -> Create -> Connect to Git, 选择对应的仓库, 值得开心的是这里可以选择私有仓库。Github Page 服务免费用户无法使用私有仓库托管 Jekyll 静态网站。 构建设定: Build command: bundle exec jekyll build Build output directory: /_site Root directory: /docs Build comments on pull requests: Enabled 使用最新版 Jekyll 及 插件 github-pages 对应的 Jekyll 版本太老,语法高亮的插件 rouge 也非常老旧。 在这里既然已经手动执行命令构建,那么可以使用一些比较新的插件,而不受 Github Page 的限制。 修改 docs/Gemfile 为: source "https://rubygems.org" gem "jekyll" gem 'jekyll-redirect-from' gem 'rouge', '~> 4.2' gem 'jekyll-paginate' 自定义域名 原先购买的域名已经转移到了 Cloudflare 上面了,这样一来域名的解析及网站的托管都在同一个平台,也方便管理。

2024/8/11
articleCard.readMore

Vim9Script 与 Lua 的速度比较

前面写过一篇Lua 与 VimL 速度比较的文章, 里面对 Lua 与 VimL 脚本计算速度做了比较。随着 Vim 的更新并且推出了新的脚本语言 Vim9Script, 也想看一下这个新的脚本语言写法及执行效率到底如何。 任然参考上一篇文章中的比较方式,Vim9Script 实现的函数如下: vim9script def V9Fibo(N: string) var start = reltime() var t = str2nr(N) var b = 0 while t > 0 t = t - 1 var a = 1 b = 1 var c = 73 while c > 0 c = c - 1 var tmp = a + b a = b b = tmp endwhile endwhile var sec = reltimefloat(reltime(start)) echo b echom printf('Vim9Fibo(%s): %.6g sec', N, sec) enddef defcompile command! -nargs=+ TestV9Func V9Fibo(<f-args>) :so % 载入脚本,分别执行如下测试命令: :TestV9Func 1000 :TestV9Func 10000000 Vim9Fibo(1000): 0.007455 sec Vim9Fibo(10000000): 69.937198 sec Fibo(1000): 0.410364 sec Fibo(10000000): 1470.280914 sec LuaFibo(1000): 9.052000e-4 sec LuaFibo(10000000): 1.235385 sec 0.41s 提升到了 0.0074s; 计算参数一千万时,从 1470s 提升到了 69s。 但是相较于 Lua 的 9.0e-4s 和 1.23s 的测试结果,还是相差太多。

2024/8/10
articleCard.readMore

Neovim 和 Vim 插件管理器的实现逻辑

插件的本质 目录结构及载入时机 autoload/ 目录 plugin/ 目录 ftplugin/ 目录 懒加载的实现逻辑 通过命令加载 on_cmd 根据函数加载 on_func 根据事件加载 on_event 根据文件类型加载 on_ft 根据按键加载 on_map 总结 Vundle.vim, 后来尝试过 vim-plug 和 neobundle.vim, 目前使用的是 dein.vim。当然了,网上搜一下,其实还有很多其他的插件管理器, 然而并没有发现什么特别吸引我的功能,所以也就没有什么动力切换了。 相较于切换插件管理器,其实我更想好好研究一下插件管理器的实现逻辑,于是看了一些文档及插件管理器的源码,整理这篇文章。 插件的本质 plugin/、color/ 等目录下 lua 文件的识别。 不管是 Vim 还是 Neovim,插件管理器添加一个插件的本质就是将插件所在的目录加入到 runtimepath 选项, 所谓的懒加载(lazy load)实际上是在适当的时机下触发前面这一步骤。 所以,很早以前,还没有插件管理器时,很多时候只是将插件下载到某一个目录,并在 vimrc 中将目录添加到 runtimepath 中。 set runtimepath+=/path/to/plugin_directory 目录结构及载入时机 :h runtimepath 可以看到标准的插件的目录结构包括: filetype.lua filetypes autoload/ automatically loaded scripts colors/ color scheme files compiler/ compiler files doc/ documentation ftplugin/ filetype plugins indent/ indent scripts keymap/ key mapping files lang/ menu translations lua/ lua modules menu.vim GUI menus pack/ packages parser/ treesitter syntax parsers plugin/ plugin scripts queries/ treesitter queries rplugin/ remote-plugin scripts spell/ spell checking files syntax/ syntax files tutor/ tutorial files ~/wsdjeg/bundle/hello-vim,示例 vimrc 文件: set nocompatible set rtp+=~/wsdjeg/bundle/hello-vim filetype plugin indent on syntax on vimrc 文件。其中 set rtp 这一行就是将插件 hello-vim 的目录加入到 runtimepath 内。 下一行 filetype plugin indent on,这个命令包含了三层意思: 启动文件类型自动识别,比如打开 *.md 文件时,自动识别并设置 filetype 为 markdown。 设置 filetype 时,根据 &filetype 的值,自动读取并执行 runtimepath 中每一个目录下 ftplugin/ 子目录里对应的 vim 文件 设置 filetype 时,根据 &filetype 的值,自动读取并执行 runtimepath 中每一个目录下 indent/ 子目录里对应的 vim 文件 :filetype 命令的使用,:h :filetype-overview 显示如下帮助文档: Overview: *:filetype-overview* command detection plugin indent ~ :filetype on on unchanged unchanged :filetype off off unchanged unchanged :filetype plugin on on on unchanged :filetype plugin off unchanged off unchanged :filetype indent on on unchanged on :filetype indent off unchanged unchanged off :filetype plugin indent on on on on :filetype plugin indent off unchanged off off To see the current status, type: > :filetype The output looks something like this: > filetype detection:ON plugin:ON indent:OFF vimrc ~/wsdjeg/bundle/hello-vim/plugin/*.vim hello-vim/ 目录下有 filetype.vim 文件,或者存在 ftdetect/ 子目录,其内 *.vim 文件也会被执行。 autoload/ 目录 autoload/ 内的 Vim 文件载入机制,我们来做这样一个测试: 文件名: ~/.SpaceVim.d/autoload/test_autoload.vim,假定 ~/.SpaceVim.d/ 已经加入 runtimepath。 echom "hi one" function! test_autoload#hi() echom "hi two" endfunction echom "hi three" echom 语句都没有执行,这也说明了 autoload 内的文件默认是不会自动执行的。 此时如果执行函数 call test_autoload#hi()。就会发现如下输出: hi one hi three hi two hi three 在 hi two 前面。 这是因为当调用 test_autoload#hi() 函数时,Vim 是根据函数的名称,确认下函数是否已定义,如果未定义, 则去找到对应的 Vim 文件读取并逐行执行,执行完了后才会调用函数。 前面执行过了 call test_autoload#hi() 函数后,如果再次调用。此时输出只会有一行: hi two call test_autoload#no() 函数,会发生什么呢? hi one hi three E117: Unknown function: test_autoload#no test_autoload#no() 函数未定义, Vim 根据函数名称会再次去读取并逐行执行 Vim 文件,也就导致了 Vim 文件中的脚本被重复执行了。不受控的重复执行肯定不行的,那么如何规避呢? 可以像下面这样修改: if exists('s:loaded') finish endif " 有很多插件喜欢用 g:plugin_xxx_loaded, 个人感觉,非必要不使用暴露给脚本外部的变量跟函数。 let s:loaded = 1 echom "hi one" function! test_autoload#hi() echom "hi two" endfunction echom "hi three" plugin/ 目录 vimrc 读取完成后, 此时会对 runtimepath 下每一个目录下的 plugin/ 子目录进行遍历。其内所有的 Vim 文件会被逐一读取并执行。 Neovim 增加了 plugin/ 目录下 lua 文件的支持。 ftplugin/ 目录 on_ft 加载机制,当 filetype 被设定时会自动载入 ftplugin/<filetype>.vim,Neovim 还支持 Lua 格式文件 ftplugin/<filetype>.lua。 懒加载的实现逻辑 Lazy load(懒加载)的功能,那么这个功能到底是如何实现,其本质逻辑是什么呢? 通过命令加载 on_cmd call dein#add('wsdjeg/FlyGrep.vim', {'on_cmd' : 'FlyGrep'}) 此时会定义如下命令: dein.vim/autoload/dein/parse.vim#L342 function! s:generate_dummy_commands(plugin) abort let a:plugin.dummy_commands = [] for name in a:plugin.on_cmd " Define dummy commands. let raw_cmd = 'command ' \ . '-complete=customlist,dein#autoload#_dummy_complete' \ . ' -bang -bar -range -nargs=* '. name \ . printf(" call dein#autoload#_on_cmd(%s, %s, <q-args>, \ expand('<bang>'), expand('<line1>'), expand('<line2>'))", \ string(name), string(a:plugin.name)) call add(a:plugin.dummy_commands, [name, raw_cmd]) silent! execute raw_cmd endfor endfunction :FlyGrep 实际上时调用 dein#autoload#_on_cmd 函数 根据函数加载 on_func autoload/ 目录下的 Vim 脚本文件在 Vim 启动时是不会自动执行的。 只有在调用对应的函数时才会载入并执行。 可能是为了更加极致的 Lazy load, 让非载入的插件完全隐藏吧。那么 on_func 这个功能到底是如何实现的呢? (Neo)Vim 有一个事件叫做 FuncUndefined,是在调用不存在的函数时触发. 下面是一段简单的 on_func 实现。 let s:lazy_plugins = [] function! Plug(path, opt) if !empty(a:opt) " 直接 添加 rtp return endif call add(s:lazy_plugins, [a:path, a:opt]) endfunction function! s:on_func(f) for [path, opt] in s:lazy_plugins if opt.on_func =~# a:f " 载入路径 path 的插件 endif endfor endfunction augroup test_on_func autocmd! autocmd FuncUndefined * call s:on_func(expand('<afile>')) augroup END call Plug('~/.SpaceVim.d/hello-vim', { \ 'on_func' : 'hello#' \ }) 根据事件加载 on_event on_cmd 和 on_func 的实现,on_event 的实现相对简单一些, 仅仅是监控事件的发生与否,对于发生时的 <amatch> 及 <afile> 不做判断。 以下为简单的实现逻辑: let s:lazy_plugins = [] augroup test_on_event autocmd! augroup END function! s:load(path) " 载入路径为 path 的插件 endfuntion function! Plug(path, opt) if !empty(a:opt) call s:load(a:path) return endif if has_key(a:opt, 'on_event') for event in a:opt.on_event exe printf('autocmd test_on_event %s call s:load("%s")', event, a:path) endfor endif endfunction call Plug('~/.SpaceVim.d/hello-vim', { \ 'on_event' : ['InsertEnter', 'WinEnter'] \ }) 根据文件类型加载 on_ft on_event 懒加载模式。前面也说到 on_event 懒加载不判断触发事件时的 <amatch> 和 <afile> 值。 而 on_ft 模式的懒加载底层逻辑是只监听 FileType 这一种事件,并且在事件触发时判断 <amatch> 值。 on_ft 懒加载的简单实现代码如下: let s:lazy_plugins = [] augroup test_on_ft autocmd! augroup END function! s:load(path) " 载入路径为 path 的插件 endfuntion function! Plug(path, opt) if !empty(a:opt) call s:load(a:path) return endif if has_key(a:opt, 'on_ft) for ft in a:opt.on_ft exe printf('autocmd test_on_ft FileType %s call s:load("%s")', ft, a:path) endfor endif endfunction call Plug('~/.SpaceVim.d/hello-vim', { \ 'on_ft' : ['java', 'python'] \ }) 根据按键加载 on_map function! s:generate_dummy_mappings(plugin) abort let a:plugin.dummy_mappings = [] let items = type(a:plugin.on_map) == v:t_dict ? \ map(items(a:plugin.on_map), \ { _, val -> [split(val[0], '\zs'), \ dein#util#_convert2list(val[1])]}) : \ map(copy(a:plugin.on_map), \ { _, val -> type(val) == v:t_list ? \ [split(val[0], '\zs'), val[1:]] : [['n', 'x'], [val]] }) for [modes, mappings] in items if mappings ==# ['<Plug>'] " Use plugin name. let mappings = ['<Plug>(' . a:plugin.normalized_name] if stridx(a:plugin.normalized_name, '-') >= 0 " The plugin mappings may use "_" instead of "-". call add(mappings, '<Plug>(' . \ substitute(a:plugin.normalized_name, '-', '_', 'g')) endif endif for mapping in mappings " Define dummy mappings. let prefix = printf('dein#autoload#_on_map(%s, %s,', \ string(substitute(mapping, '<', '<lt>', 'g')), \ string(a:plugin.name)) for mode in modes let raw_map = mode.'noremap <unique><silent> '.mapping \ . (mode ==# 'c' ? " \<C-r>=" : \ mode ==# 'i' ? " \<C-o>:call " : " :\<C-u>call ") \ . prefix . string(mode) . ')<CR>' call add(a:plugin.dummy_mappings, [mode, mapping, raw_map]) silent! execute raw_map endfor endfor endfor endfunction on_map 的实现机制跟 on_cmd 有点类似,实际上时定义了新的快捷键映射,触发时去调用 dein#autoload#on_map 函数。 再在这个函数内载入对应的插件,载入完成后使用 feedkey() 函数去模拟按下触发的快捷键映射。 总结 本文主要介绍了 (neo)vim 插件的基本目录结构以及各个目录下文件的载入机制。 充分利用好这些机制后,即便是没有使用懒加载机制的情况下也可以大大的提高插件的初始化体验。 除此之外,本文还简单介绍了目前常见的几种赖加载机制的实现底层原理。 最后,说明一下,文中示例代码仅限于体现功能实现的基本逻辑,并未做额外的条件判断及错误捕获,并不适用于直接使用。

2024/8/8
articleCard.readMore

实时调整 Neovim 的颜色主题

需求背景 早期功能实现 在主题设定后方直接修改 使用 ColorScheme 事件调用覆写函数。 使用 cpicker.nvim 实时调色 需求背景 早期功能实现 在主题设定后方直接修改 colorschem one hi VertSplit guibg=#282828 guifg=#181A1F :colorscheme 命令切换主题,那配置文件中的微调将不再生效。 使用 ColorScheme 事件调用覆写函数。 autocmd ColorScheme * call s:fix_colorscheme() function! s:fix_colorscheme() " 使用 g:colors_name 判断主题名称 " 再此使用 highlight 命令修改高亮组 endfunction 使用 cpicker.nvim 实时调色 介绍过我的 cpicker.nvim 插件,随着这个插件做出来后,借助它的调色板,又实现了一个实时调整高亮组的功能。并且,调整的结果会存住下来。 重启 Neovim 后,对应的颜色主题的修改还任然存在。 操作步骤如下: 将光标移动到需要调色的位置 使用 :CpickerCursorChangeHighlight 命令调出调色板浮窗,此时调色板默认颜色就是刚刚光标所在位置前景色。 上下移动光标至对应元素,左右进行调整,此时背景窗口中原先的字段高亮也会跟着变化。 调整完成后,使用 q 按键关闭调色板浮窗。 :CpickerClearColorPatch 命令。

2024/8/3
articleCard.readMore

悬浮滚动条插件 scrollbar.vim

插件简介 问题反馈 安装及使用 插件简介 scrollbar.vim。 这是一个支持 Neovim 和 Vim 的悬浮滚动条插件, 使用的是 Neovim 的 floating windows 或者 Vim 的 popup windows 特性。 相关内容可以阅读: Neovim 的 :h api-floatwin 或者 Vim 的 :h popup-window。 之所以前面的新字加上引号,是因为实际上这个插件早在三年多前就已经在 SpaceVim 中使用了,是参考 Xuyuanp/scrollbar.nvim 逻辑重新使用 Vim Script 实现的。 在 SpaceVim 仓库下执行::Git log autoload/SpaceVim/plugins/scrollbar.vim * f1617ae12 - Add scrollbar support (#3826) (Wang Shidong 3 years, 10 months ago) if has('nvim-0.9.0') lua require('spacevim.plugin.scrollbar').show() else call SpaceVim#plugins#scrollbar#show() endif 问题反馈 bundle/scrollbar.vim 目录中,使用 Github Action 同步到单独的仓库以便于直接使用。 如果在使用过程中遇到问题,或者有新的想法建议,欢迎在 issue tracker 留言反馈。 安装及使用 [[layers]] name = 'ui' enable_scrollbar = true vim-plug Plug 'wsdjeg/scrollbar.vim' lazy.vim -- lazy.nvim { "wsdjeg/scrollbar.vim", event = "VeryLazy", }

2024/7/30
articleCard.readMore

关于 Neovim 插件开发的指南

今天,在 Neovim 中文电报群有人分享了一个仓库 nvim-best-practices@24e835c, 是关于 Neovim 插件开发的一些“指南”,或者可以说是“建议”,当然我也回复了我的想法,抱歉,语言有些过激。 花花里胡巧的限制,我只看了开头就看不下去了,举例,命令要分成子命令, 这个除了浪费一层判断,没啥用。本来只定义一个FooInstall命令, 实际上在底层c就已经判断出来对应的函数。结果按照他这样, 需要在viml或者lua这边判断下到底这个命令是做什么的 跟谷歌的viml style guide相比差远了。另外手机上打开毫无阅读体验可谈。 合并到 Neovim 主仓库。 那我觉得就对我自己包括后来的 Neovim 用户都会有很大的影响。 因此我觉得有必要针对这个指南,写一些东西了。 逐行看一下,因为 Pull Request 的分支的一直在变动,而且是 force push,:(,因此原文在文章中有直接文字体现。 *lua-plugin.txt* Nvim :h lua-plugin 很容易让人误解,也很难将内容与这个关键词联想在一起。 如果是针对于命令定义、按键映射定义的建议,那么就不局限于 Lua 了。 而且,这个不是某个 plugin 的文档,而是插件开发的建议,:h plugin-dev 或者 :h plugin-dev-guide 感觉更加合适一些。 NVIM REFERENCE MANUAL Guide to developing Lua plugins for Nvim Type |gO| to see the table of contents. ============================================================================== Introduction *lua-plugin* This is a guide for getting started with Nvim plugin development. It is not intended as a set of rules, but as a collection of recommendations for good practices. For a guide to using Lua in Nvim, please refer to |lua-guide|. ============================================================================== Type safety *lua-plugin-type-safety* Lua, as a dynamically typed language, is great for configuration. It provides virtually immediate feedback. But for larger projects, this can be a double-edged sword, leaving your plugin susceptible to unexpected bugs at the wrong time. You can leverage LuaCATS https://luals.github.io/wiki/annotations/ annotations, along with lua-language-server https://luals.github.io/ to catch potential bugs in your CI before your plugin's users do. ------------------------------------------------------------------------------ Tools *lua-plugin-type-safety-tools* - lua-typecheck-action https://github.com/marketplace/actions/lua-typecheck-action - luacheck https://github.com/lunarmodules/luacheck for additional linting ============================================================================== User commands *lua-plugin-user-commands* Many users rely on command completion to discover available user commands. If a plugin pollutes the command namespace with lots of commands, this can quickly become overwhelming. Example: - `FooAction1 {arg}` - `FooAction2 {arg}` - `FooAction3` - `BarAction1` - `BarAction2` Instead of doing this, consider gathering subcommands under scoped commands and implementing completions for each subcommand. Example: - `Foo action1 {arg}` - `Foo action2 {arg}` - `Foo action3` - `Bar action1` - `Bar action2` FooInstall、FooUninstall,很明显就可以看出这两个命令的功能,根本没必要使用子命令。 当然,什么样情况下建议使用子命令呢?当多个命令的字面内容非常接近且难以区分时时,比如: FooInstall1 FooInstall2 FooInstall 1,FooInstall 2 或者,Foo install 1, Foo install 2 ------------------------------------------------------------------------------ Subcommand completions example *lua-plugin-user-commands-completions-example* In this example, we want to provide: - Subcommand completions if the user has typed `:Foo ...` - Argument completions if they have typed `:Foo {subcommand}` First, define a type for each subcommand, which has: - An implementation: A function which is called when executing the subcommand. - An optional command completion callback, which takes the lead of the subcommand's arguments. >lua ---@class FooSubcommand ---@field impl fun(args:string[], opts: table) ---@field complete? fun(subcmd_arg_lead: string): string[] < Next, we define a table mapping subcommands to their implementations and completions: >lua ---@type table<string, FooSubcommand> local subcommand_tbl = { action1 = { impl = function(args, opts) -- Implementation (args is a list of strings) end, -- This subcommand has no completions }, action2 = { impl = function(args, opts) -- Implementation end, complete = function(subcmd_arg_lead) -- Simplified example local install_args = { "first", "second", "third", } return vim.iter(install_args) :filter(function(install_arg) -- If the user has typed `:Foo action2 fi`, -- this will match 'first' return install_arg:find(subcmd_arg_lead) ~= nil end) :totable() end, -- ... }, } < Then, create a Lua function to implement the main command: >lua ---@param opts table :h lua-guide-commands-create local function foo_cmd(opts) local fargs = opts.fargs local subcommand_key = fargs[1] -- Get the subcommand's arguments, if any local args = #fargs > 1 and vim.list_slice(fargs, 2, #fargs) or {} local subcommand = subcommand_tbl[subcommand_key] if not subcommand then vim.notify("Foo: Unknown command: " .. subcommand_key, vim.log.levels.ERROR) return end -- Invoke the subcommand subcommand.impl(args, opts) end < See also |lua-guide-commands-create|. Finally, we register our command, along with the completions: >lua -- NOTE: the options will vary, based on your use case. vim.api.nvim_create_user_command("Foo", foo_cmd, { nargs = "+", desc = "My awesome command with subcommand completions", complete = function(arg_lead, cmdline, _) -- Get the subcommand. local subcmd_key, subcmd_arg_lead = cmdline:match("^Foo[!]*%s(%S+)%s(.*)$") if subcmd_key and subcmd_arg_lead and subcommand_tbl[subcmd_key] and subcommand_tbl[subcmd_key].complete then -- The subcommand has completions. Return them. return subcommand_tbl[subcmd_key].complete(subcmd_arg_lead) end -- Check if cmdline is a subcommand if cmdline:match("^Foo[!]*%s+%w*$") then -- Filter subcommands that match local subcommand_keys = vim.tbl_keys(subcommand_tbl) return vim.iter(subcommand_keys) :filter(function(key) return key:find(arg_lead) ~= nil end) :totable() end end, bang = true, -- If you want to support ! modifiers }) < ============================================================================== Keymaps *lua-plugin-keymaps* Avoid creating keymaps automatically, unless they are not controversial. Doing so can easily lead to conflicts with user |mapping|s. NOTE: An example for uncontroversial keymaps are buffer-local |mapping|s for specific file types or floating windows. A common approach to allow keymap configuration is to define a declarative DSL https://en.wikipedia.org/wiki/Domain-specific_language via a `setup` function. However, doing so means that - You will have to implement and document it yourself. - Users will likely face inconsistencies if another plugin has a slightly different DSL. - |init.lua| scripts that call such a `setup` function may throw an error if the plugin is not installed or disabled. As an alternative, you can provide |<Plug>| mappings to allow users to define their own keymaps with |vim.keymap.set()|. - This requires one line of code in user configs. - Even if your plugin is not installed or disabled, creating the keymap won't throw an error. Another option is to simply expose a Lua function or |user-commands|. However, some benefits of |<Plug>| mappings over this are that you can - Enforce options like `expr = true`. - Expose functionality only for specific |map-modes|. - Expose different behavior for different |map-modes| with a single |<Plug>| mapping, without adding impurity or complexity to the underlying Lua implementation. NOTE: If you have a function that takes a large options table, creating lots of |<Plug>| mappings to expose all of its uses could become overwhelming. It may still be beneficial to create some for the most common ones. ------------------------------------------------------------------------------ Example *lua-plugin-plug-mapping-example* In your plugin: >lua vim.keymap.set("n", "<Plug>(SayHello)", function() print("Hello from normal mode") end, { noremap = true }) vim.keymap.set("v", "<Plug>(SayHello)", function() print("Hello from visual mode") end, { noremap = true }) < In the user's config: >lua vim.keymap.set({"n", "v"}, "<leader>h", "<Plug>(SayHello)") < ============================================================================== Initialization *lua-plugin-initialization* Newcomers to Lua plugin development will often put all initialization logic in a single `setup` function, which takes a table of options. If you do this, users will be forced to call this function in order to use your plugin, even if they are happy with the default configuration. Strictly separated configuration and smart initialization allow your plugin to work out of the box. NOTE: A well designed plugin has minimal impact on startup time. See also |lua-plugin-lazy-loading|. Common approaches to a strictly separated configuration are: - A Lua function, e.g. `setup(opts)` or `configure(opts)`, which only overrides the default configuration and does not contain any initialization logic. - A Vimscript compatible table (e.g. in the |vim.g| or |vim.b| namespace) that your plugin reads from and validates at initialization time. See also |lua-vim-variables|. Typically, automatic initialization logic is done in a |plugin| or |ftplugin| script. See also |'runtimepath'|. ============================================================================== Lazy loading *lua-plugin-lazy-loading* When it comes to initializing your plugin, assume your users may not be using a plugin manager that takes care of lazy loading for you. Making sure your plugin does not unnecessarily impact startup time is your responsibility. A plugin's functionality may evolve over time, potentially leading to breakage if users have to hack into the loading mechanisms. Furthermore, a plugin that implements its own lazy initialization properly will likely have less overhead than the mechanisms used by a plugin manager or user to load that plugin lazily. ------------------------------------------------------------------------------ Defer `require` calls *lua-plugin-lazy-loading-defer-require* |plugin| scripts should not eagerly `require` Lua modules. For example, instead of: >lua local foo = require("foo") vim.api.nvim_create_user_command("MyCommand", function() foo.do_something() end, { -- ... }) < which will eagerly load the `foo` module and any other modules it imports eagerly, you can lazy load it by moving the `require` into the command's implementation. >lua vim.api.nvim_create_user_command("MyCommand", function() local foo = require("foo") foo.do_something() end, { -- ... }) < NOTE: For a Vimscript alternative to `require`, see |autoload|. NOTE: In case you are worried about eagerly creating user commands, autocommands or keymaps at startup: Plugin managers that provide abstractions for lazy-loading plugins on such events will need to create these themselves. ------------------------------------------------------------------------------ Filetype-specific functionality *lua-plugin-lazy-loading-filetype* Consider making use of |filetype| for any functionality that is specific to a filetype, by putting the initialization logic in a `ftplugin/{filetype}.lua` script. ------------------------------------------------------------------------------ Example *lua-plugin-lazy-loading-filetype-example* A plugin tailored to Rust development might have initialization in `ftplugin/rust.lua`: >lua if not vim.g.loaded_my_rust_plugin then -- Initialize end -- NOTE: Using `vim.g.loaded_` prevents the plugin from initializing twice -- and allows users to prevent plugins from loading -- (in both Lua and Vimscript). vim.g.loaded_my_rust_plugin = true local bufnr = vim.api.nvim_get_current_buf() -- do something specific to this buffer, -- e.g. add a |<Plug>| mapping or create a command vim.keymap.set("n", "<Plug>(MyPluginBufferAction)", function() print("Hello") end, { noremap = true, buffer = bufnr, }) < ============================================================================== Configuration *lua-plugin-configuration* Once you have merged the default configuration with the user's config, you should validate configs. Validations could include: - Correct types, see |vim.validate()| - Unknown fields in the user config (e.g. due to typos). This can be tricky to implement, and may be better suited for a |health| check, to reduce overhead. ============================================================================== Troubleshooting *lua-plugin-troubleshooting* ------------------------------------------------------------------------------ Health checks *lua-plugin-troubleshooting-health* Provide health checks in `lua/{plugin}/health.lua`. Some things to validate: - User configuration - Proper initialization - Presence of Lua dependencies (e.g. other plugins) - Presence of external dependencies See also |vim.health| and |health-dev|. ------------------------------------------------------------------------------ Minimal config template *lua-plugin-troubleshooting-minimal-config* It can be useful to provide a template for a minimal configuration, along with a guide on how to use it to reproduce issues. ============================================================================== Versioning and releases *lua-plugin-versioning-releases* Consider - Using SemVer https://semver.org/ tags and releases to properly communicate bug fixes, new features, and breaking changes. - Automating versioning and releases in CI. - Publishing to luarocks https://luarocks.org, especially if your plugin has dependencies or components that need to be built; or if it could be a dependency for another plugin. ------------------------------------------------------------------------------ Further reading *lua-plugin-versioning-releases-further-reading* - Luarocks <3 Nvim https://github.com/nvim-neorocks/sample-luarocks-plugin ------------------------------------------------------------------------------ Tools *lua-plugin-versioning-releases-tools* - luarocks-tag-release https://github.com/marketplace/actions/luarocks-tag-release - release-please-action https://github.com/marketplace/actions/release-please-action - semantic-release https://github.com/semantic-release/semantic-release ============================================================================== Documentation *lua-plugin-documentation* Provide vimdoc (see |help-writing|), so that users can read your plugin's documentation in Nvim, by entering `:h {plugin}` in |command-mode|. ------------------------------------------------------------------------------ Tools *lua-plugin-documentation-tools* - panvimdoc https://github.com/kdheepak/panvimdoc

2024/7/29
articleCard.readMore

Neovim 调色板插件 cpicker.nvim

启用插件 操作界面 后续计划 cpicker.nvim, 该插件存储于 Github 。 启用插件 require('plug').add({ { 'wsdjeg/cpicker.nvim', depends = { { 'wsdjeg/logger.nvim' }, { 'wsdjeg/notify.nvim' }, }, }, }) 操作界面 按键 功能描述 h / <Left> 减小光标下的值 l / <Right> 增加光标下的值 <Enter> 复制光标下的颜色值 后续计划 目前支持的格式包括:RGB、HSL、HSV、CMYK,计划再增加一些常用的格式。 增加颜色混合调整面板

2024/7/14
articleCard.readMore

Neovim 缓冲区(buffer)相关事件

起因 获取可用事件列表 事件的触发时机 BufAdd BufNew BufNewFile 测试示例 起因 BufNewFile 跟 BufReadPost 事件。 vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufReadPost' }, { callback = vim.schedule_wrap(function(event) if vim.api.nvim_buf_is_valid(event.buf) and index(right_hide_bufs, event.buf) == -1 and index(visiable_bufs, event.buf) == -1 and index(left_hide_bufs, event.buf) == -1 then table.insert(right_hide_bufs, event.buf) end end), group = tabline_augroup, }) :enew 命令及 :new 命令并不会触发这两个事件。修改源码增加监听 BufNew 事件。 虽然问题是修复了,但是我觉得有必要整理下缓冲区相关事件的触发时机。 获取可用事件列表 :echo getcompletion('Buf', 'event') 快速查看当前 Neovim 版本支持的缓冲区事件列表。 事件的触发时机 BufAdd 新增一个缓冲区至缓冲区列表 将一个 unlisted 缓冲区添加进 listed 缓冲区列表 修改 listed 缓冲区列表中某个缓冲区的名称 BufNew 新增一个缓冲区 修改缓冲区的名称 BufNewFile 当开始编辑一个不存在的文件时 测试示例 *Cmd 事件,避免文件读写失败。 local log = require("spacevim.logger").derive("logevent") local group = vim.api.nvim_create_augroup("logevent", { clear = true }) vim.api.nvim_create_autocmd( vim.tbl_filter(function(e) return not vim.endswith(e, "Cmd") end, vim.fn.getcompletion("Buf", "event")), { callback = vim.schedule_wrap(function(event) log.debug(event.event .. event.buf) end), group = group, } ) 执行命令::new [ logevent ] [13:20:39:562] [ Debug ] BufNew5 [ logevent ] [13:20:39:562] [ Debug ] BufAdd5 [ logevent ] [13:20:39:562] [ Debug ] BufAdd5 [ logevent ] [13:20:39:562] [ Debug ] BufLeave4 [ logevent ] [13:20:39:562] [ Debug ] BufEnter5 [ logevent ] [13:20:39:562] [ Debug ] BufWinEnter5 执行命令::enew [ logevent ] [13:22:42:452] [ Debug ] BufNew6 [ logevent ] [13:22:42:452] [ Debug ] BufAdd6 [ logevent ] [13:22:42:452] [ Debug ] BufAdd6 [ logevent ] [13:22:42:452] [ Debug ] BufLeave4 [ logevent ] [13:22:42:452] [ Debug ] BufWinLeave4 [ logevent ] [13:22:42:452] [ Debug ] BufHidden4 [ logevent ] [13:22:42:452] [ Debug ] BufEnter6 [ logevent ] [13:22:42:452] [ Debug ] BufWinEnter6 执行命令: :echo bufadd('') [ logevent ] [13:47:57:482] [ Debug ] BufNew6 echo nvim_create_buf(v:false, v:true) [ logevent ] [14:25:06:555] [ Debug ] BufNew5 echo nvim_create_buf(v:true, v:true) [ logevent ] [14:26:32:389] [ Debug ] BufNew11 [ logevent ] [14:26:32:389] [ Debug ] BufAdd11 [ logevent ] [14:26:32:389] [ Debug ] BufAdd11 :set nobuflisted, 在执行 :set buflisted [ logevent ] [14:38:15:888] [ Debug ] BufDelete4 [ logevent ] [14:38:19:857] [ Debug ] BufAdd4 [ logevent ] [14:38:19:857] [ Debug ] BufAdd4 :e README.md [ logevent ] [14:44:15:717] [ Debug ] BufNew8 [ logevent ] [14:44:15:717] [ Debug ] BufAdd8 [ logevent ] [14:44:15:717] [ Debug ] BufAdd8 [ logevent ] [14:44:15:717] [ Debug ] BufLeave4 [ logevent ] [14:44:15:717] [ Debug ] BufWinLeave4 [ logevent ] [14:44:15:717] [ Debug ] BufHidden4 [ logevent ] [14:44:15:717] [ Debug ] BufReadPre8 [ logevent ] [14:44:15:717] [ Debug ] BufReadPost8 [ logevent ] [14:44:15:717] [ Debug ] BufReadPost8 [ logevent ] [14:44:15:717] [ Debug ] BufEnter8 [ logevent ] [14:44:15:717] [ Debug ] BufWinEnter8 :e newfile.txt [ logevent ] [14:45:56:519] [ Debug ] BufNew10 [ logevent ] [14:45:56:519] [ Debug ] BufAdd10 [ logevent ] [14:45:56:519] [ Debug ] BufAdd10 [ logevent ] [14:45:56:519] [ Debug ] BufLeave4 [ logevent ] [14:45:56:519] [ Debug ] BufWinLeave4 [ logevent ] [14:45:56:519] [ Debug ] BufHidden4 [ logevent ] [14:45:56:519] [ Debug ] BufNewFile10 [ logevent ] [14:45:56:519] [ Debug ] BufEnter10 [ logevent ] [14:45:56:519] [ Debug ] BufWinEnter10 wsdjeg/logevent.nvim 可以使用 nvim-plug 直接安装: require('plug').add({ 'wsdjeg/logevent.nvim', })

2024/7/7
articleCard.readMore

(Neo)Vim 括号补全插件比较

起因 实现逻辑及功能比较 auto-pairs neopairs.vim nvim-autopairs 维护状态 起因 delimitMate , 基本功能正常,但是偶尔会出现卡屏现象,等一段时间或者按下 ctrl-c 可以结束卡屏,但是会输入很多个 <Plug>delimitMate( 。 但是这一现象并无法百分百重现。 想着这么多年过去了,会不会有新的更好用的插件,于是搜了下,目前已知的括号补全插件有: delimitMate neopairs.vim:需要 exists('#CompleteDone') == 1 nvim-autopairs:需要 Neovim 0.7.0+ mini.pairs auto-pairs autoclose.nvim ultimate-autopair.nvim 实现逻辑及功能比较 auto-pairs plugin/auto-pairs.vim (673 行)。 监听 BufEnter 事件,映射 g:AutoPairs 所设定的成对符号。 neopairs.vim CompleteDone 事件,判断补全 item 的结尾是否匹配设定的成对符号。 nvim-autopairs nvim-autopairs.setup 函数里使用以下三组事件监听来实现补全。 local group = api.nvim_create_augroup('autopairs_buf', { clear = true }) api.nvim_create_autocmd({ 'BufEnter', 'BufWinEnter' }, { --- M.on_attach }) api.nvim_create_autocmd('BufDelete', { --- }) api.nvim_create_autocmd('FileType', { --- }) M.on_attach 函数里,主要有一个 expr 映射及一个 InsertCharPre 事件监听: local enable_insert_auto = false -- 初始化是否插入的 flag local expr_map = function(key) api.nvim_buf_set_keymap(bufnr, 'i', key, '', { expr = true --- 设定 expr 映射 }) end for _, rule in pairs(rules) do -- 根据每个规则映射快捷键,并计算是 enable_insert_auto 值 end if enable_insert_auto then api.nvim_create_autocmd('InsertCharPre', { --- 如果 enable_insert_auto 为 true, 则监听 InsertCharPre 事件 }) end 维护状态 repo Github stars Issues pull requests last commit auto-pairs 4.1k 138 Open / 155 Closed 27 Open / 53 Closed Feb 27, 2019 delimitMate 2k 47 Open / 219 Closed 5 Open / 36 Closed Dec 14, 2020 nvim-autopairs 3k 12 Open / 287 Closed 1 Open / 157 Closed May 20 2024 neopairs.vim 31 0 Open / 4 Closed 0 Open / 0 Closed Mar 16, 2016 mini.nvim、mini.pairs。 因为 mini.nvim 仓库实际上是由几十个独立插件组合而成,其 Issues 跟 pull requests 列表应对到 mini.pairs 上到底有多少,无法评估。 auto-pairs 有一个较活跃的 fork 版本LunarWatcher/auto-pairs,目前维护状态正常:

2024/6/27
articleCard.readMore

Lua 与 Vim Script 之间函数相互调用

起因 基本兼容逻辑 Vim Script 中调用 Lua Lua 中调用 Vim Script 函数 起因 基本兼容逻辑 autoload/test.vim function! test#test(name) echo "hello " . name endfunction lua/test.lua local M = {} function M.test(name) print('hello ' .. name) end return M function! test#test(name) if has('nvim') lua require('test').test(vim.api.nvim_eval('name')) else echo "hello " . name endif endfunction if has('nvim') function! test#test(name) lua require('test').test(vim.api.nvim_eval('name')) endfunction else function! test#test(name) echo "hello " . name endfunction endif test#test(name) 这个函数的本体仅限于一行代码。 lua require('test').test(vim.api.nvim_eval('name')) Vim Script 中调用 Lua :lua 命令,该命令使用有两种方式。 lua print('hello') lua << EOF print('hello') EOF :lua 命令,如果遇到 Vim Script 函数原先需要有返回值的话,比较麻烦,可以使用 luaeval() 函数,比如: function! test#test(name) call luaeval("require('test').test(vim.api.nvim_eval('name'))") " 也可以使用 return 返回值 return luaeval("require('test').test(vim.api.nvim_eval('name'))") endfunction v:lua,可以更加方便地在 Vim Script 中调用 Lua, 示例如下: function! test#test(name) " 这里可以直接使用参数,而不需要使用 nvim_eval return v:lua.require('test').test(a:name) endfunction Lua 中调用 Vim Script 函数 vim.fn 以及 nvim_call_function(fn, argv)。 -- built-in functions vim.fn.executable(var) -- User autoload function vim.fn['test#test'](name) function! s:test_hello(name) endfunction let s:fn = function('s:test_hello') function! test#run(fn) if type(a:fn) == 2 let fn = string(a:fn)[10:-3] elseif type(a:fn) == type('') let fn = a:fn endif call v:lua.require('test').run(fn) endf

2024/6/11
articleCard.readMore

更新 Neovim 遇到的问题

起因 升级 Neovim 不兼容的改动 vim.o.v_te 报错 窗口分割高亮 VertSplit 失效 treesitter 高亮报错 切回稳定版 起因 升级 Neovim scoop uninstall neovim scoop install neovim-nightly 不兼容的改动 * 2ad0da42 - perf(treesitter): add default setup function (Eric Wong 49 minutes ago) * f8b280e0 - fix(flygrep): remove `t_ve` option (Eric Wong 68 minutes ago) * c46968d5 - fix(colorscheme): link WinSeparator to VertSplit (Eric Wong 79 minutes ago) * 9ee8606e - fix(telescope): fix deoplete autocmd (Eric Wong 82 minutes ago) * 74c93c6c - chore(treesitter): update nvim-treesitter to 0.9.1 for Nvim-0.8.x (Eric Wong 2 hours ago) vim.o.v_te 报错 &v_te 选项来实现修改光标的造型。但是升级到新版本后,就报如下错误: Error detected while processing function SpaceVim#plugins#flygrep#open: line 1: E5108: Error executing lua C:\Users\wsdjeg\.SpaceVim\/lua/spacevim/plugin/flygrep.lua:780: Unknown option 't_ve' stack traceback: [C]: in function '__index' C:\Users\wsdjeg\.SpaceVim\/lua/spacevim/plugin/flygrep.lua:780: in function 'open' [string ":lua"]:1: in main chunk vim.o.v_te,尝试做了一些测试,增加exists()判断,可恨的是 exists('&t_ve') 居然返回 1。 那么就无法判断了,只能是删除这些设置。 窗口分割高亮 VertSplit 失效 :hi VertSplit,输出结果显示高亮设置正常。看了 :h hl-VertSplit 才知道,原来分割窗口的高亮组名称修改了。 修改成了 WinSeparator。因此在 ColorScheme 的 autocmd 内增加了: hi link WinSeparator VertSplit treesitter 高亮报错 Error detected while processing function startify#open_buffers[13]..<SNR>276_open_buffer[12]..BufReadPost Autocommands for "*": Error executing lua callback: ...s\neovim-nightly\current\share\nvim\runtime\filetype.lua:30: Error executing lua: ...s\neovim-nightly\current\share\nvim\runtime\filetype.lua:31: function startify#open_buffers[13]..<SNR>276_open_buffer[12]..BufReadPost Autocommands for "*"..FileType Autocommands for "*"..function <SNR>1_LoadFTPlugin[20]..script D:\Scoop\apps\neovim-nightly\current\share\nvim\runtime\ftplugin\lua.lua: Vim(runtime):E5113: Error while calling lua chunk: ...rrent\share\nvim\runtime/lua/vim/treesitter/language.lua:104: no parser for 'lua' language, see :help treesitter-parsers stack traceback: [C]: in function 'error' ...rrent\share\nvim\runtime/lua/vim/treesitter/language.lua:104: in function 'add' ...t\share\nvim\runtime/lua/vim/treesitter/languagetree.lua:112: in function 'new' ...ightly\current\share\nvim\runtime/lua/vim/treesitter.lua:41: in function '_create_parser' ...ightly\current\share\nvim\runtime/lua/vim/treesitter.lua:108: in function 'get_parser' ...ightly\current\share\nvim\runtime/lua/vim/treesitter.lua:416: in function 'start' ...ovim-nightly\current\share\nvim\runtime\ftplugin\lua.lua:2: in main chunk :help treesitter-parsers 同样报错,理解为打开 help 文件也报错,同只执行 :h。 Error detected while processing modelines[274]..FileType Autocommands for "*"..function <SNR>1_LoadFTPlugin[20]..script D:\Scoop\apps\neovim-nightly\current\share\nvim\runtime\ftplugin\help.lua: E5113: Error while calling lua chunk: ...rrent\share\nvim\runtime/lua/vim/treesitter/language.lua:104: no parser for 'vimdoc' language, see :help treesitter-parsers stack traceback: [C]: in function 'error' ...rrent\share\nvim\runtime/lua/vim/treesitter/language.lua:104: in function 'add' ...t\share\nvim\runtime/lua/vim/treesitter/languagetree.lua:112: in function 'new' ...ightly\current\share\nvim\runtime/lua/vim/treesitter.lua:41: in function '_create_parser' ...ightly\current\share\nvim\runtime/lua/vim/treesitter.lua:108: in function 'get_parser' ...ightly\current\share\nvim\runtime/lua/vim/treesitter.lua:416: in function 'start' ...vim-nightly\current\share\nvim\runtime\ftplugin\help.lua:2: in main chunk vim.treesitter.start() D:\Scoop\apps\neovim-nightly\current\share\nvim\runtime\ftplugin\help.lua D:/Scoop/apps/neovim-nightly/0.10.0-2559/share/nvim/runtime/ftplugin/lua.lua 等等,懒得去一个个搜了。 cat D:/Scoop/apps/neovim-nightly/0.10.0-2559/share/nvim/runtime/ftplugin/lua.lua, 发现就两行代码,也没有任何条件判断跟 error handle。 -- use treesitter over syntax vim.treesitter.start() 切回稳定版 scoop uninstall neovim-nightly scoop install neovim nvim --version NVIM v0.9.5 Build type: RelWithDebInfo LuaJIT 2.1.1703942320 Compilation: C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe /MD /Zi /O2 /Ob1 -W3 -wd4311 -wd4146 -DUNIT_TESTING -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_WIN32_WINNT=0x0602 -DMSWIN -DINCLUDE_GENERATED_DECLARATIONS -ID:/a/neovim/neovim/.deps/usr/include/luajit-2.1 -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/build/src/nvim/auto -ID:/a/neovim/neovim/build/include -ID:/a/neovim/neovim/build/cmake.config -ID:/a/neovim/neovim/src -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/.deps/usr/include -ID:/a/neovim/neovim/.deps/usr/include system vimrc file: "$VIM\sysinit.vim" fall-back for $VIM: "C:/Program Files (x86)/nvim/share/nvim" Run :checkhealth for more info

2024/3/11
articleCard.readMore

修复 git clone 问题: gnutls_handshake() failed

前因 尝试诊断 重新编译 前因 ~/.SpaceVim 仓库的远程地址设置成 https://spacevim.org/git/repos/SpaceVim/ 后,再执行 git pull 出现错误: fatal: unable to access 'https://spacevim.org/git/repos/SpaceVim/': gnutls_handshake() failed: Error in the pull function. git pull 了。 尝试诊断 GIT_CURL_VERBOSE=1 git pull 23:29:04.865626 http.c:664 == Info: Couldn't find host spacevim.org in the (nil) file; using defaults 23:29:04.884061 http.c:664 == Info: Trying 104.21.53.107:443... 23:29:05.085433 http.c:664 == Info: Trying 2606:4700:3037::ac43:d405:443... 23:29:05.085549 http.c:664 == Info: Immediate connect fail for 2606:4700:3037::ac43:d405: Network is unreachable 23:29:05.085601 http.c:664 == Info: Trying 2606:4700:3032::6815:356b:443... 23:29:05.085623 http.c:664 == Info: Immediate connect fail for 2606:4700:3032::6815:356b: Network is unreachable 23:29:05.118268 http.c:664 == Info: Connected to spacevim.org (104.21.53.107) port 443 (#0) 23:29:05.138108 http.c:664 == Info: found 372 certificates in /etc/ssl/certs 23:29:05.138186 http.c:664 == Info: GnuTLS ciphers: NORMAL:-ARCFOUR-128:-CTYPE-ALL:+CTYPE-X509:-VERS-SSL3.0 23:29:05.138223 http.c:664 == Info: ALPN, offering h2 23:29:05.138240 http.c:664 == Info: ALPN, offering http/1.1 23:29:05.149601 http.c:664 == Info: gnutls_handshake() failed: Error in the pull function. 23:29:05.149680 http.c:664 == Info: Closing connection 0 fatal: unable to access 'https://spacevim.org/git/repos/SpaceVim/': gnutls_handshake() failed: Error in the pull function. 重新编译 git: sudo apt uninstall git wget "https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.9.5.tar.xz" tar -xvf git-2.9.5.tar.xz sudo apt-get update sudo apt-get install curl jq -y sudo apt-get install build-essential autoconf dh-autoreconf -y sudo apt-get install libcurl4-openssl-dev gettext -y make configure ./configure --prefix=/usr --with-openssl make -j4 sudo make install

2024/2/23
articleCard.readMore

《热辣滚烫》观后感

最近刷视频总是刷到热辣滚烫的相关内容,趁着假日就来看看咯,确实挺不错: .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; } 剧中杜乐莹总是毫无保留地对朋友真心付出,即便是知道对方有私心在,仍然坚持按照内心想法去做,确实难能可贵。 短短两小时的电影,看到主角为了自己的梦想所做出的改变,和所坚持的,有些汗颜,似乎我也快忘记了自己的梦想。

2024/2/15
articleCard.readMore

从 Github 迁移到 Gitlab

起因 尝试 Gitlab 的 page 功能 最终方案 cloudflare page 起因 尝试 Gitlab 的 page 功能 spacevim.org 时,都给我重定向到 https://spacevim-spacevim-c3011a6b21af70a6e8848d17c11652755bdad5d4e36c3.gitlab.io/ 最终方案 cloudflare page Liquid Exception: incompatible character encodings: ASCII-8BIT and UTF-8 <meta charset="utf-8" />, 要么是在 _config.yml 里面增加 encoding: utf-8。尝试下来都不行。 感谢 vimzh 群友依云提供的答案,在构建时,增加环境变量:export LANG=C.UTF-8

2023/9/27
articleCard.readMore

非常棒的安卓输入法 fcitx-android

初识 fcitx5-android fcitx5-android 特点 隐私策略 操作可配置性 界面可配置性 《几款流行拼音输入法的“用户体验”评测(Win)》。 而我自己手机上一直使用的是华为版的百度输入法,虽然我不知道这个输入法到底会上传哪些内容,但是个人感觉还是不安全,因为打字输入涉及的东西太多。 聊天信息、密码等等,这些内容不应该存储在不可控的服务器上。 初识 fcitx5-android fcitx5-android。Fcitx 的旧全名是“Free Chinese Input Tool of X”的首字母缩写,相信大家并不陌生,它是 Linux 系统下面一款开源、免费的输入法框架。早期我在使用 Ubuntu/ArchLinux 时候都是使用的这款输入法。 非常开心有人将这一输入法移植到了安卓平台。 fcitx5-android 的安装方式也比较简单,可以直接从 F-Droid 网站直接下载 APK 安装包。切记,根据自己的手机选择对应的 CPU 架构,一般手机都是 arm64-v8a。 fcitx5-android 特点 fcitx5-android 只支持全键盘输入模式,恰巧我也是习惯于全键盘输入,因此使用倒是没有任何障碍。而我比较看重的功能特点主要包括: 隐私策略 fcitx5-android 不要求联网权限,也不收集任何个人信息。这一点是我切换该输入法的最核心原因,因为日常输入涉及的内容还是比较多的,因此对于哪些动辄上传收集输入信息的输入法还是比较反感的。 操作可配置性 输入标点、数字的方式 长按空格键行为 振动时长 默认展开工具栏,这点比较喜欢 长按延迟时间 界面可配置性 在主题设置界面,可以设置按键按下的水波纹特效,以及是否显示按键边框,甚至按键的横向边距、纵向边距、圆角半径都可以设置。

2023/6/7
articleCard.readMore

Neo-tree.nvim 糟糕的体验

安装 neo-tree 插件配置 遇到的问题 插件优势 尝试过 nvim-tree,今天晚上花了一晚上时间测试了下 Neo-tree.nvim,测试版本为:Neo-tree.nvim@e3b4ef0。 总的来说体验不是很好。 测试环境: 操作系统:Window 11 Neovim 版本:NVIM 0.9.1 安装 neo-tree neo-tree.zip 至 bundle 文件夹,并手动更新 RTP。 插件配置 遇到的问题 SPC b t无法实现。按照文档所说:Neotree dir=%:p:h无效,打开后根目录任然是当前目录。 2、设置快捷键不是很方便; 在配置文件里,action 都是一些预设值的 string。 3、SPC f o 快捷键每次弹窗需要确认是否切换目录。 我是经常会同时编辑不同的 git 仓库文件,SPC f o 设置为打开文件树并定位到当前文件位置,即为 :Neotree reveal,但是每次一旦切换不同项目时都会弹窗提示: File is not in cmd, change to xxxxx y/n y<Cr> 两下按键,应该提供一个设置选项,自动切换目录而不需要用户手动确认。并且,就算确认,完全可以使用 getchar() 函数。 插件优势 defx,期待 neo-tree 新的改进,后续有机会再尝试。

2023/5/31
articleCard.readMore

我的 Neovim 之旅

初始 Neovim Neovim 异步 job 特性 Neovim 定时器 悬浮窗口支持 Neovim 内置终端的使用 初始 Neovim +job 特性之前, Vim 的所有函数都是在主线程内完成的,当执行某个需要很长时间的操作时,Vim 会卡住等待上一个操作完成。 这样的体验是非常不符合个人习惯的。因此,Geoff Greer 给 Vim 提交过异步执行的补丁,源码可以查阅Floobits/vim。同时,Thiago Padilha 也给 Vim 提交过类似的补丁,但是都因各种原因被拒绝,其实这也是我不太喜欢的一个 Vim 开发的弊端。 后来,Thiago Padilha 发起了 Neovim 项目,做了如下改进: 增加异步 job 特性 悬浮窗口支持 增加 timer 支持 增加终端真色支持 增加 RPC 通讯支持 增加内置终端模拟器 Neovim 异步 job 特性 call jobstart(['echo']) Neovim 定时器 timer_start(),其函数原型为: timer_start({time}, {callback} [, {options}]) 悬浮窗口支持 if has('nvim') let s:FLOAT = SpaceVim#api#import('neovim#floating') else let s:FLOAT = SpaceVim#api#import('vim#floating') endif call s:FLOAT.open_win(bufnr('%'), v:true, \ { \ 'relative': 'editor', \ 'width' : &columns, \ 'height' : &lines * 30 / 100, \ 'row': 0, \ 'col': &lines - (&lines * 30 / 100) - 2 \ }) Neovim 内置终端的使用 :terminal 命令打开内置终端。在终端窗口内,可以使用 Ctrl-\ Ctrl-n 切换到 Normal 模式。 目前,内置的:terminal命令不支持分屏模式,可以借助split-term.vim插件。 该插件增加了如下的使用方式: 水平/垂直分屏打开终端 指定分屏终端窗口初始化大小 在新的标签页打开终端 https://github.com/voldikss/vim-floaterm https://github.com/numToStr/FTerm.nvim shell.vim#L153-L292 let s:SYSTEM = SpaceVim#api#import('system') let s:FLOAT = SpaceVim#api#import('neovim#floating') let s:WIN = SpaceVim#api#import('vim#window') function! s:open_default_shell(open_with_file_cwd) abort if a:open_with_file_cwd if getwinvar(winnr(), '&buftype') ==# 'terminal' let path = getbufvar(winbufnr(winnr()), '_spacevim_shell_cwd', SpaceVim#plugins#projectmanager#current_root()) else let path = expand('%:p:h') endif else let path = SpaceVim#plugins#projectmanager#current_root() " if the current file is not in a project, the projectmanager return empty " string. Then use current directory as default cwd. if empty(path) let path = getcwd() endif endif " look for already opened terminal windows let windows = [] windo call add(windows, winnr()) for window in windows if getwinvar(window, '&buftype') ==# 'terminal' exe window . 'wincmd w' if getbufvar(winbufnr(window), '_spacevim_shell_cwd') ==# l:path " startinsert do not work in gvim if has('nvim') startinsert else normal! a endif return else " the opened terminal window is not the one we want. " close it, we're gonna open a new terminal window with the given l:path exe 'wincmd c' break endif endif endfor if s:default_position ==# 'float' && exists('*nvim_open_win') let s:term_win_id = s:FLOAT.open_win(bufnr('%'), v:true, \ { \ 'relative': 'editor', \ 'width' : &columns, \ 'height' : &lines * s:default_height / 100, \ 'row': 0, \ 'col': &lines - (&lines * s:default_height / 100) - 2 \ }) exe win_id2win(s:term_win_id) . 'wincmd w' else " no terminal window found. Open a new window let cmd = s:default_position ==# 'float' ? \ 'topleft split' : \ s:default_position ==# 'top' ? \ 'topleft split' : \ s:default_position ==# 'bottom' ? \ 'botright split' : \ s:default_position ==# 'right' ? \ 'rightbelow vsplit' : 'leftabove vsplit' exe cmd let lines = &lines * s:default_height / 100 if lines < winheight(0) && (s:default_position ==# 'top' || s:default_position ==# 'bottom') exe 'resize ' . lines endif endif let w:shell_layer_win = 1 for open_terminal in s:open_terminals_buffers if bufexists(open_terminal) if getbufvar(open_terminal, '_spacevim_shell_cwd') ==# l:path exe 'silent b' . open_terminal " clear the message if has('nvim') startinsert else normal! a endif return endif else " remove closed buffer from list call remove(s:open_terminals_buffers, 0) endif endfor " no terminal window with l:path as cwd has been found, let's open one if s:default_shell ==# 'terminal' if exists(':terminal') if has('nvim') if s:SYSTEM.isWindows let shell = empty($SHELL) ? 'cmd.exe' : $SHELL else let shell = empty($SHELL) ? 'bash' : $SHELL endif enew call termopen(shell, {'cwd': l:path}) " @bug cursor is not cleared when open terminal windows. " in neovim-qt when using :terminal to open a shell windows, the orgin " cursor position will be highlighted. switch to normal mode and back " is to clear the highlight. " This seem a bug of neovim-qt in windows. " " cc @equalsraf if s:SYSTEM.isWindows && has('nvim') stopinsert startinsert endif let s:term_buf_nr = bufnr('%') call extend(s:shell_cached_br, {getcwd() : s:term_buf_nr}) else " handle vim terminal if s:SYSTEM.isWindows let shell = empty($SHELL) ? 'cmd.exe' : $SHELL else let shell = empty($SHELL) ? 'bash' : $SHELL endif let s:term_buf_nr = term_start(shell, {'cwd': l:path, 'curwin' : 1, 'term_finish' : 'close'}) endif call add(s:open_terminals_buffers, s:term_buf_nr) let b:_spacevim_shell = shell let b:_spacevim_shell_cwd = l:path " use WinEnter autocmd to update statusline doautocmd WinEnter setlocal nobuflisted nonumber norelativenumber " use q to hide terminal buffer in vim, if vimcompatible mode is not " enabled, and smart quit is on. if !empty(g:spacevim_windows_smartclose) && !g:spacevim_vimcompatible exe 'nnoremap <buffer><silent> ' . g:spacevim_windows_smartclose . ' :hide<CR>' endif startinsert else echo ':terminal is not supported in this version' endif elseif s:default_shell ==# 'VimShell' VimShell imap <buffer> <C-d> exit<esc><Plug>(vimshell_enter) endif endfunction

2023/5/24
articleCard.readMore

写给自己·关于健康

上周,听到一则让我很吃惊的消息。陈浩(左耳朵耗子)因突发心梗辞世,享年只有四十七岁。 正常情况下,四十多岁正是事业、家庭黄金时间。当财富、声望都积累足够的时候,却没有时间去享受自己奋斗的果实,确实让人觉得惋惜。 其实,对于左耳朵耗子了解的并不多,甚至可以说完全不了解。这几天,不管是朋友圈,还是推特,抑或是订阅的一些博客,都在发文纪念他。 也正是读了这些文字才有了一些了解。其实,这类事情之前已经遇到过好几次了。比如:司徒正美。 想来,真的是健康是一,财富、声望、家庭等等一切都是零。 也正是因此,最近才开始关注一些生活习惯方面的资料。包括不限于: 健康学习到150岁 - 人体系统调优不完全指南 饮食 夜宵:拒绝夜宵,晚上9点后,不再进食 睡眠 运动 貌似没有什么适合自己的运动了。以前偶尔还去爬山,上次去宜兴竹海,只到半山腰就下来了。

2023/5/22
articleCard.readMore

停用 v2ex 账号

贴子和回复无法删除 未经允许公布用户邮箱和手机号 以往发的文字无法修改 “随意”移贴 随意删号 霸道降权 针对 v2ex/faq 上的回复 如何删除V2EX账号 https://www.v2ex.com/member/wsdjeg V2EX 第 167678 号会员,加入于 2016-04-11 13:08:11 +08:00 个人网站”并不适合我。罗列了一些存在的问题,以及个人的一些看法。 贴子和回复无法删除 未经允许公布用户邮箱和手机号 来自百度取证的证据分享 以往发的文字无法修改 “随意”移贴 https://www.v2ex.com/go/flamewar)标签下, 这个标签下的内容,非登录用户似乎是无法查阅的,会自动跳转至首页。 随意删号 因用 AI 生成的内容回复帖子,V2EX 账号直接被封。 且不说这个规则是否合理,对于十年的老用户,而且是活跃的老用户,你(@Livid )他妈的说删就删,谁敢再这个网站上写东西? 霸道降权 针对 v2ex/faq 上的回复 我是否可以编辑或者移动自己的主题? 在主题发布之后的 300 秒内,你可以自由地编辑自己的主题,或者将其移动到其他节点。但是在 300 秒(5 分钟)之后, 就不可以再进行这些操作了。对于编辑的限制是为了让大家对发表在 V2EX 的言论更加负责。 如何删除V2EX账号 如何删除V2EX账号”可以看到不少人遇到类似的问题。

2023/5/15
articleCard.readMore

母亲节·常回家看看

五一期间没有来得及回家,这次周末正好是母亲节。周五晚上工作结束后,回了一趟老家,已经快半年没有回去了。 周六早上,带爷爷、奶奶、妈妈一起去扬州世博园玩玩。避开五一假期,也是另外一番景色。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2); } 中午在宝能广场吃的“老土灶民间菜”,感觉他们家菜的口味还是挺不错的,而且也比较实惠。 吃完饭回家回家后陪爷爷奶奶还有妈妈打麻将,老年人爱好挺简单的。我记得我上次打牌还是去年11月份,手生的很。 礼拜天,是母亲节,今天中午选的是无锡融创茂的泰妃餐厅。

2023/5/14
articleCard.readMore

介绍我的新插件 gfr.vim

为什么要开发 gfr.vim gfr.vim 的功能特点 异步执行 兼容 Vim/Neovim 弹窗消息界面 如何安装 如何使用 后续计划 gfr.vim。名字取自三个词语缩写:Grep, Filter, Replace。顾名思义,这个插件提供的功能包括文本搜索、结果筛选以及文本替换功能。 为什么要开发 gfr.vim ack.vim、ag.vim、grepper.vim 等等。这些插件都提供了类似的功能,即为搜索制定的文字,并将结果输出到 Vim 的 quickfix window。 甚至,我之前还制作过一款实时异步搜索,并展示搜索结果的插件 flygrep.vim。 但是以上这些插件仅仅支持搜索指定文本的结果,不具备二次检索和替换的功能。 gfr.vim 的功能特点 异步执行 :Grep。实际上,Vim 本身也有 :grep 和 :vimgrep 命令,功能是相同的,都是执行搜索命令,并且把搜索的结果展示在 quickfix 窗口内。 但是 Vim 自带的 :grep 和 :vimgrep 命令是单线程的,如果搜索的文件夹内内容非常多会卡住当前 Vim 的操作界面,体验不是很好。 兼容 Vim/Neovim gfr.vim 集成了 SpaceVim 的 API,可以同时兼容 Vim 和 Neovim。 弹窗消息界面 gfr.vim 使用了 SpaceVim 的 notify API,对于一些插件通知消息,在 Vim 右上方使用浮窗提示。 如何安装 Plug 'wsdjeg/gfr.vim' :PlugInstall。 如何使用 gfr.vim 提供了如下命令: :Grep: 搜索指定的文本,该命令后面的参数即为需要搜索的文本,比如 :Grep hello 指的就是搜索 hello。如果直接调用:Grep,并且不带任何参数,那么会弹出一个输入框,可供输入需要搜索的文本。 :Filter:检索上一次搜索的结果,可以理解为二次筛选。对于上一次搜索或者检索的结果,进行再一次的筛选。 :GrepSave:将当前的搜索结果保存至命名标签,以便于快速访问。 :GrepResum:使用:GrepResum 标签名可以快速打开以往的搜索结果 :Replace:对搜索到的结果执行文本替换 后续计划 grep 命令,后续计划增加 ag、rg 等命令支持。

2023/5/7
articleCard.readMore

将微信朋友圈移至静态网站

归因 我需要的“朋友圈” 博客相册生成 相册,今天抽空整理了下这个过程,并记录。 归因 不开放,只能微信里面,其他地方无法查看。 图片损伤,上传到朋友圈的照片会自动压缩,无法存储原图。 审查机制,不过多解释,虽然不喜欢讨论政治等敏感话题 永久存储,不确定哪一天微信会关闭 无法发链接,格式太受限 无法更新,过往内容无法更新 我需要的“朋友圈” 开放的,输入网址即可访问,而非仅限于在微信内 内容自由的,自己可控的 安全的,内容存储于私密仓库,安全,易备份。 格式自由,虽然不需要富文本那么花俏,但是简单的 markdown 语法还是需要的 可更新 日志独立,可关联 博客相册生成 {日期} {context} {gallery} {location} dev#autodocAPI 写了一个脚本: let s:AUTODOC = SpaceVim#api#import('dev#autodoc') let s:AUTODOC.autoformat = 0 function! Gengallery() abort let s:AUTODOC.begin = '^<!-- wsdjeg.net gallery start -->$' let s:AUTODOC.end = '^<!-- wsdjeg.net gallery end -->$' let s:AUTODOC.content_func = function('s:generate_gallery') call s:AUTODOC.update() endfunction function! s:generate_gallery() abort let content = [] call extend(content, s:gallery_list()) let content += [''] return content endfunction function! s:gallery_list() abort let text = [] let gallery_dirs = globpath('docs/uploads', '*', 1, 1) let previous_t = '' for gpath in gallery_dirs if empty(previous_t) || matchstr(gpath, '\d\+') !=# previous_t call extend(text, s:dir_to_g(gpath), 0) let previous_t = matchstr(gpath, '\d\+') endif endfor return text endfunction function! s:dir_to_g(d) abort let t = [] let y = str2nr(matchstr(a:d, '\d\d\d\d')) let m = str2nr(matchstr(a:d, '\d\d\d\d\zs\d\d')) let d = matchstr(a:d, '\d\d\d\d\d\d\zs\d\d') call add(t, '### ' . y . '年' . m . '月' . (empty(d) ? '' : d . '日')) call add(t, '') " 如果存在 text.txt 文件,读取内容,写入文字 if filereadable(a:d . '/text.txt') call extend(t, readfile(a:d . '/text.txt')) call add(t, '') endif call add(t, '{% include image-gallery-nofilename.html folder="' . substitute(a:d[5:], '\', '/', 'g') . '" %}') call add(t, '') " 如果存在 location 文件,读取内容,写入地址 if filereadable(a:d . '/location') call add(t, '> <i class="fa fa-map-marker"></i> ' . readfile(a:d . '/location')[0]) call add(t, '') endif return t endfunction

2023/5/4
articleCard.readMore

五一假期·苏州一日游

转眼间毕业已经十三年了,错过了大学同学毕业十年聚会,挺可惜的。一直想找时间回学校看看。 今天正好带着家人回学校转转,本以为假日期间在学校不会看到太多的人。但是到了之后发现,有很多都是带着小孩子到学校里面拍照、画画。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; } 午饭选的是“桃花源记”,进川菜馆吃苏帮菜,有点失误了。 下午去了“诚品书店”,以前空闲的时候过去,书店里人很少,很安静。是个不错的看书的地方,今天人有点太多了,有点赶集的味道。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; } 下一站去了圆融天幕,可惜不是晚上,没有看到夜景。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; } 开车来到了平江路,一眼望去乌泱泱的人,只能放弃这一站了,还有苏州地标大秋裤也没来得及去看,期待下次吧。

2023/5/1
articleCard.readMore

《长空之王》观后感

今天是大悦城开业活动的最后一天,以前这个地方叫博大摩登。原先人气一点也不旺,但是这次过来感觉确实很热闹。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; } 中午尝了一下新白鹿,一家不错的杭州菜。晚上看了一部电影,《长空之王》。实际上本来前天就想看了,周五晚上回来后在融创茂吃了个“等鱼柒·豆花烤鱼”,已经不记得是第几次吃这家了,小朋友似乎挺爱吃的。 完了本来想去看看电影,奈何时间太晚了,只能早早回去休息。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; } 第一次了解到试飞员这一高危的职业,希望每一次试飞都可以安全落地。 看似和平的年代,实际上有很多人在背后默默付出,默默守护。 电影中穿插着各种家庭的元素,有父母、有老婆孩子,虽然不舍,但是还是默默支持,默默承受着。 张大队那句“做为军人,性命加使命才是生命”真的太贴切了,看完整个人都久久的沉浸在这样的敬佩、感动和自豪中,致敬这样一群默默守护我们的人!

2023/4/30
articleCard.readMore

Neovim lua bindeval 解决方案

最近在改写插件 tagbar 的日志系统,采用 SpaceVim 内置的日志插件。但是在调用 debug 函数时发现日志一直无法写入。 代码逻辑非常简单,lua 文件逻辑: lua/spacevim/logger.lua local M = {} function M.test() local derive = { _debug_mode = false, } function derive.debug(msg) if derive._debug_mode then print(msg) end end return derive end return M autoload/testl.vim function! testl#test() abort return luaeval('require("testluaeval").test()') endfunction let tlog = testl#test() let tlog._debug_mode = v:true call tlog.debug('hello') issue。得到的回复是,目前 neovim 还不支持 bindeval,但是支持 closures。 因此逻辑上做了如下改动: lua/spacevim/logger.lua local M = {} function M.test() local derive = { _debug_mode = false, } function derive.debug(msg) if derive._debug_mode then print(msg) end end function derive.start_debug() derive._debug_mode = true end function derive.stop_debug() derive._debug_mode = false end return derive end return M autoload/testl.vim function! testl#test() abort return luaeval('require("testluaeval").test()') endfunction let tlog = testl#test() call tlog.start_debug() call tlog.debug('hello') 通过以上的改动,测试步骤可以达到预期效果。

2023/4/3
articleCard.readMore

《忠犬八公》观后感

结束了一周的出差,周五到家后,习惯性的看看有什么好看的电影上映。正好看到《忠犬八公》上映。 刚开始还有一些疑惑,因为这部电影我记得很早以前就看过。仔细看来原来是大陆拍的,以前看的另外一个版本。 晚饭后,一家人一起去,正好赶上 7:20 这场。 可能是因为以前看过了,对剧情也大概有了心理预知,所以剧情上倒没有什么特别大的感触。 但是期间一些细节也让我感触很多: 教授第一次发飙讲的那段话,其实也很在理。人生匆匆几十年(现在已经过一小半了),没有什么比喜欢更重要。 教授妻子嘴上太伤人,但是其实内心还是很关心教授和八筒,也许她喜欢八筒只是爱屋及乌。 教授送儿子这段,父亲和母亲表达爱的方式还是有区别的。突然让我想到父亲送我上大学的那天,临走时,我秦楚地看到,父亲的眼睛很红。也是,以往都是离家很近上学。上了大学之后,包括现在工作了以后,回去的机会太少了。 儿子一直被误会在瞎折腾,倔强的坚持着他人看不懂的东西,最后实现自己的目标时开心。想想也是,很多时候,自己觉得对即可。 而最让我感触的,还是回忆结束后最后一段,看到满屋的报纸后,可以想象“八筒”日复一日的等了多久。 小狗的想法要比人类简单太多太多了,他的忠诚值得善待!

2023/3/31
articleCard.readMore

《保你平安》观后感

今天晚上正好有时间,去看了下最新出来的电影《保你平安》。其实在看这部电影之前,已经对剧情做了大致的了解。 一开始,把这部剧当作一个喜剧来看的,看着看着代入感就太强了。 现在的网络环境确实挺糟糕的。虽然做到了实名制,但是实际上对于大众来说。网络论坛上出现的各种角色还是匿名的。你无法判断他到底是谁。 只能说,其实网络也不是法外之地,希望在网络上发表各种言论时。还是要有敬畏心理,慎言!

2023/3/24
articleCard.readMore

使用 Lua 重写 SpaceVim 内置插件

最近如果你关注 Neovim 的社区,就会发现使用 Lua 开发的插件越来越多。 Neovim 默认就支持 luajit,早些时候,做过一次 luajit 与 vim script 速度的比较。 以下是一个测试的 Vim Script 脚本。 function! Fibo(N) abort let t = a:N let b = 0 while t > 0 let t = t - 1 let a = 1 let b = 1 let c = 73 while c > 0 let c = c - 1 let tmp = a + b let a = b let b = tmp endwhile endwhile echo b endfunction function! LuaFibo(N) abort lua << EOF local a, b, r, c, t t = vim.api.nvim_eval("str2nr(a:N)") while t > 0 do t = t - 1 a = 1 b = 1 c = 73 while c > 0 do c = c - 1 r = a + b a = b b = r end end print(string.format("%d", b)) EOF endfunction function! s:test(...) abort " for func in a:000 let start = reltime() call call(a:1,[a:2]) let sec = reltimefloat(reltime(start)) echom printf('%s(%d): %.6g sec', a:1, a:2, sec) " endfor endfunction command! -nargs=+ TestFunc call s:test(<f-args>) :TestFunc Fibo 1000 :TestFunc Fibo 10000000 :TestFunc LuaFibo 1000 :TestFunc LuaFibo 10000000 Fibo(1000): 0.410364 sec Fibo(10000000): 1470.280914 sec LuaFibo(1000): 9.052000e-4 sec LuaFibo(10000000): 1.235385 sec flygrep、iedit、task-manager 等等。 这些插件原先使用的是 Vim script,为的是兼容 Vim 和 Neovim。 在最新版的 SpaceVim 中,部分插件已经使用 lua 重写了。当然了,原先的 Vim script 写的版本也保留下来了,以便于兼容 Vim。 目前已经重写的插件包括: flygrep: 实时异步代码检索,根据输入的内容,实时展示代码搜索结果。 iedit: 类似于 emacs 的 iedit-mode,提供了多光标编辑的支持。 todomanager: 项目待办事项管理

2022/10/22
articleCard.readMore

使用 Vim 作为 SSH 客户端

在 Windows 下之前一直使用 xshell 作为 ssh 客户端。自从 Neovim 和 Vim 都增加了内置终端后,于是就把 xshell 这个软解卸载了。使用 Neovim 内置的函数来实现 SSH 连接。 首先,需要启用 SpaceVim 的 ssh 模块: [[layers]] name = 'ssh' 模块选项 ssh_port: 设置服务器的 ssh 端口 ssh_address: 设置服务器的地址或者 ip ssh_user: 设置默认的用户名 [[layers]] name = 'ssh' ssh_command = 'D:\Programs\Git\usr\bin\ssh.exe' ssh_user = 'root' ssh_address = '192.168.1.10' ssh_port = '8097' 快捷键 快捷键 功能描述 SPC S o 打开 ssh 窗口

2022/10/10
articleCard.readMore

安装并设置 Shadowsocks

首先需要下载 shadowsocks-rust。可以使用 curl 直接下载: curl -fLo shadowsocks.tar.xz https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.15.0-alpha.4/shadowsocks-v1.15.0-alpha.4.x86_64-unknown-linux-gnu.tar.xz tar -xf shadowsocks.tar.xz ssserver 文件,添加可执行权限: sudo chmod +x ssserver config.json { "server": "my_server_ip", "server_port": 8388, "password": "mypassword", "method": "aes-256-gcm", "local_address": "127.0.0.1", "local_port": 1080 } nohup ./ssserver -c config.json &

2022/6/3
articleCard.readMore

重新启用知乎账号

为什么注销知乎 为什么重新启用知乎 为什么注销知乎 为什么重新启用知乎 如何构建自己的笔记系统?

2022/5/20
articleCard.readMore

想对微博说 FUCK

今天下班后,回来本来想上微博看看最近有什么新鲜的事情。结果出现了这样一幕: 瞬间感觉人都不好了。很多好友,历史消息都还没有任何备份。所以说,现在完全不能依赖这类所谓的社交软件。后台管理员有无限大的权限。

2022/5/17
articleCard.readMore

nvim-tree.lua 使用初体验

使用 Vim 有很长一段时间了,我记得最早期的时候, 还是在网上看各种 Vim 配置的教程。 而这些教程里面,大部分都会提到一个叫做文件树(File Explorer)的功能。 文件树插件,也是我日常使用 Vim 必备的插件。 刚开始接触 Vim 的时候,那时候插件还很少, 只有内置的 netrw。 后来接触到了 nerdtree 以及 Shougo 的vimfiler, 包括 Shougo 使用 neovim remote 插件特性重写的 defx.nvim。 目前,SpaceVim 已经支持了以上这些文件树插件,可以通过 filemanager 选项进行设置: [options] filemanager = "nerdtree" nvim-tree.lua, 计划给 SpaceVim 也增加这个插件支持。在次之前,先体验一下这个插件到底有哪些功能: 测试版本:nvim-tree.lua@9049f364 启用 nvim-tree: [options] filemanager = "nvim-tree" 存在的问题 :NvimTreeFindFile 无法发现当前文件。 issues,最终只找到一个:Default keymaps cannot be unset。 里面有一段回复: vim.g.nvim_tree_disable_default_keybindings = 1 [NvimTree] Following options were moved to setup, see bit.ly/3vIpEOJ: nvim_tree_disable_default_keybindings

2022/5/9
articleCard.readMore

如何使用 SpaceVim 的 Job API

因为 Vim8 和 Neovim 实现的 job 函数存在很大的区别,并且使用的方式也是不一样的。在制作插件时,如果需要同时兼容 Vim 和 Neovim 就会存在很大的麻烦。因此,在 SpaceVim 中,实现了一个 job API,使用示例如下: let s:JOB = SpaceVim#api#import('job') let s:command = ['echo', 'hello world'] function! s:stdout(id, data, event) " the data is a list of string for line in a:data echo line endfor endf call s:JOB.start(s:command, { \ 'on_stdout' : function('s:stdout'), \ } \ ) on_stdout on_stderr on_exit

2022/5/3
articleCard.readMore

使用 Vim 作为聊天客户端

目前,使用较多的聊天室是 SpaceVim 的 gitter 聊天室,但是这个平台网页访问比较慢。 因此做了vim-chat,可以在 Vim/Neovim 里面快速打开聊天室进行沟通。 如果有兴趣的,可以自行尝试。这个插件目前开发是在 spacevim 的主仓库,自动更新独立仓库,因此有两种使用方式。你可以在 spacevim 里启用 chat 模块,也可以单独安装vim-chat插件。

2022/5/2
articleCard.readMore

下载安装winrar,并激活去广告

下载 激活 去广告 下载 激活 rarreg.key文件,输入以下内容并保存: RAR registration data wncn Unlimited Company License UID=1b064ef8b57de3ae9b52 64122122509b52e35fd885373b214a4a64cc2fc1284b77ed14fa20 66ebfca6509f9813b32960fce6cb5ffde62890079861be57638717 7131ced835ed65cc743d9777f2ea71a8e32c7e593cf66794343565 b41bcf56929486b8bcdac33d50ecf7739960627351a9ef03353a0e 592b327cd80645472f0ee622d1915028a9e05298e593db36384f0f f46afd5fed9b0bd095d1788266b81494b976f78fb1c551ca60a054 b17ad853ab902058b42c6887e1b3d40e0b45abf37de02106056887 去广告 ResourceHacker.exe, 运行该工具打开winrar安装目录内的winrar.exe文件: 定位并打开80:2052 删除1277所在整行,点击运行,然后保存。

2022/2/19
articleCard.readMore

在 SpaceVim 中启自动保存

前因 基本配置 前因 基本配置 [[layers]] name = 'edit' autosave_timeout = 300000 autosave_events = ['InsertLeave', 'TextChanged'] autosave_all_buffers = false edit 模块支持如下配置选项: autosave_timeout: 设置自动保存的时间间隔,默认是0,表示未开启定时自动保存。这个选项设定的值需要是毫秒数,并且需要小于100*60*1000 (100 分钟) 且 大于1000(1分钟)。比如设定成每隔5分钟自动保存一次: [[layers]] name = 'edit' autosave_timeout = 300000 autosave_events: 设定自动保存依赖的Vim事件,默认是空表。比如需要在离开插入模式时或者内容改变时自动保存: [[layers]] name = 'edit' autosave_events = ['InsertLeave', 'TextChanged'] autosave_all_buffers: 设定是否需要保存所有文件,默认是只保存当前编辑的文件,如果该选项设定成true则保存所有文件。 [[layers]] name = 'edit' autosave_all_buffers = true autosave_location: 设定保存文件的位置,默认为空,表示保存为原始路径。也可以设定成一个备份文件夹,自动保存的文件保存到指定的备份文件夹里面,而不修改原始文件。 [[layers]] name = 'edit' autosave_location = '~/.cache/backup/'

2022/2/6
articleCard.readMore

Window 7 下安装 nodejs

安装 nodejs 安装 nodejs scoop install nodejs 后,发现安装的是 nodejs16,运行提示: 无法定位程序输入点GetHostNameW于动态链接库WS2_32.dll上。 scoop uninstall nodejs 和 scoop install nodejs-lst, 之后运行还是这鬼样子。google搜了一圈,没找到合适的解释。直接: scoop uninstall nodejs-lst scoop install nodejs11

2021/10/2
articleCard.readMore

配置 zig 的 Vim 开发环境

安装zig语言 基本运行 语言语法 代码格式化 安装zig语言 zig-windows-x86_64-0.7.1+dfacac916.zip,解压后,发现: zig 目录下只有一个 zig.exe 命令,没有其他的了,这就简单了,直接把这个目录加 PATH 就行, 因为我使用的是 SpaceVim,不需要修改系统环境变量,直接载入 lang#zig 模块。 [[layers]] name = 'lang#zig' let $PATH .= ';D:\zig' 基本运行 SPC l r, 这个快捷键实际上是异步运行 zig run 加上当前文件名。 第一次运行,发现时间是9秒,不知道为什么会这么慢,但是后来的时间都是 0.14 秒左右,可能跟我的电脑有关,比较老了。 语言语法 代码格式化 #725,#4605 Github issue 已开,等坑被填了再来试试: #7654

2021/1/3
articleCard.readMore

字符串编辑距离算法

代码实现 参考资料 代码实现 fn get_distance(s1: String, s2: String) -> i32 { let chars1 = s1.chars().collect::<Vec<char>>(); let chars2 = s2.chars().collect::<Vec<char>>(); let len1 = s1.chars().count(); let len2 = s2.chars().count(); let mut d = vec![vec![0i32; len1 + 1]; len2 + 1]; for i in 0 .. len1 { d[i][0] = i as i32; } for i in 0 .. len2 { d[0][i] = i as i32; } for i in 1 .. len1 + 1 { for j in 1 .. len2 + 1 { let mut cost = 1; if chars1[i-1] == chars2[j-1] { cost = 0; } let delete = d[i-1][j] + 1; let insert = d[i][j-1] + 1; let substitution = d[i-1][j-1] + cost; d[i][j] = min(delete, insert, substitution); } } d[len1][len2] } fn min(d: i32, i: i32, s: i32) -> i32 { let temp = if d > i { i } else { d }; if s < temp { s } else { temp } } pub fn main() { println!("{}", get_distance("wsdjeg".to_string(), "wdsjgh".to_string())); } 参考资料 编辑距离 Edit distance

2020/3/24
articleCard.readMore

Rust 模块与项目文件组织

什么是模块 引入文件 同级目录相互访问 引用本地crate 参考文章 什么是模块 mod可以用来定义一个模块,或者载入一个模块文件。 引入文件 rustmod ├── Cargo.toml └── src └── main.rs src/util.rs,加入如下内容: pub fn hello_mod() { println!("{}", "hello mod"); } main.rs内使用mod util引入util.rs文件了: mod util; fn main() { util::hello_mod(); } mod util;载入模块文件,同时在main函数里调用util::hello_mod()函数,需要说明的是,该函数需要使用pub 关键字进行申明。 同级目录相互访问 rustmod ├── Cargo.toml └── src ├── main.rs ├── function.rs └── util.rs function.rs 内代码时,偶尔需要使用到uril.rs里面一些常用的工具函数,该如何操作呢? // rustmod/src/function.rs use crate::util; fn hello_function() { util::hello_mod(); } 引用本地crate rustmod ├── Cargo.toml └── src ├── main.rs ├── function.rs └── util.rs xxx ├── Cargo.toml └── src └── lib.rs rustmod/Cargo.toml: [package] name = "testmod" version = "0.1.0" authors = ["Shidong Wang"] edition = "2018" [dependencies] xxx = {path= "../xxx" } rustmod/src/main.rs内通过extern crate xxx;来引入xxx,修改main.rs: extern crate xxx; use xxx::xxx_func; fn main() { xxx_func(); } 参考文章 Rust的模块系统初探 Rust 模块和文件 - (译) Rust模块的理解

2020/3/22
articleCard.readMore

Rust 读取标准输入

起因 基本实现 参考资料 起因 cat test.py | python python test.py 基本实现 use std::io::{self, BufRead}; fn main() { let stdin = io::stdin(); for line in stdin.lock().lines() { println!("{}", line.unwrap()); } } 参考资料 How can I read a single line from stdin?

2020/3/17
articleCard.readMore

Rust 字符串算法

在搜索 Rust 相关资料,无意间看到 v2ex 上面的一些字符串相关的问题, 于是使用 rust 来测试下。 第 1 期: 前端算法精选-字符串系列 示例 1: 示例 2: 示例 3: pub fn main() { println!("{}", find_longest("aaaa")); } fn find_longest(st: &str) -> i32 { let mut i = 1; let mut rst = 1; let mut temp: String = String::new(); for c in st.chars() { if temp.contains(c) { i = 1; temp.clear(); temp.push(c); } else { i += 1; temp.push(c); } if i > rst { rst = i; } } rst - 1 }

2020/3/15
articleCard.readMore

Vim 字典补全插件

起因 简单实现 命令行工具 起因 noco-look 插件。但是换到 Windows 下之后,发现居然没有 look 这个命令。 恰巧,最近在学习 rust,于是就用 rust 简单写了一个字典补全的 Vim 插件:vim-async-dict 简单实现 from os.path import expanduser, expandvars import re import subprocess from .base import Base class Source(Base): def __init__(self, vim): Base.__init__(self, vim) self.name = 'dict' self.mark = '[D]' self.min_pattern_length = 3 命令行工具 impl Config { #[allow(dead_code)] fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); Ok(Config { query, filename }) } }

2020/3/14
articleCard.readMore

rust 格式化整理

基本用法 进制转换 对齐方式 基本用法 pub fn main() { println!("{}", "ssss"); } 进制转换 pub fn main() { println!("{}", 1); // 默认用法,打印Display println!("{:o}", 9); // 八进制 println!("{:x}", 255); // 十六进制 小写 println!("{:X}", 255); // 十六进制 大写 println!("{:p}", &0); // 指针 println!("{:b}", 15); // 二进制 println!("{:e}", 10000f32); // 科学计数(小写) println!("{:E}", 10000f32); // 科学计数(大写) println!("{:?}", "test"); // 打印Debug println!("{:#?}", ("test1", "test2")); // 带换行和缩进的Debug打印 println!("{a} {b} {b}", a = "x", b = "y"); // 命名参数 } 对齐方式 >: 右对齐 <: 左对齐 ^: 居中对齐 pub fn main() { // @question 关于字符串对齐 println!("{0:>0w$}", 9, w=6); // 八进制 } format_string := <text> [ format <text> ] * format := '{' [ argument ] [ ':' format_spec ] '}' argument := integer | identifier format_spec := [[fill]align][sign]['#'][0][width]['.' precision][type] fill := character align := '<' | '^' | '>' sign := '+' | '-' width := count precision := count | '*' type := identifier | '' count := parameter | integer parameter := integer '$'

2020/3/7
articleCard.readMore

开始学习 Rust

写在前面 1. 第一个程序 2. 基本语法 2.1 注释 2.2 变量 2.3 函数 2.4 基本数据类型 2.5 操作符 2.6 流程控制 3. 其他数据类型 3.1 结构体(struct) 3.2 枚举 3.3 实现方法和接口(impl & trait) 3.4 泛型(Generics) 3.5 常见集合 Vec 3.6 常见集合 String 3.7 常见集合 HashMap 4 错误处理 4.1 不可恢复错误 panic! 4.2 可恢复错误 Result 5 包、crate和模块 5.1 包和 crate 5.2 模块 6 测试 6.1 单元测试(unit tests) 6.2 集成测试(integration tests) 7 生命周期 写在前面 rustup-init.exe 后运行安装即可。 1. 第一个程序 main.rs: fn main() { println!("Hello, world!"); } rustc main.rs即可编译出一个可执行运行文件。 println 的一些用法: fn main() { println!("{}, {}!", "Hello", "world"); // Hello, world! println!("{0}, {1}!", "Hello", "world"); // Hello, world! println!("{greeting}, {name}!", greeting="Hello", name="world"); // Hello, world! let y = String::from("Hello, ") + "world!"; println!("{}", y); // Hello, world! } 2. 基本语法 2.1 注释 fn main() { // 单行注释 println!("hello"); // 行尾注释 /* 块注释 */ } 2.2 变量 mut 标记为可变变量。 局部变量 let a; // 声明变量,但并不赋值 let b = true; // 声明 bool 变量,并赋值 let c: bool = true; let (x, y) = (1, 2); a = 122; let mut d = 1; d = 20; 全局变量 static关键字申明全局变量,全局变量的生命周期是整个程序,必须显式标明类型,不支持类型推导; // 不可变静态变量 static N: i32 = 64; // 可变静态变量 static mut M: i32 = 10; 常量 const,和全局变量一样,生命周期也是整个程序。 const N: i32 = 10; 2.3 函数 fn来申明,例如: fn test_func() { println!("hi"); } fn test_func(a: i32, b: i32) { println!("the sum is:{}", a + b); } (),如果需要有返回值,需要在函数签名里使用->指定: fn test_func(a: i32, b: i32) -> i32 { println!("the sum is:{}", a + b); a + b // 等同于 return a + b; } fn plus_one(a: i32) -> (i32, i32) { (a, &a + 1) } fn main() { let (add_num, result) = plus_one(10); println!("{} + 1 = {}", add_num, result); // 10 + 1 = 11 } fn hello() { println!("hello world!"); } fn main() { let b = hello; b(); } 高阶函数 fn add_one(x: i32) -> i32 { x + 1 } fn apply<F>(f: F, y: i32) -> i32 where F: Fn(i32) -> i32 { f(y) * y } fn factory(x: i32) -> Box<Fn(i32) -> i32> { Box::new(move |y| x + y) } fn main() { let transform: fn(i32) -> i32 = add_one; let f0 = add_one(2i32) * 2; let f1 = apply(add_one, 2); let f2 = apply(transform, 2); println!("{}, {}, {}", f0, f1, f2); let closure = |x: i32| x + 1; let c0 = closure(2i32) * 2; let c1 = apply(closure, 2); let c2 = apply(|x| x + 1, 2); println!("{}, {}, {}", c0, c1, c2); let box_fn = factory(1i32); let b0 = box_fn(2i32) * 2; let b1 = (*box_fn)(2i32) * 2; let b2 = (&box_fn)(2i32) * 2; println!("{}, {}, {}", b0, b1, b2); let add_num = &(*box_fn); let translate: &Fn(i32) -> i32 = add_num; let z0 = add_num(2i32) * 2; let z1 = apply(add_num, 2); let z2 = apply(translate, 2); println!("{}, {}, {}", z0, z1, z2); } where 关键字的使用 use std::fmt::Debug; fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) { x.clone(); y.clone(); println!("{:?}", y); } // where 从句 fn foo<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y); } // 或者 fn foo<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y); } 发散函数 fn main() { println!("hello"); diverging(); println!("world"); } fn diverging() -> ! { panic!("This function will never return"); } 2.4 基本数据类型 布尔值(bool) 字符(char) 有符号整型(i8, i16, i32, i64, i128) 无符号整型(u8, u16, u32, u64, u128) 指针大小的有符号/无符号整型(isize/usize,取决于计算机架构,32bit 的系统上,isize 等价于i32) 浮点数(f32, f64) 数组(arrays) 元组(tuples),由相同/不同类型元素构成,长度固定。 切片(slice),指向一段内存的指针。 let a: [i32; 4] = [1, 2, 3, 4]; let b: &[i32] = &a; // 全部 let c = &a[0..4]; // [0, 4) let d = &a[..]; // 全部 let e = &a[1..3]; // [2, 3] let e = &a[1..]; // [2, 3, 4] let e = &a[..3]; // [1, 2, 3] 字符串(str) let a = "Hello, world!"; //a: &'static str let b: &str = "你好, 世界!"; // 多行字符串 let a = "line one line two"; // 字符串转义 let a = "line one\nline two" // 也可以在字符串字面量前加上r来避免转义 let a = r"line one\nline two" [T;N]。其中N表示数组大小,并且这个大小一定是个编译时就能获得的整数值, T表示泛型类型,即任意类型。我们可以这么来声明和使用一个数组: let a = [8, 9, 10]; let b: [u8;3] = [8, 6, 5]; print!("{}", a[0]); [u8; 3] != [u8; 4]。这么设计是为了更安全和高效的使用内存,当然了, 这会给第一次接触类似概念的人带来一点点困难,比如以下代码。 fn show(arr: [u8;3]) { for i in &arr { print!("{} ", i); } } fn main() { let a: [u8; 3] = [1, 2, 3]; show(a); let b: [u8; 4] = [1, 2, 3, 4]; show(b); } show(b); 传入的参数类型是 [u8, 4], 但是show(arr: [u8, 3]) 这个函数签名只接受[u8,3]。 因此可以使用切片(Slice),一个Slice的类型是&[T] 或者 &mut [T], 因此以上代码可以改为: fn show(arr: &[u8]) { for i in arr { print!("{} ", i); } println!(""); } fn main() { let a: [u8; 3] = [1, 2, 3]; let slice_a = &a[..]; show(slice_a); let b: [u8; 4] = [1, 2, 3, 4]; show(&b[..]); } let s1 = "Hello, world!".to_string(); let s2 = String::from("Hello, world!"); 函数(functions) 2.5 操作符 一元操作符 -: 取负,用于数值类型。 *: 解引用,这是一个很有用的符号,和Deref(DerefMut)这个trait关联密切。 !: 取反。 &和&mut: 租借,borrow。向一个owner租借其使用权,分别是租借一个只读使用权和读写使用权。 算数操作符 std::ops下: +: 加法。实现了std::ops::Add。 -: 减法。实现了std::ops::Sub。 *: 乘法。实现了std::ops::Mul。 /: 除法。实现了std::ops::Div。 %: 取余。实现了std::ops::Rem。 // 包括: + - * / % let a = 5; let b = a + 1; //6 let c = a - 1; //4 let d = a * 2; //10 let e = a / 2; //2 not 2.5 let f = a % 2; //1 let g = 5.0 / 2.0; //2.5 比较运算符 std::cmp::PartialEq和std::cmp::PartialOrd // == = != < > <= >= let a = 1; let b = 2; let c = a == b; //false let d = a != b; //true let e = a < b; //true let f = a > b; //false let g = a <= a; //true let h = a >= a; //true let i = true > false; //true let j = 'a' > 'A'; //true 逻辑运算符 // ! && || let a = true; let b = false; let c = !a; //false let d = a && b; //false let e = a || b; //true // rust 的逻辑运算符是惰性boolean运算符 // @question 什么是惰性boolean运算符 位运算符 &: 与操作。实现了std::ops::BitAnd。 |: 或操作。实现了std::ops::BitOr。 ^: 异或。实现了std::ops::BitXor。 <<: 左移运算符。实现了std::ops::Shl。 >>: 右移运算符。实现了std::ops::Shr。 // & | ^ << >> let a = 1; let b = 2; let c = a & b; //0 (01 && 10 -> 00) let d = a | b; //3 (01 || 10 -> 11) let e = a ^ b; //3 (01 != 10 -> 11) let f = a << b; //4 (左移 -> '01'+'00' -> 100) let g = a >> a; //0 (右移 -> o̶1̶ -> 0) 赋值运算符 let mut a = 2; a += 5; //2 + 5 = 7 a -= 2; //7 - 2 = 5 a *= 5; //5 * 5 = 25 a /= 2; //25 / 2 = 12 not 12.5 a %= 5; //12 % 5 = 2 a &= 2; //10 && 10 -> 10 -> 2 a |= 5; //010 || 101 -> 111 -> 7 a ^= 2; //111 != 010 -> 101 -> 5 a <<= 1; //'101'+'0' -> 1010 -> 10 a >>= 2; //101̶0̶ -> 10 -> 2 类型转换运算符: as let a = 15; let b = a / 2.0; //7.5 let b = (a as f64) / 2.0; //7.5 借用(Borrowing)与解引用(Dereference)操作符 // 引用/借用: & &mut fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); // The length of 'hello' is 5. } fn calculate_length(s: &String) -> usize { // 获取引用作为函数参数称为借用 s.len() } // 解引用: * fn main() { // 获取v的第2个元素的可变引用,并通过解引用修改该元素的值。 let v = &mut [1, 2, 3, 4, 5]; { let third = v.get_mut(2).unwrap(); *third += 50; } println!("v={:?}", v); // v=[1, 2, 53, 4, 5] } 2.6 流程控制 if - else if - else let team_size = 7; if team_size < 5 { println!("Small"); } else if team_size < 10 { println!("Medium"); } else { println!("Large"); } // 条件块中有返回值时,类型需要一致,可替代C语言的三目运算符 let is_below_eighteen = if team_size < 18 { true } else { false }; match let tshirt_width = 20; let tshirt_size = match tshirt_width { 16 => "S", // check 16 17 | 18 => "M", // check 17 and 18 19 ... 21 => "L", // check from 19 to 21 (19,20,21) 22 => "XL", _ => "Not Available", }; println!("{}", tshirt_size); // L _表示匹配剩下的任意情况。 match 的一些其他用法: let num = 8; match num { 13 => println!("正确"), 3|4|5|6 => println!("3 或 4 或 5 或 6" ), 14...20 => println!("14..20"), // ... 表示一个范围,可以是数字, `1 ... 10` 也可以是字母 `'a' ... 'z'` _=> println!("不正确"), } let pair = (0, -2); match pair { (0, y) => println!("y={}", y), (x, 0) => println!("x={}", x), _ => println!("default") } // 配合 if 使用 match num { x if x != 20 => println!("14..20"), _=> println!("不正确"), } // 后置套件 let x = 4; let y = false; match x { 4 | 5 if y => println!("yes"), _ => println!("no"), } // 绑定变量 match num { 13 => println!("正确"), 3|4|5|6 => println!("3 或 4 或 5 或 6" ), n @ 14...20 => println!("{}", n), _=> println!("不正确"), } while let mut a = 1; while a <= 10 { println!("Current value : {}", a); a += 1; // Rust不支持++/--自增自减语法 } loop while true let mut a = 0; loop { if a == 0 { println!("Skip Value : {}", a); a += 1; continue; } else if a == 2 { println!("Break At : {}", a); break; } println!("Current Value : {}", a); a += 1; } // Skip Value : 0 // Current Value : 1 // Break At : 2 for for a in 0..10 { //(a = 0; a <10; a++) println!("Current value : {}", a); } 'outer_for: for c1 in 1..6 { //set label outer_for 'inner_for: for c2 in 1..6 { println!("Current Value : [{}][{}]", c1, c2); if c1 == 2 && c2 == 2 { break 'outer_for; } // 结束外层循环 } } let group : [&str; 4] = ["Mark", "Larry", "Bill", "Steve"]; for person in group.iter() { println!("Current Person : {}", person); } 3. 其他数据类型 3.1 结构体(struct) struct User { username: String, email: String, sign_in_count: u64, active: bool, } 创建实例 let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; 修改某个字段的值 let mut user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; user1.email = String::from("anotheremail@example.com"); 变量与字段名同名的简写语法 fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } } 元组结构体(tuple structs) struct Color(i32, i32, i32); struct Point(i32, i32); let black = Color(0, 0, 0); let origin = Point(3, 4); struct Point { x: i32 y: i32 } let origin = Point { x: 3 y: 4 } 3.2 枚举 定义枚举 enum IpAddrKind { V4, V6, } 使用枚举值 let four = IpAddrKind::V4; fn route(ip_type: IpAddrKind) { } route(four); route(IpAddrKind::V6); 枚举成员关联数据 enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1")); 更复杂的例子 enum Message { Quit, // 不关联数据 Move { x: i32, y: i32 }, // 匿名结构体 Write(String), ChangeColor(i32, i32, i32), } match 控制流 enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => { println!("Lucky penny!"); 1 }, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } } Option 来表示。 Option 的定义如下: pub enum Option<T> { Some(T), None, } 包含2个枚举项: 1) None,表明失败或没有值 2) Some(value),元组结构体,封装了一个 T 类型的值 value 得益于Option,Rust 不允许一个可能存在空值的值,像一个正常的有效值那样工作,在编译时就能够检查出来。Rust显得更加安全,不用担心出现其他语言运行时才会出现的空指针异常的bug。例如: let x: i8 = 5; // Rust 没有空值(Null),因此 i8只能被赋予一个有效值。 let y: Option<i8> = Some(5); // y 可能为空,需要显示地表示为枚举类型 Option let sum = x + y; 相加时,编译器会报错: error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied --> | 5 | let sum = x + y; | ^ no implementation for `i8 + std::option::Option<i8>` | ,否则必须赋予有效值。而为了使用Option,需要编写处理每个成员的代码,当T 为有效值时,才能够从 Some(T) 中取出 T 的值来使用,如果 T 为无效值,可以进行其他的处理,通常使用 match 来处理这种情况。 例如,当y为有效值时,返回x和y的和;为空值时,返回x。 fn plus(x: i8, y: Option<i8>) -> i8 { match y { None => x, Some(i) => x + i, } } fn main() { let y1: Option<i8> = Some(5); let y2: Option<i8> = None; let z1 = plus(10, y1); let z2 = plus(10, y2); println!("z1={}, z2={}", z1, z2); // z1=15, z2=10 } if let 控制流 fn plus(x: i8, y: Option<i8>) { match y { Some(i) => { println!("x + y = {}", x + i) }, None => {}, } } fn main() { let y1: Option<i8> = Some(5); let y2: Option<i8> = None; plus(10, y1); // x + y = 15 plus(10, y2); } fn plus(x: i8, y: Option<i8>) { if let Some(i) = y { println!("x + y = {}", x + i); } } fn plus(x: i8, y: Option<i8>) { if y.is_some() { let i = y.unwrap(); // 获得 Some 中的 T 值。 println!("x + y = {}", x + i); } } fn plus(x: i8, y: Option<i8>) { if let Some(i) = y { println!("x + y = {}", x + i); } else { println!("y is None"); } } // 等价于 fn plus(x: i8, y: Option<i8>) { match y { Some(i) => { println!("x + y = {}", x + i) }, None => { println!("y is None") }, } } 3.3 实现方法和接口(impl & trait) 实现方法(impl) impl 在 struct、enum 或者trait 上实现方法。 关联函数 (associated function) 的第一个参数通常为self参数,有3种变体: self: 允许实现者移动和修改对象,对应的闭包特性为FnOnce。 &self: 既不允许实现者移动对象也不允许修改,对应的闭包特性为Fn。 &mut self: 允许实现者修改对象但不允许移动,对应的闭包特性为FnMut。 struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle::square(10); println!( "The area of the rectangle is {} square pixels.", rect1.area() ); println!( "The area of the rectangle is {} square pixels.", rect2.area() ); } ==, !=, >=), 也不支持像+和*这样的双目运算符,需要自己实现,或者使用match进行匹配。 类似于 lua 里面的 metatables,可以通过实现一些借口函数来实现自定义结构体的运算: struct User { name:String, age:i32, } impl Eq for User { fn eq(&self, other: User) -> bool { self.age == other.age } } 实现接口(trait) trait Summary { fn summarize(&self) -> String; } impl Summary for Rectangle { fn summarize(&self) -> String { format!("{width={}, height={}}", self.width, self.height) } } // 接口也支持继承 trait Person { fn full_name(&self) -> String; } trait Employee : Person { //Employee inherit from person trait fn job_title(&self) -> String; } trait Expat { fn salary(&self) -> f32 } trait ExpatEmployee : Employee + Expat { // 多继承,同时继承 Employee 和 Expat fn additional_tax(&self) -> f64; } trait Foo { fn foo(&self); // default method fn bar(&self) { println!("We called bar."); } } // inheritance trait FooBar : Foo { fn foobar(&self); } struct Baz; impl Foo for Baz { fn foo(&self) { println!("foo"); } } impl FooBar for Baz { fn foobar(&self) { println!("foobar"); } } 如果一个特性不在当前作用域内,它就不能被实现。 不管是特性还是impl,都只能在当前的包装箱内起作用。 impl Trait //before fn foo() -> Box<Trait> { // ... } //after fn foo() -> impl Trait { // ... } trait Trait { fn method(&self); } impl Trait for i32 { // implementation goes here } impl Trait for f32 { // implementation goes here } //before fn foo() -> Box<Trait> { Box::new(5) as Box<Trait> } //after fn foo() -> impl Trait { 5 } // before fn foo() -> Box<Fn(i32) -> i32> { Box::new(|x| x + 1) } // after fn foo() -> impl Fn(i32) -> i32 { |x| x + 1 } // before fn foo<T: Trait>(x: T) { // after fn foo(x: impl Trait) { 3.4 泛型(Generics) 告诉编译器 T 是泛型。 函数中使用泛型 fn largest<T>(list: &[T]) -> T { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest } fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!("The largest number is {}", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!("The largest char is {}", result); } struct Point<T> { x: T, y: T, } fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; } enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), } struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x()); } 3.5 常见集合 Vec 新建 let v: Vec<i32> = Vec::new(); // 空集合 // let v = vec![1, 2, 3]; // 含初始值的集合,vec!是为方便初始化Vec提供的宏。 println!("第三个元素 {}", &v[2]); // 3 println!("第100个元素 {}", &v[100]); // panic error assert_eq!(v.get(2), Some(&3)); assert_eq!(v.get(100), None); 更新 let v: Vec<i32> = Vec::new(); v.push(5); v.push(6); v.push(7); v.push(8); v.pop() //删除最后一个元素 遍历 let v = vec![100, 32, 57]; for i in &v { println!("{}", i); } let mut v2 = vec![100, 32, 57]; for i in &mut v2 { *i += 50; } if let 控制流 fn main() { let mut v = vec![1, 2, 3, 4, 5]; { let third = v.get_mut(2).unwrap(); *third += 50; } println!("v={:?}", v); // v=[1, 2, 53, 4, 5] } 枚举类型,那么可以使用if let来简化代码。 fn main() { let mut v = vec![1, 2, 3, 4, 5]; if let Some(third) = v.get_mut(2) { *third += 50; } println!("v={:?}", v); // v=[1, 2, 53, 4, 5] } while let 控制流 let mut stack = vec![1, 2, 3, 4, 5]; while let Some(top) = stack.pop() { println!("{}", top); // 依次打印 5 4 3 2 1 } let v = vec![1, 2, 3]; for i in &v { .. } // 获得引用 for i in &mut v { .. } // 获得可变引用 for i in v { .. } // 获得所有权,注意此时Vec的属主将会被转移!! // @question 为什么说使用for容易造成嵌套循环 3.6 常见集合 String 新建 let mut s1 = String::new(); let s2 = "initial contents".to_string(); let s3 = String::new(); 更新 let mut s = String::from("foo"); s.push_str("bar"); // 附加字符串 s.push('!') // 附加单字符 assert_eq!(s.remove(0), 'f'); // 删除某个位置的字符 let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; format let s1 = String::from("tic"); let s2 = String::from("tac"); let s3 = String::from("toe"); let s = format!("{}-{}-{}", s1, s2, s3); println!("{}", s); // tic-tac-toe 索引 let v = String::from("hello"); assert_eq!(Some('h'), v.chars().nth(0)); 遍历 let v = String::from("hello"); for c in v.chars() { println!("{}", c); } 的封装, 但是有些字符可能会占用超过2个字符,所以String不支持直接索引,如果需要索引需要使用 chars() 转换后再使用。 3.7 常见集合 HashMap 新建 use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); 访问 use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); let team_name = String::from("Blue"); let score = scores.get(&team_name); 更新 use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); // 10 scores.insert(String::from("Blue"), 25); // 25 // Blue 存在则不更新,不存在则更新,因此scores['Blue'] 仍为 25 scores.entry(String::from("Blue")).or_insert(50); 4 错误处理 4.1 不可恢复错误 panic! 直接调用 fn main() { panic!("crash and burn"); } $ cargo run Compiling tutu v0.1.0 (/xxx/demo/tutu) Finished dev [unoptimized + debuginfo] target(s) in 0.28s Running `target/debug/tutu` thread 'main' panicked at 'crash and burn', src/main.rs:2:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. 代码bug引起的错误 fn main() { let v = vec![1, 2, 3]; v[99]; // 越界 } $ RUST_BACKTRACE=1 cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.00s Running `target/debug/tutu` thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /rustc/xxx/src/libcore/slice/mod.rs:2717:10 stack backtrace: 0: backtrace::backtrace::libunwind::trace at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.37/src/backtrace/libunwind.rs:88 ... 17: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index at /rustc/xxx/src/liballoc/vec.rs:1796 18: tutu::main at src/main.rs:4 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. release [profile.release] panic = "abort" 4.2 可恢复错误 Result 处理 Result enum Result<T, E> { Ok(T), Err(E), } fn main(){ let f: u32 = File::create("hello.txt"); } = note: expected type `u32` found type `std::result::Result<std::fs::File, std::io::Error>` 获取到文件句柄。 下面是一个完整的示例,创建 hello.txt 文件,并尝试写入 “Hello, world!”。 use std::fs::File; use std::io::prelude::*; fn main() { let f = File::create("hello.txt"); let mut file = match f { Ok(file) => file, Err(error) => { panic!("Problem create the file: {:?}", error) }, }; match file.write_all(b"Hello, world!") { Ok(()) => {}, Err(error) => { panic!("Failed to write: {:?}", error) } }; } unwrap 和 expect 中的值,如果失败,则直接调用 !panic,程序结束。 let f = File::open("hello.txt").unwrap(); // 若成功,f则被赋值为文件句柄,失败则结束。 let f = File::open("hello.txt").expect("Failed to open hello.txt"); 返回 Result use std::io; use std::io::Read; use std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } } use std::io; use std::io::Read; use std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } 5 包、crate和模块 5.1 包和 crate . ├── Cargo.lock ├── Cargo.toml ├── benches │ └── large-input.rs ├── examples │ └── simple.rs ├── src │ ├── bin │ │ └── another_executable.rs │ ├── lib.rs │ └── main.rs └── tests └── some-integration-tests.rs 5.2 模块 声明模块 // src/main.rs mod math { mod basic { fn plus(x: i32, y: i32) -> i32 { x + y} fn mul(x: i32, y: i32) -> i32 { x * y} } } fn main() { println!("2 + 3 = {}", math::basic::plus(2, 3)); println!("2 * 3 = {}", math::basic::mul(2, 3)); } 引入作用域 // src/lib.rs pub mod greeting { pub fn hello(name: &str) { println!("Hello, {}", &name) } // pub 才能外部可见 } // src/main.rs use tutu; fn main() { tutu::greeting::hello("Jack"); // Hello, Jack } // src/main.rs use tutu::greeting; fn main() { greeting::hello("Jack"); } 分隔模块 // src/greeting.rs pub fn hello(name: &str) { println!("Hello, {}", &name) } // src/lib.rs pub mod greeting; pub fn func() { greeting::hello("Tom"); } // src/main.rs use tutu::greeting; fn main() { greeting::hello("Jack"); } 6 测试 6.1 单元测试(unit tests) fn plus(x: i32, y: i32) -> i32 { x + y } fn main() { let x = 10; let y = 20; println!("{} + {} = {}", x, y, plus(x, y)) } #[test] fn it_works() { assert_eq!(4, plus(2, 2), ); } $ cargo test running 1 test test it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out fn plus(x: i32, y: i32) -> i32 { x + y } fn main() { println!("2 + 3 = {}", plus(2, 3)); } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { assert_eq!(4, plus(2, 2), ); } } 6.2 集成测试(integration tests) ├── Cargo.toml ├── src │ └── lib.rs │ └── main.rs ├── tests └── test_lib.rs // src/lib.rs pub fn plus(x: i32, y: i32) -> i32 { // plus 必须是公共API,才能被集成测试。 x + y } // src/main.rs use tutu; fn main() { println!("2 + 3 = {}", tutu::plus(2, 3)); } // tests/test_lib.rs use tutu; #[test] fn it_works() { assert_eq!(4, tutu::plus(2, 2)); } running 1 test test it_adds_two ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 7 生命周期 借用不改变内存的所有者(Owner),借用只是对源内存的临时引用。 在借用周期内,借用方可以读写这块内存,所有者被禁止读写内存;且所有者保证在有“借用”存在的情况下,不会释放或转移内存。 失去所有权的变量不可以被借用(访问)。 在租借期内,内存所有者保证不会释放/转移/可变租借这块内存,但如果是在非可变租借的情况下,所有者是允许继续非可变租借出去的。 借用周期满后,所有者收回读写权限 借用周期小于被借用者(所有者)的生命周期。

2020/3/1
articleCard.readMore

从 Vim 切换至 SpaceVim 的细节

准备工作 迁移成本 准备工作 什么是 SpaceVim? +py、+py3)扩展以及内置的 API 更加容易使用。 如何安装 SpaceVim? 迁移成本 按键习惯 SPC 为前缀键,不占用 Leader 键,这就意味着原先的一些 以 Leader 为前缀的快捷键,可以无缝迁移过来。 配置习惯 ~/.vimrc 这个文件内 书写 Vim Script,在 SpaceVim 内引入了一个新的概念,启动函数,这是一个当 SpaceVim 启动 时候会被调用的函数,可以将 Vim 脚本写在这个函数内,比如: function! myspacevim#init() let g:focus_was_lost = 0 set notitle endfunction

2020/2/20
articleCard.readMore

SpaceVim 下的异步任务系统

配置文件 基本使用 进阶使用 make、ant等,SpaceVim 内置一个任务管理系统,通过分析项目的任务配置文件,获取相关命令,并且异步执行,同时根据配置按照需求展示运行结果。 配置文件 ~/.SpaceVim.d/tasks.toml和.SpaceVim.d/tasks.toml,局部配置文件具有更高的优先权限。 基本使用 [my-first-task] command = 'echo' args = ['hello world'] ~/.SpaceVim.d/tasks.toml后,在SpaceVim内按下SPC p t r快捷键,就会弹出如下界面: 可以使用j/k按键进行上下选择,回车键执行,也可以按下任务名称前面的序号直接执行。执行效果如下: 进阶使用 [in]: e:/ctest/ ▶ bin/ ▼ src/ main.c src/main.c至bin/目录,以及调用编译后的可以执行文件运行: [file-build] command = 'gcc $(file) -o $(workspaceFolder)/bin/$(workspaceFolderBasename)' [file-run] command = "$(workspaceFolder)/bin/$(workspaceFolderBasename)" 变量名 值 $(file) e:/ctest/src/main.c $(workspaceFolder) e:/ctest $(workspaceFolderBasename) ctest SpaceVim 中文官网: https://spacevim.org/cn/ 中文 Gitter 聊天室:https://gitter.im/SpaceVim/cn

2020/2/14
articleCard.readMore

心型脂肪酸结合蛋白(H-FABP)检测的临床意义

什么是 H-FABP? 与传统心肌标志物比较 H-FABP 测定的临床应用 急性冠脉综合征的预后价值 H-FABP 与其他项目联合检查 什么是 H-FABP? 生物标志物 分子量 (kDa) 初始检测时间 到达峰值 回到正常值 备注 H-FABP 15kDa 30 分钟 6-12 小时 24 小时 Early rise specificity Myoglobin 17kDa 1-3 小时 5-8 小时 16-24 小时 Early rise Low specificity Troponin I (TnI) 22kDa 3-6 小时 14-18 小时 5-10 天 Late rise High specificity Troponin T (TnT) 33kDa 3-6 小时 10-48 小时 10-15 天 Late rise High specificity CK-MB 86kDa 3-8 小时 9-24 小时 48-72 小时 Late rise High specificity 与传统心肌标志物比较 H-FABP 测定的临床应用 急性冠脉综合征的预后价值 H-FABP 与其他项目联合检查 《心型脂肪酸结合蛋白(H—FABP)与肌钙蛋白 I(cTnI)联合诊断 ACS 的价值》- 中西医结合心血管病电子杂志 2017 年 4 期 《Clinical Value of H-FABP》 《心型脂肪酸结合蛋白、肌钙蛋白 T、超敏 C 反应蛋白联合检测在急性心肌梗死中的价值》- 中国医药科学杂志 2014 年 10 月第 4 卷第 19 期

2019/8/22
articleCard.readMore

右键使用 SpaceVim 打开文件

我们知道,在 Linux 命令行里,可以使用 cd 命令切换到项目所在的目录, 此时启动 SpaceVim,会读取工程目录下的一些 SpaceVim 配置信息。 这样的好处有很多,假定我日常需要编辑 Java Python 和 c 语言项目, 那么在使用SpaceVim时不需要在用户配置里将所有的语言模块都启用了, 而是只需要在 Java 项目根目录创建一个 .SpaceVim.d/init.toml 文件, 在此文件里启用 lang#java 模块即可。同理,在 Python 项目根目录同样操作启用 lang#python 模块即可。 但是在 windows 系统下使用 neovim-qt 时,就不太方便了,为此,添加了这一功能,可以使用 windows 自身的文件管理器,定位到项目所在位置后使用右键打开 SpaceVim 并读取项目下的 SpaceVim 配置。 下面,我们来看看如何添加: 按下 Win + r 在运行弹窗理输入:regedit 回车,打开注册表编辑器: 定位到 HKEY_CLASSES_ROOT > * > shell : "D:\Program Files\Neovim\bin\nvim-qt.exe" -qwindowgeometry 1310x650+20+20 "%1" nvim --server \\.\pipe\nvim-pipe-123123123 --remote "%1" pcall(function() vim.fn.serverstart([[\\.\pipe\nvim-pipe-123123123]]) end) 此时,就可以在右键中使用 neovim-qt 打开文件了,上面启动参数里指定了窗口位置和大小,可以自行调节。

2019/8/7
articleCard.readMore

使用 Vim 管理待办事项

简介 安装 简介 " @todo Use new prolog plugin " call add(plugins, ['wsdjeg/prolog-vim', { 'merged' : 0}]) call add(plugins, ['wsdjeg/prolog.vim', { 'merged' : 0}]) 安装 call dein#add('wsdjeg/vim-todo') +job 特性,因此需要确保你的 Vim 版本满足要求,并且需要安装一个命令行工具 rg

2019/8/6
articleCard.readMore

如何学习并使用 SpaceVim

起因 起因 在 SpaceVim 的聊天室里,经常可以看到用户提问相类似的问题,或者说提问一些文档中已经写的很清楚的问题。

2019/8/5
articleCard.readMore

《波西米亚狂想曲》观后感

好久没有去电影院看电影了。最近,在跟天天一起走访终端,正好今天抽空去看了一场电影《波西米亚狂想曲》。 每个人都会有自己热爱的东西、向往的东西。 再往后的日子里不断地去追寻,会遇到世间各种纷纷扰扰。 只期望能保持初心。

2019/3/27
articleCard.readMore

降钙素原检测的临床意义

导读 概念 临床意义 感染类 非感染类 PCT、IL-6、CRP联合检查 导读 概念 临床意义 感染类 非感染类 手术后 严重创伤 严重烧伤 持续性心源性休克 严重灌注不足,多器官功能衰竭,重症胰腺炎 严重肾功能不全 器官移植 严重肝硬化,或急慢性病毒性肝炎 新生儿出生最初几天 中暑 某些自身免疫性疾病 肿瘤晚期 横纹肌溶解症状 持续心肺复苏后 药物因素 PCT、IL-6、CRP联合检查 特别注意:没有任何一个生物标志物是绝对敏感又绝对特异的, 不能单凭某个生物标志物的改变来诊断疾病,只有结合、 参照患者的临床表现与其他实验室检查结果,才能做出正确的判断。 联合检查是未来的趋势: 1、《感染相关生物标志物临床意义解读专家共识》指出:“多个指标的联合检测将是未来的发展趋势,可提高对感染性疾病的早期诊断率和预后判断价值。”[1] 2、PCT、IL-6、CRP相比,IL-6的敏感性优于PCT和CRP,但它的特异性比PCT差,联检项目可以优势互补,各取所长。有研究显示,针对脓毒症患者联合检测PCT+IL-6+CRP、PCT+IL-6或PCT+CRP ,有助于临床识别早期脓毒症[3](见表1) 表1 三种检测指标敏感性和特异性比较(%) 3、另一项研究PCT、CRP及 IL-6联合测定可以提高对细菌感染和病毒感染的鉴别效力,同时可有效的指导临床抗生素的使用、评估治疗效果[4]。

2019/1/1
articleCard.readMore

再见了,网易博客

用了很多年的网易博客有点舍不得,可惜后续不能再使用了,网易新推出的写作平台不是很感冒。 是时候说再见了。

2018/12/12
articleCard.readMore

Ruby 模块和包

require 语句 include 语句 Mixins 模块提供了一个命名空间和避免名字冲突。 模块实现了 mixin 装置。 模块不能实例化 模块没有子类 模块只能被另一个模块定义 module Identifier statement1 statement2 ........... end #!/usr/bin/ruby # 定义在 trig.rb 文件中的模块 module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end #!/usr/bin/ruby # 定义在 moral.rb 文件中的模块 module Moral VERY_BAD = 0 BAD = 1 def Moral.sin(badness) # ... end end require 语句 require filename $LOAD_PATH << '.' require 'trig.rb' require 'moral' y = Trig.sin(Trig::PI/4) wrongdoing = Moral.sin(Moral::VERY_BAD) include 语句 include modulename ruby/support.rb 文件中。 module Week FIRST_DAY = "Sunday" def Week.weeks_in_month puts "You have four weeks in a month" end def Week.weeks_in_year puts "You have 52 weeks in a year" end end #!/usr/bin/ruby $LOAD_PATH << './ruby' require "support" class Decade include Week no_of_yrs=10 def no_of_months puts Week::FIRST_DAY number=10*12 puts number end end d1=Decade.new puts Week::FIRST_DAY Week.weeks_in_month Week.weeks_in_year d1.no_of_months Mixins module A def a1 end def a2 end end module B def b1 end def b2 end end class Sample include A include B def s1 end end samp=Sample.new samp.a1 samp.a2 samp.b1 samp.b2 samp.s1 模块 A 由方法 a1 和 a2 组成。 模块 B 由方法 b1 和 b2 组成。 类 Sample 包含了模块 A 和 B。 类 Sample 可以访问所有四个方法,即 a1、a2、b1 和 b2。 因此,您可以看到类 Sample 继承了两个模块,您可以说类 Sample 使用了多重继承或 mixin 。

2018/10/10
articleCard.readMore

Ruby 代码块

yield 语句 块和方法 BEGIN 和 END 块 块由大量的代码组成。 您需要给块取个名称。 块中的代码总是包含在大括号 {} 内。 块总是从与其具有相同名称的函数调用。这意味着如果您的块名称为 test,那么您要使用函数 test 来调用这个块。 您可以使用 yield 语句来调用块。 block_name{ statement1 statement2 .......... } yield 语句 #!/usr/bin/ruby # -*- coding: UTF-8 -*- def test puts "在 test 方法内" yield puts "你又回到了 test 方法内" yield end test {puts "你在块内"} #!/usr/bin/ruby # -*- coding: UTF-8 -*- def test yield 5 puts "在 test 方法内" yield 100 end test { |i| puts "你在块 #{i} 内" } test {|i| puts "你在块 #{i} 内"} puts "你在块 #{i} 内" 你在块 5 内 yield a, b test {|a, b| statement} 块和方法 #!/usr/bin/ruby def test yield end test{ puts "Hello world"} #!/usr/bin/ruby def test(&block) block.call end test { puts "Hello World!"} BEGIN 和 END 块 #!/usr/bin/ruby # -*- coding: UTF-8 -*- BEGIN { # BEGIN 代码块 puts "BEGIN 代码块" } END { # END 代码块 puts "END 代码块" } # MAIN 代码块 puts "MAIN 代码块" BEGIN 代码块 MAIN 代码块 END 代码块

2018/10/4
articleCard.readMore

Ruby 函数

从方法返回值 return 语句 可变数量的参数 类方法 alias 语句 undef 语句 def method_name [( [arg [= default]]...[, * arg [, &expr ]])] expr.. end def method_name expr.. end def method_name (var1, var2) expr.. end def method_name (var1=value1, var2=value2) expr.. end method_name method_name 25, 30 #!/usr/bin/ruby # -*- coding: UTF-8 -*- def test(a1="Ruby", a2="Perl") puts "编程语言为 #{a1}" puts "编程语言为 #{a2}" end test "C", "C++" test 从方法返回值 def test i = 100 j = 10 k = 2 end tvar = test puts tvar return 语句 return [expr[',' expr...]] return 或 return 12 或 return 1,2,3 #!/usr/bin/ruby def test i = 100 j = 200 k = 300 return i, j, k end var = test puts var 可变数量的参数 #!/usr/bin/ruby def sample (*test) puts "The number of parameters is #{test.length}" for i in 0...test.length puts "The parameters are #{test[i]}" end end sample "Zara", "6", "F" sample "Mac", "36", "M", "MCA" 类方法 class Accounts def reading_charge end def Accounts.return_date puts "2019" end end # 我们已经知道方法 return_date 是如何声明的。 # 它是通过在类名后跟着一个点号,点号后跟着方法名来声明的。 # 您可以直接访问类方法,如下所示: Accounts.return_date alias 语句 alias 方法名 方法名 alias 全局变量 全局变量 alias foo bar alias $MATCH $& undef 语句 undef 方法名 undef bar

2018/9/30
articleCard.readMore

Ruby 循环

while 语句 while 修饰符 until 语句 until 修饰符 for 语句 break 语句 next 语句 redo 语句 retry 语句 while 语句 while conditional [do] code end <pre> while conditional [:] code end #!/usr/bin/ruby # -*- coding: UTF-8 -*- $i = 0 $num = 5 while $i < $num do puts("在循环语句中 i = #$i" ) $i +=1 end while 修饰符 code while condition begin code end while conditional #!/usr/bin/ruby # -*- coding: UTF-8 -*- $i = 0 $num = 5 begin puts("在循环语句中 i = #$i" ) $i +=1 end while $i < $num until 语句 until conditional [do] code end #!/usr/bin/ruby # -*- coding: UTF-8 -*- $i = 0 $num = 5 until $i > $num do puts("在循环语句中 i = #$i" ) $i +=1; end until 修饰符 code until conditional begin code end until conditional #!/usr/bin/ruby # -*- coding: UTF-8 -*- $i = 0 $num = 5 begin puts("在循环语句中 i = #$i" ) $i +=1; end until $i > $num for 语句 for variable [, variable ...] in expression [do] code end #!/usr/bin/ruby # -*- coding: UTF-8 -*- for i in 0..5 puts "局部变量的值为 #{i}" end (expression).each do |variable[, variable...]| code end #!/usr/bin/ruby # -*- coding: UTF-8 -*- (0..5).each do |i| puts "局部变量的值为 #{i}" end break 语句 break #!/usr/bin/ruby # -*- coding: UTF-8 -*- for i in 0..5 if i > 2 then break end puts "局部变量的值为 #{i}" end next 语句 next #!/usr/bin/ruby # -*- coding: UTF-8 -*- for i in 0..5 if i < 2 then next end puts "局部变量的值为 #{i}" end redo 语句 redo #!/usr/bin/ruby # -*- coding: UTF-8 -*- for i in 0..5 if i < 2 then puts "局部变量的值为 #{i}" redo end end retry 语句 retry begin do_something # 抛出的异常 rescue # 处理错误 retry # 重新从 begin 开始 end for i in 1..5 retry if some_condition # 重新从 i == 1 开始 end #!/usr/bin/ruby # -*- coding: UTF-8 -*- for i in 1..5 retry if i > 2 puts "局部变量的值为 #{i}" end 这将产生以下结果,并会进入一个无限循环:

2018/9/24
articleCard.readMore

ruby 流程控制

if…else 语句 if 修饰符 unless 语句 unless 修饰符 case 语句 if…else 语句 if conditional [then] code... [elsif conditional [then] code...]... [else code...] end if a == 4 then a = 7 end #!/usr/bin/ruby # -*- coding: UTF-8 -*- x=1 if x > 2 puts "x 大于 2" elsif x <= 2 and x!=0 puts "x 是 1" else puts "无法得知 x 的值" end if 修饰符 code if condition #!/usr/bin/ruby $debug=1 puts "debug\n" if $debug unless 语句 unless conditional [then] code [else code ] end #!/usr/bin/ruby x=1 unless x>2 puts "x 小于 2" else puts "x 大于 2" end unless 修饰符 code unless conditional #!/usr/bin/ruby # -*- coding: UTF-8 -*- $var = 1 print "1 -- 这一行输出\n" if $var print "2 -- 这一行不输出\n" unless $var $var = false print "3 -- 这一行输出\n" unless $var case 语句 case expression [when expression [, expression ...] [then] code ]... [else code ] end when a == 4 then a = 7 end case expr0 when expr1, expr2 stmt1 when expr3, expr4 stmt2 else stmt3 end _tmp = expr0 if expr1 === _tmp || expr2 === _tmp stmt1 elsif expr3 === _tmp || expr4 === _tmp stmt2 else stmt3 end #!/usr/bin/ruby # -*- coding: UTF-8 -*- $age = 5 case $age when 0 .. 2 puts "婴儿" when 3 .. 6 puts "小孩" when 7 .. 12 puts "child" when 13 .. 18 puts "少年" else puts "其他年龄段的" end

2018/9/20
articleCard.readMore

Ruby 运算符

算术运算符 比较运算符 赋值运算符 并行赋值 算术运算符 运算符 描述 实例 + 加法 - 把运算符两边的操作数相加 a + b 将得到 30 - 减法 - 把左操作数减去右操作数 a - b 将得到 -10 * 乘法 - 把运算符两边的操作数相乘 a * b 将得到 200 / 除法 - 把左操作数除以右操作数 b / a 将得到 2 % 求模 - 把左操作数除以右操作数,返回余数 b % a 将得到 0 ** 指数 - 执行指数计算 a**b 将得到 10 的 20 次方 puts 1 + 2 puts 1 * 2 puts 2 - 1 puts 2 / 2 puts 11 % 3 puts 4**3 比较运算符 运算符 描述 实例 == 检查两个操作数的值是否相等,如果相等则条件为真。 (a == b) 不为真。 != 检查两个操作数的值是否相等,如果不相等则条件为真。 (a != b) 为真。 > 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 (a > b) 不为真。 < 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 (a < b) 为真。 >= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 (a >= b) 不为真。 <= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 (a <= b) 为真。 <=> 联合比较运算符。如果第一个操作数等于第二个操作数则返回 0,如果第一个操作数大于第二个操作数则返回 1,如果第一个操作数小于第二个操作数则返回 -1。 (a <=> b) 返回 -1。 === 用于测试 case 语句的 when 子句内的相等。 (1…10) === 5 返回 true。 .eql? 如果接收器和参数具有相同的类型和相等的值,则返回 true。 1 == 1.0 返回 true,但是 1.eql?(1.0) 返回 false。 equal? 如果接收器和参数具有相同的对象 id,则返回 true。 如果 aObj 是 bObj 的副本,那么 aObj == bObj 返回 true,a.equal?bObj 返回 false,但是 a.equal?aObj 返回 true。 赋值运算符 运算符 描述 实例 = 简单的赋值运算符,把右操作数的值赋给左操作数 c = a + b 将把 a + b 的值赋给 c += 加且赋值运算符,把右操作数加上左操作数的结果赋值给左操作数 c += a 相当于 c = c + a -= 减且赋值运算符,把左操作数减去右操作数的结果赋值给左操作数 c -= a 相当于 c = c - a *= 乘且赋值运算符,把右操作数乘以左操作数的结果赋值给左操作数 c _= a 相当于 c = c _ a /= 除且赋值运算符,把左操作数除以右操作数的结果赋值给左操作数 c /= a 相当于 c = c / a %= 求模且赋值运算符,求两个操作数的模赋值给左操作数 c %= a 相当于 c = c % a **= 指数且赋值运算符,执行指数计算,并赋值给左操作数 c **= a 相当于 c = c ** a 并行赋值 a = 10 b = 20 c = 30 a, b, c = 10, 20, 30 a, b = b, a

2018/9/12
articleCard.readMore

如何在 Vim 内进行高效的排序

排序命令 排序函数 sort()、uniq() 和排序命令 :sort。机遇这两种方式,可以在 Vim 内对文本进行高效的排序。 下面分两部分详细说明下这两种方式的使用方法。 排序命令 :sort 命令的用法格式如下: :[range]sor[t][!] [b][f][i][n][o][r][u][x] [/{pattern}/] [range] 值得是一个范围,:sort 命令会基于这个范围进行排序,当未制定范围时,会对整个文档进行排序。关于 [range] 的常用方法有下面几种: 我们看到 sor[t] 最后一个字母 t 被方括号包围,表示该字母可以省略,即更简单地执行 :sor 命令。 在 :sort命令紧接其后的感叹号 ! 表示是否进行反向排序,不带感叹号则是正向排序,带上则反之。 在 :sort 命令紧接其后的第一个参数为可选参数,包括 b, f, i, n, o, r, u, x。首先,需要了解选项 n f x o b 之间是互斥的,也就是说不可以同时使用这些选项,换句话说。前面的这个五个选项可以和 i r u 这三项组合使用。下面分别说下这些参数的意义: 带 n 则排序基于每行的第一个十进制数 (在 {pattern} 匹配之后或之内)。数值包含前导的 ‘-‘。 带 f 则排序基于每行的第一个浮点数。浮点值相当于在文本( {pattern} 匹配的之后或之内) 上调用 str2float() 函数的结果。仅当 Vim 编译时加入浮点数支持时该标志位才有效。 带 x 则排序基于每行的第一个十六进制数 (在 {pattern} 匹配之后或之内)。忽略该数值前的 “0x” 或 “0X”。但包含前导的 ‘-‘。 带 o 则排序基于每行的第一个八进制数 (在 {pattern} 匹配之后或之内)。 带 b 则排序基于每行的第一个二进制数 (在 {pattern} 匹配之后或之内)。 带 u (u 代表 unique 唯一) 则只保留完全相同的行的第一行 (如果同时带 i,忽略大小写的区别)。没有这个标志位,完全相同的行的序列会按照它们原来的顺序被保留下来。 注意: 行首和行尾的的空白差异会影响唯一性的判定。 排序函数 sort() 和 uniq(),sort() 这个函数的用法如下: sort({list} [, {func} [, {dict}]]) sort() 这一函数第二个参数可以接受如下几种情况: 1 或者 i: 表示忽略大小写。 n:按照数值排序,即使用 strtod() 解析 List 内的元素,字符串、列表、字典和函数引用均视作 0。 f:按照浮点数值来排序,要求给定的 List 每一个选项都是浮点数。 一个 Funcref 变量。这个变量表示的是一个函数,则调用该函数来比较项目,该函数会使用两个项目作为参数,根据返回值确定两个项目关系。 0 表示相等,1 或者更高,表示第一个排在第二个之后,-1 或更小代表第一个排在第二个之前。

2018/9/7
articleCard.readMore

(Neo)Vim 插件开发指南

简介 基本语法 注释 变量 作用域 函数定义 插件的目录结构 Vim 自定义命令 简介 基本语法 注释 " 开头的,只存在行注释,不存在块注释。因此,对于多行注释,需要再每行开头添加 "。 示例: " 这是一行注释, let g:helloworld = 1 " 这是在行尾注释 变量 let 来申明变量,最基本的方式为: " 定义一个类型是字符串的变量 g:helloworld let g:helloworl = "sss" 类型 ID 描述 Number 0 整数 String 1 字符串 Funcref 2 函数指针 List 3 列表 Dictionary 4 字典 Float 5 浮点数 Boolean 6   None 7   Job 8   Channel 9   作用域 g: 表示全局变量,s: 表示脚本变量。 在一些特殊情况下,前缀是可以省略的,Vim 会为该变量选择默认的作用域。不同的情况下,默认的作用域是不一样的,在函数内部,默认作用域是局部变量, 而在函数外部,默认作用域是全局变量: let g:helloworld = 1 " 这是一个全局变量, g: 前缀未省略 let helloworld = 1 " 这也是一个全局变量,在函数外部,默认的作用域是全局的 function! HelloWorld() let g:helloworld = 1 " 这是函数内部全局变量 let helloworld = 1 " 这是一个函数内部的局部变量,在函数内部,默认的作用域为局部变量 endfunction 前缀 描述 g: 全局变量 l: 局部变量,只可在函数内部使用 s: 脚本变量,只可以在当前脚本函数内使用 v: Vim 特殊变量 b: 作用域限定在某一个缓冲区内 w: 作用域限定在窗口内部 t: 作用域限定在标签内部 函数定义 function 关键字定义函数,可缩写成 func 或者 fn, 格式: :fu[nction][!] {name}([arguments]) [range] [abort] [dict] [closure] closure 时,函数可以访问外部的变量或者参数,比如: function! Foo() let x = 0 function! Bar() closure let x += 1 return 1 endfunction return funcref('Bar') endfunction let F = Foo() echo F() " 1 echo F() " 2 echo F() " 3 插件的目录结构 autoload/ 自动载入脚本 colors/ 颜色主题 plugin/ 在 Vim 启动时将被载入的脚本 ftdetect/ 文件类型识别脚本 syntax/ 语法高亮文件 ftplugin/ 文件类型相关插件 compiler/ 编译器 indent/ 语法对齐 autoload/ 顾名思义,该文件夹下的脚本会在特点条件下自动被载入。这里的特定条件指的是当某一个 autoload 类型的函数被调用,并且 Vim 当前环境下并未定义该函数时。 比如调用 call helloworld#init() 时,Vim 会先检测当前环境下是否定义了该函数,若没有,则在 autoload/ 目录下找 helloworld.vim 这一文件, 并将其载入,载入完成后执行 call helloworld#init(). plugin/ 该目录里的文件将在 Vim 启动时被运行,作为一个优秀的 Vim 插件,应当尽量该目录下的脚本内容。通常,可以将插件的快捷键、命令的定义保留在这个文件里。 ftdetect/ ftdetect 目录里通常存放的是文件类型检测脚本,该目录下的文件也是在 Vim 启动时被载入的。在这一目录里的文件内容,通常比较简单,比如: autocmd BufNewFile,BufRead *.helloworld set filetype=helloworld .helloworld 为后缀的文件时,将文件类型设置为 helloworld。通常,这个脚本的文件名是和所需要设置的文件类型一样的,上面的例子中文件的名称就是 helloworld.vim。 syntax/ 这一目录下的文件,主要是定义语法高亮的。通常文件名前缀和对应的语言类型相同,比如 Java 的语法文件文件名为 java.vim。 关于如何写语法文件,将在后面详细介绍。 colors/ colors 目录下主要存储一些颜色主题脚本,当执行 :colorscheme + 主题名 命令时,对应的颜色主题脚本将被载入。比如执行 :colorscheme helloworld 时,colors/helloworld.vim 这一脚本将被载入。 compiler/ 这一名录里是一些预设的编译器参数,主要给 :make 命令使用的。在最新版的 Vim 中可以使用 :compiler! 编译器名 来为当前缓冲区设定编译器。比如当执行 :compiler! helloworld 时,compiler/helloworld.vim 这一脚本将被载入。 indent/ 在 indent 目录里,主要是一些语法对齐相关的脚本。 Vim 自定义命令 command 命令来定义,比如: command! -nargs=* -complete=custom,helloworld#complete HelloWorld call helloworld#test() command 命令其后的 ! 表示强制定义该命令,即使前面已经定义过了同样名称的命令,也将其覆盖掉。 -nargs=* 表示,该命令可接受任意个数的参数, 包括 0 个。-nargs 的取值有以下几种情况: 参数 定义 -nargs=0 不接受任何参数(默认) -nagrs=1 只接受一个参数 -nargs=* 可接收任意个数参数 -nargs=? 可接受 1 个或者 0 个参数 -nargs=+ 至少提供一个参数 -complete=custom,helloworld#complete 表示,改命令的补全方式采用的是自定义函数 helloworld#complete。-complete 可以接受的参数包括如下内容: 参数 描述 -complete=augroup autocmd 组名 -complete=buffer buffer 名称 -complete=behave :behave 命令子选项 -complete=color 颜色主题 -complete=command Ex 命令及参数 -complete=compiler 编译器 -complete=cscope :cscope 命令子选项 -complete=dir 文件夹名称 -complete=environment 环境变量名称 -complete=event 自动命令的事件名称 -complete=expression Vim 表达式 -complete=file 文件及文件夹名称 -complete=file_in_path path 选项里的文件及文件夹名称 -complete=filetype 文件类型 -complete=function 函数名称 -complete=help 帮助命令子选项 -complete=highlight 高亮组名称 -complete=history :history 子选项 -complete=locale locale 名称(相当于命令 locale -a 的输出) -complete=mapping 快捷键名称 -complete=menu 目录 -complete=messages :messages 命令子选项 -complete=option Vim 选项名称 -complete=packadd 可选的插件名称补全 -complete=shellcmd shell 命令补全 -complete=sign :sign 命令补全 -complete=syntax 语法文件名称补全 -complete=syntime :syntime 命令补全 -complete=tag tags -complete=tag_listfiles tags, file names are shown when CTRL-D is hit -complete=user user names -complete=var user variables -complete=custom,{func} custom completion, defined via {func} -complete=customlist,{func} custom completion, defined via {func} -complete=custom,{func} 和 -complete=customlist,{func}。这两种区别在与函数的返回值, 前者要求是一个 string 而后者要求补全函数的返回值是 list. 自定义命令补全函数接受三个参数。 :function {func}(ArgLead, CmdLine, CursorPos) | 表示光标位置,我按下了 <Tab> 键调用了补全函数,那么传递给补全函数的三个参数分别是: :HelloWorld hello| 参数名 描述 ArgLead 当前需要补全的部分,通常是光标前的字符串,上面的例子中是指 hello CmdLine 指的是整个命令行内的内容,此时是 HelloWorld hello CursorPos 指的当前光标所在的位置,此时是 16, 即为 len('HelloWorld hello') function! helloworld#complete(ArgLead, CmdLine, CursorPos) abort return join(['hellolily', 'hellojeky', 'hellofoo', 'world'] \ "\n") endfunction ArgLead 来筛选出可以用来补全的选项,并展示在状态栏上。 此时,四行里最后一个 world 因为开头不匹配 ArgLead 所以不会被展示在状态栏上,因此补全效果只有三个可选项。 -complete=customlist,{func} 这一参数所对应的补全函数,也是接受相同的三个参数,但该函数返回的是一个 list。 下面,我们来测试这个函数: function! helloworld#complete(ArgLead, CmdLine, CursorPos) abort return ['hellolily', 'hellojeky', 'hellofoo', 'world'] endfunction customlist 补全时不会自动根据 ArgLead 进行筛选,并且直接补全整个返回的 list,即使列表中有一个 world 完全与 ArgLead(hello) 不同, 也会将其直接覆盖。因此,当使用 customlist 时,需要在函数内根据 ArgLead 进行筛选,将函数该为如下,就可以得到相同效果了: function! helloworld#complete(ArgLead, CmdLine, CursorPos) abort return filter(['hellolily', 'hellojeky', 'hellofoo', 'world'], 'v:val =~ "^" . a:ArgLead') endfunction -bang 参数: -bang 参数来申明这个命令接受感叹号。比如 :q 与 :q!。 下面是一个实例: fu! s:hello(bang) if a:bang echom "带有叹号" else echom "不带有叹号" endif endf command! -bang Hello call s:hello(<bang>0) <bang>0, 当执行:Hello! 时,传递给 s:hello 这一函数的参数是 !0 即为 1,因此,此时看到打印了”带有叹号“。 其实除了写成 <bang>0, 还可以写 <bang>1, 甚至是 <bang> + 一个全局变量。比如: let g:hello = 0 fu! s:hello(bang) if a:bang echom "带有叹号" else echom "不带有叹号" endif endf command! -bang Hello call s:hello(<bang>g:hello)

2018/8/31
articleCard.readMore

如何配置 SpaceVim

设置 SpaceVim 选项 启用/禁用   模块 添加自定义插件 自定义快捷键及插件配置 设置  SpaceVim  选项 启动/禁用模块 添加自定义插件 添加自定义按键映射以及插件配置 设置 SpaceVim 选项 let g:spacevim_* 这样的语句来设置 SpaceVim 选项。而在新版的  SpaceVim  中,我们采用了  toml  作为默认配置文件,如果不熟悉  toml  语法的,可以先阅读一下  toml  的基本语法,当然不读也没关系, toml  已经是最简单的配置文件格式了。 所有的  SpaceVim  选项配置在一个字典里,key  为原先的选项名去除  g:spacevim_ 前缀: g:spacevim_enable_guicolors -> enable_guicolors [options]     enable_guicolors = false [options]     enable_guicolors = false     snippet_engine = "neosnippet"     statusline_separator = 'arrow'     sidebar_width = 30 启用/禁用   模块 [[layers]]     name = "shell"     default_position = "top"     default_height = 30 [[layers]]     name = "shell"     enable = false 添加自定义插件 [[custom_plugins]]     name = "lilydjwg/colorizer"     merged = 0 [[custom_plugins]]     name = "tpope/vim-scriptease"     merged = 0     on_cmd = "Scriptnames" 自定义快捷键及插件配置 [options]     enable_guicolors = false     snippet_engine = "neosnippet"     statusline_separator = 'arrow'     sidebar_width = 30     bootstrap_before = "myspacevim#before" bootstrap_after = "myspacevim#after" function! myspacevim#before() abort     let g:neomake_enabled_c_makers = ['clang']     nnoremap jk <esc> endf function! myspacevim#after() abort endf augroup MySpaceVim au! autocmd FileType markdown setlocal nowrap augroup END 也是应大多数人要求,更新的这篇文字,仓促之下,有很多内容可能还不完整,如果有什么疑问,欢迎留言。

2018/8/28
articleCard.readMore

Ruby 数据类型

数值类型(Number) 数值类型(Number) 123 # Fixnum 十进制 1_234 # Fixnum 带有下划线的十进制 -500 # 负的 Fixnum 0377 # 八进制 0xff # 十六进制 0b1011 # 二进制 ?a # 'a' 的字符编码 ?\n # 换行符(0x0a)的编码 12345678901234567890 # Bignum #整型 Integer 以下是一些整型字面量 #字面量(literal):代码中能见到的值,数值,bool值,字符串等都叫字面量 #如以下的0,1_000_000,0xa等 a1=0 #带千分符的整型 a2=1_000_000 #其它进制的表示 a3=0xa puts a1,a2 puts a3 #puts print 都是向控制台打印字符,其中puts带回车换行符 =begin 这是注释,称作:嵌入式文档注释 类似C#中的/**/ =end 123.4 # 浮点值 1.0e6 # 科学记数法 4E20 # 不是必需的 4e+20 # 指数前的符号 #浮点型 f1=0.0 f2=2.1 f3=1000000.1 puts f3 +-*/;指数操作符为** 指数不必是整数,例如 #指数算术 puts 2**(1/4)#1与4的商为0,然后2的0次方为1 puts 16**(1/4.0)#1与4.0的商为0.25(四分之一),然后开四次方根 \\ 和 \' 两个反斜线符号。 实例 #!/usr/bin/ruby -w puts 'escape using "\\"'; puts 'That\'s right'; #!/usr/bin/ruby -w puts 'escape using "\\"'; puts 'That\'s right'; escape using "\" That's right #{ expr } 替换任意 Ruby 表达式的值为一个字符串。在这里,expr 可以是任意的 Ruby 表达式。 #!/usr/bin/ruby -w puts "Multiplication Value : #{24*60*60}"; Multiplication Value : 86400 #!/usr/bin/ruby -w name="Ruby" puts name puts "#{name+",ok"}" Ruby Ruby,ok 符号 表示的字符 \n 换行符 (0x0a) \r 回车符 (0x0d) \f 换页符 (0x0c) 退格键 (0x08) \a 报警符 Bell (0x07) \e 转义符 (0x1b) \s 空格符 (0x20) \nnn 八进制表示法 (n 是 0-7) \xnn 十六进制表示法 (n 是 0-9、a-f 或 A-F) \cx, \C-x Control-x \M-x Meta-x (c | 0x80) \M-\C-x Meta-Control-x \x 字符 x #!/usr/bin/ruby ary = [ "fred", 10, 3.14, "This is a string", "last element", ] ary.each do |i| puts i end fred 10 3.14 This is a string last element #!/usr/bin/ruby hsh = colors = { "red" => 0xf00, "green" => 0x0f0, "blue" => 0x00f } hsh.each do |key, value| print key, " is ", value, "\n" end green is 240 red is 3840 blue is 15 #!/usr/bin/ruby (10..15).each do |n| print n, ' ' end 10 11 12 13 14 15

2018/8/15
articleCard.readMore

Ruby 基本语法

程序中的空白 程序中的行尾 标识符 保留字 Here Document BEGIN 语句 END 语句 注释 #!/usr/bin/ruby -w puts "Hello, Ruby!"; $ ruby test.rb Hello, Ruby! 程序中的空白 a + b 被解释为 a+b (这是一个局部变量) a +b 被解释为 a(+b) (这是一个方法调用) 程序中的行尾 标识符 保留字 BEGIN do next then END else nil true alias elsif not undef and end or unless begin ensure redo until break false rescue when case for retry while class if return while def in self __FILE__ defined? module super __LINE__ Here Document #!/usr/bin/ruby -w # -*- coding : utf-8 -*- print <<EOF 这是第一种方式创建here document 。 多行字符串。 EOF print <<"EOF"; # 与上面相同 这是第二种方式创建here document 。 多行字符串。 EOF print <<`EOC` # 执行命令 echo hi there echo lo there EOC print <<"foo", <<"bar" # 您可以把它们进行堆叠 I said foo. foo I said bar. bar This is the first way of creating her document ie. multiple line string. This is the second way of creating her document ie. multiple line string. hi there lo there I said foo. I said bar. BEGIN 语句 BEGIN { code } #!/usr/bin/ruby puts "This is main Ruby Program" BEGIN { puts "Initializing Ruby Program" } Initializing Ruby Program This is main Ruby Program END 语句 END { code } #!/usr/bin/ruby puts "This is main Ruby Program" END { puts "Terminating Ruby Program" } BEGIN { puts "Initializing Ruby Program" } Initializing Ruby Program This is main Ruby Program Terminating Ruby Program 注释 # 我是注释,请忽略我。 name = "Madisetti" # 这也是注释 # 这是注释。 # 这也是注释。 # 这也是注释。 # 这还是注释。 =begin 这是注释。 这也是注释。 这也是注释。 这还是注释。 =end

2018/8/12
articleCard.readMore

初识 Ruby 编程语言

一直想再多接触一些脚本语言,之前写了一段时间 python 和 lua,感觉都非常不错。之所以要再学习 Ruby, 主要是看到 Ruby 的一些语法上的灵活性。 Ruby 支持代码块、修饰符等这些在其他语言里似乎都没有,最简单的示例: #!/usr/bin/ruby $debug=1 puts "debug\n" if $debug 新建了一个 Ruby 的笔记仓库,用来记录 Ruby 学习资料。 https://github.com/wsdjeg/ruby-tutorial-cn

2018/8/9
articleCard.readMore

Vim 中文文档规范检查插件

初心 使用说明 协作开发 初心 使用说明 :CheckChinese 错误代码 描述 E001 中文字符后存在英文标点 E002 中英文之间没有空格 E003 中文与数字之间没有空格 E004 中文标点之后存在空格 E005 行尾含有空格 协作开发 wsdjeg/ChineseLinter.vim

2018/8/1
articleCard.readMore

为什么要学习 Vim?

Vim 的特点 Vim 的特点 极简的思想 其实,Vim 代表的是一种 KISS 的 Geek 思想,而不仅仅是炫耀。这种思想是指:“选择你需要的,舍弃那些你不需要的”, 一种极简思想。一个臃肿的工具,也许可以提供 100% 的功能,但是,实际上你只需要 10% 的功能,其余 90% 功能, 你可能都没有心思去研究。那为什么不丢弃臃肿的 90%,轻装上阵呢? 当然,适当的展示,活跃社区气氛,吸引新人入坑 Vim 是我辈使命。 Vim 模式 Vim 不同于其他编辑器的地方,就是她提供的模式化编辑,和文本对象。任何 IDE 或者编辑器模拟的 Vim 插件, 无非是实现了这两个功能。通过模式,Vim 赋予了同一个按键多种功能,大大提高了按键的功效,可以让我们双手 集中在键盘中央区域,提高效率。另外,Vim 将一切文本看成对象,比如删除一个单词,diw( delete in word ) 等等这一类用法,我可以理解为 Vim 语。 记得以前看过一段文字,大致内容是这样的: 你只有折腾 Vim 累死过 3 次,你才能真正的喜欢上它,不然你就会选择放弃,最终回到 IDE。 在 Vim 的世界里,一切都需要自己动手,所以,他不适合不喜欢折腾的人,如果你对它没有 200% 的好奇心,不要玩, 不然你一定会摔键盘. 为什么说 Vim 写代码快? 前提你累死过 3 次,成功配置了 Vim。 那么开始神奇的 Vim 之旅。你就发现: 你删代码比队友快: “first blood” 自动补全比队友快 在 20 个 G 的文件堆里定位代码各种秒杀你队友 在 vim 中完成各种 shell, 秒杀你队友 “ Killing spree ”! 优雅的更新升级, 删除 Vim 插件(前提 Vundle ) “ Dominating ”! 随性的自定义快捷键,完全属于你自己的风格, 一个配置文件随身带着走。或者压缩 Vim 文件包带着走, 想去哪就去哪,不用等你队友带节奏。Unstoppable ! 你队友还在用鼠标?那你已经进化了 。Wicked Sick !! 什么? 开始用 Vim Markdown 来写文档了? 我草,你已经超神了!! God Like !!! 你已经离不开 Vim 了。 从最开始被各种折腾到想要砸键盘的你, 如今你已经爱上他了。aM-m-m-m….(重复 8 次) Monster Kill 你注定和 Vim 过完这一生!……Holy Shit 删除一行 dd 不就行了,为什么要鼠标选中一行之后按下 backspace ? 清除一行内容 S 不就行了,为什么还要鼠标选中一行然后按下 backspace ? 给一行末尾补分号 A; 不就好了,为什么还要鼠标移过去末尾点一下然后按下;? 然后给 n 行末尾补分号,VNj 选中多行 :normal .(点可以重复上次操作)不就好了,为什么要鼠标一个一个点了然后一个个补? 复制 a 行插入到 b 行后,dd 掉 a 行在 b 行那按下 p 不就好了。 为什么要鼠标选中 a,ctrl+x,然后到 b 的末尾按下回车,然后 ctrl-v ? 所以,同为喜欢 Vim 的你,让我们一起来说 Vim 语吧!

2018/3/25
articleCard.readMore

Vim 下多光标编辑 iedit 模式

什么是 iedit 模式 如何启动 iedit 模式 iedit 模式模式快捷键 什么是 iedit 模式 如何启动 iedit 模式 Normal 模式下直接按下快捷键 SPC s e 会匹配光标所在的单词,并启动 iedit 模式。 Visual 模式下按下快捷键 SPC s e 会匹配所选择的单词,并启动 iedit 模式。 Highlight Symbol 模式按下 e 键会更具以选择的匹配位置来启动 iedit 模式。 iedit 模式模式快捷键 Key Binding From to SPC s e normal or visual iedit-Normal Key Binding Description Esc go back to Normal mode i switch to iedit-Insert mode, same as i a switch to iedit-Insert mode, same as a I go to the beginning of the current occurrence and switch to iedit-Insert mode A go to the end of the current occurrence and switch to iedit-Insert mode Move cursor to left Move cursor to right 0 go to the beginning of the current occurrence $ go to the end of the current occurrence D delete the occurrences S delete the occurrences and switch to iedit-Insert mode gg go to first occurrence G go to last occurrence n go to next occurrence N go to previous occurrence Key Binding Description Esc go back to iedit-Normal mode Move cursor to left Move cursor to right delete words before cursor delete words after cursor

2018/1/23
articleCard.readMore

Vim 从入门到精通

简介 什么是 Vim? Vim 哲学 入门 精简的 vimrc Windows 系统 Linux 或者 Mac OS 我正在使用什么样的 Vim 备忘录 基础 缓冲区,窗口,标签 已激活、已载入、已列出、已命名的缓冲区 参数列表 按键映射 映射前置键 寄存器 范围 标注 补全 动作,操作符,文本对象 自动命令 变更历史,跳转历史 内容变更历史记录 全局位置信息表,局部位置信息表 宏 颜色主题 折叠 会话 局部化 用法 获取离线帮助 获取离线帮助(补充) 获取在线帮助 执行自动命令 用户自定义事件 事件嵌套 剪切板 剪贴板的使用(Windows, OSX) 剪贴板的使用(Linux, BSD, …) 打开文件时恢复光标位置 临时文件 备份文件 交换文件 撤销文件 viminfo 文件 临时文件管理设置示例 编辑远程文件 插件管理 多行编辑 使用外部程序和过滤器 Cscope 1. 构建数据库 2. 添加数据库 3. 查询数据库 MatchIt 在 Vim 8 中安装 在 Vim 7 或者更早的版本中安装 简短的介绍 技巧 跳至选择的区域另一端 聪明地使用 n 和 N 聪明地使用命令行历史 智能 Ctrl-l 禁用错误报警声音和图标 快速移动当前行 快速添加空行 运行时检测 查看启动时间 NUL 符用新行表示 快速编辑自定义宏 快速跳转到源(头)文件 在 GUI 中快速改变字体大小 根据模式改变光标类型 防止水平滑动的时候失去选择 选择当前行至结尾,排除换行符 重新载入保存文件 更加智能的当前行高亮 更快的关键字补全 改变颜色主题的默认外观 命令 :global 和 :vglobal - 在所有匹配行执行命令 :normal 和 :execute - 脚本梦之队 重定向消息 调试 常规建议 调整日志等级 查看启动日志 查看运行时日志 Vim 脚本调试 语法文件调试 杂项 附加资源 Vim 配置集合 常见问题 编辑小文件时很慢 编辑大文件的时候很慢 持续粘贴(为什么我每次都要设置 ‘paste’ 模式) 在终端中按 ESC 后有延时 无法重复函数中执行的搜索 mhinz/vim-galore, 部分内容有增改。 简介 什么是 Vim? Vim 是一个历史悠久的文本编辑器,可以追溯到 qed。 Bram Moolenaar 于 1991 年发布初始版本。 Linux、Mac 用户,可以使用包管理器安装 Vim,对于 Windows 用户,可以使用 scoop 包管理器进行下载安装。 该版本可轻易添加 python 、python3 、lua 等支持,只需要安装 python、lua 即可。 项目在 Github 上开发,项目讨论请订阅 vim_dev 邮件列表。 通过阅读 Why, oh WHY, do those #?@! nutheads use vi? 来对 Vim 进行大致的了解。 Vim 哲学 普通模式 下浏览文件,在插入模式下插入文本, 在可视模式下选择行,在命令模式下执行命令等等。起初这听起来可能很复杂, 但是这有一个很大的优点:不需要通过同时按住多个键来完成操作, 大多数时候你只需要依次按下这些按键即可。越常用的操作,所需要的按键数量越少。 和模式编辑紧密相连的概念是 操作符 和 动作。操作符 指的是开始某个行为, 例如:修改、删除或者选择文本,之后你要用一个 动作 来指定需要操作的文本区域。 比如,要改变括号内的文本,需要执行 ci( (读做 change inner parentheses); 删除整个段落的内容,需要执行 dap (读做:delete around paragraph)。 如果你能看见 Vim 老司机操作,你会发现他们使用 Vim 脚本语言就如同钢琴师弹钢琴一样。 复杂的操作只需要几个按键就能完成。他们甚至不用刻意去想,因为这已经成为肌肉记忆了。 这减少认识负荷并帮助人们专注于实际任务。 入门 $ vimtutor 肌肉记忆 将越容易形成。 Vim 基于一个 vi 克隆,叫做 Stevie, 支持两种运行模式:”compatible” 和 “nocompatible”。在兼容模式下运行 Vim 意味着使用 vi 的默认设置, 而不是 Vim 的默认设置。除非你新建一个用户的 vimrc 或者使用 vim -N 命令启动 Vim, 否则就是在兼容模式下运行 Vim!请大家不要在兼容模式下运行 Vim。 下一步 创建你自己的 vimrc。 在第一周准备备忘录。 通读基础章节了解 Vim 还有哪些功能。 按需学习!Vim 是学不完的。如果你遇到了问题,先上网寻找解决方案,你的问题可能已经被解决了。Vim 拥有大量的参考文档,知道如何利用这些参考文档很有必要:获取离线帮助。 浏览附加资源。 插件之前,请先掌握 Vim 的基本操作。很多插件都只是对 Vim 自带功能的封装。 精简的 vimrc :version 命令查看。下面分 Windows 系统, 和 *niux 系统分别来说明 Vim 是如何载入配置文件的。 Windows 系统 system vimrc file: "$VIM\vimrc" user vimrc file: "$HOME\_vimrc" 2nd user vimrc file: "$HOME\vimfiles\vimrc" 3rd user vimrc file: "$VIM\_vimrc" user exrc file: "$HOME\_exrc" 2nd user exrc file: "$VIM\_exrc" system gvimrc file: "$VIM\gvimrc" user gvimrc file: "$HOME\_gvimrc" 2nd user gvimrc file: "$HOME\vimfiles\gvimrc" 3rd user gvimrc file: "$VIM\_gvimrc" defaults file: "$VIMRUNTIME\defaults.vim" system menu file: "$VIMRUNTIME\menu.vim" $HOME\_vimrc, 当这一文件不存在是, Vim 再去寻找 2nd user vimrc file: $HOME\vimfiles\vimrc; 倘若这个文件还是不存在,那么 Vim 会去继续寻找 3rd user vimrc file: $VIM\_vimrc。 了解以上顺序后,就不会再因为 Vim 总是不读取配置文件而感到烦恼了。 Linux 或者 Mac OS :version 命令查看 vim 载入配置的优先顺序。 系统 vimrc 文件: "/etc/vimrc" 用户 vimrc 文件: "$HOME/.vimrc" 第二用户 vimrc 文件: "~/.vim/vimrc" 用户 exrc 文件: "$HOME/.exrc" defaults file: "$VIMRUNTIME/defaults.vim" $VIM 预设值: "/etc" $VIMRUNTIME 预设值: "/usr/share/vim/vim81" minimal-vimrc 如果你有兴趣,这里是我(原作者)的 vimrc。 建议:大多数插件作者都维护不止一个插件并且将他们的 vimrc 放在 Github 上展示(通常放在叫做 “vim-config” 或者 “dotfiles” 的仓库中),所以当你发现你喜欢的插件时,去插件维护者的 Github 主页看看有没有这样的仓库。 我正在使用什么样的 Vim :version 命令将向你展示当前正在运行的 Vim 的所有相关信息,包括它是如何编译的。 第一行告诉你这个二进制文件的编译时间和版本号,比如:7.4。接下来的一行呈现 Included patches: 1-1051,这是补丁版本包。因此你 Vim 确切的版本号是 7.4.1051。 另一行显示着一些像 Tiny version without GUI 或者 Huge version with GUI 的信息。很显然这些信息告诉你当前的 Vim 是否支持 GUI,例如:从终端中运行 gvim 或者从终端模拟器中的 Vim 内运行 :gui 命令。另一个重要的信息是 Tiny 和 Huge。Vim 的特性集区分被叫做 tiny,small,normal,big and huge,所有的都实现不同的功能子集。 :version 主要的输出内容是特性列表。+clipboard 意味这剪贴板功能被编译支持了,-clipboard 意味着剪贴板特性没有被编译支持。 一些功能特性需要编译支持才能正常工作。例如:为了让 :prof 工作,你需要使用 huge 模式编译的 Vim,因为那种模式启用了 +profile 特性。 如果你的输出情况并不是那样,并且你是从包管理器安装 Vim 的,确保你安装了 vim-x,vim-x11,vim-gtk,vim-gnome 这些包或者相似的,因为这些包通常都是 huge 模式编译的。 你也可以运行下面这段代码来测试 Vim 版本以及功能支持: " Do something if running at least Vim 7.4.42 with +profile enabled. if (v:version > 704 || v:version == 704 && has('patch42')) && has('profile') " do stuff endif :h :version :h feature-list :h +feature-list :h has-patch 备忘录 http://people.csail.mit.edu/vgod/vim/vim-cheat-sheet-en.png https://cdn.shopify.com/s/files/1/0165/4168/files/preview.png http://www.nathael.org/Data/vi-vim-cheat-sheet.svg http://michael.peopleofhonoronly.com/vim/vim_cheat_sheet_for_programmers_screen.png http://www.rosipov.com/images/posts/vim-movement-commands-cheatsheet.png vim-cheat40。 基础 缓冲区,窗口,标签 缓冲区的一部分显示的。每一份文件都是在他们自己独有的缓冲区打开的,插件显示的内容也在它们自己的缓冲区中。 缓冲区有很多属性,比如这个缓冲区的内容是否可以修改,或者这个缓冲区是否和文件相关联,是否需要同步保存到磁盘上。 窗口 是缓冲区上一层的视窗。如果你想同时查看几个文件或者查看同一文件的不同位置,那样你会需要窗口。 请别把他们叫做 分屏 。你可以把一个窗口分割成两个,但是这并没有让这两个窗口完全 分离 。 窗口可以水平或者竖直分割并且现有窗口的高度和宽度都是可以被调节设置的,因此,如果你需要多种窗口布局,请考虑使用标签。 标签页 (标签)是窗口的集合。因此当你想使用多种窗口布局时候请使用标签。 简单的说,如果你启动 Vim 的时候没有附带任何参数,你会得到一个包含着一个呈现一个缓冲区的窗口的标签。 顺带提一下,缓冲区列表是全局可见的,你可以在任何标签中访问任何一个缓冲区。 已激活、已载入、已列出、已命名的缓冲区 vim file1 的命令启动 Vim 。这个文件的内容将会被加载到缓冲区中,你现在有一个已载入的缓冲区。如果你在 Vim 中保存这个文件,缓冲区内容将会被同步到磁盘上(写回文件中)。 由于这个缓冲区也在一个窗口上显示,所以他也是一个已激活的缓冲区。如果你现在通过 :e file2 命令加载另一个文件,file1 将会变成一个隐藏的缓冲区,并且 file2 变成已激活缓冲区。 使用 :ls 我们能够列出所有可以列出的缓冲区。插件缓冲区和帮助缓冲区通常被标记为不可以列出的缓冲区,因为那并不是你经常需要在编辑器中编辑的常规文件。通过 :ls! 命令可以显示被放入缓冲区列表的和未被放入列表的缓冲区。 未命名的缓冲区是一种没有关联特定文件的缓冲区,这种缓冲区经常被插件使用。比如 :enew 将会创建一个无名临时缓冲区。添加一些文本然后使用 :w /tmp/foo 将他写入到磁盘,这样这个缓冲区就会变成一个已命名的缓冲区。 参数列表 全局缓冲区列表是 Vim 的特性。在这之前的 vi 中,仅仅只有参数列表,参数列表在 Vim 中依旧可以使用。 每一个通过 shell 命令传递给 Vim 的文件名都被记录在一个参数列表中。可以有多个参数列表:默认情况下所有参数都被放在全局参数列表下,但是你可以使用 :arglocal 命令去创建一个新的本地窗口的参数列表。 使用 :args 命令可以列出当前参数。使用 :next,:previous,:first,:last 命令可以在切换在参数列表中的文件。通过使用 :argadd,:argdelete 或者 :args 等命令加上一个文件列表可以改变参数列表。 偏爱缓冲区列表还是参数列表完全是个人选择,我的印象中大多数人都是使用缓冲区列表的。 然而参数列表在有些情况下被大量使用:批处理 使用 :argdo! 一个简单的重构例子: :args **/*.[ch] :argdo %s/foo/bar/ge | update :h argument-list 按键映射 :map 命令家族你可以定义属于你自己的快捷键。该家族的每一个命令都限定在特定的模式下。从技术上来说 Vim 自带高达 12 中模式,其中 6 种可以被映射。另外一些命令作用于多种模式:   递归     非递归     模式                           :map :noremap normal, visual, operator-pending :nmap :nnoremap normal :xmap :xnoremap visual :cmap :cnoremap command-line :omap :onoremap operator-pending :imap :inoremap insert :nmap <space> :echo "foo"<cr> :nunmap <space> 可以取消这个映射。 对于更少数,不常见的模式(或者他们的组合),查看 :h map-modes。 到现在为止还好,对新手而言有一个问题会困扰他们::nmap 是递归执行的!结果是,右边执行可能的映射。 你自定义了一个简单的映射去输出“Foo”: :nmap b :echo "Foo"<cr> b (回退一个单词)的默认功能到一个键上呢? :nmap a b a,我们期望着光标回退到上一个单词,但是实际情况是“Foo”被输出到命令行里!因为在右边,b 已经被映射到别的行为上了,换句话说就是 :echo "Foo"<cr>。 解决此问题的正确方法是使用一种 非递归 的映射代替: :nnoremap a b :nmap 显示所以普通模式下的映射,:nmap <leader> 显示所有以 <leader> 键开头的普通模式下的映射。 如果你想禁止用标准映射,把他们映射到特殊字符 <nop> 上,例如::noremap <left> <nop>。 相关帮助: :h key-notation :h mapping :h 05.3 映射前置键 \\。我们可以通过在 map 中调用 <leader> 来为把它添加到其他按键映射中。 nnoremap <leader>h :helpgrep<space> \\ 然后按 h 就可以激活这个映射 :helpgrep<space>。如果你想通过先按 空格 键来触发,只需要这样做: let g:mapleader = ' ' nnoremap <leader>h :helpgrep<space> g:mapleader,因为在 Vim 脚本中,函数外的变量缺省的作用域是全局变量,但是在函数内缺省作用域是局部变量,而设置快捷键前缀需要修改全局变量 g:mapleader 的值。 另外,还有一个叫 <localleader> 的,可以把它理解为局部环境中的 <leader>,默认值依然为 \\。当我们需要只对某一个条件下(比如,特定文件类型的插件)的缓冲区设置特别的 <leader> 键,那么我们就可以通过修改当前环境下的 <localleader> 来实现。 注意:如果你打算设置 Leader 键,请确保在设置按键映射之前,先设置好 Leader 键。如果你先设置了含有 Leader 键的映射,然后又修改了 Leader 键,那么之前映射内的 Leader 键是不会因此而改变的。你可以通过执行 :nmap <leader> 来查看普通模式中已绑定给 Leader 键的所有映射。 请参阅 :h mapleader 与 :h maploacalleader 来获取更多帮助。 寄存器 y,粘贴的快捷键是 p。 Vim 为我们提供了如下的寄存器: 类型 标识 读写者 是否为只读 包含的字符来源 Unnamed " vim 否 最近一次的复制或删除操作 (d, c, s, x, y) Numbered 0至9 vim 否 寄存器 0: 最近一次复制。寄存器 1: 最近一次删除。寄存器 2: 倒数第二次删除,以此类推。对于寄存器 1 至 9,他们其实是只读的最多包含 9 个元素的队列。这里的队列即为数据类型 queue Small delete - vim 否 最近一次行内删除 Named a至z, A至Z 用户 否 如果你通过复制操作存储文本至寄存器 a,那么 a 中的文本就会被完全覆盖。如果你存储至 A,那么会将文本添加给寄存器 a,不会覆盖之前已有的文本 Read-only :与.和% vim 是 :: 最近一次使用的命令,.: 最近一次添加的文本,%: 当前的文件名 Alternate buffer # vim 否 大部分情况下,这个寄存器是当前窗口中,上一次访问的缓冲区。请参阅 :h alternate-file 来获取更多帮助 Expression = 用户 否 复制 VimL 代码时,这个寄存器用于存储代码片段的执行结果。比如,在插入模式下复制 <c-r>=5+5<cr>,那么这个寄存器就会存入 10 Selection +和* vim 否 * 和 + 是 剪贴板 寄存器 Drop ~ vim 是 最后一次拖拽添加至 Vim 的文本(需要 “+dnd” 支持,暂时只支持 GTK GUI。请参阅 :help dnd 及 :help quote~) Black hole _ vim 否 一般称为黑洞寄存器。对于当前操作,如果你不希望在其他寄存器中保留文本,那就在命令前加上 _。比如,"_dd 命令不会将文本放到寄存器 "、1、+ 或 * 中 Last search pattern / vim 否 最近一次通过 /、? 或 :global 等命令调用的匹配条件 :let @/ = 'register' n 的时候就会跳转到单词”register” 出现的地方。 有些时候,你的操作可能已经修改了寄存器,而你没有察觉到。请参阅 :h registers 获取更多帮助。 上面提到过,复制的命令是 y,粘贴的命令是 p 或者 P。但请注意,Vim 会区分「字符选取」与「行选取」。请参阅 :h linewise 获取更多帮助。 行选取: 命令 yy 或 Y 都是复制当前行。这时移动光标至其他位置,按下 p 就可以在光标下方粘贴复制的行,按下 P 就可以在光标上方粘贴至复制的行。 字符选取: 命令 0yw 可以复制第一个单词。这时移动光标至其他位置,按下 p 就可以在当前行、光标后的位置粘贴单词,按下 P 就可以在当前行、光标前的位置粘贴单词。 将文本存到指定的寄存器中: 命令 "aY 可以将当前行复制,并存储到寄存器 a 中。这时移动光标至其他位置,通过命令 "AY 就可以把这一行的内容扩展到寄存器 a 中,而之前存储的内容也不会丢失。 为了便于理解和记忆,建议大家现在就试一试上面提到的这些操作。操作过程中,你可以随时通过 :reg 来查看寄存器的变化。 有趣的是: 在 Vim 中,y 是复制命令,源于单词 “yanking”。而在 Emacs 中,”yanking” 代表的是粘贴(或者说,重新插入刚才删掉的内容),而并不是复制。 范围 很多命令都可以加一个数字,用于指明操作范围 范围可以是一个行号,用于指定某一行 范围也可以是一对通过 , 或 ; 分割的行号 大部分命令,默认只作用于当前行 只有 :write 和 :global 是默认作用于所有行的 :d 为 :delete 的缩写): 命令 操作的行 :d 当前行 :.d 当前行 :1d 第一行 :$d 最后一行 :1,$d 所有行 :%d 所有行(这是 1,$ 的语法糖) :.,5d 当前行至第 5 行 :,5d 同样是当前行至第 5 行 :,+3d 当前行及接下来的 3 行 :1,+3d 第一行至当前行再加 3 行 :,-3d 当前行及向上的 3 行(Vim 会弹出提示信息,因为这是一个保留的范围) :3,'xdelete 第三行至标注 为 x 的那一行 :/^foo/,$delete 当前行以下,以字符 “foo” 开头的那一行至结尾 :/^foo/+1,$delete 当前行以下,以字符 “foo” 开头的那一行的下一行至结尾 ; 也可以用于表示范围。区别在于,a,b 的 b 是以当前行作为参考的。而 a;b 的 b 是以 a 行作为参考的。举个例子,现在你的光标在第 5 行。这时 :1,+1d 会删除第 1 行至第 6 行,而 :1;+1d 会删除第 1 行和第 2 行。 如果你想设置多个寻找条件,只需要在条件前加上 /,比如: :/foo//bar//quux/d V 命令进入行选取模式,选中一些行后按下 : 进入命令模式,这时候你会发现 Vim 自动添加了 '<,'> 范围。这表示,接下来的命令会使用之前选取的行号作为范围。但如果后续命令不支持范围,Vim 就会报错。为了避免这样的情况发生,有些人会设置这样的按键映射::vnoremap foo :<c-u>command,组合键 Ctrl + u 可以清除当前命令行中的内容。 另一个例子是在普通模式中按下 !!,命令行中会出现 :.!。如果这时你如果输入一个外部命令,那么当前行的内容就会被这个外部命令的输出替换。你也可以通过命令 :?^$?+1,/^$/-1!ls 把当前段落的内容替换成外部命令 ls 的输出,原理是向前和向后各搜索一个空白行,删除这两个空白行之间的内容,并将外部命令 ls 的输出放到这两个空白行之间。 请参阅以下两个命令来获取更多帮助: :h cmdline-ranges :h 10.3 标注 标注 设置者 使用 a-z 用户 仅对当前的一个文件生效,也就意味着只可以在当前文件中跳转 A-Z 用户 全局标注,可以作用于不同文件。大写标注也称为「文件标注」。跳转时有可能会切换到另一个缓冲区 0-9 viminfo 0 代表 viminfo 最后一次被写入的位置。实际使用中,就代表 Vim 进程最后一次结束的位置。1 代表 Vim 进程倒数第二次结束的位置,以此类推 ' / g' 或者 ` / g` 然后按下标注名。 如果你想定义当前文件中的标注,可以先按下 m 再按下标注名。比如,按下 mm 就可以把当前位置标注为 m。在这之后,如果你的光标切换到了文件的其他位置,只需要通过 'm 或者 `m即可回到刚才标注的行。区别在于,'m会跳转回被标记行的第一个非空字符,而`m会跳转回被标记行的被标记列。根据 viminfo 的设置,你可以在退出 Vim 的时候保留小写字符标注。请参阅:h viminfo-' 来获取更多帮助。 如果你想定义全局的标注,可以先按下 m 再按下大写英文字符。比如,按下 mM 就可以把当前文件的当前位置标注为 M。在这之后,就算你切换到其他的缓冲区,依然可以通过 'M 或 `M 跳转回来。 关于跳转,还有以下的方式: 按键 跳转至 '[ 与 `[ 上一次修改或复制的第一行或第一个字符 '] 与 `] 上一次修改或复制的最后一行或最后一个字符 '< 与 `< 上一次在可视模式下选取的第一行或第一个字符 '> 与 `> 上一次在可视模式下选取的最后一行或最后一个字符 '' 与 `' 上一次跳转之前的光标位置 '" 与 `" 上一次关闭当前缓冲区时的光标位置 '^ 与 `^ 上一次插入字符后的光标位置 '. 与 `. 上一次修改文本后的光标位置 '( 与 `( 当前句子的开头 ') 与 `) 当前句子的结尾 '{ 与 `{ 当前段落的开头 '} 与 `} 当前段落的结尾 范围 一起使用。前面提到过,如果你在可视模式下选取一些文本,然后按下 :,这时候你会发现命令行已经被填充了 :'<,'>。对照上面的表格,现在你应该明白了,这段代表的就是可视模式下选取的范围。 请使用 :marks 命令来显示所有的标注,参阅 :h mark-motions 来获取关于标注的更多帮助。 补全 插入模式中通过 Ctrl + x 来触发: 映射 类型 帮助文档 <c-x><c-l> 整行 :h i^x^l <c-x><c-n> 当前缓冲区中的关键字 :h i^x^n <c-x><c-k> 字典(请参阅 :h 'dictionary')中的关键字 :h i^x^k <c-x><c-t> 同义词字典(请参阅 :h 'thesaurus')中的关键字 :h i^x^t <c-x><c-i> 当前文件以及包含的文件中的关键字 :h i^x^i <c-x><c-]> 标签 :h i^x^] <c-x><c-f> 文件名 :h i^x^f <c-x><c-d> 定义或宏定义 :h i^x^d <c-x><c-v> Vim 命令 :h i^x^v <c-x><c-u> 用户自定义补全(通过 'completefunc' 定义) :h i^x^u <c-x><c-o> Omni Completion(通过 'omnifunc' 定义) :h i^x^o <c-x>s 拼写建议 :h i^Xs 'complete' 选项,那么你就可以在一次操作中采用多种补全方案。这个选项默认包含了多种可能性,因此请按照自己的需求来配置。你可以通过 <c-n> 来调用下一个补全建议,或通过 <c-p> 来调用上一个补全建议。当然,这两个映射同样可以直接调用补全函数。请参阅 :h i^n 与 :h 'complete' 来获得更多帮助。 如果你想配置弹出菜单的行为,请一定要看一看 :h 'completeopt' 这篇帮助文档。默认的配置已经不错了,但我个人(原作者)更倾向于把 “noselect” 加上。 请参阅以下文档获取更多帮助: :h ins-completion :h popupmenu-keys :h new-omni-completion 动作,操作符,文本对象 动作也就是指移动光标的操作,你肯定很熟悉 h、j、k 和 l,以及 w 和 b。但其实,/ 也是一个动作。他们都可以搭配数字使用,比如 2?the<cr> 可以将光标移动到倒数第二个 “the” 出现的位置。 以下会列出一些常用的动作。你也可以通过 :h navigation 来获取更多的帮助。 操作符是对某个区域文本执行的操作。比如,d、~、gU 和 > 都是操作符。这些操作符既可以在普通模式下使用,也可以在可视模式下使用。在普通模式中,顺序是先按操作符,再按动作指令,比如 >j。在可视模式中,选中区域后直接按操作符就可以,比如 Vjd。 与动作一样,操作符也可以搭配数字使用,比如 2gUw 可以将当前单词以及下一个单词转成大写。由于动作和操作符都可以搭配数字使用,因此 2gU2w 与执行两次 gU2w 效果是相同的。 请参阅 :h operator 来查看所有的操作符。你也可以通过 :set tildeop 命令把 ~ 也变成一个操作符 值得注意的是,动作是单向的,而文本对象是双向的。文本对象不仅作用于符号(比如括号、中括号和大括号等)标记的范围内,也作用于整个单词、整个句子等其他情况。 文本对象不能用于普通模式中移动光标的操作,因为光标还没有智能到可以向两个方向同时跳转。但这个功能可以在可视模式中实现,因为在对象的一端选中的情况下,光标只需要跳转到另一端就可以了。 文本对象操作一般用 i 或 a 加上对象标识符操作,其中 i 表示在对象内(英文 inner)操作,a 表示对整个对象(英文 around)操作,这时开头和结尾的空格都会被考虑进来。举个例子,diw 可以删除当前单词,ci( 可以改变括号中的内容。 文本对象同样可以与数字搭配使用。比如,像 ((( ))) 这样的文本,假如光标位于最内层的括号上或最内层的括号内,那么 d2a( 将会删除从最内层开始的两对括号,以及他们之间的所有内容。其实,d2a( 这个操作等同于 2da(。在 Vim 的命令中,如果有两处都可以接收数字作为参数,那么最终结果就等同于两个数字相乘。在这里,d 与 a( 都是可以接收参数的,一个参数是 1,另一个是 2,我们可以把它们相乘然后放到最前面。 请参阅 :h text-objects 来获取更多关于文本对象的帮助。 自动命令 :au ,不要被结果吓到,这些是当前有效的所有自动命令。 请使用 :h {event} 来查看 Vim 中所有事件的列表,你也可以参考 :h autocmd-events-abc 来获取关于事件的更多帮助。 一个很常用的例子,就是针对文件类型执行某些设置: autocmd FileType ruby setlocal shiftwidth=2 softtabstop=2 comments-=:# FileType 事件。 在配置 vimrc 的时候,一般第一行加进去的就是 filetype on。这就意味着,Vim 启动时会读取 filetype.vim 文件,然后根据文件类型来触发相应的自动命令。 如果你勇于尝试,可以查看下 :e $VIMRUNTIME/filetype.vim,然后在输出中搜索 “Ruby”。这样,你就会发现其实 Vim 只是通过文件扩展名 .rb 判断某个文件是不是 Ruby 的。 注意:对于相同事件,如果有多个自动命令,那么自动命令会按照定义时的顺序执行。通过 :au 就可以查看它们的执行顺序。 au BufNewFile,BufRead *.rb,*.rbw setf ruby BufNewFile 与 BufRead 事件是被写在 Vim 源文件中的。因此,每当你通过 :e 或者类似的命令打开文件,这两个事件都会触发。然后,就是读取 filetype.vim 文件来判断打开的文件类型。 简单来说,事件和自动命令在 Vim 中的应用十分广泛。而且,Vim 为我们留出了一些易用的接口,方便用户配置适合自己的事件驱动回调。 请参阅 :h autocommand 来获取更多帮助 变更历史,跳转历史 变更历史中。如果在同一行有多个小改动,那么 Vim 会把它们合并成一个。尽管内容改动会合并,但作用的位置还是会只记录下最后一次改动的位置。 在你移动光标或跳转的时候,每一次的移动或跳转前的位置会被记录到跳转历史中。类似地,跳转历史也可以最多保存 100 条记录。对于每个窗口,跳转记录是独立的。但当你分离窗口时(比如使用 :split 命令),跳转历史会被复制过去。 Vim 中的跳转命令,包括 '、`、G、/、?、n、N、%、(、)、[[、]]、{、}、:s、:tag、L、M、H 以及开始编辑一个新文件的命令。 列表 显示所有条目 跳转到上一个位置 跳转到下一个位置 跳转历史 :jumps [count]<c-o> [count]<c-i> 变更历史 :changes [count]g; [count]g, > 标记来为你指示当前位置。通常这个标记位于 1 的下方,也就代表最后一次的位置。 如果你希望关闭 Vim 之后还保留这些条目,请参阅 :h viminfo-' 来获取更多帮助。 注意:上面提到过,最后一次跳转前的位置也会记录在标注中,也可以通过连按 \`\` 或 '' 跳转到那个位置 请参阅以下两个命令来获取更多帮助: :h changelist :h jumplist 内容变更历史记录 u 来取消更改,也可以通过「重做」操作 Ctrl + r 来恢复更改。 值得注意的是,Vim 采用 tree 数据结构来存储内容变更的历史记录,而不是采用 queue。你的每次改动都会成为存储为树的节点。而且,除了第一次改动(根节点),之后的每次改动都可以找到一个对应的父节点。每一个节点都会记录改动的内容和时间。其中,「分支」代表从任一节点到根节点的路径。当你进行了撤销操作,然后又输入了新的内容,这时候就相当于创建了分支。这个原理和 git 中的 branch(分支)十分类似。 考虑以下这一系列按键操作: ifoo<esc> obar<esc> obaz<esc> u oquux<exc> foo(1) / bar(2) / \ baz(3) quux(4) u 与重做 Ctrl + r 操作是按分支遍历。对于上面的例子,现在我们有三行字符。这时候按 u 会回退到 “bar” 节点,如果再按一次 u 则会回退到 “foo” 节点。这时,如果我们按下 Ctrl + r 就会前进至 “bar” 节点,再按一次就回前进至 “quux” 节点。在这种方式下,我们无法访问到兄弟节点(即 “baz” 节点)。 与之对应的是按时间遍历,对应的按键是 g- 和 g+。对于上面的例子,按下 g- 会首先回退到 “baz” 节点。再次按下 g- 会回退到 “bar” 节点。 命令/按键 执行效果 [count]u 或 :undo [count] 回退到 [count] 次改动之前 [count]<c-r> 或 :redo [count] 重做 [count] 次改动 U 回退至最新的改动 [count]g- 或 :earlier [count]? 根据时间回退到 [count] 次改动之前。”?” 为 “s”、”m”、”h”、”d” 或 “f”之一。例如,:earlier 2d 会回退到两天之前。:earlier 1f 则会回退到最近一次文件保存时的内容 [count]g+ 或 :later [count]? 类似 g-,但方向相反 备份文件,交换文件,撤销文件以及 viminfo 文件的处理章节的内容。 如果你觉得这一部分的内容难以理解,请参阅 undotree,这是一个可视化管理内容变更历史记录的插件。类似的还有 vim-mundo。 请参阅以下链接获取更多帮助: :h undo.txt :h usr_32 全局位置信息表,局部位置信息表 动作 全局位置信息表 局部位置信息表 打开窗口 :copen :lopen 关闭窗口 :cclose :lclose 下一个条目 :cnext :lnext 上一个条目 :cprevious :lprevious 第一个条目 :cfirst :lfirst 最后一个条目 :clast :llast :h :cc 以及底下的内容,来获取更多命令的帮助。 应用实例: 如果我们想用 grep 递归地在当前文件夹中寻找某个关键词,然后把输出结果放到全局位置信息表中,只需要这样: :let &grepprg = 'grep -Rn $* .' :grep! foo <grep output - hit enter> :copen 宏 你可以在 Vim 中录制一系列按键,并把他们存储到寄存器中。对于一些需要临时使用多次的一系列操作,把它们作为宏保存起来会显著地提升效率。对于一些复杂的操作,建议使用 Vim 脚本来实现。 首先,按下 q,然后按下你想要保存的寄存器,任何小写字母都可以。比如我们来把它保存到 q 这个寄存器中。按下 qq,你会发现命令行里已经显示了 “recording @q”。 如果你已经录制完成,那么只需要再按一次 q 就可以结束录制。 如果你想调用刚才录制的宏,只需要 [count]@q 如果你想调用上一次使用的宏,只需要 [count]@@ 实例 1: 一个插入字符串 “abc” 后换行的宏,重复调用十次: qq iabc<cr><esc> q 10@q oabc 然后 ESC 然后 10. 来实现)。 实例 2: 一个在每行前都加上行号的宏。从第一行开始,行号为 1,后面依次递增。我们可以通过 Ctrl + a 来实现递增的行号,在定义宏的时候,它会显示成 ^A。 qq 0yf jP0^A q 1000 @q qq 0yf jP0^A@q q @q :%s/^/\=line('.') . '. ')。 这里向大家展示了如何不用宏来达到相应的效果,但要注意,这些不用宏的实现方式只适用于这些简单的示例。对于一些比较复杂的自动化操作,你确实应该考虑使用宏。 请参阅以下文档获取更多帮助: :h recording :h 'lazyredraw' 颜色主题 :highlight Normal ctermbg=1 guibg=red :h :highlight 来获取更多帮助。 其实,颜色主题就是一系列的 :highlight 命令的集合。 事实上,大部分颜色主题都包含两套配置。一套适用于例如 xterm 和 iTerm 这样的终端环境(使用前缀 cterm),另一套适用于例如 gvim 和 MacVim 的图形界面环境(使用前缀 gui)。对于上面的例子,ctermbg 就是针对终端环境的,而 guibg 就是针对图形界面环境的。 如果你下载了一个颜色主题,并且在终端环境中打开了 Vim,然后发现显示的颜色与主题截图中差别很大,那很可能是配置文件只设置了图形界面环境的颜色。反之同理,如果你使用的是图形界面环境,发现显示颜色有问题,那就很可能是配置文件只设置了终端环境的颜色。 第二种情况(图形界面环境的显示问题)其实不难解决。如果你使用的是 Neovim 或者 Vim 7.4.1830 的后续版本,可以通过打开真彩色设置来解决显示问题。这就可以让终端环境的 Vim 使用 GUI 的颜色定义,但首先,你要确认一下你的终端环境和环境内的组件(比如 tmux)是否都支持真彩色。可以看一下这篇文档,描述的十分详细。 请参阅以下文档或链接来获取更多帮助: :h 'termguicolors' 主题列表 自定义主题中的颜色 折叠 折叠方式 概述 diff 在「比较窗口」中折叠未改变的文本 expr 使用 'foldexpr' 来创建新的折叠逻辑 indent 基于缩进折叠 manual 使用 zf、zF 或 :fold 来自定义折叠 marker 根据特定的文本标记折叠(通常用于代码注释) syntax 根据语法折叠,比如折叠 if 代码块 注意:折叠功能可能会显著地影响性能。如果你在使用折叠功能的时候出现了打字卡顿之类的问题,请考虑使用 FastFold 插件。这个插件可以让 Vim 按需更新折叠内容,而不是一直调用。 请参阅以下文档获取更多帮助: :h usr_28 :h folds 会话 :h :mkview),那么当前窗口、配置和按键映射都会被保存下来(请参阅 :h :loadview)。 「会话」就是存储所有窗口的相关设置,以及全局设置。简单来说,就是给当前的 Vim 运行实例拍个照,然后把相关信息存储到会话文件中。存储之后的改动就不会在会话文件中显示,你只需要在改动后更新一下会话文件就可以了。 你可以把当前工作的「项目」存储起来,然后可以在不同的「项目」之间切换。 现在就来试试吧。打开几个窗口和标签,然后执行 :mksession Foo.vim。如果你没有指定文件名,那就会默认保存为 Session.vim。这个文件会保存在当前的目录下,你可以通过 :pwd 来显示当前路径。重启 Vim 之后,你只需要执行 :source Foo.vim,就可以恢复刚才的会话了。所有的缓冲区、窗口布局、按键映射以及工作路径都会恢复到保存时的状态。 其实 Vim 的会话文件就只是 Vim 命令的集合。你可以通过命令 :vs Foo.vim 来看看会话文件中究竟有什么。 你可以决定 Vim 会话中究竟要保存哪些配置,只需要设置一下 'sessionoptions' 就可以了。 为了方便开发,Vim 把最后一次调用或写入的会话赋值给了一个内部变量 v:this_session。 请参阅以下文档来获取更多帮助: :h Session :h 'sessionoptions' :h v:this_session 局部化 全局 局部 作用域 帮助文档 :set :setlocal 缓冲区或窗口 :h local-options :map :map <buffer> 缓冲区 :h :map-local :autocmd :autocmd * <buffer> 缓冲区 :h autocmd-buflocal :cd :lcd 窗口 :h :lcd :<leader> :<localleader> 缓冲区 :h maploacalleader Vim scripting 的文档。 用法 获取离线帮助 :help :help。执行这个命令以后会在新窗口打开 $VIMRUNTIME/doc/helphelp.txt 文件并跳转到这个文件中 :help 标签的位置。 一些关于帮助主题的简单规则: 用单引号把文本包起来表示选项,如::h 'textwidth' 以小括号结尾表示 VimL 函数,如::h reverse() 以英文冒号开头表示命令,如::h :echo <c-d> (这是 ctrl+d)来列出所有包含你当前输入的内容的帮助主题。如::h tab<c-d> 会列出所有包含 tab 主题,从 softtabstop 到 setting-guitablabel (译者注:根据安装的插件不同列出的选项也会不同)。 你想查看所有的 VimL 方法吗?很简单,只要输入::h ()<c-d> 就可以了。你想查看所有与窗口相关的函数吗?输入 :h win*()<c-d>。 相信你很快就能掌握这些技巧,但是在刚开始的时候,你可能对于该通过什么进行查找一点线索都没有。这时你可以想象一些与要查找的内容相关的关键字,再让 :helpgrep 来帮忙。 :helpgrep backwards :cp / :cn 可以在匹配位置之间进行切换。或者用 :copen 命令来打开全局位置信息表,将光标定位到你想要的位置,再按 回车就可以跳转到该匹配项。详细说明请参考 :h quickfix。 获取离线帮助(补充) vim_dev,由 @chrisbra 编辑的,他是 Vim 开发人员中最活跃的一个。 经过一些微小的改动后,重新发布到了这里。 如果你知道你想要找什么,使用帮助系统的搜索会更简单一些,因为搜索出的主题都带有固定的格式。 而且帮助系统中的主题包含了你当前使用的 Vim 版本的所特有特性,而网上那些已经过时或者是早期发布的话题是不会包含这些的。 因此学习使用帮助系统以及它所用的语言是很有必要的。这里是一些例子(不一定全,我有可能忘了一些什么)。 (译者注:下面列表中提及的都是如何指定搜索主题以便快速准确的找到你想要的帮助) :h 'list' 来查看列表选项帮助。只有你明确的知道你要找这么一个选项的时候才可以这么做,不然的话你可以用 :h options.txt 来打开所有选项的帮助页面,再用正则表达式进行搜索,如:/width。某些选项有它们自己的命名空间,如::h cpo-a,:h cpo-A, :h cpo-b 等等。 :h gt 来转到“gt”命令的帮助页面。 :h /\+ 会带你到正则表达式中量词“+”的帮助页面。 :h i_CTRL-X 会带你到插入模式下的 CTRL-X 命令的用法帮助页面,这是一个自动完成类的组合键。需要注意的是某些键是有固定写法的,如 Control 键写成 CTRL。还有,查找普通模式下的组合键帮助时,可以省略开头的字母“n”,如::h CTRL-A。而 :h c_CTRL-A(译者注:原文为 :h c_CRTL-R,感觉改为 A 更符合上下文语境)会解释 CTRL-A 在命令模式下输入命令时的作用;:h v_CTRL-A 说的是在可见模式下把光标所在处的数字加 1;:h g_CTRL-A 则说的是 g 命令(你需要先按 “g” 的命令)。这里的 “g” 代表一个普通的命令,这个命令总是与其它的按键组合使用才生效,与 “z” 开始的命令相似。 :h quote: (译者注:原文为:h quote,感觉作者想以”:”来举例)来查看关于”:”寄存器的说明。 :h eval.txt 里。而某些方面的语言可以使用 :h expr-X 获取帮助,其中的 ‘X’ 是一个特定的字符,如::h expr-! 会跳转到描述 VimL 中’!’(非)的章节。另外一个重要提示,可以使用 :h function-list 来查看所有函数的简要描述,列表中包括函数名和一句话描述。 关于映射都可以在 :h map.txt 中找到。通过 :h mapmode-i 来查找 :imap 命令的相关信息;通过 :h map-topic 来查找专门针对映射的帮助(译者注:topic 为一个占位符,正如上面的字符 ‘X’ 一样,在实际使用中需要替换成相应的单词)(如::h :map-local 查询本地 buffer 的映射,:h map-bar 查询如何在映射中处理’ ’)。 :h command-bar 来查看自定义命令中’!’的作用。 :h CTRL-W_* 来查找相应的帮助(译者注:’*‘同样为占位符)(如::h CTRL-W_p 查看切换到之前访问的窗口命令的解释)。如果你想找窗口处理的命令,还可以通过访问 :h windows.txt 并逐行向下浏览,所有窗口管理的命令都在这里了。 :h :s 讲的是 “:s” 命令。 :helpgrep 在所有的帮助页面(通常还包括了已安装的插件的帮助页面)中进行搜索。参考 :h :helpgrep 来了解如何使用。当你搜索了一个话题之后,所有的匹配结果都被保存到了全局位置信息表(或局部位置信息表)当中,可以通过 :copen 或 :lopen 打开。在打开的窗口中可能通过 / 对搜索结果进行进一步的过滤。 :h helphelp 里介绍了如何使用帮助系统。 :h usr_toc.txt 打开目录(你可能已经猜到这个命令的用处了)。浏览用户手册能帮助你找出某些你想了解的话题,如你可以在第 24 章看到关于“复合字符”以及“输入特殊字符”的讲解(用 :h usr_24.txt 可以快速打开相关章节)。 hl- 开头。如::h hl-WarningMsg 说的是警告信息分组的高亮。 :syc- 开头,如::h :syn-conceal 讲的是 :syn 命令的对于隐藏字符是如何显示的。 :c 开头,而位置列表命令以 :l 开头。 :h BufWinLeave 讲的是 BufWinLeave 自动命令。还有,:h autocommand-events (译者注:原文是 :h autocommands-events,但是没有该帮助)讲的是所有可用的事件。 :h -f 会告诉你 Vim 中 “-f” 参数的作用。 :h +conceal 讲的是关于隐藏字符的支持。 :h E297 会带你到关于这一错误的详细解释。但是有时并没有转到错误描述,而是列出了经常导出这一错误的 Vim 命令,如 :h E128 (译者注:原文为:h hE128,但是并没有该帮助)会直接跳转到 :function 命令。 关于包含的语法文件的文档的帮助话题格式是 :h ft-*-syntax。如::h ft-c-syntax 说的就是 C 语言语法文件以及它所提供的选项。有的语法文件还会带有自动完成(:h ft-php-omni)或文件类型插件(:h ft-tex-plugin)相关的章节可以查看。 :h pattern.txt 里包含了 :h 03.9 和 :h usr_27 两个章节的链接。 获取在线帮助 Vim 使用邮件列表。 IRC 也是一个很不错的资源。 Freenode 上的 #vim 频道很庞大,并且里面有许多乐于助人的人。 如果你想给 Vim 提交 Bug 的话,可以使用 vim_dev 邮件列表。 执行自动命令 :doautocmd BufRead。 用户自定义事件 function! Chibby() " A lot of stuff is happening here. " And at last.. doautocmd User ChibbyExit endfunction autocmd User ChibbyExit call ChibbyCleanup() :autocmd 或 :doautocmd 时没有捕捉异常,那么会输出 “No matching autocommands” 信息。这也是为什么许多插件用 silent doautocmd ... 的原因。但是这也会有不足,那就是你不能再在 :autocmd 中使用 echo "foo" 了,取而代之的是你要使用 unsilent echo "foo" 来输出。 这就是为什么要在触发事件之前先判断事件是否存在的原因, if exists('#User#ChibbyExit') doautocmd User ChibbyExit endif :h User 事件嵌套 autocmd VimEnter * edit $MYVIMRC :edit 不会触发“BufRead”事件,所以并不会把文件类型设置成“vim”,进而 $VIMRUNTIME/syntax/vim.vim 永远不会被引入。详细信息请参考::au BufRead *.vim。要想完成上面所说的需求,使用下面这个命令: autocmd VimEnter * nested edit $MYVIMRC :h autocmd-nested 剪切板 'clipboard' 选项,则需要 +clipboard 以及可选的 +xterm_clipboard 两个特性支持。 帮助文档: :h 'clipboard' :h gui-clipboard :h gui-selections 持续粘贴(为什么我每次都要设置 ‘paste’ 模式 剪贴板的使用(Windows, OSX) 剪贴板,OSX 则带了一个粘贴板 在这两个系统中都可以用大家习惯用的 ctrl+c / cmd+c 复制选择的文本,然后在另外一个应用中用 ctrl+v / cmd+v 进行粘贴。 需要注意的是复制的文本已经被发送到了剪贴板,所以你在粘贴复制的内容之前关闭这个应用是没有任何问题的。 每次复制的时候,都会向剪贴板寄存器 * 中写入数据。 而在 Vim 中分别使用 "*y 和 "*p 来进行复制(yank) 和 粘贴(paste)。 如果你不想每次操作都要指定 * 寄存器,可以在你的 vimrc 中添加如下配置: set clipboard=unnamed " 寄存器中写入数据,而加上了上面的配置之后 * 寄存器也会被写入同样数据,因此简单的使用 y 和 p 就可以复制粘贴了。 我再说一遍:使用上面的选项意味着每一次的复制/粘贴,即使在同一个 Vim 窗口里,都会修改剪贴板的内容。你自己决定上面的选项是否适合。 如果你觉得输入 y 还是太麻烦的话,可以使用下面的设置把在可视模式下选择的内容发送到剪贴板: set clipboard=unnamed,autoselect set guioptions+=a :h clipboard-unnamed :h autoselect :h 'go_a' 剪贴板的使用(Linux, BSD, …) X 图形界面,事情会变得有一点不同。X 图形界面实现了 X 窗口系统协议, 这个协议在 1987 年发布的主版本 11,因此 X 也通常被称为 X11。 在 X10 版本中,剪贴缓冲区被用来实现像 clipboard 一样由 X 来复制文本,并且可以被所有的程序访问。现在这个机制在 X 中还存在,但是已经过时了,很多程序都不再使用这一机制。 近年来数据在程序之间是通过选择进行传递的。一共有三种选择,经常用到的有两种:PRIMARY 和 CLIPBOARD。 选择的工作工模大致是这样的: Program A:<ctrl+c> Program A:声称对 CLIPBOARD 的所有权 Program B:<ctrl+v> Program B:发现CLIPBOARD的所有权被Program A持有 Program B:从Program A请求数据 Program A:响应这个请求并发送数据给Program B Program B:从Program A接收数据并插入到窗口中 选择 何时使用 如何粘贴 如何在 Vim 中访问 PRIMARY 选择文本 鼠标中键, shift+insert * 寄存器 CLIPBOARD 选择文本并按 ctrl+c ctrl+v +寄存器 注意:X 服务器并不会保存选择(不仅仅是 CLIPBOARD 选择)!因此在关闭了相应的程序后,你用 ctrl+c 复制的内容将丢失。 使用 "*p 来贴粘 PRIMARY 选择中的内容,或者使用 "+y1G 来将整个文件的内容复制到 CLIPBOARD 选择。 如果你需要经常访问这两个寄存器,可以考虑使用如下配置: set clipboard^=unnamed " * 寄存器 " 或者 set clipboard^=unnamedplus " + 寄存器 ^= 用来将设置的值加到默认值之前,详见::h :set^=) 这会使得所有复制/删除/放入操作使用 * 或 + 寄存器代替默认的未命令寄存器 "。之后你就可以直接使用 y 或 p 访问你的 X 选择了。 帮助文档: :h clipboard-unnamed :h clipboard-unnamedplus 打开文件时恢复光标位置 autocmd BufReadPost * \ if line("'\"") > 1 && line("'\"") <= line("$") | \ exe "normal! g`\"" | \ endif g`" (转到你离开时的光标位置但是不更改跳转列表)。 这需要使用 viminfo 文件::h viminfo-。 临时文件 备份文件 :set writebackup)。如果你想一直保留这个备份文件的话,可以使用 :set backup。而如果你想禁用备份功能的话,可以使用 :set nobackup nowritebackup。 咱们来看一下上次我在 vimrc 中改了什么: $ diff ~/.vim/vimrc ~/.vim/files/backup/vimrc-vimbackup 390d389 < command! -bar -nargs=* -complete=help H helpgrep <args> :h backup 交换文件 ~/来自外太空的邪恶入侵者.txt 是在.. 好吧,你从来没有保存过。 但是并非没有希望了!在编辑某个文件的时候, Vim 会创建一个交换文件,里面保存的是对当前文件所有未保存的修改。自己试一下,打开任意的文件,并使用 :swapname 获得当前的交换文件的保存路径。你也可以将 :set noswapfile 加入到 vimrc 中来禁用交换文件。 默认情况下,交换文件会自动保存在被编辑文件所在的目录下,文件名以 .file.swp 后缀结尾,每当你修改了超过 200 个字符或是在之前 4 秒内没有任何动作时更新它的内容,在你不再编辑这个文件的时候会被删除。你可以自己修改这些数字,详见::h 'updatecount' 和 :h 'updatetime'。 而在断电时,交换文件并不会被删除。当你再次打开 vim ~/来自外太空的邪恶入侵者.txt 时, Vim 会提示你恢复这个文件。 帮助文档::h swap-file 和 :h usr_11 撤销文件 内容变更历史记录是保存在内存中的,并且会在 Vim 退出时清空。如果你想让它持久化到磁盘中,可以设置 :set undofile。这会把文件 ~/foo.c 的撤销文件保存在 ~/foo.c.un~。 帮助文档::h 'undofile' 和 :h undo-persistence viminfo 文件 ~/.viminfo。 帮助文档::h viminfo 和 :h 'viminfo' 临时文件管理设置示例 ~/.vim/files)的话,可以使用下面的配置: " 如果文件夹不存在,则新建文件夹 if !isdirectory($HOME.'/.vim/files') && exists('*mkdir') call mkdir($HOME.'/.vim/files') endif " 备份文件 set backup set backupdir =$HOME/.vim/files/backup/ set backupext =-vimbackup set backupskip = " 交换文件 set directory =$HOME/.vim/files/swap// set updatecount =100 " 撤销文件 set undofile set undodir =$HOME/.vim/files/undo/ " viminfo 文件 set viminfo ='100,n$HOME/.vim/files/info/viminfo 编辑远程文件 :e scp://bram@awesome.site.com/.vimrc ~/.ssh/config,SSH 会自动读取这里的配置: Host awesome HostName awesome.site.com Port 1234 User bram ~/.ssh/config 中有以上的内容,那么下面的命令就可以正常执行了: :e scp://awesome/.vimrc ~/.netrc, 详见::h netrc-netrc。 确保你已经看过了 :h netrw-ssh-hack 和 :h g:netrw_ssh_cmd。 另外一种编辑远程文件的方法是使用 sshfs,它会用 FUSE 来挂载远程的文件系统到你本地的系统当中。 插件管理 Pathogen是第一个比较流行的插件管理工具。实际上它只是修改了 runtimepath (:h 'rtp') 来引入所有放到该目录下的文件。你需要自己克隆插件的代码仓库到那个目录。 真正的插件管理工具会在 Vim 中提供帮助你安装或更新插件的命令。以下是一些常用的插件管理工具: dein plug vim-addon-manager vundle 多行编辑 示例。 用 <c-v> 切换到可视块模式。然后向下选中几行,按 I 或 A (译者注:大写字母,即 shift+i 或 shift+a)然后开始输入你想要输入的文本。 在刚开始的时候可能会有些迷惑,因为文本只出现在了当前编辑的行,只有在当前的插入动作结束后,之前选中的其它行才会出现插入的文本。 举一个简单的例子:<c-v>3jItext<esc>。 如果你要编辑的行长度不同,但是你想在他们后面追加相同的内容的话,可以试一下这个:<c-v>3j$Atext<esc>。 有时你可能需要把光标放到当前行末尾之后,默认情况下你是不可能做到的,但是可能通过设置 virtualedit 选项达到目的: set virtualedit=all $10l 或 90| 都会生效,即使超过了行尾的长度。 详见 :h blockwise-examples。在开始的时候可能会觉得有些复杂,但是它很快就会成为你的第二天性的。 如果你想探索更有趣的事情,可以看看多光标 使用外部程序和过滤器 :helpgrep startjob。) 使用 :! 启动一个新任务。如果你想列出当前工作目录下的所有文件,可以使用 :!ls。 用 | 来将结果通过管道重定向,如::!ls -l | sort | tail -n5。 没有使用范围时(译者注:范围就是 : 和 ! 之间的内容,. 表示当前行,+4 表示向下偏移 4 行,$ 表示最末行等,多行时用 , 将它们分开,如 .,$ 表示从当前行到末行),:! 会显示在一个可滚动的窗口中(译者注:在 GVim 和在终端里运行的结果稍有不同)。相反的,如果指定了范围,这些行会被过滤。这意味着它们会通过管道被重定向到过滤程序的 stdin,在处理后再通过过滤程序的 stdout 输出,用输出结果替换范围内的文本。例如:为接下来的 5 行文本添加行号,可以使用: :.,+4!nl -ba -w1 -s' ' : (译者注:选中后再按 ! 更方便)。还可以使用 ! 来取用一个 motion 的范围,如 !ipsort (译者注:原文为 !ip!sort ,但经过实验发现该命令执行报错,可能是因为 Vim 版本的原因造成的,新版本使用 ip 选择当前段落后自动在命令后添加了 ! ,按照作者的写法来看,可能之前的版本没有自动添加 ! )可以将当前段落的所有行按字母表顺序进行排序。 一个使用过滤器比较好的案例是Go 语言。它的缩进语法非常个性,甚至还专门提供了一个名为 gofmt 的过滤器来对 Go 语言的源文件进行正确的缩进。Go 语言的插件通常会提供一个名为 :Fmt 的函数,这个函数就是执行了 :%!gofmt 来对整个文件进行缩进。 人们常用 :r !prog 将 prog 程序的插入放到当前行的下面,这对于脚本来说是很不错的选择,但是在使用的过程中我发现 !!ls 更加方便,它会用输出结果替换当前行的内容。(译者注:前面命令中的 prog 只是个占位符,在实际使用中需要替换成其它的程序,如 :r !ls,这就与后面的 !!ls 相对应了,两者唯一的不同是第一个命令不会覆盖当前行内容,但是第二个命令会) 帮助文档: :h filter :h :read! Cscope Cscope 的功能比 ctags 要完善,但是只支持 C(通过设置 cscope.files 后同样支持 C++以及 Java)。 鉴于 Tag 文件只是知道某个符号是在哪里定义的,cscope 的数据库里的数据信息就多的多了: 符号是在哪里定义的? 符号是在哪里被使用的? 这个全局符号定义了什么? 这个变量是在哪里被赋值的? 这个函数在源文件的哪个位置? 哪些函数调用了这个函数? 这个函数调用了哪些函数? “out of space”消息是从哪来的? 在目录结构中当前的源文件在哪个位置? 哪些文件引用了这个头文件? 1. 构建数据库 $ cscope -bqR cscope{,.in,.po}.out 。把它们想象成你的数据库。 不幸的时 cscope 默认只分析 *.[c|h|y|l] 文件。如果你想在 Java 项目中使用 cscope ,需要这样做: $ find . -name "*.java" > cscope.files $ cscope -bq 2. 添加数据库 :cs add cscope.out :cs show 3. 查询数据库 :cs find <kind> <query> :cs find d foo 会列出 foo(...) 调用的所有函数。 Kind 说明 s symbol:查找使用该符号的引用 g global:查找该全局符号的定义 c calls:查找调用当前方法的位置 t text:查找出现该文本的位置 e egrep:使用 egrep 搜索当前单词 f file:打开文件名 i includes:查询引入了当前文件的文件 d depends:查找当前方法调用的方法 nnoremap <buffer> <leader>cs :cscope find s <c-r>=expand('<cword>')<cr><cr> nnoremap <buffer> <leader>cg :cscope find g <c-r>=expand('<cword>')<cr><cr> nnoremap <buffer> <leader>cc :cscope find c <c-r>=expand('<cword>')<cr><cr> nnoremap <buffer> <leader>ct :cscope find t <c-r>=expand('<cword>')<cr><cr> nnoremap <buffer> <leader>ce :cscope find e <c-r>=expand('<cword>')<cr><cr> nnoremap <buffer> <leader>cf :cscope find f <c-r>=expand('<cfile>')<cr><cr> nnoremap <buffer> <leader>ci :cscope find i ^<c-r>=expand('<cfile>')<cr>$<cr> nnoremap <buffer> <leader>cd :cscope find d <c-r>=expand('<cword>')<cr><cr> :tag (或 <c-]>)跳转到标签定义的文件,而 :cstag 可以达到同样的目的,同时还会打开 cscope 的数据库连接。'cscopetag' 选项使得 :tag 命令自动的像 :cstag 一样工作。这在你已经使用了基于标签的映射时会非常方便。 帮助文档::h cscope MatchIt { 或 #endif , 就可以使用 % 跳转到与之匹配的 } 或 #ifdef。 Vim 自带了一个名为 matchit.vim 的插件,但是默认没有启用。启用后可以用 % 在 HTML 相匹配的标签或 VimL 的 if/else/endif 块之间进行跳转,它还带来了一些新的命令。 在 Vim 8 中安装 " vimrc packadd! matchit 在 Vim 7 或者更早的版本中安装 "vimrc runtime macros/matchit.vim :!mkdir -p ~/.vim/doc :!cp $VIMRUNTIME/macros/matchit.vim ~/.vim/doc :helptags ~/.vim/doc 简短的介绍 :h matchit-intro 来获得支持的命令以及 :h matchit-languages 来获得支持的语言。 你可以很方便的定义自己的匹配对,如: autocmd FileType python let b:match_words = '\<if\>:\<elif\>:\<else\>' % (向前)或 g% (向后)在这三个片断之间跳转了。 帮助文档: :h matchit-install :h matchit :h b:match_words 技巧 跳至选择的区域另一端 v 或者 V 选择某段文字后,可以用 o 或者 O 按键跳至选择区域的开头或者结尾。 :h v_o :h v_O 聪明地使用 n 和 N n 与 N 的实际跳转方向取决于使用 / 还是 ? 来执行搜索,其中 / 是向后搜索,? 是向前搜索。一开始我(原作者)觉得这里很难理解。 如果你希望 n 始终为向后搜索,N 始终为向前搜索,那么只需要这样设置: nnoremap <expr> n 'Nn'[v:searchforward] nnoremap <expr> N 'nN'[v:searchforward] 聪明地使用命令行历史 Ctrl + p 和 Ctrl + n 来跳转到上一个/下一个条目。其实这个操作也可以用在命令行中,快速调出之前执行过的命令。 不仅如此,你会发现 上 和 下 其实更智能。如果命令行中已经存在了一些文字,我们可以通过按方向键来匹配已经存在的内容。比如,命令行中现在是 :echo,这时候我们按 上,就会帮我们补全成 :echo "Vim rocks!"(前提是,之前输入过这段命令)。 当然,Vim 用户都不愿意去按方向键,事实上我们也不需要去按,只需要设置这样的映射: cnoremap <c-n> <down> cnoremap <c-p> <up> 智能 Ctrl-l Ctrl + l 的默认功能是清空并「重新绘制」当前的屏幕,就和 :redraw! 的功能一样。下面的这个映射就是执行重新绘制,并且取消通过 / 和 ? 匹配字符的高亮,而且还可以修复代码高亮问题(有时候,由于多个代码高亮的脚本重叠,或者规则过于复杂,Vim 的代码高亮显示会出现问题)。不仅如此,还可以刷新「比较模式」(请参阅 :help diff-mode)的代码高亮: nnoremap <leader>l :nohlsearch<cr>:diffupdate<cr>:syntax sync fromstart<cr><c-l> 禁用错误报警声音和图标 set noerrorbells set novisualbell set t_vb= Vim Wiki: Disable beeping。 快速移动当前行 nnoremap [e :<c-u>execute 'move -1-'. v:count1<cr> nnoremap ]e :<c-u>execute 'move +'. v:count1<cr> 2 ] e 就可以把当前行向下移动两行。 快速添加空行 nnoremap [<space> :<c-u>put! =repeat(nr2char(10), v:count1)<cr>'[ nnoremap ]<space> :<c-u>put =repeat(nr2char(10), v:count1)<cr> 5 \[ 空格 在当前行上方插入 5 个空行。 运行时检测 :profile 命令后面跟着子命令来确定要查看什么。 如果你想查看所有的: :profile start /tmp/profile.log :profile file * :profile func * <do something in Vim> <quit Vim> :profile dump 命令) 看一下 /tmp/profile.log 文件,检查时运行的所有代码都会被显示出来,包括每一行代码运行的频率和时间。 大多数代码都是用户不熟悉的插件代码,如果你是在解决一个确切的问题, 直接跳到这个日志文件的末尾,那里有 FUNCTIONS SORTED ON TOTAL TIME 和 FUNCTIONS SORTED ON SELF TIME 两个部分,如果某个 function 运行时间过长一眼就可以看到。 查看启动时间 vim --startuptime /tmp/startup.log +q && vim /tmp/startup.log 绝对运行时间,如果在前后两行之间时间差有很大的跳跃,那么是第二个文件太大或者含有需要检查的错误的 VimL 代码。 NUL 符用新行表示 \0),在内存中被以新行(\n)保存,在缓存空间中显示为 ^@。 更多信息请参看 man 7 ascii 和 :h NL-used-for-Nul 。 快速编辑自定义宏 *)。当你设置完成后,只需要按下 回车 即可让它生效。 在录制宏的时候,我经常用这个来更改拼写错误。 nnoremap <leader>m :<c-u><c-r><c-r>='let @'. v:register .' = '. string(getreg(v:register))<cr><c-f><left> leader m 或者 " leader m 就可以调用了。 请注意,这里之所以要写成 <c-r><c-r> 是为了确保 <c-r> 执行了。请参阅 :h c_^R^R 快速跳转到源(头)文件 :h marks),然后你就可以通过连续按下 ' C 或者 ' H 快速跳转回去(请参阅 :h 'A)。 autocmd BufLeave *.{c,cpp} mark C autocmd BufLeave *.h mark H 注意:由于这个标记是设置在 viminfo 文件中,因此请先确认 :set viminfo? 中包含了 :h viminfo-'。 在 GUI 中快速改变字体大小 command! Bigger :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)+1', '') command! Smaller :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)-1', '') 根据模式改变光标类型 if empty($TMUX) let &t_SI = "\<Esc>]50;CursorShape=1\x7" let &t_EI = "\<Esc>]50;CursorShape=0\x7" let &t_SR = "\<Esc>]50;CursorShape=2\x7" else let &t_SI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=1\x7\<Esc>\\" let &t_EI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=0\x7\<Esc>\\" let &t_SR = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=2\x7\<Esc>\\" endif escape sequence。Vim 与终端之间的中间层,比如 tmux 会处理并执行上面的代码。 但上面这个还是有一个缺点的。终端环境的内部原理不尽相同,对于序列的处理方式也稍有不同。因此,上面的代码可能无法在你的环境中运行。甚至,你的运行环境也有可能不支持其他光标形状,请参阅你的 Vim 运行环境的文档。 好消息是,上面这个代码,可以在 iTerm2 中完美运行。 防止水平滑动的时候失去选择 < 或 > 来调整他们的缩进。但在调整之后就不会保持选中状态了。 你可以连续按下 g v 来重新选中他们,请参考 :h gv。因此,你可以这样来配置映射: xnoremap < <gv xnoremap > >gv >>>>> 就不会再出现上面提到的问题了。 选择当前行至结尾,排除换行符 v$ 选择当前行至结尾,但此时会把最后一个换行符也选中,通常需要按额外的 h 来取消最后选中最后一个换行符号。 Vim 提供了一个 g_ 快捷键,可以移动光标至最后一个非空字符。因此,为达到次效果,可以使用 vg_。当然,如果觉得按三个键比较麻烦, 可以添加一个映射: nnoremap L g_ vL 达到一样的效果了。 重新载入保存文件 自动命令,你可以在保存文件的同时触发一些其他功能。比如,如果这个文件是一个配置文件,那么就重新载入;或者你还可以对这个文件进行代码风格检查。 autocmd BufWritePost $MYVIMRC source $MYVIMRC autocmd BufWritePost ~/.Xdefaults call system('xrdb ~/.Xdefaults') 更加智能的当前行高亮 :h cursorline)这个功能,但我只想让这个效果出现在当前窗口,而且在插入模式中关闭这个效果: autocmd InsertLeave,WinEnter * set cursorline autocmd InsertEnter,WinLeave * set nocursorline 更快的关键字补全 <c-n> 或 <c-p>)功能的工作方式是,无论 'complete' 设置中有什么,它都会尝试着去补全。这样,一些我们用不到的标签也会出现在补全列表中。而且,它会扫描很多文件,有时候运行起来非常慢。如果你不需要这些,那么完全可以像这样把它们禁用掉: set complete-=i " disable scanning included files set complete-=t " disable searching tags 改变颜色主题的默认外观 autocmd ColorScheme * highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE :echo color_name 来查看当前可用的所有颜色主题): autocmd ColorScheme lucius highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE 命令 :h :<command name> 来了解更多关于它们的信息,如::h :global。 :global 和 :vglobal - 在所有匹配行执行命令 :global /regexp/ print 会在所有包含 “regexp” 的行上执行 print 命令(译者注:regexp 有正则表达式的意思,该命令同样支持正则表达式,在所有符合正则表达式的行上执行指定的命令)。 趣闻:你们可能都知道老牌的 grep 命令,一个由 Ken Thompson 编写的过滤程序。它是干什么用的呢?它会输出所有匹配指定正则表达式的行!现在猜一下 :global /regexp/ print 的简写形式是什么?没错!就是 :g/re/p 。 Ken Thompsom 在编写 grep 程序的时候是受了 vi :global 的启发。(译者注: https://robots.thoughtbot.com/how-grep-got-its-name) 既然它的名字是 :global,理应仅作用在所有行上,但是它也是可以带范围限制的。假设你想使用 :delete 命令删除从当前行到下一个空行(由正则表达式 ^$ 匹配)范围内所有包含 “foo” 的行: :,/^$/g/foo/d 不 匹配的行上执行命令的话,可以使用 :global! 或是它的别名 :vglobal ( V 代表的是 inVerse )。 :normal 和 :execute - 脚本梦之队 :normal 可以在命令行里进行普通模式的映射。如::normal! 4j 会令光标下移 4 行(由于加了”!”,所以不会使用自定义的映射 “j”)。 需要注意的是 :normal 同样可以使用范围数(译者注:参考 :h range 和 :h :normal-range 了解更多),故 :%norm! Iabc 会在所有行前加上 “abc”。 借助于 :execute 可以将命令和表达式混合在一起使用。假设你正在编辑一个 C 语言的文件,想切换到它的头文件: :execute 'edit' fnamemodify(expand('%'), ':r') . '.h' .h 的文件。上面的命令中 expand 获得当前文件的名称,fnamemodify 获取不带扩展名的文件名,再连上 ‘.h’ 就是头文件的文件名了,最后在使用 edit 命令打开这个头文件。) 这两个命令经常一起使用。假设你想让光标下移 n 行: :let n = 4 :execute 'normal!' n . 'j' 重定向消息 :redir 用来重定向这些消息。它可以将消息输出到文件、寄存器或是某个变量中。 " 将消息重定向到变量 `neatvar` 中 :redir => neatvar " 打印所有寄存器的内容 :reg " 结束重定向 :redir END " 输出变量 :echo neatvar " 恶搞一下,我们把它输出到当前缓冲区 :put =neatvar :put =execute('reg') :put =nicevar 但是实际会报变量未定义的错误) (实测 neovim/vim8 下没问题) 帮助文档::h :redir 调试 常规建议 vim -u NONE -N nocompatible 模式下(使用 vim 默认设置而不是 vi 的)。(搜索 :h --noplugin 命令了解更多启动加载方式) 如果仍旧能够出现该错误,那么这极有可能是 vim 本身的 bug,请给 vim_dev 发送邮件反馈错误,多数情况下问题不会立刻解决,你还需要进一步研究 许多插件经常会提供新的(默认的/自动的)操作。如果在保存的时候发生了,那么请用 :verb au BufWritePost 命令检查潜在的问题 如果你在使用一个插件管理工具,将插件行注释调,再进行调试。 问题还没有解决?如果不是插件的问题,那么肯定是你的自定义的设置的问题,可能是你的 options 或 autocmd 等等。 到了一行行代码检查的时候了,不断地排除缩小检查范围知道你找出错误,根据二分法的原理你不会花费太多时间的。 在实践过程中,可能就是这样,把 :finish 放在你的 vimrc 文件中间,Vim 会跳过它之后的设置。如果问题还在,那么问题就出在:finish之前的设置中,再把:finish放到前一部分设置的中间位置。否则问题就出现在它后面的半部分设置,那么就把:finish放到后半部分的中间位置。不断的重复即可找到。 调整日志等级 :h 'verbose'命令查看。 :e /tmp/foo :set verbose=2 :w :set verbose=0 :verbose ,放在其他命令之前,通过计数来指明等级,默认是 1. :verb set verbose " verbose=1 :10verb set verbose " verbose=10 :verb set ai? " Last set from ~/.vim/vimrc :set verbosefile=/tmp/foo | 15verbose echo "foo" | vsplit /tmp/foo -V 选项,它默认设置调试等级为 10。 例如:vim -V5 查看启动日志 查看运行时日志 Vim 脚本调试 :debug命令你很快就会感到熟悉。 只需要在任何其他命令之前加上:debug就会让你进入调试模式。也就是,被调试的 Vim 脚本会在第一行停止运行,同时该行会被显示出来。 想了解可用的 6 个调试命令,可以查阅:h >cont和阅读下面内容。需要指出的是,类似 gdb 和其他相似调试器,调试命令可以使用它们的简短形式:c、 q、n、s、 i和 f。 除了上面的之外,你还可以自由地使用任何 Vim 的命令。比如,:echo myvar,该命令会在当前的脚本代码位置和上下文上被执行。 只需要简单使用:debug 1,你就获得了REPL调试特性。 当然,调试模式下是可以定义断点的,不然的话每一行都去单步调试就会十分痛苦。(断点之所以被叫做断点,是因为运行到它们的时候,运行就会停止下来。因此,你可以利用断点跳过自己不感兴趣的代码区域)。请查阅:h :breakadd、 :h :breakdel和 :h :breaklist获取更多细节。 假设你需要知道你每次在保存一个文件的时候有哪些代码在运行: :au BufWritePost " signify BufWritePost " * call sy#start() :breakadd func *start :w " Breakpoint in "sy#start" line 1 " Entering Debug mode. Type "cont" to continue. " function sy#start " line 1: if g:signify_locked >s " function sy#start " line 3: endif > " function sy#start " line 5: let sy_path = resolve(expand('%:p')) >q :breakdel * <cr>命令会重复之前的调试命令,也就是在该例子中的s命令。 :debug命令可以和verbose选项一起使用。 语法文件调试 +profile feature特性,就可以给用户提供一个超级好用的:syntime命令。 :syntime on " 多次敲击<c-l>来重绘窗口,这样的话就会使得相应的语法规则被重新应用一次 :syntime off :syntime report :h :syntime。 杂项 附加资源 资源名称 简介 七个高效的文本编辑习惯 作者:Bram Moolenaar(即 Vim 的作者) 七个高效的文本编辑习惯 2.0(PDF 版) 同上 IBM DeveloperWorks: 使用脚本编写 Vim 编辑器 Vim 脚本编写五辑 《漫漫 Vim 路》 使用魔抓定制 Vim 插件 《 Vim 实践 (第 2 版)》 轻取 Vim 最佳书籍 Vimcasts.org Vim 录屏演示 为什么是个脚本都用 vi? 常见误区释疑 你不爱 vi,所以你不懂 Vim 简明,扼要,准确的干货 Vim 配置集合 SpaceVim 还是值得尝试的,可以节省很多自行配置的时间。 当然,网上还有很多其他很流行的配置,比如: k-vim amix’s vimrc janus 常见问题 编辑小文件时很慢 正则表达式 。尤其是 Ruby 的语法文件,以前会造成性能下降。(见调试语法文件) 屏幕重绘 。有一些功能会强制重绘所有行。 典型肇事者 原因 解决方案 :set cursorline 会导致所有行重绘 :set nocursorline :set cursorcolumn 会导致所有行重绘 :set nocursorcolumn :set relativenumber 会导致所有行重绘 :set norelativenumber :set foldmethod=syntax 如果语法文件已经很慢了,这只会变得更慢 :set foldmethod=manual,:set foldmethod=marker 或者使用快速折叠插件 :set synmaxcol=3000 由于内部表示法,Vim 处理比较长的行时会有问题。让它高亮到 3000 列…… :set synmaxcol=200 matchparen.vim Vim 默认加载的插件,用正则表达式查找配对的括号 禁用插件::h matchparen 注意:只有在你真正遇到性能问题的时候才需要做上面的调整。在大多数情况下使用上面提到的选项是完全没有问题的。 编辑大文件的时候很慢 vim_dev 中讨论)。 如果只是想查看的话,tail hugefile | vim - 是一个不错的选择。 如果你能接受没有语法高亮,并且禁用所有插件和设置的话,使用: $ vim -u NONE -N $ vim -n -u NONE -i NONE -N 持续粘贴(为什么我每次都要设置 ‘paste’ 模式) cmd+v、shirt-insert、middle-click 等进行粘贴的时候才会发生。 因为那样的话你只是向终端模拟器扔了一大堆的文本。 Vim 并不知道你刚刚是粘贴的文本,它以为你在飞速的输入。 于是它想缩进这些行但是失败了。 这明显不是个问题,如果你用 Vim 的寄存器粘贴,如:"+p ,这时 Vim 就知道了你在粘贴,就不会导致格式错乱了。 使用 :set paste 就可以解决这个问题正常进行粘贴。见 :h 'paste' 和 :h 'pastetoggle' 获取更多信息。 如果你受够了每次都要设置 'paste' 的话,看看这个能帮你自动设置的插件:bracketed-paste。 点此查看该作者对于这个插件的更多描述。 Neovim 尝试把这些变得更顺畅,如果终端支持的话,它会自动开启持续粘贴模式,无须再手动进行切换。 在终端中按 ESC 后有延时 终端模拟器 ,如 xterm、gnome-terminal、iTerm2 等等(与实际的终端不同)。 终端模拟器与他们的祖辈一样,使用 转义序列 (也叫 控制序列 )来控制光标移动、改变文本颜色等。转义序列就是以转义字符开头的 ASCII 字符串(用脱字符表示法表示成 ^[ )。当遇到这样的字符串后,终端模拟器会从终端信息数据库中查找对应的动作。 为了使用问题更加清晰,我会先来解释一下什么是映射超时。在映射存在歧义的时候就会产生映射超时: :nnoremap ,a :echo 'foo'<cr> :nnoremap ,ab :echo 'bar'<cr> ,a 之后,Vim 会延时 1 秒,因为它要确认用户是否还要输入那个 b。 转义序列会产生同样的问题: <esc> 作为返回普通模式或取消某个动作的按键而被大量使用 光标键使用转义序列进行的编码 Vim 期望 Alt (也叫作 Mate Key )会发送一个正确的 8-bit 编码的高位,但是许多终端模拟器并不支持这个(也可能默认没有启用),而只是发送一个转义序列作为代替。 vim -u NONE -N 然后输入 i<c-v><left> ,你会看到一个以 ^[ 开头的字符串,表明这是一个转义序列,^[ 就是转义字符。 简而言之,Vim 在区分录入的 <esc> 和转义序列的时候需要一定的时间。 默认情况下,Vim 用 :set timeout timeoutlen=1000,就是说它会用 1 秒的时间来区分有歧义的映射 以及 按键编码。这对于映射来说是一个比较合理的值,但是你可以自行定义按键延时的长短,这是解决该问题最根本的办法: set timeout " for mappings set timeoutlen=1000 " default value set ttimeout " for key codes set ttimeoutlen=10 " unnoticeable small value :h ttimeout 里你可以找到一个关于这些选项之间关系的小表格。 而如果你在 tmux 中使用 Vim 的话,别忘了把下面的配置加入到你的 ~/.tmux.conf文件中: set -sg escape-time 0 无法重复函数中执行的搜索 在命令中的搜索(/、:substitute 等)内容会改变“上次使用的搜索内容”。(它保存在/寄存器中,用 :echo @/ 可以输出它里面的内容) 简单的文本变化可以通过 . 重做。(它保存在 . 寄存器,用 :echo @. 可以输出它的内容) :h function-search-undo。

2017/3/27
articleCard.readMore

SpaceVim 第一个版本

Like spacemacs, but for vim. SpaceVim 放在 github 上, 3 天获取 700+ 星,该项目不仅适合 vim 以及 neovim 老用户, 对于新用户来说,也是非常适合的。 @Shougo 大神 以及 @mhinz 大神对此项目也很支持, 另外,该项目在 Hacker News 首页也出现过。拥有 200+ points 。 在 gittter #neovim 内, 也有用户支持该项目。 目前该项目 80% 收藏者来自国外,国内确实没有找到很好的 post 平台, 现尝试在 V2EX 上发布下, 希望有更多的 vim 用户支持,让 vim 中文社区更加活跃。 该项目地址 github 地址是 : SpaceVim/SpaceVim 官网 : http://spacevim.org SpaceVim 是高度可定制的模块化 Vim 配置集合,适合开发各种语言, 尤其是 Java c c++ python php 等常用语言,并且拥有非常优秀的 UI 界面, 用户可以根据自己需求载入需要的模块。如果有兴趣,欢迎进群大家一起讨论。 本群为非常专业的技术讨论群,请勿水!

2017/1/18
articleCard.readMore

在 Vim 中使用聊天工具

安装 微信及QQ聊天 依赖 启动 安装 call dein#add('wsdjeg/vim-chat') call chat#chatting#OpenMsgWin(),打开聊天窗口。 微信及QQ聊天 依赖 mojo-webqq 和 mojo-weixin:用于将QQ及微信协议转换成irc协议 IRC 依赖模块: cpanm -v Mojo::IRC::Server::Chinese, 详见: irc.md irssi: irc 聊天客户端 feh: 图片查看器,用于打开二维码 neovim: vim8 的异步存在问题,无法实现相应功能,故只能在neovim下使用 Linux: 目前仅在Linux下测试成功, Windows 下请直接使用QQ客户端 启动 call chat#qq#start(), 然后会自动弹出一个二维码,手机扫描下就可以登录了。neovim 默认使用 Alt + x 打开/关闭聊天窗口。

2016/12/30
articleCard.readMore

为什么要写博客

为什么要写博客 为什么使用独立站点 为什么使用邮件评论 关于社交软件 为什么要写博客 我想记录一些值得记忆的人和事,也许多年以后回来看看,会很欣慰 记录日常遇到的一些问题及解决的方法 记录折腾一些有意思的小玩意的过程 为什么使用独立站点 页面样式定制不自由,个人网站每一个字符、每一张图片、每一个链接都应该是完全可控的。 跑路风险,备份麻烦。这些平台都有停止服务的可能,那些写在其上的文字很难备份转移。 广告满天飞,尤其是 CSDN。 登录才能阅读,像知乎、CSDN 这类网站,非登录用户都无法阅读全文。 完全可控,自己个人站点中每一个字符、每一张图片、每一个链接都是可控的 使用 Git 仓库,编辑历史可以追溯,备份也方便 为什么使用邮件评论 关于社交软件 之前用过的社交平台软件,各种各样问题都有。以至于有很长一段时间,很想彻底摆脱这些社交软件。 Reddit 账号已删除,留张图片纪念一下:

2015/6/24
articleCard.readMore

可爱的小天使

这是一个值得记忆的日子,今天我们家迎来了一位新的小生命。因为自己的工作原因,在老婆最辛苦的这段日子里, 没能够时常陪伴身边。 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; }

2015/5/25
articleCard.readMore

不忘初心、方得始终

《华严经》里说:“不忘初心,方得始终。” 记得很长时段时间,签名是:感叹那两个少年,一个温柔了岁月,一个经验了流年。 已经无从考证这句话到底来自哪里了。当时第一次看到这段话就觉得深受感触。 每个人,在最初的时候可能会有各种各样的梦想。但是随着时间的推移,生活的磨砺。 会让我们越来越执着于眼前,淡忘了曾经的梦想。梦想是每个人出发的起点,不忘初心, 才会让自己明确从哪里来,到哪里去。 初心,给人强大的勇气,把一切困难都踩在脚下。人生最大的困厄,就是被人驱使。 被父母催促着去上学,被领导吩咐着去做事,被生活逼迫着去赚钱。 看似是出于无奈,实则缺少了情愿,总像背负着巨石,压得喘不过气来。 转换一下思维,调整一下状态,从自己的初心开始,想自己想做的事, 想着自己该肩负的责任,不后悔,肯坚持,虽难免生长的阵痛,却会得到成长的快乐。 初心,给了人积极进取的人生状态,随时能回到最初的原点,重新开始。 人生的经历,在我们大脑里塞满了各种各样的东西。 有时我们满足于鲜花和掌声,喜欢在美好的过去里追忆; 有时我们则沉浸于挫折的苦痛,在灰暗的心空里踽踽独行。 其实我们最需要的,就是不忘初心,倾空自己,回到充满渴望的婴儿般的状态, 永远在拼命地汲取,永远在奋力前行的路上。 在这个时代,初心常常被我们遗忘,正如纪伯伦所说:“我们已经走的太远,以至于忘记了为什么出发。” 人生只有一次,生命无法重来,要记得自己的初心。 经常回头望一下自己的来路,回忆起当初为什么起程; 经常让自己回到起点,给自己鼓足从头开始的勇气; 经常纯净自己的内心,给自己一双澄澈的眼睛, 不忘初心,才会找对人生的方向,才会坚定我们的追求, 抵达自己的初衷,也更容易抵达人生的目的地。

2014/7/12
articleCard.readMore

强大的vim命令 :g 和 :s

命令形式 global与substitute global标志的[range]用法 :global 和 :substitute 命令是 Vim 最强大的命令之一, 将其摸透用熟可以事半功倍,在这里我总结了一些网上的网上的经典问题, 结合自己的使用和理解,通过实例详细介绍一下其用法。示例难度不一, 其中有些可能并没有多少实用性,仅仅是为了展示功能。 命令形式 :help :g可知,该命令的使用形式为: :[range]global/{pattern}/{command} global 命令在 [range] 指定的文本范围内(缺省为整个文件)查找 {pattern}, 然后对匹配到的行执行命令{command},如果希望对没匹配上的行执行命令, 则使用 global! 或 vglobal 命令。 先来看 Vim 用户手册里的一个经典例子。 【例1】倒序文件行(即unix下的tac命令) :g/^/m 0 ^ 匹配文件的所有行(这是查找的一个常用技巧,如果用 .则是匹配非空行,不满足本例要求), 然后用 move 命令依次将每行移到第一行(第0行的下一行),从而实现了倒序功能。 global命令实际上是分成两步执行:首先扫描[range]指定范围内的所有行, 给匹配{pattern}的行打上标记;然后依次对打有标记的行执行{command}命令, 如果被标记的行在对之前匹配行的命令操作中被删除、移动或合并,则其标记自动消失,而不对该行执行{command}命令。 标记的概念很重要,以例说明。 【例2】删除偶数行 :g/^/+1 d :normal命令实现: :%normal! jdd % 指定整个文件,然后依次执行普通模式下的 jdd,即下移删除一行。与 global 命令不同之处在于, %normal! jdd 是按照行号顺序执行,在第一行时删除了第二行,后面的所有行号都减一, 因此在第二行执行 jdd 时删除的是原来的第四行。也就是说,global 命令是通过偶数行标记的消失实现的, 而 normal 命令是通过后续行的自动前移实现的。 【例3】删除奇数行 :g/^/d|m. :g/^/d显然不行,这会删除所有行,我们需要用move命令把偶数行的标记去掉。当然,本例可以很简单的转换成【例2】,在此只是用来强调标记的概念。 本例若想用 normal 命令实现比较有意思,%normal! dd也同样会删除整个文件,%normal! jkdd就可以,我不知道两者为什么不同,可能和normal命令内部的运行机制有关。 global与substitute :substitute 执行的是替换而 :global 执行的其它命令(当然,substitute 缺省的 [range] 是当前行,这点也不同)。 先看两个例子,体会一下:s和:g不同的思维方式。 【例4】double所有行 :%s/.*/&\r&/ :g/^/t. substitue是查找任意行,然后替换为两行夹回车;global是将每一行复制(:t就是:copy)到自己下面,更加清晰明了。 【例5】把以回车排版、以空行分段的文本变成以回车分段的文本 很多txt格式的ebook,以及像vim help这样的文本,每行的字符数受限,段之间用空行分隔。若把它们拷贝到word里,那些硬回车和空行就比较讨厌了,虽然word里也有自动调整格式的功能,不过在Vim里搞定更是小菜一碟。先看看用替换如何实现。 :%s/\n\n\@!// \n\n\@!是查找后面不跟回车的回车(关于\@!的用法请:h /\@!,在此不多说了),然后替换为空,也就是去掉用于排版的回车。 注:如果等宽段之间无空行,则合并命令变为:%s/\n\%(\s\{2,}\|\n\)\@!//g global命令则完全是另一种思路。 :g/./,/^$/j /./标记非空行,/^$/查找其后的空行,然后对二者之间的行进行合并操作。也许有人会问,段中的每一行会不会都执行了j命令?前面已经说过,在之前操作中消失掉的标记行不执行操作命令,在处理每段第一行时已经把段内的其余行都合并了,所以每段只会执行一次j命令。这条命令使用global标记做为[range]的起始行,这样的用法后面还会详述。 global经常与substitute组合使用,用前者定位满足一定条件的行,用后者在这些行中进行查找替换。如: 【例6】将aaa替换成bbb,除非该行中有ccc或者ddd :v/ccc\|ddd/s/aaa/bbb/g 【例7】将aaa替换成bbb,条件是该行中有ccc但不能有ddd 如何写出一个匹配aaa并满足行内有ccc但不能有ddd的正则表达式?我不知道。即便能写出来,也必定极其复杂。用global命令则并不困难: :g/ccc/if getline('.') !~ 'ddd' | s/aaa/bbb/g ccc的行,然后执行if命令(if也是ex命令!),getline函数取得当前行,然后判断是否匹配ddd,如果不匹配(!~的求值为true)则执行替换。要掌握这样的用法需要对ex命令、Vim函数和表达式有一定了解才行,实际上,这条命令已经是一个快捷版的脚本了。可能有人会想,把g和v连起来用不就行了么,可惜global命令不支持(恐怕也没法支持)嵌套。 global标志的[range]用法 :h range global命令第一步中所设的标记,可以被用来为{command}命令设定各种形式的[range]。在【例2】和【例5】中都已使用了这一技巧,灵活使用[range],是一项重要的基本功。先看看【例2】和【例3】的一般化问题。 【例8】每n行中,删除前/后m行(例如,每10行删除前/后3行) :g/^/,+2 d | ,+6 m -1 :g/^/,+6 m -1 | +1,+3 d :g 命令配合 / 命令搜索确定 range, 实际上,语法级别的 range 确定,使用正则表达式是完全不够的。 【例9】提取条件编译内容。例如,在一个多平台的C程序里有大量的条件编译代码: #ifdef WIN32 XXX1 XXX2 #endif ... #ifdef WIN32 XXX3 XXX4 #else YYY1 YYY2 #endif :g/#ifdef WIN32/+1,/#else\|#endif/-1 t $ t命令的[range]是由逗号分隔,起始行是/#ifdef WIN32/标记行的下一行,结束行是一个查找定位,是在起始行后面出现的#endif或#else的上一行,t将二者间的内容复制到末尾。 【例10】提取上述C程序中的非Win32平台的代码(YYY部分) 首先说明一下,这个例子比前例要复杂的多,主要涉及的是[range]的操作,已经和global命令没多少关系,大可不看。加到这的目的是把问题说完,供喜欢细抠的朋友参考。本例的复杂性在于:首先,不能简单的用#else和#endif定位,因为代码中可能有其它的条件编译,我们必须要将查找范围限定在#ifdef WIN32的block中;另外,在block中可能并没有#else部分,这会给定位带来很大麻烦。解决方法是: :try | g/#ifdef WIN32//#else/+1, /#endif/-1 t $ | endtry global部分:找到WIN32,再向后找到#else,将其下一行作为[range]的起始行,然后从当前的光标(WIN32所在行,而非刚找到的#else的下一行)向下找到#endif,将其上一行作为[range]的结束行,然后执行t命令。但对于没有#else的block,如第一段代码,[range]的起始行是YYY1,而结束行是XXX2(因为查找#endif时是从第一行开始的,而不是从YYY1开始),这是一个非法的[range],会引起exception,如果不放在try里面global命令就会立刻停止。 与逗号(,)不同,如果[range]是用分号(;)分隔的,则会使得当前光标移至起始行,在查找#endif时是从#else的下一行开始,这样就产生非法[range],用不着try,但带来的问题是:没有#else的block会错误的把后面block中的#else部分找出来。

2013/7/1
articleCard.readMore

欢迎来到 Racket

简介 S-表达式 高阶函数 Lambda 表达式 惰性求值 闭包 安装 执行 racket 脚本 简介 S-表达式 1+2*3 写成前缀表达式就是 (+ 1 (* 2 3)) 。 优点:容易parse,简单纯粹,不用考虑什么优先级等,也是实现代码即数据的前提; 缺点:可读性不是很强; 高阶函数 接受一个或多个函数作为输入; 输出一个函数; f(x,y)=x+y, 如果给定了 y=1,则就得到了 g(x)=x+1 这个函数。 Lambda 表达式 惰性求值 闭包 #lang racket (define (quick-sort array) (cond [(empty? array) empty] ; 快排的思想是分治+递归 [else (append (quick-sort (filter (lambda (x) (< x (first array))) array)) ; 这里的 array 就是闭包 (filter (lambda (x) (= x (first array))) array) (quick-sort (filter (lambda (x) (> x (first array))) array)))])) (quick-sort '(1 3 2 5 3 4 5 0 9 82 4)) ;; 运行结果 '(0 1 2 3 3 4 4 5 5 9 82) 安装 执行 racket 脚本 hello.rkt 文件: #lang lisp (+ 1 1) racket -t hello.rkt, 就可以看到输出 2。

2013/3/19
articleCard.readMore

使用 Windows Live Writer 发布博客

很早以前,使用 wordpress 搭建博客的时候,尝试过 Windows Live Writer 这个工具。一些操作界面记忆犹新。 图片来源于网络。 早期的记录: https://groups.google.com/g/ircubuntu-cn/c/CkxGsARUDV8 22:01:57 <Kandu> wsdjeg: wsdjeg.tk 22:02:06 <Kandu> wsdjeg: <img src="http://127.0.0.1/wordpress/wp-content/themes/twentyten/images/headers/path.jpg" 22:02:07 <wsdjeg> 怎么了 22:02:35 <wsdjeg> 为什么不现实图片呢 22:02:46 <szsloss> 你的是什么服务器啊?? 22:02:54 <szsloss> apache or nginx?? 22:03:12 <wsdjeg> 我自己的电脑阿 22:03:26 <wsdjeg> 不知道 ubuntu 22:03:31 <wsdjeg> 刚装的lamp 22:03:33 <szsloss> 要 环境的啊 22:03:35 <wsdjeg> 不会设置 22:03:37 <szsloss> 哦 22:03:43 <wsdjeg> 怎么杨设置阿 22:03:46 <szsloss> 那就是 apache 了】 22:03:50 <RuiZi> J :#ubuntu-cn 22:03:54 <myke2> Q :Read error: Connection reset by peer 22:04:05 <wsdjeg> 帮个忙 怎么设置 我就想做一个boog 22:04:20 <jiero> blog已经不流行了。 22:04:21 <jiero> 哈哈 22:04:43 <szsloss> 你 在 apache 里配置 一个 host 就可以了 22:04:45 <wsdjeg> 那该怎么设置下呢 有高手指点下么 22:04:54 <wsdjeg> 如何配置呢 Evanescence wsdjeg: you can set up Wordpress following official tutorial Evanescence wsdjeg: and ubuntu wiki is usful too 22:06:27 <szsloss> wsdjeg: http://carrot.iteye.com/blog/232558 22:07:01 <szsloss> wsdjeg: http://clin003.com/servers/windows-configure-apache-virtualhost-1850/ 22:07:09 <szsloss> 网上多的是啊 22:10:26 <wsdjeg> 还是高不明白 22:10:29 <shellex> ACTION 在咕噜牛奶 22:10:33 <bluek> sudo gedit /usr/share/gnome-session没有啊 22:10:48 <wsdjeg> 直接点阿 在什么地方加什么文件 内容是什么 22:11:48 <wsdjeg> 具体怎么弄阿 大哥 22:21:27 <caleb-> wsdjeg: 开了服务记得要设置防火墙 22:21:35 <caleb-> wsdjeg: 裸奔不是好习惯 22:21:48 <wsdjeg> 不懂 22:21:53 <wsdjeg> 怎么设置呢 22:22:18 <caleb-> wsdjeg: ubuntu 默认有图形介面的 OpenLiveWriter

2011/5/20
articleCard.readMore

大学毕业了

2006年,这一年我考上了苏州大学。家里人都非常高兴,就如同2003年那会儿考上了仪征中学一样。 在选择专业的时候第一志愿直接选择了生物科学专业,因为高中期间,几门课程当中生物课学的最好。 依然记得,新生报道是我的父亲陪同一起来的大学。 我们提前了一天来到了苏州,当时小姑姑住在白马涧那边,我们还去了白马涧公园玩了一下,河里的水清澈见底,还有桃花水母。 第二天,我和父亲一起来了学校报道。 报名好了整理完宿舍的时候,父亲再三叮嘱一些事情就离开了。 我也不太记得具体是什么事情,没过多久父亲又回来找到了我,他的脸色不太好,有些发白。 他没有说,其实我是知道的,父亲很少坐车出远门,折返回来坐车一定是晕车了。 父亲的话很少,仍然是叮嘱了那几句话就回去了,然而,这次叮嘱,明显感觉声音在哽咽。 从小打到,我记忆中就没见过父亲掉眼泪,他脾气其实不太好,和妈妈、爷爷奶奶经常拌嘴, 是个很要强的人,这是我记忆中第一次见到父亲落泪。想来也是,以往高中、 初中基本上就在家附近,哪怕是高中也是周末都回家,上了大学离家太远了。 我更新这篇日志是2024年,距离大学毕业过去了整整十四年。回过头来再看, 发现居然没人任何物件可以支撑这四年的记忆,回忆起来挺吃力的,感觉记忆也是零碎的, 前后错乱的,似乎都没有一个时间主线。依稀就记得那么一些人和事,可能再过几年,这些记忆应该慢慢会更加的淡化。

2010/6/30
articleCard.readMore

模块化 vimrc 初体验

Vim 使用已经快有4个年头了,从最早期的只有一个 ~/.vimrc 文件,到之前一个完整的 ~/.vim 目录, 期间配置文件结构也改变了很多。配置也变得越来越臃肿。 体验过 eclipse 这样的工具后,回过头来在看看 Vim 的配置,似乎也可以把一些功能做成一些模块, 然后根据自己实际的需求载入这些模块。 在这里,我们使用 Bundle 这款工具来管理插件,大致的思路如下: 主文件结构 ~/.vimrc: " 将插件管理器加入 rtp set rtp+=~/.vim/bundle/bundle call bundle#begin() " 模块逻辑 let g:modulars = ['core', 'java'] " 根据模块变量载入模块 for m in g:modulars exe 'so ~/.vim/modulars/' . m . '.vim' endfor call bundle#end() 模块结构 ~/.vim/modulars/java.vim: Bundle 'javautil.vim' Bundle 'javaclass.vim' " 插件配置 let g:java_util_foo = 'xxx'

2009/4/1
articleCard.readMore

Lua 数据库访问

2008/6/20
articleCard.readMore

Lua 面向对象

2008/6/14
articleCard.readMore

Lua 垃圾回收

Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。 Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。 Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。 垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。 垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的”两倍”速工作。 如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。 垃圾回收器函数 Lua 提供了以下函数collectgarbage ([opt [, arg]])用来控制自动内存管理: collectgarbage("collect"): 做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能: collectgarbage("count"): 以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。 collectgarbage("restart"): 重启垃圾收集器的自动运行。 collectgarbage("setpause"): 将 arg 设为收集器的 间歇率 (参见 §2.5)。 返回 间歇率 的前一个值。 collectgarbage("setstepmul"): 返回 步进倍率 的前一个值。 collectgarbage("step"): 单步运行垃圾收集器。 步长”大小”由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。 collectgarbage("stop"): 停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。 mytable = {"apple", "orange", "banana"} print(collectgarbage("count")) mytable = nil print(collectgarbage("count")) print(collectgarbage("collect")) print(collectgarbage("count")) 20.9560546875 20.9853515625 0 19.4111328125

2008/6/12
articleCard.readMore

Lua 调试(Debug)

Lua 提供了 debug 库用于提供创建我们自定义调速器的功能。Lua 本身并未有内置的调速器,但很多开发者共享了他们的 Lua 调速器代码。 Lua 中 debug 库包含以下函数: sethook ([thread,] hook, mask [, count]): debug(): 进入一个用户交互模式,运行用户输入的每个字符串。 使用简单的命令以及其它调试设置,用户可以检阅全局变量和局部变量, 改变变量的值,计算一些表达式,等等。输入一行仅包含 cont 的字符串将结束这个函数, 这样调用者就可以继续向下运行。 getfenv(object): 返回对象的环境变量。 gethook(optional thread): 返回三个表示线程钩子设置的值: 当前钩子函数,当前钩子掩码,当前钩子计数 getinfo ([thread,] f [, what]): 返回关于一个函数信息的表。 你可以直接提供该函数, 也可以用一个数字 f 表示该函数。 数字 f 表示运行在指定线程的调用栈对应层次上的函数: 0 层表示当前函数(getinfo 自身); 1 层表示调用 getinfo 的函数 (除非是尾调用,这种情况不计入栈);等等。 如果 f 是一个比活动函数数量还大的数字, getinfo 返回 nil。 debug.getlocal ([thread,] f, local): 此函数返回在栈的 f 层处函数的索引为 local 的局部变量 的名字和值。 这个函数不仅用于访问显式定义的局部变量,也包括形参、临时变量等。 getmetatable(value): 把给定索引指向的值的元表压入堆栈。如果索引无效,或是这个值没有元表,函数将返回 0 并且不会向栈上压任何东西。 getregistry(): 返回注册表表,这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值。 getupvalue (f, up): 此函数返回函数 f 的第 up 个上值的名字和值。 如果该函数没有那个上值,返回 nil 。以 ‘(‘ (开括号)打头的变量名表示没有名字的变量 (去除了调试信息的代码块)。 将一个函数作为钩子函数设入。 字符串 mask 以及数字 count 决定了钩子将在何时调用。 掩码是由下列字符组合成的字符串,每个字符有其含义: ‘c’: 每当 Lua 调用一个函数时,调用钩子; ‘r’: 每当 Lua 从一个函数内返回时,调用钩子; ‘l’: 每当 Lua 进入新的一行时,调用钩子。 setlocal ([thread,] level, local, value): 这个函数将 value 赋给 栈上第 level 层函数的第 local 个局部变量。 如果没有那个变量,函数返回 nil 。 如果 level 越界,抛出一个错误。 setmetatable (value, table): 将 value 的元表设为 table (可以是 nil)。 返回 value。 setupvalue (f, up, value): 这个函数将 value 设为函数 f 的第 up 个上值。 如果函数没有那个上值,返回 nil 否则,返回该上值的名字。 traceback ([thread,] [message [, level]]): 如果 message 有,且不是字符串或 nil, 函数不做任何处理直接返回 message。 否则,它返回调用栈的栈回溯信息。 字符串可选项 message 被添加在栈回溯信息的开头。 数字可选项 level 指明从栈的哪一层开始回溯 (默认为 1 ,即调用 traceback 的那里)。 function myfunction () print(debug.traceback("Stack trace")) print(debug.getinfo(1)) print("Stack trace end") return 10 end myfunction () print(debug.getinfo(1)) Stack trace stack traceback: test2.lua:2: in function 'myfunction' test2.lua:8: in main chunk [C]: ? table: 0054C6C8 Stack trace end function newCounter () local n = 0 local k = 0 return function () k = n n = n + 1 return n end end counter = newCounter () print(counter()) print(counter()) local i = 1 repeat name, val = debug.getupvalue(counter, i) if name then print ("index", i, name, "=", val) if(name == "n") then debug.setupvalue (counter,2,10) end i = i + 1 end -- if until not name print(counter()) 1 2 index 1 k = 1 index 2 n = 2 11 命令行调试 图形界面调试 命令行调试器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。 图形界调试器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit。

2008/6/10
articleCard.readMore

Lua 异常处理

语法错误 运行错误 pcall 和 xpcall、debug 语法错误 运行错误 语法错误 -- test.lua 文件 a == 2 lua: test.lua:2: syntax error near '==' = 号跟两个 = 号是有区别的。一个 = 是赋值表达式两个 = 是比较运算。 另外一个实例: for a= 1,10 print(a) end lua: test2.lua:2: 'do' expected near 'print' for a= 1,10 do print(a) end 运行错误 function add(a,b) return a+b end add(10) lua: test2.lua:2: attempt to perform arithmetic on local 'b' (a nil value) stack traceback: test2.lua:2: in function 'add' test2.lua:5: in main chunk [C]: ? local function add(a,b) assert(type(a) == "number", "a 不是一个数字") assert(type(b) == "number", "b 不是一个数字") return a+b end add(10) lua: test.lua:3: b 不是一个数字 stack traceback: [C]: in function 'assert' test.lua:3: in local 'add' test.lua:6: in main chunk [C]: in ? error (message [, level]) Level=1[默认]:为调用error位置(文件+行号) Level=2:指出哪个调用error的函数的函数 Level=0:不添加错误位置信息 pcall 和 xpcall、debug if pcall(function_name, ….) then -- 没有错误 else -- 一些错误 end > =pcall(function(i) print(i) end, 33) 33 true > =pcall(function(i) print(i) error('error..') end, 33) 33 false stdin:1: error.. > function f() return false,2 end > if f() then print '1' else print '0' end 0 debug.debug:提供一个Lua提示符,让用户来价差错误的原因 debug.traceback:根据调用桟来构建一个扩展的错误消息 >=xpcall(function(i) print(i) error('error..') end, function() print(debug.traceback()) end, 33) 33 stack traceback: stdin:1: in function [C]: in function 'error' stdin:1: in function [C]: in function 'xpcall' stdin:1: in main chunk [C]: in ? false nil function myfunction () n = n/nil end function myerrorhandler( err ) print( "ERROR:", err ) end status = xpcall( myfunction, myerrorhandler ) print( status) ERROR: test2.lua:2: attempt to perform arithmetic on global 'n' (a nil value) false

2008/5/30
articleCard.readMore

Lua 文件 IO

Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法 file = io.open (filename [, mode]) 模式 描述 r 以只读方式打开文件,该文件必须存在。 w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。 a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留) r+ 以可读写方式打开文件,该文件必须存在。 w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 a+ 与a类似,但此文件可读可写 b 二进制模式,如果文件是二进制文件,可以加上b + 号表示对文件既可以读也可以写 -- 以只读方式打开文件 file = io.open("test.lua", "r") -- 设置默认输入文件为 test.lua io.input(file) -- 输出文件第一行 print(io.read()) -- 关闭打开的文件 io.close(file) -- 以附加的方式打开只写文件 file = io.open("test.lua", "a") -- 设置默认输出文件为 test.lua io.output(file) -- 在文件最后一行添加 Lua 注释 io.write("-- test.lua 文件末尾注释") -- 关闭打开的文件 io.close(file) -- test.lua 文件 模式 描述 *n 读取一个数字并返回它。例:file.read('*n') *a 从当前位置读取整个文件。例:file.read("*a") *l(默认) 读取下一行,在文件尾 (EOF) 处返回 nil。例:file.read("*l") number 返回一个指定字符个数的字符串,或在 EOF 时返回 nil。例:file.read(5) io.tmpfile():返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除 io.type(file): 检测obj是否一个可用的文件句柄 io.flush(): 向文件写入缓冲中的所有数据 io.lines(optional file name): 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件 -- 以只读方式打开文件 file = io.open("test.lua", "r") -- 输出文件第一行 print(file:read()) -- 关闭打开的文件 file:close() -- 以附加的方式打开只写文件 file = io.open("test.lua", "a") -- 在文件最后一行添加 Lua 注释 file:write("--test") -- 关闭打开的文件 file:close() -- test.lua 文件 file:seek(optional whence, optional offset): 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。参数 whence 值可以是: "set": 从文件头开始 "cur": 从当前位置开始[默认] "end": 从文件尾开始 offset:默认为0 不带参数file:seek()则返回当前位置,file:seek("set")则定位到文件头,file:seek("end")则定位到文件尾并返回文件大小 file:flush(): 向文件写入缓冲中的所有数据 io.lines(optional file name): 打开指定的文件filename为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,并自动关闭文件。 若不带参数时io.lines() io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文件,如 for line in io.lines("main.lua") do   print(line)   end *a 参数,即从当期位置(倒数第 25 个位置)读取整个文件。 -- 以只读方式打开文件 file = io.open("test.lua", "r") file:seek("end",-25) print(file:read("*a")) -- 关闭打开的文件 file:close() st.lua 文件末尾--test

2008/5/24
articleCard.readMore

Lua 协同程序(coroutine)

什么是协同(coroutine)? Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。 协同是非常强大的功能,但是用起来也很复杂。 线程和协同程序区别 线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。 在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。 协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。 基本语法 方法 描述 coroutine.create() 创建 coroutine,返回 coroutine, 参数是一个函数,当和 resume 配合使用的时候就唤醒函数调用 coroutine.resume() 重启 coroutine,和 create 配合使用 coroutine.yield() 挂起 coroutine,将 coroutine 设置为挂起状态,这个和 resume 配合使用能有很多有用的效果 coroutine.status() 查看 coroutine 的状态 注:coroutine 的状态有三种:dead,suspend,running,具体什么时候有这样的状态请参考下面的程序 coroutine.wrap() 创建 coroutine,返回一个函数,一旦你调用这个函数,就进入 coroutine,和 create 功能重复 coroutine.running() 返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用 running 的时候,就是返回一个 corouting 的线程号 -- coroutine_test.lua 文件 co = coroutine.create( function(i) print(i); end ) coroutine.resume(co, 1) -- 1 print(coroutine.status(co)) -- dead print("----------") co = coroutine.wrap( function(i) print(i); end ) co(1) print("----------") co2 = coroutine.create( function() for i=1,10 do print(i) if i == 3 then print(coroutine.status(co2)) --running print(coroutine.running()) --thread:XXXXXX end coroutine.yield() end end ) coroutine.resume(co2) --1 coroutine.resume(co2) --2 coroutine.resume(co2) --3 print(coroutine.status(co2)) -- suspended print(coroutine.running()) --nil print("----------") 1 dead ---------- 1 ---------- 1 2 3 running thread: 0x7fb801c05868 false suspended thread: 0x7fb801c04c88 true ---------- function foo (a) print("foo 函数输出", a) return coroutine.yield(2 * a) -- 返回 2*a 的值 end co = coroutine.create(function (a , b) print("第一次协同程序执行输出", a, b) -- co-body 1 10 local r = foo(a + 1) print("第二次协同程序执行输出", r) local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入 print("第三次协同程序执行输出", r, s) return b, "结束协同程序" -- b的值为第二次调用协同程序时传入 end) print("main", coroutine.resume(co, 1, 10)) -- true, 4 print("--分割线----") print("main", coroutine.resume(co, "r")) -- true 11 -9 print("---分割线---") print("main", coroutine.resume(co, "x", "y")) -- true 10 end print("---分割线---") print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine print("---分割线---") 第一次协同程序执行输出 1 10 foo 函数输出 2 main true 4 --分割线---- 第二次协同程序执行输出 r main true 11 -9 ---分割线--- 第三次协同程序执行输出 x y main true 10 结束协同程序 ---分割线--- main false cannot resume dead coroutine ---分割线--- 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false; 协同程序运行; 运行到yield语句; yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数) 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数) yield返回; 协同程序继续运行; 如果使用的协同程序继续运行完成后继续调用 resumev方法则输出:cannot resume dead coroutine local newProductor function productor() local i = 0 while true do i = i + 1 send(i) -- 将生产的物品发送给消费者 end end function consumer() while true do local i = receive() -- 从生产者那里得到物品 print(i) end end function receive() local status, value = coroutine.resume(newProductor) return value end function send(x) coroutine.yield(x) -- x表示需要发送的值,值返回以后,就挂起该协同程序 end -- 启动程序 newProductor = coroutine.create(productor) consumer() 1 2 3 4 5 6 7 8 9 10 11 12 13 ……

2008/5/20
articleCard.readMore

Lua 元表(Metatable)

__index 元方法 __newindex 元方法 __tostring 元方法 __add的字段,若找到,则调用对应的值。__add 等即时字段,其对应的值(往往是一个函数或是 table)就是”元方法”。 有两个很重要的函数来处理元表: setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在__metatable键值,setmetatable 会失败 。 getmetatable(table): 返回对象的元表(metatable)。 mytable = {} -- 普通表 mymetatable = {} -- 元表 setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表 mytable = setmetatable({},{}) getmetatable(mytable) -- 这回返回mymetatable __index 元方法 __index 键。如果__index包含一个表格,Lua 会在表格中查找相应的键。 我们可以在使用 lua 命令进入交互模式查看: $ lua Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio > other = { foo = 3 } > t = setmetatable({}, { __index = other }) > t.foo 3 > t.bar nil __index包含一个函数的话,Lua 就会调用那个函数,table 和键会作为参数传递给函数。 __index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。 mytable = setmetatable({key1 = "value1"}, { __index = function(mytable, key) if key == "key2" then return "metatablevalue" else return nil end end }) print(mytable.key1,mytable.key2) value1 metatablevalue __index。 __index方法,如果__index方法是一个函数,则调用该函数。元方法中查看是否传入 “key2” 键的参数(mytable.key2 已设置),如果传入 “key2” 参数返回 “metatablevalue”,否则返回 mytable 对应的键值。 mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } }) print(mytable.key1,mytable.key2) __newindex 元方法 __newindex 元方法用来对表更新,__index则用来对表访问 。 当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。 以下实例演示了 __newindex 元方法的应用: mymetatable = {} mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable }) print(mytable.key1) mytable.newkey = "新值2" print(mytable.newkey,mymetatable.newkey) mytable.key1 = "新值1" print(mytable.key1,mymetatable.newkey1) value1 nil 新值2 新值1 nil __newindex,在对新索引键(newkey)赋值时(mytable.newkey = “新值 2”),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。 以下实例使用了 rawset 函数来更新表: mytable = setmetatable({key1 = "value1"}, { __newindex = function(mytable, key, value) rawset(mytable, key, "\""..value.."\"") end }) mytable.key1 = "new value" mytable.key2 = 4 print(mytable.key1,mytable.key2) new value "4" -- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用 -- 自定义计算表中最大值函数 table_maxn function table_maxn(t) local mn = 0 for k, v in pairs(t) do if mn < k then mn = k end end return mn end -- 两表相加操作 mytable = setmetatable({ 1, 2, 3 }, { __add = function(mytable, newtable) for i = 1, table_maxn(newtable) do table.insert(mytable, table_maxn(mytable)+1,newtable[i]) end return mytable end }) secondtable = {4,5,6} mytable = mytable + secondtable for k,v in ipairs(mytable) do print(k,v) end 1 1 2 2 3 3 4 4 5 5 6 6 __add 键包含在元表中,并进行相加操作。 表中对应的操作列表如下: 模式 描述 __add 对应的运算符 +. __sub 对应的运算符 -. __mul 对应的运算符 *. __div 对应的运算符 /. __mod 对应的运算符 %. __unm 对应的运算符 -. __concat 对应的运算符 ... __eq 对应的运算符 ==. __lt 对应的运算符 <. __le 对应的运算符 <=. __call 元方法 __call 元方法在 Lua 调用一个值时调用。以下实例演示了计算表中元素的和: -- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用 -- 自定义计算表中最大值函数 table_maxn function table_maxn(t) local mn = 0 for k, v in pairs(t) do if mn < k then mn = k end end return mn end -- 定义元方法__call mytable = setmetatable({10}, { __call = function(mytable, newtable) sum = 0 for i = 1, table_maxn(mytable) do sum = sum + mytable[i] end for i = 1, table_maxn(newtable) do sum = sum + newtable[i] end return sum end }) newtable = {10,20,30} print(mytable(newtable)) 70 __tostring 元方法 __tostring 元方法用于修改表的输出行为。以下实例我们自定义了表的输出内容: mytable = setmetatable({ 10, 20, 30 }, { __tostring = function(mytable) sum = 0 for k, v in pairs(mytable) do sum = sum + v end return "表所有元素的和为 " .. sum end }) print(mytable) 表所有元素的和为 60 从本文中我们可以知道元表可以很好的简化我们的代码功能,所以了解 Lua 的元表,可以让我们写出更加简单优秀的 Lua 代码。

2008/5/13
articleCard.readMore

Lua 模块和包

加载机制 -- 文件名为 module.lua -- 定义一个名为 module 的模块 module = {} -- 定义一个常量 module.constant = "这是一个常量" -- 定义一个函数 function module.func1() io.write("这是一个公有函数!\n") end local function func2() print("这是一个私有函数!") end function module.func3() func2() end return module require("<模块名>") require "<模块名>" -- test_module.php 文件 -- module 模块为上文提到到 module.lua require("module") print(module.constant) module.func3() 这是一个常量 这是一个私有函数! -- test_module2.php 文件 -- module 模块为上文提到到 module.lua -- 别名变量 m local m = require("module") print(m.constant) m.func3() 这是一个常量 这是一个私有函数! 加载机制 #LUA_PATH export LUA_PATH="~/lua/?.lua;;" source ~/.profile /Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua /Users/dengjoe/lua/module.lua; ./module.lua /usr/local/share/lua/5.1/module.lua /usr/local/share/lua/5.1/module/init.lua /usr/local/lib/lua/5.1/module.lua /usr/local/lib/lua/5.1/module/init.lua local path = "/usr/local/lua/lib/libluasocket.so" local f = loadlib(path, "luaopen_socket") local path = "/usr/local/lua/lib/libluasocket.so" -- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下 local f = assert(loadlib(path, "luaopen_socket")) f() -- 真正打开库 一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。 将 stub 文件所在的目录加入到 LUA_PATH,这样设定后就可以使用 require 函数加载 C 库了。

2008/5/10
articleCard.readMore

Lua tables(表)

2008/4/28
articleCard.readMore

Lua 迭代器

泛型 for 迭代器 无状态的迭代器 泛型 for 迭代器 for k, v in pairs(t) do print(k, v) end array = {"Lua", "Tutorial"} for key,value in ipairs(array) do print(key, value) end 1 Lua 2 Tutorial 首先,初始化,计算 in 后面表达式的值,表达式应该返回范性 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。 第三,将迭代函数返回的值赋给变量列表。 第四,如果返回的第一个值为 nil 循环结束,否则执行循环体。 第五,回到第二步再次调用迭代函数 无状态的迭代器 多状态的迭代器 无状态的迭代器 function square(iteratorMaxCount,currentNumber) if currentNumber<iteratorMaxCount then currentNumber = currentNumber+1 return currentNumber, currentNumber*currentNumber end end for i,n in square,3,0 do print(i,n) end 1 1 2 4 3 9 function iter (a, i) i = i + 1 local v = a[i] if v then return i, v end end function ipairs (a) return iter, a, 0 end array = {"Lua", "Tutorial"} function elementIterator (collection) local index = 0 local count = #collection -- 闭包函数 return function () index = index + 1 if index <= count then -- 返回迭代器的当前元素 return collection[index] end end end for element in elementIterator(array) do print(element) end Lua Tutorial 以上实例中我们可以看到,elementIterator 内使用了闭包函数,实现计算集合大小并输出各个元素。

2008/4/20
articleCard.readMore

Lua 数组

一维数组 多维数组 一维数组 array = {"Lua", "Tutorial"} for i= 0, 2 do print(array[i]) end nil Lua Tutorial array = {} for i= -2, 2 do array[i] = i *2 end for i = -2,2 do print(array[i]) end -4 -2 0 2 4 多维数组 -- 初始化数组 array = {} for i=1,3 do array[i] = {} for j=1,3 do array[i][j] = i*j end end -- 访问数组 for i=1,3 do for j=1,3 do print(array[i][j]) end end 1 2 3 2 4 6 3 6 9 -- 初始化数组 array = {} maxRows = 3 maxColumns = 3 for row=1,maxRows do for col=1,maxColumns do array[row*maxColumns +col] = row*col end end -- 访问数组 for row=1,maxRows do for col=1,maxColumns do print(array[row*maxColumns +col]) end end 1 2 3 2 4 6 3 6 9 正如你所看到的,以上的实例中,数组设定了指定的索引值,这样可以避免出现 nil 值,有利于节省内存空间。

2008/4/14
articleCard.readMore

Lua 字符串

字符串操作 字符串操作 string.upper 单引号间的一串字符。 双引号间的一串字符。 [[和]]间的一串字符。 string1 = "Hello" print("\"字符串 1 是\"", string1) string2 = 'lua' print("字符串 2 是", string2) string3 = [["Lua 教程"]] print("字符串 3 是", string3) "字符串 1 是" Hello 字符串 2 是 lua 字符串 3 是 "Lua 教程" 转义字符 意义 ASCII 码值(十进制) \a 响铃(BEL) 007 \b 退格(BS) ,将当前位置移到前一列 008 \f 换页(FF),将当前位置移到下页开头 012 \n 换行(LF) ,将当前位置移到下一行开头 010 \r 回车(CR) ,将当前位置移到本行开头 013 \t 水平制表(HT) (跳到下一个 TAB 位置) 009 \v 垂直制表(VT) 011 \ 代表一个反斜线字符’’' 092 ' 代表一个单引号(撇号)字符 039 " 代表一个双引号字符 034 空字符(NULL) 000   \ddd 1 到 3 位八进制数所代表的任意字符 三位八进制 \xhh 1 到 2 位十六进制所代表的任意字符 二位十六进制 字符串操作 string1 = "Lua"; print(string.upper(string1)) print(string.lower(string1)) LUA lua string = "Lua Tutorial" -- 查找字符串 print(string.find(string,"Tutorial")) reversedString = string.reverse(string) print("新字符串为",reversedString) 5 12 新字符串为 lairotuT auL string1 = "Lua" string2 = "Tutorial" number1 = 10 number2 = 20 -- 基本字符串格式化 print(string.format("基本格式化 %s %s",string1,string2)) -- 日期格式化 date = 2; month = 1; year = 2014 print(string.format("日期格式化 %02d/%02d/%03d", date, month, year)) -- 十进制格式化 print(string.format("%.4f",1/3)) 基本格式化 Lua Tutorial 日期格式化 02/01/2014 0.3333 -- 字符转换 -- 转换第一个字符 print(string.byte("Lua")) -- 转换第三个字符 print(string.byte("Lua",3)) -- 转换末尾第一个字符 print(string.byte("Lua",-1)) -- 第二个字符 print(string.byte("Lua",2)) -- 转换末尾第二个字符 print(string.byte("Lua",-2)) -- 整数 ASCII 码转换为字符 print(string.char(97)) 76 97 97 117 117 a string1 = "wsdjeg." string2 = "spacevim" string3 = ".org" -- 使用 .. 进行字符串连接 print("连接字符串",string1..string2..string3) -- 字符串长度 print("字符串长度 ",string.len(string2)) -- 字符串复制 2 次 repeatedString = string.rep(string2,2) print(repeatedString) 连接字符串 wsdjeg.spacevim.org 字符串长度 8 spacevimspacevim 字符串操作 string.upper string.upper(argument) str1 = "wsdjeg" print(string.upper(str1)) WSDJEG

2008/4/8
articleCard.readMore

Lua 运算符

算术运算符 关系运算符 算术运算符 关系运算符 逻辑运算符 其他运算符 算术运算符 操作符 描述 实例 + 加法 A + B 输出结果 30 - 减法 A - B 输出结果 -10 * 乘法 A * B 输出结果 200 / 除法 B / A 输出结果 2 % 取余 B % A 输出结果 0 ^ 乘幂 A ^ 2 输出结果 100 - 负号 -A 输出结果 -10 a = 21 b = 10 c = a + b print("Line 1 - c 的值为 ", c ) c = a - b print("Line 2 - c 的值为 ", c ) c = a * b print("Line 3 - c 的值为 ", c ) c = a / b print("Line 4 - c 的值为 ", c ) c = a % b print("Line 5 - c 的值为 ", c ) c = a^2 print("Line 6 - c 的值为 ", c ) c = -a print("Line 7 - c 的值为 ", c ) Line 1 - c 的值为 31 Line 2 - c 的值为 11 Line 3 - c 的值为 210 Line 4 - c 的值为 2.1 Line 5 - c 的值为 1 Line 6 - c 的值为 441 Line 7 - c 的值为 -21 关系运算符 操作符 描述 实例 == 等于,检测两个值是否相等,相等返回 true,否则返回 false (A == B) 为 false。 ~= 不等于,检测两个值是否相等,相等返回 false,否则返回 true (A ~= B) 为 true。 > 大于,如果左边的值大于右边的值,返回 true,否则返回 false (A > B) 为 false。 < 小于,如果左边的值大于右边的值,返回 false,否则返回 true (A < B) 为 true。 >= 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false (A >= B) is not true. <= 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false (A <= B) is true. a = 21 b = 10 if( a == b ) then print("Line 1 - a 等于 b" ) else print("Line 1 - a 不等于 b" ) end if( a ~= b ) then print("Line 2 - a 不等于 b" ) else print("Line 2 - a 等于 b" ) end if ( a < b ) then print("Line 3 - a 小于 b" ) else print("Line 3 - a 大于等于 b" ) end if ( a > b ) then print("Line 4 - a 大于 b" ) else print("Line 5 - a 小于等于 b" ) end -- 修改 a 和 b 的值 a = 5 b = 20 if ( a <= b ) then print("Line 5 - a 小于等于 b" ) end if ( b >= a ) then print("Line 6 - b 大于等于 a" ) end Line 1 - a 不等于 b Line 2 - a 不等于 b Line 3 - a 大于等于 b Line 4 - a 大于 b Line 5 - a 小于等于 b Line 6 - b 大于等于 a

2008/3/28
articleCard.readMore

Lua 函数

多返回值 可变参数 完成指定的任务,这种情况下函数作为调用语句使用; 计算并返回值,这种情况下函数作为赋值语句的表达式使用。 optional_function_scope function function_name( argument1, argument2, argument3..., argumentn) function_body return result_params_comma_separated end optional_function_scope: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数末尾为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。 function_name: 指定函数名称。 argument1, argument2, argument3..., argumentn : 函数参数,多个参数以逗号隔开,函数也可以不带参数。 function_body: 函数体,函数中需要执行的代码语句块。 result_params_comma_separated: 函数返回值,Lua 语言函数可以返回多个值,每个值以逗号隔开。 --[[ 函数返回两个值的最大值 --]] function max(num1, num2) if (num1 > num2) then result = num1; else result = num2; end return result; end -- 调用函数 print("两值比较最大值为 ",max(10,4)) print("两值比较最大值为 ",max(5,6)) myprint = function(param) print("这是打印函数 - ##",param,"##") end function add(num1,num2,functionPrint) result = num1 + num2 -- 调用传递的函数参数 functionPrint(result) end myprint(10) -- myprint 函数作为参数传递 add(2,5,myprint) 多返回值 > s, e = string.find("www.w3cschool.cn", "w3cschool") > print(s, e) 5 13 function maximum (a) local mi = 1 -- 最大值索引 local m = a[mi] -- 最大值 for i,val in ipairs(a) do if val > m then mi = i m = val end end return m, mi end print(maximum({8,10,23,12,5})) 可变参数 function average(...) result = 0 local arg={...} for i,v in ipairs(arg) do result = result + v end print("总共传入 " .. #arg .. " 个数") return result/#arg end print("平均值为",average(10,5,3,4,5,6)) 以上代码执行结果为:

2008/3/20
articleCard.readMore

Lua 流程控制

if 语句 if else 语句 else if 语句 if else 嵌套 --[ 0 为true ] if(0) then print("0 为真") end 0 为真 语句 描述 if 语句 if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。 if…else 语句 if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码。 if 嵌套语句 你可以在 if 或 else if 中使用一个或多个 if 或 else if 语句 。 if 语句 if(布尔表达式) then --[ 在布尔表达式为 true 时执行的语句 --] end --[ 定义变量 --] a = 10 --[ 使用 if 语句 --] if( a < 20 ) then --[ if 条件为 true 时打印以下信息 --] print("a 小于 20" ) end print("a 的值为:", a) if else 语句 if(布尔表达式) then --[ 布尔表达式为 true 时执行该语句块 --] else --[ 布尔表达式为 false 时执行该语句块 --] end --[ 定义变量 --] a = 100; --[ 检查条件 --] if( a < 20 ) then --[ if 条件为 true 时执行该语句块 --] print("a 小于 20" ) else --[ if 条件为 false 时执行该语句块 --] print("a 大于 20" ) end print("a 的值为 :", a) else if 语句 if( 布尔表达式 1) then --[ 在布尔表达式 1 为 true 时执行该语句块 --] else if( 布尔表达式 2) --[ 在布尔表达式 2 为 true 时执行该语句块 --] else if( 布尔表达式 3) --[ 在布尔表达式 3 为 true 时执行该语句块 --] else --[ 如果以上布尔表达式都不为 true 则执行该语句块 --] end --[ 定义变量 --] a = 100 --[ 检查布尔条件 --] if( a == 10 ) then --[ 如果条件为 true 打印以下信息 --] print("a 的值为 10" ) elseif( a == 20 ) then --[ if else if 条件为 true 时打印以下信息 --] print("a 的值为 20" ) elseif( a == 30 ) then --[ if else if condition 条件为 true 时打印以下信息 --] print("a 的值为 30" ) else --[ 以上条件语句没有一个为 true 时打印以下信息 --] print("没有匹配 a 的值" ) end print("a 的真实值为: ", a ) if else 嵌套 if( 布尔表达式 1) then --[ 布尔表达式 1 为 true 时执行该语句块 --] if(布尔表达式 2) then --[ 布尔表达式 2 为 true 时执行该语句块 --] end end --[ 定义变量 --] a = 100; b = 200; --[ 检查条件 --] if( a == 100 ) then --[ if 条件为 true 时执行以下 if 条件判断 --] if( b == 200 ) then --[ if 条件为 true 时执行该语句块 --] print("a 的值为 100 b 的值为 200" ); end end print("a 的值为 :", a ); print("b 的值为 :", b ); 以上代码执行结果如下:

2008/3/13
articleCard.readMore

Lua 循环

while 循环 for 循环 数值 for 循环 泛型 for 循环 repeat until 循环 嵌套循环 循环控制语句 循环类型 描述 while 循环 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。 for 循环 重复执行指定语句,重复次数可在 for 语句中控制。 Lua repeat…until 重复执行循环,直到 指定的条件为真时为止 循环嵌套 可以在循环内嵌套一个或多个循环语句(while、for、do..while) while 循环 while(condition) do statements end a = 15 while ( a < 20 ) do print("a 的值为:", a) a = a + 1 end for 循环 数值 for 循环 泛型 for 循环 数值 for 循环 for var=exp1,exp2,exp3 do <执行体> end for i=1,f(x) do print(i) end for i=10,1,-1 do print(i) end function f(x) print("方法被执行") return x*2 end for i=1,f(5) do print(i) end 泛型 for 循环 --打印数组a的所有值 for i,v in ipairs(a) do print(v) end days = {"Suanday","Monday","Tuesday"} for i,v in ipairs(days) do print(v) end repeat until 循环 repeat statements until( condition ) --[ 变量定义 --] a = 10 --[ 执行循环 --] repeat print("a的值为:", a) a = a + 1 until( a > 13 ) 嵌套循环 for init,max/min value, increment do for init,max/min value, increment do statements end statements end while(condition) do while(condition) do statements end statements end repeat statements repeat statements until( condition ) until( condition ) j =2 for i=2,10 do for j=2,(i/j) , 2 do if(not(i%j)) then break end if(j > (i/j))then print("i 的值为:",i) end end end 循环控制语句 --[ 定义变量 --] a = 10 --[ while 循环 --] while ( a < 20 ) do print("a 的值为:", a) a = a + 1 if ( a > 13) then --[ 使用 break 语句终止循环 --] break end end

2008/3/9
articleCard.readMore

Lua 数据类型

nil(空) boolean(布尔) number(数字) string(字符串) table(表) function(函数) thread(线程) userdata(自定义类型) 数据类型 描述 nil 这个最简单,只有值 nil 属于该类,表示一个无效值(在条件表达式中相当于 false)。 boolean 包含两个值:false 和 true。 number 表示双精度类型的实浮点数 string 字符串由一对双引号或单引号来表示 function 由 C 或 Lua 编写的函数 userdata 表示任意存储在变量中的 C 数据结构 thread 表示执行的独立线路,用于执行协同程序 table Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以是数字或者是字符串。在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。 print(type("Hello world")) --> string print(type(10.4*3)) --> number print(type(print)) --> function print(type(type)) --> function print(type(true)) --> boolean print(type(nil)) --> nil print(type(type(X))) --> string nil(空) > print(type(a)) nil > tab1 = { key1 = "val1", key2 = "val2", "val3" } for k, v in pairs(tab1) do print(k .. " - " .. v) end tab1.key1 = nil for k, v in pairs(tab1) do print(k .. " - " .. v) end boolean(布尔) print(type(true)) print(type(false)) print(type(nil)) if type(false) or type(nil) then print("false and nil are false!") else print("other is true!") end number(数字) print(type(2)) print(type(2.2)) print(type(0.2)) print(type(2e+1)) print(type(0.2e-1)) print(type(7.8263692594256e-06)) string(字符串) string1 = "this is string1" string2 = 'this is string2' html = [[ <html> <head></head> <body> <a href="//www.w3cschool.cn/">w3cschoolW3Cschool教程</a> </body> </html> ]] print(html) > print("2" + 6) 8.0 > print("2" + "6") 8.0 > print("2 + 6") 2 + 6 > print("-2e2" * "6") -1200.0 > print("error" + 1) stdin:1: attempt to perform arithmetic on a string value stack traceback: stdin:1: in main chunk [C]: in ? > > print("a" .. 'b') ab > print(157 .. 428) 157428 > > len = "www.w3cschool.cn" > print(#len) 16 > print(#"www.w3cschool.cn") 16 > table(表) -- 创建一个空的 table local tbl1 = {} -- 直接初始表 local tbl2 = {"apple", "pear", "orange", "grape"} -- table_test.lua 脚本文件 a = {} a["key"] = "value" key = 10 a[key] = 22 a[key] = a[key] + 11 for k, v in pairs(a) do print(k .. " : " .. v) end -- table_test2.lua 脚本文件 local tbl = {"apple", "pear", "orange", "grape"} for key, val in pairs(tbl) do print("Key", key) end -- table_test3.lua 脚本文件 a3 = {} for i = 1, 10 do a3[i] = i end a3["key"] = "val" print(a3["key"]) print(a3["none"]) function(函数) -- function_test.lua 脚本文件 function factorial1(n) if n == 0 then return 1 else return n * factorial1(n - 1) end end print(factorial1(5)) factorial2 = factorial1 print(factorial2(5)) -- function_test2.lua 脚本文件 function anonymous(tab, fun) for k, v in pairs(tab) do print(fun(k, v)) end end tab = { key1 = "val1", key2 = "val2" } anonymous(tab, function(key, val) return key .. " = " .. val end) thread(线程) userdata(自定义类型) userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。

2008/2/13
articleCard.readMore

Lua 基本语法

注释 标识符 关键词 全局变量 lua -i 或者 lua 命令开启。 F:\languages>lua Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio > > print("hello world!") > print("hello world!") hello world! > print("hello world!") F:\languages\lua>lua hello.lua hello world! #!/usr/local/bin/lua print("hello world!") ./hello.lua 的方式来执行。 注释 -- 这是一个单行注释 print("hello world!") --[[ 和 --]]。 --[[ 这是一个多行注释 这是一个多行注释 --]] print("hello world!") 标识符 mohd zara abc move_name a_123 myname50 _temp j a23b9 retVal 关键词 | and | break | do | else | | elseif | end | false | for | | function | if | in | local | | nil | not | or | repeat | | return | then | true | until | | while | 全局变量 > print(b) nil > b=10 > print(b) 10 > > b = nil > print(b) nil 这样变量 b 就好像从没被使用过一样。换句话说, 当且仅当一个变量不等于 nil 时,这个变量即存在。

2008/2/9
articleCard.readMore

学习 Lua 脚本语言

起因 开始学习 Lua 基本语法 Lua 数据类型 Lua 循环 Lua 流程控制 Lua 函数 Lua 运算符 Lua 字符串 Lua 数组 Lua 迭代器 Lua tables(表) Lua 模块和包 Lua 元表(Metatable) Lua 协同程序(coroutine) Lua 文件 IO Lua 异常处理 Lua 调试(Debug) Lua 垃圾回收 Lua 面向对象 Lua 数据库访问

2008/2/3
articleCard.readMore

大学选修课《Java 编程》

大学的专业是生物科学,本以为选修课会选一个实验相关的课程。科室不知道为什么鬼使神差的选了一个 Java 的。 这个似乎和生物没有一丁点关系。 感觉可能是因为以前接触过 C 语言,多多少少对编程还有一点了解,以前的兴趣死灰复燃。 C 语言更多的是面向过程编程,经常是把一个过程分离成多个函数来写。但是 Java 完全不一样, 面向对象编程这个思想,也是第一次听说。 在 Java 里,一切都是对象,做任何事情之前,得先有对象,然后通过对象来执行某件事。 第一节课上的似乎非常无趣,第一个程序是死记硬背下来的。 class People { public static void main(String args[]){ System.out.println("hello world!"); } }

2006/10/11
articleCard.readMore

进入苏州大学的第一天

都说上有天堂,下有苏杭。第一次来到苏州,市区的环境真的没有想像中的那么好。 21 号报到,提前 1 天来到了苏州,正好姑姑在苏州这边工作,去玩了一天。 恰逢啤酒节,白马涧公园晚上也很热闹。 第二天早晨,免费进园玩了一会。早晨的空气非常好,小溪里的桃花水母晶莹剔透。 办好报到,收拾好宿舍,父亲就回去了。临走是分明看到父亲的眼睛是红的, 也许是因为第一次长时间出远门,自己心里也非常堵,似乎一点没有入学的开心氛围。

2006/8/21
articleCard.readMore

高中毕业了

转眼间,高中毕业了。这三年来对我来说起伏还是很大的。刚进高中的时候,成绩还是中等偏上,上课也非常认真。可惜高一的化学、历史太枯燥无味,完全是死记硬背东西,一点兴趣也没有。那时地理跟政治两门课学的挺轻松。以至于后来上课打瞌睡,政治老师都不叫我了。 也不记得是什么时候开始迷上玩游戏的,03年那会,检查还不严,随随便便都可以去上网,后来04年就严了,未成年基本上肯定不让上网了。那时候玩的最多的是大话西游跟梦幻西游,这两游戏账号到现在还在呢。 什么样的通宵经历都有了,中午去上网的也有,下午下课后直接去的也有,晚自习下课去的也有。连续通宵一周的也有了。甚至有一次通宵完了回来第二天是考试的也有。 高一高二两年,真的把各种玩游戏的方式都体验了个遍。 高二期末考试结束,班上阿连问我考多少,结果我是倒数第二,他倒数第一,还被他笑了一通。也就是那一次让我彻底破防了,怎么说也是初中班里第二名靠近来的,这回去没法交差啦。于是,在学校后面租了一个房间,彻底戒掉网游,高考前再也不去上网了。暑假前后两个月,埋头苦学,恶补。60天结束第一次月考,考了班上第一名,班主任差点觉得我是作弊的😂,往后基本上就是前三来来去去就再也没什么大的变动了。 高考结束了哪天,晚上我又去上网啦,上了一夜回来被老爸骂了一通,不过想来,似乎也完全没瘾了。

2006/6/30
articleCard.readMore

不要轻言放弃

高中2年时间过去了,这两年时间玩了太多时间,学习完全是荒废了,期末考试成绩非常差,差到自己已经完全不相信了。 甚至有一种想自暴自弃的感觉。 但是,看到家人期许的眼神,看到父亲忙碌的背影,觉得自己错的太离谱了。 是的,这个暑假,学校有补习课,一个人住在学校后面的小屋子里,早起晚睡, 没日没夜的看书,写模拟试卷,总算感觉稍微充实一点了。

2005/9/1
articleCard.readMore

分班选生物还是化学

高一这一年过的很快,这一年没有用心学习,玩的太多了,到了高二分班的时候了。 记得初中升高中的时候,化学考的比物理都高,以为以后会一直喜欢化学这门课的。 可是完全没有想到的是,目前政治这门课成绩最好,然后是地理课,再然后是物理。 总结下来,高一的历史和化学死背的东西太多,逻辑性的东西太少,所以不喜欢,成绩自然也就落下了。 难不成要学文科?!! 不行,坚决不学文科,选一个物理吧,这个目前还算跟得上。再选一个新的课程,生物。

2004/9/1
articleCard.readMore

Vim 环视和固化分组

vim Perl 意义 \@= (?= 顺序环视 \@! (?! 顺序否定环视 \@<= (?<= 逆序环视 \@<! (?<! 逆序否定环视 \@> (?> 固化分组 \%(atom\) (?: 非捕获型括号 /(?<=foo)bar/ /\(foo\)\@<=bar Vim 使用示例 顺序环视 查找后面是 sql 的 my: /my\(sql\)\@= 顺序否定环视 查找后面不是 sql 的 my:/my\(sql\)\@! 逆序环视 查找前面是 my 的 sql: /\(my\)\@<=sql 逆序否定环视 查找前面不是 my 的 sql: /\(my\)\@<!sql 固化分组 非捕获型括号 意思是,此分组不捕获,可以理解为不算在分组信息中 :%s/\%(my\)sql\(ok\)/\1 mysqlok 替换为 ok ,由于 my 为捕获在分组中,故组中 \1 为 ok。

2004/8/1
articleCard.readMore

最后一封信

今天收到一份来自刘雅月信,非常的意外。记得高一刚开始的时候,一次在操场上, 看到一个女孩子摔倒了,鬼使神差地跑过去递了纸巾。 想到马上要分班了,也挺失落的。

2004/6/30
articleCard.readMore

Lisp 编程语言相关知识

利用暑假的时间,简单学习了解了下 Lisp 这一编程语言,也可以说这一类编程语言。 初识 Lisp (+ 1 7 9 11) 安装 Scheme 运行 scm 文件 mit-scheme -load yourfile.scm

2003/7/1
articleCard.readMore

过往的记忆

社交软件 小学毕业照 初中毕业照 社交软件 小学毕业照 初中毕业照 .image-gallery { overflow: auto; margin-left: -1% !important; } .image-gallery li { float: left; display: block; padding:3px 3px; width: 19%; } .image-gallery li a { text-align: center; text-decoration: none !important; color: #777; } .image-gallery li a span { display: block; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; padding: 3px 0; } .image-gallery li a img { width: 100%; display: block; box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2); }

2003/6/30
articleCard.readMore