Skip to content

Commit 47f92ab

Browse files
committed
refactor(server): fetch custom commands at server creation time
1 parent c7adcfe commit 47f92ab

2 files changed

Lines changed: 147 additions & 153 deletions

File tree

lua/opencode/cli/server.lua

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ local M = {}
2222
---@field cwd string
2323
---@field title string
2424
---@field subagents opencode.cli.client.Agent[]
25+
---@field custom_commands opencode.cli.client.Command[]
2526

2627
---Verify that an `opencode` process is responding on the given port,
2728
---and fetch some details about it.
@@ -64,17 +65,25 @@ local function get_server(port)
6465
resolve(subagents)
6566
end)
6667
end),
68+
Promise.new(function(resolve)
69+
require("opencode.cli.client").get_commands(port, function(custom_commands)
70+
resolve(custom_commands)
71+
end)
72+
end),
6773
})
6874
end
6975
)
70-
:next(function(results) ---@param results { [1]: string, [2]: string, [3]: opencode.cli.client.Agent[] }
71-
return {
72-
port = port,
73-
cwd = results[1],
74-
title = results[2],
75-
subagents = results[3],
76-
}
77-
end)
76+
:next(
77+
function(results) ---@param results { [1]: string, [2]: string, [3]: opencode.cli.client.Agent[], [4]: opencode.cli.client.Command[] }
78+
return {
79+
port = port,
80+
cwd = results[1],
81+
title = results[2],
82+
subagents = results[3],
83+
custom_commands = results[4],
84+
}
85+
end
86+
)
7887
end
7988

8089
---@return Promise<opencode.cli.server.Server[]>

lua/opencode/ui/select.lua

