Skip to content

Commit 7daa130

Browse files
committed
fix(repl): persist history via dirs and stabilize colored prompt
1 parent 37d4417 commit 7daa130

2 files changed

Lines changed: 47 additions & 17 deletions

File tree

js/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ publish = false
66

77
[dependencies]
88
clap = { version = "4.6.0", features = ["derive", "cargo"] }
9+
dirs = "6.0.0"
910
env_logger = { version = "0.11.10" }
1011
javascript = { path = "../", features = ["os", "std"] }
1112
rustyline = "18.0.0"

js/src/main.rs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
use javascript::*;
2+
use rustyline::Editor;
3+
use rustyline::completion::Completer;
4+
use rustyline::error::ReadlineError;
5+
use rustyline::highlight::Highlighter;
6+
use rustyline::hint::Hinter;
7+
use rustyline::history::FileHistory;
8+
use rustyline::validate::Validator;
9+
use std::borrow::Cow;
10+
use std::path::PathBuf;
211

312
#[derive(clap::Parser)]
413
#[command(name = "js", version, about = "JavaScript Rust Interpreter")]
@@ -33,6 +42,33 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
3342
}
3443
}
3544

45+
struct ReplPromptHelper;
46+
47+
impl Completer for ReplPromptHelper {
48+
type Candidate = String;
49+
}
50+
51+
impl Hinter for ReplPromptHelper {
52+
type Hint = String;
53+
}
54+
55+
impl Validator for ReplPromptHelper {}
56+
57+
impl Highlighter for ReplPromptHelper {
58+
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str, default: bool) -> Cow<'b, str> {
59+
let _ = default;
60+
if prompt == "js> " {
61+
Cow::Borrowed("\x1b[1;32mjs> \x1b[0m")
62+
} else if prompt == "... " {
63+
Cow::Borrowed("\x1b[1;33m... \x1b[0m")
64+
} else {
65+
Cow::Borrowed(prompt)
66+
}
67+
}
68+
}
69+
70+
impl rustyline::Helper for ReplPromptHelper {}
71+
3672
fn run_main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
3773
let cli = <Cli as clap::Parser>::parse();
3874

@@ -119,24 +155,23 @@ fn run_main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>
119155
// Persistent rustyline-powered REPL loop extracted into a helper to keep `main()` small.
120156
#[allow(clippy::println_empty_string)]
121157
fn run_persistent_repl() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
122-
use rustyline::Editor;
123-
use rustyline::error::ReadlineError;
124-
use std::path::PathBuf;
125-
126158
let ver = clap::crate_version!();
127-
println!("JavaScript Interpreter REPL (VM mode) v{ver}. Type 'exit' or Ctrl-D to quit.");
159+
println!("JavaScript Interpreter REPL (VM mode) v{ver}. Type '.exit' or Ctrl-D to quit.");
128160

129-
let mut rl = match Editor::<(), rustyline::history::FileHistory>::new() {
161+
let mut rl = match Editor::<ReplPromptHelper, FileHistory>::new() {
130162
Ok(e) => e,
131163
Err(err) => {
132164
eprintln!("Failed to initialize line editor: {err}");
133165
std::process::exit(1);
134166
}
135167
};
168+
rl.set_helper(Some(ReplPromptHelper));
136169

137170
// Simple history file in the user's home directory
138-
let history_path: Option<PathBuf> = std::env::var("HOME").ok().map(|h| PathBuf::from(h).join(".js_repl_history"));
139-
if let Some(ref p) = history_path {
171+
let history_path: Option<PathBuf> = dirs::home_dir().map(|p| p.join(".js_repl_history"));
172+
if let Some(ref p) = history_path
173+
&& p.exists()
174+
{
140175
rl.load_history(p)?;
141176
}
142177

@@ -145,18 +180,12 @@ fn run_persistent_repl() -> Result<(), Box<dyn std::error::Error + Send + Sync +
145180
let mut buffer = String::new();
146181

147182
loop {
148-
// Use ANSI escape codes for color: \x1b[1;32m is bold green, \x1b[1;33m is bold yellow, \x1b[0m is reset
149-
let prompt = if buffer.is_empty() {
150-
"\x1b[1;32mjs> \x1b[0m"
151-
} else {
152-
"\x1b[1;33m... \x1b[0m"
153-
};
154-
183+
let prompt = if buffer.is_empty() { "js> " } else { "... " };
155184
match rl.readline(prompt) {
156185
Ok(line) => {
157186
// support quick exit from the REPL
158187
let trimmed = line.trim();
159-
if trimmed == "exit" || trimmed == ".exit" {
188+
if trimmed == ".exit" {
160189
break;
161190
}
162191

@@ -203,7 +232,6 @@ fn run_persistent_repl() -> Result<(), Box<dyn std::error::Error + Send + Sync +
203232
continue;
204233
}
205234
Err(ReadlineError::Eof) => {
206-
println!("Goodbye");
207235
break;
208236
}
209237
Err(err) => {
@@ -212,6 +240,7 @@ fn run_persistent_repl() -> Result<(), Box<dyn std::error::Error + Send + Sync +
212240
}
213241
}
214242
}
243+
println!("Goodbye");
215244

216245
if let Some(ref p) = history_path {
217246
rl.save_history(p)?;

0 commit comments

Comments
 (0)