From b4d6a8892d55d18b5e12f535a65cd8f5c6b94e43 Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Mon, 20 Jul 2026 22:21:23 -0400 Subject: [PATCH 1/2] feat: multi-line prompt submit and session history --- README.md | 27 ++++++----- lua/jumpy/prompt.lua | 104 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 116 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d55966c..9a1ee26 100644 --- a/README.md +++ b/README.md @@ -95,16 +95,23 @@ from the Claude Code child process so an inherited API key cannot switch a subsc user to API billing. Set `claude_code_command` in `setup()` if `claude` is not on `PATH`. ## Use -| Keybind | Action | -| ----------- | ----------------------------------------- | -| `j` | Open prompt, type your change, hit `` | -| `]h` / `[h` | Next / previous hunk | -| `a` | Accept hunk | -| `x` | Reject hunk | -| `A` | Accept all hunks | -| `X` | Reject all hunks | -| `r` | Reprompt the hunk under cursor | -| `q` | Review pending hunks in quickfix | +| Keybind | Action | +| ----------- | --------------------------------------------------- | +| `j` | Open prompt | +| `` | Submit prompt (insert mode); `` inserts newline | +| `` | Submit prompt (normal mode) | +| `` / `` | Cycle prompt history (session) | +| `]h` / `[h` | Next / previous hunk | +| `a` | Accept hunk | +| `x` | Reject hunk | +| `A` | Accept all hunks | +| `X` | Reject all hunks | +| `r` | Reprompt the hunk under cursor | +| `q` | Review pending hunks in quickfix | + +In the prompt float, `` starts a new line and `` sends the request. +Use `` / `` to recall earlier prompts from the current Neovim session +(including `@file` mentions as typed). In the Jumpy quickfix list, use `a` to accept or `x` to reject the selected hunk. Jumpy advances to the next pending hunk, including hunks in other files. diff --git a/lua/jumpy/prompt.lua b/lua/jumpy/prompt.lua index 9cc18d4..70dd4c2 100644 --- a/lua/jumpy/prompt.lua +++ b/lua/jumpy/prompt.lua @@ -11,8 +11,15 @@ local state = { source_buf = nil, reprompt_hunk_idx = nil, paths = nil, + history_idx = nil, + draft_lines = nil, } +-- Session-scoped prompt history (newest at the end). +local history = {} +local HISTORY_MAX = 50 + +local TITLE_HINT = " ยท Ctrl-CR submit" local mention_ns = vim.api.nvim_create_namespace("jumpy_mentions") local function index_tagged_files(tagged_files) @@ -87,6 +94,65 @@ local function highlight_mentions(buf) end end +local function push_history(text) + if vim.trim(text) == "" then + return + end + if history[#history] == text then + return + end + history[#history + 1] = text + while #history > HISTORY_MAX do + table.remove(history, 1) + end +end + +local function apply_prompt_lines(lines) + if not state.buf or not vim.api.nvim_buf_is_valid(state.buf) then + return + end + if not lines or #lines == 0 then + lines = { "" } + end + vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, lines) + if state.win and vim.api.nvim_win_is_valid(state.win) then + local last = #lines + local col = #lines[last] + vim.api.nvim_win_set_cursor(state.win, { last, col }) + end + highlight_mentions(state.buf) +end + +local function history_prev() + if #history == 0 then + return + end + if state.history_idx == nil then + state.draft_lines = vim.api.nvim_buf_get_lines(state.buf, 0, -1, false) + state.history_idx = #history + elseif state.history_idx > 1 then + state.history_idx = state.history_idx - 1 + else + return + end + apply_prompt_lines(vim.split(history[state.history_idx], "\n", { plain = true })) +end + +local function history_next() + if state.history_idx == nil then + return + end + if state.history_idx < #history then + state.history_idx = state.history_idx + 1 + apply_prompt_lines(vim.split(history[state.history_idx], "\n", { plain = true })) + return + end + state.history_idx = nil + local draft = state.draft_lines or { "" } + state.draft_lines = nil + apply_prompt_lines(draft) +end + local function create_float(title, initial_lines) local width = math.floor(vim.o.columns * 0.6) local height = 5 @@ -109,7 +175,7 @@ local function create_float(title, initial_lines) col = col, style = "minimal", border = "rounded", - title = title or " jumpy ", + title = title or (" jumpy" .. TITLE_HINT .. " "), title_pos = "center", }) @@ -130,10 +196,12 @@ function M.open() state.source_buf = vim.api.nvim_get_current_buf() state.reprompt_hunk_idx = nil + state.history_idx = nil + state.draft_lines = nil state.paths = path.list_files() state.visual_selection = is_scoped and utils.get_visual_selection(state.source_buf) or nil - local title = is_scoped and " jumpy (scoped) " or " jumpy " + local title = is_scoped and (" jumpy (scoped)" .. TITLE_HINT .. " ") or (" jumpy" .. TITLE_HINT .. " ") state.buf, state.win = create_float(title) @@ -223,26 +291,48 @@ function M.reprompt() state.source_buf = vim.api.nvim_get_current_buf() state.reprompt_hunk_idx = hunk_idx state.visual_selection = nil + state.history_idx = nil + state.draft_lines = nil state.paths = path.list_files() - state.buf, state.win = create_float(" jumpy: reprompt this hunk ") + state.buf, state.win = create_float(" jumpy: reprompt" .. TITLE_HINT .. " ") M._set_submit_keymap() M._setup_completions(state.buf) end function M._set_submit_keymap() + -- Enter inserts a newline; Ctrl-CR submits. Completion menu still confirms with Enter. vim.keymap.set("i", "", function() if vim.fn.pumvisible() == 1 then return "" end - vim.schedule(M._submit) - return "" + return "" end, { buffer = state.buf, silent = true, expr = true }) + vim.keymap.set("i", "", function() + vim.schedule(M._submit) + end, { buffer = state.buf, silent = true }) + vim.keymap.set("n", "", function() M._submit() end, { buffer = state.buf, silent = true }) + vim.keymap.set({ "i", "n" }, "", function() + if vim.fn.mode():sub(1, 1) == "i" and vim.fn.pumvisible() == 1 then + return "" + end + history_prev() + return "" + end, { buffer = state.buf, silent = true, expr = true }) + + vim.keymap.set({ "i", "n" }, "", function() + if vim.fn.mode():sub(1, 1) == "i" and vim.fn.pumvisible() == 1 then + return "" + end + history_next() + return "" + end, { buffer = state.buf, silent = true, expr = true }) + vim.keymap.set("n", "", function() M._close() end, { buffer = state.buf, silent = true }) @@ -309,6 +399,8 @@ function M._submit() end end + push_history(prompt_text) + local request_opts = { targets = targets, label = source_rel } local function send_request(symbols) @@ -503,6 +595,8 @@ function M._close() state.win = nil state.buf = nil state.paths = nil + state.history_idx = nil + state.draft_lines = nil vim.cmd("stopinsert") end From e02966d3165796c61bf7be95575823661671ae4d Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Mon, 20 Jul 2026 22:21:23 -0400 Subject: [PATCH 2/2] fix: restore Claude Code subscription auth by dropping --bare --- lua/jumpy/llm.lua | 9 ++++++++- tests/llm_spec.lua | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lua/jumpy/llm.lua b/lua/jumpy/llm.lua index eb20f5b..a4adc3a 100644 --- a/lua/jumpy/llm.lua +++ b/lua/jumpy/llm.lua @@ -140,6 +140,11 @@ function M._build_claude_code_cmd(messages, config) end end + -- Note: we deliberately do NOT pass --bare. In current Claude Code, --bare + -- forces Anthropic auth to ANTHROPIC_API_KEY/apiKeyHelper only and never + -- reads the OAuth/keychain login, which breaks Pro/Max subscription users + -- (the CLI reports "Not logged in"). We still keep the request tool-free, + -- MCP-restricted, and session-less via the explicit flags below. local cmd = { config.claude_code_command or "claude", "-p", @@ -147,7 +152,6 @@ function M._build_claude_code_cmd(messages, config) "json", "--tools", "", - "--bare", "--strict-mcp-config", "--no-session-persistence", "--system-prompt", @@ -271,6 +275,9 @@ local function make_claude_code_request(messages, callback, opts) finish() loading.error("failed to start Claude Code CLI") else + -- Jumpy never writes to Claude Code's stdin; close it so the CLI does + -- not wait ~3s for piped input ("no stdin data received in 3s"). + pcall(vim.fn.chanclose, request_jid, "stdin") requests.set_job(request_id, request_jid) end end, diff --git a/tests/llm_spec.lua b/tests/llm_spec.lua index 6b891be..496cd4b 100644 --- a/tests/llm_spec.lua +++ b/tests/llm_spec.lua @@ -19,7 +19,6 @@ describe("llm Claude Code command", function() "json", "--tools", "", - "--bare", "--strict-mcp-config", "--no-session-persistence", "--system-prompt", @@ -45,7 +44,6 @@ describe("llm Claude Code command", function() "json", "--tools", "", - "--bare", "--strict-mcp-config", "--no-session-persistence", "--system-prompt",