Skip to content

Commit 1b9ef5b

Browse files
committed
[Feat] finished documentation
1 parent 2a90ab6 commit 1b9ef5b

File tree

14 files changed

+231
-58
lines changed

14 files changed

+231
-58
lines changed

app/src/commands/cat.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::vfs_data::{find_node, format_path, resolve_path, VfsKind};
44
use micro_cli::Parser;
55
use shell_parser::integration::{CommandInfo, ExecutableCommand};
66
use wasm_bindgen_futures::spawn_local;
7+
use yew::html;
78

89
#[derive(Parser, Debug, Default)]
910
#[command(name = "cat", about = "Print file contents")]
@@ -50,7 +51,9 @@ async fn run_cat(cli: CatCommand, ctx: CommandContext) {
5051
let uri = format!("/data/{}", path.join("/"));
5152

5253
match fetch_text_with_cache(&uri, &cache).await {
53-
Ok(text) => ctx.terminal.push_text(text),
54+
Ok(text) => ctx.terminal.push_component(html! {
55+
<span class="whitespace-break-spaces">{text}</span>
56+
}),
5457
Err(err) => ctx.terminal.push_error(format!("cat: {err}")),
5558
}
5659
}

app/src/commands/help.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::commands::CommandContext;
22
use micro_cli::Parser;
33
use shell_parser::integration::ExecutableCommand;
4+
use yew::html;
45

