mod buffer;
pub mod color;
mod data;
mod event_listener;
mod presenter;
mod style;
pub mod terminal;
mod theme_loader;
pub use self::buffer::{LexemeMapper, MappedLexeme};
pub use self::color::{Colors, RGBColor};
pub use self::data::StatusLineData;
pub use self::presenter::Presenter;
pub use self::style::Style;
pub use self::terminal::*;
use self::buffer::ScrollableRegion;
use self::buffer::{RenderCache, RenderState};
use self::event_listener::EventListener;
use self::theme_loader::ThemeLoader;
use crate::errors::*;
use crate::input::Key;
use crate::models::application::{Event, Preferences};
use scribe::buffer::Buffer;
use std::cell::RefCell;
use std::cmp;
use std::collections::HashMap;
use std::ops::Drop;
use std::rc::Rc;
use std::sync::mpsc::{self, Sender, SyncSender};
use std::sync::Arc;
use syntect::highlighting::ThemeSet;
const RENDER_CACHE_FREQUENCY: usize = 100;
pub struct View {
terminal: Arc<Box<dyn Terminal + Sync + Send + 'static>>,
scrollable_regions: HashMap<usize, ScrollableRegion>,
render_caches: HashMap<usize, Rc<RefCell<HashMap<usize, RenderState>>>>,
pub theme_set: ThemeSet,
preferences: Rc<RefCell<Preferences>>,
pub last_key: Option<Key>,
event_channel: Sender<Event>,
event_listener_killswitch: SyncSender<()>,
}
impl View {
pub fn new(
preferences: Rc<RefCell<Preferences>>,
event_channel: Sender<Event>,
) -> Result<View> {
let terminal = build_terminal().chain_err(|| "Failed to initialize terminal")?;
let theme_path = preferences.borrow().theme_path()?;
let theme_set = ThemeLoader::new(theme_path).load()?;
let (killswitch_tx, killswitch_rx) = mpsc::sync_channel(0);
EventListener::start(terminal.clone(), event_channel.clone(), killswitch_rx);
Ok(View {
terminal,
last_key: None,
preferences,
scrollable_regions: HashMap::new(),
render_caches: HashMap::new(),
theme_set,
event_channel,
event_listener_killswitch: killswitch_tx,
})
}
pub fn build_presenter(&mut self) -> Result<Presenter<'_>> {
Presenter::new(self)
}
pub fn scroll_to_cursor(&mut self, buffer: &Buffer) -> Result<()> {
self.get_region(buffer)?.scroll_into_view(buffer);
Ok(())
}
pub fn scroll_to_center(&mut self, buffer: &Buffer) -> Result<()> {
self.get_region(buffer)?.scroll_to_center(buffer);
Ok(())
}
pub fn scroll_up(&mut self, buffer: &Buffer, amount: usize) -> Result<()> {
self.get_region(buffer)?.scroll_up(amount);
Ok(())
}
pub fn scroll_down(&mut self, buffer: &Buffer, amount: usize) -> Result<()> {
let current_offset = self.get_region(buffer)?.line_offset();
let line_count = buffer.line_count();
let half_screen_height = self.terminal.height() / 2;
let max = if line_count > half_screen_height {
let visible_line_count = line_count.saturating_sub(current_offset);
visible_line_count.saturating_sub(half_screen_height)
} else {
0
};
self.get_region(buffer)?.scroll_down(cmp::min(amount, max));
Ok(())
}
pub fn forget_buffer(&mut self, buffer: &Buffer) -> Result<()> {
self.scrollable_regions.remove(&buffer_key(buffer)?);
self.render_caches.remove(&buffer_key(buffer)?);
Ok(())
}
fn get_region(&mut self, buffer: &Buffer) -> Result<&mut ScrollableRegion> {
Ok(self
.scrollable_regions
.entry(buffer_key(buffer)?)
.or_insert(ScrollableRegion::new(self.terminal.clone())))
}
fn get_render_cache(
&self,
buffer: &Buffer,
) -> Result<&Rc<RefCell<HashMap<usize, RenderState>>>> {
let cache = self
.render_caches
.get(&buffer_key(buffer)?)
.ok_or("Buffer not properly initialized (render cache not present).")?;
Ok(cache)
}
pub fn suspend(&mut self) {
let _ = self.event_listener_killswitch.send(());
self.terminal.suspend();
let (killswitch_tx, killswitch_rx) = mpsc::sync_channel(0);
EventListener::start(
self.terminal.clone(),
self.event_channel.clone(),
killswitch_rx,
);
self.event_listener_killswitch = killswitch_tx;
}
pub fn last_key(&self) -> &Option<Key> {
&self.last_key
}
pub fn initialize_buffer(&mut self, buffer: &mut Buffer) -> Result<()> {
let render_cache = Rc::new(RefCell::new(HashMap::new()));
self.render_caches
.insert(buffer_key(buffer)?, render_cache.clone());
buffer.change_callback = Some(Box::new(move |change_position| {
render_cache
.borrow_mut()
.invalidate_from(change_position.line);
}));
Ok(())
}
}
impl Drop for View {
fn drop(&mut self) {
let _ = self.event_listener_killswitch.send(());
}
}
fn buffer_key(buffer: &Buffer) -> Result<usize> {
buffer
.id
.ok_or_else(|| Error::from("Buffer ID doesn't exist"))
}
#[cfg(test)]
mod tests {
use super::View;
use crate::models::application::Preferences;
use crate::view::buffer::RenderState;
use scribe::buffer::Position;
use scribe::{Buffer, Workspace};
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::mpsc;
use syntect::highlighting::{Highlighter, ThemeSet};
#[test]
fn scroll_down_prevents_scrolling_completely_beyond_buffer() {
let preferences = Rc::new(RefCell::new(Preferences::new(None)));
let (tx, _) = mpsc::channel();
let mut view = View::new(preferences, tx).unwrap();
let mut buffer = Buffer::new();
buffer.id = Some(0);
buffer.insert("\n\n\n\n\n\n\n\n\n");
view.scroll_down(&buffer, 3).unwrap();
assert_eq!(view.get_region(&buffer).unwrap().line_offset(), 3);
view.scroll_down(&buffer, 20).unwrap();
assert_eq!(view.get_region(&buffer).unwrap().line_offset(), 5);
}
#[test]
fn scroll_down_prevents_scrolling_when_buffer_is_smaller_than_top_half() {
let preferences = Rc::new(RefCell::new(Preferences::new(None)));
let (tx, _) = mpsc::channel();
let mut view = View::new(preferences, tx).unwrap();
let mut buffer = Buffer::new();
buffer.id = Some(0);
buffer.insert("\n");
view.scroll_down(&buffer, 20).unwrap();
assert_eq!(view.get_region(&buffer).unwrap().line_offset(), 0);
}
#[test]
fn initialize_buffer_creates_render_cache_for_buffer() {
let preferences = Rc::new(RefCell::new(Preferences::new(None)));
let (tx, _) = mpsc::channel();
let mut view = View::new(preferences, tx).unwrap();
let mut buffer = Buffer::new();
buffer.id = Some(1);
assert!(view.render_caches.get(&buffer.id.unwrap()).is_none());
view.initialize_buffer(&mut buffer).unwrap();
assert!(view.render_caches.get(&buffer.id.unwrap()).is_some());
}
#[test]
fn initialize_buffer_sets_change_callback_to_clear_render_cache() {
let preferences = Rc::new(RefCell::new(Preferences::new(None)));
let (tx, _) = mpsc::channel();
let mut view = View::new(preferences, tx).unwrap();
let mut workspace = Workspace::new(Path::new("."), None).unwrap();
let mut buf = Buffer::new();
buf.path = Some(PathBuf::from("rust.rs"));
workspace.add_buffer(buf);
let mut buffer = workspace.current_buffer.as_mut().unwrap();
for _ in 0..200 {
buffer.insert("line\n");
}
view.initialize_buffer(&mut buffer).unwrap();
let theme_set = ThemeSet::load_defaults();
let highlighter = Highlighter::new(&theme_set.themes["base16-ocean.dark"]);
let render_state =
RenderState::new(&highlighter, buffer.syntax_definition.as_ref().unwrap());
view.render_caches
.get(&buffer.id.unwrap())
.unwrap()
.borrow_mut()
.insert(0, render_state.clone());
view.render_caches
.get(&buffer.id.unwrap())
.unwrap()
.borrow_mut()
.insert(100, render_state.clone());
view.render_caches
.get(&buffer.id.unwrap())
.unwrap()
.borrow_mut()
.insert(200, render_state.clone());
buffer.cursor.move_to(Position {
line: 99,
offset: 0,
});
buffer.insert("\n");
assert_eq!(
view.render_caches
.get(&buffer.id.unwrap())
.unwrap()
.borrow()
.keys()
.collect::<Vec<&usize>>(),
vec![&0]
);
}
}