This describes how Nev works under the hood, and is mainly useful for contributors
Platform (either GuiPlatform or TerminalPlatform)servicesAppAppPlatformApp is the central hub which connects everything. There is a bunch of stuff in there which should be moved out over time (e.g. most commands defined in app.nim)
app
Services type stores all services and allows access to any serviceDocument type represents a document (not necessarily text) which is usually backed by a file on disk.TextDocument is a subtype of Document and represents a text document.
Buffer, a CRDT using a Rope)Editor is the abstract base type for editorsTextDocumentEditor is an Editor for a text document
App
|
---------------- uses -------------
| | |
v v v
Services: LayoutService, CommandService, PluginService, ...
Editor Document
^ ^
| |----------------------
| | |
TextDocumentEditor ---- edits ---> TextDocument (ModelDocument, but this is not working right now)
The layout tree contains Views: View
^
|
--------------------------------------------------------------------...
| | | |
EditorView TerminalView DebuggerView Layout
(contains TextDocumentEditor) (draws a terminal) ^
|
--------------------------------------------...
| | |
each of these contains views ---> HorizontalLayout VerticalLayout TabLayout
Platform
^
|
--------------------
| |
GuiPlatform TerminalPlatform
Modules allow parts of the editor to be compiled as separate DLLs. This is primarily used for development speed – the CI build is statically linked. Modules can only interact with exposed APIs (with the apprtl pragma).
Modules live in the modules/ directory and can be either:
stats.nim, dashboard.nimterminal/terminal.nimModules fall into two categories:
src/). E.g. stats and terminal.dashboard, vcs_git, language_server_lsp, terminal, debugger, etc. (((core) core_modules) extra_modules)
core (src/) ──depends on──> stats, terminal
│
└── loaded via ──> module_imports.nim (static) OR native_plugins/*.dll (dynlib)
│
├── extra_modules (core doesn't depend on these)
│ ├── dashboard
│ ├── vcs_git, vcs_perforce
│ ├── language_server_lsp, language_server_ctags, ...
│ ├── debugger
│ └── ...
│
└── core_modules (core depends on these)
├── stats
├── command_component, hover_component, ...
└── terminal
The key mechanism is module_base.nim, which sets up the implModule compile-time constant and the rtl pragma:
-d:useDynlib: everything is statically linked, implModule is always true-d:useDynlib: implModule is true only for the module being built as a DLL (matched via -d:nevModuleName=<name>)Dependencies between modules are declared via #use comments at the top of the main .nim file:
#use stats
A minimal module requires:
module_base with the currentSourcePath2 setupimplModule blockrtl pragmainit_module_<name> proc for initializationwhen implModule:# modules/my_module.nim
import service
const currentSourcePath2 = currentSourcePath()
include module_base
type
MyService* = ref object of DynamicService
value*: int
func serviceName*(_: typedesc[MyService]): string = "MyService"
# DLL API -- visible to both static and dynlib builds. Make sure name is unique.
{.push rtl, gcsafe, raises: [].}
proc myServiceGetValue(self: MyService): int
{.pop.}
# Wrapper
{.push inline.}
proc getValue*(self: MyService): int = self.myServiceGetValue()
{.pop.}
# Implementation -- only compiled once
when implModule:
import misc/custom_logger
logCategory "my-module"
proc myServiceGetValue(self: MyService): int =
self.value
# Required: init function called when the module is loaded
proc init_module_my_module*() {.cdecl, exportc, dynlib.} =
log lvlInfo, "Initializing my_module"
let services = getServices()
if services == nil:
return
services.addService(MyService())
# Optional: shutdown function called when the editor exits
proc shutdown_module_my_module*() {.cdecl, exportc, dynlib.} =
log lvlInfo, "Shutting down my_module"
Static (default, used by CI):
All modules are compiled into the main binary. The build.nim script generates src/module_imports.nim which imports all modules and provides initModules() / shutdownModules() procs.
Dynamic (development):
Run nim c build.nim && nim c -r build.nim to build dirty modules as DLLs into native_plugins/. Use -f to force rebuild, -s for single-threaded, -r for release mode.
In desktop_main.nim, modules are loaded after services are initialized:
import module_imports; initModules() – calls each module’s init functionnative_plugins/ directory, loads each .dll with loadLib(), resolves and calls init_module_<name> by symbol lookupOn shutdown, shutdown_module_<name> is called for each loaded module (or shutdownModules() for static builds).
Important terms: Host refers to the editor (native code), guest or plugin refers to the wasm side
Important files and types:
PluginService: Service which handles loading of plugin manifests, deciding which plugins to load and whenPluginManifest: Plain data containing some meta data about the plugin.PluginSystem: Base type for the plugin system (at the moment only wasm)PluginSystemWasm: PluginSystem subtype which handles wasm engine setup and manages multiple versions of the plugin apiPluginApiBase: Base type for a version of the plugin apiPluginApi: PluginApiBase subtype which implements a specific version of the plugin api and handles instantiation of wasm modulesnimwasmtime and the API defined in the .wit file