56
#[derive(Parser, Debug, Default)]
67
#[command(name = "help", about = "Print the given text to the console")]
@@ -9,7 +10,9 @@ pub struct HelpCommand;
910
impl ExecutableCommand<CommandContext> for HelpCommand {
1011
fn run(&self, _args: &[String], ctx: &CommandContext) -> Result<(), String> {
1112
match ctx.terminal.help() {
12-
Ok(help_message) => ctx.terminal.push_text(help_message),
13+
Ok(help_message) => ctx.terminal.push_component(html! {
14+
<span class="whitespace-break-spaces">{help_message}</span>
15+
}),
1316
Err(err) => return Err(format!("Failed to get help message: {}", err)),
1417
};
1518

app/src/components/markdown_renderer/code_block.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ pub fn code_block(props: &CodeBlockProps) -> Html {
5656
};
5757

5858
html! {
59-
<pre class="my-4 overflow-x-auto rounded-lg bg-black/60 text-sm text-slate-100">
60-
<code class={classes!(lang_class, "block", "font-mono", "leading-6")}>
59+
<pre class="my-4 overflow-x-auto no-scrollbar rounded-lg bg-black/60 text-sm text-slate-100 border border-[0.5px] border-border bg-card shadow-[0_20px_60px_-25px_rgba(0,0,0,0.85)] backdrop-blur-xl ring-[0.5px] ring-border px-2 py-4">
60+
<code class={classes!(lang_class, "block", "font-mono", "leading-6", "w-fit")}>
6161
{
6262
if let Some(lines) = &*highlighted_lines {
6363
html! {

app/src/components/output_log.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ fn render_line(idx: usize, line: &TermLine) -> Html {
3636
};
3737

3838
html! {
39-
<div class="leading-relaxed flex gap-2 w-full" key={idx.to_string()}>
40-
<span class={classes!(text_class, "break-words", "whitespace-break-spaces", "w-full")}>{ content }</span>
39+
<div class="output-log leading-relaxed flex gap-2 w-full" key={idx.to_string()}>
40+
<span class={classes!(text_class, "break-words", "w-full")}>{ content }</span>
4141
</div>
4242
}
4343
}

app/src/components/typewriter.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,10 @@ pub fn typewriter(props: &TypewriterProps) -> Html {
124124
}
125125
});
126126

127-
let class = classes!("whitespace-pre-wrap", props.class.clone());
128127
let typed_html = Html::from_html_unchecked(AttrValue::from((*rendered_text).clone()));
129128

130129
html! {
131-
<span class={class}>
130+
<span class={props.class.clone()}>
132131
{ typed_html }
133132
<span ref={template_ref} style="display: none;" aria-hidden="true">
134133
{ props.content.clone() }

app/src/styles/global.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
@apply text-xl;
3939
color: #dddddd;
4040
}
41+
42+
.output-log p {
43+
@apply my-6;
44+
}
4145
}
4246

4347
@keyframes halo-drift {
@@ -63,6 +67,15 @@
6367
.halo-animate {
6468
animation: halo-drift 9s ease-in-out infinite;
6569
}
70+
71+
.no-scrollbar::-webkit-scrollbar {
72+
display: none;
73+
}
74+
75+
.no-scrollbar {
76+
-ms-overflow-style: none; /* IE and Edge */
77+
scrollbar-width: none; /* Firefox */
78+
}
6679
}
6780

6881
@layer components {

app/src/terminal.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::terminal_state::{TerminalAction, TerminalState};
66
use crate::types::{OutputKind, TermLine};
77
use crate::vfs_data::{load_vfs, VfsNode};
88
use gloo_timers::future::TimeoutFuture;
9-
use shell_parser::{with_cli, CliRunner, ScriptResult, ShellParseError};
9+
use shell_parser::{with_cli, CliRunner, CommandInvocation, ScriptResult, ShellParseError};
1010
use std::cell::RefCell;
1111
use std::ops::Deref;
1212
use std::rc::{Rc, Weak};
@@ -198,7 +198,23 @@ impl TerminalHandle {
198198
}
199199

200200
pub fn execute_command(&self, input: &str) {
201-
match self.run_script(input) {
201+
let outcome = self.run_script(input);
202+
self.handle_execution_result(outcome);
203+
}
204+
205+
fn execute_invocations(&self, commands: Vec<CommandInvocation>) {
206+
if commands.is_empty() {
207+
return;
208+
}
209+
let result = self.run_parsed(&commands);
210+
self.handle_execution_result(result);
211+
}
212+
213+
fn handle_execution_result(
214+
&self,
215+
outcome: Result<ScriptResult, shell_parser::integration::ShellCliError>,
216+
) {
217+
match outcome {
202218
Ok(ScriptResult::Completed) => {}
203219
Ok(ScriptResult::Paused {
204220
delay_ms,
@@ -211,14 +227,14 @@ impl TerminalHandle {
211227
}
212228
}
213229

214-
fn schedule_resume(&self, delay_ms: u32, remainder: String) {
230+
fn schedule_resume(&self, delay_ms: u32, remainder: Vec<CommandInvocation>) {
231+
if remainder.is_empty() {
232+
return;
233+
}
215234
let terminal = self.clone();
216235
spawn_local(async move {
217236
TimeoutFuture::new(delay_ms).await;
218-
if remainder.trim().is_empty() {
219-
return;
220-
}
221-
terminal.execute_command(&remainder);
237+
terminal.execute_invocations(remainder);
222238
});
223239
}
224240

@@ -229,6 +245,13 @@ impl TerminalHandle {
229245
self.runner_else()?.run_script(input)
230246
}
231247

248+
fn run_parsed(
249+
&self,
250+
invocations: &[CommandInvocation],
251+
) -> Result<ScriptResult, shell_parser::integration::ShellCliError> {
252+
self.runner_else()?.run_invocations(invocations)
253+
}
254+
232255
pub fn to_terminal(&self) -> Option<Terminal> {
233256
self.runner().map(|runner| Terminal {
234257
handle: self.clone(),

app/src/vfs.json

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
"kind": "directory",
55
"extension": null,
66
"size": null,
7-
"modified": "2025-12-16T09:50:02.889432225Z",
8-
"children_count": 16,
7+
"modified": "2025-12-16T11:32:55.863207394Z",
8+
"children_count": 18,
99
"children": [
1010
{
1111
"name": ".shrc",
1212
"path": ".shrc",
1313
"kind": "file",
1414
"extension": null,
15-
"size": 119,
16-
"modified": "2025-12-16T09:50:02.888989992Z",
15+
"size": 400,
16+
"modified": "2025-12-16T11:32:55.853454078Z",
1717
"children_count": null,
1818
"children": null,
1919
"title": null,
@@ -38,8 +38,34 @@
3838
"path": "02_help.md",
3939
"kind": "file",
4040
"extension": "md",
41-
"size": 676,
42-
"modified": "2025-12-16T09:47:15.486857339Z",
41+
"size": 1417,
42+
"modified": "2025-12-16T10:44:55.065899617Z",
43+
"children_count": null,
44+
"children": null,
45+
"title": null,
46+
"description": null,
47+
"is_post": false
48+
},
49+
{
50+
"name": "03_design.md",
51+
"path": "03_design.md",
52+
"kind": "file",
53+
"extension": "md",
54+
"size": 1304,
55+
"modified": "2025-12-16T11:25:01.827224659Z",
56+
"children_count": null,
57+
"children": null,
58+
"title": null,
59+
"description": null,
60+
"is_post": false
61+
},
62+
{
63+
"name": "04_configuration.md",
64+
"path": "04_configuration.md",
65+
"kind": "file",
66+
"extension": "md",
67+
"size": 1087,
68+
"modified": "2025-12-16T11:30:32.297212623Z",
4369
"children_count": null,
4470
"children": null,
4571
"title": null,

data/.shrc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
function echo_with_next_prompt() {
22
builtin echo "$@"
3+
sleep 1000
34
render -t -r 02_help.md
45
}
56

67
alias echo="echo_with_next_prompt"
8+
9+
function help_with_next_prompt() {
10+
builtin help
11+
sleep 1000
12+
render -t -r 03_design.md
13+
}
14+
15+
alias help="help_with_next_prompt"
16+
17+
function cat_with_next_prompt() {
18+
builtin cat "$@"
19+
sleep 1000
20+
render -t -r 04_configuration.md
21+
}
22+
23+
alias cat="cat_with_next_prompt"

data/02_help.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,36 @@ You just saw Hello world printed on the screen.
22
Feels familiar, doesn’t it?
33

44
It may look like a regular shell command, but this command is actually running inside zzhack.
5-
Every command you type is mapped to an implementation under app/src/commands.
5+
Every command you type is mapped to an implementation under `app/src/commands`.
66

77
If a command implements the ExecutableCommand trait, zzhack knows how to execute it.
8-
Here’s a simplified version of how echo is implemented:
8+
Here’s a simplified version of how `echo` is implemented:
99

10-
xxxxxx
10+
[app/src/commands/echo.rs](https://github.com/mistricky/zzhack/blob/main/app/src/commands/echo.rs)
11+
```rust
12+
#[derive(Parser, Debug, Default)]
13+
#[command(name = "echo", about = "Print the given text to the console")]
14+
pub struct EchoCommand {
15+
#[arg(positional, help = "Text to echo")]
16+
message: Vec<String>,
17+
}
1118

19+
impl ExecutableCommand<CommandContext> for EchoCommand {
20+
fn run(&self, args: &[String], ctx: &CommandContext) -> Result<(), String> {
21+
let Some(cli) = parse_cli::<EchoCommand>(args, ctx, self.command_name()) else {
22+
return Ok(());
23+
};
24+
let msg = cli.message.join(" ");
1225

13-
zzhack isn’t trying to be a full shell.
14-
The parser is intentionally minimal—and that’s a feature, not a limitation.
15-
For a terminal-style personal website, it’s more than enough.
26+
console::log_1(&msg.clone().into());
27+
ctx.terminal.push_text(msg);
28+
Ok(())
29+
}
30+
}
31+
```
32+
33+
34+
Notice that zzhack isn’t trying to be a full shell, the parser is intentionally minimal—and that’s a feature, not a limitation.but for a terminal-style personal website, it’s more than enough.
1635

1736
Curious what else you can run?
1837

0 commit comments

Comments
 (0)