Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions command.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
///|
/// Terminal output command.
///
/// A `Command` is inert data. It only writes bytes when passed to
/// `Tty::execute`.
pub(all) enum Command {
Print(StringView)
EnterAltScreen
LeaveAltScreen
BeginSynchronizedUpdate
EndSynchronizedUpdate
EnableBracketedPaste
DisableBracketedPaste
EnableFocusTracking
DisableFocusTracking
EnableAutoWrap
DisableAutoWrap
PushKeyboardEnhancementFlags(KeyboardEnhancementFlags)
PopKeyboardEnhancementFlags
HideCursor
ShowCursor
CursorUp(Int)
CursorDown(Int)
CursorForward(Int)
CursorBack(Int)
CursorNextLine(Int)
CursorPrevLine(Int)
CursorHorizontalAbsolute(Int)
CursorPosition(Int, Int)
EraseLineAll
EraseLineLeft
EraseLineRight
EraseDisplay
EraseScrollback
SetTopBottomMargins(Int, Int)
ResetTopBottomMargins
ReverseIndex
SetForeground(@color.Color)
SetBackground(@color.Color)
ResetForeground
ResetBackground
SetBold
ResetBold
SetItalic
ResetItalic
SetUnderline
ResetUnderline
SetReverse
ResetReverse
ResetStyle
}

///|
fn Command::write_to(self : Command, buf : @buffer.Buffer) -> Unit {
match self {
Print(text) => buf.write_string_utf8(text)
EnterAltScreen => buf.write_bytes(@vt.enter_alt_screen)
LeaveAltScreen => buf.write_bytes(@vt.leave_alt_screen)
BeginSynchronizedUpdate => buf.write_bytes(@vt.begin_synchronized_update)
EndSynchronizedUpdate => buf.write_bytes(@vt.end_synchronized_update)
EnableBracketedPaste => buf.write_bytes(@vt.enable_bracketed_paste)
DisableBracketedPaste => buf.write_bytes(@vt.disable_bracketed_paste)
EnableFocusTracking => buf.write_bytes(@vt.enable_focus_tracking)
DisableFocusTracking => buf.write_bytes(@vt.disable_focus_tracking)
EnableAutoWrap => buf.write_bytes(@vt.enable_auto_wrap)
DisableAutoWrap => buf.write_bytes(@vt.disable_auto_wrap)
PushKeyboardEnhancementFlags(flags) =>
@vt.write_push_keyboard_enhancement_flags(buf, flags.bits())
PopKeyboardEnhancementFlags =>
buf.write_bytes(@vt.pop_keyboard_enhancement_flags)
HideCursor => buf.write_bytes(@vt.hide_cursor)
ShowCursor => buf.write_bytes(@vt.show_cursor)
CursorUp(n) => @vt.write_cursor_up(buf, n)
CursorDown(n) => @vt.write_cursor_down(buf, n)
CursorForward(n) => @vt.write_cursor_forward(buf, n)
CursorBack(n) => @vt.write_cursor_back(buf, n)
CursorNextLine(n) => @vt.write_cursor_next_line(buf, n)
CursorPrevLine(n) => @vt.write_cursor_prev_line(buf, n)
CursorHorizontalAbsolute(n) => @vt.write_cursor_horizontal_absolute(buf, n)
CursorPosition(row, col) => @vt.write_cursor_position(buf, row, col)
EraseLineAll => buf.write_bytes(@vt.erase_line_all)
EraseLineLeft => buf.write_bytes(@vt.erase_line_left)
EraseLineRight => buf.write_bytes(@vt.erase_line_right)
EraseDisplay => buf.write_bytes(@vt.erase_display)
EraseScrollback => buf.write_bytes(@vt.erase_scrollback)
SetTopBottomMargins(top, bottom) =>
@vt.write_set_top_bottom_margins(buf, top, bottom)
ResetTopBottomMargins => buf.write_bytes(@vt.reset_top_bottom_margins)
ReverseIndex => buf.write_bytes(@vt.reverse_index)
SetForeground(color) => @vt.write_set_foreground(buf, color)
SetBackground(color) => @vt.write_set_background(buf, color)
ResetForeground => buf.write_bytes(@vt.reset_foreground)
ResetBackground => buf.write_bytes(@vt.reset_background)
SetBold => buf.write_bytes(@vt.bold)
ResetBold => buf.write_bytes(@vt.reset_bold)
SetItalic => buf.write_bytes(@vt.italic)
ResetItalic => buf.write_bytes(@vt.reset_italic)
SetUnderline => buf.write_bytes(@vt.underline)
ResetUnderline => buf.write_bytes(@vt.reset_underline)
SetReverse => buf.write_bytes(@vt.reverse)
ResetReverse => buf.write_bytes(@vt.reset_reverse)
ResetStyle => buf.write_bytes(@vt.reset_style)
}
}

