Skip to content

Commit

Permalink
Add scrollback support
Browse files Browse the repository at this point in the history
  • Loading branch information
ringtailsoftware committed Dec 17, 2024
1 parent 10a9f4b commit bc67e3b
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 100 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,19 @@ Enable local echo of sent data, used for devices which do not echo back characte
commy /dev/cu.usbmodem1124203 115200 -e
```

# Scrollback

Commy supports moving back and forwards in terminal history. However, unlike some serial monitors, it stores screen updates rather than lines of incoming text. This means that a terminal UI can be rewound and replayed in time visually. To enter scrollback, press `ctrl-a`, then use up/down/pageup/pagedown to move through terminal history.

To run commy with a longer scrollback history, use a larger `-b <value>`.

![](scrollback.gif)

# Why use Commy?

It tells you how to quit.

Commy does what I use GNU `screen` for, but it's better in two important respects. [First, it is slightly smaller; and secondly it has the words "ctrl-a and quit" inscribed in large friendly letters on its cover.](https://en.wikipedia.org/wiki/Towel_Day)
Commy does what I use GNU `screen` for, but it's better in two important respects. First, it is slightly smaller; and secondly it has the words "ctrl-a and quit" inscribed in large friendly letters on its cover.

# Help text

Expand All @@ -106,6 +114,7 @@ Commy does what I use GNU `screen` for, but it's better in two important respect
values: { one, two }
-f, --flow=<flow> flow
values: { none, software, hardware }
-b, --buffer=<buffer> Scrollback buffer size
-h, --help Print this help and exit

# Testing
Expand Down
2 changes: 1 addition & 1 deletion linux-test/init.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash

socat EXEC:"/bin/bash",pty,stderr,setsid,sane PTY,link=/dev/ttyS11,setsid,raw,echo=0 &
commy /dev/ttyS11 921600
commy /dev/ttyS11 921600 -b 1000
Binary file added scrollback.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 18 additions & 6 deletions src/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ pub const Config = struct {
serial_config: zig_serial.SerialConfig,
log_file: ?std.fs.File,
local_echo: bool,
scrollback: usize,
};

// parse command line and return an allocated Config
pub fn parseCommandLine(allocator: std.mem.Allocator) !?*Config {
const App = yazap.App;
const Arg = yazap.Arg;

// default config
var serial_config: zig_serial.SerialConfig = .{
.baud_rate = 115200,
Expand Down Expand Up @@ -44,34 +45,36 @@ pub fn parseCommandLine(allocator: std.mem.Allocator) !?*Config {
try root.addArg(log_opt);

// extract parity, wordsize, stop and start possibilities from the enums and create cmdline opts
var parityNames:[std.meta.fields(zig_serial.Parity).len][]const u8 = undefined;
var parityNames: [std.meta.fields(zig_serial.Parity).len][]const u8 = undefined;
inline for (std.meta.fields(zig_serial.Parity), 0..) |f, i| {
parityNames[i] = f.name;
}
const parity_opt = Arg.singleValueOptionWithValidValues("parity", 'p', "Parity", &parityNames);
try root.addArg(parity_opt);

var wordsizeNames:[std.meta.fields(zig_serial.WordSize).len][]const u8 = undefined;
var wordsizeNames: [std.meta.fields(zig_serial.WordSize).len][]const u8 = undefined;
inline for (std.meta.fields(zig_serial.WordSize), 0..) |f, i| {
wordsizeNames[i] = f.name;
}
const wordsize_opt = Arg.singleValueOptionWithValidValues("wordsize", 'w', "wordsize", &wordsizeNames);
try root.addArg(wordsize_opt);

var stopNames:[std.meta.fields(zig_serial.StopBits).len][]const u8 = undefined;
var stopNames: [std.meta.fields(zig_serial.StopBits).len][]const u8 = undefined;
inline for (std.meta.fields(zig_serial.StopBits), 0..) |f, i| {
stopNames[i] = f.name;
}
const stop_opt = Arg.singleValueOptionWithValidValues("stop", 's', "stop", &stopNames);
try root.addArg(stop_opt);

var handshakeNames:[std.meta.fields(zig_serial.Handshake).len][]const u8 = undefined;
var handshakeNames: [std.meta.fields(zig_serial.Handshake).len][]const u8 = undefined;
inline for (std.meta.fields(zig_serial.Handshake), 0..) |f, i| {
handshakeNames[i] = f.name;
}
const handshake_opt = Arg.singleValueOptionWithValidValues("flow", 'f', "flow", &handshakeNames);
try root.addArg(handshake_opt);

const scrollback_opt = Arg.singleValueOption("buffer", 'b', "Scrollback buffer size");
try root.addArg(scrollback_opt);

try root.addArg(Arg.positional("port", "serial port file", 1));
try root.addArg(Arg.positional("speed", "baudrate", 2));
Expand Down Expand Up @@ -138,7 +141,6 @@ pub fn parseCommandLine(allocator: std.mem.Allocator) !?*Config {
}
}


if (matches.containsArg("speed")) {
if (matches.getSingleValue("speed")) |speed| {
serial_config.baud_rate = std.fmt.parseInt(u32, speed, 10) catch {
Expand All @@ -156,6 +158,7 @@ pub fn parseCommandLine(allocator: std.mem.Allocator) !?*Config {
.serial_config = serial_config,
.log_file = null,
.local_echo = false,
.scrollback = 100,
};

// process options which only make sense if we have a port
Expand All @@ -166,6 +169,15 @@ pub fn parseCommandLine(allocator: std.mem.Allocator) !?*Config {
}
}

if (matches.containsArg("buffer")) {
if (matches.getSingleValue("buffer")) |buffer| {
config.scrollback = std.fmt.parseInt(u32, buffer, 10) catch {
std.debug.print("Bad length {s}\n", .{buffer});
return null;
};
}
}

if (matches.containsArg("echo")) {
config.local_echo = true;
}
Expand Down
36 changes: 36 additions & 0 deletions src/keywindow.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const std = @import("std");

// sliding window keybuffer to spot multi-char sequences in input
pub const KeyWindow = struct {
const Self = @This();
window: [4]u8,
holding: u8, // how many valid bytes in window

pub fn init() Self {
return Self{
.window = undefined,
.holding = 0,
};
}

pub fn push(self: *Self, c: u8) void {
if (self.holding < self.window.len) {
self.window[self.holding] = c;
self.holding += 1;
} else {
std.mem.copyForwards(u8, self.window[0 .. self.holding - 1], self.window[1..]);
self.window[self.window.len - 1] = c;
}
}

pub fn clear(self: *Self) void {
self.holding = 0;
}

pub fn match(self: *const Self, s: []const u8) bool {
if (self.holding < s.len) {
return false;
}
return std.mem.containsAtLeast(u8, &self.window, 1, s);
}
};
Loading

0 comments on commit bc67e3b

Please sign in to comment.