summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/gui.zig17
-rw-r--r--src/pardes.zig131
-rw-r--r--src/tty.zig6
-rw-r--r--src/tutor.txt5
-rw-r--r--test/snapshots/tag.golden90
-rw-r--r--test/snapshots/tag.snap51
6 files changed, 78 insertions, 222 deletions
diff --git a/src/gui.zig b/src/gui.zig
index 60d9501f..cc949cd1 100644
--- a/src/gui.zig
+++ b/src/gui.zig
@@ -1650,12 +1650,13 @@ fn renderFrame(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface) !void
const instances: [*]CellInstance = @ptrCast(@alignCast(vptr));
const layout = fixedCellLayout(g);
const cursor_idx: u32 = if (surface.cursor) |cu| @as(u32, cu.y) * surface.cols + cu.x else cells;
+ const cursor_bar = if (surface.cursor) |cu| cu.bar else false;
var row: u16 = 0;
while (row < surface.rows) : (row += 1) {
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
const idx: u32 = @as(u32, row) * surface.cols + col;
- emitInstance(g, instances, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx);
+ emitInstance(g, instances, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx, cursor_bar);
}
}
c.SDL_UnmapGPUTransferBuffer(g.device, g.vxfer.?);
@@ -1701,13 +1702,15 @@ fn emitInstance(
win_h: f32,
cell: *const pardes.Cell,
is_cursor: bool,
+ cursor_bar: bool,
) void {
// resolve the cell style to concrete fg/bg, matching the prototype's
// cellKey: reverse swaps, invisible hides, dim darkens. The cursor cell
- // renders as reverse (cancelling an already-reversed cell).
+ // renders as reverse (cancelling an already-reversed cell); an insert
+ // cursor keeps the cell colors and draws a bar glyph instead.
var fg = fg_default;
var bg = bg_default;
- var reverse = is_cursor;
+ var reverse = is_cursor and !cursor_bar;
if (!cell.default) {
const st = cell.style;
fg = switch (st.fg) {
@@ -1737,7 +1740,10 @@ fn emitInstance(
const y1 = 1.0 - ((py0 + layout.h) / win_h) * 2.0;
const grapheme = cell.grapheme();
- const cp: u32 = if (cell.default or grapheme.len == 0) ' ' else firstCp(grapheme);
+ var cp: u32 = if (cell.default or grapheme.len == 0) ' ' else firstCp(grapheme);
+ // ponytail: the insert bar is the left-eighth block glyph over the cell
+ // (hides the char under it); a dedicated quad if that ever matters
+ if (is_cursor and cursor_bar) cp = 0x258F;
const slot = if (cp == ' ') g.space_slot else ensureGlyph(g, cp);
const aw_f: f32 = @floatFromInt(atlas_w);
const ah_f: f32 = @floatFromInt(atlas_h);
@@ -1944,12 +1950,13 @@ fn renderFrameGl(g: *Gui, gpa: std.mem.Allocator, surface: *pardes.Surface) !voi
const win_h: f32 = @floatFromInt(sh);
const layout = cellLayout(g, surface.cols, surface.rows, sw, sh);
const cursor_idx: u32 = if (surface.cursor) |cu| @as(u32, cu.y) * surface.cols + cu.x else cells;
+ const cursor_bar = if (surface.cursor) |cu| cu.bar else false;
var row: u16 = 0;
while (row < surface.rows) : (row += 1) {
var col: u16 = 0;
while (col < surface.cols) : (col += 1) {
const idx: u32 = @as(u32, row) * surface.cols + col;
- emitInstance(g, g.gl_instances.ptr, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx);
+ emitInstance(g, g.gl_instances.ptr, idx, col, row, layout, win_w, win_h, surface.at(col, row), idx == cursor_idx, cursor_bar);
}
}
if (g.atlas_dirty) uploadAtlasGl(g);
diff --git a/src/pardes.zig b/src/pardes.zig
index 32a58f89..51965bd4 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -217,7 +217,8 @@ pub const Surface = struct {
cols: u16 = 0,
rows: u16 = 0,
cells: []Cell = &.{},
- cursor: ?struct { x: u16, y: u16 } = null,
+ /// bar: draw an insert-style thin cursor instead of the block
+ cursor: ?struct { x: u16, y: u16, bar: bool = false } = null,
images: [MAX_PANES]?ImagePlace = @splat(null),
nimages: usize = 0,
@@ -958,7 +959,8 @@ pub const Pardes = struct {
}
pane.tag_edit = true;
pane.tag_sel = false;
- pane.mode = .normal;
+ // a one-line tag has no use for normal mode: always insert
+ pane.mode = .insert;
pane.pending = 0;
const cc = std.math.clamp(click_col - @as(i32, @intCast(pfx.len)), 0, @as(i32, @intCast(pane.tag_tail.items.len)));
pane.tag_col = @intCast(cc);
@@ -968,6 +970,7 @@ pub const Pardes = struct {
pane.tag_edit = false;
pane.tag_sel = false;
pane.search_edit = false; // an abandoned `/` input stays as tag text
+ pane.mode = .normal; // tags are always insert; leaving one never is
}
fn tagSelBounds(pane: *Pane) struct { lo: usize, hi: usize } {
@@ -998,66 +1001,17 @@ pub const Pardes = struct {
/// tag-edit key dispatch: a modal one-line editor over the tail, sharing
/// the pane's mode. Newlines are always dropped.
- fn handleTagKey(p: *Pardes, pane: *Pane, key: Key) void {
- if (pane.mode == .insert) return p.tagInsertKey(pane, key);
- const lines = [1][]const u8{pane.tag_tail.items};
- const cur: modal.Cursor = .{ .row = 0, .col = pane.tag_col };
-
- if (pane.pending == 'g') {
- pane.pending = 0;
- const m: modal.Cursor = if (is(key, 'g')) modal.gotoFirst() else if (is(key, 'e')) modal.gotoLast(&lines) else if (is(key, 'h')) modal.lineStart(cur) else if (is(key, 'l')) modal.lineEnd(&lines, cur) else cur;
- pane.tag_col = @intCast(m.col);
- return;
- }
- if (is(key, 'g')) {
- pane.pending = 'g';
- return;
- }
- const moved: ?modal.Cursor =
- if (is(key, 'h') or is(key, Key.left)) modal.charLeft(cur) else if (is(key, 'l') or is(key, Key.right)) modal.charRight(&lines, cur) else if (is(key, 'w')) modal.nextWordStart(&lines, cur, false) else if (is(key, 'W')) modal.nextWordStart(&lines, cur, true) else if (is(key, 'b')) modal.prevWordStart(&lines, cur, false) else if (is(key, 'B')) modal.prevWordStart(&lines, cur, true) else if (is(key, 'e')) modal.nextWordEnd(&lines, cur, false) else if (is(key, 'E')) modal.nextWordEnd(&lines, cur, true) else if (is(key, '0') or is(key, Key.home)) modal.lineStart(cur) else if (is(key, '$') or is(key, Key.end)) modal.lineEnd(&lines, cur) else if (is(key, '^')) modal.firstNonWsOf(&lines, cur) else null;
- if (moved) |m| {
- pane.tag_col = @intCast(m.col);
- return;
- }
- if (is(key, 'i')) return tagEnterInsert(pane, pane.tag_col);
- if (is(key, 'a')) return tagEnterInsert(pane, @intCast(modal.charRight(&lines, cur).col));
- if (is(key, 'I')) return tagEnterInsert(pane, @intCast(modal.firstNonWsOf(&lines, cur).col));
- if (is(key, 'A')) return tagEnterInsert(pane, @intCast(pane.tag_tail.items.len));
- if (is(key, 'v')) {
- pane.tag_sel = !pane.tag_sel;
- if (pane.tag_sel) pane.tag_anchor = pane.tag_col;
- return;
- }
- if (is(key, 'x')) { // select the whole one-line tail
- const len = pane.tag_tail.items.len;
- if (len > 0) {
- pane.tag_sel = true;
- pane.tag_anchor = 0;
- pane.tag_col = @intCast(len - 1);
- }
- return;
- }
- if (is(key, 'd')) return p.tagDelete(pane, false);
- if (is(key, 'c')) return p.tagDelete(pane, true);
- if (is(key, 'y')) return p.tagYank(pane);
- if (is(key, 'p')) return p.tagPaste(pane);
- // Esc escalates: clear the selection, else hand focus back to the body
+ fn tagInsertKey(p: *Pardes, pane: *Pane, key: Key) void {
if (is(key, Key.escape)) {
- if (pane.tag_sel) pane.tag_sel = false else exitTagEdit(pane);
+ exitTagEdit(pane); // the tag is ALWAYS insert; Esc leaves it
return;
}
- }
-
- fn tagEnterInsert(pane: *Pane, col: u16) void {
- pane.tag_col = @min(col, @as(u16, @intCast(pane.tag_tail.items.len)));
- pane.tag_sel = false;
- pane.mode = .insert;
- pane.pending = 0;
- }
-
- fn tagInsertKey(p: *Pardes, pane: *Pane, key: Key) void {
- if (is(key, Key.escape)) {
- pane.mode = .normal;
+ // Enter looks / Tab executes the selection or the word at the cursor
+ if (is(key, Key.enter) or is(key, Key.tab)) {
+ if (p.tagChordText(pane)) |txt| {
+ pane.tag_sel = false;
+ p.actOnSelection(if (is(key, Key.tab)) .middle else .right, p.active, txt, null);
+ }
return;
}
if (key.text.len > 0) {
@@ -1087,50 +1041,6 @@ pub const Pardes = struct {
/// d/c: delete the selection (or the char under the cursor), yanking it;
/// c then enters insert
- fn tagDelete(p: *Pardes, pane: *Pane, change: bool) void {
- const tail = pane.tag_tail.items;
- var lo: usize = undefined;
- var count: usize = undefined;
- if (pane.tag_sel) {
- const b = tagSelBounds(pane);
- lo = b.lo;
- count = @min(b.hi + 1, tail.len) - b.lo;
- } else {
- lo = @min(@as(usize, pane.tag_col), tail.len);
- count = if (lo < tail.len) 1 else 0;
- }
- if (count > 0) {
- p.setYank(tail[lo .. lo + count]);
- pane.tag_tail.replaceRange(p.gpa, lo, count, "") catch return;
- }
- pane.tag_col = @intCast(lo);
- pane.tag_sel = false;
- if (change) pane.mode = .insert;
- }
-
- fn tagYank(p: *Pardes, pane: *Pane) void {
- const tail = pane.tag_tail.items;
- if (pane.tag_sel) {
- const b = tagSelBounds(pane);
- const hi = @min(b.hi + 1, tail.len);
- p.setYank(tail[b.lo..hi]);
- pane.tag_col = @intCast(b.lo);
- pane.tag_sel = false;
- } else p.setYank(tail); // whole tail, like a line yank
- }
-
- fn tagPaste(p: *Pardes, pane: *Pane) void {
- const y = p.yank orelse return;
- const col: usize = @min(@as(usize, pane.tag_col), pane.tag_tail.items.len);
- var at = col;
- for (y) |ch| {
- if (ch == '\n' or ch == '\r') continue;
- pane.tag_tail.insert(p.gpa, at, ch) catch break;
- at += 1;
- }
- pane.tag_col = @intCast(at);
- }
-
/// tag + '\n' + body: the full selectable pane text (row 0 = the tag)
fn paneText(p: *Pardes, pane: *Pane) ![]u8 {
const arena = p.scratch.allocator();
@@ -1272,13 +1182,6 @@ pub const Pardes = struct {
// (on the tag tail when it's focused for editing, else the body).
if (pane.mode == .normal and (is(key, Key.enter) or is(key, Key.tab))) {
const btn: Mouse.Button = if (is(key, Key.tab)) .middle else .right;
- if (pane.tag_edit) {
- if (p.tagChordText(pane)) |txt| {
- pane.tag_sel = false;
- p.actOnSelection(btn, p.active, txt, null);
- }
- return;
- }
pane.pinCursor();
if (p.currentSelText(pane)) |txt| {
pane.vsel.active = false;
@@ -1295,7 +1198,7 @@ pub const Pardes = struct {
// tag editing intercepts every other key: a modal one-line editor
// over the tail, sharing the pane's mode.
if (pane.tag_edit) {
- p.handleTagKey(pane, key);
+ p.tagInsertKey(pane, key);
return;
}
// normal-mode `p` pastes the SYSTEM clipboard: an async OSC 52 round
@@ -3564,7 +3467,7 @@ pub const Pardes = struct {
// cursor while editing the tail: prefix length + column within it
if (active and pane.tag_edit) {
const cx: usize = pfx.len + pane.tag_col;
- if (cx < tw) s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y };
+ if (cx < tw) s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y, .bar = true }; // tags are always insert
}
// image pane: the picture fills the body — petscii glyph art into the
@@ -3760,9 +3663,9 @@ pub const Pardes = struct {
else
ccol;
if (prow >= 0 and cx >= 0 and prow < r.h and cx < tw)
- s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y + @as(u16, @intCast(prow)) };
+ s.cursor = .{ .x = tx + @as(u16, @intCast(cx)), .y = r.y + @as(u16, @intCast(prow)), .bar = pane.mode == .insert };
} else if (cur.y + BOX_H < r.h and cur.x < tw) {
- s.cursor = .{ .x = tx + cur.x, .y = r.y + cur.y + BOX_H };
+ s.cursor = .{ .x = tx + cur.x, .y = r.y + cur.y + BOX_H, .bar = pane.mode == .insert };
}
}
diff --git a/src/tty.zig b/src/tty.zig
index f503303a..ed97fd2a 100644
--- a/src/tty.zig
+++ b/src/tty.zig
@@ -260,7 +260,11 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
h.draw(child, .{ .scale = .contain }) catch {};
}
}
- if (surface.cursor) |cur| win.showCursor(cur.x, cur.y);
+ if (surface.cursor) |cur| {
+ win.showCursor(cur.x, cur.y);
+ // insert = beam, everything else = the terminal's default shape
+ win.setCursorShape(if (cur.bar) .beam else .default);
+ }
const tz_render = tracy.zone(@src(), "vx.render");
try vx.render(tty.writer());
tz_render.end();
diff --git a/src/tutor.txt b/src/tutor.txt
index 94a2a20e..4c162b97 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -73,7 +73,10 @@
THE TAG: each pane has a one-line tag: its MODE (NOR/INS/TTY) + directory
or file path + builtins. File panes show "Save Del" by default; Save
- writes the current file to disk, Del closes the window. Terminal and
+ writes the current file to disk, Del closes the window. Clicking a tag
+ edits it, ALWAYS in insert mode (a one-line tag has no use for normal
+ mode): type straight in, Enter looks / Tab executes the word at the
+ cursor, Esc hands focus back to the body. Terminal and
image panes show "Del". "Delcol" still exists as a command: type it in
a tag or body and execute it to close the whole column. A SEPARATE bar
across the top of the screen holds the window-agnostic builtins:
diff --git a/test/snapshots/tag.golden b/test/snapshots/tag.golden
index 9632ab6a..2765c044 100644
--- a/test/snapshots/tag.golden
+++ b/test/snapshots/tag.golden
@@ -1,6 +1,6 @@
== snap tag-focus grid=100x30 cursor=34,1
|Kill Newcol Tutor Debug Colors NextColor Dump
-| NOR /tmp/pardes-snap/tag/cwd Del NOR /tmp/pardes-snap/tag/cwd Del
+| INS /tmp/pardes-snap/tag/cwd Del NOR /tmp/pardes-snap/tag/cwd Del
|
| t.txt t.txt
|
@@ -29,9 +29,9 @@
|
|
|
-== snap tag-typed grid=100x30 cursor=47,1
+== snap tag-typed grid=100x30 cursor=40,1
|Kill Newcol Tutor Debug Colors NextColor Dump
-| NOR /tmp/pardes-snap/tag/cwd Del echo TAG''OK NOR /tmp/pardes-snap/tag/cwd Del
+| INS /tmp/pardes-snap/tag/cwd Del t.txt NOR /tmp/pardes-snap/tag/cwd Del
|
| t.txt t.txt
|
@@ -60,13 +60,12 @@
|
|
|
-== snap tag-exec grid=100x30 cursor=46,1
+== snap tag-del grid=100x30 cursor=36,1
|Kill Newcol Tutor Debug Colors NextColor Dump
-| NOR /tmp/pardes-snap/tag/cwd Del echo TAG''OK NOR /tmp/pardes-snap/tag/cwd Del
+| INS /tmp/pardes-snap/tag/cwd Del t NOR /tmp/pardes-snap/tag/cwd Del
|
| t.txt t.txt
|
-| TAGOK
|
|
|
@@ -77,36 +76,6 @@
|
|
|
-| NOR /tmp/pardes-snap/tag/cwd Del
-|
-| t.txt
-|
-|
-|
-|
-|
-|
-|
-|
-|
-|
-|
-== snap tag-vsel grid=100x30 cursor=32,1
-|Kill Newcol Tutor Debug Colors NextColor Dump
-| NOR /tmp/pardes-snap/tag/cwd Del echo TAG''OK NOR /tmp/pardes-snap/tag/cwd Del
-|
-| t.txt t.txt
-|
-| TAGOK
-|
-|
-|
-|
-|
-|
-|
-|
-|
|
| NOR /tmp/pardes-snap/tag/cwd Del
|
@@ -122,44 +91,13 @@
|
|
|
-== style tag-vsel
-|0: 0-44 #969696,#2c2a3e, 45-99 d,#2c2a3e,
-|1: 0-1 d,#6e4fd0, 2-29 #969696,#2c2a3e, 30-32 #d8c8a8,#4a3a1e, 33-46 #969696,#2c2a3e, 47-49 d,#2c2a3e, 50-51 d,#383c54, 52-83 #969696,#2c2a3e, 84-99 d,#2c2a3e,
-|2: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|3: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|4: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|5: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|6: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|7: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|8: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|9: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|10: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|11: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|12: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|13: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|14: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|15: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|16: 0-1 d,#383c54, 2-33 #969696,#2c2a3e, 34-49 d,#2c2a3e, 50-51 d,#525252, 52-99 d,d,
-|17: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|18: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|19: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|20: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|21: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|22: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|23: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|24: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|25: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|26: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|27: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|28: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-|29: 0-1 d,#525252, 2-49 d,d, 50-51 d,#525252, 52-99 d,d,
-== snap tag-del grid=100x30 cursor=30,1
+== snap tag-exit grid=100x30 cursor=4,4
|Kill Newcol Tutor Debug Colors NextColor Dump
-| NOR /tmp/pardes-snap/tag/cwdl echo TAG''OK NOR /tmp/pardes-snap/tag/cwd Del
+| NOR /tmp/pardes-snap/tag/cwd Del t.txt NOR /tmp/pardes-snap/tag/cwd Del
|
| t.txt t.txt
|
-| TAGOK
+|
|
|
|
@@ -184,18 +122,18 @@
|
|
|
-== snap tag-exit grid=100x30 cursor=4,6
+== snap tag-look grid=100x30 cursor=7,2
|Kill Newcol Tutor Debug Colors NextColor Dump
-| NOR /tmp/pardes-snap/tag/cwdl echo TAG''OK NOR /tmp/pardes-snap/tag/cwd Del
-|
-| t.txt t.txt
-|
-| TAGOK
+| NOR /tmp/pardes-snap/tag/cwd/t.txt Save Del NOR /tmp/pardes-snap/tag/cwd Del
+| 1 x
+| t.txt
|
|
|
|
+| INS /tmp/pardes-snap/tag/cwd Del t.txt
|
+| t.txt
|
|
|
diff --git a/test/snapshots/tag.snap b/test/snapshots/tag.snap
index 0d7be294..449ed2ce 100644
--- a/test/snapshots/tag.snap
+++ b/test/snapshots/tag.snap
@@ -1,41 +1,42 @@
-# tag editor: click the tag row, edit the tail modally, execute a typed word
+# the tag is ALWAYS insert mode: click it and type straight in (no 'i');
+# backspace edits; Esc leaves the tag back to the body's normal mode; Enter
+# looks the word at the cursor (Tab executes it). Multi-word execution from
+# the tag stays the mouse's job (middle-drag), like anywhere else.
file t.txt x
start 30 100 -n 3
wait 8000 t.txt
stable 700 20000
-# click into the tag tail (after "Del", col past prefix) of pane 0
+# click into the tag tail of pane 0: straight into insert (INS in the prefix)
press left 40 2
release left 40 2
stable 400 5000
snap tag-focus
-# append a word: A enters insert at end of tail
-key A
-send \x20echo\x20TAG''OK
-key esc
+send \x20t.txt
settle 700
stable 400 5000
snap tag-typed
-# v-select "echo TAG''OK" (col 4 .. line end) and execute it with Tab
-key g h
-key l l l l
-key v
-key g l
-stable 400 5000
-key tab
-wait 10000 TAGOK
-stable 700 10000
-snap tag-exec
-# v-select a few chars and delete them
-key g h
-stable 400 5000
-key v l l
-stable 400 5000
-snap tag-vsel
-snapstyle tag-vsel
-key d
+key bs
+key bs
+key bs
+key bs
stable 400 5000
snap tag-del
-# esc: first collapses selection state, then leaves tag editing
+send .txt
+settle 700
+stable 400 5000
+# Esc: out of the tag, body back in normal mode, typed tail stays as text
key esc
settle 700
+stable 400 5000
snap tag-exit
+# re-enter the tag and look the typed word: Enter opens t.txt as a file pane
+press left 40 2
+release left 40 2
+stable 400 5000
+key left
+key left
+stable 400 5000
+key enter
+wait 10000 Save
+stable 700 15000
+snap tag-look