diff --git a/plugin/acme.vim b/plugin/acme.vim index 0465d57..b095fb6 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 @@ -74,7 +80,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() @@ -100,11 +106,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 +127,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 +164,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 +192,131 @@ 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: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[1]), _A[2])', [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) + 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: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) + 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(job, 'callback') && !empty(job.callback) + call call(job.callback, [a:id, '']) + endif +endfunc + +function s:NvimExit(id, status, event) + 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'), + \ } + if a:inp != '' + let job_opts.stdin = 'pipe' + endif + 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] = { + \ '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) + call chanclose(job, 'stdin') + endif +endfunc + function s:JobEnv(buf, dir) return { \ 'ACMEVIMBUF': bufnr(), @@ -207,6 +340,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', @@ -273,7 +409,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 +429,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) @@ -392,7 +528,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 @@ -411,11 +547,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) @@ -457,7 +597,7 @@ function s:ListDir() if !isdirectory(dir) || !&modifiable return endif - let lst = ['..'] + readdir(dir, 1, {'sort': 'collate'}) + 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) @@ -538,7 +678,7 @@ endfunc function s:CtxDir() let 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) @@ -624,7 +764,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 @@ -685,23 +825,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 +861,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 +876,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) @@ -839,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() @@ -856,11 +984,13 @@ 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 - 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) @@ -879,6 +1009,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 @@ -898,14 +1036,22 @@ function s:RightRelease(click) endif return endif - exe "normal! \" - let click = s:clicksel ? -1 : a:click - let text = click <= 0 ? trim(s:Sel()[0], "\r\n", 2) : getline('.') + if !has('nvim') + exe "normal! \" + endif + 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() let 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 + +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'] @@ -944,7 +1090,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 @@ -1005,7 +1151,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 @@ -1020,7 +1166,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 @@ -1069,7 +1215,8 @@ 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') @@ -1149,7 +1296,7 @@ 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() @@ -1171,6 +1318,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 +1355,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") @@ -1200,12 +1376,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