Skip to content

Commit e879e40

Browse files
committed
make server start flow async
1 parent cc8aaf4 commit e879e40

File tree

3 files changed

+108
-76
lines changed

3 files changed

+108
-76
lines changed

lua/eca/path_finder.lua

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ function M:_write_version_file(version)
100100
file:write(version)
101101
file:close()
102102
else
103-
Logger.notify("Could not write version file: " .. self._version_file, vim.log.levels.WARN)
103+
Logger.warn("Could not write version file: " .. self._version_file)
104104
end
105105
end
106106

@@ -136,7 +136,7 @@ function M:_download_latest_server(server_path, version)
136136

137137
local download_path = self._cache_dir .. "/" .. artifact_name
138138

139-
Logger.notify("Downloading latest ECA server version from: " .. download_url, vim.log.levels.INFO)
139+
Logger.debug("Downloading latest ECA server version from: " .. download_url)
140140

141141
-- Ensure cache directory exists
142142
vim.fn.mkdir(self._cache_dir, "p")
@@ -150,8 +150,7 @@ function M:_download_latest_server(server_path, version)
150150

151151
local download_result = os.execute(download_cmd)
152152
if download_result ~= 0 then
153-
Logger.notify("Failed to download ECA server from: " .. download_url, vim.log.levels.ERROR)
154-
return false
153+
error("Failed to download ECA server from: " .. download_url)
155154
end
156155

157156
-- Extract if it's a zip file
@@ -161,35 +160,33 @@ function M:_download_latest_server(server_path, version)
161160

162161
local extract_result = os.execute(extract_cmd)
163162
if extract_result ~= 0 then
164-
Logger.notify("Failed to extract ECA server", vim.log.levels.ERROR)
165-
return false
163+
error("Failed to extract ECA server")
166164
end
167165

168166
-- Remove the zip file after extraction
169167
os.remove(download_path)
170168
end
171169

172170
-- Make executable (if not Windows)
173-
if not vim.loop.os_uname().sysname:lower():match("windows") then
171+
if not uv.os_uname().sysname:lower():match("windows") then
174172
os.execute("chmod +x " .. vim.fn.shellescape(server_path))
175173
end
176174

177175
if not Utils.file_exists(server_path) then
178-
Logger.notify("ECA server binary not found after download and extraction", vim.log.levels.ERROR)
179-
return false
176+
error("ECA server binary not found after download and extraction")
180177
end
181178

182179
-- Write version file
183180
self:_write_version_file(version)
184181

185-
Logger.notify("ECA server downloaded successfully", vim.log.levels.INFO)
182+
Logger.debug("ECA server downloaded successfully")
183+
186184
return true
187185
end
188186

189187
---@return string
190-
function M:find()
188+
function M:find(custom_path)
191189
-- Check for custom server path first
192-
local custom_path = Config.server_path
193190
if custom_path and custom_path:gsub("%s+", "") ~= "" then
194191
if Utils.file_exists(custom_path) then
195192
Logger.debug("Using custom server path: " .. custom_path)
@@ -215,13 +212,18 @@ function M:find()
215212
-- Download if server doesn't exist or version is outdated
216213
if not server_exists or (latest_version and current_version ~= latest_version) then
217214
if not latest_version then
218-
Logger.notify("Could not check for latest version, using existing server", vim.log.levels.WARN)
215+
Logger.warn("Could not check for latest version, using existing server")
219216
return server_path
220217
end
221218

222-
local success = self:_download_latest_server(server_path, latest_version)
223-
if not success then
224-
error("Failed to download ECA server")
219+
local success
220+
221+
local ok, err = pcall(function()
222+
success = self:_download_latest_server(server_path, latest_version)
223+
end)
224+
225+
if not ok or not success then
226+
error((err and tostring(err)) or "Failed to download ECA server")
225227
end
226228
end
227229

lua/eca/server.lua

Lines changed: 75 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
local Utils = require("eca.utils")
22
local Config = require("eca.config")
3-
local PathFinder = require("eca.path_finder")
43
local Logger = require("eca.logger")
54

