diff options
111 files changed, 3400 insertions, 2038 deletions
diff --git a/.gitignore b/.gitignore index ddbcc9c60d..85b371b926 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Tools +.ropeproject/ + # Build/deps dir /build/ /cmake-build-debug/ @@ -17,13 +20,13 @@ tags /src/nvim/po/vim.pot /src/nvim/po/*.ck -# generated by tests with $NVIM_LOG_FILE set. +# Generated by tests with $NVIM_LOG_FILE set. /.nvimlog -# Files generated by scripts/vim-patch.sh +# Generated by scripts/vim-patch.sh /.vim-src/ -# Files generated by the tests +# Generated by old (Vim) tests. /src/nvim/testdir/del /src/nvim/testdir/test*.out /src/nvim/testdir/test*.res @@ -36,10 +39,10 @@ tags /src/nvim/testdir/valgrind.* /src/nvim/testdir/.gdbinit -# Folder generated by the unit tests +# Generated by unit tests. /test/includes/post/ -# generated by luacheck during `make testlint' +# Generated by luacheck during `make testlint' /test/.luacheckcache # local make targets diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b021ad6e7..47e873a845 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,8 +67,8 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY # If not in a git repo (e.g., a tarball) these tokens define the complete # version string, else they are combined with the result of `git describe`. set(NVIM_VERSION_MAJOR 0) -set(NVIM_VERSION_MINOR 2) -set(NVIM_VERSION_PATCH 3) +set(NVIM_VERSION_MINOR 3) +set(NVIM_VERSION_PATCH 0) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level diff --git a/appveyor.yml b/appveyor.yml index 957efc0e47..75ca74d0d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,14 +3,13 @@ environment: APPVEYOR_CACHE_ENTRY_ZIP_ARGS: "-t7z -m0=lzma -mx=9" image: Visual Studio 2017 configuration: +- MSVC_64 +- MSVC_32 - MINGW_64 - MINGW_32 -- MSVC_64 -# - MSVC_32 - MINGW_64-gcov matrix: allow_failures: - - configuration: MSVC_64 - configuration: MINGW_64-gcov install: [] before_build: diff --git a/ci/build.ps1 b/ci/build.ps1 index 7075775bcd..8eb237ccd1 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -11,7 +11,11 @@ $depsCmakeVars = @{ $nvimCmakeVars = @{ CMAKE_BUILD_TYPE = $cmakeBuildType; BUSTED_OUTPUT_TYPE = 'nvim'; - GPERF_PRG = 'C:\msys64\usr\bin\gperf.exe'; +} + +# For pull requests, skip some build configurations to save time. +if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT -and $env:CONFIGURATION -match '^(MSVC_64|MINGW_32)$') { + exit 0 } function exitIfFailed() { @@ -39,12 +43,10 @@ if ($compiler -eq 'MINGW') { # Add MinGW to the PATH $env:PATH = "C:\msys64\mingw$bits\bin;$env:PATH" - # Remove the Git sh.exe from the PATH - $env:PATH = $env:PATH.Replace('C:\Program Files\Git\usr\bin', '') # Build third-party dependencies C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm -Su" ; exitIfFailed - C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm --needed -S mingw-w64-$arch-cmake mingw-w64-$arch-perl mingw-w64-$arch-diffutils mingw-w64-$arch-unibilium gperf" ; exitIfFailed + C:\msys64\usr\bin\bash -lc "pacman --verbose --noconfirm --needed -S mingw-w64-$arch-cmake mingw-w64-$arch-perl mingw-w64-$arch-diffutils mingw-w64-$arch-unibilium" ; exitIfFailed } elseif ($compiler -eq 'MSVC') { $cmakeGeneratorArgs = '/verbosity:normal' @@ -56,6 +58,9 @@ elseif ($compiler -eq 'MSVC') { } } +# Remove Git Unix utilities from the PATH +$env:PATH = $env:PATH.Replace('C:\Program Files\Git\usr\bin', '') + # Setup python (use AppVeyor system python) C:\Python27\python.exe -m pip install neovim ; exitIfFailed C:\Python35\python.exe -m pip install neovim ; exitIfFailed @@ -91,14 +96,25 @@ cmake --build . --config $cmakeBuildType -- $cmakeGeneratorArgs ; exitIfFailed bin\nvim --version ; exitIfFailed # Functional tests -cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs ; exitIfFailed +# The $LastExitCode from MSBuild can't be trusted +$failed = $false +# Temporarily turn off tracing to reduce log file output +Set-PSDebug -Off +cmake --build . --config $cmakeBuildType --target functionaltest -- $cmakeGeneratorArgs 2>&1 | + foreach { $failed = $failed -or + $_ -match 'Running functional tests failed with error'; $_ } +Set-PSDebug -Trace 1 +if ($failed) { + exit $LastExitCode +} + if ($uploadToCodecov) { C:\msys64\usr\bin\bash -lc "cd /c/projects/neovim; bash <(curl -s https://codecov.io/bash) -c -F functionaltest || echo 'codecov upload failed.'" } # Old tests -$env:PATH += ';C:\msys64\usr\bin' +$env:PATH = "C:\msys64\usr\bin;$env:PATH" & "C:\msys64\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 if ($uploadToCodecov) { diff --git a/man/nvim.1 b/man/nvim.1 index d2a3ea5c43..12f342247e 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -25,7 +25,7 @@ To enter commands in type a colon .Pq Sq \&: which is also used in this manual to denote commands. -For more information, consult the on-line help system with the +For more information, consult the online help system with the .Ic :help command. .Bl -tag -width Fl @@ -329,6 +329,8 @@ Implies .Fl -headless . .It Fl -headless Do not start a user interface. +.It Fl -listen Ar address +Start RPC server on this pipe or TCP socket. .It Fl h , -help Print usage information and exit. .It Fl v , -version @@ -337,12 +339,12 @@ Print version information and exit. .Sh ENVIRONMENT .Bl -tag -width Fl .It Ev VIM -Used to locate various user files, such as the user's init.vim. +Used to locate various user files, such as init.vim. .It Ev VIMRUNTIME -Used to locate run time files, such as on-line documentation and +Used to locate runtime files, such as online documentation and syntax highlighting definitions. .It Ev XDG_CONFIG_HOME -Path to use for the user-local configuration directory, see +Path to the user-local configuration directory, see .Sx FILES . Defaults to .Pa ~/.config @@ -356,7 +358,7 @@ Defaults to .Pa ~/.local/share if not set. .It Ev VIMINIT -A string of Ex commands to be executed at startup. +Ex commands to be executed at startup. For example, the command to quit is .Ic :q , so to have @@ -375,41 +377,32 @@ command. .Sh FILES .Bl -tag -width "~/.config/nvim/init.vim" .It Pa ~/.config/nvim/init.vim -The user-local +User-local .Nm configuration file. -See -.Ev XDG_CONFIG_HOME -above. .It Pa ~/.config/nvim -The user-local +User-local .Nm configuration directory. -See -.Ev XDG_CONFIG_HOME -above. +See also +.Ev XDG_CONFIG_HOME . .It Pa $VIM/sysinit.vim -The system-global +System-global .Nm configuration file. .It Pa /usr/local/share/nvim -The system-global +System-global .Nm runtime directory. .El .Sh AUTHORS -.Nm -was started by -.An Thiago de Arruda , -with a lot of help from others. -.Pp +Nvim was started by +.An Thiago de Arruda . Most of Vim was written by .An -nosplit -.An Bram Moolenaar , -with a lot of help from others. +.An Bram Moolenaar . See .Ic :help credits . -.Pp Vim is based on Stevie, worked on by .An Tim Thompson , .An Tony Andrews , diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim new file mode 100644 index 0000000000..2603c6822f --- /dev/null +++ b/runtime/autoload/dist/ft.vim @@ -0,0 +1,741 @@ +" Vim functions for file type detection +" +" Maintainer: Bram Moolenaar <Bram@vim.org> +" Last Change: 2017 Nov 11 + +" These functions are moved here from runtime/filetype.vim to make startup +" faster. + +" Line continuation is used here, remove 'C' from 'cpoptions' +let s:cpo_save = &cpo +set cpo&vim + +func dist#ft#Check_inp() + if getline(1) =~ '^\*' + setf abaqus + else + let n = 1 + if line("$") > 500 + let nmax = 500 + else + let nmax = line("$") + endif + while n <= nmax + if getline(n) =~? "^header surface data" + setf trasys + break + endif + let n = n + 1 + endwhile + endif +endfunc + +" This function checks for the kind of assembly that is wanted by the user, or +" can be detected from the first five lines of the file. +func dist#ft#FTasm() + " make sure b:asmsyntax exists + if !exists("b:asmsyntax") + let b:asmsyntax = "" + endif + + if b:asmsyntax == "" + call dist#ft#FTasmsyntax() + endif + + " if b:asmsyntax still isn't set, default to asmsyntax or GNU + if b:asmsyntax == "" + if exists("g:asmsyntax") + let b:asmsyntax = g:asmsyntax + else + let b:asmsyntax = "asm" + endif + endif + + exe "setf " . fnameescape(b:asmsyntax) +endfunc + +func dist#ft#FTasmsyntax() + " see if file contains any asmsyntax=foo overrides. If so, change + " b:asmsyntax appropriately + let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). + \" ".getline(5)." " + let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') + if match != '' + let b:asmsyntax = match + elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) + let b:asmsyntax = "vmasm" + endif +endfunc + +" Check if one of the first five lines contains "VB_Name". In that case it is +" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. +func dist#ft#FTVB(alt) + if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' + setf vb + else + exe "setf " . a:alt + endif +endfunc + +func dist#ft#FTbtm() + if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm + setf dosbatch + else + setf btm + endif +endfunc + +func dist#ft#BindzoneCheck(default) + if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' + setf bindzone + elseif a:default != '' + exe 'setf ' . a:default + endif +endfunc + +func dist#ft#FTlpc() + if exists("g:lpc_syntax_for_c") + let lnum = 1 + while lnum <= 12 + if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' + setf lpc + return + endif + let lnum = lnum + 1 + endwhile + endif + setf c +endfunc + +func dist#ft#FTheader() + if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 + if exists("g:c_syntax_for_h") + setf objc + else + setf objcpp + endif + elseif exists("g:c_syntax_for_h") + setf c + elseif exists("g:ch_syntax_for_h") + setf ch + else + setf cpp + endif +endfunc + +" This function checks if one of the first ten lines start with a '@'. In +" that case it is probably a change file. +" If the first line starts with # or ! it's probably a ch file. +" If a line has "main", "include", "//" ir "/*" it's probably ch. +" Otherwise CHILL is assumed. +func dist#ft#FTchange() + let lnum = 1 + while lnum <= 10 + if getline(lnum)[0] == '@' + setf change + return + endif + if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') + setf ch + return + endif + if getline(lnum) =~ "MODULE" + setf chill + return + endif + if getline(lnum) =~ 'main\s*(\|#\s*include\|//' + setf ch + return + endif + let lnum = lnum + 1 + endwhile + setf chill +endfunc + +func dist#ft#FTent() + " This function checks for valid cl syntax in the first five lines. + " Look for either an opening comment, '#', or a block start, '{". + " If not found, assume SGML. + let lnum = 1 + while lnum < 6 + let line = getline(lnum) + if line =~ '^\s*[#{]' + setf cl + return + elseif line !~ '^\s*$' + " Not a blank line, not a comment, and not a block start, + " so doesn't look like valid cl code. + break + endif + let lnum = lnum + 1 + endw + setf dtd +endfunc + +func dist#ft#EuphoriaCheck() + if exists('g:filetype_euphoria') + exe 'setf ' . g:filetype_euphoria + else + setf euphoria3 + endif +endfunc + +func dist#ft#DtraceCheck() + let lines = getline(1, min([line("$"), 100])) + if match(lines, '^module\>\|^import\>') > -1 + " D files often start with a module and/or import statement. + setf d + elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 + setf dtrace + else + setf d + endif +endfunc + +func dist#ft#FTe() + if exists('g:filetype_euphoria') + exe 'setf ' . g:filetype_euphoria + else + let n = 1 + while n < 100 && n < line("$") + if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" + setf specman + return + endif + let n = n + 1 + endwhile + setf eiffel + endif +endfunc + +" Distinguish between HTML, XHTML and Django +func dist#ft#FThtml() + let n = 1 + while n < 10 && n < line("$") + if getline(n) =~ '\<DTD\s\+XHTML\s' + setf xhtml + return + endif + if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' + setf htmldjango + return + endif + let n = n + 1 + endwhile + setf html +endfunc + +" Distinguish between standard IDL and MS-IDL +func dist#ft#FTidl() + let n = 1 + while n < 50 && n < line("$") + if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' + setf msidl + return + endif + let n = n + 1 + endwhile + setf idl +endfunc + +" Distinguish between "default" and Cproto prototype file. */ +func dist#ft#ProtoCheck(default) + " Cproto files have a comment in the first line and a function prototype in + " the second line, it always ends in ";". Indent files may also have + " comments, thus we can't match comments to see the difference. + " IDL files can have a single ';' in the second line, require at least one + " chacter before the ';'. + if getline(2) =~ '.;$' + setf cpp + else + exe 'setf ' . a:default + endif +endfunc + +func dist#ft#FTm() + let n = 1 + let saw_comment = 0 " Whether we've seen a multiline comment leader. + while n < 100 + let line = getline(n) + if line =~ '^\s*/\*' + " /* ... */ is a comment in Objective C and Murphi, so we can't conclude + " it's either of them yet, but track this as a hint in case we don't see + " anything more definitive. + let saw_comment = 1 + endif + if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|//\)' + setf objc + return + endif + if line =~ '^\s*%' + setf matlab + return + endif + if line =~ '^\s*(\*' + setf mma + return + endif + if line =~ '^\c\s*\(\(type\|var\)\>\|--\)' + setf murphi + return + endif + let n = n + 1 + endwhile + + if saw_comment + " We didn't see anything definitive, but this looks like either Objective C + " or Murphi based on the comment leader. Assume the former as it is more + " common. + setf objc + elseif exists("g:filetype_m") + " Use user specified default filetype for .m + exe "setf " . g:filetype_m + else + " Default is matlab + setf matlab + endif +endfunc + +func dist#ft#FTmms() + let n = 1 + while n < 10 + let line = getline(n) + if line =~ '^\s*\(%\|//\)' || line =~ '^\*' + setf mmix + return + endif + if line =~ '^\s*#' + setf make + return + endif + let n = n + 1 + endwhile + setf mmix +endfunc + +" This function checks if one of the first five lines start with a dot. In +" that case it is probably an nroff file: 'filetype' is set and 1 is returned. +func dist#ft#FTnroff() + if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' + setf nroff + return 1 + endif + return 0 +endfunc + +func dist#ft#FTmm() + let n = 1 + while n < 10 + let line = getline(n) + if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' + setf objcpp + return + endif + let n = n + 1 + endwhile + setf nroff +endfunc + +func dist#ft#FTpl() + if exists("g:filetype_pl") + exe "setf " . g:filetype_pl + else + " recognize Prolog by specific text in the first non-empty line + " require a blank after the '%' because Perl uses "%list" and "%translate" + let l = getline(nextnonblank(1)) + if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' + setf prolog + else + setf perl + endif + endif +endfunc + +func dist#ft#FTinc() + if exists("g:filetype_inc") + exe "setf " . g:filetype_inc + else + let lines = getline(1).getline(2).getline(3) + if lines =~? "perlscript" + setf aspperl + elseif lines =~ "<%" + setf aspvbs + elseif lines =~ "<?" + setf php + else + call dist#ft#FTasmsyntax() + if exists("b:asmsyntax") + exe "setf " . fnameescape(b:asmsyntax) + else + setf pov + endif + endif + endif +endfunc + +func dist#ft#FTprogress_cweb() + if exists("g:filetype_w") + exe "setf " . g:filetype_w + return + endif + if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' + setf progress + else + setf cweb + endif +endfunc + +func dist#ft#FTprogress_asm() + if exists("g:filetype_i") + exe "setf " . g:filetype_i + return + endif + " This function checks for an assembly comment the first ten lines. + " If not found, assume Progress. + let lnum = 1 + while lnum <= 10 && lnum < line('$') + let line = getline(lnum) + if line =~ '^\s*;' || line =~ '^\*' + call dist#ft#FTasm() + return + elseif line !~ '^\s*$' || line =~ '^/\*' + " Not an empty line: Doesn't look like valid assembly code. + " Or it looks like a Progress /* comment + break + endif + let lnum = lnum + 1 + endw + setf progress +endfunc + +func dist#ft#FTprogress_pascal() + if exists("g:filetype_p") + exe "setf " . g:filetype_p + return + endif + " This function checks for valid Pascal syntax in the first ten lines. + " Look for either an opening comment or a program start. + " If not found, assume Progress. + let lnum = 1 + while lnum <= 10 && lnum < line('$') + let line = getline(lnum) + if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' + \ || line =~ '^\s*{' || line =~ '^\s*(\*' + setf pascal + return + elseif line !~ '^\s*$' || line =~ '^/\*' + " Not an empty line: Doesn't look like valid Pascal code. + " Or it looks like a Progress /* comment + break + endif + let lnum = lnum + 1 + endw + setf progress +endfunc + +func dist#ft#FTr() + let max = line("$") > 50 ? 50 : line("$") + + for n in range(1, max) + " Rebol is easy to recognize, check for that first + if getline(n) =~? '\<REBOL\>' + setf rebol + return + endif + endfor + + for n in range(1, max) + " R has # comments + if getline(n) =~ '^\s*#' + setf r + return + endif + " Rexx has /* comments */ + if getline(n) =~ '^\s*/\*' + setf rexx + return + endif + endfor + + " Nothing recognized, use user default or assume Rexx + if exists("g:filetype_r") + exe "setf " . g:filetype_r + else + " Rexx used to be the default, but R appears to be much more popular. + setf r + endif +endfunc + +func dist#ft#McSetf() + " Rely on the file to start with a comment. + " MS message text files use ';', Sendmail files use '#' or 'dnl' + for lnum in range(1, min([line("$"), 20])) + let line = getline(lnum) + if line =~ '^\s*\(#\|dnl\)' + setf m4 " Sendmail .mc file + return + elseif line =~ '^\s*;' + setf msmessages " MS Message text file + return + endif + endfor + setf m4 " Default: Sendmail .mc file +endfunc + +" Called from filetype.vim and scripts.vim. +func dist#ft#SetFileTypeSH(name) + if expand("<amatch>") =~ g:ft_ignore_pat + return + endif + if a:name =~ '\<csh\>' + " Some .sh scripts contain #!/bin/csh. + call dist#ft#SetFileTypeShell("csh") + return + elseif a:name =~ '\<tcsh\>' + " Some .sh scripts contain #!/bin/tcsh. + call dist#ft#SetFileTypeShell("tcsh") + return + elseif a:name =~ '\<zsh\>' + " Some .sh scripts contain #!/bin/zsh. + call dist#ft#SetFileTypeShell("zsh") + return + elseif a:name =~ '\<ksh\>' + let b:is_kornshell = 1 + if exists("b:is_bash") + unlet b:is_bash + endif + if exists("b:is_sh") + unlet b:is_sh + endif + elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' + let b:is_bash = 1 + if exists("b:is_kornshell") + unlet b:is_kornshell + endif + if exists("b:is_sh") + unlet b:is_sh + endif + elseif a:name =~ '\<sh\>' + let b:is_sh = 1 + if exists("b:is_kornshell") + unlet b:is_kornshell + endif + if exists("b:is_bash") + unlet b:is_bash + endif + endif + call dist#ft#SetFileTypeShell("sh") +endfunc + +" For shell-like file types, check for an "exec" command hidden in a comment, +" as used for Tcl. +" Also called from scripts.vim, thus can't be local to this script. +func dist#ft#SetFileTypeShell(name) + if expand("<amatch>") =~ g:ft_ignore_pat + return + endif + let l = 2 + while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' + " Skip empty and comment lines. + let l = l + 1 + endwhile + if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' + " Found an "exec" line after a comment with continuation + let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') + if n =~ '\<tclsh\|\<wish' + setf tcl + return + endif + endif + exe "setf " . a:name +endfunc + +func dist#ft#CSH() + if exists("g:filetype_csh") + call dist#ft#SetFileTypeShell(g:filetype_csh) + elseif &shell =~ "tcsh" + call dist#ft#SetFileTypeShell("tcsh") + else + call dist#ft#SetFileTypeShell("csh") + endif +endfunc + +let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' +func dist#ft#FTRules() + let path = expand('<amatch>:p') + if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' + setf udevrules + return + endif + if path =~ '^/etc/ufw/' + setf conf " Better than hog + return + endif + if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' + setf javascript + return + endif + try + let config_lines = readfile('/etc/udev/udev.conf') + catch /^Vim\%((\a\+)\)\=:E484/ + setf hog + return + endtry + let dir = expand('<amatch>:p:h') + for line in config_lines + if line =~ s:ft_rules_udev_rules_pattern + let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") + if dir == udev_rules + setf udevrules + endif + break + endif + endfor + setf hog +endfunc + +func dist#ft#SQL() + if exists("g:filetype_sql") + exe "setf " . g:filetype_sql + else + setf sql + endif +endfunc + +" If the file has an extension of 't' and is in a directory 't' or 'xt' then +" it is almost certainly a Perl test file. +" If the first line starts with '#' and contains 'perl' it's probably a Perl +" file. +" (Slow test) If a file contains a 'use' statement then it is almost certainly +" a Perl file. +func dist#ft#FTperl() + let dirname = expand("%:p:h:t") + if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') + setf perl + return 1 + endif + if getline(1)[0] == '#' && getline(1) =~ 'perl' + setf perl + return 1 + endif + if search('^use\s\s*\k', 'nc', 30) + setf perl + return 1 + endif + return 0 +endfunc + +" Choose context, plaintex, or tex (LaTeX) based on these rules: +" 1. Check the first line of the file for "%&<format>". +" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. +" 3. Default to "latex" or to g:tex_flavor, can be set in user's vimrc. +func dist#ft#FTtex() + let firstline = getline(1) + if firstline =~ '^%&\s*\a\+' + let format = tolower(matchstr(firstline, '\a\+')) + let format = substitute(format, 'pdf', '', '') + if format == 'tex' + let format = 'latex' + elseif format == 'plaintex' + let format = 'plain' + endif + elseif expand('%') =~ 'tex/context/.*/.*.tex' + let format = 'context' + else + " Default value, may be changed later: + let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' + " Save position, go to the top of the file, find first non-comment line. + let save_cursor = getpos('.') + call cursor(1,1) + let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) + if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. + let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' + let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' + let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', + \ 'cnp', firstNC + 1000) + if kwline == 1 " lpat matched + let format = 'latex' + elseif kwline == 2 " cpat matched + let format = 'context' + endif " If neither matched, keep default set above. + " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) + " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) + " if cline > 0 + " let format = 'context' + " endif + " if lline > 0 && (cline == 0 || cline > lline) + " let format = 'tex' + " endif + endif " firstNC + call setpos('.', save_cursor) + endif " firstline =~ '^%&\s*\a\+' + + " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? + if format == 'plain' + setf plaintex + elseif format == 'context' + setf context + else " probably LaTeX + setf tex + endif + return +endfunc + +func dist#ft#FTxml() + let n = 1 + while n < 100 && n < line("$") + let line = getline(n) + " DocBook 4 or DocBook 5. + let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' + let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' + if is_docbook4 || is_docbook5 + let b:docbk_type = "xml" + if is_docbook5 + let b:docbk_ver = 5 + else + let b:docbk_ver = 4 + endif + setf docbk + return + endif + if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' + setf xbl + return + endif + let n += 1 + endwhile + setf xml +endfunc + +func dist#ft#FTy() + let n = 1 + while n < 100 && n < line("$") + let line = getline(n) + if line =~ '^\s*%' + setf yacc + return + endif + if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' + setf racc + return + endif + let n = n + 1 + endwhile + setf yacc +endfunc + +func dist#ft#Redif() + let lnum = 1 + while lnum <= 5 && lnum < line('$') + if getline(lnum) =~ "^\ctemplate-type:" + setf redif + return + endif + let lnum = lnum + 1 + endwhile +endfunc + + +" Restore 'cpoptions' +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 9a04bf2824..06a016eddb 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -272,10 +272,11 @@ Name triggered by ~ |GUIEnter| after starting the GUI successfully |GUIFailed| after starting the GUI failed |TermResponse| after the terminal response to |t_RV| is received - |QuitPre| when using `:quit`, before deciding whether to quit -|VimLeavePre| before exiting Vim, before writing the shada file -|VimLeave| before exiting Vim, after writing the shada file +|VimLeavePre| before exiting Nvim, before writing the shada file +|VimLeave| before exiting Nvim, after writing the shada file +|VimResume| after Nvim is resumed +|VimSuspend| before Nvim is suspended Various |DirChanged| after the |current-directory| was changed @@ -1009,6 +1010,10 @@ VimLeavePre Before exiting Vim, just before writing the VimResized After the Vim window was resized, thus 'lines' and/or 'columns' changed. Not when starting up though. + *VimResume* +VimResume After Nvim resumes from |suspend| state. + *VimSuspend* +VimSuspend Before Nvim enters |suspend| state. *WinEnter* WinEnter After entering another window. Not done for the first window, when Vim has just started. diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index ea61e847c7..03699b3dfb 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -22,6 +22,10 @@ Commands ~ *:wv* *:wviminfo* Deprecated alias to |:wshada| command. +Environment Variables ~ +*$NVIM_LISTEN_ADDRESS* Deprecated in favor of |--listen|. If both are given, + $NVIM_LISTEN_ADDRESS is ignored. + Events ~ *EncodingChanged* Never fired; 'encoding' is always "utf-8". *FileEncoding* Never fired; equivalent to |EncodingChanged|. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 767fc133d8..20f0eb303b 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1788,9 +1788,9 @@ v:scrollstart String describing the script or function that caused the hit-enter prompt. *v:servername* *servername-variable* - *$NVIM_LISTEN_ADDRESS* -v:servername Default Nvim server address. Equivalent to - |$NVIM_LISTEN_ADDRESS| on startup. |serverstop()| +v:servername Primary listen-address of the current Nvim instance, the first + item returned by |serverlist()|. Can be set by |--listen| or + |$NVIM_LISTEN_ADDRESS| at startup. |serverstart()| |serverstop()| Read-only. @@ -2286,6 +2286,7 @@ split({expr} [, {pat} [, {keepempty}]]) List make |List| from {pat} separated {expr} sqrt({expr}) Float square root of {expr} stdioopen({dict}) Number open stdio in a headless instance. +stdpath({what}) String/List returns the standard path(s) for {what} str2float({expr}) Float convert String to Float str2nr({expr} [, {base}]) Number convert String to Number strchars({expr} [, {skipcc}]) Number character length of the String {expr} @@ -4208,8 +4209,7 @@ getftype({fname}) *getftype()* getftype("/home") < Note that a type such as "link" will only be returned on systems that support it. On some systems only "dir" and - "file" are returned. On MS-Windows a symbolic link to a - directory returns "dir" instead of "link". + "file" are returned. *getline()* getline({lnum} [, {end}]) @@ -6638,15 +6638,11 @@ server2client({clientid}, {string}) *server2client()* :echo server2client(expand("<client>"), "HELLO") < serverlist() *serverlist()* - Returns a list of available server names in a list. - When there are no servers an empty string is returned. + Returns a list of server addresses, or empty if all servers + were stopped. |serverstart()| |serverstop()| Example: > :echo serverlist() -< {Nvim} *--serverlist* - The Vim command-line option `--serverlist` was removed from - Nvim, but it can be imitated: > - nvim --cmd "echo serverlist()" --cmd "q" -< + serverstart([{address}]) *serverstart()* Opens a socket or named pipe at {address} and listens for |RPC| messages. Clients can send |API| commands to the address @@ -6674,13 +6670,9 @@ serverstart([{address}]) *serverstart()* < |$NVIM_LISTEN_ADDRESS| is set to {address} if not already set. - *--servername* - The Vim command-line option `--servername` can be imitated: > - nvim --cmd "let g:server_addr = serverstart('foo')" -< serverstop({address}) *serverstop()* - Closes the pipe or socket at {address}. Does nothing if - {address} is empty or invalid. + Closes the pipe or socket at {address}. + Returns TRUE if {address} is valid, else FALSE. If |$NVIM_LISTEN_ADDRESS| is stopped it is unset. If |v:servername| is stopped it is set to the next available address returned by |serverlist()|. @@ -7271,6 +7263,23 @@ stdioopen({opts}) *stdioopen()* - 0 on invalid arguments +stdpath({what}) *stdpath()* *E6100* + Returns the standard path(s) for {what}. + + These directories are the default locations for various files + used by Neovim. + + {what} Type Description ~ + cache String Cache directory. Useful for plugins + that need temporary files to work. + config String User configuration directory. The + |init.vim| is stored here. + config_dirs List Additional configuration directories. + data String User data directory. The |shada-file| + is stored here. + data_dirs List Additional data directories. + + str2float({expr}) *str2float()* Convert String {expr} to a Float. This mostly works the same as when using a floating point number in an expression, see diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 9ae35bea52..1feff0723b 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -40,10 +40,11 @@ char action ~ *i_CTRL-[* *i_<Esc>* <Esc> or CTRL-[ End insert or Replace mode, go back to Normal mode. Finish abbreviation. - Note: If your <Esc> key is hard to hit on your keyboard, train - yourself to use CTRL-[. - If Esc doesn't work and you are using a Mac, try CTRL-Esc. - Or disable Listening under Accessibility preferences. + Note: If your <Esc> key is hard to hit, try CTRL-[ instead. + *i_META* *i_ALT* + |ALT| (|META|) acts like <Esc> if the chord is not mapped. + For example <A-x> acts like <Esc>x if <A-x> does not have an + insert-mode mapping. *i_CTRL-C* CTRL-C Quit insert mode, go back to Normal mode. Do not check for abbreviations. Does not trigger the |InsertLeave| autocommand diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index 11fad105b5..01d4e10cea 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -70,9 +70,8 @@ An rpc socket is automatically created with each instance. The socket location is stored in |v:servername|. By default this is a named pipe with an automatically generated address. See |XXX|. -To make Nvim listen on a TCP/IP socket instead, set the - |$NVIM_LISTEN_ADDRESS| environment variable before starting Nvim: > - NVIM_LISTEN_ADDRESS=127.0.0.1:6666 nvim +To make Nvim listen on a TCP/IP socket instead, specify |--listen|: > + nvim --listen 127.0.0.1:6666 <Also, more sockets and named pipes can be listened on using |serverstart()|. Note that localhost TCP sockets are generally less secure than named pipes, diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index f4bf49c7c5..0b7c61ea18 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2036,6 +2036,11 @@ A jump table for the options with a short description can be found at |Q_op|. column of the last screen line. Overrules "lastline". uhex Show unprintable characters hexadecimal as <xx> instead of using ^C and ~C. + msgsep When showing messages longer than 'cmdheight' lines, + only scroll the message lines and not the entire + screen. This also shows a separator line filled with + chars determined by 'fillchars' option, and + highlighted with the |MsgSeparator| group. When neither "lastline" nor "truncate" is included, a last line that doesn't fit is replaced with "@" lines. @@ -2390,6 +2395,7 @@ A jump table for the options with a short description can be found at |Q_op|. vert:c '│' or '|' vertical separators |:vsplit| fold:c '·' or '-' filling 'foldtext' diff:c '-' deleted lines of the 'diff' option + msgsep:c ' ' message separator 'display' Any one that is omitted will fall back to the default. For "stl" and "stlnc" the space will be used when there is highlighting, '^' or '=' @@ -6284,15 +6290,14 @@ A jump table for the options with a short description can be found at |Q_op|. *'timeoutlen'* *'tm'* 'timeoutlen' 'tm' number (default 1000) global - The time in milliseconds that is waited for a mapped sequence to - complete. + Time in milliseconds to wait for a mapped sequence to complete. *'ttimeoutlen'* *'ttm'* 'ttimeoutlen' 'ttm' number (default 50) global - The time in milliseconds that is waited for a key code - sequence to complete. Also used for CTRL-\ CTRL-N and CTRL-\ CTRL-G - when part of a command has been typed. + Time in milliseconds to wait for a key code sequence to complete. Also + used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has + been typed. *'title'* *'notitle'* 'title' boolean (default off) diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index e9188ba641..db3eb757c0 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -355,6 +355,10 @@ argument. instead. See also |silent-mode|, which does start a (limited) UI. +--listen {addr} *--listen* + Start |RPC| server on pipe or TCP address {addr}. Sets the + primary listen address |v:servername| to {addr}. |serverstart()| + ============================================================================== 2. Initialization *initialization* *startup* @@ -683,25 +687,17 @@ CTRL-Z Suspend Vim, like ":stop". Command-line mode, the CTRL-Z is inserted as a normal character. In Visual mode Vim goes back to Normal mode. - Note: if CTRL-Z undoes a change see |mswin.vim|. - :sus[pend][!] or *:sus* *:suspend* *:st* *:stop* -:st[op][!] Suspend Vim. +:st[op][!] Suspend Vim. Vim will continue if you make it the + foreground job again. If the '!' is not given and 'autowrite' is set, every buffer with changes and a file name is written out. If the '!' is given or 'autowrite' is not set, changed buffers are not written, don't forget to bring Vim back to the foreground later! -In the GUI, suspending is implemented as iconising gvim. In Windows 95/NT, -gvim is minimized. - -On many Unix systems, it is possible to suspend Vim with CTRL-Z. This is only -possible in Normal and Visual mode (see next chapter, |vim-modes|). Vim will -continue if you make it the foreground job again. On other systems, CTRL-Z -will start a new shell. This is the same as the ":sh" command. Vim will -continue if you exit from the shell. +In the GUI, suspending is implementation-defined. In X-windows the selection is disowned when Vim suspends. this means you can't paste it in another application (since Vim is going to sleep an attempt @@ -1391,4 +1387,4 @@ RPC clients for debugging. $NVIM_LOG_FILE contains the log file path: > Usually the file is ~/.local/share/nvim/log unless that path is inaccessible or if $NVIM_LOG_FILE was set before |startup|. - vim:tw=78:ts=8:ft=help:norl: + vim:noet:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index ff9773b136..fa66d9d071 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4909,6 +4909,8 @@ MatchParen The character under the cursor or just before it, if it *hl-ModeMsg* ModeMsg 'showmode' message (e.g., "-- INSERT --") + *hl-MsgSeparator* +MsgSeparator Separator for scrolled messages, `msgsep` flag of 'display' *hl-MoreMsg* MoreMsg |more-prompt| *hl-NonText* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 3924dd4ebe..3575a420b7 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -34,7 +34,7 @@ a complete and centralized reference of those differences. - 'complete' doesn't include "i" - 'cscopeverbose' is enabled - 'directory' defaults to ~/.local/share/nvim/swap// (|xdg|), auto-created -- 'display' defaults to "lastline" +- 'display' defaults to "lastline,msgsep" - 'fillchars' defaults (in effect) to "vert:│,fold:·" - 'formatoptions' defaults to "tcqj" - 'history' defaults to 10000 (the maximum) @@ -130,7 +130,9 @@ Some `CTRL-SHIFT-...` key chords are distinguished from `CTRL-...` variants Options: 'cpoptions' flags: |cpo-_| + 'display' flag `msgsep` to minimize scrolling when showing messages 'guicursor' works in the terminal + 'fillchars' flag `msgsep` (see 'display' above) 'inccommand' shows interactive results for |:substitute|-like commands 'scrollback' 'statusline' supports unlimited alignment sections @@ -164,6 +166,7 @@ Events: Highlight groups: |hl-NormalNC| highlights non-current windows + |hl-MsgSeparator| highlights separator for scrolled messages |hl-QuickFixLine| |hl-Substitute| |hl-TermCursor| diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 4e0f145c18..f8a29fa2b3 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -52,27 +52,7 @@ endfunc au BufNewFile,BufRead $VIMRUNTIME/doc/*.txt setf help " Abaqus or Trasys -au BufNewFile,BufRead *.inp call s:Check_inp() - -func! s:Check_inp() - if getline(1) =~ '^\*' - setf abaqus - else - let n = 1 - if line("$") > 500 - let nmax = 500 - else - let nmax = line("$") - endif - while n <= nmax - if getline(n) =~? "^header surface data" - setf trasys - break - endif - let n = n + 1 - endwhile - endif -endfunc +au BufNewFile,BufRead *.inp call dist#ft#Check_inp() " A-A-P recipe au BufNewFile,BufRead *.aap setf aap @@ -170,44 +150,7 @@ au BufNewFile,BufRead */boot/grub/menu.lst,*/boot/grub/grub.conf,*/etc/grub.conf " Assembly (all kinds) " *.lst is not pure assembly, it has two extra columns (address, byte codes) -au BufNewFile,BufRead *.asm,*.[sS],*.[aA],*.mac,*.lst call s:FTasm() - -" This function checks for the kind of assembly that is wanted by the user, or -" can be detected from the first five lines of the file. -func! s:FTasm() - " make sure b:asmsyntax exists - if !exists("b:asmsyntax") - let b:asmsyntax = "" - endif - - if b:asmsyntax == "" - call s:FTasmsyntax() - endif - - " if b:asmsyntax still isn't set, default to asmsyntax or GNU - if b:asmsyntax == "" - if exists("g:asmsyntax") - let b:asmsyntax = g:asmsyntax - else - let b:asmsyntax = "asm" - endif - endif - - exe "setf " . fnameescape(b:asmsyntax) -endfunc - -func! s:FTasmsyntax() - " see if file contains any asmsyntax=foo overrides. If so, change - " b:asmsyntax appropriately - let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). - \" ".getline(5)." " - let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') - if match != '' - let b:asmsyntax = match - elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) - let b:asmsyntax = "vmasm" - endif -endfunc +au BufNewFile,BufRead *.asm,*.[sS],*.[aA],*.mac,*.lst call dist#ft#FTasm() " Macro (VAX) au BufNewFile,BufRead *.mar setf vmasm @@ -237,17 +180,7 @@ au BufNewFile,BufRead *.awk setf awk au BufNewFile,BufRead *.mch,*.ref,*.imp setf b " BASIC or Visual Basic -au BufNewFile,BufRead *.bas call s:FTVB("basic") - -" Check if one of the first five lines contains "VB_Name". In that case it is -" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. -func! s:FTVB(alt) - if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' - setf vb - else - exe "setf " . a:alt - endif -endfunc +au BufNewFile,BufRead *.bas call dist#ft#FTVB("basic") " Visual Basic Script (close to Visual Basic) or Visual Basic .NET au BufNewFile,BufRead *.vb,*.vbs,*.dsm,*.ctl setf vb @@ -265,14 +198,7 @@ au BufNewFile,BufRead *.cmd \ if getline(1) =~ '^/\*' | setf rexx | else | setf dosbatch | endif " Batch file for 4DOS -au BufNewFile,BufRead *.btm call s:FTbtm() -func! s:FTbtm() - if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm - setf dosbatch - else - setf btm - endif -endfunc +au BufNewFile,BufRead *.btm call dist#ft#FTbtm() " BC calculator au BufNewFile,BufRead *.bc setf bc @@ -292,15 +218,7 @@ au BufNewFile,BufRead named*.conf,rndc*.conf,rndc*.key setf named " BIND zone au BufNewFile,BufRead named.root setf bindzone -au BufNewFile,BufRead *.db call s:BindzoneCheck('') - -func! s:BindzoneCheck(default) - if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' - setf bindzone - elseif a:default != '' - exe 'setf ' . a:default - endif -endfunc +au BufNewFile,BufRead *.db call dist#ft#BindzoneCheck('') " Blank au BufNewFile,BufRead *.bl setf blank @@ -309,27 +227,14 @@ au BufNewFile,BufRead *.bl setf blank au BufNewFile,BufRead */etc/blkid.tab,*/etc/blkid.tab.old setf xml " Bazel (http://bazel.io) -autocmd BufRead,BufNewFile *.bzl,WORKSPACE setfiletype bzl +autocmd BufRead,BufNewFile *.bzl,WORKSPACE setf bzl if has("fname_case") - autocmd BufRead,BufNewFile BUILD setfiletype bzl + " There is another check for BUILD further below. + autocmd BufRead,BufNewFile BUILD setf bzl endif " C or lpc -au BufNewFile,BufRead *.c call s:FTlpc() - -func! s:FTlpc() - if exists("g:lpc_syntax_for_c") - let lnum = 1 - while lnum <= 12 - if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' - setf lpc - return - endif - let lnum = lnum + 1 - endwhile - endif - setf c -endfunc +au BufNewFile,BufRead *.c call dist#ft#FTlpc() " Calendar au BufNewFile,BufRead calendar setf calendar @@ -383,23 +288,7 @@ endif " .h files can be C, Ch C++, ObjC or ObjC++. " Set c_syntax_for_h if you want C, ch_syntax_for_h if you want Ch. ObjC is " detected automatically. -au BufNewFile,BufRead *.h call s:FTheader() - -func! s:FTheader() - if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 - if exists("g:c_syntax_for_h") - setf objc - else - setf objcpp - endif - elseif exists("g:c_syntax_for_h") - setf c - elseif exists("g:ch_syntax_for_h") - setf ch - else - setf cpp - endif -endfunc +au BufNewFile,BufRead *.h call dist#ft#FTheader() " Ch (CHscript) au BufNewFile,BufRead *.chf setf ch @@ -433,36 +322,7 @@ au BufNewFile,BufRead NEWS au BufNewFile,BufRead *..ch setf chill " Changes for WEB and CWEB or CHILL -au BufNewFile,BufRead *.ch call s:FTchange() - -" This function checks if one of the first ten lines start with a '@'. In -" that case it is probably a change file. -" If the first line starts with # or ! it's probably a ch file. -" If a line has "main", "include", "//" ir "/*" it's probably ch. -" Otherwise CHILL is assumed. -func! s:FTchange() - let lnum = 1 - while lnum <= 10 - if getline(lnum)[0] == '@' - setf change - return - endif - if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') - setf ch - return - endif - if getline(lnum) =~ "MODULE" - setf chill - return - endif - if getline(lnum) =~ 'main\s*(\|#\s*include\|//' - setf ch - return - endif - let lnum = lnum + 1 - endwhile - setf chill -endfunc +au BufNewFile,BufRead *.ch call dist#ft#FTchange() " ChordPro au BufNewFile,BufRead *.chopro,*.crd,*.cho,*.crdpro,*.chordpro setf chordpro @@ -474,27 +334,7 @@ au BufNewFile,BufRead *.dcl,*.icl setf clean au BufNewFile,BufRead *.eni setf cl " Clever or dtd -au BufNewFile,BufRead *.ent call s:FTent() - -func! s:FTent() - " This function checks for valid cl syntax in the first five lines. - " Look for either an opening comment, '#', or a block start, '{". - " If not found, assume SGML. - let lnum = 1 - while lnum < 6 - let line = getline(lnum) - if line =~ '^\s*[#{]' - setf cl - return - elseif line !~ '^\s*$' - " Not a blank line, not a comment, and not a block start, - " so doesn't look like valid cl code. - break - endif - let lnum = lnum + 1 - endw - setf dtd -endfunc +au BufNewFile,BufRead *.ent call dist#ft#FTent() " Clipper (or FoxPro; could also be eviews) au BufNewFile,BufRead *.prg @@ -549,19 +389,11 @@ au BufNewFile,BufRead *enlightenment/*.cfg setf c au BufNewFile,BufRead *Eterm/*.cfg setf eterm " Euphoria 3 or 4 -au BufNewFile,BufRead *.eu,*.ew,*.ex,*.exu,*.exw call s:EuphoriaCheck() +au BufNewFile,BufRead *.eu,*.ew,*.ex,*.exu,*.exw call dist#ft#EuphoriaCheck() if has("fname_case") - au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call s:EuphoriaCheck() + au BufNewFile,BufRead *.EU,*.EW,*.EX,*.EXU,*.EXW call dist#ft#EuphoriaCheck() endif -func! s:EuphoriaCheck() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - setf euphoria3 - endif -endfunc - " Lynx config files au BufNewFile,BufRead lynx.cfg setf lynx @@ -606,19 +438,7 @@ au BufNewFile,BufRead */etc/dnsmasq.conf setf dnsmasq au BufNewFile,BufRead *.desc setf desc " the D language or dtrace -au BufNewFile,BufRead *.d call s:DtraceCheck() - -func! s:DtraceCheck() - let lines = getline(1, min([line("$"), 100])) - if match(lines, '^module\>\|^import\>') > -1 - " D files often start with a module and/or import statement. - setf d - elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 - setf dtrace - else - setf d - endif -endfunc +au BufNewFile,BufRead *.d call dist#ft#DtraceCheck() " Desktop files au BufNewFile,BufRead *.desktop,.directory setf desktop @@ -650,7 +470,7 @@ au BufNewFile,BufRead *.rul \ endif " DCL (Digital Command Language - vms) or DNS zone file -au BufNewFile,BufRead *.com call s:BindzoneCheck('dcl') +au BufNewFile,BufRead *.com call dist#ft#BindzoneCheck('dcl') " DOT au BufNewFile,BufRead *.dot setf dot @@ -698,27 +518,11 @@ au BufNewFile,BufRead .editorconfig setf dosini au BufNewFile,BufRead *.ecd setf ecd " Eiffel or Specman or Euphoria -au BufNewFile,BufRead *.e,*.E call s:FTe() +au BufNewFile,BufRead *.e,*.E call dist#ft#FTe() " Elinks configuration au BufNewFile,BufRead */etc/elinks.conf,*/.elinks/elinks.conf setf elinks -func! s:FTe() - if exists('g:filetype_euphoria') - exe 'setf ' . g:filetype_euphoria - else - let n = 1 - while n < 100 && n < line("$") - if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" - setf specman - return - endif - let n = n + 1 - endwhile - setf eiffel - endif -endfunc - " ERicsson LANGuage; Yaws is erlang too au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang @@ -887,24 +691,7 @@ au BufNewFile,BufRead *.hex,*.h32 setf hex au BufNewFile,BufRead *.t.html setf tilde " HTML (.shtml and .stm for server side) -au BufNewFile,BufRead *.html,*.htm,*.shtml,*.stm call s:FThtml() - -" Distinguish between HTML, XHTML and Django -func! s:FThtml() - let n = 1 - while n < 10 && n < line("$") - if getline(n) =~ '\<DTD\s\+XHTML\s' - setf xhtml - return - endif - if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' - setf htmldjango - return - endif - let n = n + 1 - endwhile - setf html -endfunc +au BufNewFile,BufRead *.html,*.htm,*.shtml,*.stm call dist#ft#FThtml() " HTML with Ruby - eRuby au BufNewFile,BufRead *.erb,*.rhtml setf eruby @@ -931,20 +718,7 @@ au BufNewFile,BufRead *.htt,*.htb setf httest au BufNewFile,BufRead *.icn setf icon " IDL (Interface Description Language) -au BufNewFile,BufRead *.idl call s:FTidl() - -" Distinguish between standard IDL and MS-IDL -func! s:FTidl() - let n = 1 - while n < 50 && n < line("$") - if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' - setf msidl - return - endif - let n = n + 1 - endwhile - setf idl -endfunc +au BufNewFile,BufRead *.idl call dist#ft#FTidl() " Microsoft IDL (Interface Description Language) Also *.idl " MOF = WMI (Windows Management Instrumentation) Managed Object Format @@ -955,25 +729,10 @@ au BufNewFile,BufRead */.icewm/menu setf icemenu " Indent profile (must come before IDL *.pro!) au BufNewFile,BufRead .indent.pro setf indent -au BufNewFile,BufRead indent.pro call s:ProtoCheck('indent') +au BufNewFile,BufRead indent.pro call dist#ft#ProtoCheck('indent') " IDL (Interactive Data Language) -au BufNewFile,BufRead *.pro call s:ProtoCheck('idlang') - -" Distinguish between "default" and Cproto prototype file. */ -func! s:ProtoCheck(default) - " Cproto files have a comment in the first line and a function prototype in - " the second line, it always ends in ";". Indent files may also have - " comments, thus we can't match comments to see the difference. - " IDL files can have a single ';' in the second line, require at least one - " chacter before the ';'. - if getline(2) =~ '.;$' - setf cpp - else - exe 'setf ' . a:default - endif -endfunc - +au BufNewFile,BufRead *.pro call dist#ft#ProtoCheck('idlang') " Indent RC au BufNewFile,BufRead indentrc setf indent @@ -1187,51 +946,7 @@ au BufNewFile,BufRead *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md setf markdown au BufNewFile,BufRead *.mason,*.mhtml,*.comp setf mason " Mathematica, Matlab, Murphi or Objective C -au BufNewFile,BufRead *.m call s:FTm() - -func! s:FTm() - let n = 1 - let saw_comment = 0 " Whether we've seen a multiline comment leader. - while n < 100 - let line = getline(n) - if line =~ '^\s*/\*' - " /* ... */ is a comment in Objective C and Murphi, so we can't conclude - " it's either of them yet, but track this as a hint in case we don't see - " anything more definitive. - let saw_comment = 1 - endif - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|//\)' - setf objc - return - endif - if line =~ '^\s*%' - setf matlab - return - endif - if line =~ '^\s*(\*' - setf mma - return - endif - if line =~ '^\c\s*\(\(type\|var\)\>\|--\)' - setf murphi - return - endif - let n = n + 1 - endwhile - - if saw_comment - " We didn't see anything definitive, but this looks like either Objective C - " or Murphi based on the comment leader. Assume the former as it is more - " common. - setf objc - elseif exists("g:filetype_m") - " Use user specified default filetype for .m - exe "setf " . g:filetype_m - else - " Default is matlab - setf matlab - endif -endfunc +au BufNewFile,BufRead *.m call dist#ft#FTm() " Mathematica notebook au BufNewFile,BufRead *.nb setf mma @@ -1261,29 +976,11 @@ au BufNewFile,BufRead *.mgl setf mgl au BufNewFile,BufRead *.mix,*.mixal setf mix " MMIX or VMS makefile -au BufNewFile,BufRead *.mms call s:FTmms() +au BufNewFile,BufRead *.mms call dist#ft#FTmms() " Symbian meta-makefile definition (MMP) au BufNewFile,BufRead *.mmp setf mmp -func! s:FTmms() - let n = 1 - while n < 10 - let line = getline(n) - if line =~ '^\s*\(%\|//\)' || line =~ '^\*' - setf mmix - return - endif - if line =~ '^\s*#' - setf make - return - endif - let n = n + 1 - endwhile - setf mmix -endfunc - - " Modsim III (or LambdaProlog) au BufNewFile,BufRead *.mod \ if getline(1) =~ '\<module\>' | @@ -1367,33 +1064,10 @@ au BufNewFile,BufRead *.me \ setf nroff | \ endif au BufNewFile,BufRead *.tr,*.nr,*.roff,*.tmac,*.mom setf nroff -au BufNewFile,BufRead *.[1-9] call s:FTnroff() - -" This function checks if one of the first five lines start with a dot. In -" that case it is probably an nroff file: 'filetype' is set and 1 is returned. -func! s:FTnroff() - if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' - setf nroff - return 1 - endif - return 0 -endfunc +au BufNewFile,BufRead *.[1-9] call dist#ft#FTnroff() " Nroff or Objective C++ -au BufNewFile,BufRead *.mm call s:FTmm() - -func! s:FTmm() - let n = 1 - while n < 10 - let line = getline(n) - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' - setf objcpp - return - endif - let n = n + 1 - endwhile - setf nroff -endfunc +au BufNewFile,BufRead *.mm call dist#ft#FTmm() " Not Quite C au BufNewFile,BufRead *.nqc setf nqc @@ -1448,28 +1122,13 @@ au BufNewFile,BufRead *.pcmk setf pcmk " Perl if has("fname_case") - au BufNewFile,BufRead *.pl,*.PL call s:FTpl() + au BufNewFile,BufRead *.pl,*.PL call dist#ft#FTpl() else - au BufNewFile,BufRead *.pl call s:FTpl() + au BufNewFile,BufRead *.pl call dist#ft#FTpl() endif au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl au BufNewFile,BufRead *.p6,*.pm6,*.pl6 setf perl6 -func! s:FTpl() - if exists("g:filetype_pl") - exe "setf " . g:filetype_pl - else - " recognize Prolog by specific text in the first non-empty line - " require a blank after the '%' because Perl uses "%list" and "%translate" - let l = getline(nextnonblank(1)) - if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' - setf prolog - else - setf perl - endif - endif -endfunc - " Perl, XPM or XPM2 au BufNewFile,BufRead *.pm \ if getline(1) =~ "XPM2" | @@ -1482,7 +1141,7 @@ au BufNewFile,BufRead *.pm " Perl POD au BufNewFile,BufRead *.pod setf pod -au BufNewFile,BufRead *.pod6 setf pod6 +au BufNewFile,BufRead *.pod6 setf pod6 " Php, php3, php4, etc. " Also Phtml (was used for PHP 2 in the past) @@ -1532,29 +1191,7 @@ au BufNewFile,BufRead *.pov setf pov au BufNewFile,BufRead .povrayrc setf povini " Povray, PHP or assembly -au BufNewFile,BufRead *.inc call s:FTinc() - -func! s:FTinc() - if exists("g:filetype_inc") - exe "setf " . g:filetype_inc - else - let lines = getline(1).getline(2).getline(3) - if lines =~? "perlscript" - setf aspperl - elseif lines =~ "<%" - setf aspvbs - elseif lines =~ "<?" - setf php - else - call s:FTasmsyntax() - if exists("b:asmsyntax") - exe "setf " . fnameescape(b:asmsyntax) - else - setf pov - endif - endif - endif -endfunc +au BufNewFile,BufRead *.inc call dist#ft#FTinc() " Printcap and Termcap au BufNewFile,BufRead *printcap @@ -1583,74 +1220,13 @@ au BufNewFile,BufRead *.action setf privoxy au BufNewFile,BufRead .procmail,.procmailrc setf procmail " Progress or CWEB -au BufNewFile,BufRead *.w call s:FTprogress_cweb() - -func! s:FTprogress_cweb() - if exists("g:filetype_w") - exe "setf " . g:filetype_w - return - endif - if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' - setf progress - else - setf cweb - endif -endfunc +au BufNewFile,BufRead *.w call dist#ft#FTprogress_cweb() " Progress or assembly -au BufNewFile,BufRead *.i call s:FTprogress_asm() - -func! s:FTprogress_asm() - if exists("g:filetype_i") - exe "setf " . g:filetype_i - return - endif - " This function checks for an assembly comment the first ten lines. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ '^\s*;' || line =~ '^\*' - call s:FTasm() - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid assembly code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc +au BufNewFile,BufRead *.i call dist#ft#FTprogress_asm() " Progress or Pascal -au BufNewFile,BufRead *.p call s:FTprogress_pascal() - -func! s:FTprogress_pascal() - if exists("g:filetype_p") - exe "setf " . g:filetype_p - return - endif - " This function checks for valid Pascal syntax in the first ten lines. - " Look for either an opening comment or a program start. - " If not found, assume Progress. - let lnum = 1 - while lnum <= 10 && lnum < line('$') - let line = getline(lnum) - if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' - \ || line =~ '^\s*{' || line =~ '^\s*(\*' - setf pascal - return - elseif line !~ '^\s*$' || line =~ '^/\*' - " Not an empty line: Doesn't look like valid Pascal code. - " Or it looks like a Progress /* comment - break - endif - let lnum = lnum + 1 - endw - setf progress -endfunc - +au BufNewFile,BufRead *.p call dist#ft#FTprogress_pascal() " Software Distributor Product Specification File (POSIX 1387.2-1995) au BufNewFile,BufRead *.psf setf psf @@ -1736,40 +1312,7 @@ else endif " Rexx, Rebol or R -au BufNewFile,BufRead *.r,*.R call s:FTr() - -func! s:FTr() - let max = line("$") > 50 ? 50 : line("$") - - for n in range(1, max) - " Rebol is easy to recognize, check for that first - if getline(n) =~? '\<REBOL\>' - setf rebol - return - endif - endfor - - for n in range(1, max) - " R has # comments - if getline(n) =~ '^\s*#' - setf r - return - endif - " Rexx has /* comments */ - if getline(n) =~ '^\s*/\*' - setf rexx - return - endif - endfor - - " Nothing recognized, use user default or assume Rexx - if exists("g:filetype_r") - exe "setf " . g:filetype_r - else - " Rexx used to be the default, but R appears to be much more popular. - setf r - endif -endfunc +au BufNewFile,BufRead *.r,*.R call dist#ft#FTr() " Remind au BufNewFile,BufRead .reminders,*.remind,*.rem setf remind @@ -1865,23 +1408,7 @@ au BufNewFile,BufRead *.siv setf sieve au BufNewFile,BufRead sendmail.cf setf sm " Sendmail .mc files are actually m4. Could also be MS Message text file. -au BufNewFile,BufRead *.mc call s:McSetf() - -func! s:McSetf() - " Rely on the file to start with a comment. - " MS message text files use ';', Sendmail files use '#' or 'dnl' - for lnum in range(1, min([line("$"), 20])) - let line = getline(lnum) - if line =~ '^\s*\(#\|dnl\)' - setf m4 " Sendmail .mc file - return - elseif line =~ '^\s*;' - setf msmessages " MS Message text file - return - endif - endfor - setf m4 " Default: Sendmail .mc file -endfunc +au BufNewFile,BufRead *.mc call dist#ft#McSetf() " Services au BufNewFile,BufRead */etc/services setf services @@ -1922,101 +1449,23 @@ au BufNewFile,BufRead sgml.catalog* call s:StarSetf('catalog') " Shell scripts (sh, ksh, bash, bash2, csh); Allow .profile_foo etc. " Gentoo ebuilds and Arch Linux PKGBUILDs are actually bash scripts -au BufNewFile,BufRead .bashrc*,bashrc,bash.bashrc,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,*.bash,*/{,.}bash[_-]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD* call SetFileTypeSH("bash") -au BufNewFile,BufRead .kshrc*,*.ksh call SetFileTypeSH("ksh") -au BufNewFile,BufRead */etc/profile,.profile*,*.sh,*.env call SetFileTypeSH(getline(1)) +au BufNewFile,BufRead .bashrc*,bashrc,bash.bashrc,.bash[_-]profile*,.bash[_-]logout*,.bash[_-]aliases*,*.bash,*/{,.}bash[_-]completion{,.d,.sh}{,/*},*.ebuild,*.eclass,PKGBUILD* call dist#ft#SetFileTypeSH("bash") +au BufNewFile,BufRead .kshrc*,*.ksh call dist#ft#SetFileTypeSH("ksh") +au BufNewFile,BufRead */etc/profile,.profile*,*.sh,*.env call dist#ft#SetFileTypeSH(getline(1)) " Shell script (Arch Linux) or PHP file (Drupal) au BufNewFile,BufRead *.install \ if getline(1) =~ '<?php' | \ setf php | \ else | - \ call SetFileTypeSH("bash") | + \ call dist#ft#SetFileTypeSH("bash") | \ endif -" Also called from scripts.vim. -func! SetFileTypeSH(name) - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - if a:name =~ '\<csh\>' - " Some .sh scripts contain #!/bin/csh. - call SetFileTypeShell("csh") - return - elseif a:name =~ '\<tcsh\>' - " Some .sh scripts contain #!/bin/tcsh. - call SetFileTypeShell("tcsh") - return - elseif a:name =~ '\<zsh\>' - " Some .sh scripts contain #!/bin/zsh. - call SetFileTypeShell("zsh") - return - elseif a:name =~ '\<ksh\>' - let b:is_kornshell = 1 - if exists("b:is_bash") - unlet b:is_bash - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' - let b:is_bash = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_sh") - unlet b:is_sh - endif - elseif a:name =~ '\<sh\>' - let b:is_sh = 1 - if exists("b:is_kornshell") - unlet b:is_kornshell - endif - if exists("b:is_bash") - unlet b:is_bash - endif - endif - call SetFileTypeShell("sh") -endfunc - -" For shell-like file types, check for an "exec" command hidden in a comment, -" as used for Tcl. -" Also called from scripts.vim, thus can't be local to this script. -func! SetFileTypeShell(name) - if expand("<amatch>") =~ g:ft_ignore_pat - return - endif - let l = 2 - while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' - " Skip empty and comment lines. - let l = l + 1 - endwhile - if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' - " Found an "exec" line after a comment with continuation - let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') - if n =~ '\<tclsh\|\<wish' - setf tcl - return - endif - endif - exe "setf " . a:name -endfunc - " tcsh scripts -au BufNewFile,BufRead .tcshrc*,*.tcsh,tcsh.tcshrc,tcsh.login call SetFileTypeShell("tcsh") +au BufNewFile,BufRead .tcshrc*,*.tcsh,tcsh.tcshrc,tcsh.login call dist#ft#SetFileTypeShell("tcsh") " csh scripts, but might also be tcsh scripts (on some systems csh is tcsh) -au BufNewFile,BufRead .login*,.cshrc*,csh.cshrc,csh.login,csh.logout,*.csh,.alias call s:CSH() - -func! s:CSH() - if exists("g:filetype_csh") - call SetFileTypeShell(g:filetype_csh) - elseif &shell =~ "tcsh" - call SetFileTypeShell("tcsh") - else - call SetFileTypeShell("csh") - endif -endfunc +au BufNewFile,BufRead .login*,.cshrc*,csh.cshrc,csh.login,csh.logout,*.csh,.alias call dist#ft#CSH() " Z-Shell script au BufNewFile,BufRead .zprofile,*/etc/zprofile,.zfbfmarks setf zsh @@ -2087,42 +1536,7 @@ au BufNewFile,BufRead *.mib,*.my setf mib " Snort Configuration au BufNewFile,BufRead *.hog,snort.conf,vision.conf setf hog -au BufNewFile,BufRead *.rules call s:FTRules() - -let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' -func! s:FTRules() - let path = expand('<amatch>:p') - if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' - setf udevrules - return - endif - if path =~ '^/etc/ufw/' - setf conf " Better than hog - return - endif - if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' - setf javascript - return - endif - try - let config_lines = readfile('/etc/udev/udev.conf') - catch /^Vim\%((\a\+)\)\=:E484/ - setf hog - return - endtry - let dir = expand('<amatch>:p:h') - for line in config_lines - if line =~ s:ft_rules_udev_rules_pattern - let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") - if dir == udev_rules - setf udevrules - endif - break - endif - endfor - setf hog -endfunc - +au BufNewFile,BufRead *.rules call dist#ft#FTRules() " Spec (Linux RPM) au BufNewFile,BufRead *.spec setf spec @@ -2146,15 +1560,7 @@ au BufNewFile,BufRead squid.conf setf squid au BufNewFile,BufRead *.tyb,*.typ,*.tyc,*.pkb,*.pks setf sql " SQL -au BufNewFile,BufRead *.sql call s:SQL() - -func! s:SQL() - if exists("g:filetype_sql") - exe "setf " . g:filetype_sql - else - setf sql - endif -endfunc +au BufNewFile,BufRead *.sql call dist#ft#SQL() " SQLJ au BufNewFile,BufRead *.sqlj setf sqlj @@ -2201,32 +1607,9 @@ au BufNewFile,BufRead */etc/sudoers,sudoers.tmp setf sudoers " SVG (Scalable Vector Graphics) au BufNewFile,BufRead *.svg setf svg -" If the file has an extension of 't' and is in a directory 't' or 'xt' then -" it is almost certainly a Perl test file. -" If the first line starts with '#' and contains 'perl' it's probably a Perl -" file. -" (Slow test) If a file contains a 'use' statement then it is almost certainly -" a Perl file. -func! s:FTperl() - let dirname = expand("%:p:h:t") - if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') - setf perl - return 1 - endif - if getline(1)[0] == '#' && getline(1) =~ 'perl' - setf perl - return 1 - endif - if search('^use\s\s*\k', 'nc', 30) - setf perl - return 1 - endif - return 0 -endfunc - " Tads (or Nroff or Perl test file) au BufNewFile,BufRead *.t - \ if !s:FTnroff() && !s:FTperl() | setf tads | endif + \ if !dist#ft#FTnroff() && !dist#ft#FTperl() | setf tads | endif " Tags au BufNewFile,BufRead tags setf tags @@ -2255,63 +1638,7 @@ au BufNewFile,BufRead *.ti setf terminfo " TeX au BufNewFile,BufRead *.latex,*.sty,*.dtx,*.ltx,*.bbl setf tex -au BufNewFile,BufRead *.tex call s:FTtex() - -" Choose context, plaintex, or tex (LaTeX) based on these rules: -" 1. Check the first line of the file for "%&<format>". -" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. -" 3. Default to "latex" or to g:tex_flavor, can be set in user's vimrc. -func! s:FTtex() - let firstline = getline(1) - if firstline =~ '^%&\s*\a\+' - let format = tolower(matchstr(firstline, '\a\+')) - let format = substitute(format, 'pdf', '', '') - if format == 'tex' - let format = 'latex' - elseif format == 'plaintex' - let format = 'plain' - endif - elseif expand('%') =~ 'tex/context/.*/.*.tex' - let format = 'context' - else - " Default value, may be changed later: - let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' - " Save position, go to the top of the file, find first non-comment line. - let save_cursor = getpos('.') - call cursor(1,1) - let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) - if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. - let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' - let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' - let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', - \ 'cnp', firstNC + 1000) - if kwline == 1 " lpat matched - let format = 'latex' - elseif kwline == 2 " cpat matched - let format = 'context' - endif " If neither matched, keep default set above. - " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) - " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) - " if cline > 0 - " let format = 'context' - " endif - " if lline > 0 && (cline == 0 || cline > lline) - " let format = 'tex' - " endif - endif " firstNC - call setpos('.', save_cursor) - endif " firstline =~ '^%&\s*\a\+' - - " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? - if format == 'plain' - setf plaintex - elseif format == 'context' - setf context - else " probably LaTeX - setf tex - endif - return -endfunc +au BufNewFile,BufRead *.tex call dist#ft#FTtex() " ConTeXt au BufNewFile,BufRead *.mkii,*.mkiv,*.mkvi setf context @@ -2412,7 +1739,7 @@ au BufRead,BufNewFile *.hw,*.module,*.pkg \ endif " Visual Basic (also uses *.bas) or FORM -au BufNewFile,BufRead *.frm call s:FTVB("form") +au BufNewFile,BufRead *.frm call dist#ft#FTVB("form") " SaxBasic is close to Visual Basic au BufNewFile,BufRead *.sba setf vb @@ -2502,36 +1829,10 @@ au BufNewFile,BufRead .Xdefaults,.Xpdefaults,.Xresources,xdm-config,*.ad setf xd " Xmath au BufNewFile,BufRead *.msc,*.msf setf xmath au BufNewFile,BufRead *.ms - \ if !s:FTnroff() | setf xmath | endif + \ if !dist#ft#FTnroff() | setf xmath | endif " XML specific variants: docbk and xbl -au BufNewFile,BufRead *.xml call s:FTxml() - -func! s:FTxml() - let n = 1 - while n < 100 && n < line("$") - let line = getline(n) - " DocBook 4 or DocBook 5. - let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' - let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' - if is_docbook4 || is_docbook5 - let b:docbk_type = "xml" - if is_docbook5 - let b:docbk_ver = 5 - else - let b:docbk_ver = 4 - endif - setf docbk - return - endif - if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' - setf xbl - return - endif - let n += 1 - endwhile - setf xml -endfunc +au BufNewFile,BufRead *.xml call dist#ft#FTxml() " XMI (holding UML models) is also XML au BufNewFile,BufRead *.xmi setf xml @@ -2574,25 +1875,7 @@ au BufNewFile,BufRead *.xsl,*.xslt setf xslt au BufNewFile,BufRead *.yy,*.yxx,*.y++ setf yacc " Yacc or racc -au BufNewFile,BufRead *.y call s:FTy() - -func! s:FTy() - let n = 1 - while n < 100 && n < line("$") - let line = getline(n) - if line =~ '^\s*%' - setf yacc - return - endif - if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' - setf racc - return - endif - let n = n + 1 - endwhile - setf yacc -endfunc - +au BufNewFile,BufRead *.y call dist#ft#FTy() " Yaml au BufNewFile,BufRead *.yaml,*.yml setf yaml @@ -2608,9 +1891,9 @@ au BufNewFile,BufRead *.zut setf zimbutempl " Zope " dtml (zope dynamic template markup language), pt (zope page template), " cpt (zope form controller page template) -au BufNewFile,BufRead *.dtml,*.pt,*.cpt call s:FThtml() +au BufNewFile,BufRead *.dtml,*.pt,*.cpt call dist#ft#FThtml() " zsql (zope sql method) -au BufNewFile,BufRead *.zsql call s:SQL() +au BufNewFile,BufRead *.zsql call dist#ft#SQL() " Z80 assembler asz80 au BufNewFile,BufRead *.z8a setf z8a @@ -2652,6 +1935,11 @@ au BufNewFile,BufRead *asterisk*/*voicemail.conf* call s:StarSetf('asteriskvm') " Bazaar version control au BufNewFile,BufRead bzr_log.* setf bzr +" Bazel build file +if !has("fname_case") + au BufNewFile,BufRead BUILD setf bzl +endif + " BIND zone au BufNewFile,BufRead */named/db.*,*/bind/db.* call s:StarSetf('bindzone') @@ -2759,17 +2047,7 @@ au BufNewFile,BufRead *termcap* " ReDIF " Only used when the .rdf file was not detected to be XML. -au BufRead,BufNewFile *.rdf call s:Redif() -func! s:Redif() - let lnum = 1 - while lnum <= 5 && lnum < line('$') - if getline(lnum) =~ "^\ctemplate-type:" - setf redif - return - endif - let lnum = lnum + 1 - endwhile -endfunc +au BufRead,BufNewFile *.rdf call dist#ft#Redif() " Remind au BufNewFile,BufRead .reminders* call s:StarSetf('remind') diff --git a/runtime/scripts.vim b/runtime/scripts.vim index 63bf9efcf9..18263e2842 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -1,11 +1,15 @@ " Vim support file to detect file types in scripts " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last change: 2017 Aug 27 +" Last change: 2017 Nov 11 " This file is called by an autocommand for every file that has just been " loaded into a buffer. It checks if the type of file can be recognized by " the file contents. The autocommand is in $VIMRUNTIME/filetype.vim. +" +" Note that the pattern matches are done with =~# to avoid the value of the +" 'ignorecase' option making a difference. Where case is to be ignored use +" =~? instead. Do not use =~ anywhere. " Only do the rest when the FileType autocommand has not been triggered yet. @@ -28,12 +32,12 @@ set cpo&vim let s:line1 = getline(1) -if s:line1 =~ "^#!" +if s:line1 =~# "^#!" " A script that starts with "#!". " Check for a line like "#!/usr/bin/env VAR=val bash". Turn it into " "#!/usr/bin/bash" to make matching easier. - if s:line1 =~ '^#!\s*\S*\<env\s' + if s:line1 =~# '^#!\s*\S*\<env\s' let s:line1 = substitute(s:line1, '\S\+=\S\+', '', 'g') let s:line1 = substitute(s:line1, '\<env\s\+', '', '') endif @@ -44,11 +48,11 @@ if s:line1 =~ "^#!" " "#!/usr/bin/env perl [path/args]" " If there is no path use the first word: "#!perl [path/args]". " Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]". - if s:line1 =~ '^#!\s*\a:[/\\]' + if s:line1 =~# '^#!\s*\a:[/\\]' let s:name = substitute(s:line1, '^#!.*[/\\]\(\i\+\).*', '\1', '') - elseif s:line1 =~ '^#!.*\<env\>' + elseif s:line1 =~# '^#!.*\<env\>' let s:name = substitute(s:line1, '^#!.*\<env\>\s\+\(\i\+\).*', '\1', '') - elseif s:line1 =~ '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)' + elseif s:line1 =~# '^#!\s*[^/\\ ]*\>\([^/\\]\|$\)' let s:name = substitute(s:line1, '^#!\s*\([^/\\ ]*\>\).*', '\1', '') else let s:name = substitute(s:line1, '^#!\s*\S*[/\\]\(\i\+\).*', '\1', '') @@ -56,116 +60,116 @@ if s:line1 =~ "^#!" " tcl scripts may have #!/bin/sh in the first line and "exec wish" in the " third line. Suggested by Steven Atkinson. - if getline(3) =~ '^exec wish' + if getline(3) =~# '^exec wish' let s:name = 'wish' endif " Bourne-like shell scripts: bash bash2 ksh ksh93 sh - if s:name =~ '^\(bash\d*\|\|ksh\d*\|sh\)\>' - call SetFileTypeSH(s:line1) " defined in filetype.vim + if s:name =~# '^\(bash\d*\|\|ksh\d*\|sh\)\>' + call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim " csh scripts - elseif s:name =~ '^csh\>' + elseif s:name =~# '^csh\>' if exists("g:filetype_csh") - call SetFileTypeShell(g:filetype_csh) + call dist#ft#SetFileTypeShell(g:filetype_csh) else - call SetFileTypeShell("csh") + call dist#ft#SetFileTypeShell("csh") endif " tcsh scripts - elseif s:name =~ '^tcsh\>' - call SetFileTypeShell("tcsh") + elseif s:name =~# '^tcsh\>' + call dist#ft#SetFileTypeShell("tcsh") " Z shell scripts - elseif s:name =~ '^zsh\>' + elseif s:name =~# '^zsh\>' set ft=zsh " TCL scripts - elseif s:name =~ '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>' + elseif s:name =~# '^\(tclsh\|wish\|expectk\|itclsh\|itkwish\)\>' set ft=tcl " Expect scripts - elseif s:name =~ '^expect\>' + elseif s:name =~# '^expect\>' set ft=expect " Gnuplot scripts - elseif s:name =~ '^gnuplot\>' + elseif s:name =~# '^gnuplot\>' set ft=gnuplot " Makefiles - elseif s:name =~ 'make\>' + elseif s:name =~# 'make\>' set ft=make " Lua - elseif s:name =~ 'lua' + elseif s:name =~# 'lua' set ft=lua " Perl 6 - elseif s:name =~ 'perl6' + elseif s:name =~# 'perl6' set ft=perl6 " Perl - elseif s:name =~ 'perl' + elseif s:name =~# 'perl' set ft=perl " PHP - elseif s:name =~ 'php' + elseif s:name =~# 'php' set ft=php " Python - elseif s:name =~ 'python' + elseif s:name =~# 'python' set ft=python " Groovy - elseif s:name =~ '^groovy\>' + elseif s:name =~# '^groovy\>' set ft=groovy " Ruby - elseif s:name =~ 'ruby' + elseif s:name =~# 'ruby' set ft=ruby " JavaScript - elseif s:name =~ 'node\(js\)\=\>' || s:name =~ 'rhino\>' + elseif s:name =~# 'node\(js\)\=\>' || s:name =~# 'rhino\>' set ft=javascript " BC calculator - elseif s:name =~ '^bc\>' + elseif s:name =~# '^bc\>' set ft=bc " sed - elseif s:name =~ 'sed\>' + elseif s:name =~# 'sed\>' set ft=sed " OCaml-scripts - elseif s:name =~ 'ocaml' + elseif s:name =~# 'ocaml' set ft=ocaml " Awk scripts - elseif s:name =~ 'awk\>' + elseif s:name =~# 'awk\>' set ft=awk " Website MetaLanguage - elseif s:name =~ 'wml' + elseif s:name =~# 'wml' set ft=wml " Scheme scripts - elseif s:name =~ 'scheme' + elseif s:name =~# 'scheme' set ft=scheme " CFEngine scripts - elseif s:name =~ 'cfengine' + elseif s:name =~# 'cfengine' set ft=cfengine " Erlang scripts - elseif s:name =~ 'escript' + elseif s:name =~# 'escript' set ft=erlang " Haskell - elseif s:name =~ 'haskell' + elseif s:name =~# 'haskell' set ft=haskell " Scala - elseif s:name =~ 'scala\>' + elseif s:name =~# 'scala\>' set ft=scala endif @@ -180,28 +184,28 @@ else let s:line5 = getline(5) " Bourne-like shell scripts: sh ksh bash bash2 - if s:line1 =~ '^:$' - call SetFileTypeSH(s:line1) " defined in filetype.vim + if s:line1 =~# '^:$' + call dist#ft#SetFileTypeSH(s:line1) " defined in filetype.vim " Z shell scripts - elseif s:line1 =~ '^#compdef\>' || s:line1 =~ '^#autoload\>' || - \ "\n".s:line1."\n".s:line2."\n".s:line3."\n".s:line4."\n".s:line5 =~ '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>' + elseif s:line1 =~# '^#compdef\>' || s:line1 =~# '^#autoload\>' || + \ "\n".s:line1."\n".s:line2."\n".s:line3."\n".s:line4."\n".s:line5 =~# '\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>' set ft=zsh " ELM Mail files - elseif s:line1 =~ '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$' + elseif s:line1 =~# '^From \([a-zA-Z][a-zA-Z_0-9\.=-]*\(@[^ ]*\)\=\|-\) .* \(19\|20\)\d\d$' set ft=mail " Mason - elseif s:line1 =~ '^<[%&].*>' + elseif s:line1 =~# '^<[%&].*>' set ft=mason " Vim scripts (must have '" vim' as the first line to trigger this) - elseif s:line1 =~ '^" *[vV]im$' + elseif s:line1 =~# '^" *[vV]im$' set ft=vim " MOO - elseif s:line1 =~ '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$' + elseif s:line1 =~# '^\*\* LambdaMOO Database, Format Version \%([1-3]\>\)\@!\d\+ \*\*$' set ft=moo " Diff file: @@ -215,40 +219,45 @@ else " - "=== ", "--- ", "+++ " (bzr diff, common case) " - "=== (removed|added|renamed|modified)" (bzr diff, alternative) " - "# HG changeset patch" in first line (Mercurial export format) - elseif s:line1 =~ '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)' - \ || (s:line1 =~ '^--- ' && s:line2 =~ '^+++ ') - \ || (s:line1 =~ '^\* looking for ' && s:line2 =~ '^\* comparing to ') - \ || (s:line1 =~ '^\*\*\* ' && s:line2 =~ '^--- ') - \ || (s:line1 =~ '^=== ' && ((s:line2 =~ '^=\{66\}' && s:line3 =~ '^--- ' && s:line4 =~ '^+++') || (s:line2 =~ '^--- ' && s:line3 =~ '^+++ '))) - \ || (s:line1 =~ '^=== \(removed\|added\|renamed\|modified\)') + elseif s:line1 =~# '^\(diff\>\|Only in \|\d\+\(,\d\+\)\=[cda]\d\+\>\|# It was generated by makepatch \|Index:\s\+\f\+\r\=$\|===== \f\+ \d\+\.\d\+ vs edited\|==== //\f\+#\d\+\|# HG changeset patch\)' + \ || (s:line1 =~# '^--- ' && s:line2 =~# '^+++ ') + \ || (s:line1 =~# '^\* looking for ' && s:line2 =~# '^\* comparing to ') + \ || (s:line1 =~# '^\*\*\* ' && s:line2 =~# '^--- ') + \ || (s:line1 =~# '^=== ' && ((s:line2 =~# '^=\{66\}' && s:line3 =~# '^--- ' && s:line4 =~# '^+++') || (s:line2 =~# '^--- ' && s:line3 =~# '^+++ '))) + \ || (s:line1 =~# '^=== \(removed\|added\|renamed\|modified\)') set ft=diff " PostScript Files (must have %!PS as the first line, like a2ps output) - elseif s:line1 =~ '^%![ \t]*PS' + elseif s:line1 =~# '^%![ \t]*PS' set ft=postscr " M4 scripts: Guess there is a line that starts with "dnl". - elseif s:line1 =~ '^\s*dnl\>' - \ || s:line2 =~ '^\s*dnl\>' - \ || s:line3 =~ '^\s*dnl\>' - \ || s:line4 =~ '^\s*dnl\>' - \ || s:line5 =~ '^\s*dnl\>' + elseif s:line1 =~# '^\s*dnl\>' + \ || s:line2 =~# '^\s*dnl\>' + \ || s:line3 =~# '^\s*dnl\>' + \ || s:line4 =~# '^\s*dnl\>' + \ || s:line5 =~# '^\s*dnl\>' set ft=m4 + " AmigaDos scripts + elseif $TERM == "amiga" + \ && (s:line1 =~# "^;" || s:line1 =~? '^\.bra') + set ft=amiga + " SiCAD scripts (must have procn or procd as the first line to trigger this) elseif s:line1 =~? '^ *proc[nd] *$' set ft=sicad " Purify log files start with "**** Purify" - elseif s:line1 =~ '^\*\*\*\* Purify' + elseif s:line1 =~# '^\*\*\*\* Purify' set ft=purifylog " XML - elseif s:line1 =~ '<?\s*xml.*?>' + elseif s:line1 =~# '<?\s*xml.*?>' set ft=xml " XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN") - elseif s:line1 =~ '\<DTD\s\+XHTML\s' + elseif s:line1 =~# '\<DTD\s\+XHTML\s' set ft=xhtml " HTML (e.g.: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN") @@ -257,43 +266,43 @@ else set ft=html " PDF - elseif s:line1 =~ '^%PDF-' + elseif s:line1 =~# '^%PDF-' set ft=pdf " XXD output - elseif s:line1 =~ '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} ' + elseif s:line1 =~# '^\x\{7}: \x\{2} \=\x\{2} \=\x\{2} \=\x\{2} ' set ft=xxd " RCS/CVS log output - elseif s:line1 =~ '^RCS file:' || s:line2 =~ '^RCS file:' + elseif s:line1 =~# '^RCS file:' || s:line2 =~# '^RCS file:' set ft=rcslog " CVS commit - elseif s:line2 =~ '^CVS:' || getline("$") =~ '^CVS: ' + elseif s:line2 =~# '^CVS:' || getline("$") =~# '^CVS: ' set ft=cvs " Prescribe - elseif s:line1 =~ '^!R!' + elseif s:line1 =~# '^!R!' set ft=prescribe " Send-pr - elseif s:line1 =~ '^SEND-PR:' + elseif s:line1 =~# '^SEND-PR:' set ft=sendpr " SNNS files - elseif s:line1 =~ '^SNNS network definition file' + elseif s:line1 =~# '^SNNS network definition file' set ft=snnsnet - elseif s:line1 =~ '^SNNS pattern definition file' + elseif s:line1 =~# '^SNNS pattern definition file' set ft=snnspat - elseif s:line1 =~ '^SNNS result file' + elseif s:line1 =~# '^SNNS result file' set ft=snnsres " Virata - elseif s:line1 =~ '^%.\{-}[Vv]irata' - \ || s:line2 =~ '^%.\{-}[Vv]irata' - \ || s:line3 =~ '^%.\{-}[Vv]irata' - \ || s:line4 =~ '^%.\{-}[Vv]irata' - \ || s:line5 =~ '^%.\{-}[Vv]irata' + elseif s:line1 =~# '^%.\{-}[Vv]irata' + \ || s:line2 =~# '^%.\{-}[Vv]irata' + \ || s:line3 =~# '^%.\{-}[Vv]irata' + \ || s:line4 =~# '^%.\{-}[Vv]irata' + \ || s:line5 =~# '^%.\{-}[Vv]irata' set ft=virata " Strace @@ -301,17 +310,17 @@ else set ft=strace " VSE JCL - elseif s:line1 =~ '^\* $$ JOB\>' || s:line1 =~ '^// *JOB\>' + elseif s:line1 =~# '^\* $$ JOB\>' || s:line1 =~# '^// *JOB\>' set ft=vsejcl " TAK and SINDA - elseif s:line4 =~ 'K & K Associates' || s:line2 =~ 'TAK 2000' + elseif s:line4 =~# 'K & K Associates' || s:line2 =~# 'TAK 2000' set ft=takout - elseif s:line3 =~ 'S Y S T E M S I M P R O V E D ' + elseif s:line3 =~# 'S Y S T E M S I M P R O V E D ' set ft=sindaout - elseif getline(6) =~ 'Run Date: ' + elseif getline(6) =~# 'Run Date: ' set ft=takcmp - elseif getline(9) =~ 'Node File 1' + elseif getline(9) =~# 'Node File 1' set ft=sindacmp " DNS zone files @@ -319,34 +328,34 @@ else set ft=bindzone " BAAN - elseif s:line1 =~ '|\*\{1,80}' && s:line2 =~ 'VRC ' - \ || s:line2 =~ '|\*\{1,80}' && s:line3 =~ 'VRC ' + elseif s:line1 =~# '|\*\{1,80}' && s:line2 =~# 'VRC ' + \ || s:line2 =~# '|\*\{1,80}' && s:line3 =~# 'VRC ' set ft=baan " Valgrind - elseif s:line1 =~ '^==\d\+== valgrind' || s:line3 =~ '^==\d\+== Using valgrind' + elseif s:line1 =~# '^==\d\+== valgrind' || s:line3 =~# '^==\d\+== Using valgrind' set ft=valgrind " Go docs - elseif s:line1 =~ '^PACKAGE DOCUMENTATION$' + elseif s:line1 =~# '^PACKAGE DOCUMENTATION$' set ft=godoc " Renderman Interface Bytestream - elseif s:line1 =~ '^##RenderMan' + elseif s:line1 =~# '^##RenderMan' set ft=rib " Scheme scripts - elseif s:line1 =~ 'exec\s\+\S*scheme' || s:line2 =~ 'exec\s\+\S*scheme' + elseif s:line1 =~# 'exec\s\+\S*scheme' || s:line2 =~# 'exec\s\+\S*scheme' set ft=scheme " Git output - elseif s:line1 =~ '^\(commit\|tree\|object\) \x\{40\}\>\|^tag \S\+$' + elseif s:line1 =~# '^\(commit\|tree\|object\) \x\{40\}\>\|^tag \S\+$' set ft=git " Gprof (gnu profiler) elseif s:line1 == 'Flat profile:' \ && s:line2 == '' - \ && s:line3 =~ '^Each sample counts as .* seconds.$' + \ && s:line3 =~# '^Each sample counts as .* seconds.$' set ft=gprof " Erlang terms @@ -357,18 +366,18 @@ else " CVS diff else let s:lnum = 1 - while getline(s:lnum) =~ "^? " && s:lnum < line("$") + while getline(s:lnum) =~# "^? " && s:lnum < line("$") let s:lnum += 1 endwhile - if getline(s:lnum) =~ '^Index:\s\+\f\+$' + if getline(s:lnum) =~# '^Index:\s\+\f\+$' set ft=diff " locale input files: Formal Definitions of Cultural Conventions " filename must be like en_US, fr_FR@euro or en_US.UTF-8 - elseif expand("%") =~ '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_' + elseif expand("%") =~# '\a\a_\a\a\($\|[.@]\)\|i18n$\|POSIX$\|translit_' let s:lnum = 1 while s:lnum < 100 && s:lnum < line("$") - if getline(s:lnum) =~ '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$' + if getline(s:lnum) =~# '^LC_\(IDENTIFICATION\|CTYPE\|COLLATE\|MONETARY\|NUMERIC\|TIME\|MESSAGES\|PAPER\|TELEPHONE\|MEASUREMENT\|NAME\|ADDRESS\)$' setf fdcc break endif diff --git a/runtime/syntax/dircolors.vim b/runtime/syntax/dircolors.vim index 1b598c39b5..3d7f63dc55 100644 --- a/runtime/syntax/dircolors.vim +++ b/runtime/syntax/dircolors.vim @@ -32,7 +32,7 @@ syntax match dircolorsEscape '\\[abefnrtv?_\\^#]' syntax match dircolorsEscape '\\[0-9]\{3}' syntax match dircolorsEscape '\\x[0-9a-f]\{3}' -if !has('gui_running') && &t_Co == '' +if !(has('gui_running') || &termguicolors) && &t_Co == '' syntax match dircolorsNumber '\<\d\+\>' highlight default link dircolorsNumber Number endif @@ -84,7 +84,7 @@ endfunction function! s:get_hi_str(color, place) abort if a:color >= 0 && a:color <= 255 - if has('gui_running') + if has('gui_running') || &termguicolors return ' gui' . a:place . '=' . s:termguicolors[a:color] elseif a:color <= 7 || &t_Co == 256 || &t_Co == 88 return ' cterm' . a:place . '=' . a:color @@ -169,7 +169,7 @@ function! s:preview_color(linenr) abort \ ' "\_s\zs' . colordef . '\ze\_s"' let hi_attrs_str = '' if !empty(hi_attrs) - if has('gui_running') + if has('gui_running') || &termguicolors let hi_attrs_str = ' gui=' . join(hi_attrs, ',') else let hi_attrs_str = ' cterm=' . join(hi_attrs, ',') @@ -199,11 +199,11 @@ endfunction let b:dc_next_index = 0 -if has('gui_running') +if has('gui_running') || &termguicolors call s:set_guicolors() endif -if has('gui_running') || &t_Co != '' +if has('gui_running') || &termguicolors || &t_Co != '' call s:reset_colors() autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.') diff --git a/scripts/run-api-tests.exp b/scripts/run-api-tests.exp deleted file mode 100755 index 27c9c963e5..0000000000 --- a/scripts/run-api-tests.exp +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env expect - -if {$argc < 2} { - puts "Need commands for running the tests and for starting nvim" - exit 1 -} - -set timeout 60 -set run_tests [split [lindex $argv 0] " "] -set run_nvim [split [lindex $argv 1] " "] - -# don't echo to stdout -log_user 0 -# set NVIM_LISTEN_ADDRESS, so nvim will listen on a known socket -set env(NVIM_LISTEN_ADDRESS) "/tmp/nvim-[exec date +%s%N].sock" -# start nvim -spawn {*}$run_nvim -# save the job descriptor -set nvim_id $spawn_id -# Reset function that can be invoked by test runners to put nvim in a cleaner -# state -send { -:echo "read"."y" -} -# wait until nvim is ready -expect "ready" -# run tests -spawn {*}$run_tests -set tests_id $spawn_id -set status 1 -# listen for test output in the background -expect_background { - * { - # show test output to the user - send_user -- $expect_out(buffer) - } - eof { - # collect the exit status code - set spawn_id $tests_id - catch wait result - set status [lindex $result 3] - set spawn_id $nvim_id - # quit nvim - send ":qa!\r" - } -} -# switch back nvim and wait until it exits -set spawn_id $nvim_id -expect eof -exit $status diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 606baff619..2d803792c8 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -359,6 +359,10 @@ endforeach() # Our dependencies come first. +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + list(APPEND NVIM_LINK_LIBRARIES pthread c++abi) +endif() + if (LibIntl_FOUND) list(APPEND NVIM_LINK_LIBRARIES ${LibIntl_LIBRARY}) endif() @@ -437,6 +441,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty-agent.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/xxd.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/D3Dcompiler_47.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 1115faaeaf..12a4279dd7 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -47,13 +47,15 @@ typedef struct { /// @param[out] tstate Location where try state should be saved. void try_enter(TryState *const tstate) { + // TODO(ZyX-I): Check whether try_enter()/try_leave() may use + // enter_cleanup()/leave_cleanup(). Or + // save_dbg_stuff()/restore_dbg_stuff(). *tstate = (TryState) { .current_exception = current_exception, .msg_list = (const struct msglist *const *)msg_list, .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, - .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -61,7 +63,6 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; - did_throw = false; need_rethrow = false; did_emsg = false; } @@ -82,7 +83,6 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(trylevel == 0); assert(!need_rethrow); assert(!got_int); - assert(!did_throw); assert(!did_emsg); assert(msg_list == &tstate->private_msg_list); assert(*msg_list == NULL); @@ -91,7 +91,6 @@ bool try_leave(const TryState *const tstate, Error *const err) current_exception = tstate->current_exception; trylevel = tstate->trylevel; got_int = tstate->got_int; - did_throw = tstate->did_throw; need_rethrow = tstate->need_rethrow; did_emsg = tstate->did_emsg; return ret; @@ -127,7 +126,7 @@ bool try_end(Error *err) did_emsg = false; if (got_int) { - if (did_throw) { + if (current_exception) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -146,7 +145,7 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (did_throw) { + } else if (current_exception) { api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 87f334ac30..0634764f13 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -93,7 +93,6 @@ typedef struct { const struct msglist *const *msg_list; int trylevel; int got_int; - int did_throw; int need_rethrow; int did_emsg; } TryState; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 4870c3fb8a..4cd2657561 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -56,7 +56,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI already attached for channel"); + api_set_error(err, kErrorTypeException, + "UI already attached to channel: %" PRId64, channel_id); return; } @@ -130,7 +131,8 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } remote_ui_disconnect(channel_id); @@ -142,7 +144,8 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } @@ -163,7 +166,8 @@ void nvim_ui_set_option(uint64_t channel_id, String name, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(error, kErrorTypeException, "UI is not attached for channel"); + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); @@ -209,7 +213,8 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } - api_set_error(error, kErrorTypeValidation, "No such ui option"); + api_set_error(error, kErrorTypeValidation, "No such UI option: %s", + name.data); #undef UI_EXT_OPTION } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 962081cc23..07ec6e8c27 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -239,15 +239,17 @@ String nvim_command_output(String command, Error *err) } if (capture_local.ga_len > 1) { - // redir always(?) prepends a newline; remove it. - char *s = capture_local.ga_data; - assert(s[0] == '\n'); - memmove(s, s + 1, (size_t)capture_local.ga_len); - s[capture_local.ga_len - 1] = '\0'; - return (String) { // Caller will free the memory. - .data = s, - .size = (size_t)(capture_local.ga_len - 1), + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. } theend: diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 7dfaf54ff0..1153314e76 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -92,6 +92,8 @@ return { 'VimLeave', -- before exiting Vim 'VimLeavePre', -- before exiting Vim and writing ShaDa file 'VimResized', -- after Vim window was resized + 'VimResume', -- after Nvim is resumed + 'VimSuspend', -- before Nvim is suspended 'WinNew', -- when entering a new window 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 2e32af2e9a..776e2bfa86 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -237,15 +237,16 @@ void channel_create_event(Channel *chan, const char *ext_source) #endif } -void channel_incref(Channel *channel) +void channel_incref(Channel *chan) { - channel->refcount++; + chan->refcount++; } -void channel_decref(Channel *channel) +void channel_decref(Channel *chan) { - if (!(--channel->refcount)) { - multiqueue_put(main_loop.fast_events, free_channel_event, 1, channel); + if (!(--chan->refcount)) { + // delay free, so that libuv is done with the handles + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } } @@ -267,18 +268,18 @@ void callback_reader_start(CallbackReader *reader) static void free_channel_event(void **argv) { - Channel *channel = argv[0]; - if (channel->is_rpc) { - rpc_free(channel); + Channel *chan = argv[0]; + if (chan->is_rpc) { + rpc_free(chan); } - callback_reader_free(&channel->on_stdout); - callback_reader_free(&channel->on_stderr); - callback_free(&channel->on_exit); + callback_reader_free(&chan->on_stdout); + callback_reader_free(&chan->on_stderr); + callback_free(&chan->on_exit); - pmap_del(uint64_t)(channels, channel->id); - multiqueue_free(channel->events); - xfree(channel); + pmap_del(uint64_t)(channels, chan->id); + multiqueue_free(chan->events); + xfree(chan); } static void channel_destroy_early(Channel *chan) @@ -286,12 +287,15 @@ static void channel_destroy_early(Channel *chan) if ((chan->id != --next_chan_id)) { abort(); } + pmap_del(uint64_t)(channels, chan->id); + chan->id = 0; if ((--chan->refcount != 0)) { abort(); } - free_channel_event((void **)&chan); + // uv will keep a reference to handles until next loop tick, so delay free + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index b772a944f4..a0f6ce152b 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -847,7 +847,7 @@ static int insert_handle_key(InsertState *s) case ' ': - if (mod_mask != 4) { + if (mod_mask != MOD_MASK_CTRL) { goto normalchar; } // FALLTHROUGH @@ -1180,6 +1180,14 @@ static int insert_handle_key(InsertState *s) normalchar: // Insert a normal character. + + if (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META) { + // Unmapped ALT/META chord behaves like ESC+c. #8213 + stuffcharReadbuff(ESC); + stuffcharReadbuff(s->c); + break; + } + if (!p_paste) { // Trigger InsertCharPre. char_u *str = do_insert_char_pre(s->c); @@ -1432,7 +1440,7 @@ static void ins_ctrl_v(void) * line and will not removed by the redraw */ edit_unputchar(); clear_showcmd(); - insert_special(c, FALSE, TRUE); + insert_special(c, true, true); revins_chars++; revins_legal++; } @@ -3615,6 +3623,9 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) memset(cptext, 0, sizeof(cptext)); } if (word == NULL || (!aempty && *word == NUL)) { + for (size_t i = 0; i < CPT_COUNT; i++) { + xfree(cptext[i]); + } return FAIL; } return ins_compl_add((char_u *)word, -1, icase, NULL, @@ -5054,13 +5065,11 @@ static void insert_special(int c, int allow_modmask, int ctrlv) char_u *p; int len; - /* - * Special function key, translate into "<Key>". Up to the last '>' is - * inserted with ins_str(), so as not to replace characters in replace - * mode. - * Only use mod_mask for special keys, to avoid things like <S-Space>, - * unless 'allow_modmask' is TRUE. - */ + // Special function key, translate into "<Key>". Up to the last '>' is + // inserted with ins_str(), so as not to replace characters in replace + // mode. + // Only use mod_mask for special keys, to avoid things like <S-Space>, + // unless 'allow_modmask' is TRUE. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0f7a1eb004..713eb816f8 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14403,8 +14403,11 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; if (argvars[0].vval.v_string) { - server_stop((char *) argvars[0].vval.v_string); + bool rv = server_stop((char *)argvars[0].vval.v_string); + rettv->vval.v_number = (rv ? 1 : 0); } } @@ -15699,6 +15702,56 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) p_cpo = save_cpo; } +/// "stdpath()" helper for list results +static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + const void *iter = NULL; + list_T *const list = tv_list_alloc(kListLenShouldKnow); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = list; + tv_list_ref(list); + char *const dirs = stdpaths_get_xdg_var(xdg); + do { + size_t dir_len; + const char *dir; + iter = vim_env_iter(':', dirs, iter, &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + char *dir_with_nvim = xmemdupz(dir, dir_len); + dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true); + tv_list_append_string(list, dir_with_nvim, strlen(dir_with_nvim)); + xfree(dir_with_nvim); + } + } while (iter != NULL); + xfree(dirs); +} + +/// "stdpath(type)" function +static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + const char *const p = tv_get_string_chk(&argvars[0]); + if (p == NULL) { + return; // Type error; errmsg already given. + } + + if (strcmp(p, "config") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); + } else if (strcmp(p, "data") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); + } else if (strcmp(p, "cache") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); + } else if (strcmp(p, "config_dirs") == 0) { + get_xdg_var_list(kXDGConfigDirs, rettv); + } else if (strcmp(p, "data_dirs") == 0) { + get_xdg_var_list(kXDGDataDirs, rettv); + } else { + EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); + } +} + /* * "str2float()" function */ @@ -16861,6 +16914,12 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) int paused = (bool)tv_get_number(&argvars[1]); timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); if (timer != NULL) { + if (!timer->paused && paused) { + time_watcher_stop(&timer->tw); + } else if (timer->paused && !paused) { + time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, + timer->timeout); + } timer->paused = paused; } } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index b798eae187..149dae688e 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -10,6 +10,7 @@ #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" #include "nvim/channel.h" +#include "nvim/os/stdpaths_defs.h" #define COPYID_INC 2 #define COPYID_MASK (~0x1) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index daa3b637a3..801d2cc468 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -280,6 +280,7 @@ return { spellsuggest={args={1, 3}}, split={args={1, 3}}, sqrt={args=1, func="float_op_wrapper", data="&sqrt"}, + stdpath={args=1}, str2float={args=1}, str2nr={args={1, 2}}, strcharpart={args={2, 3}}, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 99495aaa61..7cd6dbdeca 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -133,7 +133,6 @@ struct dbg_stuff { char_u *vv_throwpoint; int did_emsg; int got_int; - int did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -165,12 +164,11 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) dsp->vv_exception = v_exception(NULL); dsp->vv_throwpoint = v_throwpoint(NULL); - /* Necessary for debugging an inactive ":catch", ":finally", ":endtry" */ - dsp->did_emsg = did_emsg; did_emsg = FALSE; - dsp->got_int = got_int; got_int = FALSE; - dsp->did_throw = did_throw; did_throw = FALSE; - dsp->need_rethrow = need_rethrow; need_rethrow = FALSE; - dsp->check_cstack = check_cstack; check_cstack = FALSE; + // Necessary for debugging an inactive ":catch", ":finally", ":endtry". + dsp->did_emsg = did_emsg; did_emsg = false; + dsp->got_int = got_int; got_int = false; + dsp->need_rethrow = need_rethrow; need_rethrow = false; + dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; } @@ -184,7 +182,6 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; - did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -400,16 +397,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, initial_trylevel = trylevel; - /* - * "did_throw" will be set to TRUE when an exception is being thrown. - */ - did_throw = FALSE; - /* - * "did_emsg" will be set to TRUE when emsg() is used, in which case we - * cancel the whole command line, and any if/endif or loop. - * If force_abort is set, we cancel everything. - */ - did_emsg = FALSE; + current_exception = NULL; + // "did_emsg" will be set to TRUE when emsg() is used, in which case we + // cancel the whole command line, and any if/endif or loop. + // If force_abort is set, we cancel everything. + did_emsg = false; /* * KeyTyped is only set when calling vgetc(). Reset it here when not @@ -659,7 +651,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * not to use a cs_line[] from an entry that isn't a ":while" * or ":for": It would make "current_line" invalid and can * cause a crash. */ - if (!did_emsg && !got_int && !did_throw + if (!did_emsg && !got_int && !current_exception && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -707,7 +699,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, } /* - * A ":finally" makes did_emsg, got_int, and did_throw pending for + * A ":finally" makes did_emsg, got_int and current_exception pending for * being restored at the ":endtry". Reset them here and set the * ACTIVE and FINALLY flags, so that the finally clause gets executed. * This includes the case where a missing ":endif", ":endwhile" or @@ -715,10 +707,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, */ if (cstack.cs_lflags & CSL_HAD_FINA) { cstack.cs_lflags &= ~CSL_HAD_FINA; - report_make_pending(cstack.cs_pending[cstack.cs_idx] - & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW), - did_throw ? (void *)current_exception : NULL); - did_emsg = got_int = did_throw = FALSE; + report_make_pending((cstack.cs_pending[cstack.cs_idx] + & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), + current_exception); + did_emsg = got_int = false; + current_exception = NULL; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -726,15 +719,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * within this loop. */ trylevel = initial_trylevel + cstack.cs_trylevel; - /* - * If the outermost try conditional (across function calls and sourced - * files) is aborted because of an error, an interrupt, or an uncaught - * exception, cancel everything. If it is left normally, reset - * force_abort to get the non-EH compatible abortion behavior for - * the rest of the script. - */ - if (trylevel == 0 && !did_emsg && !got_int && !did_throw) - force_abort = FALSE; + // If the outermost try conditional (across function calls and sourced + // files) is aborted because of an error, an interrupt, or an uncaught + // exception, cancel everything. If it is left normally, reset + // force_abort to get the non-EH compatible abortion behavior for + // the rest of the script. + if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + force_abort = false; + } /* Convert an interrupt to an exception if appropriate. */ (void)do_intthrow(&cstack); @@ -749,11 +741,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * - there is a command after '|', inside a :if, :while, :for or :try, or * looping for ":source" command or function call. */ - while (!((got_int - || (did_emsg && force_abort) || did_throw - ) - && cstack.cs_trylevel == 0 - ) + while (!((got_int || (did_emsg && force_abort) || current_exception) + && cstack.cs_trylevel == 0) && !(did_emsg /* Keep going when inside try/catch, so that the error can be * deal with, except when it is a syntax error, it may cause @@ -775,7 +764,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * If a sourced file or executed function ran to its end, report the * unclosed conditional. */ - if (!got_int && !did_throw + if (!got_int && !current_exception && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -815,17 +804,16 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, ? (char_u *)"endfunction" : (char_u *)NULL); if (trylevel == 0) { - /* - * When an exception is being thrown out of the outermost try - * conditional, discard the uncaught exception, disable the conversion - * of interrupts or errors to exceptions, and ensure that no more - * commands are executed. - */ - if (did_throw) { - void *p = NULL; - char_u *saved_sourcing_name; + // When an exception is being thrown out of the outermost try + // conditional, discard the uncaught exception, disable the conversion + // of interrupts or errors to exceptions, and ensure that no more + // commands are executed. + if (current_exception) { + void *p = NULL; + char_u *saved_sourcing_name; int saved_sourcing_lnum; - struct msglist *messages = NULL, *next; + struct msglist *messages = NULL; + struct msglist *next; /* * If the uncaught exception is a user exception, report it as an @@ -885,22 +873,22 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, suppress_errthrow = TRUE; } - /* - * The current cstack will be freed when do_cmdline() returns. An uncaught - * exception will have to be rethrown in the previous cstack. If a function - * has just returned or a script file was just finished and the previous - * cstack belongs to the same function or, respectively, script file, it - * will have to be checked for finally clauses to be executed due to the - * ":return" or ":finish". This is done in do_one_cmd(). - */ - if (did_throw) - need_rethrow = TRUE; + // The current cstack will be freed when do_cmdline() returns. An uncaught + // exception will have to be rethrown in the previous cstack. If a function + // has just returned or a script file was just finished and the previous + // cstack belongs to the same function or, respectively, script file, it + // will have to be checked for finally clauses to be executed due to the + // ":return" or ":finish". This is done in do_one_cmd(). + if (current_exception) { + need_rethrow = true; + } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!did_throw) - check_cstack = TRUE; + if (!current_exception) { + check_cstack = true; + } } else { /* When leaving a function, reduce nesting level. */ if (getline_equal(fgetline, cookie, get_func_line)) @@ -1480,10 +1468,11 @@ static char_u * do_one_cmd(char_u **cmdlinep, } char_u *after_modifier = ea.cmd; - ea.skip = did_emsg || got_int || did_throw || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack-> - cs_idx] - & CSF_ACTIVE)); + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); /* Count this line for profiling if ea.skip is FALSE. */ if (do_profiling == PROF_YES && !ea.skip) { @@ -1782,13 +1771,14 @@ static char_u * do_one_cmd(char_u **cmdlinep, )); - /* forced commands */ + // Forced commands. if (*p == '!' && ea.cmdidx != CMD_substitute && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { - ++p; - ea.forceit = TRUE; - } else - ea.forceit = FALSE; + p++; + ea.forceit = true; + } else { + ea.forceit = false; + } /* * 6. Parse arguments. @@ -6310,15 +6300,18 @@ static void ex_stop(exarg_T *eap) if (!eap->forceit) { autowrite_all(); } + apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); ui_cursor_goto((int)Rows - 1, 0); ui_linefeed(); ui_flush(); ui_call_suspend(); // call machine specific function + ui_flush(); maketitle(); resettitle(); // force updating the title redraw_later_clear(); ui_refresh(); // may have resized window + apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); } } @@ -6985,12 +6978,10 @@ do_exedit ( ex_no_reprint = TRUE; } -/* - * ":gui" and ":gvim" when there is no GUI. - */ +/// ":gui" and ":gvim" when there is no GUI. static void ex_nogui(exarg_T *eap) { - eap->errmsg = e_nogvim; + eap->errmsg = (char_u *)N_("E25: Nvim does not have a built-in GUI"); } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 4010a088c8..e23945c842 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -45,22 +45,21 @@ * there or even outside the try conditional. Try conditionals may be nested. */ -/* - * Configuration whether an exception is thrown on error or interrupt. When - * the preprocessor macros below evaluate to FALSE, an error (did_emsg) or - * interrupt (got_int) under an active try conditional terminates the script - * after the non-active finally clauses of all active try conditionals have been - * executed. Otherwise, errors and/or interrupts are converted into catchable - * exceptions (did_throw additionally set), which terminate the script only if - * not caught. For user exceptions, only did_throw is set. (Note: got_int can - * be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not - * a reliant test that the exception currently being thrown is an interrupt - * exception. Similarly, did_emsg can be set afterwards on an error in an - * (unskipped) conditional command inside an inactive conditional, so did_throw - * && did_emsg is not a reliant test that the exception currently being thrown - * is an error exception.) - The macros can be defined as expressions checking - * for a variable that is allowed to be changed during execution of a script. - */ +// Configuration whether an exception is thrown on error or interrupt. When +// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or +// interrupt (got_int) under an active try conditional terminates the script +// after the non-active finally clauses of all active try conditionals have been +// executed. Otherwise, errors and/or interrupts are converted into catchable +// exceptions, which terminate the script only if not caught. For user +// exceptions, only current_exception is set. (Note: got_int can be set +// asynchronously afterwards by a SIGINT, so current_exception && got_int is not +// a reliant test that the exception currently being thrown is an interrupt +// exception. Similarly, did_emsg can be set afterwards on an error in an +// (unskipped) conditional command inside an inactive conditional, so +// current_exception && did_emsg is not a reliant test that the exception +// currently being thrown is an error exception.) - The macros can be defined +// as expressions checking for a variable that is allowed to be changed during +// execution of a script. // Values used for the Vim release. #define THROW_ON_ERROR true @@ -68,6 +67,15 @@ #define THROW_ON_INTERRUPT true #define THROW_ON_INTERRUPT_TRUE +// Don't do something after an error, interrupt, or throw, or when +// there is a surrounding conditional and it was not active. +#define CHECK_SKIP \ + (did_emsg \ + || got_int \ + || current_exception \ + || (cstack->cs_idx > 0 \ + && !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE))) + #define discard_pending_return(p) tv_free((typval_T *)(p)) /* @@ -94,7 +102,7 @@ static int cause_abort = FALSE; */ int aborting(void) { - return (did_emsg && force_abort) || got_int || did_throw; + return (did_emsg && force_abort) || got_int || current_exception; } /* @@ -178,8 +186,9 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * currently throwing an exception, do nothing. The message text will * then be stored to v:errmsg by emsg() without displaying it. */ - if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) - return FALSE; + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !current_exception) { + return false; + } /* * Ignore an interrupt message when inside a try conditional or when an @@ -208,12 +217,13 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * exception currently being thrown to prevent it from being caught. Just * execute finally clauses and terminate. */ - if (did_throw) { - /* When discarding an interrupt exception, reset got_int to prevent the - * same interrupt being converted to an exception again and discarding - * the error exception we are about to throw here. */ - if (current_exception->type == ET_INTERRUPT) - got_int = FALSE; + if (current_exception) { + // When discarding an interrupt exception, reset got_int to prevent the + // same interrupt being converted to an exception again and discarding + // the error exception we are about to throw here. + if (current_exception->type == ET_INTERRUPT) { + got_int = false; + } discard_current_exception(); } @@ -333,45 +343,44 @@ void do_errthrow(struct condstack *cstack, char_u *cmdname) */ int do_intthrow(struct condstack *cstack) { - /* - * If no interrupt occurred or no try conditional is active and no exception - * is being thrown, do nothing (for compatibility of non-EH scripts). - */ - if (!got_int || (trylevel == 0 && !did_throw)) - return FALSE; + // If no interrupt occurred or no try conditional is active and no exception + // is being thrown, do nothing (for compatibility of non-EH scripts). + if (!got_int || (trylevel == 0 && !current_exception)) { + return false; + } -#ifdef THROW_TEST /* avoid warning for condition always true */ +#ifdef THROW_TEST // avoid warning for condition always true if (!THROW_ON_INTERRUPT) { - /* - * The interrupt aborts everything except for executing finally clauses. - * Discard any user or error or interrupt exception currently being - * thrown. - */ - if (did_throw) + // The interrupt aborts everything except for executing finally clauses. + // Discard any user or error or interrupt exception currently being + // thrown. + if (current_exception) { discard_current_exception(); - } else + } + } else { #endif - { - /* - * Throw an interrupt exception, so that everything will be aborted - * (except for executing finally clauses), until the interrupt exception - * is caught; if still uncaught at the top level, the script processing - * will be terminated then. - If an interrupt exception is already - * being thrown, do nothing. - * - */ - if (did_throw) { - if (current_exception->type == ET_INTERRUPT) - return FALSE; + // Throw an interrupt exception, so that everything will be aborted + // (except for executing finally clauses), until the interrupt exception + // is caught; if still uncaught at the top level, the script processing + // will be terminated then. - If an interrupt exception is already + // being thrown, do nothing. + + if (current_exception) { + if (current_exception->type == ET_INTERRUPT) { + return false; + } - /* An interrupt exception replaces any user or error exception. */ + // An interrupt exception replaces any user or error exception. discard_current_exception(); } - if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) + if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) { do_throw(cstack); + } +#ifdef THROW_TEST } +#endif - return TRUE; + return true; } // Get an exception message that is to be stored in current_exception->value. @@ -565,8 +574,7 @@ void discard_current_exception(void) // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. current_exception = NULL; - did_throw = FALSE; - need_rethrow = FALSE; + need_rethrow = false; } /* @@ -795,15 +803,7 @@ void ex_if(exarg_T *eap) ++cstack->cs_idx; cstack->cs_flags[cstack->cs_idx] = 0; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; bool error; result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); @@ -854,15 +854,7 @@ void ex_else(exarg_T *eap) int result; struct condstack *cstack = eap->cstack; - /* - * Don't do something after an error, interrupt, or throw, or when there is - * a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (cstack->cs_idx < 0 || (cstack->cs_flags[cstack->cs_idx] @@ -952,15 +944,7 @@ void ex_while(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR; - /* - * Don't do something after an error, interrupt, or throw, or when - * there is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (eap->cmdidx == CMD_while) { /* * ":while bool-expr" @@ -1233,8 +1217,6 @@ void do_throw(struct condstack *cstack) cstack->cs_flags[idx] &= ~CSF_ACTIVE; cstack->cs_exception[idx] = current_exception; } - - did_throw = TRUE; } /* @@ -1253,15 +1235,7 @@ void ex_try(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = CSF_TRY; cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (!skip) { /* Set ACTIVE and TRUE. TRUE means that the corresponding ":catch" @@ -1353,8 +1327,9 @@ void ex_catch(exarg_T *eap) * corresponding try block never got active (because of an inactive * surrounding conditional or after an error or interrupt or throw). */ - if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) - skip = TRUE; + if (!current_exception || !(cstack->cs_flags[idx] & CSF_TRUE)) { + skip = true; + } /* * Check for a match only if an exception is thrown but not caught by @@ -1413,10 +1388,10 @@ void ex_catch(exarg_T *eap) } if (caught) { - /* Make this ":catch" clause active and reset did_emsg, got_int, - * and did_throw. Put the exception on the caught stack. */ + /* Make this ":catch" clause active and reset did_emsg and got_int. + * Put the exception on the caught stack. */ cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; - did_emsg = got_int = did_throw = FALSE; + did_emsg = got_int = false; catch_exception((except_T *)cstack->cs_exception[idx]); /* It's mandatory that the current exception is stored in the cstack * so that it can be discarded at the next ":catch", ":finally", or @@ -1426,6 +1401,10 @@ void ex_catch(exarg_T *eap) if (cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_catch()"); } + // Discarding current_exceptions happens based on what is stored in + // cstack->cs_exception, *all* calls to discard_current_exception() are + // (and must be) guarded by current_exception check. + current_exception = NULL; } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1484,7 +1463,7 @@ void ex_finally(exarg_T *eap) * interrupt or throw) or for a ":finally" without ":try" or a multiple * ":finally". After every other error (did_emsg or the conditional * errors detected above) or after an interrupt (got_int) or an - * exception (did_throw), the finally clause must be executed. + * exception (current_exception), the finally clause must be executed. */ skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); @@ -1511,30 +1490,31 @@ void ex_finally(exarg_T *eap) cleanup_conditionals(cstack, CSF_TRY, FALSE); /* - * Make did_emsg, got_int, did_throw pending. If set, they overrule - * a pending ":continue", ":break", ":return", or ":finish". Then - * we have particularly to discard a pending return value (as done + * Make did_emsg, got_int, current_exception pending. If set, they + * overrule a pending ":continue", ":break", ":return", or ":finish". + * Then we have particularly to discard a pending return value (as done * by the call to cleanup_conditionals() above when did_emsg or * got_int is set). The pending values are restored by the * ":endtry", except if there is a new error, interrupt, exception, * ":continue", ":break", ":return", or ":finish" in the following * finally clause. A missing ":endwhile", ":endfor" or ":endif" - * detected here is treated as if did_emsg and did_throw had + * detected here is treated as if did_emsg and current_exception had * already been set, respectively in case that the error is not - * converted to an exception, did_throw had already been unset. + * converted to an exception, current_exception had already been unset. * We must not set did_emsg here since that would suppress the * error message. */ - if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { + if (pending == CSTP_ERROR || did_emsg || got_int || current_exception) { if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { report_discard_pending(CSTP_RETURN, cstack->cs_rettv[cstack->cs_idx]); discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); } - if (pending == CSTP_ERROR && !did_emsg) - pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0; - else - pending |= did_throw ? CSTP_THROW : 0; + if (pending == CSTP_ERROR && !did_emsg) { + pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); + } else { + pending |= (current_exception ? CSTP_THROW : 0); + } pending |= did_emsg ? CSTP_ERROR : 0; pending |= got_int ? CSTP_INTERRUPT : 0; assert(pending >= CHAR_MIN && pending <= CHAR_MAX); @@ -1547,14 +1527,15 @@ void ex_finally(exarg_T *eap) * exception. When emsg() is called for a missing ":endif" or * a missing ":endwhile"/":endfor" detected here, the * exception will be discarded. */ - if (did_throw && cstack->cs_exception[cstack->cs_idx] - != current_exception) + if (current_exception + && cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_finally()"); + } } /* * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, - * got_int, and did_throw and make the finally clause active. + * got_int, and current_exception and make the finally clause active. * This will happen after emsg() has been called for a missing * ":endif" or a missing ":endwhile"/":endfor" detected here, so * that the following finally clause will be executed even then. @@ -1589,7 +1570,7 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - skip = (did_emsg || got_int || did_throw + skip = (did_emsg || got_int || current_exception || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { @@ -1606,12 +1587,13 @@ void ex_endtry(exarg_T *eap) /* * If an exception is being thrown, discard it to prevent it from * being rethrown at the end of this function. It would be - * discarded by the error message, anyway. Resets did_throw. + * discarded by the error message, anyway. Resets current_exception. * This does not affect the script termination due to the error * since "trylevel" is decremented after emsg() has been called. */ - if (did_throw) + if (current_exception) { discard_current_exception(); + } } else { idx = cstack->cs_idx; @@ -1621,9 +1603,11 @@ void ex_endtry(exarg_T *eap) * a finally clause, we need to rethrow it after closing the try * conditional. */ - if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE) - && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + if (current_exception + && (cstack->cs_flags[idx] & CSF_TRUE) + && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } /* If there was no finally clause, show the user when debugging or @@ -1644,11 +1628,12 @@ void ex_endtry(exarg_T *eap) if (got_int) { skip = TRUE; (void)do_intthrow(cstack); - /* The do_intthrow() call may have reset did_throw or - * cstack->cs_pending[idx].*/ - rethrow = FALSE; - if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + // The do_intthrow() call may have reset current_exception or + // cstack->cs_pending[idx]. + rethrow = false; + if (current_exception && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } } @@ -1710,26 +1695,30 @@ void ex_endtry(exarg_T *eap) do_finish(eap, FALSE); break; - /* When the finally clause was entered due to an error, - * interrupt or throw (as opposed to a ":continue", ":break", - * ":return", or ":finish"), restore the pending values of - * did_emsg, got_int, and did_throw. This is skipped, if there - * was a new error, interrupt, throw, ":continue", ":break", - * ":return", or ":finish". in the finally clause. */ + // When the finally clause was entered due to an error, + // interrupt or throw (as opposed to a ":continue", ":break", + // ":return", or ":finish"), restore the pending values of + // did_emsg, got_int, and current_exception. This is skipped, if there + // was a new error, interrupt, throw, ":continue", ":break", + // ":return", or ":finish". in the finally clause. default: - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - rethrow = TRUE; + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + rethrow = true; + } break; } } - if (rethrow) - /* Rethrow the current exception (within this cstack). */ + if (rethrow) { + // Rethrow the current exception (within this cstack). do_throw(cstack); + } } } @@ -1759,33 +1748,34 @@ void enter_cleanup(cleanup_T *csp) int pending = CSTP_NONE; /* - * Postpone did_emsg, got_int, did_throw. The pending values will be + * Postpone did_emsg, got_int, current_exception. The pending values will be * restored by leave_cleanup() except if there was an aborting error, * interrupt, or uncaught exception after this function ends. */ - if (did_emsg || got_int || did_throw || need_rethrow) { - csp->pending = (did_emsg ? CSTP_ERROR : 0) - | (got_int ? CSTP_INTERRUPT : 0) - | (did_throw ? CSTP_THROW : 0) - | (need_rethrow ? CSTP_THROW : 0); - - /* If we are currently throwing an exception (did_throw), save it as - * well. On an error not yet converted to an exception, update - * "force_abort" and reset "cause_abort" (as do_errthrow() would do). - * This is needed for the do_cmdline() call that is going to be made - * for autocommand execution. We need not save *msg_list because - * there is an extra instance for every call of do_cmdline(), anyway. + if (did_emsg || got_int || current_exception || need_rethrow) { + csp->pending = (did_emsg ? CSTP_ERROR : 0) + | (got_int ? CSTP_INTERRUPT : 0) + | (current_exception ? CSTP_THROW : 0) + | (need_rethrow ? CSTP_THROW : 0); + + /* If we are currently throwing an exception, save it as well. On an error + * not yet converted to an exception, update "force_abort" and reset + * "cause_abort" (as do_errthrow() would do). This is needed for the + * do_cmdline() call that is going to be made for autocommand execution. We + * need not save *msg_list because there is an extra instance for every call + * of do_cmdline(), anyway. */ - if (did_throw || need_rethrow) + if (current_exception || need_rethrow) { csp->exception = current_exception; - else { + } else { csp->exception = NULL; if (did_emsg) { force_abort |= cause_abort; cause_abort = FALSE; } } - did_emsg = got_int = did_throw = need_rethrow = FALSE; + did_emsg = got_int = need_rethrow = false; + current_exception = NULL; /* Report if required by the 'verbose' option or when debugging. */ report_make_pending(pending, csp->exception); @@ -1857,19 +1847,20 @@ void leave_cleanup(cleanup_T *csp) force_abort = FALSE; } - /* - * Restore the pending values of did_emsg, got_int, and did_throw. - */ - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - need_rethrow = TRUE; /* did_throw will be set by do_one_cmd() */ + // Restore the pending values of did_emsg, got_int, and current_exception. + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + need_rethrow = true; // current_exception will be set by do_one_cmd() + } - /* Report if required by the 'verbose' option or when debugging. */ - report_resume_pending(pending, - (pending & CSTP_THROW) ? (void *)current_exception : NULL); + // Report if required by the 'verbose' option or when debugging. + report_resume_pending( + pending, ((pending & CSTP_THROW) ? (void *)current_exception : NULL)); } } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 52686f6651..4adff63b95 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * fileio.c: read from and write to a file - */ +// fileio.c: read from and write to a file #include <assert.h> #include <errno.h> @@ -65,57 +63,62 @@ #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ -/* - * The autocommands are stored in a list for each event. - * Autocommands for the same pattern, that are consecutive, are joined - * together, to avoid having to match the pattern too often. - * The result is an array of Autopat lists, which point to AutoCmd lists: - * - * first_autopat[0] --> Autopat.next --> Autopat.next --> NULL - * Autopat.cmds Autopat.cmds - * | | - * V V - * AutoCmd.next AutoCmd.next - * | | - * V V - * AutoCmd.next NULL - * | - * V - * NULL - * - * first_autopat[1] --> Autopat.next --> NULL - * Autopat.cmds - * | - * V - * AutoCmd.next - * | - * V - * NULL - * etc. - * - * The order of AutoCmds is important, this is the order in which they were - * defined and will have to be executed. - */ +// +// The autocommands are stored in a list for each event. +// Autocommands for the same pattern, that are consecutive, are joined +// together, to avoid having to match the pattern too often. +// The result is an array of Autopat lists, which point to AutoCmd lists: +// +// last_autopat[0] -----------------------------+ +// V +// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL +// Autopat.cmds Autopat.cmds +// | | +// V V +// AutoCmd.next AutoCmd.next +// | | +// V V +// AutoCmd.next NULL +// | +// V +// NULL +// +// last_autopat[1] --------+ +// V +// first_autopat[1] --> Autopat.next --> NULL +// Autopat.cmds +// | +// V +// AutoCmd.next +// | +// V +// NULL +// etc. +// +// The order of AutoCmds is important, this is the order in which they were +// defined and will have to be executed. +// typedef struct AutoCmd { - char_u *cmd; /* The command to be executed (NULL - when command has been removed) */ - char nested; /* If autocommands nest here */ - char last; /* last command in list */ - scid_T scriptID; /* script ID where defined */ - struct AutoCmd *next; /* Next AutoCmd in list */ + char_u *cmd; // The command to be executed (NULL + // when command has been removed) + char nested; // If autocommands nest here + char last; // last command in list + scid_T scriptID; // script ID where defined + struct AutoCmd *next; // Next AutoCmd in list } AutoCmd; typedef struct AutoPat { - char_u *pat; /* pattern as typed (NULL when pattern - has been removed) */ - regprog_T *reg_prog; /* compiled regprog for pattern */ - AutoCmd *cmds; /* list of commands to do */ - struct AutoPat *next; /* next AutoPat in AutoPat list */ - int group; /* group ID */ - int patlen; /* strlen() of pat */ - int buflocal_nr; /* !=0 for buffer-local AutoPat */ - char allow_dirs; /* Pattern may match whole path */ - char last; /* last pattern for apply_autocmds() */ + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do + int group; // group ID + int patlen; // strlen() of pat + int buflocal_nr; // !=0 for buffer-local AutoPat + char allow_dirs; // Pattern may match whole path + char last; // last pattern for apply_autocmds() } AutoPat; /* @@ -226,6 +229,15 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scrolled_ign = FALSE; } +static AutoPat *last_autopat[NUM_EVENTS] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + /* * Read lines from file "fname" into the buffer after line "from". * @@ -1725,9 +1737,17 @@ failed: xfree(buffer); if (read_stdin) { - /* Use stderr for stdin, makes shell commands work. */ close(0); +#ifndef WIN32 + // On Unix, use stderr for stdin, makes shell commands work. ignored = dup(2); +#else + // On Windows, use the console input handle for stdin. + HANDLE conin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + ignored = _open_osfhandle(conin, _O_RDONLY); +#endif } if (tmpname != NULL) { @@ -4440,7 +4460,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) /// @param size size of the buffer /// @param fp file to read from /// -/// @return true for end-of-file. +/// @return true for EOF or error bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL { char *retval; @@ -4451,7 +4471,7 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL do { errno = 0; retval = fgets((char *)buf, size, fp); - } while (retval == NULL && errno == EINTR); + } while (retval == NULL && errno == EINTR && ferror(fp)); if (buf[size - 2] != NUL && buf[size - 2] != '\n') { char tbuf[200]; @@ -4463,12 +4483,12 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL tbuf[sizeof(tbuf) - 2] = NUL; errno = 0; retval = fgets((char *)tbuf, sizeof(tbuf), fp); - if (retval == NULL && errno != EINTR) { + if (retval == NULL && (feof(fp) || errno != EINTR)) { break; } } while (tbuf[sizeof(tbuf) - 2] != NUL && tbuf[sizeof(tbuf) - 2] != '\n'); } - return retval ? false : feof(fp); + return retval == NULL; } /// Read 2 bytes from "fd" and turn them into an int, MSB first. @@ -5528,6 +5548,15 @@ static void au_cleanup(void) /* remove the pattern if it has been marked for deletion */ if (ap->pat == NULL) { + if (ap->next == NULL) { + if (prev_ap == &(first_autopat[(int)event])) { + last_autopat[(int)event] = NULL; + } else { + // this depends on the "next" field being the first in + // the struct + last_autopat[(int)event] = (AutoPat *)prev_ap; + } + } *prev_ap = ap->next; vim_regfree(ap->reg_prog); xfree(ap); @@ -6120,10 +6149,13 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, patlen = (int)STRLEN(buflocal_pat); /* but not endpat */ } - /* - * Find AutoPat entries with this pattern. - */ - prev_ap = &first_autopat[(int)event]; + // Find AutoPat entries with this pattern. When adding a command it + // always goes at or after the last one, so start at the end. + if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } while ((ap = *prev_ap) != NULL) { if (ap->pat != NULL) { /* Accept a pattern when: @@ -6209,6 +6241,7 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, } ap->cmds = NULL; *prev_ap = ap; + last_autopat[(int)event] = ap; ap->next = NULL; if (group == AUGROUP_ALL) ap->group = current_augroup; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 5d4e61d56a..03929a58b6 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1577,7 +1577,7 @@ vungetc ( /* unget one character (can only be done once!) */ old_mouse_col = mouse_col; } -/// get a character: +/// Gets a character: /// 1. from the stuffbuffer /// This is used for abbreviated commands like "D" -> "d$". /// Also used to redo a command for ".". @@ -1595,7 +1595,7 @@ vungetc ( /* unget one character (can only be done once!) */ /// if "advance" is FALSE (vpeekc()): /// just look whether there is a character available. /// -/// When "no_mapping" is zero, checks for mappings in the current mode. +/// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). /// K_SPECIAL and CSI may be escaped, need to get two more bytes then. static int vgetorpeek(int advance) diff --git a/src/nvim/globals.h b/src/nvim/globals.h index e857f5ff5b..89d93310a6 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -317,17 +317,11 @@ EXTERN int do_profiling INIT(= PROF_NONE); /* PROF_ values */ /* * The exception currently being thrown. Used to pass an exception to * a different cstack. Also used for discarding an exception before it is - * caught or made pending. Only valid when did_throw is TRUE. + * caught or made pending. */ EXTERN except_T *current_exception; /* - * did_throw: An exception is being thrown. Reset when the exception is caught - * or as long as it is pending in a finally clause. - */ -EXTERN int did_throw INIT(= FALSE); - -/* * need_rethrow: set to TRUE when a throw that cannot be handled in do_cmdline() * must be propagated to the cstack of the previously called do_cmdline(). */ @@ -953,6 +947,7 @@ EXTERN int fill_stlnc INIT(= ' '); EXTERN int fill_vert INIT(= 9474); // │ EXTERN int fill_fold INIT(= 183); // · EXTERN int fill_diff INIT(= '-'); +EXTERN int fill_msgsep INIT(= ' '); /* Whether 'keymodel' contains "stopsel" and "startsel". */ EXTERN int km_stopsel INIT(= FALSE); @@ -1075,7 +1070,6 @@ EXTERN char_u e_nesting[] INIT(= N_("E22: Scripts nested too deep")); EXTERN char_u e_noalt[] INIT(= N_("E23: No alternate file")); EXTERN char_u e_noabbr[] INIT(= N_("E24: No such abbreviation")); EXTERN char_u e_nobang[] INIT(= N_("E477: No ! allowed")); -EXTERN char_u e_nogvim[] INIT(= N_("E25: Nvim does not have a built-in GUI")); EXTERN char_u e_nogroup[] INIT(= N_("E28: No such highlight group name: %s")); EXTERN char_u e_noinstext[] INIT(= N_("E29: No inserted text yet")); EXTERN char_u e_nolastcmd[] INIT(= N_("E30: No previous command line")); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 08157935f5..3518c8bdcc 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -87,6 +87,7 @@ typedef enum { , HLF_QFL // selected quickfix line , HLF_0 // Whitespace , HLF_INACTIVE // NormalNC: Normal text in non-current windows + , HLF_MSGSEP // message separator line , HLF_COUNT // MUST be the last one } hlf_T; @@ -137,7 +138,8 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MC] = "ColorColumn", [HLF_QFL] = "QuickFixLine", [HLF_0] = "Whitespace", - [HLF_INACTIVE] = "NormalNC" + [HLF_INACTIVE] = "NormalNC", + [HLF_MSGSEP] = "MsgSeparator", }); diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 00e9cf6ed3..c64691e8ea 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -443,7 +443,7 @@ enum key_extra { #define MOD_MASK_2CLICK 0x20 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_3CLICK 0x40 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_4CLICK 0x60 // use MOD_MASK_MULTI_CLICK -#define MOD_MASK_CMD 0x80 // "super" key (OSX/Mac: command-key) +#define MOD_MASK_CMD 0x80 // "super" key (macOS: command-key) #define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK| \ MOD_MASK_4CLICK) diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 4e01265498..348df2d9b6 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -136,13 +136,21 @@ # define RESET_BINDING(wp) (wp)->w_p_scb = FALSE; (wp)->w_p_crb = FALSE -/// Calculate the length of a C array. +/// Calculate the length of a C array /// /// This should be called with a real array. Calling this with a pointer is an -/// error. A mechanism to detect many (though not all) of those errors at compile -/// time is implemented. It works by the second division producing a division by -/// zero in those cases (-Wdiv-by-zero in GCC). -#define ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) +/// error. A mechanism to detect many (though not all) of those errors at +/// compile time is implemented. It works by the second division producing +/// a division by zero in those cases (-Wdiv-by-zero in GCC). +#define ARRAY_SIZE(arr) \ + ((sizeof(arr)/sizeof((arr)[0])) \ + / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) + +/// Get last array entry +/// +/// This should be called with a real array. Calling this with a pointer is an +/// error. +#define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] // Duplicated in os/win_defs.h to avoid include-order sensitivity. #define RGB_(r, g, b) ((r << 16) | (g << 8) | b) diff --git a/src/nvim/main.c b/src/nvim/main.c index 4288d7f9d7..ce9feedd16 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -72,30 +72,30 @@ # include "nvim/os/pty_process_unix.h" #endif -/* Maximum number of commands from + or -c arguments. */ +// Maximum number of commands from + or -c arguments. #define MAX_ARG_CMDS 10 -/* values for "window_layout" */ -#define WIN_HOR 1 /* "-o" horizontally split windows */ -#define WIN_VER 2 /* "-O" vertically split windows */ -#define WIN_TABS 3 /* "-p" windows on tab pages */ +// values for "window_layout" +#define WIN_HOR 1 // "-o" horizontally split windows +#define WIN_VER 2 // "-O" vertically split windows +#define WIN_TABS 3 // "-p" windows on tab pages -/* Struct for various parameters passed between main() and other functions. */ +// Struct for various parameters passed between main() and other functions. typedef struct { int argc; char **argv; char *use_vimrc; // vimrc from -u argument - int n_commands; /* no. of commands from + or -c */ + int n_commands; // no. of commands from + or -c char *commands[MAX_ARG_CMDS]; // commands from + or -c arg - char_u cmds_tofree[MAX_ARG_CMDS]; /* commands that need free() */ - int n_pre_commands; /* no. of commands from --cmd */ + char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() + int n_pre_commands; // no. of commands from --cmd char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument - int edit_type; /* type of editing to do */ - char_u *tagname; /* tag from -t argument */ - char_u *use_ef; /* 'errorfile' from -q argument */ + int edit_type; // type of editing to do + char_u *tagname; // tag from -t argument + char_u *use_ef; // 'errorfile' from -q argument int want_full_screen; bool input_isatty; // stdin is a terminal @@ -103,13 +103,15 @@ typedef struct { bool err_isatty; // stderr is a terminal int no_swap_file; // "-n" argument used int use_debug_break_level; - int window_count; /* number of windows to use */ - int window_layout; /* 0, WIN_HOR, WIN_VER or WIN_TABS */ + int window_count; // number of windows to use + int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS #if !defined(UNIX) - int literal; /* don't expand file names */ + int literal; // don't expand file names #endif - int diff_mode; /* start with 'diff' set */ + int diff_mode; // start with 'diff' set + + char *listen_addr; // --listen {address} } mparm_T; /* Values for edit_type. */ @@ -150,7 +152,6 @@ void event_init(void) signal_init(); // finish mspgack-rpc initialization channel_init(); - server_init(); terminal_init(); } @@ -241,9 +242,8 @@ int main(int argc, char **argv) char_u *cwd = NULL; // current workding dir on startup time_init(); - /* Many variables are in "params" so that we can pass them to invoked - * functions without a lot of arguments. "argc" and "argv" are also - * copied, so that they can be changed. */ + // Many variables are in `params` so that we can pass them around easily. + // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); init_startuptime(¶ms); @@ -254,11 +254,10 @@ int main(int argc, char **argv) check_and_set_isatty(¶ms); event_init(); - /* - * Process the command line arguments. File names are put in the global - * argument list "global_alist". - */ + // Process the command line arguments. File names are put in the global + // argument list "global_alist". command_line_scan(¶ms); + server_init(params.listen_addr); if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); @@ -819,6 +818,9 @@ static void command_line_scan(mparm_T *parmp) if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { abort(); } + } else if (STRNICMP(argv[0] + argv_idx, "listen", 6) == 0) { + want_argument = true; + argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { #if !defined(UNIX) parmp->literal = TRUE; @@ -864,10 +866,6 @@ static void command_line_scan(mparm_T *parmp) case 'f': /* "-f" GUI: run in foreground. */ break; - case 'g': /* "-g" start GUI */ - main_start_gui(); - break; - case 'F': { // "-F" start in Farsi mode: rl + fkmap set. p_fkmap = true; set_option_value("rl", 1L, NULL, 0); @@ -906,18 +904,8 @@ static void command_line_scan(mparm_T *parmp) parmp->no_swap_file = TRUE; break; - case 'p': /* "-p[N]" open N tab pages */ -#ifdef TARGET_API_MAC_OSX - /* For some reason on MacOS X, an argument like: - -psn_0_10223617 is passed in when invoke from Finder - or with the 'open' command */ - if (argv[0][argv_idx] == 's') { - argv_idx = -1; /* bypass full -psn */ - main_start_gui(); - break; - } -#endif - /* default is 0: open window for each file */ + case 'p': // "-p[N]" open N tab pages + // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_TABS; break; @@ -1030,15 +1018,12 @@ static void command_line_scan(mparm_T *parmp) mainerr(err_opt_unknown, argv[0]); } - /* - * Handle option arguments with argument. - */ + // Handle option arguments with argument. if (want_argument) { - /* - * Check for garbage immediately after the option letter. - */ - if (argv[0][argv_idx] != NUL) + // Check for garbage immediately after the option letter. + if (argv[0][argv_idx] != NUL) { mainerr(err_opt_garbage, argv[0]); + } --argc; if (argc < 1 && c != 'S') /* -S has an optional argument */ @@ -1077,13 +1062,17 @@ static void command_line_scan(mparm_T *parmp) break; case '-': - if (argv[-1][2] == 'c') { - /* "--cmd {command}" execute command */ - if (parmp->n_pre_commands >= MAX_ARG_CMDS) + if (strequal(argv[-1], "--cmd")) { + // "--cmd {command}" execute command + if (parmp->n_pre_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); + } parmp->pre_commands[parmp->n_pre_commands++] = argv[0]; + } else if (strequal(argv[-1], "--listen")) { + // "--listen {address}" + parmp->listen_addr = argv[0]; } - /* "--startuptime <file>" already handled */ + // "--startuptime <file>" already handled break; case 'q': /* "-q {errorfile}" QuickFix mode */ @@ -1224,11 +1213,10 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->want_full_screen = true; paramp->use_debug_break_level = -1; paramp->window_count = -1; + paramp->listen_addr = NULL; } -/* - * Initialize global startuptime file if "--startuptime" passed as an argument. - */ +/// Initialize global startuptime file if "--startuptime" passed as an argument. static void init_startuptime(mparm_T *paramp) { for (int i = 1; i < paramp->argc; i++) { @@ -1834,17 +1822,6 @@ static void source_startup_scripts(const mparm_T *const parmp) TIME_MSG("sourcing vimrc file(s)"); } -/* - * Setup to start using the GUI. Exit with an error when not available. - */ -static void main_start_gui(void) -{ - mch_errmsg(_(e_nogvim)); - mch_errmsg("\n"); - mch_exit(2); -} - - /// Get an environment variable, and execute it as Ex commands. /// /// @param env environment variable to execute @@ -1968,6 +1945,7 @@ static void usage(void) mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); mch_msg(_(" --headless Don't start a user interface\n")); + mch_msg(_(" --listen <address> Start RPC server at this address\n")); #if !defined(UNIX) mch_msg(_(" --literal Don't expand wildcards\n")); #endif diff --git a/src/nvim/map.h b/src/nvim/map.h index 047aa163ce..ac1239a548 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -8,6 +8,11 @@ #include "nvim/api/private/dispatch.h" #include "nvim/bufhl_defs.h" +#if defined(__NetBSD__) +# undef uint64_t +# define uint64_t uint64_t +#endif + #define MAP_DECLS(T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \ \ diff --git a/src/nvim/message.c b/src/nvim/message.c index 12e5b844be..04528629c7 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1875,13 +1875,29 @@ bool message_filtered(char_u *msg) return cmdmod.filter_force ? match : !match; } +/// including horizontal separator +int msg_scrollsize(void) +{ + return msg_scrolled + p_ch + 1; +} + /* * Scroll the screen up one line for displaying the next message line. */ static void msg_scroll_up(void) { - /* scrolling up always works */ - screen_del_lines(0, 0, 1, (int)Rows, NULL); + if (dy_flags & DY_MSGSEP) { + if (msg_scrolled == 0) { + screen_fill(Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, + fill_msgsep, fill_msgsep, hl_attr(HLF_MSGSEP)); + } + int nscroll = MIN(msg_scrollsize()+1, Rows); + ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1); + screen_del_lines(Rows-nscroll, 0, 1, nscroll, NULL); + ui_reset_scroll_region(); + } else { + screen_del_lines(0, 0, 1, (int)Rows, NULL); + } } /* diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 9bf122f4db..e5d80aea1d 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -32,26 +32,27 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE; #endif /// Initializes the module -bool server_init(void) +bool server_init(const char *listen_addr) { ga_init(&watchers, sizeof(SocketWatcher *), 1); - bool must_free = false; - const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR); - if (listen_address == NULL) { - must_free = true; - listen_address = server_address_new(); - } + // $NVIM_LISTEN_ADDRESS + const char *env_addr = os_getenv(LISTEN_ADDRESS_ENV_VAR); + int rv = listen_addr == NULL ? 1 : server_start(listen_addr); - if (!listen_address) { - return false; + if (0 != rv) { + rv = env_addr == NULL ? 1 : server_start(env_addr); + if (0 != rv) { + listen_addr = server_address_new(); + if (listen_addr == NULL) { + return false; + } + rv = server_start(listen_addr); + xfree((char *)listen_addr); + } } - bool ok = (server_start(listen_address) == 0); - if (must_free) { - xfree((char *) listen_address); - } - return ok; + return rv == 0; } /// Teardown a single server @@ -120,8 +121,8 @@ bool server_owns_pipe_address(const char *path) /// @param endpoint Address of the server. Either a 'ip:[port]' string or an /// arbitrary identifier (trimmed to 256 bytes) for the Unix /// socket or named pipe. -/// @returns 0 on success, 1 on a regular error, and negative errno -/// on failure to bind or listen. +/// @returns 0: success, 1: validation error, 2: already listening, +/// -errno: failed to bind or listen. int server_start(const char *endpoint) { if (endpoint == NULL || endpoint[0] == '\0') { @@ -145,7 +146,7 @@ int server_start(const char *endpoint) uv_freeaddrinfo(watcher->uv.tcp.addrinfo); } socket_watcher_close(watcher, free_server); - return 1; + return 2; } } @@ -177,7 +178,7 @@ int server_start(const char *endpoint) /// Stops listening on the address specified by `endpoint`. /// /// @param endpoint Address of the server. -void server_stop(char *endpoint) +bool server_stop(char *endpoint) { SocketWatcher *watcher; bool watcher_found = false; @@ -196,8 +197,8 @@ void server_stop(char *endpoint) } if (!watcher_found) { - ELOG("Not listening on %s", addr); - return; + WLOG("Not listening on %s", addr); + return false; } // Unset $NVIM_LISTEN_ADDRESS if it is the stopped address. @@ -219,6 +220,8 @@ void server_stop(char *endpoint) if (STRCMP(addr, get_vim_var_str(VV_SEND_SERVER)) == 0) { set_vservername(&watchers); } + + return true; } /// Returns an allocated array of server addresses. diff --git a/src/nvim/option.c b/src/nvim/option.c index d6903c8db7..c43ba2fc4f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -13,7 +13,8 @@ // add some code to didset_window_options(). // - For a buffer option, add some code to buf_copy_options(). // - For a buffer string option, add code to check_buf_options(). -// - If it's a numeric option, add any necessary bounds checks to do_set(). +// - If it's a numeric option, add any necessary bounds checks to +// set_num_option(). // - If it's a list of flags, add some code in do_set(), search for WW_ALL. // - When adding an option with expansion (P_EXPAND), but with a different // default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP. @@ -1467,8 +1468,7 @@ do_set ( goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { - // Allow negative (for 'undolevels'), octal and - // hex numbers. + // Allow negative, octal and hex numbers. vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0); if (arg[i] != NUL && !ascii_iswhite(arg[i])) { errmsg = e_invarg; @@ -3411,6 +3411,7 @@ static char_u *set_chars_option(char_u **varp) { &fill_vert, "vert" , 9474 }, // │ { &fill_fold, "fold" , 183 }, // · { &fill_diff, "diff" , '-' }, + { &fill_msgsep, "msgsep", ' ' }, }; static struct charstab lcstab[] = { { &lcs_eol, "eol", NUL }, @@ -4089,238 +4090,259 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)e_secure; } - *pp = value; - /* Remember where the option was set. */ - set_option_scriptID_idx(opt_idx, opt_flags, current_SID); - - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - curbuf->b_p_sw = curbuf->b_p_ts; + // Many number options assume their value is in the signed int range. + if (value < INT_MIN || value > INT_MAX) { + return (char *)e_invarg; } - /* - * Number options that need some action when changed - */ - if (pp == &p_wh || pp == &p_hh) { - if (p_wh < 1) { + // Options that need some validation. + if (pp == &p_wh) { + if (value < 1) { errmsg = e_positive; - p_wh = 1; - } - if (p_wmh > p_wh) { + } else if (p_wmh > value) { errmsg = e_winheight; - p_wh = p_wmh; } - if (p_hh < 0) { + } else if (pp == &p_hh) { + if (value < 0) { errmsg = e_positive; - p_hh = 0; } - - /* Change window height NOW */ - if (!ONE_WINDOW) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); - } - } - /* 'winminheight' */ - else if (pp == &p_wmh) { - if (p_wmh < 0) { + } else if (pp == &p_wmh) { + if (value < 0) { errmsg = e_positive; - p_wmh = 0; - } - if (p_wmh > p_wh) { + } else if (value > p_wh) { errmsg = e_winheight; - p_wmh = p_wh; } - win_setminheight(); } else if (pp == &p_wiw) { - if (p_wiw < 1) { + if (value < 1) { errmsg = e_positive; - p_wiw = 1; + } else if (p_wmw > value) { + errmsg = e_winwidth; } - if (p_wmw > p_wiw) { + } else if (pp == &p_wmw) { + if (value < 0) { + errmsg = e_positive; + } else if (value > p_wiw) { errmsg = e_winwidth; - p_wiw = p_wmw; } - - /* Change window width NOW */ - if (!ONE_WINDOW && curwin->w_width < p_wiw) - win_setwidth((int)p_wiw); - } - /* 'winminwidth' */ - else if (pp == &p_wmw) { - if (p_wmw < 0) { + } else if (pp == &p_mco) { + if (value > MAX_MCO) { + errmsg = e_invarg; + } else if (value < 0) { errmsg = e_positive; - p_wmw = 0; } - if (p_wmw > p_wiw) { - errmsg = e_winwidth; - p_wmw = p_wiw; + } else if (pp == &p_titlelen) { + if (value < 0) { + errmsg = e_positive; } - win_setminheight(); - } else if (pp == &p_ls) { - /* (re)set last window status line */ - last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ - } - /* 'foldlevel' */ - else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) - curwin->w_p_fdl = 0; - newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { - foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) - foldUpdateAll(curwin); - } - /* 'foldcolumn' */ - else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { + } else if (pp == &p_uc) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_fdc = 0; - } else if (curwin->w_p_fdc > 12) { - errmsg = e_invarg; - curwin->w_p_fdc = 12; } - // 'shiftwidth' or 'tabstop' - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); + } else if (pp == &p_ch) { + if (value < 1) { + errmsg = e_positive; } - // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: - // parse 'cinoptions'. - if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { - parse_cino(curbuf); + } else if (pp == &p_tm) { + if (value < 0) { + errmsg = e_positive; } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - if (p_mco > MAX_MCO) - p_mco = MAX_MCO; - else if (p_mco < 0) - p_mco = 0; - screenclear(); /* will re-allocate the screen */ - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + } else if (pp == &p_hi) { + if (value < 0) { + errmsg = e_positive; + } else if (value > 10000) { errmsg = e_invarg; - curbuf->b_p_iminsert = B_IMODE_NONE; } - p_iminsert = curbuf->b_p_iminsert; - showmode(); - /* Show/unshow value of 'keymap' in status lines. */ - status_redraw_curbuf(); - } else if (pp == &p_window) { - if (p_window < 1) - p_window = 1; - else if (p_window >= Rows) - p_window = Rows - 1; - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + } else if (pp == &p_re) { + if (value < 0 || value > 2) { errmsg = e_invarg; - curbuf->b_p_imsearch = B_IMODE_NONE; } - p_imsearch = curbuf->b_p_imsearch; - } else if (pp == &p_channel || pp == &curbuf->b_p_channel) { - errmsg = e_invarg; - *pp = old_value; - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { - if (p_titlelen < 0) { + } else if (pp == &p_report) { + if (value < 0) { errmsg = e_positive; - p_titlelen = 85; } - if (starting != NO_SCREEN && old_value != p_titlelen) - need_maketitle = TRUE; - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - if (p_ch < 1) { + } else if (pp == &p_titlelen) { + if (value < 0) { errmsg = e_positive; - p_ch = 1; } - if (p_ch > Rows - min_rows() + 1) - p_ch = Rows - min_rows() + 1; - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) - command_height(); - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { - if (p_uc < 0) { + } else if (pp == &p_so) { + if (value < 0 && full_screen) { + errmsg = e_scroll; + } + } else if (pp == &p_siso) { + if (value < 0 && full_screen) { errmsg = e_positive; - p_uc = 100; } - if (p_uc && !old_value) - ml_open_files(); - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { + } else if (pp == &p_cwh) { + if (value < 1) { + errmsg = e_positive; + } + } else if (pp == &p_ut) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &p_ss) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdl + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdl)) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdc)) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_cole = 0; - } else if (curwin->w_p_cole > 3) { + } else if (value > 12) { errmsg = e_invarg; - curwin->w_p_cole = 3; - } - } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; - u_sync(TRUE); - curbuf->b_p_ul = value; - } - /* 'numberwidth' must be positive */ - else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { + } + } else if (pp == &curwin->w_p_cole + || pp == (long *)GLOBAL_WO(&curwin->w_p_cole)) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_nuw = 1; + } else if (value > 3) { + errmsg = e_invarg; } - if (curwin->w_p_nuw > 10) { + } else if (pp == &curwin->w_p_nuw + || pp == (long *)GLOBAL_WO(&curwin->w_p_nuw)) { + if (value < 1) { + errmsg = e_positive; + } else if (value > 10) { errmsg = e_invarg; - curwin->w_p_nuw = 10; } - curwin->w_nrwidth_line_count = 0; - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { + } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { + if (value < 0 || value > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { + if (value < -1 || value > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_channel || pp == &p_channel) { + errmsg = e_invarg; + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (value < -1 || value > SB_MAX + || (value != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { + if (value < 1) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (value < 0) { errmsg = e_positive; - curbuf->b_p_tw = 0; } + } + // Don't change the value and return early if validation failed. + if (errmsg != NULL) { + return (char *)errmsg; + } + + *pp = value; + // Remember where the option was set. + set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + + // For these options we want to fix some invalid values. + if (pp == &p_window) { + if (p_window < 1) { + p_window = Rows - 1; + } else if (p_window >= Rows) { + p_window = Rows - 1; + } + } else if (pp == &p_ch) { + if (p_ch > Rows - min_rows() + 1) { + p_ch = Rows - min_rows() + 1; + } + } + + // Number options that need some action when changed + if (pp == &p_wh) { + if (!ONE_WINDOW && curwin->w_height < p_wh) { + win_setheight((int)p_wh); + } + } else if (pp == &p_hh) { + if (!ONE_WINDOW && curbuf->b_help && curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + } else if (pp == &p_wmh) { + win_setminheight(); + } else if (pp == &p_wiw) { + if (!ONE_WINDOW && curwin->w_width < p_wiw) { + win_setwidth((int)p_wiw); + } + } else if (pp == &p_ls) { + last_status(false); // (re)set last window status line. + } else if (pp == &p_stal) { + // (re)set tab page line + shell_new_rows(); // recompute window positions and heights + } else if (pp == &curwin->w_p_fdl) { + newFoldLevel(); + } else if (pp == &curwin->w_p_fml) { + foldUpdateAll(curwin); + } else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) { + // 'shiftwidth' or 'tabstop' + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: + // parse 'cinoptions'. + if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { + parse_cino(curbuf); + } + } else if (pp == &p_mco) { + screenclear(); // will re-allocate the screen + } else if (pp == &curbuf->b_p_iminsert) { + showmode(); + // Show/unshow value of 'keymap' in status lines. + status_redraw_curbuf(); + } else if (pp == &p_titlelen) { + // if 'titlelen' has changed, redraw the title + if (starting != NO_SCREEN && old_value != p_titlelen) { + need_maketitle = true; + } + } else if (pp == &p_ch) { + // if p_ch changed value, change the command line height + // Only compute the new window layout when startup has been + // completed. Otherwise the frame sizes may be wrong. + if (p_ch != old_value && full_screen) { + command_height(); + } + } else if (pp == &p_uc) { + // when 'updatecount' changes from zero to non-zero, open swap files + if (p_uc && !old_value) { + ml_open_files(); + } + } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { + // sync undo before 'undolevels' changes + // use the old value, otherwise u_sync() may not work properly + *pp = old_value; + u_sync(true); + *pp = value; + } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { check_colorcolumn(wp); } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - // 'scrollback' - if (*pp < -1 || *pp > SB_MAX - || (*pp != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { - errmsg = e_invarg; - *pp = old_value; - } else if (curbuf->terminal) { + if (curbuf->terminal) { // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); } + } else if (pp == &curwin->w_p_nuw) { + curwin->w_nrwidth_line_count = 0; } - /* - * Check the bounds for numeric options here - */ + + // Check the (new) bounds for Rows and Columns here. if (Rows < min_rows() && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, @@ -4340,19 +4362,17 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, limit_screen_size(); - /* - * If the screen (shell) height has been changed, assume it is the - * physical screenheight. - */ + // If the screen (shell) height has been changed, assume it is the + // physical screenheight. if (old_Rows != Rows || old_Columns != Columns) { - /* Changing the screen size is not allowed while updating the screen. */ + // Changing the screen size is not allowed while updating the screen. if (updating_screen) { *pp = old_value; } else if (full_screen) { screen_resize((int)Columns, (int)Rows); } else { - /* Postpone the resizing; check the size and cmdline position for - * messages. */ + // Postpone the resizing; check the size and cmdline position for + // messages. check_shellsize(); if (cmdline_row > Rows - p_ch && Rows > p_ch) { assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); @@ -4364,14 +4384,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - curbuf->b_p_ts = 8; - } - if (p_tm < 0) { - errmsg = e_positive; - p_tm = 0; - } if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0)) @@ -4388,21 +4400,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, else /* curwin->w_p_scr > curwin->w_height */ curwin->w_p_scr = curwin->w_height; } - if (p_hi < 0) { - errmsg = e_positive; - p_hi = 0; - } else if (p_hi > 10000) { - errmsg = e_invarg; - p_hi = 10000; - } - if (p_re < 0 || p_re > 2) { - errmsg = e_invarg; - p_re = 0; - } - if (p_report < 0) { - errmsg = e_positive; - p_report = 1; - } if ((p_sj < -100 || p_sj >= Rows) && full_screen) { if (Rows != old_Rows) /* Rows changed, just adjust p_sj */ p_sj = Rows / 2; @@ -4411,30 +4408,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_sj = 1; } } - if (p_so < 0 && full_screen) { - errmsg = e_scroll; - p_so = 0; - } - if (p_siso < 0 && full_screen) { - errmsg = e_positive; - p_siso = 0; - } - if (p_cwh < 1) { - errmsg = e_positive; - p_cwh = 1; - } - if (p_ut < 0) { - errmsg = e_positive; - p_ut = 2000; - } - if (p_ss < 0) { - errmsg = e_positive; - p_ss = 0; - } - /* May set global value for local option. */ - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; + } if (pp == &curbuf->b_p_scbk && !curbuf->terminal) { // Normal buffer: reset local 'scrollback' after updating the global value. diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index e2e98f251e..66a49fd6e0 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -394,11 +394,13 @@ EXTERN char_u *p_dir; /* 'directory' */ EXTERN char_u *p_dy; /* 'display' */ EXTERN unsigned dy_flags; #ifdef IN_OPTION_C -static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", NULL }; +static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", + NULL }; #endif #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 +#define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' EXTERN int p_emoji; // 'emoji' EXTERN char_u *p_ead; // 'eadirection' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index d653147943..80484d0ad2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -642,7 +642,7 @@ return { vim=true, redraw={'all_windows'}, varname='p_dy', - defaults={if_true={vi="", vim="lastline"}} + defaults={if_true={vi="", vim="lastline,msgsep"}} }, { full_name='eadirection', abbreviation='ead', diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index e23ba8a4ee..a67e7487eb 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -12,12 +12,16 @@ # include <tlhelp32.h> // for CreateToolhelp32Snapshot #endif -#if defined(__FreeBSD__) // XXX: OpenBSD, NetBSD ? +#if defined(__FreeBSD__) // XXX: OpenBSD ? # include <string.h> # include <sys/types.h> # include <sys/user.h> #endif +#if defined(__NetBSD__) || defined(__OpenBSD__) +# include <sys/param.h> +#endif + #if defined(__APPLE__) || defined(BSD) # include <sys/sysctl.h> # include <pwd.h> @@ -155,7 +159,13 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) # define KP_PID(o) o.p_pid # define KP_PPID(o) o.p_ppid # endif +# ifdef __NetBSD__ + static int name[] = { + CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0 + }; +# else static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; +# endif // Get total process count. size_t len = 0; @@ -165,7 +175,11 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) } // Get ALL processes. +# ifdef __NetBSD__ + struct kinfo_proc2 *p_list = xmalloc(len); +# else struct kinfo_proc *p_list = xmalloc(len); +# endif rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0); if (rv) { xfree(p_list); diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index f650a51fe7..04f59d7522 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -136,7 +136,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) xfree(input.data); if (output) { - (void)write_output(output, nread, true, true); + (void)write_output(output, nread, true); xfree(output); } @@ -388,10 +388,10 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick == 0 || 1 == tick) ? ' ' : '.'; pulse_msg[2] = (tick == 0 || 1 == tick || 2 == tick) ? ' ' : '.'; if (visit == 1) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + msg_putchar('\n'); } - int lastrow = (int)Rows - 1; - screen_puts_len((char_u *)pulse_msg, ARRAY_SIZE(pulse_msg), lastrow, 0, 0); + msg_putchar('\r'); // put cursor at start of line + msg_puts(pulse_msg); ui_flush(); return true; } @@ -609,28 +609,20 @@ static void read_input(DynamicBuffer *buf) } } -static size_t write_output(char *output, size_t remaining, bool to_buffer, - bool eof) +static size_t write_output(char *output, size_t remaining, bool eof) { if (!output) { return 0; } - char replacement_NUL = to_buffer ? NL : 1; char *start = output; size_t off = 0; - int lastrow = (int)Rows - 1; while (off < remaining) { if (output[off] == NL) { // Insert the line - if (to_buffer) { - output[off] = NUL; - ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, - false); - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)off, lastrow, 0, 0); - } + output[off] = NUL; + ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, + false); size_t skip = off + 1; output += skip; remaining -= skip; @@ -640,24 +632,19 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, if (output[off] == NUL) { // Translate NUL to NL - output[off] = replacement_NUL; + output[off] = NL; } off++; } if (eof) { if (remaining) { - if (to_buffer) { - // append unfinished line - ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); - // remember that the NL was missing - curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)remaining, lastrow, 0, 0); - } + // append unfinished line + ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); + // remember that the NL was missing + curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; output += remaining; - } else if (to_buffer) { + } else { curbuf->b_no_eol_lnum = 0; } } diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index a41fb7c621..866a005228 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -88,7 +88,7 @@ char *stdpaths_get_xdg_var(const XDGVarType idx) /// /// In WIN32 get_xdg_home(kXDGDataHome) returns `{xdg_directory}/nvim-data` to /// avoid storing configuration and data files in the same path. -static char *get_xdg_home(const XDGVarType idx) +char *get_xdg_home(const XDGVarType idx) FUNC_ATTR_WARN_UNUSED_RESULT { char *dir = stdpaths_get_xdg_var(idx); diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 121f22129a..94cc63baea 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -2,10 +2,8 @@ find_package(Gettext) find_program(XGETTEXT_PRG xgettext) find_program(ICONV_PRG iconv) -if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) - set(ENV{OLD_PO_FILE_INPUT} yes) - set(ENV{OLD_PO_FILE_OUTPUT} yes) - +option(LANGUAGES "Localizations to build") +if(NOT LANGUAGES) set(LANGUAGES af ca @@ -31,6 +29,12 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) vi zh_CN.UTF-8 zh_TW.UTF-8) +endif() + +if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) + set(ENV{OLD_PO_FILE_INPUT} yes) + set(ENV{OLD_PO_FILE_OUTPUT} yes) + set(NVIM_RELATIVE_SOURCES) foreach(SRC ${NVIM_SOURCES} ${NVIM_HEADERS}) @@ -135,22 +139,30 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) endmacro() # Create some translations from others. - BuildPoIconv(ja utf-8 euc-jp) - BuildMo(ja.euc-jp) + if(";${LANGUAGES};" MATCHES ";ja;") + BuildPoIconv(ja utf-8 euc-jp) + BuildMo(ja.euc-jp) + endif() - BuildPoIconv(cs ISO-8859-2 cp1250) - BuildMo(cs.cp1250) + if(";${LANGUAGES};" MATCHES ";cs;") + BuildPoIconv(cs ISO-8859-2 cp1250) + BuildMo(cs.cp1250) + endif() - BuildPoIconv(sk ISO-8859-2 cp1250) - BuildMo(sk.cp1250) + if(";${LANGUAGES};" MATCHES ";sk;") + BuildPoIconv(sk ISO-8859-2 cp1250) + BuildMo(sk.cp1250) + endif() add_custom_target(update-po-nb COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/no.po ${CMAKE_CURRENT_SOURCE_DIR}/nb.po DEPENDS no.po) list(APPEND UPDATE_PO_TARGETS update-po-nb) - CheckPo(nb) - BuildMo(nb) + if(";${LANGUAGES};" MATCHES ";no;") + CheckPo(nb) + BuildMo(nb) + endif() foreach(LANGUAGE ${LANGUAGES}) set(poFile "${CMAKE_CURRENT_SOURCE_DIR}/${LANGUAGE}.po") diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 2d8c353f92..46124e0672 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3029,7 +3029,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) /* * Return TRUE if "buf" is the quickfix buffer. */ -int bt_quickfix(buf_T *buf) +int bt_quickfix(const buf_T *const buf) { return buf != NULL && buf->b_p_bt[0] == 'q'; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 22de08041a..731bb2658a 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -300,13 +300,25 @@ void update_screen(int type) * if the screen was scrolled up when displaying a message, scroll it down */ if (msg_scrolled) { - clear_cmdline = TRUE; - if (msg_scrolled > Rows - 5) /* clearing is faster */ + clear_cmdline = true; + if (dy_flags & DY_MSGSEP) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + int valid = Rows - msg_scrollsize(); + if (wp->w_winrow + wp->w_height > valid) { + wp->w_redr_type = NOT_VALID; + wp->w_lines_valid = 0; + } + if (wp->w_winrow + wp->w_height + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } + } else if (msg_scrolled > Rows - 5) { // clearing is faster type = CLEAR; - else if (type != CLEAR) { - check_for_delay(FALSE); - if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) + } else if (type != CLEAR) { + check_for_delay(false); + if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) { type = CLEAR; + } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_winrow < msg_scrolled) { if (wp->w_winrow + wp->w_height > msg_scrolled @@ -2032,7 +2044,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width, - wp->w_width, false, wp); + wp->w_width, false, wp, 0); /* * Update w_cline_height and w_cline_folded if the cursor line was @@ -2453,10 +2465,6 @@ win_line ( line_attr = win_hl_attr(wp, HLF_QFL); } - if (wp->w_hl_attr_normal != 0) { - line_attr = hl_combine_attr(wp->w_hl_attr_normal, line_attr); - } - if (line_attr != 0) { area_highlighting = true; } @@ -2914,7 +2922,8 @@ win_line ( && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol && filler_todo <= 0 ) { - screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. if (wp->w_p_cuc) { @@ -4007,7 +4016,8 @@ win_line ( col++; } } - screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); row++; /* @@ -4230,7 +4240,7 @@ win_line ( || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { screen_line(screen_row, wp->w_wincol, col - boguscols, - wp->w_width, wp->w_p_rl, wp); + wp->w_width, wp->w_p_rl, wp, wp->w_hl_attr_normal); boguscols = 0; ++row; ++screen_row; @@ -4400,7 +4410,7 @@ static int char_needs_redraw(int off_from, int off_to, int cols) * When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" */ static void screen_line(int row, int coloff, int endcol, - int clear_width, int rlflag, win_T *wp) + int clear_width, int rlflag, win_T *wp, int bg_attr) { unsigned off_from; unsigned off_to; @@ -4433,15 +4443,16 @@ static void screen_line(int row, int coloff, int endcol, /* Clear rest first, because it's left of the text. */ if (clear_width > 0) { while (col <= endcol && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } - if (col <= endcol) - screen_fill(row, row + 1, col + coloff, - endcol + coloff + 1, ' ', ' ', 0); + if (col <= endcol) { + screen_fill(row, row + 1, col + coloff, endcol + coloff + 1, ' ', ' ', + bg_attr); + } } col = endcol + 1; off_to = LineOffset[row] + col + coloff; @@ -4449,6 +4460,13 @@ static void screen_line(int row, int coloff, int endcol, endcol = (clear_width > 0 ? clear_width : -clear_width); } + if (bg_attr) { + for (int c = col; c < endcol; c++) { + ScreenAttrs[off_from+c] = hl_combine_attr(bg_attr, + ScreenAttrs[off_from+c]); + } + } + redraw_next = char_needs_redraw(off_from, off_to, endcol - col); while (col < endcol) { @@ -4547,15 +4565,15 @@ static void screen_line(int row, int coloff, int endcol, /* blank out the rest of the line */ while (col < clear_width && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } if (col < clear_width) { - screen_fill(row, row + 1, col + coloff, clear_width + coloff, - ' ', ' ', 0); + screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ', + bg_attr); off_to += clear_width - col; col = clear_width; } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 605d9c30a6..15ac28e1bf 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -16,12 +16,14 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/vim.h" +#include "nvim/pos.h" #include "nvim/ascii.h" #include "nvim/shada.h" #include "nvim/message.h" #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/mark.h" +#include "nvim/macros.h" #include "nvim/ops.h" #include "nvim/garray.h" #include "nvim/option.h" @@ -375,7 +377,8 @@ KHASH_MAP_INIT_STR(file_marks, FileMarks) /// Before actually writing most of the data is read to this structure. typedef struct { HistoryMergerState hms[HIST_COUNT]; ///< Structures for history merging. - PossiblyFreedShadaEntry global_marks[NGLOBALMARKS]; ///< All global marks. + PossiblyFreedShadaEntry global_marks[NMARKS]; ///< Named global marks. + PossiblyFreedShadaEntry numbered_marks[EXTRA_MARKS]; ///< Numbered marks. PossiblyFreedShadaEntry registers[NUM_SAVED_REGISTERS]; ///< All registers. PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps. size_t jumps_size; ///< Number of jumps occupied. @@ -2020,6 +2023,113 @@ shada_parse_msgpack_extra_bytes: return ret; } +/// Format shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_entry(const ShadaEntry entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + static char ret[1024]; + ret[0] = 0; + vim_snprintf(S_LEN(ret), "[ ] ts=%" PRIu64 " "); + // ^ Space for `can_free_entry` + switch (entry.type) { + case kSDItemMissing: { + vim_snprintf_add(S_LEN(ret), "Missing"); + break; + } + case kSDItemHeader: { + vim_snprintf_add(S_LEN(ret), "Header { TODO }"); + break; + } + case kSDItemBufferList: { + vim_snprintf_add(S_LEN(ret), "BufferList { TODO }"); + break; + } + case kSDItemUnknown: { + vim_snprintf_add(S_LEN(ret), "Unknown { TODO }"); + break; + } + case kSDItemSearchPattern: { + vim_snprintf_add(S_LEN(ret), "SearchPattern { TODO }"); + break; + } + case kSDItemSubString: { + vim_snprintf_add(S_LEN(ret), "SubString { TODO }"); + break; + } + case kSDItemHistoryEntry: { + vim_snprintf_add(S_LEN(ret), "HistoryEntry { TODO }"); + break; + } + case kSDItemRegister: { + vim_snprintf_add(S_LEN(ret), "Register { TODO }"); + break; + } + case kSDItemVariable: { + vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); + break; + } +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add( \ + S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) + case kSDItemGlobalMark: { + FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemChange: { + FORMAT_MARK_ENTRY("Change", "%s", ""); + break; + } + case kSDItemLocalMark: { + FORMAT_MARK_ENTRY("LocalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemJump: { + FORMAT_MARK_ENTRY("Jump", "%s", ""); + break; + } +#undef FORMAT_MARK_ENTRY + } + return ret; +} + +/// Format possibly freed shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_pfreed_entry( + const PossiblyFreedShadaEntry pfs_entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + char *ret = (char *)shada_format_entry(pfs_entry.data); + ret[1] = (pfs_entry.can_free_entry ? 'T' : 'F'); + return ret; +} + /// Read and merge in ShaDa file, used when writing /// /// @param[in] sd_reader Structure containing file reader definition. @@ -2071,9 +2181,12 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); \ } \ } \ - wms_entry->can_free_entry = true; \ - wms_entry->data = (entry); \ + *wms_entry = pfs_entry; \ } while (0) + const PossiblyFreedShadaEntry pfs_entry = { + .can_free_entry = true, + .data = entry, + }; switch (entry.type) { case kSDItemMissing: { break; @@ -2125,13 +2238,49 @@ static inline ShaDaWriteResult shada_read_when_writing( break; } case kSDItemGlobalMark: { - const int idx = mark_global_index(entry.data.filemark.name); - if (idx < 0) { - ret = shada_pack_entry(packer, entry, 0); - shada_free_shada_entry(&entry); - break; + if (ascii_isdigit(entry.data.filemark.name)) { + bool processed_mark = false; + // Completely ignore numbered mark names, make a list sorted by + // timestamp. + for (size_t i = ARRAY_SIZE(wms->numbered_marks); i > 0; i--) { + ShadaEntry wms_entry = wms->numbered_marks[i - 1].data; + if (wms_entry.type != kSDItemGlobalMark) { + continue; + } + // Ignore duplicates. + if (wms_entry.timestamp == entry.timestamp + && (wms_entry.data.filemark.additional_data == NULL + && entry.data.filemark.additional_data == NULL) + && marks_equal(wms_entry.data.filemark.mark, + entry.data.filemark.mark) + && strcmp(wms_entry.data.filemark.fname, + entry.data.filemark.fname) == 0) { + shada_free_shada_entry(&entry); + processed_mark = true; + break; + } + if (wms_entry.timestamp >= entry.timestamp) { + processed_mark = true; + if (i < ARRAY_SIZE(wms->numbered_marks)) { + replace_numbered_mark(wms, i, pfs_entry); + } else { + shada_free_shada_entry(&entry); + } + break; + } + } + if (!processed_mark) { + replace_numbered_mark(wms, 0, pfs_entry); + } + } else { + const int idx = mark_global_index(entry.data.filemark.name); + if (idx < 0) { + ret = shada_pack_entry(packer, entry, 0); + shada_free_shada_entry(&entry); + break; + } + COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); } - COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); break; } case kSDItemChange: @@ -2175,8 +2324,7 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); } } - wms_entry->can_free_entry = true; - wms_entry->data = entry; + *wms_entry = pfs_entry; } } else { #define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ @@ -2216,6 +2364,20 @@ static inline ShaDaWriteResult shada_read_when_writing( return ret; } +/// Check whether buffer should be ignored +/// +/// @param[in] buf buf_T* to check. +/// @param[in] removable_bufs Cache of buffers ignored due to their location. +/// +/// @return true or false. +static inline bool ignore_buf(const buf_T *const buf, + khash_t(bufset) *const removable_bufs) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ + return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ + || in_bufset(removable_bufs, buf)); +} + /// Get list of buffers to write to the shada file /// /// @param[in] removable_bufs Buffers which are ignored @@ -2227,11 +2389,9 @@ static inline ShadaEntry shada_get_buflist( { int max_bufs = get_shada_parameter('%'); size_t buf_count = 0; -#define IGNORE_BUF(buf)\ - (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ - || in_bufset(removable_bufs, buf)) // NOLINT(whitespace/indent) FOR_ALL_BUFFERS(buf) { - if (!IGNORE_BUF(buf) && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { + if (!ignore_buf(buf, removable_bufs) + && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { buf_count++; } } @@ -2249,7 +2409,7 @@ static inline ShadaEntry shada_get_buflist( }; size_t i = 0; FOR_ALL_BUFFERS(buf) { - if (IGNORE_BUF(buf)) { + if (ignore_buf(buf, removable_bufs)) { continue; } if (i >= buf_count) { @@ -2263,7 +2423,6 @@ static inline ShadaEntry shada_get_buflist( i++; } -#undef IGNORE_BUF return buflist_entry; } @@ -2365,6 +2524,34 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, } while (reg_iter != NULL); } +/// Replace numbered mark in WriteMergerState +/// +/// Frees the last mark, moves (including adjusting mark names) marks from idx +/// to the last-but-one one and saves the new mark at given index. +/// +/// @param[out] wms Merger state to adjust. +/// @param[in] idx Index at which new mark should be placed. +/// @param[in] entry New mark. +static inline void replace_numbered_mark(WriteMergerState *const wms, + const size_t idx, + const PossiblyFreedShadaEntry entry) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (ARRAY_LAST_ENTRY(wms->numbered_marks).can_free_entry) { + shada_free_shada_entry(&ARRAY_LAST_ENTRY(wms->numbered_marks).data); + } + for (size_t i = idx; i < ARRAY_SIZE(wms->numbered_marks) - 1; i++) { + if (wms->numbered_marks[i].data.type == kSDItemGlobalMark) { + wms->numbered_marks[i].data.data.filemark.name = (char)('0' + (int)i + 1); + } + } + memmove(wms->numbered_marks + idx + 1, wms->numbered_marks + idx, + sizeof(wms->numbered_marks[0]) + * (ARRAY_SIZE(wms->numbered_marks) - 1 - idx)); + wms->numbered_marks[idx] = entry; + wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx); +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2597,6 +2784,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize global marks if (dump_global_marks) { const void *global_mark_iter = NULL; + size_t digit_mark_idx = 0; do { char name = NUL; xfmark_T fm; @@ -2619,7 +2807,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } fname = (const char *) buf->b_ffname; } - wms->global_marks[mark_global_index(name)] = (PossiblyFreedShadaEntry) { + const PossiblyFreedShadaEntry pf_entry = { .can_free_entry = false, .data = { .type = kSDItemGlobalMark, @@ -2629,11 +2817,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .mark = fm.fmark.mark, .name = name, .additional_data = fm.fmark.additional_data, - .fname = (char *) fname, + .fname = (char *)fname, } } }, }; + if (ascii_isdigit(name)) { + replace_numbered_mark(wms, digit_mark_idx++, pf_entry); + } else { + wms->global_marks[mark_global_index(name)] = pf_entry; + } } while (global_mark_iter != NULL); } @@ -2715,6 +2908,26 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } + // Update numbered marks: '0' should be replaced with the current position, + // '9' should be removed and all other marks shifted. + if (!ignore_buf(curbuf, &removable_bufs) && curwin->w_cursor.lnum != 0) { + replace_numbered_mark(wms, 0, (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemGlobalMark, + .timestamp = os_time(), + .data = { + .filemark = { + .mark = curwin->w_cursor, + .name = '0', + .additional_data = NULL, + .fname = (char *)curbuf->b_ffname, + } + } + }, + }); + } + // Write the rest #define PACK_WMS_ARRAY(wms_array) \ do { \ @@ -2729,6 +2942,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } \ } while (0) PACK_WMS_ARRAY(wms->global_marks); + PACK_WMS_ARRAY(wms->numbered_marks); PACK_WMS_ARRAY(wms->registers); for (size_t i = 0; i < wms->jumps_size; i++) { if (shada_pack_pfreed_entry(packer, wms->jumps[i], max_kbyte) @@ -2823,6 +3037,7 @@ shada_write_exit: return ret; } +#undef IGNORE_BUF #undef PACK_STATIC_STR /// Write ShaDa file to a given location diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 8ec393e568..2613c09c19 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5999,6 +5999,7 @@ static const char *highlight_init_both[] = { "default link QuickFixLine Search", "default link Substitute Search", "default link Whitespace NonText", + "default link MsgSeparator StatusLine", NULL }; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index a31e1843fc..4bfcbf8e79 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -20,13 +20,13 @@ SCRIPTS_DEFAULT = \ test40.out \ test42.out \ test48.out \ - test49.out \ test52.out \ test64.out \ ifneq ($(OS),Windows_NT) SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ test17.out \ + test49.out \ endif diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6a7b44a76f..5c98455909 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -61,7 +61,7 @@ set nomore lang mess C " Always use forward slashes. -set shellslash +" set shellslash " Prepare for calling test_garbagecollect_now(). let v:testing = 1 @@ -237,6 +237,7 @@ let s:flaky = [ \ 'Test_oneshot()', \ 'Test_out_cb()', \ 'Test_paused()', + \ 'Test_quoteplus()', \ 'Test_reltime()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 7d6dd0c7ce..aac9fefef4 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -17,3 +17,11 @@ let &packpath = &rtp " Make sure $HOME does not get read or written. let $HOME = '/does/not/exist' + +" Use default shell on Windows to avoid segfault, caused by TUI +if has('win32') + let $SHELL = '' + let $TERM = '' + let &shell = empty($COMSPEC) ? exepath('cmd.exe') : $COMSPEC + set shellcmdflag=/s/c shellxquote=\" shellredir=>%s\ 2>&1 +endif diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 673246e1fb..be68e9ff9d 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -154,7 +154,7 @@ func Test_getcompletion() call assert_equal([], l) let l = getcompletion('', 'dir') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, expand('sautest/')) >= 0) let l = getcompletion('NoMatch', 'dir') call assert_equal([], l) @@ -246,7 +246,7 @@ func Test_getcompletion() " Command line completion tests let l = getcompletion('cd ', 'cmdline') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, expand('sautest/')) >= 0) let l = getcompletion('cd NoMatch', 'cmdline') call assert_equal([], l) let l = getcompletion('let v:n', 'cmdline') @@ -288,7 +288,7 @@ func Test_expand_star_star() call mkdir('a/b', 'p') call writefile(['asdfasdf'], 'a/b/fileXname') call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt') - call assert_equal('find a/b/fileXname', getreg(':')) + call assert_equal('find '.expand('a/b/fileXname'), getreg(':')) bwipe! call delete('a', 'rf') endfunc diff --git a/src/nvim/testdir/test_find_complete.vim b/src/nvim/testdir/test_find_complete.vim index 4732109ed0..1019246404 100644 --- a/src/nvim/testdir/test_find_complete.vim +++ b/src/nvim/testdir/test_find_complete.vim @@ -3,6 +3,8 @@ " Do all the tests in a separate window to avoid E211 when we recursively " delete the Xfind directory during cleanup func Test_find_complete() + let shellslash = &shellslash + set shellslash set belloff=all " On windows a stale "Xfind" directory may exist, remove it so that @@ -154,4 +156,5 @@ func Test_find_complete() exe 'cd ' . cwd call delete('Xfind', 'rf') set path& + let &shellslash = shellslash endfunc diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index 06c48d8e76..4d4a902031 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -30,7 +30,7 @@ func Test_help_tagjump() help sp?it call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*:split\*') + call assert_true(getline('.') =~ '\*'.(has('win32') ? 'split()' : ':split').'\*') helpclose help :? diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim index a3d5538a47..6e4c7af821 100644 --- a/src/nvim/testdir/test_makeencoding.vim +++ b/src/nvim/testdir/test_makeencoding.vim @@ -13,12 +13,19 @@ endif let s:script = 'test_makeencoding.py' -let s:message_tbl = { +if has('iconv') + let s:message_tbl = { \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', \ 'latin1': 'ÀÈÌÒÙ', \ 'cp932': 'こんにちは', \ 'cp936': '你好', \} +else + let s:message_tbl = { + \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + \ 'latin1': 'ÀÈÌÒÙ', + \} +endif " Tests for :cgetfile and :lgetfile. diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index a15d15213a..f8c3161b40 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -158,6 +158,8 @@ func Test_set_completion() call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) " Expand directories. + let shellslash = &shellslash + set shellslash call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('./samples/ ', @:) call assert_notmatch('./small.vim ', @:) @@ -168,6 +170,7 @@ func Test_set_completion() call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) + let &shellslash = shellslash endfunc func Test_set_errors() diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index dd177fd633..85f93cf3da 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -850,17 +850,17 @@ func s:dir_stack_tests(cchar) let qf = g:Xgetlist() - call assert_equal('dir1/a/habits2.txt', bufname(qf[1].bufnr)) + call assert_equal(expand('dir1/a/habits2.txt'), bufname(qf[1].bufnr)) call assert_equal(1, qf[1].lnum) - call assert_equal('dir1/a/b/habits3.txt', bufname(qf[3].bufnr)) + call assert_equal(expand('dir1/a/b/habits3.txt'), bufname(qf[3].bufnr)) call assert_equal(2, qf[3].lnum) - call assert_equal('dir1/a/habits2.txt', bufname(qf[4].bufnr)) + call assert_equal(expand('dir1/a/habits2.txt'), bufname(qf[4].bufnr)) call assert_equal(7, qf[4].lnum) - call assert_equal('dir1/c/habits4.txt', bufname(qf[6].bufnr)) + call assert_equal(expand('dir1/c/habits4.txt'), bufname(qf[6].bufnr)) call assert_equal(3, qf[6].lnum) call assert_equal('habits1.txt', bufname(qf[9].bufnr)) call assert_equal(4, qf[9].lnum) - call assert_equal('dir2/habits5.txt', bufname(qf[11].bufnr)) + call assert_equal(expand('dir2/habits5.txt'), bufname(qf[11].bufnr)) call assert_equal(5, qf[11].lnum) let &efm=save_efm @@ -1065,7 +1065,7 @@ func Test_efm2() call assert_equal(8, len(l)) call assert_equal(89, l[4].lnum) call assert_equal(1, l[4].valid) - call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + call assert_equal(expand('unittests/dbfacadeTest.py'), bufname(l[4].bufnr)) " The following sequence of commands used to crash Vim set efm=%W%m @@ -1609,11 +1609,11 @@ func Test_two_windows() laddexpr 'one.txt:3:one one one' let loc_one = getloclist(one_id) - call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr)) + call assert_equal(expand('Xone/a/one.txt'), bufname(loc_one[1].bufnr)) call assert_equal(3, loc_one[1].lnum) let loc_two = getloclist(two_id) - call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr)) + call assert_equal(expand('Xtwo/a/two.txt'), bufname(loc_two[1].bufnr)) call assert_equal(5, loc_two[1].lnum) call win_gotoid(one_id) diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim index 46d884a97c..beecb4cd0d 100644 --- a/src/nvim/testdir/test_recover.vim +++ b/src/nvim/testdir/test_recover.vim @@ -6,11 +6,6 @@ func Test_recover_root_dir() set dir=/ call assert_fails('recover', 'E305:') close! - - if has('win32') || filewritable('/') == 2 - " can write in / directory on MS-Windows - set dir=/notexist/ - endif call assert_fails('split Xtest', 'E303:') set dir& endfunc diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index 1239fe9427..0a09130b0c 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -86,7 +86,7 @@ func Test_win32_symlink_dir() let res = system('dir C:\Users /a') if match(res, '\C<SYMLINKD> *All Users') >= 0 " Get the filetype of the symlink. - call assert_equal('dir', getftype('C:\Users\All Users')) + call assert_equal('link', getftype('C:\Users\All Users')) endif endif endfunc diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index ce9d110d82..d3c0594c03 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -5,14 +5,12 @@ function! Test_System() return endif let out = system('echo 123') - " On Windows we may get a trailing space. - if out != "123 \n" - call assert_equal("123\n", out) - endif + call assert_equal("123\n", out) let out = systemlist('echo 123') - " On Windows we may get a trailing space and CR. - if out != ["123 \r"] + if &shell =~# 'cmd.exe$' + call assert_equal(["123\r"], out) + else call assert_equal(['123'], out) endif diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index d377062780..81ac2b6171 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -119,7 +119,7 @@ func Test_paused() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call assert_inrange(0, 60, slept) + call assert_inrange(0, 100, slept) else call assert_inrange(0, 10, slept) endif diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bfebe0442d..72a25b0b59 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -450,7 +450,8 @@ static void update_attrs(UI *ui, HlAttrs attrs) int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; bool bold = attr & HL_BOLD; bool italic = attr & HL_ITALIC; - bool reverse = attr & (HL_INVERSE | HL_STANDOUT); + bool reverse = attr & HL_INVERSE; + bool standout = attr & HL_STANDOUT; bool underline = attr & (HL_UNDERLINE), undercurl = attr & (HL_UNDERCURL); if (unibi_get_str(data->ut, unibi_set_attributes)) { @@ -478,6 +479,9 @@ static void update_attrs(UI *ui, HlAttrs attrs) if (underline || undercurl) { unibi_out(ui, unibi_enter_underline_mode); } + if (standout) { + unibi_out(ui, unibi_enter_standout_mode); + } if (reverse) { unibi_out(ui, unibi_enter_reverse_mode); } @@ -826,7 +830,7 @@ static void tui_cursor_goto(UI *ui, Integer row, Integer col) CursorShape tui_cursor_decode_shape(const char *shape_str) { - CursorShape shape = 0; + CursorShape shape; if (strequal(shape_str, "block")) { shape = SHAPE_BLOCK; } else if (strequal(shape_str, "vertical")) { @@ -834,7 +838,8 @@ CursorShape tui_cursor_decode_shape(const char *shape_str) } else if (strequal(shape_str, "horizontal")) { shape = SHAPE_HOR; } else { - EMSG2(_(e_invarg2), shape_str); + WLOG("Unknown shape value '%s'", shape_str); + shape = SHAPE_BLOCK; } return shape; } @@ -920,7 +925,6 @@ static void tui_set_mode(UI *ui, ModeShape mode) } TUIData *data = ui->data; cursorentry_T c = data->cursor_shapes[mode]; - int shape = c.shape; if (c.id != 0 && ui->rgb) { int attr = syn_id2attr(c.id); @@ -931,11 +935,12 @@ static void tui_set_mode(UI *ui, ModeShape mode) } } - switch (shape) { + int shape; + switch (c.shape) { + default: abort(); break; case SHAPE_BLOCK: shape = 1; break; case SHAPE_HOR: shape = 3; break; case SHAPE_VER: shape = 5; break; - default: WLOG("Unknown shape value %d", shape); break; } UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); unibi_out_ext(ui, data->unibi_ext.set_cursor_style); diff --git a/src/nvim/ui.c b/src/nvim/ui.c index c70a02d960..42366fdb76 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -186,6 +186,10 @@ Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) PUT(hl, "bold", BOOLEAN_OBJ(true)); } + if (mask & HL_STANDOUT) { + PUT(hl, "standout", BOOLEAN_OBJ(true)); + } + if (mask & HL_UNDERLINE) { PUT(hl, "underline", BOOLEAN_OBJ(true)); } @@ -198,7 +202,7 @@ Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) PUT(hl, "italic", BOOLEAN_OBJ(true)); } - if (mask & (HL_INVERSE | HL_STANDOUT)) { + if (mask & HL_INVERSE) { PUT(hl, "reverse", BOOLEAN_OBJ(true)); } diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 4196ecb9d2..ee59dc8c96 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -779,7 +779,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, eltkn_opt_scope_tab[token.data.opt.scope], (int)token.data.opt.len, token.data.opt.name) TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", - intchar2str(token.data.var.scope), (int)token.data.var.autoload) + intchar2str((int)token.data.var.scope), + (int)token.data.var.autoload) TKNARGS(kExprLexNumber, "(is_float=%i,base=%i,val=%lg)", (int)token.data.num.is_float, (int)token.data.num.base, diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 2297a0760f..fed53a3dfd 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -99,5 +99,14 @@ describe('highlight api',function() eq(false, err) eq('Invalid highlight name: ', string.match(emsg, 'Invalid.*')) + + -- Test "standout" attribute. #8054 + eq({ underline = true, }, + meths.get_hl_by_name('cursorline', 0)); + command('hi CursorLine cterm=standout,underline term=standout,underline gui=standout,underline') + command('set cursorline') + eq({ underline = true, standout = true, }, + meths.get_hl_by_name('cursorline', 0)); + end) end) diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua new file mode 100644 index 0000000000..b028a50b02 --- /dev/null +++ b/test/functional/api/ui_spec.lua @@ -0,0 +1,37 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local expect_err = helpers.expect_err +local meths = helpers.meths +local request = helpers.request + +describe('nvim_ui_attach()', function() + before_each(function() + clear() + end) + it('handles very large width/height #2180', function() + local screen = Screen.new(999, 999) + screen:attach() + eq(999, eval('&lines')) + eq(999, eval('&columns')) + end) + it('invalid option returns error', function() + expect_err('No such UI option: foo', + meths.ui_attach, 80, 24, { foo={'foo'} }) + end) + it('validates channel arg', function() + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_try_resize', 40, 10) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_set_option', 'rgb', true) + expect_err('UI not attached to channel: 1', + request, 'nvim_ui_detach') + + local screen = Screen.new() + screen:attach({rgb=false}) + expect_err('UI already attached to channel: 1', + request, 'nvim_ui_attach', 40, 10, { rgb=false }) + end) +end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index bd56161a54..718294d941 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -73,6 +73,8 @@ describe('api', function() it('captures command output', function() eq('this is\nspinal tap', nvim('command_output', [[echo "this is\nspinal tap"]])) + eq('no line ending!', + nvim('command_output', [[echon "no line ending!"]])) end) it('captures empty command output', function() @@ -579,7 +581,8 @@ describe('api', function() screen:set_default_attr_ids({ [0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {foreground = Screen.colors.White, background = Screen.colors.Red}, - [2] = {bold = true, foreground = Screen.colors.SeaGreen} + [2] = {bold = true, foreground = Screen.colors.SeaGreen}, + [3] = {bold = true, reverse = true}, }) end) @@ -600,11 +603,11 @@ describe('api', function() it('shows return prompt when more than &cmdheight lines', function() nvim_async('err_write', 'something happened\nvery bad\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:something happened} | {1:very bad} | {2:Press ENTER or type command to continue}^ | @@ -614,9 +617,9 @@ describe('api', function() it('shows return prompt after all lines are shown', function() nvim_async('err_write', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n') screen:expect([[ + | {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:FAILURE} | {1:ERROR} | {1:EXCEPTION} | @@ -644,11 +647,11 @@ describe('api', function() -- shows up to &cmdheight lines nvim_async('err_write', 'more fail\ntoo fail\n') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {3: }| {1:more fail} | {1:too fail} | {2:Press ENTER or type command to continue}^ | diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua index 8ea086fb46..3f0504d02f 100644 --- a/test/functional/autocmd/cmdline_spec.lua +++ b/test/functional/autocmd/cmdline_spec.lua @@ -59,24 +59,25 @@ describe('cmdline autocommands', function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true, reverse = true}, }) command("autocmd CmdlineEnter * echoerr 'FAIL'") command("autocmd CmdlineLeave * echoerr 'very error'") feed(':') screen:expect([[ + | {1:~ }| {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| + {4: }| : | {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | :^ | ]]) feed("put ='lorem ipsum'<cr>") screen:expect([[ - {1:~ }| - {1:~ }| + | + {4: }| : | {2:E5500: autocmd has thrown an exception: Vim(echoerr):FAIL} | :put ='lorem ipsum' | diff --git a/test/functional/clipboard/clipboard_provider_spec.lua b/test/functional/clipboard/clipboard_provider_spec.lua index a3ea3b568f..a40c080a6d 100644 --- a/test/functional/clipboard/clipboard_provider_spec.lua +++ b/test/functional/clipboard/clipboard_provider_spec.lua @@ -83,7 +83,14 @@ local function basic_register_test(noblock) end describe('clipboard', function() - before_each(clear) + local screen + + before_each(function() + clear() + screen = Screen.new(72, 4) + screen:attach() + command("set display-=msgsep") + end) it('unnamed register works without provider', function() eq('"', eval('v:register')) @@ -92,8 +99,6 @@ describe('clipboard', function() it('`:redir @+>` with invalid g:clipboard shows exactly one error #7184', function() - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END') screen:expect([[ @@ -106,8 +111,6 @@ describe('clipboard', function() it('`:redir @+>|bogus_cmd|redir END` + invalid g:clipboard must not recurse #7184', function() - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") feed_command('redir @+> | bogus_cmd | redir END') screen:expect([[ @@ -123,8 +126,6 @@ describe('clipboard', function() eq('', eval('provider#clipboard#Executable()')) eq('clipboard: invalid g:clipboard', eval('provider#clipboard#Error()')) - local screen = Screen.new(72, 4) - screen:attach() command("let g:clipboard = 'bogus'") -- Explicit clipboard attempt, should show a hint message. feed_command('let @+="foo"') @@ -493,10 +494,10 @@ describe('clipboard', function() feed_command("let g:test_clip['+'] = ['such', 'plus', 'stuff']") feed_command("registers") screen:expect([[ - ~ | - ~ | - ~ | - ~ | + | + {0:~ }| + {0:~ }| + {4: }| :registers | {1:--- Registers ---} | "* some{2:^J}star data{2:^J} | @@ -504,10 +505,11 @@ describe('clipboard', function() ": let g:test_clip['+'] = ['such', 'plus', 'stuff'] | {3:Press ENTER or type command to continue}^ | ]], { + [0] = {bold = true, foreground = Screen.colors.Blue}, [1] = {bold = true, foreground = Screen.colors.Fuchsia}, [2] = {foreground = Screen.colors.Blue}, - [3] = {bold = true, foreground = Screen.colors.SeaGreen}}, - {{bold = true, foreground = Screen.colors.Blue}}) + [3] = {bold = true, foreground = Screen.colors.SeaGreen}, + [4] = {bold = true, reverse = true}}) feed('<cr>') -- clear out of Press ENTER screen end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 45b739057f..e90339b0cd 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -685,6 +685,21 @@ describe('jobs', function() ok(string.find(err, "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set") ~= nil) end) + it('does not crash when repeatedly failing to start shell', function() + source([[ + set shell=nosuchshell + func! DoIt() + call jobstart('true') + call jobstart('true') + endfunc + ]]) + -- The crash only triggered if both jobs are cleaned up on the same event + -- loop tick. This is also prevented by try-block, so feed must be used. + feed_command("call DoIt()") + feed('<cr>') -- press RETURN + eq(2,eval('1+1')) + end) + it('jobstop() kills entire process tree #6530', function() -- XXX: Using `nvim` isn't a good test, it reaps its children on exit. -- local c = 'call jobstart([v:progpath, "-u", "NONE", "-i", "NONE", "--headless"])' diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua index 183884a51e..925e311c7d 100644 --- a/test/functional/eval/execute_spec.lua +++ b/test/functional/eval/execute_spec.lua @@ -106,16 +106,22 @@ describe('execute()', function() end) it('does not corrupt the command display #5422', function() - local screen = Screen.new(70, 5) + local screen = Screen.new(70, 7) screen:attach() feed(':echo execute("hi ErrorMsg")<CR>') screen:expect([[ - ~ | - ~ | + | + {1:~ }| + {1:~ }| + {2: }| :echo execute("hi ErrorMsg") | ErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red | - Press ENTER or type command to continue^ | - ]]) + {3:Press ENTER or type command to continue}^ | + ]], { + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + }) feed('<CR>') end) diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua index 1e6b107c60..777f49462d 100644 --- a/test/functional/eval/input_spec.lua +++ b/test/functional/eval/input_spec.lua @@ -58,6 +58,7 @@ before_each(function() RBP2={background=Screen.colors.Yellow}, RBP3={background=Screen.colors.Green}, RBP4={background=Screen.colors.Blue}, + SEP={bold = true, reverse = true}, }) end) @@ -65,9 +66,9 @@ describe('input()', function() it('works with multiline prompts', function() feed([[:call input("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Test | Foo^ | ]]) @@ -75,9 +76,9 @@ describe('input()', function() it('works with multiline prompts and :echohl', function() feed([[:echohl Test | call input("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| {T:Test} | {T:Foo}^ | ]]) @@ -242,17 +243,17 @@ describe('input()', function() it('is not hidden by :silent', function() feed([[:silent call input('Foo: ')<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Foo: ^ | | ]]) feed('Bar') screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Foo: Bar^ | | ]]) @@ -263,9 +264,9 @@ describe('inputdialog()', function() it('works with multiline prompts', function() feed([[:call inputdialog("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| Test | Foo^ | ]]) @@ -273,9 +274,9 @@ describe('inputdialog()', function() it('works with multiline prompts and :echohl', function() feed([[:echohl Test | call inputdialog("Test\nFoo")<CR>]]) screen:expect([[ + | {EOB:~ }| - {EOB:~ }| - {EOB:~ }| + {SEP: }| {T:Test} | {T:Foo}^ | ]]) diff --git a/test/functional/eval/server_spec.lua b/test/functional/eval/server_spec.lua index 393616838e..4e4aed864b 100644 --- a/test/functional/eval/server_spec.lua +++ b/test/functional/eval/server_spec.lua @@ -1,31 +1,40 @@ - local helpers = require('test.functional.helpers')(after_each) local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval local command = helpers.command local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths -local os_name = helpers.os_name +local iswin = helpers.iswin +local ok = helpers.ok +local matches = helpers.matches local function clear_serverlist() - for _, server in pairs(funcs.serverlist()) do - funcs.serverstop(server) - end + for _, server in pairs(funcs.serverlist()) do + funcs.serverstop(server) + end end -describe('serverstart(), serverstop()', function() +describe('server', function() before_each(clear) - it('sets $NVIM_LISTEN_ADDRESS on first invocation', function() + it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function() -- Unset $NVIM_LISTEN_ADDRESS command('let $NVIM_LISTEN_ADDRESS = ""') local s = eval('serverstart()') assert(s ~= nil and s:len() > 0, "serverstart() returned empty") eq(s, eval('$NVIM_LISTEN_ADDRESS')) - command("call serverstop('"..s.."')") + eq(1, eval("serverstop('"..s.."')")) eq('', eval('$NVIM_LISTEN_ADDRESS')) end) - it('sets v:servername _only_ on nvim startup unless all servers are stopped', + it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() + clear({env={NVIM_LISTEN_ADDRESS='.'}}) + eq('.', eval('$NVIM_LISTEN_ADDRESS')) + local servers = funcs.serverlist() + eq(1, #servers) + ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\… + end) + + it('sets v:servername at startup or if all servers were stopped', function() local initial_server = meths.get_vvar('servername') assert(initial_server ~= nil and initial_server:len() > 0, @@ -38,24 +47,23 @@ describe('serverstart(), serverstop()', function() neq(initial_server, s) -- serverstop() does _not_ modify v:servername... - funcs.serverstop(s) + eq(1, funcs.serverstop(s)) eq(initial_server, meths.get_vvar('servername')) -- ...unless we stop _all_ servers. - funcs.serverstop(funcs.serverlist()[1]) + eq(1, funcs.serverstop(funcs.serverlist()[1])) eq('', meths.get_vvar('servername')) -- v:servername will take the next available server. - local servername = (os_name() == 'windows' - and [[\\.\pipe\Xtest-functional-server-pipe]] - or 'Xtest-functional-server-socket') + local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] + or 'Xtest-functional-server-socket') funcs.serverstart(servername) eq(servername, meths.get_vvar('servername')) end) - it('serverstop() ignores invalid input', function() - command("call serverstop('')") - command("call serverstop('bogus-socket-name')") + it('serverstop() returns false for invalid input', function() + eq(0, eval("serverstop('')")) + eq(0, eval("serverstop('bogus-socket-name')")) end) it('parses endpoints correctly', function() @@ -96,17 +104,13 @@ describe('serverstart(), serverstop()', function() funcs.serverstart('127.0.0.1:65536') -- invalid port eq({}, funcs.serverlist()) end) -end) - -describe('serverlist()', function() - before_each(clear) - it('returns the list of servers', function() + it('serverlist() returns the list of servers', function() -- There should already be at least one server. local n = eval('len(serverlist())') - -- Add a few - local servs = (os_name() == 'windows' + -- Add some servers. + local servs = (iswin() and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] }) for _, s in ipairs(servs) do @@ -120,9 +124,31 @@ describe('serverlist()', function() -- The new servers should be at the end of the list. for i = 1, #servs do eq(servs[i], new_servs[i + n]) - command("call serverstop('"..servs[i].."')") + eq(1, eval("serverstop('"..servs[i].."')")) end -- After serverstop() the servers should NOT be in the list. eq(n, eval('len(serverlist())')) end) end) + +describe('startup --listen', function() + it('validates', function() + clear() + + local cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen') + matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd)) + + cmd = { unpack(helpers.nvim_argv) } + table.insert(cmd, '--listen2') + matches('nvim.*: Garbage after option argument: "%-%-listen2"', funcs.system(cmd)) + end) + + it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() + local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] + or 'Xtest-listen-pipe') + clear({ env={ NVIM_LISTEN_ADDRESS='Xtest-env-pipe' }, + args={ '--listen', addr } }) + eq(addr, meths.get_vvar('servername')) + end) +end) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 201426c40b..23cea4c038 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -255,10 +255,8 @@ describe('system()', function() end end) it('to backgrounded command does not crash', function() - -- cmd.exe doesn't background a command with & - if iswin() then return end -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system("echo -n echoed &")') + feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "echo -n echoed &")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) @@ -272,10 +270,8 @@ describe('system()', function() eq("input", eval('system("cat -", "input")')) end) it('to backgrounded command does not crash', function() - -- cmd.exe doesn't background a command with & - if iswin() then return end -- This is indeterminate, just exercise the codepath. May get E5677. - feed_command('call system("cat - &", "input")') + feed_command('call system(has("win32") ? "start /b /wait more" : "cat - &", "input")') local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") if v_errnum then eq("E5677:", v_errnum) diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 5b7f0942b1..77d025dcc7 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -27,6 +27,7 @@ describe('mappings with <Cmd>', function() [4] = {bold = true}, [5] = {background = Screen.colors.LightGrey}, [6] = {foreground = Screen.colors.Blue1}, + [7] = {bold = true, reverse = true}, }) screen:attach() @@ -342,11 +343,11 @@ describe('mappings with <Cmd>', function() -- error doesn't interrupt visual mode feed('ggvw<F6>') screen:expect([[ + {5:some }short lines | + of test text | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| + {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | @@ -425,11 +426,11 @@ describe('mappings with <Cmd>', function() -- error doesn't interrupt temporary visual mode feed('<esc>ggvw<c-g><F6>') screen:expect([[ + {5:some }short lines | + of test text | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| + {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | @@ -453,11 +454,11 @@ describe('mappings with <Cmd>', function() -- error doesn't interrupt select mode feed('<esc>ggvw<c-g><F1>') screen:expect([[ + {5:some }short lines | + of test text | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| + {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | @@ -527,11 +528,11 @@ describe('mappings with <Cmd>', function() -- error aborts operator-pending, operator not performed feed('d<F6>') screen:expect([[ + some short lines | + of test text | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| + {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | @@ -571,11 +572,11 @@ describe('mappings with <Cmd>', function() feed('<F6>') screen:expect([[ + indeed some short little lines | + of test text | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| + {7: }| {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | {3:Press ENTER or type command to continue}^ | @@ -675,10 +676,10 @@ describe('mappings with <Cmd>', function() feed(':echo 2<F6>') screen:expect([[ + some short lines | + of test text | {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| + {7: }| :echo 2 | {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | @@ -689,9 +690,9 @@ describe('mappings with <Cmd>', function() eq('c', eval('mode(1)')) feed('+2<cr>') screen:expect([[ - {1:~ }| - {1:~ }| - {1:~ }| + some short lines | + of test text | + {7: }| :echo 2 | {2:Error detected while processing :} | {2:E605: Exception not caught: very error} | diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua index 4002855c24..448326cdfb 100644 --- a/test/functional/ex_cmds/oldfiles_spec.lua +++ b/test/functional/ex_cmds/oldfiles_spec.lua @@ -29,6 +29,7 @@ describe(':oldfiles', function() it('shows most recently used files', function() local screen = Screen.new(100, 5) screen:attach() + feed_command("set display-=msgsep") feed_command('edit testfile1') feed_command('edit testfile2') feed_command('wshada') diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index f2f5a6220c..03c6f3bbaa 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -14,8 +14,10 @@ local check_cores = global_helpers.check_cores local check_logs = global_helpers.check_logs local neq = global_helpers.neq local eq = global_helpers.eq +local expect_err = global_helpers.expect_err local ok = global_helpers.ok local map = global_helpers.map +local matches = global_helpers.matches local filter = global_helpers.filter local dedent = global_helpers.dedent local table_flatten = global_helpers.table_flatten @@ -736,6 +738,7 @@ local module = { exc_exec = exc_exec, expect = expect, expect_any = expect_any, + expect_err = expect_err, expect_msg_seq = expect_msg_seq, expect_twostreams = expect_twostreams, feed = feed, @@ -747,6 +750,7 @@ local module = { insert = insert, iswin = iswin, map = map, + matches = matches, merge_args = merge_args, meth_pcall = meth_pcall, meths = meths, diff --git a/test/functional/insert/insert_spec.lua b/test/functional/insert/insert_spec.lua new file mode 100644 index 0000000000..427954f5a6 --- /dev/null +++ b/test/functional/insert/insert_spec.lua @@ -0,0 +1,41 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local command = helpers.command +local eq = helpers.eq +local expect = helpers.expect +local funcs = helpers.funcs + +describe('insert-mode', function() + before_each(function() + clear() + end) + + it('CTRL-@', function() + -- Inserts last-inserted text, leaves insert-mode. + insert('hello') + feed('i<C-@>x') + expect('hellhello') + + -- C-Space is the same as C-@. + -- CTRL-SPC inserts last-inserted text, leaves insert-mode. + feed('i<C-Space>x') + expect('hellhellhello') + + -- CTRL-A inserts last inserted text + feed('i<C-A>x') + expect('hellhellhellhelloxo') + end) + + it('ALT/META #8213', function() + -- Mapped ALT-chord behaves as mapped. + command('inoremap <M-l> meta-l') + command('inoremap <A-j> alt-j') + feed('i<M-l> xxx <A-j><M-h>a<A-h>') + expect('meta-l xxx alt-j') + eq({ 0, 1, 14, 0, }, funcs.getpos('.')) + -- Unmapped ALT-chord behaves as ESC+c. + command('iunmap <M-l>') + feed('0i<M-l>') + eq({ 0, 1, 2, 0, }, funcs.getpos('.')) + end) +end) diff --git a/test/functional/insert/last_inserted_spec.lua b/test/functional/insert/last_inserted_spec.lua deleted file mode 100644 index dce23a3790..0000000000 --- a/test/functional/insert/last_inserted_spec.lua +++ /dev/null @@ -1,22 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local expect = helpers.expect - -clear() - -describe('insert-mode', function() - it('CTRL-@ inserts last-inserted text, leaves insert-mode', function() - insert('hello') - feed('i<C-@>x') - expect('hellhello') - end) - -- C-Space is the same as C-@ - it('CTRL-SPC inserts last-inserted text, leaves insert-mode', function() - feed('i<C-Space>x') - expect('hellhellhello') - end) - it('CTRL-A inserts last inserted text', function() - feed('i<C-A>x') - expect('hellhellhellhelloxo') - end) -end) diff --git a/test/functional/legacy/077_mf_hash_grow_spec.lua b/test/functional/legacy/077_mf_hash_grow_spec.lua index c692127213..4719a3ecbf 100644 --- a/test/functional/legacy/077_mf_hash_grow_spec.lua +++ b/test/functional/legacy/077_mf_hash_grow_spec.lua @@ -18,7 +18,8 @@ describe('mf_hash_grow()', function() setup(clear) -- Check to see if cksum exists, otherwise skip the test - if os.execute('which cksum 2>&1 > /dev/null') ~= 0 then + local null = helpers.iswin() and 'nul' or '/dev/null' + if os.execute('cksum --help >' .. null .. ' 2>&1') ~= 0 then pending('was not tested because cksum was not found', function() end) else it('is working', function() diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 8ca5fe57ba..007d40874f 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -87,6 +87,7 @@ describe('debug.debug', function() E = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, cr = {bold = true, foreground = Screen.colors.SeaGreen4}, }) + command("set display-=msgsep") end) it('works', function() command([[lua diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 9e29baba2d..f452cafd22 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -5,12 +5,15 @@ local Screen = require('test.functional.ui.screen') local meths = helpers.meths local command = helpers.command local clear = helpers.clear +local exc_exec = helpers.exc_exec local eval = helpers.eval local eq = helpers.eq +local funcs = helpers.funcs local insert = helpers.insert local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir +local alter_slashes = helpers.alter_slashes describe('startup defaults', function() describe(':filetype', function() @@ -422,3 +425,276 @@ describe('XDG-based defaults', function() end) end) end) + + +describe('stdpath()', function() + context('returns a String', function() + describe('with "config"' , function () + it('knows XDG_CONFIG_HOME', function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('/home/docwhat/.config'), + }}) + eq(alter_slashes('/home/docwhat/.config/nvim'), funcs.stdpath('config')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original/nvim'), funcs.stdpath('config')) + command("let $XDG_CONFIG_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new/nvim'), funcs.stdpath('config')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_CONFIG_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('config')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_CONFIG_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('config')) + end) + end) + + describe('with "data"' , function () + local appended_dir + setup(function() + -- Windows appends 'nvim-data' instead of just 'nvim' to + -- prevent collisions due to XDG_CONFIG_HOME and XDG_DATA_HOME + -- being the same. + if helpers.iswin() then + appended_dir = '/nvim-data' + else + appended_dir = '/nvim' + end + end) + + it('knows XDG_DATA_HOME', function() + clear({env={ + XDG_DATA_HOME=alter_slashes('/home/docwhat/.local'), + }}) + eq(alter_slashes('/home/docwhat/.local' .. appended_dir), funcs.stdpath('data')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_DATA_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original' .. appended_dir), funcs.stdpath('data')) + command("let $XDG_DATA_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new' .. appended_dir), funcs.stdpath('data')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_DATA_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES' .. appended_dir), funcs.stdpath('data')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_DATA_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz' .. appended_dir), funcs.stdpath('data')) + end) + end) + + describe('with "cache"' , function () + it('knows XDG_CACHE_HOME', function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('/home/docwhat/.cache'), + }}) + eq(alter_slashes('/home/docwhat/.cache/nvim'), funcs.stdpath('cache')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original/nvim'), funcs.stdpath('cache')) + command("let $XDG_CACHE_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new/nvim'), funcs.stdpath('cache')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_CACHE_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('cache')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_CACHE_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('cache')) + end) + end) + end) + + context('returns a List', function() + -- Some OS specific variables the system would have set. + local function base_env() + if helpers.iswin() then + return { + HOME='C:\\Users\\docwhat', -- technically, is not a usual PATH + HOMEDRIVE='C:', + HOMEPATH='\\Users\\docwhat', + LOCALAPPDATA='C:\\Users\\docwhat\\AppData\\Local', + TEMP='C:\\Users\\docwhat\\AppData\\Local\\Temp', + TMPDIR='C:\\Users\\docwhat\\AppData\\Local\\Temp', + TMP='C:\\Users\\docwhat\\AppData\\Local\\Temp', + } + else + return { + HOME='/home/docwhat', + HOMEDRIVE='HOMEDRIVE-should-be-ignored', + HOMEPATH='HOMEPATH-should-be-ignored', + LOCALAPPDATA='LOCALAPPDATA-should-be-ignored', + TEMP='TEMP-should-be-ignored', + TMPDIR='TMPDIR-should-be-ignored', + TMP='TMP-should-be-ignored', + } + end + end + + local function set_paths_via_system(var_name, paths) + local env = base_env() + env[var_name] = table.concat(paths, ':') + clear({env=env}) + end + + local function set_paths_at_runtime(var_name, paths) + clear({env=base_env()}) + meths.set_var('env_val', table.concat(paths, ':')) + command(('let $%s=g:env_val'):format(var_name)) + end + + local function behaves_like_dir_list_env(msg, stdpath_arg, env_var_name, paths, expected_paths) + describe(msg, function() + it('set via system', function() + set_paths_via_system(env_var_name, paths) + eq(expected_paths, funcs.stdpath(stdpath_arg)) + end) + + it('set at runtime', function() + set_paths_at_runtime(env_var_name, paths) + eq(expected_paths, funcs.stdpath(stdpath_arg)) + end) + end) + end + + describe('with "config_dirs"' , function () + behaves_like_dir_list_env( + 'handles XDG_CONFIG_DIRS with one path', + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('/home/docwhat/.config') + }, + { + alter_slashes('/home/docwhat/.config/nvim') + }) + + behaves_like_dir_list_env( + 'handles XDG_CONFIG_DIRS with two paths', + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('/home/docwhat/.config'), + alter_slashes('/etc/config') + }, + { + alter_slashes('/home/docwhat/.config/nvim'), + alter_slashes('/etc/config/nvim') + }) + + behaves_like_dir_list_env( + "doesn't expand $VAR and $IBLES", + 'config_dirs', 'XDG_CONFIG_DIRS', + { '$HOME', '$TMP' }, + { + alter_slashes('$HOME/nvim'), + alter_slashes('$TMP/nvim') + }) + + + behaves_like_dir_list_env( + "doesn't expand ~/", + 'config_dirs', 'XDG_CONFIG_DIRS', + { + alter_slashes('~/.oldconfig'), + alter_slashes('~/.olderconfig') + }, + { + alter_slashes('~/.oldconfig/nvim'), + alter_slashes('~/.olderconfig/nvim') + }) + end) + + describe('with "data_dirs"' , function () + behaves_like_dir_list_env( + 'knows XDG_DATA_DIRS with one path', + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('/home/docwhat/.data') + }, + { + alter_slashes('/home/docwhat/.data/nvim') + }) + + behaves_like_dir_list_env( + 'knows XDG_DATA_DIRS with two paths', + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('/home/docwhat/.data'), + alter_slashes('/etc/local') + }, + { + alter_slashes('/home/docwhat/.data/nvim'), + alter_slashes('/etc/local/nvim'), + }) + + behaves_like_dir_list_env( + "doesn't expand $VAR and $IBLES", + 'data_dirs', 'XDG_DATA_DIRS', + { '$HOME', '$TMP' }, + { + alter_slashes('$HOME/nvim'), + alter_slashes('$TMP/nvim') + }) + + behaves_like_dir_list_env( + "doesn't expand ~/", + 'data_dirs', 'XDG_DATA_DIRS', + { + alter_slashes('~/.oldconfig'), + alter_slashes('~/.olderconfig') + }, + { + alter_slashes('~/.oldconfig/nvim'), + alter_slashes('~/.olderconfig/nvim'), + }) + end) + end) + + describe('errors', function() + it('on unknown strings', function() + eq('Vim(call):E6100: "capybara" is not a valid stdpath', exc_exec('call stdpath("capybara")')) + eq('Vim(call):E6100: "" is not a valid stdpath', exc_exec('call stdpath("")')) + eq('Vim(call):E6100: "23" is not a valid stdpath', exc_exec('call stdpath(23)')) + end) + + it('on non-strings', function() + eq('Vim(call):E731: using Dictionary as a String', exc_exec('call stdpath({"eris": 23})')) + eq('Vim(call):E730: using List as a String', exc_exec('call stdpath([23])')) + end) + end) +end) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua new file mode 100644 index 0000000000..ed17ffdd3c --- /dev/null +++ b/test/functional/options/num_options_spec.lua @@ -0,0 +1,97 @@ +-- Tests for :setlocal and :setglobal + +local helpers = require('test.functional.helpers')(after_each) +local clear, feed_command, eval, eq, meths = + helpers.clear, helpers.feed_command, helpers.eval, helpers.eq, helpers.meths + +local function should_fail(opt, value, errmsg) + feed_command('setglobal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*")) + feed_command('let v:errmsg = ""') + feed_command('setlocal ' .. opt .. '=' .. value) + eq(errmsg, eval("v:errmsg"):match("E%d*")) + feed_command('let v:errmsg = ""') + local status, err = pcall(meths.set_option, opt, value) + eq(status, false) + eq(errmsg, err:match("E%d*")) + eq('', eval("v:errmsg")) +end + +local function should_succeed(opt, value) + feed_command('setglobal ' .. opt .. '=' .. value) + feed_command('setlocal ' .. opt .. '=' .. value) + meths.set_option(opt, value) + eq(value, meths.get_option(opt)) + eq('', eval("v:errmsg")) +end + +describe(':setlocal', function() + before_each(clear) + + it('setlocal sets only local value', function() + eq(0, meths.get_option('iminsert')) + feed_command('setlocal iminsert=1') + eq(0, meths.get_option('iminsert')) + eq(0, meths.get_option('imsearch')) + feed_command('setlocal imsearch=1') + eq(0, meths.get_option('imsearch')) + end) +end) + +describe(':set validation', function() + before_each(clear) + + it('setlocal and setglobal validate values', function() + should_fail('shiftwidth', -10, 'E487') + should_succeed('shiftwidth', 0) + should_fail('tabstop', -10, 'E487') + should_fail('winheight', -10, 'E487') + should_fail('winheight', 0, 'E487') + should_fail('winminheight', -1, 'E487') + should_succeed('winminheight', 0) + should_fail('winwidth', 0, 'E487') + should_fail('helpheight', -1, 'E487') + should_fail('maxcombine', 7, 'E474') + should_fail('iminsert', 3, 'E474') + should_fail('imsearch', 3, 'E474') + should_fail('titlelen', -1, 'E487') + should_fail('cmdheight', 0, 'E487') + should_fail('updatecount', -1, 'E487') + should_fail('textwidth', -1, 'E487') + should_fail('tabstop', 0, 'E487') + should_fail('timeoutlen', -1, 'E487') + should_fail('history', 1000000, 'E474') + should_fail('regexpengine', -1, 'E474') + should_fail('regexpengine', 3, 'E474') + should_succeed('regexpengine', 2) + should_fail('report', -1, 'E487') + should_succeed('report', 0) + should_fail('scrolloff', -1, 'E49') + should_fail('sidescrolloff', -1, 'E487') + should_fail('sidescroll', -1, 'E487') + should_fail('cmdwinheight', 0, 'E487') + should_fail('updatetime', -1, 'E487') + + should_fail('foldlevel', -5, 'E487') + should_fail('foldcolumn', 13, 'E474') + should_fail('conceallevel', 4, 'E474') + should_fail('numberwidth', 11, 'E474') + should_fail('numberwidth', 0, 'E487') + + -- If smaller than 1 this one is set to 'lines'-1 + feed_command('setglobal window=-10') + meths.set_option('window', -10) + eq(23, meths.get_option('window')) + eq('', eval("v:errmsg")) + end) + + it('set wmh/wh wmw/wiw checks', function() + feed_command('set winheight=2') + feed_command('set winminheight=3') + eq('E591', eval("v:errmsg"):match("E%d*")) + + feed_command('set winwidth=2') + feed_command('set winminwidth=3') + eq('E592', eval("v:errmsg"):match("E%d*")) + end) +end) diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua index 7a15c8908b..a628baff53 100644 --- a/test/functional/shada/merging_spec.lua +++ b/test/functional/shada/merging_spec.lua @@ -525,6 +525,85 @@ describe('ShaDa marks support code', function() eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) end) + it('can merge with file with mark 9 as the only numeric mark', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + nvim_command('normal! `9oabc') + eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 + end + end + eq({['0']=1, ['1']=1}, found) + end) + + it('removes duplicates while merging', function() + wshada('\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9' + .. '\007\001\014\130\161f\196\006' .. mock_file_path .. '-\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = 0 + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f == mock_file_path .. '-' then + print(require('test.helpers').format_luav(v)) + found = found + 1 + end + end + eq(1, found) + end) + + it('does not leak when no append is performed due to too many marks', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}, found) + end) + + it('does not leak when last mark in file removes some of the earlier ones', + function() + wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'a\161n0' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'b\161n1' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'c\161n2' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'd\161n3' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'e\161n4' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'f\161n5' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'g\161n6' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'h\161n7' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'i\161n8' + .. '\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'j\161n9' + .. '\007\003\018\131\162mX\195\161f\196\006' .. mock_file_path .. 'k\161n9') + eq(0, exc_exec(sdrcmd())) + eq(0, exc_exec('wshada ' .. shada_fname)) + local found = {} + for _, v in ipairs(read_shada_file(shada_fname)) do + if v.type == 7 and v.value.f:sub(1, #mock_file_path) == mock_file_path then + found[#found + 1] = v.value.f:sub(#v.value.f) + end + end + eq({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k'}, found) + end) + it('uses last A mark with gt timestamp from file when reading with !', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') @@ -563,13 +642,14 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '-' then - found = found + 1 + if v.type == 7 and v.value.f == mock_file_path .. '-' then + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) it('uses last A mark with eq timestamp from instance when writing', @@ -580,30 +660,33 @@ describe('ShaDa marks support code', function() nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do if v.type == 7 and v.value.f == mock_file_path .. '-' then - found = found + 1 + local name = ('%c'):format(v.value.n) + found[name] = (found[name] or 0) + 1 end end - eq(1, found) + eq({['0']=1, A=1}, found) end) - it('uses last A mark with gt timestamp from file when writing', - function() + it('uses last A mark with gt timestamp from file when writing', function() wshada('\007\001\018\131\162mX\195\161f\196\006' .. mock_file_path .. '-\161nA') eq(0, exc_exec(sdrcmd())) wshada('\007\002\018\131\162mX\195\161f\196\006' .. mock_file_path .. '?\161nA') nvim_command('normal! `A') eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t')) eq(0, exc_exec('wshada ' .. shada_fname)) - local found = 0 + local found = {} for _, v in ipairs(read_shada_file(shada_fname)) do - if v.type == 7 and v.value.f == '' .. mock_file_path .. '?' then - found = found + 1 + if v.type == 7 then + local name = ('%c'):format(v.value.n) + local t = found[name] or {} + t[v.value.f] = (t[v.value.f] or 0) + 1 + found[name] = t end end - eq(1, found) + eq({['0']={[mock_file_path .. '-']=1}, A={[mock_file_path .. '?']=1}}, found) end) it('uses last a mark with gt timestamp from instance when reading', diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index ca44026852..720855860a 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -181,13 +181,13 @@ describe('ShaDa support code', function() nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) local readme_fname = funcs.resolve(paths.test_source_path) .. '/README.md' - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r~') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(readme_fname)) nvim_command('set shada-=r~') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r' .. funcs.escape( funcs.escape(paths.test_source_path, '$~'), ' "\\,')) nvim_command('wshada! ' .. shada_fname) @@ -206,7 +206,7 @@ describe('ShaDa support code', function() nvim_command('undo') nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) - eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) + eq({[7]=2, [8]=2, [9]=1, [10]=4, [11]=2}, find_file(fname)) nvim_command('set shada+=r' .. pwd .. '/АБВ') nvim_command('wshada! ' .. shada_fname) eq({}, find_file(fname)) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 171745eb57..2f9abfd3f6 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -115,16 +115,12 @@ describe('tui', function() ]]) end) - it('does not mangle unmapped ALT-key chord', function() - -- Vim represents ALT/META by setting the "high bit" of the modified key; - -- we do _not_. #3982 - -- - -- Example: for input ALT+j: - -- * Vim (Nvim prior to #3982) sets high-bit, inserts "ê". - -- * Nvim (after #3982) inserts "j". - feed_data('i\027j') + it('interprets ESC+key as ALT chord', function() + -- Vim represents ALT/META by setting the "high bit" of the modified key: + -- ALT+j inserts "ê". Nvim does not (#3982). + feed_data('i\022\027j') screen:expect([[ - j{1: } | + <M-j>{1: } | {4:~ }| {4:~ }| {4:~ }| diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index ffb6a26aef..3c316d1cfa 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -24,6 +24,7 @@ before_each(function() clear() screen = Screen.new(40, 8) screen:attach() + command("set display-=msgsep") source([[ highlight RBP1 guibg=Red highlight RBP2 guibg=Yellow @@ -737,6 +738,22 @@ describe('Command-line coloring', function() feed('<CR><CR>') eq('', meths.get_var('out')) end) + it('does not crash when callback has caught not-a-editor-command exception', + function() + source([[ + function CaughtExc(cmdline) abort + try + gibberish + catch + " Do nothing + endtry + return [] + endfunction + ]]) + set_color_cb('CaughtExc') + start_prompt('1') + eq(1, meths.eval('1')) + end) end) describe('Ex commands coloring support', function() it('works', function() diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index b47210a777..812c095add 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -194,8 +194,8 @@ describe('ui/cursor', function() if m.blinkoff then m.blinkoff = 400 end if m.blinkwait then m.blinkwait = 700 end end - if m.hl_id then m.hl_id = 48 end - if m.id_lm then m.id_lm = 49 end + if m.hl_id then m.hl_id = 49 end + if m.id_lm then m.id_lm = 50 end end -- Assert the new expectation. diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 6f1b31964b..ab3b1c3cac 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -94,6 +94,7 @@ describe('highlight defaults', function() clear() screen = Screen.new() screen:attach() + command("set display-=msgsep") end) after_each(function() @@ -312,7 +313,7 @@ describe('highlight defaults', function() end) end) -describe('guisp (special/undercurl)', function() +describe('highlight', function() local screen before_each(function() @@ -321,7 +322,31 @@ describe('guisp (special/undercurl)', function() screen:attach() end) - it('can be set and is applied like foreground or background', function() + it('cterm=standout gui=standout', function() + screen:detach() + screen = Screen.new(20,5) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {standout = true, bold = true, underline = true, + background = Screen.colors.Gray90, foreground = Screen.colors.Blue1}, + [3] = {standout = true, underline = true, + background = Screen.colors.Gray90} + }) + feed_command('hi CursorLine cterm=standout,underline gui=standout,underline') + feed_command('set cursorline') + feed_command('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') + feed('i\t abcd <cr>\t abcd <cr><esc>k') + screen:expect([[ + {1:>-------.}abcd{1:*¬} | + {2:^>-------.}{3:abcd}{2:*¬}{3: }| + {1:¬} | + {1:~ }| + | + ]]) + end) + + it('guisp (special/undercurl)', function() feed_command('syntax on') feed_command('syn keyword TmpKeyword neovim') feed_command('syn keyword TmpKeyword1 special') @@ -650,6 +675,76 @@ describe("'listchars' highlight", function() end) end) +describe("MsgSeparator highlight and msgsep fillchar", function() + before_each(clear) + it("works", function() + local screen = Screen.new(50,5) + screen:set_default_attr_ids({ + [1] = {bold=true, foreground=Screen.colors.Blue}, + [2] = {bold=true, reverse=true}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {background = Screen.colors.Cyan, bold = true, reverse = true}, + [5] = {bold = true, background = Screen.colors.Magenta} + }) + screen:attach() + + -- defaults + feed_command("ls") + screen:expect([[ + | + {2: }| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + + feed_command("set fillchars+=msgsep:-") + feed_command("ls") + screen:expect([[ + | + {2:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- linked to StatusLine per default + feed_command("hi StatusLine guibg=Cyan") + feed_command("ls") + screen:expect([[ + | + {4:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- but can be unlinked + feed_command("hi clear MsgSeparator") + feed_command("hi MsgSeparator guibg=Magenta gui=bold") + feed_command("ls") + screen:expect([[ + | + {5:--------------------------------------------------}| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + + -- when display doesn't contain msgsep, these options have no effect + feed_command("set display-=msgsep") + feed_command("ls") + screen:expect([[ + {1:~ }| + {1:~ }| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + end) +end) + describe("'winhighlight' highlight", function() local screen @@ -683,6 +778,9 @@ describe("'winhighlight' highlight", function() [22] = {bold = true, foreground = Screen.colors.SeaGreen4}, [23] = {background = Screen.colors.LightMagenta}, [24] = {background = Screen.colors.WebGray}, + [25] = {bold = true, foreground = Screen.colors.Green1}, + [26] = {background = Screen.colors.Red}, + [27] = {background = Screen.colors.DarkBlue, bold = true, foreground = Screen.colors.Green1}, }) command("hi Background1 guibg=DarkBlue") command("hi Background2 guibg=DarkGreen") @@ -952,6 +1050,39 @@ describe("'winhighlight' highlight", function() ]]) end) + it("background doesn't override syntax background", function() + command('syntax on') + command('syntax keyword Foobar foobar') + command('syntax keyword Article the') + command('hi Foobar guibg=#FF0000') + command('hi Article guifg=#00FF00 gui=bold') + insert('the foobar was foobar') + screen:expect([[ + {25:the} {26:foobar} was {26:fooba}| + {26:^r} | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + -- winhl=Normal:Group with background doesn't override syntax background, + -- but does combine with syntax foreground. + command('set winhl=Normal:Background1') + screen:expect([[ + {27:the}{1: }{26:foobar}{1: was }{26:fooba}| + {26:^r}{1: }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + end) + it('can override NonText, Conceal and EndOfBuffer', function() curbufmeths.set_lines(0,-1,true, {"raa\000"}) command('call matchaddpos("Conceal", [[1,2]], 0, -1, {"conceal": "#"})') diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 8b0cb82365..27e4066d9f 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -63,6 +63,7 @@ local function common_setup(screen, inccommand, text) command("syntax on") command("set nohlsearch") command("hi Substitute guifg=red guibg=yellow") + command("set display-=msgsep") screen:attach() screen:set_default_attr_ids({ [1] = {foreground = Screen.colors.Fuchsia}, diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 8e62a37ef1..3dd9a2506e 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed_command, nvim = helpers.clear, helpers.feed_command, helpers.nvim local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq +local command = helpers.command local expect = helpers.expect local write_file = helpers.write_file local Screen = require('test.functional.ui.screen') @@ -127,6 +128,10 @@ describe('input utf sequences that contain CSI/K_SPECIAL', function() end) describe('input non-printable chars', function() + after_each(function() + os.remove('Xtest-overwrite') + end) + it("doesn't crash when echoing them back", function() write_file("Xtest-overwrite", [[foobar]]) clear() @@ -137,6 +142,7 @@ describe('input non-printable chars', function() [3] = {bold = true, foreground = Screen.colors.SeaGreen4} }) screen:attach() + command("set display-=msgsep") feed_command("e Xtest-overwrite") screen:expect([[ diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 05d5a6409a..debd324977 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -26,6 +26,7 @@ describe('ui/mouse/input', function() [4] = {reverse = true}, [5] = {bold = true, reverse = true}, }) + command("set display-=msgsep") feed('itesting<cr>mouse<cr>support and selection<esc>') screen:expect([[ testing | diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 4246020fab..02ca566bd8 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -40,10 +40,10 @@ describe("shell command :!", function() -- to avoid triggering a UI flush. child_session.feed_data(":!printf foo; sleep 200\n") screen:expect([[ + | {4:~ }| {4:~ }| - {4:~ }| - {4:~ }| + {5: }| :!printf foo; sleep 200 | foo | {3:-- TERMINAL --} | @@ -99,6 +99,7 @@ describe("shell command :!", function() end local screen = Screen.new(50, 4) screen:attach() + command("set display-=msgsep") -- Print TAB chars. #2958 feed([[:!printf '1\t2\t3'<CR>]]) screen:expect([[ @@ -153,6 +154,7 @@ describe("shell command :!", function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {foreground = Screen.colors.Blue1}, [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true, reverse = true}, }) screen:attach() end) @@ -170,10 +172,10 @@ describe("shell command :!", function() or [[:!ls bang_filter_spec ]]) feed([[\l]]) screen:expect([[ + | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| + {4: }| ]]..result..[[ | f1 | f2 | @@ -187,9 +189,9 @@ describe("shell command :!", function() feed_command('!cat test/functional/fixtures/shell_data.txt') screen.bell = false screen:expect([[ + | {1:~ }| - {1:~ }| - {1:~ }| + {4: }| :!cat test/functional/fixtures/shell_data.txt | {2:^@^A^B^C^D^E^F^H} | {2:^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\^]^^^_} | @@ -213,8 +215,8 @@ describe("shell command :!", function() feed_command(cmd) -- Note: only the first example of split composed char works screen:expect([[ - {1:~ }| - {1:~ }| + | + {4: }| :]]..cmd..[[ | å | ref: å̲ | diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index bbdf576641..7fafd6b352 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -76,11 +76,26 @@ describe('Screen', function() local function check() eq(true, screen.suspended) end + + command('let g:ev = []') + command('autocmd VimResume * :call add(g:ev, "r")') + command('autocmd VimSuspend * :call add(g:ev, "s")') + + eq(false, screen.suspended) command('suspend') + eq({ 's', 'r' }, eval('g:ev')) + screen:expect(check) screen.suspended = false + feed('<c-z>') + eq({ 's', 'r', 's', 'r' }, eval('g:ev')) + screen:expect(check) + screen.suspended = false + + command('suspend') + eq({ 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev')) end) end) @@ -355,7 +370,8 @@ describe('Screen', function() ]]) end) - it('execute command with multi-line output', function() + it('execute command with multi-line output without msgsep', function() + command("set display-=msgsep") feed(':ls<cr>') screen:expect([[ {0:~ }| @@ -375,6 +391,28 @@ describe('Screen', function() ]]) feed('<cr>') -- skip the "Press ENTER..." state or tests will hang end) + + it('execute command with multi-line output and with msgsep', function() + command("set display+=msgsep") + feed(':ls<cr>') + screen:expect([[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {1: }| + :ls | + 1 %a "[No Name]" line 1 | + {7:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') -- skip the "Press ENTER..." state or tests will hang + end) end) describe('scrolling and clearing', function() @@ -573,6 +611,7 @@ describe('Screen', function() command('nnoremap <F1> :echo "TEST"<CR>') feed(':ls<CR>') screen:expect([[ + | {0:~ }| {0:~ }| {0:~ }| @@ -582,8 +621,7 @@ describe('Screen', function() {0:~ }| {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| + {1: }| :ls | 1 %a "[No Name]" line 1 | {7:Press ENTER or type command to continue}^ | @@ -608,21 +646,3 @@ describe('Screen', function() end) end) end) - -describe('nvim_ui_attach()', function() - before_each(function() - clear() - end) - it('handles very large width/height #2180', function() - local screen = Screen.new(999, 999) - screen:attach() - eq(999, eval('&lines')) - eq(999, eval('&columns')) - end) - it('invalid option returns error', function() - local screen = Screen.new() - local status, rv = pcall(function() screen:attach({foo={'foo'}}) end) - eq(false, status) - eq('No such ui option', rv:match("No such .*")) - end) -end) diff --git a/test/helpers.lua b/test/helpers.lua index 91ceed4df1..e0645d083f 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -16,6 +16,19 @@ end local function ok(res) return assert.is_true(res) end +local function matches(pat, actual) + if nil ~= string.match(actual, pat) then + return true + end + error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual)) +end +-- Expect an error matching pattern `pat`. +local function expect_err(pat, ...) + local fn = select(1, ...) + local fn_args = {...} + table.remove(fn_args, 1) + assert.error_matches(function() return fn(unpack(fn_args)) end, pat) +end -- initial_path: directory to recurse into -- re: include pattern (string) @@ -469,6 +482,8 @@ format_luav = function(v, indent, opts) end elseif type(v) == 'nil' then ret = 'nil' + elseif type(v) == 'boolean' then + ret = (v and 'true' or 'false') else print(type(v)) -- Not implemented yet @@ -561,6 +576,7 @@ return { deepcopy = deepcopy, dictdiff = dictdiff, eq = eq, + expect_err = expect_err, filter = filter, fixtbl = fixtbl, fixtbl_rec = fixtbl_rec, @@ -570,6 +586,7 @@ return { hasenv = hasenv, intchar2lua = intchar2lua, map = map, + matches = matches, mergedicts_copy = mergedicts_copy, neq = neq, ok = ok, diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index d082e8d883..30503e3c4a 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -124,13 +124,8 @@ set(LUAROCKS_SHA256 ea1881d6954f2a98c34f93674571c8f0cbdbc28dedb3fa3cb56b6a91886d set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v2.0.0.tar.gz) set(UNIBILIUM_SHA256 78997d38d4c8177c60d3d0c1aa8c53fd0806eb21825b7b335b1768d7116bc1c1) -if(WIN32) -set(LIBTERMKEY_URL https://github.com/equalsraf/libtermkey/archive/tb-windows.zip) -set(LIBTERMKEY_SHA256 c81e33e38662b151a49847ff4feef4f8c4b2a66f3e159a28b575cbc9bcd8ffea) -else() set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.20.tar.gz) set(LIBTERMKEY_SHA256 6c0d87c94ab9915e76ecd313baec08dedf3bd56de83743d9aa923a081935d2f5) -endif() set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/a9c7c6fd20fa35e0ad3e0e98901ca12dfca9c25c.tar.gz) set(LIBVTERM_SHA256 1a4272be91d9614dc183a503786df83b6584e4afaab7feaaa5409f841afbd796) diff --git a/third-party/cmake/BuildGperf.cmake b/third-party/cmake/BuildGperf.cmake index fd11aa1998..fa8b35f383 100644 --- a/third-party/cmake/BuildGperf.cmake +++ b/third-party/cmake/BuildGperf.cmake @@ -40,7 +40,7 @@ if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) BuildGperf( CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/gperf/configure - --prefix=${HOSTDEPS_INSTALL_DIR} + --prefix=${HOSTDEPS_INSTALL_DIR} MAKE=${MAKE_PRG} INSTALL_COMMAND ${MAKE_PRG} install) elseif(MSVC OR MINGW) @@ -61,4 +61,3 @@ elseif(MSVC OR MINGW) else() message(FATAL_ERROR "Trying to build gperf in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() - diff --git a/third-party/cmake/BuildLibtermkey.cmake b/third-party/cmake/BuildLibtermkey.cmake index b5cd0c4ca6..8147c47e1e 100644 --- a/third-party/cmake/BuildLibtermkey.cmake +++ b/third-party/cmake/BuildLibtermkey.cmake @@ -11,14 +11,22 @@ ExternalProject_Add(libtermkey -DTARGET=libtermkey -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - CONFIGURE_COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libtermkey - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - # Pass toolchain - -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - # Hack to avoid -rdynamic in Mingw - -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libtermkey init + COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libtermkey apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/libtermkey-Add-support-for-Windows.patch + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libtermkeyCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libtermkey/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libtermkey + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + # Hack to avoid -rdynamic in Mingw + -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DUNIBILIUM_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include + -DUNIBILIUM_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}unibilium${CMAKE_STATIC_LIBRARY_SUFFIX} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) else() diff --git a/third-party/cmake/BuildLibuv.cmake b/third-party/cmake/BuildLibuv.cmake index f6a9660d1c..8ac7624be6 100644 --- a/third-party/cmake/BuildLibuv.cmake +++ b/third-party/cmake/BuildLibuv.cmake @@ -43,7 +43,7 @@ set(UNIX_CFGCMD sh ${DEPS_BUILD_DIR}/src/libuv/autogen.sh && if(UNIX) BuildLibuv( - CONFIGURE_COMMAND ${UNIX_CFGCMD} + CONFIGURE_COMMAND ${UNIX_CFGCMD} MAKE=${MAKE_PRG} INSTALL_COMMAND ${MAKE_PRG} V=1 install) elseif(MINGW AND CMAKE_CROSSCOMPILING) diff --git a/third-party/cmake/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake index e65a81bba5..2fda221b12 100644 --- a/third-party/cmake/BuildLuajit.cmake +++ b/third-party/cmake/BuildLuajit.cmake @@ -35,10 +35,16 @@ function(BuildLuajit) INSTALL_COMMAND "${_luajit_INSTALL_COMMAND}") endfunction() +if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(AMD64_ABI "LDFLAGS=-lpthread -lc++abi") +else() + set(AMD64_ABI "") +endif() set(INSTALLCMD_UNIX ${MAKE_PRG} CFLAGS=-fPIC CFLAGS+=-DLUAJIT_DISABLE_JIT CFLAGS+=-DLUA_USE_APICHECK CFLAGS+=-DLUA_USE_ASSERT + ${AMD64_ABI} CCDEBUG+=-g Q= install) diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake index 2a786dd8f3..339264746c 100644 --- a/third-party/cmake/BuildLuv.cmake +++ b/third-party/cmake/BuildLuv.cmake @@ -37,9 +37,17 @@ set(LUV_SRC_DIR ${DEPS_BUILD_DIR}/src/luv) set(LUV_INCLUDE_FLAGS "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.0") +# Replace luv default rockspec with the alternate one under the "rockspecs" +# directory set(LUV_PATCH_COMMAND - ${CMAKE_COMMAND} -DLUV_SRC_DIR=${LUV_SRC_DIR} - -P ${PROJECT_SOURCE_DIR}/cmake/PatchLuv.cmake) + ${CMAKE_COMMAND} -E copy_directory ${LUV_SRC_DIR}/rockspecs ${LUV_SRC_DIR}) +if(MINGW) + set(LUV_PATCH_COMMAND + ${LUV_PATCH_COMMAND} + COMMAND ${GIT_EXECUTABLE} -C ${LUV_SRC_DIR} init + COMMAND ${GIT_EXECUTABLE} -C ${LUV_SRC_DIR} apply --ignore-whitespace + ${CMAKE_CURRENT_SOURCE_DIR}/patches/luv-Add-missing-definitions-for-MinGW.patch) +endif() set(LUV_CONFIGURE_COMMAND_COMMON ${CMAKE_COMMAND} ${LUV_SRC_DIR} @@ -78,7 +86,12 @@ else() "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS} -fPIC") endif() -set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} --build .) +if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND + (CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly")) + set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} "-DCMAKE_MAKE_PROGRAM=gmake" --build .) +else() + set(LUV_BUILD_COMMAND ${CMAKE_COMMAND} --build .) +endif() set(LUV_INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install) BuildLuv(PATCH_COMMAND ${LUV_PATCH_COMMAND} diff --git a/third-party/cmake/PatchLuv.cmake b/third-party/cmake/PatchLuv.cmake deleted file mode 100644 index 96595a2f30..0000000000 --- a/third-party/cmake/PatchLuv.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# replace luv default rockspec with the alternate one under the "rockspecs" -# directory -file(GLOB LUV_ROCKSPEC RELATIVE ${LUV_SRC_DIR} ${LUV_SRC_DIR}/*.rockspec) -file(RENAME ${LUV_SRC_DIR}/rockspecs/${LUV_ROCKSPEC} ${LUV_SRC_DIR}/${LUV_ROCKSPEC}) - -# Some versions of mingw are missing defines required by luv dns module, add -# them now -set(LUV_SRC_DNS_C_DEFS -"#ifndef AI_NUMERICSERV -# define AI_NUMERICSERV 0x0008 -#endif -#ifndef AI_ALL -# define AI_ALL 0x00000100 -#endif -#ifndef AI_ADDRCONFIG -# define AI_ADDRCONFIG 0x00000400 -#endif -#ifndef AI_V4MAPPED -# define AI_V4MAPPED 0x00000800 -#endif") - -file(READ ${LUV_SRC_DIR}/src/dns.c LUV_SRC_DNS_C) -string(REPLACE - "\n#include <netdb.h>" - "\n#include <netdb.h>\n#else\n${LUV_SRC_DNS_C_DEFS}" - LUV_SRC_DNS_C_PATCHED - "${LUV_SRC_DNS_C}") -file(WRITE ${LUV_SRC_DIR}/src/dns.c "${LUV_SRC_DNS_C_PATCHED}") - diff --git a/third-party/cmake/libtermkeyCMakeLists.txt b/third-party/cmake/libtermkeyCMakeLists.txt new file mode 100644 index 0000000000..cb57631c1c --- /dev/null +++ b/third-party/cmake/libtermkeyCMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libtermkey) + +add_definitions(-D _CRT_SECURE_NO_WARNINGS) +add_definitions(-DHAVE_UNIBILIUM) +if(NOT MSVC) + add_definitions(-std=c99) +endif() + +include_directories(${PROJECT_BINARY_DIR}/t) +include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS}) + +add_library(termkey termkey.c driver-csi.c driver-ti.c) +set_target_properties(termkey PROPERTIES + PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/termkey.h) +target_link_libraries(termkey ${UNIBILIUM_LIBRARIES}) + +include(GNUInstallDirs) +install(TARGETS termkey + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +enable_testing() +file(GLOB TESTSOURCES "t/[0-9]*.c") +foreach(f ${TESTSOURCES}) + get_filename_component(t ${f} NAME_WE) + if(${t} STREQUAL 05read) + continue() + endif() + + add_executable("test_${t}" ${f} t/taplib.c) + target_link_libraries("test_${t}" termkey) + add_test("${t}" "test_${t}") +endforeach() diff --git a/third-party/patches/libtermkey-Add-support-for-Windows.patch b/third-party/patches/libtermkey-Add-support-for-Windows.patch new file mode 100644 index 0000000000..b839e94d05 --- /dev/null +++ b/third-party/patches/libtermkey-Add-support-for-Windows.patch @@ -0,0 +1,170 @@ +From fbe91a958816d85fa93665eb8f7a7a8e05eb9650 Mon Sep 17 00:00:00 2001 +From: Rui Abreu Ferreira <raf-ep@gmx.com> +Date: Tue, 5 Apr 2016 00:12:41 +0100 +Subject: [PATCH] Add support for Windows + +Ported termkey for windows. + +- The TERMKEY_FLAG_NOTERMIOS is ignore in Windows, since there is no termios. +- The termkey_waitkey() function is not implemented in windows, since there + is no poll() alternative. +- The CMake recipe only supports unibilium, not curses. +--- + driver-ti.c | 8 +++++++- + termkey-internal.h | 11 ++++++++++- + termkey.c | 22 ++++++++++++++++++---- + 3 files changed, 35 insertions(+), 6 deletions(-) + +diff --git a/driver-ti.c b/driver-ti.c +index e673ab7..f5f8052 100644 +--- a/driver-ti.c ++++ b/driver-ti.c +@@ -17,7 +17,9 @@ + #include <ctype.h> + #include <stdio.h> + #include <string.h> +-#include <unistd.h> ++#ifndef _WIN32 ++# include <unistd.h> ++#endif + #include <sys/types.h> + #include <sys/stat.h> + +@@ -338,8 +340,10 @@ static int start_driver(TermKey *tk, void *info) + if(fstat(tk->fd, &statbuf) == -1) + return 0; + ++#ifndef _WIN32 + if(S_ISFIFO(statbuf.st_mode)) + return 1; ++#endif + + // Can't call putp or tputs because they suck and don't give us fd control + len = strlen(start_string); +@@ -367,8 +371,10 @@ static int stop_driver(TermKey *tk, void *info) + if(fstat(tk->fd, &statbuf) == -1) + return 0; + ++#ifndef _WIN32 + if(S_ISFIFO(statbuf.st_mode)) + return 1; ++#endif + + /* The terminfo database will contain keys in application cursor key mode. + * We may need to enable that mode +diff --git a/termkey-internal.h b/termkey-internal.h +index 52829b3..b796729 100644 +--- a/termkey-internal.h ++++ b/termkey-internal.h +@@ -4,7 +4,14 @@ + #include "termkey.h" + + #include <stdint.h> +-#include <termios.h> ++#ifndef _WIN32 ++# include <termios.h> ++#endif ++ ++#ifdef _MSC_VER ++#include <BaseTsd.h> ++typedef SSIZE_T ssize_t; ++#endif + + struct TermKeyDriver + { +@@ -41,8 +48,10 @@ struct TermKey { + size_t hightide; /* Position beyond buffstart at which peekkey() should next start + * normally 0, but see also termkey_interpret_csi */ + ++#ifndef _WIN32 + struct termios restore_termios; + char restore_termios_valid; ++#endif + + TermKey_Terminfo_Getstr_Hook *ti_getstr_hook; + void *ti_getstr_hook_data; +diff --git a/termkey.c b/termkey.c +index 2f01f3a..145b99f 100644 +--- a/termkey.c ++++ b/termkey.c +@@ -3,14 +3,20 @@ + + #include <ctype.h> + #include <errno.h> +-#include <poll.h> +-#include <unistd.h> ++#ifndef _WIN32 ++# include <poll.h> ++# include <unistd.h> ++# include <strings.h> ++#endif + #include <string.h> +-#include <strings.h> + + #include <stdio.h> + +-#define strcaseeq(a,b) (strcasecmp(a,b) == 0) ++#ifdef _MSC_VER ++# define strcaseeq(a,b) (_stricmp(a,b) == 0) ++#else ++# define strcaseeq(a,b) (strcasecmp(a,b) == 0) ++#endif + + void termkey_check_version(int major, int minor) + { +@@ -282,7 +288,9 @@ static TermKey *termkey_alloc(void) + tk->buffsize = 256; /* bytes */ + tk->hightide = 0; + ++#ifndef _WIN32 + tk->restore_termios_valid = 0; ++#endif + + tk->ti_getstr_hook = NULL; + tk->ti_getstr_hook_data = NULL; +@@ -483,6 +491,7 @@ int termkey_start(TermKey *tk) + if(tk->is_started) + return 1; + ++#ifndef _WIN32 + if(tk->fd != -1 && !(tk->flags & TERMKEY_FLAG_NOTERMIOS)) { + struct termios termios; + if(tcgetattr(tk->fd, &termios) == 0) { +@@ -517,6 +526,7 @@ int termkey_start(TermKey *tk) + tcsetattr(tk->fd, TCSANOW, &termios); + } + } ++#endif + + struct TermKeyDriverNode *p; + for(p = tk->drivers; p; p = p->next) +@@ -542,8 +552,10 @@ int termkey_stop(TermKey *tk) + if(p->driver->stop_driver) + (*p->driver->stop_driver)(tk, p->info); + ++#ifndef _WIN32 + if(tk->restore_termios_valid) + tcsetattr(tk->fd, TCSANOW, &tk->restore_termios); ++#endif + + tk->is_started = 0; + +@@ -1046,6 +1058,7 @@ TermKeyResult termkey_getkey_force(TermKey *tk, TermKeyKey *key) + return ret; + } + ++#ifndef _WIN32 + TermKeyResult termkey_waitkey(TermKey *tk, TermKeyKey *key) + { + if(tk->fd == -1) { +@@ -1105,6 +1118,7 @@ retry: + + /* UNREACHABLE */ + } ++#endif + + TermKeyResult termkey_advisereadable(TermKey *tk) + { +-- +2.16.1.windows.4 + diff --git a/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch b/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch index 3fb18351be..e999c0fa9b 100644 --- a/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch +++ b/third-party/patches/libvterm-Remove-VLAs-for-MSVC.patch @@ -23,7 +23,7 @@ index 84299df..f9aabb3 100644 // We'll have at most len codepoints - uint32_t codepoints[len]; -+ uint32_t* codepoints = _alloca(len); ++ uint32_t* codepoints = _alloca(len * sizeof(uint32_t)); int npoints = 0; size_t eaten = 0; @@ -32,7 +32,7 @@ index 84299df..f9aabb3 100644 int width = 0; - uint32_t chars[glyph_ends - glyph_starts + 1]; -+ uint32_t* chars = _alloca(glyph_ends - glyph_starts + 1); ++ uint32_t* chars = _alloca((glyph_ends - glyph_starts + 1) * sizeof(uint32_t)); for( ; i < glyph_ends; i++) { chars[i - glyph_starts] = codepoints[i]; diff --git a/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch b/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch new file mode 100644 index 0000000000..8954ae21c3 --- /dev/null +++ b/third-party/patches/luv-Add-missing-definitions-for-MinGW.patch @@ -0,0 +1,24 @@ +diff --git a/src/dns.c b/src/dns.c +index 8634157..5f36625 100644 +--- a/src/dns.c ++++ b/src/dns.c +@@ -20,6 +20,19 @@ + #include <sys/types.h> + #include <sys/socket.h> + #include <netdb.h> ++#elif __MINGW32__ ++# ifndef AI_NUMERICSERV ++# define AI_NUMERICSERV 0x0008 ++# endif ++# ifndef AI_ALL ++# define AI_ALL 0x00000100 ++# endif ++# ifndef AI_ADDRCONFIG ++# define AI_ADDRCONFIG 0x00000400 ++# endif ++# ifndef AI_V4MAPPED ++# define AI_V4MAPPED 0x00000800 ++# endif + #endif + + static void luv_pushaddrinfo(lua_State* L, struct addrinfo* res) { |