on 2158366 help sign in
helix/helix-tui/src/terminal.rs
225 lines 6.6 kB history raw
1use crate::{backend::Backend, buffer::Buffer};
2use helix_view::graphics::{CursorKind, Rect};
3use std::io;
5#[derive(Debug, Clone, PartialEq)]
6/// UNSTABLE
7enum ResizeBehavior {
8 Fixed,
9 Auto,
12#[derive(Debug, Clone, PartialEq)]
13/// UNSTABLE
14pub struct Viewport {
15 area: Rect,
16 resize_behavior: ResizeBehavior,
19impl Viewport {
20 /// UNSTABLE
21 pub fn fixed(area: Rect) -> Viewport {
22 Viewport {
23 area,
24 resize_behavior: ResizeBehavior::Fixed,
25 }
26 }
29#[derive(Debug, Clone, PartialEq)]
30/// Options to pass to [`Terminal::with_options`]
31pub struct TerminalOptions {
32 /// Viewport used to draw to the terminal
33 pub viewport: Viewport,
36/// Interface to the terminal backed by Termion
37#[derive(Debug)]
38pub struct Terminal<B>
39where
40 B: Backend,
42 backend: B,
43 /// Holds the results of the current and previous draw calls. The two are compared at the end
44 /// of each draw pass to output the necessary updates to the terminal
45 buffers: [Buffer; 2],
46 /// Index of the current buffer in the previous array
47 current: usize,
48 /// Kind of cursor (hidden or others)
49 cursor_kind: CursorKind,
50 /// Viewport
51 viewport: Viewport,
54impl<B> Drop for Terminal<B>
55where
56 B: Backend,
58 fn drop(&mut self) {
59 // Attempt to restore the cursor state
60 if self.cursor_kind == CursorKind::Hidden {
61 if let Err(err) = self.show_cursor(CursorKind::Block) {
62 eprintln!("Failed to show the cursor: {}", err);
63 }
64 }
65 }
68impl<B> Terminal<B>
69where
70 B: Backend,
72 /// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and
73 /// default colors for the foreground and the background
74 pub fn new(backend: B) -> io::Result<Terminal<B>> {
75 let size = backend.size()?;
76 Terminal::with_options(
77 backend,
78 TerminalOptions {
79 viewport: Viewport {
80 area: size,
81 resize_behavior: ResizeBehavior::Auto,
82 },
83 },
84 )
85 }
87 /// UNSTABLE
88 pub fn with_options(backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
89 Ok(Terminal {
90 backend,
91 buffers: [
92 Buffer::empty(options.viewport.area),
93 Buffer::empty(options.viewport.area),
94 ],
95 current: 0,
96 cursor_kind: CursorKind::Block,
97 viewport: options.viewport,
98 })
99 }
101 // /// Get a Frame object which provides a consistent view into the terminal state for rendering.
102 // pub fn get_frame(&mut self) -> Frame<B> {
103 // Frame {
104 // terminal: self,
105 // cursor_position: None,
106 // }
107 // }
109 pub fn current_buffer_mut(&mut self) -> &mut Buffer {
110 &mut self.buffers[self.current]
111 }
113 pub fn backend(&self) -> &B {
114 &self.backend
115 }
117 pub fn backend_mut(&mut self) -> &mut B {
118 &mut self.backend
119 }
121 /// Obtains a difference between the previous and the current buffer and passes it to the
122 /// current backend for drawing.
123 pub fn flush(&mut self) -> io::Result<()> {
124 let previous_buffer = &self.buffers[1 - self.current];
125 let current_buffer = &self.buffers[self.current];
126 let updates = previous_buffer.diff(current_buffer);
127 self.backend.draw(updates.into_iter())
128 }
130 /// Updates the Terminal so that internal buffers match the requested size. Requested size will
131 /// be saved so the size can remain consistent when rendering.
132 /// This leads to a full clear of the screen.
133 pub fn resize(&mut self, area: Rect) -> io::Result<()> {
134 self.buffers[self.current].resize(area);
135 self.buffers[1 - self.current].resize(area);
136 self.viewport.area = area;
137 self.clear()
138 }
140 /// Queries the backend for size and resizes if it doesn't match the previous size.
141 pub fn autoresize(&mut self) -> io::Result<Rect> {
142 let size = self.size()?;
143 if size != self.viewport.area {
144 self.resize(size)?;
145 };
146 Ok(size)
147 }
149 /// Synchronizes terminal size, calls the rendering closure, flushes the current internal state
150 /// and prepares for the next draw call.
151 pub fn draw(
152 &mut self,
153 cursor_position: Option<(u16, u16)>,
154 cursor_kind: CursorKind,
155 ) -> io::Result<()> {
156 // // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
157 // // and the terminal (if growing), which may OOB.
158 // self.autoresize()?;
160 // let mut frame = self.get_frame();
161 // f(&mut frame);
162 // // We can't change the cursor position right away because we have to flush the frame to
163 // // stdout first. But we also can't keep the frame around, since it holds a &mut to
164 // // Terminal. Thus, we're taking the important data out of the Frame and dropping it.
165 // let cursor_position = frame.cursor_position;
167 // Draw to stdout
168 self.flush()?;
170 if let Some((x, y)) = cursor_position {
171 self.set_cursor(x, y)?;
172 }
174 match cursor_kind {
175 CursorKind::Hidden => self.hide_cursor()?,
176 kind => self.show_cursor(kind)?,
177 }
179 // Swap buffers
180 self.buffers[1 - self.current].reset();
181 self.current = 1 - self.current;
183 // Flush
184 self.backend.flush()?;
185 Ok(())
186 }
188 #[inline]
189 pub fn cursor_kind(&self) -> CursorKind {
190 self.cursor_kind
191 }
193 pub fn hide_cursor(&mut self) -> io::Result<()> {
194 self.backend.hide_cursor()?;
195 self.cursor_kind = CursorKind::Hidden;
196 Ok(())
197 }
199 pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
200 self.backend.show_cursor(kind)?;
201 self.cursor_kind = kind;
202 Ok(())
203 }
205 pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
206 self.backend.get_cursor()
207 }
209 pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
210 self.backend.set_cursor(x, y)
211 }
213 /// Clear the terminal and force a full redraw on the next draw call.
214 pub fn clear(&mut self) -> io::Result<()> {
215 self.backend.clear()?;
216 // Reset the back buffer to make sure the next update will redraw everything.
217 self.buffers[1 - self.current].reset();
218 Ok(())
219 }
221 /// Queries the real size of the backend.
222 pub fn size(&self) -> io::Result<Rect> {
223 self.backend.size()
224 }