CopilotChatをNeovimで使用するにあたり、コードブロックの選択が面倒だったので選択してくれるものを作成しました。
同じ悲しみを背負っている方に共有したいとおもいます。
-- select codeblock text local function move_cursor_to_above_codeblock() local current_line_number = vim.api.nvim_win_get_cursor(0)[1] local start_line = current_line_number -- search above for i = current_line_number, 0, -1 do local _line = vim.fn.getline(i) if string.match(_line, "^```") then start_line = i - 1 break end if i <= 1 then start_line = 1 break end end vim.api.nvim_win_set_cursor(0, {start_line, 0}) end local function select_codeblock_text(cursor_position) local current_line_number = vim.api.nvim_win_get_cursor(0)[1] local start_line = current_line_number local end_line = current_line_number local max_line = vim.api.nvim_buf_line_count(0) -- search above for i = current_line_number, 0, -1 do local _line = vim.fn.getline(i) if string.match(_line, "^```") then start_line = i + 1 break end if i == 1 then start_line = 1 break end end -- search below for i = current_line_number, max_line do local _line = vim.fn.getline(i) if string.match(_line, "^```") then end_line = i - 1 break end end if start_line <= 1 then print("no codeblock text") -- restore cursor position vim.api.nvim_win_set_cursor(0, {cursor_position, 0}) else -- select lines vim.api.nvim_win_set_cursor(0, {start_line, 0}) vim.cmd("normal! V") vim.api.nvim_win_set_cursor(0, {end_line, 0}) end end local function select_last_codeblock_text() -- save cursor position local cursor_position = vim.api.nvim_win_get_cursor(0)[1] vim.cmd("normal! G") move_cursor_to_above_codeblock() select_codeblock_text(cursor_position) end vim.keymap.set("n", "<leader>vmm", select_last_codeblock_text, {desc = "select codeblock text (last)", noremap = true})
copilot chatの提案を受けたあと、<leader> + v + m + m
で最後のコードブロックの中身が選択されます。
また、以下を追加することで<leader> + v + m + d
とすると変更前、変更後のdiffを取ることが出来ます。
注意点としては/tmpにファイルを吐き出すことと、無名レジスタを使用するところです。
※tmpなのでファイルのお掃除もせず、毎回上書きです
local function save_yanked_text(path, reg) local text = vim.fn.getreg(reg) if text == nil or text == "" then print("no text in register") return false end local file = io.open(path, "w") if file == nil then print("cannot open file") return false end file:write(text) file:close() return true end local function get_filetype_from_codeblock() vim.cmd("normal! G") move_cursor_to_above_codeblock() local current_line_number = vim.api.nvim_win_get_cursor(0)[1] local block_line = current_line_number for i = current_line_number, 0, -1 do local _line = vim.fn.getline(i) if string.match(_line, "^```") then block_line = i break end end vim.api.nvim_win_set_cursor(0, {block_line, 0}) local filetype = vim.api.nvim_get_current_line() -- extract filetype from codeblock filetype = filetype:match("^```(%w+)") return filetype end local function diff_codeblock_text() local cursor_position = vim.api.nvim_win_get_cursor(0)[1] vim.cmd("normal! G") move_cursor_to_above_codeblock() local current_line_number = vim.api.nvim_win_get_cursor(0)[1] local start_line = current_line_number for i = current_line_number, 0, -1 do local _line = vim.fn.getline(i) if string.match(_line, "^```") then start_line = i + 1 break end if i == 1 then start_line = 1 break end end if start_line <= 1 then print("no codeblock in this buffer") -- restore cursor position vim.api.nvim_win_set_cursor(0, {cursor_position, 0}) return end local target_text = "/tmp/_target_text" local copilot_text = "/tmp/_copilot_suggestion" local function save_and_check(path, register) local result = save_yanked_text(path, register) if not result then error("Failed to save yanked text to " .. path) end end local function diff_texts(target_text, copilot_text, filetype) -- open copilot_text text to new tab vim.cmd("tabnew " .. copilot_text) vim.cmd("setlocal filetype=" .. filetype) vim.cmd("vertical diffsplit " .. target_text) vim.cmd("setlocal filetype=" .. filetype) end save_and_check(target_text, '"') local filetype = get_filetype_from_codeblock() select_last_codeblock_text() vim.cmd('normal! y') save_and_check(copilot_text, '"') diff_texts(target_text, copilot_text, filetype) end vim.keymap.set("n", "<leader>vmd", diff_codeblock_text, {desc = "diff codeblock text", noremap = true}) vim.keymap.set("n", "<leader>vmc", ":tabclose<CR>", {desc = "close diff tab", noremap = true})
ついさっき思いついたものなので作りは荒いですが、参考になれば幸いです。