meolix

A terminal Helix-like text editor implemented in MoonBit with moonbitlang/async.

editor
helix
async
moon add Yoorkin/meolix@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
2 months ago
Downloads
24
README

#Yoorkin/meolix

meolix is a terminal Helix-like text editor written in MoonBit.

The editor logic, buffer model, modal state, selection operations, command handling, rendering, and async file IO are implemented in MoonBit. A small native C terminal bridge is used only for OS terminal control: raw keyboard input, ANSI/VT mode, terminal restore, and window size.

#Run

This project targets MoonBit native because it uses moonbitlang/async and a native terminal bridge. On macOS and other Unix-like systems, run:

moon check --target native moon test --target native moon run --target native cmd/mx -- notes.txt moon run --target native cmd/mx -- --help

On Windows, run native builds from a Visual Studio Developer shell, or initialize it first:

$bat = 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat' $cmd = '"' + $bat + '" -arch=x64 && moon run --target native cmd/mx -- notes.txt' cmd.exe /c $cmd

The same MoonBit commands then apply:

moon check --target native moon test --target native moon run --target native cmd/mx -- notes.txt moon run --target native cmd/mx -- --help

On macOS, enable Option-as-Meta in your terminal profile if you want Alt-* bindings such as Alt-d and Alt-.; otherwise Option combinations are delivered as normal Unicode text input.

#Interactive Keys

  • Key meanings follow the basic normal/insert/select mappings from the Helix keymap: https://docs.helix-editor.com/keymap.html.
  • h/j/k/l or arrows move the cursor.
  • w/e/b and W/E/B move by word starts and word ends.
  • f/t/F/T<char> finds or moves till the next/previous character. Like Helix, this is not limited to the current line.
  • gg, ge, gh, gl, gs, and gf jump to the file start/end, line start/end, first non-whitespace character, and file path under the cursor.
  • G jumps to a line number; with a count, for example 42G, it jumps directly.
  • ga, gm, and g. jump to the alternate file, last modified file, and last modification location.
  • PgUp/PgDn, Ctrl-B/F, and Ctrl-U/D move by page or half-page.
  • Alt-. repeats the last supported motion from f/t/F/T, m m, [, or ] commands.
  • Ctrl-S, Ctrl-O, and Ctrl-I/Tab save the current location to the jumplist, jump backward, and jump forward.
  • Counts work for common movement keys, for example 10j and 42G.
  • i, a, I, A, o, and O enter insert mode, append, insert at line start/end, open below, and open above.
  • Esc returns to normal mode or cancels the prompt.
  • v enters select mode.
  • x, %, ;, and Alt-; select the current line, select the whole file, collapse selection, and flip selection anchor/cursor.
  • y, d, c, p, and P yank, delete, change, paste after, and paste before the current selection.
  • r<char>, R, J, ~, ` , and `Alt-`` replace with a character, replace with yanked text, join lines, switch case, lowercase, and uppercase.
  • u and U undo and redo.
  • m m matches brackets.
  • m a <object> and m i <object> select around/inside basic text objects: w, W, p, brackets, braces, angles, quotes, and backticks.
  • [p, ]p, [Space, and ]Space move by paragraph or add blank lines above/below.
  • [g, ]g, [G, and ]G navigate previous, next, first, and last git change for the current file using git diff --unified=0.
  • zt, zz, and zb align the cursor line to top, center, and bottom.
  • zm horizontally centers the cursor in the viewport.
  • Space opens a Helix-style which-key menu. Implemented entries include Space f workspace-root file fuzzy picker, Space F current-directory file picker, Space b buffer picker, Space j jumplist picker, Space g changed-file picker, Space ? command palette, Space / workspace-root line search picker, Space ' reopen last picker, Space c comments, Space C block comments, and Space Alt-c line comments.
  • Space y, Space p, Space P, and Space R use the system clipboard for yank, paste-after, paste-before, and replace. On Windows this uses PowerShell's clipboard commands; on Unix-like systems it tries pbcopy, wl-copy, xclip, or xsel.
  • MoonBit LSP integration includes gd, gy, gi, gr, Space k, Space s, Space S, Space d, Space D, Space r, Space a, and insert Ctrl-X for definition, type definition, implementation, references, hover, symbols, diagnostics, rename, code actions, and completion.
  • Pickers render a Helix-style list plus preview pane. Tab/Down/Ctrl-n select the next entry, Shift-Tab/Up/Ctrl-p select the previous entry, PageUp/PageDown page through matches, Ctrl-t toggles preview, Enter opens the selected entry, and Esc closes the picker.
  • Hover/info popups support Ctrl-D and Ctrl-U scrolling.
  • Picker filtering supports basic fzf-style terms: space-separated terms are combined with AND, !term excludes matches, and %path, %text, %line, or %command restrict following terms to a column.
  • : opens the command prompt.
  • / opens the search prompt, n selects the next match, and N selects the previous match.
  • :w saves.
  • Ctrl-Q quits; press it twice when the buffer is dirty.
  • Insert mode supports Ctrl-W, Alt-D, Ctrl-U, Ctrl-K, and Ctrl-D for word, line, and character deletion, plus arrows, Home, End, Backspace, Delete, Enter, Ctrl-R register insertion, Ctrl-S undo checkpoint, and Ctrl-X LSP completion.

