Skip to main content

ane/commands/chord_engine/
mod.rs

1pub mod errors;
2pub mod parser;
3pub mod patcher;
4pub mod resolver;
5pub mod text;
6pub mod types;
7
8use std::collections::HashMap;
9
10use anyhow::Result;
11
12use crate::commands::lsp_engine::LspEngine;
13use crate::data::buffer::Buffer;
14
15use types::{ChordAction, ChordQuery, ResolvedChord};
16
17pub struct ChordEngine;
18
19impl ChordEngine {
20    pub fn execute(
21        chord_input: &str,
22        buffers: &HashMap<String, Buffer>,
23        lsp: &mut LspEngine,
24    ) -> Result<HashMap<String, ChordAction>> {
25        let query = Self::parse(chord_input)?;
26        let resolved = Self::resolve(&query, buffers, lsp)?;
27        Self::patch(&resolved, buffers)
28    }
29
30    pub fn parse(chord_input: &str) -> Result<ChordQuery> {
31        parser::parse(chord_input)
32    }
33
34    pub fn resolve(
35        query: &ChordQuery,
36        buffers: &HashMap<String, Buffer>,
37        lsp: &mut LspEngine,
38    ) -> Result<ResolvedChord> {
39        resolver::resolve(query, buffers, lsp)
40    }
41
42    pub fn patch(
43        resolved: &ResolvedChord,
44        buffers: &HashMap<String, Buffer>,
45    ) -> Result<HashMap<String, ChordAction>> {
46        patcher::patch(resolved, buffers)
47    }
48
49    pub fn try_auto_submit_short(
50        input: &str,
51        cursor_line: usize,
52        cursor_col: usize,
53    ) -> Option<ChordQuery> {
54        if input.len() != 4 {
55            return None;
56        }
57        if input.chars().next().is_none_or(|c| c.is_uppercase()) {
58            return None;
59        }
60        let mut query = match Self::parse(input) {
61            Ok(q) => q,
62            Err(_) => return None,
63        };
64        query.args.cursor_pos = Some((cursor_line, cursor_col));
65        Some(query)
66    }
67}
68
69pub fn parens_balanced(input: &str) -> bool {
70    let mut depth = 0i32;
71    for c in input.chars() {
72        match c {
73            '(' => depth += 1,
74            ')' => depth -= 1,
75            _ => {}
76        }
77        if depth < 0 {
78            return false;
79        }
80    }
81    depth == 0
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::data::chord_types::{Action, Component, Positional, Scope};
88
89    #[test]
90    fn try_auto_submit_short_valid_chord_sets_cursor_pos() {
91        let query =
92            ChordEngine::try_auto_submit_short("cifn", 3, 7).expect("cifn is a valid 4-char chord");
93        assert_eq!(query.action, Action::Change);
94        assert_eq!(query.positional, Positional::Inside);
95        assert_eq!(query.scope, Scope::Function);
96        assert_eq!(query.component, Component::Name);
97        assert_eq!(query.args.cursor_pos, Some((3, 7)));
98        assert!(query.requires_lsp);
99    }
100
101    #[test]
102    fn try_auto_submit_short_invalid_chord_returns_none() {
103        assert!(ChordEngine::try_auto_submit_short("xxxx", 0, 0).is_none());
104    }
105
106    #[test]
107    fn try_auto_submit_short_too_short_returns_none() {
108        assert!(ChordEngine::try_auto_submit_short("", 0, 0).is_none());
109        assert!(ChordEngine::try_auto_submit_short("c", 0, 0).is_none());
110        assert!(ChordEngine::try_auto_submit_short("ci", 0, 0).is_none());
111        assert!(ChordEngine::try_auto_submit_short("cif", 0, 0).is_none());
112    }
113
114    #[test]
115    fn try_auto_submit_short_uppercase_first_char_returns_none() {
116        assert!(ChordEngine::try_auto_submit_short("Cifn", 0, 0).is_none());
117        assert!(ChordEngine::try_auto_submit_short("CIFN", 0, 0).is_none());
118    }
119
120    #[test]
121    fn parens_balanced_handles_simple_and_nested() {
122        assert!(parens_balanced(""));
123        assert!(parens_balanced("Cif()"));
124        assert!(parens_balanced("Cif(value:\"foo()\")"));
125        assert!(!parens_balanced("Cif(value:\"foo()\""));
126        assert!(!parens_balanced("Cif)("));
127        assert!(!parens_balanced("Cif("));
128    }
129}