Skip to content

Commit 6dfa34f

Browse files
committed
simple tab completer
1 parent 599dbb6 commit 6dfa34f

File tree

2 files changed

+109
-7
lines changed

2 files changed

+109
-7
lines changed

user/src/bin/user_shell.rs

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ extern crate alloc;
66
#[macro_use]
77
extern crate user_lib;
88

9-
use alloc::{string::String, vec::Vec};
9+
use alloc::{collections::btree_set::BTreeSet, string::String, vec::Vec};
1010
use user_lib::{
1111
chdir, close, console::getchar, dup, exec, fork, getcwd, open, pipe, waitpid, OpenFlags,
1212
};
1313

1414
const BS: u8 = 0x08;
15+
const HT: u8 = 0x09;
1516
const LF: u8 = 0x0a;
1617
const CR: u8 = 0x0d;
1718
const DL: u8 = 0x7f;
@@ -72,16 +73,107 @@ impl ProcessArguments {
7273
}
7374
}
7475

76+
struct Completer {
77+
// candidates
78+
can: BTreeSet<String>,
79+
}
80+
81+
impl Completer {
82+
pub fn new() -> Self {
83+
Self {
84+
can: BTreeSet::new(),
85+
}
86+
}
87+
88+
pub fn load_root(&mut self) {
89+
self.can.extend(root_bin());
90+
}
91+
92+
pub fn load_ad_hoc(&mut self, iter: impl IntoIterator<Item = String>) {
93+
self.can.extend(iter);
94+
}
95+
96+
pub fn hint_for(&self, s: &str) -> Vec<&String> {
97+
self.can.iter().filter(|v| v.starts_with(s)).collect()
98+
}
99+
}
100+
101+
fn root_bin() -> Vec<String> {
102+
use user_lib::{getdents, Dirent, FileType};
103+
let mut v = Vec::new();
104+
105+
let fd = open("/\0", OpenFlags::RDONLY);
106+
if fd == -1 {
107+
return v;
108+
}
109+
110+
const BUF_SIZE: usize = 16;
111+
let mut entries = alloc::vec![Dirent::default(); BUF_SIZE];
112+
let mut n = BUF_SIZE;
113+
loop {
114+
n = match getdents(fd as usize, &mut entries.as_mut_slice()[..n]) {
115+
-1 | 0 => break,
116+
v => v as usize,
117+
};
118+
for i in 0..n {
119+
let entry = &entries[i];
120+
if entry.ftype == FileType::REG {
121+
v.push(String::from(entry.name()));
122+
}
123+
}
124+
}
125+
v
126+
}
127+
75128
#[no_mangle]
76129
fn main() -> i32 {
77130
println!("Rust user shell");
131+
// completer
132+
let mut comp = Completer::new();
133+
comp.load_root();
134+
comp.load_ad_hoc([String::from("cd"), String::from("pwd")]);
78135
let mut line: String = String::new();
136+
let mut comp_leftover: Option<u8> = None;
79137
loop {
80138
line.clear();
81139
print!("{}", PROMPT);
82140
'repl: loop {
83-
let c = getchar();
141+
let c = match comp_leftover.take() {
142+
Some(v) => v,
143+
_ => getchar(),
144+
};
84145
match c {
146+
HT => {
147+
let par_input = match line.split_ascii_whitespace().last() {
148+
Some(v) if !v.is_empty() => String::from(v),
149+
_ => continue 'repl,
150+
};
151+
let hints = comp.hint_for(&par_input);
152+
if hints.is_empty() {
153+
continue 'repl;
154+
}
155+
156+
let line_end = line.len();
157+
for hint in hints.iter().cycle() {
158+
let rest = &hint[par_input.len()..];
159+
print!("{}", rest);
160+
line.push_str(rest);
161+
162+
let c = getchar();
163+
match c {
164+
HT => {
165+
// tab: clear & try next option
166+
clear_console_ch(rest.len());
167+
line.drain(line_end..);
168+
}
169+
_ => {
170+
// else: exit complete mode
171+
comp_leftover = Some(c);
172+
continue 'repl;
173+
}
174+
}
175+
}
176+
}
85177
LF | CR => {
86178
println!("");
87179
let input = line.trim();
@@ -215,7 +307,7 @@ fn main() -> i32 {
215307
}
216308
// exec
217309
if exec(&args[0], args_addr.as_slice()) == -1 {
218-
println!("[shell] cannot exec: {}", args[0]);
310+
println!("[shell] cannot exec: `{}'", args[0]);
219311
return -4;
220312
}
221313
unreachable!()
@@ -241,9 +333,7 @@ fn main() -> i32 {
241333
}
242334
BS | DL => {
243335
if !line.is_empty() {
244-
print!("{}", BS as char);
245-
print!(" ");
246-
print!("{}", BS as char);
336+
clear_console_ch(1);
247337
line.pop();
248338
}
249339
}
@@ -256,6 +346,14 @@ fn main() -> i32 {
256346
}
257347
}
258348

349+
fn clear_console_ch(n: usize) {
350+
for _ in 0..n {
351+
print!("{}", BS as char); // move cursor back
352+
print!(" "); // print SP to overwrite
353+
print!("{}", BS as char); // then move cursor back again
354+
}
355+
}
356+
259357
const UNKNOWN_BUILTIN: &'static str = "unknown builtin command!";
260358
const WRONG_NUM_ARGS: &'static str = "wrong number of args!";
261359
fn exec_builtin(cmd: &str, args: &[String]) -> Result<(), &'static str> {

user/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,11 @@ bitflags! {
409409

410410
impl Dirent {
411411
pub fn name(&self) -> &str {
412-
core::str::from_utf8(&self.name[..]).unwrap()
412+
let len = match self.name.iter().position(|v| v == &0) {
413+
Some(idx) => idx,
414+
_ => self.name.len(),
415+
};
416+
core::str::from_utf8(&self.name[..len]).unwrap()
413417
}
414418
}
415419

0 commit comments

Comments
 (0)