Not yet included: tree-sitter syntax-node text objects, true multi-window split layout, macros, shell filters, and signature-help behavior. These are not listed in meolix's in-editor key prompts until they are backed by working behavior.

#Commands

  • :w writes the file.
  • :w path writes to path and updates the current buffer path.
  • :q quits when the buffer is clean.
  • :q! quits even when the buffer is dirty.
  • :wq writes and quits.
  • :go N jumps to line N.
  • :open path, :edit path, and :e path switch to another file; dirty buffers stay open in memory and can be returned to from the buffer picker.
  • :new opens a scratch buffer when the current buffer is clean.
  • :insert TEXT inserts text at the cursor.
  • :append TEXT appends text to the current line.
  • Prompt history follows Helix-style navigation: Ctrl-P/Up selects the previous command or search entry, and Ctrl-N/Down moves forward.

#Headless Testing

The executable supports scripted runs for tests and CI. This is not the editor UI; normal use should launch the terminal interface without --cmd or --script.

moon run --target native cmd/mx -- notes.txt --headless --cmd ":go 2" --cmd "key x" --cmd "key y" moon run --target native cmd/mx -- notes.txt --headless --script edit.meolix

Script format:

# comments are ignored :go 2 key x key y key p text inserted text

#
Cursor

pub(all) struct Cursor {
row : Int
col : Int
} derive(Eq,
Debug
)

#
Document

pub(all) struct Document {
path : String?
lines : Array[String]
dirty : Bool
} derive(Eq,
Debug
)

#
DriverResult

pub(all) struct DriverResult {
editor : Editor
log : Array[String]
} derive(Eq,
Debug
)

#
Editor

pub(all) struct Editor {
doc : Document
mode : Mode
cursor : Cursor
selection_anchor : Cursor?
register : String
register_is_line : Bool
status : String
should_quit : Bool
undo_stack : Array[String]
redo_stack : Array[String]
} derive(Eq,
Debug
)

#
Editor::add_newline_above

fn Editor::add_newline_above(self : Editor) -> Unit

#
Editor::add_newline_below

fn Editor::add_newline_below(self : Editor) -> Unit

#
Editor::apply_command

fn Editor::apply_command(self : Editor, command : String) -> Unit

#
Editor::apply_editor_command

fn Editor::apply_editor_command(self : Editor, command : EditorCommand) -> Unit

#
Editor::apply_key

fn Editor::apply_key(self : Editor, key : String) -> Unit

#
Editor::backspace

fn Editor::backspace(self : Editor) -> Unit

#
Editor::buffer_text

fn Editor::buffer_text(self : Editor) -> String

#
Editor::change_number_under_cursor

fn Editor::change_number_under_cursor(self : Editor, delta : Int) -> Unit

#
Editor::change_selection

fn Editor::change_selection(self : Editor) -> Unit

#
Editor::change_selection_without_yank

fn Editor::change_selection_without_yank(self : Editor) -> Unit

#
Editor::character_info

fn Editor::character_info(self : Editor) -> String

#
Editor::collapse_selection

fn Editor::collapse_selection(self : Editor) -> Unit

