feat(defaults): session data in $XDG_STATE_HOME #15583

See: 4f2884e16d

- Move session persistent data to $XDG_STATE_HOME Change 'directory',
  'backupdir', 'undodir', 'viewdir' and 'shadafile' default location to
  $XDG_STATE_HOME/nvim.
- Move logs to $XDG_STATE_HOME, too.
- Add stdpath('log') support.

Fixes: #14805
This commit is contained in:
Ivan 2021-09-06 20:35:34 +03:00 committed by Justin M. Keyes
parent a1b663cce8
commit 78a1e6bc00
15 changed files with 303 additions and 224 deletions

View File

@ -188,7 +188,7 @@ loading plugins is also skipped.
Use Use
.Ar shada .Ar shada
instead of the default instead of the default
.Pa ~/.local/share/nvim/shada/main.shada . .Pa ~/.local/state/nvim/shada/main.shada .
If If
.Ar shada .Ar shada
is is
@ -326,7 +326,7 @@ Print version information and exit.
.Sh ENVIRONMENT .Sh ENVIRONMENT
.Bl -tag -width Fl .Bl -tag -width Fl
.It Ev NVIM_LOG_FILE .It Ev NVIM_LOG_FILE
Low-level log file, usually found at ~/.cache/nvim/log. Low-level log file, usually found at ~/.local/state/nvim/log.
:help $NVIM_LOG_FILE :help $NVIM_LOG_FILE
.It Ev VIM .It Ev VIM
Used to locate user files, such as init.vim. Used to locate user files, such as init.vim.
@ -340,12 +340,20 @@ Path to the user-local configuration directory, see
Defaults to Defaults to
.Pa ~/.config . .Pa ~/.config .
:help xdg :help xdg
.It Ev XDG_DATA_HOME .It Ev XDG_STATE_HOME
Like Like
.Ev XDG_CONFIG_HOME , .Ev XDG_CONFIG_HOME ,
but used to store data not generally edited by the user, but used to store data not generally edited by the user,
namely swap, backup, and ShaDa files. namely swap, backup, and ShaDa files.
Defaults to Defaults to
.Pa ~/.local/state .
:help xdg
.It Ev XDG_DATA_HOME
Like
.Ev XDG_CONFIG_HOME ,
but used to store data not generally edited by the user,
things like runtime files.
Defaults to
.Pa ~/.local/share . .Pa ~/.local/share .
:help xdg :help xdg
.It Ev VIMINIT .It Ev VIMINIT

View File

@ -7465,14 +7465,17 @@ stdpath({what}) *stdpath()* *E6100*
directories. directories.
{what} Type Description ~ {what} Type Description ~
cache String Cache directory. Arbitrary temporary cache String Cache directory: arbitrary temporary
storage for plugins, etc. storage for plugins, etc.
config String User configuration directory. The config String User configuration directory. |init.vim|
|init.vim| is stored here. is stored here.
config_dirs List Additional configuration directories. config_dirs List Other configuration directories.
data String User data directory. The |shada-file| data String User data directory. The |shada-file|
is stored here. is stored here.
data_dirs List Additional data directories. data_dirs List Other data directories.
log String Logs directory (for use by plugins too).
state String Session state directory: storage for file
drafts, undo history, shada, etc.
Example: > Example: >
:echo stdpath("config") :echo stdpath("config")

View File

@ -841,7 +841,7 @@ A jump table for the options with a short description can be found at |Q_op|.
again not rename the file. again not rename the file.
*'backupdir'* *'bdir'* *'backupdir'* *'bdir'*
'backupdir' 'bdir' string (default ".,$XDG_DATA_HOME/nvim/backup//") 'backupdir' 'bdir' string (default ".,$XDG_STATE_HOME/nvim/backup//")
global global
List of directories for the backup file, separated with commas. List of directories for the backup file, separated with commas.
- The backup file will be created in the first directory in the list - The backup file will be created in the first directory in the list
@ -2063,7 +2063,7 @@ A jump table for the options with a short description can be found at |Q_op|.
{char2}. See |digraphs|. {char2}. See |digraphs|.
*'directory'* *'dir'* *'directory'* *'dir'*
'directory' 'dir' string (default "$XDG_DATA_HOME/nvim/swap//") 'directory' 'dir' string (default "$XDG_STATE_HOME/nvim/swap//")
global global
List of directory names for the swap file, separated with commas. List of directory names for the swap file, separated with commas.
@ -3502,7 +3502,7 @@ A jump table for the options with a short description can be found at |Q_op|.
option. For '@' only characters up to 255 are used. option. For '@' only characters up to 255 are used.
Careful: If you change this option, it might break expanding Careful: If you change this option, it might break expanding
environment variables. E.g., when '/' is included and Vim tries to environment variables. E.g., when '/' is included and Vim tries to
expand "$HOME/.local/share/nvim/shada/main.shada". Maybe you should expand "$HOME/.local/state/nvim/shada/main.shada". Maybe you should
change 'iskeyword' instead. change 'iskeyword' instead.
*'iskeyword'* *'isk'* *'iskeyword'* *'isk'*
@ -4942,9 +4942,12 @@ A jump table for the options with a short description can be found at |Q_op|.
but are not part of the Nvim distribution. XDG_DATA_DIRS defaults but are not part of the Nvim distribution. XDG_DATA_DIRS defaults
to /usr/local/share/:/usr/share/, so system administrators are to /usr/local/share/:/usr/share/, so system administrators are
expected to install site plugins to /usr/share/nvim/site. expected to install site plugins to /usr/share/nvim/site.
5. $VIMRUNTIME, for files distributed with Neovim. 5. Applications state home directory, for files that contain your
session state (eg. backupdir, viewdir, undodir, etc).
Given by `stdpath("state")`. |$XDG_STATE_HOME|
6. $VIMRUNTIME, for files distributed with Neovim.
*after-directory* *after-directory*
6, 7, 8, 9. In after/ subdirectories of 1, 2, 3 and 4, with reverse 7, 8, 9, 10. In after/ subdirectories of 1, 2, 3 and 4, with reverse
ordering. This is for preferences to overrule or add to the ordering. This is for preferences to overrule or add to the
distributed defaults or system-wide settings (rarely needed). distributed defaults or system-wide settings (rarely needed).
@ -6623,7 +6626,7 @@ A jump table for the options with a short description can be found at |Q_op|.
'ttyfast' 'tf' Removed. |vim-differences| 'ttyfast' 'tf' Removed. |vim-differences|
*'undodir'* *'udir'* *E5003* *'undodir'* *'udir'* *E5003*
'undodir' 'udir' string (default "$XDG_DATA_HOME/nvim/undo//") 'undodir' 'udir' string (default "$XDG_STATE_HOME/nvim/undo//")
global global
List of directory names for undo files, separated with commas. List of directory names for undo files, separated with commas.
See 'backupdir' for details of the format. See 'backupdir' for details of the format.
@ -6786,7 +6789,7 @@ A jump table for the options with a short description can be found at |Q_op|.
displayed when 'verbosefile' is set. displayed when 'verbosefile' is set.
*'viewdir'* *'vdir'* *'viewdir'* *'vdir'*
'viewdir' 'vdir' string (default: "$XDG_DATA_HOME/nvim/view//") 'viewdir' 'vdir' string (default: "$XDG_STATE_HOME/nvim/view//")
global global
Name of the directory where to store files for |:mkview|. Name of the directory where to store files for |:mkview|.
This option cannot be set from a |modeline| or in the |sandbox|, for This option cannot be set from a |modeline| or in the |sandbox|, for

View File

@ -367,7 +367,7 @@ argument.
*--headless* *--headless*
--headless Start without UI, and do not wait for `nvim_ui_attach`. The --headless Start without UI, and do not wait for `nvim_ui_attach`. The
builtin TUI is not used, so stdio works as an arbitrary builtin TUI is not used, so stdio works as an arbitrary
communication channel. |channel-stdio| communication channel. |channel-stdio|
Also useful for scripting (tests) to see messages that would Also useful for scripting (tests) to see messages that would
not be printed by |-es|. not be printed by |-es|.
@ -584,7 +584,7 @@ setting can affect the entire editor in ways that are not initially obvious.
To find the cause of a problem in your config, you must "bisect" it: To find the cause of a problem in your config, you must "bisect" it:
1. Remove or disable half of your |config|. 1. Remove or disable half of your |config|.
2. Restart Nvim. 2. Restart Nvim.
3. If the problem still occurs, goto 1. 3. If the problem still occurs, goto 1.
4. If the problem is gone, restore half of the removed lines. 4. If the problem is gone, restore half of the removed lines.
5. Continue narrowing your config in this way, until you find the setting or 5. Continue narrowing your config in this way, until you find the setting or
plugin causing the issue. plugin causing the issue.
@ -701,7 +701,7 @@ vimrc file.
These commands will write ":map" and ":set" commands to a file, in such a way These commands will write ":map" and ":set" commands to a file, in such a way
that when these commands are executed, the current key mappings and options that when these commands are executed, the current key mappings and options
will be set to the same values. The options 'columns', 'endofline', will be set to the same values. The options 'columns', 'endofline',
'fileformat', 'lines', 'modified', and 'scroll' are not included, because 'fileformat', 'lines', 'modified', and 'scroll' are not included, because
these are terminal or file dependent. these are terminal or file dependent.
Note that the options 'binary', 'paste' and 'readonly' are included, this Note that the options 'binary', 'paste' and 'readonly' are included, this
might not always be what you want. might not always be what you want.
@ -718,7 +718,7 @@ with ":map" and ":set" commands and write the modified file. First read the
default vimrc in with a command like ":source ~piet/.vimrc.Cprogs", change default vimrc in with a command like ":source ~piet/.vimrc.Cprogs", change
the settings and then save them in the current directory with ":mkvimrc!". If the settings and then save them in the current directory with ":mkvimrc!". If
you want to make this file your default |config|, move it to you want to make this file your default |config|, move it to
$XDG_CONFIG_HOME/nvim. You could also use autocommands |autocommand| and/or $XDG_CONFIG_HOME/nvim. You could also use autocommands |autocommand| and/or
modelines |modeline|. modelines |modeline|.
*vimrc-option-example* *vimrc-option-example*
@ -886,7 +886,7 @@ Shada ("shared data") file *shada* *shada-file*
If you exit Vim and later start it again, you would normally lose a lot of If you exit Vim and later start it again, you would normally lose a lot of
information. The ShaDa file can be used to remember that information, which 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 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. SHAred DAta because it is used for sharing data between Neovim sessions.
This is introduced in section |21.3| of the user manual. This is introduced in section |21.3| of the user manual.
@ -917,9 +917,9 @@ The |v:oldfiles| variable is filled. The marks are not read in at startup
option upon startup. option upon startup.
*shada-write* *shada-write*
When Vim exits and 'shada' is non-empty, the info is stored in the ShaDa file 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|). (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 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'). be stored, and contains limits on how much should be stored (see 'shada').
Notes for Unix: Notes for Unix:
@ -977,75 +977,75 @@ remembered.
MERGING *shada-merging* MERGING *shada-merging*
When writing ShaDa files with |:wshada| without bang or at regular exit When writing ShaDa files with |:wshada| without bang or at regular exit
information in the existing ShaDa file is merged with information from current information in the existing ShaDa file is merged with information from current
Neovim instance. For this purpose ShaDa files store timestamps associated Neovim instance. For this purpose ShaDa files store timestamps associated
with ShaDa entries. Specifically the following is being done: with ShaDa entries. Specifically the following is being done:
1. History lines are merged, ordered by timestamp. Maximum amount of items in 1. History lines are merged, ordered by timestamp. Maximum amount of items in
ShaDa file is defined by 'shada' option (|shada-/|, |shada-:|, |shada-@|, ShaDa file is defined by 'shada' option (|shada-/|, |shada-:|, |shada-@|,
etc: one suboption for each character that represents history name etc: one suboption for each character that represents history name
(|:history|)). (|:history|)).
2. Local marks and changes for files that were not opened by Neovim are copied 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, to new ShaDa file. Marks for files that were opened by Neovim are merged,
changes to files opened by Neovim are ignored. |shada-'| changes to files opened by Neovim are ignored. |shada-'|
3. Jump list is merged: jumps are ordered by timestamp, identical jumps 3. Jump list is merged: jumps are ordered by timestamp, identical jumps
(identical position AND timestamp) are squashed. (identical position AND timestamp) are squashed.
4. Search patterns and substitute strings are not merged: search pattern or 4. Search patterns and substitute strings are not merged: search pattern or
substitute string which has greatest timestamp will be the only one copied substitute string which has greatest timestamp will be the only one copied
to ShaDa file. to ShaDa file.
5. For each register entity with greatest timestamp is the only saved. 5. For each register entity with greatest timestamp is the only saved.
|shada-<| |shada-<|
6. All saved variables are saved from current Neovim instance. Additionally 6. All saved variables are saved from current Neovim instance. Additionally
existing variable values are copied, meaning that the only way to remove 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 variable from a ShaDa file is either removing it by hand or disabling
writing variables completely. |shada-!| writing variables completely. |shada-!|
7. For each global mark entity with greatest timestamp is the only saved. 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 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 fashion: the only header and buffer list present are the ones from the
Neovim instance which was last writing the file. |shada-%| Neovim instance which was last writing the file. |shada-%|
COMPATIBILITY *shada-compatibility* COMPATIBILITY *shada-compatibility*
ShaDa files are forward and backward compatible. This means that ShaDa files are forward and backward compatible. This means that
1. Entries which have unknown type (i.e. that hold unidentified data) are 1. Entries which have unknown type (i.e. that hold unidentified data) are
ignored when reading and blindly copied when writing. ignored when reading and blindly copied when writing.
2. Register entries with unknown register name are ignored when reading and 2. Register entries with unknown register name are ignored when reading and
blindly copied when writing. Limitation: only registers that use name with blindly copied when writing. Limitation: only registers that use name with
code in interval [1, 255] are supported. |registers| code in interval [1, 255] are supported. |registers|
3. Register entries with unknown register type are ignored when reading and 3. Register entries with unknown register type are ignored when reading and
merged as usual when writing. |getregtype()| merged as usual when writing. |getregtype()|
4. Local and global mark entries with unknown mark names are ignored when 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 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 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 in the |shada-'| limit. Unknown local mark entry's timestamp is also taken
into account when calculating which files exactly should fit into this into account when calculating which files exactly should fit into this
limit. Limitation: only marks that use name with code in interval [1, 255] limit. Limitation: only marks that use name with code in interval [1, 255]
are supported. |mark-motions| are supported. |mark-motions|
5. History entries with unknown history type are ignored when reading and 5. History entries with unknown history type are ignored when reading and
blindly copied when writing. Limitation: there can be only up to 256 blindly copied when writing. Limitation: there can be only up to 256
history types. |history| history types. |history|
6. Unknown keys found in register, local mark, global mark, change, jump and 6. Unknown keys found in register, local mark, global mark, change, jump and
search pattern entries are saved internally and dumped when writing. search pattern entries are saved internally and dumped when writing.
Entries created during Neovim session never have such additions. Entries created during Neovim session never have such additions.
7. Additional elements found in replacement string and history entries are 7. Additional elements found in replacement string and history entries are
saved internally and dumped. Entries created during Neovim session never saved internally and dumped. Entries created during Neovim session never
have such additions. have such additions.
8. Additional elements found in variable entries are simply ignored when 8. Additional elements found in variable entries are simply ignored when
reading. When writing new variables they will be preserved during merging, reading. When writing new variables they will be preserved during merging,
but that's all. Variable values dumped from current Neovim session never but that's all. Variable values dumped from current Neovim session never
have additional elements, even if variables themselves were obtained by have additional elements, even if variables themselves were obtained by
reading ShaDa files. reading ShaDa files.
"Blindly" here means that there will be no attempts to somehow merge them, "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| even if other entries (with known name/type/etc) are merged. |shada-merging|
SHADA FILE NAME *shada-file-name* SHADA FILE NAME *shada-file-name*
- Default name of the |shada| file is: - Default name of the |shada| file is:
Unix: "$XDG_DATA_HOME/nvim/shada/main.shada" Unix: "$XDG_STATE_HOME/nvim/shada/main.shada"
Windows: "$XDG_DATA_HOME/nvim-data/shada/main.shada" Windows: "$XDG_STATE_HOME/nvim-data/shada/main.shada"
See also |base-directories|. See also |base-directories|.
- To choose a different file name you can use: - To choose a different file name you can use:
- The "n" flag in the 'shada' option. - The "n" flag in the 'shada' option.
@ -1067,55 +1067,55 @@ however that this means everything will be overwritten with information from
the first Vim, including the command line history, etc. the first Vim, including the command line history, etc.
The ShaDa file itself can be edited by hand too, although we suggest you 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 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 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 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 "~/.my.shada" which could contain certain settings that you always want when
you first start Neovim. For example, you can preload registers with you first start Neovim. For example, you can preload registers with
particular data, or put certain commands in the command line history. A line particular data, or put certain commands in the command line history. A line
in your |config| file like > in your |config| file like >
:rshada! ~/.my.shada :rshada! ~/.my.shada
can be used to load this information. You could even have different 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 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 file name, using the ":autocmd" command (see |:autocmd|). More information on
ShaDa file format is contained in |shada-format| section. ShaDa file format is contained in |shada-format| section.
*E136* *E929* *shada-error-handling* *E136* *E929* *shada-error-handling*
Some errors make Neovim leave temporary file named `{basename}.tmp.X` (X is 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, 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 write to it and then rename `{basename}.tmp.X` to `{basename}`. Such errors
include: include:
- Errors which make Neovim think that read file is not a ShaDa file at all: - 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 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 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). file" in place of "nvim -R file" (yes, somebody did that at least with Vim).
Such errors are listed at |shada-critical-contents-errors|. Such errors are listed at |shada-critical-contents-errors|.
- If writing to the temporary file failed: e.g. because of the insufficient - If writing to the temporary file failed: e.g. because of the insufficient
space left. space left.
- If renaming file failed: e.g. because of insufficient permissions. - If renaming file failed: e.g. because of insufficient permissions.
- If target ShaDa file has different from the Neovim instance's owners (user - If target ShaDa file has different from the Neovim instance's owners (user
and group) and changing them failed. Unix-specific, applies only when and group) and changing them failed. Unix-specific, applies only when
Neovim was launched from root. Neovim was launched from root.
Do not forget to remove the temporary file or replace the target file with 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 temporary one after getting one of the above errors or all attempts to create
a ShaDa file may fail with |E929|. If you got one of them when using a ShaDa file may fail with |E929|. If you got one of them when using
|:wshada| (and not when exiting Neovim: i.e. when you have Neovim session |:wshada| (and not when exiting Neovim: i.e. when you have Neovim session
running) you have additional options: running) you have additional options:
- First thing which you should consider if you got any error, except failure - 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 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. temporary file. Do it even if you have running Neovim instance.
- Fix the permissions and/or file ownership, free some space and attempt to - Fix the permissions and/or file ownership, free some space and attempt to
write again. Do not remove the existing file. write again. Do not remove the existing file.
- Use |:wshada| with bang. Does not help in case of permission error. If - 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 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, 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 but this still will loose buffer-local marks and change list entries for any
file which is not opened in the current Neovim instance. file which is not opened in the current Neovim instance.
- Remove the target file from shell and use |:wshada|. Consequences are not - 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 different from using |:wshada| with bang, but "rm -f" works in some cases
when you don't have write permissions. when you don't have write permissions.
*:rsh* *:rshada* *E886* *:rsh* *:rshada* *E886*
@ -1129,13 +1129,13 @@ running) you have additional options:
The information in the file is first read in to make The information in the file is first read in to make
a merge between old and new info. When [!] is used, a merge between old and new info. When [!] is used,
the old information is not read first, only the the old information is not read first, only the
internal info is written (also disables safety checks internal info is written (also disables safety checks
described in |shada-error-handling|). If 'shada' is described in |shada-error-handling|). If 'shada' is
empty, marks for up to 100 files will be written. empty, marks for up to 100 files will be written.
When you get error "E929: All .tmp.X files exist, When you get error "E929: All .tmp.X files exist,
cannot write ShaDa file!" check that no old temp files cannot write ShaDa file!" check that no old temp files
were left behind (e.g. were left behind (e.g.
~/.local/share/nvim/shada/main.shada.tmp*). ~/.local/state/nvim/shada/main.shada.tmp*).
Note: Executing :wshada will reset all |'quote| marks. Note: Executing :wshada will reset all |'quote| marks.
@ -1158,82 +1158,82 @@ running) you have additional options:
SHADA FILE FORMAT *shada-format* SHADA FILE FORMAT *shada-format*
ShaDa files are concats of MessagePack entries. Each entry is a concat of ShaDa files are concats of MessagePack entries. Each entry is a concat of
exactly four MessagePack objects: exactly four MessagePack objects:
1. First goes type of the entry. Object type must be an unsigned integer. 1. First goes type of the entry. Object type must be an unsigned integer.
Object type must not be equal to zero. Object type must not be equal to zero.
2. Second goes entry timestamp. It must also be an unsigned integer. 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 3. Third goes the length of the fourth entry. Unsigned integer as well, used
for fast skipping without parsing. for fast skipping without parsing.
4. Fourth is actual entry data. All currently used ShaDa entries use 4. Fourth is actual entry data. All currently used ShaDa entries use
containers to hold data: either map or array. All string values in those containers to hold data: either map or array. All string values in those
containers are either binary (applies to filenames) or UTF-8, yet parser containers are either binary (applies to filenames) or UTF-8, yet parser
needs to expect that invalid bytes may be present in a UTF-8 string. needs to expect that invalid bytes may be present in a UTF-8 string.
Exact format depends on the entry type: Exact format depends on the entry type:
Entry type (name) Entry data ~ Entry type (name) Entry data ~
1 (Header) Map containing data that describes the generator 1 (Header) Map containing data that describes the generator
instance that wrote this ShaDa file. It is ignored instance that wrote this ShaDa file. It is ignored
when reading ShaDa files. Contains the following data: when reading ShaDa files. Contains the following data:
Key Data ~ Key Data ~
generator Binary, software used to generate ShaDa generator Binary, software used to generate ShaDa
file. Is equal to "nvim" when ShaDa file was file. Is equal to "nvim" when ShaDa file was
written by Neovim. written by Neovim.
version Binary, generator version. version Binary, generator version.
encoding Binary, effective 'encoding' value. encoding Binary, effective 'encoding' value.
max_kbyte Integer, effective |shada-s| limit value. max_kbyte Integer, effective |shada-s| limit value.
pid Integer, instance process ID. pid Integer, instance process ID.
* It is allowed to have any number of * It is allowed to have any number of
additional keys with any data. additional keys with any data.
2 (SearchPattern) Map containing data describing last used search or 2 (SearchPattern) Map containing data describing last used search or
substitute pattern. Normally ShaDa file contains two substitute pattern. Normally ShaDa file contains two
such entries: one with "ss" key set to true (describes such entries: one with "ss" key set to true (describes
substitute pattern, see |:substitute|), and one set to substitute pattern, see |:substitute|), and one set to
false (describes search pattern, see false (describes search pattern, see
|search-commands|). "su" key should be true on one of |search-commands|). "su" key should be true on one of
the entries. If key value is equal to default then it the entries. If key value is equal to default then it
is normally not present. Keys: is normally not present. Keys:
Key Type Default Description ~ Key Type Default Description ~
sm Boolean true Effective 'magic' value. sm Boolean true Effective 'magic' value.
sc Boolean false Effective 'smartcase' value. sc Boolean false Effective 'smartcase' value.
sl Boolean true True if search pattern comes sl Boolean true True if search pattern comes
with a line offset. See with a line offset. See
|search-offset|. |search-offset|.
se Boolean false True if |search-offset| se Boolean false True if |search-offset|
requested to place cursor at requested to place cursor at
(relative to) the end of the (relative to) the end of the
pattern. pattern.
so Integer 0 Offset value. |search-offset| so Integer 0 Offset value. |search-offset|
su Boolean false True if current entry was the su Boolean false True if current entry was the
last used search pattern. last used search pattern.
ss Boolean false True if current entry describes ss Boolean false True if current entry describes
|:substitute| pattern. |:substitute| pattern.
sh Boolean false True if |v:hlsearch| is on. sh Boolean false True if |v:hlsearch| is on.
With |shada-h| or 'nohlsearch' With |shada-h| or 'nohlsearch'
this key is always false. this key is always false.
sp Binary N/A Actual pattern. Required. sp Binary N/A Actual pattern. Required.
sb Boolean false True if search direction is sb Boolean false True if search direction is
backward. backward.
* any none Other keys are allowed for * any none Other keys are allowed for
compatibility reasons, see compatibility reasons, see
|shada-compatibility|. |shada-compatibility|.
3 (SubString) Array containing last |:substitute| replacement string. 3 (SubString) Array containing last |:substitute| replacement string.
Contains single entry: binary, replacement string used. Contains single entry: binary, replacement string used.
More entries are allowed for compatibility reasons, see More entries are allowed for compatibility reasons, see
|shada-compatibility|. |shada-compatibility|.
4 (HistoryEntry) Array containing one entry from history. Should have 4 (HistoryEntry) Array containing one entry from history. Should have
two or three entries. First one is history type two or three entries. First one is history type
(unsigned integer), second is history line (binary), (unsigned integer), second is history line (binary),
third is the separator character (unsigned integer, third is the separator character (unsigned integer,
must be in interval [0, 255]). Third item is only must be in interval [0, 255]). Third item is only
valid for search history. Possible history types are valid for search history. Possible history types are
listed in |hist-names|, here are the corresponding listed in |hist-names|, here are the corresponding
numbers: 0 - cmd, 1 - search, 2 - expr, 3 - input, numbers: 0 - cmd, 1 - search, 2 - expr, 3 - input,
4 - debug. 4 - debug.
5 (Register) Map describing one register (|registers|). If key 5 (Register) Map describing one register (|registers|). If key
value is equal to default then it is normally not value is equal to default then it is normally not
present. Keys: present. Keys:
Key Type Def Description ~ Key Type Def Description ~
rt UInteger 0 Register type: rt UInteger 0 Register type:
@ -1261,12 +1261,12 @@ exactly four MessagePack objects:
* any none Other keys are allowed * any none Other keys are allowed
for compatibility reasons, for compatibility reasons,
see |shada-compatibility|. see |shada-compatibility|.
6 (Variable) Array containing two items: variable name (binary) and 6 (Variable) Array containing two items: variable name (binary) and
variable value (any object). Values are converted variable value (any object). Values are converted
using the same code |msgpackparse()| uses when reading, using the same code |msgpackparse()| uses when reading,
|msgpackdump()| when writing, so there may appear |msgpackdump()| when writing, so there may appear
|msgpack-special-dict|s. If there are more then two |msgpack-special-dict|s. If there are more then two
entries then the rest are ignored entries then the rest are ignored
(|shada-compatibility|). (|shada-compatibility|).
7 (GlobalMark) 7 (GlobalMark)
8 (Jump) 8 (Jump)
@ -1280,57 +1280,57 @@ exactly four MessagePack objects:
Data contained in the map: Data contained in the map:
Key Type Default Description ~ Key Type Default Description ~
l UInteger 1 Position line number. Must be l UInteger 1 Position line number. Must be
greater then zero. greater then zero.
c UInteger 0 Position column number. c UInteger 0 Position column number.
n UInteger 34 ('"') Mark name. Only valid for n UInteger 34 ('"') Mark name. Only valid for
GlobalMark and LocalMark GlobalMark and LocalMark
entries. entries.
f Binary N/A File name. Required. f Binary N/A File name. Required.
* any none Other keys are allowed for * any none Other keys are allowed for
compatibility reasons, see compatibility reasons, see
|shada-compatibility|. |shada-compatibility|.
9 (BufferList) Array containing maps. Each map in the array 9 (BufferList) Array containing maps. Each map in the array
represents one buffer. Possible keys: represents one buffer. Possible keys:
Key Type Default Description ~ Key Type Default Description ~
l UInteger 1 Position line number. Must be l UInteger 1 Position line number. Must be
greater then zero. greater then zero.
c UInteger 0 Position column number. c UInteger 0 Position column number.
f Binary N/A File name. Required. f Binary N/A File name. Required.
* any none Other keys are allowed for * any none Other keys are allowed for
compatibility reasons, see compatibility reasons, see
|shada-compatibility|. |shada-compatibility|.
* (Unknown) Any other entry type is allowed for compatibility * (Unknown) Any other entry type is allowed for compatibility
reasons, see |shada-compatibility|. reasons, see |shada-compatibility|.
*E575* *E576* *E575* *E576*
Errors in ShaDa file may have two types: E575 used for all “logical” errors 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 and E576 used for all “critical” errors. Critical errors trigger behaviour
described in |shada-error-handling| when writing and skipping the rest of the described in |shada-error-handling| when writing and skipping the rest of the
file when reading and include: file when reading and include:
*shada-critical-contents-errors* *shada-critical-contents-errors*
- Any of first three MessagePack objects being not an unsigned integer. - Any of first three MessagePack objects being not an unsigned integer.
- Third object requesting amount of bytes greater then bytes left in the ShaDa - Third object requesting amount of bytes greater then bytes left in the ShaDa
file. file.
- Entry with zero type. I.e. first object being equal to zero. - Entry with zero type. I.e. first object being equal to zero.
- MessagePack parser failing to parse the entry data. - MessagePack parser failing to parse the entry data.
- MessagePack parser consuming less or requesting greater bytes then described - MessagePack parser consuming less or requesting greater bytes then described
in the third object for parsing fourth object. I.e. when fourth object 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 either contains more then one MessagePack object or it does not contain
complete MessagePack object. complete MessagePack object.
============================================================================== ==============================================================================
Standard Paths *standard-path* Standard Paths *standard-path*
Nvim stores configuration, data, and logs in standard locations. Plugins are Nvim stores configuration, data, and logs in standard locations. Plugins are
strongly encouraged to follow this pattern also. Use |stdpath()| to get the strongly encouraged to follow this pattern also. Use |stdpath()| to get the
paths. paths.
*base-directories* *xdg* *base-directories* *xdg*
The "base" (root) directories conform to the XDG Base Directory Specification. The "base" (root) directories conform to the XDG Base Directory Specification.
https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
The $XDG_CONFIG_HOME and $XDG_DATA_HOME environment variables are used if they The $XDG_CONFIG_HOME, $XDG_DATA_HOME and $XDG_STATE_HOME environment variables
exist, otherwise default values (listed below) are used. are used if they exist, otherwise default values (listed below) are used.
CONFIG DIRECTORY (DEFAULT) ~ CONFIG DIRECTORY (DEFAULT) ~
*$XDG_CONFIG_HOME* Nvim: stdpath("config") *$XDG_CONFIG_HOME* Nvim: stdpath("config")
@ -1342,6 +1342,11 @@ DATA DIRECTORY (DEFAULT) ~
Unix: ~/.local/share ~/.local/share/nvim Unix: ~/.local/share ~/.local/share/nvim
Windows: ~/AppData/Local ~/AppData/Local/nvim-data Windows: ~/AppData/Local ~/AppData/Local/nvim-data
STATE DIRECTORY (DEFAULT) ~
*$XDG_STATE_HOME* Nvim: stdpath("state")
Unix: ~/.local/state ~/.local/state/nvim
Windows: ~/AppData/Local ~/AppData/Local/nvim-data
Note: Throughout the user manual these defaults are used as placeholders, e.g. Note: Throughout the user manual these defaults are used as placeholders, e.g.
"~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config". "~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config".
@ -1349,7 +1354,7 @@ LOG FILE *$NVIM_LOG_FILE*
Besides 'debug' and 'verbose', Nvim keeps a general log file for internal Besides 'debug' and 'verbose', Nvim keeps a general log file for internal
debugging, plugins and RPC clients. > debugging, plugins and RPC clients. >
:echo $NVIM_LOG_FILE :echo $NVIM_LOG_FILE
By default, the file is located at stdpath('cache')/log unless that path By default, the file is located at stdpath('log')/log unless that path
is inaccessible or if $NVIM_LOG_FILE was set before |startup|. is inaccessible or if $NVIM_LOG_FILE was set before |startup|.

