diff --git a/runtime/doc/Makefile b/runtime/doc/Makefile index dc49bb7e76..6bbcbc3676 100644 --- a/runtime/doc/Makefile +++ b/runtime/doc/Makefile @@ -17,6 +17,7 @@ DOCS = \ arabic.txt \ autocmd.txt \ change.txt \ + channel.txt \ cmdline.txt \ debug.txt \ debugger.txt \ @@ -151,6 +152,7 @@ HTMLS = \ arabic.html \ autocmd.html \ change.html \ + channel.html \ cmdline.html \ debug.html \ debugger.html \ diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt new file mode 100644 index 0000000000..21ce4ebda8 --- /dev/null +++ b/runtime/doc/channel.txt @@ -0,0 +1,218 @@ +*channel.txt* For Vim version 7.4. Last change: 2016 Jan 28 + + + VIM REFERENCE MANUAL by Bram Moolenaar + + + Inter-process communication *channel* + +DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT + +Vim uses channels to communicate with other processes. +A channel uses a socket. *socket-interface* + +Vim current supports up to 10 simultanious channels. +The Netbeans interface also uses a channel. |netbeans| + +1. Demo |channel-demo| +2. Opening a channel |channel-open| +3. Using a JSON channel |channel-use| +4. Vim commands |channel-commands| +5. Using a raw channel |channel-use| +6. Job control |job-control| + +{Vi does not have any of these features} +{only available when compiled with the |+channel| feature} + +============================================================================== +1. Demo *channel-demo* + +This requires Python. The demo program can be found in +$VIMRUNTIME/tools/demoserver.py +Run it in one terminal. We will call this T1. + +Run Vim in another terminal. Connect to the demo server with: > + let handle = connect('localhost:8765', 'json') + +In T1 you should see: + === socket opened === ~ + +You can now send a message to the server: > + echo sendexpr(handle, 'hello!') + +The message is received in T1 and a response is sent back to Vim. +You can see the raw messages in T1. What Vim sends is: + [1,"hello!"] ~ +And the response is: + [1,"got it"] ~ +The number will increase every time you send a message. + +The server can send a command to Vim. Type this on T1 (literally, including +the quotes): > + NOT IMPLEMENTED YET + ["ex","echo 'hi there'"] +And you should see the message in Vim. + +To handle asynchronous communication a callback needs to be used: > + func MyHandler(handle, msg) + echo "from the handler: " . a:msg + endfunc + call sendexpr(handle, 'hello!', "MyHandler") + +Instead of giving a callback with every send call, it can also be specified +when opening the channel: > + call disconnect(handle) + let handle = connect('localhost:8765', 'json', "MyHandler") + call sendexpr(handle, 'hello!', 0) + +============================================================================== +2. Opening a channel *channel-open* + +To open a channel: + let handle = connect({address}, {mode}, {callback}) + +{address} has the form "hostname:port". E.g., "localhost:8765". + +{mode} can be: *channel-mode* + "json" - Use JSON, see below; most convenient way + "raw" - Use raw messages + + *channel-callback* +{callback} is a function that is called when a message is received that is not +handled otherwise. It gets two arguments: the channel handle and the received +message. Example: > + func Handle(handle, msg) + echo 'Received: ' . a:msg + endfunc + let handle = connect("localhost:8765", 'json', "Handle") + +When {mode} is "json" the "msg" argument is the body of the received message, +converted to Vim types. +When {mode} is "raw" the "msg" argument is the whole message as a string. + +When {mode} is "json" the {callback} is optional. When omitted it is only +possible to receive a message after sending one. + +The handler can be added or changed later: > + call sethandler(handle, {callback}) +When {callback} is empty (zero or an empty string) the handler is removed. + +Once done with the channel, disconnect it like this: > + call disconnect(handle) + +============================================================================== +3. Using a JSON channel *channel-use* + +If {mode} is "json" then a message can be sent synchronously like this: > + let response = sendexpr(handle, {expr}) +This awaits a response from the other side. + +To send a message, without handling a response: > + call sendexpr(handle, {expr}, 0) + +To send a message and letting the response handled by a specific function, +asynchronously: > + call sendexpr(handle, {expr}, {callback}) + +The {expr} is converted to JSON and wrapped in an array. An example of the +message that the receiver will get when {expr} is the string "hello": + [12,"hello"] ~ + +The format of the JSON sent is: + [{number},{expr}] + +In which {number} is different every time. It must be used in the response +(if any): + + [{number},{response}] + +This way Vim knows which sent message matches with which received message and +can call the right handler. Also when the messages arrive out of order. + +The sender must always send valid JSON to Vim. Vim can check for the end of +the message by parsing the JSON. It will only accept the message if the end +was received. + +When the process wants to send a message to Vim without first receiving a +message, it must use the number zero: + [0,{response}] + +Then channel handler will then get {response} converted to Vim types. If the +channel does not have a handler the message is dropped. + +On read error or disconnect() the string "DETACH" is sent, if still possible. +The channel will then be inactive. + +============================================================================== +4. Vim commands *channel-commands* + +NOT IMPLEMENTED YET + +With a "json" channel the process can send commands to Vim that will be +handled by Vim internally, it does not require a handler for the channel. + +Possible commands are: + ["ex", {Ex command}] + ["normal", {Normal mode command}] + ["eval", {number}, {expression}] + ["expr", {expression}] + +With all of these: Be careful what these commands do! You can easily +interfere with what the user is doing. To avoid trouble use |mode()| to check +that the editor is in the expected state. E.g., to send keys that must be +inserted as text, not executed as a command: > + ["ex","if mode() == 'i' | call feedkeys('ClassName') | endif"] + +The "ex" command is executed as any Ex command. There is no response for +completion or error. You could use functions in an |autoload| script. +You can also invoke |feedkeys()| to insert anything. + +The "normal" command is executed like with |:normal|. + +The "eval" command will result in sending back the result of the expression: + [{number}, {result}] +Here {number} is the same as what was in the request. + +The "expr" command is similar, but does not send back any response. +Example: + ["expr","setline('$', ['one', 'two', 'three'])"] + +============================================================================== +5. Using a raw channel *channel-raw* + +If {mode} is "raw" then a message can be send like this: > + let response = sendraw(handle, {string}) +The {string} is sent as-is. The response will be what can be read from the +channel right away. Since Vim doesn't know how to recognize the end of the +message you need to take care of it yourself. + +To send a message, without expecting a response: > + call sendraw(handle, {string}, 0) +The process can send back a response, the channel handler will be called with +it. + +To send a message and letting the response handled by a specific function, +asynchronously: > + call sendraw(handle, {string}, {callback}) + +This {string} can also be JSON, use |jsonencode()| to create it and +|jsondecode()| to handle a received JSON message. + +============================================================================== +6. Job control *job-control* + +NOT IMPLEMENTED YET + +To start another process: > + call startjob({command}) + +This does not wait for {command} to exit. + +TODO: + + let handle = startjob({command}, 's') # uses stdin/stdout + let handle = startjob({command}, '', {address}) # uses socket + let handle = startjob({command}, 'd', {address}) # start if connect fails + + + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index ddc2b83ba5..aaad89f7e3 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1820,6 +1820,8 @@ complete_add( {expr}) Number add completion match complete_check() Number check for key typed during completion confirm( {msg} [, {choices} [, {default} [, {type}]]]) Number number of choice picked by user +connect( {address}, {mode} [, {callback}]) + Number open a channel copy( {expr}) any make a shallow copy of {expr} cos( {expr}) Float cosine of {expr} cosh( {expr}) Float hyperbolic cosine of {expr} @@ -2027,6 +2029,10 @@ searchpairpos( {start}, {middle}, {end} [, {flags} [, {skip} [...]]]) List search for other end of start/end pair searchpos( {pattern} [, {flags} [, {stopline} [, {timeout}]]]) List search for {pattern} +sendexpr( {handle}, {expr} [, {callback}]) + any send {expr} over JSON channel {handle} +sendraw( {handle}, {string} [, {callback}]) + any send {string} over raw channel {handle} server2client( {clientid}, {string}) Number send reply string serverlist() String get a list of available servers @@ -2660,6 +2666,18 @@ confirm({msg} [, {choices} [, {default} [, {type}]]]) don't fit, a vertical layout is used anyway. For some systems the horizontal layout is always used. +connect({address}, {mode} [, {callback}]) *connect()* + Open a channel to {address}. See |channel|. + + {address} has the form "hostname:port", e.g., + "localhost:8765". + + {mode} is either "json" or "raw". See |channel-mode| for the + meaning. + + {callback} is a function that handles received messages on the + channel. See |channel-callback|. + *copy()* copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't different from using {expr} directly. @@ -3861,7 +3879,9 @@ glob2regpat({expr}) *glob2regpat()* if filename =~ glob2regpat('Make*.mak') < This is equivalent to: > if filename =~ '^Make.*\.mak$' -< +< When {expr} is an empty string the result is "^$", match an + empty string. + *globpath()* globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]]) Perform glob() on all directories in {path} and concatenate @@ -5593,6 +5613,23 @@ searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *searchpos()* < In this example "submatch" is 2 when a lowercase letter is found |/\l|, 3 when an uppercase letter is found |/\u|. +sendexpr({handle}, {expr} [, {callback}]) *sendexpr()* + Send {expr} over JSON channel {handle}. See |channel-use|. + + When {callback} is given returns immediately. Without + {callback} waits for a JSON response and returns the decoded + expression. When there is an error or timeout returns an + empty string. + + When {callback} is zero no response is expected. + Otherwise {callback} must be a Funcref or the name of a + function. It is called when the response is received. See + |channel-callback|. + +sendraw({handle}, {string} [, {callback}]) *sendraw()* + Send {string} over raw channel {handle}. See |channel-raw|. + Works like |sendexpr()|, but does not decode the response. + server2client( {clientid}, {string}) *server2client()* Send a reply string to {clientid}. The most recent {clientid} that sent a string can be retrieved with expand(""). diff --git a/runtime/doc/netbeans.txt b/runtime/doc/netbeans.txt index 19fe1a069a..7c549a9d50 100644 --- a/runtime/doc/netbeans.txt +++ b/runtime/doc/netbeans.txt @@ -1,10 +1,10 @@ -*netbeans.txt* For Vim version 7.4. Last change: 2015 Mar 14 +*netbeans.txt* For Vim version 7.4. Last change: 2016 Jan 27 VIM REFERENCE MANUAL by Gordon Prieur et al. - *socket-interface* *netbeans* *netbeans-support* + *netbeans* *netbeans-support* Vim NetBeans Protocol: a socket interface for Vim integration into an IDE. diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index be84190e31..e31934b210 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -1,4 +1,4 @@ -*syntax.txt* For Vim version 7.4. Last change: 2016 Jan 19 +*syntax.txt* For Vim version 7.4. Last change: 2016 Jan 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -3458,7 +3458,7 @@ SYNTAX ISKEYWORD SETTING *:syn-iskeyword* If no argument is given, the current value will be output. Setting this option influences what |/\k| matches in syntax patterns - and also determines where |:syn-keywords| will be checked for a new + and also determines where |:syn-keyword| will be checked for a new match. It is recommended when writing syntax files, to use this command diff --git a/runtime/doc/tags b/runtime/doc/tags index 29ec9b2276..3b0d47afa1 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1195,6 +1195,7 @@ $VIMRUNTIME starting.txt /*$VIMRUNTIME* +browse various.txt /*+browse* +builtin_terms various.txt /*+builtin_terms* +byte_offset various.txt /*+byte_offset* ++channel various.txt /*+channel* +cindent various.txt /*+cindent* +clientserver various.txt /*+clientserver* +clipboard various.txt /*+clipboard* @@ -5222,6 +5223,15 @@ changelog.vim syntax.txt /*changelog.vim* changenr() eval.txt /*changenr()* changetick eval.txt /*changetick* changing change.txt /*changing* +channel channel.txt /*channel* +channel-callback channel.txt /*channel-callback* +channel-commands channel.txt /*channel-commands* +channel-demo channel.txt /*channel-demo* +channel-mode channel.txt /*channel-mode* +channel-open channel.txt /*channel-open* +channel-raw channel.txt /*channel-raw* +channel-use channel.txt /*channel-use* +channel.txt channel.txt /*channel.txt* char-variable eval.txt /*char-variable* char2nr() eval.txt /*char2nr()* characterwise motion.txt /*characterwise* @@ -5356,6 +5366,7 @@ complex-repeat repeat.txt /*complex-repeat* compress pi_gzip.txt /*compress* conceal syntax.txt /*conceal* confirm() eval.txt /*confirm()* +connect() eval.txt /*connect()* connection-refused message.txt /*connection-refused* console-menus gui.txt /*console-menus* control intro.txt /*control* @@ -6845,6 +6856,7 @@ java-indenting indent.txt /*java-indenting* java.vim syntax.txt /*java.vim* javascript-cinoptions indent.txt /*javascript-cinoptions* javascript-indenting indent.txt /*javascript-indenting* +job-control channel.txt /*job-control* join() eval.txt /*join()* jsbterm-mouse options.txt /*jsbterm-mouse* jsondecode() eval.txt /*jsondecode()* @@ -7989,6 +8001,8 @@ sed.vim syntax.txt /*sed.vim* self eval.txt /*self* send-money sponsor.txt /*send-money* send-to-menu gui_w32.txt /*send-to-menu* +sendexpr() eval.txt /*sendexpr()* +sendraw() eval.txt /*sendraw()* sendto gui_w32.txt /*sendto* sentence motion.txt /*sentence* server-functions usr_41.txt /*server-functions* @@ -8049,7 +8063,7 @@ sniff if_sniff.txt /*sniff* sniff-commands if_sniff.txt /*sniff-commands* sniff-compiling if_sniff.txt /*sniff-compiling* sniff-intro if_sniff.txt /*sniff-intro* -socket-interface netbeans.txt /*socket-interface* +socket-interface channel.txt /*socket-interface* sort() eval.txt /*sort()* sorting change.txt /*sorting* soundfold() eval.txt /*soundfold()* @@ -8601,7 +8615,6 @@ timestamps editing.txt /*timestamps* tips tips.txt /*tips* tips.txt tips.txt /*tips.txt* todo todo.txt /*todo* -todo.txt todo.txt /*todo.txt* toggle options.txt /*toggle* toggle-revins version4.txt /*toggle-revins* tolower() eval.txt /*tolower()* diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt index 2570ac8285..daba2e6071 100644 --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -1,4 +1,4 @@ -*todo.txt* For Vim version 7.4. Last change: 2016 Jan 21 +todo.txt* For Vim version 7.4. Last change: 2016 Jan 27 VIM REFERENCE MANUAL by Bram Moolenaar @@ -78,9 +78,24 @@ Regexp problems: - "\%1l^#.*" does not match on a line starting with "#". The zero-width match clears the start-of-line flag. ++channel: +- cleanup on exit? in mch_getout() and getout(). +- more contents in channel.txt + C89: remove __ARGS in more places -- Script: Hirohito Higashi, Jan 21. -- Update to osdef.sh, Hirohito Higashi, 2016 Jan 21. +- /tmp/noargs.vim +- /tmp/eliminate__ARGS.vim +- Script: Hirohito Higashi, Jan 25, 2nd one. +- Assume HAVE_STDARG_H is always defined. + +This difference is unexpected: + echo v:true == 1 + 1 + echo [v:true] == [1] + 0 +It's because tv_equal() works different. + +Do we need to roll-back patch 1165, that put libintl-8.dll before libintl.dll? Need to try out instructions in INSSTALLpc.txt about how to install all interfaces and how to build Vim with them. @@ -96,9 +111,18 @@ work. (ZyX, 2013 Sep 28) With examples: (Malcolm Rowe, 2015 Dec 24) Problem using ":try" inside ":execute". (ZyX, 2013 Sep 15) +jsonencode(): should convert to utf-8. (Nikolai Pavlov, 2016 Jan 23) +What if there is an invalid character? + +Should jsonencode()/jsondecode() restrict recursiveness? +Or avoid recursiveness. + Use vim.vim syntax highlighting for help file examples, but without ":" in 'iskeyword' for syntax. +Patch to make "%:h:h" return "." instead of the full path. +(Coot, 2016 Jan 24, #592) + Remove SPACE_IN_FILENAME ? What could possibly go wrong? Installation of .desktop files does not work everywhere. @@ -118,6 +142,13 @@ Win32: patch to use 64 bit stat() if possible. (Ken Takata, 2014 May 12) More tests May 14. Update May 29. Update Aug 10. Now part of large file patches. (Ken Takata, 2016 Jan 19, second one) Updated patches with ordering: Jan 20. +And another update: Jan 24 + +7 Add a watchpoint in the debug mode: An expression that breaks execution + when evaluating to non-zero. Add the "watchadd expr" command, stop when + the value of the expression changes. ":watchdel" deletes an item, + ":watchlist" lists the items. (Charles Campbell) +Patch by Christian Brabandt, 2016 Jan 27. Using ":windo" to set options in all windows has the side effect that it changes the window layout and the current window. Make a variant that saves @@ -140,6 +171,8 @@ Instead of separately uploading patches to the ftp site, we can get them from github with a URL like this: https://github.com/vim/vim/compare/v7.4.920%5E...v7.4.920.diff Diff for version.c contains more context, can't skip a patch. + +Duplication of completion suggestions for ":!hom". Issue 539. > When t_Co is changed from termresponse, the OptionSet autocmmand event isn't triggered. Use the code from the end of set_num_option() in @@ -147,6 +180,13 @@ set_color_count(). Python: ":py raw_input('prompt')" doesn't work. (Manu Hack) +Comparing nested structures with "==" uses a different comperator than when +comparing individual items. +Also, "'' == 0" evaluates to true, which isn't nice. +Add "===" to have a strict comparison (type and value match). +Add "==*" (?) to have a value match, but no automatic conversion, and v:true +equals 1 and 1.0, v:false equals 0 and 0.0.? + Plugin to use Vim in MANPAGER. Konfekt, PR #491 Using uninitialized memory. (Dominique Pelle, 2015 Nov 4) @@ -171,6 +211,10 @@ Build with Python on Mac does not always use the right library. (Kazunobu Kuriyama, 2015 Mar 28) Need a Vim equivalent of Python's None and a way to test for it. +Use v:none. var == v:none + +Patch to add arguments to argc() and argv(). (Yegappan Lakshmanan, 2016 Jan +24) Also need a way to get the global arg list? Update later on Jan 24 To support Thai (and other languages) word boundaries, include the ICU library: http://userguide.icu-project.org/boundaryanalysis @@ -221,7 +265,7 @@ Sep 10) Patch to be able to use hex numbers with :digraph. (Lcd, 2015 Sep 6) Update Sep 7. Update by Christian Brabandt, 2015 Sep 8. -Patch to improve I/O for Perl. (Damine, 2015 Jan 9) +Patch to improve I/O for Perl. (Damien, 2015 Jan 9, update Jan 22 2nd one) Patch to set antialiasing style on Windows. (Ondrej Balaz, 2013 Mar 14) Needs a different check for CLEARTYPE_QUALITY. @@ -258,6 +302,9 @@ same thing. Remarks on issue 543 (Roland Puntaier). Patch to add grepfile(). (Scott Prager, 2015 May 26) Work in progress. +Would be useful to have a treemap() or deepmap() function. Like map() but +when an item is a list or dict would recurse into it. + Patch for global-local options consistency. (Arnaud Decara, 2015 Jul 22) Is this right? @@ -326,6 +373,8 @@ Should be easy to highlight all matches with 'incsearch'. Idea by Itchyny, Wrong scrolling when using incsearch. Patch by Christian Brabandt, 2014 Dec 4. Is this a good solution? +Patch to add /pattern/ to :oldfiles. Pull #575. + Patch to allow setting w:quickfix_title via setqflist() and setloclist() functions. (Christian Brabandt, 2013 May 8, update May 21) Patch to add getlocstack() / setlocstack(). (Christian Brabandt, 2013 May 14) @@ -1115,8 +1164,6 @@ Use json format for new items in .viminfo: |["text","text text text" |"continuation line"] |["hist",242342342,{"arg":"value"}] - Use \" for a single ". Use \\ for a \. - See http://www.ietf.org/rfc/rfc4627.txt Writing nested List and Dict in viminfo gives error message and can't be read back. (Yukihiro Nakadaira, 2010 Nov 13) @@ -2136,28 +2183,6 @@ Add an option for a minimal text length before inserting a line break for Better plugin support (not plugin manager, see elsewhere for that): -- Add interface to another process, e.g. to run a background plugin. - Can use the code from netbeans to communicate over a socket. - A bit like +clientserver but without the hassle of starting another Vim. - Use json for the messages. - let handle = startjob({command}) # uses stdin/stdout - let handle = startjob({command}, {address}) # uses socket - let handle = connect({address}) # uses socket - let handle = deamon({command}, {address}) # start it if connect fails - let response = sendjson(handle, {json}) # sync - call sendjson(handle, {json}, {callback}) # async - call sethandler(handle, {callback}) - The response json is wrapped in an array: - [{code},{response}] - {code} must be positive, when zero the callback from sethandler() is called - The job can send Vim commands that do not require a handler: - ['ex', {Ex command}] - ['normal', {Normal mode command}] - ['keys', {condition}, {key sequence}] - ['eval', {expression}] sync, will send back result - ['expr', {expression}] async -- Native JSON support (to be able to commucate with any interface in the same - way). - Avoid use of feedkeys, add eval functions where needed: - manipulating the Visual selection? - Add createmark(): add a mark like mM, but return a unique ID. Need some way @@ -2166,6 +2191,8 @@ Better plugin support (not plugin manager, see elsewhere for that): - Plugins need to make a lot of effort, lots of mappings, to know what happened before pressing the key that triggers a plugin action. How about keeping the last N pressed keys, so that they do not need to be mapped? +- equivalent of netbeans_beval_cb(). With an autocommand? +- Add something to enable debugging when a remote message is received. More patches: @@ -5242,13 +5269,8 @@ Registers: Debug mode: -7 Add something to enable debugging when a remote message is received. 8 Add breakpoints for setting an option 8 Add breakpoints for assigning to a variable. -7 Add a watchpoint in the debug mode: An expression that breaks execution - when evaluating to non-zero. Add the "watchadd expr" command, stop when - the value of the expression changes. ":watchdel" deletes an item, - ":watchlist" lists the items. (Charles Campbell) 7 Store the history from debug mode in viminfo. 7 Make the debug mode history available with histget() et al. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 7ef10e3b42..9f2442e9b4 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1,4 +1,4 @@ -*usr_41.txt* For Vim version 7.4. Last change: 2015 Nov 30 +*usr_41.txt* For Vim version 7.4. Last change: 2016 Jan 28 VIM USER MANUAL - by Bram Moolenaar @@ -893,6 +893,14 @@ Testing: *test-functions* assert_false() assert that an expression is false assert_true() assert that an expression is true +Inter-process communication: + connect() open a channel + disconnect() close a channel + sendexpr() send a JSON message over a channel + sendraw() send a raw message over a channel + jsonencode() encode an expression to a JSON string + jsondecode() decode a JSON string to Vim types + Various: *various-functions* mode() get current editing mode visualmode() last visual mode used diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 16eeac1992..4d588f05e7 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -1,4 +1,4 @@ -*various.txt* For Vim version 7.4. Last change: 2016 Jan 10 +*various.txt* For Vim version 7.4. Last change: 2016 Jan 27 VIM REFERENCE MANUAL by Bram Moolenaar @@ -319,6 +319,7 @@ N *+builtin_terms* some terminals builtin |builtin-terms| B *++builtin_terms* maximal terminals builtin |builtin-terms| N *+byte_offset* support for 'o' flag in 'statusline' option, "go" and ":goto" commands. +m *+channel* inter process communication |channel| N *+cindent* |'cindent'|, C indenting N *+clientserver* Unix and Win32: Remote invocation |clientserver| *+clipboard* |clipboard| support diff --git a/runtime/indent/fortran.vim b/runtime/indent/fortran.vim index d492889fc7..e19a19fb1f 100644 --- a/runtime/indent/fortran.vim +++ b/runtime/indent/fortran.vim @@ -1,11 +1,11 @@ " Vim indent file " Language: Fortran 2008 (and older: Fortran 2003, 95, 90, and 77) -" Version: 0.42 -" Last Change: 2015 Nov. 30 +" Version: 0.44 +" Last Change: 2016 Jan. 26 " Maintainer: Ajit J. Thakkar ; " Usage: For instructions, do :help fortran-indent from Vim " Credits: -" Useful suggestions were made by: Albert Oliver Serra. +" Useful suggestions were made by: Albert Oliver Serra and Takuya Fujiwara. " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -92,10 +92,10 @@ function FortranGetIndent(lnum) "Indent do loops only if they are all guaranteed to be of do/end do type if exists("b:fortran_do_enddo") || exists("g:fortran_do_enddo") if prevstat =~? '^\s*\(\d\+\s\)\=\s*\(\a\w*\s*:\)\=\s*do\>' - let ind = ind + &sw + let ind = ind + shiftwidth() endif if getline(v:lnum) =~? '^\s*\(\d\+\s\)\=\s*end\s*do\>' - let ind = ind - &sw + let ind = ind - shiftwidth() endif endif @@ -105,14 +105,14 @@ function FortranGetIndent(lnum) \ ||prevstat=~? '^\s*\(type\|interface\|associate\|enum\)\>' \ ||prevstat=~?'^\s*\(\d\+\s\)\=\s*\(\a\w*\s*:\)\=\s*\(forall\|where\|block\)\>' \ ||prevstat=~? '^\s*\(\d\+\s\)\=\s*\(\a\w*\s*:\)\=\s*if\>' - let ind = ind + &sw + let ind = ind + shiftwidth() " Remove unwanted indent after logical and arithmetic ifs if prevstat =~? '\' && prevstat !~? '\' - let ind = ind - &sw + let ind = ind - shiftwidth() endif " Remove unwanted indent after type( statements if prevstat =~? '^\s*type\s*(' - let ind = ind - &sw + let ind = ind - shiftwidth() endif endif @@ -125,12 +125,12 @@ function FortranGetIndent(lnum) \ ||prevstat =~? '^\s*'.prefix.'subroutine\>' \ ||prevstat =~? '^\s*'.prefix.type.'function\>' \ ||prevstat =~? '^\s*'.type.prefix.'function\>' - let ind = ind + &sw + let ind = ind + shiftwidth() endif if getline(v:lnum) =~? '^\s*contains\>' \ ||getline(v:lnum)=~? '^\s*end\s*' \ .'\(function\|subroutine\|module\|program\)\>' - let ind = ind - &sw + let ind = ind - shiftwidth() endif endif @@ -141,23 +141,23 @@ function FortranGetIndent(lnum) \. '\(else\|else\s*if\|else\s*where\|case\|' \. 'end\s*\(if\|where\|select\|interface\|' \. 'type\|forall\|associate\|enum\|block\)\)\>' - let ind = ind - &sw + let ind = ind - shiftwidth() " Fix indent for case statement immediately after select if prevstat =~? '\' - let ind = ind + &sw + let ind = ind + shiftwidth() endif endif "First continuation line if prevstat =~ '&\s*$' && prev2stat !~ '&\s*$' - let ind = ind + &sw + let ind = ind + shiftwidth() endif if prevstat =~ '&\s*$' && prevstat =~ '\' - let ind = ind - &sw + let ind = ind - shiftwidth() endif "Line after last continuation line if prevstat !~ '&\s*$' && prev2stat =~ '&\s*$' && prevstat !~? '\' - let ind = ind - &sw + let ind = ind - shiftwidth() endif return ind diff --git a/runtime/indent/zimbu.vim b/runtime/indent/zimbu.vim index 9565b10843..5451877ea7 100644 --- a/runtime/indent/zimbu.vim +++ b/runtime/indent/zimbu.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: Zimbu " Maintainer: Bram Moolenaar -" Last Change: 2012 Sep 08 +" Last Change: 2016 Jan 25 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -74,9 +74,9 @@ func GetZimbuIndent(lnum) \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')" \ . " =~ '\\(Comment\\|String\\|Char\\)$'") if pp > 0 - return indent(prevLnum) + &sw + return indent(prevLnum) + shiftwidth() endif - return indent(prevLnum) + &sw * 2 + return indent(prevLnum) + shiftwidth() * 2 endif if plnumstart == p return indent(prevLnum) @@ -102,13 +102,13 @@ func GetZimbuIndent(lnum) endif if prevline =~ '^\s*\(IF\|\|ELSEIF\|ELSE\|GENERATE_IF\|\|GENERATE_ELSEIF\|GENERATE_ELSE\|WHILE\|REPEAT\|TRY\|CATCH\|FINALLY\|FOR\|DO\|SWITCH\|CASE\|DEFAULT\|FUNC\|VIRTUAL\|ABSTRACT\|DEFINE\|REPLACE\|FINAL\|PROC\|MAIN\|NEW\|ENUM\|CLASS\|INTERFACE\|BITS\|MODULE\|SHARED\)\>' - let plindent += &sw + let plindent += shiftwidth() endif if thisline =~ '^\s*\(}\|ELSEIF\>\|ELSE\>\|CATCH\|FINALLY\|GENERATE_ELSEIF\>\|GENERATE_ELSE\>\|UNTIL\>\)' - let plindent -= &sw + let plindent -= shiftwidth() endif if thisline =~ '^\s*\(CASE\>\|DEFAULT\>\)' && prevline !~ '^\s*SWITCH\>' - let plindent -= &sw + let plindent -= shiftwidth() endif " line up continued comment that started after some code diff --git a/runtime/tools/demoserver.py b/runtime/tools/demoserver.py new file mode 100644 index 0000000000..c72a58b739 --- /dev/null +++ b/runtime/tools/demoserver.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# Server that will accept connections from a Vim channel. +# Run this server and then in Vim you can open the channel: +# :let handle = connect('localhost:8765', 'json') +# +# Then Vim can send requests to the server: +# :let response = sendexpr(handle, 'hello!') +# +# And you can control Vim by typing a JSON message here, e.g.: +# ["ex","echo 'hi there'"] +# +# See ":help channel-demo" in Vim. + +import SocketServer +import json +import socket +import sys +import threading + +thesocket = None + +class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler): + + def handle(self): + print "=== socket opened ===" + global thesocket + thesocket = self.request + while True: + try: + data = self.request.recv(4096) + except socket.error: + print "=== socket error ===" + break + except IOError: + print "=== socket closed ===" + break + if data == '': + print "=== socket closed ===" + break + print "received: {}".format(data) + try: + decoded = json.loads(data) + except ValueError: + print "json decoding failed" + decoded = [0, ''] + + if decoded[1] == 'hello!': + response = "got it" + else: + response = "what?" + encoded = json.dumps([decoded[0], response]) + print "sending {}".format(encoded) + self.request.sendall(encoded) + thesocket = None + +class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + pass + +if __name__ == "__main__": + HOST, PORT = "localhost", 8765 + + server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) + ip, port = server.server_address + + # Start a thread with the server -- that thread will then start one + # more thread for each request + server_thread = threading.Thread(target=server.serve_forever) + + # Exit the server thread when the main thread terminates + server_thread.daemon = True + server_thread.start() + print "Server loop running in thread: ", server_thread.name + + print "Listening on port {}".format(PORT) + while True: + typed = sys.stdin.readline() + if "quit" in typed: + print "Goodbye!" + break + if thesocket is None: + print "No socket yet" + else: + print "sending {}".format(typed) + thesocket.sendall(typed) + + server.shutdown() + server.server_close() diff --git a/src/channel.c b/src/channel.c index 6c8c330bc9..8ec2c1bf89 100644 --- a/src/channel.c +++ b/src/channel.c @@ -77,11 +77,11 @@ struct readqueue typedef struct readqueue queue_T; typedef struct { - sock_T ch_fd; /* the socket, -1 for a closed channel */ - int ch_idx; /* used by channel_poll_setup() */ - queue_T ch_head; /* dummy node, header for circular queue */ + sock_T ch_fd; /* the socket, -1 for a closed channel */ + int ch_idx; /* used by channel_poll_setup() */ + queue_T ch_head; /* dummy node, header for circular queue */ - int ch_error; /* When TRUE an error was reported. Avoids giving + int ch_error; /* When TRUE an error was reported. Avoids giving * pages full of error messages when the other side * has exited, only mention the first error until the * connection works again. */ @@ -89,16 +89,22 @@ typedef struct { XtInputId ch_inputHandler; /* Cookie for input */ #endif #ifdef FEAT_GUI_GTK - gint ch_inputHandler; /* Cookie for input */ + gint ch_inputHandler; /* Cookie for input */ #endif #ifdef FEAT_GUI_W32 - int ch_inputHandler; /* simply ret.value of WSAAsyncSelect() */ + int ch_inputHandler; /* simply ret.value of WSAAsyncSelect() */ #endif #ifdef FEAT_GUI_MACVIM int ch_inputHandler; #endif - void (*ch_close_cb)(void); /* callback invoked when channel is closed */ + void (*ch_close_cb)(void); /* callback for when channel is closed */ + + char_u *ch_callback; /* function to call when a msg is not handled */ + char_u *ch_req_callback; /* function to call for current request */ + int ch_will_block; /* do not use callback right now */ + + int ch_json_mode; } channel_T; /* @@ -209,7 +215,7 @@ channel_gui_register(int idx) channel->ch_inputHandler = XtAppAddInput((XtAppContext)app_context, channel->ch_fd, (XtPointer)(XtInputReadMask + XtInputExceptMask), - messageFromNetbeans, (XtPointer)idx); + messageFromNetbeans, (XtPointer)(long)idx); # else # ifdef FEAT_GUI_GTK /* @@ -424,13 +430,153 @@ channel_open(char *hostname, int port_in, void (*close_cb)(void)) return idx; } +/* + * Set the json mode of channel "idx" to TRUE or FALSE. + */ + void +channel_set_json_mode(int idx, int json_mode) +{ + channels[idx].ch_json_mode = json_mode; +} + +/* + * Set the callback for channel "idx". + */ + void +channel_set_callback(int idx, char_u *callback) +{ + vim_free(channels[idx].ch_callback); + channels[idx].ch_callback = vim_strsave(callback); +} + +/* + * Set the callback for channel "idx" for the next response. + */ + void +channel_set_req_callback(int idx, char_u *callback) +{ + vim_free(channels[idx].ch_req_callback); + channels[idx].ch_req_callback = callback == NULL + ? NULL : vim_strsave(callback); +} + +/* + * Set the flag that the callback for channel "idx" should not be used now. + */ + void +channel_will_block(int idx) +{ + channels[idx].ch_will_block = TRUE; +} + +/* + * Decode JSON "msg", which must have the form "[nr, expr]". + * Put "expr" in "tv". + * Return OK or FAIL. + */ + int +channel_decode_json(char_u *msg, typval_T *tv) +{ + js_read_T reader; + typval_T listtv; + + reader.js_buf = msg; + reader.js_eof = TRUE; + reader.js_used = 0; + json_decode(&reader, &listtv); + /* TODO: use the sequence number */ + if (listtv.v_type == VAR_LIST + && listtv.vval.v_list->lv_len == 2 + && listtv.vval.v_list->lv_first->li_tv.v_type == VAR_NUMBER) + { + /* Move the item from the list and then change the type to avoid the + * item being freed. */ + *tv = listtv.vval.v_list->lv_last->li_tv; + listtv.vval.v_list->lv_last->li_tv.v_type = VAR_NUMBER; + list_unref(listtv.vval.v_list); + return OK; + } + + /* give error message? */ + clear_tv(&listtv); + return FAIL; +} + +/* + * Invoke the "callback" on channel "idx". + */ + static void +invoke_callback(int idx, char_u *callback) +{ + typval_T argv[3]; + typval_T rettv; + int dummy; + char_u *msg; + int ret = OK; + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = idx; + + /* Concatenate everything into one buffer. + * TODO: only read what the callback will use. + * TODO: avoid multiple allocations. */ + while (channel_collapse(idx) == OK) + ; + msg = channel_get(idx); + + if (channels[idx].ch_json_mode) + ret = channel_decode_json(msg, &argv[1]); + else + { + argv[1].v_type = VAR_STRING; + argv[1].vval.v_string = msg; + } + + if (ret == OK) + { + call_func(callback, (int)STRLEN(callback), + &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL); + /* If an echo command was used the cursor needs to be put back where + * it belongs. */ + setcursor(); + cursor_on(); + out_flush(); + } + vim_free(msg); +} + +/* + * Invoke a callback for channel "idx" if needed. + */ + static void +may_invoke_callback(int idx) +{ + if (channels[idx].ch_will_block) + return; + if (channel_peek(idx) == NULL) + return; + + if (channels[idx].ch_req_callback != NULL) + { + /* invoke the one-time callback */ + invoke_callback(idx, channels[idx].ch_req_callback); + channels[idx].ch_req_callback = NULL; + return; + } + + if (channels[idx].ch_callback != NULL) + /* invoke the channel callback */ + invoke_callback(idx, channels[idx].ch_callback); +} + /* * Return TRUE when channel "idx" is open. + * Also returns FALSE or invalid "idx". */ int channel_is_open(int idx) { - return channels[idx].ch_fd >= 0; + return idx >= 0 && idx < channel_count && channels[idx].ch_fd >= 0; } /* @@ -449,13 +595,16 @@ channel_close(int idx) #ifdef FEAT_GUI channel_gui_unregister(idx); #endif + vim_free(channel->ch_callback); + channel->ch_callback = NULL; } } /* * Store "buf[len]" on channel "idx". + * Returns OK or FAIL. */ - void + int channel_save(int idx, char_u *buf, int len) { queue_T *node; @@ -463,12 +612,12 @@ channel_save(int idx, char_u *buf, int len) node = (queue_T *)alloc(sizeof(queue_T)); if (node == NULL) - return; /* out of memory */ + return FAIL; /* out of memory */ node->buffer = alloc(len + 1); if (node->buffer == NULL) { vim_free(node); - return; /* out of memory */ + return FAIL; /* out of memory */ } mch_memmove(node->buffer, buf, (size_t)len); node->buffer[len] = NUL; @@ -488,9 +637,11 @@ channel_save(int idx, char_u *buf, int len) if (debugfd != NULL) { fprintf(debugfd, "RECV on %d: ", idx); - fwrite(buf, len, 1, debugfd); + if (fwrite(buf, len, 1, debugfd) != 1) + return FAIL; fprintf(debugfd, "\n"); } + return OK; } /* @@ -593,7 +744,59 @@ channel_clear(int idx) #define MAXMSGSIZE 4096 /* - * Read from channel "idx". The data is put in the read queue. + * Check for reading from "fd" with "timeout" msec. + * Return FAIL when there is nothing to read. + */ + static int +channel_wait(int fd, int timeout) +{ +#ifdef HAVE_SELECT + struct timeval tval; + fd_set rfds; + int ret; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + tval.tv_sec = timeout / 1000; + tval.tv_usec = (timeout % 1000) * 1000; + for (;;) + { + ret = select(fd + 1, &rfds, NULL, NULL, &tval); +# ifdef EINTR + if (ret == -1 && errno == EINTR) + continue; +# endif + if (ret <= 0) + return FAIL; + break; + } +#else +# ifdef HAVE_POLL + struct pollfd fds; + + fds.fd = fd; + fds.events = POLLIN; + if (poll(&fds, 1, timeout) <= 0) + return FAIL; +# endif +#endif + return OK; +} + +/* + * Return a unique ID to be used in a message. + */ + int +channel_get_id() +{ + static int next_id = 1; + + return next_id++; +} + +/* + * Read from channel "idx" for as long as there is something to read. + * The data is put in the read queue. */ void channel_read(int idx) @@ -601,14 +804,6 @@ channel_read(int idx) static char_u *buf = NULL; int len = 0; int readlen = 0; -#ifdef HAVE_SELECT - struct timeval tval; - fd_set rfds; -#else -# ifdef HAVE_POLL - struct pollfd fds; -# endif -#endif channel_T *channel = &channels[idx]; if (channel->ch_fd < 0) @@ -637,21 +832,8 @@ channel_read(int idx) * MAXMSGSIZE long. */ for (;;) { -#ifdef HAVE_SELECT - FD_ZERO(&rfds); - FD_SET(channel->ch_fd, &rfds); - tval.tv_sec = 0; - tval.tv_usec = 0; - if (select(channel->ch_fd + 1, &rfds, NULL, NULL, &tval) <= 0) + if (channel_wait(channel->ch_fd, 0) == FAIL) break; -#else -# ifdef HAVE_POLL - fds.fd = channel->ch_fd; - fds.events = POLLIN; - if (poll(&fds, 1, 0) <= 0) - break; -# endif -#endif len = sock_read(channel->ch_fd, buf, MAXMSGSIZE); if (len <= 0) break; /* error or nothing more to read */ @@ -690,12 +872,44 @@ channel_read(int idx) } } + may_invoke_callback(idx); + #if defined(CH_HAS_GUI) && defined(FEAT_GUI_GTK) if (CH_HAS_GUI && gtk_main_level() > 0) gtk_main_quit(); #endif } +/* + * Read from channel "idx". Blocks until there is something to read or the + * timeout expires. + * Returns what was read in allocated memory. + * Returns NULL in case of error or timeout. + */ + char_u * +channel_read_block(int idx) +{ + if (channel_peek(idx) == NULL) + { + /* Wait for up to 2 seconds. + * TODO: use timeout set on the channel. */ + if (channel_wait(channels[idx].ch_fd, 2000) == FAIL) + { + channels[idx].ch_will_block = FALSE; + return NULL; + } + channel_read(idx); + } + + /* Concatenate everything into one buffer. + * TODO: avoid multiple allocations. */ + while (channel_collapse(idx) == OK) + ; + + channels[idx].ch_will_block = FALSE; + return channel_get(idx); +} + # if defined(FEAT_GUI_W32) || defined(PROTO) /* * Lookup the channel index from the socket. @@ -717,8 +931,9 @@ channel_socket2idx(sock_T fd) /* * Write "buf" (NUL terminated string) to channel "idx". * When "fun" is not NULL an error message might be given. + * Return FAIL or OK. */ - void + int channel_send(int idx, char_u *buf, char *fun) { channel_T *channel = &channels[idx]; @@ -732,8 +947,10 @@ channel_send(int idx, char_u *buf, char *fun) EMSG2("E630: %s(): write while not connected", fun); } channel->ch_error = TRUE; + return FAIL; } - else if (sock_write(channel->ch_fd, buf, len) != len) + + if (sock_write(channel->ch_fd, buf, len) != len) { if (!channel->ch_error && fun != NULL) { @@ -741,9 +958,11 @@ channel_send(int idx, char_u *buf, char *fun) EMSG2("E631: %s(): write failed", fun); } channel->ch_error = TRUE; + return FAIL; } - else - channel->ch_error = FALSE; + + channel->ch_error = FALSE; + return OK; } # if (defined(UNIX) && !defined(HAVE_SELECT)) || defined(PROTO) diff --git a/src/eval.c b/src/eval.c index 16e35cc01a..8bd4bb2b11 100644 --- a/src/eval.c +++ b/src/eval.c @@ -458,7 +458,6 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate); static int find_internal_func(char_u *name); static char_u *deref_func_name(char_u *name, int *lenp, int no_autoload); static int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, dict_T *selfdict); -static int call_func(char_u *funcname, int len, typval_T *rettv, int argcount, typval_T *argvars, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, dict_T *selfdict); static void emsg_funcname(char *ermsg, char_u *name); static int non_zero_arg(typval_T *argvars); @@ -516,6 +515,9 @@ static void f_copy(typval_T *argvars, typval_T *rettv); static void f_cos(typval_T *argvars, typval_T *rettv); static void f_cosh(typval_T *argvars, typval_T *rettv); #endif +#ifdef FEAT_CHANNEL +static void f_connect(typval_T *argvars, typval_T *rettv); +#endif static void f_count(typval_T *argvars, typval_T *rettv); static void f_cscope_connection(typval_T *argvars, typval_T *rettv); static void f_cursor(typval_T *argsvars, typval_T *rettv); @@ -524,6 +526,9 @@ static void f_delete(typval_T *argvars, typval_T *rettv); static void f_did_filetype(typval_T *argvars, typval_T *rettv); static void f_diff_filler(typval_T *argvars, typval_T *rettv); static void f_diff_hlID(typval_T *argvars, typval_T *rettv); +#ifdef FEAT_CHANNEL +static void f_disconnect(typval_T *argvars, typval_T *rettv); +#endif static void f_empty(typval_T *argvars, typval_T *rettv); static void f_escape(typval_T *argvars, typval_T *rettv); static void f_eval(typval_T *argvars, typval_T *rettv); @@ -698,6 +703,10 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv); static void f_searchpair(typval_T *argvars, typval_T *rettv); static void f_searchpairpos(typval_T *argvars, typval_T *rettv); static void f_searchpos(typval_T *argvars, typval_T *rettv); +#ifdef FEAT_CHANNEL +static void f_sendexpr(typval_T *argvars, typval_T *rettv); +static void f_sendraw(typval_T *argvars, typval_T *rettv); +#endif static void f_server2client(typval_T *argvars, typval_T *rettv); static void f_serverlist(typval_T *argvars, typval_T *rettv); static void f_setbufvar(typval_T *argvars, typval_T *rettv); @@ -8170,6 +8179,9 @@ static struct fst {"complete_check", 0, 0, f_complete_check}, #endif {"confirm", 1, 4, f_confirm}, +#ifdef FEAT_CHANNEL + {"connect", 2, 3, f_connect}, +#endif {"copy", 1, 1, f_copy}, #ifdef FEAT_FLOAT {"cos", 1, 1, f_cos}, @@ -8183,6 +8195,9 @@ static struct fst {"did_filetype", 0, 0, f_did_filetype}, {"diff_filler", 1, 1, f_diff_filler}, {"diff_hlID", 2, 2, f_diff_hlID}, +#ifdef FEAT_CHANNEL + {"disconnect", 1, 1, f_disconnect}, +#endif {"empty", 1, 1, f_empty}, {"escape", 2, 2, f_escape}, {"eval", 1, 1, f_eval}, @@ -8361,6 +8376,10 @@ static struct fst {"searchpair", 3, 7, f_searchpair}, {"searchpairpos", 3, 7, f_searchpairpos}, {"searchpos", 1, 4, f_searchpos}, +#ifdef FEAT_CHANNEL + {"sendexpr", 2, 3, f_sendexpr}, + {"sendraw", 2, 3, f_sendraw}, +#endif {"server2client", 2, 2, f_server2client}, {"serverlist", 0, 0, f_serverlist}, {"setbufvar", 3, 3, f_setbufvar}, @@ -8674,7 +8693,7 @@ get_func_tv(name, len, rettv, arg, firstline, lastline, doesrange, * Return FAIL when the function can't be called, OK otherwise. * Also returns OK when an error was encountered while executing the function. */ - static int + int call_func(funcname, len, rettv, argcount, argvars, firstline, lastline, doesrange, evaluate, selfdict) char_u *funcname; /* name of the function */ @@ -10293,6 +10312,83 @@ f_count(argvars, rettv) rettv->vval.v_number = n; } +#ifdef FEAT_CHANNEL +/* + * Get a callback from "arg". It can be a Funcref or a function name. + * When "arg" is zero return an empty string. + * Return NULL for an invalid argument. + */ + static char_u * +get_callback(typval_T *arg) +{ + if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) + return arg->vval.v_string; + if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) + return (char_u *)""; + EMSG(_("E999: Invalid callback argument")); + return NULL; +} + +/* + * "connect()" function + */ + static void +f_connect(argvars, rettv) + typval_T *argvars; + typval_T *rettv; +{ + char_u *address; + char_u *mode; + char_u *callback = NULL; + char_u buf1[NUMBUFLEN]; + char_u *p; + int port; + int json_mode = FALSE; + + address = get_tv_string(&argvars[0]); + mode = get_tv_string_buf(&argvars[1], buf1); + if (argvars[2].v_type != VAR_UNKNOWN) + { + callback = get_callback(&argvars[2]); + if (callback == NULL) + return; + } + + /* parse address */ + p = vim_strchr(address, ':'); + if (p == NULL) + { + EMSG2(_(e_invarg2), address); + return; + } + *p++ = NUL; + port = atoi((char *)p); + if (*address == NUL || port <= 0) + { + p[-1] = ':'; + EMSG2(_(e_invarg2), address); + return; + } + + /* parse mode */ + if (STRCMP(mode, "json") == 0) + json_mode = TRUE; + else if (STRCMP(mode, "raw") != 0) + { + EMSG2(_(e_invarg2), mode); + return; + } + + rettv->vval.v_number = channel_open((char *)address, port, NULL); + if (rettv->vval.v_number >= 0) + { + channel_set_json_mode(rettv->vval.v_number, json_mode); + if (callback != NULL && *callback != NUL) + channel_set_callback(rettv->vval.v_number, callback); + } +} +#endif + /* * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function * @@ -10545,6 +10641,46 @@ f_diff_hlID(argvars, rettv) #endif } +#ifdef FEAT_CHANNEL +/* + * Get the channel index from the handle argument. + * Returns -1 if the handle is invalid or the channel is closed. + */ + static int +get_channel_arg(typval_T *tv) +{ + int ch_idx; + + if (tv->v_type != VAR_NUMBER) + { + EMSG2(_(e_invarg2), get_tv_string(tv)); + return -1; + } + ch_idx = tv->vval.v_number; + + if (!channel_is_open(ch_idx)) + { + EMSGN(_("E999: not an open channel"), ch_idx); + return -1; + } + return ch_idx; +} + +/* + * "disconnect()" function + */ + static void +f_disconnect(argvars, rettv) + typval_T *argvars; + typval_T *rettv UNUSED; +{ + int ch_idx = get_channel_arg(&argvars[0]); + + if (ch_idx >= 0) + channel_close(ch_idx); +} +#endif + /* * "empty({expr})" function */ @@ -17405,6 +17541,109 @@ f_searchpos(argvars, rettv) list_append_number(rettv->vval.v_list, (varnumber_T)n); } +#ifdef FEAT_CHANNEL +/* + * common for "sendexpr()" and "sendraw()" + * Returns the channel index if the caller should read the response. + * Otherwise returns -1. + */ + static int +send_common(typval_T *argvars, char_u *text, char *fun) +{ + int ch_idx; + char_u *callback = NULL; + + ch_idx = get_channel_arg(&argvars[0]); + if (ch_idx < 0) + return -1; + + if (argvars[2].v_type != VAR_UNKNOWN) + { + callback = get_callback(&argvars[2]); + if (callback == NULL) + return -1; + } + /* Set the callback or clear it. An empty callback means no callback and + * not reading the response. */ + channel_set_req_callback(ch_idx, + callback != NULL && *callback == NUL ? NULL : callback); + if (callback == NULL) + channel_will_block(ch_idx); + + if (channel_send(ch_idx, text, fun) == OK && callback == NULL) + return ch_idx; + return -1; +} + +/* + * "sendexpr()" function + */ + static void +f_sendexpr(argvars, rettv) + typval_T *argvars; + typval_T *rettv; +{ + char_u *text; + char_u *resp; + typval_T nrtv; + typval_T listtv; + int ch_idx; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + nrtv.v_type = VAR_NUMBER; + nrtv.vval.v_number = channel_get_id(); + if (rettv_list_alloc(&listtv) == FAIL) + return; + if (list_append_tv(listtv.vval.v_list, &nrtv) == FAIL + || list_append_tv(listtv.vval.v_list, &argvars[1]) == FAIL) + { + list_unref(listtv.vval.v_list); + return; + } + + text = json_encode(&listtv); + list_unref(listtv.vval.v_list); + + ch_idx = send_common(argvars, text, "sendexpr"); + if (ch_idx >= 0) + { + /* TODO: read until the whole JSON message is received */ + /* TODO: only use the message with the right message ID */ + resp = channel_read_block(ch_idx); + if (resp != NULL) + { + channel_decode_json(resp, rettv); + vim_free(resp); + } + } +} + +/* + * "sendraw()" function + */ + static void +f_sendraw(argvars, rettv) + typval_T *argvars; + typval_T *rettv; +{ + char_u buf[NUMBUFLEN]; + char_u *text; + int ch_idx; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + text = get_tv_string_buf(&argvars[1], buf); + ch_idx = send_common(argvars, text, "sendraw"); + if (ch_idx >= 0) + rettv->vval.v_string = channel_read_block(ch_idx); +} +#endif + static void f_server2client(argvars, rettv) diff --git a/src/json.c b/src/json.c index d6507c3517..7256a8ceda 100644 --- a/src/json.c +++ b/src/json.c @@ -68,7 +68,12 @@ write_string(garray_T *gap, char_u *str) default: if (c >= 0x20) { +#ifdef FEAT_MBYTE numbuf[mb_char2bytes(c, numbuf)] = NUL; +#else + numbuf[0] = c; + numbuf[1] = NUL; +#endif ga_concat(gap, numbuf); } else diff --git a/src/proto/channel.pro b/src/proto/channel.pro index 1cdef5e58f..2d46a4963f 100644 --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -1,16 +1,23 @@ /* channel.c */ void channel_gui_register_all(void); int channel_open(char *hostname, int port_in, void (*close_cb)(void)); +void channel_set_json_mode(int idx, int json_mode); +void channel_set_callback(int idx, char_u *callback); +void channel_set_req_callback(int idx, char_u *callback); +void channel_will_block(int idx); +int channel_decode_json(char_u *msg, typval_T *tv); int channel_is_open(int idx); void channel_close(int idx); -void channel_save(int idx, char_u *buf, int len); +int channel_save(int idx, char_u *buf, int len); char_u *channel_peek(int idx); char_u *channel_get(int idx); int channel_collapse(int idx); void channel_clear(int idx); +int channel_get_id(void); void channel_read(int idx); +char_u *channel_read_block(int idx); int channel_socket2idx(sock_T fd); -void channel_send(int idx, char_u *buf, char *fun); +int channel_send(int idx, char_u *buf, char *fun); int channel_poll_setup(int nfd_in, void *fds_in); int channel_poll_check(int ret_in, void *fds_in); int channel_select_setup(int maxfd_in, void *rfds_in); diff --git a/src/proto/eval.pro b/src/proto/eval.pro index f6ad4b49e8..ea2096a5d5 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -82,6 +82,7 @@ long get_dict_number(dict_T *d, char_u *key); int string2float(char_u *text, float_T *value); char_u *get_function_name(expand_T *xp, int idx); char_u *get_expr_name(expand_T *xp, int idx); +int call_func(char_u *funcname, int len, typval_T *rettv, int argcount, typval_T *argvars, linenr_T firstline, linenr_T lastline, int *doesrange, int evaluate, dict_T *selfdict); int func_call(char_u *name, typval_T *args, dict_T *selfdict, typval_T *rettv); void dict_extend(dict_T *d1, dict_T *d2, char_u *action); void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv); diff --git a/src/version.c b/src/version.c index 022b0be5a2..1cfe94ba68 100644 --- a/src/version.c +++ b/src/version.c @@ -761,6 +761,14 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1194, +/**/ + 1193, +/**/ + 1192, +/**/ + 1191, /**/ 1190, /**/