65
---@class eca.Server
@@ -13,16 +12,17 @@ local Logger = require("eca.logger")
1312
---@field on_stop function Callback when the server stops
1413
---Called when a notification is received(message without an ID)
1514
---@field on_notification fun(server: eca.Server, message: table)
16-
---@field private path_finder eca.PathFinder Server path finder
1715
---@field pending_requests {id: fun(err, data)} -- outgoing requests with callbacks
16+
---@field cwd string Current working directory for the server process
17+
---@field workspace_folders {name: string, uri: string}[] Workspace folders to send on initialize
1818
local M = {}
1919

2020
---@param opts? table
2121
---@return eca.Server
2222
function M.new(opts)
2323
opts = vim.tbl_extend("keep", opts or {}, {
2424
on_start = function(pid)
25-
require("eca.logger").notify("Started server with pid " .. pid, vim.log.levels.INFO)
25+
require("eca.logger").debug("Started server with pid " .. pid)
2626
end,
2727
on_initialize = function()
2828
require("eca.logger").notify("Server ready to receive messages", vim.log.levels.INFO)
@@ -37,7 +37,13 @@ function M.new(opts)
3737
require("eca.observer").notify(message)
3838
end)
3939
end,
40-
path_finder = PathFinder:new(),
40+
cwd = vim.fn.getcwd(),
41+
workspace_folders = {
42+
{
43+
name = vim.fn.fnamemodify(Utils.get_project_root(), ":t"),
44+
uri = "file://" .. Utils.get_project_root(),
45+
},
46+
},
4147
})
4248

4349
return setmetatable({
@@ -46,11 +52,12 @@ function M.new(opts)
4652
on_initialize = opts.on_initialize,
4753
on_stop = opts.on_stop,
4854
on_notification = opts.on_notification,
49-
path_finder = opts.path_finder,
5055
messages = {},
5156
pending_requests = {},
5257
initialized = false,
5358
next_id = 0,
59+
cwd = opts.cwd,
60+
workspace_folders = opts.workspace_folders,
5461
}, { __index = M })
5562
end
5663

@@ -93,70 +100,79 @@ end
93100
function M:start(opts)
94101
opts = opts or { initialize = true }
95102

96-
local server_path
97-
local ok, path_finder_error = pcall(function()
98-
server_path = self.path_finder:find()
99-
end)
103+
local custom_path = Config.server_path or ""
100104

101-
if not ok or not server_path then
102-
Logger.notify("Could not find or download ECA server" .. tostring(path_finder_error), vim.log.levels.ERROR)
103-
return
104-
end
105+
local this_file = debug.getinfo(1, "S").source:sub(2)
106+
local proj_root = vim.fn.fnamemodify(this_file, ":p:h:h:h")
107+
local script_path = proj_root .. "/scripts/server_path.lua"
105108

106-
Logger.debug("Starting ECA server: " .. server_path)
109+
local nvim_exe = vim.fn.exepath("nvim")
107110

108-
local args = { server_path, "server" }
109-
if Config.server_args and Config.server_args ~= "" then
110-
vim.list_extend(args, vim.split(Config.server_args, " "))
111+
if not nvim_exe or nvim_exe == "" then
112+
nvim_exe = "nvim"
111113
end
112114

113-
opts = vim.tbl_deep_extend("keep", opts, {
114-
cmd = args,
115-
text = true,
116-
cwd = vim.fn.getcwd(),
117-
stdin = true,
118-
stdout = on_stdout(self),
119-
stderr = on_stderr,
120-
---@param out vim.SystemCompleted
121-
on_exit = function(out)
122-
if out.code ~= 0 then
123-
require("eca.logger").notify(string.format("Server exited with status code %d", out.code), vim.log.levels.ERROR)
124-
end
125-
end,
126-
})
115+
local cmd = { nvim_exe, "-l", script_path, custom_path }
127116

