Skip to content

Commit ea6268d

Browse files
committed
feat: add tab completion and history hint
1 parent aba2473 commit ea6268d

5 files changed

Lines changed: 127 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ authors = ["RinChanNOW <https://github.com/RinChanNOWWW>"]
33
description = "ruborute is a command-line tool to get asphyxia@sdvx gaming data."
44
edition = "2018"
55
name = "ruborute"
6-
version = "0.1.1"
6+
version = "0.1.2"
77

88
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
99

@@ -16,5 +16,6 @@ prettytable-rs = "0.8.0"
1616
quick-xml = {version = "0.22.0", features = ["encoding", "serialize"]}
1717
rust-fuzzy-search = "0.1.1"
1818
rustyline = "9.0.0"
19+
rustyline-derive = "0.5.0"
1920
serde = {version = "1.0.129", features = ["derive"]}
2021
serde_json = "1.0.66"

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ruborute
22

3-
**Are you 暴龍天 ?**. The ruborute is a interactive command-line tool to get asphyxia@sdvx gaming data.
3+
**Are you 暴龍天 ?**. The ruborute is an interactive command-line tool to get asphyxia@sdvx gaming data.
44

55
asphyxia-core/plugins: https://github.com/asphyxia-core/plugins
66

@@ -57,8 +57,6 @@ Music 1226: <Black night>
5757
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
5858
| #4 | 1139 | Decoy | MXM | 17 | 9929078 | S | HC | 18.077 |
5959
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
60-
| #5 | 933 | 見世物ライフ | MXM | 17 | 9925958 | S | HC | 18.072 |
61-
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
6260
....
6361
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
6462
50 record(s) founded.
@@ -90,7 +88,7 @@ Your Volforce: 17.714
9088
+-------+---+------+-----+-----+----+-----+----+--------------+
9189
```
9290

93-
You can type Ctrl-C or Ctrl-D to exit.
91+
You can type Ctrl-D to exit.
9492

9593
## Features
9694

@@ -99,6 +97,9 @@ You can type Ctrl-C or Ctrl-D to exit.
9997
- [x] Compute VF.
10098
- [x] Get the best 50 records.
10199
- [x] Collect more detail statistics (Such as count of a clear type).
100+
- [x] Press "Tab" button to complete the commands.
101+
- [x] History hints supported.
102+
- [x] Type Ctrl-C to interrupt current input.
102103
- [ ] Range get records in VF order.
103104
- [ ] Get music infomation by music id.
104105
- [ ] Get music informaton by music name.

src/cmdline.rs

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
use std::{collections::HashMap, rc::Rc};
1+
use std::{collections::HashMap, rc::Rc, vec};
22

33
use clap::Clap;
44
use prettytable::{cell, row, Cell, Row, Table};
5-
use rustyline::{error::ReadlineError, Editor};
5+
use rustyline::{
6+
completion::Completer,
7+
error::ReadlineError,
8+
hint::{Hinter, HistoryHinter},
9+
Config, Editor,
10+
};
11+
use rustyline_derive::{Helper, Highlighter, Validator};
612

713
use crate::{command::*, storage, Result};
814

@@ -32,9 +38,77 @@ pub struct Opt {
3238
music_path: String,
3339
}
3440

41+
struct CmdCompleter {
42+
commands: Vec<String>,
43+
}
44+
45+
impl CmdCompleter {
46+
fn new() -> Self {
47+
CmdCompleter {
48+
commands: Vec::new(),
49+
}
50+
}
51+
fn add_command(&mut self, cmd: String) {
52+
self.commands.push(cmd)
53+
}
54+
}
55+
56+
impl Completer for CmdCompleter {
57+
type Candidate = String;
58+
59+
fn complete(
60+
&self,
61+
line: &str,
62+
_pos: usize,
63+
_ctx: &rustyline::Context<'_>,
64+
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
65+
let res = self
66+
.commands
67+
.iter()
68+
.filter(|s| s.starts_with(line))
69+
.map(|s| s.clone() + " ")
70+
.collect::<Vec<String>>();
71+
Ok((0, res))
72+
}
73+
}
74+
75+
#[derive(Helper, Highlighter, Validator)]
76+
struct CmdlineHelper {
77+
cmd_completer: CmdCompleter,
78+
cmd_hinter: HistoryHinter,
79+
}
80+
81+
impl CmdlineHelper {
82+
fn add_command(&mut self, cmd: String) {
83+
self.cmd_completer.add_command(cmd);
84+
}
85+
}
86+
87+
impl Completer for CmdlineHelper {
88+
type Candidate = String;
89+
90+
fn complete(
91+
&self,
92+
line: &str,
93+
pos: usize,
94+
ctx: &rustyline::Context<'_>,
95+
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
96+
self.cmd_completer.complete(line, pos, ctx)
97+
}
98+
}
99+
100+
impl Hinter for CmdlineHelper {
101+
type Hint = String;
102+
103+
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
104+
self.cmd_hinter.hint(line, pos, ctx)
105+
}
106+
}
107+
35108
pub struct Cmdline {
36109
help_table: Table,
37110
cmds: HashMap<String, Box<dyn Cmd>>,
111+
rl: Editor<CmdlineHelper>,
38112
}
39113

40114
impl Cmdline {
@@ -44,11 +118,30 @@ impl Cmdline {
44118
opt.record_path,
45119
opt.music_path,
46120
)?);
121+
122+
let config = Config::builder()
123+
.history_ignore_dups(true)
124+
.history_ignore_space(true)
125+
.completion_type(rustyline::CompletionType::List)
126+
.output_stream(rustyline::OutputStreamType::Stdout)
127+
.build();
128+
129+
let mut helper = CmdlineHelper {
130+
cmd_completer: CmdCompleter::new(),
131+
cmd_hinter: HistoryHinter {},
132+
};
47133
let cmds: HashMap<String, Box<dyn Cmd>> = HashMap::new();
48134
let mut help_table = Table::new();
49135
help_table.add_row(row!["name", "usage", "description"]);
50136
help_table.add_row(row!["help", "help", "show the help information."]);
51-
let mut cmdline = Cmdline { cmds, help_table };
137+
helper.add_command(String::from("help"));
138+
let mut rl = Editor::with_config(config);
139+
rl.set_helper(Some(helper));
140+
let mut cmdline = Cmdline {
141+
cmds,
142+
help_table,
143+
rl,
144+
};
52145

53146
// add commands
54147
cmdline.add_command(Box::new(CmdRecord::new(Rc::clone(&store))));
@@ -65,20 +158,23 @@ impl Cmdline {
65158
Cell::new(cmd.usage()),
66159
Cell::new(cmd.description()),
67160
]));
161+
if let Some(helper) = self.rl.helper_mut() {
162+
helper.add_command(cmd.name().to_string());
163+
}
68164
self.cmds.insert(cmd.name().to_string(), cmd);
69165
}
70166

71167
fn help(&self) {
72168
self.help_table.printstd();
73169
}
74170

75-
pub fn run(&self) -> Result<()> {
171+
pub fn run(&mut self) -> Result<()> {
76172
// run interactive cmdline
77-
let mut rl = Editor::<()>::new();
78173
loop {
79-
let readline = rl.readline(">> ");
174+
let readline = self.rl.readline(">> ");
80175
match readline {
81176
Ok(line) => {
177+
self.rl.add_history_entry(line.as_str());
82178
let cmds: Vec<String> = line
83179
.trim()
84180
.split(" ")
@@ -87,7 +183,11 @@ impl Cmdline {
87183
.collect();
88184
self.interact(cmds);
89185
}
90-
Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => {
186+
Err(ReadlineError::Interrupted) => {
187+
println!("<Keyboard Interrupted>");
188+
continue;
189+
}
190+
Err(ReadlineError::Eof) => {
91191
println!("Bye");
92192
break;
93193
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use ruborute::{Cmdline, Opt};
66
fn main() {
77
let cmdline = Cmdline::new(Opt::parse());
88
match cmdline {
9-
Ok(cl) => {
9+
Ok(mut cl) => {
1010
if let Err(e) = cl.run() {
1111
eprintln!("{}", e);
1212
exit(1)

0 commit comments

Comments
 (0)