So starten Sie eine asynchrone Funktion in Vim 8
Ich habe gewusst, dass Vim 8 uns einen job_startasynchronen Mechanismus bietet , mit dem wir einen Befehl asynchron starten und mithilfe des Kanals zurückgeben können.
Allerdings scheint es , dass job_startnur die OS - Befehl ausführen können, wie make, grep, /bin/shusw.
Ist es möglich, eine vim-Skriptfunktion mit Hilfe von aufzurufen job_start?
Hier ist mein vimrc:
function! CustomGrep(...)
// do something
endfunction
command! -nargs=? Grep :call CustomGrep(<f-args>)
Wie Sie sehen, kann ich :Grep xxxin meinem vim die Funktion aufrufen CustomGrep. Jetzt möchte ich diese Asynchronisierung durchführen, ungefähr so: command! -nargs=? Grep :job_start(...)aber ich weiß nicht, wie ich das erreichen soll.
Ich habe auch so getestet: command! -nargs=? Grep : call job_start('echo 111')aber ich kann nicht 111in meinem vim sehen, wenn ich ausführe Grep xxx.
Hier ist mein echtes benutzerdefiniertes Grep: https://github.com/YvesZHI/Camel-vim/blob/vim8/Camel.vim. Sie können dies von der Zeile 240 bis zum Ende lesen.
Mit einem Wort, als ich anfange, vim 8 zu verwenden, das eine Art Async-Job-Mechanismus bietet, denke ich, ob es möglich ist, mein benutzerdefiniertes Grep asynchron zu machen. Im Moment könnte mein benutzerdefiniertes Grep wie erwartet funktionieren. Manchmal dauert es eine Weile, bis es fertig ist, aber normalerweise sind es nur 2 oder 3 Sekunden, das ist keine große Sache. Aber was ist, wenn ich mit einem sehr großen Projekt arbeite? Es wird also großartig, wenn ich mein benutzerdefiniertes Grep asynchronisieren kann.
Antworten
Aus der Diskussion in Kommentaren habe ich erfahren, dass OP einen benutzerdefinierten vimgrep-Aufruf hat und diesen asynchron ausführen möchte. Der Nutzen davon ist ziemlich klar, besonders wenn Sie viele Suchvorgänge ausführen, die eine Weile dauern: Wenn Sie die Ausführung in einen separaten / Hintergrund-Thread verschieben, geben Sie die Benutzeroberfläche von Vim frei, sodass Sie weiterarbeiten können, ohne auf das Ergebnis zu warten.
Wie ich in einem meiner obigen Kommentare erwähnt habe, wird ein Job zum asynchronen Verwalten eines Prozesses verwendet . Das Aufrufen einer Funktion in Vim erfolgt auf einer anderen Granularitätsebene. Alles findet in einem einzigen, bereits laufenden Prozess statt ... Vim's! ... das gilt hier also nicht.
Trotzdem gibt es eine relativ einfache Möglichkeit, das zu tun, was wir wollen, und zwar mithilfe eines "Timers". Timer werden normalerweise verwendet, um eine oder beide der folgenden Aktionen auszuführen:
- Starten Sie eine Aufgabe nach einer Verzögerung.
- Führen Sie eine Aufgabe wiederholt aus
Ein Beispielanruf macht dies ziemlich deutlich
let timerid = timer_start(2000, 'RepeatMePlease', { repeat: 5 })
Der timer_start()Anruf wird sofort mit der ID des zugewiesenen Timers zurückgegeben. Währenddessen tritt im Hintergrund eine Verzögerung von 2 Sekunden auf, nach der ein Thread zum Ausführen der Funktion zugewiesen wird RepeatMePlease(). Die Verzögerung und der Anruf werden danach noch vier Mal wiederholt.
Wofür ist die Timer-ID? Nun, Sie können anhalten, pausieren oder Buchhaltungsinformationen über einen vorhandenen Timer abrufen. Dazu benötigen Sie natürlich eine Möglichkeit, darauf zu verweisen. Die Timer-ID.
(Hinweis: Ich sagte "nach 2 Sekunden", aber das ist eigentlich die minimale Verzögerung. Wenn Vim beschäftigt ist, kann es länger dauern.)
Was hat das mit einem einfachen Hintergrundanruf zu tun? Nun, wenn Sie keine Wiederholungen machen und keine Verzögerung haben, was bleibt dann übrig? Eine Funktion, die im Hintergrund ausgeführt wird.
Die einzige Komplikation hierbei ist, dass, wie Sie vielleicht bemerkt haben, keine Parameter für angegeben sind RepeatMePlease(). Das liegt daran, dass es keinen Ort gibt, an dem man sie unterbringen kann. Wenn Sie einen Funktionsnamen übergeben, wird diese Funktion von Vim mit einem einzigen Parameter aufgerufen: der Timer-ID.
Das ist ziemlich einschränkend. Was sollen wir jemals tun?
Zum Glück gibt es eine Alternative: Wir können eine Funktion inline definieren. Dies ist in Vim als Lambda- Ausdruck bekannt (und anderswo, aber manchmal wird es als "anonyme Funktion" bezeichnet). Diese haben eine Eigenschaft, die sehr nützlich ist: Sie dienen als Verschlüsse . Huh? Hier müssen Sie lediglich wissen, dass wir auf lokale Variablen und Parameter zugreifen können. Ich werde nicht viel detaillierter darauf eingehen. Dafür sind Hilfe und Google da. Lassen Sie es uns stattdessen einfach in Aktion sehen. (Keine Angst. Hier gibt es nur drei Zeilen ausgeführten Codes.)
" This is the entry point for the custom vimgrep. Let's say that you
" just pass in a pattern that was entered by the user.
function! PretendCustomGrep(patt) abort
" Here you can do whatever you need to prepare the grep call then...
" I've added a 3 second delay so you can prove to your self
" that this function returns right away and you can use Vim normally.
" In reality you don't need this.
call timer_start(3000, {-> execute("call LongRunningFun('" . a:patt . "')", "")})
endfunc
" This does the actual grep stuff
function! LongRunningFun(patt) abort
" When you have your result display it, open it, open quickfix, or
" whatever it is you want to do with it. Here I'm just showing something
" in a short-lived popup.
let l:msg = "Search for " . a:patt . " found: A RESULT!"
call popup_notification(l:msg , #{ line: 5, col:10, highlight: 'Warning', } )
endfunction
Update: Mit einer Funcref-Variablen können Sie dasselbe ohne Lambdas und Closures erreichen. Sie erhalten eine Funcref von der function()Funktion. function()nimmt einen Funktionsnamen und eine Liste. Die Liste enthält alle Parameter, die Sie an die Funktion übergeben müssen. Ja, das ist etwas verwirrend. Schauen wir uns also die überarbeitete Verwendung des Timers für die erste Funktion oben an ...
let fref = function('LongRunningFun', [patt])
call timer_start(3000, fref)
oder alternativ einfach inline ...
call timer_start(3000, function('LongRunningFun', [patt]))
Auf diese Weise gibt es etwas weniger Flexibilität, aber in den allermeisten Fällen ist dies vorzuziehen.
Ist es möglich, eine vim-Skriptfunktion mit Hilfe von job_start aufzurufen?
Nicht direkt. Ich denke, Sie würden Vim brauchen, um Multithreading zu betreiben, was in Zukunft möglich sein könnte .
Tatsächlich habe ich den Befehl vimgrep verwendet, um mein benutzerdefiniertes grep zu erstellen. Es funktionierte. Jetzt fange ich an, vim 8 zu verwenden, also überlege ich, ob es möglich ist, mein benutzerdefiniertes grep asynchron zu machen.
FWIW, ich habe vor langer Zeit einen Code mit dem gleichen Zweck geschrieben:
com -nargs=* Vim call s:vimgrep(<q-args>)
com -nargs=* Lvim call s:vimgrep(<q-args>, v:true)
fu s:vimgrep(args, loclist = v:false) abort
let tempqfl = tempname()
let tempvimrc = tempname()
let get_tempfile =<< trim END
let tempqfl = expand('%:p')
if tempqfl !~# '^/tmp/'
finish
endif
END
let cdcmd = 'cd ' .. getcwd()->fnameescape()
let setcmd = printf('set wildignore=%s suffixes=%s %signorecase %ssmartcase',
\ &wildignore, &suffixes, &ignorecase ? '' : 'no', &smartcase ? '' : 'no')
let args = s:expandargs(a:args)
let vimgrepcmd = 'noa vim ' .. args
let getqfl =<< trim END
eval getqflist()
\ ->map({_, v -> printf('%s:%d:%d:%s',
\ bufname(v.bufnr)->fnamemodify(':p'),
\ v.lnum,
\ v.col,
\ substitute(v.text, '[^[:print:]]', {m -> strtrans(m[0])}, 'g')
\ )})
\ ->writefile(tempqfl, 's')
qa!
END
call writefile(['vim9script']
\ + get_tempfile
\ + [cdcmd, setcmd, vimgrepcmd]
\ + getqfl,
\ tempvimrc, 's')
let vimcmd = printf('vim -es -Nu NONE -U NONE -i NONE -S %s %s', tempvimrc, tempqfl)
let title = (a:loclist ? ':Lvim ' : ':Vim ') .. args
let arglist = [a:loclist, tempqfl, title]
let opts = #{exit_cb: function('s:callback', arglist)}
call split(vimcmd)->job_start(opts)
endfu
fu s:callback(loclist, tempqfl, title, _j, _e) abort
let efm_save = &l:efm
let bufnr = bufnr('%')
try
setl efm=%f:%l:%c:%m
if a:loclist
exe 'lgetfile ' .. a:tempqfl
lw
call setloclist(0, [], 'a', #{title: a:title})
else
exe 'cgetfile ' .. a:tempqfl
cw
call setqflist([], 'a', #{title: a:title})
endif
finally
call setbufvar(bufnr, '&efm', efm_save)
endtry
redraw!
endfu
fu s:expandargs(args) abort
let pat = '^\(\i\@!.\)\1\ze[gj]\{,2}\s\+'
let rep = '/' .. escape(@/, '\/') .. '/'
return substitute(a:args, pat, rep, '')
\ ->substitute('\s\+\zs%\s*$', expand('%:p')->fnameescape(), '') \ ->substitute('\s\+\zs##\s*$', argv()
\ ->map({_, v -> fnamemodify(v, ':p')->fnameescape()})
\ ->join(), '')
endfu
Der Code installiert 2 benutzerdefinierte Befehle :Vimund :Lvimdie eine Art asynchronen Wrapper um die eingebauten :vimgrepund :lvimgrep. Sie können es beispielsweise testen, indem Sie diesen Befehl ausführen:
:Vim /pattern/gj $VIMRUNTIME/**
Dies sollte asynchron eine Quickfix-Liste mit allen Zeilen füllen, in patterndie in einer beliebigen Datei in Ihrem $VIMRUNTIMEVerzeichnis geschrieben ist.
Es scheint zu funktionieren, aber ich benutze es nicht regelmäßig, deshalb weiß ich nicht, wie zuverlässig es ist.
In Vim9-Skript neu geschrieben:
vim9script
com -nargs=* Vim Vimgrep(<q-args>)
com -nargs=* Lvim Vimgrep(<q-args>, true)
def Vimgrep(args: string, loclist = false)
var tempvimrc = tempname()
var tempqfl = tempname()
var get_tempfile =<< trim END
var tempqfl = expand('%:p')
if tempqfl !~ '^/tmp/'
finish
endif
END
var cdcmd = 'cd ' .. getcwd()->fnameescape()
var setcmd = printf('set wildignore=%s suffixes=%s %signorecase %ssmartcase',
&wildignore, &suffixes, &ignorecase ? '' : 'no', &smartcase ? '' : 'no')
var _args = Expandargs(args)
var vimgrepcmd = 'noa vim ' .. _args
var getqfl =<< trim END
getqflist()
->map({_, v -> printf('%s:%d:%d:%s',
bufname(v.bufnr)->fnamemodify(':p'),
v.lnum,
v.col,
substitute(v.text, '[^[:print:]]', {m -> strtrans(m[0])}, 'g')
)})
->writefile(tempqfl, 's')
qa!
END
writefile(['vim9script']
+ get_tempfile
+ [cdcmd, setcmd, vimgrepcmd]
+ getqfl,
tempvimrc, 's')
var vimcmd = printf('vim -es -Nu NONE -U NONE -i NONE -S %s %s', tempvimrc, tempqfl)
var title = (loclist ? ':Lvim ' : ':Vim ') .. _args
var arglist = [loclist, tempqfl, title]
var opts = {exit_cb: function(Callback, arglist)}
split(vimcmd)->job_start(opts)
enddef
def Callback(loclist: bool, tempqfl: string, title: string, _j: job, _e: number)
var efm_save = &l:efm
var bufnr = bufnr('%')
try
setl efm=%f:%l:%c:%m
if loclist
exe 'lgetfile ' .. tempqfl
lw
setloclist(0, [], 'a', {title: title})
else
exe 'cgetfile ' .. tempqfl
cw
setqflist([], 'a', {title: title})
endif
finally
setbufvar(bufnr, '&efm', efm_save)
endtry
redraw!
enddef
def Expandargs(args: string): string
var pat = '^\(\i\@!.\)\1\ze[gj]\{,2}\s\+'
var rep = '/' .. escape(@/, '\/') .. '/'
return substitute(args, pat, rep, '')
->substitute('\s\+\zs%\s*$', expand('%:p')->fnameescape(), '') ->substitute('\s\+\zs##\s*$', argv()
->map({_, v -> fnamemodify(v, ':p')->fnameescape()})
->join(), '')
enddef
Dies erfordert eine aktuelle Vim-Version. Es funktioniert am 8.2.2159.