diff --git a/chisel/src/bin/chisel.rs b/chisel/src/bin/chisel.rs index 77f68033e084..2f024272833e 100644 --- a/chisel/src/bin/chisel.rs +++ b/chisel/src/bin/chisel.rs @@ -59,16 +59,17 @@ pub enum ChiselParserSub { #[tokio::main] async fn main() -> eyre::Result<()> { + #[cfg(windows)] + if !Paint::enable_windows_ascii() { + Paint::disable() + } + // Parse command args let args = ChiselParser::parse(); // Keeps track of whether or not an interrupt was the last input let mut interrupt = false; - // Create a new rustyline Editor - let mut rl = Editor::::new()?; - rl.set_helper(Some(SolidityHelper)); - // Load configuration let (config, evm_opts) = args.load_config_and_evm_opts()?; @@ -127,6 +128,10 @@ async fn main() -> eyre::Result<()> { None => { /* No chisel subcommand present; Continue */ } } + // Create a new rustyline Editor + let mut rl = Editor::::new()?; + rl.set_helper(Some(SolidityHelper::default())); + // Print welcome header println!("Welcome to Chisel! Type `{}` to show available commands.", Paint::green("!help")); @@ -135,9 +140,10 @@ async fn main() -> eyre::Result<()> { // Get the prompt from the dispatcher // Variable based on status of the last entry let prompt = dispatcher.get_prompt(); + rl.helper_mut().unwrap().set_errored(dispatcher.errored); // Read the next line - let next_string = rl.readline(prompt.as_str()); + let next_string = rl.readline(prompt.as_ref()); // Try to read the string match next_string { diff --git a/chisel/src/dispatcher.rs b/chisel/src/dispatcher.rs index e3dbbbb33043..10caf5ed38a6 100644 --- a/chisel/src/dispatcher.rs +++ b/chisel/src/dispatcher.rs @@ -21,12 +21,14 @@ use regex::Regex; use reqwest::Url; use serde::{Deserialize, Serialize}; use solang_parser::diagnostics::Diagnostic; -use std::{error::Error, io::Write, path::PathBuf, process::Command, str::FromStr}; +use std::{borrow::Cow, error::Error, io::Write, path::PathBuf, process::Command, str::FromStr}; use strum::IntoEnumIterator; use yansi::Paint; -/// Prompt arrow slice +/// Prompt arrow character pub static PROMPT_ARROW: char = '➜'; +static DEFAULT_PROMPT: &str = "➜ "; + /// Command leader character pub static COMMAND_LEADER: char = '!'; /// Chisel character @@ -121,17 +123,21 @@ impl ChiselDispatcher { ChiselSession::new(config).map(|session| Self { errored: false, session }) } - /// Returns the prompt given the last input's error status - pub fn get_prompt(&self) -> String { - format!( - "{}{} ", - self.session - .id - .as_ref() - .map(|id| format!("({}: {}) ", Paint::cyan("ID"), Paint::yellow(id))) - .unwrap_or_default(), - if self.errored { Paint::red(PROMPT_ARROW) } else { Paint::green(PROMPT_ARROW) } - ) + /// Returns the prompt based on the current status of the Dispatcher + pub fn get_prompt(&self) -> Cow<'static, str> { + match self.session.id.as_deref() { + // `(ID: {id}) ➜ ` + Some(id) => { + let mut prompt = String::with_capacity(DEFAULT_PROMPT.len() + id.len() + 7); + prompt.push_str("(ID: "); + prompt.push_str(id); + prompt.push_str(") "); + prompt.push_str(DEFAULT_PROMPT); + Cow::Owned(prompt) + } + // `➜ ` + None => Cow::Borrowed(DEFAULT_PROMPT), + } } /// Dispatches a [ChiselCommand] diff --git a/chisel/src/solidity_helper.rs b/chisel/src/solidity_helper.rs index aa29609618e6..b053be1e02ff 100644 --- a/chisel/src/solidity_helper.rs +++ b/chisel/src/solidity_helper.rs @@ -3,7 +3,10 @@ //! This module contains the `SolidityHelper`, a [rustyline::Helper] implementation for //! usage in Chisel. It is ported from [soli](https://github.com/jpopesculian/soli/blob/master/src/main.rs). -use crate::prelude::{ChiselCommand, COMMAND_LEADER}; +use crate::{ + dispatcher::PROMPT_ARROW, + prelude::{ChiselCommand, COMMAND_LEADER}, +}; use rustyline::{ completion::Completer, highlight::Highlighter, @@ -34,9 +37,24 @@ const MAX_ANSI_LEN: usize = 9; pub type SpannedStyle = (usize, Style, usize); /// A rustyline helper for Solidity code -pub struct SolidityHelper; +#[derive(Clone, Debug, Default)] +pub struct SolidityHelper { + /// Whether the dispatcher has errored. + pub errored: bool, +} impl SolidityHelper { + /// Create a new SolidityHelper. + pub fn new() -> Self { + Self::default() + } + + /// Set the errored field. + pub fn set_errored(&mut self, errored: bool) -> &mut Self { + self.errored = errored; + self + } + /// Get styles for a solidity source string pub fn get_styles(input: &str) -> Vec { let mut comments = Vec::with_capacity(DEFAULT_COMMENTS); @@ -185,9 +203,20 @@ impl SolidityHelper { /// `Paint::is_enabled` #[inline] fn paint_unchecked(string: &str, style: Style, out: &mut String) { - let _ = style.fmt_prefix(out); - out.push_str(string); - let _ = style.fmt_suffix(out); + if style == Style::default() { + out.push_str(string); + } else { + let _ = style.fmt_prefix(out); + out.push_str(string); + let _ = style.fmt_suffix(out); + } + } + + #[inline] + fn paint_unchecked_owned(string: &str, style: Style) -> String { + let mut out = String::with_capacity(MAX_ANSI_LEN + string.len()); + Self::paint_unchecked(string, style, &mut out); + out } /// Whether to skip parsing this input due to known errors or panics @@ -207,6 +236,41 @@ impl Highlighter for SolidityHelper { fn highlight_char(&self, line: &str, pos: usize) -> bool { pos == line.len() } + + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + _default: bool, + ) -> Cow<'b, str> { + if !Paint::is_enabled() { + return Cow::Borrowed(prompt) + } + + let mut out = prompt.to_string(); + + // `^(\(ID: .*?\) )? ➜ ` + if prompt.starts_with("(ID: ") { + let id_end = prompt.find(')').unwrap(); + let id_span = 5..id_end; + let id = &prompt[id_span.clone()]; + out.replace_range(id_span, &Self::paint_unchecked_owned(id, Color::Yellow.style())); + out.replace_range(1..=2, &Self::paint_unchecked_owned("ID", Color::Cyan.style())); + } + + if let Some(i) = out.find(PROMPT_ARROW) { + let style = if self.errored { Color::Red.style() } else { Color::Green.style() }; + + let mut arrow = String::with_capacity(MAX_ANSI_LEN + 4); + + let _ = style.fmt_prefix(&mut arrow); + arrow.push(PROMPT_ARROW); + let _ = style.fmt_suffix(&mut arrow); + + out.replace_range(i..=i + 2, &arrow); + } + + Cow::Owned(out) + } } impl Validator for SolidityHelper { @@ -236,7 +300,8 @@ impl<'a> TokenStyle for Token<'a> { fn style(&self) -> Style { use Token::*; match self { - StringLiteral(_, _) => Style::new(Color::Green), + StringLiteral(_, _) => Color::Green.style(), + AddressLiteral(_) | HexLiteral(_) | Number(_, _) | @@ -245,18 +310,23 @@ impl<'a> TokenStyle for Token<'a> { True | False | This => Color::Yellow.style(), + Memory | Storage | Calldata | Public | Private | Internal | External | Constant | Pure | View | Payable | Anonymous | Indexed | Abstract | Virtual | Override | Modifier | Immutable | Unchecked => Color::Cyan.style(), + Contract | Library | Interface | Function | Pragma | Import | Struct | Event | Enum | Type | Constructor | As | Is | Using | New | Delete | Do | Continue | Break | Throw | Emit | Return | Returns | Revert | For | While | If | Else | Try | Catch | Assembly | Let | Leave | Switch | Case | Default | YulArrow | Arrow => { Color::Magenta.style() } + Uint(_) | Int(_) | Bytes(_) | Byte | DynamicBytes | Bool | Address | String | Mapping => Color::Blue.style(), + Identifier(_) => Style::default(), + _ => Style::default(), } }