@@ -34,7 +34,7 @@ pub struct EditorView {
34 last_insert: (commands::Command, Vec<KeyEvent>),
34 last_insert: (commands::Command, Vec<KeyEvent>),
35 completion: Option<Completion>,
35 completion: Option<Completion>,
36 spinners: ProgressSpinners,
36 spinners: ProgressSpinners,
37− pub autoinfo: Option<Info>,
37+ autoinfo: Option<Info>,
40pub const GUTTER_OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
40pub const GUTTER_OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
@@ -78,8 +78,26 @@ impl EditorView {
78 view.area.width - GUTTER_OFFSET,
78 view.area.width - GUTTER_OFFSET,
79 view.area.height.saturating_sub(1),
79 view.area.height.saturating_sub(1),
80 ); // - 1 for statusline
80 ); // - 1 for statusline
81+ let offset = Position::new(view.first_line, view.first_col);
82+ let height = view.area.height.saturating_sub(1); // - 1 for statusline
82− self.render_buffer(doc, view, area, surface, theme, is_focused, loader);
84+ let highlights = Self::doc_syntax_highlights(doc, offset, height, theme, loader);
85+ let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme));
86+ let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
87+ Box::new(syntax::merge(
89+ Self::doc_selection_highlights(doc, view, theme),
95+ Self::render_text_highlights(doc, offset, area, surface, theme, highlights);
96+ Self::render_gutter(doc, view, area, surface, theme);
99+ Self::render_focused_view_elements(view, doc, area, theme, surface);
84 // if we're not at the edge of the screen, draw a right border
102 // if we're not at the edge of the screen, draw a right border
85 if viewport.right() != view.area.right() {
103 if viewport.right() != view.area.right() {
@@ -94,7 +112,7 @@ impl EditorView {
97− self.render_diagnostics(doc, view, area, surface, theme, is_focused);
115+ self.render_diagnostics(doc, view, area, surface, theme);
99 let area = Rect::new(
117 let area = Rect::new(
100 view.area.x,
118 view.area.x,
@@ -105,31 +123,34 @@ impl EditorView {
105 self.render_statusline(doc, view, area, surface, theme, is_focused);
123 self.render_statusline(doc, view, area, surface, theme, is_focused);
126+ /// Get syntax highlights for a document in a view represented by the first line
127+ /// and column (`offset`) and the last line. This is done instead of using a view
128+ /// directly to enable rendering syntax highlighted docs anywhere (eg. picker preview)
108 #[allow(clippy::too_many_arguments)]
129 #[allow(clippy::too_many_arguments)]
109− pub fn render_buffer(
130+ pub fn doc_syntax_highlights<'doc>(
111− doc: &Document,
131+ doc: &'doc Document,
112− view: &View,
132+ offset: Position,
113− viewport: Rect,
133+ height: u16,
114− surface: &mut Surface,
115 theme: &Theme,
134 theme: &Theme,
117 loader: &syntax::Loader,
135 loader: &syntax::Loader,
118− ) {
136+ ) -> Box<dyn Iterator<Item = HighlightEvent> + 'doc> {
119 let text = doc.text().slice(..);
137 let text = doc.text().slice(..);
121− let last_line = view.last_line(doc);
138+ let last_line = std::cmp::min(
139+ // Saturating subs to make it inclusive zero indexing.
140+ (offset.row + height as usize).saturating_sub(1),
141+ doc.text().len_lines().saturating_sub(1),
123 let range = {
144 let range = {
124 // calculate viewport byte ranges
145 // calculate viewport byte ranges
125− let start = text.line_to_byte(view.first_line);
146+ let start = text.line_to_byte(offset.row);
126 let end = text.line_to_byte(last_line + 1);
147 let end = text.line_to_byte(last_line + 1);
128 start..end
149 start..end
131 // TODO: range doesn't actually restrict source, just highlight range
152 // TODO: range doesn't actually restrict source, just highlight range
132− let highlights: Vec<_> = match doc.syntax() {
153+ let highlights = match doc.syntax() {
133 Some(syntax) => {
154 Some(syntax) => {
134 let scopes = theme.scopes();
155 let scopes = theme.scopes();
@@ -151,20 +172,16 @@ impl EditorView {
151 Some(config_ref)
172 Some(config_ref)
175+ .map(|event| event.unwrap())
154 .collect() // TODO: we collect here to avoid holding the lock, fix later
176 .collect() // TODO: we collect here to avoid holding the lock, fix later
156− None => vec![Ok(HighlightEvent::Source {
178+ None => vec![HighlightEvent::Source {
157 start: range.start,
179 start: range.start,
158 end: range.end,
180 end: range.end,
161− let mut spans = Vec::new();
183+ .into_iter()
162− let mut visual_x = 0u16;
163− let mut line = 0u16;
164− let tab_width = doc.tab_width();
165− let tab = " ".repeat(tab_width);
167− let highlights = highlights.into_iter().map(|event| match event.unwrap() {
184+ .map(move |event| match event {
168 // convert byte offsets to char offset
185 // convert byte offsets to char offset
169 HighlightEvent::Source { start, end } => {
186 HighlightEvent::Source { start, end } => {
170 let start = ensure_grapheme_boundary_next(text, text.byte_to_char(start));
187 let start = ensure_grapheme_boundary_next(text, text.byte_to_char(start));
@@ -174,13 +191,44 @@ impl EditorView {
174 event => event,
191 event => event,
194+ Box::new(highlights)
197+ /// Get highlight spans for document diagnostics
198+ pub fn doc_diagnostics_highlights(
201+ ) -> Vec<(usize, std::ops::Range<usize>)> {
202+ let diagnostic_scope = theme
203+ .find_scope_index("diagnostic")
204+ .or_else(|| theme.find_scope_index("ui.cursor"))
205+ .or_else(|| theme.find_scope_index("ui.selection"))
206+ .expect("no selection scope found!");
213+ diagnostic.range.start..diagnostic.range.end,
219+ /// Get highlight spans for selections in a document view.
220+ pub fn doc_selection_highlights(
224+ ) -> Vec<(usize, std::ops::Range<usize>)> {
225+ let text = doc.text().slice(..);
177− let selections = doc.selection(view.id);
226+ let selection = doc.selection(view.id);
178− let primary_idx = selections.primary_index();
227+ let primary_idx = selection.primary_index();
180 let selection_scope = theme
229 let selection_scope = theme
181 .find_scope_index("ui.selection")
230 .find_scope_index("ui.selection")
182 .expect("no selection scope found!");
231 .expect("no selection scope found!");
184 let base_cursor_scope = theme
232 let base_cursor_scope = theme
185 .find_scope_index("ui.cursor")
233 .find_scope_index("ui.cursor")
186 .unwrap_or(selection_scope);
234 .unwrap_or(selection_scope);
@@ -192,64 +240,59 @@ impl EditorView {
193 .unwrap_or(base_cursor_scope);
241 .unwrap_or(base_cursor_scope);
195− let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
196− // TODO: primary + insert mode patching:
197− // (ui.cursor.primary).patch(mode).unwrap_or(cursor)
198− let primary_cursor_scope = theme
243+ let primary_cursor_scope = theme
199− .find_scope_index("ui.cursor.primary")
244+ .find_scope_index("ui.cursor.primary")
200− .unwrap_or(cursor_scope);
245+ .unwrap_or(cursor_scope);
201− let primary_selection_scope = theme
246+ let primary_selection_scope = theme
202− .find_scope_index("ui.selection.primary")
247+ .find_scope_index("ui.selection.primary")
203− .unwrap_or(selection_scope);
205− // inject selections as highlight scopes
206− let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
207− for (i, range) in selections.iter().enumerate() {
208− let (cursor_scope, selection_scope) = if i == primary_idx {
209− (primary_cursor_scope, primary_selection_scope)
211− (cursor_scope, selection_scope)
248+ .unwrap_or(selection_scope);
214− // Special-case: cursor at end of the rope.
250+ let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
215− if range.head == range.anchor && range.head == text.len_chars() {
251+ for (i, range) in selection.iter().enumerate() {
216− spans.push((cursor_scope, range.head..range.head + 1));
252+ let (cursor_scope, selection_scope) = if i == primary_idx {
217− continue;
253+ (primary_cursor_scope, primary_selection_scope)
255+ (cursor_scope, selection_scope)
220− let range = range.min_width_1(text);
258+ // Special-case: cursor at end of the rope.
221− if range.head > range.anchor {
259+ if range.head == range.anchor && range.head == text.len_chars() {
223− let cursor_start = prev_grapheme_boundary(text, range.head);
224− spans.push((selection_scope, range.anchor..cursor_start));
225− spans.push((cursor_scope, cursor_start..range.head));
228− let cursor_end = next_grapheme_boundary(text, range.head);
229− spans.push((cursor_scope, range.head..cursor_end));
260+ spans.push((cursor_scope, range.head..range.head + 1));
230− spans.push((selection_scope, cursor_end..range.anchor));
261+ continue;
234− Box::new(syntax::merge(highlights, spans))
264+ let range = range.min_width_1(text);
265+ if range.head > range.anchor {
267+ let cursor_start = prev_grapheme_boundary(text, range.head);
268+ spans.push((selection_scope, range.anchor..cursor_start));
269+ spans.push((cursor_scope, cursor_start..range.head));
235− } else {
270+ } else {
272+ let cursor_end = next_grapheme_boundary(text, range.head);
273+ spans.push((cursor_scope, range.head..cursor_end));
274+ spans.push((selection_scope, cursor_end..range.anchor));
281+ pub fn render_text_highlights<H: Iterator<Item = HighlightEvent>>(
285+ surface: &mut Surface,
236− Box::new(highlights)
287+ highlights: H,
289+ let text = doc.text().slice(..);
239− // diagnostic injection
291+ let mut spans = Vec::new();
240− let diagnostic_scope = theme.find_scope_index("diagnostic").unwrap_or(cursor_scope);
292+ let mut visual_x = 0u16;
241− let highlights = Box::new(syntax::merge(
293+ let mut line = 0u16;
242− highlights,
294+ let tab_width = doc.tab_width();
243− doc.diagnostics()
295+ let tab = " ".repeat(tab_width);
248− diagnostic.range.start..diagnostic.range.end,
254 'outer: for event in highlights {
297 'outer: for event in highlights {
255 match event {
298 match event {
@@ -273,14 +316,14 @@ impl EditorView {
275 for grapheme in RopeGraphemes::new(text) {
318 for grapheme in RopeGraphemes::new(text) {
276− let out_of_bounds = visual_x < view.first_col as u16
319+ let out_of_bounds = visual_x < offset.col as u16
277− || visual_x >= viewport.width + view.first_col as u16;
320+ || visual_x >= viewport.width + offset.col as u16;
279 if LineEnding::from_rope_slice(&grapheme).is_some() {
322 if LineEnding::from_rope_slice(&grapheme).is_some() {
280 if !out_of_bounds {
323 if !out_of_bounds {
281 // we still want to render an empty cell with the style
324 // we still want to render an empty cell with the style
282 surface.set_string(
325 surface.set_string(
283− viewport.x + visual_x - view.first_col as u16,
326+ viewport.x + visual_x - offset.col as u16,
284 viewport.y + line,
327 viewport.y + line,
@@ -310,7 +353,7 @@ impl EditorView {
310 if !out_of_bounds {
353 if !out_of_bounds {
311 // if we're offscreen just keep going until we hit a new line
354 // if we're offscreen just keep going until we hit a new line
312 surface.set_string(
355 surface.set_string(
313− viewport.x + visual_x - view.first_col as u16,
356+ viewport.x + visual_x - offset.col as u16,
314 viewport.y + line,
357 viewport.y + line,
315 grapheme,
358 grapheme,
@@ -323,14 +366,108 @@ impl EditorView {
371+ /// Render brace match, selected line numbers, etc (meant for the focused view only)
372+ pub fn render_focused_view_elements(
377+ surface: &mut Surface,
379+ let text = doc.text().slice(..);
380+ let selection = doc.selection(view.id);
381+ let last_line = view.last_line(doc);
383+ let start = text.line_to_char(view.first_line);
384+ let end = text.line_to_char(last_line + 1) + 1; // +1 for cursor at end of text.
385+ Range::new(start, end)
388+ // render selected linenr(s)
389+ let linenr_select: Style = theme
390+ .try_get("ui.linenr.selected")
391+ .unwrap_or_else(|| theme.get("ui.linenr"));
393+ // Whether to draw the line number for the last line of the
394+ // document or not. We only draw it if it's not an empty line.
395+ let draw_last = text.line_to_byte(last_line) < text.len_bytes();
397+ for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
398+ let head = view.screen_coords_at_pos(
401+ if selection.head > selection.anchor {
407+ if let Some(head) = head {
408+ // Highlight line number for selected lines.
409+ let line_number = view.first_line + head.row;
410+ let line_number_text = if line_number == last_line && !draw_last {
413+ format!("{:>5}", line_number + 1)
415+ surface.set_stringn(
416+ viewport.x - GUTTER_OFFSET + 1,
417+ viewport.y + head.row as u16,
423+ // Highlight matching braces
424+ // TODO: set cursor position for IME
425+ if let Some(syntax) = doc.syntax() {
426+ use helix_core::match_brackets;
430+ .cursor(doc.text().slice(..));
431+ let pos = match_brackets::find(syntax, doc.text(), pos)
432+ .and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
434+ if let Some(pos) = pos {
435+ // ensure col is on screen
436+ if (pos.col as u16) < viewport.width + view.first_col as u16
437+ && pos.col >= view.first_col
439+ let style = theme.try_get("ui.cursor.match").unwrap_or_else(|| {
441+ .add_modifier(Modifier::REVERSED)
442+ .add_modifier(Modifier::DIM)
446+ .get_mut(viewport.x + pos.col as u16, viewport.y + pos.row as u16)
327− // render gutters
455+ #[allow(clippy::too_many_arguments)]
456+ pub fn render_gutter(
460+ surface: &mut Surface,
463+ let text = doc.text().slice(..);
464+ let last_line = view.last_line(doc);
329− let linenr: Style = theme.get("ui.linenr");
466+ let linenr = theme.get("ui.linenr");
330− let warning: Style = theme.get("warning");
467+ let warning = theme.get("warning");
331− let error: Style = theme.get("error");
468+ let error = theme.get("error");
332− let info: Style = theme.get("info");
469+ let info = theme.get("info");
333− let hint: Style = theme.get("hint");
470+ let hint = theme.get("hint");
335 // Whether to draw the line number for the last line of the
472 // Whether to draw the line number for the last line of the
336 // document or not. We only draw it if it's not an empty line.
473 // document or not. We only draw it if it's not an empty line.
@@ -368,80 +505,6 @@ impl EditorView {
372− // render selections and selected linenr(s)
373− let linenr_select: Style = theme
374− .try_get("ui.linenr.selected")
375− .unwrap_or_else(|| theme.get("ui.linenr"));
379− let start = text.line_to_char(view.first_line);
380− let end = text.line_to_char(last_line + 1) + 1; // +1 for cursor at end of text.
381− Range::new(start, end)
384− let selection = doc.selection(view.id);
386− for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
387− let head = view.screen_coords_at_pos(
390− if selection.head > selection.anchor {
396− if let Some(head) = head {
397− // Draw line number for selected lines.
398− let line_number = view.first_line + head.row;
399− let line_number_text = if line_number == last_line && !draw_last {
402− format!("{:>5}", line_number + 1)
404− surface.set_stringn(
405− viewport.x + 1 - GUTTER_OFFSET,
406− viewport.y + head.row as u16,
412− // TODO: set cursor position for IME
413− if let Some(syntax) = doc.syntax() {
414− use helix_core::match_brackets;
418− .cursor(doc.text().slice(..));
419− let pos = match_brackets::find(syntax, doc.text(), pos)
420− .and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
422− if let Some(pos) = pos {
423− // ensure col is on screen
424− if (pos.col as u16) < viewport.width + view.first_col as u16
425− && pos.col >= view.first_col
427− let style = theme.try_get("ui.cursor.match").unwrap_or_else(|| {
429− .add_modifier(Modifier::REVERSED)
430− .add_modifier(Modifier::DIM)
435− viewport.x + pos.col as u16,
436− viewport.y + pos.row as u16,
447 pub fn render_diagnostics(
510 pub fn render_diagnostics(
@@ -451,7 +514,6 @@ impl EditorView {
451 viewport: Rect,
514 viewport: Rect,
452 surface: &mut Surface,
515 surface: &mut Surface,
453 theme: &Theme,
516 theme: &Theme,
456 use helix_core::diagnostic::Severity;
518 use helix_core::diagnostic::Severity;
457 use tui::{
519 use tui::{
@@ -469,10 +531,10 @@ impl EditorView {
469 diagnostic.range.start <= cursor && diagnostic.range.end >= cursor
531 diagnostic.range.start <= cursor && diagnostic.range.end >= cursor
472− let warning: Style = theme.get("warning");
534+ let warning = theme.get("warning");
473− let error: Style = theme.get("error");
535+ let error = theme.get("error");
474− let info: Style = theme.get("info");
536+ let info = theme.get("info");
475− let hint: Style = theme.get("hint");
537+ let hint = theme.get("hint");
477 // Vec::with_capacity(diagnostics.len()); // rough estimate
539 // Vec::with_capacity(diagnostics.len()); // rough estimate
478 let mut lines = Vec::new();
540 let mut lines = Vec::new();
@@ -961,7 +1023,7 @@ impl Component for EditorView {
964− fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
1026+ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
965 // clear with background color
1027 // clear with background color
966 surface.set_style(area, cx.editor.theme.get("ui.background"));
1028 surface.set_style(area, cx.editor.theme.get("ui.background"));
@@ -983,7 +1045,7 @@ impl Component for EditorView {
986− if let Some(ref info) = self.autoinfo {
1048+ if let Some(ref mut info) = self.autoinfo {
987 info.render(area, surface, cx);
1049 info.render(area, surface, cx);
@@ -1030,7 +1092,7 @@ impl Component for EditorView {
1033− if let Some(completion) = &self.completion {
1095+ if let Some(completion) = self.completion.as_mut() {
1034 completion.render(area, surface, cx);
1096 completion.render(area, surface, cx);