mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Syntax highlighting
This commit is contained in:
parent
c523ae0f48
commit
9a639fd27b
3 changed files with 83 additions and 15 deletions
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue