Skip to content

Commit 17ac981

Browse files
Add help page; visible by hitting F1; small changes to a couple keybindings.
1 parent 7cf1ce2 commit 17ac981

2 files changed

Lines changed: 211 additions & 4 deletions

File tree

src/app.rs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub const MAX_BUFFER_SIZE: usize = 9;
2525
const BOTTOM_CHROME_HEIGHT: usize = 2;
2626
const DEFAULT_SCROLLOFF: usize = 2;
2727

28+
// Help contents that we pipe to less.
29+
const HELP: &str = std::include_str!("./sless.help");
30+
2831
pub struct App<W: std::io::Write + AsFd, D: Document> {
2932
doc_while_waiting_for_input: Option<D>,
3033
viewer: Option<DocumentViewer<D>>,
@@ -147,8 +150,12 @@ impl<W: std::io::Write + AsFd, D: Document> App<W, D> {
147150
let count_or_1 = count.unwrap_or(1);
148151

149152
let action = match key_event {
150-
Key::Down | Key::Char('j') => Some(Action::MoveCursorDown(count_or_1)),
151-
Key::Up | Key::Char('k') => Some(Action::MoveCursorUp(count_or_1)),
153+
Key::Down | Key::Char('j') | Key::Ctrl('n') => {
154+
Some(Action::MoveCursorDown(count_or_1))
155+
}
156+
Key::Up | Key::Char('k') | Key::Ctrl('p') => {
157+
Some(Action::MoveCursorUp(count_or_1))
158+
}
152159
Key::Right | Key::Char('l') => {
153160
Some(Action::ExpandOrMoveCursorRightOrDown)
154161
}
@@ -169,11 +176,13 @@ impl<W: std::io::Write + AsFd, D: Document> App<W, D> {
169176
Some(Action::CollapseNodeAndSiblings(Some(count_or_1)))
170177
}
171178
Key::Char('C') => Some(Action::CollapseNodeAndSiblings(count)),
172-
Key::Home | Key::Char('g') => match count {
179+
Key::Home => Some(Action::FocusTop),
180+
Key::End => Some(Action::FocusBottom),
181+
Key::Char('g') => match count {
173182
None => Some(Action::FocusTop),
174183
Some(n) => Some(Action::MoveToLineIndex(n - 1)),
175184
},
176-
Key::End | Key::Char('G') => match count {
185+
Key::Char('G') => match count {
177186
None => Some(Action::FocusBottom),
178187
Some(n) => Some(Action::MoveToLineIndex(n - 1)),
179188
},
@@ -225,6 +234,10 @@ impl<W: std::io::Write + AsFd, D: Document> App<W, D> {
225234
}
226235
None
227236
}
237+
Key::F(1) => {
238+
self.show_help();
239+
None
240+
}
228241
_ => None,
229242
};
230243
self.input_buffer.clear();
@@ -361,6 +374,38 @@ impl<W: std::io::Write + AsFd, D: Document> App<W, D> {
361374
let _ = self.stdout.suspend_raw_mode();
362375
}
363376

377+
fn show_help(&mut self) {
378+
use std::io::Write;
379+
380+
// less also uses the alternate screen, so we first clear the screen.
381+
// We don't switch back to the main screen, otherwise there's a flicker.
382+
let _ = write!(self.stdout, "\x1b[2J");
383+
let _ = std::io::stdout().flush();
384+
385+
let child = std::process::Command::new("less")
386+
.arg("-r")
387+
.stdin(std::process::Stdio::piped())
388+
.stdout(std::process::Stdio::inherit())
389+
.spawn();
390+
391+
match child {
392+
Ok(mut child) => {
393+
if let Some(ref mut stdin) = child.stdin {
394+
let _ = stdin.write(HELP.as_bytes());
395+
let _ = stdin.flush();
396+
}
397+
let _ = child.wait();
398+
}
399+
Err(err) => {
400+
self.set_error_message(format!("Error piping help documentation to less: {err}"));
401+
}
402+
}
403+
404+
// Make sure to restore any settings that less might have messed with.
405+
let _ = TerminalSettings::enable_jless_settings();
406+
let _ = std::io::stdout().flush();
407+
}
408+
364409
fn buffer_input(&mut self, ch: u8) {
365410
// Don't buffer leading 0s.
366411
if self.input_buffer.is_empty() && ch == b'0' {

src/sless.help

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+

2+
sless - a terminal sexp viewer
3+
4+
SUMMARY OF SLESS COMMANDS
5+
6+
Commands marked with * may be preceded by a number, N, which will
7+
repeatedly perform a command the given number of times. A key
8+
preceded by a caret indicates the Ctrl key; thus ^E is ctrl-E.
9+
10+
Commands requiring multiple key-presses may be cancelled with the
11+
Escape key.
12+
13+
q :q[uit] ^c Exit sless.
14+
15+
F1 :h[elp] Show this help screen.
16+
17+
^z Suspend sless.
18+
19+
MOVING
20+
21+
j DownArrow * Move focus down one line (or N lines).
22+
^n
23+
24+
k UpArrow * Move focus up one line (or N lines).
25+
^p
26+
27+
h LeftArrow When focused on an expanded sexp, collapse it. Otherwise,
28+
move focus to the left of the focused node, or to a parent of
29+
the focused node.
30+
31+
H Move focus to the left or to a parent of the focused node
32+
without collapsing the focused node.
33+
34+
l RightArrow When focused on a collapsed sexp, expand it. Otherwise,
35+
move focus to the right of the focused node, or to a parent
36+
of the focused node.
37+
38+
PageDown * Move down by one window (or N windows).
39+
PageUp * Move up by one window (or N windows).
40+
41+
0 Move to the focused node's first sibling.
42+
$ Move to the focused node's last sibling.
43+
44+
Home Focus the first line in the document.
45+
End Focus the last line in the document.
46+
47+
g * Focus the first line in the document.
48+
G * Focus the last line in the document, and tail the bottom of
49+
document if sless is still receiving input.
50+
51+
If a count is given to either of these commands, focus that
52+
line number. If that line is not visible because a parent node
53+
is collapsed, focus the first preceding visible line.
54+
55+
c * Shallowly collapse the focused node and all its siblings.
56+
C * Deeply collapse the focused node and all its siblings.
57+
e * Shallowly expand the focused node and all its siblings.
58+
E * Deeply expand the focused node and all its siblings.
59+
60+
If a count is give to any of these commands, it will
61+
collapse/expand the focused node and all its siblings
62+
N levels to the given depth.
63+
64+
SCROLLING
65+
66+
^e * Scroll down one line (or N lines).
67+
^y * Scroll up one line (or N lines).
68+
69+
^d * Scroll down by half the height of the screen (or by N lines).
70+
^u * Scroll up by half the height of the screen (or by N lines).
71+
72+
For these commands, focus is also moved by the specified
73+
number of lines. If no count is specified, the number of
74+
lines to scroll is recalled from previous executions.
75+
76+
zz Move the focused node to the center of the screen.
77+
zt Move the focused node to the top of the screen.
78+
zb Move the focused node to the bottom of the screen.
79+
80+
COPYING AND PRINTING
81+
82+
You can copy various parts of the sexp file to your clipboard using
83+
any one of a set of commands starting with 'y'.
84+
85+
yy Copy the currently focused sexp, pretty printed. When focused on
86+
record field, this will not include the key.
87+
ym yY Copy the currently focused sexp, like yy, but on a single line,
88+
similar to [sexp print -machine], but with spaces separating atoms.
89+
Excludes comments.
90+
ya yA Copy the currently focused sexp and "all" its siblings either pretty
91+
printed like yy (ya), or on one line per node like ym (yA).
92+
93+
ys When the currently focused value is an atom, copy the contents
94+
of the string, with all escape sequences unescaped.
95+
96+
yk When focused on a record field, yank the key.
97+
98+
yc When focused on a variant, yank the constructor.
99+
100+
SEARCH
101+
102+
sless supports full-text search over the input sexp.
103+
104+
/pattern * Search forward for the given pattern (or its Nth occurrence).
105+
?pattern * Search backwards for the given pattern (or its Nth occurrence).
106+
107+
n * Move in the search direction to the next match (or forward
108+
N matches).
109+
N * Move in the opposite of the search direction to the previous
110+
match (or previous N matches).
111+
112+
^f * Move to the next match like n, but keeps the cursor in the same
113+
spot on screen and scrolls the viewport underneath it.
114+
^g * Move to the previous match like N, but keeps the cursor in the
115+
same spot on screen and scrolls the viewport underneath it.
116+
117+
Searching uses "smart case" by default. If the input pattern doesn't
118+
contain any capital letters, a case insensitive search will be
119+
performed. If there are any capital letters, it will be case sensitive.
120+
You can force a case-sensitive search by appending '/s' to your query.
121+
122+
A trailing slash will be removed from a pattern; to search for a
123+
pattern ending in '/' (or '/s'), just add another '/' to the end.
124+
125+
Search patterns are interpreted as mostly standard regular expressions.
126+
Because sexps contain endless parentheses, parenthese do *not* take on
127+
their usual meaning (grouping), and are instead interpreted literally.
128+
To search for a group, escape the parentheses with backslashes.
129+
130+
/(1 2 3) matches the list: (1 2 3)
131+
/\(b|c|h\)at matches "bat", "cat", or "hat"
132+
133+
For exhaustive documentation of the supported regular expression syntax,
134+
see the following documentation of the underlying regex engine:
135+
136+
https://docs.rs/regex/latest/regex/index.html#syntax
137+
138+
SEARCH INPUT
139+
140+
The search is *not* performed over the original input, but over a "nicely"
141+
formatted version of the input sexp that is (usually) formatted on a
142+
single line. If the input contains multiple top-level sexps, each one will
143+
be on a separate line.
144+
145+
Consider the following two ways to format an equivalent sexp:
146+
147+
((a 1)(b true)(c"x y z"))
148+
149+
((a 1)
150+
(b true)
151+
(c "x y z"))
152+
153+
sless will create an internal representation formatted as follows:
154+
155+
((a 1) (b true) (c "x y z"))
156+
157+
A sexp may be split across multiple lines in the internal representation
158+
if forced to do so due to line comments (e.g. "; this is a comment").
159+
160+
Searching will be performed over this internal representation so that
161+
patterns can include multiple elements without worrying about
162+
newlines or the exact input format.

0 commit comments

Comments
 (0)