View File

@ -352,12 +352,12 @@ another session.
this yourself then. Example: > this yourself then. Example: >
:mksession! ~/.config/nvim/secret.vim :mksession! ~/.config/nvim/secret.vim
:wshada! ~/.local/share/nvim/shada/secret.shada :wshada! ~/.local/state/nvim/shada/secret.shada
And to restore this again: > And to restore this again: >
:source ~/.config/nvim/secret.vim :source ~/.config/nvim/secret.vim
:rshada! ~/.local/share/nvim/shada/secret.shada :rshada! ~/.local/state/nvim/shada/secret.shada
============================================================================== ==============================================================================
*21.5* Views *21.5* Views

View File

@ -17,7 +17,7 @@ centralized reference of the differences.
- Use `$XDG_CONFIG_HOME/nvim/init.vim` instead of `.vimrc` for your |config|. - Use `$XDG_CONFIG_HOME/nvim/init.vim` instead of `.vimrc` for your |config|.
- Use `$XDG_CONFIG_HOME/nvim` instead of `.vim` to store configuration files. - Use `$XDG_CONFIG_HOME/nvim` instead of `.vim` to store configuration files.
- Use `$XDG_DATA_HOME/nvim/shada/main.shada` instead of `.viminfo` for persistent - Use `$XDG_STATE_HOME/nvim/shada/main.shada` instead of `.viminfo` for persistent
session information. |shada| session information. |shada|
============================================================================== ==============================================================================
@ -32,12 +32,12 @@ centralized reference of the differences.
- 'autoread' is enabled - 'autoread' is enabled
- 'background' defaults to "dark" (unless set automatically by the terminal/UI) - 'background' defaults to "dark" (unless set automatically by the terminal/UI)
- 'backspace' defaults to "indent,eol,start" - 'backspace' defaults to "indent,eol,start"
- 'backupdir' defaults to .,~/.local/share/nvim/backup// (|xdg|), auto-created - 'backupdir' defaults to .,~/.local/state/nvim/backup// (|xdg|), auto-created
- 'belloff' defaults to "all" - 'belloff' defaults to "all"
- 'compatible' is always disabled - 'compatible' is always disabled
- 'complete' excludes "i" - 'complete' excludes "i"
- 'cscopeverbose' is enabled - 'cscopeverbose' is enabled
- 'directory' defaults to ~/.local/share/nvim/swap// (|xdg|), auto-created - 'directory' defaults to ~/.local/state/nvim/swap// (|xdg|), auto-created
- 'display' defaults to "lastline,msgsep" - 'display' defaults to "lastline,msgsep"
- 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding) - 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding)
- 'fillchars' defaults (in effect) to "vert:│,fold:·,sep:│" - 'fillchars' defaults (in effect) to "vert:│,fold:·,sep:│"
@ -65,7 +65,7 @@ centralized reference of the differences.
- 'tags' defaults to "./tags;,tags" - 'tags' defaults to "./tags;,tags"
- 'ttimeoutlen' defaults to 50 - 'ttimeoutlen' defaults to 50
- 'ttyfast' is always set - 'ttyfast' is always set
- 'undodir' defaults to ~/.local/share/nvim/undo// (|xdg|), auto-created - 'undodir' defaults to ~/.local/state/nvim/undo// (|xdg|), auto-created
- 'viewoptions' includes "unix,slash", excludes "options" - 'viewoptions' includes "unix,slash", excludes "options"
- 'viminfo' includes "!" - 'viminfo' includes "!"
- 'wildmenu' is enabled - 'wildmenu' is enabled

