diff options
Diffstat (limited to 'src/nvim/README.md')
-rw-r--r-- | src/nvim/README.md | 190 |
1 files changed, 152 insertions, 38 deletions
diff --git a/src/nvim/README.md b/src/nvim/README.md index f16c6de12f..02464c2500 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,25 +1,119 @@ -## Source code overview +Nvim core +========= -Since Neovim has inherited most code from Vim, some information in [its -README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) still -applies. +Module-specific details are documented at the top of each module (`terminal.c`, +`screen.c`, …). -This document aims to give a high level overview of how Neovim works internally, -focusing on parts that are different from Vim. Currently this is still a work in -progress, especially because I have avoided adding too many details about parts -that are constantly changing. As the code becomes more organized and stable, -this document will be updated to reflect the changes. +See `:help dev` for guidelines. -If you are looking for module-specific details, it is best to read the source -code. Some files are extensively commented at the top (e.g. terminal.c, -screen.c). +Filename conventions +-------------------- -### Top-level program loops +The source files use extensions to hint about their purpose. -First let's understand what a Vim-like program does by analyzing the workflow of -a typical editing session: +- `*.c`, `*.generated.c` - full C files, with all includes, etc. +- `*.c.h` - parametrized C files, contain all necessary includes, but require + defining macros before actually using. Example: `typval_encode.c.h` +- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. +- `*.h.generated.h` - exported functions’ declarations. +- `*.c.generated.h` - static functions’ declarations. -01. Vim dispays the welcome screen +Logs +---- + +Low-level log messages sink to `$NVIM_LOG_FILE`. + +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*)`. + +UI events are logged at DEBUG level (`DEBUG_LOG_LEVEL`). + + rm -rf build/ + make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" + +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/share/nvim/log | cat -v | stdbuf -o0 grep -v UI | stdbuf -o0 tee -a log + +Build with ASAN +--------------- + +Building Nvim with Clang sanitizers (Address Sanitizer: ASan, Undefined +Behavior Sanitizer: UBSan, Memory Sanitizer: MSan, Thread Sanitizer: TSan) is +a good way to catch undefined behavior, leaks and other errors as soon as they +happen. It's significantly faster than Valgrind. + +Requires clang 3.4 or later: + + clang --version + +Build Nvim with sanitizer instrumentation: + + CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" + +Create a directory to store logs: + + mkdir -p "$HOME/logs" + +Enable the sanitizer(s) via these environment variables: + + # Change to detect_leaks=1 to detect memory leaks (slower). + export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" + export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + + export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan" + +Logs will be written to `${HOME}/logs/*san.PID`. + +TUI debugging +------------- + +### TUI troubleshoot + +Nvim logs its internal terminfo state at 'verbose' level 3. This makes it +possible to see exactly what terminfo values Nvim is using on any system. + + nvim -V3log + +### TUI trace + +The ancient `script` command is still the "state of the art" for tracing +terminal behavior. The libvterm `vterm-dump` utility formats the result for +human-readability. + +Record a Nvim terminal session and format it with `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. + +### TUI redraw + +Set the 'writedelay' option to see where and when the UI is painted. + + :set writedelay=1 + +### Terminal reference + +- `man terminfo` +- http://bazaar.launchpad.net/~libvterm/libvterm/trunk/view/head:/doc/seqs.txt +- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + +Nvim lifecycle +-------------- + +Following describes how Nvim processes input. + +Consider a typical Vim-like editing session: + +01. Vim displays the welcome screen 02. User types: `:` 03. Vim enters command-line mode 04. User types: `edit README.txt<CR>` @@ -41,16 +135,14 @@ a typical editing session: 21. User types: `word<ESC>` 22. Vim inserts "word" at the beginning and returns to normal mode -Note that we have split user actions into sequences of inputs that change the -state of the editor. While there's no documentation about a "g command -mode" (step 16), internally it is implemented similarly to "operator-pending -mode". +Note that we split user actions into sequences of inputs that change the state +of the editor. While there's no documentation about a "g command mode" (step +16), internally it is implemented similarly to "operator-pending mode". -From this we can see that Vim has the behavior of a input-driven state -machine (more specifically, a pushdown automaton since it requires a stack for +From this we can see that Vim has the behavior of an input-driven state machine +(more specifically, a pushdown automaton since it requires a stack for transitioning back from states). Assuming each state has a callback responsible -for handling keys, this pseudocode (a python-like language) shows a good -representation of the main program loop: +for handling keys, this pseudocode represents the main program loop: ```py def state_enter(state_callback, data): @@ -126,12 +218,11 @@ def insert_state(data, key): return true ``` -While the actual code is much more complicated, the above gives an idea of how -Neovim is organized internally. Some states like the `g_command_state` or -`get_operator_count_state` do not have a dedicated `state_enter` callback, but -are implicitly embedded into other states (this will change later as we continue -the refactoring effort). To start reading the actual code, here's the -recommended order: +The above gives an idea of how Nvim is organized internally. Some states like +the `g_command_state` or `get_operator_count_state` do not have a dedicated +`state_enter` callback, but are implicitly embedded into other states (this +will change later as we continue the refactoring effort). To start reading the +actual code, here's the recommended order: 1. `state_enter()` function (state.c). This is the actual program loop, note that a `VimState` structure is used, which contains function pointers @@ -152,16 +243,17 @@ modes managed by the `state_enter` loop: - insert mode: `insert_{enter,check,execute}()`(`edit.c`) - terminal mode: `terminal_{enter,execute}()`(`terminal.c`) -### Async event support +Async event support +------------------- -One of the features Neovim added is the support for handling arbitrary +One of the features Nvim added is the support for handling arbitrary asynchronous events, which can include: -- msgpack-rpc requests +- RPC requests - job control callbacks -- timers (not implemented yet but the support code is already there) +- timers -Neovim implements this functionality by entering another event loop while +Nvim implements this functionality by entering another event loop while waiting for characters, so instead of: ```py @@ -171,7 +263,7 @@ def state_enter(state_callback, data): while state_callback(data, key) # invoke the callback for the current state ``` -Neovim program loop is more like: +Nvim program loop is more like: ```py def state_enter(state_callback, data): @@ -182,9 +274,31 @@ def state_enter(state_callback, data): where `event` is something the operating system delivers to us, including (but not limited to) user input. The `read_next_event()` part is internally -implemented by libuv, the platform layer used by Neovim. +implemented by libuv, the platform layer used by Nvim. -Since Neovim inherited its code from Vim, the states are not prepared to receive +Since Nvim inherited its code from Vim, the states are not prepared to receive "arbitrary events", so we use a special key to represent those (When a state receives an "arbitrary event", it normally doesn't do anything other update the screen). + +Main loop +--------- + +The `Loop` structure (which describes `main_loop`) abstracts multiple queues +into one loop: + + uv_loop_t uv; + MultiQueue *events; + MultiQueue *thread_events; + MultiQueue *fast_events; + +`loop_poll_events` checks `Loop.uv` and `Loop.fast_events` whenever Nvim is +idle, and also at `os_breakcheck` intervals. + +MultiQueue is cool because you can attach throw-away "child queues" trivially. +For example `do_os_system()` does this (for every spawned process!) to +automatically route events onto the `main_loop`: + + Process *proc = &uvproc.process; + MultiQueue *events = multiqueue_new_child(main_loop.events); + proc->events = events; |