use std::hash::{Hash, Hasher};
use crate::widgets::{
code_widget::foundation::CodeLanguage,
document_viewer::{
CacheState, DisplaySettings, DocumentOutlineItem, RenderedDocument, ScrollState,
SelectionState, SourceState, VimState,
},
};
#[derive(Debug, Clone, Default)]
pub struct CodeState {
pub source: SourceState,
pub scroll: ScrollState,
pub selection: SelectionState,
pub vim: VimState,
pub display: DisplaySettings,
pub cache: CacheState,
pub outline: Vec<DocumentOutlineItem>,
pub outline_hovered: bool,
pub outline_hovered_entry: Option<usize>,
pub language_override: Option<String>,
pub rendered_document: Option<RenderedDocument>,
}
impl CodeState {
pub fn set_language_override(&mut self, language: impl Into<String>) {
self.language_override = Some(language.into());
}
pub fn selected_text(&self) -> Option<String> {
let range = self.selection.selected_range()?;
let lines: Vec<&str> = self.source.content().lines().collect();
let selected: Vec<&str> = range
.filter_map(|index| lines.get(index).copied())
.collect();
if selected.is_empty() {
None
} else {
Some(selected.join("\n"))
}
}
pub fn copy_selection(&mut self) -> Option<String> {
let text = self.selected_text()?;
self.selection.last_copied_text = Some(text.clone());
Some(text)
}
pub fn language_override(&self) -> Option<&str> {
self.language_override.as_deref()
}
pub fn render_cache_hash(
content: &str,
display: &DisplaySettings,
language: &CodeLanguage,
) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
content.hash(&mut hasher);
language.hash(&mut hasher);
display.show_line_numbers.hash(&mut hasher);
display.relative_line_numbers.hash(&mut hasher);
display.highlight_current_line.hash(&mut hasher);
display.show_outline.hash(&mut hasher);
display.tab_width.hash(&mut hasher);
hasher.finish()
}
pub fn remember_render_cache(
&mut self,
content: &str,
display: &DisplaySettings,
language: &CodeLanguage,
) {
self.cache
.set_content_hash(Self::render_cache_hash(content, display, language));
}
pub fn cached_rendered_document(&self, cache_hash: u64) -> Option<&RenderedDocument> {
if self.cache.content_hash == cache_hash {
self.rendered_document.as_ref()
} else {
None
}
}
pub fn store_rendered_document(&mut self, cache_hash: u64, document: RenderedDocument) {
self.cache.set_content_hash(cache_hash);
self.rendered_document = Some(document);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cache_hash_changes_with_content() {
let mut state = CodeState::default();
let display = DisplaySettings::default();
state.remember_render_cache("one", &display, &CodeLanguage::Rust);
let first = state.cache.content_hash;
state.remember_render_cache("two", &display, &CodeLanguage::Rust);
assert_ne!(first, state.cache.content_hash);
}
#[test]
fn cache_hash_changes_with_display() {
let mut state = CodeState::default();
let mut display = DisplaySettings::default();
state.remember_render_cache("one", &display, &CodeLanguage::Rust);
let first = state.cache.content_hash;
display.show_outline = !display.show_outline;
state.remember_render_cache("one", &display, &CodeLanguage::Rust);
assert_ne!(first, state.cache.content_hash);
}
#[test]
fn cached_document_requires_matching_hash() {
let mut state = CodeState::default();
let display = DisplaySettings::default();
let cache_hash = CodeState::render_cache_hash("one", &display, &CodeLanguage::Rust);
state.store_rendered_document(cache_hash, RenderedDocument::default());
assert!(state.cached_rendered_document(cache_hash).is_some());
assert!(state.cached_rendered_document(cache_hash + 1).is_none());
}
}