Skip to content

Commit

Permalink
Add --color option (tweag#1033)
Browse files Browse the repository at this point in the history
This flag has three possible values: auto, always and never. Its default
is 'auto'.

With 'auto' selected, we allow the codespan_reporting and rustyline libs
to determine whether to show colors based on the current terminal
settings.

With 'always' selected, we force-enable colors in both error reporting
and the repl.

With 'never' selected, we disable all colors in output.
  • Loading branch information
matthew-healy authored Jan 6, 2023
1 parent 50f4772 commit 1b65525
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 17 deletions.
10 changes: 8 additions & 2 deletions src/bin/nickel.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Entry point of the program.
use nickel_lang::error::{Error, IOError};
use nickel_lang::eval::cache::CBNCache;
use nickel_lang::program::Program;
use nickel_lang::program::{ColorOpt, Program};
use nickel_lang::repl::query_print;
#[cfg(feature = "repl")]
use nickel_lang::repl::rustyline_frontend;
Expand Down Expand Up @@ -31,6 +31,10 @@ struct Opt {
#[structopt(long)]
nostdlib: bool,

/// Coloring: auto, always, never.
#[structopt(long, global = true, case_insensitive = true, default_value = "auto")]
color: ColorOpt,

#[structopt(subcommand)]
command: Option<Command>,
}
Expand Down Expand Up @@ -100,7 +104,7 @@ fn main() {
.join(".nickel_history")
};
#[cfg(feature = "repl")]
if rustyline_frontend::repl(histfile).is_err() {
if rustyline_frontend::repl(histfile, opts.color).is_err() {
process::exit(1);
}

Expand All @@ -122,6 +126,8 @@ fn main() {
program.set_skip_stdlib();
}

program.set_color(opts.color);

let result = match opts.command {
Some(Command::PprintAst { transform }) => program.pprint_ast(
&mut std::io::BufWriter::new(Box::new(std::io::stdout())),
Expand Down
58 changes: 52 additions & 6 deletions src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ use std::ffi::OsString;
use std::io::{self, Read};
use std::result::Result;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ColorOpt {
Auto,
Always,
Never,
}

impl std::str::FromStr for ColorOpt {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"auto" => Ok(Self::Auto),
"always" => Ok(Self::Always),
"never" => Ok(Self::Never),
_ => Err("possible values are 'auto', 'always' or 'never'."),
}
}
}

/// A Nickel program.
///
/// Manage a file database, which stores the original source code of the program and eventually the
Expand All @@ -43,6 +63,8 @@ pub struct Program<EC: EvalCache> {
main_id: FileId,
/// The state of the Nickel virtual machine.
vm: VirtualMachine<Cache, EC>,
/// The color option to use when reporting errors.
color_opt: ColorOpt,
}

impl<EC: EvalCache> Program<EC> {
Expand All @@ -56,7 +78,11 @@ impl<EC: EvalCache> Program<EC> {
let main_id = cache.add_file(path)?;
let vm = VirtualMachine::new(cache);

Ok(Self { main_id, vm })
Ok(Self {
main_id,
vm,
color_opt: ColorOpt::Auto,
})
}

/// Create a program by reading it from a generic source.
Expand All @@ -69,7 +95,11 @@ impl<EC: EvalCache> Program<EC> {
let main_id = cache.add_source(source_name, source)?;
let vm = VirtualMachine::new(cache);

Ok(Self { main_id, vm })
Ok(Self {
main_id,
vm,
color_opt: ColorOpt::Auto,
})
}

/// Retrieve the parsed term and typecheck it, and generate a fresh initial environment. Return
Expand Down Expand Up @@ -130,7 +160,7 @@ impl<EC: EvalCache> Program<EC> {
where
E: ToDiagnostic<FileId>,
{
report(self.vm.import_resolver_mut(), error)
report(self.vm.import_resolver_mut(), error, self.color_opt)
}

/// Create a markdown file with documentation for the specified program in `.nickel/doc/program_main_file_name.md`
Expand All @@ -144,6 +174,10 @@ impl<EC: EvalCache> Program<EC> {
self.vm.import_resolver_mut().skip_stdlib = true;
}

pub fn set_color(&mut self, c: ColorOpt) {
self.color_opt = c;
}

pub fn pprint_ast(
&mut self,
out: &mut std::io::BufWriter<Box<dyn std::io::Write>>,
Expand All @@ -152,7 +186,9 @@ impl<EC: EvalCache> Program<EC> {
use crate::pretty::*;
use pretty::BoxAllocator;

let Program { ref main_id, vm } = self;
let Program {
ref main_id, vm, ..
} = self;
let allocator = BoxAllocator;

let rt = vm.import_resolver().parse_nocache(*main_id)?.0;
Expand Down Expand Up @@ -224,11 +260,11 @@ pub fn query<EC: EvalCache>(
/// to produce a diagnostic (see `crate::error::label_alt`).
//TODO: not sure where this should go. It seems to embed too much logic to be in `Cache`, but is
//common to both `Program` and `Repl`. Leaving it here as a stand-alone function for now
pub fn report<E>(cache: &mut Cache, error: E)
pub fn report<E>(cache: &mut Cache, error: E, color_opt: ColorOpt)
where
E: ToDiagnostic<FileId>,
{
let writer = StandardStream::stderr(ColorChoice::Always);
let writer = StandardStream::stderr(color_opt.into());
let config = codespan_reporting::term::Config::default();
let contracts_id = cache.id_of("<stdlib/contract.ncl>");
let diagnostics = error.to_diagnostic(cache.files_mut(), contracts_id);
Expand All @@ -245,6 +281,16 @@ where
};
}

impl From<ColorOpt> for ColorChoice {
fn from(c: ColorOpt) -> Self {
match c {
ColorOpt::Auto => ColorChoice::Auto,
ColorOpt::Always => ColorChoice::Always,
ColorOpt::Never => ColorChoice::Never,
}
}
}

#[cfg(feature = "doc")]
mod doc {
use crate::cache::Cache;
Expand Down
39 changes: 30 additions & 9 deletions src/repl/rustyline_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,59 @@ use std::path::PathBuf;
use super::command::Command;
use super::*;

use crate::program;
use crate::program::{self, ColorOpt};
use ansi_term::{Colour, Style};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
use rustyline::{Config, EditMode, Editor};

/// The config of rustyline's editor.
pub fn config() -> Config {
pub fn config(color_opt: ColorOpt) -> Config {
Config::builder()
.history_ignore_space(true)
.edit_mode(EditMode::Emacs)
.color_mode(color_opt.into())
.output_stream(OutputStreamType::Stdout)
.build()
}

impl From<ColorOpt> for rustyline::config::ColorMode {
fn from(c: ColorOpt) -> Self {
use rustyline::config::ColorMode;
match c {
ColorOpt::Always => ColorMode::Forced,
ColorOpt::Auto => ColorMode::Enabled,
ColorOpt::Never => ColorMode::Disabled,
}
}
}

/// Main loop of the REPL.
pub fn repl(histfile: PathBuf) -> Result<(), InitError> {
pub fn repl(histfile: PathBuf, color_opt: ColorOpt) -> Result<(), InitError> {
let mut repl = ReplImpl::<crate::eval::cache::CBNCache>::new();

match repl.load_stdlib() {
Ok(()) => (),
Err(err) => {
program::report(repl.cache_mut(), err);
program::report(repl.cache_mut(), err, color_opt);
return Err(InitError::Stdlib);
}
}

let validator = InputParser::new(repl.cache_mut().add_tmp("<repl-input>", String::new()));

let mut editor = Editor::with_config(config());
let mut editor = Editor::with_config(config(color_opt));
let _ = editor.load_history(&histfile);
editor.set_helper(Some(validator));
let prompt = Style::new().fg(Colour::Green).paint("nickel> ").to_string();
let prompt = {
let style = Style::new();
let style = if color_opt != ColorOpt::Never {
style.fg(Colour::Green)
} else {
style
};
style.paint("nickel> ").to_string()
};

let result = loop {
let line = editor.readline(&prompt);
Expand Down Expand Up @@ -80,7 +100,7 @@ pub fn repl(histfile: PathBuf) -> Result<(), InitError> {
match repl.eval_full(&exp) {
Ok(EvalResult::Evaluated(rt)) => println!("{}\n", rt.as_ref().deep_repr()),
Ok(EvalResult::Bound(_)) => (),
Err(err) => program::report(repl.cache_mut(), err),
Err(err) => program::report(repl.cache_mut(), err, color_opt),
};
Ok(())
}
Expand All @@ -96,7 +116,7 @@ pub fn repl(histfile: PathBuf) -> Result<(), InitError> {
};

if let Err(err) = result {
program::report(repl.cache_mut(), err);
program::report(repl.cache_mut(), err, color_opt);
} else {
println!();
}
Expand All @@ -105,7 +125,7 @@ pub fn repl(histfile: PathBuf) -> Result<(), InitError> {
match repl.eval_full(&line) {
Ok(EvalResult::Evaluated(rt)) => println!("{}\n", rt.as_ref().deep_repr()),
Ok(EvalResult::Bound(_)) => (),
Err(err) => program::report(repl.cache_mut(), err),
Err(err) => program::report(repl.cache_mut(), err, color_opt),
};
}
Err(ReadlineError::Eof) => {
Expand All @@ -118,6 +138,7 @@ pub fn repl(histfile: PathBuf) -> Result<(), InitError> {
program::report(
repl.cache_mut(),
Error::IOError(IOError(format!("{}", err))),
color_opt,
);
}
}
Expand Down

0 comments on commit 1b65525

Please sign in to comment.