Skip to content

Commit 2361ca5

Browse files
committed
refactor: move impls out of opencode.lua
and expose `start` and `show`
1 parent c7594f8 commit 2361ca5

File tree

6 files changed

+190
-188
lines changed

6 files changed

+190
-188
lines changed

lua/opencode.lua

Lines changed: 8 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,182 +1,14 @@
1+
---`opencode.nvim` public API.
12
local M = {}
23

3-
---@param callback fun(port: number)
4-
local function get_port(callback)
5-
require("opencode.cli.server").get_port(function(ok, result)
6-
if ok then
7-
callback(result)
8-
else
9-
vim.notify(result, vim.log.levels.ERROR, { title = "opencode" })
10-
end
11-
end)
12-
end
4+
M.ask = require("opencode.ui.ask").ask
5+
M.select = require("opencode.ui.select").select
136

14-
---@class opencode.prompt.Opts
15-
---@field clear? boolean Clear the TUI input before.
16-
---@field submit? boolean Submit the TUI input after.
17-
---@field context? opencode.Context The context the prompt was written or selected in, if any.
7+
M.prompt = require("opencode.api.prompt").prompt
8+
M.command = require("opencode.api.command").command
189

19-
---Prompt `opencode`.
20-
---
21-
---1. Resolves `prompt` if it's a prompt name from `opts.prompts`.
22-
---2. Clears the TUI input if `opts.clear`.
23-
---3. Injects `opts.contexts` into `prompt`.
24-
---4. Appends `prompt` to the TUI input.
25-
---5. Submits the TUI input if `opts.submit`.
26-
---6. Listens for Server-Sent-Events to forward as `OpencodeEvent` autocmd.
27-
---7. Calls `callback` if provided.
28-
---
29-
---@param prompt string The prompt to send to `opencode`, or a prompt name from `opts.prompts`.
30-
---@param opts? opencode.prompt.Opts
31-
---@param callback? fun()
32-
function M.prompt(prompt, opts, callback)
33-
local referenced_prompt = require("opencode.config").opts.prompts[prompt]
34-
prompt = referenced_prompt and referenced_prompt.prompt or prompt
35-
opts = {
36-
clear = opts and opts.clear or false,
37-
submit = opts and opts.submit or false,
38-
context = opts and opts.context or require("opencode.context").new(),
39-
}
40-
41-
get_port(function(port)
42-
require("opencode.provider").show()
43-
44-
require("opencode.util").chain({
45-
function(next)
46-
if opts.clear then
47-
require("opencode.cli.client").tui_clear_prompt(port, next)
48-
else
49-
next()
50-
end
51-
end,
52-
function(next)
53-
local rendered = opts.context:render(prompt)
54-
local plaintext = opts.context.plaintext(rendered.output)
55-
require("opencode.cli.client").tui_append_prompt(plaintext, port, next)
56-
end,
57-
function(next)
58-
if opts.submit then
59-
-- WARNING: If user never prompts opencode via the plugin, we'll never receive SSEs.
60-
-- Could register in `/plugin` and even periodically check, but is it worth the complexity and performance hit?
61-
require("opencode.cli.client").listen_to_sse(port, function(response)
62-
vim.api.nvim_exec_autocmds("User", {
63-
pattern = "OpencodeEvent",
64-
data = {
65-
event = response,
66-
port = port,
67-
},
68-
})
69-
end)
70-
71-
require("opencode.cli.client").tui_submit_prompt(port, next)
72-
else
73-
next()
74-
end
75-
end,
76-
function()
77-
if callback then
78-
callback()
79-
end
80-
end,
81-
})
82-
end)
83-
end
84-
85-
---@alias opencode.Command
86-
---| 'session_new'
87-
---| 'session_share'
88-
---| 'session_interrupt'
89-
---| 'session_compact'
90-
---| 'messages_page_up'
91-
---| 'messages_page_down'
92-
---| 'messages_half_page_up'
93-
---| 'messages_half_page_down'
94-
---| 'messages_first'
95-
---| 'messages_last'
96-
---| 'messages_copy'
97-
---| 'messages_undo'
98-
---| 'messages_redo'
99-
---| 'input_clear'
100-
---| 'agent_cycle'
101-
102-
---Send a [command](https://opencode.ai/docs/keybinds) to `opencode`.
103-
---
104-
---@param command opencode.Command|string The command to send to `opencode`.
105-
---@param callback fun(response: table)|nil
106-
function M.command(command, callback)
107-
get_port(function(port)
108-
-- No need to register SSE here - commands don't trigger any.
109-
-- (except maybe the `input_*` commands? but no reason for user to use those).
110-
111-
require("opencode.provider").show()
112-
113-
---@cast command opencode.Command|string
114-
require("opencode.cli.client").tui_execute_command(command, port, callback)
115-
end)
116-
end
117-
118-
---Input a prompt to send to `opencode`.
119-
---Press the up arrow to browse recent prompts.
120-
---
121-
--- - Highlights `opts.contexts` placeholders.
122-
--- - Completes `opts.contexts` placeholders.
123-
--- - Press `<Tab>` to trigger built-in completion.
124-
--- - When using `blink.cmp` and `snacks.input`, registers `opts.auto_register_cmp_sources`.
125-
---
126-
---@param default? string Text to prefill the input with.
127-
---@param opts? opencode.prompt.Opts Options for `prompt()`.
128-
function M.ask(default, opts)
129-
opts = opts or {}
130-
opts.context = opts.context or require("opencode.context").new()
131-
132-
require("opencode.ui.ask").input(default, opts.context, function(value)
133-
if value and value ~= "" then
134-
M.prompt(value, opts)
135-
end
136-
end)
137-
end
138-
139-
---Select a prompt, command, or provider function.
140-
---Includes previews when using `snacks.picker`.
141-
---@param opts? opencode.select.Opts Control what's shown in the select menu.
142-
function M.select(opts)
143-
local context = require("opencode.context").new()
144-
opts = opts or {
145-
prompts = true,
146-
commands = true,
147-
provider = true,
148-
}
149-
150-
require("opencode.ui.select").select(opts, context, function(choice)
151-
if not choice then
152-
return
153-
elseif choice.__type == "prompt" then
154-
---@type opencode.Prompt
155-
local prompt = require("opencode.config").opts.prompts[choice.name]
156-
prompt.context = context
157-
if prompt.ask then
158-
require("opencode").ask(prompt.prompt, prompt)
159-
else
160-
require("opencode").prompt(prompt.prompt, prompt)
161-
end
162-
elseif choice.__type == "command" then
163-
require("opencode").command(choice.name)
164-
elseif choice.__type == "provider" then
165-
local provider = require("opencode.provider")
166-
if choice.name == "toggle" then
167-
provider.toggle()
168-
elseif choice.name == "start" then
169-
provider.start()
170-
elseif choice.name == "show" then
171-
provider.show()
172-
end
173-
end
174-
end)
175-
end
176-
177-
---Toggle `opencode` via `opts.provider`.
178-
function M.toggle()
179-
require("opencode.provider").toggle()
180-
end
10+
M.toggle = require("opencode.provider").toggle
11+
M.start = require("opencode.provider").start
12+
M.show = require("opencode.provider").show
18113

18214
return M

lua/opencode/api/command.lua

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
local M = {}
2+
3+
---@alias opencode.Command
4+
---| 'session_new'
5+
---| 'session_share'
6+
---| 'session_interrupt'
7+
---| 'session_compact'
8+
---| 'messages_page_up'
9+
---| 'messages_page_down'
10+
---| 'messages_half_page_up'
11+
---| 'messages_half_page_down'
12+
---| 'messages_first'
13+
---| 'messages_last'
14+
---| 'messages_copy'
15+
---| 'messages_undo'
16+
---| 'messages_redo'
17+
---| 'input_clear'
18+
---| 'agent_cycle'
19+
20+
---Send a [command](https://opencode.ai/docs/keybinds) to `opencode`.
21+
---
22+
---@param command opencode.Command|string The command to send to `opencode`.
23+
---@param callback fun(response: table)|nil
24+
function M.command(command, callback)
25+
require("opencode.cli.server").get_port(function(ok, port)
26+
if not ok then
27+
vim.notify(port, vim.log.levels.ERROR, { title = "opencode" })
28+
return
29+
end
30+
31+
-- No need to register SSE here - commands don't trigger any.
32+
-- (except maybe the `input_*` commands? but no reason for user to use those).
33+
34+
require("opencode.provider").show()
35+
36+
require("opencode.cli.client").tui_execute_command(command, port, callback)
37+
end)
38+
end
39+
40+
return M

lua/opencode/api/prompt.lua

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
local M = {}
2+
3+
---@class opencode.prompt.Opts
4+
---@field clear? boolean Clear the TUI input before.
5+
---@field submit? boolean Submit the TUI input after.
6+
---@field context? opencode.Context The context the prompt was written or selected in, if any.
7+
8+
---Prompt `opencode`.
9+
---
10+
---1. Resolves `prompt` if it's a prompt name from `opts.prompts`.
11+
---2. Clears the TUI input if `opts.clear`.
12+
---3. Injects `opts.contexts` into `prompt`.
13+
---4. Appends `prompt` to the TUI input.
14+
---5. Submits the TUI input if `opts.submit`.
15+
---6. Listens for Server-Sent-Events to forward as `OpencodeEvent` autocmd.
16+
---7. Calls `callback` if provided.
17+
---
18+
---@param prompt string The prompt to send to `opencode`, or a prompt name from `opts.prompts`.
19+
---@param opts? opencode.prompt.Opts
20+
---@param callback? fun()
21+
function M.prompt(prompt, opts, callback)
22+
local referenced_prompt = require("opencode.config").opts.prompts[prompt]
23+
prompt = referenced_prompt and referenced_prompt.prompt or prompt
24+
opts = {
25+
clear = opts and opts.clear or false,
26+
submit = opts and opts.submit or false,
27+
context = opts and opts.context or require("opencode.context").new(),
28+
}
29+
30+
require("opencode.cli.server").get_port(function(ok, port)
31+
if not ok then
32+
vim.notify(port, vim.log.levels.ERROR, { title = "opencode" })
33+
return
34+
end
35+
36+
require("opencode.provider").show()
37+
38+
require("opencode.util").chain({
39+
function(next)
40+
if opts.clear then
41+
require("opencode.cli.client").tui_clear_prompt(port, next)
42+
else
43+
next()
44+
end
45+
end,
46+
function(next)
47+
local rendered = opts.context:render(prompt)
48+
local plaintext = opts.context.plaintext(rendered.output)
49+
require("opencode.cli.client").tui_append_prompt(plaintext, port, next)
50+
end,
51+
function(next)
52+
if opts.submit then
53+
-- WARNING: If user never prompts opencode via the plugin, we'll never receive SSEs.
54+
-- Could register in `/plugin` and even periodically check, but is it worth the complexity and performance hit?
55+
require("opencode.cli.client").listen_to_sse(port, function(response)
56+
vim.api.nvim_exec_autocmds("User", {
57+
pattern = "OpencodeEvent",
58+
data = {
59+
event = response,
60+
port = port,
61+
},
62+
})
63+
end)
64+
65+
require("opencode.cli.client").tui_submit_prompt(port, next)
66+
else
67+
next()
68+
end
69+
end,
70+
function()
71+
if callback then
72+
callback()
73+
end
74+
end,
75+
})
76+
end)
77+
end
78+
79+
return M

lua/opencode/provider.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ end
5454

5555
local started = false
5656

57+
---Toggle `opencode` via `opts.provider`.
5758
function M.toggle()
5859
if provider and provider.toggle then
5960
provider:toggle()
@@ -67,6 +68,7 @@ function M.toggle()
6768
end
6869
end
6970

71+
---Start `opencode` via `opts.provider`.
7072
function M.start()
7173
if provider and provider.start then
7274
provider:start()
@@ -80,6 +82,8 @@ function M.start()
8082
end
8183
end
8284

85+
---Show `opencode` via `opts.provider`.
86+
--- Only called if `provider.toggle` or `provider.start` was previously called.
8387
function M.show()
8488
if provider and provider.show and started then
8589
provider:show()

lua/opencode/ui/ask.lua

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,29 @@
22

33
local M = {}
44

5+
---Input a prompt to send to `opencode`.
6+
---Press the up arrow to browse recent prompts.
7+
---
8+
--- - Highlights `opts.contexts` placeholders.
9+
--- - Completes `opts.contexts` placeholders.
10+
--- - Press `<Tab>` to trigger built-in completion.
11+
--- - When using `blink.cmp` and `snacks.input`, registers `opts.auto_register_cmp_sources`.
12+
---
513
---@param default? string Text to pre-fill the input with.
6-
---@param context opencode.Context
7-
---@param on_confirm fun(value: string|nil, callback?: fun())
8-
function M.input(default, context, on_confirm)
14+
---@param opts? opencode.prompt.Opts Options for `prompt()`.
15+
function M.ask(default, opts)
16+
opts = opts or {}
17+
opts.context = opts.context or require("opencode.context").new()
18+
919
---@type snacks.input.Opts
1020
local input_opts = {
1121
default = default,
1222
highlight = function(text)
13-
local rendered = context:render(text)
23+
local rendered = opts.context:render(text)
1424
-- Transform to `:help input()-highlight` format
1525
return vim.tbl_map(function(extmark)
1626
return { extmark.col, extmark.end_col, extmark.hl_group }
17-
end, context.extmarks(rendered.input))
27+
end, opts.context.extmarks(rendered.input))
1828
end,
1929
completion = "customlist,v:lua.opencode_completion",
2030
-- snacks-only options
@@ -43,9 +53,13 @@ function M.input(default, context, on_confirm)
4353
},
4454
}
4555

46-
require("opencode.cmp.blink").context = context
56+
require("opencode.cmp.blink").context = opts.context
4757

48-
vim.ui.input(vim.tbl_deep_extend("keep", require("opencode.config").opts.input, input_opts), on_confirm)
58+
vim.ui.input(vim.tbl_deep_extend("force", input_opts, require("opencode.config").opts.input), function(value)
59+
if value and value ~= "" then
60+
require("opencode").prompt(value, opts)
61+
end
62+
end)
4963
end
5064

5165
-- FIX: Overridden by blink.cmp cmdline completion if both are enabled, and that won't have our items.

0 commit comments

Comments
 (0)