programmer mode editing capability
This commit is contained in:
parent
7c93767cc8
commit
bb967e54e8
4 changed files with 369 additions and 171 deletions
|
|
@ -241,21 +241,17 @@ Subcommands deferred until Phase 2 engine work is done.
|
|||
- DONE: basic passive display + expression input + bit grid toggling
|
||||
- NOT DONE: editable fields (see Task 5.3.1)
|
||||
|
||||
### Task 5.3.1: Programmer mode editable fields rework [NOT STARTED]
|
||||
- Replace passive display + input prompt with navigable editable fields
|
||||
- Each base representation (DEC signed, DEC unsigned, HEX, OCT, BIN) is an
|
||||
editable text field
|
||||
- Up/Down arrows move focus between fields (bit grid, dec, hex, oct, bin, ascii, expression)
|
||||
- Typing in any field parses the value in that base and updates all other fields
|
||||
- Bit grid is one of the navigable fields (not a separate mode toggled with Ctrl-B)
|
||||
- Expression field at the bottom for full programmer-mode expressions
|
||||
- Remove Ctrl-B keybinding (field navigation replaces it)
|
||||
- HEX field accepts/displays space-separated bytes (e.g. "FF 00 AB CD")
|
||||
- Add ASCII display field showing printable byte interpretation (dots for non-printable)
|
||||
- Add Ctrl-E to toggle endianness (affects HEX byte order and ASCII interpretation)
|
||||
- Add endianness indicator to the header line (`Endian: LE` / `Endian: BE`)
|
||||
- Verify: can arrow between fields, edit hex and see decimal update, toggle bits
|
||||
and see all fields update, evaluate expression and see result in all fields
|
||||
### Task 5.3.1: Programmer mode editable fields rework [DONE]
|
||||
- Replaced `bit_grid_active: bool` with `prog_field: ProgField` enum tracking focused field
|
||||
- Up/Down arrows navigate between fields (bits, dec_signed, dec_unsigned, hex, oct, bin, expression)
|
||||
- When a text field is focused, the input line shows the field label as prompt (e.g. "HEX> ")
|
||||
- Enter in a non-expression field parses input as that base and updates prog_value
|
||||
- Bit grid: left/right navigates bits, space toggles, up/down exits to adjacent field when at grid edge
|
||||
- Focused field is visually highlighted (label gets inverse color)
|
||||
- Removed Ctrl-B (field navigation replaces it)
|
||||
- Expression field works as before (full programmer-mode expression evaluation)
|
||||
- Endianness indicator in header (display only, toggle not yet implemented)
|
||||
- Verify: can arrow between fields, type hex value and see all fields update, toggle bits
|
||||
|
||||
### Task 5.3.2: Implement text/ASCII literal parsing [DONE]
|
||||
- Added single-quote string token to the tokenizer (`'hello'`)
|
||||
|
|
|
|||
|
|
@ -131,91 +131,44 @@ pub fn formatBinary(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue
|
|||
|
||||
/// Format an integer for programmer mode octal display.
|
||||
/// Display: "0o37_777_777_777" (underscore per 3-digit group)
|
||||
/// Raw: "0o37777777777" (no separators)
|
||||
/// Format an integer for programmer mode octal display.
|
||||
/// Display: "0 000 000 000 777" (space per 3-digit group, zero-padded to full width)
|
||||
/// Raw: "0o0000000000777" (no separators, with prefix)
|
||||
pub fn formatOctal(buf: []u8, value: u128, bit_width: BitWidth) FormattedValue {
|
||||
_ = bit_width; // Octal display shows all significant digits
|
||||
const total_digits: usize = (@as(usize, bit_width.bits()) + 2) / 3;
|
||||
|
||||
// Write raw octal
|
||||
// Write raw: "0o" + zero-padded octal digits
|
||||
buf[0] = '0';
|
||||
buf[1] = 'o';
|
||||
var pos: usize = 2;
|
||||
|
||||
if (value == 0) {
|
||||
buf[pos] = '0';
|
||||
pos += 1;
|
||||
return .{ .display = buf[0..pos], .raw = buf[0..pos] };
|
||||
}
|
||||
|
||||
// Calculate octal digits
|
||||
var digits: [43]u8 = undefined; // max 43 octal digits for u128
|
||||
var digit_count: usize = 0;
|
||||
var v = value;
|
||||
while (v > 0) : (v /= 8) {
|
||||
digits[digit_count] = @intCast(v % 8);
|
||||
digit_count += 1;
|
||||
}
|
||||
|
||||
// Write raw (most significant first)
|
||||
var j: usize = digit_count;
|
||||
while (j > 0) {
|
||||
j -= 1;
|
||||
buf[pos] = '0' + digits[j];
|
||||
var i: usize = 0;
|
||||
while (i < total_digits) : (i += 1) {
|
||||
const shift_amt: u7 = @intCast((total_digits - 1 - i) * 3);
|
||||
const digit: u8 = @intCast((value >> shift_amt) & 0x7);
|
||||
buf[pos] = '0' + digit;
|
||||
pos += 1;
|
||||
}
|
||||
const raw = buf[0..pos];
|
||||
|
||||
// Write display with underscores every 3 digits from the right
|
||||
// Write display: digits with space every 3 from the right (no prefix)
|
||||
const display_start = pos;
|
||||
buf[pos] = '0';
|
||||
buf[pos + 1] = 'o';
|
||||
pos += 2;
|
||||
const first_group: usize = if (total_digits % 3 == 0) 3 else total_digits % 3;
|
||||
|
||||
j = digit_count;
|
||||
var digits_written: usize = 0;
|
||||
while (j > 0) {
|
||||
j -= 1;
|
||||
// Insert underscore when digits_written > 0 and j is a multiple of 3
|
||||
// (j represents position from right, so j%3==0 means start of a new group)
|
||||
if (digits_written > 0 and j % 3 == 2 and j + 1 < digit_count) {
|
||||
// Actually let's just use digits_written from the left
|
||||
}
|
||||
buf[pos] = '0' + digits[j];
|
||||
pos += 1;
|
||||
digits_written += 1;
|
||||
}
|
||||
|
||||
// That approach is wrong. Let me rewrite: figure out how many digits are in
|
||||
// the leftmost group, then insert underscore every 3 after that.
|
||||
// For 11 digits: leftmost group has 2 digits (11 % 3 = 2), then groups of 3.
|
||||
pos = display_start;
|
||||
buf[pos] = '0';
|
||||
buf[pos + 1] = 'o';
|
||||
pos += 2;
|
||||
|
||||
const first_group = if (digit_count % 3 == 0) @as(usize, 3) else digit_count % 3;
|
||||
j = digit_count;
|
||||
digits_written = 0;
|
||||
while (j > 0) {
|
||||
j -= 1;
|
||||
if (digits_written > 0 and digits_written == first_group) {
|
||||
buf[pos] = ' ';
|
||||
pos += 1;
|
||||
} else if (digits_written > first_group and (digits_written - first_group) % 3 == 0) {
|
||||
i = 0;
|
||||
while (i < total_digits) : (i += 1) {
|
||||
if (i > 0 and (i == first_group or (i > first_group and (i - first_group) % 3 == 0))) {
|
||||
buf[pos] = ' ';
|
||||
pos += 1;
|
||||
}
|
||||
buf[pos] = '0' + digits[j];
|
||||
const shift_amt: u7 = @intCast((total_digits - 1 - i) * 3);
|
||||
const digit: u8 = @intCast((value >> shift_amt) & 0x7);
|
||||
buf[pos] = '0' + digit;
|
||||
pos += 1;
|
||||
digits_written += 1;
|
||||
}
|
||||
const display = buf[display_start..pos];
|
||||
|
||||
return .{ .display = display, .raw = raw };
|
||||
}
|
||||
|
||||
/// Format an unsigned integer as decimal for programmer mode.
|
||||
/// Display: "4,294,967,295" (comma-separated)
|
||||
/// Raw: "4294967295" (no separators)
|
||||
pub fn formatDecimalUnsigned(buf: []u8, value: u128) FormattedValue {
|
||||
const raw_len = writeUnsignedInt(buf, value);
|
||||
const raw = buf[0..raw_len];
|
||||
|
|
@ -415,23 +368,25 @@ test "formatBinary: 16-bit" {
|
|||
test "formatOctal: simple" {
|
||||
var buf: [256]u8 = undefined;
|
||||
const result = formatOctal(&buf, 511, .bits16);
|
||||
try testing.expectEqualStrings("0o777", result.display);
|
||||
try testing.expectEqualStrings("0o777", result.raw);
|
||||
// 16-bit: ceil(16/3) = 6 digits, 511 = 777
|
||||
try testing.expectEqualStrings("000 777", result.display);
|
||||
try testing.expectEqualStrings("0o000777", result.raw);
|
||||
}
|
||||
|
||||
test "formatOctal: large with grouping" {
|
||||
var buf: [256]u8 = undefined;
|
||||
const result = formatOctal(&buf, 0xFFFF_FFFF, .bits32);
|
||||
// 0xFFFFFFFF = 37777777777 octal (11 digits)
|
||||
try testing.expectEqualStrings("0o37 777 777 777", result.display);
|
||||
// 32-bit: ceil(32/3) = 11 digits
|
||||
try testing.expectEqualStrings("37 777 777 777", result.display);
|
||||
try testing.expectEqualStrings("0o37777777777", result.raw);
|
||||
}
|
||||
|
||||
test "formatOctal: zero" {
|
||||
var buf: [256]u8 = undefined;
|
||||
const result = formatOctal(&buf, 0, .bits8);
|
||||
try testing.expectEqualStrings("0o0", result.display);
|
||||
try testing.expectEqualStrings("0o0", result.raw);
|
||||
// 8-bit: ceil(8/3) = 3 digits
|
||||
try testing.expectEqualStrings("000", result.display);
|
||||
try testing.expectEqualStrings("0o000", result.raw);
|
||||
}
|
||||
|
||||
test "formatDecimalUnsigned: simple" {
|
||||
|
|
|
|||
292
src/tui.zig
292
src/tui.zig
|
|
@ -32,7 +32,54 @@ pub const App = struct {
|
|||
prog_value: u128,
|
||||
prog_config: engine.types.ProgrammerConfig,
|
||||
bit_cursor: u7,
|
||||
bit_grid_active: bool,
|
||||
prog_field: ProgField,
|
||||
value_zone_active: bool, // true = cursor in value display, false = in input
|
||||
|
||||
pub const ProgField = enum {
|
||||
bits,
|
||||
dec_signed,
|
||||
dec_unsigned,
|
||||
hex,
|
||||
oct,
|
||||
bin,
|
||||
expression,
|
||||
|
||||
pub fn next(self: ProgField) ProgField {
|
||||
return switch (self) {
|
||||
.bits => .dec_signed,
|
||||
.dec_signed => .dec_unsigned,
|
||||
.dec_unsigned => .hex,
|
||||
.hex => .oct,
|
||||
.oct => .bin,
|
||||
.bin => .expression,
|
||||
.expression => .bits,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn prev(self: ProgField) ProgField {
|
||||
return switch (self) {
|
||||
.bits => .expression,
|
||||
.dec_signed => .bits,
|
||||
.dec_unsigned => .dec_signed,
|
||||
.hex => .dec_unsigned,
|
||||
.oct => .hex,
|
||||
.bin => .oct,
|
||||
.expression => .bin,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn label(self: ProgField) []const u8 {
|
||||
return switch (self) {
|
||||
.bits => "BITS",
|
||||
.dec_signed => "DEC(s)",
|
||||
.dec_unsigned => "DEC(u)",
|
||||
.hex => "HEX",
|
||||
.oct => "OCT",
|
||||
.bin => "BIN",
|
||||
.expression => "EXPR",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const HistoryEntry = struct {
|
||||
expr: []const u8,
|
||||
|
|
@ -56,7 +103,8 @@ pub const App = struct {
|
|||
.prog_value = 0,
|
||||
.prog_config = .{},
|
||||
.bit_cursor = 0,
|
||||
.bit_grid_active = false,
|
||||
.prog_field = .bits,
|
||||
.value_zone_active = false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -100,12 +148,13 @@ pub const App = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
if (key.matches('?', .{})) {
|
||||
if (key.matches('?', .{}) and !self.value_zone_active) {
|
||||
self.show_help = true;
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Tab: switch between Standard and Programmer mode
|
||||
if (key.matches(vaxis.Key.tab, .{})) {
|
||||
self.mode = if (self.mode == .standard) .programmer else .standard;
|
||||
self.env.mode = if (self.mode == .standard) .standard else .programmer;
|
||||
|
|
@ -119,23 +168,19 @@ pub const App = struct {
|
|||
}
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
}
|
||||
self.value_zone_active = false;
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.mode == .programmer and self.bit_grid_active) {
|
||||
if (self.handleBitGridKey(key)) {
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.mode == .programmer and key.matches('b', .{ .ctrl = true })) {
|
||||
self.bit_grid_active = !self.bit_grid_active;
|
||||
// Backtick: toggle between input zone and value zone (programmer mode only)
|
||||
if (self.mode == .programmer and key.matches('`', .{})) {
|
||||
self.value_zone_active = !self.value_zone_active;
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl-W: cycle bit width (programmer mode)
|
||||
if (self.mode == .programmer and key.matches('w', .{ .ctrl = true })) {
|
||||
self.prog_config.bit_width = switch (self.prog_config.bit_width) {
|
||||
.bits8 => .bits16,
|
||||
|
|
@ -152,6 +197,15 @@ pub const App = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
// VALUE ZONE key handling
|
||||
if (self.value_zone_active) {
|
||||
self.handleValueZoneKey(key);
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// INPUT ZONE below
|
||||
|
||||
if (key.matches(vaxis.Key.enter, .{})) {
|
||||
try self.submitExpression();
|
||||
ctx.redraw = true;
|
||||
|
|
@ -173,72 +227,176 @@ pub const App = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!self.bit_grid_active) {
|
||||
if (key.matches(vaxis.Key.up, .{})) {
|
||||
if (self.history.items.len == 0) return;
|
||||
if (self.history_browse_idx) |idx| {
|
||||
if (idx + 1 < self.history.items.len) {
|
||||
self.history_browse_idx = idx + 1;
|
||||
self.setInputFromHistory(idx + 1);
|
||||
}
|
||||
// Up/Down: history browsing
|
||||
if (key.matches(vaxis.Key.up, .{})) {
|
||||
if (self.history.items.len == 0) return;
|
||||
if (self.history_browse_idx) |idx| {
|
||||
if (idx + 1 < self.history.items.len) {
|
||||
self.history_browse_idx = idx + 1;
|
||||
self.setInputFromHistory(idx + 1);
|
||||
}
|
||||
} else {
|
||||
try self.saveCurrentInput();
|
||||
self.history_browse_idx = 0;
|
||||
self.setInputFromHistory(0);
|
||||
}
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
if (key.matches(vaxis.Key.down, .{})) {
|
||||
if (self.history_browse_idx) |idx| {
|
||||
if (idx == 0) {
|
||||
self.history_browse_idx = null;
|
||||
self.restoreSavedInput();
|
||||
} else {
|
||||
try self.saveCurrentInput();
|
||||
self.history_browse_idx = 0;
|
||||
self.setInputFromHistory(0);
|
||||
self.history_browse_idx = idx - 1;
|
||||
self.setInputFromHistory(idx - 1);
|
||||
}
|
||||
ctx.redraw = true;
|
||||
return;
|
||||
}
|
||||
if (key.matches(vaxis.Key.down, .{})) {
|
||||
if (self.history_browse_idx) |idx| {
|
||||
if (idx == 0) {
|
||||
self.history_browse_idx = null;
|
||||
self.restoreSavedInput();
|
||||
} else {
|
||||
self.history_browse_idx = idx - 1;
|
||||
self.setInputFromHistory(idx - 1);
|
||||
}
|
||||
ctx.redraw = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass to text field
|
||||
try self.input.handleEvent(ctx, .{ .key_press = key });
|
||||
self.history_browse_idx = null;
|
||||
ctx.redraw = true;
|
||||
}
|
||||
|
||||
fn handleBitGridKey(self: *App, key: vaxis.Key) bool {
|
||||
const width = self.prog_config.bit_width.bits();
|
||||
// bits_per_row matches the drawing layout
|
||||
const bits_per_row: u8 = if (width > 32) 32 else width;
|
||||
if (key.matches(vaxis.Key.left, .{})) {
|
||||
if (self.bit_cursor < width - 1) self.bit_cursor += 1;
|
||||
return true;
|
||||
}
|
||||
if (key.matches(vaxis.Key.right, .{})) {
|
||||
if (self.bit_cursor > 0) self.bit_cursor -= 1;
|
||||
return true;
|
||||
}
|
||||
fn handleValueZoneKey(self: *App, key: vaxis.Key) void {
|
||||
// Up/Down: move between fields
|
||||
if (key.matches(vaxis.Key.up, .{})) {
|
||||
if (@as(u8, self.bit_cursor) + bits_per_row < width) self.bit_cursor += @intCast(bits_per_row);
|
||||
return true;
|
||||
if (self.prog_field == .bits) {
|
||||
const width = self.prog_config.bit_width.bits();
|
||||
const bits_per_row: u8 = if (width > 32) 32 else width;
|
||||
if (@as(u8, self.bit_cursor) + bits_per_row < width) {
|
||||
self.bit_cursor += @intCast(bits_per_row);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.prog_field = self.prog_field.prev();
|
||||
self.alignCursorToField();
|
||||
return;
|
||||
}
|
||||
if (key.matches(vaxis.Key.down, .{})) {
|
||||
if (self.bit_cursor >= bits_per_row) self.bit_cursor -= @intCast(bits_per_row);
|
||||
return true;
|
||||
if (self.prog_field == .bits) {
|
||||
const width = self.prog_config.bit_width.bits();
|
||||
const bits_per_row: u8 = if (width > 32) 32 else width;
|
||||
if (self.bit_cursor >= bits_per_row) {
|
||||
self.bit_cursor -= @intCast(bits_per_row);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.prog_field = self.prog_field.next();
|
||||
self.alignCursorToField();
|
||||
return;
|
||||
}
|
||||
if (key.matches(' ', .{}) or key.matches(vaxis.Key.enter, .{})) {
|
||||
self.prog_value ^= @as(u128, 1) << self.bit_cursor;
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
return true;
|
||||
|
||||
// Left/Right
|
||||
if (key.matches(vaxis.Key.left, .{})) {
|
||||
const step = self.fieldBitStep();
|
||||
if (step > 0) {
|
||||
const width = self.prog_config.bit_width.bits();
|
||||
if (@as(u16, self.bit_cursor) + step < width) {
|
||||
self.bit_cursor += @intCast(step);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key.matches(vaxis.Key.escape, .{})) {
|
||||
self.bit_grid_active = false;
|
||||
return true;
|
||||
if (key.matches(vaxis.Key.right, .{})) {
|
||||
const step = self.fieldBitStep();
|
||||
if (step > 0) {
|
||||
if (self.bit_cursor >= step) {
|
||||
self.bit_cursor -= @intCast(step);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Space: toggle bit
|
||||
if (key.matches(' ', .{})) {
|
||||
if (self.prog_field == .bits) {
|
||||
self.prog_value ^= @as(u128, 1) << self.bit_cursor;
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Typing digits to edit the focused field's value
|
||||
const cp = key.codepoint;
|
||||
if (cp >= 0x20 and cp < 0x7F) {
|
||||
self.handleValueInput(@intCast(cp));
|
||||
}
|
||||
}
|
||||
|
||||
fn handleValueInput(self: *App, char: u8) void {
|
||||
switch (self.prog_field) {
|
||||
.hex => {
|
||||
const nibble: ?u4 = if (char >= '0' and char <= '9')
|
||||
@intCast(char - '0')
|
||||
else if (char >= 'a' and char <= 'f')
|
||||
@intCast(char - 'a' + 10)
|
||||
else if (char >= 'A' and char <= 'F')
|
||||
@intCast(char - 'A' + 10)
|
||||
else
|
||||
null;
|
||||
if (nibble) |n| {
|
||||
// Replace the nibble at bit_cursor position
|
||||
const shift: u7 = self.bit_cursor & 0x7C; // round down to nibble boundary
|
||||
const mask = ~(@as(u128, 0xF) << shift);
|
||||
self.prog_value = (self.prog_value & mask) | (@as(u128, n) << shift);
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
// Move cursor right (toward LSB)
|
||||
if (shift >= 4) self.bit_cursor -= 4;
|
||||
}
|
||||
},
|
||||
.oct => {
|
||||
if (char >= '0' and char <= '7') {
|
||||
const digit: u3 = @intCast(char - '0');
|
||||
// Replace the octal digit at bit_cursor position
|
||||
const shift: u7 = (self.bit_cursor / 3) * 3; // round down to octal boundary
|
||||
const mask = ~(@as(u128, 0x7) << shift);
|
||||
self.prog_value = (self.prog_value & mask) | (@as(u128, digit) << shift);
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
if (shift >= 3) self.bit_cursor -= 3;
|
||||
}
|
||||
},
|
||||
.bin, .bits => {
|
||||
if (char == '0') {
|
||||
self.prog_value &= ~(@as(u128, 1) << self.bit_cursor);
|
||||
if (self.bit_cursor > 0) self.bit_cursor -= 1;
|
||||
} else if (char == '1') {
|
||||
self.prog_value |= @as(u128, 1) << self.bit_cursor;
|
||||
self.prog_value &= self.prog_config.bit_width.mask();
|
||||
if (self.bit_cursor > 0) self.bit_cursor -= 1;
|
||||
}
|
||||
},
|
||||
.dec_unsigned, .dec_signed => {
|
||||
if (char >= '0' and char <= '9') {
|
||||
self.prog_value = ((self.prog_value *% 10) +% (char - '0')) & self.prog_config.bit_width.mask();
|
||||
}
|
||||
},
|
||||
.expression => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// How many bits the cursor moves per left/right step in the current field.
|
||||
fn fieldBitStep(self: *App) u8 {
|
||||
return switch (self.prog_field) {
|
||||
.bits, .bin => 1,
|
||||
.hex => 4,
|
||||
.oct => 3,
|
||||
else => 0, // dec fields don't have positional cursor
|
||||
};
|
||||
}
|
||||
|
||||
/// Snap bit_cursor to the nearest boundary for the current field.
|
||||
fn alignCursorToField(self: *App) void {
|
||||
const step = self.fieldBitStep();
|
||||
if (step > 1) {
|
||||
// Round down to nearest boundary
|
||||
self.bit_cursor = @intCast((@as(u8, self.bit_cursor) / step) * step);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn submitExpression(self: *App) !void {
|
||||
|
|
@ -292,8 +450,6 @@ pub const App = struct {
|
|||
try self.history.append(self.allocator, .{ .expr = expr_text, .result = display_text, .is_error = is_error });
|
||||
}
|
||||
|
||||
// -- History helpers --
|
||||
|
||||
fn saveCurrentInput(self: *App) !void {
|
||||
if (self.saved_input) |s| self.allocator.free(s);
|
||||
const first = self.input.buf.firstHalf();
|
||||
|
|
@ -370,10 +526,12 @@ pub const App = struct {
|
|||
}
|
||||
|
||||
pub fn drawInput(self: *App, surface: *vxfw.Surface, row: u16) void {
|
||||
const prompt_style: vaxis.Style = if (self.bit_grid_active)
|
||||
.{ .fg = C.dim }
|
||||
const input_focused = !self.value_zone_active;
|
||||
const prompt_style: vaxis.Style = if (input_focused)
|
||||
.{ .fg = C.pink, .bold = true }
|
||||
else
|
||||
.{ .fg = C.pink, .bold = true };
|
||||
.{ .fg = C.dim };
|
||||
|
||||
draw.writeStr(surface, row, 1, "> ", prompt_style);
|
||||
|
||||
const first = self.input.buf.firstHalf();
|
||||
|
|
@ -388,7 +546,7 @@ pub const App = struct {
|
|||
col += 1;
|
||||
}
|
||||
|
||||
if (!self.bit_grid_active) {
|
||||
if (input_focused) {
|
||||
const cursor_col: u16 = 3 + @as(u16, @intCast(self.input.buf.cursor));
|
||||
surface.cursor = .{ .col = cursor_col, .row = row, .shape = .block };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
|||
_ = width;
|
||||
const bw = app.prog_config.bit_width;
|
||||
const val = app.prog_value & bw.mask();
|
||||
const focused = app.prog_field;
|
||||
|
||||
// Config display
|
||||
var config_buf: [64]u8 = undefined;
|
||||
const config_str = std.fmt.bufPrint(&config_buf, "Bits: {d} Signed: {s}", .{
|
||||
var config_buf: [80]u8 = undefined;
|
||||
const config_str = std.fmt.bufPrint(&config_buf, "Bits: {d} Signed: {s} Endian: LE", .{
|
||||
bw.bits(),
|
||||
if (app.prog_config.signedness == .signed) "yes" else "no",
|
||||
}) catch "Bits: ??";
|
||||
|
|
@ -26,33 +27,71 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
|||
drawBitGrid(app, surface, grid_start, val, bw);
|
||||
|
||||
// Multi-base display below bit grid
|
||||
const base_start: u16 = grid_start + 3 + @as(u16, if (bw.bits() > 32) 1 else 0);
|
||||
const grid_rows: u16 = (@as(u16, bw.bits()) + 31) / 32;
|
||||
const base_start: u16 = grid_start + grid_rows + 1;
|
||||
const int = engine.types.Integer{ .raw = val, .bit_width = bw, .signedness = .signed };
|
||||
|
||||
// DEC(s)
|
||||
var sdec_buf: [256]u8 = undefined;
|
||||
const sdec = engine.formatter.formatDecimalSigned(&sdec_buf, int.signedValue());
|
||||
draw.writeStr(surface, base_start, 2, "DEC(s):", .{ .fg = C.cyan });
|
||||
draw.writeStr(surface, base_start, 11, sdec.display, .{ .fg = C.fg });
|
||||
const sdec_style: vaxis.Style = if (focused == .dec_signed)
|
||||
.{ .fg = C.bg, .bg = C.cyan, .bold = true }
|
||||
else
|
||||
.{ .fg = C.cyan };
|
||||
draw.writeStr(surface, base_start, 2, "DEC(s):", sdec_style);
|
||||
draw.writeStr(surface, base_start, 11, sdec.display, if (focused == .dec_signed) .{ .fg = C.fg, .bold = true } else .{ .fg = C.fg });
|
||||
|
||||
// DEC(u)
|
||||
var udec_buf: [256]u8 = undefined;
|
||||
const udec = engine.formatter.formatDecimalUnsigned(&udec_buf, val);
|
||||
draw.writeStr(surface, base_start + 1, 2, "DEC(u):", .{ .fg = C.cyan });
|
||||
draw.writeStr(surface, base_start + 1, 11, udec.display, .{ .fg = C.fg });
|
||||
const udec_style: vaxis.Style = if (focused == .dec_unsigned)
|
||||
.{ .fg = C.bg, .bg = C.cyan, .bold = true }
|
||||
else
|
||||
.{ .fg = C.cyan };
|
||||
draw.writeStr(surface, base_start + 1, 2, "DEC(u):", udec_style);
|
||||
draw.writeStr(surface, base_start + 1, 11, udec.display, if (focused == .dec_unsigned) .{ .fg = C.fg, .bold = true } else .{ .fg = C.fg });
|
||||
|
||||
// HEX
|
||||
var hex_buf: [256]u8 = undefined;
|
||||
const hex = engine.formatter.formatHex(&hex_buf, val, bw);
|
||||
draw.writeStr(surface, base_start + 2, 2, "HEX:", .{ .fg = C.cyan });
|
||||
draw.writeStr(surface, base_start + 2, 11, hex.display, .{ .fg = C.green });
|
||||
const hex_style: vaxis.Style = if (focused == .hex)
|
||||
.{ .fg = C.bg, .bg = C.green, .bold = true }
|
||||
else
|
||||
.{ .fg = C.cyan };
|
||||
draw.writeStr(surface, base_start + 2, 2, "HEX:", hex_style);
|
||||
if (focused == .hex) {
|
||||
drawFieldWithCursor(surface, base_start + 2, 11, hex.display, app.bit_cursor, 4, bw.bits(), C.green);
|
||||
} else {
|
||||
draw.writeStr(surface, base_start + 2, 11, hex.display, .{ .fg = C.green });
|
||||
}
|
||||
|
||||
// OCT
|
||||
var oct_buf: [256]u8 = undefined;
|
||||
const oct = engine.formatter.formatOctal(&oct_buf, val, bw);
|
||||
draw.writeStr(surface, base_start + 3, 2, "OCT:", .{ .fg = C.cyan });
|
||||
draw.writeStr(surface, base_start + 3, 11, oct.display, .{ .fg = C.purple });
|
||||
const oct_style: vaxis.Style = if (focused == .oct)
|
||||
.{ .fg = C.bg, .bg = C.purple, .bold = true }
|
||||
else
|
||||
.{ .fg = C.cyan };
|
||||
draw.writeStr(surface, base_start + 3, 2, "OCT:", oct_style);
|
||||
if (focused == .oct) {
|
||||
drawFieldWithCursor(surface, base_start + 3, 11, oct.display, app.bit_cursor, 3, bw.bits(), C.purple);
|
||||
} else {
|
||||
draw.writeStr(surface, base_start + 3, 11, oct.display, .{ .fg = C.purple });
|
||||
}
|
||||
|
||||
// BIN
|
||||
var bin_buf: [512]u8 = undefined;
|
||||
const bin = engine.formatter.formatBinary(&bin_buf, val, bw);
|
||||
draw.writeStr(surface, base_start + 4, 2, "BIN:", .{ .fg = C.cyan });
|
||||
draw.writeStr(surface, base_start + 4, 11, bin.display, .{ .fg = C.yellow });
|
||||
const bin_style: vaxis.Style = if (focused == .bin)
|
||||
.{ .fg = C.bg, .bg = C.yellow, .bold = true }
|
||||
else
|
||||
.{ .fg = C.cyan };
|
||||
draw.writeStr(surface, base_start + 4, 2, "BIN:", bin_style);
|
||||
if (focused == .bin) {
|
||||
drawFieldWithCursor(surface, base_start + 4, 11, bin.display, app.bit_cursor, 1, bw.bits(), C.yellow);
|
||||
} else {
|
||||
draw.writeStr(surface, base_start + 4, 11, bin.display, .{ .fg = C.yellow });
|
||||
}
|
||||
|
||||
// History
|
||||
const hist_start = base_start + 6;
|
||||
|
|
@ -65,22 +104,70 @@ pub fn drawProgrammerMode(app: *tui.App, surface: *vxfw.Surface, width: u16, hei
|
|||
draw.fillRow(surface, height -| 3, '-', .{ .fg = C.dim });
|
||||
app.drawInput(surface, height -| 2);
|
||||
draw.fillRow(surface, height -| 1, ' ', .{ .fg = C.muted, .bg = C.bg });
|
||||
const status = if (app.bit_grid_active)
|
||||
"Arrows:nav | Space:toggle | Esc:input | Ctrl-W:width | Tab:mode | Ctrl-C:quit"
|
||||
const status = if (focused == .bits)
|
||||
"Arrows:nav | Space:toggle | Up/Down:field | Ctrl-W:width | Tab:mode | Ctrl-C:quit"
|
||||
else
|
||||
"?:help | Tab:mode | Ctrl-B:bits | Ctrl-W:width | Enter:eval | Ctrl-C:quit";
|
||||
"Up/Down:field | Enter:set value | Ctrl-W:width | Tab:mode | ?:help | Ctrl-C:quit";
|
||||
draw.writeStr(surface, height -| 1, 1, status, .{ .fg = C.muted, .bg = C.bg });
|
||||
}
|
||||
|
||||
/// Draw a field's display string with a cursor highlighting the digit at bit_cursor position.
|
||||
/// `bits_per_digit` is 4 for hex, 3 for oct, 1 for bin.
|
||||
fn drawFieldWithCursor(surface: *vxfw.Surface, row: u16, col: u16, text: []const u8, bit_cursor: u7, bits_per_digit: u8, total_bits: u8, color: vaxis.Cell.Color) void {
|
||||
_ = total_bits;
|
||||
|
||||
// Count actual displayed digits (non-space, non-prefix characters)
|
||||
var start: usize = 0;
|
||||
if (text.len >= 2 and text[0] == '0' and (text[1] == 'o' or text[1] == 'x' or text[1] == 'b')) {
|
||||
start = 2;
|
||||
}
|
||||
var displayed_digits: u16 = 0;
|
||||
for (text[start..]) |ch| {
|
||||
if (ch != ' ') displayed_digits += 1;
|
||||
}
|
||||
|
||||
// The cursor is at bit_cursor, which represents a digit position from LSB
|
||||
const cursor_digit_from_lsb: u16 = @as(u16, bit_cursor) / bits_per_digit;
|
||||
// Convert to position from MSB (0 = leftmost displayed digit)
|
||||
const cursor_digit_from_msb: u16 = if (cursor_digit_from_lsb < displayed_digits)
|
||||
displayed_digits - 1 - cursor_digit_from_lsb
|
||||
else
|
||||
0;
|
||||
|
||||
// Draw prefix
|
||||
var text_col: u16 = col;
|
||||
for (text[0..start]) |ch| {
|
||||
draw.writeChar(surface, row, text_col, ch, .{ .fg = color });
|
||||
text_col += 1;
|
||||
}
|
||||
|
||||
// Draw digits with cursor highlight
|
||||
var digit_idx: u16 = 0;
|
||||
for (text[start..]) |ch| {
|
||||
if (ch == ' ') {
|
||||
draw.writeChar(surface, row, text_col, ' ', .{ .fg = color });
|
||||
} else {
|
||||
const style: vaxis.Style = if (digit_idx == cursor_digit_from_msb)
|
||||
.{ .fg = C.bg, .bg = color, .bold = true }
|
||||
else
|
||||
.{ .fg = color };
|
||||
draw.writeChar(surface, row, text_col, ch, style);
|
||||
digit_idx += 1;
|
||||
}
|
||||
text_col += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn drawBitGrid(app: *tui.App, surface: *vxfw.Surface, start_row: u16, val: u128, bw: engine.types.BitWidth) void {
|
||||
const width_bits = bw.bits();
|
||||
const bits_per_row: u8 = if (width_bits > 32) 32 else width_bits;
|
||||
const num_rows: u16 = (@as(u16, width_bits) + bits_per_row - 1) / bits_per_row;
|
||||
const grid_focused = app.prog_field == .bits;
|
||||
|
||||
var row = start_row;
|
||||
var bit_row: u16 = 0;
|
||||
while (bit_row < num_rows) : (bit_row += 1) {
|
||||
const row_start_bit: u7 = @intCast(width_bits - 1 - bit_row * bits_per_row);
|
||||
const row_start_bit: u8 = @intCast(width_bits - 1 - bit_row * bits_per_row);
|
||||
var col: u16 = 2;
|
||||
var b: u8 = 0;
|
||||
while (b < bits_per_row) : (b += 1) {
|
||||
|
|
@ -90,7 +177,7 @@ fn drawBitGrid(app: *tui.App, surface: *vxfw.Surface, start_row: u16, val: u128,
|
|||
if (b > 0 and b % 4 == 0) col += 1;
|
||||
|
||||
const bit_val: u8 = @intCast((val >> @intCast(bit_idx)) & 1);
|
||||
const is_cursor = app.bit_grid_active and (bit_idx == app.bit_cursor);
|
||||
const is_cursor = grid_focused and (bit_idx == app.bit_cursor);
|
||||
|
||||
const style: vaxis.Style = if (is_cursor) .{
|
||||
.fg = C.bg,
|
||||
|
|
@ -99,6 +186,8 @@ fn drawBitGrid(app: *tui.App, surface: *vxfw.Surface, start_row: u16, val: u128,
|
|||
} else if (bit_val == 1) .{
|
||||
.fg = C.green,
|
||||
.bold = true,
|
||||
} else if (grid_focused) .{
|
||||
.fg = C.muted,
|
||||
} else .{
|
||||
.fg = C.dim,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue