diff options
34 files changed, 1206 insertions, 191 deletions
| diff --git a/appveyor.yml b/appveyor.yml index 2d6135c7a2..30b7947da0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,6 +9,8 @@ matrix:    allow_failures:      - configuration: MINGW_64-gcov  install: [] +before_build: +- ps: Install-Product node 8  build_script:  - call ci\build.bat  cache: diff --git a/ci/before_install.sh b/ci/before_install.sh index f84ad935bc..f5a57ad657 100755 --- a/ci/before_install.sh +++ b/ci/before_install.sh @@ -37,3 +37,15 @@ else    # https://github.com/travis-ci/travis-ci/issues/8363    pip3 -q install --user --upgrade pip || true  fi + +if [[ "${TRAVIS_OS_NAME}" == linux ]]; then +  echo "Install node (LTS)" + +  if [ ! -f ~/.nvm/nvm.sh ]; then +    curl -o ~/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/master/nvm.sh +  fi + +  source ~/.nvm/nvm.sh +  nvm install --lts +  nvm use --lts +fi diff --git a/ci/build.bat b/ci/build.bat index 25f949b5e4..9909d102a4 100644 --- a/ci/build.bat +++ b/ci/build.bat @@ -37,6 +37,9 @@ set PATH=C:\Ruby24\bin;%PATH%  cmd /c gem.cmd install neovim || goto :error  where.exe neovim-ruby-host.bat || goto :error +cmd /c npm.cmd install -g neovim || goto :error +where.exe neovim-node-host.cmd || goto :error +  mkdir .deps  cd .deps  cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo ..\third-party\ || goto :error diff --git a/ci/install.sh b/ci/install.sh index c8a0c8825d..2fe4f88822 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -23,3 +23,8 @@ CC=cc pip3 -q install --user --upgrade neovim || true  echo "Install neovim RubyGem."  gem install --no-document --version ">= 0.2.0" neovim + +if [[ "${TRAVIS_OS_NAME}" == linux ]]; then +  echo "Install neovim npm package" +  npm install -g neovim +fi diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 0201ed8062..39e592c471 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -253,14 +253,18 @@ function! s:check_python(version) abort    if !empty(pyenv)      if empty(pyenv_root) -      call health#report_warn( -            \ 'pyenv was found, but $PYENV_ROOT is not set.', -            \ ['Did you follow the final install instructions?', -            \  'If you use a shell "framework" like Prezto or Oh My Zsh, try without.', -            \  'Try a different shell (bash).'] +      call health#report_info( +            \ 'pyenv was found, but $PYENV_ROOT is not set. `pyenv root` will be used.' +            \ .' If you run into problems, try setting $PYENV_ROOT explicitly.'              \ ) +      let pyenv_root = s:trim(s:system([pyenv, 'root'])) +    endif + +    if !isdirectory(pyenv_root) +      call health#report_error('Invalid pyenv root: '.pyenv_root)      else -      call health#report_ok(printf('pyenv found: "%s"', pyenv)) +      call health#report_info(printf('pyenv: %s', pyenv)) +      call health#report_info(printf('pyenv root: %s', pyenv_root))      endif    endif @@ -451,10 +455,11 @@ function! s:check_ruby() abort    let host = provider#ruby#Detect()    if empty(host) -    call health#report_warn('Missing "neovim" gem.', -          \ ['Run in shell: gem install neovim', -          \  'Is the gem bin directory in $PATH? Check `gem environment`.', -          \  'If you are using rvm/rbenv/chruby, try "rehashing".']) +    call health#report_warn("`neovim-ruby-host` not found.", +          \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.', +          \  'Run `gem environment` to ensure the gem bin directory is in $PATH.', +          \  'If you are using rvm/rbenv/chruby, try "rehashing".', +          \  'See :help g:ruby_host_prog for non-standard gem installations.'])      return    endif    call health#report_info('Host: '. host) @@ -488,7 +493,7 @@ function! s:check_ruby() abort  endfunction  function! s:check_node() abort -  call health#report_start('Node provider (optional)') +  call health#report_start('Node.js provider (optional)')    let loaded_var = 'g:loaded_node_provider'    if exists(loaded_var) && !exists('*provider#node#Call') @@ -502,7 +507,16 @@ function! s:check_node() abort            \ ['Install Node.js and verify that `node` and `npm` commands work.'])      return    endif -  call health#report_info('Node: '. s:system('node -v')) +  let node_v = get(split(s:system('node -v'), "\n"), 0, '') +  call health#report_info('Node.js: '. node_v) +  if !s:shell_error && s:version_cmp(node_v[1:], '6.0.0') < 0 +    call health#report_warn('Neovim node.js host does not support '.node_v) +    " Skip further checks, they are nonsense if nodejs is too old. +    return +  endif +  if !provider#node#can_inspect() +    call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.') +  endif    let host = provider#node#Detect()    if empty(host) @@ -511,7 +525,7 @@ function! s:check_node() abort            \  'Is the npm bin directory in $PATH?'])      return    endif -  call health#report_info('Host: '. host) +  call health#report_info('Neovim node.js host: '. host)    let latest_npm_cmd = has('win32') ? 'cmd /c npm info neovim --json' : 'npm info neovim --json'    let latest_npm = s:system(split(latest_npm_cmd)) @@ -530,11 +544,11 @@ function! s:check_node() abort      let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse')    endif -  let current_npm_cmd = host .' --version' +  let current_npm_cmd = ['node', host, '--version']    let current_npm = s:system(current_npm_cmd)    if s:shell_error -    call health#report_error('Failed to run: '. current_npm_cmd, -          \ ['Report this issue with the output of: ', current_npm_cmd]) +    call health#report_error('Failed to run: '. string(current_npm_cmd), +          \ ['Report this issue with the output of: ', string(current_npm_cmd)])      return    endif @@ -544,7 +558,7 @@ function! s:check_node() abort            \ current_npm, latest_npm),            \ ['Run in shell: npm update neovim'])    else -    call health#report_ok('Latest "neovim" npm is installed: '. current_npm) +    call health#report_ok('Latest "neovim" npm package is installed: '. current_npm)    endif  endfunction diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim index b08ad4f316..4e737fb51c 100644 --- a/runtime/autoload/provider/node.vim +++ b/runtime/autoload/provider/node.vim @@ -5,11 +5,56 @@ let g:loaded_node_provider = 1  let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')} +function! s:is_minimum_version(version, min_major, min_minor) abort +  if empty(a:version) +    let nodejs_version = get(split(system(['node', '-v']), "\n"), 0, '') +    if v:shell_error || nodejs_version[0] !=# 'v' +      return 0 +    endif +  else +    let nodejs_version = a:version +  endif +  " Remove surrounding junk.  Example: 'v4.12.0' => '4.12.0' +  let nodejs_version = matchstr(nodejs_version, '\(\d\.\?\)\+') +  " [major, minor, patch] +  let v_list = split(nodejs_version, '\.') +  return len(v_list) == 3 +    \ && ((str2nr(v_list[0]) > str2nr(a:min_major)) +    \     || (str2nr(v_list[0]) == str2nr(a:min_major) +    \         && str2nr(v_list[1]) >= str2nr(a:min_minor))) +endfunction + +" Support for --inspect-brk requires node 6.12+ or 7.6+ or 8+ +" Return 1 if it is supported +" Return 0 otherwise +function! provider#node#can_inspect() abort +  if !executable('node') +    return 0 +  endif +  let ver = get(split(system(['node', '-v']), "\n"), 0, '') +  if v:shell_error || ver[0] !=# 'v' +    return 0 +  endif +  return (ver[1] ==# '6' && s:is_minimum_version(ver, 6, 12)) +    \ || s:is_minimum_version(ver, 7, 6) +endfunction +  function! provider#node#Detect() abort -  return has('win32') ? exepath('neovim-node-host.cmd') : exepath('neovim-node-host') +  let global_modules = get(split(system('npm root -g'), "\n"), 0, '') +  if v:shell_error || !isdirectory(global_modules) +    return '' +  endif +  if !s:is_minimum_version(v:null, 6, 0) +    return '' +  endif +  let entry_point = glob(global_modules . '/neovim/bin/cli.js') +  if !filereadable(entry_point) +    return '' +  endif +  return entry_point  endfunction -function! provider#node#Prog() +function! provider#node#Prog() abort    return s:prog  endfunction @@ -19,18 +64,14 @@ function! provider#node#Require(host) abort      return    endif -  if has('win32') -    let args = provider#node#Prog() -  else -    let args = ['node'] - -    if !empty($NVIM_NODE_HOST_DEBUG) -      call add(args, '--inspect-brk') -    endif +  let args = ['node'] -    call add(args , provider#node#Prog()) +  if !empty($NVIM_NODE_HOST_DEBUG) && provider#node#can_inspect() +    call add(args, '--inspect-brk')    endif +  call add(args, provider#node#Prog()) +    try      let channel_id = jobstart(args, s:job_opts)      if rpcrequest(channel_id, 'poll') ==# 'ok' @@ -49,7 +90,7 @@ function! provider#node#Require(host) abort    throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_NODE_LOG_FILE')  endfunction -function! provider#node#Call(method, args) +function! provider#node#Call(method, args) abort    if s:err != ''      echoerr s:err      return diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim index 81fe194cb9..a06cbe4814 100644 --- a/runtime/autoload/provider/python.vim +++ b/runtime/autoload/provider/python.vim @@ -11,11 +11,11 @@ let g:loaded_python_provider = 1  let [s:prog, s:err] = provider#pythonx#Detect(2) -function! provider#python#Prog() +function! provider#python#Prog() abort    return s:prog  endfunction -function! provider#python#Error() +function! provider#python#Error() abort    return s:err  endfunction @@ -29,7 +29,7 @@ endif  call remote#host#RegisterClone('legacy-python-provider', 'python')  call remote#host#RegisterPlugin('legacy-python-provider', 'script_host.py', []) -function! provider#python#Call(method, args) +function! provider#python#Call(method, args) abort    if s:err != ''      return    endif diff --git a/runtime/autoload/provider/python3.vim b/runtime/autoload/provider/python3.vim index 0c3b75b73d..242a224cb3 100644 --- a/runtime/autoload/provider/python3.vim +++ b/runtime/autoload/provider/python3.vim @@ -11,11 +11,11 @@ let g:loaded_python3_provider = 1  let [s:prog, s:err] = provider#pythonx#Detect(3) -function! provider#python3#Prog() +function! provider#python3#Prog() abort    return s:prog  endfunction -function! provider#python3#Error() +function! provider#python3#Error() abort    return s:err  endfunction @@ -29,7 +29,7 @@ endif  call remote#host#RegisterClone('legacy-python3-provider', 'python3')  call remote#host#RegisterPlugin('legacy-python3-provider', 'script_host.py', []) -function! provider#python3#Call(method, args) +function! provider#python3#Call(method, args) abort    if s:err != ''      return    endif diff --git a/runtime/autoload/provider/ruby.vim b/runtime/autoload/provider/ruby.vim index da73a0dfc0..3fb65fecdf 100644 --- a/runtime/autoload/provider/ruby.vim +++ b/runtime/autoload/provider/ruby.vim @@ -7,7 +7,7 @@ let g:loaded_ruby_provider = 1  let s:stderr = {}  let s:job_opts = {'rpc': v:true} -function! s:job_opts.on_stderr(chan_id, data, event) +function! s:job_opts.on_stderr(chan_id, data, event) abort    let stderr = get(s:stderr, a:chan_id, [''])    let last = remove(stderr, -1)    let a:data[0] = last.a:data[0] @@ -23,7 +23,7 @@ function! provider#ruby#Detect() abort    end  endfunction -function! provider#ruby#Prog() +function! provider#ruby#Prog() abort    return s:prog  endfunction @@ -50,7 +50,7 @@ function! provider#ruby#Require(host) abort    throw remote#host#LoadErrorForHost(a:host.orig_name, '$NVIM_RUBY_LOG_FILE')  endfunction -function! provider#ruby#Call(method, args) +function! provider#ruby#Call(method, args) abort    if s:err != ''      echoerr s:err      return diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 6c3fd43db0..a9474b58a3 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6845,6 +6845,7 @@ setqflist({list} [, {action}[, {what}]])		*setqflist()*  		    nr		error number  		    text	description of the error  		    type	single-character error type, 'E', 'W', etc. +		    valid	recognized error message  		The "col", "vcol", "nr", "type" and "text" entries are  		optional.  Either "lnum" or "pattern" entry can be used to @@ -6854,21 +6855,26 @@ setqflist({list} [, {action}[, {what}]])		*setqflist()*  		item will not be handled as an error line.  		If both "pattern" and "lnum" are present then "pattern" will  		be used. +		If the "valid" entry is not supplied, then the valid flag is +		set when "bufnr" is a valid buffer or "filename" exists.  		If you supply an empty {list}, the quickfix list will be  		cleared.  		Note that the list is not exactly the same as what  		|getqflist()| returns. -							*E927* -		If {action} is set to 'a', then the items from {list} are -		added to the existing quickfix list. If there is no existing -		list, then a new list is created. +		{action} values:				*E927* +		'a'	The items from {list} are added to the existing +			quickfix list. If there is no existing list, then a +			new list is created. -		If {action} is set to 'r', then the items from the current -		quickfix list are replaced with the items from {list}.  This -		can also be used to clear the list: > -			:call setqflist([], 'r') +		'r'	The items from the current quickfix list are replaced +			with the items from {list}.  This can also be used to +			clear the list: > +				:call setqflist([], 'r')  <	 +		'f'	All the quickfix lists in the quickfix stack are +			freed. +  		If {action} is not present or is set to ' ', then a new list  		is created. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index d3072d83e2..f70ec32bd8 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3809,6 +3809,23 @@ A jump table for the options with a short description can be found at |Q_op|.  	This option cannot be set from a |modeline| or in the |sandbox|, for  	security reasons. +					*'makeencoding'* *'menc'* +'makeencoding' 'menc'	string	(default "") +			global or local to buffer |global-local| +			{only available when compiled with the |+multi_byte| +			feature} +			{not in Vi} +	Encoding used for reading the output of external commands.  When empty, +	encoding is not converted. +	This is used for `:make`, `:lmake`, `:grep`, `:lgrep`, `:grepadd`, +	`:lgrepadd`, `:cfile`, `:cgetfile`, `:caddfile`, `:lfile`, `:lgetfile`, +	and `:laddfile`. + +	This would be mostly useful when you use MS-Windows.  If |+iconv| is +	enabled and GNU libiconv is used, setting 'makeencoding' to "char" has +	the same effect as setting to the system locale encoding.  Example: > +		:set makeencoding=char	" system locale is used +<  						*'makeprg'* *'mp'*  'makeprg' 'mp'		string	(default "make")  			global or local to buffer |global-local| diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index da167c0f5b..74f82b2c65 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -165,6 +165,9 @@ processing a quickfix or location list command, it will be aborted.  			keep Vim running while compiling.  If you give the  			name of the errorfile, the 'errorfile' option will  			be set to [errorfile].  See |:cc| for [!]. +			If the encoding of the error file differs from the +			'encoding' option, you can use the 'makeencoding' +			option to specify the encoding.  							*:lf* *:lfile*  :lf[ile][!] [errorfile]	Same as ":cfile", except the location list for the @@ -176,6 +179,9 @@ processing a quickfix or location list command, it will be aborted.  :cg[etfile] [errorfile]					*:cg* *:cgetfile*  			Read the error file.  Just like ":cfile" but don't  			jump to the first error. +			If the encoding of the error file differs from the +			'encoding' option, you can use the 'makeencoding' +			option to specify the encoding.  :lg[etfile] [errorfile]					*:lg* *:lgetfile* @@ -186,6 +192,9 @@ processing a quickfix or location list command, it will be aborted.  :caddf[ile] [errorfile]	Read the error file and add the errors from the  			errorfile to the current quickfix list. If a quickfix  			list is not present, then a new list is created. +			If the encoding of the error file differs from the +			'encoding' option, you can use the 'makeencoding' +			option to specify the encoding.  							*:laddf* *:laddfile*  :laddf[ile] [errorfile]	Same as ":caddfile", except the location list for the @@ -322,6 +331,7 @@ use this code: >  	endfunction  	au QuickfixCmdPost make call QfMakeConv() +Another option is using 'makeencoding'.  EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST:  							*:cdo* @@ -586,6 +596,9 @@ lists, use ":cnewer 99" first.  			   like |:cnext| and |:cprevious|, see above.  			This command does not accept a comment, any "  			characters are considered part of the arguments. +			If the encoding of the program output differs from the +			'encoding' option, you can use the 'makeencoding' +			option to specify the encoding.  							*:lmak* *:lmake*  :lmak[e][!] [arguments] @@ -645,6 +658,7 @@ read the error messages: >  	au QuickfixCmdPost make call QfMakeConv()  (Example by Faque Cheng) +Another option is using 'makeencoding'.  ==============================================================================  5. Using :vimgrep and :grep				*grep* *lid* @@ -759,6 +773,9 @@ id-utils) in a similar way to its compiler integration (see |:make| above).  			When 'grepprg' is "internal" this works like  			|:vimgrep|.  Note that the pattern needs to be  			enclosed in separator characters then. +			If the encoding of the program output differs from the +			'encoding' option, you can use the 'makeencoding' +			option to specify the encoding.  							    *:lgr* *:lgrep*  :lgr[ep][!] [arguments]	Same as ":grep", except the location list for the @@ -783,6 +800,10 @@ id-utils) in a similar way to its compiler integration (see |:make| above).  				  \ | catch /E480:/  				  \ | endtry"  < +			If the encoding of the program output differs from the +			'encoding' option, you can use the 'makeencoding' +			option to specify the encoding. +  							*:lgrepa* *:lgrepadd*  :lgrepa[dd][!] [arguments]  			Same as ":grepadd", except the location list for the diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index b22d2afa7e..902b0175a2 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -762,6 +762,7 @@ Short explanation of each option:		*option-list*  'loadplugins'	  'lpl'     load plugin scripts when starting up  'magic'			    changes special characters in search patterns  'makeef'	  'mef'     name of the errorfile for ":make" +'makeencoding'	  'menc'    encoding of external make/grep commands  'makeprg'	  'mp'	    program to use for the ":make" command  'matchpairs'	  'mps'     pairs of characters that "%" can match  'matchtime'	  'mat'     tenths of a second to show matching paren diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 04bc16dd25..530701e223 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -92,8 +92,11 @@ get_vim_sources() {  }  commit_message() { -  printf 'vim-patch:%s\n\n%s\n\n%s' "${vim_version}" \ -    "${vim_message}" "${vim_commit_url}" +  if [[ -n "$vim_tag" ]]; then +    printf '%s\n\n%s' "${vim_message}" "${vim_commit_url}" +  else +    printf 'vim-patch:%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" +  fi  }  find_git_remote() { @@ -108,22 +111,23 @@ assign_commit_details() {      vim_tag="v${1}"      vim_commit=$(cd "${VIM_SOURCE_DIR}" \        && git log -1 --format="%H" "${vim_tag}") -    local strip_commit_line=true +    local munge_commit_line=true    else      # Interpret parameter as commit hash.      vim_version="${1:0:12}" +    vim_tag=      vim_commit=$(cd "${VIM_SOURCE_DIR}" \        && git log -1 --format="%H" "${vim_version}") -    local strip_commit_line=false +    local munge_commit_line=false    fi    vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}"    vim_message="$(cd "${VIM_SOURCE_DIR}" \      && git log -1 --pretty='format:%B' "${vim_commit}" \        | sed -e 's/\(#[0-9]*\)/vim\/vim\1/g')" -  if [[ ${strip_commit_line} == "true" ]]; then +  if [[ ${munge_commit_line} == "true" ]]; then      # Remove first line of commit message. -    vim_message="$(echo "${vim_message}" | sed -e '1d')" +    vim_message="$(echo "${vim_message}" | sed -e '1s/^patch /vim-patch:/')"    fi    patch_file="vim-${vim_version}.patch"  } @@ -286,9 +290,10 @@ submit_pr() {    local git_remote    git_remote="$(find_git_remote)"    local pr_body -  pr_body="$(git log --reverse --format='#### %s%n%n%b%n' "${git_remote}"/master..HEAD)" +  pr_body="$(git log --grep=vim-patch --reverse --format='#### %s%n%n%b%n' "${git_remote}"/master..HEAD)"    local patches -  patches=("$(git log --reverse --format='%s' "${git_remote}"/master..HEAD)") +  # Extract just the "vim-patch:X.Y.ZZZZ" or "vim-patch:sha" portion of each log +  patches=("$(git log --grep=vim-patch --reverse --format='%s' "${git_remote}"/master..HEAD | sed 's/: .*//')")    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/.asan-blacklist b/src/.asan-blacklist index 928d81bd5a..9d7f721a88 100644 --- a/src/.asan-blacklist +++ b/src/.asan-blacklist @@ -1,3 +1,7 @@  # multiqueue.h pointer arithmetic is not accepted by asan  fun:multiqueue_node_data  fun:tv_dict_watcher_node_data + +# Allocation in loop_schedule_deferred() is freed by loop_deferred_event(), but +# this sometimes does not happen during teardown. +func:loop_schedule_deferred diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 766003a021..21830539f5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1808,6 +1808,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)    buf->b_p_ul = NO_LOCAL_UNDOLEVEL;    clear_string_option(&buf->b_p_lw);    clear_string_option(&buf->b_p_bkc); +  clear_string_option(&buf->b_p_menc);  } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f1cbcb2627..5702ceaaef 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -637,6 +637,7 @@ struct file_buffer {    uint32_t b_p_fex_flags;       ///< flags for 'formatexpr'    char_u *b_p_kp;               ///< 'keywordprg'    int b_p_lisp;                 ///< 'lisp' +  char_u *b_p_menc;             ///< 'makeencoding'    char_u *b_p_mps;              ///< 'matchpairs'    int b_p_ml;                   ///< 'modeline'    int b_p_ml_nobin;             ///< b_p_ml saved for binary mode diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6b04dd4eea..e6cb8cdec0 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5183,6 +5183,8 @@ bool garbage_collect(bool testing)      ABORTING(set_ref_list)(sub.additional_elements, copyID);    } +  ABORTING(set_ref_in_quickfix)(copyID); +    bool did_free = false;    if (!abort) {      // 2. Free lists and dictionaries that are not referenced. @@ -14611,7 +14613,8 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)      return;    }    const char *const act = tv_get_string_chk(action_arg); -  if ((*act == 'a' || *act == 'r' || *act == ' ') && act[1] == NUL) { +  if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') +      && act[1] == NUL) {      action = *act;    } else {      EMSG2(_(e_invact), act); diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 5adf16c0f3..55ef0261d9 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -30,19 +30,22 @@ void loop_init(Loop *loop, void *data)    uv_signal_init(&loop->uv, &loop->children_watcher);    uv_timer_init(&loop->uv, &loop->children_kill_timer);    uv_timer_init(&loop->uv, &loop->poll_timer); +  loop->poll_timer.data = xmalloc(sizeof(bool));  // "timeout expired" flag  } -void loop_poll_events(Loop *loop, int ms) +/// @returns true if `ms` timeout was reached +bool loop_poll_events(Loop *loop, int ms)  {    if (loop->recursive++) {      abort();  // Should not re-enter uv_run    }    uv_run_mode mode = UV_RUN_ONCE; +  bool timeout_expired = false;    if (ms > 0) { -    // Use a repeating timeout of ms milliseconds to make sure -    // we do not block indefinitely for I/O. +    *((bool *)loop->poll_timer.data) = false;  // reset "timeout expired" flag +    // Dummy timer to ensure UV_RUN_ONCE does not block indefinitely for I/O.      uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms);    } else if (ms == 0) {      // For ms == 0, do a non-blocking event poll. @@ -52,11 +55,13 @@ void loop_poll_events(Loop *loop, int ms)    uv_run(&loop->uv, mode);    if (ms > 0) { +    timeout_expired = *((bool *)loop->poll_timer.data);      uv_timer_stop(&loop->poll_timer);    }    loop->recursive--;  // Can re-enter uv_run now    multiqueue_process_events(loop->fast_events); +  return timeout_expired;  }  /// Schedules an event from another thread. @@ -111,7 +116,7 @@ bool loop_close(Loop *loop, bool wait)    uv_mutex_destroy(&loop->mutex);    uv_close((uv_handle_t *)&loop->children_watcher, NULL);    uv_close((uv_handle_t *)&loop->children_kill_timer, NULL); -  uv_close((uv_handle_t *)&loop->poll_timer, NULL); +  uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb);    uv_close((uv_handle_t *)&loop->async, NULL);    uint64_t start = wait ? os_hrtime() : 0;    while (true) { @@ -163,5 +168,11 @@ static void async_cb(uv_async_t *handle)  static void timer_cb(uv_timer_t *handle)  { +  bool *timeout_expired = handle->data; +  *timeout_expired = true;  } +static void timer_close_cb(uv_handle_t *handle) +{ +  xfree(handle->data); +} diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 3c02f5acbd..654b4630c5 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1012,9 +1012,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,        fclose(f);        if (use_ll)           /* Use location list */          wp = curwin; -      /* '-' starts a new error list */ +      // '-' starts a new error list        if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m", -              *qfpos == '-', cmdline) > 0) { +                  *qfpos == '-', cmdline, NULL) > 0) {          if (postponed_split != 0) {            (void)win_split(postponed_split > 0 ? postponed_split : 0,                            postponed_split_flags); diff --git a/src/nvim/main.c b/src/nvim/main.c index 0346414697..0b24023ad0 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1369,7 +1369,7 @@ static void handle_quickfix(mparm_T *paramp)        set_string_option_direct((char_u *)"ef", -1,            paramp->use_ef, OPT_FREE, SID_CARG);      vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); -    if (qf_init(NULL, p_ef, p_efm, true, IObuff) < 0) { +    if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) {        ui_linefeed();        mch_exit(3);      } diff --git a/src/nvim/option.c b/src/nvim/option.c index a345906200..192d2b0f78 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2217,6 +2217,7 @@ void check_buf_options(buf_T *buf)    check_string_option(&buf->b_p_tsr);    check_string_option(&buf->b_p_lw);    check_string_option(&buf->b_p_bkc); +  check_string_option(&buf->b_p_menc);  }  /* @@ -2635,8 +2636,8 @@ did_set_string_option (    else if (varp == &p_ei) {      if (check_ei() == FAIL)        errmsg = e_invarg; -  /* 'encoding' and 'fileencoding' */ -  } else if (varp == &p_enc || gvarp == &p_fenc) { +  // 'encoding', 'fileencoding' and 'makeencoding' +  } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) {      if (gvarp == &p_fenc) {        if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) {          errmsg = e_modifiable; @@ -5400,6 +5401,9 @@ void unset_global_local_option(char *name, void *from)      case PV_LW:        clear_string_option(&buf->b_p_lw);        break; +    case PV_MENC: +      clear_string_option(&buf->b_p_menc); +      break;    }  } @@ -5433,6 +5437,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)      case PV_UL:   return (char_u *)&(curbuf->b_p_ul);      case PV_LW:   return (char_u *)&(curbuf->b_p_lw);      case PV_BKC:  return (char_u *)&(curbuf->b_p_bkc); +    case PV_MENC: return (char_u *)&(curbuf->b_p_menc);      }      return NULL;     /* "cannot happen" */    } @@ -5488,6 +5493,8 @@ static char_u *get_varp(vimoption_T *p)             ? (char_u *)&(curbuf->b_p_ul) : p->var;    case PV_LW:   return *curbuf->b_p_lw != NUL             ? (char_u *)&(curbuf->b_p_lw) : p->var; +  case PV_MENC: return *curbuf->b_p_menc != NUL +           ? (char_u *)&(curbuf->b_p_menc) : p->var;    case PV_ARAB:   return (char_u *)&(curwin->w_p_arab);    case PV_LIST:   return (char_u *)&(curwin->w_p_list); @@ -5885,6 +5892,7 @@ void buf_copy_options(buf_T *buf, int flags)        buf->b_p_qe = vim_strsave(p_qe);        buf->b_p_udf = p_udf;        buf->b_p_lw = empty_option; +      buf->b_p_menc = empty_option;        /*         * Don't copy the options set by ex_help(), use the saved values, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 1b978137ae..864723a10c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -489,6 +489,7 @@ EXTERN char_u   *p_lcs;         // 'listchars'  EXTERN int p_lz;                // 'lazyredraw'  EXTERN int p_lpl;               // 'loadplugins'  EXTERN int p_magic;             // 'magic' +EXTERN char_u   *p_menc;        // 'makeencoding'  EXTERN char_u   *p_mef;         // 'makeef'  EXTERN char_u   *p_mp;          // 'makeprg'  EXTERN char_u   *p_cc;          // 'colorcolumn' @@ -736,6 +737,7 @@ enum {    , BV_KP    , BV_LISP    , BV_LW +  , BV_MENC    , BV_MA    , BV_ML    , BV_MOD diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 45efd49391..35baaf948f 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1457,6 +1457,13 @@ return {        defaults={if_true={vi=""}}      },      { +      full_name='makeencoding', abbreviation='menc', +      type='string', scope={'global', 'buffer'}, +      vi_def=true, +      varname='p_menc', +      defaults={if_true={vi=""}} +    }, +    {        full_name='makeprg', abbreviation='mp',        type='string', scope={'global', 'buffer'},        secure=true, diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 7e2193b8bb..f85009dca8 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -85,6 +85,7 @@ typedef struct qf_list_S {    int qf_nonevalid;             // TRUE if not a single valid entry found    char_u      *qf_title;        // title derived from the command that created                                  // the error list +  typval_T    *qf_ctx;          // context set by setqflist/setloclist  } qf_list_T;  struct qf_info_S { @@ -161,6 +162,7 @@ typedef struct {    buf_T      *buf;    linenr_T buflnum;    linenr_T lnumlast; +  vimconv_T vc;  } qfstate_T;  typedef struct { @@ -194,19 +196,19 @@ typedef struct {  static char_u *qf_last_bufname = NULL;  static bufref_T  qf_last_bufref = { NULL, 0, 0 }; -/* - * Read the errorfile "efile" into memory, line by line, building the error - * list. Set the error list's title to qf_title. - * Return -1 for error, number of errors for success. - */ -int  -qf_init ( -    win_T *wp, -    char_u *efile, -    char_u *errorformat, -    int newlist,                            /* TRUE: start a new error list */ -    char_u *qf_title -) +/// Read the errorfile "efile" into memory, line by line, building the error +/// list. Set the error list's title to qf_title. +/// +/// @params  wp  If non-NULL, make a location list +/// @params  efile  If non-NULL, errorfile to parse +/// @params  errorformat  'errorformat' string used to parse the error lines +/// @params  newlist  If true, create a new error list +/// @params  qf_title  If non-NULL, title of the error list +/// @params  enc  If non-NULL, encoding used to parse errors +/// +/// @returns -1 for error, number of errors for success. +int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist, +            char_u *qf_title, char_u *enc)  {    qf_info_T       *qi = &ql_info; @@ -215,8 +217,8 @@ qf_init (    }    return qf_init_ext(qi, efile, curbuf, NULL, errorformat, newlist, -      (linenr_T)0, (linenr_T)0, -      qf_title); +                     (linenr_T)0, (linenr_T)0, +                     qf_title, enc);  }  // Maximum number of bytes allowed per line while reading an errorfile. @@ -640,6 +642,22 @@ retry:    } else {      state->linebuf = IObuff;    } + +  // Convert a line if it contains a non-ASCII character +  if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) { +    char_u *line = string_convert(&state->vc, state->linebuf, &state->linelen); +    if (line != NULL) { +      if (state->linelen < IOSIZE) { +        STRLCPY(state->linebuf, line, state->linelen + 1); +        xfree(line); +      } else { +        xfree(state->growbuf); +        state->linebuf = state->growbuf = line; +        state->growbufsiz = state->linelen < LINE_MAXLEN +          ? state->linelen : LINE_MAXLEN; +      } +    } +  }    return QF_OK;  } @@ -785,7 +803,7 @@ restofline:          fields->type = *regmatch.startp[i];        }        if (fmt_ptr->flags == '+' && !qi->qf_multiscan) {       // %+ -        if (linelen > fields->errmsglen) { +        if (linelen >= fields->errmsglen) {            // linelen + null terminator            fields->errmsg = xrealloc(fields->errmsg, linelen + 1);            fields->errmsglen = linelen + 1; @@ -796,7 +814,7 @@ restofline:            continue;          }          len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); -        if (len > fields->errmsglen) { +        if (len >= fields->errmsglen) {            // len + null terminator            fields->errmsg = xrealloc(fields->errmsg, len + 1);            fields->errmsglen = len + 1; @@ -873,7 +891,7 @@ restofline:      fields->namebuf[0] = NUL;                // no match found, remove file name      fields->lnum = 0;                        // don't jump to this line      fields->valid = false; -    if (linelen > fields->errmsglen) { +    if (linelen >= fields->errmsglen) {        // linelen + null terminator        fields->errmsg = xrealloc(fields->errmsg, linelen + 1);        fields->errmsglen = linelen + 1; @@ -978,15 +996,15 @@ qf_init_ext(      buf_T *buf,      typval_T *tv,      char_u *errorformat, -    int newlist,                            /* TRUE: start a new error list */ -    linenr_T lnumfirst,                     /* first line number to use */ -    linenr_T lnumlast,                      /* last line number to use */ -    char_u *qf_title +    int newlist,                            // TRUE: start a new error list +    linenr_T lnumfirst,                     // first line number to use +    linenr_T lnumlast,                      // last line number to use +    char_u *qf_title, +    char_u *enc  )  { -  qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, -                      NULL, NULL, 0, 0 }; -  qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; +  qfstate_T state; +  qffields_T fields;    qfline_T        *old_last = NULL;    bool adding = false;    static efm_T    *fmt_first = NULL; @@ -999,6 +1017,13 @@ qf_init_ext(    xfree(qf_last_bufname);    qf_last_bufname = NULL; +  memset(&state, 0, sizeof(state)); +  memset(&fields, 0, sizeof(fields)); +  state.vc.vc_type = CONV_NONE; +  if (enc != NULL && *enc != NUL) { +    convert_setup(&state.vc, enc, p_enc); +  } +    fields.namebuf = xmalloc(CMDBUFFSIZE + 1);    fields.errmsglen = CMDBUFFSIZE + 1;    fields.errmsg = xmalloc(fields.errmsglen); @@ -1150,6 +1175,10 @@ qf_init_end:    qf_update_buffer(qi, old_last); +  if (state.vc.vc_type != CONV_NONE) { +    convert_setup(&state.vc, NULL, NULL); +  } +    return retval;  } @@ -1384,6 +1413,13 @@ void copy_loclist(win_T *from, win_T *to)      else        to_qfl->qf_title = NULL; +    if (from_qfl->qf_ctx != NULL) { +      to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T)); +      tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); +    } else { +      to_qfl->qf_ctx = NULL; +    } +      if (from_qfl->qf_count) {        qfline_T    *from_qfp;        qfline_T    *prevp; @@ -2385,12 +2421,21 @@ static void qf_free(qf_info_T *qi, int idx)    qi->qf_lists[idx].qf_start = NULL;    qi->qf_lists[idx].qf_ptr = NULL;    qi->qf_lists[idx].qf_title = NULL; +  tv_free(qi->qf_lists[idx].qf_ctx); +  qi->qf_lists[idx].qf_ctx = NULL;    qi->qf_lists[idx].qf_index = 0; +  qi->qf_lists[idx].qf_start = NULL; +  qi->qf_lists[idx].qf_last = NULL; +  qi->qf_lists[idx].qf_ptr = NULL; +  qi->qf_lists[idx].qf_nonevalid = true;    qf_clean_dir_stack(&qi->qf_dir_stack);    qi->qf_directory = NULL;    qf_clean_dir_stack(&qi->qf_file_stack);    qi->qf_currfile = NULL; +  qi->qf_multiline = false; +  qi->qf_multiignore = false; +  qi->qf_multiscan = false;  }  /* @@ -3027,6 +3072,7 @@ void ex_make(exarg_T *eap)    qf_info_T   *qi = &ql_info;    int res;    char_u      *au_name = NULL; +  char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc;    /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */    if (grep_internal(eap->cmdidx)) { @@ -3086,11 +3132,11 @@ void ex_make(exarg_T *eap)    res = qf_init(wp, fname, (eap->cmdidx != CMD_make                              && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, -      (eap->cmdidx != CMD_grepadd -       && eap->cmdidx != CMD_lgrepadd), -      *eap->cmdlinep); -  if (wp != NULL) +                (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), +                *eap->cmdlinep, enc); +  if (wp != NULL) {      qi = GET_LOC_LIST(wp); +  }    if (au_name != NULL) {      apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,          curbuf->b_fname, TRUE, curbuf); @@ -3432,19 +3478,18 @@ void ex_cfile(exarg_T *eap)    if (*eap->arg != NUL)      set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); -  /* -   * This function is used by the :cfile, :cgetfile and :caddfile -   * commands. -   * :cfile always creates a new quickfix list and jumps to the -   * first error. -   * :cgetfile creates a new quickfix list but doesn't jump to the -   * first error. -   * :caddfile adds to an existing quickfix list. If there is no -   * quickfix list then a new list is created. -   */ +  char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; +  // This function is used by the :cfile, :cgetfile and :caddfile +  // commands. +  // :cfile always creates a new quickfix list and jumps to the +  // first error. +  // :cgetfile creates a new quickfix list but doesn't jump to the +  // first error. +  // :caddfile adds to an existing quickfix list. If there is no +  // quickfix list then a new list is created.    if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile                                  && eap->cmdidx != CMD_laddfile), -          *eap->cmdlinep) > 0 +              *eap->cmdlinep, enc) > 0        && (eap->cmdidx == CMD_cfile            || eap->cmdidx == CMD_lfile)) {      if (au_name != NULL) @@ -4028,6 +4073,7 @@ enum {    QF_GETLIST_ITEMS = 0x2,    QF_GETLIST_NR = 0x4,    QF_GETLIST_WINID = 0x8, +  QF_GETLIST_CONTEXT = 0x10,    QF_GETLIST_ALL = 0xFF  }; @@ -4078,6 +4124,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)      flags |= QF_GETLIST_WINID;    } +  if (tv_dict_find(what, S_LEN("context")) != NULL) { +    flags |= QF_GETLIST_CONTEXT; +  } +    if (flags & QF_GETLIST_TITLE) {      char_u *t = qi->qf_lists[qf_idx].qf_title;      if (t == NULL) { @@ -4095,6 +4145,20 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)      }    } +  if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { +    if (qi->qf_lists[qf_idx].qf_ctx != NULL) { +      di = tv_dict_item_alloc_len(S_LEN("context")); +      if (di != NULL) { +        tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); +        if (tv_dict_add(retdict, di) == FAIL) { +          tv_dict_item_free(di); +        } +      } +    } else { +      status = tv_dict_add_str(retdict, S_LEN("context"), ""); +    } +  } +    return status;  } @@ -4158,6 +4222,11 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title,        bufnum = 0;      } +    // If the 'valid' field is present it overrules the detected value. +    if (tv_dict_find(d, "valid", -1) != NULL) { +      valid = (int)tv_dict_get_number(d, "valid"); +    } +      int status = qf_add_entry(qi,                                NULL,      // dir                                (char_u *)filename, @@ -4213,7 +4282,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action)    if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) {      // Use the specified quickfix/location list      if (di->di_tv.v_type == VAR_NUMBER) { -      qf_idx = (int)di->di_tv.vval.v_number - 1; +      // for zero use the current list +      if (di->di_tv.vval.v_number != 0) { +        qf_idx = (int)di->di_tv.vval.v_number - 1; +      }        if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {          return FAIL;        } @@ -4240,9 +4312,75 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action)      }    } +  if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { +    tv_free(qi->qf_lists[qf_idx].qf_ctx); + +    typval_T *ctx = xcalloc(1, sizeof(typval_T)); +    tv_copy(&di->di_tv, ctx); +    qi->qf_lists[qf_idx].qf_ctx = ctx; +  } +    return retval;  } +// Find the non-location list window with the specified location list. +static win_T * find_win_with_ll(qf_info_T *qi) +{ +  FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { +    if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) { +      return wp; +    } +  } + +  return NULL; +} + +// Free the entire quickfix/location list stack. +// If the quickfix/location list window is open, then clear it. +static void qf_free_stack(win_T *wp, qf_info_T *qi) +{ +  win_T *qfwin = qf_find_win(qi); + +  if (qfwin != NULL) { +    // If the quickfix/location list window is open, then clear it +    if (qi->qf_curlist < qi->qf_listcount) { +      qf_free(qi, qi->qf_curlist); +    } +    qf_update_buffer(qi, NULL); +  } + +  win_T *llwin = NULL; +  win_T *orig_wp = wp; +  if (wp != NULL && IS_LL_WINDOW(wp)) { +    // If in the location list window, then use the non-location list +    // window with this location list (if present) +    llwin = find_win_with_ll(qi); +    if (llwin != NULL) { +      wp = llwin; +    } +  } + +  qf_free_all(wp); +  if (wp == NULL) { +    // quickfix list +    qi->qf_curlist = 0; +    qi->qf_listcount = 0; +  } else if (IS_LL_WINDOW(orig_wp)) { +    // If the location list window is open, then create a new empty location +    // list +    qf_info_T *new_ll = ll_new_list(); + +    // first free the list reference in the location list window +    ll_free_all(&orig_wp->w_llist_ref); + +    orig_wp->w_llist_ref = new_ll; +    if (llwin != NULL) { +      llwin->w_llist = new_ll; +      new_ll->qf_refcount++; +    } +  } +} +  // Populate the quickfix list with the items supplied in the list  // of dictionaries. "title" will be copied to w:quickfix_title  // "action" is 'a' for add, 'r' for replace.  Otherwise create a new list. @@ -4256,7 +4394,10 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,      qi = ll_get_or_alloc_list(wp);    } -  if (what != NULL) { +  if (action == 'f') { +    // Free the entire quickfix or location list stack +    qf_free_stack(wp, qi); +  } else if (what != NULL) {      retval = qf_set_properties(qi, what, action);    } else {      retval = qf_add_entries(qi, list, title, action); @@ -4265,6 +4406,42 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,    return retval;  } +static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) +{ +  bool abort = false; + +  for (int i = 0; i < LISTCOUNT && !abort; i++) { +    typval_T *ctx = qi->qf_lists[i].qf_ctx; +    if (ctx != NULL && ctx->v_type != VAR_NUMBER +        && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) { +      abort = set_ref_in_item(ctx, copyID, NULL, NULL); +    } +  } + +  return abort; +} + +/// Mark the context of the quickfix list and the location lists (if present) as +/// "in use". So that garabage collection doesn't free the context. +bool set_ref_in_quickfix(int copyID) +{ +  bool abort = mark_quickfix_ctx(&ql_info, copyID); +  if (abort) { +    return abort; +  } + +  FOR_ALL_TAB_WINDOWS(tp, win) { +    if (win->w_llist != NULL) { +      abort = mark_quickfix_ctx(win->w_llist, copyID); +      if (abort) { +        return abort; +      } +    } +  } + +  return abort; +} +  /*   * ":[range]cbuffer [bufnr]" command.   * ":[range]caddbuffer [bufnr]" command. @@ -4342,7 +4519,7 @@ void ex_cbuffer(exarg_T *eap)        if (qf_init_ext(qi, NULL, buf, NULL, p_efm,                        (eap->cmdidx != CMD_caddbuffer                         && eap->cmdidx != CMD_laddbuffer), -                      eap->line1, eap->line2, qf_title) > 0) { +                      eap->line1, eap->line2, qf_title, NULL) > 0) {          if (au_name != NULL) {            apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,                           curbuf->b_fname, true, curbuf); @@ -4407,7 +4584,7 @@ void ex_cexpr(exarg_T *eap)        if (qf_init_ext(qi, NULL, NULL, &tv, p_efm,                        (eap->cmdidx != CMD_caddexpr                         && eap->cmdidx != CMD_laddexpr), -                      (linenr_T)0, (linenr_T)0, *eap->cmdlinep) > 0) { +                      (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) {          if (au_name != NULL) {            apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,                           curbuf->b_fname, true, curbuf); @@ -4478,6 +4655,9 @@ void ex_helpgrep(exarg_T *eap)      }    } +  // Autocommands may change the list. Save it for later comparison +  qf_info_T *save_qi = qi; +    regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING);    regmatch.rm_ic = FALSE;    if (regmatch.regprog != NULL) { @@ -4590,10 +4770,11 @@ void ex_helpgrep(exarg_T *eap)    if (au_name != NULL) {      apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, -        curbuf->b_fname, TRUE, curbuf); -    if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) -      /* autocommands made "qi" invalid */ +                   curbuf->b_fname, true, curbuf); +    if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) { +      // autocommands made "qi" invalid        return; +    }    }    /* Jump to first match. */ diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index e1faaccb84..1f8cf8a0e7 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -65,6 +65,7 @@ NEW_TESTS ?= \  	    test_increment_dbcs.res \  	    test_lambda.res \  	    test_langmap.res \ +	    test_makeencoding.res \  	    test_marks.res \  	    test_match.res \  	    test_matchadd_conceal.res \ diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py new file mode 100644 index 0000000000..041edadc0a --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Test program for :make, :grep and :cgetfile. + +from __future__ import print_function, unicode_literals +import locale +import io +import sys + +def set_output_encoding(enc=None): +    """Set the encoding of stdout and stderr + +    arguments: +      enc -- Encoding name. +             If omitted, locale.getpreferredencoding() is used. +    """ +    if enc is None: +        enc = locale.getpreferredencoding() + +    def get_text_writer(fo, **kwargs): +        kw = dict(kwargs) +        kw.setdefault('errors', 'backslashreplace') # use \uXXXX style +        kw.setdefault('closefd', False) + +        if sys.version_info[0] < 3: +            # Work around for Python 2.x +            # New line conversion isn't needed here. Done in somewhere else. +            writer = io.open(fo.fileno(), mode='w', newline='', **kw) +            write = writer.write    # save the original write() function +            enc = locale.getpreferredencoding() +            def convwrite(s): +                if isinstance(s, bytes): +                    write(s.decode(enc))    # convert to unistr +                else: +                    write(s) +                try: +                    writer.flush()  # needed on Windows +                except IOError: +                    pass +            writer.write = convwrite +        else: +            writer = io.open(fo.fileno(), mode='w', **kw) +        return writer + +    sys.stdout = get_text_writer(sys.stdout, encoding=enc) +    sys.stderr = get_text_writer(sys.stderr, encoding=enc) + + +def main(): +    enc = 'utf-8' +    if len(sys.argv) > 1: +        enc = sys.argv[1] +    set_output_encoding(enc) + +    message_tbl = { +            'utf-8': 'ÀÈÌÒÙ こんにちは 你好', +            'latin1': 'ÀÈÌÒÙ', +            'cp932': 'こんにちは', +            'cp936': '你好', +            } + +    print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc)) + + +if __name__ == "__main__": +    main() diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim new file mode 100644 index 0000000000..a3d5538a47 --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.vim @@ -0,0 +1,106 @@ +" Tests for 'makeencoding'. +if !has('multi_byte') +  finish +endif + +source shared.vim + +let s:python = PythonProg() +if s:python == '' +  " Can't run this test. +  finish +endif + +let s:script = 'test_makeencoding.py' + +let s:message_tbl = { +      \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', +      \ 'latin1': 'ÀÈÌÒÙ', +      \ 'cp932': 'こんにちは', +      \ 'cp936': '你好', +      \} + + +" Tests for :cgetfile and :lgetfile. +func Test_getfile() +  set errorfile=Xerror.txt +  set errorformat=%f(%l)\ :\ %m + +  " :cgetfile +  for enc in keys(s:message_tbl) +    let &makeencoding = enc +    exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile +    cgetfile +    copen +    call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", +          \ getline('.')) +    cclose +  endfor + +  " :lgetfile +  for enc in keys(s:message_tbl) +    let &makeencoding = enc +    exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile +    lgetfile +    lopen +    call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", +          \ getline('.')) +    lclose +  endfor + +  call delete(&errorfile) +endfunc + + +" Tests for :grep and :lgrep. +func Test_grep() +  let &grepprg = s:python +  set grepformat=%f(%l)\ :\ %m + +  " :grep +  for enc in keys(s:message_tbl) +    let &makeencoding = enc +    exec "silent grep! " . s:script . " " . enc +    copen +    call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", +          \ getline('.')) +    cclose +  endfor + +  " :lgrep +  for enc in keys(s:message_tbl) +    let &makeencoding = enc +    exec "silent lgrep! " . s:script . " " . enc +    lopen +    call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", +          \ getline('.')) +    lclose +  endfor +endfunc + + +" Tests for :make and :lmake. +func Test_make() +  let &makeprg = s:python +  set errorformat=%f(%l)\ :\ %m + +  " :make +  for enc in keys(s:message_tbl) +    let &makeencoding = enc +    exec "silent make! " . s:script . " " . enc +    copen +    call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", +          \ getline('.')) +    cclose +  endfor + +  " :lmake +  for enc in keys(s:message_tbl) +    let &makeencoding = enc +    exec "silent lmake! " . s:script . " " . enc +    lopen +    call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", +          \ getline('.')) +    lclose +  endfor +endfunc diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index aff5fc2eed..30023dddc9 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -24,19 +24,21 @@ func s:setup_commands(cchar)      command! -nargs=* Xgetbuffer <mods>cgetbuffer <args>      command! -nargs=* Xaddbuffer <mods>caddbuffer <args>      command! -nargs=* Xrewind <mods>crewind <args> -    command! -nargs=* -bang Xnext <mods>cnext<bang> <args> -    command! -nargs=* -bang Xprev <mods>cprev<bang> <args> +    command! -count -nargs=* -bang Xnext <mods><count>cnext<bang> <args> +    command! -count -nargs=* -bang Xprev <mods><count>cprev<bang> <args>      command! -nargs=* -bang Xfirst <mods>cfirst<bang> <args>      command! -nargs=* -bang Xlast <mods>clast<bang> <args>      command! -nargs=* -bang Xnfile <mods>cnfile<bang> <args>      command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>      command! -nargs=* Xexpr <mods>cexpr <args> -    command! -nargs=* Xvimgrep <mods>vimgrep <args> +    command! -range -nargs=* Xvimgrep <mods><count>vimgrep <args> +    command! -nargs=* Xvimgrepadd <mods>vimgrepadd <args>      command! -nargs=* Xgrep <mods> grep <args>      command! -nargs=* Xgrepadd <mods> grepadd <args>      command! -nargs=* Xhelpgrep helpgrep <args>      let g:Xgetlist = function('getqflist')      let g:Xsetlist = function('setqflist') +    call setqflist([], 'f')    else      command! -nargs=* -bang Xlist <mods>llist<bang> <args>      command! -nargs=* Xgetexpr <mods>lgetexpr <args> @@ -54,19 +56,21 @@ func s:setup_commands(cchar)      command! -nargs=* Xgetbuffer <mods>lgetbuffer <args>      command! -nargs=* Xaddbuffer <mods>laddbuffer <args>      command! -nargs=* Xrewind <mods>lrewind <args> -    command! -nargs=* -bang Xnext <mods>lnext<bang> <args> -    command! -nargs=* -bang Xprev <mods>lprev<bang> <args> +    command! -count -nargs=* -bang Xnext <mods><count>lnext<bang> <args> +    command! -count -nargs=* -bang Xprev <mods><count>lprev<bang> <args>      command! -nargs=* -bang Xfirst <mods>lfirst<bang> <args>      command! -nargs=* -bang Xlast <mods>llast<bang> <args>      command! -nargs=* -bang Xnfile <mods>lnfile<bang> <args>      command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>      command! -nargs=* Xexpr <mods>lexpr <args> -    command! -nargs=* Xvimgrep <mods>lvimgrep <args> +    command! -range -nargs=* Xvimgrep <mods><count>lvimgrep <args> +    command! -nargs=* Xvimgrepadd <mods>lvimgrepadd <args>      command! -nargs=* Xgrep <mods> lgrep <args>      command! -nargs=* Xgrepadd <mods> lgrepadd <args>      command! -nargs=* Xhelpgrep lhelpgrep <args>      let g:Xgetlist = function('getloclist', [0])      let g:Xsetlist = function('setloclist', [0]) +    call setloclist(0, [], 'f')    endif  endfunc @@ -74,6 +78,9 @@ endfunc  func XlistTests(cchar)    call s:setup_commands(a:cchar) +  if a:cchar == 'l' +      call assert_fails('llist', 'E776:') +  endif    " With an empty list, command should return error    Xgetexpr []    silent! Xlist @@ -85,49 +92,52 @@ func XlistTests(cchar)  		  \ 'non-error 3', 'Xtestfile3:3:1:Line3']    " List only valid entries -  redir => result -  Xlist -  redir END -  let l = split(result, "\n") +  let l = split(execute('Xlist', ''), "\n")    call assert_equal([' 2 Xtestfile1:1 col 3: Line1',  		   \ ' 4 Xtestfile2:2 col 2: Line2',  		   \ ' 6 Xtestfile3:3 col 1: Line3'], l)    " List all the entries -  redir => result -  Xlist! -  redir END -  let l = split(result, "\n") +  let l = split(execute('Xlist!', ''), "\n")    call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1',  		   \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2',  		   \ ' 5: non-error 3', ' 6 Xtestfile3:3 col 1: Line3'], l)    " List a range of errors -  redir => result -  Xlist 3,6 -  redir END -  let l = split(result, "\n") +  let l = split(execute('Xlist 3,6', ''), "\n")    call assert_equal([' 4 Xtestfile2:2 col 2: Line2',  		   \ ' 6 Xtestfile3:3 col 1: Line3'], l) -  redir => result -  Xlist! 3,4 -  redir END -  let l = split(result, "\n") +  let l = split(execute('Xlist! 3,4', ''), "\n")    call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) -  redir => result -  Xlist -6,-4 -  redir END -  let l = split(result, "\n") +  let l = split(execute('Xlist -6,-4', ''), "\n")    call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l) -  redir => result -  Xlist! -5,-3 -  redir END -  let l = split(result, "\n") +  let l = split(execute('Xlist! -5,-3', ''), "\n")    call assert_equal([' 2 Xtestfile1:1 col 3: Line1',  		   \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + +  " Test for '+' +  let l = split(execute('Xlist! +2', ''), "\n") +  call assert_equal([' 2 Xtestfile1:1 col 3: Line1', +		   \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + +  " Different types of errors +  call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11}, +	      \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22}, +	      \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33}, +	      \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44}, +	      \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55}]) +  let l = split(execute('Xlist', ""), "\n") +  call assert_equal([' 1:10 col 5 warning  11: Warning', +	      \ ' 2:20 col 10 error  22: Error', +	      \ ' 3:30 col 15 info  33: Info', +	      \ ' 4:40 col 20 x  44: Other', +	      \ ' 5:50 col 25  55: one'], l) + +  " Error cases +  call assert_fails('Xlist abc', 'E488:')  endfunc  func Test_clist() @@ -141,6 +151,9 @@ endfunc  func XageTests(cchar)    call s:setup_commands(a:cchar) +  let list = [{'bufnr': 1, 'lnum': 1}] +  call g:Xsetlist(list) +    " Jumping to a non existent list should return error    silent! Xolder 99    call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack') @@ -174,11 +187,7 @@ func XageTests(cchar)  endfunc  func Test_cage() -  let list = [{'bufnr': 1, 'lnum': 1}] -  call setqflist(list)    call XageTests('c') - -  call setloclist(0, list)    call XageTests('l')  endfunc @@ -187,6 +196,11 @@ endfunc  func XwindowTests(cchar)    call s:setup_commands(a:cchar) +  " Opening the location list window without any errors should fail +  if a:cchar == 'l' +      call assert_fails('lopen', 'E776:') +  endif +    " Create a list with no valid entries    Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3'] @@ -227,6 +241,19 @@ func XwindowTests(cchar)    " Calling cwindow should close the quickfix window with no valid errors    Xwindow    call assert_true(winnr('$') == 1) + +  if a:cchar == 'c' +      " Opening the quickfix window in multiple tab pages should reuse the +      " quickfix buffer +      Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', +		  \ 'Xtestfile3:3:1:Line3'] +      Xopen +      let qfbufnum = bufnr('%') +      tabnew +      Xopen +      call assert_equal(qfbufnum, bufnr('%')) +      new | only | tabonly +  endif  endfunc  func Test_cwindow() @@ -316,6 +343,23 @@ func XbufferTests(cchar)  	\ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750')    enew! +  " Check for invalid buffer +  call assert_fails('Xbuffer 199', 'E474:') + +  " Check for unloaded buffer +  edit Xtestfile1 +  let bnr = bufnr('%') +  enew! +  call assert_fails('Xbuffer ' . bnr, 'E681:') + +  " Check for invalid range +  " Using Xbuffer will not run the range check in the cbuffer/lbuffer +  " commands. So directly call the commands. +  if (a:cchar == 'c') +      call assert_fails('900,999cbuffer', 'E16:') +  else +      call assert_fails('900,999lbuffer', 'E16:') +  endif  endfunc  func Test_cbuffer() @@ -338,13 +382,22 @@ endfunc  func Xtest_browse(cchar)    call s:setup_commands(a:cchar) +  " Jumping to first or next location list entry without any error should +  " result in failure +  if a:cchar == 'l' +      call assert_fails('lfirst', 'E776:') +      call assert_fails('lnext', 'E776:') +  endif +    call s:create_test_file('Xqftestfile1')    call s:create_test_file('Xqftestfile2')    Xgetexpr ['Xqftestfile1:5:Line5',  		\ 'Xqftestfile1:6:Line6',  		\ 'Xqftestfile2:10:Line10', -		\ 'Xqftestfile2:11:Line11'] +		\ 'Xqftestfile2:11:Line11', +		\ 'RegularLine1', +		\ 'RegularLine2']    Xfirst    call assert_fails('Xprev', 'E553') @@ -356,6 +409,7 @@ func Xtest_browse(cchar)    call assert_equal('Xqftestfile1', bufname('%'))    call assert_equal(6, line('.'))    Xlast +  Xprev    call assert_equal('Xqftestfile2', bufname('%'))    call assert_equal(11, line('.'))    call assert_fails('Xnext', 'E553') @@ -364,6 +418,16 @@ func Xtest_browse(cchar)    call assert_equal('Xqftestfile1', bufname('%'))    call assert_equal(5, line('.')) +  10Xnext +  call assert_equal('Xqftestfile2', bufname('%')) +  call assert_equal(11, line('.')) +  10Xprev +  call assert_equal('Xqftestfile1', bufname('%')) +  call assert_equal(5, line('.')) + +  Xexpr "" +  call assert_fails('Xnext', 'E42:') +    call delete('Xqftestfile1')    call delete('Xqftestfile2')  endfunc @@ -383,8 +447,32 @@ func s:test_xhelpgrep(cchar)      let title_text = ':lhelpgrep quickfix'    endif    call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) + +  " Jumping to a help topic should open the help window +  only +  Xnext +  call assert_true(&buftype == 'help') +  call assert_true(winnr('$') == 2) +  " Jumping to the next match should reuse the help window +  Xnext +  call assert_true(&buftype == 'help') +  call assert_true(winnr() == 1) +  call assert_true(winnr('$') == 2) +  " Jumping to the next match from the quickfix window should reuse the help +  " window +  Xopen +  Xnext +  call assert_true(&buftype == 'help') +  call assert_true(winnr() == 1) +  call assert_true(winnr('$') == 2) +    " This wipes out the buffer, make sure that doesn't cause trouble.    Xclose + +  new | only + +  " Search for non existing help string +  call assert_fails('Xhelpgrep a1b2c3', 'E480:')  endfunc  func Test_helpgrep() @@ -521,10 +609,7 @@ func Test_locationlist()      lrewind      enew      lopen -    lnext -    lnext -    lnext -    lnext +    4lnext      vert split      wincmd L      lopen @@ -578,7 +663,7 @@ func Test_locationlist()      wincmd n | only      augroup! testgroup -  endfunc +endfunc  func Test_locationlist_curwin_was_closed()      augroup testgroup @@ -597,7 +682,7 @@ func Test_locationlist_curwin_was_closed()      call assert_fails('lrewind', 'E924:')      augroup! testgroup -  endfunc +endfunc  func Test_locationlist_cross_tab_jump()    call writefile(['loclistfoo'], 'loclistfoo') @@ -734,7 +819,7 @@ func Test_efm1()      call delete('Xerrorfile1')      call delete('Xerrorfile2')      call delete('Xtestfile') -  endfunc +endfunc  " Test for quickfix directory stack support  func s:dir_stack_tests(cchar) @@ -893,30 +978,44 @@ func Test_efm2()    call assert_equal(l[0].pattern, '^\VLine search text\$')    call assert_equal(l[0].lnum, 0) +  let l = split(execute('clist', ''), "\n") +  call assert_equal([' 1 Xtestfile:^\VLine search text\$:  '], l) +    " Test for %P, %Q and %t format specifiers    let lines=["[Xtestfile1]",  	      \ "(1,17)  error: ';' missing",  	      \ "(21,2)  warning: variable 'z' not defined",  	      \ "(67,3)  error: end of file found before string ended", +	      \ "--",  	      \ "",  	      \ "[Xtestfile2]", +	      \ "--",  	      \ "",  	      \ "[Xtestfile3]",  	      \ "NEW compiler v1.1",  	      \ "(2,2)   warning: variable 'x' not defined", -	      \ "(67,3)  warning: 's' already defined" +	      \ "(67,3)  warning: 's' already defined", +	      \ "--"  	      \] -  set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q +  set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r +  " To exercise the push/pop file functionality in quickfix, the test files +  " need to be created. +  call writefile(['Line1'], 'Xtestfile1') +  call writefile(['Line2'], 'Xtestfile2') +  call writefile(['Line3'], 'Xtestfile3')    cexpr ""    for l in lines        caddexpr l    endfor    let l = getqflist() -  call assert_equal(9, len(l)) +  call assert_equal(12, len(l))    call assert_equal(21, l[2].lnum)    call assert_equal(2, l[2].col)    call assert_equal('w', l[2].type)    call assert_equal('e', l[3].type) +  call delete('Xtestfile1') +  call delete('Xtestfile2') +  call delete('Xtestfile3')    " Tests for %E, %C and %Z format specifiers    let lines = ["Error 275", @@ -968,6 +1067,25 @@ func Test_efm2()    call assert_equal(1, l[4].valid)    call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) +  " The following sequence of commands used to crash Vim +  set efm=%W%m +  cgetexpr ['msg1'] +  let l = getqflist() +  call assert_equal(1, len(l), string(l)) +  call assert_equal('msg1', l[0].text) +  set efm=%C%m +  lexpr 'msg2' +  let l = getloclist(0) +  call assert_equal(1, len(l), string(l)) +  call assert_equal('msg2', l[0].text) +  lopen +  call setqflist([], 'r') +  caddbuf +  let l = getqflist() +  call assert_equal(1, len(l), string(l)) +  call assert_equal('|| msg2', l[0].text) + +  new | only    let &efm = save_efm  endfunc @@ -1064,6 +1182,32 @@ func SetXlistTests(cchar, bnum)    call g:Xsetlist([])    let l = g:Xgetlist()    call assert_equal(0, len(l)) + +  " Tests for setting the 'valid' flag +  call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}]) +  Xwindow +  call assert_equal(1, winnr('$')) +  let l = g:Xgetlist() +  call g:Xsetlist(l) +  call assert_equal(0, g:Xgetlist()[0].valid) +  call g:Xsetlist([{'text':'Text1', 'valid':1}]) +  Xwindow +  call assert_equal(2, winnr('$')) +  Xclose +  let save_efm = &efm +  set efm=%m +  Xgetexpr 'TestMessage' +  let l = g:Xgetlist() +  call g:Xsetlist(l) +  call assert_equal(1, g:Xgetlist()[0].valid) +  let &efm = save_efm + +  " Error cases: +  " Refer to a non-existing buffer and pass a non-dictionary type +  call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," . +	      \ " {'bufnr':999, 'lnum':5}])", 'E92:') +  call g:Xsetlist([[1, 2,3]]) +  call assert_equal(0, len(g:Xgetlist()))  endfunc  func Test_setqflist() @@ -1082,7 +1226,8 @@ func Xlist_empty_middle(cchar)    call s:setup_commands(a:cchar)    " create three quickfix lists -  Xvimgrep Test_ test_quickfix.vim +  let @/ = 'Test_' +  Xvimgrep // test_quickfix.vim    let testlen = len(g:Xgetlist())    call assert_true(testlen > 0)    Xvimgrep empty test_quickfix.vim @@ -1290,18 +1435,18 @@ func Test_switchbuf()    let winid = win_getid()    cfirst | cnext    call assert_equal(winid, win_getid()) -  cnext | cnext +  2cnext    call assert_equal(winid, win_getid()) -  cnext | cnext +  2cnext    call assert_equal(winid, win_getid())    enew    set switchbuf=useopen    cfirst | cnext    call assert_equal(file1_winid, win_getid()) -  cnext | cnext +  2cnext    call assert_equal(file2_winid, win_getid()) -  cnext | cnext +  2cnext    call assert_equal(file2_winid, win_getid())    enew | only @@ -1311,9 +1456,9 @@ func Test_switchbuf()    tabfirst    cfirst | cnext    call assert_equal(2, tabpagenr()) -  cnext | cnext +  2cnext    call assert_equal(3, tabpagenr()) -  cnext | cnext +  2cnext    call assert_equal(3, tabpagenr())    tabfirst | tabonly | enew @@ -1351,11 +1496,25 @@ func Test_switchbuf()    call assert_equal(2, winnr('$'))    call assert_equal(1, bufwinnr('Xqftestfile3')) +  " If only quickfix window is open in the current tabpage, jumping to an +  " entry with 'switchubf' set to 'usetab' should search in other tabpages.    enew | only +  set switchbuf=usetab +  tabedit Xqftestfile1 +  tabedit Xqftestfile2 +  tabedit Xqftestfile3 +  tabfirst +  copen | only +  clast +  call assert_equal(4, tabpagenr()) +  tabfirst | tabonly | enew | only    call delete('Xqftestfile1')    call delete('Xqftestfile2')    call delete('Xqftestfile3') +  set switchbuf&vim + +  enew | only  endfunc  func Xadjust_qflnum(cchar) @@ -1468,6 +1627,11 @@ endfunc  func XbottomTests(cchar)    call s:setup_commands(a:cchar) +  " Calling lbottom without any errors should fail +  if a:cchar == 'l' +      call assert_fails('lbottom', 'E776:') +  endif +    call g:Xsetlist([{'filename': 'foo', 'lnum': 42}])     Xopen    let wid = win_getid() @@ -1489,10 +1653,9 @@ endfunc  func HistoryTest(cchar)    call s:setup_commands(a:cchar) -  call assert_fails(a:cchar . 'older 99', 'E380:')    " clear all lists after the first one, then replace the first one.    call g:Xsetlist([]) -  Xolder +  call assert_fails('Xolder 99', 'E380:')    let entry = {'filename': 'foo', 'lnum': 42}    call g:Xsetlist([entry], 'r')    call g:Xsetlist([entry, entry]) @@ -1535,6 +1698,7 @@ func Xproperty_tests(cchar)      call assert_fails('call g:Xsetlist([], "a", [])', 'E715:')      " Set and get the title +    call g:Xsetlist([])      Xopen      wincmd p      call g:Xsetlist([{'filename':'foo', 'lnum':27}]) @@ -1561,6 +1725,22 @@ func Xproperty_tests(cchar)      call g:Xsetlist([], ' ', {'title' : 'N3'})      call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) +    " Changing the title of an earlier quickfix list +    call g:Xsetlist([], ' ', {'title' : 'NewTitle', 'nr' : 2}) +    call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title) + +    " Changing the title of an invalid quickfix list +    call assert_equal(-1, g:Xsetlist([], ' ', +		\ {'title' : 'SomeTitle', 'nr' : 99})) +    call assert_equal(-1, g:Xsetlist([], ' ', +		\ {'title' : 'SomeTitle', 'nr' : 'abc'})) + +    if a:cchar == 'c' +	copen +	call assert_equal({'winid':win_getid()}, getqflist({'winid':1})) +	cclose +    endif +      " Invalid arguments      call assert_fails('call g:Xgetlist([])', 'E715')      call assert_fails('call g:Xsetlist([], "a", [])', 'E715') @@ -1568,16 +1748,81 @@ func Xproperty_tests(cchar)      call assert_equal(-1, s)      call assert_equal({}, g:Xgetlist({'abc':1})) +    call assert_equal({}, g:Xgetlist({'nr':99, 'title':1})) +    call assert_equal({}, g:Xgetlist({'nr':[], 'title':1}))      if a:cchar == 'l' -        call assert_equal({}, getloclist(99, {'title': 1})) +	call assert_equal({}, getloclist(99, {'title': 1}))      endif -  endfunc + +    " Context related tests +    call g:Xsetlist([], 'a', {'context':[1,2,3]}) +    call test_garbagecollect_now() +    let d = g:Xgetlist({'context':1}) +    call assert_equal([1,2,3], d.context) +    call g:Xsetlist([], 'a', {'context':{'color':'green'}}) +    let d = g:Xgetlist({'context':1}) +    call assert_equal({'color':'green'}, d.context) +    call g:Xsetlist([], 'a', {'context':"Context info"}) +    let d = g:Xgetlist({'context':1}) +    call assert_equal("Context info", d.context) +    call g:Xsetlist([], 'a', {'context':246}) +    let d = g:Xgetlist({'context':1}) +    call assert_equal(246, d.context) +    if a:cchar == 'l' +	" Test for copying context across two different location lists +	new | only +	let w1_id = win_getid() +	let l = [1] +	call setloclist(0, [], 'a', {'context':l}) +	new +	let w2_id = win_getid() +	call add(l, 2) +	call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context) +	call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) +	unlet! l +	call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) +	only +	call setloclist(0, [], 'f') +	call assert_equal({}, getloclist(0, {'context':1})) +    endif + +    " Test for changing the context of previous quickfix lists +    call g:Xsetlist([], 'f') +    Xexpr "One" +    Xexpr "Two" +    Xexpr "Three" +    call g:Xsetlist([], ' ', {'context' : [1], 'nr' : 1}) +    call g:Xsetlist([], ' ', {'context' : [2], 'nr' : 2}) +    " Also, check for setting the context using quickfix list number zero. +    call g:Xsetlist([], ' ', {'context' : [3], 'nr' : 0}) +    call test_garbagecollect_now() +    let l = g:Xgetlist({'nr' : 1, 'context' : 1}) +    call assert_equal([1], l.context) +    let l = g:Xgetlist({'nr' : 2, 'context' : 1}) +    call assert_equal([2], l.context) +    let l = g:Xgetlist({'nr' : 3, 'context' : 1}) +    call assert_equal([3], l.context) + +    " Test for changing the context through reference and for garbage +    " collection of quickfix context +    let l = ["red"] +    call g:Xsetlist([], ' ', {'context' : l}) +    call add(l, "blue") +    let x = g:Xgetlist({'context' : 1}) +    call add(x.context, "green") +    call assert_equal(["red", "blue", "green"], l) +    call assert_equal(["red", "blue", "green"], x.context) +    unlet l +    call test_garbagecollect_now() +    let m = g:Xgetlist({'context' : 1}) +    call assert_equal(["red", "blue", "green"], m.context) +endfunc  func Test_qf_property()      call Xproperty_tests('c')      call Xproperty_tests('l') -  endfunc +endfunc  " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands  func QfAutoCmdHandler(loc, cmd) @@ -1673,3 +1918,187 @@ func Test_dirstack_cleanup()    caddbuffer    let &efm = save_efm  endfunc + +" Tests for jumping to entries from the location list window and quickfix +" window +func Test_cwindow_jump() +  set efm=%f%%%l%%%m +  lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] +  lopen | only +  lfirst +  call assert_true(winnr('$') == 2) +  call assert_true(winnr() == 1) +  " Location list for the new window should be set +  call assert_true(getloclist(0)[2].text == 'Line 30') + +  " Open a scratch buffer +  " Open a new window and create a location list +  " Open the location list window and close the other window +  " Jump to an entry. +  " Should create a new window and jump to the entry. The scrtach buffer +  " should not be used. +  enew | only +  set buftype=nofile +  below new +  lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] +  lopen +  2wincmd c +  lnext +  call assert_true(winnr('$') == 3) +  call assert_true(winnr() == 2) + +  " Open two windows with two different location lists +  " Open the location list window and close the previous window +  " Jump to an entry in the location list window +  " Should open the file in the first window and not set the location list. +  enew | only +  lgetexpr ["F1%5%Line 5"] +  below new +  lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] +  lopen +  2wincmd c +  lnext +  call assert_true(winnr() == 1) +  call assert_true(getloclist(0)[0].text == 'Line 5') + +  enew | only +  cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] +  copen +  cnext +  call assert_true(winnr('$') == 2) +  call assert_true(winnr() == 1) + +  enew | only +  set efm&vim +endfunc + +func XvimgrepTests(cchar) +  call s:setup_commands(a:cchar) + +  call writefile(['Editor:VIM vim', +	      \ 'Editor:Emacs EmAcS', +	      \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1') +  call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2') + +  " Error cases +  call assert_fails('Xvimgrep /abc *', 'E682:') + +  let @/='' +  call assert_fails('Xvimgrep // *', 'E35:') + +  call assert_fails('Xvimgrep abc', 'E683:') +  call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:') +  call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:') + +  Xexpr "" +  Xvimgrepadd Notepad Xtestfile1 +  Xvimgrepadd MacOS Xtestfile2 +  let l = g:Xgetlist() +  call assert_equal(2, len(l)) +  call assert_equal('Editor:Notepad NOTEPAD', l[0].text) + +  Xvimgrep #\cvim#g Xtestfile? +  let l = g:Xgetlist() +  call assert_equal(2, len(l)) +  call assert_equal(8, l[0].col) +  call assert_equal(12, l[1].col) + +  1Xvimgrep ?Editor? Xtestfile* +  let l = g:Xgetlist() +  call assert_equal(1, len(l)) +  call assert_equal('Editor:VIM vim', l[0].text) + +  edit +3 Xtestfile2 +  Xvimgrep +\cemacs+j Xtestfile1 +  let l = g:Xgetlist() +  call assert_equal('Xtestfile2', bufname('')) +  call assert_equal('Editor:Emacs EmAcS', l[0].text) + +  call delete('Xtestfile1') +  call delete('Xtestfile2') +endfunc + +" Tests for the :vimgrep command +func Test_vimgrep() +  call XvimgrepTests('c') +  call XvimgrepTests('l') +endfunc + +func XfreeTests(cchar) +  call s:setup_commands(a:cchar) + +  enew | only + +  " Deleting the quickfix stack should work even When the current list is +  " somewhere in the middle of the stack +  Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] +  Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] +  Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] +  Xolder +  call g:Xsetlist([], 'f') +  call assert_equal(0, len(g:Xgetlist())) + +  " After deleting the stack, adding a new list should create a stack with a +  " single list. +  Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] +  call assert_equal(1, g:Xgetlist({'all':1}).nr) + +  " Deleting the stack from a quickfix window should update/clear the +  " quickfix/location list window. +  Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] +  Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] +  Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] +  Xolder +  Xwindow +  call g:Xsetlist([], 'f') +  call assert_equal(2, winnr('$')) +  call assert_equal(1, line('$')) +  Xclose + +  " Deleting the stack from a non-quickfix window should update/clear the +  " quickfix/location list window. +  Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] +  Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] +  Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] +  Xolder +  Xwindow +  wincmd p +  call g:Xsetlist([], 'f') +  call assert_equal(0, len(g:Xgetlist())) +  wincmd p +  call assert_equal(2, winnr('$')) +  call assert_equal(1, line('$')) + +  " After deleting the location list stack, if the location list window is +  " opened, then a new location list should be created. So opening the +  " location list window again should not create a new window. +  if a:cchar == 'l' +      lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] +      wincmd p +      lopen +      call assert_equal(2, winnr('$')) +  endif +  Xclose +endfunc + +" Tests for the quickifx free functionality +func Test_qf_free() +  call XfreeTests('c') +  call XfreeTests('l') +endfunc + +" Test for buffer overflow when parsing lines and adding new entries to +" the quickfix list. +func Test_bufoverflow() +  set efm=%f:%l:%m +  cgetexpr ['File1:100:' . repeat('x', 1025)] + +  set efm=%+GCompiler:\ %.%#,%f:%l:%m +  cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World'] + +  set efm=%DEntering\ directory\ %f,%f:%l:%m +  cgetexpr ['Entering directory ' . repeat('a', 1006), +	      \ 'File1:10:Hello World'] +  set efm&vim +endfunc + diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 021bb6eac4..dbe1222dc0 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -70,7 +70,6 @@ typedef struct {    UIBridgeData *bridge;    Loop *loop;    bool stop; -  uv_timer_t after_startup_timer;    unibi_var_t params[9];    char buf[OUTBUF_SIZE];    size_t bufpos; @@ -291,18 +290,6 @@ static void terminfo_stop(UI *ui)    unibi_destroy(data->ut);  } -static void after_startup_timer_cb(uv_timer_t *handle) -  FUNC_ATTR_NONNULL_ALL -{ -  UI *ui = handle->data; -  TUIData *data = ui->data; -  uv_timer_stop(&data->after_startup_timer); - -  // Emit this after Nvim startup, not during.  This works around a tmux -  // 2.3 bug(?) which caused slow drawing during startup.  #7649 -  unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); -} -  static void tui_terminal_start(UI *ui)  {    TUIData *data = ui->data; @@ -312,8 +299,16 @@ static void tui_terminal_start(UI *ui)    update_size(ui);    signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH);    term_input_start(&data->input); +} + +static void tui_terminal_after_startup(UI *ui) +  FUNC_ATTR_NONNULL_ALL +{ +  TUIData *data = ui->data; -  uv_timer_start(&data->after_startup_timer, after_startup_timer_cb, 500, 0); +  // Emit this after Nvim startup, not during.  This works around a tmux +  // 2.3 bug(?) which caused slow drawing during startup.  #7649 +  unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting);  }  static void tui_terminal_stop(UI *ui) @@ -347,8 +342,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui)  #ifdef UNIX    signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT);  #endif -  uv_timer_init(&data->loop->uv, &data->after_startup_timer); -  data->after_startup_timer.data = ui;  #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18    data->input.tk_ti_hook_fn = tui_tk_ti_getstr; @@ -363,11 +356,21 @@ static void tui_main(UIBridgeData *bridge, UI *ui)    loop_schedule_deferred(&main_loop,                           event_create(show_termcap_event, 1, data->ut)); +  // "Active" loop: first ~100 ms of startup. +  for (size_t ms = 0; ms < 100 && !data->stop;) { +    ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); +  } +  if (!data->stop) { +    tui_terminal_after_startup(ui); +    // Tickle `main_loop` with a dummy event, else the initial "focus-gained" +    // terminal response may not get processed until user hits a key. +    loop_schedule_deferred(&main_loop, event_create(tui_dummy_event, 0)); +  } +  // "Passive" (I/O-driven) loop: TUI thread "main loop".    while (!data->stop) {      loop_poll_events(&tui_loop, -1);  // tui_loop.events is never processed    } -  uv_close((uv_handle_t *)&data->after_startup_timer, NULL);    ui_bridge_stopped(bridge);    term_input_destroy(&data->input);    signal_watcher_stop(&data->cont_handle); @@ -379,6 +382,10 @@ static void tui_main(UIBridgeData *bridge, UI *ui)    xfree(ui);  } +static void tui_dummy_event(void **argv) +{ +} +  static void tui_scheduler(Event event, void *d)  {    UI *ui = d; @@ -1100,6 +1107,7 @@ static void suspend_event(void **argv)      loop_poll_events(data->loop, -1);    }    tui_terminal_start(ui); +  tui_terminal_after_startup(ui);    if (enable_mouse) {      tui_mouse_on(ui);    } diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index f939567693..bff8d065f8 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -614,7 +614,7 @@ local function new_pipename()  end  local function missing_provider(provider) -  if provider == 'ruby' then +  if provider == 'ruby' or provider == 'node' then      local prog = funcs['provider#' .. provider .. '#Detect']()      return prog == '' and (provider .. ' not detected') or false    elseif provider == 'python' or provider == 'python3' then diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua new file mode 100644 index 0000000000..0a12b1a154 --- /dev/null +++ b/test/functional/provider/nodejs_spec.lua @@ -0,0 +1,61 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq, clear = helpers.eq, helpers.clear +local missing_provider = helpers.missing_provider +local command = helpers.command +local write_file = helpers.write_file +local eval = helpers.eval +local retry = helpers.retry + +do +  clear() +  if missing_provider('node') then +    pending("Missing nodejs host, or nodejs version is too old.", function()end) +    return +  end +end + +before_each(function() +  clear() +  command([[let $NODE_PATH = get(split(system('npm root -g'), "\n"), 0, '')]]) +end) + +describe('nodejs host', function() +  teardown(function () +    os.remove('Xtest-nodejs-hello.js') +    os.remove('Xtest-nodejs-hello-plugin.js') +  end) + +  it('works', function() +    local fname = 'Xtest-nodejs-hello.js' +    write_file(fname, [[ +      const socket = process.env.NVIM_LISTEN_ADDRESS; +      const neovim = require('neovim'); +      const nvim = neovim.attach({socket: socket}); +      nvim.command('let g:job_out = "hello"'); +      nvim.command('call jobstop(g:job_id)'); +    ]]) +    command('let g:job_id = jobstart(["node", "'..fname..'"])') +    retry(nil, 1000, function() eq('hello', eval('g:job_out')) end) +  end) +  it('plugin works', function() +    local fname = 'Xtest-nodejs-hello-plugin.js' +    write_file(fname, [[ +      const socket = process.env.NVIM_LISTEN_ADDRESS; +      const neovim = require('neovim'); +      const nvim = neovim.attach({socket: socket}); + +      class TestPlugin { +          hello() { +              this.nvim.command('let g:job_out = "hello-plugin"') +          } +      } + +      const PluginClass = neovim.Plugin(TestPlugin); +      const plugin = new PluginClass(nvim); +      plugin.hello(); +      nvim.command('call jobstop(g:job_id)'); +    ]]) +    command('let g:job_id = jobstart(["node", "'..fname..'"])') +    retry(nil, 1000, function() eq('hello-plugin', eval('g:job_out')) end) +  end) +end) diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index c70f90da1c..a2c6c6a10e 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -15,9 +15,7 @@ local missing_provider = helpers.missing_provider  do    clear()    if missing_provider('ruby') then -    pending( -      "Cannot find the neovim RubyGem. Try :checkhealth", -      function() end) +    pending("Missing neovim RubyGem.", function() end)      return    end  end diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index ab9ff1f60d..66f921ffcc 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -103,8 +103,8 @@ set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333)  set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.2.tar.gz)  set(LUAROCKS_SHA256 eef88c2429c715a7beb921e4b1ba571dddb7c74a250fbb0d3cc0d4be7a5865d9) -set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.0.tar.gz) -set(UNIBILIUM_SHA256 623af1099515e673abfd3cae5f2fa808a09ca55dda1c65a7b5c9424eb304ead8) +set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.1.tar.gz) +set(UNIBILIUM_SHA256 6045b4f6adca7b1123284007675ca71f718f70942d3a93d8b9fa5bd442006ec1)  if(WIN32)  set(LIBTERMKEY_URL https://github.com/equalsraf/libtermkey/archive/tb-windows.zip) | 
