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
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
Run the fast local gate while iterating, then run the complete gate before requesting review:
make lint # Formatting + changed Credo + compile warnings + incremental Dialyzer + ExDNA + Reach
make lint.full # Full formatting + Credo + compile warnings + classic Dialyzer + ExDNA + Reach
mix test --warnings-as-errors # Tests
CI also runs full Credo, ExDNA, Reach, and classic Dialyzer before merge.
Project Layout
See AGENTS.md (in the repo root) for the full project structure, coding standards, and conventions. The highlights:
@specon 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.ex→test/minga/buffer/document_test.exs - Property-based tests with StreamData for data structures
Key Documentation
| Doc | What it covers |
|---|---|
| README.md | Project overview and quick start |
| docs/ARCHITECTURE.md | Two-process design, supervision, port protocol |
AGENTS.md | Coding standards, project structure, how to add features |
How to Add Things
A new command
- Define in
Minga.Command.Registrywith name + description - Add keybinding in
Minga.Keymap.Defaults - Implement in the appropriate
lib/minga/editor/commands/*.exmodule - Test the command and the keybinding lookup
A new motion
- Add function to the appropriate
lib/minga/motion/*.exmodule with@spec - Wire into
Minga.Mode.NormalandMinga.Mode.OperatorPending - Test against known buffer content, including Unicode edge cases
A new text object
- Add function to
Minga.TextObjectwith@spec - Register in
Minga.Mode.OperatorPending - Test: cursor inside, cursor outside, nested, empty content
A new render command (both sides)
- Add opcode + encoder in
Minga.Port.Protocol - Add decoder + handler in
zig/src/protocol.zigandzig/src/renderer.zig - 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, :debugThis shows output like:
[render:layout] 12µs
[render:scroll] 45µs
[render:content] 89µs
[render:agent_content] 4µ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
| Event | Metadata | What it measures |
|---|---|---|
[:minga, :render, :pipeline] | window_count | Full render frame |
[:minga, :render, :stage] | stage atom | Individual render stage |
[:minga, :input, :dispatch] | Keystroke through input router | |
[:minga, :command, :execute] | command atom | Named command execution |
[:minga, :port, :emit] | byte_count | Protocol 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 descriptionTypes: feat, fix, refactor, test, docs, chore Scopes: buffer, port, editor, mode, keymap, zig, cli
Examples:
feat(buffer): implement gap buffer with cursor movementfix(editor): reparse highlights on normal-mode operatorstest(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