aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cursor.rs10
-rw-r--r--src/lib.rs208
-rw-r--r--src/line/buf.rs (renamed from src/linebuf.rs)12
-rw-r--r--src/line/mod.rs219
-rw-r--r--src/parse/mod.rs84
-rw-r--r--src/parse/span.rs86
-rw-r--r--src/run/builtin.rs61
-rw-r--r--src/syntax_highlighting.rs76
8 files changed, 556 insertions, 200 deletions
diff --git a/src/cursor.rs b/src/cursor.rs
index fbaacbb..1ec23d5 100644
--- a/src/cursor.rs
+++ b/src/cursor.rs
@@ -8,9 +8,9 @@ pub enum Direction {
Right,
}
-pub fn move_cursor(direction: Direction, n: usize) {
+pub fn fmove_cursor(direction: Direction, n: usize, stdout: &mut dyn Write) -> std::io::Result<()> {
if n == 0 {
- return;
+ return Ok(());
}
let code = match direction {
@@ -20,7 +20,11 @@ pub fn move_cursor(direction: Direction, n: usize) {
Direction::Left => 'D',
};
- print!("\x1b[{n}{code}");
+ write!(stdout, "\x1b[{n}{code}")
+}
+
+pub fn move_cursor(direction: Direction, n: usize) {
+ fmove_cursor(direction, n, &mut std::io::stdout()).unwrap()
}
pub fn save() {
diff --git a/src/lib.rs b/src/lib.rs
index 3931f5d..2a9a1ee 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,7 +25,7 @@ pub mod date;
pub mod defer;
pub mod export_fun;
pub mod history;
-pub mod linebuf;
+pub mod line;
pub mod panic;
pub mod parse;
pub mod raw;
@@ -33,15 +33,15 @@ pub mod reload;
pub mod run;
pub mod rw;
pub mod serialization;
+pub mod syntax_highlighting;
pub mod wait;
-use linebuf::LineBuf;
use raw::*;
use crate::completion::{PathCache, completion};
use crate::ctrlc::CtrlC;
-use crate::cursor::{Direction, move_cursor};
use crate::history::HistoryEntry;
+use crate::line::Line;
use crate::parse::{Block, ExpString, Parse, PostExpansion};
macro_rules! print {
@@ -93,7 +93,7 @@ impl PushAll for BString {
pub struct Session {
raw: Option<ScopedRawMode>,
- line: LineBuf,
+ line: Line,
history: Vec<HistoryEntry>,
prev_path: BString,
builtins: HashMap<BString, &'static dyn run::BuiltinClone>,
@@ -115,13 +115,15 @@ pub struct Session {
/// n before end of history.len()
/// 0 == not checking history
history_visit: usize,
+
+ highlighter: syntax_highlighting::Highlighter,
}
impl Session {
pub fn new_noninteractive() -> Self {
Self {
raw: None,
- line: LineBuf::new(),
+ line: Line::new(),
history: Vec::new(),
prev_path: b".".into(),
builtins: HashMap::new(),
@@ -136,6 +138,7 @@ impl Session {
debug_keystrokes: false,
loud: false,
history_visit: 0,
+ highlighter: syntax_highlighting::Highlighter::new(),
}
}
}
@@ -212,17 +215,8 @@ impl Session {
expanded
}
- fn clear_prompt(&mut self) {
- cursor::move_cursor(Direction::Right, self.line.distance_from_right_end());
- for _ in 0..self.line.len() {
- print!("\x08 \x08");
- }
- io::stdout().lock().flush().unwrap();
- self.line.clear();
- }
-
fn prompt_clear(&mut self) {
- self.clear_prompt();
+ self.line.clear_prompt().unwrap();
self.history_visit = 0;
}
@@ -234,7 +228,7 @@ impl Session {
}
fn display_historic_entry(&mut self) {
- self.clear_prompt();
+ self.line.clear_prompt().unwrap();
let new = if self.history_visit == 0 {
Vec::new()
} else {
@@ -244,7 +238,7 @@ impl Session {
};
io::stdout().write_all(&new).unwrap();
io::stdout().flush().unwrap();
- self.line.set_content(new);
+ self.line.set_content(new).unwrap();
}
fn history_up(&mut self) {
@@ -261,150 +255,26 @@ impl Session {
}
}
- fn type_bytes(&mut self, bs: &[u8]) {
- for b in bs.iter() {
- self.line.add(*b);
- }
- io::stdout().lock().write_all(&bs).unwrap();
- self.line.display_post(b"");
- }
-
- fn type_byte(&mut self, b: u8) {
- self.type_bytes(&[b]);
- }
-
- fn del_left(&mut self) {
- if self.line.del_left().is_some() {
- cursor::move_cursor(Direction::Left, 1);
- self.line.display_post(b" ");
- }
- }
-
- fn del_right(&mut self) {
- self.line.del_right();
- self.line.display_post(b" ");
- }
-
fn del_left_or_previous(&mut self) {
- if self.line.is_empty() && !self.line.is_dirty() && !self.history.is_empty() {
+ if self.line.is_empty() && !self.line.had_contents() && !self.history.is_empty() {
// take previous command for editing
let cmd = self.history[self.history.len() - 1].cmd.clone();
- self.type_bytes(&cmd);
+ self.line.type_bytes(&cmd).unwrap();
} else {
- self.del_left();
+ self.line.del_left().unwrap();
}
}
fn prompt_pipe_previous(&mut self) {
- if self.line.is_empty() && let Some(prev) = self.history.last() {
+ if self.line.is_empty()
+ && let Some(prev) = self.history.last()
+ {
let mut cmd = prev.cmd.clone();
cmd.push_all(b" | ");
- self.type_bytes(&cmd);
+ self.line.type_bytes(&cmd).unwrap();
} else {
- self.type_byte(b'|');
- }
- }
-
- fn move_to_begin(&mut self) {
- cursor::move_cursor(Direction::Left, self.line.all_left());
- io::stdout().flush().unwrap();
- }
-
- fn move_to_end(&mut self) {
- cursor::move_cursor(Direction::Right, self.line.all_right());
- io::stdout().flush().unwrap();
- }
-
- fn del_left_word(&mut self) {
- let mut del = 0;
- while let Some(x) = self.line.get_left()
- && x == b' '
- {
- self.line.del_left();
- del += 1;
- }
- while let Some(x) = self.line.get_left()
- && x != b' '
- {
- self.line.del_left();
- del += 1;
- }
- cursor::move_cursor(Direction::Left, del);
- self.line.display_post(&vec![b' '; del]);
- }
-
- fn del_right_word(&mut self) {
- let mut del = 0;
- while let Some(x) = self.line.get_right()
- && x == b' '
- {
- self.line.del_right();
- del += 1;
- }
- while let Some(x) = self.line.get_right()
- && x != b' '
- {
- self.line.del_right();
- del += 1;
- }
- self.line.display_post(&vec![b' '; del]);
- }
-
- fn cursor_left(&mut self) {
- if self.line.left() {
- move_cursor(Direction::Left, 1);
- io::stdout().lock().flush().unwrap();
- }
- }
-
- fn cursor_right(&mut self) {
- if self.line.right() {
- move_cursor(Direction::Right, 1);
- io::stdout().lock().flush().unwrap();
- }
- }
-
- // move to next
- fn cursor_left_word(&mut self) {
- let mut i = 0;
-
- // find word
- while let Some(b' ') = self.line.get_left() {
- self.line.left();
- i += 1;
- }
-
- // skip it
- while let Some(x) = self.line.get_left()
- && !x.is_ascii_whitespace()
- {
- self.line.left();
- i += 1;
- }
-
- cursor::move_cursor(Direction::Left, i);
- io::stdout().flush().unwrap();
- }
-
- fn cursor_right_word(&mut self) {
- let mut i = 0;
-
- // find word
- while let Some(b' ') = self.line.get_right() {
- self.line.right();
- i += 1;
+ self.line.type_byte(b'|').unwrap();
}
-
- // skip it
- while let Some(x) = self.line.get_right()
- && !x.is_ascii_whitespace()
- {
- self.line.right();
- i += 1;
- }
-
- cursor::move_cursor(Direction::Right, i);
- io::stdout().flush().unwrap();
}
fn complete(session: Arc<Mutex<Session>>) {
@@ -414,7 +284,7 @@ impl Session {
let mut se = session.lock().unwrap();
- se.type_bytes(&comp.shared_prefix);
+ se.line.type_bytes(&comp.shared_prefix).unwrap();
if comp.suggestions.len() > 1 {
print!("\r\n");
@@ -435,7 +305,7 @@ impl Session {
let parsed = match parse::do_parse(&line) {
Ok(p) => p,
Err(_) => {
- se.line.add(b'\n');
+ se.line.type_byte(b'\n').unwrap();
print!("\r\n> ");
return;
}
@@ -467,6 +337,27 @@ impl Session {
r.disable();
}
}
+
+ /// do cleanup actions after a bunch of mutations.
+ fn cohere(&mut self) -> io::Result<()> {
+ if self.line.is_dirty() {
+ self.line.mark_clean();
+
+ // overwrite line in terminal with syntax-highlighted line.
+ if self.highlighter.enabled {
+ let buf = self.line.into_bytes();
+ let mut parser = parse::Cursor::new(&buf, parse::ParseMode::Completion);
+ let _res = parse::Ast::parse(&mut parser);
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(cursor::Direction::Left, self.line.left_len(), &mut stdout)?;
+ self.highlighter
+ .pretty_print(&buf, parser.highlights, &mut stdout)?;
+ cursor::fmove_cursor(cursor::Direction::Left, self.line.right_len(), &mut stdout)?;
+ }
+ }
+
+ Ok(())
+ }
}
fn exec_rc_file(se: Arc<Mutex<Session>>) {
@@ -492,21 +383,14 @@ pub fn event_loop() {
let se = Session {
raw: Some(raw),
- line: LineBuf::new(),
- history: Vec::new(),
+ line: Line::new(),
builtins: run::builtin_map(),
prev_path: vec![b'.'],
history_visit: 0,
socket_running: None,
- vars: run::Vars::default(),
- funs: HashMap::new(),
- aliases: run::Aliases::new(),
path_cache: Default::default(),
ctrlc: Default::default(),
- debug_keystrokes: false,
- loud: false,
- ti_keybinds: HashMap::new(),
- ascii_keybinds: HashMap::new(),
+ ..Session::new_noninteractive()
};
let session = Arc::new(Mutex::new(se));
@@ -540,7 +424,7 @@ pub fn event_loop() {
}
match key {
- ansi::KbInput::Key([x]) => se.type_byte(x),
+ ansi::KbInput::Key([x]) => se.line.type_byte(x).unwrap(),
ansi::KbInput::Escape(escape) => {
for terminfo_key in escape.keys.iter() {
if let Some(cmd) = se.ti_keybinds.get(terminfo_key.as_bytes()) {
@@ -554,6 +438,8 @@ pub fn event_loop() {
}
ansi::KbInput::InvalidEscape(_) => continue,
}
+
+ se.cohere().unwrap();
}
session.lock().unwrap().raw_disable();
diff --git a/src/linebuf.rs b/src/line/buf.rs
index 548ec75..420546a 100644
--- a/src/linebuf.rs
+++ b/src/line/buf.rs
@@ -67,11 +67,11 @@ impl LineBuf {
n
}
- pub fn get_left(&self) -> Option<u8>{
+ pub fn get_left(&self) -> Option<u8> {
self.pre.last().cloned()
}
- pub fn get_right(&self) -> Option<u8>{
+ pub fn get_right(&self) -> Option<u8> {
self.post.last().cloned()
}
@@ -139,4 +139,12 @@ impl LineBuf {
move_cursor(Direction::Left, self.post.len() + post.len());
std::io::stdout().flush().unwrap();
}
+
+ pub fn left_len(&self) -> usize {
+ self.pre.len()
+ }
+
+ pub fn right_len(&self) -> usize {
+ self.post.len()
+ }
}
diff --git a/src/line/mod.rs b/src/line/mod.rs
new file mode 100644
index 0000000..72f2fe1
--- /dev/null
+++ b/src/line/mod.rs
@@ -0,0 +1,219 @@
+mod buf;
+
+use std::io::{self, Write};
+
+use crate::cursor::{self, Direction};
+pub use buf::LineBuf;
+
+pub struct Line {
+ buf: LineBuf,
+ dirty: bool,
+}
+
+impl std::ops::Deref for Line {
+ type Target = LineBuf;
+
+ fn deref(&self) -> &Self::Target {
+ &self.buf
+ }
+}
+
+impl Line {
+ pub fn new() -> Self {
+ Self {
+ buf: LineBuf::new(),
+ dirty: false,
+ }
+ }
+
+ pub fn mark_clean(&mut self) {
+ self.dirty = false;
+ }
+
+ pub fn is_dirty(&self) -> bool {
+ self.dirty
+ }
+
+ pub fn had_contents(&self) -> bool {
+ self.buf.is_dirty()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.buf.is_empty()
+ }
+
+ pub fn cursor_left(&mut self) -> io::Result<()> {
+ if self.buf.left() {
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(Direction::Left, 1, &mut stdout)?;
+ stdout.flush()?;
+ }
+ Ok(())
+ }
+
+ pub fn cursor_right(&mut self) -> io::Result<()> {
+ if self.buf.right() {
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(Direction::Right, 1, &mut stdout)?;
+ stdout.flush()?;
+ }
+ Ok(())
+ }
+
+ pub fn move_to_begin(&mut self) -> io::Result<()> {
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(Direction::Left, self.buf.all_left(), &mut stdout)?;
+ stdout.flush()
+ }
+
+ pub fn move_to_end(&mut self) -> io::Result<()> {
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(Direction::Right, self.buf.all_right(), &mut stdout)?;
+ stdout.flush()
+ }
+
+ // move to next
+ pub fn cursor_left_word(&mut self) -> io::Result<()> {
+ let mut i = 0;
+
+ // find word
+ while let Some(b' ') = self.buf.get_left() {
+ self.buf.left();
+ i += 1;
+ }
+
+ // skip it
+ while let Some(x) = self.buf.get_left()
+ && !x.is_ascii_whitespace()
+ {
+ self.buf.left();
+ i += 1;
+ }
+
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(Direction::Left, i, &mut stdout)?;
+ stdout.flush()
+ }
+
+ pub fn cursor_right_word(&mut self) -> io::Result<()> {
+ let mut i = 0;
+
+ // find word
+ while let Some(b' ') = self.buf.get_right() {
+ self.buf.right();
+ i += 1;
+ }
+
+ // skip it
+ while let Some(x) = self.buf.get_right()
+ && !x.is_ascii_whitespace()
+ {
+ self.buf.right();
+ i += 1;
+ }
+
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(Direction::Right, i, &mut stdout)?;
+ stdout.flush()
+ }
+}
+
+macro_rules! mutating_impls {
+ ($(pub fn $fun:ident(&mut $self:ident $($arg:tt)*) -> $ty:ty $body:block )*) => {
+ impl Line {$(
+ pub fn $fun(&mut $self $($arg)*) -> $ty {
+ $self.dirty = true;
+ $body
+ }
+ )*}
+ };
+}
+
+mutating_impls! {
+ pub fn clear_prompt(&mut self) -> io::Result<()> {
+ let mut stdout = io::stdout().lock();
+ cursor::fmove_cursor(
+ Direction::Right,
+ self.buf.distance_from_right_end(),
+ &mut stdout,
+ )?;
+ for _ in 0..self.buf.len() {
+ stdout.write_all(b"\x08 \x08")?;
+ }
+ self.buf.clear();
+ stdout.flush()
+ }
+
+ pub fn type_bytes(&mut self, bs: &[u8]) -> io::Result<()> {
+ for b in bs.iter() {
+ self.buf.add(*b);
+ }
+ io::stdout().lock().write_all(&bs)?;
+ self.buf.display_post(b"");
+ Ok(())
+ }
+
+ pub fn type_byte(&mut self, b: u8) -> io::Result<()> {
+ self.type_bytes(&[b])
+ }
+
+ pub fn del_left(&mut self) -> io::Result<()> {
+ if self.buf.del_left().is_some() {
+ cursor::fmove_cursor(Direction::Left, 1, &mut io::stdout())?;
+ self.buf.display_post(b" ");
+ }
+ Ok(())
+ }
+
+ pub fn del_right(&mut self) -> io::Result<()> {
+ self.buf.del_right();
+ self.buf.display_post(b" ");
+ Ok(())
+ }
+
+ pub fn del_left_word(&mut self) -> io::Result<()> {
+ let mut del = 0;
+ while let Some(x) = self.buf.get_left()
+ && x == b' '
+ {
+ self.buf.del_left();
+ del += 1;
+ }
+ while let Some(x) = self.buf.get_left()
+ && x != b' '
+ {
+ self.buf.del_left();
+ del += 1;
+ }
+ cursor::fmove_cursor(Direction::Left, del, &mut io::stdout())?;
+ self.buf.display_post(&vec![b' '; del]);
+ Ok(())
+ }
+
+ pub fn del_right_word(&mut self) -> io::Result<()> {
+ let mut del = 0;
+ while let Some(x) = self.buf.get_right()
+ && x == b' '
+ {
+ self.buf.del_right();
+ del += 1;
+ }
+ while let Some(x) = self.buf.get_right()
+ && x != b' '
+ {
+ self.buf.del_right();
+ del += 1;
+ }
+ self.buf.display_post(&vec![b' '; del]);
+ Ok(())
+ }
+
+ pub fn set_content(&mut self, content: Vec<u8>) -> io::Result<()> {
+ self.buf.set_content(content);
+ Ok(())
+ }
+
+ pub fn dump(&mut self) -> Vec<u8> {
+ self.buf.dump()
+ }
+}
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 102b334..97a6e4a 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -3,6 +3,8 @@ use crate::{BString, PushAll, bstr};
#[cfg(test)]
mod test;
+mod span;
+
pub trait Stage: PartialEq {
type Str: std::fmt::Debug + Clone + PartialEq;
}
@@ -908,6 +910,7 @@ impl Parse for ExpString {
let mut last_delim = StringDelimiter::None;
'outer: loop {
+ let begin = b.loc();
let Some(delim) = StringDelimiter::try_begin(b) else {
break;
};
@@ -1034,6 +1037,14 @@ impl Parse for ExpString {
add_char(p, x);
}
}
+
+ if !delim.is_none() {
+ let end = b.loc_u32();
+ b.highlights.push(Highlight {
+ span: begin.to(end),
+ kind: HighlightKind::String,
+ });
+ }
}
if already_parsed {
@@ -1285,6 +1296,18 @@ pub enum ParseMode {
Completion,
}
+#[derive(Copy, Clone)]
+pub enum HighlightKind {
+ Keyword(Keyword),
+ String,
+ None,
+}
+
+pub struct Highlight {
+ pub span: span::Span,
+ pub kind: HighlightKind,
+}
+
pub struct Cursor<'a> {
buf: &'a [u8],
mode: ParseMode,
@@ -1293,6 +1316,13 @@ pub struct Cursor<'a> {
spaced: bool,
pub backtrace: bool,
+
+ pub highlights: Vec<Highlight>,
+
+ file: span::FileId,
+
+ buf_start: u64,
+ buf_len: u32,
}
#[derive(Default)]
@@ -1305,11 +1335,20 @@ struct SpaceStats {
impl<'a> Cursor<'a> {
pub fn new(buf: &'a [u8], mode: ParseMode) -> Self {
+ assert!(
+ buf.len() < u32::MAX as usize,
+ "cannot support larger parse buffers for now - what are you even doing."
+ );
+
Self {
buf,
mode,
spaced: false,
backtrace: false,
+ highlights: Vec::new(),
+ file: span::FileId::new(),
+ buf_start: buf.as_ptr() as u64,
+ buf_len: buf.len() as u32,
}
}
@@ -1377,6 +1416,18 @@ impl<'a> Cursor<'a> {
}
}
+ fn loc_u32(&self) -> u32 {
+ let now_loc = self.buf.as_ptr() as u64;
+ assert!(now_loc >= self.buf_start, "not the original buffer");
+ let relative_loc = (now_loc - self.buf_start) as u32;
+ assert!(relative_loc <= self.buf_len, "not the original buffer");
+ relative_loc
+ }
+
+ fn loc(&self) -> span::SpanFrom {
+ self.file.from(self.loc_u32())
+ }
+
fn spaces_stats(&mut self) -> SpaceStats {
let mut stats = SpaceStats::default();
while self.has() && b" \t\n\r".contains(&self.buf[0]) {
@@ -1418,7 +1469,8 @@ impl<'a> Cursor<'a> {
}
self.spaces();
- if self.buf.starts_with(bytes) {
+ let span = self.loc().with_len(bytes.len() as u32);
+ let result = if self.buf.starts_with(bytes) {
if kw.requires_space() {
if self.buf.len() > bytes.len() && self.buf[bytes.len()].is_ascii_whitespace() {
self.buf = &self.buf[bytes.len() + 1..];
@@ -1439,11 +1491,20 @@ impl<'a> Cursor<'a> {
}
} else {
Err(ParseError::ExpectedKeyword(kw))
+ };
+
+ if result.is_ok() {
+ self.highlights.push(Highlight {
+ span,
+ kind: HighlightKind::Keyword(kw),
+ })
}
+
+ result
}
}
-#[derive(Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Keyword {
If,
While,
@@ -1546,8 +1607,8 @@ impl Parse for While {
}
}
-impl Parse for Ast<PreExpansion> {
- fn parse(b: &mut Cursor<'_>) -> Result<Self> {
+impl Ast<PreExpansion> {
+ fn parse_inner(b: &mut Cursor<'_>) -> Result<Self> {
b.spaces();
let orig_len = b.buf.len();
@@ -1586,6 +1647,21 @@ impl Parse for Ast<PreExpansion> {
}
}
+impl Parse for Ast<PreExpansion> {
+ fn parse(b: &mut Cursor<'_>) -> Result<Self> {
+ let begin = b.loc();
+ let result = Ast::parse_inner(b);
+ let span = begin.to(b.loc_u32());
+ if result.is_ok() {
+ b.highlights.push(Highlight {
+ span,
+ kind: HighlightKind::None,
+ });
+ }
+ result
+ }
+}
+
impl Parse for Command<PreExpansion> {
fn parse(b: &mut Cursor<'_>) -> Result<Self> {
let path: ExpString = b.parse()?;
diff --git a/src/parse/span.rs b/src/parse/span.rs
new file mode 100644
index 0000000..340a078
--- /dev/null
+++ b/src/parse/span.rs
@@ -0,0 +1,86 @@
+use std::sync::atomic::AtomicU32;
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct FileId {
+ id: u32,
+}
+
+impl FileId {
+ pub fn new() -> Self {
+ static GEN: AtomicU32 = AtomicU32::new(0);
+ Self {
+ id: GEN.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
+ }
+ }
+
+ pub fn from(self, start: u32) -> SpanFrom {
+ SpanFrom::new(self, start)
+ }
+
+ pub fn span(self, start: u32, end: u32) -> Span {
+ Span::new(self, start, end)
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct SpanFrom {
+ pub file: FileId,
+ pub start: u32,
+}
+
+impl SpanFrom {
+ pub fn new(file: FileId, start: u32) -> Self {
+ Self { file, start }
+ }
+
+ pub fn to(self, end: u32) -> Span {
+ Span::new(self.file, self.start, end)
+ }
+
+ pub fn with_len(self, len: u32) -> Span {
+ self.to(self.start + len)
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Span {
+ pub file: FileId,
+ pub start: u32,
+ pub end: u32,
+}
+
+/// manual implementation of PartialOrd to ensure shorter Spans are first
+impl PartialOrd for Span {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ match self.file.partial_cmp(&other.file) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ match self.start.partial_cmp(&other.start) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ other.end.partial_cmp(&self.end)
+ }
+}
+
+/// manual implementation of Ord to ensure shorter Spans are first
+impl Ord for Span {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ match self.file.cmp(&other.file) {
+ core::cmp::Ordering::Equal => {}
+ ord => return ord,
+ }
+ match self.start.cmp(&other.start) {
+ core::cmp::Ordering::Equal => {}
+ ord => return ord,
+ }
+ other.end.cmp(&self.end)
+ }
+}
+
+impl Span {
+ pub fn new(file: FileId, start: u32, end: u32) -> Self {
+ Self { file, start, end }
+ }
+}
diff --git a/src/run/builtin.rs b/src/run/builtin.rs
index 638cfcd..c946472 100644
--- a/src/run/builtin.rs
+++ b/src/run/builtin.rs
@@ -802,37 +802,38 @@ impl Builtin for ct {
return Err(Error::Exit(-1));
};
- let mut se = session.lock().unwrap();
-
- match &arg[..] {
- b"cursor_begin" => se.move_to_begin(),
- b"cursor_end" => se.move_to_end(),
- b"cursor_right" => se.cursor_right(),
- b"cursor_left" => se.cursor_left(),
- b"cursor_right_word" => se.cursor_right_word(),
- b"cursor_left_word" => se.cursor_left_word(),
- b"prompt_clear" => se.prompt_clear(),
- b"screen_clear" => {
- drop(se);
- Session::screen_clear(session);
- }
- b"history_previous" => se.history_up(),
- b"history_next" => se.history_down(),
- b"prompt_del_left" => se.del_left(),
- b"prompt_del_right" => se.del_right(),
- b"prompt_del_left_word" => se.del_left_word(),
- b"prompt_del_right_word" => se.del_right_word(),
- b"prompt_del_left_or_previous" => se.del_left_or_previous(),
- b"prompt_pipe_previous" => se.prompt_pipe_previous(),
- b"complete" => {
- drop(se);
- Session::complete(session)
- }
- b"try_submit_command" => {
- drop(se);
- Session::try_submit_command(session);
+ {
+ let mut se = session.lock().unwrap();
+ match &arg[..] {
+ b"cursor_begin" => se.line.move_to_begin().unwrap(),
+ b"cursor_end" => se.line.move_to_end().unwrap(),
+ b"cursor_right" => se.line.cursor_right().unwrap(),
+ b"cursor_left" => se.line.cursor_left().unwrap(),
+ b"cursor_right_word" => se.line.cursor_right_word().unwrap(),
+ b"cursor_left_word" => se.line.cursor_left_word().unwrap(),
+ b"prompt_clear" => se.prompt_clear(),
+ b"screen_clear" => {
+ drop(se);
+ Session::screen_clear(session.clone());
+ }
+ b"history_previous" => se.history_up(),
+ b"history_next" => se.history_down(),
+ b"prompt_del_left" => se.line.del_left().unwrap(),
+ b"prompt_del_right" => se.line.del_right().unwrap(),
+ b"prompt_del_left_word" => se.line.del_left_word().unwrap(),
+ b"prompt_del_right_word" => se.line.del_right_word().unwrap(),
+ b"prompt_del_left_or_previous" => se.del_left_or_previous(),
+ b"prompt_pipe_previous" => se.prompt_pipe_previous(),
+ b"complete" => {
+ drop(se);
+ Session::complete(session.clone())
+ }
+ b"try_submit_command" => {
+ drop(se);
+ Session::try_submit_command(session.clone());
+ }
+ _ => return Err(Error::Exit(-2)),
}
- _ => return Err(Error::Exit(-2)),
}
Ok(())
diff --git a/src/syntax_highlighting.rs b/src/syntax_highlighting.rs
new file mode 100644
index 0000000..8a369da
--- /dev/null
+++ b/src/syntax_highlighting.rs
@@ -0,0 +1,76 @@
+use crate::parse::{Highlight, HighlightKind, Keyword};
+
+pub struct Highlighter {
+ pub enabled: bool,
+}
+
+impl Highlighter {
+ pub fn new() -> Self {
+ Self { enabled: true }
+ }
+
+ pub fn color(&self, h: HighlightKind) -> &[u8] {
+ // TODO: configurable
+ const GREEN: &[u8] = b"\x1b[32m";
+ const BLUE: &[u8] = b"\x1b[36m";
+ const MAGENTA: &[u8] = b"\x1b[95m";
+ const COLOR_RESET: &[u8] = b"\x1b[0m";
+ match h {
+ HighlightKind::Keyword(
+ Keyword::If | Keyword::Elif | Keyword::Else | Keyword::While,
+ ) => GREEN,
+ HighlightKind::Keyword(Keyword::OpenBrace | Keyword::CloseBrace) => BLUE,
+ HighlightKind::String => MAGENTA,
+ HighlightKind::None => COLOR_RESET,
+ }
+ }
+
+ pub fn pretty_print(
+ &self,
+ bytes: &[u8],
+ colors: Vec<Highlight>,
+ stdout: &mut dyn std::io::Write,
+ ) -> std::io::Result<()> {
+ let mut coloring: Vec<_> = colors
+ .into_iter()
+ .flat_map(|hi| {
+ [
+ (hi.span.start as usize, false, hi.kind),
+ (hi.span.end as usize, true, hi.kind),
+ ]
+ })
+ .collect();
+ coloring.sort_by_key(|x| (x.0, x.1));
+ let mut coloring = &coloring[..];
+ let mut color_stack = Vec::new();
+
+ let mut current_color = self.color(HighlightKind::None);
+
+ for (i, x) in bytes.iter().cloned().enumerate() {
+ while let Some((k, is_end, kind)) = coloring.first().cloned()
+ && k == i
+ {
+ coloring = &coloring[1..];
+ if is_end {
+ color_stack.pop();
+ } else {
+ color_stack.push(kind);
+ }
+
+ let new_color =
+ self.color(color_stack.last().cloned().unwrap_or(HighlightKind::None));
+
+ if current_color != new_color {
+ stdout.write_all(new_color)?;
+ current_color = new_color;
+ }
+ }
+
+ stdout.write_all(&[x])?;
+ }
+
+ stdout.write_all(self.color(HighlightKind::None))?;
+
+ Ok(())
+ }
+}