Nvim :help pages, generated
from source
using the tree-sitter-vimdoc parser.
BUILD.md.
2. Clone the source repo and "cd" into it:git clone https://github.com/neovim/neovim cd neovim # (Optional) Build and run Nvim: make ./build/bin/nvim3. Run a single test. We will start with "example_spec.lua", which is a real test that shows how tests are written:
make functionaltest TEST_FILE=test/functional/example_spec.lua4. Notice the
before_each block in the test file. Because it calls
clear(), each it() test will start a new Nvim instance.
5. Tests will do stuff in the Nvim instance and make assertions using eq().
Tests that want to check the UI can also use screen:expect().
6. Now make a code change in Nvim itself, then you can see the effects. The
example test does feed('iline1…'), so let's make a change to the
insert-mode code, which lives in src/nvim/edit.c. In the
insert_handle_key function, just after the normalchar label, add this
code:s->c = 'x';7. Then run the "example_spec.lua" test again, and it should fail with something like this:
test/functional/example_spec.lua:31: Row 1 did not match.
Expected:
|*line1 |
|*line^2 |
|{0:~ }|
|{0:~ }|
| |
Actual:
|*xine1 |
|*xine^2 |
|{0:~ }|
|{0:~ }|
| |
You now understand how to modify the codebase, write tests, and run tests. See
dev-arch for details about the internal architecture.
$NVIM_LOG_FILE.
rm -rf build/ make CMAKE_EXTRA_FLAGS="-DLOG_DEBUG"Use
LOG_CALLSTACK() (Linux only) to log the current stacktrace. To log to an
alternate file (e.g. stderr) use LOG_CALLSTACK_TO_FILE(FILE*). Requires
-no-pie ([ref](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860394#15)):rm -rf build/ make CMAKE_EXTRA_FLAGS="-DLOG_DEBUG -DCMAKE_C_FLAGS=-no-pie"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:
tail -F ~/.local/state/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log
LUA_GEN_PRG to
a LuaJIT binary built with LUAJIT_SECURITY_PRN=0. See commit
cb757f2663e6950e655c6306d713338dfa66b18d.
nvim -V3log
--headless
flag which does not launch the TUI and will allow you to set breakpoints in code
not related to TUI rendering like so:lldb -- ./build/bin/nvim --headless --listen ~/.cache/nvim/debug-server.pipeWhile in lldb, enter
run. You can then attach to the headless process in a
new terminal window to interact with the editor like so:./build/bin/nvim --remote-ui --server ~/.cache/nvim/debug-server.pipeConversely for debugging TUI rendering, you can start a headless process and debug the remote-ui process multiple times without losing editor state.
:set writedelay=50 rdb=compositorNote: Nvim uses an internal screenbuffer to only send minimal updates even if a large region is repainted internally. To also highlight excess internal redraws, use
:set writedelay=50 rdb=compositor,nodelta
script command is still the "state of the art". The libvterm
vterm-dump utility formats the result for human-readability.
vterm-dump:script foo
./build/bin/nvim -u NONE
# Exit the script session with CTRL-d
# Use `vterm-dump` utility to format the result.
./.deps/usr/bin/vterm-dump foo > bar
Then you can compare bar with another session, to debug TUI behavior.
man terminfo
perf + flamegraph.
NVIM_PROBE ([#12036](https://github.com/neovim/neovim/pull/12036)).
#!/usr/bin/env bpftrace
BEGIN {
@depth = -1;
}
tracepoint:sched:sched_process_fork /@pidmap[args->parent_pid]/ {
@pidmap[args->child_pid] = 1;
}
tracepoint:sched:sched_process_exit /@pidmap[args->pid]/ {
delete(@pidmap[args->pid]);
}
usdt:build/bin/nvim:neovim:eval__call_func__entry {
@pidmap[pid] = 1;
@depth++;
@funcentry[@depth] = nsecs;
}
usdt:build/bin/nvim:neovim:eval__call_func__return {
$func = str(arg0);
$msecs = (nsecs - @funcentry[@depth]) / 1000000;
@time_histo = hist($msecs);
if ($msecs >= 1000) {
printf("%u ms for %s\n", $msecs, $func);
print(@files);
}
clear(@files);
delete(@funcentry[@depth]);
@depth--;
}
tracepoint:syscalls:sys_enter_open,
tracepoint:syscalls:sys_enter_openat {
if (@pidmap[pid] == 1 && @depth >= 0) {
@files[str(args->filename)] = count();
}
}
END {
clear(@depth);
}
$ sudo bpftrace funcslower.bt
1527 ms for Slower
@files[/usr/lib/libstdc++.so.6]: 2
@files[/etc/fish/config.fish]: 2
<snip>
^C
@time_histo:
[0] 71430 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1] 346 | |
[2, 4) 208 | |
[4, 8) 91 | |
[8, 16) 22 | |
[16, 32) 85 | |
[32, 64) 7 | |
[64, 128) 0 | |
[128, 256) 0 | |
[256, 512) 6 | |
[512, 1K) 1 | |
[1K, 2K) 5 | |
ulimit -c unlimited
coredumpctl -1 gdb
coredumpctl is an optional tool, so you may need to install it:
sudo apt install systemd-coredump
backtrace.txt file
when reporting a bug related to a crash:
2>&1 coredumpctl -1 gdb | tee -a backtrace.txt
(gdb) thread apply all bt full
coredumpctl, you may find a core dump file appearing
in the current directory or in other locations. On Linux systems where
apport is installed (such as Ubuntu), the directory where core dump files
are saved can be /var/lib/apport/coredump or elsewhere, depending on the
system configuration (see /proc/sys/kernel/core_pattern). See also:
https://stackoverflow.com/a/18368068
./core dump file:
gdb build/bin/nvim ./core 2>&1 | tee backtrace.txt
(gdb) thread apply all bt full
nvim crashes, you can see the backtrace in Console.app (under "Crash
Reports" or "User Diagnostic Reports" for older macOS versions).
open -a Console
/cores/ directory exists and is writable:
sudo mkdir /cores
sudo chown root:admin /cores
sudo chmod 1775 /cores
unlimited:
ulimit -c unlimited
~/.bashrc
or similar).
lldb:
lldb -c /cores/core.12345
/etc/launchd.conf).
TEST_TAG to run tests matching busted tags (of the form #foo e.g.
it("test #foo ...", ...)):
GDB=1 TEST_TAG=foo make functionaltest
gdb build/bin/nvim
(gdb) target remote localhost:7777
-- See nvim_argv in https://github.com/neovim/neovim/blob/master/test/functional/testnvim.lua.
lldb .deps/usr/bin/luajit -- .deps/usr/bin/busted --lpath="./build/?.lua" test/unit/
nvim process with a pid of 1234 (Tip: the pid of a
running Nvim instance can be obtained by calling getpid()), for instance:
gdb -tui -p 1234 build/bin/nvim
gdb interactive prompt will appear. At any time you can:
break foo to set a breakpoint on the foo() function
n to step over the next statement
<Enter> to repeat the last command
s to step into the next statement
c to continue
finish to step out of the current function
p zub to print the value of zub
bt to see a backtrace (callstack) from the current location
CTRL-x CTRL-a or tui enable to show a TUI view of the source file in the
current debugging context. This can be extremely useful as it avoids the
need for a gdb "frontend".
<up> and <down> to scroll the source file view
set record full insn-number-max unlimited
continue for a bit (at least until main() is executed
record
revert-next, reverse-step, etc. to rewind the
debugger
gdb clients to the same running nvim
process, or you may want to connect to a remote nvim process with a local
gdb. Using gdbserver, you can attach to a single process and control it
from multiple gdb clients.
gdbserver attached to nvim like this:
gdbserver :6666 build/bin/nvim 2> gdbserver.log
gdbserver is now listening on port 6666. You then need to attach to this
debugging session in another terminal:
gdb build/bin/nvim
gdb, you need to attach to the remote session:
(gdb) target remote localhost:6666
signal (SIGTTOU, SIG_IGN);
if (!tcsetpgrp(data->input.in_fd, getpid())) {
perror("tcsetpgrp failed");
}
tui.c:terminfo_start.
gdbserver method mentioned above.
This example local.mk will create the debugging session when you type
make debug.
.PHONY: dbg-start dbg-attach debug build
build:
@$(MAKE) nvim
dbg-start: build
@tmux new-window -n 'dbg-neovim' 'gdbserver :6666 ./build/bin/nvim -D'
dbg-attach:
@tmux new-window -n 'dbg-cgdb' 'cgdb -x gdb_start.sh ./build/bin/nvim'
debug: dbg-start dbg-attach
gdb_start.sh includes gdb commands to be called when the debugger
starts. It needs to attach to the server started by the dbg-start rule. For
example:
(gdb) target remote localhost:6666 (gdb) br main
llvm-symbolizer must be in $PATH:clang --versionBuild Nvim with sanitizer instrumentation (choose one):
CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_ASAN_UBSAN=ON" CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_MSAN=ON" CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_TSAN=ON"Create a directory to store logs:
mkdir -p "$HOME/logs"Configure the sanitizer(s) via these environment variables:
# Change to detect_leaks=1 to detect memory leaks (slower, noisier).
export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan"
# Show backtraces in the logs.
export MSAN_OPTIONS="log_path=${HOME}/logs/msan"
export TSAN_OPTIONS="log_path=${HOME}/logs/tsan"
Logs will be written to ${HOME}/logs/*san.PID then.