128-
local started, process_or_err = pcall(vim.system, opts.cmd, {
129-
cwd = opts.cwd,
130-
text = opts.text,
131-
stdin = opts.stdin,
132-
stdout = opts.stdout,
133-
stderr = opts.stderr,
134-
}, opts.on_exit)
135-
136-
if not started then
137-
self.process = nil
138-
Logger.notify(vim.inspect(process_or_err), vim.log.levels.ERROR)
139-
return
140-
end
117+
vim.system(cmd, { text = true }, function(out)
118+
if out.code ~= 0 then
119+
Logger.notify(out.stderr, vim.log.levels.ERROR)
120+
return
121+
end
141122

142-
self.process = process_or_err
143-
if self.on_start then
144-
self.on_start(process_or_err.pid)
145-
end
123+
local stdout_lines = Utils.split_lines(out.stdout)
124+
local server_path = stdout_lines[#stdout_lines]
146125

147-
if opts.initialize then
148-
self:initialize()
149-
end
126+
Logger.debug("Starting ECA server: " .. server_path)
127+
128+
local args = { server_path, "server" }
129+
130+
if Config.server_args and Config.server_args ~= "" then
131+
vim.list_extend(args, vim.split(Config.server_args, " "))
132+
end
133+
134+
opts = vim.tbl_deep_extend("keep", opts, {
135+
cmd = args,
136+
text = true,
137+
cwd = self.cwd,
138+
stdin = true,
139+
stdout = on_stdout(self),
140+
stderr = on_stderr,
141+
---@param output vim.SystemCompleted
142+
on_exit = function(output)
143+
if output.code ~= 0 then
144+
require("eca.logger").notify(string.format("Server exited with status code %d", output.code), vim.log.levels
145+
.ERROR)
146+
end
147+
end,
148+
})
149+
150+
local started, process_or_err = pcall(vim.system, opts.cmd, {
151+
cwd = opts.cwd,
152+
text = opts.text,
153+
stdin = opts.stdin,
154+
stdout = opts.stdout,
155+
stderr = opts.stderr,
156+
}, opts.on_exit)
157+
158+
if not started then
159+
self.process = nil
160+
Logger.notify(vim.inspect(process_or_err), vim.log.levels.ERROR)
161+
return
162+
end
163+
164+
self.process = process_or_err
165+
if self.on_start then
166+
self.on_start(process_or_err.pid)
167+
end
168+
169+
if opts.initialize then
170+
self:initialize()
171+
end
172+
end)
150173
end
151174

152175
function M:initialize()
153-
local workspace_folders = {
154-
{
155-
name = vim.fn.fnamemodify(Utils.get_project_root(), ":t"),
156-
uri = "file://" .. Utils.get_project_root(),
157-
},
158-
}
159-
160176
self:send_request("initialize", {
161177
processId = vim.fn.getpid(),
162178
clientInfo = {
@@ -168,7 +184,7 @@ function M:initialize()
168184
chat = true,
169185
},
170186
},
171-
workspaceFolders = workspace_folders,
187+
workspaceFolders = vim.deepcopy(self.workspace_folders),
172188
}, function(err, _)
173189
if err then
174190
Logger.notify("Could not initialize server: " .. err, vim.log.levels.ERROR)

tests/test_server_integration.lua

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ T["server"] = MiniTest.new_set()
4141
T["server"]["start"] = function()
4242
child.lua("_G.server.start({cmd = {'fake'}})")
4343
eq(child.lua_get("_G.server.process"), vim.NIL)
44-
child.lua("_G.server:start()")
44+
child.lua([[
45+
_G.server:start()
46+
_G.server_started = vim.wait(5000, function()
47+
return _G.server and _G.server:is_running()
48+
end, 50)
49+
]])
50+
eq(child.lua_get("_G.server_started"), true)
4551
eq(child.lua_get("_G.server:is_running()"), true)
4652
end
4753

@@ -80,6 +86,14 @@ T["server"]["initialize"] = function()
8086
end
8187
child.lua("_G.method = " .. vim.inspect(test_case.method))
8288
child.lua("_G.server:start({initialize = false})")
89+
-- Wait for async server start
90+
child.lua([[
91+
_G.server_started = vim.wait(5000, function()
92+
return _G.server and _G.server:is_running()
93+
end, 50)
94+
]])
95+
eq(child.lua_get("_G.server_started"), true)
96+
8397
child.lua_func(function()
8498
_G.server:send_request(_G.method, {}, function(err, result)
8599
_G.err = err

0 commit comments

Comments
 (0)