Skip to content

Latest commit

 

History

History
108 lines (69 loc) · 6.5 KB

CONTRIBUTING.md

File metadata and controls

108 lines (69 loc) · 6.5 KB

Contributions

issues and discussions

I'd like to keep the issues down to just known bugs and things that I'm confident about implementing. Support questions and feature requests should go in the discussion board.

Dev setup, etc...

setup

Please run scripts/setup-hooks to set up precommit hooks which will typecheck, run eslint and prettier.

how to use local version

I use lazy.nvim with the "dev" option link

-- lazy config
require("lazy").setup {
...
  dev = {
    path = "~/src",
  }
}

-- magenta config
{
  "dlants/magenta.nvim",
  dev = true,
  lazy = false,
  build = "npm install --frozen-lockfile",
  config = function()
    require('magenta').setup()
  end
}

This will load the plugin from ~/src/magenta.nvim instead of from git. You can toggle the dev = true option to go back to the official version in github.

how to test

All significant changes should come with accompanying tests. There should go into *.spec.ts files placed adjacent to the code they are testing. So if the "meat" of the functionality you're testing is in a.ts, you should put the test for that in a.spec.ts in the same directory.

To run tests, use npx vitest. You can also mark certain tests to run via describe.only or it.only and then run npx vitest filter to just tests marked as only from test files matching filter.

Some test make use of the vitest snapshot feature. To update a snapshot, use npx vitest -u.

how to see logs

You can run npx tsx node_modules/nvim-node/src/cli/index.ts logs magenta to see logs generated by the plugin.

You can run npx tsx node_modules/nvim-node/src/cli/index.ts logs test to see logs generated when running tests.

These will contain all of the logs sent via nvim.logger. It will also log all of the RPC messages sent between neovim and the node process.

how to debug

When invoking tests, you can run npx vitest filter --inspect-wait. This will print a url that you can open in your browser to bring up a debug console. This will stop on debugger statements encountered in the code, and will allow you to step through the code.

Code orientation

startup

When neovim starts, the start function is run in init.lua. This kicks off the node process. Neovim creates a socket and passes it to the node process via the NVIM env var.

The entrypoint for the node process is index.ts. This checks for the presence of the env variable, establishes the nvim-node connection, and kicks off the static Magenta.start method.

The start function in magenta.ts sets up the notification listeners and calls the require('magenta').bridge method from init.lua. This passes the channelId back to the lua side, so that it can finish initializing the magenta lua module, which we can then invoke to communicate back to the plugin.

Most commands are defined in init.lua, though some are defined on the node side, like sidebar.ts

testing setup

The startup for tests is a little different, handled in test/preamble.ts. Here, the node process starts first. In every tests, it creates an nvim socket, and then starts nvim with the --listen flag to attach to that socket. It then proceeds to init the magenta plugin against that socket, as in the normal startup sequence.

Each test gets a fresh neovim instance. Convenience methods for interacting with the nvim/plugin setup live in test/driver.ts.

architecture

The project is organized according to the elm architecture, or TEA.

Each component consists of:

  • Model - the current state of the component. This should be a serializable object. Usually initialized via the initModel function.
  • Msg - something that should trigger the state to change. This should be a serializable object.
  • update - something that takes a model and a message, and returns the next model. This should be a pure function. It can optionally also return a thunk, which is where all side effects, like asynchronous code, should go.
  • view - a function that takes a model and returns what the display buffer should look like. This is done in a declarative way using the d template literal (I don't remember why I chose "d"... dynamic text maybe?). You can attach bindings to different parts of the text via withBindings.

So the general flow is:

  • you have your model. You render it with a view, and some bindings.
  • the user takes some action on the view. This triggers a command or a binding, which dispatches a message.
  • the message is combined with the current model via the update function, which creates a next model, and potentially a thunk.
  • the next model is rendered, and the thunk is executed, potentially resulting in more dispatches.
  • rinse and repeat.

The key files are:

  • tea.ts - sets up the render - dispatch - update cycle
  • view.ts - implements the VDOM-like declarative rendering template

code organization

  • magenta.ts - the entrypoint. Sets up the communication with the neovim process, initializes the app, receives commands from the neovim process.
  • sidebar.ts - manages the chat sidebar state. Mostly just for showing/hiding it, managing the keybindings, etc...
  • toolManager.ts - manages the tool executions and rendering. Each tool execution has an id, and this contains the state mapping that id to the execution state. Also provides tools for dealing with generic "tools".
  • provider.ts - abstraction around an LLM provider. Creates general ways of declaring tools, messages and other interactions with various providers.
  • chat.ts - the top-level of the TEA app. Contains messages, context, etc... this is what is displayed in the chat display buffer.