aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/README.md190
1 files changed, 190 insertions, 0 deletions
diff --git a/src/nvim/README.md b/src/nvim/README.md
new file mode 100644
index 0000000000..e4939d94fd
--- /dev/null
+++ b/src/nvim/README.md
@@ -0,0 +1,190 @@
+## Source code overview
+
+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.
+
+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.
+
+If you are looking for module-specific details, it is best to read the source
+code. Some files are extensively commented at the top(eg: terminal.c,
+screen.c).
+
+### Top-level program loops
+
+First let's understand what a Vim-like program does by analyzing the workflow of
+a typical editing session:
+
+01. Vim dispays the welcome screen
+02. User types: `:`
+03. Vim enters command-line mode
+04. User types: `edit README.txt<CR>`
+05. Vim opens the file and returns to normal mode
+06. User types: `G`
+07. Vim navigates to the end of the file
+09. User types: `5`
+10. Vim enters count-pending mode
+11. User types: `d`
+12. Vim enters operator-pending mode
+13. User types: `w`
+14. Vim deletes 5 words
+15. User types: `g`
+16. Vim enters the "g command mode"
+17. User types: `g`
+18. Vim goes to the beginning of the file
+19. User types: `i`
+20. Vim enters insert mode
+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".
+
+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
+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:
+
+```py
+def state_enter(state_callback, data):
+ do
+ key = readkey() # read a key from the user
+ while state_callback(data, key) # invoke the callback for the current state
+```
+
+That is, each state is entered by calling `state_enter` and passing a
+state-specific callback and data. Here is a high-level pseudocode for a program
+that implements something like the workflow described above:
+
+```py
+def main()
+ state_enter(normal_state, {}):
+
+def normal_state(data, key):
+ if key == ':':
+ state_enter(command_line_state, {})
+ elif key == 'i':
+ state_enter(insert_state, {})
+ elif key == 'd':
+ state_enter(delete_operator_state, {})
+ elif key == 'g':
+ state_enter(g_command_state, {})
+ elif is_number(key):
+ state_enter(get_operator_count_state, {'count': key})
+ elif key == 'G'
+ jump_to_eof()
+ return true
+
+def command_line_state(data, key):
+ if key == '<cr>':
+ if data['input']:
+ execute_ex_command(data['input'])
+ return false
+ elif key == '<esc>'
+ return false
+
+ if not data['input']:
+ data['input'] = ''
+
+ data['input'] += key
+ return true
+
+def delete_operator_state(data, key):
+ count = data['count'] or 1
+ if key == 'w':
+ delete_word(count)
+ elif key == '$':
+ delete_to_eol(count)
+ return false # return to normal mode
+
+def g_command_state(data, key):
+ if key == 'g':
+ go_top()
+ elif key == 'v':
+ reselect()
+ return false # return to normal mode
+
+def get_operator_count_state(data, key):
+ if is_number(key):
+ data['count'] += key
+ return true
+ unshift_key(key) # return key to the input buffer
+ state_enter(delete_operator_state, data)
+ return false
+
+def insert_state(data, key):
+ if key == '<esc>':
+ return false # exit insert mode
+ self_insert(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:
+
+1. `state_enter()` function(state.c). This is the actual program loop,
+ note that a `VimState` structure is used, which contains function pointers
+ for the callback and state data.
+2. `main()` function(main.c). After all startup, `normal_enter` is called
+ at the end of function to enter normal mode.
+3. `normal_enter()` function(normal.c) is a small wrapper for setting
+ up the NormalState structure and calling `state_enter`.
+4. `normal_check()` function(normal.c) is called before each iteration of
+ normal mode.
+5. `normal_execute()` function(normal.c) is called when a key is read in normal
+ mode.
+
+The basic structure described for normal mode in 3, 4 and 5 is used for other
+modes managed by the `state_enter` loop:
+
+- command-line mode: `command_line_{enter,check,execute}()`(`ex_getln.c`)
+- insert mode: `insert_{enter,check,execute}()`(`edit.c`)
+- terminal mode: `terminal_{enter,execute}()`(`terminal.c`)
+
+### Async event support
+
+One of the features Neovim added is the support for handling arbitrary
+asynchronous events, which can include:
+
+- msgpack-rpc requests
+- job control callbacks
+- timers(not implemented yet but the support code is already there)
+
+Neovim implements this functionality by entering another event loop while
+waiting for characters, so instead of:
+
+```py
+def state_enter(state_callback, data):
+ do
+ key = readkey() # read a key from the user
+ while state_callback(data, key) # invoke the callback for the current state
+```
+
+Neovim program loop is more like:
+
+```py
+def state_enter(state_callback, data):
+ do
+ event = read_next_event() # read an event from the operating system
+ while state_callback(data, event) # invoke the callback for the current state
+```
+
+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.
+
+Since Neovim 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).