1extern crate self as escriba_render;
10
11pub mod gpu;
12pub mod langs;
13
14pub use gpu::{GpuRenderer, SharedState};
15
16use escriba_buffer::BufferSet;
17use escriba_core::Position;
18use escriba_ui::Layout;
19use ishou_tokens::VellumPalette;
20use serde::{Deserialize, Serialize};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23pub enum RenderTarget {
24 Text,
25 Gpu,
26}
27
28pub trait Renderer {
29 fn render_frame(&mut self, layout: &Layout, buffers: &BufferSet, cursor: Position) -> String;
30}
31
32pub struct TextRenderer;
33
34impl Renderer for TextRenderer {
35 fn render_frame(&mut self, layout: &Layout, buffers: &BufferSet, cursor: Position) -> String {
36 let Some(win) = layout.active_window() else {
37 return "<no window>\n".to_string();
38 };
39 let Some(buf) = buffers.get(win.buffer_id) else {
40 return "<no buffer>\n".to_string();
41 };
42 let mut out = String::new();
43 let top = win.viewport.top_line;
44 let left = win.viewport.left_column as usize;
45 let vis_cols = win.viewport.visible_columns as usize;
46 let height = win.viewport.visible_lines.max(10);
47 for row in 0..height {
48 let ln = top + row;
49 if ln >= buf.line_count() {
50 break;
51 }
52 let line = buf.line(ln).unwrap_or_default();
53 let line = line.trim_end_matches('\n').trim_end_matches('\r');
54 let visible: Vec<char> = line.chars().skip(left).take(vis_cols).collect();
59 if ln == cursor.line && cursor.column as usize >= left {
60 let rel = cursor.column as usize - left;
61 let before: String = visible.iter().take(rel).collect();
62 let under = visible.get(rel).copied().unwrap_or(' ');
63 let rest: String = visible.iter().skip(rel + 1).collect();
64 out.push_str(&format!(
65 "{:4} │ {before}\x1b[7m{under}\x1b[0m{rest}\n",
66 ln + 1,
67 ));
68 } else {
69 let visible: String = visible.iter().collect();
70 out.push_str(&format!("{:4} │ {visible}\n", ln + 1));
71 }
72 }
73 let accent = VellumPalette::vellum().ice_cyan; out.push_str(&format!(
75 "\x1b[7m escriba · {} · {}:{} \x1b[0m\n",
76 accent.hex(),
77 cursor.line + 1,
78 cursor.column + 1,
79 ));
80 out
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use escriba_buffer::BufferSet;
88 use escriba_core::{BufferId, WindowId};
89 use escriba_ui::{Layout, Rect, Viewport, Window};
90
91 #[test]
92 fn renders_buffer_lines() {
93 let mut bufs = BufferSet::new();
94 let id = bufs.scratch("hello\nworld\nfoo");
95 let _ = BufferId::default();
96 let layout = Layout::single(Window {
97 id: WindowId(1),
98 buffer_id: id,
99 viewport: Viewport {
100 top_line: 0,
101 left_column: 0,
102 visible_lines: 20,
103 visible_columns: 80,
104 },
105 rect: Rect::default(),
106 });
107 let mut r = TextRenderer;
108 let frame = r.render_frame(&layout, &bufs, Position::new(2, 0));
110 assert!(frame.contains("hello"));
111 assert!(frame.contains("world"));
112 }
113
114 #[test]
115 fn cursor_is_highlighted() {
116 let mut bufs = BufferSet::new();
117 let id = bufs.scratch("hello world");
118 let layout = Layout::single(Window {
119 id: WindowId(1),
120 buffer_id: id,
121 viewport: Viewport {
122 top_line: 0,
123 left_column: 0,
124 visible_lines: 10,
125 visible_columns: 80,
126 },
127 rect: Rect::default(),
128 });
129 let mut r = TextRenderer;
130 let frame = r.render_frame(&layout, &bufs, Position::new(0, 3));
131 assert!(frame.contains("\x1b[7m"));
132 }
133}