在 Neovim 中使用 Lua 创建条件键映射

问题描述 投票:0回答:1

我正在尝试将使用 Ctrl + 箭头键上下移动视觉块的特定 vimscript 代码翻译为 lua。

这是我的 .vimrc 中的 Vimscript 代码:

vnoremap ^[[1;5A  :m '<-2<CR>gv=gv
vnoremap ^[[1;5B  :m '>+1<CR>gv=gv

这是我的 init.lua 中的 Lua 代码:

-- Global Variable 
local opts = { noremap = true, silent = true }
-- Key Mappings
vim.api.nvim_set_keymap('v', '<C-Up>', ':m \'<-2<CR>gv=gv', opts)
vim.api.nvim_set_keymap('v', '<C-Down>', ':m \'>+1<CR>gv=gv', opts)

但是,问题是,当我到达文件的顶部或底部时,我得到一个

E16: Invalid range error
并且视觉选择被取消选择。为了在 Vim 中处理这个问题,我在 .vimrc 中添加了一个检查,如下所示:

vnoremap <expr> ^[[1;5A (line("'<") == 1 ? "" : ":m '<-2<CR>gv=gv")
vnoremap <expr> ^[[1;5B (line("'>") == line("$") ? "" : ":m '>+1<CR>gv=gv")

将其翻译为 lua,我想出了以下内容:

-- Global Variable 
local opts = { noremap = true, silent = true }

-- Custom function to move visual block up
function _G.move_visual_block_up()
  if vim.fn.line("'<") > 1 then
    vim.cmd(":m '<-2<CR>gv=gv")
  end
end

-- Custom function to move visual block down
function _G.move_visual_block_down()
  if vim.fn.line("'>") < vim.fn.line("$") then
    vim.cmd(":m '>+1<CR>gv=gv")
  end
end

-- Key Mappings
vim.api.nvim_set_keymap('v', '<C-Up>',   '<Cmd>lua _G.move_visual_block_up()<CR>', opts)
vim.api.nvim_set_keymap('v', '<C-Down>', '<Cmd>lua _G.move_visual_block_down()<CR>', opts)

但是,按 ctrl + up 没有效果;另一方面,按 ctrl + down 会导致错误......

E5108: Error executing lua vim/_editor.lua:0: nvim_exec2(): Vim(move):E20: Mark
not set
stack traceback:
        [C]: in function 'nvim_exec2'
        vim/_editor.lua: in function 'cmd'
        /etc/nvim/init.lua:206: in function 'move_visual_block_down'
        [string ":lua"]:1: in main chunk

这个问题对我来说似乎不清楚……这个标记到底是什么?我尝试查找错误,但没有任何可用的线索。

PS:

虽然这个问题看起来很相似,但它无助于解决我的问题。

lua neovim
1个回答
0
投票

这里发生了很多事情,这确实比应有的更难......在你的原始地图中,

:m '<-2<CR>gv=gv
,vim 基本上会模拟所有这些类似于宏的击键。您可以自己按所有这些并获得相同的结果。您会特别注意到的是,当您有视觉选择并按
:
时,您将自动插入
'<,'>
,并且您将失去活动的视觉选择。但
'<,'>
是一个范围,作用于从
'<
标记
'>
标记。 (这些是错误消息中引用的“标记”,我们稍后会介绍)。来自
:h '<
:h '>

'<  <                   To the first line or character of the last selected
                        Visual area in the current buffer.  For block mode it
                        may also be the last character in the first line (to
                        be able to define the block).

                                                        '> `>
'>  >                   To the last line or character of the last selected
                        Visual area in the current buffer.  For block mode it
                        may also be the first character of the last line (to
                        be able to define the block).  Note that 'selection'
                        applies, the position may be just after the Visual
                        area.

因此,我们不再使视觉选择处于活动状态,但这正是允许该范围起作用的原因,因为它指的是之前处于活动状态的视觉选择。由于我们刚刚选择的区域现在被视为之前的区域,因此它的目标是正确的部分。因此,所需区域将移动到该区域结束线加一。

gv=gv
将激活之前的视觉选择,对其进行格式化,然后再次激活该选择。

现在我们已经解释了原始版本的工作原理,我们可以继续修复您的 lua 版本。

问题的一部分是,键盘映射的

vim.cmd()
<Cmd>
部分都不会像常规
:
那样模拟
vnoremap
击键,而只是执行命令。这与您不必手动输入
<CR>
的原因相同。因此,我们不会像之前那样将该范围自动添加到
m
命令中,这意味着默认情况下它只会作用于当前行。这也意味着在执行命令之前视觉选择不会被停用,因此如果这是您在缓冲区中所做的第一个选择,则不会有“上一个”选择,因为您仍然只有当前的选择。我强烈怀疑这就是为什么您会收到“标记未设置”错误的原因,因为
'<
/
'>
还没有之前的视觉选择可供挂钩。

因此,要使用 lua 命令复制此行为,我们可以正确模拟

:
击键,通过退出视觉模式并手动使用
'<
'>
标记或仅获取视觉线条来解决此问题区域,同时它仍然被选中并使用这些区域。我将向您展示如何成功使用此列表中的第二种方法,如果您想修补并修复它,我还将包括我尝试使用第三种方法工作的代码。

模拟转义按键:
我们将编写一个辅助函数来模拟用户按下退出键以离开可视模式。

local function simulate_escape()
  vim.api.nvim_feedkeys(
    vim.api.nvim_replace_termcodes('<Esc>', true, false, true),
    -- It is essential that you include "x" in this string; without it, the '<
    -- and '> marks won't get updated correctly after <Esc> is simulated.
    'nx',
    false
  )
end

接下来,我们来实现实际的搬家:

local function move_linewise_visual_area(move_up)
  simulate_escape()

  if move_up and vim.fn.line("'<") > 1 then
    vim.cmd("'<,'>m '<-2")
    vim.cmd("normal gv=gv")
  elseif not move_up and vim.fn.line("'>") < vim.fn.line("$") then
    vim.cmd("'<,'>m '>+1")
    vim.cmd("normal gv=gv")
  else
    vim.cmd('normal gv=gv')
  end
end

这或多或少只是复制原始键盘映射的行为,但也会检查文件的开头和结尾。

现在,进入键盘映射。

vim.keymap.set
应该是 lua 键盘映射的首选函数。它提供了比
vim.api.nvim_set_keymap
更好的体验,因为它内置了一些额外功能。一旦我们做出了改变,那么你就可以用实际的 lua 函数本身替换该字符串,而不是在键盘映射中调用
'<Cmd>...'

那么你的键盘映射就会变成

vim.keymap.set('v', '<C-Up>', function() move_linewise_visual_area(true) end, opts)
vim.keymap.set('v', '<C-Down>', function() move_linewise_visual_area(false) end, opts)

请注意,我们必须定义一个内联函数来调用该函数,因为我们向内部函数传递了一个参数。如果不使用函数定义包装它,我们只需在 init.lua 读取它时调用它一次,它就会向实际的键盘映射函数返回一个

nil
。如果我们不需要内部命令的参数,我们可以直接传递它不带括号以达到相同的效果。

这是最终结果,我尝试的另一种方法仅供参考。

local opts = { noremap = true, silent = true }

local function simulate_escape()
  vim.api.nvim_feedkeys(
    vim.api.nvim_replace_termcodes('<Esc>', true, false, true),
    -- It is essential that you include "x" in this string; without it, the '<
    -- and '> marks won't get updated correctly after <Esc> is simulated.
    'nx',
    false
  )
end

local function move_linewise_visual_area(move_up)
  simulate_escape()

  if move_up and vim.fn.line("'<") > 1 then
    vim.cmd("'<,'>m '<-2")
    vim.cmd("normal gv=gv")
  elseif not move_up and vim.fn.line("'>") < vim.fn.line("$") then
    vim.cmd("'<,'>m '>+1")
    vim.cmd("normal gv=gv")
  else
    vim.cmd('normal gv=gv')
  end
end

-- Key Mappings
vim.keymap.set('v', '<C-Up>', function() move_linewise_visual_area(true) end, opts)
vim.keymap.set('v', '<C-Down>', function() move_linewise_visual_area(false) end, opts)

-- Other attempt

local function get_visual_area_line_range()
  -- Line that cursor is on (one end of the visual area)
  local cursor_line = vim.fn.line(".")

  -- Line opposite the cursor's end of the visual area
  local opposite_line = vim.fn.line("v")

  -- Return the start and end lines of the visual area in that order
  if cursor_line < opposite_line then
    return cursor_line, opposite_line
  else
    return opposite_line, cursor_line
  end
end

local function move_linewise_visual_area_2(move_up)
  local start_line, end_line = get_visual_area_line_range()
  if move_up and start_line > 1 then

    vim.cmd(start_line .. "," .. end_line .. "m " .. start_line - 2)
    vim.cmd("normal =gv")
  elseif not move_up and end_line < vim.fn.line("$") then
    vim.cmd(start_line .. "," .. end_line .. "m " .. end_line + 1)
    vim.cmd("normal =gv")
  end
end


-- vim.keymap.set('v', '<C-Up>', function() move_linewise_visual_area_2(true) end, opts)
-- vim.keymap.set('v', '<C-Down>', function() move_linewise_visual_area_2(false) end, opts)
© www.soinside.com 2019 - 2024. All rights reserved.