1use crossterm::event::{KeyCode, KeyModifiers};
19use hjkl_engine::{
20 Editor, Input, Key,
21 types::{Attrs, Color, Host, Style},
22};
23use ratatui::style::{Color as RColor, Modifier as RMod, Style as RStyle};
24
25pub use crossterm::event::KeyEvent;
27
28pub fn crossterm_to_input(key: KeyEvent) -> Input {
35 let k = match key.code {
36 KeyCode::Char(c) => Key::Char(c),
37 KeyCode::Backspace => Key::Backspace,
38 KeyCode::Delete => Key::Delete,
39 KeyCode::Enter => Key::Enter,
40 KeyCode::Left => Key::Left,
41 KeyCode::Right => Key::Right,
42 KeyCode::Up => Key::Up,
43 KeyCode::Down => Key::Down,
44 KeyCode::Home => Key::Home,
45 KeyCode::End => Key::End,
46 KeyCode::Tab => Key::Tab,
47 KeyCode::Esc => Key::Esc,
48 _ => Key::Null,
49 };
50 Input {
51 key: k,
52 ctrl: key.modifiers.contains(KeyModifiers::CONTROL),
53 alt: key.modifiers.contains(KeyModifiers::ALT),
54 shift: key.modifiers.contains(KeyModifiers::SHIFT),
55 }
56}
57
58pub fn style_to_ratatui(s: Style) -> RStyle {
62 let mut out = RStyle::default();
63 if let Some(c) = s.fg {
64 out = out.fg(RColor::Rgb(c.0, c.1, c.2));
65 }
66 if let Some(c) = s.bg {
67 out = out.bg(RColor::Rgb(c.0, c.1, c.2));
68 }
69 let mut m = RMod::empty();
70 if s.attrs.contains(Attrs::BOLD) {
71 m |= RMod::BOLD;
72 }
73 if s.attrs.contains(Attrs::ITALIC) {
74 m |= RMod::ITALIC;
75 }
76 if s.attrs.contains(Attrs::UNDERLINE) {
77 m |= RMod::UNDERLINED;
78 }
79 if s.attrs.contains(Attrs::REVERSE) {
80 m |= RMod::REVERSED;
81 }
82 if s.attrs.contains(Attrs::DIM) {
83 m |= RMod::DIM;
84 }
85 if s.attrs.contains(Attrs::STRIKE) {
86 m |= RMod::CROSSED_OUT;
87 }
88 out.add_modifier(m)
89}
90
91pub fn style_from_ratatui(s: RStyle) -> Style {
96 fn c(rc: RColor) -> Color {
97 match rc {
98 RColor::Rgb(r, g, b) => Color(r, g, b),
99 RColor::Black => Color(0, 0, 0),
100 RColor::Red => Color(205, 49, 49),
101 RColor::Green => Color(13, 188, 121),
102 RColor::Yellow => Color(229, 229, 16),
103 RColor::Blue => Color(36, 114, 200),
104 RColor::Magenta => Color(188, 63, 188),
105 RColor::Cyan => Color(17, 168, 205),
106 RColor::Gray => Color(229, 229, 229),
107 RColor::DarkGray => Color(102, 102, 102),
108 RColor::LightRed => Color(241, 76, 76),
109 RColor::LightGreen => Color(35, 209, 139),
110 RColor::LightYellow => Color(245, 245, 67),
111 RColor::LightBlue => Color(59, 142, 234),
112 RColor::LightMagenta => Color(214, 112, 214),
113 RColor::LightCyan => Color(41, 184, 219),
114 RColor::White => Color(255, 255, 255),
115 _ => Color(0, 0, 0),
116 }
117 }
118 let mut attrs = Attrs::empty();
119 if s.add_modifier.contains(RMod::BOLD) {
120 attrs |= Attrs::BOLD;
121 }
122 if s.add_modifier.contains(RMod::ITALIC) {
123 attrs |= Attrs::ITALIC;
124 }
125 if s.add_modifier.contains(RMod::UNDERLINED) {
126 attrs |= Attrs::UNDERLINE;
127 }
128 if s.add_modifier.contains(RMod::REVERSED) {
129 attrs |= Attrs::REVERSE;
130 }
131 if s.add_modifier.contains(RMod::DIM) {
132 attrs |= Attrs::DIM;
133 }
134 if s.add_modifier.contains(RMod::CROSSED_OUT) {
135 attrs |= Attrs::STRIKE;
136 }
137 Style {
138 fg: s.fg.map(c),
139 bg: s.bg.map(c),
140 attrs,
141 }
142}
143
144pub trait EditorRatatuiExt {
150 fn intern_ratatui_style(&mut self, style: RStyle) -> u32;
154
155 fn install_ratatui_syntax_spans(&mut self, spans: Vec<Vec<(usize, usize, RStyle)>>);
160
161 fn patch_ratatui_syntax_spans_range(
165 &mut self,
166 rows: std::ops::Range<usize>,
167 spans: &[Vec<(usize, usize, RStyle)>],
168 );
169
170 fn ratatui_style_table(&self) -> Vec<RStyle>;
174}
175
176impl<H: Host> EditorRatatuiExt for Editor<hjkl_buffer::Buffer, H> {
177 fn intern_ratatui_style(&mut self, style: RStyle) -> u32 {
178 self.intern_style(style_from_ratatui(style))
179 }
180
181 fn install_ratatui_syntax_spans(&mut self, spans: Vec<Vec<(usize, usize, RStyle)>>) {
182 let engine_spans: Vec<Vec<(usize, usize, Style)>> = spans
183 .into_iter()
184 .map(|row_spans| {
185 row_spans
186 .into_iter()
187 .map(|(start, end, style)| (start, end, style_from_ratatui(style)))
188 .collect()
189 })
190 .collect();
191 self.install_syntax_spans(engine_spans);
192 }
193
194 fn patch_ratatui_syntax_spans_range(
195 &mut self,
196 rows: std::ops::Range<usize>,
197 spans: &[Vec<(usize, usize, RStyle)>],
198 ) {
199 let engine_spans: Vec<Vec<(usize, usize, Style)>> = spans
200 .iter()
201 .map(|row_spans| {
202 row_spans
203 .iter()
204 .map(|(start, end, style)| (*start, *end, style_from_ratatui(*style)))
205 .collect()
206 })
207 .collect();
208 self.patch_syntax_spans_range(rows, &engine_spans);
209 }
210
211 fn ratatui_style_table(&self) -> Vec<RStyle> {
212 self.style_table()
213 .iter()
214 .copied()
215 .map(style_to_ratatui)
216 .collect()
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use hjkl_engine::{Editor, types::DefaultHost};
224
225 fn fresh_editor(content: &str) -> Editor {
226 let mut e = Editor::new(
227 hjkl_buffer::Buffer::new(),
228 DefaultHost::new(),
229 hjkl_engine::types::Options::default(),
230 );
231 e.set_content(content);
232 e
233 }
234
235 #[test]
236 fn intern_ratatui_style_dedups_repeated_styles() {
237 use ratatui::style::{Color, Style};
238 let mut e = fresh_editor("");
239 let red = Style::default().fg(Color::Red);
240 let blue = Style::default().fg(Color::Blue);
241 let id_r1 = e.intern_ratatui_style(red);
242 let id_r2 = e.intern_ratatui_style(red);
243 let id_b = e.intern_ratatui_style(blue);
244 assert_eq!(id_r1, id_r2);
245 assert_ne!(id_r1, id_b);
246 assert_eq!(e.style_table().len(), 2);
247 }
248
249 #[test]
250 fn install_ratatui_syntax_spans_translates_styled_spans() {
251 use hjkl_engine::types::Color as EColor;
252 use ratatui::style::{Color, Style};
253 let mut e = fresh_editor("SELECT foo");
254 e.install_ratatui_syntax_spans(vec![vec![(0, 6, Style::default().fg(Color::Red))]]);
255 let by_row = e.buffer_spans();
256 assert_eq!(by_row.len(), 1);
257 assert_eq!(by_row[0].len(), 1);
258 assert_eq!(by_row[0][0].start_byte, 0);
259 assert_eq!(by_row[0][0].end_byte, 6);
260 let id = by_row[0][0].style;
261 assert_eq!(e.style_table()[id as usize].fg, Some(EColor(205, 49, 49)));
264 }
265
266 #[test]
267 fn install_ratatui_syntax_spans_clamps_sentinel_end() {
268 use ratatui::style::{Color, Style};
269 let mut e = fresh_editor("hello");
270 e.install_ratatui_syntax_spans(vec![vec![(
271 0,
272 usize::MAX,
273 Style::default().fg(Color::Blue),
274 )]]);
275 let by_row = e.buffer_spans();
276 assert_eq!(by_row[0][0].end_byte, 5);
277 }
278
279 #[test]
280 fn install_ratatui_syntax_spans_drops_zero_width() {
281 use ratatui::style::{Color, Style};
282 let mut e = fresh_editor("abc");
283 e.install_ratatui_syntax_spans(vec![vec![(2, 2, Style::default().fg(Color::Red))]]);
284 assert!(e.buffer_spans()[0].is_empty());
285 }
286}