Commit c7a37e88 authored by ticki's avatar ticki

Merge pull request #40 from wesleywiser/multiple_buffers

[WIP] Multiple buffers
parents 61f820a5 9533230b
......@@ -124,9 +124,12 @@ Following commands are valid:
- unset <option> : Unset <option>
- toggle <option> : Toggle <option>
- get <option> : Get the state of <option>
- o <filename> : Open <filename>
- o <filename> : Open <filename> in a new buffer
- help : Open this guide.
- w <filename> : Write the buffer to <filename>
- ls : List the available buffers
- b<numeral> : Switch to buffer <numeral>
- bd : Delete the current buffer
- q : Quit Sodium.
Following option exists:
......
use caret::position::to_signed_pos;
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
use io::parse::Inst;
use state::editor::Editor;
......@@ -22,8 +22,8 @@ impl Editor {
Char('j') => Some(self.down(n.d())),
Char('k') => Some(self.up(n.d())),
Char('g') => Some((0, n.or(1) - 1)),
Char('G') => Some((0, self.buffer.len() - 1)),
Char('L') => Some((self.buffer[y].len() - 1, y)),
Char('G') => Some((0, self.buffers.current_buffer().len() - 1)),
Char('L') => Some((self.buffers.current_buffer()[y].len() - 1, y)),
Char('H') => Some((0, y)),
Char('t') => {
......@@ -71,8 +71,8 @@ impl Editor {
Char('j') => Some(self.down_unbounded(n.d())),
Char('k') => Some(self.up_unbounded(n.d())),
Char('g') => Some((0, n.or(1) as isize - 1)),
Char('G') => Some((0, self.buffer.len() as isize - 1)),
Char('L') => Some(to_signed_pos((x, self.buffer[y].len()))),
Char('G') => Some((0, self.buffers.current_buffer().len() as isize - 1)),
Char('L') => Some(to_signed_pos((x, self.buffers.current_buffer()[y].len()))),
Char('H') => Some((0, y as isize)),
Char('t') => {
......
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
use state::editor::Editor;
impl Editor {
......@@ -26,22 +26,22 @@ impl Editor {
pub fn after(&self, n: usize, (x, y): (usize, usize)) -> Option<(usize, usize)> {
// TODO: Make this more idiomatic {
if x + n < self.buffer[y].len() {
if x + n < self.buffers.current_buffer()[y].len() {
Some((x + n, y))
} else {
if y + 1 >= self.buffer.len() {
if y + 1 >= self.buffers.current_buffer().len() {
None
} else {
let mut mv = n + x - self.buffer[y].len();
let mut mv = n + x - self.buffers.current_buffer()[y].len();
let mut ry = y + 1;
loop {
if mv < self.buffer[ry].len() {
if mv < self.buffers.current_buffer()[ry].len() {
return Some((mv, ry));
} else {
if ry + 1 < self.buffer.len() {
mv -= self.buffer[ry].len();
if ry + 1 < self.buffers.current_buffer().len() {
mv -= self.buffers.current_buffer()[ry].len();
ry += 1;
} else {
return None;
......@@ -68,11 +68,11 @@ impl Editor {
let mut ry = y - 1;
loop {
if mv <= self.buffer[ry].len() {
return Some((self.buffer[ry].len() - mv, ry));
if mv <= self.buffers.current_buffer()[ry].len() {
return Some((self.buffers.current_buffer()[ry].len() - mv, ry));
} else {
if ry > 0 && mv >= self.buffer[ry].len() {
mv -= self.buffer[ry].len();
if ry > 0 && mv >= self.buffers.current_buffer()[ry].len() {
mv -= self.buffers.current_buffer()[ry].len();
ry -= 1;
} else if ry == 0 {
return None;
......@@ -142,7 +142,7 @@ impl Editor {
let mut dn = 0;
let x = self.x();
for ch in self.buffer[self.y()].chars().skip(x) {
for ch in self.buffers.current_buffer()[self.y()].chars().skip(x) {
if dn == n {
if ch == c {
dn += 1;
......@@ -162,7 +162,7 @@ impl Editor {
let x = self.x();
let y = self.y();
for ch in self.buffer[y].chars().rev().skip(self.buffer[y].len() - x) {
for ch in self.buffers.current_buffer()[y].chars().rev().skip(self.buffers.current_buffer()[y].len() - x) {
if dn == n {
if ch == c {
dn += 1;
......
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
use state::editor::Editor;
/// Convert a usize tuple to isize
......@@ -31,13 +31,13 @@ impl Editor {
pub fn bound(&self, (x, mut y): (usize, usize), tight: bool) -> (usize, usize) {
y = if y >= self.buffer.len() {
self.buffer.len() - 1
y = if y >= self.buffers.current_buffer().len() {
self.buffers.current_buffer().len() - 1
} else {
y
};
let ln = self.buffer[y].len() + if tight {0} else {1};
let ln = self.buffers.current_buffer()[y].len() + if tight {0} else {1};
if x >= ln {
if ln == 0 {
(0, y)
......@@ -61,8 +61,8 @@ impl Editor {
pub fn bound_ver(&self, (x, mut y): (usize, usize)) -> (usize, usize) {
// Is this premature optimization? Yes, yes it is!
y = if y > self.buffer.len() - 1 {
self.buffer.len() - 1
y = if y > self.buffers.current_buffer().len() - 1 {
self.buffers.current_buffer().len() - 1
} else {
y
};
......
......@@ -3,7 +3,7 @@ use io::parse::{Inst, Parameter};
use state::mode::{Mode, CommandMode, PrimitiveMode};
use edit::insert::{InsertOptions, InsertMode};
use io::redraw::RedrawTask;
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
// TODO: Move the command definitions outta here
impl Editor {
......@@ -53,12 +53,12 @@ impl Editor {
(Command(Normal), Char('o')) => {
let y = self.y();
let ind = if self.options.autoindent {
self.buffer.get_indent(y).to_owned()
self.buffers.current_buffer().get_indent(y).to_owned()
} else {
String::new()
};
let last = ind.len();
self.buffer.insert_line(y, ind.into());
self.buffers.current_buffer_mut().insert_line(y, ind.into());
self.goto((last, y + 1));
self.cursor_mut().mode =
Mode::Primitive(PrimitiveMode::Insert(InsertOptions {
......@@ -106,7 +106,7 @@ impl Editor {
self.goto(bounded);
}
(Command(Normal), Char('L')) => {
let ln_end = (self.buffer[self.y()].len(), self.y());
let ln_end = (self.buffers.current_buffer()[self.y()].len(), self.y());
self.goto(ln_end);
mov = true;
}
......@@ -117,12 +117,13 @@ impl Editor {
(Command(Normal), Char('r')) => {
let (x, y) = self.pos();
let c = self.get_char();
let current_buffer = self.buffers.current_buffer_info_mut();
// If there is nothing in the current buffer
// ignore the command
if self.buffer[y].len() > 0 {
self.buffer[y].remove(x);
if current_buffer.raw_buffer[y].len() > 0 {
current_buffer.raw_buffer[y].remove(x);
}
self.buffer[y].insert(x, c);
current_buffer.raw_buffer[y].insert(x, c);
}
(Command(Normal), Char('R')) => {
self.cursor_mut().mode =
......@@ -137,7 +138,7 @@ impl Editor {
}
}
(Command(Normal), Char('G')) => {
let last = self.buffer.len() - 1;
let last = self.buffers.current_buffer().len() - 1;
self.goto((0, last));
mov = true;
}
......@@ -156,9 +157,10 @@ impl Editor {
}
(Command(Normal), Char('b')) => {
// Branch cursor
if self.cursors.len() < 255 {
if self.buffers.current_buffer_info().cursors.len() < 255 {
let cursor = self.cursor().clone();
self.cursors.insert(self.current_cursor as usize, cursor);
let current_cursor_index = self.buffers.current_buffer_info().current_cursor as usize;
self.buffers.current_buffer_info_mut().cursors.insert(current_cursor_index, cursor);
self.next_cursor();
}
else {
......@@ -167,8 +169,9 @@ impl Editor {
}
(Command(Normal), Char('B')) => {
// Delete cursor
if self.cursors.len() > 1 {
self.cursors.remove(self.current_cursor as usize);
if self.buffers.current_buffer_info().cursors.len() > 1 {
let current_cursor_index = self.buffers.current_buffer_info().current_cursor;
self.buffers.current_buffer_info_mut().cursors.remove(current_cursor_index as usize);
self.prev_cursor();
}
else {
......@@ -203,18 +206,18 @@ impl Editor {
match param {
Parameter::Null => {
if let Some(m) = self.to_motion(Inst(param, cmd)) {
self.scroll_y = m.1;
self.buffers.current_buffer_info_mut().scroll_y = m.1;
self.goto(m);
}
}
Parameter::Int(n) => {
self.scroll_y = n;
self.buffers.current_buffer_info_mut().scroll_y = n;
}
}
self.redraw_task = RedrawTask::Full;
}
(Command(Normal), Char('Z')) => {
self.scroll_y = self.y() - 3;
self.buffers.current_buffer_info_mut().scroll_y = self.y() - 3;
self.redraw_task = RedrawTask::Full;
}
(Command(Normal), Char('~')) => {
......
use io::file::FileStatus;
use state::editor::Editor;
use io::redraw::RedrawTask;
use state::editor::{Buffer, BufferManager, Editor};
use edit::buffer::{TextBuffer, SplitBuffer};
use std::process::exit;
......@@ -50,6 +52,21 @@ impl Editor {
FileStatus::Other => format!("Couldn't write {}", sec_cmd),
}
},
"ls" => {
let description = get_buffers_description(&self.buffers);
let mut new_buffer: Buffer = SplitBuffer::from_str(&description).into();
new_buffer.title = Some("<Buffers>".into());
new_buffer.is_transient = true; // delete the buffer when the user switches away
let new_buffer_index = self.buffers.new_buffer(new_buffer);
self.buffers.switch_to(new_buffer_index);
self.redraw_task = RedrawTask::Full;
},
"bd" => {
let ix = self.buffers.current_buffer_index();
self.buffers.delete_buffer(ix);
self.redraw_task = RedrawTask::Full;
},
"help" => {
self.open("/apps/sodium/help.txt");
},
......@@ -57,10 +74,47 @@ impl Editor {
exit(0);
},
c => {
self.status_bar.msg = format!("Unknown command: {}", c);
if c.starts_with("b") {
let rest: String = c.chars().skip(1).collect();
if let Ok(number) = rest.parse::<usize>() {
if !self.buffers.is_buffer_index_valid(number) {
self.status_bar.msg = format!("Invalid buffer #{}", number);
} else {
self.buffers.switch_to(number);
self.redraw_task = RedrawTask::Full;
self.status_bar.msg = format!("Switched to buffer #{}", number);
}
} else {
self.status_bar.msg = format!("Unknown command: {}", c);
}
} else {
self.status_bar.msg = format!("Unknown command: {}", c);
}
}
}
self.hint();
}
}
fn get_buffers_description(buffers: &BufferManager) -> String {
fn print_buffer(i: usize, b: &Buffer) -> String {
let title = b.title.as_ref().map(|s| s.as_str()).unwrap_or("<No Title>");
format!("b{}\t\t\t{}", i, title)
}
let descriptions =
buffers
.iter()
// don't include transient buffers like the one
// this is going to be shown in
.filter(|b| !b.is_transient)
.enumerate()
.map(|(i, b)| print_buffer(i, b))
.collect::<Vec<_>>()
.join("\n");
format!("Buffers\n=====================================\n\n{}", descriptions)
}
......@@ -20,7 +20,7 @@ impl<'a, T: AsRef<str>> Line<'a> for T {
}
/// A buffer structure
pub trait Buffer<'a> {
pub trait TextBuffer<'a> {
/// The line type of the buffer.
type Line: 'a + Line<'a>;
/// The line iterator.
......@@ -101,7 +101,7 @@ impl SplitBuffer {
}
}
impl<'a> Buffer<'a> for SplitBuffer {
impl<'a> TextBuffer<'a> for SplitBuffer {
type Line = String;
type LineIter = SplitBufIter<'a>;
......
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
use state::cursor::Cursor;
use state::editor::Editor;
use io::redraw::RedrawTask;
......@@ -8,14 +8,14 @@ impl Editor {
#[inline]
pub fn delete(&mut self) {
let &Cursor{ x, y, .. } = self.cursor();
if x == self.buffer[y].len() {
if y + 1 < self.buffer.len() {
let s = self.buffer.remove_line(y + 1);
self.buffer[y].push_str(&s);
if x == self.buffers.current_buffer()[y].len() {
if y + 1 < self.buffers.current_buffer().len() {
let s = self.buffers.current_buffer_mut().remove_line(y + 1);
self.buffers.current_buffer_mut()[y].push_str(&s);
self.redraw_task = RedrawTask::Lines(y..y + 1);
}
} else if x < self.buffer[y].len() {
self.buffer[y].remove(x);
} else if x < self.buffers.current_buffer()[y].len() {
self.buffers.current_buffer_mut()[y].remove(x);
self.redraw_task = RedrawTask::LinesAfter(y);
}
......
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
use io::key::Key;
use io::redraw::RedrawTask;
use state::editor::Editor;
......@@ -26,33 +26,33 @@ impl Editor {
let (mut x, mut y) = self.pos();
match (mode, k) {
(InsertMode::Insert, Key::Char('\n')) => {
let first_part = self.buffer[y][..x].to_owned();
let second_part = self.buffer[y][x..].to_owned();
let first_part = self.buffers.current_buffer()[y][..x].to_owned();
let second_part = self.buffers.current_buffer()[y][x..].to_owned();
self.buffer[y] = first_part;
self.buffers.current_buffer_mut()[y] = first_part;
let nl = if self.options.autoindent {
self.buffer.get_indent(y).to_owned()
self.buffers.current_buffer().get_indent(y).to_owned()
} else {
String::new()
};
let begin = nl.len();
self.buffer.insert_line(y, nl + &second_part);
self.buffers.current_buffer_mut().insert_line(y, nl + &second_part);
self.redraw_task = RedrawTask::LinesAfter(y);
self.goto((begin, y + 1));
},
(InsertMode::Insert, Key::Backspace) => self.backspace(),
(InsertMode::Insert, Key::Char(c)) => {
self.buffer[y].insert(x, c);
self.buffers.current_buffer_mut()[y].insert(x, c);
self.redraw_task = RedrawTask::Lines(y..y + 1);
let right = self.right(1, false);
self.goto(right);
},
(InsertMode::Replace, Key::Char(c)) => {
if x == self.buffer[y].len() {
if x == self.buffers.current_buffer()[y].len() {
let next = self.next(1);
if let Some(p) = next {
self.goto(p);
......@@ -61,15 +61,15 @@ impl Editor {
}
}
if self.buffer.len() != y {
if self.buffer[y].len() == x {
if self.buffers.current_buffer_mut().len() != y {
if self.buffers.current_buffer()[y].len() == x {
let next = self.next(1);
if let Some(p) = next {
self.goto(p);
}
} else {
self.buffer[y].remove(x);
self.buffer[y].insert(x, c);
self.buffers.current_buffer_mut()[y].remove(x);
self.buffers.current_buffer_mut()[y].insert(x, c);
}
}
let next = self.next(1);
......
......@@ -8,8 +8,8 @@ impl Editor {
let current = self.current();
if let Some(cur) = current {
self.buffer[y].remove(x);
self.buffer[y].insert(x, invert(cur));
self.buffers.current_buffer_mut()[y].remove(x);
self.buffers.current_buffer_mut()[y].insert(x, invert(cur));
}
if let Some(m) = self.next(1) {
self.goto(m);
......
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
use state::editor::Editor;
impl Editor {
......@@ -15,7 +15,7 @@ impl Editor {
} else {
(self.x(), x)
};
for _ in self.buffer[y].drain(a..b) {}
for _ in self.buffers.current_buffer_mut()[y].drain(a..b) {}
} else {
let (_, y) = self.bound((x as usize, y as usize), true);
// Full line mode
......@@ -27,10 +27,10 @@ impl Editor {
// TODO: Make this more idiomatic (drain)
for _ in a..(b + 1) {
if self.buffer.len() > 1 {
self.buffer.remove_line(a);
if self.buffers.current_buffer().len() > 1 {
self.buffers.current_buffer_mut().remove_line(a);
} else {
self.buffer[0] = String::new();
self.buffers.current_buffer_mut()[0] = String::new();
}
}
}
......
use edit::buffer::{Buffer, SplitBuffer};
use state::editor::Editor;
use edit::buffer::{TextBuffer, SplitBuffer};
use state::editor::{Buffer, Editor};
use std::fs::File;
use std::io::{Read, Write};
......@@ -16,12 +16,15 @@ pub enum FileStatus {
impl Editor {
/// Open a file.
pub fn open(&mut self, path: &str) -> FileStatus {
self.status_bar.file = path.to_owned();
if let Some(mut file) = File::open(path).ok() {
let mut con = String::new();
let _ = file.read_to_string(&mut con);
self.buffer = SplitBuffer::from_str(&con);
let mut new_buffer: Buffer = SplitBuffer::from_str(&con).into();
new_buffer.title = Some(path.into());
let new_buffer_index = self.buffers.new_buffer(new_buffer);
self.buffers.switch_to(new_buffer_index);
self.hint();
FileStatus::Ok
} else {
......@@ -31,9 +34,9 @@ impl Editor {
/// Write the file.
pub fn write(&mut self, path: &str) -> FileStatus {
self.status_bar.file = path.to_owned();
self.buffers.current_buffer_info_mut().title = Some(path.into());
if let Some(mut file) = File::create(path).ok() {
if file.write(self.buffer.to_string().as_bytes()).is_ok() {
if file.write(self.buffers.current_buffer().to_string().as_bytes()).is_ok() {
FileStatus::Ok
} else {
FileStatus::Other
......
use edit::buffer::Buffer;
use edit::buffer::TextBuffer;
use io::redraw::RedrawTask;
use state::editor::Editor;
use state::mode::{Mode, PrimitiveMode};
......@@ -11,6 +11,11 @@ impl Editor {
/// Redraw the window
pub fn redraw(&mut self) {
// TODO: Only draw when relevant for the window
let (scroll_x, scroll_y) = {
let current_buffer = self.buffers.current_buffer_info();
(current_buffer.scroll_x, current_buffer.scroll_y)
};
let (pos_x, pos_y) = self.pos();
// Redraw window
self.window.set(Color::rgb(25, 25, 25));
......@@ -19,14 +24,14 @@ impl Editor {
if self.options.line_marker {
self.window.rect(0,
(pos_y - self.scroll_y) as i32 * 16,
(pos_y - scroll_y) as i32 * 16,
w,
16,
Color::rgb(45, 45, 45));
}
self.window.rect(8 * (pos_x - self.scroll_x) as i32,
16 * (pos_y - self.scroll_y) as i32,
self.window.rect(8 * (pos_x - scroll_x) as i32,
16 * (pos_y - scroll_y) as i32,
8,
16,
Color::rgb(255, 255, 255));
......@@ -34,7 +39,7 @@ impl Editor {
let mut string = false;
for (y, row) in self.buffer.lines().enumerate() {
for (y, row) in self.buffers.current_buffer().lines().enumerate() {
for (x, c) in row.chars().enumerate() {
// TODO: Move outta here
let color = if self.options.highlight {
......@@ -76,13 +81,13 @@ impl Editor {
};
if pos_x == x && pos_y == y {
self.window.char(8 * (x - self.scroll_x) as i32,
16 * (y - self.scroll_y) as i32,
self.window.char(8 * (x - scroll_x) as i32,
16 * (y - scroll_y) as i32,
c,
Color::rgb(color.0 / 3, color.1 / 3, color.2 / 3));
} else {
self.window.char(8 * (x - self.scroll_x) as i32,
16 * (y - self.scroll_y) as i32,
self.window.char(8 * (x - scroll_x) as i32,
16 * (y - scroll_y) as i32,
c,
Color::rgb(color.0, color.1, color.2));
}
......@@ -130,9 +135,12 @@ impl Editor {
let mode = self.cursor().mode;
let current_title =
self.buffers.current_buffer_info().title.as_ref().map(|s| s.as_str()).unwrap_or("");
let items = [
(self.status_bar.mode, 0, 4),
(&self.status_bar.file, 1, 4),
(current_title, 1, 4),
(&self.status_bar.cmd, 2, 4),
(&self.status_bar.msg, 3, 4)
];
......@@ -166,8 +174,6 @@ impl Editor {
pub struct StatusBar {
/// The current mode
pub mode: &'static str,
/// The cureent char
pub file: String,
/// The current command
pub cmd: String,
/// A message (such as an error or other info to the user)
......@@ -179,7 +185,6 @@ impl StatusBar {
pub fn new() -> Self {
StatusBar {
mode: "Normal",
file: String::new(),
cmd: String::new(),
msg: "Welcome to Sodium!".to_string(),
}
......
......@@ -29,7 +29,7 @@ impl Editor {
#[inline]
pub fn current(&self) -> Option<char> {
let (x, y) = self.pos();
match self.buffer[y].chars().nth(x) {
match self.buffers.current_buffer()[y].chars().nth(x) {
Some(c) => Some(c),
None => None,
}
......@@ -38,29 +38,33 @@ impl Editor {
/// Get the current cursor
#[inline]
pub fn cursor(&self) -> &Cursor {
self.cursors.get(self.current_cursor as usize).unwrap()
let buffer = self.buffers.current_buffer_info();
buffer.cursors.get(buffer.current_cursor as usize).unwrap()
}
/// Get the current cursor mutably
#[inline]
pub fn cursor_mut(&mut self) -> &mut Cursor {
self.cursors.get_mut(self.current_cursor as usize).unwrap()
let buffer = self.buffers.current_buffer_info_mut();
buffer.cursors.get_mut(buffer.current_cursor as usize).unwrap()
}
/// Go to next cursor
#[inline]
pub fn next_cursor(&mut self) {
self.current_cursor = (self.current_cursor.wrapping_add(1)) % (self.cursors.len() as u8);
let buffer = self.buffers.current_buffer_info_mut();
buffer.current_cursor = (buffer.current_cursor.wrapping_add(1)) % (buffer.cursors.len() as u8);
}
/// Go to previous cursor
#[inline]
pub fn prev_cursor(&mut self) {
if self.current_cursor != 0 {
self.current_cursor -= 1;
let buffer = self.buffers.current_buffer_info_mut();
if buffer.current_cursor != 0 {
buffer.current_cursor -= 1;
}
else {
self.current_cursor = self.cursors.len() as u8;
buffer.current_cursor = buffer.cursors.len() as u8;
}
}
}
use edit::buffer::{Buffer, SplitBuffer};
use std::slice::Iter;
use edit::buffer::{TextBuffer, SplitBuffer};
use state::cursor::Cursor;
use io::graphics::StatusBar;
use io::key::{Key, Cmd};
......@@ -12,18 +13,151 @@ use orbclient::Window;
use std::env::args;
/// The current state of the editor, including the file, the cursor, the scrolling info, etc.
pub struct Editor {
/// A SplitBuffer and related state
pub struct Buffer {
/// The document
pub raw_buffer: SplitBuffer,
/// The current cursor
pub current_cursor: u8,
/// The cursors
pub cursors: Vec<Cursor>,
/// The buffer (document)
pub buffer: SplitBuffer,
/// The x coordinate of the scroll
pub scroll_x: usize,
/// The y coordinate of the scroll
pub scroll_y: usize,
/// The title of the document
pub title: Option<String>,
/// True if the buffer is transient and should be deleted when
/// it is no longer the current buffer.
pub is_transient: bool,
}
impl Buffer {
/// Create a new Buffer with default values.
fn new() -> Buffer {
Buffer {
raw_buffer: SplitBuffer::new(),
current_cursor: 0,
cursors: vec![Cursor::new()],
scroll_x: 0,
scroll_y: 0,
title: None,
is_transient: false,
}
}
}
impl From<SplitBuffer> for Buffer {
fn from(b: SplitBuffer) -> Buffer {
let mut info = Buffer::new();
info.raw_buffer = b;
info
}
}
/// Provides access to buffer manipulation functions.
pub struct BufferManager {
buffers: Vec<Buffer>,
current_buffer_index: usize,
}
impl BufferManager {
/// Create a new BufferManager with default values.