File size: 3,099 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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
use std::path::{Path, PathBuf};
use std::sync::Arc;

use forge_select::{ForgeWidget, PreviewLayout, PreviewPlacement, SelectRow};
use forge_walker::Walker;

use crate::completer::CommandCompleter;
use crate::completer::search_term::{SearchTerm, Span};
use crate::model::ForgeCommandManager;

pub fn select_workspace_file(cwd: &Path, query: Option<String>) -> anyhow::Result<Option<String>> {
    let files: Vec<String> = Walker::max_all()
        .cwd(cwd.to_path_buf())
        .get_blocking()
        .unwrap_or_default()
        .into_iter()
        .map(|file| file.path)
        .collect();

    if files.is_empty() {
        return Ok(None);
    }

    let has_bat = std::process::Command::new("bat")
        .arg("--version")
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()
        .is_ok();
    let cat_cmd = if has_bat {
        "bat --color=always --style=numbers,changes --line-range=:500"
    } else {
        "cat"
    };

    let preview_cmd = format!(
        "if [ -d {{}} ]; then ls -la --color=always {{}} 2>/dev/null || ls -la {{}}; else {cat_cmd} {{}}; fi"
    );
    let rows: Vec<SelectRow> = files
        .into_iter()
        .map(|path| SelectRow {
            raw: path.clone(),
            display: path.clone(),
            search: path.clone(),
            fields: vec![path],
        })
        .collect();

    Ok(ForgeWidget::select_rows("File ❯ ", rows)
        .query(Some(query.unwrap_or_default()))
        .preview(Some(preview_cmd))
        .preview_layout(PreviewLayout { placement: PreviewPlacement::Bottom, percent: 75 })
        .prompt()?
        .map(|row| row.raw))
}

pub struct InputCompleter {
    cwd: PathBuf,
    command: CommandCompleter,
}

pub struct InputSuggestion {
    pub value: String,
    pub span: Span,
    pub append_whitespace: bool,
}

impl InputCompleter {
    pub fn new(cwd: PathBuf, command_manager: Arc<ForgeCommandManager>) -> Self {
        Self { cwd, command: CommandCompleter::new(command_manager) }
    }

    pub fn complete(&mut self, line: &str, pos: usize) -> Vec<InputSuggestion> {
        if line.starts_with('/') || line.starts_with(':') {
            // if the line starts with '/' or ':' it's probably a command, so we delegate to
            // the command completer.
            let result = self.command.complete(line, pos);
            if !result.is_empty() {
                return result;
            }
        }

        if let Some(query) = SearchTerm::new(line, pos).process() {
            let initial_text = if !query.term.is_empty() {
                Some(query.term.to_string())
            } else {
                None
            };

            if let Ok(Some(selected)) = select_workspace_file(&self.cwd, initial_text) {
                let value = format!("[{}]", selected);
                return vec![InputSuggestion {
                    value,
                    span: Span::new(query.span.start, query.span.end),
                    append_whitespace: true,
                }];
            }
        }

        vec![]
    }
}