@@ -9,7 +9,7 @@ use crate::{
99use helix_core::{
1010 coords_at_pos,
1111 graphemes::ensure_grapheme_boundary,
12− syntax::{self, HighlightEvent},
12+ syntax::{self, Highlight, HighlightEvent},
1313 LineEnding, Position, Range,
1414};
1515use helix_lsp::LspProgressMap;
@@ -44,6 +44,124 @@ impl Default for EditorView {
4444 }
4545}
4646
47+struct Merge<I> {
48+ iter: I,
49+ spans: Box<dyn Iterator<Item = (usize, std::ops::Range<usize>)>>,
50+
51+ next_event: Option<HighlightEvent>,
52+ next_span: Option<(usize, std::ops::Range<usize>)>,
53+
54+ queue: Vec<HighlightEvent>,
55+}
56+
57+fn merge<I: Iterator<Item = HighlightEvent>>(
58+ iter: I,
59+ spans: Vec<(usize, std::ops::Range<usize>)>,
60+) -> impl Iterator<Item = HighlightEvent> {
61+ let spans = Box::new(spans.into_iter());
62+ let mut merge = Merge {
63+ iter,
64+ spans,
65+ next_event: None,
66+ next_span: None,
67+ queue: Vec::new(),
68+ };
69+ merge.next_event = merge.iter.next();
70+ merge.next_span = merge.spans.next();
71+ merge
72+}
73+
74+impl<I: Iterator<Item = HighlightEvent>> Iterator for Merge<I> {
75+ type Item = HighlightEvent;
76+ fn next(&mut self) -> Option<Self::Item> {
77+ use HighlightEvent::*;
78+ if let Some(event) = self.queue.pop() {
79+ return Some(event);
80+ }
81+
82+ loop {
83+ match (self.next_event, &self.next_span) {
84+ // this happens when range is partially or fully offscreen
85+ (Some(Source { start, end }), Some((span, range))) if start > range.start => {
86+ if start > range.end {
87+ self.next_span = self.spans.next();
88+ } else {
89+ self.next_span = Some((*span, start..range.end));
90+ };
91+ }
92+ _ => break,
93+ }
94+ }
95+
96+ match (self.next_event, &self.next_span) {
97+ (Some(HighlightStart(i)), _) => {
98+ self.next_event = self.iter.next();
99+ return Some(HighlightStart(i));
100+ }
101+ (Some(HighlightEnd), _) => {
102+ self.next_event = self.iter.next();
103+ return Some(HighlightEnd);
104+ }
105+ (Some(Source { start, end }), Some((span, range))) if start < range.start => {
106+ let intersect = range.start.min(end);
107+ let event = Source {
108+ start,
109+ end: intersect,
110+ };
111+
112+ if end == intersect {
113+ // the event is complete
114+ self.next_event = self.iter.next();
115+ } else {
116+ // subslice the event
117+ self.next_event = Some(Source {
118+ start: intersect,
119+ end,
120+ });
121+ };
122+
123+ Some(event)
124+ }
125+ (Some(Source { start, end }), Some((span, range))) if start == range.start => {
126+ let intersect = range.end.min(end);
127+ let event = HighlightStart(Highlight(*span));
128+
129+ // enqueue in reverse order
130+ self.queue.push(HighlightEnd);
131+ self.queue.push(Source {
132+ start,
133+ end: intersect,
134+ });
135+
136+ if end == intersect {
137+ // the event is complete
138+ self.next_event = self.iter.next();
139+ } else {
140+ // subslice the event
141+ self.next_event = Some(Source {
142+ start: intersect,
143+ end,
144+ });
145+ };
146+
147+ if intersect == range.end {
148+ self.next_span = self.spans.next();
149+ } else {
150+ self.next_span = Some((*span, intersect..range.end));
151+ }
152+
153+ Some(event)
154+ }
155+ (Some(event), None) => {
156+ self.next_event = self.iter.next();
157+ return Some(event);
158+ }
159+ (None, None) => return None,
160+ e => unreachable!("{:?}", e),
161+ }
162+ }
163+}
164+
47165impl EditorView {
48166 pub fn new(keymaps: Keymaps) -> Self {
49167 Self {
@@ -140,9 +258,91 @@ impl EditorView {
140258 let mut line = 0u16;
141259 let tab_width = doc.tab_width();
142260
261+ let highlights = highlights.into_iter().map(|event| match event.unwrap() {
262+ // convert byte offsets to char offset
263+ HighlightEvent::Source { start, end } => {
264+ let start = ensure_grapheme_boundary(text, text.byte_to_char(start));
265+ let end = ensure_grapheme_boundary(text, text.byte_to_char(end));
266+ HighlightEvent::Source { start, end }
267+ }
268+ event => event,
269+ });
270+
271+ let selections = doc.selection(view.id);
272+ let primary_idx = selections.primary_index();
273+
274+ let selection_scope = theme
275+ .find_scope_index("ui.selection")
276+ .expect("no selection scope found!");
277+
278+ let base_cursor_scope = theme
279+ .find_scope_index("ui.cursor")
280+ .unwrap_or(selection_scope);
281+
282+ let cursor_scope = match doc.mode() {
283+ Mode::Insert => theme.find_scope_index("ui.cursor.insert"),
284+ Mode::Select => theme.find_scope_index("ui.cursor.select"),
285+ Mode::Normal => Some(base_cursor_scope),
286+ }
287+ .unwrap_or(base_cursor_scope);
288+
289+ let primary_selection_scope = theme
290+ .find_scope_index("ui.selection.primary")
291+ .unwrap_or(selection_scope);
292+
293+ // TODO: primary + insert mode patching
294+ // let primary_cursor_style = theme
295+ // .try_get("ui.cursor.primary")
296+ // .map(|style| {
297+ // if mode != Mode::Normal {
298+ // // we want to make sure that the insert and select highlights
299+ // // also affect the primary cursor if set
300+ // style.patch(cursor_style)
301+ // } else {
302+ // style
303+ // }
304+ // })
305+ // .unwrap_or(cursor_style);
306+
307+ let primary_cursor_scope = theme
308+ .find_scope_index("ui.cursor.primary")
309+ .unwrap_or(cursor_scope);
310+
311+ let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
312+ // inject selections as highlight scopes
313+ let mut spans_: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
314+
315+ for (i, range) in selections.iter().enumerate() {
316+ let (cursor_scope, selection_scope) = if i == primary_idx {
317+ (primary_cursor_scope, primary_selection_scope)
318+ } else {
319+ (cursor_scope, selection_scope)
320+ };
321+
322+ if range.head == range.anchor {
323+ spans_.push((cursor_scope, range.head..range.head + 1));
324+ continue;
325+ }
326+
327+ let reverse = range.head < range.anchor;
328+
329+ if reverse {
330+ spans_.push((cursor_scope, range.head..range.head + 1));
331+ spans_.push((selection_scope, range.head + 1..range.anchor + 1));
332+ } else {
333+ spans_.push((selection_scope, range.anchor..range.head));
334+ spans_.push((cursor_scope, range.head..range.head + 1));
335+ }
336+ }
337+
338+ Box::new(merge(highlights, spans_))
339+ } else {
340+ Box::new(highlights)
341+ };
342+
143343 'outer: for event in highlights {
144− match event.unwrap() {
145− HighlightEvent::HighlightStart(mut span) => {
344+ match event {
345+ HighlightEvent::HighlightStart(span) => {
146346 spans.push(span);
147347 }
148348 HighlightEvent::HighlightEnd => {
@@ -150,29 +350,14 @@ impl EditorView {
150350 }
151351 HighlightEvent::Source { start, end } => {
152352 // TODO: filter out spans out of viewport for now..
153−
154− // TODO: do these before iterating
155− let start = ensure_grapheme_boundary(text, text.byte_to_char(start));
156− let end = ensure_grapheme_boundary(text, text.byte_to_char(end));
157−
158353 let text = text.slice(start..end);
159354
160355 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
161356
162− // TODO: scope matching: biggest union match? [string] & [html, string], [string, html] & [ string, html]
163− // can do this by sorting our theme matches based on array len (longest first) then stopping at the
164− // first rule that matches (rule.all(|scope| scopes.contains(scope)))
165− // log::info!(
166− // "scopes: {:?}",
167− // spans
168− // .iter()
169− // .map(|span| theme.scopes()[span.0].as_str())
170− // .collect::<Vec<_>>()
171− // );
172− let style = match spans.first() {
173− Some(span) => theme.get(theme.scopes()[span.0].as_str()),
174− None => theme.get("ui.text"),
175− };
357+ let style = spans.iter().fold(theme.get("ui.text"), |acc, span| {
358+ let style = theme.get(theme.scopes()[span.0].as_str());
359+ acc.patch(style)
360+ });
176361
177362 // TODO: we could render the text to a surface, then cache that, that
178363 // way if only the selection/cursor changes we can copy from cache
@@ -183,7 +368,19 @@ impl EditorView {
183368
184369 // iterate over range char by char
185370 for grapheme in RopeGraphemes::new(text) {
371+ let out_of_bounds = visual_x < view.first_col as u16
372+ || visual_x >= viewport.width + view.first_col as u16;
186373 if LineEnding::from_rope_slice(&grapheme).is_some() {
374+ if !out_of_bounds {
375+ // we still want to render an empty cell with the style
376+ surface.set_string(
377+ viewport.x + visual_x - view.first_col as u16,
378+ viewport.y + line,
379+ " ",
380+ style,
381+ );
382+ }
383+
187384 visual_x = 0;
188385 line += 1;
189386
@@ -192,11 +389,18 @@ impl EditorView {
192389 break 'outer;
193390 }
194391 } else if grapheme == "\t" {
392+ if !out_of_bounds {
393+ // we still want to render an empty cell with the style
394+ surface.set_string(
395+ viewport.x + visual_x - view.first_col as u16,
396+ viewport.y + line,
397+ " ".repeat(tab_width),
398+ style,
399+ );
400+ }
401+
195402 visual_x = visual_x.saturating_add(tab_width as u16);
196403 } else {
197− let out_of_bounds = visual_x < view.first_col as u16
198− || visual_x >= viewport.width + view.first_col as u16;
199−
200404 // Cow will prevent allocations if span contained in a single slice
201405 // which should really be the majority case
202406 let grapheme = Cow::from(grapheme);
@@ -283,126 +487,11 @@ impl EditorView {
283487 Range::new(start, end)
284488 };
285489
286− let mode = doc.mode();
287− let base_cursor_style = theme
288− .try_get("ui.cursor")
289− .unwrap_or_else(|| Style::default().add_modifier(Modifier::REVERSED));
290− let cursor_style = match mode {
291− Mode::Insert => theme.try_get("ui.cursor.insert"),
292− Mode::Select => theme.try_get("ui.cursor.select"),
293− Mode::Normal => Some(base_cursor_style),
294− }
295− .unwrap_or(base_cursor_style);
296− let primary_cursor_style = theme
297− .try_get("ui.cursor.primary")
298− .map(|style| {
299− if mode != Mode::Normal {
300− // we want to make sure that the insert and select highlights
301− // also affect the primary cursor if set
302− style.patch(cursor_style)
303− } else {
304− style
305− }
306− })
307− .unwrap_or(cursor_style);
308−
309− let selection_style = theme.get("ui.selection");
310− let primary_selection_style = theme
311− .try_get("ui.selection.primary")
312− .unwrap_or(selection_style);
313−
314490 let selection = doc.selection(view.id);
315− let primary_idx = selection.primary_index();
316−
317− for (i, selection) in selection
318− .iter()
319− .enumerate()
320− .filter(|(_, range)| range.overlaps(&screen))
321− {
322− // TODO: render also if only one of the ranges is in viewport
323− let mut start = view.screen_coords_at_pos(doc, text, selection.anchor);
324− let mut end = view.screen_coords_at_pos(doc, text, selection.head);
325−
326− let (cursor_style, selection_style) = if i == primary_idx {
327− (primary_cursor_style, primary_selection_style)
328− } else {
329− (cursor_style, selection_style)
330− };
331−
332− let head = end;
333491
334− if selection.head < selection.anchor {
335− std::mem::swap(&mut start, &mut end);
336− }
337− let start = start.unwrap_or_else(|| Position::new(0, 0));
338− let end = end.unwrap_or_else(|| {
339− Position::new(viewport.height as usize, viewport.width as usize)
340− });
341−
342− if start.row == end.row {
343− surface.set_style(
344− Rect::new(
345− viewport.x + start.col as u16,
346− viewport.y + start.row as u16,
347− // .min is important, because set_style does a
348− // for i in area.left()..area.right() and
349− // area.right = x + width !!! which shouldn't be > then surface.area.right()
350− // This is checked by a debug_assert! in Buffer::index_of
351− ((end.col - start.col) as u16 + 1).min(
352− surface
353− .area
354− .width
355− .saturating_sub(viewport.x + start.col as u16),
356− ),
357− 1,
358− ),
359− selection_style,
360− );
361− } else {
362− surface.set_style(
363− Rect::new(
364− viewport.x + start.col as u16,
365− viewport.y + start.row as u16,
366− // text.line(view.first_line).len_chars() as u16 - start.col as u16,
367− viewport.width.saturating_sub(start.col as u16),
368− 1,
369− ),
370− selection_style,
371− );
372− for i in start.row + 1..end.row {
373− surface.set_style(
374− Rect::new(
375− viewport.x,
376− viewport.y + i as u16,
377− // text.line(view.first_line + i).len_chars() as u16,
378− viewport.width,
379− 1,
380− ),
381− selection_style,
382− );
383− }
384− surface.set_style(
385− Rect::new(
386− viewport.x,
387− viewport.y + end.row as u16,
388− (end.col as u16).min(viewport.width),
389− 1,
390− ),
391− selection_style,
392− );
393− }
394−
395− // cursor
492+ for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
493+ let head = view.screen_coords_at_pos(doc, text, selection.head);
396494 if let Some(head) = head {
397− surface.set_style(
398− Rect::new(
399− viewport.x + head.col as u16,
400− viewport.y + head.row as u16,
401− 1,
402− 1,
403− ),
404− cursor_style,
405− );
406495 surface.set_stringn(
407496 viewport.x + 1 - OFFSET,
408497 viewport.y + head.row as u16,
@@ -410,6 +499,7 @@ impl EditorView {
410499 5,
411500 linenr_select,
412501 );
502+
413503 // TODO: set cursor position for IME
414504 if let Some(syntax) = doc.syntax() {
415505 use helix_core::match_brackets;