Commit b2119a8c authored by Ticki's avatar Ticki

Refactor it all into modules

parent d89667f7
/// Motions.
///
/// A motion is a command defining some movement from point A to point B, these can be used in
/// mulitple context, for example as argument for other commands.
pub mod motion;
/// Movement.
pub mod movement;
/// Calculations and bounding of positions.
pub mod position;
use caret::position::to_signed_pos;
use edit::buffer::Buffer;
use io::parse::Inst;
use state::editor::Editor;
impl Editor {
/// Convert an instruction to a motion (new coordinate). Returns None if the instructions given
/// either is invalid or has no movement.
///
/// A motion is a namespace (i.e. non mode-specific set of commands), which represents
/// movements. These are useful for commands which takes a motion as post-parameter, such as d.
/// d deletes the text given by the motion following. Other commands can make use of motions,
/// using this method.
pub fn to_motion(&mut self, Inst(n, cmd): Inst) -> Option<(usize, usize)> {
use io::key::Key::*;
let y = self.y();
match cmd.key {
Char('h') => Some(self.left(n.d())),
Char('l') => Some(self.right(n.d(), true)),
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('H') => Some((0, y)),
Char('t') => {
let ch = self.get_char();
if let Some(o) = self.next_ocur(ch, n.d()) {
Some((o, y))
} else {
None
}
}
Char('f') => {
let ch = self.get_char();
if let Some(o) = self.previous_ocur(ch, n.d()) {
Some((o, y))
} else {
None
}
}
Char(c) => {
self.status_bar.msg = format!("Motion not defined: '{}'", c);
self.redraw_status_bar();
None
}
_ => {
self.status_bar.msg = format!("Motion not defined");
None
}
}
}
/// Like to_motion() but does not bound to the text. Therefore it returns an isize, and in some
/// cases it's a position which is out of bounds. This is useful when commands want to mesure
/// the relative movement over the movement.
pub fn to_motion_unbounded(&mut self, Inst(n, cmd): Inst) -> Option<(isize, isize)> {
use io::key::Key::*;
let x = self.x();
let y = self.y();
match cmd.key {
Char('h') => Some(self.left_unbounded(n.d())),
Char('l') => Some(self.right_unbounded(n.d())),
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('H') => Some((0, y as isize)),
Char('t') => {
let ch = self.get_char();
if let Some(o) = self.next_ocur(ch, n.d()) {
Some((to_signed_pos((o, y))))
} else {
None
}
}
Char('f') => {
let ch = self.get_char();
if let Some(o) = self.previous_ocur(ch, n.d()) {
Some((to_signed_pos((o, y))))
} else {
None
}
}
_ => None,
}
}
}
use edit::buffer::Buffer;
use state::editor::Editor;
impl Editor {
/// Goto a given position. Does not automatically bound.
#[inline]
pub fn goto(&mut self, (x, y): (usize, usize)) {
self.cursor_mut().y = y;
self.cursor_mut().x = x;
}
/// Get the previous position, i.e. the position before the cursor (*not* left to the cursor).
/// Includes newline positions.
#[inline]
pub fn previous(&self, n: usize) -> Option<(usize, usize)> {
self.before(n, self.pos())
}
/// Get the next position, i.e. the position after the cursor (*not* right to the cursor)
#[inline]
pub fn next(&self, n: usize) -> Option<(usize, usize)> {
self.after(n, self.pos())
}
/// Get position after a given position, i.e. a generalisation of .next()
#[inline]
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() {
Some((x + n, y))
} else {
if y + 1 >= self.buffer.len() {
None
} else {
let mut mv = n + x - self.buffer[y].len();
let mut ry = y + 1;
loop {
if mv < self.buffer[ry].len() {
return Some((mv, ry));
} else {
if ry + 1 < self.buffer.len() {
mv -= self.buffer[ry].len();
ry += 1;
} else {
return None;
}
}
}
}
}
// }
}
/// Get the position before a given position, i.e. a generalisation .before(). Includes
/// newline positions.
#[inline]
pub fn before(&self, n: usize, (x, y): (usize, usize)) -> Option<(usize, usize)> {
if x >= n {
Some((x - n, y))
} else {
if y == 0 {
None
} else {
let mut mv = n - x - 1;
let mut ry = y - 1;
loop {
if mv <= self.buffer[ry].len() {
return Some((self.buffer[ry].len() - mv, ry));
} else {
if ry > 0 && mv >= self.buffer[ry].len() {
mv -= self.buffer[ry].len();
ry -= 1;
} else if ry == 0 {
return None;
}
}
}
}
}
}
/// Get the position of the character right to the cursor (horizontally bounded)
#[inline]
pub fn right(&self, n: usize, tight: bool) -> (usize, usize) {
self.bound_hor((self.x() + n, self.y()), tight)
}
/// Get the position of the character right to the cursor (unbounded)
#[inline]
pub fn right_unbounded(&self, n: usize) -> (isize, isize) {
((self.x() + n) as isize, self.y() as isize)
}
/// Get the position of the character left to the cursor (horizontally bounded)
#[inline]
pub fn left(&self, n: usize) -> (usize, usize) {
if n <= self.x() {
(self.x() - n, self.y())
} else {
(0, self.y())
}
}
/// Get the position of the character left to the cursor (unbounded)
#[inline]
pub fn left_unbounded(&self, n: usize) -> (isize, isize) {
(self.x() as isize - n as isize, self.y() as isize)
}
/// Get the position of the character above the cursor (vertically bounded)
#[inline]
pub fn up(&self, n: usize) -> (usize, usize) {
if n <= self.y() {
(self.cursor().x, self.y() - n)
} else {
(self.cursor().x, 0)
}
}
/// Get the position of the character above the cursor (unbounded)
#[inline]
pub fn up_unbounded(&self, n: usize) -> (isize, isize) {
(self.cursor().x as isize, self.y() as isize - n as isize)
}
/// Get the position of the character under the cursor (vertically bounded)
#[inline]
pub fn down(&self, n: usize) -> (usize, usize) {
self.bound_ver((self.cursor().x, self.y() + n))
}
/// Get the position of the character above the cursor (unbounded)
#[inline]
pub fn down_unbounded(&self, n: usize) -> (isize, isize) {
(self.cursor().x as isize, self.y() as isize + n as isize)
}
/// Get n'th next ocurrence of a given charecter (relatively to the cursor)
pub fn next_ocur(&self, c: char, n: usize) -> Option<usize> {
let mut dn = 0;
let x = self.x();
for ch in self.buffer[self.y()].chars().skip(x) {
if dn == n {
if ch == c {
dn += 1;
if dn == n {
return Some(x);
}
}
}
}
None
}
/// Get n'th previous ocurrence of a given charecter (relatively to the cursor)
pub fn previous_ocur(&self, c: char, n: usize) -> Option<usize> {
let mut dn = 0;
let x = self.x();
let y = self.y();
for ch in self.buffer[y].chars().rev().skip(self.buffer[y].len() - x) {
if dn == n {
if ch == c {
dn += 1;
if dn == n {
return Some(x);
}
}
}
}
None
}
}
use edit::buffer::Buffer;
use state::editor::Editor;
/// Convert a usize tuple to isize
pub fn to_signed_pos((x, y): (usize, usize)) -> (isize, isize) {
(x as isize, y as isize)
}
impl Editor {
/// Get the position of the current cursor, bounded
#[inline]
pub fn pos(&self) -> (usize, usize) {
let cursor = self.cursor();
self.bound((cursor.x, cursor.y), false)
}
#[inline]
/// Get the X coordinate of the current cursor (bounded)
pub fn x(&self) -> usize {
self.pos().0
}
#[inline]
/// Get the Y coordinate of the current cursor (bounded)
pub fn y(&self) -> usize {
self.pos().1
}
/// Convert a position value to a bounded position value
#[inline]
pub fn bound(&self, (x, mut y): (usize, usize), tight: bool) -> (usize, usize) {
y = if y >= self.buffer.len() {
self.buffer.len() - 1
} else {
y
};
let ln = self.buffer[y].len() + if tight {0} else {1};
if x >= ln {
if ln == 0 {
(0, y)
} else {
(ln - 1, y)
}
} else {
(x, y)
}
}
/// Bound horizontally, i.e. don't change the vertical axis only make sure that the horizontal
/// axis is bounded.
#[inline]
pub fn bound_hor(&self, (x, y): (usize, usize), tight: bool) -> (usize, usize) {
(self.bound((x, y), tight).0, y)
}
/// Bound vertically, i.e. don't change the horizontal axis only make sure that the vertical
/// axis is bounded.
#[inline]
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
} else {
y
};
(x, y)
}
}
/// Write to console if the debug option is set (with newline)
#[macro_export]
macro_rules! debugln {
($e:expr, $($arg:tt)*) => ({
if $e.options.debug {
println!($($arg)*);
}
});
}
/// Write to console if the debug option is set
#[macro_export]
macro_rules! debug {
($e:expr, $($arg:tt)*) => ({
if $e.options.debug {
print!($($arg)*);
}
});
}
use state::editor::Editor;
use io::parse::{Inst, Parameter};
use state::mode::{Mode, CommandMode, PrimitiveMode};
use edit::insert::{InsertOptions, InsertMode};
use io::redraw::RedrawTask;
use edit::buffer::Buffer;
// TODO: Move the command definitions outta here
impl Editor {
/// Execute an instruction
pub fn exec(&mut self, Inst(para, cmd): Inst) {
use io::key::Key::*;
use state::mode::Mode::*;
use state::mode::PrimitiveMode::*;
use state::mode::CommandMode::*;
let n = para.d();
let bef = self.pos();
let mut mov = false;
match (self.cursor().mode, cmd.key) {
(Primitive(Prompt), Char(' ')) if self.key_state.shift => {
self.prompt = String::new();
self.cursor_mut().mode = Mode::Command(CommandMode::Normal);
},
(Primitive(Insert(_)), Char(' ')) if self.key_state.shift => {
let left = self.left(1);
self.goto(left);
self.cursor_mut().mode = Mode::Command(CommandMode::Normal);
},
(_, Char(' ')) if self.key_state.shift =>
self.cursor_mut().mode = Mode::Command(CommandMode::Normal),
(_, Char(' ')) if self.key_state.alt => self.next_cursor(),
_ if self.key_state.alt => if let Some(m) = self.to_motion(Inst(para, cmd)) {
self.goto(m);
},
(Command(Normal), Char('i')) => {
self.cursor_mut().mode =
Mode::Primitive(PrimitiveMode::Insert(InsertOptions {
mode: InsertMode::Insert,
}));
}
(Command(Normal), Char('a')) => {
let pos = self.right(1, false);
self.goto( pos );
self.cursor_mut().mode =
Mode::Primitive(PrimitiveMode::Insert(InsertOptions {
mode: InsertMode::Insert,
}));
}
(Command(Normal), Char('o')) => {
let y = self.y();
let ind = if self.options.autoindent {
self.buffer.get_indent(y).to_owned()
} else {
String::new()
};
let last = ind.len();
self.buffer.insert_line(y, ind.into());
self.goto((last, y + 1));
self.cursor_mut().mode =
Mode::Primitive(PrimitiveMode::Insert(InsertOptions {
mode: InsertMode::Insert,
}));
}
(Command(Normal), Char('h')) => {
let left = self.left(n);
self.goto(left);
mov = true;
}
(Command(Normal), Char('j')) => {
let down = self.down(n);
self.goto(down);
mov = true;
}
(Command(Normal), Char('k')) => {
let up = self.up(n);
self.goto(up);
mov = true;
}
(Command(Normal), Char('l')) => {
let right = self.right(n, true);
self.goto(right);
mov = true;
}
(Command(Normal), Char('J')) => {
let down = self.down(15 * n);
self.goto(down);
mov = true;
}
(Command(Normal), Char('K')) => {
let up = self.up(15 * n);
self.goto(up);
mov = true;
}
(Command(Normal), Char('x')) => {
self.delete();
let bounded = self.bound(self.pos(), true);
self.goto(bounded);
}
(Command(Normal), Char('X')) => {
self.backspace();
let bounded = self.bound(self.pos(), true);
self.goto(bounded);
}
(Command(Normal), Char('L')) => {
let ln_end = (self.buffer[self.y()].len(), self.y());
self.goto(ln_end);
mov = true;
}
(Command(Normal), Char('H')) => {
self.cursor_mut().x = 0;
mov = true;
}
(Command(Normal), Char('r')) => {
let (x, y) = self.pos();
let c = self.get_char();
// If there is nothing in the current buffer
// ignore the command
if self.buffer[y].len() > 0 {
self.buffer[y].remove(x);
}
self.buffer[y].insert(x, c);
}
(Command(Normal), Char('R')) => {
self.cursor_mut().mode =
Mode::Primitive(PrimitiveMode::Insert(InsertOptions {
mode: InsertMode::Replace,
}));
}
(Command(Normal), Char('d')) => {
let ins = self.get_inst();
if let Some(m) = self.to_motion_unbounded(ins) {
self.remove_rb(m);
}
}
(Command(Normal), Char('G')) => {
let last = self.buffer.len() - 1;
self.goto((0, last));
mov = true;
}
(Command(Normal), Char('g')) => {
if let Parameter::Int(n) = para {
self.goto((0, n - 1));
mov = true;
} else {
let inst = self.get_inst();
if let Some(m) = self.to_motion(inst) {
self.goto(m); // fix
mov = true;
}
}
}
(Command(Normal), Char('b')) => {
// Branch cursor
if self.cursors.len() < 255 {
let cursor = self.cursor().clone();
self.cursors.insert(self.current_cursor as usize, cursor);
self.next_cursor();
}
else {
self.status_bar.msg = format!("At max 255 cursors");
}
}
(Command(Normal), Char('B')) => {
// Delete cursor
if self.cursors.len() > 1 {
self.cursors.remove(self.current_cursor as usize);
self.prev_cursor();
}
else {
self.status_bar.msg = format!("No other cursors!");
}
}
(Command(Normal), Char('t')) => {
let ch = self.get_char();
let pos = self.next_ocur(ch, n);
if let Some(p) = pos {
let y = self.y();
self.goto((p, y));
mov = true;
}
}
(Command(Normal), Char('f')) => {
let ch = self.get_char();
let pos = self.previous_ocur(ch, n);
if let Some(p) = pos {
let y = self.y();
self.goto((p, y));
mov = true;
}
}
(Command(Normal), Char(';')) =>
self.cursor_mut().mode = Mode::Primitive(PrimitiveMode::Prompt),
(Command(Normal), Char(' ')) => self.next_cursor(),
(Command(Normal), Char('z')) => {
let Inst(param, cmd) = self.get_inst();
match param {
Parameter::Null => {
if let Some(m) = self.to_motion(Inst(param, cmd)) {
self.scroll_y = m.1;
self.goto(m);
}
}
Parameter::Int(n) => {
self.scroll_y = n;
}
}
self.redraw_task = RedrawTask::Full;
}
(Command(Normal), Char('Z')) => {
self.scroll_y = self.y() - 3;
self.redraw_task = RedrawTask::Full;
}
(Command(Normal), Char('~')) => {
self.invert_chars(n);
}
(Command(Normal), Char(c)) => {
self.status_bar.msg = format!("Unknown command: {}", c);
self.redraw_task = RedrawTask::StatusBar;
}
(Primitive(Insert(opt)), k) => self.insert(k, opt),
(Primitive(Prompt), Char('\n')) => {
self.cursor_mut().mode = Command(Normal);
let cmd = self.prompt.clone();
self.invoke(cmd);
self.prompt = String::new();
self.redraw_task = RedrawTask::StatusBar;
},
(Primitive(Prompt), Backspace) => {
self.prompt.pop();
self.redraw_task = RedrawTask::StatusBar;
},
(Primitive(Prompt), Char(c)) => {
self.prompt.push(c);
self.redraw_task = RedrawTask::StatusBar;
},
_ => {
self.status_bar.msg = format!("Unknown command");
self.redraw_task = RedrawTask::StatusBar;
},
}
if mov {
self.redraw_task = RedrawTask::Cursor(bef, self.pos());
}
}
}
/// Primitives for debugging.
#[macro_use]
pub mod debug;
/// Executing commands.
pub mod exec;
/// The command prompt.
pub mod prompt;
use io::file::FileStatus;
use state::editor::Editor;
use std::process::exit;
impl Editor {
/// Invoke a command in the prompt
pub fn invoke(&mut self, cmd: String) {
let mut split = cmd.split(' ');
let base_cmd = split.nth(0).unwrap_or("");
let sec_cmd = split.nth(0).unwrap_or("");
match base_cmd {
"set" => {
self.status_bar.msg = match self.options.set(sec_cmd) {
Ok(()) => format!("Option set: {}", sec_cmd),
Err(()) => format!("Option does not exist: {}", sec_cmd),
}
},
"unset" => {
self.status_bar.msg = match self.options.unset(sec_cmd) {
Ok(()) => format!("Option unset: {}", sec_cmd),
Err(()) => format!("Option does not exist: {}", sec_cmd),
}
},
"toggle" | "tog" => {
self.status_bar.msg = match self.options.toggle(sec_cmd) {
Ok(()) => format!("Option toggled: {}", sec_cmd),
Err(()) => format!("Option does not exist: {}", sec_cmd),
}
},
"get" => {
self.status_bar.msg = match self.options.get(sec_cmd) {
Some(true) => format!("Option set: {}", sec_cmd),
Some(false) => format!("Option unset: {}", sec_cmd),
None => format!("Option does not exist: {}", sec_cmd),
}
},
"o" | "open" => {
self.status_bar.msg = match self.open(sec_cmd) {
FileStatus::NotFound => format!("File {} could not be opened", sec_cmd),
FileStatus::Ok => format!("File {} opened", sec_cmd),
_ => unreachable!(),
}
},
"w" | "write" => {
self.status_bar.msg = match self.write(sec_cmd) {
FileStatus::NotFound => format!("File {} could not be opened", sec_cmd),
FileStatus::Ok => format!("File {} written", sec_cmd),
FileStatus::Other => format!("Couldn't write {}", sec_cmd),
}
},
"help" => {
self.open("/apps/sodium/help.txt");
},
"q" | "quit" => {
exit(0);
},
c => {
self.status_bar.msg = format!("Unknown command: {}", c);
}
}
self.hint();
}
}
use std::cmp::min;