diff options
42 files changed, 1469 insertions, 290 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/eval.txt b/runtime/doc/eval.txt index 148ac7732a..e341a2247a 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -5548,14 +5548,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 @@ -5563,6 +5564,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 @@ -6144,6 +6151,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 @@ -6158,6 +6169,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/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 ecd13d5ee0..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 */ @@ -955,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/edit.c b/src/nvim/edit.c index 3a4b475bc8..667ce1e779 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -7741,6 +7741,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 31b264395b..9553c7a7ed 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8304,12 +8304,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; @@ -8321,8 +8321,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; @@ -13536,14 +13538,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. @@ -13569,13 +13571,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); @@ -13591,9 +13594,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; @@ -13614,10 +13615,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) { @@ -14947,6 +14953,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; @@ -14968,6 +14976,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. @@ -15114,6 +15137,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; @@ -15135,6 +15160,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; @@ -17836,6 +17867,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 69bf7abe6e..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> @@ -273,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; @@ -302,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); @@ -360,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) { @@ -370,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; @@ -423,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 @@ -452,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; @@ -460,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; @@ -5016,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_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/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/if_cscope.c b/src/nvim/if_cscope.c index daddd7cee4..2f9ec0b3ff 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1062,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/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/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 f9d1cdbaec..3376348d6f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2057,6 +2057,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 +2145,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); @@ -2983,6 +2985,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; @@ -5111,6 +5131,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; @@ -5164,6 +5188,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); @@ -5201,6 +5226,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 @@ -5580,6 +5607,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/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/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/testdir/Makefile b/src/nvim/testdir/Makefile index 55a49122d5..4debdd9acc 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -36,9 +36,12 @@ 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_alot.res SCRIPTS_GUI := test16.out 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/version.c b/src/nvim/version.c index ca6b66f220..e07c4a242a 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -533,8 +533,8 @@ static int included_patches[] = { // 1147, // 1146 NA // 1145 NA - // 1144 NA - // 1143, + 1144, + 1143, // 1142, 1141, // 1140, @@ -584,7 +584,7 @@ static int included_patches[] = { // 1096, // 1095 NA // 1094, - // 1093, + 1093, // 1092, // 1091, // 1090, @@ -693,7 +693,7 @@ static int included_patches[] = { // 987 NA // 986 NA // 985 NA - // 984, + 984, // 983, // 982 NA 981, @@ -720,13 +720,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 @@ -735,8 +735,8 @@ static int included_patches[] = { 945, 944, // 943 NA - // 942, - // 941, + 942, + 941, // 940 NA 939, // 938 NA @@ -855,7 +855,7 @@ static int included_patches[] = { 825, // 824 NA 823, - // 822, + 822, // 821 NA 820, 819, diff --git a/src/nvim/window.c b/src/nvim/window.c index 93a7a58678..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 */ 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/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/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', |