Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lua/opencode/api_client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ end
--- Subscribe to events (streaming)
--- @param directory string|nil Directory path
--- @param on_event fun(event: table) Event callback
--- @return Job The streaming job handle
--- @return table The streaming job handle
function OpencodeApiClient:subscribe_to_events(directory, on_event)
self:_ensure_base_url()
local url = self.base_url .. '/event'
Expand Down
3 changes: 3 additions & 0 deletions lua/opencode/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ function M.open(opts)
else
if not state.active_session then
state.active_session = session.get_last_workspace_session()
if not state.active_session then
state.active_session = M.create_new_session()
end
else
if not state.display_route and are_windows_closed then
-- We're not displaying /help or something like that but we have an active session
Expand Down
7 changes: 1 addition & 6 deletions lua/opencode/promise.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ function Promise.new()
end

---@generic T
---@param self Promise<T>
---@param value T
---@return Promise<T>
function Promise:resolve(value)
Expand All @@ -50,8 +49,7 @@ function Promise:resolve(value)
end

---@generic T
---@param self Promise<T>
---@param error any
---@param err any
---@return self
function Promise:reject(err)
if self._resolved then
Expand All @@ -70,7 +68,6 @@ function Promise:reject(err)
end

---@generic T, U
---@param self Promise<T>
---@param callback fun(value: T): U | Promise<U>
---@return Promise<U>
function Promise:and_then(callback)
Expand Down Expand Up @@ -116,7 +113,6 @@ function Promise:and_then(callback)
end

---@generic T
---@param self Promise<T>
---@param error_callback fun(err: any): any | Promise<any>
---@return Promise<T>
function Promise:catch(error_callback)
Expand Down Expand Up @@ -161,7 +157,6 @@ function Promise:catch(error_callback)
end

---@generic T
---@param self Promise<T>
---@param timeout integer|nil Timeout in milliseconds (default: 5000)
---@param interval integer|nil Interval in milliseconds to check (default: 20)
---@return T
Expand Down
4 changes: 4 additions & 0 deletions lua/opencode/provider.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ function M._get_models()
local config_file = require('opencode.config_file')
local response = config_file.get_opencode_providers()

if not response then
return {}
end

local models = {}
for _, provider in ipairs(response.providers) do
for _, model in pairs(provider.models) do
Expand Down
58 changes: 30 additions & 28 deletions lua/opencode/server_job.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,42 @@ end
function M.call_api(url, method, body)
local call_promise = Promise.new()
state.job_count = state.job_count + 1

local request_entry = { nil, call_promise }
table.insert(M.requests, request_entry)

-- Remove completed promises from list, update job_count
local function remove_from_requests()
for i, entry in ipairs(M.requests) do
if entry == request_entry then
table.remove(M.requests, i)
break
end
end
state.job_count = #M.requests
end