Lines changed: 130 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -32,165 +32,150 @@ function M.select(opts)
3232
return require("opencode.cli.server")
3333
.get()
3434
:next(function(server) ---@param server opencode.cli.server.Server
35-
if opts.sections.commands then
36-
return Promise.new(function(resolve)
37-
require("opencode.cli.client").get_commands(server.port, function(custom_commands)
38-
resolve({ server = server, custom_commands = custom_commands })
39-
end)
40-
end)
41-
else
42-
return { server = server }
35+
local prompts = require("opencode.config").opts.prompts or {}
36+
local commands = require("opencode.config").opts.select.sections.commands or {}
37+
for _, command in ipairs(server.custom_commands) do
38+
commands[command.name] = command.description
4339
end
44-
end)
45-
:next(
46-
function(result) ---@param result { server: opencode.cli.server.Server, custom_commands: opencode.cli.client.Command[]}
47-
local prompts = require("opencode.config").opts.prompts or {}
48-
local commands = require("opencode.config").opts.select.sections.commands or {}
49-
for _, command in ipairs(result.custom_commands) do
50-
commands[command.name] = command.description
51-
end
5240

53-
---@class opencode.select.Item : snacks.picker.finder.Item, { __type: "prompt" | "command" | "server", ask?: boolean, submit?: boolean }
41+
---@class opencode.select.Item : snacks.picker.finder.Item, { __type: "prompt" | "command" | "server", ask?: boolean, submit?: boolean }
42+
local items = {}
5443

55-
---@type opencode.select.Item[]
56-
local items = {}
57-
58-
-- Prompts section
59-
if opts.sections.prompts then
60-
table.insert(items, { __group = true, name = "PROMPT", preview = { text = "" } })
61-
local prompt_items = {}
62-
for name, prompt in pairs(prompts) do
63-
local rendered = context:render(prompt.prompt, result.server.subagents)
64-
---@type snacks.picker.finder.Item
65-
local item = {
66-
__type = "prompt",
67-
name = name,
68-
text = prompt.prompt .. (prompt.ask and "" or ""),
69-
highlights = rendered.input, -- `snacks.picker`'s `select` seems to ignore this, so we incorporate it ourselves in `format_item`
70-
preview = {
71-
text = context.plaintext(rendered.output),
72-
extmarks = context.extmarks(rendered.output),
73-
},
74-
ask = prompt.ask,
75-
submit = prompt.submit,
76-
}
77-
table.insert(prompt_items, item)
78-
end
79-
-- Sort: ask=true, submit=false, name
80-
table.sort(prompt_items, function(a, b)
81-
if a.ask and not b.ask then
82-
return true
83-
elseif not a.ask and b.ask then
84-
return false
85-
elseif not a.submit and b.submit then
86-
return true
87-
elseif a.submit and not b.submit then
88-
return false
89-
else
90-
return a.name < b.name
91-
end
92-
end)
93-
for _, item in ipairs(prompt_items) do
94-
table.insert(items, item)
95-
end
44+
-- Prompts section
45+
if opts.sections.prompts then
46+
table.insert(items, { __group = true, name = "PROMPT", preview = { text = "" } })
47+
local prompt_items = {}
48+
for name, prompt in pairs(prompts) do
49+
local rendered = context:render(prompt.prompt, server.subagents)
50+
---@type snacks.picker.finder.Item
51+
local item = {
52+
__type = "prompt",
53+
name = name,
54+
text = prompt.prompt .. (prompt.ask and "" or ""),
55+
highlights = rendered.input, -- `snacks.picker`'s `select` seems to ignore this, so we incorporate it ourselves in `format_item`
56+
preview = {
57+
text = context.plaintext(rendered.output),
58+
extmarks = context.extmarks(rendered.output),
59+
},
60+
ask = prompt.ask,
61+
submit = prompt.submit,
62+
}
63+
table.insert(prompt_items, item)
9664
end
97-
98-
-- Commands section
99-
if type(opts.sections.commands) == "table" then
100-
table.insert(items, { __group = true, name = "COMMAND", preview = { text = "" } })
101-
local command_items = {}
102-
for name, description in pairs(commands) do
103-
table.insert(command_items, {
104-
__type = "command",
105-
name = name, -- TODO: Truncate if it'd run into `text`
106-
text = description,
107-
highlights = { { description, "Comment" } },
108-
preview = {
109-
text = "",
110-
},
111-
})
112-
end
113-
table.sort(command_items, function(a, b)
65+
-- Sort: ask=true, submit=false, name
66+
table.sort(prompt_items, function(a, b)
67+
if a.ask and not b.ask then
68+
return true
69+
elseif not a.ask and b.ask then
70+
return false
71+
elseif not a.submit and b.submit then
72+
return true
73+
elseif a.submit and not b.submit then
74+
return false
75+
else
11476
return a.name < b.name
115-
end)
116-
for _, item in ipairs(command_items) do
117-
table.insert(items, item)
11877
end
78+
end)
79+
for _, item in ipairs(prompt_items) do
80+
table.insert(items, item)
11981
end
82+
end
12083

121-
-- Server section
122-
if opts.sections.server then
123-
table.insert(items, { __group = true, name = "SERVER", preview = { text = "" } })
124-
table.insert(items, {
125-
__type = "server",
126-
name = "server.select",
127-
text = "Select server",
128-
highlights = { { "Select server", "Comment" } },
129-
preview = { text = "" },
130-
})
131-
table.insert(items, {
132-
__type = "server",
133-
name = "server.start",
134-
text = "Start server",
135-
highlights = { { "Start server", "Comment" } },
136-
preview = { text = "" },
137-
})
138-
table.insert(items, {
139-
__type = "server",
140-
name = "server.stop",
141-
text = "Stop server",
142-
highlights = { { "Stop server", "Comment" } },
143-
preview = { text = "" },
144-
})
145-
table.insert(items, {
146-
__type = "server",
147-
name = "server.toggle",
148-
text = "Toggle server",
149-
highlights = { { "Toggle server", "Comment" } },
150-
preview = { text = "" },
84+
-- Commands section
85+
if type(opts.sections.commands) == "table" then
86+
table.insert(items, { __group = true, name = "COMMAND", preview = { text = "" } })
87+
local command_items = {}
88+
for name, description in pairs(commands) do
89+
table.insert(command_items, {
90+
__type = "command",
91+
name = name, -- TODO: Truncate if it'd run into `text`
92+
text = description,
93+
highlights = { { description, "Comment" } },
94+
preview = {
95+
text = "",
96+
},
15197
})
15298
end
153-
154-
for i, item in ipairs(items) do
155-
item.idx = i -- Store the index for non-snacks formatting
99+
table.sort(command_items, function(a, b)
100+
return a.name < b.name
101+
end)
102+
for _, item in ipairs(command_items) do
103+
table.insert(items, item)
156104
end
105+
end
157106

158-
---@type snacks.picker.ui_select.Opts
159-
local select_opts = {
160-
---@param item snacks.picker.finder.Item
161-
---@param is_snacks boolean
162-
format_item = function(item, is_snacks)
163-
if is_snacks then
164-
if item.__group then
165-
return { { item.name, "Title" } }
166-
end
167-
local formatted = vim.deepcopy(item.highlights or {})
168-
if item.ask then
169-
table.insert(formatted, { "", "Keyword" })
170-
end
171-
table.insert(formatted, 1, { item.name, "Keyword" })
172-
table.insert(formatted, 2, { string.rep(" ", 18 - #item.name) })
173-
return formatted
174-
else
175-
local indent = #tostring(#items) - #tostring(item.idx)
176-
if item.__group then
177-
local divider = string.rep("", (80 - #item.name) / 2)
178-
return string.rep(" ", indent) .. divider .. item.name .. divider
179-
end
180-
return ("%s[%s]%s%s"):format(
181-
string.rep(" ", indent),
182-
item.name,
183-
string.rep(" ", 18 - #item.name),
184-
item.text or ""
185-
)
186-
end
187-
end,
188-
}
189-
select_opts = vim.tbl_deep_extend("force", select_opts, opts)
107+
-- Server section
108+
if opts.sections.server then
109+
table.insert(items, { __group = true, name = "SERVER", preview = { text = "" } })
110+
table.insert(items, {
111+
__type = "server",
112+
name = "server.select",
113+
text = "Select server",
114+
highlights = { { "Select server", "Comment" } },
115+
preview = { text = "" },
116+
})
117+
table.insert(items, {
118+
__type = "server",
119+
name = "server.start",
120+
text = "Start server",
121+
highlights = { { "Start server", "Comment" } },
122+
preview = { text = "" },
123+
})
124+
table.insert(items, {
125+
__type = "server",
126+
name = "server.stop",
127+
text = "Stop server",
128+
highlights = { { "Stop server", "Comment" } },
129+
preview = { text = "" },
130+
})
131+
table.insert(items, {
132+
__type = "server",
133+
name = "server.toggle",
134+
text = "Toggle server",
135+
highlights = { { "Toggle server", "Comment" } },
136+
preview = { text = "" },
137+
})
138+
end
190139

191-
return Promise.select(items, select_opts)
140+
for i, item in ipairs(items) do
141+
item.idx = i -- Store the index for non-snacks formatting
192142
end
193-
)
143+
144+
---@type snacks.picker.ui_select.Opts
145+
local select_opts = {
146+
---@param item snacks.picker.finder.Item
147+
---@param is_snacks boolean
148+
format_item = function(item, is_snacks)
149+
if is_snacks then
150+
if item.__group then
151+
return { { item.name, "Title" } }
152+
end
153+
local formatted = vim.deepcopy(item.highlights or {})
154+
if item.ask then
155+
table.insert(formatted, { "", "Keyword" })
156+
end
157+
table.insert(formatted, 1, { item.name, "Keyword" })
158+
table.insert(formatted, 2, { string.rep(" ", 18 - #item.name) })
159+
return formatted
160+
else
161+
local indent = #tostring(#items) - #tostring(item.idx)
162+
if item.__group then
163+
local divider = string.rep("", (80 - #item.name) / 2)
164+
return string.rep(" ", indent) .. divider .. item.name .. divider
165+
end
166+
return ("%s[%s]%s%s"):format(
167+
string.rep(" ", indent),
168+
item.name,
169+
string.rep(" ", 18 - #item.name),
170+
item.text or ""
171+
)
172+
end
173+
end,
174+
}
175+
select_opts = vim.tbl_deep_extend("force", select_opts, opts)
176+
177+
return Promise.select(items, select_opts)
178+
end)
194179
:next(function(choice) ---@param choice opencode.select.Item
195180
if choice.__type == "prompt" then
196181
---@type opencode.Prompt

0 commit comments

Comments
 (0)