gpui-component/crates/ui/src/input/code_highlighter.rs

77 lines
2.4 KiB
Rust

use std::{collections::HashMap, ops::Range, rc::Rc};
use gpui::{App, HighlightStyle, SharedString, TextRun, TextStyle};
use crate::highlighter::Highlighter;
#[derive(Debug, Clone)]
pub(crate) struct LineHighlightStyle {
pub(crate) styles: Rc<Vec<(Range<usize>, HighlightStyle)>>,
}
impl LineHighlightStyle {
pub(super) fn to_run(&self, text_style: &TextStyle) -> Vec<TextRun> {
self.styles
.iter()
.map(|(range, style)| text_style.clone().highlight(*style).to_run(range.len()))
// Add last `\n` Run with len 1
.chain(std::iter::once(text_style.clone().to_run(1)))
.collect()
}
}
#[derive(Clone)]
pub(super) struct CodeHighlighter {
pub(super) highlighter: Rc<Highlighter<'static>>,
pub(super) text: SharedString,
/// The lines by split \n
pub(super) lines: Vec<LineHighlightStyle>,
pub(super) cache: HashMap<u64, LineHighlightStyle>,
}
impl CodeHighlighter {
pub(super) fn new(highlighter: Rc<Highlighter<'static>>) -> Self {
Self {
highlighter,
text: SharedString::default(),
lines: vec![],
cache: HashMap::new(),
}
}
pub fn set_highlighter(&mut self, highlighter: Rc<Highlighter<'static>>, cx: &mut App) {
self.highlighter = highlighter;
self.lines.clear();
self.cache.clear();
self.update(self.text.clone(), true, cx);
}
pub fn update(&mut self, text: SharedString, force: bool, _: &mut App) {
if self.text == text && !force {
return;
}
let mut lines = vec![];
let mut new_cache = HashMap::new();
for line in text.split('\n') {
let cache_key = gpui::hash(&line);
// cache hit
if let Some(line_style) = self.cache.get(&cache_key) {
new_cache.insert(cache_key, line_style.clone());
lines.push(line_style.clone());
} else {
// cache miss
let styles = Rc::new(self.highlighter.highlight(line));
let line_style = LineHighlightStyle { styles };
new_cache.insert(cache_key, line_style.clone());
lines.push(line_style);
}
}
// Ensure to recreate cache to remove unused caches.
self.cache = new_cache;
self.lines = lines;
self.text = text;
}
}