Skip to main content

voirs_cli/lsp/
completion.rs

1//! LSP completion provider
2
3use serde_json::Value;
4
5/// Completion item kind
6#[derive(Debug, Clone, Copy)]
7#[allow(dead_code)]
8pub enum CompletionItemKind {
9    Text = 1,
10    Method = 2,
11    Function = 3,
12    Constructor = 4,
13    Field = 5,
14    Variable = 6,
15    Class = 7,
16    Interface = 8,
17    Module = 9,
18    Property = 10,
19    Unit = 11,
20    Value = 12,
21    Enum = 13,
22    Keyword = 14,
23    Snippet = 15,
24    Color = 16,
25    File = 17,
26    Reference = 18,
27}
28
29/// Get SSML tag completions
30pub fn get_ssml_completions() -> Vec<Value> {
31    vec![
32        create_completion_item(
33            "speak",
34            CompletionItemKind::Snippet,
35            "SSML root element",
36            "<speak>$1</speak>",
37        ),
38        create_completion_item(
39            "voice",
40            CompletionItemKind::Snippet,
41            "Voice selection element",
42            "<voice name=\"${1:kokoro-en}\">$2</voice>",
43        ),
44        create_completion_item(
45            "prosody",
46            CompletionItemKind::Snippet,
47            "Prosody control (rate, pitch, volume)",
48            "<prosody rate=\"${1:1.0}\" pitch=\"${2:0}\" volume=\"${3:0}\">$4</prosody>",
49        ),
50        create_completion_item(
51            "break",
52            CompletionItemKind::Snippet,
53            "Insert pause",
54            "<break time=\"${1:500ms}\"/>",
55        ),
56        create_completion_item(
57            "emphasis",
58            CompletionItemKind::Snippet,
59            "Emphasis level control",
60            "<emphasis level=\"${1:moderate}\">$2</emphasis>",
61        ),
62        create_completion_item(
63            "say-as",
64            CompletionItemKind::Snippet,
65            "Control interpretation",
66            "<say-as interpret-as=\"${1:cardinal}\">$2</say-as>",
67        ),
68        create_completion_item(
69            "phoneme",
70            CompletionItemKind::Snippet,
71            "Phonetic pronunciation",
72            "<phoneme alphabet=\"${1:ipa}\" ph=\"${2:pronunciation}\">$3</phoneme>",
73        ),
74        create_completion_item(
75            "sub",
76            CompletionItemKind::Snippet,
77            "Substitute pronunciation",
78            "<sub alias=\"${1:alias}\">$2</sub>",
79        ),
80    ]
81}
82
83/// Get voice name completions
84pub fn get_voice_completions() -> Vec<Value> {
85    vec![
86        create_simple_completion(
87            "kokoro-en",
88            CompletionItemKind::Value,
89            "Kokoro English voice",
90        ),
91        create_simple_completion(
92            "kokoro-ja",
93            CompletionItemKind::Value,
94            "Kokoro Japanese voice",
95        ),
96        create_simple_completion(
97            "kokoro-zh",
98            CompletionItemKind::Value,
99            "Kokoro Chinese voice",
100        ),
101        create_simple_completion("en-us-male", CompletionItemKind::Value, "English US Male"),
102        create_simple_completion(
103            "en-us-female",
104            CompletionItemKind::Value,
105            "English US Female",
106        ),
107    ]
108}
109
110/// Get emphasis level completions
111pub fn get_emphasis_levels() -> Vec<Value> {
112    vec![
113        create_simple_completion("strong", CompletionItemKind::Keyword, "Strong emphasis"),
114        create_simple_completion("moderate", CompletionItemKind::Keyword, "Moderate emphasis"),
115        create_simple_completion("reduced", CompletionItemKind::Keyword, "Reduced emphasis"),
116        create_simple_completion("none", CompletionItemKind::Keyword, "No emphasis"),
117    ]
118}
119
120/// Get say-as interpretation completions
121pub fn get_interpret_as_types() -> Vec<Value> {
122    vec![
123        create_simple_completion("cardinal", CompletionItemKind::Keyword, "Cardinal number"),
124        create_simple_completion("ordinal", CompletionItemKind::Keyword, "Ordinal number"),
125        create_simple_completion("characters", CompletionItemKind::Keyword, "Spell out"),
126        create_simple_completion("date", CompletionItemKind::Keyword, "Date format"),
127        create_simple_completion("time", CompletionItemKind::Keyword, "Time format"),
128        create_simple_completion("telephone", CompletionItemKind::Keyword, "Phone number"),
129    ]
130}
131
132/// Create a completion item with snippet support
133fn create_completion_item(
134    label: &str,
135    kind: CompletionItemKind,
136    detail: &str,
137    insert_text: &str,
138) -> Value {
139    serde_json::json!({
140        "label": label,
141        "kind": kind as u32,
142        "detail": detail,
143        "insertText": insert_text,
144        "insertTextFormat": 2, // Snippet format
145        "documentation": {
146            "kind": "markdown",
147            "value": format!("**{}**\n\n{}", label, detail)
148        }
149    })
150}
151
152/// Create a simple text completion item
153fn create_simple_completion(label: &str, kind: CompletionItemKind, detail: &str) -> Value {
154    serde_json::json!({
155        "label": label,
156        "kind": kind as u32,
157        "detail": detail,
158        "insertText": label,
159        "insertTextFormat": 1 // Plain text
160    })
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_ssml_completions() {
169        let completions = get_ssml_completions();
170        assert!(!completions.is_empty());
171        assert!(completions.iter().any(|c| c["label"] == "speak"));
172        assert!(completions.iter().any(|c| c["label"] == "voice"));
173        assert!(completions.iter().any(|c| c["label"] == "prosody"));
174    }
175
176    #[test]
177    fn test_voice_completions() {
178        let completions = get_voice_completions();
179        assert!(!completions.is_empty());
180        assert!(completions.iter().any(|c| c["label"] == "kokoro-en"));
181    }
182
183    #[test]
184    fn test_emphasis_levels() {
185        let levels = get_emphasis_levels();
186        assert_eq!(levels.len(), 4);
187        assert!(levels.iter().any(|l| l["label"] == "strong"));
188        assert!(levels.iter().any(|l| l["label"] == "moderate"));
189    }
190
191    #[test]
192    fn test_interpret_as_types() {
193        let types = get_interpret_as_types();
194        assert!(!types.is_empty());
195        assert!(types.iter().any(|t| t["label"] == "cardinal"));
196        assert!(types.iter().any(|t| t["label"] == "date"));
197    }
198}