roc/crates/repl_ui/src/lib.rs
2024-02-14 18:09:41 +01:00

126 lines
4.2 KiB
Rust

//! UI functionality, shared between CLI and web, for the Read-Evaluate-Print-Loop (REPL).
// We don't do anything here related to the terminal (doesn't exist on the web) or LLVM (too big for the web).
pub mod colors;
pub mod repl_state;
use bumpalo::Bump;
use colors::{CYAN, END_COL, GREEN};
use const_format::concatcp;
use repl_state::{parse_src, ParseOutcome};
use roc_parse::ast::{Expr, ValueDef};
use roc_repl_eval::gen::{Problems, ReplOutput};
use roc_reporting::report::StyleCodes;
// TODO add link to repl tutorial (does not yet exist).
pub const TIPS: &str = concatcp!(
"\nEnter an expression to evaluate, or a definition (like ",
CYAN,
"x = 1",
END_COL,
") to use later.\n\n",
if cfg!(target_family = "wasm") {
"" // In the web repl, we render tips in the UI around the repl instead of in the repl itself.
} else {
// We use ctrl-v + ctrl-j for newlines because on Unix, terminals cannot distinguish between Shift-Enter and Enter
concatcp!(
CYAN,
" - ",
END_COL,
GREEN,
"ctrl-v",
END_COL,
" + ",
GREEN,
"ctrl-j",
END_COL,
" makes a newline\n",
CYAN,
" - ",
END_COL,
GREEN,
":q",
END_COL,
" quits\n",
CYAN,
" - ",
END_COL,
GREEN,
":help",
END_COL,
" shows this text again\n",
)
}
);
// For when nothing is entered in the repl
// TODO add link to repl tutorial(does not yet exist).
pub const SHORT_INSTRUCTIONS: &str = "Enter an expression, or :help, or :q to quit.\n\n";
pub const PROMPT: &str = concatcp!(CYAN, "»", END_COL, " ");
pub const CONT_PROMPT: &str = concatcp!(CYAN, "", END_COL, " ");
pub fn is_incomplete(input: &str) -> bool {
let arena = Bump::new();
match parse_src(&arena, input) {
ParseOutcome::Incomplete => !input.ends_with('\n'),
// Standalone annotations are default incomplete, because we can't know
// whether they're about to annotate a body on the next line
// (or if not, meaning they stay standalone) until you press Enter again!
//
// So it's Incomplete until you've pressed Enter again (causing the input to end in "\n")
ParseOutcome::ValueDef(ValueDef::Annotation(_, _)) if !input.ends_with('\n') => true,
ParseOutcome::Expr(Expr::When(_, _)) => {
// There might be lots of `when` branches, so don't assume the user is done entering
// them until they enter a blank line!
!input.ends_with('\n')
}
ParseOutcome::Empty
| ParseOutcome::Help
| ParseOutcome::Exit
| ParseOutcome::ValueDef(_)
| ParseOutcome::TypeDef(_)
| ParseOutcome::SyntaxErr
| ParseOutcome::Expr(_) => false,
}
}
pub fn format_output(
style_codes: StyleCodes,
opt_output: Option<ReplOutput>,
problems: Problems,
) -> String {
let mut buf = String::new();
for message in problems.errors.iter().chain(problems.warnings.iter()) {
if !buf.is_empty() {
buf.push_str("\n\n");
}
buf.push('\n');
buf.push_str(message);
buf.push('\n');
}
if let Some(ReplOutput { expr, expr_type }) = opt_output {
// If expr was empty, it was a type annotation or ability declaration;
// don't print anything!
//
// Also, for now we also don't print anything if there was a compile-time error.
// In the future, it would be great to run anyway and print useful output here!
if !expr.is_empty() && problems.errors.is_empty() {
const EXPR_TYPE_SEPARATOR: &str = " : "; // e.g. in "5 : Num *"
// Print the expr and its type
{
buf.push('\n');
buf.push_str(&expr);
buf.push_str(style_codes.green); // Color for the type separator
buf.push_str(EXPR_TYPE_SEPARATOR);
buf.push_str(style_codes.reset);
buf.push_str(&expr_type);
}
}
}
buf
}