View File

@ -25,12 +25,12 @@ do
local function path_join(...) local function path_join(...)
return table.concat(vim.tbl_flatten({ ... }), path_sep) return table.concat(vim.tbl_flatten({ ... }), path_sep)
end end
local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log') local logfilename = path_join(vim.fn.stdpath('log'), 'lsp.log')
-- TODO: Ideally the directory should be created in open_logfile(), right -- TODO: Ideally the directory should be created in open_logfile(), right
-- before opening the log file, but open_logfile() can be called from libuv -- before opening the log file, but open_logfile() can be called from libuv
-- callbacks, where using fn.mkdir() is not allowed. -- callbacks, where using fn.mkdir() is not allowed.
vim.fn.mkdir(vim.fn.stdpath('cache'), 'p') vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
--- Returns the log filename. --- Returns the log filename.
---@returns (string) log filename ---@returns (string) log filename

View File

@ -38,7 +38,7 @@ alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires
Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to
filter the log, e.g. at DEBUG level you might want to exclude UI messages: filter the log, e.g. at DEBUG level you might want to exclude UI messages:
tail -F ~/.cache/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log tail -F ~/.local/state/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log
Build with ASAN Build with ASAN
--------------- ---------------

View File

@ -9842,6 +9842,10 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_string = get_xdg_home(kXDGDataHome); rettv->vval.v_string = get_xdg_home(kXDGDataHome);
} else if (strequal(p, "cache")) { } else if (strequal(p, "cache")) {
rettv->vval.v_string = get_xdg_home(kXDGCacheHome); rettv->vval.v_string = get_xdg_home(kXDGCacheHome);
} else if (strequal(p, "state")) {
rettv->vval.v_string = get_xdg_home(kXDGStateHome);
} else if (strequal(p, "log")) {
rettv->vval.v_string = get_xdg_home(kXDGStateHome);
} else if (strequal(p, "config_dirs")) { } else if (strequal(p, "config_dirs")) {
get_xdg_var_list(kXDGConfigDirs, rettv); get_xdg_var_list(kXDGConfigDirs, rettv);
} else if (strequal(p, "data_dirs")) { } else if (strequal(p, "data_dirs")) {

View File

@ -51,7 +51,7 @@ static bool log_try_create(char *fname)
/// Initializes path to log file. Sets $NVIM_LOG_FILE if empty. /// Initializes path to log file. Sets $NVIM_LOG_FILE if empty.
/// ///
/// Tries $NVIM_LOG_FILE, or falls back to $XDG_CACHE_HOME/nvim/log. Path to log /// Tries $NVIM_LOG_FILE, or falls back to $XDG_STATE_HOME/nvim/log. Path to log
/// file is cached, so only the first call has effect, unless first call was not /// file is cached, so only the first call has effect, unless first call was not
/// successful. Failed initialization indicates either a bug in expand_env() /// successful. Failed initialization indicates either a bug in expand_env()
/// or both $NVIM_LOG_FILE and $HOME environment variables are undefined. /// or both $NVIM_LOG_FILE and $HOME environment variables are undefined.
@ -69,16 +69,16 @@ static bool log_path_init(void)
|| log_file_path[0] == '\0' || log_file_path[0] == '\0'
|| os_isdir((char_u *)log_file_path) || os_isdir((char_u *)log_file_path)
|| !log_try_create(log_file_path)) { || !log_try_create(log_file_path)) {
// Make kXDGCacheHome if it does not exist. // Make kXDGStateHome if it does not exist.
char *cachehome = get_xdg_home(kXDGCacheHome); char *loghome = get_xdg_home(kXDGStateHome);
char *failed_dir = NULL; char *failed_dir = NULL;
bool log_dir_failure = false; bool log_dir_failure = false;
if (!os_isdir((char_u *)cachehome)) { if (!os_isdir((char_u *)loghome)) {
log_dir_failure = (os_mkdir_recurse(cachehome, 0700, &failed_dir) != 0); log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0);
} }
XFREE_CLEAR(cachehome); XFREE_CLEAR(loghome);
// Invalid $NVIM_LOG_FILE or failed to expand; fall back to default. // Invalid $NVIM_LOG_FILE or failed to expand; fall back to default.
char *defaultpath = stdpaths_user_cache_subpath("log"); char *defaultpath = stdpaths_user_state_subpath("log", 0, true);
size_t len = xstrlcpy(log_file_path, defaultpath, size); size_t len = xstrlcpy(log_file_path, defaultpath, size);
xfree(defaultpath); xfree(defaultpath);
// Fall back to .nvimlog // Fall back to .nvimlog

