channel.txt ForVim version 9.2. Last change: 2026 Feb 18VIM REFERENCE MANUAL by Bram Moolenaar Inter-process communicationchannelVim uses channels to communicate with other processes.Achannel usesa socket or pipes.socket-interfaceJobs can be used to start processes and communicate with them.The Netbeansinterface also usesa channel.netbeans1. Overviewjob-channel-overview2.Channel demochannel-demo3. Openingachannelchannel-open4. Usinga JSON or JSchannelchannel-use5.Channel commandschannel-commands6. Usinga RAW or NLchannelchannel-raw7. Morechannelfunctionschannel-more8.Channelfunctions detailschannel-functions-details9. Startingajob withachanneljob-start10. Startingajob withoutachanneljob-start-nochannel11.Jobfunctionsjob-functions-details12.Joboptionsjob-options13. Controllingajobjob-control14. Usinga prompt bufferprompt-buffer15. Language Server Protocollanguage-server-protocolE1277{only when compiled with the |+channel| feature for channel stuff}You can check this with:has('channel'){only when compiled with the |+job| feature for job stuff}You can check this with:has('job')==============================================================================1. Overviewjob-channel-overviewThere are four main types of jobs:1.A daemon, serving several Vim instances. Vim connects toit witha socket.2. Onejob working with one Vim instance, asynchronously. Usesa socket or pipes.3.Ajob performing some work fora short time, asynchronously. Usesa socket or pipes.4. Runninga filter, synchronously. Uses pipes.For when using sockets Seejob-start,job-start-nochannel andchannel-open. For 2 and 3, one or more jobs using pipes, seejob-start.For 4 use the ":{range}!cmd" command, seefilter.Over the socket and pipes these protocols are available:RAWnothing known, Vim cannot tell wherea message endsNLevery message ends ina NL (newline) characterJSONJSON encodingjson_encode()JSJavaScript style JSON-like encodingjs_encode()LSPLanguage Server Protocol encodinglanguage-server-protocolCommon combination are:- Usingajob connected through pipes in NL mode. E.g., to runa style checker and receiveerrors and warnings.- Usinga daemon, connecting overa socket in JSON mode. E.g. to lookup cross-references ina database.==============================================================================2.Channel demochannel-demodemoserver.pyThis requires Python. The demo program can be found in$VIMRUNTIME/tools/demoserver.pyRunit in one terminal. We will call this T1.Run Vim in another terminal. Connect to the demo server with:let channel = ch_open('localhost:8765')In T1 you should see:=== socket opened ===You can now senda message to the server:echo ch_evalexpr(channel, 'hello!')The messageis received in T1 anda responseis sent back to Vim.You can see the rawmessages in T1. What Vim sends is:[1,"hello!"]And the response is:[1,"got it"]The number will increase every time you senda message.The server can senda command to Vim. Type this on T1 (literally, includingthe quotes):["ex","echo 'hi there'"]And you should see the message in Vim. You can move the cursorawordforward:["normal","w"]To handle asynchronous communicationa callback needs to be used:func MyHandler(channel, msg) echo "from the handler: " .. a:msgendfunccall ch_sendexpr(channel, 'hello!', {'callback': "MyHandler"})Vim will not wait fora response. Now the server can send the response laterand MyHandler will be invoked.Instead of givinga callback with every send call,it can also be specifiedwhen opening the channel:call ch_close(channel)let channel = ch_open('localhost:8765', {'callback': "MyHandler"})call ch_sendexpr(channel, 'hello channel!')When trying out channels it's useful to see whatis going on. You can tellVim to write lines in log file:call ch_logfile('channellog', 'w')Seech_logfile().==============================================================================3. Openingachannelchannel-openTo opena channel: let channel = ch_open({address} [, {options}]) if ch_status(channel) == "open" " use the channelUsech_status() to see if thechannel could be opened.channel-address{address} can bea domain name or an IP address, followed bya port number, ora Unix-domain socket path prefixed by "unix:". E.g. www.example.com:80 " domain + port 127.0.0.1:1234 " IPv4 + port [2001:db8::1]:8765 " IPv6 + port unix:/tmp/my-socket " Unix-domain socket pathWhena domain name resolves to multiple addresses (e.g., both IPv6 and IPv4),Vim tries each address in order. Ifa connectionis slow or unreachable,itquickly falls back to the next address. This helps when IPv6 or IPv4isunreachable on the network.{options}isa dictionary with optional entries:channel-open-options"mode" can be:channel-mode"json"- Use JSON, see below; most convenient way. Default."js"- Use JS (JavaScript) encoding, more efficient than JSON."nl"- Usemessages thatend ina NL character"raw"- Use rawmessages"lsp"- Use language server protocol encodingchannel-callbackE921"callback"A function thatis called whena messageis received thatisnot handled otherwise (e.g.a JSON message with ID zero). Itgets two arguments: thechannel and the received message.Example:func Handle(channel, msg) echo 'Received: ' .. a:msgendfunclet channel = ch_open("localhost:8765", {"callback": "Handle"})When "mode"is "json" or "js" or "lsp" the "msg" argumentisthe body of the received message, converted to Vim types.When "mode"is "nl" the "msg" argumentis one message,excluding the NL.When "mode"is "raw" the "msg" argumentis the whole messageasa string.For all callbacks: Usefunction() to bindit to argumentsand/ora Dictionary. Or use the form "dict.function" to bindthe Dictionary.Callbacks are only calledata "safe" moment, usually when Vimis waiting for the user to typea character. Vim does not usemulti-threading.close_cb"close_cb"A function thatis called when thechannel gets closed, otherthan by calling ch_close(). It should be defined like this:func MyCloseHandler(channel)Vim will invoke callbacks that handle data before invokingclose_cb, thus when this functionis called no more data willbe passed to the callbacks. However, ifa callback causes Vimto check for messages, theclose_cb may be invoked while stillin the callback. Thepluginmust handle this somehow,it canbe useful to know that no more datais coming.Ifitis not known if thereisa message to be read, useatry/catch block:try let msg = ch_readraw(a:channel)catch let msg = 'no message'endtrytry let err = ch_readraw(a:channel, #{part: 'err'})catch let err = 'no error'endtrychannel-drop"drop"Specifies when to drop messages: "auto"When thereis no callback to handlea message.The "close_cb"is also considered for this. "never"Allmessages will be kept.channel-noblock"noblock"Same effectasjob-noblock. Only matters for writing.waittime"waittime"The time to wait for the connection to be made inmilliseconds.A negative number waits forever.The defaultis zero, don't wait, whichis useful ifa localserveris supposed to be running already. OnUnix Vimactually usesa 1 msec timeout, thatis required on manysystems. Usea larger value fora remote server, e.g. 10msecat least.channel-timeout"timeout"The time to wait fora request when blocking, E.g. when usingch_evalexpr(). In milliseconds. The defaultis 2000 (2seconds).When "mode"is "json" or "js" the "callback"is optional. When omitteditisonly possible to receivea message after sending one.To change thechanneloptions after openingit usech_setoptions(). Thearguments are similar to whatis passed toch_open(), but "waittime" cannotbe given, since that only applies to opening the channel.For example, the handler can be added or changed: call ch_setoptions(channel, {'callback': callback})When "callback"is empty (zero or an empty string) the handleris removed.Aftera callback has been invoked Vim will update the screen andput thecursor back whereit belongs. Thus the callback should not need todo:redraw.The timeout can be changed: call ch_setoptions(channel, {'timeout': msec})channel-closeE906Once done with the channel, disconnectit like this: call ch_close(channel)Whena socketis used this will close the socket for both directions. Whenpipes are used (stdin/stdout/stderr) they are all closed. This might not bewhat you want! Stopping thejob withjob_stop() might be better.All readaheadis discarded, callbacks will no longer be invoked.Note thatachannelis closed in three stages:- The I/O ends, log message: "Closingchannel". There can still be queuedmessages to read or callbacks to invoke.- The readaheadis cleared, log message: "Clearingchannel". Somevariables may stillreference the channel.- Thechannelis freed, log message: "Freeingchannel".When thechannel can't be opened you will get an error message. Thereisadifference betweenMS-Windows and Unix: OnUnix when the port doesn't existch_open() fails quickly. OnMS-Windows "waittime" applies.E898E901E902If thereis an error reading orwritingachannelit will be closed.E630E631==============================================================================4. Usinga JSON or JSchannelchannel-useIf modeis JSON thena message can be sent synchronously like this: let response = ch_evalexpr(channel, {expr})This awaitsa response from the other side.When modeis JS this works the same, except that themessages useJavaScript encoding. Seejs_encode() for the difference.To senda message, without handlinga response or letting thechannel callbackhandle the response: call ch_sendexpr(channel, {expr})To senda message and letting the response handled bya specific function,asynchronously: call ch_sendexpr(channel, {expr}, {'callback': Handler})Vim will match the response with the request using the message ID. Once theresponseis received the callback will be invoked. Further responses with thesame ID will be ignored. If your server sends back multiple responses youneed to send them with ID zero, they will be passed to thechannel callback.The{expr}is converted to JSON and wrapped in an array. An example of themessage that the receiver will get when{expr}is thestring "hello":[12,"hello"]The format of the JSON sent is: [{number},{expr}]In which{number}is different every time. Itmust be used in the response(if any): [{number},{response}]This way Vim knows which sent message matches with which received message andcan call the right handler. Also when themessages arrive out of order.A newline characteris terminating the JSON text. This can be used toseparate the read text. For example, in Python:splitidx= read_text.find('\n')message= read_text[:splitidx]rest= read_text[splitidx+ 1:]The sendermust always send valid JSON to Vim. Vim can check for theend ofthe message by parsing the JSON. It will only accept the message if theendwas received.A newline after the messageis optional.When the process wants to senda message to Vim without first receivingamessage,itmust use the number zero: [0,{response}]Thenchannel handler will then get{response} converted to Vim types. If thechannel does not havea handler the messageis dropped.Itis also possible to usech_sendraw() andch_evalraw() ona JSON or JSchannel. The calleris then completely responsible for correct encoding anddecoding.==============================================================================5.Channel commandschannel-commandsWitha JSONchannel the process can send commands to Vim that will behandled by Vim internally,it does not requirea handler for the channel.Possible commands are:E903E904E905["redraw",{forced}]["ex",{Ex command}]["normal",{Normal mode command}]["expr",{expression},{number}]["expr",{expression}]["call",{func name},{argument list},{number}]["call",{func name},{argument list}]With all of these: Be careful what these commands do! You can easilyinterfere with what the useris doing. To avoid trouble usemode() to checkthat the editoris in the expected state. E.g., to send keys thatmust beinsertedas text, not executedasa command: ["ex","if mode() == 'i' | call feedkeys('ClassName') | endif"]Errors in these commands are normally not reported to avoid them messing upthe display. If youdo want to see them, set the'verbose' option to 3 orhigher.Command "redraw"The other commandsdo not explicitly update the screen, so that you can sendasequence of commands without the cursor moving around.A redraw can happenasa side effect of some commands. Youmustend with the "redraw" command toshow any changed text and show the cursor whereit belongs.The argumentis normally an empty string:["redraw", ""]To first clear the screen pass "force":["redraw", "force"]Command "ex"The "ex" commandis executedas anyEx command. Thereis no response forcompletion or error. You could usefunctions in anautoload script:["ex","call myscript#MyFunc(arg)"]You can also use "callfeedkeys()" toinsert any key sequence.When thereis an errora messageis written to thechannel log, ifit exists,andv:errmsgis set to the error.Command "normal"The "normal" commandis executed like with ":normal!", commands are notmapped. Example to open thefolds under the cursor:["normal" "zO"]Command "expr" with responseThe "expr" command can be used to get the result of an expression. Forexample, to get the number of lines in the current buffer:["expr","line('$')", -2]It will send back the result of the expression:[-2, "last line"]The format is:[{number},{result}]Here{number}is the sameas what was in the request. Usea negative numberto avoid confusion with message that Vim sends. Usea different number onevery request to be able to match the request with the response.{result}is the result of the evaluation andis JSON encoded. If theevaluation fails or the result can't be encoded in JSONitis thestring"ERROR".Command "expr" without a responseThis commandis similar to "expr" above, but does not send back any response.Example:["expr","setline('$', ['one', 'two', 'three'])"]Thereis no third argument in the request.Command "call"Thisis similar to "expr", but instead of passing the wholeexpressionasastring this passes the name ofa function andalist of arguments. Thisavoids the conversion of the arguments toastring and escaping andconcatenating them. Example:["call", "line", ["$"], -2]Leave out the fourth argument if no responseis to be sent:["call", "setline", ["$", ["one", "two", "three"]]]==============================================================================6. Usinga RAW or NLchannelchannel-rawIf modeis RAW or NL thena message can be sent like this: let response = ch_evalraw(channel, {string})The{string}is sent as-is. The response will be what can be read from thechannel right away. Since Vim doesn't know how to recognize theend of themessage you need to take care ofit yourself. The timeout applies for readingthe first byte, after thatit will not wait for anything more.If modeis "nl" you can senda message ina similar way. You are expectedtoput in the NL after each message. Thus you can also send severalmessagesending ina NLat once. The response will be the text up to and including thefirst NL. This can also be just the NL for an empty response.If no NL was read before thechannel timeout an emptystringis returned.To senda message, without expectinga response: call ch_sendraw(channel, {string})The process can send backa response, thechannel handler will be called withit.channel-onetime-callbackTo senda message and letting the response handled bya specific function,asynchronously: call ch_sendraw(channel, {string}, {'callback': 'MyHandler'})This{string} can also be JSON, usejson_encode() to createit andjson_decode() to handlea received JSON message.Itis not possible to usech_evalexpr() orch_sendexpr() ona raw channel.AString in Vim cannot contain NUL bytes. To send or receive NUL bytes reador write froma buffer. Seein_io-buffer andout_io-buffer.==============================================================================7. Morechannelfunctionschannel-moreTo obtain the status ofa channel: ch_status(channel). The possible resultsare:"fail"Failed to open the channel."open"Thechannel can be used."buffered"Thechannel was closed but thereis data to read."closed"Thechannel was closed.To obtain thejob associated witha channel: ch_getjob(channel)To read one message froma channel:let output = ch_read(channel)This uses thechannel timeout. To read withouta timeout, just get anymessage thatis available:let output = ch_read(channel, {'timeout': 0})When no message was available then the resultisv:none fora JSON or JS modechannels, an emptystring fora RAW or NL channel. You can usech_canread()to check if thereis something to read.Note that when thereis no callback,messages are dropped. To avoid that adda close callback to the channel.To read all normal output froma RAWchannel thatis available:let output = ch_readraw(channel)To read all error output froma RAWchannel thatis available:let output = ch_readraw(channel, {"part": "err"})Note that if thechannelis in NL mode,ch_readraw() will only return one linefor each call.ch_read() andch_readraw() use thechannel timeout. When thereis nothing toread within that time an emptystringis returned. To specifya differenttimeout in msec use the "timeout" option:{"timeout": 123}To read from the error output use the "part" option:{"part": "err"}To reada message witha specific ID, ona JS or JSON channel:{"id": 99}When no IDis specified or the IDis -1, the first messageis returned. Thisoverrules any callback waiting for this message.Fora RAWchannel this returns whateveris available, since Vim does not knowwherea message ends.Fora NLchannel this returns one message.Fora JS or JSONchannel this returns one decoded message.This includes any sequence number.==============================================================================8.Channelfunctions detailschannel-functions-detailsch_canread({handle})ch_canread()Return non-zero when thereis something to read from{handle}.{handle} can beaChannel oraJob that hasa Channel.Thisis useful to read fromachannelata convenient time,e.g. froma timer.Note thatmessages are dropped when thechannel does not havea callback. Adda close callback to avoid that.Can also be usedasamethod:GetChannel()->ch_canread()Return type:Numberch_close({handle})ch_close()Close{handle}. Seechannel-close.{handle} can beaChannel oraJob that hasa Channel.A close callbackis not invoked.Can also be usedasamethod:GetChannel()->ch_close()Return type:Numberch_close_in({handle})ch_close_in()Close the "in" part of{handle}. Seechannel-close-in.{handle} can beaChannel oraJob that hasa Channel.A close callbackis not invoked.Can also be usedasamethod:GetChannel()->ch_close_in()Return type:Numberch_evalexpr({handle},{expr} [,{options}])ch_evalexpr()Send{expr} over{handle}. The{expr}is encodedaccording to the type of channel. The function cannot be usedwitha raw channel. Seechannel-use.{handle} can beaChannel oraJob that hasa Channel.When using the "lsp"channel mode,{expr}must beaDict.E917{options}must bea Dictionary. Itmust not havea "callback"entry. It can havea "timeout" entry to specify the timeoutfor this specific request.ch_evalexpr() waits fora response and returns the decodedexpression. When thereis an error or timeoutit returns anemptyString or, when using the "lsp"channel mode, returns anemptyDict.Note that while waiting for the response, Vim handles othermessages. You need to make sure this doesn't cause trouble.Can also be usedasamethod:GetChannel()->ch_evalexpr(expr)Return type: dict<any> orStringch_evalraw({handle},{string} [,{options}])ch_evalraw()Send{string} over{handle}.{handle} can beaChannel oraJob that hasa Channel.Works likech_evalexpr(), but does not encode the request ordecode the response. The calleris responsible for thecorrect contents. Also does not adda newline forachannelin NL mode, the callermustdo that. The NL in the responseis removed.Note that Vim does not know when the text received ona rawchannelis complete,it may only return the first part and youneed to usech_readraw() tofetch the rest.Seechannel-use.Can also be usedasamethod:GetChannel()->ch_evalraw(rawstring)Return type: dict<any> orStringch_getbufnr({handle},{what})ch_getbufnr()Get the buffer number that{handle}is using forString{what}.{handle} can beaChannel oraJob that hasa Channel.{what} can be "err" for stderr, "out" for stdout or empty forsocket output.Returns -1 when thereis no buffer.Can also be usedasamethod:GetChannel()->ch_getbufnr(what)Return type:Numberch_getjob({channel})ch_getjob()Get theJob associated with{channel}.If thereis nojob callingjob_status() on the returnedJobwill result in "fail".Can also be usedasamethod:GetChannel()->ch_getjob()Return type:job orStringch_info({handle})ch_info()ReturnsaDictionary with information about{handle}. Theitems are: "id" number of thechannel "status" "open", "buffered" or "closed", likech_status()When opened with ch_open(): "hostname" the hostname of the address "port" the port of the address "path" the path of the Unix-domain socket "sock_status" "open" or "closed" "sock_mode" "NL", "RAW", "JSON" or "JS" "sock_io" "socket" "sock_timeout" timeout in msecNote that "path"is only present for Unix-domain sockets, forregular ones "hostname" and "port" are present instead.When opened with job_start(): "out_status" "open", "buffered" or "closed" "out_mode" "NL", "RAW", "JSON" or "JS" "out_io" "null", "pipe", "file" or "buffer" "out_timeout" timeout in msec "err_status" "open", "buffered" or "closed" "err_mode" "NL", "RAW", "JSON" or "JS" "err_io" "out", "null", "pipe", "file" or "buffer" "err_timeout" timeout in msec "in_status" "open" or "closed" "in_mode" "NL", "RAW", "JSON", "JS" or "LSP" "in_io" "null", "pipe", "file" or "buffer" "in_timeout" timeout in msecCan also be usedasamethod:GetChannel()->ch_info()Return type: dict<any>ch_log({msg} [,{handle}])ch_log()WriteString{msg} in thechannel log file, ifit was openedwithch_logfile().The text "ch_log():"is prepended to the message to make clearit came from this function call and makeit easier to find inthe log file.When{handle}is passed thechannel numberis used for themessage.{handle} can beaChannel oraJob that hasa Channel. TheChannelmust be open for thechannel number to be used.Can also be usedasamethod:'did something'->ch_log()Return type: dict<any>ch_logfile({fname} [,{mode}])ch_logfile()Start loggingchannel activity to{fname}.When{fname}is an empty string: stop logging.When{mode}is omitted or contains "a" oris "o" then appendto the file.When{mode} contains "w" and not "a" start with an empty file.When{mode} contains "o" then log allterminal output.Otherwise only some interestingterminal outputis logged.Usech_log() to write log messages. The fileis flushedafter every message, onUnix you can use "tail-f" to see whatis going on in real time.To enable the log very early, to see whatis received fromaterminal during startup, use--log (this uses mode "ao"):vim --log logfileThis functionis not available in thesandbox.NOTE: thechannel communicationis stored in the file, beaware that this may contain confidential and privacy sensitiveinformation, e.g.a password you type inaterminal window.Can also be usedasamethod:'logfile'->ch_logfile('w')Return type:Numberch_open({address} [,{options}])ch_open()Openachannel to{address}. Seechannel.Returnsa Channel. Usech_status() to check for failure.{address}isa String, seechannel-address for the possibleaccepted forms.If{options}is givenitmust beaDictionary.Seechannel-open-options.Can also be usedasamethod:GetAddress()->ch_open()Return type:channelch_read({handle} [,{options}])ch_read()Read from{handle} and return the received message.{handle} can beaChannel oraJob that hasa Channel.Fora NLchannel this waits fora NL to arrive, except whenthereis nothing more to read (channel was closed).Seechannel-more.Can also be usedasamethod:GetChannel()->ch_read()Return type:Stringch_readblob({handle} [,{options}])ch_readblob()Likech_read() but reads binary data and returnsaBlob.Seechannel-more.Can also be usedasamethod:GetChannel()->ch_readblob()Return type:Blobch_readraw({handle} [,{options}])ch_readraw()Likech_read() but fora JS and JSONchannel does not decodethe message. Fora NLchannelit does not block waiting forthe NL to arrive, but otherwise works like ch_read().Seechannel-more.Can also be usedasamethod:GetChannel()->ch_readraw()Return type:Stringch_sendexpr({handle},{expr} [,{options}])ch_sendexpr()Send{expr} over{handle}. The{expr}is encodedaccording to the type of channel. The function cannot be usedwitha raw channel.Seechannel-use.E912{handle} can beaChannel oraJob that hasa Channel.When using the "lsp"channel mode,{expr}must beaDict.If thechannel modeis "lsp", then returnsa Dict. Otherwisereturns an empty String. If the "callback" itemis present in{options}, then the returnedDict contains the ID of therequest message. The ID can be used to senda cancellationrequest to the LSP server (if needed). Returns an emptyDicton error.Ifa response messageis not expected for{expr}, then don'tspecify the "callback" item in{options}.Can also be usedasamethod:GetChannel()->ch_sendexpr(expr)Return type: dict<any> orStringch_sendraw({handle},{expr} [,{options}])ch_sendraw()SendString orBlob{expr} over{handle}.Works likech_sendexpr(), but does not encode the request ordecode the response. The calleris responsible for thecorrect contents. Also does not adda newline forachannelin NL mode, the callermustdo that. The NL in the responseis removed.Seechannel-use.Can also be usedasamethod:GetChannel()->ch_sendraw(rawexpr)Return type: dict<any> orStringch_setoptions({handle},{options})ch_setoptions()Setoptions on{handle}:"callback"thechannel callback"timeout"default read timeout in msec"mode"mode for the wholechannelSeech_open() for more explanation.{handle} can beaChannel oraJob that hasa Channel.Note thatchanging the mode may cause queuedmessages to belost.Theseoptions cannot be changed:"waittime"only applies toch_open()Can also be usedasamethod:GetChannel()->ch_setoptions(options)Return type:Numberch_status({handle} [,{options}])ch_status()Return the status of{handle}:"fail"failed to open thechannel"open"channel can be used"buffered"channel can be read, not written to"closed"channel can not be used{handle} can beaChannel oraJob that hasa Channel."buffered"is used when thechannel was closed but thereisstill data that can be obtained withch_read().If{options}is givenit can containa "part" entry to specifythe part of thechannel to return the status for: "out" or"err". For example, to get the error status:ch_status(job, {"part": "err"})Can also be usedasamethod:GetChannel()->ch_status()Return type:String==============================================================================9. Startingajob withachanneljob-startjobTo startajob and openachannel for stdin/stdout/stderr: let job = job_start(command, {options})You can get thechannel with: let channel = job_getchannel(job)Thechannel will use NL mode. If you want another mode it's best to specifythis in{options}. Whenchanging the mode later some text may have alreadybeen received and not parsed correctly.If the command producesa line of output that you want to deal with, specifya handler for stdout: let job = job_start(command, {"out_cb": "MyHandler"})The function will be called with thechannel anda message. You would defineit like this: func MyHandler(channel, msg)Without the handler you need to read the output withch_read() orch_readraw(). You cando this in the close callback, seeread-in-close-cb.Note that if thejob exits before you read the output, the output may be lost.This depends on the system (onUnix this happens because closing the writeendofa pipe causes the readend to get EOF). To avoid this make thejob sleepfora short while beforeit exits.The handler defined for "out_cb" will not receive stderr. If you want tohandle that separately, add an "err_cb" handler: let job = job_start(command, {"out_cb": "MyHandler", \ "err_cb": "ErrHandler"})If you want to handle both stderr and stdout with one handler use the"callback" option: let job = job_start(command, {"callback": "MyHandler"})Depending on the system,startingajob canput Vim in the background, thestartedjob gets the focus. To avoid that, use theforeground() function.This might not always work when called early,put in the callback handler oruseatimer to callit after thejob has started.You can senda message to the command with ch_evalraw(). If thechannelis inJSON or JS mode you can use ch_evalexpr().There are severaloptions you can use, seejob-options.For example, to startajob and write its output in buffer "dummy":let logjob = job_start("tail -f /tmp/log", \ {'out_io': 'buffer', 'out_name': 'dummy'})sbuf dummyJob input from a bufferin_io-bufferTo runajob that reads froma buffer:let job = job_start({command}, \ {'in_io': 'buffer', 'in_name': 'mybuffer'})E915E918The bufferis found by name, similar tobufnr(). The buffermust exist andbe loaded whenjob_start()is called.By default this reads the whole buffer. This can be changed with the "in_top"and "in_bot" options.A special modeis when "in_top"is set to zero and "in_bot"is not set: Everytimea lineis added to the buffer, the last-but-one line will be sent to thejob stdin. This allows for editing the last line and sendingit when pressingEnter.channel-close-inWhen not using the special mode the pipe or socket will be closed after thelast line has been written. This signals the readingend that the inputfinished. You can also usech_close_in() to closeit sooner.NUL bytes in the text will be passed to thejob (internally Vim stores theseas NL bytes).Reading job output in the close callbackread-in-close-cbIf thejob can take some time and you don't need intermediate results, you canadda close callback and read the output there:func! CloseHandler(channel) while ch_status(a:channel, {'part': 'out'}) == 'buffered' echomsg ch_read(a:channel) endwhileendfunclet job = job_start(command, {'close_cb': 'CloseHandler'})You will want todo something more useful than "echomsg".==============================================================================10. Startingajob withoutachanneljob-start-nochannelTo start another process without creatinga channel: let job = job_start(command,\ {"in_io": "null", "out_io": "null", "err_io": "null"})This starts{command} in the background, Vim does not wait forit to finish.When Vim sees that neither stdin, stdout or stderr are connected, nochannelwill be created. Often you will want to include redirection in the command toavoidit getting stuck.There are severaloptions you can use, seejob-options.job-start-if-neededTo startajob only when connecting to an address does not work,do somethinglike this:let channel = ch_open(address, {"waittime": 0})if ch_status(channel) == "fail" let job = job_start(command) let channel = ch_open(address, {"waittime": 1000})endifNote that thewaittime forch_open() gives thejob one second to make the portavailable.==============================================================================11.Jobfunctionsjob-functions-detailsjob_getchannel({job})job_getchannel()Get thechannel handle that{job}is using.To check if thejob has no channel:if string(job_getchannel(job)) == 'channel fail'Can also be usedasamethod:GetJob()->job_getchannel()Return type:channeljob_info([{job}])job_info()ReturnsaDictionary with information about{job}: "status"whatjob_status() returns "channel"whatjob_getchannel() returns "cmd"List of command arguments used to start thejob "process"process ID "tty_in"terminal input name, empty when none "tty_out"terminal output name, empty when none "exitval"only valid when "status"is "dead" "exit_cb"function to be called on exit "stoponexit"job-stoponexit Only in Unix: "termsig"the signal which terminated the process(Seejob_stop() for the values)only valid when "status"is "dead" Only in MS-Windows: "tty_type"Type of virtual console in use.Values are "winpty" or "conpty".See'termwintype'.Without any arguments, returnsaList with allJob objects.Can also be usedasamethod:GetJob()->job_info()Return type: dict<any> or list<job> depending on whether{job}was givenjob_setoptions({job},{options})job_setoptions()Changeoptions for{job}. Supported are: "stoponexit"job-stoponexit "exit_cb"job-exit_cbCan also be usedasamethod:GetJob()->job_setoptions(options)Return type:Numberjob_start({command} [,{options}])job_start()Startajob and returnaJob object. Unlikesystem() and:!cmd this does not wait for thejob to finish.To startajob inaterminalwindow seeterm_start().If thejob fails to start thenjob_status() on the returnedJobobject results in "fail" and none of the callbacks will beinvoked.{command} can bea String. This works best on MS-Windows. OnUnixitis split up in whitespace separated parts to bepassed to execvp(). Arguments in doublequotes can containwhite space.{command} can bea List, where the first itemis theexecutable and further items are the arguments. All items areconverted to String. This works best on Unix.On MS-Windows,job_start() makesaGUI application hidden. Ifyou want to show it, use:!start instead.The commandis executed directly, not througha shell, the'shell' optionis not used. To use the shell:let job = job_start(["/bin/sh", "-c", "echo hello"])Or:let job = job_start('/bin/sh -c "echo hello"')Note that this will start two processes, the shell and thecommandit executes. If you don't want this use the "exec"shell command.OnUnix $PATHis used to search for the executable only whenthe command does not containa slash.Thejob will use the sameterminalas Vim. Ifit reads fromstdin thejob and Vim will be fighting over input, thatdoesn't work. Redirect stdin and stdout to avoid problems:let job = job_start(['sh', '-c', "myserver </dev/null >/dev/null"])The returnedJobobject can be used to get the status withjob_status() and stop thejob withjob_stop().Note that thejobobject will be deleted if there are noreferences to it. This closes the stdin and stderr, which maycause thejob to fail with an error. To avoid this keepareference to the job. Thus instead of:call job_start('my-command')use:let myjob = job_start('my-command')and unlet "myjob" once thejobis not needed oris past thepoint whereit would fail (e.g. whenit printsa message onstartup). Keep in mind thatvariables local toa functionwill cease to exist if the function returns. Useascript-local variable if needed:let s:myjob = job_start('my-command'){options}must bea Dictionary. It can contain many optionalitems, seejob-options.Can also be usedasamethod:BuildCommand()->job_start()Return type:jobjob_status({job})job_status()E916ReturnsaString with the status of{job}:"run"jobis running"fail"job failed to start"dead"job died or was stopped after runningOnUnixa non-existing command results in "dead" instead of"fail", becauseafork happens before the failure can bedetected.If inVim9scripta variableis declared with type "job" butnever assigned to, passing that variable tojob_status()returns "fail".If an exit callback was set with the "exit_cb" option and thejobis now detected to be "dead" the callback will be invoked.For more information seejob_info().Can also be usedasamethod:GetJob()->job_status()Return type:Stringjob_stop({job} [,{how}])job_stop()Stop the{job}. This can also be used to signal the job.When{how}is omitted oris "term" thejob will be terminated.ForUnix SIGTERMis sent. OnMS-Windows thejob will beterminated forcedly (thereis no "gentle" way).This goes to the process group, thus children may also beaffected.Effect for Unix:"term" SIGTERM (default)"hup" SIGHUP"quit" SIGQUIT"int" SIGINT"kill" SIGKILL (strongest way to stop)number signal with that numberEffect for MS-Windows:"term" terminate process forcedly (default)"hup" CTRL_BREAK"quit" CTRL_BREAK"int" CTRL_C"kill" terminate process forcedlyOthers CTRL_BREAKOnUnix the signalis sent to the process group. This meansthat when thejobis "sh-c command"it affects both the shelland the command.The resultisa Number: 1 if the operation could be executed,0 if "how"is not supported on the system.Note that even when the operation was executed, whether thejob was actually stopped needs to be checked withjob_status().If the status of thejobis "dead", the signal will not besent. Thisis to avoid to stop the wrongjob (esp. on Unix,where process numbers are recycled).When using "kill" Vim will assume thejob will die and closethe channel.Can also be usedasamethod:GetJob()->job_stop()Return type:Number==============================================================================12.Joboptionsjob-optionsThe{options} argument injob_start()isa dictionary. All entries areoptional. Someoptions can be used after thejob has started, usingjob_setoptions(job,{options}). Manyoptions can be used with thechannelrelated to the job, using ch_setoptions(channel,{options}).Seejob_setoptions() andch_setoptions().in_modeout_modeerr_mode"in_mode"mode specifically for stdin, only when using pipes"out_mode"mode specifically for stdout, only when using pipes"err_mode"mode specifically for stderr, only when using pipesSeechannel-mode for the values.Note: when setting "mode" the part specific modeisoverwritten. Therefore set "mode" first and the partspecific mode later.Note: whenwriting toa file or buffer and whenreading froma buffer NL modeis used by default.job-noblock"noblock": 1Whenwriting usea non-blocking write call. Thisavoids getting stuck if Vim should handle othermessages in between, e.g. whenajob sends back datato Vim. It implies that whench_sendraw() returnsnot all data may have been written yet.This option was added in patch 8.1.0350, test with:if has("patch-8.1.350") let options['noblock'] = 1endifjob-callback"callback": handlerCallback for something to read on any part of thechannel.job-out_cbout_cb"out_cb": handlerCallback for when thereis something to read onstdout. Only for when thechannel uses pipes. When"out_cb" wasn't set thechannel callbackis used.The two arguments are thechannel and the message.job-err_cberr_cb"err_cb": handlerCallback for when thereis something to read onstderr. Only for when thechannel uses pipes. When"err_cb" wasn't set thechannel callbackis used.The two arguments are thechannel and the message.job-close_cb"close_cb": handlerCallback for when thechannelis closed. Sameas"close_cb" onch_open(), seeclose_cb.job-drop"drop": whenSpecifies when to drop messages. Sameas "drop" onch_open(), seechannel-drop. For "auto" theexit_cbis not considered.job-exit_cb"exit_cb": handlerCallback for when thejob ends. The arguments are thejob and the exit status.Vim checks up to 10 times per second for jobs thatended. The check can also be triggered by callingjob_status(), which may then invoke the exit_cbhandler.Note that data can be buffered, callbacks may still becalled after the process ends.job-timeout"timeout": timeThe time to wait fora request when blocking, E.g.when using ch_evalexpr(). In milliseconds. Thedefaultis 2000 (2 seconds).out_timeouterr_timeout"out_timeout": timeTimeout for stdout. Only when using pipes."err_timeout": timeTimeout for stderr. Only when using pipes.Note: when setting "timeout" the part specific modeisoverwritten. Therefore set "timeout" first and thepart specific mode later.job-stoponexit"stoponexit":{signal}Send{signal} to thejob when Vim exits. Seejob_stop() for possible values."stoponexit": ""Do not stop thejob when Vim exits.The defaultis "term".job-term"term": "open"Startaterminal ina newwindow and connect thejobstdin/stdout/stderr to it. Similar to using:terminal.NOTE: Not implemented yet!"channel":{channel}Use an existingchannel instead of creatinga new one.The parts of thechannel that get used for the newjobwill be disconnected from what they were used before.If thechannel was still used by anotherjob this maycause I/O errors.Existing callbacks and other settings remain."pty": 1Usea pty (pseudo-tty) instead ofa pipe whenpossible. Thisis most useful in combination withaterminal window, seeterminal.{only on Unix and Unix-like systems}job-in_ioin_topin_botin_namein_buf"in_io": "null"disconnect stdin (read from /dev/null)"in_io": "pipe"stdinis connected to thechannel (default)"in_io": "file"stdin reads froma file"in_io": "buffer"stdin reads froma buffer"in_top": numberwhen using "buffer": first line to send (default: 1)"in_bot": numberwhen using "buffer": last line to send (default: last)"in_name": "/path/file"the name of the file or buffer to read from"in_buf": numberthe number of the buffer to read fromjob-out_ioout_nameout_buf"out_io": "null"disconnect stdout (goes to /dev/null)"out_io": "pipe"stdoutis connected to thechannel (default)"out_io": "file"stdout writes toa file"out_io": "buffer"stdout appends toa buffer (see below)"out_name": "/path/file" the name of the file or buffer to write to"out_buf": numberthe number of the buffer to write to"out_modifiable":0whenwriting toa buffer,'modifiable' will be off(see below)"out_msg":0whenwriting toa new buffer, the first line will beset to "Reading fromchannel output..."job-err_ioerr_nameerr_buf"err_io": "out"stderrmessages togo to stdout"err_io": "null"disconnect stderr (goes to /dev/null)"err_io": "pipe"stderris connected to thechannel (default)"err_io": "file"stderr writes toa file"err_io": "buffer"stderr appends toa buffer (see below)"err_name": "/path/file" the name of the file or buffer to write to"err_buf": numberthe number of the buffer to write to"err_modifiable":0whenwriting toa buffer,'modifiable' will be off(see below)"err_msg":0whenwriting toa new buffer, the first line will beset to "Reading fromchannel error...""block_write": numberonly for testing: pretend every other write to stdinwill block"env":dictenvironmentvariables for the new process"cwd": "/path/to/dir"current working directory for the new process;if the directory does not exist an erroris givenWriting to a bufferout_io-bufferWhen the out_io or err_io modeis "buffer" and thereisa callback, the textis appended to the buffer before invoking the callback.Whena bufferis used both for input and output, the output lines areputabove the last line, since the last lineis whatis written to thechannelinput. Otherwise lines are appended below the last line.When using JS or JSON mode with "buffer", onlymessages with zero or negativeID will be added to the buffer, after decoding+ encoding. Messages withapositive number will be handled bya callback, commands are handledas usual.The name of the buffer from "out_name" or "err_name"is compared the full nameof existing buffers, also after expanding the name for the current directory.E.g., whena buffer was created with ":edit somename" and the buffer nameis"somename"it will use that buffer.If thereis no matching buffera new bufferis created. Use an empty name toalways createa new buffer.ch_getbufnr() can then be used to get thebuffer number.Fora new buffer'buftype'is set to "nofile" and'bufhidden' to "hide". Ifyou prefer other settings, create the buffer first and pass the buffer number.out_modifiableerr_modifiableThe "out_modifiable" and "err_modifiable"options can be used to set the'modifiable' option off, or write toa buffer that has'modifiable' off. Thatmeans that lines will be appended to the buffer, but the user can't easilychange the buffer.out_msgerr_msgThe "out_msg" option can be used to specify whethera new buffer will have thefirst line set to "Reading fromchannel output...". The defaultis to add themessage. "err_msg" does the same forchannel error.When an existing bufferis to be written where'modifiable'is off and the"out_modifiable" or "err_modifiable"optionsis not zero, an erroris givenand the buffer will not be written to.When the buffer written tois displayed inawindow and the cursoris in thefirst column of the last line, the cursor will be moved to the newly addedline and thewindowis scrolled up to show the cursor if needed.Undois synced for every added line. NUL bytes are accepted (internally Vimstores theseas NL bytes).Writing to a fileE920The fileis created with permissions 600 (read-write for the user, notaccessible for others). Usesetfperm() to change this.If the file already existsitis truncated.==============================================================================13. Controllingajobjob-controlTo get the status ofa job:echo job_status(job)To makeajob stop running:job_stop(job)Thisis the normal way toenda job. OnUnixit sendsa SIGTERM to the job.Itis possible to use other ways to stop the job, or even send arbitrarysignals. E.g. to forceajob to stop, "killit":job_stop(job, "kill")For moreoptions seejob_stop().==============================================================================14. Usinga prompt bufferprompt-bufferIf you want to type input for thejob ina Vimwindow you havea few options:- Usea normal buffer and handle all possible commands yourself. This will be complicated, since there are so many possible commands.- Useaterminal window. This works well if what you type goes directly to thejob and thejob outputis directly displayed in the window. Seeterminal-window.- Useawindow witha prompt buffer. This works well when enteringa line for thejob in Vim while displaying (possibly filtered) output from the job.A prompt bufferis created by setting'buftype' to "prompt". You wouldnormally onlydo that ina newly created buffer.The user can edit and enter one line of textat the very last line of thebuffer. When pressing Enter in the prompt line the callback set withprompt_setcallback()is invoked. It would normally send the line toa job.Another callback would receive the output from thejob and displayit in thebuffer, below the prompt (and above the next prompt).Only the text in the last line, after the prompt,is editable. The rest ofthe bufferis not modifiable withNormal mode commands. It can be modified bycalling functions, suchasappend(). Using other commands may mess up thebuffer.After setting'buftype' to "prompt" Vim does not automatically startInsertmode, use:startinsert if you want to enterInsert mode, so that the usercan start typinga line.The text of the prompt can be set with theprompt_setprompt() function. Ifno promptis set withprompt_setprompt(), "% "is used. You can get theeffective prompt text fora buffer, withprompt_getprompt().The user cango toNormal mode and navigate through the buffer. This can beuseful to see older output or copy text.TheCTRL-W key can be used to startawindow command, suchasCTRL-Ww toswitch to the next window. This also works inInsert mode (use Shift-CTRL-Wto deletea word). When leaving thewindowInsert mode will be stopped. Whencoming back to the promptwindowInsert mode will be restored.Any command that startsInsert mode, suchas "a", "i", "A" and "I", will movethe cursor to the last line. "A" will move to theend of the line, "I" to thestart of the line.Hereis an example for Unix. It startsa shell in the background and promptsfor the next shell command. Output from the shellis displayed above theprompt." Create a channel log so we can see what happens.call ch_logfile('logfile', 'w')" Function handling a line of text that has been typed.func TextEntered(text) " Send the text to a shell with Enter appended. call ch_sendraw(g:shell_job, a:text .. "\n")endfunc" Function handling output from the shell: Add it above the prompt.func GotOutput(channel, msg) call append(line("$") - 1, "- " .. a:msg)endfunc" Function handling the shell exits: close the window.func JobExit(job, status) quit!endfunc" Start a shell in the background.let shell_job = job_start(["/bin/sh"], #{\ out_cb: function('GotOutput'),\ err_cb: function('GotOutput'),\ exit_cb: function('JobExit'),\ })newset buftype=promptlet buf = bufnr('')call prompt_setcallback(buf, function("TextEntered"))eval prompt_setprompt(buf, "shell command: ")" start accepting shell commandsstartinsertThe same inVim9 script:vim9script# Create a channel log so we can see what happens.ch_logfile('logfile', 'w')var shell_job: job# Function handling a line of text that has been typed.def TextEntered(text: string) # Send the text to a shell with Enter appended. ch_sendraw(shell_job, text .. "\n")enddef# Function handling output from the shell: Add it above the prompt.def GotOutput(channel: channel, msg: string) append(line("$") - 1, "- " .. msg)enddef# Function handling the shell exits: close the window.def JobExit(job: job, status: number) quit!enddef# Start a shell in the background.shell_job = job_start(["/bin/sh"], { out_cb: GotOutput, err_cb: GotOutput, exit_cb: JobExit, })newset buftype=promptvar buf = bufnr('')prompt_setcallback(buf, TextEntered)prompt_setprompt(buf, "shell command: ")# start accepting shell commandsstartinsert==============================================================================15. Language Server Protocollanguage-server-protocolThe language server protocol specificationis available at:https://microsoft.github.io/language-server-protocol/specificationEach LSP protocol message starts witha simple HTTP header followed by thepayload encoded in JSON-RPC format. Thisis described in:https://www.jsonrpc.org/specificationTo encode and senda LSP request/notification message ina VimDict intoaLSP JSON-RPC message and to receive and decodea LSP JSON-RPCresponse/notification message intoa VimDict, connect to the LSP serverwith thechannel-mode set to "lsp".Formessages received onachannel withchannel-mode set to "lsp", Vim willprocess the HTTP header and decode the JSON-RPC payload intoa VimDict typeand call thechannel-callback function or the specifiedchannel-onetime-callback function. When sendingmessages onachannel usingthech_evalexpr() orch_sendexpr() functions, Vim will add the HTTP headerand encode the Vimexpression into JSON. Refer tojson_encode() andjson_decode() for more information about how Vim encodes and decodes thebuiltin types into JSON.To openachannel using the "lsp" mode, set the "mode" item in thech_open(){options} argument to "lsp". Example: let ch = ch_open(..., #{mode: 'lsp'})To openachannel using the "lsp" mode witha job, set the "in_mode" and"out_mode" items in thejob_start(){options} argument to "lsp". Example: let cmd = ['clangd', '--background-index', '--clang-tidy'] let opts = {} let opts.in_mode = 'lsp' let opts.out_mode = 'lsp' let opts.err_mode = 'nl' let opts.out_cb = function('LspOutCallback') let opts.err_cb = function('LspErrCallback') let opts.exit_cb = function('LspExitCallback') let job = job_start(cmd, opts)Note that ifajob outputs LSPmessages on stdout and non-LSPmessages onstderr, then thechannel-callback function should handle both the messageformats appropriately or you should usea separate callback function for"out_cb" and "err_cb" to handle themas shown above.To synchronously senda JSON-RPC request to the server, use thech_evalexpr() function. This function will wait and return the decodedresponse message from the server. You can use either thechannel-timeout orthe "timeout" field in the{options} argument tocontrol the response waittime. If the request times out, then an emptyDictis returned. Example: let req = {} let req.method = 'textDocument/definition' let req.params = {} let req.params.textDocument = #{uri: 'a.c'} let req.params.position = #{line: 10, character: 3} let defs = ch_evalexpr(ch, req, #{timeout: 100}) if defs->empty() ... <handle failure> endifNote that in the request message the "id" field should not be specified. Ifitis specified, then Vim will overwrite the value with an internallygenerated identifier. Vim currently supports onlya number type for the "id"field.The callback function will be invoked for botha successful anda failed RPCrequest.To senda JSON-RPC request to the server and asynchronously process theresponse, use thech_sendexpr() function and supplya callback function. Ifthe "id" fieldis present in the request message, then Vim will overwriteitwith an internally generated number. This function returnsaDict with theidentifier used for the message. This can be used to send cancellationrequest to the LSP server (if needed). Example: let req = {} let req.method = 'textDocument/hover' let req.id = 200 let req.params = {} let req.params.textDocument = #{uri: 'a.c'} let req.params.position = #{line: 10, character: 3} let resp = ch_sendexpr(ch, req, #{callback: 'HoverFunc'})To cancel an outstanding asynchronous LSP request sent to the server using thech_sendexpr() function, senda cancellation message to the server using thech_sendexpr() function with the ID returned by thech_sendexpr() functionfor the request. Example: " send a completion request let req = {} let req.method = 'textDocument/completion' let req.params = {} let req.params.textDocument = #{uri: 'a.c'} let req.params.position = #{line: 10, character: 3} let reqstatus = ch_sendexpr(ch, req, #{callback: 'LspComplete'}) " send a cancellation notification let notif = {} let notif.method = '$/cancelRequest' let notif.id = reqstatus.id call ch_sendexpr(ch, notif)To senda JSON-RPC notification message to the server, use thech_sendexpr()function. As the server will not senda response message to the notification,don't specify the "callback" item. Example: call ch_sendexpr(ch, #{method: 'initialized'})To respond toa JSON-RPC request message from the server, use thech_sendexpr() function. In the response message, copy the "id" field valuefrom the server request message. Example: let resp = {} let resp.id = req.id let resp.result = 1 call ch_sendexpr(ch, resp)The JSON-RPC notificationmessages from the server are delivered through thechannel-callback function.Depending on the use case, you can use the ch_evalexpr(),ch_sendexpr() andch_sendraw()functions on the same channel.A LSP request message has the following format (expressedasa Vim Dict). The"params" fieldis optional: {"jsonrpc": "2.0","id": <number>,"method": <string>,"params": <list|dict> }A LSP response message has the following format (expressedasa Vim Dict).The "result" and "error" fields are optional: {"jsonrpc": "2.0","id": <number>,"result": <vim type>"error": <dict> }A LSP notification message has the following format (expressedasa Vim Dict).The "params" fieldis optional: {"jsonrpc": "2.0","method": <string>,"params": <list|dict> } vim:tw=78:ts=8:noet:ft=help:norl: