Contributing to Minga

Copy Markdown View Source

Thanks for your interest! Minga is in early development and contributions are welcome, whether that's bug reports, feature ideas, or code.

Build from source

Minga is two programs: an Elixir app (editor logic) and a Zig binary (terminal rendering). You need both toolchains plus Erlang. A version manager makes this painless.

Install the toolchain

Using asdf or mise:

asdf plugin add erlang
asdf plugin add elixir
asdf plugin add zig

Clone and build

git clone https://github.com/jsmestad/minga.git
cd minga
asdf install          # Installs pinned Erlang, Elixir, Zig from .tool-versions
mix deps.get
mix compile           # Builds both Elixir and Zig

The first build takes a few minutes (Zig compiles tree-sitter grammars for 24 languages). After that, rebuilds are incremental and fast.

Run it

bin/minga              # Empty buffer
bin/minga path/to/file # Open a file

Running Tests

mix test                       # Elixir tests
cd zig && zig build test       # Zig renderer tests

Before Committing

All three must pass:

make lint                         # Formatting + Credo + compile warnings
mix test --warnings-as-errors     # Tests
mix dialyzer                      # Typespec consistency

Project Layout

See AGENTS.md (in the repo root) for the full project structure, coding standards, and conventions. The highlights:

  • @spec on every public function: Elixir 1.19's type system is strict
  • Pattern matching over if/cond: multi-clause functions preferred
  • Test files mirror lib/: lib/minga/buffer/document.extest/minga/buffer/document_test.exs
  • Property-based tests with StreamData for data structures

Key Documentation

DocWhat it covers
README.mdProject overview and quick start
docs/ARCHITECTURE.mdTwo-process design, supervision, port protocol
docs/PERFORMANCE.mdOptimization roadmap with BEAM-specific techniques
AGENTS.mdCoding standards, project structure, how to add features

How to Add Things

A new command

  1. Define in Minga.Command.Registry with name + description
  2. Add keybinding in Minga.Keymap.Defaults
  3. Implement in the appropriate lib/minga/editor/commands/*.ex module
  4. Test the command and the keybinding lookup

A new motion

  1. Add function to the appropriate lib/minga/motion/*.ex module with @spec
  2. Wire into Minga.Mode.Normal and Minga.Mode.OperatorPending
  3. Test against known buffer content, including Unicode edge cases

A new text object

  1. Add function to Minga.TextObject with @spec
  2. Register in Minga.Mode.OperatorPending
  3. Test: cursor inside, cursor outside, nested, empty content

A new render command (both sides)

  1. Add opcode + encoder in Minga.Port.Protocol
  2. Add decoder + handler in zig/src/protocol.zig and zig/src/renderer.zig
  3. Test encode/decode round-trip on both sides

Performance Debugging with Telemetry

Minga instruments the keystroke-to-render critical path with :telemetry spans. Set :log_level_render to :debug in your config to see per-stage timing in *Messages* (press SPC b m to view):

# In ~/.config/minga/config.exs
set :log_level_render, :debug

This shows output like:

[render:invalidation] 0µs
[render:layout] 12µs
[render:scroll] 45µs
[render:content] 89µs
[render:chrome] 34µs
[render:compose] 18µs
[render:emit] 22µs
[render:total] 224µs
[input:dispatch] 312µs
[command:move_down] 8µs
[port:emit] 48µs (1234 bytes)

Available telemetry events

EventMetadataWhat it measures
[:minga, :render, :pipeline]window_countFull render frame
[:minga, :render, :stage]stage atomIndividual render stage
[:minga, :input, :dispatch]Keystroke through input router
[:minga, :command, :execute]command atomNamed command execution
[:minga, :port, :emit]byte_countProtocol encoding + port write

Attaching custom handlers

For deeper analysis (histograms, percentile tracking), attach your own handler in IEx or a config file:

:telemetry.attach("my-handler", [:minga, :render, :pipeline, :stop], fn _event, measurements, metadata, _config ->
  duration_us = System.convert_time_unit(measurements.duration, :native, :microsecond)
  IO.puts("Frame: #{duration_us}µs, windows: #{metadata.window_count}")
end, nil)

Running the overhead benchmark

To verify telemetry overhead is negligible:

mix run benchmarks/telemetry_overhead.exs

Commit Messages

type(scope): short description

Types: feat, fix, refactor, test, docs, chore Scopes: buffer, port, editor, mode, keymap, zig, cli

Examples:

  • feat(buffer): implement gap buffer with cursor movement
  • fix(editor): reparse highlights on normal-mode operators
  • test(buffer): add property-based tests for insert/delete

Updating Documentation

When you finish a feature or change the architecture, update:

  • docs/ARCHITECTURE.md: if you add process types, opcodes, or change supervision
  • docs/PERFORMANCE.md: mark optimizations as done