View File

@ -491,17 +491,17 @@ void set_init_1(bool clean_arg)
#endif #endif
false); false);
char *backupdir = stdpaths_user_data_subpath("backup", 2, true); char *backupdir = stdpaths_user_state_subpath("backup", 2, true);
const size_t backupdir_len = strlen(backupdir); const size_t backupdir_len = strlen(backupdir);
backupdir = xrealloc(backupdir, backupdir_len + 3); backupdir = xrealloc(backupdir, backupdir_len + 3);
memmove(backupdir + 2, backupdir, backupdir_len + 1); memmove(backupdir + 2, backupdir, backupdir_len + 1);
memmove(backupdir, ".,", 2); memmove(backupdir, ".,", 2);
set_string_default("backupdir", backupdir, true); set_string_default("backupdir", backupdir, true);
set_string_default("viewdir", stdpaths_user_data_subpath("view", 2, true), set_string_default("viewdir", stdpaths_user_state_subpath("view", 2, true),
true); true);
set_string_default("directory", stdpaths_user_data_subpath("swap", 2, true), set_string_default("directory", stdpaths_user_state_subpath("swap", 2, true),
true); true);
set_string_default("undodir", stdpaths_user_data_subpath("undo", 2, true), set_string_default("undodir", stdpaths_user_state_subpath("undo", 2, true),
true); true);
// Set default for &runtimepath. All necessary expansions are performed in // Set default for &runtimepath. All necessary expansions are performed in
// this function. // this function.

View File

@ -14,6 +14,7 @@ static const char *xdg_env_vars[] = {
[kXDGConfigHome] = "XDG_CONFIG_HOME", [kXDGConfigHome] = "XDG_CONFIG_HOME",
[kXDGDataHome] = "XDG_DATA_HOME", [kXDGDataHome] = "XDG_DATA_HOME",
[kXDGCacheHome] = "XDG_CACHE_HOME", [kXDGCacheHome] = "XDG_CACHE_HOME",
[kXDGStateHome] = "XDG_STATE_HOME",
[kXDGRuntimeDir] = "XDG_RUNTIME_DIR", [kXDGRuntimeDir] = "XDG_RUNTIME_DIR",
[kXDGConfigDirs] = "XDG_CONFIG_DIRS", [kXDGConfigDirs] = "XDG_CONFIG_DIRS",
[kXDGDataDirs] = "XDG_DATA_DIRS", [kXDGDataDirs] = "XDG_DATA_DIRS",
@ -24,6 +25,7 @@ static const char *const xdg_defaults_env_vars[] = {
[kXDGConfigHome] = "LOCALAPPDATA", [kXDGConfigHome] = "LOCALAPPDATA",
[kXDGDataHome] = "LOCALAPPDATA", [kXDGDataHome] = "LOCALAPPDATA",
[kXDGCacheHome] = "TEMP", [kXDGCacheHome] = "TEMP",
[kXDGStateHome] = "LOCALAPPDATA",
[kXDGRuntimeDir] = NULL, [kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = NULL, [kXDGConfigDirs] = NULL,
[kXDGDataDirs] = NULL, [kXDGDataDirs] = NULL,
@ -38,6 +40,7 @@ static const char *const xdg_defaults[] = {
[kXDGConfigHome] = "~\\AppData\\Local", [kXDGConfigHome] = "~\\AppData\\Local",
[kXDGDataHome] = "~\\AppData\\Local", [kXDGDataHome] = "~\\AppData\\Local",
[kXDGCacheHome] = "~\\AppData\\Local\\Temp", [kXDGCacheHome] = "~\\AppData\\Local\\Temp",
[kXDGStateHome] = "~\\AppData\\Local",
[kXDGRuntimeDir] = NULL, [kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = NULL, [kXDGConfigDirs] = NULL,
[kXDGDataDirs] = NULL, [kXDGDataDirs] = NULL,
@ -45,6 +48,7 @@ static const char *const xdg_defaults[] = {
[kXDGConfigHome] = "~/.config", [kXDGConfigHome] = "~/.config",
[kXDGDataHome] = "~/.local/share", [kXDGDataHome] = "~/.local/share",
[kXDGCacheHome] = "~/.cache", [kXDGCacheHome] = "~/.cache",
[kXDGStateHome] = "~/.local/state",
[kXDGRuntimeDir] = NULL, [kXDGRuntimeDir] = NULL,
[kXDGConfigDirs] = "/etc/xdg/", [kXDGConfigDirs] = "/etc/xdg/",
[kXDGDataDirs] = "/usr/local/share/:/usr/share/", [kXDGDataDirs] = "/usr/local/share/:/usr/share/",
@ -133,15 +137,26 @@ char *stdpaths_user_conf_subpath(const char *fname)
/// Return subpath of $XDG_DATA_HOME /// Return subpath of $XDG_DATA_HOME
/// ///
/// @param[in] fname New component of the path. /// @param[in] fname New component of the path.
///
/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`
char *stdpaths_user_data_subpath(const char *fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
return concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true);
}
/// Return subpath of $XDG_STATE_HOME
///
/// @param[in] fname New component of the path.
/// @param[in] trailing_pathseps Amount of trailing path separators to add. /// @param[in] trailing_pathseps Amount of trailing path separators to add.
/// @param[in] escape_commas If true, all commas will be escaped. /// @param[in] escape_commas If true, all commas will be escaped.
/// ///
/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`. /// @return [allocated] `$XDG_STATE_HOME/nvim/{fname}`.
char *stdpaths_user_data_subpath(const char *fname, const size_t trailing_pathseps, char *stdpaths_user_state_subpath(const char *fname, const size_t trailing_pathseps,
const bool escape_commas) const bool escape_commas)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{ {
char *ret = concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true); char *ret = concat_fnames_realloc(get_xdg_home(kXDGStateHome), fname, true);
const size_t len = strlen(ret); const size_t len = strlen(ret);
const size_t numcommas = (escape_commas ? memcnt(ret, ',', len) : 0); const size_t numcommas = (escape_commas ? memcnt(ret, ',', len) : 0);
if (numcommas || trailing_pathseps) { if (numcommas || trailing_pathseps) {

View File

@ -7,6 +7,7 @@ typedef enum {
kXDGConfigHome, ///< XDG_CONFIG_HOME kXDGConfigHome, ///< XDG_CONFIG_HOME
kXDGDataHome, ///< XDG_DATA_HOME kXDGDataHome, ///< XDG_DATA_HOME
kXDGCacheHome, ///< XDG_CACHE_HOME kXDGCacheHome, ///< XDG_CACHE_HOME
kXDGStateHome, ///< XDG_STATE_HOME
kXDGRuntimeDir, ///< XDG_RUNTIME_DIR kXDGRuntimeDir, ///< XDG_RUNTIME_DIR
kXDGConfigDirs, ///< XDG_CONFIG_DIRS kXDGConfigDirs, ///< XDG_CONFIG_DIRS
kXDGDataDirs, ///< XDG_DATA_DIRS kXDGDataDirs, ///< XDG_DATA_DIRS

View File

@ -1447,7 +1447,7 @@ static const char *shada_get_default_file(void)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_WARN_UNUSED_RESULT
{ {
if (default_shada_file == NULL) { if (default_shada_file == NULL) {
char *shada_dir = stdpaths_user_data_subpath("shada", 0, false); char *shada_dir = stdpaths_user_state_subpath("shada", 0, false);
default_shada_file = concat_fnames_realloc(shada_dir, "main.shada", true); default_shada_file = concat_fnames_realloc(shada_dir, "main.shada", true);
} }
return default_shada_file; return default_shada_file;

View File

@ -163,7 +163,7 @@ describe('startup defaults', function()
end) end)
it("'shadafile' ('viminfofile')", function() it("'shadafile' ('viminfofile')", function()
local env = {XDG_DATA_HOME='Xtest-userdata', XDG_CONFIG_HOME='Xtest-userconfig'} local env = {XDG_DATA_HOME='Xtest-userdata', XDG_STATE_HOME='Xtest-userstate', XDG_CONFIG_HOME='Xtest-userconfig'}
clear{args={}, args_rm={'-i'}, env=env} clear{args={}, args_rm={'-i'}, env=env}
-- Default 'shadafile' is empty. -- Default 'shadafile' is empty.
-- This means use the default location. :help shada-file-name -- This means use the default location. :help shada-file-name
@ -178,7 +178,7 @@ describe('startup defaults', function()
clear{args={}, args_rm={'-i'}, env=env} clear{args={}, args_rm={'-i'}, env=env}
eq({ f }, eval('v:oldfiles')) eq({ f }, eval('v:oldfiles'))
os.remove('Xtest-foo') os.remove('Xtest-foo')
rmdir('Xtest-userdata') rmdir('Xtest-userstate')
-- Handles viminfo/viminfofile as alias for shada/shadafile. -- Handles viminfo/viminfofile as alias for shada/shadafile.
eq('\n shadafile=', eval('execute("set shadafile?")')) eq('\n shadafile=', eval('execute("set shadafile?")'))
@ -206,7 +206,7 @@ describe('startup defaults', function()
describe('$NVIM_LOG_FILE', function() describe('$NVIM_LOG_FILE', function()
local xdgdir = 'Xtest-startup-xdg-logpath' local xdgdir = 'Xtest-startup-xdg-logpath'
local xdgcachedir = xdgdir..'/nvim' local xdgstatedir = xdgdir..'/nvim'
after_each(function() after_each(function()
os.remove('Xtest-logpath') os.remove('Xtest-logpath')
rmdir(xdgdir) rmdir(xdgdir)
@ -218,21 +218,21 @@ describe('startup defaults', function()
}}) }})
eq('Xtest-logpath', eval('$NVIM_LOG_FILE')) eq('Xtest-logpath', eval('$NVIM_LOG_FILE'))
end) end)
it('defaults to stdpath("cache")/log if empty', function() it('defaults to stdpath("log")/log if empty', function()
eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) eq(true, mkdir(xdgdir) and mkdir(xdgstatedir))
clear({env={ clear({env={
XDG_CACHE_HOME=xdgdir, XDG_STATE_HOME=xdgdir,
NVIM_LOG_FILE='', -- Empty is invalid. NVIM_LOG_FILE='', -- Empty is invalid.
}}) }})
eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) eq(xdgstatedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
end) end)
it('defaults to stdpath("cache")/log if invalid', function() it('defaults to stdpath("log")/log if invalid', function()
eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) eq(true, mkdir(xdgdir) and mkdir(xdgstatedir))
clear({env={ clear({env={
XDG_CACHE_HOME=xdgdir, XDG_STATE_HOME=xdgdir,
NVIM_LOG_FILE='.', -- Any directory is invalid. NVIM_LOG_FILE='.', -- Any directory is invalid.
}}) }})
eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) eq(xdgstatedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
end) end)
end) end)
end) end)
@ -264,6 +264,7 @@ describe('XDG-based defaults', function()
XDG_CONFIG_HOME=nil, XDG_CONFIG_HOME=nil,
XDG_DATA_HOME=nil, XDG_DATA_HOME=nil,
XDG_CACHE_HOME=nil, XDG_CACHE_HOME=nil,
XDG_STATE_HOME=nil,
XDG_RUNTIME_DIR=nil, XDG_RUNTIME_DIR=nil,
XDG_CONFIG_DIRS=nil, XDG_CONFIG_DIRS=nil,
XDG_DATA_DIRS=nil, XDG_DATA_DIRS=nil,
@ -293,6 +294,7 @@ describe('XDG-based defaults', function()
local env_sep = iswin() and ';' or ':' local env_sep = iswin() and ';' or ':'
local data_dir = iswin() and 'nvim-data' or 'nvim' local data_dir = iswin() and 'nvim-data' or 'nvim'
local state_dir = iswin() and 'nvim-data' or 'nvim'
local root_path = iswin() and 'C:' or '' local root_path = iswin() and 'C:' or ''
describe('with too long XDG variables', function() describe('with too long XDG variables', function()
@ -303,6 +305,7 @@ describe('XDG-based defaults', function()
.. env_sep.. root_path .. ('/b'):rep(2048) .. env_sep.. root_path .. ('/b'):rep(2048)
.. (env_sep .. root_path .. '/c'):rep(512)), .. (env_sep .. root_path .. '/c'):rep(512)),
XDG_DATA_HOME=(root_path .. ('/X'):rep(4096)), XDG_DATA_HOME=(root_path .. ('/X'):rep(4096)),
XDG_STATE_HOME=(root_path .. ('/X'):rep(4096)),
XDG_DATA_DIRS=(root_path .. ('/A'):rep(2048) XDG_DATA_DIRS=(root_path .. ('/A'):rep(2048)
.. env_sep .. root_path .. ('/B'):rep(2048) .. env_sep .. root_path .. ('/B'):rep(2048)
.. (env_sep .. root_path .. '/C'):rep(512)), .. (env_sep .. root_path .. '/C'):rep(512)),
@ -355,13 +358,13 @@ describe('XDG-based defaults', function()
.. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after' .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after'
.. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after' .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after'
):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. data_dir .. '/backup//', eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. state_dir .. '/backup//',
(meths.get_option('backupdir'):gsub('\\', '/'))) (meths.get_option('backupdir'):gsub('\\', '/')))
eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/swap//', eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/swap//',
(meths.get_option('directory')):gsub('\\', '/')) (meths.get_option('directory')):gsub('\\', '/'))
eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/undo//', eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/undo//',
(meths.get_option('undodir')):gsub('\\', '/')) (meths.get_option('undodir')):gsub('\\', '/'))
eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/view//', eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/view//',
(meths.get_option('viewdir')):gsub('\\', '/')) (meths.get_option('viewdir')):gsub('\\', '/'))
end) end)
end) end)
@ -372,6 +375,7 @@ describe('XDG-based defaults', function()
XDG_CONFIG_HOME='$XDG_DATA_HOME', XDG_CONFIG_HOME='$XDG_DATA_HOME',
XDG_CONFIG_DIRS='$XDG_DATA_DIRS', XDG_CONFIG_DIRS='$XDG_DATA_DIRS',
XDG_DATA_HOME='$XDG_CONFIG_HOME', XDG_DATA_HOME='$XDG_CONFIG_HOME',
XDG_STATE_HOME='$XDG_CONFIG_HOME',
XDG_DATA_DIRS='$XDG_CONFIG_DIRS', XDG_DATA_DIRS='$XDG_CONFIG_DIRS',
}}) }})
end) end)
@ -405,13 +409,13 @@ describe('XDG-based defaults', function()
.. ',$XDG_DATA_DIRS/nvim/after' .. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after' .. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup//'), eq(('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
meths.get_option('backupdir'):gsub('\\', '/')) meths.get_option('backupdir'):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'), eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
meths.get_option('directory'):gsub('\\', '/')) meths.get_option('directory'):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo//'), eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
meths.get_option('undodir'):gsub('\\', '/')) meths.get_option('undodir'):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view//'), eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
meths.get_option('viewdir'):gsub('\\', '/')) meths.get_option('viewdir'):gsub('\\', '/'))
meths.command('set all&') meths.command('set all&')
eq(('$XDG_DATA_HOME/nvim' eq(('$XDG_DATA_HOME/nvim'
@ -425,13 +429,13 @@ describe('XDG-based defaults', function()
.. ',$XDG_DATA_DIRS/nvim/after' .. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after' .. ',$XDG_DATA_HOME/nvim/after'
):gsub('\\', '/'), (meths.get_option('runtimepath')):gsub('\\', '/')) ):gsub('\\', '/'), (meths.get_option('runtimepath')):gsub('\\', '/'))
eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup//'), eq(('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
meths.get_option('backupdir'):gsub('\\', '/')) meths.get_option('backupdir'):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'), eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
meths.get_option('directory'):gsub('\\', '/')) meths.get_option('directory'):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo//'), eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
meths.get_option('undodir'):gsub('\\', '/')) meths.get_option('undodir'):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view//'), eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
meths.get_option('viewdir'):gsub('\\', '/')) meths.get_option('viewdir'):gsub('\\', '/'))
end) end)
end) end)
@ -442,6 +446,7 @@ describe('XDG-based defaults', function()
XDG_CONFIG_HOME=', , ,', XDG_CONFIG_HOME=', , ,',
XDG_CONFIG_DIRS=',-,-,' .. env_sep .. '-,-,-', XDG_CONFIG_DIRS=',-,-,' .. env_sep .. '-,-,-',
XDG_DATA_HOME=',=,=,', XDG_DATA_HOME=',=,=,',
XDG_STATE_HOME=',=,=,',
XDG_DATA_DIRS=',≡,≡,' .. env_sep .. '≡,≡,≡', XDG_DATA_DIRS=',≡,≡,' .. env_sep .. '≡,≡,≡',
}}) }})
end) end)
@ -484,13 +489,13 @@ describe('XDG-based defaults', function()
.. ',\\,-\\,-\\,' .. path_sep ..'nvim' .. path_sep ..'after' .. ',\\,-\\,-\\,' .. path_sep ..'nvim' .. path_sep ..'after'
.. ',\\, \\, \\,' .. path_sep ..'nvim' .. path_sep ..'after' .. ',\\, \\, \\,' .. path_sep ..'nvim' .. path_sep ..'after'
), meths.get_option('runtimepath')) ), meths.get_option('runtimepath'))
eq('.,\\,=\\,=\\,' .. path_sep .. data_dir .. '' .. path_sep ..'backup' .. (path_sep):rep(2), eq('.,\\,=\\,=\\,' .. path_sep .. state_dir .. '' .. path_sep ..'backup' .. (path_sep):rep(2),
meths.get_option('backupdir')) meths.get_option('backupdir'))
eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2), eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2),
meths.get_option('directory')) meths.get_option('directory'))
eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'undo' .. (path_sep):rep(2), eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'undo' .. (path_sep):rep(2),
meths.get_option('undodir')) meths.get_option('undodir'))
eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'view' .. (path_sep):rep(2), eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'view' .. (path_sep):rep(2),
meths.get_option('viewdir')) meths.get_option('viewdir'))
end) end)
end) end)
@ -499,8 +504,9 @@ end)
describe('stdpath()', function() describe('stdpath()', function()
-- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions -- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions
-- due to XDG_CONFIG_HOME and XDG_DATA_HOME being the same. -- due to XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME being the same.
local datadir = iswin() and 'nvim-data' or 'nvim' local datadir = iswin() and 'nvim-data' or 'nvim'
local statedir = iswin() and 'nvim-data' or 'nvim'
local env_sep = iswin() and ';' or ':' local env_sep = iswin() and ';' or ':'
it('acceptance', function() it('acceptance', function()
@ -509,6 +515,7 @@ describe('stdpath()', function()
eq('nvim', funcs.fnamemodify(funcs.stdpath('cache'), ':t')) eq('nvim', funcs.fnamemodify(funcs.stdpath('cache'), ':t'))
eq('nvim', funcs.fnamemodify(funcs.stdpath('config'), ':t')) eq('nvim', funcs.fnamemodify(funcs.stdpath('config'), ':t'))
eq(datadir, funcs.fnamemodify(funcs.stdpath('data'), ':t')) eq(datadir, funcs.fnamemodify(funcs.stdpath('data'), ':t'))
eq(statedir, funcs.fnamemodify(funcs.stdpath('state'), ':t'))
eq('table', type(funcs.stdpath('config_dirs'))) eq('table', type(funcs.stdpath('config_dirs')))
eq('table', type(funcs.stdpath('data_dirs'))) eq('table', type(funcs.stdpath('data_dirs')))
assert_alive() -- Check for crash. #8393 assert_alive() -- Check for crash. #8393
@ -582,6 +589,39 @@ describe('stdpath()', function()
end) end)
end) end)
describe('with "state"' , function ()
it('knows XDG_STATE_HOME', function()
clear({env={
XDG_STATE_HOME=alter_slashes('/home/docwhat/.local'),
}})
eq(alter_slashes('/home/docwhat/.local/'..statedir), funcs.stdpath('state'))
end)
it('handles changes during runtime', function()
clear({env={
XDG_STATE_HOME=alter_slashes('/home/original'),
}})
eq(alter_slashes('/home/original/'..statedir), funcs.stdpath('state'))
command("let $XDG_STATE_HOME='"..alter_slashes('/home/new').."'")
eq(alter_slashes('/home/new/'..statedir), funcs.stdpath('state'))
end)
it("doesn't expand $VARIABLES", function()
clear({env={
XDG_STATE_HOME='$VARIABLES',
VARIABLES='this-should-not-happen',
}})
eq(alter_slashes('$VARIABLES/'..statedir), funcs.stdpath('state'))
end)
it("doesn't expand ~/", function()
clear({env={
XDG_STATE_HOME=alter_slashes('~/frobnitz'),
}})
eq(alter_slashes('~/frobnitz/'..statedir), funcs.stdpath('state'))
end)
end)
describe('with "cache"' , function () describe('with "cache"' , function ()
it('knows XDG_CACHE_HOME', function() it('knows XDG_CACHE_HOME', function()
clear({env={ clear({env={