ane/commands/chord_engine/
mod.rs1pub 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}