Vim & Neovim
Command Cheat Sheet (Get & Set)
And now for buffers etc
And globals
Lua Globals (_G) vs. Vim Globals (vim.g)
Summary of the "Big Three"
- vim.opt: Switches/knobs for the Editor (Line numbers, tabs).
- vim.g: Settings for Plugins (Theme names, enable/disable flags).
- _G: Logic for Functions (Your custom code that needs to run everywhere).
Ok so.... In a lua console
Then can do EITHER :lua print(pls()) or :lua print(_G.pls()) as _G is the global namespace!
Then, the equivalent in vim is
notice that
print()does not appear, as it goes to message history?vim.notifyWILL print however
This STILL doesn't make sense to me - but I guess that the lua print() is only really for debugging? In general an API show either log to a file or notify?
I found the docs here where it says lua print() redirects its output to the nvim message area?
Ok well the various types of printing in vim and lua are frying my brain a bit - especially since print() in lua claims to print to the message area but... does not? well gemini (tried) to explain
vim.notify() is fairly modern so that does make sense honestly. its the others
Ok NEVER MIND - it was because i was in the lua console - i assume print() is found to a different messages area or something
Vimscript functions
So weird weird details of how functions work in vim
system()is a FUNCTION:echois a COMMAND
and functions CANNOT be called directly from the command line!
Hence why :system(...) does not work! need :echo or :call to invoke it
Even weirder... even inside a vimscript function this still applies? which seems strange to me but oh well... So like doing : to invoke a command the same applies in vimscript in will complain if you try and do
will yield
as you need echo or call!
Wincmd?
So apparently :tabdo executes for each tab?
Nice! so like
Goes and prints bufname for each tab (active buffer)
Ahhhh
:wincmd = is the same as C-W = a window command...
Interestingly, the reason the the outline escapes from this when its called (and graciously so, wouldn't want the outline taking up half the page horizontally...)
It is the winfixwidth property that forces the win to stay constant (when C-w = is called)
Vim: Redirect command output to register
:new | put =execute('SomeCommand') — this opens a new scratch buffer and pastes the output of any ex command into it. Breakdown:
:new— opens a new empty splitput =— puts the result of an expression into the bufferexecute('...')— runs an ex command and captures its output as a string
So execute('Fidget history') runs :Fidget history and returns the output text, then put = pastes it into the buffer where you can search with /.
Works with any command, e.g. :new | put =execute('messages'), :new | put =execute('map')
Ok so (ME not claude as of now...)
| is not actually what i thought like a pipe in bash, it is actually just like ; in bash (see :h :bar in vim)
So in this case we are just saying :new to open a new buffer and THEN :put to dump the output of the register
Gotcha: | doesn't work with all commands. Some commands treat | as part of their own arguments instead of as a command separator. User-defined commands (from plugins) do this by default unless the plugin author explicitly opted in to | separation with -bar (see :h command-bar). So:
Fix: use <cmd>...<cr> chaining in a keymap, or just use separate commands. In ex mode, there's no clean workaround — run them separately:
Or in a keymap:
e.g.
dumps 'pls' into current buffer
i did try this
BUT echo doesn't return to a buffer so it wouldn't work. WAIT IGNORE. just needed to escape the " with
....or...
Vim registers and the " prefix
Registers in vim are accessed with " (double quote) in normal mode.
Special registers:
to see help can use vims special 'quote' like
:h quote=is equivalent to:h "=
Accessing registers — " vs @:
Why quote= in vim help?
Vim help spells " as quote in its help tags, so :h quote= means "help for the "= register", :h quotea for "a, etc.
Vim variable scopes cheat sheet
Options (settings like number, expandtab) use vim.opt / vim.o / vim.bo / vim.wo:
Examples:
vim.optvsvim.o:vim.optreturns a special object with:get(),:append(),:prepend(),:remove().vim.ois simpler direct access. For setting simple values they're interchangeable.
Terminal escape sequences cheat sheet
Escape sequences control terminal behavior. Two main types: CSI (Control Sequence Introducer) for cursor/display and OSC (Operating System Command) for terminal features.
Key:
\eor\033= ESC (0x1B)\aor\007= BEL (terminates OSC)- CSI ends with a letter (
J,H,m,q,l,h) - OSC ends with BEL (
\a) or ST (\e\\)
SGR (Select Graphic Rendition) codes for \e[...m:
Example combining: \e[1;31m = bold + red, \e[38;2;255;100;0mOrange\e[0m = truecolor orange then reset
OSC 52 clipboard breakdown:
Why base64? The clipboard content might contain special characters that would break the escape sequence. Base64 ensures it's safe ASCII.
Works over SSH because the escape sequence travels through the terminal stream back to your local terminal (kitty/wezterm/etc) which handles the actual clipboard.
Ref: https://terminalguide.namepad.de/seq/osc-52/
Revisiting folding
So can do it like this (with LSP)
See :h vim.lsp.foldexpr() for more details on how it works
An interactive way to see it for current line is
Whereby line('.') is current line (see :h :line()) and the vim.lsp.foldexpr() takes an arg of the current line number
To use treesitter it must be
see https://neovim.io/doc/user/treesitter.html#vim.treesitter.foldexpr()
Similarly, can test it with
foldlevel / foldlevelstart
foldlevel is the active threshold for a buffer — folds deeper than N are closed.
foldlevelstart sets the initial foldlevel when a buffer opens. Default is 0 (all closed).
Treesitter and folding
Ok so claude came up with a very interesting note about treesitter and how it supports folding.
Treesitter has flags for types of treesitter (nodes?) on which to do folds. For SQL this is https://github.com/nvim-treesitter/nvim-treesitter/blob/master/queries/sql/folds.scm here where it is JUST statement notes. (WITH, SELECT) i believe but not including union selects?
So if i go into a sql file with select * .... union all select the second select is NOT a statement hence can't fold on it!
So have some options:
- use LSP for SQL?
- override treesitter to add fold commands for union/select?
also need to add fallback to indent for
:LspInfowhich is of filetypecheckhealthno supported by treesitter or LSP (i assume)
Also.... trying out the sqls and sqlls couldnt get folding to work?
Well.... checking out https://neovim.io/doc/user/lsp.html it says you need "textDocument/foldingRange" to support folding?
And with sqlls I tried:
And couldnt see it there....
Claude wrote me this a custom fallback function for folding myself?
Treesitter folding quirk with multi-line signatures
vim.treesitter.foldexpr() breaks fold hierarchy when a function has a multi-line signature. Example:
The parameters treesitter node spans multiple lines, inflating the fold level to 3 (class > func > parameters). When parameters ends, the foldexpr never drops back to level 2 before try_statement starts its own >3. Vim sees two separate level-3 folds rather than a level-2 fold containing a level-3 fold.
Result: closing the function fold only collapses the signature (lines 108-111), not the body. The try block is a separate fold at the same level.
Key foldexpr syntax (:h fold-expr):
>N= start a new fold at level NN= continue at level N<N= end a fold at level N
Useful built-in level commands:
zm/zr= increment/decrement foldlevel by 1zM/zR= close/open all folds:set foldlevel=N= close all folds deeper than N
Single-line signatures fold correctly. This is not a treesitter parser bug - the parser correctly nests try_statement inside function_definition. The bug is in neovim's vim.treesitter.foldexpr() which translates the tree into fold levels. It counts parameters as a foldable multi-line node at the same depth as try_statement (both direct children of function_definition), so it assigns them the same level instead of treating the function body as deeper than the signature. LSP folding (vim.lsp.foldexpr()) may handle it better but LSP fold support varies by server.
Neovim misc
Buffers
Ok, time to start making some notes on neovim, will be a long journey I'm sure
To start with buffers
- telescope has
space spaceto view buffers echo bufnr()(orecho bufname()to view buffer name)- view
help buffersto view vim's help - according to claude,
ctrl-gis also view buffer number?
This is however false, as i found out!
to view vim's list of all commands do
:help index. then can search forctrl-gand see the following line
Right I FINALLY know what the buffer things mean in vim
(yes i know i could have just done
:h buffersall this time...)
But, h is hidden, a is active and
Ahhh and unlisted buffers! that's where help, outline, aerial lazy etc put their buffers! makes sense now.
To view just to :buffers! with the exclamation mark
Command redirection
see :help redir
e.g.
then
then
Type hints
So... type hints in lua are.. interesting, why they couldnt just use typescripts way? 😠
Turns out i can configure the lua language server DIRECTLY.
TODO
I need to research this more, but can configure workspace.library?
e.g. from claude? if i wanted to set some types file for a lib?
But i actually already have lazydev installed, which according to claude helps as it can
- adds nvim library stuffs to lua (guess would be nightmare trying to do myself)
already added wezterm types in there
Finding commands
With my neovim setup based on kickstart, i have leader(space)-s-k to search commands
But in general (from claude 😆)
:help index is the closest to "view all keybindings" - it shows built-in Vim commands organized by mode.
For your custom mappings, use:
nice, cat pls.txt gives:
Lua
so lua in neovim has alot of ways to interact with vim itself, here some of the main ones
but also, given that extensions/plugins must register commands with vim itself to be able to call
e.g.
registers HelloWorld with vim itself (but defined in lua)? so we can call :HelloWorld
One useful link for documentation on lua type hints is here
Lsps
It seems that most lsp configs are NOT typed. i guess that calling
would be difficult to type all of them?
BUT all the configurations can be found here, just have to dig through the docs normally
Now i couldn't get luals to give type hints to work, and lazydev docs are about as useful as a sack of potatoes, so I'm digging through myself. (with some help from claude)
Now
- neovim's state is installed to
~/.local/share/nvim - once again 0 documentation about this very helpfully, found out the
lua lsfrom mason is at~/.local/share/nvim/mason/bin/lua-language-server - soooo, running ``~/.local/share/nvim/mason/bin/lua-language-server --help
, found out can add--loglevel=debug` to cmd args!
I've yet to figure out how to put this into my own "standard" lib, but having a utility to deep print all lua object properties.... welllll turns out thats vim.inspect 😆. but will put it here for my own learnings
Understanding the docs
Can use :h notation to see how the neovim documentation refers to stuff like option chars in [] (e.g. :mes[sages] could be :mes or :messages)
Another good one is :helpgrep for searching through the help. Although finding help (sections NOT searching) is provided by telescope via <LEADER>-s-h
Another one is :h key-notation.
something to note is its not mentioned anywhere epxlicitly that sometimes in the docs they use
caret notation, like^Vforctrl-v
Didn't know about execution mode gQ either, pretty cool for re-running commands
Tags
Just do :h tagsrch honestly (short for tag and special search)
Apparently calling :ta[g] is the same as C-]
soooo
C-] = :tag = :ta
Also can name tag to go to? like :tag help to go to help tag
So tags are not that fancy after all, just *tag_here*. like in the outline help can use c-] on outline-prerequisites to jump to the tag, but unformatted text is just like this!
Vim stores these in files (somewhere) to keep an index of tags so can jump between files
Registers
stressed-boomer
Ahhhh finally, i should have googled this a LONG time ago.
The black hole register means the data dies, so can delete without putting it into the register!.
e.g. "_d to delete to black hole register
oh this is cool!
Like.... erm?
to set buffer a to buffer number? couldn't think of a better example 🤷
Like.... erm?
to set buffer a to buffer number? couldn't think of a better example 🤷
The + register is quoteplus which apparently is the system clipboard?
So can do
To set a string to clipboard
Tabs
Didn't realise this b ut I can use :1tabn to go to tab 1 and :4tabn to go to tab 4 etc
This is also equiv to 1gt
Also I can stop doing :tabnew and then :help ...
And just (if I'd bothered to do this before lol 🤣 :h :tab)
Misc
Pipe
So :help :bar will explain but can show an example that makes more sense:
Will notice above that the help is in the bottom half of the window if this is called. I.e. the commands are chained NOT piped. My interpretation of this is
- open a new tab
- open help for buffers
Ok wow this example taught me alot:
read i guess takes not file file name but also bash commands to read in?
And then (remember | is NOT pipe but chaining commands), go to mark [ which is beginning of yank or previously unchanged text (i.e. top of output in english)
remember that ' is for marks
Set
So set sets an option but would be interesting to see a test.
I actually have relativenumber set in my init.lua but I think some plugins must be overriding because when i call set relativenumber? to retrieve its blank!
Something more simple is the :set timeoutlen? gets me the timeoutlen = 300 value
Messages
Really need to read into this more, but :NoiceAll has more messages.
According to claude :redraw can help flush messages? although that doesn't make sense to me as this is Noice and NOT vim internally
Delete Command
FML..... I was reading the wrong command
:[range]d register
wondering why it wasn't working.
but this, :d is DIFFERENT to d, as it is an execution/command
TODO
- read this, noice guide on msgs
- have a read through
:help ui-messages
Execute Thanks clude, i didn't understand this myself...
Literally just executing stringified expressions, but vim expressions (not terminal).
Like, lets combine a register?
So earlier I was playing around with setting registers. :echo getreg('a') prints 3, the buffer name I set earlier.
So then to open the tab with a buffer from register a can do
Or another one(I suppose as we are technically in cmd mode gg wouldn't work without the normal prefix)
Commands vs functions vs expressions
An important distinction here is that getreg is a vim function, NOT a command
- functions cannot be called like
:as commands are - functions can return values
- functions must be invoked like
run_function()with parenthesis
Or wait, I (think) command is a bit ambiguous here? A command being like :e or :reg etc etc.
Ok doing :h vimeval.txt would be a good read as it covers vim expressions and its syntax which is pretty key to understanding all of this long term
This table is a super useful reference point from :help expression to see how to access different types of vars/registers etc
Eval
To show here a good usecase, it clearly says in the docs that :h :echo that you :echo {expr} i.e. echo and expression
So :echo 4+5 is 9. where 4+5 is the expression
Or, could have done :echo eval("4+5") to achieve the same thing
So...
:executeexecutes a string (command written as text). e.g:execute 'normal gg':echoexecutes an expression and prints the result e.g.:echo xto print variablexevalexecutes a string (expression written as text). e.g.eval("x")would retrieve variablex
History Wow must NOT forget these, command line history and search history respectively!
q:g/
Regexps
Ok so I (roughly) understand what this magic stuff is about in regexp
Can see from :set magic? that its magic (and NOT nomagic, strange syntax choice here from vim that true is :set magic and false is :set nomagic)... so the no is the invert bit?
So the default behaviour is to take everything literally, like brackets (), pipe | etc...
The only exception I have see is / which is the last pattern (ok this is much quicker than doing / and then up to find last pattern tbf). also backslash \ must always be escaped
Ok so default is 'magic':
- in magic, in general stuff must be escaped (like
\.for literal dot) \mforces magic mode (e.g.\m\.forces magic modes and searches for literal dot)- in "no magic" (NOT default), magic characters are generally off (just
.for literal dot) \Mforces no magic mode (e.g.\M.forces no magic mode and searches for literal dot)
Additional modes
- "very" magic, even
(and)literals must be escaped
this one is SO annoying. given the default is sort od regexp EXCEPT for braces 👿
-
\vtriggers "very" magic, e.g.\v\(to search for(char -
the default is
(just to search for(char -
"very" not magic, even
\$is required -
\Vto trigger "very" not magic
Mappings
I ALWAYS get lost looking at the strange mapping syntax in vim nnnoommapremap shite. So i got claude to write me a diagram of the i (for insert), vs no for no remapping and various combinations....
Special Combinations
The
noinnoremap
"no" = "NO remap" — it means disable remapping of the right-hand side. The "no" is a prefix meaning "don't allow remapping".
:map vs :noremap — The Key Difference
:map (allows remapping)
You press: a
- → remapped to
b - → remapped again to
c - Result:
cexecutes
⚠️ Can cause chains and loops!
:noremap (disables remapping)
You press: a
- → mapped to
b(literal, won't scan again) - Result:
bexecutes
✓ Safe and predictable!
All the noremap variants
Why this matters: when a Vim mapping like ['<C-/>'] silently does nothing, or <C-[> behaves identically to <Esc>, the cause is almost always a mismatch between what you think the keypress sends and what the terminal actually sends.
The ASCII table in two halves
The left column (0–31) is not printable — these are control signals (move cursor, ring bell, etc). The right column (64–95) is the @A-Z[\]^_ block of printable ASCII.
The rule: Ctrl clears bits 6 and 7
Pressing Ctrl with a character in the @–_ range (ASCII 64–95) masks off the top 2 bits, leaving only the bottom 5:
The pattern is symmetric: each letter in @…_ is exactly 64 higher than its control code, and 64 = 0b01000000 — bit 6. Masking it off is the same as subtracting 64.
Quick reference: surprising aliases
<C-[> IS <Esc> — always
They produce the exact same byte (0x1B). You cannot map <C-[> to do something different from <Esc> in a legacy terminal — they are identical. Kitty's extended keyboard protocol can distinguish them, but only if both the terminal and the application (Neovim, Telescope) opt in.
The C-/ edge case
/ is ASCII 47, which is below 64 — outside the clean @–_ range. The AND rule gives 47 & 31 = 15 (0x0F), not 0x1F. So why do most terminals send 0x1F for Ctrl+/?
On a US keyboard, / and ? share the same physical key. When applying a Ctrl modifier to a key outside the normal range, many terminals use the shifted variant of that key instead:
0x1F is the same byte as Ctrl+_. So Ctrl+/ and Ctrl+_ arrive at Neovim as the same byte, and Neovim represents it as <C-_>.
['<C-/>'] = 'to_fuzzy_refine' silently failed in Telescope
The mapping was registered under the name <C-/>, but the terminal sent byte 0x1F which Neovim labels <C-_>. Two different names, same physical key, no match → fell through to Telescope's default <C-/> handler (which_key).
Fix: map both names:
Verify what your terminal actually sends
Press a key in insert mode and insert the raw byte with Ctrl+V:
Or check with cat in a shell — it prints the raw bytes:
Or inspect the actual hex:

