Skip to content
Open
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
61 changes: 50 additions & 11 deletions docs/plans/view.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,17 @@ made for it.
picked term, so nothing is lost. No colour: reverse video is the
terminal's own, and a background chosen here is a bet on somebody's
theme.
- ⚠ **Toggling `z` keeps what was SELECTED, not the row numbers.** Both
ends travel as offsets, and the upper one is taken to the END of the
entry it lands in: a joined row is an entry, so unjoining it is several
lines and an offset resolves to the first of them. Keeping the numbers
collapsed a selection of two records to their two first lines — a
highlight that read as lost, and a copy that then took two lines of the
fourteen selected. Joined, every row is a first, so the rule widens
nothing in that direction and needs no test of which way the toggle
went. The cursor is one of those ends, so where it was the upper one
the view follows it to the entry's end; a selection longer than the
screen cannot show both ends anyway.
- ⚠ **Toggling `z`, or opening one entry with `Z`, keeps what was
SELECTED, not the row numbers.** Both ends travel as offsets, and the
upper one is taken to the END of the entry it lands in: a joined row is
an entry, so unjoining it is several lines and an offset resolves to
the first of them. Keeping the numbers collapsed a selection of two
records to their two first lines — a highlight that read as lost, and a
copy that then took two lines of the fourteen selected. Joined, every
row is a first, so the rule widens nothing in that direction and needs
no test of which way the toggle went. The cursor is one of those ends,
so where it was the upper one the view follows it to the entry's end; a
selection longer than the screen cannot show both ends anyway.
- **A joined row copies as the log's own lines.** `z` renders an entry as
one row with `↵` between its lines; that is a thing to read, and the
clipboard wants what the log holds. So `z` then a mark is how entry-wide
Expand Down Expand Up @@ -175,6 +175,45 @@ multiplexer.
there too rather than being handed to something that would truncate it
without saying so.

## Built: ten traces on one screen, and one of them opened

`z` renders every multi-line entry as one row and `Z` opens the entry
under the cursor back out into its own lines.

- **The question `z` answers is "are these ten the same failure".** Four
hundred lines of trace answer it with no screen to answer it on; ten
rows put the messages in a column where the eye compares them, with
the frames trailing off to the right where `h`/`l` can go and read
them. The continuation lines are LSTRIPPED as they join — their indent
is what puts frames under a message that is no longer above them, and
it is the difference between ten rows that line up and ten that do not.
- **A rendering, not a fold.** Every line of the entry is on the row, so
the search, `Tab`, the hit list and the entry motion all keep working:
each row is a real line with a real address. A fold would have had to
teach every one of those about lines that are not on the screen.
- **And `Z` is the next question**: which is exactly one of the ten, read
in full, while the other nine stay rows to come back to. Several can be
open at once, because comparing two of them is what picking them out of
the ten was for. It is an EXCEPTION to a rendering rather than a second
mode — an open entry is the source's own lines, so its framing is the
entry framing again and a bare `c` still takes the whole trace — and
`z`, which sets the whole screen, builds the join afresh and so clears
the exceptions.
- **Any of its lines closes it**, not just its first: what the reader is
pointing at is the entry, and having to walk back to its head to put it
away is a coordinate they would have to keep.
- ⚠ **An open entry is remembered by its ORDINAL**, the nth entry of the
answer — not by its offset, which is how every other position in this
pager is held. An entry still at a live edge has no offset yet and they
all report 0, so a set of offsets would open every live-edge entry at
once. The answer is a closed set, so the nth entry stays the nth.
- **A decorator over the source, not a mode inside it.** `Tape` and
`Records` both have entries in this sense, the view swaps its source at
runtime when a hit is in another store, and neither should learn about
a display option. On the tape, which parses nothing, every line is its
own entry — so joining is the identity there and `Z` says there is
nothing folded in, exactly as the entry motion is a line there.

## /etc/hosts, and the DNS that would replace it

**The hosts file half is BUILT.** `TIMBERFS_CMD` plus `TIMBERFS_HOSTS` was
Expand Down
21 changes: 20 additions & 1 deletion packaging/timberview.1
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ all keep working, because every line on the screen is a real line with a
real address.
.RE
.TP
.B Z
Open THIS entry back out of the join, as its own lines \(em the one of
the ten you picked out to read, while the other nine stay rows to come
back to. Pressing it again joins it, from ANY of its lines: what you are
pointing at is the entry, not the row.
.RS
.PP
Several can be open at once, because comparing two traces is the next
thing after picking them out of the ten. It is an exception to a
rendering and not a second mode \(em the entry is still one entry to the
entry motion and to a bare
.BR c ,
and
.BR z ,
which sets the whole screen, builds the join afresh and so clears them.
.RE
.TP
.BR space " " b
A page.
.BR h " " l " " \(<- " " \(->
Expand Down Expand Up @@ -240,7 +257,9 @@ in \(em that rendering is for reading. So
.B z
and then a mark is how a run of whole entries is selected. Toggling
.B z
with a region live keeps what was selected rather than the row numbers:
\(em or opening one entry with
.B Z
\(em with a region live keeps what was selected rather than the row numbers:
both ends travel as offsets, and the upper one is taken to the END of the
entry it lands in, since a row unjoins into several lines and an offset
resolves to the first of them.
Expand Down
104 changes: 104 additions & 0 deletions tests/timberview/test-timberview
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,110 @@ def joining_keeps_the_entry_you_are_on():
(v.cur, v.source.lines[v.cur].text)


@test
def one_entry_can_be_opened_back_out_of_the_join():
"""`z` is the whole answer at once, which is what makes ten traces
comparable — and then one of them is the one you read. Opening it is
an exception to the join and not a second mode: everything else stays
a row, and the entry is still one entry to the motions and the copy."""
frames = "\n".join(f" at frame.{i}(F.java:1)" for i in range(5))
r = tv.Records(answer(("one", {"offset": 0}),
(f"boom\n{frames}", {"offset": 100}),
("three", {"offset": 900})))
v = tv.View(Fake(["x\n"]), source=r)
# Through the KEYS: a binding nothing exercises is one a refactor can
# lose, and `Z` is only reachable through one.
screen = Screen_for_test()
screen.view = v
screen.step(Screenish(), ord("z"))
assert len(v.source.lines) == 3, [ln.text for ln in v.source.lines]

v.cur = 1
screen.step(Screenish(), ord("Z"))
assert len(v.source.lines) == 8, [ln.text for ln in v.source.lines]
# The other two are untouched, and the cursor is on what it was on.
assert [v.source.lines[i].text for i in (0, 7)] == ["one", "three"]
assert v.cur == 1 and v.source.lines[1].text == "boom", v.cur
assert "↵" not in "".join(ln.text for ln in v.source.lines)
# Still ONE entry: the whole trace is what a bare `c` takes, and the
# entry motion steps over it.
assert v.entry_span() == (1, 6), v.entry_span()
assert v.selection()[1] == "this entry, 6 lines", v.selection()[1]
v.move_entry(1)
assert v.source.lines[v.cur].text == "three", v.source.lines[v.cur].text

# From ANY of its lines, because what you point at is the entry.
v.cur = 4
v.open_entry()
assert len(v.source.lines) == 3, [ln.text for ln in v.source.lines]
assert v.source.lines[v.cur].text.startswith("boom ↵ at frame.0")

# A one-line entry has nothing folded into it, and says so.
v.cur = 0
v.open_entry()
assert len(v.source.lines) == 3 and "one line" in v.message, v.message


@test
def opening_one_entry_is_a_property_of_the_join():
"""It is an exception to a rendering, so it lives and dies with one:
`z` sets the whole screen, and a screen that is not joined has
nothing to open out of."""
r = tv.Records(answer(("boom\n at A\n at B", {"offset": 0}),
("next", {"offset": 100})))
v = tv.View(Fake(["x\n"]), source=r)
v.open_entry()
assert len(v.source.lines) == 4 and "nothing is joined" in v.message, \
v.message

v.join_entries()
v.cur = 0
v.open_entry()
assert len(v.source.lines) == 4, [ln.text for ln in v.source.lines]
v.join_entries() # everything as written
v.join_entries() # ...and joined again
assert len(v.source.lines) == 2, [ln.text for ln in v.source.lines]


@test
def entries_at_a_live_edge_open_one_at_a_time():
"""An entry that has not been placed yet has no offset, and every one
of them reports 0 — so an open entry is remembered by its ORDINAL. By
offset, opening one of them would open all the others with it."""
r = tv.Records(answer(("first\n at A", {}), ("second\n at B", {})))
v = tv.View(Fake(["x\n"]), source=r)
assert r.unplaced == 2, r.unplaced
v.join_entries()
v.cur = 1
v.open_entry()
assert [ln.text for ln in v.source.lines] == \
["first ↵ at A", "second", " at B"], \
[ln.text for ln in v.source.lines]


@test
def opening_an_entry_keeps_what_was_selected():
"""The rows renumber under it exactly as they do under `z`, so both
ends of a region travel as offsets — a region that quietly became a
different set of lines is the copy that takes the wrong thing."""
r = tv.Records(answer(("one", {"offset": 0}),
("boom\n at A\n at B", {"offset": 100}),
("three", {"offset": 900})))
v = tv.View(Fake(["x\n"]), source=r)
v.join_entries()
v.cur = 0
v.set_mark()
v.move(1) # "one", then the trace
whole = "one\nboom\n at A\n at B\n"
assert v.selection()[0] == whole, v.selection()

v.open_entry()
assert v.region() == (0, 3), v.region()
assert v.selection()[0] == whole, v.selection()
v.open_entry()
assert v.region() == (0, 1) and v.selection()[0] == whole, v.selection()


@test
def the_join_survives_the_view_changing_store():
"""A hit in another store replaces the source outright. A display
Expand Down
9 changes: 9 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,15 @@ match.
- **A multi-line entry is one entry.** The lines of a stack trace belong
to the entry that raised it; splitting them would be the same lie as
splitting a line across a chunk boundary.
- **Ten of them is four hundred lines**, and every one pushes the next
entry off the screen — which leaves no screen for the thing you are
doing, deciding whether these ten are the same failure. `z` renders
each entry as ONE row, the trace trailing off to the right of the
message that raised it, and `Z` opens the one you have picked out back
into its own lines while the other nine stay rows. Nothing is hidden
either way: a joined row carries every line of the entry, `h`/`l` read
along it, and the search, the terms and the entry motion keep working
because each row is a real line with a real address.
- **An answer is a closed set.** Both ends are ends, nothing extends, and
the boundary says "end of the answer" rather than naming a chunk you
are not in.
Expand Down
Loading
Loading