extern crate libc;
extern crate nix;
extern crate unicode_width;
extern crate encoding;
extern crate strcursor;
mod enc;
mod error;
mod buffer;
mod history;
mod parser;
mod instr;
mod term;
use std::os::unix::io::{RawFd, AsRawFd};
use encoding::types::EncodingRef;
use encoding::all::{ASCII, UTF_8};
pub use enc::Encoding;
pub use error::Error;
use history::History;
use buffer::Buffer;
use term::{Term, RawMode};
struct EditCtx<'a> {
term: &'a mut Term,
raw: &'a mut RawMode,
history: &'a History,
prompt: &'a str,
enc: EncodingRef
}
fn edit<'a>(ctx: EditCtx<'a>) -> Result<String, Error> {
let mut buffer = Buffer::new();
let mut seq: Vec<u8> = Vec::new();
let mut history_cursor = history::Cursor::new(ctx.history);
loop {
try!(ctx.raw.write(&buffer.get_line(ctx.prompt)));
let byte = try!(try!(ctx.term.read_byte()).ok_or(Error::EndOfFile));
seq.push(byte);
match parser::parse(&seq, ctx.enc) {
parser::Result::Error(len) => {
for _ in (0..len) {
seq.remove(0);
}
},
parser::Result::Incomplete => (),
parser::Result::Success(token, len) => {
match instr::interpret_token(token) {
instr::Instr::Done => {
return Result::Ok(buffer.to_string());
},
instr::Instr::DeleteCharLeftOfCursor => {
buffer.delete_char_left_of_cursor();
},
instr::Instr::DeleteCharRightOfCursor => {
buffer.delete_char_right_of_cursor();
},
instr::Instr::DeleteCharRightOfCursorOrEOF => {
if !buffer.delete_char_right_of_cursor() {
return Err(Error::EndOfFile);
}
},
instr::Instr::MoveCursorLeft => {
buffer.move_left();
},
instr::Instr::MoveCursorRight => {
buffer.move_right();
},
instr::Instr::MoveCursorStart => buffer.move_start(),
instr::Instr::MoveCursorEnd => buffer.move_end(),
instr::Instr::HistoryPrev => {
if history_cursor.incr() {
buffer.swap()
}
history_cursor.get().map(|s| buffer.replace(s));
},
instr::Instr::HistoryNext => {
if history_cursor.decr() {
buffer.swap()
}
history_cursor.get().map(|s| buffer.replace(s));
},
instr::Instr::Noop => (),
instr::Instr::Cancel => return Err(Error::Cancel),
instr::Instr::Clear => try!(ctx.raw.clear()),
instr::Instr::InsertAtCursor(text) => {
buffer.insert_chars_at_cursor(text)
}
};
for _ in (0..len) {
seq.remove(0);
}
}
};
}
}
pub struct Copperline {
term: Term,
history: History
}
impl Copperline {
pub fn new() -> Copperline {
Copperline::new_from_raw_fds(libc::STDIN_FILENO, libc::STDOUT_FILENO)
}
pub fn new_from_io<I: AsRawFd, O: AsRawFd>(i: &I, o: &O) -> Copperline {
Copperline::new_from_raw_fds(i.as_raw_fd(), o.as_raw_fd())
}
pub fn new_from_raw_fds(ifd: RawFd, ofd: RawFd) -> Copperline {
Copperline {
term: Term::new(ifd, ofd),
history: History::new()
}
}
fn read_line_with_enc(&mut self, prompt: &str, enc: EncodingRef) -> Result<String, Error> {
if Term::is_unsupported_term() || !self.term.is_a_tty() {
return Err(Error::UnsupportedTerm);
}
let result = self.term.acquire_raw_mode().and_then(|mut raw| {
edit(EditCtx {
term: &mut self.term,
raw: &mut raw,
history: &self.history,
prompt: prompt,
enc: enc
})
});
println!("");
result
}
pub fn read_line(&mut self, prompt: &str, encoding: Encoding) -> Result<String, Error> {
self.read_line_with_enc(prompt, enc::to_encoding_ref(encoding))
}
pub fn read_line_ascii(&mut self, prompt: &str) -> Result<String, Error> {
self.read_line_with_enc(prompt, ASCII)
}
pub fn read_line_utf8(&mut self, prompt: &str) -> Result<String, Error> {
self.read_line_with_enc(prompt, UTF_8)
}
pub fn get_current_history_length(&self) -> usize {
self.history.len()
}
pub fn add_history(&mut self, line: String) {
self.history.push(line)
}
pub fn get_history_item(&self, idx: usize) -> Option<&String> {
self.history.get(idx)
}
pub fn remove_history_item(&mut self, idx: usize) -> Option<String> {
self.history.remove(idx)
}
pub fn clear_history(&mut self) {
self.history.clear()
}
pub fn clear_screen(&mut self) -> Result<(), Error> {
let mut raw = try!(self.term.acquire_raw_mode());
raw.clear().map_err(Error::ErrNo)
}
}