local opts = {
url = url,
method = method or 'GET',
headers = { ['Content-Type'] = 'application/json' },
proxy = '',
callback = function(response)
remove_from_requests()
handle_api_response(response, function(err, result)
if err then
local ok, pcall_err = pcall(call_promise.reject, call_promise, err)
local ok, pcall_err = pcall(function()
call_promise:reject(err)
end)
if not ok then
vim.schedule(function()
vim.notify('Error while handling API error response: ' .. vim.inspect(pcall_err))
end)
end
else
local ok, pcall_err = pcall(call_promise.resolve, call_promise, result)
local ok, pcall_err = pcall(function()
call_promise:resolve(result)
end)
if not ok then
vim.schedule(function()
vim.notify('Error while handling API response: ' .. vim.inspect(pcall_err))
Expand All @@ -52,7 +72,10 @@ function M.call_api(url, method, body)
end)
end,
on_error = function(err)
local ok, pcall_err = pcall(call_promise.reject, call_promise, err)
remove_from_requests()
local ok, pcall_err = pcall(function()
call_promise:reject(err)
end)
if not ok then
vim.schedule(function()
vim.notify('Error while handling API on_error: ' .. vim.inspect(pcall_err))
Expand All @@ -65,29 +88,8 @@ function M.call_api(url, method, body)
opts.body = body and vim.json.encode(body) or '{}'
end

local request_entry = { opts, call_promise }
table.insert(M.requests, request_entry)

-- Remove completed promises from list, update job_count
local function remove_from_requests()
for i, entry in ipairs(M.requests) do
if entry == request_entry then
table.remove(M.requests, i)
break
end
end
state.job_count = #M.requests
end

call_promise:and_then(function(result)
remove_from_requests()
return result
end)

call_promise:catch(function(err)
remove_from_requests()
error(err)
end)
-- add opts to request_entry for request tracking
request_entry[1] = opts

curl.request(opts)
return call_promise
Expand All @@ -98,7 +100,7 @@ end
--- @param method string|nil HTTP method (default: 'GET')
--- @param body table|nil|boolean Request body (will be JSON encoded)
--- @param on_chunk fun(chunk: string) Callback invoked for each chunk of data received
--- @return Job job The underlying job instance
--- @return table The underlying job instance
function M.stream_api(url, method, body, on_chunk)
local opts = {
url = url,
Expand All @@ -125,7 +127,7 @@ function M.stream_api(url, method, body, on_chunk)
opts.body = body and vim.json.encode(body) or '{}'
end

return curl.request(opts)
return curl.request(opts) --[[@as table]]
end

function M.ensure_server()
Expand Down
18 changes: 14 additions & 4 deletions lua/opencode/ui/input_window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function M._build_input_win_config()
col = 2,
style = 'minimal',
zindex = 41,
}
} --[[@as vim.api.keyset.win_config]]
end

function M.create_window(windows)
Expand All @@ -41,6 +41,7 @@ function M.close()
if not M.mounted() then
return
end
---@cast state.windows { input_win: integer, input_buf: integer }

pcall(vim.api.nvim_win_close, state.windows.input_win, true)
pcall(vim.api.nvim_buf_delete, state.windows.input_buf, { force = true })
Expand All @@ -51,6 +52,8 @@ function M.handle_submit()
if not windows or not M.mounted(windows) then
return
end
---@cast windows { input_buf: integer }

local input_content = table.concat(vim.api.nvim_buf_get_lines(windows.input_buf, 0, -1, false), '\n')
vim.api.nvim_buf_set_lines(windows.input_buf, 0, -1, false, {})
vim.api.nvim_exec_autocmds('TextChanged', {
Expand Down Expand Up @@ -177,6 +180,11 @@ function M.recover_input(windows)
end

function M.focus_input()
if not M.mounted() then
return
end
---@cast state.windows { input_win: integer, input_buf: integer }

vim.api.nvim_set_current_win(state.windows.input_win)

local lines = vim.api.nvim_buf_get_lines(state.windows.input_buf, 0, -1, false)
Expand All @@ -192,6 +200,7 @@ function M.set_content(text, windows)
if not M.mounted(windows) then
return
end
---@cast windows { input_win: integer, input_buf: integer }

local lines = type(text) == 'table' and text or vim.split(tostring(text), '\n')

Expand All @@ -212,6 +221,7 @@ function M.remove_mention(mention_name, windows)
if not M.mounted(windows) then
return
end
---@cast windows { input_buf: integer }

local lines = vim.api.nvim_buf_get_lines(windows.input_buf, 0, -1, false)
for i, line in ipairs(lines) do
Expand All @@ -228,12 +238,12 @@ function M.remove_mention(mention_name, windows)
end

function M.is_empty()
local windows = state.windows
if not windows or not M.mounted() then
if not M.mounted() then
return true
end
---@cast state.windows { input_buf: integer }

local lines = vim.api.nvim_buf_get_lines(windows.input_buf, 0, -1, false)
local lines = vim.api.nvim_buf_get_lines(state.windows.input_buf, 0, -1, false)
return #lines == 0 or (#lines == 1 and lines[1] == '')
end

Expand Down
2 changes: 1 addition & 1 deletion lua/opencode/ui/output.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Output.__index = Output

---@class Output
---@field lines string[]
---@field extmarks table<number, OutputExtmark>
---@field extmarks table<number, OutputExtmark[]>
---@field actions OutputAction[]
---@field add_line fun(self: Output, line: string, fit?: boolean): number
---@field get_line fun(self: Output, idx: number): string?
Expand Down
4 changes: 2 additions & 2 deletions lua/opencode/ui/renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ function M.reset()

if state.current_permission and state.api_client then
require('opencode.api').respond_to_permission('reject')
state.current_permission = nil
end
state.current_permission = nil
trigger_on_data_rendered()
end

---Set up all subscriptions, for both local and server events
function M.setup_subscriptions(_)
M._subscriptions.active_session = function(_, new, old)
M._subscriptions.active_session = function(_, new, _)
M.reset()
if new then
M.render_full_session()
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/core_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@ describe('opencode.core', function()
assert.is_false(input_focused)
assert.is_true(output_focused)
end)

it('creates a new session when no active session and no last session exists', function()
state.windows = nil
state.active_session = nil
session.get_last_workspace_session:revert()
stub(session, 'get_last_workspace_session').returns(nil)

core.open({ new_session = false, focus = 'input' })

assert.truthy(state.active_session)
assert.truthy(state.active_session.id)
end)
end)

describe('select_session', function()
Expand Down