Files
IntelliJ-IdeaVim/doc/posts/environment-variable-expansion-in-file-commands.md

93 lines
4.3 KiB
Markdown

# Environment Variable Expansion in File Commands
What can be more interesting than environment variable expansion rules in Vim? Probably anything. Yet, here is what we
learned about it from Vim.
Commands like `:source $HOME/.vimrc` or `:split ~/notes.txt` use environment variables and tilde in file paths. Vim
expands these before opening files, but the exact rules are more nuanced than the documentation suggests.
## Vim's File Argument Expansion
In Vim's source code (`src/ex_cmds.h`), commands that accept file arguments are marked with special flags:
- **`EX_FILE1`** - Single file argument with expansion
- **`EX_FILES`** - Multiple file arguments with expansion
- **`EX_XFILE`** - Enable wildcard and environment variable expansion
When these flags are set, Vim automatically expands:
- Environment variables: `$VAR`, `${VAR}`
- Tilde: `~`, `~/path`
- Wildcards: `*`, `?`
- Special chars: `%` (current file), `#` (alternate file)
## Two Different Expansion Behaviors
Vim has **two different behaviors** for environment variable expansion:
### 1. File Commands (`:source`, `:split`, etc.)
Non-existent variables expand to **empty string**:
```vim
:source $NONEXISTENT/file.vim → :source /file.vim
```
### 2. Option Settings (`:set` command)
The `:help expand-env` documentation describes expansion for the `:set` command. Only **39 specific options** support
expansion, controlled by the `P_EXPAND` flag (`0x10`) defined in `src/option.h`.
Options with `P_EXPAND` include: `shell`, `path`, `backupdir`, `makeprg`, `grepprg`, `runtimepath`, and others.
Non-existent variables are **left as-is**:
```vim
:set shell=$NONEXISTENT → shell=$NONEXISTENT (kept literally)
:set shell=$HOME/bash → shell=/Users/you/bash (expanded)
```
**Note**: Setting options via `:let` does **not** perform expansion:
```vim
:let &shell = "$HOME/bash" → shell=$HOME/bash (literal string, not expanded)
```
This distinction was verified in both Vim 9.1 and Nvim 0.11.4.
## Filename Modifiers (`%`, `%:h`, `%:t`, …)
In file commands, `%` expands to the current buffer's name (path). Modifiers change what part of the path is used:
| Modifier | Name | Example (file: `/home/user/project/src/foo.py`) |
|-----------|---------------------------------|-------------------------------------------------|
| `%` | current file (as opened) | `src/foo.py` |
| `%:p` | **p**ull path — absolute | `/home/user/project/src/foo.py` |
| `%:h` | **h**ead — directory part | `src/foo` |
| `%:p:h` | absolute directory | `/home/user/project/src` |
| `%:t` | **t**ail — filename only | `foo.py` |
| `%:r` | **r**oot — strip last extension | `src/foo` |
| `%:t:r` | filename without extension | `foo` |
| `%:e` | **e**xtension | `py` |
| `%:p:h:h` | parent of parent (chain `:h`) | `/home/user/project` |
Modifiers can be chained left-to-right: `%:p:h:t` → last segment of the absolute directory (`src`).
## Vim Commands with File Argument Expansion
In Vim's source code (`src/ex_cmds.h`), **92 commands** are marked with `EX_FILE1`, `EX_FILES`, or `EX_XFILE` flags to
enable file argument expansion:
- **File Editing (24)**: `:edit`, `:split`, `:vsplit`, `:new`, `:vnew`, `:find`, `:tabedit`, `:read`, `:write`,
`:saveas`, etc.
- **Exit/Write-Quit (7)**: `:exit`, `:xit`, `:wq`, `:wqall`, `:wnext`, etc.
- **Argument List (8)**: `:args`, `:argadd`, `:next`, `:argedit`, etc.
- **Directory (6)**: `:cd`, `:lcd`, `:tcd`, `:chdir`, etc.
- **Build/Search (12)**: `:make`, `:grep`, `:vimgrep`, `:cscope`, etc.
- **Quickfix (6)**: `:cfile`, `:cgetfile`, `:lfile`, etc.
- **Session (5)**: `:mksession`, `:mkview`, `:loadview`, etc.
- **Scripting (9)**: `:source`, `:runtime`, `:luafile`, `:pyfile`, `:rubyfile`, etc.
- **Diff (2)**: `:diffpatch`, `:diffsplit`
- **Undo/Viminfo (4)**: `:wundo`, `:rundo`, `:wviminfo`, `:rviminfo`
- **Miscellaneous (9)**: `:redir`, `:helptags`, `:mkspell`, `:packadd`, `:terminal`, etc.