#
Editor::delete_char

fn Editor::delete_char(self : Editor) -> Unit

#
Editor::delete_line

fn Editor::delete_line(self : Editor) -> Unit

#
Editor::delete_selection

fn Editor::delete_selection(self : Editor) -> Unit

#
Editor::delete_selection_without_yank

fn Editor::delete_selection_without_yank(self : Editor) -> Unit

#
Editor::delete_surround

fn Editor::delete_surround(self : Editor, marker : Char) -> Unit

#
Editor::delete_to_line_end

fn Editor::delete_to_line_end(self : Editor) -> Unit

#
Editor::delete_to_line_start

fn Editor::delete_to_line_start(self : Editor) -> Unit

#
Editor::delete_word_backward

fn Editor::delete_word_backward(self : Editor) -> Unit

#
Editor::delete_word_forward

fn Editor::delete_word_forward(self : Editor) -> Unit

#
Editor::enter_insert

fn Editor::enter_insert(self : Editor) -> Unit

#
Editor::enter_normal

fn Editor::enter_normal(self : Editor) -> Unit

#
Editor::enter_select

fn Editor::enter_select(self : Editor) -> Unit

#
Editor::extend_selection_to_line_bounds

fn Editor::extend_selection_to_line_bounds(self : Editor) -> Unit

#
Editor::find_char_backward

fn Editor::find_char_backward(self : Editor, ch : Char, till : Bool) -> Bool

#
Editor::find_char_forward

fn Editor::find_char_forward(self : Editor, ch : Char, till : Bool) -> Bool

#
Editor::flip_selection

fn Editor::flip_selection(self : Editor) -> Unit

#
Editor::indent_selection

fn Editor::indent_selection(self : Editor) -> Unit

#
Editor::insert_at_line_end

fn Editor::insert_at_line_end(self : Editor) -> Unit

#
Editor::insert_at_line_start

fn Editor::insert_at_line_start(self : Editor) -> Unit

#
Editor::insert_char

fn Editor::insert_char(self : Editor, ch : Char) -> Unit

#
Editor::insert_completion

fn Editor::insert_completion(self : Editor, text : String) -> Unit

#
Editor::insert_text

fn Editor::insert_text(self : Editor, text : String) -> Unit

#
Editor::is_selected

fn Editor::is_selected(self : Editor, row : Int, col : Int) -> Bool

#
Editor::join_lines

fn Editor::join_lines(self : Editor) -> Unit

#
Editor::lower_selection

fn Editor::lower_selection(self : Editor) -> Unit

#
Editor::match_bracket

fn Editor::match_bracket(self : Editor) -> Bool

#
Editor::move_down

fn Editor::move_down(self : Editor) -> Unit

#
Editor::move_file_end

fn Editor::move_file_end(self : Editor) -> Unit

#
Editor::move_file_start

fn Editor::move_file_start(self : Editor) -> Unit

#
Editor::move_left

fn Editor::move_left(self : Editor) -> Unit

#
Editor::move_line_end

fn Editor::move_line_end(self : Editor) -> Unit

#
Editor::move_line_first_non_whitespace

fn Editor::move_line_first_non_whitespace(self : Editor) -> Unit

#
Editor::move_line_start

fn Editor::move_line_start(self : Editor) -> Unit

#
Editor::move_next_long_word

fn Editor::move_next_long_word(self : Editor) -> Unit

#
Editor::move_next_long_word_end

fn Editor::move_next_long_word_end(self : Editor) -> Unit

#
Editor::move_next_word

fn Editor::move_next_word(self : Editor) -> Unit

#
Editor::move_next_word_end

fn Editor::move_next_word_end(self : Editor) -> Unit

#
Editor::move_paragraph_next

fn Editor::move_paragraph_next(self : Editor) -> Unit

#
Editor::move_paragraph_prev

fn Editor::move_paragraph_prev(self : Editor) -> Unit

#
Editor::move_prev_long_word

fn Editor::move_prev_long_word(self : Editor) -> Unit

#
Editor::move_prev_word

fn Editor::move_prev_word(self : Editor) -> Unit

#
Editor::move_right

fn Editor::move_right(self : Editor) -> Unit

#
Editor::move_to_column

