From c1facbe6f1d095acf33e5855bfb81e26e92e724c Mon Sep 17 00:00:00 2001 From: PaddiM8 Date: Sun, 7 Jun 2020 18:56:28 +0200 Subject: [PATCH] Implemented simple syntax highlighting and symbol-completion. --- kalk_cli/Cargo.lock | 59 ++++++++++++++++---- kalk_cli/Cargo.toml | 2 + kalk_cli/src/repl.rs | 126 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 173 insertions(+), 14 deletions(-) diff --git a/kalk_cli/Cargo.lock b/kalk_cli/Cargo.lock index 8c8d8f8..c59223c 100644 --- a/kalk_cli/Cargo.lock +++ b/kalk_cli/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -86,22 +95,21 @@ dependencies = [ ] [[package]] -name = "dirs" -version = "2.0.2" +name = "dirs-next" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" dependencies = [ "cfg-if", - "dirs-sys", + "dirs-sys-next", ] [[package]] -name = "dirs-sys" -version = "0.3.4" +name = "dirs-sys-next" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" dependencies = [ - "cfg-if", "libc", "redox_users", "winapi", @@ -143,6 +151,8 @@ version = "0.1.0" dependencies = [ "ansi_term", "kalk", + "phf", + "regex", "rustyline", ] @@ -328,6 +338,24 @@ dependencies = [ "rust-argon2", ] +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + [[package]] name = "rug" version = "1.9.0" @@ -353,12 +381,12 @@ dependencies = [ [[package]] name = "rustyline" -version = "6.1.2" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd20b28d972040c627e209eb29f19c24a71a19d661cc5a220089176e20ee202" +checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777" dependencies = [ "cfg-if", - "dirs", + "dirs-next", "libc", "log", "memchr", @@ -405,6 +433,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "unicode-segmentation" version = "1.6.0" diff --git a/kalk_cli/Cargo.toml b/kalk_cli/Cargo.toml index 6377d66..cfbe794 100644 --- a/kalk_cli/Cargo.toml +++ b/kalk_cli/Cargo.toml @@ -11,3 +11,5 @@ panic = "abort" kalk = { path = "../kalk" } rustyline = "6.1.2" ansi_term = "0.12" +regex = "1" +phf = { version = "0.8", features = ["macros"] } diff --git a/kalk_cli/src/repl.rs b/kalk_cli/src/repl.rs index ffe95b4..ab85134 100644 --- a/kalk_cli/src/repl.rs +++ b/kalk_cli/src/repl.rs @@ -1,12 +1,29 @@ use crate::output; -use ansi_term::Colour::Cyan; +use ansi_term::Colour::{self, Cyan}; use kalk::parser; +use regex::Captures; +use regex::Regex; +use rustyline::completion::Completer; use rustyline::error::ReadlineError; -use rustyline::Editor; +use rustyline::highlight::Highlighter; +use rustyline::hint::Hinter; +use rustyline::hint::HistoryHinter; +use rustyline::validate::MatchingBracketValidator; +use rustyline::validate::ValidationContext; +use rustyline::validate::ValidationResult; +use rustyline::validate::Validator; +use rustyline::{Editor, Helper}; +use std::borrow::Cow; +use std::borrow::Cow::Owned; use std::process; pub fn start(mut parser: &mut parser::Context) { - let mut editor = Editor::<()>::new(); + let mut editor = Editor::::new(); + editor.set_helper(Some(RLHelper { + highlighter: LineHighlighter {}, + hinter: HistoryHinter {}, + validator: MatchingBracketValidator::new(), + })); loop { let readline = editor.readline(&Cyan.paint(">> ").to_string()); @@ -30,3 +47,106 @@ fn eval_repl(parser: &mut parser::Context, input: &str) { _ => output::eval(parser, input), } } + +struct LineHighlighter {} + +impl Highlighter for LineHighlighter { + fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> { + let mut coloured = line.to_string(); + + let reg = Regex::new(r"([A-z]+|[\+-/\*\^!])").unwrap(); + let unit = Regex::new(r"(deg|rad)").unwrap(); + let identifier = Regex::new(r"[^0-9\.,\(\)=\+-/\*\^!]+").unwrap(); + let op = Regex::new(r"[\+-/\*\^!]+").unwrap(); + + coloured = reg + .replace_all(&coloured, |caps: &Captures| { + let cap = &caps[0]; + + if unit.is_match(cap) { + Colour::Yellow.paint(cap).to_string() + } else if identifier.is_match(cap) { + Colour::Blue.paint(cap).to_string() + } else if op.is_match(cap) { + Colour::Fixed(172).paint(cap).to_string() + } else { + cap.to_string() + } + }) + .to_string(); + + Owned(coloured) + } +} + +struct RLHelper { + highlighter: LineHighlighter, + hinter: HistoryHinter, + validator: MatchingBracketValidator, +} + +impl Helper for RLHelper {} + +const COMPLETION_FUNCS: phf::Map<&'static str, &'static str> = phf::phf_map! { + "sqrt" => "√", + "deg" => "°", +}; + +impl Completer for RLHelper { + type Candidate = String; + fn complete( + &self, + line: &str, + pos: usize, + _ctx: &rustyline::Context<'_>, + ) -> Result<(usize, Vec), ReadlineError> { + for key in COMPLETION_FUNCS.keys() { + if line[..pos].ends_with(key) { + return Ok(( + pos - key.len(), + vec![String::from(*COMPLETION_FUNCS.get(key).unwrap())], + )); + } + } + + Ok((0, vec![line.to_string()])) + } +} + +impl Highlighter for RLHelper { + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + self.highlighter.highlight_hint(hint) + } + + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + self.highlighter.highlight(line, pos) + } + + fn highlight_candidate<'c>( + &self, + candidate: &'c str, + _completion: rustyline::CompletionType, + ) -> Cow<'c, str> { + self.highlighter.highlight(candidate, 0) + } + + fn highlight_char(&self, line: &str, _: usize) -> bool { + line.len() > 0 + } +} + +impl Hinter for RLHelper { + fn hint(&self, line: &str, a: usize, b: &rustyline::Context) -> Option { + self.hinter.hint(line, a, b) + } +} + +impl Validator for RLHelper { + fn validate(&self, ctx: &mut ValidationContext) -> Result { + self.validator.validate(ctx) + } + + fn validate_while_typing(&self) -> bool { + self.validator.validate_while_typing() + } +}