use std::cmp;
use std::borrow::Cow;
use std::path::Path;
use std::path::PathBuf;
use std::io::Write;
use std::fs::{File, rename};
use std::sync::{Mutex, Arc};
use tempdir::TempDir;
use unicode_width::UnicodeWidthChar;
use buffer::{Buffer, Mark};
use uibuf::{UIBuffer, CharColor};
use frontends::Frontend;
use overlay::{Overlay, OverlayType};
use utils;
use textobject::{Anchor, TextObject, Kind, Offset};
pub struct View {
pub buffer: Arc<Mutex<Buffer>>,
pub last_buffer: Option<Arc<Mutex<Buffer>>>,
pub overlay: Overlay,
top_line: Mark,
left_col: usize,
cursor: Mark,
uibuf: UIBuffer,
threshold: usize,
}
impl View {
pub fn new(buffer: Arc<Mutex<Buffer>>, width: usize, height: usize) -> View {
let cursor = Mark::Cursor(0);
let top_line = Mark::DisplayMark(0);
{
let mut b = buffer.lock().unwrap();
b.set_mark(cursor, 0);
b.set_mark(top_line, 0);
}
View {
buffer: buffer,
last_buffer: None,
top_line: top_line,
left_col: 0,
cursor: cursor,
uibuf: UIBuffer::new(width, height),
overlay: Overlay::None,
threshold: 5,
}
}
pub fn set_buffer(&mut self, buffer: Arc<Mutex<Buffer>>) {
self.last_buffer = Some(self.buffer.clone());
{
let mut b = buffer.lock().unwrap();
b.set_mark(self.cursor, 0);
b.set_mark(self.top_line, 0);
}
self.buffer = buffer;
}
pub fn switch_last_buffer(&mut self) {
let buffer = self.buffer.clone();
let last_buffer = match self.last_buffer.clone() {
Some(buf) => buf,
None => return
};
self.buffer = last_buffer;
self.last_buffer = Some(buffer);
}
pub fn get_height(&self) -> usize {
let status_bar_height = 1;
let uibuf_height = self.uibuf.get_height();
uibuf_height - status_bar_height
}
pub fn get_width(&self) -> usize {
self.uibuf.get_width()
}
pub fn resize(&mut self, width: usize, height: usize) {
let uibuf = UIBuffer::new(width, height);
self.uibuf = uibuf;
}
pub fn clear<T: Frontend>(&mut self, frontend: &mut T) {
self.uibuf.fill(' ');
self.uibuf.draw_everything(frontend);
}
pub fn draw<T: Frontend>(&mut self, frontend: &mut T) {
{
let buffer = self.buffer.lock().unwrap();
let height = self.get_height() - 1;
let mut lines = buffer.lines_from(self.top_line).unwrap().take(height);
for y_position in 0..height {
let line = lines.next().unwrap_or(vec![]);
draw_line(&mut self.uibuf, &line, y_position, self.left_col);
}
}
match self.overlay {
Overlay::None => self.draw_cursor(frontend),
_ => {
self.overlay.draw(frontend, &mut self.uibuf);
self.overlay.draw_cursor(frontend);
}
}
self.draw_status(frontend);
self.uibuf.draw_everything(frontend);
}
fn draw_status<T: Frontend>(&mut self, frontend: &mut T) {
let buffer = self.buffer.lock().unwrap();
let buffer_status = buffer.status_text();
let mut cursor_status = buffer.get_mark_coords(self.cursor).unwrap_or((0,0));
cursor_status = (cursor_status.0 + 1, cursor_status.1 + 1);
let status_text = format!("{} ({}, {})", buffer_status, cursor_status.0, cursor_status.1).into_bytes();
let status_text_len = status_text.len();
let width = self.get_width();
let height = self.get_height() - 1;
for index in 0..width {
let mut ch: char = ' ';
if index < status_text_len {
ch = status_text[index] as char;
}
self.uibuf.update_cell(index, height, ch, CharColor::Black, CharColor::Blue);
}
self.uibuf.draw_range(frontend, height, height+1);
}
fn draw_cursor<T: Frontend>(&mut self, frontend: &mut T) {
let buffer = self.buffer.lock().unwrap();
if let Some(top_line) = buffer.get_mark_coords(self.top_line) {
if let Some((x, y)) = buffer.get_mark_coords(self.cursor) {
frontend.draw_cursor((x - self.left_col) as isize, y as isize - top_line.1 as isize);
}
}
}
pub fn set_overlay(&mut self, overlay_type: OverlayType) {
match overlay_type {
OverlayType::Prompt => {
self.overlay = Overlay::Prompt {
cursor_x: 1,
prefix: ":",
data: String::new(),
};
}
OverlayType::SelectFile => {
let prefix = "Enter file path:";
self.overlay = Overlay::SelectFile {
cursor_x: prefix.len(),
prefix: prefix,
data: String::new(),
};
}
_ => {}
}
}
pub fn move_mark(&mut self, mark: Mark, object: TextObject) {
self.buffer.lock().unwrap().set_mark_to_object(mark, object);
self.maybe_move_screen();
}
fn maybe_move_screen(&mut self) {
let mut buffer = self.buffer.lock().unwrap();
if let (Some(cursor), Some((_, top_line))) = (buffer.get_mark_coords(self.cursor),
buffer.get_mark_coords(self.top_line)) {
let width = (self.get_width() - self.threshold) as isize;
let height = (self.get_height() - self.threshold) as isize;
self.left_col = match cursor.0 as isize - self.left_col as isize {
x_offset if x_offset < self.threshold as isize => {
cmp::max(0, self.left_col as isize - (self.threshold as isize - x_offset)) as usize
}
x_offset if x_offset >= width => {
self.left_col + (x_offset - width + 1) as usize
}
_ => { self.left_col }
};
match cursor.1 as isize - top_line as isize {
y_offset if y_offset < self.threshold as isize && top_line > 0 => {
let amount = (self.threshold as isize - y_offset) as usize;
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Backward(amount, self.top_line)
};
buffer.set_mark_to_object(self.top_line, obj);
}
y_offset if y_offset >= height => {
let amount = (y_offset - height + 1) as usize;
let obj = TextObject {
kind: Kind::Line(Anchor::Same),
offset: Offset::Forward(amount, self.top_line)
};
buffer.set_mark_to_object(self.top_line, obj);
}
_ => { }
}
}
}
pub fn delete_object(&mut self, object: TextObject) {
self.buffer.lock().unwrap().remove_object(object);
}
pub fn delete_from_mark_to_object(&mut self, mark: Mark, object: TextObject) {
let mut buffer = self.buffer.lock().unwrap();
if let Some((idx, _)) = buffer.get_object_index(object) {
if let Some(midx) = buffer.get_mark_idx(mark) {
buffer.remove_from_mark_to_object(mark, object);
buffer.set_mark(mark, cmp::min(idx, midx));
}
}
}
pub fn insert_char(&mut self, ch: char) {
self.buffer.lock().unwrap().insert_char(self.cursor, ch as u8);
if let Some(ch_width) = utils::char_width(ch, false, 4, 1) {
let obj = TextObject {
kind: Kind::Char,
offset: Offset::Forward(ch_width, Mark::Cursor(0))
};
self.move_mark(Mark::Cursor(0), obj)
}
}
pub fn undo(&mut self) {
{
let mut buffer = self.buffer.lock().unwrap();
let point = if let Some(transaction) = buffer.undo() { transaction.end_point }
else { return; };
buffer.set_mark(self.cursor, point);
}
self.maybe_move_screen();
}
pub fn redo(&mut self) {
{
let mut buffer = self.buffer.lock().unwrap();
let point = if let Some(transaction) = buffer.redo() { transaction.end_point }
else { return; };
buffer.set_mark(self.cursor, point);
}
self.maybe_move_screen();
}
fn save_buffer(&mut self) {
let buffer = self.buffer.lock().unwrap();
let path = match buffer.file_path {
Some(ref p) => Cow::Borrowed(p),
None => {
Cow::Owned(PathBuf::from("untitled"))
},
};
let tmpdir = match TempDir::new_in(&Path::new("."), "iota") {
Ok(d) => d,
Err(e) => panic!("file error: {}", e)
};
let tmppath = tmpdir.path().join(Path::new("tmpfile"));
let mut file = match File::create(&tmppath) {
Ok(f) => f,
Err(e) => {
panic!("file error: {}", e)
}
};
for line in buffer.lines() {
let result = file.write_all(&*line);
if result.is_err() {
panic!("Something went wrong while writing the file");
}
}
if let Err(e) = rename(&tmppath, &*path) {
panic!("file error: {}", e);
}
}
pub fn try_save_buffer(&mut self) {
let mut should_save = false;
{
let buffer = self.buffer.lock().unwrap();
match buffer.file_path {
Some(_) => { should_save = true }
None => {
let prefix = "Enter file name: ";
self.overlay = Overlay::SavePrompt {
cursor_x: prefix.len(),
prefix: prefix,
data: String::new(),
};
},
}
}
if should_save { self.save_buffer() }
}
}
pub fn draw_line(buf: &mut UIBuffer, line: &[u8], idx: usize, left: usize) {
let width = buf.get_width() - 1;
let mut x = 0;
for ch in line.iter().skip(left) {
let ch = *ch as char;
match ch {
'\t' => {
let w = 4 - x % 4;
for _ in 0..w {
buf.update_cell_content(x, idx, ' ');
x += 1;
}
}
'\n' => {}
_ => {
buf.update_cell_content(x, idx, ch);
x += UnicodeWidthChar::width(ch).unwrap_or(1);
}
}
if x >= width {
break;
}
}
while x < width {
buf.update_cell_content(x, idx, ' ');
x += 1;
}
let indicator = if line.len() > width + left { '→' } else { ' ' };
buf.update_cell_content(width, idx, indicator);
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use view::View;
use input::Input;
use buffer::Buffer;
fn setup_view(testcase: &'static str) -> View {
let mut buffer = Arc::new(Mutex::new(Buffer::new()));
let mut view = View::new(buffer.clone(), 50, 50);
for ch in testcase.chars() {
view.insert_char(ch);
}
let mut buffer = buffer.lock().unwrap();
buffer.set_mark(view.cursor, 0);
view
}
#[test]
fn test_insert_char() {
let mut view = setup_view("test\nsecond");
view.insert_char('t');
{
let mut buffer = view.buffer.lock().unwrap();
assert_eq!(buffer.lines().next().unwrap(), b"ttest\n");
}
}
}