@@ -11,10 +11,6 @@ M.config = {
1111 rows = 24 ,
1212 -- Socket directory (nil = let daemon decide)
1313 socket_dir = nil ,
14- -- Max wait time for daemon socket creation after `pterm new`
15- attach_wait_ms = 3000 ,
16- -- Poll interval while waiting for socket
17- attach_poll_ms = 50 ,
1814}
1915
2016--- Active connections: session_name -> { buf, job_id, session_name }
@@ -73,13 +69,6 @@ local function socket_path(session_name)
7369 return socket_dir () .. " /" .. session_name .. " /socket"
7470end
7571
76- local function wait_for_socket (session_name , timeout_ms , poll_ms )
77- local sock = socket_path (session_name )
78- return vim .wait (timeout_ms , function ()
79- return vim .uv .fs_stat (sock ) ~= nil
80- end , poll_ms )
81- end
82-
8372--- Recursively scan the socket directory for active sessions (pure Lua).
8473--- Mirrors the Rust `find_sessions()` logic without spawning a subprocess,
8574--- which avoids instability when called during command-line completion.
@@ -133,101 +122,9 @@ function M.kill(session_name)
133122 vim .notify (" Killed session: " .. session_name , vim .log .levels .INFO )
134123end
135124
136- --- Open or attach to a session.
137- --- If session exists, attach. Otherwise create new.
138- function M .open (session_name , args )
139- args = args or {}
140-
141- -- Default session name
142- if not session_name or session_name == " " then
143- session_name = " main"
144- end
145-
146- -- Already connected?
147- if M .connections [session_name ] then
148- -- Switch to existing buffer
149- local conn = M .connections [session_name ]
150- if vim .api .nvim_buf_is_valid (conn .buf ) then
151- vim .api .nvim_set_current_buf (conn .buf )
152- vim .cmd (" startinsert" )
153- return
154- else
155- -- Buffer was closed, clean up
156- M .connections [session_name ] = nil
157- end
158- end
159-
160- local sock = socket_path (session_name )
161- local bin = find_binary ()
162-
163- -- Check if session exists
164- if vim .uv .fs_stat (sock ) == nil then
165- -- Create new session
166- local cmd_parts = {}
167- -- Skip session name from args, collect rest as command
168- local found_name = false
169- for _ , arg in ipairs (args ) do
170- if not found_name and arg == session_name then
171- found_name = true
172- elseif found_name then
173- table.insert (cmd_parts , arg )
174- end
175- end
176-
177- -- Do NOT pass --cols/--rows to `pterm new` here. The bridge
178- -- (started by M.attach) will send an accurate RESIZE based on
179- -- the actual PTY size after terminal-friendly window options
180- -- (number=false, signcolumn=no, …) have been applied. Passing
181- -- dimensions at this point would use the pre-option window size,
182- -- which may differ and trigger an unnecessary resize + snapshot
183- -- race on the daemon side.
184- local create_cmd = {
185- bin ,
186- " new" ,
187- session_name ,
188- }
189-
190- if # cmd_parts > 0 then
191- table.insert (create_cmd , " --" )
192- for _ , part in ipairs (cmd_parts ) do
193- table.insert (create_cmd , part )
194- end
195- end
196-
197- vim .fn .system (create_cmd )
198- if vim .v .shell_error ~= 0 then
199- vim .notify (
200- " Failed to create session '" .. session_name .. " ' (pterm new exited " .. vim .v .shell_error .. " )" ,
201- vim .log .levels .ERROR
202- )
203- return
204- end
205-
206- -- Wait for daemon socket before attach to avoid new->attach race.
207- local ok = wait_for_socket (session_name , M .config .attach_wait_ms , M .config .attach_poll_ms )
208- if not ok then
209- vim .notify (
210- " Session '" .. session_name .. " ' was created but socket did not appear in time" ,
211- vim .log .levels .ERROR
212- )
213- return
214- end
215- end
216-
217- -- Attach to session
218- M .attach (session_name )
219- end
220-
221- --- Attach to an existing session.
222- function M .attach (session_name )
223- local sock = socket_path (session_name )
224- local bin = find_binary ()
225-
226- if vim .uv .fs_stat (sock ) == nil then
227- vim .notify (" Session '" .. session_name .. " ' not found" , vim .log .levels .ERROR )
228- return
229- end
230-
125+ --- Internal: create a terminal buffer and start a pterm bridge process.
126+ --- `cmd` is the full argv for jobstart (e.g. {"pterm","open","main"}).
127+ local function start_terminal (session_name , cmd )
231128 -- Clean up any stale buffer with the same name from a previous connection
232129 local buf_name = " pterm://" .. session_name
233130 local existing = vim .fn .bufnr (buf_name )
@@ -252,11 +149,9 @@ function M.attach(session_name)
252149 -- Let the bridge read the actual PTY size via TIOCGWINSZ instead of
253150 -- passing --cols/--rows from Lua. jobstart({term=true}) creates a PTY
254151 -- sized to the current window, and the bridge's get_winsize(stdout)
255- -- will return exactly that size. This avoids any mismatch between the
256- -- Lua-reported dimensions and the real PTY geometry.
257- local job_id = vim .fn .jobstart ({
258- bin , " attach" , session_name ,
259- }, {
152+ -- will return exactly that size.
153+ local job_id
154+ job_id = vim .fn .jobstart (cmd , {
260155 term = true ,
261156 on_exit = function (_ , exit_code , _ )
262157 vim .schedule (function ()
@@ -270,7 +165,7 @@ function M.attach(session_name)
270165 })
271166
272167 if job_id <= 0 then
273- vim .notify (" Failed to start pterm attach for '" .. session_name .. " '" , vim .log .levels .ERROR )
168+ vim .notify (" Failed to start pterm for '" .. session_name .. " '" , vim .log .levels .ERROR )
274169 if vim .api .nvim_buf_is_valid (buf ) then
275170 pcall (vim .api .nvim_buf_delete , buf , { force = true })
276171 end
@@ -344,6 +239,71 @@ function M.attach(session_name)
344239 vim .cmd (" startinsert" )
345240end
346241
242+ --- Open or attach to a session.
243+ --- If session exists, attach. Otherwise create new.
244+ --- Uses `pterm open` which handles both creation and attachment in a single
245+ --- process, eliminating the timing gap between daemon creation and bridge
246+ --- connection that caused wrong-size snapshot delivery.
247+ function M .open (session_name , args )
248+ args = args or {}
249+
250+ -- Default session name
251+ if not session_name or session_name == " " then
252+ session_name = " main"
253+ end
254+
255+ -- Already connected?
256+ if M .connections [session_name ] then
257+ -- Switch to existing buffer
258+ local conn = M .connections [session_name ]
259+ if vim .api .nvim_buf_is_valid (conn .buf ) then
260+ vim .api .nvim_set_current_buf (conn .buf )
261+ vim .cmd (" startinsert" )
262+ return
263+ else
264+ -- Buffer was closed, clean up
265+ M .connections [session_name ] = nil
266+ end
267+ end
268+
269+ local bin = find_binary ()
270+
271+ -- Build `pterm open` command with optional child command arguments.
272+ local cmd = { bin , " open" , session_name }
273+
274+ local cmd_parts = {}
275+ local found_name = false
276+ for _ , arg in ipairs (args ) do
277+ if not found_name and arg == session_name then
278+ found_name = true
279+ elseif found_name then
280+ table.insert (cmd_parts , arg )
281+ end
282+ end
283+
284+ if # cmd_parts > 0 then
285+ table.insert (cmd , " --" )
286+ for _ , part in ipairs (cmd_parts ) do
287+ table.insert (cmd , part )
288+ end
289+ end
290+
291+ start_terminal (session_name , cmd )
292+ end
293+
294+ --- Attach to an existing session.
295+ function M .attach (session_name )
296+ local sock = socket_path (session_name )
297+
298+ if vim .uv .fs_stat (sock ) == nil then
299+ vim .notify (" Session '" .. session_name .. " ' not found" , vim .log .levels .ERROR )
300+ return
301+ end
302+
303+ local bin = find_binary ()
304+ start_terminal (session_name , { bin , " attach" , session_name })
305+ end
306+
347307--- Detach from a session (does not kill the daemon).
348308function M .detach (session_name )
349309 local conn = M .connections [session_name ]
0 commit comments