ricecoder_completion/
ghost_text.rs1use crate::types::{CompletionItem, GhostText, Position, Range};
6
7pub trait GhostTextGenerator: Send + Sync {
9 fn generate_ghost_text(&self, completion: &CompletionItem, position: Position) -> GhostText;
11
12 fn generate_multiline_ghost_text(
14 &self,
15 completion: &CompletionItem,
16 position: Position,
17 ) -> GhostText;
18}
19
20pub struct BasicGhostTextGenerator;
22
23impl BasicGhostTextGenerator {
24 pub fn new() -> Self {
25 Self
26 }
27}
28
29impl Default for BasicGhostTextGenerator {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl GhostTextGenerator for BasicGhostTextGenerator {
36 fn generate_ghost_text(&self, completion: &CompletionItem, position: Position) -> GhostText {
37 let text = completion.insert_text.clone();
39 let end_position = Position::new(position.line, position.character + text.len() as u32);
40
41 GhostText::new(text, Range::new(position, end_position))
42 }
43
44 fn generate_multiline_ghost_text(
45 &self,
46 completion: &CompletionItem,
47 position: Position,
48 ) -> GhostText {
49 let text = completion.insert_text.clone();
50 let lines: Vec<&str> = text.lines().collect();
51
52 let end_position = if lines.len() > 1 {
53 let last_line = lines[lines.len() - 1];
55 Position::new(
56 position.line + (lines.len() - 1) as u32,
57 last_line.len() as u32,
58 )
59 } else {
60 Position::new(position.line, position.character + text.len() as u32)
62 };
63
64 GhostText::new(text, Range::new(position, end_position))
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
70pub enum GhostTextStyle {
71 #[default]
73 Faded,
74 Italic,
76 Dimmed,
78 Custom,
80}
81
82pub trait GhostTextRenderer: Send + Sync {
84 fn render(&self, ghost_text: &GhostText, style: GhostTextStyle) -> String;
86
87 fn get_styled_text(&self, ghost_text: &GhostText, style: GhostTextStyle) -> String;
89}
90
91pub struct BasicGhostTextRenderer;
93
94impl BasicGhostTextRenderer {
95 pub fn new() -> Self {
96 Self
97 }
98}
99
100impl Default for BasicGhostTextRenderer {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106impl GhostTextRenderer for BasicGhostTextRenderer {
107 fn render(&self, ghost_text: &GhostText, style: GhostTextStyle) -> String {
108 match style {
109 GhostTextStyle::Faded => format!("(faded) {}", ghost_text.text),
110 GhostTextStyle::Italic => format!("(italic) {}", ghost_text.text),
111 GhostTextStyle::Dimmed => format!("(dimmed) {}", ghost_text.text),
112 GhostTextStyle::Custom => ghost_text.text.clone(),
113 }
114 }
115
116 fn get_styled_text(&self, ghost_text: &GhostText, _style: GhostTextStyle) -> String {
117 ghost_text.text.clone()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_ghost_text_generation_single_line() {
127 let generator = BasicGhostTextGenerator::new();
128 let completion = CompletionItem::new(
129 "println".to_string(),
130 crate::types::CompletionItemKind::Function,
131 "println!(\"Hello\")".to_string(),
132 );
133 let position = Position::new(0, 5);
134
135 let ghost_text = generator.generate_ghost_text(&completion, position);
136
137 assert_eq!(ghost_text.text, "println!(\"Hello\")");
138 assert_eq!(ghost_text.range.start, position);
139 assert_eq!(
140 ghost_text.range.end.character,
141 position.character + "println!(\"Hello\")".len() as u32
142 );
143 }
144
145 #[test]
146 fn test_ghost_text_generation_multiline() {
147 let generator = BasicGhostTextGenerator::new();
148 let completion = CompletionItem::new(
149 "fn".to_string(),
150 crate::types::CompletionItemKind::Keyword,
151 "fn main() {\n \n}".to_string(),
152 );
153 let position = Position::new(0, 0);
154
155 let ghost_text = generator.generate_multiline_ghost_text(&completion, position);
156
157 assert_eq!(ghost_text.text, "fn main() {\n \n}");
158 assert_eq!(ghost_text.range.start, position);
159 assert_eq!(ghost_text.range.end.line, 2);
161 }
162
163 #[test]
164 fn test_ghost_text_renderer_faded() {
165 let renderer = BasicGhostTextRenderer::new();
166 let ghost_text = GhostText::new(
167 "test".to_string(),
168 Range::new(Position::new(0, 0), Position::new(0, 4)),
169 );
170
171 let rendered = renderer.render(&ghost_text, GhostTextStyle::Faded);
172 assert!(rendered.contains("test"));
173 }
174
175 #[test]
176 fn test_ghost_text_renderer_italic() {
177 let renderer = BasicGhostTextRenderer::new();
178 let ghost_text = GhostText::new(
179 "test".to_string(),
180 Range::new(Position::new(0, 0), Position::new(0, 4)),
181 );
182
183 let rendered = renderer.render(&ghost_text, GhostTextStyle::Italic);
184 assert!(rendered.contains("test"));
185 }
186}