Skip to content

Commit 4afaced

Browse files
author
ComputelessComputer
committed
add / command autocomplete dropdown
1 parent a07c376 commit 4afaced

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

src/main.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ struct App {
269269
input_saved: String,
270270

271271
at_autocomplete_selected: usize,
272+
slash_autocomplete_selected: usize,
272273

273274
update_rx: Option<mpsc::Receiver<String>>,
274275

@@ -502,6 +503,7 @@ fn main() -> io::Result<()> {
502503
input_history_index: None,
503504
input_saved: String::new(),
504505
at_autocomplete_selected: 0,
506+
slash_autocomplete_selected: 0,
505507
update_rx: None,
506508
suggestions: Vec::new(),
507509
suggestions_selected: 0,
@@ -1148,7 +1150,78 @@ fn input_has_at_prefix(input: &str, cursor: usize) -> bool {
11481150
active_at_query(input, cursor).is_some()
11491151
}
11501152

1153+
fn active_slash_query(input: &str, cursor: usize) -> Option<String> {
1154+
let chars: Vec<char> = input.chars().collect();
1155+
let end = cursor.min(chars.len());
1156+
let mut start = end;
1157+
while start > 0 && chars[start - 1] != ' ' {
1158+
start -= 1;
1159+
}
1160+
if start < end && chars[start] == '/' {
1161+
Some(chars[start + 1..end].iter().collect())
1162+
} else {
1163+
None
1164+
}
1165+
}
1166+
1167+
fn slash_completions(input: &str, cursor: usize) -> Vec<(&'static str, &'static str)> {
1168+
let query = match active_slash_query(input, cursor) {
1169+
Some(q) => q,
1170+
None => return Vec::new(),
1171+
};
1172+
let all_commands: &[(&str, &str)] = &[
1173+
("clear", "Clear AI chat history"),
1174+
("buckets", "List all buckets"),
1175+
("bucket add", "Add a new bucket"),
1176+
("bucket rename", "Rename a bucket"),
1177+
("organize", "AI restructures all tasks"),
1178+
("exit", "Quit the app"),
1179+
];
1180+
let query_lower = query.to_lowercase();
1181+
all_commands
1182+
.iter()
1183+
.filter(|(cmd, _)| cmd.starts_with(&query_lower) || query_lower.is_empty())
1184+
.copied()
1185+
.collect()
1186+
}
1187+
11511188
fn handle_input_key(app: &mut App, key: KeyEvent) -> io::Result<bool> {
1189+
// / command autocomplete interception.
1190+
let slash_comps = slash_completions(&app.input, app.input_cursor);
1191+
if !slash_comps.is_empty() {
1192+
match key.code {
1193+
KeyCode::Up => {
1194+
if app.slash_autocomplete_selected == 0 {
1195+
app.slash_autocomplete_selected = slash_comps.len() - 1;
1196+
} else {
1197+
app.slash_autocomplete_selected -= 1;
1198+
}
1199+
return Ok(false);
1200+
}
1201+
KeyCode::Down => {
1202+
app.slash_autocomplete_selected =
1203+
(app.slash_autocomplete_selected + 1) % slash_comps.len();
1204+
return Ok(false);
1205+
}
1206+
KeyCode::Enter | KeyCode::Tab => {
1207+
let sel = app
1208+
.slash_autocomplete_selected
1209+
.min(slash_comps.len().saturating_sub(1));
1210+
let (cmd, _) = slash_comps[sel];
1211+
let replacement = format!("/{}", cmd);
1212+
let (new_input, new_cursor) =
1213+
replace_at_token(&app.input, app.input_cursor, &replacement);
1214+
app.input = new_input;
1215+
app.input_cursor = new_cursor;
1216+
app.slash_autocomplete_selected = 0;
1217+
return Ok(false);
1218+
}
1219+
_ => {
1220+
app.slash_autocomplete_selected = 0;
1221+
}
1222+
}
1223+
}
1224+
11521225
// @ autocomplete interception.
11531226
let completions = at_completions(&app.tasks, &app.input, app.input_cursor);
11541227
if !completions.is_empty() {
@@ -4665,6 +4738,41 @@ fn render_input_bar(stdout: &mut Stdout, app: &App, cols: u16, rows: u16) -> io:
46654738
}
46664739
}
46674740
}
4741+
} else {
4742+
let slash_comps = slash_completions(&app.input, app.input_cursor);
4743+
if !slash_comps.is_empty() {
4744+
const MAX_SHOW: usize = 8;
4745+
let show = slash_comps.len().min(MAX_SHOW);
4746+
let sel = app
4747+
.slash_autocomplete_selected
4748+
.min(slash_comps.len().saturating_sub(1));
4749+
let scroll = if sel >= show { sel - show + 1 } else { 0 };
4750+
for (draw_i, (cmd, desc)) in slash_comps.iter().enumerate().skip(scroll).take(show)
4751+
{
4752+
let row_from_bottom = show - (draw_i - scroll) - 1;
4753+
let y_row = y_sep_top - 1 - row_from_bottom as u16;
4754+
let label = format!(" /{} — {}", cmd, desc);
4755+
let padded = pad_to_width(&clamp_text(&label, content_width), content_width);
4756+
queue!(stdout, MoveTo(x, y_row))?;
4757+
if draw_i == sel {
4758+
queue!(
4759+
stdout,
4760+
SetForegroundColor(Color::Black),
4761+
SetBackgroundColor(Color::White),
4762+
Print(&padded),
4763+
ResetColor
4764+
)?;
4765+
} else {
4766+
queue!(
4767+
stdout,
4768+
SetForegroundColor(Color::White),
4769+
SetBackgroundColor(Color::DarkGrey),
4770+
Print(&padded),
4771+
ResetColor
4772+
)?;
4773+
}
4774+
}
4775+
}
46684776
}
46694777
}
46704778

0 commit comments

Comments
 (0)