实现一个neovim插件nvim_rotate_chars来轮转代码
文章介绍了如果实现一个轮转代码的neovim插件,简单的策略就是a->b, b->c, …, z->a。 试想一下你的代码经过轮转以后会是怎么样,轮转一个位置、n个位置呢?配上快捷键,在别人看你代码的时候,轮转一下字符,向左转了还能支持向反方向转回来。需要实现如下功能:
- 支持指定轮转的步数、方向(即向左向右);
- 支持unicode字符,比如中文字符的改变;
- neovim配置快捷键和command命令行输入;
- 采用rust和lua编写,基于nvim-oxi,和传统rpc不同,直接调用c-api bind。
听起来还有点传统的代码加密算法的意思,旋转加密或者叫轮转机加密。
简介¶
KenForever1/nvim-rotate-chars采用nvim-oxi开发的nvim插件,用于旋转选中的文本字符。nvim-oxi提供了Neovim编辑器的Rust API绑定。 传统的插件方法比如:Vimscript or Lua是通过RPC channels和neovim进行通信,采用API绑定的方式。主要是避免了序列化开销,可以在同一个进程中直接调用api绑定回调函数,以及编程时提供了方便的api提示和函数解释。优势的话,相比lua可以使用rust丰富的crate库,还有编译时检查。
nvim-oxi provides safe and idiomatic Rust bindings to the rich API exposed by the Neovim text editor.The project is mostly intended for plugin authors, although nothing’s stopping end users from writing their Neovim configs in Rust.
如何写一个插件,首先需要创建一个lib,lib.rs内容:
use nvim_oxi::{Dictionary, Function, Object};
#[nvim_oxi::plugin]
fn calc() -> Dictionary {
let add = Function::from_fn(|(a, b): (i32, i32)| a + b);
let multiply = Function::from_fn(|(a, b): (i32, i32)| a * b);
let compute = Function::from_fn(
|(fun, a, b): (Function<(i32, i32), i32>, i32, i32)| {
fun.call((a, b)).unwrap()
},
);
Dictionary::from_iter([
("add", Object::from(add)),
("multiply", Object::from(multiply)),
("compute", Object::from(compute)),
])
}
[lib]
crate-type = ["cdylib"]
[dependencies]
nvim-oxi = "0.3"
nvim-roate-chars 插件¶
实现的功能,比如:
aaa
bbb
ccc
bbb
ccc
ddd
效果:
- 快捷键触发
- command触发
使用方法¶
使用方法1¶
通过快捷键<leader-r>
进行旋转。根据提示输入需要旋转的方向(true代表向右, false代表向左)和字符数。
例如:visual mode下将选中的行,向右旋转1个字符。
Enter direction and rotation number: true 1
使用方法2¶
通过Command输入进行旋转。在 Neovim 中进入视觉模式,选中一段文本,然后执行命令:
:RotateChars [direction] [steps]
:'<,'> RotateChars true 1
:'<,'> RotateChars false 1
实现一些细节¶
采用rayon并行高效旋转字符¶
命令参数:命令 RotateChars 接受两个可选参数: + direction:旋转方向,可以是 “left” 或 “right”,默认 “right”。 + steps:旋转的位数,默认为 1。 字符轮转逻辑:
rotate_char 函数:判断字符是否在 a-z, A-Z, 0-9 范围内,如果是进行轮转,否则保持不变。
use rayon::prelude::*;
/// 假设 rotate_char 是一个高效的旋转字符函数
#[inline]
fn rotate_char(c: u8, direction: bool, steps: u8) -> u8 {
let steps = steps % 26;
// 例如,一个简单的 Caesar cipher 实现(假设输入是 ASCII 字母)
if (b'a'..=b'z').contains(&c) {
let base = b'a';
((c - base + if direction { steps } else { 26 - steps }) % 26) + base
} else if (b'A'..=b'Z').contains(&c) {
let base = b'A';
((c - base + if direction { steps } else { 26 - steps }) % 26) + base
} else {
c
}
}
/// 高效旋转内容的函数
pub fn rotate_content(content: &[String], direction: bool, steps: u8) -> Vec<String> {
content
.par_iter() // 使用 Rayon 进行并行处理
.map(|line| {
let bytes = line.as_bytes();
let mut rotated = String::with_capacity(bytes.len());
for &c in bytes {
rotated.push(rotate_char(c, direction, steps) as char);
}
rotated
})
.collect()
}
旋转字符逻辑¶
通过调用nvim-oxi::api中的函数获取当前Buffer,以及get_lines、set_lines。
/// 插件的主要逻辑函数
fn rotate_chars(
buffer: &mut api::Buffer,
row_range: std::ops::Range<usize>,
// col_range: std::ops::Range<usize>,
direction: bool,
steps: usize,
) -> oxi::Result<()> {
// 获取选中的范围lines [start_row, end_row)
let content = buffer.get_lines(row_range.to_owned(), false)?;
let content_string_list = content
.enumerate()
.map(|(_, s)| s.to_string())
.collect::<Vec<_>>();
// let rotated_content = rotate_content(&content_string_list, direction, steps as u8);
let rotated_content = rotate_unicode_content(&content_string_list, direction, steps as u8, false);
// 替换选中的内容
buffer.set_lines(row_range, false, rotated_content)?;
Ok(())
}
neovim中lua配置实现¶
lua配置快捷键¶
function processString(input)
local boolStr, numStr = input:match("^(%S+)%s+(%S+)$")
if not boolStr or not numStr then
error("Input string does not contain exactly two parts.")
end
local boolValue
if boolStr:lower() == "true" then
boolValue = true
elseif boolStr:lower() == "false" then
boolValue = false
else
error("Invalid boolean string: " .. boolStr)
end
local numValue = tonumber(numStr)
if not numValue then
error("Invalid number string: " .. numStr)
end
return boolValue, numValue
end
vim.keymap.set('v', '<Leader>r', function()
-- 请求用户输入数字参数
vim.ui.input({ prompt = 'Enter direction and rotation number: ' }, function(input)
print("bool_val:" .. input)
local bool_val, number_arg = processString(input)
print("bool_val:" .. tostring(bool_val))
--print("bool_val:" .. number_arg)
if number_arg == nil then
-- 如果输入不是有效数字,则提示错误并退出
print("Error: Please enter a valid number.")
return
end
-- 获取选区的起始和结束行
vim.cmd([[ execute "normal! \<ESC>" ]])
local mode = vim.fn.visualmode()
local start_line = vim.fn.getpos("'<")[2] - 1
local end_line = vim.fn.getpos("'>")[2]
print("start " .. start_line .. "end " .. end_line)
-- 调用插件的函数,将输入的数字作为参数
require("nvim_rotate_chars").RotateCharsWithRange(bool_val, number_arg, start_line, end_line)
end)
end, { noremap = true, silent = true, desc = "Rotate selected characters" })
lua配置快捷键和Command¶
vim.api.nvim_create_user_command(
'RotateChars',
function(opts)
-- 检查是否提供了正确数量的参数
if #opts.fargs < 2 then
print("Usage: :RotateChars <boolean> <number>")
return
end
-- 将第一个参数解析为布尔值
local bool_arg = opts.fargs[1] == "true"
-- 尝试将第二个参数解析为数字
local num_arg = tonumber(opts.fargs[2])
if not num_arg then
print("Error: The second argument must be a number.")
return
end
-- 调用插件的函数
require("nvim_rotate_chars").RotateChars(bool_arg, num_arg)
end,
{ range = 2, nargs = "+" }
)
通过这篇文章,您学会了rust和lua配合给neovim实现一个自定义的插件,又多了一个武器技能。 感谢您的阅读!!!开源地址:KenForever1/nvim-rotate-chars。