fn Editor::move_to_column(self : Editor, column : Int) -> Unit

#
Editor::move_up

fn Editor::move_up(self : Editor) -> Unit

#
Editor::open_above

fn Editor::open_above(self : Editor) -> Unit

#
Editor::open_below

fn Editor::open_below(self : Editor) -> Unit

#
Editor::paste_after

fn Editor::paste_after(self : Editor) -> Unit

#
Editor::paste_before

fn Editor::paste_before(self : Editor) -> Unit

#
Editor::redo

fn Editor::redo(self : Editor) -> Unit

#
Editor::render

fn Editor::render(self : Editor, height? : Int) -> String

#
Editor::replace_range

fn Editor::replace_range(self : Editor, start_row : Int, start_col : Int, end_row : Int, end_col : Int, text : String) -> Unit

#
Editor::replace_selection_with_char

fn Editor::replace_selection_with_char(self : Editor, ch : Char) -> Unit

#
Editor::replace_surround

fn Editor::replace_surround(self : Editor, old : Char, replacement : Char) -> Unit

#
Editor::replace_with_yanked

fn Editor::replace_with_yanked(self : Editor) -> Unit

#
Editor::save

async fn Editor::save(self : Editor) -> Unit

#
Editor::save_as

async fn Editor::save_as(self : Editor, path : String) -> Unit

#
Editor::search_backward

fn Editor::search_backward(self : Editor, query : String) -> Bool

#
Editor::search_forward

fn Editor::search_forward(self : Editor, query : String) -> Bool

#
Editor::search_word_under_cursor

fn Editor::search_word_under_cursor(self : Editor) -> String?

#
Editor::select_all

fn Editor::select_all(self : Editor) -> Unit

#
Editor::select_line

fn Editor::select_line(self : Editor) -> Unit

#
Editor::select_textobject

fn Editor::select_textobject(self : Editor, marker : Char, inside : Bool) -> Unit

#
Editor::selected_text

fn Editor::selected_text(self : Editor) -> String

#
Editor::selection_bounds

fn Editor::selection_bounds(self : Editor) -> (Cursor, Cursor)?

#
Editor::set_status

fn Editor::set_status(self : Editor, value : String) -> Unit

#
Editor::split_line

fn Editor::split_line(self : Editor) -> Unit

#
Editor::surround_selection

fn Editor::surround_selection(self : Editor, marker : Char) -> Unit

#
Editor::switch_selection_case

fn Editor::switch_selection_case(self : Editor) -> Unit

#
Editor::toggle_block_comment

fn Editor::toggle_block_comment(self : Editor) -> Unit

#
Editor::toggle_line_comment

fn Editor::toggle_line_comment(self : Editor) -> Unit

#
Editor::trim_selection

fn Editor::trim_selection(self : Editor) -> Unit

#
Editor::undo

fn Editor::undo(self : Editor) -> Unit

#
Editor::unindent_selection

fn Editor::unindent_selection(self : Editor) -> Unit

#
Editor::upper_selection

fn Editor::upper_selection(self : Editor) -> Unit

#
Editor::word_under_cursor

fn Editor::word_under_cursor(self : Editor) -> String

#
Editor::yank_line

fn Editor::yank_line(self : Editor) -> Unit

#
Editor::yank_selection

fn Editor::yank_selection(self : Editor) -> Unit

#
EditorCommand

pub(all) enum EditorCommand {
Key(String)
Ex(String)
Quit
} derive(Eq,
Debug
)

#
Mode

pub(all) enum Mode {
Normal
Insert
Select
Command
} derive(Eq,
Debug
)

#
WordCategory

type WordCategory derive(Eq,
Debug
)

#
document

fn document(path? : String, text? : String) -> Document

#
empty_editor

fn empty_editor() -> Editor

#
load

async fn load(path : String) -> Editor

#
open_text

fn open_text(path : String, text : String) -> Editor

#
parse_script

fn parse_script(text : String) -> Array[EditorCommand]

#
parse_script_line

fn parse_script_line(line : String) -> EditorCommand?

#
run_commands

async fn run_commands(editor : Editor, commands : Array[EditorCommand], save_on_write? : Bool) -> DriverResult

Source Files

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io