irongit

My nvim configuration

feat(blink): toggle blink on and off

huncholanehuncholaneauthored
parent af7a888commit 670047451f9b389c0eafc82a8bf3c2a47e4121c4Browse files

6 files changed, +198 -2

+16-1claude-nvim/lua/claude/ui.lua
@@ -541,19 +541,34 @@ ensure_panel_for_current_tab = function()
541541 return p
542542 end
543543
544+local function buf_display_path(buf)
545+ local fname = vim.api.nvim_buf_get_name(buf)
546+ if fname == "" then return "[No Name]" end
547+ return vim.fn.fnamemodify(fname, ":.")
548+end
549+
544550 function M.prompt_visual_chat()
545551 local origin_buf = vim.api.nvim_get_current_buf()
546552 local origin_win = vim.api.nvim_get_current_win()
547553 if vim.fn.mode():match("^[vV\22]") then
548554 vim.cmd("noautocmd normal! \27")
549555 end
556+ local s = vim.fn.getpos("'<")
557+ local e = vim.fn.getpos("'>")
550558 local sel = capture_visual_selection(origin_buf)
551559 if sel == "" then
552560 vim.notify("claude: empty selection", vim.log.levels.WARN)
553561 return
554562 end
555563 local ft = vim.bo[origin_buf].filetype or ""
556- local context = string.format("```%s\n%s\n```", ft, sel)
564+ local path = buf_display_path(origin_buf)
565+ local sl, sc = s[2], s[3]
566+ local el, ec = e[2], e[3]
567+ -- For linewise selections '> column is huge (MAXCOL); clamp to line len.
568+ local last_line = vim.api.nvim_buf_get_lines(origin_buf, el - 1, el, false)[1] or ""
569+ if ec > #last_line then ec = math.max(1, #last_line) end
570+ local range = string.format("%s:%d:%d-%d:%d", path, sl, sc, el, ec)
571+ local context = string.format("Selection from `%s`:\n```%s\n%s\n```", range, ft, sel)
557572
558573 ensure_panel_for_current_tab()
559574 M.open_input({ return_to = origin_win, context = context })
+157-0claude-nvim/tests/visual_context_spec.lua
@@ -0,0 +1,157 @@
1+-- <leader>p (visual mode -> ui.prompt_visual_chat) must include
2+-- file path + line:col-line:col range alongside the selected text in the
3+-- context sent to claude.
4+
5+local function reload()
6+ package.loaded["claude.ui"] = nil
7+ package.loaded["claude.runner"] = nil
8+ package.loaded["claude.store"] = nil
9+ return require("claude.ui"), require("claude.runner"), require("claude.store")
10+end
11+
12+describe("visual selection context", function()
13+ local ui, runner
14+ local tmp_cwd, orig_cwd, tmp_data, orig_stdpath
15+ local orig_open_input
16+
17+ before_each(function()
18+ orig_cwd = vim.fn.getcwd()
19+ tmp_cwd = vim.fn.tempname()
20+ vim.fn.mkdir(tmp_cwd, "p")
21+ vim.cmd("cd " .. vim.fn.fnameescape(tmp_cwd))
22+
23+ tmp_data = vim.fn.tempname()
24+ vim.fn.mkdir(tmp_data, "p")
25+ orig_stdpath = vim.fn.stdpath
26+ vim.fn.stdpath = function(k) ---@diagnostic disable-line: duplicate-set-field
27+ if k == "data" then return tmp_data end
28+ return orig_stdpath(k)
29+ end
30+
31+ while vim.fn.tabpagenr("$") > 1 do vim.cmd("tabclose") end
32+
33+ ui, runner = reload()
34+ ui.setup({ width = 80, skip_permissions = false })
35+ runner.setup({ skip_permissions = false, debug = false })
36+
37+ orig_open_input = ui.open_input
38+ end)
39+
40+ after_each(function()
41+ ui.open_input = orig_open_input
42+ while vim.fn.tabpagenr("$") > 1 do vim.cmd("tabclose") end
43+ vim.fn.stdpath = orig_stdpath
44+ vim.cmd("cd " .. vim.fn.fnameescape(orig_cwd))
45+ vim.fn.delete(tmp_cwd, "rf")
46+ vim.fn.delete(tmp_data, "rf")
47+ end)
48+
49+ local function setup_buffer(rel_name, lines, ft)
50+ local fpath = tmp_cwd .. "/" .. rel_name
51+ vim.cmd("edit " .. vim.fn.fnameescape(fpath))
52+ local buf = vim.api.nvim_get_current_buf()
53+ vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
54+ vim.bo[buf].filetype = ft
55+ return buf
56+ end
57+
58+ it("linewise selection: context has filename, line range, ft fence, and selected lines", function()
59+ setup_buffer("foo.lua", {
60+ "local M = {}",
61+ "function M.hello()",
62+ " return 'hi'",
63+ "end",
64+ "return M",
65+ }, "lua")
66+
67+ -- Visual line select lines 2..4
68+ vim.cmd("normal! 2GVjj\27")
69+
70+ local captured
71+ ui.open_input = function(opts) captured = opts end ---@diagnostic disable-line: duplicate-set-field
72+
73+ ui.prompt_visual_chat()
74+
75+ assert.is_truthy(captured, "open_input should have been called")
76+ local ctx = captured.context or ""
77+ assert.matches("foo%.lua:2:1%-4:", ctx, "context should contain filename and line range")
78+ assert.matches("```lua", ctx, "should fence with filetype")
79+ assert.matches("function M%.hello%(%)", ctx, "selected text should appear")
80+ assert.matches("return 'hi'", ctx)
81+ assert.matches("end", ctx)
82+ end)
83+
84+ it("charwise selection: column boundaries reflect actual selection", function()
85+ setup_buffer("bar.lua", {
86+ "abcdef",
87+ "ghijkl",
88+ "mnopqr",
89+ }, "lua")
90+
91+ -- Char-visual from line 1 col 3 ('c') to line 2 col 4 ('j')
92+ vim.cmd("normal! gg")
93+ vim.cmd("normal! 0lvj0lll\27")
94+ -- Set marks explicitly to be deterministic
95+ vim.fn.setpos("'<", { 0, 1, 3, 0 })
96+ vim.fn.setpos("'>", { 0, 2, 4, 0 })
97+
98+ -- Force visualmode to charwise so capture_visual_selection picks the
99+ -- column-aware path, not the linewise fallback.
100+ local orig_vm = vim.fn.visualmode
101+ vim.fn.visualmode = function() return "v" end ---@diagnostic disable-line: duplicate-set-field
102+
103+ local captured
104+ ui.open_input = function(opts) captured = opts end ---@diagnostic disable-line: duplicate-set-field
105+ ui.prompt_visual_chat()
106+ vim.fn.visualmode = orig_vm
107+
108+ assert.is_truthy(captured)
109+ local ctx = captured.context or ""
110+ assert.matches("bar%.lua:1:3%-2:4", ctx, "context should reflect 1:3-2:4 range")
111+ -- selection text spans "cdef" + newline + "ghij"
112+ assert.matches("cdef\nghij", ctx, "selected chars should appear in fence")
113+ end)
114+
115+ it("buffer with no name falls back to [No Name]", function()
116+ -- Don't create a backing file: just a scratch buf.
117+ vim.cmd("enew")
118+ local buf = vim.api.nvim_get_current_buf()
119+ vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "alpha", "beta", "gamma" })
120+ vim.bo[buf].filetype = "text"
121+
122+ vim.cmd("normal! ggVj\27")
123+
124+ local captured
125+ ui.open_input = function(opts) captured = opts end ---@diagnostic disable-line: duplicate-set-field
126+ ui.prompt_visual_chat()
127+
128+ assert.is_truthy(captured)
129+ assert.matches("%[No Name%]:1:1%-2:", captured.context or "")
130+ end)
131+
132+ it("send() concatenates context + user prompt and forwards to runner.send", function()
133+ setup_buffer("baz.lua", { "x = 1", "y = 2", "z = 3" }, "lua")
134+ vim.cmd("normal! ggVj\27")
135+
136+ -- Use real open_input but stub runner.send so we can inspect final_text.
137+ local sent
138+ runner.send = function(_sid, text, _opts) ---@diagnostic disable-line: duplicate-set-field
139+ sent = text
140+ return true
141+ end
142+
143+ ui.prompt_visual_chat()
144+ -- The floating prompt window is now open and is current. Type into it.
145+ vim.api.nvim_put({ "What does this do?" }, "c", true, true)
146+ -- Trigger <C-s> mapping to send.
147+ vim.cmd("stopinsert")
148+ vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-s>", true, false, true), "x", false)
149+
150+ assert.is_truthy(sent, "runner.send should have been invoked")
151+ assert.matches("baz%.lua:1:1%-2:", sent, "final text should contain file:line range")
152+ assert.matches("```lua", sent)
153+ assert.matches("x = 1", sent)
154+ assert.matches("y = 2", sent)
155+ assert.matches("What does this do%?", sent, "user-typed text should follow context")
156+ end)
157+end)
+1-0lazy-lock.json
@@ -9,6 +9,7 @@
99 "gitsigns.nvim": { "branch": "main", "commit": "20ad4419564d6e22b189f6738116b38871082332" },
1010 "grug-far.nvim": { "branch": "main", "commit": "b58b2d65863f4ebad88b10a1ddd519e5380466e0" },
1111 "hydra.nvim": { "branch": "main", "commit": "8c4a9f621ec7cdc30411a1f3b6d5eebb12b469dc" },
12+ "image.nvim": { "branch": "master", "commit": "da2be65c153ba15a14a342b05591652a6df70d58" },
1213 "kulala.nvim": { "branch": "main", "commit": "c328aeb219c4b77106917dd2698c90ea9657281b" },
1314 "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
1415 "leetcode.nvim": { "branch": "master", "commit": "fdd3f91800b3983e27bc9fcfb99cfa7293d7f11a" },
+7-1lua/keymaps.lua
@@ -22,9 +22,14 @@ easymap("n", "<leader>,", ":Telescope current_folder<cr>", "Search Current Folde
2222 easymap("n", "<leader>m", ':exe "resize ".float2nr(&lines*0.8)<cr>', "80% Window")
2323 -- easymap("n", "<leader>s", ":Scratch<cr>", "Scratch")
2424 easymap("n", "<leader>.", ":Telescope all_files<cr>", "All Files")
25-easymap("n", "<leader>i", function()
25+easymap("n", "gri", function()
2626 vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ 0 }), { 0 })
2727 end, "Toggle Inlay Hints")
28+easymap("n", "<leader>lt", function()
29+ local on = vim.g.blink_cmp_enabled ~= false
30+ vim.g.blink_cmp_enabled = not on
31+ vim.notify("blink.cmp " .. (vim.g.blink_cmp_enabled and "on" or "off"))
32+end, "Toggle Completion")
2833 for i = 1, 9 do
2934 easymap("n", "<leader>" .. i, i .. "gt", "Tab " .. i)
3035 end
@@ -110,6 +115,7 @@ vim.keymap.set("n", "<C-S-Up>", ":resize +2<CR>")
110115 -- -Gotos
111116 -- ########################################################################## --
112117 easymap("n", "gd", vim.lsp.buf.definition, "Goto Definition")
118+easymap("n", "gD", vim.lsp.buf.type_definition, "Goto Type Definition")
113119 easymap({ "n", "v" }, "gy", '"+y', "System Clipboard Copy")
114120 easymap({ "n", "v" }, "gp", '"+p', "System Clipboard Paste")
115121 easymap("n", "gs", "<cmd>EditCurrentFiletype<cr>", "Filetype Settings")
+14-0lua/plugins.lua
@@ -5,6 +5,20 @@ local plugins = {
55 { "ellisonleao/dotenv.nvim", opts = {} },
66 { "folke/tokyonight.nvim" },
77 { "mg979/vim-visual-multi" },
8+ {
9+ "3rd/image.nvim",
10+ opts = {
11+ backend = "sixel",
12+ processor = "magick_cli",
13+ integrations = {
14+ markdown = {
15+ enabled = true,
16+ only_render_image_at_cursor = true,
17+ only_render_image_at_cursor_mode = "popup",
18+ },
19+ },
20+ },
21+ },
822 {
923 "mason-org/mason.nvim",
1024 opts = {},
+3-0lua/plugins/blink.lua
@@ -48,6 +48,9 @@ return {
4848 },
4949 build = "cargo build --release",
5050 opts = {
51+ enabled = function()
52+ return vim.g.blink_cmp_enabled ~= false
53+ end,
5154 cmdline = { enabled = false },
5255 sources = {
5356 default = { "lsp", "buffer", "snippets", "path" },