on master help sign in
wip: Move to new rendering structure.
8695415Blaž Hrastnik
Cargo.lock+20−30
@@ -11,9 +11,9 @@ dependencies = [
11
11
12[[package]]
12[[package]]
13name = "anyhow"
13name = "anyhow"
14−version = "1.0.34"
14+version = "1.0.35"
15source = "registry+https://github.com/rust-lang/crates.io-index"
15source = "registry+https://github.com/rust-lang/crates.io-index"
16−checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
16+checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
17
17
18[[package]]
18[[package]]
19name = "arrayref"
19name = "arrayref"
@@ -195,9 +195,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
195
195
196[[package]]
196[[package]]
197name = "cc"
197name = "cc"
198−version = "1.0.65"
198+version = "1.0.66"
199source = "registry+https://github.com/rust-lang/crates.io-index"
199source = "registry+https://github.com/rust-lang/crates.io-index"
200−checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
200+checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
201dependencies = [
201dependencies = [
202 "jobserver",
202 "jobserver",
203]
203]
@@ -242,15 +242,6 @@ dependencies = [
242 "vec_map",
242 "vec_map",
243]
243]
244
244
245−[[package]]
246−name = "cloudabi"
247−version = "0.1.0"
248−source = "registry+https://github.com/rust-lang/crates.io-index"
249−checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
250−dependencies = [
251− "bitflags",
252−]
253−
254[[package]]
245[[package]]
255name = "concurrent-queue"
246name = "concurrent-queue"
256version = "1.2.2"
247version = "1.2.2"
@@ -615,9 +606,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
615
606
616[[package]]
607[[package]]
617name = "libc"
608name = "libc"
618−version = "0.2.80"
609+version = "0.2.81"
619source = "registry+https://github.com/rust-lang/crates.io-index"
610source = "registry+https://github.com/rust-lang/crates.io-index"
620−checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
611+checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
621
612
622[[package]]
613[[package]]
623name = "lock_api"
614name = "lock_api"
@@ -777,12 +768,11 @@ dependencies = [
777
768
778[[package]]
769[[package]]
779name = "parking_lot_core"
770name = "parking_lot_core"
780−version = "0.8.0"
771+version = "0.8.1"
781source = "registry+https://github.com/rust-lang/crates.io-index"
772source = "registry+https://github.com/rust-lang/crates.io-index"
782−checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
773+checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0"
783dependencies = [
774dependencies = [
784− "cfg-if 0.1.10",
775+ "cfg-if 1.0.0",
785− "cloudabi",
786 "instant",
776 "instant",
787 "libc",
777 "libc",
788 "redox_syscall",
778 "redox_syscall",
@@ -947,18 +937,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
947
937
948[[package]]
938[[package]]
949name = "serde"
939name = "serde"
950−version = "1.0.117"
940+version = "1.0.118"
951source = "registry+https://github.com/rust-lang/crates.io-index"
941source = "registry+https://github.com/rust-lang/crates.io-index"
952−checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
942+checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
953dependencies = [
943dependencies = [
954 "serde_derive",
944 "serde_derive",
955]
945]
956
946
957[[package]]
947[[package]]
958name = "serde_derive"
948name = "serde_derive"
959−version = "1.0.117"
949+version = "1.0.118"
960source = "registry+https://github.com/rust-lang/crates.io-index"
950source = "registry+https://github.com/rust-lang/crates.io-index"
961−checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
951+checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
962dependencies = [
952dependencies = [
963 "proc-macro2",
953 "proc-macro2",
964 "quote",
954 "quote",
@@ -967,9 +957,9 @@ dependencies = [
967
957
968[[package]]
958[[package]]
969name = "serde_json"
959name = "serde_json"
970−version = "1.0.59"
960+version = "1.0.60"
971source = "registry+https://github.com/rust-lang/crates.io-index"
961source = "registry+https://github.com/rust-lang/crates.io-index"
972−checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
962+checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
973dependencies = [
963dependencies = [
974 "itoa",
964 "itoa",
975 "ryu",
965 "ryu",
@@ -1024,9 +1014,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1024
1014
1025[[package]]
1015[[package]]
1026name = "smallvec"
1016name = "smallvec"
1027−version = "1.5.0"
1017+version = "1.5.1"
1028source = "registry+https://github.com/rust-lang/crates.io-index"
1018source = "registry+https://github.com/rust-lang/crates.io-index"
1029−checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
1019+checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
1030
1020
1031[[package]]
1021[[package]]
1032name = "smol"
1022name = "smol"
@@ -1060,9 +1050,9 @@ dependencies = [
1060
1050
1061[[package]]
1051[[package]]
1062name = "syn"
1052name = "syn"
1063−version = "1.0.53"
1053+version = "1.0.54"
1064source = "registry+https://github.com/rust-lang/crates.io-index"
1054source = "registry+https://github.com/rust-lang/crates.io-index"
1065−checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
1055+checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
1066dependencies = [
1056dependencies = [
1067 "proc-macro2",
1057 "proc-macro2",
1068 "quote",
1058 "quote",
@@ -1156,7 +1146,7 @@ dependencies = [
1156[[package]]
1146[[package]]
1157name = "tui"
1147name = "tui"
1158version = "0.13.0"
1148version = "0.13.0"
1159−source = "git+https://github.com/fdehau/tui-rs#efdd6bfb193dafcb5e3bdc75e7d2d314065da1d7"
1149+source = "git+https://github.com/fdehau/tui-rs#74243394d90ea1316b6bedac6c9e4f26971c76b6"
1160dependencies = [
1150dependencies = [
1161 "bitflags",
1151 "bitflags",
1162 "cassowary",
1152 "cassowary",
helix-lsp/src/transport.rs−1
@@ -177,7 +177,6 @@ impl Transport {
177 .expect("pending_request with id not found!");
177 .expect("pending_request with id not found!");
178 tx.send(Err(error.into())).await?;
178 tx.send(Err(error.into())).await?;
179 }
179 }
180− msg => unimplemented!("{:?}", msg),
181 }
180 }
182 Ok(())
181 Ok(())
183 }
182 }
helix-syntax/languages.tomlnew file+5
@@ -0,0 +1,5 @@
1+[[language]]
2+name = "rust"
3+scope = "source.rust"
4+injection-regex = "rust"
5+file-types = ["rs"]
helix-term/src/application.rs+48−419
@@ -1,13 +1,9 @@
1−use crate::{
2− commands,
3− keymap::{self, Keymaps},
4−};
5use clap::ArgMatches as Args;
1use clap::ArgMatches as Args;
6−use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
7
2
8use helix_view::{document::Mode, Document, Editor, Theme, View};
3use helix_view::{document::Mode, Document, Editor, Theme, View};
9
4
10use crate::compositor::{Component, Compositor, EventResult};
5use crate::compositor::{Component, Compositor, EventResult};
6+use crate::editor_view::EditorView;
11use crate::prompt::Prompt;
7use crate::prompt::Prompt;
12
8
13use log::{debug, info};
9use log::{debug, info};
@@ -37,426 +33,54 @@ use tui::{
37 style::{Color, Modifier, Style},
33 style::{Color, Modifier, Style},
38};
34};
39
35
40−type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
36+type Terminal = crate::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
41−
42−const BASE_WIDTH: u16 = 30;
43
37
44−pub struct Application<'a> {
38+pub struct Application {
45 compositor: Compositor,
39 compositor: Compositor,
46 editor: Editor,
40 editor: Editor,
47− renderer: Renderer,
41+ terminal: Terminal,
48
42
49− executor: &'a smol::Executor<'a>,
43+ executor: &'static smol::Executor<'static>,
50 language_server: helix_lsp::Client,
44 language_server: helix_lsp::Client,
51}
45}
52
46
53−pub struct Renderer {
47+// TODO: temp
54− size: (u16, u16),
48+#[inline(always)]
55− terminal: Terminal,
49+pub fn text_color() -> Style {
56− surface: Surface,
50+ return Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
57− cache: Surface,
58− text_color: Style,
59}
51}
60
52
61−impl Renderer {
53+// pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
54+// let mut stdout = stdout();
55+// match view.doc.mode() {
56+// Mode::Insert => write!(stdout, "\x1B[6 q"),
57+// mode => write!(stdout, "\x1B[2 q"),
58+// };
59+// let pos = if let Some(prompt) = prompt {
60+// Position::new(self.size.0 as usize, 2 + prompt.cursor)
61+// } else {
62+// let cursor = view.doc.state.selection().cursor();
63+
64+// let mut pos = view
65+// .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
66+// .expect("Cursor is out of bounds.");
67+// pos.col += viewport.x as usize;
68+// pos.row += viewport.y as usize;
69+// pos
70+// };
71+
72+// execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
73+// }
74+
75+impl Application {
62− pub fn new() -> Result<Self, Error> {
76+ pub fn new(mut args: Args, executor: &'static smol::Executor<'static>) -> Result<Self, Error> {
63 let backend = CrosstermBackend::new(stdout());
77 let backend = CrosstermBackend::new(stdout());
64 let mut terminal = Terminal::new(backend)?;
78 let mut terminal = Terminal::new(backend)?;
65− let size = terminal::size().unwrap();
66− let text_color: Style = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
67−
68− let area = Rect::new(0, 0, size.0, size.1);
69−
70− Ok(Self {
71− size,
72− terminal,
73− surface: Surface::empty(area),
74− cache: Surface::empty(area),
75− text_color,
76− })
77− }
78−
79− pub fn resize(&mut self, width: u16, height: u16) {
80− self.size = (width, height);
81− let area = Rect::new(0, 0, width, height);
82− self.surface = Surface::empty(area);
83− self.cache = Surface::empty(area);
84− }
85−
86− pub fn render_view(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
87− self.render_buffer(view, viewport, theme);
88− self.render_statusline(view, theme);
89− }
90−
91− // TODO: ideally not &mut View but highlights require it because of cursor cache
92− pub fn render_buffer(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
93− let area = Rect::new(0, 0, self.size.0, self.size.1);
94−
95− // clear with background color
96− self.surface.set_style(area, theme.get("ui.background"));
97−
98− // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
99− let source_code = view.doc.text().to_string();
100−
101− let last_line = view.last_line();
102−
103− let range = {
104− // calculate viewport byte ranges
105− let start = view.doc.text().line_to_byte(view.first_line);
106− let end = view.doc.text().line_to_byte(last_line)
107− + view.doc.text().line(last_line).len_bytes();
108−
109− start..end
110− };
111−
112− // TODO: range doesn't actually restrict source, just highlight range
113− // TODO: cache highlight results
114− // TODO: only recalculate when state.doc is actually modified
115− let highlights: Vec<_> = match view.doc.syntax.as_mut() {
116− Some(syntax) => {
117− syntax
118− .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
119− .unwrap()
120− .collect() // TODO: we collect here to avoid double borrow, fix later
121− }
122− None => vec![Ok(HighlightEvent::Source {
123− start: range.start,
124− end: range.end,
125− })],
126− };
127− let mut spans = Vec::new();
128− let mut visual_x = 0;
129− let mut line = 0u16;
130− let visible_selections: Vec<Range> = view
131− .doc
132− .state
133− .selection()
134− .ranges()
135− .iter()
136− // TODO: limit selection to one in viewport
137− // .filter(|range| !range.is_empty()) // && range.overlaps(&Range::new(start, end + 1))
138− .copied()
139− .collect();
140−
141− 'outer: for event in highlights {
142− match event.unwrap() {
143− HighlightEvent::HighlightStart(span) => {
144− spans.push(span);
145− }
146− HighlightEvent::HighlightEnd => {
147− spans.pop();
148− }
149− HighlightEvent::Source { start, end } => {
150− // TODO: filter out spans out of viewport for now..
151−
152− let start = view.doc.text().byte_to_char(start);
153− let end = view.doc.text().byte_to_char(end); // <-- index 744, len 743
154−
155− let text = view.doc.text().slice(start..end);
156−
157− use helix_core::graphemes::{grapheme_width, RopeGraphemes};
158−
159− let style = match spans.first() {
160− Some(span) => theme.get(theme.scopes()[span.0].as_str()),
161− None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
162− };
163−
164− // TODO: we could render the text to a surface, then cache that, that
165− // way if only the selection/cursor changes we can copy from cache
166− // and paint the new cursor.
167−
168− let mut char_index = start;
169−
170− // iterate over range char by char
171− for grapheme in RopeGraphemes::new(&text) {
172− // TODO: track current char_index
173−
174− if grapheme == "\n" {
175− visual_x = 0;
176− line += 1;
177−
178− // TODO: with proper iter this shouldn't be necessary
179− if line >= viewport.height {
180− break 'outer;
181− }
182− } else if grapheme == "\t" {
183− visual_x += (TAB_WIDTH as u16);
184− } else {
185− // Cow will prevent allocations if span contained in a single slice
186− // which should really be the majority case
187− let grapheme = Cow::from(grapheme);
188− let width = grapheme_width(&grapheme) as u16;
189−
190− // TODO: this should really happen as an after pass
191− let style = if visible_selections
192− .iter()
193− .any(|range| range.contains(char_index))
194− {
195− // cedar
196− style.clone().bg(Color::Rgb(128, 47, 0))
197− } else {
198− style
199− };
200−
201− let style = if visible_selections
202− .iter()
203− .any(|range| range.head == char_index)
204− {
205− style.clone().bg(Color::Rgb(255, 255, 255))
206− } else {
207− style
208− };
209−
210− // ugh, improve with a traverse method
211− // or interleave highlight spans with selection and diagnostic spans
212− let style = if view.doc.diagnostics.iter().any(|diagnostic| {
213− diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
214− }) {
215− style.clone().add_modifier(Modifier::UNDERLINED)
216− } else {
217− style
218− };
219−
220− // TODO: paint cursor heads except primary
221−
222− self.surface.set_string(
223− viewport.x + visual_x,
224− viewport.y + line,
225− grapheme,
226− style,
227− );
228−
229− visual_x += width;
230− }
231−
232− char_index += 1;
233− }
234− }
235− }
236− }
237−
238− let style: Style = theme.get("ui.linenr");
239− let warning: Style = theme.get("warning");
240− let last_line = view.last_line();
241− for (i, line) in (view.first_line..last_line).enumerate() {
242− if view.doc.diagnostics.iter().any(|d| d.line == line) {
243− self.surface.set_stringn(0, i as u16, "●", 1, warning);
244− }
245−
246− self.surface
247− .set_stringn(1, i as u16, format!("{:>5}", line + 1), 5, style);
248− }
249− }
250−
251− pub fn render_statusline(&mut self, view: &View, theme: &Theme) {
252− let mode = match view.doc.mode() {
253− Mode::Insert => "INS",
254− Mode::Normal => "NOR",
255− Mode::Goto => "GOTO",
256− };
257− // statusline
258− self.surface.set_style(
259− Rect::new(0, self.size.1 - 2, self.size.0, 1),
260− theme.get("ui.statusline"),
261− );
262− self.surface
263− .set_string(1, self.size.1 - 2, mode, self.text_color);
264−
265− if let Some(path) = view.doc.path() {
266− self.surface
267− .set_string(6, self.size.1 - 2, path.to_string_lossy(), self.text_color);
268− }
269−
270− self.surface.set_string(
271− self.size.0 - 10,
272− self.size.1 - 2,
273− format!("{}", view.doc.diagnostics.len()),
274− self.text_color,
275− );
276− }
277−
278− pub fn render_prompt(&mut self, prompt: &Prompt, theme: &Theme) {
279− // completion
280− if !prompt.completion.is_empty() {
281− // TODO: find out better way of clearing individual lines of the screen
282− let mut row = 0;
283− let mut col = 0;
284− let max_col = self.size.0 / BASE_WIDTH;
285− let col_height = ((prompt.completion.len() as u16 + max_col - 1) / max_col);
286−
287− for i in (3..col_height + 3) {
288− self.surface.set_string(
289− 0,
290− self.size.1 - i as u16,
291− " ".repeat(self.size.0 as usize),
292− self.text_color,
293− );
294− }
295− self.surface.set_style(
296− Rect::new(0, self.size.1 - col_height - 2, self.size.0, col_height),
297− theme.get("ui.statusline"),
298− );
299− for (i, command) in prompt.completion.iter().enumerate() {
300− let color = if prompt.completion_selection_index.is_some()
301− && i == prompt.completion_selection_index.unwrap()
302− {
303− Style::default().bg(Color::Rgb(104, 060, 232))
304− } else {
305− self.text_color
306− };
307− self.surface.set_stringn(
308− 1 + col * BASE_WIDTH,
309− self.size.1 - col_height - 2 + row,
310− &command,
311− BASE_WIDTH as usize - 1,
312− color,
313− );
314− row += 1;
315− if row > col_height - 1 {
316− row = 0;
317− col += 1;
318− }
319− if col > max_col {
320− break;
321− }
322− }
323− }
324− // render buffer text
325− self.surface
326− .set_string(1, self.size.1 - 1, &prompt.prompt, self.text_color);
327− self.surface
328− .set_string(2, self.size.1 - 1, &prompt.line, self.text_color);
329− }
330−
331− pub fn draw_and_swap(&mut self) {
332− use tui::backend::Backend;
333− // TODO: theres probably a better place for this
334− self.terminal
335− .backend_mut()
336− .draw(self.cache.diff(&self.surface).into_iter());
337− // swap the buffer
338− std::mem::swap(&mut self.surface, &mut self.cache);
339− self.surface.reset(); // reset is faster than allocating new empty surface
340− }
341−
342− pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
343− let mut stdout = stdout();
344− match view.doc.mode() {
345− Mode::Insert => write!(stdout, "\x1B[6 q"),
346− mode => write!(stdout, "\x1B[2 q"),
347− };
348− let pos = if let Some(prompt) = prompt {
349− Position::new(self.size.0 as usize, 2 + prompt.cursor)
350− } else {
351− let cursor = view.doc.state.selection().cursor();
352−
353− let mut pos = view
354− .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
355− .expect("Cursor is out of bounds.");
356− pos.col += viewport.x as usize;
357− pos.row += viewport.y as usize;
358− pos
359− };
360−
361− execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
362− }
363−}
364−
365−struct EditorView {
366− keymap: Keymaps,
367−}
368−
369−impl EditorView {
370− fn new() -> Self {
371− Self {
372− keymap: keymap::default(),
373− }
374− }
375−}
376−
377−use crate::compositor::Context;
378−
379−impl Component for EditorView {
380− fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
381− match event {
382− Event::Resize(width, height) => {
383− // TODO: simplistic ensure cursor in view for now
384− // TODO: loop over views
385− if let Some(view) = cx.editor.view_mut() {
386− view.size = (width, height);
387− view.ensure_cursor_in_view()
388− };
389− EventResult::Consumed(None)
390− }
391− Event::Key(event) => {
392− if let Some(view) = cx.editor.view_mut() {
393− let keys = vec![event];
394− // TODO: sequences (`gg`)
395− let mode = view.doc.mode();
396− // TODO: handle count other than 1
397− let mut cx = commands::Context {
398− view,
399− executor: cx.executor,
400− count: 1,
401− callback: None,
402− };
403−
404− match mode {
405− Mode::Insert => {
406− if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
407− command(&mut cx);
408− } else if let KeyEvent {
409− code: KeyCode::Char(c),
410− ..
411− } = event
412− {
413− commands::insert::insert_char(&mut cx, c);
414− }
415− }
416− mode => {
417− if let Some(command) = self.keymap[&mode].get(&keys) {
418− command(&mut cx);
419−
420− // TODO: simplistic ensure cursor in view for now
421− }
422− }
423− }
424− // appease borrowck
425− let callback = cx.callback.take();
426−
427− view.ensure_cursor_in_view();
428−
429− EventResult::Consumed(callback)
430− } else {
431− EventResult::Ignored
432− }
433− }
434− Event::Mouse(_) => EventResult::Ignored,
435− }
436− }
437− fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) {
438− const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
439− let viewport = Rect::new(OFFSET, 0, renderer.size.0, renderer.size.1 - 2); // - 2 for statusline and prompt
440−
441− // SAFETY: we cheat around the view_mut() borrow because it doesn't allow us to also borrow
442− // theme. Theme is immutable mutating view won't disrupt theme_ref.
443− let theme_ref = unsafe { &*(&cx.editor.theme as *const Theme) };
444− if let Some(view) = cx.editor.view_mut() {
445− renderer.render_view(view, viewport, theme_ref);
446− }
447−
448− // TODO: drop unwrap
449− renderer.render_cursor(cx.editor.view().unwrap(), None, viewport);
450− }
451−}
452−
453−impl<'a> Application<'a> {
454− pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
455− let renderer = Renderer::new()?;
456 let mut editor = Editor::new();
79 let mut editor = Editor::new();
80+ let size = terminal.size()?;
457
81
458 if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
82 if let Some(file) = args.values_of_t::<PathBuf>("files").unwrap().pop() {
459− editor.open(file, renderer.size)?;
83+ editor.open(file, (size.width, size.height))?;
460 }
84 }
461
85
462 let mut compositor = Compositor::new();
86 let mut compositor = Compositor::new();
@@ -466,7 +90,7 @@ impl<'a> Application<'a> {
466
90
467 let mut app = Self {
91 let mut app = Self {
468 editor,
92 editor,
469− renderer,
93+ terminal,
470 // TODO; move to state
94 // TODO; move to state
471 compositor,
95 compositor,
472
96
@@ -478,12 +102,17 @@ impl<'a> Application<'a> {
478 }
102 }
479
103
480 fn render(&mut self) {
104 fn render(&mut self) {
105+ let executor = &self.executor;
106+ let editor = &mut self.editor;
107+ let compositor = &self.compositor;
108+
109+ // TODO: should be unnecessary
110+ // self.terminal.autoresize();
481− let mut cx = crate::compositor::Context {
111+ let mut cx = crate::compositor::Context { editor, executor };
482− editor: &mut self.editor,
112+ let area = self.terminal.size().unwrap();
483− executor: &self.executor,
484− };
485− self.compositor.render(&mut self.renderer, &mut cx); // viewport,
113+ compositor.render(area, self.terminal.current_buffer_mut(), &mut cx);
486− self.renderer.draw_and_swap();
114+
115+ self.terminal.draw();
487 }
116 }
488
117
489 pub async fn event_loop(&mut self) {
118 pub async fn event_loop(&mut self) {
@@ -524,7 +153,7 @@ impl<'a> Application<'a> {
524 // Handle key events
153 // Handle key events
525 let should_redraw = match event {
154 let should_redraw = match event {
526 Some(Ok(Event::Resize(width, height))) => {
155 Some(Ok(Event::Resize(width, height))) => {
527− self.renderer.resize(width, height);
156+ self.terminal.resize(Rect::new(0, 0, width, height));
528
157
529 self.compositor
158 self.compositor
530 .handle_event(Event::Resize(width, height), &mut cx)
159 .handle_event(Event::Resize(width, height), &mut cx)
helix-term/src/compositor.rs+7−7
@@ -13,10 +13,10 @@
13// Q: how does this work with popups?
13// Q: how does this work with popups?
14// cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), <component>)
14// cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), <component>)
15
15
16−use crate::application::Renderer;
17use crossterm::event::Event;
16use crossterm::event::Event;
18use smol::Executor;
17use smol::Executor;
19use tui::buffer::Buffer as Surface;
18use tui::buffer::Buffer as Surface;
19+use tui::layout::Rect;
20
20
21pub type Callback = Box<dyn Fn(&mut Compositor)>;
21pub type Callback = Box<dyn Fn(&mut Compositor)>;
22
22
@@ -36,9 +36,9 @@ pub enum EventResult {
36
36
37use helix_view::{Editor, View};
37use helix_view::{Editor, View};
38// shared with commands.rs
38// shared with commands.rs
39−pub struct Context<'a, 'b> {
39+pub struct Context<'a> {
40 pub editor: &'a mut Editor,
40 pub editor: &'a mut Editor,
41− pub executor: &'a smol::Executor<'b>,
41+ pub executor: &'static smol::Executor<'static>,
42}
42}
43
43
44pub trait Component {
44pub trait Component {
@@ -51,7 +51,7 @@ pub trait Component {
51 true
51 true
52 }
52 }
53
53
54− fn render(&mut self, renderer: &mut Renderer, ctx: &mut Context);
54+ fn render(&self, area: Rect, frame: &mut Surface, ctx: &mut Context);
55}
55}
56
56
57// struct Editor { };
57// struct Editor { };
@@ -133,9 +133,9 @@ impl Compositor {
133 false
133 false
134 }
134 }
135
135
136− pub fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) {
136+ pub fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
137− for layer in &mut self.layers {
137+ for layer in &self.layers {
138− layer.render(renderer, cx)
138+ layer.render(area, surface, cx)
139 }
139 }
140 }
140 }
141}
141}
helix-term/src/editor_view.rsnew file+311
@@ -0,0 +1,311 @@
1+use crate::application::text_color;
2+use crate::commands;
3+use crate::compositor::{Component, Compositor, EventResult};
4+use crate::keymap::{self, Keymaps};
5+use crossterm::{
6+ cursor,
7+ event::{read, Event, EventStream, KeyCode, KeyEvent},
8+};
9+use helix_view::{document::Mode, Document, Editor, Theme, View};
10+use std::borrow::Cow;
11+use tui::{
12+ backend::CrosstermBackend,
13+ buffer::Buffer as Surface,
14+ layout::Rect,
15+ style::{Color, Modifier, Style},
16+};
17+
18+use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
19+
20+pub struct EditorView {
21+ keymap: Keymaps,
22+}
23+
24+impl EditorView {
25+ pub fn new() -> Self {
26+ Self {
27+ keymap: keymap::default(),
28+ }
29+ }
30+ pub fn render_view(
31+ &self,
32+ view: &mut View,
33+ viewport: Rect,
34+ surface: &mut Surface,
35+ theme: &Theme,
36+ ) {
37+ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
38+ let area = Rect::new(OFFSET, 0, viewport.width - OFFSET, viewport.height - 2); // - 2 for statusline and prompt
39+ self.render_buffer(view, area, surface, theme);
40+ let area = Rect::new(0, viewport.height - 2, viewport.width, 1);
41+ self.render_statusline(view, viewport, surface, theme);
42+ }
43+
44+ // TODO: ideally not &mut View but highlights require it because of cursor cache
45+ pub fn render_buffer(
46+ &self,
47+ view: &mut View,
48+ viewport: Rect,
49+ surface: &mut Surface,
50+ theme: &Theme,
51+ ) {
52+ // clear with background color
53+ surface.set_style(viewport, theme.get("ui.background"));
54+
55+ // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
56+ let source_code = view.doc.text().to_string();
57+
58+ let last_line = view.last_line();
59+
60+ let range = {
61+ // calculate viewport byte ranges
62+ let start = view.doc.text().line_to_byte(view.first_line);
63+ let end = view.doc.text().line_to_byte(last_line)
64+ + view.doc.text().line(last_line).len_bytes();
65+
66+ start..end
67+ };
68+
69+ // TODO: range doesn't actually restrict source, just highlight range
70+ // TODO: cache highlight results
71+ // TODO: only recalculate when state.doc is actually modified
72+ let highlights: Vec<_> = match view.doc.syntax.as_mut() {
73+ Some(syntax) => {
74+ syntax
75+ .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
76+ .unwrap()
77+ .collect() // TODO: we collect here to avoid double borrow, fix later
78+ }
79+ None => vec![Ok(HighlightEvent::Source {
80+ start: range.start,
81+ end: range.end,
82+ })],
83+ };
84+ let mut spans = Vec::new();
85+ let mut visual_x = 0;
86+ let mut line = 0u16;
87+ let visible_selections: Vec<Range> = view
88+ .doc
89+ .state
90+ .selection()
91+ .ranges()
92+ .iter()
93+ // TODO: limit selection to one in viewport
94+ // .filter(|range| !range.is_empty()) // && range.overlaps(&Range::new(start, end + 1))
95+ .copied()
96+ .collect();
97+
98+ 'outer: for event in highlights {
99+ match event.unwrap() {
100+ HighlightEvent::HighlightStart(span) => {
101+ spans.push(span);
102+ }
103+ HighlightEvent::HighlightEnd => {
104+ spans.pop();
105+ }
106+ HighlightEvent::Source { start, end } => {
107+ // TODO: filter out spans out of viewport for now..
108+
109+ let start = view.doc.text().byte_to_char(start);
110+ let end = view.doc.text().byte_to_char(end); // <-- index 744, len 743
111+
112+ let text = view.doc.text().slice(start..end);
113+
114+ use helix_core::graphemes::{grapheme_width, RopeGraphemes};
115+
116+ let style = match spans.first() {
117+ Some(span) => theme.get(theme.scopes()[span.0].as_str()),
118+ None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
119+ };
120+
121+ // TODO: we could render the text to a surface, then cache that, that
122+ // way if only the selection/cursor changes we can copy from cache
123+ // and paint the new cursor.
124+
125+ let mut char_index = start;
126+
127+ // iterate over range char by char
128+ for grapheme in RopeGraphemes::new(&text) {
129+ // TODO: track current char_index
130+
131+ if grapheme == "\n" {
132+ visual_x = 0;
133+ line += 1;
134+
135+ // TODO: with proper iter this shouldn't be necessary
136+ if line >= viewport.height {
137+ break 'outer;
138+ }
139+ } else if grapheme == "\t" {
140+ visual_x += (TAB_WIDTH as u16);
141+ } else {
142+ // Cow will prevent allocations if span contained in a single slice
143+ // which should really be the majority case
144+ let grapheme = Cow::from(grapheme);
145+ let width = grapheme_width(&grapheme) as u16;
146+
147+ // TODO: this should really happen as an after pass
148+ let style = if visible_selections
149+ .iter()
150+ .any(|range| range.contains(char_index))
151+ {
152+ // cedar
153+ style.clone().bg(Color::Rgb(128, 47, 0))
154+ } else {
155+ style
156+ };
157+
158+ let style = if visible_selections
159+ .iter()
160+ .any(|range| range.head == char_index)
161+ {
162+ style.clone().bg(Color::Rgb(255, 255, 255))
163+ } else {
164+ style
165+ };
166+
167+ // ugh, improve with a traverse method
168+ // or interleave highlight spans with selection and diagnostic spans
169+ let style = if view.doc.diagnostics.iter().any(|diagnostic| {
170+ diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
171+ }) {
172+ style.clone().add_modifier(Modifier::UNDERLINED)
173+ } else {
174+ style
175+ };
176+
177+ // TODO: paint cursor heads except primary
178+
179+ surface.set_string(
180+ viewport.x + visual_x,
181+ viewport.y + line,
182+ grapheme,
183+ style,
184+ );
185+
186+ visual_x += width;
187+ }
188+
189+ char_index += 1;
190+ }
191+ }
192+ }
193+ }
194+
195+ let style: Style = theme.get("ui.linenr");
196+ let warning: Style = theme.get("warning");
197+ let last_line = view.last_line();
198+ for (i, line) in (view.first_line..last_line).enumerate() {
199+ if view.doc.diagnostics.iter().any(|d| d.line == line) {
200+ surface.set_stringn(0, i as u16, "●", 1, warning);
201+ }
202+
203+ surface.set_stringn(1, i as u16, format!("{:>5}", line + 1), 5, style);
204+ }
205+ }
206+
207+ pub fn render_statusline(
208+ &self,
209+ view: &View,
210+ viewport: Rect,
211+ surface: &mut Surface,
212+ theme: &Theme,
213+ ) {
214+ let mode = match view.doc.mode() {
215+ Mode::Insert => "INS",
216+ Mode::Normal => "NOR",
217+ Mode::Goto => "GOTO",
218+ };
219+ // statusline
220+ surface.set_style(
221+ Rect::new(0, viewport.y, viewport.height, 1),
222+ theme.get("ui.statusline"),
223+ );
224+ surface.set_string(1, viewport.y, mode, text_color());
225+
226+ if let Some(path) = view.doc.path() {
227+ surface.set_string(6, viewport.y, path.to_string_lossy(), text_color());
228+ }
229+
230+ surface.set_string(
231+ viewport.width - 10,
232+ viewport.y,
233+ format!("{}", view.doc.diagnostics.len()),
234+ text_color(),
235+ );
236+ }
237+}
238+
239+use crate::compositor::Context;
240+
241+impl Component for EditorView {
242+ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
243+ match event {
244+ Event::Resize(width, height) => {
245+ // TODO: simplistic ensure cursor in view for now
246+ // TODO: loop over views
247+ if let Some(view) = cx.editor.view_mut() {
248+ view.size = (width, height);
249+ view.ensure_cursor_in_view()
250+ };
251+ EventResult::Consumed(None)
252+ }
253+ Event::Key(event) => {
254+ if let Some(view) = cx.editor.view_mut() {
255+ let keys = vec![event];
256+ // TODO: sequences (`gg`)
257+ let mode = view.doc.mode();
258+ // TODO: handle count other than 1
259+ let mut cx = commands::Context {
260+ view,
261+ executor: cx.executor,
262+ count: 1,
263+ callback: None,
264+ };
265+
266+ match mode {
267+ Mode::Insert => {
268+ if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
269+ command(&mut cx);
270+ } else if let KeyEvent {
271+ code: KeyCode::Char(c),
272+ ..
273+ } = event
274+ {
275+ commands::insert::insert_char(&mut cx, c);
276+ }
277+ }
278+ mode => {
279+ if let Some(command) = self.keymap[&mode].get(&keys) {
280+ command(&mut cx);
281+
282+ // TODO: simplistic ensure cursor in view for now
283+ }
284+ }
285+ }
286+ // appease borrowck
287+ let callback = cx.callback.take();
288+
289+ view.ensure_cursor_in_view();
290+
291+ EventResult::Consumed(callback)
292+ } else {
293+ EventResult::Ignored
294+ }
295+ }
296+ Event::Mouse(_) => EventResult::Ignored,
297+ }
298+ }
299+
300+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
301+ // SAFETY: we cheat around the view_mut() borrow because it doesn't allow us to also borrow
302+ // theme. Theme is immutable mutating view won't disrupt theme_ref.
303+ let theme_ref = unsafe { &*(&cx.editor.theme as *const Theme) };
304+ if let Some(view) = cx.editor.view_mut() {
305+ self.render_view(view, area, surface, theme_ref);
306+ }
307+
308+ // TODO: drop unwrap
309+ // TODO: !!! self.render_cursor(cx.editor.view().unwrap(), None, viewport);
310+ }
311+}
helix-term/src/main.rs+2
@@ -3,8 +3,10 @@
3mod application;
3mod application;
4mod commands;
4mod commands;
5mod compositor;
5mod compositor;
6+mod editor_view;
6mod keymap;
7mod keymap;
7mod prompt;
8mod prompt;
9+mod terminal;
8
10
9use application::Application;
11use application::Application;
10
12
helix-term/src/prompt.rs+66−6
@@ -1,9 +1,7 @@
1−use crate::{
2− application::Renderer,
3− compositor::{Component, Context, EventResult},
1+use crate::compositor::{Component, Context, EventResult};
4−};
5use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
2use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
6use helix_view::Editor;
3use helix_view::Editor;
4+use helix_view::Theme;
7use std::string::String;
5use std::string::String;
8
6
9pub struct Prompt {
7pub struct Prompt {
@@ -85,6 +83,68 @@ impl Prompt {
85 }
83 }
86}
84}
87
85
86+use tui::{
87+ buffer::Buffer as Surface,
88+ layout::Rect,
89+ style::{Color, Modifier, Style},
90+};
91+
92+const BASE_WIDTH: u16 = 30;
93+use crate::application::text_color;
94+
95+impl Prompt {
96+ pub fn render_prompt(&self, area: Rect, surface: &mut Surface, theme: &Theme) {
97+ // completion
98+ if !self.completion.is_empty() {
99+ // TODO: find out better way of clearing individual lines of the screen
100+ let mut row = 0;
101+ let mut col = 0;
102+ let max_col = area.width / BASE_WIDTH;
103+ let col_height = ((self.completion.len() as u16 + max_col - 1) / max_col);
104+
105+ for i in (3..col_height + 3) {
106+ surface.set_string(
107+ 0,
108+ area.height - i as u16,
109+ " ".repeat(area.width as usize),
110+ text_color(),
111+ );
112+ }
113+ surface.set_style(
114+ Rect::new(0, area.height - col_height - 2, area.width, col_height),
115+ theme.get("ui.statusline"),
116+ );
117+ for (i, command) in self.completion.iter().enumerate() {
118+ let color = if self.completion_selection_index.is_some()
119+ && i == self.completion_selection_index.unwrap()
120+ {
121+ Style::default().bg(Color::Rgb(104, 060, 232))
122+ } else {
123+ text_color()
124+ };
125+ surface.set_stringn(
126+ 1 + col * BASE_WIDTH,
127+ area.height - col_height - 2 + row,
128+ &command,
129+ BASE_WIDTH as usize - 1,
130+ color,
131+ );
132+ row += 1;
133+ if row > col_height - 1 {
134+ row = 0;
135+ col += 1;
136+ }
137+ if col > max_col {
138+ break;
139+ }
140+ }
141+ }
142+ // render buffer text
143+ surface.set_string(1, area.height - 1, &self.prompt, text_color());
144+ surface.set_string(2, area.height - 1, &self.line, text_color());
145+ }
146+}
147+
88impl Component for Prompt {
148impl Component for Prompt {
89 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
149 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
90 let event = match event {
150 let event = match event {
@@ -137,7 +197,7 @@ impl Component for Prompt {
137 EventResult::Consumed(None)
197 EventResult::Consumed(None)
138 }
198 }
139
199
140− fn render(&mut self, renderer: &mut Renderer, cx: &mut Context) {
200+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
141− renderer.render_prompt(self, &cx.editor.theme)
201+ self.render_prompt(area, surface, &cx.editor.theme)
142 }
202 }
143}
203}
helix-term/src/terminal.rsnew file+221
@@ -0,0 +1,221 @@
1+use std::io;
2+use tui::{
3+ backend::Backend,
4+ buffer::Buffer,
5+ layout::Rect,
6+ widgets::{StatefulWidget, Widget},
7+};
8+
9+#[derive(Debug, Clone, PartialEq)]
10+/// UNSTABLE
11+enum ResizeBehavior {
12+ Fixed,
13+ Auto,
14+}
15+
16+#[derive(Debug, Clone, PartialEq)]
17+/// UNSTABLE
18+pub struct Viewport {
19+ area: Rect,
20+ resize_behavior: ResizeBehavior,
21+}
22+
23+impl Viewport {
24+ /// UNSTABLE
25+ pub fn fixed(area: Rect) -> Viewport {
26+ Viewport {
27+ area,
28+ resize_behavior: ResizeBehavior::Fixed,
29+ }
30+ }
31+}
32+
33+#[derive(Debug, Clone, PartialEq)]
34+/// Options to pass to [`Terminal::with_options`]
35+pub struct TerminalOptions {
36+ /// Viewport used to draw to the terminal
37+ pub viewport: Viewport,
38+}
39+
40+/// Interface to the terminal backed by Termion
41+#[derive(Debug)]
42+pub struct Terminal<B>
43+where
44+ B: Backend,
45+{
46+ backend: B,
47+ /// Holds the results of the current and previous draw calls. The two are compared at the end
48+ /// of each draw pass to output the necessary updates to the terminal
49+ buffers: [Buffer; 2],
50+ /// Index of the current buffer in the previous array
51+ current: usize,
52+ /// Whether the cursor is currently hidden
53+ hidden_cursor: bool,
54+ /// Viewport
55+ viewport: Viewport,
56+}
57+
58+impl<B> Drop for Terminal<B>
59+where
60+ B: Backend,
61+{
62+ fn drop(&mut self) {
63+ // Attempt to restore the cursor state
64+ if self.hidden_cursor {
65+ if let Err(err) = self.show_cursor() {
66+ eprintln!("Failed to show the cursor: {}", err);
67+ }
68+ }
69+ }
70+}
71+
72+impl<B> Terminal<B>
73+where
74+ B: Backend,
75+{
76+ /// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and
77+ /// default colors for the foreground and the background
78+ pub fn new(backend: B) -> io::Result<Terminal<B>> {
79+ let size = backend.size()?;
80+ Terminal::with_options(
81+ backend,
82+ TerminalOptions {
83+ viewport: Viewport {
84+ area: size,
85+ resize_behavior: ResizeBehavior::Auto,
86+ },
87+ },
88+ )
89+ }
90+
91+ /// UNSTABLE
92+ pub fn with_options(backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
93+ Ok(Terminal {
94+ backend,
95+ buffers: [
96+ Buffer::empty(options.viewport.area),
97+ Buffer::empty(options.viewport.area),
98+ ],
99+ current: 0,
100+ hidden_cursor: false,
101+ viewport: options.viewport,
102+ })
103+ }
104+
105+ // /// Get a Frame object which provides a consistent view into the terminal state for rendering.
106+ // pub fn get_frame(&mut self) -> Frame<B> {
107+ // Frame {
108+ // terminal: self,
109+ // cursor_position: None,
110+ // }
111+ // }
112+
113+ pub fn current_buffer_mut(&mut self) -> &mut Buffer {
114+ &mut self.buffers[self.current]
115+ }
116+
117+ pub fn backend(&self) -> &B {
118+ &self.backend
119+ }
120+
121+ pub fn backend_mut(&mut self) -> &mut B {
122+ &mut self.backend
123+ }
124+
125+ /// Obtains a difference between the previous and the current buffer and passes it to the
126+ /// current backend for drawing.
127+ pub fn flush(&mut self) -> io::Result<()> {
128+ let previous_buffer = &self.buffers[1 - self.current];
129+ let current_buffer = &self.buffers[self.current];
130+ let updates = previous_buffer.diff(current_buffer);
131+ self.backend.draw(updates.into_iter())
132+ }
133+
134+ /// Updates the Terminal so that internal buffers match the requested size. Requested size will
135+ /// be saved so the size can remain consistent when rendering.
136+ /// This leads to a full clear of the screen.
137+ pub fn resize(&mut self, area: Rect) -> io::Result<()> {
138+ self.buffers[self.current].resize(area);
139+ self.buffers[1 - self.current].resize(area);
140+ self.viewport.area = area;
141+ self.clear()
142+ }
143+
144+ /// Queries the backend for size and resizes if it doesn't match the previous size.
145+ pub fn autoresize(&mut self) -> io::Result<()> {
146+ if self.viewport.resize_behavior == ResizeBehavior::Auto {
147+ let size = self.size()?;
148+ if size != self.viewport.area {
149+ self.resize(size)?;
150+ }
151+ };
152+ Ok(())
153+ }
154+
155+ /// Synchronizes terminal size, calls the rendering closure, flushes the current internal state
156+ /// and prepares for the next draw call.
157+ pub fn draw(&mut self) -> io::Result<()> {
158+ // // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
159+ // // and the terminal (if growing), which may OOB.
160+ // self.autoresize()?;
161+
162+ // let mut frame = self.get_frame();
163+ // f(&mut frame);
164+ // // We can't change the cursor position right away because we have to flush the frame to
165+ // // stdout first. But we also can't keep the frame around, since it holds a &mut to
166+ // // Terminal. Thus, we're taking the important data out of the Frame and dropping it.
167+ // let cursor_position = frame.cursor_position;
168+
169+ // Draw to stdout
170+ self.flush()?;
171+
172+ // match cursor_position {
173+ // None => self.hide_cursor()?,
174+ // Some((x, y)) => {
175+ // self.show_cursor()?;
176+ // self.set_cursor(x, y)?;
177+ // }
178+ // }
179+
180+ // Swap buffers
181+ self.buffers[1 - self.current].reset();
182+ self.current = 1 - self.current;
183+
184+ // Flush
185+ self.backend.flush()?;
186+ Ok(())
187+ }
188+
189+ pub fn hide_cursor(&mut self) -> io::Result<()> {
190+ self.backend.hide_cursor()?;
191+ self.hidden_cursor = true;
192+ Ok(())
193+ }
194+
195+ pub fn show_cursor(&mut self) -> io::Result<()> {
196+ self.backend.show_cursor()?;
197+ self.hidden_cursor = false;
198+ Ok(())
199+ }
200+
201+ pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
202+ self.backend.get_cursor()
203+ }
204+
205+ pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
206+ self.backend.set_cursor(x, y)
207+ }
208+
209+ /// Clear the terminal and force a full redraw on the next draw call.
210+ pub fn clear(&mut self) -> io::Result<()> {
211+ self.backend.clear()?;
212+ // Reset the back buffer to make sure the next update will redraw everything.
213+ self.buffers[1 - self.current].reset();
214+ Ok(())
215+ }
216+
217+ /// Queries the real size of the backend.
218+ pub fn size(&self) -> io::Result<Rect> {
219+ self.backend.size()
220+ }
221+}