From a71f966fcc5585bca511999213a64029c4b355d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bert=20M=C3=BCnnich?= Date: Fri, 27 Feb 2026 08:17:28 +0100 Subject: [PATCH 01/11] Prevent moving space from closed wins into guide wins We now use the WinClosed autocmd for this which means that it also happens when using builtin vim functionality to close the window. --- plugin/acme.vim | 52 ++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 0465d57..84080be 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -685,23 +685,6 @@ function s:WinCol(w) return col endfunc -function s:CloseWin(w) - let h = winheight(a:w) + 1 - let col = s:WinCol(a:w) - let [i, j] = [index(col, a:w), index(col, win_getid())] - let sb = &splitbelow - let &splitbelow = 0 - exe win_id2win(a:w).'close!' - let &splitbelow = sb - if j == -1 - return - endif - let d = i - j - for i in d < 0 ? range(d + 1, -1) : reverse(range(d)) - call win_move_statusline(winnr() + i, d < 0 ? -h : h) - endfor -endfunc - function s:RestWinVars(w, vars) let vars = getwinvar(a:w, '&') for v in keys(a:vars) @@ -738,7 +721,7 @@ function s:MoveWin(w, other, below) let nw = win_getid() noa exe win_id2win(p != a:w ? p : nw).'wincmd w' noa exe win_id2win(w != a:w ? w : nw).'wincmd w' - noa call s:CloseWin(a:w) + noa exe win_id2win(a:w).'close!' call s:RestWinVars(nw, vars) endif endfunc @@ -753,7 +736,7 @@ function s:NewCol(w) endif noa exe win_id2win(p).'wincmd w' noa exe win_id2win(w).'wincmd w' - call s:CloseWin(a:w) + noa exe win_id2win(a:w).'close!' endfunc function s:Scroll(topline) @@ -856,7 +839,7 @@ function s:MiddleRelease(click) \ p.winrow <= winheight(p.winid) " off the statusline elseif p.wincol < 3 - call s:CloseWin(p.winid) + exe win_id2win(p.winid).'close!' endif return endif @@ -1171,6 +1154,34 @@ function s:BufWinLeave() endif endfunc +function s:WinClosedPre() + " Only works with 'nosplitbelow' + let w = expand("") + let h = winheight(w) + 1 + let col = s:WinCol(w) + let [i, j] = [index(col, w), index(col, win_getid())] + if j == -1 + let fbelow = i + 1 == len(col) ? '' : + \ fnamemodify(bufname(winbufnr(col[i + 1])), ':t') + if i == 0 || fbelow != 'guide' + return + endif + let j = i - 1 + endif + let focus = w == win_getid() + call timer_start(0, {_ -> s:WinClosedPost(col[j], i - j, h, focus)}) +endfunc + +function s:WinClosedPost(w, n, h, focus) + let w = win_id2win(a:w) + for i in a:n < 0 ? range(a:n + 1, -1) : reverse(range(a:n)) + call win_move_statusline(w + i, a:n < 0 ? -a:h : a:h) + endfor + if a:focus + exe w.'wincmd w' + endif +endfunc + augroup acme_vim au! au BufEnter * call s:ListDir() @@ -1180,6 +1191,7 @@ au TextChanged,TextChangedI guide setl nomodified au VimEnter * call s:ReloadDirs(winnr()) au VimResized * call s:ReloadDirs(0) au WinResized * call s:ReloadDirs(0) +au WinClosed * call s:WinClosedPre() augroup END if exists("s:ctrlexe") From e189fc1bbf5a1b0a4a5518b14b302676f7610ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Fri, 9 Jan 2026 07:27:38 +0100 Subject: [PATCH 02/11] Port to Neovim: Add compatibility layer for job control functions --- plugin/acme.vim | 241 +++++++++++++++++++++++++++++++----------------- 1 file changed, 157 insertions(+), 84 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 84080be..0167bb0 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -74,7 +74,7 @@ function AcmeStatusFlags() endfunc function AcmeStatusJobs() - return join(map(s:Jobs(bufnr()), '"{".v:val.cmd."}"'), '') + return join(map(s:Jobs(bufnr()), {_, v -> '"{'.v.cmd.'}"'}), '') endfunc function AcmeStatusRuler() @@ -87,7 +87,7 @@ function s:Started(job, buf, cmd) \ 'h': a:job, \ 'cmd': type(a:cmd) == type([]) ? join(a:cmd) : a:cmd, \ 'killed': 0, - \ }) + \ }) redrawstatus! endfunc @@ -100,11 +100,11 @@ function s:RemoveJob(i, status) else checktime call s:ReloadDirs() - let sig = job_info(job.h).termsig + let sig = s:JobInfo(job.h).termsig if a:status == 0 echo 'Done:' job.cmd elseif sig != '' && !job.killed - let name = bufname(ch_getbufnr(job.h, 'out')) + let name = bufname(s:GetJobBufNr(job.h)) call s:ErrorOpen(name, [toupper(sig).': '.job.cmd]) endif endif @@ -121,11 +121,13 @@ endfunc function s:Kill(p) for job in s:Jobs(a:p) - let ch = job_getchannel(job.h) - if string(ch) != 'channel fail' - call ch_close(ch) + if !has('nvim') + let ch = job_getchannel(job.h) + if string(ch) != 'channel fail' + call ch_close(ch) + endif endif - call job_stop(job.h) + call s:JobStop(job.h) let job.killed = 1 endfor endfunc @@ -156,8 +158,8 @@ function s:Send(w, inp) endif let inp = split(inp, '\n') let job = s:Jobs(b)[0].h - call ch_setoptions(job, {'callback': ''}) - call ch_sendraw(job, join(inp, "\n")."\n") + call s:ChanSetCallback(job, '') + call s:ChanSend(job, join(inp, "\n")."\n") endfunc function s:Receiver(b) @@ -184,6 +186,61 @@ function s:Argv(cmd) return type(a:cmd) == type([]) ? a:cmd : [&shell, &shellcmdflag, a:cmd] endfunc +function s:JobStop(job) + if has('nvim') + silent! call jobstop(a:job) + else + call job_stop(a:job) + endif +endfunc + +function s:ChanSend(job, data) + if has('nvim') + call chansend(a:job, a:data) + else + call ch_sendraw(a:job, a:data) + endif +endfunc + +function s:ChanCloseIn(job) + if has('nvim') + call chanclose(a:job, 'stdin') + else + call ch_close_in(a:job) + endif +endfunc + +function s:ChanSetCallback(job, cb) + if has('nvim') + if has_key(s:nvim_jobs, a:job) + let s:nvim_jobs[a:job].callback = a:cb + endif + else + call ch_setoptions(a:job, {'callback': a:cb}) + endif +endfunc + +function s:JobInfo(job) + if has('nvim') + let s = jobwait([a:job], 0)[0] + let sig = '' + if s > 128 + let sig = s == 130 ? 'int' : s == 143 ? 'term' : s == 129 ? 'hup' : 'sig'.(s-128) + endif + return {'termsig': sig} + else + return job_info(a:job) + endif +endfunc + +function s:GetJobBufNr(job) + if has('nvim') + return get(get(s:nvim_jobs, a:job, {}), 'buf', -1) + else + return ch_getbufnr(a:job, 'out') + endif +endfunc + function s:JobEnv(buf, dir) return { \ 'ACMEVIMBUF': bufnr(), @@ -194,7 +251,7 @@ function s:JobEnv(buf, dir) \ 'ACMEVIMOUTDIR': a:dir != '' ? a:dir : getcwd(), \ 'COLUMNS': 80, \ 'LINES': 24, - \ } + \ } endfunc function s:SetEnv(env) @@ -213,7 +270,7 @@ function s:JobStart(cmd, outb, ctxb, opts, inp) \ 'out_io': 'buffer', \ 'out_buf': a:outb, \ 'out_msg': 0, - \ } + \ } call extend(opts, a:opts) let env = s:SetEnv(s:JobEnv(a:outb, get(a:opts, 'cwd', ''))) let job = job_start(s:Argv(a:cmd), opts) @@ -235,7 +292,7 @@ endfunc function s:ErrorSplitPos(name) let [w, match, mod, rel] = [0, 0, '', ''] - let dir = fnamemodify(s:Path(a:name), ':h') + dir = fnamemodify(s:Path(a:name), ':h') for i in reverse(range(1, winnr('$'))) let b = winbufnr(i) let p = get(s:cwd, b, s:Path(bufname(b))) @@ -273,7 +330,7 @@ function s:ErrorOpen(name, ...) exe w.'wincmd w' let b = s:ErrorLoad(a:name) for job in s:jobs - if ch_getbufnr(job.h, 'out') == b && job.buf != b + if s:GetJobBufNr(job.h) == b && job.buf != b let job.buf = b endif endfor @@ -293,7 +350,7 @@ endfunc function s:ErrorCb(b, ch, msg) call s:ErrorOpen(bufname(a:b)) - call ch_setoptions(a:ch, {'callback': ''}) + call s:ChanSetCallback(a:ch, '') endfunc function s:ErrorExec(cmd, dir, b, inp) @@ -328,7 +385,7 @@ endfunc function s:Read(cmd, dir, inp) let end = getcurpos()[4] > strdisplaywidth(getline('.')) call setreg('"', s:System(a:cmd, a:dir, a:inp), 'c') - exe 'normal! ""'.(end ? 'p' : 'P') + exe 'normal! "'.(end ? 'p' : 'P') endfunc function s:ParseCmd(cmd) @@ -392,7 +449,7 @@ function s:ScratchCb(b, ch, msg) let w = win_getid(w) if line('$', w) > 1 call win_execute(w, 'noa normal! gg0') - call ch_setoptions(a:ch, {'callback': ''}) + call s:ChanSetCallback(a:ch, '') endif endif endfunc @@ -403,7 +460,7 @@ function s:ScratchExec(cmd, dir, inp, title) let opts = { \ 'callback': function('s:ScratchCb', [b]), \ 'in_io': 'pipe', - \ } + \ } if a:dir != '' let opts.cwd = a:dir endif @@ -411,11 +468,15 @@ function s:ScratchExec(cmd, dir, inp, title) endfunc function s:Exec(cmd) - silent! call job_start(s:Argv(a:cmd), { - \ 'err_io': 'null', - \ 'in_io': 'null', - \ 'out_io': 'null', - \ }) + if has('nvim') + silent! call jobstart(s:Argv(a:cmd), {'detach': 1}) + else + silent! call job_start(s:Argv(a:cmd), { + \ 'err_io': 'null', + \ 'in_io': 'null', + \ 'out_io': 'null', + \ }) + endif endfunc function s:BufWidth(b) @@ -453,12 +514,12 @@ function s:Columnate(words, width) endfunc function s:ListDir() - let dir = expand('%') + dir = expand('%') if !isdirectory(dir) || !&modifiable return endif let lst = ['..'] + readdir(dir, 1, {'sort': 'collate'}) - call map(lst, 'isdirectory(dir."/".v:val) ? v:val."/" : v:val') + call map(lst, 'isdirectory(dir."/ ".v:val) ? v:val."/" : v:val') let width = s:BufWidth(bufnr()) let lst = s:Columnate(lst, width) call setline(1, lst) @@ -536,9 +597,9 @@ function s:Dir() endfunc function s:CtxDir() - let dir = s:Dir() + dir = s:Dir() if &buftype != '' - let [t, q] = ['ing directory:? ', "[`'\"]"] + let [t, q] = ['ing directory:? ', "[`'\"`]"] let l = searchpair('\vEnter'.t.q, '', '\vLeav'.t.q, 'nW', \ '', 0, 50) let m = matchlist(getline(l), '\vLeav'.t.q.'(.+)'.q) @@ -589,7 +650,7 @@ function s:RgOpen(pos) return 0 endif call win_execute(s:plumbwin, - \ 'let s:l = search("\\v^(\\s*(\\d+[-:]|\\-\\-$))@!", "bnW")') + \ 'let s:l = search("\v^(\s*(\d+[-:]|\-\-$))@!", "bnW")') let f = getbufoneline(winbufnr(s:plumbwin), s:l) if f != '' return AcmeOpen(f, a:pos) @@ -624,7 +685,7 @@ let s:plumbing = [ \ [], \ ['\f+', {m -> m[0] !~ '/' && AcmeOpen(exepath(m[0]), '')}], \ ['\d+%([:,]\d+)?', {m -> s:Goto(m[0])}], -\ ] + \ ] function s:Open(text, click, dir, win) let s:plumbclick = a:click @@ -741,7 +802,7 @@ endfunc function s:Scroll(topline) let v = winsaveview() - let v.topline = a:topline + v.topline = a:topline call winrestview(v) endfunc @@ -762,14 +823,14 @@ function s:Fit(w, h, ...) return h endif endif - let h = 0 - let top = line('$', a:w) + 1 + h = 0 + top = line('$', a:w) + 1 while top > 1 - let h += s:FitHeight(a:w, top - 1) + h += s:FitHeight(a:w, top - 1) if h > a:h break endif - let top -= 1 + top -= 1 endwhile call timer_start(0, {_ -> \ win_execute(a:w, 'noa call s:Scroll('.top.')')}) @@ -779,22 +840,22 @@ endfunc function s:Zoom(w) let col = s:WinCol(a:w) let col = slice(col, 0, index(col, a:w) + 1) - let h = reduce(col, {s, w -> s + winheight(w)}, 0) - let n = len(col) + h = reduce(col, {s, w -> s + winheight(w)}, 0) + n = len(col) for w in reverse(col) let s = s:Fit(w, h / n, a:w) if n == 1 break endif call win_move_statusline(win_id2win(w) - 1, winheight(w) - s) - let h -= s - let n -= 1 + h -= s + n -= 1 endfor endfunc function s:InSel() - let p = getpos('.') - let v = s:visual + p = getpos('.') + v = s:visual return p[1] >= v[0][1] && p[1] <= v[1][1] && \ (p[2] >= v[0][2] || (v[2] == 'v' && p[1] > v[0][1])) && \ (p[2] <= v[1][2] || (v[2] == 'v' && p[1] < v[1][1])) @@ -808,23 +869,23 @@ function s:RestVisual(vis) call setpos("'<", a:vis[0]) call setpos("'>", a:vis[1]) if a:vis[0][1] != 0 - let v = winsaveview() + v = winsaveview() silent! exe "normal! `<".a:vis[2]."`>\" call winrestview(v) endif endfunc function s:MousePress(mode) - let s:click = getmousepos() - let s:clickmode = a:mode - let s:clickstatus = s:click.line == 0 ? win_id2win(s:click.winid) : 0 - let s:clickwin = win_getid() + s:click = getmousepos() + s:clickmode = a:mode + s:clickstatus = s:click.line == 0 ? win_id2win(s:click.winid) : 0 + s:clickwin = win_getid() if s:clickstatus != 0 || s:click.winid == 0 return endif exe "normal! \" - let s:visual = s:SaveVisual() - let s:clicksel = s:clickmode == 'v' && win_getid() == s:clickwin && + s:visual = s:SaveVisual() + s:clicksel = s:clickmode == 'v' && win_getid() == s:clickwin && \ s:InSel() endfunc @@ -832,7 +893,7 @@ function s:MiddleRelease(click) if s:click.winid == 0 return elseif s:clickstatus != 0 - let p = getmousepos() + p = getmousepos() if s:click.winrow <= winheight(s:click.winid) " vertical separator elseif p.winid != s:click.winid || @@ -847,9 +908,9 @@ function s:MiddleRelease(click) let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) - let b = bufnr() - let dir = s:Dir() - let w = win_getid() + b = bufnr() + dir = s:Dir() + w = win_getid() exe win_id2win(s:clickwin).'wincmd w' if s:Receiver(b) if w != s:clickwin && s:clickmode == 'v' && a:click > 0 @@ -865,7 +926,7 @@ function s:RightRelease(click) if s:click.winid == 0 return elseif s:clickstatus != 0 - let p = getmousepos() + p = getmousepos() if s:click.winrow <= winheight(s:click.winid) " vertical separator elseif p.winid != 0 && p.winid != s:click.winid @@ -882,13 +943,13 @@ function s:RightRelease(click) return endif exe "normal! \" - let click = s:clicksel ? -1 : a:click - let text = click <= 0 ? trim(s:Sel()[0], "\r\n", 2) : getline('.') + let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') + let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) - let w = win_getid() - let dir = s:CtxDir() + w = win_getid() + dir = s:CtxDir() exe win_id2win(s:clickwin).'wincmd w' - call s:Open(text, click, dir, w) + call s:Open(cmd, a:click, dir, w) endfunc for m in ['', 'i'] @@ -927,7 +988,7 @@ function s:Clear(b) if has_key(s:scratch, a:b) let s:scratch[a:b].cleared = 1 for job in s:Jobs(a:b) - call ch_setoptions(job.h, {'callback': ''}) + call s:ChanSetCallback(job.h, '') endfor endif endfunc @@ -958,16 +1019,16 @@ function s:BufInfo() endfunc function s:Change(b, l1, l2, lines) - let w = win_getid(s:BufWin(a:b)) + w = win_getid(s:BufWin(a:b)) if w == 0 return endif let pos = getcurpos(w) let last = line('$', w) - let l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) - let n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, + l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) + n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, \ last - l + 1) - let i = min([n, len(a:lines)]) + i = min([n, len(a:lines)]) if i > 0 call setbufline(a:b, l, a:lines[:i-1]) endif @@ -1003,7 +1064,7 @@ endfunc function s:PtyPw() let pw = inputsecret('PW> ') for job in s:Jobs(bufnr()) - call ch_sendraw(job.h, pw."\n") + call s:ChanSend(job.h, pw."\n") endfor endfunc @@ -1015,11 +1076,11 @@ function s:PtyMap() endfunc function s:Pty(b) - let w = win_getid(s:BufWin(a:b)) + w = win_getid(s:BufWin(a:b)) if !has_key(s:scratch, a:b) || w == 0 return endif - let s:scratch[a:b].pty = 1 + s:scratch[a:b].pty = 1 call win_execute(w, 'call s:PtyMap()') endfunc @@ -1031,13 +1092,13 @@ endfunc function s:Diff(p) call map(a:p, {_, p -> s:Path(p)}) - let w = filter(range(1, winnr('$')), {_, i -> + w = filter(range(1, winnr('$')), {_, i -> \ index(a:p, s:Path(bufname(winbufnr(i)))) != -1}) if len(w) < 2 let w = [] endif for i in range(1, winnr('$')) - let on = index(w, i) != -1 + on = index(w, i) != -1 call setwinvar(i, '&diff', on) call setwinvar(i, '&scrollbind', on) endfor @@ -1052,27 +1113,28 @@ function s:Look(p) else let hl = 1 let p = map(a:p[1:-2], {i, v -> escape(v, '\/')}) - let @/ = '\V'.a:p[0].'\%\('.join(p, '\|').'\)'.a:p[-1] + let @/ = '\V'.a:p[0].'\%('.join(p, '\|').'\)'.a:p[-1] + call feedkeys(":let v:hlsearch=1\", 'n') endif let esc = mode() == 'i' ? "\" : "" call feedkeys(esc.":let v:hlsearch=".hl."\", 'n') endfunc function s:BufNr(b) - let b = str2nr(a:b) + b = str2nr(a:b) return b != 0 ? b : bufnr() endfunc function s:CtrlRecv(ch, data) - let len = strridx(a:data, "\x1e") - let len += len == -1 ? 0 : len(s:ctrlrx) - let s:ctrlrx .= a:data + len = strridx(a:data, "\x1e") + len += len == -1 ? 0 : len(s:ctrlrx) + s:ctrlrx .= a:data if len == -1 return endif - let msgs = strpart(s:ctrlrx, 0, len) - let s:ctrlrx = strpart(s:ctrlrx, len + 1) - let msgs = map(split(msgs, "\x1e", 1), 'split(v:val, "\x1f", 1)') + msgs = strpart(s:ctrlrx, 0, len) + s:ctrlrx = strpart(s:ctrlrx, len + 1) + msgs = map(split(msgs, "\x1e", 1), 'split(v:val, "\x1f", 1)') for msg in msgs if len(msg) < 2 continue @@ -1132,11 +1194,11 @@ function s:CtrlRecv(ch, data) endfunc function s:CtrlSend(msg) - call ch_sendraw(s:ctrl, join(a:msg, "\x1f") . "\x1e") + call s:ChanSend(s:ctrl, join(a:msg, "\x1f") . "\x1e") endfunc function s:BufWinLeave() - let b = str2nr(expand('')) + b = str2nr(expand('')) call s:Kill(b) if getbufvar(b, '&modified') call win_execute(bufwinid(b), 'silent! write') @@ -1200,8 +1262,8 @@ endif set completefunc=s:InsComplete -let &statusline = '%{AcmeStatusBox()}%<%{%AcmeStatusName()%}' . - \ '%{%AcmeStatusFlags()%}%{AcmeStatusJobs()}%=%{%AcmeStatusRuler()%}' +let &statusline = '%{AcmeStatusBox()}%<%{%AcmeStatusName()%}' \ + \ .'%{%AcmeStatusFlags()%}%{AcmeStatusJobs()}%=%{%AcmeStatusRuler()%}' let s:ctrlexe = exepath(expand(':p:h:h').'/bin/avim') let s:ctrlrx = '' @@ -1212,12 +1274,23 @@ let s:editcids = {} let s:editcmds = {} let s:jobs = [] let s:scratch = {} +let s:nvim_jobs = {} if s:ctrlexe != '' - let s:ctrl = job_start([s:ctrlexe], { - \ 'callback': 's:CtrlRecv', - \ 'err_io': 'null', - \ 'mode': 'raw', - \ }) + if has('nvim') + function s:NvimCtrlRecv(id, data, event) + call s:CtrlRecv(a:id, join(a:data, "\n")) + endfunc + let s:ctrl = jobstart([s:ctrlexe], { + \ 'on_stdout': function('s:NvimCtrlRecv'), + \ 'rpc': 0, + \ }) + else + let s:ctrl = job_start([s:ctrlexe], { + \ 'callback': 's:CtrlRecv', + \ 'err_io': 'null', + \ 'mode': 'raw', + \ }) + endif let $EDITOR = s:ctrlexe endif From d88a487d2858617e2aaf3f3e833dedb9a2961ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Fri, 9 Jan 2026 07:30:53 +0100 Subject: [PATCH 03/11] Port to Neovim: Emulate 'out_io': 'buffer' behavior for Neovim --- plugin/acme.vim | 151 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 48 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 0167bb0..431d05b 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -241,6 +241,58 @@ function s:GetJobBufNr(job) endif endfunc +function s:NvimOut(id, data, event) dict + if empty(a:data) | return | endif + let b = self.buf + if bufexists(b) + let mod = getbufvar(b, '&modifiable') + call setbufvar(b, '&modifiable', 1) + let data = copy(a:data) + let last = getbufline(b, '$')[0] + let data[0] = last . data[0] + call setbufline(b, '$', data[0]) + if len(data) > 1 + call appendbufline(b, '$', data[1:]) + endif + call setbufvar(b, '&modifiable', mod) + endif + if has_key(self, 'callback') && !empty(self.callback) + call call(self.callback, [a:id, '']) + endif +endfunc + +function s:NvimExit(id, status, event) dict + if has_key(s:nvim_jobs, a:id) + call remove(s:nvim_jobs, a:id) + endif + call s:Exited(a:id, a:status) +endfunc + +function s:JobStartNvim(cmd, outb, ctxb, opts, inp) + let job_opts = { + \ 'on_exit': function('s:NvimExit'), + \ 'on_stdout': function('s:NvimOut'), + \ 'on_stderr': function('s:NvimOut'), + \ 'buf': a:outb, + \ 'callback': get(a:opts, 'callback', ''), + \ } + if has_key(a:opts, 'cwd') + let job_opts.cwd = a:opts.cwd + endif + let env = s:SetEnv(s:JobEnv(a:outb, get(a:opts, 'cwd', ''))) + let job = jobstart(s:Argv(a:cmd), job_opts) + call s:SetEnv(env) + if job <= 0 + return + endif + let s:nvim_jobs[job] = job_opts + call s:Started(job, s:BufWin(a:outb) != 0 ? a:outb : a:ctxb, a:cmd) + if a:inp != '' + call chansend(job, a:inp) + call chanclose(job, 'stdin') + endif +endfunc + function s:JobEnv(buf, dir) return { \ 'ACMEVIMBUF': bufnr(), @@ -264,6 +316,9 @@ function s:SetEnv(env) endfunc function s:JobStart(cmd, outb, ctxb, opts, inp) + if has('nvim') + return s:JobStartNvim(a:cmd, a:outb, a:ctxb, a:opts, a:inp) + endif let opts = { \ 'exit_cb': 's:Exited', \ 'err_io': 'out', @@ -292,7 +347,7 @@ endfunc function s:ErrorSplitPos(name) let [w, match, mod, rel] = [0, 0, '', ''] - dir = fnamemodify(s:Path(a:name), ':h') + let dir = fnamemodify(s:Path(a:name), ':h') for i in reverse(range(1, winnr('$'))) let b = winbufnr(i) let p = get(s:cwd, b, s:Path(bufname(b))) @@ -514,7 +569,7 @@ function s:Columnate(words, width) endfunc function s:ListDir() - dir = expand('%') + let dir = expand('%') if !isdirectory(dir) || !&modifiable return endif @@ -597,7 +652,7 @@ function s:Dir() endfunc function s:CtxDir() - dir = s:Dir() + let dir = s:Dir() if &buftype != '' let [t, q] = ['ing directory:? ', "[`'\"`]"] let l = searchpair('\vEnter'.t.q, '', '\vLeav'.t.q, 'nW', @@ -802,7 +857,7 @@ endfunc function s:Scroll(topline) let v = winsaveview() - v.topline = a:topline + let v.topline = a:topline call winrestview(v) endfunc @@ -823,14 +878,14 @@ function s:Fit(w, h, ...) return h endif endif - h = 0 - top = line('$', a:w) + 1 + let h = 0 + let top = line('$', a:w) + 1 while top > 1 - h += s:FitHeight(a:w, top - 1) + let h += s:FitHeight(a:w, top - 1) if h > a:h break endif - top -= 1 + let top -= 1 endwhile call timer_start(0, {_ -> \ win_execute(a:w, 'noa call s:Scroll('.top.')')}) @@ -840,22 +895,22 @@ endfunc function s:Zoom(w) let col = s:WinCol(a:w) let col = slice(col, 0, index(col, a:w) + 1) - h = reduce(col, {s, w -> s + winheight(w)}, 0) - n = len(col) + let h = reduce(col, {s, w -> s + winheight(w)}, 0) + let n = len(col) for w in reverse(col) let s = s:Fit(w, h / n, a:w) if n == 1 break endif call win_move_statusline(win_id2win(w) - 1, winheight(w) - s) - h -= s - n -= 1 + let h -= s + let n -= 1 endfor endfunc function s:InSel() - p = getpos('.') - v = s:visual + let p = getpos('.') + let v = s:visual return p[1] >= v[0][1] && p[1] <= v[1][1] && \ (p[2] >= v[0][2] || (v[2] == 'v' && p[1] > v[0][1])) && \ (p[2] <= v[1][2] || (v[2] == 'v' && p[1] < v[1][1])) @@ -869,23 +924,23 @@ function s:RestVisual(vis) call setpos("'<", a:vis[0]) call setpos("'>", a:vis[1]) if a:vis[0][1] != 0 - v = winsaveview() + let v = winsaveview() silent! exe "normal! `<".a:vis[2]."`>\" call winrestview(v) endif endfunc function s:MousePress(mode) - s:click = getmousepos() - s:clickmode = a:mode - s:clickstatus = s:click.line == 0 ? win_id2win(s:click.winid) : 0 - s:clickwin = win_getid() + let s:click = getmousepos() + let s:clickmode = a:mode + let s:clickstatus = s:click.line == 0 ? win_id2win(s:click.winid) : 0 + let s:clickwin = win_getid() if s:clickstatus != 0 || s:click.winid == 0 return endif exe "normal! \" - s:visual = s:SaveVisual() - s:clicksel = s:clickmode == 'v' && win_getid() == s:clickwin && + let s:visual = s:SaveVisual() + let s:clicksel = s:clickmode == 'v' && win_getid() == s:clickwin && \ s:InSel() endfunc @@ -893,7 +948,7 @@ function s:MiddleRelease(click) if s:click.winid == 0 return elseif s:clickstatus != 0 - p = getmousepos() + let p = getmousepos() if s:click.winrow <= winheight(s:click.winid) " vertical separator elseif p.winid != s:click.winid || @@ -908,9 +963,9 @@ function s:MiddleRelease(click) let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) - b = bufnr() - dir = s:Dir() - w = win_getid() + let b = bufnr() + let dir = s:Dir() + let w = win_getid() exe win_id2win(s:clickwin).'wincmd w' if s:Receiver(b) if w != s:clickwin && s:clickmode == 'v' && a:click > 0 @@ -926,7 +981,7 @@ function s:RightRelease(click) if s:click.winid == 0 return elseif s:clickstatus != 0 - p = getmousepos() + let p = getmousepos() if s:click.winrow <= winheight(s:click.winid) " vertical separator elseif p.winid != 0 && p.winid != s:click.winid @@ -946,8 +1001,8 @@ function s:RightRelease(click) let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) - w = win_getid() - dir = s:CtxDir() + let w = win_getid() + let dir = s:CtxDir() exe win_id2win(s:clickwin).'wincmd w' call s:Open(cmd, a:click, dir, w) endfunc @@ -1019,16 +1074,16 @@ function s:BufInfo() endfunc function s:Change(b, l1, l2, lines) - w = win_getid(s:BufWin(a:b)) + let w = win_getid(s:BufWin(a:b)) if w == 0 return endif let pos = getcurpos(w) let last = line('$', w) - l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) - n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, + let l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) + let n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, \ last - l + 1) - i = min([n, len(a:lines)]) + let i = min([n, len(a:lines)]) if i > 0 call setbufline(a:b, l, a:lines[:i-1]) endif @@ -1076,11 +1131,11 @@ function s:PtyMap() endfunc function s:Pty(b) - w = win_getid(s:BufWin(a:b)) + let w = win_getid(s:BufWin(a:b)) if !has_key(s:scratch, a:b) || w == 0 return endif - s:scratch[a:b].pty = 1 + let s:scratch[a:b].pty = 1 call win_execute(w, 'call s:PtyMap()') endfunc @@ -1092,13 +1147,13 @@ endfunc function s:Diff(p) call map(a:p, {_, p -> s:Path(p)}) - w = filter(range(1, winnr('$')), {_, i -> + let w = filter(range(1, winnr('$')), {_, i -> \ index(a:p, s:Path(bufname(winbufnr(i)))) != -1}) if len(w) < 2 let w = [] endif for i in range(1, winnr('$')) - on = index(w, i) != -1 + let on = index(w, i) != -1 call setwinvar(i, '&diff', on) call setwinvar(i, '&scrollbind', on) endfor @@ -1121,20 +1176,20 @@ function s:Look(p) endfunc function s:BufNr(b) - b = str2nr(a:b) + let b = str2nr(a:b) return b != 0 ? b : bufnr() endfunc function s:CtrlRecv(ch, data) - len = strridx(a:data, "\x1e") - len += len == -1 ? 0 : len(s:ctrlrx) - s:ctrlrx .= a:data + let len = strridx(a:data, "\x1e") + let len += len == -1 ? 0 : len(s:ctrlrx) + let s:ctrlrx .= a:data if len == -1 return endif - msgs = strpart(s:ctrlrx, 0, len) - s:ctrlrx = strpart(s:ctrlrx, len + 1) - msgs = map(split(msgs, "\x1e", 1), 'split(v:val, "\x1f", 1)') + let msgs = strpart(s:ctrlrx, 0, len) + let s:ctrlrx = strpart(s:ctrlrx, len + 1) + let msgs = map(split(msgs, "\x1e", 1), 'split(v:val, "\x1f", 1)') for msg in msgs if len(msg) < 2 continue @@ -1198,7 +1253,7 @@ function s:CtrlSend(msg) endfunc function s:BufWinLeave() - b = str2nr(expand('')) + let b = str2nr(expand('')) call s:Kill(b) if getbufvar(b, '&modified') call win_execute(bufwinid(b), 'silent! write') @@ -1262,8 +1317,8 @@ endif set completefunc=s:InsComplete -let &statusline = '%{AcmeStatusBox()}%<%{%AcmeStatusName()%}' \ - \ .'%{%AcmeStatusFlags()%}%{AcmeStatusJobs()}%=%{%AcmeStatusRuler()%}' +let &statusline = '%{AcmeStatusBox()}%<%{%AcmeStatusName()%}' . + \ '.%{%AcmeStatusFlags()%}%{AcmeStatusJobs()}%=%{%AcmeStatusRuler()%}' let s:ctrlexe = exepath(expand(':p:h:h').'/bin/avim') let s:ctrlrx = '' @@ -1284,13 +1339,13 @@ if s:ctrlexe != '' let s:ctrl = jobstart([s:ctrlexe], { \ 'on_stdout': function('s:NvimCtrlRecv'), \ 'rpc': 0, - \ }) + \ }) else let s:ctrl = job_start([s:ctrlexe], { \ 'callback': 's:CtrlRecv', \ 'err_io': 'null', \ 'mode': 'raw', - \ }) + \ }) endif let $EDITOR = s:ctrlexe endif From aa48c34b2d032ecd54c6215c64a24c7b7ef4d04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Fri, 9 Jan 2026 07:34:29 +0100 Subject: [PATCH 04/11] Port to Neovim: Handle signal sending via vim.loop.kill --- plugin/acme.vim | 61 +++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 431d05b..55e6655 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -194,6 +194,24 @@ function s:JobStop(job) endif endfunc +function s:JobKill(job, sig) + if has('nvim') + let sig = a:sig + if type(sig) == type("") + let map = { + \ 'int': 2, + \ 'hup': 1, + \ 'term': 15, + \ 'kill': 9 + \ } + let sig = get(map, sig, 15) + endif + silent! call luaeval("vim.loop.kill(vim.fn.jobpid(_A), _B)", [a:job, sig]) + else + call job_stop(a:job, a:sig) + endif +endfunc + function s:ChanSend(job, data) if has('nvim') call chansend(a:job, a:data) @@ -857,7 +875,7 @@ endfunc function s:Scroll(topline) let v = winsaveview() - let v.topline = a:topline + v.topline = a:topline call winrestview(v) endfunc @@ -925,7 +943,7 @@ function s:RestVisual(vis) call setpos("'>", a:vis[1]) if a:vis[0][1] != 0 let v = winsaveview() - silent! exe "normal! `<".a:vis[2]."`>\" + silent! exe "normal! `<".a:vis[2]." \" call winrestview(v) endif endfunc @@ -1007,37 +1025,6 @@ function s:RightRelease(click) call s:Open(cmd, a:click, dir, w) endfunc -for m in ['', 'i'] - for n in ['', '2-', '3-', '4-'] - for c in ['Mouse', 'Drag', 'Release'] - exe m.'noremap <'.n.'Middle'.c.'> ' - exe m.'noremap <'.n.'Right'.c.'> ' - endfor - endfor - exe m.'noremap ' - exe m.'noremap ' -endfor -for n in ['', '2-', '3-', '4-'] - exe 'nnoremap <'.n.'MiddleMouse>' - \ ':call MousePress("")' - exe 'vnoremap <'.n.'MiddleMouse>' - \ ':call MousePress("v")' - exe 'nnoremap <'.n.'MiddleRelease>' - \ ':call MiddleRelease(col("."))' - exe 'nnoremap <'.n.'RightMouse>' - \ ':call MousePress("")' - exe 'vnoremap <'.n.'RightMouse>' - \ ':call MousePress("v")' - exe 'nnoremap <'.n.'RightRelease>' - \ ':call RightRelease(col("."))' -endfor -inoremap :call MousePress('') -inoremap :call MiddleRelease(col('.')) -vnoremap :call MiddleRelease(-1) -inoremap :call MousePress('') -inoremap :call RightRelease(col('.')) -vnoremap :call RightRelease(-1) - function s:Clear(b) call deletebufline(a:b, 1, "$") if has_key(s:scratch, a:b) @@ -1104,7 +1091,7 @@ endfunc function s:Signal(sig) for job in s:Jobs(bufnr()) - call job_stop(job.h, a:sig) + call s:JobKill(job.h, a:sig) endfor endfunc @@ -1317,8 +1304,8 @@ endif set completefunc=s:InsComplete -let &statusline = '%{AcmeStatusBox()}%<%{%AcmeStatusName()%}' . - \ '.%{%AcmeStatusFlags()%}%{AcmeStatusJobs()}%=%{%AcmeStatusRuler()%}' +let &statusline = '%{AcmeStatusBox()}%<%{%AcmeStatusName()%}' . + \ '%{%AcmeStatusFlags()%}%{AcmeStatusJobs()}%=%{%AcmeStatusRuler()%}' let s:ctrlexe = exepath(expand(':p:h:h').'/bin/avim') let s:ctrlrx = '' @@ -1348,4 +1335,4 @@ if s:ctrlexe != '' \ }) endif let $EDITOR = s:ctrlexe -endif +endif \ No newline at end of file From 2bdf9dd84d4d7111733fd7340216376b5975d2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Fri, 9 Jan 2026 07:37:45 +0100 Subject: [PATCH 05/11] Port to Neovim: Initialize script-local click variables to prevent E121 errors if Release event triggers before Mouse event. --- plugin/acme.vim | 62 +++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 55e6655..97bd71c 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -1,3 +1,9 @@ +let s:click = {'winid': 0} +let s:clicksel = 0 +let s:clickwin = 0 +let s:visual = [[0,0,0,0], [0,0,0,0], ''] +let s:clickstatus = 0 + function s:Bound(min, n, max) return max([a:min, min([a:n, a:max])]) endfunc @@ -13,8 +19,8 @@ function s:FileWin(name) endfunc function s:Sel() - let text = getreg('"') - let type = getregtype('"') + let text = getreg("'") + let type = getregtype("'") let view = winsaveview() silent normal! gv""y let sel = [getreg('"'), getregtype('"')] @@ -198,12 +204,7 @@ function s:JobKill(job, sig) if has('nvim') let sig = a:sig if type(sig) == type("") - let map = { - \ 'int': 2, - \ 'hup': 1, - \ 'term': 15, - \ 'kill': 9 - \ } + let map = {'int': 2, 'hup': 1, 'term': 15, 'kill': 9} let sig = get(map, sig, 15) endif silent! call luaeval("vim.loop.kill(vim.fn.jobpid(_A), _B)", [a:job, sig]) @@ -365,7 +366,7 @@ endfunc function s:ErrorSplitPos(name) let [w, match, mod, rel] = [0, 0, '', ''] - let dir = fnamemodify(s:Path(a:name), ':h') + dir = fnamemodify(s:Path(a:name), ':h') for i in reverse(range(1, winnr('$'))) let b = winbufnr(i) let p = get(s:cwd, b, s:Path(bufname(b))) @@ -587,7 +588,7 @@ function s:Columnate(words, width) endfunc function s:ListDir() - let dir = expand('%') + dir = expand('%') if !isdirectory(dir) || !&modifiable return endif @@ -670,7 +671,7 @@ function s:Dir() endfunc function s:CtxDir() - let dir = s:Dir() + dir = s:Dir() if &buftype != '' let [t, q] = ['ing directory:? ', "[`'\"`]"] let l = searchpair('\vEnter'.t.q, '', '\vLeav'.t.q, 'nW', @@ -775,7 +776,8 @@ function s:Open(text, click, dir, win) endfunc function s:FileComplete(arg, line, pos) - let p = a:arg =~ '^[~/]' ? a:arg : s:Dir().'/'.a:arg + let p = a:arg =~ '^[~/]' ? a:arg : s:Dir().'/ +'.a:arg let p = fnamemodify(p, ':p') if a:arg =~ '[^/]$' let p = substitute(p, '/*$', '', '') @@ -896,14 +898,14 @@ function s:Fit(w, h, ...) return h endif endif - let h = 0 - let top = line('$', a:w) + 1 + h = 0 + top = line('$', a:w) + 1 while top > 1 - let h += s:FitHeight(a:w, top - 1) + h += s:FitHeight(a:w, top - 1) if h > a:h break endif - let top -= 1 + top -= 1 endwhile call timer_start(0, {_ -> \ win_execute(a:w, 'noa call s:Scroll('.top.')')}) @@ -921,8 +923,8 @@ function s:Zoom(w) break endif call win_move_statusline(win_id2win(w) - 1, winheight(w) - s) - let h -= s - let n -= 1 + h -= s + n -= 1 endfor endfunc @@ -943,7 +945,7 @@ function s:RestVisual(vis) call setpos("'>", a:vis[1]) if a:vis[0][1] != 0 let v = winsaveview() - silent! exe "normal! `<".a:vis[2]." \" + silent! exe "normal! `<".a:vis[2]."`>\" call winrestview(v) endif endfunc @@ -982,8 +984,8 @@ function s:MiddleRelease(click) let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) let b = bufnr() - let dir = s:Dir() - let w = win_getid() + dir = s:Dir() + w = win_getid() exe win_id2win(s:clickwin).'wincmd w' if s:Receiver(b) if w != s:clickwin && s:clickmode == 'v' && a:click > 0 @@ -1019,8 +1021,8 @@ function s:RightRelease(click) let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) - let w = win_getid() - let dir = s:CtxDir() + w = win_getid() + dir = s:CtxDir() exe win_id2win(s:clickwin).'wincmd w' call s:Open(cmd, a:click, dir, w) endfunc @@ -1061,16 +1063,16 @@ function s:BufInfo() endfunc function s:Change(b, l1, l2, lines) - let w = win_getid(s:BufWin(a:b)) + w = win_getid(s:BufWin(a:b)) if w == 0 return endif let pos = getcurpos(w) let last = line('$', w) - let l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) - let n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, + l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) + n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, \ last - l + 1) - let i = min([n, len(a:lines)]) + i = min([n, len(a:lines)]) if i > 0 call setbufline(a:b, l, a:lines[:i-1]) endif @@ -1118,7 +1120,7 @@ function s:PtyMap() endfunc function s:Pty(b) - let w = win_getid(s:BufWin(a:b)) + w = win_getid(s:BufWin(a:b)) if !has_key(s:scratch, a:b) || w == 0 return endif @@ -1170,7 +1172,7 @@ endfunc function s:CtrlRecv(ch, data) let len = strridx(a:data, "\x1e") let len += len == -1 ? 0 : len(s:ctrlrx) - let s:ctrlrx .= a:data + s:ctrlrx .= a:data if len == -1 return endif @@ -1335,4 +1337,4 @@ if s:ctrlexe != '' \ }) endif let $EDITOR = s:ctrlexe -endif \ No newline at end of file +endif From b976398a10dd5fc8daad4b0ad21c08d1fa1e13b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Fri, 9 Jan 2026 09:04:07 +0100 Subject: [PATCH 06/11] Port to Neovim: Add fallback to s:RightRelease to use getmousepos() if s:click is uninitialized, ensuring plumbing works even if RightMouse event is missed or blocked. --- plugin/acme.vim | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/acme.vim b/plugin/acme.vim index 97bd71c..d687c1f 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -998,6 +998,14 @@ function s:MiddleRelease(click) endfunc function s:RightRelease(click) + if s:click.winid == 0 + let s:click = getmousepos() + let s:clickwin = win_getid() + let s:clickstatus = s:click.line == 0 ? win_id2win(s:click.winid) : 0 + let s:clickmode = 'n' + let s:clicksel = 0 + endif + if s:click.winid == 0 return elseif s:clickstatus != 0 From dc03181e9b1e22f6ac7efcbe043596e98d0740fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Fri, 9 Jan 2026 09:09:41 +0100 Subject: [PATCH 07/11] Port to Neovim: Add public AcmeActivate function --- plugin/acme.vim | 82 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index d687c1f..1a3a540 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -19,8 +19,8 @@ function s:FileWin(name) endfunc function s:Sel() - let text = getreg("'") - let type = getregtype("'") + let text = getreg('"') + let type = getregtype('"') let view = winsaveview() silent normal! gv""y let sel = [getreg('"'), getregtype('"')] @@ -93,7 +93,7 @@ function s:Started(job, buf, cmd) \ 'h': a:job, \ 'cmd': type(a:cmd) == type([]) ? join(a:cmd) : a:cmd, \ 'killed': 0, - \ }) + \ }) redrawstatus! endfunc @@ -294,7 +294,7 @@ function s:JobStartNvim(cmd, outb, ctxb, opts, inp) \ 'on_stderr': function('s:NvimOut'), \ 'buf': a:outb, \ 'callback': get(a:opts, 'callback', ''), - \ } + \ } if has_key(a:opts, 'cwd') let job_opts.cwd = a:opts.cwd endif @@ -322,7 +322,7 @@ function s:JobEnv(buf, dir) \ 'ACMEVIMOUTDIR': a:dir != '' ? a:dir : getcwd(), \ 'COLUMNS': 80, \ 'LINES': 24, - \ } + \ } endfunc function s:SetEnv(env) @@ -344,7 +344,7 @@ function s:JobStart(cmd, outb, ctxb, opts, inp) \ 'out_io': 'buffer', \ 'out_buf': a:outb, \ 'out_msg': 0, - \ } + \ } call extend(opts, a:opts) let env = s:SetEnv(s:JobEnv(a:outb, get(a:opts, 'cwd', ''))) let job = job_start(s:Argv(a:cmd), opts) @@ -366,7 +366,7 @@ endfunc function s:ErrorSplitPos(name) let [w, match, mod, rel] = [0, 0, '', ''] - dir = fnamemodify(s:Path(a:name), ':h') + let dir = fnamemodify(s:Path(a:name), ':h') for i in reverse(range(1, winnr('$'))) let b = winbufnr(i) let p = get(s:cwd, b, s:Path(bufname(b))) @@ -534,7 +534,7 @@ function s:ScratchExec(cmd, dir, inp, title) let opts = { \ 'callback': function('s:ScratchCb', [b]), \ 'in_io': 'pipe', - \ } + \ } if a:dir != '' let opts.cwd = a:dir endif @@ -549,7 +549,7 @@ function s:Exec(cmd) \ 'err_io': 'null', \ 'in_io': 'null', \ 'out_io': 'null', - \ }) + \ }) endif endfunc @@ -588,7 +588,7 @@ function s:Columnate(words, width) endfunc function s:ListDir() - dir = expand('%') + let dir = expand('%') if !isdirectory(dir) || !&modifiable return endif @@ -671,7 +671,7 @@ function s:Dir() endfunc function s:CtxDir() - dir = s:Dir() + let dir = s:Dir() if &buftype != '' let [t, q] = ['ing directory:? ', "[`'\"`]"] let l = searchpair('\vEnter'.t.q, '', '\vLeav'.t.q, 'nW', @@ -776,8 +776,7 @@ function s:Open(text, click, dir, win) endfunc function s:FileComplete(arg, line, pos) - let p = a:arg =~ '^[~/]' ? a:arg : s:Dir().'/ -'.a:arg + let p = a:arg =~ '^[~/]' ? a:arg : s:Dir().'/'.a:arg let p = fnamemodify(p, ':p') if a:arg =~ '[^/]$' let p = substitute(p, '/*$', '', '') @@ -898,14 +897,14 @@ function s:Fit(w, h, ...) return h endif endif - h = 0 - top = line('$', a:w) + 1 + let h = 0 + let top = line('$', a:w) + 1 while top > 1 - h += s:FitHeight(a:w, top - 1) + let h += s:FitHeight(a:w, top - 1) if h > a:h break endif - top -= 1 + let top -= 1 endwhile call timer_start(0, {_ -> \ win_execute(a:w, 'noa call s:Scroll('.top.')')}) @@ -1035,6 +1034,43 @@ function s:RightRelease(click) call s:Open(cmd, a:click, dir, w) endfunc +function AcmeActivate(mode) + let text = a:mode == 'v' ? trim(s:Sel()[0], "\r\n", 2) : getline('.') + let click = a:mode == 'v' ? -1 : col('.') + call s:Open(text, click, s:CtxDir(), win_getid()) +endfunc + +for m in ['', 'i'] + for n in ['', '2-', '3-', '4-'] + for c in ['Mouse', 'Drag', 'Release'] + exe m.'noremap <'.n.'Middle'.c.'> ' + exe m.'noremap <'.n.'Right'.c.'> ' + endfor + endfor + exe m.'noremap ' + exe m.'noremap ' +endfor +for n in ['', '2-', '3-', '4-'] + exe 'nnoremap <'.n.'MiddleMouse>' + \ ':call MousePress("")' + exe 'vnoremap <'.n.'MiddleMouse>' + \ ':call MousePress("v")' + exe 'nnoremap <'.n.'MiddleRelease>' + \ ':call MiddleRelease(col("."))' + exe 'nnoremap <'.n.'RightMouse>' + \ ':call MousePress("")' + exe 'vnoremap <'.n.'RightMouse>' + \ ':call MousePress("v")' + exe 'nnoremap <'.n.'RightRelease>' + \ ':call RightRelease(col("."))' +endfor +inoremap :call MousePress('') +inoremap :call MiddleRelease(col('.')) +vnoremap :call MiddleRelease(-1) +inoremap :call MousePress('') +inoremap :call RightRelease(col('.')) +vnoremap :call RightRelease(-1) + function s:Clear(b) call deletebufline(a:b, 1, "$") if has_key(s:scratch, a:b) @@ -1071,7 +1107,7 @@ function s:BufInfo() endfunc function s:Change(b, l1, l2, lines) - w = win_getid(s:BufWin(a:b)) + let w = win_getid(s:BufWin(a:b)) if w == 0 return endif @@ -1128,7 +1164,7 @@ function s:PtyMap() endfunc function s:Pty(b) - w = win_getid(s:BufWin(a:b)) + let w = win_getid(s:BufWin(a:b)) if !has_key(s:scratch, a:b) || w == 0 return endif @@ -1180,7 +1216,7 @@ endfunc function s:CtrlRecv(ch, data) let len = strridx(a:data, "\x1e") let len += len == -1 ? 0 : len(s:ctrlrx) - s:ctrlrx .= a:data + let s:ctrlrx .= a:data if len == -1 return endif @@ -1336,13 +1372,13 @@ if s:ctrlexe != '' let s:ctrl = jobstart([s:ctrlexe], { \ 'on_stdout': function('s:NvimCtrlRecv'), \ 'rpc': 0, - \ }) + \ }) else let s:ctrl = job_start([s:ctrlexe], { \ 'callback': 's:CtrlRecv', \ 'err_io': 'null', \ 'mode': 'raw', - \ }) + \ }) endif let $EDITOR = s:ctrlexe -endif +endif \ No newline at end of file From b5b029cce667292bdb8c6646d4b66d411ad03c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Thu, 8 Jan 2026 18:11:08 +0100 Subject: [PATCH 08/11] fix: readdir() has only two parameters, not three --- plugin/acme.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 1a3a540..95991ed 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -592,8 +592,8 @@ function s:ListDir() if !isdirectory(dir) || !&modifiable return endif - let lst = ['..'] + readdir(dir, 1, {'sort': 'collate'}) - call map(lst, 'isdirectory(dir."/ ".v:val) ? v:val."/" : v:val') + let lst = ['..'] + readdir(dir, 1) + call map(lst, 'isdirectory(dir."/".v:val) ? v:val."/" : v:val') let width = s:BufWidth(bufnr()) let lst = s:Columnate(lst, width) call setline(1, lst) From 3a4ab595ed1ec5e0c65063973668d1a5b558c49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Tue, 24 Feb 2026 17:20:41 +0100 Subject: [PATCH 09/11] fix: repair neovim job callbacks and vimscript regressions Fix Neovim signal sending and job output routing, and restore valid Vimscript assignments/escaping that were breaking plumbing and register pastes. --- plugin/acme.vim | 51 +++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 95991ed..419b46d 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -207,7 +207,7 @@ function s:JobKill(job, sig) let map = {'int': 2, 'hup': 1, 'term': 15, 'kill': 9} let sig = get(map, sig, 15) endif - silent! call luaeval("vim.loop.kill(vim.fn.jobpid(_A), _B)", [a:job, sig]) + silent! call luaeval('vim.loop.kill(vim.fn.jobpid(_A[1]), _A[2])', [a:job, sig]) else call job_stop(a:job, a:sig) endif @@ -260,9 +260,10 @@ function s:GetJobBufNr(job) endif endfunc -function s:NvimOut(id, data, event) dict - if empty(a:data) | return | endif - let b = self.buf +function s:NvimOut(id, data, event) + if empty(a:data) || a:data == [''] | return | endif + let job = get(s:nvim_jobs, a:id, {}) + let b = get(job, 'buf', -1) if bufexists(b) let mod = getbufvar(b, '&modifiable') call setbufvar(b, '&modifiable', 1) @@ -275,12 +276,12 @@ function s:NvimOut(id, data, event) dict endif call setbufvar(b, '&modifiable', mod) endif - if has_key(self, 'callback') && !empty(self.callback) - call call(self.callback, [a:id, '']) + if has_key(job, 'callback') && !empty(job.callback) + call call(job.callback, [a:id, '']) endif endfunc -function s:NvimExit(id, status, event) dict +function s:NvimExit(id, status, event) if has_key(s:nvim_jobs, a:id) call remove(s:nvim_jobs, a:id) endif @@ -292,9 +293,10 @@ function s:JobStartNvim(cmd, outb, ctxb, opts, inp) \ 'on_exit': function('s:NvimExit'), \ 'on_stdout': function('s:NvimOut'), \ 'on_stderr': function('s:NvimOut'), - \ 'buf': a:outb, - \ 'callback': get(a:opts, 'callback', ''), \ } + if a:inp != '' + let job_opts.stdin = 'pipe' + endif if has_key(a:opts, 'cwd') let job_opts.cwd = a:opts.cwd endif @@ -304,7 +306,10 @@ function s:JobStartNvim(cmd, outb, ctxb, opts, inp) if job <= 0 return endif - let s:nvim_jobs[job] = job_opts + let s:nvim_jobs[job] = { + \ 'buf': a:outb, + \ 'callback': get(a:opts, 'callback', ''), + \ } call s:Started(job, s:BufWin(a:outb) != 0 ? a:outb : a:ctxb, a:cmd) if a:inp != '' call chansend(job, a:inp) @@ -459,7 +464,7 @@ endfunc function s:Read(cmd, dir, inp) let end = getcurpos()[4] > strdisplaywidth(getline('.')) call setreg('"', s:System(a:cmd, a:dir, a:inp), 'c') - exe 'normal! "'.(end ? 'p' : 'P') + exe 'normal! ""'.(end ? 'p' : 'P') endfunc function s:ParseCmd(cmd) @@ -724,7 +729,7 @@ function s:RgOpen(pos) return 0 endif call win_execute(s:plumbwin, - \ 'let s:l = search("\v^(\s*(\d+[-:]|\-\-$))@!", "bnW")') + \ 'let s:l = search("\\v^(\\s*(\\d+[-:]|\\-\\-$))@!", "bnW")') let f = getbufoneline(winbufnr(s:plumbwin), s:l) if f != '' return AcmeOpen(f, a:pos) @@ -876,7 +881,7 @@ endfunc function s:Scroll(topline) let v = winsaveview() - v.topline = a:topline + let v.topline = a:topline call winrestview(v) endfunc @@ -922,8 +927,8 @@ function s:Zoom(w) break endif call win_move_statusline(win_id2win(w) - 1, winheight(w) - s) - h -= s - n -= 1 + let h -= s + let n -= 1 endfor endfunc @@ -983,8 +988,8 @@ function s:MiddleRelease(click) let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) let b = bufnr() - dir = s:Dir() - w = win_getid() + let dir = s:Dir() + let w = win_getid() exe win_id2win(s:clickwin).'wincmd w' if s:Receiver(b) if w != s:clickwin && s:clickmode == 'v' && a:click > 0 @@ -1028,8 +1033,8 @@ function s:RightRelease(click) let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) - w = win_getid() - dir = s:CtxDir() + let w = win_getid() + let dir = s:CtxDir() exe win_id2win(s:clickwin).'wincmd w' call s:Open(cmd, a:click, dir, w) endfunc @@ -1113,10 +1118,10 @@ function s:Change(b, l1, l2, lines) endif let pos = getcurpos(w) let last = line('$', w) - l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) - n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, + let l = s:Bound(1, a:l1 < 0 ? a:l1 + last + 2 : a:l1, last + 1) + let n = s:Bound(0, (a:l2 < 0 ? a:l2 + last + 2 : a:l2) - l + 1, \ last - l + 1) - i = min([n, len(a:lines)]) + let i = min([n, len(a:lines)]) if i > 0 call setbufline(a:b, l, a:lines[:i-1]) endif @@ -1381,4 +1386,4 @@ if s:ctrlexe != '' \ }) endif let $EDITOR = s:ctrlexe -endif \ No newline at end of file +endif From 89ab3b566c667fd14b045f94214b7653b93da29d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:03:32 +0000 Subject: [PATCH 10/11] fix: make right-click mouse work in Neovim 0.12+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Neovim 0.12+, normal! with mouse key events (, ) is restricted and cannot be used to replay mouse events. This caused E492 errors in s:RightRelease. Fixes: - s:MousePress: use wincmd w + cursor() for Neovim instead of normal! \ - s:MiddleRelease: skip normal! \ for Neovim - s:RightRelease: skip normal! \ for Neovim (root cause of the reported E492 error in issue #1) Co-authored-by: Matěj Cepl --- plugin/acme.vim | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 419b46d..061ed90 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -962,7 +962,12 @@ function s:MousePress(mode) if s:clickstatus != 0 || s:click.winid == 0 return endif - exe "normal! \" + if has('nvim') + exe win_id2win(s:click.winid).'wincmd w' + call cursor(s:click.line, s:click.column) + else + exe "normal! \" + endif let s:visual = s:SaveVisual() let s:clicksel = s:clickmode == 'v' && win_getid() == s:clickwin && \ s:InSel() @@ -983,7 +988,9 @@ function s:MiddleRelease(click) endif return endif - exe "normal! \" + if !has('nvim') + exe "normal! \" + endif let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) @@ -1029,7 +1036,9 @@ function s:RightRelease(click) endif return endif - exe "normal! \" + if !has('nvim') + exe "normal! \" + endif let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) From 66df91c0e9b4f0a07098d898e1ebb4a6f7c8c472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Wed, 4 Mar 2026 18:47:18 +0100 Subject: [PATCH 11/11] fix: use getline('.') in s:RightRelease for correct plumbing Using expand('') in s:RightRelease meant that s:Open received only a single word, but still with the absolute column index of the mouse click. This caused regex column matches in s:Match to fail silently. By using getline('.'), s:Open receives the full line, ensuring that column-based plumbing patterns (like URL detection) work correctly when right-clicking. --- plugin/acme.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/acme.vim b/plugin/acme.vim index 061ed90..b095fb6 100644 --- a/plugin/acme.vim +++ b/plugin/acme.vim @@ -1039,7 +1039,7 @@ function s:RightRelease(click) if !has('nvim') exe "normal! \" endif - let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : expand('') + let cmd = a:click <= 0 || s:clicksel ? s:Sel()[0] : getline('.') let vis = s:clickmode == 'v' && (a:click <= 0 || !s:clicksel) call s:RestVisual(s:visual) let w = win_getid()