diff options
91 files changed, 3507 insertions, 1214 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..b08a27f2a2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +charset = utf_8 + +[Makefile] +indent_style = tab +tab_width = 4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 527a085d3e..3dbe98ab67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 2.8.7) -project(NEOVIM) +project(nvim) # Point CMake at any custom modules we may ship list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") @@ -206,6 +206,12 @@ if(MSVC) else() add_definitions(-Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-prototypes -std=gnu99) + + # On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang + # 3.4.1 used there. + if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + add_definitions(-Wno-c11-extensions) + endif() endif() if(MINGW) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..4abcc4eae1 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,9 @@ +- Neovim version: +- Operating system: +- Terminal emulator: + +### Actual behaviour + +### Expected behaviour + +### Steps to reproduce @@ -2552,7 +2552,7 @@ def CheckBraces(filename, clean_lines, linenum, error): # If should always have a brace for blockstart in ('if', 'while', 'for'): - if Match(r'\s*{0}[^{{]*$'.format(blockstart), line): + if Match(r'\s*{0}(?!\w)[^{{]*$'.format(blockstart), line): pos = line.find(blockstart) pos = line.find('(', pos) if pos > 0: diff --git a/cmake/RunLint.cmake b/cmake/RunLint.cmake index 42ef7a86ad..306e938232 100644 --- a/cmake/RunLint.cmake +++ b/cmake/RunLint.cmake @@ -2,7 +2,11 @@ get_filename_component(LINT_DIR ${LINT_DIR} ABSOLUTE) get_filename_component(LINT_PREFIX ${LINT_DIR} PATH) set(LINT_SUPPRESS_FILE "${LINT_PREFIX}/errors.json") -file(GLOB_RECURSE LINT_FILES ${LINT_DIR}/*.c ${LINT_DIR}/*.h) +if(DEFINED ENV{LINT_FILE}) + file(GLOB_RECURSE LINT_FILES "$ENV{LINT_FILE}") +else() + file(GLOB_RECURSE LINT_FILES ${LINT_DIR}/*.c ${LINT_DIR}/*.h) +endif() set(LINT_ARGS) diff --git a/config/config.h.in b/config/config.h.in index 27705f8b38..d13e7de2e6 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -12,6 +12,9 @@ #define ARCH_32 #endif +#define PROJECT_NAME "@PROJECT_NAME@" +#define LOCALE_INSTALL_DIR "@CMAKE_INSTALL_FULL_LOCALEDIR@" + #cmakedefine HAVE__NSGETENVIRON #cmakedefine HAVE_FD_CLOEXEC #cmakedefine HAVE_FSEEKO diff --git a/runtime/autoload/msgpack.vim b/runtime/autoload/msgpack.vim index e6022922fe..2bb7ec5b02 100644 --- a/runtime/autoload/msgpack.vim +++ b/runtime/autoload/msgpack.vim @@ -395,7 +395,8 @@ endfunction "" " Dump floating-point value. function s:msgpack_dump_float(v) abort - return string(type(a:v) == type({}) ? a:v._VAL : a:v) + return substitute(string(type(a:v) == type({}) ? a:v._VAL : a:v), + \'\V\^\(-\)\?str2float(''\(inf\|nan\)'')\$', '\1\2', '') endfunction "" diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt new file mode 100644 index 0000000000..ca79465e0d --- /dev/null +++ b/runtime/doc/api.txt @@ -0,0 +1,99 @@ +*api.txt* For Nvim. {Nvim} + + + NVIM REFERENCE MANUAL by Thiago de Arruda + +The C API of Nvim *nvim-api* + +1. Introduction |nvim-api-intro| +2. API Types |nvim-api-types| +3. API metadata |nvim-api-metadata| +4. Buffer highlighting |nvim-api-highlights| + +============================================================================== +1. Introduction *nvim-api-intro* + +Nvim defines a C API as the primary way for external code to interact with +the NVim core. In the present version of Nvim the API is primarily used by +external processes to interact with Nvim using the msgpack-rpc protocol, see +|msgpack-rpc|. The API will also be used from vimscript to access new Nvim core +features, but this is not implemented yet. Later on, Nvim might be embeddable +in C applications as libnvim, and the application will then control the +embedded instance by calling the C API directly. + +============================================================================== +2. API Types *nvim-api-types* + +Nvim's C API uses custom types for all functions. Some are just typedefs +around C99 standard types, and some are Nvim defined data structures. + +Boolean -> bool +Integer (signed 64-bit integer) -> int64_t +Float (IEEE 754 double precision) -> double +String -> {char* data, size_t size} struct + +Additionally, the following data structures are defined: + +Array +Dictionary +Object + +The following handle types are defined as integer typedefs, but are +discriminated as separate types in an Object: + +Buffer -> enum value kObjectTypeBuffer +Window -> enum value kObjectTypeWindow +Tabpage -> enum value kObjectTypeTabpage + +============================================================================== +3. API metadata *nvim-api-metadata* + +Nvim exposes metadata about the API as a Dictionary with the following keys: + +functions calling signature of the API functions +types The custom handle types defined by Nvim +error_types The possible kinds of errors an API function can exit with. + +This metadata is mostly useful for external programs accessing the api over +msgpack-api, see |msgpack-rpc-api|. + +============================================================================== +4. Buffer highlighting *nvim-api-highlights* + +Nvim allows plugins to add position-based highlights to buffers. This is +similar to |matchaddpos()| but with some key differences. The added highlights +are associated with a buffer and adapts to line insertions and deletions, +similar to signs. It is also possible to manage a set of highlights as a group +and delete or replace all at once. + +The intended use case are linter or semantic highlighter plugins that monitor +a buffer for changes, and in the background compute highlights to the buffer. +Another use case are plugins that show output in an append-only buffer, and +want to add highlights to the outputs. Highlight data cannot be preserved +on writing and loading a buffer to file, nor in undo/redo cycles. + +Highlights are registered using the |buffer_add_highlight| function, see the +generated API documentation for details. If an external highlighter plugin is +adding a large number of highlights in a batch, performance can be improved by +calling |buffer_add_highlight| as an asynchronous notification, after first +(synchronously) reqesting a source id. Here is an example using wrapper +functions in the python client: +> + src = vim.new_highlight_source() + + buf = vim.current.buffer + for i in range(5): + buf.add_highlight("String",i,0,-1,src_id=src) + + # some time later + + buf.clear_highlight(src) +< +If the highlights don't need to be deleted or updated, just pass -1 as +src_id (this is the default in python). |buffer_clear_highlight| can be used +to clear highligts from a specific source, in a specific line range or the +entire buffer by passing in the line range 0, -1 (the later is the default +in python as used above). + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index e17281821c..ec5818e16f 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -308,6 +308,8 @@ Name triggered by ~ |InsertCharPre| when a character was typed in Insert mode, before inserting it +|TextYankPost| when some text is yanked or deleted + |TextChanged| after a change was made to the text in Normal mode |TextChangedI| after a change was made to the text in Insert mode @@ -722,6 +724,18 @@ InsertCharPre When a character is typed in Insert mode, It is not allowed to change the text |textlock|. The event is not triggered when 'paste' is set. + *TextYankPost* +TextYankPost Just after a |yank| or |deleting| command, but not + if the black hole register |quote_| is used nor + for |setreg()|. Pattern must be * because its + meaning may change in the future. + Sets these |v:event| keys: + operator + regcontents + regname + regtype + Recursion is ignored. + It is not allowed to change the text |textlock|. *InsertEnter* InsertEnter Just before starting Insert mode. Also for Replace mode and Virtual Replace mode. The diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 45980f5d94..0b2880ef03 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1386,6 +1386,22 @@ v:errors Errors found by assert functions, such as |assert_true()|. < If v:errors is set to anything but a list it is made an empty list by the assert function. + *v:event* *event-variable* +v:event Dictionary of event data for the current |autocommand|. The + available keys differ per event type and are specified at the + documentation for each |event|. The possible keys are: + operator The operation performed. Unlike + |v:operator|, it is set also for an Ex + mode command. For instance, |:yank| is + translated to "|y|". + regcontents Text stored in the register as a + |readfile()|-style list of lines. + regname Requested register (e.g "x" for "xyy) + or the empty string for an unnamed + operation. + regtype Type of register as returned by + |getregtype()|. + *v:exception* *exception-variable* v:exception The value of the exception most recently caught and not finished. See also |v:throwpoint| and |throw-variables|. @@ -1786,8 +1802,10 @@ cursor( {lnum}, {col} [, {off}]) cursor( {list}) Number move cursor to position in {list} deepcopy( {expr} [, {noref}]) any make a full copy of {expr} delete( {fname}) Number delete file {fname} -dictwatcheradd({dict}, {pattern}, {callback}) Start watching a dictionary -dictwatcherdel({dict}, {pattern}, {callback}) Stop watching a dictionary +dictwatcheradd( {dict}, {pattern}, {callback}) + Start watching a dictionary +dictwatcherdel( {dict}, {pattern}, {callback}) + Stop watching a dictionary did_filetype() Number TRUE if FileType autocommand event used diff_filler( {lnum}) Number diff filler lines about {lnum} diff_hlID( {lnum}, {col}) Number diff highlighting at {lnum}/{col} @@ -1807,7 +1825,7 @@ feedkeys( {string} [, {mode}]) Number add key sequence to typeahead buffer filereadable( {file}) Number TRUE if {file} is a readable file filewritable( {file}) Number TRUE if {file} is a writable file filter( {expr}, {string}) List/Dict remove items from {expr} where - {string} is 0 + {string} is 0 finddir( {name}[, {path}[, {count}]]) String find directory {name} in {path} findfile( {name}[, {path}[, {count}]]) @@ -1889,20 +1907,22 @@ inputdialog( {p} [, {t} [, {c}]]) String like input() but in a GUI dialog inputlist( {textlist}) Number let the user pick from a choice list inputrestore() Number restore typeahead inputsave() Number save and clear typeahead -inputsecret( {prompt} [, {text}]) String like input() but hiding the text -insert( {list}, {item} [, {idx}]) List insert {item} in {list} [before {idx}] +inputsecret( {prompt} [, {text}]) + String like input() but hiding the text +insert( {list}, {item} [, {idx}]) + List insert {item} in {list} [before {idx}] 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} -jobclose({job}[, {stream}]) Number Closes a job stream(s) -jobpid({job}) Number Returns pid of a job. -jobresize({job}, {width}, {height}) +jobclose( {job}[, {stream}]) Number Closes a job stream(s) +jobpid( {job}) Number Returns pid of a job. +jobresize( {job}, {width}, {height}) Number Resize {job}'s pseudo terminal window -jobsend({job}, {data}) Number Writes {data} to {job}'s stdin -jobstart({cmd}[, {opts}]) Number Spawns {cmd} as a job -jobstop({job}) Number Stops a job -jobwait({ids}[, {timeout}]) Number Wait for a set of jobs +jobsend( {job}, {data}) Number Writes {data} to {job}'s stdin +jobstart( {cmd}[, {opts}]) Number Spawns {cmd} as a job +jobstop( {job}) Number Stops a job +jobwait( {ids}[, {timeout}]) Number Wait for a set of jobs join( {list} [, {sep}]) String join {list} items into one String keys( {dict}) List keys in {dict} len( {expr}) Number the length of {expr} @@ -1972,12 +1992,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}...]) +rpcnotify( {channel}, {event}[, {args}...]) Sends a |msgpack-rpc| notification to {channel} -rpcrequest({channel}, {method}[, {args}...]) +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} +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 @@ -1999,11 +2019,12 @@ setbufvar( {expr}, {varname}, {val}) set {varname} in buffer {expr} to {val} setcharsearch( {dict}) Dict set character search from {dict} setcmdpos( {pos}) Number set cursor position in command-line setline( {lnum}, {line}) Number set line {lnum} to {line} -setloclist( {nr}, {list}[, {action}]) +setloclist( {nr}, {list}[, {action}[, {title}]]) Number modify location list using {list} setmatches( {list}) Number restore a list of matches setpos( {expr}, {list}) Number set the {expr} position to {list} -setqflist( {list}[, {action}]) Number modify quickfix list using {list} +setqflist( {list}[, {action}[, {title}]] + Number modify quickfix list using {list} setreg( {n}, {v}[, {opt}]) Number set register to value and type settabvar( {nr}, {varname}, {val}) set {varname} in tab page {nr} to {val} settabwinvar( {tabnr}, {winnr}, {varname}, {val}) set {varname} in window @@ -5732,11 +5753,13 @@ setline({lnum}, {text}) *setline()* :endfor < Note: The '[ and '] marks are not set. -setloclist({nr}, {list} [, {action}]) *setloclist()* +setloclist({nr}, {list} [, {action}[, {title}]]) *setloclist()* Create or replace or add to the location list for window {nr}. When {nr} is zero the current window is used. For a location list window, the displayed location list is modified. For an - invalid window number {nr}, -1 is returned. + invalid window number {nr}, -1 is returned. If {title} is + given, it will be used to set |w:quickfix_title| after opening + the location window. Otherwise, same as |setqflist()|. Also see |location-list|. @@ -5793,7 +5816,7 @@ setpos({expr}, {list}) |winrestview()|. -setqflist({list} [, {action}]) *setqflist()* +setqflist({list} [, {action}[, {title}]]) *setqflist()* Create or replace or add to the quickfix list using the items in {list}. Each item in {list} is a dictionary. Non-dictionary items in {list} are ignored. Each dictionary @@ -5832,6 +5855,9 @@ setqflist({list} [, {action}]) *setqflist()* with the items from {list}. If {action} is not present or is set to ' ', then a new list is created. + If {title} is given, it will be used to set |w:quickfix_title| + after opening the quickfix window. + Returns zero for success, -1 for failure. This function can be used to create a quickfix list @@ -6232,12 +6258,22 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number, {expr} type result ~ String 'string' Number 123 - Float 123.123456 or 1.123456e8 - Funcref function('name') + Float 123.123456 or 1.123456e8 or + `str2float('inf')` + Funcref `function('name')` List [item, item] Dictionary {key: value, key: value} Note that in String values the ' character is doubled. Also see |strtrans()|. + Note 2: Output format is mostly compatible with YAML, except + for infinite and NaN floating-point values representations + which use |str2float()|. Strings are also dumped literally, + only single quote is escaped, which does not allow using YAML + for parsing back binary strings (including text when + 'encoding' is not UTF-8). |eval()| should always work for + strings and floats though and this is the only official + method, use |msgpackdump()| or |json_encode()| if you need to + share data with other application. *strlen()* strlen({expr}) The result is a Number, which is the length of the String diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index fdf106a7bb..cbe017e051 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -452,7 +452,7 @@ notation meaning equivalent decimal value(s) ~ <C-...> control-key *control* *ctrl* *<C-* <M-...> alt-key or meta-key *meta* *alt* *<M-* <A-...> same as <M-...> *<A-* -<D-...> command-key (Macintosh only) *<D-* +<D-...> command-key or "super" key *<D-* <t_xx> key with "xx" entry in termcap ----------------------------------------------------------------------- diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index d732e7f818..bafb9dfc2c 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -7,7 +7,7 @@ The Msgpack-RPC Interface to Nvim *msgpack-rpc* 1. Introduction |msgpack-rpc-intro| -2. API |msgpack-rpc-api| +2. API mapping |msgpack-rpc-api| 3. Connecting |msgpack-rpc-connecting| 4. Clients |msgpack-rpc-clients| 5. Types |msgpack-rpc-types| @@ -36,13 +36,13 @@ Nvim's msgpack-rpc interface is like a more powerful version of Vim's `clientserver` feature. ============================================================================== -2. API *msgpack-rpc-api* +2. API mapping *msgpack-rpc-api* -The 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, which matches msgpack-rpc method names with -non-static API functions, converting/validating arguments and return values -back to msgpack. +The Nvim C API, see |nvim-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, which matches 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. The wrappers can be automatically generated by reading @@ -63,7 +63,7 @@ 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* @@ -162,8 +162,8 @@ https://github.com/msgpack-rpc/msgpack-rpc-ruby/blob/master/lib/msgpack/rpc/tran ============================================================================== 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: +Nvim's C API uses custom types for all functions, se |nvim-api-types|. +For the purpose of mapping to msgpack, he 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) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b5bda8ac05..44696d10bb 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1360,7 +1360,7 @@ A jump table for the options with a short description can be found at |Q_op|. option, yank and delete operations (but not put) will additionally copy the text into register '*'. See |nvim-clipboard|. -< + *clipboard-autoselect* autoselect Works like the 'a' flag in 'guioptions': If present, then whenever Visual mode is started, or the Visual diff --git a/runtime/doc/tabpage.txt b/runtime/doc/tabpage.txt index 59c4a28ff2..70e6953211 100644 --- a/runtime/doc/tabpage.txt +++ b/runtime/doc/tabpage.txt @@ -273,8 +273,8 @@ window on the same buffer and then edit another buffer. Thus ":tabnew" triggers: WinLeave leave current window TabLeave leave current tab page - TabEnter enter new tab page WinEnter enter window in new tab page + TabEnter enter new tab page BufLeave leave current buffer BufEnter enter new empty buffer @@ -282,8 +282,8 @@ When switching to another tab page the order is: BufLeave WinLeave TabLeave - TabEnter WinEnter + TabEnter BufEnter When entering a new tab page (|:tabnew|), TabNew is triggered before TabEnter diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index d3768409f5..8722fced26 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -99,6 +99,8 @@ are always available and may be used simultaneously in separate plugins. The Same thing applies to |string()| (though it uses construct like "{E724@level}"), but this is not reliable because |string()| continues to error out. +4. Stringifyed infinite and NaN values now use |str2float()| and can be evaled + back. Viminfo text files were replaced with binary (messagepack) ShaDa files. Additional differences: diff --git a/scripts/gendeclarations.lua b/scripts/gendeclarations.lua index 637f4cdffa..4e74e4e301 100755 --- a/scripts/gendeclarations.lua +++ b/scripts/gendeclarations.lua @@ -239,24 +239,23 @@ end non_static = non_static .. footer static = static .. footer +local F +F = io.open(static_fname, 'w') +F:write(static) +F:close() --- Before generating the headers, check if the current file (if exists) is --- different from the new one. If they are the same, we won't touch the --- current version to avoid triggering an unnecessary rebuilds of modules +-- Before generating the non-static headers, check if the current file(if +-- exists) is different from the new one. If they are the same, we won't touch +-- the current version to avoid triggering an unnecessary rebuilds of modules -- that depend on this one -local update_changed = function (fname, contents) - local F = io.open(fname, 'r') - if F ~= nil then - if F:read('*a') == contents then - return - end - io.close(F) +F = io.open(non_static_fname, 'r') +if F ~= nil then + if F:read('*a') == non_static then + os.exit(0) end - - F = io.open(fname, 'w') - F:write(contents) - F:close() + io.close(F) end -update_changed(static_fname, static) -update_changed(non_static_fname, non_static) +F = io.open(non_static_fname, 'w') +F:write(non_static) +F:close() diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index bdd3d6209b..7612a2ada0 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -5,9 +5,12 @@ set -u set -o pipefail readonly NEOVIM_SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -readonly VIM_SOURCE_DIR_DEFAULT=${NEOVIM_SOURCE_DIR}/.vim-src +readonly VIM_SOURCE_DIR_DEFAULT="${NEOVIM_SOURCE_DIR}/.vim-src" readonly VIM_SOURCE_DIR="${VIM_SOURCE_DIR:-${VIM_SOURCE_DIR_DEFAULT}}" readonly BASENAME="$(basename "${0}")" +readonly BRANCH_PREFIX="vim-" + +CREATED_FILES=() usage() { echo "Helper script for porting Vim patches. For more information, see" @@ -21,6 +24,7 @@ usage() { echo " -p {vim-revision} Download and apply the Vim patch vim-revision." echo " vim-revision can be a version number of the " echo " format '7.4.xxx' or a Git commit hash." + echo " -s Submit a vim-patch pull request to Neovim." echo " -r {pr-number} Review a vim-patch pull request to Neovim." echo echo "Set VIM_SOURCE_DIR to change where Vim's sources are stored." @@ -35,6 +39,27 @@ check_executable() { fi } +clean_files() { + if [[ ${#CREATED_FILES[@]} -eq 0 ]]; then + return + fi + + echo + echo "Created files:" + local file + for file in ${CREATED_FILES[@]}; do + echo " • ${file}" + done + + read -p "Delete these files (Y/n)? " -n 1 -r reply + echo + if [[ "${reply}" =~ ^[Yy]$ ]]; then + rm -- ${CREATED_FILES[@]} + else + echo "You can use 'git clean' to remove these files when you're done." + fi +} + get_vim_sources() { check_executable git @@ -66,22 +91,26 @@ assign_commit_details() { # Interpret parameter as version number (tag). vim_version="${1}" vim_tag="v${1}" - vim_commit=$( cd "${VIM_SOURCE_DIR}" \ - && git log -1 --format="%H" ${vim_tag} ) + vim_commit=$(cd "${VIM_SOURCE_DIR}" \ + && git log -1 --format="%H" ${vim_tag}) local strip_commit_line=true else # Interpret parameter as commit hash. vim_version="${1:0:7}" - vim_commit="${1}" + vim_commit=$(cd "${VIM_SOURCE_DIR}" \ + && git log -1 --format="%H" ${vim_version}) local strip_commit_line=false fi vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}" - vim_message="$(git log -1 --pretty='format:%B' "${vim_commit}")" + vim_message="$(cd "${VIM_SOURCE_DIR}" \ + && git log -1 --pretty='format:%B' "${vim_commit}" \ + | sed -e 's/\(#[0-9]*\)/vim\/vim\1/g')" if [[ ${strip_commit_line} == "true" ]]; then # Remove first line of commit message. vim_message="$(echo "${vim_message}" | sed -e '1d')" fi + patch_file="vim-${vim_version}.patch" } get_vim_patch() { @@ -96,66 +125,108 @@ get_vim_patch() { echo echo "✔ Found Vim revision '${vim_commit}'." - # Collect patch details and store into variables. - vim_full="$(git show -1 --pretty=medium "${vim_commit}")" # Patch surgery: preprocess the patch. # - transform src/ paths to src/nvim/ - vim_diff="$(git show -1 "${vim_commit}" \ + local vim_full="$(git show -1 --pretty=medium "${vim_commit}" \ | LC_ALL=C sed -e 's/\( [ab]\/src\)/\1\/nvim/g')" - neovim_message="$(commit_message)" - neovim_pr=" -\`\`\` -${vim_message} -\`\`\` - -${vim_commit_url} - -Original patch: - -\`\`\`diff -${vim_diff} -\`\`\`" - neovim_branch="vim-${vim_version}" + local neovim_branch="${BRANCH_PREFIX}${vim_version}" - echo - echo "Creating Git branch." cd "${NEOVIM_SOURCE_DIR}" - output="$(git checkout -b "${neovim_branch}" 2>&1)" && - echo "✔ ${output}" || - (echo "✘ ${output}"; false) + local checked_out_branch="$(git rev-parse --abbrev-ref HEAD)" + if [[ "${checked_out_branch}" == ${BRANCH_PREFIX}* ]]; then + echo "✔ Current branch '${checked_out_branch}' seems to be a vim-patch" + echo " branch; not creating a new branch." + else + echo + echo "Fetching 'upstream/master'." + output="$(git fetch upstream master 2>&1)" && + echo "✔ ${output}" || + (echo "✘ ${output}"; false) + + echo + echo "Creating new branch '${neovim_branch}' based on 'upstream/master'." + cd "${NEOVIM_SOURCE_DIR}" + output="$(git checkout -b "${neovim_branch}" upstream/master 2>&1)" && + echo "✔ ${output}" || + (echo "✘ ${output}"; false) + fi echo echo "Creating empty commit with correct commit message." - output="$(git commit --allow-empty --file 2>&1 - <<< "${neovim_message}")" && + output="$(commit_message | git commit --allow-empty --file 2>&1 -)" && echo "✔ ${output}" || (echo "✘ ${output}"; false) echo echo "Creating files." - echo "${vim_diff}" > "${NEOVIM_SOURCE_DIR}/${neovim_branch}.diff" - echo "✔ Saved diff to '${NEOVIM_SOURCE_DIR}/${neovim_branch}.diff'." - echo "${vim_full}" > "${NEOVIM_SOURCE_DIR}/${neovim_branch}.patch" - echo "✔ Saved full commit details to '${NEOVIM_SOURCE_DIR}/${neovim_branch}.patch'." - echo "${neovim_pr}" > "${NEOVIM_SOURCE_DIR}/${neovim_branch}.pr" - echo "✔ Saved suggested PR description to '${NEOVIM_SOURCE_DIR}/${neovim_branch}.pr'." - echo "You can use 'git clean' to remove these files when you're done." + echo "${vim_full}" > "${NEOVIM_SOURCE_DIR}/${patch_file}" + echo "✔ Saved full commit details to '${NEOVIM_SOURCE_DIR}/${patch_file}'." echo echo "Instructions:" echo echo " Proceed to port the patch." - echo " You might want to try 'patch -p1 < ${neovim_branch}.diff' first." + echo " You might want to try 'patch -p1 < ${patch_file}' first." + echo + echo " If the patch contains a new test, consider porting it to Lua." + echo " You might want to try 'scripts/legacy2luatest.pl'." echo echo " Stage your changes ('git add ...') and use 'git commit --amend' to commit." echo - echo " Push your changes with 'git push origin ${neovim_branch}' and create a" - echo " pull request called '[RFC] vim-patch:${vim_version}'. You might want " - echo " to use the text in '${neovim_branch}.pr' as the description of this pull request." + echo " To port additional patches related to ${vim_version} and add them to the current" + echo " branch, call '${BASENAME} -p' again. Please use this only if it wouldn't make" + echo " sense to send in each patch individually, as it will increase the size of the" + echo " pull request and make it harder to review." + echo + echo " When you are finished, use '${BASENAME} -s' to submit a pull request." echo echo " See https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-vim" echo " for more information." } +submit_pr() { + check_executable git + check_executable hub + + cd "${NEOVIM_SOURCE_DIR}" + local checked_out_branch="$(git rev-parse --abbrev-ref HEAD)" + if [[ "${checked_out_branch}" != ${BRANCH_PREFIX}* ]]; then + echo "✘ Current branch '${checked_out_branch}' doesn't seem to be a vim-patch branch." + exit 1 + fi + + local pr_body="$(git log --reverse --format='#### %s%n%n%b%n' upstream/master..HEAD)" + local patches=("$(git log --reverse --format='%s' upstream/master..HEAD)") + patches=(${patches[@]//vim-patch:}) # Remove 'vim-patch:' prefix for each item in array. + local pr_title="${patches[@]}" # Create space-separated string from array. + pr_title="${pr_title// /,}" # Replace spaces with commas. + + local pr_message="$(printf '[RFC] vim-patch:%s\n\n%s\n' "${pr_title#,}" "${pr_body}")" + + echo "Pushing to 'origin/${checked_out_branch}'." + output="$(git push origin "${checked_out_branch}" 2>&1)" && + echo "✔ ${output}" || + (echo "✘ ${output}"; git reset --soft HEAD^1; false) + + echo + echo "Creating pull request." + output="$(hub pull-request -F - 2>&1 <<< "${pr_message}")" && + echo "✔ ${output}" || + (echo "✘ ${output}"; false) + + echo + echo "Cleaning up files." + local patch_file + for patch_file in ${patches[@]}; do + patch_file="vim-${patch_file}.patch" + if [[ ! -f "${NEOVIM_SOURCE_DIR}/${patch_file}" ]]; then + continue + fi + rm -- "${NEOVIM_SOURCE_DIR}/${patch_file}" + echo "✔ Removed '${NEOVIM_SOURCE_DIR}/${patch_file}'." + done +} + list_vim_patches() { get_vim_sources @@ -189,7 +260,8 @@ list_vim_patches() { echo "Instructions:" echo echo " To port one of the above patches to Neovim, execute" - echo " this script with the patch revision as argument." + echo " this script with the patch revision as argument and" + echo " follow the instructions." echo echo " Examples: '${BASENAME} -p 7.4.487'" echo " '${BASENAME} -p 1e8ebf870720e7b671f98f22d653009826304c4f'" @@ -198,32 +270,28 @@ list_vim_patches() { echo " Out-of-order patches increase the possibility of bugs." } -review_pr() { - check_executable curl - check_executable nvim - - get_vim_sources - - local pr="${1}" - echo - echo "Downloading data for pull request #${pr}." +review_commit() { + local neovim_commit_url="${1}" + local neovim_patch_url="${neovim_commit_url}.patch" local git_patch_prefix='Subject: \[PATCH\] ' - local neovim_patch="$(curl -Ssf "https://patch-diff.githubusercontent.com/raw/neovim/neovim/pull/${pr}.patch")" - echo "${neovim_patch}" > a + local neovim_patch="$(curl -Ssf "${neovim_patch_url}")" local vim_version="$(head -n 4 <<< "${neovim_patch}" | sed -n "s/${git_patch_prefix}vim-patch:\([a-z0-9.]*\)$/\1/p")" + echo if [[ -n "${vim_version}" ]]; then echo "✔ Detected Vim patch '${vim_version}'." else echo "✘ Could not detect the Vim patch number." - echo " This script assumes that the PR contains a single commit" - echo " with 'vim-patch:XXX' as its title." + echo " This script assumes that the PR contains only commits" + echo " with 'vim-patch:XXX' in their title." exit 1 fi assign_commit_details "${vim_version}" + local vim_patch_url="${vim_commit_url}.patch" + local expected_commit_message="$(commit_message)" local message_length="$(wc -l <<< "${expected_commit_message}")" local commit_message="$(tail -n +4 <<< "${neovim_patch}" | head -n "${message_length}")" @@ -235,26 +303,57 @@ review_pr() { echo "${expected_commit_message}" echo " Actual:" echo "${commit_message#${git_patch_prefix}}" - exit 1 fi - local base_name="vim-${vim_version}" echo echo "Creating files." - curl -Ssfo "${NEOVIM_SOURCE_DIR}/n${base_name}.diff" "https://patch-diff.githubusercontent.com/raw/neovim/neovim/pull/${pr}.diff" - echo "✔ Saved pull request diff to '${NEOVIM_SOURCE_DIR}/n${base_name}.diff'." - echo "${neovim_patch}" > "${NEOVIM_SOURCE_DIR}/n${base_name}.patch" - echo "✔ Saved full pull request commit details to '${NEOVIM_SOURCE_DIR}/n${base_name}.patch'." - git show "${vim_commit}" > "${NEOVIM_SOURCE_DIR}/${base_name}.diff" - echo "✔ Saved Vim diff to '${NEOVIM_SOURCE_DIR}/${base_name}.diff'." - echo "You can use 'git clean' to remove these files when you're done." + echo "${neovim_patch}" > "${NEOVIM_SOURCE_DIR}/n${patch_file}" + echo "✔ Saved pull request diff to '${NEOVIM_SOURCE_DIR}/n${patch_file}'." + CREATED_FILES+=("${NEOVIM_SOURCE_DIR}/n${patch_file}") + + curl -Ssfo "${NEOVIM_SOURCE_DIR}/${patch_file}" "${vim_patch_url}" + echo "✔ Saved Vim diff to '${NEOVIM_SOURCE_DIR}/${patch_file}'." + CREATED_FILES+=("${NEOVIM_SOURCE_DIR}/${patch_file}") echo echo "Launching nvim." - exec nvim -O "${NEOVIM_SOURCE_DIR}/${base_name}.diff" "${NEOVIM_SOURCE_DIR}/n${base_name}.diff" + nvim -c "cd ${NEOVIM_SOURCE_DIR}" \ + -O "${NEOVIM_SOURCE_DIR}/${patch_file}" "${NEOVIM_SOURCE_DIR}/n${patch_file}" } -while getopts "hlp:r:" opt; do +review_pr() { + check_executable curl + check_executable nvim + check_executable jq + + get_vim_sources + + local pr="${1}" + echo + echo "Downloading data for pull request #${pr}." + + local pr_commit_urls=($(curl -Ssf "https://api.github.com/repos/neovim/neovim/pulls/${pr}/commits" \ + | jq -r '.[].html_url')) + + echo "Found ${#pr_commit_urls[@]} commit(s)." + + local pr_commit_url + local reply + for pr_commit_url in ${pr_commit_urls[@]}; do + review_commit "${pr_commit_url}" + if [[ "${pr_commit_url}" != "${pr_commit_urls[-1]}" ]]; then + read -p "Continue with next commit (Y/n)? " -n 1 -r reply + echo + if [[ ! "${reply}" =~ ^[Yy]$ ]]; then + break + fi + fi + done + + clean_files +} + +while getopts "hlp:r:s" opt; do case ${opt} in h) usage @@ -272,6 +371,10 @@ while getopts "hlp:r:" opt; do review_pr "${OPTARG}" exit 0 ;; + s) + submit_pr + exit 0 + ;; *) exit 1 ;; diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index ea5125e4e7..ef9e11fbef 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -76,7 +76,6 @@ set(CONV_SOURCES diff.c edit.c eval.c - ex_cmds2.c ex_cmds.c ex_docmd.c ex_getln.c diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b7a86af134..fa4b8e5f7d 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -19,6 +19,7 @@ #include "nvim/mark.h" #include "nvim/fileio.h" #include "nvim/move.h" +#include "nvim/syntax.h" #include "nvim/window.h" #include "nvim/undo.h" @@ -514,6 +515,99 @@ ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err) return rv; } +/// Adds a highlight to buffer. +/// +/// This can be used for plugins which dynamically generate highlights to a +/// buffer (like a semantic highlighter or linter). The function adds a single +/// highlight to a buffer. Unlike matchaddpos() highlights follow changes to +/// line numbering (as lines are inserted/removed above the highlighted line), +/// like signs and marks do. +/// +/// "src_id" is useful for batch deletion/updating of a set of highlights. When +/// called with src_id = 0, an unique source id is generated and returned. +/// Succesive calls can pass in it as "src_id" to add new highlights to the same +/// source group. All highlights in the same group can then be cleared with +/// buffer_clear_highlight. If the highlight never will be manually deleted +/// pass in -1 for "src_id". +/// +/// If "hl_group" is the empty string no highlight is added, but a new src_id +/// is still returned. This is useful for an external plugin to synchrounously +/// request an unique src_id at initialization, and later asynchronously add and +/// clear highlights in response to buffer changes. +/// +/// @param buffer The buffer handle +/// @param src_id Source group to use or 0 to use a new group, +/// or -1 for ungrouped highlight +/// @param hl_group Name of the highlight group to use +/// @param line The line to highlight +/// @param col_start Start of range of columns to highlight +/// @param col_end End of range of columns to highlight, +/// or -1 to highlight to end of line +/// @param[out] err Details of an error that may have occurred +/// @return The src_id that was used +Integer buffer_add_highlight(Buffer buffer, + Integer src_id, + String hl_group, + Integer line, + Integer col_start, + Integer col_end, + Error *err) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (line < 0 || line >= MAXLNUM) { + api_set_error(err, Validation, _("Line number outside range")); + return 0; + } + if (col_start < 0 || col_start > MAXCOL) { + api_set_error(err, Validation, _("Column value outside range")); + return 0; + } + if (col_end < 0 || col_end > MAXCOL) { + col_end = MAXCOL; + } + + int hlg_id = syn_name2id((char_u*)hl_group.data); + src_id = bufhl_add_hl(buf, (int)src_id, hlg_id, (linenr_T)line+1, + (colnr_T)col_start+1, (colnr_T)col_end); + return src_id; +} + +/// Clears highlights from a given source group and a range of lines +/// +/// To clear a source group in the entire buffer, pass in 1 and -1 to +/// line_start and line_end respectively. +/// +/// @param buffer The buffer handle +/// @param src_id Highlight source group to clear, or -1 to clear all groups. +/// @param line_start Start of range of lines to clear +/// @param line_end End of range of lines to clear (exclusive) +/// or -1 to clear to end of file. +/// @param[out] err Details of an error that may have occurred +void buffer_clear_highlight(Buffer buffer, + Integer src_id, + Integer line_start, + Integer line_end, + Error *err) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return; + } + + if (line_start < 0 || line_start >= MAXLNUM) { + api_set_error(err, Validation, _("Line number outside range")); + return; + } + if (line_end < 0 || line_end > MAXLNUM) { + line_end = MAXLNUM; + } + + bufhl_clear_line_range(buf, (int)src_id, (int)line_start+1, (int)line_end); +} // Check if deleting lines made the cursor position invalid. // Changed the lines from "lo" to "hi" and added "extra" lines (negative if diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index aa4a8d8332..8d891effae 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -83,6 +83,7 @@ return { 'TermResponse', -- after setting "v:termresponse" 'TextChanged', -- text was modified 'TextChangedI', -- text was modified in Insert mode + 'TextYankPost', -- after a yank or delete was done (y, d, c) 'User', -- user defined autocommand 'VimEnter', -- after starting Vim 'VimLeave', -- before exiting Vim diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 9806623433..62ab7495da 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -580,16 +580,17 @@ free_buffer_stuff ( ) { if (free_options) { - clear_wininfo(buf); /* including window-local options */ - free_buf_options(buf, TRUE); + clear_wininfo(buf); // including window-local options + free_buf_options(buf, true); ga_clear(&buf->b_s.b_langp); } - vars_clear(&buf->b_vars->dv_hashtab); /* free all internal variables */ + vars_clear(&buf->b_vars->dv_hashtab); // free all internal variables hash_init(&buf->b_vars->dv_hashtab); - uc_clear(&buf->b_ucmds); /* clear local user commands */ - buf_delete_signs(buf); /* delete any signs */ - map_clear_int(buf, MAP_ALL_MODES, TRUE, FALSE); /* clear local mappings */ - map_clear_int(buf, MAP_ALL_MODES, TRUE, TRUE); /* clear local abbrevs */ + uc_clear(&buf->b_ucmds); // clear local user commands + buf_delete_signs(buf); // delete any signs + bufhl_clear_all(buf); // delete any highligts + map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings + map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs xfree(buf->b_start_fenc); buf->b_start_fenc = NULL; } @@ -1741,12 +1742,15 @@ int buflist_findpat( int toggledollar; if (pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) { - if (*pattern == '%') + if (*pattern == '%') { match = curbuf->b_fnum; - else + } else { match = curwin->w_alt_fnum; - if (diffmode && !diff_mode_buf(buflist_findnr(match))) + } + buf_T *found_buf = buflist_findnr(match); + if (diffmode && !(found_buf && diff_mode_buf(found_buf))) { match = -1; + } } /* * Try four ways of matching a listed buffer: @@ -4867,6 +4871,224 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_a } } +// bufhl: plugin highlights associated with a buffer + +/// Adds a highlight to buffer. +/// +/// Unlike matchaddpos() highlights follow changes to line numbering (as lines +/// are inserted/removed above the highlighted line), like signs and marks do. +/// +/// When called with "src_id" set to 0, a unique source id is generated and +/// returned. Succesive calls can pass it in as "src_id" to add new highlights +/// to the same source group. All highlights in the same group can be cleared +/// at once. If the highlight never will be manually deleted pass in -1 for +/// "src_id" +/// +/// if "hl_id" or "lnum" is invalid no highlight is added, but a new src_id +/// is still returned. +/// +/// @param buf The buffer to add highlights to +/// @param src_id src_id to use or 0 to use a new src_id group, +/// or -1 for ungrouped highlight. +/// @param hl_id Id of the highlight group to use +/// @param lnum The line to highlight +/// @param col_start First column to highlight +/// @param col_end The last column to highlight, +/// or -1 to highlight to end of line +/// @return The src_id that was used +int bufhl_add_hl(buf_T *buf, + int src_id, + int hl_id, + linenr_T lnum, + colnr_T col_start, + colnr_T col_end) { + static int next_src_id = 1; + if (src_id == 0) { + src_id = next_src_id++; + } + if (hl_id <= 0) { + // no highlight group or invalid line, just return src_id + return src_id; + } + if (!buf->b_bufhl_info) { + buf->b_bufhl_info = map_new(linenr_T, bufhl_vec_T)(); + } + bufhl_vec_T* lineinfo = map_ref(linenr_T, bufhl_vec_T)(buf->b_bufhl_info, + lnum, true); + + bufhl_hl_item_T *hlentry = kv_pushp(bufhl_hl_item_T, *lineinfo); + hlentry->src_id = src_id; + hlentry->hl_id = hl_id; + hlentry->start = col_start; + hlentry->stop = col_end; + + if (0 < lnum && lnum <= buf->b_ml.ml_line_count) { + changed_lines_buf(buf, lnum, lnum+1, 0); + redraw_buf_later(buf, VALID); + } + return src_id; +} + +/// Clear bufhl highlights from a given source group and range of lines. +/// +/// @param buf The buffer to remove highlights from +/// @param src_id highlight source group to clear, or -1 to clear all groups. +/// @param line_start first line to clear +/// @param line_end last line to clear or MAXLNUM to clear to end of file. +void bufhl_clear_line_range(buf_T *buf, + int src_id, + linenr_T line_start, + linenr_T line_end) { + if (!buf->b_bufhl_info) { + return; + } + linenr_T line; + linenr_T first_changed = MAXLNUM, last_changed = -1; + // In the case line_start - line_end << bufhl_info->size + // it might be better to reverse this, i e loop over the lines + // to clear on. + bufhl_vec_T unused; + map_foreach(buf->b_bufhl_info, line, unused, { + (void)unused; + if (line_start <= line && line <= line_end) { + if (bufhl_clear_line(buf->b_bufhl_info, src_id, line)) { + if (line > last_changed) { + last_changed = line; + } + if (line < first_changed) { + first_changed = line; + } + } + } + }) + + if (last_changed != -1) { + changed_lines_buf(buf, first_changed, last_changed+1, 0); + redraw_buf_later(buf, VALID); + } +} + +/// Clear bufhl highlights from a given source group and given line +/// +/// @param bufhl_info The highlight info for the buffer +/// @param src_id Highlight source group to clear, or -1 to clear all groups. +/// @param lnum Linenr where the highlight should be cleared +static bool bufhl_clear_line(bufhl_info_T *bufhl_info, int src_id, int lnum) { + bufhl_vec_T* lineinfo = map_ref(linenr_T, bufhl_vec_T)(bufhl_info, + lnum, false); + size_t oldsize = kv_size(*lineinfo); + if (src_id < 0) { + kv_size(*lineinfo) = 0; + } else { + size_t newind = 0; + for (size_t i = 0; i < kv_size(*lineinfo); i++) { + if (kv_A(*lineinfo, i).src_id != src_id) { + if (i != newind) { + kv_A(*lineinfo, newind) = kv_A(*lineinfo, i); + } + newind++; + } + } + kv_size(*lineinfo) = newind; + } + + if (kv_size(*lineinfo) == 0) { + kv_destroy(*lineinfo); + map_del(linenr_T, bufhl_vec_T)(bufhl_info, lnum); + } + return kv_size(*lineinfo) != oldsize; +} + +/// Remove all highlights and free the highlight data +void bufhl_clear_all(buf_T* buf) { + if (!buf->b_bufhl_info) { + return; + } + bufhl_clear_line_range(buf, -1, 1, MAXLNUM); + map_free(linenr_T, bufhl_vec_T)(buf->b_bufhl_info); + buf->b_bufhl_info = NULL; +} + +/// Adjust a placed highlight for inserted/deleted lines. +void bufhl_mark_adjust(buf_T* buf, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after) { + if (!buf->b_bufhl_info) { + return; + } + + bufhl_info_T *newmap = map_new(linenr_T, bufhl_vec_T)(); + linenr_T line; + bufhl_vec_T lineinfo; + map_foreach(buf->b_bufhl_info, line, lineinfo, { + if (line >= line1 && line <= line2) { + if (amount == MAXLNUM) { + bufhl_clear_line(buf->b_bufhl_info, -1, line); + continue; + } else { + line += amount; + } + } else if (line > line2) { + line += amount_after; + } + map_put(linenr_T, bufhl_vec_T)(newmap, line, lineinfo); + }); + map_free(linenr_T, bufhl_vec_T)(buf->b_bufhl_info); + buf->b_bufhl_info = newmap; +} + + +/// Get highlights to display at a specific line +/// +/// @param buf The buffer handle +/// @param lnum The line number +/// @param[out] info The highligts for the line +/// @return true if there was highlights to display +bool bufhl_start_line(buf_T *buf, linenr_T lnum, bufhl_lineinfo_T *info) { + if (!buf->b_bufhl_info) { + return false; + } + + info->valid_to = -1; + info->entries = map_get(linenr_T, bufhl_vec_T)(buf->b_bufhl_info, lnum); + return kv_size(info->entries) > 0; +} + +/// get highlighting at column col +/// +/// It is is assumed this will be called with +/// non-decreasing column nrs, so that it is +/// possible to only recalculate highlights +/// at endpoints. +/// +/// @param info The info returned by bufhl_start_line +/// @param col The column to get the attr for +/// @return The highilight attr to display at the column +int bufhl_get_attr(bufhl_lineinfo_T *info, colnr_T col) { + if (col <= info->valid_to) { + return info->current; + } + int attr = 0; + info->valid_to = MAXCOL; + for (size_t i = 0; i < kv_size(info->entries); i++) { + bufhl_hl_item_T entry = kv_A(info->entries, i); + if (entry.start <= col && col <= entry.stop) { + int entry_attr = syn_id2attr(entry.hl_id); + attr = hl_combine_attr(attr, entry_attr); + if (entry.stop < info->valid_to) { + info->valid_to = entry.stop; + } + } else if (col < entry.start && entry.start-1 < info->valid_to) { + info->valid_to = entry.start-1; + } + } + info->current = attr; + return attr; +} + + /* * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. */ diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index bdea609820..936a14b903 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -28,6 +28,8 @@ typedef struct file_buffer buf_T; // Forward declaration #include "nvim/profile.h" // for String #include "nvim/api/private/defs.h" +// for Map(K, V) +#include "nvim/map.h" #define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma) @@ -59,21 +61,21 @@ typedef struct file_buffer buf_T; // Forward declaration #define VALID_BOTLINE_AP 0x40 /* w_botine is approximated */ #define VALID_TOPLINE 0x80 /* w_topline is valid (for cursor position) */ -/* flags for b_flags */ -#define BF_RECOVERED 0x01 /* buffer has been recovered */ -#define BF_CHECK_RO 0x02 /* need to check readonly when loading file - into buffer (set by ":e", may be reset by - ":buf" */ -#define BF_NEVERLOADED 0x04 /* file has never been loaded into buffer, - many variables still need to be set */ -#define BF_NOTEDITED 0x08 /* Set when file name is changed after - starting to edit, reset when file is - written out. */ -#define BF_NEW 0x10 /* file didn't exist when editing started */ -#define BF_NEW_W 0x20 /* Warned for BF_NEW and file created */ -#define BF_READERR 0x40 /* got errors while reading the file */ -#define BF_DUMMY 0x80 /* dummy buffer, only used internally */ -#define BF_PRESERVED 0x100 /* ":preserve" was used */ +// flags for b_flags +#define BF_RECOVERED 0x01 // buffer has been recovered +#define BF_CHECK_RO 0x02 // need to check readonly when loading file + // into buffer (set by ":e", may be reset by + // ":buf") +#define BF_NEVERLOADED 0x04 // file has never been loaded into buffer, + // many variables still need to be set +#define BF_NOTEDITED 0x08 // Set when file name is changed after + // starting to edit, reset when file is + // written out. +#define BF_NEW 0x10 // file didn't exist when editing started +#define BF_NEW_W 0x20 // Warned for BF_NEW and file created +#define BF_READERR 0x40 // got errors while reading the file +#define BF_DUMMY 0x80 // dummy buffer, only used internally +#define BF_PRESERVED 0x100 // ":preserve" was used /* Mask to check for flags that prevent normal writing */ #define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR) @@ -101,6 +103,11 @@ typedef int scid_T; /* script ID */ // for signlist_T #include "nvim/sign_defs.h" +// for bufhl_*_T +#include "nvim/bufhl_defs.h" + +typedef Map(linenr_T, bufhl_vec_T) bufhl_info_T; + // for FileID #include "nvim/os/fs_defs.h" @@ -754,6 +761,8 @@ struct file_buffer { dict_T *additional_data; // Additional data from shada file if any. int b_mapped_ctrl_c; // modes where CTRL-C is mapped + + bufhl_info_T *b_bufhl_info; // buffer stored highlights }; /* diff --git a/src/nvim/bufhl_defs.h b/src/nvim/bufhl_defs.h new file mode 100644 index 0000000000..e47bb2a238 --- /dev/null +++ b/src/nvim/bufhl_defs.h @@ -0,0 +1,25 @@ +#ifndef NVIM_BUFHL_DEFS_H +#define NVIM_BUFHL_DEFS_H + +#include "nvim/pos.h" +#include "nvim/lib/kvec.h" +// bufhl: buffer specific highlighting + +struct bufhl_hl_item +{ + int src_id; + int hl_id; // highlight group + colnr_T start; // first column to highlight + colnr_T stop; // last column to highlight +}; +typedef struct bufhl_hl_item bufhl_hl_item_T; + +typedef kvec_t(struct bufhl_hl_item) bufhl_vec_T; + +typedef struct { + bufhl_vec_T entries; + int current; + colnr_T valid_to; +} bufhl_lineinfo_T; + +#endif // NVIM_BUFHL_DEFS_H diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d3b556f669..6313ea2e81 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1247,7 +1247,7 @@ normalchar: static void insert_do_complete(InsertState *s) { compl_busy = true; - if (ins_complete(s->c) == FAIL) { + if (ins_complete(s->c, true) == FAIL) { compl_cont_status = 0; } compl_busy = false; @@ -2321,9 +2321,10 @@ static int ins_compl_make_cyclic(void) */ void set_completion(colnr_T startcol, list_T *list) { - /* If already doing completions stop it. */ - if (ctrl_x_mode != 0) + // If already doing completions stop it. + if (ctrl_x_mode != 0) { ins_compl_prep(' '); + } ins_compl_clear(); if (stop_arrow() == FAIL) @@ -2349,16 +2350,23 @@ void set_completion(colnr_T startcol, list_T *list) compl_started = TRUE; compl_used_match = TRUE; compl_cont_status = 0; + int save_w_wrow = curwin->w_wrow; compl_curr_match = compl_first_match; if (compl_no_insert) { - ins_complete(K_DOWN); + ins_complete(K_DOWN, false); } else { - ins_complete(Ctrl_N); + ins_complete(Ctrl_N, false); if (compl_no_select) { - ins_complete(Ctrl_P); + ins_complete(Ctrl_P, false); } } + + // Lazily show the popup menu, unless we got interrupted. + if (!compl_interrupted) { + show_pum(save_w_wrow); + } + ui_flush(); } @@ -2935,18 +2943,17 @@ static void ins_compl_new_leader(void) ins_bytes(compl_leader + ins_compl_len()); compl_used_match = FALSE; - if (compl_started) + if (compl_started) { ins_compl_set_original_text(compl_leader); - else { - spell_bad_len = 0; /* need to redetect bad word */ - /* - * Matches were cleared, need to search for them now. - * Set "compl_restarting" to avoid that the first match is inserted. - */ - compl_restarting = TRUE; - if (ins_complete(Ctrl_N) == FAIL) + } else { + spell_bad_len = 0; // need to redetect bad word + // Matches were cleared, need to search for them now. + // Set "compl_restarting" to avoid that the first match is inserted. + compl_restarting = true; + if (ins_complete(Ctrl_N, true) == FAIL) { compl_cont_status = 0; - compl_restarting = FALSE; + } + compl_restarting = false; } compl_enter_selects = !compl_used_match; @@ -4298,7 +4305,7 @@ static int ins_compl_use_match(int c) * Called when character "c" was typed, which has a meaning for completion. * Returns OK if completion was done, FAIL if something failed. */ -static int ins_complete(int c) +static int ins_complete(int c, bool enable_pum) { char_u *line; int startcol = 0; /* column where searched text starts */ @@ -4781,20 +4788,9 @@ static int ins_complete(int c) } } - /* Show the popup menu, unless we got interrupted. */ - if (!compl_interrupted) { - /* RedrawingDisabled may be set when invoked through complete(). */ - n = RedrawingDisabled; - RedrawingDisabled = 0; - - /* If the cursor moved we need to remove the pum first. */ - setcursor(); - if (save_w_wrow != curwin->w_wrow) - ins_compl_del_pum(); - - ins_compl_show_pum(); - setcursor(); - RedrawingDisabled = n; + // Show the popup menu, unless we got interrupted. + if (enable_pum && !compl_interrupted) { + show_pum(save_w_wrow); } compl_was_interrupted = compl_interrupted; compl_interrupted = FALSE; @@ -4947,15 +4943,10 @@ int get_literal(void) return cc; } -/* - * Insert character, taking care of special keys and mod_mask - */ -static void -insert_special ( - int c, - int allow_modmask, - int ctrlv /* c was typed after CTRL-V */ -) +/// Insert character, taking care of special keys and mod_mask +/// +/// @param ctrlv `c` was typed after CTRL-V +static void insert_special(int c, int allow_modmask, int ctrlv) { char_u *p; int len; @@ -4967,6 +4958,9 @@ insert_special ( * Only use mod_mask for special keys, to avoid things like <S-Space>, * unless 'allow_modmask' is TRUE. */ + if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. + allow_modmask = true; + } if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) { p = get_special_key_name(c, mod_mask); len = (int)STRLEN(p); @@ -8522,3 +8516,20 @@ static char_u *do_insert_char_pre(int c) return res; } + +static void show_pum(int save_w_wrow) +{ + // RedrawingDisabled may be set when invoked through complete(). + int n = RedrawingDisabled; + RedrawingDisabled = 0; + + // If the cursor moved we need to remove the pum first. + setcursor(); + if (save_w_wrow != curwin->w_wrow) { + ins_compl_del_pum(); + } + + ins_compl_show_pum(); + setcursor(); + RedrawingDisabled = n; +} diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ec085efc07..0e5da13242 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -378,6 +378,7 @@ static struct vimvar { { VV_NAME("option_type", VAR_STRING), VV_RO }, { VV_NAME("errors", VAR_LIST), 0 }, { VV_NAME("msgpack_types", VAR_DICT), VV_RO }, + { VV_NAME("event", VAR_DICT), VV_RO }, }; /* shorthand */ @@ -545,6 +546,10 @@ void eval_init(void) set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict); set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc()); + + dict_T *v_event = dict_alloc(); + v_event->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_EVENT, v_event); set_vim_var_list(VV_ERRORS, list_alloc()); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); @@ -4010,12 +4015,22 @@ eval6 ( * When either side is a float the result is a float. */ if (use_float) { - if (op == '*') + if (op == '*') { f1 = f1 * f2; - else if (op == '/') { - /* We rely on the floating point library to handle divide - * by zero to result in "inf" and not a crash. */ - f1 = f2 != 0 ? f1 / f2 : INFINITY; + } else if (op == '/') { + // Division by zero triggers error from AddressSanitizer + f1 = (f2 == 0 + ? ( +#ifdef NAN + f1 == 0 + ? NAN + : +#endif + (f1 > 0 + ? INFINITY + : -INFINITY) + ) + : f1 / f2); } else { EMSG(_("E804: Cannot use '%' with Float")); return FAIL; @@ -6007,6 +6022,27 @@ static void rettv_dict_alloc(typval_T *rettv) ++d->dv_refcount; } +/// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. +/// +/// @param d The Dictionary to clear +void dict_clear(dict_T *d) + FUNC_ATTR_NONNULL_ALL +{ + hash_lock(&d->dv_hashtab); + assert(d->dv_hashtab.ht_locked > 0); + + size_t todo = d->dv_hashtab.ht_used; + for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + dictitem_free(HI2DI(hi)); + hash_remove(&d->dv_hashtab, hi); + todo--; + } + } + + hash_unlock(&d->dv_hashtab); +} + /* * Unreference a Dictionary: decrement the reference count and free it when it @@ -6248,6 +6284,24 @@ int dict_add_list(dict_T *d, char *key, list_T *list) return OK; } +/// Set all existing keys in "dict" as read-only. +/// +/// This does not protect against adding new keys to the Dictionary. +/// +/// @param dict The dict whose keys should be frozen +void dict_set_keys_readonly(dict_T *dict) + FUNC_ATTR_NONNULL_ALL +{ + size_t todo = dict->dv_hashtab.ht_used; + for (hashitem_T *hi = dict->dv_hashtab.ht_array; todo > 0 ; hi++) { + if (HASHITEM_EMPTY(hi)) { + continue; + } + todo--; + HI2DI(hi)->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; + } +} + /* * Get the number of items in a Dictionary. */ @@ -6848,9 +6902,25 @@ vim_to_msgpack_error_ret: \ #define CONV_FLOAT(flt) \ do { \ - char numbuf[NUMBUFLEN]; \ - vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", (flt)); \ - ga_concat(gap, (char_u *) numbuf); \ + const float_T flt_ = (flt); \ + switch (fpclassify(flt_)) { \ + case FP_NAN: { \ + ga_concat(gap, (char_u *) "str2float('nan')"); \ + break; \ + } \ + case FP_INFINITE: { \ + if (flt_ < 0) { \ + ga_append(gap, '-'); \ + } \ + ga_concat(gap, (char_u *) "str2float('inf')"); \ + break; \ + } \ + default: { \ + char numbuf[NUMBUFLEN]; \ + vim_snprintf(numbuf, NUMBUFLEN - 1, "%g", flt_); \ + ga_concat(gap, (char_u *) numbuf); \ + } \ + } \ } while (0) #define CONV_FUNC(fun) \ @@ -7290,7 +7360,7 @@ static struct fst { { "pathshorten", 1, 1, f_pathshorten }, { "pow", 2, 2, f_pow }, { "prevnonblank", 1, 1, f_prevnonblank }, - { "printf", 2, 19, f_printf }, + { "printf", 2, MAX_FUNC_ARGS, f_printf }, { "pumvisible", 0, 0, f_pumvisible }, { "py3eval", 1, 1, f_py3eval }, { "pyeval", 1, 1, f_pyeval }, @@ -7304,8 +7374,8 @@ 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 }, + { "rpcnotify", 2, MAX_FUNC_ARGS, f_rpcnotify }, + { "rpcrequest", 2, MAX_FUNC_ARGS, f_rpcrequest }, { "rpcstart", 1, 2, f_rpcstart }, { "rpcstop", 1, 1, f_rpcstop }, { "screenattr", 2, 2, f_screenattr }, @@ -7324,10 +7394,10 @@ static struct fst { { "setcharsearch", 1, 1, f_setcharsearch }, { "setcmdpos", 1, 1, f_setcmdpos }, { "setline", 2, 2, f_setline }, - { "setloclist", 2, 3, f_setloclist }, + { "setloclist", 2, 4, f_setloclist }, { "setmatches", 1, 1, f_setmatches }, { "setpos", 2, 2, f_setpos }, - { "setqflist", 1, 2, f_setqflist }, + { "setqflist", 1, 3, f_setqflist }, { "setreg", 2, 3, f_setreg }, { "settabvar", 3, 3, f_settabvar }, { "settabwinvar", 4, 4, f_settabwinvar }, @@ -10553,8 +10623,6 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv) { char_u *strregname; int regname; - char_u buf[NUMBUFLEN + 2]; - long reglen = 0; if (argvars[0].v_type != VAR_UNKNOWN) { strregname = get_tv_string_chk(&argvars[0]); @@ -10571,18 +10639,13 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv) if (regname == 0) regname = '"'; - buf[0] = NUL; - buf[1] = NUL; - switch (get_reg_type(regname, ®len)) { - case MLINE: buf[0] = 'V'; break; - case MCHAR: buf[0] = 'v'; break; - case MBLOCK: - buf[0] = Ctrl_V; - sprintf((char *)buf + 1, "%" PRId64, (int64_t)(reglen + 1)); - break; - } + colnr_T reglen = 0; + char buf[NUMBUFLEN + 2]; + char_u reg_type = get_reg_type(regname, ®len); + format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); + rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(buf); + rettv->vval.v_string = (char_u *)xstrdup(buf); } /* @@ -10698,11 +10761,11 @@ getwinvar ( int off /* 1 for gettabwinvar() */ ) { - win_T *win, *oldcurwin; - char_u *varname; - dictitem_T *v; - tabpage_T *tp = NULL; - tabpage_T *oldtabpage = NULL; + win_T *win, *oldcurwin; + char_u *varname; + dictitem_T *v; + tabpage_T *tp = NULL; + tabpage_T *oldtabpage = NULL; bool done = false; if (off == 1) @@ -10717,12 +10780,16 @@ getwinvar ( rettv->vval.v_string = NULL; if (win != NULL && varname != NULL) { - /* Set curwin to be our win, temporarily. Also set the tabpage, - * otherwise the window is not valid. */ - if (switch_win(&oldcurwin, &oldtabpage, win, tp, TRUE) == OK) { - if (*varname == '&') { /* window-local-option */ - if (get_option_tv(&varname, rettv, 1) == OK) + // Set curwin to be our win, temporarily. Also set the tabpage, + // otherwise the window is not valid. Only do this when needed, + // autocommands get blocked. + bool need_switch_win = tp != curtab || win != curwin; + if (!need_switch_win + || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { + if (*varname == '&') { // window-local-option + if (get_option_tv(&varname, rettv, 1) == OK) { done = true; + } } else { // Look up the variable. // Let getwinvar({nr}, "") return the "w:" dictionary. @@ -10734,8 +10801,10 @@ getwinvar ( } } - /* restore previous notion of curwin */ - restore_win(oldcurwin, oldtabpage, TRUE); + if (need_switch_win) { + // restore previous notion of curwin + restore_win(oldcurwin, oldtabpage, true); + } } if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) @@ -15209,14 +15278,26 @@ static void f_setline(typval_T *argvars, typval_T *rettv) appended_lines_mark(lcount, added); } - -/* - * Used by "setqflist()" and "setloclist()" functions - */ -static void set_qf_ll_list(win_T *wp, typval_T *list_arg, typval_T *action_arg, typval_T *rettv) +/// Create quickfix/location list from VimL values +/// +/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid +/// list_arg, action_arg and title_arg arguments in which case errors out, +/// including VAR_UNKNOWN parameters. +/// +/// @param[in,out] wp Window to create location list for. May be NULL in +/// which case quickfix list will be created. +/// @param[in] list_arg Quickfix list contents. +/// @param[in] action_arg Action to perform: append to an existing list, +/// replace its content or create a new one. +/// @param[in] title_arg New list title. Defaults to caller function name. +/// @param[out] rettv Return value: 0 in case of success, -1 otherwise. +static void set_qf_ll_list(win_T *wp, typval_T *list_arg, typval_T *action_arg, + typval_T *title_arg, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(2, 3, 4, 5) { char_u *act; int action = ' '; + char_u *title = NULL; rettv->vval.v_number = -1; @@ -15225,7 +15306,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *list_arg, typval_T *action_arg, else { list_T *l = list_arg->vval.v_list; - if (action_arg->v_type == VAR_STRING) { + if (action_arg->v_type != VAR_UNKNOWN) { act = get_tv_string_chk(action_arg); if (act == NULL) return; /* type error; errmsg already given */ @@ -15233,9 +15314,20 @@ static void set_qf_ll_list(win_T *wp, typval_T *list_arg, typval_T *action_arg, action = *act; } - if (l != NULL && set_errorlist(wp, l, action, - (char_u *)(wp == NULL ? "setqflist()" : "setloclist()")) == OK) + if (title_arg->v_type == VAR_STRING) { + title = get_tv_string_chk(title_arg); + if (!title) { + return; // type error; errmsg already given + } + } + + if (!title) { + title = (char_u*)(wp ? "setloclist()" : "setqflist()"); + } + + if (l && set_errorlist(wp, l, action, title) == OK) { rettv->vval.v_number = 0; + } } } @@ -15249,8 +15341,9 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = -1; win = find_win_by_nr(&argvars[0], NULL); - if (win != NULL) - set_qf_ll_list(win, &argvars[1], &argvars[2], rettv); + if (win != NULL) { + set_qf_ll_list(win, &argvars[1], &argvars[2], &argvars[3], rettv); + } } /* @@ -15386,7 +15479,7 @@ static void f_setpos(typval_T *argvars, typval_T *rettv) */ static void f_setqflist(typval_T *argvars, typval_T *rettv) { - set_qf_ll_list(NULL, &argvars[0], &argvars[1], rettv); + set_qf_ll_list(NULL, &argvars[0], &argvars[1], &argvars[2], rettv); } /* @@ -15560,26 +15653,32 @@ static void setwinvar(typval_T *argvars, typval_T *rettv, int off) varname = get_tv_string_chk(&argvars[off + 1]); varp = &argvars[off + 2]; - if (win != NULL && varname != NULL && varp != NULL - && switch_win(&save_curwin, &save_curtab, win, tp, TRUE) == OK) { - if (*varname == '&') { - long numval; - char_u *strval; - int error = FALSE; - - ++varname; - numval = get_tv_number_chk(varp, &error); - strval = get_tv_string_buf_chk(varp, nbuf); - if (!error && strval != NULL) - set_option_value(varname, numval, strval, OPT_LOCAL); - } else { - winvarname = xmalloc(STRLEN(varname) + 3); - STRCPY(winvarname, "w:"); - STRCPY(winvarname + 2, varname); - set_var(winvarname, varp, TRUE); - xfree(winvarname); + if (win != NULL && varname != NULL && varp != NULL) { + bool need_switch_win = tp != curtab || win != curwin; + if (!need_switch_win + || switch_win(&save_curwin, &save_curtab, win, tp, true) == OK) { + if (*varname == '&') { + long numval; + char_u *strval; + int error = false; + + ++varname; + numval = get_tv_number_chk(varp, &error); + strval = get_tv_string_buf_chk(varp, nbuf); + if (!error && strval != NULL) { + set_option_value(varname, numval, strval, OPT_LOCAL); + } + } else { + winvarname = xmalloc(STRLEN(varname) + 3); + STRCPY(winvarname, "w:"); + STRCPY(winvarname + 2, varname); + set_var(winvarname, varp, true); + xfree(winvarname); + } + } + if (need_switch_win) { + restore_win(save_curwin, save_curtab, true); } - restore_win(save_curwin, save_curtab, TRUE); } } @@ -18092,23 +18191,16 @@ void set_vim_var_list(int idx, list_T *val) } /// Set Dictionary v: variable to "val". -void set_vim_var_dict(int idx, dict_T *val) FUNC_ATTR_NONNULL_ALL +void set_vim_var_dict(int idx, dict_T *val) { dict_unref(vimvars[idx].vv_dict); + vimvars[idx].vv_dict = val; - // Set readonly - int todo = (int)val->dv_hashtab.ht_used; - for (hashitem_T *hi = val->dv_hashtab.ht_array; todo > 0 ; ++hi) { - if (HASHITEM_EMPTY(hi)) { - continue; - } - - --todo; - HI2DI(hi)->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + if (val != NULL) { + ++val->dv_refcount; + // Set readonly + dict_set_keys_readonly(val); } - - vimvars[idx].vv_dict = val; - ++val->dv_refcount; } /* @@ -20914,14 +21006,18 @@ call_user_func ( save_sourcing_name = sourcing_name; save_sourcing_lnum = sourcing_lnum; sourcing_lnum = 1; - sourcing_name = xmalloc((save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) - + STRLEN(fp->uf_name) + 13); + // need space for function name + ("function " + 3) or "[number]" + size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name)) + + STRLEN(fp->uf_name) + 20; + sourcing_name = xmalloc(len); { if (save_sourcing_name != NULL - && STRNCMP(save_sourcing_name, "function ", 9) == 0) - sprintf((char *)sourcing_name, "%s..", save_sourcing_name); - else + && STRNCMP(save_sourcing_name, "function ", 9) == 0) { + vim_snprintf((char *)sourcing_name, len, "%s[%zu]..", + save_sourcing_name, save_sourcing_lnum); + } else { STRCPY(sourcing_name, "function "); + } cat_func_name(sourcing_name + STRLEN(sourcing_name), fp); if (p_verbose >= 12) { @@ -22187,7 +22283,6 @@ static void on_process_exit(Process *proc, int status, void *d) char msg[22]; snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status); terminal_close(data->term, msg); - apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, curbuf); } if (data->status_ptr) { diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 79a1341d98..f51b0f4921 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -113,7 +113,8 @@ enum { VV_OPTION_TYPE, VV_ERRORS, VV_MSGPACK_TYPES, - VV_LEN, /* number of v: vars */ + VV_EVENT, + VV_LEN, // number of v: vars }; /// Maximum number of function arguments diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4d62dd0ff9..96a7c8c5af 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3017,7 +3017,7 @@ void do_sub(exarg_T *eap) // The number of lines joined is the number of lines in the range linenr_T joined_lines_count = eap->line2 - eap->line1 + 1 // plus one extra line if not at the end of file. - + eap->line2 < curbuf->b_ml.ml_line_count ? 1 : 0; + + (eap->line2 < curbuf->b_ml.ml_line_count ? 1 : 0); if (joined_lines_count > 1) { do_join(joined_lines_count, FALSE, TRUE, FALSE, true); sub_nsubs = joined_lines_count - 1; @@ -4779,6 +4779,7 @@ void ex_helptags(exarg_T *eap) WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); if (dirname == NULL || !os_isdir(dirname)) { EMSG2(_("E150: Not a directory: %s"), eap->arg); + xfree(dirname); return; } @@ -4876,16 +4877,13 @@ helptags_one ( int fi; char_u *s; char_u *fname; - int dirlen; int utf8 = MAYBE; int this_utf8; int firstline; int mix = FALSE; /* detected mixed encodings */ - /* - * Find all *.txt files. - */ - dirlen = (int)STRLEN(dir); + // Find all *.txt files. + size_t dirlen = STRLEN(dir); STRCPY(NameBuff, dir); STRCAT(NameBuff, "/**/*"); STRCAT(NameBuff, ext); @@ -4907,7 +4905,7 @@ helptags_one ( */ STRCPY(NameBuff, dir); add_pathsep((char *)NameBuff); - STRCAT(NameBuff, tagfname); + STRNCAT(NameBuff, tagfname, sizeof(NameBuff) - dirlen - 2); fd_tags = mch_fopen((char *)NameBuff, "w"); if (fd_tags == NULL) { EMSG2(_("E152: Cannot open %s for writing"), NameBuff); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 47774f5a99..71ea170e1c 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -57,23 +57,23 @@ typedef struct scriptitem_S { char_u *sn_name; bool file_id_valid; FileID file_id; - int sn_prof_on; /* TRUE when script is/was profiled */ - int sn_pr_force; /* forceit: profile functions in this script */ - proftime_T sn_pr_child; /* time set when going into first child */ - int sn_pr_nest; /* nesting for sn_pr_child */ - /* profiling the script as a whole */ - int sn_pr_count; /* nr of times sourced */ - proftime_T sn_pr_total; /* time spent in script + children */ - proftime_T sn_pr_self; /* time spent in script itself */ - proftime_T sn_pr_start; /* time at script start */ - proftime_T sn_pr_children; /* time in children after script start */ - /* profiling the script per line */ - garray_T sn_prl_ga; /* things stored for every line */ - proftime_T sn_prl_start; /* start time for current line */ - proftime_T sn_prl_children; /* time spent in children for this line */ - proftime_T sn_prl_wait; /* wait start time for current line */ - int sn_prl_idx; /* index of line being timed; -1 if none */ - int sn_prl_execed; /* line being timed was executed */ + bool sn_prof_on; ///< true when script is/was profiled + int sn_pr_force; ///< forceit: profile functions in this script + proftime_T sn_pr_child; ///< time set when going into first child + int sn_pr_nest; ///< nesting for sn_pr_child + // profiling the script as a whole + int sn_pr_count; ///< nr of times sourced + proftime_T sn_pr_total; ///< time spent in script + children + proftime_T sn_pr_self; ///< time spent in script itself + proftime_T sn_pr_start; ///< time at script start + proftime_T sn_pr_children; ///< time in children after script start + // profiling the script per line + garray_T sn_prl_ga; ///< things stored for every line + proftime_T sn_prl_start; ///< start time for current line + proftime_T sn_prl_children; ///< time spent in children for this line + proftime_T sn_prl_wait; ///< wait start time for current line + linenr_T sn_prl_idx; ///< index of line being timed; -1 if none + int sn_prl_execed; ///< line being timed was executed } scriptitem_T; static garray_T script_items = {0, 0, sizeof(scriptitem_T), 4, NULL}; @@ -81,9 +81,9 @@ static garray_T script_items = {0, 0, sizeof(scriptitem_T), 4, NULL}; /* Struct used in sn_prl_ga for every line of a script. */ typedef struct sn_prl_S { - int snp_count; /* nr of times line was executed */ - proftime_T sn_prl_total; /* time spent in a line + children */ - proftime_T sn_prl_self; /* time spent in a line itself */ + int snp_count; ///< nr of times line was executed + proftime_T sn_prl_total; ///< time spent in a line + children + proftime_T sn_prl_self; ///< time spent in a line itself } sn_prl_T; /* @@ -93,18 +93,18 @@ typedef struct sn_prl_S { * sourcing can be done recursively. */ struct source_cookie { - FILE *fp; /* opened file for sourcing */ - char_u *nextline; /* if not NULL: line that was read ahead */ - int finished; /* ":finish" used */ + FILE *fp; ///< opened file for sourcing + char_u *nextline; ///< if not NULL: line that was read ahead + int finished; ///< ":finish" used #if defined(USE_CRNL) - int fileformat; /* EOL_UNKNOWN, EOL_UNIX or EOL_DOS */ - int error; /* TRUE if LF found after CR-LF */ + int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS + int error; ///< TRUE if LF found after CR-LF #endif - linenr_T breakpoint; /* next line with breakpoint or zero */ - char_u *fname; /* name of sourced file */ - int dbg_tick; /* debug_tick when breakpoint was set */ - int level; /* top nesting level of sourced file */ - vimconv_T conv; /* type of conversion */ + linenr_T breakpoint; ///< next line with breakpoint or zero + char_u *fname; ///< name of sourced file + int dbg_tick; ///< debug_tick when breakpoint was set + int level; ///< top nesting level of sourced file + vimconv_T conv; ///< type of conversion }; # define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) @@ -272,7 +272,7 @@ void do_debug(char_u *cmd) xfree(cmdline); } - lines_left = Rows - 1; + lines_left = (int)(Rows - 1); } xfree(cmdline); @@ -281,7 +281,7 @@ void do_debug(char_u *cmd) redraw_all_later(NOT_VALID); need_wait_return = FALSE; msg_scroll = save_msg_scroll; - lines_left = Rows - 1; + lines_left = (int)(Rows - 1); State = save_State; did_emsg = save_did_emsg; cmd_silent = save_cmd_silent; @@ -392,12 +392,12 @@ int dbg_check_skipped(exarg_T *eap) * This is a grow-array of structs. */ struct debuggy { - int dbg_nr; /* breakpoint number */ - int dbg_type; /* DBG_FUNC or DBG_FILE */ - char_u *dbg_name; /* function or file name */ - regprog_T *dbg_prog; /* regexp program */ - linenr_T dbg_lnum; /* line number in function or file */ - int dbg_forceit; /* ! used */ + int dbg_nr; ///< breakpoint number + int dbg_type; ///< DBG_FUNC or DBG_FILE + char_u *dbg_name; ///< function or file name + regprog_T *dbg_prog; ///< regexp program + linenr_T dbg_lnum; ///< line number in function or file + int dbg_forceit; ///< ! used }; static garray_T dbg_breakp = {0, 0, sizeof(struct debuggy), 4, NULL}; @@ -563,13 +563,14 @@ void ex_breakdel(exarg_T *eap) } if (ascii_isdigit(*eap->arg)) { - /* ":breakdel {nr}" */ - nr = atol((char *)eap->arg); - for (int i = 0; i < gap->ga_len; ++i) + // ":breakdel {nr}" + nr = atoi((char *)eap->arg); + for (int i = 0; i < gap->ga_len; ++i) { if (DEBUGGY(gap, i).dbg_nr == nr) { todel = i; break; } + } } else if (*eap->arg == '*') { todel = 0; del_all = TRUE; @@ -602,11 +603,13 @@ void ex_breakdel(exarg_T *eap) --gap->ga_len; if (todel < gap->ga_len) memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), - (gap->ga_len - todel) * sizeof(struct debuggy)); - if (eap->cmdidx == CMD_breakdel) + (size_t)(gap->ga_len - todel) * sizeof(struct debuggy)); + if (eap->cmdidx == CMD_breakdel) { ++debug_tick; - if (!del_all) + } + if (!del_all) { break; + } } /* If all breakpoints were removed clear the array. */ @@ -810,8 +813,8 @@ void ex_pydo3(exarg_T *eap) /* Command line expansion for :profile. */ static enum { - PEXP_SUBCMD, /* expand :profile sub-commands */ - PEXP_FUNC /* expand :profile func {funcname} */ + PEXP_SUBCMD, ///< expand :profile sub-commands + PEXP_FUNC ///< expand :profile func {funcname} } pexpand_what; static char *pexpand_cmds[] = { @@ -892,7 +895,7 @@ static void profile_reset(void) for (int id = 1; id <= script_items.ga_len; id++) { scriptitem_T *si = &SCRIPT_ITEM(id); if (si->sn_prof_on) { - si->sn_prof_on = 0; + si->sn_prof_on = false; si->sn_pr_force = 0; si->sn_pr_child = profile_zero(); si->sn_pr_nest = 0; @@ -949,7 +952,7 @@ static void profile_init(scriptitem_T *si) ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100); si->sn_prl_idx = -1; - si->sn_prof_on = TRUE; + si->sn_prof_on = true; si->sn_pr_nest = 0; } @@ -1255,7 +1258,7 @@ check_changed_any ( int save; int i; int bufnum = 0; - int bufcount = 0; + size_t bufcount = 0; int *bufnrs; FOR_ALL_BUFFERS(buf) { @@ -1520,7 +1523,7 @@ do_arglist ( didone = TRUE; xfree(ARGLIST[match].ae_fname); memmove(ARGLIST + match, ARGLIST + match + 1, - (ARGCOUNT - match - 1) * sizeof(aentry_T)); + (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); --ALIST(curwin)->al_ga.ga_len; if (curwin->w_arg_idx > match) --curwin->w_arg_idx; @@ -1537,9 +1540,7 @@ do_arglist ( int i = expand_wildcards(new_ga.ga_len, (char_u **)new_ga.ga_data, &exp_count, &exp_files, EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); ga_clear(&new_ga); - if (i == FAIL) - return FAIL; - if (exp_count == 0) { + if (i == FAIL || exp_count == 0) { EMSG(_(e_nomatch)); return FAIL; } @@ -1702,10 +1703,11 @@ void ex_argument(exarg_T *eap) { int i; - if (eap->addr_count > 0) - i = eap->line2 - 1; - else + if (eap->addr_count > 0) { + i = (int)eap->line2 - 1; + } else { i = curwin->w_arg_idx; + } do_argfile(eap, i); } @@ -1844,22 +1846,25 @@ void ex_argadd(exarg_T *eap) void ex_argdelete(exarg_T *eap) { if (eap->addr_count > 0) { - /* ":1,4argdel": Delete all arguments in the range. */ - if (eap->line2 > ARGCOUNT) + // ":1,4argdel": Delete all arguments in the range. + if (eap->line2 > ARGCOUNT) { eap->line2 = ARGCOUNT; - int n = eap->line2 - eap->line1 + 1; - if (*eap->arg != NUL || n <= 0) + } + linenr_T n = eap->line2 - eap->line1 + 1; + if (*eap->arg != NUL || n <= 0) { EMSG(_(e_invarg)); - else { - for (int i = eap->line1; i <= eap->line2; ++i) + } else { + for (linenr_T i = eap->line1; i <= eap->line2; ++i) { xfree(ARGLIST[i - 1].ae_fname); + } memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, - (size_t)((ARGCOUNT - eap->line2) * sizeof(aentry_T))); - ALIST(curwin)->al_ga.ga_len -= n; - if (curwin->w_arg_idx >= eap->line2) - curwin->w_arg_idx -= n; - else if (curwin->w_arg_idx > eap->line1) - curwin->w_arg_idx = eap->line1; + (size_t)(ARGCOUNT - eap->line2) * sizeof(aentry_T)); + ALIST(curwin)->al_ga.ga_len -= (int)n; + if (curwin->w_arg_idx >= eap->line2) { + curwin->w_arg_idx -= (int)n; + } else if (curwin->w_arg_idx > eap->line1) { + curwin->w_arg_idx = (int)eap->line1; + } } } else if (*eap->arg == NUL) EMSG(_(e_argreq)); @@ -1909,7 +1914,7 @@ void ex_listdo(exarg_T *eap) } break; case CMD_argdo: - i = eap->line1 - 1; + i = (int)eap->line1 - 1; break; default: break; @@ -1942,10 +1947,11 @@ void ex_listdo(exarg_T *eap) ex_cc(eap); buf = curbuf; - i = eap->line1 - 1; + i = (int)eap->line1 - 1; if (eap->addr_count <= 0) { // Default to all quickfix/location list entries. - eap->line2 = qf_size; + assert(qf_size < MAXLNUM); + eap->line2 = (linenr_T)qf_size; } } } else { @@ -2098,7 +2104,7 @@ alist_add_list ( after = ARGCOUNT; if (after < ARGCOUNT) memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), - (ARGCOUNT - after) * sizeof(aentry_T)); + (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); for (int i = 0; i < count; ++i) { ARGLIST[after + i].ae_fname = files[i]; ARGLIST[after + i].ae_fnum = buflist_add(files[i], BLN_LISTED); @@ -2571,7 +2577,7 @@ do_source ( while (script_items.ga_len < current_SID) { ++script_items.ga_len; SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; - SCRIPT_ITEM(script_items.ga_len).sn_prof_on = FALSE; + SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false; } si = &SCRIPT_ITEM(current_SID); si->sn_name = fname_exp; @@ -3386,8 +3392,8 @@ static void script_host_execute(char *name, exarg_T *eap) // script list_append_string(args, script ? script : eap->arg, -1); // current range - list_append_number(args, eap->line1); - list_append_number(args, eap->line2); + list_append_number(args, (int)eap->line1); + list_append_number(args, (int)eap->line2); (void)eval_call_provider(name, "execute", args); } @@ -3403,16 +3409,16 @@ static void script_host_execute_file(char *name, exarg_T *eap) // filename list_append_string(args, buffer, -1); // current range - list_append_number(args, eap->line1); - list_append_number(args, eap->line2); + list_append_number(args, (int)eap->line1); + list_append_number(args, (int)eap->line2); (void)eval_call_provider(name, "execute_file", args); } static void script_host_do_range(char *name, exarg_T *eap) { list_T *args = list_alloc(); - list_append_number(args, eap->line1); - list_append_number(args, eap->line2); + list_append_number(args, (int)eap->line1); + list_append_number(args, (int)eap->line2); list_append_string(args, eap->arg, -1); (void)eval_call_provider(name, "do_range", args); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index dfae2b849d..a7e98e7f04 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -381,15 +381,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, suppress_errthrow = FALSE; } - /* - * If requested, store and reset the global values controlling the - * exception handling (used when debugging). Otherwise clear it to avoid - * a bogus compiler warning when the optimizer uses inline functions... - */ - if (flags & DOCMD_EXCRESET) + // If requested, store and reset the global values controlling the + // exception handling (used when debugging). Otherwise clear it to avoid + // a bogus compiler warning when the optimizer uses inline functions... + if (flags & DOCMD_EXCRESET) { save_dbg_stuff(&debug_saved); - else - memset(&debug_saved, 0, 1); + } else { + memset(&debug_saved, 0, sizeof(debug_saved)); + } initial_trylevel = trylevel; @@ -2679,16 +2678,25 @@ set_one_cmd_context ( p = cmd + 1; } else { p = cmd; - while (ASCII_ISALPHA(*p) || *p == '*') /* Allow * wild card */ - ++p; - /* check for non-alpha command */ - if (p == cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL) - ++p; - /* for python 3.x: ":py3*" commands completion */ + while (ASCII_ISALPHA(*p) || *p == '*') { // Allow * wild card + p++; + } + // a user command may contain digits + if (ASCII_ISUPPER(cmd[0])) { + while (ASCII_ISALNUM(*p) || *p == '*') { + p++; + } + } + // for python 3.x: ":py3*" commands completion if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { - ++p; - while (ASCII_ISALPHA(*p) || *p == '*') - ++p; + p++; + while (ASCII_ISALPHA(*p) || *p == '*') { + p++; + } + } + // check for non-alpha command + if (p == cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL) { + p++; } len = (int)(p - cmd); @@ -2702,9 +2710,11 @@ set_one_cmd_context ( (size_t)len) == 0) break; - if (cmd[0] >= 'A' && cmd[0] <= 'Z') - while (ASCII_ISALNUM(*p) || *p == '*') /* Allow * wild card */ - ++p; + if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card + p++; + } + } } /* @@ -4310,7 +4320,7 @@ static void ex_unmap(exarg_T *eap) */ static void ex_mapclear(exarg_T *eap) { - map_clear(eap->cmd, eap->arg, eap->forceit, FALSE); + map_clear_mode(eap->cmd, eap->arg, eap->forceit, false); } /* @@ -4318,7 +4328,7 @@ static void ex_mapclear(exarg_T *eap) */ static void ex_abclear(exarg_T *eap) { - map_clear(eap->cmd, eap->arg, TRUE, TRUE); + map_clear_mode(eap->cmd, eap->arg, true, true); } static void ex_autocmd(exarg_T *eap) @@ -6280,10 +6290,8 @@ void ex_splitview(exarg_T *eap) if (eap->cmdidx == CMD_tabedit || eap->cmdidx == CMD_tabfind || eap->cmdidx == CMD_tabnew) { - if (win_new_tabpage(cmdmod.tab != 0 ? cmdmod.tab - : eap->addr_count == 0 ? 0 - : (int)eap->line2 + 1) != FAIL) { - apply_autocmds(EVENT_TABNEW, eap->arg, eap->arg, FALSE, curbuf); + if (win_new_tabpage(cmdmod.tab != 0 ? cmdmod.tab : eap->addr_count == 0 + ? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) { do_exedit(eap, old_curwin); apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, FALSE, curbuf); @@ -8774,19 +8782,18 @@ static int ses_do_frame(frame_T *fr) return FALSE; } -/* - * Return non-zero if window "wp" is to be stored in the Session. - */ +/// Return non-zero if window "wp" is to be stored in the Session. static int ses_do_win(win_T *wp) { if (wp->w_buffer->b_fname == NULL - /* When 'buftype' is "nofile" can't restore the window contents. */ - || bt_nofile(wp->w_buffer) - ) + // When 'buftype' is "nofile" can't restore the window contents. + || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { return ssop_flags & SSOP_BLANK; - if (wp->w_buffer->b_help) + } + if (wp->w_buffer->b_help) { return ssop_flags & SSOP_HELP; - return TRUE; + } + return true; } /* @@ -9352,52 +9359,55 @@ static void ex_nohlsearch(exarg_T *eap) redraw_all_later(SOME_VALID); } -/* - * ":[N]match {group} {pattern}" - * Sets nextcmd to the start of the next command, if any. Also called when - * skipping commands to find the next command. - */ +// ":[N]match {group} {pattern}" +// Sets nextcmd to the start of the next command, if any. Also called when +// skipping commands to find the next command. static void ex_match(exarg_T *eap) { - char_u *p; - char_u *g = NULL; - char_u *end; + char_u *p; + char_u *g = NULL; + char_u *end; int c; int id; - if (eap->line2 <= 3) + if (eap->line2 <= 3) { id = eap->line2; - else { + } else { EMSG(e_invcmd); return; } - /* First clear any old pattern. */ - if (!eap->skip) - match_delete(curwin, id, FALSE); + // First clear any old pattern. + if (!eap->skip) { + match_delete(curwin, id, false); + } - if (ends_excmd(*eap->arg)) + if (ends_excmd(*eap->arg)) { end = eap->arg; - else if ((STRNICMP(eap->arg, "none", 4) == 0 - && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) + } else if ((STRNICMP(eap->arg, "none", 4) == 0 + && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { end = eap->arg + 4; - else { + } else { p = skiptowhite(eap->arg); - if (!eap->skip) + if (!eap->skip) { g = vim_strnsave(eap->arg, (int)(p - eap->arg)); + } p = skipwhite(p); if (*p == NUL) { - /* There must be two arguments. */ + // There must be two arguments. + xfree(g); EMSG2(_(e_invarg2), eap->arg); return; } - end = skip_regexp(p + 1, *p, TRUE, NULL); + end = skip_regexp(p + 1, *p, true, NULL); if (!eap->skip) { if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { + xfree(g); eap->errmsg = e_trailing; return; } if (*end != *p) { + xfree(g); EMSG2(_(e_invarg2), p); return; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index d015f6b4a0..39bff9b2ad 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2957,20 +2957,37 @@ ExpandOne ( } } - /* Find longest common part */ + // Find longest common part if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { size_t len; - for (len = 0; xp->xp_files[0][len]; ++len) { - for (i = 0; i < xp->xp_numfiles; ++i) { + size_t mb_len = 1; + int c0; + int ci; + + for (len = 0; xp->xp_files[0][len]; len += mb_len) { + if (has_mbyte) { + mb_len = (* mb_ptr2len)(&xp->xp_files[0][len]); + c0 = (* mb_ptr2char)(&xp->xp_files[0][len]); + } else { + c0 = xp->xp_files[0][len]; + } + for (i = 1; i < xp->xp_numfiles; ++i) { + if (has_mbyte) { + ci =(* mb_ptr2char)(&xp->xp_files[i][len]); + } else { + ci = xp->xp_files[i][len]; + } + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_SHELLCMD || xp->xp_context == EXPAND_BUFFERS)) { - if (TOLOWER_LOC(xp->xp_files[i][len]) != - TOLOWER_LOC(xp->xp_files[0][len])) + if (vim_tolower(c0) != vim_tolower(ci)) { break; - } else if (xp->xp_files[i][len] != xp->xp_files[0][len]) + } + } else if (c0 != ci) { break; + } } if (i < xp->xp_numfiles) { if (!(options & WILD_NO_BEEP)) { @@ -2979,8 +2996,9 @@ ExpandOne ( break; } } + ss = (char_u *)xstrndup((char *)xp->xp_files[0], len); - findex = -1; /* next p_wc gets first one */ + findex = -1; // next p_wc gets first one } // Concatenate all matching names diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 89d22ad811..437495faa4 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1380,13 +1380,15 @@ int vgetc(void) } else { mod_mask = 0x0; last_recorded_len = 0; - for (;; ) { /* this is done twice if there are modifiers */ - if (mod_mask) { /* no mapping after modifier has been read */ + for (;; ) { // this is done twice if there are modifiers + bool did_inc = false; + if (mod_mask) { // no mapping after modifier has been read ++no_mapping; ++allow_keys; + did_inc = true; // mod_mask may change value } - c = vgetorpeek(TRUE); - if (mod_mask) { + c = vgetorpeek(true); + if (did_inc) { --no_mapping; --allow_keys; } @@ -2913,9 +2915,9 @@ do_map ( did_it = TRUE; } } - if (mp->m_mode == 0) { /* entry can be deleted */ - map_free(mpp); - continue; /* continue with *mpp */ + if (mp->m_mode == 0) { // entry can be deleted + mapblock_free(mpp); + continue; // continue with *mpp } /* @@ -3010,7 +3012,7 @@ theend: * Delete one entry from the abbrlist or maphash[]. * "mpp" is a pointer to the m_next field of the PREVIOUS entry! */ -static void map_free(mapblock_T **mpp) +static void mapblock_free(mapblock_T **mpp) { mapblock_T *mp; @@ -3078,7 +3080,7 @@ int get_map_mode(char_u **cmdp, int forceit) * Clear all mappings or abbreviations. * 'abbr' should be FALSE for mappings, TRUE for abbreviations. */ -void map_clear(char_u *cmdp, char_u *arg, int forceit, int abbr) +void map_clear_mode(char_u *cmdp, char_u *arg, int forceit, int abbr) { int mode; int local; @@ -3130,8 +3132,8 @@ map_clear_int ( mp = *mpp; if (mp->m_mode & mode) { mp->m_mode &= ~mode; - if (mp->m_mode == 0) { /* entry can be deleted */ - map_free(mpp); + if (mp->m_mode == 0) { // entry can be deleted + mapblock_free(mpp); continue; } /* diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index d236501b3f..7169a1d963 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1632,77 +1632,79 @@ static char *cs_pathcomponents(char *path) return s; } -/* - * PRIVATE: cs_print_tags_priv - * - * called from cs_manage_matches() - */ +/// Print cscope output that was converted into ctags style entries. +/// +/// Only called from cs_manage_matches(). +/// +/// @param matches Array of cscope lines in ctags style. Every entry was +// produced with a format string of the form +// "%s\t%s\t%s;\"\t%s" or +// "%s\t%s\t%s;\"" +// by cs_make_vim_style_matches(). +/// @param cntxts Context for matches. +/// @param num_matches Number of entries in matches/cntxts, always greater 0. static void cs_print_tags_priv(char **matches, char **cntxts, - size_t num_matches) + size_t num_matches) FUNC_ATTR_NONNULL_ALL { - char *ptag; - char *fname, *lno, *extra, *tbuf; - size_t num; - char *globalcntx = "GLOBAL"; - char *context; - char *cstag_msg = _("Cscope tag: %s"); + char *globalcntx = "GLOBAL"; + char *cstag_msg = _("Cscope tag: %s"); - assert (num_matches > 0); + assert(num_matches > 0); + assert(strcnt(matches[0], '\t') >= 2); - tbuf = xmalloc(strlen(matches[0]) + 1); + char *ptag = matches[0]; + char *ptag_end = strchr(ptag, '\t'); + assert(ptag_end >= ptag); + // NUL terminate tag string in matches[0]. + *ptag_end = NUL; - strcpy(tbuf, matches[0]); - ptag = strtok(tbuf, "\t"); - - size_t newsize = strlen(cstag_msg) + strlen(ptag); + // The "%s" in cstag_msg won't appear in the result string, so we don't need + // extra memory for terminating NUL. + size_t newsize = strlen(cstag_msg) + (size_t)(ptag_end - ptag); char *buf = xmalloc(newsize); size_t bufsize = newsize; // Track available bufsize - (void)sprintf(buf, cstag_msg, ptag); + (void)snprintf(buf, bufsize, cstag_msg, ptag); MSG_PUTS_ATTR(buf, hl_attr(HLF_T)); + msg_clr_eos(); - xfree(tbuf); + // restore matches[0] + *ptag_end = '\t'; - MSG_PUTS_ATTR(_("\n # line"), hl_attr(HLF_T)); /* strlen is 7 */ + // Column headers for match number, line number and filename. + MSG_PUTS_ATTR(_("\n # line"), hl_attr(HLF_T)); msg_advance(msg_col + 2); MSG_PUTS_ATTR(_("filename / context / line\n"), hl_attr(HLF_T)); - num = 1; for (size_t i = 0; i < num_matches; i++) { - size_t idx = i; - - /* if we really wanted to, we could avoid this malloc and strcpy - * by parsing matches[i] on the fly and placing stuff into buf - * directly, but that's too much of a hassle - */ - tbuf = xmalloc(strlen(matches[idx]) + 1); - (void)strcpy(tbuf, matches[idx]); - - if (strtok(tbuf, (const char *)"\t") == NULL) - continue; - if ((fname = strtok(NULL, (const char *)"\t")) == NULL) - continue; - if ((lno = strtok(NULL, (const char *)"\t")) == NULL) - continue; - extra = strtok(NULL, (const char *)"\t"); - - lno[strlen(lno)-2] = '\0'; /* ignore ;" at the end */ + assert(strcnt(matches[i], '\t') >= 2); + + // Parse filename, line number and optional part. + char *fname = strchr(matches[i], '\t') + 1; + char *fname_end = strchr(fname, '\t'); + // Replace second '\t' in matches[i] with NUL to terminate fname. + *fname_end = NUL; + + char *lno = fname_end + 1; + char *extra = xstrchrnul(lno, '\t'); + // Ignore ;" at the end of lno. + char *lno_end = extra - 2; + *lno_end = NUL; + // Do we have an optional part? + extra = *extra ? extra + 1 : NULL; const char *csfmt_str = "%4zu %6s "; - /* hopefully 'num' (num of matches) will be less than 10^16 */ - newsize = strlen(csfmt_str) + 16 + strlen(lno); + // hopefully num_matches will be less than 10^16 + newsize = strlen(csfmt_str) + 16 + (size_t)(lno_end - lno); if (bufsize < newsize) { buf = xrealloc(buf, newsize); bufsize = newsize; } - (void)sprintf(buf, csfmt_str, num, lno); + (void)snprintf(buf, bufsize, csfmt_str, i + 1, lno); MSG_PUTS_ATTR(buf, hl_attr(HLF_CM)); MSG_PUTS_LONG_ATTR(cs_pathcomponents(fname), hl_attr(HLF_CM)); - /* compute the required space for the context */ - if (cntxts[idx] != NULL) - context = cntxts[idx]; - else - context = globalcntx; + // compute the required space for the context + char *context = cntxts[i] ? cntxts[i] : globalcntx; const char *cntxformat = " <<%s>>"; // '%s' won't appear in result string, so: @@ -1713,11 +1715,13 @@ static void cs_print_tags_priv(char **matches, char **cntxts, buf = xrealloc(buf, newsize); bufsize = newsize; } - (void)sprintf(buf, cntxformat, context); + int buf_len = snprintf(buf, bufsize, cntxformat, context); + assert(buf_len >= 0); - /* print the context only if it fits on the same line */ - if (msg_col + (int)strlen(buf) >= (int)Columns) + // Print the context only if it fits on the same line. + if (msg_col + buf_len >= (int)Columns) { msg_putchar('\n'); + } msg_advance(12); MSG_PUTS_LONG(buf); msg_putchar('\n'); @@ -1726,23 +1730,23 @@ static void cs_print_tags_priv(char **matches, char **cntxts, MSG_PUTS_LONG(extra); } - xfree(tbuf); /* only after printing extra due to strtok use */ + // restore matches[i] + *fname_end = '\t'; + *lno_end = ';'; - if (msg_col) + if (msg_col) { msg_putchar('\n'); + } os_breakcheck(); if (got_int) { - got_int = FALSE; /* don't print any more matches */ + got_int = false; // don't print any more matches break; } - - num++; - } /* for all matches */ + } xfree(buf); -} /* cs_print_tags_priv */ - +} /* * PRIVATE: cs_read_prompt diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 340287499e..17fadc4bfd 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -69,23 +69,33 @@ find_start_comment ( /* XXX */ return pos; } -/* - * Find the start of a comment or raw string, not knowing if we are in a - * comment or raw string right now. - * Search starts at w_cursor.lnum and goes backwards. - * Return NULL when not inside a comment or raw string. - * "CORS" -> Comment Or Raw String - */ +/// Find the start of a comment or raw string, not knowing if we are in a +/// comment or raw string right now. +/// Search starts at w_cursor.lnum and goes backwards. +/// +/// @returns NULL when not inside a comment or raw string. +/// +/// @note "CORS" -> Comment Or Raw String static pos_T *ind_find_start_CORS(void) -{ /* XXX */ - pos_T *comment_pos = find_start_comment(curbuf->b_ind_maxcomment); - pos_T *rs_pos = find_start_rawstring(curbuf->b_ind_maxcomment); - - /* If comment_pos is before rs_pos the raw string is inside the comment. - * If rs_pos is before comment_pos the comment is inside the raw string. */ - if (comment_pos == NULL || (rs_pos != NULL && lt(*rs_pos, *comment_pos))) - return rs_pos; - return comment_pos; +{ + // XXX + static pos_T comment_pos_copy; + + pos_T *comment_pos = find_start_comment(curbuf->b_ind_maxcomment); + if (comment_pos != NULL) { + // Need to make a copy of the static pos in findmatchlimit(), + // calling find_start_rawstring() may change it. + comment_pos_copy = *comment_pos; + comment_pos = &comment_pos_copy; + } + pos_T *rs_pos = find_start_rawstring(curbuf->b_ind_maxcomment); + + // If comment_pos is before rs_pos the raw string is inside the comment. + // If rs_pos is before comment_pos the comment is inside the raw string. + if (comment_pos == NULL || (rs_pos != NULL && lt(*rs_pos, *comment_pos))) { + return rs_pos; + } + return comment_pos; } /* @@ -847,13 +857,27 @@ static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) return FALSE; while (*s && *s != '(' && *s != ';' && *s != '\'' && *s != '"') { - if (cin_iscomment(s)) /* ignore comments */ + // ignore comments + if (cin_iscomment(s)) { s = cin_skipcomment(s); - else - ++s; + } else if (*s == ':') { + if (*(s + 1) == ':') { + s += 2; + } else { + // To avoid a mistake in the following situation: + // A::A(int a, int b) + // : a(0) // <--not a function decl + // , b(0) + // {... + return false; + } + } else { + s++; + } + } + if (*s != '(') { + return false; // ';', ' or " before any () or no '(' } - if (*s != '(') - return FALSE; /* ';', ' or " before any () or no '(' */ while (*s && *s != ';' && *s != '\'' && *s != '"') { if (*s == ')' && cin_nocode(s + 1)) { @@ -1122,13 +1146,21 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { pos->lnum = lnum; line = ml_get(lnum); - s = cin_skipcomment(line); + s = line; for (;; ) { if (*s == NUL) { - if (lnum == curwin->w_cursor.lnum) + if (lnum == curwin->w_cursor.lnum) { break; - /* Continue in the cursor line. */ + } + // Continue in the cursor line. line = ml_get(++lnum); + s = line; + } + if (s == line) { + // don't recognize "case (foo):" as a baseclass */ + if (cin_iscase(s, false)) { + break; + } s = cin_skipcomment(line); if (*s == NUL) continue; @@ -2707,7 +2739,8 @@ int get_c_indent(void) if (terminated == 0 || (lookfor != LOOKFOR_UNTERM && terminated == ',')) { - if (*skipwhite(l) == '[' || l[STRLEN(l) - 1] == '[') { + if (lookfor != LOOKFOR_ENUM_OR_INIT + && (*skipwhite(l) == '[' || l[STRLEN(l) - 1] == '[')) { amount += ind_continuation; } // If we're in the middle of a paren thing, Go back to the line @@ -2915,34 +2948,35 @@ int get_c_indent(void) continue; } - /* Ignore unterminated lines in between, but - * reduce indent. */ - if (amount > cur_amount) + // Ignore unterminated lines in between, but + // reduce indent. + if (amount > cur_amount) { amount = cur_amount; + } } else { - /* - * Found first unterminated line on a row, may - * line up with this line, remember its indent - * 100 + - * -> here; - */ + // Found first unterminated line on a row, may + // line up with this line, remember its indent + // 100 + // NOLINT(whitespace/tab) + // -> here; // NOLINT(whitespace/tab) l = get_cursor_line_ptr(); amount = cur_amount; - if (*skipwhite(l) == ']' || l[STRLEN(l) - 1] == ']') { + + n = (int)STRLEN(l); + if (terminated == ',' + && (*skipwhite(l) == ']' + || (n >=2 && l[n - 2] == ']'))) { break; } - /* - * If previous line ends in ',', check whether we - * are in an initialization or enum - * struct xxx = - * { - * sizeof a, - * 124 }; - * or a normal possible continuation line. - * but only, of no other statement has been found - * yet. - */ + // If previous line ends in ',', check whether we + // are in an initialization or enum + // struct xxx = + // { + // sizeof a, + // 124 }; + // or a normal possible continuation line. + // but only, of no other statement has been found + // yet. if (lookfor == LOOKFOR_INITIAL && terminated == ',') { if (curbuf->b_ind_js) { // Search for a line ending in a comma diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 65c808eb06..6c75d8bdf4 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -1,8 +1,3 @@ -/* - * functions that use lookup tables for various things, generally to do with - * special key codes. - */ - #include <assert.h> #include <inttypes.h> #include <limits.h> @@ -39,7 +34,8 @@ static struct modmasktable { {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'}, {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'}, {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'}, - /* 'A' must be the last one */ + {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'}, + // 'A' must be the last one {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'}, {0, 0, NUL} }; @@ -658,9 +654,11 @@ static int extract_modifiers(int key, int *modp) { int modifiers = *modp; - if ((modifiers & MOD_MASK_SHIFT) && ASCII_ISALPHA(key)) { - key = TOUPPER_ASC(key); - modifiers &= ~MOD_MASK_SHIFT; + if (!(modifiers & MOD_MASK_CMD)) { // Command-key is special + if ((modifiers & MOD_MASK_SHIFT) && ASCII_ISALPHA(key)) { + key = TOUPPER_ASC(key); + modifiers &= ~MOD_MASK_SHIFT; + } } if ((modifiers & MOD_MASK_CTRL) && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) { diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 766362d145..8f9980c6b4 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -112,11 +112,11 @@ #define TO_SPECIAL(a, b) ((a) == KS_SPECIAL ? K_SPECIAL : (a) == \ KS_ZERO ? K_ZERO : TERMCAP2KEY(a, b)) -/* - * Codes for keys that do not have a termcap name. - * - * K_SPECIAL KS_EXTRA KE_xxx - */ +// Codes for keys that do not have a termcap name. +// +// K_SPECIAL KS_EXTRA KE_xxx +// +// Entries must be in the range 0x02-0x7f (see comment at K_SPECIAL). enum key_extra { KE_NAME = 3 /* name of this terminal entry */ @@ -436,11 +436,12 @@ enum key_extra { /* 0x01 cannot be used, because the modifier must be 0x02 or higher */ #define MOD_MASK_SHIFT 0x02 #define MOD_MASK_CTRL 0x04 -#define MOD_MASK_ALT 0x08 /* aka META */ -#define MOD_MASK_META 0x10 /* META when it's different from ALT */ -#define MOD_MASK_2CLICK 0x20 /* use MOD_MASK_MULTI_CLICK */ -#define MOD_MASK_3CLICK 0x40 /* use MOD_MASK_MULTI_CLICK */ -#define MOD_MASK_4CLICK 0x60 /* use MOD_MASK_MULTI_CLICK */ +#define MOD_MASK_ALT 0x08 // aka META +#define MOD_MASK_META 0x10 // META when it's different from ALT +#define MOD_MASK_2CLICK 0x20 // use MOD_MASK_MULTI_CLICK +#define MOD_MASK_3CLICK 0x40 // use MOD_MASK_MULTI_CLICK +#define MOD_MASK_4CLICK 0x60 // use MOD_MASK_MULTI_CLICK +#define MOD_MASK_CMD 0x80 // "super" key (OSX/Mac: command-key) #define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK| \ MOD_MASK_4CLICK) @@ -451,14 +452,13 @@ enum key_extra { */ #define MAX_KEY_NAME_LEN 25 -/* Maximum length of a special key event as tokens. This includes modifiers. - * The longest event is something like <M-C-S-T-4-LeftDrag> which would be the - * following string of tokens: - * - * <K_SPECIAL> <KS_MODIFIER> bitmask <K_SPECIAL> <KS_EXTRA> <KT_LEFTDRAG>. - * - * This is a total of 6 tokens, and is currently the longest one possible. - */ +// Maximum length of a special key event as tokens. This includes modifiers. +// The longest event is something like <M-C-S-T-4-LeftDrag> which would be the +// following string of tokens: +// +// <K_SPECIAL> <KS_MODIFIER> bitmask <K_SPECIAL> <KS_EXTRA> <KE_LEFTDRAG>. +// +// This is a total of 6 tokens, and is currently the longest one possible. #define MAX_KEY_CODE_LEN 6 diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h index 56be29d14c..8287cb14da 100644 --- a/src/nvim/lib/khash.h +++ b/src/nvim/lib/khash.h @@ -184,7 +184,7 @@ typedef khint_t khiter_t; #define kfree(P) xfree(P) #endif -static const double __ac_HASH_UPPER = 0.77; +#define __ac_HASH_UPPER 0.77 #define __KHASH_TYPE(name, khkey_t, khval_t) \ typedef struct { \ diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 0466cb229c..53ecf232c6 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -77,10 +77,10 @@ int main() { (v).items[(v).size++] = (x); \ } while (0) -#define kv_pushp(type, v) (((v).size == (v).capacity)? \ +#define kv_pushp(type, v) ((((v).size == (v).capacity)? \ ((v).capacity = ((v).capacity? (v).capacity<<1 : 8), \ (v).items = (type*)xrealloc((v).items, sizeof(type) * (v).capacity), 0) \ - : 0), ((v).items + ((v).size++)) + : 0), ((v).items + ((v).size++))) #define kv_a(type, v, i) (((v).capacity <= (size_t)(i)? \ ((v).capacity = (v).size = (i) + 1, kv_roundup32((v).capacity), \ diff --git a/src/nvim/main.c b/src/nvim/main.c index d3cdfe3edf..5b5c8a22aa 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -317,14 +317,16 @@ int main(int argc, char **argv) } // open terminals when opening files that start with term:// - do_cmdline_cmd("autocmd BufReadCmd term://* " +#define PROTO "term://" + do_cmdline_cmd("autocmd BufReadCmd " PROTO "* nested " ":call termopen( " // Capture the command string "matchstr(expand(\"<amatch>\"), " - "'\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " + "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " // capture the working directory "{'cwd': get(matchlist(expand(\"<amatch>\"), " - "'\\c\\mterm://\\(.\\{-}\\)//'), 1, '')})"); + "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, '')})"); +#undef PROTO /* Execute --cmd arguments. */ exe_pre_commands(¶ms); @@ -657,6 +659,9 @@ static void init_locale(void) setlocale(LC_NUMERIC, "C"); # endif +# ifdef LOCALE_INSTALL_DIR // gnu/linux standard: $prefix/share/locale + bindtextdomain(PROJECT_NAME, LOCALE_INSTALL_DIR); +# else // old vim style: $runtime/lang { char_u *p; @@ -665,11 +670,12 @@ static void init_locale(void) p = (char_u *)vim_getenv("VIMRUNTIME"); if (p != NULL && *p != NUL) { vim_snprintf((char *)NameBuff, MAXPATHL, "%s/lang", p); - bindtextdomain(VIMPACKAGE, (char *)NameBuff); + bindtextdomain(PROJECT_NAME, (char *)NameBuff); } xfree(p); - textdomain(VIMPACKAGE); } +# endif + textdomain(PROJECT_NAME); TIME_MSG("locale set"); } #endif diff --git a/src/nvim/map.c b/src/nvim/map.c index ed7bda4cce..d4262ae9a8 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -18,6 +18,9 @@ #define uint32_t_eq kh_int_hash_equal #define int_hash kh_int_hash_func #define int_eq kh_int_hash_equal +#define linenr_T_hash kh_int_hash_func +#define linenr_T_eq kh_int_hash_equal + #if defined(ARCH_64) #define ptr_t_hash(key) uint64_t_hash((uint64_t)key) @@ -78,6 +81,25 @@ return rv; \ } \ \ + U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put) \ + { \ + int ret; \ + khiter_t k; \ + if (put) { \ + k = kh_put(T##_##U##_map, map->table, key, &ret); \ + if (ret) { \ + kh_val(map->table, k) = INITIALIZER(T, U); \ + } \ + } else { \ + k = kh_get(T##_##U##_map, map->table, key); \ + if (k == kh_end(map->table)) { \ + return NULL; \ + } \ + } \ + \ + return &kh_val(map->table, k); \ + } \ + \ U map_##T##_##U##_del(Map(T, U) *map, T key) \ { \ U rv = INITIALIZER(T, U); \ @@ -118,3 +140,5 @@ MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) #define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .async = false} MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) +#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } +MAP_IMPL(linenr_T, bufhl_vec_T, KVEC_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index c0e2ca3aac..e90cc360ce 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -6,6 +6,7 @@ #include "nvim/map_defs.h" #include "nvim/api/private/defs.h" #include "nvim/msgpack_rpc/defs.h" +#include "nvim/bufhl_defs.h" #define MAP_DECLS(T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \ @@ -19,6 +20,7 @@ U map_##T##_##U##_get(Map(T, U) *map, T key); \ bool map_##T##_##U##_has(Map(T, U) *map, T key); \ U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \ + U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put); \ U map_##T##_##U##_del(Map(T, U) *map, T key); \ void map_##T##_##U##_clear(Map(T, U) *map); @@ -28,12 +30,14 @@ MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(String, MsgpackRpcRequestHandler) +MAP_DECLS(linenr_T, bufhl_vec_T) #define map_new(T, U) map_##T##_##U##_new #define map_free(T, U) map_##T##_##U##_free #define map_get(T, U) map_##T##_##U##_get #define map_has(T, U) map_##T##_##U##_has #define map_put(T, U) map_##T##_##U##_put +#define map_ref(T, U) map_##T##_##U##_ref #define map_del(T, U) map_##T##_##U##_del #define map_clear(T, U) map_##T##_##U##_clear diff --git a/src/nvim/mark.c b/src/nvim/mark.c index e2f212340c..fe802e48ba 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -922,6 +922,7 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after) } sign_mark_adjust(line1, line2, amount, amount_after); + bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after); } /* previous context mark */ diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index fdd83f9dac..f0a249919f 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -571,8 +571,8 @@ char_u * mb_init(void) #ifdef HAVE_WORKING_LIBINTL /* GNU gettext 0.10.37 supports this feature: set the codeset used for * translated messages independently from the current locale. */ - (void)bind_textdomain_codeset(VIMPACKAGE, - enc_utf8 ? "utf-8" : (char *)p_enc); + (void)bind_textdomain_codeset(PROJECT_NAME, + enc_utf8 ? "utf-8" : (char *)p_enc); #endif diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 6c969a43fc..db303fd54a 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1985,13 +1985,13 @@ changed_lines ( changed_common(lnum, col, lnume, xtra); } -static void -changed_lines_buf ( - buf_T *buf, - linenr_T lnum, /* first line with change */ - linenr_T lnume, /* line below last changed line */ - long xtra /* number of extra lines (negative when deleting) */ -) +/// Mark line range in buffer as changed. +/// +/// @param buf the buffer where lines were changed +/// @param lnum first line with change +/// @param lnume line below last changed line +/// @param xtra number of extra lines (negative when deleting) +void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra) { if (buf->b_mod_set) { /* find the maximum area that must be redisplayed */ diff --git a/src/nvim/msgpack_rpc/defs.h b/src/nvim/msgpack_rpc/defs.h index d97cf28ca1..5611636d4f 100644 --- a/src/nvim/msgpack_rpc/defs.h +++ b/src/nvim/msgpack_rpc/defs.h @@ -1,8 +1,6 @@ #ifndef NVIM_MSGPACK_RPC_DEFS_H #define NVIM_MSGPACK_RPC_DEFS_H -#include <msgpack.h> - /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// functions of this type. @@ -24,22 +22,6 @@ void msgpack_rpc_add_method_handler(String method, 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 -/// to C types, and converting the return value back to msgpack types. -/// The implementation is generated at compile time with metadata extracted -/// from the api/*.h headers, -/// -/// @param channel_id The channel id -/// @param method_id The method id -/// @param req The parsed request object -/// @param error Pointer to error structure -/// @return Some object -Object msgpack_rpc_dispatch(uint64_t channel_id, - msgpack_object *req, - Error *error) - FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3); - MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len) FUNC_ATTR_NONNULL_ARG(1); diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e064d34e09..f5fb47568e 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2344,9 +2344,12 @@ do_mouse ( if (mouse_row == 0 && firstwin->w_winrow > 0) { if (is_drag) { if (in_tab_line) { - tabpage_move(tab_page_click_defs[mouse_col].type == kStlClickTabClose - ? 9999 - : tab_page_click_defs[mouse_col].tabnr - 1); + if (tab_page_click_defs[mouse_col].type == kStlClickTabClose) { + tabpage_move(9999); + } else { + int tabnr = tab_page_click_defs[mouse_col].tabnr; + tabpage_move(tabnr < tabpage_index(curtab) ? tabnr - 1 : tabnr); + } } return false; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 0efce8c4c0..8c7805ba04 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -19,6 +19,7 @@ #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_getln.h" +#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/indent.h" @@ -1410,8 +1411,9 @@ int op_delete(oparg_T *oap) op_yank_reg(oap, false, reg, false); } - if(oap->regname == 0) { + if (oap->regname == 0) { set_clipboard(0, reg); + yank_do_autocmd(oap, reg); } } @@ -2310,6 +2312,8 @@ bool op_yank(oparg_T *oap, bool message) yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK); op_yank_reg(oap, message, reg, is_append_register(oap->regname)); set_clipboard(oap->regname, reg); + yank_do_autocmd(oap, reg); + return true; } @@ -2525,6 +2529,58 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, long y_idx) *pnew = NUL; } +/// Execute autocommands for TextYankPost. +/// +/// @param oap Operator arguments. +/// @param reg The yank register used. +static void yank_do_autocmd(oparg_T *oap, yankreg_T *reg) + FUNC_ATTR_NONNULL_ALL +{ + static bool recursive = false; + + if (recursive || !has_event(EVENT_TEXTYANKPOST)) { + // No autocommand was defined + // or we yanked from this autocommand. + return; + } + + recursive = true; + + // set v:event to a dictionary with information about the yank + dict_T *dict = get_vim_var_dict(VV_EVENT); + + // the yanked text + list_T *list = list_alloc(); + for (linenr_T i = 0; i < reg->y_size; i++) { + list_append_string(list, reg->y_array[i], -1); + } + list->lv_lock = VAR_FIXED; + dict_add_list(dict, "regcontents", list); + + // the register type + char buf[NUMBUFLEN+2]; + format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf)); + dict_add_nr_str(dict, "regtype", 0, (char_u *)buf); + + // name of requested register or the empty string for an unnamed operation. + buf[0] = (char)oap->regname; + buf[1] = NUL; + dict_add_nr_str(dict, "regname", 0, (char_u *)buf); + + // kind of operation (yank/delete/change) + buf[0] = get_op_char(oap->op_type); + buf[1] = NUL; + dict_add_nr_str(dict, "operator", 0, (char_u *)buf); + + dict_set_keys_readonly(dict); + textlock++; + apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf); + textlock--; + dict_clear(dict); + + recursive = false; +} + /* * Put contents of register "regname" into the text. @@ -4632,7 +4688,7 @@ theend: * Used for getregtype() * Returns MAUTO for error. */ -char_u get_reg_type(int regname, long *reglen) +char_u get_reg_type(int regname, colnr_T *reg_width) { switch (regname) { case '%': /* file name */ @@ -4655,13 +4711,45 @@ char_u get_reg_type(int regname, long *reglen) yankreg_T *reg = get_yank_register(regname, YREG_PASTE); if (reg->y_array != NULL) { - if (reglen != NULL && reg->y_type == MBLOCK) - *reglen = reg->y_width; + if (reg_width != NULL && reg->y_type == MBLOCK) { + *reg_width = reg->y_width; + } return reg->y_type; } return MAUTO; } +/// Format the register type as a string. +/// +/// @param reg_type The register type. +/// @param reg_width The width, only used if "reg_type" is MBLOCK. +/// @param[out] buf Buffer to store formatted string. The allocated size should +/// be at least NUMBUFLEN+2 to always fit the value. +/// @param buf_len The allocated size of the buffer. +void format_reg_type(char_u reg_type, colnr_T reg_width, + char* buf, size_t buf_len) + FUNC_ATTR_NONNULL_ALL +{ + assert(buf_len > 1); + switch (reg_type) { + case MLINE: + buf[0] = 'V'; + buf[1] = NUL; + break; + case MCHAR: + buf[0] = 'v'; + buf[1] = NUL; + break; + case MBLOCK: + snprintf(buf, buf_len, CTRL_V_STR "%" PRIdCOLNR, reg_width + 1); + break; + case MAUTO: + buf[0] = NUL; + break; + } +} + + /// When `flags` has `kGRegList` return a list with text `s`. /// Otherwise just return `s`. /// diff --git a/src/nvim/option.c b/src/nvim/option.c index 9aa2b145e8..5efd71444a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2077,7 +2077,7 @@ static void didset_options2(void) (void)highlight_changed(); // Parse default for 'clipboard'. - opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); + (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); // Parse default for 'fillchars'. (void)set_chars_option(&p_fcs); @@ -5947,13 +5947,17 @@ option_value2string ( if (opp->flags & P_NUM) { long wc = 0; - if (wc_use_keyname(varp, &wc)) - STRCPY(NameBuff, get_special_key_name((int)wc, 0)); - else if (wc != 0) - STRCPY(NameBuff, transchar((int)wc)); - else - sprintf((char *)NameBuff, "%" PRId64, (int64_t)*(long *)varp); - } else { /* P_STRING */ + if (wc_use_keyname(varp, &wc)) { + STRLCPY(NameBuff, get_special_key_name((int)wc, 0), sizeof(NameBuff)); + } else if (wc != 0) { + STRLCPY(NameBuff, transchar((int)wc), sizeof(NameBuff)); + } else { + snprintf((char *)NameBuff, + sizeof(NameBuff), + "%" PRId64, + (int64_t)*(long *)varp); + } + } else { // P_STRING varp = *(char_u **)(varp); if (varp == NULL) /* just in case */ NameBuff[0] = NUL; diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index c1804067e9..384a17004e 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -262,8 +262,25 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one, startstr_len = (int)STRLEN(startstr); src = skipwhite(srcp); - --dstlen; // leave one char space for "\," + dstlen--; // leave one char space for "\," while (*src && dstlen > 0) { + // Skip over `=expr`. + if (src[0] == '`' && src[1] == '=') { + var = src; + src += 2; + (void)skip_expr(&src); + if (*src == '`') { + src++; + } + size_t len = (size_t)(src - var); + if (len > (size_t)dstlen) { + len = (size_t)dstlen; + } + memcpy((char *)dst, (char *)var, len); + dst += len; + dstlen -= (int)len; + continue; + } copy_char = true; if ((*src == '$') || (*src == '~' && at_start)) { mustfree = false; @@ -742,15 +759,15 @@ char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET void vim_setenv(const char *name, const char *val) { os_setenv(name, val, 1); - /* - * When setting $VIMRUNTIME adjust the directory to find message - * translations to $VIMRUNTIME/lang. - */ +#ifndef LOCALE_INSTALL_DIR + // When setting $VIMRUNTIME adjust the directory to find message + // translations to $VIMRUNTIME/lang. if (*val != NUL && STRICMP(name, "VIMRUNTIME") == 0) { char *buf = (char *)concat_str((char_u *)val, (char_u *)"/lang"); - bindtextdomain(VIMPACKAGE, buf); + bindtextdomain(PROJECT_NAME, buf); xfree(buf); } +#endif } diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index e632544856..f317fd6b5a 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -250,6 +250,14 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, int col, row, advance; if (sscanf(*ptr, "<%d,%d>%n", &col, &row, &advance) != EOF && advance) { if (col >= 0 && row >= 0) { + // Make sure the mouse position is valid. Some terminals may + // return weird values. + if (col >= Columns) { + col = (int)Columns - 1; + } + if (row >= Rows) { + row = (int)Rows - 1; + } mouse_row = row; mouse_col = col; } diff --git a/src/nvim/path.c b/src/nvim/path.c index e0e5f19911..5cd93ab811 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -556,8 +556,9 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, return 0; } - /* make room for file name */ - buf = xmalloc(STRLEN(path) + BASENAMELEN + 5); + // Make room for file name. When doing encoding conversion the actual + // length may be quite a bit longer, thus use the maximum possible length. + buf = xmalloc(MAXPATHL); /* * Find the first part in the path name that contains a wildcard. @@ -1158,12 +1159,17 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, add_pat = -1; p = pat[i]; - if (vim_backtick(p)) + if (vim_backtick(p)) { add_pat = expand_backtick(&ga, p, flags); - else { - /* - * First expand environment variables, "~/" and "~user/". - */ + if (add_pat == -1) { + recursive = false; + FreeWild(ga.ga_len, (char_u **)ga.ga_data); + *num_file = 0; + *file = NULL; + return FAIL; + } + } else { + // First expand environment variables, "~/" and "~user/". if (has_env_var(p) || *p == '~') { p = expand_env_save_opt(p, true); if (p == NULL) @@ -1246,13 +1252,10 @@ static int vim_backtick(char_u *p) return *p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`'; } -/* - * Expand an item in `backticks` by executing it as a command. - * Currently only works when pat[] starts and ends with a `. - * Returns number of file names found. - */ -static int -expand_backtick ( +// Expand an item in `backticks` by executing it as a command. +// Currently only works when pat[] starts and ends with a `. +// Returns number of file names found, -1 if an error is encountered. +static int expand_backtick( garray_T *gap, char_u *pat, int flags /* EW_* flags */ @@ -1273,8 +1276,9 @@ expand_backtick ( buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL); xfree(cmd); - if (buffer == NULL) - return 0; + if (buffer == NULL) { + return -1; + } cmd = buffer; while (*cmd != NUL) { diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index d1e08db65e..184c4894b9 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -72,8 +72,8 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) install_helper( FILES ${moFile} - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/lang/${name}/LC_MESSAGES - RENAME nvim.mo) + DESTINATION ${CMAKE_INSTALL_LOCALEDIR}/${name}/LC_MESSAGES + RENAME ${PROJECT_NAME}.mo) list(APPEND LANGUAGE_MO_FILES ${moFile}) endmacro() diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 001740943f..5ad621e666 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -545,7 +545,11 @@ static int pum_set_selected(int n, int repeat) g_do_tagpreview = (int)p_pvh; } RedrawingDisabled++; + // Prevent undo sync here, if an autocommand syncs undo weird + // things can happen to the undo tree. + no_u_sync++; resized = prepare_tagpreview(false); + no_u_sync--; RedrawingDisabled--; g_do_tagpreview = 0; @@ -629,7 +633,9 @@ static int pum_set_selected(int n, int repeat) // the window when needed, otherwise it will always be // redraw. if (resized) { + no_u_sync++; win_enter(curwin_save, true); + no_u_sync--; update_topline(); } @@ -640,7 +646,9 @@ static int pum_set_selected(int n, int repeat) pum_do_redraw = FALSE; if (!resized && win_valid(curwin_save)) { + no_u_sync++; win_enter(curwin_save, true); + no_u_sync--; } // May need to update the screen again when there are diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 7071df51e8..864f3fe866 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -2,7 +2,11 @@ #define NVIM_POS_H typedef long linenr_T; // line number type -typedef int colnr_T; // column number type + +/// Column number type +typedef int colnr_T; +/// Format used to print values which have colnr_T type +#define PRIdCOLNR "d" #define MAXLNUM 0x7fffffff // maximum (invalid) line number #define MAXCOL 0x7fffffff // maximum column number, 31 bits diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f3abf864fb..4a8391430b 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2299,13 +2299,15 @@ static void qf_fill_buffer(qf_info_T *qi) if (qfp->qf_fnum != 0 && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL && errbuf->b_fname != NULL) { - if (qfp->qf_type == 1) /* :helpgrep */ - STRCPY(IObuff, path_tail(errbuf->b_fname)); - else - STRCPY(IObuff, errbuf->b_fname); + if (qfp->qf_type == 1) { // :helpgrep + STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff)); + } else { + STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); + } len = (int)STRLEN(IObuff); - } else + } else { len = 0; + } IObuff[len++] = '|'; if (qfp->qf_lnum > 0) { diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index dd41535110..e2849fb17a 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -6180,7 +6180,8 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col) if (prog->reghasz == REX_SET) { cleanup_zsubexpr(); re_extmatch_out = make_extmatch(); - for (i = 0; i < subs.synt.in_use; i++) { + // Loop over \z1, \z2, etc. There is no \z0. + for (i = 1; i < subs.synt.in_use; i++) { if (REG_MULTI) { struct multipos *mpos = &subs.synt.list.multi[i]; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 382fcbac25..1cc270023c 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2184,6 +2184,10 @@ win_line ( int prev_c1 = 0; /* first composing char for prev_c */ int did_line_attr = 0; + bool has_bufhl = false; // this buffer has highlight matches + int bufhl_attr = 0; // attributes desired by bufhl + bufhl_lineinfo_T bufhl_info; // bufhl data for this line + /* draw_state: items that are drawn in sequence: */ #define WL_START 0 /* nothing done yet */ # define WL_CMDLINE WL_START + 1 /* cmdline window column */ @@ -2244,6 +2248,11 @@ win_line ( } } + if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) { + has_bufhl = true; + extra_check = true; + } + /* Check for columns to display for 'colorcolumn'. */ color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; if (color_cols != NULL) @@ -3335,6 +3344,17 @@ win_line ( char_attr = hl_combine_attr(spell_attr, char_attr); } + if (has_bufhl && v > 0) { + bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v); + if (bufhl_attr != 0) { + if (!attr_pri) { + char_attr = hl_combine_attr(char_attr, bufhl_attr); + } else { + char_attr = hl_combine_attr(bufhl_attr, char_attr); + } + } + } + if (wp->w_buffer->terminal) { char_attr = hl_combine_attr(char_attr, term_attrs[vcol]); } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index b7d8a19de9..478fa973a1 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3294,7 +3294,7 @@ static void syn_cmd_onoff(exarg_T *eap, char *name) eap->nextcmd = check_nextcmd(eap->arg); if (!eap->skip) { char buf[100]; - strncpy(buf, "so ", 3); + strncpy(buf, "so ", 4); vim_snprintf(buf + 3, sizeof(buf) - 3, SYNTAX_FNAME, name); do_cmdline_cmd(buf); } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 0a7807d811..42fd81f643 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -141,10 +141,6 @@ struct terminal { int pressed_button; // pending width/height bool pending_resize; - // color palette. this isn't set directly in the vterm instance because - // the default values are used to obtain the color numbers passed to cterm - // colors - RgbValue colors[256]; // With a reference count of 0 the terminal can be freed. size_t refcount; }; @@ -205,6 +201,7 @@ void terminal_teardown(void) Terminal *terminal_open(TerminalOptions opts) { + bool true_color = ui_rgb_attached(); // Create a new terminal instance and configure it Terminal *rv = xcalloc(1, sizeof(Terminal)); rv->opts = opts; @@ -220,7 +217,7 @@ Terminal *terminal_open(TerminalOptions opts) // Set up screen rv->vts = vterm_obtain_screen(rv->vt); vterm_screen_enable_altscreen(rv->vts, true); - // delete empty lines at the end of the buffer + // delete empty lines at the end of the buffer vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv); vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL); vterm_screen_reset(rv->vts, 1); @@ -236,7 +233,7 @@ Terminal *terminal_open(TerminalOptions opts) set_option_value((uint8_t *)"relativenumber", false, NULL, OPT_LOCAL); RESET_BINDING(curwin); // Apply TermOpen autocmds so the user can configure the terminal - apply_autocmds(EVENT_TERMOPEN, NULL, NULL, true, curbuf); + apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); // Configure the scrollback buffer. Try to get the size from: // @@ -250,12 +247,18 @@ Terminal *terminal_open(TerminalOptions opts) rv->sb_size = MIN(rv->sb_size, 100000); rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); + if (!true_color) { + return rv; + } + + vterm_state_set_bold_highbright(state, true); + // Configure the color palette. Try to get the color from: // // - b:terminal_color_{NUM} // - g:terminal_color_{NUM} // - the VTerm instance - for (int i = 0; i < (int)ARRAY_SIZE(rv->colors); i++) { + for (int i = 0; i < 16; i++) { RgbValue color_val = -1; char var[64]; snprintf(var, sizeof(var), "terminal_color_%d", i); @@ -265,16 +268,13 @@ Terminal *terminal_open(TerminalOptions opts) xfree(name); if (color_val != -1) { - rv->colors[i] = color_val; + VTermColor color; + color.red = (uint8_t)((color_val >> 16) & 0xFF); + color.green = (uint8_t)((color_val >> 8) & 0xFF); + color.blue = (uint8_t)((color_val >> 0) & 0xFF); + vterm_state_set_palette_color(state, i, &color); } } - - if (color_val == -1) { - // the default is taken from vterm - VTermColor color; - vterm_state_get_palette_color(state, i, &color); - rv->colors[i] = RGB(color.red, color.green, color.blue); - } } return rv; @@ -288,8 +288,9 @@ void terminal_close(Terminal *term, char *msg) term->forward_mouse = false; term->closed = true; + buf_T *buf = handle_get_buffer(term->buf_handle); + if (!msg || exiting) { - buf_T *buf = handle_get_buffer(term->buf_handle); // If no msg was given, this was called by close_buffer(buffer.c). Or if // exiting, we must inform the buffer the terminal no longer exists so that // close_buffer() doesn't call this again. @@ -304,6 +305,10 @@ void terminal_close(Terminal *term, char *msg) } else { terminal_receive(term, msg, strlen(msg)); } + + if (buf) { + apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf); + } } void terminal_resize(Terminal *term, uint16_t width, uint16_t height) @@ -543,10 +548,6 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, map_get(int, int)(color_indexes, vt_fg) : 0; int vt_bg_idx = vt_bg != default_vt_bg ? map_get(int, int)(color_indexes, vt_bg) : 0; - // The index is now used to get the final rgb value from the - // user-customizable palette. - int vt_fg_rgb = vt_fg_idx != 0 ? term->colors[vt_fg_idx - 1] : -1; - int vt_bg_rgb = vt_bg_idx != 0 ? term->colors[vt_bg_idx - 1] : -1; int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0) | (cell.attrs.italic ? HL_ITALIC : 0) @@ -561,8 +562,8 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, .cterm_fg_color = vt_fg_idx, .cterm_bg_color = vt_bg_idx, .rgb_ae_attr = (int16_t)hl_attrs, - .rgb_fg_color = vt_fg_rgb, - .rgb_bg_color = vt_bg_rgb, + .rgb_fg_color = vt_fg, + .rgb_bg_color = vt_bg, }); } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index f077414e18..63ca4cf6c4 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -7,28 +7,37 @@ export SHELL := sh VIMPROG := ../../../build/bin/nvim SCRIPTSOURCE := ../../../runtime -SCRIPTS := \ - test8.out test10.out \ - test11.out test12.out test13.out test14.out \ - test17.out \ - test24.out \ - test30.out \ - test32.out test34.out \ - test36.out test37.out test40.out \ - test42.out \ - test47.out test48.out test49.out \ - test52.out test53.out test55.out \ - test64.out \ - test68.out test69.out \ - test73.out \ - test79.out \ - test88.out \ - test_listlbr.out \ - test_breakindent.out \ - test_charsearch.out \ - test_close_count.out \ - test_marks.out \ - test_match_conceal.out \ +SCRIPTS := \ + test8.out \ + test10.out \ + test12.out \ + test13.out \ + test14.out \ + test17.out \ + test24.out \ + test30.out \ + test32.out \ + test34.out \ + test37.out \ + test40.out \ + test42.out \ + test47.out \ + test48.out \ + test49.out \ + test52.out \ + test53.out \ + test55.out \ + test64.out \ + test68.out \ + test69.out \ + test73.out \ + test79.out \ + test88.out \ + test_listlbr.out \ + test_breakindent.out \ + test_close_count.out \ + test_marks.out \ + test_match_conceal.out \ NEW_TESTS = diff --git a/src/nvim/testdir/test11.in b/src/nvim/testdir/test11.in deleted file mode 100644 index 9e9e257c1d..0000000000 --- a/src/nvim/testdir/test11.in +++ /dev/null @@ -1,84 +0,0 @@ -Tests for autocommands: -- FileWritePre writing a compressed file -- FileReadPost reading a compressed file -- BufNewFile reading a file template -- BufReadPre decompressing the file to be read -- FilterReadPre substituting characters in the temp file -- FilterReadPost substituting characters after filtering -- FileReadPre set options for decompression -- FileReadPost decompress the file - -Note: This test is skipped if "gzip" is not available. -$GZIP is made empty, "-v" would cause trouble. -Use a FileChangedShell autocommand to avoid a prompt for "Xtestfile.gz" being -modified outside of Vim (noticed on Solaris). - -STARTTEST -:so small.vim -:" drop out when there is no gzip program -:if !executable("gzip") -: e! test.ok -: w! test.out -: qa! -:endif -:let $GZIP = "" -:au FileChangedShell * echo "caught FileChangedShell" -:set bin -:au FileWritePre *.gz '[,']!gzip -:au FileWritePost *.gz undo -:/^start of testfile/,/^end of testfile/w! Xtestfile.gz -:au FileReadPost *.gz '[,']!gzip -d -:$r Xtestfile.gz " Read and decompress the testfile -:?startstart?,$w! test.out " Write contents of this file -:au BufNewFile *.c read Xtest.c -:/^start of test.c/+1,/^end of test.c/-1w! Xtest.c -:e! foo.c " Will load Xtest.c -:au FileAppendPre *.out '[,']s/new/NEW/ -:au FileAppendPost *.out !cat Xtest.c >>test.out -:w>>test.out " Append it to the output file -:au! FileAppendPre -:" setup autocommands to decompress before reading and re-compress afterwards -:au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand("<afile>")) -:au BufReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>")) -:au BufReadPost *.gz call rename(expand("<afile>"), expand("<afile>:r")) -:au BufReadPost *.gz exe '!gzip ' . shellescape(expand("<afile>:r")) -:e! Xtestfile.gz " Edit compressed file -:w>>test.out " Append it to the output file -:set shelltemp " need temp files here -:au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t") -:au FilterReadPre *.out exe 'silent !sed s/e/E/ ' . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>")) -:au FilterReadPre *.out exe 'silent !rm ' . shellescape(expand("<afile>")) . '.t' -:au FilterReadPost *.out '[,']s/x/X/g -:e! test.out " Edit the output file -:23,$!cat -:23,$s/\r$// " remove CR for when sed adds them -:au! FileReadPre *.gz exe 'silent !gzip -d ' . shellescape(expand("<afile>")) -:au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>")) -:au! FileReadPost *.gz '[,']s/l/L/ -:$r Xtestfile.gz " Read compressed file -:w " write it, after filtering -:au! " remove all autocommands -:e " Edit test.out again -:set nobin ff& " use the default fileformat for writing -:w -:qa! -ENDTEST - -startstart -start of testfile -line 2 Abcdefghijklmnopqrstuvwxyz -line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 4 Abcdefghijklmnopqrstuvwxyz -line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 6 Abcdefghijklmnopqrstuvwxyz -line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 8 Abcdefghijklmnopqrstuvwxyz -line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 10 Abcdefghijklmnopqrstuvwxyz -end of testfile - -start of test.c -/* - * Here is a new .c file - */ -end of test.c diff --git a/src/nvim/testdir/test11.ok b/src/nvim/testdir/test11.ok deleted file mode 100644 index af8c5ce261..0000000000 --- a/src/nvim/testdir/test11.ok +++ /dev/null @@ -1,61 +0,0 @@ -startstart -start of testfile -line 2 Abcdefghijklmnopqrstuvwxyz -line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 4 Abcdefghijklmnopqrstuvwxyz -line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 6 Abcdefghijklmnopqrstuvwxyz -line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 8 Abcdefghijklmnopqrstuvwxyz -line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 10 Abcdefghijklmnopqrstuvwxyz -end of testfile - -start of test.c -/* - * Here is a new .c file - */ -end of test.c -start of testfile -line 2 Abcdefghijklmnopqrstuvwxyz -line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -line 4 Abcdefghijklmnopqrstuvwxyz -linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -linE 6 AbcdefghijklmnopqrstuvwXyz -linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -linE 8 AbcdefghijklmnopqrstuvwXyz -linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -linE 10 AbcdefghijklmnopqrstuvwXyz -End of testfile - -/* - * HEre is a NEW .c file - */ -/* - * HEre is a new .c file - */ -start of tEstfile -linE 2 AbcdefghijklmnopqrstuvwXyz -linE 3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -linE 4 AbcdefghijklmnopqrstuvwXyz -linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -linE 6 AbcdefghijklmnopqrstuvwXyz -linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -linE 8 AbcdefghijklmnopqrstuvwXyz -linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -linE 10 AbcdefghijklmnopqrstuvwXyz -End of testfile -/* - * HEre is a new .c file - */ -start of testfiLe -Line 2 Abcdefghijklmnopqrstuvwxyz -Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -Line 4 Abcdefghijklmnopqrstuvwxyz -Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -Line 6 Abcdefghijklmnopqrstuvwxyz -Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -Line 8 Abcdefghijklmnopqrstuvwxyz -Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -Line 10 Abcdefghijklmnopqrstuvwxyz -end of testfiLe diff --git a/src/nvim/testdir/test36.in b/src/nvim/testdir/test36.in deleted file mode 100644 index 8cdb5262bd..0000000000 --- a/src/nvim/testdir/test36.in +++ /dev/null @@ -1,105 +0,0 @@ -Test character classes in regexp using regexpengine 0, 1, 2. - -STARTTEST -/^start-here/+1 -Y:s/\%#=0\d//g -p:s/\%#=1\d//g -p:s/\%#=2\d//g -p:s/\%#=0[0-9]//g -p:s/\%#=1[0-9]//g -p:s/\%#=2[0-9]//g -p:s/\%#=0\D//g -p:s/\%#=1\D//g -p:s/\%#=2\D//g -p:s/\%#=0[^0-9]//g -p:s/\%#=1[^0-9]//g -p:s/\%#=2[^0-9]//g -p:s/\%#=0\o//g -p:s/\%#=1\o//g -p:s/\%#=2\o//g -p:s/\%#=0[0-7]//g -p:s/\%#=1[0-7]//g -p:s/\%#=2[0-7]//g -p:s/\%#=0\O//g -p:s/\%#=1\O//g -p:s/\%#=2\O//g -p:s/\%#=0[^0-7]//g -p:s/\%#=1[^0-7]//g -p:s/\%#=2[^0-7]//g -p:s/\%#=0\x//g -p:s/\%#=1\x//g -p:s/\%#=2\x//g -p:s/\%#=0[0-9A-Fa-f]//g -p:s/\%#=1[0-9A-Fa-f]//g -p:s/\%#=2[0-9A-Fa-f]//g -p:s/\%#=0\X//g -p:s/\%#=1\X//g -p:s/\%#=2\X//g -p:s/\%#=0[^0-9A-Fa-f]//g -p:s/\%#=1[^0-9A-Fa-f]//g -p:s/\%#=2[^0-9A-Fa-f]//g -p:s/\%#=0\w//g -p:s/\%#=1\w//g -p:s/\%#=2\w//g -p:s/\%#=0[0-9A-Za-z_]//g -p:s/\%#=1[0-9A-Za-z_]//g -p:s/\%#=2[0-9A-Za-z_]//g -p:s/\%#=0\W//g -p:s/\%#=1\W//g -p:s/\%#=2\W//g -p:s/\%#=0[^0-9A-Za-z_]//g -p:s/\%#=1[^0-9A-Za-z_]//g -p:s/\%#=2[^0-9A-Za-z_]//g -p:s/\%#=0\h//g -p:s/\%#=1\h//g -p:s/\%#=2\h//g -p:s/\%#=0[A-Za-z_]//g -p:s/\%#=1[A-Za-z_]//g -p:s/\%#=2[A-Za-z_]//g -p:s/\%#=0\H//g -p:s/\%#=1\H//g -p:s/\%#=2\H//g -p:s/\%#=0[^A-Za-z_]//g -p:s/\%#=1[^A-Za-z_]//g -p:s/\%#=2[^A-Za-z_]//g -p:s/\%#=0\a//g -p:s/\%#=1\a//g -p:s/\%#=2\a//g -p:s/\%#=0[A-Za-z]//g -p:s/\%#=1[A-Za-z]//g -p:s/\%#=2[A-Za-z]//g -p:s/\%#=0\A//g -p:s/\%#=1\A//g -p:s/\%#=2\A//g -p:s/\%#=0[^A-Za-z]//g -p:s/\%#=1[^A-Za-z]//g -p:s/\%#=2[^A-Za-z]//g -p:s/\%#=0\l//g -p:s/\%#=1\l//g -p:s/\%#=2\l//g -p:s/\%#=0[a-z]//g -p:s/\%#=1[a-z]//g -p:s/\%#=2[a-z]//g -p:s/\%#=0\L//g -p:s/\%#=1\L//g -p:s/\%#=2\L//g -p:s/\%#=0[^a-z]//g -p:s/\%#=1[^a-z]//g -p:s/\%#=2[^a-z]//g -p:s/\%#=0\u//g -p:s/\%#=1\u//g -p:s/\%#=2\u//g -p:s/\%#=0[A-Z]//g -p:s/\%#=1[A-Z]//g -p:s/\%#=2[A-Z]//g -p:s/\%#=0\U//g -p:s/\%#=1\U//g -p:s/\%#=2\U//g -p:s/\%#=0[^A-Z]//g -p:s/\%#=1[^A-Z]//g -p:s/\%#=2[^A-Z]//g -:/^start-here/+1,$wq! test.out -ENDTEST - -start-here -
!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ diff --git a/src/nvim/testdir/test36.ok b/src/nvim/testdir/test36.ok deleted file mode 100644 index f72a74b2b7..0000000000 --- a/src/nvim/testdir/test36.ok +++ /dev/null @@ -1,96 +0,0 @@ -
!"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -0123456789 -0123456789 -0123456789 -0123456789 -0123456789 -0123456789 -
!"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~ -01234567 -01234567 -01234567 -01234567 -01234567 -01234567 -
!"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~ -
!"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~ -0123456789ABCDEFabcdef -0123456789ABCDEFabcdef -0123456789ABCDEFabcdef -0123456789ABCDEFabcdef -0123456789ABCDEFabcdef -0123456789ABCDEFabcdef -
!"#$%&'()#+'-./:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./:;<=>?@[\]^`{|}~ -0123456789ABCDEFGHIXYZ_abcdefghiwxyz -0123456789ABCDEFGHIXYZ_abcdefghiwxyz -0123456789ABCDEFGHIXYZ_abcdefghiwxyz -0123456789ABCDEFGHIXYZ_abcdefghiwxyz -0123456789ABCDEFGHIXYZ_abcdefghiwxyz -0123456789ABCDEFGHIXYZ_abcdefghiwxyz -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~ -ABCDEFGHIXYZ_abcdefghiwxyz -ABCDEFGHIXYZ_abcdefghiwxyz -ABCDEFGHIXYZ_abcdefghiwxyz -ABCDEFGHIXYZ_abcdefghiwxyz -ABCDEFGHIXYZ_abcdefghiwxyz -ABCDEFGHIXYZ_abcdefghiwxyz -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~ -ABCDEFGHIXYZabcdefghiwxyz -ABCDEFGHIXYZabcdefghiwxyz -ABCDEFGHIXYZabcdefghiwxyz -ABCDEFGHIXYZabcdefghiwxyz -ABCDEFGHIXYZabcdefghiwxyz -ABCDEFGHIXYZabcdefghiwxyz -
!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~ -abcdefghiwxyz -abcdefghiwxyz -abcdefghiwxyz -abcdefghiwxyz -abcdefghiwxyz -abcdefghiwxyz -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~ -
!"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~ -ABCDEFGHIXYZ -ABCDEFGHIXYZ -ABCDEFGHIXYZ -ABCDEFGHIXYZ -ABCDEFGHIXYZ -ABCDEFGHIXYZ diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index afee9d882c..70d388b06a 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@ " Vim script language tests " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2013 Jun 06 +" Last Change: 2015 Sep 25 "------------------------------------------------------------------------------- " Test environment {{{1 @@ -5188,19 +5188,19 @@ catch /.*/ Xpath 65536 " X: 65536 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(1, "oops", '\<F\.\.G\.\.T\>', '\<2\>') + call CHECK(1, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') exec "let exception = v:exception" exec "let throwpoint = v:throwpoint" - call CHECK(2, "oops", '\<F\.\.G\.\.T\>', '\<2\>') + call CHECK(2, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') CmdException CmdThrowpoint - call CHECK(3, "oops", '\<F\.\.G\.\.T\>', '\<2\>') + call CHECK(3, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') call FuncException() call FuncThrowpoint() - call CHECK(4, "oops", '\<F\.\.G\.\.T\>', '\<2\>') + call CHECK(4, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') exec "source" scriptException exec "source" scriptThrowPoint - call CHECK(5, "oops", '\<F\.\.G\.\.T\>', '\<2\>') + call CHECK(5, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') try Xpath 131072 " X: 131072 call G("arrgh", 4) @@ -5208,7 +5208,7 @@ catch /.*/ Xpath 262144 " X: 262144 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(6, "arrgh", '\<G\.\.T\>', '\<4\>') + call CHECK(6, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') try Xpath 524288 " X: 524288 let g:arg = "autsch" @@ -5226,7 +5226,7 @@ catch /.*/ Xpath 2097152 " X: 2097152 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(8, "arrgh", '\<G\.\.T\>', '\<4\>') + call CHECK(8, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') try Xpath 4194304 " X: 4194304 let g:arg = "brrrr" @@ -5242,27 +5242,27 @@ catch /.*/ Xpath 16777216 " X: 16777216 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(10, "arrgh", '\<G\.\.T\>', '\<4\>') + call CHECK(10, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') endtry Xpath 33554432 " X: 33554432 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(11, "arrgh", '\<G\.\.T\>', '\<4\>') + call CHECK(11, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') endtry Xpath 67108864 " X: 67108864 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(12, "arrgh", '\<G\.\.T\>', '\<4\>') + call CHECK(12, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') finally Xpath 134217728 " X: 134217728 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(13, "oops", '\<F\.\.G\.\.T\>', '\<2\>') + call CHECK(13, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') endtry Xpath 268435456 " X: 268435456 let exception = v:exception let throwpoint = v:throwpoint - call CHECK(14, "oops", '\<F\.\.G\.\.T\>', '\<2\>') + call CHECK(14, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') finally Xpath 536870912 " X: 536870912 let exception = v:exception diff --git a/src/nvim/testdir/test_charsearch.in b/src/nvim/testdir/test_charsearch.in deleted file mode 100644 index 5085cb39bc..0000000000 --- a/src/nvim/testdir/test_charsearch.in +++ /dev/null @@ -1,25 +0,0 @@ -Test for character searches - -STARTTEST -:so small.vim -:" check that "fe" and ";" work -/^X -ylfep;;p,,p: -:" check that save/restore works -/^Y -ylfep:let csave = getcharsearch() -fip:call setcharsearch(csave) -;p;p: -:" check that setcharsearch() changes the settins. -/^Z -ylfep:call setcharsearch({'char': 'k'}) -;p:call setcharsearch({'forward': 0}) -$;p:call setcharseearch({'until'}: 1}) -;;p: -:/^X/,$w! test.out -:qa! -ENDTEST - -Xabcdefghijkemnopqretuvwxyz -Yabcdefghijkemnopqretuvwxyz -Zabcdefghijkemnokqretkvwxyz diff --git a/src/nvim/testdir/test_charsearch.ok b/src/nvim/testdir/test_charsearch.ok deleted file mode 100644 index a0c90e24f9..0000000000 --- a/src/nvim/testdir/test_charsearch.ok +++ /dev/null @@ -1,3 +0,0 @@ -XabcdeXfghijkeXmnopqreXtuvwxyz -YabcdeYfghiYjkeYmnopqreYtuvwxyz -ZabcdeZfghijkZemnokZqretkZvwxyz diff --git a/src/nvim/undo.c b/src/nvim/undo.c index b8cdffcda0..4a8a24d79d 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -4,29 +4,29 @@ * The saved lines are stored in a list of lists (one for each buffer): * * b_u_oldhead------------------------------------------------+ - * | - * V - * +--------------+ +--------------+ +--------------+ - * b_u_newhead--->| u_header | | u_header | | u_header | - * | uh_next------>| uh_next------>| uh_next---->NULL - * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | - * | uh_entry | | uh_entry | | uh_entry | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ +--------------+ +--------------+ - * | u_entry | | u_entry | | u_entry | - * | ue_next | | ue_next | | ue_next | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ NULL NULL - * | u_entry | - * | ue_next | - * +--------|-----+ - * | - * V - * etc. + * | + * V + * +--------------+ +--------------+ +--------------+ + * b_u_newhead--->| u_header | | u_header | | u_header | + * | uh_next------>| uh_next------>| uh_next---->NULL + * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | + * | uh_entry | | uh_entry | | uh_entry | + * +--------|-----+ +--------|-----+ +--------|-----+ + * | | | + * V V V + * +--------------+ +--------------+ +--------------+ + * | u_entry | | u_entry | | u_entry | + * | ue_next | | ue_next | | ue_next | + * +--------|-----+ +--------|-----+ +--------|-----+ + * | | | + * V V V + * +--------------+ NULL NULL + * | u_entry | + * | ue_next | + * +--------|-----+ + * | + * V + * etc. * * Each u_entry list contains the information for one undo or redo. * curbuf->b_u_curhead points to the header of the last undo (the next redo), @@ -37,30 +37,30 @@ * uh_seq field is numbered sequentially to be able to find a newer or older * branch. * - * +---------------+ +---------------+ - * b_u_oldhead --->| u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next ----> NULL - * NULL <----- uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next | | uh_alt_next | - * b_u_newhead --->| uh_alt_prev | | uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * NULL +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next | - * | uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * etc. etc. + * +---------------+ +---------------+ + * b_u_oldhead --->| u_header | | u_header | + * | uh_alt_next ---->| uh_alt_next ----> NULL + * NULL <----- uh_alt_prev |<------ uh_alt_prev | + * | uh_prev | | uh_prev | + * +-----|---------+ +-----|---------+ + * | | + * V V + * +---------------+ +---------------+ + * | u_header | | u_header | + * | uh_alt_next | | uh_alt_next | + * b_u_newhead --->| uh_alt_prev | | uh_alt_prev | + * | uh_prev | | uh_prev | + * +-----|---------+ +-----|---------+ + * | | + * V V + * NULL +---------------+ +---------------+ + * | u_header | | u_header | + * | uh_alt_next ---->| uh_alt_next | + * | uh_alt_prev |<------ uh_alt_prev | + * | uh_prev | | uh_prev | + * +-----|---------+ +-----|---------+ + * | | + * etc. etc. * * * All data is allocated and will all be freed when the buffer is unloaded. diff --git a/src/nvim/version.c b/src/nvim/version.c index 874d10a44a..552da03739 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -69,6 +69,8 @@ static char *features[] = { // clang-format off static int included_patches[] = { + 1366, + // 1219 NA // 1218 NA // 1217 NA @@ -176,7 +178,7 @@ static int included_patches[] = { // 1115 NA // 1114, // 1113, - // 1112, + 1112, // 1111, // 1110, // 1109 NA @@ -247,7 +249,7 @@ static int included_patches[] = { // 1044 NA, // 1043 NA, // 1042, - // 1041, + 1041, // 1040 NA, // 1039, // 1038 NA, @@ -295,7 +297,7 @@ static int included_patches[] = { // 996 NA // 995 NA // 994 NA - // 993, + // 993 NA // 992 NA 991, // 990 NA @@ -314,12 +316,12 @@ static int included_patches[] = { 977, // 976 NA 975, - // 974, + 974, // 973, 972, // 971 NA // 970 NA - // 969, + // 969 NA // 968 NA // 967 NA // 966 NA @@ -332,7 +334,7 @@ static int included_patches[] = { // 959 NA // 958, // 957, - // 956, + // 956 NA 955, // 954 NA 953, @@ -341,32 +343,32 @@ static int included_patches[] = { 950, 949, // 948 NA - // 947, + // 947 NA 946, 945, 944, - // 943, + // 943 NA // 942, // 941, // 940 NA - // 939, + 939, // 938 NA - // 937, - // 936, - // 935, + 937, + 936, + // 935 NA // 934 NA - // 933, - // 932, + 933, + 932, // 931 NA // 930 NA 929, // 928 NA // 927 NA - // 926, + 926, 925, // 924 NA // 923 NA - // 922, + 922, // 921 NA // 920 NA // 919 NA @@ -374,43 +376,43 @@ static int included_patches[] = { // 917 NA 916, 915, - // 914, + // 914 NA // 913 NA - // 912, + 912, // 911 NA // 910 NA - // 909, + // 909 NA // 908 NA // 907 NA // 906 NA - // 905, - // 904, - // 903, + // 905 NA + // 904 NA + 903, // 902 NA - // 901, + 901, // 900 NA // 899 NA 898, // 897 NA // 896, - // 895, + 895, // 894 NA - // 893, - // 892, - // 891, + 893, + // 892 NA + 891, // 890 NA // 889, - // 888, + 888, 887, // 886 NA 885, // 884 NA 883, // 882, - // 881, + 881, // 880 NA - // 879, - // 878, + 879, + 878, 877, // 876 NA // 875 NA @@ -418,7 +420,7 @@ static int included_patches[] = { // 873 NA // 872 NA // 871, - // 870, + 870, // 869 NA 868, // 867 NA @@ -431,7 +433,7 @@ static int included_patches[] = { // 860 NA 859, 858, - // 857, + 857, 856, // 855 NA // 854 NA @@ -443,7 +445,7 @@ static int included_patches[] = { 848, 847, // 846 NA - // 845, + 845, 844, 843, // 842 NA @@ -456,8 +458,8 @@ static int included_patches[] = { 835, 834, 833, - // 832, - // 831, + 832, + 831, 830, // 829 NA 828, diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 762d349470..545b903d2f 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -27,12 +27,6 @@ Error: configure did not run properly.Check auto/config.log. # endif #endif - -/* Can't use "PACKAGE" here, conflicts with a Perl include file. */ -#ifndef VIMPACKAGE -# define VIMPACKAGE "nvim" -#endif - #include "nvim/os/os_defs.h" /* bring lots of system header files */ /// length of a buffer to store a number in ASCII (64 bits binary + NUL) diff --git a/src/nvim/window.c b/src/nvim/window.c index 36cb48f3a1..b79aa366e5 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -248,7 +248,7 @@ newwindow: /* First create a new tab with the window, then go back to * the old tab and close the window there. */ wp = curwin; - if (win_new_tabpage((int)Prenum) == OK + if (win_new_tabpage((int)Prenum, NULL) == OK && valid_tabpage(oldtab)) { newtab = curtab; goto_tabpage_tp(oldtab, TRUE, TRUE); @@ -2952,14 +2952,15 @@ void free_tabpage(tabpage_T *tp) xfree(tp); } -/* - * Create a new Tab page with one window. - * It will edit the current buffer, like after ":split". - * When "after" is 0 put it just after the current Tab page. - * Otherwise put it just before tab page "after". - * Return FAIL or OK. - */ -int win_new_tabpage(int after) +/// Create a new tabpage with one window. +/// +/// It will edit the current buffer, like after :split. +/// +/// @param after Put new tabpage after tabpage "after", or after the current +/// tabpage in case of 0. +/// @param filename Will be passed to apply_autocmds(). +/// @return Was the new tabpage created successfully? FAIL or OK. +int win_new_tabpage(int after, char_u *filename) { tabpage_T *tp = curtab; tabpage_T *newtp; @@ -2999,10 +3000,12 @@ int win_new_tabpage(int after) newtp->tp_topframe = topframe; last_status(FALSE); - redraw_all_later(CLEAR); - apply_autocmds(EVENT_WINENTER, NULL, NULL, FALSE, curbuf); - apply_autocmds(EVENT_TABENTER, NULL, NULL, FALSE, curbuf); + + apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); + apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); + return OK; } @@ -3023,7 +3026,7 @@ int may_open_tabpage(void) if (n != 0) { cmdmod.tab = 0; /* reset it to avoid doing it twice */ postponed_split_tab = 0; - return win_new_tabpage(n); + return win_new_tabpage(n, NULL); } return FAIL; } @@ -3047,9 +3050,11 @@ int make_tabpages(int maxcount) */ block_autocmds(); - for (todo = count - 1; todo > 0; --todo) - if (win_new_tabpage(0) == FAIL) + for (todo = count - 1; todo > 0; --todo) { + if (win_new_tabpage(0, NULL) == FAIL) { break; + } + } unblock_autocmds(); @@ -4575,10 +4580,19 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } assert(fr); - if (room < offset) /* Not enough room */ - offset = room; /* Move as far as we can */ - if (offset <= 0) /* No room at all, quit. */ + // Not enough room + if (room < offset) { + offset = room; // Move as far as we can + } + + // No room at all, quit. + if (offset <= 0) { return; + } + + if (fr == NULL) { + return; // Safety check, should not happen. + } /* grow frame fr by offset lines */ frame_new_width(fr, fr->fr_width + offset, left, FALSE); diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index f4a9ddc698..9c9759adf6 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -144,15 +144,23 @@ describe('vim_* functions', function() describe('replace_termcodes', function() it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function() - eq(helpers.nvim('replace_termcodes', '\128', true, true, true), '\128\254X') + eq('\128\254X', helpers.nvim('replace_termcodes', '\128', true, true, true)) end) - it('leaves non K_SPECIAL string unchanged', function() - eq(helpers.nvim('replace_termcodes', 'abc', true, true, true), 'abc') + it('leaves non-K_SPECIAL string unchanged', function() + eq('abc', helpers.nvim('replace_termcodes', 'abc', true, true, true)) end) it('converts <expressions>', function() - eq(helpers.nvim('replace_termcodes', '<Leader>', true, true, true), '\\') + eq('\\', helpers.nvim('replace_termcodes', '<Leader>', true, true, true)) + end) + + it('converts <LeftMouse> to K_SPECIAL KS_EXTRA KE_LEFTMOUSE', function() + -- K_SPECIAL KS_EXTRA KE_LEFTMOUSE + -- 0x80 0xfd 0x2c + -- 128 253 44 + eq('\128\253\44', helpers.nvim('replace_termcodes', + '<LeftMouse>', true, true, true)) end) end) diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua index 0961340e61..4de3f039c1 100644 --- a/test/functional/autocmd/termclose_spec.lua +++ b/test/functional/autocmd/termclose_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, execute, feed, nvim, nvim_dir = helpers.clear, helpers.execute, helpers.feed, helpers.nvim, helpers.nvim_dir +local eval, eq = helpers.eval, helpers.eq describe('TermClose event', function() local screen @@ -25,4 +26,19 @@ describe('TermClose event', function() TermClose works! | ]]) end) + + it('reports the correct <abuf>', function() + execute('set hidden') + execute('autocmd TermClose * let g:abuf = expand("<abuf>")') + execute('edit foo') + execute('edit bar') + eq(2, eval('bufnr("%")')) + execute('terminal') + feed('<c-\\><c-n>') + eq(3, eval('bufnr("%")')) + execute('buffer 1') + eq(1, eval('bufnr("%")')) + execute('3bdelete!') + eq('3', eval('g:abuf')) + end) end) diff --git a/test/functional/autocmd/textyankpost_spec.lua b/test/functional/autocmd/textyankpost_spec.lua new file mode 100644 index 0000000000..965b19581a --- /dev/null +++ b/test/functional/autocmd/textyankpost_spec.lua @@ -0,0 +1,216 @@ +local helpers = require('test.functional.helpers') +local clear, eval, eq, insert = helpers.clear, helpers.eval, helpers.eq, helpers.insert +local feed, execute, expect, command = helpers.feed, helpers.execute, helpers.expect, helpers.command +local curbufmeths, funcs, neq = helpers.curbufmeths, helpers.funcs, helpers.neq + +describe('TextYankPost', function() + before_each(function() + clear() + + -- emulate the clipboard so system clipboard isn't affected + execute('let &rtp = "test/functional/fixtures,".&rtp') + + execute('let g:count = 0') + execute('autocmd TextYankPost * let g:event = copy(v:event)') + execute('autocmd TextYankPost * let g:count += 1') + + curbufmeths.set_line_slice(0, -1, true, true, { + 'foo\0bar', + 'baz text', + }) + end) + + it('is executed after yank and handles register types', function() + feed('yy') + eq({ + operator = 'y', + regcontents = { 'foo\nbar' }, + regname = '', + regtype = 'V' + }, eval('g:event')) + eq(1, eval('g:count')) + + -- v:event is cleared after the autocommand is done + eq({}, eval('v:event')) + + feed('+yw') + eq({ + operator = 'y', + regcontents = { 'baz ' }, + regname = '', + regtype = 'v' + }, eval('g:event')) + eq(2, eval('g:count')) + + feed('<c-v>eky') + eq({ + operator = 'y', + regcontents = { 'foo', 'baz' }, + regname = '', + regtype = "\0223" -- ^V + block width + }, eval('g:event')) + eq(3, eval('g:count')) + end) + + it('makes v:event immutable', function() + feed('yy') + eq({ + operator = 'y', + regcontents = { 'foo\nbar' }, + regname = '', + regtype = 'V' + }, eval('g:event')) + + execute('set debug=msg') + -- the regcontents should not be changed without copy. + local status, err = pcall(command,'call extend(g:event.regcontents, ["more text"])') + eq(status,false) + neq(nil, string.find(err, ':E742:')) + + -- can't mutate keys inside the autocommand + execute('autocmd! TextYankPost * let v:event.regcontents = 0') + status, err = pcall(command,'normal yy') + eq(status,false) + neq(nil, string.find(err, ':E46:')) + + -- can't add keys inside the autocommand + execute('autocmd! TextYankPost * let v:event.mykey = 0') + status, err = pcall(command,'normal yy') + eq(status,false) + neq(nil, string.find(err, ':E742:')) + end) + + it('is not invoked recursively', function() + execute('autocmd TextYankPost * normal "+yy') + feed('yy') + eq({ + operator = 'y', + regcontents = { 'foo\nbar' }, + regname = '', + regtype = 'V' + }, eval('g:event')) + eq(1, eval('g:count')) + eq({ 'foo\nbar' }, funcs.getreg('+',1,1)) + end) + + it('is executed after delete and change', function() + feed('dw') + eq({ + operator = 'd', + regcontents = { 'foo' }, + regname = '', + regtype = 'v' + }, eval('g:event')) + eq(1, eval('g:count')) + + feed('dd') + eq({ + operator = 'd', + regcontents = { '\nbar' }, + regname = '', + regtype = 'V' + }, eval('g:event')) + eq(2, eval('g:count')) + + feed('cwspam<esc>') + eq({ + operator = 'c', + regcontents = { 'baz' }, + regname = '', + regtype = 'v' + }, eval('g:event')) + eq(3, eval('g:count')) + end) + + it('is not executed after black-hole operation', function() + feed('"_dd') + eq(0, eval('g:count')) + + feed('"_cwgood<esc>') + eq(0, eval('g:count')) + + expect([[ + good text]]) + feed('"_yy') + eq(0, eval('g:count')) + + execute('delete _') + eq(0, eval('g:count')) + end) + + it('gives the correct register name', function() + feed('$"byiw') + eq({ + operator = 'y', + regcontents = { 'bar' }, + regname = 'b', + regtype = 'v' + }, eval('g:event')) + + feed('"*yy') + eq({ + operator = 'y', + regcontents = { 'foo\nbar' }, + regname = '*', + regtype = 'V' + }, eval('g:event')) + + execute("set clipboard=unnamed") + + -- regname still shows the name the user requested + feed('yy') + eq({ + operator = 'y', + regcontents = { 'foo\nbar' }, + regname = '', + regtype = 'V' + }, eval('g:event')) + + feed('"*yy') + eq({ + operator = 'y', + regcontents = { 'foo\nbar' }, + regname = '*', + regtype = 'V' + }, eval('g:event')) + end) + + it('works with Ex commands', function() + execute('1delete +') + eq({ + operator = 'd', + regcontents = { 'foo\nbar' }, + regname = '+', + regtype = 'V' + }, eval('g:event')) + eq(1, eval('g:count')) + + execute('yank') + eq({ + operator = 'y', + regcontents = { 'baz text' }, + regname = '', + regtype = 'V' + }, eval('g:event')) + eq(2, eval('g:count')) + + execute('normal yw') + eq({ + operator = 'y', + regcontents = { 'baz ' }, + regname = '', + regtype = 'v' + }, eval('g:event')) + eq(3, eval('g:count')) + + execute('normal! dd') + eq({ + operator = 'd', + regcontents = { 'baz text' }, + regname = '', + regtype = 'V' + }, eval('g:event')) + eq(4, eval('g:count')) + end) + +end) diff --git a/test/functional/eval/operators_spec.lua b/test/functional/eval/operators_spec.lua new file mode 100644 index 0000000000..bc9a17935c --- /dev/null +++ b/test/functional/eval/operators_spec.lua @@ -0,0 +1,28 @@ +local helpers = require('test.functional.helpers') +local eq = helpers.eq +local eval = helpers.eval +local clear = helpers.clear + +describe('Division operator', function() + before_each(clear) + + it('returns infinity on {positive}/0.0', function() + eq('str2float(\'inf\')', eval('string(1.0/0.0)')) + eq('str2float(\'inf\')', eval('string(1.0e-100/0.0)')) + eq('str2float(\'inf\')', eval('string(1.0e+100/0.0)')) + eq('str2float(\'inf\')', eval('string((1.0/0.0)/0.0)')) + end) + + it('returns -infinity on {negative}/0.0', function() + eq('-str2float(\'inf\')', eval('string((-1.0)/0.0)')) + eq('-str2float(\'inf\')', eval('string((-1.0e-100)/0.0)')) + eq('-str2float(\'inf\')', eval('string((-1.0e+100)/0.0)')) + eq('-str2float(\'inf\')', eval('string((-1.0/0.0)/0.0)')) + end) + + it('returns NaN on 0.0/0.0', function() + eq('str2float(\'nan\')', eval('string(0.0/0.0)')) + eq('str2float(\'nan\')', eval('string(-(0.0/0.0))')) + eq('str2float(\'nan\')', eval('string((-0.0)/0.0)')) + end) +end) diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua new file mode 100644 index 0000000000..f7f5dca70a --- /dev/null +++ b/test/functional/eval/string_spec.lua @@ -0,0 +1,175 @@ +local helpers = require('test.functional.helpers') +local clear = helpers.clear +local eq = helpers.eq +local command = helpers.command +local meths = helpers.meths +local eval = helpers.eval +local exc_exec = helpers.exc_exec +local redir_exec = helpers.redir_exec +local funcs = helpers.funcs +local write_file = helpers.write_file + +describe('string() function', function() + before_each(clear) + + describe('used to represent floating-point values', function() + it('dumps NaN values', function() + eq('str2float(\'nan\')', eval('string(str2float(\'nan\'))')) + end) + + it('dumps infinite values', function() + eq('str2float(\'inf\')', eval('string(str2float(\'inf\'))')) + eq('-str2float(\'inf\')', eval('string(str2float(\'-inf\'))')) + end) + + it('dumps regular values', function() + eq('1.5', funcs.string(1.5)) + eq('1.56e-20', funcs.string(1.56000e-020)) + eq('0.0', eval('string(0.0)')) + end) + + it('dumps values with at most six digits after the decimal point', + function() + eq('1.234568e-20', funcs.string(1.23456789123456789123456789e-020)) + eq('1.234568', funcs.string(1.23456789123456789123456789)) + end) + + it('dumps values with at most seven digits before the decimal point', + function() + eq('1234567.891235', funcs.string(1234567.89123456789123456789)) + eq('1.234568e7', funcs.string(12345678.9123456789123456789)) + end) + + it('dumps negative values', function() + eq('-1.5', funcs.string(-1.5)) + eq('-1.56e-20', funcs.string(-1.56000e-020)) + eq('-1.234568e-20', funcs.string(-1.23456789123456789123456789e-020)) + eq('-1.234568', funcs.string(-1.23456789123456789123456789)) + eq('-1234567.891235', funcs.string(-1234567.89123456789123456789)) + eq('-1.234568e7', funcs.string(-12345678.9123456789123456789)) + end) + end) + + describe('used to represent numbers', function() + it('dumps regular values', function() + eq('0', funcs.string(0)) + eq('-1', funcs.string(-1)) + eq('1', funcs.string(1)) + end) + + it('dumps large values', function() + eq('2147483647', funcs.string(2^31-1)) + eq('-2147483648', funcs.string(-2^31)) + end) + end) + + describe('used to represent strings', function() + it('dumps regular strings', function() + eq('\'test\'', funcs.string('test')) + end) + + it('dumps empty strings', function() + eq('\'\'', funcs.string('')) + end) + + it('dumps strings with \' inside', function() + eq('\'\'\'\'\'\'\'\'', funcs.string('\'\'\'')) + eq('\'a\'\'b\'\'\'\'\'', funcs.string('a\'b\'\'')) + eq('\'\'\'b\'\'\'\'d\'', funcs.string('\'b\'\'d')) + eq('\'a\'\'b\'\'c\'\'d\'', funcs.string('a\'b\'c\'d')) + end) + end) + + describe('used to represent funcrefs', function() + local fname = 'Xtest-functional-eval-string_spec-fref-script.vim' + + before_each(function() + write_file(fname, [[ + function Test1() + endfunction + + function s:Test2() + endfunction + + function g:Test3() + endfunction + + let g:Test2_f = function('s:Test2') + ]]) + command('source ' .. fname) + end) + + after_each(function() + os.remove(fname) + end) + + it('dumps references to built-in functions', function() + eq('function(\'function\')', eval('string(function("function"))')) + end) + + it('dumps references to user functions', function() + eq('function(\'Test1\')', eval('string(function("Test1"))')) + eq('function(\'g:Test3\')', eval('string(function("g:Test3"))')) + end) + + it('dumps references to script functions', function() + eq('function(\'<SNR>1_Test2\')', eval('string(Test2_f)')) + end) + end) + + describe('used to represent lists', function() + it('dumps empty list', function() + eq('[]', funcs.string({})) + end) + + it('dumps nested lists', function() + eq('[[[[[]]]]]', funcs.string({{{{{}}}}})) + end) + + it('dumps nested non-empty lists', function() + eq('[1, [[3, [[5], 4]], 2]]', funcs.string({1, {{3, {{5}, 4}}, 2}})) + end) + + it('errors when dumping recursive lists', function() + meths.set_var('l', {}) + eval('add(l, l)') + eq('Vim(echo):E724: unable to correctly dump variable with self-referencing container', + exc_exec('echo string(l)')) + end) + + it('dumps recursive lists despite the error', function() + meths.set_var('l', {}) + eval('add(l, l)') + eq('\nE724: unable to correctly dump variable with self-referencing container\n[{E724@0}]', + redir_exec('echo string(l)')) + eq('\nE724: unable to correctly dump variable with self-referencing container\n[[{E724@1}]]', + redir_exec('echo string([l])')) + end) + end) + + describe('used to represent dictionaries', function() + it('dumps empty dictionary', function() + eq('{}', eval('string({})')) + end) + + it('dumps non-empty dictionary', function() + eq('{\'t\'\'est\': 1}', funcs.string({['t\'est']=1})) + end) + + it('errors when dumping recursive dictionaries', function() + meths.set_var('d', {d=1}) + eval('extend(d, {"d": d})') + eq('Vim(echo):E724: unable to correctly dump variable with self-referencing container', + exc_exec('echo string(d)')) + end) + + it('dumps recursive dictionaries despite the error', function() + meths.set_var('d', {d=1}) + eval('extend(d, {"d": d})') + eq('\nE724: unable to correctly dump variable with self-referencing container\n{\'d\': {E724@0}}', + redir_exec('echo string(d)')) + eq('\nE724: unable to correctly dump variable with self-referencing container\n{\'out\': {\'d\': {E724@1}}}', + redir_exec('echo string({"out": d})')) + end) + end) +end) diff --git a/test/functional/eval/vvar_event_spec.lua b/test/functional/eval/vvar_event_spec.lua new file mode 100644 index 0000000000..bbac86524f --- /dev/null +++ b/test/functional/eval/vvar_event_spec.lua @@ -0,0 +1,15 @@ +local helpers = require('test.functional.helpers') +local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq +local command = helpers.command +describe('v:event', function() + before_each(clear) + it('is empty before any autocommand', function() + eq({}, eval('v:event')) + end) + + it('is immutable', function() + eq(false, pcall(command, 'let v:event = {}')) + eq(false, pcall(command, 'let v:event.mykey = {}')) + end) +end) + diff --git a/test/functional/fixtures/shell-test.c b/test/functional/fixtures/shell-test.c index 5fa8a58049..d9ec254aff 100644 --- a/test/functional/fixtures/shell-test.c +++ b/test/functional/fixtures/shell-test.c @@ -1,25 +1,59 @@ -// A simple implementation of a shell for testing -// `termopen([&sh, &shcf, '{cmd'}])` and `termopen([&sh])`. -// -// If launched with no arguments, prints "ready $ ", otherwise prints -// "ready $ {cmd}\n". - #include <stdio.h> #include <string.h> +#include <stdint.h> + +static void help(void) +{ + puts("A simple implementation of a shell for testing termopen()."); + puts(""); + puts("Usage:"); + puts(" shell-test --help"); + puts(" Prints this help to stdout."); + puts(" shell-test"); + puts(" shell-test EXE"); + puts(" Prints \"ready $ \" to stderr."); + puts(" shell-test EXE \"prog args...\""); + puts(" Prints \"ready $ prog args...\\n\" to stderr."); + puts(" shell-test REP {byte} \"line line line\""); + puts(" Prints \"{lnr}: line line line\\n\" to stdout {byte} times."); + puts(" I.e. for `shell-test REP ab \"test\"'"); + puts(" 0: test"); + puts(" ..."); + puts(" 96: test"); + puts(" will be printed because byte `a' is equal to 97."); +} int main(int argc, char **argv) { - fprintf(stderr, "ready $ "); + if (argc == 2 && strcmp(argv[1], "--help") == 0) { + help(); + } - if (argc == 3) { - // argv should be {"terminal-test", "EXE", "prog args..."} - if (strcmp(argv[1], "EXE") != 0) { - fprintf(stderr, "first argument must be 'EXE'\n"); - return 2; + if (argc >= 2) { + if (strcmp(argv[1], "EXE") == 0) { + fprintf(stderr, "ready $ "); + if (argc >= 3) { + fprintf(stderr, "%s\n", argv[2]); + } + } else if (strcmp(argv[1], "REP") == 0) { + if (argc < 4) { + fprintf(stderr, "Not enough REP arguments\n"); + return 4; + } + uint8_t number = (uint8_t) *argv[2]; + for (uint8_t i = 0; i < number; i++) { + printf("%d: %s\n", (int) i, argv[3]); + } + } else { + fprintf(stderr, "Unknown first argument\n"); + return 3; } - - fprintf(stderr, "%s\n", argv[2]); + return 0; + } else if (argc == 1) { + fprintf(stderr, "ready $ "); + return 0; + } else { + fprintf(stderr, "Missing first argument\n"); + return 2; } - - return 0; } diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua index 19694550f4..4b838eda1d 100644 --- a/test/functional/legacy/003_cindent_spec.lua +++ b/test/functional/legacy/003_cindent_spec.lua @@ -674,6 +674,13 @@ describe('cindent', function() { } + A::A(int a, int b) + : aa(a), + bb(b), + cc(c) + { + } + class CAbc : public BaseClass1, protected BaseClass2 @@ -919,6 +926,55 @@ describe('cindent', function() )foo"; } + { + int a[4] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + }; + } + + { + a = b[2] + + 3; + } + + { + if (1) + /* aaaaa + * bbbbb + */ + a = 1; + } + + void func() + { + switch (foo) + { + case (bar): + if (baz()) + quux(); + break; + case (shmoo): + if (!bar) + { + } + case (foo1): + switch (bar) + { + case baz: + baz_f(); + break; + } + break; + default: + baz(); + baz(); + break; + } + } + /* end of AUTO */ ]=]) @@ -1580,6 +1636,13 @@ describe('cindent', function() { } + A::A(int a, int b) + : aa(a), + bb(b), + cc(c) + { + } + class CAbc : public BaseClass1, protected BaseClass2 @@ -1825,6 +1888,55 @@ describe('cindent', function() )foo"; } + { + int a[4] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + }; + } + + { + a = b[2] + + 3; + } + + { + if (1) + /* aaaaa + * bbbbb + */ + a = 1; + } + + void func() + { + switch (foo) + { + case (bar): + if (baz()) + quux(); + break; + case (shmoo): + if (!bar) + { + } + case (foo1): + switch (bar) + { + case baz: + baz_f(); + break; + } + break; + default: + baz(); + baz(); + break; + } + } + /* end of AUTO */ ]=]) end) diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua new file mode 100644 index 0000000000..483e465cee --- /dev/null +++ b/test/functional/legacy/011_autocommands_spec.lua @@ -0,0 +1,230 @@ +-- Tests for autocommands +-- - FileWritePre writing a compressed file +-- - FileReadPost reading a compressed file +-- - BufNewFile reading a file template +-- - BufReadPre decompressing the file to be read +-- - FilterReadPre substituting characters in the temp file +-- - FilterReadPost substituting characters after filtering +-- - FileReadPre set options for decompression +-- - FileReadPost decompress the file +-- Note: This test is skipped if "gzip" is not available. +-- $GZIP is made empty, "-v" would cause trouble. +-- Use a FileChangedShell autocommand to avoid a prompt for "Xtestfile.gz" +-- being modified outside of Vim (noticed on Solaris). + +local helpers, lfs = require('test.functional.helpers'), require('lfs') +local clear, execute, expect, eq, neq, dedent, write_file, feed = + helpers.clear, helpers.execute, helpers.expect, helpers.eq, helpers.neq, + helpers.dedent, helpers.write_file, helpers.feed + +local function has_gzip() + return os.execute('gzip --help >/dev/null 2>&1') == 0 +end + +local function prepare_gz_file(name, text) + write_file(name, text..'\n') + -- Compress the file with gzip. + os.execute('gzip --force '..name) + -- This should create the .gz file and delete the original. + neq(nil, lfs.attributes(name..'.gz')) + eq(nil, lfs.attributes(name)) +end + +describe('file reading, writing and bufnew and filter autocommands', function() + local text1 = dedent([[ + start of testfile + line 2 Abcdefghijklmnopqrstuvwxyz + line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 4 Abcdefghijklmnopqrstuvwxyz + line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 6 Abcdefghijklmnopqrstuvwxyz + line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 8 Abcdefghijklmnopqrstuvwxyz + line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 10 Abcdefghijklmnopqrstuvwxyz + end of testfile]]) + setup(function() + write_file('Xtest.c', [[ + /* + * Here is a new .c file + */ + ]]) + end) + before_each(clear) + teardown(function() + os.remove('Xtestfile.gz') + os.remove('Xtest.c') + os.remove('test.out') + end) + + if not has_gzip() then + pending('skipped (missing `gzip` utility)', function() end) + else + + it('FileReadPost (using gzip)', function() + prepare_gz_file('Xtestfile', text1) + execute('let $GZIP = ""') + --execute('au FileChangedShell * echo "caught FileChangedShell"') + execute('set bin') + execute("au FileReadPost *.gz '[,']!gzip -d") + -- Read and decompress the testfile. + execute('$r Xtestfile.gz') + expect('\n'..text1) + end) + + it('BufReadPre, BufReadPost (using gzip)', function() + prepare_gz_file('Xtestfile', text1) + local gzip_data = io.open('Xtestfile.gz'):read('*all') + execute('let $GZIP = ""') + -- Setup autocommands to decompress before reading and re-compress afterwards. + execute("au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand('<afile>'))") + execute("au BufReadPre *.gz call rename(expand('<afile>:r'), expand('<afile>'))") + execute("au BufReadPost *.gz call rename(expand('<afile>'), expand('<afile>:r'))") + execute("au BufReadPost *.gz exe '!gzip ' . shellescape(expand('<afile>:r'))") + -- Edit compressed file. + execute('e! Xtestfile.gz') + -- Discard all prompts and messages. + feed('<C-L>') + -- Expect the decompressed file in the buffer. + expect(text1) + -- Expect the original file to be unchanged. + eq(gzip_data, io.open('Xtestfile.gz'):read('*all')) + end) + + it('FileReadPre, FileReadPost', function() + prepare_gz_file('Xtestfile', text1) + execute('au! FileReadPre *.gz exe "silent !gzip -d " . shellescape(expand("<afile>"))') + execute('au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))') + execute("au! FileReadPost *.gz '[,']s/l/L/") + -- Read compressed file. + execute('$r Xtestfile.gz') + -- Discard all prompts and messages. + feed('<C-L>') + expect([[ + + start of testfiLe + Line 2 Abcdefghijklmnopqrstuvwxyz + Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Line 4 Abcdefghijklmnopqrstuvwxyz + Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Line 6 Abcdefghijklmnopqrstuvwxyz + Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Line 8 Abcdefghijklmnopqrstuvwxyz + Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Line 10 Abcdefghijklmnopqrstuvwxyz + end of testfiLe]]) + end) + + end + + it('FileAppendPre, FileAppendPost', function() + execute('au BufNewFile *.c read Xtest.c') + -- Will load Xtest.c. + execute('e! foo.c') + execute("au FileAppendPre *.out '[,']s/new/NEW/") + execute('au FileAppendPost *.out !cat Xtest.c >>test.out') + -- Append it to the output file. + execute('w>>test.out') + -- Discard all prompts and messages. + feed('<C-L>') + -- Expect the decompressed file in the buffer. + execute('e test.out') + expect([[ + + /* + * Here is a NEW .c file + */]]) + end) + + it('FilterReadPre, FilterReadPost', function() + -- Write a special input file for this test block. + write_file('test.out', dedent([[ + startstart + ]]) .. text1 .. dedent([[ + + + start of test.c + /* + * Here is a new .c file + */ + end of test.c + ]]) .. text1 .. dedent([[ + + + /* + * Here is a NEW .c file + */ + /* + * Here is a new .c file + */ + ]]) .. text1 .. dedent([[ + + /* + * Here is a new .c file + */]])) + -- Need temp files here. + execute('set shelltemp') + execute('au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t")') + execute('au FilterReadPre *.out exe "silent !sed s/e/E/ " . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>"))') + execute('au FilterReadPre *.out exe "silent !rm " . shellescape(expand("<afile>")) . ".t"') + execute("au FilterReadPost *.out '[,']s/x/X/g") + -- Edit the output file. + execute('e! test.out') + execute('23,$!cat') + -- Discard all prompts and messages. + feed('<C-L>') + -- Remove CR for when sed adds them. + execute([[23,$s/\r$//]]) + expect([[ + startstart + start of testfile + line 2 Abcdefghijklmnopqrstuvwxyz + line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 4 Abcdefghijklmnopqrstuvwxyz + line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 6 Abcdefghijklmnopqrstuvwxyz + line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 8 Abcdefghijklmnopqrstuvwxyz + line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 10 Abcdefghijklmnopqrstuvwxyz + end of testfile + + start of test.c + /* + * Here is a new .c file + */ + end of test.c + start of testfile + line 2 Abcdefghijklmnopqrstuvwxyz + line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + line 4 Abcdefghijklmnopqrstuvwxyz + linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + linE 6 AbcdefghijklmnopqrstuvwXyz + linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + linE 8 AbcdefghijklmnopqrstuvwXyz + linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + linE 10 AbcdefghijklmnopqrstuvwXyz + End of testfile + + /* + * HEre is a NEW .c file + */ + /* + * HEre is a new .c file + */ + start of tEstfile + linE 2 AbcdefghijklmnopqrstuvwXyz + linE 3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + linE 4 AbcdefghijklmnopqrstuvwXyz + linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + linE 6 AbcdefghijklmnopqrstuvwXyz + linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + linE 8 AbcdefghijklmnopqrstuvwXyz + linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + linE 10 AbcdefghijklmnopqrstuvwXyz + End of testfile + /* + * HEre is a new .c file + */]]) + end) +end) diff --git a/test/functional/legacy/036_regexp_character_classes_spec.lua b/test/functional/legacy/036_regexp_character_classes_spec.lua new file mode 100644 index 0000000000..3c264423ff --- /dev/null +++ b/test/functional/legacy/036_regexp_character_classes_spec.lua @@ -0,0 +1,271 @@ +-- Test character classes in regexp using regexpengine 0, 1, 2. + +local helpers = require('test.functional.helpers') +local ffi = require('ffi') +local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect +local source, write_file = helpers.source, helpers.write_file + +local function sixlines(text) + local result = '' + for _ = 1, 6 do + result = result .. text .. '\n' + end + return result +end + +local function diff(text, nodedent) + local tmpname = os.tmpname() + if ffi.os == 'OSX' and string.match(tmpname, '^/tmp') then + tmpname = '/private'..tmpname + end + execute('w! '..tmpname) + helpers.wait() + local data = io.open(tmpname):read('*all') + if nodedent then + helpers.eq(text, data) + else + helpers.eq(helpers.dedent(text), data) + end + os.remove(tmpname) +end + +describe('character classes in regexp', function() + local ctrl1 = '\t\x0c\r' + local punct1 = " !\"#$%&'()#+'-./" + local digits = '0123456789' + local punct2 = ':;<=>?@' + local upper = 'ABCDEFGHIXYZ' + local punct3 = '[\\]^_`' + local lower = 'abcdefghiwxyz' + local punct4 = '{|}~' + local ctrl2 = '\x7f\x80\x82\x90\x9b' + local iso_text = '\xa6\xb1\xbc\xc7\xd3\xe9' -- "¦±¼ÇÓé" in utf-8 + setup(function() + -- The original test32.in file was not in utf-8 encoding and did also + -- contain some control characters. We use lua escape sequences to write + -- them to the test file. + local line = ctrl1..punct1..digits..punct2..upper..punct3..lower..punct4..ctrl2..iso_text + write_file('test36.in', sixlines(line)) + end) + before_each(function() + clear() + execute('e test36.in') + end) + teardown(function() + os.remove('test36.in') + end) + + it('is working', function() + source([[ + 1 s/\%#=0\d//g + 2 s/\%#=1\d//g + 3 s/\%#=2\d//g + 4 s/\%#=0[0-9]//g + 5 s/\%#=1[0-9]//g + 6 s/\%#=2[0-9]//g]]) + diff(sixlines(ctrl1..punct1..punct2..upper..punct3..lower..punct4.. + ctrl2..iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\D//g + 2 s/\%#=1\D//g + 3 s/\%#=2\D//g + 4 s/\%#=0[^0-9]//g + 5 s/\%#=1[^0-9]//g + 6 s/\%#=2[^0-9]//g]]) + expect([[ + 0123456789 + 0123456789 + 0123456789 + 0123456789 + 0123456789 + 0123456789]]) + end) + it('is working', function() + source([[ + 1 s/\%#=0\o//g + 2 s/\%#=1\o//g + 3 s/\%#=2\o//g + 4 s/\%#=0[0-7]//g + 5 s/\%#=1[0-7]//g + 6 s/\%#=2[0-7]//g]]) + diff(sixlines(ctrl1..punct1..'89'..punct2..upper..punct3..lower..punct4..ctrl2.. + iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\O//g + 2 s/\%#=1\O//g + 3 s/\%#=2\O//g + 4 s/\%#=0[^0-7]//g + 5 s/\%#=1[^0-7]//g + 6 s/\%#=2[^0-7]//g]]) + expect([[ + 01234567 + 01234567 + 01234567 + 01234567 + 01234567 + 01234567]]) + end) + it('is working', function() + source([[ + 1 s/\%#=0\x//g + 2 s/\%#=1\x//g + 3 s/\%#=2\x//g + 4 s/\%#=0[0-9A-Fa-f]//g + 5 s/\%#=1[0-9A-Fa-f]//g + 6 s/\%#=2[0-9A-Fa-f]//g]]) + diff(sixlines(ctrl1..punct1..punct2..'GHIXYZ'..punct3..'ghiwxyz'..punct4..ctrl2..iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\X//g + 2 s/\%#=1\X//g + 3 s/\%#=2\X//g + 4 s/\%#=0[^0-9A-Fa-f]//g + 5 s/\%#=1[^0-9A-Fa-f]//g + 6 s/\%#=2[^0-9A-Fa-f]//g]]) + expect([[ + 0123456789ABCDEFabcdef + 0123456789ABCDEFabcdef + 0123456789ABCDEFabcdef + 0123456789ABCDEFabcdef + 0123456789ABCDEFabcdef + 0123456789ABCDEFabcdef]]) + end) + it('is working', function() + source([[ + 1 s/\%#=0\w//g + 2 s/\%#=1\w//g + 3 s/\%#=2\w//g + 4 s/\%#=0[0-9A-Za-z_]//g + 5 s/\%#=1[0-9A-Za-z_]//g + 6 s/\%#=2[0-9A-Za-z_]//g]]) + diff(sixlines(ctrl1..punct1..punct2..'[\\]^`'..punct4..ctrl2..iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\W//g + 2 s/\%#=1\W//g + 3 s/\%#=2\W//g + 4 s/\%#=0[^0-9A-Za-z_]//g + 5 s/\%#=1[^0-9A-Za-z_]//g + 6 s/\%#=2[^0-9A-Za-z_]//g]]) + expect([[ + 0123456789ABCDEFGHIXYZ_abcdefghiwxyz + 0123456789ABCDEFGHIXYZ_abcdefghiwxyz + 0123456789ABCDEFGHIXYZ_abcdefghiwxyz + 0123456789ABCDEFGHIXYZ_abcdefghiwxyz + 0123456789ABCDEFGHIXYZ_abcdefghiwxyz + 0123456789ABCDEFGHIXYZ_abcdefghiwxyz]]) + end) + it('is working', function() + source([[ + 1 s/\%#=0\h//g + 2 s/\%#=1\h//g + 3 s/\%#=2\h//g + 4 s/\%#=0[A-Za-z_]//g + 5 s/\%#=1[A-Za-z_]//g + 6 s/\%#=2[A-Za-z_]//g]]) + diff(sixlines(ctrl1..punct1..digits..punct2..'[\\]^`'..punct4..ctrl2.. + iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\H//g + 2 s/\%#=1\H//g + 3 s/\%#=2\H//g + 4 s/\%#=0[^A-Za-z_]//g + 5 s/\%#=1[^A-Za-z_]//g + 6 s/\%#=2[^A-Za-z_]//g]]) + expect([[ + ABCDEFGHIXYZ_abcdefghiwxyz + ABCDEFGHIXYZ_abcdefghiwxyz + ABCDEFGHIXYZ_abcdefghiwxyz + ABCDEFGHIXYZ_abcdefghiwxyz + ABCDEFGHIXYZ_abcdefghiwxyz + ABCDEFGHIXYZ_abcdefghiwxyz]]) + end) + it('is working', function() + source([[ + 1 s/\%#=0\a//g + 2 s/\%#=1\a//g + 3 s/\%#=2\a//g + 4 s/\%#=0[A-Za-z]//g + 5 s/\%#=1[A-Za-z]//g + 6 s/\%#=2[A-Za-z]//g]]) + diff(sixlines(ctrl1..punct1..digits..punct2..punct3..punct4..ctrl2..iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\A//g + 2 s/\%#=1\A//g + 3 s/\%#=2\A//g + 4 s/\%#=0[^A-Za-z]//g + 5 s/\%#=1[^A-Za-z]//g + 6 s/\%#=2[^A-Za-z]//g]]) + expect([[ + ABCDEFGHIXYZabcdefghiwxyz + ABCDEFGHIXYZabcdefghiwxyz + ABCDEFGHIXYZabcdefghiwxyz + ABCDEFGHIXYZabcdefghiwxyz + ABCDEFGHIXYZabcdefghiwxyz + ABCDEFGHIXYZabcdefghiwxyz]]) + end) + it('is working', function() + source([[ + 1 s/\%#=0\l//g + 2 s/\%#=1\l//g + 3 s/\%#=2\l//g + 4 s/\%#=0[a-z]//g + 5 s/\%#=1[a-z]//g + 6 s/\%#=2[a-z]//g]]) + diff(sixlines(ctrl1..punct1..digits..punct2..upper..punct3..punct4.. + ctrl2..iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\L//g + 2 s/\%#=1\L//g + 3 s/\%#=2\L//g + 4 s/\%#=0[^a-z]//g + 5 s/\%#=1[^a-z]//g + 6 s/\%#=2[^a-z]//g]]) + expect([[ + abcdefghiwxyz + abcdefghiwxyz + abcdefghiwxyz + abcdefghiwxyz + abcdefghiwxyz + abcdefghiwxyz]]) + end) + it('is working', function() + source([[ + 1 s/\%#=0\u//g + 2 s/\%#=1\u//g + 3 s/\%#=2\u//g + 4 s/\%#=0[A-Z]//g + 5 s/\%#=1[A-Z]//g + 6 s/\%#=2[A-Z]//g]]) + diff(sixlines(ctrl1..punct1..digits..punct2..punct3..lower..punct4.. + ctrl2..iso_text)) + end) + it('is working', function() + source([[ + 1 s/\%#=0\U//g + 2 s/\%#=1\U//g + 3 s/\%#=2\U//g + 4 s/\%#=0[^A-Z]//g + 5 s/\%#=1[^A-Z]//g + 6 s/\%#=2[^A-Z]//g]]) + expect([[ + ABCDEFGHIXYZ + ABCDEFGHIXYZ + ABCDEFGHIXYZ + ABCDEFGHIXYZ + ABCDEFGHIXYZ + ABCDEFGHIXYZ]]) + end) +end) diff --git a/test/functional/legacy/062_tab_pages_spec.lua b/test/functional/legacy/062_tab_pages_spec.lua index f1c8b8d58b..c3cdcac471 100644 --- a/test/functional/legacy/062_tab_pages_spec.lua +++ b/test/functional/legacy/062_tab_pages_spec.lua @@ -139,6 +139,7 @@ describe('tab pages', function() autocmd TabLeave * :call add(g:r, 'TabLeave') autocmd WinLeave * :call add(g:r, 'WinLeave') autocmd BufLeave * :call add(g:r, 'BufLeave') + autocmd TabNew * :call add(g:r, 'TabNew') let t:a='a' C tab split let t:a='b' @@ -185,11 +186,13 @@ describe('tab pages', function() === tab split === WinLeave TabLeave + TabNew WinEnter TabEnter === tabnew === WinLeave TabLeave + TabNew WinEnter TabEnter BufLeave @@ -222,6 +225,7 @@ describe('tab pages', function() === tabnew === WinLeave TabLeave + TabNew WinEnter TabEnter BufLeave diff --git a/test/functional/legacy/autocmd_option_spec.lua b/test/functional/legacy/autocmd_option_spec.lua index 855e9c6271..6349371808 100644 --- a/test/functional/legacy/autocmd_option_spec.lua +++ b/test/functional/legacy/autocmd_option_spec.lua @@ -2,6 +2,8 @@ local helpers = require('test.functional.helpers') local nvim = helpers.meths local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq local curbuf, buf = helpers.curbuf, helpers.bufmeths +local curwin = helpers.curwin +local redir_exec = helpers.redir_exec local source, execute = helpers.source, helpers.execute local function declare_hook_function() @@ -86,7 +88,7 @@ end local function make_buffer() local old_buf = curbuf() - execute('new') + execute('botright new') local new_buf = curbuf() execute('wincmd p') -- move previous window @@ -96,6 +98,19 @@ local function make_buffer() return new_buf end +local function get_new_window_number() + local old_win = curwin() + execute('botright new') + local new_win = curwin() + local new_winnr = redir_exec('echo winnr()') + execute('wincmd p') -- move previous window + + neq(old_win, new_win) + eq(old_win, curwin()) + + return new_winnr:gsub('\n', '') +end + describe('au OptionSet', function() describe('with any opton (*)', function() @@ -248,6 +263,32 @@ describe('au OptionSet', function() end) end) + describe('being set by setwinvar()', function() + it('should not trigger because option name does not match with backup', function() + set_hook('backup') + + execute('call setwinvar(1, "&l:bk", 1)') + expected_empty() + end) + + it('should trigger, use correct option name backup', function() + set_hook('backup') + + execute('call setwinvar(1, "&backup", 1)') + expected_combination({'backup', 0, 1, 'local'}) + end) + + it('should not trigger if the current window is different from the targetted window', function() + set_hook('cursorcolumn') + + local new_winnr = get_new_window_number() + + execute('call setwinvar(' .. new_winnr .. ', "&cursorcolumn", 1)') + -- expected_combination({'cursorcolumn', 0, 1, 'local', {winnr = new_winnr}}) + expected_empty() + end) + end) + describe('being set by neovim api', function() it('should trigger if a boolean option be set globally', function() set_hook('autochdir') diff --git a/test/functional/legacy/charsearch_spec.lua b/test/functional/legacy/charsearch_spec.lua new file mode 100644 index 0000000000..4a83801cfc --- /dev/null +++ b/test/functional/legacy/charsearch_spec.lua @@ -0,0 +1,42 @@ +-- Test for character searches + +local helpers = require('test.functional.helpers') +local feed, insert = helpers.feed, helpers.insert +local clear, execute, expect = helpers.clear, helpers.execute, helpers.expect + +describe('charsearch', function() + setup(clear) + + it('is working', function() + insert([[ + Xabcdefghijkemnopqretuvwxyz + Yabcdefghijkemnopqretuvwxyz + Zabcdefghijkemnokqretkvwxyz]]) + + -- Check that "fe" and ";" work. + execute('/^X') + feed('ylfep;;p,,p') + -- Check that save/restore works. + execute('/^Y') + feed('ylfep') + execute('let csave = getcharsearch()') + feed('fip') + execute('call setcharsearch(csave)') + feed(';p;p') + -- Check that setcharsearch() changes the settings. + execute('/^Z') + feed('ylfep') + execute("call setcharsearch({'char': 'k'})") + feed(';p') + execute("call setcharsearch({'forward': 0})") + feed('$;p') + execute("call setcharsearch({'until': 1})") + feed(';;p') + + -- Assert buffer contents. + expect([[ + XabcdeXfghijkeXmnopqreXtuvwxyz + YabcdeYfghiYjkeYmnopqreYtuvwxyz + ZabcdeZfghijkZZemnokqretkZvwxyz]]) + end) +end) diff --git a/test/functional/legacy/utf8_spec.lua b/test/functional/legacy/utf8_spec.lua index c16b1c45f4..d33ba6b5fd 100644 --- a/test/functional/legacy/utf8_spec.lua +++ b/test/functional/legacy/utf8_spec.lua @@ -4,9 +4,10 @@ local helpers = require('test.functional.helpers') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local execute, expect = helpers.execute, helpers.expect local eq, eval = helpers.eq, helpers.eval +local source = helpers.source describe('utf8', function() - setup(clear) + before_each(clear) it('is working', function() insert('start:') @@ -50,4 +51,33 @@ describe('utf8', function() eq(1, eval('strchars("\\u20dd", 0)')) eq(1, eval('strchars("\\u20dd", 1)')) end) + + it('customlist completion', function() + source([[ + function! CustomComplete1(lead, line, pos) + return ['あ', 'い'] + endfunction + command -nargs=1 -complete=customlist,CustomComplete1 Test1 echo]]) + feed(":Test1 <C-L>'<C-B>$put='<CR>") + + source([[ + function! CustomComplete2(lead, line, pos) + return ['あたし', 'あたま', 'あたりめ'] + endfunction + command -nargs=1 -complete=customlist,CustomComplete2 Test2 echo]]) + feed(":Test2 <C-L>'<C-B>$put='<CR>") + + source([[ + function! CustomComplete3(lead, line, pos) + return ['Nこ', 'Nん', 'Nぶ'] + endfunction + command -nargs=1 -complete=customlist,CustomComplete3 Test3 echo]]) + feed(":Test3 <C-L>'<C-B>$put='<CR>") + + expect([[ + + Test1 + Test2 あた + Test3 N]]) + end) end) diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua new file mode 100644 index 0000000000..6da1521121 --- /dev/null +++ b/test/functional/terminal/edit_spec.lua @@ -0,0 +1,75 @@ +local helpers = require('test.functional.helpers') +local screen = require('test.functional.ui.screen') + +local curbufmeths = helpers.curbufmeths +local curwinmeths = helpers.curwinmeths +local nvim_dir = helpers.nvim_dir +local command = helpers.command +local meths = helpers.meths +local clear = helpers.clear +local eq = helpers.eq + +describe(':edit term://*', function() + local get_screen = function(columns, lines) + local scr = screen.new(columns, lines) + scr:attach(false) + return scr + end + + before_each(function() + clear() + meths.set_option('shell', nvim_dir .. '/shell-test') + meths.set_option('shellcmdflag', 'EXE') + end) + + it('runs TermOpen event', function() + meths.set_var('termopen_runs', {}) + command('autocmd TermOpen * :call add(g:termopen_runs, expand("<amatch>"))') + command('edit term://') + local termopen_runs = meths.get_var('termopen_runs') + eq(1, #termopen_runs) + eq(termopen_runs[1], termopen_runs[1]:match('^term://.//%d+:$')) + end) + + it('runs TermOpen early enough to respect terminal_scrollback_buffer_size', function() + local columns, lines = 20, 4 + local scr = get_screen(columns, lines) + local rep = 'a' + meths.set_option('shellcmdflag', 'REP ' .. rep) + local rep_size = rep:byte() + local sb = 10 + local gsb = 20 + meths.set_var('terminal_scrollback_buffer_size', gsb) + command('autocmd TermOpen * :let b:terminal_scrollback_buffer_size = ' + .. tostring(sb)) + command('edit term://foobar') + local bufcontents = {} + local winheight = curwinmeths.get_height() + -- I have no idea why there is + 4 needed. But otherwise it works fine with + -- different scrollbacks. + local shift = -4 + local buf_cont_start = rep_size - 1 - sb - winheight - shift + local bufline = function(i) return ('%d: foobar'):format(i) end + for i = buf_cont_start,(rep_size - 1) do + bufcontents[#bufcontents + 1] = bufline(i) + end + bufcontents[#bufcontents + 1] = '' + bufcontents[#bufcontents + 1] = '[Process exited 0]' + -- Do not ask me why displayed screen is one line *before* buffer + -- contents: buffer starts with 87:, screen with 86:. + local exp_screen = '\n' + local did_cursor = false + for i = 0,(winheight - 1) do + local line = bufline(buf_cont_start + i - 1) + exp_screen = (exp_screen + .. (did_cursor and '' or '^') + .. line + .. (' '):rep(columns - #line) + .. '|\n') + did_cursor = true + end + exp_screen = exp_screen .. (' '):rep(columns) .. '|\n' + scr:expect(exp_screen) + eq(bufcontents, curbufmeths.get_line_slice(1, -1, true, true)) + end) +end) diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua new file mode 100644 index 0000000000..58f5b11de0 --- /dev/null +++ b/test/functional/ui/bufhl_spec.lua @@ -0,0 +1,261 @@ +local helpers = require('test.functional.helpers') +local Screen = require('test.functional.ui.screen') +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local execute, request, neq = helpers.execute, helpers.request, helpers.neq + + +describe('Buffer highlighting', function() + local screen + local curbuf + + local hl_colors = { + NonText = Screen.colors.Blue, + Question = Screen.colors.SeaGreen, + String = Screen.colors.Fuchsia, + Statement = Screen.colors.Brown, + Special = Screen.colors.SlateBlue, + Identifier = Screen.colors.DarkCyan + } + + before_each(function() + clear() + execute("syntax on") + screen = Screen.new(40, 8) + screen:attach() + screen:set_default_attr_ignore( {{bold=true, foreground=hl_colors.NonText}} ) + screen:set_default_attr_ids({ + [1] = {foreground = hl_colors.String}, + [2] = {foreground = hl_colors.Statement, bold = true}, + [3] = {foreground = hl_colors.Special}, + [4] = {bold = true, foreground = hl_colors.Special}, + [5] = {foreground = hl_colors.Identifier}, + [6] = {bold = true}, + [7] = {underline = true, bold = true, foreground = hl_colors.Special}, + [8] = {foreground = hl_colors.Special, underline = true} + }) + curbuf = request('vim_get_current_buffer') + end) + + after_each(function() + screen:detach() + end) + + local function add_hl(...) + return request('buffer_add_highlight', curbuf, ...) + end + + local function clear_hl(...) + return request('buffer_clear_highlight', curbuf, ...) + end + + + it('works', function() + insert([[ + these are some lines + with colorful text]]) + feed('+') + + screen:expect([[ + these are some lines | + with colorful tex^t | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + + add_hl(-1, "String", 0 , 10, 14) + add_hl(-1, "Statement", 1 , 5, -1) + + screen:expect([[ + these are {1:some} lines | + with {2:colorful tex^t} | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + + feed("ggo<esc>") + screen:expect([[ + these are {1:some} lines | + ^ | + with {2:colorful text} | + ~ | + ~ | + ~ | + ~ | + | + ]]) + + clear_hl(-1, 0 , -1) + screen:expect([[ + these are some lines | + ^ | + with colorful text | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) + + describe('support adding multiple sources', function() + local id1, id2 + before_each(function() + insert([[ + a longer example + in order to demonstrate + combining highlights + from different sources]]) + + execute("hi ImportantWord gui=bold cterm=bold") + id1 = add_hl(0, "ImportantWord", 0, 2, 8) + add_hl(id1, "ImportantWord", 1, 12, -1) + add_hl(id1, "ImportantWord", 2, 0, 9) + add_hl(id1, "ImportantWord", 3, 5, 14) + + id2 = add_hl(0, "Special", 0, 2, 8) + add_hl(id2, "Identifier", 1, 3, 8) + add_hl(id2, "Special", 1, 14, 20) + add_hl(id2, "Underlined", 2, 6, 12) + add_hl(id2, "Underlined", 3, 0, 9) + neq(id1, id2) + + screen:expect([[ + a {4:longer} example | + in {5:order} to {6:de}{4:monstr}{6:ate} | + {6:combin}{7:ing}{8: hi}ghlights | + {8:from }{7:diff}{6:erent} source^s | + ~ | + ~ | + ~ | + :hi ImportantWord gui=bold cterm=bold | + ]]) + end) + + it('and clearing the first added', function() + clear_hl(id1, 0, -1) + screen:expect([[ + a {3:longer} example | + in {5:order} to de{3:monstr}ate | + combin{8:ing hi}ghlights | + {8:from diff}erent source^s | + ~ | + ~ | + ~ | + :hi ImportantWord gui=bold cterm=bold | + ]]) + end) + + it('and clearing the second added', function() + clear_hl(id2, 0, -1) + screen:expect([[ + a {6:longer} example | + in order to {6:demonstrate} | + {6:combining} highlights | + from {6:different} source^s | + ~ | + ~ | + ~ | + :hi ImportantWord gui=bold cterm=bold | + ]]) + end) + + it('and clearing line ranges', function() + clear_hl(-1, 0, 1) + clear_hl(id1, 1, 2) + clear_hl(id2, 2, -1) + screen:expect([[ + a longer example | + in {5:order} to de{3:monstr}ate | + {6:combining} highlights | + from {6:different} source^s | + ~ | + ~ | + ~ | + :hi ImportantWord gui=bold cterm=bold | + ]]) + end) + + it('and renumbering lines', function() + feed('3Gddggo<esc>') + screen:expect([[ + a {4:longer} example | + ^ | + in {5:order} to {6:de}{4:monstr}{6:ate} | + {8:from }{7:diff}{6:erent} sources | + ~ | + ~ | + ~ | + | + ]]) + + execute(':3move 4') + screen:expect([[ + a {4:longer} example | + | + {8:from }{7:diff}{6:erent} sources | + ^in {5:order} to {6:de}{4:monstr}{6:ate} | + ~ | + ~ | + ~ | + ::3move 4 | + ]]) + end) + end) + + it('prioritizes latest added highlight', function() + insert([[ + three overlapping colors]]) + add_hl(0, "Identifier", 0, 6, 17) + add_hl(0, "String", 0, 14, 23) + local id = add_hl(0, "Special", 0, 0, 9) + + screen:expect([[ + {3:three ove}{5:rlapp}{1:ing color}^s | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + + clear_hl(id, 0, 1) + screen:expect([[ + three {5:overlapp}{1:ing color}^s | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) + + it('works with multibyte text', function() + insert([[ + Ta båten över sjön!]]) + add_hl(-1, "Identifier", 0, 3, 9) + add_hl(-1, "String", 0, 16, 21) + + screen:expect([[ + Ta {5:båten} över {1:sjön}^! | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) +end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 4818830940..6f5cadaf81 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -25,6 +25,9 @@ describe('mappings', function() add_mapping('<s-up>', '<s-up>') add_mapping('<c-s-up>', '<c-s-up>') add_mapping('<c-s-a-up>', '<c-s-a-up>') + add_mapping('<c-s-a-d-up>', '<c-s-a-d-up>') + add_mapping('<c-d-a>', '<c-d-a>') + add_mapping('<d-1>', '<d-1>') end) it('ok', function() @@ -37,6 +40,12 @@ describe('mappings', function() check_mapping('<s-a-c-up>', '<c-s-a-up>') check_mapping('<a-c-s-up>', '<c-s-a-up>') check_mapping('<a-s-c-up>', '<c-s-a-up>') + check_mapping('<c-s-a-d-up>', '<c-s-a-d-up>') + check_mapping('<s-a-d-c-up>', '<c-s-a-d-up>') + check_mapping('<d-s-a-c-up>', '<c-s-a-d-up>') + check_mapping('<c-d-a>', '<c-d-a>') + check_mapping('<d-c-a>', '<c-d-a>') + check_mapping('<d-1>', '<d-1>') end) end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 80f46326ee..b35e0cde69 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -219,12 +219,23 @@ function Screen:expect(expected, attr_ids, attr_ignore) local ids = attr_ids or self._default_attr_ids local ignore = attr_ignore or self._default_attr_ignore self:wait(function() + local actual_rows = {} for i = 1, self._height do - local expected_row = expected_rows[i] - local actual_row = self:_row_repr(self._rows[i], ids, ignore) - if expected_row ~= actual_row then - return 'Row '..tostring(i)..' didn\'t match.\nExpected: "'.. - expected_row..'"\nActual: "'..actual_row..'"' + actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore) + end + for i = 1, self._height do + if expected_rows[i] ~= actual_rows[i] then + local msg_expected_rows = {} + for j = 1, #expected_rows do + msg_expected_rows[j] = expected_rows[j] + end + msg_expected_rows[i] = '*' .. msg_expected_rows[i] + actual_rows[i] = '*' .. actual_rows[i] + return ( + 'Row ' .. tostring(i) .. ' didn\'t match.\n' + .. 'Expected:\n|' .. table.concat(msg_expected_rows, '|\n|') .. '|\n' + .. 'Actual:\n|' .. table.concat(actual_rows, '|\n|') .. '|' + ) end end end) diff --git a/test/functional/viml/errorlist_spec.lua b/test/functional/viml/errorlist_spec.lua new file mode 100644 index 0000000000..78e25297f2 --- /dev/null +++ b/test/functional/viml/errorlist_spec.lua @@ -0,0 +1,49 @@ +local helpers = require('test.functional.helpers') + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local get_cur_win_var = helpers.curwinmeths.get_var +-- local exc_exec = helpers.exc_exec + +describe('setqflist()', function() + local setqflist = helpers.funcs.setqflist + + before_each(clear) + + it('sets w:quickfix_title', function() + setqflist({''}, 'r', 'foo') + command('copen') + eq(':foo', get_cur_win_var('quickfix_title')) + end) + + it('expects a proper type for {title}', function() + command('copen') + setqflist({''}, 'r', '5') + eq(':5', get_cur_win_var('quickfix_title')) + setqflist({''}, 'r', 6) + eq(':setqflist()', get_cur_win_var('quickfix_title')) + -- local exc = exc_exec('call setqflist([""], "r", function("function"))') + -- eq('Vim(call):E729: using Funcref as a String', exc) + -- exc = exc_exec('call setqflist([""], "r", [])') + -- eq('Vim(call):E730: using List as a String', exc) + -- exc = exc_exec('call setqflist([""], "r", {})') + -- eq('Vim(call):E731: using Dictionary as a String', exc) + end) +end) + +describe('setloclist()', function() + local setloclist = helpers.funcs.setloclist + + before_each(clear) + + it('sets w:quickfix_title for the correct window', function() + command('rightbelow vsplit') + setloclist(1, {''}, 'r', 'foo') + setloclist(2, {''}, 'r', 'bar') + command('lopen') + eq(':bar', get_cur_win_var('quickfix_title')) + command('lclose | wincmd w | lopen') + eq(':foo', get_cur_win_var('quickfix_title')) + end) +end) diff --git a/test/functional/viml/function_spec.lua b/test/functional/viml/function_spec.lua new file mode 100644 index 0000000000..665f5d4467 --- /dev/null +++ b/test/functional/viml/function_spec.lua @@ -0,0 +1,29 @@ +local helpers = require('test.functional.helpers') + +local clear = helpers.clear +local eq = helpers.eq +local exc_exec = helpers.exc_exec + +describe('Up to MAX_FUNC_ARGS arguments are handled by', function() + local max_func_args = 20 -- from eval.h + local range = helpers.funcs.range + + before_each(clear) + + it('printf()', function() + local printf = helpers.funcs.printf + local rep = helpers.funcs['repeat'] + local expected = '2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,' + eq(expected, printf(rep('%d,', max_func_args-1), unpack(range(2, max_func_args)))) + local ret = exc_exec('call printf("", 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)') + eq('Vim(call):E740: Too many arguments for function printf', ret) + end) + + it('rpcnotify()', function() + local rpcnotify = helpers.funcs.rpcnotify + local ret = rpcnotify(0, 'foo', unpack(range(3, max_func_args))) + eq(1, ret) + ret = exc_exec('call rpcnotify(0, "foo", 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)') + eq('Vim(call):E740: Too many arguments for function rpcnotify', ret) + end) +end) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index d7bb620236..08e76ceef0 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -71,8 +71,8 @@ endif() include(ExternalProject) -set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.7.3.tar.gz) -set(LIBUV_SHA256 db5d46318e18330c696d954747036e1be8e2346411d4f30236d7e2f499f0cfab) +set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.8.0.tar.gz) +set(LIBUV_SHA256 906e1a5c673c95cb261adeacdb7308a65b4a8f7c9c50d85f3021364951fa9cde) set(MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/cpp-1.0.0.tar.gz) set(MSGPACK_SHA256 afda64ca445203bb7092372b822bae8b2539fdcebbfc3f753f393628c2bcfe7d) |