///|
/// Execute terminal output commands with one serialized write.
pub async fn Tty::execute(self : Self, commands : Array[Command]) -> Unit {
if commands.length() == 0 {
return
}
let buf = @buffer.Buffer()
for command in commands {
command.write_to(buf)
}
self.write(buf.to_bytes())
}
88 changes: 88 additions & 0 deletions docs/plans/2026-06-15-command-execute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Command Execute

## Goal

Add a first batch of pure output commands so callers can describe terminal
operations as data and execute a command batch with one serialized terminal
write.

## Accepted Design

- Add a root-package `pub(all) enum Command`.
- Keep command values as inert data. No command writes to the terminal until it
is passed to `Tty::execute`.
- Keep `Command::write_to(buf)` private to the root package.
- `Tty::execute(commands)` builds one `@buffer.Buffer`, appends each command in
order, and calls `Tty::write` once. An empty command array is a no-op.
- Only include pure write-only commands in this batch. Exclude terminal query
operations, `with_*` scope helpers, and mouse composite commands for now.
- Add `Print(StringView)` as the text-output command. It writes the string view
as UTF-8 into the command batch buffer and does not escape or sanitize
terminal control bytes embedded in the text.

## Target Files / Surfaces

- `command.mbt`: command enum, private encoder, and `Tty::execute`.
- `moon.pkg`: add `moonbitlang/core/buffer` to normal imports.
- `tty_test.mbt`: black-box tests for command batching and empty batches.
- `pkg.generated.mbti`: generated root public API diff.

## API / Interface Diff

- Root package gains `pub(all) enum Command`.
- Root package gains `pub async fn Tty::execute(Self, Array[Command]) -> Unit`.
- `Command` variants cover cursor movement, screen mode constants,
erase/cursor-visibility commands, style/color commands, scroll margins,
synchronized updates, bracketed paste/focus/auto-wrap modes, kitty keyboard
enhancement push/pop, and `Print(StringView)`.
- This command task should not add further `internal/vt` API. The same PR also
includes the prior VT write-helper task; its `internal/vt` API diff is tracked
in `docs/plans/2026-06-15-vt-write-helpers.md`.

## Open Questions

- Mouse enable/disable commands are intentionally deferred because they are
composite sequences rather than one VT command each.
- Query commands are intentionally deferred because they require coordinating
writes with input responses.

## Next Implementation Step

Replace the draft `command.mbt` with the accepted command enum and buffer-backed
executor, then add tests that validate batch output order and empty no-op
behavior.

## Validation Plan

- `moon fmt`
- `moon test .`
- `moon check`
- `moon info`
- Review root `pkg.generated.mbti` and `internal/vt/pkg.generated.mbti` diffs.
- `git diff --check`

## Validation Results

- `moon fmt`: passed.
- `moon test .`: passed, 27 tests.
- `moon test`: passed, 172 tests.
- `moon check`: passed.
- `moon info`: passed.
- `git diff --cached --check`: passed.
- `Print(StringView)` addendum:
- `moon fmt`: passed.
- `moon test .`: passed, 27 tests.
- `moon test`: passed, 172 tests.
- `moon check`: passed.
- `moon info`: passed.

