fix and cache syntax highlighting state
3 files changed, 116 insertions(+), 15 deletions(-)

M cursive/plugins/syntect/src/highlighting.rs
M cursive/src/highlight/mod.rs => cursive/src/highlight.rs
M cursive/src/views/editor.rs
M cursive/plugins/syntect/src/highlighting.rs +17 -0
@@ 124,6 124,7 @@ impl highlight::Highlighting for Highlig
     }
 }
 
+#[derive(Clone)]
 pub struct Highlight {
     state: (ParseState, SyntectHighlightState),
     theme: Signal<SyntectTheme>

          
@@ 167,6 168,22 @@ impl highlight::Highlight for Highlight 
             .collect()
         )
     }
+
+    fn parse(&mut self, line: &str) {
+        let state = &mut self.state;
+        self.theme.sample_with(|theme|
+            HighlightIterator::new(
+                &mut state.1,
+                state.0.parse_line(line).as_slice(),
+                line,
+                &SyntectHighlighter::new(&theme)
+            ).count()
+        );
+    }
+
+    fn clone_dyn(&self) -> Box<dyn highlight::Highlight> {
+        Box::new(self.clone())
+    }
 }
 
 fn mod_color_back(target: &Option<ColorStyle>, back: &SyntectColor, palette: &Palette) -> ColorStyle {

          
M cursive/src/highlight/mod.rs => cursive/src/highlight.rs +20 -3
@@ 1,5 1,10 @@ 
-use cursive::utils::markup::StyledIndexedSpan;
-use cursive::theme::{Style, Effect};
+use cursive::{
+    utils::markup::StyledIndexedSpan,
+    theme::{
+        Style,
+        Effect
+    }
+};
 
 pub trait Highlighting {
     fn start(&self) -> Box<dyn Highlight>;

          
@@ 11,10 16,16 @@ pub trait Highlighting {
     fn cursor_line(&self, _: &mut Style) {}
 }
 
-/// *Stateful*: highlights lines based on its state.
+/// **Stateful**: highlights lines based on its state.
 pub trait Highlight {
     /// Highlight the next line.
     fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan>;
+
+    /// Only parses the next line to advance internal state.
+    /// This is equivalent to `highlight()` without output.
+    fn parse(&mut self, line: &str);
+
+    fn clone_dyn(&self) -> Box<dyn Highlight>;
 }
 
 impl Highlighting for () {

          
@@ 27,4 38,10 @@ impl Highlight for () {
     fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan> {
         vec![StyledIndexedSpan::simple(line, Style::none())]
     }
+
+    fn parse(&mut self, _: &str) {}
+
+    fn clone_dyn(&self) -> Box<dyn Highlight> {
+        Box::new(())
+    }
 }

          
M cursive/src/views/editor.rs +79 -12
@@ 1,4 1,3 @@ 
-use frappe::{Sink, Stream, Signal};
 use super::unicode_width::UnicodeWidthStr;
 use {
     frappe::{

          
@@ 9,11 8,18 @@ use {
     std::{
         fmt,
         ops::Range,
+        cmp::{
+            Ord,
+            Ordering
+        },
         time::{
             Instant,
             Duration
         },
-        collections::BTreeSet
+        collections::{
+            BTreeSet,
+            BinaryHeap
+        }
     },
     cursive::{
         Printer,

          
@@ 58,7 64,10 @@ use {
         },
         command::CursorCmd
     },
-    highlight::Highlighting
+    highlight::{
+        Highlighting,
+        Highlight
+    }
 };
 
 /// An open buffer with a cursor and history.

          
@@ 81,6 90,7 @@ pub struct EditorView {
     // rendering
     lines: LineCache<String>,
     spans: LineCache<Vec<StyledIndexedSpan>>,
+    highlight_heap: BinaryHeap<LineHighlightState>,
 
     lines_to_highlight: BTreeSet<usize>,
     /// Draw this instead of `spans` if a cursor is on the line.

          
@@ 98,6 108,31 @@ pub struct EditorView {
 
 type KeyMapper = TrieState<Event, Cmd>;
 
+struct LineHighlightState {
+    line_idx: usize,
+    highlight: Box<dyn Highlight>
+}
+
+impl PartialEq for LineHighlightState {
+    fn eq(&self, other: &Self) -> bool {
+        self.line_idx == other.line_idx
+    }
+}
+
+impl Eq for LineHighlightState {}
+
+impl PartialOrd for LineHighlightState {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.line_idx.partial_cmp(&other.line_idx)
+    }
+}
+
+impl Ord for LineHighlightState {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.line_idx.cmp(&other.line_idx)
+    }
+}
+
 #[derive(Clone)]
 pub enum Cmd {
     Cur(CursorCmd),

          
@@ 231,6 266,7 @@ impl Builder {
 
             lines: LineCache::new(),
             spans: LineCache::new(),
+            highlight_heap: BinaryHeap::new(),
             lines_to_highlight: BTreeSet::new(),
             line_highlight: {
                 let mut style = ColorStyle::primary().into();

          
@@ 358,15 394,40 @@ impl View for EditorView {
             }
         }
 
-        // Cache the line styles.
-        let mut highlighter = self.highlighting.start(); // FIXME missing context from lines before `line_idc`
-        for line_idx in line_idc.clone() {
-            if let None = self.spans.get(line_idx) {
-                let line = self.lines.get(line_idx).expect("line not allocated").as_str();
-                self.spans.cache(line_idx,
-                    // highlight syntax
-                    highlighter.highlight(line)
-                );
+        { // Cache the line styles.
+            let mut current_highlight = None;
+            for line_idx in line_idc.clone() {
+                if let None = self.spans.get(line_idx) {
+                    let mut highlight = current_highlight.take()
+                        .unwrap_or_else(|| {
+                            let (start_line_idx, mut highlight) = self.highlight_heap.peek()
+                                .map(|state| (state.line_idx + 1, state.highlight.clone_dyn()))
+                                .unwrap_or_else(|| (0, self.highlighting.start()));
+
+                            for line_idx in start_line_idx..line_idx {
+                                highlight.parse(
+                                    self.lines.get(line_idx).expect("line not allocated").as_str()
+                                );
+                            }
+
+                            highlight
+                        });
+
+                    let line = self.lines.get(line_idx).expect("line not allocated").as_str();
+                    self.spans.cache(line_idx,
+                        // highlight syntax
+                        highlight.highlight(line)
+                    );
+
+                    current_highlight = Some(highlight);
+                }
+            }
+
+            if let Some(highlight) = current_highlight {
+                self.highlight_heap.push(LineHighlightState {
+                    line_idx: line_idc.end - 1,
+                    highlight
+                });
             }
         }
 

          
@@ 844,6 905,12 @@ impl View for EditorView {
             self.lines          .invalidate_lines_from(first_damage..);
             self.spans          .invalidate_lines_from(first_damage..);
             self.spans_highlight.invalidate_lines_from(first_damage..);
+            while self.highlight_heap.peek()
+                .map(|x| x.line_idx >= first_damage)
+                .unwrap_or(false)
+            {
+                self.highlight_heap.pop();
+            }
         }
 
         if tx_buf.has_changed() {