File size: 2,772 Bytes
d90101d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use std::sync::Arc;

use forge_select::ForgeWidget;

use crate::completer::input_completer::InputSuggestion;
use crate::completer::search_term::Span;
use crate::model::{ForgeCommand, ForgeCommandManager};

/// A display wrapper for `ForgeCommand` that renders the name and description
/// side-by-side for the interactive picker.
#[derive(Clone)]
struct CommandRow(ForgeCommand);

impl std::fmt::Display for CommandRow {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:<30} {}", self.0.name, self.0.description)
    }
}

#[derive(Clone)]
pub struct CommandCompleter(Arc<ForgeCommandManager>);

impl CommandCompleter {
    pub fn new(command_manager: Arc<ForgeCommandManager>) -> Self {
        Self(command_manager)
    }
}

impl CommandCompleter {
    pub fn complete(&mut self, line: &str, _: usize) -> Vec<InputSuggestion> {
        // Determine which sentinel the user typed (`:` or `/`), defaulting to `/`.
        let sentinel = if line.starts_with(':') { ':' } else { '/' };

        // Build the list of display names using the same sentinel the user typed.
        let commands: Vec<CommandRow> = self
            .0
            .list()
            .into_iter()
            .filter_map(|cmd| {
                let display_name = if cmd.name.starts_with('!') {
                    cmd.name.clone()
                } else {
                    format!("{}{}", sentinel, cmd.name)
                };

                // Only include commands that match what the user has typed so far.
                if display_name.starts_with(line) {
                    Some(CommandRow(ForgeCommand {
                        name: display_name,
                        description: cmd.description,
                        value: cmd.value,
                    }))
                } else {
                    None
                }
            })
            .collect();

        if commands.is_empty() {
            return vec![];
        }

        // Extract the initial query text (everything after the leading sentinel or
        // `!`).
        let initial_query = line
            .strip_prefix('/')
            .or_else(|| line.strip_prefix(':'))
            .or_else(|| line.strip_prefix('!'))
            .unwrap_or(line);

        let mut builder = ForgeWidget::select("Command", commands);
        if !initial_query.is_empty() {
            builder = builder.with_initial_text(initial_query);
        }

        match builder.prompt() {
            Ok(Some(row)) => {
                vec![InputSuggestion {
                    value: row.0.name,
                    span: Span::new(0, line.len()),
                    append_whitespace: true,
                }]
            }
            _ => vec![],
        }
    }
}