## Public API Audit

- Root `pkg.generated.mbti` gained only the accepted `pub(all) enum Command`
and `pub async fn Tty::execute(Self, Array[Command]) -> Unit`.
- The `Print(StringView)` addendum changes only the accepted command enum
surface.
- Relative to the command task, `internal/vt/pkg.generated.mbti` remained
unchanged. The PR-level `internal/vt` API additions from the earlier
destination-passing VT work are audited in
`docs/plans/2026-06-15-vt-write-helpers.md`.
78 changes: 78 additions & 0 deletions docs/plans/2026-06-15-vt-write-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# VT Write Helpers

## Goal

Add destination-passing VT sequence helpers so command batching can encode
multiple terminal operations into one buffer before a single `Tty::write`.

## Accepted Design

- Keep `moonbit-community/tty/internal/vt` as the only owner of VT byte
encoding.
- Add `write_*` helpers that take `buf : @buffer.Buffer` as their first
argument and append the requested sequence into that buffer.
- Keep existing `Bytes`-returning helpers with their current names and behavior.
These helpers may become thin wrappers around the new `write_*` helpers.
- Do not expose `write_*` helpers from the root package; they are internal
implementation surface for root `Tty`/command batching work.
- Constant byte sequences may continue as `pub let` values. A `write_*` helper is
only required where avoiding per-command temporary `Bytes` allocation matters.

## Target Files / Surfaces

- `internal/vt/moon.pkg`: import `moonbitlang/core/buffer`.
- `internal/vt/sequence.mbt`: add low-level CSI and decimal write helpers.
- `internal/vt/cursor.mbt`: add cursor `write_*` helpers and keep existing
`Bytes` helpers.
- `internal/vt/sgr.mbt`: add SGR/color `write_*` helpers and keep existing
`Bytes` helpers.
- `internal/vt/screen.mbt`: add write helper for kitty keyboard flag push.
- `internal/vt/scroll.mbt`: add write helper for top/bottom margins.
- `internal/vt/*_test.mbt`: verify `write_*` output matches existing byte APIs.

## API / Interface Diff

- Root package `.mbti` should remain unchanged.
- `internal/vt/pkg.generated.mbti` will gain public internal-package functions:
`write_cursor_*`, `write_sgr*`, `write_set_*`,
`write_push_keyboard_enhancement_flags`, and
`write_set_top_bottom_margins`.
- Existing `internal/vt` public `Bytes` helpers remain present and compatible.

## Open Questions

- Future command batching may decide whether all constants need corresponding
`write_*` helpers for call-site uniformity. This task only adds dynamic
helpers.

## Next Implementation Step

Add buffer-backed sequence writing primitives, then refactor dynamic cursor,
SGR/color, keyboard flag, and scroll-margin helpers to use them.

## Validation Plan

- `moon fmt`
- `moon test internal/vt`
- `moon check`
- `moon info`
- Review `internal/vt/pkg.generated.mbti` and root `pkg.generated.mbti` diffs.

## Validation Results

- `moon fmt internal/vt`: passed.
- `moon test internal/vt`: passed, 24 tests.
- `moon check`: passed with warnings only from the separate draft
`command.mbt` file (`write_to` unused, missing explicit core/buffer import,
unfinished `Tty::execute` placeholder).
- `moon info internal/vt`: passed.
- `git diff --cached --check`: passed.

## Public API Audit

- Root package `pkg.generated.mbti` was intentionally not regenerated because
the root `command.mbt` draft is incomplete and would pollute the root public
interface.
- `internal/vt/pkg.generated.mbti` gained only the intended internal-package
`write_*` helpers plus its `moonbitlang/core/buffer` import.
- Existing `internal/vt` `Bytes` helpers remain present and behavior-compatible.
Loading
Loading