diff options
78 files changed, 3476 insertions, 1169 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0953bcd93a..98ffc77b15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY # version string, else it is combined with the result of `git describe`. set(NVIM_VERSION_MAJOR 0) set(NVIM_VERSION_MINOR 1) -set(NVIM_VERSION_PATCH 4) +set(NVIM_VERSION_PATCH 5) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index e0185844ad..d9fd758177 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,6 +1,5 @@ - Neovim version: -- [ ] Vim behaves differently? - - Vim version: +- [ ] Vim behaves differently? Vim version: - Operating system/version: - Terminal name/version: - `$TERM`: @@ -9,7 +8,9 @@ ### Expected behaviour -### Steps to reproduce using `nvim -u NONE` +### Steps to reproduce using `nvim -u NORC` -1. `nvim -u NONE` -2. +``` +nvim -u NORC + +``` diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index cfe8a87746..56b45497dc 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -1646,7 +1646,7 @@ Vim has a sorting function and a sorting command. The sorting function can be found here: |sort()|, |uniq()|. *:sor* *:sort* -:[range]sor[t][!] [i][u][r][n][x][o][b] [/{pattern}/] +:[range]sor[t][!] [b][f][i][n][o][r][u][x] [/{pattern}/] Sort lines in [range]. When no range is given all lines are sorted. @@ -1654,10 +1654,18 @@ found here: |sort()|, |uniq()|. With [i] case is ignored. + Options [n][f][x][o][b] are mutually exclusive. + With [n] sorting is done on the first decimal number in the line (after or inside a {pattern} match). One leading '-' is included in the number. + With [f] sorting is done on the Float in the line. + The value of Float is determined similar to passing + the text (after or inside a {pattern} match) to + str2float() function. This option is available only + if Vim was compiled with Floating point support. + With [x] sorting is done on the first hexadecimal number in the line (after or inside a {pattern} match). A leading "0x" or "0X" is ignored. @@ -1669,10 +1677,10 @@ found here: |sort()|, |uniq()|. With [b] sorting is done on the first binary number in the line (after or inside a {pattern} match). - With [u] only keep the first of a sequence of - identical lines (ignoring case when [i] is used). - Without this flag, a sequence of identical lines - will be kept in their original order. + With [u] (u stands for unique) only keep the first of + a sequence of identical lines (ignoring case when [i] + is used). Without this flag, a sequence of identical + lines will be kept in their original order. Note that leading and trailing white space may cause lines to be different. diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index d2b9374143..0ad917006f 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1216,12 +1216,18 @@ use has("browsefilter"): > ============================================================================== 7. The current directory *current-directory* -You may use the |:cd| and |:lcd| commands to change to another directory, so -you will not have to type that directory name in front of the file names. It -also makes a difference for executing external commands, e.g. ":!ls". +You can use |:cd|, |:tcd| and |:lcd| to change to another directory, so you +will not have to type that directory name in front of the file names. It also +makes a difference for executing external commands, e.g. ":!ls" or ":te ls". -Changing directory fails when the current buffer is modified, the '.' flag is -present in 'cpoptions' and "!" is not used in the command. +There are three current-directory "scopes": global, tab and window. The +window-local working directory takes precedence over the tab-local +working directory, which in turn takes precedence over the global +working directory. If a local working directory (tab or window) does not +exist, the next-higher scope in the hierarchy applies. + +Commands for changing the working directory can be suffixed with a bang "!" +(e.g. |:cd!|) which is ignored, for compatibility with Vim. *:cd* *E747* *E472* :cd[!] On non-Unix systems: Print the current directory @@ -1246,29 +1252,50 @@ present in 'cpoptions' and "!" is not used in the command. *:chd* *:chdir* :chd[ir][!] [path] Same as |:cd|. + *:tc* *:tcd* *E5000* *E5001* *E5002* +:tc[d][!] {path} Like |:cd|, but set the current directory for the + current tab and window. The current directory for + other tabs and windows is not changed. + + *:tcd-* +:tcd[!] - Change to the previous current directory (before the + previous ":tcd {path}" command). + + *:tch* *:tchdir* +:tch[dir][!] Same as |:tcd|. + *:lc* *:lcd* :lc[d][!] {path} Like |:cd|, but only set the current directory for the current window. The current directory for other - windows is not changed. + windows or any tabs is not changed. *:lch* *:lchdir* :lch[dir][!] Same as |:lcd|. + *:lcd-* +:lcd[!] - Change to the previous current directory (before the + previous ":tcd {path}" command). + *:pw* *:pwd* *E187* :pw[d] Print the current directory name. Also see |getcwd()|. -So long as no |:lcd| command has been used, all windows share the same current -directory. Using a command to jump to another window doesn't change anything -for the current directory. -When a |:lcd| command has been used for a window, the specified directory -becomes the current directory for that window. Windows where the |:lcd| -command has not been used stick to the global current directory. When jumping -to another window the current directory will become the last specified local -current directory. If none was specified, the global current directory is -used. -When a |:cd| command is used, the current window will lose his local current -directory and will use the global current directory from now on. +So long as no |:tcd| or |:lcd| command has been used, all windows share the +same "current directory". Using a command to jump to another window doesn't +change anything for the current directory. + +When |:lcd| has been used for a window, the specified directory becomes the +current directory for that window. Windows where the |:lcd| command has not +been used stick to the global or tab-local directory. When jumping to another +window the current directory will become the last specified local current +directory. If none was specified, the global or tab-local directory is used. + +When changing tabs the same behaviour applies. If the current tab has no +local working directory the global working directory is used. When a |:cd| +command is used, the current window and tab will lose their local current +directories and will use the global current directory from now on. When +a |:tcd| command is used, only the current window will lose its local working +directory. After using |:cd| the full path name will be used for reading and writing files. On some networked file systems this may cause problems. The result of @@ -1317,9 +1344,7 @@ There are a few things to remember when editing binary files: 9. Encryption *encryption* *:X* *E817* *E818* *E819* *E820* -Support for editing encrypted files has been removed, but may be added back in -the future. See the following discussions for more information: - +Support for editing encrypted files has been removed. https://github.com/neovim/neovim/issues/694 https://github.com/neovim/neovim/issues/701 diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 476ab71461..933c4decee 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 7.4. Last change: 2015 Sep 19 +*eval.txt* For Vim version 7.4. Last change: 2016 Jan 16 VIM REFERENCE MANUAL by Bram Moolenaar @@ -1788,9 +1788,10 @@ arglistid( [{winnr} [, {tabnr}]]) Number argument list id argv( {nr}) String {nr} entry of the argument list argv( ) List the argument list -assert_equal( {exp}, {act} [, {msg}]) none assert that {exp} equals {act} -assert_false( {actual} [, {msg}]) none assert that {actual} is false -assert_true( {actual} [, {msg}]) none assert that {actual} is true +assert_equal( {exp}, {act} [, {msg}]) none assert {exp} equals {act} +assert_exception({error} [, {msg}]) none assert {error} is in v:exception +assert_false( {actual} [, {msg}]) none assert {actual} is false +assert_true( {actual} [, {msg}]) none assert {actual} is true asin( {expr}) Float arc sine of {expr} atan( {expr}) Float arc tangent of {expr} atan2( {expr}, {expr}) Float arc tangent of {expr1} / {expr2} @@ -1830,7 +1831,7 @@ cursor( {lnum}, {col} [, {off}]) Number move cursor to {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} +delete( {fname} [, {flags}]) Number delete the file or directory {fname} dictwatcheradd( {dict}, {pattern}, {callback}) Start watching a dictionary dictwatcherdel( {dict}, {pattern}, {callback}) @@ -1886,7 +1887,7 @@ getcmdpos() Number return cursor position in command-line getcmdtype() String return current command-line type getcmdwintype() String return current command-line window type getcurpos() List position of the cursor -getcwd() String the current working directory +getcwd( [{scope}]) String the current working directory getfontname( [{name}]) String name of font being used getfperm( {fname}) String file permissions of file {fname} getfsize( {fname}) Number size in bytes of file {fname} @@ -2244,7 +2245,20 @@ assert_equal({expected}, {actual}, [, {msg}]) < Will result in a string to be added to |v:errors|: test.vim line 12: Expected 'foo' but got 'bar' ~ -assert_false({actual}, [, {msg}]) *assert_false()* +assert_exception({error} [, {msg}]) *assert_exception()* + When v:exception does not contain the string {error} an error + message is added to |v:errors|. + This can be used to assert that a command throws an exception. + Using the error number, followed by a colon, avoids problems + with translations: > + try + commandthatfails + call assert_false(1, 'command should have failed') + catch + call assert_exception('E492:') + endtry + +assert_false({actual} [, {msg}]) *assert_false()* When {actual} is not false an error message is added to |v:errors|, like with |assert_equal()|. A value is false when it is zero or |v:false|. When "{actual}" @@ -2252,7 +2266,7 @@ assert_false({actual}, [, {msg}]) *assert_false()* When {msg} is omitted an error in the form "Expected False but got {actual}" is produced. -assert_true({actual}, [, {msg}]) *assert_true()* +assert_true({actual} [, {msg}]) *assert_true()* When {actual} is not true an error message is added to |v:errors|, like with |assert_equal()|. A value is true when it is a non-zero number or |v:true|. @@ -2770,13 +2784,19 @@ deepcopy({expr}[, {noref}]) *deepcopy()* *E698* {noref} set to 1 will fail. Also see |copy()|. -delete({fname}) *delete()* - Deletes the file by the name {fname}. The result is a Number, - which is 0 if the file was deleted successfully, and non-zero - when the deletion failed. - Use |remove()| to delete an item from a |List|. - To delete a line from the buffer use |:delete|. Use |:exe| - when the line number is in a variable. +delete({fname} [, {flags}]) *delete()* + Without {flags} or with {flags} empty: Deletes the file by the + name {fname}. This also works when {fname} is a symbolic link. + A symbolic link itself is deleted, not what it points to. + + When {flags} is "d": Deletes the directory by the name + {fname}. This fails when directory {fname} is not empty. + + When {flags} is "rf": Deletes the directory by the name + {fname} and everything in it, recursively. BE CAREFUL! + + The result is a Number, which is 0 if the delete operation was + successful and -1 when the deletion failed or partly failed. dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()* Adds a watcher to a dictionary. A dictionary watcher is @@ -3559,9 +3579,18 @@ getcurpos() Get the position of the cursor. This is like getpos('.'), but MoveTheCursorAround call setpos('.', save_cursor) < - *getcwd()* -getcwd() The result is a String, which is the name of the current - working directory. +getcwd([{window}[, {tab}]]) *getcwd()* + With no arguments the result is a String, which is the name of + the current effective working directory. With {window} or + {tab} the working directory of that scope is returned. + Tabs and windows are identified by their respective numbers, + 0 means current tab or window. Missing argument implies 0. + Thus the following are equivalent: > + getcwd() + getcwd(0) + getcwd(0, 0) +< If {window} is -1 it is ignored, only the tab is resolved. + getfsize({fname}) *getfsize()* The result is a Number, which is the size in bytes of the @@ -3896,9 +3925,18 @@ has_key({dict}, {key}) *has_key()* The result is a Number, which is 1 if |Dictionary| {dict} has an entry with key {key}. Zero otherwise. -haslocaldir() *haslocaldir()* - The result is a Number, which is 1 when the current - window has set a local path via |:lcd|, and 0 otherwise. +haslocaldir([{window}[, {tab}]]) *haslocaldir()* + The result is a Number, which is 1 when the specified tabpage + or window has a local path set via |:lcd| or |:tcd|, and + 0 otherwise. + + Tabs and windows are identified by their respective numbers, + 0 means current tab or window. Missing argument implies 0. + Thus the following are equivalent: > + haslocaldir() + haslocaldir(0) + haslocaldir(0, 0) +< If {window} is -1 it is ignored, only the tab is resolved. hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()* The result is a Number, which is 1 if there is a mapping that @@ -5524,14 +5562,15 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()* move. No error message is given. {flags} is a String, which can contain these character flags: - 'b' search backward instead of forward - 'c' accept a match at the cursor position + 'b' search Backward instead of forward + 'c' accept a match at the Cursor position 'e' move to the End of the match 'n' do Not move the cursor - 'p' return number of matching sub-pattern (see below) - 's' set the ' mark at the previous location of the cursor - 'w' wrap around the end of the file - 'W' don't wrap around the end of the file + 'p' return number of matching sub-Pattern (see below) + 's' Set the ' mark at the previous location of the cursor + 'w' Wrap around the end of the file + 'W' don't Wrap around the end of the file + 'z' start searching at the cursor column instead of Zero If neither 'w' or 'W' is given, the 'wrapscan' option applies. If the 's' flag is supplied, the ' mark is set, only if the @@ -5539,6 +5578,12 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()* flag. 'ignorecase', 'smartcase' and 'magic' are used. + + When the 'z' flag is not given seaching always starts in + column zero and then matches before the cursor are skipped. + When the 'c' flag is present in 'cpo' the next search starts + after the match. Without the 'c' flag the next search starts + one column further. When the {stopline} argument is given then the search stops after searching this line. This is useful to restrict the @@ -6120,6 +6165,10 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702* strtod() function to parse numbers, Strings, Lists, Dicts and Funcrefs will be considered as being 0). + When {func} is given and it is 'N' then all items will be + sorted numerical. This is like 'n' but a string containing + digits will be used as the number they represent. + When {func} is a |Funcref| or a function name, this function is called to compare items. The function is invoked with two items as argument and must return zero if they are equal, 1 or @@ -6134,6 +6183,11 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702* on numbers, text strings will sort next to each other, in the same order as they were originally. + The sort is stable, items which compare equal (as number or as + string) will keep their relative position. E.g., when sorting + on numbers, text strings will sort next to each other, in the + same order as they were originally. + Also see |uniq()|. Example: > diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 6ed83ca855..ebb2f28fa5 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3410,7 +3410,7 @@ A jump table for the options with a short description can be found at |Q_op|. global Ignore case in search patterns. Also used when searching in the tags file. - Also see 'smartcase'. + Also see 'smartcase' and 'tagcase'. Can be overruled by using "\c" or "\C" in the pattern, see |/ignorecase|. @@ -6321,19 +6321,22 @@ A jump table for the options with a short description can be found at |Q_op|. < [The whitespace before and after the '0' must be a single <Tab>] When a binary search was done and no match was found in any of the - files listed in 'tags', and 'ignorecase' is set or a pattern is used + files listed in 'tags', and case is ignored or a pattern is used instead of a normal tag name, a retry is done with a linear search. Tags in unsorted tags files, and matches with different case will only be found in the retry. If a tag file indicates that it is case-fold sorted, the second, - linear search can be avoided for the 'ignorecase' case. Use a value - of '2' in the "!_TAG_FILE_SORTED" line for this. A tag file can be - case-fold sorted with the -f switch to "sort" in most unices, as in - the command: "sort -f -o tags tags". For "Exuberant ctags" version - 5.x or higher (at least 5.5) the --sort=foldcase switch can be used - for this as well. Note that case must be folded to uppercase for this - to work. + linear search can be avoided when case is ignored. Use a value of '2' + in the "!_TAG_FILE_SORTED" line for this. A tag file can be case-fold + sorted with the -f switch to "sort" in most unices, as in the command: + "sort -f -o tags tags". For "Exuberant ctags" version 5.x or higher + (at least 5.5) the --sort=foldcase switch can be used for this as + well. Note that case must be folded to uppercase for this to work. + + By default, tag searches are case-sensitive. Case is ignored when + 'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is + "ignore". When 'tagbsearch' is off, tags searching is slower when a full match exists, but faster when no full match exists. Tags in unsorted tags @@ -6344,6 +6347,16 @@ A jump table for the options with a short description can be found at |Q_op|. This option doesn't affect commands that find all matching tags (e.g., command-line completion and ":help"). + *'tagcase'* *'tc'* +'tagcase' 'tc' string (default "followic") + global or local to buffer |global-local| + {not in Vi} + This option specifies how case is handled when searching the tags + file: + followic Follow the 'ignorecase' option + ignore Ignore case + match Match case + *'taglength'* *'tl'* 'taglength' 'tl' number (default 0) global diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 120e027242..66773875c3 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -879,6 +879,7 @@ Short explanation of each option: *option-list* 'tabpagemax' 'tpm' maximum number of tab pages for |-p| and "tab all" 'tabstop' 'ts' number of spaces that <Tab> in file uses 'tagbsearch' 'tbs' use binary searching in tags files +'tagcase' 'tc' how to handle case when searching in tags files 'taglength' 'tl' number of significant characters for a tag 'tagrelative' 'tr' file names in tag file are relative 'tags' 'tag' list of file names used by the tag command diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 7d3697db07..75d820d072 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -84,11 +84,13 @@ changed, to avoid confusion when using ":tnext". It is changed when using ":tag {ident}". The ignore-case matches are not found for a ":tag" command when the -'ignorecase' option is off. They are found when a pattern is used (starting -with a "/") and for ":tselect", also when 'ignorecase' is off. Note that -using ignore-case tag searching disables binary searching in the tags file, -which causes a slowdown. This can be avoided by fold-case sorting the tag -file. See the 'tagbsearch' option for an explanation. +'ignorecase' option is off and 'tagcase' is "followic" or when 'tagcase' is +"match". They are found when a pattern is used (starting with a "/") and for +":tselect", also when 'ignorecase' is off and 'tagcase' is "followic" or when +'tagcase' is "match". Note that using ignore-case tag searching disables +binary searching in the tags file, which causes a slowdown. This can be +avoided by fold-case sorting the tag file. See the 'tagbsearch' option for an +explanation. ============================================================================== 2. Tag stack *tag-stack* *tagstack* *E425* @@ -418,12 +420,13 @@ file "tags". It can also be used to access a common tags file. The next file in the list is not used when: - A matching static tag for the current buffer has been found. - A matching global tag has been found. -This also depends on the 'ignorecase' option. If it is off, and the tags file -only has a match without matching case, the next tags file is searched for a -match with matching case. If no tag with matching case is found, the first -match without matching case is used. If 'ignorecase' is on, and a matching -global tag with or without matching case is found, this one is used, no -further tags files are searched. +This also depends on whether case is ignored. Case is ignored when +'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is +"ignore". If case is not ignored, and the tags file only has a match without +matching case, the next tags file is searched for a match with matching case. +If no tag with matching case is found, the first match without matching case +is used. If case is ignored, and a matching global tag with or without +matching case is found, this one is used, no further tags files are searched. When a tag file name starts with "./", the '.' is replaced with the path of the current file. This makes it possible to use a tags file in the directory @@ -556,8 +559,10 @@ that indicates if the file was sorted. When this line is found, Vim uses binary searching for the tags file: !_TAG_FILE_SORTED<Tab>1<Tab>{anything} ~ -A tag file may be case-fold sorted to avoid a linear search when 'ignorecase' -is on. See 'tagbsearch' for details. The value '2' should be used then: +A tag file may be case-fold sorted to avoid a linear search when case is +ignored. (Case is ignored when 'ignorecase' is set and 'tagcase' is +"followic", or when 'tagcase' is "ignore".) See 'tagbsearch' for details. +The value '2' should be used then: !_TAG_FILE_SORTED<Tab>2<Tab>{anything} ~ The other tag that Vim recognizes, but only when compiled with the diff --git a/runtime/doc/usr_29.txt b/runtime/doc/usr_29.txt index 22de2f6ce6..e495aad06d 100644 --- a/runtime/doc/usr_29.txt +++ b/runtime/doc/usr_29.txt @@ -255,7 +255,8 @@ function. RELATED ITEMS -You can set 'ignorecase' to make case in tag names be ignored. +To make case in tag names be ignored, you can set 'ignorecase' while leaving +'tagcase' as "followic", or set 'tagcase' to "ignore". The 'tagbsearch' option tells if the tags file is sorted or not. The default is to assume a sorted tags file, which makes a tags search a lot faster, but diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 536c87ad7f..7050436aab 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -289,6 +289,10 @@ call append("$", " \tset tl=" . &tl) call append("$", "tags\tlist of file names to search for tags") call append("$", "\t(global or local to buffer)") call <SID>OptionG("tag", &tag) +call append("$", "tagcase\thow to handle case when searching in tags files:") +call append("$", "\t\"followic\" to follow 'ignorecase', \"ignore\" or \"match\"") +call append("$", "\t(global or local to buffer)") +call <SID>OptionG("tc", &tc) call append("$", "tagrelative\tfile names in a tags file are relative to the tags file") call <SID>BinOptionG("tr", &tr) call append("$", "tagstack\ta :tag command will use the tagstack") diff --git a/scripts/git-log-pretty-since.sh b/scripts/git-log-pretty-since.sh new file mode 100755 index 0000000000..d8e3282fb3 --- /dev/null +++ b/scripts/git-log-pretty-since.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Shows a log with changes grouped next to their merge-commit. +# +# Parameters: +# $1 "since" commit +# $2 "inverse match" regex pattern + +set -e +set -u +set -o pipefail + +__SINCE=$1 +__INVMATCH=$2 + +is_merge_commit() { + git rev-parse $1 >/dev/null 2>&1 \ + || { echo "ERROR: invalid commit: $1"; exit 1; } + git log $1^2 >/dev/null 2>&1 && return 0 || return 1 +} + +for commit in $(git log --format='%H' --first-parent --since $__SINCE); do + if is_merge_commit ${commit} ; then + if [ -z "$__INVMATCH" ] || ! git log --oneline ${commit}^1..${commit}^2 \ + | grep -E "$__INVMATCH" >/dev/null 2>&1 ; then + git log -1 --oneline ${commit} + git log --format=' %h %s' ${commit}^1..${commit}^2 + fi + else + git log -1 --oneline ${commit} + fi +done diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000000..514e5b380a --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# Performs steps to tag a release. +# +# Steps: +# Create the "release" commit: +# - CMakeLists.txt: Unset NVIM_VERSION_PRERELEASE +# - Tag the commit. +# Create the "version bump" commit: +# - CMakeLists.txt: Set NVIM_VERSION_PRERELEASE to "-dev" +# +# Manual steps: +# - CMakeLists.txt: Bump NVIM_VERSION_* as appropriate. +# - git push --follow-tags + +set -e +set -u +set -o pipefail + +cd "$(git rev-parse --show-toplevel)" + +__LAST_TAG=$(git describe --abbrev=0) +[ -z "$__LAST_TAG" ] && { echo 'ERROR: no tag found'; exit 1; } +__VERSION_MAJOR=$(grep 'set(NVIM_VERSION_MAJOR' CMakeLists.txt\ + |sed -r 's/.*NVIM_VERSION_MAJOR ([[:digit:]]).*/\1/') +__VERSION_MINOR=$(grep 'set(NVIM_VERSION_MINOR' CMakeLists.txt\ + |sed -r 's/.*NVIM_VERSION_MINOR ([[:digit:]]).*/\1/') +__VERSION_PATCH=$(grep 'set(NVIM_VERSION_PATCH' CMakeLists.txt\ + |sed -r 's/.*NVIM_VERSION_PATCH ([[:digit:]]).*/\1/') +__VERSION="${__VERSION_MAJOR}.${__VERSION_MINOR}.${__VERSION_PATCH}" +{ [ -z "$__VERSION_MAJOR" ] || [ -z "$__VERSION_MINOR" ] || [ -z "$__VERSION_PATCH" ]; } \ + && { echo "ERROR: version parse failed: '${__VERSION}'"; exit 1; } +__RELEASE_MSG="NVIM v${__VERSION} + +Features: + +Fixes: + +Changes: + +" +__BUMP_MSG="version bump" + +echo "Most recent tag: ${__LAST_TAG}" +echo "Release version: ${__VERSION}" +sed -i -r 's/(NVIM_VERSION_PRERELEASE) "-dev"/\1 ""/' CMakeLists.txt +echo "Building changelog since ${__LAST_TAG}..." +__CHANGELOG="$(./scripts/git-log-pretty-since.sh $__LAST_TAG 'vim-patch:\S')" + +git add CMakeLists.txt +git commit --edit -m "${__RELEASE_MSG} ${__CHANGELOG}" +git tag -a v${__VERSION} -m "NVIM v${__VERSION}" + +sed -i -r 's/(NVIM_VERSION_PRERELEASE) ""/\1 "-dev"/' CMakeLists.txt +nvim -c '/NVIM_VERSION' -c 'echo "Update version numbers"' CMakeLists.txt +git add CMakeLists.txt +git commit -m "$__BUMP_MSG" + +echo " +Next steps: + - Double-check NVIM_VERSION_* in CMakeLists.txt + - git push --follow-tags + - update website: index.html" diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 4dc24a8cdf..70777535cb 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -86,6 +86,11 @@ commit_message() { "${vim_message}" "${vim_commit_url}" } +find_git_remote() { + git remote -v \ + | awk '$2 ~ /github.com[:/]neovim\/neovim/ && $3 == "(fetch)" {print $1}' +} + assign_commit_details() { if [[ ${1} =~ [0-9]\.[0-9]\.[0-9]{3,4} ]]; then # Interpret parameter as version number (tag). @@ -132,21 +137,23 @@ get_vim_patch() { local neovim_branch="${BRANCH_PREFIX}${vim_version}" cd "${NEOVIM_SOURCE_DIR}" + local git_remote=$(find_git_remote) 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 "Fetching '${git_remote}/master'." + output="$(git fetch "${git_remote}" master 2>&1)" && echo "✔ ${output}" || (echo "✘ ${output}"; false) echo - echo "Creating new branch '${neovim_branch}' based on 'upstream/master'." + echo "Creating new branch '${neovim_branch}' based on '${git_remote}/master'." cd "${NEOVIM_SOURCE_DIR}" - output="$(git checkout -b "${neovim_branch}" upstream/master 2>&1)" && + output="$(git checkout -b "${neovim_branch}" "${git_remote}/master" 2>&1)" && echo "✔ ${output}" || (echo "✘ ${output}"; false) fi @@ -195,8 +202,9 @@ submit_pr() { 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)") + local git_remote=$(find_git_remote) + local pr_body="$(git log --reverse --format='#### %s%n%n%b%n' ${git_remote}/master..HEAD)" + local patches=("$(git log --reverse --format='%s' ${git_remote}/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. diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 10110b0f62..46ac3c9022 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -116,8 +116,14 @@ String vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, } char *ptr = NULL; - replace_termcodes((char_u *)str.data, (char_u **)&ptr, - from_part, do_lt, special); + // Set 'cpoptions' the way we want it. + // FLAG_CPO_BSLASH set - backslashes are *not* treated specially + // FLAG_CPO_KEYCODE set - keycodes are *not* reverse-engineered + // FLAG_CPO_SPECI unset - <Key> sequences *are* interpreted + // The third from end parameter of replace_termcodes() is true so that the + // <lt> sequence is recognised - needed for a real backslash. + replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr, + from_part, do_lt, special, CPO_TO_CPO_FLAGS); return cstr_as_string(ptr); } @@ -291,7 +297,7 @@ void vim_change_directory(String dir, Error *err) return; } - post_chdir(false); + post_chdir(kCdScopeGlobal); try_end(err); } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c514c4378e..529d0889bd 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1553,6 +1553,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_ep); clear_string_option(&buf->b_p_path); clear_string_option(&buf->b_p_tags); + clear_string_option(&buf->b_p_tc); clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_tsr); clear_string_option(&buf->b_p_qe); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 86e63eb52c..c0cd801cd4 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -666,19 +666,21 @@ struct file_buffer { long b_p_wm_nopaste; ///< b_p_wm saved for paste mode char_u *b_p_keymap; ///< 'keymap' - /* local values for options which are normally global */ - char_u *b_p_gp; /* 'grepprg' local value */ - char_u *b_p_mp; /* 'makeprg' local value */ - char_u *b_p_efm; /* 'errorformat' local value */ - char_u *b_p_ep; /* 'equalprg' local value */ - char_u *b_p_path; /* 'path' local value */ - int b_p_ar; /* 'autoread' local value */ - char_u *b_p_tags; /* 'tags' local value */ - char_u *b_p_dict; /* 'dictionary' local value */ - char_u *b_p_tsr; /* 'thesaurus' local value */ - long b_p_ul; /* 'undolevels' local value */ - int b_p_udf; /* 'undofile' */ - char_u *b_p_lw; // 'lispwords' local value + // local values for options which are normally global + char_u *b_p_gp; ///< 'grepprg' local value + char_u *b_p_mp; ///< 'makeprg' local value + char_u *b_p_efm; ///< 'errorformat' local value + char_u *b_p_ep; ///< 'equalprg' local value + char_u *b_p_path; ///< 'path' local value + int b_p_ar; ///< 'autoread' local value + char_u *b_p_tags; ///< 'tags' local value + char_u *b_p_tc; ///< 'tagcase' local value + unsigned b_tc_flags; ///< flags for 'tagcase' + char_u *b_p_dict; ///< 'dictionary' local value + char_u *b_p_tsr; ///< 'thesaurus' local value + long b_p_ul; ///< 'undolevels' local value + int b_p_udf; ///< 'undofile' + char_u *b_p_lw; ///< 'lispwords' local value /* end of buffer options */ @@ -816,10 +818,12 @@ struct tabpage_S { was set */ diff_T *tp_first_diff; buf_T *(tp_diffbuf[DB_COUNT]); - int tp_diff_invalid; /* list of diffs is outdated */ - frame_T *(tp_snapshot[SNAP_COUNT]); /* window layout snapshots */ - dictitem_T tp_winvar; /* variable for "t:" Dictionary */ - dict_T *tp_vars; /* internal variables, local to tab page */ + int tp_diff_invalid; ///< list of diffs is outdated */ + frame_T *(tp_snapshot[SNAP_COUNT]); ///< window layout snapshots + dictitem_T tp_winvar; ///< variable for "t:" Dictionary + dict_T *tp_vars; ///< internal variables, local to tab page + char_u *localdir; ///< Absolute path of local directory or + ///< NULL }; /* @@ -953,16 +957,14 @@ struct window_S { time through cursupdate() to the current virtual column */ - /* - * the next six are used to update the visual part - */ - char w_old_visual_mode; /* last known VIsual_mode */ - linenr_T w_old_cursor_lnum; /* last known end of visual part */ - colnr_T w_old_cursor_fcol; /* first column for block visual part */ - colnr_T w_old_cursor_lcol; /* last column for block visual part */ - linenr_T w_old_visual_lnum; /* last known start of visual part */ - colnr_T w_old_visual_col; /* last known start of visual part */ - colnr_T w_old_curswant; /* last known value of Curswant */ + // the next seven are used to update the visual part + char w_old_visual_mode; ///< last known VIsual_mode + linenr_T w_old_cursor_lnum; ///< last known end of visual part + colnr_T w_old_cursor_fcol; ///< first column for block visual part + colnr_T w_old_cursor_lcol; ///< last column for block visual part + linenr_T w_old_visual_lnum; ///< last known start of visual part + colnr_T w_old_visual_col; ///< last known start of visual part + colnr_T w_old_curswant; ///< last known value of Curswant /* * "w_topline", "w_leftcol" and "w_skipcol" specify the offsets for diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 83e2aaa6e6..d0dc7b66fc 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1794,10 +1794,11 @@ bool vim_isblankline(char_u *lbuf) /// @param nptr Returns the signed result. /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. -void vim_str2nr(char_u *start, int *prep, int *len, int what, - long *nptr, unsigned long *unptr, int maxlen) +void vim_str2nr(const char_u *const start, int *const prep, int *const len, + const int what, long *const nptr, unsigned long *const unptr, + const int maxlen) { - char_u *ptr = start; + const char_u *ptr = start; int pre = 0; // default is decimal bool negative = false; unsigned long un = 0; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index a3063de869..4826e70727 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -29,7 +29,6 @@ #include "nvim/path.h" #include "nvim/screen.h" #include "nvim/strings.h" -#include "nvim/tempfile.h" #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" diff --git a/src/nvim/edit.c b/src/nvim/edit.c index bca8b9bff0..005c569561 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -7746,6 +7746,8 @@ static void ins_mousescroll(int dir) (long)(curwin->w_botline - curwin->w_topline)); else scroll_redraw(dir, 3L); + } else { + mouse_scroll_horiz(dir); } did_scroll = TRUE; } diff --git a/src/nvim/edit.h b/src/nvim/edit.h index 0289b2c3a6..0d61f26bcc 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -36,12 +36,6 @@ typedef int (*IndentGetter)(void); #define INSCHAR_NO_FEX 8 /* don't use 'formatexpr' */ #define INSCHAR_COM_LIST 16 /* format comments with list/2nd line indent */ -/* direction for nv_mousescroll() and ins_mousescroll() */ -#define MSCR_DOWN 0 /* DOWN must be FALSE */ -#define MSCR_UP 1 -#define MSCR_LEFT -1 -#define MSCR_RIGHT -2 - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "edit.h.generated.h" #endif diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b51480afc6..0ff70df54d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -66,7 +66,6 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" -#include "nvim/tempfile.h" #include "nvim/ui.h" #include "nvim/mouse.h" #include "nvim/terminal.h" @@ -4741,13 +4740,14 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) ++name; break; - /* Special key, e.g.: "\<C-W>" */ - case '<': extra = trans_special(&p, name, TRUE); + // Special key, e.g.: "\<C-W>" + case '<': + extra = trans_special((const char_u **) &p, STRLEN(p), name, true); if (extra != 0) { name += extra; break; } - /* FALLTHROUGH */ + // FALLTHROUGH default: MB_COPY_CHAR(p, name); break; @@ -6656,7 +6656,7 @@ static struct fst { } functions[] = { { "abs", 1, 1, f_abs }, - { "acos", 1, 1, f_acos }, // WJMc + { "acos", 1, 1, f_acos }, // WJMc { "add", 2, 2, f_add }, { "and", 2, 2, f_and }, { "append", 2, 2, f_append }, @@ -6666,6 +6666,7 @@ static struct fst { { "argv", 0, 1, f_argv }, { "asin", 1, 1, f_asin }, // WJMc { "assert_equal", 2, 3, f_assert_equal }, + { "assert_exception", 1, 2, f_assert_exception }, { "assert_false", 1, 2, f_assert_false }, { "assert_true", 1, 2, f_assert_true }, { "atan", 1, 1, f_atan }, @@ -6673,9 +6674,9 @@ static struct fst { { "browse", 4, 4, f_browse }, { "browsedir", 2, 2, f_browsedir }, { "bufexists", 1, 1, f_bufexists }, - { "buffer_exists", 1, 1, f_bufexists }, // obsolete - { "buffer_name", 1, 1, f_bufname }, // obsolete - { "buffer_number", 1, 1, f_bufnr }, // obsolete + { "buffer_exists", 1, 1, f_bufexists }, // obsolete + { "buffer_name", 1, 1, f_bufname }, // obsolete + { "buffer_number", 1, 1, f_bufnr }, // obsolete { "buflisted", 1, 1, f_buflisted }, { "bufloaded", 1, 1, f_bufloaded }, { "bufname", 1, 1, f_bufname }, @@ -6702,7 +6703,7 @@ static struct fst { { "cscope_connection", 0, 3, f_cscope_connection }, { "cursor", 1, 3, f_cursor }, { "deepcopy", 1, 2, f_deepcopy }, - { "delete", 1, 1, f_delete }, + { "delete", 1, 2, f_delete }, { "dictwatcheradd", 3, 3, f_dictwatcheradd }, { "dictwatcherdel", 3, 3, f_dictwatcherdel }, { "did_filetype", 0, 0, f_did_filetype }, @@ -6719,7 +6720,7 @@ static struct fst { { "expand", 1, 3, f_expand }, { "extend", 2, 3, f_extend }, { "feedkeys", 1, 2, f_feedkeys }, - { "file_readable", 1, 1, f_filereadable }, // obsolete + { "file_readable", 1, 1, f_filereadable }, // obsolete { "filereadable", 1, 1, f_filereadable }, { "filewritable", 1, 1, f_filewritable }, { "filter", 2, 2, f_filter }, @@ -6749,7 +6750,7 @@ static struct fst { { "getcmdtype", 0, 0, f_getcmdtype }, { "getcmdwintype", 0, 0, f_getcmdwintype }, { "getcurpos", 0, 0, f_getcurpos }, - { "getcwd", 0, 0, f_getcwd }, + { "getcwd", 0, 2, f_getcwd }, { "getfontname", 0, 1, f_getfontname }, { "getfperm", 1, 1, f_getfperm }, { "getfsize", 1, 1, f_getfsize }, @@ -6773,7 +6774,7 @@ static struct fst { { "globpath", 2, 5, f_globpath }, { "has", 1, 1, f_has }, { "has_key", 2, 2, f_has_key }, - { "haslocaldir", 0, 0, f_haslocaldir }, + { "haslocaldir", 0, 2, f_haslocaldir }, { "hasmapto", 1, 3, f_hasmapto }, { "highlightID", 1, 1, f_hlID }, // obsolete { "highlight_exists", 1, 1, f_hlexists }, // obsolete @@ -7628,6 +7629,26 @@ static void f_assert_equal(typval_T *argvars, typval_T *rettv) } } +/// "assert_exception(string[, msg])" function +static void f_assert_exception(typval_T *argvars, typval_T *rettv) +{ + garray_T ga; + + char *error = (char *)get_tv_string_chk(&argvars[0]); + if (vimvars[VV_EXCEPTION].vv_str == NULL) { + prepare_assert_error(&ga); + ga_concat(&ga, (char_u *)"v:exception is not set"); + assert_error(&ga); + ga_clear(&ga); + } else if (strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + &vimvars[VV_EXCEPTION].vv_tv); + assert_error(&ga); + ga_clear(&ga); + } +} + // Common for assert_true() and assert_false(). static void assert_bool(typval_T *argvars, bool is_true) { @@ -8305,12 +8326,12 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = cs_connection(num, dbpath, prepend); } -/* - * "cursor(lnum, col)" function - * - * Moves the cursor to the specified line and column. - * Returns 0 when the position could be set, -1 otherwise. - */ +/// "cursor(lnum, col)" function, or +/// "cursor(list)" +/// +/// Moves the cursor to the specified line and column. +/// +/// @returns 0 when the position could be set, -1 otherwise. static void f_cursor(typval_T *argvars, typval_T *rettv) { long line, col; @@ -8322,8 +8343,10 @@ static void f_cursor(typval_T *argvars, typval_T *rettv) colnr_T curswant = -1; if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { + EMSG(_(e_invarg)); return; } + line = pos.lnum; col = pos.col; coladd = pos.coladd; @@ -8374,15 +8397,42 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv) } } -/* - * "delete()" function - */ +// "delete()" function static void f_delete(typval_T *argvars, typval_T *rettv) { - if (check_restricted() || check_secure()) - rettv->vval.v_number = -1; - else - rettv->vval.v_number = os_remove((char *)get_tv_string(&argvars[0])); + char_u nbuf[NUMBUFLEN]; + char_u *name; + char_u *flags; + + rettv->vval.v_number = -1; + if (check_restricted() || check_secure()) { + return; + } + + name = get_tv_string(&argvars[0]); + if (name == NULL || *name == NUL) { + EMSG(_(e_invarg)); + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + flags = get_tv_string_buf(&argvars[1], nbuf); + } else { + flags = (char_u *)""; + } + + if (*flags == NUL) { + // delete a file + rettv->vval.v_number = os_remove((char *)name) == 0 ? 0 : -1; + } else if (STRCMP(flags, "d") == 0) { + // delete an empty directory + rettv->vval.v_number = os_rmdir((char *)name) == 0 ? 0 : -1; + } else if (STRCMP(flags, "rf") == 0) { + // delete a directory recursively + rettv->vval.v_number = delete_recursive(name); + } else { + EMSG2(_(e_invexpr2), flags); + } } // dictwatcheradd(dict, key, funcref) function @@ -9758,22 +9808,143 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv) rettv->vval.v_string[0] = cmdwin_type; } -/* - * "getcwd()" function - */ +/// `getcwd([{win}[, {tab}]])` function +/// +/// Every scope not specified implies the currently selected scope object. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be a string. static void f_getcwd(typval_T *argvars, typval_T *rettv) { - char_u *cwd; + // Possible scope of working directory to return. + CdScope scope = kCdScopeWindow; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTab ] = 0, // Number of tab to look at. + }; + + char_u *cwd = NULL; // Current working directory to print + char_u *from = NULL; // The original string to copy + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + + // Pre-conditions and scope extraction together + for (int i = 0; i < kCdScopeGlobal; i++) { + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + EMSG(_(e_invarg)); + return; + } + scope_number[i] = argvars[i].vval.v_number; + // The scope is the current iteration step. + scope = i; + + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + return; + } + } + + // Allocate and initialize the string to return. cwd = xmalloc(MAXPATHL); - if (os_dirname(cwd, MAXPATHL) != FAIL) { - rettv->vval.v_string = vim_strsave(cwd); + + // Get the scope and numbers from the arguments + for (int i = 0; i < MAX_CD_SCOPE; i++) { + // If there is no argument there are no more scopes after it, break out. + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + scope_number[i] = argvars[i].vval.v_number; + // The scope is the current iteration step. + scope = i; + // It is an error for the scope number to be less than `-1`. + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + goto end; + } + } + + // If the deepest scope number is `-1` advance the scope. + if (scope_number[scope] < 0) { + scope++; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTab] == -1) { + tp = NULL; + } else if (scope_number[kCdScopeTab] > 0) { + tp = find_tabpage(scope_number[kCdScopeTab]); + if (!tp) { + EMSG(_("E5000: Cannot find tab number.")); + goto end; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] == -1) { + win = NULL; + } else if (scope_number[kCdScopeWindow] >= 0) { + if (!tp) { + EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + goto end; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], curtab); + if (!win) { + EMSG(_("E5002: Cannot find window number.")); + goto end; + } + } + } + + switch (scope) { + case kCdScopeWindow: + from = win->w_localdir; + if (from) { + break; + } + case kCdScopeTab: // FALLTHROUGH + from = tp->localdir; + if (from) { + break; + } + case kCdScopeGlobal: // FALLTHROUGH + // The `globaldir` variable is not always set. + if (globaldir) { + from = globaldir; + } else { + // Copy the OS path directly into output string and jump to the end. + if (os_dirname(cwd, MAXPATHL) == FAIL) { + EMSG(_("E41: Could not display path.")); + goto end; + } + } + break; + } + + if (from) { + xstrlcpy((char *)cwd, (char *)from, MAXPATHL); + } + + rettv->vval.v_string = vim_strsave(cwd); #ifdef BACKSLASH_IN_FILENAME - slash_adjust(rettv->vval.v_string); + slash_adjust(rettv->vval.v_string); #endif - } + +end: xfree(cwd); } @@ -10024,6 +10195,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); if (getcurpos) { + update_curswant(); list_append_number(l, curwin->w_curswant == MAXCOL ? (varnumber_T)MAXCOL : (varnumber_T)curwin->w_curswant + 1); @@ -10593,12 +10765,98 @@ static void f_has_key(typval_T *argvars, typval_T *rettv) get_tv_string(&argvars[1]), -1) != NULL; } -/* - * "haslocaldir()" function - */ +/// `haslocaldir([{win}[, {tab}]])` function +/// +/// Returns `1` if the scope object has a local directory, `0` otherwise. If a +/// scope object is not specified the current one is implied. This function +/// share a lot of code with `f_getcwd`. +/// +/// @pre The arguments must be of type number. +/// @pre There may not be more than two arguments. +/// @pre An argument may not be -1 if preceding arguments are not all -1. +/// +/// @post The return value will be either the number `1` or `0`. static void f_haslocaldir(typval_T *argvars, typval_T *rettv) { - rettv->vval.v_number = (curwin->w_localdir != NULL); + // Possible scope of working directory to return. + CdScope scope = kCdScopeWindow; + + // Numbers of the scope objects (window, tab) we want the working directory + // of. A `-1` means to skip this scope, a `0` means the current object. + int scope_number[] = { + [kCdScopeWindow] = 0, // Number of window to look at. + [kCdScopeTab ] = 0, // Number of tab to look at. + }; + + tabpage_T *tp = curtab; // The tabpage to look at. + win_T *win = curwin; // The window to look at. + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + // Pre-conditions and scope extraction together + for (int i = 0; i < kCdScopeGlobal; i++) { + if (argvars[i].v_type == VAR_UNKNOWN) { + break; + } + if (argvars[i].v_type != VAR_NUMBER) { + EMSG(_(e_invarg)); + return; + } + scope_number[i] = argvars[i].vval.v_number; + // The scope is the current iteration step. + scope = i; + if (scope_number[i] < -1) { + EMSG(_(e_invarg)); + return; + } + } + + // It the deepest scope number is `-1` advance the scope by one. + if (scope_number[scope] < 0) { + ++scope; + } + + // Find the tabpage by number + if (scope_number[kCdScopeTab] == -1) { + tp = NULL; + } else if (scope_number[kCdScopeTab] > 0) { + tp = find_tabpage(scope_number[kCdScopeTab]); + if (!tp) { + EMSG(_("5000: Cannot find tab number.")); + return; + } + } + + // Find the window in `tp` by number, `NULL` if none. + if (scope_number[kCdScopeWindow] == -1) { + win = NULL; + } else if (scope_number[kCdScopeWindow] >= 0) { + if (!tp) { + EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); + return; + } + + if (scope_number[kCdScopeWindow] > 0) { + win = find_win_by_nr(&argvars[0], curtab); + if (!win) { + EMSG(_("E5002: Cannot find window number.")); + return; + } + } + } + + switch (scope) { + case kCdScopeWindow: + rettv->vval.v_number = win->w_localdir ? 1 : 0; + break; + case kCdScopeTab: + rettv->vval.v_number = tp->localdir ? 1 : 0; + break; + case kCdScopeGlobal: + assert(0); + break; + } } /* @@ -10631,21 +10889,22 @@ static void f_hasmapto(typval_T *argvars, typval_T *rettv) */ static void f_histadd(typval_T *argvars, typval_T *rettv) { - int histype; + HistoryType histype; char_u *str; char_u buf[NUMBUFLEN]; - rettv->vval.v_number = FALSE; - if (check_restricted() || check_secure()) + rettv->vval.v_number = false; + if (check_restricted() || check_secure()) { return; - str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ - histype = str != NULL ? get_histtype(str) : -1; - if (histype >= 0) { + } + str = get_tv_string_chk(&argvars[0]); // NULL on type error + histype = str != NULL ? get_histtype(str, STRLEN(str), false) : HIST_INVALID; + if (histype != HIST_INVALID) { str = get_tv_string_buf(&argvars[1], buf); if (*str != NUL) { init_history(); - add_to_history(histype, str, FALSE, NUL); - rettv->vval.v_number = TRUE; + add_to_history(histype, str, false, NUL); + rettv->vval.v_number = true; return; } } @@ -10660,20 +10919,21 @@ static void f_histdel(typval_T *argvars, typval_T *rettv) char_u buf[NUMBUFLEN]; char_u *str; - str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ - if (str == NULL) + str = get_tv_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { n = 0; - else if (argvars[1].v_type == VAR_UNKNOWN) - /* only one argument: clear entire history */ - n = clr_history(get_histtype(str)); - else if (argvars[1].v_type == VAR_NUMBER) - /* index given: remove that entry */ - n = del_history_idx(get_histtype(str), - (int)get_tv_number(&argvars[1])); - else - /* string given: remove all matching entries */ - n = del_history_entry(get_histtype(str), - get_tv_string_buf(&argvars[1], buf)); + } else if (argvars[1].v_type == VAR_UNKNOWN) { + // only one argument: clear entire history + n = clr_history(get_histtype(str, STRLEN(str), false)); + } else if (argvars[1].v_type == VAR_NUMBER) { + // index given: remove that entry + n = del_history_idx(get_histtype(str, STRLEN(str), false), + (int) get_tv_number(&argvars[1])); + } else { + // string given: remove all matching entries + n = del_history_entry(get_histtype(str, STRLEN(str), false), + get_tv_string_buf(&argvars[1], buf)); + } rettv->vval.v_number = n; } @@ -10682,20 +10942,21 @@ static void f_histdel(typval_T *argvars, typval_T *rettv) */ static void f_histget(typval_T *argvars, typval_T *rettv) { - int type; + HistoryType type; int idx; char_u *str; - str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ - if (str == NULL) + str = get_tv_string_chk(&argvars[0]); // NULL on type error + if (str == NULL) { rettv->vval.v_string = NULL; - else { - type = get_histtype(str); - if (argvars[1].v_type == VAR_UNKNOWN) + } else { + type = get_histtype(str, STRLEN(str), false); + if (argvars[1].v_type == VAR_UNKNOWN) { idx = get_history_idx(type); - else + } else { idx = (int)get_tv_number_chk(&argvars[1], NULL); - /* -1 on type error */ + } + // -1 on type error rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); } rettv->v_type = VAR_STRING; @@ -10710,11 +10971,13 @@ static void f_histnr(typval_T *argvars, typval_T *rettv) char_u *history = get_tv_string_chk(&argvars[0]); - i = history == NULL ? HIST_CMD - 1 : get_histtype(history); - if (i >= HIST_CMD && i < HIST_COUNT) + i = history == NULL ? HIST_CMD - 1 : get_histtype(history, STRLEN(history), + false); + if (i != HIST_INVALID) { i = get_history_idx(i); - else + } else { i = -1; + } rettv->vval.v_number = i; } @@ -11851,8 +12114,9 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) mode = get_map_mode(&which, 0); - keys = replace_termcodes(keys, &keys_buf, TRUE, TRUE, FALSE); - rhs = check_map(keys, mode, exact, FALSE, abbr, &mp, &buffer_local); + keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, false, + CPO_TO_CPO_FLAGS); + rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); xfree(keys_buf); if (!get_dict) { @@ -13303,14 +13567,14 @@ static void f_reverse(typval_T *argvars, typval_T *rettv) } } -#define SP_NOMOVE 0x01 /* don't move cursor */ -#define SP_REPEAT 0x02 /* repeat to find outer pair */ -#define SP_RETCOUNT 0x04 /* return matchcount */ -#define SP_SETPCMARK 0x08 /* set previous context mark */ -#define SP_START 0x10 /* accept match at start position */ -#define SP_SUBPAT 0x20 /* return nr of matching sub-pattern */ -#define SP_END 0x40 /* leave cursor at end of match */ - +#define SP_NOMOVE 0x01 ///< don't move cursor +#define SP_REPEAT 0x02 ///< repeat to find outer pair +#define SP_RETCOUNT 0x04 ///< return matchcount +#define SP_SETPCMARK 0x08 ///< set previous context mark +#define SP_START 0x10 ///< accept match at start position +#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern +#define SP_END 0x40 ///< leave cursor at end of match +#define SP_COLUMN 0x80 ///< start at cursor column /* * Get flags for a search function. @@ -13336,13 +13600,14 @@ static int get_search_arg(typval_T *varp, int *flagsp) default: mask = 0; if (flagsp != NULL) switch (*flags) { - case 'c': mask = SP_START; break; - case 'e': mask = SP_END; break; - case 'm': mask = SP_RETCOUNT; break; - case 'n': mask = SP_NOMOVE; break; - case 'p': mask = SP_SUBPAT; break; - case 'r': mask = SP_REPEAT; break; - case 's': mask = SP_SETPCMARK; break; + case 'c': mask = SP_START; break; + case 'e': mask = SP_END; break; + case 'm': mask = SP_RETCOUNT; break; + case 'n': mask = SP_NOMOVE; break; + case 'p': mask = SP_SUBPAT; break; + case 'r': mask = SP_REPEAT; break; + case 's': mask = SP_SETPCMARK; break; + case 'z': mask = SP_COLUMN; break; } if (mask == 0) { EMSG2(_(e_invarg2), flags); @@ -13358,9 +13623,7 @@ static int get_search_arg(typval_T *varp, int *flagsp) return dir; } -/* - * Shared by search() and searchpos() functions - */ +// Shared by search() and searchpos() functions. static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { int flags; @@ -13381,10 +13644,15 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) if (dir == 0) goto theend; flags = *flagsp; - if (flags & SP_START) + if (flags & SP_START) { options |= SEARCH_START; - if (flags & SP_END) + } + if (flags & SP_END) { options |= SEARCH_END; + } + if (flags & SP_COLUMN) { + options |= SEARCH_COL; + } /* Optional arguments: line number to stop searching and timeout. */ if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { @@ -14714,6 +14982,8 @@ typedef struct { static int item_compare_ic; static bool item_compare_numeric; +static bool item_compare_numbers; +static bool item_compare_float; static char_u *item_compare_func; static dict_T *item_compare_selfdict; static int item_compare_func_err; @@ -14735,6 +15005,21 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) si2 = (sortItem_T *)s2; typval_T *tv1 = &si1->item->li_tv; typval_T *tv2 = &si2->item->li_tv; + + if (item_compare_numbers) { + long v1 = get_tv_number(tv1); + long v2 = get_tv_number(tv2); + + return v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + } + + if (item_compare_float) { + float_T v1 = get_tv_float(tv1); + float_T v2 = get_tv_float(tv2); + + return v1 == v2 ? 0 : v1 > v2 ? 1 : -1; + } + // encode_tv2string() puts quotes around a string and allocates memory. Don't // do that for string variables. Use a single quote when comparing with // a non-string to do what the docs promise. @@ -14881,6 +15166,8 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) item_compare_ic = FALSE; item_compare_numeric = false; + item_compare_numbers = false; + item_compare_float = false; item_compare_func = NULL; item_compare_selfdict = NULL; @@ -14902,6 +15189,12 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (STRCMP(item_compare_func, "n") == 0) { item_compare_func = NULL; item_compare_numeric = true; + } else if (STRCMP(item_compare_func, "N") == 0) { + item_compare_func = NULL; + item_compare_numbers = true; + } else if (STRCMP(item_compare_func, "f") == 0) { + item_compare_func = NULL; + item_compare_float = true; } else if (STRCMP(item_compare_func, "i") == 0) { item_compare_func = NULL; item_compare_ic = TRUE; @@ -17603,6 +17896,33 @@ long get_tv_number_chk(typval_T *varp, int *denote) return n; } +static float_T get_tv_float(typval_T *varp) +{ + switch (varp->v_type) { + case VAR_NUMBER: + return (float_T)(varp->vval.v_number); + case VAR_FLOAT: + return varp->vval.v_float; + break; + case VAR_FUNC: + EMSG(_("E891: Using a Funcref as a Float")); + break; + case VAR_STRING: + EMSG(_("E892: Using a String as a Float")); + break; + case VAR_LIST: + EMSG(_("E893: Using a List as a Float")); + break; + case VAR_DICT: + EMSG(_("E894: Using a Dictionary as a Float")); + break; + default: + EMSG2(_(e_intern2), "get_tv_float()"); + break; + } + return 0; +} + /* * Get the lnum from the first argument. * Also accepts ".", "$", etc., but that only works for the current buffer. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index d344daed11..d020bc8f20 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3,6 +3,7 @@ */ #include <assert.h> +#include <float.h> #include <stdbool.h> #include <string.h> #include <stdlib.h> @@ -50,7 +51,6 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" -#include "nvim/tempfile.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/window.h" @@ -274,17 +274,26 @@ static int linelen(int *has_tab) static char_u *sortbuf1; static char_u *sortbuf2; -static int sort_ic; /* ignore case */ -static int sort_nr; /* sort on number */ -static int sort_rx; /* sort on regex instead of skipping it */ +static int sort_ic; ///< ignore case +static int sort_nr; ///< sort on number +static int sort_rx; ///< sort on regex instead of skipping it +static int sort_flt; ///< sort on floating number -static int sort_abort; /* flag to indicate if sorting has been interrupted */ +static int sort_abort; ///< flag to indicate if sorting has been interrupted -/* Struct to store info to be sorted. */ +/// Struct to store info to be sorted. typedef struct { - linenr_T lnum; /* line number */ - long start_col_nr; /* starting column number or number */ - long end_col_nr; /* ending column number */ + linenr_T lnum; ///< line number + long start_col_nr; ///< starting column number or number + long end_col_nr; ///< ending column number + union { + struct { + long start_col_nr; ///< starting column number + long end_col_nr; ///< ending column number + } line; + long value; ///< value if sorting by integer + float_T value_flt; ///< value if sorting by float + } st_u; } sorti_T; @@ -303,21 +312,26 @@ static int sort_compare(const void *s1, const void *s2) if (got_int) sort_abort = TRUE; - /* When sorting numbers "start_col_nr" is the number, not the column - * number. */ - if (sort_nr) - result = l1.start_col_nr == l2.start_col_nr ? 0 - : l1.start_col_nr > l2.start_col_nr ? 1 : -1; - else { - /* We need to copy one line into "sortbuf1", because there is no - * guarantee that the first pointer becomes invalid when obtaining the - * second one. */ - STRNCPY(sortbuf1, ml_get(l1.lnum) + l1.start_col_nr, - l1.end_col_nr - l1.start_col_nr + 1); - sortbuf1[l1.end_col_nr - l1.start_col_nr] = 0; - STRNCPY(sortbuf2, ml_get(l2.lnum) + l2.start_col_nr, - l2.end_col_nr - l2.start_col_nr + 1); - sortbuf2[l2.end_col_nr - l2.start_col_nr] = 0; + // When sorting numbers "start_col_nr" is the number, not the column + // number. + if (sort_nr) { + result = l1.st_u.value == l2.st_u.value + ? 0 : l1.st_u.value > l2.st_u.value + ? 1 : -1; + } else if (sort_flt) { + result = l1.st_u.value_flt == l2.st_u.value_flt + ? 0 : l1.st_u.value_flt > l2.st_u.value_flt + ? 1 : -1; + } else { + // We need to copy one line into "sortbuf1", because there is no + // guarantee that the first pointer becomes invalid when obtaining the + // second one. + STRNCPY(sortbuf1, ml_get(l1.lnum) + l1.st_u.line.start_col_nr, + l1.st_u.line.end_col_nr - l1.st_u.line.start_col_nr + 1); + sortbuf1[l1.st_u.line.end_col_nr - l1.st_u.line.start_col_nr] = 0; + STRNCPY(sortbuf2, ml_get(l2.lnum) + l2.st_u.line.start_col_nr, + l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr + 1); + sortbuf2[l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr] = 0; result = sort_ic ? STRICMP(sortbuf1, sortbuf2) : STRCMP(sortbuf1, sortbuf2); @@ -361,7 +375,7 @@ void ex_sort(exarg_T *eap) regmatch.regprog = NULL; sorti_T *nrs = xmalloc(count * sizeof(sorti_T)); - sort_abort = sort_ic = sort_rx = sort_nr = 0; + sort_abort = sort_ic = sort_rx = sort_nr = sort_flt = 0; size_t format_found = 0; for (p = eap->arg; *p != NUL; ++p) { @@ -371,7 +385,10 @@ void ex_sort(exarg_T *eap) } else if (*p == 'r') { sort_rx = true; } else if (*p == 'n') { - sort_nr = 2; + sort_nr = 1; + format_found++; + } else if (*p == 'f') { + sort_flt = 1; format_found++; } else if (*p == 'b') { sort_what = STR2NR_BIN + STR2NR_FORCE; @@ -424,7 +441,8 @@ void ex_sort(exarg_T *eap) goto sortend; } - // From here on "sort_nr" is used as a flag for any number sorting. + // From here on "sort_nr" is used as a flag for any integer number + // sorting. sort_nr += sort_what; // Make an array with all line numbers. This avoids having to copy all @@ -453,7 +471,7 @@ void ex_sort(exarg_T *eap) end_col = 0; } - if (sort_nr) { + if (sort_nr || sort_flt) { // Make sure vim_str2nr doesn't read any digits past the end // of the match, by temporarily terminating the string there s2 = s + end_col; @@ -461,29 +479,42 @@ void ex_sort(exarg_T *eap) *s2 = NUL; // Sorting on number: Store the number itself. p = s + start_col; - if (sort_what & STR2NR_HEX) { - s = skiptohex(p); - } else if (sort_what & STR2NR_BIN) { - s = (char_u*) skiptobin((char*) p); - } else { - s = skiptodigit(p); - } - if (s > p && s[-1] == '-') { - // include preceding negative sign - s--; - } - if (*s == NUL) { - // empty line should sort before any number - nrs[lnum - eap->line1].start_col_nr = -MAXLNUM; + if (sort_nr) { + if (sort_what & STR2NR_HEX) { + s = skiptohex(p); + } else if (sort_what & STR2NR_BIN) { + s = (char_u *)skiptobin((char *)p); + } else { + s = skiptodigit(p); + } + if (s > p && s[-1] == '-') { + s--; // include preceding negative sign + } + if (*s == NUL) { + // empty line should sort before any number + nrs[lnum - eap->line1].st_u.value = -MAXLNUM; + } else { + vim_str2nr(s, NULL, NULL, sort_what, + &nrs[lnum - eap->line1].st_u.value, NULL, 0); + } } else { - vim_str2nr(s, NULL, NULL, sort_what, - &nrs[lnum - eap->line1].start_col_nr, NULL, 0); + s = skipwhite(p); + if (*s == '+') { + s = skipwhite(s + 1); + } + + if (*s == NUL) { + // empty line should sort before any number + nrs[lnum - eap->line1].st_u.value_flt = -DBL_MAX; + } else { + nrs[lnum - eap->line1].st_u.value_flt = strtod((char *)s, NULL); + } } *s2 = c; } else { // Store the column to sort at. - nrs[lnum - eap->line1].start_col_nr = start_col; - nrs[lnum - eap->line1].end_col_nr = end_col; + nrs[lnum - eap->line1].st_u.line.start_col_nr = start_col; + nrs[lnum - eap->line1].st_u.line.end_col_nr = end_col; } nrs[lnum - eap->line1].lnum = lnum; @@ -5017,7 +5048,9 @@ helptags_one ( /* * Sort the tags. */ - sort_strings((char_u **)ga.ga_data, ga.ga_len); + if (ga.ga_data != NULL) { + sort_strings((char_u **)ga.ga_data, ga.ga_len); + } /* * Check for duplicates. diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 6c8835b5c5..04fd88cc8d 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2575,6 +2575,18 @@ return { func='ex_copymove', }, { + command='tcd', + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + addr_type=ADDR_LINES, + func='ex_cd', + }, + { + command='tchdir', + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + addr_type=ADDR_LINES, + func='ex_cd', + }, + { command='tNext', flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR), addr_type=ADDR_LINES, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 2c8271c696..a0a0b9d3a5 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2373,8 +2373,9 @@ static FILE *fopen_noinh_readbin(char *filename) # ifdef HAVE_FD_CLOEXEC { int fdflags = fcntl(fd_tmp, F_GETFD); - if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) - fcntl(fd_tmp, F_SETFD, fdflags | FD_CLOEXEC); + if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) { + (void)fcntl(fd_tmp, F_SETFD, fdflags | FD_CLOEXEC); + } } # endif diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6391e023c3..648a3a8487 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2958,8 +2958,11 @@ set_one_cmd_context ( case CMD_chdir: case CMD_lcd: case CMD_lchdir: - if (xp->xp_context == EXPAND_FILES) + case CMD_tcd: + case CMD_tchdir: + if (xp->xp_context == EXPAND_FILES) { xp->xp_context = EXPAND_DIRECTORIES; + } break; case CMD_help: xp->xp_context = EXPAND_HELP; @@ -4553,7 +4556,8 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, char_u *rep_buf = NULL; garray_T *gap; - replace_termcodes(rep, &rep_buf, FALSE, FALSE, FALSE); + replace_termcodes(rep, STRLEN(rep), &rep_buf, false, false, false, + CPO_TO_CPO_FLAGS); if (rep_buf == NULL) { /* Can't replace termcodes - try using the string as is */ rep_buf = vim_strsave(rep); @@ -6814,36 +6818,55 @@ void free_cd_dir(void) #endif -/* - * Deal with the side effects of changing the current directory. - * When "local" is TRUE then this was after an ":lcd" command. - */ -void post_chdir(int local) +/// Deal with the side effects of changing the current directory. +/// +/// @param scope Scope of the function call (global, tab or window). +void post_chdir(CdScope scope) { + // The local directory of the current window is always overwritten. xfree(curwin->w_localdir); curwin->w_localdir = NULL; - if (local) { - /* If still in global directory, need to remember current - * directory as global directory. */ - if (globaldir == NULL && prev_dir != NULL) + + // Overwrite the local directory of the current tab page for `cd` and `tcd` + if (scope >= kCdScopeTab) { + xfree(curtab->localdir); + curtab->localdir = NULL; + } + + if (scope < kCdScopeGlobal) { + // If still in global directory, need to remember current directory as + // global directory. + if (globaldir == NULL && prev_dir != NULL) { globaldir = vim_strsave(prev_dir); - /* Remember this local directory for the window. */ - if (os_dirname(NameBuff, MAXPATHL) == OK) - curwin->w_localdir = vim_strsave(NameBuff); - } else { - /* We are now in the global directory, no need to remember its - * name. */ + } + } + + switch (scope) { + case kCdScopeGlobal: + // We are now in the global directory, no need to remember its name. xfree(globaldir); globaldir = NULL; + break; + case kCdScopeTab: + // Remember this local directory for the tab page. + if (os_dirname(NameBuff, MAXPATHL) == OK) { + curtab->localdir = vim_strsave(NameBuff); + } + break; + case kCdScopeWindow: + // Remember this local directory for the window. + if (os_dirname(NameBuff, MAXPATHL) == OK) { + curwin->w_localdir = vim_strsave(NameBuff); + } + break; } shorten_fnames(TRUE); } -/* - * ":cd", ":lcd", ":chdir" and ":lchdir". - */ + +/// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. void ex_cd(exarg_T *eap) { char_u *new_dir; @@ -6884,10 +6907,25 @@ void ex_cd(exarg_T *eap) new_dir = NameBuff; } #endif - if (new_dir == NULL || vim_chdir(new_dir)) + if (vim_chdir(new_dir)) { EMSG(_(e_failed)); - else { - post_chdir(eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir); + } else { + CdScope scope = kCdScopeGlobal; // Depends on command invoked + + switch (eap->cmdidx) { + case CMD_tcd: + case CMD_tchdir: + scope = kCdScopeTab; + break; + case CMD_lcd: + case CMD_lchdir: + scope = kCdScopeWindow; + break; + default: + break; + } + + post_chdir(scope); /* Echo the new current directory if the command was typed. */ if (KeyTyped || p_verbose >= 5) diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index a5a4edbbbf..7af3ee233c 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -19,6 +19,18 @@ #define EXMODE_NORMAL 1 #define EXMODE_VIM 2 +/// The scope of a command. +/// +/// The lower a number, the deeper the scope. +typedef enum { + kCdScopeWindow, ///< Affects one window. + kCdScopeTab, ///< Affects one tab page. + kCdScopeGlobal, ///< Affects the entire instance of NeoVim. +} CdScope; + +/// Last `:cd` scope defined. +#define MAX_CD_SCOPE kCdScopeGlobal + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.h.generated.h" #endif diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index cffda1ca55..a9e9ee76d5 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4272,20 +4272,33 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) * Command line history stuff * *********************************/ -/* - * Translate a history character to the associated type number. - */ -static int hist_char2type(int c) +/// Translate a history character to the associated type number +static HistoryType hist_char2type(const int c) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - if (c == ':') - return HIST_CMD; - if (c == '=') - return HIST_EXPR; - if (c == '@') - return HIST_INPUT; - if (c == '>') - return HIST_DEBUG; - return HIST_SEARCH; /* must be '?' or '/' */ + switch (c) { + case ':': { + return HIST_CMD; + } + case '=': { + return HIST_EXPR; + } + case '@': { + return HIST_INPUT; + } + case '>': { + return HIST_DEBUG; + } + case '/': + case '?': { + return HIST_SEARCH; + } + default: { + assert(false); + } + } + // Silence -Wreturn-type + return 0; } /* @@ -4454,28 +4467,38 @@ in_history ( return false; } -/* - * Convert history name (from table above) to its HIST_ equivalent. - * When "name" is empty, return "cmd" history. - * Returns -1 for unknown history name. - */ -int get_histtype(char_u *name) +/// Convert history name to its HIST_ equivalent +/// +/// Names are taken from the table above. When `name` is empty returns currently +/// active history or HIST_DEFAULT, depending on `return_default` argument. +/// +/// @param[in] name Converted name. +/// @param[in] len Name length. +/// @param[in] return_default Determines whether HIST_DEFAULT should be +/// returned or value based on `ccline.cmdfirstc`. +/// +/// @return Any value from HistoryType enum, including HIST_INVALID. May not +/// return HIST_DEFAULT unless return_default is true. +HistoryType get_histtype(const char_u *const name, const size_t len, + const bool return_default) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - int i; - int len = (int)STRLEN(name); - - /* No argument: use current history. */ - if (len == 0) - return hist_char2type(ccline.cmdfirstc); + // No argument: use current history. + if (len == 0) { + return return_default ? HIST_DEFAULT :hist_char2type(ccline.cmdfirstc); + } - for (i = 0; history_names[i] != NULL; ++i) - if (STRNICMP(name, history_names[i], len) == 0) + for (HistoryType i = 0; history_names[i] != NULL; i++) { + if (STRNICMP(name, history_names[i], len) == 0) { return i; + } + } - if (vim_strchr((char_u *)":=@>?/", name[0]) != NULL && name[1] == NUL) + if (vim_strchr((char_u *)":=@>?/", name[0]) != NULL && len == 1) { return hist_char2type(name[0]); + } - return -1; + return HIST_INVALID; } static int last_maptick = -1; /* last seen maptick */ @@ -4847,23 +4870,20 @@ void ex_history(exarg_T *eap) while (ASCII_ISALPHA(*end) || vim_strchr((char_u *)":=@>/?", *end) != NULL) end++; - i = *end; - *end = NUL; - histype1 = get_histtype(arg); - if (histype1 == -1) { - if (STRNICMP(arg, "all", STRLEN(arg)) == 0) { + histype1 = get_histtype(arg, end - arg, false); + if (histype1 == HIST_INVALID) { + if (STRNICMP(arg, "all", end - arg) == 0) { histype1 = 0; histype2 = HIST_COUNT-1; } else { - *end = i; EMSG(_(e_trailing)); return; } } else histype2 = histype1; - *end = i; - } else + } else { end = arg; + } if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { EMSG(_(e_trailing)); return; diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 21da8b9d42..24eebdc303 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -27,11 +27,13 @@ /// Present history tables typedef enum { - HIST_CMD, ///< Colon commands. - HIST_SEARCH, ///< Search commands. - HIST_EXPR, ///< Expressions (e.g. from entering = register). - HIST_INPUT, ///< input() lines. - HIST_DEBUG, ///< Debug commands. + HIST_DEFAULT = -2, ///< Default (current) history. + HIST_INVALID = -1, ///< Unknown history. + HIST_CMD = 0, ///< Colon commands. + HIST_SEARCH, ///< Search commands. + HIST_EXPR, ///< Expressions (e.g. from entering = register). + HIST_INPUT, ///< input() lines. + HIST_DEBUG, ///< Debug commands. } HistoryType; /// Number of history tables diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 32e1b645d0..c7870b9f69 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -45,7 +45,6 @@ #include "nvim/search.h" #include "nvim/sha256.h" #include "nvim/strings.h" -#include "nvim/tempfile.h" #include "nvim/ui.h" #include "nvim/types.h" #include "nvim/undo.h" @@ -5116,6 +5115,147 @@ void forward_slash(char_u *fname) } #endif +/// Name of Vim's own temp dir. Ends in a slash. +static char_u *vim_tempdir = NULL; + +/// Create a directory for private use by this instance of Neovim. +/// This is done once, and the same directory is used for all temp files. +/// This method avoids security problems because of symlink attacks et al. +/// It's also a bit faster, because we only need to check for an existing +/// file when creating the directory and not for each temp file. +static void vim_maketempdir(void) +{ + static const char *temp_dirs[] = TEMP_DIR_NAMES; + // Try the entries in `TEMP_DIR_NAMES` to create the temp directory. + char_u template[TEMP_FILE_PATH_MAXLEN]; + char_u path[TEMP_FILE_PATH_MAXLEN]; + for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); i++) { + // Expand environment variables, leave room for "/nvimXXXXXX/999999999" + expand_env((char_u *)temp_dirs[i], template, TEMP_FILE_PATH_MAXLEN - 22); + if (!os_isdir(template)) { // directory doesn't exist + continue; + } + + add_pathsep((char *)template); + // Concatenate with temporary directory name pattern + STRCAT(template, "nvimXXXXXX"); + + if (os_mkdtemp((const char *)template, (char *)path) != 0) { + continue; + } + + if (vim_settempdir((char *)path)) { + // Successfully created and set temporary directory so stop trying. + break; + } else { + // Couldn't set `vim_tempdir` to `path` so remove created directory. + os_rmdir((char *)path); + } + } +} + +/// Delete "name" and everything in it, recursively. +/// @param name The path which should be deleted. +/// @return 0 for success, -1 if some file was not deleted. +int delete_recursive(char_u *name) +{ + int result = 0; + + if (os_isrealdir(name)) { + snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT + + char_u **files; + int file_count; + char_u *exp = vim_strsave(NameBuff); + if (gen_expand_wildcards(1, &exp, &file_count, &files, + EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS + | EW_DODOT | EW_EMPTYOK) == OK) { + for (int i = 0; i < file_count; i++) { + if (delete_recursive(files[i]) != 0) { + result = -1; + } + } + FreeWild(file_count, files); + } else { + result = -1; + } + + xfree(exp); + os_rmdir((char *)name); + } else { + result = os_remove((char *)name) == 0 ? 0 : -1; + } + + return result; +} + +/// Delete the temp directory and all files it contains. +void vim_deltempdir(void) +{ + if (vim_tempdir != NULL) { + // remove the trailing path separator + path_tail(vim_tempdir)[-1] = NUL; + delete_recursive(vim_tempdir); + xfree(vim_tempdir); + vim_tempdir = NULL; + } +} + +/// Get the name of temp directory. This directory would be created on the first +/// call to this function. +char_u *vim_gettempdir(void) +{ + if (vim_tempdir == NULL) { + vim_maketempdir(); + } + + return vim_tempdir; +} + +/// Set Neovim own temporary directory name to `tempdir`. This directory should +/// be already created. Expand this name to a full path and put it in +/// `vim_tempdir`. This avoids that using `:cd` would confuse us. +/// +/// @param tempdir must be no longer than MAXPATHL. +/// +/// @return false if we run out of memory. +static bool vim_settempdir(char *tempdir) +{ + char *buf = verbose_try_malloc(MAXPATHL + 2); + if (!buf) { + return false; + } + vim_FullName(tempdir, buf, MAXPATHL, false); + add_pathsep(buf); + vim_tempdir = (char_u *)xstrdup(buf); + xfree(buf); + return true; +} + +/// Return a unique name that can be used for a temp file. +/// +/// @note The temp file is NOT created. +/// +/// @return pointer to the temp file name or NULL if Neovim can't create +/// temporary directory for its own temporary files. +char_u *vim_tempname(void) +{ + // Temp filename counter. + static uint32_t temp_count; + + char_u *tempdir = vim_gettempdir(); + if (!tempdir) { + return NULL; + } + + // There is no need to check if the file exists, because we own the directory + // and nobody else creates a file in it. + char_u template[TEMP_FILE_PATH_MAXLEN]; + snprintf((char *)template, TEMP_FILE_PATH_MAXLEN, + "%s%" PRIu32, tempdir, temp_count++); + return vim_strsave(template); +} + /* * Code for automatic commands. diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 7f46a37315..ac3cf959c8 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2110,10 +2110,11 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *gap, int level, */ if (getlevel == foldlevelMarker && flp->start <= flp->lvl - level && flp->lvl > 0) { - foldFind(gap, startlnum - 1, &fp); + (void)foldFind(gap, startlnum - 1, &fp); if (fp >= ((fold_T *)gap->ga_data) + gap->ga_len - || fp->fd_top >= startlnum) + || fp->fd_top >= startlnum) { fp = NULL; + } } /* @@ -2167,13 +2168,15 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *gap, int level, } } if (lvl < level + i) { - foldFind(&fp->fd_nested, flp->lnum - fp->fd_top, &fp2); - if (fp2 != NULL) + (void)foldFind(&fp->fd_nested, flp->lnum - fp->fd_top, &fp2); + if (fp2 != NULL) { bot = fp2->fd_top + fp2->fd_len - 1 + fp->fd_top; - } else if (fp->fd_top + fp->fd_len <= flp->lnum && lvl >= level) - finish = TRUE; - else + } + } else if (fp->fd_top + fp->fd_len <= flp->lnum && lvl >= level) { + finish = true; + } else { break; + } } /* At the start of the first nested fold and at the end of the current diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 437495faa4..43cdd7a033 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1562,7 +1562,7 @@ int char_avail(void) { int retval; - ++no_mapping; + no_mapping++; retval = vpeekc(); --no_mapping; return retval != NUL; @@ -2688,22 +2688,24 @@ do_map ( goto theend; } - /* - * If mapping has been given as ^V<C_UP> say, then replace the term codes - * with the appropriate two bytes. If it is a shifted special key, unshift - * it too, giving another two bytes. - * replace_termcodes() may move the result to allocated memory, which - * needs to be freed later (*keys_buf and *arg_buf). - * replace_termcodes() also removes CTRL-Vs and sometimes backslashes. - */ - if (haskey) - keys = replace_termcodes(keys, &keys_buf, TRUE, TRUE, special); + // If mapping has been given as ^V<C_UP> say, then replace the term codes + // with the appropriate two bytes. If it is a shifted special key, unshift + // it too, giving another two bytes. + // replace_termcodes() may move the result to allocated memory, which + // needs to be freed later (*keys_buf and *arg_buf). + // replace_termcodes() also removes CTRL-Vs and sometimes backslashes. + if (haskey) { + keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, special, + CPO_TO_CPO_FLAGS); + } orig_rhs = rhs; if (hasarg) { - if (STRICMP(rhs, "<nop>") == 0) /* "<Nop>" means nothing */ + if (STRICMP(rhs, "<nop>") == 0) { // "<Nop>" means nothing rhs = (char_u *)""; - else - rhs = replace_termcodes(rhs, &arg_buf, FALSE, TRUE, special); + } else { + rhs = replace_termcodes(rhs, STRLEN(rhs), &arg_buf, false, true, special, + CPO_TO_CPO_FLAGS); + } } /* @@ -3270,7 +3272,8 @@ int map_to_exists(char_u *str, char_u *modechars, int abbr) char_u *buf; int retval; - rhs = replace_termcodes(str, &buf, FALSE, TRUE, FALSE); + rhs = replace_termcodes(str, STRLEN(str), &buf, false, true, false, + CPO_TO_CPO_FLAGS); if (vim_strchr(modechars, 'n') != NULL) mode |= NORMAL; @@ -3465,7 +3468,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) mp = maphash[hash]; for (; mp; mp = mp->m_next) { if (mp->m_mode & expand_mapmodes) { - p = translate_mapping(mp->m_keys, TRUE); + p = translate_mapping(mp->m_keys, true, CPO_TO_CPO_FLAGS); if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) { if (round == 1) ++count; @@ -4190,14 +4193,15 @@ void add_map(char_u *map, int mode) // Returns NULL when there is a problem. static char_u * translate_mapping ( char_u *str, - int expmap // TRUE when expanding mappings on command-line + int expmap, // True when expanding mappings on command-line + int cpo_flags // Value of various flags present in &cpo ) { garray_T ga; ga_init(&ga, 1, 40); - int cpo_bslash = (vim_strchr(p_cpo, CPO_BSLASH) != NULL); - int cpo_special = (vim_strchr(p_cpo, CPO_SPECI) != NULL); + bool cpo_bslash = !(cpo_flags&FLAG_CPO_BSLASH); + bool cpo_special = !(cpo_flags&FLAG_CPO_SPECI); for (; *str; ++str) { int c = *str; diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index cc49bcd074..6ce8954fef 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -32,7 +32,6 @@ #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/version.h" -#include "nvim/tempfile.h" #include "nvim/os/os.h" #include "nvim/os/input.h" diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index a143490356..2f9ec0b3ff 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -27,7 +27,6 @@ #include "nvim/quickfix.h" #include "nvim/strings.h" #include "nvim/tag.h" -#include "nvim/tempfile.h" #include "nvim/window.h" #include "nvim/os/os.h" #include "nvim/os/input.h" @@ -1063,8 +1062,8 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m", *qfpos == '-', cmdline) > 0) { if (postponed_split != 0) { - win_split(postponed_split > 0 ? postponed_split : 0, - postponed_split_flags); + (void)win_split(postponed_split > 0 ? postponed_split : 0, + postponed_split_flags); RESET_BINDING(curwin); postponed_split = 0; } diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 6c75d8bdf4..99e94fc60f 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -482,26 +482,28 @@ char_u *get_special_key_name(int c, int modifiers) return string; } -/* - * Try translating a <> name at (*srcp)[] to dst[]. - * Return the number of characters added to dst[], zero for no match. - * If there is a match, srcp is advanced to after the <> name. - * dst[] must be big enough to hold the result (up to six characters)! - */ -unsigned int -trans_special ( - char_u **srcp, - char_u *dst, - int keycode /* prefer key code, e.g. K_DEL instead of DEL */ -) +/// Try translating a <> name +/// +/// @param[in,out] srcp Source from which <> are translated. Is advanced to +/// after the <> name if there is a match. +/// @param[in] src_len Length of the srcp. +/// @param[out] dst Location where translation result will be kept. Must have +/// at least six bytes. +/// @param[in] keycode Prefer key code, e.g. K_DEL in place of DEL. +/// +/// @return Number of characters added to dst, zero for no match. +unsigned int trans_special(const char_u **srcp, const size_t src_len, + char_u *const dst, const bool keycode) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { int modifiers = 0; int key; unsigned int dlen = 0; - key = find_special_key(srcp, &modifiers, keycode, FALSE); - if (key == 0) + key = find_special_key(srcp, src_len, &modifiers, keycode, false); + if (key == 0) { return 0; + } /* Put the appropriate modifier in a string */ if (modifiers != 0) { @@ -514,69 +516,78 @@ trans_special ( dst[dlen++] = K_SPECIAL; dst[dlen++] = (char_u)KEY2TERMCAP0(key); dst[dlen++] = KEY2TERMCAP1(key); - } else if (has_mbyte && !keycode) + } else if (has_mbyte && !keycode) { dlen += (unsigned int)(*mb_char2bytes)(key, dst + dlen); - else if (keycode) { + } else if (keycode) { char_u *after = add_char2buf(key, dst + dlen); assert(after >= dst && (uintmax_t)(after - dst) <= UINT_MAX); dlen = (unsigned int)(after - dst); - } - else + } else { dst[dlen++] = (char_u)key; + } return dlen; } -// Try translating a <> name at (*srcp)[], return the key and modifiers. -// srcp is advanced to after the <> name. -// returns 0 if there is no match. -int find_special_key( - char_u **srcp, - int *modp, - int keycode, // prefer key code, e.g. K_DEL instead of DEL - int keep_x_key // don't translate xHome to Home key -) +/// Try translating a <> name +/// +/// @param[in,out] srcp Translated <> name. Is advanced to after the <> name. +/// @param[in] src_len srcp length. +/// @param[out] modp Location where information about modifiers is saved. +/// @param[in] keycode Prefer key code, e.g. K_DEL in place of DEL. +/// @param[in] keep_x_key Don’t translate xHome to Home key. +/// +/// @return Key and modifiers or 0 if there is no match. +int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, + const bool keycode, const bool keep_x_key) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - char_u *last_dash; - char_u *end_of_name; - char_u *src; - char_u *bp; + const char_u *last_dash; + const char_u *end_of_name; + const char_u *src; + const char_u *bp; + const char_u *const end = *srcp + src_len - 1; int modifiers; int bit; int key; unsigned long n; int l; + if (src_len == 0) { + return 0; + } + src = *srcp; - if (src[0] != '<') + if (src[0] != '<') { return 0; + } // Find end of modifier list last_dash = src; - for (bp = src + 1; *bp == '-' || vim_isIDc(*bp); bp++) { + for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) { if (*bp == '-') { last_dash = bp; - if (bp[1] != NUL) { + if (bp + 1 <= end) { if (has_mbyte) { - l = mb_ptr2len(bp + 1); + l = mb_ptr2len_len(bp + 1, (int) (end - bp) + 1); } else { l = 1; } - if (bp[l + 1] == '>') { - bp += l; // anything accepted, like <C-?> + if (end - bp > l && bp[l + 1] == '>') { + bp += l; // anything accepted, like <C-?> } } } - if (bp[0] == 't' && bp[1] == '_' && bp[2] && bp[3]) { - bp += 3; // skip t_xx, xx may be '-' or '>' - } else if (STRNICMP(bp, "char-", 5) == 0) { + if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') { + bp += 3; // skip t_xx, xx may be '-' or '>' + } else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) { vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0); bp += l + 5; break; } } - if (*bp == '>') { /* found matching '>' */ + if (bp <= end && *bp == '>') { // found matching '>' end_of_name = bp + 1; /* Which modifiers are given? */ @@ -696,7 +707,7 @@ int find_special_key_in_table(int c) * termcap name. * Return the key code, or 0 if not found. */ -int get_special_key_code(char_u *name) +int get_special_key_code(const char_u *name) { char_u *table_name; int i, j; @@ -730,50 +741,58 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag) return 0; /* Shouldn't get here */ } -// Replace any terminal code strings in from[] with the equivalent internal -// vim representation. This is used for the "from" and "to" part of a -// mapping, and the "to" part of a menu command. -// Any strings like "<C-UP>" are also replaced, unless 'cpoptions' contains -// '<'. -// K_SPECIAL by itself is replaced by K_SPECIAL KS_SPECIAL KE_FILLER. -// -// The replacement is done in result[] and finally copied into allocated -// memory. If this all works well *bufp is set to the allocated memory and a -// pointer to it is returned. If something fails *bufp is set to NULL and from -// is returned. -// -// CTRL-V characters are removed. When "from_part" is TRUE, a trailing CTRL-V -// is included, otherwise it is removed (for ":map xx ^V", maps xx to -// nothing). When 'cpoptions' does not contain 'B', a backslash can be used -// instead of a CTRL-V. -char_u * replace_termcodes ( - char_u *from, - char_u **bufp, - int from_part, - int do_lt, // also translate <lt> - int special // always accept <key> notation -) +/// Replace any terminal code strings with the equivalent internal +/// representation +/// +/// This is used for the "from" and "to" part of a mapping, and the "to" part of +/// a menu command. Any strings like "<C-UP>" are also replaced, unless +/// 'cpoptions' contains '<'. K_SPECIAL by itself is replaced by K_SPECIAL +/// KS_SPECIAL KE_FILLER. +/// +/// @param[in] from What characters to replace. +/// @param[in] from_len Length of the "from" argument. +/// @param[out] bufp Location where results were saved in case of success +/// (allocated). Will be set to NULL in case of failure. +/// @param[in] do_lt If true, also translate <lt>. +/// @param[in] from_part If true, trailing <C-v> is included, otherwise it is +/// removed (to make ":map xx ^V" map xx to nothing). +/// When cpo_flags contains #FLAG_CPO_BSLASH, a backslash +/// can be used in place of <C-v>. All other <C-v> +/// characters are removed. +/// @param[in] special If true, always accept <key> notation. +/// @param[in] cpo_flags Relevant flags derived from p_cpo, see +/// #CPO_TO_CPO_FLAGS. +/// +/// @return Pointer to an allocated memory in case of success, "from" in case of +/// failure. In case of success returned pointer is also saved to +/// "bufp". +char_u *replace_termcodes(const char_u *from, const size_t from_len, + char_u **bufp, const bool from_part, const bool do_lt, + const bool special, int cpo_flags) + FUNC_ATTR_NONNULL_ALL { ssize_t i; size_t slen; char_u key; size_t dlen = 0; - char_u *src; + const char_u *src; + const char_u *const end = from + from_len - 1; int do_backslash; // backslash is a special character int do_special; // recognize <> key codes char_u *result; // buffer for resulting string - do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); - do_special = (vim_strchr(p_cpo, CPO_SPECI) == NULL) || special; + do_backslash = !(cpo_flags&FLAG_CPO_BSLASH); + do_special = !(cpo_flags&FLAG_CPO_SPECI) || special; // Allocate space for the translation. Worst case a single character is // replaced by 6 bytes (shifted special key), plus a NUL at the end. - result = xmalloc(STRLEN(from) * 6 + 1); + result = xmalloc(from_len * 6 + 1); src = from; // Check for #n at start only: function key n - if (from_part && src[0] == '#' && ascii_isdigit(src[1])) { // function key + if (from_part && from_len > 1 && src[0] == '#' + && ascii_isdigit(src[1])) { // function key result[dlen++] = K_SPECIAL; result[dlen++] = 'k'; if (src[1] == '0') { @@ -785,13 +804,14 @@ char_u * replace_termcodes ( } // Copy each byte from *from to result[dlen] - while (*src != NUL) { + while (src <= end) { // If 'cpoptions' does not contain '<', check for special key codes, // like "<C-S-LeftMouse>" - if (do_special && (do_lt || STRNCMP(src, "<lt>", 4) != 0)) { + if (do_special && (do_lt || ((end - src) >= 3 + && STRNCMP(src, "<lt>", 4) != 0))) { // Replace <SID> by K_SNR <script-nr> _. // (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14) - if (STRNICMP(src, "<SID>", 5) == 0) { + if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) { if (current_SID <= 0) { EMSG(_(e_usingsid)); } else { @@ -806,7 +826,7 @@ char_u * replace_termcodes ( } } - slen = trans_special(&src, result + dlen, TRUE); + slen = trans_special(&src, (size_t) (end - src) + 1, result + dlen, true); if (slen) { dlen += slen; continue; @@ -819,10 +839,10 @@ char_u * replace_termcodes ( // Replace <Leader> by the value of "mapleader". // Replace <LocalLeader> by the value of "maplocalleader". // If "mapleader" or "maplocalleader" isn't set use a backslash. - if (STRNICMP(src, "<Leader>", 8) == 0) { + if (end - src >= 7 && STRNICMP(src, "<Leader>", 8) == 0) { len = 8; p = get_var_value((char_u *)"g:mapleader"); - } else if (STRNICMP(src, "<LocalLeader>", 13) == 0) { + } else if (end - src >= 12 && STRNICMP(src, "<LocalLeader>", 13) == 0) { len = 13; p = get_var_value((char_u *)"g:maplocalleader"); } else { @@ -851,8 +871,8 @@ char_u * replace_termcodes ( // If 'cpoptions' does not contain 'B', also accept a backslash. key = *src; if (key == Ctrl_V || (do_backslash && key == '\\')) { - ++src; // skip CTRL-V or backslash - if (*src == NUL) { + src++; // skip CTRL-V or backslash + if (src > end) { if (from_part) { result[dlen++] = key; } @@ -861,7 +881,7 @@ char_u * replace_termcodes ( } // skip multibyte char correctly - for (i = (*mb_ptr2len)(src); i > 0; --i) { + for (i = (*mb_ptr2len_len)(src, (int) (end - src) + 1); i > 0; i--) { // If the character is K_SPECIAL, replace it with K_SPECIAL // KS_SPECIAL KE_FILLER. // If compiled with the GUI replace CSI with K_CSI. diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 8f9980c6b4..bb8ba84a6a 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -1,6 +1,8 @@ #ifndef NVIM_KEYMAP_H #define NVIM_KEYMAP_H +#include "nvim/strings.h" + /* * Keycode definitions for special keys. * @@ -461,6 +463,14 @@ enum key_extra { // This is a total of 6 tokens, and is currently the longest one possible. #define MAX_KEY_CODE_LEN 6 +#define FLAG_CPO_BSLASH 0x01 +#define FLAG_CPO_SPECI 0x02 +#define CPO_TO_CPO_FLAGS (((vim_strchr(p_cpo, CPO_BSLASH) == NULL) \ + ? 0 \ + : FLAG_CPO_BSLASH)| \ + (vim_strchr(p_cpo, CPO_SPECI) == NULL \ + ? 0 \ + : FLAG_CPO_SPECI)) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "keymap.h.generated.h" diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 10176752d5..f82f88a88f 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -65,7 +65,6 @@ #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/version.h" -#include "nvim/tempfile.h" #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 91a72abfc5..3c2394d579 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -215,10 +215,12 @@ ex_menu ( if (STRICMP(map_to, "<nop>") == 0) { /* "<Nop>" means nothing */ map_to = (char_u *)""; map_buf = NULL; - } else if (modes & MENU_TIP_MODE) - map_buf = NULL; /* Menu tips are plain text. */ - else - map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE, special); + } else if (modes & MENU_TIP_MODE) { + map_buf = NULL; // Menu tips are plain text. + } else { + map_to = replace_termcodes(map_to, STRLEN(map_to), &map_buf, false, true, + special, CPO_TO_CPO_FLAGS); + } menuarg.modes = modes; menuarg.noremap[0] = noremap; menuarg.silent[0] = silent; diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index a20ee562fa..43e0dd0c1a 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -43,7 +43,6 @@ #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/tag.h" -#include "nvim/tempfile.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/window.h" diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 3ae1a6a890..4611b69424 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -14,6 +14,8 @@ #include "nvim/misc1.h" #include "nvim/cursor.h" #include "nvim/buffer_defs.h" +#include "nvim/memline.h" +#include "nvim/charset.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mouse.c.generated.h" @@ -503,3 +505,95 @@ void set_mouse_topline(win_T *wp) orig_topfill = wp->w_topfill; } +/// +/// Return length of line "lnum" for horizontal scrolling. +/// +static colnr_T scroll_line_len(linenr_T lnum) +{ + colnr_T col = 0; + char_u *line = ml_get(lnum); + if (*line != NUL) { + for (;;) { + int numchar = chartabsize(line, col); + mb_ptr_adv(line); + if (*line == NUL) { // don't count the last character + break; + } + col += numchar; + } + } + return col; +} + +/// +/// Find longest visible line number. +/// +static linenr_T find_longest_lnum(void) +{ + linenr_T ret = 0; + + // Calculate maximum for horizontal scrollbar. Check for reasonable + // line numbers, topline and botline can be invalid when displaying is + // postponed. + if (curwin->w_topline <= curwin->w_cursor.lnum && + curwin->w_botline > curwin->w_cursor.lnum && + curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) { + long max = 0; + + // Use maximum of all visible lines. Remember the lnum of the + // longest line, closest to the cursor line. Used when scrolling + // below. + for (linenr_T lnum = curwin->w_topline; lnum < curwin->w_botline; lnum++) { + colnr_T len = scroll_line_len(lnum); + if (len > (colnr_T)max) { + max = len; + ret = lnum; + } else if (len == (colnr_T)max + && abs((int)(lnum - curwin->w_cursor.lnum)) + < abs((int)(ret - curwin->w_cursor.lnum))) { + ret = lnum; + } + } + } else { + // Use cursor line only. + ret = curwin->w_cursor.lnum; + } + + return ret; +} + +/// +/// Do a horizontal scroll. Return TRUE if the cursor moved, FALSE otherwise. +/// +bool mouse_scroll_horiz(int dir) +{ + if (curwin->w_p_wrap) { + return false; + } + + int step = 6; + if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { + step = curwin->w_width; + } + + int leftcol = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : +step); + if (leftcol < 0) { + leftcol = 0; + } + + if (curwin->w_leftcol == leftcol) { + return false; + } + + curwin->w_leftcol = (colnr_T)leftcol; + + // When the line of the cursor is too short, move the cursor to the + // longest visible line. + if (!virtual_active() + && (colnr_T)leftcol > scroll_line_len(curwin->w_cursor.lnum)) { + curwin->w_cursor.lnum = find_longest_lnum(); + curwin->w_cursor.col = 0; + } + + return leftcol_changed(); +} diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index c824bcc8f0..0149f7c7c0 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -34,6 +34,12 @@ #define MOUSE_X1 0x300 // Mouse-button X1 (6th) #define MOUSE_X2 0x400 // Mouse-button X2 +// Direction for nv_mousescroll() and ins_mousescroll() +#define MSCR_DOWN 0 // DOWN must be FALSE +#define MSCR_UP 1 +#define MSCR_LEFT -1 +#define MSCR_RIGHT -2 + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mouse.h.generated.h" diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index bf384e3379..6cc56ba3dd 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -14,7 +14,7 @@ #include "nvim/vim.h" #include "nvim/memory.h" #include "nvim/log.h" -#include "nvim/tempfile.h" +#include "nvim/fileio.h" #include "nvim/path.h" #include "nvim/strings.h" diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 5b7c4b68b1..75ee11bc9d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2598,11 +2598,10 @@ do_mouse ( end_visual.col = leftcol; else end_visual.col = rightcol; - if (curwin->w_cursor.lnum < - (start_visual.lnum + end_visual.lnum) / 2) - end_visual.lnum = end_visual.lnum; - else + if (curwin->w_cursor.lnum >= + (start_visual.lnum + end_visual.lnum) / 2) { end_visual.lnum = start_visual.lnum; + } /* move VIsual to the right column */ start_visual = curwin->w_cursor; /* save the cursor pos */ @@ -3244,9 +3243,9 @@ void clear_showcmd(void) top = curwin->w_cursor.lnum; bot = VIsual.lnum; } - /* Include closed folds as a whole. */ - hasFolding(top, &top, NULL); - hasFolding(bot, NULL, &bot); + // Include closed folds as a whole. + (void)hasFolding(top, &top, NULL); + (void)hasFolding(bot, NULL, &bot); lines = bot - top + 1; if (VIsual_mode == Ctrl_V) { @@ -3927,6 +3926,8 @@ static void nv_mousescroll(cmdarg_T *cap) cap->count0 = 3; nv_scroll_line(cap); } + } else { + mouse_scroll_horiz(cap->arg); } curwin->w_redr_status = true; @@ -5152,9 +5153,10 @@ static void nv_gotofile(cmdarg_T *cap) ptr = grab_file_name(cap->count1, &lnum); if (ptr != NULL) { - /* do autowrite if necessary */ - if (curbufIsChanged() && curbuf->b_nwindows <= 1 && !P_HID(curbuf)) - autowrite(curbuf, false); + // do autowrite if necessary + if (curbufIsChanged() && curbuf->b_nwindows <= 1 && !P_HID(curbuf)) { + (void)autowrite(curbuf, false); + } setpcmark(); (void)do_ecmd(0, ptr, NULL, NULL, ECMD_LAST, P_HID(curbuf) ? ECMD_HIDE : 0, curwin); diff --git a/src/nvim/option.c b/src/nvim/option.c index 816900d2aa..df271e9eb5 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1177,28 +1177,27 @@ do_set ( errmsg = e_invarg; goto skip; } - arg[len] = NUL; /* put NUL after name */ - if (arg[1] == 't' && arg[2] == '_') /* could be term code */ - opt_idx = findoption(arg + 1); - arg[len++] = '>'; /* restore '>' */ - if (opt_idx == -1) + if (arg[1] == 't' && arg[2] == '_') { // could be term code + opt_idx = findoption_len(arg + 1, (size_t) (len - 1)); + } + len++; + if (opt_idx == -1) { key = find_key_option(arg + 1); + } } else { len = 0; - /* - * The two characters after "t_" may not be alphanumeric. - */ - if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3]) + // The two characters after "t_" may not be alphanumeric. + if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3]) { len = 4; - else - while (ASCII_ISALNUM(arg[len]) || arg[len] == '_') - ++len; - nextchar = arg[len]; - arg[len] = NUL; /* put NUL after name */ - opt_idx = findoption(arg); - arg[len] = nextchar; /* restore nextchar */ - if (opt_idx == -1) + } else { + while (ASCII_ISALNUM(arg[len]) || arg[len] == '_') { + len++; + } + } + opt_idx = findoption_len(arg, (size_t) len); + if (opt_idx == -1) { key = find_key_option(arg); + } } /* remember character after option name */ @@ -2057,6 +2056,7 @@ static void didset_options(void) (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); + (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); (void)spell_check_msm(); (void)spell_check_sps(); @@ -2144,6 +2144,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_ep); check_string_option(&buf->b_p_path); check_string_option(&buf->b_p_tags); + check_string_option(&buf->b_p_tc); check_string_option(&buf->b_p_dict); check_string_option(&buf->b_p_tsr); check_string_option(&buf->b_p_lw); @@ -2966,7 +2967,8 @@ did_set_string_option ( /* 'pastetoggle': translate key codes like in a mapping */ else if (varp == &p_pt) { if (*p_pt) { - (void)replace_termcodes(p_pt, &p, TRUE, TRUE, FALSE); + (void)replace_termcodes(p_pt, STRLEN(p_pt), &p, true, true, false, + CPO_TO_CPO_FLAGS); if (p != NULL) { if (new_value_alloced) free_string_option(p_pt); @@ -2986,6 +2988,24 @@ did_set_string_option ( if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { errmsg = e_invarg; } + } else if (gvarp == &p_tc) { // 'tagcase' + unsigned int *flags; + + if (opt_flags & OPT_LOCAL) { + p = curbuf->b_p_tc; + flags = &curbuf->b_tc_flags; + } else { + p = p_tc; + flags = &tc_flags; + } + + if ((opt_flags & OPT_LOCAL) && *p == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else if (*p == NUL + || opt_strings_flags(p, p_tc_values, flags, false) != OK) { + errmsg = e_invarg; + } } else if (varp == &p_cmp) { // 'casemap' if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) errmsg = e_invarg; @@ -4286,14 +4306,16 @@ static void check_redraw(uint32_t flags) redraw_all_later(NOT_VALID); } -/* - * Find index for option 'arg'. - * Return -1 if not found. - */ -static int findoption(char_u *arg) +/// Find index for named option +/// +/// @param[in] arg Option to find index for. +/// @param[in] len Length of the option. +/// +/// @return Index of the option or -1 if option was not found. +int findoption_len(const char_u *const arg, const size_t len) { - char *s, *p; - static short quick_tab[27] = {0, 0}; /* quick access table */ + char *s, *p; + static int quick_tab[27] = { 0, 0 }; // quick access table int is_term_opt; /* @@ -4317,25 +4339,31 @@ static int findoption(char_u *arg) /* * Check for name starting with an illegal character. */ - if (arg[0] < 'a' || arg[0] > 'z') + if (len == 0 || arg[0] < 'a' || arg[0] > 'z') { return -1; + } int opt_idx; - is_term_opt = (arg[0] == 't' && arg[1] == '_'); - if (is_term_opt) + is_term_opt = (len > 2 && arg[0] == 't' && arg[1] == '_'); + if (is_term_opt) { opt_idx = quick_tab[26]; - else + } else { opt_idx = quick_tab[CharOrdLow(arg[0])]; + } + // Match full name for (; (s = options[opt_idx].fullname) != NULL; opt_idx++) { - if (STRCMP(arg, s) == 0) /* match full name */ + if (STRNCMP(arg, s, len) == 0 && s[len] == NUL) { break; + } } if (s == NULL && !is_term_opt) { opt_idx = quick_tab[CharOrdLow(arg[0])]; + // Match short name for (; options[opt_idx].fullname != NULL; opt_idx++) { s = options[opt_idx].shortname; - if (s != NULL && STRCMP(arg, s) == 0) /* match short name */ + if (s != NULL && STRNCMP(arg, s, len) == 0 && s[len] == NUL) { break; + } s = NULL; } } @@ -4403,6 +4431,15 @@ bool set_tty_option(char *name, char *value) } /* + * Find index for option 'arg'. + * Return -1 if not found. + */ +static int findoption(char_u *arg) +{ + return findoption_len(arg, STRLEN(arg)); +} + +/* * Get the value for an option. * * Returns: @@ -4658,27 +4695,32 @@ char_u *get_highlight_default(void) /* * Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number. */ -static int find_key_option(char_u *arg) +int find_key_option_len(const char_u *arg, size_t len) { int key; int modifiers; - /* - * Don't use get_special_key_code() for t_xx, we don't want it to call - * add_termcap_entry(). - */ - if (arg[0] == 't' && arg[1] == '_' && arg[2] && arg[3]) + // Don't use get_special_key_code() for t_xx, we don't want it to call + // add_termcap_entry(). + if (len >= 4 && arg[0] == 't' && arg[1] == '_') { key = TERMCAP2KEY(arg[2], arg[3]); - else { - --arg; /* put arg at the '<' */ + } else { + arg--; // put arg at the '<' modifiers = 0; - key = find_special_key(&arg, &modifiers, TRUE, TRUE); - if (modifiers) /* can't handle modifiers here */ + key = find_special_key(&arg, len + 1, &modifiers, true, true); + if (modifiers) { // can't handle modifiers here key = 0; + } } return key; } +static int find_key_option(const char_u *arg) +{ + return find_key_option_len(arg, STRLEN(arg)); +} + + /* * if 'all' == 0: show changed options * if 'all' == 1: show all normal options @@ -5114,6 +5156,10 @@ void unset_global_local_option(char *name, void *from) case PV_TAGS: clear_string_option(&buf->b_p_tags); break; + case PV_TC: + clear_string_option(&buf->b_p_tc); + buf->b_tc_flags = 0; + break; case PV_DEF: clear_string_option(&buf->b_p_def); break; @@ -5167,6 +5213,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_PATH: return (char_u *)&(curbuf->b_p_path); case PV_AR: return (char_u *)&(curbuf->b_p_ar); case PV_TAGS: return (char_u *)&(curbuf->b_p_tags); + case PV_TC: return (char_u *)&(curbuf->b_p_tc); case PV_DEF: return (char_u *)&(curbuf->b_p_def); case PV_INC: return (char_u *)&(curbuf->b_p_inc); case PV_DICT: return (char_u *)&(curbuf->b_p_dict); @@ -5204,6 +5251,8 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_ar) : p->var; case PV_TAGS: return *curbuf->b_p_tags != NUL ? (char_u *)&(curbuf->b_p_tags) : p->var; + case PV_TC: return *curbuf->b_p_tc != NUL + ? (char_u *)&(curbuf->b_p_tc) : p->var; case PV_BKC: return *curbuf->b_p_bkc != NUL ? (char_u *)&(curbuf->b_p_bkc) : p->var; case PV_DEF: return *curbuf->b_p_def != NUL @@ -5583,6 +5632,8 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_kp = empty_option; buf->b_p_path = empty_option; buf->b_p_tags = empty_option; + buf->b_p_tc = empty_option; + buf->b_tc_flags = 0; buf->b_p_def = empty_option; buf->b_p_inc = empty_option; buf->b_p_inex = vim_strsave(p_inex); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index bbe40873ec..87a9a7398c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -597,6 +597,14 @@ static char *(p_swb_values[]) = #define SWB_NEWTAB 0x008 #define SWB_VSPLIT 0x010 EXTERN int p_tbs; ///< 'tagbsearch' +EXTERN char_u *p_tc; ///< 'tagcase' +EXTERN unsigned tc_flags; ///< flags from 'tagcase' +#ifdef IN_OPTION_C +static char *(p_tc_values[]) = { "followic", "ignore", "match", NULL }; +#endif +#define TC_FOLLOWIC 0x01 +#define TC_IGNORE 0x02 +#define TC_MATCH 0x04 EXTERN long p_tl; ///< 'taglength' EXTERN int p_tr; ///< 'tagrelative' EXTERN char_u *p_tags; ///< 'tags' @@ -737,6 +745,7 @@ enum { , BV_SW , BV_SWF , BV_TAGS + , BV_TC , BV_TS , BV_TW , BV_TX diff --git a/src/nvim/options.lua b/src/nvim/options.lua index df77c374ec..a743e8c605 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2332,6 +2332,13 @@ return { defaults={if_true={vi=true}} }, { + full_name='tagcase', abbreviation='tc', + type='string', scope={'global', 'buffer'}, + vim=true, + varname='p_tc', + defaults={if_true={vi="followic", vim="followic"}} + }, + { full_name='taglength', abbreviation='tl', type='number', scope={'global'}, vi_def=true, diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 34d8fde4f1..bc2d37764d 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -59,6 +59,23 @@ int os_dirname(char_u *buf, size_t len) return OK; } +/// Check if the given path is a directory and not a symlink to a directory. +/// @return `true` if `name` is a directory and NOT a symlink to a directory. +/// `false` if `name` is not a directory or if an error occurred. +bool os_isrealdir(const char_u *name) + FUNC_ATTR_NONNULL_ALL +{ + uv_fs_t request; + if (uv_fs_lstat(&fs_loop, &request, (char *)name, NULL) != kLibuvSuccess) { + return false; + } + if (S_ISLNK(request.statbuf.st_mode)) { + return false; + } else { + return S_ISDIR(request.statbuf.st_mode); + } +} + /// Check if the given path is a directory or not. /// /// @return `true` if `fname` is a directory. diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index f317fd6b5a..e0826aa272 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -175,8 +175,9 @@ size_t input_enqueue(String keys) char *ptr = keys.data, *end = ptr + keys.size; while (rbuffer_space(input_buffer) >= 6 && ptr < end) { - uint8_t buf[6] = {0}; - unsigned int new_size = trans_special((uint8_t **)&ptr, buf, true); + uint8_t buf[6] = { 0 }; + unsigned int new_size = trans_special((const uint8_t **)&ptr, keys.size, + buf, true); if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index cb9a58cc77..2a859a0f7a 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -34,7 +34,6 @@ #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/tempfile.h" #include "nvim/ui.h" #include "nvim/types.h" #include "nvim/os/os.h" diff --git a/src/nvim/path.c b/src/nvim/path.c index aaf54bc5b4..29ff62ef77 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -604,7 +604,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, starstar = true; // convert the file pattern to a regexp pattern - int starts_with_dot = (*s == '.'); + int starts_with_dot = *s == '.'; char_u *pat = file_pat_to_reg_pat(s, e, NULL, false); if (pat == NULL) { xfree(buf); @@ -647,9 +647,12 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, if (os_file_is_readable(dirpath) && os_scandir(&dir, dirpath)) { // Find all matching entries. char_u *name; - scandir_next_with_dots(NULL /* initialize */); - while((name = (char_u *) scandir_next_with_dots(&dir)) && name != NULL) { - if ((name[0] != '.' || starts_with_dot) + scandir_next_with_dots(NULL); // initialize + while ((name = (char_u *) scandir_next_with_dots(&dir)) && name != NULL) { + if ((name[0] != '.' + || starts_with_dot + || ((flags & EW_DODOT) + && name[1] != NUL && (name[1] != '.' || name[2] != NUL))) && ((regmatch.regprog != NULL && vim_regexec(®match, name, 0)) || ((flags & EW_NOTWILD) && fnamencmp(path + (s - buf), name, e - s) == 0))) { @@ -1220,7 +1223,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, recursive = false; - return (ga.ga_data != NULL) ? OK : FAIL; + return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? OK : FAIL; } @@ -1923,7 +1926,7 @@ int expand_wildcards_eval(char_u **pat, int *num_file, char_u ***file, /// If FAIL is returned, *num_file and *file are either /// unchanged or *num_file is set to 0 and *file is set to /// NULL or points to "". -int expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, +int expand_wildcards(int num_pat, char_u **pat, int *num_files, char_u ***files, int flags) { int retval; @@ -1931,7 +1934,7 @@ int expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, char_u *p; int non_suf_match; /* number without matching suffix */ - retval = gen_expand_wildcards(num_pat, pat, num_file, file, flags); + retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags); /* When keeping all matches, return here */ if ((flags & EW_KEEPALL) || retval == FAIL) @@ -1943,18 +1946,20 @@ int expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, if (*p_wig) { char_u *ffname; - /* check all files in (*file)[] */ - for (i = 0; i < *num_file; ++i) { - ffname = (char_u *)FullName_save((char *)(*file)[i], FALSE); - if (ffname == NULL) /* out of memory */ + // check all filess in (*files)[] + for (i = 0; i < *num_files; i++) { + ffname = (char_u *)FullName_save((char *)(*files)[i], false); + if (ffname == NULL) { // out of memory break; - if (match_file_list(p_wig, (*file)[i], ffname)) { - /* remove this matching file from the list */ - xfree((*file)[i]); - for (j = i; j + 1 < *num_file; ++j) - (*file)[j] = (*file)[j + 1]; - --*num_file; - --i; + } + if (match_file_list(p_wig, (*files)[i], ffname)) { + // remove this matching files from the list + xfree((*files)[i]); + for (j = i; j + 1 < *num_files; j++) { + (*files)[j] = (*files)[j + 1]; + } + (*num_files)--; + i--; } xfree(ffname); } @@ -1963,26 +1968,28 @@ int expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, /* * Move the names where 'suffixes' match to the end. */ - if (*num_file > 1) { + if (*num_files > 1) { non_suf_match = 0; - for (i = 0; i < *num_file; ++i) { - if (!match_suffix((*file)[i])) { - /* - * Move the name without matching suffix to the front - * of the list. - */ - p = (*file)[i]; - for (j = i; j > non_suf_match; --j) - (*file)[j] = (*file)[j - 1]; - (*file)[non_suf_match++] = p; + for (i = 0; i < *num_files; i++) { + if (!match_suffix((*files)[i])) { + // + // Move the name without matching suffix to the front + // of the list. + // + p = (*files)[i]; + for (j = i; j > non_suf_match; j--) { + (*files)[j] = (*files)[j - 1]; + } + (*files)[non_suf_match++] = p; } } } // Free empty array of matches - if (*num_file == 0) { - xfree(*file); - *file = NULL; + if (*num_files == 0) { + xfree(*files); + *files = NULL; + return FAIL; } return retval; diff --git a/src/nvim/path.h b/src/nvim/path.h index eac367d0ac..88e5935c24 100644 --- a/src/nvim/path.h +++ b/src/nvim/path.h @@ -21,6 +21,8 @@ /* Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND * is used when executing commands and EW_SILENT for interactive expanding. */ #define EW_ALLLINKS 0x1000 // also links not pointing to existing file +#define EW_DODOT 0x4000 // also files starting with a dot +#define EW_EMPTYOK 0x8000 // no matches is not an error /// Return value for the comparison of two files. Also @see path_full_compare. typedef enum file_comparison { diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 28c0425e2c..97db69d3f3 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -39,7 +39,6 @@ #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/ui.h" -#include "nvim/tempfile.h" #include "nvim/window.h" #include "nvim/os/os.h" #include "nvim/os/input.h" diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index e2849fb17a..27409352b5 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -3464,13 +3464,12 @@ static char *pim_info(nfa_pim_T *pim) #endif -/* Used during execution: whether a match has been found. */ +// Used during execution: whether a match has been found. static int nfa_match; +static proftime_T *nfa_time_limit; +static int nfa_time_count; - -/* - * Copy postponed invisible match info from "from" to "to". - */ +// Copy postponed invisible match info from "from" to "to". static void copy_pim(nfa_pim_T *to, nfa_pim_T *from) { to->result = from->result; @@ -4566,7 +4565,7 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T if (log_fd != NULL) { fprintf(log_fd, "****************************\n"); fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); - fprintf(log_fd, "MATCH = %s\n", result == TRUE ? "OK" : "FALSE"); + fprintf(log_fd, "MATCH = %s\n", !result ? "FALSE" : "OK"); fprintf(log_fd, "****************************\n"); } else { EMSG(_( @@ -4813,22 +4812,21 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) #undef PTR2LEN } -/* - * Main matching routine. - * - * Run NFA to determine whether it matches reginput. - * - * When "nfa_endp" is not NULL it is a required end-of-match position. - * - * Return TRUE if there is a match, FALSE otherwise. - * When there is a match "submatch" contains the positions. - * Note: Caller must ensure that: start != NULL. - */ -static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *submatch, regsubs_T *m) +/// Main matching routine. +/// +/// Run NFA to determine whether it matches reginput. +/// +/// When "nfa_endp" is not NULL it is a required end-of-match position. +/// +/// Return TRUE if there is a match, FALSE otherwise. +/// When there is a match "submatch" contains the positions. +/// Note: Caller must ensure that: start != NULL. +static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, + regsubs_T *submatch, regsubs_T *m) { int result; int flag = 0; - int go_to_nextline = FALSE; + bool go_to_nextline = false; nfa_thread_T *t; nfa_list_T list[2]; int listidx; @@ -4845,18 +4843,22 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (debug == NULL) { EMSG2(_("(NFA) COULD NOT OPEN %s !"), NFA_REGEXP_DEBUG_LOG); - return FALSE; + return false; } #endif - /* Some patterns may take a long time to match, especially when using - * recursive_regmatch(). Allow interrupting them with CTRL-C. */ + // Some patterns may take a long time to match, especially when using + // recursive_regmatch(). Allow interrupting them with CTRL-C. fast_breakcheck(); - if (got_int) - return FALSE; + if (got_int) { + return false; + } + if (nfa_time_limit != NULL && profile_passed_limit(*nfa_time_limit)) { + return false; + } - nfa_match = FALSE; + nfa_match = false; - /* Allocate memory for the lists of nodes. */ + // Allocate memory for the lists of nodes. size_t size = (nstate + 1) * sizeof(nfa_thread_T); list[0].t = xmalloc(size); list[0].len = nstate + 1; @@ -4924,7 +4926,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } if (curc == NUL) { clen = 0; - go_to_nextline = FALSE; + go_to_nextline = false; } /* swap lists */ @@ -5007,7 +5009,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (enc_utf8 && !ireg_icombine && utf_iscomposing(curc)) { break; } - nfa_match = TRUE; + nfa_match = true; copy_sub(&submatch->norm, &t->subs.norm); if (nfa_has_zsubexpr) copy_sub(&submatch->synt, &t->subs.synt); @@ -5072,10 +5074,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm fprintf(log_fd, "Match found:\n"); log_subsexpr(m); #endif - nfa_match = TRUE; - /* See comment above at "goto nextchar". */ - if (nextlist->n == 0) + nfa_match = true; + // See comment above at "goto nextchar". + if (nextlist->n == 0) { clen = 0; + } goto nextchar; case NFA_START_INVISIBLE: @@ -5092,8 +5095,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm failure_chance(t->state->out, 0), failure_chance(t->state->out1->out, 0)); #endif - /* Do it directly if there already is a PIM or when - * nfa_postprocess() detected it will work better. */ + // Do it directly if there already is a PIM or when + // nfa_postprocess() detected it will work better. if (t->pim.result != NFA_PIM_UNUSED || t->state->c == NFA_START_INVISIBLE_FIRST || t->state->c == NFA_START_INVISIBLE_NEG_FIRST @@ -5101,42 +5104,40 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm || t->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) { int in_use = m->norm.in_use; - /* Copy submatch info for the recursive call, opposite - * of what happens on success below. */ + // Copy submatch info for the recursive call, opposite + // of what happens on success below. copy_sub_off(&m->norm, &t->subs.norm); if (nfa_has_zsubexpr) copy_sub_off(&m->synt, &t->subs.synt); - /* - * First try matching the invisible match, then what - * follows. - */ - result = recursive_regmatch(t->state, NULL, prog, - submatch, m, &listids); + // First try matching the invisible match, then what + // follows. + result = recursive_regmatch(t->state, NULL, prog, submatch, m, + &listids); if (result == NFA_TOO_EXPENSIVE) { nfa_match = result; goto theend; } - /* for \@! and \@<! it is a match when the result is - * FALSE */ + // for \@! and \@<! it is a match when the result is + // FALSE if (result != (t->state->c == NFA_START_INVISIBLE_NEG || t->state->c == NFA_START_INVISIBLE_NEG_FIRST || t->state->c == NFA_START_INVISIBLE_BEFORE_NEG || t->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { - /* Copy submatch info from the recursive call */ + // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &m->norm); if (nfa_has_zsubexpr) copy_sub_off(&t->subs.synt, &m->synt); - /* If the pattern has \ze and it matched in the - * sub pattern, use it. */ + // If the pattern has \ze and it matched in the + // sub pattern, use it. copy_ze_off(&t->subs.norm, &m->norm); - /* t->state->out1 is the corresponding - * END_INVISIBLE node; Add its out to the current - * list (zero-width match). */ + // t->state->out1 is the corresponding + // END_INVISIBLE node; Add its out to the current + // list (zero-width match). add_here = true; add_state = t->state->out1->out; } @@ -5144,12 +5145,10 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } else { nfa_pim_T pim; - /* - * First try matching what follows. Only if a match - * is found verify the invisible match matches. Add a - * nfa_pim_T to the following states, it contains info - * about the invisible match. - */ + // First try matching what follows. Only if a match + // is found verify the invisible match matches. Add a + // nfa_pim_T to the following states, it contains info + // about the invisible match. pim.state = t->state; pim.result = NFA_PIM_TODO; pim.subs.norm.in_use = 0; @@ -5160,9 +5159,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } else pim.end.ptr = reginput; - /* t->state->out1 is the corresponding END_INVISIBLE - * node; Add its out to the current list (zero-width - * match). */ + // t->state->out1 is the corresponding END_INVISIBLE + // node; Add its out to the current list (zero-width + // match). addstate_here(thislist, t->state->out1->out, &t->subs, &pim, &listidx); } @@ -5176,8 +5175,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm int skip_lid = 0; #endif - /* There is no point in trying to match the pattern if the - * output state is not going to be added to the list. */ + // There is no point in trying to match the pattern if the + // output state is not going to be added to the list. if (state_in_list(nextlist, t->state->out1->out, &t->subs)) { skip = t->state->out1->out; #ifdef REGEXP_DEBUG @@ -5206,15 +5205,16 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #endif break; } - /* Copy submatch info to the recursive call, opposite of what - * happens afterwards. */ + // Copy submatch info to the recursive call, opposite of what + // happens afterwards. copy_sub_off(&m->norm, &t->subs.norm); - if (nfa_has_zsubexpr) + if (nfa_has_zsubexpr) { copy_sub_off(&m->synt, &t->subs.synt); + } - /* First try matching the pattern. */ - result = recursive_regmatch(t->state, NULL, prog, - submatch, m, &listids); + // First try matching the pattern. + result = recursive_regmatch(t->state, NULL, prog, submatch, m, + &listids); if (result == NFA_TOO_EXPENSIVE) { nfa_match = result; goto theend; @@ -5226,36 +5226,38 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm fprintf(log_fd, "NFA_START_PATTERN matches:\n"); log_subsexpr(m); #endif - /* Copy submatch info from the recursive call */ + // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &m->norm); - if (nfa_has_zsubexpr) + if (nfa_has_zsubexpr) { copy_sub_off(&t->subs.synt, &m->synt); - /* Now we need to skip over the matched text and then - * continue with what follows. */ - if (REG_MULTI) - /* TODO: multi-line match */ + } + // Now we need to skip over the matched text and then + // continue with what follows. + if (REG_MULTI) { + // TODO(RE): multi-line match bytelen = m->norm.list.multi[0].end_col - (int)(reginput - regline); - else + } else { bytelen = (int)(m->norm.list.line[0].end - reginput); + } #ifdef REGEXP_DEBUG fprintf(log_fd, "NFA_START_PATTERN length: %d\n", bytelen); #endif if (bytelen == 0) { - /* empty match, output of corresponding - * NFA_END_PATTERN/NFA_SKIP to be used at current - * position */ + // empty match, output of corresponding + // NFA_END_PATTERN/NFA_SKIP to be used at current + // position add_here = true; add_state = t->state->out1->out->out; } else if (bytelen <= clen) { - /* match current character, output of corresponding - * NFA_END_PATTERN to be used at next position. */ + // match current character, output of corresponding + // NFA_END_PATTERN to be used at next position. add_state = t->state->out1->out->out; add_off = clen; } else { - /* skip over the matched characters, set character - * count in NFA_SKIP */ + // skip over the matched characters, set character + // count in NFA_SKIP add_state = t->state->out1->out; add_off = bytelen; add_count = bytelen - clen; @@ -5279,23 +5281,25 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm break; case NFA_BOW: - result = TRUE; + result = true; - if (curc == NUL) - result = FALSE; - else if (has_mbyte) { + if (curc == NUL) { + result = false; + } else if (has_mbyte) { int this_class; - /* Get class of current and previous char (if it exists). */ + // Get class of current and previous char (if it exists). this_class = mb_get_class_buf(reginput, reg_buf); - if (this_class <= 1) - result = FALSE; - else if (reg_prev_class() == this_class) - result = FALSE; + if (this_class <= 1) { + result = false; + } else if (reg_prev_class() == this_class) { + result = false; + } } else if (!vim_iswordc_buf(curc, reg_buf) || (reginput > regline - && vim_iswordc_buf(reginput[-1], reg_buf))) - result = FALSE; + && vim_iswordc_buf(reginput[-1], reg_buf))) { + result = false; + } if (result) { add_here = true; add_state = t->state->out; @@ -5303,22 +5307,24 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm break; case NFA_EOW: - result = TRUE; - if (reginput == regline) - result = FALSE; - else if (has_mbyte) { + result = true; + if (reginput == regline) { + result = false; + } else if (has_mbyte) { int this_class, prev_class; - /* Get class of current and previous char (if it exists). */ + // Get class of current and previous char (if it exists). this_class = mb_get_class_buf(reginput, reg_buf); prev_class = reg_prev_class(); if (this_class == prev_class - || prev_class == 0 || prev_class == 1) - result = FALSE; + || prev_class == 0 || prev_class == 1) { + result = false; + } } else if (!vim_iswordc_buf(reginput[-1], reg_buf) || (reginput[0] != NUL - && vim_iswordc_buf(curc, reg_buf))) - result = FALSE; + && vim_iswordc_buf(curc, reg_buf))) { + result = false; + } if (result) { add_here = true; add_state = t->state->out; @@ -5353,30 +5359,31 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm sta = t->state->out; len = 0; if (utf_iscomposing(sta->c)) { - /* Only match composing character(s), ignore base - * character. Used for ".{composing}" and "{composing}" - * (no preceding character). */ + // Only match composing character(s), ignore base + // character. Used for ".{composing}" and "{composing}" + // (no preceding character). len += mb_char2len(mc); } if (ireg_icombine && len == 0) { - /* If \Z was present, then ignore composing characters. - * When ignoring the base character this always matches. */ - if (sta->c != curc) + // If \Z was present, then ignore composing characters. + // When ignoring the base character this always matches. + if (sta->c != curc) { result = FAIL; - else + } else { result = OK; - while (sta->c != NFA_END_COMPOSING) + } + while (sta->c != NFA_END_COMPOSING) { sta = sta->out; - } - /* Check base character matches first, unless ignored. */ - else if (len > 0 || mc == sta->c) { + } + } else if (len > 0 || mc == sta->c) { + // Check base character matches first, unless ignored. if (len == 0) { len += mb_char2len(mc); sta = sta->out; } - /* We don't care about the order of composing characters. - * Get them into cchars[] first. */ + // We don't care about the order of composing characters. + // Get them into cchars[] first. while (len < clen) { mc = mb_ptr2char(reginput + len); cchars[ccount++] = mc; @@ -5385,9 +5392,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm break; } - /* Check that each composing char in the pattern matches a - * composing char in the text. We do not check if all - * composing chars are matched. */ + // Check that each composing char in the pattern matches a + // composing char in the text. We do not check if all + // composing chars are matched. result = OK; while (sta->c != NFA_END_COMPOSING) { for (j = 0; j < ccount; ++j) @@ -5402,7 +5409,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } else result = FAIL; - end = t->state->out1; /* NFA_END_COMPOSING */ + end = t->state->out1; // NFA_END_COMPOSING ADD_STATE_IF_MATCH(end); break; } @@ -5410,13 +5417,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm case NFA_NEWL: if (curc == NUL && !reg_line_lbr && REG_MULTI && reglnum <= reg_maxline) { - go_to_nextline = TRUE; - /* Pass -1 for the offset, which means taking the position - * at the start of the next line. */ + go_to_nextline = true; + // Pass -1 for the offset, which means taking the position + // at the start of the next line. add_state = t->state->out; add_off = -1; } else if (curc == '\n' && reg_line_lbr) { - /* match \n as if it is an ordinary character */ + // match \n as if it is an ordinary character add_state = t->state->out; add_off = 1; } @@ -5425,16 +5432,17 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm case NFA_START_COLL: case NFA_START_NEG_COLL: { - /* What follows is a list of characters, until NFA_END_COLL. - * One of them must match or none of them must match. */ + // What follows is a list of characters, until NFA_END_COLL. + // One of them must match or none of them must match. nfa_state_T *state; int result_if_matched; int c1, c2; - /* Never match EOL. If it's part of the collection it is added - * as a separate state with an OR. */ - if (curc == NUL) + // Never match EOL. If it's part of the collection it is added + // as a separate state with an OR. + if (curc == NUL) { break; + } state = t->state->out; result_if_matched = (t->state->c == NFA_START_COLL); @@ -5445,7 +5453,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } if (state->c == NFA_RANGE_MIN) { c1 = state->val; - state = state->out; /* advance to NFA_RANGE_MAX */ + state = state->out; // advance to NFA_RANGE_MAX c2 = state->val; #ifdef REGEXP_DEBUG fprintf(log_fd, "NFA_RANGE_MIN curc=%d c1=%d c2=%d\n", @@ -5478,8 +5486,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm state = state->out; } if (result) { - /* next state is in out of the NFA_END_COLL, out1 of - * START points to the END state */ + // next state is in out of the NFA_END_COLL, out1 of + // START points to the END state add_state = t->state->out1->out; add_off = clen; } @@ -5487,7 +5495,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } case NFA_ANY: - /* Any char except '\0', (end of input) does not match. */ + // Any char except '\0', (end of input) does not match. if (curc > 0) { add_state = t->state->out; add_off = clen; @@ -5506,157 +5514,155 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm add_state = t->state->out; break; - /* - * Character classes like \a for alpha, \d for digit etc. - */ - case NFA_IDENT: /* \i */ + // Character classes like \a for alpha, \d for digit etc. + case NFA_IDENT: // \i result = vim_isIDc(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_SIDENT: /* \I */ + case NFA_SIDENT: // \I result = !ascii_isdigit(curc) && vim_isIDc(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_KWORD: /* \k */ + case NFA_KWORD: // \k result = vim_iswordp_buf(reginput, reg_buf); ADD_STATE_IF_MATCH(t->state); break; - case NFA_SKWORD: /* \K */ + case NFA_SKWORD: // \K result = !ascii_isdigit(curc) && vim_iswordp_buf(reginput, reg_buf); ADD_STATE_IF_MATCH(t->state); break; - case NFA_FNAME: /* \f */ + case NFA_FNAME: // \f result = vim_isfilec(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_SFNAME: /* \F */ + case NFA_SFNAME: // \F result = !ascii_isdigit(curc) && vim_isfilec(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_PRINT: /* \p */ + case NFA_PRINT: // \p result = vim_isprintc(PTR2CHAR(reginput)); ADD_STATE_IF_MATCH(t->state); break; - case NFA_SPRINT: /* \P */ + case NFA_SPRINT: // \P result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(reginput)); ADD_STATE_IF_MATCH(t->state); break; - case NFA_WHITE: /* \s */ + case NFA_WHITE: // \s result = ascii_iswhite(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NWHITE: /* \S */ + case NFA_NWHITE: // \S result = curc != NUL && !ascii_iswhite(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_DIGIT: /* \d */ + case NFA_DIGIT: // \d result = ri_digit(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NDIGIT: /* \D */ + case NFA_NDIGIT: // \D result = curc != NUL && !ri_digit(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_HEX: /* \x */ + case NFA_HEX: // \x result = ri_hex(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NHEX: /* \X */ + case NFA_NHEX: // \X result = curc != NUL && !ri_hex(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_OCTAL: /* \o */ + case NFA_OCTAL: // \o result = ri_octal(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NOCTAL: /* \O */ + case NFA_NOCTAL: // \O result = curc != NUL && !ri_octal(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_WORD: /* \w */ + case NFA_WORD: // \w result = ri_word(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NWORD: /* \W */ + case NFA_NWORD: // \W result = curc != NUL && !ri_word(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_HEAD: /* \h */ + case NFA_HEAD: // \h result = ri_head(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NHEAD: /* \H */ + case NFA_NHEAD: // \H result = curc != NUL && !ri_head(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_ALPHA: /* \a */ + case NFA_ALPHA: // \a result = ri_alpha(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NALPHA: /* \A */ + case NFA_NALPHA: // \A result = curc != NUL && !ri_alpha(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_LOWER: /* \l */ + case NFA_LOWER: // \l result = ri_lower(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NLOWER: /* \L */ + case NFA_NLOWER: // \L result = curc != NUL && !ri_lower(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_UPPER: /* \u */ + case NFA_UPPER: // \u result = ri_upper(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NUPPER: /* \U */ + case NFA_NUPPER: // \U result = curc != NUL && !ri_upper(curc); ADD_STATE_IF_MATCH(t->state); break; - case NFA_LOWER_IC: /* [a-z] */ + case NFA_LOWER_IC: // [a-z] result = ri_lower(curc) || (ireg_ic && ri_upper(curc)); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NLOWER_IC: /* [^a-z] */ + case NFA_NLOWER_IC: // [^a-z] result = curc != NUL && !(ri_lower(curc) || (ireg_ic && ri_upper(curc))); ADD_STATE_IF_MATCH(t->state); break; - case NFA_UPPER_IC: /* [A-Z] */ + case NFA_UPPER_IC: // [A-Z] result = ri_upper(curc) || (ireg_ic && ri_lower(curc)); ADD_STATE_IF_MATCH(t->state); break; - case NFA_NUPPER_IC: /* ^[A-Z] */ + case NFA_NUPPER_IC: // [^A-Z] result = curc != NUL && !(ri_upper(curc) || (ireg_ic && ri_lower(curc))); ADD_STATE_IF_MATCH(t->state); @@ -5680,7 +5686,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm case NFA_ZREF7: case NFA_ZREF8: case NFA_ZREF9: - /* \1 .. \9 \z1 .. \z9 */ + // \1 .. \9 \z1 .. \z9 { int subidx; int bytelen; @@ -5695,18 +5701,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (result) { if (bytelen == 0) { - /* empty match always works, output of NFA_SKIP to be - * used next */ + // empty match always works, output of NFA_SKIP to be + // used next add_here = true; add_state = t->state->out->out; } else if (bytelen <= clen) { - /* match current character, jump ahead to out of - * NFA_SKIP */ + // match current character, jump ahead to out of + // NFA_SKIP add_state = t->state->out->out; add_off = clen; } else { - /* skip over the matched characters, set character - * count in NFA_SKIP */ + // skip over the matched characters, set character + // count in NFA_SKIP add_state = t->state->out; add_off = bytelen; add_count = bytelen - clen; @@ -5715,13 +5721,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm break; } case NFA_SKIP: - /* character of previous matching \1 .. \9 or \@> */ + // character of previous matching \1 .. \9 or \@> if (t->count - clen <= 0) { - /* end of match, go to what follows */ + // end of match, go to what follows add_state = t->state->out; add_off = clen; } else { - /* add state again with decremented count */ + // add state again with decremented count add_state = t->state; add_off = 0; add_count = t->count - clen; @@ -5773,7 +5779,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm break; } - result = FALSE; + result = false; win_T *wp = reg_win == NULL ? curwin : reg_win; if (op == 1 && col - 1 > t->state->val && col > 100) { long ts = wp->w_buffer->b_p_ts; @@ -5803,9 +5809,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm { pos_T *pos = getmark_buf(reg_buf, t->state->val, FALSE); - /* Compare the mark position to the match position. */ - result = (pos != NULL /* mark doesn't exist */ - && pos->lnum > 0 /* mark isn't set in reg_buf */ + // Compare the mark position to the match position. + result = (pos != NULL // mark doesn't exist + && pos->lnum > 0 // mark isn't set in reg_buf && (pos->lnum == reglnum + reg_firstlnum ? (pos->col == (colnr_T)(reginput - regline) ? t->state->c == NFA_MARK @@ -5862,11 +5868,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm case NFA_ZOPEN9: case NFA_NOPEN: case NFA_ZSTART: - /* These states are only added to be able to bail out when - * they are added again, nothing is to be done. */ + // These states are only added to be able to bail out when + // they are added again, nothing is to be done. break; - default: /* regular character */ + default: // regular character { int c = t->state->c; @@ -5888,8 +5894,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm ADD_STATE_IF_MATCH(t->state); break; } - - } /* switch (t->state->c) */ + } // switch (t->state->c) if (add_state != NULL) { nfa_pim_T *pim; @@ -5900,8 +5905,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm else pim = &t->pim; - /* Handle the postponed invisible match if the match might end - * without advancing and before the end of the line. */ + // Handle the postponed invisible match if the match might end + // without advancing and before the end of the line. if (pim != NULL && (clen == 0 || match_follows(add_state, 0))) { if (pim->result == NFA_PIM_TODO) { #ifdef REGEXP_DEBUG @@ -5913,15 +5918,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm result = recursive_regmatch(pim->state, pim, prog, submatch, m, &listids); pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH; - /* for \@! and \@<! it is a match when the result is - * FALSE */ + // for \@! and \@<! it is a match when the result is + // FALSE if (result != (pim->state->c == NFA_START_INVISIBLE_NEG || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST || pim->state->c == NFA_START_INVISIBLE_BEFORE_NEG || pim->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { - /* Copy submatch info from the recursive call */ + // Copy submatch info from the recursive call copy_sub_off(&pim->subs.norm, &m->norm); if (nfa_has_zsubexpr) copy_sub_off(&pim->subs.synt, &m->synt); @@ -5934,34 +5939,35 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm log_fd, "Using previous recursive nfa_regmatch() result, result == %d\n", pim->result); - fprintf(log_fd, "MATCH = %s\n", result == TRUE ? "OK" : "FALSE"); + fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "FALSE"); fprintf(log_fd, "\n"); #endif } - /* for \@! and \@<! it is a match when result is FALSE */ + // for \@! and \@<! it is a match when result is FALSE if (result != (pim->state->c == NFA_START_INVISIBLE_NEG || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST || pim->state->c == NFA_START_INVISIBLE_BEFORE_NEG || pim->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { - /* Copy submatch info from the recursive call */ + // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &pim->subs.norm); if (nfa_has_zsubexpr) copy_sub_off(&t->subs.synt, &pim->subs.synt); - } else - /* look-behind match failed, don't add the state */ + } else { + // look-behind match failed, don't add the state continue; + } - /* Postponed invisible match was handled, don't add it to - * following states. */ + // Postponed invisible match was handled, don't add it to + // following states. pim = NULL; } - /* If "pim" points into l->t it will become invalid when - * adding the state causes the list to be reallocated. Make a - * local copy to avoid that. */ + // If "pim" points into l->t it will become invalid when + // adding the state causes the list to be reallocated. Make a + // local copy to avoid that. if (pim == &t->pim) { copy_pim(&pim_copy, pim); pim = &pim_copy; @@ -5975,18 +5981,17 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm nextlist->t[nextlist->n - 1].count = add_count; } } - - } /* for (thislist = thislist; thislist->state; thislist++) */ - - /* Look for the start of a match in the current position by adding the - * start state to the list of states. - * The first found match is the leftmost one, thus the order of states - * matters! - * Do not add the start state in recursive calls of nfa_regmatch(), - * because recursive calls should only start in the first position. - * Unless "nfa_endp" is not NULL, then we match the end position. - * Also don't start a match past the first line. */ - if (nfa_match == FALSE + } // for (thislist = thislist; thislist->state; thislist++) + + // Look for the start of a match in the current position by adding the + // start state to the list of states. + // The first found match is the leftmost one, thus the order of states + // matters! + // Do not add the start state in recursive calls of nfa_regmatch(), + // because recursive calls should only start in the first position. + // Unless "nfa_endp" is not NULL, then we match the end position. + // Also don't start a match past the first line. + if (!nfa_match && ((toplevel && reglnum == 0 && clen != 0 @@ -6002,8 +6007,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #ifdef REGEXP_DEBUG fprintf(log_fd, "(---) STARTSTATE\n"); #endif - /* Inline optimized code for addstate() if we know the state is - * the first MOPEN. */ + // Inline optimized code for addstate() if we know the state is + // the first MOPEN. if (toplevel) { int add = TRUE; int c; @@ -6012,18 +6017,19 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (nextlist->n == 0) { colnr_T col = (colnr_T)(reginput - regline) + clen; - /* Nextlist is empty, we can skip ahead to the - * character that must appear at the start. */ - if (skip_to_start(prog->regstart, &col) == FAIL) + // Nextlist is empty, we can skip ahead to the + // character that must appear at the start. + if (skip_to_start(prog->regstart, &col) == FAIL) { break; + } #ifdef REGEXP_DEBUG fprintf(log_fd, " Skipping ahead %d bytes to regstart\n", col - ((colnr_T)(reginput - regline) + clen)); #endif reginput = regline + col - clen; } else { - /* Checking if the required start character matches is - * cheaper than adding a state that won't match. */ + // Checking if the required start character matches is + // cheaper than adding a state that won't match. c = PTR2CHAR(reginput + clen); if (c != prog->regstart && (!ireg_ic || vim_tolower(c) != vim_tolower(prog->regstart))) { @@ -6060,21 +6066,29 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #endif nextchar: - /* Advance to the next character, or advance to the next line, or - * finish. */ - if (clen != 0) + // Advance to the next character, or advance to the next line, or + // finish. + if (clen != 0) { reginput += clen; - else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI - && reglnum < nfa_endp->se_u.pos.lnum)) + } else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI + && reglnum < nfa_endp->se_u.pos.lnum)) { reg_nextline(); - else + } else { break; + } // Allow interrupting with CTRL-C. - fast_breakcheck(); + line_breakcheck(); if (got_int) { break; } + // Check for timeout once every twenty times to avoid overhead. + if (nfa_time_limit != NULL && ++nfa_time_count == 20) { + nfa_time_count = 0; + if (profile_passed_limit(*nfa_time_limit)) { + break; + } + } } #ifdef REGEXP_DEBUG @@ -6084,7 +6098,7 @@ nextchar: #endif theend: - /* Free memory */ + // Free memory xfree(list[0].t); xfree(list[1].t); xfree(listids); @@ -6096,11 +6110,9 @@ theend: return nfa_match; } -/* - * Try match of "prog" with at regline["col"]. - * Returns <= 0 for failure, number of lines contained in the match otherwise. - */ -static long nfa_regtry(nfa_regprog_T *prog, colnr_T col) +// Try match of "prog" with at regline["col"]. +// Returns <= 0 for failure, number of lines contained in the match otherwise. +static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm) { int i; regsubs_T subs, m; @@ -6110,6 +6122,8 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col) #endif reginput = regline + col; + nfa_time_limit = tm; + nfa_time_count = 0; #ifdef REGEXP_DEBUG f = fopen(NFA_REGEXP_RUN_LOG, "a"); @@ -6134,7 +6148,7 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col) clear_sub(&m.synt); int result = nfa_regmatch(prog, start, &subs, &m); - if (result == FALSE) { + if (!result) { return 0; } else if (result == NFA_TOO_EXPENSIVE) { return result; @@ -6207,17 +6221,16 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col) return 1 + reglnum; } -/* - * Match a regexp against a string ("line" points to the string) or multiple - * lines ("line" is NULL, use reg_getline()). - * - * Returns <= 0 for failure, number of lines contained in the match otherwise. - */ -static long -nfa_regexec_both ( - char_u *line, - colnr_T startcol /* column to start looking for match */ -) +/// Match a regexp against a string ("line" points to the string) or multiple +/// lines ("line" is NULL, use reg_getline()). +/// +/// @param line String in which to search or NULL +/// @param startcol Column to start looking for match +/// @param tm Timeout limit or NULL +/// +/// @return <= 0 if there is no match and number of lines contained in the +/// match otherwise. +static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm) { nfa_regprog_T *prog; long retval = 0L; @@ -6297,7 +6310,7 @@ nfa_regexec_both ( prog->state[i].lastlist[1] = 0; } - retval = nfa_regtry(prog, col); + retval = nfa_regtry(prog, col, tm); nfa_regengine.expr = NULL; @@ -6449,7 +6462,7 @@ nfa_regexec_nl ( ireg_ic = rmp->rm_ic; ireg_icombine = FALSE; ireg_maxcol = 0; - return nfa_regexec_both(line, col); + return nfa_regexec_both(line, col, NULL); } /// Matches a regexp against multiple lines. @@ -6500,5 +6513,5 @@ static long nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, ireg_icombine = FALSE; ireg_maxcol = rmp->rmm_maxcol; - return nfa_regexec_both(NULL, col); + return nfa_regexec_both(NULL, col, tm); } diff --git a/src/nvim/search.c b/src/nvim/search.c index fffae1ecb2..faf70472bf 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -453,25 +453,24 @@ void last_pat_prog(regmmatch_T *regmatch) --emsg_off; } -/* - * lowest level search function. - * Search for 'count'th occurrence of pattern 'pat' in direction 'dir'. - * Start at position 'pos' and return the found position in 'pos'. - * - * if (options & SEARCH_MSG) == 0 don't give any messages - * if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages - * if (options & SEARCH_MSG) == SEARCH_MSG give all messages - * if (options & SEARCH_HIS) put search pattern in history - * if (options & SEARCH_END) return position at end of match - * if (options & SEARCH_START) accept match at pos itself - * if (options & SEARCH_KEEP) keep previous search pattern - * if (options & SEARCH_FOLD) match only once in a closed fold - * if (options & SEARCH_PEEK) check for typed char, cancel search - * - * Return FAIL (zero) for failure, non-zero for success. - * Returns the index of the first matching - * subpattern plus one; one if there was none. - */ +/// lowest level search function. +/// Search for 'count'th occurrence of pattern 'pat' in direction 'dir'. +/// Start at position 'pos' and return the found position in 'pos'. +/// +/// if (options & SEARCH_MSG) == 0 don't give any messages +/// if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages +/// if (options & SEARCH_MSG) == SEARCH_MSG give all messages +/// if (options & SEARCH_HIS) put search pattern in history +/// if (options & SEARCH_END) return position at end of match +/// if (options & SEARCH_START) accept match at pos itself +/// if (options & SEARCH_KEEP) keep previous search pattern +/// if (options & SEARCH_FOLD) match only once in a closed fold +/// if (options & SEARCH_PEEK) check for typed char, cancel search +/// if (options & SEARCH_COL) start at pos->col instead of zero +/// +/// @returns FAIL (zero) for failure, non-zero for success. +/// the index of the first matching +/// subpattern plus one; one if there was none. int searchit( win_T *win, /* window to search in, can be NULL for a buffer without a window! */ @@ -571,16 +570,14 @@ int searchit( if (tm != NULL && profile_passed_limit(*tm)) break; - /* - * Look for a match somewhere in line "lnum". - */ + // Look for a match somewhere in line "lnum". + colnr_T col = at_first_line && (options & SEARCH_COL) ? pos->col : 0; nmatched = vim_regexec_multi(®match, win, buf, - lnum, (colnr_T)0, - tm - ); - /* Abort searching on an error (e.g., out of stack). */ - if (called_emsg) + lnum, col, tm); + // Abort searching on an error (e.g., out of stack). + if (called_emsg) { break; + } if (nmatched > 0) { /* match may actually be in another line when using \zs */ matchpos = regmatch.startpos[0]; @@ -881,9 +878,8 @@ static void set_vv_searchforward(void) set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/')); } -/* - * Return the number of the first subpat that matched. - */ +// Return the number of the first subpat that matched. +// Return zero if none of them matched. static int first_submatch(regmmatch_T *rp) { int submatch; diff --git a/src/nvim/search.h b/src/nvim/search.h index 6947f79d49..d4e40cb287 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -15,19 +15,20 @@ #define ACTION_SHOW_ALL 4 #define ACTION_EXPAND 5 -/* Values for 'options' argument in do_search() and searchit() */ -#define SEARCH_REV 0x01 /* go in reverse of previous dir. */ -#define SEARCH_ECHO 0x02 /* echo the search command and handle options */ -#define SEARCH_MSG 0x0c /* give messages (yes, it's not 0x04) */ -#define SEARCH_NFMSG 0x08 /* give all messages except not found */ -#define SEARCH_OPT 0x10 /* interpret optional flags */ -#define SEARCH_HIS 0x20 /* put search pattern in history */ -#define SEARCH_END 0x40 /* put cursor at end of match */ -#define SEARCH_NOOF 0x80 /* don't add offset to position */ -#define SEARCH_START 0x100 /* start search without col offset */ -#define SEARCH_MARK 0x200 /* set previous context mark */ -#define SEARCH_KEEP 0x400 /* keep previous search pattern */ -#define SEARCH_PEEK 0x800 /* peek for typed char, cancel search */ +// Values for 'options' argument in do_search() and searchit() +#define SEARCH_REV 0x01 ///< go in reverse of previous dir. +#define SEARCH_ECHO 0x02 ///< echo the search command and handle options +#define SEARCH_MSG 0x0c ///< give messages (yes, it's not 0x04) +#define SEARCH_NFMSG 0x08 ///< give all messages except not found +#define SEARCH_OPT 0x10 ///< interpret optional flags +#define SEARCH_HIS 0x20 ///< put search pattern in history +#define SEARCH_END 0x40 ///< put cursor at end of match +#define SEARCH_NOOF 0x80 ///< don't add offset to position +#define SEARCH_START 0x100 ///< start search without col offset +#define SEARCH_MARK 0x200 ///< set previous context mark +#define SEARCH_KEEP 0x400 ///< keep previous search pattern +#define SEARCH_PEEK 0x800 ///< peek for typed char, cancel search +#define SEARCH_COL 0x1000 ///< start at specified column instead of zero /* Values for flags argument for findmatchlimit() */ #define FM_BACKWARD 0x01 /* search backwards */ diff --git a/src/nvim/spell.c b/src/nvim/spell.c index fdae89b84c..0acaa9ae2b 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -319,7 +319,6 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" -#include "nvim/tempfile.h" #include "nvim/undo.h" #include "nvim/os/os.h" #include "nvim/os/input.h" diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 26a89094aa..da84106454 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1147,6 +1147,22 @@ find_tags ( int get_it_again = FALSE; int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); + int save_p_ic = p_ic; + + // Change the value of 'ignorecase' according to 'tagcase' for the + // duration of this function. + switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags) { + case TC_FOLLOWIC: + break; + case TC_IGNORE: + p_ic = true; + break; + case TC_MATCH: + p_ic = false; + break; + default: + assert(false); + } help_save = curbuf->b_help; orgpat.pat = pat; @@ -1955,6 +1971,8 @@ findtag_end: curbuf->b_help = help_save; xfree(saved_pat); + p_ic = save_p_ic; + return retval; } diff --git a/src/nvim/tempfile.c b/src/nvim/tempfile.c deleted file mode 100644 index afe926b2ef..0000000000 --- a/src/nvim/tempfile.c +++ /dev/null @@ -1,139 +0,0 @@ -#include <inttypes.h> -#include <stdbool.h> -#include <stdlib.h> -#include <stdio.h> - -#include "nvim/ascii.h" -#include "nvim/memory.h" -#include "nvim/misc1.h" -#include "nvim/os/os.h" -#include "nvim/os/os_defs.h" -#include "nvim/path.h" -#include "nvim/strings.h" -#include "nvim/tempfile.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "tempfile.c.generated.h" -#endif - -/// Name of Vim's own temp dir. Ends in a slash. -static char_u *vim_tempdir = NULL; - -/// Create a directory for private use by this instance of Neovim. -/// This is done once, and the same directory is used for all temp files. -/// This method avoids security problems because of symlink attacks et al. -/// It's also a bit faster, because we only need to check for an existing -/// file when creating the directory and not for each temp file. -static void vim_maketempdir(void) -{ - static const char *temp_dirs[] = TEMP_DIR_NAMES; - // Try the entries in `TEMP_DIR_NAMES` to create the temp directory. - char_u template[TEMP_FILE_PATH_MAXLEN]; - char_u path[TEMP_FILE_PATH_MAXLEN]; - for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); ++i) { - // Expand environment variables, leave room for "/nvimXXXXXX/999999999" - // Skip the directory check if the expansion fails. - expand_env((char_u *)temp_dirs[i], template, TEMP_FILE_PATH_MAXLEN - 22); - if (template[0] == '$' || !os_isdir(template)) { - continue; - } - - add_pathsep((char *)template); - // Concatenate with temporary directory name pattern - STRCAT(template, "nvimXXXXXX"); - - if (os_mkdtemp((const char *)template, (char *)path) != 0) { - continue; - } - - if (vim_settempdir((char *)path)) { - // Successfully created and set temporary directory so stop trying. - break; - } else { - // Couldn't set `vim_tempdir` to `path` so remove created directory. - os_rmdir((char *)path); - } - } -} - -/// Delete the temp directory and all files it contains. -void vim_deltempdir(void) -{ - if (vim_tempdir != NULL) { - snprintf((char *)NameBuff, MAXPATHL, "%s*", vim_tempdir); - - char_u **files; - int file_count; - - // Note: We cannot just do `&NameBuff` because it is a statically - // sized array so `NameBuff == &NameBuff` according to C semantics. - char_u *buff_list[1] = {NameBuff}; - if (gen_expand_wildcards(1, buff_list, &file_count, &files, - EW_DIR|EW_FILE|EW_SILENT) == OK) { - for (int i = 0; i < file_count; ++i) { - os_remove((char *)files[i]); - } - FreeWild(file_count, files); - } - path_tail(NameBuff)[-1] = NUL; - os_rmdir((char *)NameBuff); - - xfree(vim_tempdir); - vim_tempdir = NULL; - } -} - -/// Get the name of temp directory. This directory would be created on the first -/// call to this function. -char_u *vim_gettempdir(void) -{ - if (vim_tempdir == NULL) { - vim_maketempdir(); - } - - return vim_tempdir; -} - -/// Set Neovim own temporary directory name to `tempdir`. This directory should -/// be already created. Expand this name to a full path and put it in -/// `vim_tempdir`. This avoids that using `:cd` would confuse us. -/// -/// @param tempdir must be no longer than MAXPATHL. -/// -/// @return false if we run out of memory. -static bool vim_settempdir(char *tempdir) -{ - char *buf = verbose_try_malloc(MAXPATHL + 2); - if (!buf) { - return false; - } - vim_FullName(tempdir, buf, MAXPATHL, false); - add_pathsep(buf); - vim_tempdir = (char_u *)xstrdup(buf); - xfree(buf); - return true; -} - -/// Return a unique name that can be used for a temp file. -/// -/// @note The temp file is NOT created. -/// -/// @return pointer to the temp file name or NULL if Neovim can't create -/// temporary directory for its own temporary files. -char_u *vim_tempname(void) -{ - // Temp filename counter. - static uint32_t temp_count; - - char_u *tempdir = vim_gettempdir(); - if (!tempdir) { - return NULL; - } - - // There is no need to check if the file exists, because we own the directory - // and nobody else creates a file in it. - char_u template[TEMP_FILE_PATH_MAXLEN]; - snprintf((char *)template, TEMP_FILE_PATH_MAXLEN, - "%s%" PRIu32, tempdir, temp_count++); - return vim_strsave(template); -} diff --git a/src/nvim/tempfile.h b/src/nvim/tempfile.h deleted file mode 100644 index c030a70eeb..0000000000 --- a/src/nvim/tempfile.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef NVIM_TEMPFILE_H -#define NVIM_TEMPFILE_H - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "tempfile.h.generated.h" -#endif - -#endif // NVIM_TEMPFILE_H diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 55a49122d5..82c7cd4de9 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -36,9 +36,13 @@ SCRIPTS := \ test_breakindent.out \ test_close_count.out \ test_marks.out \ - test_match_conceal.out \ -NEW_TESTS = test_viml.res +# Tests using runtest.vim.vim. +# Keep test_alot*.res as the last one, sort the others. +NEW_TESTS = \ + test_viml.res \ + test_cursor_func.res \ + test_alot.res SCRIPTS_GUI := test16.out diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 1c610eab51..6601dcf52f 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -62,6 +62,7 @@ else endif " Locate Test_ functions and execute them. +set nomore redir @q function /^Test_ redir END diff --git a/src/nvim/testdir/test49.in b/src/nvim/testdir/test49.in index 1ce57246ee..d95052e14c 100644 --- a/src/nvim/testdir/test49.in +++ b/src/nvim/testdir/test49.in @@ -8,7 +8,9 @@ STARTTEST :se nomore :lang mess C :so test49.vim -GGGGGGGGGGGGGG"rp:.-,$w! test.out +:" Go back to this file and append the results from register r. +:buf test49.in +G"rp:/^Results/,$w! test.out :" :" make valgrind happy :redir => funclist diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim new file mode 100644 index 0000000000..1d1da94bac --- /dev/null +++ b/src/nvim/testdir/test_alot.vim @@ -0,0 +1,3 @@ +" A series of tests that can run in one Vim invocation. +" This makes testing go faster, since Vim doesn't need to restart. + diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim new file mode 100644 index 0000000000..d819b7b092 --- /dev/null +++ b/src/nvim/testdir/test_cursor_func.vim @@ -0,0 +1,52 @@ +" Tests for cursor(). + +func Test_wrong_arguments() + try + call cursor(1. 3) + " not reached + call assert_false(1) + catch + call assert_exception('E474:') + endtry +endfunc + +func Test_move_cursor() + new + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + + call cursor([1, 1, 0, 1]) + call assert_equal([1, 1, 0, 1], getcurpos()[1:]) + call cursor([4, 3, 0, 3]) + call assert_equal([4, 3, 0, 3], getcurpos()[1:]) + + call cursor(2, 2) + call assert_equal([2, 2, 0, 2], getcurpos()[1:]) + " line number zero keeps the line number + call cursor(0, 1) + call assert_equal([2, 1, 0, 1], getcurpos()[1:]) + " col number zero keeps the column + call cursor(3, 0) + call assert_equal([3, 1, 0, 1], getcurpos()[1:]) + " below last line goes to last line + call cursor(9, 1) + call assert_equal([4, 1, 0, 1], getcurpos()[1:]) + + quit! +endfunc + +" Very short version of what matchparen does. +function s:Highlight_Matching_Pair() + let save_cursor = getcurpos() + call setpos('.', save_cursor) +endfunc + +func Test_curswant_with_autocommand() + new + call setline(1, ['func()', '{', '}', '----']) + autocmd! CursorMovedI * call s:Highlight_Matching_Pair() + exe "normal! 3Ga\<Down>X\<Esc>" + call assert_equal('-X---', getline(4)) + autocmd! CursorMovedI * + quit! +endfunc + diff --git a/src/nvim/version.c b/src/nvim/version.c index d6d22ef3f4..51b7ae0001 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -69,84 +69,396 @@ static char *features[] = { // clang-format off static int included_patches[] = { + 1757, 1755, 1753, 1654, 1652, 1643, 1641, + + // 1600 NA + // 1599 NA + // 1598 NA + // 1597 NA + // 1596, + // 1595 NA + // 1594 NA + // 1593 NA + // 1592, + // 1591, + // 1590, + // 1589, + // 1588, + // 1587 NA + // 1586, + // 1585, + // 1584 NA + // 1583 NA + // 1582, + + // 1581, + // 1580, + // 1579, + // 1578, + // 1577, + 1576, + // 1575 NA 1574, + // 1573, + // 1572 NA + // 1571, 1570, + 1569, + // 1568, + // 1567, + // 1566 NA + // 1565, + // 1564, + // 1563, + // 1562, + // 1561 NA + // 1560, + // 1559, + // 1558, + // 1557, + // 1556, + // 1555, + // 1554, + // 1553, + // 1552, + // 1551, + // 1550, + // 1549, + // 1548, + // 1547, + // 1546, + // 1545 NA + // 1544 NA + // 1543 NA + // 1542 NA + // 1541 NA + // 1540 NA + // 1539 NA + // 1538 NA + // 1537 NA + // 1536 NA + // 1535, + // 1534 NA + // 1533, + // 1532 NA + // 1531 NA + // 1530 NA + // 1529 NA + // 1528, + // 1527 NA + // 1526 NA + // 1525 NA + // 1524 NA + // 1523 NA + // 1522 NA + // 1521, + // 1520 NA + // 1519 NA + // 1518 NA + // 1517 NA + // 1516, + // 1515 NA + // 1514 NA + // 1513, + // 1512 NA 1511, + // 1510 NA + // 1509 NA + // 1508 NA + // 1507 NA + // 1506 NA + // 1505 NA + // 1504 NA + // 1503 NA + // 1502 NA + // 1501 NA + 1500, + // 1499, + // 1498 NA + // 1497 NA + // 1496 NA + // 1495 NA + // 1494, + // 1493 NA + // 1492, + // 1491, + // 1490 NA + // 1489 NA + // 1488 NA + // 1487 NA + // 1486, + // 1485 NA + // 1484 NA + // 1483 NA + // 1482 NA + // 1481 NA + // 1480, + // 1479, + // 1478, + // 1477, + // 1476 NA + // 1475 NA + // 1474 NA + // 1473 NA + // 1472 NA + // 1471 NA + // 1470 NA + // 1469 NA + // 1468, + // 1467 NA + // 1466 NA + // 1465 NA + // 1464, + // 1463 NA + // 1462 NA + // 1461 NA + // 1460 NA + // 1459 NA + // 1458 NA + // 1457 NA + // 1456, + // 1455 NA + // 1454 NA + // 1453 NA + // 1452 NA + // 1451 NA + // 1450 NA + // 1449 NA + // 1448 NA + // 1447 NA + // 1446 NA + // 1445 NA + // 1444 NA + // 1443 NA + // 1442 NA + // 1441 NA + // 1440 NA + // 1439 NA + // 1438 NA + // 1437 NA + // 1436 NA + // 1435 NA + // 1434 NA + // 1433 NA + // 1432 NA + // 1431 NA + // 1430 NA + // 1429 NA + // 1428 NA + // 1427 NA + // 1426 NA 1425, + // 1424 NA + // 1423 NA + // 1422 NA + // 1421 NA + // 1420 NA + // 1419 NA + // 1418 NA + // 1417 NA + // 1416 NA + // 1415 NA + // 1414 NA + // 1413 NA + // 1412 NA + // 1411 NA + 1410, + // 1409 NA + // 1408 NA + // 1407 NA + 1406, + 1405, + // 1404 NA + // 1403 NA + // 1402 NA + // 1401, + // 1400 NA + // 1399 NA + // 1398 NA + // 1397, + // 1396, + // 1395 NA + // 1394, + // 1393 NA + // 1392 NA + // 1391 NA + // 1390 NA + // 1389 NA + // 1388, + // 1387 NA + // 1386 NA + // 1385 NA + // 1384, + // 1383 NA + // 1382 NA + // 1381 NA + // 1380 NA + // 1379 NA + // 1378 NA + // 1377 NA + // 1376 NA + // 1375 NA + // 1374 NA + // 1373 NA + // 1372 NA + // 1371 NA + // 1370 NA + // 1369 NA + // 1368 NA + // 1367 NA 1366, + // 1365, + // 1364 NA + // 1363 NA + // 1362 NA + // 1361 NA + // 1360 NA + // 1359 NA + // 1358 NA + // 1357 NA + // 1356 NA + // 1355 NA + // 1354 NA + // 1353 NA + // 1352, + // 1351 NA + // 1350 NA + // 1349 NA + // 1348 NA + // 1347, + 1346, + // 1345 NA + // 1344 NA + // 1343 NA + // 1342 NA + // 1341 NA + // 1340 NA + // 1339 NA + // 1338 NA + // 1337 NA + // 1336 NA + // 1335 NA + // 1334 NA + // 1333 NA + // 1332 NA + // 1331 NA + // 1330 NA + // 1329 NA + // 1328 NA + // 1327 NA + // 1326 NA + // 1325 NA + // 1324 NA + // 1323 NA + // 1322 NA + // 1321 NA + // 1320 NA + // 1319 NA + // 1318 NA + // 1317 NA + // 1316 NA + // 1315 NA + // 1314 NA + // 1313 NA + // 1312 NA + // 1311 NA + // 1310 NA + 1309, + // 1308 NA + // 1307 NA + // 1306 NA + // 1305, 1304, + // 1303 NA + // 1302 NA + // 1301 NA + // 1300 NA + // 1299 NA + // 1298 NA + // 1297 NA + // 1296, + // 1295 NA + // 1294 NA + // 1293 NA 1292, + // 1291 NA + // 1290 NA + // 1289 NA + // 1288 NA + // 1287 NA + // 1286 NA + // 1285, 1284, - // 1283 + // 1283 NA 1282, - // 1281 - // 1280 + // 1281, + // 1280 NA // 1279 NA // 1278 NA - // 1277 - // 1276 - // 1275 - // 1274 - // 1273 - // 1272 + // 1277 NA + // 1276, + // 1275 NA + // 1274 NA + // 1273, + // 1272 NA 1271, - // 1270 + // 1270 NA 1269, - // 1268 + // 1268 NA 1267, // 1266 - // 1265 - // 1264 - // 1263 - // 1262 - // 1261 - // 1260 - // 1259 - // 1258 - // 1257 - // 1256 - // 1255 - // 1254 - // 1253 - // 1252 - // 1251 - // 1250 - // 1249 - // 1248 - // 1247 - // 1246 - // 1245 - // 1244 + // 1265 NA + // 1264 NA + // 1263 NA + // 1262 NA + // 1261 NA + // 1260 NA + // 1259, + // 1258 NA + // 1257 NA + // 1256 NA + // 1255 NA + // 1254 NA + // 1253 NA + // 1252 NA + // 1251 NA + // 1250 NA + // 1249 NA + // 1248 NA + // 1247 NA + // 1246 NA + // 1245 NA + // 1244 NA // 1243 NA - // 1242 - // 1241 - // 1240 - // 1239 - // 1238 - // 1237 - // 1236 - // 1235 - // 1234 - // 1233 - // 1232 + // 1242 NA + // 1241 NA + // 1240 NA + // 1239 NA + // 1238 NA + // 1237, + // 1236, + // 1235 NA + // 1234 NA + // 1233 NA + // 1232 NA // 1231 NA - // 1230 - // 1229 + // 1230 NA + // 1229 NA 1228, - // 1227 - // 1226 - // 1225 - // 1224 - // 1223 - // 1223 - // 1221 - // 1220 + // 1227 NA + // 1226 NA + // 1225 NA + // 1224 NA + // 1223, + // 1222 NA + // 1221 NA + // 1220 NA // 1219 NA // 1218 NA // 1217 NA @@ -178,7 +490,7 @@ static int included_patches[] = { // 1191 NA // 1190 NA // 1189 NA - // 1188, + // 1188 NA // 1187 NA // 1186, // 1185 NA @@ -212,7 +524,7 @@ static int included_patches[] = { 1157, // 1156 NA // 1155 NA - // 1154, + // 1154 NA // 1153, // 1152 NA // 1151, @@ -222,8 +534,8 @@ static int included_patches[] = { // 1147, // 1146 NA // 1145 NA - // 1144 NA - // 1143, + 1144, + 1143, // 1142, 1141, // 1140, @@ -231,8 +543,8 @@ static int included_patches[] = { // 1138 NA 1137, // 1136, - // 1135 NA, - // 1134 NA, + // 1135 NA + // 1134 NA // 1133 NA // 1132, // 1131 NA @@ -246,20 +558,20 @@ static int included_patches[] = { // 1123, // 1122 NA // 1121, - // 1120, + 1120, // 1119, - // 1118, - // 1117, - // 1116, + 1118, + 1117, + 1116, // 1115 NA - // 1114, + 1114, 1113, 1112, // 1111, // 1110, // 1109 NA // 1108, - // 1107, + 1107, // 1106 NA 1105, // 1104 NA @@ -273,8 +585,8 @@ static int included_patches[] = { // 1096, // 1095 NA // 1094, - // 1093, - // 1092, + 1093, + 1092, // 1091, // 1090, 1089, @@ -283,30 +595,30 @@ static int included_patches[] = { // 1086, 1085, 1084, - // 1083 NA, - // 1082 NA, + // 1083 NA + // 1082 NA 1081, - // 1080 NA, + // 1080 NA // 1079, - // 1078 NA, - // 1077 NA, + // 1078 NA + // 1077 NA 1076, // 1075, - // 1074 NA, + // 1074 NA // 1073, 1072, // 1071, - // 1070 NA, - // 1069 NA, + // 1070 NA + // 1069 NA // 1068, - // 1067 NA, - // 1066 NA, + // 1067 NA + // 1066 NA 1065, // 1064, - // 1063 NA, - // 1062 NA, + // 1063 NA + // 1062 NA // 1061, - // 1060 NA, + // 1060 NA // 1059, // 1058, // 1057, @@ -321,46 +633,46 @@ static int included_patches[] = { // 1048, // 1047, // 1046, - // 1045 NA, - // 1044 NA, - // 1043 NA, + // 1045 NA + // 1044 NA + // 1043 NA // 1042, 1041, - // 1040 NA, + // 1040 NA // 1039, - // 1038 NA, + // 1038 NA // 1037, // 1036, // 1035, // 1034, - // 1033 NA, + // 1033 NA 1032, // 1031 NA, 1030, 1029, - // 1028 NA, + // 1028 NA 1027, - // 1026 NA, - // 1025 NA, - // 1024 NA, - // 1023 NA, - // 1022 NA, - // 1021 NA, - // 1020 NA, - // 1019 NA, + // 1026 NA + // 1025 NA + // 1024 NA + // 1023 NA + // 1022 NA + // 1021 NA + // 1020 NA + // 1019 NA // 1018, // 1017, - // 1016 NA, + // 1016 NA // 1015, - // 1014 NA, + // 1014 NA 1013, - // 1012 NA, - // 1011 NA, + // 1012 NA + // 1011 NA // 1010, - // 1009 NA, - // 1008 NA, + // 1009 NA + // 1008 NA // 1007, - // 1006, + 1006, // 1005, // 1004 NA, // 1003 NA, @@ -368,7 +680,7 @@ static int included_patches[] = { 1001, 1000, // 999 NA - // 998, + 998, // 997 NA // 996 NA // 995 NA @@ -382,8 +694,8 @@ static int included_patches[] = { // 987 NA // 986 NA // 985 NA - // 984, - // 983, + 984, + // 983 NA // 982 NA 981, 980, @@ -409,13 +721,13 @@ static int included_patches[] = { // 960 NA // 959 NA 958, - // 957, + 957, // 956 NA 955, // 954 NA 953, 952, - // 951, + 951, 950, 949, // 948 NA @@ -424,8 +736,8 @@ static int included_patches[] = { 945, 944, // 943 NA - // 942, - // 941, + 942, + 941, // 940 NA 939, // 938 NA @@ -544,7 +856,7 @@ static int included_patches[] = { 825, // 824 NA 823, - // 822, + 822, // 821 NA 820, 819, @@ -569,7 +881,7 @@ static int included_patches[] = { 800, 799, 798, - // 797, + // 797 NA // 796 NA 795, // 794 NA @@ -633,7 +945,7 @@ static int included_patches[] = { 736, // 735 NA 734, - // 733, + // 733 NA 732, // 731 NA // 730 NA @@ -753,7 +1065,7 @@ static int included_patches[] = { 616, 615, 614, - // 613, + 613, 612, // 611 NA // 610 NA @@ -1122,13 +1434,13 @@ static int included_patches[] = { 247, // 246 NA 245, - // 244, + // 244 NA 243, 242, 241, 240, 239, - // 238, + // 238 NA 237, 236, 235, diff --git a/src/nvim/window.c b/src/nvim/window.c index 1b8c953596..bea55c465f 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -97,7 +97,7 @@ do_window ( * don't replicate the quickfix buffer. */ if (bt_quickfix(curbuf)) goto newwindow; - win_split((int)Prenum, 0); + (void)win_split((int)Prenum, 0); break; /* split current window in two parts, vertically */ @@ -108,7 +108,7 @@ do_window ( * don't replicate the quickfix buffer. */ if (bt_quickfix(curbuf)) goto newwindow; - win_split((int)Prenum, WSP_VERT); + (void)win_split((int)Prenum, WSP_VERT); break; /* split current window and edit alternate file */ @@ -2948,7 +2948,7 @@ void free_tabpage(tabpage_T *tp) unref_var_dict(tp->tp_vars); - + xfree(tp->localdir); // Free tab-local working directory xfree(tp); } @@ -3560,18 +3560,24 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int tri curwin->w_cursor.coladd = 0; changed_line_abv_curs(); /* assume cursor position needs updating */ - if (curwin->w_localdir != NULL) { - /* Window has a local directory: Save current directory as global - * directory (unless that was done already) and change to the local - * directory. */ + // The new directory is either the local directory of the window, of the tab + // or NULL. + char_u *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->localdir; + + if (new_dir) { + // Window/tab has a local directory: Save current directory as global + // directory (unless that was done already) and change to the local + // directory. if (globaldir == NULL) { char_u cwd[MAXPATHL]; - if (os_dirname(cwd, MAXPATHL) == OK) + if (os_dirname(cwd, MAXPATHL) == OK) { globaldir = vim_strsave(cwd); + } + } + if (os_chdir((char *)new_dir) == 0) { + shorten_fnames(true); } - if (os_chdir((char *)curwin->w_localdir) == 0) - shorten_fnames(TRUE); } else if (globaldir != NULL) { /* Window doesn't have a local directory and we are not in the global * directory: Change to the global directory. */ diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua new file mode 100644 index 0000000000..f5cce65104 --- /dev/null +++ b/test/functional/ex_cmds/cd_spec.lua @@ -0,0 +1,148 @@ +-- Specs for :cd, :tcd, :lcd and getcwd() + +local helpers = require('test.functional.helpers') +local execute, eq, clear, eval, exc_exec = + helpers.execute, helpers.eq, helpers.clear, helpers.eval, helpers.exc_exec + + +-- These directories will be created for testing +local directories = { + 'Xtest-functional-ex_cmds-cd_spec.1', -- Tab + 'Xtest-functional-ex_cmds-cd_spec.2', -- Window + 'Xtest-functional-ex_cmds-cd_spec.3', -- New global +} + +-- Shorthand writing to get the current working directory +local cwd = function() return eval('getcwd( )') end -- effective working dir +local wcwd = function() return eval('getcwd( 0 )') end -- window dir +local tcwd = function() return eval('getcwd(-1, 0)') end -- tab dir +local gcwd = function() return eval('getcwd(-1, -1)') end -- global dir + +-- Same, except these tell us if there is a working directory at all +local lwd = function() return eval('haslocaldir( )') end -- effective working dir +local wlwd = function() return eval('haslocaldir( 0 )') end -- window dir +local tlwd = function() return eval('haslocaldir(-1, 0)') end -- tab dir +local glwd = function() return eval('haslocaldir(-1, -1)') end -- global dir + +-- Test both the `cd` and `chdir` variants +for _, cmd in ipairs {'cd', 'chdir'} do + describe(':*' .. cmd, function() + before_each(function() + clear() + for _, d in ipairs(directories) do + lfs.mkdir(d) + end + end) + + after_each(function() + for _, d in ipairs(directories) do + lfs.rmdir(d) + end + end) + + it('works', function() + -- Store the initial working directory + local globalDir = cwd() + + -- Create a new tab first and verify that is has the same working dir + execute('tabnew') + eq(globalDir, cwd()) + eq(globalDir, tcwd()) -- has no tab-local directory + eq(0, tlwd()) + eq(globalDir, wcwd()) -- has no window-local directory + eq(0, wlwd()) + + -- Change tab-local working directory and verify it is different + execute('t' .. cmd .. ' ' .. directories[1]) + eq(globalDir .. '/' .. directories[1], cwd()) + eq(cwd(), tcwd()) -- working directory maches tab directory + eq(1, tlwd()) + eq(cwd(), wcwd()) -- still no window-directory + eq(0, wlwd()) + + -- Create a new window in this tab to test `:lcd` + execute('new') + eq(1, tlwd()) -- Still tab-local working directory + eq(0, wlwd()) -- Still no window-local working directory + eq(globalDir .. '/' .. directories[1], cwd()) + execute('l' .. cmd .. ' ../' .. directories[2]) + eq(globalDir .. '/' .. directories[2], cwd()) + eq(globalDir .. '/' .. directories[1], tcwd()) + eq(1, wlwd()) + + -- Verify the first window still has the tab local directory + execute('wincmd w') + eq(globalDir .. '/' .. directories[1], cwd()) + eq(globalDir .. '/' .. directories[1], tcwd()) + eq(0, wlwd()) -- No window-local directory + + -- Change back to initial tab and verify working directory has stayed + execute('tabnext') + eq(globalDir, cwd() ) + eq(0, tlwd()) + eq(0, wlwd()) + + -- Verify global changes don't affect local ones + execute('' .. cmd .. ' ' .. directories[3]) + eq(globalDir .. '/' .. directories[3], cwd()) + execute('tabnext') + eq(globalDir .. '/' .. directories[1], cwd()) + eq(globalDir .. '/' .. directories[1], tcwd()) + eq(0, wlwd()) -- Still no window-local directory in this window + + -- Unless the global change happened in a tab with local directory + execute('' .. cmd .. ' ..') + eq(globalDir, cwd() ) + eq(0 , tlwd()) + eq(0 , wlwd()) + -- Which also affects the first tab + execute('tabnext') + eq(globalDir, cwd()) + + -- But not in a window with its own local directory + execute('tabnext | wincmd w') + eq(globalDir .. '/' .. directories[2], cwd() ) + eq(0 , tlwd()) + eq(globalDir .. '/' .. directories[2], wcwd()) + end) + end) +end + +-- Test legal parameters for 'getcwd' and 'haslocaldir' +for _, cmd in ipairs {'getcwd', 'haslocaldir'} do + describe(cmd..'()', function() + -- Test invalid argument types + local expected = 'Vim(call):E474: Invalid argument' + it('fails on string', function() + eq(expected, exc_exec('call ' .. cmd .. '("some string")')) + end) + it('fails on float', function() + eq(expected, exc_exec('call ' .. cmd .. '(1.0)')) + end) + it('fails on list', function() + eq(expected, exc_exec('call ' .. cmd .. '([1, 2])')) + end) + it('fails on dictionary', function() + eq(expected, exc_exec('call ' .. cmd .. '({"key": "value"})')) + end) + it('fails on funcref', function() + eq(expected, exc_exec('call ' .. cmd .. '(function("tr"))')) + end) + + -- Test invalid numbers + it('fails on number less than -1', function() + eq(expected, exc_exec('call ' .. cmd .. '(-2)')) + end) + local expected = 'Vim(call):E5001: Higher scope cannot be -1 if lower scope is >= 0.' + it('fails on -1 if previous arg is >=0', function() + eq(expected, exc_exec('call ' .. cmd .. '(0, -1)')) + end) + + -- Test wrong number of arguments + local expected = 'Vim(call):E118: Too many arguments for function: ' .. cmd + it('fails to parse more than one argument', function() + eq(expected, exc_exec('call ' .. cmd .. '(0, 0, 0)')) + end) + end) +end + diff --git a/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua b/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua index 1359b45228..2a4c0149fa 100644 --- a/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua +++ b/test/functional/legacy/044_099_regexp_multibyte_magic_spec.lua @@ -31,7 +31,8 @@ local function run_test_with_regexpengine(regexpengine) h AÀÁÂÃÄÅĀĂĄǍǞǠẢ BḂḆ CÇĆĈĊČ DĎĐḊḎḐ EÈÉÊËĒĔĖĘĚẺẼ FḞ GĜĞĠĢǤǦǴḠ HĤĦḢḦḨ IÌÍÎÏĨĪĬĮİǏỈ JĴ KĶǨḰḴ LĹĻĽĿŁḺ MḾṀ NÑŃŅŇṄṈ OÒÓÔÕÖØŌŎŐƠǑǪǬỎ PṔṖ Q RŔŖŘṘṞ SŚŜŞŠṠ TŢŤŦṪṮ UÙÚÛÜŨŪŬŮŰŲƯǓỦ VṼ WŴẀẂẄẆ XẊẌ YÝŶŸẎỲỶỸ ZŹŻŽƵẐẔ i aàáâãäåāăąǎǟǡả bḃḇ cçćĉċč dďđḋḏḑ eèéêëēĕėęěẻẽ fḟ gĝğġģǥǧǵḡ hĥħḣḧḩẖ iìíîïĩīĭįǐỉ jĵǰ kķǩḱḵ lĺļľŀłḻ mḿṁ nñńņňʼnṅṉ oòóôõöøōŏőơǒǫǭỏ pṕṗ q rŕŗřṙṟ sśŝşšṡ tţťŧṫṯẗ uùúûüũūŭůűųưǔủ vṽ wŵẁẃẅẇẘ xẋẍ yýÿŷẏẙỳỷỹ zźżžƶẑẕ j 0123❤x - k combinations]]) + k combinations + l ä ö ü ᾱ̆́]]) execute('set re=' .. regexpengine) @@ -85,6 +86,11 @@ local function run_test_with_regexpengine(regexpengine) execute([[let @w=':%s#comb[i]nations#œ̄ṣ́m̥̄ᾱ̆́#g']]) execute('@w') + -- Line l. Ex command ":s/ \?/ /g" should NOT split multi-byte characters + -- into bytes (fixed by vim-7.3.192). + execute([[/^l]]) + execute([[s/ \?/ /g]]) + -- Additional tests. Test matchstr() with multi-byte characters. feed('G') execute([[put =matchstr(\"אבגד\", \".\", 0, 2)]]) -- ב @@ -123,6 +129,7 @@ local function run_test_with_regexpengine(regexpengine) i aàáâãäåāăąǎǟǡả bḃḇ cçćĉċč dďđḋḏḑ eèéêëēĕėęěẻẽ fḟ gĝğġģǥǧǵḡ hĥħḣḧḩẖ iìíîïĩīĭįǐỉ jĵǰ kķǩḱḵ lĺļľŀłḻ mḿṁ nñńņňʼnṅṉ oòóôõöøōŏőơǒǫǭỏ pṕṗ q rŕŗřṙṟ sśŝşšṡ tţťŧṫṯẗ uùúûüũūŭůűųưǔủ vṽ wŵẁẃẅẇẘ xẋẍ yýÿŷẏẙỳỷỹ zźżžƶẑ j 012❤ k œ̄ṣ́m̥̄ᾱ̆́ + l ä ö ü ᾱ̆́ ב בג א diff --git a/test/functional/legacy/057_sort_spec.lua b/test/functional/legacy/057_sort_spec.lua index 7eed31e292..36062ded3a 100644 --- a/test/functional/legacy/057_sort_spec.lua +++ b/test/functional/legacy/057_sort_spec.lua @@ -668,4 +668,22 @@ describe(':sort', function() b0b101100 b0b111000]]) end) + + it('float', function() + insert([[ + 1.234 + 0.88 + 123.456 + 1.15e-6 + -1.1e3 + -1.01e3]]) + execute([[sort f]]) + expect([[ + -1.1e3 + -1.01e3 + 1.15e-6 + 0.88 + 1.234 + 123.456]]) + end) end) diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua new file mode 100644 index 0000000000..cd18a8f750 --- /dev/null +++ b/test/functional/legacy/delete_spec.lua @@ -0,0 +1,99 @@ +local helpers = require('test.functional.helpers') +local clear, source = helpers.clear, helpers.source +local eq, eval, execute = helpers.eq, helpers.eval, helpers.execute + +describe('Test for delete()', function() + before_each(clear) + + it('file delete', function() + execute('split Xfile') + execute("call setline(1, ['a', 'b'])") + execute('wq') + eq(eval("['a', 'b']"), eval("readfile('Xfile')")) + eq(0, eval("delete('Xfile')")) + eq(-1, eval("delete('Xfile')")) + end) + + it('directory delete', function() + execute("call mkdir('Xdir1')") + eq(1, eval("isdirectory('Xdir1')")) + eq(0, eval("delete('Xdir1', 'd')")) + eq(0, eval("isdirectory('Xdir1')")) + eq(-1, eval("delete('Xdir1', 'd')")) + end) + it('recursive delete', function() + execute("call mkdir('Xdir1')") + execute("call mkdir('Xdir1/subdir')") + execute("call mkdir('Xdir1/empty')") + execute('split Xdir1/Xfile') + execute("call setline(1, ['a', 'b'])") + execute('w') + execute('w Xdir1/subdir/Xfile') + execute('close') + + eq(1, eval("isdirectory('Xdir1')")) + eq(eval("['a', 'b']"), eval("readfile('Xdir1/Xfile')")) + eq(1, eval("isdirectory('Xdir1/subdir')")) + eq(eval("['a', 'b']"), eval("readfile('Xdir1/subdir/Xfile')")) + eq(1, eval("isdirectory('Xdir1/empty')")) + eq(0, eval("delete('Xdir1', 'rf')")) + eq(0, eval("isdirectory('Xdir1')")) + eq(-1, eval("delete('Xdir1', 'd')")) + end) + + it('symlink delete', function() + source([[ + split Xfile + call setline(1, ['a', 'b']) + wq + silent !ln -s Xfile Xlink + ]]) + -- Delete the link, not the file + eq(0, eval("delete('Xlink')")) + eq(-1, eval("delete('Xlink')")) + eq(0, eval("delete('Xfile')")) + end) + + it('symlink directory delete', function() + execute("call mkdir('Xdir1')") + execute("silent !ln -s Xdir1 Xlink") + eq(1, eval("isdirectory('Xdir1')")) + eq(1, eval("isdirectory('Xlink')")) + -- Delete the link, not the directory + eq(0, eval("delete('Xlink')")) + eq(-1, eval("delete('Xlink')")) + eq(0, eval("delete('Xdir1', 'd')")) + end) + + it('symlink recursive delete', function() + source([[ + call mkdir('Xdir3') + call mkdir('Xdir3/subdir') + call mkdir('Xdir4') + split Xdir3/Xfile + call setline(1, ['a', 'b']) + w + w Xdir3/subdir/Xfile + w Xdir4/Xfile + close + silent !ln -s ../Xdir4 Xdir3/Xlink + ]]) + + eq(1, eval("isdirectory('Xdir3')")) + eq(eval("['a', 'b']"), eval("readfile('Xdir3/Xfile')")) + eq(1, eval("isdirectory('Xdir3/subdir')")) + eq(eval("['a', 'b']"), eval("readfile('Xdir3/subdir/Xfile')")) + eq(1, eval("isdirectory('Xdir4')")) + eq(1, eval("isdirectory('Xdir3/Xlink')")) + eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')")) + + eq(0, eval("delete('Xdir3', 'rf')")) + eq(0, eval("isdirectory('Xdir3')")) + eq(-1, eval("delete('Xdir3', 'd')")) + -- symlink is deleted, not the directory it points to + eq(1, eval("isdirectory('Xdir4')")) + eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')")) + eq(0, eval("delete('Xdir4/Xfile')")) + eq(0, eval("delete('Xdir4', 'd')")) + end) +end) diff --git a/test/functional/legacy/function_sort_spec.lua b/test/functional/legacy/function_sort_spec.lua new file mode 100644 index 0000000000..9083911021 --- /dev/null +++ b/test/functional/legacy/function_sort_spec.lua @@ -0,0 +1,29 @@ +local helpers = require('test.functional.helpers') +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval + +describe('sort', function() + before_each(clear) + + it('numbers compared as strings', function() + eq({1, 2, 3}, eval('sort([3, 2, 1])')) + eq({13, 28, 3}, eval('sort([3, 28, 13])')) + end) + + it('numbers compared as numeric', function() + eq({1, 2, 3}, eval("sort([3, 2, 1], 'n')")) + eq({3, 13, 28}, eval("sort([3, 28, 13], 'n')")) + -- Strings are not sorted. + eq({'13', '28', '3'}, eval("sort(['13', '28', '3'], 'n')")) + end) + + it('numbers compared as numbers', function() + eq({3, 13, 28}, eval("sort([13, 28, 3], 'N')")) + eq({'3', '13', '28'}, eval("sort(['13', '28', '3'], 'N')")) + end) + + it('numbers compared as float', function() + eq({0.28, 3, 13.5}, eval("sort([13.5, 0.28, 3], 'f')")) + end) +end) diff --git a/test/functional/legacy/searchpos_spec.lua b/test/functional/legacy/searchpos_spec.lua new file mode 100644 index 0000000000..1c9b1ccee6 --- /dev/null +++ b/test/functional/legacy/searchpos_spec.lua @@ -0,0 +1,35 @@ +local helpers = require('test.functional.helpers') +local call = helpers.call +local clear = helpers.clear +local execute = helpers.execute +local eq = helpers.eq +local eval = helpers.eval +local insert = helpers.insert + +describe('searchpos', function() + before_each(clear) + + it('is working', function() + insert([[ + 1a3 + 123xyz]]) + + call('cursor', 1, 1) + eq({1, 1, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) + call('cursor', 1, 2) + eq({2, 1, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) + + execute('set cpo-=c') + call('cursor', 1, 2) + eq({1, 2, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) + call('cursor', 1, 3) + eq({1, 3, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) + + -- Now with \zs, first match is in column 0, "a" is matched. + call('cursor', 1, 3) + eq({2, 4, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcW')]])) + -- With z flag start at cursor column, don't see the "a". + call('cursor', 1, 3) + eq({2, 4, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcWz')]])) + end) +end) diff --git a/test/functional/legacy/tagcase_spec.lua b/test/functional/legacy/tagcase_spec.lua new file mode 100644 index 0000000000..9a8c6fbe42 --- /dev/null +++ b/test/functional/legacy/tagcase_spec.lua @@ -0,0 +1,150 @@ +local helpers = require('test.functional.helpers') +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local exc_exec = helpers.exc_exec +local expect = helpers.expect +local insert = helpers.insert +local source = helpers.source +local write_file = helpers.write_file + +describe("'tagcase' option", function() + setup(function() + write_file('Xtags', [[ + Bar Xtext 3 + Foo Xtext 2 + foo Xtext 4]]) + end) + + before_each(function() + clear() + source([[ + lang mess C + set tags=Xtags]]) + end) + + teardown(function() + os.remove('Xtags') + end) + + it('should have correct default values', function() + source([[ + set ic& + setg tc& + setl tc& + ]]) + + eq(0, eval('&ic')) + eq('followic', eval('&g:tc')) + eq('followic', eval('&l:tc')) + eq('followic', eval('&tc')) + end) + + it('should accept <empty> only for setlocal', function() + -- Verify that the local setting accepts <empty> but that the global setting + -- does not. The first of these (setting the local value to <empty>) should + -- succeed; the other two should fail. + eq(0, exc_exec('setl tc=')) + eq('Vim(setglobal):E474: Invalid argument: tc=', exc_exec('setg tc=')) + eq('Vim(set):E474: Invalid argument: tc=', exc_exec('set tc=')) + end) + + it("should work with 'ignorecase' correctly in all combinations", function() + -- Verify that the correct number of matching tags is found for all values of + -- 'ignorecase' and global and local values 'tagcase', in all combinations. + insert([[ + + Foo + Bar + foo + + end text]]) + + source([[ + for &ic in [0, 1] + for &g:tc in ["followic", "ignore", "match"] + for &l:tc in ["", "followic", "ignore", "match"] + call append('$', "ic=".&ic." g:tc=".&g:tc." l:tc=".&l:tc." tc=".&tc) + call append('$', len(taglist("^foo$"))) + call append('$', len(taglist("^Foo$"))) + endfor + endfor + endfor + + 1,/^end text$/d]]) + + expect([[ + ic=0 g:tc=followic l:tc= tc=followic + 1 + 1 + ic=0 g:tc=followic l:tc=followic tc=followic + 1 + 1 + ic=0 g:tc=followic l:tc=ignore tc=ignore + 2 + 2 + ic=0 g:tc=followic l:tc=match tc=match + 1 + 1 + ic=0 g:tc=ignore l:tc= tc=ignore + 2 + 2 + ic=0 g:tc=ignore l:tc=followic tc=followic + 1 + 1 + ic=0 g:tc=ignore l:tc=ignore tc=ignore + 2 + 2 + ic=0 g:tc=ignore l:tc=match tc=match + 1 + 1 + ic=0 g:tc=match l:tc= tc=match + 1 + 1 + ic=0 g:tc=match l:tc=followic tc=followic + 1 + 1 + ic=0 g:tc=match l:tc=ignore tc=ignore + 2 + 2 + ic=0 g:tc=match l:tc=match tc=match + 1 + 1 + ic=1 g:tc=followic l:tc= tc=followic + 2 + 2 + ic=1 g:tc=followic l:tc=followic tc=followic + 2 + 2 + ic=1 g:tc=followic l:tc=ignore tc=ignore + 2 + 2 + ic=1 g:tc=followic l:tc=match tc=match + 1 + 1 + ic=1 g:tc=ignore l:tc= tc=ignore + 2 + 2 + ic=1 g:tc=ignore l:tc=followic tc=followic + 2 + 2 + ic=1 g:tc=ignore l:tc=ignore tc=ignore + 2 + 2 + ic=1 g:tc=ignore l:tc=match tc=match + 1 + 1 + ic=1 g:tc=match l:tc= tc=match + 1 + 1 + ic=1 g:tc=match l:tc=followic tc=followic + 2 + 2 + ic=1 g:tc=match l:tc=ignore tc=ignore + 2 + 2 + ic=1 g:tc=match l:tc=match tc=match + 1 + 1]]) + end) +end) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index da9d6a0cd2..d0d791308b 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -426,4 +426,35 @@ describe('Mouse input', function() | ]]) end) + + it('horizontal scrolling', function() + feed("<esc>:set nowrap<cr>") + + feed("a <esc>20Ab<esc>") + screen:expect([[ + | + | + bbbbbbbbbbbbbbb^b | + ~ | + | + ]]) + + feed("<ScrollWheelLeft><0,0>") + screen:expect([[ + | + | + n bbbbbbbbbbbbbbbbbbb^b | + ~ | + | + ]]) + + feed("^<ScrollWheelRight><0,0>") + screen:expect([[ + g | + | + ^t and selection bbbbbbbbb| + ~ | + | + ]]) + end) end) diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index 8b507c8ffc..322d777ab6 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -6,8 +6,22 @@ local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq local execute, source, expect = helpers.execute, helpers.source, helpers.expect describe('completion', function() + local screen + before_each(function() clear() + screen = Screen.new(60, 8) + screen:attach() + screen:set_default_attr_ignore({{bold=true, foreground=Screen.colors.Blue}}) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.LightMagenta}, + [2] = {background = Screen.colors.Grey}, + [3] = {bold = true}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen}, + [5] = {foreground = Screen.colors.Red}, + [6] = {background = Screen.colors.Black}, + [7] = {foreground = Screen.colors.White, background = Screen.colors.Red}, + }) end) describe('v:completed_item', function() @@ -15,18 +29,40 @@ describe('completion', function() eq({}, eval('v:completed_item')) end) it('is empty dict if the candidate is not inserted', function() - feed('ifoo<ESC>o<C-x><C-n><C-e><ESC>') + feed('ifoo<ESC>o<C-x><C-n>') + screen:expect([[ + foo | + foo^ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- Keyword Local completion (^N^P) The only match} | + ]]) + feed('<C-e>') + screen:expect([[ + foo | + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) + feed('<ESC>') eq({}, eval('v:completed_item')) end) it('returns expected dict in normal completion', function() - feed('ifoo<ESC>o<C-x><C-n><ESC>') + feed('ifoo<ESC>o<C-x><C-n>') eq('foo', eval('getline(2)')) eq({word = 'foo', abbr = '', menu = '', info = '', kind = ''}, eval('v:completed_item')) end) it('is readonly', function() + screen:try_resize(80, 8) feed('ifoo<ESC>o<C-x><C-n><ESC>') - execute('let v:completed_item.word = "bar"') neq(nil, string.find(eval('v:errmsg'), '^E46: ')) execute('let v:errmsg = ""') @@ -51,17 +87,29 @@ describe('completion', function() source([[ function! TestOmni(findstart, base) abort return a:findstart ? 0 : [{'word': 'foo', 'abbr': 'bar', - \ 'menu': 'baz', 'info': 'foobar', 'kind': 'foobaz'}] + \ 'menu': 'baz', 'info': 'foobar', 'kind': 'foobaz'}, + \ {'word': 'word', 'abbr': 'abbr', 'menu': 'menu', 'info': 'info', 'kind': 'kind'}] endfunction setlocal omnifunc=TestOmni ]]) - feed('i<C-x><C-o><ESC>') + feed('i<C-x><C-o>') eq('foo', eval('getline(1)')) + screen:expect([[ + foo^ | + {2:bar foobaz baz } | + {1:abbr kind menu } | + ~ | + ~ | + ~ | + ~ | + {3:-- Omni completion (^O^N^P) }{4:match 1 of 2} | + ]]) eq({word = 'foo', abbr = 'bar', menu = 'baz', info = 'foobar', kind = 'foobaz'}, eval('v:completed_item')) end) end) + describe('completeopt', function() before_each(function() source([[ @@ -74,30 +122,182 @@ describe('completion', function() it('inserts the first candidate if default', function() execute('set completeopt+=menuone') - feed('ifoo<ESC>o<C-x><C-n>bar<ESC>') + feed('ifoo<ESC>o') + screen:expect([[ + foo | + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) + feed('<C-x>') + -- the ^X prompt, only test this once + screen:expect([[ + foo | + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} | + ]]) + feed('<C-n>') + screen:expect([[ + foo | + foo^ | + {2:foo } | + ~ | + ~ | + ~ | + ~ | + {3:-- Keyword Local completion (^N^P) The only match} | + ]]) + feed('bar<ESC>') eq('foobar', eval('getline(2)')) - feed('o<C-r>=TestComplete()<CR><ESC>') + feed('o<C-r>=TestComplete()<CR>') + screen:expect([[ + foo | + foobar | + foo^ | + {2:foo } | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) eq('foo', eval('getline(3)')) end) it('selects the first candidate if noinsert', function() execute('set completeopt+=menuone,noinsert') - feed('ifoo<ESC>o<C-x><C-n><C-y><ESC>') + feed('ifoo<ESC>o<C-x><C-n>') + screen:expect([[ + foo | + ^ | + {2:foo } | + ~ | + ~ | + ~ | + ~ | + {3:-- Keyword Local completion (^N^P) The only match} | + ]]) + feed('<C-y>') + screen:expect([[ + foo | + foo^ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) + feed('<ESC>') eq('foo', eval('getline(2)')) - feed('o<C-r>=TestComplete()<CR><C-y><ESC>') + feed('o<C-r>=TestComplete()<CR>') + screen:expect([[ + foo | + foo | + ^ | + {2:foo } | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) + feed('<C-y><ESC>') eq('foo', eval('getline(3)')) end) it('does not insert the first candidate if noselect', function() execute('set completeopt+=menuone,noselect') - feed('ifoo<ESC>o<C-x><C-n>bar<ESC>') + feed('ifoo<ESC>o<C-x><C-n>') + screen:expect([[ + foo | + ^ | + {1:foo } | + ~ | + ~ | + ~ | + ~ | + {3:-- Keyword Local completion (^N^P) }{5:Back at original} | + ]]) + feed('b') + screen:expect([[ + foo | + b^ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- Keyword Local completion (^N^P) }{5:Back at original} | + ]]) + feed('ar<ESC>') eq('bar', eval('getline(2)')) - feed('o<C-r>=TestComplete()<CR>bar<ESC>') + feed('o<C-r>=TestComplete()<CR>') + screen:expect([[ + foo | + bar | + ^ | + {1:foo } | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) + feed('bar<ESC>') eq('bar', eval('getline(3)')) end) it('does not select/insert the first candidate if noselect and noinsert', function() execute('set completeopt+=menuone,noselect,noinsert') - feed('ifoo<ESC>o<C-x><C-n><ESC>') + feed('ifoo<ESC>o<C-x><C-n>') + screen:expect([[ + foo | + ^ | + {1:foo } | + ~ | + ~ | + ~ | + ~ | + {3:-- Keyword Local completion (^N^P) }{5:Back at original} | + ]]) + feed('<ESC>') + screen:expect([[ + foo | + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) eq('', eval('getline(2)')) - feed('o<C-r>=TestComplete()<CR><ESC>') + feed('o<C-r>=TestComplete()<CR>') + screen:expect([[ + foo | + | + ^ | + {1:foo } | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) + feed('<ESC>') + screen:expect([[ + foo | + | + ^ | + ~ | + ~ | + ~ | + ~ | + | + ]]) eq('', eval('getline(3)')) end) it('does not change modified state if noinsert', function() @@ -142,12 +342,130 @@ describe('completion', function() end ) it('completes on each input char', function () - feed('i<C-x><C-u>gu<Down><C-y>') + feed('i<C-x><C-u>') + screen:expect([[ + ^ | + {1:January }{6: } | + {1:February }{6: } | + {1:March }{6: } | + {1:April }{2: } | + {1:May }{2: } | + {1:June }{2: } | + {3:-- User defined completion (^U^N^P) }{5:Back at original} | + ]]) + feed('u') + screen:expect([[ + u^ | + {1:January } | + {1:February } | + {1:June } | + {1:July } | + {1:August } | + ~ | + {3:-- User defined completion (^U^N^P) }{5:Back at original} | + ]]) + feed('g') + screen:expect([[ + ug^ | + {1:August } | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- User defined completion (^U^N^P) }{5:Back at original} | + ]]) + feed('<Down>') + screen:expect([[ + ug^ | + {2:August } | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- User defined completion (^U^N^P) The only match} | + ]]) + feed('<C-y>') + screen:expect([[ + August^ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) expect('August') end) it("repeats correctly after backspace #2674", function () - feed('o<C-x><C-u>Ja<BS><C-n><C-n><Esc>') + feed('o<C-x><C-u>Ja') + screen:expect([[ + | + Ja^ | + {1:January } | + ~ | + ~ | + ~ | + ~ | + {3:-- User defined completion (^U^N^P) }{5:Back at original} | + ]]) + feed('<BS>') + screen:expect([[ + | + J^ | + {1:January } | + {1:June } | + {1:July } | + ~ | + ~ | + {3:-- User defined completion (^U^N^P) }{5:Back at original} | + ]]) + feed('<C-n>') + screen:expect([[ + | + January^ | + {2:January } | + {1:June } | + {1:July } | + ~ | + ~ | + {3:-- User defined completion (^U^N^P) }{4:match 1 of 3} | + ]]) + feed('<C-n>') + screen:expect([[ + | + June^ | + {1:January } | + {2:June } | + {1:July } | + ~ | + ~ | + {3:-- User defined completion (^U^N^P) }{4:match 2 of 3} | + ]]) + feed('<Esc>') + screen:expect([[ + | + Jun^e | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) feed('.') + screen:expect([[ + | + June | + Jun^e | + ~ | + ~ | + ~ | + ~ | + | + ]]) expect([[ June @@ -155,60 +473,245 @@ describe('completion', function() end) end) + describe('with a lot of items', function() + before_each(function() + source([[ + function! TestComplete() abort + call complete(1, map(range(0,100), "string(v:val)")) + return '' + endfunction + ]]) + execute("set completeopt=menuone,noselect") + end) + + it("works", function() + feed('i<C-r>=TestComplete()<CR>') + screen:expect([[ + ^ | + {1:0 }{6: } | + {1:1 }{2: } | + {1:2 }{2: } | + {1:3 }{2: } | + {1:4 }{2: } | + {1:5 }{2: } | + {3:-- INSERT --} | + ]]) + feed('7') + screen:expect([[ + 7^ | + {1:7 }{6: } | + {1:70 }{6: } | + {1:71 }{6: } | + {1:72 }{2: } | + {1:73 }{2: } | + {1:74 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<c-n>') + screen:expect([[ + 7^ | + {2:7 }{6: } | + {1:70 }{6: } | + {1:71 }{6: } | + {1:72 }{2: } | + {1:73 }{2: } | + {1:74 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<c-n>') + screen:expect([[ + 70^ | + {1:7 }{6: } | + {2:70 }{6: } | + {1:71 }{6: } | + {1:72 }{2: } | + {1:73 }{2: } | + {1:74 }{2: } | + {3:-- INSERT --} | + ]]) + end) + + it('can be navigated with <PageDown>, <PageUp>', function() + feed('i<C-r>=TestComplete()<CR>') + screen:expect([[ + ^ | + {1:0 }{6: } | + {1:1 }{2: } | + {1:2 }{2: } | + {1:3 }{2: } | + {1:4 }{2: } | + {1:5 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<PageDown>') + screen:expect([[ + ^ | + {1:0 }{6: } | + {1:1 }{2: } | + {1:2 }{2: } | + {2:3 } | + {1:4 }{2: } | + {1:5 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<PageDown>') + screen:expect([[ + ^ | + {1:5 }{6: } | + {1:6 }{2: } | + {2:7 } | + {1:8 }{2: } | + {1:9 }{2: } | + {1:10 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<Down>') + screen:expect([[ + ^ | + {1:5 }{6: } | + {1:6 }{2: } | + {1:7 }{2: } | + {2:8 } | + {1:9 }{2: } | + {1:10 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<PageUp>') + screen:expect([[ + ^ | + {1:2 }{6: } | + {1:3 }{2: } | + {2:4 } | + {1:5 }{2: } | + {1:6 }{2: } | + {1:7 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<PageUp>') -- stop on first item + screen:expect([[ + ^ | + {2:0 }{6: } | + {1:1 }{2: } | + {1:2 }{2: } | + {1:3 }{2: } | + {1:4 }{2: } | + {1:5 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<PageUp>') -- when on first item, unselect + screen:expect([[ + ^ | + {1:0 }{6: } | + {1:1 }{2: } | + {1:2 }{2: } | + {1:3 }{2: } | + {1:4 }{2: } | + {1:5 }{2: } | + {3:-- INSERT --} | + ]]) + feed('<PageUp>') -- when unselected, select last item + screen:expect([[ + ^ | + {1:95 }{2: } | + {1:96 }{2: } | + {1:97 }{2: } | + {1:98 }{2: } | + {1:99 }{2: } | + {2:100 }{6: } | + {3:-- INSERT --} | + ]]) + feed('<PageUp>') + screen:expect([[ + ^ | + {1:94 }{2: } | + {1:95 }{2: } | + {2:96 } | + {1:97 }{2: } | + {1:98 }{2: } | + {1:99 }{6: } | + {3:-- INSERT --} | + ]]) + feed('<cr>') + screen:expect([[ + 96^ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- INSERT --} | + ]]) + end) + end) + + it('disables folding during completion', function () execute("set foldmethod=indent") - feed('i<Tab>foo<CR><Tab>bar<Esc>ggA<C-x><C-l>') + feed('i<Tab>foo<CR><Tab>bar<Esc>gg') + screen:expect([[ + ^foo | + bar | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + feed('A<C-x><C-l>') + screen:expect([[ + foo^ | + bar | + ~ | + ~ | + ~ | + ~ | + ~ | + {3:-- Whole line completion (^L^N^P) }{7:Pattern not found} | + ]]) eq(-1, eval('foldclosed(1)')) end) it('popupmenu is not interrupted by events', function () - local screen = Screen.new(40, 8) - screen:attach() - screen:set_default_attr_ignore({{bold=true, foreground=Screen.colors.Blue}}) - screen:set_default_attr_ids({ - [1] = {background = Screen.colors.LightMagenta}, - [2] = {background = Screen.colors.Grey}, - [3] = {bold = true}, - [4] = {bold = true, foreground = Screen.colors.SeaGreen}, - }) - execute("set complete=.") + feed('ifoobar fooegg<cr>f<c-p>') screen:expect([[ - foobar fooegg | - fooegg^ | - {1:foobar } | - {2:fooegg } | - ~ | - ~ | - ~ | - {3:-- }{4:match 1 of 2} | + foobar fooegg | + fooegg^ | + {1:foobar } | + {2:fooegg } | + ~ | + ~ | + ~ | + {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | ]]) eval('1 + 1') -- popupmenu still visible screen:expect([[ - foobar fooegg | - fooegg^ | - {1:foobar } | - {2:fooegg } | - ~ | - ~ | - ~ | - {3:-- }{4:match 1 of 2} | + foobar fooegg | + fooegg^ | + {1:foobar } | + {2:fooegg } | + ~ | + ~ | + ~ | + {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | ]]) feed('<c-p>') -- Didn't restart completion: old matches still used screen:expect([[ - foobar fooegg | - foobar^ | - {2:foobar } | - {1:fooegg } | - ~ | - ~ | - ~ | - {3:-- }{4:match 2 of 2} | + foobar fooegg | + foobar^ | + {2:foobar } | + {1:fooegg } | + ~ | + ~ | + ~ | + {3:-- Keyword completion (^N^P) }{4:match 2 of 2} | ]]) end) diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index 3f86c5f1b1..00637e0b8d 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -238,7 +238,7 @@ local function standalone(...) -- luacheck: ignore end -- uncomment this line (and comment the `return`) for standalone debugging -- example usage: --- ../../.deps/usr/bin/luajit formatc.lua ../../include/tempfile.h.generated.h +-- ../../.deps/usr/bin/luajit formatc.lua ../../include/fileio.h.generated.h -- ../../.deps/usr/bin/luajit formatc.lua /usr/include/malloc.h -- standalone(...) return formatc diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 9b9c1fef0f..426ae2d9e0 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -140,6 +140,7 @@ do local time = cimport('./src/nvim/os/time.h') time.time_init() main.early_init() + main.event_init() end -- C constants. diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua index 6d1a9f3589..93103e4e8c 100644 --- a/test/unit/os/shell_spec.lua +++ b/test/unit/os/shell_spec.lua @@ -11,7 +11,7 @@ if allowed_os[jit.os] ~= true then end local helpers = require('test.unit.helpers') -local shell = helpers.cimport( +local cimported = helpers.cimport( './src/nvim/os/shell.h', './src/nvim/option_defs.h', './src/nvim/main.h', @@ -25,18 +25,17 @@ local NULL = ffi.cast('void *', 0) describe('shell functions', function() setup(function() - shell.event_init() -- os_system() can't work when the p_sh and p_shcf variables are unset - shell.p_sh = to_cstr('/bin/bash') - shell.p_shcf = to_cstr('-c') + cimported.p_sh = to_cstr('/bin/bash') + cimported.p_shcf = to_cstr('-c') end) teardown(function() - shell.event_teardown() + cimported.event_teardown() end) local function shell_build_argv(cmd, extra_args) - local res = shell.shell_build_argv( + local res = cimported.shell_build_argv( cmd and to_cstr(cmd), extra_args and to_cstr(extra_args)) local argc = 0 @@ -45,10 +44,10 @@ describe('shell functions', function() -- crash. while res[argc] ~= nil do ret[#ret + 1] = ffi.string(res[argc]) - shell.xfree(res[argc]) + cimported.xfree(res[argc]) argc = argc + 1 end - shell.xfree(res) + cimported.xfree(res) return ret end @@ -59,8 +58,8 @@ describe('shell functions', function() local nread = ffi.new('size_t[1]') local argv = ffi.cast('char**', - shell.shell_build_argv(to_cstr(cmd), nil)) - local status = shell.os_system(argv, input_or, input_len, output, nread) + cimported.shell_build_argv(to_cstr(cmd), nil)) + local status = cimported.os_system(argv, input_or, input_len, output, nread) return status, intern(output[0], nread[0]) end @@ -97,13 +96,13 @@ describe('shell functions', function() local saved_opts = {} setup(function() - saved_opts.p_sh = shell.p_sh - saved_opts.p_shcf = shell.p_shcf + saved_opts.p_sh = cimported.p_sh + saved_opts.p_shcf = cimported.p_shcf end) teardown(function() - shell.p_sh = saved_opts.p_sh - shell.p_shcf = saved_opts.p_shcf + cimported.p_sh = saved_opts.p_sh + cimported.p_shcf = saved_opts.p_shcf end) it('works with NULL arguments', function() @@ -123,8 +122,8 @@ describe('shell functions', function() end) it('splits and unquotes &shell and &shellcmdflag', function() - shell.p_sh = to_cstr('/Program" "Files/zsh -f') - shell.p_shcf = to_cstr('-x -o "sh word split" "-"c') + cimported.p_sh = to_cstr('/Program" "Files/zsh -f') + cimported.p_shcf = to_cstr('-x -o "sh word split" "-"c') eq({'/Program Files/zsh', '-f', 'ghi jkl', '-x', '-o', 'sh word split', diff --git a/test/unit/tempfile_spec.lua b/test/unit/tempfile_spec.lua index e558ff04c8..b3e84db132 100644 --- a/test/unit/tempfile_spec.lua +++ b/test/unit/tempfile_spec.lua @@ -2,7 +2,7 @@ local lfs = require 'lfs' local helpers = require 'test.unit.helpers' local os = helpers.cimport './src/nvim/os/os.h' -local tempfile = helpers.cimport './src/nvim/tempfile.h' +local tempfile = helpers.cimport './src/nvim/fileio.h' describe('tempfile related functions', function() after_each(function() |