diff options
109 files changed, 4760 insertions, 3198 deletions
diff --git a/.ci/api-python.sh b/.ci/api-python.sh index 556361cb96..acdbcc2666 100644 --- a/.ci/api-python.sh +++ b/.ci/api-python.sh @@ -1,6 +1,6 @@ . "$CI_SCRIPTS/common.sh" -set_environment /opt/neovim-deps +set_environment /opt/neovim-deps/64 sudo apt-get install expect valgrind @@ -23,7 +23,7 @@ fi valgrind_check "$tmpdir" -export NVIM_SPAWN_ARGV="[\"valgrind\", \"-q\", \"--track-origins=yes\", \"--leak-check=yes\", \"--suppressions=$suppressions\", \"--log-file=$tmpdir/valgrind-%p.log\", \"../build/bin/nvim\", \"-u\", \"NONE\", \"--embedded-mode\"]" +export NVIM_SPAWN_ARGV="[\"valgrind\", \"-q\", \"--track-origins=yes\", \"--leak-check=yes\", \"--suppressions=$suppressions\", \"--log-file=$tmpdir/valgrind-%p.log\", \"../build/bin/nvim\", \"-u\", \"NONE\", \"--embed\"]" if ! nosetests --verbosity=2 --nologcapture; then valgrind_check "$tmpdir" exit 1 diff --git a/.ci/clang-asan.sh b/.ci/clang-asan.sh index 3217f754d6..4b5ff26d0d 100644 --- a/.ci/clang-asan.sh +++ b/.ci/clang-asan.sh @@ -2,7 +2,7 @@ install_vroom -set_environment /opt/neovim-deps +set_environment /opt/neovim-deps/64 sudo pip install cpp-coveralls diff --git a/.ci/common.sh b/.ci/common.sh index a31b3063e9..ba00011222 100644 --- a/.ci/common.sh +++ b/.ci/common.sh @@ -48,7 +48,7 @@ asan_check() { } set_environment() { - local prefix="$1" + local prefix="$1/usr" eval $($prefix/bin/luarocks path) export PATH="$prefix/bin:$PATH" export PKG_CONFIG_PATH="$prefix/lib/pkgconfig" @@ -67,7 +67,7 @@ install_prebuilt_deps() { install_vroom() { ( - sudo pip install neovim + sudo pip install git+https://github.com/neovim/python-client.git git clone git://github.com/google/vroom cd vroom python setup.py build diff --git a/.ci/gcc-ia32.sh b/.ci/gcc-ia32.sh index 05b539021e..97e4190ccc 100644 --- a/.ci/gcc-ia32.sh +++ b/.ci/gcc-ia32.sh @@ -16,7 +16,7 @@ sudo apt-get install gcc-multilib g++-multilib # correctly. sudo apt-get install libncurses5-dev:i386 -CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DBUSTED_OUTPUT_TYPE=color_terminal \ +CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON \ -DCMAKE_SYSTEM_PROCESSOR=i386 \ -DCMAKE_SYSTEM_LIBRARY_PATH=/lib32:/usr/lib32:/usr/local/lib32 \ -DFIND_LIBRARY_USE_LIB64_PATHS=OFF \ diff --git a/.ci/gcc-unittest.sh b/.ci/gcc-unittest.sh index a4e4a6f35f..95925ddbc9 100644 --- a/.ci/gcc-unittest.sh +++ b/.ci/gcc-unittest.sh @@ -1,11 +1,11 @@ . "$CI_SCRIPTS/common.sh" -set_environment /opt/neovim-deps +set_environment /opt/neovim-deps/64 sudo pip install cpp-coveralls export CC=gcc export SKIP_EXEC=1 -$MAKE_CMD CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DBUSTED_OUTPUT_TYPE=color_terminal -DUSE_GCOV=ON" unittest +$MAKE_CMD CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DUSE_GCOV=ON" unittest coveralls --encoding iso-8859-1 || echo 'coveralls upload failed.' diff --git a/CMakeLists.txt b/CMakeLists.txt index 16887376b2..cb680da902 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,7 +94,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # Find Lua interpreter include(LuaHelpers) -set(LUA_DEPENDENCIES lpeg cmsgpack bit) +set(LUA_DEPENDENCIES lpeg MessagePack bit) if(NOT LUA_PRG) foreach(CURRENT_LUA_PRG luajit lua) # If LUA_PRG is set find_program() will not search @@ -147,7 +147,7 @@ if(CMAKE_GENERATOR MATCHES "Makefiles") endif() if(NOT BUSTED_OUTPUT_TYPE) - set(BUSTED_OUTPUT_TYPE "utf_terminal") + set(BUSTED_OUTPUT_TYPE "utfTerminal") endif() if(BUSTED_PRG) @@ -34,7 +34,7 @@ For lots more details, see ### What's being worked on now -- Port all IO to libuv +- Port all IO to [libuv](https://github.com/joyent/libuv/blob/master/README.md) - Lots of refactoring - A VimL => Lua transpiler diff --git a/cmake/FindMsgpack.cmake b/cmake/FindMsgpack.cmake index b2c89b13d1..6b3c8bb977 100644 --- a/cmake/FindMsgpack.cmake +++ b/cmake/FindMsgpack.cmake @@ -24,13 +24,13 @@ find_path(MSGPACK_INCLUDE_DIR msgpack.h HINTS ${PC_MSGPACK_INCLUDEDIR} ${PC_MSGPACK_INCLUDE_DIRS} ${LIMIT_SEARCH}) -# If we're asked to use static linkage, add libmsgpackc.a as a preferred library name. +# If we're asked to use static linkage, add libmsgpack.a as a preferred library name. if(MSGPACK_USE_STATIC) list(APPEND MSGPACK_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}msgpackc${CMAKE_STATIC_LIBRARY_SUFFIX}") + "${CMAKE_STATIC_LIBRARY_PREFIX}msgpack${CMAKE_STATIC_LIBRARY_SUFFIX}") endif() -list(APPEND MSGPACK_NAMES msgpackc) +list(APPEND MSGPACK_NAMES msgpack) find_library(MSGPACK_LIBRARY NAMES ${MSGPACK_NAMES} HINTS ${PC_MSGPACK_LIBDIR} ${PC_MSGPACK_LIBRARY_DIRS} diff --git a/cmake/RunUnittests.cmake b/cmake/RunUnittests.cmake index 6fce228864..f51fc8a90a 100644 --- a/cmake/RunUnittests.cmake +++ b/cmake/RunUnittests.cmake @@ -5,7 +5,7 @@ if(DEFINED ENV{TEST_FILE}) endif() execute_process( - COMMAND ${BUSTED_PRG} -o ${BUSTED_OUTPUT_TYPE} --lpath=${BUILD_DIR}/?.lua ${TEST_DIR} + COMMAND ${BUSTED_PRG} -v -o ${BUSTED_OUTPUT_TYPE} --lpath=${BUILD_DIR}/?.lua ${TEST_DIR} WORKING_DIRECTORY ${WORKING_DIR} RESULT_VARIABLE res) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 2a8becebff..734202cd6c 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -303,6 +303,7 @@ Name triggered by ~ |InsertLeave| when leaving Insert mode |InsertCharPre| when a character was typed in Insert mode, before inserting it +|JobActivity| when something interesting happens with a job |TextChanged| after a change was made to the text in Normal mode |TextChangedI| after a change was made to the text in Insert mode @@ -712,6 +713,10 @@ InsertEnter Just before starting Insert mode. Also for *InsertLeave* InsertLeave When leaving Insert mode. Also when using CTRL-O |i_CTRL-O|. But not for |i_CTRL-C|. + {Nvim} *JobActivity* +JobActivity When something interesting happens with a job + spawned by |jobstart()|. See |job-control| for + details. *MenuPopup* MenuPopup Just before showing the popup menu (under the right mouse button). Useful for adjusting the @@ -1383,4 +1388,4 @@ This will write the file without triggering the autocommands defined by the gzip plugin. - vim:tw=78:ts=8:ft=help:norl: + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index cdad6a9d7e..ec2086e3eb 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1865,6 +1865,10 @@ invert( {expr}) Number bitwise invert isdirectory( {directory}) Number TRUE if {directory} is a directory islocked( {expr}) Number TRUE if {expr} is locked items( {dict}) List key-value pairs in {dict} +job_close({job}) Closes a job with id {job} +job_send({job}, {data}) Writes {data} to {job}'s stdin +job_spawn({name}, {prog}[, {argv}]) + Spawns {prog} as a job associated with {name} join( {list} [, {sep}]) String join {list} items into one String keys( {dict}) List keys in {dict} len( {expr}) Number the length of {expr} @@ -1934,6 +1938,12 @@ repeat( {expr}, {count}) String repeat {expr} {count} times resolve( {filename}) String get filename a shortcut points to reverse( {list}) List reverse {list} in-place round( {expr}) Float round off {expr} +rpcnotify({channel}, {event}[, {args}...]) + Sends a |msgpack-rpc| notification to {channel} +rpcrequest({channel}, {method}[, {args}...]) + Sends a |msgpack-rpc| request to {channel} +rpcstart({prog}[, {argv}]) Spawns {prog} and opens a |msgpack-rpc| channel +rpcstop({channel}) Closes a |msgpack-rpc| {channel} screenattr( {row}, {col}) Number attribute at screen position screenchar( {row}, {col}) Number character at screen position screencol() Number current cursor column @@ -4002,6 +4012,27 @@ items({dict}) *items()* entry and the value of this entry. The |List| is in arbitrary order. +jobsend({job}, {data}) {Nvim} *jobsend()* + Send data to {job} by writing it to the stdin of the process. + See |job-control| for more information. + +jobstart({name}, {prog}[, {argv}]) {Nvim} *jobstart()* + Spawns {prog} as a job and associate it with the {name} string, + which will be used to match the "filename pattern" in + |JobActivity| events. It returns: + - The job id on success, which is used by |jobsend()| and + |jobstop()| + - 0 when the job table is full or on invalid arguments + - -1 when {prog} is not executable + See |job-control| for more information. + +jobstop({job}) {Nvim} *jobstop()* + Stop a job created with |jobstart| by sending a `SIGTERM` + to the corresponding process. If the process doesn't exit + cleanly soon, a `SIGKILL` will be sent. When the job is + finally closed, a |JobActivity| event will trigger with + `v:job_data[0]` set to `exited`. See |job-control| for more + information. join({list} [, {sep}]) *join()* Join the items in {list} together into one String. @@ -5043,6 +5074,34 @@ round({expr}) *round()* < -5.0 {only available when compiled with the |+float| feature} +rpcnotify({channel}, {event}[, {args}...]) {Nvim} *rpcnotify()* + Sends {event} to {channel} via |msgpack-rpc| and returns + immediately. If {channel} is 0, the event is broadcast to all + channels. Example: > + :au VimLeave call rpcnotify(0, "leaving") + +rpcrequest({channel}, {method}[, {args}...]) {Nvim} *rpcrequest()* + Sends a request to {channel} to invoke {method} via + |msgpack-rpc| and blocks until a response is received. + Example: > + :let result = rpcrequest(rpc_chan, "func", 1, 2, 3) + +rpcstart({prog}[, {argv}]) {Nvim} *rpcstart()* + Spawns {prog} as a job(optionally passing the {argv} list), + and opens a |msgpack-rpc| channel with the spawned process + stdin/stdout. It returns: + - The channel id on success, which is used by |rpcrequest()|, + |rpcnotify()| and |rpcstop()| + - 0 on failure. + Example: > + :let rpc_chan = rpcstart('prog', ['arg1', 'arg2']) + +rpcstop({channel}) {Nvim} *rpcstop()* + Closes a |msgpack-rpc| channel, possibly created via + |rpcspawn()| (Though it will also close channels created by + connections to |NVIM_LISTEN_ADDRESS|). It accepts the rpc + channel id as only argument. + screenattr(row, col) *screenattr()* Like screenchar(), but return the attribute. This is a rather arbitrary number that can only be used to compare to the @@ -8874,4 +8933,4 @@ This is not allowed when the textlock is active: - etc. - vim:tw=78:ts=8:ft=help:norl: + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt new file mode 100644 index 0000000000..49ee3889bc --- /dev/null +++ b/runtime/doc/job_control.txt @@ -0,0 +1,101 @@ +*job_control.txt* For Nvim. {Nvim} + + + NVIM REFERENCE MANUAL by Thiago de Arruda + + +Nvim's facilities for job control *job-control* + +1. Introduction |job-control-intro| +2. Usage |job-control-usage| + +============================================================================== +1. Introduction *job-control-intro* + +Job control is a simple way to perform multitasking in vimscript. Wikipedia +contains a more generic/detailed description: + +"Job control in computing refers to the control of multiple tasks or Jobs on a +computer system, ensuring that they each have access to adequate resources to +perform correctly, that competition for limited resources does not cause a +deadlock where two or more jobs are unable to complete, resolving such +situations where they do occur, and terminating jobs that, for any reason, are +not performing as expected." + +In a few words: It allows a vimscript programmer to concurrently spawn and +control multiple processes without blocking the current Nvim instance. + +Nvim's job control was designed to be simple and familiar to vimscript +programmers, instead of being very powerful but complex. Unlike Vim's +facilities for calling with external commands, job control does not depend +on installed shells, calling OS functions for process management directly. + +Internally, Nvim job control is powered by libuv, which has a nice +cross-platform API for managing processes. See https://github.com/joyent/libuv +for details + +============================================================================== +2. Usage *job-control-usage* + +Job control is achieved by calling a combination of the |jobstart()|, +|jobsend()| and |jobstop()| functions, and by listening to the |JobActivity| +event. The best way to understand is with a complete example: +> + set nocp + let job1 = jobstart('shell1', 'bash') + let job2 = jobstart('shell2', 'bash', ['-c', 'for ((i = 0; i < 10; i++)); do echo -n hello $i!; sleep 2; done']) + + function JobHandler() + if v:job_data[1] == 'stdout' + let str = 'shell '. v:job_data[0].' stdout: '.v:job_data[2] + elseif v:job_data[1] == 'stderr' + let str = 'shell '.v:job_data[0].' stderr: '.v:job_data[2] + else + let str = 'shell '.v:job_data[0].' exited' + endif + + call append(line('$'), str) + endfunction + + au JobActivity shell* call JobHandler() +< +To test the above, copy it to the ~/jobcontrol.vim file and start with a clean +nvim instance: + > + nvim -u NONE -S ~/jobcontrol.vim +< +Here's what is happening: + +- Two bash instances are spawned by |jobstart()| and their stdin/stdout/stderr + are connected to nvim. +- The first shell is idle, waiting to read commands from it's stdin +- The second shell is passed the -c option to execute a command and exit. In + our case, the command is a for loop that will print numbers and exit after + a while. +- The JobHandler function is called by the JobActivity autocommand(notice how + the shell* pattern matches the `shell1` and `shell2` names passed to + |jobstart()|), and it takes care of displaying stdout/stderr received from + the shells. +- The v:job_data is an array set by the JobActivity event. It has the + following elements: + 0: The job id + 1: The kind of activity: one of "stdout", "stderr" or "exit" + 2: When "activity" is "stdout" or "stderr", this will contain the data read + from stdout or stderr + +To send data to the job's stdin, one can use the |jobsend()| function, like +this: +> + :call jobsend(job1, 'ls\n') + :call jobsend(job1, 'invalid-command\n') + :call jobsend(job1, 'exit\n') +< +A job may be killed at any time with the |jobstop()| function: +> + :call jobstop(job1) +< +When |jobstop()| is called, it will send `SIGTERM` to the job. If a job +doesn't exit after a while, `SIGKILL` will be sent. + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt new file mode 100644 index 0000000000..eb15075d85 --- /dev/null +++ b/runtime/doc/msgpack_rpc.txt @@ -0,0 +1,245 @@ +*msgpack_rpc.txt* For Nvim. {Nvim} + + + NVIM REFERENCE MANUAL by Thiago de Arruda + + +The Msgpack-RPC Interface to Nvim *msgpack-rpc* + +1. Introduction |msgpack-rpc-intro| +2. API |msgpack-rpc-api| +3. Connecting |msgpack-rpc-connecting| +4. Clients |msgpack-rpc-clients| +5. Types |msgpack-rpc-types| +6. Wrapping methods |msgpack-rpc-wrap-methods| +7. Vimscript functions |msgpack-rpc-vim-functions| + +============================================================================== +1. Introduction *msgpack-rpc-intro* + +The primary means of controlling a running Nvim instance is through +MessagePack-RPC, a messaging protocol that uses the MessagePack serialization +format: https://github.com/msgpack/msgpack/blob/7498cf3/spec.md. +From now on, we'll be referring to the protocol as msgpack-rpc. + +At this point, only plugins use msgpack-rpc, but eventually even user +interaction will be achieved through the protocol, since user interfaces will +be separate programs that control a headless Nvim instance. + +This is what can be achieved by connecting to the msgpack-rpc interface: + +- Call any Nvim API function +- Listen for Nvim events +- Receive remote calls from Nvim + +Nvim's msgpack-rpc interface can be seen as a more powerful version of Vim's +`clientserver` feature. + +============================================================================== +2. API *msgpack-rpc-api* + +Nvim C API is automatically exposed to the msgpack-rpc interface by the +build system, which parses headers at src/nvim/api from the project root. A +dispatch function is generated, and it will match msgpack-rpc method names +with non-static API functions, converting/validating arguments and return +values back to msgpack. + +Client libraries will normally provide wrappers that hide msgpack-rpc details +from programmers, which can be automatically generated by reading bundled api +metadata from a compiled nvim instance. + +There are two ways to obtain API metadata: + +- By connecting to a running nvim instance and calling `vim_get_api_metadata` + via msgpack-rpc. This is the preferred way for clients written in + dynamically-typed languages, which can define functions at runtime. +- Through the `--api-info` command-line option, which makes nvim to dump a + msgpack blob containing the metadata to stdout and exit. This is preferred + when writing clients for statically-typed languages, which require a + separate compilation step. + +Here's a simple way to get human-readable description of the API(requires +python and the pyyaml/msgpack-python pip packages): +> + nvim --api-info | python -c 'import msgpack, sys, yaml; print yaml.dump(msgpack.unpackb(sys.stdin.read()))' > api.yaml + +============================================================================== +3. Connecting *msgpack-rpc-connecting* + + +There are four ways to open msgpack-rpc streams to nvim: + +1. Through nvim's stdin/stdout when started with the `--embed` option. This is + how other programs can embed nvim. + +2. Through stdin/stdout of a program spawned by the |rpcstart()| function. + +3. Through the socket automatically created with every instance. To find out + the socket location(which is random by default) from a running nvim + instance, one can inspect the *$NVIM_LISTEN_ADDRESS* environment variable + like this: +> + :echo $NVIM_LISTEN_ADDRESS +< +4. Through a tcp/ip socket. To make nvim listen on a tcp/ip socket, you need + to set the NVIM_LISTEN_ADDRESS environment variable before starting, like + this: +> + NVIM_LISTEN_ADDRESS=127.0.0.1:6666 nvim +< +Connecting to the socket is the easiest way a programmer can test the API, +which can be done through any msgpack-rpc client library or a fully-featured +Nvim client(which we'll see below). Here's a ruby script that will print the +string 'hello world!' on the current nvim instance: +> + #!/usr/bin/env ruby + # Requires msgpack-rpc: gem install msgpack-rpc + # + # To run this script, execute it from a running nvim instance(notice the + # trailing '&' which is required since nvim won't process events while + # running a blocking command): + # + # :!./hello.rb & + # + # Or from another shell by setting NVIM_LISTEN_ADDRESS: + # $ NVIM_LISTEN_ADDRESS=[address] ./hello.rb + + require 'msgpack/rpc' + require 'msgpack/rpc/transport/unix' + + vim = MessagePack::RPC::Client.new(MessagePack::RPC::UNIXTransport.new, ENV['NVIM_LISTEN_ADDRESS']) + result = vim.call(:vim_command, 'echo "hello world!"') +< +A better way is to use the python REPL with the `neovim` package, where API +functions can be called interactively: +> + >>> import neovim; vim = neovim.connect('[address]') + >>> vim.command('echo "hello world!"') +< +============================================================================== +4. Implementing new clients *msgpack-rpc-clients* + +Nvim is still alpha and there's no in-depth documentation explaining how to +properly implement a client library. The python client(neovim pip package) +will be always up-to-date with the latest API changes, so it's source code is +the best documentation currently available. There are some guidelines however: + +- Separate the transport layer from the rest of the library(See + |msgpack-rpc-connecting| for details of how a client can connect to nvim). +- Use a msgpack library that implements the spec version 5, Nvim uses the + BIN/EXT types. +- Read api metadata in order to create client-side wrappers for all + msgpack-rpc methods. +- Use a single-threaded event loop library/pattern. +- Use a fiber/coroutine library for the language you are implementing a client + for. These greatly simplify concurrency and allow the library to expose a + blocking API on top of a non-blocking event loop without the complexity + that comes with preemptive multitasking. +- Don't assume anything about the order that responses to msgpack-rpc requests + will arrive. +- Clients should expect to receive msgpack-rpc requests, which need to be + handled immediately since Nvim is blocked while waiting for the client + response. +- Clients should expect to receive msgpack-rpc notifications, but these don't + need to be handled immediately because they won't block Nvim(Though you + probably want to handle them immediately anyway). + + +Most of the complexity could be handled by a msgpack-rpc library that supports +server->client requests and notifications, but it's not clear if this is part +of the msgpack-rpc spec. At least the ruby msgpack-rpc library does not seem +to support it: +https://github.com/msgpack-rpc/msgpack-rpc-ruby/blob/master/lib/msgpack/rpc/transport/tcp.rb#L150-L158 + +============================================================================== +5. Types *msgpack-rpc-types* + +Nvim's C API uses custom types for all functions(some are just typedefs +around C99 standard types). The types can be split into two groups: + +- Basic types that map natively to msgpack(and probably have a default + representation in msgpack-supported programming languages) +- Special Nvim types that map to msgpack EXT with custom type codes. + +Basic type mapping: + +Nil -> msgpack nil +Boolean -> msgpack boolean +Integer (signed 64-bit integer) -> msgpack integer +Float (IEEE 754 double precision) -> msgpack float +String -> msgpack binary +Array -> msgpack array +Dictionary -> msgpack map + +Special Nvim types that use msgpack EXT: + +Buffer -> enum value kObjectTypeBuffer +Window -> enum value kObjectTypeWindow +Tabpage -> enum value kObjectTypeTabpage + +The most reliable way of determining the type codes for the special nvim types +is at runtime by inspecting the `types` key of metadata dictionary returned by +`vim_get_api_metadata` method. Here's an example json representation of the +`types` object: +> + "types": { + "Buffer": { + "id": 0 + }, + "Window": { + "id": 1 + }, + "Tabpage": { + "id": 2 + } + } +< +Even for statically compiled clients, it's a good practice to avoid hardcoding +the type codes, because a client may build for a Nvim version and connect to +another that may have different type codes. + +============================================================================== +6. Wrapping methods *msgpack-rpc-wrap-methods* + +As mentioned before, clients should provide an API that hides msgpack-rpc +details from programmers, and the API metadata object contains information +that makes this task easier: + +- The "functions" key contains a list of metadata objects for individual + functions. +- Each function metadata object has type information about the return value + and parameters. These can be used for generating strongly-typed APIs in + static languages. +- Container types may be decorated with type/size constraints, eg: + ArrayOf(Buffer) or ArrayOf(Integer, 2). This can be useful to generate even + more strongly-typed APIs. +- Methods that operate instances of Nvim's types are prefixed with the type + name in lower case. Eg: `buffer_get_line` represents the `get_line` method + of a Buffer instance. +- Global methods are prefixed with `vim`. Eg: `vim_list_buffers` + +So, for a object-oriented language, a client library would have the classes +that represent Nvim's types, and the methods of each class could be defined +by inspecting the method name prefix. There could also be a singleton Vim +class with methods mapped to functions prefixed with `vim_` + +============================================================================== +7. Vimscript functions *msgpack-rpc-vim-functions* + +Four functions related to msgpack-rpc are available to vimscript: + +- |rpcstart()|: Similarly to |jobstart()|, this will spawn a co-process with + it's standard handles connected to Nvim, the difference is that it's not + possible to process raw data to/from the process stdin/stdout/stderr(Since + the job's stdin/stdout combo are used as a msgpack channel that is + processed directly by Nvim C code). +- |rpcstop()|: Same as |jobstop()|, but operates on handles returned by + |rpcstart().| +- |rpcrequest()|: Sends a msgpack-rpc request to the process. +- |rpcnotify()|: Sends a msgpack-rpc notification to the process. + +The last two functions may also be used with channels created from +connections to |NVIM_LISTEN_ADDRESS|. + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/nvim_clipboard.txt b/runtime/doc/nvim_clipboard.txt new file mode 100644 index 0000000000..3dd5fb5fcb --- /dev/null +++ b/runtime/doc/nvim_clipboard.txt @@ -0,0 +1,47 @@ +*nvim_clipboard.txt* For Nvim. {Nvim} + + + NVIM REFERENCE MANUAL by Thiago de Arruda + + +Clipboard integration for Nvim *nvim-clipboard* + +By default, Nvim has no connection to the system clipboard. Eventually that +will be implemented by UI programs, which connect to Nvim via |msgpack-rpc|. + +Even though externalized UIs are not available yet, there's a workaround that +enables clipboard usage through the python interface(which also uses +|msgpack-rpc| and consequently can implement the clipboard methods required +by Nvim): + +- Make sure you follow the setup instructions in |nvim-python-quickstart|. +- Install the `xerox` python module: + > + $ pip install xerox +< +- Create a ~/.vim/pythonx/nvim_clipboard.py file with the following contents: + > + import xerox + + class NvimClipboard(object): + def __init__(self, vim): + self.provides = ['clipboard'] + + def clipboard_get(self): + return xerox.paste().split('\n') + + def clipboard_set(self, lines): + xerox.copy(u'\n'.join([line.decode('utf-8') for line in lines])) +< +This should enable the '+' and '*' registers. As an optional step, set the +'unnamedclip' option to transparently access clipboard using the unnamed +register. If you use the same |vimrc| for both Vim and Nvim, make sure you +only set the option when `has('nvim')` is true: +> + if has('nvim') + set unnamedclip + endif +< + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/nvim_intro.txt b/runtime/doc/nvim_intro.txt new file mode 100644 index 0000000000..d3aa459ba2 --- /dev/null +++ b/runtime/doc/nvim_intro.txt @@ -0,0 +1,22 @@ +*nvim_intro.txt* For Nvim. {Nvim} + + + NVIM REFERENCE MANUAL by Thiago de Arruda + + +Introduction to Nvim *nvim-intro* + +This is an introduction to Vim users that are just getting started with Nvim. +It is not meant for Vim beginners. For a basic introduction to Vim, +see |help.txt|. + +For now, it is just an index with the most relevant topics/features that +differentiate Nvim from Vim: + +1. Msgpack-RPC |msgpack-rpc| +2. Job control |job-control| +3. Python plugins |nvim-python| +4. Clipboard integration |nvim-clipboard| + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/nvim_python.txt b/runtime/doc/nvim_python.txt new file mode 100644 index 0000000000..b618562900 --- /dev/null +++ b/runtime/doc/nvim_python.txt @@ -0,0 +1,40 @@ +*nvim_python.txt* For Nvim. {Nvim} + + + NVIM REFERENCE MANUAL by Thiago de Arruda + + +Python plugins and scripting in Nvim *nvim-python* + +1. Introduction |nvim-python-intro| +2. Quickstart |nvim-python-quickstart| + +============================================================================== +1. Introduction *nvim-python-intro* + +Through an external python interpreter connected via |msgpack-rpc|, Nvim +offers some support for the classic |python-vim| interface. For now only the +old Vim 7.3 API is supported. + +============================================================================== +2. Quickstart *nvim-python-quickstart* + +If you just want to start using python plugins with Nvim quickly, here's a +simple step-by-step: + +- Make sure python 2.6 or 2.7 is available on your `$PATH` +- Install the `neovim` python package: + > + $ pip install neovim +< +- Add the following snippet to your `vimrc`, before any python plugins are + loaded: +> + if has('nvim') + runtime! plugin/python_setup.vim + endif +< +Most python plugins created for Vim 7.3 should work after these steps. + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index d38fb84f10..293d113c1f 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -7647,6 +7647,13 @@ A jump table for the options with a short description can be found at |Q_op|. Note that this causes the whole buffer to be stored in memory. Set this option to a lower value if you run out of memory. + {Nvim} *'unnamedclip'* *ucp'* +'unnamedclip' 'ucp' boolean (default off) + global + Use the unnamed register to access the clipboard(when available). + This option has the same effect of setting 'clipboard' to + 'unnamed/unnamedplus' in Vim. + *'updatecount'* *'uc'* 'updatecount' 'uc' number (default: 200) global @@ -8311,4 +8318,4 @@ A jump table for the options with a short description can be found at |Q_op|. screen. When non-zero, characters are sent to the terminal one by one. For MS-DOS pcterm this does not work. For debugging purposes. - vim:tw=78:ts=8:ft=help:norl: + vim:tw=78:ts=8:ft=help:noet:norl: diff --git a/runtime/plugin/python_setup.vim b/runtime/plugin/python_setup.vim new file mode 100644 index 0000000000..db8c6e3251 --- /dev/null +++ b/runtime/plugin/python_setup.vim @@ -0,0 +1,46 @@ +" Nvim plugin for loading python extensions via an external interpreter +if exists("did_python_setup") || &cp + finish +endif +let did_python_setup = 1 + + +let s:get_version = + \ ' -c "import sys; sys.stdout.write(str(sys.version_info[0]) + '. + \ '\".\" + str(sys.version_info[1]))"' + +let s:supported = ['2.6', '2.7'] + +" To load the python host a python 2 executable must be available +if exists('python_interpreter') + \ && executable(g:python_interpreter) + \ && index(s:supported, system(g:python_interpreter.s:get_version)) >= 0 + let s:python_interpreter = g:python_interpreter +elseif executable('python') + \ && index(s:supported, system('python'.s:get_version)) >= 0 + let s:python_interpreter = 'python' +elseif executable('python2') + \ && index(s:supported, system('python2'.s:get_version)) >= 0 + " In some distros, python3 is the default python + let s:python_interpreter = 'python2' +else + finish +endif + +" Execute python, import neovim and print a string. If import_result matches +" the printed string, we can probably start the host +let s:import_result = system(s:python_interpreter . + \ ' -c "import neovim, sys; sys.stdout.write(\"ok\")"') +if s:import_result != 'ok' + finish +endif + +let s:pyhost_id = rpcstart(s:python_interpreter, + \ ['-c', 'import neovim; neovim.start_host()']) +" Evaluate an expression in the script host as an additional sanity check, and +" to block until all providers have been registered(or else some plugins loaded +" by the user's vimrc would not get has('python') == 1 +if rpcrequest(s:pyhost_id, 'python_eval', '"o"+"k"') != 'ok' || !has('python') + " Something went wrong + rpcstop(s:pyhost_id) +endif diff --git a/runtime/syntax/help.vim b/runtime/syntax/help.vim index 3368b1ddc5..c55d32ef7c 100644 --- a/runtime/syntax/help.vim +++ b/runtime/syntax/help.vim @@ -42,6 +42,8 @@ syn match helpNormal "|||" syn match helpNormal ":|vim:|" " for :help modeline syn match helpVim "\<Vim version [0-9][0-9.a-z]*" syn match helpVim "VIM REFERENCE.*" +syn match helpVim "\<Nvim\." +syn match helpVim "NVIM REFERENCE.*" syn match helpOption "'[a-z]\{2,\}'" syn match helpOption "'t_..'" syn match helpCommand "`[^` \t]\+`"hs=s+1,he=e-1 contains=helpBacktick diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 8d98f7d7d8..55b04bc2d8 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -33,7 +33,7 @@ syn keyword vimOption contained akm anti arshape awa backupskip bex bioskey brow syn keyword vimOption contained al antialias autochdir background balloondelay bexpr bk bs casemap cf cink cmdheight colorcolumn completefunc copyindent cryptmethod cscopeverbose csverb debug dict dir eb enc errorbells expandtab fdl fenc fileencodings fkmap foldenable foldminlines formatprg gdefault gp guifontset helpfile hidden hl ignorecase imcmdline imsf indentexpr is isp keywordprg lazyredraw lispwords ls makeef maxmapdepth mfd mmd modified mousemodel msm numberwidth operatorfunc pastetoggle pexpr pmbfn printexpr pt re restorescreen rnu rulerformat scr sect sft shellredir shiftwidth showmatch siso smc spc spl ss statusline suffixesadd sws tabline tags tbs textmode timeoutlen to tsl ttyfast uc undoreload vdir viewoptions warn wfh wig wildmode winfixwidth wiw wrap writedelay syn keyword vimOption contained aleph ar autoindent backspace ballooneval bg bkc bsdir cb cfu cinkeys cmdwinheight columns completeopt cot cscopepathcomp cspc cuc deco dictionary directory ed encoding errorfile exrc fdls fencs fileformat flp foldexpr foldnestmax fp gfm grepformat guifontwide helpheight highlight hlg im imd imstatusfunc indentkeys isf isprint km lbr list lsp makeprg maxmem mh mmp more mouses mzq nuw opfunc patchexpr pfn popt printfont pumheight readonly revins ro runtimepath scroll sections sh shellslash shm showmode sj smd spell splitbelow ssl stl sw sxe tabpagemax tagstack tenc textwidth title toolbar tsr ttym udf updatecount ve viminfo wb wfw wildchar wildoptions winheight wm wrapmargin ws syn keyword vimOption contained allowrevins arab autoread backup balloonexpr bh bl bsk cc ch cino cmp com concealcursor cp cscopeprg csprg cul def diff display edcompatible endofline errorformat fcl fdm fex fileformats fml foldignore foldopen fs gfn grepprg guiheadroom helplang history hls imactivatefunc imdisable inc indk isfname joinspaces kmp lcs listchars lw mat maxmempattern mis mmt mouse mouseshape mzquantum odev osfiletype patchmode ph preserveindent printheader pvh redrawtime ri rs sb scrollbind secure shcf shelltemp shortmess showtabline slm sn spellcapcheck splitright ssop stmp swapfile sxq tabstop tal term tf titlelen toolbariconsize ttimeout ttymouse udir updatetime verbose virtualedit wc wh wildcharm wim winminheight wmh wrapscan ww -syn keyword vimOption contained altkeymap arabic autowrite backupcopy bdir bin bomb bt ccv charconvert cinoptions cms comments conceallevel cpo cscopequickfix csqf cursorbind define diffexpr dy ef eol esckeys fcs fdn ff fileignorecase fmr foldlevel foldtext fsync gfs gtl guioptions hf hk hlsearch imactivatekey imi include inex isi js kp linebreak lm lz matchpairs maxmemtot mkspellmem mod mousef mouset nf oft pa path pheader previewheight printmbcharset pvw regexpengine rightleft rtp sbo scrolljump sel shell shelltype shortname shq sm so spellfile spr st sts swapsync syn tag tb termbidi tgst titleold top ttimeoutlen ttyscroll ul ur verbosefile visualbell +syn keyword vimOption contained altkeymap arabic autowrite backupcopy bdir bin bomb bt ccv charconvert cinoptions cms comments conceallevel cpo cscopequickfix csqf cursorbind define diffexpr dy ef eol esckeys fcs fdn ff fileignorecase fmr foldlevel foldtext fsync gfs gtl guioptions hf hk hlsearch imactivatekey imi include inex isi js kp linebreak lm lz matchpairs maxmemtot mkspellmem mod mousef mouset nf oft pa path pheader previewheight printmbcharset pvw regexpengine rightleft rtp sbo scrolljump sel shell shelltype shortname shq sm so spellfile spr st sts swapsync syn tag tb termbidi tgst titleold top ttimeoutlen ttyscroll ul ur unnamedclip unc verbosefile visualbell " vimOptions: These are the turn-off setting variants {{{2 syn keyword vimOption contained noacd noallowrevins noantialias noarabic noarshape noautoread noaw noballooneval nobinary nobk nobuflisted nocin noconfirm nocopyindent nocscoperelative nocsre nocuc nocursorcolumn nodelcombine nodigraph noed noendofline noerrorbells noex nofen nofk nogd nohid nohkmap nohkp nohlsearch noicon noim noimcmdline noimdisable noinf noinsertmode nojoinspaces nolazyredraw nolinebreak nolist nolpl noma nomagic noml nomodeline nomodified nomousef nomousehide nonumber noopendevice nopi nopreviewwindow nopvw norelativenumber norestorescreen nori norl noro noru nosb noscb noscs nosft noshelltemp noshortname noshowfulltag noshowmode nosm nosmartindent nosmd nosol nosplitbelow nospr nossl nostartofline noswapfile nota notagrelative notbi notbs noterse notextmode notgst notimeout noto notr nottybuiltin notx noundofile novisualbell nowarn noweirdinvert nowfw nowildignorecase nowinfixheight nowiv nowrap nowrite nowritebackup @@ -62,7 +62,7 @@ syn keyword vimErrSetting contained hardtabs ht w1200 w300 w9600 " AutoCmd Events {{{2 syn case ignore -syn keyword vimAutoEvent contained BufAdd BufCreate BufDelete BufEnter BufFilePost BufFilePre BufHidden BufLeave BufNew BufNewFile BufRead BufReadCmd BufReadPost BufReadPre BufUnload BufWinEnter BufWinLeave BufWipeout BufWrite BufWriteCmd BufWritePost BufWritePre Cmd-event CmdwinEnter CmdwinLeave ColorScheme CompleteDone CursorHold CursorHoldI CursorMoved CursorMovedI EncodingChanged FileAppendCmd FileAppendPost FileAppendPre FileChangedRO FileChangedShell FileChangedShellPost FileEncoding FileReadCmd FileReadPost FileReadPre FileType FileWriteCmd FileWritePost FileWritePre FilterReadPost FilterReadPre FilterWritePost FilterWritePre FocusGained FocusLost FuncUndefined GUIEnter GUIFailed InsertChange InsertCharPre InsertEnter InsertLeave MenuPopup QuickFixCmdPost QuickFixCmdPre QuitPre RemoteReply SessionLoadPost ShellCmdPost ShellFilterPost SourceCmd SourcePre SpellFileMissing StdinReadPost StdinReadPre SwapExists Syntax TabEnter TabLeave TermChanged TermResponse TextChanged TextChangedI User UserGettingBored VimEnter VimLeave VimLeavePre VimResized WinEnter WinLeave +syn keyword vimAutoEvent contained BufAdd BufCreate BufDelete BufEnter BufFilePost BufFilePre BufHidden BufLeave BufNew BufNewFile BufRead BufReadCmd BufReadPost BufReadPre BufUnload BufWinEnter BufWinLeave BufWipeout BufWrite BufWriteCmd BufWritePost BufWritePre Cmd-event CmdwinEnter CmdwinLeave ColorScheme CompleteDone CursorHold CursorHoldI CursorMoved CursorMovedI EncodingChanged FileAppendCmd FileAppendPost FileAppendPre FileChangedRO FileChangedShell FileChangedShellPost FileEncoding FileReadCmd FileReadPost FileReadPre FileType FileWriteCmd FileWritePost FileWritePre FilterReadPost FilterReadPre FilterWritePost FilterWritePre FocusGained FocusLost FuncUndefined GUIEnter GUIFailed InsertChange InsertCharPre InsertEnter InsertLeave JobActivity MenuPopup QuickFixCmdPost QuickFixCmdPre QuitPre RemoteReply SessionLoadPost ShellCmdPost ShellFilterPost SourceCmd SourcePre SpellFileMissing StdinReadPost StdinReadPre SwapExists Syntax TabEnter TabLeave TermChanged TermResponse TextChanged TextChangedI User UserGettingBored VimEnter VimLeave VimLeavePre VimResized WinEnter WinLeave " Highlight commonly used Groupnames {{{2 syn keyword vimGroup contained Comment Constant String Character Number Boolean Float Identifier Function Statement Conditional Repeat Label Operator Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug Underlined Ignore Error Todo @@ -75,7 +75,7 @@ syn case match " Function Names {{{2 syn keyword vimFuncName contained abs and argidx atan browsedir bufloaded bufwinnr byteidxcomp changenr clearmatches complete_add copy count deepcopy diff_filler escape executable expand feedkeys filter float2nr fnameescape foldclosedend foldtextresult garbagecollect getbufvar getcmdline getcwd getfsize getline getpid getreg gettabwinvar getwinvar has hasmapto histget hlID indent inputdialog inputsave invert items len line localtime luaeval mapcheck matcharg matchlist min mzeval or prevnonblank py3eval readfile remote_expr remote_read rename reverse screenchar search searchpairpos serverlist setcmdpos setloclist setpos setreg settabwinvar sha256 shiftwidth sin sort spellbadword split str2float strchars strftime string strpart strtrans submatch synconcealed synIDattr synstack tabpagebuflist tabpagewinnr taglist tanh tolower tr type undotree virtcol wildmenumode wincol winline winrestcmd winsaveview writefile syn keyword vimFuncName contained acos append argv atan2 bufexists bufname byte2line call char2nr col complete_check cos cscope_connection delete diff_hlID eval exists expr8 filereadable finddir floor fnamemodify foldlevel foreground get getchar getcmdpos getfontname getftime getloclist getpos getregtype getwinposx glob has_key histadd histnr hostname index inputlist inputsecret isdirectory join libcall line2byte log map match matchdelete matchstr mkdir nextnonblank pathshorten printf pyeval reltime remote_foreground remote_send repeat round screencol searchdecl searchpos setbufvar setline setmatches setqflist settabvar setwinvar shellescape simplify sinh soundfold spellsuggest sqrt str2nr strdisplaywidth stridx strlen strridx strwidth substitute synID synIDtrans system tabpagenr tagfiles tan tempname toupper trunc undofile values visualmode winbufnr winheight winnr winrestview winwidth xor -syn keyword vimFuncName contained add argc asin browse buflisted bufnr byteidx ceil cindent complete confirm cosh cursor did_filetype empty eventhandler exp extend filewritable findfile fmod foldclosed foldtext function getbufline getcharmod getcmdtype getfperm getftype getmatches getqflist gettabvar getwinposy globpath haslocaldir histdel hlexists iconv input inputrestore insert islocked keys libcallnr lispindent log10 maparg matchadd matchend max mode nr2char pow pumvisible range reltimestr remote_peek remove resolve screenattr screenrow searchpair server2client +syn keyword vimFuncName contained add argc asin browse buflisted bufnr byteidx ceil cindent complete confirm cosh cursor did_filetype empty eventhandler exp extend filewritable findfile fmod foldclosed foldtext function getbufline getcharmod getcmdtype getfperm getftype getmatches getqflist gettabvar getwinposy globpath haslocaldir histdel hlexists iconv input inputrestore insert islocked keys libcallnr lispindent log10 maparg matchadd matchend max mode nr2char pow pumvisible range reltimestr remote_peek remove resolve screenattr screenrow searchpair server2client jobsend jobstart jobstop rpcnotify rpcrequest rpcstart rpcstop "--- syntax here and above generated by mkvimvim --- " Special Vim Highlighting (not automatic) {{{1 diff --git a/scripts/gendeclarations.lua b/scripts/gendeclarations.lua index 76711cc214..bc55b48a0a 100755 --- a/scripts/gendeclarations.lua +++ b/scripts/gendeclarations.lua @@ -59,9 +59,16 @@ local right_word = concat( raw_word, neg_look_ahead(aw) ) -local word = concat( - neg_look_behind(aw), - right_word +local word = branch( + concat( + branch(lit('ArrayOf('), lit('DictionaryOf(')), -- typed container macro + one_or_more(any_character - lit(')')), + lit(')') + ), + concat( + neg_look_behind(aw), + right_word + ) ) local spaces = any_amount(branch( s, @@ -204,7 +211,7 @@ while init ~= nil do declaration = declaration:gsub('\n', ' ') declaration = declaration:gsub('%s+', ' ') declaration = declaration:gsub(' ?%( ?', '(') - declaration = declaration:gsub(' ?%) ?', ')') + -- declaration = declaration:gsub(' ?%) ?', ')') declaration = declaration:gsub(' ?, ?', ', ') declaration = declaration:gsub(' ?(%*+) ?', ' %1') declaration = declaration:gsub(' ?(FUNC_ATTR_)', ' %1') diff --git a/scripts/msgpack-gen.lua b/scripts/msgpack-gen.lua index 8940cc72f6..916597afda 100644 --- a/scripts/msgpack-gen.lua +++ b/scripts/msgpack-gen.lua @@ -1,5 +1,5 @@ lpeg = require('lpeg') -msgpack = require('cmsgpack') +msgpack = require('MessagePack') -- lpeg grammar for building api metadata from a set of header files. It -- ignores comments and preprocessor commands and parses a very small subset @@ -16,7 +16,12 @@ ws = S(' \t') + nl fill = ws ^ 0 c_comment = P('//') * (not_nl ^ 0) c_preproc = P('#') * (not_nl ^ 0) -c_id = letter * (alpha ^ 0) +typed_container = + (P('ArrayOf(') + P('DictionaryOf(')) * ((any - P(')')) ^ 1) * P(')') +c_id = ( + typed_container + + (letter * (alpha ^ 0)) +) c_void = P('void') c_param_type = ( ((P('Error') * fill * P('*') * fill) * Cc('error')) + @@ -35,12 +40,8 @@ grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1) -- we need at least 2 arguments since the last one is the output file assert(#arg >= 1) --- api metadata -api = { - functions = {}, - -- Helpers for object-oriented languages - classes = {'Buffer', 'Window', 'Tabpage'} -} +functions = {} + -- names of all headers relative to the source root(for inclusion in the -- generated file) headers = {} @@ -59,9 +60,8 @@ for i = 1, #arg - 1 do local input = io.open(full_path, 'rb') local tmp = grammar:match(input:read('*all')) for i = 1, #tmp do - api.functions[#api.functions + 1] = tmp[i] - local fn_id = #api.functions - local fn = api.functions[fn_id] + functions[#functions + 1] = tmp[i] + local fn = tmp[i] if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then -- this function should receive the channel id fn.receives_channel_id = true @@ -75,8 +75,6 @@ for i = 1, #arg - 1 do -- for specifying errors fn.parameters[#fn.parameters] = nil end - -- assign a unique integer id for each api function - fn.id = fn_id end input:close() end @@ -93,9 +91,11 @@ output:write([[ #include "nvim/map.h" #include "nvim/log.h" +#include "nvim/vim.h" #include "nvim/os/msgpack_rpc.h" #include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/defs.h" ]]) for i = 1, #headers do @@ -107,12 +107,12 @@ end output:write([[ -const uint8_t msgpack_metadata[] = { +static const uint8_t msgpack_metadata[] = { ]]) -- serialize the API metadata using msgpack and embed into the resulting -- binary for easy querying by clients -packed = msgpack.pack(api) +packed = msgpack.pack(functions) for i = 1, #packed do output:write(string.byte(packed, i)..', ') if i % 10 == 0 then @@ -121,16 +121,42 @@ for i = 1, #packed do end output:write([[ }; -const unsigned int msgpack_metadata_size = sizeof(msgpack_metadata); + +void msgpack_rpc_init_function_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)msgpack_metadata, + sizeof(msgpack_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object functions; + msgpack_rpc_to_object(&unpacked.data, &functions); + msgpack_unpacked_destroy(&unpacked); + PUT(*metadata, "functions", functions); +} ]]) --- start the handler functions. First handler (method_id=0) is reserved for --- querying the metadata, usually it is the first function called by clients. --- Visit each function metadata to build the handler function with code --- generated for validating arguments and calling to the real API. -for i = 1, #api.functions do - local fn = api.functions[i] +local function real_type(type) + local rv = type + if typed_container:match(rv) then + if rv:match('Array') then + rv = 'Array' + else + rv = 'Dictionary' + end + end + return rv +end + +-- start the handler functions. Visit each function metadata to build the +-- handler function with code generated for validating arguments and calling to +-- the real API. +for i = 1, #functions do + local fn = functions[i] local args = {} output:write('static Object handle_'..fn.name..'(uint64_t channel_id, msgpack_object *req, Error *error)') @@ -140,7 +166,7 @@ for i = 1, #api.functions do for j = 1, #fn.parameters do local param = fn.parameters[j] local converted = 'arg_'..j - output:write('\n '..param[1]..' '..converted..' msgpack_rpc_init_'..string.lower(param[1])..';') + output:write('\n '..param[1]..' '..converted..' api_init_'..string.lower(real_type(param[1]))..';') end output:write('\n') output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {') @@ -155,7 +181,7 @@ for i = 1, #api.functions do param = fn.parameters[j] arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')' converted = 'arg_'..j - convert_arg = 'msgpack_rpc_to_'..string.lower(param[1]) + convert_arg = 'msgpack_rpc_to_'..real_type(param[1]):lower() output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {') output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");') output:write('\n error->set = true;') @@ -202,7 +228,7 @@ for i = 1, #api.functions do end if fn.return_type ~= 'void' then - output:write('\n Object ret = '..string.upper(fn.return_type)..'_OBJ(rv);') + output:write('\n Object ret = '..string.upper(real_type(fn.return_type))..'_OBJ(rv);') end -- Now generate the cleanup label for freeing memory allocated for the -- arguments @@ -210,7 +236,7 @@ for i = 1, #api.functions do for j = 1, #fn.parameters do local param = fn.parameters[j] - output:write('\n msgpack_rpc_free_'..string.lower(param[1])..'(arg_'..j..');') + output:write('\n api_free_'..string.lower(real_type(param[1]))..'(arg_'..j..');') end if fn.return_type ~= 'void' then output:write('\n return ret;\n}\n\n'); @@ -219,47 +245,26 @@ for i = 1, #api.functions do end end -output:write([[ -static Object handle_missing_method(uint64_t channel_id, - msgpack_object *req, - Error *error) -{ - snprintf(error->msg, sizeof(error->msg), "Invalid function id"); - error->set = true; - return NIL; -} - -]]) - --- Generate the table of handler functions indexed by method id -output:write([[ -static const rpc_method_handler_fn rpc_method_handlers[] = { - [0] = (rpc_method_handler_fn)NULL]]) - -for i = 1, #api.functions do - local fn = api.functions[i] - output:write(',\n ['..i..'] = handle_'..fn.name..'') -end -output:write('\n};\n\n') - -- Generate a function that initializes method names with handler functions output:write([[ -static Map(cstr_t, uint64_t) *rpc_method_ids = NULL; +static Map(String, rpc_method_handler_fn) *methods = NULL; void msgpack_rpc_init(void) { - rpc_method_ids = map_new(cstr_t, uint64_t)(); + methods = map_new(String, rpc_method_handler_fn)(); ]]) --- Msgpack strings must be copied to a 0-terminated temporary buffer before --- searching in the map, so we keep track of the maximum method name length in --- order to create the smallest possible buffer for xstrlcpy +-- Keep track of the maximum method name length in order to avoid walking +-- strings longer than that when searching for a method handler local max_fname_len = 0 -for i = 1, #api.functions do - local fn = api.functions[i] - output:write(' map_put(cstr_t, uint64_t)(rpc_method_ids, "' - ..fn.name..'", '..i..');\n') +for i = 1, #functions do + local fn = functions[i] + output:write(' map_put(String, rpc_method_handler_fn)(methods, '.. + '(String) {.data = "'..fn.name..'", '.. + '.size = sizeof("'..fn.name..'") - 1}, handle_'.. + fn.name..');\n') + if #fn.name > max_fname_len then max_fname_len = #fn.name end @@ -268,30 +273,27 @@ end output:write('\n}\n\n') output:write([[ -#define min(X, Y) (X < Y ? X : Y) - Object msgpack_rpc_dispatch(uint64_t channel_id, msgpack_object *req, Error *error) { msgpack_object method = req->via.array.ptr[2]; - uint64_t method_id = method.via.u64; - - if (method.type == MSGPACK_OBJECT_RAW) { - char method_name[]]..(max_fname_len + 1)..[[]; - xstrlcpy(method_name, method.via.raw.ptr, min(method.via.raw.size, ]] ..(max_fname_len)..[[) + 1); - method_id = map_get(cstr_t, uint64_t)(rpc_method_ids, method_name); - if (!method_id) { - method_id = UINT64_MAX; - } + rpc_method_handler_fn handler = NULL; + + if (method.type == MSGPACK_OBJECT_BIN || method.type == MSGPACK_OBJECT_STR) { +]]) +output:write(' handler = map_get(String, rpc_method_handler_fn)') +output:write('(methods, (String){.data=(char *)method.via.bin.ptr,') +output:write('.size=min(method.via.bin.size, '..max_fname_len..')});\n') +output:write([[ } + + if (!handler) { + handler = msgpack_rpc_handle_missing_method; + } + + return handler(channel_id, req, error); +} ]]) -output:write('\n // method_id=0 is specially handled') -output:write('\n assert(method_id > 0);') -output:write('\n'); -output:write('\n rpc_method_handler_fn handler = (method_id <= '..#api.functions..') ?') -output:write('\n rpc_method_handlers[method_id] : handle_missing_method;') -output:write('\n return handler(channel_id, req, error);') -output:write('\n}\n') output:close() diff --git a/scripts/run-api-tests.exp b/scripts/run-api-tests.exp index 6209a77cfc..27c9c963e5 100755 --- a/scripts/run-api-tests.exp +++ b/scripts/run-api-tests.exp @@ -11,8 +11,8 @@ set run_nvim [split [lindex $argv 1] " "] # don't echo to stdout log_user 0 -# set NEOVIM_LISTEN_ADDRESS, so nvim will listen on a known socket -set env(NEOVIM_LISTEN_ADDRESS) "/tmp/nvim-[exec date +%s%N].sock" +# set NVIM_LISTEN_ADDRESS, so nvim will listen on a known socket +set env(NVIM_LISTEN_ADDRESS) "/tmp/nvim-[exec date +%s%N].sock" # start nvim spawn {*}$run_nvim # save the job descriptor diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index a268e04559..8355bfe868 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -51,10 +51,10 @@ Integer buffer_get_length(Buffer buffer, Error *err) String buffer_get_line(Buffer buffer, Integer index, Error *err) { String rv = {.size = 0}; - StringArray slice = buffer_get_slice(buffer, index, index, true, true, err); + Array slice = buffer_get_slice(buffer, index, index, true, true, err); if (!err->set && slice.size) { - rv = slice.items[0]; + rv = slice.items[0].data.string; } free(slice.items); @@ -70,7 +70,8 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// @param[out] err Details of an error that may have occurred void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) { - StringArray array = {.items = &line, .size = 1}; + Object l = STRING_OBJ(line); + Array array = {.items = &l, .size = 1}; buffer_set_slice(buffer, index, index, true, true, array, err); } @@ -81,7 +82,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) /// @param[out] err Details of an error that may have occurred void buffer_del_line(Buffer buffer, Integer index, Error *err) { - StringArray array = ARRAY_DICT_INIT; + Array array = ARRAY_DICT_INIT; buffer_set_slice(buffer, index, index, true, true, array, err); } @@ -94,14 +95,14 @@ void buffer_del_line(Buffer buffer, Integer index, Error *err) /// @param include_end True if the slice includes the `end` parameter /// @param[out] err Details of an error that may have occurred /// @return An array of lines -StringArray buffer_get_slice(Buffer buffer, - Integer start, - Integer end, - Boolean include_start, - Boolean include_end, - Error *err) +ArrayOf(String) buffer_get_slice(Buffer buffer, + Integer start, + Integer end, + Boolean include_start, + Boolean include_end, + Error *err) { - StringArray rv = ARRAY_DICT_INIT; + Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -118,7 +119,7 @@ StringArray buffer_get_slice(Buffer buffer, } rv.size = (size_t)(end - start); - rv.items = xcalloc(sizeof(String), rv.size); + rv.items = xcalloc(sizeof(Object), rv.size); for (size_t i = 0; i < rv.size; i++) { int64_t lnum = start + (int64_t)i; @@ -129,13 +130,13 @@ StringArray buffer_get_slice(Buffer buffer, } const char *bufstr = (char *) ml_get_buf(buf, (linenr_T) lnum, false); - rv.items[i] = cstr_to_string(bufstr); + rv.items[i] = STRING_OBJ(cstr_to_string(bufstr)); } end: if (err->set) { for (size_t i = 0; i < rv.size; i++) { - free(rv.items[i].data); + free(rv.items[i].data.string.data); } free(rv.items); @@ -152,15 +153,15 @@ end: /// @param end The last line index /// @param include_start True if the slice includes the `start` parameter /// @param include_end True if the slice includes the `end` parameter -/// @param lines An array of lines to use as replacement(A 0-length array -/// will simply delete the line range) +/// @param replacement An array of lines to use as replacement(A 0-length +// array will simply delete the line range) /// @param[out] err Details of an error that may have occurred void buffer_set_slice(Buffer buffer, Integer start, Integer end, Boolean include_start, Boolean include_end, - StringArray replacement, + ArrayOf(String) replacement, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -184,10 +185,15 @@ void buffer_set_slice(Buffer buffer, size_t new_len = replacement.size; size_t old_len = (size_t)(end - start); ssize_t extra = 0; // lines added to text, can be negative - char **lines = (new_len != 0) ? xmalloc(new_len * sizeof(char *)) : NULL; + char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL; for (size_t i = 0; i < new_len; i++) { - String l = replacement.items[i]; + if (replacement.items[i].type != kObjectTypeString) { + set_api_error("all items in the replacement array must be strings", err); + goto end; + } + + String l = replacement.items[i].data.string; lines[i] = xmemdupz(l.data, l.size); } @@ -430,7 +436,10 @@ Boolean buffer_is_valid(Buffer buffer) /// to the end of the buffer. /// @param lines An array of lines /// @param[out] err Details of an error that may have occurred -void buffer_insert(Buffer buffer, Integer lnum, StringArray lines, Error *err) +void buffer_insert(Buffer buffer, + Integer lnum, + ArrayOf(String) lines, + Error *err) { buffer_set_slice(buffer, lnum, lnum, false, true, lines, err); } @@ -441,9 +450,9 @@ void buffer_insert(Buffer buffer, Integer lnum, StringArray lines, Error *err) /// @param name The mark's name /// @param[out] err Details of an error that may have occurred /// @return The (row, col) tuple -Position buffer_get_mark(Buffer buffer, String name, Error *err) +ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err) { - Position rv = POSITION_INIT; + Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -473,8 +482,9 @@ Position buffer_get_mark(Buffer buffer, String name, Error *err) return rv; } - rv.row = posp->lnum; - rv.col = posp->col; + ADD(rv, INTEGER_OBJ(posp->lnum)); + ADD(rv, INTEGER_OBJ(posp->col)); + return rv; } diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index b049412014..2dd229ec5f 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -5,17 +5,15 @@ #include <stdbool.h> #include <string.h> -#define ARRAY_DICT_INIT {.size = 0, .items = NULL} +#define ARRAY_DICT_INIT {.size = 0, .capacity = 0, .items = NULL} #define STRING_INIT {.data = NULL, .size = 0} #define OBJECT_INIT { .type = kObjectTypeNil } -#define POSITION_INIT { .row = 0, .col = 0 } #define REMOTE_TYPE(type) typedef uint64_t type -#define TYPED_ARRAY_OF(type) \ - typedef struct { \ - type *items; \ - size_t size; \ - } type##Array +#ifdef INCLUDE_GENERATED_DECLARATIONS + #define ArrayOf(...) Array + #define DictionaryOf(...) Dictionary +#endif // Basic types typedef struct { @@ -38,15 +36,6 @@ REMOTE_TYPE(Tabpage); typedef struct object Object; -TYPED_ARRAY_OF(Buffer); -TYPED_ARRAY_OF(Window); -TYPED_ARRAY_OF(Tabpage); -TYPED_ARRAY_OF(String); - -typedef struct { - Integer row, col; -} Position; - typedef struct { Object *items; size_t size, capacity; @@ -60,40 +49,30 @@ typedef struct { } Dictionary; typedef enum { + kObjectTypeBuffer, + kObjectTypeWindow, + kObjectTypeTabpage, kObjectTypeNil, kObjectTypeBoolean, kObjectTypeInteger, kObjectTypeFloat, kObjectTypeString, - kObjectTypeBuffer, - kObjectTypeWindow, - kObjectTypeTabpage, kObjectTypeArray, kObjectTypeDictionary, - kObjectTypePosition, - kObjectTypeStringArray, - kObjectTypeBufferArray, - kObjectTypeWindowArray, - kObjectTypeTabpageArray, } ObjectType; struct object { ObjectType type; union { + Buffer buffer; + Window window; + Tabpage tabpage; Boolean boolean; Integer integer; Float floating; String string; - Buffer buffer; - Window window; - Tabpage tabpage; Array array; Dictionary dictionary; - Position position; - StringArray stringarray; - BufferArray bufferarray; - WindowArray windowarray; - TabpageArray tabpagearray; } data; }; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f6fb46e1d1..14a820aa1b 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -6,6 +6,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/handle.h" +#include "nvim/os/provider.h" #include "nvim/ascii.h" #include "nvim/vim.h" #include "nvim/buffer.h" @@ -449,6 +450,130 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) return true; } +void api_free_string(String value) +{ + if (!value.data) { + return; + } + + free(value.data); +} + +void api_free_object(Object value) +{ + switch (value.type) { + case kObjectTypeNil: + case kObjectTypeBoolean: + case kObjectTypeInteger: + case kObjectTypeFloat: + case kObjectTypeBuffer: + case kObjectTypeWindow: + case kObjectTypeTabpage: + break; + + case kObjectTypeString: + api_free_string(value.data.string); + break; + + case kObjectTypeArray: + api_free_array(value.data.array); + break; + + case kObjectTypeDictionary: + api_free_dictionary(value.data.dictionary); + break; + + default: + abort(); + } +} + +void api_free_array(Array value) +{ + for (size_t i = 0; i < value.size; i++) { + api_free_object(value.items[i]); + } + + free(value.items); +} + +void api_free_dictionary(Dictionary value) +{ + for (size_t i = 0; i < value.size; i++) { + api_free_string(value.items[i].key); + api_free_object(value.items[i].value); + } + + free(value.items); +} + +Dictionary api_metadata(void) +{ + static Dictionary metadata = ARRAY_DICT_INIT; + + if (!metadata.size) { + msgpack_rpc_init_function_metadata(&metadata); + init_type_metadata(&metadata); + provider_init_feature_metadata(&metadata); + } + + return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; +} + +static void init_type_metadata(Dictionary *metadata) +{ + Dictionary types = ARRAY_DICT_INIT; + + Dictionary buffer_metadata = ARRAY_DICT_INIT; + PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer)); + + Dictionary window_metadata = ARRAY_DICT_INIT; + PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow)); + + Dictionary tabpage_metadata = ARRAY_DICT_INIT; + PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage)); + + PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata)); + PUT(types, "Window", DICTIONARY_OBJ(window_metadata)); + PUT(types, "Tabpage", DICTIONARY_OBJ(tabpage_metadata)); + + PUT(*metadata, "types", DICTIONARY_OBJ(types)); +} + +/// Creates a deep clone of an object +static Object copy_object(Object obj) +{ + switch (obj.type) { + case kObjectTypeNil: + case kObjectTypeBoolean: + case kObjectTypeInteger: + case kObjectTypeFloat: + return obj; + + case kObjectTypeString: + return STRING_OBJ(cstr_to_string(obj.data.string.data)); + + case kObjectTypeArray: { + Array rv = ARRAY_DICT_INIT; + for (size_t i = 0; i < obj.data.array.size; i++) { + ADD(rv, copy_object(obj.data.array.items[i])); + } + return ARRAY_OBJ(rv); + } + + case kObjectTypeDictionary: { + Dictionary rv = ARRAY_DICT_INIT; + for (size_t i = 0; i < obj.data.dictionary.size; i++) { + KeyValuePair item = obj.data.dictionary.items[i]; + PUT(rv, item.key.data, copy_object(item.value)); + } + return DICTIONARY_OBJ(rv); + } + default: + abort(); + } +} + /// Recursion helper for the `vim_to_object`. This uses a pointer table /// to avoid infinite recursion due to cyclic references /// diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index f1b9dc3bc8..f3ecdaacc4 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -51,36 +51,11 @@ .data.array = a \ }) -#define STRINGARRAY_OBJ(a) ((Object) { \ - .type = kObjectTypeStringArray, \ - .data.stringarray = a \ - }) - -#define BUFFERARRAY_OBJ(a) ((Object) { \ - .type = kObjectTypeBufferArray, \ - .data.bufferarray = a \ - }) - -#define WINDOWARRAY_OBJ(a) ((Object) { \ - .type = kObjectTypeWindowArray, \ - .data.windowarray = a \ - }) - -#define TABPAGEARRAY_OBJ(a) ((Object) { \ - .type = kObjectTypeTabpageArray, \ - .data.tabpagearray = a \ - }) - #define DICTIONARY_OBJ(d) ((Object) { \ .type = kObjectTypeDictionary, \ .data.dictionary = d \ }) -#define POSITION_OBJ(p) ((Object) { \ - .type = kObjectTypePosition, \ - .data.position = p \ - }) - #define NIL ((Object) {.type = kObjectTypeNil}) #define PUT(dict, k, v) \ @@ -91,6 +66,25 @@ #define ADD(array, item) \ kv_push(Object, array, item) +// Helpers used by the generated msgpack-rpc api wrappers +#define api_init_boolean +#define api_init_integer +#define api_init_float +#define api_init_string = STRING_INIT +#define api_init_buffer +#define api_init_window +#define api_init_tabpage +#define api_init_object = NIL +#define api_init_array = ARRAY_DICT_INIT +#define api_init_dictionary = ARRAY_DICT_INIT + +#define api_free_boolean(value) +#define api_free_integer(value) +#define api_free_float(value) +#define api_free_buffer(value) +#define api_free_window(value) +#define api_free_tabpage(value) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" #endif diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 535722c087..91020d6850 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -13,9 +13,9 @@ /// @param tabpage The tabpage /// @param[out] err Details of an error that may have occurred /// @return The number of windows in `tabpage` -WindowArray tabpage_get_windows(Tabpage tabpage, Error *err) +ArrayOf(Window) tabpage_get_windows(Tabpage tabpage, Error *err) { - WindowArray rv = ARRAY_DICT_INIT; + Array rv = ARRAY_DICT_INIT; tabpage_T *tab = find_tab_by_handle(tabpage, err); if (!tab) { @@ -32,14 +32,14 @@ WindowArray tabpage_get_windows(Tabpage tabpage, Error *err) rv.size++; } - rv.items = xmalloc(sizeof(Window) * rv.size); + rv.items = xmalloc(sizeof(Object) * rv.size); size_t i = 0; FOR_ALL_TAB_WINDOWS(tp, wp) { if (tp != tab) { break; } - rv.items[i++] = wp->handle; + rv.items[i++] = WINDOW_OBJ(wp->handle); } return rv; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a2c50b4c81..43f2aafdc8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -149,9 +149,9 @@ Integer vim_strwidth(String str, Error *err) /// Returns a list of paths contained in 'runtimepath' /// /// @return The list of paths -StringArray vim_list_runtime_paths(void) +ArrayOf(String) vim_list_runtime_paths(void) { - StringArray rv = ARRAY_DICT_INIT; + Array rv = ARRAY_DICT_INIT; uint8_t *rtp = p_rtp; if (*rtp == NUL) { @@ -168,19 +168,20 @@ StringArray vim_list_runtime_paths(void) } // Allocate memory for the copies - rv.items = xmalloc(sizeof(String) * rv.size); + rv.items = xmalloc(sizeof(Object) * rv.size); // reset the position rtp = p_rtp; // Start copying for (size_t i = 0; i < rv.size && *rtp != NUL; i++) { - rv.items[i].data = xmalloc(MAXPATHL); + rv.items[i].type = kObjectTypeString; + rv.items[i].data.string.data = xmalloc(MAXPATHL); // Copy the path from 'runtimepath' to rv.items[i] int length = copy_option_part(&rtp, - (char_u *)rv.items[i].data, + (char_u *)rv.items[i].data.string.data, MAXPATHL, ","); assert(length >= 0); - rv.items[i].size = (size_t)length; + rv.items[i].data.string.size = (size_t)length; } return rv; @@ -307,12 +308,22 @@ void vim_err_write(String str) write_msg(str, true); } +/// Higher level error reporting function that ensures all str contents +/// are written by sending a trailing linefeed to `vim_wrr_write` +/// +/// @param str The message +void vim_report_error(String str) +{ + vim_err_write(str); + vim_err_write((String) {.data = "\n", .size = 1}); +} + /// Gets the current list of buffer handles /// /// @return The number of buffers -BufferArray vim_get_buffers(void) +ArrayOf(Buffer) vim_get_buffers(void) { - BufferArray rv = ARRAY_DICT_INIT; + Array rv = ARRAY_DICT_INIT; buf_T *b = firstbuf; while (b) { @@ -320,12 +331,12 @@ BufferArray vim_get_buffers(void) b = b->b_next; } - rv.items = xmalloc(sizeof(Buffer) * rv.size); + rv.items = xmalloc(sizeof(Object) * rv.size); size_t i = 0; b = firstbuf; while (b) { - rv.items[i++] = b->handle; + rv.items[i++] = BUFFER_OBJ(b->handle); b = b->b_next; } @@ -370,9 +381,9 @@ void vim_set_current_buffer(Buffer buffer, Error *err) /// Gets the current list of window handles /// /// @return The number of windows -WindowArray vim_get_windows(void) +ArrayOf(Window) vim_get_windows(void) { - WindowArray rv = ARRAY_DICT_INIT; + Array rv = ARRAY_DICT_INIT; tabpage_T *tp; win_T *wp; @@ -380,11 +391,11 @@ WindowArray vim_get_windows(void) rv.size++; } - rv.items = xmalloc(sizeof(Window) * rv.size); + rv.items = xmalloc(sizeof(Object) * rv.size); size_t i = 0; FOR_ALL_TAB_WINDOWS(tp, wp) { - rv.items[i++] = wp->handle; + rv.items[i++] = WINDOW_OBJ(wp->handle); } return rv; @@ -426,9 +437,9 @@ void vim_set_current_window(Window window, Error *err) /// Gets the current list of tabpage handles /// /// @return The number of tab pages -TabpageArray vim_get_tabpages(void) +ArrayOf(Tabpage) vim_get_tabpages(void) { - TabpageArray rv = ARRAY_DICT_INIT; + Array rv = ARRAY_DICT_INIT; tabpage_T *tp = first_tabpage; while (tp) { @@ -436,12 +447,12 @@ TabpageArray vim_get_tabpages(void) tp = tp->tp_next; } - rv.items = xmalloc(sizeof(Tabpage) * rv.size); + rv.items = xmalloc(sizeof(Object) * rv.size); size_t i = 0; tp = first_tabpage; while (tp) { - rv.items[i++] = tp->handle; + rv.items[i++] = TABPAGE_OBJ(tp->handle); tp = tp->tp_next; } @@ -501,22 +512,33 @@ void vim_unsubscribe(uint64_t channel_id, String event) channel_unsubscribe(channel_id, e); } -/// Registers the channel as the provider for `method`. This fails if -/// a provider for `method` is already registered. +/// Registers the channel as the provider for `feature`. This fails if +/// a provider for `feature` is already provided by another channel. /// /// @param channel_id The channel id -/// @param method The method name +/// @param feature The feature name /// @param[out] err Details of an error that may have occurred -void vim_register_provider(uint64_t channel_id, String method, Error *err) +void vim_register_provider(uint64_t channel_id, String feature, Error *err) { char buf[METHOD_MAXLEN]; - xstrlcpy(buf, method.data, sizeof(buf)); + xstrlcpy(buf, feature.data, sizeof(buf)); if (!provider_register(buf, channel_id)) { - set_api_error("Provider already registered", err); + set_api_error("Feature doesn't exist", err); } } +Array vim_get_api_info(uint64_t channel_id) +{ + Array rv = ARRAY_DICT_INIT; + + assert(channel_id <= INT64_MAX); + ADD(rv, INTEGER_OBJ((int64_t)channel_id)); + ADD(rv, DICTIONARY_OBJ(api_metadata())); + + return rv; +} + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing /// later. diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 1ab441bed3..dd256f2b6d 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -33,14 +33,14 @@ Buffer window_get_buffer(Window window, Error *err) /// @param window The window handle /// @param[out] err Details of an error that may have occurred /// @return the (row, col) tuple -Position window_get_cursor(Window window, Error *err) +ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err) { - Position rv = POSITION_INIT; + Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); if (win) { - rv.row = win->w_cursor.lnum; - rv.col = win->w_cursor.col; + ADD(rv, INTEGER_OBJ(win->w_cursor.lnum)); + ADD(rv, INTEGER_OBJ(win->w_cursor.col)); } return rv; @@ -51,31 +51,35 @@ Position window_get_cursor(Window window, Error *err) /// @param window The window handle /// @param pos the (row, col) tuple representing the new position /// @param[out] err Details of an error that may have occurred -void window_set_cursor(Window window, Position pos, Error *err) +void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) { win_T *win = find_window_by_handle(window, err); - if (!win) { + if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger || + pos.items[1].type != kObjectTypeInteger) { + set_api_error("\"pos\" argument must be a [row, col] array", err); return; } - if (pos.row <= 0 || pos.row > win->w_buffer->b_ml.ml_line_count) { - set_api_error("cursor position outside buffer", err); + if (!win) { return; } - if (pos.row > LONG_MAX || pos.row < LONG_MIN) { - set_api_error("Row value outside range", err); + int64_t row = pos.items[0].data.integer; + int64_t col = pos.items[1].data.integer; + + if (row <= 0 || row > win->w_buffer->b_ml.ml_line_count) { + set_api_error("cursor position outside buffer", err); return; } - if (pos.col > INT_MAX || pos.col < INT_MIN) { + if (col > MAXCOL || col < 0) { set_api_error("Column value outside range", err); return; } - win->w_cursor.lnum = (linenr_T)pos.row; - win->w_cursor.col = (colnr_T)pos.col; + win->w_cursor.lnum = (linenr_T)row; + win->w_cursor.col = (colnr_T)col; win->w_cursor.coladd = 0; // When column is out of range silently correct it. check_cursor_col_win(win); @@ -243,14 +247,14 @@ void window_set_option(Window window, String name, Object value, Error *err) /// @param window The window handle /// @param[out] err Details of an error that may have occurred /// @return The (row, col) tuple with the window position -Position window_get_position(Window window, Error *err) +ArrayOf(Integer, 2) window_get_position(Window window, Error *err) { - Position rv = POSITION_INIT; + Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); if (win) { - rv.col = win->w_wincol; - rv.row = win->w_winrow; + ADD(rv, INTEGER_OBJ(win->w_winrow)); + ADD(rv, INTEGER_OBJ(win->w_wincol)); } return rv; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 77bed67d5f..11171617ef 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1314,7 +1314,7 @@ buflist_new ( * for hard links. */ FileID file_id; bool file_id_valid = (sfname != NULL && - os_get_file_id((char *)sfname, &file_id)); + os_fileid((char *)sfname, &file_id)); if (ffname != NULL && !(flags & BLN_DUMMY) && (buf = buflist_findname_file_id(ffname, &file_id, file_id_valid)) != NULL) { @@ -1671,7 +1671,7 @@ buf_T *buflist_findname_exp(char_u *fname) buf_T *buflist_findname(char_u *ffname) { FileID file_id; - bool file_id_valid = os_get_file_id((char *)ffname, &file_id); + bool file_id_valid = os_fileid((char *)ffname, &file_id); return buflist_findname_file_id(ffname, &file_id, file_id_valid); } @@ -1763,13 +1763,16 @@ buflist_findpat ( if (curtab_only) { /* Ignore the match if the buffer is not open in * the current tab. */ - win_T *wp; - - for (wp = firstwin; wp != NULL; wp = wp->w_next) - if (wp->w_buffer == buf) + bool found_window = false; + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer == buf) { + found_window = true; break; - if (wp == NULL) + } + } + if (!found_window) { continue; + } } if (match >= 0) { /* already found a match */ match = -2; @@ -2020,22 +2023,22 @@ static void buflist_setfpos(buf_T *buf, win_T *win, linenr_T lnum, colnr_T col, /* - * Return TRUE when "wip" has 'diff' set and the diff is only for another tab + * Return true when "wip" has 'diff' set and the diff is only for another tab * page. That's because a diff is local to a tab page. */ -static int wininfo_other_tab_diff(wininfo_T *wip) +static bool wininfo_other_tab_diff(wininfo_T *wip) { - win_T *wp; - if (wip->wi_opt.wo_diff) { - for (wp = firstwin; wp != NULL; wp = wp->w_next) - /* return FALSE when it's a window in the current tab page, thus + FOR_ALL_WINDOWS(wp) { + /* return false when it's a window in the current tab page, thus * the buffer was in diff mode here */ - if (wip->wi_win == wp) - return FALSE; - return TRUE; + if (wip->wi_win == wp) { + return false; + } + } + return true; } - return FALSE; + return false; } /* @@ -2221,7 +2224,7 @@ setfname ( * - if the buffer is loaded, fail * - if the buffer is not loaded, delete it from the list */ - file_id_valid = os_get_file_id((char *)ffname, &file_id); + file_id_valid = os_fileid((char *)ffname, &file_id); if (!(buf->b_flags & BF_DUMMY)) { obuf = buflist_findname_file_id(ffname, &file_id, file_id_valid); } @@ -2399,7 +2402,7 @@ static int otherfile_buf(buf_T *buf, char_u *ffname, /* If no struct stat given, get it now */ if (file_id_p == NULL) { file_id_p = &file_id; - file_id_valid = os_get_file_id((char *)ffname, file_id_p); + file_id_valid = os_fileid((char *)ffname, file_id_p); } if (!file_id_valid) { // file_id not valid, assume files are different. @@ -2429,7 +2432,7 @@ void buf_set_file_id(buf_T *buf) { FileID file_id; if (buf->b_fname != NULL - && os_get_file_id((char *)buf->b_fname, &file_id)) { + && os_fileid((char *)buf->b_fname, &file_id)) { buf->file_id_valid = true; buf->file_id = file_id; } else { @@ -2441,7 +2444,7 @@ void buf_set_file_id(buf_T *buf) static bool buf_same_file_id(buf_T *buf, FileID *file_id) { return buf->file_id_valid - && os_file_id_equal(&(buf->file_id), file_id); + && os_fileid_equal(&(buf->file_id), file_id); } /* @@ -3601,7 +3604,6 @@ do_arg_all ( ) { int i; - win_T *wp, *wpnext; char_u *opened; /* Array of weight for which args are open: * 0: not opened * 1: opened in other tab @@ -3651,8 +3653,9 @@ do_arg_all ( if (had_tab > 0) goto_tabpage_tp(first_tabpage, TRUE, TRUE); for (;; ) { + win_T *wpnext = NULL; tpnext = curtab->tp_next; - for (wp = firstwin; wp != NULL; wp = wpnext) { + for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { wpnext = wp->w_next; buf = wp->w_buffer; if (buf->b_ffname == NULL @@ -3762,13 +3765,14 @@ do_arg_all ( if (opened[i] > 0) { /* Move the already present window to below the current window */ if (curwin->w_arg_idx != i) { - for (wpnext = firstwin; wpnext != NULL; wpnext = wpnext->w_next) { - if (wpnext->w_arg_idx == i) { + FOR_ALL_WINDOWS(wp) { + if (wp->w_arg_idx == i) { if (keep_tabs) { - new_curwin = wpnext; + new_curwin = wp; new_curtab = curtab; - } else - win_move_after(wpnext, curwin); + } else { + win_move_after(wp, curwin); + } break; } } @@ -4421,8 +4425,8 @@ linenr_T buf_delsign( } /* When deleted the last sign needs to redraw the windows to remove the - * sign column. */ - if (buf->b_signlist == NULL) { + * sign column. Not when curwin is NULL (this means we're exiting). */ + if (buf->b_signlist != NULL && curwin != NULL) { redraw_buf_later(buf, NOT_VALID); changed_cline_bef_curs(); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 4162df63ab..84d55fb730 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -255,6 +255,7 @@ struct wininfo_S { typedef struct arglist { garray_T al_ga; /* growarray with the array of file names */ int al_refcount; /* number of windows using this arglist */ + int id; ///< id of this arglist } alist_T; /* @@ -512,7 +513,7 @@ struct file_buffer { long b_mtime; /* last change time of original file */ long b_mtime_read; /* last change time when reading */ - off_t b_orig_size; /* size of original file in bytes */ + uint64_t b_orig_size; /* size of original file in bytes */ int b_orig_mode; /* mode of original file */ pos_T b_namedm[NMARKS]; /* current named marks (mark.c) */ @@ -880,6 +881,28 @@ typedef struct { proftime_T tm; /* for a time limit */ } match_T; +/// number of positions supported by matchaddpos() +#define MAXPOSMATCH 8 + +/// Same as lpos_T, but with additional field len. +typedef struct +{ + linenr_T lnum; ///< line number + colnr_T col; ///< column number + int len; ///< length: 0 - to the end of line +} llpos_T; + +/// posmatch_T provides an array for storing match items for matchaddpos() +/// function. +typedef struct posmatch posmatch_T; +struct posmatch +{ + llpos_T pos[MAXPOSMATCH]; ///< array of positions + int cur; ///< internal position counter + linenr_T toplnum; ///< top buffer line + linenr_T botlnum; ///< bottom buffer line +}; + /* * matchitem_T provides a linked list for storing match items for ":match" and * the match functions. @@ -892,6 +915,7 @@ struct matchitem { char_u *pattern; /* pattern to highlight */ int hlg_id; /* highlight group ID */ regmmatch_T match; /* regexp program for pattern */ + posmatch_T pos; // position matches match_T hl; /* struct for doing the actual highlighting */ }; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index ab0c80112f..8f540fbe09 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -87,14 +87,14 @@ void diff_buf_adjust(win_T *win) if (!win->w_p_diff) { // When there is no window showing a diff for this buffer, remove // it from the diffs. - win_T *wp; - for (wp = firstwin; wp != NULL; wp = wp->w_next) { + bool found_win = false; + FOR_ALL_WINDOWS(wp) { if ((wp->w_buffer == win->w_buffer) && wp->w_p_diff) { - break; + found_win = true; } } - if (wp == NULL) { + if (!found_win) { int i = diff_buf_idx(win->w_buffer); if (i != DB_COUNT) { curtab->tp_diffbuf[i] = NULL; @@ -581,8 +581,7 @@ static int diff_check_sanity(tabpage_T *tp, diff_T *dp) /// @param dofold Also recompute the folds static void diff_redraw(int dofold) { - win_T *wp; - for (wp = firstwin; wp != NULL; wp = wp->w_next) { + FOR_ALL_WINDOWS(wp) { if (wp->w_p_diff) { redraw_win_later(wp, SOME_VALID); if (dofold && foldmethodIsDiff(wp)) { @@ -947,9 +946,10 @@ void ex_diffpatch(exarg_T *eap) os_remove((char *)buf); // Only continue if the output file was created. - off_t file_size; - bool file_size_success = os_get_file_size((char *)tmp_new, &file_size); - if (!file_size_success || file_size == 0) { + FileInfo file_info; + bool info_ok = os_fileinfo((char *)tmp_new, &file_info); + uint64_t filesize = os_fileinfo_size(&file_info); + if (!info_ok || filesize == 0) { EMSG(_("E816: Cannot read patch output")); } else { if (curbuf->b_fname != NULL) { @@ -1110,8 +1110,7 @@ void ex_diffoff(exarg_T *eap) win_T *old_curwin = curwin; int diffwin = FALSE; - win_T *wp; - for (wp = firstwin; wp != NULL; wp = wp->w_next) { + FOR_ALL_WINDOWS(wp) { if (eap->forceit ? wp->w_p_diff : (wp == curwin)) { // Set 'diff', 'scrollbind' off and 'wrap' on. If option values // were saved in diff_win_options() restore them. @@ -2363,10 +2362,8 @@ void ex_diffgetput(exarg_T *eap) /// @param skip_idx static void diff_fold_update(diff_T *dp, int skip_idx) { - win_T *wp; - for (wp = firstwin; wp != NULL; wp = wp->w_next) { - int i; - for (i = 0; i < DB_COUNT; ++i) { + FOR_ALL_WINDOWS(wp) { + for (int i = 0; i < DB_COUNT; ++i) { if ((curtab->tp_diffbuf[i] == wp->w_buffer) && (i != skip_idx)) { foldUpdate(wp, dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i]); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 6158176e56..b3f4e4d449 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3798,6 +3798,8 @@ static void ins_compl_delete(void) */ i = compl_col + (compl_cont_status & CONT_ADDING ? compl_length : 0); backspace_until_column(i); + // TODO: is this sufficient for redrawing? Redrawing everything causes + // flicker, thus we can't do that. changed_cline_bef_curs(); } @@ -6917,8 +6919,9 @@ ins_esc ( State &= ~REPLACE_FLAG; (void)start_redo_ins(); - if (cmdchar == 'r' || cmdchar == 'v') - stuffReadbuff(ESC_STR); /* no ESC in redo buffer */ + if (cmdchar == 'r' || cmdchar == 'v') { + stuffRedoReadbuff(ESC_STR); // No ESC in redo buffer + } ++RedrawingDisabled; disabled_redraw = TRUE; return FALSE; /* repeat the insert */ diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 34af143446..7793f5040c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -84,7 +84,6 @@ #include "nvim/os/channel.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/os/dl.h" #include "nvim/os/provider.h" @@ -4758,7 +4757,7 @@ void listitem_free(listitem_T *item) */ void listitem_remove(list_T *l, listitem_T *item) { - list_remove(l, item, item); + vim_list_remove(l, item, item); listitem_free(item); } @@ -5231,30 +5230,29 @@ static list_T *list_copy(list_T *orig, int deep, int copyID) return copy; } -/* - * Remove items "item" to "item2" from list "l". - * Does not free the listitem or the value! - */ -void list_remove(list_T *l, listitem_T *item, listitem_T *item2) +/// Remove items "item" to "item2" from list "l". +/// @warning Does not free the listitem or the value! +void vim_list_remove(list_T *l, listitem_T *item, listitem_T *item2) { - listitem_T *ip; - - /* notify watchers */ - for (ip = item; ip != NULL; ip = ip->li_next) { + // notify watchers + for (listitem_T *ip = item; ip != NULL; ip = ip->li_next) { --l->lv_len; list_fix_watch(l, ip); - if (ip == item2) + if (ip == item2) { break; + } } - if (item2->li_next == NULL) + if (item2->li_next == NULL) { l->lv_last = item->li_prev; - else + } else { item2->li_next->li_prev = item->li_prev; - if (item->li_prev == NULL) + } + if (item->li_prev == NULL) { l->lv_first = item2->li_next; - else + } else { item->li_prev->li_next = item2->li_next; + } l->lv_idx_item = NULL; } @@ -6311,6 +6309,7 @@ static struct fst { {"append", 2, 2, f_append}, {"argc", 0, 0, f_argc}, {"argidx", 0, 0, f_argidx}, + {"arglistid", 0, 2, f_arglistid}, {"argv", 0, 1, f_argv}, {"asin", 1, 1, f_asin}, /* WJMc */ {"atan", 1, 1, f_atan}, @@ -6436,9 +6435,9 @@ static struct fst { {"isdirectory", 1, 1, f_isdirectory}, {"islocked", 1, 1, f_islocked}, {"items", 1, 1, f_items}, - {"jobstart", 2, 3, f_job_start}, - {"jobstop", 1, 1, f_job_stop}, - {"jobwrite", 2, 2, f_job_write}, + {"jobsend", 2, 2, f_jobsend}, + {"jobstart", 2, 3, f_jobstart}, + {"jobstop", 1, 1, f_jobstop}, {"join", 1, 2, f_join}, {"keys", 1, 1, f_keys}, {"last_buffer_nr", 0, 0, f_last_buffer_nr}, /* obsolete */ @@ -6456,6 +6455,7 @@ static struct fst { {"mapcheck", 1, 3, f_mapcheck}, {"match", 2, 4, f_match}, {"matchadd", 2, 4, f_matchadd}, + {"matchaddpos", 2, 4, f_matchaddpos}, {"matcharg", 1, 1, f_matcharg}, {"matchdelete", 1, 1, f_matchdelete}, {"matchend", 2, 4, f_matchend}, @@ -6484,6 +6484,10 @@ static struct fst { {"resolve", 1, 1, f_resolve}, {"reverse", 1, 1, f_reverse}, {"round", 1, 1, f_round}, + {"rpcnotify", 2, 64, f_rpcnotify}, + {"rpcrequest", 2, 64, f_rpcrequest}, + {"rpcstart", 1, 2, f_rpcstart}, + {"rpcstop", 1, 1, f_rpcstop}, {"screenattr", 2, 2, f_screenattr}, {"screenchar", 2, 2, f_screenchar}, {"screencol", 0, 0, f_screencol}, @@ -6493,8 +6497,6 @@ static struct fst { {"searchpair", 3, 7, f_searchpair}, {"searchpairpos", 3, 7, f_searchpairpos}, {"searchpos", 1, 4, f_searchpos}, - {"send_call", 2, 64, f_send_call}, - {"send_event", 2, 64, f_send_event}, {"setbufvar", 3, 3, f_setbufvar}, {"setcmdpos", 1, 1, f_setcmdpos}, {"setline", 2, 2, f_setline}, @@ -7125,6 +7127,32 @@ static void f_argidx(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = curwin->w_arg_idx; } +/// "arglistid" function +static void f_arglistid(typval_T *argvars, typval_T *rettv) +{ + rettv->vval.v_number = -1; + if (argvars[0].v_type != VAR_UNKNOWN) { + tabpage_T *tp = NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + long n = get_tv_number(&argvars[1]); + if (n >= 0) { + tp = find_tabpage(n); + } + } else { + tp = curtab; + } + + if (tp != NULL) { + win_T *wp = find_win_by_nr(&argvars[0], tp); + if (wp != NULL) { + rettv->vval.v_number = wp->w_alist->id; + } + } + } else { + rettv->vval.v_number = curwin->w_alist->id; + } +} + /* * "argv(nr)" function */ @@ -7360,19 +7388,20 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv) */ static void f_bufwinnr(typval_T *argvars, typval_T *rettv) { - win_T *wp; - int winnr = 0; - buf_T *buf; - (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ ++emsg_off; - buf = get_buf_tv(&argvars[0], TRUE); - for (wp = firstwin; wp; wp = wp->w_next) { + + buf_T *buf = get_buf_tv(&argvars[0], TRUE); + int winnr = 0; + bool found_buf = false; + FOR_ALL_WINDOWS(wp) { ++winnr; - if (wp->w_buffer == buf) + if (wp->w_buffer == buf) { + found_buf = true; break; + } } - rettv->vval.v_number = (wp != NULL ? winnr : -1); + rettv->vval.v_number = (found_buf ? winnr : -1); --emsg_off; } @@ -9165,15 +9194,16 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv) rettv->v_type = VAR_NUMBER; - off_t file_size; - if (os_get_file_size(fname, &file_size)) { + FileInfo file_info; + if (os_fileinfo(fname, &file_info)) { + uint64_t filesize = os_fileinfo_size(&file_info); if (os_isdir((char_u *)fname)) rettv->vval.v_number = 0; else { - rettv->vval.v_number = (varnumber_T)file_size; + rettv->vval.v_number = (varnumber_T)filesize; /* non-perfect check for overflow */ - if ((off_t)rettv->vval.v_number != file_size) { + if ((uint64_t)rettv->vval.v_number != filesize) { rettv->vval.v_number = -2; } } @@ -9190,7 +9220,7 @@ static void f_getftime(typval_T *argvars, typval_T *rettv) char *fname = (char *)get_tv_string(&argvars[0]); FileInfo file_info; - if (os_get_file_info(fname, &file_info)) { + if (os_fileinfo(fname, &file_info)) { rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; } else { rettv->vval.v_number = -1; @@ -9210,7 +9240,7 @@ static void f_getftype(typval_T *argvars, typval_T *rettv) rettv->v_type = VAR_STRING; FileInfo file_info; - if (os_get_file_info_link((char *)fname, &file_info)) { + if (os_fileinfo_link((char *)fname, &file_info)) { uint64_t mode = file_info.stat.st_mode; #ifdef S_ISREG if (S_ISREG(mode)) @@ -9300,12 +9330,34 @@ static void f_getline(typval_T *argvars, typval_T *rettv) static void f_getmatches(typval_T *argvars, typval_T *rettv) { matchitem_T *cur = curwin->w_match_head; + int i; rettv_list_alloc(rettv); while (cur != NULL) { dict_T *dict = dict_alloc(); + if (cur->match.regprog == NULL) { + // match added with matchaddpos() + for (i = 0; i < MAXPOSMATCH; ++i) { + llpos_T *llpos; + char buf[6]; + + llpos = &cur->pos.pos[i]; + if (llpos->lnum == 0) { + break; + } + list_T *l = list_alloc(); + list_append_number(l, (varnumber_T)llpos->lnum); + if (llpos->col > 0) { + list_append_number(l, (varnumber_T)llpos->col); + list_append_number(l, (varnumber_T)llpos->len); + } + sprintf(buf, "pos%d", i + 1); + dict_add_list(dict, buf, l); + } + } else { + dict_add_nr_str(dict, "pattern", 0L, cur->pattern); + } dict_add_nr_str(dict, "group", 0L, syn_id2name(cur->hlg_id)); - dict_add_nr_str(dict, "pattern", 0L, cur->pattern); dict_add_nr_str(dict, "priority", (long)cur->priority, NULL); dict_add_nr_str(dict, "id", (long)cur->id, NULL); list_append_dict(rettv->vval.v_list, dict); @@ -9790,7 +9842,7 @@ static void f_has(typval_T *argvars, typval_T *rettv) "windows", "winaltkeys", "writebackup", - "neovim", + "nvim", NULL }; @@ -10456,8 +10508,40 @@ static void f_items(typval_T *argvars, typval_T *rettv) dict_list(argvars, rettv, 2); } +// "jobsend()" function +static void f_jobsend(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_STRING) { + // First argument is the job id and second is the string to write to + // the job's stdin + EMSG(_(e_invarg)); + return; + } + + Job *job = job_find(argvars[0].vval.v_number); + + if (!job) { + // Invalid job id + EMSG(_(e_invjob)); + return; + } + + WBuffer *buf = wstream_new_buffer(xstrdup((char *)argvars[1].vval.v_string), + strlen((char *)argvars[1].vval.v_string), + 1, + free); + rettv->vval.v_number = job_write(job, buf); +} + // "jobstart()" function -static void f_job_start(typval_T *argvars, typval_T *rettv) +static void f_jobstart(typval_T *argvars, typval_T *rettv) { list_T *args = NULL; listitem_T *arg; @@ -10535,7 +10619,7 @@ static void f_job_start(typval_T *argvars, typval_T *rettv) } // "jobstop()" function -static void f_job_stop(typval_T *argvars, typval_T *rettv) +static void f_jobstop(typval_T *argvars, typval_T *rettv) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -10562,38 +10646,6 @@ static void f_job_stop(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = 1; } -// "jobwrite()" function -static void f_job_write(typval_T *argvars, typval_T *rettv) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_STRING) { - // First argument is the job id and second is the string to write to - // the job's stdin - EMSG(_(e_invarg)); - return; - } - - Job *job = job_find(argvars[0].vval.v_number); - - if (!job) { - // Invalid job id - EMSG(_(e_invjob)); - return; - } - - WBuffer *buf = wstream_new_buffer(xstrdup((char *)argvars[1].vval.v_string), - strlen((char *)argvars[1].vval.v_string), - 1, - free); - rettv->vval.v_number = job_write(job, buf); -} - /* * "join()" function */ @@ -11104,7 +11156,52 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv) return; } - rettv->vval.v_number = match_add(curwin, grp, pat, prio, id); + rettv->vval.v_number = match_add(curwin, grp, pat, prio, id, NULL); +} + +static void f_matchaddpos(typval_T *argvars, typval_T *rettv) FUNC_ATTR_NONNULL_ALL +{ + rettv->vval.v_number = -1; + + char_u buf[NUMBUFLEN]; + char_u *group; + group = get_tv_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } + + if (argvars[1].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "matchaddpos()"); + return; + } + + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } + + int error = false; + int prio = 10; + int id = -1; + + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = get_tv_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = get_tv_number_chk(&argvars[3], &error); + } + } + if (error == true) { + return; + } + + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + EMSGN("E798: ID is reserved for \"match\": %" PRId64, id); + return; + } + + rettv->vval.v_number = match_add(curwin, group, NULL, prio, id, l); } /* @@ -11832,8 +11929,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv) EMSGN(_(e_listidx), idx); else { if (argvars[2].v_type == VAR_UNKNOWN) { - /* Remove one item, return its value. */ - list_remove(l, item, item); + // Remove one item, return its value. + vim_list_remove(l, item, item); *rettv = item->li_tv; free(item); } else { @@ -11854,7 +11951,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv) if (li == NULL) /* didn't find "item2" after "item" */ EMSG(_(e_invrange)); else { - list_remove(l, item, item2); + vim_list_remove(l, item, item2); rettv_list_alloc(rettv); l = rettv->vval.v_list; l->lv_first = item; @@ -12273,6 +12370,169 @@ static void f_round(typval_T *argvars, typval_T *rettv) rettv->vval.v_float = 0.0; } +// "rpcnotify()" function +static void f_rpcnotify(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) { + EMSG2(_(e_invarg2), "Channel id must be a positive integer"); + return; + } + + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "Event type must be a string"); + return; + } + + Array args = ARRAY_DICT_INIT; + + for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { + ADD(args, vim_to_object(tv)); + } + + if (!channel_send_event((uint64_t)argvars[0].vval.v_number, + (char *)argvars[1].vval.v_string, + args)) { + EMSG2(_(e_invarg2), "Channel doesn't exist"); + return; + } + + rettv->vval.v_number = 1; +} + +// "rpcrequest()" function +static void f_rpcrequest(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) { + EMSG2(_(e_invarg2), "Channel id must be a positive integer"); + return; + } + + if (argvars[1].v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "Method name must be a string"); + return; + } + + Array args = ARRAY_DICT_INIT; + + for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { + ADD(args, vim_to_object(tv)); + } + + bool errored; + Object result; + if (!channel_send_call((uint64_t)argvars[0].vval.v_number, + (char *)argvars[1].vval.v_string, + args, + &result, + &errored)) { + EMSG2(_(e_invarg2), "Channel doesn't exist"); + return; + } + + if (errored) { + vim_report_error(result.data.string); + goto end; + } + + Error conversion_error = {.set = false}; + if (!object_to_vim(result, rettv, &conversion_error)) { + EMSG(_("Error converting the call result")); + } + +end: + api_free_object(result); +} + +// "rpcstart()" function +static void f_rpcstart(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_STRING + || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) { + // Wrong argument types + EMSG(_(e_invarg)); + return; + } + + list_T *args = NULL; + int argsl = 0; + if (argvars[1].v_type == VAR_LIST) { + args = argvars[1].vval.v_list; + argsl = args->lv_len; + // Assert that all list items are strings + for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { + if (arg->li_tv.v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + } + } + + // Allocate extra memory for the argument vector and the NULL pointer + int argvl = argsl + 2; + char **argv = xmalloc(sizeof(char_u *) * argvl); + + // Copy program name + argv[0] = xstrdup((char *)argvars[0].vval.v_string); + + int i = 1; + // Copy arguments to the vector + if (argsl > 0) { + for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { + argv[i++] = xstrdup((char *)arg->li_tv.vval.v_string); + } + } + + // The last item of argv must be NULL + argv[i] = NULL; + uint64_t channel_id = channel_from_job(argv); + + if (!channel_id) { + EMSG(_(e_api_spawn_failed)); + } + + rettv->vval.v_number = (varnumber_T)channel_id; +} + +// "rpcstop()" function +static void f_rpcstop(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER) { + // Wrong argument types + EMSG(_(e_invarg)); + return; + } + + rettv->vval.v_number = channel_close(argvars[0].vval.v_number); +} + /* * "screenattr()" function */ @@ -12612,89 +12872,6 @@ do_searchpair ( return retval; } -// "send_call()" function -static void f_send_call(typval_T *argvars, typval_T *rettv) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) { - EMSG2(_(e_invarg2), "Channel id must be a positive integer"); - return; - } - - if (argvars[1].v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "Method name must be a string"); - return; - } - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - bool errored; - Object result; - if (!channel_send_call((uint64_t)argvars[0].vval.v_number, - (char *)argvars[1].vval.v_string, - args, - &result, - &errored)) { - EMSG2(_(e_invarg2), "Channel doesn't exist"); - return; - } - - Error conversion_error = {.set = false}; - if (errored || !object_to_vim(result, rettv, &conversion_error)) { - EMSG(errored ? - result.data.string.data : - _("Error converting the call result")); - } - - msgpack_rpc_free_object(result); -} - -// "send_event()" function -static void f_send_event(typval_T *argvars, typval_T *rettv) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) { - EMSG2(_(e_invarg2), "Channel id must be a positive integer"); - return; - } - - if (argvars[1].v_type != VAR_STRING) { - EMSG2(_(e_invarg2), "Event type must be a string"); - return; - } - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - if (!channel_send_event((uint64_t)argvars[0].vval.v_number, - (char *)argvars[1].vval.v_string, - args)) { - EMSG2(_(e_invarg2), "Channel doesn't exist"); - return; - } - - rettv->vval.v_number = 1; -} - /* * "searchpos()" function */ @@ -12926,7 +13103,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv) match_add(curwin, get_dict_string(d, (char_u *)"group", FALSE), get_dict_string(d, (char_u *)"pattern", FALSE), (int)get_dict_number(d, (char_u *)"priority"), - (int)get_dict_number(d, (char_u *)"id")); + (int)get_dict_number(d, (char_u *)"id"), NULL); li = li->li_next; } rettv->vval.v_number = 0; @@ -13209,11 +13386,18 @@ static void f_sinh(typval_T *argvars, typval_T *rettv) rettv->vval.v_float = 0.0; } +/// struct used in the array that's given to qsort() +typedef struct { + listitem_T *item; + int idx; +} sortItem_T; static int item_compare_ic; +static bool item_compare_numeric; static char_u *item_compare_func; static dict_T *item_compare_selfdict; static int item_compare_func_err; +static bool item_compare_keep_zero; #define ITEM_COMPARE_FAIL 999 /* @@ -13221,22 +13405,40 @@ static int item_compare_func_err; */ static int item_compare(const void *s1, const void *s2) { + sortItem_T *si1, *si2; char_u *p1, *p2; char_u *tofree1, *tofree2; int res; char_u numbuf1[NUMBUFLEN]; char_u numbuf2[NUMBUFLEN]; - p1 = tv2string(&(*(listitem_T **)s1)->li_tv, &tofree1, numbuf1, 0); - p2 = tv2string(&(*(listitem_T **)s2)->li_tv, &tofree2, numbuf2, 0); + si1 = (sortItem_T *)s1; + si2 = (sortItem_T *)s2; + p1 = tv2string(&si1->item->li_tv, &tofree1, numbuf1, 0); + p2 = tv2string(&si2->item->li_tv, &tofree2, numbuf2, 0); if (p1 == NULL) p1 = (char_u *)""; if (p2 == NULL) p2 = (char_u *)""; - if (item_compare_ic) - res = STRICMP(p1, p2); - else - res = STRCMP(p1, p2); + if (!item_compare_numeric) { + if (item_compare_ic) { + res = STRICMP(p1, p2); + } else { + res = STRCMP(p1, p2); + } + } else { + double n1, n2; + n1 = strtod((char *)p1, (char **)&p1); + n2 = strtod((char *)p2, (char **)&p2); + res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1; + } + + // When the result would be zero, compare the pointers themselves. Makes + // the sort stable. + if (res == 0 && !item_compare_keep_zero) { + res = si1->idx > si2->idx ? 1 : -1; + } + free(tofree1); free(tofree2); return res; @@ -13244,6 +13446,7 @@ static int item_compare(const void *s1, const void *s2) static int item_compare2(const void *s1, const void *s2) { + sortItem_T *si1, *si2; int res; typval_T rettv; typval_T argv[3]; @@ -13253,10 +13456,13 @@ static int item_compare2(const void *s1, const void *s2) if (item_compare_func_err) return 0; - /* copy the values. This is needed to be able to set v_lock to VAR_FIXED - * in the copy without changing the original list items. */ - copy_tv(&(*(listitem_T **)s1)->li_tv, &argv[0]); - copy_tv(&(*(listitem_T **)s2)->li_tv, &argv[1]); + si1 = (sortItem_T *)s1; + si2 = (sortItem_T *)s2; + + // Copy the values. This is needed to be able to set v_lock to VAR_FIXED + // in the copy without changing the original list items. + copy_tv(&si1->item->li_tv, &argv[0]); + copy_tv(&si2->item->li_tv, &argv[1]); rettv.v_type = VAR_UNKNOWN; /* clear_tv() uses this */ res = call_func(item_compare_func, (int)STRLEN(item_compare_func), @@ -13272,6 +13478,13 @@ static int item_compare2(const void *s1, const void *s2) if (item_compare_func_err) res = ITEM_COMPARE_FAIL; /* return value has wrong type */ clear_tv(&rettv); + + // When the result would be zero, compare the pointers themselves. Makes + // the sort stable. + if (res == 0 && !item_compare_keep_zero) { + res = si1->idx > si2->idx ? 1 : -1; + } + return res; } @@ -13282,7 +13495,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { list_T *l; listitem_T *li; - listitem_T **ptrs; + sortItem_T *ptrs; long len; long i; @@ -13303,6 +13516,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) return; /* short list sorts pretty quickly */ item_compare_ic = FALSE; + item_compare_numeric = false; item_compare_func = NULL; item_compare_selfdict = NULL; @@ -13320,6 +13534,15 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) item_compare_ic = TRUE; else item_compare_func = get_tv_string(&argvars[1]); + if (item_compare_func != NULL) { + if (STRCMP(item_compare_func, "n") == 0) { + item_compare_func = NULL; + item_compare_numeric = true; + } else if (STRCMP(item_compare_func, "i") == 0) { + item_compare_func = NULL; + item_compare_ic = TRUE; + } + } } if (argvars[2].v_type != VAR_UNKNOWN) { @@ -13333,23 +13556,26 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } /* Make an array with each entry pointing to an item in the List. */ - ptrs = xmalloc((size_t)(len * sizeof (listitem_T *))); + ptrs = xmalloc((size_t)(len * sizeof (sortItem_T))); i = 0; if (sort) { // sort(): ptrs will be the list to sort. for (li = l->lv_first; li != NULL; li = li->li_next) { - ptrs[i++] = li; + ptrs[i].item = li; + ptrs[i].idx = i; + i++; } item_compare_func_err = FALSE; + item_compare_keep_zero = false; // Test the compare function. if (item_compare_func != NULL && item_compare2(&ptrs[0], &ptrs[1]) == ITEM_COMPARE_FAIL) { EMSG(_("E702: Sort compare function failed")); } else { // Sort the array with item pointers. - qsort(ptrs, (size_t)len, sizeof (listitem_T *), + qsort(ptrs, (size_t)len, sizeof (sortItem_T), item_compare_func == NULL ? item_compare : item_compare2); if (!item_compare_func_err) { @@ -13360,7 +13586,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) l->lv_len = 0; for (i = 0; i < len; i++) { - list_append(l, ptrs[i]); + list_append(l, ptrs[i].item); } } } @@ -13369,11 +13595,12 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) // f_uniq(): ptrs will be a stack of items to remove. item_compare_func_err = FALSE; + item_compare_keep_zero = true; item_compare_func_ptr = item_compare_func ? item_compare2 : item_compare; for (li = l->lv_first; li != NULL && li->li_next != NULL; li = li->li_next) { if (item_compare_func_ptr(&li, &li->li_next) == 0) { - ptrs[i++] = li; + ptrs[i++].item = li; } if (item_compare_func_err) { EMSG(_("E882: Uniq compare function failed")); @@ -13383,12 +13610,12 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (!item_compare_func_err) { while (--i >= 0) { - li = ptrs[i]->li_next; - ptrs[i]->li_next = li->li_next; + li = ptrs[i].item->li_next; + ptrs[i].item->li_next = li->li_next; if (li->li_next != NULL) { - li->li_next->li_prev = ptrs[i]; + li->li_next->li_prev = ptrs[i].item; } else { - l->lv_last = ptrs[i]; + l->lv_last = ptrs[i].item; } list_fix_watch(l, li); listitem_free(li); @@ -14681,13 +14908,12 @@ static void f_winnr(typval_T *argvars, typval_T *rettv) */ static void f_winrestcmd(typval_T *argvars, typval_T *rettv) { - win_T *wp; int winnr = 1; garray_T ga; char_u buf[50]; ga_init(&ga, (int)sizeof(char), 70); - for (wp = firstwin; wp != NULL; wp = wp->w_next) { + FOR_ALL_WINDOWS(wp) { sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); ga_concat(&ga, buf); sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); @@ -19171,7 +19397,7 @@ static void script_host_eval(char *method, typval_T *argvars, typval_T *rettv) Error err = {.set = false}; object_to_vim(result, rettv, &err); - msgpack_rpc_free_object(result); + api_free_object(result); if (err.set) { EMSG("Error converting value back to vim"); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 5b6604fc93..b72d1941ec 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1533,7 +1533,7 @@ void write_viminfo(char_u *file, int forceit) */ FileInfo old_info; // FileInfo of existing viminfo file - if (os_get_file_info((char *)fname, &old_info) + if (os_fileinfo((char *)fname, &old_info) && getuid() != ROOT_UID && !(old_info.stat.st_uid == getuid() ? (old_info.stat.st_mode & 0200) @@ -4610,19 +4610,19 @@ prepare_tagpreview ( bool undo_sync /* sync undo when leaving the window */ ) { - win_T *wp; - - /* * If there is already a preview window open, use that one. */ if (!curwin->w_p_pvw) { - for (wp = firstwin; wp != NULL; wp = wp->w_next) - if (wp->w_p_pvw) + bool found_win = false; + FOR_ALL_WINDOWS(wp) { + if (wp->w_p_pvw) { + win_enter(wp, undo_sync); + found_win = true; break; - if (wp != NULL) - win_enter(wp, undo_sync); - else { + } + } + if (!found_win) { /* * There is no preview window open yet. Create one. */ diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 3809858875..c3d34e9991 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -55,7 +55,6 @@ #include "nvim/os/shell.h" #include "nvim/os/fs_defs.h" #include "nvim/os/provider.h" -#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" @@ -1206,7 +1205,6 @@ check_changed_any ( int bufcount = 0; int *bufnrs; tabpage_T *tp; - win_T *wp; FOR_ALL_BUFFERS(buf) { ++bufcount; @@ -1220,15 +1218,21 @@ check_changed_any ( /* curbuf */ bufnrs[bufnum++] = curbuf->b_fnum; /* buf in curtab */ - FOR_ALL_WINDOWS(wp) - if (wp->w_buffer != curbuf) - add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer != curbuf) { + add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); + } + } /* buf in other tab */ - for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) - if (tp != curtab) - for (wp = tp->tp_firstwin; wp != NULL; wp = wp->w_next) + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + if (tp != curtab) { + for (win_T *wp = tp->tp_firstwin; wp != NULL; wp = wp->w_next) { add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); + } + } + } + /* any other buf */ FOR_ALL_BUFFERS(buf) { add_bufnum(bufnrs, &bufnum, buf->b_fnum); @@ -1277,16 +1281,19 @@ check_changed_any ( } /* Try to find a window that contains the buffer. */ - if (buf != curbuf) - FOR_ALL_TAB_WINDOWS(tp, wp) - if (wp->w_buffer == buf) { - goto_tabpage_win(tp, wp); - /* Paranoia: did autocms wipe out the buffer with changes? */ - if (!buf_valid(buf)) { - goto theend; + if (buf != curbuf) { + win_T *wp; + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + goto_tabpage_win(tp, wp); + /* Paranoia: did autocms wipe out the buffer with changes? */ + if (!buf_valid(buf)) { + goto theend; + } + goto buf_found; } - goto buf_found; } + } buf_found: /* Open the changed buffer in the current window. */ @@ -1907,7 +1914,7 @@ void ex_listdo(exarg_T *eap) break; } } - if (buf_still_exists) { + if (!buf_still_exists) { break; } @@ -2417,13 +2424,13 @@ do_source ( */ save_current_SID = current_SID; FileID file_id; - bool file_id_ok = os_get_file_id((char *)fname_exp, &file_id); + bool file_id_ok = os_fileid((char *)fname_exp, &file_id); for (current_SID = script_items.ga_len; current_SID > 0; --current_SID) { si = &SCRIPT_ITEM(current_SID); // Compare dev/ino when possible, it catches symbolic links. // Also compare file names, the inode may change when the file was edited. bool file_id_equal = file_id_ok && si->file_id_valid - && os_file_id_equal(&(si->file_id), &file_id); + && os_fileid_equal(&(si->file_id), &file_id); if (si->sn_name != NULL && (file_id_equal || fnamecmp(si->sn_name, fname_exp) == 0)) { break; @@ -3256,7 +3263,7 @@ static void script_host_execute(char *method, exarg_T *eap) Object result = provider_call(method, args); // We don't care about the result, so free it just in case a bad provider // returned something - msgpack_rpc_free_object(result); + api_free_object(result); } free(script); @@ -3270,7 +3277,7 @@ static void script_host_execute_file(char *method, exarg_T *eap) Array args = ARRAY_DICT_INIT; ADD(args, STRING_OBJ(cstr_to_string(buffer))); Object result = provider_call(method, args); - msgpack_rpc_free_object(result); + api_free_object(result); } static void script_host_do_range(char *method, exarg_T *eap) @@ -3280,6 +3287,6 @@ static void script_host_do_range(char *method, exarg_T *eap) ADD(args, INTEGER_OBJ(eap->line2)); ADD(args, STRING_OBJ(cstr_to_string((char *)eap->arg))); Object result = provider_call(method, args); - msgpack_rpc_free_object(result); + api_free_object(result); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ab7add1c5b..1117b6fbcf 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5188,13 +5188,12 @@ static void ex_close(exarg_T *eap) */ static void ex_pclose(exarg_T *eap) { - win_T *win; - - for (win = firstwin; win != NULL; win = win->w_next) + FOR_ALL_WINDOWS(win) { if (win->w_p_pvw) { ex_win_close(eap->forceit, win, NULL); break; } + } } /* @@ -5505,6 +5504,7 @@ void alist_new(void) { curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); curwin->w_alist->al_refcount = 1; + curwin->w_alist->id = ++max_alist_id; alist_init(curwin->w_alist); } @@ -6119,7 +6119,6 @@ static void ex_swapname(exarg_T *eap) */ static void ex_syncbind(exarg_T *eap) { - win_T *wp; win_T *save_curwin = curwin; buf_T *save_curbuf = curbuf; long topline; @@ -6133,15 +6132,17 @@ static void ex_syncbind(exarg_T *eap) */ if (curwin->w_p_scb) { topline = curwin->w_topline; - for (wp = firstwin; wp; wp = wp->w_next) { + FOR_ALL_WINDOWS(wp) { if (wp->w_p_scb && wp->w_buffer) { y = wp->w_buffer->b_ml.ml_line_count - p_so; - if (topline > y) + if (topline > y) { topline = y; + } } } - if (topline < 1) + if (topline < 1) { topline = 1; + } } else { topline = 1; } @@ -8859,7 +8860,7 @@ static void ex_match(exarg_T *eap) c = *end; *end = NUL; - match_add(curwin, g, p + 1, 10, id); + match_add(curwin, g, p + 1, 10, id, NULL); free(g); *end = c; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 69fb1d344a..810df627c1 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -287,6 +287,14 @@ getcmdline ( do_digraph(-1); /* init digraph typeahead */ + // If something above caused an error, reset the flags, we do want to type + // and execute commands. Display may be messed up a bit. + if (did_emsg) { + redrawcmd(); + } + did_emsg = FALSE; + got_int = FALSE; + /* * Collect the command string, handling editing keys. */ diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index c7e1f5cbbc..955b0b0a68 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1097,7 +1097,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * url = true; } else { ff_expand_buffer[0] = NUL; - if (!os_get_file_id((char *)fname, &file_id)) { + if (!os_fileid((char *)fname, &file_id)) { return FAIL; } } @@ -1106,7 +1106,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) { if ((url && fnamecmp(vp->ffv_fname, ff_expand_buffer) == 0) || (!url && vp->file_id_valid - && os_file_id_equal(&(vp->file_id), &file_id))) { + && os_fileid_equal(&(vp->file_id), &file_id))) { /* are the wildcard parts equal */ if (ff_wc_equal(vp->ffv_wc_path, wc_path) == TRUE) /* already visited */ diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 2e932e9695..97daa035f8 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -472,7 +472,7 @@ readfile ( if (newfile && !read_stdin && !read_buffer) { /* Remember time of file. */ FileInfo file_info; - if (os_get_file_info((char *)fname, &file_info)) { + if (os_fileinfo((char *)fname, &file_info)) { buf_store_file_info(curbuf, &file_info); curbuf->b_mtime_read = curbuf->b_mtime; #ifdef UNIX @@ -2583,7 +2583,7 @@ buf_write ( #if defined(UNIX) perm = -1; FileInfo file_info_old; - if (!os_get_file_info((char *)fname, &file_info_old)) { + if (!os_fileinfo((char *)fname, &file_info_old)) { newfile = TRUE; } else { perm = file_info_old.stat.st_mode; @@ -2629,7 +2629,7 @@ buf_write ( goto fail; } if (overwriting) { - os_get_file_info((char *)fname, &file_info_old); + os_fileinfo((char *)fname, &file_info_old); } } @@ -2712,9 +2712,9 @@ buf_write ( * - it's a symbolic link * - we don't have write permission in the directory */ - if (file_info_old.stat.st_nlink > 1 - || !os_get_file_info_link((char *)fname, &file_info) - || !os_file_info_id_equal(&file_info, &file_info_old)) { + if (os_fileinfo_hardlinks(&file_info_old) > 1 + || !os_fileinfo_link((char *)fname, &file_info) + || !os_fileinfo_id_equal(&file_info, &file_info_old)) { backup_copy = TRUE; } else # endif @@ -2728,7 +2728,7 @@ buf_write ( STRCPY(IObuff, fname); for (i = 4913;; i += 123) { sprintf((char *)path_tail(IObuff), "%d", i); - if (!os_get_file_info_link((char *)IObuff, &file_info)) { + if (!os_fileinfo_link((char *)IObuff, &file_info)) { break; } } @@ -2739,7 +2739,7 @@ buf_write ( else { # ifdef UNIX os_fchown(fd, file_info_old.stat.st_uid, file_info_old.stat.st_gid); - if (!os_get_file_info((char *)IObuff, &file_info) + if (!os_fileinfo((char *)IObuff, &file_info) || file_info.stat.st_uid != file_info_old.stat.st_uid || file_info.stat.st_gid != file_info_old.stat.st_gid || (long)file_info.stat.st_mode != perm) { @@ -2759,20 +2759,20 @@ buf_write ( */ if ((bkc_flags & BKC_BREAKSYMLINK) || (bkc_flags & BKC_BREAKHARDLINK)) { # ifdef UNIX - bool file_info_link_ok = os_get_file_info_link((char *)fname, &file_info); + bool file_info_link_ok = os_fileinfo_link((char *)fname, &file_info); /* Symlinks. */ if ((bkc_flags & BKC_BREAKSYMLINK) && file_info_link_ok - && !os_file_info_id_equal(&file_info, &file_info_old)) { + && !os_fileinfo_id_equal(&file_info, &file_info_old)) { backup_copy = FALSE; } /* Hardlinks. */ if ((bkc_flags & BKC_BREAKHARDLINK) - && file_info_old.stat.st_nlink > 1 + && os_fileinfo_hardlinks(&file_info_old) > 1 && (!file_info_link_ok - || os_file_info_id_equal(&file_info, &file_info_old))) { + || os_fileinfo_id_equal(&file_info, &file_info_old))) { backup_copy = FALSE; } # endif @@ -2837,14 +2837,14 @@ buf_write ( /* * Check if backup file already exists. */ - if (os_get_file_info((char *)backup, &file_info_new)) { + if (os_fileinfo((char *)backup, &file_info_new)) { /* * Check if backup file is same as original file. * May happen when modname() gave the same file back (e.g. silly * link). If we don't check here, we either ruin the file when * copying or erase it after writing. */ - if (os_file_info_id_equal(&file_info_new, &file_info_old)) { + if (os_fileinfo_id_equal(&file_info_new, &file_info_old)) { free(backup); backup = NULL; /* no backup file to delete */ } @@ -2861,7 +2861,7 @@ buf_write ( wp = backup; *wp = 'z'; while (*wp > 'a' - && os_get_file_info((char *)backup, &file_info_new)) { + && os_fileinfo((char *)backup, &file_info_new)) { --*wp; } /* They all exist??? Must be something wrong. */ @@ -3201,9 +3201,9 @@ nobackup: FileInfo file_info; /* Don't delete the file when it's a hard or symbolic link. */ - if ((!newfile && file_info_old.stat.st_nlink > 1) - || (os_get_file_info_link((char *)fname, &file_info) - && !os_file_info_id_equal(&file_info, &file_info_old))) { + if ((!newfile && os_fileinfo_hardlinks(&file_info) > 1) + || (os_fileinfo_link((char *)fname, &file_info) + && !os_fileinfo_id_equal(&file_info, &file_info_old))) { errmsg = (char_u *)_("E166: Can't open linked file for writing"); } else #endif @@ -3416,7 +3416,7 @@ restore_backup: /* don't change the owner when it's already OK, some systems remove * permission or ACL stuff */ FileInfo file_info; - if (!os_get_file_info((char *)wfname, &file_info) + if (!os_fileinfo((char *)wfname, &file_info) || file_info.stat.st_uid != file_info_old.stat.st_uid || file_info.stat.st_gid != file_info_old.stat.st_gid) { os_fchown(fd, file_info_old.stat.st_uid, file_info_old.stat.st_gid); @@ -3713,7 +3713,7 @@ nofail: /* Update the timestamp to avoid an "overwrite changed file" * prompt when writing again. */ - if (os_get_file_info((char *)fname, &file_info_old)) { + if (os_fileinfo((char *)fname, &file_info_old)) { buf_store_file_info(buf, &file_info_old); buf->b_mtime_read = buf->b_mtime; } @@ -4536,7 +4536,7 @@ int vim_rename(char_u *from, char_u *to) // Fail if the "from" file doesn't exist. Avoids that "to" is deleted. FileInfo from_info; - if (!os_get_file_info((char *)from, &from_info)) { + if (!os_fileinfo((char *)from, &from_info)) { return -1; } @@ -4544,8 +4544,8 @@ int vim_rename(char_u *from, char_u *to) // This happens when "from" and "to" differ in case and are on a FAT32 // filesystem. In that case go through a temp file name. FileInfo to_info; - if (os_get_file_info((char *)to, &to_info) - && os_file_info_id_equal(&from_info, &to_info)) { + if (os_fileinfo((char *)to, &to_info) + && os_fileinfo_id_equal(&from_info, &to_info)) { use_tmp_file = true; } @@ -4790,7 +4790,7 @@ buf_check_timestamp ( int helpmesg = FALSE; int reload = FALSE; int can_reload = FALSE; - off_t orig_size = buf->b_orig_size; + uint64_t orig_size = buf->b_orig_size; int orig_mode = buf->b_orig_mode; static int busy = FALSE; int n; @@ -4812,7 +4812,7 @@ buf_check_timestamp ( bool file_info_ok; if (!(buf->b_flags & BF_NOTEDITED) && buf->b_mtime != 0 - && (!(file_info_ok = os_get_file_info((char *)buf->b_ffname, &file_info)) + && (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info)) || time_differs((long)file_info.stat.st_mtim.tv_sec, buf->b_mtime) || (int)file_info.stat.st_mode != buf->b_orig_mode )) { @@ -5127,9 +5127,10 @@ void buf_reload(buf_T *buf, int orig_mode) } void buf_store_file_info(buf_T *buf, FileInfo *file_info) + FUNC_ATTR_NONNULL_ALL { buf->b_mtime = (long)file_info->stat.st_mtim.tv_sec; - buf->b_orig_size = file_info->stat.st_size; + buf->b_orig_size = os_fileinfo_size(file_info); buf->b_orig_mode = (int)file_info->stat.st_mode; } @@ -6176,12 +6177,17 @@ aucmd_prepbuf ( int save_acd; /* Find a window that is for the new buffer */ - if (buf == curbuf) /* be quick when buf is curbuf */ + if (buf == curbuf) { /* be quick when buf is curbuf */ win = curwin; - else - for (win = firstwin; win != NULL; win = win->w_next) - if (win->w_buffer == buf) + } else { + win = NULL; + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer == buf) { + win = wp; break; + } + } + } /* Allocate "aucmd_win" when needed. If this fails (out of memory) fall * back to using the current window. */ diff --git a/src/nvim/fold.c b/src/nvim/fold.c index f65bbc0875..186e75981e 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -460,13 +460,10 @@ void newFoldLevel(void) newFoldLevelWin(curwin); if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { - win_T *wp; - /* * Set the same foldlevel in other windows in diff mode. */ - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { wp->w_p_fdl = curwin->w_p_fdl; newFoldLevelWin(wp); @@ -1140,19 +1137,18 @@ setManualFold ( ) { if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { - win_T *wp; linenr_T dlnum; /* * Do the same operation in other windows in diff mode. Calculate the * line number from the diffs. */ - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { dlnum = diff_lnum_win(curwin->w_cursor.lnum, wp); - if (dlnum != 0) + if (dlnum != 0) { (void)setManualFoldWin(wp, dlnum, opening, recurse, NULL); + } } } } diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 6c772a8a66..0d61172d69 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -601,6 +601,13 @@ void stuffReadbuff(char_u *s) add_buff(&readbuf1, s, -1L); } +/// Append string "s" to the redo stuff buffer. +/// @remark CSI and K_SPECIAL must already have been escaped. +void stuffRedoReadbuff(char_u *s) +{ + add_buff(&readbuf2, s, -1L); +} + void stuffReadbuffLen(char_u *s, long len) { add_buff(&readbuf1, s, len); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 1ff8887598..674786ff08 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -519,7 +519,7 @@ EXTERN win_T *firstwin; /* first window */ EXTERN win_T *lastwin; /* last window */ EXTERN win_T *prevwin INIT(= NULL); /* previous window */ # define W_NEXT(wp) ((wp)->w_next) -# define FOR_ALL_WINDOWS(wp) for (wp = firstwin; wp != NULL; wp = wp->w_next) +# define FOR_ALL_WINDOWS(wp) for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) /* * When using this macro "break" only breaks out of the inner loop. Use "goto" * to break out of the tabpage loop. @@ -568,6 +568,7 @@ EXTERN int mf_dont_release INIT(= FALSE); /* don't release blocks */ * to this when the window is using the global argument list. */ EXTERN alist_T global_alist; /* global argument list */ +EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id EXTERN int arg_had_last INIT(= FALSE); /* accessed last file in global_alist */ @@ -1098,6 +1099,7 @@ EXTERN garray_T error_ga * Excluded are errors that are only used once and debugging messages. */ EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted")); +EXTERN char_u e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job")); EXTERN char_u e_argreq[] INIT(= N_("E471: Argument required")); EXTERN char_u e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &")); EXTERN char_u e_cmdwin[] INIT(= N_( diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 9e07a60ee1..667e6512f3 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -473,7 +473,7 @@ cs_add_common ( fname = (char *)vim_strnsave((char_u *)fname, len); free(fbuf); FileInfo file_info; - bool file_info_ok = os_get_file_info(fname, &file_info); + bool file_info_ok = os_fileinfo(fname, &file_info); if (!file_info_ok) { staterr: if (p_csverbose) @@ -504,7 +504,7 @@ staterr: else (void)sprintf(fname2, "%s/%s", fname, CSCOPE_DBFILE); - file_info_ok = os_get_file_info(fname2, &file_info); + file_info_ok = os_fileinfo(fname2, &file_info); if (!file_info_ok) { if (p_csverbose) cs_stat_emsg(fname2); @@ -1181,7 +1181,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags, i = -1; /* can be set to the index of an empty item in csinfo */ for (j = 0; j < csinfo_size; j++) { if (csinfo[j].fname != NULL - && os_file_id_equal_file_info(&(csinfo[j].file_id), file_info)) { + && os_fileid_equal_fileinfo(&(csinfo[j].file_id), file_info)) { if (p_csverbose) (void)EMSG(_("E568: duplicate cscope database not added")); return -1; @@ -1224,7 +1224,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags, } else csinfo[i].flags = NULL; - os_file_info_get_id(file_info, &(csinfo[i].file_id)); + os_fileinfo_id(file_info, &(csinfo[i].file_id)); return i; } /* cs_insert_filelist */ diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 9258ee93b6..7090e007bf 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -451,6 +451,7 @@ int get_breakindent_win(win_T *wp, char_u *line) { static int prev_indent = 0; /* cached indent value */ static int prev_ts = 0L; /* cached tabstop value */ static char_u *prev_line = NULL; /* cached pointer to line */ + static int prev_tick = 0; // changedtick of cached value int bri = 0; /* window width minus window margin space, i.e. what rests for text */ const int eff_wwidth = wp->w_width @@ -459,10 +460,11 @@ int get_breakindent_win(win_T *wp, char_u *line) { ? number_width(wp) + 1 : 0); /* used cached indent, unless pointer or 'tabstop' changed */ - if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts) - { + if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts + || prev_tick != wp->w_buffer->b_changedtick) { prev_line = line; prev_ts = wp->w_buffer->b_p_ts; + prev_tick = wp->w_buffer->b_changedtick; prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list); } diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 6e12194b63..509f94dbf2 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -2009,12 +2009,14 @@ int get_c_indent(void) * ldfd) { * } */ - if (curbuf->b_ind_js || (curbuf->b_ind_keep_case_label - && cin_iscase(skipwhite(get_cursor_line_ptr()), - FALSE))) + if ((curbuf->b_ind_js || curbuf->b_ind_keep_case_label) + && cin_iscase(skipwhite(get_cursor_line_ptr()), FALSE)) { amount = get_indent(); - else + } else if (curbuf->b_ind_js) { + amount = get_indent_lnum(lnum); + } else { amount = skip_label(lnum, &l); + } start_brace = BRACE_AT_END; } diff --git a/src/nvim/main.c b/src/nvim/main.c index 2f06a2cbf2..7dc299e73b 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -12,6 +12,8 @@ #include <string.h> #include <stdbool.h> +#include <msgpack.h> + #include "nvim/ascii.h" #include "nvim/vim.h" #include "nvim/main.h" @@ -57,6 +59,9 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/signal.h" +#include "nvim/os/msgpack_rpc_helpers.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" /* Maximum number of commands from + or -c arguments. */ #define MAX_ARG_CMDS 10 @@ -116,9 +121,6 @@ static void init_locale(void); # endif #endif /* NO_VIM_MAIN */ -extern const uint8_t msgpack_metadata[]; -extern const unsigned int msgpack_metadata_size; - /* * Different types of error messages. */ @@ -190,6 +192,7 @@ int main(int argc, char **argv) init_yank(); /* init yank buffers */ alist_init(&global_alist); /* Init the argument list to empty. */ + global_alist.id = 0; /* * Set the default values for the options. @@ -471,11 +474,10 @@ int main(int argc, char **argv) edit_buffers(¶ms); if (params.diff_mode) { - win_T *wp; - /* set options in each window for "vimdiff". */ - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { diff_win_options(wp, TRUE); + } } /* @@ -1027,12 +1029,18 @@ static void command_line_scan(mparm_T *parmp) msg_putchar('\n'); msg_didout = FALSE; mch_exit(0); - } else if (STRICMP(argv[0] + argv_idx, "api-msgpack-metadata") == 0) { - for (unsigned int i = 0; i<msgpack_metadata_size; i++) { - putchar(msgpack_metadata[i]); + } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { + msgpack_sbuffer* b = msgpack_sbuffer_new(); + msgpack_packer* p = msgpack_packer_new(b, msgpack_sbuffer_write); + Object md = DICTIONARY_OBJ(api_metadata()); + msgpack_rpc_from_object(md, p); + + for (size_t i = 0; i < b->size; i++) { + putchar(b->data[i]); } + mch_exit(0); - } else if (STRICMP(argv[0] + argv_idx, "embedded-mode") == 0) { + } else if (STRICMP(argv[0] + argv_idx, "embed") == 0) { embedded_mode = true; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { #if !defined(UNIX) @@ -2094,9 +2102,9 @@ static int file_owned(char *fname) { uid_t uid = getuid(); FileInfo file_info; - bool file_owned = os_get_file_info(fname, &file_info) + bool file_owned = os_fileinfo(fname, &file_info) && file_info.stat.st_uid == uid; - bool link_owned = os_get_file_info_link(fname, &file_info) + bool link_owned = os_fileinfo_link(fname, &file_info) && file_info.stat.st_uid == uid; return file_owned && link_owned; } @@ -2213,8 +2221,8 @@ static void usage(void) main_msg(_("-W <scriptout>\tWrite all typed commands to file <scriptout>")); main_msg(_("--startuptime <file>\tWrite startup timing messages to <file>")); main_msg(_("-i <viminfo>\t\tUse <viminfo> instead of .viminfo")); - main_msg(_("--api-msgpack-metadata\tDump API metadata information and exit")); - main_msg(_("--embedded-mode\tUse stdin/stdout as a msgpack-rpc channel. " + main_msg(_("--api-info\t\tDump API metadata serialized to msgpack and exit")); + main_msg(_("--embed\t\tUse stdin/stdout as a msgpack-rpc channel. " "This can be used for embedding Neovim into other programs")); main_msg(_("-h or --help\tPrint Help (this message) and exit")); main_msg(_("--version\t\tPrint version information and exit")); diff --git a/src/nvim/map.c b/src/nvim/map.c index 2e47e8b249..24aa38d67d 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -1,10 +1,12 @@ #include <stdlib.h> #include <stdbool.h> +#include <string.h> #include "nvim/map.h" #include "nvim/map_defs.h" #include "nvim/vim.h" #include "nvim/memory.h" +#include "nvim/os/msgpack_rpc.h" #include "nvim/lib/khash.h" @@ -87,7 +89,23 @@ return rv; \ } +static inline khint_t String_hash(String s) +{ + khint_t h = 0; + for (size_t i = 0; i < s.size && s.data[i]; i++) { + h = (h << 5) - h + (uint8_t)s.data[i]; + } + return h; +} + +static inline bool String_eq(String a, String b) +{ + return strncmp(a.data, b.data, min(a.size, b.size)) == 0; +} + + MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) +MAP_IMPL(String, rpc_method_handler_fn, DEFAULT_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index 73698cba22..616516c3e1 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -4,6 +4,8 @@ #include <stdbool.h> #include "nvim/map_defs.h" +#include "nvim/api/private/defs.h" +#include "nvim/os/msgpack_rpc.h" #define MAP_DECLS(T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \ @@ -23,6 +25,7 @@ MAP_DECLS(cstr_t, uint64_t) MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) +MAP_DECLS(String, rpc_method_handler_fn) #define map_new(T, U) map_##T##_##U##_new #define map_free(T, U) map_##T##_##U##_free diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 43469cab45..6339cf8275 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -500,7 +500,6 @@ void fmarks_check_names(buf_T *buf) { char_u *name; int i; - win_T *wp; if (buf->b_ffname == NULL) return; @@ -512,10 +511,10 @@ void fmarks_check_names(buf_T *buf) for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) fmarks_check_one(&namedfm[i], name, buf); - FOR_ALL_WINDOWS(wp) - { - for (i = 0; i < wp->w_jumplistlen; ++i) + FOR_ALL_WINDOWS(wp) { + for (i = 0; i < wp->w_jumplistlen; ++i) { fmarks_check_one(&wp->w_jumplist[i], name, buf); + } } free(name); @@ -1042,7 +1041,6 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a { int i; int fnum = curbuf->b_fnum; - win_T *win; pos_T *posp; if ((col_amount == 0L && lnum_amount == 0L) || cmdmod.lockmarks) @@ -1085,22 +1083,26 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a /* * Adjust items in all windows related to the current buffer. */ - FOR_ALL_WINDOWS(win) - { + FOR_ALL_WINDOWS(win) { /* marks in the jumplist */ - for (i = 0; i < win->w_jumplistlen; ++i) - if (win->w_jumplist[i].fmark.fnum == fnum) + for (i = 0; i < win->w_jumplistlen; ++i) { + if (win->w_jumplist[i].fmark.fnum == fnum) { col_adjust(&(win->w_jumplist[i].fmark.mark)); + } + } if (win->w_buffer == curbuf) { /* marks in the tag stack */ - for (i = 0; i < win->w_tagstacklen; i++) - if (win->w_tagstack[i].fmark.fnum == fnum) + for (i = 0; i < win->w_tagstacklen; i++) { + if (win->w_tagstack[i].fmark.fnum == fnum) { col_adjust(&(win->w_tagstack[i].fmark.mark)); + } + } /* cursor position for other windows with the same buffer */ - if (win != curwin) + if (win != curwin) { col_adjust(&win->w_cursor); + } } } } @@ -1526,10 +1528,7 @@ void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags fputs((char *)line, fp_out); } if (load_marks) { - win_T *wp; - - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp->w_buffer == curbuf) wp->w_changelistidx = curbuf->b_changelistlen; } diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 82369b739a..827cff2299 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -122,11 +122,11 @@ memfile_T *mf_open(char_u *fname, int flags) * mf_blocknr_max must be rounded up. */ FileInfo file_info; - if (mfp->mf_fd >= 0 - && os_get_file_info_fd(mfp->mf_fd, &file_info) - && file_info.stat.st_blksize >= MIN_SWAP_PAGE_SIZE - && file_info.stat.st_blksize <= MAX_SWAP_PAGE_SIZE) { - mfp->mf_page_size = file_info.stat.st_blksize; + if (mfp->mf_fd >= 0 && os_fileinfo_fd(mfp->mf_fd, &file_info)) { + uint64_t blocksize = os_fileinfo_blocksize(&file_info); + if (blocksize >= MIN_SWAP_PAGE_SIZE && blocksize <= MAX_SWAP_PAGE_SIZE) { + mfp->mf_page_size = blocksize; + } } if (mfp->mf_fd < 0 || (flags & (O_TRUNC|O_EXCL)) @@ -1017,7 +1017,7 @@ mf_do_open ( */ FileInfo file_info; if ((flags & O_CREAT) - && os_get_file_info_link((char *)mfp->mf_fname, &file_info)) { + && os_fileinfo_link((char *)mfp->mf_fname, &file_info)) { mfp->mf_fd = -1; EMSG(_("E300: Swap file already exists (symlink attack?)")); } else { diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 229de4ae1c..04ee0d7f55 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -686,9 +686,9 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) } } FileInfo file_info; - if (os_get_file_info((char *)buf->b_ffname, &file_info)) { + if (os_fileinfo((char *)buf->b_ffname, &file_info)) { long_to_char((long)file_info.stat.st_mtim.tv_sec, b0p->b0_mtime); - long_to_char((long)os_file_info_get_inode(&file_info), b0p->b0_ino); + long_to_char((long)os_fileinfo_inode(&file_info), b0p->b0_ino); buf_store_file_info(buf, &file_info); buf->b_mtime_read = buf->b_mtime; } else { @@ -961,8 +961,8 @@ void ml_recover(void) FileInfo swp_file_info; mtime = char_to_long(b0p->b0_mtime); if (curbuf->b_ffname != NULL - && os_get_file_info((char *)curbuf->b_ffname, &org_file_info) - && ((os_get_file_info((char *)mfp->mf_fname, &swp_file_info) + && os_fileinfo((char *)curbuf->b_ffname, &org_file_info) + && ((os_fileinfo((char *)mfp->mf_fname, &swp_file_info) && org_file_info.stat.st_mtim.tv_sec > swp_file_info.stat.st_mtim.tv_sec) || org_file_info.stat.st_mtim.tv_sec != mtime)) { @@ -1494,7 +1494,7 @@ static time_t swapfile_info(char_u *fname) /* print the swap file date */ FileInfo file_info; - if (os_get_file_info((char *)fname, &file_info)) { + if (os_fileinfo((char *)fname, &file_info)) { #ifdef UNIX /* print name of owner of the file */ if (os_get_uname(file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) { @@ -1630,9 +1630,9 @@ void ml_sync_all(int check_file, int check_char) * call ml_preserve() to get rid of all negative numbered blocks. */ FileInfo file_info; - if (!os_get_file_info((char *)buf->b_ffname, &file_info) + if (!os_fileinfo((char *)buf->b_ffname, &file_info) || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read - || (off_t)file_info.stat.st_size != buf->b_orig_size) { + || os_fileinfo_size(&file_info) != buf->b_orig_size) { ml_preserve(buf, FALSE); did_check_timestamps = FALSE; need_check_timestamps = TRUE; /* give message later */ @@ -3180,7 +3180,7 @@ attention_message ( msg_outtrans(buf->b_fname); MSG_PUTS("\"\n"); FileInfo file_info; - if (os_get_file_info((char *)buf->b_fname, &file_info)) { + if (os_fileinfo((char *)buf->b_fname, &file_info)) { MSG_PUTS(_(" dated: ")); x = file_info.stat.st_mtim.tv_sec; p = ctime(&x); // includes '\n' @@ -3294,7 +3294,7 @@ findswapname ( // Extra security check: When a swap file is a symbolic link, this // is most likely a symlink attack. FileInfo file_info; - bool file_or_link_found = os_get_file_info_link((char *)fname, &file_info); + bool file_or_link_found = os_fileinfo_link((char *)fname, &file_info); if (!file_or_link_found) { break; } @@ -3558,8 +3558,8 @@ fnamecmp_ino ( int retval_s; /* flag: buf_s valid */ FileInfo file_info; - if (os_get_file_info((char *)fname_c, &file_info)) { - ino_c = os_file_info_get_inode(&file_info); + if (os_fileinfo((char *)fname_c, &file_info)) { + ino_c = os_fileinfo_inode(&file_info); } /* @@ -3567,8 +3567,8 @@ fnamecmp_ino ( * the swap file may be outdated. If that fails (e.g. this path is not * valid on this machine), use the inode from block 0. */ - if (os_get_file_info((char *)fname_s, &file_info)) { - ino_s = os_file_info_get_inode(&file_info); + if (os_fileinfo((char *)fname_s, &file_info)) { + ino_s = os_fileinfo_inode(&file_info); } else { ino_s = (uint64_t)ino_block0; } diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 50dcca3c84..1c3d6e372c 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -121,7 +121,8 @@ void *xmalloc(size_t size) void *ret = try_malloc(size); if (!ret) { - OUT_STR("Vim: Error: Out of memory.\n"); + OUT_STR(e_outofmem); + out_char('\n'); preserve_exit(); } return ret; @@ -147,7 +148,8 @@ void *xcalloc(size_t count, size_t size) if (!ret && (!count || !size)) ret = calloc(1, 1); if (!ret) { - OUT_STR("Vim: Error: Out of memory.\n"); + OUT_STR(e_outofmem); + out_char('\n'); preserve_exit(); } } @@ -174,7 +176,8 @@ void *xrealloc(void *ptr, size_t size) if (!ret && !size) ret = realloc(ptr, 1); if (!ret) { - OUT_STR("Vim: Error: Out of memory.\n"); + OUT_STR(e_outofmem); + out_char('\n'); preserve_exit(); } } @@ -194,7 +197,7 @@ void *xmallocz(size_t size) void *ret; if (total_size < size) { - OUT_STR("Vim: Data too large to fit into virtual memory space\n"); + OUT_STR(_("Vim: Data too large to fit into virtual memory space\n")); preserve_exit(); } @@ -316,7 +319,8 @@ char *xstrdup(const char *str) try_to_free_memory(); ret = strdup(str); if (!ret) { - OUT_STR("Vim: Error: Out of memory.\n"); + OUT_STR(e_outofmem); + out_char('\n'); preserve_exit(); } } diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index d31bd44493..d7e9618639 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1877,16 +1877,16 @@ void changed_bytes(linenr_T lnum, colnr_T col) /* Diff highlighting in other diff windows may need to be updated too. */ if (curwin->w_p_diff) { - win_T *wp; linenr_T wlnum; - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { if (wp->w_p_diff && wp != curwin) { redraw_win_later(wp, VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) changedOneline(wp->w_buffer, wlnum); } + } } } @@ -1973,17 +1973,18 @@ changed_lines ( /* When the number of lines doesn't change then mark_adjust() isn't * called and other diff buffers still need to be marked for * displaying. */ - win_T *wp; linenr_T wlnum; - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { if (wp->w_p_diff && wp != curwin) { redraw_win_later(wp, VALID); wlnum = diff_lnum_win(lnum, wp); - if (wlnum > 0) + if (wlnum > 0) { changed_lines_buf(wp->w_buffer, wlnum, lnume - lnum + wlnum, 0L); + } } + } } changed_common(lnum, col, lnume, xtra); @@ -2214,14 +2215,14 @@ unchanged ( */ void check_status(buf_T *buf) { - win_T *wp; - - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { if (wp->w_buffer == buf && wp->w_status_height) { wp->w_redr_status = TRUE; - if (must_redraw < VALID) + if (must_redraw < VALID) { must_redraw = VALID; + } } + } } /* diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 55b86f61dd..cc82630548 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4008,12 +4008,9 @@ dozet: /* Redraw when 'foldenable' changed */ if (old_fen != curwin->w_p_fen) { - win_T *wp; - if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { /* Adjust 'foldenable' in diff-synced windows. */ - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { wp->w_p_fen = curwin->w_p_fen; changed_window_setting_win(wp); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index f1cb34577b..9b98c84be4 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -48,7 +48,6 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/provider.h" -#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/api/private/helpers.h" /* @@ -3698,17 +3697,15 @@ op_format ( } if (oap->is_VIsual) { - win_T *wp; - - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp->w_old_cursor_lnum != 0) { /* When lines have been inserted or deleted, adjust the end of * the Visual area to be redrawn. */ - if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) + if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) { wp->w_old_cursor_lnum += old_line_count; - else + } else { wp->w_old_visual_lnum += old_line_count; + } } } } @@ -5258,7 +5255,7 @@ static void get_clipboard(int name) return; err: - msgpack_rpc_free_object(result); + api_free_object(result); free(reg->y_array); reg->y_array = NULL; reg->y_size = 0; @@ -5289,5 +5286,5 @@ static void set_clipboard(int name) ADD(args, ARRAY_OBJ(lines)); Object result = provider_call("clipboard_set", args); - msgpack_rpc_free_object(result); + api_free_object(result); } diff --git a/src/nvim/option.c b/src/nvim/option.c index 6eac1033c4..b26b6ed4cc 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -974,12 +974,6 @@ static struct vimoption {"infercase", "inf", P_BOOL|P_VI_DEF, (char_u *)&p_inf, PV_INF, {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT}, - {"initclipboard","icpb",P_STRING|P_VI_DEF|P_SECURE, - (char_u *)&p_icpb, PV_NONE, - {(char_u *)"", (char_u *)0L} SCRIPTID_INIT}, - {"initpython","ipy",P_STRING|P_VI_DEF|P_SECURE, - (char_u *)&p_ipy, PV_NONE, - {(char_u *)"", (char_u *)0L} SCRIPTID_INIT}, {"insertmode", "im", P_BOOL|P_VI_DEF|P_VIM, (char_u *)&p_im, PV_NONE, {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT}, @@ -4306,7 +4300,6 @@ did_set_string_option ( /* When 'spelllang' or 'spellfile' is set and there is a window for this * buffer in which 'spell' is set load the wordlists. */ else if (varp == &(curbuf->b_s.b_p_spl) || varp == &(curbuf->b_s.b_p_spf)) { - win_T *wp; int l; if (varp == &(curbuf->b_s.b_p_spf)) { @@ -4317,10 +4310,11 @@ did_set_string_option ( } if (errmsg == NULL) { - FOR_ALL_WINDOWS(wp) - if (wp->w_buffer == curbuf && wp->w_p_spell) { - errmsg = did_set_spelllang(wp); - break; + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer == curbuf && wp->w_p_spell) { + errmsg = did_set_spelllang(wp); + break; + } } } } @@ -5075,9 +5069,7 @@ set_bool_option ( /* There can be only one window with 'previewwindow' set. */ else if ((int *)varp == &curwin->w_p_pvw) { if (curwin->w_p_pvw) { - win_T *win; - - for (win = firstwin; win != NULL; win = win->w_next) { + FOR_ALL_WINDOWS(win) { if (win->w_p_pvw && win != curwin) { curwin->w_p_pvw = FALSE; return (char_u *)N_("E590: A preview window already exists"); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 555e9166d6..cd61b6427c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -631,8 +631,6 @@ EXTERN int p_write; /* 'write' */ EXTERN int p_wa; /* 'writeany' */ EXTERN int p_wb; /* 'writebackup' */ EXTERN long p_wd; /* 'writedelay' */ -EXTERN char *p_ipy; // 'initpython' -EXTERN char *p_icpb; // 'initclipboard' /* * "indir" values for buffer-local opions. diff --git a/src/nvim/os/channel.c b/src/nvim/os/channel.c index 39455df862..1670424e4e 100644 --- a/src/nvim/os/channel.c +++ b/src/nvim/os/channel.c @@ -22,8 +22,10 @@ #include "nvim/memory.h" #include "nvim/os_unix.h" #include "nvim/message.h" +#include "nvim/term.h" #include "nvim/map.h" #include "nvim/log.h" +#include "nvim/misc1.h" #include "nvim/lib/kvec.h" #define CHANNEL_BUFFER_SIZE 0xffff @@ -156,7 +158,7 @@ bool channel_send_event(uint64_t id, char *name, Array args) if (id > 0) { if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { - msgpack_rpc_free_array(args); + api_free_array(args); return false; } send_event(channel, name, args); @@ -184,7 +186,7 @@ bool channel_send_call(uint64_t id, Channel *channel = NULL; if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { - msgpack_rpc_free_array(args); + api_free_array(args); return false; } @@ -194,11 +196,10 @@ bool channel_send_call(uint64_t id, char buf[256]; snprintf(buf, sizeof(buf), - "Channel %" PRIu64 " was closed due to a high stack depth " - "while processing a RPC call", + "Channel %" PRIu64 " crossed maximum stack depth", channel->id); *result = STRING_OBJ(cstr_to_string(buf)); - msgpack_rpc_free_array(args); + api_free_array(args); return false; } @@ -223,14 +224,6 @@ bool channel_send_call(uint64_t id, channel->enabled && // the channel is still enabled kv_size(channel->call_stack) >= size); // the call didn't return - if (!(kv_size(channel->call_stack) - || channel->enabled - || channel->rpc_call_level)) { - // Close the channel if it has been disabled and we have not been called - // by `parse_msgpack`(It would be unsafe to close the channel otherwise) - close_channel(channel); - } - *errored = frame.errored; *result = frame.result; @@ -274,6 +267,23 @@ void channel_unsubscribe(uint64_t id, char *event) unsubscribe(channel, event); } +/// Closes a channel +/// +/// @param id The channel id +/// @return true if successful, false otherwise +bool channel_close(uint64_t id) +{ + Channel *channel; + + if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { + return false; + } + + channel_kill(channel); + channel->enabled = false; + return true; +} + /// Creates an API channel from stdin/stdout. This is used when embedding /// Neovim static void channel_from_stdio(void) @@ -340,11 +350,11 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof) msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); - UnpackResult result; + msgpack_unpack_return result; // Deserialize everything we can. - while ((result = msgpack_rpc_unpack(channel->unpacker, &unpacked)) - == kUnpackResultOk) { + while ((result = msgpack_unpacker_next(channel->unpacker, &unpacked)) == + MSGPACK_UNPACK_SUCCESS) { if (kv_size(channel->call_stack) && is_rpc_response(&unpacked.data)) { if (is_valid_rpc_response(&unpacked.data, channel)) { call_stack_pop(&unpacked.data, channel); @@ -371,7 +381,13 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof) } } - if (result == kUnpackResultFail) { + if (result == MSGPACK_UNPACK_NOMEM_ERROR) { + OUT_STR(e_outofmem); + out_char('\n'); + preserve_exit(); + } + + if (result == MSGPACK_UNPACK_PARSE_ERROR) { // See src/msgpack/unpack_template.h in msgpack source tree for // causes for this error(search for 'goto _failed') // @@ -450,7 +466,7 @@ static void broadcast_event(char *name, Array args) }); if (!kv_size(subscribed)) { - msgpack_rpc_free_array(args); + api_free_array(args); goto end; } @@ -498,7 +514,13 @@ static void close_channel(Channel *channel) pmap_free(cstr_t)(channel->subscribed_events); kv_destroy(channel->call_stack); + channel_kill(channel); + free(channel); +} + +static void channel_kill(Channel *channel) +{ if (channel->is_job) { if (channel->data.job) { job_stop(channel->data.job); @@ -513,8 +535,6 @@ static void close_channel(Channel *channel) mch_exit(0); } } - - free(channel); } static void close_cb(uv_handle_t *handle) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index aca7005064..bb4e897887 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -258,20 +258,6 @@ int os_file_is_writable(const char *name) return 0; } -/// Get the size of a file in bytes. -/// -/// @param[out] size pointer to an off_t to put the size into. -/// @return `true` for success, `false` for failure. -bool os_get_file_size(const char *name, off_t *size) -{ - uv_stat_t statbuf; - if (os_stat(name, &statbuf)) { - *size = statbuf.st_size; - return true; - } - return false; -} - /// Rename a file or directory. /// /// @return `OK` for success, `FAIL` for failure. @@ -345,7 +331,7 @@ int os_remove(const char *path) /// @param path Path to the file. /// @param[out] file_info Pointer to a FileInfo to put the information in. /// @return `true` on success, `false` for failure. -bool os_get_file_info(const char *path, FileInfo *file_info) +bool os_fileinfo(const char *path, FileInfo *file_info) { return os_stat(path, &(file_info->stat)); } @@ -355,7 +341,7 @@ bool os_get_file_info(const char *path, FileInfo *file_info) /// @param path Path to the file. /// @param[out] file_info Pointer to a FileInfo to put the information in. /// @return `true` on success, `false` for failure. -bool os_get_file_info_link(const char *path, FileInfo *file_info) +bool os_fileinfo_link(const char *path, FileInfo *file_info) { uv_fs_t request; int result = uv_fs_lstat(uv_default_loop(), &request, path, NULL); @@ -369,7 +355,7 @@ bool os_get_file_info_link(const char *path, FileInfo *file_info) /// @param file_descriptor File descriptor of the file. /// @param[out] file_info Pointer to a FileInfo to put the information in. /// @return `true` on success, `false` for failure. -bool os_get_file_info_fd(int file_descriptor, FileInfo *file_info) +bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) { uv_fs_t request; int result = uv_fs_fstat(uv_default_loop(), &request, file_descriptor, NULL); @@ -381,7 +367,7 @@ bool os_get_file_info_fd(int file_descriptor, FileInfo *file_info) /// Compare the inodes of two FileInfos /// /// @return `true` if the two FileInfos represent the same file. -bool os_file_info_id_equal(const FileInfo *file_info_1, +bool os_fileinfo_id_equal(const FileInfo *file_info_1, const FileInfo *file_info_2) { return file_info_1->stat.st_ino == file_info_2->stat.st_ino @@ -392,7 +378,7 @@ bool os_file_info_id_equal(const FileInfo *file_info_1, /// /// @param file_info Pointer to the `FileInfo` /// @param[out] file_id Pointer to a `FileID` -void os_file_info_get_id(const FileInfo *file_info, FileID *file_id) +void os_fileinfo_id(const FileInfo *file_info, FileID *file_id) { file_id->inode = file_info->stat.st_ino; file_id->device_id = file_info->stat.st_dev; @@ -403,17 +389,44 @@ void os_file_info_get_id(const FileInfo *file_info, FileID *file_id) /// @deprecated Use `FileID` instead, this function is only needed in memline.c /// @param file_info Pointer to the `FileInfo` /// @return the inode number -uint64_t os_file_info_get_inode(const FileInfo *file_info) +uint64_t os_fileinfo_inode(const FileInfo *file_info) { return file_info->stat.st_ino; } +/// Get the size of a file from a `FileInfo`. +/// +/// @return filesize in bytes. +uint64_t os_fileinfo_size(const FileInfo *file_info) + FUNC_ATTR_NONNULL_ALL +{ + return file_info->stat.st_size; +} + +/// Get the number of hardlinks from a `FileInfo`. +/// +/// @return number of hardlinks. +uint64_t os_fileinfo_hardlinks(const FileInfo *file_info) + FUNC_ATTR_NONNULL_ALL +{ + return file_info->stat.st_nlink; +} + +/// Get the blocksize from a `FileInfo`. +/// +/// @return blocksize in bytes. +uint64_t os_fileinfo_blocksize(const FileInfo *file_info) + FUNC_ATTR_NONNULL_ALL +{ + return file_info->stat.st_blksize; +} + /// Get the `FileID` for a given path /// /// @param path Path to the file. /// @param[out] file_info Pointer to a `FileID` to fill in. /// @return `true` on sucess, `false` for failure. -bool os_get_file_id(const char *path, FileID *file_id) +bool os_fileid(const char *path, FileID *file_id) { uv_stat_t statbuf; if (os_stat(path, &statbuf)) { @@ -429,7 +442,7 @@ bool os_get_file_id(const char *path, FileID *file_id) /// @param file_id_1 Pointer to first `FileID` /// @param file_id_2 Pointer to second `FileID` /// @return `true` if the two `FileID`s represent te same file. -bool os_file_id_equal(const FileID *file_id_1, const FileID *file_id_2) +bool os_fileid_equal(const FileID *file_id_1, const FileID *file_id_2) { return file_id_1->inode == file_id_2->inode && file_id_1->device_id == file_id_2->device_id; @@ -440,7 +453,7 @@ bool os_file_id_equal(const FileID *file_id_1, const FileID *file_id_2) /// @param file_id Pointer to a `FileID` /// @param file_info Pointer to a `FileInfo` /// @return `true` if the `FileID` and the `FileInfo` represent te same file. -bool os_file_id_equal_file_info(const FileID *file_id, +bool os_fileid_equal_fileinfo(const FileID *file_id, const FileInfo *file_info) { return file_id->inode == file_info->stat.st_ino diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c index 9deca9de74..9fb2a49e50 100644 --- a/src/nvim/os/job.c +++ b/src/nvim/os/job.c @@ -197,6 +197,12 @@ Job *job_start(char **argv, job->stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; job->stdio[2].data.stream = (uv_stream_t *)&job->proc_stderr; + // Give all handles a reference to the job + handle_set_job((uv_handle_t *)&job->proc, job); + handle_set_job((uv_handle_t *)&job->proc_stdin, job); + handle_set_job((uv_handle_t *)&job->proc_stdout, job); + handle_set_job((uv_handle_t *)&job->proc_stderr, job); + // Spawn the job if (uv_spawn(uv_default_loop(), &job->proc, &job->proc_opts) != 0) { free_job(job); @@ -204,12 +210,6 @@ Job *job_start(char **argv, return NULL; } - // Give all handles a reference to the job - handle_set_job((uv_handle_t *)&job->proc, job); - handle_set_job((uv_handle_t *)&job->proc_stdin, job); - handle_set_job((uv_handle_t *)&job->proc_stdout, job); - handle_set_job((uv_handle_t *)&job->proc_stderr, job); - job->in = wstream_new(maxmem); wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin); // Start the readable streams diff --git a/src/nvim/os/msgpack_rpc.c b/src/nvim/os/msgpack_rpc.c index c6e2af2f1c..d7e3d33c4b 100644 --- a/src/nvim/os/msgpack_rpc.c +++ b/src/nvim/os/msgpack_rpc.c @@ -17,9 +17,6 @@ # include "os/msgpack_rpc.c.generated.h" #endif -extern const uint8_t msgpack_metadata[]; -extern const unsigned int msgpack_metadata_size; - /// Validates the basic structure of the msgpack-rpc call and fills `res` /// with the basic response structure. /// @@ -39,11 +36,6 @@ WBuffer *msgpack_rpc_call(uint64_t channel_id, return serialize_response(response_id, err, NIL, sbuffer); } - if (req->via.array.ptr[2].type == MSGPACK_OBJECT_POSITIVE_INTEGER - && req->via.array.ptr[2].via.u64 == 0) { - return serialize_metadata(response_id, channel_id, sbuffer); - } - // dispatch the call Error error = { .set = false }; Object rv = msgpack_rpc_dispatch(channel_id, req, &error); @@ -63,42 +55,6 @@ WBuffer *msgpack_rpc_call(uint64_t channel_id, return serialize_response(response_id, NULL, rv, sbuffer); } -/// Try to unpack a msgpack document from the data in the unpacker buffer. This -/// function is a replacement to msgpack.h `msgpack_unpack_next` that lets -/// the called know if the unpacking failed due to bad input or due to missing -/// data. -/// -/// @param unpacker The unpacker containing the parse buffer -/// @param result The result which will contain the parsed object -/// @return kUnpackResultOk : An object was parsed -/// kUnpackResultFail : Got bad input -/// kUnpackResultNeedMore: Need more data -UnpackResult msgpack_rpc_unpack(msgpack_unpacker* unpacker, - msgpack_unpacked* result) - FUNC_ATTR_NONNULL_ALL -{ - if (result->zone != NULL) { - msgpack_zone_free(result->zone); - } - - int res = msgpack_unpacker_execute(unpacker); - - if (res > 0) { - result->zone = msgpack_unpacker_release_zone(unpacker); - result->data = msgpack_unpacker_data(unpacker); - msgpack_unpacker_reset(unpacker); - return kUnpackResultOk; - } - - if (res < 0) { - // Since we couldn't parse it, destroy the data consumed so far - msgpack_unpacker_reset(unpacker); - return kUnpackResultFail; - } - - return kUnpackResultNeedMore; -} - /// Finishes the msgpack-rpc call with an error message. /// /// @param msg The error message @@ -109,12 +65,22 @@ void msgpack_rpc_error(char *msg, msgpack_packer *res) size_t len = strlen(msg); // error message - msgpack_pack_raw(res, len); - msgpack_pack_raw_body(res, msg, len); + msgpack_pack_bin(res, len); + msgpack_pack_bin_body(res, msg, len); // Nil result msgpack_pack_nil(res); } +/// Handler executed when an invalid method name is passed +Object msgpack_rpc_handle_missing_method(uint64_t channel_id, + msgpack_object *req, + Error *error) +{ + snprintf(error->msg, sizeof(error->msg), "Invalid method name"); + error->set = true; + return NIL; +} + /// Serializes a msgpack-rpc request or notification(id == 0) WBuffer *serialize_request(uint64_t request_id, String method, @@ -132,14 +98,14 @@ WBuffer *serialize_request(uint64_t request_id, msgpack_pack_uint64(&pac, request_id); } - msgpack_pack_raw(&pac, method.size); - msgpack_pack_raw_body(&pac, method.data, method.size); + msgpack_pack_bin(&pac, method.size); + msgpack_pack_bin_body(&pac, method.data, method.size); msgpack_rpc_from_array(args, &pac); WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size), sbuffer->size, refcount, free); - msgpack_rpc_free_array(args); + api_free_array(args); msgpack_sbuffer_clear(sbuffer); return rv; } @@ -160,8 +126,8 @@ WBuffer *serialize_response(uint64_t response_id, if (err_msg) { String err = {.size = strlen(err_msg), .data = err_msg}; // error message - msgpack_pack_raw(&pac, err.size); - msgpack_pack_raw_body(&pac, err.data, err.size); + msgpack_pack_bin(&pac, err.size); + msgpack_pack_bin_body(&pac, err.data, err.size); // Nil result msgpack_pack_nil(&pac); } else { @@ -175,32 +141,7 @@ WBuffer *serialize_response(uint64_t response_id, sbuffer->size, 1, // responses only go though 1 channel free); - msgpack_rpc_free_object(arg); - msgpack_sbuffer_clear(sbuffer); - return rv; -} - -WBuffer *serialize_metadata(uint64_t id, - uint64_t channel_id, - msgpack_sbuffer *sbuffer) - FUNC_ATTR_NONNULL_ALL -{ - msgpack_packer pac; - msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write); - msgpack_pack_array(&pac, 4); - msgpack_pack_int(&pac, 1); - msgpack_pack_uint64(&pac, id); - // Nil error - msgpack_pack_nil(&pac); - // The result is the [channel_id, metadata] array - msgpack_pack_array(&pac, 2); - msgpack_pack_uint64(&pac, channel_id); - msgpack_pack_raw(&pac, msgpack_metadata_size); - msgpack_pack_raw_body(&pac, msgpack_metadata, msgpack_metadata_size); - WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size), - sbuffer->size, - 1, - free); + api_free_object(arg); msgpack_sbuffer_clear(sbuffer); return rv; } @@ -234,9 +175,9 @@ static char *msgpack_rpc_validate(uint64_t *response_id, msgpack_object *req) return "Message type must be 0"; } - if (req->via.array.ptr[2].type != MSGPACK_OBJECT_POSITIVE_INTEGER - && req->via.array.ptr[2].type != MSGPACK_OBJECT_RAW) { - return "Method must be a positive integer or a string"; + if (req->via.array.ptr[2].type != MSGPACK_OBJECT_BIN + && req->via.array.ptr[2].type != MSGPACK_OBJECT_STR) { + return "Method must be a string"; } if (req->via.array.ptr[3].type != MSGPACK_OBJECT_ARRAY) { diff --git a/src/nvim/os/msgpack_rpc.h b/src/nvim/os/msgpack_rpc.h index 35f175d2a0..3476d791ea 100644 --- a/src/nvim/os/msgpack_rpc.h +++ b/src/nvim/os/msgpack_rpc.h @@ -25,6 +25,7 @@ typedef Object (*rpc_method_handler_fn)(uint64_t channel_id, /// Initializes the msgpack-rpc method table void msgpack_rpc_init(void); +void msgpack_rpc_init_function_metadata(Dictionary *metadata); /// Dispatches to the actual API function after basic payload validation by /// `msgpack_rpc_call`. It is responsible for validating/converting arguments diff --git a/src/nvim/os/msgpack_rpc_helpers.c b/src/nvim/os/msgpack_rpc_helpers.c index e2c277abe4..b14de8245c 100644 --- a/src/nvim/os/msgpack_rpc_helpers.c +++ b/src/nvim/os/msgpack_rpc_helpers.c @@ -7,61 +7,67 @@ #include "nvim/vim.h" #include "nvim/memory.h" -#define REMOTE_FUNCS_IMPL(t, lt) \ - bool msgpack_rpc_to_##lt(msgpack_object *obj, t *arg) \ - { \ - *arg = obj->via.u64; \ - return obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER; \ - } \ - \ - void msgpack_rpc_from_##lt(t result, msgpack_packer *res) \ - { \ - msgpack_pack_uint64(res, result); \ - } +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/msgpack_rpc_helpers.c.generated.h" +#endif -#define TYPED_ARRAY_IMPL(t, lt) \ - bool msgpack_rpc_to_##lt##array(msgpack_object *obj, t##Array *arg) \ +static msgpack_zone zone; +static msgpack_sbuffer sbuffer; + +#define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \ + bool msgpack_rpc_to_##lt(msgpack_object *obj, t *arg) \ + FUNC_ATTR_NONNULL_ALL \ { \ - if (obj->type != MSGPACK_OBJECT_ARRAY) { \ + if (obj->type != MSGPACK_OBJECT_EXT \ + || obj->via.ext.type != kObjectType##t) { \ return false; \ } \ \ - arg->size = obj->via.array.size; \ - arg->items = xcalloc(obj->via.array.size, sizeof(t)); \ + msgpack_object data; \ + msgpack_unpack_return ret = msgpack_unpack(obj->via.ext.ptr, \ + obj->via.ext.size, \ + NULL, \ + &zone, \ + &data); \ \ - for (size_t i = 0; i < obj->via.array.size; i++) { \ - if (!msgpack_rpc_to_##lt(obj->via.array.ptr + i, &arg->items[i])) { \ - return false; \ - } \ + if (ret != MSGPACK_UNPACK_SUCCESS) { \ + return false; \ } \ \ + *arg = data.via.u64; \ return true; \ } \ \ - void msgpack_rpc_from_##lt##array(t##Array result, msgpack_packer *res) \ + void msgpack_rpc_from_##lt(t o, msgpack_packer *res) \ + FUNC_ATTR_NONNULL_ARG(2) \ { \ - msgpack_pack_array(res, result.size); \ - \ - for (size_t i = 0; i < result.size; i++) { \ - msgpack_rpc_from_##lt(result.items[i], res); \ - } \ - } \ - \ - void msgpack_rpc_free_##lt##array(t##Array value) { \ - for (size_t i = 0; i < value.size; i++) { \ - msgpack_rpc_free_##lt(value.items[i]); \ - } \ - \ - free(value.items); \ + msgpack_packer pac; \ + msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \ + msgpack_pack_uint64(&pac, o); \ + msgpack_pack_ext(res, sbuffer.size, kObjectType##t); \ + msgpack_pack_ext_body(res, sbuffer.data, sbuffer.size); \ + msgpack_sbuffer_clear(&sbuffer); \ } +void msgpack_rpc_helpers_init(void) +{ + msgpack_zone_init(&zone, 0xfff); + msgpack_sbuffer_init(&sbuffer); +} + +HANDLE_TYPE_CONVERSION_IMPL(Buffer, buffer) +HANDLE_TYPE_CONVERSION_IMPL(Window, window) +HANDLE_TYPE_CONVERSION_IMPL(Tabpage, tabpage) + bool msgpack_rpc_to_boolean(msgpack_object *obj, Boolean *arg) + FUNC_ATTR_NONNULL_ALL { *arg = obj->via.boolean; return obj->type == MSGPACK_OBJECT_BOOLEAN; } bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg) + FUNC_ATTR_NONNULL_ALL { if (obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER && obj->via.u64 <= INT64_MAX) { @@ -74,23 +80,27 @@ bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg) } bool msgpack_rpc_to_float(msgpack_object *obj, Float *arg) + FUNC_ATTR_NONNULL_ALL { *arg = obj->via.dec; return obj->type == MSGPACK_OBJECT_DOUBLE; } bool msgpack_rpc_to_string(msgpack_object *obj, String *arg) + FUNC_ATTR_NONNULL_ALL { - if (obj->type != MSGPACK_OBJECT_RAW) { + if (obj->type == MSGPACK_OBJECT_BIN || obj->type == MSGPACK_OBJECT_STR) { + arg->data = xmemdupz(obj->via.bin.ptr, obj->via.bin.size); + arg->size = obj->via.bin.size; + } else { return false; } - arg->data = xmemdupz(obj->via.raw.ptr, obj->via.raw.size); - arg->size = obj->via.raw.size; return true; } bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg) + FUNC_ATTR_NONNULL_ALL { switch (obj->type) { case MSGPACK_OBJECT_NIL: @@ -110,7 +120,8 @@ bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg) arg->type = kObjectTypeFloat; return msgpack_rpc_to_float(obj, &arg->data.floating); - case MSGPACK_OBJECT_RAW: + case MSGPACK_OBJECT_BIN: + case MSGPACK_OBJECT_STR: arg->type = kObjectTypeString; return msgpack_rpc_to_string(obj, &arg->data.string); @@ -122,21 +133,22 @@ bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg) arg->type = kObjectTypeDictionary; return msgpack_rpc_to_dictionary(obj, &arg->data.dictionary); + case MSGPACK_OBJECT_EXT: + switch (obj->via.ext.type) { + case kObjectTypeBuffer: + return msgpack_rpc_to_buffer(obj, &arg->data.buffer); + case kObjectTypeWindow: + return msgpack_rpc_to_window(obj, &arg->data.window); + case kObjectTypeTabpage: + return msgpack_rpc_to_tabpage(obj, &arg->data.tabpage); + } default: return false; } } -bool msgpack_rpc_to_position(msgpack_object *obj, Position *arg) -{ - return obj->type == MSGPACK_OBJECT_ARRAY - && obj->via.array.size == 2 - && msgpack_rpc_to_integer(obj->via.array.ptr, &arg->row) - && msgpack_rpc_to_integer(obj->via.array.ptr + 1, &arg->col); -} - - bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg) + FUNC_ATTR_NONNULL_ALL { if (obj->type != MSGPACK_OBJECT_ARRAY) { return false; @@ -155,6 +167,7 @@ bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg) } bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg) + FUNC_ATTR_NONNULL_ALL { if (obj->type != MSGPACK_OBJECT_MAP) { return false; @@ -180,6 +193,7 @@ bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg) } void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res) + FUNC_ATTR_NONNULL_ARG(2) { if (result) { msgpack_pack_true(res); @@ -189,22 +203,26 @@ void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res) } void msgpack_rpc_from_integer(Integer result, msgpack_packer *res) + FUNC_ATTR_NONNULL_ARG(2) { msgpack_pack_int64(res, result); } void msgpack_rpc_from_float(Float result, msgpack_packer *res) + FUNC_ATTR_NONNULL_ARG(2) { msgpack_pack_double(res, result); } void msgpack_rpc_from_string(String result, msgpack_packer *res) + FUNC_ATTR_NONNULL_ARG(2) { - msgpack_pack_raw(res, result.size); - msgpack_pack_raw_body(res, result.data, result.size); + msgpack_pack_bin(res, result.size); + msgpack_pack_bin_body(res, result.data, result.size); } void msgpack_rpc_from_object(Object result, msgpack_packer *res) + FUNC_ATTR_NONNULL_ARG(2) { switch (result.type) { case kObjectTypeNil: @@ -231,10 +249,6 @@ void msgpack_rpc_from_object(Object result, msgpack_packer *res) msgpack_rpc_from_array(result.data.array, res); break; - case kObjectTypePosition: - msgpack_rpc_from_position(result.data.position, res); - break; - case kObjectTypeBuffer: msgpack_rpc_from_buffer(result.data.buffer, res); break; @@ -247,36 +261,14 @@ void msgpack_rpc_from_object(Object result, msgpack_packer *res) msgpack_rpc_from_tabpage(result.data.tabpage, res); break; - case kObjectTypeStringArray: - msgpack_rpc_from_stringarray(result.data.stringarray, res); - break; - - case kObjectTypeBufferArray: - msgpack_rpc_from_bufferarray(result.data.bufferarray, res); - break; - - case kObjectTypeWindowArray: - msgpack_rpc_from_windowarray(result.data.windowarray, res); - break; - - case kObjectTypeTabpageArray: - msgpack_rpc_from_tabpagearray(result.data.tabpagearray, res); - break; - case kObjectTypeDictionary: msgpack_rpc_from_dictionary(result.data.dictionary, res); break; } } -void msgpack_rpc_from_position(Position result, msgpack_packer *res) -{ - msgpack_pack_array(res, 2);; - msgpack_pack_int64(res, result.row); - msgpack_pack_int64(res, result.col); -} - void msgpack_rpc_from_array(Array result, msgpack_packer *res) + FUNC_ATTR_NONNULL_ARG(2) { msgpack_pack_array(res, result.size); @@ -286,6 +278,7 @@ void msgpack_rpc_from_array(Array result, msgpack_packer *res) } void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res) + FUNC_ATTR_NONNULL_ARG(2) { msgpack_pack_map(res, result.size); @@ -294,87 +287,3 @@ void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res) msgpack_rpc_from_object(result.items[i].value, res); } } - -void msgpack_rpc_free_string(String value) -{ - if (!value.data) { - return; - } - - free(value.data); -} - -void msgpack_rpc_free_object(Object value) -{ - switch (value.type) { - case kObjectTypeNil: - case kObjectTypeBoolean: - case kObjectTypeInteger: - case kObjectTypeFloat: - case kObjectTypePosition: - case kObjectTypeBuffer: - case kObjectTypeWindow: - case kObjectTypeTabpage: - break; - - case kObjectTypeString: - msgpack_rpc_free_string(value.data.string); - break; - - case kObjectTypeArray: - msgpack_rpc_free_array(value.data.array); - break; - - case kObjectTypeStringArray: - msgpack_rpc_free_stringarray(value.data.stringarray); - break; - - case kObjectTypeBufferArray: - msgpack_rpc_free_bufferarray(value.data.bufferarray); - break; - - case kObjectTypeWindowArray: - msgpack_rpc_free_windowarray(value.data.windowarray); - break; - - case kObjectTypeTabpageArray: - msgpack_rpc_free_tabpagearray(value.data.tabpagearray); - break; - - case kObjectTypeDictionary: - msgpack_rpc_free_dictionary(value.data.dictionary); - break; - - default: - abort(); - } -} - -void msgpack_rpc_free_array(Array value) -{ - for (uint32_t i = 0; i < value.size; i++) { - msgpack_rpc_free_object(value.items[i]); - } - - free(value.items); -} - -void msgpack_rpc_free_dictionary(Dictionary value) -{ - for (uint32_t i = 0; i < value.size; i++) { - msgpack_rpc_free_string(value.items[i].key); - msgpack_rpc_free_object(value.items[i].value); - } - - free(value.items); -} - -REMOTE_FUNCS_IMPL(Buffer, buffer) -REMOTE_FUNCS_IMPL(Window, window) -REMOTE_FUNCS_IMPL(Tabpage, tabpage) - -TYPED_ARRAY_IMPL(Buffer, buffer) -TYPED_ARRAY_IMPL(Window, window) -TYPED_ARRAY_IMPL(Tabpage, tabpage) -TYPED_ARRAY_IMPL(String, string) - diff --git a/src/nvim/os/msgpack_rpc_helpers.h b/src/nvim/os/msgpack_rpc_helpers.h index e3d1e756ef..aede6b1587 100644 --- a/src/nvim/os/msgpack_rpc_helpers.h +++ b/src/nvim/os/msgpack_rpc_helpers.h @@ -6,119 +6,11 @@ #include <msgpack.h> -#include "nvim/func_attr.h" #include "nvim/api/private/defs.h" -/// Functions for validating and converting from msgpack types to C types. -/// These are used by `msgpack_rpc_dispatch` to validate and convert each -/// argument. -/// -/// @param obj The object to convert -/// @param[out] arg A pointer to the avalue -/// @return true if the conversion succeeded, false otherwise -bool msgpack_rpc_to_boolean(msgpack_object *obj, Boolean *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_float(msgpack_object *obj, Float *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_position(msgpack_object *obj, Position *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_string(msgpack_object *obj, String *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_buffer(msgpack_object *obj, Buffer *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_window(msgpack_object *obj, Window *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_tabpage(msgpack_object *obj, Tabpage *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_stringarray(msgpack_object *obj, StringArray *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_bufferarray(msgpack_object *obj, BufferArray *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_windowarray(msgpack_object *obj, WindowArray *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_tabpagearray(msgpack_object *obj, TabpageArray *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg) - FUNC_ATTR_NONNULL_ALL; -bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg) - FUNC_ATTR_NONNULL_ALL; - -/// Functions for converting from C types to msgpack types. -/// These are used by `msgpack_rpc_dispatch` to convert return values -/// from the API -/// -/// @param result A pointer to the result -/// @param res A packer that contains the response -void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_integer(Integer result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_float(Float result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_position(Position result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_string(String result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_buffer(Buffer result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_window(Window result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_tabpage(Tabpage result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_object(Object result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_stringarray(StringArray result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_bufferarray(BufferArray result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_windowarray(WindowArray result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_tabpagearray(TabpageArray result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_array(Array result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); -void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res) - FUNC_ATTR_NONNULL_ARG(2); - -/// Helpers for initializing types that may be freed later -#define msgpack_rpc_init_boolean -#define msgpack_rpc_init_integer -#define msgpack_rpc_init_float -#define msgpack_rpc_init_position -#define msgpack_rpc_init_string = STRING_INIT -#define msgpack_rpc_init_buffer -#define msgpack_rpc_init_window -#define msgpack_rpc_init_tabpage -#define msgpack_rpc_init_object = {.type = kObjectTypeNil} -#define msgpack_rpc_init_stringarray = ARRAY_DICT_INIT -#define msgpack_rpc_init_bufferarray = ARRAY_DICT_INIT -#define msgpack_rpc_init_windowarray = ARRAY_DICT_INIT -#define msgpack_rpc_init_tabpagearray = ARRAY_DICT_INIT -#define msgpack_rpc_init_array = ARRAY_DICT_INIT -#define msgpack_rpc_init_dictionary = ARRAY_DICT_INIT - -/// Helpers for freeing arguments/return value -/// -/// @param value The value to be freed -#define msgpack_rpc_free_boolean(value) -#define msgpack_rpc_free_integer(value) -#define msgpack_rpc_free_float(value) -#define msgpack_rpc_free_position(value) -void msgpack_rpc_free_string(String value); -#define msgpack_rpc_free_buffer(value) -#define msgpack_rpc_free_window(value) -#define msgpack_rpc_free_tabpage(value) -void msgpack_rpc_free_object(Object value); -void msgpack_rpc_free_stringarray(StringArray value); -void msgpack_rpc_free_bufferarray(BufferArray value); -void msgpack_rpc_free_windowarray(WindowArray value); -void msgpack_rpc_free_tabpagearray(TabpageArray value); -void msgpack_rpc_free_array(Array value); -void msgpack_rpc_free_dictionary(Dictionary value); +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/msgpack_rpc_helpers.h.generated.h" +#endif #endif // NVIM_OS_MSGPACK_RPC_HELPERS_H diff --git a/src/nvim/os/provider.c b/src/nvim/os/provider.c index 07e757fe0e..2e7a677793 100644 --- a/src/nvim/os/provider.c +++ b/src/nvim/os/provider.c @@ -14,37 +14,34 @@ #include "nvim/log.h" #include "nvim/map.h" #include "nvim/message.h" -#include "nvim/os/msgpack_rpc_helpers.h" #define FEATURE_COUNT (sizeof(features) / sizeof(features[0])) -#define FEATURE(feature_name, provider_bootstrap_command, ...) { \ +#define FEATURE(feature_name, ...) { \ .name = feature_name, \ - .bootstrap_command = provider_bootstrap_command, \ - .argv = NULL, \ .channel_id = 0, \ .methods = (char *[]){__VA_ARGS__, NULL} \ } -static struct feature { - char *name, **bootstrap_command, **argv, **methods; +typedef struct { + char *name, **methods; size_t name_length; uint64_t channel_id; -} features[] = { +} Feature; + +static Feature features[] = { FEATURE("python", - &p_ipy, "python_execute", "python_execute_file", "python_do_range", "python_eval"), FEATURE("clipboard", - &p_icpb, "clipboard_get", "clipboard_set") }; -static Map(cstr_t, uint64_t) *registered_providers = NULL; +static PMap(cstr_t) *registered_providers = NULL; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/provider.c.generated.h" @@ -53,163 +50,101 @@ static Map(cstr_t, uint64_t) *registered_providers = NULL; void provider_init(void) { - registered_providers = map_new(cstr_t, uint64_t)(); + registered_providers = pmap_new(cstr_t)(); } bool provider_has_feature(char *name) { - for (size_t i = 0; i < FEATURE_COUNT; i++) { - struct feature *f = &features[i]; - if (!STRICMP(name, f->name)) { - return f->channel_id || can_execute(f); - } - } - - return false; + Feature *f = find_feature(name); + return f != NULL && channel_exists(f->channel_id); } -bool provider_available(char *method) +bool provider_register(char *name, uint64_t channel_id) { - return map_has(cstr_t, uint64_t)(registered_providers, method); -} + Feature *f = find_feature(name); -bool provider_register(char *method, uint64_t channel_id) -{ - if (map_has(cstr_t, uint64_t)(registered_providers, method)) { + if (!f) { return false; } - // First check if this method is part of a feature, and if so, update - // the feature structure with the channel id - struct feature *f = get_feature_for(method); - if (f) { - DLOG("Registering provider for \"%s\" " - "which is part of the \"%s\" feature", - method, - f->name); - f->channel_id = channel_id; + if (f->channel_id && channel_exists(f->channel_id)) { + ILOG("Feature \"%s\" is already provided by another channel" + "(will be replaced)", name); + } + + DLOG("Registering provider for \"%s\"", name); + f->channel_id = channel_id; + + // Associate all method names with the feature struct + size_t i; + char *method; + for (method = f->methods[i = 0]; method; method = f->methods[++i]) { + pmap_put(cstr_t)(registered_providers, method, f); + DLOG("Channel \"%" PRIu64 "\" will be sent requests for \"%s\"", + channel_id, + method); } - map_put(cstr_t, uint64_t)(registered_providers, xstrdup(method), channel_id); - ILOG("Registered channel %" PRIu64 " as the provider for \"%s\"", + ILOG("Registered channel %" PRIu64 " as the provider for the \"%s\" feature", channel_id, - method); + name); return true; } Object provider_call(char *method, Array args) { - uint64_t channel_id = get_provider_for(method); + Feature *f = pmap_get(cstr_t)(registered_providers, method); - if (!channel_id) { + if (!f || !channel_exists(f->channel_id)) { char buf[256]; snprintf(buf, sizeof(buf), - "Provider for \"%s\" is not available", + "Provider for method \"%s\" is not available", method); - report_error(buf); - msgpack_rpc_free_array(args); + vim_report_error(cstr_as_string(buf)); + api_free_array(args); return NIL; } bool error = false; Object result = NIL; - channel_send_call(channel_id, method, args, &result, &error); + channel_send_call(f->channel_id, method, args, &result, &error); if (error) { - report_error(result.data.string.data); - msgpack_rpc_free_object(result); + vim_report_error(result.data.string); + api_free_object(result); return NIL; } return result; } -static uint64_t get_provider_for(char *method) +void provider_init_feature_metadata(Dictionary *metadata) { - uint64_t channel_id = map_get(cstr_t, uint64_t)(registered_providers, method); + Dictionary md = ARRAY_DICT_INIT; - if (channel_id) { - return channel_id; - } - - // Try to bootstrap if the method is part of a feature - struct feature *f = get_feature_for(method); - - if (!f || !can_execute(f)) { - ELOG("Cannot bootstrap provider for \"%s\"", method); - goto err; - } - - if (f->channel_id) { - ELOG("Already bootstrapped provider for \"%s\"", f->name); - goto err; - } - - f->channel_id = channel_from_job(f->argv); - - if (!f->channel_id) { - ELOG("The provider for \"%s\" failed to bootstrap", f->name); - goto err; - } - - return f->channel_id; - -err: - // Ensure we won't try to restart the provider - if (f) { - f->bootstrap_command = NULL; - f->channel_id = 0; - } - return 0; -} - -static bool can_execute(struct feature *f) -{ - if (!f->bootstrap_command) { - return false; - } - - char *cmd = *f->bootstrap_command; - - if (!cmd || !strlen(cmd)) { - return false; - } - - if (!f->argv) { - f->argv = shell_build_argv((uint8_t *)cmd, NULL); - } - - return os_can_exe((uint8_t *)f->argv[0]); -} - -static void report_error(char *str) -{ - vim_err_write((String) {.data = str, .size = strlen(str)}); - vim_err_write((String) {.data = "\n", .size = 1}); -} - -static bool feature_has_method(struct feature *f, char *method) -{ - size_t i; - char *m; + for (size_t i = 0; i < FEATURE_COUNT; i++) { + Array methods = ARRAY_DICT_INIT; + Feature *f = &features[i]; - for (m = f->methods[i = 0]; m; m = f->methods[++i]) { - if (!STRCMP(method, m)) { - return true; + size_t j; + char *method; + for (method = f->methods[j = 0]; method; method = f->methods[++j]) { + ADD(methods, STRING_OBJ(cstr_to_string(method))); } + + PUT(md, f->name, ARRAY_OBJ(methods)); } - return false; + PUT(*metadata, "features", DICTIONARY_OBJ(md)); } - -static struct feature *get_feature_for(char *method) +static Feature * find_feature(char *name) { for (size_t i = 0; i < FEATURE_COUNT; i++) { - struct feature *f = &features[i]; - if (feature_has_method(f, method)) { + Feature *f = &features[i]; + if (!STRICMP(name, f->name)) { return f; } } diff --git a/src/nvim/os/server.c b/src/nvim/os/server.c index 2e8934ecfb..66dd0ecd88 100644 --- a/src/nvim/os/server.c +++ b/src/nvim/os/server.c @@ -17,6 +17,7 @@ #define MAX_CONNECTIONS 32 #define ADDRESS_MAX_SIZE 256 #define NEOVIM_DEFAULT_TCP_PORT 7450 +#define LISTEN_ADDRESS_ENV_VAR "NVIM_LISTEN_ADDRESS" typedef enum { kServerTypeTcp, @@ -51,13 +52,13 @@ void server_init(void) { servers = pmap_new(cstr_t)(); - if (!os_getenv("NEOVIM_LISTEN_ADDRESS")) { + if (!os_getenv(LISTEN_ADDRESS_ENV_VAR)) { char *listen_address = (char *)vim_tempname(); - os_setenv("NEOVIM_LISTEN_ADDRESS", listen_address, 1); + os_setenv(LISTEN_ADDRESS_ENV_VAR, listen_address, 1); free(listen_address); } - server_start((char *)os_getenv("NEOVIM_LISTEN_ADDRESS")); + server_start((char *)os_getenv(LISTEN_ADDRESS_ENV_VAR)); } /// Teardown the server module diff --git a/src/nvim/os/wstream.c b/src/nvim/os/wstream.c index 44463c7c88..00a53d1628 100644 --- a/src/nvim/os/wstream.c +++ b/src/nvim/os/wstream.c @@ -72,12 +72,12 @@ WStream * wstream_new(size_t maxmem) /// @param wstream The `WStream` instance void wstream_free(WStream *wstream) { if (!wstream->pending_reqs) { - handle_set_wstream((uv_handle_t *)wstream->stream, NULL); if (wstream->free_handle) { uv_close((uv_handle_t *)wstream->stream, close_cb); + } else { + handle_set_wstream((uv_handle_t *)wstream->stream, NULL); + free(wstream); } - - free(wstream); } else { wstream->freed = true; } @@ -238,12 +238,7 @@ static void release_wbuffer(WBuffer *buffer) static void close_cb(uv_handle_t *handle) { - WStream *wstream = handle_get_wstream(handle); - - if (wstream) { - free(wstream); - } - + free(handle_get_wstream(handle)); free(handle->data); free(handle); } diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 33b08d7df6..6c79fbd479 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -55,6 +55,7 @@ #include "nvim/os/signal.h" #include "nvim/os/job.h" #include "nvim/os/msgpack_rpc.h" +#include "nvim/os/msgpack_rpc_helpers.h" #if defined(HAVE_SYS_IOCTL_H) # include <sys/ioctl.h> @@ -166,6 +167,7 @@ void mch_init(void) #endif msgpack_rpc_init(); + msgpack_rpc_helpers_init(); event_init(); } @@ -328,7 +330,7 @@ int len /* buffer size, only used when name gets longer */ struct dirent *dp; FileInfo file_info; - if (os_get_file_info_link((char *)name, &file_info)) { + if (os_fileinfo_link((char *)name, &file_info)) { /* Open the directory where the file is located. */ slash = vim_strrchr(name, '/'); if (slash == NULL) { @@ -354,8 +356,8 @@ int len /* buffer size, only used when name gets longer */ STRLCPY(newname + (tail - name), dp->d_name, MAXPATHL - (tail - name) + 1); FileInfo file_info_new; - if (os_get_file_info_link((char *)newname, &file_info_new) - && os_file_info_id_equal(&file_info, &file_info_new)) { + if (os_fileinfo_link((char *)newname, &file_info_new) + && os_fileinfo_id_equal(&file_info, &file_info_new)) { STRCPY(tail, dp->d_name); break; } diff --git a/src/nvim/path.c b/src/nvim/path.c index 0c18ab7bd4..4e05c506f8 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -61,10 +61,10 @@ FileComparison path_full_compare(char_u *s1, char_u *s2, int checkname) FileID file_id_1, file_id_2; expand_env(s1, exp1, MAXPATHL); - bool id_ok_1 = os_get_file_id((char *)exp1, &file_id_1); - bool id_ok_2 = os_get_file_id((char *)s2, &file_id_2); + bool id_ok_1 = os_fileid((char *)exp1, &file_id_1); + bool id_ok_2 = os_fileid((char *)s2, &file_id_2); if (!id_ok_1 && !id_ok_2) { - // If os_get_file_id() doesn't work, may compare the names. + // If os_fileid() doesn't work, may compare the names. if (checkname) { vim_FullName(exp1, full1, MAXPATHL, FALSE); vim_FullName(s2, full2, MAXPATHL, FALSE); @@ -77,7 +77,7 @@ FileComparison path_full_compare(char_u *s1, char_u *s2, int checkname) if (!id_ok_1 || !id_ok_2) { return kOneFileMissing; } - if (os_file_id_equal(&file_id_1, &file_id_2)) { + if (os_fileid_equal(&file_id_1, &file_id_2)) { return kEqualFiles; } return kDifferentFiles; @@ -1304,7 +1304,7 @@ void simplify_filename(char_u *filename) saved_char = p[-1]; p[-1] = NUL; FileInfo file_info; - if (!os_get_file_info_link((char *)filename, &file_info)) { + if (!os_fileinfo_link((char *)filename, &file_info)) { do_strip = TRUE; } p[-1] = saved_char; @@ -1327,7 +1327,7 @@ void simplify_filename(char_u *filename) * components. */ saved_char = *tail; *tail = NUL; - if (os_get_file_info((char *)filename, &file_info)) { + if (os_fileinfo((char *)filename, &file_info)) { do_strip = TRUE; } else @@ -1343,15 +1343,15 @@ void simplify_filename(char_u *filename) * component's parent directory.) */ FileInfo new_file_info; if (p == start && relative) { - os_get_file_info(".", &new_file_info); + os_fileinfo(".", &new_file_info); } else { saved_char = *p; *p = NUL; - os_get_file_info((char *)filename, &new_file_info); + os_fileinfo((char *)filename, &new_file_info); *p = saved_char; } - if (!os_file_info_id_equal(&file_info, &new_file_info)) { + if (!os_fileinfo_id_equal(&file_info, &new_file_info)) { do_strip = FALSE; /* We don't disable stripping of later * components since the unstripped path name is diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 2415858e0f..ce2a80adaa 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1294,7 +1294,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) int len; int old_KeyTyped = KeyTyped; /* getting file may reset it */ int ok = OK; - int usable_win; + bool usable_win; if (qi == NULL) qi = &ql_info; @@ -1379,14 +1379,16 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) * For ":helpgrep" find a help window or open one. */ if (qf_ptr->qf_type == 1 && (!curwin->w_buffer->b_help || cmdmod.tab != 0)) { - win_T *wp; + win_T *wp = NULL; - if (cmdmod.tab != 0) - wp = NULL; - else - for (wp = firstwin; wp != NULL; wp = wp->w_next) - if (wp->w_buffer != NULL && wp->w_buffer->b_help) + if (cmdmod.tab == 0) { + FOR_ALL_WINDOWS(wp2) { + if (wp2->w_buffer != NULL && wp2->w_buffer->b_help) { + wp = wp2; break; + } + } + } if (wp != NULL && wp->w_buffer->b_nwindows > 0) win_enter(wp, true); else { @@ -1433,26 +1435,29 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) if (qf_ptr->qf_fnum == 0) goto theend; - usable_win = 0; + usable_win = false; ll_ref = curwin->w_llist_ref; if (ll_ref != NULL) { /* Find a window using the same location list that is not a * quickfix window. */ - FOR_ALL_WINDOWS(usable_win_ptr) - if (usable_win_ptr->w_llist == ll_ref - && usable_win_ptr->w_buffer->b_p_bt[0] != 'q') { - usable_win = 1; - break; + FOR_ALL_WINDOWS(wp) { + if (wp->w_llist == ll_ref + && wp->w_buffer->b_p_bt[0] != 'q') { + usable_win = true; + usable_win_ptr = wp; + break; + } } } if (!usable_win) { /* Locate a window showing a normal buffer */ - FOR_ALL_WINDOWS(win) - if (win->w_buffer->b_p_bt[0] == NUL) { - usable_win = 1; - break; + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer->b_p_bt[0] == NUL) { + usable_win = true; + break; + } } } @@ -1468,7 +1473,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) { if (wp->w_buffer->b_fnum == qf_ptr->qf_fnum) { goto_tabpage_win(tp, wp); - usable_win = 1; + usable_win = true; goto win_found; } } @@ -1501,9 +1506,12 @@ win_found: win = usable_win_ptr; if (win == NULL) { /* Find the window showing the selected file */ - FOR_ALL_WINDOWS(win) - if (win->w_buffer->b_fnum == qf_ptr->qf_fnum) - break; + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer->b_fnum == qf_ptr->qf_fnum) { + win = wp; + break; + } + } if (win == NULL) { /* Find a previous usable window */ win = curwin; @@ -2212,13 +2220,13 @@ static int is_qf_win(win_T *win, qf_info_T *qi) */ static win_T *qf_find_win(qf_info_T *qi) { - win_T *win; - - FOR_ALL_WINDOWS(win) - if (is_qf_win(win, qi)) - break; + FOR_ALL_WINDOWS(win) { + if (is_qf_win(win, qi)) { + return win; + } + } - return win; + return NULL; } /* @@ -2564,7 +2572,7 @@ static char_u *get_mef_name(void) STRCAT(name, p + 2); // Don't accept a symbolic link, its a security risk. FileInfo file_info; - bool file_or_link_found = os_get_file_info_link((char *)name, &file_info); + bool file_or_link_found = os_fileinfo_link((char *)name, &file_info); if (!file_or_link_found) { break; } @@ -3478,7 +3486,6 @@ void ex_helpgrep(exarg_T *eap) char_u *lang; qf_info_T *qi = &ql_info; int new_qi = FALSE; - win_T *wp; char_u *au_name = NULL; /* Check for a specified language */ @@ -3501,16 +3508,16 @@ void ex_helpgrep(exarg_T *eap) p_cpo = empty_option; if (eap->cmdidx == CMD_lhelpgrep) { - /* Find an existing help window */ - FOR_ALL_WINDOWS(wp) - if (wp->w_buffer != NULL && wp->w_buffer->b_help) - break; + qi = NULL; - if (wp == NULL) /* Help window not found */ - qi = NULL; - else - qi = wp->w_llist; + /* Find an existing help window */ + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer != NULL && wp->w_buffer->b_help) { + qi = wp->w_llist; + } + } + /* Help window not found */ if (qi == NULL) { /* Allocate a new location list for help text matches */ qi = ll_new_list(); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 811f265902..122c23ed84 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -206,10 +206,7 @@ void redraw_later_clear(void) */ void redraw_all_later(int type) { - win_T *wp; - - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { redraw_win_later(wp, type); } } @@ -224,12 +221,10 @@ void redraw_curbuf_later(int type) void redraw_buf_later(buf_T *buf, int type) { - win_T *wp; - - FOR_ALL_WINDOWS(wp) - { - if (wp->w_buffer == buf) + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer == buf) { redraw_win_later(wp, type); + } } } @@ -390,7 +385,6 @@ void update_curbuf(int type) */ void update_screen(int type) { - win_T *wp; static int did_intro = FALSE; int did_one; @@ -438,8 +432,7 @@ void update_screen(int type) check_for_delay(FALSE); if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) type = CLEAR; - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp->w_winrow < msg_scrolled) { if (wp->w_winrow + wp->w_height > msg_scrolled && wp->w_redr_type < REDRAW_TOP @@ -450,8 +443,9 @@ void update_screen(int type) } else { wp->w_redr_type = NOT_VALID; if (wp->w_winrow + wp->w_height + wp->w_status_height - <= msg_scrolled) + <= msg_scrolled) { wp->w_redr_status = TRUE; + } } } } @@ -512,8 +506,7 @@ void update_screen(int type) * Correct stored syntax highlighting info for changes in each displayed * buffer. Each buffer must only be done once. */ - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp->w_buffer->b_mod_set) { win_T *wwp; @@ -534,8 +527,7 @@ void update_screen(int type) */ did_one = FALSE; search_hl.rm.regprog = NULL; - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp->w_redr_type != 0) { cursor_off(); if (!did_one) { @@ -558,8 +550,9 @@ void update_screen(int type) /* Reset b_mod_set flags. Going through all windows is probably faster * than going through all buffers (there could be many buffers). */ - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { wp->w_buffer->b_mod_set = FALSE; + } updating_screen = FALSE; @@ -662,31 +655,28 @@ static void update_finish(void) void update_debug_sign(buf_T *buf, linenr_T lnum) { - win_T *wp; int doit = FALSE; win_foldinfo.fi_level = 0; /* update/delete a specific mark */ - FOR_ALL_WINDOWS(wp) - { - if (buf != NULL && lnum > 0) { - if (wp->w_buffer == buf && lnum >= wp->w_topline - && lnum < wp->w_botline) - { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { - wp->w_redraw_top = lnum; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { - wp->w_redraw_bot = lnum; - } - redraw_win_later(wp, VALID); - } - } else { - redraw_win_later(wp, VALID); - } - if (wp->w_redr_type != 0) { - doit = TRUE; + FOR_ALL_WINDOWS(wp) { + if (buf != NULL && lnum > 0) { + if (wp->w_buffer == buf && lnum >= wp->w_topline + && lnum < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { + wp->w_redraw_top = lnum; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { + wp->w_redraw_bot = lnum; + } + redraw_win_later(wp, VALID); } + } else { + redraw_win_later(wp, VALID); + } + if (wp->w_redr_type != 0) { + doit = TRUE; + } } /* Return when there is nothing to do, screen updating is already @@ -698,13 +688,13 @@ void update_debug_sign(buf_T *buf, linenr_T lnum) /* update all windows that need updating */ update_prepare(); - for (wp = firstwin; wp; wp = wp->w_next) { - if (wp->w_redr_type != 0) { - win_update(wp); - } - if (wp->w_redr_status) { - win_redr_status(wp); - } + FOR_ALL_WINDOWS(wp) { + if (wp->w_redr_type != 0) { + win_update(wp); + } + if (wp->w_redr_status) { + win_redr_status(wp); + } } update_finish(); @@ -1366,8 +1356,9 @@ static void win_update(win_T *wp) (foldmethodIsSyntax(wp) && hasAnyFolding(wp)) || syntax_check_changed(lnum))) - /* match in fixed position might need redraw */ - || wp->w_match_head != NULL + // match in fixed position might need redraw + // if lines were inserted or deleted + || (wp->w_match_head != NULL && buf->b_mod_xlines != 0) ))))) { if (lnum == mod_top) top_to_mod = FALSE; @@ -2658,38 +2649,42 @@ win_line ( shl->startcol = MAXCOL; shl->endcol = MAXCOL; shl->attr_cur = 0; - if (shl->rm.regprog != NULL) { - v = (long)(ptr - line); - next_search_hl(wp, shl, lnum, (colnr_T)v); + v = (long)(ptr - line); + if (cur != NULL) { + cur->pos.cur = 0; + } + next_search_hl(wp, shl, lnum, (colnr_T)v, cur); - /* Need to get the line again, a multi-line regexp may have made it - * invalid. */ - line = ml_get_buf(wp->w_buffer, lnum, FALSE); - ptr = line + v; + // Need to get the line again, a multi-line regexp may have made it + // invalid. + line = ml_get_buf(wp->w_buffer, lnum, false); + ptr = line + v; - if (shl->lnum != 0 && shl->lnum <= lnum) { - if (shl->lnum == lnum) - shl->startcol = shl->rm.startpos[0].col; - else - shl->startcol = 0; - if (lnum == shl->lnum + shl->rm.endpos[0].lnum - - shl->rm.startpos[0].lnum) + if (shl->lnum != 0 && shl->lnum <= lnum) { + if (shl->lnum == lnum) { + shl->startcol = shl->rm.startpos[0].col; + } else { + shl->startcol = 0; + } + if (lnum == shl->lnum + shl->rm.endpos[0].lnum + - shl->rm.startpos[0].lnum) { shl->endcol = shl->rm.endpos[0].col; - else + } else { shl->endcol = MAXCOL; - /* Highlight one character for an empty match. */ - if (shl->startcol == shl->endcol) { - if (has_mbyte && line[shl->endcol] != NUL) - shl->endcol += (*mb_ptr2len)(line + shl->endcol); - else - ++shl->endcol; - } - if ((long)shl->startcol < v) { /* match at leftcol */ - shl->attr_cur = shl->attr; - search_attr = shl->attr; - } - area_highlighting = TRUE; } + // Highlight one character for an empty match. + if (shl->startcol == shl->endcol) { + if (has_mbyte && line[shl->endcol] != NUL) { + shl->endcol += (*mb_ptr2len)(line + shl->endcol); + } else { + ++shl->endcol; + } + } + if ((long)shl->startcol < v) { // match at leftcol + shl->attr_cur = shl->attr; + search_attr = shl->attr; + } + area_highlighting = true; } if (shl != &search_hl && cur != NULL) cur = cur->next; @@ -2699,9 +2694,9 @@ win_line ( * when Visual mode is active, because it's not clear what is selected * then. */ if (wp->w_p_cul && lnum == wp->w_cursor.lnum - && !(wp == curwin && VIsual_active)) { + && !(wp == curwin && VIsual_active)) { line_attr = hl_attr(HLF_CUL); - area_highlighting = TRUE; + area_highlighting = true; } off = (unsigned)(current_ScreenLine - ScreenLines); @@ -2942,15 +2937,22 @@ win_line ( shl_flag = TRUE; } else shl = &cur->hl; - while (shl->rm.regprog != NULL) { + if (cur != NULL) { + cur->pos.cur = 0; + } + bool pos_inprogress = true; // mark that a position match search is + // in progress + while (shl->rm.regprog != NULL + || (cur != NULL && pos_inprogress)) { if (shl->startcol != MAXCOL && v >= (long)shl->startcol && v < (long)shl->endcol) { shl->attr_cur = shl->attr; - } else if (v == (long)shl->endcol) { + } else if (v >= (long)shl->endcol) { shl->attr_cur = 0; - next_search_hl(wp, shl, lnum, (colnr_T)v); + next_search_hl(wp, shl, lnum, (colnr_T)v, cur); + pos_inprogress = !(cur == NULL || cur->pos.cur == 0); /* Need to get the line again, a multi-line regexp * may have made it invalid. */ @@ -3563,7 +3565,7 @@ win_line ( } else if (c != NUL) { p_extra = transchar(c); if (n_extra == 0) { - n_extra = byte2cells(c); + n_extra = byte2cells(c) - 1; } if ((dy_flags & DY_UHEX) && wp->w_p_rl) rl_mirror(p_extra); /* reverse "<12>" */ @@ -4552,13 +4554,13 @@ void rl_mirror(char_u *str) */ void status_redraw_all(void) { - win_T *wp; - for (wp = firstwin; wp; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { if (wp->w_status_height) { wp->w_redr_status = TRUE; redraw_later(VALID); } + } } /* @@ -4566,13 +4568,12 @@ void status_redraw_all(void) */ void status_redraw_curbuf(void) { - win_T *wp; - - for (wp = firstwin; wp; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { if (wp->w_status_height != 0 && wp->w_buffer == curbuf) { wp->w_redr_status = TRUE; redraw_later(VALID); } + } } /* @@ -4580,11 +4581,11 @@ void status_redraw_curbuf(void) */ void redraw_statuslines(void) { - win_T *wp; - - for (wp = firstwin; wp; wp = wp->w_next) - if (wp->w_redr_status) + FOR_ALL_WINDOWS(wp) { + if (wp->w_redr_status) { win_redr_status(wp); + } + } if (redraw_tabline) draw_tabline(); } @@ -5602,9 +5603,16 @@ static void prepare_search_hl(win_T *wp, linenr_T lnum) NULL, NULL, TRUE, NULL)) break; } + if (cur != NULL) { + cur->pos.cur = 0; + } + bool pos_inprogress = true; // mark that a position match search is + // in progress n = 0; - while (shl->first_lnum < lnum && shl->rm.regprog != NULL) { - next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n); + while (shl->first_lnum < lnum && (shl->rm.regprog != NULL + || (cur != NULL && pos_inprogress))) { + next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n, cur); + pos_inprogress = !(cur == NULL || cur->pos.cur == 0); if (shl->lnum != 0) { shl->first_lnum = shl->lnum + shl->rm.endpos[0].lnum @@ -5634,12 +5642,13 @@ next_search_hl ( win_T *win, match_T *shl, /* points to search_hl or a match */ linenr_T lnum, - colnr_T mincol /* minimal column for a match */ + colnr_T mincol, /* minimal column for a match */ + matchitem_T *cur /* to retrieve match positions if any */ ) { linenr_T l; colnr_T matchcol; - long nmatched; + long nmatched = 0; if (shl->lnum != 0) { /* Check for three situations: @@ -5674,8 +5683,8 @@ next_search_hl ( if (shl->lnum == 0) matchcol = 0; else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL - || (shl->rm.endpos[0].lnum == 0 - && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { + || (shl->rm.endpos[0].lnum == 0 + && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { char_u *ml; matchcol = shl->rm.startpos[0].col; @@ -5693,20 +5702,22 @@ next_search_hl ( matchcol = shl->rm.endpos[0].col; shl->lnum = lnum; - nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, - &(shl->tm) - ); - if (called_emsg || got_int) { - /* Error while handling regexp: stop using this regexp. */ - if (shl == &search_hl) { - /* don't free regprog in the match list, it's a copy */ - vim_regfree(shl->rm.regprog); - SET_NO_HLSEARCH(TRUE); + if (shl->rm.regprog != NULL) { + nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, &(shl->tm)); + if (called_emsg || got_int) { + // Error while handling regexp: stop using this regexp. + if (shl == &search_hl) { + // don't free regprog in the match list, it's a copy + vim_regfree(shl->rm.regprog); + SET_NO_HLSEARCH(TRUE); + } + shl->rm.regprog = NULL; + shl->lnum = 0; + got_int = FALSE; // avoid the "Type :quit to exit Vim" message + break; } - shl->rm.regprog = NULL; - shl->lnum = 0; - got_int = FALSE; /* avoid the "Type :quit to exit Vim" message */ - break; + } else if (cur != NULL) { + nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); } if (nmatched == 0) { shl->lnum = 0; /* no match found */ @@ -5722,6 +5733,59 @@ next_search_hl ( } } +static int +next_search_hl_pos( + match_T *shl, // points to a match + linenr_T lnum, + posmatch_T *posmatch, // match positions + colnr_T mincol // minimal column for a match +) +{ + int i; + int bot = -1; + + shl->lnum = 0; + for (i = posmatch->cur; i < MAXPOSMATCH; i++) { + if (posmatch->pos[i].lnum == 0) { + break; + } + if (posmatch->pos[i].col < mincol) { + continue; + } + if (posmatch->pos[i].lnum == lnum) { + if (shl->lnum == lnum) { + // partially sort positions by column numbers + // on the same line + if (posmatch->pos[i].col < posmatch->pos[bot].col) { + llpos_T tmp = posmatch->pos[i]; + + posmatch->pos[i] = posmatch->pos[bot]; + posmatch->pos[bot] = tmp; + } + } else { + bot = i; + shl->lnum = lnum; + } + } + } + posmatch->cur = 0; + if (shl->lnum == lnum) { + colnr_T start = posmatch->pos[bot].col == 0 + ? 0: posmatch->pos[bot].col - 1; + colnr_T end = posmatch->pos[bot].col == 0 + ? MAXCOL : start + posmatch->pos[bot].len; + + shl->rm.startpos[0].lnum = 0; + shl->rm.startpos[0].col = start; + shl->rm.endpos[0].lnum = 0; + shl->rm.endpos[0].col = end; + posmatch->cur = bot + 1; + return true; + } + return false; +} + + static void screen_start_highlight(int attr) { attrentry_T *aep = NULL; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index d277d71d99..5e40ae7708 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -4155,8 +4155,6 @@ void spell_free_all(void) // Used after 'encoding' is set and when ":mkspell" was used. void spell_reload(void) { - win_T *wp; - // Initialize the table for spell_iswordp(). init_spell_chartab(); @@ -4164,7 +4162,7 @@ void spell_reload(void) spell_free_all(); // Go through all buffers and handle 'spelllang'. - for (wp = firstwin; wp != NULL; wp = wp->w_next) { + FOR_ALL_WINDOWS(wp) { // Only load the wordlists when 'spelllang' is set and there is a // window for this buffer in which 'spell' is set. if (*wp->w_s->b_p_spl != NUL) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 5f6e09925e..5001d6498e 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -984,16 +984,13 @@ static void syn_stack_free_block(synblock_T *block) */ void syn_stack_free_all(synblock_T *block) { - win_T *wp; - syn_stack_free_block(block); - /* When using "syntax" fold method, must update all folds. */ - FOR_ALL_WINDOWS(wp) - { - if (wp->w_s == block && foldmethodIsSyntax(wp)) + FOR_ALL_WINDOWS(wp) { + if (wp->w_s == block && foldmethodIsSyntax(wp)) { foldUpdateAll(wp); + } } } @@ -1075,14 +1072,12 @@ static void syn_stack_alloc(void) */ void syn_stack_apply_changes(buf_T *buf) { - win_T *wp; - syn_stack_apply_changes_block(&buf->b_s, buf); - FOR_ALL_WINDOWS(wp) - { - if ((wp->w_buffer == buf) && (wp->w_s != &buf->b_s)) + FOR_ALL_WINDOWS(wp) { + if ((wp->w_buffer == buf) && (wp->w_s != &buf->b_s)) { syn_stack_apply_changes_block(wp->w_s, buf); + } } } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 92fd47ff6b..81dc49e800 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -33,7 +33,8 @@ SCRIPTS := test_autoformat_join.out \ test106.out test107.out \ test_options.out \ test_listlbr.out test_listlbr_utf8.out \ - test_breakindent.out + test_breakindent.out \ + test_insertcount.out SCRIPTS_GUI := test16.out diff --git a/src/nvim/testdir/test55.in b/src/nvim/testdir/test55.in index 85a8063774..8e073f30f2 100644 --- a/src/nvim/testdir/test55.in +++ b/src/nvim/testdir/test55.in @@ -332,6 +332,12 @@ let l = [0, 1, 2, 3] :$put =string(reverse(sort(l))) :$put =string(sort(reverse(sort(l)))) :$put =string(uniq(sort(l))) +:let l=[7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] +:$put =string(sort(copy(l), 'n')) +:let l=[7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] +:$put =string(sort(copy(l), 1)) +:$put =string(sort(copy(l), 'i')) +:$put =string(sort(copy(l))) :" :" splitting a string to a List :$put =string(split(' aa bb ')) diff --git a/src/nvim/testdir/test55.ok b/src/nvim/testdir/test55.ok index be476e8e93..dfd8060db7 100644 --- a/src/nvim/testdir/test55.ok +++ b/src/nvim/testdir/test55.ok @@ -101,6 +101,10 @@ caught a:000[3] [[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'] ['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]] ['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]] +[-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255] +['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}] +['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}] +['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}] ['aa', 'bb'] ['aa', 'bb'] ['', 'aa', 'bb', ''] diff --git a/src/nvim/testdir/test63.in b/src/nvim/testdir/test63.in index 74339c3e35..ea66ee6dea 100644 --- a/src/nvim/testdir/test63.in +++ b/src/nvim/testdir/test63.in @@ -1,5 +1,5 @@ Test for ":match", ":2match", ":3match", "clearmatches()", "getmatches()", -"matchadd()", "matcharg()", "matchdelete()", and "setmatches()". +"matchadd()", "matchaddpos()", "matcharg()", "matchdelete()", and "setmatches()". STARTTEST :so small.vim @@ -147,9 +147,26 @@ STARTTEST :unlet rf1 :unlet rf2 :unlet rf3 -:highlight clear MyGroup1 -:highlight clear MyGroup2 -:highlight clear MyGroup3 +:" --- Check that "matchaddpos()" positions matches correctly +:let @r .= "*** Test 11:\n" +:set nolazyredraw +:call setline(1, 'abcdefghijklmnopq') +:call matchaddpos("MyGroup1", [[1, 5], [1, 8, 3]], 10, 3) +:1 +:redraw! +:let v1 = screenattr(1, 1) +:let v5 = screenattr(1, 5) +:let v6 = screenattr(1, 6) +:let v8 = screenattr(1, 8) +:let v10 = screenattr(1, 10) +:let v11 = screenattr(1, 11) +:let @r .= string(getmatches())."\n" +:if v1 != v5 && v6 == v1 && v8 == v5 && v10 == v5 && v11 == v1 +: let @r .= "OK\n" +:else +: let @r .= "FAILED\n" +:endif +:call clearmatches() G"rp :/^Results/,$wq! test.out ENDTEST diff --git a/src/nvim/testdir/test63.ok b/src/nvim/testdir/test63.ok index 14973985eb..f804b693ac 100644 --- a/src/nvim/testdir/test63.ok +++ b/src/nvim/testdir/test63.ok @@ -9,3 +9,6 @@ Results of test63: *** Test 8: OK *** Test 9: OK *** Test 10: OK +*** Test 11: +[{'group': 'MyGroup1', 'id': 3, 'priority': 10, 'pos1': [1, 5, 1], 'pos2': [1, 8, 3]}] +OK diff --git a/src/nvim/testdir/test86.in b/src/nvim/testdir/test86.in index ecb06bafd3..11ff35cfd3 100644 --- a/src/nvim/testdir/test86.in +++ b/src/nvim/testdir/test86.in @@ -9,7 +9,7 @@ STARTTEST :so small.vim :set encoding=latin1 :set noswapfile -:if !has('python') || has('neovim') | e! test.ok | wq! test.out | endif +:if !has('python') || has('nvim') | e! test.ok | wq! test.out | endif :lang C :fun Test() :py import vim diff --git a/src/nvim/testdir/test_insertcount.in b/src/nvim/testdir/test_insertcount.in new file mode 100644 index 0000000000..7a40573e63 --- /dev/null +++ b/src/nvim/testdir/test_insertcount.in @@ -0,0 +1,14 @@ +Tests for repeating insert and replace. + +STARTTEST +:so small.vim +:/Second +4gro +:/^First/,$wq! test.out +:" get here when failed and in Insert mode +:.wq! test.out +ENDTEST + +First line +Second line +Last line diff --git a/src/nvim/testdir/test_insertcount.ok b/src/nvim/testdir/test_insertcount.ok new file mode 100644 index 0000000000..57afab00ff --- /dev/null +++ b/src/nvim/testdir/test_insertcount.ok @@ -0,0 +1,3 @@ +First line +ooooecond line +Last line diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 8c7b5b38e9..9fbe21aeb0 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1116,8 +1116,8 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) */ FileInfo file_info_old; FileInfo file_info_new; - if (os_get_file_info((char *)buf->b_ffname, &file_info_old) - && os_get_file_info((char *)file_name, &file_info_new) + if (os_fileinfo((char *)buf->b_ffname, &file_info_old) + && os_fileinfo((char *)file_name, &file_info_new) && file_info_old.stat.st_gid != file_info_new.stat.st_gid && os_fchown(fd, -1, file_info_old.stat.st_gid) != 0) { os_setperm(file_name, (perm & 0707) | ((perm & 07) << 3)); @@ -1249,8 +1249,8 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) * owner of the text file or equal to the current user. */ FileInfo file_info_orig; FileInfo file_info_undo; - if (os_get_file_info((char *)orig_name, &file_info_orig) - && os_get_file_info((char *)file_name, &file_info_undo) + if (os_fileinfo((char *)orig_name, &file_info_orig) + && os_fileinfo((char *)file_name, &file_info_undo) && file_info_orig.stat.st_uid != file_info_undo.stat.st_uid && file_info_undo.stat.st_uid != getuid()) { if (p_verbose > 0) { @@ -2191,12 +2191,10 @@ u_undo_end ( u_add_time(msgbuf, sizeof(msgbuf), uhp->uh_time); { - win_T *wp; - - FOR_ALL_WINDOWS(wp) - { - if (wp->w_buffer == curbuf && wp->w_p_cole > 0) + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer == curbuf && wp->w_p_cole > 0) { redraw_win_later(wp, NOT_VALID); + } } } diff --git a/src/nvim/version.c b/src/nvim/version.c index b164f94bb1..bf0dace6ef 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -186,17 +186,17 @@ static char *(features[]) = { static int included_patches[] = { // Add new patch number below this line //410, - //409, + //409 NA //408, //407, //406, //405, - //404, - //403, + //404 NA + //403 NA //402, - //401, - //400, - //399, + //401 NA + //400 NA + //399 NA //398, //397, //396, @@ -208,10 +208,10 @@ static int included_patches[] = { //390, //389, 388, - //387, + 387, //386, //385, - //384, + //384 NA //383, //382, //381, @@ -219,53 +219,53 @@ static int included_patches[] = { //379, //378, //377, - //376, + 376, //375, //374, - //373, + //373 NA //372, 371, 370, - //369, - //368, - //367, + 369, + 368, + 367, //366, //365, //364, //363, - //362, + 362, //361, //360, //359, - //358, - //357, + 358, + 357, //356 NA //355, - //354, + //354 NA 353, 352, - //351, + 351, //350, - //349, - //348, - //347, + 349, + 348, + 347, 346, - //345, - //344, - //343, + 345, + 344, + 343, //342 NA - //341, + 341, //340 NA 339, 338, - //337, + 337, //336, 335, - //334, + 334, //333 NA //332 NA 331, - //330, + 330, 329, 328, 327, @@ -283,7 +283,7 @@ static int included_patches[] = { 315, 314, //313, - //312, + 312, //311, //310, 309, @@ -298,7 +298,7 @@ static int included_patches[] = { //300 NA //299 NA 298, - //297, + 297, 296, 295, 294, @@ -312,19 +312,19 @@ static int included_patches[] = { 286, 285, 284, - //283, + //283 NA 282, 281, 280, 279, - //278, + 278, 277, 276, 275, 274, //273 NA 272, - //271, + //271 NA //270 NA 269, 268, @@ -332,11 +332,11 @@ static int included_patches[] = { 266, 265, 264, - //263, + //263 NA 262, 261, 260, - //259, + //259 NA //258 NA //257 NA //256, @@ -345,7 +345,7 @@ static int included_patches[] = { 253, //252 NA 251, - //250, + //250 NA //249, //248, //247, diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 6479aeaafb..3ef291ef7c 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -8,6 +8,8 @@ #ifndef NVIM_VIM_H # define NVIM_VIM_H +#define min(X, Y) (X < Y ? X : Y) + #include "nvim/types.h" #include "nvim/pos.h" // for linenr_T, MAXCOL, etc... diff --git a/src/nvim/window.c b/src/nvim/window.c index 3499b14688..27fb160035 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -982,13 +982,13 @@ static void win_init_some(win_T *newp, win_T *oldp) */ int win_valid(win_T *win) { - win_T *wp; - if (win == NULL) return FALSE; - for (wp = firstwin; wp != NULL; wp = wp->w_next) - if (wp == win) + FOR_ALL_WINDOWS(wp) { + if (wp == win) { return TRUE; + } + } return FALSE; } @@ -997,11 +997,11 @@ int win_valid(win_T *win) */ int win_count(void) { - win_T *wp; int count = 0; - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { ++count; + } return count; } @@ -1677,20 +1677,19 @@ static int last_window(void) * Return TRUE if there is only one window other than "aucmd_win" in the * current tab page. */ -int one_window(void) +bool one_window(void) { - win_T *wp; - int seen_one = FALSE; + bool seen_one = false; - FOR_ALL_WINDOWS(wp) - { + FOR_ALL_WINDOWS(wp) { if (wp != aucmd_win) { - if (seen_one) - return FALSE; - seen_one = TRUE; + if (seen_one) { + return false; + } + seen_one = true; } } - return TRUE; + return true; } /* @@ -2002,6 +2001,10 @@ void win_free_all(void) while (firstwin != NULL) (void)win_free_mem(firstwin, &dummy, NULL); + + // No window should be used after this. Set curwin to NULL to crash + // instead of using freed memory. + curwin = NULL; } #endif @@ -3476,14 +3479,14 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int tri */ win_T *buf_jump_open_win(buf_T *buf) { - win_T *wp; + FOR_ALL_WINDOWS(wp) { + if (wp->w_buffer == buf) { + win_enter(wp, false); + return wp; + } + } - for (wp = firstwin; wp != NULL; wp = wp->w_next) - if (wp->w_buffer == buf) - break; - if (wp != NULL) - win_enter(wp, false); - return wp; + return NULL; } /* @@ -3785,11 +3788,9 @@ void shell_new_columns(void) void win_size_save(garray_T *gap) { - win_T *wp; - ga_init(gap, (int)sizeof(int), 1); ga_grow(gap, win_count() * 2); - for (wp = firstwin; wp != NULL; wp = wp->w_next) { + FOR_ALL_WINDOWS(wp) { ((int *)gap->ga_data)[gap->ga_len++] = wp->w_width + wp->w_vsep_width; ((int *)gap->ga_data)[gap->ga_len++] = wp->w_height; @@ -3802,13 +3803,16 @@ void win_size_save(garray_T *gap) */ void win_size_restore(garray_T *gap) { - win_T *wp; - if (win_count() * 2 == gap->ga_len) { - int i = 0; - for (wp = firstwin; wp != NULL; wp = wp->w_next) { - frame_setwidth(wp->w_frame, ((int *)gap->ga_data)[i++]); - win_setheight_win(((int *)gap->ga_data)[i++], wp); + /* The order matters, because frames contain other frames, but it's + * difficult to get right. The easy way out is to do it twice. */ + for (int j = 0; j < 2; ++j) + { + int i = 0; + FOR_ALL_WINDOWS(wp) { + frame_setwidth(wp->w_frame, ((int *)gap->ga_data)[i++]); + win_setheight_win(((int *)gap->ga_data)[i++], wp); + } } /* recompute the window positions */ (void)win_comp_pos(); @@ -4234,14 +4238,14 @@ void win_setminheight(void) { int room; int first = TRUE; - win_T *wp; /* loop until there is a 'winminheight' that is possible */ while (p_wmh > 0) { /* TODO: handle vertical splits */ room = -p_wh; - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { room += wp->w_height - p_wmh; + } if (room >= 0) break; --p_wmh; @@ -4932,20 +4936,21 @@ int min_rows(void) int only_one_window(void) { int count = 0; - win_T *wp; /* If there is another tab page there always is another window. */ if (first_tabpage->tp_next != NULL) return FALSE; - for (wp = firstwin; wp != NULL; wp = wp->w_next) + FOR_ALL_WINDOWS(wp) { if (wp->w_buffer != NULL && (!((wp->w_buffer->b_help && !curbuf->b_help) || wp->w_p_pvw ) || wp == curwin) && wp != aucmd_win - ) + ) { ++count; + } + } return count <= 1; } @@ -5190,16 +5195,18 @@ void restore_buffer(buf_T *save_curbuf) * If no particular ID is desired, -1 must be specified for 'id'. * Return ID of added match, -1 on failure. */ -int match_add(win_T *wp, char_u *grp, char_u *pat, int prio, int id) +int match_add(win_T *wp, char_u *grp, char_u *pat, int prio, int id, list_T *pos_list) { matchitem_T *cur; matchitem_T *prev; matchitem_T *m; int hlg_id; - regprog_T *regprog; + regprog_T *regprog = NULL; + int rtype = SOME_VALID; - if (*grp == NUL || *pat == NUL) + if (*grp == NUL || (pat != NULL && *pat == NUL)) { return -1; + } if (id < -1 || id == 0) { EMSGN("E799: Invalid ID: %" PRId64 " (must be greater than or equal to 1)", @@ -5220,7 +5227,7 @@ int match_add(win_T *wp, char_u *grp, char_u *pat, int prio, int id) EMSG2(_(e_nogroup), grp); return -1; } - if ((regprog = vim_regcomp(pat, RE_MAGIC)) == NULL) { + if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL) { EMSG2(_(e_invarg2), pat); return -1; } @@ -5236,15 +5243,106 @@ int match_add(win_T *wp, char_u *grp, char_u *pat, int prio, int id) } /* Build new match. */ - m = xmalloc(sizeof(matchitem_T)); + m = xcalloc(1, sizeof(matchitem_T)); m->id = id; m->priority = prio; - m->pattern = vim_strsave(pat); + m->pattern = pat == NULL ? NULL: vim_strsave(pat); m->hlg_id = hlg_id; m->match.regprog = regprog; m->match.rmm_ic = FALSE; m->match.rmm_maxcol = 0; + // Set up position matches + if (pos_list != NULL) + { + linenr_T toplnum = 0; + linenr_T botlnum = 0; + listitem_T *li; + int i; + + for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH; + i++, li = li->li_next) { + linenr_T lnum = 0; + colnr_T col = 0; + int len = 1; + list_T *subl; + listitem_T *subli; + int error = false; + + if (li->li_tv.v_type == VAR_LIST) { + subl = li->li_tv.vval.v_list; + if (subl == NULL) { + goto fail; + } + subli = subl->lv_first; + if (subli == NULL) { + goto fail; + } + lnum = get_tv_number_chk(&subli->li_tv, &error); + if (error == true) { + goto fail; + } + if (lnum == 0) { + --i; + continue; + } + m->pos.pos[i].lnum = lnum; + subli = subli->li_next; + if (subli != NULL) { + col = get_tv_number_chk(&subli->li_tv, &error); + if (error == true) + goto fail; + subli = subli->li_next; + if (subli != NULL) { + len = get_tv_number_chk(&subli->li_tv, &error); + if (error == true) { + goto fail; + } + } + } + m->pos.pos[i].col = col; + m->pos.pos[i].len = len; + } else if (li->li_tv.v_type == VAR_NUMBER) { + if (li->li_tv.vval.v_number == 0) { + --i; + continue; + } + m->pos.pos[i].lnum = li->li_tv.vval.v_number; + m->pos.pos[i].col = 0; + m->pos.pos[i].len = 0; + } else { + EMSG(_("List or number required")); + goto fail; + } + if (toplnum == 0 || lnum < toplnum) { + toplnum = lnum; + } + if (botlnum == 0 || lnum >= botlnum) { + botlnum = lnum + 1; + } + } + + // Calculate top and bottom lines for redrawing area + if (toplnum != 0){ + if (wp->w_buffer->b_mod_set) { + if (wp->w_buffer->b_mod_top > toplnum) { + wp->w_buffer->b_mod_top = toplnum; + } + if (wp->w_buffer->b_mod_bot < botlnum) { + wp->w_buffer->b_mod_bot = botlnum; + } + } else { + wp->w_buffer->b_mod_set = TRUE; + wp->w_buffer->b_mod_top = toplnum; + wp->w_buffer->b_mod_bot = botlnum; + wp->w_buffer->b_mod_xlines = 0; + } + m->pos.toplnum = toplnum; + m->pos.botlnum = botlnum; + rtype = VALID; + } + } + /* Insert new match. The match list is in ascending order with regard to * the match priorities. */ cur = wp->w_match_head; @@ -5259,8 +5357,12 @@ int match_add(win_T *wp, char_u *grp, char_u *pat, int prio, int id) prev->next = m; m->next = cur; - redraw_later(SOME_VALID); + redraw_later(rtype); return id; + +fail: + free(m); + return -1; } /* @@ -5271,6 +5373,7 @@ int match_delete(win_T *wp, int id, int perr) { matchitem_T *cur = wp->w_match_head; matchitem_T *prev = cur; + int rtype = SOME_VALID; if (id < 1) { if (perr == TRUE) @@ -5294,8 +5397,24 @@ int match_delete(win_T *wp, int id, int perr) prev->next = cur->next; vim_regfree(cur->match.regprog); free(cur->pattern); + if (cur->pos.toplnum != 0) { + if (wp->w_buffer->b_mod_set) { + if (wp->w_buffer->b_mod_top > cur->pos.toplnum) { + wp->w_buffer->b_mod_top = cur->pos.toplnum; + } + if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) { + wp->w_buffer->b_mod_bot = cur->pos.botlnum; + } + } else { + wp->w_buffer->b_mod_set = TRUE; + wp->w_buffer->b_mod_top = cur->pos.toplnum; + wp->w_buffer->b_mod_bot = cur->pos.botlnum; + wp->w_buffer->b_mod_xlines = 0; + } + rtype = VALID; + } free(cur); - redraw_later(SOME_VALID); + redraw_later(rtype); return 0; } diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 11d99a7bcb..5244c2af86 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -9,11 +9,6 @@ local buffer = helpers.cimport("./src/nvim/buffer.h") local window = helpers.cimport("./src/nvim/window.h") local option = helpers.cimport("./src/nvim/option.h") ---{ Initialize the options needed for interacting with buffers -window.win_alloc_first() -option.set_init_1() ---} - describe('buffer functions', function() local buflist_new = function(file, flags) diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index dc1fb3fd9c..792894f349 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -212,19 +212,21 @@ local function formatc(str) return table.concat(result) end --- uncomment the following lines (and comment the return) for standalone --- operation (very handy for debugging) +-- standalone operation (very handy for debugging) local function standalone(...) - require "moonscript" Preprocess = require("preprocess") Preprocess.add_to_include_path('./../../src') Preprocess.add_to_include_path('./../../build/include') Preprocess.add_to_include_path('./../../.deps/usr/include') - input = Preprocess.preprocess_stream(arg[1]) + local input = Preprocess.preprocess_stream(arg[1]) local raw = input:read('*all') input:close() + if raw == nil then + print("ERROR: Preprocess.preprocess_stream():read() returned empty") + end + local formatted if #arg == 2 and arg[2] == 'no' then formatted = raw @@ -234,6 +236,9 @@ local function standalone(...) print(formatted) end +-- uncomment this line (and comment the `return`) for standalone debugging +-- example usage: +-- ../../.deps/usr/bin/luajit formatc.lua ../../include/tempfile.h.generated.h +-- ../../.deps/usr/bin/luajit formatc.lua /usr/include/malloc.h -- standalone(...) - return formatc diff --git a/test/unit/garray_spec.lua b/test/unit/garray_spec.lua new file mode 100644 index 0000000000..e779cab8a7 --- /dev/null +++ b/test/unit/garray_spec.lua @@ -0,0 +1,392 @@ +local helpers = require("test.unit.helpers") + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local NULL = helpers.NULL + +local garray = cimport('stdlib.h', './src/nvim/garray.h') + +local itemsize = 14 +local growsize = 95 + +-- define a basic interface to garray. We could make it a lot nicer by +-- constructing a class wrapper around garray. It could for example associate +-- ga_clear_strings to the underlying garray cdata if the garray is a string +-- array. But for now I estimate that that kind of magic might make testing +-- less "transparant" (i.e.: the interface would become quite different as to +-- how one would use it from C. + +-- accessors +local ga_len = function(garr) + return garr[0].ga_len +end + +local ga_maxlen = function(garr) + return garr[0].ga_maxlen +end + +local ga_itemsize = function(garr) + return garr[0].ga_itemsize +end + +local ga_growsize = function(garr) + return garr[0].ga_growsize +end + +local ga_data = function(garr) + return garr[0].ga_data +end + +-- derived accessors +local ga_size = function(garr) + return ga_len(garr) * ga_itemsize(garr) +end + +local ga_maxsize = function(garr) + return ga_maxlen(garr) * ga_itemsize(garr) +end + +local ga_data_as_bytes = function(garr) + return ffi.cast('uint8_t *', ga_data(garr)) +end + +local ga_data_as_strings = function(garr) + return ffi.cast('char **', ga_data(garr)) +end + +local ga_data_as_ints = function(garr) + return ffi.cast('int *', ga_data(garr)) +end + +-- garray manipulation +local ga_init = function(garr, itemsize, growsize) + return garray.ga_init(garr, itemsize, growsize) +end + +local ga_clear = function(garr) + return garray.ga_clear(garr) +end + +local ga_clear_strings = function(garr) + assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) + return garray.ga_clear_strings(garr) +end + +local ga_grow = function(garr, n) + return garray.ga_grow(garr, n) +end + +local ga_concat = function(garr, str) + return garray.ga_concat(garr, to_cstr(str)) +end + +local ga_append = function(garr, b) + if type(b) == 'string' then + return garray.ga_append(garr, string.byte(b)) + else + return garray.ga_append(garr, b) + end +end + +local ga_concat_strings = function(garr) + return internalize(garray.ga_concat_strings(garr)) +end + +local ga_concat_strings_sep = function(garr, sep) + return internalize(garray.ga_concat_strings_sep(garr, to_cstr(sep))) +end + +local ga_remove_duplicate_strings = function(garr) + return garray.ga_remove_duplicate_strings(garr) +end + +-- derived manipulators +local ga_set_len = function(garr, len) + assert.is_true(len <= ga_maxlen(garr)) + garr[0].ga_len = len +end + +local ga_inc_len = function(garr, by) + return ga_set_len(garr, ga_len(garr) + 1) +end + +-- custom append functions +-- not the C ga_append, which only works for bytes +local ga_append_int = function(garr, it) + assert.is_true(ga_itemsize(garr) == ffi.sizeof('int')) + ga_grow(garr, 1) + local data = ga_data_as_ints(garr) + data[ga_len(garr)] = it + return ga_inc_len(garr, 1) +end + +local ga_append_string = function(garr, it) + assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) + -- make a non-garbage collected string and copy the lua string into it, + -- TODO(aktau): we should probably call xmalloc here, though as long as + -- xmalloc is based on malloc it should work. + local mem = ffi.C.malloc(string.len(it) + 1) + ffi.copy(mem, it) + ga_grow(garr, 1) + local data = ga_data_as_strings(garr) + data[ga_len(garr)] = mem + return ga_inc_len(garr, 1) +end + +local ga_append_strings = function(garr, ...) + local prevlen = ga_len(garr) + local len = select('#', ...) + for i = 1, len do + ga_append_string(garr, select(i, ...)) + end + return eq(prevlen + len, ga_len(garr)) +end + +local ga_append_ints = function(garr, ...) + local prevlen = ga_len(garr) + local len = select('#', ...) + for i = 1, len do + ga_append_int(garr, select(i, ...)) + end + return eq(prevlen + len, ga_len(garr)) +end + +-- enhanced constructors +local garray_ctype = ffi.typeof('garray_T[1]') +local new_garray = function() + local garr = garray_ctype() + return ffi.gc(garr, ga_clear) +end + +local new_string_garray = function() + local garr = garray_ctype() + ga_init(garr, ffi.sizeof("unsigned char *"), 1) + return ffi.gc(garr, ga_clear_strings) +end + +local randomByte = function() + return ffi.cast('uint8_t', math.random(0, 255)) +end + +-- scramble the data in a garray +local ga_scramble = function(garr) + local size, bytes = ga_size(garr), ga_data_as_bytes(garr) + for i = 0, size - 1 do + bytes[i] = randomByte() + end +end + +describe('garray', function() + + describe('ga_init', function() + it('initializes the values of the garray', function() + local garr = new_garray() + ga_init(garr, itemsize, growsize) + eq(0, ga_len(garr)) + eq(0, ga_maxlen(garr)) + eq(growsize, ga_growsize(garr)) + eq(itemsize, ga_itemsize(garr)) + eq(NULL, ga_data(garr)) + end) + end) + + describe('ga_grow', function() + local new_and_grow + function new_and_grow(itemsize, growsize, req) + local garr = new_garray() + ga_init(garr, itemsize, growsize) + eq(0, ga_size(garr)) -- should be 0 at first + eq(NULL, ga_data(garr)) -- should be NULL + ga_grow(garr, req) -- add space for `req` items + return garr + end + + it('grows by growsize items if num < growsize', function() + itemsize = 16 + growsize = 4 + local grow_by = growsize - 1 + local garr = new_and_grow(itemsize, growsize, grow_by) + neq(NULL, ga_data(garr)) -- data should be a ptr to memory + eq(growsize, ga_maxlen(garr)) -- we requested LESS than growsize, so... + end) + + it('grows by num items if num > growsize', function() + itemsize = 16 + growsize = 4 + local grow_by = growsize + 1 + local garr = new_and_grow(itemsize, growsize, grow_by) + neq(NULL, ga_data(garr)) -- data should be a ptr to memory + eq(grow_by, ga_maxlen(garr)) -- we requested MORE than growsize, so... + end) + + it('does not grow when nothing is requested', function() + local garr = new_and_grow(16, 4, 0) + eq(NULL, ga_data(garr)) + eq(0, ga_maxlen(garr)) + end) + end) + + describe('ga_clear', function() + it('clears an already allocated array', function() + -- allocate and scramble an array + local garr = garray_ctype() + ga_init(garr, itemsize, growsize) + ga_grow(garr, 4) + ga_set_len(garr, 4) + ga_scramble(garr) + + -- clear it and check + ga_clear(garr) + eq(NULL, ga_data(garr)) + eq(0, ga_maxlen(garr)) + eq(0, ga_len(garr)) + end) + end) + + describe('ga_append', function() + it('can append bytes', function() + -- this is the actual ga_append, the others are just emulated lua + -- versions + local garr = new_garray() + ga_init(garr, ffi.sizeof("uint8_t"), 1) + ga_append(garr, 'h') + ga_append(garr, 'e') + ga_append(garr, 'l') + ga_append(garr, 'l') + ga_append(garr, 'o') + ga_append(garr, 0) + local bytes = ga_data_as_bytes(garr) + eq('hello', ffi.string(bytes)) + end) + + it('can append integers', function() + local garr = new_garray() + ga_init(garr, ffi.sizeof("int"), 1) + local input = { + -20, + 94, + 867615, + 90927, + 86 + } + ga_append_ints(garr, unpack(input)) + local ints = ga_data_as_ints(garr) + for i = 0, #input - 1 do + eq(input[i + 1], ints[i]) + end + end) + + it('can append strings to a growing array of strings', function() + local garr = new_string_garray() + local input = { + "some", + "str", + "\r\n\r●●●●●●,,,", + "hmm", + "got it" + } + ga_append_strings(garr, unpack(input)) + -- check that we can get the same strings out of the array + local strings = ga_data_as_strings(garr) + for i = 0, #input - 1 do + eq(input[i + 1], ffi.string(strings[i])) + end + end) + end) + + describe('ga_concat', function() + it('concatenates the parameter to the growing byte array', function() + local garr = new_garray() + ga_init(garr, ffi.sizeof("char"), 1) + local str = "ohwell●●" + local loop = 5 + for i = 1, loop do + ga_concat(garr, str) + end + + -- ga_concat does NOT append the NUL in the src string to the + -- destination, you have to do that manually by calling something like + -- ga_append(gar, '\0'). I'ts always used like that in the vim + -- codebase. I feel that this is a bit of an unnecesesary + -- micro-optimization. + ga_append(garr, 0) + local result = ffi.string(ga_data_as_bytes(garr)) + eq(string.rep(str, loop), result) + end) + end) + + function test_concat_fn(input, fn, sep) + local garr = new_string_garray() + ga_append_strings(garr, unpack(input)) + if sep == nil then + eq(table.concat(input, ','), fn(garr)) + else + eq(table.concat(input, sep), fn(garr, sep)) + end + end + + describe('ga_concat_strings', function() + it('returns an empty string when concatenating an empty array', function() + test_concat_fn({ }, ga_concat_strings) + end) + + it('can concatenate a non-empty array', function() + test_concat_fn({ + 'oh', + 'my', + 'neovim' + }, ga_concat_strings) + end) + end) + + describe('ga_concat_strings_sep', function() + it('returns an empty string when concatenating an empty array', function() + test_concat_fn({ }, ga_concat_strings_sep, '---') + end) + + it('can concatenate a non-empty array', function() + local sep = '-●●-' + test_concat_fn({ + 'oh', + 'my', + 'neovim' + }, ga_concat_strings_sep, sep) + end) + end) + + describe('ga_remove_duplicate_strings', function() + it('sorts and removes duplicate strings', function() + local garr = new_string_garray() + local input = { + 'ccc', + 'aaa', + 'bbb', + 'ddd●●', + 'aaa', + 'bbb', + 'ccc', + 'ccc', + 'ddd●●' + } + local sorted_dedup_input = { + 'aaa', + 'bbb', + 'ccc', + 'ddd●●' + } + ga_append_strings(garr, unpack(input)) + ga_remove_duplicate_strings(garr) + eq(#sorted_dedup_input, ga_len(garr)) + local strings = ga_data_as_strings(garr) + for i = 0, #sorted_dedup_input - 1 do + eq(sorted_dedup_input[i + 1], ffi.string(strings[i])) + end + end) + end) +end) diff --git a/test/unit/garray_spec.moon b/test/unit/garray_spec.moon deleted file mode 100644 index 5d4dbe690c..0000000000 --- a/test/unit/garray_spec.moon +++ /dev/null @@ -1,271 +0,0 @@ -{:cimport, :internalize, :eq, :neq, :ffi, :lib, :cstr, :to_cstr, :NULL} = require 'test.unit.helpers' - -garray = cimport './src/nvim/garray.h' - --- define a basic interface to garray. We could make it a lot nicer by --- constructing a moonscript class wrapper around garray. It could for --- example associate ga_clear_strings to the underlying garray cdata if the --- garray is a string array. But for now I estimate that that kind of magic --- might make testing less "transparant" (i.e.: the interface would become --- quite different as to how one would use it from C. - --- accessors -ga_len = (garr) -> - garr[0].ga_len -ga_maxlen = (garr) -> - garr[0].ga_maxlen -ga_itemsize = (garr) -> - garr[0].ga_itemsize -ga_growsize = (garr) -> - garr[0].ga_growsize -ga_data = (garr) -> - garr[0].ga_data - --- derived accessors -ga_size = (garr) -> - ga_len(garr) * ga_itemsize(garr) -ga_maxsize = (garr) -> - ga_maxlen(garr) * ga_itemsize(garr) -ga_data_as_bytes = (garr) -> - ffi.cast('uint8_t *', ga_data(garr)) -ga_data_as_strings = (garr) -> - ffi.cast('char **', ga_data(garr)) -ga_data_as_ints = (garr) -> - ffi.cast('int *', ga_data(garr)) - --- garray manipulation -ga_init = (garr, itemsize, growsize) -> - garray.ga_init(garr, itemsize, growsize) -ga_clear = (garr) -> - garray.ga_clear(garr) -ga_clear_strings = (garr) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) - garray.ga_clear_strings(garr) -ga_grow = (garr, n) -> - garray.ga_grow(garr, n) -ga_concat = (garr, str) -> - garray.ga_concat(garr, to_cstr(str)) -ga_append = (garr, b) -> - if type(b) == 'string' - garray.ga_append(garr, string.byte(b)) - else - garray.ga_append(garr, b) -ga_concat_strings = (garr) -> - internalize(garray.ga_concat_strings(garr)) -ga_concat_strings_sep = (garr, sep) -> - internalize(garray.ga_concat_strings_sep(garr, to_cstr(sep))) -ga_remove_duplicate_strings = (garr) -> - garray.ga_remove_duplicate_strings(garr) - --- derived manipulators -ga_set_len = (garr, len) -> - assert.is_true(len <= ga_maxlen(garr)) - garr[0].ga_len = len -ga_inc_len = (garr, by) -> - ga_set_len(garr, ga_len(garr) + 1) - --- custom append functions --- not the C ga_append, which only works for bytes -ga_append_int = (garr, it) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('int')) - - ga_grow(garr, 1) - data = ga_data_as_ints(garr) - data[ga_len(garr)] = it - ga_inc_len(garr, 1) -ga_append_string = (garr, it) -> - assert.is_true(ga_itemsize(garr) == ffi.sizeof('char *')) - - -- make a non-garbage collected string and copy the lua string into it, - -- TODO(aktau): we should probably call xmalloc here, though as long as - -- xmalloc is based on malloc it should work. - mem = ffi.C.malloc(string.len(it) + 1) - ffi.copy(mem, it) - - ga_grow(garr, 1) - data = ga_data_as_strings(garr) - data[ga_len(garr)] = mem - ga_inc_len(garr, 1) -ga_append_strings = (garr, ...) -> - prevlen = ga_len(garr) - len = select('#', ...) - for i = 1, len - ga_append_string(garr, select(i, ...)) - eq prevlen + len, ga_len(garr) -ga_append_ints = (garr, ...) -> - prevlen = ga_len(garr) - len = select('#', ...) - for i = 1, len - ga_append_int(garr, select(i, ...)) - eq prevlen + len, ga_len(garr) - --- enhanced constructors -garray_ctype = ffi.typeof('garray_T[1]') -new_garray = -> - garr = garray_ctype() - ffi.gc(garr, ga_clear) -new_string_garray = -> - garr = garray_ctype() - ga_init(garr, ffi.sizeof("char_u *"), 1) - ffi.gc(garr, ga_clear_strings) - -randomByte = -> - ffi.cast('uint8_t', math.random(0, 255)) - --- scramble the data in a garray -ga_scramble = (garr) -> - size, bytes = ga_size(garr), ga_data_as_bytes(garr) - - for i = 0, size - 1 - bytes[i] = randomByte() - -describe 'garray', -> - itemsize = 14 - growsize = 95 - - describe 'ga_init', -> - it 'initializes the values of the garray', -> - garr = new_garray() - ga_init(garr, itemsize, growsize) - eq 0, ga_len(garr) - eq 0, ga_maxlen(garr) - eq growsize, ga_growsize(garr) - eq itemsize, ga_itemsize(garr) - eq NULL, ga_data(garr) - - describe 'ga_grow', -> - new_and_grow = (itemsize, growsize, req) -> - garr = new_garray() - ga_init(garr, itemsize, growsize) - - eq 0, ga_size(garr) -- should be 0 at first - eq NULL, ga_data(garr) -- should be NULL - ga_grow(garr, req) -- add space for `req` items - - garr - - it 'grows by growsize items if num < growsize', -> - itemsize = 16 - growsize = 4 - grow_by = growsize - 1 - garr = new_and_grow(itemsize, growsize, grow_by) - neq NULL, ga_data(garr) -- data should be a ptr to memory - eq growsize, ga_maxlen(garr) -- we requested LESS than growsize, so... - - it 'grows by num items if num > growsize', -> - itemsize = 16 - growsize = 4 - grow_by = growsize + 1 - garr = new_and_grow(itemsize, growsize, grow_by) - neq NULL, ga_data(garr) -- data should be a ptr to memory - eq grow_by, ga_maxlen(garr) -- we requested MORE than growsize, so... - - it 'does not grow when nothing is requested', -> - garr = new_and_grow(16, 4, 0) - eq NULL, ga_data(garr) - eq 0, ga_maxlen(garr) - - describe 'ga_clear', -> - it 'clears an already allocated array', -> - -- allocate and scramble an array - garr = garray_ctype() - ga_init(garr, itemsize, growsize) - ga_grow(garr, 4) - ga_set_len(garr, 4) - ga_scramble(garr) - - -- clear it and check - ga_clear(garr) - eq NULL, ga_data(garr) - eq 0, ga_maxlen(garr) - eq 0, ga_len(garr) - - describe 'ga_append', -> - it 'can append bytes', -> - -- this is the actual ga_append, the others are just emulated lua - -- versions - garr = new_garray() - ga_init(garr, ffi.sizeof("uint8_t"), 1) - ga_append(garr, 'h') - ga_append(garr, 'e') - ga_append(garr, 'l') - ga_append(garr, 'l') - ga_append(garr, 'o') - ga_append(garr, 0) - bytes = ga_data_as_bytes(garr) - eq 'hello', ffi.string(bytes) - - it 'can append integers', -> - garr = new_garray() - ga_init(garr, ffi.sizeof("int"), 1) - input = {-20, 94, 867615, 90927, 86} - ga_append_ints(garr, unpack(input)) - - ints = ga_data_as_ints(garr) - for i = 0, #input - 1 - eq input[i+1], ints[i] - - it 'can append strings to a growing array of strings', -> - garr = new_string_garray() - input = {"some", "str", "\r\n\r●●●●●●,,,", "hmm", "got it"} - ga_append_strings(garr, unpack(input)) - - -- check that we can get the same strings out of the array - strings = ga_data_as_strings(garr) - for i = 0, #input - 1 - eq input[i+1], ffi.string(strings[i]) - - describe 'ga_concat', -> - it 'concatenates the parameter to the growing byte array', -> - garr = new_garray() - ga_init(garr, ffi.sizeof("char"), 1) - - str = "ohwell●●" - loop = 5 - for i = 1, loop - ga_concat(garr, str) - - -- ga_concat does NOT append the NUL in the src string to the - -- destination, you have to do that manually by calling something like - -- ga_append(gar, '\0'). I'ts always used like that in the vim - -- codebase. I feel that this is a bit of an unnecesesary - -- micro-optimization. - ga_append(garr, 0) - - result = ffi.string(ga_data_as_bytes(garr)) - eq string.rep(str, loop), result - - test_concat_fn = (input, fn, sep) -> - garr = new_string_garray() - ga_append_strings(garr, unpack(input)) - if sep == nil - eq table.concat(input, ','), fn(garr) - else - eq table.concat(input, sep), fn(garr, sep) - - describe 'ga_concat_strings', -> - it 'returns an empty string when concatenating an empty array', -> - test_concat_fn({}, ga_concat_strings) - it 'can concatenate a non-empty array', -> - test_concat_fn({'oh', 'my', 'neovim'}, ga_concat_strings) - - describe 'ga_concat_strings_sep', -> - it 'returns an empty string when concatenating an empty array', -> - test_concat_fn({}, ga_concat_strings_sep, '---') - it 'can concatenate a non-empty array', -> - sep = '-●●-' - test_concat_fn({'oh', 'my', 'neovim'}, ga_concat_strings_sep, sep) - - describe 'ga_remove_duplicate_strings', -> - it 'sorts and removes duplicate strings', -> - garr = new_string_garray() - input = {'ccc', 'aaa', 'bbb', 'ddd●●', 'aaa', 'bbb', 'ccc', 'ccc', 'ddd●●'} - sorted_dedup_input = {'aaa', 'bbb', 'ccc', 'ddd●●'} - - ga_append_strings(garr, unpack(input)) - ga_remove_duplicate_strings(garr) - eq #sorted_dedup_input, ga_len(garr) - - strings = ga_data_as_strings(garr) - for i = 0, #sorted_dedup_input - 1 - eq sorted_dedup_input[i+1], ffi.string(strings[i]) diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua new file mode 100644 index 0000000000..3f1984f6de --- /dev/null +++ b/test/unit/helpers.lua @@ -0,0 +1,176 @@ +local ffi = require('ffi') +local formatc = require('test.unit.formatc') +local Set = require('test.unit.set') +local Preprocess = require('test.unit.preprocess') +local Paths = require('test.config.paths') + +-- add some standard header locations +for _, p in ipairs(Paths.include_paths) do + Preprocess.add_to_include_path(p) +end + +-- load neovim shared library +local libnvim = ffi.load(Paths.test_libnvim_path) + +local function trim(s) + return s:match('^%s*(.*%S)') or '' +end + +-- a Set that keeps around the lines we've already seen +if cdefs == nil then + cdefs = Set:new() +end + +if imported == nil then + imported = Set:new() +end + +-- some things are just too complex for the LuaJIT C parser to digest. We +-- usually don't need them anyway. +local function filter_complex_blocks(body) + local result = {} + + for line in body:gmatch("[^\r\n]+") do + if not (string.find(line, "(^)", 1, true) ~= nil or + string.find(line, "_ISwupper", 1, true)) then + result[#result + 1] = line + end + end + + return table.concat(result, "\n") +end + +-- use this helper to import C files, you can pass multiple paths at once, +-- this helper will return the C namespace of the nvim library. +local function cimport(...) + local paths = {} + local args = {...} + + -- filter out paths we've already imported + for _,path in pairs(args) do + if path ~= nil and not imported:contains(path) then + paths[#paths + 1] = path + end + end + + for _,path in pairs(paths) do + imported:add(path) + end + + if #paths == 0 then + return libnvim + end + + local body = nil + for i=1, 3 do + local stream = Preprocess.preprocess_stream(unpack(paths)) + body = stream:read("*a") + stream:close() + if body ~= nil then break end + end + + if body == nil then + print("ERROR: helpers.lua: Preprocess.preprocess_stream():read() returned empty") + end + + -- format it (so that the lines are "unique" statements), also filter out + -- Objective-C blocks + body = formatc(body) + body = filter_complex_blocks(body) + + -- add the formatted lines to a set + local new_cdefs = Set:new() + for line in body:gmatch("[^\r\n]+") do + new_cdefs:add(trim(line)) + end + + -- subtract the lines we've already imported from the new lines, then add + -- the new unique lines to the old lines (so they won't be imported again) + new_cdefs:diff(cdefs) + cdefs:union(new_cdefs) + + if new_cdefs:size() == 0 then + -- if there's no new lines, just return + return libnvim + end + + -- request a sorted version of the new lines (same relative order as the + -- original preprocessed file) and feed that to the LuaJIT ffi + local new_lines = new_cdefs:to_table() + ffi.cdef(table.concat(new_lines, "\n")) + + return libnvim +end + +local function cppimport(path) + return cimport(Paths.test_include_path .. '/' .. path) +end + +cimport('./src/nvim/types.h') + +-- take a pointer to a C-allocated string and return an interned +-- version while also freeing the memory +local function internalize(cdata, len) + ffi.gc(cdata, ffi.C.free) + return ffi.string(cdata, len) +end + +local cstr = ffi.typeof('char[?]') +local function to_cstr(string) + return cstr((string.len(string)) + 1, string) +end + +-- initialize some global variables, this is still necessary to unit test +-- functions that rely on global state. +local function vim_init() + if vim_init_called ~= nil then + return + end + -- import os_unix.h for mch_early_init(), which initializes some globals + local all = cimport('./src/nvim/os_unix.h', + './src/nvim/misc1.h', + './src/nvim/eval.h', + './src/nvim/os_unix.h', + './src/nvim/option.h', + './src/nvim/ex_cmds2.h', + './src/nvim/window.h', + './src/nvim/ops.h', + './src/nvim/normal.h', + './src/nvim/mbyte.h') + all.mch_early_init() + all.mb_init() + all.eval_init() + all.init_normal_cmds() + all.win_alloc_first() + all.init_yank() + all.init_homedir() + all.set_init_1() + all.set_lang_var() + vim_init_called = true +end + +-- C constants. +local NULL = ffi.cast('void*', 0) + +local OK = 1 +local FAIL = 0 + +return { + cimport = cimport, + cppimport = cppimport, + internalize = internalize, + eq = function(expected, actual) + return assert.are.same(expected, actual) + end, + neq = function(expected, actual) + return assert.are_not.same(expected, actual) + end, + ffi = ffi, + lib = libnvim, + cstr = cstr, + to_cstr = to_cstr, + vim_init = vim_init, + NULL = NULL, + OK = OK, + FAIL = FAIL +} diff --git a/test/unit/helpers.moon b/test/unit/helpers.moon deleted file mode 100644 index f533b9a1c6..0000000000 --- a/test/unit/helpers.moon +++ /dev/null @@ -1,131 +0,0 @@ -ffi = require 'ffi' -lpeg = require 'lpeg' -formatc = require 'test.unit.formatc' -Set = require 'test.unit.set' -Preprocess = require 'test.unit.preprocess' -Paths = require 'test.config.paths' - --- add some standard header locations -for i,p in ipairs(Paths.include_paths) - Preprocess.add_to_include_path(p) - --- load neovim shared library -libnvim = ffi.load Paths.test_libnvim_path - -trim = (s) -> - s\match'^%s*(.*%S)' or '' - --- a Set that keeps around the lines we've already seen -export cdefs -if cdefs == nil - cdefs = Set! - -export imported -if imported == nil - imported = Set! - --- some things are just too complex for the LuaJIT C parser to digest. We --- usually don't need them anyway. -filter_complex_blocks = (body) -> - result = {} - for line in body\gmatch("[^\r\n]+") - -- remove all lines that contain Objective-C block syntax, the LuaJIT ffi - -- doesn't understand it. - if string.find(line, "(^)", 1, true) ~= nil - continue - if string.find(line, "_ISwupper", 1, true) ~= nil - continue - result[#result + 1] = line - table.concat(result, "\n") - --- use this helper to import C files, you can pass multiple paths at once, --- this helper will return the C namespace of the nvim library. --- cimport = (path) -> -cimport = (...) -> - -- filter out paths we've already imported - paths = [path for path in *{...} when not imported\contains(path)] - for path in *paths - imported\add(path) - - if #paths == 0 - return libnvim - - -- preprocess the header - stream = Preprocess.preprocess_stream(unpack(paths)) - body = stream\read("*a") - stream\close! - - -- format it (so that the lines are "unique" statements), also filter out - -- Objective-C blocks - body = formatc(body) - body = filter_complex_blocks(body) - - -- add the formatted lines to a set - new_cdefs = Set! - for line in body\gmatch("[^\r\n]+") - new_cdefs\add(trim(line)) - - -- subtract the lines we've already imported from the new lines, then add - -- the new unique lines to the old lines (so they won't be imported again) - new_cdefs\diff(cdefs) - cdefs\union(new_cdefs) - - if new_cdefs\size! == 0 - -- if there's no new lines, just return - return libnvim - - -- request a sorted version of the new lines (same relative order as the - -- original preprocessed file) and feed that to the LuaJIT ffi - new_lines = new_cdefs\to_table! - ffi.cdef(table.concat(new_lines, "\n")) - - return libnvim - -cppimport = (path) -> - return cimport Paths.test_include_path .. '/' .. path - -cimport './src/nvim/types.h' - --- take a pointer to a C-allocated string and return an interned --- version while also freeing the memory -internalize = (cdata, len) -> - ffi.gc cdata, ffi.C.free - return ffi.string cdata, len - -cstr = ffi.typeof 'char[?]' - -to_cstr = (string) -> - cstr (string.len string) + 1, string - -export vim_init_called --- initialize some global variables, this is still necessary to unit test --- functions that rely on global state. -vim_init = -> - if vim_init_called ~= nil - return - -- import os_unix.h for mch_early_init(), which initializes some globals - os = cimport './src/nvim/os_unix.h' - os.mch_early_init! - vim_init_called = true - --- C constants. -NULL = ffi.cast 'void*', 0 - -OK = 1 -FAIL = 0 - -return { - cimport: cimport - cppimport: cppimport - internalize: internalize - eq: (expected, actual) -> assert.are.same expected, actual - neq: (expected, actual) -> assert.are_not.same expected, actual - ffi: ffi - lib: libnvim - cstr: cstr - to_cstr: to_cstr - vim_init: vim_init - NULL: NULL - OK: OK - FAIL: FAIL -} diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua new file mode 100644 index 0000000000..d04754f5ee --- /dev/null +++ b/test/unit/os/env_spec.lua @@ -0,0 +1,130 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local NULL = helpers.NULL + +require('lfs') + +local env = cimport('./src/nvim/os/os.h') + +describe('env function', function() + function os_setenv(name, value, override) + return env.os_setenv((to_cstr(name)), (to_cstr(value)), override) + end + + function os_getenv(name) + local rval = env.os_getenv((to_cstr(name))) + if rval ~= NULL then + return ffi.string(rval) + else + return NULL + end + end + + describe('os_setenv', function() + local OK = 0 + + it('sets an env variable and returns OK', function() + local name = 'NEOVIM_UNIT_TEST_SETENV_1N' + local value = 'NEOVIM_UNIT_TEST_SETENV_1V' + eq(nil, os.getenv(name)) + eq(OK, (os_setenv(name, value, 1))) + eq(value, os.getenv(name)) + end) + + it("dosn't overwrite an env variable if overwrite is 0", function() + local name = 'NEOVIM_UNIT_TEST_SETENV_2N' + local value = 'NEOVIM_UNIT_TEST_SETENV_2V' + local value_updated = 'NEOVIM_UNIT_TEST_SETENV_2V_UPDATED' + eq(OK, (os_setenv(name, value, 0))) + eq(value, os.getenv(name)) + eq(OK, (os_setenv(name, value_updated, 0))) + eq(value, os.getenv(name)) + end) + end) + + describe('os_getenv', function() + it('reads an env variable', function() + local name = 'NEOVIM_UNIT_TEST_GETENV_1N' + local value = 'NEOVIM_UNIT_TEST_GETENV_1V' + eq(NULL, os_getenv(name)) + -- need to use os_setenv, because lua dosn't have a setenv function + os_setenv(name, value, 1) + eq(value, os_getenv(name)) + end) + + it('returns NULL if the env variable is not found', function() + local name = 'NEOVIM_UNIT_TEST_GETENV_NOTFOUND' + return eq(NULL, os_getenv(name)) + end) + end) + + describe('os_getenvname_at_index', function() + it('returns names of environment variables', function() + local test_name = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N' + local test_value = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1V' + os_setenv(test_name, test_value, 1) + local i = 0 + local names = { } + local found_name = false + local name = env.os_getenvname_at_index(i) + while name ~= NULL do + table.insert(names, ffi.string(name)) + if (ffi.string(name)) == test_name then + found_name = true + end + i = i + 1 + name = env.os_getenvname_at_index(i) + end + eq(true, (table.getn(names)) > 0) + eq(true, found_name) + end) + + it('returns NULL if the index is out of bounds', function() + local huge = ffi.new('size_t', 10000) + local maxuint32 = ffi.new('size_t', 4294967295) + eq(NULL, env.os_getenvname_at_index(huge)) + eq(NULL, env.os_getenvname_at_index(maxuint32)) + + if ffi.abi('64bit') then + -- couldn't use a bigger number because it gets converted to + -- double somewere, should be big enough anyway + -- maxuint64 = ffi.new 'size_t', 18446744073709551615 + local maxuint64 = ffi.new('size_t', 18446744073709000000) + eq(NULL, env.os_getenvname_at_index(maxuint64)) + end + end) + end) + + describe('os_get_pid', function() + it('returns the process ID', function() + local stat_file = io.open('/proc/self/stat') + if stat_file then + local stat_str = stat_file:read('*l') + stat_file:close() + local pid = tonumber((stat_str:match('%d+'))) + eq(pid, tonumber(env.os_get_pid())) + else + -- /proc is not available on all systems, test if pid is nonzero. + eq(true, (env.os_get_pid() > 0)) + end + end) + end) + + describe('os_get_hostname', function() + it('returns the hostname', function() + local handle = io.popen('hostname') + local hostname = handle:read('*l') + handle:close() + local hostname_buf = cstr(255, '') + env.os_get_hostname(hostname_buf, 255) + eq(hostname, (ffi.string(hostname_buf))) + end) + end) +end) diff --git a/test/unit/os/env_spec.moon b/test/unit/os/env_spec.moon deleted file mode 100644 index ab5b940a58..0000000000 --- a/test/unit/os/env_spec.moon +++ /dev/null @@ -1,106 +0,0 @@ -{:cimport, :internalize, :eq, :ffi, :lib, :cstr, :to_cstr, :NULL} = require 'test.unit.helpers' -require 'lfs' - -env = cimport './src/nvim/os/os.h' - -describe 'env function', -> - - os_setenv = (name, value, override) -> - env.os_setenv (to_cstr name), (to_cstr value), override - - os_getenv = (name) -> - rval = env.os_getenv (to_cstr name) - if rval != NULL - ffi.string rval - else - NULL - - describe 'os_setenv', -> - - OK = 0 - - it 'sets an env variable and returns OK', -> - name = 'NEOVIM_UNIT_TEST_SETENV_1N' - value = 'NEOVIM_UNIT_TEST_SETENV_1V' - eq nil, os.getenv name - eq OK, (os_setenv name, value, 1) - eq value, os.getenv name - - it "dosn't overwrite an env variable if overwrite is 0", -> - name = 'NEOVIM_UNIT_TEST_SETENV_2N' - value = 'NEOVIM_UNIT_TEST_SETENV_2V' - value_updated = 'NEOVIM_UNIT_TEST_SETENV_2V_UPDATED' - eq OK, (os_setenv name, value, 0) - eq value, os.getenv name - eq OK, (os_setenv name, value_updated, 0) - eq value, os.getenv name - - describe 'os_getenv', -> - - it 'reads an env variable', -> - name = 'NEOVIM_UNIT_TEST_GETENV_1N' - value = 'NEOVIM_UNIT_TEST_GETENV_1V' - eq NULL, os_getenv name - -- need to use os_setenv, because lua dosn't have a setenv function - os_setenv name, value, 1 - eq value, os_getenv name - - it 'returns NULL if the env variable is not found', -> - name = 'NEOVIM_UNIT_TEST_GETENV_NOTFOUND' - eq NULL, os_getenv name - - describe 'os_getenvname_at_index', -> - - it 'returns names of environment variables', -> - test_name = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1N' - test_value = 'NEOVIM_UNIT_TEST_GETENVNAME_AT_INDEX_1V' - os_setenv test_name, test_value, 1 - i = 0 - names = {} - found_name = false - name = env.os_getenvname_at_index i - while name != NULL - table.insert names, ffi.string name - if (ffi.string name) == test_name - found_name = true - i += 1 - name = env.os_getenvname_at_index i - - eq true, (table.getn names) > 0 - eq true, found_name - - it 'returns NULL if the index is out of bounds', -> - huge = ffi.new 'size_t', 10000 - maxuint32 = ffi.new 'size_t', 4294967295 - eq NULL, env.os_getenvname_at_index huge - eq NULL, env.os_getenvname_at_index maxuint32 - if ffi.abi '64bit' - -- couldn't use a bigger number because it gets converted to - -- double somewere, should be big enough anyway - -- maxuint64 = ffi.new 'size_t', 18446744073709551615 - maxuint64 = ffi.new 'size_t', 18446744073709000000 - eq NULL, env.os_getenvname_at_index maxuint64 - - describe 'os_get_pid', -> - - it 'returns the process ID', -> - stat_file = io.open '/proc/self/stat' - if stat_file - stat_str = stat_file\read '*l' - stat_file\close! - pid = tonumber (stat_str\match '%d+') - eq pid, tonumber env.os_get_pid! - else - -- /proc is not available on all systems, test if pid is nonzero. - eq true, (env.os_get_pid! > 0) - - describe 'os_get_hostname', -> - - it 'returns the hostname', -> - handle = io.popen 'hostname' - hostname = handle\read '*l' - handle\close! - hostname_buf = cstr 255, '' - env.os_get_hostname hostname_buf, 255 - eq hostname, (ffi.string hostname_buf) - diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua new file mode 100644 index 0000000000..67ca8f6c24 --- /dev/null +++ b/test/unit/os/fs_spec.lua @@ -0,0 +1,710 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local cppimport = helpers.cppimport +local internalize = helpers.internalize +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local OK = helpers.OK +local FAIL = helpers.FAIL + +require('lfs') +require('bit') + +cimport('unistd.h') +local fs = cimport('./src/nvim/os/os.h') +cppimport('sys/stat.h') +cppimport('sys/fcntl.h') +cppimport('sys/errno.h') + +local len = 0 +local buf = "" +local directory = nil +local executable_name = nil + +local function assert_file_exists(filepath) + eq(false, nil == (lfs.attributes(filepath, 'r'))) +end + +local function assert_file_does_not_exist(filepath) + eq(true, nil == (lfs.attributes(filepath, 'r'))) +end + +describe('fs function', function() + + setup(function() + lfs.mkdir('unit-test-directory'); + io.open('unit-test-directory/test.file', 'w').close() + io.open('unit-test-directory/test_2.file', 'w').close() + lfs.link('test.file', 'unit-test-directory/test_link.file', true) + -- Since the tests are executed, they are called by an executable. We use + -- that executable for several asserts. + local absolute_executable = arg[0] + -- Split absolute_executable into a directory and the actual file name for + -- later usage. + directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') + end) + + teardown(function() + os.remove('unit-test-directory/test.file') + os.remove('unit-test-directory/test_2.file') + os.remove('unit-test-directory/test_link.file') + os.remove('unit-test-directory/test_hlink.file') + lfs.rmdir('unit-test-directory') + end) + + describe('os_dirname', function() + local function os_dirname(buf, len) + return fs.os_dirname(buf, len) + end + + before_each(function() + len = (string.len(lfs.currentdir())) + 1 + buf = cstr(len, '') + end) + + it('returns OK and writes current directory into the buffer if it is large\n enough', function() + eq(OK, (os_dirname(buf, len))) + eq(lfs.currentdir(), (ffi.string(buf))) + end) + + -- What kind of other failing cases are possible? + it('returns FAIL if the buffer is too small', function() + local buf = cstr((len - 1), '') + eq(FAIL, (os_dirname(buf, (len - 1)))) + end) + end) + + local function os_isdir(name) + return fs.os_isdir((to_cstr(name))) + end + + describe('os_isdir', function() + it('returns false if an empty string is given', function() + eq(false, (os_isdir(''))) + end) + + it('returns false if a nonexisting directory is given', function() + eq(false, (os_isdir('non-existing-directory'))) + end) + + it('returns false if a nonexisting absolute directory is given', function() + eq(false, (os_isdir('/non-existing-directory'))) + end) + + it('returns false if an existing file is given', function() + eq(false, (os_isdir('unit-test-directory/test.file'))) + end) + + it('returns true if the current directory is given', function() + eq(true, (os_isdir('.'))) + end) + + it('returns true if the parent directory is given', function() + eq(true, (os_isdir('..'))) + end) + + it('returns true if an arbitrary directory is given', function() + eq(true, (os_isdir('unit-test-directory'))) + end) + + it('returns true if an absolute directory is given', function() + eq(true, (os_isdir(directory))) + end) + end) + + describe('os_can_exe', function() + local function os_can_exe(name) + return fs.os_can_exe((to_cstr(name))) + end + + it('returns false when given a directory', function() + eq(false, (os_can_exe('./unit-test-directory'))) + end) + + it('returns false when given a regular file without executable bit set', function() + eq(false, (os_can_exe('unit-test-directory/test.file'))) + end) + + it('returns false when the given file does not exists', function() + eq(false, (os_can_exe('does-not-exist.file'))) + end) + + it('returns true when given an executable inside $PATH', function() + eq(true, (os_can_exe(executable_name))) + end) + + it('returns true when given an executable relative to the current dir', function() + local old_dir = lfs.currentdir() + lfs.chdir(directory) + local relative_executable = './' .. executable_name + eq(true, (os_can_exe(relative_executable))) + lfs.chdir(old_dir) + end) + end) + + describe('file permissions', function() + local function os_getperm(filename) + local perm = fs.os_getperm((to_cstr(filename))) + return tonumber(perm) + end + + local function os_setperm(filename, perm) + return fs.os_setperm((to_cstr(filename)), perm) + end + + local function os_fchown(filename, user_id, group_id) + local fd = ffi.C.open(filename, 0) + local res = fs.os_fchown(fd, user_id, group_id) + ffi.C.close(fd) + return res + end + + local function os_file_is_readonly(filename) + return fs.os_file_is_readonly((to_cstr(filename))) + end + + local function os_file_is_writable(filename) + return fs.os_file_is_writable((to_cstr(filename))) + end + + local function bit_set(number, check_bit) + return 0 ~= (bit.band(number, check_bit)) + end + + local function set_bit(number, to_set) + return bit.bor(number, to_set) + end + + local function unset_bit(number, to_unset) + return bit.band(number, (bit.bnot(to_unset))) + end + + describe('os_getperm', function() + it('returns -1 when the given file does not exist', function() + eq(-1, (os_getperm('non-existing-file'))) + end) + + it('returns a perm > 0 when given an existing file', function() + assert.is_true((os_getperm('unit-test-directory')) > 0) + end) + + it('returns S_IRUSR when the file is readable', function() + local perm = os_getperm('unit-test-directory') + assert.is_true((bit_set(perm, ffi.C.kS_IRUSR))) + end) + end) + + describe('os_setperm', function() + it('can set and unset the executable bit of a file', function() + local perm = os_getperm('unit-test-directory/test.file') + perm = unset_bit(perm, ffi.C.kS_IXUSR) + eq(OK, (os_setperm('unit-test-directory/test.file', perm))) + perm = os_getperm('unit-test-directory/test.file') + assert.is_false((bit_set(perm, ffi.C.kS_IXUSR))) + perm = set_bit(perm, ffi.C.kS_IXUSR) + eq(OK, os_setperm('unit-test-directory/test.file', perm)) + perm = os_getperm('unit-test-directory/test.file') + assert.is_true((bit_set(perm, ffi.C.kS_IXUSR))) + end) + + it('fails if given file does not exist', function() + local perm = ffi.C.kS_IXUSR + eq(FAIL, (os_setperm('non-existing-file', perm))) + end) + end) + + describe('os_fchown', function() + local filename = 'unit-test-directory/test.file' + it('does not change owner and group if respective IDs are equal to -1', function() + local uid = lfs.attributes(filename, 'uid') + local gid = lfs.attributes(filename, 'gid') + eq(0, os_fchown(filename, -1, -1)) + eq(uid, lfs.attributes(filename, 'uid')) + return eq(gid, lfs.attributes(filename, 'gid')) + end) + + it('owner of a file may change the group of the file to any group of which that owner is a member', function() + -- Some systems may not have `id` utility. + if (os.execute('id -G &> /dev/null') == 0) then + local file_gid = lfs.attributes(filename, 'gid') + + -- Gets ID of any group of which current user is a member except the + -- group that owns the file. + local id_fd = io.popen('id -G') + local new_gid = id_fd:read('*n') + if (new_gid == file_gid) then + new_gid = id_fd:read('*n') + end + id_fd:close() + + -- User can be a member of only one group. + -- In that case we can not perform this test. + if new_gid then + eq(0, (os_fchown(filename, -1, new_gid))) + eq(new_gid, (lfs.attributes(filename, 'gid'))) + end + end + end) + + it('returns nonzero if process has not enough permissions', function() + -- On Windows `os_fchown` always returns 0 + -- because `uv_fs_chown` is no-op on this platform. + if (ffi.os ~= 'Windows' and ffi.C.geteuid() ~= 0) then + -- chown to root + neq(0, os_fchown(filename, 0, 0)) + end + end) + end) + + describe('os_file_is_readonly', function() + it('returns true if the file is readonly', function() + local perm = os_getperm('unit-test-directory/test.file') + local perm_orig = perm + perm = unset_bit(perm, ffi.C.kS_IWUSR) + perm = unset_bit(perm, ffi.C.kS_IWGRP) + perm = unset_bit(perm, ffi.C.kS_IWOTH) + eq(OK, (os_setperm('unit-test-directory/test.file', perm))) + eq(true, os_file_is_readonly('unit-test-directory/test.file')) + eq(OK, (os_setperm('unit-test-directory/test.file', perm_orig))) + end) + + it('returns false if the file is writable', function() + eq(false, os_file_is_readonly('unit-test-directory/test.file')) + end) + end) + + describe('os_file_is_writable', function() + it('returns 0 if the file is readonly', function() + local perm = os_getperm('unit-test-directory/test.file') + local perm_orig = perm + perm = unset_bit(perm, ffi.C.kS_IWUSR) + perm = unset_bit(perm, ffi.C.kS_IWGRP) + perm = unset_bit(perm, ffi.C.kS_IWOTH) + eq(OK, (os_setperm('unit-test-directory/test.file', perm))) + eq(0, os_file_is_writable('unit-test-directory/test.file')) + eq(OK, (os_setperm('unit-test-directory/test.file', perm_orig))) + end) + + it('returns 1 if the file is writable', function() + eq(1, os_file_is_writable('unit-test-directory/test.file')) + end) + + it('returns 2 when given a folder with rights to write into', function() + eq(2, os_file_is_writable('unit-test-directory')) + end) + end) + end) + + describe('file operations', function() + local function os_file_exists(filename) + return fs.os_file_exists((to_cstr(filename))) + end + + local function os_rename(path, new_path) + return fs.os_rename((to_cstr(path)), (to_cstr(new_path))) + end + + local function os_remove(path) + return fs.os_remove((to_cstr(path))) + end + + local function os_open(path, flags, mode) + return fs.os_open((to_cstr(path)), flags, mode) + end + + describe('os_file_exists', function() + it('returns false when given a non-existing file', function() + eq(false, (os_file_exists('non-existing-file'))) + end) + + it('returns true when given an existing file', function() + eq(true, (os_file_exists('unit-test-directory/test.file'))) + end) + end) + + describe('os_rename', function() + local test = 'unit-test-directory/test.file' + local not_exist = 'unit-test-directory/not_exist.file' + + it('can rename file if destination file does not exist', function() + eq(OK, (os_rename(test, not_exist))) + eq(false, (os_file_exists(test))) + eq(true, (os_file_exists(not_exist))) + eq(OK, (os_rename(not_exist, test))) -- restore test file + end) + + it('fail if source file does not exist', function() + eq(FAIL, (os_rename(not_exist, test))) + end) + + it('can overwrite destination file if it exists', function() + local other = 'unit-test-directory/other.file' + local file = io.open(other, 'w') + file:write('other') + file:flush() + file:close() + + eq(OK, (os_rename(other, test))) + eq(false, (os_file_exists(other))) + eq(true, (os_file_exists(test))) + file = io.open(test, 'r') + eq('other', (file:read('*all'))) + file:close() + end) + end) + + describe('os_remove', function() + before_each(function() + io.open('unit-test-directory/test_remove.file', 'w').close() + end) + + after_each(function() + os.remove('unit-test-directory/test_remove.file') + end) + + it('returns non-zero when given a non-existing file', function() + neq(0, (os_remove('non-existing-file'))) + end) + + it('removes the given file and returns 0', function() + local f = 'unit-test-directory/test_remove.file' + assert_file_exists(f) + eq(0, (os_remove(f))) + assert_file_does_not_exist(f) + end) + end) + + describe('os_open', function() + before_each(function() + io.open('unit-test-directory/test_existing.file', 'w').close() + end) + + after_each(function() + os.remove('unit-test-directory/test_existing.file') + os.remove('test_new_file') + end) + + local new_file = 'test_new_file' + local existing_file = 'unit-test-directory/test_existing.file' + + it('returns -ENOENT for O_RDWR on a non-existing file', function() + eq(-ffi.C.kENOENT, (os_open('non-existing-file', ffi.C.kO_RDWR, 0))) + end) + + it('returns non-negative for O_CREAT on a non-existing file', function() + assert_file_does_not_exist(new_file) + assert.is_true(0 <= (os_open(new_file, ffi.C.kO_CREAT, 0))) + end) + + it('returns non-negative for O_CREAT on a existing file', function() + assert_file_exists(existing_file) + assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_CREAT, 0))) + end) + + it('returns -EEXIST for O_CREAT|O_EXCL on a existing file', function() + assert_file_exists(existing_file) + eq(-ffi.C.kEEXIST, (os_open(existing_file, (bit.bor(ffi.C.kO_CREAT, ffi.C.kO_EXCL)), 0))) + end) + + it('sets `rwx` permissions for O_CREAT 700', function() + assert_file_does_not_exist(new_file) + --create the file + os_open(new_file, ffi.C.kO_CREAT, tonumber("700", 8)) + --verify permissions + eq('rwx------', lfs.attributes(new_file)['permissions']) + end) + + it('sets `rw` permissions for O_CREAT 600', function() + assert_file_does_not_exist(new_file) + --create the file + os_open(new_file, ffi.C.kO_CREAT, tonumber("600", 8)) + --verify permissions + eq('rw-------', lfs.attributes(new_file)['permissions']) + end) + + it('returns a non-negative file descriptor for an existing file', function() + assert.is_true(0 <= (os_open(existing_file, ffi.C.kO_RDWR, 0))) + end) + end) + end) + + describe('folder operations', function() + local function os_mkdir(path, mode) + return fs.os_mkdir(to_cstr(path), mode) + end + + local function os_rmdir(path) + return fs.os_rmdir(to_cstr(path)) + end + + describe('os_mkdir', function() + it('returns non-zero when given an already existing directory', function() + local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR + neq(0, (os_mkdir('unit-test-directory', mode))) + end) + + it('creates a directory and returns 0', function() + local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR + eq(false, (os_isdir('unit-test-directory/new-dir'))) + eq(0, (os_mkdir('unit-test-directory/new-dir', mode))) + eq(true, (os_isdir('unit-test-directory/new-dir'))) + lfs.rmdir('unit-test-directory/new-dir') + end) + end) + + describe('os_rmdir', function() + it('returns non_zero when given a non-existing directory', function() + neq(0, (os_rmdir('non-existing-directory'))) + end) + + it('removes the given directory and returns 0', function() + lfs.mkdir('unit-test-directory/new-dir') + eq(0, (os_rmdir('unit-test-directory/new-dir', mode))) + eq(false, (os_isdir('unit-test-directory/new-dir'))) + end) + end) + end) + + describe('FileInfo', function() + local function file_info_new() + local file_info = ffi.new('FileInfo[1]') + file_info[0].stat.st_ino = 0 + file_info[0].stat.st_dev = 0 + return file_info + end + + local function is_file_info_filled(file_info) + return file_info[0].stat.st_ino > 0 and file_info[0].stat.st_dev > 0 + end + + local function file_id_new() + local file_info = ffi.new('FileID[1]') + file_info[0].inode = 0 + file_info[0].device_id = 0 + return file_info + end + + describe('os_fileinfo', function() + it('returns false if given a non-existing file', function() + local file_info = file_info_new() + assert.is_false((fs.os_fileinfo('/non-existent', file_info))) + end) + + it('returns true if given an existing file and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileinfo(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + end) + + it('returns the file info of the linked file, not the link', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_fileinfo(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + local mode = tonumber(file_info[0].stat.st_mode) + return eq(ffi.C.kS_IFREG, (bit.band(mode, ffi.C.kS_IFMT))) + end) + end) + + describe('os_fileinfo_link', function() + it('returns false if given a non-existing file', function() + local file_info = file_info_new() + assert.is_false((fs.os_fileinfo_link('/non-existent', file_info))) + end) + + it('returns true if given an existing file and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileinfo_link(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + end) + + it('returns the file info of the link, not the linked file', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_fileinfo_link(path, file_info))) + assert.is_true((is_file_info_filled(file_info))) + local mode = tonumber(file_info[0].stat.st_mode) + eq(ffi.C.kS_IFLNK, (bit.band(mode, ffi.C.kS_IFMT))) + end) + end) + + describe('os_fileinfo_fd', function() + it('returns false if given an invalid file descriptor', function() + local file_info = file_info_new() + assert.is_false((fs.os_fileinfo_fd(-1, file_info))) + end) + + it('returns true if given a file descriptor and fills file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + local fd = ffi.C.open(path, 0) + assert.is_true((fs.os_fileinfo_fd(fd, file_info))) + assert.is_true((is_file_info_filled(file_info))) + ffi.C.close(fd) + end) + end) + + describe('os_fileinfo_id_equal', function() + it('returns false if file infos represent different files', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_fileinfo(path_1, file_info_1))) + assert.is_true((fs.os_fileinfo(path_2, file_info_2))) + assert.is_false((fs.os_fileinfo_id_equal(file_info_1, file_info_2))) + end) + + it('returns true if file infos represent the same file', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileinfo(path, file_info_1))) + assert.is_true((fs.os_fileinfo(path, file_info_2))) + assert.is_true((fs.os_fileinfo_id_equal(file_info_1, file_info_2))) + end) + + it('returns true if file infos represent the same file (symlink)', function() + local file_info_1 = file_info_new() + local file_info_2 = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_link.file' + assert.is_true((fs.os_fileinfo(path_1, file_info_1))) + assert.is_true((fs.os_fileinfo(path_2, file_info_2))) + assert.is_true((fs.os_fileinfo_id_equal(file_info_1, file_info_2))) + end) + end) + + describe('os_fileinfo_id', function() + it('extracts ino/dev from file_info into file_id', function() + local file_info = file_info_new() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileinfo(path, file_info))) + fs.os_fileinfo_id(file_info, file_id) + eq(file_info[0].stat.st_ino, file_id[0].inode) + eq(file_info[0].stat.st_dev, file_id[0].device_id) + end) + end) + + describe('os_fileinfo_inode', function() + it('returns the inode from file_info', function() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileinfo(path, file_info))) + local inode = fs.os_fileinfo_inode(file_info) + eq(file_info[0].stat.st_ino, inode) + end) + end) + + describe('os_fileinfo_size', function() + it('returns the correct size of a file', function() + local path = 'unit-test-directory/test.file' + local file = io.open(path, 'w') + file:write('some bytes to get filesize != 0') + file:flush() + file:close() + local size = lfs.attributes(path, 'size') + local file_info = file_info_new() + assert.is_true(fs.os_fileinfo(path, file_info)) + eq(size, fs.os_fileinfo_size(file_info)) + end) + end) + + describe('os_fileinfo_hardlinks', function() + it('returns the correct number of hardlinks', function() + local path = 'unit-test-directory/test.file' + local path_link = 'unit-test-directory/test_hlink.file' + local file_info = file_info_new() + assert.is_true(fs.os_fileinfo(path, file_info)) + eq(1, fs.os_fileinfo_hardlinks(file_info)) + lfs.link(path, path_link) + assert.is_true(fs.os_fileinfo(path, file_info)) + eq(2, fs.os_fileinfo_hardlinks(file_info)) + end) + end) + + describe('os_fileinfo_blocksize', function() + it('returns the correct blocksize of a file', function() + local path = 'unit-test-directory/test.file' + -- there is a bug in luafilesystem where + -- `lfs.attributes path, 'blksize'` returns the worng value: + -- https://github.com/keplerproject/luafilesystem/pull/44 + -- using this workaround for now: + local blksize = lfs.attributes(path).blksize + local file_info = file_info_new() + assert.is_true(fs.os_fileinfo(path, file_info)) + if blksize then + eq(blksize, fs.os_fileinfo_blocksize(file_info)) + else + -- luafs dosn't support blksize on windows + -- libuv on windows returns a constant value as blocksize + -- checking for this constant value should be enough + eq(2048, fs.os_fileinfo_blocksize(file_info)) + end + end) + end) + + describe('os_fileid', function() + it('returns false if given an non-existing file', function() + local file_id = file_id_new() + assert.is_false((fs.os_fileid('/non-existent', file_id))) + end) + + it('returns true if given an existing file and fills file_id', function() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileid(path, file_id))) + assert.is_true(0 < file_id[0].inode) + assert.is_true(0 < file_id[0].device_id) + end) + end) + + describe('os_fileid_equal', function() + it('returns true if two FileIDs are equal', function() + local file_id = file_id_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileid(path, file_id))) + assert.is_true((fs.os_fileid_equal(file_id, file_id))) + end) + + it('returns false if two FileIDs are not equal', function() + local file_id_1 = file_id_new() + local file_id_2 = file_id_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_fileid(path_1, file_id_1))) + assert.is_true((fs.os_fileid(path_2, file_id_2))) + assert.is_false((fs.os_fileid_equal(file_id_1, file_id_2))) + end) + end) + + describe('os_fileid_equal_fileinfo', function() + it('returns true if file_id and file_info represent the same file', function() + local file_id = file_id_new() + local file_info = file_info_new() + local path = 'unit-test-directory/test.file' + assert.is_true((fs.os_fileid(path, file_id))) + assert.is_true((fs.os_fileinfo(path, file_info))) + assert.is_true((fs.os_fileid_equal_fileinfo(file_id, file_info))) + end) + + it('returns false if file_id and file_info represent different files', function() + local file_id = file_id_new() + local file_info = file_info_new() + local path_1 = 'unit-test-directory/test.file' + local path_2 = 'unit-test-directory/test_2.file' + assert.is_true((fs.os_fileid(path_1, file_id))) + assert.is_true((fs.os_fileinfo(path_2, file_info))) + assert.is_false((fs.os_fileid_equal_fileinfo(file_id, file_info))) + end) + end) + end) +end) diff --git a/test/unit/os/fs_spec.moon b/test/unit/os/fs_spec.moon deleted file mode 100644 index dd787e76cd..0000000000 --- a/test/unit/os/fs_spec.moon +++ /dev/null @@ -1,525 +0,0 @@ -{:cimport, :cppimport, :internalize, :eq, :neq, :ffi, :lib, :cstr, :to_cstr, :OK, :FAIL} = require 'test.unit.helpers' -require 'lfs' -require 'bit' - -fs = cimport './src/nvim/os/os.h' - -cppimport 'sys/stat.h' -cppimport 'sys/fcntl.h' -cppimport 'sys/errno.h' - -assert_file_exists = (filepath) -> - eq false, nil == (lfs.attributes filepath, 'r') - -assert_file_does_not_exist = (filepath) -> - eq true, nil == (lfs.attributes filepath, 'r') - -describe 'fs function', -> - setup -> - lfs.mkdir 'unit-test-directory' - (io.open 'unit-test-directory/test.file', 'w').close! - (io.open 'unit-test-directory/test_2.file', 'w').close! - lfs.link 'test.file', 'unit-test-directory/test_link.file', true - - -- Since the tests are executed, they are called by an executable. We use - -- that executable for several asserts. - export absolute_executable = arg[0] - - -- Split absolute_executable into a directory and the actual file name for - -- later usage. - export directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') - - teardown -> - os.remove 'unit-test-directory/test.file' - os.remove 'unit-test-directory/test_2.file' - os.remove 'unit-test-directory/test_link.file' - lfs.rmdir 'unit-test-directory' - - describe 'os_dirname', -> - os_dirname = (buf, len) -> - fs.os_dirname buf, len - - before_each -> - export len = (string.len lfs.currentdir!) + 1 - export buf = cstr len, '' - - it 'returns OK and writes current directory into the buffer if it is large - enough', -> - eq OK, (os_dirname buf, len) - eq lfs.currentdir!, (ffi.string buf) - - -- What kind of other failing cases are possible? - it 'returns FAIL if the buffer is too small', -> - buf = cstr (len-1), '' - eq FAIL, (os_dirname buf, (len-1)) - - os_isdir = (name) -> - fs.os_isdir (to_cstr name) - - describe 'os_isdir', -> - it 'returns false if an empty string is given', -> - eq false, (os_isdir '') - - it 'returns false if a nonexisting directory is given', -> - eq false, (os_isdir 'non-existing-directory') - - it 'returns false if a nonexisting absolute directory is given', -> - eq false, (os_isdir '/non-existing-directory') - - it 'returns false if an existing file is given', -> - eq false, (os_isdir 'unit-test-directory/test.file') - - it 'returns true if the current directory is given', -> - eq true, (os_isdir '.') - - it 'returns true if the parent directory is given', -> - eq true, (os_isdir '..') - - it 'returns true if an arbitrary directory is given', -> - eq true, (os_isdir 'unit-test-directory') - - it 'returns true if an absolute directory is given', -> - eq true, (os_isdir directory) - - describe 'os_can_exe', -> - os_can_exe = (name) -> - fs.os_can_exe (to_cstr name) - - it 'returns false when given a directory', -> - eq false, (os_can_exe './unit-test-directory') - - it 'returns false when given a regular file without executable bit set', -> - eq false, (os_can_exe 'unit-test-directory/test.file') - - it 'returns false when the given file does not exists', -> - eq false, (os_can_exe 'does-not-exist.file') - - it 'returns true when given an executable inside $PATH', -> - eq true, (os_can_exe executable_name) - - it 'returns true when given an executable relative to the current dir', -> - old_dir = lfs.currentdir! - lfs.chdir directory - relative_executable = './' .. executable_name - eq true, (os_can_exe relative_executable) - lfs.chdir old_dir - - describe 'file permissions', -> - os_getperm = (filename) -> - perm = fs.os_getperm (to_cstr filename) - tonumber perm - - os_setperm = (filename, perm) -> - fs.os_setperm (to_cstr filename), perm - - os_fchown = (filename, user_id, group_id) -> - fd = ffi.C.open filename, 0 - res = fs.os_fchown fd, user_id, group_id - ffi.C.close fd - return res - - os_file_is_readonly = (filename) -> - fs.os_file_is_readonly (to_cstr filename) - - os_file_is_writable = (filename) -> - fs.os_file_is_writable (to_cstr filename) - - bit_set = (number, check_bit) -> - if 0 == (bit.band number, check_bit) then false else true - - set_bit = (number, to_set) -> - return bit.bor number, to_set - - unset_bit = (number, to_unset) -> - return bit.band number, (bit.bnot to_unset) - - describe 'os_getperm', -> - it 'returns -1 when the given file does not exist', -> - eq -1, (os_getperm 'non-existing-file') - - it 'returns a perm > 0 when given an existing file', -> - assert.is_true (os_getperm 'unit-test-directory') > 0 - - it 'returns S_IRUSR when the file is readable', -> - perm = os_getperm 'unit-test-directory' - assert.is_true (bit_set perm, ffi.C.kS_IRUSR) - - describe 'os_setperm', -> - it 'can set and unset the executable bit of a file', -> - perm = os_getperm 'unit-test-directory/test.file' - - perm = unset_bit perm, ffi.C.kS_IXUSR - eq OK, (os_setperm 'unit-test-directory/test.file', perm) - - perm = os_getperm 'unit-test-directory/test.file' - assert.is_false (bit_set perm, ffi.C.kS_IXUSR) - - perm = set_bit perm, ffi.C.kS_IXUSR - eq OK, os_setperm 'unit-test-directory/test.file', perm - - perm = os_getperm 'unit-test-directory/test.file' - assert.is_true (bit_set perm, ffi.C.kS_IXUSR) - - it 'fails if given file does not exist', -> - perm = ffi.C.kS_IXUSR - eq FAIL, (os_setperm 'non-existing-file', perm) - - describe 'os_fchown', -> - filename = 'unit-test-directory/test.file' - - it 'does not change owner and group if respective IDs are equal to -1', -> - uid = lfs.attributes filename, 'uid' - gid = lfs.attributes filename, 'gid' - eq 0, os_fchown filename, -1, -1 - eq uid, lfs.attributes filename, 'uid' - eq gid, lfs.attributes filename, 'gid' - - it 'owner of a file may change the group of the file - to any group of which that owner is a member', -> - -- Some systems may not have `id` utility. - if (os.execute('id -G &> /dev/null') == 0) - file_gid = lfs.attributes filename, 'gid' - - -- Gets ID of any group of which current user is a member except the - -- group that owns the file. - id_fd = io.popen('id -G') - new_gid = id_fd\read '*n' - if (new_gid == file_gid) - new_gid = id_fd\read '*n' - id_fd\close! - - -- User can be a member of only one group. - -- In that case we can not perform this test. - if new_gid - eq 0, (os_fchown filename, -1, new_gid) - eq new_gid, (lfs.attributes filename, 'gid') - - it 'returns nonzero if process has not enough permissions', -> - -- On Windows `os_fchown` always returns 0 - -- because `uv_fs_chown` is no-op on this platform. - if (ffi.os != 'Windows' and ffi.C.geteuid! != 0) - -- chown to root - neq 0, os_fchown filename, 0, 0 - - describe 'os_file_is_readonly', -> - it 'returns true if the file is readonly', -> - perm = os_getperm 'unit-test-directory/test.file' - perm_orig = perm - perm = unset_bit perm, ffi.C.kS_IWUSR - perm = unset_bit perm, ffi.C.kS_IWGRP - perm = unset_bit perm, ffi.C.kS_IWOTH - eq OK, (os_setperm 'unit-test-directory/test.file', perm) - eq true, os_file_is_readonly 'unit-test-directory/test.file' - eq OK, (os_setperm 'unit-test-directory/test.file', perm_orig) - - it 'returns false if the file is writable', -> - eq false, os_file_is_readonly 'unit-test-directory/test.file' - - describe 'os_file_is_writable', -> - it 'returns 0 if the file is readonly', -> - perm = os_getperm 'unit-test-directory/test.file' - perm_orig = perm - perm = unset_bit perm, ffi.C.kS_IWUSR - perm = unset_bit perm, ffi.C.kS_IWGRP - perm = unset_bit perm, ffi.C.kS_IWOTH - eq OK, (os_setperm 'unit-test-directory/test.file', perm) - eq 0, os_file_is_writable 'unit-test-directory/test.file' - eq OK, (os_setperm 'unit-test-directory/test.file', perm_orig) - - it 'returns 1 if the file is writable', -> - eq 1, os_file_is_writable 'unit-test-directory/test.file' - - it 'returns 2 when given a folder with rights to write into', -> - eq 2, os_file_is_writable 'unit-test-directory' - - describe 'file operations', -> - os_file_exists = (filename) -> - fs.os_file_exists (to_cstr filename) - - os_rename = (path, new_path) -> - fs.os_rename (to_cstr path), (to_cstr new_path) - - os_remove = (path) -> - fs.os_remove (to_cstr path) - - os_open = (path, flags, mode) -> - fs.os_open (to_cstr path), flags, mode - - describe 'os_file_exists', -> - it 'returns false when given a non-existing file', -> - eq false, (os_file_exists 'non-existing-file') - - it 'returns true when given an existing file', -> - eq true, (os_file_exists 'unit-test-directory/test.file') - - describe 'os_rename', -> - test = 'unit-test-directory/test.file' - not_exist = 'unit-test-directory/not_exist.file' - - it 'can rename file if destination file does not exist', -> - eq OK, (os_rename test, not_exist) - eq false, (os_file_exists test) - eq true, (os_file_exists not_exist) - eq OK, (os_rename not_exist, test) -- restore test file - - it 'fail if source file does not exist', -> - eq FAIL, (os_rename not_exist, test) - - it 'can overwrite destination file if it exists', -> - other = 'unit-test-directory/other.file' - file = io.open other, 'w' - file\write 'other' - file\flush! - file\close! - - eq OK, (os_rename other, test) - eq false, (os_file_exists other) - eq true, (os_file_exists test) - file = io.open test, 'r' - eq 'other', (file\read '*all') - file\close! - - describe 'os_remove', -> - before_each -> - (io.open 'unit-test-directory/test_remove.file', 'w').close! - after_each -> - os.remove 'unit-test-directory/test_remove.file' - - it 'returns non-zero when given a non-existing file', -> - neq 0, (os_remove 'non-existing-file') - - it 'removes the given file and returns 0', -> - f = 'unit-test-directory/test_remove.file' - assert_file_exists f - eq 0, (os_remove f) - assert_file_does_not_exist f - - describe 'os_open', -> - before_each -> - (io.open 'unit-test-directory/test_existing.file', 'w').close! - after_each -> - os.remove 'unit-test-directory/test_existing.file' - os.remove 'test_new_file' - - new_file = 'test_new_file' - existing_file = 'unit-test-directory/test_existing.file' - - it 'returns -ENOENT for O_RDWR on a non-existing file', -> - eq -ffi.C.kENOENT, (os_open 'non-existing-file', ffi.C.kO_RDWR, 0) - - it 'returns non-negative for O_CREAT on a non-existing file', -> - assert_file_does_not_exist new_file - assert.is_true 0 <= (os_open new_file, ffi.C.kO_CREAT, 0) - - it 'returns non-negative for O_CREAT on a existing file', -> - assert_file_exists existing_file - assert.is_true 0 <= (os_open existing_file, ffi.C.kO_CREAT, 0) - - it 'returns -EEXIST for O_CREAT|O_EXCL on a existing file', -> - assert_file_exists existing_file - eq -ffi.C.kEEXIST, (os_open existing_file, (bit.bor ffi.C.kO_CREAT, ffi.C.kO_EXCL), 0) - - it 'sets `rwx` permissions for O_CREAT 700', -> - assert_file_does_not_exist new_file - --create the file - os_open new_file, ffi.C.kO_CREAT, tonumber("700", 8) - --verify permissions - eq 'rwx------', lfs.attributes(new_file)['permissions'] - - it 'sets `rw` permissions for O_CREAT 600', -> - assert_file_does_not_exist new_file - --create the file - os_open new_file, ffi.C.kO_CREAT, tonumber("600", 8) - --verify permissions - eq 'rw-------', lfs.attributes(new_file)['permissions'] - - it 'returns a non-negative file descriptor for an existing file', -> - assert.is_true 0 <= (os_open existing_file, ffi.C.kO_RDWR, 0) - - describe 'folder operations', -> - os_mkdir = (path, mode) -> - fs.os_mkdir (to_cstr path), mode - - os_rmdir = (path) -> - fs.os_rmdir (to_cstr path) - - describe 'os_mkdir', -> - it 'returns non-zero when given an already existing directory', -> - mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR - neq 0, (os_mkdir 'unit-test-directory', mode) - - it 'creates a directory and returns 0', -> - mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR - eq false, (os_isdir 'unit-test-directory/new-dir') - eq 0, (os_mkdir 'unit-test-directory/new-dir', mode) - eq true, (os_isdir 'unit-test-directory/new-dir') - lfs.rmdir 'unit-test-directory/new-dir' - - describe 'os_rmdir', -> - it 'returns non_zero when given a non-existing directory', -> - neq 0, (os_rmdir 'non-existing-directory') - - it 'removes the given directory and returns 0', -> - lfs.mkdir 'unit-test-directory/new-dir' - eq 0, (os_rmdir 'unit-test-directory/new-dir', mode) - eq false, (os_isdir 'unit-test-directory/new-dir') - - describe 'FileInfo', -> - - file_info_new = () -> - file_info = ffi.new 'FileInfo[1]' - file_info[0].stat.st_ino = 0 - file_info[0].stat.st_dev = 0 - file_info - - is_file_info_filled = (file_info) -> - file_info[0].stat.st_ino > 0 and file_info[0].stat.st_dev > 0 - - file_id_new = () -> - file_info = ffi.new 'FileID[1]' - file_info[0].inode = 0 - file_info[0].device_id = 0 - file_info - - describe 'os_get_file_info', -> - it 'returns false if given a non-existing file', -> - file_info = file_info_new! - assert.is_false (fs.os_get_file_info '/non-existent', file_info) - - it 'returns true if given an existing file and fills file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info) - assert.is_true (is_file_info_filled file_info) - - it 'returns the file info of the linked file, not the link', -> - file_info = file_info_new! - path = 'unit-test-directory/test_link.file' - assert.is_true (fs.os_get_file_info path, file_info) - assert.is_true (is_file_info_filled file_info) - mode = tonumber file_info[0].stat.st_mode - eq ffi.C.kS_IFREG, (bit.band mode, ffi.C.kS_IFMT) - - describe 'os_get_file_info_link', -> - it 'returns false if given a non-existing file', -> - file_info = file_info_new! - assert.is_false (fs.os_get_file_info_link '/non-existent', file_info) - - it 'returns true if given an existing file and fills file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info_link path, file_info) - assert.is_true (is_file_info_filled file_info) - - it 'returns the file info of the link, not the linked file', -> - file_info = file_info_new! - path = 'unit-test-directory/test_link.file' - assert.is_true (fs.os_get_file_info_link path, file_info) - assert.is_true (is_file_info_filled file_info) - mode = tonumber file_info[0].stat.st_mode - eq ffi.C.kS_IFLNK, (bit.band mode, ffi.C.kS_IFMT) - - describe 'os_get_file_info_fd', -> - it 'returns false if given an invalid file descriptor', -> - file_info = file_info_new! - assert.is_false (fs.os_get_file_info_fd -1, file_info) - - it 'returns true if given a file descriptor and fills file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - fd = ffi.C.open path, 0 - assert.is_true (fs.os_get_file_info_fd fd, file_info) - assert.is_true (is_file_info_filled file_info) - ffi.C.close fd - - describe 'os_file_info_id_equal', -> - it 'returns false if file infos represent different files', -> - file_info_1 = file_info_new! - file_info_2 = file_info_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_2.file' - assert.is_true (fs.os_get_file_info path_1, file_info_1) - assert.is_true (fs.os_get_file_info path_2, file_info_2) - assert.is_false (fs.os_file_info_id_equal file_info_1, file_info_2) - - it 'returns true if file infos represent the same file', -> - file_info_1 = file_info_new! - file_info_2 = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info_1) - assert.is_true (fs.os_get_file_info path, file_info_2) - assert.is_true (fs.os_file_info_id_equal file_info_1, file_info_2) - - it 'returns true if file infos represent the same file (symlink)', -> - file_info_1 = file_info_new! - file_info_2 = file_info_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_link.file' - assert.is_true (fs.os_get_file_info path_1, file_info_1) - assert.is_true (fs.os_get_file_info path_2, file_info_2) - assert.is_true (fs.os_file_info_id_equal file_info_1, file_info_2) - - describe 'os_file_info_get_id', -> - it 'extracts ino/dev from file_info into file_id', -> - file_info = file_info_new! - file_id = file_id_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info) - fs.os_file_info_get_id(file_info, file_id) - eq file_info[0].stat.st_ino, file_id[0].inode - eq file_info[0].stat.st_dev, file_id[0].device_id - - describe 'os_file_info_get_inode', -> - it 'returns the inode from file_info', -> - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_info path, file_info) - inode = fs.os_file_info_get_inode(file_info) - eq file_info[0].stat.st_ino, inode - - describe 'os_get_file_id', -> - it 'returns false if given an non-existing file', -> - file_id = file_id_new! - assert.is_false (fs.os_get_file_id '/non-existent', file_id) - - it 'returns true if given an existing file and fills file_id', -> - file_id = file_id_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_id path, file_id) - assert.is_true 0 < file_id[0].inode - assert.is_true 0 < file_id[0].device_id - - describe 'os_file_id_equal', -> - it 'returns true if two FileIDs are equal', -> - file_id = file_id_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_id path, file_id) - assert.is_true (fs.os_file_id_equal file_id, file_id) - - it 'returns false if two FileIDs are not equal', -> - file_id_1 = file_id_new! - file_id_2 = file_id_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_2.file' - assert.is_true (fs.os_get_file_id path_1, file_id_1) - assert.is_true (fs.os_get_file_id path_2, file_id_2) - assert.is_false (fs.os_file_id_equal file_id_1, file_id_2) - - describe 'os_file_id_equal_file_info', -> - it 'returns true if file_id and file_info represent the same file', -> - file_id = file_id_new! - file_info = file_info_new! - path = 'unit-test-directory/test.file' - assert.is_true (fs.os_get_file_id path, file_id) - assert.is_true (fs.os_get_file_info path, file_info) - assert.is_true (fs.os_file_id_equal_file_info file_id, file_info) - - it 'returns false if file_id and file_info represent different files',-> - file_id = file_id_new! - file_info = file_info_new! - path_1 = 'unit-test-directory/test.file' - path_2 = 'unit-test-directory/test_2.file' - assert.is_true (fs.os_get_file_id path_1, file_id) - assert.is_true (fs.os_get_file_info path_2, file_info) - assert.is_false (fs.os_file_id_equal_file_info file_id, file_info) - diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua index 870034aad9..03aafe7e3c 100644 --- a/test/unit/os/shell_spec.lua +++ b/test/unit/os/shell_spec.lua @@ -24,11 +24,7 @@ local NULL = ffi.cast('void *', 0) describe('shell functions', function() setup(function() - -- the logging functions are complain if I don't do this - shell.init_homedir() - shell.event_init() - -- os_system() can't work when the p_sh and p_shcf variables are unset shell.p_sh = to_cstr('/bin/bash') shell.p_shcf = to_cstr('-c') diff --git a/test/unit/os/users_spec.lua b/test/unit/os/users_spec.lua new file mode 100644 index 0000000000..df5d2365c6 --- /dev/null +++ b/test/unit/os/users_spec.lua @@ -0,0 +1,91 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local NULL = helpers.NULL +local OK = helpers.OK +local FAIL = helpers.FAIL + +local users = cimport('./src/nvim/os/os.h', 'unistd.h') + +function garray_new() + return ffi.new('garray_T[1]') +end + +function garray_get_len(array) + return array[0].ga_len +end + +function garray_get_item(array, index) + return (ffi.cast('void **', array[0].ga_data))[index] +end + +describe('users function', function() + -- will probably not work on windows + local current_username = os.getenv('USER') + + describe('os_get_usernames', function() + it('returns FAIL if called with NULL', function() + eq(FAIL, users.os_get_usernames(NULL)) + end) + + it('fills the names garray with os usernames and returns OK', function() + local ga_users = garray_new() + eq(OK, users.os_get_usernames(ga_users)) + local user_count = garray_get_len(ga_users) + assert.is_true(user_count > 0) + local current_username_found = false + for i = 0, user_count - 1 do + local name = ffi.string((garray_get_item(ga_users, i))) + if name == current_username then + current_username_found = true + end + end + assert.is_true(current_username_found) + end) + end) + + describe('os_get_user_name', function() + it('should write the username into the buffer and return OK', function() + local name_out = ffi.new('char[100]') + eq(OK, users.os_get_user_name(name_out, 100)) + eq(current_username, ffi.string(name_out)) + end) + end) + + describe('os_get_uname', function() + it('should write the username into the buffer and return OK', function() + local name_out = ffi.new('char[100]') + local user_id = lib.getuid() + eq(OK, users.os_get_uname(user_id, name_out, 100)) + eq(current_username, ffi.string(name_out)) + end) + + it('should FAIL if the userid is not found', function() + local name_out = ffi.new('char[100]') + -- hoping nobody has this uid + local user_id = 2342 + eq(FAIL, users.os_get_uname(user_id, name_out, 100)) + eq('2342', ffi.string(name_out)) + end) + end) + + describe('os_get_user_directory', function() + it('should return NULL if called with NULL', function() + eq(NULL, users.os_get_user_directory(NULL)) + end) + + it('should return $HOME for the current user', function() + local home = os.getenv('HOME') + eq(home, ffi.string((users.os_get_user_directory(current_username)))) + end) + + it('should return NULL if the user is not found', function() + eq(NULL, users.os_get_user_directory('neovim_user_not_found_test')) + end) + end) +end) diff --git a/test/unit/os/users_spec.moon b/test/unit/os/users_spec.moon deleted file mode 100644 index 35c23ccc6f..0000000000 --- a/test/unit/os/users_spec.moon +++ /dev/null @@ -1,70 +0,0 @@ -{:cimport, :internalize, :eq, :ffi, :lib, :cstr, :NULL, :OK, :FAIL} = require 'test.unit.helpers' - -users = cimport './src/nvim/os/os.h', 'unistd.h' - -garray_new = () -> - ffi.new 'garray_T[1]' - -garray_get_len = (array) -> - array[0].ga_len - -garray_get_item = (array, index) -> - (ffi.cast 'void **', array[0].ga_data)[index] - - -describe 'users function', -> - - -- will probably not work on windows - current_username = os.getenv 'USER' - - describe 'os_get_usernames', -> - - it 'returns FAIL if called with NULL', -> - eq FAIL, users.os_get_usernames NULL - - it 'fills the names garray with os usernames and returns OK', -> - ga_users = garray_new! - eq OK, users.os_get_usernames ga_users - user_count = garray_get_len ga_users - assert.is_true user_count > 0 - current_username_found = false - for i = 0, user_count - 1 - name = ffi.string (garray_get_item ga_users, i) - if name == current_username - current_username_found = true - assert.is_true current_username_found - - describe 'os_get_user_name', -> - - it 'should write the username into the buffer and return OK', -> - name_out = ffi.new 'char[100]' - eq OK, users.os_get_user_name(name_out, 100) - eq current_username, ffi.string name_out - - describe 'os_get_uname', -> - - it 'should write the username into the buffer and return OK', -> - name_out = ffi.new 'char[100]' - user_id = lib.getuid! - eq OK, users.os_get_uname(user_id, name_out, 100) - eq current_username, ffi.string name_out - - it 'should FAIL if the userid is not found', -> - name_out = ffi.new 'char[100]' - -- hoping nobody has this uid - user_id = 2342 - eq FAIL, users.os_get_uname(user_id, name_out, 100) - eq '2342', ffi.string name_out - - describe 'os_get_user_directory', -> - - it 'should return NULL if called with NULL', -> - eq NULL, users.os_get_user_directory NULL - - it 'should return $HOME for the current user', -> - home = os.getenv('HOME') - eq home, ffi.string (users.os_get_user_directory current_username) - - it 'should return NULL if the user is not found', -> - eq NULL, users.os_get_user_directory 'neovim_user_not_found_test' - diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua new file mode 100644 index 0000000000..1824eaeccc --- /dev/null +++ b/test/unit/path_spec.lua @@ -0,0 +1,482 @@ +local helpers = require('test.unit.helpers') + +local cimport = helpers.cimport +local internalize = helpers.internalize +local eq = helpers.eq +local neq = helpers.neq +local ffi = helpers.ffi +local lib = helpers.lib +local cstr = helpers.cstr +local to_cstr = helpers.to_cstr +local NULL = helpers.NULL +local OK = helpers.OK +local FAIL = helpers.FAIL + +require('lfs') +cimport('string.h') +local path = cimport('./src/nvim/path.h') + +-- import constants parsed by ffi +local kEqualFiles = path.kEqualFiles +local kDifferentFiles = path.kDifferentFiles +local kBothFilesMissing = path.kBothFilesMissing +local kOneFileMissing = path.kOneFileMissing +local kEqualFileNames = path.kEqualFileNames + +local len = 0 +local buffer = nil + +describe('path function', function() + describe('path_full_dir_name', function() + setup(function() + lfs.mkdir('unit-test-directory') + end) + + teardown(function() + lfs.rmdir('unit-test-directory') + end) + + function path_full_dir_name(directory, buffer, len) + directory = to_cstr(directory) + return path.path_full_dir_name(directory, buffer, len) + end + + before_each(function() + -- Create empty string buffer which will contain the resulting path. + len = (string.len(lfs.currentdir())) + 22 + buffer = cstr(len, '') + end) + + it('returns the absolute directory name of a given relative one', function() + local result = path_full_dir_name('..', buffer, len) + eq(OK, result) + local old_dir = lfs.currentdir() + lfs.chdir('..') + local expected = lfs.currentdir() + lfs.chdir(old_dir) + eq(expected, (ffi.string(buffer))) + end) + + it('returns the current directory name if the given string is empty', function() + eq(OK, (path_full_dir_name('', buffer, len))) + eq(lfs.currentdir(), (ffi.string(buffer))) + end) + + it('fails if the given directory does not exist', function() + eq(FAIL, path_full_dir_name('does_not_exist', buffer, len)) + end) + + it('works with a normal relative dir', function() + local result = path_full_dir_name('unit-test-directory', buffer, len) + eq(lfs.currentdir() .. '/unit-test-directory', (ffi.string(buffer))) + eq(OK, result) + end) + end) + + describe('path_full_compare', function() + function path_full_compare(s1, s2, cn) + s1 = to_cstr(s1) + s2 = to_cstr(s2) + return path.path_full_compare(s1, s2, cn or 0) + end + + local f1 = 'f1.o' + local f2 = 'f2.o' + before_each(function() + -- create the three files that will be used in this spec + io.open(f1, 'w').close() + io.open(f2, 'w').close() + end) + + after_each(function() + os.remove(f1) + os.remove(f2) + end) + + it('returns kEqualFiles when passed the same file', function() + eq(kEqualFiles, (path_full_compare(f1, f1))) + end) + + it('returns kEqualFileNames when files that dont exist and have same name', function() + eq(kEqualFileNames, (path_full_compare('null.txt', 'null.txt', true))) + end) + + it('returns kBothFilesMissing when files that dont exist', function() + eq(kBothFilesMissing, (path_full_compare('null.txt', 'null.txt'))) + end) + + it('returns kDifferentFiles when passed different files', function() + eq(kDifferentFiles, (path_full_compare(f1, f2))) + eq(kDifferentFiles, (path_full_compare(f2, f1))) + end) + + it('returns kOneFileMissing if only one does not exist', function() + eq(kOneFileMissing, (path_full_compare(f1, 'null.txt'))) + eq(kOneFileMissing, (path_full_compare('null.txt', f1))) + end) + end) + + describe('path_tail', function() + function path_tail(file) + local res = path.path_tail((to_cstr(file))) + neq(NULL, res) + return ffi.string(res) + end + + it('returns the tail of a given file path', function() + eq('file.txt', path_tail('directory/file.txt')) + end) + + it('returns an empty string if file ends in a slash', function() + eq('', path_tail('directory/')) + end) + end) + + describe('path_tail_with_sep', function() + function path_tail_with_sep(file) + local res = path.path_tail_with_sep((to_cstr(file))) + neq(NULL, res) + return ffi.string(res) + end + + it('returns the tail of a file together with its separator', function() + eq('///file.txt', path_tail_with_sep('directory///file.txt')) + end) + + it('returns an empty string when given an empty file name', function() + eq('', path_tail_with_sep('')) + end) + + it('returns only the separator if there is a trailing separator', function() + eq('/', path_tail_with_sep('some/directory/')) + end) + + it('cuts a leading separator', function() + eq('file.txt', path_tail_with_sep('/file.txt')) + eq('', path_tail_with_sep('/')) + end) + + it('returns the whole file name if there is no separator', function() + eq('file.txt', path_tail_with_sep('file.txt')) + end) + end) + + describe('invocation_path_tail', function() + -- Returns the path tail and length (out param) of the tail. + -- Does not convert the tail from C-pointer to lua string for use with + -- strcmp. + function invocation_path_tail(invk) + local plen = ffi.new('size_t[?]', 1) + local ptail = path.invocation_path_tail((to_cstr(invk)), plen) + neq(NULL, ptail) + + -- it does not change the output if len==NULL + local tail2 = path.invocation_path_tail((to_cstr(invk)), NULL) + neq(NULL, tail2) + eq((ffi.string(ptail)), (ffi.string(tail2))) + return ptail, plen[0] + end + + -- This test mimics the intended use in C. + function compare(base, pinvk, len) + return eq(0, (ffi.C.strncmp((to_cstr(base)), pinvk, len))) + end + + it('returns the executable name of an invocation given a relative invocation', function() + local invk, len = invocation_path_tail('directory/exe a b c') + compare("exe a b c", invk, len) + eq(3, len) + end) + + it('returns the executable name of an invocation given an absolute invocation', function() + if ffi.os == 'Windows' then + local invk, len = invocation_path_tail('C:\\Users\\anyone\\Program Files\\z a b') + compare('z a b', invk, len) + eq(1, len) + else + local invk, len = invocation_path_tail('/usr/bin/z a b') + compare('z a b', invk, len) + eq(1, len) + end + end) + + it('does not count arguments to the executable as part of its path', function() + local invk, len = invocation_path_tail('exe a/b\\c') + compare("exe a/b\\c", invk, len) + eq(3, len) + end) + + it('only accepts whitespace as a terminator for the executable name', function() + local invk, len = invocation_path_tail('exe-a+b_c[]()|#!@$%^&*') + eq('exe-a+b_c[]()|#!@$%^&*', (ffi.string(invk))) + end) + + it('is equivalent to path_tail when args do not contain a path separator', function() + local ptail = path.path_tail(to_cstr("a/b/c x y z")) + neq(NULL, ptail) + local tail = ffi.string(ptail) + local invk, len = invocation_path_tail("a/b/c x y z") + eq(tail, ffi.string(invk)) + end) + + it('is not equivalent to path_tail when args contain a path separator', function() + local ptail = path.path_tail(to_cstr("a/b/c x y/z")) + neq(NULL, ptail) + local invk, len = invocation_path_tail("a/b/c x y/z") + neq((ffi.string(ptail)), (ffi.string(invk))) + end) + end) + + describe('path_next_component', function() + function path_next_component(file) + local res = path.path_next_component((to_cstr(file))) + neq(NULL, res) + return ffi.string(res) + end + + it('returns', function() + eq('directory/file.txt', path_next_component('some/directory/file.txt')) + end) + + it('returns empty string if given file contains no separator', function() + eq('', path_next_component('file.txt')) + end) + end) + + describe('path_shorten_fname', function() + it('returns NULL if `full_path` is NULL', function() + local dir = to_cstr('some/directory/file.txt') + eq(NULL, (path.path_shorten_fname(NULL, dir))) + end) + + it('returns NULL if the path and dir does not match', function() + local dir = to_cstr('not/the/same') + local full = to_cstr('as/this.txt') + eq(NULL, (path.path_shorten_fname(full, dir))) + end) + + it('returns NULL if the path is not separated properly', function() + local dir = to_cstr('some/very/long/') + local full = to_cstr('some/very/long/directory/file.txt') + eq(NULL, (path.path_shorten_fname(full, dir))) + end) + + it('shortens the filename if `dir_name` is the start of `full_path`', function() + local full = to_cstr('some/very/long/directory/file.txt') + local dir = to_cstr('some/very/long') + eq('directory/file.txt', (ffi.string(path.path_shorten_fname(full, dir)))) + end) + end) +end) + +describe('path_shorten_fname_if_possible', function() + local cwd = lfs.currentdir() + + before_each(function() + lfs.mkdir('ut_directory') + end) + + after_each(function() + lfs.chdir(cwd) + lfs.rmdir('ut_directory') + end) + + describe('path_shorten_fname_if_possible', function() + it('returns shortened path if possible', function() + lfs.chdir('ut_directory') + local full = to_cstr(lfs.currentdir() .. '/subdir/file.txt') + eq('subdir/file.txt', (ffi.string(path.path_shorten_fname_if_possible(full)))) + end) + + it('returns `full_path` if a shorter version is not possible', function() + local old = lfs.currentdir() + lfs.chdir('ut_directory') + local full = old .. '/subdir/file.txt' + eq(full, (ffi.string(path.path_shorten_fname_if_possible(to_cstr(full))))) + end) + + it('returns NULL if `full_path` is NULL', function() + eq(NULL, (path.path_shorten_fname_if_possible(NULL))) + end) + end) +end) + +describe('more path function', function() + setup(function() + lfs.mkdir('unit-test-directory'); + io.open('unit-test-directory/test.file', 'w').close() + + -- Since the tests are executed, they are called by an executable. We use + -- that executable for several asserts. + absolute_executable = arg[0] + + -- Split absolute_executable into a directory and the actual file name for + -- later usage. + directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') + end) + + teardown(function() + os.remove('unit-test-directory/test.file') + lfs.rmdir('unit-test-directory') + end) + + describe('vim_FullName', function() + function vim_FullName(filename, buffer, length, force) + filename = to_cstr(filename) + return path.vim_FullName(filename, buffer, length, force) + end + + before_each(function() + -- Create empty string buffer which will contain the resulting path. + len = (string.len(lfs.currentdir())) + 33 + buffer = cstr(len, '') + end) + + it('fails if given filename is NULL', function() + local force_expansion = 1 + local result = path.vim_FullName(NULL, buffer, len, force_expansion) + eq(FAIL, result) + end) + + it('uses the filename if the filename is a URL', function() + local force_expansion = 1 + local filename = 'http://www.neovim.org' + local result = vim_FullName(filename, buffer, len, force_expansion) + eq(filename, (ffi.string(buffer))) + eq(OK, result) + end) + + it('fails and uses filename if given filename contains non-existing directory', function() + local force_expansion = 1 + local filename = 'non_existing_dir/test.file' + local result = vim_FullName(filename, buffer, len, force_expansion) + eq(filename, (ffi.string(buffer))) + eq(FAIL, result) + end) + + it('concatenates given filename if it does not contain a slash', function() + local force_expansion = 1 + local result = vim_FullName('test.file', buffer, len, force_expansion) + local expected = lfs.currentdir() .. '/test.file' + eq(expected, (ffi.string(buffer))) + eq(OK, result) + end) + + it('concatenates given filename if it is a directory but does not contain a\n slash', function() + local force_expansion = 1 + local result = vim_FullName('..', buffer, len, force_expansion) + local expected = lfs.currentdir() .. '/..' + eq(expected, (ffi.string(buffer))) + eq(OK, result) + end) + + -- Is it possible for every developer to enter '..' directory while running + -- the unit tests? Which other directory would be better? + it('enters given directory (instead of just concatenating the strings) if possible and if path contains a slash', function() + local force_expansion = 1 + local result = vim_FullName('../test.file', buffer, len, force_expansion) + local old_dir = lfs.currentdir() + lfs.chdir('..') + local expected = lfs.currentdir() .. '/test.file' + lfs.chdir(old_dir) + eq(expected, (ffi.string(buffer))) + eq(OK, result) + end) + + it('just copies the path if it is already absolute and force=0', function() + local force_expansion = 0 + local absolute_path = '/absolute/path' + local result = vim_FullName(absolute_path, buffer, len, force_expansion) + eq(absolute_path, (ffi.string(buffer))) + eq(OK, result) + end) + + it('fails and uses filename when the path is relative to HOME', function() + local force_expansion = 1 + local absolute_path = '~/home.file' + local result = vim_FullName(absolute_path, buffer, len, force_expansion) + eq(absolute_path, (ffi.string(buffer))) + eq(FAIL, result) + end) + + it('works with some "normal" relative path with directories', function() + local force_expansion = 1 + local result = vim_FullName('unit-test-directory/test.file', buffer, len, force_expansion) + eq(OK, result) + eq(lfs.currentdir() .. '/unit-test-directory/test.file', (ffi.string(buffer))) + end) + + it('does not modify the given filename', function() + local force_expansion = 1 + local filename = to_cstr('unit-test-directory/test.file') + -- Don't use the wrapper here but pass a cstring directly to the c + -- function. + local result = path.vim_FullName(filename, buffer, len, force_expansion) + eq(lfs.currentdir() .. '/unit-test-directory/test.file', (ffi.string(buffer))) + eq('unit-test-directory/test.file', (ffi.string(filename))) + eq(OK, result) + end) + end) + + describe('append_path', function() + it('joins given paths with a slash', function() + local path1 = cstr(100, 'path1') + local to_append = to_cstr('path2') + eq(OK, (path.append_path(path1, to_append, 100))) + eq("path1/path2", (ffi.string(path1))) + end) + + it('joins given paths without adding an unnecessary slash', function() + local path1 = cstr(100, 'path1/') + local to_append = to_cstr('path2') + eq(OK, path.append_path(path1, to_append, 100)) + eq("path1/path2", (ffi.string(path1))) + end) + + it('fails and uses filename if there is not enough space left for to_append', function() + local path1 = cstr(11, 'path1/') + local to_append = to_cstr('path2') + eq(FAIL, (path.append_path(path1, to_append, 11))) + end) + + it('does not append a slash if to_append is empty', function() + local path1 = cstr(6, 'path1') + local to_append = to_cstr('') + eq(OK, (path.append_path(path1, to_append, 6))) + eq('path1', (ffi.string(path1))) + end) + + it('does not append unnecessary dots', function() + local path1 = cstr(6, 'path1') + local to_append = to_cstr('.') + eq(OK, (path.append_path(path1, to_append, 6))) + eq('path1', (ffi.string(path1))) + end) + + it('copies to_append to path, if path is empty', function() + local path1 = cstr(7, '') + local to_append = to_cstr('/path2') + eq(OK, (path.append_path(path1, to_append, 7))) + eq('/path2', (ffi.string(path1))) + end) + end) + + describe('path_is_absolute_path', function() + function path_is_absolute_path(filename) + filename = to_cstr(filename) + return path.path_is_absolute_path(filename) + end + + it('returns true if filename starts with a slash', function() + eq(OK, path_is_absolute_path('/some/directory/')) + end) + + it('returns true if filename starts with a tilde', function() + eq(OK, path_is_absolute_path('~/in/my/home~/directory')) + end) + + it('returns false if filename starts not with slash nor tilde', function() + eq(FAIL, path_is_absolute_path('not/in/my/home~/directory')) + end) + end) +end) diff --git a/test/unit/path_spec.moon b/test/unit/path_spec.moon deleted file mode 100644 index f7e5ed00bc..0000000000 --- a/test/unit/path_spec.moon +++ /dev/null @@ -1,383 +0,0 @@ -{:cimport, :internalize, :eq, :neq, :ffi, :lib, :cstr, :to_cstr, :NULL, :OK, :FAIL} = require 'test.unit.helpers' -require 'lfs' - -path = cimport './src/nvim/path.h' - --- import constants parsed by ffi -{:kEqualFiles, :kDifferentFiles, :kBothFilesMissing, :kOneFileMissing, :kEqualFileNames} = path - -describe 'path function', -> - describe 'path_full_dir_name', -> - setup -> - lfs.mkdir 'unit-test-directory' - - teardown -> - lfs.rmdir 'unit-test-directory' - - path_full_dir_name = (directory, buffer, len) -> - directory = to_cstr directory - path.path_full_dir_name directory, buffer, len - - before_each -> - -- Create empty string buffer which will contain the resulting path. - export len = (string.len lfs.currentdir!) + 22 - export buffer = cstr len, '' - - it 'returns the absolute directory name of a given relative one', -> - result = path_full_dir_name '..', buffer, len - eq OK, result - old_dir = lfs.currentdir! - lfs.chdir '..' - expected = lfs.currentdir! - lfs.chdir old_dir - eq expected, (ffi.string buffer) - - it 'returns the current directory name if the given string is empty', -> - eq OK, (path_full_dir_name '', buffer, len) - eq lfs.currentdir!, (ffi.string buffer) - - it 'fails if the given directory does not exist', -> - eq FAIL, path_full_dir_name('does_not_exist', buffer, len) - - it 'works with a normal relative dir', -> - result = path_full_dir_name('unit-test-directory', buffer, len) - eq lfs.currentdir! .. '/unit-test-directory', (ffi.string buffer) - eq OK, result - - describe 'path_full_compare', -> - - path_full_compare = (s1, s2, cn) -> - s1 = to_cstr s1 - s2 = to_cstr s2 - path.path_full_compare s1, s2, cn or 0 - - f1 = 'f1.o' - f2 = 'f2.o' - - before_each -> - -- create the three files that will be used in this spec - (io.open f1, 'w').close! - (io.open f2, 'w').close! - - after_each -> - os.remove f1 - os.remove f2 - - it 'returns kEqualFiles when passed the same file', -> - eq kEqualFiles, (path_full_compare f1, f1) - - it 'returns kEqualFileNames when files that dont exist and have same name', -> - eq kEqualFileNames, (path_full_compare 'null.txt', 'null.txt', true) - - it 'returns kBothFilesMissing when files that dont exist', -> - eq kBothFilesMissing, (path_full_compare 'null.txt', 'null.txt') - - it 'returns kDifferentFiles when passed different files', -> - eq kDifferentFiles, (path_full_compare f1, f2) - eq kDifferentFiles, (path_full_compare f2, f1) - - it 'returns kOneFileMissing if only one does not exist', -> - eq kOneFileMissing, (path_full_compare f1, 'null.txt') - eq kOneFileMissing, (path_full_compare 'null.txt', f1) - - describe 'path_tail', -> - path_tail = (file) -> - res = path.path_tail (to_cstr file) - neq NULL, res - ffi.string res - - it 'returns the tail of a given file path', -> - eq 'file.txt', path_tail 'directory/file.txt' - - it 'returns an empty string if file ends in a slash', -> - eq '', path_tail 'directory/' - - describe 'path_tail_with_sep', -> - path_tail_with_sep = (file) -> - res = path.path_tail_with_sep (to_cstr file) - neq NULL, res - ffi.string res - - it 'returns the tail of a file together with its separator', -> - eq '///file.txt', path_tail_with_sep 'directory///file.txt' - - it 'returns an empty string when given an empty file name', -> - eq '', path_tail_with_sep '' - - it 'returns only the separator if there is a trailing separator', -> - eq '/', path_tail_with_sep 'some/directory/' - - it 'cuts a leading separator', -> - eq 'file.txt', path_tail_with_sep '/file.txt' - eq '', path_tail_with_sep '/' - - it 'returns the whole file name if there is no separator', -> - eq 'file.txt', path_tail_with_sep 'file.txt' - - describe 'invocation_path_tail', -> - -- Returns the path tail and length (out param) of the tail. - -- Does not convert the tail from C-pointer to lua string for use with - -- strcmp. - invocation_path_tail = (invk) -> - plen = ffi.new 'size_t[?]', 1 - ptail = path.invocation_path_tail (to_cstr invk), plen - neq NULL, ptail - - -- it does not change the output if len==NULL - tail2 = path.invocation_path_tail (to_cstr invk), NULL - neq NULL, tail2 - eq (ffi.string ptail), (ffi.string tail2) - - ptail, plen[0] - - -- This test mimics the intended use in C. - compare = (base, pinvk, len) -> - eq 0, (ffi.C.strncmp (to_cstr base), pinvk, len) - - it 'returns the executable name of an invocation given a relative invocation', -> - invk, len = invocation_path_tail 'directory/exe a b c' - compare "exe a b c", invk, len - eq 3, len - - it 'returns the executable name of an invocation given an absolute invocation', -> - if ffi.os == 'Windows' - invk, len = invocation_path_tail 'C:\\Users\\anyone\\Program Files\\z a b' - compare 'z a b', invk, len - eq 1, len - else - invk, len = invocation_path_tail '/usr/bin/z a b' - compare 'z a b', invk, len - eq 1, len - - it 'does not count arguments to the executable as part of its path', -> - invk, len = invocation_path_tail 'exe a/b\\c' - compare "exe a/b\\c", invk, len - eq 3, len - - it 'only accepts whitespace as a terminator for the executable name', -> - invk, len = invocation_path_tail 'exe-a+b_c[]()|#!@$%^&*' - eq 'exe-a+b_c[]()|#!@$%^&*', (ffi.string invk) - - it 'is equivalent to path_tail when args do not contain a path separator', -> - ptail = path.path_tail to_cstr "a/b/c x y z" - neq NULL, ptail - tail = ffi.string ptail - - invk, len = invocation_path_tail "a/b/c x y z" - eq tail, ffi.string invk - - it 'is not equivalent to path_tail when args contain a path separator', -> - ptail = path.path_tail to_cstr "a/b/c x y/z" - neq NULL, ptail - - invk, len = invocation_path_tail "a/b/c x y/z" - neq (ffi.string ptail), (ffi.string invk) - - describe 'path_next_component', -> - path_next_component = (file) -> - res = path.path_next_component (to_cstr file) - neq NULL, res - ffi.string res - - it 'returns', -> - eq 'directory/file.txt', path_next_component 'some/directory/file.txt' - - it 'returns empty string if given file contains no separator', -> - eq '', path_next_component 'file.txt' - - describe 'path_shorten_fname', -> - it 'returns NULL if `full_path` is NULL', -> - dir = to_cstr 'some/directory/file.txt' - eq NULL, (path.path_shorten_fname NULL, dir) - - it 'returns NULL if the path and dir does not match', -> - dir = to_cstr 'not/the/same' - full = to_cstr 'as/this.txt' - eq NULL, (path.path_shorten_fname full, dir) - - it 'returns NULL if the path is not separated properly', -> - dir = to_cstr 'some/very/long/' - full = to_cstr 'some/very/long/directory/file.txt' - eq NULL, (path.path_shorten_fname full, dir) - - it 'shortens the filename if `dir_name` is the start of `full_path`', -> - full = to_cstr 'some/very/long/directory/file.txt' - dir = to_cstr 'some/very/long' - eq 'directory/file.txt', (ffi.string path.path_shorten_fname full, dir) - -describe 'path_shorten_fname_if_possible', -> - cwd = lfs.currentdir! - before_each -> - lfs.mkdir 'ut_directory' - after_each -> - lfs.chdir cwd - lfs.rmdir 'ut_directory' - - describe 'path_shorten_fname_if_possible', -> - it 'returns shortened path if possible', -> - lfs.chdir 'ut_directory' - full = to_cstr lfs.currentdir! .. '/subdir/file.txt' - eq 'subdir/file.txt', (ffi.string path.path_shorten_fname_if_possible full) - - it 'returns `full_path` if a shorter version is not possible', -> - old = lfs.currentdir! - lfs.chdir 'ut_directory' - full = old .. '/subdir/file.txt' - eq full, (ffi.string path.path_shorten_fname_if_possible to_cstr full) - - it 'returns NULL if `full_path` is NULL', -> - eq NULL, (path.path_shorten_fname_if_possible NULL) - -describe 'more path function', -> - setup -> - lfs.mkdir 'unit-test-directory' - (io.open 'unit-test-directory/test.file', 'w').close! - - -- Since the tests are executed, they are called by an executable. We use - -- that executable for several asserts. - export absolute_executable = arg[0] - - -- Split absolute_executable into a directory and the actual file name for - -- later usage. - export directory, executable_name = string.match(absolute_executable, '^(.*)/(.*)$') - - teardown -> - os.remove 'unit-test-directory/test.file' - lfs.rmdir 'unit-test-directory' - - describe 'vim_FullName', -> - vim_FullName = (filename, buffer, length, force) -> - filename = to_cstr filename - path.vim_FullName filename, buffer, length, force - - before_each -> - -- Create empty string buffer which will contain the resulting path. - export len = (string.len lfs.currentdir!) + 33 - export buffer = cstr len, '' - - it 'fails if given filename is NULL', -> - force_expansion = 1 - result = path.vim_FullName NULL, buffer, len, force_expansion - eq FAIL, result - - it 'uses the filename if the filename is a URL', -> - force_expansion = 1 - filename = 'http://www.neovim.org' - result = vim_FullName filename, buffer, len, force_expansion - eq filename, (ffi.string buffer) - eq OK, result - - it 'fails and uses filename if given filename contains non-existing directory', -> - force_expansion = 1 - filename = 'non_existing_dir/test.file' - result = vim_FullName filename, buffer, len, force_expansion - eq filename, (ffi.string buffer) - eq FAIL, result - - it 'concatenates given filename if it does not contain a slash', -> - force_expansion = 1 - result = vim_FullName 'test.file', buffer, len, force_expansion - expected = lfs.currentdir! .. '/test.file' - eq expected, (ffi.string buffer) - eq OK, result - - it 'concatenates given filename if it is a directory but does not contain a - slash', -> - force_expansion = 1 - result = vim_FullName '..', buffer, len, force_expansion - expected = lfs.currentdir! .. '/..' - eq expected, (ffi.string buffer) - eq OK, result - - -- Is it possible for every developer to enter '..' directory while running - -- the unit tests? Which other directory would be better? - it 'enters given directory (instead of just concatenating the strings) if - possible and if path contains a slash', -> - force_expansion = 1 - result = vim_FullName '../test.file', buffer, len, force_expansion - old_dir = lfs.currentdir! - lfs.chdir '..' - expected = lfs.currentdir! .. '/test.file' - lfs.chdir old_dir - eq expected, (ffi.string buffer) - eq OK, result - - it 'just copies the path if it is already absolute and force=0', -> - force_expansion = 0 - absolute_path = '/absolute/path' - result = vim_FullName absolute_path, buffer, len, force_expansion - eq absolute_path, (ffi.string buffer) - eq OK, result - - it 'fails and uses filename when the path is relative to HOME', -> - force_expansion = 1 - absolute_path = '~/home.file' - result = vim_FullName absolute_path, buffer, len, force_expansion - eq absolute_path, (ffi.string buffer) - eq FAIL, result - - it 'works with some "normal" relative path with directories', -> - force_expansion = 1 - result = vim_FullName 'unit-test-directory/test.file', buffer, len, force_expansion - eq OK, result - eq lfs.currentdir! .. '/unit-test-directory/test.file', (ffi.string buffer) - - it 'does not modify the given filename', -> - force_expansion = 1 - filename = to_cstr 'unit-test-directory/test.file' - -- Don't use the wrapper here but pass a cstring directly to the c - -- function. - result = path.vim_FullName filename, buffer, len, force_expansion - eq lfs.currentdir! .. '/unit-test-directory/test.file', (ffi.string buffer) - eq 'unit-test-directory/test.file', (ffi.string filename) - eq OK, result - - describe 'append_path', -> - it 'joins given paths with a slash', -> - path1 = cstr 100, 'path1' - to_append = to_cstr 'path2' - eq OK, (path.append_path path1, to_append, 100) - eq "path1/path2", (ffi.string path1) - - it 'joins given paths without adding an unnecessary slash', -> - path1 = cstr 100, 'path1/' - to_append = to_cstr 'path2' - eq OK, path.append_path path1, to_append, 100 - eq "path1/path2", (ffi.string path1) - - it 'fails and uses filename if there is not enough space left for to_append', -> - path1 = cstr 11, 'path1/' - to_append = to_cstr 'path2' - eq FAIL, (path.append_path path1, to_append, 11) - - it 'does not append a slash if to_append is empty', -> - path1 = cstr 6, 'path1' - to_append = to_cstr '' - eq OK, (path.append_path path1, to_append, 6) - eq 'path1', (ffi.string path1) - - it 'does not append unnecessary dots', -> - path1 = cstr 6, 'path1' - to_append = to_cstr '.' - eq OK, (path.append_path path1, to_append, 6) - eq 'path1', (ffi.string path1) - - it 'copies to_append to path, if path is empty', -> - path1 = cstr 7, '' - to_append = to_cstr '/path2' - eq OK, (path.append_path path1, to_append, 7) - eq '/path2', (ffi.string path1) - - describe 'path_is_absolute_path', -> - path_is_absolute_path = (filename) -> - filename = to_cstr filename - path.path_is_absolute_path filename - - it 'returns true if filename starts with a slash', -> - eq OK, path_is_absolute_path '/some/directory/' - - it 'returns true if filename starts with a tilde', -> - eq OK, path_is_absolute_path '~/in/my/home~/directory' - - it 'returns false if filename starts not with slash nor tilde', -> - eq FAIL, path_is_absolute_path 'not/in/my/home~/directory' diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua new file mode 100644 index 0000000000..f17c7ba666 --- /dev/null +++ b/test/unit/preprocess.lua @@ -0,0 +1,198 @@ +-- helps managing loading different headers into the LuaJIT ffi. Untested on +-- windows, will probably need quite a bit of adjustment to run there. + +local ffi = require("ffi") + +local ccs = {} + +local env_cc = os.getenv("CC") +if env_cc then + table.insert(ccs, {path = "/usr/bin/env " .. tostring(env_cc), type = "gcc"}) +end + +if ffi.os == "Windows" then + table.insert(ccs, {path = "cl", type = "msvc"}) +end + +table.insert(ccs, {path = "/usr/bin/env cc", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc-4.9", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc-4.8", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env gcc-4.7", type = "gcc"}) +table.insert(ccs, {path = "/usr/bin/env clang", type = "clang"}) +table.insert(ccs, {path = "/usr/bin/env icc", type = "gcc"}) + +local quote_me = '[^%w%+%-%=%@%_%/]' -- complement (needn't quote) +local function shell_quote(str) + if string.find(str, quote_me) or str == '' then + return "'" .. string.gsub(str, "'", [['"'"']]) .. "'" + else + return str + end +end + +-- parse Makefile format dependencies into a Lua table +local function parse_make_deps(deps) + -- remove line breaks and line concatenators + deps = deps:gsub("\n", ""):gsub("\\", "") + -- remove the Makefile "target:" element + deps = deps:gsub(".+:", "") + -- remove redundant spaces + deps = deps:gsub(" +", " ") + + -- split according to token (space in this case) + local headers = {} + for token in deps:gmatch("[^%s]+") do + -- headers[token] = true + headers[#headers + 1] = token + end + + -- resolve path redirections (..) to normalize all paths + for i, v in ipairs(headers) do + -- double dots (..) + headers[i] = v:gsub("/[^/%s]+/%.%.", "") + -- single dot (.) + headers[i] = v:gsub("%./", "") + end + + return headers +end + +-- will produce a string that represents a meta C header file that includes +-- all the passed in headers. I.e.: +-- +-- headerize({"stdio.h", "math.h", true} +-- produces: +-- #include <stdio.h> +-- #include <math.h> +-- +-- headerize({"vim.h", "memory.h", false} +-- produces: +-- #include "vim.h" +-- #include "memory.h" +local function headerize(headers, global) + local pre = '"' + local post = pre + if global then + pre = "<" + post = ">" + end + + local formatted = {} + for i = 1, #headers do + local hdr = headers[i] + formatted[#formatted + 1] = "#include " .. + tostring(pre) .. + tostring(hdr) .. + tostring(post) + end + + return table.concat(formatted, "\n") +end + +local Gcc = { + -- preprocessor flags that will hopefully make the compiler produce C + -- declarations that the LuaJIT ffi understands. + preprocessor_extra_flags = { + '-D "aligned(ARGS)="', + '-D "__attribute__(ARGS)="', + '-D "__asm(ARGS)="', + '-D "__asm__(ARGS)="', + '-D "__inline__="', + '-D "EXTERN=extern"', + '-D "INIT(...)="', + '-D_GNU_SOURCE', + '-DINCLUDE_GENERATED_DECLARATIONS' + } +} + +function Gcc:new(obj) + obj = obj or {} + setmetatable(obj, self) + self.__index = self + return obj +end + +function Gcc:add_to_include_path(...) + local paths = {...} + for i = 1, #paths do + local path = paths[i] + local directive = '-I ' .. '"' .. path .. '"' + local ef = self.preprocessor_extra_flags + ef[#ef + 1] = directive + end +end + +-- returns a list of the headers files upon which this file relies +function Gcc:dependencies(hdr) + local out = io.popen(tostring(self.path) .. " -M " .. tostring(hdr) .. " 2>&1") + local deps = out:read("*a") + out:close() + if deps then + return parse_make_deps(deps) + else + return nil + end +end + +-- returns a stream representing a preprocessed form of the passed-in headers. +-- Don't forget to close the stream by calling the close() method on it. +function Gcc:preprocess_stream(...) + -- create pseudo-header + local pseudoheader = headerize({...}, false) + local defines = table.concat(self.preprocessor_extra_flags, ' ') + local cmd = ("echo $hdr | " .. + tostring(self.path) .. + " " .. + tostring(defines) .. + " -std=c99 -P -E -"):gsub('$hdr', shell_quote(pseudoheader)) + -- lfs = require("lfs") + -- print("CWD: #{lfs.currentdir!}") + -- print("CMD: #{cmd}") + -- io.stderr\write("CWD: #{lfs.currentdir!}\n") + -- io.stderr\write("CMD: #{cmd}\n") + return io.popen(cmd) +end + +local Clang = Gcc:new() +local Msvc = Gcc:new() + +local type_to_class = { + ["gcc"] = Gcc, + ["clang"] = Clang, + ["msvc"] = Msvc +} + +-- find the best cc. If os.exec causes problems on windows (like popping up +-- a console window) we might consider using something like this: +-- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec +local function find_best_cc(ccs) + for _, meta in pairs(ccs) do + local version = io.popen(tostring(meta.path) .. " -v 2>&1") + version:close() + if version then + return type_to_class[meta.type]:new({path = meta.path}) + end + end + return nil +end + +-- find the best cc. If os.exec causes problems on windows (like popping up +-- a console window) we might consider using something like this: +-- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec +local cc = nil +if cc == nil then + cc = find_best_cc(ccs) +end + +return { + includes = function(hdr) + return cc:dependencies(hdr) + end, + preprocess_stream = function(...) + return cc:preprocess_stream(...) + end, + add_to_include_path = function(...) + return cc:add_to_include_path(...) + end +} diff --git a/test/unit/preprocess.moon b/test/unit/preprocess.moon deleted file mode 100644 index cb734da2f7..0000000000 --- a/test/unit/preprocess.moon +++ /dev/null @@ -1,158 +0,0 @@ --- helps managing loading different headers into the LuaJIT ffi. Untested on --- windows, will probably need quite a bit of adjustment to run there. - -ffi = require("ffi") - -ccs = {} - -env_cc = os.getenv("CC") -if env_cc - table.insert(ccs, {path: "/usr/bin/env #{env_cc}", type: "gcc"}) - -if ffi.os == "Windows" - table.insert(ccs, {path: "cl", type: "msvc"}) - -table.insert(ccs, {path: "/usr/bin/env cc", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc-4.9", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc-4.8", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env gcc-4.7", type: "gcc"}) -table.insert(ccs, {path: "/usr/bin/env clang", type: "clang"}) -table.insert(ccs, {path: "/usr/bin/env icc", type: "gcc"}) - -quote_me = '[^%w%+%-%=%@%_%/]' -- complement (needn't quote) -shell_quote = (str) -> - if string.find(str, quote_me) or str == '' then - "'" .. string.gsub(str, "'", [['"'"']]) .. "'" - else - str - --- parse Makefile format dependencies into a Lua table -parse_make_deps = (deps) -> - -- remove line breaks and line concatenators - deps = deps\gsub("\n", "")\gsub("\\", "") - - -- remove the Makefile "target:" element - deps = deps\gsub(".+:", "") - - -- remove redundant spaces - deps = deps\gsub(" +", " ") - - -- split according to token (space in this case) - headers = {} - for token in deps\gmatch("[^%s]+") - -- headers[token] = true - headers[#headers + 1] = token - - -- resolve path redirections (..) to normalize all paths - for i, v in ipairs(headers) - -- double dots (..) - headers[i] = v\gsub("/[^/%s]+/%.%.", "") - - -- single dot (.) - headers[i] = v\gsub("%./", "") - - headers - --- will produce a string that represents a meta C header file that includes --- all the passed in headers. I.e.: --- --- headerize({"stdio.h", "math.h", true} --- produces: --- #include <stdio.h> --- #include <math.h> --- --- headerize({"vim.h", "memory.h", false} --- produces: --- #include "vim.h" --- #include "memory.h" -headerize = (headers, global) -> - pre = '"' - post = pre - if global - pre = "<" - post = ">" - - formatted = ["#include #{pre}#{hdr}#{post}" for hdr in *headers] - table.concat(formatted, "\n") - -class Gcc - -- preprocessor flags that will hopefully make the compiler produce C - -- declarations that the LuaJIT ffi understands. - @@preprocessor_extra_flags = { - '-D "aligned(ARGS)="', - '-D "__attribute__(ARGS)="', - '-D "__asm(ARGS)="', - '-D "__asm__(ARGS)="', - '-D "__inline__="', - '-D "EXTERN=extern"', - '-D "INIT(...)="', - '-D_GNU_SOURCE', - '-DINCLUDE_GENERATED_DECLARATIONS' - } - - new: (path) => - @path = path - - add_to_include_path: (...) => - paths = {...} - for path in *paths - directive = '-I ' .. '"' .. path .. '"' - @@preprocessor_extra_flags[#@@preprocessor_extra_flags + 1] = directive - - -- returns a list of the headers files upon which this file relies - dependencies: (hdr) => - out = io.popen("#{@path} -M #{hdr} 2>&1") - deps = out\read("*a") - out\close! - - if deps - parse_make_deps(deps) - else - nil - - -- returns a stream representing a preprocessed form of the passed-in - -- headers. Don't forget to close the stream by calling the close() method - -- on it. - preprocess_stream: (...) => - paths = {...} - -- create pseudo-header - pseudoheader = headerize(paths, false) - defines = table.concat(@@preprocessor_extra_flags, ' ') - cmd = ("echo $hdr | #{@path} #{defines} -std=c99 -P -E -")\gsub('$hdr', shell_quote(pseudoheader)) - -- lfs = require("lfs") - -- print("CWD: #{lfs.currentdir!}") - -- print("CMD: #{cmd}") - -- io.stderr\write("CWD: #{lfs.currentdir!}\n") - -- io.stderr\write("CMD: #{cmd}\n") - io.popen(cmd) - -class Clang extends Gcc -class Msvc extends Gcc - -type_to_class = { - "gcc": Gcc, - "clang": Clang, - "msvc": Msvc -} - -find_best_cc = (ccs) -> - for _, meta in pairs(ccs) - version = io.popen("#{meta.path} -v 2>&1") - version\close! - if version - return type_to_class[meta.type](meta.path) - nil - --- find the best cc. If os.exec causes problems on windows (like popping up --- a console window) we might consider using something like this: --- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec -cc = nil -if cc == nil - cc = find_best_cc(ccs) - -return { - includes: (hdr) -> cc\dependencies(hdr) - preprocess_stream: (...) -> cc\preprocess_stream(...) - add_to_include_path: (...) -> cc\add_to_include_path(...) -} diff --git a/test/unit/set.lua b/test/unit/set.lua new file mode 100644 index 0000000000..bfb6b8c41c --- /dev/null +++ b/test/unit/set.lua @@ -0,0 +1,127 @@ +-- a set class for fast union/diff, can always return a table with the lines +-- in the same relative order in which they were added by calling the +-- to_table method. It does this by keeping two lua tables that mirror each +-- other: +-- 1) index => item +-- 2) item => index +local Set = {} + +function Set:new(items) + local obj = {} + setmetatable(obj, self) + self.__index = self + + if type(items) == 'table' then + local tempset = Set:new() + tempset:union_table(items) + obj.tbl = tempset:raw_tbl() + obj.items = tempset:raw_items() + obj.nelem = tempset:size() + else + obj.tbl = {} + obj.items = {} + obj.nelem = 0 + end + + return obj +end + +-- adds the argument Set to this Set +function Set:union(other) + for e in other:iterator() do + self:add(e) + end +end + +-- adds the argument table to this Set +function Set:union_table(t) + for k, v in pairs(t) do + self:add(v) + end +end + +-- subtracts the argument Set from this Set +function Set:diff(other) + if other:size() > self:size() then + -- this set is smaller than the other set + for e in self:iterator() do + if other:contains(e) then + self:remove(e) + end + end + else + -- this set is larger than the other set + for e in other:iterator() do + if self.items[e] then + self:remove(e) + end + end + end +end + +function Set:add(it) + if not self:contains(it) then + local idx = #self.tbl + 1 + self.tbl[idx] = it + self.items[it] = idx + self.nelem = self.nelem + 1 + end +end + +function Set:remove(it) + if self:contains(it) then + local idx = self.items[it] + self.tbl[idx] = nil + self.items[it] = nil + self.nelem = self.nelem - 1 + end +end + +function Set:contains(it) + return self.items[it] or false +end + +function Set:size() + return self.nelem +end + +function Set:raw_tbl() + return self.tbl +end + +function Set:raw_items() + return self.items +end + +function Set:iterator() + return pairs(self.items) +end + +function Set:to_table() + -- there might be gaps in @tbl, so we have to be careful and sort first + local keys + do + local _accum_0 = { } + local _len_0 = 1 + for idx, _ in pairs(self.tbl) do + _accum_0[_len_0] = idx + _len_0 = _len_0 + 1 + end + keys = _accum_0 + end + table.sort(keys) + local copy + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #keys do + local idx = keys[_index_0] + _accum_0[_len_0] = self.tbl[idx] + _len_0 = _len_0 + 1 + end + copy = _accum_0 + end + return copy +end + +return Set diff --git a/test/unit/set.moon b/test/unit/set.moon deleted file mode 100644 index affbd5c29a..0000000000 --- a/test/unit/set.moon +++ /dev/null @@ -1,72 +0,0 @@ --- a set class for fast union/diff, can always return a table with the lines --- in the same relative order in which they were added by calling the --- to_table method. It does this by keeping two lua tables that mirror each --- other: --- 1) index => item --- 2) item => index -class Set - new: (items) => - if type(items) == 'table' - tempset = Set() - tempset\union_table(items) - @tbl = tempset\raw_tbl! - @items = tempset\raw_items! - @nelem = tempset\size! - else - @tbl = {} - @items = {} - @nelem = 0 - - -- adds the argument Set to this Set - union: (other) => - for e in other\iterator! - @add(e) - - -- adds the argument table to this Set - union_table: (t) => - for k,v in pairs(t) - @add(v) - - -- subtracts the argument Set from this Set - diff: (other) => - if other\size! > @size! - -- this set is smaller than the other set - for e in @iterator! - if other\contains(e) - @remove(e) - else - -- this set is larger than the other set - for e in other\iterator! - if @items[e] - @remove(e) - - add: (it) => - if not @contains(it) - idx = #@tbl + 1 - @tbl[idx] = it - @items[it] = idx - @nelem += 1 - - remove: (it) => - if @contains(it) - idx = @items[it] - @tbl[idx] = nil - @items[it] = nil - @nelem -= 1 - - contains: (it) => - @items[it] or false - - size: => @nelem - raw_tbl: => @tbl - raw_items: => @items - iterator: => pairs(@items) - - to_table: => - -- there might be gaps in @tbl, so we have to be careful and sort first - keys = [idx for idx, _ in pairs(@tbl)] - table.sort(keys) - copy = [@tbl[idx] for idx in *keys] - copy - -return Set diff --git a/test/unit/tempfile_spec.lua b/test/unit/tempfile_spec.lua index e558ff04c8..6484a98b8f 100644 --- a/test/unit/tempfile_spec.lua +++ b/test/unit/tempfile_spec.lua @@ -4,6 +4,8 @@ local helpers = require 'test.unit.helpers' local os = helpers.cimport './src/nvim/os/os.h' local tempfile = helpers.cimport './src/nvim/tempfile.h' +helpers.vim_init() + describe('tempfile related functions', function() after_each(function() tempfile.vim_deltempdir() diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 1935bff4eb..66d43ff7ca 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -43,14 +43,15 @@ if(CMAKE_GENERATOR MATCHES "Makefiles") set(MAKE_PRG "$(MAKE)") endif() +set(DEPS_C_COMPILER "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}") include(ExternalProject) set(LIBUV_URL https://github.com/joyent/libuv/archive/v0.11.28.tar.gz) set(LIBUV_MD5 1a849ba4fc571d531482ed74bc7aabc4) -set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-0.5.8/msgpack-0.5.8.tar.gz) -set(MSGPACK_MD5 ea0bee0939d2980c0df91f0e4843ccc4) +set(MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/ecf4b09acd29746829b6a02939db91dfdec635b4.tar.gz) +set(MSGPACK_MD5 3599eaf904b8ba0c36cea7ed80973364) set(LUAJIT_URL http://luajit.org/download/LuaJIT-2.0.3.tar.gz) set(LUAJIT_MD5 f14e9104be513913810cd59c8c658dc0) @@ -73,7 +74,7 @@ if(USE_BUNDLED_LIBUV) -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake CONFIGURE_COMMAND sh ${DEPS_BUILD_DIR}/src/libuv/autogen.sh && ${DEPS_BUILD_DIR}/src/libuv/configure --with-pic --disable-shared - --prefix=${DEPS_INSTALL_DIR} CC=${CMAKE_C_COMPILER} + --prefix=${DEPS_INSTALL_DIR} CC=${DEPS_C_COMPILER} INSTALL_COMMAND ${MAKE_PRG} install) list(APPEND THIRD_PARTY_DEPS libuv) endif() @@ -91,9 +92,17 @@ if(USE_BUNDLED_MSGPACK) -DEXPECTED_MD5=${MSGPACK_MD5} -DTARGET=msgpack -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/msgpack/configure --disable-shared - --with-pic --prefix=${DEPS_INSTALL_DIR} CC=${CMAKE_C_COMPILER} - INSTALL_COMMAND ${MAKE_PRG} install) + CONFIGURE_COMMAND cmake ${DEPS_BUILD_DIR}/src/msgpack + -DMSGPACK_ENABLE_CXX=OFF + -DMSGPACK_BUILD_TESTS=OFF + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC" + BUILD_COMMAND ${MAKE_PRG} + INSTALL_COMMAND ${CMAKE_COMMAND} + -DMAKE_PRG=${MAKE_PRG} + -DREMOVE_FILE_GLOB=${DEPS_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}msgpack*${CMAKE_SHARED_LIBRARY_SUFFIX}* + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/InstallMsgpack.cmake) list(APPEND THIRD_PARTY_DEPS msgpack) endif() @@ -113,7 +122,7 @@ if(USE_BUNDLED_LUAJIT) CONFIGURE_COMMAND "" BUILD_IN_SOURCE 1 BUILD_COMMAND "" - INSTALL_COMMAND ${MAKE_PRG} CC=${CMAKE_C_COMPILER} + INSTALL_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} PREFIX=${DEPS_INSTALL_DIR} CFLAGS=-fPIC BUILDMODE=static @@ -150,39 +159,33 @@ if(USE_BUNDLED_LUAROCKS) add_dependencies(luarocks luajit) endif() - add_custom_command(OUTPUT ${DEPS_BIN_DIR}/moon ${DEPS_BIN_DIR}/moonc - COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install moonscript - DEPENDS luarocks) - add_custom_target(moonscript - DEPENDS ${DEPS_BIN_DIR}/moon ${DEPS_BIN_DIR}/moonc) - - # Busted doesn't depend on luarocks, but luarocks is unhappy to have two - # instances running in parallel. So we depend on moonscript to force it - # to be serialized. add_custom_command(OUTPUT ${DEPS_BIN_DIR}/busted - COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install busted 1.10.0 - DEPENDS moonscript) + COMMAND ${DEPS_BIN_DIR}/luarocks + ARGS build busted 2.0.rc3 CC=${DEPS_C_COMPILER} LD=${DEPS_C_COMPILER} + DEPENDS luarocks) add_custom_target(busted DEPENDS ${DEPS_BIN_DIR}/busted) - # Like busted dependency on moonscript, we add a dependency on busted - # to serialize the install - add_custom_command(OUTPUT ${DEPS_LIB_DIR}/luarocks/rocks/lua-cmsgpack - COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install lua-cmsgpack + # lua-messagepack doesn't depend on busted, but luarocks is unhappy to have + # two instances running in parallel. So we depend on busted to force it to + # be serialized. + add_custom_command(OUTPUT ${DEPS_LIB_DIR}/luarocks/rocks/lua-messagepack + COMMAND ${DEPS_BIN_DIR}/luarocks + ARGS build lua-messagepack CC=${DEPS_C_COMPILER} LD=${DEPS_C_COMPILER} DEPENDS busted) - add_custom_target(lua-cmsgpack - DEPENDS ${DEPS_LIB_DIR}/luarocks/rocks/lua-cmsgpack) + add_custom_target(lua-messagepack + DEPENDS ${DEPS_LIB_DIR}/luarocks/rocks/lua-messagepack) - # lpeg is a moonscript dependency, but since it is also required for - # normal compilation(even without unit testing) we better add it explicitly. - # Like before, depend on cmsgpack to ensure serialization of install commands + # Like before, depend on lua-messagepack to ensure serialization of install + # commands add_custom_command(OUTPUT ${DEPS_LIB_DIR}/luarocks/rocks/lpeg - COMMAND ${DEPS_BIN_DIR}/luarocks ARGS install lpeg - DEPENDS lua-cmsgpack) + COMMAND ${DEPS_BIN_DIR}/luarocks + ARGS build lpeg CC=${DEPS_C_COMPILER} LD=${DEPS_C_COMPILER} + DEPENDS lua-messagepack) add_custom_target(lpeg DEPENDS ${DEPS_LIB_DIR}/luarocks/rocks/lpeg) - list(APPEND THIRD_PARTY_DEPS moonscript busted lua-cmsgpack lpeg) + list(APPEND THIRD_PARTY_DEPS busted lua-messagepack lpeg) endif() add_custom_target(third-party ALL diff --git a/third-party/cmake/InstallMsgpack.cmake b/third-party/cmake/InstallMsgpack.cmake new file mode 100644 index 0000000000..d5e5d7e816 --- /dev/null +++ b/third-party/cmake/InstallMsgpack.cmake @@ -0,0 +1,12 @@ +execute_process( + COMMAND ${MAKE_PRG} install + RESULT_VARIABLE res) + +if(NOT res EQUAL 0) + message(FATAL_ERROR "Installing msgpack failed.") +endif() + +file(GLOB FILES_TO_REMOVE ${REMOVE_FILE_GLOB}) +if(FILES_TO_REMOVE) + file(REMOVE ${FILES_TO_REMOVE}) +endif() |