Syntax highlighting

This commit is contained in:
Yehuda Katz 2019-05-30 17:53:54 -07:00
parent c523ae0f48
commit 9a639fd27b
3 changed files with 83 additions and 15 deletions

View file

@ -14,7 +14,7 @@ use parser::PipelineParser;
pub fn parse(input: &str, _registry: &dyn CommandRegistry) -> Result<Pipeline, ShellError> { pub fn parse(input: &str, _registry: &dyn CommandRegistry) -> Result<Pipeline, ShellError> {
let parser = PipelineParser::new(); let parser = PipelineParser::new();
let tokens = Lexer::new(input); let tokens = Lexer::new(input, false);
match parser.parse(tokens) { match parser.parse(tokens) {
Ok(val) => Ok(val), Ok(val) => Ok(val),

View file

@ -375,14 +375,15 @@ pub enum Token {
crate struct Lexer<'source> { crate struct Lexer<'source> {
lexer: logos::Lexer<TopToken, &'source str>, lexer: logos::Lexer<TopToken, &'source str>,
first: bool, first: bool,
// state: LexerState, whitespace: bool, // state: LexerState
} }
impl Lexer<'source> { impl Lexer<'source> {
crate fn new(source: &str) -> Lexer<'_> { crate fn new(source: &str, whitespace: bool) -> Lexer<'_> {
Lexer { Lexer {
first: true, first: true,
lexer: logos::Logos::lexer(source), lexer: logos::Logos::lexer(source),
whitespace
// state: LexerState::default(), // state: LexerState::default(),
} }
} }
@ -400,7 +401,7 @@ impl Iterator for Lexer<'source> {
TopToken::Error => { TopToken::Error => {
return Some(Err(lex_error(&self.lexer.range(), self.lexer.source))) return Some(Err(lex_error(&self.lexer.range(), self.lexer.source)))
} }
TopToken::Whitespace => return self.next(), TopToken::Whitespace if !self.whitespace => return self.next(),
other => { other => {
return spanned(other.to_token()?, self.lexer.slice(), &self.lexer.range()) return spanned(other.to_token()?, self.lexer.slice(), &self.lexer.range())
} }
@ -415,7 +416,7 @@ impl Iterator for Lexer<'source> {
match token { match token {
TopToken::Error => return Some(Err(lex_error(&range, self.lexer.source))), TopToken::Error => return Some(Err(lex_error(&range, self.lexer.source))),
TopToken::Whitespace => return self.next(), TopToken::Whitespace if !self.whitespace => return self.next(),
other => return spanned(other.to_token()?, slice, &range), other => return spanned(other.to_token()?, slice, &range),
} }
} }
@ -429,7 +430,7 @@ impl Iterator for Lexer<'source> {
AfterMemberDot::Error => { AfterMemberDot::Error => {
return Some(Err(lex_error(&range, self.lexer.source))) return Some(Err(lex_error(&range, self.lexer.source)))
} }
AfterMemberDot::Whitespace => self.next(), AfterMemberDot::Whitespace if !self.whitespace => self.next(),
other => return spanned(other.to_token()?, slice, &range), other => return spanned(other.to_token()?, slice, &range),
} }
} }
@ -443,7 +444,7 @@ impl Iterator for Lexer<'source> {
AfterVariableToken::Error => { AfterVariableToken::Error => {
return Some(Err(lex_error(&range, self.lexer.source))) return Some(Err(lex_error(&range, self.lexer.source)))
} }
AfterVariableToken::Whitespace => self.next(), AfterVariableToken::Whitespace if !self.whitespace => self.next(),
other => return spanned(other.to_token()?, slice, &range), other => return spanned(other.to_token()?, slice, &range),
} }
@ -508,7 +509,7 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
fn assert_lex(source: &str, tokens: &[TestToken<'_>]) { fn assert_lex(source: &str, tokens: &[TestToken<'_>]) {
let lex = Lexer::new(source); let lex = Lexer::new(source, false);
let mut current = 0; let mut current = 0;
let expected_tokens: Vec<SpannedToken> = tokens let expected_tokens: Vec<SpannedToken> = tokens

View file

@ -1,15 +1,17 @@
use crate::shell::completer::NuCompleter; use crate::shell::completer::NuCompleter;
use crate::parser::lexer::SpannedToken;
use crate::prelude::*; use crate::prelude::*;
use ansi_term::Color;
use log::debug;
use rustyline::completion::{self, Completer, FilenameCompleter}; use rustyline::completion::{self, Completer, FilenameCompleter};
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter}; use rustyline::highlight::Highlighter;
use rustyline::hint::{Hinter, HistoryHinter}; use rustyline::hint::{Hinter, HistoryHinter};
use std::borrow::Cow::{self, Owned}; use std::borrow::Cow::{self, Owned};
crate struct Helper { crate struct Helper {
completer: NuCompleter, completer: NuCompleter,
highlighter: MatchingBracketHighlighter,
hinter: HistoryHinter, hinter: HistoryHinter,
} }
@ -20,7 +22,6 @@ impl Helper {
file_completer: FilenameCompleter::new(), file_completer: FilenameCompleter::new(),
commands, commands,
}, },
highlighter: MatchingBracketHighlighter::new(),
hinter: HistoryHinter {}, hinter: HistoryHinter {},
} }
} }
@ -54,12 +55,78 @@ impl Highlighter for Helper {
Owned("\x1b[1m".to_owned() + hint + "\x1b[m") Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
} }
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
self.highlighter.highlight(line, pos) let tokens = crate::parser::lexer::Lexer::new(line, true);
let tokens: Result<Vec<(usize, SpannedToken, usize)>, _> = tokens.collect();
match tokens {
Err(_) => Cow::Borrowed(line),
Ok(v) => {
let mut out = String::new();
let mut iter = v.iter();
let mut state = State::Command;
loop {
match iter.next() {
None => return Cow::Owned(out),
Some((start, token, end)) => {
let (style, new_state) = token_style(&token, state);
debug!("token={:?}", token);
debug!("style={:?}", style);
debug!("new_state={:?}", new_state);
state = new_state;
let slice = &line[*start..*end];
let styled = style.paint(slice);
out.push_str(&styled.to_string());
}
}
}
}
}
} }
fn highlight_char(&self, line: &str, pos: usize) -> bool { fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
self.highlighter.highlight_char(line, pos) true
}
}
#[derive(Debug)]
enum State {
Command,
Flag,
Var,
Bare,
None,
}
fn token_style(
token: &crate::parser::lexer::SpannedToken,
state: State,
) -> (ansi_term::Style, State) {
use crate::parser::lexer::Token::*;
match (state, &token.token) {
(State::Command, Bare) => (Color::Cyan.bold(), State::None),
(State::Command, Whitespace) => (Color::White.normal(), State::Command),
(State::Flag, Bare) => (Color::Black.bold(), State::None),
(State::Var, Variable) => (Color::Yellow.bold(), State::None),
(State::Bare, Dot) => (Color::Green.normal(), State::Bare),
(State::Bare, Member) => (Color::Green.normal(), State::Bare),
(_, Dash) | (_, DashDash) => (Color::Black.bold(), State::Flag),
(_, Dollar) => (Color::Yellow.bold(), State::Var),
(_, Bare) => (Color::Green.normal(), State::Bare),
(_, Member) => (Color::Cyan.normal(), State::None),
(_, Num) => (Color::Purple.bold(), State::None),
(_, DQString) | (_, SQString) => (Color::Green.normal(), State::None),
(_, Pipe) => (Color::White.normal(), State::Command),
_ => (Color::White.normal(), State::None),
} }
} }