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