aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--config/CMakeLists.txt50
-rw-r--r--config/config.h.in3
-rw-r--r--contrib/YouCompleteMe/ycm_extra_conf.py2
-rw-r--r--man/nvim.114
-rw-r--r--runtime/autoload/netrw.vim2484
-rw-r--r--runtime/autoload/netrwSettings.vim9
-rw-r--r--runtime/autoload/netrw_gitignore.vim13
-rw-r--r--runtime/doc/autocmd.txt10
-rw-r--r--runtime/doc/cmdline.txt2
-rw-r--r--runtime/doc/eval.txt18
-rw-r--r--runtime/doc/gui_w32.txt2
-rw-r--r--runtime/doc/index.txt10
-rw-r--r--runtime/doc/mbyte.txt2
-rw-r--r--runtime/doc/motion.txt16
-rw-r--r--runtime/doc/msgpack_rpc.txt2
-rw-r--r--runtime/doc/options.txt261
-rw-r--r--runtime/doc/pi_netrw.txt766
-rw-r--r--runtime/doc/quickref.txt14
-rw-r--r--runtime/doc/repeat.txt6
-rw-r--r--runtime/doc/starting.txt484
-rw-r--r--runtime/doc/syntax.txt10
-rw-r--r--runtime/doc/todo.txt31
-rw-r--r--runtime/doc/usr_21.txt59
-rw-r--r--runtime/doc/usr_45.txt2
-rw-r--r--runtime/doc/usr_toc.txt2
-rw-r--r--runtime/doc/various.txt2
-rw-r--r--runtime/doc/vim_diff.txt25
-rw-r--r--runtime/doc/windows.txt2
-rw-r--r--runtime/filetype.vim5
-rw-r--r--runtime/ftplugin/less.vim20
-rw-r--r--runtime/ftplugin/matlab.vim14
-rw-r--r--runtime/indent/less.vim13
-rw-r--r--runtime/macros/less.vim2
-rw-r--r--runtime/optwin.vim4
-rw-r--r--runtime/plugin/netrwPlugin.vim44
-rw-r--r--runtime/syntax/diff.vim7
-rw-r--r--runtime/syntax/html.vim7
-rw-r--r--runtime/syntax/less.vim79
-rw-r--r--runtime/syntax/netrw.vim16
-rwxr-xr-xscripts/shadacat.py86
-rw-r--r--src/nvim/api/private/helpers.c2
-rw-r--r--src/nvim/api/private/helpers.h2
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/buffer.c118
-rw-r--r--src/nvim/buffer_defs.h25
-rw-r--r--src/nvim/edit.c6
-rw-r--r--src/nvim/eval.c452
-rw-r--r--src/nvim/eval.h4
-rw-r--r--src/nvim/eval_defs.h13
-rw-r--r--src/nvim/ex_cmds.c604
-rw-r--r--src/nvim/ex_cmds.h14
-rw-r--r--src/nvim/ex_cmds.lua16
-rw-r--r--src/nvim/ex_docmd.c26
-rw-r--r--src/nvim/ex_getln.c428
-rw-r--r--src/nvim/ex_getln.h29
-rw-r--r--src/nvim/fileio.c12
-rw-r--r--src/nvim/globals.h4
-rw-r--r--src/nvim/lib/khash.h433
-rw-r--r--src/nvim/lib/ringbuf.h281
-rw-r--r--src/nvim/main.c25
-rw-r--r--src/nvim/mark.c764
-rw-r--r--src/nvim/mark.h69
-rw-r--r--src/nvim/mark_defs.h38
-rw-r--r--src/nvim/mbyte.c2
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/misc1.c7
-rw-r--r--src/nvim/msgpack_rpc/helpers.c7
-rw-r--r--src/nvim/normal.c4
-rw-r--r--src/nvim/ops.c319
-rw-r--r--src/nvim/ops.h63
-rw-r--r--src/nvim/option.c32
-rw-r--r--src/nvim/option_defs.h2
-rw-r--r--src/nvim/options.lua10
-rw-r--r--src/nvim/os/fs_defs.h5
-rw-r--r--src/nvim/os/time.c9
-rw-r--r--src/nvim/os/time.h2
-rw-r--r--src/nvim/os/unix_defs.h4
-rw-r--r--src/nvim/os/win_defs.h2
-rw-r--r--src/nvim/search.c192
-rw-r--r--src/nvim/search.h24
-rw-r--r--src/nvim/shada.c4040
-rw-r--r--src/nvim/shada.h7
-rw-r--r--src/nvim/strings.c15
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/testdir/test8.in2
-rw-r--r--src/nvim/tui/input.c1
-rw-r--r--src/nvim/undo.c27
-rw-r--r--src/nvim/undo_defs.h3
-rw-r--r--src/nvim/window.c2
-rw-r--r--test/config/paths.lua.in1
-rw-r--r--test/functional/eval/msgpack_functions_spec.lua6
-rw-r--r--test/functional/ex_cmds/recover_spec.lua3
-rw-r--r--test/functional/ex_cmds/wviminfo_spec.lua37
-rw-r--r--test/functional/ex_getln/history_spec.lua36
-rw-r--r--test/functional/helpers.lua77
-rw-r--r--test/functional/legacy/074_global_var_in_viminfo_spec.lua24
-rw-r--r--test/functional/shada/buffers_spec.lua51
-rw-r--r--test/functional/shada/compatibility_spec.lua453
-rw-r--r--test/functional/shada/errors_spec.lua490
-rw-r--r--test/functional/shada/helpers.lua94
-rw-r--r--test/functional/shada/history_spec.lua312
-rw-r--r--test/functional/shada/marks_spec.lua180
-rw-r--r--test/functional/shada/merging_spec.lua1032
-rw-r--r--test/functional/shada/registers_spec.lua163
-rw-r--r--test/functional/shada/shada_spec.lua232
-rw-r--r--test/functional/shada/variables_spec.lua139
-rw-r--r--third-party/CMakeLists.txt4
108 files changed, 12319 insertions, 3767 deletions
diff --git a/.gitignore b/.gitignore
index d483afdd73..ee4a579f91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,8 @@
*.o
*.so
+tags
+
/src/nvim/po/vim.pot
/src/nvim/po/*.ck
diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt
index e9a872d9be..b780291264 100644
--- a/config/CMakeLists.txt
+++ b/config/CMakeLists.txt
@@ -2,6 +2,8 @@ include(CheckTypeSize)
include(CheckSymbolExists)
include(CheckFunctionExists)
include(CheckIncludeFiles)
+include(CheckCSourceRuns)
+include(CheckCSourceCompiles)
check_type_size("int" SIZEOF_INT)
check_type_size("long" SIZEOF_LONG)
@@ -71,6 +73,54 @@ if(HAVE_LANGINFO_H)
check_symbol_exists(CODESET "langinfo.h" HAVE_NL_LANGINFO_CODESET)
endif()
+set(SI "#include <stdint.h>\n")
+set(MS "int main(int argc,char**argv)\n{\n uint64_t i=0x0102030405060708ULL;")
+set(ME "}")
+check_c_source_compiles("
+ #define _BSD_SOURCE 1
+ #define _DEFAULT_SOURCE 1
+ ${SI}
+ #include <endian.h>
+ #ifndef be64toh
+ # error No be64toh macros
+ #endif
+ ${MS}
+ uint64_t j = be64toh(i);
+ return (j == 0); // j must not be zero
+ ${ME}"
+ HAVE_BE64TOH_MACROS)
+if(NOT "${HAVE_BE64TOH_MACROS}")
+ check_function_exists(be64toh HAVE_BE64TOH_FUNC)
+endif()
+if("${HAVE_BE64TOH_MACROS}" OR "${HAVE_BE64TOH_FUNC}")
+ set(HAVE_BE64TOH 1)
+endif()
+if (NOT "${HAVE_BE64TOH}")
+ if (NOT "${CMAKE_CROSSCOMPILING}")
+ # It is safe to make ORDER_BIG_ENDIAN not defined if
+ # - HAVE_BE64TOH is true. In this case be64toh will be used unconditionally in
+ # any case and ORDER_BIG_ENDIAN will not be examined.
+ # - CMAKE_CROSSCOMPILING *and* HAVE_BE64TOH are both false. In this case
+ # be64toh function which uses cycle and arithmetic operations is used which
+ # will work regardless of endianess. Function is sub-optimal though.
+ check_c_source_runs("
+ ${SI}
+ ${MS}
+ char *s = (char *) &i;
+ return (
+ s[0] == 0x01
+ && s[1] == 0x02
+ && s[2] == 0x03
+ && s[3] == 0x04
+ && s[4] == 0x05
+ && s[5] == 0x06
+ && s[6] == 0x07
+ && s[7] == 0x08) ? 0 : 1;
+ ${ME}"
+ ORDER_BIG_ENDIAN)
+ endif()
+endif()
+
# generate configuration header and update include directories
configure_file (
"${PROJECT_SOURCE_DIR}/config/config.h.in"
diff --git a/config/config.h.in b/config/config.h.in
index c234501820..f8a795327f 100644
--- a/config/config.h.in
+++ b/config/config.h.in
@@ -61,4 +61,7 @@
#cmakedefine HAVE_JEMALLOC
#endif
+#cmakedefine HAVE_BE64TOH
+#cmakedefine ORDER_BIG_ENDIAN
+
#endif // AUTO_CONFIG_H
diff --git a/contrib/YouCompleteMe/ycm_extra_conf.py b/contrib/YouCompleteMe/ycm_extra_conf.py
index 7c54677c8f..12ad080143 100644
--- a/contrib/YouCompleteMe/ycm_extra_conf.py
+++ b/contrib/YouCompleteMe/ycm_extra_conf.py
@@ -9,7 +9,7 @@ def DirectoryOfThisScript():
def GetDatabase():
compilation_database_folder = os.path.join(DirectoryOfThisScript(),
- '..', 'build')
+ '..', '..', 'build')
if os.path.exists(compilation_database_folder):
return ycm_core.CompilationDatabase(compilation_database_folder)
return None
diff --git a/man/nvim.1 b/man/nvim.1
index 08c4a6db05..83544523e0 100644
--- a/man/nvim.1
+++ b/man/nvim.1
@@ -138,7 +138,7 @@ Sets the options 'hkmap' and 'rightleft'.
.It Fl V Ns Oo Ar N Oc Ns Op Ar file
Verbose mode.
Print messages about which files are being sourced and for reading and
-writing an nviminfo file.
+writing a ShaDa file.
.Ar N
is the value for the 'verbose' option; defaults to
.Cm 10
@@ -191,18 +191,18 @@ is
loading plugins is also skipped.
See
.Ic :help initialization .
-.It Fl i Ar nviminfo
+.It Fl i Ar shada
Use
-.Ar nviminfo
+.Ar shada
instead of the default of
-.Pa ~/.nviminfo .
+.Pa ~/.nvim/shada/main.shada .
If
-.Ar nviminfo
+.Ar shada
is
.Cm NONE ,
-do not read or write an nviminfo file.
+do not read or write a ShaDa file.
See
-.Ic :help viminfo .
+.Ic :help shada .
.It Fl -noplugin
Skip loading plugins.
Implied by
diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index d8d1857099..42439a57d3 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -1,7 +1,7 @@
" netrw.vim: Handles file transfer and remote directory listing across
" AUTOLOAD SECTION
-" Date: May 13, 2014
-" Version: 152
+" Date: Jan 05, 2015
+" Version: 153
" Maintainer: Charles E Campbell <NdrOchip@ScampbellPfamily.AbizM-NOSPAM>
" GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
" Copyright: Copyright (C) 1999-2013 Charles E. Campbell {{{1
@@ -29,7 +29,7 @@ if v:version < 704 || !has("patch213")
let s:needpatch213= 1
finish
endif
-let g:loaded_netrw = "v152"
+let g:loaded_netrw = "v153"
if !exists("s:NOTE")
let s:NOTE = 0
let s:WARNING = 1
@@ -51,7 +51,10 @@ setl cpo&vim
" 0=note = s:NOTE
" 1=warning = s:WARNING
" 2=error = s:ERROR
-" Apr 16, 2014 : max errnum currently is 97
+" Usage: netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,"some message",error-number)
+" netrw#ErrorMsg(s:NOTE | s:WARNING | s:ERROR,["message1","message2",...],error-number)
+" (this function can optionally take a list of messages)
+" May 15, 2014 : max errnum currently is 98
fun! netrw#ErrorMsg(level,msg,errnum)
" call Dfunc("netrw#ErrorMsg(level=".a:level." msg<".a:msg."> errnum=".a:errnum.") g:netrw_use_errorwindow=".g:netrw_use_errorwindow)
@@ -84,18 +87,31 @@ fun! netrw#ErrorMsg(level,msg,errnum)
exe bufwinnr("NetrwMessage")."wincmd w"
" call Decho("setl ma noro")
setl ma noro
- keepj call setline(line("$")+1,level.a:msg)
- keepj $
+ if type(a:msg) == 3
+ for msg in a:msg
+ NetrwKeepj call setline(line("$")+1,level.msg)
+ endfor
+ else
+ NetrwKeepj call setline(line("$")+1,level.a:msg)
+ endif
+ NetrwKeepj $
else
" call Decho("create a NetrwMessage buffer window")
bo 1split
sil! call s:NetrwEnew()
- sil! keepj call s:NetrwSafeOptions()
+ sil! NetrwKeepj call s:NetrwSafeOptions()
setl bt=nofile
- keepj file NetrwMessage
+ NetrwKeepj file NetrwMessage
" call Decho("setl ma noro")
setl ma noro
- call setline(line("$"),level.a:msg)
+ if type(a:msg) == 3
+ for msg in a:msg
+ NetrwKeepj call setline(line("$")+1,level.msg)
+ endfor
+ else
+ NetrwKeepj call setline(line("$"),level.a:msg)
+ endif
+ NetrwKeepj $
endif
" call Decho("wrote msg<".level.a:msg."> to NetrwMessage win#".winnr())
if &fo !~ '[ta]'
@@ -118,7 +134,15 @@ fun! netrw#ErrorMsg(level,msg,errnum)
elseif a:level == s:ERROR
echohl Error
endif
- echomsg level.a:msg
+
+ if type(a:msg) == 3
+ for msg in a:msg
+ echomsg level.msg
+ endfor
+ else
+ echomsg level.a:msg
+ endif
+
" call Decho("echomsg ***netrw*** ".a:msg)
echohl None
endif
@@ -127,7 +151,7 @@ fun! netrw#ErrorMsg(level,msg,errnum)
endfun
" ---------------------------------------------------------------------
-" NetrwInit: initializes variables if they haven't been defined {{{2
+" s:NetrwInit: initializes variables if they haven't been defined {{{2
" Loosely, varname = value.
fun s:NetrwInit(varname,value)
" call Decho("varname<".a:varname."> value=".a:value)
@@ -175,6 +199,13 @@ if !exists("g:netrw_fetch_cmd")
let g:netrw_fetch_cmd = ""
endif
endif
+if !exists("g:netrw_file_cmd")
+ if executable("elinks")
+ call s:NetrwInit("g:netrw_file_cmd","elinks")
+ elseif executable("links")
+ call s:NetrwInit("g:netrw_file_cmd","links")
+ endif
+endif
if !exists("g:netrw_ftp_cmd")
let g:netrw_ftp_cmd = "ftp"
endif
@@ -203,6 +234,7 @@ if !exists("g:netrw_http_cmd")
endif
endif
call s:NetrwInit("g:netrw_http_put_cmd","curl -T")
+call s:NetrwInit("g:netrw_keepj","keepj")
call s:NetrwInit("g:netrw_rcp_cmd" , "rcp")
call s:NetrwInit("g:netrw_rsync_cmd", "rsync")
if !exists("g:netrw_scp_cmd")
@@ -300,12 +332,20 @@ if !exists("g:netrw_list_cmd")
" provide a pscp-based listing command
let g:netrw_scp_cmd ="pscp -i C:\\private.ppk"
endif
- let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME:"
+ if exists("g:netrw_list_cmd_options")
+ let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME: ".g:netrw_list_cmd_options
+ else
+ let g:netrw_list_cmd= g:netrw_scp_cmd." -ls USEPORT HOSTNAME:"
+ endif
elseif executable(g:netrw_ssh_cmd)
" provide a scp-based default listing command
- let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa"
+ if exists("g:netrw_list_cmd_options")
+ let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa ".g:netrw_list_cmd_options
+ else
+ let g:netrw_list_cmd= g:netrw_ssh_cmd." USEPORT HOSTNAME ls -FLa"
+ endif
else
-" call Decho("g:netrw_ssh_cmd." is not executable")
+" call Decho(g:netrw_ssh_cmd." is not executable")
let g:netrw_list_cmd= ""
endif
endif
@@ -331,6 +371,19 @@ if !exists("g:netrw_localcopycmd")
let g:netrw_localcopycmd= ""
endif
endif
+if !exists("g:netrw_localcopydircmd")
+ if has("win32") || has("win95") || has("win64") || has("win16")
+ if g:netrw_cygwin
+ let g:netrw_localcopydircmd= "cp -R"
+ else
+ let g:netrw_localcopycmd= expand("$COMSPEC")." /c xcopy /e /c /h /i /k"
+ endif
+ elseif has("unix") || has("macunix")
+ let g:netrw_localcopydircmd= "cp -R"
+ else
+ let g:netrw_localcopycmd= ""
+ endif
+endif
if exists("g:netrw_local_mkdir")
let g:netrw_localmkdir= g:netrw_local_mkdir
call netrw#ErrorMsg(s:NOTE,"g:netrw_local_mkdir is deprecated in favor of g:netrw_localmkdir",87)
@@ -399,6 +452,7 @@ else
endif
call s:NetrwInit("g:netrw_preview" , 0)
call s:NetrwInit("g:netrw_scpport" , "-P")
+call s:NetrwInit("g:netrw_servername" , "NETRWSERVER")
call s:NetrwInit("g:netrw_sshport" , "-p")
call s:NetrwInit("g:netrw_rename_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME mv")
call s:NetrwInit("g:netrw_rm_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rm")
@@ -407,16 +461,20 @@ call s:NetrwInit("g:netrw_rmf_cmd" , g:netrw_ssh_cmd." USEPORT HOSTNAME rm
" Default values - q-s ---------- {{{3
call s:NetrwInit("g:netrw_quickhelp",0)
let s:QuickHelp= ["-:go up dir D:delete R:rename s:sort-by x:special",
- \ "%:create new file d:create new directory",
- \ "o:split&open v:vert-split&open",
- \ "i:style qf:file info O:obtain r:reverse p:preview",
- \ "mf:mark file mt:set target mm:move mc:copy",
- \ "-bookmarks- mb:make mB:delete qb:list gb:go to",
- \ "-history- qb:list u:go up U:go down",
- \ "-targets- mt:target Tb:use bookmark Th:use history"]
+ \ "(create new) %:file d:directory",
+ \ "(windows split&open) o:horz v:vert p:preview",
+ \ "i:style qf:file info O:obtain r:reverse",
+ \ "(marks) mf:mark file mt:set target mm:move mc:copy",
+ \ "(bookmarks) mb:make mB:delete qb:list gb:go to",
+ \ "(history) qb:list u:go up U:go down",
+ \ "(targets) mt:target Tb:use bookmark Th:use history"]
" g:netrw_sepchr: picking a character that doesn't appear in filenames that can be used to separate priority from filename
call s:NetrwInit("g:netrw_sepchr" , (&enc == "euc-jp")? "\<Char-0x01>" : "\<Char-0xff>")
-call s:NetrwInit("s:netrw_silentxfer" , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil keepj " : "keepj ")
+if !exists("g:netrw_keepj") || g:netrw_keepj == "keepj"
+ call s:NetrwInit("s:netrw_silentxfer" , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil keepj " : "keepj ")
+else
+ call s:NetrwInit("s:netrw_silentxfer" , (exists("g:netrw_silent") && g:netrw_silent != 0)? "sil " : " ")
+endif
call s:NetrwInit("g:netrw_sort_by" , "name") " alternatives: date , size
call s:NetrwInit("g:netrw_sort_options" , "")
call s:NetrwInit("g:netrw_sort_direction", "normal") " alternative: reverse (z y x ...)
@@ -488,6 +546,13 @@ if v:version >= 700 && has("balloon_eval") && !exists("s:initbeval") && !exists(
endif
au WinEnter * if &ft == "netrw"|call s:NetrwInsureWinVars()|endif
+if g:netrw_keepj =~ "keepj"
+ com! -nargs=* NetrwKeepj keepj <args>
+else
+ let g:netrw_keepj= ""
+ com! -nargs=* NetrwKeepj <args>
+endif
+
" ==============================
" Netrw Utility Functions: {{{1
" ==============================
@@ -525,7 +590,7 @@ if v:version >= 700 && has("balloon_eval") && has("syntax") && exists("g:syntax_
" if v:version < 700 |call Decho("did not load netrw#BalloonHelp(): vim version ".v:version." < 700 -")|endif
" if !has("balloon_eval") |call Decho("did not load netrw#BalloonHelp(): does not have balloon eval") |endif
" if !has("syntax") |call Decho("did not load netrw#BalloonHelp(): syntax disabled") |endif
-" if !exists("g:syntax_on") |call Decho("did not load netrw#BalloonHelp(): g:syntax_on=".g:syntax_on) |endif
+" if !exists("g:syntax_on") |call Decho("did not load netrw#BalloonHelp(): g:syntax_on n/a") |endif
" if exists("g:netrw_nobeval") |call Decho("did not load netrw#BalloonHelp(): g:netrw_nobeval exists") |endif
endif
@@ -655,7 +720,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" else " Decho
" call Decho("case a:dosplit=".a:dosplit." AND modified=".&modified." AND a:style=".a:style." is not 6")
endif
- keepj norm! 0
+ NetrwKeepj norm! 0
if a:0 > 0
" call Decho("case [a:0=".a:0."] > 0: a:1<".a:1.">")
@@ -751,7 +816,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
endif
if dirname =~ '^scp://' || dirname =~ '^ftp://'
call netrw#Nread(2,dirname)
- "call s:NetrwBrowse(0,dirname)
else
if dirname == ""
let dirname= getcwd()
@@ -778,9 +842,9 @@ fun! netrw#Explore(indx,dosplit,style,...)
" ---------------------------------------------------------------------
" Jan 24, 2013: not sure why the following was present. See P08-Ingelrest
" if has("win32") || has("win95") || has("win64") || has("win16")
-" keepj call search('\<'.substitute(curdir,'^.*[/\\]','','e').'\>','cW')
+" NetrwKeepj call search('\<'.substitute(curdir,'^.*[/\\]','','e').'\>','cW')
" else
-" keepj call search('\<'.substitute(curdir,'^.*/','','e').'\>','cW')
+" NetrwKeepj call search('\<'.substitute(curdir,'^.*/','','e').'\>','cW')
" endif
" ---------------------------------------------------------------------
@@ -811,7 +875,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" Nexplore
" call Decho("..case Nexplore with starpat=".starpat.": (indx=".indx.")")
if !exists("w:netrw_explore_list") " sanity check
- keepj call netrw#ErrorMsg(s:WARNING,"using Nexplore or <s-down> improperly; see help for netrw-starstar",40)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Nexplore or <s-down> improperly; see help for netrw-starstar",40)
if has("clipboard")
sil! let @* = keepregstar
sil! let @+ = keepregstar
@@ -836,7 +900,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" Pexplore
" call Decho("case Pexplore with starpat=".starpat.": (indx=".indx.")")
if !exists("w:netrw_explore_list") " sanity check
- keepj call netrw#ErrorMsg(s:WARNING,"using Pexplore or <s-up> improperly; see help for netrw-starstar",41)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"using Pexplore or <s-up> improperly; see help for netrw-starstar",41)
if has("clipboard")
sil! let @* = keepregstar
sil! let @+ = keepregstar
@@ -861,7 +925,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" Explore -- initialize
" build list of files to Explore with Nexplore/Pexplore
" call Decho("..starpat=".starpat.": case Explore: initialize (indx=".indx.")")
- keepj keepalt call s:NetrwClearExplore()
+ NetrwKeepj keepalt call s:NetrwClearExplore()
let w:netrw_explore_indx= 0
if !exists("b:netrw_curdir")
let b:netrw_curdir= getcwd()
@@ -874,7 +938,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" call Decho("..case starpat=".starpat.": build *//pattern list (curdir-only srch for files containing pattern) &hls=".&hls)
" call Decho("....pattern<".pattern.">")
try
- exe "keepj noautocmd vimgrep /".pattern."/gj ".fnameescape(b:netrw_curdir)."/*"
+ exe "NetrwKeepj noautocmd vimgrep /".pattern."/gj ".fnameescape(b:netrw_curdir)."/*"
catch /^Vim\%((\a\+)\)\=:E480/
keepalt call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pattern.">",76)
" call Dret("netrw#Explore : unable to find pattern<".pattern.">")
@@ -888,7 +952,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" call Decho("..case starpat=".starpat.": build **//pattern list (recursive descent files containing pattern)")
" call Decho("....pattern<".pattern.">")
try
- exe "sil keepj noautocmd keepalt vimgrep /".pattern."/gj "."**/*"
+ exe "sil NetrwKeepj noautocmd keepalt vimgrep /".pattern."/gj "."**/*"
catch /^Vim\%((\a\+)\)\=:E480/
keepalt call netrw#ErrorMsg(s:WARNING,'no files matched pattern<'.pattern.'>',45)
if &hls | let keepregslash= s:ExplorePatHls(pattern) | endif
@@ -927,7 +991,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" call Decho("....w:netrw_explore_listlen=".w:netrw_explore_listlen)
if w:netrw_explore_listlen == 0 || (w:netrw_explore_listlen == 1 && w:netrw_explore_list[0] =~ '\*\*\/')
- keepalt keepj call netrw#ErrorMsg(s:WARNING,"no files matched",42)
+ keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no files matched",42)
if has("clipboard")
sil! let @* = keepregstar
sil! let @+ = keepregstar
@@ -947,7 +1011,7 @@ fun! netrw#Explore(indx,dosplit,style,...)
" call Decho("....wrap indx (indx=".indx." listlen=".w:netrw_explore_listlen.")")
let indx = (indx < 0)? ( w:netrw_explore_listlen - 1 ) : 0
let w:netrw_explore_indx= indx
- keepalt keepj call netrw#ErrorMsg(s:NOTE,"no more files match Explore pattern",43)
+ keepalt NetrwKeepj call netrw#ErrorMsg(s:NOTE,"no more files match Explore pattern",43)
endif
exe "let dirfile= w:netrw_explore_list[".indx."]"
@@ -961,20 +1025,20 @@ fun! netrw#Explore(indx,dosplit,style,...)
let w:netrw_liststyle= g:netrw_liststyle
endif
if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:LONGLIST
- keepalt keepj call search('^'.substitute(dirfile,"^.*/","","").'\>',"W")
+ keepalt NetrwKeepj call search('^'.substitute(dirfile,"^.*/","","").'\>',"W")
else
- keepalt keepj call search('\<'.substitute(dirfile,"^.*/","","").'\>',"w")
+ keepalt NetrwKeepj call search('\<'.substitute(dirfile,"^.*/","","").'\>',"w")
endif
let w:netrw_explore_mtchcnt = indx + 1
let w:netrw_explore_bufnr = bufnr("%")
let w:netrw_explore_line = line(".")
- keepalt keepj call s:SetupNetrwStatusLine('%f %h%m%r%=%9*%{NetrwStatusLine()}')
+ keepalt NetrwKeepj call s:SetupNetrwStatusLine('%f %h%m%r%=%9*%{NetrwStatusLine()}')
" call Decho("....explore: mtchcnt=".w:netrw_explore_mtchcnt." bufnr=".w:netrw_explore_bufnr." line#".w:netrw_explore_line)
else
" call Decho("..your vim does not have +path_extra")
if !exists("g:netrw_quiet")
- keepalt keepj call netrw#ErrorMsg(s:WARNING,"your vim needs the +path_extra feature for Exploring with **!",44)
+ keepalt NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your vim needs the +path_extra feature for Exploring with **!",44)
endif
if has("clipboard")
sil! let @* = keepregstar
@@ -993,9 +1057,9 @@ fun! netrw#Explore(indx,dosplit,style,...)
endif
let newdir= dirname
if !exists("b:netrw_curdir")
- keepj call netrw#LocalBrowseCheck(getcwd())
+ NetrwKeepj call netrw#LocalBrowseCheck(getcwd())
else
- keepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,newdir))
+ NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,newdir))
endif
endif
@@ -1057,50 +1121,99 @@ endfun
" ---------------------------------------------------------------------
" netrw#Lexplore: toggle Explorer window, keeping it on the left of the current tab {{{2
-fun! netrw#Lexplore(...)
-" call Dfunc("netrw#Lexplore() a:0=".a:0)
+fun! netrw#Lexplore(count,rightside,...)
+" call Dfunc("netrw#Lexplore(count=".a:count."rightside=".a:rightside.",...) a:0=".a:0." ft=".&ft)
+ let curwin= winnr()
+
if a:0 > 0 && a:1 != ""
" if a netrw window is already on the left-side of the tab
" and a directory has been specified, explore with that
" directory.
- let lexwinnr= winnr()
+ let a1 = expand(a:1)
+" call Decho("a:1<".a:1."> curwin#".curwin)
exe "1wincmd w"
if &ft == "netrw"
- exe "Explore ".fnameescape(a:1)
- exe lexwinnr."wincmd w"
+" call Decho("exe Explore ".fnameescape(a:1))
+ exe "Explore ".fnameescape(a1)
+ exe curwin."wincmd w"
+ if exists("t:netrw_lexposn")
+" call Decho("forgetting t:netrw_lexposn")
+ unlet t:netrw_lexposn
+ endif
+" call Dret("netrw#Lexplore")
+ return
endif
- exe lexwinnr."wincmd w"
-" call Dret("netrw#Lexplore")
- return
+ exe curwin."wincmd w"
+ else
+ let a1= ""
endif
if exists("t:netrw_lexbufnr")
- " close down netrw explorer window
+ " check if t:netrw_lexbufnr refers to a netrw window
let lexwinnr = bufwinnr(t:netrw_lexbufnr)
- if lexwinnr != -1
- let curwin = winnr()
- exe lexwinnr."wincmd w"
- close
- exe curwin."wincmd w"
- endif
+ else
+ let lexwinnr= 0
+ endif
+
+ if lexwinnr > 0
+ " close down netrw explorer window
+" call Decho("t:netrw_lexbufnr#".t:netrw_lexbufnr.": close down netrw window")
+ exe lexwinnr."wincmd w"
+ let g:netrw_winsize = -winwidth(0)
+ let t:netrw_lexposn = netrw#SavePosn()
+" call Decho("saving t:netrw_lexposn")
+ close
+ if lexwinnr < curwin
+ let curwin= curwin - 1
+ endif
+ exe curwin."wincmd w"
unlet t:netrw_lexbufnr
else
" open netrw explorer window
+" call Decho("t:netrw_lexbufnr<n/a>: open netrw explorer window")
exe "1wincmd w"
let keep_altv = g:netrw_altv
let g:netrw_altv = 0
- if a:0 > 0 && a:1 != ""
- exe "Vexplore ".fnameescape(a:1)
+ if a:count != 0
+ let netrw_winsize = g:netrw_winsize
+ let g:netrw_winsize = a:count
+ endif
+ let curfile= expand("%")
+" call Decho("curfile<".curfile.">")
+ exe (a:rightside? "botright" : "topleft")." vertical ".((g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize) . " new"
+ if a:0 > 0 && a1 != ""
+" call Decho("case 1: Explore ".a1)
+ exe "Explore ".fnameescape(a1)
+ elseif curfile =~ '^\a\+://'
+" call Decho("case 2: Explore ".substitute(curfile,'[^/\\]*$','',''))
+ exe "Explore ".substitute(curfile,'[^/\\]*$','','')
else
- Vexplore .
+" call Decho("case 3: Explore .")
+ Explore .
endif
+ if a:count != 0
+ let g:netrw_winsize = netrw_winsize
+ endif
+ setlocal winfixwidth
let g:netrw_altv = keep_altv
let t:netrw_lexbufnr = bufnr("%")
+ if exists("t:netrw_lexposn")
+" call Decho("restoring to t:netrw_lexposn")
+ call netrw#RestorePosn(t:netrw_lexposn)
+ unlet t:netrw_lexposn
+ endif
endif
+
+ " set up default window for editing via <cr>
if exists("g:netrw_chgwin") && g:netrw_chgwin == -1
- let g:netrw_chgwin= 2
+ if a:rightside
+ let g:netrw_chgwin= 1
+ else
+ let g:netrw_chgwin= 2
+ endif
endif
+
" call Dret("netrw#Lexplore")
endfun
@@ -1261,17 +1374,16 @@ fun! netrw#Obtain(islocal,fname,...)
let path= ""
endif
let filelist= join(map(deepcopy(fnamelist),'shellescape(g:netrw_machine.":".path.v:val,1)'))
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_scp_cmd.shellescape(useport,1)." ".filelist." ".shellescape(tgtdir,1))
- exe s:netrw_silentxfer."!".g:netrw_scp_cmd.shellescape(useport,1)." ".filelist." ".shellescape(tgtdir,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.shellescape(useport,1)." ".filelist." ".shellescape(tgtdir,1))
elseif b:netrw_method == 2
" obtain file using ftp + .netrc
" call Decho("obtain via ftp+.netrc (method #2)")
- call s:SaveBufVars()|sil keepjumps new|call s:RestoreBufVars()
+ call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars()
let tmpbufnr= bufnr("%")
setl ff=unix
if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
- keepj put =g:netrw_ftpmode
+ NetrwKeepj put =g:netrw_ftpmode
" call Decho("filter input: ".getline('$'))
endif
@@ -1281,7 +1393,7 @@ fun! netrw#Obtain(islocal,fname,...)
endif
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('$'))
endif
for fname in fnamelist
@@ -1289,11 +1401,9 @@ fun! netrw#Obtain(islocal,fname,...)
" call Decho("filter input: ".getline('$'))
endfor
if exists("g:netrw_port") && g:netrw_port != ""
-" call Decho("executing: %!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
else
-" call Decho("executing: %!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
endif
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
@@ -1306,53 +1416,53 @@ fun! netrw#Obtain(islocal,fname,...)
elseif b:netrw_method == 3
" obtain with ftp + machine, id, passwd, and fname (ie. no .netrc)
" call Decho("obtain via ftp+mipf (method #3)")
- call s:SaveBufVars()|sil keepjumps new|call s:RestoreBufVars()
+ call s:SaveBufVars()|sil NetrwKeepj new|call s:RestoreBufVars()
let tmpbufnr= bufnr("%")
setl ff=unix
if exists("g:netrw_port") && g:netrw_port != ""
- keepj put ='open '.g:netrw_machine.' '.g:netrw_port
+ NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
" call Decho("filter input: ".getline('$'))
else
- keepj put ='open '.g:netrw_machine
+ NetrwKeepj put ='open '.g:netrw_machine
" call Decho("filter input: ".getline('$'))
endif
if exists("g:netrw_uid") && g:netrw_uid != ""
if exists("g:netrw_ftp") && g:netrw_ftp == 1
- keepj put =g:netrw_uid
+ NetrwKeepj put =g:netrw_uid
" call Decho("filter input: ".getline('$'))
if exists("s:netrw_passwd") && s:netrw_passwd != ""
- keepj put ='\"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
endif
" call Decho("filter input: ".getline('$'))
elseif exists("s:netrw_passwd")
- keepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
" call Decho("filter input: ".getline('$'))
endif
endif
if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
- keepj put =g:netrw_ftpmode
+ NetrwKeepj put =g:netrw_ftpmode
" call Decho("filter input: ".getline('$'))
endif
if exists("b:netrw_fname") && b:netrw_fname != ""
- keepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
+ NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
" call Decho("filter input: ".getline('$'))
endif
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('$'))
endif
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('$'))
endif
for fname in fnamelist
- keepj call setline(line("$")+1,'get "'.fname.'"')
+ NetrwKeepj call setline(line("$")+1,'get "'.fname.'"')
endfor
" call Decho("filter input: ".getline('$'))
@@ -1360,14 +1470,13 @@ fun! netrw#Obtain(islocal,fname,...)
" -i : turns off interactive prompting from ftp
" -n unix : DON'T use <.netrc>, even though it exists
" -n win32: quit being obnoxious about password
- keepj norm! 1Gdd
-" call Decho("executing: %!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options
+ NetrwKeepj norm! 1Gdd
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
if getline(1) !~ "^$"
" call Decho("error<".getline(1).">")
if !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:ERROR,getline(1),5)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),5)
endif
endif
@@ -1379,8 +1488,7 @@ fun! netrw#Obtain(islocal,fname,...)
else
let localfile= a:fname
endif
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1).shellescape(localfile)." ".shellescape(tgtdir))
- exe s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1).shellescape(localfile)." ".shellescape(tgtdir)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1).shellescape(localfile)." ".shellescape(tgtdir))
elseif !exists("b:netrw_method") || b:netrw_method < 0
" probably a badly formed url; protocol not recognized
@@ -1390,7 +1498,7 @@ fun! netrw#Obtain(islocal,fname,...)
else
" protocol recognized but not supported for Obtain (yet?)
if !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:ERROR,"current protocol not supported for obtaining file",97)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"current protocol not supported for obtaining file",97)
endif
" call Dret("netrw#Obtain : current protocol not supported for obtaining file")
return
@@ -1398,7 +1506,7 @@ fun! netrw#Obtain(islocal,fname,...)
" restore status line
if type(a:fname) == 1 && exists("s:netrw_users_stl")
- keepj call s:SetupNetrwStatusLine(s:netrw_users_stl)
+ NetrwKeepj call s:SetupNetrwStatusLine(s:netrw_users_stl)
endif
endif
@@ -1440,7 +1548,7 @@ fun! s:NetrwOptionRestore(vt)
if !exists("{a:vt}netrw_optionsave")
if exists("s:nbcd_curpos_{bufnr('%')}")
" call Decho("restoring previous position (s:nbcd_curpos_".bufnr('%')." exists)")
- keepj call netrw#RestorePosn(s:nbcd_curpos_{bufnr('%')})
+ NetrwKeepj call netrw#RestorePosn(s:nbcd_curpos_{bufnr('%')})
" call Decho("win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> winnr($)=".winnr("$"))
" call Decho("unlet s:nbcd_curpos_".bufnr('%'))
unlet s:nbcd_curpos_{bufnr('%')}
@@ -1481,7 +1589,9 @@ fun! s:NetrwOptionRestore(vt)
if exists("{a:vt}netrw_cpokeep") |let &l:cpo = {a:vt}netrw_cpokeep |unlet {a:vt}netrw_cpokeep |endif
if exists("{a:vt}netrw_diffkeep") |let &l:diff = {a:vt}netrw_diffkeep |unlet {a:vt}netrw_diffkeep |endif
if exists("{a:vt}netrw_fenkeep") |let &l:fen = {a:vt}netrw_fenkeep |unlet {a:vt}netrw_fenkeep |endif
- if exists("{a:vt}netrw_ffkeep") |let &l:ff = {a:vt}netrw_ffkeep |unlet {a:vt}netrw_ffkeep |endif
+ if exists("g:netrw_ffkep") && g:netrw_ffkeep
+ if exists("{a:vt}netrw_ffkeep") |let &l:ff = {a:vt}netrw_ffkeep |unlet {a:vt}netrw_ffkeep |endif
+ endif
if exists("{a:vt}netrw_fokeep") |let &l:fo = {a:vt}netrw_fokeep |unlet {a:vt}netrw_fokeep |endif
if exists("{a:vt}netrw_gdkeep") |let &l:gd = {a:vt}netrw_gdkeep |unlet {a:vt}netrw_gdkeep |endif
if exists("{a:vt}netrw_hidkeep") |let &l:hidden = {a:vt}netrw_hidkeep |unlet {a:vt}netrw_hidkeep |endif
@@ -1535,7 +1645,7 @@ fun! s:NetrwOptionRestore(vt)
if exists("{a:vt}netrw_regslash")|sil! let @/= {a:vt}netrw_regslash|unlet {a:vt}netrw_regslash|endif
if exists("s:nbcd_curpos_{bufnr('%')}")
" call Decho("restoring previous position (s:nbcd_curpos_".bufnr('%')." exists)")
- keepj call netrw#RestorePosn(s:nbcd_curpos_{bufnr('%')})
+ NetrwKeepj call netrw#RestorePosn(s:nbcd_curpos_{bufnr('%')})
" call Decho("unlet s:nbcd_curpos_".bufnr('%'))
if exists("s:nbcd_curpos_".bufnr('%'))
unlet s:nbcd_curpos_{bufnr('%')}
@@ -1599,7 +1709,7 @@ fun! s:NetrwOptionSave(vt)
let {a:vt}netrw_cpokeep = &l:cpo
let {a:vt}netrw_diffkeep = &l:diff
let {a:vt}netrw_fenkeep = &l:fen
- if !exists("g:netrw_ffkeep") || g:netrw_ffkeep == 1
+ if !exists("g:netrw_ffkeep") || g:netrw_ffkeep
let {a:vt}netrw_ffkeep = &l:ff
endif
let {a:vt}netrw_fokeep = &l:fo " formatoptions
@@ -1686,10 +1796,10 @@ fun! s:NetrwSafeOptions()
" call Decho("ft<".&ft."> ei=".&ei)
if &ft == "netrw"
" call Decho("do any netrw FileType autocmds (doau FileType netrw)")
- sil! keepalt keepj doau FileType netrw
+ sil! keepalt NetrwKeepj doau FileType netrw
endif
-" call Decho("fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." bh=".&l:bh)
+" call Decho("fo=".&fo.(exists("+acd")? " acd=".&acd : " acd doesn't exist")." bh=".&l:bh." bt<".&bt.">")
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dret("s:NetrwSafeOptions")
endfun
@@ -1744,6 +1854,10 @@ fun! netrw#NetRead(mode,...)
call s:NetrwOptionSave("w:")
call s:NetrwSafeOptions()
call s:RestoreCursorline()
+ " NetrwSafeOptions sets a buffer up for a netrw listing, which includes buflisting off.
+ " However, this setting is not wanted for a remote editing session. The buffer should be "nofile", still.
+ setl bl
+" call Decho("(netrw#NetRead) buf#".bufnr("%")."<".bufname("%")."> bl=".&bl." bt=".&bt." bh=".&bh)
" NetRead: interpret mode into a readcmd {{{3
if a:mode == 0 " read remote file before current line
@@ -1790,6 +1904,7 @@ fun! netrw#NetRead(mode,...)
echomsg ':Nread fetch://machine/path uses fetch'
echomsg ':Nread ftp://[user@]machine[:port]/path uses ftp autodetects <.netrc>'
echomsg ':Nread http://[user@]machine/path uses http wget'
+ echomsg ':Nread file:///path uses elinks'
echomsg ':Nread https://[user@]machine/path uses http wget'
echomsg ':Nread rcp://[user@]machine/path uses rcp'
echomsg ':Nread rsync://machine[:port]/path uses rsync'
@@ -1841,7 +1956,7 @@ fun! netrw#NetRead(mode,...)
" call Decho("checking if NetrwBrowse() should handle choice<".choice."> with netrw_list_cmd<".g:netrw_list_cmd.">")
if choice =~ "^.*[\/]$" && b:netrw_method != 5 && choice !~ '^https\=://'
" call Decho("yes, choice matches '^.*[\/]$'")
- keepj call s:NetrwBrowse(0,choice)
+ NetrwKeepj call s:NetrwBrowse(0,choice)
" call Dret("netrw#NetRead :3 getcwd<".getcwd().">")
return
endif
@@ -1875,8 +1990,7 @@ fun! netrw#NetRead(mode,...)
let uid_machine = g:netrw_machine
endif
endif
-" call Decho("executing: !".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(uid_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1))
- exe s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(uid_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(uid_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1))
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
let b:netrw_lastfile = choice
@@ -1885,29 +1999,27 @@ fun! netrw#NetRead(mode,...)
elseif b:netrw_method == 2 " read with ftp + <.netrc>
" call Decho("read via ftp+.netrc (method #2)")
let netrw_fname= b:netrw_fname
- keepj call s:SaveBufVars()|new|keepj call s:RestoreBufVars()
+ NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars()
let filtbuf= bufnr("%")
setl ff=unix
- keepj put =g:netrw_ftpmode
+ NetrwKeepj put =g:netrw_ftpmode
" call Decho("filter input: ".getline(line("$")))
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline(line("$")))
endif
call setline(line("$")+1,'get "'.netrw_fname.'" '.tmpfile)
" call Decho("filter input: ".getline(line("$")))
if exists("g:netrw_port") && g:netrw_port != ""
-" call Decho("executing: %!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
else
-" call Decho("executing: %!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
endif
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
let debugkeep = &debug
setl debug=msg
- keepj call netrw#ErrorMsg(s:ERROR,getline(1),4)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),4)
let &debug = debugkeep
endif
call s:SaveBufVars()
@@ -1926,49 +2038,48 @@ fun! netrw#NetRead(mode,...)
" Construct execution string (four lines) which will be passed through filter
" call Decho("read via ftp+mipf (method #3)")
let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
- keepj call s:SaveBufVars()|new|keepj call s:RestoreBufVars()
+ NetrwKeepj call s:SaveBufVars()|new|NetrwKeepj call s:RestoreBufVars()
let filtbuf= bufnr("%")
setl ff=unix
if exists("g:netrw_port") && g:netrw_port != ""
- keepj put ='open '.g:netrw_machine.' '.g:netrw_port
+ NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
" call Decho("filter input: ".getline('.'))
else
- keepj put ='open '.g:netrw_machine
+ NetrwKeepj put ='open '.g:netrw_machine
" call Decho("filter input: ".getline('.'))
endif
if exists("g:netrw_uid") && g:netrw_uid != ""
if exists("g:netrw_ftp") && g:netrw_ftp == 1
- keepj put =g:netrw_uid
+ NetrwKeepj put =g:netrw_uid
" call Decho("filter input: ".getline('.'))
if exists("s:netrw_passwd")
- keepj put ='\"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
endif
" call Decho("filter input: ".getline('.'))
elseif exists("s:netrw_passwd")
- keepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
" call Decho("filter input: ".getline('.'))
endif
endif
if exists("g:netrw_ftpmode") && g:netrw_ftpmode != ""
- keepj put =g:netrw_ftpmode
+ NetrwKeepj put =g:netrw_ftpmode
" call Decho("filter input: ".getline('.'))
endif
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('.'))
endif
- keepj put ='get \"'.netrw_fname.'\" '.tmpfile
+ NetrwKeepj put ='get \"'.netrw_fname.'\" '.tmpfile
" call Decho("filter input: ".getline('.'))
" perform ftp:
" -i : turns off interactive prompting from ftp
" -n unix : DON'T use <.netrc>, even though it exists
" -n win32: quit being obnoxious about password
- keepj norm! 1Gdd
-" call Decho("executing: %!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options
+ NetrwKeepj norm! 1Gdd
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
if getline(1) !~ "^$"
" call Decho("error<".getline(1).">")
@@ -1989,8 +2100,14 @@ fun! netrw#NetRead(mode,...)
else
let useport= ""
endif
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1))
- exe s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1)
+ " 'C' in 'C:\path\to\file' is handled as hostname on windows.
+ " This is workaround to avoid mis-handle windows local-path:
+ if g:netrw_scp_cmd =~ '^scp' && (has("win32") || has("win95") || has("win64") || has("win16"))
+ let tmpfile_get = substitute(tr(tmpfile, '\', '/'), '^\(\a\):[/\\]\(.*\)$', '/\1/\2', '')
+ else
+ let tmpfile_get = tmpfile
+ endif
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile_get,1))
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
let b:netrw_lastfile = choice
@@ -2010,11 +2127,9 @@ fun! netrw#NetRead(mode,...)
" using g:netrw_http_cmd (usually elinks, links, curl, wget, or fetch)
" call Decho('using '.g:netrw_http_cmd.' (# not in b:netrw_fname<'.b:netrw_fname.">)")
if exists("g:netrw_http_xcmd")
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)." ".g:netrw_http_xcmd." ".shellescape(tmpfile,1))
- exe s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)." ".g:netrw_http_xcmd." ".shellescape(tmpfile,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)." ".g:netrw_http_xcmd." ".shellescape(tmpfile,1))
else
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile,1)." ".shellescape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1))
- exe s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile,1)." ".shellescape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile,1)." ".shellescape(b:netrw_http."://".g:netrw_machine.b:netrw_fname,1))
endif
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
@@ -2025,11 +2140,10 @@ fun! netrw#NetRead(mode,...)
let netrw_tag = substitute(b:netrw_fname,"^.*#","","")
" call Decho("netrw_html<".netrw_html.">")
" call Decho("netrw_tag <".netrw_tag.">")
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile,1)." ".shellescape(b:netrw_http."://".g:netrw_machine.netrw_html,1))
- exe s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile,1)." ".shellescape(b:netrw_http."://".g:netrw_machine.netrw_html,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile,1)." ".shellescape(b:netrw_http."://".g:netrw_machine.netrw_html,1))
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
" call Decho('<\s*a\s*name=\s*"'.netrw_tag.'"/')
- exe 'keepj norm! 1G/<\s*a\s*name=\s*"'.netrw_tag.'"/'."\<CR>"
+ exe 'NetrwKeepj norm! 1G/<\s*a\s*name=\s*"'.netrw_tag.'"/'."\<CR>"
endif
let b:netrw_lastfile = choice
" call Decho("setl ro")
@@ -2046,28 +2160,26 @@ fun! netrw#NetRead(mode,...)
return
endif
if g:netrw_dav_cmd =~ "curl"
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_dav_cmd." ".shellescape("dav://".g:netrw_machine.b:netrw_fname,1)." ".shellescape(tmpfile,1))
- exe s:netrw_silentxfer."!".g:netrw_dav_cmd." ".shellescape("dav://".g:netrw_machine.b:netrw_fname,1)." ".shellescape(tmpfile,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_dav_cmd." ".shellescape("dav://".g:netrw_machine.b:netrw_fname,1)." ".shellescape(tmpfile,1))
else
" Construct execution string (four lines) which will be passed through filter
let netrw_fname= escape(b:netrw_fname,g:netrw_fname_escape)
new
setl ff=unix
if exists("g:netrw_port") && g:netrw_port != ""
- keepj put ='open '.g:netrw_machine.' '.g:netrw_port
+ NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
else
- keepj put ='open '.g:netrw_machine
+ NetrwKeepj put ='open '.g:netrw_machine
endif
if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != ""
- keepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
+ NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
endif
- keepj put ='get '.netrw_fname.' '.tmpfile
- keepj put ='quit'
+ NetrwKeepj put ='get '.netrw_fname.' '.tmpfile
+ NetrwKeepj put ='quit'
" perform cadaver operation:
- keepj norm! 1Gdd
-" call Decho("executing: %!".g:netrw_dav_cmd)
- exe s:netrw_silentxfer."%!".g:netrw_dav_cmd
+ NetrwKeepj norm! 1Gdd
+ call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd)
bd!
endif
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
@@ -2077,8 +2189,7 @@ fun! netrw#NetRead(mode,...)
" NetRead: (rsync) NetRead Method #7 {{{3
elseif b:netrw_method == 7
" call Decho("read via rsync (method #7)")
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1))
- exe s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".shellescape(tmpfile,1))
let result = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method)
let b:netrw_lastfile = choice
@@ -2089,7 +2200,7 @@ fun! netrw#NetRead(mode,...)
" call Decho("read via fetch (method #8)")
if g:netrw_fetch_cmd == ""
if !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:ERROR,"fetch command not available",7)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"fetch command not available",7)
endif
" call Dret("NetRead")
return
@@ -2102,11 +2213,9 @@ fun! netrw#NetRead(mode,...)
" call Decho("read via fetch for ".netrw_option)
if exists("g:netrw_uid") && g:netrw_uid != "" && exists("s:netrw_passwd") && s:netrw_passwd != ""
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".shellescape(tmpfile,1)." ".shellescape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".b:netrw_fname,1))
- exe s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".shellescape(tmpfile,1)." ".shellescape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".b:netrw_fname,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".shellescape(tmpfile,1)." ".shellescape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".b:netrw_fname,1))
else
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".shellescape(tmpfile,1)." ".shellescape(netrw_option."://".g:netrw_machine."/".b:netrw_fname,1))
- exe s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".shellescape(tmpfile,1)." ".shellescape(netrw_option."://".g:netrw_machine."/".b:netrw_fname,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".shellescape(tmpfile,1)." ".shellescape(netrw_option."://".g:netrw_machine."/".b:netrw_fname,1))
endif
let result = s:NetrwGetFile(readcmd,tmpfile, b:netrw_method)
@@ -2118,12 +2227,19 @@ fun! netrw#NetRead(mode,...)
" NetRead: (sftp) NetRead Method #9 {{{3
elseif b:netrw_method == 9
" call Decho("read via sftp (method #9)")
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile)
- exe s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile)
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
let b:netrw_lastfile = choice
".........................................
+ " NetRead: (file) NetRead Method #10 {{{3
+ elseif b:netrw_method == 10 && exists("g:netrw_file_cmd")
+" " call Decho("read via ".b:netrw_file_cmd." (method #10)")
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_file_cmd." ".shellescape(b:netrw_fname,1)." ".tmpfile)
+ let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
+ let b:netrw_lastfile = choice
+
+ ".........................................
" NetRead: Complain {{{3
else
call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . choice . ">",8)
@@ -2138,9 +2254,9 @@ fun! netrw#NetRead(mode,...)
endif
if s:FileReadable(tmpfile) && tmpfile !~ '.tar.bz2$' && tmpfile !~ '.tar.gz$' && tmpfile !~ '.zip' && tmpfile !~ '.tar' && readcmd != 't' && tmpfile !~ '.tar.xz$' && tmpfile !~ '.txz'
" call Decho("cleanup by deleting tmpfile<".tmpfile.">")
- keepj call s:NetrwDelete(tmpfile)
+ NetrwKeepj call s:NetrwDelete(tmpfile)
endif
- keepj call s:NetrwOptionRestore("w:")
+ NetrwKeepj call s:NetrwOptionRestore("w:")
" call Dret("netrw#NetRead :5 getcwd<".getcwd().">")
endfun
@@ -2175,16 +2291,16 @@ fun! netrw#NetWrite(...) range
" (line numbers don't really make sense for that).
" Also supports the writing of tar and zip files.
" call Decho("(write entire file) sil exe w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile))
- exe "sil keepj w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
+ exe "sil NetrwKeepj w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
elseif g:netrw_cygwin
" write (selected portion of) file to temporary
let cygtmpfile= substitute(tmpfile,g:netrw_cygdrive.'/\(.\)','\1:','')
" call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile))
- exe "sil keepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile)
+ exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(cygtmpfile)
else
" write (selected portion of) file to temporary
" call Decho("(write selected portion) sil exe ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile))
- exe "sil keepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
+ exe "sil NetrwKeepj ".a:firstline."," . a:lastline . "w! ".fnameescape(v:cmdarg)." ".fnameescape(tmpfile)
endif
if curbufname == ""
@@ -2251,7 +2367,7 @@ fun! netrw#NetWrite(...) range
" call Decho("choice<" . choice . "> ichoice=".ichoice)
" Determine method of write (ftp, rcp, etc) {{{4
- keepj call s:NetrwMethod(choice)
+ NetrwKeepj call s:NetrwMethod(choice)
if !exists("b:netrw_method") || b:netrw_method < 0
" call Dfunc("netrw#NetWrite : unsupported method")
return
@@ -2282,8 +2398,7 @@ fun! netrw#NetWrite(...) range
let uid_machine = g:netrw_machine
endif
endif
-" call Decho("executing: !".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(tmpfile,1)." ".shellescape(uid_machine.":".b:netrw_fname,1))
- exe s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(tmpfile,1)." ".shellescape(uid_machine.":".b:netrw_fname,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(tmpfile,1)." ".shellescape(uid_machine.":".b:netrw_fname,1))
let b:netrw_lastfile = choice
".........................................
@@ -2300,26 +2415,24 @@ fun! netrw#NetWrite(...) range
" call Decho("filter input window#".winnr())
setl ff=unix
- keepj put =g:netrw_ftpmode
+ NetrwKeepj put =g:netrw_ftpmode
" call Decho("filter input: ".getline('$'))
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline("$"))
endif
- keepj call setline(line("$")+1,'put "'.tmpfile.'" "'.netrw_fname.'"')
+ NetrwKeepj call setline(line("$")+1,'put "'.tmpfile.'" "'.netrw_fname.'"')
" call Decho("filter input: ".getline("$"))
if exists("g:netrw_port") && g:netrw_port != ""
-" call Decho("executing: %!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
else
" call Decho("filter input window#".winnr())
-" call Decho("executing: %!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
endif
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
if getline(1) !~ "^$"
if !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:ERROR,getline(1),14)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,getline(1),14)
endif
let mod=1
endif
@@ -2347,32 +2460,32 @@ fun! netrw#NetWrite(...) range
setl ff=unix
if exists("g:netrw_port") && g:netrw_port != ""
- keepj put ='open '.g:netrw_machine.' '.g:netrw_port
+ NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
" call Decho("filter input: ".getline('.'))
else
- keepj put ='open '.g:netrw_machine
+ NetrwKeepj put ='open '.g:netrw_machine
" call Decho("filter input: ".getline('.'))
endif
if exists("g:netrw_uid") && g:netrw_uid != ""
if exists("g:netrw_ftp") && g:netrw_ftp == 1
- keepj put =g:netrw_uid
+ NetrwKeepj put =g:netrw_uid
" call Decho("filter input: ".getline('.'))
if exists("s:netrw_passwd") && s:netrw_passwd != ""
- keepj put ='\"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
endif
" call Decho("filter input: ".getline('.'))
elseif exists("s:netrw_passwd") && s:netrw_passwd != ""
- keepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
" call Decho("filter input: ".getline('.'))
endif
endif
- keepj put =g:netrw_ftpmode
+ NetrwKeepj put =g:netrw_ftpmode
" call Decho("filter input: ".getline('$'))
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline("$"))
endif
- keepj put ='put \"'.tmpfile.'\" \"'.netrw_fname.'\"'
+ NetrwKeepj put ='put \"'.tmpfile.'\" \"'.netrw_fname.'\"'
" call Decho("filter input: ".getline('.'))
" save choice/id/password for future use
let b:netrw_lastfile = choice
@@ -2381,9 +2494,8 @@ fun! netrw#NetWrite(...) range
" -i : turns off interactive prompting from ftp
" -n unix : DON'T use <.netrc>, even though it exists
" -n win32: quit being obnoxious about password
- keepj norm! 1Gdd
-" call Decho("executing: %!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options
+ NetrwKeepj norm! 1Gdd
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
if getline(1) !~ "^$"
if !exists("g:netrw_quiet")
@@ -2407,8 +2519,7 @@ fun! netrw#NetWrite(...) range
else
let useport= ""
endif
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(tmpfile,1)." ".shellescape(g:netrw_machine.":".b:netrw_fname,1))
- exe s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(tmpfile,1)." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(tmpfile,1)." ".shellescape(g:netrw_machine.":".b:netrw_fname,1))
let b:netrw_lastfile = choice
".........................................
@@ -2418,8 +2529,7 @@ fun! netrw#NetWrite(...) range
let curl= substitute(g:netrw_http_put_cmd,'\s\+.*$',"","")
if executable(curl)
let url= g:netrw_choice
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_http_put_cmd." ".shellescape(tmpfile,1)." ".shellescape(url,1) )
- exe s:netrw_silentxfer."!".g:netrw_http_put_cmd." ".shellescape(tmpfile,1)." ".shellescape(url,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_http_put_cmd." ".shellescape(tmpfile,1)." ".shellescape(url,1) )
elseif !exists("g:netrw_quiet")
call netrw#ErrorMsg(s:ERROR,"can't write to http using <".g:netrw_http_put_cmd".">".",16)
endif
@@ -2440,19 +2550,18 @@ fun! netrw#NetWrite(...) range
setl ff=unix
if exists("g:netrw_port") && g:netrw_port != ""
- keepj put ='open '.g:netrw_machine.' '.g:netrw_port
+ NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
else
- keepj put ='open '.g:netrw_machine
+ NetrwKeepj put ='open '.g:netrw_machine
endif
if exists("g:netrw_uid") && exists("s:netrw_passwd") && g:netrw_uid != ""
- keepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
+ NetrwKeepj put ='user '.g:netrw_uid.' '.s:netrw_passwd
endif
- keepj put ='put '.tmpfile.' '.netrw_fname
+ NetrwKeepj put ='put '.tmpfile.' '.netrw_fname
" perform cadaver operation:
- keepj norm! 1Gdd
-" call Decho("executing: %!".g:netrw_dav_cmd)
- exe s:netrw_silentxfer."%!".g:netrw_dav_cmd
+ NetrwKeepj norm! 1Gdd
+ call s:NetrwExe(s:netrw_silentxfer."%!".g:netrw_dav_cmd)
" remove enew buffer (quietly)
let filtbuf= bufnr("%")
@@ -2466,8 +2575,7 @@ fun! netrw#NetWrite(...) range
" NetWrite: (rsync) NetWrite Method #7 {{{3
elseif b:netrw_method == 7
" call Decho("write via rsync (method #7)")
-" call Decho("executing: !".g:netrw_rsync_cmd." ".shellescape(tmpfile,1)." ".shellescape(g:netrw_machine.":".b:netrw_fname,1))
- exe s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".shellescape(tmpfile,1)." ".shellescape(g:netrw_machine.":".b:netrw_fname,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".shellescape(tmpfile,1)." ".shellescape(g:netrw_machine.":".b:netrw_fname,1))
let b:netrw_lastfile = choice
".........................................
@@ -2490,9 +2598,8 @@ fun! netrw#NetWrite(...) range
setl ff=unix
call setline(1,'put "'.escape(tmpfile,'\').'" '.netrw_fname)
" call Decho("filter input: ".getline('.'))
-" call Decho("executing: %!".g:netrw_sftp_cmd.' '.shellescape(uid_machine,1))
let sftpcmd= substitute(g:netrw_sftp_cmd,"%TEMPFILE%",escape(tmpfile,'\'),"g")
- exe s:netrw_silentxfer."%!".sftpcmd.' '.shellescape(uid_machine,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".sftpcmd.' '.shellescape(uid_machine,1))
let filtbuf= bufnr("%")
exe curbuf."b!"
let &l:bh = bhkeep
@@ -2670,12 +2777,12 @@ fun! s:NetrwGetFile(readcmd, tfile, method)
call tar#Browse(tfile)
else
" call Decho("edit temporary file")
- e!
+ NetrwKeepj e!
endif
" rename buffer back to remote filename
" call Decho("exe sil! keepalt file ".fnameescape(rfile))
- exe "sil! keepj keepalt file ".fnameescape(rfile)
+ exe "sil! NetrwKeepj keepalt file ".fnameescape(rfile)
" Detect filetype of local version of remote file.
" Note that isk must not include a "/" for scripts.vim
@@ -2690,7 +2797,7 @@ fun! s:NetrwGetFile(readcmd, tfile, method)
elseif !&ma
" attempting to read a file after the current line in the file, but the buffer is not modifiable
- keepj call netrw#ErrorMsg(s:WARNING,"attempt to read<".a:tfile."> into a non-modifiable buffer!",94)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"attempt to read<".a:tfile."> into a non-modifiable buffer!",94)
" call Dret("NetrwGetFile : attempt to read<".a:tfile."> into a non-modifiable buffer!")
return
@@ -2700,7 +2807,7 @@ fun! s:NetrwGetFile(readcmd, tfile, method)
let curline = line(".")
let lastline= line("$")
" call Decho("exe<".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile)."> line#".curline)
- exe "keepj ".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile)
+ exe "NetrwKeepj ".a:readcmd." ".fnameescape(v:cmdarg)." ".fnameescape(a:tfile)
let line1= curline + 1
let line2= line("$") - lastline + 1
@@ -2708,7 +2815,7 @@ fun! s:NetrwGetFile(readcmd, tfile, method)
" not readable
" call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
" call Decho("tfile<".a:tfile."> not readable")
- keepj call netrw#ErrorMsg(s:WARNING,"file <".a:tfile."> not readable",9)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"file <".a:tfile."> not readable",9)
" call Dret("NetrwGetFile : tfile<".a:tfile."> not readable")
return
endif
@@ -2716,14 +2823,14 @@ fun! s:NetrwGetFile(readcmd, tfile, method)
" User-provided (ie. optional) fix-it-up command
if exists("*NetReadFixup")
" call Decho("calling NetReadFixup(method<".a:method."> line1=".line1." line2=".line2.")")
- keepj call NetReadFixup(a:method, line1, line2)
+ NetrwKeepj call NetReadFixup(a:method, line1, line2)
" else " Decho
" call Decho("NetReadFixup() not called, doesn't exist (line1=".line1." line2=".line2.")")
endif
if has("gui") && has("menu") && has("gui_running") && &go =~# 'm' && g:netrw_menu
" update the Buffers menu
- keepj call s:UpdateBuffersMenu()
+ NetrwKeepj call s:UpdateBuffersMenu()
endif
" call Decho("readcmd<".a:readcmd."> cmdarg<".v:cmdarg."> tfile<".a:tfile."> readable=".s:FileReadable(a:tfile))
@@ -2749,6 +2856,7 @@ endfun
" 7: rsync
" 8: fetch
" 9: sftp
+" 10: file
" g:netrw_machine= hostname
" b:netrw_fname = filename
" g:netrw_port = optional port number (for ftp)
@@ -2799,11 +2907,9 @@ fun! s:NetrwMethod(choice)
" rsyncurm : rsync://host[:port]/path Use rsync
" fetchurm : fetch://[user@]host[:http]/filename Use fetch (defaults to ftp, override for http)
" sftpurm : sftp://[user@]host/filename Use scp
+ " fileurm : file://[user@]host/filename Use elinks or links
let mipf = '^\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)\s\+\(\S\+\)$'
let mf = '^\(\S\+\)\s\+\(\S\+\)$'
-" let ftpurm = '^ftp://\(\([^/@]\{-}\)@\)\=\([^/#:]\{-}\)\([#:]\d\+\)\=/\(.*\)$'
-" let rcpurm = '^rcp://\%(\([^/@]\{-}\)@\)\=\([^/]\{-}\)/\(.*\)$'
-" let fetchurm = '^fetch://\(\([^/@]\{-}\)@\)\=\([^/#:]\{-}\)\(:http\)\=/\(.*\)$'
let ftpurm = '^ftp://\(\([^/]*\)@\)\=\([^/#:]\{-}\)\([#:]\d\+\)\=/\(.*\)$'
let rcpurm = '^rcp://\%(\([^/]*\)@\)\=\([^/]\{-}\)/\(.*\)$'
let rcphf = '^\(\(\h\w*\)@\)\=\(\h\w*\):\([^@]\+\)$'
@@ -2813,6 +2919,7 @@ fun! s:NetrwMethod(choice)
let rsyncurm = '^rsync://\([^/]\{-}\)/\(.*\)\=$'
let fetchurm = '^fetch://\(\([^/]*\)@\)\=\([^/#:]\{-}\)\(:http\)\=/\(.*\)$'
let sftpurm = '^sftp://\([^/]\{-}\)/\(.*\)\=$'
+ let fileurm = '^file\=://\(.*\)$'
" call Decho("determine method:")
" Determine Method
@@ -2969,6 +3076,13 @@ fun! s:NetrwMethod(choice)
let g:netrw_uid= userid
endif
+ " Method#10: file://user@hostname/...path-to-file {{{3
+ elseif match(a:choice,fileurm) == 0 && exists("g:netrw_file_cmd")
+" call Decho("http[s]://...")
+ let b:netrw_method = 10
+ let b:netrw_fname = substitute(a:choice,fileurm,'\1',"")
+" call Decho('\1<'.substitute(a:choice,fileurm,'\1',"").">")
+
" Cannot Determine Method {{{3
else
if !exists("g:netrw_quiet")
@@ -3023,7 +3137,7 @@ if has("win95") && exists("g:netrw_win95ftp") && g:netrw_win95ftp
if method == 3 " ftp (no <.netrc>)
let fourblanklines= line2 - 3
if fourblanklines >= line1
- exe "sil keepj ".fourblanklines.",".line2."g/^\s*$/d"
+ exe "sil NetrwKeepj ".fourblanklines.",".line2."g/^\s*$/d"
call histdel("/",-1)
endif
endif
@@ -3163,9 +3277,9 @@ endfun
" 0: (user: <mb>) bookmark current directory
" 1: (user: <gb>) change to the bookmarked directory
" 2: (user: <qb>) list bookmarks
-" 3: (browsing) record current directory history
-" 4: (user: <u>) go up (previous) bookmark
-" 5: (user: <U>) go down (next) bookmark
+" 3: (browsing) records current directory history
+" 4: (user: <u>) go up (previous) directory, using history
+" 5: (user: <U>) go down (next) directory, using history
" 6: (user: <mB>) delete bookmark
fun! s:NetrwBookHistHandler(chg,curdir)
" call Dfunc("s:NetrwBookHistHandler(chg=".a:chg." curdir<".a:curdir.">) cnt=".v:count." histcnt=".g:netrw_dirhist_cnt." histmax=".g:netrw_dirhistmax)
@@ -3174,26 +3288,26 @@ fun! s:NetrwBookHistHandler(chg,curdir)
return
endif
- let ykeep= @@
+ let ykeep = @@
+ let curbufnr = bufnr("%")
+
if a:chg == 0
" bookmark the current directory
" call Decho("(user: <b>) bookmark the current directory")
- if !exists("g:netrw_bookmarklist")
- let g:netrw_bookmarklist= []
- endif
- if index(g:netrw_bookmarklist,a:curdir) == -1
- " curdir not currently in g:netrw_bookmarklist, so include it
- call add(g:netrw_bookmarklist,a:curdir)
- call sort(g:netrw_bookmarklist)
+ if exists("s:netrwmarkfilelist_{curbufnr}")
+ call s:NetrwBookmark(0)
+ echo "bookmarked marked files"
+ else
+ call s:MakeBookmark(a:curdir)
+ echo "bookmarked the current directory"
endif
- echo "bookmarked the current directory"
elseif a:chg == 1
" change to the bookmarked directory
" call Decho("(user: <".v:count."gb>) change to the bookmarked directory")
if exists("g:netrw_bookmarklist[v:count-1]")
" call Decho("(user: <".v:count."gb>) bookmarklist=".string(g:netrw_bookmarklist))
- exe "keepj e ".fnameescape(g:netrw_bookmarklist[v:count-1])
+ exe "NetrwKeepj e ".fnameescape(g:netrw_bookmarklist[v:count-1])
else
echomsg "Sorry, bookmark#".v:count." doesn't exist!"
endif
@@ -3267,13 +3381,13 @@ fun! s:NetrwBookHistHandler(chg,curdir)
if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
setl ma noro
" call Decho("setl ma noro")
- sil! keepj %d
+ sil! NetrwKeepj %d
setl nomod
" call Decho("setl nomod")
" call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
endif
" call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhist_cnt}))
- exe "keepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhist_cnt})
+ exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhist_cnt})
else
if g:netrw_dirhistmax > 0
let g:netrw_dirhist_cnt= ( g:netrw_dirhist_cnt + v:count1 ) % g:netrw_dirhistmax
@@ -3293,14 +3407,14 @@ fun! s:NetrwBookHistHandler(chg,curdir)
if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir")
" call Decho("setl ma noro")
setl ma noro
- sil! keepj %d
+ sil! NetrwKeepj %d
" call Decho("removed all lines from buffer (%d)")
" call Decho("setl nomod")
setl nomod
" call Decho("(set nomod) ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
endif
" call Decho("exe e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhist_cnt}))
- exe "keepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhist_cnt})
+ exe "NetrwKeepj e! ".fnameescape(g:netrw_dirhist_{g:netrw_dirhist_cnt})
else
let g:netrw_dirhist_cnt= ( g:netrw_dirhist_cnt - 1 ) % g:netrw_dirhistmax
if g:netrw_dirhist_cnt < 0
@@ -3314,17 +3428,17 @@ fun! s:NetrwBookHistHandler(chg,curdir)
endif
elseif a:chg == 6
- " delete the v:count'th bookmark
-" call Decho("delete bookmark#".v:count."<".g:netrw_bookmarklist[v:count-1].">")
- let savefile= s:NetrwHome()."/.netrwbook"
- if filereadable(savefile)
-" call Decho("merge bookmarks (active and file)")
- keepj call s:NetrwBookHistSave() " done here to merge bookmarks first
-" call Decho("bookmark delete savefile<".savefile.">")
- keepj call delete(savefile)
- endif
+ if exists("s:netrwmarkfilelist_{curbufnr}")
+ call s:NetrwBookmark(1)
+ echo "removed marked files from bookmarks"
+ else
+ " delete the v:count'th bookmark
+" call Decho("delete bookmark#".v:count."<".g:netrw_bookmarklist[v:count-1].">")
+ call s:MergeBookmarks()
" call Decho("remove g:netrw_bookmarklist[".(v:count-1)."]")
- keepj call remove(g:netrw_bookmarklist,v:count-1)
+ NetrwKeepj call remove(g:netrw_bookmarklist,v:count-1)
+ echo "removed current directory from bookmarks"
+ endif
" call Decho("resulting g:netrw_bookmarklist=".string(g:netrw_bookmarklist))
endif
call s:NetrwBookmarkMenu()
@@ -3335,6 +3449,7 @@ endfun
" ---------------------------------------------------------------------
" s:NetrwBookHistRead: this function reads bookmarks and history {{{2
+" Will source the history file (.netrwhist) only if the g:netrw_disthistmax is > 0.
" Sister function: s:NetrwBookHistSave()
fun! s:NetrwBookHistRead()
" call Dfunc("s:NetrwBookHistRead()")
@@ -3348,13 +3463,13 @@ fun! s:NetrwBookHistRead()
let savefile= home."/.netrwbook"
if filereadable(savefile)
" call Decho("sourcing .netrwbook")
- exe "keepalt keepj so ".savefile
+ exe "keepalt NetrwKeepj so ".savefile
endif
if g:netrw_dirhistmax > 0
let savefile= home."/.netrwhist"
if filereadable(savefile)
" call Decho("sourcing .netrwhist")
- exe "keepalt keepj so ".savefile
+ exe "keepalt NetrwKeepj so ".savefile
endif
let s:netrw_initbookhist= 1
au VimLeave * call s:NetrwBookHistSave()
@@ -3369,6 +3484,8 @@ endfun
" Sister function: s:NetrwBookHistRead()
" I used to do this via viminfo but that appears to
" be unreliable for long-term storage
+" If g:netrw_dirhistmax is <= 0, no history or bookmarks
+" will be saved.
fun! s:NetrwBookHistSave()
" call Dfunc("s:NetrwBookHistSave() dirhistmax=".g:netrw_dirhistmax)
if !exists("g:netrw_dirhistmax") || g:netrw_dirhistmax <= 0
@@ -3383,7 +3500,7 @@ fun! s:NetrwBookHistSave()
setl nocin noai noci magic nospell nohid wig= noaw
setl ma noro write
if exists("+acd") | setl noacd | endif
- sil! keepj keepalt %d
+ sil! NetrwKeepj keepalt %d
" save .netrwhist -- no attempt to merge
sil! keepalt file .netrwhist
@@ -3397,21 +3514,20 @@ fun! s:NetrwBookHistSave()
endwhile
exe "sil! w! ".savefile
- sil keepj %d
+ sil NetrwKeepj %d
if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != []
" merge and write .netrwbook
let savefile= s:NetrwHome()."/.netrwbook"
if filereadable(savefile)
let booklist= deepcopy(g:netrw_bookmarklist)
- exe "sil keepj keepalt so ".savefile
+ exe "sil NetrwKeepj keepalt so ".savefile
for bdm in booklist
if index(g:netrw_bookmarklist,bdm) == -1
call add(g:netrw_bookmarklist,bdm)
endif
endfor
call sort(g:netrw_bookmarklist)
- exe "sil! w! ".savefile
endif
" construct and save .netrwbook
@@ -3436,12 +3552,18 @@ fun! s:NetrwBrowse(islocal,dirname)
" call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")." modified=".&modified." modifiable=".&modifiable." readonly=".&readonly)
" call Dredir("ls!")
- " s:NetrwBrowse: initialize history {{{3
+ " save alternate-file's filename if w:netrw_rexlocal doesn't exist
+ " This is useful when one edits a local file, then :e ., then :Rex
+ if a:islocal && !exists("w:netrw_rexfile") && bufname("#") != ""
+ let w:netrw_rexfile= bufname("#")
+ endif
+
+ " s:NetrwBrowse : initialize history {{{3
if !exists("s:netrw_initbookhist")
- keepj call s:NetrwBookHistRead()
+ NetrwKeepj call s:NetrwBookHistRead()
endif
- " s:NetrwBrowse: simplify the dirname (especially for ".."s in dirnames) {{{3
+ " s:NetrwBrowse : simplify the dirname (especially for ".."s in dirnames) {{{3
if a:dirname !~ '^\a\+://'
let dirname= simplify(a:dirname)
else
@@ -3455,77 +3577,83 @@ fun! s:NetrwBrowse(islocal,dirname)
return
endif
- " s:NetrwBrowse: sanity checks: {{{3
+ " s:NetrwBrowse : sanity checks: {{{3
if !exists("*shellescape")
- keepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing shellescape()",69)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing shellescape()",69)
" call Dret("s:NetrwBrowse : missing shellescape()")
return
endif
if !exists("*fnameescape")
- keepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing fnameescape()",70)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw can't run -- your vim is missing fnameescape()",70)
" call Dret("s:NetrwBrowse : missing fnameescape()")
return
endif
- " s:NetrwBrowse: save options: {{{3
+ " s:NetrwBrowse : save options: {{{3
call s:NetrwOptionSave("w:")
- " s:NetrwBrowse: re-instate any marked files {{{3
+ " s:NetrwBrowse : re-instate any marked files {{{3
if exists("s:netrwmarkfilelist_{bufnr('%')}")
" call Decho("clearing marked files")
exe "2match netrwMarkFile /".s:netrwmarkfilemtch_{bufnr("%")}."/"
endif
if a:islocal && exists("w:netrw_acdkeep") && w:netrw_acdkeep
- " s:NetrwBrowse: set up "safe" options for local directory/file {{{3
+ " s:NetrwBrowse : set up "safe" options for local directory/file {{{3
" call Decho("handle w:netrw_acdkeep:")
-" call Decho("keepjumps lcd ".fnameescape(dirname)." (due to w:netrw_acdkeep=".w:netrw_acdkeep." - acd=".&acd.")")
+" call Decho("NetrwKeepj lcd ".fnameescape(dirname)." (due to w:netrw_acdkeep=".w:netrw_acdkeep." - acd=".&acd.")")
call s:NetrwLcd(dirname)
call s:NetrwSafeOptions()
" call Decho("getcwd<".getcwd().">")
elseif !a:islocal && dirname !~ '[\/]$' && dirname !~ '^"'
- " s:NetrwBrowse: looks like a remote regular file, attempt transfer {{{3
-" call Decho("attempt transfer as regular file<".dirname.">")
-
- " remove any filetype indicator from end of dirname, except for the
- " "this is a directory" indicator (/).
- " There shouldn't be one of those here, anyway.
- let path= substitute(dirname,'[*=@|]\r\=$','','e')
-" call Decho("new path<".path.">")
- call s:RemotePathAnalysis(dirname)
-
- " s:NetrwBrowse: remote-read the requested file into current buffer {{{3
- keepj mark '
- call s:NetrwEnew(dirname)
- call s:NetrwSafeOptions()
- setl ma noro
-" call Decho("setl ma noro")
- let b:netrw_curdir = dirname
- let url = s:method."://".s:user.s:machine.(s:port ? ":".s:port : "")."/".s:path
-" call Decho("exe sil! keepalt file ".fnameescape(url)." (bt=".&bt.")")
- exe "sil! keepj keepalt file ".fnameescape(url)
- exe "sil! keepj keepalt doau BufReadPre ".fnameescape(s:fname)
- sil call netrw#NetRead(2,url)
- " netrw.vim and tar.vim have already handled decompression of the tarball; avoiding gzip.vim error
-" call Decho("url<".url.">")
-" call Decho("s:path<".s:path.">")
-" call Decho("s:fname<".s:fname.">")
- if s:path =~ '.bz2'
- exe "sil keepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.bz2$','',''))
- elseif s:path =~ '.gz'
- exe "sil keepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.gz$','',''))
- elseif s:path =~ '.gz'
- exe "sil keepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.txz$','',''))
+ " s:NetrwBrowse : remote regular file handler {{{3
+" call Decho("handle remote regular file: dirname<".dirname.">")
+ if bufname(dirname) != ""
+" call Decho("edit buf#".bufname(dirname)." in win#".winnr())
+ exe "NetrwKeepj b ".bufname(dirname)
else
- exe "sil keepj keepalt doau BufReadPost ".fnameescape(s:fname)
+ " attempt transfer of remote regular file
+" call Decho("attempt transfer as regular file<".dirname.">")
+
+ " remove any filetype indicator from end of dirname, except for the
+ " "this is a directory" indicator (/).
+ " There shouldn't be one of those here, anyway.
+ let path= substitute(dirname,'[*=@|]\r\=$','','e')
+" call Decho("new path<".path.">")
+ call s:RemotePathAnalysis(dirname)
+
+ " s:NetrwBrowse : remote-read the requested file into current buffer {{{3
+ call s:NetrwEnew(dirname)
+ call s:NetrwSafeOptions()
+ setl ma noro
+" call Decho("setl ma noro")
+ let b:netrw_curdir = dirname
+ let url = s:method."://".((s:user == "")? "" : s:user."@").s:machine.(s:port ? ":".s:port : "")."/".s:path
+" call Decho("exe sil! keepalt file ".fnameescape(url)." (bt=".&bt.")")
+ exe "sil! NetrwKeepj keepalt file ".fnameescape(url)
+ exe "sil! NetrwKeepj keepalt doau BufReadPre ".fnameescape(s:fname)
+ sil call netrw#NetRead(2,url)
+ " netrw.vim and tar.vim have already handled decompression of the tarball; avoiding gzip.vim error
+" call Decho("url<".url.">")
+" call Decho("s:path<".s:path.">")
+" call Decho("s:fname<".s:fname.">")
+ if s:path =~ '.bz2'
+ exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.bz2$','',''))
+ elseif s:path =~ '.gz'
+ exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.gz$','',''))
+ elseif s:path =~ '.gz'
+ exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(substitute(s:fname,'\.txz$','',''))
+ else
+ exe "sil NetrwKeepj keepalt doau BufReadPost ".fnameescape(s:fname)
+ endif
endif
- " s:NetrwBrowse: save certain window-oriented variables into buffer-oriented variables {{{3
+ " s:NetrwBrowse : save certain window-oriented variables into buffer-oriented variables {{{3
call s:SetBufWinVars()
call s:NetrwOptionRestore("w:")
" call Decho("setl ma nomod")
- setl ma nomod
+ setl ma nomod noro
" call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
" call Dret("s:NetrwBrowse : file<".s:fname.">")
@@ -3541,9 +3669,11 @@ fun! s:NetrwBrowse(islocal,dirname)
let s:last_sort_by = g:netrw_sort_by
" set up menu {{{3
- keepj call s:NetrwMenu(1)
+ NetrwKeepj call s:NetrwMenu(1)
" get/set-up buffer {{{3
+" call Decho("saving position across a buffer refresh")
+ let svpos = netrw#SavePosn()
let reusing= s:NetrwGetBuffer(a:islocal,dirname)
" maintain markfile highlighting
if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != ""
@@ -3559,7 +3689,7 @@ fun! s:NetrwBrowse(islocal,dirname)
" call Decho("setl noma nomod nowrap")
setl noma nomod nowrap
" call Decho("(set noma nomod nowrap) ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
-" call Dret("s:NetrwBrowse : re-using buffer")
+" call Dret("s:NetrwBrowse : re-using not-cleared buffer")
return
endif
@@ -3631,9 +3761,9 @@ fun! s:NetrwBrowse(islocal,dirname)
let dirpat = '^\(\w\{-}\)://\(\w\+@\)\=\([^/]\+\)/\(.*\)$'
if dirname !~ dirpat
if !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:ERROR,"netrw doesn't understand your dirname<".dirname.">",20)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"netrw doesn't understand your dirname<".dirname.">",20)
endif
- keepj call s:NetrwOptionRestore("w:")
+ NetrwKeepj call s:NetrwOptionRestore("w:")
" call Decho("setl noma nomod nowrap")
setl noma nomod nowrap
" call Decho(" ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
@@ -3647,9 +3777,9 @@ fun! s:NetrwBrowse(islocal,dirname)
" -----------------------
" Directory Listing: {{{3
" -----------------------
- keepj call s:NetrwMaps(a:islocal)
- keepj call s:NetrwCommands(a:islocal)
- keepj call s:PerformListing(a:islocal)
+ NetrwKeepj call s:NetrwMaps(a:islocal)
+ NetrwKeepj call s:NetrwCommands(a:islocal)
+ NetrwKeepj call s:PerformListing(a:islocal)
if v:version >= 700 && has("balloon_eval") && &beval == 0 && &l:bexpr == "" && !exists("g:netrw_nobeval")
let &l:bexpr= "netrw#BalloonHelp()"
" call Decho("set up balloon help: l:bexpr=".&l:bexpr)
@@ -3657,6 +3787,12 @@ fun! s:NetrwBrowse(islocal,dirname)
endif
call s:NetrwOptionRestore("w:")
+ " restore position and jumplist entry
+ if !reusing
+" call Decho("restoring position across buffer refresh")
+ call netrw#RestorePosn(svpos)
+ endif
+
" The s:LocalBrowseRefresh() function is called by an autocmd
" installed by s:LocalFastBrowser() when g:netrw_fastbrowse <= 1 (ie. slow, medium speed).
" However, s:NetrwBrowse() causes the FocusGained event to fire the firstt time.
@@ -3738,7 +3874,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
exe "sil! noswapfile keepalt b ".w:netrw_treebufnr
let &ei= eikeep
setl ma
- sil! keepj %d
+ sil! NetrwKeepj %d
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dret("s:NetrwGetBuffer 0<buffer cleared> : bufnum#".w:netrw_treebufnr."<NetrwTreeListing>")
return 0
@@ -3819,7 +3955,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
" get enew buffer and name it -or- re-use buffer {{{3
" call Decho(" get enew buffer and name it OR re-use buffer")
- sil! keepj keepalt mark '
+ sil! NetrwKeepj keepalt mark '
if bufnum < 0 || !bufexists(bufnum)
" call Decho("--get enew buffer and name it (bufnum#".bufnum."<0 OR bufexists(".bufnum.")=".bufexists(bufnum)."==0)")
call s:NetrwEnew(dirname)
@@ -3843,8 +3979,8 @@ fun! s:NetrwGetBuffer(islocal,dirname)
nnoremap <silent> <buffer> ]] :sil call <SID>TreeListMove(']')<cr>
" call Decho(" tree listing#".s:netrw_treelistnum." bufnr=".w:netrw_treebufnr)
else
-" let v:errmsg= "" " Decho
- let escdirname= fnameescape(dirname)
+" let v:errmsg = "" " Decho
+ let escdirname = fnameescape(dirname)
" call Decho(" errmsg<".v:errmsg."> bufnr(escdirname<".escdirname.">)=".bufnr(escdirname)." bufname()<".bufname(bufnr(escdirname)).">")
" call Decho(' exe sil! keepalt file '.escdirname)
" let v:errmsg= "" " Decho
@@ -3869,25 +4005,30 @@ fun! s:NetrwGetBuffer(islocal,dirname)
exe "sil! keepalt file ".fnameescape(getcwd())
endif
let &ei= eikeep
+
if line("$") <= 1
- keepj call s:NetrwListSettings(a:islocal)
+ NetrwKeepj call s:NetrwListSettings(a:islocal)
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dret("s:NetrwGetBuffer 0<buffer empty> : re-using buffer#".bufnr("%").", but its empty, so refresh it")
return 0
+
elseif g:netrw_fastbrowse == 0 || (a:islocal && g:netrw_fastbrowse == 1)
- keepj call s:NetrwListSettings(a:islocal)
- sil keepj %d
+" call Decho("g:netrw_fastbrowse=".g:netrw_fastbrowse." a:islocal=".a:islocal.": clear buffer")
+ NetrwKeepj call s:NetrwListSettings(a:islocal)
+ sil NetrwKeepj %d
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dret("s:NetrwGetBuffer 0<cleared buffer> : re-using buffer#".bufnr("%").", but refreshing due to g:netrw_fastbrowse=".g:netrw_fastbrowse)
return 0
+
elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
" call Decho("--re-use tree listing--")
" call Decho(" clear buffer<".expand("%")."> with :%d")
- sil keepj %d
- keepj call s:NetrwListSettings(a:islocal)
+ sil NetrwKeepj %d
+ NetrwKeepj call s:NetrwListSettings(a:islocal)
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dret("s:NetrwGetBuffer 0<cleared buffer> : re-using buffer#".bufnr("%").", but treelist mode always needs a refresh")
return 0
+
else
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dret("s:NetrwGetBuffer 1<buffer not cleared> : buf#".bufnr("%"))
@@ -3902,14 +4043,14 @@ fun! s:NetrwGetBuffer(islocal,dirname)
" fast 2 H H
" call Decho("--do netrw settings: make this buffer#".bufnr("%")." not-a-file, modifiable, not line-numbered, etc--")
let fname= expand("%")
- keepj call s:NetrwListSettings(a:islocal)
+ NetrwKeepj call s:NetrwListSettings(a:islocal)
" call Decho("exe sil! keepalt file ".fnameescape(fname))
- exe "sil! keepj keepalt file ".fnameescape(fname)
+ exe "sil! NetrwKeepj keepalt file ".fnameescape(fname)
" delete all lines from buffer {{{3
" call Decho("--delete all lines from buffer--")
" call Decho(" clear buffer<".expand("%")."> with :%d")
- sil! keepalt keepj %d
+ sil! keepalt NetrwKeepj %d
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dret("s:NetrwGetBuffer 0<cleared buffer> : tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%"))
@@ -3936,7 +4077,7 @@ endfun
" ---------------------------------------------------------------------
" s:NetrwGetWord: it gets the directory/file named under the cursor {{{2
fun! s:NetrwGetWord()
-" call Dfunc("s:NetrwGetWord() line#".line(".")." liststyle=".g:netrw_liststyle." virtcol=".virtcol("."))
+" call Dfunc("s:NetrwGetWord() line#".line(".")." liststyle=".s:ShowStyle()." virtcol=".virtcol("."))
call s:UseBufWinVars()
" insure that w:netrw_liststyle is set up
@@ -3946,18 +4087,18 @@ fun! s:NetrwGetWord()
else
let w:netrw_liststyle= s:THINLIST
endif
-" call Decho("w:netrw_liststyle=".w:netrw_liststyle)
+" "call Decho("w:netrw_liststyle=".w:netrw_liststyle)
endif
if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt
" Active Banner support
-" call Decho("active banner handling")
- keepj norm! 0
+" "call Decho("active banner handling")
+ NetrwKeepj norm! 0
let dirname= "./"
let curline= getline('.')
if curline =~ '"\s*Sorted by\s'
- keepj norm s
+ NetrwKeepj norm s
let s:netrw_skipbrowse= 1
echo 'Pressing "s" also works'
@@ -3966,67 +4107,67 @@ fun! s:NetrwGetWord()
echo 'Press "S" to edit sorting sequence'
elseif curline =~ '"\s*Quick Help:'
- keepj norm ?
+ NetrwKeepj norm ?
let s:netrw_skipbrowse= 1
- echo 'Pressing "?" also works'
elseif curline =~ '"\s*\%(Hiding\|Showing\):'
- keepj norm a
+ NetrwKeepj norm a
let s:netrw_skipbrowse= 1
echo 'Pressing "a" also works'
elseif line("$") > w:netrw_bannercnt
- exe 'sil keepj '.w:netrw_bannercnt
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt
endif
elseif w:netrw_liststyle == s:THINLIST
-" call Decho("thin column handling")
- keepj norm! 0
- let dirname= getline('.')
+" "call Decho("thin column handling")
+ NetrwKeepj norm! 0
+ let dirname= substitute(getline('.'),'\t -->.*$','','')
elseif w:netrw_liststyle == s:LONGLIST
-" call Decho("long column handling")
- keepj norm! 0
+" "call Decho("long column handling")
+ NetrwKeepj norm! 0
let dirname= substitute(getline('.'),'^\(\%(\S\+ \)*\S\+\).\{-}$','\1','e')
elseif w:netrw_liststyle == s:TREELIST
-" call Decho("treelist handling")
+" "call Decho("treelist handling")
let dirname= substitute(getline('.'),'^\('.s:treedepthstring.'\)*','','e')
+ let dirname= substitute(dirname,'\t -->.*$','','')
else
-" call Decho("obtain word from wide listing")
+" "call Decho("obtain word from wide listing")
let dirname= getline('.')
if !exists("b:netrw_cpf")
let b:netrw_cpf= 0
- exe 'sil keepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
call histdel("/",-1)
-" call Decho("computed cpf=".b:netrw_cpf)
+" "call Decho("computed cpf=".b:netrw_cpf)
endif
-" call Decho("buf#".bufnr("%")."<".bufname("%").">")
+" "call Decho("buf#".bufnr("%")."<".bufname("%").">")
let filestart = (virtcol(".")/b:netrw_cpf)*b:netrw_cpf
-" call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart." bannercnt=".w:netrw_bannercnt)
-" call Decho("1: dirname<".dirname.">")
+" "call Decho("filestart= ([virtcol=".virtcol(".")."]/[b:netrw_cpf=".b:netrw_cpf."])*b:netrw_cpf=".filestart." bannercnt=".w:netrw_bannercnt)
+" "call Decho("1: dirname<".dirname.">")
if filestart == 0
- keepj norm! 0ma
+ NetrwKeepj norm! 0ma
else
call cursor(line("."),filestart+1)
- keepj norm! ma
+ NetrwKeepj norm! ma
endif
let rega= @a
let eofname= filestart + b:netrw_cpf + 1
if eofname <= col("$")
call cursor(line("."),filestart+b:netrw_cpf+1)
- keepj norm! "ay`a
+ NetrwKeepj norm! "ay`a
else
- keepj norm! "ay$
+ NetrwKeepj norm! "ay$
endif
let dirname = @a
let @a = rega
-" call Decho("2: dirname<".dirname.">")
+" "call Decho("2: dirname<".dirname.">")
let dirname= substitute(dirname,'\s\+$','','e')
-" call Decho("3: dirname<".dirname.">")
+" "call Decho("3: dirname<".dirname.">")
endif
" symlinks are indicated by a trailing "@". Remove it before further processing.
@@ -4101,7 +4242,7 @@ fun! s:NetrwListStyle(islocal)
let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
else
- keepj call netrw#ErrorMsg(s:WARNING,"bad value for g:netrw_liststyle (=".w:netrw_liststyle.")",46)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"bad value for g:netrw_liststyle (=".w:netrw_liststyle.")",46)
let g:netrw_liststyle = s:THINLIST
let w:netrw_liststyle = g:netrw_liststyle
let g:netrw_list_cmd = substitute(g:netrw_list_cmd,' -l','','ge')
@@ -4111,7 +4252,7 @@ fun! s:NetrwListStyle(islocal)
" clear buffer - this will cause NetrwBrowse/LocalBrowseCheck to do a refresh
" call Decho("clear buffer<".expand("%")."> with :%d")
- sil! keepj %d
+ sil! NetrwKeepj %d
" following prevents tree listing buffer from being marked "modified"
" call Decho("setl nomod")
setl nomod
@@ -4119,11 +4260,11 @@ fun! s:NetrwListStyle(islocal)
" refresh the listing
" call Decho("refresh the listing")
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call s:NetrwCursor()
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call s:NetrwCursor()
" restore position; keep cursor on the filename
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call netrw#RestorePosn(svpos)
let @@= ykeep
" call Dret("NetrwListStyle".(exists("w:netrw_liststyle")? ' : w:netrw_liststyle='.w:netrw_liststyle : ""))
@@ -4144,17 +4285,87 @@ fun! s:NetrwBannerCtrl(islocal)
" keep cursor on the filename
let fname= s:NetrwGetWord()
- sil keepj $
+ sil NetrwKeepj $
let result= search('\%(^\%(|\+\s\)\=\|\s\{2,}\)\zs'.escape(fname,'.\[]*$^').'\%(\s\{2,}\|$\)','bc')
" call Decho("search result=".result." w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'N/A'))
if result <= 0 && exists("w:netrw_bannercnt")
- exe "keepj ".w:netrw_bannercnt
+ exe "NetrwKeepj ".w:netrw_bannercnt
endif
let @@= ykeep
" call Dret("s:NetrwBannerCtrl : g:netrw_banner=".g:netrw_banner)
endfun
" ---------------------------------------------------------------------
+" s:NetrwBookmark: supports :NetrwMB[!] [file]s {{{2
+"
+" No bang: enters files/directories into Netrw's bookmark system
+" No argument and in netrw buffer:
+" if there are marked files: bookmark marked files
+" otherwise : bookmark file/directory under cursor
+" No argument and not in netrw buffer: bookmarks current open file
+" Has arguments: globs them individually and bookmarks them
+"
+" With bang: deletes files/directories from Netrw's bookmark system
+fun! s:NetrwBookmark(del,...)
+" call Dfunc("s:NetrwBookmark(del=".a:del.",...) a:0=".a:0)
+ if a:0 == 0
+ if &ft == "netrw"
+ let curbufnr = bufnr("%")
+
+ if exists("s:netrwmarkfilelist_{curbufnr}")
+ " for every filename in the marked list
+" call Decho("bookmark every filename in marked list")
+ let svpos = netrw#SavePosn()
+ let islocal= expand("%") !~ '^\a\+://'
+ for fname in s:netrwmarkfilelist_{curbufnr}
+ if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
+ endfor
+ let curdir = exists("b:netrw_curdir")? b:netrw_curdir : getcwd()
+ call s:NetrwUnmarkList(curbufnr,curdir)
+ NetrwKeepj call s:NetrwRefresh(islocal,s:NetrwBrowseChgDir(islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
+ else
+ let fname= s:NetrwGetWord()
+ if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
+ endif
+
+ else
+ " bookmark currently open file
+" call Decho("bookmark currently open file")
+ let fname= expand("%")
+ if a:del|call s:DeleteBookmark(fname)|else|call s:MakeBookmark(fname)|endif
+ endif
+
+ else
+ " bookmark specified files
+ " attempts to infer if working remote or local
+ " by deciding if the current file begins with an url
+ " Globbing cannot be done remotely.
+ let islocal= expand("%") !~ '^\a\+://'
+" call Decho("bookmark specified file".((a:0>1)? "s" : ""))
+ let i = 1
+ while i <= a:0
+ if islocal
+ let mbfiles= glob(a:{i},0,1)
+ else
+ let mbfiles= [a:{i}]
+ endif
+" call Decho("mbfiles".string(mbfiles))
+ for mbfile in mbfiles
+" call Decho("mbfile<".mbfile.">")
+ if a:del|call s:DeleteBookmark(mbfile)|else|call s:MakeBookmark(mbfile)|endif
+ endfor
+ let i= i + 1
+ endwhile
+ endif
+
+ " update the menu
+ call s:NetrwBookmarkMenu()
+
+" call Dret("s:NetrwBookmark")
+endfun
+
+" ---------------------------------------------------------------------
" s:NetrwBookmarkMenu: Uses menu priorities {{{2
" .2.[cnt] for bookmarks, and
" .3.[cnt] for history
@@ -4241,8 +4452,8 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" NetrwBrowseChgDir: save options and initialize {{{3
" call Decho("saving options")
- keepj call s:NetrwOptionSave("s:")
- keepj call s:NetrwSafeOptions()
+ NetrwKeepj call s:NetrwOptionSave("s:")
+ NetrwKeepj call s:NetrwSafeOptions()
let nbcd_curpos = netrw#SavePosn()
let s:nbcd_curpos_{bufnr('%')} = nbcd_curpos
" call Decho("setting s:nbcd_curpos_".bufnr('%')." to SavePosn")
@@ -4257,6 +4468,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" call Decho("dirname<".dirname.">")
" ignore <cr>s when done in the banner
+" call Decho('ignore <cr>s when done in banner (g:netrw_banner='.g:netrw_banner.")")
if g:netrw_banner
" call Decho("w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a')." line(.)#".line('.')." line($)#".line("#"))
if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt && line("$") >= w:netrw_bannercnt
@@ -4265,10 +4477,10 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
let g:netrw_quickhelp= (g:netrw_quickhelp + 1)%len(s:QuickHelp)
" call Decho("#2: quickhelp=".g:netrw_quickhelp." ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
setl ma noro nowrap
- keepj call setline(line('.'),'" Quick Help: <F1>:help '.s:QuickHelp[g:netrw_quickhelp])
+ NetrwKeepj call setline(line('.'),'" Quick Help: <F1>:help '.s:QuickHelp[g:netrw_quickhelp])
setl noma nomod nowrap
- keepj call netrw#RestorePosn(nbcd_curpos)
- keepj call s:NetrwOptionRestore("s:")
+ NetrwKeepj call netrw#RestorePosn(nbcd_curpos)
+ NetrwKeepj call s:NetrwOptionRestore("s:")
" call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
endif
endif
@@ -4291,8 +4503,9 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
let dirname= dirname.'/'
" call Decho("adjusting dirname<".dirname.">")
endif
+" call Decho("newdir<".newdir."> !~ dirpat<".dirpat.">? ".((newdir !~ dirpat)? "yes" : "no"))
- if newdir !~ dirpat
+ if newdir !~ dirpat && !(a:islocal && isdirectory(newdir))
" ------------------------------
" NetrwBrowseChgDir: edit a file {{{3
" ------------------------------
@@ -4308,7 +4521,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict") && newdir !~ '^\(/\|\a:\)'
" call Decho("edit-a-file: handle tree listing: w:netrw_treedict<".(exists("w:netrw_treedict")? string(w:netrw_treedict) : 'n/a').">")
" call Decho("edit-a-file: newdir<".newdir.">")
- let dirname= s:NetrwTreeDir()
+ let dirname= s:NetrwTreeDir(a:islocal)
if dirname =~ '/$'
let dirname= dirname.newdir
else
@@ -4322,29 +4535,40 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
let dirname= s:ComposePath(dirname,newdir)
endif
" call Decho("edit-a-file: handling a file: dirname<".dirname."> (a:0=".a:0.")")
- " this lets NetrwBrowseX avoid the edit
+ " this lets netrw#BrowseX avoid the edit
if a:0 < 1
" call Decho("edit-a-file: set up windows for editing<".fnameescape(dirname)."> didsplit=".(exists("s:didsplit")? s:didsplit : "doesn't exist"))
- keepj call s:NetrwOptionRestore("s:")
+ NetrwKeepj call s:NetrwOptionRestore("s:")
if !exists("s:didsplit")
-" call Decho("edit-a-file: s:didsplit does not exist; g:netrw_browse_split=".g:netrw_browse_split." win#".winnr())
- if g:netrw_browse_split == 1
+" call Decho("edit-a-file: s:didsplit does not exist; g:netrw_browse_split=".string(g:netrw_browse_split)." win#".winnr())
+ if type(g:netrw_browse_split) == 3
+ " open file in server
+ " Note that g:netrw_browse_split is a List: [servername,tabnr,winnr]
+" call Decho("edit-a-file: open file in server")
+ call s:NetrwServerEdit(a:islocal,dirname)
+" call Dret("s:NetrwBrowseChgDir")
+ return
+ elseif g:netrw_browse_split == 1
" horizontally splitting the window first
+" call Decho("edit-a-file: horizontally splitting window prior to edit")
keepalt new
if !&ea
keepalt wincmd _
endif
elseif g:netrw_browse_split == 2
" vertically splitting the window first
+" call Decho("edit-a-file: vertically splitting window prior to edit")
keepalt rightb vert new
if !&ea
keepalt wincmd |
endif
elseif g:netrw_browse_split == 3
" open file in new tab
+" call Decho("edit-a-file: opening new tab prior to edit")
keepalt tabnew
elseif g:netrw_browse_split == 4
" act like "P" (ie. open previous window)
+" call Decho("edit-a-file: use previous window for edit")
if s:NetrwPrevWinOpen(2) == 3
let @@= ykeep
" call Dret("s:NetrwBrowseChgDir")
@@ -4356,15 +4580,16 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
call s:NetrwMenu(0)
" optional change to window
if g:netrw_chgwin >= 1
+" call Decho("edit-a-file: changing window to #".g:netrw_chgwin)
if winnr("$")+1 == g:netrw_chgwin
" if g:netrw_chgwin is set to one more than the last window, then
" vertically split the last window to make that window available.
let curwin= winnr()
- exe "keepj keepalt ".g:netrw_chgwin."wincmd ".winnr("$")
+ exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd ".winnr("$")
vs
- exe "keepj keepalt ".g:netrw_chgwin."wincmd ".curwin
+ exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd ".curwin
endif
- exe "keepj keepalt ".g:netrw_chgwin."wincmd w"
+ exe "NetrwKeepj keepalt ".g:netrw_chgwin."wincmd w"
endif
endif
endif
@@ -4377,13 +4602,12 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" some like c-^ to return to the last edited file
" others like c-^ to return to the netrw buffer
if exists("g:netrw_altfile") && g:netrw_altfile
- exe "keepj keepalt e! ".fnameescape(dirname)
+ exe "NetrwKeepj keepalt e! ".fnameescape(dirname)
else
- exe "keepj e! ".fnameescape(dirname)
+ exe "NetrwKeepj e! ".fnameescape(dirname)
endif
-" call Decho("after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod)
+" call Decho("edit-a-file: after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod)
call s:NetrwCursor()
-" call Decho("COMBAK#1: after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod)
if &hidden || &bufhidden == "hide"
" file came from vim's hidden storage. Don't "restore" options with it.
let dorestore= 0
@@ -4392,7 +4616,6 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" call Decho("edit-a-file: remote file: NetrwBrowse will edit it")
endif
let dolockout= 1
-" call Decho("COMBAK#2: after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod)
" handle g:Netrw_funcref -- call external-to-netrw functions
" This code will handle g:Netrw_funcref as an individual function reference
@@ -4402,12 +4625,12 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" call Decho("edit-a-file: handle optional Funcrefs")
if type(g:Netrw_funcref) == 2
" call Decho("edit-a-file: handling a g:Netrw_funcref")
- keepj call g:Netrw_funcref()
+ NetrwKeepj call g:Netrw_funcref()
elseif type(g:Netrw_funcref) == 3
" call Decho("edit-a-file: handling a list of g:Netrw_funcrefs")
for Fncref in g:Netrw_funcref
if type(FncRef) == 2
- keepj call FncRef()
+ NetrwKeepj call FncRef()
endif
endfor
endif
@@ -4419,16 +4642,16 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" NetrwBrowseChgDir: just go to the new directory spec {{{3
" ----------------------------------------------------
" call Decho('goto-newdir: case "just go to new directory spec": newdir<'.newdir.'>')
- let dirname = newdir
- keepj call s:SetRexDir(a:islocal,dirname)
- keepj call s:NetrwOptionRestore("s:")
+ let dirname = newdir
+ NetrwKeepj call s:SetRexDir(a:islocal,dirname)
+ NetrwKeepj call s:NetrwOptionRestore("s:")
elseif newdir == './'
" ---------------------------------------------
" NetrwBrowseChgDir: refresh the directory list {{{3
" ---------------------------------------------
" call Decho('refresh-dirlist: case "refresh directory listing": newdir == "./"')
- keepj call s:SetRexDir(a:islocal,dirname)
+ NetrwKeepj call s:SetRexDir(a:islocal,dirname)
elseif newdir == '../'
" --------------------------------------
@@ -4441,7 +4664,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" call Decho("go-up: clear buffer<".expand("%")."> with :%d")
" call Decho("go-up: setl noro ma")
setl noro ma
- keepj %d
+ NetrwKeepj %d
endif
if has("amiga")
@@ -4483,7 +4706,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
endif
" call Decho("go-up: unix: dirname<".dirname."> (go up one dir)")
endif
- keepj call s:SetRexDir(a:islocal,dirname)
+ NetrwKeepj call s:SetRexDir(a:islocal,dirname)
elseif exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
" --------------------------------------
@@ -4495,14 +4718,15 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
setl noro ma
if !(exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("b:netrw_curdir"))
" call Decho("tree-list: clear buffer<".expand("%")."> with :%d")
- keepj %d
+ NetrwKeepj %d
endif
- let treedir = s:NetrwTreeDir()
+ let treedir = s:NetrwTreeDir(a:islocal)
let s:treecurpos = nbcd_curpos
- let haskey= 0
+ let haskey = 0
" call Decho("tree-list: w:netrw_treedict<".string(w:netrw_treedict).">")
" search treedict for tree dir as-is
+" call Decho("search treedict for tree dir as-is")
if has_key(w:netrw_treedict,treedir)
" call Decho('tree-list: ....searched for treedir<'.treedir.'> : found it!')
let haskey= 1
@@ -4511,6 +4735,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
endif
" search treedict for treedir with a / appended
+" call Decho("search treedict for treedir with a / appended")
if !haskey && treedir !~ '/$'
if has_key(w:netrw_treedict,treedir."/")
let treedir= treedir."/"
@@ -4522,6 +4747,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
endif
" search treedict for treedir with any trailing / elided
+" call Decho("search treedict for treedir with any trailing / elided")
if !haskey && treedir =~ '/$'
let treedir= substitute(treedir,'/$','','')
if has_key(w:netrw_treedict,treedir)
@@ -4532,6 +4758,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
endif
endif
+" call Decho("haskey=".haskey)
if haskey
" close tree listing for selected subdirectory
" call Decho("tree-list: closing selected subdirectory<".dirname.">")
@@ -4543,8 +4770,10 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" go down one directory
let dirname= substitute(treedir,'/*$','/','')
" call Decho("tree-list: go down one dir: treedir<".treedir.">")
+" call Decho("tree-list: ... : dirname<".dirname.">")
endif
- keepj call s:SetRexDir(a:islocal,dirname)
+ NetrwKeepj call s:SetRexDir(a:islocal,dirname)
+" call Decho("setting s:treeforceredraw to true")
let s:treeforceredraw = 1
else
@@ -4553,22 +4782,20 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" ----------------------------------------
let dirname = s:ComposePath(dirname,newdir)
" call Decho("go down one dir: dirname<".dirname."> newdir<".newdir.">")
- keepj call s:SetRexDir(a:islocal,dirname)
+ NetrwKeepj call s:SetRexDir(a:islocal,dirname)
endif
" --------------------------------------
" NetrwBrowseChgDir: Restore and Cleanup {{{3
" --------------------------------------
-" call Decho("COMBAK#3: after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod)
if dorestore
" dorestore is zero'd when a local file was hidden or bufhidden;
" in such a case, we want to keep whatever settings it may have.
" call Decho("doing option restore (dorestore=".dorestore.")")
- keepj call s:NetrwOptionRestore("s:")
+ NetrwKeepj call s:NetrwOptionRestore("s:")
" else " Decho
" call Decho("skipping option restore (dorestore==0): hidden=".&hidden." bufhidden=".&bufhidden." mod=".&mod)
endif
-" call Decho("COMBAK#4: after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod)
if dolockout && dorestore
" call Decho("restore: filewritable(dirname<".dirname.">)=".filewritable(dirname))
if filewritable(dirname)
@@ -4583,7 +4810,6 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" call Decho("restore: ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
endif
endif
-" call Decho("COMBAK#5: after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod)
let @@= ykeep
" call Dret("s:NetrwBrowseChgDir <".dirname."> : curpos<".string(getpos(".")).">")
@@ -4596,9 +4822,18 @@ endfun
" for tree, keeps cursor on current filename
fun! s:NetrwBrowseUpDir(islocal)
" call Dfunc("s:NetrwBrowseUpDir(islocal=".a:islocal.")")
+ if exists("w:netrw_bannercnt") && line(".") < w:netrw_bannercnt-1
+ " this test needed because occasionally this function seems to be incorrectly called
+ " when multiple leftmouse clicks are taken when atop the one line help in the banner.
+ " I'm allowing the very bottom line to permit a "-" exit so that one may escape empty
+ " directories.
+" call Dret("s:NetrwBrowseUpDir : cursor not in file area")
+ return
+ endif
+
norm! 0
if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
-" call Decho("ftp + treestyle")
+" call Decho("case: treestyle")
let curline= getline(".")
let swwline= winline() - 1
if exists("w:netrw_treetop")
@@ -4611,19 +4846,19 @@ fun! s:NetrwBrowseUpDir(islocal)
endif
if !search('\c^'.s:treedepthstring.curline,'cw')
if !search('\c^'.curline,'cw')
- sil! keepj 1
+ sil! NetrwKeepj 1
endif
endif
- exe "sil! keepj norm! z\<cr>"
+ exe "sil! NetrwKeepj norm! z\<cr>"
while winline() < swwline
let curwinline= winline()
- exe "sil! keepj norm! \<c-y>"
+ exe "sil! NetrwKeepj norm! \<c-y>"
if curwinline == winline()
break
endif
endwhile
else
-" call Decho("ftp + not treestyle")
+" call Decho("case: not treestyle")
if a:islocal
call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,'../'))
else
@@ -4640,15 +4875,26 @@ fun! s:NetrwBrowseUpDir(islocal)
endfun
" ---------------------------------------------------------------------
-" s:NetrwBrowseX: (implements "x") executes a special "viewer" script or program for the {{{2
+" netrw#BrowseX: (implements "x") executes a special "viewer" script or program for the {{{2
" given filename; typically this means given their extension.
" 0=local, 1=remote
-fun! netrw#NetrwBrowseX(fname,remote)
-" call Dfunc("NetrwBrowseX(fname<".a:fname."> remote=".a:remote.")")
+fun! netrw#BrowseX(fname,remote)
+" call Dfunc("netrw#BrowseX(fname<".a:fname."> remote=".a:remote.")")
+
+ " if its really just a directory, then do a "gf" instead
+ if a:fname =~ '/$'
+ norm! gf
+" call Dret("netrw#BrowseX : did gf instead")
+ endif
+
let ykeep = @@
let screenposn = netrw#SavePosn()
+ " need to save and restore aw setting as gx can invoke this function from non-netrw buffers
+ let awkeep = &aw
+ set noaw
+
" special core dump handler
if a:fname =~ '/core\(\.\d\+\)\=$'
if exists("g:Netrw_corehandler")
@@ -4667,7 +4913,8 @@ fun! netrw#NetrwBrowseX(fname,remote)
endif
call netrw#RestorePosn(screenposn)
let @@= ykeep
-" call Dret("NetrwBrowseX : coredump handler invoked")
+ let &aw= awkeep
+" call Dret("netrw#BrowseX : coredump handler invoked")
return
endif
endif
@@ -4680,21 +4927,6 @@ fun! netrw#NetrwBrowseX(fname,remote)
endif
" call Decho("exten<".exten.">")
- " seems kde systems often have gnome-open due to dependencies, even though
- " gnome-open's subsidiary display tools are largely absent. Kde systems
- " usually have "kdeinit" running, though... (tnx Mikolaj Machowski)
- if !exists("s:haskdeinit")
- if has("unix") && executable("ps") && !has("win32unix")
- let s:haskdeinit= system("ps -e") =~ 'kdeinit'
- if v:shell_error
- let s:haskdeinit = 0
- endif
- else
- let s:haskdeinit= 0
- endif
-" call Decho("setting s:haskdeinit=".s:haskdeinit)
- endif
-
if a:remote == 1
" create a local copy
" call Decho("remote: a:remote=".a:remote.": create a local copy of <".a:fname.">")
@@ -4769,18 +5001,15 @@ fun! netrw#NetrwBrowseX(fname,remote)
elseif exists("g:netrw_browsex_viewer") && executable(viewer)
" call Decho("g:netrw_browsex_viewer<".g:netrw_browsex_viewer.">")
-" call Decho("exe sil !".viewer." ".viewopt.shellescape(fname,1).redir)
- exe "sil !".viewer." ".viewopt.shellescape(fname,1).redir
+ call s:NetrwExe("sil !".viewer." ".viewopt.shellescape(fname,1).redir)
let ret= v:shell_error
elseif has("win32") || has("win64")
" call Decho("windows")
if executable("start")
-" call Decho('exe sil !start rundll32 url.dll,FileProtocolHandler '.shellescape(fname,1))
- exe 'sil !start rundll32 url.dll,FileProtocolHandler '.shellescape(fname,1)
+ call s:NetrwExe('sil! !start rundll32 url.dll,FileProtocolHandler '.shellescape(fname,1))
elseif executable("rundll32")
-" call Decho('exe sil !rundll32 url.dll,FileProtocolHandler '.shellescape(fname,1))
- exe 'sil !rundll32 url.dll,FileProtocolHandler '.shellescape(fname,1)
+ call s:NetrwExe('sil! !rundll32 url.dll,FileProtocolHandler '.shellescape(fname,1))
else
call netrw#ErrorMsg(s:WARNING,"rundll32 not on path",74)
endif
@@ -4791,33 +5020,30 @@ fun! netrw#NetrwBrowseX(fname,remote)
let winfname= 'c:\cygwin'.substitute(fname,'/','\\','g')
" call Decho("cygwin: winfname<".shellescape(winfname,1).">")
if executable("start")
-" call Decho('exe sil !start rundll32 url.dll,FileProtocolHandler '.shellescape(winfname,1))
- exe 'sil !start rundll32 url.dll,FileProtocolHandler '.shellescape(winfname,1)
+ call s:NetrwExe('sil !start rundll32 url.dll,FileProtocolHandler '.shellescape(winfname,1))
elseif executable("rundll32")
-" call Decho('exe sil !rundll32 url.dll,FileProtocolHandler '.shellescape(winfname,1))
- exe 'sil !rundll32 url.dll,FileProtocolHandler '.shellescape(winfname,1)
+ call s:NetrwExe('sil !rundll32 url.dll,FileProtocolHandler '.shellescape(winfname,1))
+ elseif executable("cygstart")
+ call s:NetrwExe('sil !cygstart '.shellescape(fname,1))
else
call netrw#ErrorMsg(s:WARNING,"rundll32 not on path",74)
endif
call inputsave()|call input("Press <cr> to continue")|call inputrestore()
let ret= v:shell_error
- elseif has("unix") && executable("xdg-open") && !s:haskdeinit
+ elseif has("unix") && executable("xdg-open") && !s:CheckIfKde()
" call Decho("unix and xdg-open")
-" call Decho("exe sil !xdg-open ".shellescape(fname,1)." ".redir)
- exe "sil !xdg-open ".shellescape(fname,1).redir
+ call s:NetrwExe("sil !xdg-open ".shellescape(fname,1).redir)
let ret= v:shell_error
- elseif has("unix") && executable("kfmclient") && s:haskdeinit
+ elseif has("unix") && executable("kfmclient") && s:CheckIfKde()
" call Decho("unix and kfmclient")
-" call Decho("exe sil !kfmclient exec ".shellescape(fname,1)." ".redir)
- exe "sil !kfmclient exec ".shellescape(fname,1)." ".redir
+ call s:NetrwExe("sil !kfmclient exec ".shellescape(fname,1)." ".redir)
let ret= v:shell_error
elseif has("macunix") && executable("open")
" call Decho("macunix and open")
-" call Decho("exe sil !open ".shellescape(fname,1)." ".redir)
- exe "sil !open ".shellescape(fname,1)." ".redir
+ call s:NetrwExe("sil !open ".shellescape(fname,1)." ".redir)
let ret= v:shell_error
else
@@ -4848,13 +5074,39 @@ fun! netrw#NetrwBrowseX(fname,remote)
if g:netrw_use_noswf
setl noswf
endif
- exe "sil! keepj norm! \<c-o>"
+ exe "sil! NetrwKeepj norm! \<c-o>"
" redraw!
endif
call netrw#RestorePosn(screenposn)
- let @@= ykeep
+ let @@ = ykeep
+ let &aw= awkeep
-" call Dret("NetrwBrowseX")
+" call Dret("netrw#BrowseX")
+endfun
+
+" ---------------------------------------------------------------------
+" netrw#BrowseXVis: used by gx in visual mode to select a file for browsing {{{2
+fun! netrw#BrowseXVis()
+" call Dfunc("netrw#BrowseXVis()")
+ let atkeep = @@
+ norm! gvy
+" call Decho("@@<".@@.">")
+ call netrw#BrowseX(@@,netrw#CheckIfRemote())
+ let @@ = atkeep
+" call Dret("netrw#BrowseXVis")
+endfun
+
+" ---------------------------------------------------------------------
+" netrw#CheckIfRemote: returns 1 if current file looks like an url, 0 else {{{2
+fun! netrw#CheckIfRemote()
+" call Dfunc("netrw#CheckIfRemote()")
+ if expand("%") =~ '^\a\+://'
+" call Dret("netrw#CheckIfRemote 1")
+ return 1
+ else
+" call Dret("netrw#CheckIfRemote 0")
+ return 0
+ endif
endfun
" ---------------------------------------------------------------------
@@ -4870,16 +5122,41 @@ fun! s:NetrwChgPerm(islocal,curdir)
" call Decho("chgperm<".chgperm.">")
call system(chgperm)
if v:shell_error != 0
- keepj call netrw#ErrorMsg(1,"changing permission on file<".expand("<cfile>")."> seems to have failed",75)
+ NetrwKeepj call netrw#ErrorMsg(1,"changing permission on file<".expand("<cfile>")."> seems to have failed",75)
endif
if a:islocal
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
endif
let @@= ykeep
" call Dret("s:NetrwChgPerm")
endfun
" ---------------------------------------------------------------------
+" s:CheckIfKde: checks if kdeinit is running {{{2
+" Returns 0: kdeinit not running
+" 1: kdeinit is running
+fun! s:CheckIfKde()
+" call Dfunc("s:CheckIfKde()")
+ " seems kde systems often have gnome-open due to dependencies, even though
+ " gnome-open's subsidiary display tools are largely absent. Kde systems
+ " usually have "kdeinit" running, though... (tnx Mikolaj Machowski)
+ if !exists("s:haskdeinit")
+ if has("unix") && executable("ps") && !has("win32unix")
+ let s:haskdeinit= system("ps -e") =~ '\<kdeinit'
+ if v:shell_error
+ let s:haskdeinit = 0
+ endif
+ else
+ let s:haskdeinit= 0
+ endif
+" call Decho("setting s:haskdeinit=".s:haskdeinit)
+ endif
+
+" call Dret("s:CheckIfKde ".s:haskdeinit)
+ return s:haskdeinit
+endfun
+
+" ---------------------------------------------------------------------
" s:NetrwClearExplore: clear explore variables (if any) {{{2
fun! s:NetrwClearExplore()
" call Dfunc("s:NetrwClearExplore()")
@@ -4994,24 +5271,24 @@ fun! s:NetrwHide(islocal)
" call Decho("hide: g:netrw_list_hide<".g:netrw_list_hide.">")
endif
endfor
- keepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
+ NetrwKeepj call s:NetrwUnmarkList(bufnr("%"),b:netrw_curdir)
let g:netrw_hide= 1
else
" switch between show-all/show-not-hidden/show-hidden
let g:netrw_hide=(g:netrw_hide+1)%3
- exe "keepj norm! 0"
+ exe "NetrwKeepj norm! 0"
if g:netrw_hide && g:netrw_list_hide == ""
- keepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"your hiding list is empty!",49)
let @@= ykeep
" call Dret("NetrwHide")
return
endif
endif
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
let @@= ykeep
" call Dret("NetrwHide")
endfun
@@ -5034,8 +5311,8 @@ fun! s:NetrwHidden(islocal)
endif
" refresh screen and return to saved position
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
let @@= ykeep
" call Dret("s:NetrwHidden")
endfun
@@ -5101,22 +5378,24 @@ fun! s:NetrwLeftmouse(islocal)
" call Dret("s:NetrwLeftmouse : detected a status bar leftmouse click")
return
endif
- " NOTE: following test is preventing leftmouse selection/deselection of directories and files in treelist mode (Dec 04, 2013)
+ " Dec 04, 2013: following test prevents leftmouse selection/deselection of directories and files in treelist mode
" Windows are separated by vertical separator bars - but the mouse seems to be doing what it should when dragging that bar
- " without this test.
-" if v:mouse_col != col('.')
-" let @@= ykeep
-" call Dret("s:NetrwLeftmouse : detected a vertical separator bar leftmouse click")
-" return
-" endif
+ " without this test when its disabled.
+ " May 26, 2014: edit file, :Lex, resize window -- causes refresh. Reinstated a modified test. See if problems develop.
+" call Decho("v:mouse_col=".v:mouse_col." col#".col('.')." virtcol#".virtcol('.')." col($)#".col("$")." virtcol($)#".virtcol("$"))
+ if v:mouse_col > virtcol('.')
+ let @@= ykeep
+" call Dret("s:NetrwLeftmouse : detected a vertical separator bar leftmouse click")
+ return
+ endif
if a:islocal
if exists("b:netrw_curdir")
- keepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
+ NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
endif
else
if exists("b:netrw_curdir")
- keepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord()))
+ NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord()))
endif
endif
let @@= ykeep
@@ -5124,6 +5403,128 @@ fun! s:NetrwLeftmouse(islocal)
endfun
" ---------------------------------------------------------------------
+" s:NetrwServerEdit: edit file in a server gvim, usually NETRWSERVER (implements <c-r>){{{2
+" a:islocal=0 : <c-r> not used, remote
+" a:islocal=1 : <c-r> no used, local
+" a:islocal=2 : <c-r> used, remote
+" a:islocal=3 : <c-r> used, local
+fun! s:NetrwServerEdit(islocal,fname)
+" call Dfunc("s:NetrwServerEdit(islocal=".a:islocal.",fname<".a:fname.">)")
+ let islocal = a:islocal%2 " =0: remote =1: local
+ let ctrlr = a:islocal >= 2 " =0: <c-r> not used =1: <c-r> used
+
+ if (islocal && isdirectory(a:fname)) || (!islocal && a:fname =~ '/$')
+ " handle directories in the local window -- not in the remote vim server
+ " user must have closed the NETRWSERVER window. Treat as a normal editing from netrw.
+ let g:netrw_browse_split= 0
+ if exists("s:netrw_browse_split_".winnr())
+ let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
+ unlet s:netrw_browse_split_{winnr()}
+ endif
+ call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname))
+" call Dret("s:NetrwServerEdit")
+ return
+ endif
+
+ if has("clientserver") && executable("gvim")
+" call Decho("has clientserver and gvim")
+
+ if exists("g:netrw_browse_split") && type(g:netrw_browse_split) == 3
+" call Decho("g:netrw_browse_split=".string(g:netrw_browse_split))
+ let srvrname = g:netrw_browse_split[0]
+ let tabnum = g:netrw_browse_split[1]
+ let winnum = g:netrw_browse_split[2]
+
+ if serverlist() !~ '\<'.srvrname.'\>'
+" call Decho("server not available; ctrlr=".ctrlr)
+
+ if !ctrlr
+ " user must have closed the server window and the user did not use <c-r>, but
+ " used something like <cr>.
+" call Decho("user must have closed server AND did not use ctrl-r")
+ if exists("g:netrw_browse_split")
+ unlet g:netrw_browse_split
+ endif
+ let g:netrw_browse_split= 0
+ if exists("s:netrw_browse_split_".winnr())
+ let g:netrw_browse_split= s:netrw_browse_split_{winnr()}
+ endif
+ call s:NetrwBrowseChgDir(islocal,a:fname)
+" call Dret("s:NetrwServerEdit")
+ return
+
+ elseif has("win32") && executable("start")
+ " start up remote netrw server under windows
+" call Decho("starting up gvim server<".srvrname."> for windows")
+ call system("start gvim --servername ".srvrname)
+
+ else
+ " start up remote netrw server under linux
+" call Decho("starting up gvim server<".srvrname.">")
+ call system("gvim --servername ".srvrname)
+ endif
+ endif
+
+" call Decho("srvrname<".srvrname."> tabnum=".tabnum." winnum=".winnum." server-editing<".a:fname.">")
+ call remote_send(srvrname,":tabn ".tabnum."\<cr>")
+ call remote_send(srvrname,":".winnum."wincmd w\<cr>")
+ call remote_send(srvrname,":e ".fnameescape(a:fname)."\<cr>")
+
+ else
+
+ if serverlist() !~ '\<'.g:netrw_servername.'\>'
+
+ if !ctrlr
+" call Decho("server<".g:netrw_servername."> not available and ctrl-r not used")
+ if exists("g:netrw_browse_split")
+ unlet g:netrw_browse_split
+ endif
+ let g:netrw_browse_split= 0
+ call s:NetrwBrowse(islocal,s:NetrwBrowseChgDir(islocal,a:fname))
+" call Dret("s:NetrwServerEdit")
+ return
+
+ else
+" call Decho("server<".g:netrw_servername."> not available but ctrl-r used")
+ if has("win32") && executable("start")
+ " start up remote netrw server under windows
+" call Decho("starting up gvim server<".g:netrw_servername."> for windows")
+ call system("start gvim --servername ".g:netrw_servername)
+ else
+ " start up remote netrw server under linux
+" call Decho("starting up gvim server<".g:netrw_servername.">")
+ call system("gvim --servername ".g:netrw_servername)
+ endif
+ endif
+ endif
+
+ while 1
+ try
+" call Decho("remote-send: e ".a:fname)
+ call remote_send(g:netrw_servername,":e ".fnameescape(a:fname)."\<cr>")
+ break
+ catch /^Vim\%((\a\+)\)\=:E241/
+ sleep 200m
+ endtry
+ endwhile
+
+ if exists("g:netrw_browse_split")
+ if type(g:netrw_browse_split) != 3
+ let s:netrw_browse_split_{winnr()}= g:netrw_browse_split
+ endif
+ unlet g:netrw_browse_split
+ endif
+ let g:netrw_browse_split= [g:netrw_servername,1,1]
+ endif
+
+ else
+ call netrw#ErrorMsg(s:ERROR,"you need a gui-capable vim and client-server to use <ctrl-r>",98)
+ endif
+
+" call Dret("s:NetrwServerEdit")
+endfun
+
+" ---------------------------------------------------------------------
" s:NetrwSLeftmouse: marks the file under the cursor. May be dragged to select additional files {{{2
fun! s:NetrwSLeftmouse(islocal)
" call Dfunc("s:NetrwSLeftmouse(islocal=".a:islocal.")")
@@ -5177,7 +5578,7 @@ endfun
" s:NetrwListHide: uses [range]g~...~d to delete files that match comma {{{2
" separated patterns given in g:netrw_list_hide
fun! s:NetrwListHide()
-" call Dfunc("NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">")
+" call Dfunc("s:NetrwListHide() g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">")
let ykeep= @@
" find a character not in the "hide" string to use as a separator for :g and :v commands
@@ -5200,23 +5601,23 @@ fun! s:NetrwListHide()
" Prune the list by hiding any files which match
if g:netrw_hide == 1
" call Decho("hiding<".hide."> listhide<".listhide.">")
- exe 'sil! keepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d'
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'d'
elseif g:netrw_hide == 2
" call Decho("showing<".hide."> listhide<".listhide.">")
- exe 'sil! keepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @'
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g'.sep.hide.sep.'s@^@ /-KEEP-/ @'
endif
endwhile
if g:netrw_hide == 2
- exe 'sil! keepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d'
- exe 'sil! keepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e'
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$v@^ /-KEEP-/ @d'
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s@^\%( /-KEEP-/ \)\+@@e'
endif
" remove any blank lines that have somehow remained.
" This seems to happen under Windows.
- exe 'sil! keepj 1,$g@^\s*$@d'
+ exe 'sil! NetrwKeepj 1,$g@^\s*$@d'
let @@= ykeep
-" call Dret("NetrwListHide")
+" call Dret("s:NetrwListHide")
endfun
" ---------------------------------------------------------------------
@@ -5236,7 +5637,7 @@ fun! s:NetrwHideEdit(islocal)
" call Decho("new g:netrw_list_hide<".g:netrw_list_hide.">")
" refresh the listing
- sil keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./"))
+ sil NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,"./"))
" restore cursor position
call netrw#RestorePosn(svpos)
@@ -5258,8 +5659,8 @@ fun! s:NetSortSequence(islocal)
" refresh the listing
let g:netrw_sort_sequence= newsortseq
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
let @@= ykeep
" call Dret("NetSortSequence")
@@ -5267,6 +5668,7 @@ endfun
" ---------------------------------------------------------------------
" s:NetrwMakeDir: this function makes a directory (both local and remote) {{{2
+" implements the "d" mapping.
fun! s:NetrwMakeDir(usrhost)
" call Dfunc("s:NetrwMakeDir(usrhost<".a:usrhost.">)")
@@ -5294,7 +5696,7 @@ fun! s:NetrwMakeDir(usrhost)
" call Decho("fullnewdir<".fullnewdir.">")
if isdirectory(fullnewdir)
if !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a directory!",24)
endif
let @@= ykeep
" call Dret("s:NetrwMakeDir : directory<".newdirname."> exists previously")
@@ -5302,7 +5704,7 @@ fun! s:NetrwMakeDir(usrhost)
endif
if s:FileReadable(fullnewdir)
if !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"<".newdirname."> is already a file!",25)
endif
let @@= ykeep
" call Dret("s:NetrwMakeDir : file<".newdirname."> exists previously")
@@ -5321,8 +5723,7 @@ fun! s:NetrwMakeDir(usrhost)
let netrw_origdir= s:NetrwGetcwd(1)
call s:NetrwLcd(b:netrw_curdir)
" call Decho("netrw_origdir<".netrw_origdir.">: lcd b:netrw_curdir<".fnameescape(b:netrw_curdir).">")
-" call Decho("exe sil! !".g:netrw_localmkdir.' '.shellescape(newdirname,1))
- exe "sil! !".g:netrw_localmkdir.' '.shellescape(newdirname,1)
+ call s:NetrwExe("sil! !".g:netrw_localmkdir.' '.shellescape(newdirname,1))
if v:shell_error != 0
let @@= ykeep
call netrw#ErrorMsg(s:ERROR,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
@@ -5351,15 +5752,14 @@ fun! s:NetrwMakeDir(usrhost)
" call Decho("remote mkdir")
let mkdircmd = s:MakeSshCmd(g:netrw_mkdir_cmd)
let newdirname= substitute(b:netrw_curdir,'^\%(.\{-}/\)\{3}\(.*\)$','\1','').newdirname
-" call Decho("exe sil! !".mkdircmd." ".shellescape(newdirname,1))
- exe "sil! !".mkdircmd." ".shellescape(newdirname,1)
+ call s:NetrwExe("sil! !".mkdircmd." ".shellescape(newdirname,1))
if v:shell_error == 0
" refresh listing
let svpos= netrw#SavePosn()
- keepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
elseif !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to make directory<".newdirname.">",27)
endif
" redraw!
@@ -5374,8 +5774,8 @@ fun! s:NetrwMakeDir(usrhost)
let remotepath= ""
endif
call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
- keepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
elseif b:netrw_method == 3
" Remote mkdir: using ftp + machine, id, passwd, and fname (ie. no .netrc)
@@ -5388,8 +5788,8 @@ fun! s:NetrwMakeDir(usrhost)
let remotepath= ""
endif
call s:NetrwRemoteFtpCmd(remotepath,g:netrw_remote_mkdir.' "'.newdirname.'"')
- keepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
endif
let @@= ykeep
@@ -5452,6 +5852,7 @@ fun! s:NetrwMaps(islocal)
nnoremap <buffer> <silent> C :<c-u>call <SID>NetrwSetChgwin()<cr>
nnoremap <buffer> <silent> <cr> :call netrw#LocalBrowseCheck(<SID>NetrwBrowseChgDir(1,<SID>NetrwGetWord()))<cr>
nnoremap <buffer> <silent> <s-cr> :call <SID>TreeSqueezeDir(1)<cr>
+ nnoremap <buffer> <silent> <c-r> :call <SID>NetrwServerEdit(3,<SID>NetrwGetWord())<cr>
nnoremap <buffer> <silent> d :call <SID>NetrwMakeDir("")<cr>
nnoremap <buffer> <silent> - :call <SID>NetrwBrowseUpDir(1)<cr>
nnoremap <buffer> <silent> gb :<c-u>call <SID>NetrwBookHistHandler(1,b:netrw_curdir)<cr>
@@ -5477,8 +5878,9 @@ fun! s:NetrwMaps(islocal)
nnoremap <buffer> <silent> mt :<c-u>call <SID>NetrwMarkFileTgt(1)<cr>
nnoremap <buffer> <silent> mT :<c-u>call <SID>NetrwMarkFileTag(1)<cr>
nnoremap <buffer> <silent> mu :<c-u>call <SID>NetrwUnMarkFile(1)<cr>
- nnoremap <buffer> <silent> mx :<c-u>call <SID>NetrwMarkFileExe(1)<cr>
- nnoremap <buffer> <silent> mX :<c-u>call <SID>NetrwMarkFileVimCmd(1)<cr>
+ nnoremap <buffer> <silent> mv :<c-u>call <SID>NetrwMarkFileVimCmd(1)<cr>
+ nnoremap <buffer> <silent> mx :<c-u>call <SID>NetrwMarkFileExe(1,0)<cr>
+ nnoremap <buffer> <silent> mX :<c-u>call <SID>NetrwMarkFileExe(1,1)<cr>
nnoremap <buffer> <silent> mz :<c-u>call <SID>NetrwMarkFileCompress(1)<cr>
nnoremap <buffer> <silent> O :call <SID>NetrwObtain(1)<cr>
nnoremap <buffer> <silent> o :call <SID>NetrwSplit(3)<cr>
@@ -5496,11 +5898,11 @@ fun! s:NetrwMaps(islocal)
nnoremap <buffer> <silent> u :<c-u>call <SID>NetrwBookHistHandler(4,expand("%"))<cr>
nnoremap <buffer> <silent> U :<c-u>call <SID>NetrwBookHistHandler(5,expand("%"))<cr>
nnoremap <buffer> <silent> v :call <SID>NetrwSplit(5)<cr>
- nnoremap <buffer> <silent> x :call netrw#NetrwBrowseX(<SID>NetrwBrowseChgDir(1,<SID>NetrwGetWord(),0),0)"<cr>
+ nnoremap <buffer> <silent> x :call netrw#BrowseX(<SID>NetrwBrowseChgDir(1,<SID>NetrwGetWord(),0),0)"<cr>
nnoremap <buffer> <silent> X :call <SID>NetrwLocalExecute(expand("<cword>"))"<cr>
" local insert-mode maps
inoremap <buffer> <silent> a <c-o>:call <SID>NetrwHide(1)<cr>
- inoremap <buffer> <silent> c <c-o>:exe "keepjumps lcd ".fnameescape(b:netrw_curdir)<cr>
+ inoremap <buffer> <silent> c <c-o>:exe "NetrwKeepj lcd ".fnameescape(b:netrw_curdir)<cr>
inoremap <buffer> <silent> c <c-o>:call <SID>NetrwLcd(b:netrw_curdir)<cr>
inoremap <buffer> <silent> C <c-o>:call <SID>NetrwSetChgwin()<cr>
inoremap <buffer> <silent> % <c-o>:call <SID>NetrwOpenFile(1)<cr>
@@ -5528,8 +5930,9 @@ fun! s:NetrwMaps(islocal)
inoremap <buffer> <silent> mT <c-o>:<c-u>call <SID>NetrwMarkFileTag(1)<cr>
inoremap <buffer> <silent> mt <c-o>:<c-u>call <SID>NetrwMarkFileTgt(1)<cr>
inoremap <buffer> <silent> mu <c-o>:<c-u>call <SID>NetrwUnMarkFile(1)<cr>
- inoremap <buffer> <silent> mx <c-o>:<c-u>call <SID>NetrwMarkFileExe(1)<cr>
- inoremap <buffer> <silent> mX <c-o>:<c-u>call <SID>NetrwMarkFileVimCmd(1)<cr>
+ inoremap <buffer> <silent> mv <c-o>:<c-u>call <SID>NetrwMarkFileVimCmd(1)<cr>
+ inoremap <buffer> <silent> mx <c-o>:<c-u>call <SID>NetrwMarkFileExe(1,0)<cr>
+ inoremap <buffer> <silent> mX <c-o>:<c-u>call <SID>NetrwMarkFileExe(1,1)<cr>
inoremap <buffer> <silent> mz <c-o>:<c-u>call <SID>NetrwMarkFileCompress(1)<cr>
inoremap <buffer> <silent> O <c-o>:call <SID>NetrwObtain(1)<cr>
inoremap <buffer> <silent> o <c-o>:call <SID>NetrwSplit(3)<cr>
@@ -5547,7 +5950,7 @@ fun! s:NetrwMaps(islocal)
inoremap <buffer> <silent> u <c-o>:<c-u>call <SID>NetrwBookHistHandler(4,expand("%"))<cr>
inoremap <buffer> <silent> U <c-o>:<c-u>call <SID>NetrwBookHistHandler(5,expand("%"))<cr>
inoremap <buffer> <silent> v <c-o>:call <SID>NetrwSplit(5)<cr>
- inoremap <buffer> <silent> x <c-o>:call netrw#NetrwBrowseX(<SID>NetrwBrowseChgDir(1,<SID>NetrwGetWord(),0),0)"<cr>
+ inoremap <buffer> <silent> x <c-o>:call netrw#BrowseX(<SID>NetrwBrowseChgDir(1,<SID>NetrwGetWord(),0),0)"<cr>
if !hasmapto('<Plug>NetrwHideEdit')
nmap <buffer> <unique> <c-h> <Plug>NetrwHideEdit
imap <buffer> <unique> <c-h> <Plug>NetrwHideEdit
@@ -5608,6 +6011,7 @@ fun! s:NetrwMaps(islocal)
nnoremap <buffer> <silent> <cr> :call <SID>NetrwBrowse(0,<SID>NetrwBrowseChgDir(0,<SID>NetrwGetWord()))<cr>
nnoremap <buffer> <silent> <s-cr> :call <SID>TreeSqueezeDir(0)<cr>
nnoremap <buffer> <silent> <c-l> :call <SID>NetrwRefresh(0,<SID>NetrwBrowseChgDir(0,'./'))<cr>
+ nnoremap <buffer> <silent> <c-r> :call <SID>NetrwServerEdit(2,<SID>NetrwGetWord())<cr>
nnoremap <buffer> <silent> - :call <SID>NetrwBrowseUpDir(0)<cr>
nnoremap <buffer> <silent> a :call <SID>NetrwHide(0)<cr>
nnoremap <buffer> <silent> mb :<c-u>call <SID>NetrwBookHistHandler(0,b:netrw_curdir)<cr>
@@ -5625,8 +6029,9 @@ fun! s:NetrwMaps(islocal)
nnoremap <buffer> <silent> mt :<c-u>call <SID>NetrwMarkFileTgt(0)<cr>
nnoremap <buffer> <silent> mT :<c-u>call <SID>NetrwMarkFileTag(0)<cr>
nnoremap <buffer> <silent> mu :<c-u>call <SID>NetrwUnMarkFile(0)<cr>
- nnoremap <buffer> <silent> mx :<c-u>call <SID>NetrwMarkFileExe(0)<cr>
- nnoremap <buffer> <silent> mX :<c-u>call <SID>NetrwMarkFileVimCmd(0)<cr>
+ nnoremap <buffer> <silent> mv :<c-u>call <SID>NetrwMarkFileVimCmd(0)<cr>
+ nnoremap <buffer> <silent> mx :<c-u>call <SID>NetrwMarkFileExe(0,0)<cr>
+ nnoremap <buffer> <silent> mX :<c-u>call <SID>NetrwMarkFileExe(0,1)<cr>
nnoremap <buffer> <silent> mz :<c-u>call <SID>NetrwMarkFileCompress(0)<cr>
nnoremap <buffer> <silent> gb :<c-u>call <SID>NetrwBookHistHandler(1,b:netrw_curdir)<cr>
nnoremap <buffer> <silent> gd :<c-u>call <SID>NetrwForceChgDir(0,<SID>NetrwGetWord())<cr>
@@ -5653,7 +6058,7 @@ fun! s:NetrwMaps(islocal)
nnoremap <buffer> <silent> u :<c-u>call <SID>NetrwBookHistHandler(4,b:netrw_curdir)<cr>
nnoremap <buffer> <silent> U :<c-u>call <SID>NetrwBookHistHandler(5,b:netrw_curdir)<cr>
nnoremap <buffer> <silent> v :call <SID>NetrwSplit(2)<cr>
- nnoremap <buffer> <silent> x :call netrw#NetrwBrowseX(<SID>NetrwBrowseChgDir(0,<SID>NetrwGetWord()),1)<cr>
+ nnoremap <buffer> <silent> x :call netrw#BrowseX(<SID>NetrwBrowseChgDir(0,<SID>NetrwGetWord()),1)<cr>
nnoremap <buffer> <silent> % :call <SID>NetrwOpenFile(0)<cr>
" remote insert-mode maps
inoremap <buffer> <silent> <cr> <c-o>:call <SID>NetrwBrowse(0,<SID>NetrwBrowseChgDir(0,<SID>NetrwGetWord()))<cr>
@@ -5675,8 +6080,10 @@ fun! s:NetrwMaps(islocal)
inoremap <buffer> <silent> mt <c-o>:<c-u>call <SID>NetrwMarkFileTgt(0)<cr>
inoremap <buffer> <silent> mT <c-o>:<c-u>call <SID>NetrwMarkFileTag(0)<cr>
inoremap <buffer> <silent> mu <c-o>:<c-u>call <SID>NetrwUnMarkFile(0)<cr>
- inoremap <buffer> <silent> mx <c-o>:<c-u>call <SID>NetrwMarkFileExe(0)<cr>
- inoremap <buffer> <silent> mX <c-o>:<c-u>call <SID>NetrwMarkFileVimCmd(0)<cr>
+ nnoremap <buffer> <silent> mv :<c-u>call <SID>NetrwMarkFileVimCmd(1)<cr>
+ inoremap <buffer> <silent> mx <c-o>:<c-u>call <SID>NetrwMarkFileExe(0,0)<cr>
+ inoremap <buffer> <silent> mX <c-o>:<c-u>call <SID>NetrwMarkFileExe(0,1)<cr>
+ inoremap <buffer> <silent> mv <c-o>:<c-u>call <SID>NetrwMarkFileVimCmd(0)<cr>
inoremap <buffer> <silent> mz <c-o>:<c-u>call <SID>NetrwMarkFileCompress(0)<cr>
inoremap <buffer> <silent> gb <c-o>:<c-u>call <SID>NetrwBookHistHandler(1,b:netrw_curdir)<cr>
inoremap <buffer> <silent> gh <c-o>:<c-u>call <SID>NetrwHidden(0)<cr>
@@ -5701,7 +6108,7 @@ fun! s:NetrwMaps(islocal)
inoremap <buffer> <silent> u <c-o>:<c-u>call <SID>NetrwBookHistHandler(4,b:netrw_curdir)<cr>
inoremap <buffer> <silent> U <c-o>:<c-u>call <SID>NetrwBookHistHandler(5,b:netrw_curdir)<cr>
inoremap <buffer> <silent> v <c-o>:call <SID>NetrwSplit(2)<cr>
- inoremap <buffer> <silent> x <c-o>:call netrw#NetrwBrowseX(<SID>NetrwBrowseChgDir(0,<SID>NetrwGetWord()),1)<cr>
+ inoremap <buffer> <silent> x <c-o>:call netrw#BrowseX(<SID>NetrwBrowseChgDir(0,<SID>NetrwGetWord()),1)<cr>
inoremap <buffer> <silent> % <c-o>:call <SID>NetrwOpenFile(0)<cr>
if !hasmapto('<Plug>NetrwHideEdit')
nmap <buffer> <c-h> <Plug>NetrwHideEdit
@@ -5714,7 +6121,7 @@ fun! s:NetrwMaps(islocal)
endif
let mapsafepath = escape(s:path, s:netrw_map_escape)
- let mapsafeusermach = escape(s:user.s:machine, s:netrw_map_escape)
+ let mapsafeusermach = escape(((s:user == "")? "" : s:user."@").s:machine, s:netrw_map_escape)
nnoremap <buffer> <silent> <Plug>NetrwRefresh :call <SID>NetrwRefresh(0,<SID>NetrwBrowseChgDir(0,'./'))<cr>
if g:netrw_mousemaps == 1
@@ -5755,23 +6162,31 @@ fun! s:NetrwMaps(islocal)
inoremap <buffer> <F1> <c-o>:he netrw-quickhelp<cr>
endif
- keepj call s:SetRexDir(a:islocal,b:netrw_curdir)
+ NetrwKeepj call s:SetRexDir(a:islocal,b:netrw_curdir)
" call Dret("s:NetrwMaps")
endfun
" ---------------------------------------------------------------------
-" s:NetrwCommands: sets up commands available only in the netrw buffer windows {{{2
+" s:NetrwCommands: sets up commands {{{2
+" If -buffer, the command is only available from within netrw buffers
+" Otherwise, the command is available from any window, so long as netrw
+" has been used at least once in the session.
fun! s:NetrwCommands(islocal)
" call Dfunc("s:NetrwCommands(islocal=".a:islocal.")")
+ com! -nargs=* -complete=file -bang NetrwMB call s:NetrwBookmark(<bang>0,<f-args>)
+ com! -nargs=* NetrwC call s:NetrwSetChgwin(<q-args>)
com! Rexplore if exists("w:netrw_rexlocal")|call s:NetrwRexplore(w:netrw_rexlocal,exists("w:netrw_rexdir")? w:netrw_rexdir : ".")|else|call netrw#ErrorMsg(s:WARNING,"not a former netrw window",79)|endif
if a:islocal
- com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(1,<f-args>)
+ com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(1,<f-args>)
else
- com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(0,<f-args>)
+ com! -buffer -nargs=+ -complete=file MF call s:NetrwMarkFiles(0,<f-args>)
endif
- com! -buffer -nargs=? -complete=file MT call s:NetrwMarkTarget(<q-args>)
+ com! -buffer -nargs=? -complete=file MT call s:NetrwMarkTarget(<q-args>)
+ " the following two commands are intended to be used for testing only, so I'm not advertising them in the manual
+ com! -buffer -nargs=0 CRL call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
+ com! -buffer -nargs=0 CRR call <SID>NetrwBrowse(0,<SID>NetrwBrowseChgDir(0,<SID>NetrwGetWord()))
" call Dret("s:NetrwCommands")
endfun
@@ -5964,7 +6379,7 @@ fun! s:NetrwMarkFileCompress(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileCompress")
return
endif
@@ -5992,10 +6407,10 @@ fun! s:NetrwMarkFileCompress(islocal)
if a:islocal
call system(exe." ".fname)
else
- keepj call s:RemoteSystem(exe." ".fname)
+ NetrwKeepj call s:RemoteSystem(exe." ".fname)
endif
else
- keepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to apply<".exe."> to file<".fname.">",50)
endif
endif
unlet sfx
@@ -6007,13 +6422,13 @@ fun! s:NetrwMarkFileCompress(islocal)
call system(netrw#WinPath(g:netrw_compress)." ".shellescape(s:ComposePath(b:netrw_curdir,fname)))
else
" fname not a compressed file, so compress it
- keepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".shellescape(fname))
+ NetrwKeepj call s:RemoteSystem(netrw#WinPath(g:netrw_compress)." ".shellescape(fname))
endif
endfor " for every file in the marked list
call s:NetrwUnmarkList(curbufnr,curdir)
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
endif
" call Dret("s:NetrwMarkFileCompress")
endfun
@@ -6037,14 +6452,14 @@ fun! s:NetrwMarkFileCopy(islocal,...)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileCopy")
return
endif
" call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}))
if !exists("s:netrwmftgt")
- keepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your marked file target is empty! (:help netrw-mt)",67)
" call Dret("s:NetrwMarkFileCopy 0")
return 0
endif
@@ -6063,14 +6478,17 @@ fun! s:NetrwMarkFileCopy(islocal,...)
if simplify(s:netrwmftgt) == simplify(b:netrw_curdir)
if len(s:netrwmarkfilelist_{bufnr('%')}) == 1
" only one marked file
+" call Decho("case: only one marked file")
let args = shellescape(b:netrw_curdir."/".s:netrwmarkfilelist_{bufnr('%')}[0])
let oldname = s:netrwmarkfilelist_{bufnr('%')}[0]
elseif a:0 == 1
+" call Decho("case: handling one input argument")
" this happens when the next case was used to recursively call s:NetrwMarkFileCopy()
let args = shellescape(b:netrw_curdir."/".a:1)
let oldname = a:1
else
" copy multiple marked files inside the same directory
+" call Decho("case: handling a multiple marked files")
let s:recursive= 1
for oldname in s:netrwmarkfilelist_{bufnr("%")}
let ret= s:NetrwMarkFileCopy(a:islocal,oldname)
@@ -6101,18 +6519,34 @@ fun! s:NetrwMarkFileCopy(islocal,...)
let args= substitute(args,'/','\\','g')
let tgt = substitute(tgt, '/','\\','g')
endif
+ if args =~ "'"|let args= substitute(args,"'\\(.*\\)'",'\1','')|endif
+ if tgt =~ "'"|let tgt = substitute(tgt,"'\\(.*\\)'",'\1','') |endif
+ if isdirectory(args)
+" call Decho("args<".args."> is a directory")
+ let copycmd= g:netrw_localcopydircmd
+" call Decho("using copydircmd<".copycmd.">")
+ if !g:netrw_cygwin && (has("win32") || has("win95") || has("win64") || has("win16"))
+ " window's xcopy doesn't copy a directory to a target properly. Instead, it copies a directory's
+ " contents to a target. One must append the source directory name to the target to get xcopy to
+ " do the right thing.
+ let tgt= tgt.'\'.substitute(a:1,'^.*[\\/]','','')
+" call Decho("modified tgt for xcopy")
+ endif
+ else
+ let copycmd= g:netrw_localcopycmd
+ endif
if g:netrw_localcopycmd =~ '\s'
- let copycmd = substitute(g:netrw_localcopycmd,'\s.*$','','')
- let copycmdargs = substitute(g:netrw_localcopycmd,'^.\{-}\(\s.*\)$','\1','')
+ let copycmd = substitute(copycmd,'\s.*$','','')
+ let copycmdargs = substitute(copycmd,'^.\{-}\(\s.*\)$','\1','')
let copycmd = netrw#WinPath(copycmd).copycmdargs
else
- let copycmd = netrw#WinPath(g:netrw_localcopycmd)
+ let copycmd = netrw#WinPath(copycmd)
endif
" call Decho("args <".args.">")
" call Decho("tgt <".tgt.">")
" call Decho("copycmd<".copycmd.">")
-" call Decho("system(".copycmd." ".args." ".tgt.")")
- call system(copycmd." ".args." ".tgt)
+" call Decho("system(".copycmd." '".args."' '".tgt."')")
+ call system(copycmd." '".args."' '".tgt."'")
if v:shell_error != 0
call netrw#ErrorMsg(s:ERROR,"tried using g:netrw_localcopycmd<".g:netrw_localcopycmd.">; it doesn't work!",80)
" call Dret("s:NetrwMarkFileCopy 0 : failed: system(".g:netrw_localcopycmd." ".args." ".shellescape(s:netrwmftgt))
@@ -6122,12 +6556,12 @@ fun! s:NetrwMarkFileCopy(islocal,...)
elseif a:islocal && !s:netrwmftgt_islocal
" Copy marked files, local directory to remote directory
" call Decho("copy from local to remote")
- keepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
+ NetrwKeepj call s:NetrwUpload(s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
elseif !a:islocal && s:netrwmftgt_islocal
" Copy marked files, remote directory to local directory
" call Decho("copy from remote to local")
- keepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
+ NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},s:netrwmftgt)
elseif !a:islocal && !s:netrwmftgt_islocal
" Copy marked files, remote directory to remote directory
@@ -6140,7 +6574,7 @@ fun! s:NetrwMarkFileCopy(islocal,...)
if exists("*mkdir")
call mkdir(tmpdir)
else
- exe "sil! !".g:netrw_localmkdir.' '.shellescape(tmpdir,1)
+ call s:NetrwExe("sil! !".g:netrw_localmkdir.' '.shellescape(tmpdir,1))
if v:shell_error != 0
call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localmkdir<".g:netrw_localmkdir."> to something that works",80)
" call Dret("s:NetrwMarkFileCopy : failed: sil! !".g:netrw_localmkdir.' '.shellescape(tmpdir,1) )
@@ -6149,15 +6583,15 @@ fun! s:NetrwMarkFileCopy(islocal,...)
endif
if isdirectory(tmpdir)
call s:NetrwLcd(tmpdir)
- keepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir)
+ NetrwKeepj call netrw#Obtain(a:islocal,s:netrwmarkfilelist_{bufnr('%')},tmpdir)
let localfiles= map(deepcopy(s:netrwmarkfilelist_{bufnr('%')}),'substitute(v:val,"^.*/","","")')
- keepj call s:NetrwUpload(localfiles,s:netrwmftgt)
+ NetrwKeepj call s:NetrwUpload(localfiles,s:netrwmftgt)
if getcwd() == tmpdir
for fname in s:netrwmarkfilelist_{bufnr('%')}
- keepj call s:NetrwDelete(fname)
+ NetrwKeepj call s:NetrwDelete(fname)
endfor
call s:NetrwLcd(curdir)
- exe "sil !".g:netrw_localrmdir." ".shellescape(tmpdir,1)
+ call s:NetrwExe("sil !".g:netrw_localrmdir." ".shellescape(tmpdir,1))
if v:shell_error != 0
call netrw#ErrorMsg(s:WARNING,"consider setting g:netrw_localrmdir<".g:netrw_localrmdir."> to something that works",80)
" call Dret("s:NetrwMarkFileCopy : failed: sil !".g:netrw_localrmdir." ".shellescape(tmpdir,1) )
@@ -6183,10 +6617,10 @@ fun! s:NetrwMarkFileCopy(islocal,...)
call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
endif
if a:islocal
- keepj call s:NetrwRefreshDir(a:islocal,curdir)
+ NetrwKeepj call s:NetrwRefreshDir(a:islocal,curdir)
endif
if g:netrw_fastbrowse <= 1
- keepj call s:LocalBrowseRefresh()
+ NetrwKeepj call s:LocalBrowseRefresh()
endif
" call Dret("s:NetrwMarkFileCopy 1")
@@ -6204,7 +6638,7 @@ fun! s:NetrwMarkFileDiff(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileDiff")
return
endif
@@ -6217,13 +6651,13 @@ fun! s:NetrwMarkFileDiff(islocal)
let cnt= cnt + 1
if cnt == 1
" call Decho("diffthis: fname<".fname.">")
- exe "e ".fnameescape(fname)
+ exe "NetrwKeepj e ".fnameescape(fname)
diffthis
elseif cnt == 2 || cnt == 3
vsplit
wincmd l
" call Decho("diffthis: ".fname)
- exe "e ".fnameescape(fname)
+ exe "NetrwKeepj e ".fnameescape(fname)
diffthis
else
break
@@ -6246,7 +6680,7 @@ fun! s:NetrwMarkFileEdit(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileEdit")
return
endif
@@ -6298,72 +6732,106 @@ fun! s:NetrwMarkFileQFEL(islocal,qfel)
endfun
" ---------------------------------------------------------------------
-" s:NetrwMarkFileExe: (invoked by mx) execute arbitrary system command on marked files, one at a time {{{2
-" Uses the local marked-file list.
-fun! s:NetrwMarkFileExe(islocal)
-" call Dfunc("s:NetrwMarkFileExe(islocal=".a:islocal.")")
+" s:NetrwMarkFileExe: (invoked by mx and mX) execute arbitrary system command on marked files {{{2
+" mx enbloc=0: Uses the local marked-file list, applies command to each file individually
+" mX enbloc=1: Uses the global marked-file list, applies command to entire list
+fun! s:NetrwMarkFileExe(islocal,enbloc)
+" call Dfunc("s:NetrwMarkFileExe(islocal=".a:islocal.",enbloc=".a:enbloc.")")
let svpos = netrw#SavePosn()
let curdir = b:netrw_curdir
let curbufnr = bufnr("%")
- " sanity check
- if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
-" call Dret("s:NetrwMarkFileExe")
- return
- endif
-" call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}))
-
- if exists("s:netrwmarkfilelist_{curbufnr}")
- " get the command
- call inputsave()
- let cmd= input("Enter command: ","","file")
- call inputrestore()
-" call Decho("cmd<".cmd.">")
- if cmd == ""
-" " call Dret("s:NetrwMarkFileExe : early exit, empty command")
- return
- endif
+ if a:enbloc == 0
+ " individually apply command to files, one at a time
+ " sanity check
+ if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+" call Dret("s:NetrwMarkFileExe")
+ return
+ endif
+" call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}))
- " apply command to marked files. Substitute: filename -> %
- " If no %, then append a space and the filename to the command
- for fname in s:netrwmarkfilelist_{curbufnr}
- if a:islocal
- if g:netrw_keepdir
- let fname= shellescape(netrw#WinPath(s:ComposePath(curdir,fname)))
+ if exists("s:netrwmarkfilelist_{curbufnr}")
+ " get the command
+ call inputsave()
+ let cmd= input("Enter command: ","","file")
+ call inputrestore()
+" call Decho("cmd<".cmd.">")
+ if cmd == ""
+" call Dret("s:NetrwMarkFileExe : early exit, empty command")
+ return
endif
- else
- let fname= shellescape(netrw#WinPath(b:netrw_curdir.fname))
- endif
- if cmd =~ '%'
- let xcmd= substitute(cmd,'%',fname,'g')
- else
- let xcmd= cmd.' '.fname
- endif
- if a:islocal
-" call Decho("local: xcmd<".xcmd.">")
- let ret= system(xcmd)
- else
-" call Decho("remote: xcmd<".xcmd.">")
- let ret= s:RemoteSystem(xcmd)
- endif
- if v:shell_error < 0
- keepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
- break
- else
- echo ret
- endif
- endfor
+
+ " apply command to marked files, individually. Substitute: filename -> %
+ " If no %, then append a space and the filename to the command
+ for fname in s:netrwmarkfilelist_{curbufnr}
+ if a:islocal
+ if g:netrw_keepdir
+ let fname= shellescape(netrw#WinPath(s:ComposePath(curdir,fname)))
+ endif
+ else
+ let fname= shellescape(netrw#WinPath(b:netrw_curdir.fname))
+ endif
+ if cmd =~ '%'
+ let xcmd= substitute(cmd,'%',fname,'g')
+ else
+ let xcmd= cmd.' '.fname
+ endif
+ if a:islocal
+" call Decho("local: xcmd<".xcmd.">")
+ let ret= system(xcmd)
+ else
+" call Decho("remote: xcmd<".xcmd.">")
+ let ret= s:RemoteSystem(xcmd)
+ endif
+ if v:shell_error < 0
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
+ break
+ else
+ echo ret
+ endif
+ endfor
" unmark marked file list
call s:NetrwUnmarkList(curbufnr,curdir)
" refresh the listing
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
else
- keepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
endif
+
+ else " apply command to global list of files, en bloc
+
+ call inputsave()
+ let cmd= input("Enter command: ","","file")
+ call inputrestore()
+" call Decho("cmd<".cmd.">")
+ if cmd == ""
+" call Dret("s:NetrwMarkFileExe : early exit, empty command")
+ return
+ endif
+ if cmd =~ '%'
+ let cmd= substitute(cmd,'%',join(map(s:netrwmarkfilelist,'shellescape(v:val)'),' '),'g')
+ else
+ let cmd= cmd.' '.join(map(s:netrwmarkfilelist,'shellescape(v:val)'),' ')
+ endif
+ if a:islocal
+ call system(cmd)
+ if v:shell_error < 0
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"command<".xcmd."> failed, aborting",54)
+ endif
+ else
+ let ret= s:RemoteSystem(cmd)
+ endif
+ call s:NetrwUnmarkAll()
+
+ " refresh the listing
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
+
+ endif
" call Dret("s:NetrwMarkFileExe")
endfun
@@ -6416,17 +6884,17 @@ fun! s:NetrwMarkHideSfx(islocal)
endfor
" refresh the listing
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
else
- keepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
endif
" call Dret("s:NetrwMarkHideSfx")
endfun
" ---------------------------------------------------------------------
-" s:NetrwMarkFileVimCmd: (invoked by mX) execute arbitrary vim command on marked files, one at a time {{{2
+" s:NetrwMarkFileVimCmd: (invoked by mv) execute arbitrary vim command on marked files, one at a time {{{2
" Uses the local marked-file list.
fun! s:NetrwMarkFileVimCmd(islocal)
" call Dfunc("s:NetrwMarkFileVimCmd(islocal=".a:islocal.")")
@@ -6436,7 +6904,7 @@ fun! s:NetrwMarkFileVimCmd(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileVimCmd")
return
endif
@@ -6459,13 +6927,13 @@ fun! s:NetrwMarkFileVimCmd(islocal)
" call Decho("fname<".fname.">")
if a:islocal
1split
- exe "sil! keepalt e ".fnameescape(fname)
+ exe "sil! NetrwKeepj keepalt e ".fnameescape(fname)
" call Decho("local<".fname.">: exe ".cmd)
exe cmd
exe "sil! keepalt wq!"
else
" call Decho("remote<".fname.">: exe ".cmd." : NOT SUPPORTED YET")
- echo "sorry, \"mX\" not supported yet for remote files"
+ echo "sorry, \"mv\" not supported yet for remote files"
endif
endfor
@@ -6473,10 +6941,10 @@ fun! s:NetrwMarkFileVimCmd(islocal)
call s:NetrwUnmarkList(curbufnr,curdir)
" refresh the listing
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
else
- keepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
endif
" call Dret("s:NetrwMarkFileVimCmd")
@@ -6530,10 +6998,10 @@ fun! s:NetrwMarkHideSfx(islocal)
endfor
" refresh the listing
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
else
- keepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"no files marked!",59)
endif
" call Dret("s:NetrwMarkHideSfx")
@@ -6575,16 +7043,16 @@ fun! s:NetrwMarkFileGrep(islocal)
" use vimgrep for both local and remote
" call Decho("exe vimgrep".patbang." ".pat." ".netrwmarkfilelist)
try
- exe "keepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist
+ exe "NetrwKeepj noautocmd vimgrep".patbang." ".pat." ".netrwmarkfilelist
catch /^Vim\%((\a\+)\)\=:E480/
- keepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"no match with pattern<".pat.">",76)
" call Dret("s:NetrwMarkFileGrep : unable to find pattern<".pat.">")
return
endtry
echo "(use :cn, :cp to navigate, :Rex to return)"
2match none
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call netrw#RestorePosn(svpos)
if exists("nonisi")
" original, user-supplied pattern did not begin with a character from isident
@@ -6609,14 +7077,14 @@ fun! s:NetrwMarkFileMove(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileMove")
return
endif
" call Decho("sanity chk passed: s:netrwmarkfilelist_".curbufnr."<".string(s:netrwmarkfilelist_{curbufnr}))
if !exists("s:netrwmftgt")
- keepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67)
+ NetrwKeepj call netrw#ErrorMsg(2,"your marked file target is empty! (:help netrw-mt)",67)
" call Dret("s:NetrwMarkFileCopy 0")
return 0
endif
@@ -6666,7 +7134,7 @@ fun! s:NetrwMarkFileMove(islocal)
" call Decho("move from local to remote")
" call Decho("copy")
let mflist= s:netrwmarkfilelist_{bufnr("%")}
- keepj call s:NetrwMarkFileCopy(a:islocal)
+ NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
" call Decho("remove")
for fname in mflist
let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
@@ -6679,7 +7147,7 @@ fun! s:NetrwMarkFileMove(islocal)
" call Decho("move from remote to local")
" call Decho("copy")
let mflist= s:netrwmarkfilelist_{bufnr("%")}
- keepj call s:NetrwMarkFileCopy(a:islocal)
+ NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
" call Decho("remove")
for fname in mflist
let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
@@ -6692,7 +7160,7 @@ fun! s:NetrwMarkFileMove(islocal)
" call Decho("move from remote to remote")
" call Decho("copy")
let mflist= s:netrwmarkfilelist_{bufnr("%")}
- keepj call s:NetrwMarkFileCopy(a:islocal)
+ NetrwKeepj call s:NetrwMarkFileCopy(a:islocal)
" call Decho("remove")
for fname in mflist
let barefname = substitute(fname,'^\(.*/\)\(.\{-}\)$','\2','')
@@ -6712,15 +7180,15 @@ fun! s:NetrwMarkFileMove(islocal)
" refresh buffers
if !s:netrwmftgt_islocal
" call Decho("refresh netrwmftgt<".s:netrwmftgt.">")
- keepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
+ NetrwKeepj call s:NetrwRefreshDir(s:netrwmftgt_islocal,s:netrwmftgt)
endif
if a:islocal
" call Decho("refresh b:netrw_curdir<".b:netrw_curdir.">")
- keepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir)
+ NetrwKeepj call s:NetrwRefreshDir(a:islocal,b:netrw_curdir)
endif
if g:netrw_fastbrowse <= 1
" call Decho("since g:netrw_fastbrowse=".g:netrw_fastbrowse.", perform shell cmd refresh")
- keepj call s:LocalBrowseRefresh()
+ NetrwKeepj call s:LocalBrowseRefresh()
endif
" call Dret("s:NetrwMarkFileMove")
@@ -6735,7 +7203,7 @@ fun! s:NetrwMarkFilePrint(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFilePrint")
return
endif
@@ -6755,7 +7223,7 @@ fun! s:NetrwMarkFilePrint(islocal)
1split
" the autocmds will handle both local and remote files
" call Decho("exe sil e ".escape(fname,' '))
- exe "sil e ".fnameescape(fname)
+ exe "sil NetrwKeepj e ".fnameescape(fname)
" call Decho("hardcopy")
hardcopy
q
@@ -6788,7 +7256,7 @@ fun! s:NetrwMarkFileRegexp(islocal)
" mark the list of files
for fname in filelist
" call Decho("fname<".fname.">")
- keepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','',''))
+ NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(fname,'^.*/','',''))
endfor
else
@@ -6797,38 +7265,38 @@ fun! s:NetrwMarkFileRegexp(islocal)
" convert displayed listing into a filelist
let eikeep = &ei
let areg = @a
- sil keepj %y a
+ sil NetrwKeepj %y a
setl ei=all ma
" call Decho("setl ei=all ma")
1split
- keepj call s:NetrwEnew()
- keepj call s:NetrwSafeOptions()
- sil keepj norm! "ap
- keepj 2
+ NetrwKeepj call s:NetrwEnew()
+ NetrwKeepj call s:NetrwSafeOptions()
+ sil NetrwKeepj norm! "ap
+ NetrwKeepj 2
let bannercnt= search('^" =====','W')
- exe "sil keepj 1,".bannercnt."d"
+ exe "sil NetrwKeepj 1,".bannercnt."d"
setl bt=nofile
if g:netrw_liststyle == s:LONGLIST
- sil keepj %s/\s\{2,}\S.*$//e
+ sil NetrwKeepj %s/\s\{2,}\S.*$//e
call histdel("/",-1)
elseif g:netrw_liststyle == s:WIDELIST
- sil keepj %s/\s\{2,}/\r/ge
+ sil NetrwKeepj %s/\s\{2,}/\r/ge
call histdel("/",-1)
elseif g:netrw_liststyle == s:TREELIST
- exe 'sil keepj %s/^'.s:treedepthstring.' //e'
- sil! keepj g/^ .*$/d
+ exe 'sil NetrwKeepj %s/^'.s:treedepthstring.' //e'
+ sil! NetrwKeepj g/^ .*$/d
call histdel("/",-1)
call histdel("/",-1)
endif
" convert regexp into the more usual glob-style format
let regexp= substitute(regexp,'\*','.*','g')
" call Decho("regexp<".regexp.">")
- exe "sil! keepj v/".escape(regexp,'/')."/d"
+ exe "sil! NetrwKeepj v/".escape(regexp,'/')."/d"
call histdel("/",-1)
let filelist= getline(1,line("$"))
q!
for filename in filelist
- keepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','',''))
+ NetrwKeepj call s:NetrwMarkFile(a:islocal,substitute(filename,'^.*/','',''))
endfor
unlet filelist
let @a = areg
@@ -6848,7 +7316,7 @@ fun! s:NetrwMarkFileSource(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileSource")
return
endif
@@ -6885,7 +7353,7 @@ fun! s:NetrwMarkFileTag(islocal)
" sanity check
if !exists("s:netrwmarkfilelist_{curbufnr}") || empty(s:netrwmarkfilelist_{curbufnr})
- keepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
+ NetrwKeepj call netrw#ErrorMsg(2,"there are no marked files in this window (:help netrw-mf)",66)
" call Dret("s:NetrwMarkFileTag")
return
endif
@@ -6908,10 +7376,10 @@ fun! s:NetrwMarkFileTag(islocal)
call netrw#Obtain(a:islocal,"tags")
let curdir= b:netrw_curdir
1split
- e tags
+ NetrwKeepj e tags
let path= substitute(curdir,'^\(.*\)/[^/]*$','\1/','')
" call Decho("curdir<".curdir."> path<".path.">")
- exe 'keepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e'
+ exe 'NetrwKeepj %s/\t\(\S\+\)\t/\t'.escape(path,"/\n\r\\").'\1\t/e'
call histdel("/",-1)
wq!
endif
@@ -6990,7 +7458,7 @@ fun! s:NetrwMarkFileTgt(islocal)
call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
call netrw#RestorePosn(svpos)
if !hadtgt
- sil! keepj norm! j
+ sil! NetrwKeepj norm! j
endif
" call Dret("s:NetrwMarkFileTgt : netrwmftgt<".(exists("s:netrwmftgt")? s:netrwmftgt : "").">")
@@ -7011,7 +7479,7 @@ fun! s:NetrwOpenFile(islocal)
endif
let g:netrw_quiet = 1
if b:netrw_curdir =~ '/$'
- exe "e ".fnameescape(b:netrw_curdir.fname)
+ exe "NetrwKeepj e ".fnameescape(b:netrw_curdir.fname)
else
exe "e ".fnameescape(b:netrw_curdir."/".fname)
endif
@@ -7022,7 +7490,7 @@ fun! s:NetrwOpenFile(islocal)
endif
endif
else
- exe "e ".fnameescape(fname)
+ exe "NetrwKeepj e ".fnameescape(fname)
endif
let @@= ykeep
" call Dret("s:NetrwOpenFile")
@@ -7149,6 +7617,7 @@ fun! s:NetrwMenu(domenu)
exe 'sil! menu '.g:NetrwMenuPriority.'.11.2 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.Preview\ File/Directory<tab>p p'
exe 'sil! menu '.g:NetrwMenuPriority.'.11.3 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ Previous\ Window<tab>P P'
exe 'sil! menu '.g:NetrwMenuPriority.'.11.4 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Window<tab>o o'
+ exe 'sil! menu '.g:NetrwMenuPriority.'.11.5 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Tab<tab>t t'
exe 'sil! menu '.g:NetrwMenuPriority.'.11.5 '.g:NetrwTopLvlMenu.'Edit\ File/Dir.In\ New\ Vertical\ Window<tab>v v'
exe 'sil! menu '.g:NetrwMenuPriority.'.12.1 '.g:NetrwTopLvlMenu.'Explore.Directory\ Name :Explore '
exe 'sil! menu '.g:NetrwMenuPriority.'.12.2 '.g:NetrwTopLvlMenu.'Explore.Filenames\ Matching\ Pattern\ (curdir\ only)<tab>:Explore\ */ :Explore */'
@@ -7249,7 +7718,7 @@ fun! s:NetrwPrevWinOpen(islocal)
let lastwinnr = winnr("$")
let curword = s:NetrwGetWord()
let choice = 0
- let s:treedir = s:NetrwTreeDir()
+ let s:treedir = s:NetrwTreeDir(a:islocal)
let curdir = s:treedir
" call Decho("winnr($)#".lastwinnr." curword<".curword.">")
@@ -7258,10 +7727,12 @@ fun! s:NetrwPrevWinOpen(islocal)
" if only one window, open a new one first
" call Decho("only one window, so open a new one (g:netrw_alto=".g:netrw_alto.")")
if g:netrw_preview
+ " vertically split preview window
let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winheight(0))/100 : -g:netrw_winsize
" call Decho("exe ".(g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s")
exe (g:netrw_alto? "top " : "bot ")."vert ".winsz."wincmd s"
else
+ " horizontally split preview window
let winsz= (g:netrw_winsize > 0)? (g:netrw_winsize*winwidth(0))/100 : -g:netrw_winsize
" call Decho("exe ".(g:netrw_alto? "bel " : "abo ").winsz."wincmd s")
exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
@@ -7270,7 +7741,7 @@ fun! s:NetrwPrevWinOpen(islocal)
" call Decho("did split")
else
- keepj call s:SaveBufVars()
+ NetrwKeepj call s:SaveBufVars()
let eikeep= &ei
setl ei=all
wincmd p
@@ -7284,7 +7755,7 @@ fun! s:NetrwPrevWinOpen(islocal)
let prevbufname = bufname("%")
let prevmod = &mod
let bnrcnt = 0
- keepj call s:RestoreBufVars()
+ NetrwKeepj call s:RestoreBufVars()
" call Decho("after wincmd p: win#".winnr()." win($)#".winnr("$")." origwin#".origwin." &mod=".&mod." bufname(%)<".bufname("%")."> prevbufnr=".prevbufnr)
" if the previous window's buffer has been changed (ie. its modified flag is set),
@@ -7376,7 +7847,7 @@ fun! s:NetrwUpload(fname,tgt,...)
" call Decho("handle uploading a single file via NetWrite")
1split
" call Decho("exe e ".fnameescape(a:fname))
- exe "e ".fnameescape(a:fname)
+ exe "NetrwKeepj e ".fnameescape(a:fname)
" call Decho("now locally editing<".expand("%").">, has ".line("$")." lines")
if a:tgt =~ '/$'
let wfname= substitute(a:fname,'^.*/','','')
@@ -7404,8 +7875,7 @@ fun! s:NetrwUpload(fname,tgt,...)
endif
let machine = substitute(a:tgt,'^scp://\([^/:]\+\).*$','\1','')
let tgt = substitute(a:tgt,'^scp://[^/]\+/\(.*\)$','\1','')
-" call Decho("exe ".s:netrw_silentxfer."!".g:netrw_scp_cmd.shellescape(useport,1)." ".args." ".shellescape(machine.":".tgt,1))
- exe s:netrw_silentxfer."!".g:netrw_scp_cmd.shellescape(useport,1)." ".args." ".shellescape(machine.":".tgt,1)
+ call s:NetrwExe(s:netrw_silentxfer."!".g:netrw_scp_cmd.shellescape(useport,1)." ".args." ".shellescape(machine.":".tgt,1))
call s:NetrwLcd(curdir)
elseif a:tgt =~ '^ftp:'
@@ -7414,41 +7884,39 @@ fun! s:NetrwUpload(fname,tgt,...)
if b:netrw_method == 2
" handle uploading a list of files via ftp+.netrc
let netrw_fname = b:netrw_fname
- sil keepj new
+ sil NetrwKeepj new
" call Decho("filter input window#".winnr())
- keepj put =g:netrw_ftpmode
+ NetrwKeepj put =g:netrw_ftpmode
" call Decho("filter input: ".getline('$'))
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('$'))
endif
- keepj call setline(line("$")+1,'lcd "'.fromdir.'"')
+ NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"')
" call Decho("filter input: ".getline('$'))
if tgtdir == ""
let tgtdir= '/'
endif
- keepj call setline(line("$")+1,'cd "'.tgtdir.'"')
+ NetrwKeepj call setline(line("$")+1,'cd "'.tgtdir.'"')
" call Decho("filter input: ".getline('$'))
for fname in a:fname
- keepj call setline(line("$")+1,'put "'.fname.'"')
+ NetrwKeepj call setline(line("$")+1,'put "'.fname.'"')
" call Decho("filter input: ".getline('$'))
endfor
if exists("g:netrw_port") && g:netrw_port != ""
-" call Decho("executing: ".s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
else
" call Decho("filter input window#".winnr())
-" call Decho("executing: ".s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
endif
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
- sil keepj g/Local directory now/d
+ sil NetrwKeepj g/Local directory now/d
call histdel("/",-1)
if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
call netrw#ErrorMsg(s:ERROR,getline(1),14)
@@ -7459,47 +7927,47 @@ fun! s:NetrwUpload(fname,tgt,...)
elseif b:netrw_method == 3
" upload with ftp + machine, id, passwd, and fname (ie. no .netrc)
let netrw_fname= b:netrw_fname
- keepj call s:SaveBufVars()|sil keepj new|keepj call s:RestoreBufVars()
+ NetrwKeepj call s:SaveBufVars()|sil NetrwKeepj new|NetrwKeepj call s:RestoreBufVars()
let tmpbufnr= bufnr("%")
setl ff=unix
if exists("g:netrw_port") && g:netrw_port != ""
- keepj put ='open '.g:netrw_machine.' '.g:netrw_port
+ NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
" call Decho("filter input: ".getline('$'))
else
- keepj put ='open '.g:netrw_machine
+ NetrwKeepj put ='open '.g:netrw_machine
" call Decho("filter input: ".getline('$'))
endif
if exists("g:netrw_uid") && g:netrw_uid != ""
if exists("g:netrw_ftp") && g:netrw_ftp == 1
- keepj put =g:netrw_uid
+ NetrwKeepj put =g:netrw_uid
" call Decho("filter input: ".getline('$'))
if exists("s:netrw_passwd")
- keepj call setline(line("$")+1,'"'.s:netrw_passwd.'"')
+ NetrwKeepj call setline(line("$")+1,'"'.s:netrw_passwd.'"')
endif
" call Decho("filter input: ".getline('$'))
elseif exists("s:netrw_passwd")
- keepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
" call Decho("filter input: ".getline('$'))
endif
endif
- keepj call setline(line("$")+1,'lcd "'.fromdir.'"')
+ NetrwKeepj call setline(line("$")+1,'lcd "'.fromdir.'"')
" call Decho("filter input: ".getline('$'))
if exists("b:netrw_fname") && b:netrw_fname != ""
- keepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
+ NetrwKeepj call setline(line("$")+1,'cd "'.b:netrw_fname.'"')
" call Decho("filter input: ".getline('$'))
endif
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('$'))
endif
for fname in a:fname
- keepj call setline(line("$")+1,'put "'.fname.'"')
+ NetrwKeepj call setline(line("$")+1,'put "'.fname.'"')
" call Decho("filter input: ".getline('$'))
endfor
@@ -7507,11 +7975,10 @@ fun! s:NetrwUpload(fname,tgt,...)
" -i : turns off interactive prompting from ftp
" -n unix : DON'T use <.netrc>, even though it exists
" -n win32: quit being obnoxious about password
- keepj norm! 1Gdd
-" call Decho("executing: ".s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
- exe s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options
+ NetrwKeepj norm! 1Gdd
+ call s:NetrwExe(s:netrw_silentxfer."%!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
" If the result of the ftp operation isn't blank, show an error message (tnx to Doug Claar)
- sil keepj g/Local directory now/d
+ sil NetrwKeepj g/Local directory now/d
call histdel("/",-1)
if getline(1) !~ "^$" && !exists("g:netrw_quiet") && getline(1) !~ '^Trying '
let debugkeep= &debug
@@ -7539,8 +8006,8 @@ endfun
fun! s:NetrwPreview(path) range
" call Dfunc("NetrwPreview(path<".a:path.">)")
let ykeep= @@
- keepj call s:NetrwOptionSave("s:")
- keepj call s:NetrwSafeOptions()
+ NetrwKeepj call s:NetrwOptionSave("s:")
+ NetrwKeepj call s:NetrwSafeOptions()
if has("quickfix")
if !isdirectory(a:path)
if g:netrw_preview && !g:netrw_alto
@@ -7553,12 +8020,12 @@ fun! s:NetrwPreview(path) range
let &pvh= pvhkeep
endif
elseif !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:WARNING,"sorry, cannot preview a directory such as <".a:path.">",38)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, cannot preview a directory such as <".a:path.">",38)
endif
elseif !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:WARNING,"sorry, to preview your vim needs the quickfix feature compiled in",39)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"sorry, to preview your vim needs the quickfix feature compiled in",39)
endif
- keepj call s:NetrwOptionRestore("s:")
+ NetrwKeepj call s:NetrwOptionRestore("s:")
let @@= ykeep
" call Dret("NetrwPreview")
endfun
@@ -7566,7 +8033,7 @@ endfun
" ---------------------------------------------------------------------
" s:NetrwRefresh: {{{2
fun! s:NetrwRefresh(islocal,dirname)
-" call Dfunc("NetrwRefresh(islocal<".a:islocal.">,dirname=".a:dirname.") hide=".g:netrw_hide." sortdir=".g:netrw_sort_direction)
+" call Dfunc("s:NetrwRefresh(islocal<".a:islocal.">,dirname=".a:dirname.") hide=".g:netrw_hide." sortdir=".g:netrw_sort_direction)
" at the current time (Mar 19, 2007) all calls to NetrwRefresh() call NetrwBrowseChgDir() first.
setl ma noro
" call Decho("setl ma noro")
@@ -7577,15 +8044,15 @@ fun! s:NetrwRefresh(islocal,dirname)
let screenposn = netrw#SavePosn()
" call Decho("win#".winnr().": ".winheight(0)."x".winwidth(0)." curfile<".expand("%").">")
" call Decho("clearing buffer prior to refresh")
- sil! keepj %d
+ sil! NetrwKeepj %d
if a:islocal
- keepj call netrw#LocalBrowseCheck(a:dirname)
+ NetrwKeepj call netrw#LocalBrowseCheck(a:dirname)
else
- keepj call s:NetrwBrowse(a:islocal,a:dirname)
+ NetrwKeepj call s:NetrwBrowse(a:islocal,a:dirname)
endif
" restore position
- keepj call netrw#RestorePosn(screenposn)
+ NetrwKeepj call netrw#RestorePosn(screenposn)
" restore file marks
if exists("s:netrwmarkfilemtch_{bufnr('%')}") && s:netrwmarkfilemtch_{bufnr("%")} != ""
@@ -7598,7 +8065,7 @@ fun! s:NetrwRefresh(islocal,dirname)
" restore
let @@= ykeep
-" call Dret("NetrwRefresh")
+" call Dret("s:NetrwRefresh")
endfun
" ---------------------------------------------------------------------
@@ -7618,7 +8085,7 @@ fun! s:NetrwRefreshDir(islocal,dirname)
let curwin= winnr()
" call Decho("refresh tgtwin#".tgtwin." (curwin#".curwin.")")
exe tgtwin."wincmd w"
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
exe curwin."wincmd w"
elseif bufnr(a:dirname) > 0
@@ -7629,7 +8096,7 @@ fun! s:NetrwRefreshDir(islocal,dirname)
elseif g:netrw_fastbrowse <= 1
" call Decho("medium-speed mode: refresh local buffers only")
- keepj call s:LocalBrowseRefresh()
+ NetrwKeepj call s:LocalBrowseRefresh()
endif
" call Dret("s:NetrwRefreshDir")
endfun
@@ -7639,14 +8106,21 @@ endfun
" window number to do its editing in.
" Supports [count]C where the count, if present, is used to specify
" a window to use for editing via the <cr> mapping.
-fun! s:NetrwSetChgwin()
+fun! s:NetrwSetChgwin(...)
" call Dfunc("s:NetrwSetChgwin() v:count=".v:count)
- if v:count > 0
+ if a:0 > 0
+" call Decho("a:1<".a:1.">")
+ if a:1 == "" " :NetrwC win#
+ let g:netrw_chgwin= winnr()
+ else " :NetrwC
+ let g:netrw_chgwin= a:1
+ endif
+ elseif v:count > 0 " [count]C
let g:netrw_chgwin= v:count
- else
+ else " C
let g:netrw_chgwin= winnr()
endif
-" call Dret("s:NetrwSetChgwin")
+" call Dret("s:NetrwSetChgwin : g:netrw_chgwin=".g:netrw_chgwin)
endfun
" ---------------------------------------------------------------------
@@ -7696,18 +8170,18 @@ fun! s:NetrwSetSort()
if seq == '*'
let starpriority= spriority
else
- exe 'sil keepj '.w:netrw_bannercnt.',$g/'.seq.'/s/^/'.spriority.'/'
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/'.seq.'/s/^/'.spriority.'/'
call histdel("/",-1)
" sometimes multiple sorting patterns will match the same file or directory.
" The following substitute is intended to remove the excess matches.
- exe 'sil keepj '.w:netrw_bannercnt.',$g/^\d\{3}'.g:netrw_sepchr.'\d\{3}\//s/^\d\{3}'.g:netrw_sepchr.'\(\d\{3}\/\).\@=/\1/e'
- keepj call histdel("/",-1)
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^\d\{3}'.g:netrw_sepchr.'\d\{3}\//s/^\d\{3}'.g:netrw_sepchr.'\(\d\{3}\/\).\@=/\1/e'
+ NetrwKeepj call histdel("/",-1)
endif
let priority = priority + 1
endwhile
if exists("starpriority")
- exe 'sil keepj '.w:netrw_bannercnt.',$v/^\d\{3}'.g:netrw_sepchr.'/s/^/'.starpriority.'/e'
- keepj call histdel("/",-1)
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$v/^\d\{3}'.g:netrw_sepchr.'/s/^/'.starpriority.'/e'
+ NetrwKeepj call histdel("/",-1)
endif
" Following line associated with priority -- items that satisfy a priority
@@ -7716,8 +8190,8 @@ fun! s:NetrwSetSort()
" priority pattern needs to be retained. So, at this point, these excess
" priority prefixes need to be removed, but not directories that happen to
" be just digits themselves.
- exe 'sil keepj '.w:netrw_bannercnt.',$s/^\(\d\{3}'.g:netrw_sepchr.'\)\%(\d\{3}'.g:netrw_sepchr.'\)\+\ze./\1/e'
- keepj call histdel("/",-1)
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{3}'.g:netrw_sepchr.'\)\%(\d\{3}'.g:netrw_sepchr.'\)\+\ze./\1/e'
+ NetrwKeepj call histdel("/",-1)
let @@= ykeep
" call Dret("SetSort")
@@ -7758,13 +8232,13 @@ endfun
" s:NetrwSortStyle: change sorting style (name - time - size) and refresh display {{{2
fun! s:NetrwSortStyle(islocal)
" call Dfunc("s:NetrwSortStyle(islocal=".a:islocal.") netrw_sort_by<".g:netrw_sort_by.">")
- keepj call s:NetrwSaveWordPosn()
+ NetrwKeepj call s:NetrwSaveWordPosn()
let svpos= netrw#SavePosn()
let g:netrw_sort_by= (g:netrw_sort_by =~ 'n')? 'time' : (g:netrw_sort_by =~ 't')? 'size' : 'name'
- keepj norm! 0
- keepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj norm! 0
+ NetrwKeepj call s:NetrwRefresh(a:islocal,s:NetrwBrowseChgDir(a:islocal,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
" call Dret("s:NetrwSortStyle : netrw_sort_by<".g:netrw_sort_by.">")
endfun
@@ -7790,8 +8264,8 @@ fun! s:NetrwSplit(mode)
" call Decho("exe ".(g:netrw_alto? "bel " : "abo ").winsz."wincmd s")
exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
let s:didsplit= 1
- keepj call s:RestoreWinVars()
- keepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord()))
+ NetrwKeepj call s:RestoreWinVars()
+ NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord()))
unlet s:didsplit
elseif a:mode == 1
@@ -7800,8 +8274,8 @@ fun! s:NetrwSplit(mode)
" call Decho("tabnew")
tabnew
let s:didsplit= 1
- keepj call s:RestoreWinVars()
- keepj call s:NetrwBrowse(0,newdir)
+ NetrwKeepj call s:RestoreWinVars()
+ NetrwKeepj call s:NetrwBrowse(0,newdir)
unlet s:didsplit
elseif a:mode == 2
@@ -7811,8 +8285,8 @@ fun! s:NetrwSplit(mode)
" call Decho("exe ".(g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v")
exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v"
let s:didsplit= 1
- keepj call s:RestoreWinVars()
- keepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord()))
+ NetrwKeepj call s:RestoreWinVars()
+ NetrwKeepj call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord()))
unlet s:didsplit
elseif a:mode == 3
@@ -7822,8 +8296,8 @@ fun! s:NetrwSplit(mode)
" call Decho("exe ".(g:netrw_alto? "bel " : "abo ").winsz."wincmd s")
exe (g:netrw_alto? "bel " : "abo ").winsz."wincmd s"
let s:didsplit= 1
- keepj call s:RestoreWinVars()
- keepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
+ NetrwKeepj call s:RestoreWinVars()
+ NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
unlet s:didsplit
elseif a:mode == 4
@@ -7833,23 +8307,23 @@ fun! s:NetrwSplit(mode)
let netrw_winnr = winnr()
let netrw_line = line(".")
let netrw_col = virtcol(".")
- keepj norm! H0
+ NetrwKeepj norm! H0
let netrw_hline = line(".")
setl ei=all
- exe "keepj norm! ".netrw_hline."G0z\<CR>"
- exe "keepj norm! ".netrw_line."G0".netrw_col."\<bar>"
+ exe "NetrwKeepj norm! ".netrw_hline."G0z\<CR>"
+ exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\<bar>"
let &ei= eikeep
- let netrw_curdir= s:NetrwTreeDir()
+ let netrw_curdir= s:NetrwTreeDir(0)
" call Decho("tabnew")
tabnew
let b:netrw_curdir = netrw_curdir
let s:didsplit = 1
- keepj call s:RestoreWinVars()
- keepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,cursorword))
+ NetrwKeepj call s:RestoreWinVars()
+ NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,cursorword))
if &ft == "netrw"
setl ei=all
- exe "keepj norm! ".netrw_hline."G0z\<CR>"
- exe "keepj norm! ".netrw_line."G0".netrw_col."\<bar>"
+ exe "NetrwKeepj norm! ".netrw_hline."G0z\<CR>"
+ exe "NetrwKeepj norm! ".netrw_line."G0".netrw_col."\<bar>"
let &ei= eikeep
endif
unlet s:didsplit
@@ -7861,12 +8335,13 @@ fun! s:NetrwSplit(mode)
" call Decho("exe ".(g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v")
exe (g:netrw_altv? "rightb " : "lefta ").winsz."wincmd v"
let s:didsplit= 1
- keepj call s:RestoreWinVars()
- keepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
+ NetrwKeepj call s:RestoreWinVars()
+ NetrwKeepj call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
+ "call netrw#LocalBrowseCheck(s:NetrwBrowseChgDir(1,s:NetrwGetWord()))
unlet s:didsplit
else
- keepj call netrw#ErrorMsg(s:ERROR,"(NetrwSplit) unsupported mode=".a:mode,45)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"(NetrwSplit) unsupported mode=".a:mode,45)
endif
let @@= ykeep
@@ -7926,8 +8401,8 @@ endfun
" ---------------------------------------------------------------------
" s:NetrwTreeDir: determine tree directory given current cursor position {{{2
" (full path directory with trailing slash returned)
-fun! s:NetrwTreeDir()
-" call Dfunc("s:NetrwTreeDir() getline(".line(".").")"."<".getline('.')."> b:netrw_curdir<".b:netrw_curdir."> tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> ft=".&ft)
+fun! s:NetrwTreeDir(islocal)
+" call Dfunc("s:NetrwTreeDir(islocal=".a:islocal.") getline(".line(".").")"."<".getline('.')."> b:netrw_curdir<".b:netrw_curdir."> tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> ft=".&ft)
if exists("s:treedir")
" s:NetrwPrevWinOpen opens a "previous" window -- and thus needs to and does call s:NetrwTreeDir early
@@ -7948,7 +8423,8 @@ fun! s:NetrwTreeDir()
" call Decho("line#".line(".")." getline(.)<".getline('.')."> treecurpos<".string(s:treecurpos).">")
" extract tree directory if on a line specifying a subdirectory (ie. ends with "/")
- if getline('.') =~ '/$'
+ let curline= substitute(getline('.'),"\t -->.*$",'','')
+ if curline =~ '/$'
" call Decho("extract tree subdirectory from current line")
let treedir= substitute(getline('.'),'^\%('.s:treedepthstring.'\)*\([^'.s:treedepthstring.'].\{-}\)$','\1','e')
" call Decho("treedir<".treedir.">")
@@ -7958,24 +8434,38 @@ fun! s:NetrwTreeDir()
endif
" detect user attempting to close treeroot
-" call Decho("win#".winnr()." buf#".bufnr("%")."<".bufname("%").">")
-" call Decho("getline(".line(".").")<".getline('.').'> '.((getline('.') =~ '^'.s:treedepthstring)? '=~' : '!~').' ^'.s:treedepthstring)
- if getline('.') !~ '^'.s:treedepthstring && getline('.') != '..'
-" call Decho("user may have attempted to close treeroot")
+" call Decho("check if user is attempting to close treeroot")
+" call Decho(".win#".winnr()." buf#".bufnr("%")."<".bufname("%").">")
+" call Decho(".getline(".line(".").")<".getline('.').'> '.((getline('.') =~ '^'.s:treedepthstring)? '=~' : '!~').' ^'.s:treedepthstring)
+ if curline !~ '^'.s:treedepthstring && getline('.') != '..'
+" call Decho(".user may have attempted to close treeroot")
" now force a refresh
-" call Decho("clear buffer<".expand("%")."> with :%d")
- sil! keepj %d
+" call Decho(".force refresh: clear buffer<".expand("%")."> with :%d")
+ sil! NetrwKeepj %d
" call Dret("s:NetrwTreeDir <".treedir."> : (side effect) s:treecurpos<".string(s:treecurpos).">")
return b:netrw_curdir
" else " Decho
-" call Decho("user did not attempt to close treeroot")
+" call Decho(".user did not attempt to close treeroot")
endif
- let treedir = s:NetrwTreePath(w:netrw_treetop)
+" call Decho("islocal=".a:islocal." curline<".curline.">")
+" call Decho("after subst<".substitute(curline,'^'.s:treedepthstring.'\+ \(.*\)$','\1','').">")
+ let potentialdir= substitute(curline,'^'.s:treedepthstring.'* \(.*\)@$','\1','')
+" call Decho("potentialdir<".potentialdir."> isdir=".isdirectory(potentialdir))
+
+ if a:islocal && curline =~ '@$' && isdirectory(potentialdir)
+ let newdir = w:netrw_treetop.'/'.potentialdir
+ let treedir = s:NetrwTreePath(newdir)
+ let w:netrw_treetop = newdir
+" call Decho("newdir <".newdir.">")
+ else
+ let treedir = s:NetrwTreePath(w:netrw_treetop)
+ endif
endif
" sanity maintenance: keep those //s away...
let treedir= substitute(treedir,'//$','/','')
+" call Decho("treedir<".treedir.">")
" call Dret("s:NetrwTreeDir <".treedir."> : (side effect) s:treecurpos<".string(s:treecurpos).">")
return treedir
@@ -8022,13 +8512,13 @@ fun! s:NetrwTreeDisplay(dir,depth)
" call Decho("dir<".dir."> entry<".entry."> direntry<".direntry.">")
if entry =~ '/$' && has_key(w:netrw_treedict,direntry)
" call Decho("<".direntry."> is a key in treedict - display subtree for it")
- keepj call s:NetrwTreeDisplay(direntry,depth)
+ NetrwKeepj call s:NetrwTreeDisplay(direntry,depth)
elseif entry =~ '/$' && has_key(w:netrw_treedict,direntry.'/')
" call Decho("<".direntry."/> is a key in treedict - display subtree for it")
- keepj call s:NetrwTreeDisplay(direntry.'/',depth)
+ NetrwKeepj call s:NetrwTreeDisplay(direntry.'/',depth)
else
" call Decho("<".entry."> is not a key in treedict (no subtree)")
- sil! keepj call setline(line("$")+1,depth.entry)
+ sil! NetrwKeepj call setline(line("$")+1,depth.entry)
endif
endfor
@@ -8062,10 +8552,10 @@ fun! s:NetrwTreeListing(dirname)
" update the directory listing for the current directory
" call Decho("updating dictionary with ".a:dirname.":[..directory listing..]")
" call Decho("w:netrw_bannercnt=".w:netrw_bannercnt." line($)=".line("$"))
- exe "sil! keepj ".w:netrw_bannercnt.',$g@^\.\.\=/$@d'
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g@^\.\.\=/$@d'
let w:netrw_treedict[a:dirname]= getline(w:netrw_bannercnt,line("$"))
" call Decho("w:treedict[".a:dirname."]= ".string(w:netrw_treedict[a:dirname]))
- exe "sil! keepj ".w:netrw_bannercnt.",$d"
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d"
" if past banner, record word
if exists("w:netrw_bannercnt") && line(".") > w:netrw_bannercnt
@@ -8077,7 +8567,7 @@ fun! s:NetrwTreeListing(dirname)
" call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")")
" display from treetop on down
- keepj call s:NetrwTreeDisplay(w:netrw_treetop,"")
+ NetrwKeepj call s:NetrwTreeDisplay(w:netrw_treetop,"")
" call Decho("s:NetrwTreeDisplay) setl noma nomod ro")
" remove any blank line remaining as line#1 (happens in treelisting mode with banner suppressed)
@@ -8145,8 +8635,8 @@ fun! s:NetrwWideListing()
" call Decho("setl ma noro")
let b:netrw_cpf= 0
if line("$") >= w:netrw_bannercnt
- exe 'sil keepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
- keepj call histdel("/",-1)
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$g/^./if virtcol("$") > b:netrw_cpf|let b:netrw_cpf= virtcol("$")|endif'
+ NetrwKeepj call histdel("/",-1)
else
" call Dret("NetrwWideListing")
return
@@ -8162,8 +8652,8 @@ fun! s:NetrwWideListing()
" call Decho("fpl= [winwidth=".winwidth(0)."]/[b:netrw_cpf=".b:netrw_cpf.']='.w:netrw_fpl)
" make wide display
- exe 'sil keepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'s",submatch(0)),"\\")/'
- keepj call histdel("/",-1)
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^.*$/\=escape(printf("%-'.b:netrw_cpf.'s",submatch(0)),"\\")/'
+ NetrwKeepj call histdel("/",-1)
let fpc = (line("$") - w:netrw_bannercnt + w:netrw_fpl)/w:netrw_fpl
let newcolstart = w:netrw_bannercnt + fpc
let newcolend = newcolstart + fpc - 1
@@ -8176,20 +8666,20 @@ fun! s:NetrwWideListing()
let newcolqty= newcolend - newcolstart
exe newcolstart
if newcolqty == 0
- exe "sil! keepj norm! 0\<c-v>$hx".w:netrw_bannercnt."G$p"
+ exe "sil! NetrwKeepj norm! 0\<c-v>$hx".w:netrw_bannercnt."G$p"
else
- exe "sil! keepj norm! 0\<c-v>".newcolqty.'j$hx'.w:netrw_bannercnt.'G$p'
+ exe "sil! NetrwKeepj norm! 0\<c-v>".newcolqty.'j$hx'.w:netrw_bannercnt.'G$p'
endif
- exe "sil! keepj ".newcolstart.','.newcolend.'d'
- exe 'sil! keepj '.w:netrw_bannercnt
+ exe "sil! NetrwKeepj ".newcolstart.','.newcolend.'d'
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt
endwhile
if has("clipboard")
sil! let @*= keepregstar
endif
- exe "sil! keepj ".w:netrw_bannercnt.',$s/\s\+$//e'
- keepj call histdel("/",-1)
- exe "nmap <buffer> <silent> w /^\\\\|\\s\\s\\zs\\S/\<cr>"
- exe "nmap <buffer> <silent> b ?^\\\\|\\s\\s\\zs\\S?\<cr>"
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/\s\+$//e'
+ NetrwKeepj call histdel("/",-1)
+ exe 'nno <buffer> <silent> w :call search(''^.\\|\s\s\zs\S'',''W'')'."\<cr>"
+ exe 'nno <buffer> <silent> b :call search(''^.\\|\s\s\zs\S'',''bW'')'."\<cr>"
" call Decho("NetrwWideListing) setl noma nomod ro")
exe "setl ".g:netrw_bufsettings
" call Decho("(NetrwWideListing) ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
@@ -8213,18 +8703,10 @@ fun! s:PerformListing(islocal)
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (enter)")
" set up syntax highlighting {{{3
-" call Decho("set up syntax highlighting")
- if has("syntax")
- if !exists("g:syntax_on") || !g:syntax_on
-" call Decho("but g:syntax_on".(exists("g:syntax_on")? "=".g:syntax_on : "<doesn't exist>"))
- setl ft=
- elseif &ft != "netrw"
-" call Decho("setl ft=netrw")
- setl ft=netrw
- endif
- endif
+" call Decho("set up syntax highlighting (ie. setl ft=netrw)")
+ sil! setl ft=netrw
- keepj call s:NetrwSafeOptions()
+ NetrwKeepj call s:NetrwSafeOptions()
setl noro ma
" call Decho("setl noro ma bh=".&bh)
@@ -8236,26 +8718,26 @@ fun! s:PerformListing(islocal)
if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST && exists("w:netrw_treedict")
" force a refresh for tree listings
" call Decho("force refresh for treelisting: clear buffer<".expand("%")."> with :%d")
- sil! keepj %d
+ sil! NetrwKeepj %d
endif
" save current directory on directory history list
- keepj call s:NetrwBookHistHandler(3,b:netrw_curdir)
+ NetrwKeepj call s:NetrwBookHistHandler(3,b:netrw_curdir)
" Set up the banner {{{3
if g:netrw_banner
" call Decho("set up banner")
- keepj call setline(1,'" ============================================================================')
- keepj call setline(2,'" Netrw Directory Listing (netrw '.g:loaded_netrw.')')
+ NetrwKeepj call setline(1,'" ============================================================================')
+ NetrwKeepj call setline(2,'" Netrw Directory Listing (netrw '.g:loaded_netrw.')')
if exists("g:netrw_bannerbackslash") && g:netrw_bannerbackslash
- keepj call setline(3,'" '.substitute(b:netrw_curdir,'/','\\','g'))
+ NetrwKeepj call setline(3,'" '.substitute(b:netrw_curdir,'/','\\','g'))
else
- keepj call setline(3,'" '.b:netrw_curdir)
+ NetrwKeepj call setline(3,'" '.b:netrw_curdir)
endif
let w:netrw_bannercnt= 3
- keepj exe "sil! keepj ".w:netrw_bannercnt
+ NetrwKeepj exe "sil! NetrwKeepj ".w:netrw_bannercnt
else
- keepj 1
+ NetrwKeepj 1
let w:netrw_bannercnt= 1
endif
" call Decho("w:netrw_bannercnt=".w:netrw_bannercnt." win#".winnr())
@@ -8271,16 +8753,16 @@ fun! s:PerformListing(islocal)
if g:netrw_sort_by =~ "^n"
" call Decho("directories will be sorted by name")
" sorted by name
- keepj put ='\" Sorted by '.sortby
- keepj put ='\" Sort sequence: '.g:netrw_sort_sequence
+ NetrwKeepj put ='\" Sorted by '.sortby
+ NetrwKeepj put ='\" Sort sequence: '.g:netrw_sort_sequence
let w:netrw_bannercnt= w:netrw_bannercnt + 2
else
" call Decho("directories will be sorted by size or time")
" sorted by size or date
- keepj put ='\" Sorted by '.sortby
+ NetrwKeepj put ='\" Sorted by '.sortby
let w:netrw_bannercnt= w:netrw_bannercnt + 1
endif
- exe "sil! keepj ".w:netrw_bannercnt
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt
" else " Decho
" call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")")
endif
@@ -8289,17 +8771,17 @@ fun! s:PerformListing(islocal)
if g:netrw_banner
if exists("s:netrwmftgt") && exists("s:netrwmftgt_islocal")
" call Decho("show copy/move target<".s:netrwmftgt.">")
- keepj put =''
+ NetrwKeepj put =''
if s:netrwmftgt_islocal
- sil! keepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (local)')
+ sil! NetrwKeepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (local)')
else
- sil! keepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (remote)')
+ sil! NetrwKeepj call setline(line("."),'" Copy/Move Tgt: '.s:netrwmftgt.' (remote)')
endif
let w:netrw_bannercnt= w:netrw_bannercnt + 1
else
" call Decho("s:netrwmftgt does not exist, don't make Copy/Move Tgt")
endif
- exe "sil! keepj ".w:netrw_bannercnt
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt
endif
" Hiding... -or- Showing... {{{3
@@ -8307,20 +8789,20 @@ fun! s:PerformListing(islocal)
" call Decho("handle hiding/showing (g:netrw_hide=".g:netrw_list_hide." g:netrw_list_hide<".g:netrw_list_hide.">)")
if g:netrw_list_hide != "" && g:netrw_hide
if g:netrw_hide == 1
- keepj put ='\" Hiding: '.g:netrw_list_hide
+ NetrwKeepj put ='\" Hiding: '.g:netrw_list_hide
else
- keepj put ='\" Showing: '.g:netrw_list_hide
+ NetrwKeepj put ='\" Showing: '.g:netrw_list_hide
endif
let w:netrw_bannercnt= w:netrw_bannercnt + 1
endif
- exe "keepjumps ".w:netrw_bannercnt
+ exe "NetrwKeepj ".w:netrw_bannercnt
" call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
let quickhelp = g:netrw_quickhelp%len(s:QuickHelp)
" call Decho("quickhelp =".quickhelp)
- keepj put ='\" Quick Help: <F1>:help '.s:QuickHelp[quickhelp]
+ NetrwKeepj put ='\" Quick Help: <F1>:help '.s:QuickHelp[quickhelp]
" call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
- keepj put ='\" =============================================================================='
+ NetrwKeepj put ='\" =============================================================================='
let w:netrw_bannercnt= w:netrw_bannercnt + 2
" else " Decho
" call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")")
@@ -8329,7 +8811,7 @@ fun! s:PerformListing(islocal)
" bannercnt should index the line just after the banner
if g:netrw_banner
let w:netrw_bannercnt= w:netrw_bannercnt + 1
- exe "sil! keepj ".w:netrw_bannercnt
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt
" call Decho("w:netrw_bannercnt=".w:netrw_bannercnt." (should index line just after banner) line($)=".line("$"))
" else " Decho
" call Decho("g:netrw_banner=".g:netrw_banner.": banner ".(g:netrw_banner? "enabled" : "suppressed").": (line($)=".line("$")." byte2line(1)=".byte2line(1)." bannercnt=".w:netrw_bannercnt.")")
@@ -8338,9 +8820,9 @@ fun! s:PerformListing(islocal)
" get list of files
" call Decho("Get list of files - islocal=".a:islocal)
if a:islocal
- keepj call s:LocalListing()
+ NetrwKeepj call s:LocalListing()
else " remote
- keepj let badresult= s:NetrwRemoteListing()
+ NetrwKeepj let badresult= s:NetrwRemoteListing()
if badresult
" call Decho("w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a')." win#".winnr()." buf#".bufnr("%")."<".bufname("%").">")
" call Dret("s:PerformListing : error detected by NetrwRemoteListing")
@@ -8359,49 +8841,49 @@ fun! s:PerformListing(islocal)
" call Decho("manipulate directory listing (hide)")
" call Decho("g:netrw_hide=".g:netrw_hide." g:netrw_list_hide<".g:netrw_list_hide.">")
if g:netrw_hide && g:netrw_list_hide != ""
- keepj call s:NetrwListHide()
+ NetrwKeepj call s:NetrwListHide()
endif
if !g:netrw_banner || line("$") >= w:netrw_bannercnt
" call Decho("manipulate directory listing (sort) : g:netrw_sort_by<".g:netrw_sort_by.">")
if g:netrw_sort_by =~ "^n"
" sort by name
- keepj call s:NetrwSetSort()
+ NetrwKeepj call s:NetrwSetSort()
if !g:netrw_banner || w:netrw_bannercnt < line("$")
" call Decho("g:netrw_sort_direction=".g:netrw_sort_direction." (bannercnt=".w:netrw_bannercnt.")")
if g:netrw_sort_direction =~ 'n'
" normal direction sorting
- exe 'sil keepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
else
" reverse direction sorting
- exe 'sil keepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
endif
endif
" remove priority pattern prefix
" call Decho("remove priority pattern prefix")
- exe 'sil! keepj '.w:netrw_bannercnt.',$s/^\d\{3}'.g:netrw_sepchr.'//e'
- keepj call histdel("/",-1)
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{3}'.g:netrw_sepchr.'//e'
+ NetrwKeepj call histdel("/",-1)
elseif a:islocal
if !g:netrw_banner || w:netrw_bannercnt < line("$")
" call Decho("g:netrw_sort_direction=".g:netrw_sort_direction)
if g:netrw_sort_direction =~ 'n'
-" call Decho('exe sil keepjumps '.w:netrw_bannercnt.',$sort')
- exe 'sil! keepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
+" call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort')
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort'.' '.g:netrw_sort_options
else
-" call Decho('exe sil keepjumps '.w:netrw_bannercnt.',$sort!')
- exe 'sil! keepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
+" call Decho('exe sil NetrwKeepj '.w:netrw_bannercnt.',$sort!')
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$sort!'.' '.g:netrw_sort_options
endif
- exe 'sil! keepj '.w:netrw_bannercnt.',$s/^\d\{-}\///e'
- keepj call histdel("/",-1)
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{-}\///e'
+ NetrwKeepj call histdel("/",-1)
endif
endif
elseif g:netrw_sort_direction =~ 'r'
" call Decho('(s:PerformListing) reverse the sorted listing')
if !g:netrw_banner || w:netrw_bannercnt < line('$')
- exe 'sil! keepj '.w:netrw_bannercnt.',$g/^/m '.w:netrw_bannercnt
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$g/^/m '.w:netrw_bannercnt
call histdel("/",-1)
endif
endif
@@ -8411,16 +8893,21 @@ fun! s:PerformListing(islocal)
" convert to wide/tree listing {{{3
" call Decho("modify display if wide/tree listing style")
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#1)")
- keepj call s:NetrwWideListing()
+ NetrwKeepj call s:NetrwWideListing()
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#2)")
- keepj call s:NetrwTreeListing(b:netrw_curdir)
+ NetrwKeepj call s:NetrwTreeListing(b:netrw_curdir)
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#3)")
+ " resolve symbolic links if local and (thin or tree)
+ if a:islocal && (w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:TREELIST)
+ g/@$/call s:ShowLink()
+ endif
+
if exists("w:netrw_bannercnt") && (line("$") > w:netrw_bannercnt || !g:netrw_banner)
" place cursor on the top-left corner of the file listing
" call Decho("place cursor on top-left corner of file listing")
- exe 'sil! keepj '.w:netrw_bannercnt
- sil! keepj norm! 0
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt
+ sil! NetrwKeepj norm! 0
endif
" record previous current directory
@@ -8429,9 +8916,9 @@ fun! s:PerformListing(islocal)
" save certain window-oriented variables into buffer-oriented variables {{{3
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#4)")
- keepj call s:SetBufWinVars()
+ NetrwKeepj call s:SetBufWinVars()
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#5)")
- keepj call s:NetrwOptionRestore("w:")
+ NetrwKeepj call s:NetrwOptionRestore("w:")
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#6)")
" set display to netrw display settings
@@ -8445,7 +8932,7 @@ fun! s:PerformListing(islocal)
if exists("s:treecurpos")
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo. " (internal#8)")
- keepj call netrw#RestorePosn(s:treecurpos)
+ NetrwKeepj call netrw#RestorePosn(s:treecurpos)
unlet s:treecurpos
endif
@@ -8526,27 +9013,27 @@ fun! s:NetrwRemoteFtpCmd(path,listcmd)
" clear off any older non-banner lines " {{{3
" note that w:netrw_bannercnt indexes the line after the banner
-" call Decho('exe sil! keepjumps '.w:netrw_bannercnt.",$d (clear off old non-banner lines)")
- exe "sil! keepjumps ".w:netrw_bannercnt.",$d"
+" call Decho('exe sil! NetrwKeepj '.w:netrw_bannercnt.",$d (clear off old non-banner lines)")
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt.",$d"
".........................................
if w:netrw_method == 2 || w:netrw_method == 5 " {{{3
" ftp + <.netrc>: Method #2
if a:path != ""
- keepj put ='cd \"'.a:path.'\"'
+ NetrwKeepj put ='cd \"'.a:path.'\"'
endif
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('.'))
endif
- keepj call setline(line("$")+1,a:listcmd)
-" exe "keepjumps ".w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."))'
+ NetrwKeepj call setline(line("$")+1,a:listcmd)
+" exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."))'
if exists("g:netrw_port") && g:netrw_port != ""
" call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1))
- exe s:netrw_silentxfer." keepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1)
+ exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)." ".shellescape(g:netrw_port,1)
else
" call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1))
- exe s:netrw_silentxfer." keepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)
+ exe s:netrw_silentxfer." NetrwKeepj ".w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." -i ".shellescape(g:netrw_machine,1)
endif
".........................................
@@ -8554,9 +9041,9 @@ fun! s:NetrwRemoteFtpCmd(path,listcmd)
" ftp + machine,id,passwd,filename: Method #3
setl ff=unix
if exists("g:netrw_port") && g:netrw_port != ""
- keepj put ='open '.g:netrw_machine.' '.g:netrw_port
+ NetrwKeepj put ='open '.g:netrw_machine.' '.g:netrw_port
else
- keepj put ='open '.g:netrw_machine
+ NetrwKeepj put ='open '.g:netrw_machine
endif
" handle userid and password
@@ -8567,23 +9054,23 @@ fun! s:NetrwRemoteFtpCmd(path,listcmd)
endif
if exists("g:netrw_uid") && g:netrw_uid != ""
if exists("g:netrw_ftp") && g:netrw_ftp == 1
- keepj put =g:netrw_uid
+ NetrwKeepj put =g:netrw_uid
if exists("s:netrw_passwd") && s:netrw_passwd != ""
- keepj put ='\"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='\"'.s:netrw_passwd.'\"'
endif
elseif exists("s:netrw_passwd")
- keepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
+ NetrwKeepj put ='user \"'.g:netrw_uid.'\" \"'.s:netrw_passwd.'\"'
endif
endif
if a:path != ""
- keepj put ='cd \"'.a:path.'\"'
+ NetrwKeepj put ='cd \"'.a:path.'\"'
endif
if exists("g:netrw_ftpextracmd")
- keepj put =g:netrw_ftpextracmd
+ NetrwKeepj put =g:netrw_ftpextracmd
" call Decho("filter input: ".getline('.'))
endif
- keepj call setline(line("$")+1,a:listcmd)
+ NetrwKeepj call setline(line("$")+1,a:listcmd)
" perform ftp:
" -i : turns off interactive prompting from ftp
@@ -8591,11 +9078,10 @@ fun! s:NetrwRemoteFtpCmd(path,listcmd)
" -n win32: quit being obnoxious about password
if exists("w:netrw_bannercnt")
" exe w:netrw_bannercnt.',$g/^./call Decho("ftp#".line(".").": ".getline("."))'
-" call Decho("exe ".s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
- exe s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." ".g:netrw_ftp_options
+ call s:NetrwExe(s:netrw_silentxfer.w:netrw_bannercnt.",$!".s:netrw_ftp_cmd." ".g:netrw_ftp_options)
" else " Decho
" call Decho("WARNING: w:netrw_bannercnt doesn't exist!")
-" g/^./call Decho("SKIPPING ftp#".line(".").": ".getline(".")) " COMBAK
+" g/^./call Decho("SKIPPING ftp#".line(".").": ".getline("."))
endif
".........................................
@@ -8611,34 +9097,34 @@ fun! s:NetrwRemoteFtpCmd(path,listcmd)
".........................................
else " {{{3
- keepj call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . bufname("%") . ">",23)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"unable to comply with your request<" . bufname("%") . ">",23)
endif
" cleanup for Windows " {{{3
if has("win32") || has("win95") || has("win64") || has("win16")
- sil! keepj %s/\r$//e
- keepj call histdel("/",-1)
+ sil! NetrwKeepj %s/\r$//e
+ NetrwKeepj call histdel("/",-1)
endif
if a:listcmd == "dir"
" infer directory/link based on the file permission string
- sil! keepj g/d\%([-r][-w][-x]\)\{3}/keepj s@$@/@e
- sil! keepj g/l\%([-r][-w][-x]\)\{3}/keepj s/$/@/e
- keepj call histdel("/",-1)
- keepj call histdel("/",-1)
+ sil! NetrwKeepj g/d\%([-r][-w][-x]\)\{3}/NetrwKeepj s@$@/@e
+ sil! NetrwKeepj g/l\%([-r][-w][-x]\)\{3}/NetrwKeepj s/$/@/e
+ NetrwKeepj call histdel("/",-1)
+ NetrwKeepj call histdel("/",-1)
if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || w:netrw_liststyle == s:TREELIST
- exe "sil! keepj ".w:netrw_bannercnt.',$s/^\%(\S\+\s\+\)\{8}//e'
- keepj call histdel("/",-1)
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$s/^\%(\S\+\s\+\)\{8}//e'
+ NetrwKeepj call histdel("/",-1)
endif
endif
" ftp's listing doesn't seem to include ./ or ../ " {{{3
if !search('^\.\/$\|\s\.\/$','wn')
- exe 'keepj '.w:netrw_bannercnt
- keepj put ='./'
+ exe 'NetrwKeepj '.w:netrw_bannercnt
+ NetrwKeepj put ='./'
endif
if !search('^\.\.\/$\|\s\.\.\/$','wn')
- exe 'keepj '.w:netrw_bannercnt
- keepj put ='../'
+ exe 'NetrwKeepj '.w:netrw_bannercnt
+ NetrwKeepj put ='../'
endif
" restore settings " {{{3
@@ -8677,13 +9163,13 @@ fun! s:NetrwRemoteListing()
" call Decho("g:netrw_list_cmd<",(exists("g:netrw_list_cmd")? 'n/a' : "-empty-").">")
if !exists("g:netrw_quiet")
if g:netrw_list_cmd == ""
- keepj call netrw#ErrorMsg(s:ERROR,"your g:netrw_list_cmd is empty; perhaps ".g:netrw_ssh_cmd." is not executable on your system",47)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your g:netrw_list_cmd is empty; perhaps ".g:netrw_ssh_cmd." is not executable on your system",47)
else
- keepj call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ".g:netrw_list_cmd,19)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"this system doesn't support remote directory listing via ".g:netrw_list_cmd,19)
endif
endif
- keepj call s:NetrwOptionRestore("w:")
+ NetrwKeepj call s:NetrwOptionRestore("w:")
" call Dret("s:NetrwRemoteListing -1")
return -1
endif " (remote handling sanity check)
@@ -8706,7 +9192,7 @@ fun! s:NetrwRemoteListing()
endif
" call Decho("listcmd<".listcmd."> (using g:netrw_ftp_list_cmd)")
call s:NetrwRemoteFtpCmd(s:path,listcmd)
-" exe "sil! keepalt keepj ".w:netrw_bannercnt.',$g/^./call Decho("raw listing: ".getline("."))'
+" exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("raw listing: ".getline("."))'
" report on missing file or directory messages
if search('[Nn]o such file or directory\|Failed to change directory')
@@ -8716,7 +9202,7 @@ fun! s:NetrwRemoteListing()
exe w:netrw_bannercnt.",$d"
setl noma
endif
- keepj call s:NetrwOptionRestore("w:")
+ NetrwKeepj call s:NetrwOptionRestore("w:")
call netrw#ErrorMsg(s:WARNING,mesg,96)
" call Dret("s:NetrwRemoteListing : -1")
return -1
@@ -8725,41 +9211,41 @@ fun! s:NetrwRemoteListing()
if w:netrw_liststyle == s:THINLIST || w:netrw_liststyle == s:WIDELIST || w:netrw_liststyle == s:TREELIST
" shorten the listing
" call Decho("generate short listing")
- exe "sil! keepalt keepj ".w:netrw_bannercnt
+ exe "sil! keepalt NetrwKeepj ".w:netrw_bannercnt
" cleanup
if g:netrw_ftp_browse_reject != ""
- exe "sil! keepalt keepj g/".g:netrw_ftp_browse_reject."/keepj d"
- keepj call histdel("/",-1)
+ exe "sil! keepalt NetrwKeepj g/".g:netrw_ftp_browse_reject."/NetrwKeepj d"
+ NetrwKeepj call histdel("/",-1)
endif
- sil! keepj %s/\r$//e
- keepj call histdel("/",-1)
+ sil! NetrwKeepj %s/\r$//e
+ NetrwKeepj call histdel("/",-1)
" if there's no ../ listed, then put ../ in
let line1= line(".")
- exe "sil! keepj ".w:netrw_bannercnt
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt
let line2= search('\.\.\/\%(\s\|$\)','cnW')
" call Decho("search(".'\.\.\/\%(\s\|$\)'."','cnW')=".line2." w:netrw_bannercnt=".w:netrw_bannercnt)
if line2 == 0
" call Decho("netrw is putting ../ into listing")
- sil! keepj put='../'
+ sil! NetrwKeepj put='../'
endif
- exe "sil! keepj ".line1
- sil! keepj norm! 0
+ exe "sil! NetrwKeepj ".line1
+ sil! NetrwKeepj norm! 0
" call Decho("line1=".line1." line2=".line2." line(.)=".line("."))
if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup
" call Decho("M$ ftp cleanup")
- exe 'sil! keepj '.w:netrw_bannercnt.',$s/^\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(<DIR>\|\d\+\)\s\+//'
- keepj call histdel("/",-1)
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(<DIR>\|\d\+\)\s\+//'
+ NetrwKeepj call histdel("/",-1)
else " normal ftp cleanup
" call Decho("normal ftp cleanup")
- exe 'sil! keepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2/e'
- exe "sil! keepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*/$#/#e'
- exe "sil! keepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*$#/#e'
- keepj call histdel("/",-1)
- keepj call histdel("/",-1)
- keepj call histdel("/",-1)
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2/e'
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*/$#/#e'
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt.',$g/ -> /s# -> .*$#/#e'
+ NetrwKeepj call histdel("/",-1)
+ NetrwKeepj call histdel("/",-1)
+ NetrwKeepj call histdel("/",-1)
endif
endif
@@ -8769,35 +9255,35 @@ fun! s:NetrwRemoteListing()
let listcmd= s:MakeSshCmd(g:netrw_list_cmd)
" call Decho("listcmd<".listcmd."> (using g:netrw_list_cmd)")
if g:netrw_scp_cmd =~ '^pscp'
-" call Decho("1: exe sil r! ".shellescape(listcmd.s:path, 1))
- exe "sil! keepj r! ".listcmd.shellescape(s:path, 1)
+" call Decho("1: exe r! ".shellescape(listcmd.s:path, 1))
+ exe "NetrwKeepj r! ".listcmd.shellescape(s:path, 1)
" remove rubbish and adjust listing format of 'pscp' to 'ssh ls -FLa' like
- sil! keepj g/^Listing directory/keepj d
- sil! keepj g/^d[-rwx][-rwx][-rwx]/keepj s+$+/+e
- sil! keepj g/^l[-rwx][-rwx][-rwx]/keepj s+$+@+e
- keepj call histdel("/",-1)
- keepj call histdel("/",-1)
- keepj call histdel("/",-1)
+ sil! NetrwKeepj g/^Listing directory/NetrwKeepj d
+ sil! NetrwKeepj g/^d[-rwx][-rwx][-rwx]/NetrwKeepj s+$+/+e
+ sil! NetrwKeepj g/^l[-rwx][-rwx][-rwx]/NetrwKeepj s+$+@+e
+ NetrwKeepj call histdel("/",-1)
+ NetrwKeepj call histdel("/",-1)
+ NetrwKeepj call histdel("/",-1)
if g:netrw_liststyle != s:LONGLIST
- sil! keepj g/^[dlsp-][-rwx][-rwx][-rwx]/keepj s/^.*\s\(\S\+\)$/\1/e
- keepj call histdel("/",-1)
+ sil! NetrwKeepj g/^[dlsp-][-rwx][-rwx][-rwx]/NetrwKeepj s/^.*\s\(\S\+\)$/\1/e
+ NetrwKeepj call histdel("/",-1)
endif
else
if s:path == ""
-" call Decho("2: exe sil r! ".listcmd)
- exe "sil! keepj keepalt r! ".listcmd
+" call Decho("2: exe r! ".listcmd)
+ exe "NetrwKeepj keepalt r! ".listcmd
else
-" call Decho("3: exe sil r! ".listcmd.' '.shellescape(fnameescape(s:path),1))
- exe "sil! keepj keepalt r! ".listcmd.' '.shellescape(fnameescape(s:path),1)
+" call Decho("3: exe r! ".listcmd.' '.shellescape(fnameescape(s:path),1))
+ exe "NetrwKeepj keepalt r! ".listcmd.' '.shellescape(fnameescape(s:path),1)
" call Decho("listcmd<".listcmd."> path<".s:path.">")
endif
endif
" cleanup
if g:netrw_ssh_browse_reject != ""
-" call Decho("cleanup: exe sil! g/".g:netrw_ssh_browse_reject."/keepjumps d")
- exe "sil! g/".g:netrw_ssh_browse_reject."/keepj d"
- keepj call histdel("/",-1)
+" call Decho("cleanup: exe sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d")
+ exe "sil! g/".g:netrw_ssh_browse_reject."/NetrwKeepj d"
+ NetrwKeepj call histdel("/",-1)
endif
endif
@@ -8807,40 +9293,40 @@ fun! s:NetrwRemoteListing()
if s:method == "ftp"
" cleanup
- exe "sil! keepj ".w:netrw_bannercnt
+ exe "sil! NetrwKeepj ".w:netrw_bannercnt
while getline('.') =~ g:netrw_ftp_browse_reject
- sil! keepj d
+ sil! NetrwKeepj d
endwhile
" if there's no ../ listed, then put ../ in
let line1= line(".")
- sil! keepj 1
- sil! keepj call search('^\.\.\/\%(\s\|$\)','W')
+ sil! NetrwKeepj 1
+ sil! NetrwKeepj call search('^\.\.\/\%(\s\|$\)','W')
let line2= line(".")
if line2 == 0
if b:netrw_curdir != '/'
- exe 'sil! keepj '.w:netrw_bannercnt."put='../'"
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt."put='../'"
endif
endif
- exe "sil! keepj ".line1
- sil! keepj norm! 0
+ exe "sil! NetrwKeepj ".line1
+ sil! NetrwKeepj norm! 0
endif
if search('^\d\{2}-\d\{2}-\d\{2}\s','n') " M$ ftp site cleanup
" call Decho("M$ ftp site listing cleanup")
- exe 'sil! keepj '.w:netrw_bannercnt.',$s/^\(\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(<DIR>\|\d\+\)\s\+\)\(\w.*\)$/\2\t\1/'
+ exe 'sil! NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\d\{2}-\d\{2}-\d\{2}\s\+\d\+:\d\+[AaPp][Mm]\s\+\%(<DIR>\|\d\+\)\s\+\)\(\w.*\)$/\2\t\1/'
elseif exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$")
" call Decho("normal ftp site listing cleanup: bannercnt=".w:netrw_bannercnt." line($)=".line("$"))
- exe 'sil keepj '.w:netrw_bannercnt.',$s/ -> .*$//e'
- exe 'sil keepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2\t\1/e'
- exe 'sil keepj '.w:netrw_bannercnt
- keepj call histdel("/",-1)
- keepj call histdel("/",-1)
- keepj call histdel("/",-1)
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/ -> .*$//e'
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt.',$s/^\(\%(\S\+\s\+\)\{7}\S\+\)\s\+\(\S.*\)$/\2\t\1/e'
+ exe 'sil NetrwKeepj '.w:netrw_bannercnt
+ NetrwKeepj call histdel("/",-1)
+ NetrwKeepj call histdel("/",-1)
+ NetrwKeepj call histdel("/",-1)
endif
endif
" if exists("w:netrw_bannercnt") && w:netrw_bannercnt <= line("$") " Decho
-" exe "keepj ".w:netrw_bannercnt.',$g/^./call Decho("listing: ".getline("."))'
+" exe "NetrwKeepj ".w:netrw_bannercnt.',$g/^./call Decho("listing: ".getline("."))'
" endif " Decho
" call Dret("s:NetrwRemoteListing 0")
@@ -8877,7 +9363,7 @@ fun! s:NetrwRemoteRm(usrhost,path) range
" remove multiple files and directories
while ctr <= a:lastline
- exe "keepj ".ctr
+ exe "NetrwKeepj ".ctr
let ok= s:NetrwRemoteRmFile(a:path,s:NetrwGetWord(),all)
if ok =~ 'q\%[uit]'
break
@@ -8890,8 +9376,8 @@ fun! s:NetrwRemoteRm(usrhost,path) range
" refresh the (remote) directory listing
" call Decho("refresh remote directory listing")
- keepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
" call Dret("s:NetrwRemoteRm")
endfun
@@ -8931,14 +9417,14 @@ fun! s:NetrwRemoteRmFile(path,rmfile,all)
if path =~ '^\a\+://'
let path= substitute(path,'^\a\+://[^/]\+/','','')
endif
- sil! keepj .,$d
+ sil! NetrwKeepj .,$d
call s:NetrwRemoteFtpCmd(path,"delete ".'"'.a:rmfile.'"')
else
" call Decho("case ssh: g:netrw_rm_cmd<".g:netrw_rm_cmd.">")
let netrw_rm_cmd= s:MakeSshCmd(g:netrw_rm_cmd)
" call Decho("netrw_rm_cmd<".netrw_rm_cmd.">")
if !exists("b:netrw_curdir")
- keepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
let ok="q"
else
let remotedir= substitute(b:netrw_curdir,'^.*//[^/]\+/\(.*\)$','\1','')
@@ -8953,7 +9439,7 @@ fun! s:NetrwRemoteRmFile(path,rmfile,all)
" call Decho("call system(".netrw_rm_cmd.")")
let ret= system(netrw_rm_cmd)
if ret != 0
- keepj call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"cmd<".netrw_rm_cmd."> failed",60)
endif
" call Decho("returned=".ret." errcode=".v:shell_error)
endif
@@ -8980,7 +9466,7 @@ fun! s:NetrwRemoteRmFile(path,rmfile,all)
if all || ok =~ 'y\%[es]' || ok == ""
if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
- keepj call s:NetrwRemoteFtpCmd(a:path,"rmdir ".a:rmfile)
+ NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rmdir ".a:rmfile)
else
let rmfile = substitute(a:path.a:rmfile,'/$','','')
let netrw_rmdir_cmd = s:MakeSshCmd(netrw#WinPath(g:netrw_rmdir_cmd)).' '.shellescape(netrw#WinPath(rmfile))
@@ -8996,7 +9482,7 @@ fun! s:NetrwRemoteRmFile(path,rmfile,all)
" call Decho("returned=".ret." errcode=".v:shell_error)
if v:shell_error != 0 && !exists("g:netrw_quiet")
- keepj call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",22)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",22)
endif
endif
endif
@@ -9040,7 +9526,7 @@ fun! s:NetrwRemoteRename(usrhost,path) range
endif
if exists("w:netrw_method") && (w:netrw_method == 2 || w:netrw_method == 3)
- keepj call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname)
+ NetrwKeepj call s:NetrwRemoteFtpCmd(a:path,"rename ".oldname." ".newname)
else
let oldname= shellescape(a:path.oldname)
let newname= shellescape(a:path.newname)
@@ -9055,7 +9541,7 @@ fun! s:NetrwRemoteRename(usrhost,path) range
" attempt to rename files/directories
while ctr <= a:lastline
- exe "keepj ".ctr
+ exe "NetrwKeepj ".ctr
let oldname= s:NetrwGetWord()
" call Decho("oldname<".oldname.">")
@@ -9078,8 +9564,8 @@ fun! s:NetrwRemoteRename(usrhost,path) range
endif
" refresh the directory
- keepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(0,s:NetrwBrowseChgDir(0,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
" call Dret("NetrwRemoteRename")
endfun
@@ -9127,10 +9613,10 @@ fun! netrw#FileUrlRead(fname)
" call Decho("fname2396<".fname2396.">")
" call Decho("plainfname<".plainfname.">")
exe "sil doau BufReadPre ".fname2396e
- exe 'keepj r '.plainfname
+ exe 'NetrwKeepj r '.plainfname
exe 'sil! bdelete '.plainfname
exe 'keepalt file! '.plainfname
- keepj 1d
+ NetrwKeepj 1d
" call Decho("setl nomod")
setl nomod
" call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
@@ -9152,26 +9638,28 @@ fun! netrw#LocalBrowseCheck(dirname)
" call Decho("isdir<".a:dirname.">=".isdirectory(a:dirname).((exists("s:treeforceredraw")? " treeforceredraw" : "")))
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
" call Dredir("ls!","ls!")
+ norm! m`
let ykeep= @@
if isdirectory(a:dirname)
" call Decho("is-directory ft<".&ft."> b:netrw_curdir<".(exists("b:netrw_curdir")? b:netrw_curdir : " doesn't exist")."> dirname<".a:dirname.">"." line($)=".line("$")." ft<".&ft."> g:netrw_fastbrowse=".g:netrw_fastbrowse)
let svposn= netrw#SavePosn()
+
if &ft != "netrw" || (exists("b:netrw_curdir") && b:netrw_curdir != a:dirname) || g:netrw_fastbrowse <= 1
" call Decho("case 1 : ft=".&ft)
- sil! keepj keepalt call s:NetrwBrowse(1,a:dirname)
- keepalt call netrw#RestorePosn(svposn)
+ sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
+ NetrwKeepj keepalt call netrw#RestorePosn(svposn)
elseif &ft == "netrw" && line("$") == 1
" call Decho("case 2 (ft≡netrw && line($)≡1)")
- sil! keepj keepalt call s:NetrwBrowse(1,a:dirname)
- keepalt call netrw#RestorePosn(svposn)
+ sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
+ NetrwKeepj keepalt call netrw#RestorePosn(svposn)
elseif exists("s:treeforceredraw")
" call Decho("case 3 (treeforceredraw)")
unlet s:treeforceredraw
- sil! keepj keepalt call s:NetrwBrowse(1,a:dirname)
- keepalt call netrw#RestorePosn(svposn)
+ sil! NetrwKeepj keepalt call s:NetrwBrowse(1,a:dirname)
+ NetrwKeepj keepalt call netrw#RestorePosn(svposn)
endif
" call Dret("netrw#LocalBrowseCheck")
@@ -9257,7 +9745,7 @@ fun! s:LocalBrowseRefresh()
let g:netrw_quickhelp= g:netrw_quickhelp - 1
endif
" call Decho("#3: quickhelp=".g:netrw_quickhelp)
- keepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./'))
+ NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./'))
endif
let ibl= ibl + 1
endfor
@@ -9353,7 +9841,7 @@ fun! s:LocalListing()
let dirnamelen = strlen(b:netrw_curdir)
let filelist = glob(s:ComposePath(dirname,"*"),0,1)
let filelist = filelist + glob(s:ComposePath(dirname,".*"),0,1)
-" call Decho("filelist=".filelist)
+" call Decho("filelist=".string(filelist))
if g:netrw_cygwin == 0 && (has("win32") || has("win95") || has("win64") || has("win16"))
" call Decho("filelist=".string(filelist))
@@ -9446,29 +9934,29 @@ fun! s:LocalListing()
" call Decho("getftime(".filename.")=".getftime(filename))
let t = getftime(filename)
let ft = strpart("000000000000000000",1,18-strlen(t)).t
-" call Decho("exe keepjumps put ='".ft.'/'.filename."'")
+" call Decho("exe NetrwKeepj put ='".ft.'/'.filename."'")
let ftpfile= ft.'/'.pfile
- sil! keepj put=ftpfile
+ sil! NetrwKeepj put=ftpfile
elseif g:netrw_sort_by =~ "^s"
" sort by size (handles file sizes up to 1 quintillion bytes, US)
" call Decho("getfsize(".filename.")=".getfsize(filename))
let sz = getfsize(filename)
let fsz = strpart("000000000000000000",1,18-strlen(sz)).sz
-" call Decho("exe keepj put ='".fsz.'/'.filename."'")
+" call Decho("exe NetrwKeepj put ='".fsz.'/'.filename."'")
let fszpfile= fsz.'/'.pfile
- sil! keepj put =fszpfile
+ sil! NetrwKeepj put =fszpfile
else
" sort by name
-" call Decho("exe keepjumps put ='".pfile."'")
- sil! keepj put=pfile
+" call Decho("exe NetrwKeepj put ='".pfile."'")
+ sil! NetrwKeepj put=pfile
endif
endfor
" cleanup any windows mess at end-of-line
- sil! keepj g/^$/d
- sil! keepj %s/\r$//e
+ sil! NetrwKeepj g/^$/d
+ sil! NetrwKeepj %s/\r$//e
call histdel("/",-1)
" call Decho("exe setl ts=".(g:netrw_maxfilenamelen+1))
exe "setl ts=".(g:netrw_maxfilenamelen+1)
@@ -9505,7 +9993,7 @@ fun! s:NetrwLocalExecute(cmd)
endfun
" ---------------------------------------------------------------------
-" s:NetrwLocalRename: rename a remote file or directory {{{2
+" s:NetrwLocalRename: rename a local file or directory {{{2
fun! s:NetrwLocalRename(path) range
" call Dfunc("NetrwLocalRename(path<".a:path.">)")
@@ -9540,7 +10028,7 @@ fun! s:NetrwLocalRename(path) range
" attempt to rename files/directories
while ctr <= a:lastline
- exe "keepj ".ctr
+ exe "NetrwKeepj ".ctr
" sanity checks
if line(".") < w:netrw_bannercnt
@@ -9553,7 +10041,7 @@ fun! s:NetrwLocalRename(path) range
continue
endif
- keepj norm! 0
+ NetrwKeepj norm! 0
let oldname= s:ComposePath(a:path,curword)
" call Decho("oldname<".oldname.">")
@@ -9570,8 +10058,8 @@ fun! s:NetrwLocalRename(path) range
" refresh the directory
" call Decho("refresh the directory listing")
- keepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
let @@= ykeep
" call Dret("NetrwLocalRename")
@@ -9608,7 +10096,7 @@ fun! s:NetrwLocalRm(path) range
let ctr = a:firstline
while ctr <= a:lastline
- exe "keepj ".ctr
+ exe "NetrwKeepj ".ctr
" sanity checks
if line(".") < w:netrw_bannercnt
@@ -9633,8 +10121,8 @@ fun! s:NetrwLocalRm(path) range
" refresh the directory
" call Decho("bufname<".bufname("%").">")
if bufname("%") != "NetrwMessage"
- keepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./'))
- keepj call netrw#RestorePosn(svpos)
+ NetrwKeepj call s:NetrwRefresh(1,s:NetrwBrowseChgDir(1,'./'))
+ NetrwKeepj call netrw#RestorePosn(svpos)
endif
let @@= ykeep
@@ -9649,7 +10137,7 @@ fun! s:NetrwLocalRmFile(path,fname,all)
let all= a:all
let ok = ""
- keepj norm! 0
+ NetrwKeepj norm! 0
let rmfile= s:ComposePath(a:path,a:fname)
" call Decho("rmfile<".rmfile.">")
@@ -9745,7 +10233,7 @@ fun! netrw#Access(ilist)
endfun
" ------------------------------------------------------------------------
-" netrw#RestorePosn: restores the cursor and file position as saved by NetrwSavePosn() {{{2
+" netrw#RestorePosn: restores the cursor and file position as saved by netrw#SavePosn() {{{2
fun! netrw#RestorePosn(...)
" call Dfunc("netrw#RestorePosn() a:0=".a:0." winnr=".(exists("w:netrw_winnr")? w:netrw_winnr : -1)." line=".(exists("w:netrw_line")? w:netrw_line : -1)." col=".(exists("w:netrw_col")? w:netrw_col : -1)." hline=".(exists("w:netrw_hline")? w:netrw_hline : -1))
let eikeep= &ei
@@ -9759,60 +10247,60 @@ fun! netrw#RestorePosn(...)
if a:0 > 0
exe "keepj ".a:1
endif
-" call Decho("a:1 = ".((a:0 > 0)? a:1 : 'n/a'))
-" call Decho("liststyle = ".(exists("liststyle")? liststyle : 'n/a'). " w:netrw_liststyle=".(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'))
+" "call Decho("a:1 = ".((a:0 > 0)? a:1 : 'n/a'))
+" "call Decho("liststyle = ".(exists("liststyle")? liststyle : 'n/a'). " w:netrw_liststyle=".(exists("w:netrw_liststyle")? w:netrw_liststyle : 'n/a'))
if exists("liststyle") && exists("w:netrw_liststyle") && liststyle != w:netrw_liststyle
let usesrch= 1
else
let usesrch= 0
endif
-" call Decho("winh = ".(exists("w:netrw_winh")? w:netrw_winh : -1))
-" call Decho("winw = ".(exists("w:netrw_winw")? w:netrw_winw : -1))
-" call Decho("cur winheight=".winheight(0)." winwidth=".winwidth(0))
-" call Decho("w:netrw_winfile = ".(exists("w:netrw_winfile")? w:netrw_winfile : 'n/a'))
+" "call Decho("winh = ".(exists("w:netrw_winh")? w:netrw_winh : -1))
+" "call Decho("winw = ".(exists("w:netrw_winw")? w:netrw_winw : -1))
+" "call Decho("cur winheight=".winheight(0)." winwidth=".winwidth(0))
+" "call Decho("w:netrw_winfile = ".(exists("w:netrw_winfile")? w:netrw_winfile : 'n/a'))
" restore window
if exists("w:netrw_winnr")
-" call Decho("restore window: exe sil! ".w:netrw_winnr."wincmd w")
+" "call Decho("restore window: exe sil! ".w:netrw_winnr."wincmd w")
exe "sil! ".w:netrw_winnr."wincmd w"
endif
- if v:shell_error == 0
+" if v:shell_error == 0
" as suggested by Bram M: redraw on no error
" allows protocol error messages to remain visible
" redraw!
- endif
+" endif
" restore top-of-screen line
if exists("w:netrw_hline")
-" call Decho("restore topofscreen: exe keepj norm! ".w:netrw_hline."G0z")
+" "call Decho("restore topofscreen: exe keepj norm! ".w:netrw_hline."G0z")
exe "keepj norm! ".w:netrw_hline."G0z\<CR>"
endif
" restore position
" when the window's height x width has changed, the line,col is no longer useful
if w:netrw_winh == winheight(0) && w:netrw_winw == winwidth(0) && exists("w:netrw_line") && exists("w:netrw_col") && !usesrch
-" call Decho("using posn: exe keepj norm! ".w:netrw_line."G0".w:netrw_col."|")
+" "call Decho("using posn: exe keepj norm! ".w:netrw_line."G0".w:netrw_col."|")
exe "keepj norm! ".w:netrw_line."G0".w:netrw_col."\<bar>"
elseif exists("w:netrw_winfile")
if !search('\<'.escape(w:netrw_winfile,g:netrw_fname_escape),'cw')
if exists("w:netrw_bannercnt")
-" call Decho("using bannercnt: win#".winnr()." ".winheight(0)."x".winwidth(0)." w:netrw_winfile<".w:netrw_winfile.">")
+" "call Decho("using bannercnt: win#".winnr()." ".winheight(0)."x".winwidth(0)." w:netrw_winfile<".w:netrw_winfile.">")
exe "keepj ".w:netrw_bannercnt
norm! 0
else
" go to upper left corner
-" call Decho("goto ulc: win#".winnr()." ".winheight(0)."x".winwidth(0)." w:netrw_winfile<".w:netrw_winfile.">")
+" "call Decho("goto ulc: win#".winnr()." ".winheight(0)."x".winwidth(0)." w:netrw_winfile<".w:netrw_winfile.">")
keepj 1
norm! 0
endif
else
-" call Decho("used search: w:netrw_winfile<".w:netrw_winfile.">")
+" "call Decho("used search: w:netrw_winfile<".w:netrw_winfile.">")
endif
else
-" call Decho("goto ulc: win#".winnr()." ".winheight(0)."x".winwidth(0))
+" "call Decho("goto ulc: win#".winnr()." ".winheight(0)."x".winwidth(0))
keepj 1
norm! 0
endif
@@ -9838,22 +10326,22 @@ fun! netrw#SavePosn()
let w:netrw_winnr= winnr()
let w:netrw_line = line(".")
let w:netrw_col = virtcol(".")
-" call Decho("currently, win#".w:netrw_winnr." line#".w:netrw_line." col#".w:netrw_col)
+" "call Decho("currently, win#".w:netrw_winnr." line#".w:netrw_line." col#".w:netrw_col)
" save filename under cursor
-" call Decho("line#".line(".")." w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a'))
+" "call Decho("line#".line(".")." w:netrw_bannercnt=".(exists("w:netrw_bannercnt")? w:netrw_bannercnt : 'n/a'))
if exists("w:netrw_bannercnt") && line(".") >= w:netrw_bannercnt && &ft == "netrw"
- let winfile = "|let w:netrw_winfile='".s:NetrwGetWord()."'"
+ let winfile = "|let w:netrw_winfile=\"".fnameescape(s:NetrwGetWord())."\""
else
let winfile= ""
endif
-" call Decho("winfile<".winfile.">")
+" "call Decho("winfile<".winfile.">")
if exists("w:netrw_liststyle")
let liststyle = "|let liststyle=".w:netrw_liststyle
else
let liststyle= ""
endif
-" call Decho("liststyle=".liststyle)
+" "call Decho("liststyle=".liststyle)
" Save top-of-screen line
keepj norm! H0
@@ -9951,6 +10439,33 @@ fun! s:ComposePath(base,subdir)
endfun
" ---------------------------------------------------------------------
+" s:DeleteBookmark: deletes a file/directory from Netrw's bookmark system {{{2
+" Related Functions: s:MakeBookmark() s:NetrwBookHistHandler() s:NetrwBookmark()
+fun! s:DeleteBookmark(fname)
+" call Dfunc("s:DeleteBookmark(fname<".a:fname.">)")
+ call s:MergeBookmarks()
+
+ if exists("g:netrw_bookmarklist")
+ let indx= index(g:netrw_bookmarklist,a:fname)
+ if indx == -1
+ let indx= 0
+ while indx < len(g:netrw_bookmarklist)
+ if g:netrw_bookmarklist[indx] =~ a:fname
+ call remove(g:netrw_bookmarklist,indx)
+ let indx= indx - 1
+ endif
+ let indx= indx + 1
+ endwhile
+ else
+ " remove exact match
+ call remove(g:netrw_bookmarklist,indx)
+ endif
+ endif
+
+" call Dret("s:DeleteBookmark")
+endfun
+
+" ---------------------------------------------------------------------
" s:FileReadable: o/s independent filereadable {{{2
fun! s:FileReadable(fname)
" call Dfunc("s:FileReadable(fname<".a:fname.">)")
@@ -9983,13 +10498,13 @@ fun! s:GetTempfile(fname)
" sanity check -- does the temporary file's directory exist?
if !isdirectory(substitute(tmpfile,'[^/]\+$','','e'))
" call Decho("ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)")
- keepj call netrw#ErrorMsg(s:ERROR,"your <".substitute(tmpfile,'[^/]\+$','','e')."> directory is missing!",2)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"your <".substitute(tmpfile,'[^/]\+$','','e')."> directory is missing!",2)
" call Dret("s:GetTempfile getcwd<".getcwd().">")
return ""
endif
" let netrw#NetSource() know about the tmpfile
- let s:netrw_tmpfile= tmpfile " used by netrw#NetSource() and netrw#NetrwBrowseX()
+ let s:netrw_tmpfile= tmpfile " used by netrw#NetSource() and netrw#BrowseX()
" call Decho("tmpfile<".tmpfile."> s:netrw_tmpfile<".s:netrw_tmpfile.">")
" o/s dependencies
@@ -10057,6 +10572,45 @@ fun! s:MakeSshCmd(sshcmd)
endfun
" ---------------------------------------------------------------------
+" s:MakeBookmark: enters a bookmark into Netrw's bookmark system {{{2
+fun! s:MakeBookmark(fname)
+" call Dfunc("s:MakeBookmark(fname<".a:fname.">)")
+
+ if !exists("g:netrw_bookmarklist")
+ let g:netrw_bookmarklist= []
+ endif
+
+ if index(g:netrw_bookmarklist,a:fname) == -1
+ " curdir not currently in g:netrw_bookmarklist, so include it
+ if isdirectory(a:fname) && a:fname !~ '/$'
+ call add(g:netrw_bookmarklist,a:fname.'/')
+ elseif a:fname !~ '/'
+ call add(g:netrw_bookmarklist,getcwd()."/".a:fname)
+ else
+ call add(g:netrw_bookmarklist,a:fname)
+ endif
+ call sort(g:netrw_bookmarklist)
+ endif
+
+" call Dret("s:MakeBookmark")
+endfun
+
+" ---------------------------------------------------------------------
+" s:MergeBookmarks: merge current bookmarks with saved bookmarks {{{2
+fun! s:MergeBookmarks()
+" call Dfunc("s:MergeBookmarks()")
+ " get bookmarks from .netrwbook file
+ let savefile= s:NetrwHome()."/.netrwbook"
+ if filereadable(savefile)
+" call Decho("merge bookmarks (active and file)")
+ NetrwKeepj call s:NetrwBookHistSave()
+" call Decho("bookmark delete savefile<".savefile.">")
+ NetrwKeepj call delete(savefile)
+ endif
+" call Dret("s:MergeBookmarks")
+endfun
+
+" ---------------------------------------------------------------------
" s:NetrwBMShow: {{{2
fun! s:NetrwBMShow()
" call Dfunc("s:NetrwBMShow()")
@@ -10069,7 +10623,7 @@ fun! s:NetrwBMShow()
if bmshowfuncs != []
let bmshowfunc = substitute(bmshowfuncs[0],'^.*:\(call.*BMShow()\).*$','\1','')
if bmshowfunc =~ '^call.*BMShow()'
- exe "sil! keepj ".bmshowfunc
+ exe "sil! NetrwKeepj ".bmshowfunc
endif
endif
endif
@@ -10176,7 +10730,7 @@ fun! s:NetrwDelete(path)
let result= delete(path)
endif
if result < 0
- keepj call netrw#ErrorMsg(s:WARNING,"delete(".path.") failed!",71)
+ NetrwKeepj call netrw#ErrorMsg(s:WARNING,"delete(".path.") failed!",71)
endif
" call Dret("s:NetrwDelete ".result)
@@ -10208,13 +10762,13 @@ fun! s:NetrwEnew(...)
if exists("b:netrw_option") |let netrw_option = b:netrw_option |endif
if exists("b:netrw_prvdir") |let netrw_prvdir = b:netrw_prvdir |endif
- keepj call s:NetrwOptionRestore("w:")
-" call Decho("generate a buffer with keepjumps keepalt enew!")
+ NetrwKeepj call s:NetrwOptionRestore("w:")
+" call Decho("generate a buffer with NetrwKeepj keepalt enew!")
let netrw_keepdiff= &l:diff
- noswapfile keepj keepalt enew!
+ noswapfile NetrwKeepj keepalt enew!
let &l:diff= netrw_keepdiff
" call Decho("bufnr($)=".bufnr("$")." winnr($)=".winnr("$"))
- keepj call s:NetrwOptionSave("w:")
+ NetrwKeepj call s:NetrwOptionSave("w:")
" copy function-local-variables to buffer variable equivalents
" call Decho("copy function-local variables back to buffer netrw variables")
@@ -10253,6 +10807,22 @@ fun! s:NetrwEnew(...)
endfun
" ---------------------------------------------------------------------
+" s:NetrwExe: executes a string using "!" {{{2
+fun! s:NetrwExe(cmd)
+" call Dfunc("s:NetrwExe(a:cmd)")
+ if has("win32") && &shell !~? 'cmd' && !g:netrw_cygwin
+ let savedShell=[&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash]
+ set shell& shellcmdflag& shellxquote& shellxescape&
+ set shellquote& shellpipe& shellredir& shellslash&
+ exe a:cmd
+ let [&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash] = savedShell
+ else
+ exe a:cmd
+ endif
+" call Dret("s:NetrwExe")
+endfun
+
+" ---------------------------------------------------------------------
" s:NetrwInsureWinVars: insure that a netrw buffer has its w: variables in spite of a wincmd v or s {{{2
fun! s:NetrwInsureWinVars()
" call Dfunc("s:NetrwInsureWinVars() win#".winnr())
@@ -10286,7 +10856,7 @@ fun! s:NetrwLcd(newdir)
" call Dfunc("s:NetrwLcd(newdir<".a:newdir.">)")
try
- exe 'keepj sil lcd '.fnameescape(a:newdir)
+ exe 'NetrwKeepj sil lcd '.fnameescape(a:newdir)
catch /^Vim\%((\a\+)\)\=:E344/
" Vim's lcd fails with E344 when attempting to go above the 'root' of a Windows share.
" Therefore, detect if a Windows share is present, and if E344 occurs, just settle at
@@ -10296,7 +10866,7 @@ fun! s:NetrwLcd(newdir)
if (has("win32") || has("win95") || has("win64") || has("win16")) && !g:netrw_cygwin
if a:newdir =~ '^\\\\\w\+' || a:newdir =~ '^//\w\+'
let dirname = '\'
- exe 'keepj sil lcd '.fnameescape(dirname)
+ exe 'NetrwKeepj sil lcd '.fnameescape(dirname)
endif
endif
catch /^Vim\%((\a\+)\)\=:E472/
@@ -10388,9 +10958,9 @@ endfun
fun! s:RemoteSystem(cmd)
" call Dfunc("s:RemoteSystem(cmd<".a:cmd.">)")
if !executable(g:netrw_ssh_cmd)
- keepj call netrw#ErrorMsg(s:ERROR,"g:netrw_ssh_cmd<".g:netrw_ssh_cmd."> is not executable!",52)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"g:netrw_ssh_cmd<".g:netrw_ssh_cmd."> is not executable!",52)
elseif !exists("b:netrw_curdir")
- keepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
+ NetrwKeepj call netrw#ErrorMsg(s:ERROR,"for some reason b:netrw_curdir doesn't exist!",53)
else
let cmd = s:MakeSshCmd(g:netrw_ssh_cmd." USEPORT HOSTNAME")
let remotedir= substitute(b:netrw_curdir,'^.*//[^/]\+/\(.*\)$','\1','')
@@ -10447,13 +11017,21 @@ fun! s:NetrwRexplore(islocal,dirname)
" call Decho("ft=".&ft." win#".winnr()." w:netrw_rexfile<".(exists("w:netrw_rexfile")? w:netrw_rexfile : 'n/a').">")
if &ft == "netrw" && exists("w:netrw_rexfile") && w:netrw_rexfile != ""
+ " a :Rex while in a netrw buffer means: edit the file in w:netrw_rexfile
" call Decho("in netrw buffer, will edit file<".w:netrw_rexfile.">")
- exe "e ".w:netrw_rexfile
+ exe "NetrwKeepj e ".w:netrw_rexfile
unlet w:netrw_rexfile
" call Dret("s:NetrwRexplore returning from netrw to buf#".bufnr("%")."<".bufname("%")."> (ft=".&ft.")")
return
+" else " Decho
+" call Decho("treating as not-netrw-buffer: ft=".&ft.((&ft == "netrw")? " == netrw" : "!= netrw"))
+" call Decho("treating as not-netrw-buffer: w:netrw_rexfile<".((exists("w:netrw_rexfile"))? w:netrw_rexfile : 'n/a').">")
endif
+ " ---------------------------
+ " :Rex issued while in a file
+ " ---------------------------
+
" record current file so :Rex can return to it from netrw
let w:netrw_rexfile= expand("%")
@@ -10463,16 +11041,26 @@ fun! s:NetrwRexplore(islocal,dirname)
endif
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo)
if w:netrw_rexlocal
- keepj call netrw#LocalBrowseCheck(w:netrw_rexdir)
- else
+ if g:netrw_keepj =~ "keepj"
+ keepj call netrw#LocalBrowseCheck(w:netrw_rexdir)
+ else
+ call netrw#LocalBrowseCheck(w:netrw_rexdir)
+ endif
+ elseif g:netrw_keepj =~ "keepj"
keepj call s:NetrwBrowse(0,w:netrw_rexdir)
+ else
+ call s:NetrwBrowse(0,w:netrw_rexdir)
endif
if exists("s:initbeval")
setl beval
endif
if exists("s:rexposn_".bufnr("%"))
" call Decho("restore posn, then unlet s:rexposn_".bufnr('%'))
- keepj call netrw#RestorePosn(s:rexposn_{bufnr('%')})
+ if g:netrw_keepj =~ "keepj"
+ keepj call netrw#RestorePosn(s:rexposn_{bufnr('%')})
+ else
+ call netrw#RestorePosn(s:rexposn_{bufnr('%')})
+ endif
unlet s:rexposn_{bufnr('%')}
else
" call Decho("s:rexposn_".bufnr('%')." doesn't exist")
@@ -10559,6 +11147,54 @@ fun! s:SetRexDir(islocal,dirname)
endfun
" ---------------------------------------------------------------------
+" s:ShowLink: used to modify thin and tree listings to show links {{{2
+fun! s:ShowLink()
+" " call Dfunc("s:ShowLink()")
+" " call Decho("b:netrw_curdir<".(exists("b:netrw_curdir")? b:netrw_curdir : "doesn't exist").">")
+" " call Decho(printf("line#%4d: %s",line("."),getline(".")))
+ if exists("b:netrw_curdir")
+ norm! $?\a
+ let fname = b:netrw_curdir.'/'.s:NetrwGetWord()
+ let resname = resolve(fname)
+ if resname =~ '^\M'.b:netrw_curdir
+ let dirlen = strlen(b:netrw_curdir)
+ let resname = strpart(resname,dirlen+1)
+" " call Decho("resname<".resname."> (b:netrw_curdir elided)")
+" " else " Decho
+" " call Decho("resname<".fname.">")
+ endif
+ let modline = getline(".")."\t --> ".resname
+" " call Decho("fname <".fname.">")
+" " call Decho("modline<".modline.">")
+ setl noro ma
+ call setline(".",modline)
+ setl ro noma nomod
+ endif
+" " call Dret("s:ShowLink".((exists("fname")? ' : '.fname : 'n/a')))
+endfun
+
+" ---------------------------------------------------------------------
+" s:ShowStyle: {{{2
+fun! s:ShowStyle()
+ if !exists("w:netrw_liststyle")
+ let liststyle= g:netrw_liststyle
+ else
+ let liststyle= w:netrw_liststyle
+ endif
+ if liststyle == s:THINLIST
+ return s:THINLIST.":thin"
+ elseif liststyle == s:LONGLIST
+ return s:LONGLIST.":long"
+ elseif liststyle == s:WIDELIST
+ return s:WIDELIST.":wide"
+ elseif liststyle == s:TREELIST
+ return s:TREELIST.":tree"
+ else
+ return 'n/a'
+ endif
+endfun
+
+" ---------------------------------------------------------------------
" s:Strlen: this function returns the length of a string, even if its using multi-byte characters. {{{2
" Solution from Nicolai Weibull, vim docs (:help strlen()),
" Tony Mechelynck, and my own invention.
@@ -10593,7 +11229,7 @@ fun! s:Strlen(x)
call setline(line("."),a:x)
let ret= virtcol("$") - 1
d
- keepj norm! k
+ NetrwKeepj norm! k
let &l:mod= modkeep
else
@@ -10622,17 +11258,17 @@ fun! s:TreeListMove(dir)
if curline !~ '/$'
" call Decho('regfile')
if a:dir == '[' && prvline != ''
- keepj norm! 0
+ NetrwKeepj norm! 0
let nl = search('^'.indentm1.'[^'.s:treedepthstring.']','bWe') " search backwards from regular file
" call Decho("regfile srch back: ".nl)
elseif a:dir == ']' && nxtline != ''
- keepj norm! $
+ NetrwKeepj norm! $
let nl = search('^'.indentm1.'[^'.s:treedepthstring.']','We') " search forwards from regular file
" call Decho("regfile srch fwd: ".nl)
endif
elseif a:dir == '[' && prvline != ''
- keepj norm! 0
+ NetrwKeepj norm! 0
let curline= line(".")
let nl = search('^'.curindent.'[^'.s:treedepthstring.']','bWe') " search backwards From directory, same indentation
" call Decho("dir srch back ind: ".nl)
@@ -10644,7 +11280,7 @@ fun! s:TreeListMove(dir)
endif
elseif a:dir == ']' && nxtline != ''
- keepj norm! $
+ NetrwKeepj norm! $
let curline = line(".")
let nl = search('^'.curindent.'[^'.s:treedepthstring.']','We') " search forwards from directory, same indentation
" call Decho("dir srch fwd ind: ".nl)
@@ -10672,7 +11308,7 @@ fun! s:UpdateBuffersMenu()
sil emenu Buffers.Refresh\ menu
catch /^Vim\%((\a\+)\)\=:E/
let v:errmsg= ""
- sil keepj call s:NetrwBMShow()
+ sil NetrwKeepj call s:NetrwBMShow()
endtry
endif
" call Dret("s:UpdateBuffersMenu")
diff --git a/runtime/autoload/netrwSettings.vim b/runtime/autoload/netrwSettings.vim
index 541ee65ccb..4639909ee8 100644
--- a/runtime/autoload/netrwSettings.vim
+++ b/runtime/autoload/netrwSettings.vim
@@ -1,7 +1,7 @@
" netrwSettings.vim: makes netrw settings simpler
-" Date: Aug 27, 2013
+" Date: Dec 30, 2014
" Maintainer: Charles E Campbell <drchipNOSPAM at campbellfamily dot biz>
-" Version: 14
+" Version: 15
" Copyright: Copyright (C) 1999-2007 Charles E. Campbell {{{1
" Permission is hereby granted to use and distribute this code,
" with or without modifications, provided that this copyright
@@ -19,7 +19,7 @@
if exists("g:loaded_netrwSettings") || &cp
finish
endif
-let g:loaded_netrwSettings = "v14"
+let g:loaded_netrwSettings = "v15"
if v:version < 700
echohl WarningMsg
echo "***warning*** this version of netrwSettings needs vim 7.0"
@@ -31,7 +31,7 @@ endif
" NetrwSettings: {{{1
fun! netrwSettings#NetrwSettings()
" this call is here largely just to insure that netrw has been loaded
- call netrw#NetrwSavePosn()
+ call netrw#SavePosn()
if !exists("g:loaded_netrw")
echohl WarningMsg | echomsg "***sorry*** netrw needs to be loaded prior to using NetrwSettings" | echohl None
return
@@ -177,6 +177,7 @@ fun! netrwSettings#NetrwSettings()
put = 'let g:netrw_sort_direction = '.g:netrw_sort_direction
put = 'let g:netrw_sort_options = '.g:netrw_sort_options
put = 'let g:netrw_sort_sequence = '.g:netrw_sort_sequence
+ put = 'let g:netrw_servername = '.g:netrw_servername
put = 'let g:netrw_special_syntax = '.g:netrw_special_syntax
put = 'let g:netrw_ssh_browse_reject = '.g:netrw_ssh_browse_reject
put = 'let g:netrw_ssh_cmd = '.g:netrw_ssh_cmd
diff --git a/runtime/autoload/netrw_gitignore.vim b/runtime/autoload/netrw_gitignore.vim
index 0de902cbb4..da3f50bca0 100644
--- a/runtime/autoload/netrw_gitignore.vim
+++ b/runtime/autoload/netrw_gitignore.vim
@@ -61,9 +61,16 @@ function! netrw_gitignore#Hide(...)
" convert gitignore patterns to Netrw/Vim regex patterns
let escaped_lines = []
for line in gitignore_lines
- let escaped = line
- let escaped = substitute(escaped, '\.', '\\.', 'g')
- let escaped = substitute(escaped, '*', '.*', 'g')
+ let escaped = line
+ let escaped = substitute(escaped, '\*\*', '*', 'g')
+ let escaped = substitute(escaped, '\.', '\\.', 'g')
+ let escaped = substitute(escaped, '\$', '\\$', 'g')
+ let escaped = substitute(escaped, '*', '.*', 'g')
+ " correction: dot, dollar and asterisks chars shouldn't be escaped when
+ " within regex matching groups.
+ let escaped = substitute(escaped, '\(\[[^]]*\)\zs\\\.', '\.', 'g')
+ let escaped = substitute(escaped, '\(\[[^]]*\)\zs\\\$', '\$', 'g')
+ let escaped = substitute(escaped, '\(\[[^]]*\)\zs\.\*', '*', 'g')
let escaped_lines = add(escaped_lines, escaped)
endfor
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index af9676272f..e922d5b601 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -265,8 +265,8 @@ Name triggered by ~
|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 viminfo file
-|VimLeave| before exiting Vim, after writing the viminfo file
+|VimLeavePre| before exiting Vim, before writing the shada file
+|VimLeave| before exiting Vim, after writing the shada file
Various
|FileChangedShell| Vim notices that a file changed since editing started
@@ -912,14 +912,14 @@ VimEnter After doing all the startup stuff, including
the buffers in them.
*VimLeave*
VimLeave Before exiting Vim, just after writing the
- .viminfo file. Executed only once, like
+ .shada file. Executed only once, like
VimLeavePre.
To detect an abnormal exit use |v:dying|.
When v:dying is 2 or more this event is not
triggered.
*VimLeavePre*
VimLeavePre Before exiting Vim, just before writing the
- .viminfo file. This is executed only once,
+ .shada file. This is executed only once,
if there is a match with the name of what
happens to be the current buffer when exiting.
Mostly useful with a "*" pattern. >
@@ -1375,7 +1375,7 @@ use search patterns normally, e.g., with the "n" command.
If you want an autocommand to set the search pattern, such that it is used
after the autocommand finishes, use the ":let @/ =" command.
The search-highlighting cannot be switched off with ":nohlsearch" in an
-autocommand. Use the 'h' flag in the 'viminfo' option to disable search-
+autocommand. Use the 'h' flag in the 'shada' option to disable search-
highlighting when starting Vim.
*Cmd-event*
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index b208bba9a8..627a7000ec 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -729,7 +729,7 @@ function expand() |expand()|.
#<n (where n is a number > 0) is replaced with old *:_#<* *c_#<*
file name n. See |:oldfiles| or |v:oldfiles| to get the
number. *E809*
- {only when compiled with the |+eval| and |+viminfo| features}
+ {only when compiled with the |+eval| and |+shada| features}
Note that these, except "#<n", give the file name as it was typed. If an
absolute path is needed (when using the file name from a different directory),
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 2951566ecb..0a17521c08 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -557,9 +557,9 @@ Functions that can be used with a Dictionary: >
If you need to know the type of a variable or expression, use the |type()|
function.
-When the '!' flag is included in the 'viminfo' option, global variables that
+When the '!' flag is included in the 'shada' option, global variables that
start with an uppercase letter, and don't contain a lowercase letter, are
-stored in the viminfo file |viminfo-file|.
+stored in the shada file |shada-file|.
When the 'sessionoptions' option contains "global", global variables that
start with an uppercase letter and contain at least one lowercase letter are
@@ -568,7 +568,7 @@ stored in the session file |session-file|.
variable name can be stored where ~
my_var_6 not
My_Var_6 session file
-MY_VAR_6 viminfo file
+MY_VAR_6 shada file
It's possible to form a variable name with curly braces, see
@@ -1524,16 +1524,16 @@ v:msgpack_types Dictionary containing msgpack types used by |msgpackparse()|
of msgpack types, use |is| operator.
*v:oldfiles* *oldfiles-variable*
-v:oldfiles List of file names that is loaded from the |viminfo| file on
+v:oldfiles List of file names that is loaded from the |shada| file on
startup. These are the files that Vim remembers marks for.
The length of the List is limited by the ' argument of the
- 'viminfo' option (default is 100).
- When the |viminfo| file is not used the List is empty.
+ 'shada' option (default is 100).
+ When the |shada| file is not used the List is empty.
Also see |:oldfiles| and |c_#<|.
The List can be modified, but this has no effect on what is
- stored in the |viminfo| file later. If you use values other
+ stored in the |shada| file later. If you use values other
than String this will cause trouble.
- {only when compiled with the |+viminfo| feature}
+ {only when compiled with the |+shada| feature}
*v:operator* *operator-variable*
v:operator The last operator given in Normal mode. This is a single
@@ -6936,6 +6936,7 @@ quickfix Compiled with |quickfix| support.
reltime Compiled with |reltime()| support.
rightleft Compiled with 'rightleft' support.
scrollbind Compiled with 'scrollbind' support.
+shada Compiled with shada support.
showcmd Compiled with 'showcmd' support.
signs Compiled with |:sign| support.
smartindent Compiled with 'smartindent' support.
@@ -6964,7 +6965,6 @@ unix Unix version of Vim.
user_commands User-defined commands.
vertsplit Compiled with vertically split windows |:vsplit|.
vim_starting True while initial source'ing takes place. |startup|
-viminfo Compiled with viminfo support.
virtualedit Compiled with 'virtualedit' option.
visual Compiled with Visual mode.
visualextra Compiled with extra Visual mode commands.
diff --git a/runtime/doc/gui_w32.txt b/runtime/doc/gui_w32.txt
index 2bf4d72343..6ae7fa2431 100644
--- a/runtime/doc/gui_w32.txt
+++ b/runtime/doc/gui_w32.txt
@@ -1,4 +1,4 @@
-*gui_w32.txt* For Vim version 7.4. Last change: 2012 Aug 04
+*gui_w32.txt* For Vim version 7.4. Last change: 2014 Dec 20
VIM REFERENCE MANUAL by Bram Moolenaar
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index 81ba0495be..0238d7cc53 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -1,4 +1,4 @@
-*index.txt* For Vim version 7.4. Last change: 2014 Sep 19
+*index.txt* For Vim version 7.4. Last change: 2015 Jan 07
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1359,7 +1359,7 @@ tag command action ~
|:number| :nu[mber] print lines with line number
|:nunmap| :nun[map] like ":unmap" but for Normal mode
|:nunmenu| :nunme[nu] remove menu for Normal mode
-|:oldfiles| :ol[dfiles] list files that have marks in the viminfo file
+|:oldfiles| :ol[dfiles] list files that have marks in the ShaDa file
|:open| :o[pen] start open mode (not implemented)
|:omap| :om[ap] like ":map" but for Operator-pending mode
|:omapclear| :omapc[lear] remove all mappings for Operator-pending mode
@@ -1418,9 +1418,9 @@ tag command action ~
|:rewind| :rew[ind] go to the first file in the argument list
|:right| :ri[ght] right align text
|:rightbelow| :rightb[elow] make split window appear right or below
+|:rshada| :rsh[ada] read from ShaDa file
|:rundo| :rund[o] read undo information from a file
|:runtime| :ru[ntime] source vim scripts in 'runtimepath'
-|:rviminfo| :rv[iminfo] read from viminfo file
|:substitute| :s[ubstitute] find and replace text
|:sNext| :sN[ext] split window and go to previous file in
argument list
@@ -1446,7 +1446,7 @@ tag command action ~
buffer list
|:sbrewind| :sbr[ewind] split window and go to first file in the
buffer list
-|:scriptnames| :scrip[tnames] list names of all sourced Vim scripts
+|:scriptnames| :scr[iptnames] list names of all sourced Vim scripts
|:scriptencoding| :scripte[ncoding] encoding used in sourced Vim script
|:scscope| :scs[cope] split window and execute cscope command
|:set| :se[t] show or set options
@@ -1579,8 +1579,8 @@ tag command action ~
argument list
|:wq| :wq write to a file and quit window or Vim
|:wqall| :wqa[ll] write all changed buffers and quit Vim
+|:wshada| :wsh[ada] write to ShaDa file
|:wundo| :wu[ndo] write undo information to a file
-|:wviminfo| :wv[iminfo] write to viminfo file
|:xit| :x[it] write if buffer changed and quit window or Vim
|:xall| :xa[ll] same as ":wqall"
|:xmapclear| :xmapc[lear] remove all mappings for Visual mode
diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt
index c959c12fb4..17d96103b8 100644
--- a/runtime/doc/mbyte.txt
+++ b/runtime/doc/mbyte.txt
@@ -387,7 +387,7 @@ Vim will automatically convert from one to another encoding in several places:
'encoding' (requires a gettext version that supports this).
- When reading a Vim script where |:scriptencoding| is different from
'encoding'.
-- When reading or writing a |viminfo| file.
+- When reading or writing a |shada| file.
Most of these require the |+iconv| feature. Conversion for reading and
writing files may also be specified with the 'charconvert' option.
diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index 8e96aedba6..2e8f0801dc 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -805,7 +805,7 @@ unrelated.
'a - 'z lowercase marks, valid within one file
'A - 'Z uppercase marks, also called file marks, valid between files
-'0 - '9 numbered marks, set from .viminfo file
+'0 - '9 numbered marks, set from .shada file
Lowercase marks 'a to 'z are remembered as long as the file remains in the
buffer list. If you remove the file from the buffer list, all its marks are
@@ -820,14 +820,14 @@ Uppercase marks 'A to 'Z include the file name.
You can use them to jump from file to file. You can only use an uppercase
mark with an operator if the mark is in the current file. The line number of
the mark remains correct, even if you insert/delete lines or edit another file
-for a moment. When the 'viminfo' option is not empty, uppercase marks are
-kept in the .viminfo file. See |viminfo-file-marks|.
+for a moment. When the 'shada' option is not empty, uppercase marks are
+kept in the .shada file. See |shada-file-marks|.
Numbered marks '0 to '9 are quite different. They can not be set directly.
-They are only present when using a viminfo file |viminfo-file|. Basically '0
+They are only present when using a shada file |shada-file|. Basically '0
is the location of the cursor when you last exited Vim, '1 the last but one
-time, etc. Use the "r" flag in 'viminfo' to specify files for which no
-Numbered mark should be stored. See |viminfo-file-marks|.
+time, etc. Use the "r" flag in 'shada' to specify files for which no
+Numbered mark should be stored. See |shada-file-marks|.
*'[* *`[*
@@ -1074,8 +1074,8 @@ if you stop editing a file without writing, like with ":n!".
When you split a window, the jumplist will be copied to the new window.
-If you have included the ' item in the 'viminfo' option the jumplist will be
-stored in the viminfo file and restored when starting Vim.
+If you have included the ' item in the 'shada' option the jumplist will be
+stored in the ShaDa file and restored when starting Vim.
CHANGE LIST JUMPS *changelist* *change-list-jumps* *E664*
diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt
index d03d97ec85..d732e7f818 100644
--- a/runtime/doc/msgpack_rpc.txt
+++ b/runtime/doc/msgpack_rpc.txt
@@ -175,7 +175,7 @@ Nil -> msgpack nil
Boolean -> msgpack boolean
Integer (signed 64-bit integer) -> msgpack integer
Float (IEEE 754 double precision) -> msgpack float
-String -> msgpack binary
+String -> msgpack string
Array -> msgpack array
Dictionary -> msgpack map
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 897137ff42..4b3dbb8a15 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt* For Vim version 7.4. Last change: 2014 Nov 05
+*options.txt* For Vim version 7.4. Last change: 2014 Dec 17
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -663,8 +663,6 @@ A jump table for the options with a short description can be found at |Q_op|.
open a file, switch buffers, delete a buffer or open/close a window.
It will change to the directory containing the file which was opened
or selected.
- This option is provided for backward compatibility with the Vim
- released with Sun ONE Studio 4 Enterprise Edition.
Note: When this option is on some plugins may not work.
*'arabic'* *'arab'* *'noarabic'* *'noarab'*
@@ -1292,8 +1290,8 @@ A jump table for the options with a short description can be found at |Q_op|.
used.
Conversion between "latin1", "unicode", "ucs-2", "ucs-4" and "utf-8"
is done internally by Vim, 'charconvert' is not used for this.
- 'charconvert' is also used to convert the viminfo file, if the 'c'
- flag is present in 'viminfo'. Also used for Unicode conversion.
+ 'charconvert' is also used to convert the shada file, if 'encoding' is
+ not "utf-8". Also used for Unicode conversion.
Example: >
set charconvert=CharConvert()
fun CharConvert()
@@ -2165,7 +2163,7 @@ A jump table for the options with a short description can be found at |Q_op|.
feature}
Sets the character encoding used inside Vim. It applies to text in
the buffers, registers, Strings in expressions, text stored in the
- viminfo file, etc. It sets the kind of characters which Vim can work
+ shada file, etc. It sets the kind of characters which Vim can work
with. See |encoding-names| for the possible values.
'encoding' cannot be changed after startup, because (1) it causes
@@ -2197,7 +2195,7 @@ A jump table for the options with a short description can be found at |Q_op|.
When "unicode", "ucs-2" or "ucs-4" is used, Vim internally uses utf-8.
You don't notice this while editing, but it does matter for the
- |viminfo-file|. And Vim expects the terminal to use utf-8 too. Thus
+ |shada-file|. And Vim expects the terminal to use utf-8 too. Thus
setting 'encoding' to one of these values instead of utf-8 only has
effect for encoding used for files when 'fileencoding' is empty.
@@ -3409,7 +3407,7 @@ A jump table for the options with a short description can be found at |Q_op|.
line below a closed fold. A match in a previous line which is not
drawn may not continue in a newly drawn line.
You can specify whether the highlight status is restored on startup
- with the 'h' flag in 'viminfo' |viminfo-h|.
+ with the 'h' flag in 'shada' |shada-h|.
*'history'* *'hi'*
'history' 'hi' number (Vim default: 10000, Vi default: 0)
@@ -3795,7 +3793,8 @@ A jump table for the options with a short description can be found at |Q_op|.
option.
Careful: If you change this option, it might break expanding
environment variables. E.g., when '/' is included and Vim tries to
- expand "$HOME/.viminfo". Maybe you should change 'iskeyword' instead.
+ expand "$HOME/.nvim/shada/main.shada". Maybe you should change
+ 'iskeyword' instead.
*'iskeyword'* *'isk'*
'iskeyword' 'isk' string (Vim default for
@@ -3907,8 +3906,6 @@ A jump table for the options with a short description can be found at |Q_op|.
be able to execute Normal mode commands.
This is the opposite of the 'keymap' option, where characters are
mapped in Insert mode.
- Also consider setting 'langnoremap' to avoid 'langmap' applies to
- characters resulting from a mapping.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
@@ -3967,10 +3964,8 @@ A jump table for the options with a short description can be found at |Q_op|.
'langnoremap' 'lnr' boolean (default on)
global
When on, setting 'langmap' does not apply to characters resulting from
- a mapping. This basically means, if you noticed that setting
- 'langmap' disables some of your mappings, try setting this option.
- This option defaults to off for backwards compatibility. Set it on if
- that works for you to avoid mappings to break.
+ a mapping. If setting 'langmap' disables some of your mappings, make
+ sure this option is set.
*'laststatus'* *'ls'*
'laststatus' 'ls' number (default 2)
@@ -5290,6 +5285,123 @@ A jump table for the options with a short description can be found at |Q_op|.
with Unix. The Unix version of Vim cannot source dos format scripts,
but the Windows version of Vim can source unix format scripts.
+ *'shada'* *'sd'* *E526* *E527* *E528*
+'shada' 'sd' string (Vim default for
+ Win32: '!,100,<50,s10,h,rA:,rB:
+ others: '!,100,<50,s10,h
+ Vi default: "")
+ global
+ When non-empty, the shada file is read upon startup and written
+ when exiting Vim (see |shada-file|). The string should be a comma
+ separated list of parameters, each consisting of a single character
+ identifying the particular parameter, followed by a number or string
+ which specifies the value of that parameter. If a particular
+ character is left out, then the default value is used for that
+ parameter. The following is a list of the identifying characters and
+ the effect of their value.
+ CHAR VALUE ~
+ *shada-!*
+ ! When included, save and restore global variables that start
+ with an uppercase letter, and don't contain a lowercase
+ letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis"
+ and "_K_L_M" are not. Nested List and Dict items may not be
+ read back correctly, you end up with an empty item.
+ *shada-quote*
+ " Maximum number of lines saved for each register. Old name of
+ the '<' item, with the disadvantage that you need to put a
+ backslash before the ", otherwise it will be recognized as the
+ start of a comment!
+ *shada-%*
+ % When included, save and restore the buffer list. If Vim is
+ started with a file name argument, the buffer list is not
+ restored. If Vim is started without a file name argument, the
+ buffer list is restored from the shada file. Buffers
+ without a file name and buffers for help files are not written
+ to the shada file.
+ When followed by a number, the number specifies the maximum
+ number of buffers that are stored. Without a number all
+ buffers are stored.
+ *shada-'*
+ ' Maximum number of previously edited files for which the marks
+ are remembered. This parameter must always be included when
+ 'shada' is non-empty.
+ Including this item also means that the |jumplist| and the
+ |changelist| are stored in the shada file.
+ *shada-/*
+ / Maximum number of items in the search pattern history to be
+ saved. If non-zero, then the previous search and substitute
+ patterns are also saved. When not included, the value of
+ 'history' is used.
+ *shada-:*
+ : Maximum number of items in the command-line history to be
+ saved. When not included, the value of 'history' is used.
+ *shada-<*
+ < Maximum number of lines saved for each register. If zero then
+ registers are not saved. When not included, all lines are
+ saved. '"' is the old name for this item.
+ Also see the 's' item below: limit specified in KiB.
+ *shada-@*
+ @ Maximum number of items in the input-line history to be
+ saved. When not included, the value of 'history' is used.
+ *shada-c*
+ c Dummy option, kept for compatibility reasons. Has no actual
+ effect. Current encoding state is described in
+ |shada-encoding|.
+ *shada-f*
+ f Whether file marks need to be stored. If zero, file marks ('0
+ to '9, 'A to 'Z) are not stored. When not present or when
+ non-zero, they are all stored. '0 is used for the current
+ cursor position (when exiting or when doing |:wshada|).
+ *shada-h*
+ h Disable the effect of 'hlsearch' when loading the shada
+ file. When not included, it depends on whether ":nohlsearch"
+ has been used since the last search command.
+ *shada-n*
+ n Name of the shada file. The name must immediately follow
+ the 'n'. Must be the last one! If the "-i" argument was
+ given when starting Vim, that file name overrides the one
+ given here with 'shada'. Environment variables are expanded
+ when opening the file, not when setting the option.
+ *shada-r*
+ r Removable media. The argument is a string (up to the next
+ ','). This parameter can be given several times. Each
+ specifies the start of a path for which no marks will be
+ stored. This is to avoid removable media. For MS-DOS you
+ could use "ra:,rb:". You can also use it for temp files,
+ e.g., for Unix: "r/tmp". Case is ignored.
+ *shada-s*
+ s Maximum size of an item contents in KiB. If zero then nothing
+ is saved. Unlike Vim this applies to all items, except for
+ the buffer list and header. Full item size is off by three
+ unsigned integers: with `s10` maximum item size may be 1 byte
+ (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
+ integer) + 3 bytes (item size: up to 16-bit integer because
+ 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
+ contents size) = 10253 bytes.
+
+ Example: >
+ :set shada='50,<1000,s100,:0,n~/nvim/shada
+<
+ '50 Marks will be remembered for the last 50 files you
+ edited.
+ <1000 Contents of registers (up to 1000 lines each) will be
+ remembered.
+ s100 Items with contents occupying more then 100 KiB are
+ skipped.
+ :0 Command-line history will not be saved.
+ n~/nvim/shada The name of the file to use is "~/nvim/shada".
+ no / Since '/' is not specified, the default will be used,
+ that is, save all of the search history, and also the
+ previous search and substitute patterns.
+ no % The buffer list will not be saved nor read back.
+ no h 'hlsearch' highlighting will be restored.
+
+ When setting 'shada' from an empty value you can use |:rshada| to
+ load the contents of the file, this is not done automatically.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+
*'shell'* *'sh'* *E91*
'shell' 'sh' string (default $SHELL or "sh",
MS-DOS and Win32: "command.com" or
@@ -6622,7 +6734,7 @@ A jump table for the options with a short description can be found at |Q_op|.
global
When bigger than zero, Vim will give messages about what it is doing.
Currently, these messages are given:
- >= 1 When the viminfo file is read or written.
+ >= 1 When the shada file is read or written.
>= 2 When a file is ":source"'ed.
>= 5 Every searched tags file and include file.
>= 8 Files for which a group of autocommands is executed.
@@ -6682,120 +6794,11 @@ A jump table for the options with a short description can be found at |Q_op|.
with Unix. The Unix version of Vim cannot source dos format scripts,
but the Windows version of Vim can source unix format scripts.
- *'viminfo'* *'vi'* *E526* *E527* *E528*
-'viminfo' 'vi' string (Vim default for
- Win32: '!,100,<50,s10,h,rA:,rB:
- others: '!,100,<50,s10,h
- Vi default: "")
+ *'viminfo'* *'vi'*
+'viminfo' 'vi' string
global
- {not available when compiled without the |+viminfo|
- feature}
- When non-empty, the viminfo file is read upon startup and written
- when exiting Vim (see |viminfo-file|). The string should be a comma
- separated list of parameters, each consisting of a single character
- identifying the particular parameter, followed by a number or string
- which specifies the value of that parameter. If a particular
- character is left out, then the default value is used for that
- parameter. The following is a list of the identifying characters and
- the effect of their value.
- CHAR VALUE ~
- *viminfo-!*
- ! When included, save and restore global variables that start
- with an uppercase letter, and don't contain a lowercase
- letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis"
- and "_K_L_M" are not. Nested List and Dict items may not be
- read back correctly, you end up with an empty item.
- *viminfo-quote*
- " Maximum number of lines saved for each register. Old name of
- the '<' item, with the disadvantage that you need to put a
- backslash before the ", otherwise it will be recognized as the
- start of a comment!
- *viminfo-%*
- % When included, save and restore the buffer list. If Vim is
- started with a file name argument, the buffer list is not
- restored. If Vim is started without a file name argument, the
- buffer list is restored from the viminfo file. Buffers
- without a file name and buffers for help files are not written
- to the viminfo file.
- When followed by a number, the number specifies the maximum
- number of buffers that are stored. Without a number all
- buffers are stored.
- *viminfo-'*
- ' Maximum number of previously edited files for which the marks
- are remembered. This parameter must always be included when
- 'viminfo' is non-empty.
- Including this item also means that the |jumplist| and the
- |changelist| are stored in the viminfo file.
- *viminfo-/*
- / Maximum number of items in the search pattern history to be
- saved. If non-zero, then the previous search and substitute
- patterns are also saved. When not included, the value of
- 'history' is used.
- *viminfo-:*
- : Maximum number of items in the command-line history to be
- saved. When not included, the value of 'history' is used.
- *viminfo-<*
- < Maximum number of lines saved for each register. If zero then
- registers are not saved. When not included, all lines are
- saved. '"' is the old name for this item.
- Also see the 's' item below: limit specified in Kbyte.
- *viminfo-@*
- @ Maximum number of items in the input-line history to be
- saved. When not included, the value of 'history' is used.
- *viminfo-c*
- c When included, convert the text in the viminfo file from the
- 'encoding' used when writing the file to the current
- 'encoding'. See |viminfo-encoding|.
- *viminfo-f*
- f Whether file marks need to be stored. If zero, file marks ('0
- to '9, 'A to 'Z) are not stored. When not present or when
- non-zero, they are all stored. '0 is used for the current
- cursor position (when exiting or when doing ":wviminfo").
- *viminfo-h*
- h Disable the effect of 'hlsearch' when loading the viminfo
- file. When not included, it depends on whether ":nohlsearch"
- has been used since the last search command.
- *viminfo-n*
- n Name of the viminfo file. The name must immediately follow
- the 'n'. Must be the last one! If the "-i" argument was
- given when starting Vim, that file name overrides the one
- given here with 'viminfo'. Environment variables are expanded
- when opening the file, not when setting the option.
- *viminfo-r*
- r Removable media. The argument is a string (up to the next
- ','). This parameter can be given several times. Each
- specifies the start of a path for which no marks will be
- stored. This is to avoid removable media. For MS-DOS you
- could use "ra:,rb:". You can also use it for temp files,
- e.g., for Unix: "r/tmp". Case is ignored. Maximum length of
- each 'r' argument is 50 characters.
- *viminfo-s*
- s Maximum size of an item in Kbyte. If zero then registers are
- not saved. Currently only applies to registers. The default
- "s10" will exclude registers with more than 10 Kbyte of text.
- Also see the '<' item above: line count limit.
-
- Example: >
- :set viminfo='50,<1000,s100,:0,n~/vim/viminfo
-<
- '50 Marks will be remembered for the last 50 files you
- edited.
- <1000 Contents of registers (up to 1000 lines each) will be
- remembered.
- s100 Registers with more than 100 Kbyte text are skipped.
- :0 Command-line history will not be saved.
- n~/vim/viminfo The name of the file to use is "~/vim/viminfo".
- no / Since '/' is not specified, the default will be used,
- that is, save all of the search history, and also the
- previous search and substitute patterns.
- no % The buffer list will not be saved nor read back.
- no h 'hlsearch' highlighting will be restored.
-
- When setting 'viminfo' from an empty value you can use |:rviminfo| to
- load the contents of the file, this is not done automatically.
-
- This option cannot be set from a |modeline| or in the |sandbox|, for
- security reasons.
+ Deprecated alias for 'shada' option. Is kept for compatibility
+ reasons.
*'virtualedit'* *'ve'*
'virtualedit' 've' string (default "")
diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt
index 80815a70ec..7f5825ba25 100644
--- a/runtime/doc/pi_netrw.txt
+++ b/runtime/doc/pi_netrw.txt
@@ -1,4 +1,4 @@
-*pi_netrw.txt* For Vim version 7.4. Last change: 2014 May 13
+*pi_netrw.txt* For Vim version 7.4. Last change: 2015 Jan 05
------------------------------------------------
NETRW REFERENCE MANUAL by Charles E. Campbell
@@ -6,7 +6,7 @@
Author: Charles E. Campbell <NdrOchip@ScampbellPfamily.AbizM>
(remove NOSPAM from Campbell's email first)
-Copyright: Copyright (C) 1999-2014 Charles E Campbell *netrw-copyright*
+Copyright: Copyright (C) 1999-2015 Charles E Campbell *netrw-copyright*
The VIM LICENSE applies to the files in this package, including
netrw.vim, pi_netrw.txt, netrwFileHandlers.vim, netrwSettings.vim, and
syntax/netrw.vim. Like anything else that's free, netrw.vim and its
@@ -18,9 +18,10 @@ Copyright: Copyright (C) 1999-2014 Charles E Campbell *netrw-copyright*
software. Use at your own risk!
+ *netrw*
*dav* *ftp* *netrw-file* *rcp* *scp*
*davs* *http* *netrw.vim* *rsync* *sftp*
- *fetch* *netrw* *network*
+ *fetch* *network*
==============================================================================
1. Contents *netrw-contents* {{{1
@@ -57,7 +58,7 @@ Copyright: Copyright (C) 1999-2014 Charles E Campbell *netrw-copyright*
Changing To A Bookmarked Directory..................|netrw-gb|
Changing To A Predecessor Directory.................|netrw-u|
Changing To A Successor Directory...................|netrw-U|
- Customizing Browsing With A User Function...........|netrw-x|
+ Customizing Browsing With A Special Handler.........|netrw-x|
Deleting Bookmarks..................................|netrw-mB|
Deleting Files Or Directories.......................|netrw-D|
Directory Exploring Commands........................|netrw-explore|
@@ -76,7 +77,9 @@ Copyright: Copyright (C) 1999-2014 Charles E Campbell *netrw-copyright*
Unmarking Files.....................................|netrw-mF|
Marking Files By QuickFix List......................|netrw-qF|
Marking Files By Regular Expression.................|netrw-mr|
- Marked Files: Arbitrary Command.....................|netrw-mx|
+ Marked Files: Arbitrary Shell Command...............|netrw-mx|
+ Marked Files: Arbitrary Shell Command, En Bloc......|netrw-mX|
+ Marked Files: Arbitrary Vim Command.................|netrw-mv|
Marked Files: Compression And Decompression.........|netrw-mz|
Marked Files: Copying...............................|netrw-mc|
Marked Files: Diff..................................|netrw-md|
@@ -207,6 +210,7 @@ EXTERNAL APPLICATIONS AND PROTOCOLS *netrw-externapp* {{{2
rsync: *g:netrw_rsync_cmd* = "rsync -a"
scp: *g:netrw_scp_cmd* = "scp -q"
sftp: *g:netrw_sftp_cmd* = "sftp"
+ file: *g:netrw_file_cmd* = "elinks" or "links"
*g:netrw_http_xcmd* : the option string for http://... protocols are
specified via this variable and may be independently overridden. By
@@ -344,7 +348,7 @@ settings are described below, in |netrw-browser-options|, and in
per-buffer basis (supports plain :Nw )
*g:netrw_bufsettings* the settings that netrw buffers have
- (default) noma nomod nonu nowrap ro nobl
+ (default) noma nomod nonu nowrap ro nobl
*g:netrw_chgwin* specifies a window number where subsequent file edits
will take place. (also see |netrw-C|)
@@ -397,18 +401,26 @@ settings are described below, in |netrw-browser-options|, and in
be available (see |netrw-gx|)
*g:netrw_uid* (ftp) user-id, retained on a per-vim-session basis
- *s:netrw_passwd* (ftp) password, retained on a per-vim-session basis
+ *s:netrw_passwd* (ftp) password, retained on a per-vim-session basis
*g:netrw_preview* =0 (default) preview window shown in a horizontally
split window
=1 preview window shown in a vertically split window.
Also affects the "previous window" (see |netrw-P|)
in the same way.
+ The |g:netrw_alto| variable may be used to provide
+ additional splitting control:
+ g:netrw_preview g:netrw_alto result
+ 0 0 |:aboveleft|
+ 0 1 |:belowright|
+ 1 0 |:topleft|
+ 1 1 |:botright|
+ To control sizing, see |g:netrw_winsize|
*g:netrw_scpport* = "-P" : option to use to set port for scp
*g:netrw_sshport* = "-p" : option to use to set port for ssh
- *g:netrw_sepchr* =\0xff
+ *g:netrw_sepchr* =\0xff
=\0x01 for enc == euc-jp (and perhaps it should be for
others, too, please let me know)
Separates priority codes from filenames internally.
@@ -428,7 +440,7 @@ settings are described below, in |netrw-browser-options|, and in
lines that o/s's ftp "provides" on transfers
=0 force normal ftp behavior (no trailing line removal)
- *g:netrw_cygwin* =1 assume scp under windows is from cygwin. Also
+ *g:netrw_cygwin* =1 assume scp under windows is from cygwin. Also
permits network browsing to use ls with time and
size sorting (default if windows)
=0 assume Windows' scp accepts windows-style paths
@@ -679,6 +691,16 @@ your <.vimrc> customization file: >
filetype plugin indent on
endif
<
+By also including the following lines in your .vimrc, one may have netrw
+immediately activate when using [g]vim without any filenames, showing the
+current directory: >
+
+ " Augroup VimStartup:
+ augroup VimStartup
+ au!
+ au VimEnter * if expand("%") == "" | e . | endif
+ augroup END
+<
==============================================================================
6. Transparent Remote File Editing *netrw-transparent* {{{1
@@ -836,7 +858,7 @@ variables listed below, and may be modified by the user.
g:netrw_http_cmd var ="fetch -o" if fetch is available
g:netrw_http_cmd var ="wget -O" else if wget is available
g:netrw_http_put_cmd var ="curl -T"
- g:netrw_list_cmd var ="ssh USEPORT HOSTNAME ls -Fa"
+ |g:netrw_list_cmd| var ="ssh USEPORT HOSTNAME ls -Fa"
g:netrw_rcp_cmd var ="rcp"
g:netrw_rsync_cmd var ="rsync -a"
g:netrw_scp_cmd var ="scp -q"
@@ -1040,6 +1062,9 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
< <F1> Causes Netrw to issue help
<cr> Netrw will enter the directory or read the file |netrw-cr|
<del> Netrw will attempt to remove the file/directory |netrw-del|
+ <c-h> Edit file hiding list |netrw-ctrl-h|
+ <c-l> Causes Netrw to refresh the directory listing |netrw-ctrl-l|
+ <c-r> Browse using a gvim server |netrw-ctrl-r|
- Makes Netrw go up one directory |netrw--|
a Toggles between normal display, |netrw-a|
hiding (suppress display of files matching g:netrw_list_hide)
@@ -1049,23 +1074,27 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
d Make a directory |netrw-d|
D Attempt to remove the file(s)/directory(ies) |netrw-D|
gb Go to previous bookmarked directory |netrw-gb|
+ gd Force treatment as directory |netrw-gd|
+ gf Force treatment as file |netrw-gf|
gh Quick hide/unhide of dot-files |netrw-gh|
- <c-h> Edit file hiding list |netrw-ctrl-h|
i Cycle between thin, long, wide, and tree listings |netrw-i|
- <c-l> Causes Netrw to refresh the directory listing |netrw-ctrl-l|
mb Bookmark current directory |netrw-mb|
mc Copy marked files to marked-file target directory |netrw-mc|
md Apply diff to marked files (up to 3) |netrw-md|
me Place marked files on arg list and edit them |netrw-me|
mf Mark a file |netrw-mf|
+ mF Unmark files |netrw-mF|
+ mg Apply vimgrep to marked files |netrw-mg|
mh Toggle marked file suffices' presence on hiding list |netrw-mh|
mm Move marked files to marked-file target directory |netrw-mm|
mp Print marked files |netrw-mp|
- mr Mark files satisfying a shell-style |regexp| |netrw-mr|
+ mr Mark files using a shell-style |regexp| |netrw-mr|
mt Current browsing directory becomes markfile target |netrw-mt|
mT Apply ctags to marked files |netrw-mT|
mu Unmark all marked files |netrw-mu|
+ mv Apply arbitrary vim command to marked files |netrw-mv|
mx Apply arbitrary shell command to marked files |netrw-mx|
+ mX Apply arbitrary shell command to marked files en bloc|netrw-mX|
mz Compress/decompress marked files |netrw-mz|
o Enter the file/directory under the cursor in a new |netrw-o|
browser window. A horizontal split is used.
@@ -1074,6 +1103,7 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
P Browse in the previously used window |netrw-P|
qb List bookmarked directories and history |netrw-qb|
qf Display information on file |netrw-qf|
+ qF Mark files using a quickfix list |netrw-qF|
r Reverse sorting order |netrw-r|
R Rename the designed file(s)/directory(ies) |netrw-R|
s Select sorting style: by name, time, or file size |netrw-s|
@@ -1097,7 +1127,7 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
mouse
<2-leftmouse> (gvim only) when:
* in a netrw-selected file, AND
- * |g:netrw_retmap| == 1 AND
+ * |g:netrw_retmap| == 1 AND
* the user doesn't already have a <2-leftmouse>
mapping defined before netrw is autoloaded,
then a double clicked leftmouse button will return
@@ -1115,7 +1145,7 @@ QUICK REFERENCE: COMMANDS *netrw-explore-cmds* *netrw-browse-cmds* {{{2
:Ntree....................................................|netrw-ntree|
:Explore[!] [dir] Explore directory of current file......|netrw-explore|
:Hexplore[!] [dir] Horizontal Split & Explore.............|netrw-explore|
- :Lexplore [dir] Left Explorer Toggle...................|netrw-explore|
+ :Lexplore[!] [dir] Left Explorer Toggle...................|netrw-explore|
:Nexplore[!] [dir] Vertical Split & Explore...............|netrw-explore|
:Pexplore[!] [dir] Vertical Split & Explore...............|netrw-explore|
:Rexplore Return to Explorer.....................|netrw-explore|
@@ -1123,19 +1153,45 @@ QUICK REFERENCE: COMMANDS *netrw-explore-cmds* *netrw-browse-cmds* {{{2
:Texplore[!] [dir] Tab & Explore..........................|netrw-explore|
:Vexplore[!] [dir] Vertical Split & Explore...............|netrw-explore|
-BOOKMARKING A DIRECTORY *netrw-mb* *netrw-bookmark* *netrw-bookmarks* {{{2
+BOOKMARKING A DIRECTORY *netrw-mb* *netrw-bookmark* *netrw-bookmarks* {{{2
-One may easily "bookmark" a directory by using >
+One may easily "bookmark" the currently browsed directory by using >
mb
<
+ *.netrwbook*
Bookmarks are retained in between sessions in a $HOME/.netrwbook file, and are
kept in sorted order.
+If there are marked files and/or directories, mb will add them to the bookmark
+list.
+
+*netrw-:NetrwMB*
+Addtionally, one may use :NetrwMB to bookmark files or directories. >
+
+ :NetrwMB[!] [files/directories]
+
+< No bang: enters files/directories into Netrw's bookmark system
+
+ No argument and in netrw buffer:
+ if there are marked files: bookmark marked files
+ otherwise : bookmark file/directory under cursor
+ No argument and not in netrw buffer: bookmarks current open file
+ Has arguments: globs them individually and bookmarks them
+
+ With bang: deletes files/directories from Netrw's bookmark system
+
+The :NetrwMB command is available outside of netrw buffers (once netrw has been
+invoked in the session).
+
+The file ".netrwbook" holds bookmarks when netrw (and vim) is not active. By
+default, its stored on the first directory on the user's |'runtimepath'|.
+
Related Topics:
|netrw-gb| how to return (go) to a bookmark
|netrw-mB| how to delete bookmarks
|netrw-qb| how to list bookmarks
+ |g:netrw_home| controls where .netrwbook is kept
BROWSING *netrw-cr* {{{2
@@ -1161,7 +1217,7 @@ horizontally or vertically, respectively. When the option is set to three, a
When using the gui (gvim), one may select a file by pressing the <leftmouse>
button. In addition, if
- *|g:netrw_retmap| == 1 AND (its default value is 0)
+ * |g:netrw_retmap| == 1 AND (its default value is 0)
* in a netrw-selected file, AND
* the user doesn't already have a <2-leftmouse> mapping defined before
netrw is loaded
@@ -1179,19 +1235,20 @@ The price for such re-use is that when changes are made (such as new files
are introduced into a directory), the listing may become out-of-date. One may
always refresh directory listing buffers by pressing ctrl-L (see
|netrw-ctrl-l|).
-
*:netrw-s-cr*
Squeezing the Current Tree-Listing Directory~
When the tree listing style is enabled (see |netrw-i|) and one is using
gvim, then the <s-cr> mapping may be used to squeeze (close) the
directory currently containing the cursor.
-
-Related topics: |netrw-o| |netrw-p| |netrw-P| |netrw-t| |netrw-v|
-Associated setting variables: |g:netrw_browse_split| |g:netrw_fastbrowse|
- |g:netrw_ftp_list_cmd| |g:netrw_ftp_sizelist_cmd|
- |g:netrw_ftp_timelist_cmd| |g:netrw_ssh_cmd|
- |g:netrw_ssh_browse_reject| |g:netrw_use_noswf|
+Related topics:
+ |netrw-ctrl-r| |netrw-o| |netrw-p|
+ |netrw-P| |netrw-t| |netrw-v|
+Associated setting variables:
+ |g:netrw_browse_split| |g:netrw_fastbrowse|
+ |g:netrw_ftp_list_cmd| |g:netrw_ftp_sizelist_cmd|
+ |g:netrw_ftp_timelist_cmd| |g:netrw_ssh_browse_reject|
+ |g:netrw_ssh_cmd| |g:netrw_use_noswf|
BROWSING WITH A HORIZONTALLY SPLIT WINDOW *netrw-o* *netrw-horiz* {{{2
@@ -1205,12 +1262,14 @@ cursor at the top.
Associated setting variables: |g:netrw_alto| |g:netrw_winsize|
-Related Actions |netrw-cr| |netrw-p| |netrw-t| |netrw-v|
+Related topics:
+ |netrw-ctrl-r| |netrw-o| |netrw-p|
+ |netrw-P| |netrw-t| |netrw-v|
Associated setting variables:
|g:netrw_alto| control above/below splitting
|g:netrw_winsize| control initial sizing
-BROWSING WITH A NEW TAB *netrw-t*
+BROWSING WITH A NEW TAB *netrw-t* {{{2
Normally one enters a file or directory using the <cr>. The "t" map
allows one to open a new window holding the new directory listing or file in
@@ -1218,7 +1277,9 @@ a new tab.
If you'd like to have the new listing in a background tab, use |gT|.
-Related Actions |netrw-cr| |netrw-o| |netrw-p| |netrw-v|
+Related topics:
+ |netrw-ctrl-r| |netrw-o| |netrw-p|
+ |netrw-P| |netrw-t| |netrw-v|
Associated setting variables:
|g:netrw_winsize| control initial sizing
@@ -1234,13 +1295,30 @@ cursor at the left.
There is only one tree listing buffer; using "v" on a displayed subdirectory
will split the screen, but the same buffer will be shown twice.
-Related Actions: |netrw-cr| |netrw-o| |netrw-t| |netrw-v|
+Related topics:
+ |netrw-ctrl-r| |netrw-o| |netrw-p|
+ |netrw-P| |netrw-t| |netrw-v|
Associated setting variables:
|g:netrw_altv| control right/left splitting
|g:netrw_winsize| control initial sizing
-CHANGE LISTING STYLE (THIN LONG WIDE TREE) *netrw-i* {{{2
+BROWSING USING A GVIM SERVER *netrw-ctrl-r* {{{2
+
+One may keep a browsing gvim separate from the gvim being used to edit.
+Use the <c-r> map on a file (not a directory) in the netrw browser, and it
+will use a gvim server (see |g:netrw_servername|). Subsequent use of <cr>
+(see |netrw-cr|) will re-use that server for editing files.
+
+Related topics:
+ |netrw-ctrl-r| |netrw-o| |netrw-p|
+ |netrw-P| |netrw-t| |netrw-v|
+Associated setting variables:
+ |g:netrw_servername| : sets name of server
+ |g:netrw_browse_split| : controls how <cr> will open files
+
+
+CHANGE LISTING STYLE (THIN LONG WIDE TREE) *netrw-i* {{{2
The "i" map cycles between the thin, long, wide, and tree listing formats.
@@ -1265,7 +1343,7 @@ name.
One may make a preferred listing style your default; see |g:netrw_liststyle|.
As an example, by putting the following line in your .vimrc, >
- let g:netrw_liststyle= 4
+ let g:netrw_liststyle= 3
the tree style will become your default listing style.
One typical way to use the netrw tree display is to: >
@@ -1319,8 +1397,15 @@ The "u" map also accepts counts to go back in the history several slots.
For your convenience, |netrw-qb| lists the history number which can be
re-used in that count.
+ *.netrwhist*
See |g:netrw_dirhistmax| for how to control the quantity of history stack
-slots.
+slots. The file ".netrwhist" holds history when netrw (and vim) is not
+active. By default, its stored on the first directory on the user's
+|'runtimepath'|.
+
+Related Topics:
+ |netrw-U| changing to a successor directory
+ |g:netrw_home| controls where .netrwhist is kept
CHANGING TO A SUCCESSOR DIRECTORY *netrw-U* *netrw-downdir* {{{2
@@ -1335,7 +1420,7 @@ See |g:netrw_dirhistmax| for how to control the quantity of history stack
slots.
-CHANGING TREE TOP *netrw-ntree* *:Ntree*
+CHANGING TREE TOP *netrw-ntree* *:Ntree* {{{2
One may specify a new tree top for tree listings using >
@@ -1346,7 +1431,7 @@ information is elided).
With a "dirname", the specified directory name is used.
-NETRW CLEAN *netrw-clean* *:NetrwClean*
+NETRW CLEAN *netrw-clean* *:NetrwClean* {{{2
With :NetrwClean one may easily remove netrw from one's home directory;
more precisely, from the first directory on your |'runtimepath'|.
@@ -1359,17 +1444,16 @@ that the removal is in fact what you want to do. If netrw doesn't have
permission to remove a file, it will issue an error message.
*netrw-gx*
-CUSTOMIZING BROWSING WITH A USER FUNCTION *netrw-x* *netrw-handler* {{{2
+CUSTOMIZING BROWSING WITH A SPECIAL HANDLER *netrw-x* *netrw-handler* {{{2
(also see |netrw_filehandler|)
Certain files, such as html, gif, jpeg, (word/office) doc, etc, files, are
-best seen with a special handler (ie. a tool provided with your computer).
-Netrw allows one to invoke such special handlers by: >
+best seen with a special handler (ie. a tool provided with your computer's
+operating system). Netrw allows one to invoke such special handlers by: >
* when Exploring, hit the "x" key
* when editing, hit gx with the cursor atop the special filename
-< (not available if the |g:netrw_nogx| variable exists)
-
+< (latter not available if the |g:netrw_nogx| variable exists)
Netrw determines which special handler by the following method:
* if |g:netrw_browsex_viewer| exists, then it will be used to attempt to
@@ -1377,14 +1461,14 @@ Netrw determines which special handler by the following method:
:let g:netrw_browsex_viewer= "kfmclient exec"
< or >
- :let g:netrw_browsex_viewer= "gnome-open"
+ :let g:netrw_browsex_viewer= "xdg-open"
<
- If g:netrw_browsex_viewer == '-', then netrwFileHandler() will be
- invoked first (see |netrw_filehandler|).
+ If g:netrw_browsex_viewer == '-', then netrwFileHandlers#Invoke() will be
+ used instead (see |netrw_filehandler|).
* for Windows 32 or 64, the url and FileProtocolHandler dlls are used.
* for Gnome (with gnome-open): gnome-open is used.
- * for KDE (with kfmclient) : kfmclient is used.
+ * for KDE (with kfmclient) : kfmclient is used
* for Mac OS X : open is used.
* otherwise the netrwFileHandler plugin is used.
@@ -1393,17 +1477,31 @@ appropriate application to use to "handle" these files. Such things as
OpenOffice (*.sfx), visualization (*.jpg, *.gif, etc), and PostScript (*.ps,
*.eps) can be handled.
+The gx mapping extends to all buffers; apply "gx" while atop a word and netrw
+will apply a special handler to it (like "x" works when in a netrw buffer).
+One may also use visual mode (see |visual-start|) to select the text that the
+special handler will use. Normally gx uses expand("<cfile>") to pick up the
+text under the cursor; one may change what |expand()| uses via the
+|g:netrw_gx| variable. Alternatively, one may select the text to be used by
+gx via first making a visual selection (see |visual-block|).
+
+Associated setting variables:
+ |g:netrw_gx| control how gx picks up the text under the cursor
+ |g:netrw_nogx| prevent gx map while editing
+
*netrw_filehandler*
-The "x" map applies a function to a file, based on its extension. Of course,
-the handler function must exist for it to be called!
+When |g:netrw_browsex_viewer| exists and is "-", then netrw will attempt to
+handle the special file with a vim function. The "x" map applies a function
+to a file, based on its extension. Of course, the handler function must exist
+for it to be called!
>
- Ex. mypgm.html x ->
- NFH_html("scp://user@host/some/path/mypgm.html")
-<
-Users may write their own netrw File Handler functions to support more
-suffixes with special handling. See <autoload/netrwFileHandlers.vim> for
-examples on how to make file handler functions. As an example: >
+ Ex. mypgm.html x -> NFH_html("scp://user@host/some/path/mypgm.html")
+
+< Users may write their own netrw File Handler functions to
+ support more suffixes with special handling. See
+ <autoload/netrwFileHandlers.vim> for examples on how to make
+ file handler functions. As an example: >
" NFH_suffix(filename)
fun! NFH_suffix(filename)
@@ -1437,7 +1535,14 @@ DELETING BOOKMARKS *netrw-mB* {{{2
To delete a bookmark, use >
{cnt}mB
-<
+
+If there are marked files, then mB will remove them from the
+bookmark list.
+
+Alternatively, one may use :NetrwMB! (see |netrw-:NetrwMB|). >
+
+ :NetrwMB! [files/directories]
+
Related Topics:
|netrw-gb| how to return (go) to a bookmark
|netrw-mb| how to make a bookmark
@@ -1496,11 +1601,11 @@ DIRECTORY EXPLORATION COMMANDS {{{2
:[N]Explore[!] [dir]... Explore directory of current file *:Explore*
:[N]Hexplore[!] [dir]... Horizontal Split & Explore *:Hexplore*
- :Rexplore ... Return to/from Explorer *:Rexplore*
+ :[N]Lexplore[!] [dir]... Left Explorer Toggle *:Lexplore*
:[N]Sexplore[!] [dir]... Split&Explore current file's directory *:Sexplore*
- :Texplore [dir]... Tab & Explore *:Texplore*
:[N]Vexplore[!] [dir]... Vertical Split & Explore *:Vexplore*
- :Lexplore [dir]... Left Explorer Toggle *:Lexplore*
+ :Texplore [dir]... Tab & Explore *:Texplore*
+ :Rexplore ... Return to/from Explorer *:Rexplore*
Used with :Explore **/pattern : (also see |netrw-starstar|)
:Nexplore............. go to next matching file *:Nexplore*
@@ -1509,54 +1614,73 @@ DIRECTORY EXPLORATION COMMANDS {{{2
*netrw-:Explore*
:Explore will open the local-directory browser on the current file's
directory (or on directory [dir] if specified). The window will be
- split only if the file has been modified, otherwise the browsing
- window will take over that window. Normally the splitting is taken
- horizontally.
+ split only if the file has been modified and |'hidden'| is not set,
+ otherwise the browsing window will take over that window. Normally
+ the splitting is taken horizontally.
Also see: |netrw-:Rexplore|
:Explore! is like :Explore, but will use vertical splitting.
- *netrw-:Lexplore*
-:Lexplore [dir] toggles an Explorer window on the left hand side
- of the current tab It will open a netrw window on the current
- directory if [dir] is omitted; a :Lexplore [dir] will show
- the specified directory in the left-hand side browser display
- no matter from which window the command is issued. By default,
- :Lexplore will change an uninitialized |g:netrw_chgwin| to 2;
- edits will thus be preferentially made in window#2.
- Also see: |netrw-C|| |g:netrw_chgwin| |g:netrw_winsize|
- |netrw-p| |netrw-P|| |g:netrw_browse_split|
- *netrw-:Sexplore*
-:Sexplore will always split the window before invoking the local-directory
- browser. As with Explore, the splitting is normally done
- horizontally.
-:Sexplore! [dir] is like :Sexplore, but the splitting will be done vertically.
+
*netrw-:Hexplore*
:Hexplore [dir] does an :Explore with |:belowright| horizontal splitting.
:Hexplore! [dir] does an :Explore with |:aboveleft| horizontal splitting.
- *netrw-:Vexplore*
-:Vexplore [dir] does an :Explore with |:leftabove| vertical splitting.
-:Vexplore! [dir] does an :Explore with |:rightbelow| vertical splitting.
+
+ *netrw-:Lexplore*
+:[N]Lexplore [dir] toggles a full height Explorer window on the left hand side
+ of the current tab. It will open a netrw window on the current
+ directory if [dir] is omitted; a :Lexplore [dir] will show the
+ specified directory in the left-hand side browser display no matter
+ from which window the command is issued. By default, :Lexplore will
+ change an uninitialized |g:netrw_chgwin| to 2; edits will thus
+ preferentially be made in window#2.
+ The [N] specifies a |g:netrw_winsize| just for the new :Lexplore
+ window.
+ Those who like this method often also like tree style displays;
+ see |g:netrw_liststyle|.
+
+ Also see: |netrw-C| |g:netrw_chgwin| |g:netrw_winsize|
+ |netrw-p| |netrw-P| |g:netrw_browse_split|
+
+:[N]Lexplore! is like :Lexplore, except that the full-height Explorer window
+ will open on the right hand side, and an uninitialized |g:netrw_chgwin|
+ will be set to 1.
+
+ *netrw-:Sexplore*
+:[N]Sexplore will always split the window before invoking the local-directory
+ browser. As with Explore, the splitting is normally done
+ horizontally.
+:[N]Sexplore! [dir] is like :Sexplore, but the splitting will be done vertically.
+
*netrw-:Texplore*
:Texplore [dir] does a |:tabnew| before generating the browser window
-By default, these commands use the current file's directory. However, one may
-explicitly provide a directory (path) to use.
+ *netrw-:Vexplore*
+:[N]Vexplore [dir] does an :Explore with |:leftabove| vertical splitting.
+:[N]Vexplore! [dir] does an :Explore with |:rightbelow| vertical splitting.
-The [N] will override |g:netrw_winsize| to specify the quantity of rows and/or
-columns the new explorer window should have.
+The optional parameters are:
-Otherwise, the |g:netrw_winsize| variable, if it has been specified by the
-user, is used to control the quantity of rows and/or columns new explorer
-windows should have.
+ [N]: This parameter will override |g:netrw_winsize| to specify the quantity of
+ rows and/or columns the new explorer window should have.
+ Otherwise, the |g:netrw_winsize| variable, if it has been specified by the
+ user, is used to control the quantity of rows and/or columns new
+ explorer windows should have.
+ [dir]: By default, these explorer commands use the current file's directory.
+ However, one may explicitly provide a directory (path) to use instead;
+ ie. >
+
+ :Explore /some/path
+<
*netrw-:Rexplore*
-:Rexplore This command is a little different from the others as it doesn't
- necessarily open an Explorer window.
+:Rexplore This command is a little different from the other Explore commands
+ as it doesn't necessarily open an Explorer window.
Return to Explorer~
- When one edits a file, for example by pressing <cr> when the
- cursor is atop a file in a netrw browser window, :Rexplore will
- return the display to that of the last netrw browser display
- in that window.
+ When one edits a file using netrw which can occur, for example,
+ when pressing <cr> while the cursor is atop a filename in a netrw
+ browser window, a :Rexplore issued while editing that file will
+ return the display to that of the last netrw browser display in
+ that window.
Return from Explorer~
Conversely, when one is editing a directory, issuing a :Rexplore
@@ -1569,11 +1693,11 @@ windows should have.
Also see: |g:netrw_alto| |g:netrw_altv| |g:netrw_winsize|
-*netrw-star* *netrw-starpat* *netrw-starstar* *netrw-starstarpat*
-EXPLORING WITH STARS AND PATTERNS
+*netrw-star* *netrw-starpat* *netrw-starstar* *netrw-starstarpat* *netrw-grep*
+EXPLORING WITH STARS AND PATTERNS {{{2
When Explore, Sexplore, Hexplore, or Vexplore are used with one of the
-following four styles, Explore generates a list of files which satisfy
+following four patterns Explore generates a list of files which satisfy
the request. >
*/filepat files in current directory which satisfy filepat
@@ -1595,7 +1719,7 @@ will clear the explore list.
If your console or gui produces recognizable shift-up or shift-down sequences,
then you'll likely find using shift-downarrow and shift-uparrow convenient.
-They're mapped by netrw:
+They're mapped by netrw as follows:
<s-down> == Nexplore, and
<s-up> == Pexplore.
@@ -1676,7 +1800,7 @@ Related topics: |netrw-s| |netrw-S|
Associated setting variables: |g:netrw_sort_sequence| |g:netrw_sort_options|
-EXECUTING FILE UNDER CURSOR VIA SYSTEM() *netrw-X*
+EXECUTING FILE UNDER CURSOR VIA SYSTEM() *netrw-X* {{{2
Pressing X while the cursor is atop an executable file will yield a prompt
using the filename asking for any arguments. Upon pressing a [return], netrw
@@ -1767,18 +1891,19 @@ Associated topics: |netrw-a| |netrw-ctrl-h| |netrw-mh|
Netrw provides a helper function 'netrw_gitignore#Hide()' that, when used with
|g:netrw_list_hide| automatically hides all git-ignored files.
-'netrw_gitignore#Hide' searches for patterns in the following files:
+'netrw_gitignore#Hide' searches for patterns in the following files: >
+
'./.gitignore'
'./.git/info/exclude'
global gitignore file: `git config --global core.excludesfile`
system gitignore file: `git config --system core.excludesfile`
-
+<
Files that do not exist, are ignored.
Git-ignore patterns are taken from existing files, and converted to patterns for
hiding files. For example, if you had '*.log' in your '.gitignore' file, it
would be converted to '.*\.log'.
-To use this function, simply assign it's output to |g:netrw_list_hide| option.
+To use this function, simply assign its output to |g:netrw_list_hide| option. >
Example: let g:netrw_list_hide= netrw_gitignore#Hide()
Git-ignored files are hidden in Netrw.
@@ -1788,7 +1913,7 @@ To use this function, simply assign it's output to |g:netrw_list_hide| option.
Example: g:netrw_list_hide= netrw_gitignore#Hide() . '.*\.swp$'
Combining 'netrw_gitignore#Hide' with custom patterns.
-
+<
IMPROVING BROWSING *netrw-listhack* *netrw-ssh-hack* {{{2
@@ -1924,7 +2049,7 @@ Associated setting variable: |g:netrw_keepdir|
MARKING FILES *netrw-:MF* *netrw-mf* {{{2
(also see |netrw-mr|)
-Netrw has several ways of marking files:
+Netrw provides several ways to mark files:
* One may mark files with the cursor atop a filename and
then pressing "mf".
@@ -1947,6 +2072,8 @@ The following netrw maps make use of marked files:
|netrw-a| Hide marked files/directories
|netrw-D| Delete marked files/directories
+ |netrw-mb| Append marked files to bookmarks
+ |netrw-mB| Delete marked files from bookmarks
|netrw-mc| Copy marked files to target
|netrw-md| Apply vimdiff to marked files
|netrw-me| Edit marked files
@@ -1956,7 +2083,9 @@ The following netrw maps make use of marked files:
|netrw-mp| Print marked files
|netrw-mt| Set target for |netrw-mm| and |netrw-mc|
|netrw-mT| Generate tags using marked files
+ |netrw-mv| Apply vim command to marked files
|netrw-mx| Apply shell command to marked files
+ |netrw-mX| Apply shell command to marked files, en bloc
|netrw-mz| Compress/Decompress marked files
|netrw-O| Obtain marked files
|netrw-R| Rename marked files
@@ -1980,13 +2109,13 @@ If the mouse is enabled and works with your vim, you may use <s-leftmouse> to
mark one or more files. You may mark multiple files by dragging the shifted
leftmouse. (see |netrw-mouse|)
-*markfilelist* *global_markfilelist* *local_markfilelist*
+ *markfilelist* *global_markfilelist* *local_markfilelist*
All marked files are entered onto the global marked file list; there is only
-one such list. In addition, every netrw buffer also has its own local marked
-file list; since netrw buffers are associated with specific directories, this
-means that each directory has its own local marked file list. The various
-commands which operate on marked files use one or the other of the marked file
-lists.
+one such list. In addition, every netrw buffer also has its own buffer-local
+marked file list; since netrw buffers are associated with specific
+directories, this means that each directory has its own local marked file
+list. The various commands which operate on marked files use one or the other
+of the marked file lists.
Known Problem: if one is using tree mode (|g:netrw_liststyle|) and several
directories have files with the same name, then marking such a file will
@@ -2002,7 +2131,7 @@ This command will unmark all files in the current buffer. One may also use
mf (|netrw-mf|) on a specific file to unmark just that file.
-MARKING FILES BY QUICKFIX LIST *netrw-qF*
+MARKING FILES BY QUICKFIX LIST *netrw-qF* {{{2
(also see |netrw-mf|)
One may convert the |quickfix-error-lists| into a marked file list using
@@ -2022,7 +2151,20 @@ future I may make it possible to use |regexp|s instead of glob()-style
expressions (yet-another-option).
-MARKED FILES: ARBITRARY COMMAND *netrw-mx* {{{2
+MARKED FILES, ARBITRARY VIM COMMAND *netrw-mv* {{{2
+ (See |netrw-mf| and |netrw-mr| for how to mark files)
+ (uses the local marked-file list)
+
+The "mv" map causes netrw execute an arbitrary vim command on each file
+on the local marked file list, individually:
+
+ * 1split
+ * sil! keepalt e file
+ * run vim command
+ * sil! keepalt wq!
+
+
+MARKED FILES, ARBITRARY SHELL COMMAND *netrw-mx* {{{2
(See |netrw-mf| and |netrw-mr| for how to mark files)
(uses the local marked-file list)
@@ -2031,6 +2173,37 @@ command to be applied to all marked files. All "%"s in the command will be
substituted with the name of each marked file in turn. If no "%"s are in the
command, then the command will be followed by a space and a marked filename.
+Example:
+ (mark files)
+ mx
+ Enter command: cat
+
+ The result is a series of shell commands:
+ cat 'file1'
+ cat 'file2'
+ ...
+
+
+MARKED FILES, ARBITRARY SHELL COMMAND, EN BLOC *netrw-mX* {{{2
+ (See |netrw-mf| and |netrw-mr| for how to mark files)
+ (uses the global marked-file list)
+
+Upon activation of the 'mX' map, netrw will query the user for some (external)
+command to be applied to all marked files on the global marked file list. The
+"en bloc" means that one command will be executed on all the files at once: >
+
+ command files
+
+It is useful, for example, to select files and make a tarball:
+
+ (mark files)
+ mX
+ Enter command: tar cf mynewtarball.tar
+
+The command that will be run in this example:
+
+ tar cf mynewtarball.tar 'file1' 'file2' ...
+
MARKED FILES: COMPRESSION AND DECOMPRESSION *netrw-mz* {{{2
(See |netrw-mf| and |netrw-mr| for how to mark files)
@@ -2044,6 +2217,10 @@ that's "gzip".
For decompression, netrw provides a |Dictionary| of suffices and their
associated decompressing utilities; see |g:netrw_decompress|.
+Remember that one can mark multiple files by regular expression
+(see |netrw-mr|); this is particularly useful to facilitate compressing and
+decompressing a large number of files.
+
Associated setting variables: |g:netrw_compress| |g:netrw_decompress|
MARKED FILES: COPYING *netrw-mc* {{{2
@@ -2054,7 +2231,16 @@ Select a target directory with mt (|netrw-mt|). Then change directory,
select file(s) (see |netrw-mf|), and press "mc". The copy is done
from the current window (where one does the mf) to the target.
-Associated setting variable: |g:netrw_localcopycmd| |g:netrw_ssh_cmd|
+If one does not have a target directory set with |netrw-mt|, then netrw
+will query you for a directory to copy to.
+
+One may also copy directories and their contents (local only) to a target
+directory.
+
+Associated setting variables:
+ |g:netrw_localcopycmd|
+ |g:netrw_localcopydircmd|
+ |g:netrw_ssh_cmd|
MARKED FILES: DIFF *netrw-md* {{{2
(See |netrw-mf| and |netrw-mr| for how to mark files)
@@ -2088,7 +2274,7 @@ Thus, one may use >
mr ...file-pattern
mg ..contents-pattern
to have a marked file list satisfying the file-pattern but containing the
-desried contents-pattern.
+desired contents-pattern.
MARKED FILES: HIDING AND UNHIDING BY SUFFIX *netrw-mh* {{{2
(See |netrw-mf| and |netrw-mr| for how to mark files)
@@ -2146,7 +2332,7 @@ Set the marked file copy/move-to target (see |netrw-mc| and |netrw-mm|):
* Also, if the cursor is in the banner, then the netrw window's currently
displayed directory is used for the copy/move-to target.
Unless the target already is the current directory. In which case,
- remove the target.
+ typing "mf" clears the target.
* However, if the cursor is atop a directory name, then that directory is
used for the copy/move-to target
@@ -2155,9 +2341,9 @@ Set the marked file copy/move-to target (see |netrw-mc| and |netrw-mm|):
This command uses |<q-args>|, so spaces in the directory name are
permitted without escaping.
-There is only one copy/move-to target per vim session; ie. the target is a
-script variable (see |s:var|) and is shared between all netrw windows (in an
-instance of vim).
+There is only one copy/move-to target at a time in a vim session; ie. the
+target is a script variable (see |s:var|) and is shared between all netrw
+windows (in an instance of vim).
When using menus and gvim, netrw provides a "Targets" entry which allows one
to pick a target from the list of bookmarks and history.
@@ -2194,7 +2380,7 @@ edit the desired file and go to the tag.
Associated setting variables: |g:netrw_ctags| |g:netrw_ssh_cmd|
-MARKED FILES: TARGET DIRECTORY USING BOOKMARKS *netrw-Tb* {{{2
+MARKED FILES: TARGET DIRECTORY USING BOOKMARKS *netrw-Tb* {{{2
Sets the marked file copy/move-to target.
@@ -2212,7 +2398,7 @@ Related topics:
Moving files to target.............................|netrw-mm|
-MARKED FILES: TARGET DIRECTORY USING HISTORY *netrw-Th* {{{2
+MARKED FILES: TARGET DIRECTORY USING HISTORY *netrw-Th* {{{2
Sets the marked file copy/move-to target.
@@ -2267,15 +2453,13 @@ your browsing preferences. (see also: |netrw-settings|)
*g:netrw_banner* enable/suppress the banner
=0: suppress the banner
=1: banner is enabled (default)
- NOTE: suppressing the banner is a new feature
- which may cause problems.
*g:netrw_bannerbackslash* if this variable exists and is not zero, the
banner will be displayed with backslashes
rather than forward slashes.
*g:netrw_browse_split* when browsing, <cr> will open the file by:
- =0: re-using the same window
+ =0: re-using the same window (default)
=1: horizontally splitting the window first
=2: vertically splitting the window first
=3: open file in new tab
@@ -2283,10 +2467,19 @@ your browsing preferences. (see also: |netrw-settings|)
Note that |g:netrw_preview| may be used
to get vertical splitting instead of
horizontal splitting.
+ =[servername,tab-number,window-number]
+ Given a |List| such as this, a remote server
+ named by the "servername" will be used for
+ editing. It will also use the specified tab
+ and window numbers to perform editing
+ (see |clientserver|, |netrw-ctrl-r|)
+ This option does not affect |:Lexplore|
+ windows.
Related topics:
- |netrw-cr| |netrw-C|
- |g:netrw_alto| |g:netrw_altv|
+ |g:netrw_alto| |g:netrw_altv|
+ |netrw-C| |netrw-cr|
+ |netrw-ctrl-r|
*g:netrw_browsex_viewer* specify user's preference for a viewer: >
"kfmclient exec"
@@ -2319,7 +2512,7 @@ your browsing preferences. (see also: |netrw-settings|)
tags
*g:netrw_cursor* = 2 (default)
- This option controls the use of the
+ This option controls the use of the
|'cursorline'| (cul) and |'cursorcolumn'|
(cuc) settings by netrw:
@@ -2383,7 +2576,7 @@ your browsing preferences. (see also: |netrw-settings|)
versus speed.
*g:netrw_ffkeep* (default: doesn't exist)
- If this variable exists and is zero, then
+ If this variable exists and is zero, then
netrw will not do a save and restore for
|'fileformat'|.
@@ -2426,6 +2619,11 @@ your browsing preferences. (see also: |netrw-settings|)
These characters in directory names are
escaped before applying glob()
+ *g:netrw_gx* ="<cfile>"
+ This option controls how gx (|netrw-gx|) picks
+ up the text under the cursor. See |expand()|
+ for possibilities.
+
*g:netrw_hide* Controlled by the "a" map (see |netrw-a|)
=0 : show all
=1 : show not-hidden files
@@ -2445,10 +2643,22 @@ your browsing preferences. (see also: |netrw-settings|)
The current browsing directory is contained in
b:netrw_curdir (also see |netrw-c|)
+ *g:netrw_keepj* ="keepj" (default) netrw attempts to keep the
+ |:jumps| table unaffected.
+ ="" netrw will not use |:keepjumps| with
+ exceptions only for the
+ saving/restoration of position.
+
*g:netrw_list_cmd* command for listing remote directories
default: (if ssh is executable)
"ssh HOSTNAME ls -FLa"
+ *g:netrw_list_cmd_options* If this variable exists, then its contents are
+ appended to the g:netrw_list_cmd. For
+ example, use "2>/dev/null" to get rid of banner
+ messages on unix systems.
+
+
*g:netrw_liststyle* Set the default listing style:
= 0: thin listing (one file per line)
= 1: long listing (one file per line with time
@@ -2466,7 +2676,7 @@ your browsing preferences. (see also: |netrw-settings|)
Examples:
let g:netrw_list_hide= '.*\.swp$'
- let g:netrw_list_hide= netrw_gitignore#Hide().'.*\.swp$'
+ let g:netrw_list_hide= netrw_gitignore#Hide().'.*\.swp$'
default: ""
*g:netrw_localcopycmd* ="cp" Linux/Unix/MacOS/Cygwin
@@ -2474,6 +2684,11 @@ your browsing preferences. (see also: |netrw-settings|)
Copies marked files (|netrw-mf|) to target
directory (|netrw-mt|, |netrw-mc|)
+ *g:netrw_localcopydircmd* ="cp -R" Linux/Unix/MacOS/Cygwin
+ ="xcopy /e /c /h/ /i /k" Windows
+ Copies directories to target directory.
+ (|netrw-mc|, |netrw-mt|)
+
*g:netrw_localmkdir* command for making a local directory
default: "mkdir"
@@ -2497,7 +2712,7 @@ your browsing preferences. (see also: |netrw-settings|)
default: "ssh USEPORT HOSTNAME mkdir"
*g:netrw_mousemaps* =1 (default) enables mouse buttons while
- browsing to:
+ browsing to:
leftmouse : open file/directory
shift-leftmouse : mark file
middlemouse : same as P
@@ -2541,6 +2756,10 @@ your browsing preferences. (see also: |netrw-settings|)
*g:netrw_rmf_cmd* command for removing remote softlinks
default: "ssh USEPORT HOSTNAME rm -f"
+ *g:netrw_servername* use this variable to provide a name for
+ |netrw-ctrl-r| to use for its server.
+ default: "NETRWSERVER"
+
*g:netrw_sort_by* sort by "name", "time", or "size"
default: "name"
@@ -2729,7 +2948,7 @@ Related topics:
directory, see |g:netrw_keepdir|.
*netrw-createfile*
-OPEN A NEW FILE IN NETRW'S CURRENT DIRECTORY *netrw-%*
+OPEN A NEW FILE IN NETRW'S CURRENT DIRECTORY *netrw-%* {{{2
To open a new file in netrw's current directory, press "%". This map
will query the user for a new filename; an empty file by that name will
@@ -2742,9 +2961,9 @@ PREVIEW WINDOW *netrw-p* *netrw-preview* {{{2
One may use a preview window by using the "p" key when the cursor is atop the
desired filename to be previewed. The display will then split to show both
-the browser (where the cursor will remain) and the file (see |:pedit|).
-By default, the split will be taken horizontally; one may use vertical
-splitting if one has set |g:netrw_preview| first.
+the browser (where the cursor will remain) and the file (see |:pedit|). By
+default, the split will be taken horizontally; one may use vertical splitting
+if one has set |g:netrw_preview| first.
An interesting set of netrw settings is: >
@@ -2753,6 +2972,7 @@ An interesting set of netrw settings is: >
let g:netrw_winsize = 30
These will:
+
1. Make vertical splitting the default for previewing files
2. Make the default listing style "tree"
3. When a vertical preview window is opened, the directory listing
@@ -2815,6 +3035,17 @@ If there are marked files: (see |netrw-mf|)
Marked files will be renamed (moved). You will be queried as above in
order to specify where you want the file/directory to be moved.
+ If you answer a renaming query with a "s/frompattern/topattern/", then
+ subsequent files on the marked file list will be renamed by taking each
+ name, applying that substitute, and renaming each file to the result.
+ As an example : >
+
+ mr [query: reply with *.c]
+ R [query: reply with s/^\(.*\)\.c$/\1.cpp/]
+<
+ This example will mark all *.c files and then rename them to *.cpp
+ files.
+
WARNING:~
Note that moving files is a dangerous operation; copies are safer. That's
@@ -2841,17 +3072,24 @@ Related topics: |netrw-r| |netrw-S|
Associated setting variables: |g:netrw_sort_by| |g:netrw_sort_sequence|
-SETTING EDITING WINDOW *netrw-C* {{{2
+SETTING EDITING WINDOW *netrw-C* *netrw-:NetrwC* {{{2
-One may select a netrw window for editing with the "C" mapping, or by setting
-g:netrw_chgwin to the selected window number. Subsequent selection of a file
-to edit (|netrw-cr|) will use that window.
+One may select a netrw window for editing with the "C" mapping, using the
+:NetrwC [win#] command, or by setting g:netrw_chgwin to the selected window
+number. Subsequent selection of a file to edit (|netrw-cr|) will use that
+window.
* C by itself, will select the current window for editing via
|netrw-cr|
* [count]C the count will be used as the window number to be used
for editing via |netrw-cr|.
+
+ * :NetrwC will set |g:netrw_chgwin| to the current window
+
+ * :NetrwC win# will set |g:netrw_chgwin| to the specified window
+ number
+
Using >
let g:netrw_chgwin= -1
will restore the default editing behavior (ie. use the current window).
@@ -3105,7 +3343,7 @@ Associated setting variables: |g:netrw_chgwin|
"let g:netrw_scp_cmd = "d:\\dev\\putty\\PSCP.exe"
<
*netrw-p14*
- P14. I'd would like to speed up writes using Nwrite and scp/ssh
+ P14. I would like to speed up writes using Nwrite and scp/ssh
style connections. How? (Thomer M. Gil)
Try using ssh's ControlMaster and ControlPath (see the ssh_config
@@ -3153,15 +3391,66 @@ Associated setting variables: |g:netrw_chgwin|
directory. Start if from your $HOME or another writable
directory.
+ *netrw-p17*
+ P17. Netrw is closing buffers on its own.
+ What steps will reproduce the problem?
+ 1. :Explore, navigate directories, open a file
+ 2. :Explore, open another file
+ 3. Buffer opened in step 1 will be closed. o
+ What is the expected output? What do you see instead?
+ I expect both buffers to exist, but only the last one does.
+
+ (Lance) Problem is caused by "set autochdir" in .vimrc.
+ (drchip) I am able to duplicate this problem with |'acd'| set.
+ It appears that the buffers are not exactly closed;
+ a ":ls!" will show them (although ":ls" does not).
+
+ *netrw-P18*
+ P18. How to locally edit a file that's only available via
+ another server accessible via ssh?
+ See http://stackoverflow.com/questions/12469645/
+ "Using Vim to Remotely Edit A File on ServerB Only
+ Accessible From ServerA"
+
==============================================================================
11. Debugging Netrw Itself *netrw-debug* {{{1
-The <netrw.vim> script is typically available as something like:
+Step 1: check that the problem you've encountered hasn't already been resolved
+by obtaining a copy of the latest (often developmental) netrw at:
+
+ http://www.drchip.org/astronaut/vim/index.html#NETRW
+
+The <netrw.vim> script is typically installed on systems as something like:
>
/usr/local/share/vim/vim7x/plugin/netrwPlugin.vim
/usr/local/share/vim/vim7x/autoload/netrw.vim
+ (see output of :echo &rtp)
+<
+which is loaded automatically at startup (assuming :set nocp). If you
+installed a new netrw, then it will be located at >
+
+ $HOME/.vim/plugin/netrwPlugin.vim
+ $HOME/.vim/autoload/netrw.vim
<
-which is loaded automatically at startup (assuming :set nocp).
+Step 2: assuming that you've installed the latest version of netrw,
+check that your problem is really due to netrw. Create a file
+called netrw.vimrc with the following contents: >
+
+ set nocp
+ so $HOME/.vim/plugin/netrwPlugin.vim
+<
+Then run netrw as follows: >
+
+ vim -u netrw.vimrc --noplugins [some path here]
+<
+Perform whatever netrw commands you need to, and check that the problem is
+still present. This procedure sidesteps any issues due to personal .vimrc
+settings and other plugins. If the problem does not appear, then you need
+to determine what setting in your .vimrc is causing the conflict with netrw
+or which plugin.
+
+Step 3: If the problem still is present, then get a debugging trace from
+netrw:
1. Get the <Decho.vim> script, available as:
@@ -3190,7 +3479,8 @@ which is loaded automatically at startup (assuming :set nocp).
3. Then bring up vim and attempt to evoke the problem by doing a
transfer or doing some browsing. A set of messages should appear
concerning the steps that <netrw.vim> took in attempting to
- read/write your file over the network in a separate tab.
+ read/write your file over the network in a separate tab or
+ server vim window.
To save the file, use >
@@ -3205,14 +3495,82 @@ which is loaded automatically at startup (assuming :set nocp).
debugging trace is due to which command.
Please send that information to <netrw.vim>'s maintainer along
- with the o/s you're using and vim version you're using
+ with the o/s you're using and the vim version that you're using
(see |:version|) >
NdrOchip at ScampbellPfamily.AbizM - NOSPAM
<
==============================================================================
12. History *netrw-history* {{{1
- v152: Apr 08, 2014 * uses the |'noswapfile'| option (requires
+ v153: May 13, 2014 * added another |g:netrw_ffkeep| usage {{{2
+ May 14, 2014 * changed s:PerformListing() so that it
+ always sets ft=netrw for netrw buffers
+ (ie. even when syntax highlighting is
+ off, not available, etc)
+ May 16, 2014 * introduced the |netrw-ctrl-r| functionality
+ May 17, 2014 * introduced the |netrw-:NetrwMB| functionality
+ * mb and mB (|netrw-mb|, |netrw-mB|) will
+ add/remove marked files from bookmark list
+ May 20, 2014 * (Enno Nagel) reported that :Lex <dirname>
+ wasn't working. Fixed.
+ May 26, 2014 * restored test to prevent leftmouse window
+ resizing from causing refresh.
+ (see s:NetrwLeftmouse())
+ * fixed problem where a refresh caused cursor
+ to go just under the banner instead of
+ staying put
+ May 28, 2014 * (László Bimba) provided a patch for opening
+ the |:Lexplore| window 100% high, optionally
+ on the right, and will work with remote
+ files.
+ May 29, 2014 * implemented :NetrwC (see |netrw-:NetrwC|)
+ Jun 01, 2014 * Removed some "silent"s from commands used
+ to implemented scp://... and pscp://...
+ directory listing. Permits request for
+ password to appear.
+ Jun 05, 2014 * (Enno Nagel) reported that user maps "/"
+ caused problems with "b" and "w", which
+ are mapped (for wide listings only) to
+ skip over files rather than just words.
+ Jun 10, 2014 * |g:netrw_gx| introduced to allow users to
+ override default "<cfile>" with the gx
+ (|netrw-gx|) map
+ Jun 11, 2014 * gx (|netrw-gx|), with |'autowrite'| set,
+ will write modified files. s:NetrwBrowseX()
+ will now save, turn off, and restore the
+ |'autowrite'| setting.
+ Jun 13, 2014 * added visual map for gx use
+ Jun 15, 2014 * (Enno Nagel) reported that with having hls
+ set and wide listing style in use, that the
+ b and w maps caused unwanted highlighting.
+ Jul 05, 2014 * |netrw-mv| and |netrw-mX| commands included
+ Jul 09, 2014 * |g:netrw_keepj| included, allowing optional
+ keepj
+ Jul 09, 2014 * fixing bugs due to previous update
+ Jul 21, 2014 * (Bruno Sutic) provided an updated
+ netrw_gitignore.vim
+ Jul 30, 2014 * (Yavuz Yetim) reported that editing two
+ remote files of the same name caused the
+ second instance to have a "temporary"
+ name. Fixed: now they use the same buffer.
+ Sep 18, 2014 * (Yasuhiro Matsumoto) provided a patch which
+ allows scp and windows local paths to work.
+ Oct 07, 2014 * gx (see |netrw-gx|) when atop a directory,
+ will now do |gf| instead
+ Nov 06, 2014 * For cygwin: cygstart will be available for
+ netrw#BrowseX() to use if its executable.
+ Nov 07, 2014 * Began support for file://... urls. Will use
+ |g:netrw_file_cmd| (typically elinks or links)
+ Dec 02, 2014 * began work on having mc (|netrw-mc|) copy
+ directories. Works for linux machines,
+ cygwin+vim, but not for windows+gvim.
+ Dec 02, 2014 * in tree mode, netrw was not opening
+ directories via symbolic links.
+ Dec 02, 2014 * added resolved link information to
+ thin and tree modes
+ Dec 30, 2014 * (issue#231) |:ls| was not showing
+ remote-file buffers reliably. Fixed.
+ v152: Apr 08, 2014 * uses the |'noswapfile'| option (requires {{{2
vim 7.4 with patch 213)
* (Enno Nagel) turn |'rnu'| off in netrw
buffers.
@@ -3238,7 +3596,7 @@ which is loaded automatically at startup (assuming :set nocp).
chgwin window.
May 09, 2014 * SavePosn was "saving filename under cursor"
from a non-netrw window when using :Rex.
- v151: Jan 22, 2014 * extended :Rexplore to return to buffer
+ v151: Jan 22, 2014 * extended :Rexplore to return to buffer {{{2
prior to Explore or editing a directory
* (Ken Takata) netrw gave error when
clipboard was disabled. Sol'n: Placed
@@ -3285,7 +3643,7 @@ which is loaded automatically at startup (assuming :set nocp).
and it also handles Window's shares
* Fixed |netrw-d| command when applied with ftp
* https: support included for netrw#NetRead()
- v150: Jul 12, 2013 * removed a "keepalt" to allow ":e #" to
+ v150: Jul 12, 2013 * removed a "keepalt" to allow ":e #" to {{{2
return to the netrw directory listing
Jul 13, 2013 * (Jonas Diemer) suggested changing
a <cWORD> to <cfile>.
@@ -3340,7 +3698,7 @@ which is loaded automatically at startup (assuming :set nocp).
style, with a previous window open, that
the wrong directory was being used to open
a file. Fixed. (P21)
- v149: Apr 18, 2013 * in wide listing format, now have maps for
+ v149: Apr 18, 2013 * in wide listing format, now have maps for {{{2
w and b to move to next/previous file
Apr 26, 2013 * one may now copy files in the same
directory; netrw will issue requests for
@@ -3354,133 +3712,9 @@ which is loaded automatically at startup (assuming :set nocp).
requested by Paul Domaskis.
Jul 03, 2013 * Explore now avoids splitting when a buffer
will be hidden.
- v148: Apr 16, 2013 * changed Netrw's Style menu to allow direct
+ v148: Apr 16, 2013 * changed Netrw's Style menu to allow direct {{{2
choice of listing style, hiding style, and
sorting style
- v147: Nov 24, 2012 * (James McCoy) Even with g:netrw_dirhistmax
- at zero, the .vim/ directory would be
- created to support history/bookmarks. I've
- gone over netrw to suppress history and
- bookmarking when g:netrw_dirhistmax is zero.
- For instance, the menus will display
- (disabled) when attempts to use
- bookmarks/history are made.
- Nov 29, 2012 * (Kim Jang-hwan) reported that with
- g:Align_xstrlen set to 3 that the cursor was
- moved (linewise) after invocation. This
- problem also afflicted netrw.
- (see |g:netrw_xstrlen|) Fixed.
- Jan 21, 2013 * (mattn) provided a patch to insert some
- endifs needed with the code implementing
- |netrw-O|.
- Jan 24, 2013 * (John Szakmeister) found that remote file
- editing resulted in filetype options being
- overwritten by NetrwOptionRestore(). I
- moved filetype detect from NetrwGetFile()
- to NetrwOptionRestore.
- Feb 17, 2013 * (Yukhiro Nakadaira) provided a patch
- correcting some syntax errors.
- Feb 28, 2013 * (Ingo Karkat) provided a patch preventing
- receipt of an |E95| when revisiting a
- file://... style url.
- Mar 18, 2013 * (Gary Johnson) pointed out that changing
- cedit to <Esc> caused problems with visincr;
- the cedit setting is now bypassed in netrw too.
- Apr 02, 2013 * (Paul Domaskis) reported an undefined
- variable error (s:didstarstar) was
- occurring. It is now defined at
- initialization.
- * included additional sanity checking for the
- marked file functions.
- * included |netrw-qF| and special "j" option
- handling for |netrw-mg|
- Apr 12, 2013 * |netrw-u| and |netrw-U| now handle counts
- * the former mapping for "T" has been removed;
- in its place are new maps, |netrw-Tb| and |netrw-Th|.
- * the menu now supports a "Targets" entry for
- easier target selection. (see |netrw-mt|)
- * (Paul Domaskis) reported some problems with
- moving/copying files under Windows' gvim
- (ie. not cygwin). Fixed.
- * (Paul Mueller) provided a patch to get
- start and rundll working via |netrw-gx|
- by bypassing the user's |'shellslash'| option.
- v146: Oct 20, 2012 * (David Kotchan) reported that under Windows,
- directories named with unusual characters
- such as "#" or "$" were not being listed
- properly.
- * (Kenny Lee) reported that the buffer list
- was being populated by netrw buffers.
- Netrw will now |:bwipe| netrw buffers
- upon editing a file if g:netrw_fastbrowse
- is zero and its not in tree listing style.
- * fixed a bug with s:NetrwInit() that
- prevented initialization with |Lists| and
- |Dictionaries|.
- * |netrw-mu| now unmarks marked-file lists
- v145: Apr 05, 2012 * moved some command from a g:netrw_local_...
- format to g:netwr_local... format
- * included some NOTE level messages about
- commands that aren't executable
- * |g:netrw_errorlvl| (default: NOTE=0)
- option introduced
- May 18, 2012 * (Ilya Dogolazky) a scenario where a
- |g:netrw_fastbrowse| of zero did not
- have a local directory refreshed fixed.
- Jul 10, 2012 * (Donatas) |netrw-gb| wasn't working due
- to an incorrectly used variable.
- Aug 09, 2012 * (Bart Baker) netrw was doubling
- of entries after a split.
- * (code by Takahiro Yoshihara) implemented
- |g:netrw_dynamic_maxfilenamelen|
- Aug 31, 2012 * (Andrew Wong) netrw refresh overwriting
- the yank buffer.
- v144: Mar 12, 2012 * when |CTRL-W_s| or |CTRL-W_v| are used,
- or their wincmd equivalents, on a netrw
- buffer, the netrw's w: variables were
- not copied over. Fixed.
- Mar 13, 2012 * nbcd_curpos_{bufnr('%')} was commented
- out, and was mistakenly used during
- RestorePosn. Unfortunately, I'm not
- sure why it was commented out, so this
- "fix" may re-introduce an earlier problem.
- Mar 21, 2012 * included s:rexposn internally to make
- :Rex return the cursor to the same pos'n
- upon restoration of netrw buffer
- Mar 27, 2012 * (sjbesse) s:NetrwGetFile() needs to remove
- "/" from the netrw buffer's usual |'isk'|
- in order to allow "filetype detect" to work
- properly for scripts.
- v143: Jun 01, 2011 * |g:netrw_winsize| will accept a negative
- number; the absolute value of it will then
- be used to specify lines/columns instead of
- a percentage.
- Jul 05, 2011 * the "d" map now supports mkdir via ftp
- See |netrw-d| and |g:netrw_remote_mkdir|
- Jul 11, 2011 * Changed Explore!, Sexplore!, and Vexplore
- to use a percentage of |winwidth()| instead
- of a percentage of |winheight()|.
- Jul 11, 2011 * included support for https://... I'm just
- beginning to test this, however.
- Aug 01, 2011 * changed RestoreOptions to also restore
- cursor position in netrw buffers.
- Aug 12, 2011 * added a note about "%" to the balloon
- Aug 30, 2011 * if |g:netrw_nobeval| exists, then balloon
- evaluation is suppressed.
- Aug 31, 2011 * (Benjamin R Haskell) provided a patch that
- implements non-standard port handling for
- files opened via the remote browser.
- Aug 31, 2011 * Fixed a **//pattern Explorer bug
- Sep 15, 2011 * (reported by Francesco Campana) netrw
- now permits the "@" to be part of the
- user id (if there's an @ that appears
- to the right).
- Nov 21, 2011 * New option: |g:netrw_ftp_options|
- Dec 07, 2011 * (James Sinclair) provided a fix handling
- attempts to use a uid and password when
- they weren't defined. This affected
- NetWrite (NetRead already had that fix).
-
==============================================================================
13. Todo *netrw-todo* {{{1
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index c42d98ba90..1b24efc4f9 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -838,6 +838,7 @@ Short explanation of each option: *option-list*
'selection' 'sel' what type of selection to use
'selectmode' 'slm' when to use Select mode instead of Visual mode
'sessionoptions' 'ssop' options for |:mksession|
+'shada' 'sd' use .shada file upon startup and exiting
'shell' 'sh' name of shell to use for external commands
'shellcmdflag' 'shcf' flag to shell to execute one command
'shellpipe' 'sp' string to put output of ":make" in error file
@@ -911,7 +912,6 @@ Short explanation of each option: *option-list*
'verbosefile' 'vfile' file to write messages in
'viewdir' 'vdir' directory where to store files with :mkview
'viewoptions' 'vop' specifies what to save for :mkview
-'viminfo' 'vi' use .viminfo file upon startup and exiting
'virtualedit' 've' when to use virtual editing
'visualbell' 'vb' use visual bell instead of beeping
'warn' warn for shell command when buffer was changed
@@ -1131,7 +1131,7 @@ Context-sensitive completion on the command-line:
|-w| -w {scriptout} write typed chars to file {scriptout} (append)
|-W| -W {scriptout} write typed chars to file {scriptout} (overwrite)
|-u| -u {vimrc} read inits from {vimrc} instead of other inits
-|-i| -i {viminfo} read info from {viminfo} instead of other files
+|-i| -i {shada} read info from {shada} instead of other files
|---| -- end of options, other arguments are file names
|--help| --help show list of arguments and exit
|--version| --version show version info and exit
@@ -1215,12 +1215,12 @@ Context-sensitive completion on the command-line:
------------------------------------------------------------------------------
*Q_ac* Automatic Commands
-|viminfo-file| read registers, marks, history at startup, save when exiting.
+|shada-file| read registers, marks, history at startup, save when exiting.
-|:rviminfo| :rv[iminfo] [file] read info from viminfo file [file]
-|:rviminfo| :rv[iminfo]! [file] idem, overwrite existing info
-|:wviminfo| :wv[iminfo] [file] add info to viminfo file [file]
-|:wviminfo| :wv[iminfo]! [file] write info to viminfo file [file]
+|:rshada| :rsh[ada] [file] read info from ShaDa file [file]
+|:rshada| :rsh[ada]! [file] idem, overwrite existing info
+|:wshada| :wsh[ada] [file] add info to ShaDa file [file]
+|:wshada| :wsh[ada]! [file] write info to ShaDa file [file]
|modeline| Automatic option setting when editing a file
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index 301143b4e9..0952de425b 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -1,4 +1,4 @@
-*repeat.txt* For Vim version 7.4. Last change: 2014 Oct 29
+*repeat.txt* For Vim version 7.4. Last change: 2015 Jan 07
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -222,8 +222,8 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
Mark) in utf-8 format Vim will recognize it, no need
to use ":scriptencoding utf-8" then.
- *:scrip* *:scriptnames*
-:scrip[tnames] List all sourced script names, in the order they were
+ *:scr* *:scriptnames*
+:scr[iptnames] List all sourced script names, in the order they were
first sourced. The number is used for the script ID
|<SID>|.
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index aa37913779..84bd70db62 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -12,7 +12,7 @@ Starting Vim *starting*
4. Suspending |suspend|
5. Saving settings |save-settings|
6. Views and Sessions |views-sessions|
-7. The viminfo file |viminfo-file|
+7. The ShaDa file |shada-file|
==============================================================================
1. Vim arguments *vim-arguments*
@@ -128,7 +128,7 @@ argument.
"pat" in the first file being edited (see |pattern| for the
available search patterns). The search starts at the cursor
position, which can be the first line or the cursor position
- last used from |viminfo|. To force a search from the first
+ last used from |shada|. To force a search from the first
line use "+1 +/pat".
+{command} *-+c* *-c*
@@ -251,7 +251,7 @@ argument.
*-V* *verbose*
-V[N] Verbose. Sets the 'verbose' option to [N] (default: 10).
Messages will be given for each file that is ":source"d and
- for reading or writing a viminfo file. Can be used to find
+ for reading or writing a ShaDa file. Can be used to find
out what is happening upon startup and exit.
Example: >
vim -V8 foobar
@@ -322,10 +322,10 @@ argument.
same effect as "NONE", but loading plugins is not skipped.
*-i*
--i {viminfo} The file "viminfo" is used instead of the default viminfo
- file. If the name "NONE" is used (all uppercase), no viminfo
- file is read or written, even if 'viminfo' is set or when
- ":rv" or ":wv" are used. See also |viminfo-file|.
+-i {shada} The file {shada} is used instead of the default ShaDa
+ file. If the name "NONE" is used (all uppercase), no ShaDa
+ file is read or written, even if 'shada' is set or when
+ ":rsh" or ":wsh" are used. See also |shada-file|.
*-s*
-s {scriptin} The script file "scriptin" is read. The characters in the
@@ -475,9 +475,9 @@ accordingly. Vim proceeds in this order:
Only when starting "gvim", the GUI initializations will be done. See
|gui-init|.
-9. Read the viminfo file
- If the 'viminfo' option is not empty, the viminfo file is read. See
- |viminfo-file|.
+9. Read the ShaDa file
+ If the 'shada' option is not empty, the ShaDa file is read. See
+ |shada-file|.
10. Read the quickfix file
If the "-q" flag was given to Vim, the quickfix file is read. If this
@@ -564,10 +564,10 @@ just like executing a command from a vimrc/exrc in the current directory.
If Vim takes a long time to start up, use the |--startuptime| argument to find
out what happens.
-If you have "viminfo" enabled, the loading of the viminfo file may take a
-while. You can find out if this is the problem by disabling viminfo for a
+If you have 'shada' enabled, the loading of the ShaDa file may take a
+while. You can find out if this is the problem by disabling ShaDa for a
moment (use the Vim argument "-i NONE", |-i|). Try reducing the number of
-lines stored in a register with ":set viminfo='20,<50,s10". |viminfo-file|.
+lines stored in a register with ":set shada='20,<50,s10". |shada-file|.
*:intro*
When Vim starts without a file name, an introductory message is displayed (for
@@ -768,8 +768,8 @@ save a Session and when you restore it later the window layout looks the same.
You can use a Session to quickly switch between different projects,
automatically loading the files you were last working on in that project.
-Views and Sessions are a nice addition to viminfo-files, which are used to
-remember information for all Views and Sessions together |viminfo-file|.
+Views and Sessions are a nice addition to ShaDa files, which are used to
+remember information for all Views and Sessions together |shada-file|.
You can quickly start editing with a previously saved View or Session with the
|-S| argument: >
@@ -865,7 +865,7 @@ The output of ":mkview" contains these items:
Note that Views and Sessions are not perfect:
- They don't restore everything. For example, defined functions, autocommands
and ":syntax on" are not included. Things like register contents and
- command line history are in viminfo, not in Sessions or Views.
+ command line history are in ShaDa, not in Sessions or Views.
- Global option values are only set when they differ from the default value.
When the current value is not the default value, loading a Session will not
set it back to the default value. Local options will be set back to the
@@ -896,15 +896,16 @@ To automatically save and restore views for *.c files: >
au BufWinEnter *.c silent loadview
==============================================================================
-8. The viminfo file *viminfo* *viminfo-file* *E136*
- *E575* *E576* *E577*
+8. The ShaDa file *shada* *shada-file*
+
If you exit Vim and later start it again, you would normally lose a lot of
-information. The viminfo file can be used to remember that information, which
-enables you to continue where you left off.
+information. The ShaDa file can be used to remember that information, which
+enables you to continue where you left off. Its name is the abbreviation of
+SHAred DAta because it is used for sharing data between Neovim sessions.
This is introduced in section |21.3| of the user manual.
-The viminfo file is used to store:
+The ShaDa file is used to store:
- The command line history.
- The search string history.
- The input-line history.
@@ -915,62 +916,59 @@ The viminfo file is used to store:
- The buffer list.
- Global variables.
-The viminfo file is not supported when the |+viminfo| feature has been
-disabled at compile time.
-
-You could also use a Session file. The difference is that the viminfo file
+You could also use a Session file. The difference is that the ShaDa file
does not depend on what you are working on. There normally is only one
-viminfo file. Session files are used to save the state of a specific editing
+ShaDa file. Session files are used to save the state of a specific editing
Session. You could have several Session files, one for each project you are
-working on. Viminfo and Session files together can be used to effectively
+working on. ShaDa and Session files together can be used to effectively
enter Vim and directly start working in your desired setup. |session-file|
- *viminfo-read*
-When Vim is started and the 'viminfo' option is non-empty, the contents of
-the viminfo file are read and the info can be used in the appropriate places.
+ *shada-read*
+When Vim is started and the 'shada' option is non-empty, the contents of
+the ShaDa file are read and the info can be used in the appropriate places.
The |v:oldfiles| variable is filled. The marks are not read in at startup
-(but file marks are). See |initialization| for how to set the 'viminfo'
+(but file marks are). See |initialization| for how to set the 'shada'
option upon startup.
- *viminfo-write*
-When Vim exits and 'viminfo' is non-empty, the info is stored in the viminfo
-file (it's actually merged with the existing one, if one exists). The
-'viminfo' option is a string containing information about what info should be
-stored, and contains limits on how much should be stored (see 'viminfo').
+ *shada-write*
+When Vim exits and 'shada' is non-empty, the info is stored in the ShaDa file
+(it's actually merged with the existing one, if one exists |shada-merging|).
+The 'shada' option is a string containing information about what info should
+be stored, and contains limits on how much should be stored (see 'shada').
Notes for Unix:
-- The file protection for the viminfo file will be set to prevent other users
+- The file protection for the ShaDa file will be set to prevent other users
from being able to read it, because it may contain any text or commands that
you have worked with.
-- If you want to share the viminfo file with other users (e.g. when you "su"
+- If you want to share the ShaDa file with other users (e.g. when you "su"
to another user), you can make the file writable for the group or everybody.
- Vim will preserve this when writing new viminfo files. Be careful, don't
- allow just anybody to read and write your viminfo file!
-- Vim will not overwrite a viminfo file that is not writable by the current
+ Vim will preserve this when writing new ShaDa files. Be careful, don't
+ allow just anybody to read and write your ShaDa file!
+- Vim will not overwrite a ShaDa file that is not writable by the current
"real" user. This helps for when you did "su" to become root, but your
$HOME is still set to a normal user's home directory. Otherwise Vim would
- create a viminfo file owned by root that nobody else can read.
-- The viminfo file cannot be a symbolic link. This is to avoid security
+ create a ShaDa file owned by root that nobody else can read.
+- The ShaDa file cannot be a symbolic link. This is to avoid security
issues.
-Marks are stored for each file separately. When a file is read and 'viminfo'
-is non-empty, the marks for that file are read from the viminfo file. NOTE:
+Marks are stored for each file separately. When a file is read and 'shada'
+is non-empty, the marks for that file are read from the ShaDa file. NOTE:
The marks are only written when exiting Vim, which is fine because marks are
remembered for all the files you have opened in the current editing session,
unless ":bdel" is used. If you want to save the marks for a file that you are
-about to abandon with ":bdel", use ":wv". The '[' and ']' marks are not
+about to abandon with ":bdel", use ":wsh". The '[' and ']' marks are not
stored, but the '"' mark is. The '"' mark is very useful for jumping to the
cursor position when the file was last exited. No marks are saved for files
-that start with any string given with the "r" flag in 'viminfo'. This can be
+that start with any string given with the "r" flag in 'shada'. This can be
used to avoid saving marks for files on removable media (for MS-DOS you would
use "ra:,rb:".
-The |v:oldfiles| variable is filled with the file names that the viminfo file
+The |v:oldfiles| variable is filled with the file names that the ShaDa file
has marks for.
- *viminfo-file-marks*
-Uppercase marks ('A to 'Z) are stored when writing the viminfo file. The
-numbered marks ('0 to '9) are a bit special. When the viminfo file is written
-(when exiting or with the ":wviminfo" command), '0 is set to the current cursor
+ *shada-file-marks*
+Uppercase marks ('A to 'Z) are stored when writing the ShaDa file. The
+numbered marks ('0 to '9) are a bit special. When the ShaDa file is written
+(when exiting or with the |:wshada| command), '0 is set to the current cursor
position and file. The old '0 is moved to '1, '1 to '2, etc. This
resembles what happens with the "1 to "9 delete registers. If the current
cursor position is already present in '0 to '9, it is moved to '0, to avoid
@@ -988,93 +986,196 @@ For a bash-like shell: >
alias lvim='vim -c "normal '\''0"'
-Use the "r" flag in 'viminfo' to specify for which files no marks should be
+Use the "r" flag in 'shada' to specify for which files no marks should be
remembered.
-
-VIMINFO FILE NAME *viminfo-file-name*
-
-- The default name of the viminfo file is "$HOME/.viminfo" for Unix,
- "$HOME\_viminfo" for MS-DOS and Win32. For the last two, when $HOME is not
- set, "$VIM\_viminfo" is used. When $VIM is also not set, "c:\_viminfo" is
- used.
-- The 'n' flag in the 'viminfo' option can be used to specify another viminfo
- file name |'viminfo'|.
+MERGING *shada-merging*
+ {Nvim}
+When writing ShaDa files with |:wshada| without bang or at regular exit
+information in the existing ShaDa file is merged with information from current
+Neovim instance. For this purpose ShaDa files store timestamps associated
+with ShaDa entries. Specifically the following is being done:
+
+1. History lines are merged, ordered by timestamp. Maximum amount of items in
+ ShaDa file is defined by 'shada' option (|shada-/|, |shada-:|, |shada-@|,
+ etc: one suboption for each character that represents history name
+ (|:history|)).
+2. Local marks and changes for files that were not opened by Neovim are copied
+ to new ShaDa file. Marks for files that were opened by Neovim are merged,
+ changes to files opened by Neovim are ignored. |shada-'|
+3. Jump list is merged: jumps are ordered by timestamp, identical jumps
+ (identical position AND timestamp) are squashed.
+4. Search patterns and substitute strings are not merged: search pattern or
+ substitute string which has greatest timestamp will be the only one copied
+ to ShaDa file.
+5. For each register entity with greatest timestamp is the only saved.
+ |shada-<|
+6. All saved variables are saved from current Neovim instance. Additionally
+ existing variable values are copied, meaning that the only way to remove
+ variable from a ShaDa file is either removing it by hand or disabling
+ writing variables completely. |shada-!|
+7. For each global mark entity with greatest timestamp is the only saved.
+8. Buffer list and header are the only entries which are not merged in any
+ fashion: the only header and buffer list present are the ones from the
+ Neovim instance which was last writing the file. |shada-%|
+
+COMPATIBILITY *shada-compatibility*
+ {Nvim}
+ShaDa files are forward and backward compatible. This means that
+
+1. Entries which have unknown type (i.e. that hold unidentified data) are
+ ignored when reading and blindly copied when writing.
+2. Register entries with unknown register name are ignored when reading and
+ blindly copied when writing. Limitation: only registers that use name with
+ code in interval [1, 255] are supported. |registers|
+3. Register entries with unknown register type are ignored when reading and
+ merged as usual when writing. |getregtype()|
+4. Local and global mark entries with unknown mark names are ignored when
+ reading. When writing global mark entries are blindly copied and local mark
+ entries are also blindly copied, but only if file they are attached to fits
+ in the |shada-'| limit. Unknown local mark entry's timestamp is also taken
+ into account when calculating which files exactly should fit into this
+ limit. Limitation: only marks that use name with code in interval [1, 255]
+ are supported. |mark-motions|
+5. History entries with unknown history type are ignored when reading and
+ blindly copied when writing. Limitation: there can be only up to 256
+ history types. |history|
+6. Unknown keys found in register, local mark, global mark, change, jump and
+ search pattern entries are saved internally and dumped when writing.
+ Entries created during Neovim session never have such additions.
+7. Additional elements found in replacement string and history entries are
+ saved internally and dumped. Entries created during Neovim session never
+ have such additions.
+8. Additional elements found in variable entries are simply ignored when
+ reading. When writing new variables they will be preserved during merging,
+ but that's all. Variable values dumped from current Neovim session never
+ have additional elements, even if variables themselves were obtained by
+ reading ShaDa files.
+
+"Blindly" here means that there will be no attempts to somehow merge them,
+even if other entries (with known name/type/etc) are merged. |shada-merging|
+
+SHADA FILE NAME *shada-file-name*
+
+- The default name of the ShaDa file is "$HOME/.nvim/shada/main.shada" for
+ Unix, "$HOME\_nvim\shada\main.shada" for MS-DOS and Win32. For the last
+ two, when $HOME is not set, "$VIM\_nvim\shada\main.shada" is used. When
+ $VIM is also not set, "c:\_nvim\shada\main.shada" is used.
+- The 'n' flag in the 'shada' option can be used to specify another ShaDa
+ file name |'shada'|.
- The "-i" Vim argument can be used to set another file name, |-i|. When the
- file name given is "NONE" (all uppercase), no viminfo file is ever read or
+ file name given is "NONE" (all uppercase), no ShaDa file is ever read or
written. Also not for the commands below!
- For the commands below, another file name can be given, overriding the
- default and the name given with 'viminfo' or "-i" (unless it's NONE).
+ default and the name given with 'shada' or "-i" (unless it's NONE).
-CHARACTER ENCODING *viminfo-encoding*
+CHARACTER ENCODING *shada-encoding*
-The text in the viminfo file is encoded as specified with the 'encoding'
-option. Normally you will always work with the same 'encoding' value, and
-this works just fine. However, if you read the viminfo file with another
-value for 'encoding' than what it was written with, some of the text
-(non-ASCII characters) may be invalid. If this is unacceptable, add the 'c'
-flag to the 'viminfo' option: >
- :set viminfo+=c
-Vim will then attempt to convert the text in the viminfo file from the
-'encoding' value it was written with to the current 'encoding' value. This
-requires Vim to be compiled with the |+iconv| feature. Filenames are not
-converted.
+The text in the ShaDa file is UTF-8-encoded. Normally you will always work
+with the same 'encoding' value, and this works just fine. However, if you
+read the ShaDa file with value for 'encoding' different from utf-8 and
+'encoding' used when writing ShaDa file, some of the text (non-ASCII
+characters) may be invalid as Neovim always attempts to convert the text in
+the ShaDa file from the UTF-8 to the current 'encoding' value. Filenames are
+never converted, affected elements are:
+- history strings;
+- variable values;
+- register values;
+- last used search and substitute patterns;
+- last used substitute replacement string.
-MANUALLY READING AND WRITING *viminfo-read-write*
-Two commands can be used to read and write the viminfo file manually. This
+MANUALLY READING AND WRITING *shada-read-write*
+
+Two commands can be used to read and write the ShaDa file manually. This
can be used to exchange registers between two running Vim programs: First
-type ":wv" in one and then ":rv" in the other. Note that if the register
-already contained something, then ":rv!" would be required. Also note
+type ":wsh" in one and then ":rsh" in the other. Note that if the register
+already contained something, then ":rsh!" would be required. Also note
however that this means everything will be overwritten with information from
the first Vim, including the command line history, etc.
-The viminfo file itself can be edited by hand too, although we suggest you
-start with an existing one to get the format right. It is reasonably
-self-explanatory once you're in there. This can be useful in order to
-create a second file, say "~/.my_viminfo" which could contain certain
-settings that you always want when you first start Vim. For example, you
-can preload registers with particular data, or put certain commands in the
-command line history. A line in your .vimrc file like >
- :rviminfo! ~/.my_viminfo
-can be used to load this information. You could even have different viminfos
-for different types of files (e.g., C code) and load them based on the file
-name, using the ":autocmd" command (see |:autocmd|).
-
- *viminfo-errors*
-When Vim detects an error while reading a viminfo file, it will not overwrite
-that file. If there are more than 10 errors, Vim stops reading the viminfo
-file. This was done to avoid accidentally destroying a file when the file
-name of the viminfo file is wrong. This could happen when accidentally typing
-"vim -i file" when you wanted "vim -R file" (yes, somebody accidentally did
-that!). If you want to overwrite a viminfo file with an error in it, you will
-either have to fix the error, or delete the file (while Vim is running, so
-most of the information will be restored).
-
- *:rv* *:rviminfo* *E195*
-:rv[iminfo][!] [file] Read from viminfo file [file] (default: see above).
+The ShaDa file itself can be edited by hand too, although we suggest you
+start with an existing one to get the format right. You need to understand
+MessagePack (or, more likely, find software that is able to use it) format to
+do this. This can be useful in order to create a second file, say
+"~/.my.shada" which could contain certain settings that you always want when
+you first start Neovim. For example, you can preload registers with
+particular data, or put certain commands in the command line history. A line
+in your .nvimrc file like >
+ :rshada! ~/.my.shada
+can be used to load this information. You could even have different ShaDa
+files for different types of files (e.g., C code) and load them based on the
+file name, using the ":autocmd" command (see |:autocmd|). More information on
+ShaDa file format is contained in |shada-format| section.
+
+ *E136* *E138* *shada-error-handling*
+Some errors make Neovim leave temporary file named `{basename}.tmp.X` (X is
+any free letter from `a` to `z`) while normally it will create this file,
+write to it and then rename `{basename}.tmp.X` to `{basename}`. Such errors
+include:
+
+- Errors which make Neovim think that read file is not a ShaDa file at all:
+ non-ShaDa files are not overwritten for safety reasons to avoid accidentally
+ destroying an unrelated file. This could happen e.g. when typing "nvim -i
+ file" in place of "nvim -R file" (yes, somebody did that at least with Vim).
+ Such errors are listed at |shada-critical-contents-errors|.
+- If writing to the temporary file failed: e.g. because of the insufficient
+ space left.
+- If renaming file failed: e.g. because of insufficient permissions.
+- If target ShaDa file has different from the Neovim instance's owners (user
+ and group) and changing them failed. Unix-specific, applies only when
+ Neovim was launched from root.
+
+Do not forget to remove the temporary file or replace the target file with
+temporary one after getting one of the above errors or all attempts to create
+a ShaDa file may fail with |E138|. If you got one of them when using
+|:wshada| (and not when exiting Neovim: i.e. when you have Neovim session
+running) you have additional options:
+
+- First thing which you should consider if you got any error, except failure
+ to write to the temporary file: remove existing file and replace it with the
+ temporary file. Do it even if you have running Neovim instance.
+- Fix the permissions and/or file ownership, free some space and attempt to
+ write again. Do not remove the existing file.
+- Use |:wshada| with bang. Does not help in case of permission error. If
+ target file was actually the ShaDa file some information may be lost in this
+ case. To make the matters slightly better use |:rshada| prior to writing,
+ but this still will loose buffer-local marks and change list entries for any
+ file which is not opened in the current Neovim instance.
+- Remove the target file from shell and use |:wshada|. Consequences are not
+ different from using |:wshada| with bang, but "rm -f" works in some cases
+ when you don't have write permissions.
+
+ *:rsh* *:rshada* *E886*
+:rsh[ada][!] [file] Read from ShaDa file [file] (default: see above).
If [!] is given, then any information that is
already set (registers, marks, |v:oldfiles|, etc.)
will be overwritten.
- *:wv* *:wviminfo* *E137* *E138* *E574* *E886*
-:wv[iminfo][!] [file] Write to viminfo file [file] (default: see above).
+ *:rv* *:rviminfo*
+:rv[iminfo][!] [file] Deprecated alias to |:rshada| command.
+
+ *:wsh* *:wshada* *E137*
+:wsh[ada][!] [file] Write to ShaDa file [file] (default: see above).
The information in the file is first read in to make
a merge between old and new info. When [!] is used,
the old information is not read first, only the
- internal info is written. If 'viminfo' is empty, marks
- for up to 100 files will be written.
- When you get error "E138: Can't write viminfo file"
- check that no old temp files were left behind (e.g.
- ~/.viminf*) and that you can write in the directory of
- the .viminfo file.
+ internal info is written (also disables safety checks
+ described in |shada-error-handling|). If 'shada' is
+ empty, marks for up to 100 files will be written.
+ When you get error "E138: All .tmp.X files exist,
+ cannot write ShaDa file!" check that no old temp files
+ were left behind (e.g. ~/.nvim/shada/main.shada.tmp*).
+
+ *:wv* *:wviminfo*
+:wv[iminfo][!] [file] Deprecated alias to |:wshada| command.
*:ol* *:oldfiles*
-:ol[dfiles] List the files that have marks stored in the viminfo
+:ol[dfiles] List the files that have marks stored in the ShaDa
file. This list is read on startup and only changes
- afterwards with ":rviminfo!". Also see |v:oldfiles|.
+ afterwards with ":rshada!". Also see |v:oldfiles|.
The number can be used with |c_#<|.
:bro[wse] ol[dfiles][!]
@@ -1085,4 +1186,159 @@ most of the information will be restored).
and still get the prompt to enter a file number.
Use ! to abandon a modified buffer. |abandon|
+SHADA FILE FORMAT *shada-format*
+
+ShaDa files are concats of MessagePack entries. Each entry is a concat of
+exactly four MessagePack objects:
+
+1. First goes type of the entry. Object type must be an unsigned integer.
+ Object type must not be equal to zero.
+2. Second goes entry timestamp. It must also be an unsigned integer.
+3. Third goes the length of the fourth entry. Unsigned integer as well, used
+ for fast skipping without parsing.
+4. Fourth is actual entry data. All currently used ShaDa entries use
+ containers to hold data: either map or array. Exact format depends on the
+ entry type:
+
+ Entry type (name) Entry data ~
+ 1 (Header) Map containing data that describes the generator
+ instance that wrote this ShaDa file. It is ignored
+ when reading ShaDa files. Contains the following data:
+ Key Data ~
+ generator Binary, software used to generate ShaDa
+ file. Is equal to "nvim" when ShaDa file was
+ written by Neovim.
+ version Binary, generator version.
+ encoding Binary, effective 'encoding' value.
+ max_kbyte Integer, effective |shada-s| limit value.
+ pid Integer, instance process ID.
+ * It is allowed to have any number of
+ additional keys with any data.
+ 2 (SearchPattern) Map containing data describing last used search or
+ substitute pattern. Normally ShaDa file contains two
+ such entries: one with "ss" key set to true (describes
+ substitute pattern, see |:substitute|), and one set to
+ false (describes search pattern, see
+ |search-commands|). "su" key should be true on one of
+ the entries. If key value is equal to default then it
+ is normally not present. Keys:
+ Key Type Default Description ~
+ sm Boolean true Effective 'magic' value.
+ sc Boolean false Effective 'smartcase' value.
+ sl Boolean true True if search pattern comes
+ with a line offset. See
+ |search-offset|.
+ se Boolean false True if |search-offset|
+ requested to place cursor at
+ (relative to) the end of the
+ pattern.
+ so Integer 0 Offset value. |search-offset|
+ su Boolean false True if current entry was the
+ last used search pattern.
+ ss Boolean false True if current entry describes
+ |:substitute| pattern.
+ sh Boolean false True if |v:hlsearch| is on.
+ With |shada-h| or 'nohlsearch'
+ this key is always false.
+ sp Binary N/A Actual pattern. Required.
+ * any none Other keys are allowed for
+ compatibility reasons, see
+ |shada-compatibility|.
+ 3 (SubString) Array containing last |:substitute| replacement string.
+ Contains single entry: binary, replacement string used.
+ More entries are allowed for compatibility reasons, see
+ |shada-compatibility|.
+ 4 (HistoryEntry) Array containing one entry from history. Should have
+ two or three entries. First one is history type
+ (unsigned integer), second is history line (binary),
+ third is the separator character (unsigned integer,
+ must be in interval [0, 255]). Third item is only
+ valid for search history. Possible history types are
+ listed in |hist-names|, here are the corresponding
+ numbers: 0 - cmd, 1 - search, 2 - expr, 3 - input,
+ 4 - debug.
+ 5 (Register) Map describing one register (|registers|). If key
+ value is equal to default then it is normally not
+ present. Keys:
+ Key Type Def Description ~
+ rt UInteger 0 Register type:
+ No Description ~
+ 0 |characterwise-register|
+ 1 |linewise-register|
+ 2 |blockwise-register|
+ rw UInteger 0 Register width. Only valid
+ for |blockwise-register|s.
+ rc Array of binary N/A Register contents. Each
+ entry in the array
+ represents its own line.
+ NUL characters inside the
+ line should be represented
+ as NL according to
+ |NL-used-for-Nul|.
+ n UInteger N/A Register name: character
+ code in range [1, 255].
+ Example: |quote0| register
+ has name 48 (ASCII code for
+ zero character).
+ * any none Other keys are allowed
+ for compatibility reasons,
+ see |shada-compatibility|.
+ 6 (Variable) Array containing two items: variable name (binary) and
+ variable value (any object). Values are converted
+ using the same code |msgpackparse()| uses when reading,
+ |msgpackdump()| when writing, so there may appear
+ |msgpack-special-dict|s. If there are more then two
+ entries then the rest are ignored
+ (|shada-compatibility|).
+ 7 (GlobalMark)
+ 8 (Jump)
+ 10 (LocalMark)
+ 11 (Change) Map containing some position description:
+ Entry Position ~
+ GlobaMark Global mark position. |'A|
+ LocalMark Local mark position. |'a|
+ Jump One position from the |jumplist|.
+ Change One position from the |changelist|.
+
+ Data contained in the map:
+ Key Type Default Description ~
+ l UInteger 1 Position line number. Must be
+ greater then zero.
+ c UInteger 0 Position column number.
+ n UInteger 34 ('"') Mark name. Only valid for
+ GlobalMark and LocalMark
+ entries.
+ f Binary N/A File name. Required.
+ * any none Other keys are allowed for
+ compatibility reasons, see
+ |shada-compatibility|.
+ 9 (BufferList) Array containing maps. Each map in the array
+ represents one buffer. Possible keys:
+ Key Type Default Description ~
+ l UInteger 1 Position line number. Must be
+ greater then zero.
+ c UInteger 0 Position column number.
+ f Binary N/A File name. Required.
+ * any none Other keys are allowed for
+ compatibility reasons, see
+ |shada-compatibility|.
+ * (Unknown) Any other entry type is allowed for compatibility
+ reasons, see |shada-compatibility|.
+
+ *E575* *E576*
+Errors in ShaDa file may have two types: E575 used for all “logical” errors
+and E576 used for all “critical” errors. Critical errors trigger behaviour
+described in |shada-error-handling| when writing and skipping the rest of the
+file when reading and include:
+ *shada-critical-contents-errors*
+- Any of first three MessagePack objects being not an unsigned integer.
+- Third object requesting amount of bytes greater then bytes left in the ShaDa
+ file.
+- Entry with zero type. I.e. first object being equal to zero.
+- MessagePack parser failing to parse the entry data.
+- MessagePack parser consuming less or requesting greater bytes then described
+ in the third object for parsing fourth object. I.e. when fourth object
+ either contains more then one MessagePack object or it does not contain
+ complete MessagePack object.
+
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index 88f23e6c76..8fe6b4b9d1 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -1,4 +1,4 @@
-*syntax.txt* For Vim version 7.4. Last change: 2014 Nov 19
+*syntax.txt* For Vim version 7.4. Last change: 2015 Jan 07
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1123,6 +1123,14 @@ to standard by placing this in your vimrc file: >
:let enforce_freedesktop_standard = 1
+DIFF *diff.vim*
+
+The diff highlighting normally finds translated headers. This can be slow if
+there are very long lines in the file. To disable translations: >
+
+ :let diff_translations = 0
+
+
DIRCOLORS *dircolors.vim* *ft-dircolors-syntax*
The dircolors utility highlighting definition has one option. It exists to
diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt
index 78ae536c5a..65e69a901b 100644
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -67,6 +67,7 @@ Regexp problems:
- Using back reference before the capturing group sometimes works with the old
engine, can we do this with the new engine? E.g. with
"/\%(<\1>\)\@<=.*\%(<\/\(\w\+\)>\)\@=" matching text inside HTML tags.
+- Diff highlighting can be very slow. (Issue 309)
Still using freed memory after using setloclist(). (lcd, 2014 Jul 23)
More info Jul 24. Not clear why.
@@ -130,6 +131,16 @@ Update Nov 5.
MS-Windows: Crash opening very long file name starting with "\\".
(Christian Brock, 2012 Jun 29)
+Patch for this from Marcin Szamotulski, 2014 Dec 28:
+8 Make the # register writable, so that it can be restored after jumping
+ around in windows.
+
+Using CTRL-L while popup menu is visible behaves like CTRL-P, which is wrong.
+Patch by Yasuhiro Matsumoto, 2015 Jan 5.
+Is this right? Comment from Amadeus Demarzi.
+Another patch from Christian, Jan 6.
+Comment from Hirohito Higashi, Jan 6.
+
Cursorline background color not mixed with character highlight.
Patch by Yasuhiro Matsumoto, 2014 Dec 3.
@@ -142,9 +153,19 @@ ml_updatechunk() is slow when retrying for another encoding. (John Little,
Patch to add a different escape sequence for replace mode.
(Omar Sandoval, 2014 Nov 30)
+Calling setreg() with an empty list doesn't work.
+Patch by Yasuhiro Matsumoto, 2014 Dec 14.
+
+Extended file attributes lost on write (backupcopy=no). Issue 306.
+
+Window height computed incorrectly when Vim is minimized.
+Patch to fix this. (Ingo Karkat, 2014 Dec 19)
+
Patch to allow values greater than 255 for ctermfg/ctermbg on Windows.
(Yasuhiro Matsumoto, 2014 Dec 5)
+Mixup of highlighting when there is a match and SpellBad. (ZyX, 2015 Jan 1)
+
When 'balloonexpr' returns a list the result has a trailing newline.
Just remove one trailing newline. (lcd, 2014 Oct 17)
@@ -192,6 +213,8 @@ Redo only remembers the last change. Could use "{count}g." to redo an older
change. How does the user know which change? At least have a way to list
them: ":repeats".
+Patch for glob(), adding slash to normal files. (Ingo Karkat, 2014 Dec 22)
+
Using "." to repeat an Ex command puts that command in history. Probably
should not happen. If the command is the result of a mapping it's not put in
history either. (Jacob Niehus, 2014 Nov 2)
@@ -222,6 +245,9 @@ Spell files use a latin single quote. Unicode also has another single quote:
New OpenOffice spell files support this with ICONV. But they are not
compatible with Vim spell files. The old files can no longer be downloaded.
+Patch to make FocusGained and FocusLost work in modern terminals. (Hayaki
+Saito, 2013 Apr 24)
+
Win32: patch to use 64 bit stat() if possible. (Ken Takata, 2014 May 12)
More tests May 14. Update May 29. Update Aug 10.
@@ -542,9 +568,6 @@ Patch to improve equivalence classes in regexp patterns.
Patch with suggestions for starting.txt. (Tony Mechelynck, 2012 Oct 24)
But use Gnome instead of GTK?
-Patch to make FocusGained and FocusLost work in modern terminals. (Hayaki
-Saito, 2013 Apr 24)
-
Should be possible to enable/disable matchparen per window or buffer.
Add a check for b:no_match_paren in Highlight_matching_Pair() (Marcin
Szamotulski, 2012 Nov 8)
@@ -4405,8 +4428,6 @@ Argument list:
Registers:
8 Don't display empty registers with ":display". (Etienne)
-8 Make the # register writable, so that it can be restored after jumping
- around in windows.
8 Add put command that overwrites existing text. Should also work for
blocks. Useful to move text around in a table. Works like using "R ^R r"
for every line.
diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt
index 2ce23f0abf..8bc208dc30 100644
--- a/runtime/doc/usr_21.txt
+++ b/runtime/doc/usr_21.txt
@@ -12,7 +12,7 @@ it later.
|21.1| Suspend and resume
|21.2| Executing shell commands
-|21.3| Remembering information; viminfo
+|21.3| Remembering information; ShaDa
|21.4| Sessions
|21.5| Views
|21.6| Modelines
@@ -78,13 +78,14 @@ This is similar to using CTRL-Z to suspend Vim. The difference is that a new
shell is started.
==============================================================================
-*21.3* Remembering information; viminfo
+*21.3* Remembering information; ShaDa
After editing for a while you will have text in registers, marks in various
files, a command line history filled with carefully crafted commands. When
you exit Vim all of this is lost. But you can get it back!
-The viminfo file is designed to store status information:
+The ShaDa (abbreviation of SHAred DAta) file is designed to store status
+information:
Command-line and Search pattern history
Text in registers
@@ -92,38 +93,38 @@ The viminfo file is designed to store status information:
The buffer list
Global variables
-Each time you exit Vim it will store this information in a file, the viminfo
-file. When Vim starts again, the viminfo file is read and the information
+Each time you exit Vim it will store this information in a file, the ShaDa
+file. When Vim starts again, the ShaDa file is read and the information
restored.
-The 'viminfo' option is set by default to restore a limited number of items.
+The 'shada' option is set by default to restore a limited number of items.
You might want to set it to remember more information. This is done through
the following command: >
- :set viminfo=string
+ :set shada=string
The string specifies what to save. The syntax of this string is an option
character followed by an argument. The option/argument pairs are separated by
commas.
- Take a look at how you can build up your own viminfo string. First, the '
+ Take a look at how you can build up your own shada string. First, the '
option is used to specify how many files for which you save marks (a-z). Pick
a nice even number for this option (1000, for instance). Your command now
looks like this: >
- :set viminfo='1000
+ :set shada='1000
The f option controls whether global marks (A-Z and 0-9) are stored. If this
option is 0, none are stored. If it is 1 or you do not specify an f option,
the marks are stored. You want this feature, so now you have this: >
- :set viminfo='1000,f1
+ :set shada='1000,f1
The < option controls how many lines are saved for each of the registers. By
default, all the lines are saved. If 0, nothing is saved. To avoid adding
-thousands of lines to your viminfo file (which might never get used and makes
+thousands of lines to your ShaDa file (which might never get used and makes
starting Vim slower) you use a maximum of 500 lines: >
- :set viminfo='1000,f1,<500
+ :set shada='1000,f1,<500
<
Other options you might want to use:
: number of lines to save from the command line history
@@ -137,9 +138,9 @@ Other options you might want to use:
% the buffer list (only restored when starting Vim without file
arguments)
c convert the text using 'encoding'
- n name used for the viminfo file (must be the last option)
+ n name used for the ShaDa file (must be the last option)
-See the 'viminfo' option and |viminfo-file| for more information.
+See the 'shada' option and |shada-file| for more information.
When you run Vim multiple times, the last one exiting will store its
information. This may cause information that previously exiting Vims stored
@@ -168,7 +169,7 @@ exiting Vim, there is a slightly more complicated way. You can see a list of
files by typing the command: >
:oldfiles
-< 1: ~/.viminfo ~
+< 1: ~/.vimrc ~
2: ~/text/resume.txt ~
3: /tmp/draft ~
@@ -188,7 +189,7 @@ That #<123 thing is a bit complicated when you just want to edit a file.
Fortunately there is a simpler way: >
:browse oldfiles
-< 1: ~/.viminfo ~
+< 1: ~/.vimrc ~
2: ~/text/resume.txt ~
3: /tmp/draft ~
-- More --
@@ -205,25 +206,25 @@ More info at |:oldfiles|, |v:oldfiles| and |c_#<|.
MOVE INFO FROM ONE VIM TO ANOTHER
-You can use the ":wviminfo" and ":rviminfo" commands to save and restore the
+You can use the ":wshada" and ":rshada" commands to save and restore the
information while still running Vim. This is useful for exchanging register
contents between two instances of Vim, for example. In the first Vim do: >
- :wviminfo! ~/tmp/viminfo
+ :wshada! ~/tmp/shada
And in the second Vim do: >
- :rviminfo! ~/tmp/viminfo
+ :rshada! ~/tmp/shada
Obviously, the "w" stands for "write" and the "r" for "read".
- The ! character is used by ":wviminfo" to forcefully overwrite an existing
+ The ! character is used by ":wshada" to forcefully overwrite an existing
file. When it is omitted, and the file exists, the information is merged into
the file.
- The ! character used for ":rviminfo" means that all the information is
-used, this may overwrite existing information. Without the ! only information
-that wasn't set is used.
+ The ! character used for ":rshada" means that all the information in ShaDa
+file has priority over existing information, this may overwrite it. Without
+the ! only information that wasn't set is used.
These commands can also be used to store info and use it again later. You
-could make a directory full of viminfo files, each containing info for a
+could make a directory full of ShaDa files, each containing info for a
different purpose.
==============================================================================
@@ -355,12 +356,12 @@ Similarly, MS-Windows Vim understands file names with / to separate names, but
Unix Vim doesn't understand \.
-SESSIONS AND VIMINFO
+SESSIONS AND SHADA
Sessions store many things, but not the position of marks, contents of
-registers and the command line history. You need to use the viminfo feature
+registers and the command line history. You need to use the shada feature
for these things.
- In most situations you will want to use sessions separately from viminfo.
+ In most situations you will want to use sessions separately from shada.
This can be used to switch to another session, but keep the command line
history. And yank text into registers in one session, and paste it back in
another session.
@@ -368,12 +369,12 @@ another session.
this yourself then. Example: >
:mksession! ~/.vim/secret.vim
- :wviminfo! ~/.vim/secret.viminfo
+ :wshada! ~/.vim/secret.shada
And to restore this again: >
:source ~/.vim/secret.vim
- :rviminfo! ~/.vim/secret.viminfo
+ :rshada! ~/.vim/secret.shada
==============================================================================
*21.5* Views
diff --git a/runtime/doc/usr_45.txt b/runtime/doc/usr_45.txt
index ac697e4bd5..808a9fc725 100644
--- a/runtime/doc/usr_45.txt
+++ b/runtime/doc/usr_45.txt
@@ -235,7 +235,7 @@ actually view a file that way, if you have lots of time at hand.
Note:
Since 'encoding' is used for all text inside Vim, changing it makes
all non-ASCII text invalid. You will notice this when using registers
- and the 'viminfo' file (e.g., a remembered search pattern). It's
+ and the |shada-file| (e.g., a remembered search pattern). It's
recommended to set 'encoding' in your vimrc file, and leave it alone.
==============================================================================
diff --git a/runtime/doc/usr_toc.txt b/runtime/doc/usr_toc.txt
index be9f0f1a77..77ea462a23 100644
--- a/runtime/doc/usr_toc.txt
+++ b/runtime/doc/usr_toc.txt
@@ -181,7 +181,7 @@ Subjects that can be read independently.
|usr_21.txt| Go away and come back
|21.1| Suspend and resume
|21.2| Executing shell commands
- |21.3| Remembering information; viminfo
+ |21.3| Remembering information; ShaDa
|21.4| Sessions
|21.5| Views
|21.6| Modelines
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 3cc0e7c8ab..df5d367469 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -370,6 +370,7 @@ N *+reltime* |reltime()| function, 'hlsearch'/'incsearch' timeout,
'redrawtime' option
B *+rightleft* Right to left typing |'rightleft'|
N *+scrollbind* |'scrollbind'|
+N *+shada* |'shada'|
B *+signs* |:sign|
N *+smartindent* |'smartindent'|
N *+startuptime* |--startuptime| argument
@@ -387,7 +388,6 @@ N *+textobjects* |text-objects| selection
N *+title* Setting the window 'title' and 'icon'
N *+toolbar* |gui-toolbar|
N *+user_commands* User-defined commands. |user-commands|
-N *+viminfo* |'viminfo'|
N *+vertsplit* Vertically split windows |:vsplit|
N *+virtualedit* |'virtualedit'|
S *+visual* Visual mode |Visual-mode| Always enabled since 7.4.200.
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index de061d3828..3b3f257351 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -23,7 +23,8 @@ these differences.
- Use `.nvimrc` instead of `.vimrc` for storing configuration.
- Use `.nvim` instead of `.vim` to store configuration files.
-- Use `.nviminfo` instead of `.viminfo` for persistent session information.
+- Use `.nvim/shada/main.shada` instead of `.viminfo` for persistent session
+ information.
==============================================================================
2. Option defaults *nvim-option-defaults*
@@ -93,6 +94,28 @@ are always available and may be used simultaneously in separate plugins. The
"{E724@level}"), but this is not reliable because |string()| continues to
error out.
+Viminfo text files were replaced with binary (messagepack) ShaDa files.
+Additional differences:
+
+- |shada-c| has no effect.
+- |shada-s| now limits size of every item and not just registers.
+- When reading ShaDa files items are merged according to the timestamp.
+ |shada-merging|
+- 'viminfo' option got renamed to 'shada'. Old option is kept as an alias for
+ compatibility reasons.
+- |:wviminfo| was renamed to |:wshada|, |:rviminfo| to |:rshada|. Old
+ commands are still kept.
+- When writing (|:wshada| without bang or at exit) it merges much more data,
+ and does this according to the timestamp. Vim merges only marks.
+ |shada-merging|
+- ShaDa file format was designed with forward and backward compatibility in
+ mind. |shada-compatibility|
+- Some errors make ShaDa code keep temporary file in-place for user to decide
+ what to do with it. Vim deletes temporary file in these cases.
+ |shada-error-handling|
+- Vim keeps no timestamps at all, neither in viminfo file nor in the instance
+ itself.
+
==============================================================================
4. New Features *nvim-features-new*
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index b649e8a319..95be125c33 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -53,7 +53,7 @@ hidden: The buffer is not displayed. If there is a file for this buffer, it
*inactive-buffer*
inactive: The buffer is not displayed and does not contain anything. Options
for the buffer are remembered if the file was once loaded. It can
- contain marks from the |viminfo| file. But the buffer doesn't
+ contain marks from the |shada| file. But the buffer doesn't
contain text.
In a table:
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 66e747a06f..9e0e39e3f8 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -1,7 +1,7 @@
" Vim support file to detect file types
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2014 Dec 06
+" Last Change: 2015 Jan 07
" Listen very carefully, I will say this only once
if exists("did_load_filetypes")
@@ -1048,6 +1048,9 @@ au BufNewFile,BufRead *.ldif setf ldif
" Ld loader
au BufNewFile,BufRead *.ld setf ld
+" Less
+au BufNewFile,BufRead *.less setf less
+
" Lex
au BufNewFile,BufRead *.lex,*.l,*.lxx,*.l++ setf lex
diff --git a/runtime/ftplugin/less.vim b/runtime/ftplugin/less.vim
new file mode 100644
index 0000000000..637e9d292e
--- /dev/null
+++ b/runtime/ftplugin/less.vim
@@ -0,0 +1,20 @@
+" Vim filetype plugin
+" Language: less
+" Maintainer: Alessandro Vioni <jenoma@gmail.com>
+" URL: https://github.com/genoma/vim-less
+" Last Change: 2014 November 24
+
+" Only do this when not done yet for this buffer
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let b:undo_ftplugin = "setl cms< def< inc< inex< ofu< sua<"
+
+setlocal formatoptions-=t formatoptions+=croql
+
+setlocal comments=:// commentstring=//\ %s
+
+setlocal omnifunc=csscomplete#CompleteCSS
+setlocal suffixesadd=.less
diff --git a/runtime/ftplugin/matlab.vim b/runtime/ftplugin/matlab.vim
index 6bfb3d7618..205111c3c2 100644
--- a/runtime/ftplugin/matlab.vim
+++ b/runtime/ftplugin/matlab.vim
@@ -1,7 +1,10 @@
" Vim filetype plugin file
" Language: matlab
" Maintainer: Jake Wasserman <jwasserman at gmail dot com>
-" Last Changed: 2006 Jan 12
+" Last Changed: 2014 Dec 30
+
+" Contributors:
+" Charles Campbell
if exists("b:did_ftplugin")
finish
@@ -12,10 +15,11 @@ let s:save_cpo = &cpo
set cpo-=C
if exists("loaded_matchit")
- let s:conditionalEnd = '\(([^()]*\)\@!\<end\>\([^()]*)\)\@!'
- let b:match_words = '\<if\>\|\<while\>\|\<for\>\|\<switch\>:' .
- \ s:conditionalEnd . ',\<if\>:\<elseif\>:\<else\>:' .
- \ s:conditionalEnd
+ let s:conditionalEnd = '\%(([^()]*\)\@!\<end\>\%([^()]*)\)\@!'
+ let b:match_words=
+ \ '\<\%(if\|switch\|for\|while\)\>:\<\%(elseif\|case\|break\|continue\|else\|otherwise\)\>:'.s:conditionalEnd.','.
+ \ '\<function\>:\<return\>:\<endfunction\>'
+ unlet s:conditionalEnd
endif
setlocal suffixesadd=.m
diff --git a/runtime/indent/less.vim b/runtime/indent/less.vim
new file mode 100644
index 0000000000..82bf2d87fc
--- /dev/null
+++ b/runtime/indent/less.vim
@@ -0,0 +1,13 @@
+" Vim indent file
+" Language: less
+" Maintainer: Alessandro Vioni <jenoma@gmail.com>
+" URL: https://github.com/genoma/vim-less
+" Last Change: 2014 November 24
+
+if exists("b:did_indent")
+ finish
+endif
+
+runtime! indent/css.vim
+
+" vim:set sw=2:
diff --git a/runtime/macros/less.vim b/runtime/macros/less.vim
index 9042e849ca..d38bd3781d 100644
--- a/runtime/macros/less.vim
+++ b/runtime/macros/less.vim
@@ -42,7 +42,7 @@ set hlsearch
set incsearch
nohlsearch
" Don't remember file names and positions
-set viminfo=
+set shada=
set nows
" Inhibit screen updates while searching
let s:lz = &lz
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 636fa4b328..d1ab204180 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -1226,8 +1226,8 @@ if has("mksession")
call append("$", "viewdir\tdirectory where to store files with :mkview")
call <SID>OptionG("vdir", &vdir)
endif
-if has("viminfo")
- call append("$", "viminfo\tlist that specifies what to write in the viminfo file")
+if has("shada")
+ call append("$", "viminfo\tlist that specifies what to write in the ShaDa file")
call <SID>OptionG("vi", &vi)
endif
if has("quickfix")
diff --git a/runtime/plugin/netrwPlugin.vim b/runtime/plugin/netrwPlugin.vim
index e3e79ddea8..cad4d31a04 100644
--- a/runtime/plugin/netrwPlugin.vim
+++ b/runtime/plugin/netrwPlugin.vim
@@ -1,6 +1,6 @@
" netrwPlugin.vim: Handles file transfer and remote directory listing across a network
" PLUGIN SECTION
-" Date: Jan 22, 2014
+" Date: Nov 07, 2014
" Maintainer: Charles E Campbell <NdrOchip@ScampbellPfamily.AbizM-NOSPAM>
" GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
" Copyright: Copyright (C) 1999-2013 Charles E. Campbell {{{1
@@ -20,7 +20,7 @@
if &cp || exists("g:loaded_netrwPlugin")
finish
endif
-let g:loaded_netrwPlugin = "v152"
+let g:loaded_netrwPlugin = "v153"
if v:version < 702
echohl WarningMsg
echo "***warning*** you need vim version 7.2 for this version of netrw"
@@ -54,15 +54,15 @@ augroup END
" Network Browsing Reading Writing: {{{2
augroup Network
au!
- au BufReadCmd file://* call netrw#FileUrlRead(expand("<amatch>"))
- au BufReadCmd ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufReadPre ".fnameescape(expand("<amatch>"))|call netrw#Nread(2,expand("<amatch>"))|exe "sil doau BufReadPost ".fnameescape(expand("<amatch>"))
- au FileReadCmd ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileReadPre ".fnameescape(expand("<amatch>"))|call netrw#Nread(1,expand("<amatch>"))|exe "sil doau FileReadPost ".fnameescape(expand("<amatch>"))
- au BufWriteCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufWritePre ".fnameescape(expand("<amatch>"))|exe 'Nwrite '.fnameescape(expand("<amatch>"))|exe "sil doau BufWritePost ".fnameescape(expand("<amatch>"))
- au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileWritePre ".fnameescape(expand("<amatch>"))|exe "'[,']".'Nwrite '.fnameescape(expand("<amatch>"))|exe "sil doau FileWritePost ".fnameescape(expand("<amatch>"))
- try
- au SourceCmd ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("<amatch>"))
- catch /^Vim\%((\a\+)\)\=:E216/
- au SourcePre ftp://*,rcp://*,scp://*,http://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("<amatch>"))
+ au BufReadCmd file://* call netrw#FileUrlRead(expand("<amatch>"))
+ au BufReadCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufReadPre ".fnameescape(expand("<amatch>"))|call netrw#Nread(2,expand("<amatch>"))|exe "sil doau BufReadPost ".fnameescape(expand("<amatch>"))
+ au FileReadCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileReadPre ".fnameescape(expand("<amatch>"))|call netrw#Nread(1,expand("<amatch>"))|exe "sil doau FileReadPost ".fnameescape(expand("<amatch>"))
+ au BufWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau BufWritePre ".fnameescape(expand("<amatch>"))|exe 'Nwrite '.fnameescape(expand("<amatch>"))|exe "sil doau BufWritePost ".fnameescape(expand("<amatch>"))
+ au FileWriteCmd ftp://*,rcp://*,scp://*,http://*,file://*,dav://*,davs://*,rsync://*,sftp://* exe "sil doau FileWritePre ".fnameescape(expand("<amatch>"))|exe "'[,']".'Nwrite '.fnameescape(expand("<amatch>"))|exe "sil doau FileWritePost ".fnameescape(expand("<amatch>"))
+ try
+ au SourceCmd ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("<amatch>"))
+ catch /^Vim\%((\a\+)\)\=:E216/
+ au SourcePre ftp://*,rcp://*,scp://*,http://*,file://*,https://*,dav://*,davs://*,rsync://*,sftp://* exe 'Nsource '.fnameescape(expand("<amatch>"))
endtry
augroup END
@@ -81,18 +81,26 @@ com! -nargs=* -bar -bang -count=0 -complete=dir Vexplore call netrw#Explore(<cou
com! -nargs=* -bar -count=0 -complete=dir Texplore call netrw#Explore(<count>,0,6 ,<q-args>)
com! -nargs=* -bar -bang Nexplore call netrw#Explore(-1,0,0,<q-args>)
com! -nargs=* -bar -bang Pexplore call netrw#Explore(-2,0,0,<q-args>)
-com! -nargs=* -bar -complete=dir Lexplore call netrw#Lexplore(<q-args>)
+com! -nargs=* -bar -bang -count=0 -complete=dir Lexplore call netrw#Lexplore(<count>,<bang>0,<q-args>)
" Commands: NetrwSettings {{{2
com! -nargs=0 NetrwSettings call netrwSettings#NetrwSettings()
com! -bang NetrwClean call netrw#Clean(<bang>0)
" Maps:
-if !exists("g:netrw_nogx") && maparg('gx','n') == ""
- if !hasmapto('<Plug>NetrwBrowseX')
- nmap <unique> gx <Plug>NetrwBrowseX
+if !exists("g:netrw_nogx")
+ if maparg('gx','n') == ""
+ if !hasmapto('<Plug>NetrwBrowseX')
+ nmap <unique> gx <Plug>NetrwBrowseX
+ endif
+ nno <silent> <Plug>NetrwBrowseX :call netrw#BrowseX(expand((exists("g:netrw_gx")? g:netrw_gx : '<cfile>')),netrw#CheckIfRemote())<cr>
+ endif
+ if maparg('gx','v') == ""
+ if !hasmapto('<Plug>NetrwBrowseXVis')
+ vmap <unique> gx <Plug>NetrwBrowseXVis
+ endif
+ vno <silent> <Plug>NetrwBrowseXVis :<c-u>call netrw#BrowseXVis()<cr>
endif
- nno <silent> <Plug>NetrwBrowseX :call netrw#NetrwBrowseX(expand("<cfile>"),0)<cr>
endif
" ---------------------------------------------------------------------
@@ -119,7 +127,7 @@ fun! s:LocalBrowse(dirname)
" call Decho("(LocalBrowse) dirname<".a:dirname."> (isdirectory, amiga)")
if a:dirname != '' && isdirectory(a:dirname)
sil! call netrw#LocalBrowseCheck(a:dirname)
- if exists("w:netrw_bannercnt")
+ if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt
exe w:netrw_bannercnt
endif
endif
@@ -128,7 +136,7 @@ fun! s:LocalBrowse(dirname)
" call Decho("(LocalBrowse) dirname<".a:dirname."> ft=".&ft." (isdirectory, not amiga)")
" call Dredir("LocalBrowse ft last set: ","verbose set ft")
sil! call netrw#LocalBrowseCheck(a:dirname)
- if exists("w:netrw_bannercnt")
+ if exists("w:netrw_bannercnt") && line('.') < w:netrw_bannercnt
exe w:netrw_bannercnt
endif
diff --git a/runtime/syntax/diff.vim b/runtime/syntax/diff.vim
index a0005140b2..ff69c5860b 100644
--- a/runtime/syntax/diff.vim
+++ b/runtime/syntax/diff.vim
@@ -2,7 +2,7 @@
" Language: Diff (context or unified)
" Maintainer: Bram Moolenaar <Bram@vim.org>
" Translations by Jakson Alves de Aquino.
-" Last Change: 2014 Nov 12
+" Last Change: 2015 Jan 07
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
@@ -18,6 +18,9 @@ syn match diffIsA "^File .* is a .* while file .* is a .*"
syn match diffNoEOL "^\\ No newline at end of file .*"
syn match diffCommon "^Common subdirectories: .*"
+" Disable the translations by setting diff_translations to zero.
+if !exists("diff_translations") || diff_translations
+
" ca
syn match diffOnly "^Només a .*"
syn match diffIdentical "^Els fitxers .* i .* són idèntics$"
@@ -320,6 +323,8 @@ syn match diffIsA "^檔案 .* 是.*而檔案 .* 是.*"
syn match diffNoEOL "^\\ 檔案末沒有 newline 字元"
syn match diffCommon "^.* 和 .* 有共同的副目錄$"
+endif
+
syn match diffRemoved "^-.*"
syn match diffRemoved "^<.*"
diff --git a/runtime/syntax/html.vim b/runtime/syntax/html.vim
index d59126dede..ab1f9b3c13 100644
--- a/runtime/syntax/html.vim
+++ b/runtime/syntax/html.vim
@@ -2,7 +2,8 @@
" Language: HTML
" Maintainer: Claudio Fleiner <claudio@fleiner.com>
" URL: http://www.fleiner.com/vim/syntax/html.vim
-" Last Change: 2012 Oct 05
+" Last Change: 2015 Jan 07
+" included patch from David Felix
" Please check :help html.vim for some comments and a description of the options
@@ -168,7 +169,7 @@ if main_syntax != 'java' || exists("java_javascript")
" JAVA SCRIPT
syn include @htmlJavaScript syntax/javascript.vim
unlet b:current_syntax
- syn region javaScript start=+<script\_[^>]*>+ keepend end=+</script>+me=s-1 contains=@htmlJavaScript,htmlCssStyleComment,htmlScriptTag,@htmlPreproc
+ syn region javaScript start=+<script\_[^>]*>+ keepend end=+</script\_[^>]*>+me=s-1 contains=@htmlJavaScript,htmlCssStyleComment,htmlScriptTag,@htmlPreproc
syn region htmlScriptTag contained start=+<script+ end=+>+ fold contains=htmlTagN,htmlString,htmlArg,htmlValue,htmlTagError,htmlEvent
HtmlHiLink htmlScriptTag htmlTag
@@ -193,7 +194,7 @@ if main_syntax != 'java' || exists("java_vb")
" VB SCRIPT
syn include @htmlVbScript syntax/vb.vim
unlet b:current_syntax
- syn region javaScript start=+<script \_[^>]*language *=\_[^>]*vbscript\_[^>]*>+ keepend end=+</script>+me=s-1 contains=@htmlVbScript,htmlCssStyleComment,htmlScriptTag,@htmlPreproc
+ syn region javaScript start=+<script \_[^>]*language *=\_[^>]*vbscript\_[^>]*>+ keepend end=+</script\_[^>]*>+me=s-1 contains=@htmlVbScript,htmlCssStyleComment,htmlScriptTag,@htmlPreproc
endif
syn cluster htmlJavaScript add=@htmlPreproc
diff --git a/runtime/syntax/less.vim b/runtime/syntax/less.vim
new file mode 100644
index 0000000000..5cc8c6d7bd
--- /dev/null
+++ b/runtime/syntax/less.vim
@@ -0,0 +1,79 @@
+" Vim syntax file
+" Language: less
+" Maintainer: Alessandro Vioni <jenoma@gmail.com>
+" URL: https://github.com/genoma/vim-less
+" Last Change: 2014 November 24
+
+if exists("b:current_syntax")
+ finish
+endif
+
+runtime! syntax/css.vim
+runtime! after/syntax/css.vim
+
+syn case ignore
+
+syn cluster lessCssProperties contains=cssFontProp,cssFontDescriptorProp,cssColorProp,cssTextProp,cssBoxProp,cssGeneratedContentProp,cssPagingProp,cssUIProp,cssRenderProp,cssAuralProp,cssTableProp
+syn cluster lessCssAttributes contains=css.*Attr,lessEndOfLineComment,lessComment,cssValue.*,cssColor,cssURL,lessDefault,cssImportant,cssError,cssStringQ,cssStringQQ,cssFunction,cssUnicodeEscape,cssRenderProp
+
+syn region lessDefinition matchgroup=cssBraces start="{" end="}" contains=TOP
+
+syn match lessProperty "\%([{};]\s*\|^\)\@<=\%([[:alnum:]-]\|#{[^{}]*}\)\+\s*:" contains=css.*Prop skipwhite nextgroup=lessCssAttribute contained containedin=lessDefinition
+syn match lessProperty "^\s*\zs\s\%(\%([[:alnum:]-]\|#{[^{}]*}\)\+\s*:\|:[[:alnum:]-]\+\)"hs=s+1 contains=css.*Prop skipwhite nextgroup=lessCssAttribute
+syn match lessProperty "^\s*\zs\s\%(:\=[[:alnum:]-]\+\s*=\)"hs=s+1 contains=css.*Prop skipwhite nextgroup=lessCssAttribute
+syn match lessCssAttribute +\%("\%([^"]\|\\"\)*"\|'\%([^']\|\\'\)*'\|#{[^{}]*}\|[^{};]\)*+ contained contains=@lessCssAttributes,lessVariable,lessFunction,lessInterpolation
+syn match lessDefault "!default\>" contained
+
+" less variables and media queries
+syn match lessVariable "@[[:alnum:]_-]\+" nextgroup=lessCssAttribute skipwhite
+syn match lessMedia "@media" nextgroup=lessCssAttribute skipwhite
+
+" Less functions
+syn match lessFunction "\<\%(escape\|e\|unit\)\>(\@=" contained
+syn match lessFunction "\<\%(ceil\|floor\|percentage\|round\|sqrt\|abs\|sin\|asin\|cos\|acos\|tan\|atan\|pi\|pow\|min\|max\)\>(\@=" contained
+syn match lessFunction "\<\%(rgb\|rgba\|argb\|argb\|hsl\|hsla\|hsv\|hsva\)\>(\@=" contained
+syn match lessFunction "\<\%(hue\|saturation\|lightness\|red\|green\|blue\|alpha\|luma\)\>(\@=" contained
+syn match lessFunction "\<\%(saturate\|desaturate\|lighten\|darken\|fadein\|fadeout\|fade\|spin\|mix\|greyscale\|contrast\)\>(\@=" contained
+syn match lessFunction "\<\%(multiply\|screen\|overlay\|softlight\|hardlight\|difference\|exclusion\|average\|negation\)\>(\@=" contained
+
+" Less id class visualization
+syn match lessIdChar "#[[:alnum:]_-]\@=" nextgroup=lessId,lessClassIdCall
+syn match lessId "[[:alnum:]_-]\+" contained
+syn match lessClassIdCall "[[:alnum:]_-]\+()" contained
+
+syn match lessClassChar "\.[[:alnum:]_-]\@=" nextgroup=lessClass,lessClassCall
+syn match lessClass "[[:alnum:]_-]\+" contained
+syn match lessClassCall "[[:alnum:]_-]\+()" contained
+
+syn match lessAmpersand "&" contains=lessIdChar,lessClassChar
+
+syn region lessInclude start="@import" end=";\|$" contains=lessComment,cssURL,cssUnicodeEscape,cssMediaType,cssStringQ,cssStringQQ
+
+syn keyword lessTodo FIXME NOTE TODO OPTIMIZE XXX contained
+syn region lessComment start="^\z(\s*\)//" end="^\%(\z1 \)\@!" contains=lessTodo,@Spell
+syn region lessCssComment start="^\z(\s*\)/\*" end="^\%(\z1 \)\@!" contains=lessTodo,@Spell
+syn match lessEndOfLineComment "//.*" contains=lessComment,lessTodo,@Spell
+
+hi def link lessEndOfLineComment lessComment
+hi def link lessCssComment lessComment
+hi def link lessComment Comment
+hi def link lessDefault cssImportant
+hi def link lessVariable Identifier
+hi def link lessFunction PreProc
+hi def link lessTodo Todo
+hi def link lessInclude Include
+hi def link lessIdChar Special
+hi def link lessClassChar Special
+hi def link lessAmpersand Character
+hi def link lessId Identifier
+hi def link lessClass Type
+hi def link lessCssAttribute PreProc
+hi def link lessClassCall Type
+hi def link lessClassIdCall Type
+hi def link lessTagName cssTagName
+hi def link lessDeprecated cssDeprecated
+hi def link lessMedia cssMedia
+
+let b:current_syntax = "less"
+
+" vim:set sw=2:
diff --git a/runtime/syntax/netrw.vim b/runtime/syntax/netrw.vim
index 16e3c90c44..980fe5dde5 100644
--- a/runtime/syntax/netrw.vim
+++ b/runtime/syntax/netrw.vim
@@ -1,7 +1,7 @@
" Language : Netrw Remote-Directory Listing Syntax
-" Maintainer : Charles E. Campbell, Jr.
-" Last change: Mar 07, 2014
-" Version : 18
+" Maintainer : Charles E. Campbell
+" Last change: Oct 06, 2014
+" Version : 19
" ---------------------------------------------------------------------
" Syntax Clearing: {{{1
@@ -16,7 +16,7 @@ endif
syn cluster NetrwGroup contains=netrwHide,netrwSortBy,netrwSortSeq,netrwQuickHelp,netrwVersion,netrwCopyTgt
syn cluster NetrwTreeGroup contains=netrwDir,netrwSymLink,netrwExe
-syn match netrwPlain "\(\S\+ \)*\S\+" contains=@NoSpell
+syn match netrwPlain "\(\S\+ \)*\S\+" contains=netrwLink,@NoSpell
syn match netrwSpecial "\%(\S\+ \)*\S\+[*|=]\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell
syn match netrwDir "\.\{1,2}/" contains=netrwClassify,@NoSpell
"syn match netrwDir "\%(\S\+ \)*\S\+/" contains=netrwClassify,@NoSpell
@@ -24,7 +24,11 @@ syn match netrwDir "\%(\S\+ \)*\S\+/\ze\%(\s\{2,}\|$\)" contains=netrwClassi
syn match netrwSizeDate "\<\d\+\s\d\{1,2}/\d\{1,2}/\d\{4}\s" skipwhite contains=netrwDateSep,@NoSpell nextgroup=netrwTime
syn match netrwSymLink "\%(\S\+ \)*\S\+@\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell
syn match netrwExe "\%(\S\+ \)*\S*[^~]\*\ze\%(\s\{2,}\|$\)" contains=netrwClassify,@NoSpell
+if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4')
syn match netrwTreeBar "^\%([-+|│] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup
+else
+syn match netrwTreeBar "^\%([-+|] \)\+" contains=netrwTreeBarSpace nextgroup=@netrwTreeGroup
+endif
syn match netrwTreeBarSpace " " contained
syn match netrwClassify "[*=|@/]\ze\%(\s\{2,}\|$\)" contained
@@ -44,10 +48,11 @@ syn match netrwList ".*$" contained contains=netrwComma,@NoSpell
syn match netrwComma "," contained
syn region netrwQuickHelp matchgroup=Comment start="Quick Help:\s\+" end="$" contains=netrwHelpCmd,netrwQHTopic,@NoSpell keepend contained
syn match netrwHelpCmd "\S\+\ze:" contained skipwhite contains=@NoSpell nextgroup=netrwCmdSep
-syn match netrwQHTopic "-\a\+-" contained skipwhite
+syn match netrwQHTopic "([a-zA-Z &]\+)" contained skipwhite
syn match netrwCmdSep ":" contained nextgroup=netrwCmdNote
syn match netrwCmdNote ".\{-}\ze " contained contains=@NoSpell
syn match netrwVersion "(netrw.*)" contained contains=@NoSpell
+syn match netrwLink "-->" contained skipwhite
" -----------------------------
" Special filetype highlighting {{{1
@@ -95,6 +100,7 @@ if !exists("did_drchip_netrwlist_syntax")
hi default link netrwComma netrwComment
hi default link netrwHide netrwComment
hi default link netrwMarkFile TabLineSel
+ hi default link netrwLink Special
" special syntax highlighting (see :he g:netrw_special_syntax)
hi default link netrwBak NonText
diff --git a/scripts/shadacat.py b/scripts/shadacat.py
new file mode 100755
index 0000000000..4ff493bfbc
--- /dev/null
+++ b/scripts/shadacat.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3.4
+
+import os
+import sys
+import codecs
+
+from enum import Enum
+from datetime import datetime
+from functools import reduce
+
+import msgpack
+
+
+class EntryTypes(Enum):
+ Unknown = -1
+ Missing = 0
+ Header = 1
+ SearchPattern = 2
+ SubString = 3
+ HistoryEntry = 4
+ Register = 5
+ Variable = 6
+ GlobalMark = 7
+ Jump = 8
+ BufferList = 9
+ LocalMark = 10
+ Change = 11
+
+
+def strtrans_errors(e):
+ if not isinstance(e, UnicodeDecodeError):
+ raise NotImplementedError('don’t know how to handle {0} error'.format(
+ e.__class__.__name__))
+ return '<{0:x}>'.format(reduce((lambda a, b: a*0x100+b),
+ list(e.object[e.start:e.end]))), e.end
+
+
+codecs.register_error('strtrans', strtrans_errors)
+
+
+def idfunc(o):
+ return o
+
+
+class CharInt(int):
+ def __repr__(self):
+ return super(CharInt, self).__repr__() + ' (\'%s\')' % chr(self)
+
+
+ctable = {
+ bytes: lambda s: s.decode('utf-8', 'strtrans'),
+ dict: lambda d: dict((mnormalize(k), mnormalize(v)) for k, v in d.items()),
+ list: lambda l: list(mnormalize(i) for i in l),
+ int: lambda n: CharInt(n) if 0x20 <= n <= 0x7E else n,
+}
+
+
+def mnormalize(o):
+ return ctable.get(type(o), idfunc)(o)
+
+
+fname = sys.argv[1]
+poswidth = len(str(os.stat(fname).st_size or 1000))
+
+
+with open(fname, 'rb') as fp:
+ unpacker = msgpack.Unpacker(file_like=fp, read_size=1)
+ max_type = max(typ.value for typ in EntryTypes)
+ while True:
+ try:
+ pos = fp.tell()
+ typ = unpacker.unpack()
+ except msgpack.OutOfData:
+ break
+ else:
+ timestamp = unpacker.unpack()
+ time = datetime.fromtimestamp(timestamp)
+ length = unpacker.unpack()
+ if typ > max_type:
+ entry = fp.read(length)
+ typ = EntryTypes.Unknown
+ else:
+ entry = unpacker.unpack()
+ typ = EntryTypes(typ)
+ print('%*u %13s %s %5u %r' % (
+ poswidth, pos, typ.name, time.isoformat(), length, mnormalize(entry)))
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 0485fbacd2..7a0b5191d7 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -597,7 +597,7 @@ static void init_type_metadata(Dictionary *metadata)
}
/// Creates a deep clone of an object
-static Object copy_object(Object obj)
+Object copy_object(Object obj)
{
switch (obj.type) {
case kObjectTypeNil:
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index f29deb53f9..a0f14ac7a4 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -69,6 +69,8 @@
#define ADD(array, item) \
kv_push(Object, array, item)
+#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1})
+
// Helpers used by the generated msgpack-rpc api wrappers
#define api_init_boolean
#define api_init_integer
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index af801c6f1a..3d8a75febd 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -84,7 +84,7 @@ return {
'User', -- user defined autocommand
'VimEnter', -- after starting Vim
'VimLeave', -- before exiting Vim
- 'VimLeavePre', -- before exiting Vim and writing .viminfo
+ 'VimLeavePre', -- before exiting Vim and writing ShaDa file
'VimResized', -- after Vim window was resized
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b212e75283..b3eba4f5f6 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -73,6 +73,7 @@
#include "nvim/undo.h"
#include "nvim/version.h"
#include "nvim/window.h"
+#include "nvim/shada.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
@@ -555,9 +556,21 @@ static void free_buffer(buf_T *buf)
free_buffer_stuff(buf, TRUE);
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
+ dict_unref(buf->additional_data);
+ clear_fmark(&buf->b_last_cursor);
+ clear_fmark(&buf->b_last_insert);
+ clear_fmark(&buf->b_last_change);
+ for (size_t i = 0; i < NMARKS; i++) {
+ free_fmark(buf->b_namedm[i]);
+ }
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ free_fmark(buf->b_changelist[i]);
+ }
if (autocmd_busy) {
// Do not free the buffer structure while autocommands are executing,
// it's still needed. Free it when autocmd_busy is reset.
+ memset(&buf->b_namedm[0], 0, sizeof(buf->b_namedm));
+ memset(&buf->b_changelist[0], 0, sizeof(buf->b_changelist));
buf->b_next = au_pending_free_buf;
au_pending_free_buf = buf;
} else {
@@ -1978,12 +1991,18 @@ buflist_nr2name (
fullname ? buf->b_ffname : buf->b_fname);
}
-/*
- * Set the "lnum" and "col" for the buffer "buf" and the current window.
- * When "copy_options" is TRUE save the local window option values.
- * When "lnum" is 0 only do the options.
- */
-static void buflist_setfpos(buf_T *buf, win_T *win, linenr_T lnum, colnr_T col, int copy_options)
+/// Set the line and column numbers for the given buffer and window
+///
+/// @param[in,out] buf Buffer for which line and column are set.
+/// @param[in,out] win Window for which line and column are set.
+/// @param[in] lnum Line number to be set. If it is zero then only
+/// options are touched.
+/// @param[in] col Column number to be set.
+/// @param[in] copy_options If true save the local window option values.
+void buflist_setfpos(buf_T *const buf, win_T *const win,
+ linenr_T lnum, colnr_T col,
+ bool copy_options)
+ FUNC_ATTR_NONNULL_ALL
{
wininfo_T *wip;
@@ -4164,93 +4183,6 @@ chk_modeline (
return retval;
}
-int read_viminfo_bufferlist(vir_T *virp, int writing)
-{
- char_u *tab;
- linenr_T lnum;
- colnr_T col;
- buf_T *buf;
- char_u *sfname;
- char_u *xline;
-
- /* Handle long line and escaped characters. */
- xline = viminfo_readstring(virp, 1, FALSE);
-
- /* don't read in if there are files on the command-line or if writing: */
- if (xline != NULL && !writing && ARGCOUNT == 0
- && find_viminfo_parameter('%') != NULL) {
- /* Format is: <fname> Tab <lnum> Tab <col>.
- * Watch out for a Tab in the file name, work from the end. */
- lnum = 0;
- col = 0;
- tab = vim_strrchr(xline, '\t');
- if (tab != NULL) {
- *tab++ = '\0';
- col = (colnr_T)atoi((char *)tab);
- tab = vim_strrchr(xline, '\t');
- if (tab != NULL) {
- *tab++ = '\0';
- lnum = atol((char *)tab);
- }
- }
-
- /* Expand "~/" in the file name at "line + 1" to a full path.
- * Then try shortening it by comparing with the current directory */
- expand_env(xline, NameBuff, MAXPATHL);
- sfname = path_shorten_fname_if_possible(NameBuff);
-
- buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED);
- if (buf != NULL) { /* just in case... */
- buf->b_last_cursor.lnum = lnum;
- buf->b_last_cursor.col = col;
- buflist_setfpos(buf, curwin, lnum, col, FALSE);
- }
- }
- xfree(xline);
-
- return viminfo_readline(virp);
-}
-
-void write_viminfo_bufferlist(FILE *fp)
-{
- char_u *line;
- int max_buffers;
-
- if (find_viminfo_parameter('%') == NULL)
- return;
-
- /* Without a number -1 is returned: do all buffers. */
- max_buffers = get_viminfo_parameter('%');
-
- /* Allocate room for the file name, lnum and col. */
-#define LINE_BUF_LEN (MAXPATHL + 40)
- line = xmalloc(LINE_BUF_LEN);
-
- FOR_ALL_TAB_WINDOWS(tp, win) {
- set_last_cursor(win);
- }
-
- fputs(_("\n# Buffer list:\n"), fp);
- FOR_ALL_BUFFERS(buf) {
- if (buf->b_fname == NULL
- || !buf->b_p_bl
- || bt_quickfix(buf)
- || removable(buf->b_ffname))
- continue;
-
- if (max_buffers-- == 0)
- break;
- putc('%', fp);
- home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE);
- vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%" PRId64 "\t%d",
- (int64_t)buf->b_last_cursor.lnum,
- buf->b_last_cursor.col);
- viminfo_writestring(fp, line);
- }
- xfree(line);
-}
-
-
/*
* Return special buffer name.
* Returns NULL when the buffer has a normal file name.
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 98fbef9c87..3eabb7ee43 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -327,15 +327,6 @@ typedef struct {
bool vc_fail; /* fail for invalid char, don't use '?' */
} vimconv_T;
-/*
- * Structure used for reading from the viminfo file.
- */
-typedef struct {
- char_u *vir_line; /* text of the current line */
- FILE *vir_fd; /* file descriptor */
- vimconv_T vir_conv; /* encoding conversion */
-} vir_T;
-
#define CONV_NONE 0
#define CONV_TO_UTF8 1
#define CONV_9_TO_UTF8 2
@@ -515,21 +506,21 @@ struct file_buffer {
uint64_t b_orig_size; /* size of original file in bytes */
int b_orig_mode; /* mode of original file */
- pos_T b_namedm[NMARKS]; /* current named marks (mark.c) */
+ fmark_T b_namedm[NMARKS]; /* current named marks (mark.c) */
/* These variables are set when VIsual_active becomes FALSE */
visualinfo_T b_visual;
int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */
- pos_T b_last_cursor; /* cursor position when last unloading this
- buffer */
- pos_T b_last_insert; /* where Insert mode was left */
- pos_T b_last_change; /* position of last change: '. mark */
+ fmark_T b_last_cursor; // cursor position when last unloading this
+ // buffer
+ fmark_T b_last_insert; // where Insert mode was left
+ fmark_T b_last_change; // position of last change: '. mark
/*
* the changelist contains old change positions
*/
- pos_T b_changelist[JUMPLISTSIZE];
+ fmark_T b_changelist[JUMPLISTSIZE];
int b_changelistlen; /* number of active entries */
bool b_new_change; /* set by u_savecommon() */
@@ -553,7 +544,7 @@ struct file_buffer {
pos_T b_op_start_orig; // used for Insstart_orig
pos_T b_op_end;
- bool b_marks_read; /* Have we read viminfo marks yet? */
+ bool b_marks_read; /* Have we read ShaDa marks yet? */
/*
* The following only used in undo.c.
@@ -757,6 +748,8 @@ struct file_buffer {
signlist_T *b_signlist; /* list of signs to draw */
Terminal *terminal; // Terminal instance associated with the buffer
+
+ dict_T *additional_data; // Additional data from shada file if any.
};
/*
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 427623e052..310191ba06 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -60,6 +60,7 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/event/loop.h"
+#include "nvim/mark.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
@@ -6991,8 +6992,9 @@ ins_esc (
curwin->w_set_curswant = TRUE;
/* Remember the last Insert position in the '^ mark. */
- if (!cmdmod.keepjumps)
- curbuf->b_last_insert = curwin->w_cursor;
+ if (!cmdmod.keepjumps) {
+ RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum);
+ }
/*
* The cursor should end up on the last inserted character.
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 65afd19bbe..d02348028a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -108,18 +108,6 @@
function/variable names. */
/*
- * In a hashtab item "hi_key" points to "di_key" in a dictitem.
- * This avoids adding a pointer to the hashtab item.
- * DI2HIKEY() converts a dictitem pointer to a hashitem key pointer.
- * HIKEY2DI() converts a hashitem key pointer to a dictitem pointer.
- * HI2DI() converts a hashitem pointer to a dictitem pointer.
- */
-static dictitem_T dumdi;
-#define DI2HIKEY(di) ((di)->di_key)
-#define HIKEY2DI(p) ((dictitem_T *)(p - (dumdi.di_key - (char_u *)&dumdi)))
-#define HI2DI(hi) HIKEY2DI((hi)->hi_key)
-
-/*
* Structure returned by get_lval() and used by set_var_lval().
* For a plain name:
* "name" points to the variable name.
@@ -354,7 +342,7 @@ typedef struct {
typedef enum {
VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */
VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */
- VAR_FLAVOUR_VIMINFO /* all uppercase */
+ VAR_FLAVOUR_SHADA /* all uppercase */
} var_flavour_T;
/* values for vv_flags: */
@@ -5352,7 +5340,7 @@ static int list_concat(list_T *l1, list_T *l2, typval_T *tv)
return FAIL;
/* make a copy of the first list. */
- l = list_copy(l1, FALSE, 0);
+ l = list_copy(NULL, l1, false, 0);
if (l == NULL)
return FAIL;
tv->v_type = VAR_LIST;
@@ -5363,13 +5351,20 @@ static int list_concat(list_T *l1, list_T *l2, typval_T *tv)
return OK;
}
-/*
- * Make a copy of list "orig". Shallow if "deep" is FALSE.
- * The refcount of the new list is set to 1.
- * See item_copy() for "copyID".
- * Returns NULL if orig is NULL or some failure happens.
- */
-static list_T *list_copy(list_T *orig, int deep, int copyID)
+/// Make a copy of list
+///
+/// @param[in] conv If non-NULL, then all internal strings will be converted.
+/// @param[in] orig Original list to copy.
+/// @param[in] deep If false, then shallow copy will be done.
+/// @param[in] copyID See var_item_copy().
+///
+/// @return Copied list. May be NULL in case original list is NULL or some
+/// failure happens. The refcount of the new list is set to 1.
+static list_T *list_copy(const vimconv_T *const conv,
+ list_T *const orig,
+ const bool deep,
+ const int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
listitem_T *item;
listitem_T *ni;
@@ -5388,7 +5383,7 @@ static list_T *list_copy(list_T *orig, int deep, int copyID)
item = item->li_next) {
ni = listitem_alloc();
if (deep) {
- if (item_copy(&item->li_tv, &ni->li_tv, deep, copyID) == FAIL) {
+ if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) {
xfree(ni);
break;
}
@@ -5546,6 +5541,7 @@ static int list_join(garray_T *const gap, list_T *const l,
bool garbage_collect(void)
{
bool abort = false;
+#define ABORTING(func) abort = abort || func
// Only do this once.
want_garbage_collect = false;
@@ -5564,45 +5560,117 @@ bool garbage_collect(void)
// referenced through previous_funccal. This must be first, because if
// the item is referenced elsewhere the funccal must not be freed.
for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
}
// script-local variables
for (int i = 1; i <= ga_scripts.ga_len; ++i) {
- abort = abort || set_ref_in_ht(&SCRIPT_VARS(i), copyID, NULL);
+ ABORTING(set_ref_in_ht)(&SCRIPT_VARS(i), copyID, NULL);
}
- // buffer-local variables
FOR_ALL_BUFFERS(buf) {
- abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
+ // buffer-local variables
+ ABORTING(set_ref_in_item)(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
+ // buffer marks (ShaDa additional data)
+ ABORTING(set_ref_in_fmark)(buf->b_last_cursor, copyID);
+ ABORTING(set_ref_in_fmark)(buf->b_last_insert, copyID);
+ ABORTING(set_ref_in_fmark)(buf->b_last_change, copyID);
+ for (size_t i = 0; i < NMARKS; i++) {
+ ABORTING(set_ref_in_fmark)(buf->b_namedm[i], copyID);
+ }
+ // buffer change list (ShaDa additional data)
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ ABORTING(set_ref_in_fmark)(buf->b_changelist[i], copyID);
+ }
+ // buffer ShaDa additional data
+ ABORTING(set_ref_dict)(buf->additional_data, copyID);
}
- // window-local variables
FOR_ALL_TAB_WINDOWS(tp, wp) {
- abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, NULL, NULL);
+ // window-local variables
+ ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL);
+ // window jump list (ShaDa additional data)
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
+ ABORTING(set_ref_in_fmark)(wp->w_jumplist[i].fmark, copyID);
+ }
}
if (aucmd_win != NULL) {
- abort = abort ||
- set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL);
+ ABORTING(set_ref_in_item)(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL);
+ }
+
+ // registers (ShaDa additional data)
+ {
+ const void *reg_iter = NULL;
+ do {
+ yankreg_T reg;
+ char name = NUL;
+ reg_iter = op_register_iter(reg_iter, &name, &reg);
+ if (name != NUL) {
+ ABORTING(set_ref_dict)(reg.additional_data, copyID);
+ }
+ } while (reg_iter != NULL);
+ }
+
+ // global marks (ShaDa additional data)
+ {
+ const void *mark_iter = NULL;
+ do {
+ xfmark_T fm;
+ char name = NUL;
+ mark_iter = mark_global_iter(mark_iter, &name, &fm);
+ if (name != NUL) {
+ ABORTING(set_ref_dict)(fm.fmark.additional_data, copyID);
+ }
+ } while (mark_iter != NULL);
}
// tabpage-local variables
FOR_ALL_TABS(tp) {
- abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, NULL, NULL);
+ ABORTING(set_ref_in_item)(&tp->tp_winvar.di_tv, copyID, NULL, NULL);
}
// global variables
- abort = abort || set_ref_in_ht(&globvarht, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&globvarht, copyID, NULL);
// function-local variables
for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL);
}
// v: vars
- abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
+ ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
+
+ // history items (ShaDa additional elements)
+ if (p_hi) {
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ const void *iter = NULL;
+ do {
+ histentry_T hist;
+ iter = hist_iter(iter, i, false, &hist);
+ if (hist.hisstr != NULL) {
+ ABORTING(set_ref_list)(hist.additional_elements, copyID);
+ }
+ } while (iter != NULL);
+ }
+ }
+
+ // previously used search/substitute patterns (ShaDa additional data)
+ {
+ SearchPattern pat;
+ get_search_pattern(&pat);
+ ABORTING(set_ref_dict)(pat.additional_data, copyID);
+ get_substitute_pattern(&pat);
+ ABORTING(set_ref_dict)(pat.additional_data, copyID);
+ }
+
+ // previously used replacement string
+ {
+ SubReplacementString sub;
+ sub_get_replacement(&sub);
+ ABORTING(set_ref_list)(sub.additional_elements, copyID);
+ }
bool did_free = false;
if (!abort) {
@@ -5631,6 +5699,7 @@ bool garbage_collect(void)
verb_msg((char_u *)_(
"Not enough memory to set references, garbage collection aborted!"));
}
+#undef ABORTING
return did_free;
}
@@ -5690,6 +5759,7 @@ static int free_unref_items(int copyID)
///
/// @returns true if setting references failed somehow.
bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
bool abort = false;
ht_stack_T *ht_stack = NULL;
@@ -5732,6 +5802,7 @@ bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
///
/// @returns true if setting references failed somehow.
bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
bool abort = false;
list_stack_T *list_stack = NULL;
@@ -5772,6 +5843,7 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
/// @returns true if setting references failed somehow.
bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
list_stack_T **list_stack)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
bool abort = false;
@@ -5821,6 +5893,52 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
return abort;
}
+/// Mark all lists and dicts referenced in given mark
+///
+/// @returns true if setting references failed somehow.
+static inline bool set_ref_in_fmark(fmark_T fm, int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (fm.additional_data != NULL
+ && fm.additional_data->dv_copyID != copyID) {
+ fm.additional_data->dv_copyID = copyID;
+ return set_ref_in_ht(&fm.additional_data->dv_hashtab, copyID, NULL);
+ }
+ return false;
+}
+
+/// Mark all lists and dicts referenced in given list and the list itself
+///
+/// @returns true if setting references failed somehow.
+static inline bool set_ref_list(list_T *list, int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (list != NULL) {
+ typval_T tv = (typval_T) {
+ .v_type = VAR_LIST,
+ .vval = { .v_list = list }
+ };
+ return set_ref_in_item(&tv, copyID, NULL, NULL);
+ }
+ return false;
+}
+
+/// Mark all lists and dicts referenced in given dict and the dict itself
+///
+/// @returns true if setting references failed somehow.
+static inline bool set_ref_dict(dict_T *dict, int copyID)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (dict != NULL) {
+ typval_T tv = (typval_T) {
+ .v_type = VAR_DICT,
+ .vval = { .v_dict = dict }
+ };
+ return set_ref_in_item(&tv, copyID, NULL, NULL);
+ }
+ return false;
+}
+
/*
* Allocate an empty header for a dictionary.
*/
@@ -5964,13 +6082,20 @@ void dictitem_free(dictitem_T *item)
xfree(item);
}
-/*
- * Make a copy of dict "d". Shallow if "deep" is FALSE.
- * The refcount of the new dict is set to 1.
- * See item_copy() for "copyID".
- * Returns NULL if orig is NULL or some other failure.
- */
-static dict_T *dict_copy(dict_T *orig, int deep, int copyID)
+/// Make a copy of dictionary
+///
+/// @param[in] conv If non-NULL, then all internal strings will be converted.
+/// @param[in] orig Original dictionary to copy.
+/// @param[in] deep If false, then shallow copy will be done.
+/// @param[in] copyID See var_item_copy().
+///
+/// @return Copied dictionary. May be NULL in case original dictionary is NULL
+/// or some failure happens. The refcount of the new dictionary is set
+/// to 1.
+static dict_T *dict_copy(const vimconv_T *const conv,
+ dict_T *const orig,
+ const bool deep,
+ const int copyID)
{
dictitem_T *di;
int todo;
@@ -5990,10 +6115,21 @@ static dict_T *dict_copy(dict_T *orig, int deep, int copyID)
if (!HASHITEM_EMPTY(hi)) {
--todo;
- di = dictitem_alloc(hi->hi_key);
+ if (conv == NULL || conv->vc_type == CONV_NONE) {
+ di = dictitem_alloc(hi->hi_key);
+ } else {
+ char *const key = (char *) string_convert((vimconv_T *) conv,
+ hi->hi_key, NULL);
+ if (key == NULL) {
+ di = dictitem_alloc(hi->hi_key);
+ } else {
+ di = dictitem_alloc((char_u *) key);
+ xfree(key);
+ }
+ }
if (deep) {
- if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv, deep,
- copyID) == FAIL) {
+ if (var_item_copy(conv, &HI2DI(hi)->di_tv, &di->di_tv, deep,
+ copyID) == FAIL) {
xfree(di);
break;
}
@@ -6305,7 +6441,7 @@ failret:
/// the results.
/// @param firstargname Name of the first argument.
/// @param name Name of the target converter.
-#define DEFINE_VIML_CONV_FUNCTIONS(name, firstargtype, firstargname) \
+#define DEFINE_VIML_CONV_FUNCTIONS(scope, name, firstargtype, firstargname) \
static int name##_convert_one_value(firstargtype firstargname, \
MPConvStack *const mpstack, \
typval_T *const tv, \
@@ -6543,7 +6679,7 @@ name##_convert_one_value_regular_dict: \
return OK; \
} \
\
-static int vim_to_##name(firstargtype firstargname, typval_T *const tv) \
+scope int vim_to_##name(firstargtype firstargname, typval_T *const tv) \
FUNC_ATTR_WARN_UNUSED_RESULT \
{ \
current_copyID += COPYID_INC; \
@@ -6739,7 +6875,7 @@ vim_to_msgpack_error_ret: \
#define CONV_ALLOW_SPECIAL false
-DEFINE_VIML_CONV_FUNCTIONS(string, garray_T *const, gap)
+DEFINE_VIML_CONV_FUNCTIONS(static, string, garray_T *const, gap)
#undef CONV_RECURSE
#define CONV_RECURSE(val, conv_type) \
@@ -6769,7 +6905,7 @@ DEFINE_VIML_CONV_FUNCTIONS(string, garray_T *const, gap)
return OK; \
} while (0)
-DEFINE_VIML_CONV_FUNCTIONS(echo, garray_T *const, gap)
+DEFINE_VIML_CONV_FUNCTIONS(static, echo, garray_T *const, gap)
#undef CONV_STRING
#undef CONV_STR_STRING
@@ -8344,7 +8480,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv)
*/
static void f_copy(typval_T *argvars, typval_T *rettv)
{
- item_copy(&argvars[0], rettv, FALSE, 0);
+ var_item_copy(NULL, &argvars[0], rettv, false, 0);
}
/*
@@ -8513,7 +8649,9 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv)
EMSG(_(e_invarg));
else {
current_copyID += COPYID_INC;
- item_copy(&argvars[0], rettv, TRUE, noref == 0 ? current_copyID : 0);
+ var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
+ ? current_copyID
+ : 0));
}
}
@@ -10482,6 +10620,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
"scrollbind",
"showcmd",
"cmdline_info",
+ "shada",
"signs",
"smartindent",
"startuptime",
@@ -10498,7 +10637,6 @@ static void f_has(typval_T *argvars, typval_T *rettv)
"title",
"user-commands", /* was accidentally included in 5.4 */
"user_commands",
- "viminfo",
"vertsplit",
"virtualedit",
"visual",
@@ -12486,7 +12624,7 @@ static inline bool vim_list_to_buf(const list_T *const list,
#define CONV_ALLOW_SPECIAL true
-DEFINE_VIML_CONV_FUNCTIONS(msgpack, msgpack_packer *const, packer)
+DEFINE_VIML_CONV_FUNCTIONS(, msgpack, msgpack_packer *const, packer)
#undef CONV_STRING
#undef CONV_STR_STRING
@@ -12592,7 +12730,7 @@ static inline ListReaderState init_lrstate(const list_T *const list)
}
/// Convert msgpack object to a VimL one
-static int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
+int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
#define INIT_SPECIAL_DICT(tv, type, val) \
@@ -17353,14 +17491,14 @@ char_u *get_vim_var_str(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
* Get List v: variable value. Caller must take care of reference count when
* needed.
*/
-list_T *get_vim_var_list(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
+list_T *get_vim_var_list(int idx) FUNC_ATTR_PURE
{
return vimvars[idx].vv_list;
}
/// Get Dictionary v: variable value. Caller must take care of reference count
/// when needed.
-dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET
+dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE
{
return vimvars[idx].vv_dict;
}
@@ -18446,14 +18584,28 @@ void copy_tv(typval_T *from, typval_T *to)
}
}
-/*
- * Make a copy of an item.
- * Lists and Dictionaries are also copied. A deep copy if "deep" is set.
- * For deepcopy() "copyID" is zero for a full copy or the ID for when a
- * reference to an already copied list/dict can be used.
- * Returns FAIL or OK.
- */
-static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
+/// Make a copy of an item
+///
+/// Lists and Dictionaries are also copied.
+///
+/// @param[in] conv If not NULL, convert all copied strings.
+/// @param[in] from Value to copy.
+/// @param[out] to Location where to copy to.
+/// @param[in] deep If true, use copy the container and all of the contained
+/// containers (nested).
+/// @param[in] copyID If non-zero then when container is referenced more then
+/// once then copy of it that was already done is used. E.g.
+/// when copying list `list = [list2, list2]` (`list[0] is
+/// list[1]`) var_item_copy with zero copyID will emit
+/// a copy with (`copy[0] isnot copy[1]`), with non-zero it
+/// will emit a copy with (`copy[0] is copy[1]`) like in the
+/// original list. Not use when deep is false.
+int var_item_copy(const vimconv_T *const conv,
+ typval_T *const from,
+ typval_T *const to,
+ const bool deep,
+ const int copyID)
+ FUNC_ATTR_NONNULL_ARG(2, 3)
{
static int recurse = 0;
int ret = OK;
@@ -18467,10 +18619,23 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
switch (from->v_type) {
case VAR_NUMBER:
case VAR_FLOAT:
- case VAR_STRING:
case VAR_FUNC:
copy_tv(from, to);
break;
+ case VAR_STRING:
+ if (conv == NULL || conv->vc_type == CONV_NONE) {
+ copy_tv(from, to);
+ } else {
+ to->v_type = VAR_STRING;
+ to->v_lock = 0;
+ if ((to->vval.v_string = string_convert((vimconv_T *)conv,
+ from->vval.v_string,
+ NULL))
+ == NULL) {
+ to->vval.v_string = (char_u *) xstrdup((char *) from->vval.v_string);
+ }
+ }
+ break;
case VAR_LIST:
to->v_type = VAR_LIST;
to->v_lock = 0;
@@ -18480,8 +18645,9 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
/* use the copy made earlier */
to->vval.v_list = from->vval.v_list->lv_copylist;
++to->vval.v_list->lv_refcount;
- } else
- to->vval.v_list = list_copy(from->vval.v_list, deep, copyID);
+ } else {
+ to->vval.v_list = list_copy(conv, from->vval.v_list, deep, copyID);
+ }
if (to->vval.v_list == NULL)
ret = FAIL;
break;
@@ -18494,13 +18660,14 @@ static int item_copy(typval_T *from, typval_T *to, int deep, int copyID)
/* use the copy made earlier */
to->vval.v_dict = from->vval.v_dict->dv_copydict;
++to->vval.v_dict->dv_refcount;
- } else
- to->vval.v_dict = dict_copy(from->vval.v_dict, deep, copyID);
+ } else {
+ to->vval.v_dict = dict_copy(conv, from->vval.v_dict, deep, copyID);
+ }
if (to->vval.v_dict == NULL)
ret = FAIL;
break;
default:
- EMSG2(_(e_intern2), "item_copy()");
+ EMSG2(_(e_intern2), "var_item_copy()");
ret = FAIL;
}
--recurse;
@@ -20710,109 +20877,64 @@ static var_flavour_T var_flavour(char_u *varname)
if (ASCII_ISUPPER(*p)) {
while (*(++p))
- if (ASCII_ISLOWER(*p))
+ if (ASCII_ISLOWER(*p)) {
return VAR_FLAVOUR_SESSION;
- return VAR_FLAVOUR_VIMINFO;
- } else
+ }
+ return VAR_FLAVOUR_SHADA;
+ } else {
return VAR_FLAVOUR_DEFAULT;
+ }
}
-/*
- * Restore global vars that start with a capital from the viminfo file
- */
-int read_viminfo_varlist(vir_T *virp, int writing)
-{
- char_u *tab;
- int type = VAR_NUMBER;
- typval_T tv;
-
- if (!writing && (find_viminfo_parameter('!') != NULL)) {
- tab = vim_strchr(virp->vir_line + 1, '\t');
- if (tab != NULL) {
- *tab++ = NUL; /* isolate the variable name */
- switch (*tab) {
- case 'S': type = VAR_STRING; break;
- case 'F': type = VAR_FLOAT; break;
- case 'D': type = VAR_DICT; break;
- case 'L': type = VAR_LIST; break;
- }
-
- tab = vim_strchr(tab, '\t');
- if (tab != NULL) {
- tv.v_type = type;
- if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST)
- tv.vval.v_string = viminfo_readstring(virp,
- (int)(tab - virp->vir_line + 1), TRUE);
- else if (type == VAR_FLOAT)
- (void)string2float(tab + 1, &tv.vval.v_float);
- else
- tv.vval.v_number = atol((char *)tab + 1);
- if (type == VAR_DICT || type == VAR_LIST) {
- typval_T *etv = eval_expr(tv.vval.v_string, NULL);
-
- if (etv == NULL)
- /* Failed to parse back the dict or list, use it as a
- * string. */
- tv.v_type = VAR_STRING;
- else {
- xfree(tv.vval.v_string);
- tv = *etv;
- xfree(etv);
- }
- }
-
- set_var(virp->vir_line + 1, &tv, FALSE);
-
- if (tv.v_type == VAR_STRING)
- xfree(tv.vval.v_string);
- else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST)
- clear_tv(&tv);
- }
+/// Iterate over global variables
+///
+/// @warning No modifications to global variable dictionary must be performed
+/// while iteration is in progress.
+///
+/// @param[in] iter Iterator. Pass NULL to start iteration.
+/// @param[out] name Variable name.
+/// @param[out] rettv Variable value.
+///
+/// @return Pointer that needs to be passed to next `var_shada_iter` invocation
+/// or NULL to indicate that iteration is over.
+const void *var_shada_iter(const void *const iter, const char **const name,
+ typval_T *rettv)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
+{
+ const hashitem_T *hi;
+ const hashitem_T *hifirst = globvarht.ht_array;
+ const size_t hinum = (size_t) globvarht.ht_mask + 1;
+ *name = NULL;
+ if (iter == NULL) {
+ hi = globvarht.ht_array;
+ while ((size_t) (hi - hifirst) < hinum
+ && (HASHITEM_EMPTY(hi)
+ || var_flavour(HI2DI(hi)->di_key) != VAR_FLAVOUR_SHADA)) {
+ hi++;
+ }
+ if ((size_t) (hi - hifirst) == hinum) {
+ return NULL;
+ }
+ } else {
+ hi = (const hashitem_T *) iter;
+ }
+ *name = (char *) HI2DI(hi)->di_key;
+ copy_tv(&(HI2DI(hi)->di_tv), rettv);
+ while ((size_t) (++hi - hifirst) < hinum) {
+ if (!HASHITEM_EMPTY(hi)
+ && var_flavour(HI2DI(hi)->di_key) == VAR_FLAVOUR_SHADA) {
+ return hi;
}
}
-
- return viminfo_readline(virp);
+ return NULL;
}
-/*
- * Write global vars that start with a capital to the viminfo file
- */
-void write_viminfo_varlist(FILE *fp)
+void var_set_global(const char *const name, typval_T vartv)
{
- hashitem_T *hi;
- dictitem_T *this_var;
- int todo;
- char *s;
- char_u *p;
-
- if (find_viminfo_parameter('!') == NULL)
- return;
-
- fputs(_("\n# global variables:\n"), fp);
-
- todo = (int)globvarht.ht_used;
- for (hi = globvarht.ht_array; todo > 0; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- this_var = HI2DI(hi);
- if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) {
- switch (this_var->di_tv.v_type) {
- case VAR_STRING: s = "STR"; break;
- case VAR_NUMBER: s = "NUM"; break;
- case VAR_FLOAT: s = "FLO"; break;
- case VAR_DICT: s = "DIC"; break;
- case VAR_LIST: s = "LIS"; break;
- default: continue;
- }
- fprintf(fp, "!%s\t%s\t", this_var->di_key, s);
- p = (char_u *) echo_string(&this_var->di_tv, NULL);
- if (p != NULL) {
- viminfo_writestring(fp, p);
- }
- xfree(p);
- }
- }
- }
+ funccall_T *const saved_current_funccal = current_funccal;
+ current_funccal = NULL;
+ set_var((char_u *) name, &vartv, false);
+ current_funccal = saved_current_funccal;
}
int store_session_globals(FILE *fd)
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 8f065eda33..864daed716 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -1,6 +1,8 @@
#ifndef NVIM_EVAL_H
#define NVIM_EVAL_H
+#include <msgpack.h>
+
#include "nvim/profile.h"
/* Defines for Vim variables. These must match vimvars[] in eval.c! */
@@ -72,6 +74,8 @@ enum {
/// Maximum number of function arguments
#define MAX_FUNC_ARGS 20
+int vim_to_msgpack(msgpack_packer *const, typval_T *const);
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
#endif
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index a8a8acd048..373f1e6278 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -2,6 +2,7 @@
#define NVIM_EVAL_DEFS_H
#include <limits.h>
+#include <stddef.h>
#include "nvim/hashtab.h"
@@ -132,4 +133,16 @@ typedef struct list_stack_S {
struct list_stack_S *prev;
} list_stack_T;
+// In a hashtab item "hi_key" points to "di_key" in a dictitem.
+// This avoids adding a pointer to the hashtab item.
+
+/// Convert a dictitem pointer to a hashitem key pointer
+#define DI2HIKEY(di) ((di)->di_key)
+
+/// Convert a hashitem key pointer to a dictitem pointer
+#define HIKEY2DI(p) ((dictitem_T *)(p - offsetof(dictitem_T, di_key)))
+
+/// Convert a hashitem pointer to a dictitem pointer
+#define HI2DI(hi) HIKEY2DI((hi)->hi_key)
+
#endif // NVIM_EVAL_DEFS_H
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 81abf2fa63..5db3880026 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -67,6 +67,7 @@
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/input.h"
+#include "nvim/os/time.h"
/*
* Struct to hold the sign properties.
@@ -1391,550 +1392,6 @@ void append_redir(char_u *buf, int buflen, char_u *opt, char_u *fname)
(char *)opt, (char *)fname);
}
-
-static int viminfo_errcnt;
-
-static int no_viminfo(void)
-{
- /* "vim -i NONE" does not read or write a viminfo file */
- return use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0;
-}
-
-/*
- * Report an error for reading a viminfo file.
- * Count the number of errors. When there are more than 10, return TRUE.
- */
-int viminfo_error(char *errnum, char *message, char_u *line)
-{
- vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "),
- errnum, message);
- STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1);
- if (IObuff[STRLEN(IObuff) - 1] == '\n')
- IObuff[STRLEN(IObuff) - 1] = NUL;
- emsg(IObuff);
- if (++viminfo_errcnt >= 10) {
- EMSG(_("E136: viminfo: Too many errors, skipping rest of file"));
- return TRUE;
- }
- return FALSE;
-}
-
-/*
- * read_viminfo() -- Read the viminfo file. Registers etc. which are already
- * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb
- */
-int
-read_viminfo (
- char_u *file, /* file name or NULL to use default name */
- int flags /* VIF_WANT_INFO et al. */
-)
-{
- FILE *fp;
- char_u *fname;
-
- if (no_viminfo())
- return FAIL;
-
- fname = viminfo_filename(file); /* get file name in allocated buffer */
- fp = mch_fopen((char *)fname, READBIN);
-
- if (p_verbose > 0) {
- verbose_enter();
- smsg(_("Reading viminfo file \"%s\"%s%s%s"),
- fname,
- (flags & VIF_WANT_INFO) ? _(" info") : "",
- (flags & VIF_WANT_MARKS) ? _(" marks") : "",
- (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "",
- fp == NULL ? _(" FAILED") : "");
- verbose_leave();
- }
-
- xfree(fname);
- if (fp == NULL)
- return FAIL;
-
- viminfo_errcnt = 0;
- do_viminfo(fp, NULL, flags);
-
- fclose(fp);
- return OK;
-}
-
-/*
- * Write the viminfo file. The old one is read in first so that effectively a
- * merge of current info and old info is done. This allows multiple vims to
- * run simultaneously, without losing any marks etc.
- * If "forceit" is TRUE, then the old file is not read in, and only internal
- * info is written to the file.
- */
-void write_viminfo(char_u *file, int forceit)
-{
- char_u *fname;
- FILE *fp_in = NULL; /* input viminfo file, if any */
- FILE *fp_out = NULL; /* output viminfo file */
- char_u *tempname = NULL; /* name of temp viminfo file */
- char_u *wp;
-#if defined(UNIX)
- mode_t umask_save;
-#endif
-
- if (no_viminfo())
- return;
-
- fname = viminfo_filename(file); /* may set to default if NULL */
-
- fp_in = mch_fopen((char *)fname, READBIN);
- if (fp_in == NULL) {
- /* if it does exist, but we can't read it, don't try writing */
- if (os_file_exists(fname))
- goto end;
-#if defined(UNIX)
- /*
- * For Unix we create the .viminfo non-accessible for others,
- * because it may contain text from non-accessible documents.
- */
- umask_save = umask(077);
-#endif
- fp_out = mch_fopen((char *)fname, WRITEBIN);
-#if defined(UNIX)
- (void)umask(umask_save);
-#endif
- } else {
- /*
- * There is an existing viminfo file. Create a temporary file to
- * write the new viminfo into, in the same directory as the
- * existing viminfo file, which will be renamed later.
- */
-#ifdef UNIX
- /*
- * For Unix we check the owner of the file. It's not very nice to
- * overwrite a user's viminfo file after a "su root", with a
- * viminfo file that the user can't read.
- */
-
- FileInfo old_info; // FileInfo of existing viminfo file
- if (os_fileinfo((char *)fname, &old_info)
- && getuid() != ROOT_UID
- && !(old_info.stat.st_uid == getuid()
- ? (old_info.stat.st_mode & 0200)
- : (old_info.stat.st_gid == getgid()
- ? (old_info.stat.st_mode & 0020)
- : (old_info.stat.st_mode & 0002)))) {
- int tt = msg_didany;
-
- /* avoid a wait_return for this message, it's annoying */
- EMSG2(_("E137: Viminfo file is not writable: %s"), fname);
- msg_didany = tt;
- fclose(fp_in);
- goto end;
- }
-#endif
-
- // Make tempname
- tempname = (char_u *)modname((char *)fname, ".tmp", FALSE);
- if (tempname != NULL) {
- /*
- * Check if tempfile already exists. Never overwrite an
- * existing file!
- */
- if (os_file_exists(tempname)) {
- /*
- * Try another name. Change one character, just before
- * the extension.
- */
- wp = tempname + STRLEN(tempname) - 5;
- if (wp < path_tail(tempname)) /* empty file name? */
- wp = path_tail(tempname);
- for (*wp = 'z'; os_file_exists(tempname); --*wp) {
- /*
- * They all exist? Must be something wrong! Don't
- * write the viminfo file then.
- */
- if (*wp == 'a') {
- xfree(tempname);
- tempname = NULL;
- break;
- }
- }
- }
- }
-
- if (tempname != NULL) {
- int fd;
-
- /* Use os_open() to be able to use O_NOFOLLOW and set file
- * protection:
- * Unix: same as original file, but strip s-bit. Reset umask to
- * avoid it getting in the way.
- * Others: r&w for user only. */
-# ifdef UNIX
- umask_save = umask(0);
- fd = os_open((char *)tempname,
- O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW,
- (int)((old_info.stat.st_mode & 0777) | 0600));
- (void)umask(umask_save);
-# else
- fd = os_open((char *)tempname,
- O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600);
-# endif
- if (fd < 0)
- fp_out = NULL;
- else
- fp_out = fdopen(fd, WRITEBIN);
-
- /*
- * If we can't create in the same directory, try creating a
- * "normal" temp file.
- */
- if (fp_out == NULL) {
- xfree(tempname);
- if ((tempname = vim_tempname()) != NULL)
- fp_out = mch_fopen((char *)tempname, WRITEBIN);
- }
-
-#ifdef UNIX
- /*
- * Make sure the owner can read/write it. This only works for
- * root.
- */
- if (fp_out != NULL) {
- os_fchown(fileno(fp_out), old_info.stat.st_uid, old_info.stat.st_gid);
- }
-#endif
- }
- }
-
- /*
- * Check if the new viminfo file can be written to.
- */
- if (fp_out == NULL) {
- EMSG2(_("E138: Can't write viminfo file %s!"),
- (fp_in == NULL || tempname == NULL) ? fname : tempname);
- if (fp_in != NULL)
- fclose(fp_in);
- goto end;
- }
-
- if (p_verbose > 0) {
- verbose_enter();
- smsg(_("Writing viminfo file \"%s\""), fname);
- verbose_leave();
- }
-
- viminfo_errcnt = 0;
- do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS));
-
- fclose(fp_out); /* errors are ignored !? */
- if (fp_in != NULL) {
- fclose(fp_in);
-
- /* In case of an error keep the original viminfo file. Otherwise
- * rename the newly written file. Give an error if that fails. */
- if (viminfo_errcnt == 0 && vim_rename(tempname, fname) == -1) {
- viminfo_errcnt++;
- EMSG2(_("E886: Can't rename viminfo file to %s!"), fname);
- }
- if (viminfo_errcnt > 0) {
- os_remove((char *)tempname);
- }
- }
-
-end:
- xfree(fname);
- xfree(tempname);
-}
-
-/*
- * Get the viminfo file name to use.
- * If "file" is given and not empty, use it (has already been expanded by
- * cmdline functions).
- * Otherwise use "-i file_name", value from 'viminfo' or the default, and
- * expand environment variables.
- * Returns an allocated string.
- */
-static char_u *viminfo_filename(char_u *file)
-{
- if (file == NULL || *file == NUL) {
- if (use_viminfo != NULL)
- file = use_viminfo;
- else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) {
-#ifdef VIMINFO_FILE2
- // don't use $HOME when not defined (turned into "c:/"!).
- if (!os_env_exists("HOME")) {
- // don't use $VIM when not available.
- expand_env((char_u *)"$VIM", NameBuff, MAXPATHL);
- if (STRCMP("$VIM", NameBuff) != 0) /* $VIM was expanded */
- file = (char_u *)VIMINFO_FILE2;
- else
- file = (char_u *)VIMINFO_FILE;
- } else
-#endif
- file = (char_u *)VIMINFO_FILE;
- }
- expand_env(file, NameBuff, MAXPATHL);
- file = NameBuff;
- }
- return vim_strsave(file);
-}
-
-/*
- * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo().
- */
-static void do_viminfo(FILE *fp_in, FILE *fp_out, int flags)
-{
- int count = 0;
- int eof = FALSE;
- vir_T vir;
- int merge = FALSE;
-
- vir.vir_line = xmalloc(LSIZE);
- vir.vir_fd = fp_in;
- vir.vir_conv.vc_type = CONV_NONE;
-
- if (fp_in != NULL) {
- if (flags & VIF_WANT_INFO) {
- eof = read_viminfo_up_to_marks(&vir,
- flags & VIF_FORCEIT, fp_out != NULL);
- merge = TRUE;
- } else if (flags != 0)
- /* Skip info, find start of marks */
- while (!(eof = viminfo_readline(&vir))
- && vir.vir_line[0] != '>')
- ;
- }
- if (fp_out != NULL) {
- /* Write the info: */
- fprintf(fp_out, _("# This viminfo file was generated by Nvim %s.\n"),
- mediumVersion);
- fputs(_("# You may edit it if you're careful!\n\n"), fp_out);
- fputs(_("# Value of 'encoding' when this file was written\n"), fp_out);
- fprintf(fp_out, "*encoding=%s\n\n", p_enc);
- write_viminfo_search_pattern(fp_out);
- write_viminfo_sub_string(fp_out);
- write_viminfo_history(fp_out, merge);
- write_viminfo_registers(fp_out);
- write_viminfo_varlist(fp_out);
- write_viminfo_filemarks(fp_out);
- write_viminfo_bufferlist(fp_out);
- count = write_viminfo_marks(fp_out);
- }
- if (fp_in != NULL
- && (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT)))
- copy_viminfo_marks(&vir, fp_out, count, eof, flags);
-
- xfree(vir.vir_line);
- if (vir.vir_conv.vc_type != CONV_NONE)
- convert_setup(&vir.vir_conv, NULL, NULL);
-}
-
-/*
- * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the
- * first part of the viminfo file which contains everything but the marks that
- * are local to a file. Returns TRUE when end-of-file is reached. -- webb
- */
-static int read_viminfo_up_to_marks(vir_T *virp, int forceit, int writing)
-{
- int eof;
-
- prepare_viminfo_history(forceit ? 9999 : 0, writing);
- eof = viminfo_readline(virp);
- while (!eof && virp->vir_line[0] != '>') {
- switch (virp->vir_line[0]) {
- /* Characters reserved for future expansion, ignored now */
- case '+': /* "+40 /path/dir file", for running vim without args */
- case '|': /* to be defined */
- case '^': /* to be defined */
- case '<': /* long line - ignored */
- /* A comment or empty line. */
- case NUL:
- case '\r':
- case '\n':
- case '#':
- eof = viminfo_readline(virp);
- break;
- case '*': /* "*encoding=value" */
- eof = viminfo_encoding(virp);
- break;
- case '!': /* global variable */
- eof = read_viminfo_varlist(virp, writing);
- break;
- case '%': /* entry for buffer list */
- eof = read_viminfo_bufferlist(virp, writing);
- break;
- case '"':
- eof = read_viminfo_register(virp, forceit);
- break;
- case '/': /* Search string */
- case '&': /* Substitute search string */
- case '~': /* Last search string, followed by '/' or '&' */
- eof = read_viminfo_search_pattern(virp, forceit);
- break;
- case '$':
- eof = read_viminfo_sub_string(virp, forceit);
- break;
- case ':':
- case '?':
- case '=':
- case '@':
- eof = read_viminfo_history(virp, writing);
- break;
- case '-':
- case '\'':
- eof = read_viminfo_filemark(virp, forceit);
- break;
- default:
- if (viminfo_error("E575: ", _("Illegal starting char"),
- virp->vir_line))
- eof = TRUE;
- else
- eof = viminfo_readline(virp);
- break;
- }
- }
-
- /* Finish reading history items. */
- if (!writing)
- finish_viminfo_history();
-
- /* Change file names to buffer numbers for fmarks. */
- FOR_ALL_BUFFERS(buf) {
- fmarks_check_names(buf);
- }
-
- return eof;
-}
-
-/*
- * Compare the 'encoding' value in the viminfo file with the current value of
- * 'encoding'. If different and the 'c' flag is in 'viminfo', setup for
- * conversion of text with iconv() in viminfo_readstring().
- */
-static int viminfo_encoding(vir_T *virp)
-{
- char_u *p;
- int i;
-
- if (get_viminfo_parameter('c') != 0) {
- p = vim_strchr(virp->vir_line, '=');
- if (p != NULL) {
- /* remove trailing newline */
- ++p;
- for (i = 0; vim_isprintc(p[i]); ++i)
- ;
- p[i] = NUL;
-
- convert_setup(&virp->vir_conv, p, p_enc);
- }
- }
- return viminfo_readline(virp);
-}
-
-/*
- * Read a line from the viminfo file.
- * Returns TRUE for end-of-file;
- */
-int viminfo_readline(vir_T *virp)
-{
- return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
-}
-
-/*
- * check string read from viminfo file
- * remove '\n' at the end of the line
- * - replace CTRL-V CTRL-V with CTRL-V
- * - replace CTRL-V 'n' with '\n'
- *
- * Check for a long line as written by viminfo_writestring().
- *
- * Return the string in allocated memory.
- */
-char_u *
-viminfo_readstring (
- vir_T *virp,
- int off, /* offset for virp->vir_line */
- int convert /* convert the string */
-)
- FUNC_ATTR_NONNULL_RET
-{
- char_u *retval;
- char_u *s, *d;
-
- if (virp->vir_line[off] == Ctrl_V && ascii_isdigit(virp->vir_line[off + 1])) {
- ssize_t len = atol((char *)virp->vir_line + off + 1);
- retval = xmalloc(len);
- // TODO(philix): change type of vim_fgets() size argument to size_t
- (void)vim_fgets(retval, (int)len, virp->vir_fd);
- s = retval + 1; /* Skip the leading '<' */
- } else {
- retval = vim_strsave(virp->vir_line + off);
- s = retval;
- }
-
- /* Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. */
- d = retval;
- while (*s != NUL && *s != '\n') {
- if (s[0] == Ctrl_V && s[1] != NUL) {
- if (s[1] == 'n')
- *d++ = '\n';
- else
- *d++ = Ctrl_V;
- s += 2;
- } else
- *d++ = *s++;
- }
- *d = NUL;
-
- if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) {
- d = string_convert(&virp->vir_conv, retval, NULL);
- if (d != NULL) {
- xfree(retval);
- retval = d;
- }
- }
-
- return retval;
-}
-
-/*
- * write string to viminfo file
- * - replace CTRL-V with CTRL-V CTRL-V
- * - replace '\n' with CTRL-V 'n'
- * - add a '\n' at the end
- *
- * For a long line:
- * - write " CTRL-V <length> \n " in first line
- * - write " < <string> \n " in second line
- */
-void viminfo_writestring(FILE *fd, char_u *p)
-{
- int c;
- char_u *s;
- int len = 0;
-
- for (s = p; *s != NUL; ++s) {
- if (*s == Ctrl_V || *s == '\n')
- ++len;
- ++len;
- }
-
- /* If the string will be too long, write its length and put it in the next
- * line. Take into account that some room is needed for what comes before
- * the string (e.g., variable name). Add something to the length for the
- * '<', NL and trailing NUL. */
- if (len > LSIZE / 2)
- fprintf(fd, "\026%d\n<", len + 3);
-
- while ((c = *p++) != NUL) {
- if (c == Ctrl_V || c == '\n') {
- putc(Ctrl_V, fd);
- if (c == '\n')
- c = 'n';
- }
- putc(c, fd);
- }
- putc('\n', fd);
-}
-
void print_line_no_prefix(linenr_T lnum, int use_number, int list)
{
char_u numbuf[30];
@@ -3364,8 +2821,33 @@ int check_secure(void)
return FALSE;
}
-static char_u *old_sub = NULL; /* previous substitute pattern */
-static int global_need_beginline; /* call beginline() after ":g" */
+/// Previous substitute replacement string
+static SubReplacementString old_sub = {NULL, 0, NULL};
+
+static int global_need_beginline; // call beginline() after ":g"
+
+/// Get old substitute replacement string
+///
+/// @param[out] ret_sub Location where old string will be saved.
+void sub_get_replacement(SubReplacementString *const ret_sub)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *ret_sub = old_sub;
+}
+
+/// Set substitute string and timestamp
+///
+/// @warning `sub` must be in allocated memory. It is not copied.
+///
+/// @param[in] sub New replacement string.
+void sub_set_replacement(SubReplacementString sub)
+{
+ xfree(old_sub.sub);
+ if (sub.additional_elements != old_sub.additional_elements) {
+ list_unref(old_sub.additional_elements);
+ }
+ old_sub = sub;
+}
/* do_sub()
*
@@ -3473,16 +2955,19 @@ void do_sub(exarg_T *eap)
}
if (!eap->skip) {
- xfree(old_sub);
- old_sub = vim_strsave(sub);
+ sub_set_replacement((SubReplacementString) {
+ .sub = xstrdup((char *) sub),
+ .timestamp = os_time(),
+ .additional_elements = NULL,
+ });
}
} else if (!eap->skip) { /* use previous pattern and substitution */
- if (old_sub == NULL) { /* there is no previous command */
+ if (old_sub.sub == NULL) { /* there is no previous command */
EMSG(_(e_nopresub));
return;
}
pat = NULL; /* search_regcomp() will use previous pattern */
- sub = old_sub;
+ sub = (char_u *) old_sub.sub;
/* Vi compatibility quirk: repeating with ":s" keeps the cursor in the
* last column after using "$". */
@@ -4501,27 +3986,10 @@ void global_exe(char_u *cmd)
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
}
-int read_viminfo_sub_string(vir_T *virp, int force)
-{
- if (force)
- xfree(old_sub);
- if (force || old_sub == NULL)
- old_sub = viminfo_readstring(virp, 1, TRUE);
- return viminfo_readline(virp);
-}
-
-void write_viminfo_sub_string(FILE *fp)
-{
- if (get_viminfo_parameter('/') != 0 && old_sub != NULL) {
- fputs(_("\n# Last Substitute String:\n$"), fp);
- viminfo_writestring(fp, old_sub);
- }
-}
-
#if defined(EXITFREE)
void free_old_sub(void)
{
- xfree(old_sub);
+ sub_set_replacement((SubReplacementString) {NULL, 0, NULL});
}
#endif
diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h
index eabbbd15ac..721145efd8 100644
--- a/src/nvim/ex_cmds.h
+++ b/src/nvim/ex_cmds.h
@@ -3,6 +3,9 @@
#include <stdbool.h>
+#include "nvim/os/time.h"
+#include "nvim/eval_defs.h"
+
/* flags for do_ecmd() */
#define ECMD_HIDE 0x01 /* don't free the current buffer */
#define ECMD_SET_HELP 0x02 /* set b_help flag of (new) buffer before
@@ -16,11 +19,12 @@
#define ECMD_LAST (linenr_T)-1 /* use last position in all files */
#define ECMD_ONE (linenr_T)1 /* use first line */
-/* flags for read_viminfo() and children */
-#define VIF_WANT_INFO 1 /* load non-mark info */
-#define VIF_WANT_MARKS 2 /* load file marks */
-#define VIF_FORCEIT 4 /* overwrite info already read */
-#define VIF_GET_OLDFILES 8 /* load v:oldfiles */
+/// Previous :substitute replacement string definition
+typedef struct {
+ char *sub; ///< Previous replacement string.
+ Timestamp timestamp; ///< Time when it was last set.
+ list_T *additional_elements; ///< Additional data left from ShaDa file.
+} SubReplacementString;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.h.generated.h"
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 5221554306..77f7dba81b 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -2120,6 +2120,12 @@ return {
func='ex_wrongmodifier',
},
{
+ command='rshada',
+ flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_shada',
+ },
+ {
command='runtime',
flags=bit.bor(BANG, NEEDARG, FILES, TRLBAR, SBOXOK, CMDWIN),
addr_type=ADDR_LINES,
@@ -2153,7 +2159,7 @@ return {
command='rviminfo',
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_viminfo',
+ func='ex_shada',
},
{
command='substitute',
@@ -3032,6 +3038,12 @@ return {
func='ex_wsverb',
},
{
+ command='wshada',
+ flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_shada',
+ },
+ {
command='wundo',
flags=bit.bor(BANG, NEEDARG, FILE1),
addr_type=ADDR_LINES,
@@ -3041,7 +3053,7 @@ return {
command='wviminfo',
flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_viminfo',
+ func='ex_shada',
},
{
command='xit',
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index f7162896ff..a9262ca6ea 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -75,6 +75,7 @@
#include "nvim/mouse.h"
#include "nvim/event/rstream.h"
#include "nvim/event/wstream.h"
+#include "nvim/shada.h"
static int quitmore = 0;
static int ex_pressedreturn = FALSE;
@@ -9139,22 +9140,21 @@ int put_line(FILE *fd, char *s)
}
/*
- * ":rviminfo" and ":wviminfo".
+ * ":rshada" and ":wshada".
*/
-static void ex_viminfo(exarg_T *eap)
+static void ex_shada(exarg_T *eap)
{
- char_u *save_viminfo;
+ char_u *save_shada;
- save_viminfo = p_viminfo;
- if (*p_viminfo == NUL)
- p_viminfo = (char_u *)"'100";
- if (eap->cmdidx == CMD_rviminfo) {
- if (read_viminfo(eap->arg, VIF_WANT_INFO | VIF_WANT_MARKS
- | (eap->forceit ? VIF_FORCEIT : 0)) == FAIL)
- EMSG(_("E195: Cannot open viminfo file for reading"));
- } else
- write_viminfo(eap->arg, eap->forceit);
- p_viminfo = save_viminfo;
+ save_shada = p_shada;
+ if (*p_shada == NUL)
+ p_shada = (char_u *)"'100";
+ if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) {
+ (void) shada_read_everything((char *) eap->arg, eap->forceit, false);
+ } else {
+ shada_write_file((char *) eap->arg, eap->forceit);
+ }
+ p_shada = save_shada;
}
/*
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 9739090d7c..24e31b1ed7 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -65,6 +65,7 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/event/loop.h"
+#include "nvim/os/time.h"
/*
* Variables shared between getcmdline(), redrawcmdline() and others.
@@ -100,12 +101,6 @@ static int cmd_showtail; /* Only show path tail in lists ? */
static int new_cmdpos; /* position set by set_cmdline_pos() */
-typedef struct hist_entry {
- int hisnum; /* identifying number */
- int viminfo; /* when TRUE hisstr comes from viminfo */
- char_u *hisstr; /* actual entry, separator char after the NUL */
-} histentry_T;
-
/*
* Type used by call_user_expand_func
*/
@@ -4230,12 +4225,10 @@ void init_history(void)
// delete entries that don't fit in newlen, if any
for (int i = 0; i < i1; i++) {
- xfree(history[type][i].hisstr);
- history[type][i].hisstr = NULL;
+ hist_free_entry(history[type] + i);
}
for (int i = i1 + l1; i < i2; i++) {
- xfree(history[type][i].hisstr);
- history[type][i].hisstr = NULL;
+ hist_free_entry(history[type] + i);
}
}
@@ -4253,11 +4246,18 @@ void init_history(void)
}
}
-static void clear_hist_entry(histentry_T *hisptr)
+static inline void hist_free_entry(histentry_T *hisptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ xfree(hisptr->hisstr);
+ list_unref(hisptr->additional_elements);
+ clear_hist_entry(hisptr);
+}
+
+static inline void clear_hist_entry(histentry_T *hisptr)
+ FUNC_ATTR_NONNULL_ALL
{
- hisptr->hisnum = 0;
- hisptr->viminfo = FALSE;
- hisptr->hisstr = NULL;
+ memset(hisptr, 0, sizeof(*hisptr));
}
/*
@@ -4268,9 +4268,8 @@ static int
in_history (
int type,
char_u *str,
- int move_to_front, /* Move the entry to the front if it exists */
- int sep,
- int writing /* ignore entries read from viminfo */
+ int move_to_front, // Move the entry to the front if it exists
+ int sep
)
{
int i;
@@ -4288,7 +4287,6 @@ in_history (
* well. */
p = history[type][i].hisstr;
if (STRCMP(str, p) == 0
- && !(writing && history[type][i].viminfo)
&& (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
if (!move_to_front)
return TRUE;
@@ -4300,6 +4298,7 @@ in_history (
} while (i != hisidx[type]);
if (last_i >= 0) {
+ list_T *const list = history[type][i].additional_elements;
str = history[type][i].hisstr;
while (i != hisidx[type]) {
if (++i >= hislen)
@@ -4307,12 +4306,14 @@ in_history (
history[type][last_i] = history[type][i];
last_i = i;
}
+ list_unref(list);
history[type][i].hisnum = ++hisnum[type];
- history[type][i].viminfo = FALSE;
history[type][i].hisstr = str;
- return TRUE;
+ history[type][i].timestamp = os_time();
+ history[type][i].additional_elements = NULL;
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -4372,27 +4373,27 @@ add_to_history (
if (maptick == last_maptick) {
/* Current line is from the same mapping, remove it */
hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
- xfree(hisptr->hisstr);
- clear_hist_entry(hisptr);
+ hist_free_entry(hisptr);
--hisnum[histype];
if (--hisidx[HIST_SEARCH] < 0)
hisidx[HIST_SEARCH] = hislen - 1;
}
last_maptick = -1;
}
- if (!in_history(histype, new_entry, TRUE, sep, FALSE)) {
+ if (!in_history(histype, new_entry, true, sep)) {
if (++hisidx[histype] == hislen)
hisidx[histype] = 0;
hisptr = &history[histype][hisidx[histype]];
- xfree(hisptr->hisstr);
+ hist_free_entry(hisptr);
/* Store the separator after the NUL of the string. */
len = (int)STRLEN(new_entry);
hisptr->hisstr = vim_strnsave(new_entry, len + 2);
+ hisptr->timestamp = os_time();
+ hisptr->additional_elements = NULL;
hisptr->hisstr[len + 1] = sep;
hisptr->hisnum = ++hisnum[histype];
- hisptr->viminfo = FALSE;
if (histype == HIST_SEARCH && in_map)
last_maptick = maptick;
}
@@ -4545,23 +4546,21 @@ char_u *get_history_entry(int histype, int idx)
return (char_u *)"";
}
-/*
- * Clear all entries of a history.
- * "histype" may be one of the HIST_ values.
- */
-int clr_history(int histype)
+/// Clear all entries in a history
+///
+/// @param[in] histype One of the HIST_ values.
+///
+/// @return OK if there was something to clean and histype was one of HIST_
+/// values, FAIL otherwise.
+int clr_history(const int histype)
{
- int i;
- histentry_T *hisptr;
-
if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
- hisptr = history[histype];
- for (i = hislen; i--; ) {
- xfree(hisptr->hisstr);
- clear_hist_entry(hisptr);
+ histentry_T *hisptr = history[histype];
+ for (int i = hislen; i--; hisptr++) {
+ hist_free_entry(hisptr);
}
- hisidx[histype] = -1; /* mark history as cleared */
- hisnum[histype] = 0; /* reset identifier counter */
+ hisidx[histype] = -1; // mark history as cleared
+ hisnum[histype] = 0; // reset identifier counter
return OK;
}
return FAIL;
@@ -4578,7 +4577,7 @@ int del_history_entry(int histype, char_u *str)
int idx;
int i;
int last;
- int found = FALSE;
+ bool found = false;
regmatch.regprog = NULL;
regmatch.rm_ic = FALSE; /* always match case */
@@ -4595,9 +4594,8 @@ int del_history_entry(int histype, char_u *str)
if (hisptr->hisstr == NULL)
break;
if (vim_regexec(&regmatch, hisptr->hisstr, (colnr_T)0)) {
- found = TRUE;
- xfree(hisptr->hisstr);
- clear_hist_entry(hisptr);
+ found = true;
+ hist_free_entry(hisptr);
} else {
if (i != last) {
history[histype][last] = *hisptr;
@@ -4628,7 +4626,7 @@ int del_history_idx(int histype, int idx)
if (i < 0)
return FALSE;
idx = hisidx[histype];
- xfree(history[histype][i].hisstr);
+ hist_free_entry(&history[histype][i]);
/* When deleting the last added search string in a mapping, reset
* last_maptick, so that the last added search string isn't deleted again.
@@ -4641,9 +4639,10 @@ int del_history_idx(int histype, int idx)
history[histype][i] = history[histype][j];
i = j;
}
- clear_hist_entry(&history[histype][i]);
- if (--i < 0)
+ clear_hist_entry(&history[histype][idx]);
+ if (--i < 0) {
i += hislen;
+ }
hisidx[histype] = i;
return TRUE;
}
@@ -4762,248 +4761,31 @@ void ex_history(exarg_T *eap)
}
}
-/*
- * Buffers for history read from a viminfo file. Only valid while reading.
- */
-static char_u **viminfo_history[HIST_COUNT] = {NULL, NULL, NULL, NULL};
-static int viminfo_hisidx[HIST_COUNT] = {0, 0, 0, 0};
-static int viminfo_hislen[HIST_COUNT] = {0, 0, 0, 0};
-static int viminfo_add_at_front = FALSE;
-
-
-/*
- * Translate a history type number to the associated character.
- */
-static int
-hist_type2char (
- int type,
- int use_question /* use '?' instead of '/' */
-)
+/// Translate a history type number to the associated character
+int hist_type2char(int type)
+ FUNC_ATTR_CONST
{
- if (type == HIST_CMD)
- return ':';
- if (type == HIST_SEARCH) {
- if (use_question)
- return '?';
- else
+ switch (type) {
+ case HIST_CMD: {
+ return ':';
+ }
+ case HIST_SEARCH: {
return '/';
- }
- if (type == HIST_EXPR)
- return '=';
- return '@';
-}
-
-/*
- * Prepare for reading the history from the viminfo file.
- * This allocates history arrays to store the read history lines.
- */
-void prepare_viminfo_history(int asklen, int writing)
-{
- int i;
- int num;
-
- init_history();
- viminfo_add_at_front = (asklen != 0 && !writing);
- if (asklen > hislen)
- asklen = hislen;
-
- for (int type = 0; type < HIST_COUNT; ++type) {
- /* Count the number of empty spaces in the history list. Entries read
- * from viminfo previously are also considered empty. If there are
- * more spaces available than we request, then fill them up. */
- for (i = 0, num = 0; i < hislen; i++)
- if (history[type][i].hisstr == NULL || history[type][i].viminfo)
- num++;
- int len = asklen;
- if (num > len)
- len = num;
- if (len <= 0)
- viminfo_history[type] = NULL;
- else
- viminfo_history[type] = xmalloc(len * sizeof(char_u *));
- if (viminfo_history[type] == NULL)
- len = 0;
- viminfo_hislen[type] = len;
- viminfo_hisidx[type] = 0;
- }
-}
-
-/*
- * Accept a line from the viminfo, store it in the history array when it's
- * new.
- */
-int read_viminfo_history(vir_T *virp, int writing)
-{
- int type;
- char_u *val;
-
- type = hist_char2type(virp->vir_line[0]);
- if (viminfo_hisidx[type] < viminfo_hislen[type]) {
- val = viminfo_readstring(virp, 1, TRUE);
- if (val != NULL && *val != NUL) {
- int sep = (*val == ' ' ? NUL : *val);
-
- if (!in_history(type, val + (type == HIST_SEARCH),
- viminfo_add_at_front, sep, writing)) {
- /* Need to re-allocate to append the separator byte. */
- size_t len = STRLEN(val);
- char_u *p = xmalloc(len + 2);
- if (type == HIST_SEARCH) {
- /* Search entry: Move the separator from the first
- * column to after the NUL. */
- memmove(p, val + 1, len);
- p[len] = sep;
- } else {
- /* Not a search entry: No separator in the viminfo
- * file, add a NUL separator. */
- memmove(p, val, len + 1);
- p[len + 1] = NUL;
- }
- viminfo_history[type][viminfo_hisidx[type]++] = p;
- }
}
- xfree(val);
- }
- return viminfo_readline(virp);
-}
-
-/*
- * Finish reading history lines from viminfo. Not used when writing viminfo.
- */
-void finish_viminfo_history(void)
-{
- int idx;
- int i;
- int type;
-
- for (type = 0; type < HIST_COUNT; ++type) {
- if (history[type] == NULL)
- continue;
- idx = hisidx[type] + viminfo_hisidx[type];
- if (idx >= hislen)
- idx -= hislen;
- else if (idx < 0)
- idx = hislen - 1;
- if (viminfo_add_at_front)
- hisidx[type] = idx;
- else {
- if (hisidx[type] == -1)
- hisidx[type] = hislen - 1;
- do {
- if (history[type][idx].hisstr != NULL
- || history[type][idx].viminfo)
- break;
- if (++idx == hislen)
- idx = 0;
- } while (idx != hisidx[type]);
- if (idx != hisidx[type] && --idx < 0)
- idx = hislen - 1;
+ case HIST_EXPR: {
+ return '=';
}
- for (i = 0; i < viminfo_hisidx[type]; i++) {
- xfree(history[type][idx].hisstr);
- history[type][idx].hisstr = viminfo_history[type][i];
- history[type][idx].viminfo = TRUE;
- if (--idx < 0)
- idx = hislen - 1;
+ case HIST_INPUT: {
+ return '@';
}
- idx += 1;
- idx %= hislen;
- for (i = 0; i < viminfo_hisidx[type]; i++) {
- history[type][idx++].hisnum = ++hisnum[type];
- idx %= hislen;
+ case HIST_DEBUG: {
+ return '>';
}
- xfree(viminfo_history[type]);
- viminfo_history[type] = NULL;
- viminfo_hisidx[type] = 0;
- }
-}
-
-/*
- * Write history to viminfo file in "fp".
- * When "merge" is TRUE merge history lines with a previously read viminfo
- * file, data is in viminfo_history[].
- * When "merge" is FALSE just write all history lines. Used for ":wviminfo!".
- */
-void write_viminfo_history(FILE *fp, int merge)
-{
- int i;
- int type;
- int num_saved;
- char_u *p;
- int c;
- int round;
-
- init_history();
- if (hislen == 0)
- return;
- for (type = 0; type < HIST_COUNT; ++type) {
- num_saved = get_viminfo_parameter(hist_type2char(type, FALSE));
- if (num_saved == 0)
- continue;
- if (num_saved < 0) /* Use default */
- num_saved = hislen;
- fprintf(fp, _("\n# %s History (newest to oldest):\n"),
- type == HIST_CMD ? _("Command Line") :
- type == HIST_SEARCH ? _("Search String") :
- type == HIST_EXPR ? _("Expression") :
- _("Input Line"));
- if (num_saved > hislen)
- num_saved = hislen;
-
- /*
- * Merge typed and viminfo history:
- * round 1: history of typed commands.
- * round 2: history from recently read viminfo.
- */
- for (round = 1; round <= 2; ++round) {
- if (round == 1)
- /* start at newest entry, somewhere in the list */
- i = hisidx[type];
- else if (viminfo_hisidx[type] > 0)
- /* start at newest entry, first in the list */
- i = 0;
- else
- /* empty list */
- i = -1;
- if (i >= 0)
- while (num_saved > 0
- && !(round == 2 && i >= viminfo_hisidx[type])) {
- p = round == 1 ? history[type][i].hisstr
- : viminfo_history[type] == NULL ? NULL
- : viminfo_history[type][i];
- if (p != NULL && (round == 2
- || !merge
- || !history[type][i].viminfo)) {
- --num_saved;
- fputc(hist_type2char(type, TRUE), fp);
- /* For the search history: put the separator in the
- * second column; use a space if there isn't one. */
- if (type == HIST_SEARCH) {
- c = p[STRLEN(p) + 1];
- putc(c == NUL ? ' ' : c, fp);
- }
- viminfo_writestring(fp, p);
- }
- if (round == 1) {
- /* Decrement index, loop around and stop when back at
- * the start. */
- if (--i < 0)
- i = hislen - 1;
- if (i == hisidx[type])
- break;
- } else {
- /* Increment index. Stop at the end in the while. */
- ++i;
- }
- }
+ default: {
+ assert(false);
}
- for (i = 0; i < viminfo_hisidx[type]; ++i)
- if (viminfo_history[type] != NULL)
- xfree(viminfo_history[type][i]);
- xfree(viminfo_history[type]);
- viminfo_history[type] = NULL;
- viminfo_hisidx[type] = 0;
}
+ return NUL;
}
/*
@@ -5294,3 +5076,87 @@ char_u *script_get(exarg_T *eap, char_u *cmd)
return (char_u *)ga.ga_data;
}
+
+/// Iterate over history items
+///
+/// @warning No history-editing functions must be run while iteration is in
+/// progress.
+///
+/// @param[in] iter Pointer to the last history entry.
+/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
+/// parameter is not NULL.
+/// @param[in] zero If true then zero (but not free) returned items.
+///
+/// @warning When using this parameter user is
+/// responsible for calling clr_history()
+/// itself after iteration is over. If
+/// clr_history() is not called behaviour is
+/// undefined. No functions that work with
+/// history must be called during iteration
+/// in this case.
+/// @param[out] hist Next history entry.
+///
+/// @return Pointer used in next iteration or NULL to indicate that iteration
+/// was finished.
+const void *hist_iter(const void *const iter, const uint8_t history_type,
+ const bool zero, histentry_T *const hist)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
+{
+ *hist = (histentry_T) {
+ .hisstr = NULL
+ };
+ if (hisidx[history_type] == -1) {
+ return NULL;
+ }
+ histentry_T *const hstart = &(history[history_type][0]);
+ histentry_T *const hlast = (
+ &(history[history_type][hisidx[history_type]]));
+ const histentry_T *const hend = &(history[history_type][hislen - 1]);
+ histentry_T *hiter;
+ if (iter == NULL) {
+ histentry_T *hfirst = hlast;
+ do {
+ hfirst++;
+ if (hfirst > hend) {
+ hfirst = hstart;
+ }
+ if (hfirst->hisstr != NULL) {
+ break;
+ }
+ } while (hfirst != hlast);
+ hiter = hfirst;
+ } else {
+ hiter = (histentry_T *) iter;
+ }
+ if (hiter == NULL) {
+ return NULL;
+ }
+ *hist = *hiter;
+ if (zero) {
+ memset(hiter, 0, sizeof(*hiter));
+ }
+ if (hiter == hlast) {
+ return NULL;
+ }
+ hiter++;
+ return (const void *) ((hiter > hend) ? hstart : hiter);
+}
+
+/// Get array of history items
+///
+/// @param[in] history_type Type of the history to get array for.
+/// @param[out] new_hisidx Location where last index in the new array should
+/// be saved.
+/// @param[out] new_hisnum Location where last history number in the new
+/// history should be saved.
+///
+/// @return Pointer to the array or NULL.
+histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
+ int **const new_hisnum)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ init_history();
+ *new_hisidx = &(hisidx[history_type]);
+ *new_hisnum = &(hisnum[history_type]);
+ return history[history_type];
+}
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 2b82f934d5..c537d681c6 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -1,6 +1,7 @@
#ifndef NVIM_EX_GETLN_H
#define NVIM_EX_GETLN_H
+#include "nvim/eval_defs.h"
#include "nvim/ex_cmds.h"
/* Values for nextwild() and ExpandOne(). See ExpandOne() for meaning. */
@@ -23,18 +24,28 @@
#define WILD_ESCAPE 128
#define WILD_ICASE 256
-/*
- * There are four history tables:
- */
-#define HIST_CMD 0 /* colon commands */
-#define HIST_SEARCH 1 /* search commands */
-#define HIST_EXPR 2 /* expressions (from entering = register) */
-#define HIST_INPUT 3 /* input() lines */
-#define HIST_DEBUG 4 /* debug commands */
-#define HIST_COUNT 5 /* number of history tables */
+/// Present history tables
+typedef enum {
+ HIST_CMD, ///< Colon commands.
+ HIST_SEARCH, ///< Search commands.
+ HIST_EXPR, ///< Expressions (e.g. from entering = register).
+ HIST_INPUT, ///< input() lines.
+ HIST_DEBUG, ///< Debug commands.
+} HistoryType;
+
+/// Number of history tables
+#define HIST_COUNT (HIST_DEBUG + 1)
typedef char_u *(*CompleteListItemGetter)(expand_T *, int);
+/// History entry definition
+typedef struct hist_entry {
+ int hisnum; ///< Entry identifier number.
+ char_u *hisstr; ///< Actual entry, separator char after the NUL.
+ Timestamp timestamp; ///< Time when entry was added.
+ list_T *additional_elements; ///< Additional entries from ShaDa file.
+} histentry_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.h.generated.h"
#endif
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 302f6b30fb..a7472b40e2 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -57,6 +57,7 @@
#include "nvim/types.h"
#include "nvim/undo.h"
#include "nvim/window.h"
+#include "nvim/shada.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
@@ -2166,16 +2167,17 @@ readfile_charconvert (
/*
- * Read marks for the current buffer from the viminfo file, when we support
+ * Read marks for the current buffer from the ShaDa file, when we support
* buffer marks and the buffer has a name.
*/
static void check_marks_read(void)
{
- if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0
- && curbuf->b_ffname != NULL)
- read_viminfo(NULL, VIF_WANT_MARKS);
+ if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0
+ && curbuf->b_ffname != NULL) {
+ shada_read_marks();
+ }
- /* Always set b_marks_read; needed when 'viminfo' is changed to include
+ /* Always set b_marks_read; needed when 'shada' is changed to include
* the ' parameter after opening a buffer. */
curbuf->b_marks_read = true;
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 60d03cec0c..0ef0a12889 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -891,8 +891,8 @@ EXTERN int must_redraw INIT(= 0); /* type of redraw necessary */
EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */
EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */
-EXTERN int need_highlight_changed INIT(= TRUE);
-EXTERN char_u *use_viminfo INIT(= NULL); /* name of viminfo file to use */
+EXTERN int need_highlight_changed INIT(= true);
+EXTERN char *used_shada_file INIT(= NULL); // name of the ShaDa file to use
#define NSCRIPT 15
EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */
diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h
index 96e7ea6df0..56be29d14c 100644
--- a/src/nvim/lib/khash.h
+++ b/src/nvim/lib/khash.h
@@ -114,8 +114,8 @@ int main() {
*/
-#ifndef __AC_KHASH_H
-#define __AC_KHASH_H
+#ifndef NVIM_LIB_KHASH_H
+#define NVIM_LIB_KHASH_H
/*!
@header
@@ -194,166 +194,229 @@ static const double __ac_HASH_UPPER = 0.77;
khval_t *vals; \
} kh_##name##_t;
-#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
- extern kh_##name##_t *kh_init_##name(void); \
- extern void kh_destroy_##name(kh_##name##_t *h); \
- extern void kh_clear_##name(kh_##name##_t *h); \
- extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
- extern void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
- extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
- extern void kh_del_##name(kh_##name##_t *h, khint_t x);
-
-#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- SCOPE kh_##name##_t *kh_init_##name(void) { \
- return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
- } \
- SCOPE void kh_destroy_##name(kh_##name##_t *h) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_destroy_##name(kh_##name##_t *h) \
- { \
- if (h) { \
- kfree((void *)h->keys); kfree(h->flags); \
- kfree((void *)h->vals); \
- kfree(h); \
- } \
- } \
- SCOPE void kh_clear_##name(kh_##name##_t *h) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_clear_##name(kh_##name##_t *h) \
- { \
- if (h && h->flags) { \
- memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
- h->size = h->n_occupied = 0; \
- } \
- } \
- SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
- REAL_FATTR_UNUSED; \
- SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
- { \
- if (h->n_buckets) { \
- khint_t k, i, last, mask, step = 0; \
- mask = h->n_buckets - 1; \
- k = __hash_func(key); i = k & mask; \
- last = i; \
- while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
- i = (i + (++step)) & mask; \
- if (i == last) return h->n_buckets; \
- } \
- return __ac_iseither(h->flags, i)? h->n_buckets : i; \
- } else return 0; \
- } \
- SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
- { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
- khint32_t *new_flags = 0; \
- khint_t j = 1; \
- { \
- kroundup32(new_n_buckets); \
- if (new_n_buckets < 4) new_n_buckets = 4; \
- if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
- else { /* hash table size to be changed (shrink or expand); rehash */ \
- new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
- memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
- if (h->n_buckets < new_n_buckets) { /* expand */ \
- khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
- h->keys = new_keys; \
- if (kh_is_map) { \
- khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
- h->vals = new_vals; \
- } \
- } /* otherwise shrink */ \
- } \
- } \
- if (j) { /* rehashing is needed */ \
- for (j = 0; j != h->n_buckets; ++j) { \
- if (__ac_iseither(h->flags, j) == 0) { \
- khkey_t key = h->keys[j]; \
- khval_t val; \
- khint_t new_mask; \
- new_mask = new_n_buckets - 1; \
- if (kh_is_map) val = h->vals[j]; \
- __ac_set_isdel_true(h->flags, j); \
- while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
- khint_t k, i, step = 0; \
- k = __hash_func(key); \
- i = k & new_mask; \
- while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
- __ac_set_isempty_false(new_flags, i); \
- if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
- { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
- if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
- __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
- } else { /* write the element and jump out of the loop */ \
- h->keys[i] = key; \
- if (kh_is_map) h->vals[i] = val; \
- break; \
- } \
- } \
- } \
- } \
- if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
- h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
- if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
- } \
- kfree(h->flags); /* free the working space */ \
- h->flags = new_flags; \
- h->n_buckets = new_n_buckets; \
- h->n_occupied = h->size; \
- h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
- } \
- } \
- SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
- REAL_FATTR_UNUSED; \
- SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
- { \
- khint_t x; \
- if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
- if (h->n_buckets > (h->size<<1)) { \
- kh_resize_##name(h, h->n_buckets - 1); /* clear "deleted" elements */ \
- } else { \
- kh_resize_##name(h, h->n_buckets + 1); /* expand the hash table */ \
- } \
- } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
- { \
- khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
- x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
- if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
- else { \
- last = i; \
- while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
- if (__ac_isdel(h->flags, i)) site = i; \
- i = (i + (++step)) & mask; \
- if (i == last) { x = site; break; } \
- } \
- if (x == h->n_buckets) { \
- if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
- else x = i; \
- } \
- } \
- } \
- if (__ac_isempty(h->flags, x)) { /* not present at all */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- ++h->size; ++h->n_occupied; \
- *ret = 1; \
- } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- ++h->size; \
- *ret = 2; \
- } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
- return x; \
- } \
- SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
- { \
- if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
- __ac_set_isdel_true(h->flags, x); \
- --h->size; \
- } \
- }
+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
+ extern kh_##name##_t *kh_init_##name(void); \
+ extern void kh_dealloc_##name(kh_##name##_t *h); \
+ extern void kh_destroy_##name(kh_##name##_t *h); \
+ extern void kh_clear_##name(kh_##name##_t *h); \
+ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
+ extern void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
+ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
+ extern void kh_del_##name(kh_##name##_t *h, khint_t x);
+
+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, \
+ __hash_equal) \
+ SCOPE kh_##name##_t *kh_init_##name(void) \
+ REAL_FATTR_UNUSED; \
+ SCOPE kh_##name##_t *kh_init_##name(void) { \
+ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
+ } \
+ SCOPE void kh_dealloc_##name(kh_##name##_t *h) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_dealloc_##name(kh_##name##_t *h) \
+ { \
+ kfree(h->keys); \
+ kfree(h->flags); \
+ kfree(h->vals); \
+ } \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ { \
+ if (h) { \
+ kh_dealloc_##name(h); \
+ kfree(h); \
+ } \
+ } \
+ SCOPE void kh_clear_##name(kh_##name##_t *h) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_clear_##name(kh_##name##_t *h) \
+ { \
+ if (h && h->flags) { \
+ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
+ h->size = h->n_occupied = 0; \
+ } \
+ } \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ REAL_FATTR_UNUSED; \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ { \
+ if (h->n_buckets) { \
+ khint_t k, i, last, mask, step = 0; \
+ mask = h->n_buckets - 1; \
+ k = __hash_func(key); i = k & mask; \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || \
+ !__hash_equal(h->keys[i], key))) { \
+ i = (i + (++step)) & mask; \
+ if (i == last) { \
+ return h->n_buckets; \
+ } \
+ } \
+ return __ac_iseither(h->flags, i) ? h->n_buckets : i; \
+ } else { \
+ return 0; \
+ } \
+ } \
+ SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ { /* This function uses 0.25*n_buckets bytes of working space instead of */ \
+ /* [sizeof(key_t+val_t)+.25]*n_buckets. */ \
+ khint32_t *new_flags = 0; \
+ khint_t j = 1; \
+ { \
+ kroundup32(new_n_buckets); \
+ if (new_n_buckets < 4) { \
+ new_n_buckets = 4; \
+ } \
+ /* requested size is too small */ \
+ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) { \
+ j = 0; \
+ } else { /* hash table size to be changed (shrink or expand); rehash */ \
+ new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) \
+ * sizeof(khint32_t)); \
+ memset(new_flags, 0xaa, \
+ __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (h->n_buckets < new_n_buckets) { /* expand */ \
+ khkey_t *new_keys = (khkey_t*)krealloc( \
+ (void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+ h->keys = new_keys; \
+ if (kh_is_map) { \
+ khval_t *new_vals = (khval_t*)krealloc( \
+ (void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+ h->vals = new_vals; \
+ } \
+ } /* otherwise shrink */ \
+ } \
+ } \
+ if (j) { /* rehashing is needed */ \
+ for (j = 0; j != h->n_buckets; ++j) { \
+ if (__ac_iseither(h->flags, j) == 0) { \
+ khkey_t key = h->keys[j]; \
+ khval_t val; \
+ khint_t new_mask; \
+ new_mask = new_n_buckets - 1; \
+ if (kh_is_map) { \
+ val = h->vals[j]; \
+ } \
+ __ac_set_isdel_true(h->flags, j); \
+ /* kick-out process; sort of like in Cuckoo hashing */ \
+ while (1) { \
+ khint_t k, i, step = 0; \
+ k = __hash_func(key); \
+ i = k & new_mask; \
+ while (!__ac_isempty(new_flags, i)) { \
+ i = (i + (++step)) & new_mask; \
+ } \
+ __ac_set_isempty_false(new_flags, i); \
+ /* kick out the existing element */ \
+ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { \
+ { \
+ khkey_t tmp = h->keys[i]; \
+ h->keys[i] = key; \
+ key = tmp; \
+ } \
+ if (kh_is_map) { \
+ khval_t tmp = h->vals[i]; \
+ h->vals[i] = val; \
+ val = tmp; \
+ } \
+ /* mark it as deleted in the old hash table */ \
+ __ac_set_isdel_true(h->flags, i); \
+ } else { /* write the element and jump out of the loop */ \
+ h->keys[i] = key; \
+ if (kh_is_map) { \
+ h->vals[i] = val; \
+ } \
+ break; \
+ } \
+ } \
+ } \
+ } \
+ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
+ h->keys = (khkey_t*)krealloc((void *)h->keys, \
+ new_n_buckets * sizeof(khkey_t)); \
+ if (kh_is_map) { \
+ h->vals = (khval_t*)krealloc((void *)h->vals, \
+ new_n_buckets * sizeof(khval_t)); \
+ } \
+ } \
+ kfree(h->flags); /* free the working space */ \
+ h->flags = new_flags; \
+ h->n_buckets = new_n_buckets; \
+ h->n_occupied = h->size; \
+ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
+ } \
+ } \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ REAL_FATTR_UNUSED; \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ { \
+ khint_t x; \
+ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
+ if (h->n_buckets > (h->size << 1)) { \
+ kh_resize_##name(h, h->n_buckets - 1); /* clear "deleted" elements */ \
+ } else { \
+ kh_resize_##name(h, h->n_buckets + 1); /* expand the hash table */ \
+ } \
+ } /* TODO: implement automatically shrinking; */ \
+ /* resize() already support shrinking */ \
+ { \
+ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
+ x = site = h->n_buckets; \
+ k = __hash_func(key); \
+ i = k & mask; \
+ if (__ac_isempty(h->flags, i)) { \
+ x = i; /* for speed up */ \
+ } else { \
+ last = i; \
+ while (!__ac_isempty(h->flags, i) \
+ && (__ac_isdel(h->flags, i) \
+ || !__hash_equal(h->keys[i], key))) { \
+ if (__ac_isdel(h->flags, i)) { \
+ site = i; \
+ } \
+ i = (i + (++step)) & mask; \
+ if (i == last) { \
+ x = site; \
+ break; \
+ } \
+ } \
+ if (x == h->n_buckets) { \
+ if (__ac_isempty(h->flags, i) && site != h->n_buckets) { \
+ x = site; \
+ } else { \
+ x = i; \
+ } \
+ } \
+ } \
+ } \
+ if (__ac_isempty(h->flags, x)) { /* not present at all */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ h->size++; \
+ h->n_occupied++; \
+ *ret = 1; \
+ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ h->size++; \
+ *ret = 2; \
+ } else { \
+ *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
+ } \
+ return x; \
+ } \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ REAL_FATTR_UNUSED; \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ { \
+ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
+ __ac_set_isdel_true(h->flags, x); \
+ --h->size; \
+ } \
+ }
#define KHASH_DECLARE(name, khkey_t, khval_t) \
__KHASH_TYPE(name, khkey_t, khval_t) \
@@ -447,6 +510,13 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key)
#define kh_destroy(name, h) kh_destroy_##name(h)
/*! @function
+ @abstract Free memory referenced directly inside a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_dealloc(name, h) kh_dealloc_##name(h)
+
+/*! @function
@abstract Reset a hash table without deallocating memory.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@@ -577,6 +647,24 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key)
code; \
} }
+/*! @function
+ @abstract Iterate over the keys in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param kvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach_key(h, kvar, code) \
+ { \
+ khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); __i++) { \
+ if (!kh_exist(h, __i)) { \
+ continue; \
+ } \
+ (kvar) = kh_key(h, __i); \
+ code; \
+ } \
+ }
+
/* More conenient interfaces */
/*! @function
@@ -622,6 +710,21 @@ typedef const char *kh_cstr_t;
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
-#define KHASH_MAP_INIT_STR(name, khval_t) \
- KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
-#endif /* __AC_KHASH_H */
+#define KHASH_MAP_INIT_STR(name, khval_t) \
+ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+/*! @function
+ @abstract Return a literal for an empty hash table.
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_EMPTY_TABLE(name) \
+ ((kh_##name##_t) { \
+ .n_buckets = 0, \
+ .size = 0, \
+ .n_occupied = 0, \
+ .upper_bound = 0, \
+ .flags = NULL, \
+ .keys = NULL, \
+ .vals = NULL, \
+ })
+#endif // NVIM_LIB_KHASH_H
diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h
new file mode 100644
index 0000000000..cb71500bb7
--- /dev/null
+++ b/src/nvim/lib/ringbuf.h
@@ -0,0 +1,281 @@
+/// Macros-based ring buffer implementation.
+///
+/// Supported functions:
+///
+/// - new: allocates new ring buffer.
+/// - dealloc: free ring buffer itself.
+/// - free: free ring buffer and all its elements.
+/// - push: adds element to the end of the buffer.
+/// - length: get buffer length.
+/// - size: size of the ring buffer.
+/// - idx: get element at given index.
+/// - idx_p: get pointer to the element at given index.
+/// - insert: insert element at given position.
+/// - remove: remove element from given position.
+#ifndef NVIM_LIB_RINGBUF_H
+#define NVIM_LIB_RINGBUF_H
+
+#include <string.h>
+#include <assert.h>
+#include <stdint.h>
+
+#include "nvim/memory.h"
+#include "nvim/func_attr.h"
+
+#define _RINGBUF_LENGTH(rb) \
+ ((rb)->first == NULL ? 0 \
+ : ((rb)->next == (rb)->first) ? (size_t) ((rb)->buf_end - (rb)->buf) + 1 \
+ : ((rb)->next > (rb)->first) ? (size_t) ((rb)->next - (rb)->first) \
+ : (size_t) ((rb)->next - (rb)->buf + (rb)->buf_end - (rb)->first + 1))
+
+#define _RINGBUF_NEXT(rb, var) \
+ ((var) == (rb)->buf_end ? (rb)->buf : (var) + 1)
+#define _RINGBUF_PREV(rb, var) \
+ ((var) == (rb)->buf ? (rb)->buf_end : (var) - 1)
+
+/// Iterate over all ringbuf values
+///
+/// @param rb Ring buffer to iterate over.
+/// @param RBType Type of the ring buffer element.
+/// @param varname Variable name.
+#define RINGBUF_FORALL(rb, RBType, varname) \
+ size_t varname##_length_fa_ = _RINGBUF_LENGTH(rb); \
+ for (RBType *varname = ((rb)->first == NULL ? (rb)->next : (rb)->first); \
+ varname##_length_fa_; \
+ (varname = _RINGBUF_NEXT(rb, varname)), \
+ varname##_length_fa_--)
+
+/// Iterate over all ringbuf values, from end to the beginning
+///
+/// Unlike previous RINGBUF_FORALL uses already defined variable, in place of
+/// defining variable in the cycle body.
+///
+/// @param rb Ring buffer to iterate over.
+/// @param RBType Type of the ring buffer element.
+/// @param varname Variable name.
+#define RINGBUF_ITER_BACK(rb, RBType, varname) \
+ size_t varname##_length_ib_ = _RINGBUF_LENGTH(rb); \
+ for (varname = ((rb)->next == (rb)->buf ? (rb)->buf_end : (rb)->next - 1); \
+ varname##_length_ib_; \
+ (varname = _RINGBUF_PREV(rb, varname)), \
+ varname##_length_ib_--)
+
+/// Define a ring buffer structure
+///
+/// @param TypeName Ring buffer type name. Actual type name will be
+/// `{TypeName}RingBuffer`.
+/// @param RBType Type of the single ring buffer element.
+#define RINGBUF_TYPEDEF(TypeName, RBType) \
+typedef struct { \
+ RBType *buf; \
+ RBType *next; \
+ RBType *first; \
+ RBType *buf_end; \
+} TypeName##RingBuffer;
+
+/// Initialize a new ring buffer
+///
+/// @param TypeName Ring buffer type name. Actual type name will be
+/// `{TypeName}RingBuffer`.
+/// @param funcprefix Prefix for all ring buffer functions. Function name will
+/// look like `{funcprefix}_rb_{function_name}`.
+/// @param RBType Type of the single ring buffer element.
+/// @param rbfree Function used to free ring buffer element. May be
+/// a macros like `#define RBFREE(item)` (to skip freeing).
+///
+/// Intended function signature: `void *rbfree(RBType *)`;
+#define RINGBUF_INIT(TypeName, funcprefix, RBType, rbfree) \
+ \
+ \
+static inline TypeName##RingBuffer funcprefix##_rb_new(const size_t size) \
+ REAL_FATTR_WARN_UNUSED_RESULT; \
+static inline TypeName##RingBuffer funcprefix##_rb_new(const size_t size) \
+{ \
+ assert(size != 0); \
+ RBType *buf = xmalloc(size * sizeof(RBType)); \
+ return (TypeName##RingBuffer) { \
+ .buf = buf, \
+ .next = buf, \
+ .first = NULL, \
+ .buf_end = buf + size - 1, \
+ }; \
+} \
+ \
+static inline void funcprefix##_rb_free(TypeName##RingBuffer *const rb) \
+ REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_free(TypeName##RingBuffer *const rb) \
+{ \
+ if (rb == NULL) { \
+ return; \
+ } \
+ RINGBUF_FORALL(rb, RBType, rbitem) { \
+ rbfree(rbitem); \
+ } \
+ xfree(rb->buf); \
+} \
+ \
+static inline void funcprefix##_rb_dealloc(TypeName##RingBuffer *const rb) \
+ REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_dealloc(TypeName##RingBuffer *const rb) \
+{ \
+ xfree(rb->buf); \
+} \
+ \
+static inline void funcprefix##_rb_push(TypeName##RingBuffer *const rb, \
+ RBType item) \
+ REAL_FATTR_NONNULL_ARG(1); \
+static inline void funcprefix##_rb_push(TypeName##RingBuffer *const rb, \
+ RBType item) \
+{ \
+ if (rb->next == rb->first) { \
+ rbfree(rb->first); \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } else if (rb->first == NULL) { \
+ rb->first = rb->next; \
+ } \
+ *rb->next = item; \
+ rb->next = _RINGBUF_NEXT(rb, rb->next); \
+} \
+ \
+static inline ptrdiff_t funcprefix##_rb_find_idx( \
+ const TypeName##RingBuffer *const rb, const RBType *const item_p) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE REAL_FATTR_UNUSED; \
+static inline ptrdiff_t funcprefix##_rb_find_idx( \
+ const TypeName##RingBuffer *const rb, const RBType *const item_p) \
+{ \
+ assert(rb->buf <= item_p); \
+ assert(rb->buf_end >= item_p); \
+ if (rb->first == NULL) { \
+ return -1; \
+ } else if (item_p >= rb->first) { \
+ return item_p - rb->first; \
+ } else { \
+ return item_p - rb->buf + rb->buf_end - rb->first + 1; \
+ } \
+} \
+ \
+static inline size_t funcprefix##_rb_size( \
+ const TypeName##RingBuffer *const rb) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE; \
+static inline size_t funcprefix##_rb_size( \
+ const TypeName##RingBuffer *const rb) \
+{ \
+ return (size_t) (rb->buf_end - rb->buf) + 1; \
+} \
+ \
+static inline size_t funcprefix##_rb_length( \
+ const TypeName##RingBuffer *const rb) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE; \
+static inline size_t funcprefix##_rb_length( \
+ const TypeName##RingBuffer *const rb) \
+{ \
+ return _RINGBUF_LENGTH(rb); \
+} \
+ \
+static inline RBType *funcprefix##_rb_idx_p( \
+ const TypeName##RingBuffer *const rb, const size_t idx) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE; \
+static inline RBType *funcprefix##_rb_idx_p( \
+ const TypeName##RingBuffer *const rb, const size_t idx) \
+{ \
+ assert(idx <= funcprefix##_rb_size(rb)); \
+ assert(idx <= funcprefix##_rb_length(rb)); \
+ if (rb->first + idx > rb->buf_end) { \
+ return rb->buf + ((rb->first + idx) - (rb->buf_end + 1)); \
+ } else { \
+ return rb->first + idx; \
+ } \
+} \
+ \
+static inline RBType funcprefix##_rb_idx(const TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE REAL_FATTR_UNUSED; \
+static inline RBType funcprefix##_rb_idx(const TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+{ \
+ return *funcprefix##_rb_idx_p(rb, idx); \
+} \
+ \
+static inline void funcprefix##_rb_insert(TypeName##RingBuffer *const rb, \
+ const size_t idx, \
+ RBType item) \
+ REAL_FATTR_NONNULL_ARG(1) REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_insert(TypeName##RingBuffer *const rb, \
+ const size_t idx, \
+ RBType item) \
+{ \
+ assert(idx <= funcprefix##_rb_size(rb)); \
+ assert(idx <= funcprefix##_rb_length(rb)); \
+ const size_t length = funcprefix##_rb_length(rb); \
+ if (idx == length) { \
+ funcprefix##_rb_push(rb, item); \
+ return; \
+ } \
+ RBType *const insertpos = funcprefix##_rb_idx_p(rb, idx); \
+ if (insertpos == rb->next) { \
+ funcprefix##_rb_push(rb, item); \
+ return; \
+ } \
+ if (length == funcprefix##_rb_size(rb)) { \
+ rbfree(rb->first); \
+ } \
+ if (insertpos < rb->next) { \
+ memmove(insertpos + 1, insertpos, \
+ (size_t) ((uintptr_t) rb->next - (uintptr_t) insertpos)); \
+ } else { \
+ assert(insertpos > rb->first); \
+ assert(rb->next <= rb->first); \
+ memmove(rb->buf + 1, rb->buf, \
+ (size_t) ((uintptr_t) rb->next - (uintptr_t) rb->buf)); \
+ *rb->buf = *rb->buf_end; \
+ memmove(insertpos + 1, insertpos, \
+ (size_t) ((uintptr_t) (rb->buf_end + 1) - (uintptr_t) insertpos)); \
+ } \
+ *insertpos = item; \
+ if (length == funcprefix##_rb_size(rb)) { \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } \
+ rb->next = _RINGBUF_NEXT(rb, rb->next); \
+} \
+ \
+static inline void funcprefix##_rb_remove(TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+ REAL_FATTR_NONNULL_ARG(1) REAL_FATTR_UNUSED; \
+static inline void funcprefix##_rb_remove(TypeName##RingBuffer *const rb, \
+ const size_t idx) \
+{ \
+ assert(idx < funcprefix##_rb_size(rb)); \
+ assert(idx < funcprefix##_rb_length(rb)); \
+ RBType *const rmpos = funcprefix##_rb_idx_p(rb, idx); \
+ rbfree(rmpos); \
+ if (rmpos == rb->next - 1) { \
+ rb->next--; \
+ if (rb->first == rb->next) { \
+ rb->first = NULL; \
+ rb->next = rb->buf; \
+ } \
+ } else if (rmpos == rb->first) { \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ if (rb->first == rb->next) { \
+ rb->first = NULL; \
+ rb->next = rb->buf; \
+ } \
+ } else if (rb->first < rb->next || rb->next == rb->buf) { \
+ assert(rmpos > rb->first); \
+ assert(rmpos <= _RINGBUF_PREV(rb, rb->next)); \
+ memmove(rb->first + 1, rb->first, \
+ (size_t) ((uintptr_t) rmpos - (uintptr_t) rb->first)); \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } else if (rmpos < rb->next) { \
+ memmove(rmpos, rmpos + 1, \
+ (size_t) ((uintptr_t) rb->next - (uintptr_t) rmpos)); \
+ rb->next = _RINGBUF_PREV(rb, rb->next); \
+ } else { \
+ assert(rb->first < rb->buf_end); \
+ memmove(rb->first + 1, rb->first, \
+ (size_t) ((uintptr_t) rmpos - (uintptr_t) rb->first)); \
+ rb->first = _RINGBUF_NEXT(rb, rb->first); \
+ } \
+}
+
+#endif // NVIM_LIB_RINGBUF_H
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 27f8340ec7..d865260295 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -49,6 +49,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os_unix.h"
+#include "nvim/os/os_defs.h"
#include "nvim/path.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
@@ -58,6 +59,7 @@
#include "nvim/ui.h"
#include "nvim/version.h"
#include "nvim/window.h"
+#include "nvim/shada.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
@@ -377,12 +379,12 @@ int main(int argc, char **argv)
}
/*
- * Read in registers, history etc, but not marks, from the viminfo file.
+ * Read in registers, history etc, from the ShaDa file.
* This is where v:oldfiles gets filled.
*/
- if (*p_viminfo != NUL) {
- read_viminfo(NULL, VIF_WANT_INFO | VIF_GET_OLDFILES);
- TIME_MSG("reading viminfo");
+ if (*p_shada != NUL) {
+ shada_read_everything(NULL, false, true);
+ TIME_MSG("reading ShaDa");
}
/* It's better to make v:oldfiles an empty list than NULL. */
if (get_vim_var_list(VV_OLDFILES) == NULL)
@@ -803,9 +805,10 @@ void getout(int exitval)
apply_autocmds(EVENT_VIMLEAVEPRE, NULL, NULL, FALSE, curbuf);
}
- if (p_viminfo && *p_viminfo != NUL)
- /* Write out the registers, history, marks etc, to the viminfo file */
- write_viminfo(NULL, FALSE);
+ if (p_shada && *p_shada != NUL) {
+ // Write out the registers, history, marks etc, to the ShaDa file
+ shada_write_file(NULL, false);
+ }
if (get_vim_var_nr(VV_DYING) <= 1)
apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf);
@@ -1164,7 +1167,7 @@ static void command_line_scan(mparm_T *parmp)
}
/*FALLTHROUGH*/
case 'S': /* "-S {file}" execute Vim script */
- case 'i': /* "-i {viminfo}" use for viminfo */
+ case 'i': /* "-i {shada}" use for ShaDa file */
case 'u': /* "-u {vimrc}" vim inits file */
case 'U': /* "-U {gvimrc}" gvim inits file */
case 'W': /* "-W {scriptout}" overwrite */
@@ -1235,8 +1238,8 @@ static void command_line_scan(mparm_T *parmp)
parmp->use_ef = (char_u *)argv[0];
break;
- case 'i': /* "-i {viminfo}" use for viminfo */
- use_viminfo = (char_u *)argv[0];
+ case 'i': /* "-i {shada}" use for shada */
+ used_shada_file = argv[0];
break;
case 's': /* "-s {scriptin}" read from script file */
@@ -2039,7 +2042,7 @@ static void usage(void)
mch_msg(_(" -r, -L List swap files and exit\n"));
mch_msg(_(" -r <file> Recover crashed session\n"));
mch_msg(_(" -u <nvimrc> Use <nvimrc> instead of the default\n"));
- mch_msg(_(" -i <nviminfo> Use <nviminfo> instead of the default\n"));
+ mch_msg(_(" -i <shada> Use <shada> instead of the default " SHADA_FILE "\n")); // NOLINT(whitespace/line_length)
mch_msg(_(" --noplugin Don't load plugin scripts\n"));
mch_msg(_(" -o[N] Open N windows (default: one for each file)\n"));
mch_msg(_(" -O[N] Like -o but split vertically\n"));
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index ce44149ffa..dd49b311d3 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -39,6 +39,7 @@
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/os/os.h"
+#include "nvim/os/time.h"
#include "nvim/os/input.h"
/*
@@ -48,13 +49,13 @@
/*
* If a named file mark's lnum is non-zero, it is valid.
* If a named file mark's fnum is non-zero, it is for an existing buffer,
- * otherwise it is from .viminfo and namedfm[n].fname is the file name.
+ * otherwise it is from .shada and namedfm[n].fname is the file name.
* There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing
- * viminfo).
+ * shada).
*/
-#define EXTRA_MARKS 10 /* marks 0-9 */
-static xfmark_T namedfm[NMARKS + EXTRA_MARKS]; /* marks with file nr */
+/// Global marks (marks with file number or name)
+static xfmark_T namedfm[NGLOBALMARKS];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.c.generated.h"
@@ -68,6 +69,27 @@ int setmark(int c)
return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum);
}
+/// Free fmark_T item
+void free_fmark(fmark_T fm)
+{
+ dict_unref(fm.additional_data);
+}
+
+/// Free xfmark_T item
+void free_xfmark(xfmark_T fm)
+{
+ xfree(fm.fname);
+ free_fmark(fm.fmark);
+}
+
+/// Free and clear fmark_T item
+void clear_fmark(fmark_T *fm)
+ FUNC_ATTR_NONNULL_ALL
+{
+ free_fmark(*fm);
+ memset(fm, 0, sizeof(*fm));
+}
+
/*
* Set named mark "c" to position "pos".
* When "c" is upper case use file "fnum".
@@ -92,7 +114,7 @@ int setmark_pos(int c, pos_T *pos, int fnum)
}
if (c == '"') {
- curbuf->b_last_cursor = *pos;
+ RESET_FMARK(&curbuf->b_last_cursor, *pos, curbuf->b_fnum);
return OK;
}
@@ -123,16 +145,13 @@ int setmark_pos(int c, pos_T *pos, int fnum)
return FAIL;
if (islower(c)) {
i = c - 'a';
- curbuf->b_namedm[i] = *pos;
+ RESET_FMARK(curbuf->b_namedm + i, *pos, curbuf->b_fnum);
return OK;
}
if (isupper(c)) {
assert(c >= 'A' && c <= 'Z');
i = c - 'A';
- namedfm[i].fmark.mark = *pos;
- namedfm[i].fmark.fnum = fnum;
- xfree(namedfm[i].fname);
- namedfm[i].fname = NULL;
+ RESET_XFMARK(namedfm + i, *pos, fnum, NULL);
return OK;
}
return FAIL;
@@ -144,7 +163,6 @@ int setmark_pos(int c, pos_T *pos, int fnum)
*/
void setpcmark(void)
{
- int i;
xfmark_T *fm;
/* for :global the mark is set only once */
@@ -157,16 +175,14 @@ void setpcmark(void)
/* If jumplist is full: remove oldest entry */
if (++curwin->w_jumplistlen > JUMPLISTSIZE) {
curwin->w_jumplistlen = JUMPLISTSIZE;
- xfree(curwin->w_jumplist[0].fname);
- for (i = 1; i < JUMPLISTSIZE; ++i)
- curwin->w_jumplist[i - 1] = curwin->w_jumplist[i];
+ free_xfmark(curwin->w_jumplist[0]);
+ memmove(&curwin->w_jumplist[0], &curwin->w_jumplist[1],
+ (JUMPLISTSIZE - 1) * sizeof(curwin->w_jumplist[0]));
}
curwin->w_jumplistidx = curwin->w_jumplistlen;
fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
- fm->fmark.mark = curwin->w_pcmark;
- fm->fmark.fnum = curbuf->b_fnum;
- fm->fname = NULL;
+ SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, NULL);
}
/*
@@ -260,7 +276,7 @@ pos_T *movechangelist(int count)
} else
n += count;
curwin->w_changelistidx = n;
- return curbuf->b_changelist + n;
+ return &(curbuf->b_changelist[n].mark);
}
/*
@@ -296,22 +312,21 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
* to crash. */
if (c < 0)
return posp;
- if (c > '~') /* check for islower()/isupper() */
- ;
- else if (c == '\'' || c == '`') { /* previous context mark */
- pos_copy = curwin->w_pcmark; /* need to make a copy because */
- posp = &pos_copy; /* w_pcmark may be changed soon */
- } else if (c == '"') /* to pos when leaving buffer */
- posp = &(buf->b_last_cursor);
- else if (c == '^') /* to where Insert mode stopped */
- posp = &(buf->b_last_insert);
- else if (c == '.') /* to where last change was made */
- posp = &(buf->b_last_change);
- else if (c == '[') /* to start of previous operator */
+ if (c > '~') { // check for islower()/isupper()
+ } else if (c == '\'' || c == '`') { // previous context mark
+ pos_copy = curwin->w_pcmark; // need to make a copy because
+ posp = &pos_copy; // w_pcmark may be changed soon
+ } else if (c == '"') { // to pos when leaving buffer
+ posp = &(buf->b_last_cursor.mark);
+ } else if (c == '^') { // to where Insert mode stopped
+ posp = &(buf->b_last_insert.mark);
+ } else if (c == '.') { // to where last change was made
+ posp = &(buf->b_last_change.mark);
+ } else if (c == '[') { // to start of previous operator
posp = &(buf->b_op_start);
- else if (c == ']') /* to end of previous operator */
+ } else if (c == ']') { // to end of previous operator
posp = &(buf->b_op_end);
- else if (c == '{' || c == '}') { /* to previous/next paragraph */
+ } else if (c == '{' || c == '}') { // to previous/next paragraph
pos_T pos;
oparg_T oa;
int slcb = listcmd_busy;
@@ -357,7 +372,7 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
pos_copy.coladd = 0;
}
} else if (ASCII_ISLOWER(c)) { /* normal named mark */
- posp = &(buf->b_namedm[c - 'a']);
+ posp = &(buf->b_namedm[c - 'a'].mark);
} else if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { /* named file mark */
if (ascii_isdigit(c))
c = c - '0' + NMARKS;
@@ -365,8 +380,9 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
c -= 'A';
posp = &(namedfm[c].fmark.mark);
- if (namedfm[c].fmark.fnum == 0)
+ if (namedfm[c].fmark.fnum == 0) {
fname2fnum(&namedfm[c]);
+ }
if (fnum != NULL)
*fnum = namedfm[c].fmark.fnum;
@@ -420,15 +436,15 @@ getnextmark (
pos.col = MAXCOL;
for (i = 0; i < NMARKS; i++) {
- if (curbuf->b_namedm[i].lnum > 0) {
+ if (curbuf->b_namedm[i].mark.lnum > 0) {
if (dir == FORWARD) {
- if ((result == NULL || lt(curbuf->b_namedm[i], *result))
- && lt(pos, curbuf->b_namedm[i]))
- result = &curbuf->b_namedm[i];
+ if ((result == NULL || lt(curbuf->b_namedm[i].mark, *result))
+ && lt(pos, curbuf->b_namedm[i].mark))
+ result = &curbuf->b_namedm[i].mark;
} else {
- if ((result == NULL || lt(*result, curbuf->b_namedm[i]))
- && lt(curbuf->b_namedm[i], pos))
- result = &curbuf->b_namedm[i];
+ if ((result == NULL || lt(*result, curbuf->b_namedm[i].mark))
+ && lt(curbuf->b_namedm[i].mark, pos))
+ result = &curbuf->b_namedm[i].mark;
}
}
}
@@ -438,12 +454,12 @@ getnextmark (
/*
* For an xtended filemark: set the fnum from the fname.
- * This is used for marks obtained from the .viminfo file. It's postponed
+ * This is used for marks obtained from the .shada file. It's postponed
* until the mark is used to avoid a long startup delay.
*/
static void fname2fnum(xfmark_T *fm)
{
- char_u *p;
+ char_u *p;
if (fm->fname != NULL) {
/*
@@ -475,19 +491,17 @@ static void fname2fnum(xfmark_T *fm)
/*
* Check all file marks for a name that matches the file name in buf.
* May replace the name with an fnum.
- * Used for marks that come from the .viminfo file.
+ * Used for marks that come from the .shada file.
*/
void fmarks_check_names(buf_T *buf)
{
- char_u *name;
+ char_u *name = buf->b_ffname;
int i;
if (buf->b_ffname == NULL)
return;
- name = home_replace_save(buf, buf->b_ffname);
-
- for (i = 0; i < NMARKS + EXTRA_MARKS; ++i)
+ for (i = 0; i < NGLOBALMARKS; ++i)
fmarks_check_one(&namedfm[i], name, buf);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -495,8 +509,6 @@ void fmarks_check_names(buf_T *buf)
fmarks_check_one(&wp->w_jumplist[i], name, buf);
}
}
-
- xfree(name);
}
static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf)
@@ -541,23 +553,12 @@ int check_mark(pos_T *pos)
*/
void clrallmarks(buf_T *buf)
{
- static int i = -1;
-
- if (i == -1) /* first call ever: initialize */
- for (i = 0; i < NMARKS + 1; i++) {
- namedfm[i].fmark.mark.lnum = 0;
- namedfm[i].fname = NULL;
- }
-
- for (i = 0; i < NMARKS; i++)
- buf->b_namedm[i].lnum = 0;
+ memset(&(buf->b_namedm[0]), 0, sizeof(buf->b_namedm));
buf->b_op_start.lnum = 0; /* start/end op mark cleared */
buf->b_op_end.lnum = 0;
- buf->b_last_cursor.lnum = 1; /* '" mark cleared */
- buf->b_last_cursor.col = 0;
- buf->b_last_cursor.coladd = 0;
- buf->b_last_insert.lnum = 0; /* '^ mark cleared */
- buf->b_last_change.lnum = 0; /* '. mark cleared */
+ RESET_FMARK(&buf->b_last_cursor, ((pos_T) {1, 0, 0}), 0); // '" mark
+ CLEAR_FMARK(&buf->b_last_insert); // '^ mark
+ CLEAR_FMARK(&buf->b_last_change); // '. mark
buf->b_changelistlen = 0;
}
@@ -610,10 +611,10 @@ void do_marks(exarg_T *eap)
if (arg != NULL && *arg == NUL)
arg = NULL;
- show_one_mark('\'', arg, &curwin->w_pcmark, NULL, TRUE);
+ show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true);
for (i = 0; i < NMARKS; ++i)
- show_one_mark(i + 'a', arg, &curbuf->b_namedm[i], NULL, TRUE);
- for (i = 0; i < NMARKS + EXTRA_MARKS; ++i) {
+ show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true);
+ for (i = 0; i < NGLOBALMARKS; ++i) {
if (namedfm[i].fmark.fnum != 0)
name = fm_getname(&namedfm[i].fmark, 15);
else
@@ -626,14 +627,14 @@ void do_marks(exarg_T *eap)
xfree(name);
}
}
- show_one_mark('"', arg, &curbuf->b_last_cursor, NULL, TRUE);
- show_one_mark('[', arg, &curbuf->b_op_start, NULL, TRUE);
- show_one_mark(']', arg, &curbuf->b_op_end, NULL, TRUE);
- show_one_mark('^', arg, &curbuf->b_last_insert, NULL, TRUE);
- show_one_mark('.', arg, &curbuf->b_last_change, NULL, TRUE);
- show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, TRUE);
- show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, TRUE);
- show_one_mark(-1, arg, NULL, NULL, FALSE);
+ show_one_mark('"', arg, &curbuf->b_last_cursor.mark, NULL, true);
+ show_one_mark('[', arg, &curbuf->b_op_start, NULL, true);
+ show_one_mark(']', arg, &curbuf->b_op_end, NULL, true);
+ show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true);
+ show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true);
+ show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, true);
+ show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, true);
+ show_one_mark(-1, arg, NULL, NULL, false);
}
static void
@@ -727,13 +728,14 @@ void ex_delmarks(exarg_T *eap)
from = to = *p;
for (i = from; i <= to; ++i) {
- if (lower)
- curbuf->b_namedm[i - 'a'].lnum = 0;
- else {
- if (digit)
+ if (lower) {
+ curbuf->b_namedm[i - 'a'].mark.lnum = 0;
+ } else {
+ if (digit) {
n = i - '0' + NMARKS;
- else
+ } else {
n = i - 'A';
+ }
namedfm[n].fmark.mark.lnum = 0;
xfree(namedfm[n].fname);
namedfm[n].fname = NULL;
@@ -741,9 +743,9 @@ void ex_delmarks(exarg_T *eap)
}
} else
switch (*p) {
- case '"': curbuf->b_last_cursor.lnum = 0; break;
- case '^': curbuf->b_last_insert.lnum = 0; break;
- case '.': curbuf->b_last_change.lnum = 0; break;
+ case '"': CLEAR_FMARK(&curbuf->b_last_cursor); break;
+ case '^': CLEAR_FMARK(&curbuf->b_last_insert); break;
+ case '.': CLEAR_FMARK(&curbuf->b_last_change); break;
case '[': curbuf->b_op_start.lnum = 0; break;
case ']': curbuf->b_op_end.lnum = 0; break;
case '<': curbuf->b_visual.vi_start.lnum = 0; break;
@@ -811,7 +813,7 @@ void ex_changes(exarg_T *eap)
MSG_PUTS_TITLE(_("\nchange line col text"));
for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) {
- if (curbuf->b_changelist[i].lnum != 0) {
+ if (curbuf->b_changelist[i].mark.lnum != 0) {
msg_putchar('\n');
if (got_int)
break;
@@ -819,10 +821,10 @@ void ex_changes(exarg_T *eap)
i == curwin->w_changelistidx ? '>' : ' ',
i > curwin->w_changelistidx ? i - curwin->w_changelistidx
: curwin->w_changelistidx - i,
- (long)curbuf->b_changelist[i].lnum,
- curbuf->b_changelist[i].col);
+ (long)curbuf->b_changelist[i].mark.lnum,
+ curbuf->b_changelist[i].mark.col);
msg_outtrans(IObuff);
- name = mark_line(&curbuf->b_changelist[i], 17);
+ name = mark_line(&curbuf->b_changelist[i].mark, 17);
msg_outtrans_attr(name, hl_attr(HLF_D));
xfree(name);
os_breakcheck();
@@ -886,29 +888,29 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after)
if (!cmdmod.lockmarks) {
/* named marks, lower case and upper case */
for (i = 0; i < NMARKS; i++) {
- one_adjust(&(curbuf->b_namedm[i].lnum));
+ one_adjust(&(curbuf->b_namedm[i].mark.lnum));
if (namedfm[i].fmark.fnum == fnum)
one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
}
- for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) {
+ for (i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum)
one_adjust_nodel(&(namedfm[i].fmark.mark.lnum));
}
/* last Insert position */
- one_adjust(&(curbuf->b_last_insert.lnum));
+ one_adjust(&(curbuf->b_last_insert.mark.lnum));
/* last change position */
- one_adjust(&(curbuf->b_last_change.lnum));
+ one_adjust(&(curbuf->b_last_change.mark.lnum));
/* last cursor position, if it was set */
- if (!equalpos(curbuf->b_last_cursor, initpos))
- one_adjust(&(curbuf->b_last_cursor.lnum));
+ if (!equalpos(curbuf->b_last_cursor.mark, initpos))
+ one_adjust(&(curbuf->b_last_cursor.mark.lnum));
/* list of change positions */
for (i = 0; i < curbuf->b_changelistlen; ++i)
- one_adjust_nodel(&(curbuf->b_changelist[i].lnum));
+ one_adjust_nodel(&(curbuf->b_changelist[i].mark.lnum));
/* Visual area */
one_adjust_nodel(&(curbuf->b_visual.vi_start.lnum));
@@ -1038,24 +1040,24 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a
/* named marks, lower case and upper case */
for (i = 0; i < NMARKS; i++) {
- col_adjust(&(curbuf->b_namedm[i]));
+ col_adjust(&(curbuf->b_namedm[i].mark));
if (namedfm[i].fmark.fnum == fnum)
col_adjust(&(namedfm[i].fmark.mark));
}
- for (i = NMARKS; i < NMARKS + EXTRA_MARKS; i++) {
+ for (i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum)
col_adjust(&(namedfm[i].fmark.mark));
}
/* last Insert position */
- col_adjust(&(curbuf->b_last_insert));
+ col_adjust(&(curbuf->b_last_insert.mark));
/* last change position */
- col_adjust(&(curbuf->b_last_change));
+ col_adjust(&(curbuf->b_last_change.mark));
/* list of change positions */
for (i = 0; i < curbuf->b_changelistlen; ++i)
- col_adjust(&(curbuf->b_changelist[i]));
+ col_adjust(&(curbuf->b_changelist[i].mark));
/* Visual area */
col_adjust(&(curbuf->b_visual.vi_start));
@@ -1101,7 +1103,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a
* When deleting lines, this may create duplicate marks in the
* jumplist. They will be removed here for the current window.
*/
-static void cleanup_jumplist(void)
+void cleanup_jumplist(void)
{
int i;
int from, to;
@@ -1117,10 +1119,17 @@ static void cleanup_jumplist(void)
&& curwin->w_jumplist[i].fmark.mark.lnum
== curwin->w_jumplist[from].fmark.mark.lnum)
break;
- if (i >= curwin->w_jumplistlen) /* no duplicate */
- curwin->w_jumplist[to++] = curwin->w_jumplist[from];
- else
+ if (i >= curwin->w_jumplistlen) { // no duplicate
+ if (to != from) {
+ // Not using curwin->w_jumplist[to++] = curwin->w_jumplist[from] because
+ // this way valgrind complains about overlapping source and destination
+ // in memcpy() call. (clang-3.6.0, debug build with -DEXITFREE).
+ curwin->w_jumplist[to] = curwin->w_jumplist[from];
+ }
+ to++;
+ } else {
xfree(curwin->w_jumplist[from].fname);
+ }
}
if (curwin->w_jumplistidx == curwin->w_jumplistlen)
curwin->w_jumplistidx = to;
@@ -1143,382 +1152,253 @@ void copy_jumplist(win_T *from, win_T *to)
to->w_jumplistidx = from->w_jumplistidx;
}
-/*
- * Free items in the jumplist of window "wp".
- */
-void free_jumplist(win_T *wp)
-{
- int i;
-
- for (i = 0; i < wp->w_jumplistlen; ++i)
- xfree(wp->w_jumplist[i].fname);
-}
-
-void set_last_cursor(win_T *win)
+/// Iterate over jumplist items
+///
+/// @warning No jumplist-editing functions must be run while iteration is in
+/// progress.
+///
+/// @param[in] iter Iterator. Pass NULL to start iteration.
+/// @param[in] win Window for which jump list is processed.
+/// @param[out] fm Item definition.
+///
+/// @return Pointer that needs to be passed to next `mark_jumplist_iter` call or
+/// NULL if iteration is over.
+const void *mark_jumplist_iter(const void *const iter, const win_T *const win,
+ xfmark_T *const fm)
+ FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (win->w_buffer != NULL)
- win->w_buffer->b_last_cursor = win->w_cursor;
+ if (iter == NULL && win->w_jumplistlen == 0) {
+ *fm = (xfmark_T) {{{0, 0, 0}, 0, 0, NULL}, NULL};
+ return NULL;
+ }
+ const xfmark_T *const iter_mark =
+ (iter == NULL
+ ? &(win->w_jumplist[0])
+ : (const xfmark_T *const) iter);
+ *fm = *iter_mark;
+ if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) {
+ return NULL;
+ } else {
+ return iter_mark + 1;
+ }
}
-#if defined(EXITFREE)
-void free_all_marks(void)
+/// Iterate over global marks
+///
+/// @warning No mark-editing functions must be run while iteration is in
+/// progress.
+///
+/// @param[in] iter Iterator. Pass NULL to start iteration.
+/// @param[out] name Mark name.
+/// @param[out] fm Mark definition.
+///
+/// @return Pointer that needs to be passed to next `mark_global_iter` call or
+/// NULL if iteration is over.
+const void *mark_global_iter(const void *const iter, char *const name,
+ xfmark_T *const fm)
+ FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
- int i;
-
- for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
- if (namedfm[i].fmark.mark.lnum != 0)
- xfree(namedfm[i].fname);
+ *name = NUL;
+ const xfmark_T *iter_mark = (iter == NULL
+ ? &(namedfm[0])
+ : (const xfmark_T *const) iter);
+ while ((size_t) (iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)
+ && !iter_mark->fmark.mark.lnum) {
+ iter_mark++;
+ }
+ if ((size_t) (iter_mark - &(namedfm[0])) == ARRAY_SIZE(namedfm)
+ || !iter_mark->fmark.mark.lnum) {
+ return NULL;
+ }
+ size_t iter_off = (size_t) (iter_mark - &(namedfm[0]));
+ *name = (char) (iter_off < NMARKS
+ ? 'A' + (char) iter_off
+ : '0' + (char) (iter_off - NMARKS));
+ *fm = *iter_mark;
+ while ((size_t) (++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) {
+ if (iter_mark->fmark.mark.lnum) {
+ return (const void *) iter_mark;
+ }
+ }
+ return NULL;
}
-#endif
-
-int read_viminfo_filemark(vir_T *virp, int force)
+/// Get next mark and its name
+///
+/// @param[in] buf Buffer for which next mark is taken.
+/// @param[in,out] mark_name Pointer to the current mark name. Next mark name
+/// will be saved at this address as well.
+///
+/// Current mark name must either be NUL, '"', '^',
+/// '.' or 'a' .. 'z'. If it is neither of these
+/// behaviour is undefined.
+///
+/// @return Pointer to the next mark or NULL.
+static inline const fmark_T *next_buffer_mark(const buf_T *const buf,
+ char *const mark_name)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- char_u *str;
- xfmark_T *fm;
- int i;
-
- /* We only get here if line[0] == '\'' or '-'.
- * Illegal mark names are ignored (for future expansion). */
- str = virp->vir_line + 1;
- if (
- *str <= 127 &&
- ((*virp->vir_line == '\'' && (ascii_isdigit(*str) || isupper(*str)))
- || (*virp->vir_line == '-' && *str == '\''))) {
- if (*str == '\'') {
- /* If the jumplist isn't full insert fmark as oldest entry */
- if (curwin->w_jumplistlen == JUMPLISTSIZE)
- fm = NULL;
- else {
- for (i = curwin->w_jumplistlen; i > 0; --i)
- curwin->w_jumplist[i] = curwin->w_jumplist[i - 1];
- ++curwin->w_jumplistidx;
- ++curwin->w_jumplistlen;
- fm = &curwin->w_jumplist[0];
- fm->fmark.mark.lnum = 0;
- fm->fname = NULL;
- }
- } else if (ascii_isdigit(*str))
- fm = &namedfm[*str - '0' + NMARKS];
- else { // is uppercase
- assert(*str >= 'A' && *str <= 'Z');
- fm = &namedfm[*str - 'A'];
+ switch (*mark_name) {
+ case NUL: {
+ *mark_name = '"';
+ return &(buf->b_last_cursor);
}
- if (fm != NULL && (fm->fmark.mark.lnum == 0 || force)) {
- str = skipwhite(str + 1);
- fm->fmark.mark.lnum = getdigits_long(&str);
- str = skipwhite(str);
- fm->fmark.mark.col = getdigits_int(&str);
- fm->fmark.mark.coladd = 0;
- fm->fmark.fnum = 0;
- str = skipwhite(str);
- xfree(fm->fname);
- fm->fname = viminfo_readstring(virp, (int)(str - virp->vir_line),
- FALSE);
+ case '"': {
+ *mark_name = '^';
+ return &(buf->b_last_insert);
+ }
+ case '^': {
+ *mark_name = '.';
+ return &(buf->b_last_change);
+ }
+ case '.': {
+ *mark_name = 'a';
+ return &(buf->b_namedm[0]);
+ }
+ case 'z': {
+ return NULL;
+ }
+ default: {
+ (*mark_name)++;
+ return &(buf->b_namedm[*mark_name - 'a']);
}
}
- return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd);
}
-void write_viminfo_filemarks(FILE *fp)
+/// Iterate over buffer marks
+///
+/// @warning No mark-editing functions must be run while iteration is in
+/// progress.
+///
+/// @param[in] iter Iterator. Pass NULL to start iteration.
+/// @param[in] buf Buffer.
+/// @param[out] name Mark name.
+/// @param[out] fm Mark definition.
+///
+/// @return Pointer that needs to be passed to next `mark_buffer_iter` call or
+/// NULL if iteration is over.
+const void *mark_buffer_iter(const void *const iter, const buf_T *const buf,
+ char *const name, fmark_T *const fm)
+ FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
{
- int i;
- char_u *name;
- buf_T *buf;
- xfmark_T *fm;
-
- if (get_viminfo_parameter('f') == 0)
- return;
-
- fputs(_("\n# File marks:\n"), fp);
-
- /*
- * Find a mark that is the same file and position as the cursor.
- * That one, or else the last one is deleted.
- * Move '0 to '1, '1 to '2, etc. until the matching one or '9
- * Set '0 mark to current cursor position.
- */
- if (curbuf->b_ffname != NULL && !removable(curbuf->b_ffname)) {
- name = buflist_nr2name(curbuf->b_fnum, TRUE, FALSE);
- for (i = NMARKS; i < NMARKS + EXTRA_MARKS - 1; ++i)
- if (namedfm[i].fmark.mark.lnum == curwin->w_cursor.lnum
- && (namedfm[i].fname == NULL
- ? namedfm[i].fmark.fnum == curbuf->b_fnum
- : (name != NULL
- && STRCMP(name, namedfm[i].fname) == 0)))
- break;
- xfree(name);
-
- xfree(namedfm[i].fname);
- for (; i > NMARKS; --i)
- namedfm[i] = namedfm[i - 1];
- namedfm[NMARKS].fmark.mark = curwin->w_cursor;
- namedfm[NMARKS].fmark.fnum = curbuf->b_fnum;
- namedfm[NMARKS].fname = NULL;
+ *name = NUL;
+ char mark_name = (char) (iter == NULL
+ ? NUL
+ : (iter == &(buf->b_last_cursor)
+ ? '"'
+ : (iter == &(buf->b_last_insert)
+ ? '^'
+ : (iter == &(buf->b_last_change)
+ ? '.'
+ : 'a' + (char) ((const fmark_T *)iter
+ - &(buf->b_namedm[0]))))));
+ const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name);
+ while (iter_mark != NULL && iter_mark->mark.lnum == 0) {
+ iter_mark = next_buffer_mark(buf, &mark_name);
}
-
- /* Write the filemarks '0 - '9 and 'A - 'Z */
- for (i = 0; i < NMARKS + EXTRA_MARKS; i++)
- write_one_filemark(fp, &namedfm[i], '\'',
- i < NMARKS ? i + 'A' : i - NMARKS + '0');
-
- /* Write the jumplist with -' */
- fputs(_("\n# Jumplist (newest first):\n"), fp);
- setpcmark(); /* add current cursor position */
- cleanup_jumplist();
- for (fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1];
- fm >= &curwin->w_jumplist[0]; --fm) {
- if (fm->fmark.fnum == 0
- || ((buf = buflist_findnr(fm->fmark.fnum)) != NULL
- && !removable(buf->b_ffname)))
- write_one_filemark(fp, fm, '-', '\'');
+ if (iter_mark == NULL) {
+ return NULL;
+ }
+ size_t iter_off = (size_t) (iter_mark - &(buf->b_namedm[0]));
+ if (mark_name) {
+ *name = mark_name;
+ } else {
+ *name = (char) ('a' + (char) iter_off);
}
+ *fm = *iter_mark;
+ return (const void *) iter_mark;
}
-static void write_one_filemark(FILE *fp, xfmark_T *fm, int c1, int c2)
+/// Set global mark
+///
+/// @param[in] name Mark name.
+/// @param[in] fm Mark to be set.
+/// @param[in] update If true then only set global mark if it was created
+/// later then existing one.
+///
+/// @return true on success, false on failure.
+bool mark_set_global(const char name, const xfmark_T fm, const bool update)
{
- char_u *name;
-
- if (fm->fmark.mark.lnum == 0) /* not set */
- return;
-
- if (fm->fmark.fnum != 0) /* there is a buffer */
- name = buflist_nr2name(fm->fmark.fnum, TRUE, FALSE);
- else
- name = fm->fname; /* use name from .viminfo */
- if (name != NULL && *name != NUL) {
- fprintf(fp, "%c%c %" PRId64 " %" PRId64 " ",
- c1, c2, (int64_t)fm->fmark.mark.lnum, (int64_t)fm->fmark.mark.col);
- viminfo_writestring(fp, name);
+ const int idx = mark_global_index(name);
+ if (idx == -1) {
+ return false;
}
-
- if (fm->fmark.fnum != 0)
- xfree(name);
+ xfmark_T *const fm_tgt = &(namedfm[idx]);
+ if (update && fm.fmark.timestamp <= fm_tgt->fmark.timestamp) {
+ return false;
+ }
+ if (fm_tgt->fmark.mark.lnum != 0) {
+ free_xfmark(*fm_tgt);
+ }
+ *fm_tgt = fm;
+ return true;
}
-/*
- * Return TRUE if "name" is on removable media (depending on 'viminfo').
- */
-int removable(char_u *name)
+/// Set local mark
+///
+/// @param[in] name Mark name.
+/// @param[in] buf Pointer to the buffer to set mark in.
+/// @param[in] fm Mark to be set.
+/// @param[in] update If true then only set global mark if it was created
+/// later then existing one.
+///
+/// @return true on success, false on failure.
+bool mark_set_local(const char name, buf_T *const buf,
+ const fmark_T fm, const bool update)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u *p;
- char_u part[51];
- int retval = FALSE;
- size_t n;
-
- name = home_replace_save(NULL, name);
- for (p = p_viminfo; *p; ) {
- copy_option_part(&p, part, 51, ", ");
- if (part[0] == 'r') {
- n = STRLEN(part + 1);
- if (mb_strnicmp(part + 1, name, n) == 0) {
- retval = TRUE;
- break;
- }
- }
+ fmark_T *fm_tgt = NULL;
+ if (ASCII_ISLOWER(name)) {
+ fm_tgt = &(buf->b_namedm[name - 'a']);
+ } else if (name == '"') {
+ fm_tgt = &(buf->b_last_cursor);
+ } else if (name == '^') {
+ fm_tgt = &(buf->b_last_insert);
+ } else if (name == '.') {
+ fm_tgt = &(buf->b_last_change);
+ } else {
+ return false;
+ }
+ if (update && fm.timestamp <= fm_tgt->timestamp) {
+ return false;
}
- xfree(name);
- return retval;
+ if (fm_tgt->mark.lnum != 0) {
+ free_fmark(*fm_tgt);
+ }
+ *fm_tgt = fm;
+ return true;
}
-
/*
- * Write all the named marks for all buffers.
- * Return the number of buffers for which marks have been written.
+ * Free items in the jumplist of window "wp".
*/
-int write_viminfo_marks(FILE *fp_out)
+void free_jumplist(win_T *wp)
{
- /*
- * Set b_last_cursor for the all buffers that have a window.
- */
- FOR_ALL_TAB_WINDOWS(tp, win) {
- set_last_cursor(win);
- }
+ int i;
- fputs(_("\n# History of marks within files (newest to oldest):\n"), fp_out);
- int count = 0;
- FOR_ALL_BUFFERS(buf) {
- /*
- * Only write something if buffer has been loaded and at least one
- * mark is set.
- */
- if (buf->b_marks_read) {
- bool is_mark_set = true;
- if (buf->b_last_cursor.lnum == 0) {
- is_mark_set = false;
- for (int i = 0; i < NMARKS; i++) {
- if (buf->b_namedm[i].lnum != 0) {
- is_mark_set = true;
- break;
- }
- }
- }
- if (is_mark_set && buf->b_ffname != NULL
- && buf->b_ffname[0] != NUL && !removable(buf->b_ffname)) {
- home_replace(NULL, buf->b_ffname, IObuff, IOSIZE, TRUE);
- fprintf(fp_out, "\n> ");
- viminfo_writestring(fp_out, IObuff);
- write_one_mark(fp_out, '"', &buf->b_last_cursor);
- write_one_mark(fp_out, '^', &buf->b_last_insert);
- write_one_mark(fp_out, '.', &buf->b_last_change);
- /* changelist positions are stored oldest first */
- for (int i = 0; i < buf->b_changelistlen; ++i) {
- write_one_mark(fp_out, '+', &buf->b_changelist[i]);
- }
- for (int i = 0; i < NMARKS; i++) {
- write_one_mark(fp_out, 'a' + i, &buf->b_namedm[i]);
- }
- count++;
- }
- }
+ for (i = 0; i < wp->w_jumplistlen; ++i) {
+ free_xfmark(wp->w_jumplist[i]);
}
-
- return count;
+ wp->w_jumplistlen = 0;
}
-static void write_one_mark(FILE *fp_out, int c, pos_T *pos)
+void set_last_cursor(win_T *win)
{
- if (pos->lnum != 0)
- fprintf(fp_out, "\t%c\t%" PRId64 "\t%d\n", c,
- (int64_t)pos->lnum, (int)pos->col);
+ if (win->w_buffer != NULL) {
+ RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0);
+ }
}
-/*
- * Handle marks in the viminfo file:
- * fp_out != NULL: copy marks for buffers not in buffer list
- * fp_out == NULL && (flags & VIF_WANT_MARKS): read marks for curbuf only
- * fp_out == NULL && (flags & VIF_GET_OLDFILES | VIF_FORCEIT): fill v:oldfiles
- */
-void copy_viminfo_marks(vir_T *virp, FILE *fp_out, int count, int eof, int flags)
+#if defined(EXITFREE)
+void free_all_marks(void)
{
- char_u *line = virp->vir_line;
- buf_T *buf;
- int num_marked_files;
- int load_marks;
- int copy_marks_out;
- char_u *str;
int i;
- char_u *p;
- char_u *name_buf;
- pos_T pos;
- list_T *list = NULL;
-
- name_buf = xmalloc(LSIZE);
- *name_buf = NUL;
-
- if (fp_out == NULL && (flags & (VIF_GET_OLDFILES | VIF_FORCEIT))) {
- list = list_alloc();
- set_vim_var_list(VV_OLDFILES, list);
- }
-
- num_marked_files = get_viminfo_parameter('\'');
- while (!eof && (count < num_marked_files || fp_out == NULL)) {
- if (line[0] != '>') {
- if (line[0] != '\n' && line[0] != '\r' && line[0] != '#') {
- if (viminfo_error("E576: ", _("Missing '>'"), line))
- break; /* too many errors, return now */
- }
- eof = vim_fgets(line, LSIZE, virp->vir_fd);
- continue; /* Skip this dud line */
- }
-
- /*
- * Handle long line and translate escaped characters.
- * Find file name, set str to start.
- * Ignore leading and trailing white space.
- */
- str = skipwhite(line + 1);
- str = viminfo_readstring(virp, (int)(str - virp->vir_line), FALSE);
- p = str + STRLEN(str);
- while (p != str && (*p == NUL || ascii_isspace(*p)))
- p--;
- if (*p)
- p++;
- *p = NUL;
-
- if (list != NULL)
- list_append_string(list, str, -1);
-
- /*
- * If fp_out == NULL, load marks for current buffer.
- * If fp_out != NULL, copy marks for buffers not in buflist.
- */
- load_marks = copy_marks_out = FALSE;
- if (fp_out == NULL) {
- if ((flags & VIF_WANT_MARKS) && curbuf->b_ffname != NULL) {
- if (*name_buf == NUL) /* only need to do this once */
- home_replace(NULL, curbuf->b_ffname, name_buf, LSIZE, TRUE);
- if (fnamecmp(str, name_buf) == 0)
- load_marks = TRUE;
- }
- } else { /* fp_out != NULL */
- /* This is slow if there are many buffers!! */
- buf = NULL;
- FOR_ALL_BUFFERS(bp) {
- if (bp->b_ffname != NULL) {
- home_replace(NULL, bp->b_ffname, name_buf, LSIZE, TRUE);
- if (fnamecmp(str, name_buf) == 0) {
- buf = bp;
- break;
- }
- }
- }
- /*
- * copy marks if the buffer has not been loaded
- */
- if (buf == NULL || !buf->b_marks_read) {
- copy_marks_out = TRUE;
- fputs("\n> ", fp_out);
- viminfo_writestring(fp_out, str);
- count++;
- }
- }
- xfree(str);
-
- pos.coladd = 0;
- while (!(eof = viminfo_readline(virp)) && line[0] == TAB) {
- if (load_marks) {
- if (line[1] != NUL) {
- int64_t lnum_64;
- unsigned int u;
- sscanf((char *)line + 2, "%" SCNd64 "%u", &lnum_64, &u);
- // safely downcast to linenr_T (long); remove when linenr_T refactored
- assert(lnum_64 <= LONG_MAX);
- pos.lnum = (linenr_T)lnum_64;
- assert(u <= INT_MAX);
- pos.col = (colnr_T)u;
- switch (line[1]) {
- case '"': curbuf->b_last_cursor = pos; break;
- case '^': curbuf->b_last_insert = pos; break;
- case '.': curbuf->b_last_change = pos; break;
- case '+':
- /* changelist positions are stored oldest
- * first */
- if (curbuf->b_changelistlen == JUMPLISTSIZE)
- /* list is full, remove oldest entry */
- memmove(curbuf->b_changelist,
- curbuf->b_changelist + 1,
- sizeof(pos_T) * (JUMPLISTSIZE - 1));
- else
- ++curbuf->b_changelistlen;
- curbuf->b_changelist[
- curbuf->b_changelistlen - 1] = pos;
- break;
- default: if ((i = line[1] - 'a') >= 0 && i < NMARKS)
- curbuf->b_namedm[i] = pos;
- }
- }
- } else if (copy_marks_out)
- fputs((char *)line, fp_out);
- }
- if (load_marks) {
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == curbuf)
- wp->w_changelistidx = curbuf->b_changelistlen;
- }
- break;
+ for (i = 0; i < NGLOBALMARKS; i++) {
+ if (namedfm[i].fmark.mark.lnum != 0) {
+ free_xfmark(namedfm[i]);
}
}
- xfree(name_buf);
+ memset(&namedfm[0], 0, sizeof(namedfm));
}
+#endif
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index aa89a5b625..aff6e7273a 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -1,9 +1,78 @@
#ifndef NVIM_MARK_H
#define NVIM_MARK_H
+#include "nvim/macros.h"
+#include "nvim/ascii.h"
#include "nvim/buffer_defs.h"
#include "nvim/mark_defs.h"
+#include "nvim/memory.h"
#include "nvim/pos.h"
+#include "nvim/os/time.h"
+
+/// Set fmark using given value
+#define SET_FMARK(fmarkp_, mark_, fnum_) \
+ do { \
+ fmark_T *const fmarkp__ = fmarkp_; \
+ fmarkp__->mark = mark_; \
+ fmarkp__->fnum = fnum_; \
+ fmarkp__->timestamp = os_time(); \
+ fmarkp__->additional_data = NULL; \
+ } while (0)
+
+/// Free and set fmark using given value
+#define RESET_FMARK(fmarkp_, mark_, fnum_) \
+ do { \
+ fmark_T *const fmarkp___ = fmarkp_; \
+ free_fmark(*fmarkp___); \
+ SET_FMARK(fmarkp___, mark_, fnum_); \
+ } while (0)
+
+/// Clear given fmark
+#define CLEAR_FMARK(fmarkp_) \
+ RESET_FMARK(fmarkp_, ((pos_T) {0, 0, 0}), 0)
+
+/// Set given extended mark (regular mark + file name)
+#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
+ do { \
+ xfmark_T *const xfmarkp__ = xfmarkp_; \
+ xfmarkp__->fname = fname_; \
+ SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
+ } while (0)
+
+/// Free and set given extended mark (regular mark + file name)
+#define RESET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \
+ do { \
+ xfmark_T *const xfmarkp__ = xfmarkp_; \
+ free_xfmark(*xfmarkp__); \
+ xfmarkp__->fname = fname_; \
+ SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \
+ } while (0)
+
+/// Convert mark name to the offset
+static inline int mark_global_index(const char name)
+ FUNC_ATTR_CONST
+{
+ return (ASCII_ISUPPER(name)
+ ? (name - 'A')
+ : (ascii_isdigit(name)
+ ? (NMARKS + (name - '0'))
+ : -1));
+}
+
+/// Convert local mark name to the offset
+static inline int mark_local_index(const char name)
+ FUNC_ATTR_CONST
+{
+ return (ASCII_ISLOWER(name)
+ ? (name - 'a')
+ : (name == '"'
+ ? NMARKS
+ : (name == '^'
+ ? NMARKS + 1
+ : (name == '.'
+ ? NMARKS + 2
+ : -1))));
+}
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.h.generated.h"
diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h
index 67392234d3..720b2475ed 100644
--- a/src/nvim/mark_defs.h
+++ b/src/nvim/mark_defs.h
@@ -2,25 +2,47 @@
#define NVIM_MARK_DEFS_H
#include "nvim/pos.h"
+#include "nvim/os/time.h"
+#include "nvim/eval_defs.h"
/*
* marks: positions in a file
* (a normal mark is a lnum/col pair, the same as a file position)
*/
-#define NMARKS ('z' - 'a' + 1) /* max. # of named marks */
-#define JUMPLISTSIZE 100 /* max. # of marks in jump list */
-#define TAGSTACKSIZE 20 /* max. # of tags in tag stack */
+/// Number of possible numbered global marks
+#define EXTRA_MARKS ('9' - '0' + 1)
+/// Maximum possible number of letter marks
+#define NMARKS ('z' - 'a' + 1)
+
+/// Total possible number of global marks
+#define NGLOBALMARKS (NMARKS + EXTRA_MARKS)
+
+/// Total possible number of local marks
+///
+/// That are uppercase marks plus '"', '^' and '.'. There are other local marks,
+/// but they are not saved in ShaDa files.
+#define NLOCALMARKS (NMARKS + 3)
+
+/// Maximum number of marks in jump list
+#define JUMPLISTSIZE 100
+
+/// Maximum number of tags in tag stack
+#define TAGSTACKSIZE 20
+
+/// Structure defining single local mark
typedef struct filemark {
- pos_T mark; /* cursor position */
- int fnum; /* file number */
+ pos_T mark; ///< Cursor position.
+ int fnum; ///< File number.
+ Timestamp timestamp; ///< Time when this mark was last set.
+ dict_T *additional_data; ///< Additional data from ShaDa file.
} fmark_T;
-/* Xtended file mark: also has a file name */
+/// Structure defining extended mark (mark with file name attached)
typedef struct xfilemark {
- fmark_T fmark;
- char_u *fname; /* file name, used when fnum == 0 */
+ fmark_T fmark; ///< Actual mark.
+ char_u *fname; ///< File name, used when fnum == 0.
} xfmark_T;
#endif // NVIM_MARK_DEFS_H
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 100b66a608..087d2e677c 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -66,7 +66,7 @@
* (4) The encoding of the file is specified with 'fileencoding'. Conversion
* is to be done when it's different from 'encoding'.
*
- * The viminfo file is a special case: Only text is converted, not file names.
+ * The ShaDa file is a special case: Only text is converted, not file names.
* Vim scripts may contain an ":encoding" command. This has an effect for
* some commands, like ":menutrans"
*/
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 132c895997..d25dc7c941 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -30,7 +30,7 @@
/// Try to free memory. Used when trying to recover from out of memory errors.
/// @see {xmalloc}
-static void try_to_free_memory(void)
+void try_to_free_memory(void)
{
static bool trying_to_free = false;
// avoid recursive calls
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 8407198b13..1f1b5c2aa9 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -2042,8 +2042,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
/* set the '. mark */
if (!cmdmod.keepjumps) {
- curbuf->b_last_change.lnum = lnum;
- curbuf->b_last_change.col = col;
+ RESET_FMARK(&curbuf->b_last_change, ((pos_T) {lnum, col, 0}), 0);
/* Create a new entry if a new undo-able change was started or we
* don't have an entry yet. */
@@ -2054,7 +2053,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
/* Don't create a new entry when the line number is the same
* as the last one and the column is not too far away. Avoids
* creating many entries for typing "xxxxx". */
- p = &curbuf->b_changelist[curbuf->b_changelistlen - 1];
+ p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
if (p->lnum != lnum)
add = TRUE;
else {
@@ -2074,7 +2073,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
/* changelist is full: remove oldest entry */
curbuf->b_changelistlen = JUMPLISTSIZE - 1;
memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
- sizeof(pos_T) * (JUMPLISTSIZE - 1));
+ sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
FOR_ALL_TAB_WINDOWS(tp, wp) {
/* Correct position in changelist for other windows on
* this buffer. */
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index 473958c765..a119c4653a 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -225,8 +225,8 @@ void msgpack_rpc_from_float(Float result, msgpack_packer *res)
void msgpack_rpc_from_string(String result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{
- msgpack_pack_bin(res, result.size);
- msgpack_pack_bin_body(res, result.data, result.size);
+ msgpack_pack_str(res, result.size);
+ msgpack_pack_str_body(res, result.data, result.size);
}
void msgpack_rpc_from_object(Object result, msgpack_packer *res)
@@ -332,8 +332,7 @@ void msgpack_rpc_serialize_request(uint64_t request_id,
msgpack_pack_uint64(pac, request_id);
}
- msgpack_pack_bin(pac, method.size);
- msgpack_pack_bin_body(pac, method.data, method.size);
+ msgpack_rpc_from_string(method, pac);
msgpack_rpc_from_array(args, pac);
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 467b74f9e6..5354fb20ad 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -6327,8 +6327,8 @@ static void nv_g_cmd(cmdarg_T *cap)
* "gi": start Insert at the last position.
*/
case 'i':
- if (curbuf->b_last_insert.lnum != 0) {
- curwin->w_cursor = curbuf->b_last_insert;
+ if (curbuf->b_last_insert.mark.lnum != 0) {
+ curwin->w_cursor = curbuf->b_last_insert.mark;
check_cursor_lnum();
i = (int)STRLEN(get_cursor_line_ptr());
if (curwin->w_cursor.col > (colnr_T)i) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 3163132b8a..3fd2c0b773 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -50,26 +50,11 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/input.h"
-
-/*
- * Registers:
- * 0 = register for latest (unnamed) yank
- * 1..9 = registers '1' to '9', for deletes
- * 10..35 = registers 'a' to 'z'
- * 36 = delete register '-'
- * 37 = selection register '*'
- * 38 = clipboard register '+'
- */
-#define DELETION_REGISTER 36
-#define NUM_SAVED_REGISTERS 37
-// The following registers should not be saved in viminfo:
-#define STAR_REGISTER 37
-#define PLUS_REGISTER 38
-#define NUM_REGISTERS 39
+#include "nvim/os/time.h"
static yankreg_T y_regs[NUM_REGISTERS];
-static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
+static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
static bool clipboard_didwarn_unnamed = false;
@@ -778,19 +763,11 @@ yankreg_T *get_yank_register(int regname, int mode)
return y_previous;
}
- int i = 0; // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
- if (ascii_isdigit(regname))
- i = regname - '0';
- else if (ASCII_ISLOWER(regname))
- i = CharOrdLow(regname) + 10;
- else if (ASCII_ISUPPER(regname)) {
- i = CharOrdUp(regname) + 10;
- } else if (regname == '-')
- i = DELETION_REGISTER;
- else if (regname == '*')
- i = STAR_REGISTER;
- else if (regname == '+')
- i = PLUS_REGISTER;
+ int i = op_reg_index(regname);
+ // when not 0-9, a-z, A-Z or '-'/'+'/'*': use register 0
+ if (i == -1) {
+ i = 0;
+ }
reg = &y_regs[i];
if (mode == YREG_YANK) {
@@ -890,6 +867,16 @@ int do_record(int c)
return retval;
}
+static void set_yreg_additional_data(yankreg_T *reg, dict_T *additional_data)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (reg->additional_data == additional_data) {
+ return;
+ }
+ dict_unref(reg->additional_data);
+ reg->additional_data = additional_data;
+}
+
/*
* Stuff string "p" into yank register "regname" as a single line (append if
* uppercase). "p" must have been alloced.
@@ -919,11 +906,13 @@ static int stuff_yank(int regname, char_u *p)
*pp = lp;
} else {
free_register(reg);
+ set_yreg_additional_data(reg, NULL);
reg->y_array = (char_u **)xmalloc(sizeof(char_u *));
reg->y_array[0] = p;
reg->y_size = 1;
reg->y_type = MCHAR; /* used to be MLINE, why? */
}
+ reg->timestamp = os_time();
return OK;
}
@@ -2266,10 +2255,7 @@ int op_change(oparg_T *oap)
*/
void init_yank(void)
{
- int i;
-
- for (i = 0; i < NUM_REGISTERS; i++)
- y_regs[i].y_array = NULL;
+ memset(&(y_regs[0]), 0, sizeof(y_regs));
}
#if defined(EXITFREE)
@@ -2291,6 +2277,7 @@ void clear_registers(void)
void free_register(yankreg_T *reg)
FUNC_ATTR_NONNULL_ALL
{
+ set_yreg_additional_data(reg, NULL);
if (reg->y_array != NULL) {
long i;
@@ -2369,6 +2356,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
reg->y_type = yanktype; /* set the yank register type */
reg->y_width = 0;
reg->y_array = xcalloc(yanklines, sizeof(char_u *));
+ reg->additional_data = NULL;
+ reg->timestamp = os_time();
y_idx = 0;
lnum = oap->start.lnum;
@@ -4433,171 +4422,6 @@ int do_addsub(int command, linenr_T Prenum1)
return OK;
}
-int read_viminfo_register(vir_T *virp, int force)
-{
- int eof;
- int do_it = TRUE;
- int size;
- int limit;
- int set_prev = FALSE;
- char_u *str;
- char_u **array = NULL;
-
- /* We only get here (hopefully) if line[0] == '"' */
- str = virp->vir_line + 1;
-
- /* If the line starts with "" this is the y_previous register. */
- if (*str == '"') {
- set_prev = TRUE;
- str++;
- }
-
- if (!ASCII_ISALNUM(*str) && *str != '-') {
- if (viminfo_error("E577: ", _("Illegal register name"), virp->vir_line))
- return TRUE; /* too many errors, pretend end-of-file */
- do_it = FALSE;
- }
- yankreg_T *reg = get_yank_register(*str++, YREG_PUT);
- if (!force && reg->y_array != NULL)
- do_it = FALSE;
-
- if (*str == '@') {
- /* "x@: register x used for @@ */
- if (force || execreg_lastc == NUL)
- execreg_lastc = str[-1];
- }
-
- size = 0;
- limit = 100; /* Optimized for registers containing <= 100 lines */
- if (do_it) {
- if (set_prev) {
- y_previous = reg;
- }
-
- free_register(reg);
- array = xmalloc(limit * sizeof(char_u *));
-
- str = skipwhite(skiptowhite(str));
- if (STRNCMP(str, "CHAR", 4) == 0) {
- reg->y_type = MCHAR;
- } else if (STRNCMP(str, "BLOCK", 5) == 0) {
- reg->y_type = MBLOCK;
- } else {
- reg->y_type = MLINE;
- }
- /* get the block width; if it's missing we get a zero, which is OK */
- str = skipwhite(skiptowhite(str));
- reg->y_width = getdigits_int(&str);
- }
-
- while (!(eof = viminfo_readline(virp))
- && (virp->vir_line[0] == TAB || virp->vir_line[0] == '<')) {
- if (do_it) {
- if (size >= limit) {
- limit *= 2;
- array = xrealloc(array, limit * sizeof(char_u *));
- }
- array[size++] = viminfo_readstring(virp, 1, TRUE);
- }
- }
-
- if (do_it) {
- if (size == 0) {
- xfree(array);
- } else if (size < limit) {
- reg->y_array = xrealloc(array, size * sizeof(char_u *));
- } else {
- reg->y_array = array;
- }
- reg->y_size = size;
- }
- return eof;
-}
-
-void write_viminfo_registers(FILE *fp)
-{
- int i, j;
- char_u *type;
- char_u c;
- int num_lines;
- int max_num_lines;
- int max_kbyte;
- long len;
-
- fputs(_("\n# Registers:\n"), fp);
-
- /* Get '<' value, use old '"' value if '<' is not found. */
- max_num_lines = get_viminfo_parameter('<');
- if (max_num_lines < 0)
- max_num_lines = get_viminfo_parameter('"');
- if (max_num_lines == 0)
- return;
- max_kbyte = get_viminfo_parameter('s');
- if (max_kbyte == 0)
- return;
-
- // don't include clipboard registers '*'/'+'
- for (i = 0; i < NUM_SAVED_REGISTERS; i++) {
- if (y_regs[i].y_array == NULL)
- continue;
-
- /* Skip empty registers. */
- num_lines = y_regs[i].y_size;
- if (num_lines == 0
- || (num_lines == 1 && y_regs[i].y_type == MCHAR
- && *y_regs[i].y_array[0] == NUL))
- continue;
-
- if (max_kbyte > 0) {
- /* Skip register if there is more text than the maximum size. */
- len = 0;
- for (j = 0; j < num_lines; j++)
- len += (long)STRLEN(y_regs[i].y_array[j]) + 1L;
- if (len > (long)max_kbyte * 1024L)
- continue;
- }
-
- switch (y_regs[i].y_type) {
- case MLINE:
- type = (char_u *)"LINE";
- break;
- case MCHAR:
- type = (char_u *)"CHAR";
- break;
- case MBLOCK:
- type = (char_u *)"BLOCK";
- break;
- default:
- sprintf((char *)IObuff, _("E574: Unknown register type %d"),
- y_regs[i].y_type);
- emsg(IObuff);
- type = (char_u *)"LINE";
- break;
- }
- if (y_previous == &y_regs[i])
- fprintf(fp, "\"");
- c = get_register_name(i);
- fprintf(fp, "\"%c", c);
- if (c == execreg_lastc)
- fprintf(fp, "@");
- fprintf(fp, "\t%s\t%d\n", type,
- (int)y_regs[i].y_width
- );
-
- /* If max_num_lines < 0, then we save ALL the lines in the register */
- if (max_num_lines > 0 && num_lines > max_num_lines)
- num_lines = max_num_lines;
- for (j = 0; j < num_lines; j++) {
- putc('\t', fp);
- viminfo_writestring(fp, y_regs[i].y_array[j]);
- }
- }
-}
-
-
-
-
-
/*
* Return the type of a register.
* Used for getregtype()
@@ -4739,7 +4563,6 @@ void *get_reg_contents(int regname, int flags)
return retval;
}
-
static yankreg_T *init_write_reg(int name, yankreg_T **old_y_previous, bool must_append)
{
if (!valid_yank_reg(name, true)) { // check for valid reg name
@@ -4973,6 +4796,8 @@ static void str_to_reg(yankreg_T *y_ptr, int yank_type, const char_u *str,
}
y_ptr->y_type = type;
y_ptr->y_size = lnum;
+ set_yreg_additional_data(y_ptr, NULL);
+ y_ptr->timestamp = os_time();
if (type == MBLOCK) {
y_ptr->y_width = (blocklen == -1 ? (colnr_T) maxlen - 1 : blocklen);
} else {
@@ -5363,6 +5188,10 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
reg->y_array = xcalloc(lines->lv_len, sizeof(uint8_t *));
reg->y_size = lines->lv_len;
+ reg->additional_data = NULL;
+ reg->timestamp = 0;
+ // Timestamp is not saved for clipboard registers because clipboard registers
+ // are not saved in the ShaDa file.
int i = 0;
for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) {
@@ -5411,6 +5240,8 @@ err:
}
reg->y_array = NULL;
reg->y_size = 0;
+ reg->additional_data = NULL;
+ reg->timestamp = 0;
if (errmsg) {
EMSG("clipboard: provider returned invalid data");
}
@@ -5478,3 +5309,91 @@ void end_global_changes(void)
clipboard_needs_update = false;
}
}
+
+/// Check whether register is empty
+static inline bool reg_empty(const yankreg_T *const reg)
+ FUNC_ATTR_PURE
+{
+ return (reg->y_array == NULL
+ || reg->y_size == 0
+ || (reg->y_size == 1
+ && reg->y_type == MCHAR
+ && *(reg->y_array[0]) == NUL));
+}
+
+/// Iterate over registerrs
+///
+/// @param[in] iter Iterator. Pass NULL to start iteration.
+/// @param[out] name Register name.
+/// @param[out] reg Register contents.
+///
+/// @return Pointer that needs to be passed to next `op_register_iter` call or
+/// NULL if iteration is over.
+const void *op_register_iter(const void *const iter, char *const name,
+ yankreg_T *const reg)
+ FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ *name = NUL;
+ const yankreg_T *iter_reg = (iter == NULL
+ ? &(y_regs[0])
+ : (const yankreg_T *const) iter);
+ while (iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
+ iter_reg++;
+ }
+ if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
+ return NULL;
+ }
+ size_t iter_off = iter_reg - &(y_regs[0]);
+ *name = (char) get_register_name(iter_off);
+ *reg = *iter_reg;
+ while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
+ if (!reg_empty(iter_reg)) {
+ return (void *) iter_reg;
+ }
+ }
+ return NULL;
+}
+
+/// Get a number of non-empty registers
+size_t op_register_amount(void)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t ret = 0;
+ for (size_t i = 0; i < NUM_SAVED_REGISTERS; i++) {
+ if (!reg_empty(y_regs + i)) {
+ ret++;
+ }
+ }
+ return ret;
+}
+
+/// Set register to a given value
+///
+/// @param[in] name Register name.
+/// @param[in] reg Register value.
+///
+/// @return true on success, false on failure.
+bool op_register_set(const char name, const yankreg_T reg)
+{
+ int i = op_reg_index(name);
+ if (i == -1) {
+ return false;
+ }
+ free_register(&y_regs[i]);
+ y_regs[i] = reg;
+ return true;
+}
+
+/// Get register with the given name
+///
+/// @param[in] name Register name.
+///
+/// @return Pointer to the register contents or NULL.
+const yankreg_T *op_register_get(const char name)
+{
+ int i = op_reg_index(name);
+ if (i == -1) {
+ return NULL;
+ }
+ return &y_regs[i];
+}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 99683165f9..507f933acf 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -3,7 +3,11 @@
#include <stdbool.h>
+#include "nvim/macros.h"
+#include "nvim/ascii.h"
#include "nvim/types.h"
+#include "nvim/eval_defs.h"
+#include "nvim/os/time.h"
typedef int (*Indenter)(void);
@@ -16,6 +20,22 @@ typedef int (*Indenter)(void);
#define PUT_LINE_FORWARD 32 /* put linewise register below Visual sel. */
/*
+ * Registers:
+ * 0 = register for latest (unnamed) yank
+ * 1..9 = registers '1' to '9', for deletes
+ * 10..35 = registers 'a' to 'z'
+ * 36 = delete register '-'
+ * 37 = selection register '*'
+ * 38 = clipboard register '+'
+ */
+#define DELETION_REGISTER 36
+#define NUM_SAVED_REGISTERS 37
+// The following registers should not be saved in ShaDa file:
+#define STAR_REGISTER 37
+#define PLUS_REGISTER 38
+#define NUM_REGISTERS 39
+
+/*
* Operator IDs; The order must correspond to opchars[] in ops.c!
*/
#define OP_NOP 0 /* no pending operation */
@@ -47,14 +67,6 @@ typedef int (*Indenter)(void);
#define OP_FORMAT2 26 /* "gw" format operator, keeps cursor pos */
#define OP_FUNCTION 27 /* "g@" call 'operatorfunc' */
-/// Contents of a yank (read-write) register
-typedef struct yankreg {
- char_u **y_array; ///< pointer to array of line pointers
- linenr_T y_size; ///< number of lines in y_array
- char_u y_type; ///< MLINE, MCHAR or MBLOCK
- colnr_T y_width; ///< only set if y_type == MBLOCK
-} yankreg_T;
-
/// Flags for get_reg_contents().
enum GRegFlags {
kGRegNoExpr = 1, ///< Do not allow expression register.
@@ -62,6 +74,41 @@ enum GRegFlags {
kGRegList = 4 ///< Return list.
};
+/// Definition of one register
+typedef struct yankreg {
+ char_u **y_array; ///< Pointer to an array of line pointers.
+ linenr_T y_size; ///< Number of lines in y_array.
+ char_u y_type; ///< Register type: MLINE, MCHAR or MBLOCK.
+ colnr_T y_width; ///< Register width (only valid for y_type == MBLOCK).
+ Timestamp timestamp; ///< Time when register was last modified.
+ dict_T *additional_data; ///< Additional data from ShaDa file.
+} yankreg_T;
+
+/// Convert register name into register index
+///
+/// @param[in] regname Register name.
+///
+/// @return Index in y_regs array or -1 if register name was not recognized.
+static inline int op_reg_index(const int regname)
+ FUNC_ATTR_CONST
+{
+ if (ascii_isdigit(regname)) {
+ return regname - '0';
+ } else if (ASCII_ISLOWER(regname)) {
+ return CharOrdLow(regname) + 10;
+ } else if (ASCII_ISUPPER(regname)) {
+ return CharOrdUp(regname) + 10;
+ } else if (regname == '-') {
+ return DELETION_REGISTER;
+ } else if (regname == '*') {
+ return STAR_REGISTER;
+ } else if (regname == '+') {
+ return PLUS_REGISTER;
+ } else {
+ return -1;
+ }
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
#endif
diff --git a/src/nvim/option.c b/src/nvim/option.c
index c47616620c..cbb22a0546 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1738,16 +1738,16 @@ set_options_bin (
/*
* Find the parameter represented by the given character (eg ', :, ", or /),
- * and return its associated value in the 'viminfo' string.
+ * and return its associated value in the 'shada' string.
* Only works for number parameters, not for 'r' or 'n'.
* If the parameter is not specified in the string or there is no following
* number, return -1.
*/
-int get_viminfo_parameter(int type)
+int get_shada_parameter(int type)
{
char_u *p;
- p = find_viminfo_parameter(type);
+ p = find_shada_parameter(type);
if (p != NULL && ascii_isdigit(*p))
return atoi((char *)p);
return -1;
@@ -1755,14 +1755,14 @@ int get_viminfo_parameter(int type)
/*
* Find the parameter represented by the given character (eg ''', ':', '"', or
- * '/') in the 'viminfo' option and return a pointer to the string after it.
+ * '/') in the 'shada' option and return a pointer to the string after it.
* Return NULL if the parameter is not specified in the string.
*/
-char_u *find_viminfo_parameter(int type)
+char_u *find_shada_parameter(int type)
{
char_u *p;
- for (p = p_viminfo; *p; ++p) {
+ for (p = p_shada; *p; ++p) {
if (*p == type)
return p + 1;
if (*p == 'n') /* 'n' is always the last one */
@@ -1968,6 +1968,8 @@ static void redraw_titles(void) {
redraw_tabline = TRUE;
}
+static int shada_idx = -1;
+
/*
* Set a string option to a new value (without checking the effect).
* The string is copied into allocated memory.
@@ -2001,6 +2003,8 @@ set_string_option_direct (
if (options[idx].var == NULL) /* can't set hidden option */
return;
+ assert((void *) options[idx].var != (void *) &p_shada);
+
s = vim_strsave(val);
{
varp = (char_u **)get_varp_scope(&(options[idx]),
@@ -2441,10 +2445,16 @@ did_set_string_option (
verbose_stop();
if (*p_vfile != NUL && verbose_open() == FAIL)
errmsg = e_invarg;
- }
- /* 'viminfo' */
- else if (varp == &p_viminfo) {
- for (s = p_viminfo; *s; ) {
+ /* 'shada' */
+ } else if (varp == &p_shada) {
+ // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo
+ // option.
+ opt_idx = ((options[opt_idx].fullname[0] == 'v')
+ ? (shada_idx == -1
+ ? ((shada_idx = findoption((char_u *) "shada")))
+ : shada_idx)
+ : opt_idx);
+ for (s = p_shada; *s; ) {
/* Check it's a valid character */
if (vim_strchr((char_u *)"!\"%'/:<@cfhnrs", *s) == NULL) {
errmsg = illegal_char(errbuf, *s);
@@ -2486,7 +2496,7 @@ did_set_string_option (
break;
}
}
- if (*p_viminfo && errmsg == NULL && get_viminfo_parameter('\'') < 0)
+ if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0)
errmsg = (char_u *)N_("E528: Must specify a ' value");
}
/* 'showbreak' */
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 5340f4bdd5..ab3169bff6 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -558,7 +558,7 @@ EXTERN long p_ur; /* 'undoreload' */
EXTERN long p_uc; /* 'updatecount' */
EXTERN long p_ut; /* 'updatetime' */
EXTERN char_u *p_fcs; /* 'fillchar' */
-EXTERN char_u *p_viminfo; /* 'viminfo' */
+EXTERN char_u *p_shada; /* 'shada' */
EXTERN char_u *p_vdir; /* 'viewdir' */
EXTERN char_u *p_vop; /* 'viewoptions' */
EXTERN unsigned vop_flags; /* uses SSOP_ flags */
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index ca3882b8a0..5973e4b938 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1992,6 +1992,14 @@ return {
}}
},
{
+ full_name='shada', abbreviation='sd',
+ type='string', list='comma', scope={'global'},
+ deny_duplicates=true,
+ secure=true,
+ varname='p_shada',
+ defaults={if_true={vi="", vim="!,'100,<50,s10,h"}}
+ },
+ {
full_name='shell', abbreviation='sh',
type='string', scope={'global'},
secure=true,
@@ -2584,7 +2592,7 @@ return {
type='string', list='comma', scope={'global'},
deny_duplicates=true,
secure=true,
- varname='p_viminfo',
+ varname='p_shada',
defaults={if_true={vi="", vim="!,'100,<50,s10,h"}}
},
{
diff --git a/src/nvim/os/fs_defs.h b/src/nvim/os/fs_defs.h
index ddd382a3cb..df1031b721 100644
--- a/src/nvim/os/fs_defs.h
+++ b/src/nvim/os/fs_defs.h
@@ -21,4 +21,9 @@ typedef struct {
uv_dirent_t ent; ///< @private The entry information.
} Directory;
+/// Function to convert -errno error to char * error description
+///
+/// -errno errors are returned by a number of os functions.
+#define os_strerror uv_strerror
+
#endif // NVIM_OS_FS_DEFS_H
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index ee17938afc..ba1dcf631a 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -103,3 +103,12 @@ struct tm *os_get_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
time_t rawtime = time(NULL);
return os_localtime_r(&rawtime, result);
}
+
+/// Obtains the current UNIX timestamp
+///
+/// @return Seconds since epoch.
+Timestamp os_time(void)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (Timestamp) time(NULL);
+}
diff --git a/src/nvim/os/time.h b/src/nvim/os/time.h
index b21808307f..ad4886446a 100644
--- a/src/nvim/os/time.h
+++ b/src/nvim/os/time.h
@@ -5,6 +5,8 @@
#include <stdbool.h>
#include <time.h>
+typedef uint64_t Timestamp;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.h.generated.h"
#endif
diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h
index 9ab4ba1c1a..949973bf40 100644
--- a/src/nvim/os/unix_defs.h
+++ b/src/nvim/os/unix_defs.h
@@ -43,8 +43,8 @@
#ifndef VIMRC_FILE
# define VIMRC_FILE ".nvimrc"
#endif
-#ifndef VIMINFO_FILE
-# define VIMINFO_FILE "~/.nviminfo"
+#ifndef SHADA_FILE
+# define SHADA_FILE "~/.nvim/shada/main.shada"
#endif
// Default for 'backupdir'.
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index 9773b73428..b7ec50a109 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -9,7 +9,7 @@
// Defines needed to fix the build on Windows:
// - USR_EXRC_FILE
// - USR_VIMRC_FILE
-// - VIMINFO_FILE
+// - SHADA_FILE
// - DFLT_DIR
// - DFLT_BDIR
// - DFLT_VDIR
diff --git a/src/nvim/search.c b/src/nvim/search.c
index a758e02105..8ba888841c 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -79,23 +79,6 @@
* Henry Spencer's regular expression library. See regexp.c.
*/
-/* The offset for a search command is store in a soff struct */
-/* Note: only spats[0].off is really used */
-struct soffset {
- int dir; /* search direction, '/' or '?' */
- int line; /* search has line offset */
- int end; /* search set cursor at end */
- long off; /* line or char offset */
-};
-
-/* A search pattern and its attributes are stored in a spat struct */
-struct spat {
- char_u *pat; /* the pattern (in allocated memory) or NULL */
- int magic; /* magicness of the pattern */
- int no_scs; /* no smartcase for this pattern */
- struct soffset off;
-};
-
/*
* Two search patterns are remembered: One for the :substitute command and
* one for other searches. last_idx points to the one that was used the last
@@ -103,8 +86,10 @@ struct spat {
*/
static struct spat spats[2] =
{
- {NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, /* last used search pat */
- {NULL, TRUE, FALSE, {'/', 0, 0, 0L}} /* last used substitute pat */
+ // Last used search pattern
+ [0] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL},
+ // Last used substitute pattern
+ [1] = {NULL, true, false, 0, {'/', false, false, 0L}, NULL}
};
static int last_idx = 0; /* index in spats[] for RE_LAST */
@@ -256,10 +241,12 @@ char_u *reverse_text(char_u *s) FUNC_ATTR_NONNULL_RET
void save_re_pat(int idx, char_u *pat, int magic)
{
if (spats[idx].pat != pat) {
- xfree(spats[idx].pat);
+ free_spat(&spats[idx]);
spats[idx].pat = vim_strsave(pat);
spats[idx].magic = magic;
spats[idx].no_scs = no_smartcase;
+ spats[idx].timestamp = os_time();
+ spats[idx].additional_data = NULL;
last_idx = idx;
/* If 'hlsearch' set and search pat changed: need redraw. */
if (p_hls)
@@ -291,21 +278,29 @@ void save_search_patterns(void)
void restore_search_patterns(void)
{
if (--save_level == 0) {
- xfree(spats[0].pat);
+ free_spat(&spats[0]);
spats[0] = saved_spats[0];
set_vv_searchforward();
- xfree(spats[1].pat);
+ free_spat(&spats[1]);
spats[1] = saved_spats[1];
last_idx = saved_last_idx;
SET_NO_HLSEARCH(saved_no_hlsearch);
}
}
+static inline void free_spat(struct spat *const spat)
+{
+ xfree(spat->pat);
+ dict_unref(spat->additional_data);
+}
+
#if defined(EXITFREE)
void free_search_patterns(void)
{
- xfree(spats[0].pat);
- xfree(spats[1].pat);
+ free_spat(&spats[0]);
+ free_spat(&spats[1]);
+
+ memset(spats, 0, sizeof(spats));
if (mr_pattern_alloced) {
xfree(mr_pattern);
@@ -414,17 +409,19 @@ void reset_search_dir(void)
}
/*
- * Set the last search pattern. For ":let @/ =" and viminfo.
+ * Set the last search pattern. For ":let @/ =" and ShaDa file.
* Also set the saved search pattern, so that this works in an autocommand.
*/
void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
{
- xfree(spats[idx].pat);
+ free_spat(&spats[idx]);
/* An empty string means that nothing should be matched. */
if (*s == NUL)
spats[idx].pat = NULL;
else
spats[idx].pat = (char_u *) xstrdup((char *) s);
+ spats[idx].timestamp = os_time();
+ spats[idx].additional_data = NULL;
spats[idx].magic = magic;
spats[idx].no_scs = FALSE;
spats[idx].off.dir = '/';
@@ -435,7 +432,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
if (setlast)
last_idx = idx;
if (save_level) {
- xfree(saved_spats[idx].pat);
+ free_spat(&saved_spats[idx]);
saved_spats[idx] = spats[0];
if (spats[idx].pat == NULL)
saved_spats[idx].pat = NULL;
@@ -1053,7 +1050,7 @@ int do_search(
else if ((options & SEARCH_OPT) &&
(*p == 'e' || *p == 's' || *p == 'b')) {
if (*p == 'e') /* end */
- spats[0].off.end = SEARCH_END;
+ spats[0].off.end = true;
++p;
}
if (ascii_isdigit(*p) || *p == '+' || *p == '-') { /* got an offset */
@@ -1166,12 +1163,13 @@ int do_search(
lrFswap(searchstr,0);
c = searchit(curwin, curbuf, &pos, dirc == '/' ? FORWARD : BACKWARD,
- searchstr, count, spats[0].off.end + (options &
- (SEARCH_KEEP + SEARCH_PEEK +
- SEARCH_HIS
- + SEARCH_MSG + SEARCH_START
- + ((pat != NULL && *pat ==
- ';') ? 0 : SEARCH_NOOF))),
+ searchstr, count, (spats[0].off.end * SEARCH_END
+ + (options &
+ (SEARCH_KEEP + SEARCH_PEEK +
+ SEARCH_HIS
+ + SEARCH_MSG + SEARCH_START
+ + ((pat != NULL && *pat ==
+ ';') ? 0 : SEARCH_NOOF)))),
RE_LAST, (linenr_T)0, tm);
if (dircp != NULL)
@@ -4605,105 +4603,45 @@ static void show_pat_in_path(char_u *line, int type, int did_show, int action, F
}
}
-int read_viminfo_search_pattern(vir_T *virp, int force)
+/// Get last search pattern
+void get_search_pattern(SearchPattern *const pat)
{
- char_u *lp;
- int idx = -1;
- int magic = FALSE;
- int no_scs = FALSE;
- int off_line = FALSE;
- int off_end = 0;
- long off = 0;
- int setlast = FALSE;
- static int hlsearch_on = FALSE;
- char_u *val;
+ memcpy(pat, &(spats[0]), sizeof(spats[0]));
+}
- /*
- * Old line types:
- * "/pat", "&pat": search/subst. pat
- * "~/pat", "~&pat": last used search/subst. pat
- * New line types:
- * "~h", "~H": hlsearch highlighting off/on
- * "~<magic><smartcase><line><end><off><last><which>pat"
- * <magic>: 'm' off, 'M' on
- * <smartcase>: 's' off, 'S' on
- * <line>: 'L' line offset, 'l' char offset
- * <end>: 'E' from end, 'e' from start
- * <off>: decimal, offset
- * <last>: '~' last used pattern
- * <which>: '/' search pat, '&' subst. pat
- */
- lp = virp->vir_line;
- if (lp[0] == '~' && (lp[1] == 'm' || lp[1] == 'M')) { /* new line type */
- if (lp[1] == 'M') /* magic on */
- magic = TRUE;
- if (lp[2] == 's')
- no_scs = TRUE;
- if (lp[3] == 'L')
- off_line = TRUE;
- if (lp[4] == 'E')
- off_end = SEARCH_END;
- lp += 5;
- off = getdigits_long(&lp);
- }
- if (lp[0] == '~') { /* use this pattern for last-used pattern */
- setlast = TRUE;
- lp++;
- }
- if (lp[0] == '/')
- idx = RE_SEARCH;
- else if (lp[0] == '&')
- idx = RE_SUBST;
- else if (lp[0] == 'h') /* ~h: 'hlsearch' highlighting off */
- hlsearch_on = FALSE;
- else if (lp[0] == 'H') /* ~H: 'hlsearch' highlighting on */
- hlsearch_on = TRUE;
- if (idx >= 0) {
- if (force || spats[idx].pat == NULL) {
- val = viminfo_readstring(virp, (int)(lp - virp->vir_line + 1), TRUE);
- set_last_search_pat(val, idx, magic, setlast);
- xfree(val);
- spats[idx].no_scs = no_scs;
- spats[idx].off.line = off_line;
- spats[idx].off.end = off_end;
- spats[idx].off.off = off;
- if (setlast) {
- SET_NO_HLSEARCH(!hlsearch_on);
- }
- }
- }
- return viminfo_readline(virp);
+/// Get last substitute pattern
+void get_substitute_pattern(SearchPattern *const pat)
+{
+ memcpy(pat, &(spats[1]), sizeof(spats[1]));
+ memset(&(pat->off), 0, sizeof(pat->off));
}
-void write_viminfo_search_pattern(FILE *fp)
+/// Set last search pattern
+void set_search_pattern(const SearchPattern pat)
{
- if (get_viminfo_parameter('/') != 0) {
- fprintf(fp, "\n# hlsearch on (H) or off (h):\n~%c",
- (no_hlsearch || find_viminfo_parameter('h') != NULL) ? 'h' : 'H');
- wvsp_one(fp, RE_SEARCH, "", '/');
- wvsp_one(fp, RE_SUBST, _("Substitute "), '&');
- }
+ free_spat(&spats[0]);
+ memcpy(&(spats[0]), &pat, sizeof(spats[0]));
}
-static void
-wvsp_one (
- FILE *fp, /* file to write to */
- int idx, /* spats[] index */
- char *s, /* search pat */
- int sc /* dir char */
-)
+/// Set last substitute pattern
+void set_substitute_pattern(const SearchPattern pat)
{
- if (spats[idx].pat != NULL) {
- fprintf(fp, _("\n# Last %sSearch Pattern:\n~"), s);
- /* off.dir is not stored, it's reset to forward */
- fprintf(fp, "%c%c%c%c%" PRId64 "%s%c",
- spats[idx].magic ? 'M' : 'm', /* magic */
- spats[idx].no_scs ? 's' : 'S', /* smartcase */
- spats[idx].off.line ? 'L' : 'l', /* line offset */
- spats[idx].off.end ? 'E' : 'e', /* offset from end */
- (int64_t)spats[idx].off.off, /* offset */
- last_idx == idx ? "~" : "", /* last used pat */
- sc);
- viminfo_writestring(fp, spats[idx].pat);
- }
+ free_spat(&spats[1]);
+ memcpy(&(spats[1]), &pat, sizeof(spats[1]));
+ memset(&(spats[1].off), 0, sizeof(spats[1].off));
+}
+
+/// Set last used search pattern
+///
+/// @param[in] is_substitute_pattern If true set substitute pattern as last
+/// used. Otherwise sets search pattern.
+void set_last_used_pattern(const bool is_substitute_pattern)
+{
+ last_idx = (is_substitute_pattern ? 1 : 0);
+}
+
+/// Returns true if search pattern was the last used one
+bool search_was_last_used(void)
+{
+ return last_idx == 0;
}
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 1bcde2c7ab..6947f79d49 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -1,6 +1,9 @@
#ifndef NVIM_SEARCH_H
#define NVIM_SEARCH_H
+#include <stdbool.h>
+#include <stdint.h>
+
/* Values for the find_pattern_in_path() function args 'type' and 'action': */
#define FIND_ANY 1
#define FIND_DEFINE 2
@@ -39,6 +42,27 @@
#define RE_BOTH 2 /* save pat in both patterns */
#define RE_LAST 2 /* use last used pattern if "pat" is NULL */
+/// Structure containing offset definition for the last search pattern
+///
+/// @note Only offset for the last search pattern is used, not for the last
+/// substitute pattern.
+typedef struct soffset {
+ char dir; ///< Search direction: forward ('/') or backward ('?')
+ bool line; ///< True if search has line offset.
+ bool end; ///< True if search sets cursor at the end.
+ int64_t off; ///< Actual offset value.
+} SearchOffset;
+
+/// Structure containing last search pattern and its attributes.
+typedef struct spat {
+ char_u *pat; ///< The pattern (in allocated memory) or NULL.
+ bool magic; ///< Magicness of the pattern.
+ bool no_scs; ///< No smartcase for this pattern.
+ Timestamp timestamp; ///< Time of the last change.
+ SearchOffset off; ///< Pattern offset.
+ dict_T *additional_data; ///< Additional data from ShaDa file.
+} SearchPattern;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
#endif
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
new file mode 100644
index 0000000000..523f8db6f0
--- /dev/null
+++ b/src/nvim/shada.c
@@ -0,0 +1,4040 @@
+#ifdef HAVE_BE64TOH
+# define _BSD_SOURCE 1
+# define _DEFAULT_SOURCE 1
+# include <endian.h>
+#endif
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <msgpack.h>
+#include <uv.h>
+
+#include "nvim/os/os.h"
+#include "nvim/os/time.h"
+#include "nvim/vim.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/ops.h"
+#include "nvim/garray.h"
+#include "nvim/option.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/misc2.h"
+#include "nvim/ex_getln.h"
+#include "nvim/search.h"
+#include "nvim/eval.h"
+#include "nvim/regexp.h"
+#include "nvim/eval_defs.h"
+#include "nvim/version.h"
+#include "nvim/path.h"
+#include "nvim/fileio.h"
+#include "nvim/strings.h"
+#include "nvim/lib/khash.h"
+#include "nvim/lib/kvec.h"
+
+// Note: when using bufset hash pointers are intentionally casted to uintptr_t
+// and not to khint32_t or khint64_t: this way compiler must give a warning
+// (-Wconversion) when types change.
+#ifdef ARCH_32
+KHASH_SET_INIT_INT(bufset)
+#elif defined(ARCH_64)
+KHASH_SET_INIT_INT64(bufset)
+#else
+# error Not a 64- or 32-bit architecture
+#endif
+KHASH_MAP_INIT_STR(fnamebufs, buf_T *)
+KHASH_SET_INIT_STR(strset)
+
+#define copy_option_part(src, dest, ...) \
+ ((char *) copy_option_part((char_u **) src, (char_u *) dest, __VA_ARGS__))
+#define find_shada_parameter(...) \
+ ((const char *) find_shada_parameter(__VA_ARGS__))
+#define emsg2(a, b) emsg2((char_u *) a, (char_u *) b)
+#define emsg3(a, b, c) emsg3((char_u *) a, (char_u *) b, (char_u *) c)
+#define emsgu(a, ...) emsgu((char_u *) a, __VA_ARGS__)
+#define home_replace_save(a, b) \
+ ((char *)home_replace_save(a, (char_u *)b))
+#define home_replace(a, b, c, d, e) \
+ home_replace(a, (char_u *)b, (char_u *)c, d, e)
+#define vim_rename(a, b) \
+ (vim_rename((char_u *)a, (char_u *)b))
+#define mb_strnicmp(a, b, c) \
+ (mb_strnicmp((char_u *)a, (char_u *)b, c))
+#define has_non_ascii(a) (has_non_ascii((char_u *)a))
+#define string_convert(a, b, c) \
+ ((char *)string_convert((vimconv_T *)a, (char_u *)b, c))
+#define path_shorten_fname_if_possible(b) \
+ ((char *)path_shorten_fname_if_possible((char_u *)b))
+#define buflist_new(ffname, sfname, ...) \
+ (buflist_new((char_u *)ffname, (char_u *)sfname, __VA_ARGS__))
+#define convert_setup(vcp, from, to) \
+ (convert_setup(vcp, (char_u *)from, (char_u *)to))
+#define os_getperm(f) \
+ (os_getperm((char_u *) f))
+#define os_isdir(f) (os_isdir((char_u *) f))
+#define regtilde(s, m) ((char *) regtilde((char_u *) s, m))
+#define path_tail_with_sep(f) ((char *) path_tail_with_sep((char_u *)f))
+
+#define SEARCH_KEY_MAGIC "sm"
+#define SEARCH_KEY_SMARTCASE "sc"
+#define SEARCH_KEY_HAS_LINE_OFFSET "sl"
+#define SEARCH_KEY_PLACE_CURSOR_AT_END "se"
+#define SEARCH_KEY_IS_LAST_USED "su"
+#define SEARCH_KEY_IS_SUBSTITUTE_PATTERN "ss"
+#define SEARCH_KEY_HIGHLIGHTED "sh"
+#define SEARCH_KEY_OFFSET "so"
+#define SEARCH_KEY_PAT "sp"
+
+#define REG_KEY_TYPE "rt"
+#define REG_KEY_WIDTH "rw"
+#define REG_KEY_CONTENTS "rc"
+
+#define KEY_LNUM "l"
+#define KEY_COL "c"
+#define KEY_FILE "f"
+#define KEY_NAME_CHAR "n"
+
+// Error messages formerly used by viminfo code:
+// E136: viminfo: Too many errors, skipping rest of file
+// E137: Viminfo file is not writable: %s
+// E138: Can't write viminfo file %s!
+// E195: Cannot open ShaDa file for reading
+// E574: Unknown register type %d
+// E575: Illegal starting char
+// E576: Missing '>'
+// E577: Illegal register name
+// E886: Can't rename viminfo file to %s!
+// Now only five of them are used:
+// E137: ShaDa file is not writeable (for pre-open checks)
+// E138: All %s.tmp.X files exist, cannot write ShaDa file!
+// RCERR (E576) for critical read errors.
+// RNERR (E136) for various errors when renaming.
+// RERR (E575) for various errors inside read ShaDa file.
+// SERR (E886) for various “system” errors (always contains output of
+// strerror)
+
+/// Common prefix for all errors inside ShaDa file
+///
+/// I.e. errors occurred while parsing, but not system errors occurred while
+/// reading.
+#define RERR "E575: "
+
+/// Common prefix for critical read errors
+///
+/// I.e. errors that make shada_read_next_item return kSDReadStatusNotShaDa.
+#define RCERR "E576: "
+
+/// Common prefix for all “system” errors
+#define SERR "E886: "
+
+/// Common prefix for all “rename” errors
+#define RNERR "E136: "
+
+/// Flags for shada_read_file and children
+typedef enum {
+ kShaDaWantInfo = 1, ///< Load non-mark information
+ kShaDaWantMarks = 2, ///< Load local file marks and change list
+ kShaDaForceit = 4, ///< Overwrite info already read
+ kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
+ kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
+} ShaDaReadFileFlags;
+
+/// Possible ShaDa entry types
+///
+/// @warning Enum values are part of the API and must not be altered.
+///
+/// All values that are not in enum are ignored.
+typedef enum {
+ kSDItemUnknown = -1, ///< Unknown item.
+ kSDItemMissing = 0, ///< Missing value. Should never appear in a file.
+ kSDItemHeader = 1, ///< Header. Present for debugging purposes.
+ kSDItemSearchPattern = 2, ///< Last search pattern (*not* history item).
+ ///< Comes from user searches (e.g. when typing
+ ///< "/pat") or :substitute command calls.
+ kSDItemSubString = 3, ///< Last substitute replacement string.
+ kSDItemHistoryEntry = 4, ///< History item.
+ kSDItemRegister = 5, ///< Register.
+ kSDItemVariable = 6, ///< Global variable.
+ kSDItemGlobalMark = 7, ///< Global mark definition.
+ kSDItemJump = 8, ///< Item from jump list.
+ kSDItemBufferList = 9, ///< Buffer list.
+ kSDItemLocalMark = 10, ///< Buffer-local mark.
+ kSDItemChange = 11, ///< Item from buffer change list.
+#define SHADA_LAST_ENTRY ((uint64_t) kSDItemChange)
+} ShadaEntryType;
+
+/// Possible results when reading ShaDa file
+typedef enum {
+ kSDReadStatusSuccess, ///< Reading was successfull.
+ kSDReadStatusFinished, ///< Nothing more to read.
+ kSDReadStatusReadError, ///< Failed to read from file.
+ kSDReadStatusNotShaDa, ///< Input is most likely not a ShaDa file.
+ kSDReadStatusMalformed, ///< Error in the currently read item.
+} ShaDaReadResult;
+
+/// Possible results of shada_write function.
+typedef enum {
+ kSDWriteSuccessfull, ///< Writing was successfull.
+ kSDWriteReadNotShada, ///< Writing was successfull, but when reading it
+ ///< attempted to read file that did not look like
+ ///< a ShaDa file.
+ kSDWriteFailed, ///< Writing was not successfull (e.g. because there
+ ///< was no space left on device).
+} ShaDaWriteResult;
+
+/// Flags for shada_read_next_item
+enum SRNIFlags {
+ kSDReadHeader = (1 << kSDItemHeader), ///< Determines whether header should
+ ///< be read (it is usually ignored).
+ kSDReadUndisableableData = (
+ (1 << kSDItemSearchPattern)
+ | (1 << kSDItemSubString)
+ | (1 << kSDItemJump)), ///< Data reading which cannot be disabled by &shada
+ ///< or other options except for disabling reading
+ ///< ShaDa as a whole.
+ kSDReadRegisters = (1 << kSDItemRegister), ///< Determines whether registers
+ ///< should be read (may only be
+ ///< disabled when writing, but
+ ///< not when reading).
+ kSDReadHistory = (1 << kSDItemHistoryEntry), ///< Determines whether history
+ ///< should be read (can only be
+ ///< disabled by &history).
+ kSDReadVariables = (1 << kSDItemVariable), ///< Determines whether variables
+ ///< should be read (disabled by
+ ///< removing ! from &shada).
+ kSDReadBufferList = (1 << kSDItemBufferList), ///< Determines whether buffer
+ ///< list should be read
+ ///< (disabled by removing
+ ///< % entry from &shada).
+ kSDReadUnknown = (1 << (SHADA_LAST_ENTRY + 1)), ///< Determines whether
+ ///< unknown items should be
+ ///< read (usually disabled).
+ kSDReadGlobalMarks = (1 << kSDItemGlobalMark), ///< Determines whether global
+ ///< marks should be read. Can
+ ///< only be disabled by
+ ///< having f0 in &shada when
+ ///< writing.
+ kSDReadLocalMarks = (1 << kSDItemLocalMark), ///< Determines whether local
+ ///< marks should be read. Can
+ ///< only be disabled by
+ ///< disabling &shada or putting
+ ///< '0 there. Is also used for
+ ///< v:oldfiles.
+ kSDReadChanges = (1 << kSDItemChange), ///< Determines whether change list
+ ///< should be read. Can only be
+ ///< disabled by disabling &shada or
+ ///< putting '0 there.
+};
+// Note: SRNIFlags enum name was created only to make it possible to reference
+// it. This name is not actually used anywhere outside of the documentation.
+
+/// Structure defining a single ShaDa file entry
+typedef struct {
+ ShadaEntryType type;
+ Timestamp timestamp;
+ union {
+ Dictionary header;
+ struct shada_filemark {
+ char name;
+ pos_T mark;
+ char *fname;
+ dict_T *additional_data;
+ } filemark;
+ struct search_pattern {
+ bool magic;
+ bool smartcase;
+ bool has_line_offset;
+ bool place_cursor_at_end;
+ int64_t offset;
+ bool is_last_used;
+ bool is_substitute_pattern;
+ bool highlighted;
+ char *pat;
+ dict_T *additional_data;
+ } search_pattern;
+ struct history_item {
+ uint8_t histtype;
+ char *string;
+ char sep;
+ list_T *additional_elements;
+ } history_item;
+ struct reg {
+ char name;
+ uint8_t type;
+ char **contents;
+ size_t contents_size;
+ size_t width;
+ dict_T *additional_data;
+ } reg;
+ struct global_var {
+ char *name;
+ typval_T value;
+ list_T *additional_elements;
+ } global_var;
+ struct {
+ uint64_t type;
+ char *contents;
+ size_t size;
+ } unknown_item;
+ struct sub_string {
+ char *sub;
+ list_T *additional_elements;
+ } sub_string;
+ struct buffer_list {
+ size_t size;
+ struct buffer_list_buffer {
+ pos_T pos;
+ char *fname;
+ dict_T *additional_data;
+ } *buffers;
+ } buffer_list;
+ } data;
+} ShadaEntry;
+
+struct hm_llist_entry;
+
+/// One entry in sized linked list
+typedef struct hm_llist_entry {
+ ShadaEntry data; ///< Entry data.
+ bool can_free_entry; ///< True if data can be freed.
+ struct hm_llist_entry *next; ///< Pointer to next entry or NULL.
+ struct hm_llist_entry *prev; ///< Pointer to previous entry or NULL.
+} HMLListEntry;
+
+KHASH_MAP_INIT_STR(hmll_entries, HMLListEntry *)
+
+/// Sized linked list structure for history merger
+typedef struct {
+ HMLListEntry *entries; ///< Pointer to the start of the allocated array of
+ ///< entries.
+ HMLListEntry *first; ///< First entry in the list (is not necessary start
+ ///< of the array) or NULL.
+ HMLListEntry *last; ///< Last entry in the list or NULL.
+ HMLListEntry *free_entry; ///< Last free entry removed by hmll_remove.
+ HMLListEntry *last_free_entry; ///< Last unused element in entries array.
+ size_t size; ///< Number of allocated entries.
+ size_t num_entries; ///< Number of entries already used.
+ khash_t(hmll_entries) contained_entries; ///< Hash mapping all history entry
+ ///< strings to corresponding entry
+ ///< pointers.
+} HMLList;
+
+typedef struct {
+ HMLList hmll;
+ bool do_merge;
+ bool reading;
+ const void *iter;
+ ShadaEntry last_hist_entry;
+ uint8_t history_type;
+} HistoryMergerState;
+
+/// ShadaEntry structure that knows whether it should be freed
+typedef struct {
+ ShadaEntry data; ///< ShadaEntry data.
+ bool can_free_entry; ///< True if entry can be freed.
+} PossiblyFreedShadaEntry;
+
+/// Structure that holds one file marks.
+typedef struct {
+ PossiblyFreedShadaEntry marks[NLOCALMARKS]; ///< All file marks.
+ PossiblyFreedShadaEntry changes[JUMPLISTSIZE]; ///< All file changes.
+ size_t changes_size; ///< Number of changes occupied.
+ ShadaEntry *additional_marks; ///< All marks with unknown names.
+ size_t additional_marks_size; ///< Size of the additional_marks array.
+ Timestamp greatest_timestamp; ///< Greatest timestamp among marks.
+} FileMarks;
+
+KHASH_MAP_INIT_STR(file_marks, FileMarks)
+
+/// State structure used by shada_write
+///
+/// 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 registers[NUM_SAVED_REGISTERS]; ///< All registers.
+ PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps.
+ size_t jumps_size; ///< Number of jumps occupied.
+ PossiblyFreedShadaEntry search_pattern; ///< Last search pattern.
+ PossiblyFreedShadaEntry sub_search_pattern; ///< Last s/ search pattern.
+ PossiblyFreedShadaEntry replacement; ///< Last s// replacement string.
+ khash_t(strset) dumped_variables; ///< Names of already dumped variables.
+ khash_t(file_marks) file_marks; ///< All file marks.
+} WriteMergerState;
+
+struct sd_read_def;
+
+/// Function used to close files defined by ShaDaReadDef
+typedef void (*ShaDaReadCloser)(struct sd_read_def *const sd_reader)
+ REAL_FATTR_NONNULL_ALL;
+
+/// Function used to read ShaDa files
+typedef ptrdiff_t (*ShaDaFileReader)(struct sd_read_def *const sd_reader,
+ void *const dest,
+ const size_t size)
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Function used to skip in ShaDa files
+typedef int (*ShaDaFileSkipper)(struct sd_read_def *const sd_reader,
+ const size_t offset)
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Structure containing necessary pointers for reading ShaDa files
+typedef struct sd_read_def {
+ ShaDaFileReader read; ///< Reader function.
+ ShaDaReadCloser close; ///< Close function.
+ ShaDaFileSkipper skip; ///< Function used to skip some bytes.
+ void *cookie; ///< Data describing object read from.
+ bool eof; ///< True if reader reached end of file.
+ char *error; ///< Error message in case of error.
+ uintmax_t fpos; ///< Current position (amount of bytes read since
+ ///< reader structure initialization). May overflow.
+ vimconv_T sd_conv; ///< Structure used for converting encodings of some
+ ///< items.
+} ShaDaReadDef;
+
+struct sd_write_def;
+
+/// Function used to close files defined by ShaDaWriteDef
+typedef void (*ShaDaWriteCloser)(struct sd_write_def *const sd_writer)
+ REAL_FATTR_NONNULL_ALL;
+
+/// Function used to write ShaDa files
+typedef ptrdiff_t (*ShaDaFileWriter)(struct sd_write_def *const sd_writer,
+ const void *const src,
+ const size_t size)
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
+
+/// Structure containing necessary pointers for writing ShaDa files
+typedef struct sd_write_def {
+ ShaDaFileWriter write; ///< Writer function.
+ ShaDaWriteCloser close; ///< Close function.
+ void *cookie; ///< Data describing object written to.
+ char *error; ///< Error message in case of error.
+ vimconv_T sd_conv; ///< Structure used for converting encodings of some
+ ///< items.
+} ShaDaWriteDef;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "shada.c.generated.h"
+#endif
+
+#define DEF_SDE(name, attr, ...) \
+ [kSDItem##name] = { \
+ .timestamp = 0, \
+ .type = kSDItem##name, \
+ .data = { \
+ .attr = { __VA_ARGS__ } \
+ } \
+ }
+#define DEFAULT_POS {1, 0, 0}
+static const pos_T default_pos = DEFAULT_POS;
+static const ShadaEntry sd_default_values[] = {
+ [kSDItemMissing] = { .type = kSDItemMissing, .timestamp = 0 },
+ DEF_SDE(Header, header, .size = 0),
+ DEF_SDE(SearchPattern, search_pattern,
+ .magic = true,
+ .smartcase = false,
+ .has_line_offset = false,
+ .place_cursor_at_end = false,
+ .offset = 0,
+ .is_last_used = true,
+ .is_substitute_pattern = false,
+ .highlighted = false,
+ .pat = NULL,
+ .additional_data = NULL),
+ DEF_SDE(SubString, sub_string, .sub = NULL, .additional_elements = NULL),
+ DEF_SDE(HistoryEntry, history_item,
+ .histtype = HIST_CMD,
+ .string = NULL,
+ .sep = NUL,
+ .additional_elements = NULL),
+ DEF_SDE(Register, reg,
+ .name = NUL,
+ .type = MCHAR,
+ .contents = NULL,
+ .contents_size = 0,
+ .width = 0,
+ .additional_data = NULL),
+ DEF_SDE(Variable, global_var,
+ .name = NULL,
+ .value = {
+ .v_type = VAR_UNKNOWN,
+ .vval = { .v_string = NULL }
+ },
+ .additional_elements = NULL),
+ DEF_SDE(GlobalMark, filemark,
+ .name = '"',
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+ DEF_SDE(Jump, filemark,
+ .name = NUL,
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+ DEF_SDE(BufferList, buffer_list,
+ .size = 0,
+ .buffers = NULL),
+ DEF_SDE(LocalMark, filemark,
+ .name = '"',
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+ DEF_SDE(Change, filemark,
+ .name = NUL,
+ .mark = DEFAULT_POS,
+ .fname = NULL,
+ .additional_data = NULL),
+};
+#undef DEFAULT_POS
+#undef DEF_SDE
+
+/// Initialize new linked list
+///
+/// @param[out] hmll List to initialize.
+/// @param[in] size Maximum size of the list.
+static inline void hmll_init(HMLList *const hmll, const size_t size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *hmll = (HMLList) {
+ .entries = xcalloc(size, sizeof(hmll->entries[0])),
+ .first = NULL,
+ .last = NULL,
+ .free_entry = NULL,
+ .size = size,
+ .num_entries = 0,
+ .contained_entries = KHASH_EMPTY_TABLE(hmll_entries),
+ };
+ hmll->last_free_entry = hmll->entries;
+}
+
+/// Iterate over HMLList in forward direction
+///
+/// @param hmll Pointer to the list.
+/// @param cur_entry Name of the variable to iterate over.
+///
+/// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`).
+#define HMLL_FORALL(hmll, cur_entry) \
+ for (HMLListEntry *cur_entry = (hmll)->first; cur_entry != NULL; \
+ cur_entry = cur_entry->next)
+
+/// Remove entry from the linked list
+///
+/// @param hmll List to remove from.
+/// @param hmll_entry Entry to remove.
+static inline void hmll_remove(HMLList *const hmll,
+ HMLListEntry *const hmll_entry)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (hmll_entry == hmll->last_free_entry - 1) {
+ hmll->last_free_entry--;
+ } else {
+ assert(hmll->free_entry == NULL);
+ hmll->free_entry = hmll_entry;
+ }
+ const khiter_t k = kh_get(hmll_entries, &hmll->contained_entries,
+ hmll_entry->data.data.history_item.string);
+ assert(k != kh_end(&hmll->contained_entries));
+ kh_del(hmll_entries, &hmll->contained_entries, k);
+ if (hmll_entry->next == NULL) {
+ hmll->last = hmll_entry->prev;
+ } else {
+ hmll_entry->next->prev = hmll_entry->prev;
+ }
+ if (hmll_entry->prev == NULL) {
+ hmll->first = hmll_entry->next;
+ } else {
+ hmll_entry->prev->next = hmll_entry->next;
+ }
+ hmll->num_entries--;
+ if (hmll_entry->can_free_entry) {
+ shada_free_shada_entry(&hmll_entry->data);
+ }
+}
+
+
+/// Insert entry to the linked list
+///
+/// @param[out] hmll List to insert to.
+/// @param[in] hmll_entry Entry to insert after or NULL if it is needed
+/// to insert at the first entry.
+/// @param[in] data Data to insert.
+/// @param[in] can_free_entry True if data can be freed.
+static inline void hmll_insert(HMLList *const hmll,
+ HMLListEntry *hmll_entry,
+ const ShadaEntry data,
+ const bool can_free_entry)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (hmll->num_entries == hmll->size) {
+ if (hmll_entry == hmll->first) {
+ hmll_entry = NULL;
+ }
+ hmll_remove(hmll, hmll->first);
+ }
+ HMLListEntry *target_entry;
+ if (hmll->free_entry == NULL) {
+ assert((size_t) (hmll->last_free_entry - hmll->entries)
+ == hmll->num_entries);
+ target_entry = hmll->last_free_entry++;
+ } else {
+ assert((size_t) (hmll->last_free_entry - hmll->entries) - 1
+ == hmll->num_entries);
+ target_entry = hmll->free_entry;
+ hmll->free_entry = NULL;
+ }
+ target_entry->data = data;
+ target_entry->can_free_entry = can_free_entry;
+ int kh_ret;
+ const khiter_t k = kh_put(hmll_entries, &hmll->contained_entries,
+ data.data.history_item.string, &kh_ret);
+ if (kh_ret > 0) {
+ kh_val(&hmll->contained_entries, k) = target_entry;
+ }
+ hmll->num_entries++;
+ target_entry->prev = hmll_entry;
+ if (hmll_entry == NULL) {
+ target_entry->next = hmll->first;
+ hmll->first = target_entry;
+ } else {
+ target_entry->next = hmll_entry->next;
+ hmll_entry->next = target_entry;
+ }
+ if (target_entry->next == NULL) {
+ hmll->last = target_entry;
+ } else {
+ target_entry->next->prev = target_entry;
+ }
+}
+
+/// Iterate over HMLList in backward direction
+///
+/// @param hmll Pointer to the list.
+/// @param cur_entry Name of the variable to iterate over, must be already
+/// defined.
+///
+/// @return `for` cycle header (use `HMLL_FORALL(hmll, cur_entry) {body}`).
+#define HMLL_ITER_BACK(hmll, cur_entry) \
+ for (cur_entry = (hmll)->last; cur_entry != NULL; \
+ cur_entry = cur_entry->prev)
+
+/// Free linked list
+///
+/// @param[in] hmll List to free.
+static inline void hmll_dealloc(HMLList *const hmll)
+ FUNC_ATTR_NONNULL_ALL
+{
+ kh_dealloc(hmll_entries, &hmll->contained_entries);
+ xfree(hmll->entries);
+}
+
+/// Wrapper for reading from file descriptors
+///
+/// @return -1 or number of bytes read.
+static ptrdiff_t read_file(ShaDaReadDef *const sd_reader, void *const dest,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t read_bytes = 0;
+ bool did_try_to_free = false;
+ const int fd = (int)(intptr_t) sd_reader->cookie;
+ while (read_bytes != size) {
+ const ptrdiff_t cur_read_bytes = read(fd, ((char *) dest) + read_bytes,
+ size - read_bytes);
+ if (cur_read_bytes > 0) {
+ read_bytes += (size_t) cur_read_bytes;
+ sd_reader->fpos += (uintmax_t) cur_read_bytes;
+ assert(read_bytes <= size);
+ }
+ if (cur_read_bytes < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ errno = 0;
+ continue;
+ } else if (errno == ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ errno = 0;
+ continue;
+ } else {
+ sd_reader->error = strerror(errno);
+ errno = 0;
+ return -1;
+ }
+ }
+ if (cur_read_bytes == 0) {
+ sd_reader->eof = true;
+ break;
+ }
+ }
+ return (ptrdiff_t) read_bytes;
+}
+
+/// Read one character
+static int read_char(ShaDaReadDef *const sd_reader)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ uint8_t ret;
+ ptrdiff_t read_bytes = sd_reader->read(sd_reader, &ret, 1);
+ if (read_bytes != 1) {
+ return EOF;
+ }
+ return (int) ret;
+}
+
+/// Wrapper for writing to file descriptors
+///
+/// @return -1 or number of bytes written.
+static ptrdiff_t write_file(ShaDaWriteDef *const sd_writer,
+ const void *const dest,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t written_bytes = 0;
+ const int fd = (int)(intptr_t) sd_writer->cookie;
+ while (written_bytes != size) {
+ const ptrdiff_t cur_written_bytes = write(fd, (char *) dest + written_bytes,
+ size - written_bytes);
+ if (cur_written_bytes > 0) {
+ written_bytes += (size_t) cur_written_bytes;
+ }
+ if (cur_written_bytes < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ errno = 0;
+ continue;
+ } else {
+ sd_writer->error = strerror(errno);
+ errno = 0;
+ return -1;
+ }
+ }
+ if (cur_written_bytes == 0) {
+ sd_writer->error = "Zero bytes written.";
+ return -1;
+ }
+ }
+ return (ptrdiff_t) written_bytes;
+}
+
+/// Wrapper for closing file descriptors opened for reading
+static void close_sd_reader(ShaDaReadDef *const sd_reader)
+ FUNC_ATTR_NONNULL_ALL
+{
+ close_file((int)(intptr_t) sd_reader->cookie);
+}
+
+/// Wrapper for closing file descriptors opened for writing
+static void close_sd_writer(ShaDaWriteDef *const sd_writer)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const int fd = (int)(intptr_t) sd_writer->cookie;
+ if (fsync(fd) < 0) {
+ emsg2(_(SERR "System error while synchronizing ShaDa file: %s"),
+ strerror(errno));
+ errno = 0;
+ }
+ close_file(fd);
+}
+
+/// Wrapper for read that reads to IObuff and ignores bytes read
+///
+/// Used for skipping.
+///
+/// @param[in,out] sd_reader File read.
+/// @param[in] offset Amount of bytes to skip.
+///
+/// @return FAIL in case of failure, OK in case of success. May set
+/// sd_reader->eof or sd_reader->error.
+static int sd_reader_skip_read(ShaDaReadDef *const sd_reader,
+ const size_t offset)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t read_bytes = 0;
+ do {
+ ptrdiff_t new_read_bytes = sd_reader->read(
+ sd_reader, IObuff, (size_t) (offset - read_bytes > IOSIZE
+ ? IOSIZE
+ : offset - read_bytes));
+ if (new_read_bytes == -1) {
+ return FAIL;
+ }
+ read_bytes += (size_t) new_read_bytes;
+ } while (read_bytes < offset && !sd_reader->eof);
+
+ return (read_bytes == offset ? OK : FAIL);
+}
+
+/// Wrapper for read that can be used when lseek cannot be used
+///
+/// E.g. when trying to read from a pipe.
+///
+/// @param[in,out] sd_reader File read.
+/// @param[in] offset Amount of bytes to skip.
+///
+/// @return kSDReadStatusReadError, kSDReadStatusNotShaDa or
+/// kSDReadStatusSuccess.
+static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader,
+ const size_t offset)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ if (sd_reader->skip(sd_reader, offset) != OK) {
+ if (sd_reader->error != NULL) {
+ emsg2(_(SERR "System error while skipping in ShaDa file: %s"),
+ sd_reader->error);
+ return kSDReadStatusReadError;
+ } else if (sd_reader->eof) {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "last entry specified that it occupies %" PRIu64 " bytes, "
+ "but file ended earlier"),
+ (uint64_t) offset);
+ return kSDReadStatusNotShaDa;
+ }
+ assert(false);
+ }
+ return kSDReadStatusSuccess;
+}
+
+/// Wrapper for opening file descriptors
+///
+/// All arguments are passed to os_open().
+///
+/// @return file descriptor or -errno on failure.
+static int open_file(const char *const fname, const int flags, const int mode)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ bool did_try_to_free = false;
+ int fd;
+open_file_start:
+ fd = os_open(fname, flags, mode);
+
+ if (fd < 0) {
+ if (-fd == ENOENT) {
+ return fd;
+ }
+ if (-fd == ENOMEM && !did_try_to_free) {
+ try_to_free_memory();
+ did_try_to_free = true;
+ goto open_file_start;
+ }
+ if (-fd != EEXIST) {
+ emsg3(_(SERR "System error while opening ShaDa file %s: %s"),
+ fname, os_strerror(fd));
+ }
+ return fd;
+ }
+ return fd;
+}
+
+/// Open ShaDa file for reading
+///
+/// @param[in] fname File name to open.
+/// @param[out] sd_reader Location where reader structure will be saved.
+///
+/// @return -errno in case of error, 0 otherwise.
+static int open_shada_file_for_reading(const char *const fname,
+ ShaDaReadDef *sd_reader)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ const intptr_t fd = (intptr_t) open_file(fname, O_RDONLY, 0);
+
+ if (fd < 0) {
+ return (int) fd;
+ }
+
+ *sd_reader = (ShaDaReadDef) {
+ .read = &read_file,
+ .close = &close_sd_reader,
+ .skip = &sd_reader_skip_read,
+ .error = NULL,
+ .eof = false,
+ .fpos = 0,
+ .cookie = (void *) fd,
+ };
+
+ convert_setup(&sd_reader->sd_conv, "utf-8", p_enc);
+
+ return 0;
+}
+
+/// Wrapper for closing file descriptors
+static void close_file(int fd)
+{
+close_file_start:
+ if (close(fd) == -1) {
+ if (errno == EINTR) {
+ errno = 0;
+ goto close_file_start;
+ } else {
+ emsg2(_(SERR "System error while closing ShaDa file: %s"),
+ strerror(errno));
+ errno = 0;
+ }
+ }
+}
+
+/// Check whether buffer is in the given set
+///
+/// @param[in] set Set to check within.
+/// @param[in] buf Buffer to find.
+///
+/// @return true or false.
+static inline bool in_bufset(const khash_t(bufset) *const set, const buf_T *buf)
+ FUNC_ATTR_PURE
+{
+ return kh_get(bufset, set, (uintptr_t) buf) != kh_end(set);
+}
+
+/// Check whether string is in the given set
+///
+/// @param[in] set Set to check within.
+/// @param[in] buf Buffer to find.
+///
+/// @return true or false.
+static inline bool in_strset(const khash_t(strset) *const set, char *str)
+ FUNC_ATTR_PURE
+{
+ return kh_get(strset, set, str) != kh_end(set);
+}
+
+/// Msgpack callback for writing to ShaDaWriteDef*
+static int msgpack_sd_writer_write(void *data, const char *buf, size_t len)
+{
+ ShaDaWriteDef *const sd_writer = (ShaDaWriteDef *) data;
+ ptrdiff_t written_bytes = sd_writer->write(sd_writer, buf, len);
+ if (written_bytes == -1) {
+ emsg2(_(SERR "System error while writing ShaDa file: %s"),
+ sd_writer->error);
+ return -1;
+ }
+ return 0;
+}
+
+/// Check whether writing to shada file was disabled with -i NONE
+///
+/// @return true if it was disabled, false otherwise.
+static bool shada_disabled(void)
+ FUNC_ATTR_PURE
+{
+ return used_shada_file != NULL && STRCMP(used_shada_file, "NONE") == 0;
+}
+
+/// Read ShaDa file
+///
+/// @param[in] file File to read or NULL to use default name.
+/// @param[in] flags Flags, see ShaDaReadFileFlags enum.
+///
+/// @return FAIL if reading failed for some reason and OK otherwise.
+static int shada_read_file(const char *const file, const int flags)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (shada_disabled()) {
+ return FAIL;
+ }
+
+ char *const fname = shada_filename(file);
+
+ ShaDaReadDef sd_reader;
+ const int of_ret = open_shada_file_for_reading(fname, &sd_reader);
+
+ if (p_verbose > 0) {
+ verbose_enter();
+ smsg(_("Reading ShaDa file \"%s\"%s%s%s"),
+ fname,
+ (flags & kShaDaWantInfo) ? _(" info") : "",
+ (flags & kShaDaWantMarks) ? _(" marks") : "",
+ (flags & kShaDaGetOldfiles) ? _(" oldfiles") : "",
+ of_ret != 0 ? _(" FAILED") : "");
+ verbose_leave();
+ }
+
+ if (of_ret != 0) {
+ if (-of_ret == ENOENT && (flags & kShaDaMissingError)) {
+ emsg3(_(SERR "System error while opening ShaDa file %s for reading: %s"),
+ fname, os_strerror(of_ret));
+ }
+ xfree(fname);
+ return FAIL;
+ }
+ xfree(fname);
+
+ shada_read(&sd_reader, flags);
+ sd_reader.close(&sd_reader);
+
+ return OK;
+}
+
+/// Wrapper for hist_iter() function which produces ShadaEntry values
+///
+/// @param[in] iter Current iteration state.
+/// @param[in] history_type Type of the history (HIST_*).
+/// @param[in] zero If true, then item is removed from instance
+/// memory upon reading.
+/// @param[out] hist Location where iteration results should be saved.
+///
+/// @return Next iteration state.
+static const void *shada_hist_iter(const void *const iter,
+ const uint8_t history_type,
+ const bool zero,
+ ShadaEntry *const hist)
+ FUNC_ATTR_NONNULL_ARG(4) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ histentry_T hist_he;
+ const void *const ret = hist_iter(iter, history_type, zero, &hist_he);
+ if (hist_he.hisstr == NULL) {
+ *hist = (ShadaEntry) { .type = kSDItemMissing };
+ } else {
+ *hist = (ShadaEntry) {
+ .type = kSDItemHistoryEntry,
+ .timestamp = hist_he.timestamp,
+ .data = {
+ .history_item = {
+ .histtype = history_type,
+ .string = (char *) hist_he.hisstr,
+ .sep = (char) (history_type == HIST_SEARCH
+ ? (char) hist_he.hisstr[STRLEN(hist_he.hisstr) + 1]
+ : 0),
+ .additional_elements = hist_he.additional_elements,
+ }
+ }
+ };
+ }
+ return ret;
+}
+
+/// Insert history entry
+///
+/// Inserts history entry at the end of the ring buffer (may insert earlier
+/// according to the timestamp). If entry was already in the ring buffer
+/// existing entry will be removed unless it has greater timestamp.
+///
+/// Before the new entry entries from the current Neovim history will be
+/// inserted unless `do_iter` argument is false.
+///
+/// @param[in,out] hms_p Ring buffer and associated structures.
+/// @param[in] entry Inserted entry.
+/// @param[in] do_iter Determines whether Neovim own history should
+/// be used. Must be true only if inserting
+/// entry from current Neovim history.
+/// @param[in] can_free_entry True if entry can be freed.
+static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry,
+ const bool do_iter, const bool can_free_entry)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (do_iter) {
+ while (hms_p->last_hist_entry.type != kSDItemMissing
+ && hms_p->last_hist_entry.timestamp < entry.timestamp) {
+ hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading);
+ if (hms_p->iter == NULL) {
+ hms_p->last_hist_entry.type = kSDItemMissing;
+ break;
+ }
+ hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type,
+ hms_p->reading, &hms_p->last_hist_entry);
+ }
+ }
+ HMLList *const hmll = &hms_p->hmll;
+ const khiter_t k = kh_get(hmll_entries, &hms_p->hmll.contained_entries,
+ entry.data.history_item.string);
+ if (k != kh_end(&hmll->contained_entries)) {
+ HMLListEntry *const existing_entry = kh_val(&hmll->contained_entries, k);
+ if (entry.timestamp > existing_entry->data.timestamp) {
+ hmll_remove(hmll, existing_entry);
+ } else if (!do_iter && entry.timestamp == existing_entry->data.timestamp) {
+ // Prefer entry from the current Neovim instance.
+ if (existing_entry->can_free_entry) {
+ shada_free_shada_entry(&existing_entry->data);
+ }
+ existing_entry->data = entry;
+ existing_entry->can_free_entry = can_free_entry;
+ // Previous key was freed above, as part of freeing the ShaDa entry.
+ kh_key(&hmll->contained_entries, k) = entry.data.history_item.string;
+ return;
+ } else {
+ return;
+ }
+ }
+ HMLListEntry *insert_after;
+ HMLL_ITER_BACK(hmll, insert_after) {
+ if (insert_after->data.timestamp <= entry.timestamp) {
+ break;
+ }
+ }
+ hmll_insert(hmll, insert_after, entry, can_free_entry);
+}
+
+/// Initialize the history merger
+///
+/// @param[out] hms_p Structure to be initialized.
+/// @param[in] history_type History type (one of HIST_\* values).
+/// @param[in] num_elements Number of elements in the result.
+/// @param[in] do_merge Prepare structure for merging elements.
+/// @param[in] reading If true, then merger is reading history for use
+/// in Neovim.
+static inline void hms_init(HistoryMergerState *const hms_p,
+ const uint8_t history_type,
+ const size_t num_elements,
+ const bool do_merge,
+ const bool reading)
+ FUNC_ATTR_NONNULL_ALL
+{
+ hmll_init(&hms_p->hmll, num_elements);
+ hms_p->do_merge = do_merge;
+ hms_p->reading = reading;
+ hms_p->iter = shada_hist_iter(NULL, history_type, hms_p->reading,
+ &hms_p->last_hist_entry);
+ hms_p->history_type = history_type;
+}
+
+/// Merge in all remaining Neovim own history entries
+///
+/// @param[in,out] hms_p Merger structure into which history should be
+/// inserted.
+static inline void hms_insert_whole_neovim_history(
+ HistoryMergerState *const hms_p)
+ FUNC_ATTR_NONNULL_ALL
+{
+ while (hms_p->last_hist_entry.type != kSDItemMissing) {
+ hms_insert(hms_p, hms_p->last_hist_entry, false, hms_p->reading);
+ if (hms_p->iter == NULL) {
+ break;
+ }
+ hms_p->iter = shada_hist_iter(hms_p->iter, hms_p->history_type,
+ hms_p->reading, &hms_p->last_hist_entry);
+ }
+}
+
+/// Convert merger structure to Neovim internal structure for history
+///
+/// @param[in] hms_p Converted merger structure.
+/// @param[out] hist_array Array with the results.
+/// @param[out] new_hisidx New last history entry index.
+/// @param[out] new_hisnum Amount of history items in merger structure.
+static inline void hms_to_he_array(const HistoryMergerState *const hms_p,
+ histentry_T *const hist_array,
+ int *const new_hisidx,
+ int *const new_hisnum)
+ FUNC_ATTR_NONNULL_ALL
+{
+ histentry_T *hist = hist_array;
+ HMLL_FORALL(&hms_p->hmll, cur_entry) {
+ hist->timestamp = cur_entry->data.timestamp;
+ hist->hisnum = (int) (hist - hist_array) + 1;
+ hist->hisstr = (char_u *) cur_entry->data.data.history_item.string;
+ hist->additional_elements =
+ cur_entry->data.data.history_item.additional_elements;
+ hist++;
+ }
+ *new_hisnum = (int) (hist - hist_array);
+ *new_hisidx = *new_hisnum - 1;
+}
+
+/// Free history merger structure
+///
+/// @param[in] hms_p Structure to be freed.
+static inline void hms_dealloc(HistoryMergerState *const hms_p)
+ FUNC_ATTR_NONNULL_ALL
+{
+ hmll_dealloc(&hms_p->hmll);
+}
+
+/// Iterate over all history entries in history merger, in order
+///
+/// @param[in] hms_p Merger structure to iterate over.
+/// @param[out] cur_entry Name of the iterator variable.
+///
+/// @return for cycle header. Use `HMS_ITER(hms_p, cur_entry) {body}`.
+#define HMS_ITER(hms_p, cur_entry) \
+ HMLL_FORALL(&((hms_p)->hmll), cur_entry)
+
+/// Find buffer for given buffer name (cached)
+///
+/// @param[in,out] fname_bufs Cache containing fname to buffer mapping.
+/// @param[in] fname File name to find.
+///
+/// @return Pointer to the buffer or NULL.
+static buf_T *find_buffer(khash_t(fnamebufs) *const fname_bufs,
+ const char *const fname)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ int kh_ret;
+ khint_t k = kh_put(fnamebufs, fname_bufs, fname, &kh_ret);
+ if (!kh_ret) {
+ return kh_val(fname_bufs, k);
+ }
+ kh_key(fname_bufs, k) = xstrdup(fname);
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL) {
+ if (fnamecmp(fname, buf->b_ffname) == 0) {
+ kh_val(fname_bufs, k) = buf;
+ return buf;
+ }
+ }
+ }
+ kh_val(fname_bufs, k) = NULL;
+ return NULL;
+}
+
+/// Compare two marks
+static inline bool marks_equal(const pos_T a, const pos_T b)
+{
+ return (a.lnum == b.lnum) && (a.col == b.col);
+}
+
+#define MERGE_JUMPS(jumps_size, jumps, jumps_type, timestamp_attr, mark_attr, \
+ entry, fname_cond, free_func, fin_func, \
+ idxadj_func, afterfree_func) \
+ do { \
+ const int jl_len = (int) jumps_size; \
+ int i; \
+ for (i = jl_len; i > 0; i--) { \
+ const jumps_type jl_entry = jumps[i - 1]; \
+ if (jl_entry.timestamp_attr <= entry.timestamp) { \
+ if (marks_equal(jl_entry.mark_attr, entry.data.filemark.mark) \
+ && fname_cond) { \
+ i = -1; \
+ } \
+ break; \
+ } \
+ } \
+ if (i > 0) { \
+ if (jl_len == JUMPLISTSIZE) { \
+ free_func(jumps[0]); \
+ i--; \
+ if (i > 0) { \
+ memmove(&jumps[0], &jumps[1], sizeof(jumps[1]) * (size_t) i); \
+ } \
+ } else if (i != jl_len) { \
+ memmove(&jumps[i + 1], &jumps[i], \
+ sizeof(jumps[0]) * (size_t) (jl_len - i)); \
+ } \
+ } else if (i == 0) { \
+ if (jl_len == JUMPLISTSIZE) { \
+ i = -1; \
+ } else if (jl_len > 0) { \
+ memmove(&jumps[1], &jumps[0], sizeof(jumps[0]) * (size_t) jl_len); \
+ } \
+ } \
+ if (i != -1) { \
+ jumps[i] = fin_func(entry); \
+ if (jl_len < JUMPLISTSIZE) { \
+ jumps_size++; \
+ } \
+ idxadj_func(i); \
+ } else { \
+ shada_free_shada_entry(&entry); \
+ afterfree_func(entry); \
+ } \
+ } while (0)
+
+/// Read data from ShaDa file
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[in] flags What to read, see ShaDaReadFileFlags enum.
+static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES);
+ const bool force = flags & kShaDaForceit;
+ const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit)
+ && (force || oldfiles_list == NULL
+ || oldfiles_list->lv_len == 0));
+ const bool want_marks = flags & kShaDaWantMarks;
+ const unsigned srni_flags = (unsigned) (
+ (flags & kShaDaWantInfo
+ ? (kSDReadUndisableableData
+ | kSDReadRegisters
+ | kSDReadGlobalMarks
+ | (p_hi ? kSDReadHistory : 0)
+ | (find_shada_parameter('!') != NULL
+ ? kSDReadVariables
+ : 0)
+ | (find_shada_parameter('%') != NULL
+ && ARGCOUNT == 0
+ ? kSDReadBufferList
+ : 0))
+ : 0)
+ | (want_marks && get_shada_parameter('\'') > 0
+ ? kSDReadLocalMarks | kSDReadChanges
+ : 0)
+ | (get_old_files
+ ? kSDReadLocalMarks
+ : 0));
+ if (srni_flags == 0) {
+ // Nothing to do.
+ return;
+ }
+ HistoryMergerState hms[HIST_COUNT];
+ if (srni_flags & kSDReadHistory) {
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ hms_init(&hms[i], i, (size_t) p_hi, true, true);
+ }
+ }
+ ShadaEntry cur_entry;
+ khash_t(bufset) cl_bufs = KHASH_EMPTY_TABLE(bufset);
+ khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs);
+ khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset);
+ if (get_old_files && (oldfiles_list == NULL || force)) {
+ oldfiles_list = list_alloc();
+ set_vim_var_list(VV_OLDFILES, oldfiles_list);
+ }
+ ShaDaReadResult srni_ret;
+ while ((srni_ret = shada_read_next_item(sd_reader, &cur_entry, srni_flags, 0))
+ != kSDReadStatusFinished) {
+ switch (srni_ret) {
+ case kSDReadStatusSuccess: {
+ break;
+ }
+ case kSDReadStatusFinished: {
+ // Should be handled by the while condition.
+ assert(false);
+ }
+ case kSDReadStatusNotShaDa:
+ case kSDReadStatusReadError: {
+ goto shada_read_main_cycle_end;
+ }
+ case kSDReadStatusMalformed: {
+ continue;
+ }
+ }
+ switch (cur_entry.type) {
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ break;
+ }
+ case kSDItemHeader: {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ if (!force) {
+ SearchPattern pat;
+ (cur_entry.data.search_pattern.is_substitute_pattern
+ ? &get_substitute_pattern
+ : &get_search_pattern)(&pat);
+ if (pat.pat != NULL && pat.timestamp >= cur_entry.timestamp) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ }
+ (cur_entry.data.search_pattern.is_substitute_pattern
+ ? &set_substitute_pattern
+ : &set_search_pattern)((SearchPattern) {
+ .magic = cur_entry.data.search_pattern.magic,
+ .no_scs = !cur_entry.data.search_pattern.smartcase,
+ .off = {
+ .line = cur_entry.data.search_pattern.has_line_offset,
+ .end = cur_entry.data.search_pattern.place_cursor_at_end,
+ .off = cur_entry.data.search_pattern.offset,
+ },
+ .pat = (char_u *) cur_entry.data.search_pattern.pat,
+ .additional_data = cur_entry.data.search_pattern.additional_data,
+ .timestamp = cur_entry.timestamp,
+ });
+ if (cur_entry.data.search_pattern.is_last_used) {
+ set_last_used_pattern(
+ cur_entry.data.search_pattern.is_substitute_pattern);
+ }
+ if (cur_entry.data.search_pattern.is_last_used) {
+ SET_NO_HLSEARCH(!cur_entry.data.search_pattern.highlighted);
+ }
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemSubString: {
+ if (!force) {
+ SubReplacementString sub;
+ sub_get_replacement(&sub);
+ if (sub.sub != NULL && sub.timestamp >= cur_entry.timestamp) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ }
+ sub_set_replacement((SubReplacementString) {
+ .sub = cur_entry.data.sub_string.sub,
+ .timestamp = cur_entry.timestamp,
+ .additional_elements = cur_entry.data.sub_string.additional_elements,
+ });
+ // Without using regtilde and without / &cpo flag previous substitute
+ // string is close to useless: you can only use it with :& or :~ and
+ // that’s all because s//~ is not available until the first call to
+ // regtilde. Vim was not calling this for some reason.
+ (void) regtilde(cur_entry.data.sub_string.sub, p_magic);
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (cur_entry.data.history_item.histtype >= HIST_COUNT) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ hms_insert(hms + cur_entry.data.history_item.histtype, cur_entry, true,
+ true);
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemRegister: {
+ if (cur_entry.data.reg.type != MCHAR
+ && cur_entry.data.reg.type != MLINE
+ && cur_entry.data.reg.type != MBLOCK) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ if (!force) {
+ const yankreg_T *const reg = op_register_get(cur_entry.data.reg.name);
+ if (reg == NULL || reg->timestamp >= cur_entry.timestamp) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ }
+ if (!op_register_set(cur_entry.data.reg.name, (yankreg_T) {
+ .y_array = (char_u **) cur_entry.data.reg.contents,
+ .y_size = (linenr_T) cur_entry.data.reg.contents_size,
+ .y_type = cur_entry.data.reg.type,
+ .y_width = (colnr_T) cur_entry.data.reg.width,
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.reg.additional_data,
+ })) {
+ shada_free_shada_entry(&cur_entry);
+ }
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemVariable: {
+ var_set_global(cur_entry.data.global_var.name,
+ cur_entry.data.global_var.value);
+ cur_entry.data.global_var.value.v_type = VAR_UNKNOWN;
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemJump:
+ case kSDItemGlobalMark: {
+ buf_T *buf = find_buffer(&fname_bufs, cur_entry.data.filemark.fname);
+ if (buf != NULL) {
+ xfree(cur_entry.data.filemark.fname);
+ cur_entry.data.filemark.fname = NULL;
+ }
+ xfmark_T fm = (xfmark_T) {
+ .fname = (char_u *) (buf == NULL
+ ? cur_entry.data.filemark.fname
+ : NULL),
+ .fmark = {
+ .mark = cur_entry.data.filemark.mark,
+ .fnum = (buf == NULL ? 0 : buf->b_fnum),
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.filemark.additional_data,
+ },
+ };
+ if (cur_entry.type == kSDItemGlobalMark) {
+ if (!mark_set_global(cur_entry.data.filemark.name, fm, !force)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ } else {
+#define SDE_TO_XFMARK(entry) fm
+#define ADJUST_IDX(i) \
+ if (curwin->w_jumplistidx >= i \
+ && curwin->w_jumplistidx + 1 <= curwin->w_jumplistlen) { \
+ curwin->w_jumplistidx++; \
+ }
+#define DUMMY_AFTERFREE(entry)
+ MERGE_JUMPS(curwin->w_jumplistlen, curwin->w_jumplist, xfmark_T,
+ fmark.timestamp, fmark.mark, cur_entry,
+ (buf == NULL
+ ? (jl_entry.fname != NULL
+ && STRCMP(fm.fname, jl_entry.fname) == 0)
+ : fm.fmark.fnum == jl_entry.fmark.fnum),
+ free_xfmark, SDE_TO_XFMARK, ADJUST_IDX, DUMMY_AFTERFREE);
+#undef SDE_TO_XFMARK
+#undef ADJUST_IDX
+#undef DUMMY_AFTERFREE
+ }
+ // Do not free shada entry: its allocated memory was saved above.
+ break;
+ }
+ case kSDItemBufferList: {
+ for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) {
+ char *const sfname = path_shorten_fname_if_possible(
+ cur_entry.data.buffer_list.buffers[i].fname);
+ buf_T *const buf = buflist_new(
+ cur_entry.data.buffer_list.buffers[i].fname, sfname, 0,
+ BLN_LISTED);
+ if (buf != NULL) {
+ RESET_FMARK(&buf->b_last_cursor,
+ cur_entry.data.buffer_list.buffers[i].pos, 0);
+ buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum,
+ buf->b_last_cursor.mark.col, false);
+ buf->additional_data =
+ cur_entry.data.buffer_list.buffers[i].additional_data;
+ cur_entry.data.buffer_list.buffers[i].additional_data = NULL;
+ }
+ }
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemLocalMark: {
+ if (get_old_files && !in_strset(&oldfiles_set,
+ cur_entry.data.filemark.fname)) {
+ char *fname = cur_entry.data.filemark.fname;
+ if (want_marks) {
+ // Do not bother with allocating memory for the string if already
+ // allocated string from cur_entry can be used. It cannot be used if
+ // want_marks is set because this way it may be used for a mark.
+ fname = xstrdup(fname);
+ }
+ int kh_ret;
+ (void) kh_put(strset, &oldfiles_set, fname, &kh_ret);
+ list_append_allocated_string(oldfiles_list, fname);
+ if (!want_marks) {
+ // Avoid free because this string was already used.
+ cur_entry.data.filemark.fname = NULL;
+ }
+ }
+ if (!want_marks) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ buf_T *buf = find_buffer(&fname_bufs, cur_entry.data.filemark.fname);
+ if (buf == NULL) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ const fmark_T fm = (fmark_T) {
+ .mark = cur_entry.data.filemark.mark,
+ .fnum = 0,
+ .timestamp = cur_entry.timestamp,
+ .additional_data = cur_entry.data.filemark.additional_data,
+ };
+ if (cur_entry.type == kSDItemLocalMark) {
+ if (!mark_set_local(cur_entry.data.filemark.name, buf, fm, !force)) {
+ shada_free_shada_entry(&cur_entry);
+ break;
+ }
+ } else {
+ int kh_ret;
+ (void) kh_put(bufset, &cl_bufs, (uintptr_t) buf, &kh_ret);
+#define SDE_TO_FMARK(entry) fm
+#define AFTERFREE(entry) (entry).data.filemark.fname = NULL
+#define DUMMY_IDX_ADJ(i)
+ MERGE_JUMPS(buf->b_changelistlen, buf->b_changelist, fmark_T,
+ timestamp, mark, cur_entry, true,
+ free_fmark, SDE_TO_FMARK, DUMMY_IDX_ADJ, AFTERFREE);
+#undef SDE_TO_FMARK
+#undef AFTERFREE
+#undef DUMMY_IDX_ADJ
+ }
+ // Do not free shada entry: except for fname, its allocated memory (i.e.
+ // additional_data attribute contents if non-NULL) was saved above.
+ xfree(cur_entry.data.filemark.fname);
+ break;
+ }
+ }
+ }
+shada_read_main_cycle_end:
+ // Warning: shada_hist_iter returns ShadaEntry elements which use strings from
+ // original history list. This means that once such entry is removed
+ // from the history Neovim array will no longer be valid. To reduce
+ // amount of memory allocations ShaDa file reader allocates enough
+ // memory for the history string itself and separator character which
+ // may be assigned right away.
+ if (srni_flags & kSDReadHistory) {
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ hms_insert_whole_neovim_history(&hms[i]);
+ clr_history(i);
+ int *new_hisidx;
+ int *new_hisnum;
+ histentry_T *hist = hist_get_array(i, &new_hisidx, &new_hisnum);
+ if (hist != NULL) {
+ hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum);
+ }
+ hms_dealloc(&hms[i]);
+ }
+ }
+ if (cl_bufs.n_occupied) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ (void) tp;
+ if (in_bufset(&cl_bufs, wp->w_buffer)) {
+ wp->w_changelistidx = wp->w_buffer->b_changelistlen;
+ }
+ }
+ }
+ kh_dealloc(bufset, &cl_bufs);
+ const char *key;
+ kh_foreach_key(&fname_bufs, key, {
+ xfree((void *) key);
+ })
+ kh_dealloc(fnamebufs, &fname_bufs);
+ kh_dealloc(strset, &oldfiles_set);
+}
+
+/// Get the ShaDa file name to use
+///
+/// If "file" is given and not empty, use it (has already been expanded by
+/// cmdline functions). Otherwise use "-i file_name", value from 'shada' or the
+/// default, and expand environment variables.
+///
+/// @param[in] file Forced file name or NULL.
+///
+/// @return An allocated string containing shada file name.
+static char *shada_filename(const char *file)
+ FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (file == NULL || *file == NUL) {
+ if (used_shada_file != NULL) {
+ file = used_shada_file;
+ } else {
+ if ((file = find_shada_parameter('n')) == NULL || *file == NUL) {
+#ifdef SHADA_FILE2
+ // don't use $HOME when not defined (turned into "c:/"!).
+ if (os_getenv((char_u *)"HOME") == NULL) {
+ // don't use $VIM when not available.
+ expand_env((char_u *)"$VIM", NameBuff, MAXPATHL);
+ if (STRCMP("$VIM", NameBuff) != 0) { // $VIM was expanded
+ file = SHADA_FILE2;
+ } else {
+ file = SHADA_FILE;
+ }
+ } else {
+#endif
+ file = SHADA_FILE;
+#ifdef SHADA_FILE2
+ }
+#endif
+ }
+ // XXX It used to be one level lower, so that whatever is in
+ // `used_shada_file` was expanded. I intentionally moved it here
+ // because various expansions must have already be done by the shell.
+ // If shell is not performing them then they should be done in main.c
+ // where arguments are parsed, *not here*.
+ expand_env((char_u *)file, &(NameBuff[0]), MAXPATHL);
+ file = (const char *) &(NameBuff[0]);
+ }
+ }
+ return xstrdup(file);
+}
+
+#define PACK_STATIC_STR(s) \
+ do { \
+ msgpack_pack_str(spacker, sizeof(s) - 1); \
+ msgpack_pack_str_body(spacker, s, sizeof(s) - 1); \
+ } while (0)
+#define PACK_STRING(s) \
+ do { \
+ const String s_ = (s); \
+ msgpack_pack_str(spacker, s_.size); \
+ msgpack_pack_str_body(spacker, s_.data, s_.size); \
+ } while (0)
+#define PACK_BIN(s) \
+ do { \
+ const String s_ = (s); \
+ msgpack_pack_bin(spacker, s_.size); \
+ msgpack_pack_bin_body(spacker, s_.data, s_.size); \
+ } while (0)
+
+/// Write single ShaDa entry
+///
+/// @param[in] packer Packer used to write entry.
+/// @param[in] entry Entry written.
+/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no
+/// restrictions.
+static bool shada_pack_entry(msgpack_packer *const packer,
+ ShadaEntry entry,
+ const size_t max_kbyte)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer sbuf;
+ msgpack_sbuffer_init(&sbuf);
+ msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write);
+#define DUMP_ADDITIONAL_ELEMENTS(src) \
+ do { \
+ if ((src) != NULL) { \
+ for (listitem_T *li = (src)->lv_first; li != NULL; li = li->li_next) { \
+ if (vim_to_msgpack(spacker, &li->li_tv) == FAIL) { \
+ goto shada_pack_entry_error; \
+ } \
+ } \
+ } \
+ } while (0)
+#define DUMP_ADDITIONAL_DATA(src) \
+ do { \
+ dict_T *const d = (src); \
+ if (d != NULL) { \
+ size_t todo = d->dv_hashtab.ht_used; \
+ for (const hashitem_T *hi= d->dv_hashtab.ht_array; todo; hi++) { \
+ if (!HASHITEM_EMPTY(hi)) { \
+ todo--; \
+ dictitem_T *const di = HI2DI(hi); \
+ const size_t key_len = strlen((const char *) hi->hi_key); \
+ msgpack_pack_str(spacker, key_len); \
+ msgpack_pack_str_body(spacker, (const char *) hi->hi_key, key_len); \
+ if (vim_to_msgpack(spacker, &di->di_tv) == FAIL) { \
+ goto shada_pack_entry_error; \
+ } \
+ } \
+ } \
+ } \
+ } while (0)
+#define CHECK_DEFAULT(entry, attr) \
+ (sd_default_values[entry.type].data.attr == entry.data.attr)
+#define ONE_IF_NOT_DEFAULT(entry, attr) \
+ ((size_t) (!CHECK_DEFAULT(entry, attr)))
+ switch (entry.type) {
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ if (spacker->callback(spacker->data, entry.data.unknown_item.contents,
+ (unsigned) entry.data.unknown_item.size) == -1) {
+ goto shada_pack_entry_error;
+ }
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ const bool is_hist_search =
+ entry.data.history_item.histtype == HIST_SEARCH;
+ const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) (
+ entry.data.history_item.additional_elements == NULL
+ ? 0
+ : entry.data.history_item.additional_elements->lv_len);
+ msgpack_pack_array(spacker, arr_size);
+ msgpack_pack_uint8(spacker, entry.data.history_item.histtype);
+ PACK_BIN(cstr_as_string(entry.data.history_item.string));
+ if (is_hist_search) {
+ msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep);
+ }
+ DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements);
+ break;
+ }
+ case kSDItemVariable: {
+ const size_t arr_size = 2 + (size_t) (
+ entry.data.global_var.additional_elements == NULL
+ ? 0
+ : entry.data.global_var.additional_elements->lv_len);
+ msgpack_pack_array(spacker, arr_size);
+ PACK_BIN(cstr_as_string(entry.data.global_var.name));
+ if (vim_to_msgpack(spacker, &entry.data.global_var.value) == FAIL) {
+ goto shada_pack_entry_error;
+ }
+ DUMP_ADDITIONAL_ELEMENTS(entry.data.global_var.additional_elements);
+ break;
+ }
+ case kSDItemSubString: {
+ const size_t arr_size = 1 + (size_t) (
+ entry.data.sub_string.additional_elements == NULL
+ ? 0
+ : entry.data.sub_string.additional_elements->lv_len);
+ msgpack_pack_array(spacker, arr_size);
+ PACK_BIN(cstr_as_string(entry.data.sub_string.sub));
+ DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ const size_t map_size = (size_t) (
+ 1 // Search pattern is always present
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_substitute_pattern)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
+ // finally, additional data:
+ + (size_t) (
+ entry.data.search_pattern.additional_data
+ ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used
+ : 0));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(SEARCH_KEY_PAT);
+ PACK_BIN(cstr_as_string(entry.data.search_pattern.pat));
+#define PACK_BOOL(entry, name, attr) \
+ do { \
+ if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \
+ PACK_STATIC_STR(name); \
+ if (sd_default_values[entry.type].data.search_pattern.attr) { \
+ msgpack_pack_false(spacker); \
+ } else { \
+ msgpack_pack_true(spacker); \
+ } \
+ } \
+ } while (0)
+ PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic);
+ PACK_BOOL(entry, SEARCH_KEY_IS_LAST_USED, is_last_used);
+ PACK_BOOL(entry, SEARCH_KEY_SMARTCASE, smartcase);
+ PACK_BOOL(entry, SEARCH_KEY_HAS_LINE_OFFSET, has_line_offset);
+ PACK_BOOL(entry, SEARCH_KEY_PLACE_CURSOR_AT_END, place_cursor_at_end);
+ PACK_BOOL(entry, SEARCH_KEY_IS_SUBSTITUTE_PATTERN, is_substitute_pattern);
+ PACK_BOOL(entry, SEARCH_KEY_HIGHLIGHTED, highlighted);
+ if (!CHECK_DEFAULT(entry, search_pattern.offset)) {
+ PACK_STATIC_STR(SEARCH_KEY_OFFSET);
+ msgpack_pack_int64(spacker, entry.data.search_pattern.offset);
+ }
+#undef PACK_BOOL
+ DUMP_ADDITIONAL_DATA(entry.data.search_pattern.additional_data);
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark:
+ case kSDItemJump: {
+ const size_t map_size = (size_t) (
+ 1 // File name
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.name)
+ // Additional entries, if any:
+ + (size_t) (
+ entry.data.filemark.additional_data == NULL
+ ? 0
+ : entry.data.filemark.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(KEY_FILE);
+ PACK_BIN(cstr_as_string(entry.data.filemark.fname));
+ if (!CHECK_DEFAULT(entry, filemark.mark.lnum)) {
+ PACK_STATIC_STR(KEY_LNUM);
+ msgpack_pack_long(spacker, entry.data.filemark.mark.lnum);
+ }
+ if (!CHECK_DEFAULT(entry, filemark.mark.col)) {
+ PACK_STATIC_STR(KEY_COL);
+ msgpack_pack_long(spacker, entry.data.filemark.mark.col);
+ }
+ assert(entry.type == kSDItemJump || entry.type == kSDItemChange
+ ? CHECK_DEFAULT(entry, filemark.name)
+ : true);
+ if (!CHECK_DEFAULT(entry, filemark.name)) {
+ PACK_STATIC_STR(KEY_NAME_CHAR);
+ msgpack_pack_uint8(spacker, (uint8_t) entry.data.filemark.name);
+ }
+ DUMP_ADDITIONAL_DATA(entry.data.filemark.additional_data);
+ break;
+ }
+ case kSDItemRegister: {
+ const size_t map_size = (size_t) (
+ 2 // Register contents and name
+ + ONE_IF_NOT_DEFAULT(entry, reg.type)
+ + ONE_IF_NOT_DEFAULT(entry, reg.width)
+ // Additional entries, if any:
+ + (size_t) (entry.data.reg.additional_data == NULL
+ ? 0
+ : entry.data.reg.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(REG_KEY_CONTENTS);
+ msgpack_pack_array(spacker, entry.data.reg.contents_size);
+ for (size_t i = 0; i < entry.data.reg.contents_size; i++) {
+ PACK_BIN(cstr_as_string(entry.data.reg.contents[i]));
+ }
+ PACK_STATIC_STR(KEY_NAME_CHAR);
+ msgpack_pack_char(spacker, entry.data.reg.name);
+ if (!CHECK_DEFAULT(entry, reg.type)) {
+ PACK_STATIC_STR(REG_KEY_TYPE);
+ msgpack_pack_uint8(spacker, entry.data.reg.type);
+ }
+ if (!CHECK_DEFAULT(entry, reg.width)) {
+ PACK_STATIC_STR(REG_KEY_WIDTH);
+ msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width);
+ }
+ DUMP_ADDITIONAL_DATA(entry.data.reg.additional_data);
+ break;
+ }
+ case kSDItemBufferList: {
+ msgpack_pack_array(spacker, entry.data.buffer_list.size);
+ for (size_t i = 0; i < entry.data.buffer_list.size; i++) {
+ const size_t map_size = (size_t) (
+ 1 // Buffer name
+ + (size_t) (entry.data.buffer_list.buffers[i].pos.lnum
+ != default_pos.lnum)
+ + (size_t) (entry.data.buffer_list.buffers[i].pos.col
+ != default_pos.col)
+ // Additional entries, if any:
+ + (size_t) (
+ entry.data.buffer_list.buffers[i].additional_data == NULL
+ ? 0
+ : (entry.data.buffer_list.buffers[i].additional_data
+ ->dv_hashtab.ht_used)));
+ msgpack_pack_map(spacker, map_size);
+ PACK_STATIC_STR(KEY_FILE);
+ PACK_BIN(cstr_as_string(entry.data.buffer_list.buffers[i].fname));
+ if (entry.data.buffer_list.buffers[i].pos.lnum != 1) {
+ PACK_STATIC_STR(KEY_LNUM);
+ msgpack_pack_uint64(
+ spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.lnum);
+ }
+ if (entry.data.buffer_list.buffers[i].pos.col != 0) {
+ PACK_STATIC_STR(KEY_COL);
+ msgpack_pack_uint64(
+ spacker, (uint64_t) entry.data.buffer_list.buffers[i].pos.col);
+ }
+ DUMP_ADDITIONAL_DATA(entry.data.buffer_list.buffers[i].additional_data);
+ }
+ break;
+ }
+ case kSDItemHeader: {
+ msgpack_pack_map(spacker, entry.data.header.size);
+ for (size_t i = 0; i < entry.data.header.size; i++) {
+ PACK_STRING(entry.data.header.items[i].key);
+ const Object obj = entry.data.header.items[i].value;
+ switch (obj.type) {
+ case kObjectTypeString: {
+ PACK_BIN(obj.data.string);
+ break;
+ }
+ case kObjectTypeInteger: {
+ msgpack_pack_int64(spacker, (int64_t) obj.data.integer);
+ break;
+ }
+ default: {
+ assert(false);
+ }
+ }
+ }
+ break;
+ }
+ }
+#undef CHECK_DEFAULT
+#undef ONE_IF_NOT_DEFAULT
+ if (!max_kbyte || sbuf.size <= max_kbyte * 1024) {
+ if (entry.type == kSDItemUnknown) {
+ if (msgpack_pack_uint64(packer, entry.data.unknown_item.type) == -1) {
+ goto shada_pack_entry_error;
+ }
+ } else {
+ if (msgpack_pack_uint64(packer, (uint64_t) entry.type) == -1) {
+ goto shada_pack_entry_error;
+ }
+ }
+ if (msgpack_pack_uint64(packer, (uint64_t) entry.timestamp) == -1) {
+ goto shada_pack_entry_error;
+ }
+ if (sbuf.size > 0) {
+ if ((msgpack_pack_uint64(packer, (uint64_t) sbuf.size) == -1)
+ || (packer->callback(packer->data, sbuf.data,
+ (unsigned) sbuf.size) == -1)) {
+ goto shada_pack_entry_error;
+ }
+ }
+ }
+ msgpack_packer_free(spacker);
+ msgpack_sbuffer_destroy(&sbuf);
+ return true;
+shada_pack_entry_error:
+ msgpack_packer_free(spacker);
+ msgpack_sbuffer_destroy(&sbuf);
+ return false;
+}
+#undef PACK_STRING
+
+/// Write single ShaDa entry, converting it if needed
+///
+/// @warning Frees entry after packing.
+///
+/// @param[in] packer Packer used to write entry.
+/// @param[in] sd_conv Conversion definitions.
+/// @param[in] entry Entry written. If entry.can_free_entry is false then
+/// it assumes that entry was not converted, otherwise it
+/// is assumed that entry was already converted.
+/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no
+/// restrictions.
+static bool shada_pack_encoded_entry(msgpack_packer *const packer,
+ const vimconv_T *const sd_conv,
+ PossiblyFreedShadaEntry entry,
+ const size_t max_kbyte)
+ FUNC_ATTR_NONNULL_ALL
+{
+ bool ret = true;
+ if (entry.can_free_entry) {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ shada_free_shada_entry(&entry.data);
+ return ret;
+ }
+#define RUN_WITH_CONVERTED_STRING(cstr, code) \
+ do { \
+ bool did_convert = false; \
+ if (sd_conv->vc_type != CONV_NONE && has_non_ascii((cstr))) { \
+ char *const converted_string = string_convert(sd_conv, (cstr), NULL); \
+ if (converted_string != NULL) { \
+ (cstr) = converted_string; \
+ did_convert = true; \
+ } \
+ } \
+ code \
+ if (did_convert) { \
+ xfree((cstr)); \
+ } \
+ } while (0)
+ switch (entry.data.type) {
+ case kSDItemUnknown:
+ case kSDItemMissing: {
+ assert(false);
+ }
+ case kSDItemSearchPattern: {
+ RUN_WITH_CONVERTED_STRING(entry.data.data.search_pattern.pat, {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ });
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ RUN_WITH_CONVERTED_STRING(entry.data.data.history_item.string, {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ });
+ break;
+ }
+ case kSDItemSubString: {
+ RUN_WITH_CONVERTED_STRING(entry.data.data.sub_string.sub, {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ });
+ break;
+ }
+ case kSDItemVariable: {
+ if (sd_conv->vc_type != CONV_NONE) {
+ typval_T tgttv;
+ var_item_copy(sd_conv, &entry.data.data.global_var.value, &tgttv,
+ true, 0);
+ clear_tv(&entry.data.data.global_var.value);
+ entry.data.data.global_var.value = tgttv;
+ }
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ break;
+ }
+ case kSDItemRegister: {
+ bool did_convert = false;
+ if (sd_conv->vc_type != CONV_NONE) {
+ size_t first_non_ascii = 0;
+ for (size_t i = 0; i < entry.data.data.reg.contents_size; i++) {
+ if (has_non_ascii(entry.data.data.reg.contents[i])) {
+ first_non_ascii = i;
+ did_convert = true;
+ break;
+ }
+ }
+ if (did_convert) {
+ entry.data.data.reg.contents =
+ xmemdup(entry.data.data.reg.contents,
+ (entry.data.data.reg.contents_size
+ * sizeof(entry.data.data.reg.contents)));
+ for (size_t i = 0; i < entry.data.data.reg.contents_size; i++) {
+ if (i >= first_non_ascii) {
+ entry.data.data.reg.contents[i] = get_converted_string(
+ sd_conv,
+ entry.data.data.reg.contents[i],
+ strlen(entry.data.data.reg.contents[i]));
+ } else {
+ entry.data.data.reg.contents[i] =
+ xstrdup(entry.data.data.reg.contents[i]);
+ }
+ }
+ }
+ }
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ if (did_convert) {
+ for (size_t i = 0; i < entry.data.data.reg.contents_size; i++) {
+ xfree(entry.data.data.reg.contents[i]);
+ }
+ xfree(entry.data.data.reg.contents);
+ }
+ break;
+ }
+ case kSDItemHeader:
+ case kSDItemGlobalMark:
+ case kSDItemJump:
+ case kSDItemBufferList:
+ case kSDItemLocalMark:
+ case kSDItemChange: {
+ ret = shada_pack_entry(packer, entry.data, max_kbyte);
+ break;
+ }
+ }
+#undef RUN_WITH_CONVERTED_STRING
+ return ret;
+}
+
+/// Compare two FileMarks structure to order them by greatest_timestamp
+///
+/// Order is reversed: structure with greatest greatest_timestamp comes first.
+/// Function signature is compatible with qsort.
+static int compare_file_marks(const void *a, const void *b)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ const FileMarks *const *const a_fms = a;
+ const FileMarks *const *const b_fms = b;
+ return ((*a_fms)->greatest_timestamp == (*b_fms)->greatest_timestamp
+ ? 0
+ : ((*a_fms)->greatest_timestamp > (*b_fms)->greatest_timestamp
+ ? -1
+ : 1));
+}
+
+/// Parse msgpack object that has given length
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[in] length Object length.
+/// @param[out] ret_unpacked Location where read result should be saved. If
+/// NULL then unpacked data will be freed. Must be
+/// NULL if `ret_buf` is NULL.
+/// @param[out] ret_buf Buffer containing parsed string.
+///
+/// @return kSDReadStatusNotShaDa, kSDReadStatusReadError or
+/// kSDReadStatusSuccess.
+static inline ShaDaReadResult shada_parse_msgpack(
+ ShaDaReadDef *const sd_reader, const size_t length,
+ msgpack_unpacked *ret_unpacked, char **const ret_buf)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+{
+ const uintmax_t initial_fpos = sd_reader->fpos;
+ char *const buf = xmalloc(length);
+
+ const ShaDaReadResult fl_ret = fread_len(sd_reader, buf, length);
+ if (fl_ret != kSDReadStatusSuccess) {
+ xfree(buf);
+ return fl_ret;
+ }
+ bool did_try_to_free = false;
+shada_parse_msgpack_read_next: {}
+ size_t off = 0;
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ const msgpack_unpack_return result =
+ msgpack_unpack_next(&unpacked, buf, length, &off);
+ ShaDaReadResult ret = kSDReadStatusSuccess;
+ switch (result) {
+ case MSGPACK_UNPACK_SUCCESS: {
+ if (off < length) {
+ goto shada_parse_msgpack_extra_bytes;
+ }
+ break;
+ }
+ case MSGPACK_UNPACK_PARSE_ERROR: {
+ emsgu(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error "
+ "at position %" PRIu64),
+ (uint64_t) initial_fpos);
+ ret = kSDReadStatusNotShaDa;
+ break;
+ }
+ case MSGPACK_UNPACK_NOMEM_ERROR: {
+ if (!did_try_to_free) {
+ did_try_to_free = true;
+ try_to_free_memory();
+ goto shada_parse_msgpack_read_next;
+ }
+ EMSG(_(e_outofmem));
+ ret = kSDReadStatusReadError;
+ break;
+ }
+ case MSGPACK_UNPACK_CONTINUE: {
+ emsgu(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string "
+ "at position %" PRIu64),
+ (uint64_t) initial_fpos);
+ ret = kSDReadStatusNotShaDa;
+ break;
+ }
+ case MSGPACK_UNPACK_EXTRA_BYTES: {
+shada_parse_msgpack_extra_bytes:
+ emsgu(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string "
+ "at position %" PRIu64),
+ (uint64_t) initial_fpos);
+ ret = kSDReadStatusNotShaDa;
+ break;
+ }
+ }
+ if (ret_buf != NULL && ret == kSDReadStatusSuccess) {
+ if (ret_unpacked == NULL) {
+ msgpack_unpacked_destroy(&unpacked);
+ } else {
+ *ret_unpacked = unpacked;
+ }
+ *ret_buf = buf;
+ } else {
+ assert(ret_buf == NULL || ret != kSDReadStatusSuccess);
+ msgpack_unpacked_destroy(&unpacked);
+ xfree(buf);
+ }
+ return ret;
+}
+
+/// Read and merge in ShaDa file, used when writing
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[in] srni_flags Flags determining what to read.
+/// @param[in] max_kbyte Maximum size of one element.
+/// @param[in,out] ret_wms Location where results are saved.
+/// @param[out] packer MessagePack packer for entries which are not
+/// merged.
+static inline ShaDaWriteResult shada_read_when_writing(
+ ShaDaReadDef *const sd_reader, const unsigned srni_flags,
+ const size_t max_kbyte, WriteMergerState *const wms,
+ msgpack_packer *const packer)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ ShaDaWriteResult ret = kSDWriteSuccessfull;
+ ShadaEntry entry;
+ ShaDaReadResult srni_ret;
+ while ((srni_ret = shada_read_next_item(sd_reader, &entry, srni_flags,
+ max_kbyte))
+ != kSDReadStatusFinished) {
+ switch (srni_ret) {
+ case kSDReadStatusSuccess: {
+ break;
+ }
+ case kSDReadStatusFinished: {
+ // Should be handled by the while condition.
+ assert(false);
+ }
+ case kSDReadStatusNotShaDa: {
+ ret = kSDWriteReadNotShada;
+ // fallthrough
+ }
+ case kSDReadStatusReadError: {
+ return ret;
+ }
+ case kSDReadStatusMalformed: {
+ continue;
+ }
+ }
+#define COMPARE_WITH_ENTRY(wms_entry_, entry) \
+ do { \
+ PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \
+ if (wms_entry->data.type != kSDItemMissing) { \
+ if (wms_entry->data.timestamp >= (entry).timestamp) { \
+ shada_free_shada_entry(&(entry)); \
+ break; \
+ } \
+ if (wms_entry->can_free_entry) { \
+ shada_free_shada_entry(&wms_entry->data); \
+ } \
+ } \
+ wms_entry->can_free_entry = true; \
+ wms_entry->data = (entry); \
+ } while (0)
+ switch (entry.type) {
+ case kSDItemMissing: {
+ break;
+ }
+ case kSDItemHeader:
+ case kSDItemBufferList: {
+ assert(false);
+ }
+ case kSDItemUnknown: {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ COMPARE_WITH_ENTRY((entry.data.search_pattern.is_substitute_pattern
+ ? &wms->sub_search_pattern
+ : &wms->search_pattern), entry);
+ break;
+ }
+ case kSDItemSubString: {
+ COMPARE_WITH_ENTRY(&wms->replacement, entry);
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (entry.data.history_item.histtype >= HIST_COUNT) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true,
+ true);
+ break;
+ }
+ case kSDItemRegister: {
+ const int idx = op_reg_index(entry.data.reg.name);
+ if (idx < 0) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ COMPARE_WITH_ENTRY(&wms->registers[idx], entry);
+ break;
+ }
+ case kSDItemVariable: {
+ if (!in_strset(&wms->dumped_variables, entry.data.global_var.name)) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ case kSDItemGlobalMark: {
+ const int idx = mark_global_index(entry.data.filemark.name);
+ if (idx < 0) {
+ if (!shada_pack_entry(packer, entry, 0)) {
+ ret = kSDWriteFailed;
+ }
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry);
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemLocalMark: {
+ if (shada_removable(entry.data.filemark.fname)) {
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ const char *const fname = (const char *) entry.data.filemark.fname;
+ khiter_t k;
+ int kh_ret;
+ k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
+ FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
+ if (kh_ret > 0) {
+ memset(filemarks, 0, sizeof(*filemarks));
+ }
+ if (entry.timestamp > filemarks->greatest_timestamp) {
+ filemarks->greatest_timestamp = entry.timestamp;
+ }
+ if (entry.type == kSDItemLocalMark) {
+ const int idx = mark_local_index(entry.data.filemark.name);
+ if (idx < 0) {
+ filemarks->additional_marks = xrealloc(
+ filemarks->additional_marks,
+ (++filemarks->additional_marks_size
+ * sizeof(filemarks->additional_marks[0])));
+ filemarks->additional_marks[filemarks->additional_marks_size - 1] =
+ entry;
+ } else {
+ PossiblyFreedShadaEntry *const wms_entry = &filemarks->marks[idx];
+ if (wms_entry->data.type != kSDItemMissing) {
+ if (wms_entry->data.timestamp >= entry.timestamp) {
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ if (wms_entry->can_free_entry) {
+ if (kh_key(&wms->file_marks, k)
+ == wms_entry->data.data.filemark.fname) {
+ kh_key(&wms->file_marks, k) = entry.data.filemark.fname;
+ }
+ shada_free_shada_entry(&wms_entry->data);
+ }
+ }
+ wms_entry->can_free_entry = true;
+ wms_entry->data = entry;
+ }
+ } else {
+#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \
+ do { \
+ if (entry.can_free_entry) { \
+ shada_free_shada_entry(&entry.data); \
+ } \
+ } while (0)
+#define SDE_TO_PFSDE(entry) \
+ ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = entry })
+#define AFTERFREE_DUMMY(entry)
+#define DUMMY_IDX_ADJ(i)
+ MERGE_JUMPS(filemarks->changes_size, filemarks->changes,
+ PossiblyFreedShadaEntry, data.timestamp,
+ data.data.filemark.mark, entry, true,
+ FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE,
+ DUMMY_IDX_ADJ, AFTERFREE_DUMMY);
+ }
+ break;
+ }
+ case kSDItemJump: {
+ MERGE_JUMPS(wms->jumps_size, wms->jumps, PossiblyFreedShadaEntry,
+ data.timestamp, data.data.filemark.mark, entry,
+ strcmp(jl_entry.data.data.filemark.fname,
+ entry.data.filemark.fname) == 0,
+ FREE_POSSIBLY_FREED_SHADA_ENTRY, SDE_TO_PFSDE,
+ DUMMY_IDX_ADJ, AFTERFREE_DUMMY);
+#undef FREE_POSSIBLY_FREED_SHADA_ENTRY
+#undef SDE_TO_PFSDE
+#undef DUMMY_IDX_ADJ
+#undef AFTERFREE_DUMMY
+ break;
+ }
+ }
+ }
+#undef COMPARE_WITH_ENTRY
+ return ret;
+}
+
+/// Write ShaDa file
+///
+/// @param[in] sd_writer Structure containing file writer definition.
+/// @param[in] sd_reader Structure containing file reader definition. If it is
+/// not NULL then contents of this file will be merged
+/// with current Neovim runtime.
+static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
+ ShaDaReadDef *const sd_reader)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ ShaDaWriteResult ret = kSDWriteSuccessfull;
+ int max_kbyte_i = get_shada_parameter('s');
+ if (max_kbyte_i < 0) {
+ max_kbyte_i = 10;
+ }
+ if (max_kbyte_i == 0) {
+ return ret;
+ }
+
+ WriteMergerState *const wms = xcalloc(1, sizeof(*wms));
+ bool dump_one_history[HIST_COUNT];
+ const bool dump_global_vars = (find_shada_parameter('!') != NULL);
+ int max_reg_lines = get_shada_parameter('<');
+ if (max_reg_lines < 0) {
+ max_reg_lines = get_shada_parameter('"');
+ }
+ const bool limit_reg_lines = max_reg_lines >= 0;
+ const bool dump_registers = (max_reg_lines != 0);
+ khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ const size_t max_kbyte = (size_t) max_kbyte_i;
+ const size_t num_marked_files = (size_t) get_shada_parameter('\'');
+ const bool dump_global_marks = get_shada_parameter('f') != 0;
+ bool dump_history = false;
+
+ // Initialize history merger
+ for (uint8_t i = 0; i < HIST_COUNT; i++) {
+ long num_saved = get_shada_parameter(hist_type2char(i));
+ if (num_saved == -1) {
+ num_saved = p_hi;
+ }
+ if (num_saved > 0) {
+ dump_history = true;
+ dump_one_history[i] = true;
+ hms_init(&wms->hms[i], i, (size_t) num_saved, sd_reader != NULL, false);
+ } else {
+ dump_one_history[i] = false;
+ }
+ }
+
+ const unsigned srni_flags = (unsigned) (
+ kSDReadUndisableableData
+ | kSDReadUnknown
+ | (dump_history ? kSDReadHistory : 0)
+ | (dump_registers ? kSDReadRegisters : 0)
+ | (dump_global_vars ? kSDReadVariables : 0)
+ | (dump_global_marks ? kSDReadGlobalMarks : 0)
+ | (num_marked_files ? kSDReadLocalMarks | kSDReadChanges : 0));
+
+ msgpack_packer *const packer = msgpack_packer_new(sd_writer,
+ &msgpack_sd_writer_write);
+
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) {
+ int kh_ret;
+ (void) kh_put(bufset, &removable_bufs, (uintptr_t) buf, &kh_ret);
+ }
+ }
+
+ // Write header
+ if (!shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemHeader,
+ .timestamp = os_time(),
+ .data = {
+ .header = {
+ .size = 5,
+ .capacity = 5,
+ .items = ((KeyValuePair[]) {
+ { STATIC_CSTR_AS_STRING("generator"),
+ STRING_OBJ(STATIC_CSTR_AS_STRING("nvim")) },
+ { STATIC_CSTR_AS_STRING("version"),
+ STRING_OBJ(cstr_as_string(longVersion)) },
+ { STATIC_CSTR_AS_STRING("max_kbyte"),
+ INTEGER_OBJ((Integer) max_kbyte) },
+ { STATIC_CSTR_AS_STRING("pid"),
+ INTEGER_OBJ((Integer) os_get_pid()) },
+ { STATIC_CSTR_AS_STRING("encoding"),
+ STRING_OBJ(cstr_as_string((char *) p_enc)) },
+ }),
+ }
+ }
+ }, 0)) {
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+
+ // Write buffer list
+ if (find_shada_parameter('%') != NULL) {
+ size_t buf_count = 0;
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL && !in_bufset(&removable_bufs, buf)) {
+ buf_count++;
+ }
+ }
+
+ ShadaEntry buflist_entry = (ShadaEntry) {
+ .type = kSDItemBufferList,
+ .timestamp = os_time(),
+ .data = {
+ .buffer_list = {
+ .size = buf_count,
+ .buffers = xmalloc(buf_count
+ * sizeof(*buflist_entry.data.buffer_list.buffers)),
+ },
+ },
+ };
+ size_t i = 0;
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) {
+ continue;
+ }
+ buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) {
+ .pos = buf->b_last_cursor.mark,
+ .fname = (char *) buf->b_ffname,
+ .additional_data = buf->additional_data,
+ };
+ i++;
+ }
+ if (!shada_pack_entry(packer, buflist_entry, 0)) {
+ xfree(buflist_entry.data.buffer_list.buffers);
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ xfree(buflist_entry.data.buffer_list.buffers);
+ }
+
+ // Write some of the variables
+ if (dump_global_vars) {
+ const void *var_iter = NULL;
+ const Timestamp cur_timestamp = os_time();
+ do {
+ typval_T vartv;
+ const char *name = NULL;
+ var_iter = var_shada_iter(var_iter, &name, &vartv);
+ if (name == NULL) {
+ break;
+ }
+ typval_T tgttv;
+ if (sd_writer->sd_conv.vc_type != CONV_NONE) {
+ var_item_copy(&sd_writer->sd_conv, &vartv, &tgttv, true, 0);
+ } else {
+ copy_tv(&vartv, &tgttv);
+ }
+ if (!shada_pack_entry(packer, (ShadaEntry) {
+ .type = kSDItemVariable,
+ .timestamp = cur_timestamp,
+ .data = {
+ .global_var = {
+ .name = (char *) name,
+ .value = tgttv,
+ .additional_elements = NULL,
+ }
+ }
+ }, max_kbyte)) {
+ clear_tv(&vartv);
+ clear_tv(&tgttv);
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ clear_tv(&vartv);
+ clear_tv(&tgttv);
+ int kh_ret;
+ (void) kh_put(strset, &wms->dumped_variables, name, &kh_ret);
+ } while (var_iter != NULL);
+ }
+
+ const bool search_highlighted = !(no_hlsearch
+ || find_shada_parameter('h') != NULL);
+ const bool search_last_used = search_was_last_used();
+#define ADD_SEARCH_PAT(func, wms_attr, hlo, pcae, o, is_sub) \
+ do { \
+ SearchPattern pat; \
+ func(&pat); \
+ if (pat.pat != NULL) { \
+ wms->wms_attr = (PossiblyFreedShadaEntry) { \
+ .can_free_entry = false, \
+ .data = { \
+ .type = kSDItemSearchPattern, \
+ .timestamp = pat.timestamp, \
+ .data = { \
+ .search_pattern = { \
+ .magic = pat.magic, \
+ .smartcase = !pat.no_scs, \
+ .has_line_offset = hlo, \
+ .place_cursor_at_end = pcae, \
+ .offset = o, \
+ .is_last_used = (is_sub ^ search_last_used), \
+ .is_substitute_pattern = is_sub, \
+ .highlighted = ((is_sub ^ search_last_used) \
+ && search_highlighted), \
+ .pat = (char *) pat.pat, \
+ .additional_data = pat.additional_data, \
+ } \
+ } \
+ } \
+ }; \
+ } \
+ } while (0)
+
+ // Initialize search pattern
+ ADD_SEARCH_PAT(get_search_pattern, search_pattern, pat.off.line, \
+ pat.off.end, pat.off.off, false);
+
+ // Initialize substitute search pattern
+ ADD_SEARCH_PAT(get_substitute_pattern, sub_search_pattern, false, false, 0,
+ true);
+#undef ADD_SEARCH_PAT
+
+ // Initialize substitute replacement string
+ {
+ SubReplacementString sub;
+ sub_get_replacement(&sub);
+ wms->replacement = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemSubString,
+ .timestamp = sub.timestamp,
+ .data = {
+ .sub_string = {
+ .sub = (char *) sub.sub,
+ .additional_elements = sub.additional_elements,
+ }
+ }
+ }
+ };
+ }
+
+ // Initialize jump list
+ const void *jump_iter = NULL;
+ do {
+ xfmark_T fm;
+ cleanup_jumplist();
+ jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
+ const buf_T *const buf = (fm.fmark.fnum == 0
+ ? NULL
+ : buflist_findnr(fm.fmark.fnum));
+ if (buf != NULL
+ ? in_bufset(&removable_bufs, buf)
+ : fm.fmark.fnum != 0) {
+ continue;
+ }
+ const char *const fname = (char *) (fm.fmark.fnum == 0
+ ? (fm.fname == NULL ? NULL : fm.fname)
+ : buf->b_ffname);
+ if (fname == NULL) {
+ continue;
+ }
+ wms->jumps[wms->jumps_size++] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemJump,
+ .timestamp = fm.fmark.timestamp,
+ .data = {
+ .filemark = {
+ .name = NUL,
+ .mark = fm.fmark.mark,
+ .fname = (char *) fname,
+ .additional_data = fm.fmark.additional_data,
+ }
+ }
+ }
+ };
+ } while (jump_iter != NULL);
+
+ // Initialize global marks
+ if (dump_global_marks) {
+ const void *global_mark_iter = NULL;
+ do {
+ char name = NUL;
+ xfmark_T fm;
+ global_mark_iter = mark_global_iter(global_mark_iter, &name, &fm);
+ if (name == NUL) {
+ break;
+ }
+ const char *fname;
+ if (fm.fmark.fnum == 0) {
+ assert(fm.fname != NULL);
+ if (shada_removable((const char *) fm.fname)) {
+ continue;
+ }
+ fname = (const char *) fm.fname;
+ } else {
+ const buf_T *const buf = buflist_findnr(fm.fmark.fnum);
+ if (buf == NULL || buf->b_ffname == NULL
+ || in_bufset(&removable_bufs, buf)) {
+ continue;
+ }
+ fname = (const char *) buf->b_ffname;
+ }
+ wms->global_marks[mark_global_index(name)] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemGlobalMark,
+ .timestamp = fm.fmark.timestamp,
+ .data = {
+ .filemark = {
+ .mark = fm.fmark.mark,
+ .name = name,
+ .additional_data = fm.fmark.additional_data,
+ .fname = (char *) fname,
+ }
+ }
+ },
+ };
+ } while (global_mark_iter != NULL);
+ }
+
+ // Initialize registers
+ if (dump_registers) {
+ const void *reg_iter = NULL;
+ do {
+ yankreg_T reg;
+ char name = NUL;
+ reg_iter = op_register_iter(reg_iter, &name, &reg);
+ if (name == NUL) {
+ break;
+ }
+ if (limit_reg_lines && reg.y_size > max_reg_lines) {
+ continue;
+ }
+ wms->registers[op_reg_index(name)] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemRegister,
+ .timestamp = reg.timestamp,
+ .data = {
+ .reg = {
+ .contents = (char **) reg.y_array,
+ .contents_size = (size_t) reg.y_size,
+ .type = (uint8_t) reg.y_type,
+ .width = (size_t) (reg.y_type == MBLOCK ? reg.y_width : 0),
+ .additional_data = reg.additional_data,
+ .name = name,
+ }
+ }
+ }
+ };
+ } while (reg_iter != NULL);
+ }
+
+ // Initialize buffers
+ if (num_marked_files > 0) {
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) {
+ continue;
+ }
+ const void *local_marks_iter = NULL;
+ const char *const fname = (const char *) buf->b_ffname;
+ khiter_t k;
+ int kh_ret;
+ k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
+ FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
+ if (kh_ret > 0) {
+ memset(filemarks, 0, sizeof(*filemarks));
+ }
+ do {
+ fmark_T fm;
+ char name = NUL;
+ local_marks_iter = mark_buffer_iter(local_marks_iter, buf, &name, &fm);
+ if (name == NUL) {
+ break;
+ }
+ filemarks->marks[mark_local_index(name)] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemLocalMark,
+ .timestamp = fm.timestamp,
+ .data = {
+ .filemark = {
+ .mark = fm.mark,
+ .name = name,
+ .fname = (char *) fname,
+ .additional_data = fm.additional_data,
+ }
+ }
+ }
+ };
+ if (fm.timestamp > filemarks->greatest_timestamp) {
+ filemarks->greatest_timestamp = fm.timestamp;
+ }
+ } while (local_marks_iter != NULL);
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ const fmark_T fm = buf->b_changelist[i];
+ filemarks->changes[i] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemChange,
+ .timestamp = fm.timestamp,
+ .data = {
+ .filemark = {
+ .mark = fm.mark,
+ .fname = (char *) fname,
+ .additional_data = fm.additional_data,
+ }
+ }
+ }
+ };
+ if (fm.timestamp > filemarks->greatest_timestamp) {
+ filemarks->greatest_timestamp = fm.timestamp;
+ }
+ }
+ filemarks->changes_size = (size_t) buf->b_changelistlen;
+ }
+ }
+
+ if (sd_reader != NULL) {
+ const ShaDaWriteResult srww_ret = shada_read_when_writing(
+ sd_reader, srni_flags, max_kbyte, wms, packer);
+ if (srww_ret != kSDWriteSuccessfull) {
+ ret = srww_ret;
+ }
+ }
+
+ // Write the rest
+#define PACK_WMS_ARRAY(wms_array) \
+ do { \
+ for (size_t i_ = 0; i_ < ARRAY_SIZE(wms_array); i_++) { \
+ if (wms_array[i_].data.type != kSDItemMissing) { \
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv, \
+ wms_array[i_], \
+ max_kbyte)) { \
+ ret = kSDWriteFailed; \
+ goto shada_write_exit; \
+ } \
+ } \
+ } \
+ } while (0)
+ PACK_WMS_ARRAY(wms->global_marks);
+ PACK_WMS_ARRAY(wms->registers);
+ for (size_t i = 0; i < wms->jumps_size; i++) {
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms->jumps[i],
+ max_kbyte)) {
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ }
+#define PACK_WMS_ENTRY(wms_entry) \
+ do { \
+ if (wms_entry.data.type != kSDItemMissing) { \
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv, wms_entry, \
+ max_kbyte)) { \
+ ret = kSDWriteFailed; \
+ goto shada_write_exit; \
+ } \
+ } \
+ } while (0)
+ PACK_WMS_ENTRY(wms->search_pattern);
+ PACK_WMS_ENTRY(wms->sub_search_pattern);
+ PACK_WMS_ENTRY(wms->replacement);
+#undef PACK_WMS_ENTRY
+
+ const size_t file_markss_size = kh_size(&wms->file_marks);
+ FileMarks **const all_file_markss =
+ xmalloc(file_markss_size * sizeof(*all_file_markss));
+ FileMarks **cur_file_marks = all_file_markss;
+ for (khint_t i = kh_begin(&wms->file_marks); i != kh_end(&wms->file_marks);
+ i++) {
+ if (kh_exist(&wms->file_marks, i)) {
+ *cur_file_marks++ = &kh_val(&wms->file_marks, i);
+ }
+ }
+ qsort((void *) all_file_markss, file_markss_size, sizeof(*all_file_markss),
+ &compare_file_marks);
+ const size_t file_markss_to_dump = MIN(num_marked_files, file_markss_size);
+ for (size_t i = 0; i < file_markss_to_dump; i++) {
+ PACK_WMS_ARRAY(all_file_markss[i]->marks);
+ for (size_t j = 0; j < all_file_markss[i]->changes_size; j++) {
+ if (!shada_pack_encoded_entry(packer, &sd_writer->sd_conv,
+ all_file_markss[i]->changes[j],
+ max_kbyte)) {
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ }
+ for (size_t j = 0; j < all_file_markss[i]->additional_marks_size; j++) {
+ if (!shada_pack_entry(packer, all_file_markss[i]->additional_marks[j],
+ 0)) {
+ shada_free_shada_entry(&all_file_markss[i]->additional_marks[j]);
+ ret = kSDWriteFailed;
+ goto shada_write_exit;
+ }
+ shada_free_shada_entry(&all_file_markss[i]->additional_marks[j]);
+ }
+ xfree(all_file_markss[i]->additional_marks);
+ }
+ xfree(all_file_markss);
+#undef PACK_WMS_ARRAY
+
+ if (dump_history) {
+ for (size_t i = 0; i < HIST_COUNT; i++) {
+ if (dump_one_history[i]) {
+ hms_insert_whole_neovim_history(&wms->hms[i]);
+ HMS_ITER(&wms->hms[i], cur_entry) {
+ if (!shada_pack_encoded_entry(
+ packer, &sd_writer->sd_conv, (PossiblyFreedShadaEntry) {
+ .data = cur_entry->data,
+ .can_free_entry = cur_entry->can_free_entry,
+ }, max_kbyte)) {
+ ret = kSDWriteFailed;
+ break;
+ }
+ }
+ hms_dealloc(&wms->hms[i]);
+ if (ret == kSDWriteFailed) {
+ goto shada_write_exit;
+ }
+ }
+ }
+ }
+
+shada_write_exit:
+ kh_dealloc(file_marks, &wms->file_marks);
+ kh_dealloc(bufset, &removable_bufs);
+ msgpack_packer_free(packer);
+ kh_dealloc(strset, &wms->dumped_variables);
+ xfree(wms);
+ return ret;
+}
+
+#undef PACK_STATIC_STR
+
+/// Write ShaDa file to a given location
+///
+/// @param[in] fname File to write to. If it is NULL or empty then default
+/// location is used.
+/// @param[in] nomerge If true then old file is ignored.
+///
+/// @return OK if writing was successfull, FAIL otherwise.
+int shada_write_file(const char *const file, bool nomerge)
+{
+ if (shada_disabled()) {
+ return FAIL;
+ }
+
+ char *const fname = shada_filename(file);
+ char *tempname = NULL;
+ ShaDaWriteDef sd_writer = (ShaDaWriteDef) {
+ .write = &write_file,
+ .close = &close_sd_writer,
+ .error = NULL,
+ };
+ ShaDaReadDef sd_reader;
+
+ intptr_t fd;
+
+ if (!nomerge) {
+ if (open_shada_file_for_reading(fname, &sd_reader) != 0) {
+ nomerge = true;
+ goto shada_write_file_nomerge;
+ }
+ tempname = modname(fname, ".tmp.a", false);
+ if (tempname == NULL) {
+ nomerge = true;
+ goto shada_write_file_nomerge;
+ }
+
+ // Save permissions from the original file, with modifications:
+ int perm = (int) os_getperm(fname);
+ perm = (perm >= 0) ? ((perm & 0777) | 0600) : 0600;
+ // ^3 ^1 ^2 ^2,3
+ // 1: Strip SUID bit if any.
+ // 2: Make sure that user can always read and write the result.
+ // 3: If somebody happened to delete the file after it was opened for
+ // reading use u=rw permissions.
+shada_write_file_open:
+ fd = (intptr_t) open_file(tempname, O_CREAT|O_WRONLY|O_NOFOLLOW|O_EXCL,
+ perm);
+ if (fd < 0) {
+ if (-fd == EEXIST
+#ifdef ELOOP
+ || -fd == ELOOP
+#endif
+ ) {
+ // File already exists, try another name
+ char *const wp = tempname + strlen(tempname) - 1;
+ if (*wp == 'z') {
+ // Tried names from .tmp.a to .tmp.z, all failed. Something must be
+ // wrong then.
+ EMSG2(_("E138: All %s.tmp.X files exist, cannot write ShaDa file!"),
+ fname);
+ xfree(fname);
+ xfree(tempname);
+ return FAIL;
+ } else {
+ (*wp)++;
+ goto shada_write_file_open;
+ }
+ }
+ }
+ }
+ if (nomerge) {
+shada_write_file_nomerge: {}
+ char *const tail = path_tail_with_sep(fname);
+ if (tail != fname) {
+ const char tail_save = *tail;
+ *tail = NUL;
+ if (!os_isdir(fname)) {
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) {
+ EMSG3(_(SERR "Failed to create directory %s "
+ "for writing ShaDa file: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(fname);
+ xfree(failed_dir);
+ return FAIL;
+ }
+ }
+ *tail = tail_save;
+ }
+ fd = (intptr_t) open_file(fname, O_CREAT|O_WRONLY|O_TRUNC,
+ 0600);
+ }
+
+ if (p_verbose > 0) {
+ verbose_enter();
+ smsg(_("Writing ShaDa file \"%s\""), fname);
+ verbose_leave();
+ }
+
+ if (fd < 0) {
+ xfree(fname);
+ xfree(tempname);
+ return FAIL;
+ }
+
+ sd_writer.cookie = (void *) fd;
+
+ convert_setup(&sd_writer.sd_conv, p_enc, "utf-8");
+
+ const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (nomerge
+ ? NULL
+ : &sd_reader));
+#ifndef UNIX
+ sd_writer.close(&sd_writer);
+#endif
+ if (!nomerge) {
+ sd_reader.close(&sd_reader);
+ bool did_remove = false;
+ if (sw_ret == kSDWriteSuccessfull) {
+#ifdef UNIX
+ bool closed = false;
+ // For Unix we check the owner of the file. It's not very nice to
+ // overwrite a user’s viminfo file after a "su root", with a
+ // viminfo file that the user can't read.
+ FileInfo old_info;
+ if (os_fileinfo((char *)fname, &old_info)) {
+ if (getuid() == ROOT_UID) {
+ if (old_info.stat.st_uid != ROOT_UID
+ || old_info.stat.st_gid != getgid()) {
+ const uv_uid_t old_uid = (uv_uid_t) old_info.stat.st_uid;
+ const uv_gid_t old_gid = (uv_gid_t) old_info.stat.st_gid;
+ const int fchown_ret = os_fchown((int) fd, old_uid, old_gid);
+ sd_writer.close(&sd_writer);
+ if (fchown_ret != 0) {
+ EMSG3(_(RNERR "Failed setting uid and gid for file %s: %s"),
+ tempname, os_strerror(fchown_ret));
+ goto shada_write_file_did_not_remove;
+ }
+ closed = true;
+ }
+ } else if (!(old_info.stat.st_uid == getuid()
+ ? (old_info.stat.st_mode & 0200)
+ : (old_info.stat.st_gid == getgid()
+ ? (old_info.stat.st_mode & 0020)
+ : (old_info.stat.st_mode & 0002)))) {
+ EMSG2(_("E137: ShaDa file is not writable: %s"), fname);
+ sd_writer.close(&sd_writer);
+ goto shada_write_file_did_not_remove;
+ }
+ }
+ if (!closed) {
+ sd_writer.close(&sd_writer);
+ }
+#endif
+ if (vim_rename(tempname, fname) == -1) {
+ EMSG3(_(RNERR "Can't rename ShaDa file from %s to %s!"),
+ tempname, fname);
+ } else {
+ did_remove = true;
+ os_remove(tempname);
+ }
+ } else {
+ if (sw_ret == kSDWriteReadNotShada) {
+ EMSG3(_(RNERR "Did not rename %s because %s "
+ "does not looks like a ShaDa file"), tempname, fname);
+ } else {
+ EMSG3(_(RNERR "Did not rename %s to %s because there were errors "
+ "during writing it"), tempname, fname);
+ }
+ }
+ if (!did_remove) {
+#ifdef UNIX
+shada_write_file_did_not_remove:
+#endif
+ EMSG3(_(RNERR "Do not forget to remove %s or rename it manually to %s."),
+ tempname, fname);
+ }
+ xfree(tempname);
+ }
+
+ xfree(fname);
+ return OK;
+}
+
+/// Read marks information from ShaDa file
+///
+/// @return OK in case of success, FAIL otherwise.
+int shada_read_marks(void)
+{
+ return shada_read_file(NULL, kShaDaWantMarks);
+}
+
+/// Read all information from ShaDa file
+///
+/// @param[in] fname File to write to. If it is NULL or empty then default
+/// @param[in] forceit If true, use forced reading (prioritize file contents
+/// over current Neovim state).
+/// @param[in] missing_ok If true, do not error out when file is missing.
+///
+/// @return OK in case of success, FAIL otherwise.
+int shada_read_everything(const char *const fname, const bool forceit,
+ const bool missing_ok)
+{
+ return shada_read_file(fname,
+ kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles
+ |(forceit?kShaDaForceit:0)
+ |(missing_ok?0:kShaDaMissingError));
+}
+
+static void shada_free_shada_entry(ShadaEntry *const entry)
+{
+ if (entry == NULL) {
+ return;
+ }
+ switch (entry->type) {
+ case kSDItemMissing: {
+ break;
+ }
+ case kSDItemUnknown: {
+ xfree(entry->data.unknown_item.contents);
+ break;
+ }
+ case kSDItemHeader: {
+ api_free_dictionary(entry->data.header);
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemJump:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark: {
+ dict_unref(entry->data.filemark.additional_data);
+ xfree(entry->data.filemark.fname);
+ break;
+ }
+ case kSDItemSearchPattern: {
+ dict_unref(entry->data.search_pattern.additional_data);
+ xfree(entry->data.search_pattern.pat);
+ break;
+ }
+ case kSDItemRegister: {
+ dict_unref(entry->data.reg.additional_data);
+ for (size_t i = 0; i < entry->data.reg.contents_size; i++) {
+ xfree(entry->data.reg.contents[i]);
+ }
+ xfree(entry->data.reg.contents);
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ list_unref(entry->data.history_item.additional_elements);
+ xfree(entry->data.history_item.string);
+ break;
+ }
+ case kSDItemVariable: {
+ list_unref(entry->data.global_var.additional_elements);
+ xfree(entry->data.global_var.name);
+ clear_tv(&entry->data.global_var.value);
+ break;
+ }
+ case kSDItemSubString: {
+ list_unref(entry->data.sub_string.additional_elements);
+ xfree(entry->data.sub_string.sub);
+ break;
+ }
+ case kSDItemBufferList: {
+ for (size_t i = 0; i < entry->data.buffer_list.size; i++) {
+ xfree(entry->data.buffer_list.buffers[i].fname);
+ dict_unref(entry->data.buffer_list.buffers[i].additional_data);
+ }
+ xfree(entry->data.buffer_list.buffers);
+ break;
+ }
+ }
+}
+
+#ifndef HAVE_BE64TOH
+static inline uint64_t be64toh(uint64_t big_endian_64_bits)
+{
+#ifdef ORDER_BIG_ENDIAN
+ return big_endian_64_bits;
+#else
+ // It may appear that when !defined(ORDER_BIG_ENDIAN) actual order is big
+ // endian. This variant is suboptimal, but it works regardless of actual
+ // order.
+ uint8_t *buf = (uint8_t *) &big_endian_64_bits;
+ uint64_t ret = 0;
+ for (size_t i = 8; i; i--) {
+ ret |= ((uint64_t) buf[i - 1]) << ((8 - i) * 8);
+ }
+ return ret;
+#endif
+}
+#endif
+
+/// Read given number of bytes into given buffer, display error if needed
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[out] buffer Where to save the results.
+/// @param[in] length How many bytes should be read.
+///
+/// @return kSDReadStatusSuccess if everything was OK, kSDReadStatusNotShaDa if
+/// there were not enough bytes to read or kSDReadStatusReadError if
+/// there was some error while reading.
+static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader,
+ char *const buffer,
+ const size_t length)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const ptrdiff_t read_bytes = sd_reader->read(sd_reader, buffer, length);
+ (void) read_bytes;
+
+ if (sd_reader->error != NULL) {
+ emsg2(_(SERR "System error while reading ShaDa file: %s"),
+ sd_reader->error);
+ return kSDReadStatusReadError;
+ } else if (sd_reader->eof) {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "last entry specified that it occupies %" PRIu64 " bytes, "
+ "but file ended earlier"),
+ (uint64_t) length);
+ return kSDReadStatusNotShaDa;
+ }
+ assert(read_bytes >= 0 && (size_t) read_bytes == length);
+ return kSDReadStatusSuccess;
+}
+
+/// Read next unsigned integer from file
+///
+/// Errors out if the result is not an unsigned integer.
+///
+/// Unlike msgpack own function this one works with `FILE *` and reads *exactly*
+/// as much bytes as needed, making it possible to avoid both maintaining own
+/// buffer and calling `fseek`.
+///
+/// One byte from file stream is always consumed, even if it is not correct.
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[out] result Location where result is saved.
+///
+/// @return kSDReadStatusSuccess if reading was successfull,
+/// kSDReadStatusNotShaDa if there were not enough bytes to read or
+/// kSDReadStatusReadError if reading failed for whatever reason.
+static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader,
+ const int first_char,
+ uint64_t *const result)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const uintmax_t fpos = sd_reader->fpos - 1;
+
+ if (first_char == EOF) {
+ if (sd_reader->error) {
+ emsg2(_(SERR "System error while reading integer from ShaDa file: %s"),
+ sd_reader->error);
+ return kSDReadStatusReadError;
+ } else if (sd_reader->eof) {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "expected positive integer at position %" PRIu64
+ ", but got nothing"),
+ (uint64_t) fpos);
+ return kSDReadStatusNotShaDa;
+ }
+ }
+
+ if (~first_char & 0x80) {
+ // Positive fixnum
+ *result = (uint64_t) ((uint8_t) first_char);
+ } else {
+ size_t length = 0;
+ switch (first_char) {
+ case 0xCC: { // uint8
+ length = 1;
+ break;
+ }
+ case 0xCD: { // uint16
+ length = 2;
+ break;
+ }
+ case 0xCE: { // uint32
+ length = 4;
+ break;
+ }
+ case 0xCF: { // uint64
+ length = 8;
+ break;
+ }
+ default: {
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "expected positive integer at position %" PRIu64),
+ (uint64_t) fpos);
+ return kSDReadStatusNotShaDa;
+ }
+ }
+ uint64_t buf = 0;
+ char *buf_u8 = (char *) &buf;
+ ShaDaReadResult fl_ret;
+ if ((fl_ret = fread_len(sd_reader, &(buf_u8[sizeof(buf)-length]), length))
+ != kSDReadStatusSuccess) {
+ return fl_ret;
+ }
+ *result = be64toh(buf);
+ }
+ return kSDReadStatusSuccess;
+}
+
+/// Convert or copy and return a string
+///
+/// @param[in] sd_conv Conversion definition.
+/// @param[in] str String to convert.
+/// @param[in] len String length.
+///
+/// @return [allocated] converted string or copy of the original string.
+static inline char *get_converted_string(const vimconv_T *const sd_conv,
+ const char *const str,
+ const size_t len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (!has_non_ascii_len(str, len)) {
+ return xmemdupz(str, len);
+ }
+ size_t new_len = len;
+ char *const new_str = string_convert(sd_conv, str, &new_len);
+ if (new_str == NULL) {
+ return xmemdupz(str, len);
+ }
+ return new_str;
+}
+
+#define READERR(entry_name, error_desc) \
+ RERR "Error while reading ShaDa file: " \
+ entry_name " entry at position %" PRIu64 " " \
+ error_desc
+#define CHECK_KEY(key, expected) ( \
+ key.via.str.size == sizeof(expected) - 1 \
+ && STRNCMP(key.via.str.ptr, expected, sizeof(expected) - 1) == 0)
+#define CLEAR_GA_AND_ERROR_OUT(ga) \
+ do { \
+ ga_clear(&ga); \
+ goto shada_read_next_item_error; \
+ } while (0)
+#define ID(s) s
+#define BINDUP(b) xmemdupz(b.ptr, b.size)
+#define TOINT(s) ((int) (s))
+#define TOLONG(s) ((long) (s))
+#define TOCHAR(s) ((char) (s))
+#define TOU8(s) ((uint8_t) (s))
+#define TOSIZE(s) ((size_t) (s))
+#define CHECKED_ENTRY(condition, error_desc, entry_name, obj, tgt, attr, \
+ proc) \
+ do { \
+ if (!(condition)) { \
+ emsgu(_(READERR(entry_name, error_desc)), initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ } \
+ tgt = proc(obj.via.attr); \
+ } while (0)
+#define CHECK_KEY_IS_STR(entry_name) \
+ do { \
+ if (unpacked.data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \
+ emsgu(_(READERR(entry_name, "has key which is not a string")), \
+ initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ } else if (unpacked.data.via.map.ptr[i].key.via.str.size == 0) { \
+ emsgu(_(READERR(entry_name, "has empty key")), initial_fpos); \
+ CLEAR_GA_AND_ERROR_OUT(ad_ga); \
+ } \
+ } while (0)
+#define CHECKED_KEY(entry_name, name, error_desc, tgt, condition, attr, proc) \
+ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, name)) { \
+ CHECKED_ENTRY( \
+ condition, "has " name " key value " error_desc, \
+ entry_name, unpacked.data.via.map.ptr[i].val, \
+ tgt, attr, proc); \
+ }
+#define TYPED_KEY(entry_name, name, type_name, tgt, objtype, attr, proc) \
+ CHECKED_KEY( \
+ entry_name, name, "which is not " type_name, tgt, \
+ unpacked.data.via.map.ptr[i].val.type == MSGPACK_OBJECT_##objtype, \
+ attr, proc)
+#define BOOLEAN_KEY(entry_name, name, tgt) \
+ TYPED_KEY(entry_name, name, "a boolean", tgt, BOOLEAN, boolean, ID)
+#define STRING_KEY(entry_name, name, tgt) \
+ TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BINDUP)
+#define CONVERTED_STRING_KEY(entry_name, name, tgt) \
+ TYPED_KEY(entry_name, name, "a binary", tgt, BIN, bin, BIN_CONVERTED)
+#define INT_KEY(entry_name, name, tgt, proc) \
+ CHECKED_KEY( \
+ entry_name, name, "which is not an integer", tgt, \
+ (unpacked.data.via.map.ptr[i].val.type \
+ == MSGPACK_OBJECT_POSITIVE_INTEGER \
+ || unpacked.data.via.map.ptr[i].val.type \
+ == MSGPACK_OBJECT_NEGATIVE_INTEGER), \
+ i64, proc)
+#define INTEGER_KEY(entry_name, name, tgt) \
+ INT_KEY(entry_name, name, tgt, TOINT)
+#define LONG_KEY(entry_name, name, tgt) \
+ INT_KEY(entry_name, name, tgt, TOLONG)
+#define ADDITIONAL_KEY \
+ { \
+ ga_grow(&ad_ga, 1); \
+ memcpy(((char *)ad_ga.ga_data) + ((size_t) ad_ga.ga_len \
+ * sizeof(*unpacked.data.via.map.ptr)), \
+ unpacked.data.via.map.ptr + i, \
+ sizeof(*unpacked.data.via.map.ptr)); \
+ ad_ga.ga_len++; \
+ }
+#define CONVERTED(str, len) ( \
+ sd_reader->sd_conv.vc_type != CONV_NONE \
+ ? get_converted_string(&sd_reader->sd_conv, (str), (len)) \
+ : xmemdupz((str), (len)))
+#define BIN_CONVERTED(b) CONVERTED(b.ptr, b.size)
+#define SET_ADDITIONAL_DATA(tgt, name) \
+ do { \
+ if (ad_ga.ga_len) { \
+ msgpack_object obj = { \
+ .type = MSGPACK_OBJECT_MAP, \
+ .via = { \
+ .map = { \
+ .size = (uint32_t) ad_ga.ga_len, \
+ .ptr = ad_ga.ga_data, \
+ } \
+ } \
+ }; \
+ typval_T adtv; \
+ if (msgpack_to_vim(obj, &adtv) == FAIL \
+ || adtv.v_type != VAR_DICT) { \
+ emsgu(_(READERR(name, \
+ "cannot be converted to a VimL dictionary")), \
+ initial_fpos); \
+ ga_clear(&ad_ga); \
+ clear_tv(&adtv); \
+ goto shada_read_next_item_error; \
+ } \
+ tgt = adtv.vval.v_dict; \
+ } \
+ ga_clear(&ad_ga); \
+ } while (0)
+#define SET_ADDITIONAL_ELEMENTS(src, src_maxsize, tgt, name) \
+ do { \
+ if ((src).size > (size_t) (src_maxsize)) { \
+ msgpack_object obj = { \
+ .type = MSGPACK_OBJECT_ARRAY, \
+ .via = { \
+ .array = { \
+ .size = ((src).size - (uint32_t) (src_maxsize)), \
+ .ptr = (src).ptr + (src_maxsize), \
+ } \
+ } \
+ }; \
+ typval_T aetv; \
+ if (msgpack_to_vim(obj, &aetv) == FAIL) { \
+ emsgu(_(READERR(name, "cannot be converted to a VimL list")), \
+ initial_fpos); \
+ clear_tv(&aetv); \
+ goto shada_read_next_item_error; \
+ } \
+ assert(aetv.v_type == VAR_LIST); \
+ (tgt) = aetv.vval.v_list; \
+ } \
+ } while (0)
+
+/// Iterate over shada file contents
+///
+/// @param[in] sd_reader Structure containing file reader definition.
+/// @param[out] entry Address where next entry contents will be saved.
+/// @param[in] flags Flags, determining whether and which items should be
+/// skipped (see SRNIFlags enum).
+/// @param[in] max_kbyte If non-zero, skip reading entries which have length
+/// greater then given.
+///
+/// @return Any value from ShaDaReadResult enum.
+static ShaDaReadResult shada_read_next_item(ShaDaReadDef *const sd_reader,
+ ShadaEntry *const entry,
+ const unsigned flags,
+ const size_t max_kbyte)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ ShaDaReadResult ret = kSDReadStatusMalformed;
+shada_read_next_item_start:
+ // Set entry type to kSDItemMissing and also make sure that all pointers in
+ // data union are NULL so they are safe to xfree(). This is needed in case
+ // somebody calls goto shada_read_next_item_error before anything is set in
+ // the switch.
+ memset(entry, 0, sizeof(*entry));
+ if (sd_reader->eof) {
+ return kSDReadStatusFinished;
+ }
+
+ // First: manually unpack type, timestamp and length.
+ // This is needed to avoid both seeking and having to maintain a buffer.
+ uint64_t type_u64 = (uint64_t) kSDItemMissing;
+ uint64_t timestamp_u64;
+ uint64_t length_u64;
+
+ const uint64_t initial_fpos = (uint64_t) sd_reader->fpos;
+ const int first_char = read_char(sd_reader);
+ if (first_char == EOF && sd_reader->eof) {
+ return kSDReadStatusFinished;
+ }
+
+ ShaDaReadResult mru_ret;
+ if (((mru_ret = msgpack_read_uint64(sd_reader, first_char, &type_u64))
+ != kSDReadStatusSuccess)
+ || ((mru_ret = msgpack_read_uint64(sd_reader, read_char(sd_reader),
+ &timestamp_u64))
+ != kSDReadStatusSuccess)
+ || ((mru_ret = msgpack_read_uint64(sd_reader, read_char(sd_reader),
+ &length_u64))
+ != kSDReadStatusSuccess)) {
+ return mru_ret;
+ }
+
+ const size_t length = (size_t) length_u64;
+ entry->timestamp = (Timestamp) timestamp_u64;
+
+ if (type_u64 == 0) {
+ // kSDItemUnknown cannot possibly pass that far because it is -1 and that
+ // will fail in msgpack_read_uint64. But kSDItemMissing may and it will
+ // otherwise be skipped because (1 << 0) will never appear in flags.
+ emsgu(_(RCERR "Error while reading ShaDa file: "
+ "there is an item at position %" PRIu64 " "
+ "that must not be there: Missing items are "
+ "for internal uses only"),
+ initial_fpos);
+ return kSDReadStatusNotShaDa;
+ }
+
+ if ((type_u64 > SHADA_LAST_ENTRY
+ ? !(flags & kSDReadUnknown)
+ : !((unsigned) (1 << type_u64) & flags))
+ || (max_kbyte && length > max_kbyte * 1024)) {
+ // First entry is unknown or equal to "\n" (10)? Most likely this means that
+ // current file is not a ShaDa file because first item should normally be
+ // a header (excluding tests where first item is tested item). Check this by
+ // parsing entry contents: in non-ShaDa files this will most likely result
+ // in incomplete MessagePack string.
+ if (initial_fpos == 0
+ && (type_u64 == '\n' || type_u64 > SHADA_LAST_ENTRY)) {
+ const ShaDaReadResult spm_ret = shada_parse_msgpack(sd_reader, length,
+ NULL, NULL);
+ if (spm_ret != kSDReadStatusSuccess) {
+ return spm_ret;
+ }
+ } else {
+ const ShaDaReadResult srs_ret = sd_reader_skip(sd_reader, length);
+ if (srs_ret != kSDReadStatusSuccess) {
+ return srs_ret;
+ }
+ }
+ goto shada_read_next_item_start;
+ }
+
+ if (type_u64 > SHADA_LAST_ENTRY) {
+ entry->type = kSDItemUnknown;
+ entry->data.unknown_item.size = length;
+ entry->data.unknown_item.type = type_u64;
+ if (initial_fpos == 0) {
+ const ShaDaReadResult spm_ret = shada_parse_msgpack(
+ sd_reader, length, NULL, &entry->data.unknown_item.contents);
+ if (spm_ret != kSDReadStatusSuccess) {
+ entry->type = kSDItemMissing;
+ }
+ return spm_ret;
+ } else {
+ entry->data.unknown_item.contents = xmalloc(length);
+ const ShaDaReadResult fl_ret = fread_len(
+ sd_reader, entry->data.unknown_item.contents, length);
+ if (fl_ret != kSDReadStatusSuccess) {
+ shada_free_shada_entry(entry);
+ entry->type = kSDItemMissing;
+ }
+ return fl_ret;
+ }
+ }
+
+ msgpack_unpacked unpacked;
+ char *buf = NULL;
+
+ const ShaDaReadResult spm_ret = shada_parse_msgpack(sd_reader, length,
+ &unpacked, &buf);
+ if (spm_ret != kSDReadStatusSuccess) {
+ ret = spm_ret;
+ goto shada_read_next_item_error;
+ }
+ ret = kSDReadStatusMalformed;
+ entry->data = sd_default_values[type_u64].data;
+ switch ((ShadaEntryType) type_u64) {
+ case kSDItemHeader: {
+ if (!msgpack_rpc_to_dictionary(&(unpacked.data), &(entry->data.header))) {
+ emsgu(_(READERR("header", "is not a dictionary")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ break;
+ }
+ case kSDItemSearchPattern: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(READERR("search pattern", "is not a dictionary")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("search pattern");
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_MAGIC,
+ entry->data.search_pattern.magic)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_SMARTCASE,
+ entry->data.search_pattern.smartcase)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_HAS_LINE_OFFSET,
+ entry->data.search_pattern.has_line_offset)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_PLACE_CURSOR_AT_END,
+ entry->data.search_pattern.place_cursor_at_end)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_LAST_USED,
+ entry->data.search_pattern.is_last_used)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_IS_SUBSTITUTE_PATTERN,
+ entry->data.search_pattern.is_substitute_pattern)
+ else
+ BOOLEAN_KEY("search pattern", SEARCH_KEY_HIGHLIGHTED,
+ entry->data.search_pattern.highlighted)
+ else
+ INTEGER_KEY("search pattern", SEARCH_KEY_OFFSET,
+ entry->data.search_pattern.offset)
+ else
+ CONVERTED_STRING_KEY("search pattern", SEARCH_KEY_PAT,
+ entry->data.search_pattern.pat)
+ else
+ ADDITIONAL_KEY
+ }
+ if (entry->data.search_pattern.pat == NULL) {
+ emsgu(_(READERR("search pattern", "has no pattern")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(entry->data.search_pattern.additional_data,
+ "search pattern");
+ break;
+ }
+ case kSDItemChange:
+ case kSDItemJump:
+ case kSDItemGlobalMark:
+ case kSDItemLocalMark: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(READERR("mark", "is not a dictionary")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("mark");
+ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, KEY_NAME_CHAR)) {
+ if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) {
+ emsgu(_(READERR("mark", "has n key which is only valid for "
+ "local and global mark entries")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ CHECKED_ENTRY(
+ (unpacked.data.via.map.ptr[i].val.type
+ == MSGPACK_OBJECT_POSITIVE_INTEGER),
+ "has n key value which is not an unsigned integer",
+ "mark", unpacked.data.via.map.ptr[i].val,
+ entry->data.filemark.name, u64, TOCHAR);
+ } else {
+ LONG_KEY("mark", KEY_LNUM, entry->data.filemark.mark.lnum)
+ else
+ INTEGER_KEY("mark", KEY_COL, entry->data.filemark.mark.col)
+ else
+ STRING_KEY("mark", KEY_FILE, entry->data.filemark.fname)
+ else
+ ADDITIONAL_KEY
+ }
+ }
+ if (entry->data.filemark.fname == NULL) {
+ emsgu(_(READERR("mark", "is missing file name")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.filemark.mark.lnum <= 0) {
+ emsgu(_(READERR("mark", "has invalid line number")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.filemark.mark.col < 0) {
+ emsgu(_(READERR("mark", "has invalid column number")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(entry->data.filemark.additional_data, "mark");
+ break;
+ }
+ case kSDItemRegister: {
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(READERR("register", "is not a dictionary")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("register");
+ TYPED_KEY("register", REG_KEY_TYPE, "an unsigned integer",
+ entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8)
+ else
+ TYPED_KEY("register", KEY_NAME_CHAR, "an unsigned integer",
+ entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR)
+ else
+ TYPED_KEY("register", REG_KEY_WIDTH, "an unsigned integer",
+ entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE)
+ else
+ if (CHECK_KEY(unpacked.data.via.map.ptr[i].key,
+ REG_KEY_CONTENTS)) {
+ if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR(
+ "register",
+ "has " REG_KEY_CONTENTS " key with non-array value")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) {
+ emsgu(_(READERR("register",
+ "has " REG_KEY_CONTENTS " key with empty array")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ const msgpack_object_array arr =
+ unpacked.data.via.map.ptr[i].val.via.array;
+ for (size_t i = 0; i < arr.size; i++) {
+ if (arr.ptr[i].type != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("register", "has " REG_KEY_CONTENTS " array "
+ "with non-binary value")), initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ }
+ entry->data.reg.contents_size = arr.size;
+ entry->data.reg.contents = xmalloc(arr.size * sizeof(char *));
+ for (size_t i = 0; i < arr.size; i++) {
+ entry->data.reg.contents[i] = BIN_CONVERTED(arr.ptr[i].via.bin);
+ }
+ } else {
+ ADDITIONAL_KEY
+ }
+ }
+ if (entry->data.reg.contents == NULL) {
+ emsgu(_(READERR("register", "has missing " REG_KEY_CONTENTS " array")),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(entry->data.reg.additional_data, "register");
+ break;
+ }
+ case kSDItemHistoryEntry: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("history", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size < 2) {
+ emsgu(_(READERR("history", "does not have enough elements")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type
+ != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ emsgu(_(READERR("history", "has wrong history type type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[1].type
+ != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("history", "has wrong history string type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (memchr(unpacked.data.via.array.ptr[1].via.bin.ptr, 0,
+ unpacked.data.via.array.ptr[1].via.bin.size) != NULL) {
+ emsgu(_(READERR("history", "contains string with zero byte inside")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.history_item.histtype =
+ (uint8_t) unpacked.data.via.array.ptr[0].via.u64;
+ const bool is_hist_search =
+ entry->data.history_item.histtype == HIST_SEARCH;
+ if (is_hist_search) {
+ if (unpacked.data.via.array.size < 3) {
+ emsgu(_(READERR("search history",
+ "does not have separator character")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[2].type
+ != MSGPACK_OBJECT_POSITIVE_INTEGER) {
+ emsgu(_(READERR("search history",
+ "has wrong history separator type")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.history_item.sep =
+ (char) unpacked.data.via.array.ptr[2].via.u64;
+ }
+ size_t strsize;
+ if (sd_reader->sd_conv.vc_type == CONV_NONE
+ || !has_non_ascii_len(unpacked.data.via.array.ptr[1].via.bin.ptr,
+ unpacked.data.via.array.ptr[1].via.bin.size)) {
+shada_read_next_item_hist_no_conv:
+ strsize = (
+ unpacked.data.via.array.ptr[1].via.bin.size
+ + 1 // Zero byte
+ + 1); // Separator character
+ entry->data.history_item.string = xmalloc(strsize);
+ memcpy(entry->data.history_item.string,
+ unpacked.data.via.array.ptr[1].via.bin.ptr,
+ unpacked.data.via.array.ptr[1].via.bin.size);
+ } else {
+ size_t len = unpacked.data.via.array.ptr[1].via.bin.size;
+ char *const converted = string_convert(
+ &sd_reader->sd_conv, unpacked.data.via.array.ptr[1].via.bin.ptr,
+ &len);
+ if (converted != NULL) {
+ strsize = len + 2;
+ entry->data.history_item.string = xrealloc(converted, strsize);
+ } else {
+ goto shada_read_next_item_hist_no_conv;
+ }
+ }
+ entry->data.history_item.string[strsize - 2] = 0;
+ entry->data.history_item.string[strsize - 1] =
+ entry->data.history_item.sep;
+ SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, (2 + is_hist_search),
+ entry->data.history_item.additional_elements,
+ "history");
+ break;
+ }
+ case kSDItemVariable: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("variable", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size < 2) {
+ emsgu(_(READERR("variable", "does not have enough elements")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("variable", "has wrong variable name type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_NIL
+ || unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_EXT) {
+ emsgu(_(READERR("variable", "has wrong variable value type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.global_var.name =
+ xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr,
+ unpacked.data.via.array.ptr[0].via.bin.size);
+ if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
+ &(entry->data.global_var.value)) == FAIL) {
+ emsgu(_(READERR("variable", "has value that cannot "
+ "be converted to the VimL value")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (sd_reader->sd_conv.vc_type != CONV_NONE) {
+ typval_T tgttv;
+ var_item_copy(&sd_reader->sd_conv,
+ &entry->data.global_var.value,
+ &tgttv,
+ true,
+ 0);
+ clear_tv(&entry->data.global_var.value);
+ entry->data.global_var.value = tgttv;
+ }
+ SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
+ entry->data.global_var.additional_elements,
+ "variable");
+ break;
+ }
+ case kSDItemSubString: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("sub string", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size < 1) {
+ emsgu(_(READERR("sub string", "does not have enough elements")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
+ emsgu(_(READERR("sub string", "has wrong sub string type")),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.sub_string.sub =
+ BIN_CONVERTED(unpacked.data.via.array.ptr[0].via.bin);
+ SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 1,
+ entry->data.sub_string.additional_elements,
+ "sub string");
+ break;
+ }
+ case kSDItemBufferList: {
+ if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ emsgu(_(READERR("buffer list", "is not an array")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (unpacked.data.via.array.size == 0) {
+ break;
+ }
+ entry->data.buffer_list.buffers =
+ xcalloc(unpacked.data.via.array.size,
+ sizeof(*entry->data.buffer_list.buffers));
+ for (size_t i = 0; i < unpacked.data.via.array.size; i++) {
+ entry->data.buffer_list.size++;
+ msgpack_unpacked unpacked_2 = (msgpack_unpacked) {
+ .data = unpacked.data.via.array.ptr[i],
+ };
+ {
+ msgpack_unpacked unpacked = unpacked_2;
+ if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry that is not a dictionary"),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ entry->data.buffer_list.buffers[i].pos = default_pos;
+ garray_T ad_ga;
+ ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
+ {
+ const size_t j = i;
+ {
+ for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
+ CHECK_KEY_IS_STR("buffer list entry");
+ LONG_KEY("buffer list entry", KEY_LNUM,
+ entry->data.buffer_list.buffers[j].pos.lnum)
+ else
+ INTEGER_KEY("buffer list entry", KEY_COL,
+ entry->data.buffer_list.buffers[j].pos.col)
+ else
+ STRING_KEY("buffer list entry", KEY_FILE,
+ entry->data.buffer_list.buffers[j].fname)
+ else
+ ADDITIONAL_KEY
+ }
+ }
+ }
+ if (entry->data.buffer_list.buffers[i].pos.lnum <= 0) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry with invalid line number"),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.buffer_list.buffers[i].pos.col < 0) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry with invalid column number"),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ if (entry->data.buffer_list.buffers[i].fname == NULL) {
+ emsgu(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry that does not have a file name"),
+ initial_fpos);
+ CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ }
+ SET_ADDITIONAL_DATA(
+ entry->data.buffer_list.buffers[i].additional_data,
+ "buffer list entry");
+ }
+ }
+ break;
+ }
+ case kSDItemMissing:
+ case kSDItemUnknown: {
+ assert(false);
+ }
+ }
+ entry->type = (ShadaEntryType) type_u64;
+ ret = kSDReadStatusSuccess;
+shada_read_next_item_end:
+ if (buf != NULL) {
+ msgpack_unpacked_destroy(&unpacked);
+ xfree(buf);
+ }
+ return ret;
+shada_read_next_item_error:
+ entry->type = (ShadaEntryType) type_u64;
+ shada_free_shada_entry(entry);
+ entry->type = kSDItemMissing;
+ goto shada_read_next_item_end;
+}
+#undef BIN_CONVERTED
+#undef CONVERTED
+#undef CHECK_KEY
+#undef BOOLEAN_KEY
+#undef CONVERTED_STRING_KEY
+#undef STRING_KEY
+#undef ADDITIONAL_KEY
+#undef ID
+#undef BINDUP
+#undef TOCHAR
+#undef TOINT
+#undef TOLONG
+#undef TYPED_KEY
+#undef INT_KEY
+#undef INTEGER_KEY
+#undef LONG_KEY
+#undef TOU8
+#undef TOSIZE
+#undef SET_ADDITIONAL_DATA
+#undef SET_ADDITIONAL_ELEMENTS
+#undef CLEAR_GA_AND_ERROR_OUT
+
+/// Check whether "name" is on removable media (according to 'shada')
+///
+/// @param[in] name Checked name.
+///
+/// @return True if it is, false otherwise.
+static bool shada_removable(const char *name)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char *p;
+ char part[MAXPATHL + 1];
+ bool retval = false;
+
+ char *new_name = home_replace_save(NULL, name);
+ for (p = (char *) p_shada; *p; ) {
+ (void) copy_option_part(&p, part, ARRAY_SIZE(part), ", ");
+ if (part[0] == 'r') {
+ home_replace(NULL, part + 1, NameBuff, MAXPATHL, true);
+ size_t n = STRLEN(NameBuff);
+ if (mb_strnicmp(NameBuff, new_name, n) == 0) {
+ retval = true;
+ break;
+ }
+ }
+ }
+ xfree(new_name);
+ return retval;
+}
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
new file mode 100644
index 0000000000..49986ac1c1
--- /dev/null
+++ b/src/nvim/shada.h
@@ -0,0 +1,7 @@
+#ifndef NVIM_SHADA_H
+#define NVIM_SHADA_H
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "shada.h.generated.h"
+#endif
+#endif // NVIM_SHADA_H
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index b876753d57..9ffa5c6a76 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -483,6 +483,21 @@ bool has_non_ascii(const char_u *s)
return false;
}
+/// Return true if string "s" contains a non-ASCII character (128 or higher).
+/// When "s" is NULL false is returned.
+bool has_non_ascii_len(const char *const s, const size_t len)
+ FUNC_ATTR_PURE
+{
+ if (s != NULL) {
+ for (size_t i = 0; i < len; i++) {
+ if ((uint8_t) s[i] >= 128) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
/*
* Concatenate two strings and return the result in allocated memory.
*/
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 75055cc21a..b0d1a17c89 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -130,7 +130,7 @@ static char_u *tagmatchname = NULL; /* name of last used tag */
* Tag for preview window is remembered separately, to avoid messing up the
* normal tagstack.
*/
-static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0}, 0, 0};
+static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0, 0, NULL}, 0, 0};
/*
* Jump to tag; handling of tag commands and tag stack
diff --git a/src/nvim/testdir/test8.in b/src/nvim/testdir/test8.in
index 0f27c813ec..41e6262e92 100644
--- a/src/nvim/testdir/test8.in
+++ b/src/nvim/testdir/test8.in
@@ -32,7 +32,7 @@ endfunc
$put ='VimLeave done'
write
endfunc
-:set viminfo='100
+:set shada='100
:au BufUnload * call CloseAll()
:au VimLeave * call WriteToOut()
:e small.vim
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 32b3f1583c..654b857301 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -249,6 +249,7 @@ static void tk_getkeys(TermInput *input, bool force)
static void timer_cb(TimeWatcher *watcher, void *data)
{
tk_getkeys(data, true);
+ flush_input(data, true);
}
static bool handle_bracketed_paste(TermInput *input)
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 063c13084d..2b0ffefa7e 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -325,6 +325,14 @@ static long get_undolevel(void)
return curbuf->b_p_ul;
}
+static inline void zero_fmark_additional_data(fmark_T *fmarks)
+{
+ for (size_t i = 0; i < NMARKS; i++) {
+ dict_unref(fmarks[i].additional_data);
+ fmarks[i].additional_data = NULL;
+ }
+}
+
/*
* Common code for various ways to save text before a change.
* "top" is the line above the first changed line.
@@ -467,7 +475,9 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0);
/* save named marks and Visual marks for undo */
- memmove(uhp->uh_namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);
+ zero_fmark_additional_data(curbuf->b_namedm);
+ memmove(uhp->uh_namedm, curbuf->b_namedm,
+ sizeof(curbuf->b_namedm[0]) * NMARKS);
uhp->uh_visual = curbuf->b_visual;
curbuf->b_u_newhead = uhp;
@@ -785,7 +795,7 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp)
undo_write_bytes(bi, (uintmax_t)uhp->uh_flags, 2);
// Assume NMARKS will stay the same.
for (size_t i = 0; i < (size_t)NMARKS; i++) {
- serialize_pos(bi, uhp->uh_namedm[i]);
+ serialize_pos(bi, uhp->uh_namedm[i].mark);
}
serialize_visualinfo(bi, &uhp->uh_visual);
uint8_t time_buf[8];
@@ -831,8 +841,11 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name)
unserialize_pos(bi, &uhp->uh_cursor);
uhp->uh_cursor_vcol = undo_read_4c(bi);
uhp->uh_flags = undo_read_2c(bi);
+ const Timestamp cur_timestamp = os_time();
for (size_t i = 0; i < (size_t)NMARKS; i++) {
- unserialize_pos(bi, &uhp->uh_namedm[i]);
+ unserialize_pos(bi, &uhp->uh_namedm[i].mark);
+ uhp->uh_namedm[i].timestamp = cur_timestamp;
+ uhp->uh_namedm[i].fnum = 0;
}
unserialize_visualinfo(bi, &uhp->uh_visual);
uhp->uh_time = undo_read_time(bi);
@@ -2009,7 +2022,7 @@ static void u_undoredo(int undo)
u_entry_T *newlist = NULL;
int old_flags;
int new_flags;
- pos_T namedm[NMARKS];
+ fmark_T namedm[NMARKS];
visualinfo_T visualinfo;
int empty_buffer; /* buffer became empty */
u_header_T *curhead = curbuf->b_u_curhead;
@@ -2029,7 +2042,8 @@ static void u_undoredo(int undo)
/*
* save marks before undo/redo
*/
- memmove(namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);
+ zero_fmark_additional_data(curbuf->b_namedm);
+ memmove(namedm, curbuf->b_namedm, sizeof(curbuf->b_namedm[0]) * NMARKS);
visualinfo = curbuf->b_visual;
curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count;
curbuf->b_op_start.col = 0;
@@ -2158,7 +2172,8 @@ static void u_undoredo(int undo)
* restore marks from before undo/redo
*/
for (i = 0; i < NMARKS; ++i)
- if (curhead->uh_namedm[i].lnum != 0) {
+ if (curhead->uh_namedm[i].mark.lnum != 0) {
+ free_fmark(curbuf->b_namedm[i]);
curbuf->b_namedm[i] = curhead->uh_namedm[i];
curhead->uh_namedm[i] = namedm[i];
}
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 610adb4367..d841210815 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -5,6 +5,7 @@
#include "nvim/pos.h"
#include "nvim/buffer_defs.h"
+#include "nvim/mark_defs.h"
/* Structure to store info about the Visual area. */
typedef struct {
@@ -54,7 +55,7 @@ struct u_header {
pos_T uh_cursor; /* cursor position before saving */
long uh_cursor_vcol;
int uh_flags; /* see below */
- pos_T uh_namedm[NMARKS]; /* marks before undo/after redo */
+ fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
visualinfo_T uh_visual; /* Visual areas before undo/after redo */
time_t uh_time; /* timestamp when the change was made */
long uh_save_nr; /* set when the file was saved after the
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 992463a8fc..b71b2cb603 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1925,7 +1925,7 @@ int win_close(win_T *win, int free_buf)
&& (last_window() || curtab != prev_curtab
|| close_last_window_tabpage(win, free_buf, prev_curtab))) {
/* Autocommands have close all windows, quit now. Restore
- * curwin->w_buffer, otherwise writing viminfo may fail. */
+ * curwin->w_buffer, otherwise writing ShaDa file may fail. */
if (curwin->w_buffer == NULL)
curwin->w_buffer = curbuf;
getout(0);
diff --git a/test/config/paths.lua.in b/test/config/paths.lua.in
index a13230ed28..80cc5629d1 100644
--- a/test/config/paths.lua.in
+++ b/test/config/paths.lua.in
@@ -7,6 +7,7 @@ end
module.test_include_path = "${CMAKE_BINARY_DIR}/test/includes/post"
module.test_libnvim_path = "${TEST_LIBNVIM_PATH}"
+module.test_source_path = "${CMAKE_SOURCE_DIR}"
table.insert(module.include_paths, "${CMAKE_BINARY_DIR}/include")
return module
diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua
index 943bdfca30..9a7b630f64 100644
--- a/test/functional/eval/msgpack_functions_spec.lua
+++ b/test/functional/eval/msgpack_functions_spec.lua
@@ -452,12 +452,12 @@ describe('msgpackparse() function', function()
end)
it('msgpackparse(systemlist(...)) does not segfault. #3135', function()
- local cmd = "msgpackparse(systemlist('"
- ..helpers.nvim_prog.." --api-info'))['_TYPE']['_VAL'][0][0]"
+ local cmd = "sort(keys(msgpackparse(systemlist('"
+ ..helpers.nvim_prog.." --api-info'))[0]))"
local api_info = eval(cmd)
api_info = eval(cmd) -- do it again (try to force segfault)
api_info = eval(cmd) -- do it again
- eq('functions', api_info)
+ eq({'error_types', 'functions', 'types'}, api_info)
end)
it('fails when called with no arguments', function()
diff --git a/test/functional/ex_cmds/recover_spec.lua b/test/functional/ex_cmds/recover_spec.lua
index b92739e40e..a24a60af81 100644
--- a/test/functional/ex_cmds/recover_spec.lua
+++ b/test/functional/ex_cmds/recover_spec.lua
@@ -47,7 +47,8 @@ describe(':preserve', function()
--TODO(justinmk): this is an ugly hack to force `helpers` to support
--multiple sessions.
- local nvim2 = helpers.spawn({helpers.nvim_prog, '-u', 'NONE', '--embed'})
+ local nvim2 = helpers.spawn({helpers.nvim_prog, '-u', 'NONE', '--embed'},
+ true)
helpers.set_session(nvim2)
source(init)
diff --git a/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua
index f4911cd3e8..0c85157a0f 100644
--- a/test/functional/ex_cmds/wviminfo_spec.lua
+++ b/test/functional/ex_cmds/wviminfo_spec.lua
@@ -3,8 +3,8 @@ local clear, execute, eq, neq, spawn, nvim_prog, set_session, wait, write_file
= helpers.clear, helpers.execute, helpers.eq, helpers.neq, helpers.spawn,
helpers.nvim_prog, helpers.set_session, helpers.wait, helpers.write_file
-describe(':wviminfo', function()
- local viminfo_file = 'wviminfo_test'
+describe(':wshada', function()
+ local shada_file = 'wshada_test'
local session
before_each(function()
@@ -17,38 +17,41 @@ describe(':wviminfo', function()
'--cmd', 'set swapfile'})
set_session(session)
- os.remove(viminfo_file)
+ os.remove(shada_file)
end)
- it('creates a viminfo file', function()
+ it('creates a shada file', function()
-- file should _not_ exist
- eq(nil, lfs.attributes(viminfo_file))
- execute('wv! '..viminfo_file)
+ eq(nil, lfs.attributes(shada_file))
+ execute('wsh! '..shada_file)
wait()
-- file _should_ exist
- neq(nil, lfs.attributes(viminfo_file))
+ neq(nil, lfs.attributes(shada_file))
end)
it('overwrites existing files', function()
- local text = 'wviminfo test'
+ local text = 'wshada test'
-- Create a dummy file
- write_file(viminfo_file, text)
+ write_file(shada_file, text)
-- sanity check
- eq(text, io.open(viminfo_file):read())
- neq(nil, lfs.attributes(viminfo_file))
+ eq(text, io.open(shada_file):read())
+ neq(nil, lfs.attributes(shada_file))
- execute('wv! '..viminfo_file)
+ execute('wsh! '..shada_file)
wait()
- -- File should have been overwritten with a viminfo file.
- local line1 = io.lines(viminfo_file)()
- assert(nil ~= string.find(line1, 'This viminfo file was generated by Nvim'),
- viminfo_file..' should be a viminfo-formatted file')
+ -- File should have been overwritten with a shada file.
+ local fp = io.open(shada_file, 'r')
+ local char1 = fp:read(1)
+ fp:close()
+ -- ShaDa file starts with a “header” entry
+ assert(char1:byte() == 0x01,
+ shada_file..' should be a shada file')
end)
teardown(function()
- os.remove(viminfo_file)
+ os.remove(shada_file)
end)
end)
diff --git a/test/functional/ex_getln/history_spec.lua b/test/functional/ex_getln/history_spec.lua
new file mode 100644
index 0000000000..532c81dab9
--- /dev/null
+++ b/test/functional/ex_getln/history_spec.lua
@@ -0,0 +1,36 @@
+local helpers = require('test.functional.helpers')
+local clear, meths, funcs, eq =
+ helpers.clear, helpers.meths, helpers.funcs, helpers.eq
+
+describe('history support code', function()
+ before_each(clear)
+
+ it('correctly clears start of the history', function()
+ -- Regression test: check absense of the memory leak when clearing start of
+ -- the history using ex_getln.c/clr_history().
+ eq(1, funcs.histadd(':', 'foo'))
+ eq(1, funcs.histdel(':'))
+ eq('', funcs.histget(':', -1))
+ end)
+
+ it('correctly clears end of the history', function()
+ -- Regression test: check absense of the memory leak when clearing end of
+ -- the history using ex_getln.c/clr_history().
+ meths.set_option('history', 1)
+ eq(1, funcs.histadd(':', 'foo'))
+ eq(1, funcs.histdel(':'))
+ eq('', funcs.histget(':', -1))
+ end)
+
+ it('correctly removes item from history', function()
+ -- Regression test: check that ex_getln.c/del_history_idx() correctly clears
+ -- history index after removing history entry. If it does not then deleting
+ -- history will result in a double free.
+ eq(1, funcs.histadd(':', 'foo'))
+ eq(1, funcs.histadd(':', 'bar'))
+ eq(1, funcs.histadd(':', 'baz'))
+ eq(1, funcs.histdel(':', -2))
+ eq(1, funcs.histdel(':'))
+ eq('', funcs.histget(':', -1))
+ end)
+end)
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 80cb1e5ce3..a62f7a3d00 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -44,11 +44,15 @@ elseif os.getenv('GDB') then
end
if prepend_argv then
+ local new_nvim_argv = {}
local len = #prepend_argv
+ for i = 1, len do
+ new_nvim_argv[i] = prepend_argv[i]
+ end
for i = 1, #nvim_argv do
- prepend_argv[i + len] = nvim_argv[i]
+ new_nvim_argv[i + len] = nvim_argv[i]
end
- nvim_argv = prepend_argv
+ nvim_argv = new_nvim_argv
end
local session, loop_running, loop_stopped, last_error
@@ -174,12 +178,27 @@ local function rawfeed(...)
end
end
-local function spawn(argv)
+local function merge_args(...)
+ local i = 1
+ local argv = {}
+ for anum = 1,select('#', ...) do
+ local args = select(anum, ...)
+ if args then
+ for _, arg in ipairs(args) do
+ argv[i] = arg
+ i = i + 1
+ end
+ end
+ end
+ return argv
+end
+
+local function spawn(argv, merge)
local loop = Loop.new()
local msgpack_stream = MsgpackStream.new(loop)
local async_session = AsyncSession.new(msgpack_stream)
local session = Session.new(async_session)
- loop:spawn(argv)
+ loop:spawn(merge and merge_args(prepend_argv, argv) or argv)
return session
end
@@ -216,9 +235,12 @@ local function execute(...)
end
-- Dedent the given text and write it to the file name.
-local function write_file(name, text)
+local function write_file(name, text, dont_dedent)
local file = io.open(name, 'w')
- file:write(dedent(text))
+ if not dont_dedent then
+ text = dedent(text)
+ end
+ file:write(text)
file:flush()
file:close()
end
@@ -337,7 +359,40 @@ local exc_exec = function(cmd)
return ret
end
+local function redir_exec(cmd)
+ nvim_command(([[
+ redir => g:__output
+ silent! execute "%s"
+ redir END
+ ]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0')))
+ local ret = nvim_eval('get(g:, "__output", 0)')
+ nvim_command('unlet! g:__output')
+ return ret
+end
+
+local function create_callindex(func)
+ local tbl = {}
+ setmetatable(tbl, {
+ __index = function(tbl, arg1)
+ ret = function(...) return func(arg1, ...) end
+ tbl[arg1] = ret
+ return ret
+ end,
+ })
+ return tbl
+end
+
+local funcs = create_callindex(nvim_call)
+local meths = create_callindex(nvim)
+local bufmeths = create_callindex(buffer)
+local winmeths = create_callindex(window)
+local tabmeths = create_callindex(tabpage)
+local curbufmeths = create_callindex(curbuf)
+local curwinmeths = create_callindex(curwin)
+local curtabmeths = create_callindex(curtab)
+
return {
+ prepend_argv = prepend_argv,
clear = clear,
spawn = spawn,
dedent = dedent,
@@ -374,4 +429,14 @@ return {
rmdir = rmdir,
mkdir = lfs.mkdir,
exc_exec = exc_exec,
+ redir_exec = redir_exec,
+ merge_args = merge_args,
+ funcs = funcs,
+ meths = meths,
+ bufmeths = bufmeths,
+ winmeths = winmeths,
+ tabmeths = tabmeths,
+ curbufmeths = curbufmeths,
+ curwinmeths = curwinmeths,
+ curtabmeths = curtabmeths,
}
diff --git a/test/functional/legacy/074_global_var_in_viminfo_spec.lua b/test/functional/legacy/074_global_var_in_viminfo_spec.lua
index a89a4181cd..2428b7f74d 100644
--- a/test/functional/legacy/074_global_var_in_viminfo_spec.lua
+++ b/test/functional/legacy/074_global_var_in_viminfo_spec.lua
@@ -1,14 +1,15 @@
--- Tests for storing global variables in the .viminfo file
+-- Tests for storing global variables in the .shada file
local helpers, lfs = require('test.functional.helpers'), require('lfs')
local clear, execute, eq, neq, eval, wait, spawn =
helpers.clear, helpers.execute, helpers.eq, helpers.neq, helpers.eval,
helpers.wait, helpers.spawn
-describe('storing global variables in viminfo files', function()
+describe('storing global variables in ShaDa files', function()
+ local tempname = 'Xtest-functional-legacy-074'
setup(function()
clear()
- os.remove("Xviminfo")
+ os.remove(tempname)
end)
it('is working', function()
@@ -27,33 +28,34 @@ describe('storing global variables in viminfo files', function()
execute(
-- This will cause a few errors, do it silently.
'set visualbell',
- 'set viminfo+=!',
+ 'set shada+=!',
"let MY_GLOBAL_DICT={'foo': 1, 'bar': 0, 'longvarible': 1000}",
- -- Store a really long list, so line wrapping will occur in viminfo
- -- file.
+ -- Store a really long list. Initially this was testing line wrapping in
+ -- viminfo, but shada files has no line wrapping, no matter how long the
+ -- list is.
'let MY_GLOBAL_LIST=range(1,100)'
)
eq(test_dict, eval('MY_GLOBAL_DICT'))
eq(test_list, eval('MY_GLOBAL_LIST'))
- execute('wv! Xviminfo')
+ execute('wsh! ' .. tempname)
wait()
- -- Assert that the viminfo file exists.
- neq(nil, lfs.attributes('Xviminfo'))
+ -- Assert that the shada file exists.
+ neq(nil, lfs.attributes(tempname))
execute('unlet MY_GLOBAL_DICT',
'unlet MY_GLOBAL_LIST')
-- Assert that the variables where deleted.
eq(0, eval('exists("MY_GLOBAL_DICT")'))
eq(0, eval('exists("MY_GLOBAL_LIST")'))
- execute('rv! Xviminfo')
+ execute('rsh! ' .. tempname)
eq(test_list, eval('MY_GLOBAL_LIST'))
eq(test_dict, eval('MY_GLOBAL_DICT'))
end)
teardown(function()
- os.remove('Xviminfo')
+ os.remove(tempname)
end)
end)
diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua
new file mode 100644
index 0000000000..3666b718f0
--- /dev/null
+++ b/test/functional/shada/buffers_spec.lua
@@ -0,0 +1,51 @@
+-- ShaDa buffer list saving/reading support
+local helpers = require('test.functional.helpers')
+local nvim_command, funcs, eq =
+ helpers.command, helpers.funcs, helpers.eq
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, set_additional_cmd, clear =
+ shada_helpers.reset, shada_helpers.set_additional_cmd,
+ shada_helpers.clear
+
+describe('ShaDa support code', function()
+ testfilename = 'Xtestfile-functional-shada-buffers'
+ testfilename_2 = 'Xtestfile-functional-shada-buffers-2'
+ before_each(reset)
+ after_each(clear)
+
+ it('is able to dump and restore buffer list', function()
+ set_additional_cmd('set shada+=%')
+ reset()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('edit ' .. testfilename_2)
+ nvim_command('qall')
+ reset()
+ eq(3, funcs.bufnr('$'))
+ eq('', funcs.bufname(1))
+ eq(testfilename, funcs.bufname(2))
+ eq(testfilename_2, funcs.bufname(3))
+ end)
+
+ it('does not restore buffer list without % in &shada', function()
+ set_additional_cmd('set shada+=%')
+ reset()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('edit ' .. testfilename_2)
+ set_additional_cmd('')
+ nvim_command('qall')
+ reset()
+ eq(1, funcs.bufnr('$'))
+ eq('', funcs.bufname(1))
+ end)
+
+ it('does not dump buffer list without % in &shada', function()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('edit ' .. testfilename_2)
+ set_additional_cmd('set shada+=%')
+ nvim_command('qall')
+ reset()
+ eq(1, funcs.bufnr('$'))
+ eq('', funcs.bufname(1))
+ end)
+end)
diff --git a/test/functional/shada/compatibility_spec.lua b/test/functional/shada/compatibility_spec.lua
new file mode 100644
index 0000000000..342dee377b
--- /dev/null
+++ b/test/functional/shada/compatibility_spec.lua
@@ -0,0 +1,453 @@
+-- ShaDa compatibility support
+local helpers = require('test.functional.helpers')
+local nvim_command, funcs, eq = helpers.command, helpers.funcs, helpers.eq
+local exc_exec = helpers.exc_exec
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, set_additional_cmd, clear, get_shada_rw =
+ shada_helpers.reset, shada_helpers.set_additional_cmd,
+ shada_helpers.clear, shada_helpers.get_shada_rw
+local read_shada_file = shada_helpers.read_shada_file
+
+local wshada, sdrcmd, shada_fname = get_shada_rw('Xtest-functional-shada-compatibility.shada')
+
+describe('ShaDa forward compatibility support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('works with search pattern item with BOOL unknown (sX) key value', function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ local found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and not v.value.ss then
+ eq(false, v.value.sX)
+ found = true
+ end
+ end
+ eq(true, found)
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('silent! /---/')
+ nvim_command('wshada ' .. shada_fname)
+ found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and not v.value.ss then
+ eq(nil, v.value.sX)
+ found = true
+ end
+ end
+ eq(true, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada! ' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with s/search pattern item with BOOL unknown (sX) key value', function()
+ wshada('\002\001\015\131\162sX\194\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ local found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.ss then
+ eq(false, v.value.sX)
+ found = true
+ end
+ end
+ eq(true, found)
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('silent! s/--/---/ge')
+ nvim_command('wshada ' .. shada_fname)
+ found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.ss then
+ eq(nil, v.value.sX)
+ found = true
+ end
+ end
+ eq(true, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with replacement item with BOOL additional value in list', function()
+ wshada('\003\000\005\146\196\001-\194')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ local found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 3 then
+ eq(2, #v.value)
+ eq(false, v.value[2])
+ found = true
+ end
+ end
+ eq(true, found)
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('silent! s/--/---/ge')
+ nvim_command('wshada ' .. shada_fname)
+ found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 3 then
+ eq(1, #v.value)
+ found = true
+ end
+ end
+ eq(true, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ for _, v in ipairs({{name='global mark', mpack='\007\001\018\131\162mX\195\161f\196\006/a/b/c\161nA'},
+ {name='jump', mpack='\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002'},
+ {name='local mark', mpack='\010\001\018\131\162mX\195\161f\196\006/a/b/c\161na'},
+ {name='change', mpack='\011\001\015\130\162mX\195\161f\196\006/a/b/c'},
+ }) do
+ it('works with ' .. v.name .. ' item with BOOL unknown (mX) key value', function()
+ nvim_command('silent noautocmd edit /a/b/c')
+ eq('/a/b/c', funcs.bufname('%'))
+ funcs.setline('.', {'1', '2', '3'})
+ wshada(v.mpack)
+ eq(0, exc_exec(sdrcmd(true)))
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ local found = false
+ for _, subv in ipairs(read_shada_file(shada_fname)) do
+ if subv.type == v.mpack:byte() then
+ if subv.value.mX == true then
+ found = true
+ end
+ end
+ end
+ eq(true, found)
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('bwipeout!')
+ funcs.setpos('\'A', {0, 1, 1, 0})
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ found = false
+ for _, subv in ipairs(read_shada_file(shada_fname)) do
+ if subv.type == v.mpack:byte() then
+ if subv.value.mX == true then
+ found = true
+ end
+ end
+ end
+ eq(false, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ if v.name == 'global mark' or v.name == 'local mark' then
+ it('works with ' .. v.name .. ' item with <C-a> name', function()
+ nvim_command('silent noautocmd edit /a/b/c')
+ eq('/a/b/c', funcs.bufname('%'))
+ funcs.setline('.', {'1', '2', '3'})
+ wshada(v.mpack:gsub('n.$', 'n\001')
+ .. v.mpack:gsub('n.$', 'n\002')
+ .. v.mpack:gsub('n.$', 'n\003'):gsub('/a/b/c', '/d/e/f'))
+ eq(0, exc_exec(sdrcmd(true)))
+ nvim_command('wshada ' .. shada_fname)
+ local found = 0
+ for i, subv in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, subv.type)
+ end
+ if subv.type == v.mpack:byte() then
+ if subv.value.mX == true and subv.value.n <= 3 then
+ found = found + 1
+ end
+ end
+ end
+ eq(3, found)
+ nvim_command('wshada! ' .. shada_fname)
+ local found = 0
+ for i, subv in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, subv.type)
+ end
+ if subv.type == v.mpack:byte() then
+ if subv.value.mX == true and subv.value.n <= 3 then
+ found = found + 1
+ end
+ end
+ end
+ eq(0, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+ end
+ end
+
+ it('works with register item with BOOL unknown (rX) key', function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ local found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 5 and v.value.rX == false then
+ found = true
+ end
+ end
+ eq(true, found)
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('let @a = "Test"')
+ nvim_command('wshada ' .. shada_fname)
+ found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 5 and v.value.rX == false then
+ found = true
+ end
+ end
+ eq(false, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with register item with <C-a> name', function()
+ wshada('\005\001\015\131\161n\001\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd(true)))
+ nvim_command('wshada ' .. shada_fname)
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 5 then
+ if v.value.rX == false and v.value.n == 1 then
+ found = found + 1
+ end
+ end
+ end
+ eq(1, found)
+ nvim_command('wshada! ' .. shada_fname)
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 5 then
+ if v.value.rX == false and v.value.n == 1 then
+ found = found + 1
+ end
+ end
+ end
+ eq(0, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with register item with type 10', function()
+ wshada('\005\001\019\132\161na\162rX\194\162rc\145\196\001-\162rt\010')
+ eq(0, exc_exec(sdrcmd(true)))
+ -- getreg may return empty list as list with NULL pointer which API
+ -- translates into nil for some reason.
+ eq({}, funcs.getreg('a', 1, 1) or {})
+ eq('', funcs.getregtype('a'))
+ nvim_command('wshada ' .. shada_fname)
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 5 then
+ if v.value.rX == false and v.value.rt == 10 then
+ found = found + 1
+ end
+ end
+ end
+ eq(1, found)
+ nvim_command('wshada! ' .. shada_fname)
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 5 then
+ if v.value.rX == false and v.value.rt == 10 then
+ found = found + 1
+ end
+ end
+ end
+ eq(0, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with buffer list item with BOOL unknown (bX) key', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\016\145\130\161f\196\006/a/b/c\162bX\195')
+ eq(0, exc_exec(sdrcmd()))
+ eq(2, funcs.bufnr('$'))
+ eq('/a/b/c', funcs.bufname(2))
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ local found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 9 and #v.value == 1 and v.value[1].bX == true then
+ found = true
+ end
+ end
+ eq(true, found)
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('buffer 2')
+ nvim_command('edit!')
+ nvim_command('wshada ' .. shada_fname)
+ found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 5 and v.value.rX == false then
+ found = true
+ end
+ end
+ eq(false, found)
+ nvim_command('bwipeout!')
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with history item with BOOL additional value in list', function()
+ wshada('\004\000\006\147\000\196\001-\194')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ nvim_command('wshada ' .. shada_fname)
+ local found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == '-' then
+ eq(false, v.value[3])
+ eq(3, #v.value)
+ found = true
+ end
+ end
+ eq(true, found)
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ funcs.histadd(':', '--')
+ funcs.histadd(':', '-')
+ nvim_command('wshada ' .. shada_fname)
+ found = false
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == '-' then
+ eq(2, #v.value)
+ found = true
+ end
+ end
+ eq(true, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with history item with type 10', function()
+ wshada('\004\000\006\147\010\196\001-\194')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('wshada ' .. shada_fname)
+ eq(0, exc_exec(sdrcmd()))
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 4 then
+ if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then
+ found = found + 1
+ end
+ end
+ end
+ eq(1, found)
+ nvim_command('wshada! ' .. shada_fname)
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 4 then
+ if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then
+ found = found + 1
+ end
+ end
+ end
+ eq(0, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+
+ it('works with item with 100 type', function()
+ wshada('\100\000\006\147\010\196\001-\194')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('wshada ' .. shada_fname)
+ eq(0, exc_exec(sdrcmd()))
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 100 then
+ if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then
+ found = found + 1
+ end
+ end
+ end
+ eq(1, found)
+ nvim_command('wshada! ' .. shada_fname)
+ local found = 0
+ for i, v in ipairs(read_shada_file(shada_fname)) do
+ if i == 1 then
+ eq(1, v.type)
+ end
+ if v.type == 100 then
+ if v.value[1] == 10 and #v.value == 3 and v.value[3] == false then
+ found = found + 1
+ end
+ end
+ end
+ eq(0, found)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ nvim_command('rshada!' .. shada_fname)
+ funcs.garbagecollect(1)
+ funcs.garbagecollect(1)
+ end)
+end)
diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua
new file mode 100644
index 0000000000..16ae77af02
--- /dev/null
+++ b/test/functional/shada/errors_spec.lua
@@ -0,0 +1,490 @@
+-- ShaDa errors handling support
+local helpers = require('test.functional.helpers')
+local nvim_command, eq, exc_exec = helpers.command, helpers.eq, helpers.exc_exec
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, clear, get_shada_rw =
+ shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw
+
+local wshada, sdrcmd, shada_fname, clean =
+ get_shada_rw('Xtest-functional-shada-errors.shada')
+
+describe('ShaDa error handling', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ clean()
+ end)
+
+ -- Note: most of tests have additional items like sX, mX, rX. These are for
+ -- valgrind tests, to check for memory leaks (i.e. whether error handling code
+ -- does (not) forget to call ga_clear). Not needed for array-based items like
+ -- history because they are not using ad_ga.
+
+ it('does not fail on empty file', function()
+ wshada('')
+ eq(0, exc_exec(sdrcmd()))
+ end)
+
+ it('fails on zero', function()
+ wshada('\000')
+ eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 0, but got nothing', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on missing item', function()
+ wshada('\000\000\000')
+ eq('Vim(rshada):E576: Error while reading ShaDa file: there is an item at position 0 that must not be there: Missing items are for internal uses only', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on -2 type', function()
+ wshada('\254\000\000')
+ eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 0', exc_exec(sdrcmd()))
+ end)
+
+ it('does not fail on header with zero length', function()
+ -- Header items are skipped when reading.
+ wshada('\001\000\000')
+ eq(0, exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with zero length', function()
+ wshada('\002\000\000')
+ eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 3', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with -2 timestamp', function()
+ wshada('\002\254\000')
+ eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 1', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with -2 length', function()
+ wshada('\002\000\254')
+ eq('Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 2', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with length greater then file length', function()
+ wshada('\002\000\002\000')
+ eq('Vim(rshada):E576: Error while reading ShaDa file: last entry specified that it occupies 2 bytes, but file ended earlier', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with invalid byte', function()
+ -- 195 (== 0xC1) cannot start any valid messagepack entry (the only byte
+ -- that cannot do this). Specifically unpack_template.h contains
+ --
+ -- //case 0xc1: // string
+ -- // again_terminal_trail(NEXT_CS(p), p+1);
+ --
+ -- (literally: commented out code) which means that in place of this code
+ -- `goto _failed` is used from default: case. I do not know any other way to
+ -- get MSGPACK_UNPACK_PARSE_ERROR and not MSGPACK_UNPACK_CONTINUE or
+ -- MSGPACK_UNPACK_EXTRA_BYTES.
+ wshada('\002\000\001\193')
+ eq('Vim(rshada):E576: Failed to parse ShaDa file due to a msgpack parser error at position 3', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with incomplete map', function()
+ wshada('\002\000\001\129')
+ eq('Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 3', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item without a pattern', function()
+ wshada('\002\000\005\129\162sX\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has no pattern', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern with extra bytes', function()
+ wshada('\002\000\002\128\000')
+ eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL value', function()
+ wshada('\002\000\001\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 is not a dictionary', exc_exec(sdrcmd()))
+ end)
+
+ -- sp entry is here because it causes an allocation.
+ it('fails on search pattern item with BIN key', function()
+ wshada('\002\000\014\131\162sp\196\001a\162sX\192\196\000\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has key which is not a string', exc_exec(sdrcmd()))
+ end)
+
+ -- sp entry is here because it causes an allocation.
+ it('fails on search pattern item with empty key', function()
+ wshada('\002\000\013\131\162sp\196\001a\162sX\192\160\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has empty key', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL magic key value', function()
+ wshada('\002\000\009\130\162sX\192\162sm\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sm key value which is not a boolean', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL smartcase key value', function()
+ wshada('\002\000\009\130\162sX\192\162sc\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sc key value which is not a boolean', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL has_line_offset key value', function()
+ wshada('\002\000\009\130\162sX\192\162sl\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sl key value which is not a boolean', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL place_cursor_at_end key value', function()
+ wshada('\002\000\009\130\162sX\192\162se\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has se key value which is not a boolean', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL is_last_used key value', function()
+ wshada('\002\000\009\130\162sX\192\162su\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has su key value which is not a boolean', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL is_substitute_pattern key value', function()
+ wshada('\002\000\009\130\162sX\192\162ss\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has ss key value which is not a boolean', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL highlighted key value', function()
+ wshada('\002\000\009\130\162sX\192\162sh\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sh key value which is not a boolean', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL offset key value', function()
+ wshada('\002\000\009\130\162sX\192\162so\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has so key value which is not an integer', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with NIL pat key value', function()
+ wshada('\002\000\009\130\162sX\192\162sp\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sp key value which is not a binary', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search pattern item with STR pat key value', function()
+ wshada('\002\000\011\130\162sX\192\162sp\162sp')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sp key value which is not a binary', exc_exec(sdrcmd()))
+ end)
+
+ for _, v in ipairs({{name='global mark', mpack='\007'},
+ {name='jump', mpack='\008'},
+ {name='local mark', mpack='\010'},
+ {name='change', mpack='\011'},
+ }) do
+ local is_mark_test = ({['global mark']=true, ['local mark']=true})[v.name]
+
+ it('fails on ' .. v.name .. ' item with NIL value', function()
+ wshada(v.mpack .. '\000\001\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 is not a dictionary', exc_exec(sdrcmd()))
+ end)
+
+ -- f entry is here because it causes an allocation.
+ it('fails on ' .. v.name .. ' item with BIN key', function()
+ wshada(v.mpack .. '\000\013\131\161f\196\001/\162mX\192\196\000\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has key which is not a string', exc_exec(sdrcmd()))
+ end)
+
+ -- f entry is here because it causes an allocation.
+ it('fails on ' .. v.name .. ' item with empty key', function()
+ wshada(v.mpack .. '\000\012\131\161f\196\001/\162mX\192\160\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has empty key', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item without f key', function()
+ wshada(v.mpack .. '\000\008\130\162mX\192\161l\001')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 is missing file name', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item with zero l key', function()
+ wshada(v.mpack .. '\000\013\131\162mX\192\161f\196\001/\161l\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has invalid line number', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item with negative l key', function()
+ wshada(v.mpack .. '\000\013\131\162mX\192\161f\196\001/\161l\255')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has invalid line number', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item with negative c key', function()
+ wshada(v.mpack .. '\000\013\131\162mX\192\161f\196\001/\161c\255')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has invalid column number', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item with STR n key value', function()
+ wshada(v.mpack .. '\000\011\130\162mX\192\161n\163spa')
+ eq(is_mark_test and 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has n key value which is not an unsigned integer' or 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has n key which is only valid for local and global mark entries', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item with STR l key value', function()
+ wshada(v.mpack .. '\000\010\130\162mX\192\161l\162sp')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has l key value which is not an integer', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item with STR c key value', function()
+ wshada(v.mpack .. '\000\010\130\162mX\192\161c\162sp')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has c key value which is not an integer', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on ' .. v.name .. ' item with STR f key value', function()
+ wshada(v.mpack .. '\000\010\130\162mX\192\161f\162sp')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has f key value which is not a binary', exc_exec(sdrcmd()))
+ end)
+ end
+
+ it('fails on register item with NIL value', function()
+ wshada('\005\000\001\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 is not a dictionary', exc_exec(sdrcmd()))
+ end)
+
+ -- rc entry is here because it causes an allocation
+ it('fails on register item with BIN key', function()
+ wshada('\005\000\015\131\162rc\145\196\001a\162rX\192\196\000\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has key which is not a string', exc_exec(sdrcmd()))
+ end)
+
+ -- rc entry is here because it causes an allocation
+ it('fails on register item with BIN key', function()
+ wshada('\005\000\014\131\162rc\145\196\001a\162rX\192\160\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has empty key', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on register item with NIL rt key value', function()
+ wshada('\005\000\009\130\162rX\192\162rt\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rt key value which is not an unsigned integer', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on register item with NIL rw key value', function()
+ wshada('\005\000\009\130\162rX\192\162rw\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rw key value which is not an unsigned integer', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on register item with NIL rc key value', function()
+ wshada('\005\000\009\130\162rX\192\162rc\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc key with non-array value', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on register item with empty rc key value', function()
+ wshada('\005\000\009\130\162rX\192\162rc\144')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc key with empty array', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on register item with NIL in rc array', function()
+ wshada('\005\000\013\130\162rX\192\162rc\146\196\001a\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc array with non-binary value', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on register item without rc array', function()
+ wshada('\005\000\009\129\162rX\146\196\001a\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has missing rc array', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on history item with NIL value', function()
+ wshada('\004\000\001\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 is not an array', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on history item with empty value', function()
+ wshada('\004\000\001\144')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 does not have enough elements', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on history item with single element value', function()
+ wshada('\004\000\002\145\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 does not have enough elements', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on history item with NIL first item', function()
+ wshada('\004\000\003\146\192\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 has wrong history type type', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on history item with FIXUINT second item', function()
+ wshada('\004\000\003\146\000\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 has wrong history string type', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on history item with second item with zero byte', function()
+ wshada('\004\000\007\146\000\196\003ab\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 contains string with zero byte inside', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search history item without third item', function()
+ wshada('\004\000\007\146\001\196\003abc')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search history entry at position 0 does not have separator character', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on search history item with NIL third item', function()
+ wshada('\004\000\007\147\001\196\002ab\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: search history entry at position 0 has wrong history separator type', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on variable item with NIL value', function()
+ wshada('\006\000\001\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 is not an array', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on variable item with empty value', function()
+ wshada('\006\000\001\144')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 does not have enough elements', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on variable item with single element value', function()
+ wshada('\006\000\002\145\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 does not have enough elements', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on variable item with NIL first item', function()
+ wshada('\006\000\003\146\192\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 has wrong variable name type', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on replacement item with NIL value', function()
+ wshada('\003\000\001\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 is not an array', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on replacement item with empty value', function()
+ wshada('\003\000\001\144')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 does not have enough elements', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on replacement item with NIL first item', function()
+ wshada('\003\000\002\145\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 has wrong sub string type', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with NIL value', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\001\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list entry at position 0 is not an array', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with NIL item in the array', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\008\146\129\161f\196\001/\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that is not a dictionary', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with empty item', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\008\146\129\161f\196\001/\128')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that does not have a file name', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with NIL l key', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161l\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list entry entry at position 0 has l key value which is not an integer', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with zero l key', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161l\000')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry with invalid line number', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with negative l key', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161l\255')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry with invalid line number', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with negative c key', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161c\255')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry with invalid column number', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on buffer list item with NIL c key', function()
+ nvim_command('set shada+=%')
+ wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161c\192')
+ eq('Vim(rshada):E575: Error while reading ShaDa file: buffer list entry entry at position 0 has c key value which is not an integer', exc_exec(sdrcmd()))
+ end)
+
+ it('fails on invalid ShaDa file (viminfo file)', function()
+ wshada([[# This viminfo file was generated by Vim 7.4.
+# You may edit it if you're careful!
+
+# Value of 'encoding' when this file was written
+*encoding=utf-8
+
+
+# hlsearch on (H) or off (h):
+~h
+# Last Search Pattern:
+~MSle0~/buffer=abuf
+
+# Last Substitute Search Pattern:
+~MSle0&^$
+
+# Last Substitute String:
+$
+
+# Command Line History (newest to oldest):
+:cq
+
+# Search String History (newest to oldest):
+? \<TMUX\>
+
+# Expression History (newest to oldest):
+=system('echo "\xAB"')
+
+# Input Line History (newest to oldest):
+@i
+
+# Input Line History (newest to oldest):
+
+# Registers:
+"0 LINE 0
+ case FLAG_B: puts("B"); break;
+"1 LINE 0
+ pick 874a489 shada,functests: Test compatibility support
+""- CHAR 0
+ .
+
+# global variables:
+!STUF_HISTORY_TRANSLIT LIS []
+!TR3_INPUT_HISTORY LIS []
+
+# File marks:
+'A 8320 12 ~/a.a/Proj/c/neovim-2076/src/nvim/ex_docmd.c
+'0 66 5 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo
+'1 7 0 ~/.vam/powerline/.git/MERGE_MSG
+'2 64 4 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo
+'3 9 0 ~/a.a/Proj/c/neovim/.git/COMMIT_EDITMSG
+'4 62 0 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo
+'5 57 4 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo
+'6 1 0 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo
+'7 399 7 /usr/share/vim/vim74/doc/motion.txt
+'8 1 0 ~/a.a/Proj/c/zpython/build/CMakeFiles/3.2.2/CMakeCCompiler.cmake
+'9 1 0 ~/a.a/Proj/c/vim/README.txt
+
+# Jumplist (newest first):
+-' 66 5 ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo
+
+# History of marks within files (newest to oldest):
+
+> ~/a.a/Proj/c/neovim/.git/rebase-merge/git-rebase-todo
+ " 66 5
+ ^ 66 6
+ . 66 5
+ + 65 0
+ + 65 0
+]])
+ eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec(sdrcmd()))
+ eq('Vim(wshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec('wshada ' .. shada_fname))
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ end)
+
+ it('fails on invalid ShaDa file (wrapper script)', function()
+ wshada('#!/bin/sh\n\npowerline "$@" 2>&1 | tee -a powerline\n')
+ eq('Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec(sdrcmd()))
+ eq('Vim(wshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', exc_exec('wshada ' .. shada_fname))
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ end)
+
+ it('fails on invalid ShaDa file (failing skip in second item)', function()
+ wshada('\001\000\001\128#!/')
+ eq('Vim(rshada):E576: Error while reading ShaDa file: last entry specified that it occupies 47 bytes, but file ended earlier', exc_exec(sdrcmd()))
+ eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 47 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname))
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ end)
+end)
diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua
new file mode 100644
index 0000000000..c2ff4cadd1
--- /dev/null
+++ b/test/functional/shada/helpers.lua
@@ -0,0 +1,94 @@
+local helpers = require('test.functional.helpers')
+local spawn, set_session, meths, nvim_prog =
+ helpers.spawn, helpers.set_session, helpers.meths, helpers.nvim_prog
+local write_file, merge_args = helpers.write_file, helpers.merge_args
+
+local msgpack = require('MessagePack')
+
+local tmpname = os.tmpname()
+local additional_cmd = ''
+
+local function nvim_argv()
+ local ret
+ local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', tmpname, '-N',
+ '--cmd', 'set shortmess+=I background=light noswapfile',
+ '--cmd', additional_cmd,
+ '--embed'}
+ if helpers.prepend_argv then
+ return merge_args(helpers.prepend_argv, nvim_argv)
+ else
+ return nvim_argv
+ end
+end
+
+local session = nil
+
+local reset = function()
+ if session then
+ session:exit(0)
+ end
+ session = spawn(nvim_argv())
+ set_session(session)
+ meths.set_var('tmpname', tmpname)
+end
+
+local set_additional_cmd = function(s)
+ additional_cmd = s
+end
+
+local clear = function()
+ os.remove(tmpname)
+ set_additional_cmd('')
+end
+
+local get_shada_rw = function(fname)
+ local wshada = function(text)
+ write_file(fname, text, true)
+ end
+ local sdrcmd = function(bang)
+ return 'rshada' .. (bang and '!' or '') .. ' ' .. fname
+ end
+ local clean = function()
+ os.remove(fname)
+ local i = ('a'):byte()
+ while i <= ('z'):byte() do
+ if not os.remove(fname .. ('.tmp.%c'):format(i)) then
+ break
+ end
+ i = i + 1
+ end
+ end
+ return wshada, sdrcmd, fname, clean
+end
+
+local mpack_keys = {'type', 'timestamp', 'length', 'value'}
+
+local read_shada_file = function(fname)
+ local fd = io.open(fname, 'r')
+ local mstring = fd:read('*a')
+ fd:close()
+ local unpacker = msgpack.unpacker(mstring)
+ local ret = {}
+ local cur
+ local i = 0
+ while true do
+ local off, val = unpacker()
+ if not off then break end
+ if i % 4 == 0 then
+ cur = {}
+ ret[#ret + 1] = cur
+ end
+ cur[mpack_keys[(i % 4) + 1]] = val
+ i = i + 1
+ end
+ return ret
+end
+
+return {
+ reset=reset,
+ set_additional_cmd=set_additional_cmd,
+ clear=clear,
+ exc_exec=exc_exec,
+ get_shada_rw=get_shada_rw,
+ read_shada_file=read_shada_file,
+}
diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua
new file mode 100644
index 0000000000..1123f829d2
--- /dev/null
+++ b/test/functional/shada/history_spec.lua
@@ -0,0 +1,312 @@
+-- ShaDa history saving/reading support
+local helpers = require('test.functional.helpers')
+local nvim_command, funcs, meths, nvim_feed, eq =
+ helpers.command, helpers.funcs, helpers.meths, helpers.feed, helpers.eq
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, set_additional_cmd, clear =
+ shada_helpers.reset, shada_helpers.set_additional_cmd,
+ shada_helpers.clear
+
+describe('ShaDa support code', function()
+ before_each(reset)
+ after_each(clear)
+
+ it('is able to dump and read back command-line history', function()
+ nvim_command('set shada=\'0')
+ nvim_feed(':" Test\n')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada=\'0')
+ nvim_command('rshada')
+ eq('" Test', funcs.histget(':', -1))
+ end)
+
+ it('is able to dump and read back 2 items in command-line history', function()
+ nvim_command('set shada=\'0 history=2')
+ nvim_feed(':" Test\n')
+ nvim_feed(':" Test 2\n')
+ nvim_command('qall')
+ reset()
+ nvim_command('set shada=\'0 history=2')
+ nvim_command('rshada')
+ eq('" Test 2', funcs.histget(':', -1))
+ eq('" Test', funcs.histget(':', -2))
+ nvim_command('qall')
+ end)
+
+ it('respects &history when dumping',
+ function()
+ nvim_command('set shada=\'0 history=1')
+ nvim_feed(':" Test\n')
+ nvim_feed(':" Test 2\n')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada=\'0 history=2')
+ nvim_command('rshada')
+ eq('" Test 2', funcs.histget(':', -1))
+ eq('', funcs.histget(':', -2))
+ end)
+
+ it('respects &history when loading',
+ function()
+ nvim_command('set shada=\'0 history=2')
+ nvim_feed(':" Test\n')
+ nvim_feed(':" Test 2\n')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada=\'0 history=1')
+ nvim_command('rshada')
+ eq('" Test 2', funcs.histget(':', -1))
+ eq('', funcs.histget(':', -2))
+ end)
+
+ it('dumps only requested amount of command-line history items', function()
+ nvim_command('set shada=\'0,:1')
+ nvim_feed(':" Test\n')
+ nvim_feed(':" Test 2\n')
+ nvim_command('wshada')
+ -- Regression test: :wshada should not alter or free history.
+ eq('" Test 2', funcs.histget(':', -1))
+ eq('" Test', funcs.histget(':', -2))
+ reset()
+ nvim_command('set shada=\'0')
+ nvim_command('rshada')
+ eq('" Test 2', funcs.histget(':', -1))
+ eq('', funcs.histget(':', -2))
+ end)
+
+ it('does not respect number in &shada when loading history', function()
+ nvim_command('set shada=\'0')
+ nvim_feed(':" Test\n')
+ nvim_feed(':" Test 2\n')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada=\'0,:1')
+ nvim_command('rshada')
+ eq('" Test 2', funcs.histget(':', -1))
+ eq('" Test', funcs.histget(':', -2))
+ end)
+
+ it('dumps and loads all kinds of histories', function()
+ nvim_command('debuggreedy')
+ nvim_feed(':debug echo "Test"\n" Test 2\nc\n') -- Debug history.
+ nvim_feed(':call input("")\nTest 2\n') -- Input history.
+ nvim_feed('"="Test"\nyy') -- Expression history.
+ nvim_feed('/Test\n') -- Search history
+ nvim_feed(':" Test\n') -- Command-line history
+ nvim_command('0debuggreedy')
+ nvim_command('wshada')
+ reset()
+ nvim_command('rshada')
+ eq('" Test', funcs.histget(':', -1))
+ eq('Test', funcs.histget('/', -1))
+ eq('"Test"', funcs.histget('=', -1))
+ eq('Test 2', funcs.histget('@', -1))
+ eq('c', funcs.histget('>', -1))
+ end)
+
+ it('dumps and loads last search pattern with offset', function()
+ funcs.setline('.', {'foo', 'bar'})
+ nvim_feed('gg0/a/e+1\n')
+ eq({0, 2, 3, 0}, funcs.getpos('.'))
+ nvim_command('wshada')
+ reset()
+ funcs.setline('.', {'foo', 'bar'})
+ nvim_feed('gg0n')
+ eq({0, 2, 3, 0}, funcs.getpos('.'))
+ end)
+
+ it('saves v:hlsearch=1', function()
+ nvim_command('set hlsearch shada-=h')
+ nvim_feed('/test\n')
+ eq(1, meths.get_vvar('hlsearch'))
+ nvim_command('qall')
+ reset()
+ eq(1, meths.get_vvar('hlsearch'))
+ end)
+
+ it('saves v:hlsearch=0 with :nohl', function()
+ nvim_command('set hlsearch shada-=h')
+ nvim_feed('/test\n')
+ nvim_command('nohlsearch')
+ nvim_command('qall')
+ reset()
+ eq(0, meths.get_vvar('hlsearch'))
+ end)
+
+ it('saves v:hlsearch=0 with default &shada', function()
+ nvim_command('set hlsearch')
+ nvim_feed('/test\n')
+ eq(1, meths.get_vvar('hlsearch'))
+ nvim_command('qall')
+ reset()
+ eq(0, meths.get_vvar('hlsearch'))
+ end)
+
+ it('dumps and loads last substitute pattern and replacement string', function()
+ funcs.setline('.', {'foo', 'bar'})
+ nvim_command('%s/f/g/g')
+ eq('goo', funcs.getline(1))
+ nvim_command('wshada')
+ reset()
+ funcs.setline('.', {'foo', 'bar'})
+ nvim_command('&')
+ eq('goo', funcs.getline(1))
+ end)
+
+ it('dumps and loads history correctly when &encoding is not UTF-8', function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_feed(':echo "\171"\n')
+ nvim_command('qall')
+ reset()
+ eq('echo "\171"', funcs.histget(':', -1))
+ end)
+
+ it('dumps and loads history correctly when &encoding /= UTF-8 when dumping',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_feed(':echo "\171"\n')
+ set_additional_cmd('')
+ nvim_command('qall')
+ reset()
+ eq('echo "«"', funcs.histget(':', -1))
+ end)
+
+ it('dumps and loads history correctly when &encoding /= UTF-8 when loading',
+ function()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_feed(':echo "«"\n')
+ set_additional_cmd('set encoding=latin1')
+ nvim_command('qall')
+ reset()
+ eq('echo "\171"', funcs.histget(':', -1))
+ end)
+
+ it('dumps and loads replacement correctly when &encoding is not UTF-8',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('substitute/./\171/ge')
+ nvim_command('qall!')
+ reset()
+ funcs.setline('.', {'.'})
+ nvim_command('&')
+ eq('\171', funcs.getline('.'))
+ end)
+
+ it('dumps&loads replacement correctly when &encoding /= UTF-8 when dumping',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('substitute/./\171/ge')
+ set_additional_cmd('')
+ nvim_command('qall')
+ reset()
+ funcs.setline('.', {'.'})
+ nvim_command('&')
+ eq('«', funcs.getline('.'))
+ end)
+
+ it('dumps&loads replacement correctly when &encoding /= UTF-8 when loading',
+ function()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('substitute/./«/ge')
+ set_additional_cmd('set encoding=latin1')
+ nvim_command('qall')
+ reset()
+ funcs.setline('.', {'.'})
+ nvim_command('&')
+ eq('\171', funcs.getline('.'))
+ end)
+
+ it('dumps and loads substitute pattern correctly when &encoding is not UTF-8',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('substitute/\171/./ge')
+ nvim_command('qall!')
+ reset()
+ funcs.setline('.', {'\171«'})
+ nvim_command('&')
+ eq('.«', funcs.getline('.'))
+ end)
+
+ it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when dumping',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('substitute/\171/./ge')
+ set_additional_cmd('')
+ nvim_command('qall')
+ reset()
+ funcs.setline('.', {'«\171'})
+ nvim_command('&')
+ eq('.\171', funcs.getline('.'))
+ end)
+
+ it('dumps&loads s/pattern correctly when &encoding /= UTF-8 when loading',
+ function()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('substitute/«/./ge')
+ set_additional_cmd('set encoding=latin1')
+ nvim_command('qall')
+ reset()
+ funcs.setline('.', {'\171«'})
+ nvim_command('&')
+ eq('.«', funcs.getline('.'))
+ end)
+
+ it('dumps and loads search pattern correctly when &encoding is not UTF-8',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('silent! /\171/')
+ nvim_command('set shada+=/0')
+ nvim_command('qall!')
+ reset()
+ funcs.setline('.', {'\171«'})
+ nvim_command('~&')
+ eq('«', funcs.getline('.'))
+ eq('', funcs.histget('/', -1))
+ end)
+
+ it('dumps&loads /pattern correctly when &encoding /= UTF-8 when dumping',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('silent! /\171/')
+ nvim_command('set shada+=/0')
+ set_additional_cmd('')
+ nvim_command('qall')
+ reset()
+ funcs.setline('.', {'«\171'})
+ nvim_command('~&')
+ eq('\171', funcs.getline('.'))
+ eq('', funcs.histget('/', -1))
+ end)
+
+ it('dumps&loads /pattern correctly when &encoding /= UTF-8 when loading',
+ function()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ nvim_command('silent! /«/')
+ nvim_command('set shada+=/0')
+ set_additional_cmd('set encoding=latin1')
+ nvim_command('qall')
+ reset()
+ funcs.setline('.', {'\171«'})
+ nvim_command('~&')
+ eq('«', funcs.getline('.'))
+ eq('', funcs.histget('/', -1))
+ end)
+end)
diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua
new file mode 100644
index 0000000000..b03af39662
--- /dev/null
+++ b/test/functional/shada/marks_spec.lua
@@ -0,0 +1,180 @@
+-- ShaDa marks saving/reading support
+local helpers = require('test.functional.helpers')
+local meths, curwinmeths, curbufmeths, nvim_command, funcs, eq =
+ helpers.meths, helpers.curwinmeths, helpers.curbufmeths, helpers.command,
+ helpers.funcs, helpers.eq
+local exc_exec, redir_exec = helpers.exc_exec, helpers.redir_exec
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, set_additional_cmd, clear =
+ shada_helpers.reset, shada_helpers.set_additional_cmd,
+ shada_helpers.clear
+
+local nvim_current_line = function()
+ return curwinmeths.get_cursor()[1]
+end
+
+describe('ShaDa support code', function()
+ testfilename = 'Xtestfile-functional-shada-marks'
+ testfilename_2 = 'Xtestfile-functional-shada-marks-2'
+ before_each(function()
+ reset()
+ local fd = io.open(testfilename, 'w')
+ fd:write('test\n')
+ fd:write('test2\n')
+ fd:close()
+ local fd = io.open(testfilename_2, 'w')
+ fd:write('test3\n')
+ fd:write('test4\n')
+ fd:close()
+ end)
+ after_each(function()
+ clear()
+ os.remove(testfilename)
+ os.remove(testfilename_2)
+ end)
+
+ it('is able to dump and read back global mark', function()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('mark A')
+ nvim_command('2')
+ nvim_command('kB')
+ nvim_command('wshada')
+ reset()
+ nvim_command('rshada')
+ nvim_command('normal! `A')
+ eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ eq(1, nvim_current_line())
+ nvim_command('normal! `B')
+ eq(2, nvim_current_line())
+ end)
+
+ it('does not dump global mark with `f0` in shada', function()
+ nvim_command('set shada+=f0')
+ nvim_command('edit ' .. testfilename)
+ nvim_command('mark A')
+ nvim_command('2')
+ nvim_command('kB')
+ nvim_command('wshada')
+ reset()
+ nvim_command('language C')
+ eq('Vim(normal):E20: Mark not set', exc_exec('normal! `A'))
+ end)
+
+ it('does read back global mark even with `\'0` and `f0` in shada', function()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('mark A')
+ nvim_command('2')
+ nvim_command('kB')
+ nvim_command('wshada')
+ set_additional_cmd('set shada=\'0,f0')
+ reset()
+ nvim_command('language C')
+ nvim_command('normal! `A')
+ eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ eq(1, nvim_current_line())
+ end)
+
+ it('is able to dump and read back local mark', function()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('mark a')
+ nvim_command('2')
+ nvim_command('kb')
+ nvim_command('qall')
+ reset()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('normal! `a')
+ eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ eq(1, nvim_current_line())
+ nvim_command('normal! `b')
+ eq(2, nvim_current_line())
+ end)
+
+ it('is able to populate v:oldfiles', function()
+ nvim_command('edit ' .. testfilename)
+ local tf_full = curbufmeths.get_name()
+ nvim_command('edit ' .. testfilename_2)
+ local tf_full_2 = curbufmeths.get_name()
+ nvim_command('qall')
+ reset()
+ local oldfiles = meths.get_vvar('oldfiles')
+ eq(2, #oldfiles)
+ eq(testfilename, oldfiles[1]:sub(-#testfilename))
+ eq(testfilename_2, oldfiles[2]:sub(-#testfilename_2))
+ eq(tf_full, oldfiles[1])
+ eq(tf_full_2, oldfiles[2])
+ nvim_command('rshada!')
+ local oldfiles = meths.get_vvar('oldfiles')
+ eq(2, #oldfiles)
+ eq(testfilename, oldfiles[1]:sub(-#testfilename))
+ eq(testfilename_2, oldfiles[2]:sub(-#testfilename_2))
+ eq(tf_full, oldfiles[1])
+ eq(tf_full_2, oldfiles[2])
+ end)
+
+ it('is able to dump and restore jump list', function()
+ nvim_command('edit ' .. testfilename_2)
+ nvim_command('normal! G')
+ nvim_command('normal! gg')
+ nvim_command('edit ' .. testfilename)
+ nvim_command('normal! G')
+ nvim_command('normal! gg')
+ nvim_command('enew')
+ nvim_command('normal! gg')
+ local saved = redir_exec('jumps')
+ nvim_command('qall')
+ reset()
+ eq(saved, redir_exec('jumps'))
+ end)
+
+ it('is able to dump and restore jump list with different times (slow!)',
+ function()
+ nvim_command('edit ' .. testfilename_2)
+ nvim_command('sleep 2')
+ nvim_command('normal! G')
+ nvim_command('sleep 2')
+ nvim_command('normal! gg')
+ nvim_command('sleep 2')
+ nvim_command('edit ' .. testfilename)
+ nvim_command('sleep 2')
+ nvim_command('normal! G')
+ nvim_command('sleep 2')
+ nvim_command('normal! gg')
+ nvim_command('qall')
+ reset()
+ nvim_command('redraw')
+ nvim_command('edit ' .. testfilename)
+ eq(testfilename, funcs.bufname('%'))
+ eq(1, nvim_current_line())
+ nvim_command('execute "normal! \\<C-o>"')
+ eq(testfilename, funcs.bufname('%'))
+ eq(1, nvim_current_line())
+ nvim_command('execute "normal! \\<C-o>"')
+ eq(testfilename, funcs.bufname('%'))
+ eq(2, nvim_current_line())
+ nvim_command('execute "normal! \\<C-o>"')
+ eq(testfilename_2, funcs.bufname('%'))
+ eq(1, nvim_current_line())
+ nvim_command('execute "normal! \\<C-o>"')
+ eq(testfilename_2, funcs.bufname('%'))
+ eq(2, nvim_current_line())
+ end)
+
+ it('is able to dump and restore change list', function()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('normal! Gra')
+ nvim_command('normal! ggrb')
+ nvim_command('qall!')
+ reset()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('normal! Gg;')
+ -- Note: without “sync” “commands” test has good changes to fail for unknown
+ -- reason (in first eq expected 1 is compared with 2). Any command inserted
+ -- causes this to work properly.
+ nvim_command('" sync')
+ eq(1, nvim_current_line())
+ nvim_command('normal! g;')
+ nvim_command('" sync 2')
+ eq(2, nvim_current_line())
+ end)
+end)
diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua
new file mode 100644
index 0000000000..7066ca9f54
--- /dev/null
+++ b/test/functional/shada/merging_spec.lua
@@ -0,0 +1,1032 @@
+-- ShaDa merging data support
+local helpers = require('test.functional.helpers')
+local nvim_command, meths, funcs, curbufmeths, eq =
+ helpers.command, helpers.meths, helpers.funcs,
+ helpers.curbufmeths, helpers.eq
+local exc_exec, redir_exec = helpers.exc_exec, helpers.redir_exec
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, clear, get_shada_rw =
+ shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw
+local read_shada_file = shada_helpers.read_shada_file
+
+local wshada, sdrcmd, shada_fname =
+ get_shada_rw('Xtest-functional-shada-merging.shada')
+
+describe('ShaDa history merging code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('takes item with greater timestamp from Neovim instance when reading',
+ function()
+ wshada('\004\001\009\147\000\196\002ab\196\001a')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\004\000\009\147\000\196\002ab\196\001b')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then
+ eq(1, v.timestamp)
+ eq('a', v.value[3])
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('takes item with equal timestamp from Neovim instance when reading',
+ function()
+ wshada('\004\000\009\147\000\196\002ab\196\001a')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\004\000\009\147\000\196\002ab\196\001b')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then
+ eq(0, v.timestamp)
+ eq('a', v.value[3])
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('takes item with greater timestamp from ShaDa when reading',
+ function()
+ wshada('\004\000\009\147\000\196\002ab\196\001a')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\004\001\009\147\000\196\002ab\196\001b')
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then
+ eq(1, v.timestamp)
+ eq('b', v.value[3])
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('takes item with greater timestamp from Neovim instance when writing',
+ function()
+ wshada('\004\001\009\147\000\196\002ab\196\001a')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\004\000\009\147\000\196\002ab\196\001b')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then
+ eq(1, v.timestamp)
+ eq('a', v.value[3])
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('takes item with equal timestamp from Neovim instance when writing',
+ function()
+ wshada('\004\000\009\147\000\196\002ab\196\001a')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\004\000\009\147\000\196\002ab\196\001b')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then
+ eq(0, v.timestamp)
+ eq('a', v.value[3])
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('takes item with greater timestamp from ShaDa when writing',
+ function()
+ wshada('\004\000\009\147\000\196\002ab\196\001a')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\004\001\009\147\000\196\002ab\196\001b')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 and v.value[2] == 'ab' then
+ eq(1, v.timestamp)
+ eq('b', v.value[3])
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('correctly reads history items with messed up timestamps',
+ function()
+ wshada('\004\010\009\147\000\196\002ab\196\001a'
+ .. '\004\010\009\147\000\196\002ac\196\001a'
+ .. '\004\005\009\147\000\196\002ad\196\001a'
+ .. '\004\100\009\147\000\196\002ae\196\001a'
+ .. '\004\090\009\147\000\196\002af\196\001a'
+ )
+ eq(0, exc_exec(sdrcmd()))
+ os.remove(shada_fname)
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ local items = {'ad', 'ab', 'ac', 'af', 'ae'}
+ for i, v in ipairs(items) do
+ eq(v, funcs.histget(':', i))
+ end
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 then
+ found = found + 1
+ eq(items[found], v.value[2])
+ eq('a', v.value[3])
+ end
+ end
+ eq(#items, found)
+ end)
+
+ it('correctly reorders history items with messed up timestamps when writing',
+ function()
+ wshada('\004\010\009\147\000\196\002ab\196\001a'
+ .. '\004\010\009\147\000\196\002ac\196\001a'
+ .. '\004\005\009\147\000\196\002ad\196\001a'
+ .. '\004\100\009\147\000\196\002ae\196\001a'
+ .. '\004\090\009\147\000\196\002af\196\001a'
+ )
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local items = {'ad', 'ab', 'ac', 'af', 'ae'}
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 then
+ found = found + 1
+ eq(items[found], v.value[2])
+ eq('a', v.value[3])
+ end
+ end
+ eq(#items, found)
+ end)
+
+ it('correctly merges history items with duplicate mid entry when writing',
+ function()
+ -- Regression test: ShaDa code used to crash here.
+ -- Conditions:
+ -- 1. Entry which is duplicate to non-last entry.
+ -- 2. At least one more non-duplicate entry.
+ wshada('\004\000\009\147\000\196\002ab\196\001a'
+ .. '\004\001\009\147\000\196\002ac\196\001a'
+ .. '\004\002\009\147\000\196\002ad\196\001a'
+ .. '\004\003\009\147\000\196\002ac\196\001a'
+ .. '\004\004\009\147\000\196\002af\196\001a'
+ .. '\004\005\009\147\000\196\002ae\196\001a'
+ .. '\004\006\009\147\000\196\002ag\196\001a'
+ .. '\004\007\009\147\000\196\002ah\196\001a'
+ .. '\004\008\009\147\000\196\002ai\196\001a'
+ )
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local items = {'ab', 'ad', 'ac', 'af', 'ae', 'ag', 'ah', 'ai'}
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 then
+ found = found + 1
+ eq(items[found], v.value[2])
+ eq('a', v.value[3])
+ end
+ end
+ eq(#items, found)
+ end)
+
+ it('correctly merges history items with duplicate adj entry when writing',
+ function()
+ wshada('\004\000\009\147\000\196\002ab\196\001a'
+ .. '\004\001\009\147\000\196\002ac\196\001a'
+ .. '\004\002\009\147\000\196\002ad\196\001a'
+ .. '\004\003\009\147\000\196\002ad\196\001a'
+ .. '\004\004\009\147\000\196\002af\196\001a'
+ .. '\004\005\009\147\000\196\002ae\196\001a'
+ .. '\004\006\009\147\000\196\002ag\196\001a'
+ .. '\004\007\009\147\000\196\002ah\196\001a'
+ .. '\004\008\009\147\000\196\002ai\196\001a'
+ )
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local items = {'ab', 'ac', 'ad', 'af', 'ae', 'ag', 'ah', 'ai'}
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 and v.value[1] == 0 then
+ found = found + 1
+ eq(items[found], v.value[2])
+ eq('a', v.value[3])
+ end
+ end
+ eq(#items, found)
+ end)
+end)
+
+describe('ShaDa search pattern support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('uses last search pattern with gt timestamp from instance when reading',
+ function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\000\011\130\162sX\194\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('-', funcs.getreg('/'))
+ end)
+
+ it('uses last search pattern with gt tstamp from file when reading with bang',
+ function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\000\011\130\162sX\194\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd(true)))
+ eq('?', funcs.getreg('/'))
+ end)
+
+ it('uses last search pattern with eq timestamp from instance when reading',
+ function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\001\011\130\162sX\194\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('-', funcs.getreg('/'))
+ end)
+
+ it('uses last search pattern with gt timestamp from file when reading',
+ function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\002\011\130\162sX\194\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('?', funcs.getreg('/'))
+ end)
+
+ it('uses last search pattern with gt timestamp from instance when writing',
+ function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\000\011\130\162sX\194\162sp\196\001?')
+ eq('-', funcs.getreg('/'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.sp == '-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last search pattern with eq timestamp from instance when writing',
+ function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\001\011\130\162sX\194\162sp\196\001?')
+ eq('-', funcs.getreg('/'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.sp == '-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last search pattern with gt timestamp from file when writing',
+ function()
+ wshada('\002\001\011\130\162sX\194\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\002\011\130\162sX\194\162sp\196\001?')
+ eq('-', funcs.getreg('/'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.sp == '?' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last s/ pattern with gt timestamp from instance when reading',
+ function()
+ wshada('\002\001\011\130\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\000\011\130\162ss\195\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('-', funcs.getreg('/'))
+ end)
+
+ it('uses last s/ pattern with gt timestamp from file when reading with !',
+ function()
+ wshada('\002\001\011\130\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\000\011\130\162ss\195\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd(true)))
+ eq('?', funcs.getreg('/'))
+ end)
+
+ it('uses last s/ pattern with eq timestamp from instance when reading',
+ function()
+ wshada('\002\001\011\130\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\001\011\130\162ss\195\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('-', funcs.getreg('/'))
+ end)
+
+ it('uses last s/ pattern with gt timestamp from file when reading',
+ function()
+ wshada('\002\001\011\130\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\002\011\130\162ss\195\162sp\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('?', funcs.getreg('/'))
+ end)
+
+ it('uses last s/ pattern with gt timestamp from instance when writing',
+ function()
+ wshada('\002\001\011\130\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\000\011\130\162ss\195\162sp\196\001?')
+ eq('-', funcs.getreg('/'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.sp == '-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last s/ pattern with eq timestamp from instance when writing',
+ function()
+ wshada('\002\001\011\130\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\001\011\130\162ss\195\162sp\196\001?')
+ eq('-', funcs.getreg('/'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.sp == '-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last s/ pattern with gt timestamp from file when writing',
+ function()
+ wshada('\002\001\011\130\162ss\195\162sp\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\002\002\011\130\162ss\195\162sp\196\001?')
+ eq('-', funcs.getreg('/'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 2 and v.value.sp == '?' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+end)
+
+describe('ShaDa replacement string support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('uses last replacement with gt timestamp from instance when reading',
+ function()
+ wshada('\003\001\004\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\003\000\004\145\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('s/.*/~')
+ eq('-', funcs.getline('.'))
+ nvim_command('bwipeout!')
+ end)
+
+ it('uses last replacement with gt timestamp from file when reading with bang',
+ function()
+ wshada('\003\001\004\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\003\000\004\145\196\001?')
+ eq(0, exc_exec(sdrcmd(true)))
+ nvim_command('s/.*/~')
+ eq('?', funcs.getline('.'))
+ nvim_command('bwipeout!')
+ end)
+
+ it('uses last replacement with eq timestamp from instance when reading',
+ function()
+ wshada('\003\001\004\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\003\001\004\145\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('s/.*/~')
+ eq('-', funcs.getline('.'))
+ nvim_command('bwipeout!')
+ end)
+
+ it('uses last replacement with gt timestamp from file when reading',
+ function()
+ wshada('\003\001\004\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\003\002\004\145\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('s/.*/~')
+ eq('?', funcs.getline('.'))
+ nvim_command('bwipeout!')
+ end)
+
+ it('uses last replacement with gt timestamp from instance when writing',
+ function()
+ wshada('\003\001\004\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\003\000\004\145\196\001?')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 3 and v.value[1] == '-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last replacement with eq timestamp from instance when writing',
+ function()
+ wshada('\003\001\004\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\003\001\004\145\196\001?')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 3 and v.value[1] == '-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last replacement with gt timestamp from file when writing',
+ function()
+ wshada('\003\001\004\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\003\002\004\145\196\001?')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 3 and v.value[1] == '?' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+end)
+
+describe('ShaDa marks support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('uses last A mark with gt timestamp from instance when reading',
+ function()
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('normal! `A')
+ eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ 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/a/b/-\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA')
+ eq(0, exc_exec(sdrcmd(true)))
+ nvim_command('normal! `A')
+ eq('?', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ end)
+
+ it('uses last A mark with eq timestamp from instance when reading',
+ function()
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('normal! `A')
+ eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ end)
+
+ it('uses last A mark with gt timestamp from file when reading',
+ function()
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\007\002\018\131\162mX\195\161f\196\006/a/b/?\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('normal! `A')
+ eq('?', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ end)
+
+ it('uses last A mark with gt timestamp from instance when writing',
+ function()
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\007\000\018\131\162mX\195\161f\196\006/a/b/?\161nA')
+ nvim_command('normal! `A')
+ eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ 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 == '/a/b/-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last A mark with eq timestamp from instance when writing',
+ function()
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/?\161nA')
+ nvim_command('normal! `A')
+ eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ 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 == '/a/b/-' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last A mark with gt timestamp from file when writing',
+ function()
+ wshada('\007\001\018\131\162mX\195\161f\196\006/a/b/-\161nA')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\007\002\018\131\162mX\195\161f\196\006/a/b/?\161nA')
+ nvim_command('normal! `A')
+ eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ 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 == '/a/b/?' then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last a mark with gt timestamp from instance when reading',
+ function()
+ nvim_command('edit /a/b/-')
+ funcs.setline(1, {'-', '?'})
+ wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('normal! `a')
+ eq('-', funcs.getline('.'))
+ end)
+
+ it('uses last a mark with gt timestamp from file when reading with !',
+ function()
+ nvim_command('edit /a/b/-')
+ funcs.setline(1, {'-', '?'})
+ wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd(true)))
+ nvim_command('normal! `a')
+ eq('?', funcs.getline('.'))
+ end)
+
+ it('uses last a mark with eq timestamp from instance when reading',
+ function()
+ nvim_command('edit /a/b/-')
+ funcs.setline(1, {'-', '?'})
+ wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('normal! `a')
+ eq('-', funcs.getline('.'))
+ end)
+
+ it('uses last a mark with gt timestamp from file when reading',
+ function()
+ nvim_command('edit /a/b/-')
+ funcs.setline(1, {'-', '?'})
+ wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ nvim_command('normal! `a')
+ eq('?', funcs.getline('.'))
+ end)
+
+ it('uses last a mark with gt timestamp from instance when writing',
+ function()
+ nvim_command('edit /a/b/-')
+ funcs.setline(1, {'-', '?'})
+ wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\010\000\017\131\161l\002\161f\196\006/a/b/-\161na')
+ nvim_command('normal! `a')
+ eq('-', funcs.getline('.'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then
+ eq(true, v.value.l == 1 or v.value.l == nil)
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last a mark with eq timestamp from instance when writing',
+ function()
+ nvim_command('edit /a/b/-')
+ funcs.setline(1, {'-', '?'})
+ wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\010\001\017\131\161l\002\161f\196\006/a/b/-\161na')
+ nvim_command('normal! `a')
+ eq('-', funcs.getline('.'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then
+ eq(true, v.value.l == 1 or v.value.l == nil)
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last a mark with gt timestamp from file when writing',
+ function()
+ nvim_command('edit /a/b/-')
+ funcs.setline(1, {'-', '?'})
+ wshada('\010\001\017\131\161l\001\161f\196\006/a/b/-\161na')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\010\002\017\131\161l\002\161f\196\006/a/b/-\161na')
+ nvim_command('normal! `a')
+ eq('-', funcs.fnamemodify(curbufmeths.get_name(), ':t'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 10 and v.value.f == '/a/b/-' and v.value.n == ('a'):byte() then
+ eq(2, v.value.l)
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+end)
+
+describe('ShaDa registers support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('uses last a register with gt timestamp from instance when reading',
+ function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('-', funcs.getreg('a'))
+ end)
+
+ it('uses last a register with gt timestamp from file when reading with !',
+ function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?')
+ eq(0, exc_exec(sdrcmd(true)))
+ eq('?', funcs.getreg('a'))
+ end)
+
+ it('uses last a register with eq timestamp from instance when reading',
+ function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('-', funcs.getreg('a'))
+ end)
+
+ it('uses last a register with gt timestamp from file when reading',
+ function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\005\002\015\131\161na\162rX\194\162rc\145\196\001?')
+ eq(0, exc_exec(sdrcmd()))
+ eq('?', funcs.getreg('a'))
+ end)
+
+ it('uses last a register with gt timestamp from instance when writing',
+ function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\005\000\015\131\161na\162rX\194\162rc\145\196\001?')
+ eq('-', funcs.getreg('a'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 5 and v.value.n == ('a'):byte() then
+ eq({'-'}, v.value.rc)
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last a register with eq timestamp from instance when writing',
+ function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001?')
+ eq('-', funcs.getreg('a'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 5 and v.value.n == ('a'):byte() then
+ eq({'-'}, v.value.rc)
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('uses last a register with gt timestamp from file when writing',
+ function()
+ wshada('\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\005\002\015\131\161na\162rX\194\162rc\145\196\001?')
+ eq('-', funcs.getreg('a'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 5 and v.value.n == ('a'):byte() then
+ eq({'?'}, v.value.rc)
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+end)
+
+describe('ShaDa jumps support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('merges jumps when reading', function()
+ wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002'
+ .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\002'
+ .. '\008\007\018\131\162mX\195\161f\196\006/a/b/e\161l\002')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002'
+ .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\003'
+ .. '\008\007\018\131\162mX\195\161f\196\006/a/b/f\161l\002')
+ eq(0, exc_exec(sdrcmd()))
+ eq('', curbufmeths.get_name())
+ eq('\n'
+ .. ' jump line col file/text\n'
+ .. ' 6 2 0 /a/b/c\n'
+ .. ' 5 2 0 /a/b/d\n'
+ .. ' 4 3 0 /a/b/d\n'
+ .. ' 3 2 0 /a/b/e\n'
+ .. ' 2 2 0 /a/b/f\n'
+ .. ' 1 1 0 \n'
+ .. '>', redir_exec('jumps'))
+ end)
+
+ it('merges jumps when writing', function()
+ wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002'
+ .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\002'
+ .. '\008\007\018\131\162mX\195\161f\196\006/a/b/e\161l\002')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\008\001\018\131\162mX\195\161f\196\006/a/b/c\161l\002'
+ .. '\008\004\018\131\162mX\195\161f\196\006/a/b/d\161l\003'
+ .. '\008\007\018\131\162mX\195\161f\196\006/a/b/f\161l\002')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local jumps = {
+ {file='/a/b/c', line=2},
+ {file='/a/b/d', line=2},
+ {file='/a/b/d', line=3},
+ {file='/a/b/e', line=2},
+ {file='/a/b/f', line=2},
+ }
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 8 then
+ found = found + 1
+ eq(jumps[found].file, v.value.f)
+ eq(jumps[found].line, v.value.l)
+ end
+ end
+ eq(found, #jumps)
+ end)
+
+ it('merges JUMPLISTSIZE jumps when writing', function()
+ local jumps = {}
+ local shada = ''
+ for i = 1,100 do
+ shada = shada .. ('\008%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c'
+ ):format(i, i)
+ jumps[i] = {file='/a/b/c', line=i}
+ end
+ wshada(shada)
+ eq(0, exc_exec(sdrcmd()))
+ local shada = ''
+ for i = 1,101 do
+ local t = i * 2
+ shada = shada .. (
+ '\008\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c'
+ ):format(t, t)
+ jumps[(t > #jumps + 1) and (#jumps + 1) or t] = {file='/a/b/c', line=t}
+ end
+ wshada(shada)
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local shift = #jumps - 100
+ for i = 1,100 do
+ jumps[i] = jumps[i + shift]
+ end
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 8 then
+ found = found + 1
+ eq(jumps[found].file, v.value.f)
+ eq(jumps[found].line, v.value.l)
+ end
+ end
+ eq(found, 100)
+ end)
+end)
+
+describe('ShaDa changes support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ os.remove(shada_fname)
+ end)
+
+ it('merges changes when reading', function()
+ nvim_command('edit /a/b/c')
+ nvim_command('keepjumps call setline(1, range(7))')
+ wshada('\011\001\018\131\162mX\195\161f\196\006/a/b/c\161l\001'
+ .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\002'
+ .. '\011\007\018\131\162mX\195\161f\196\006/a/b/c\161l\003')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\011\001\018\131\162mX\194\161f\196\006/a/b/c\161l\001'
+ .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\005'
+ .. '\011\008\018\131\162mX\195\161f\196\006/a/b/c\161l\004')
+ eq(0, exc_exec(sdrcmd()))
+ eq('\n'
+ .. 'change line col text\n'
+ .. ' 5 1 0 0\n'
+ .. ' 4 2 0 1\n'
+ .. ' 3 5 0 4\n'
+ .. ' 2 3 0 2\n'
+ .. ' 1 4 0 3\n'
+ .. '>', redir_exec('changes'))
+ end)
+
+ it('merges changes when writing', function()
+ nvim_command('edit /a/b/c')
+ nvim_command('keepjumps call setline(1, range(7))')
+ wshada('\011\001\018\131\162mX\195\161f\196\006/a/b/c\161l\001'
+ .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\002'
+ .. '\011\007\018\131\162mX\195\161f\196\006/a/b/c\161l\003')
+ eq(0, exc_exec(sdrcmd()))
+ wshada('\011\001\018\131\162mX\194\161f\196\006/a/b/c\161l\001'
+ .. '\011\004\018\131\162mX\195\161f\196\006/a/b/c\161l\005'
+ .. '\011\008\018\131\162mX\195\161f\196\006/a/b/c\161l\004')
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local changes = {
+ {line=1},
+ {line=2},
+ {line=5},
+ {line=3},
+ {line=4},
+ }
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 11 and v.value.f == '/a/b/c' then
+ found = found + 1
+ eq(changes[found].line, v.value.l or 1)
+ end
+ end
+ eq(found, #changes)
+ end)
+
+ it('merges JUMPLISTSIZE changes when writing', function()
+ nvim_command('edit /a/b/c')
+ nvim_command('keepjumps call setline(1, range(202))')
+ local changes = {}
+ local shada = ''
+ for i = 1,100 do
+ shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c'
+ ):format(i, i)
+ changes[i] = {line=i}
+ end
+ wshada(shada)
+ eq(0, exc_exec(sdrcmd()))
+ local shada = ''
+ for i = 1,101 do
+ local t = i * 2
+ shada = shada .. (
+ '\011\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c'
+ ):format(t, t)
+ changes[(t > #changes + 1) and (#changes + 1) or t] = {line=t}
+ end
+ wshada(shada)
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local shift = #changes - 100
+ for i = 1,100 do
+ changes[i] = changes[i + shift]
+ end
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 11 and v.value.f == '/a/b/c' then
+ found = found + 1
+ eq(changes[found].line, v.value.l)
+ end
+ end
+ eq(found, 100)
+ end)
+
+ it('merges JUMPLISTSIZE changes when writing, with new items between old',
+ function()
+ nvim_command('edit /a/b/c')
+ nvim_command('keepjumps call setline(1, range(202))')
+ local shada = ''
+ for i = 1,101 do
+ local t = i * 2
+ shada = shada .. (
+ '\011\204%c\019\131\162mX\195\161f\196\006/a/b/c\161l\204%c'
+ ):format(t, t)
+ end
+ wshada(shada)
+ eq(0, exc_exec(sdrcmd()))
+ local shada = ''
+ for i = 1,100 do
+ shada = shada .. ('\011%c\018\131\162mX\195\161f\196\006/a/b/c\161l%c'
+ ):format(i, i)
+ end
+ local changes = {}
+ for i = 1, 100 do
+ changes[i] = {line=i}
+ end
+ for i = 1, 101 do
+ local t = i * 2
+ changes[(t > #changes + 1) and (#changes + 1) or t] = {line=t}
+ end
+ wshada(shada)
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local shift = #changes - 100
+ for i = 1,100 do
+ changes[i] = changes[i + shift]
+ end
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 11 and v.value.f == '/a/b/c' then
+ found = found + 1
+ eq(changes[found].line, v.value.l)
+ end
+ end
+ eq(found, 100)
+ end)
+end)
diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua
new file mode 100644
index 0000000000..f0133b1086
--- /dev/null
+++ b/test/functional/shada/registers_spec.lua
@@ -0,0 +1,163 @@
+-- ShaDa registers saving/reading support
+local helpers = require('test.functional.helpers')
+local nvim_command, funcs, eq = helpers.command, helpers.funcs, helpers.eq
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, set_additional_cmd, clear =
+ shada_helpers.reset, shada_helpers.set_additional_cmd,
+ shada_helpers.clear
+
+local setreg = function(name, contents, typ)
+ if type(contents) == 'string' then
+ contents = {contents}
+ end
+ funcs.setreg(name, contents, typ)
+end
+
+local getreg = function(name)
+ return {
+ funcs.getreg(name, 1, 1),
+ funcs.getregtype(name),
+ }
+end
+
+describe('ShaDa support code', function()
+ before_each(reset)
+ after_each(clear)
+
+ it('is able to dump and restore registers and their type', function()
+ setreg('c', {'d', 'e', ''}, 'c')
+ setreg('l', {'a', 'b', 'cde'}, 'l')
+ setreg('b', {'bca', 'abc', 'cba'}, 'b3')
+ nvim_command('qall')
+ reset()
+ eq({{'d', 'e', ''}, 'v'}, getreg('c'))
+ eq({{'a', 'b', 'cde'}, 'V'}, getreg('l'))
+ eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b'))
+ end)
+
+ it('does not dump registers with zero <', function()
+ nvim_command('set shada=\'0,<0')
+ setreg('c', {'d', 'e', ''}, 'c')
+ setreg('l', {'a', 'b', 'cde'}, 'l')
+ setreg('b', {'bca', 'abc', 'cba'}, 'b3')
+ nvim_command('qall')
+ reset()
+ eq({nil, ''}, getreg('c'))
+ eq({nil, ''}, getreg('l'))
+ eq({nil, ''}, getreg('b'))
+ end)
+
+ it('does restore registers with zero <', function()
+ setreg('c', {'d', 'e', ''}, 'c')
+ setreg('l', {'a', 'b', 'cde'}, 'l')
+ setreg('b', {'bca', 'abc', 'cba'}, 'b3')
+ set_additional_cmd('set shada=\'0,<0')
+ nvim_command('qall')
+ reset()
+ eq({{'d', 'e', ''}, 'v'}, getreg('c'))
+ eq({{'a', 'b', 'cde'}, 'V'}, getreg('l'))
+ eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b'))
+ end)
+
+ it('does not dump registers with zero "', function()
+ nvim_command('set shada=\'0,\\"0')
+ setreg('c', {'d', 'e', ''}, 'c')
+ setreg('l', {'a', 'b', 'cde'}, 'l')
+ setreg('b', {'bca', 'abc', 'cba'}, 'b3')
+ nvim_command('qall')
+ reset()
+ eq({nil, ''}, getreg('c'))
+ eq({nil, ''}, getreg('l'))
+ eq({nil, ''}, getreg('b'))
+ end)
+
+ it('does restore registers with zero "', function()
+ setreg('c', {'d', 'e', ''}, 'c')
+ setreg('l', {'a', 'b', 'cde'}, 'l')
+ setreg('b', {'bca', 'abc', 'cba'}, 'b3')
+ set_additional_cmd('set shada=\'0,\\"0')
+ nvim_command('qall')
+ reset()
+ eq({{'d', 'e', ''}, 'v'}, getreg('c'))
+ eq({{'a', 'b', 'cde'}, 'V'}, getreg('l'))
+ eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b'))
+ end)
+
+ it('does dump registers with zero ", but non-zero <', function()
+ nvim_command('set shada=\'0,\\"0,<50')
+ setreg('c', {'d', 'e', ''}, 'c')
+ setreg('l', {'a', 'b', 'cde'}, 'l')
+ setreg('b', {'bca', 'abc', 'cba'}, 'b3')
+ nvim_command('qall')
+ reset()
+ eq({{'d', 'e', ''}, 'v'}, getreg('c'))
+ eq({{'a', 'b', 'cde'}, 'V'}, getreg('l'))
+ eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b'))
+ end)
+
+ it('does limit number of lines according to <', function()
+ nvim_command('set shada=\'0,<2')
+ setreg('o', {'d'}, 'c')
+ setreg('t', {'a', 'b', 'cde'}, 'l')
+ nvim_command('qall')
+ reset()
+ eq({{'d'}, 'v'}, getreg('o'))
+ eq({nil, ''}, getreg('t'))
+ end)
+
+ it('does limit number of lines according to "', function()
+ nvim_command('set shada=\'0,\\"2')
+ setreg('o', {'d'}, 'c')
+ setreg('t', {'a', 'b', 'cde'}, 'l')
+ nvim_command('qall')
+ reset()
+ eq({{'d'}, 'v'}, getreg('o'))
+ eq({nil, ''}, getreg('t'))
+ end)
+
+ it('does limit number of lines according to < rather then "', function()
+ nvim_command('set shada=\'0,\\"2,<3')
+ setreg('o', {'d'}, 'c')
+ setreg('t', {'a', 'b', 'cde'}, 'l')
+ setreg('h', {'abc', 'acb', 'bac', 'bca', 'cab', 'cba'}, 'b3')
+ nvim_command('qall')
+ reset()
+ eq({{'d'}, 'v'}, getreg('o'))
+ eq({{'a', 'b', 'cde'}, 'V'}, getreg('t'))
+ eq({nil, ''}, getreg('h'))
+ end)
+
+ it('dumps and loads register correctly when &encoding is not UTF-8',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ setreg('e', {'\171'}, 'c')
+ nvim_command('qall')
+ reset()
+ eq({{'\171'}, 'v'}, getreg('e'))
+ end)
+
+ it('dumps and loads history correctly when &encoding /= UTF-8 when dumping',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ setreg('e', {'\171'}, 'c')
+ set_additional_cmd('')
+ nvim_command('qall')
+ reset()
+ eq({{'«'}, 'v'}, getreg('e'))
+ end)
+
+ it('dumps and loads history correctly when &encoding /= UTF-8 when loading',
+ function()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ setreg('e', {'«'}, 'c')
+ set_additional_cmd('set encoding=latin1')
+ nvim_command('qall')
+ reset()
+ eq({{'\171'}, 'v'}, getreg('e'))
+ end)
+end)
diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua
new file mode 100644
index 0000000000..2bc855a239
--- /dev/null
+++ b/test/functional/shada/shada_spec.lua
@@ -0,0 +1,232 @@
+-- Other ShaDa tests
+local helpers = require('test.functional.helpers')
+local meths, nvim_command, funcs, eq =
+ helpers.meths, helpers.command, helpers.funcs, helpers.eq
+local write_file, spawn, set_session, nvim_prog, exc_exec =
+ helpers.write_file, helpers.spawn, helpers.set_session, helpers.nvim_prog,
+ helpers.exc_exec
+local lfs = require('lfs')
+local paths = require('test.config.paths')
+
+local msgpack = require('MessagePack')
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, clear, get_shada_rw =
+ shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw
+local read_shada_file = shada_helpers.read_shada_file
+
+local wshada, _, shada_fname, clean =
+ get_shada_rw('Xtest-functional-shada-shada.shada')
+
+describe('ShaDa support code', function()
+ before_each(reset)
+ after_each(function()
+ clear()
+ clean()
+ end)
+
+ it('preserves `s` item size limit with unknown entries', function()
+ wshada('\100\000\207\000\000\000\000\000\000\004\000\218\003\253' .. ('-'):rep(1024 - 3)
+ .. '\100\000\207\000\000\000\000\000\000\004\001\218\003\254' .. ('-'):rep(1025 - 3))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 100 then
+ found = found + 1
+ end
+ end
+ eq(2, found)
+ eq(0, exc_exec('set shada-=s10 shada+=s1'))
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 100 then
+ found = found + 1
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('preserves `s` item size limit with instance history entries', function()
+ local hist1 = ('-'):rep(1024 - 5)
+ local hist2 = ('-'):rep(1025 - 5)
+ nvim_command('set shada-=s10 shada+=s1')
+ funcs.histadd(':', hist1)
+ funcs.histadd(':', hist2)
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == 4 then
+ found = found + 1
+ eq(hist1, v.value[2])
+ end
+ end
+ eq(1, found)
+ end)
+
+ it('leaves .tmp.a in-place when there is error in original ShaDa', function()
+ wshada('Some text file')
+ eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname))
+ eq(1, read_shada_file(shada_fname .. '.tmp.a')[1].type)
+ end)
+
+ it('does not leave .tmp.a in-place when there is error in original ShaDa, but writing with bang', function()
+ wshada('Some text file')
+ eq(0, exc_exec('wshada! ' .. shada_fname))
+ eq(1, read_shada_file(shada_fname)[1].type)
+ eq(nil, lfs.attributes(shada_fname .. '.tmp.a'))
+ end)
+
+ it('leaves .tmp.b in-place when there is error in original ShaDa and it has .tmp.a', function()
+ wshada('Some text file')
+ eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname))
+ eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname))
+ eq(1, read_shada_file(shada_fname .. '.tmp.a')[1].type)
+ eq(1, read_shada_file(shada_fname .. '.tmp.b')[1].type)
+ end)
+
+ it('leaves .tmp.z in-place when there is error in original ShaDa and it has .tmp.a … .tmp.x', function()
+ wshada('Some text file')
+ local i = ('a'):byte()
+ while i < ('z'):byte() do
+ write_file(shada_fname .. ('.tmp.%c'):format(i), 'Some text file', true)
+ i = i + 1
+ end
+ eq('Vim(wshada):E576: Error while reading ShaDa file: last entry specified that it occupies 109 bytes, but file ended earlier', exc_exec('wshada ' .. shada_fname))
+ eq(1, read_shada_file(shada_fname .. '.tmp.z')[1].type)
+ end)
+
+ it('errors out when there are .tmp.a … .tmp.z ShaDa files', function()
+ wshada('')
+ local i = ('a'):byte()
+ while i <= ('z'):byte() do
+ write_file(shada_fname .. ('.tmp.%c'):format(i), '', true)
+ i = i + 1
+ end
+ eq('Vim(wshada):E138: All Xtest-functional-shada-shada.shada.tmp.X files exist, cannot write ShaDa file!', exc_exec('wshada ' .. shada_fname))
+ end)
+
+ it('reads correctly various timestamps', function()
+ local mpack = {
+ '\100', -- Positive fixnum 100
+ '\204\255', -- uint 8 255
+ '\205\010\003', -- uint 16 2563
+ '\206\255\010\030\004', -- uint 32 4278853124
+ '\207\005\100\060\250\255\010\030\004', -- uint 64 388502516579048964
+ }
+ local s = '\100'
+ local e = '\001\192'
+ wshada(s .. table.concat(mpack, e .. s) .. e)
+ eq(0, exc_exec('wshada ' .. shada_fname))
+ local found = 0
+ local typ = select(2, msgpack.unpacker(s)())
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if v.type == typ then
+ found = found + 1
+ eq(select(2, msgpack.unpacker(mpack[found])()), v.timestamp)
+ end
+ end
+ eq(#mpack, found)
+ end)
+
+ it('does not write NONE file', function()
+ local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
+ '--cmd', 'qall'}, true)
+ session:exit(0)
+ eq(nil, lfs.attributes('NONE'))
+ eq(nil, lfs.attributes('NONE.tmp.a'))
+ end)
+
+ it('does not read NONE file', function()
+ write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
+ true)
+ set_session(session)
+ eq('', funcs.getreg('a'))
+ session:exit(0)
+ os.remove('NONE')
+ end)
+
+ local marklike = {[7]=true, [8]=true, [10]=true, [11]=true}
+ local find_file = function(fname)
+ local found = {}
+ for _, v in ipairs(read_shada_file(shada_fname)) do
+ if marklike[v.type] and v.value.f == fname then
+ found[v.type] = (found[v.type] or 0) + 1
+ elseif v.type == 9 then
+ for _, b in ipairs(v.value) do
+ if b.f == fname then
+ found[v.type] = (found[v.type] or 0) + 1
+ end
+ end
+ end
+ end
+ return found
+ end
+
+ it('correctly uses shada-r option', function()
+ meths.set_var('__home', paths.test_source_path)
+ nvim_command('let $HOME = __home')
+ nvim_command('unlet __home')
+ nvim_command('edit ~/README.md')
+ nvim_command('normal! GmAggmaAabc')
+ nvim_command('undo')
+ nvim_command('set shada+=%')
+ nvim_command('wshada! ' .. shada_fname)
+ local readme_fname = paths.test_source_path .. '/README.md'
+ eq({[7]=1, [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))
+ nvim_command('set shada+=r' .. paths.test_source_path)
+ nvim_command('wshada! ' .. shada_fname)
+ eq({}, find_file(readme_fname))
+ end)
+
+ it('correctly ignores case with shada-r option', function()
+ local pwd = funcs.getcwd()
+ local relfname = 'абв/test'
+ local fname = pwd .. '/' .. relfname
+ meths.set_var('__fname', fname)
+ nvim_command('silent! edit `=__fname`')
+ funcs.setline(1, {'a', 'b', 'c', 'd'})
+ nvim_command('normal! GmAggmaAabc')
+ 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))
+ nvim_command('set shada+=r' .. pwd .. '/АБВ')
+ nvim_command('wshada! ' .. shada_fname)
+ eq({}, find_file(fname))
+ end)
+
+ it('is able to set &shada after &viminfo', function()
+ meths.set_option('viminfo', '\'10')
+ eq('\'10', meths.get_option('viminfo'))
+ eq('\'10', meths.get_option('shada'))
+ meths.set_option('shada', '')
+ eq('', meths.get_option('viminfo'))
+ eq('', meths.get_option('shada'))
+ end)
+
+ it('is able to set all& after setting &shada', function()
+ meths.set_option('shada', '\'10')
+ eq('\'10', meths.get_option('viminfo'))
+ eq('\'10', meths.get_option('shada'))
+ nvim_command('set all&')
+ eq('!,\'100,<50,s10,h', meths.get_option('viminfo'))
+ eq('!,\'100,<50,s10,h', meths.get_option('shada'))
+ end)
+
+ it('is able to set &shada after &viminfo using :set', function()
+ nvim_command('set viminfo=\'10')
+ eq('\'10', meths.get_option('viminfo'))
+ eq('\'10', meths.get_option('shada'))
+ nvim_command('set shada=')
+ eq('', meths.get_option('viminfo'))
+ eq('', meths.get_option('shada'))
+ end)
+end)
diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua
new file mode 100644
index 0000000000..6225971e5f
--- /dev/null
+++ b/test/functional/shada/variables_spec.lua
@@ -0,0 +1,139 @@
+-- ShaDa variables saving/reading support
+local helpers = require('test.functional.helpers')
+local meths, funcs, nvim_command, eq =
+ helpers.meths, helpers.funcs, helpers.command, helpers.eq
+
+local shada_helpers = require('test.functional.shada.helpers')
+local reset, set_additional_cmd, clear =
+ shada_helpers.reset, shada_helpers.set_additional_cmd,
+ shada_helpers.clear
+
+describe('ShaDa support code', function()
+ before_each(reset)
+ after_each(clear)
+
+ it('is able to dump and read back string variable', function()
+ meths.set_var('STRVAR', 'foo')
+ nvim_command('set shada+=!')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
+ eq('foo', meths.get_var('STRVAR'))
+ end)
+
+ local autotest = function(tname, varname, varval)
+ it('is able to dump and read back ' .. tname .. ' variable automatically',
+ function()
+ set_additional_cmd('set shada+=!')
+ reset()
+ meths.set_var(varname, varval)
+ -- Exit during `reset` is not a regular exit: it does not write shada
+ -- automatically
+ nvim_command('qall')
+ reset()
+ eq(varval, meths.get_var(varname))
+ end)
+ end
+
+ autotest('string', 'STRVAR', 'foo')
+ autotest('number', 'NUMVAR', 42)
+ autotest('float', 'FLTVAR', 42.5)
+ autotest('dictionary', 'DCTVAR', {a=10})
+ autotest('list', 'LSTVAR', {{a=10}, {b=10.5}, {c='str'}})
+
+ it('does not read back variables without `!` in &shada', function()
+ meths.set_var('STRVAR', 'foo')
+ nvim_command('set shada+=!')
+ nvim_command('wshada')
+ set_additional_cmd('set shada-=!')
+ reset()
+ nvim_command('rshada')
+ eq(0, funcs.exists('g:STRVAR'))
+ end)
+
+ it('does not dump variables without `!` in &shada', function()
+ nvim_command('set shada-=!')
+ meths.set_var('STRVAR', 'foo')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
+ eq(0, funcs.exists('g:STRVAR'))
+ end)
+
+ it('does not dump session variables', function()
+ nvim_command('set shada+=!')
+ meths.set_var('StrVar', 'foo')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
+ eq(0, funcs.exists('g:StrVar'))
+ end)
+
+ it('does not dump regular variables', function()
+ nvim_command('set shada+=!')
+ meths.set_var('str_var', 'foo')
+ nvim_command('wshada')
+ reset()
+ nvim_command('set shada+=!')
+ nvim_command('rshada')
+ eq(0, funcs.exists('g:str_var'))
+ end)
+
+ it('dumps and loads variables correctly when &encoding is not UTF-8',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ meths.set_var('STRVAR', '\171')
+ meths.set_var('LSTVAR', {'\171'})
+ meths.set_var('DCTVAR', {['\171']='\171'})
+ meths.set_var('NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'},
+ {a='Test'}}})
+ nvim_command('qall')
+ reset()
+ eq('\171', meths.get_var('STRVAR'))
+ eq({'\171'}, meths.get_var('LSTVAR'))
+ eq({['\171']='\171'}, meths.get_var('DCTVAR'))
+ eq({['\171']={{'\171'}, {['\171']='\171'}, {a='Test'}}},
+ meths.get_var('NESTEDVAR'))
+ end)
+
+ it('dumps and loads variables correctly when &encoding /= UTF-8 when dumping',
+ function()
+ set_additional_cmd('set encoding=latin1')
+ reset()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ meths.set_var('STRVAR', '\171')
+ meths.set_var('LSTVAR', {'\171'})
+ meths.set_var('DCTVAR', {['\171']='\171'})
+ meths.set_var('NESTEDVAR', {['\171']={{'\171'}, {['\171']='\171'},
+ {a='Test'}}})
+ set_additional_cmd('')
+ nvim_command('qall')
+ reset()
+ eq('«', meths.get_var('STRVAR'))
+ eq({'«'}, meths.get_var('LSTVAR'))
+ eq({['«']='«'}, meths.get_var('DCTVAR'))
+ eq({['«']={{'«'}, {['«']='«'}, {a='Test'}}}, meths.get_var('NESTEDVAR'))
+ end)
+
+ it('dumps and loads variables correctly when &encoding /= UTF-8 when loading',
+ function()
+ -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1
+ meths.set_var('STRVAR', '«')
+ meths.set_var('LSTVAR', {'«'})
+ meths.set_var('DCTVAR', {['«']='«'})
+ meths.set_var('NESTEDVAR', {['«']={{'«'}, {['«']='«'}, {a='Test'}}})
+ set_additional_cmd('set encoding=latin1')
+ nvim_command('qall')
+ reset()
+ eq('\171', meths.get_var('STRVAR'))
+ eq({'\171'}, meths.get_var('LSTVAR'))
+ eq({['\171']='\171'}, meths.get_var('DCTVAR'))
+ eq({['\171']={{'\171'}, {['\171']='\171'}, {a='Test'}}},
+ meths.get_var('NESTEDVAR'))
+ end)
+end)
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index 4790eb3817..c7053e6a93 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -86,8 +86,8 @@ set(LUAROCKS_SHA256 cae709111c5701235770047dfd7169f66b82ae1c7b9b79207f9df0afb722
set(UNIBILIUM_URL https://github.com/mauke/unibilium/archive/v1.2.0.tar.gz)
set(UNIBILIUM_SHA256 623af1099515e673abfd3cae5f2fa808a09ca55dda1c65a7b5c9424eb304ead8)
-set(LIBTERMKEY_URL https://github.com/neovim/libtermkey/archive/8c0cb7108cc63218ea19aa898968eede19e19603.tar.gz)
-set(LIBTERMKEY_SHA256 21846369081e6c9a0b615f4b3889c4cb809321c5ccc6e6c1640eb138f1590072)
+set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.18.tar.gz)
+set(LIBTERMKEY_SHA256 239746de41c845af52bb3c14055558f743292dd6c24ac26c2d6567a5a6093926)
set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/1b745d29d45623aa8d22a7b9288c7b0e331c7088.tar.gz)
set(LIBVTERM_SHA256 3fc75908256c0d158d6c2a32d39f34e86bfd26364f5404b7d9c03bb70cdc3611)