on master help sign in
refactoring editor.rs
commit9e7b6465c6d4ce087af82ec07e41e19dbf4abdfb authorJan Hrastnik <jan.hrastnik2@gmail.com> dateFri 9 Oct 2020, 22:05 (6 years ago) parent0c0c2c7
1 file changed+214−228
helix-term/src/editor.rs+214−228
helix-term/src/editor.rs+214−228
@@ -4,7 +4,7 @@ use helix_view::{commands, keymap, View};
44
55use std::{
66 borrow::Cow,
7− io::{self, stdout, Write},
7+ io::{self, stdout, Stdout, Write},
88 path::PathBuf,
99 time::Duration,
1010};
@@ -24,6 +24,8 @@ use crossterm::{
2424
2525use tui::{backend::CrosstermBackend, buffer::Buffer as Surface, layout::Rect, style::Style};
2626
27+const OFFSET: u16 = 6; // 5 linenr + 1 gutter
28+
2729type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
2830
2931static EX: smol::Executor = smol::Executor::new();
@@ -68,232 +70,235 @@ impl Editor {
6870 use tui::backend::Backend;
6971 use tui::style::Color;
7072 // TODO: ideally not mut but highlights require it because of cursor cache
71− match &mut self.view {
72− Some(view) => {
73− let area = Rect::new(0, 0, self.size.0, self.size.1);
74− let mut stdout = stdout();
75− self.surface.reset(); // reset is faster than allocating new empty surface
73+ let viewport = Rect::new(OFFSET, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
74+ let text_color: Style = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
7675
77− // clear with background color
78− self.surface
79− .set_style(area, view.theme.get("ui.background"));
76+ self.render_view(viewport);
8077
81− let offset = 5 + 1; // 5 linenr + 1 gutter
82− let viewport = Rect::new(offset, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
78+ self.render_prompt(text_color);
8379
84− // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
85− let source_code = view.state.doc().to_string();
80+ self.render_cursor(viewport, text_color);
81+ }
8682
87− let last_line = view.last_line();
83+ pub fn render_view(&mut self, viewport: Rect) {
84+ use tui::style::Color;
85+ let area = Rect::new(0, 0, self.size.0, self.size.1);
86+ let mut view: &mut View = self.view.as_mut().unwrap();
87+ self.surface.reset(); // reset is faster than allocating new empty surface
8888
89− let range = {
90− // calculate viewport byte ranges
91− let start = view.state.doc().line_to_byte(view.first_line);
92− let end = view.state.doc().line_to_byte(last_line)
93− + view.state.doc().line(last_line).len_bytes();
89+ // clear with background color
90+ self.surface
91+ .set_style(area, view.theme.get("ui.background"));
9492
95− start..end
96− };
93+ // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
94+ let source_code = view.state.doc().to_string();
9795
98− // TODO: range doesn't actually restrict source, just highlight range
96+ let last_line = view.last_line();
9997
100− // TODO: cache highlight results
101− // TODO: only recalculate when state.doc is actually modified
102− let highlights: Vec<_> = match view.state.syntax.as_mut() {
103− Some(syntax) => {
104− syntax
105− .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
106− .unwrap()
107− .collect() // TODO: we collect here to avoid double borrow, fix later
108− }
109− None => vec![Ok(HighlightEvent::Source {
110− start: range.start,
111− end: range.end,
112− })],
113− };
114−
115− let mut spans = Vec::new();
116−
117− let mut visual_x = 0;
118− let mut line = 0u16;
119−
120− let visible_selections: Vec<Range> = view
121− .state
122− .selection()
123− .ranges()
124− .iter()
125− // TODO: limit selection to one in viewport
126− // .filter(|range| !range.is_empty()) // && range.overlaps(&Range::new(start, end + 1))
127− .copied()
128− .collect();
129−
130− 'outer: for event in highlights {
131− match event.unwrap() {
132− HighlightEvent::HighlightStart(span) => {
133− spans.push(span);
134− }
135− HighlightEvent::HighlightEnd => {
136− spans.pop();
137− }
138− HighlightEvent::Source { start, end } => {
139− // TODO: filter out spans out of viewport for now..
98+ let range = {
99+ // calculate viewport byte ranges
100+ let start = view.state.doc().line_to_byte(view.first_line);
101+ let end = view.state.doc().line_to_byte(last_line)
102+ + view.state.doc().line(last_line).len_bytes();
140103
141− let start = view.state.doc().byte_to_char(start);
142− let end = view.state.doc().byte_to_char(end); // <-- index 744, len 743
104+ start..end
105+ };
143106
144− let text = view.state.doc().slice(start..end);
107+ // TODO: range doesn't actually restrict source, just highlight range
108+ // TODO: cache highlight results
109+ // TODO: only recalculate when state.doc is actually modified
110+ let highlights: Vec<_> = match view.state.syntax.as_mut() {
111+ Some(syntax) => {
112+ syntax
113+ .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
114+ .unwrap()
115+ .collect() // TODO: we collect here to avoid double borrow, fix later
116+ }
117+ None => vec![Ok(HighlightEvent::Source {
118+ start: range.start,
119+ end: range.end,
120+ })],
121+ };
122+ let mut spans = Vec::new();
123+ let mut visual_x = 0;
124+ let mut line = 0u16;
125+ let visible_selections: Vec<Range> = view
126+ .state
127+ .selection()
128+ .ranges()
129+ .iter()
130+ // TODO: limit selection to one in viewport
131+ // .filter(|range| !range.is_empty()) // && range.overlaps(&Range::new(start, end + 1))
132+ .copied()
133+ .collect();
134+
135+ 'outer: for event in highlights {
136+ match event.unwrap() {
137+ HighlightEvent::HighlightStart(span) => {
138+ spans.push(span);
139+ }
140+ HighlightEvent::HighlightEnd => {
141+ spans.pop();
142+ }
143+ HighlightEvent::Source { start, end } => {
144+ // TODO: filter out spans out of viewport for now..
145145
146− use helix_core::graphemes::{grapheme_width, RopeGraphemes};
146+ let start = view.state.doc().byte_to_char(start);
147+ let end = view.state.doc().byte_to_char(end); // <-- index 744, len 743
147148
148− let style = match spans.first() {
149− Some(span) => view.theme.get(view.theme.scopes()[span.0].as_str()),
150− None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
151− };
149+ let text = view.state.doc().slice(start..end);
152150
153− // TODO: we could render the text to a surface, then cache that, that
154− // way if only the selection/cursor changes we can copy from cache
155− // and paint the new cursor.
156−
157− let mut char_index = start;
158−
159− // iterate over range char by char
160− for grapheme in RopeGraphemes::new(&text) {
161− // TODO: track current char_index
162−
163− if grapheme == "\n" {
164− visual_x = 0;
165− line += 1;
166−
167− // TODO: with proper iter this shouldn't be necessary
168− if line >= viewport.height {
169− break 'outer;
170− }
171− } else if grapheme == "\t" {
172− visual_x += (TAB_WIDTH as u16);
173− } else {
174− // Cow will prevent allocations if span contained in a single slice
175− // which should really be the majority case
176− let grapheme = Cow::from(grapheme);
177− let width = grapheme_width(&grapheme) as u16;
178−
179− // TODO: this should really happen as an after pass
180− let style = if visible_selections
181− .iter()
182− .any(|range| range.contains(char_index))
183− {
184− // cedar
185− style.clone().bg(Color::Rgb(128, 47, 0))
186− } else {
187− style
188− };
189−
190− let style = if visible_selections
191− .iter()
192− .any(|range| range.head == char_index)
193− {
194− style.clone().bg(Color::Rgb(255, 255, 255))
195− } else {
196− style
197− };
198−
199− // TODO: paint cursor heads except primary
200−
201− self.surface.set_string(
202− offset + visual_x,
203− line,
204− grapheme,
205− style,
206− );
207−
208− visual_x += width;
209− }
210− // if grapheme == "\t"
151+ use helix_core::graphemes::{grapheme_width, RopeGraphemes};
211152
212− char_index += 1;
153+ let style = match spans.first() {
154+ Some(span) => view.theme.get(view.theme.scopes()[span.0].as_str()),
155+ None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
156+ };
157+
158+ // TODO: we could render the text to a surface, then cache that, that
159+ // way if only the selection/cursor changes we can copy from cache
160+ // and paint the new cursor.
161+
162+ let mut char_index = start;
163+
164+ // iterate over range char by char
165+ for grapheme in RopeGraphemes::new(&text) {
166+ // TODO: track current char_index
167+
168+ if grapheme == "\n" {
169+ visual_x = 0;
170+ line += 1;
171+
172+ // TODO: with proper iter this shouldn't be necessary
173+ if line >= viewport.height {
174+ break 'outer;
213175 }
214− }
215− }
216− }
176+ } else if grapheme == "\t" {
177+ visual_x += (TAB_WIDTH as u16);
178+ } else {
179+ // Cow will prevent allocations if span contained in a single slice
180+ // which should really be the majority case
181+ let grapheme = Cow::from(grapheme);
182+ let width = grapheme_width(&grapheme) as u16;
183+
184+ // TODO: this should really happen as an after pass
185+ let style = if visible_selections
186+ .iter()
187+ .any(|range| range.contains(char_index))
188+ {
189+ // cedar
190+ style.clone().bg(Color::Rgb(128, 47, 0))
191+ } else {
192+ style
193+ };
217194
218− let style: Style = view.theme.get("ui.linenr");
219− for (i, line) in (view.first_line..last_line).enumerate() {
220− self.surface
221− .set_stringn(0, i as u16, format!("{:>5}", line + 1), 5, style);
222− // lavender
223− }
195+ let style = if visible_selections
196+ .iter()
197+ .any(|range| range.head == char_index)
198+ {
199+ style.clone().bg(Color::Rgb(255, 255, 255))
200+ } else {
201+ style
202+ };
224203
225− // // iterate over selections and render them
226− // for range in state.selection.ranges() {
227− // // get terminal coords for x,y for each range pos
228− // // TODO: this won't work with multiline
229− // let (y1, x1) = coords_at_pos(&text, range.from());
230− // let (y2, x2) = coords_at_pos(&text, range.to());
231− // let area = Rect::new(
232− // (x1 + 2) as u16,
233− // y1 as u16,
234− // (x2 - x1 + 1) as u16,
235− // (y2 - y1 + 1) as u16,
236− // );
237− // self.surface.set_style(area, select);
238− // }
239−
240− // statusline
241− let mode = match view.state.mode() {
242− Mode::Insert => "INS",
243− Mode::Normal => "NOR",
244− Mode::Goto => "GOTO",
245− Mode::Command => ":",
246− };
247− self.surface.set_style(
248− Rect::new(0, self.size.1 - 1, self.size.0, 1),
249− view.theme.get("ui.statusline"),
250− );
251− // TODO: unfocused one with different color
252− let text_color = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
253− self.surface
254− .set_string(1, self.size.1 - 1, mode, text_color);
255−
256− // set cursor shape
257− match view.state.mode() {
258− Mode::Insert => write!(stdout, "\x1B[6 q"),
259− Mode::Normal => write!(stdout, "\x1B[2 q"),
260− Mode::Goto => write!(stdout, "\x1B[2 q"),
261− Mode::Command => write!(stdout, "\x1B[2 q"),
262− };
263−
264− // render the cursor
265− let mut pos: Position;
266− if view.state.mode() == Mode::Command {
267− pos = Position::new(self.size.0 as usize, 2);
268− } else {
269− if let Some(path) = view.state.path() {
270− self.surface.set_string(
271− 6,
272− self.size.1 - 1,
273− path.to_string_lossy(),
274− text_color,
275− );
276− }
204+ // TODO: paint cursor heads except primary
205+
206+ self.surface
207+ .set_string(OFFSET + visual_x, line, grapheme, style);
277208
278− let cursor = view.state.selection().cursor();
209+ visual_x += width;
210+ }
211+ // if grapheme == "\t"
279212
280− pos = view
281− .screen_coords_at_pos(&view.state.doc().slice(..), cursor)
282− .expect("Cursor is out of bounds.");
283− pos.col += viewport.x as usize;
284− pos.row += viewport.y as usize;
213+ char_index += 1;
214+ }
285215 }
216+ }
217+ }
218+ }
286219
287− self.terminal
288− .backend_mut()
289− .draw(self.cache.diff(&self.surface).into_iter());
290− // swap the buffer
291− std::mem::swap(&mut self.surface, &mut self.cache);
220+ pub fn render_prompt(&mut self, text_color: Style) {
221+ use tui::backend::Backend;
222+ let view = self.view.as_ref().unwrap();
223+ let style: Style = view.theme.get("ui.linenr");
224+ let last_line = view.last_line();
225+ for (i, line) in (view.first_line..last_line).enumerate() {
226+ self.surface
227+ .set_stringn(0, i as u16, format!("{:>5}", line + 1), 5, style);
228+ // lavender
229+ }
292230
293− execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
231+ // let lines = state
232+ // .doc
233+ // .lines_at(self.first_line as usize)
234+ // .take(self.size.1 as usize)
235+ // .map(|x| x.as_str().unwrap());
236+
237+ // // iterate over selections and render them
238+ // let select = Style::default().bg(tui::style::Color::LightBlue);
239+ // let text = state.doc.slice(..);
240+ // for range in state.selection.ranges() {
241+ // // get terminal coords for x,y for each range pos
242+ // // TODO: this won't work with multiline
243+ // let (y1, x1) = coords_at_pos(&text, range.from());
244+ // let (y2, x2) = coords_at_pos(&text, range.to());
245+ // let area = Rect::new(
246+ // (x1 + 2) as u16,
247+ // y1 as u16,
248+ // (x2 - x1 + 1) as u16,
249+ // (y2 - y1 + 1) as u16,
250+ // );
251+ // self.surface.set_style(area, select);
252+
253+ // // TODO: don't highlight next char in append mode
254+ // }
255+
256+ // statusline
257+ let mode = match view.state.mode() {
258+ Mode::Insert => "INS",
259+ Mode::Normal => "NOR",
260+ Mode::Goto => "GOTO",
261+ Mode::Command => ":",
262+ };
263+ self.surface.set_style(
264+ Rect::new(0, self.size.1 - 1, self.size.0, 1),
265+ view.theme.get("ui.statusline"),
266+ );
267+ self.surface
268+ .set_string(1, self.size.1 - 1, mode, text_color);
269+ self.terminal
270+ .backend_mut()
271+ .draw(self.cache.diff(&self.surface).into_iter());
272+ // swap the buffer
273+ std::mem::swap(&mut self.surface, &mut self.cache);
274+ }
275+
276+ pub fn render_cursor(&mut self, viewport: Rect, text_color: Style) {
277+ let mut pos: Position;
278+ let view = self.view.as_ref().unwrap();
279+ let mut stdout = stdout();
280+ match view.state.mode() {
281+ Mode::Insert => write!(stdout, "\x1B[6 q"),
282+ mode => write!(stdout, "\x1B[2 q"),
283+ };
284+ if view.state.mode() == Mode::Command {
285+ pos = Position::new(self.size.0 as usize, 2);
286+ } else {
287+ if let Some(path) = view.state.path() {
288+ self.surface
289+ .set_string(6, self.size.1 - 1, path.to_string_lossy(), text_color);
294290 }
295− None => (),
291+
292+ let cursor = view.state.selection().cursor();
293+
294+ pos = view
295+ .screen_coords_at_pos(&view.state.doc().slice(..), cursor)
296+ .expect("Cursor is out of bounds.");
297+ pos.col += viewport.x as usize;
298+ pos.row += viewport.y as usize;
296299 }
300+
301+ execute!(stdout, cursor::MoveTo(pos.col as u16, pos.row as u16));
297302 }
298303
299304 pub async fn event_loop(&mut self) {
@@ -331,9 +336,9 @@ impl Editor {
331336 // TODO: sequences (`gg`)
332337 // TODO: handle count other than 1
333338 if let Some(view) = &mut self.view {
339+ let keys = vec![event];
334340 match view.state.mode() {
335341 Mode::Insert => {
336− let keys = vec![event];
337342 if let Some(command) = keymap[&Mode::Insert].get(&keys) {
338343 command(view, 1);
339344 } else if let KeyEvent {
@@ -344,44 +349,25 @@ impl Editor {
344349 commands::insert::insert_char(view, c);
345350 }
346351 view.ensure_cursor_in_view();
347−
348352 self.render();
349353 }
350− Mode::Normal => {
351− let keys = vec![event];
352− if let Some(command) = keymap[&Mode::Normal].get(&keys) {
354+ Mode::Command => {
355+ if let Some(command) = keymap[&Mode::Command].get(&keys) {
353356 command(view, 1);
354−
355− // TODO: simplistic ensure cursor in view for now
356− view.ensure_cursor_in_view();
357−
358357 self.render();
359358 }
360359 }
361− Mode::Goto => {
362− // TODO: handle modes and sequences (`gg`)
363− let keys = vec![event];
364− if let Some(command) = keymap[&Mode::Goto].get(&keys) {
365− // TODO: handle count other than 1
360+ mode => {
361+ if let Some(command) = keymap[&mode].get(&keys) {
366362 command(view, 1);
367363
368364 // TODO: simplistic ensure cursor in view for now
369365 view.ensure_cursor_in_view();
370−
371− self.render();
372− }
373− }
374− Mode::Command => {
375− // TODO: handle modes and sequences (`gg`)
376− let keys = vec![event];
377− if let Some(command) = keymap[&Mode::Goto].get(&keys) {
378− // TODO: handle count other than 1
379− command(view, 1);
380−
381366 self.render();
382367 }
383368 }
384369 }
370+ self.render();
385371 }
386372 }
387373 Some(Ok(_)) => {