Browse Source

Web: Add HTML formatter

David Peter 2 years ago
parent
commit
5e8449ca9b

+ 5 - 0
numbat-wasm/src/buffered_writer.rs

@@ -0,0 +1,5 @@
+use termcolor::WriteColor;
+
+pub trait BufferedWriter: WriteColor {
+    fn to_string(&self) -> String;
+}

+ 113 - 0
numbat-wasm/src/html_formatter.rs

@@ -0,0 +1,113 @@
+use crate::buffered_writer::BufferedWriter;
+use numbat::markup::{FormatType, FormattedString, Formatter};
+
+use termcolor::{Color, WriteColor};
+
+pub struct HtmlFormatter;
+
+pub fn html_format(class: Option<&str>, content: &str) -> String {
+    if content.is_empty() {
+        return "".into();
+    }
+
+    let content = html_escape::encode_text(content);
+
+    if let Some(class) = class {
+        format!("<span class=\"numbat-{class}\">{content}</span>")
+    } else {
+        content.into()
+    }
+}
+
+impl Formatter for HtmlFormatter {
+    fn format_part(
+        &self,
+        FormattedString(_output_type, format_type, s): &FormattedString,
+    ) -> String {
+        let css_class = match format_type {
+            FormatType::Whitespace => None,
+            FormatType::Emphasized => Some("emphasized"),
+            FormatType::Dimmed => Some("dimmed"),
+            FormatType::Text => None,
+            FormatType::String => Some("string"),
+            FormatType::Keyword => Some("keyword"),
+            FormatType::Value => Some("value"),
+            FormatType::Unit => Some("unit"),
+            FormatType::Identifier => Some("identifier"),
+            FormatType::TypeIdentifier => Some("type-identifier"),
+            FormatType::Operator => Some("operator"),
+            FormatType::Decorator => Some("decorator"),
+        };
+        html_format(css_class, s)
+    }
+}
+
+pub struct HtmlWriter {
+    buffer: Vec<u8>,
+    color: Option<termcolor::ColorSpec>,
+}
+
+impl HtmlWriter {
+    pub fn new() -> Self {
+        HtmlWriter {
+            buffer: vec![],
+            color: None,
+        }
+    }
+}
+
+impl BufferedWriter for HtmlWriter {
+    fn to_string(&self) -> String {
+        String::from_utf8_lossy(&self.buffer).into()
+    }
+}
+
+impl std::io::Write for HtmlWriter {
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+        if let Some(color) = &self.color {
+            if color.fg() == Some(&Color::Red) {
+                self.buffer
+                    .write("<span class=\"numbat-diagnostic-red\">".as_bytes())?;
+                let size = self.buffer.write(buf)?;
+                self.buffer.write("</span>".as_bytes())?;
+                Ok(size)
+            } else if color.fg() == Some(&Color::Blue) {
+                self.buffer
+                    .write("<span class=\"numbat-diagnostic-blue\">".as_bytes())?;
+                let size = self.buffer.write(buf)?;
+                self.buffer.write("</span>".as_bytes())?;
+                Ok(size)
+            } else if color.bold() {
+                self.buffer
+                    .write("<span class=\"numbat-diagnostic-bold\">".as_bytes())?;
+                let size = self.buffer.write(buf)?;
+                self.buffer.write("</span>".as_bytes())?;
+                Ok(size)
+            } else {
+                self.buffer.write(buf)
+            }
+        } else {
+            self.buffer.write(buf)
+        }
+    }
+
+    fn flush(&mut self) -> std::io::Result<()> {
+        self.buffer.flush()
+    }
+}
+
+impl WriteColor for HtmlWriter {
+    fn supports_color(&self) -> bool {
+        true
+    }
+
+    fn set_color(&mut self, spec: &termcolor::ColorSpec) -> std::io::Result<()> {
+        self.color = Some(spec.clone());
+        Ok(())
+    }
+
+    fn reset(&mut self) -> std::io::Result<()> {
+        self.color = None;
+        Ok(())
+    }
+}

+ 4 - 1
numbat-wasm/src/jquery_terminal_formatter.rs

@@ -1,3 +1,4 @@
+use crate::buffered_writer::BufferedWriter;
 use numbat::markup::{FormatType, FormattedString, Formatter};
 
 use termcolor::{Color, WriteColor};
@@ -55,8 +56,10 @@ impl JqueryTerminalWriter {
             color: None,
         }
     }
+}
 
-    pub fn to_string(self) -> String {
+impl BufferedWriter for JqueryTerminalWriter {
+    fn to_string(&self) -> String {
         String::from_utf8_lossy(&self.buffer).into()
     }
 }

+ 58 - 25
numbat-wasm/src/lib.rs

@@ -1,3 +1,5 @@
+mod buffered_writer;
+mod html_formatter;
 mod jquery_terminal_formatter;
 mod utils;
 
@@ -6,7 +8,9 @@ use numbat::module_importer::BuiltinModuleImporter;
 use std::sync::{Arc, Mutex};
 use wasm_bindgen::prelude::*;
 
-use jquery_terminal_formatter::JqueryTerminalFormatter;
+use crate::buffered_writer::BufferedWriter;
+use html_formatter::{HtmlFormatter, HtmlWriter};
+use jquery_terminal_formatter::{JqueryTerminalFormatter, JqueryTerminalWriter};
 
 use numbat::help::help_markup;
 use numbat::markup::Formatter;
@@ -15,8 +19,6 @@ use numbat::resolver::CodeSource;
 use numbat::{markup as m, NameResolutionError, NumbatError};
 use numbat::{Context, InterpreterSettings};
 
-use crate::jquery_terminal_formatter::JqueryTerminalWriter;
-
 // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
 // allocator.
 #[cfg(feature = "wee_alloc")]
@@ -28,9 +30,18 @@ pub fn setup_panic_hook() {
     utils::set_panic_hook();
 }
 
+#[wasm_bindgen]
+#[derive(Debug, Clone, Copy)]
+pub enum FormatType {
+    JqueryTerminal,
+    Html,
+}
+
 #[wasm_bindgen]
 pub struct Numbat {
     ctx: Context,
+    enable_pretty_printing: bool,
+    format_type: FormatType,
 }
 
 #[wasm_bindgen]
@@ -50,11 +61,15 @@ impl InterpreterOutput {
 
 #[wasm_bindgen]
 impl Numbat {
-    pub fn new() -> Self {
+    pub fn new(enable_pretty_printing: bool, format_type: FormatType) -> Self {
         let mut ctx = Context::new(BuiltinModuleImporter::default());
         let _ = ctx.interpret("use prelude", CodeSource::Internal).unwrap();
         ctx.set_terminal_width(Some(84)); // terminal width with current layout
-        Numbat { ctx }
+        Numbat {
+            ctx,
+            enable_pretty_printing,
+            format_type,
+        }
     }
 
     pub fn set_exchange_rates(&mut self, xml_content: &str) {
@@ -65,16 +80,17 @@ impl Numbat {
             .unwrap();
     }
 
-    fn jq_format(&self, markup: &numbat::markup::Markup, indent: bool) -> String {
-        let fmt = JqueryTerminalFormatter {};
+    fn format(&self, markup: &numbat::markup::Markup, indent: bool) -> String {
+        let fmt: Box<dyn Formatter> = match self.format_type {
+            FormatType::JqueryTerminal => Box::new(JqueryTerminalFormatter {}),
+            FormatType::Html => Box::new(HtmlFormatter {}),
+        };
         fmt.format(&markup, indent).into()
     }
 
     pub fn interpret(&mut self, code: &str) -> InterpreterOutput {
         let mut output = String::new();
 
-        let registry = self.ctx.dimension_registry().clone();
-
         let to_be_printed: Arc<Mutex<Vec<m::Markup>>> = Arc::new(Mutex::new(vec![]));
         let to_be_printed_c = to_be_printed.clone();
         let mut settings = InterpreterSettings {
@@ -83,28 +99,42 @@ impl Numbat {
             }),
         };
 
+        let nl = &self.format(&numbat::markup::nl(), false);
+
+        let enable_indentation = match self.format_type {
+            FormatType::JqueryTerminal => true,
+            FormatType::Html => false,
+        };
+
         match self
             .ctx
             .interpret_with_settings(&mut settings, &code, CodeSource::Text)
         {
             Ok((statements, result)) => {
                 // Pretty print
-                output.push_str("\n");
-                for statement in &statements {
-                    output.push_str(&self.jq_format(&statement.pretty_print(), true));
-                    output.push_str("\n");
+                if self.enable_pretty_printing {
+                    output.push_str(nl);
+                    for statement in &statements {
+                        output
+                            .push_str(&self.format(&statement.pretty_print(), enable_indentation));
+                        output.push_str(nl);
+                    }
+                    output.push_str(nl);
                 }
-                output.push_str("\n");
 
                 // print(…) and type(…) results
                 let to_be_printed = to_be_printed.lock().unwrap();
                 for content in to_be_printed.iter() {
-                    output.push_str(&self.jq_format(content, true));
-                    output.push_str("\n");
+                    output.push_str(&self.format(content, enable_indentation));
+                    output.push_str(nl);
                 }
 
-                let result_markup = result.to_markup(statements.last(), &registry, true);
-                output.push_str(&self.jq_format(&result_markup, true));
+                let result_markup = result.to_markup(
+                    statements.last(),
+                    &self.ctx.dimension_registry().clone(),
+                    true,
+                );
+                output.push_str(&self.format(&result_markup, enable_indentation));
 
                 InterpreterOutput {
                     output,
@@ -122,27 +152,27 @@ impl Numbat {
     }
 
     pub fn print_environment(&self) -> JsValue {
-        self.jq_format(&self.ctx.print_environment(), false).into()
+        self.format(&self.ctx.print_environment(), false).into()
     }
 
     pub fn print_functions(&self) -> JsValue {
-        self.jq_format(&self.ctx.print_functions(), false).into()
+        self.format(&self.ctx.print_functions(), false).into()
     }
 
     pub fn print_dimensions(&self) -> JsValue {
-        self.jq_format(&self.ctx.print_dimensions(), false).into()
+        self.format(&self.ctx.print_dimensions(), false).into()
     }
 
     pub fn print_variables(&self) -> JsValue {
-        self.jq_format(&self.ctx.print_variables(), false).into()
+        self.format(&self.ctx.print_variables(), false).into()
     }
 
     pub fn print_units(&self) -> JsValue {
-        self.jq_format(&self.ctx.print_units(), false).into()
+        self.format(&self.ctx.print_units(), false).into()
     }
 
     pub fn help(&self) -> JsValue {
-        self.jq_format(&help_markup(), true).into()
+        self.format(&help_markup(), true).into()
     }
 
     pub fn get_completions_for(&self, input: &str) -> Vec<JsValue> {
@@ -155,7 +185,10 @@ impl Numbat {
     fn print_diagnostic(&self, error: &dyn ErrorDiagnostic) -> InterpreterOutput {
         use codespan_reporting::term::{self, Config};
 
-        let mut writer = JqueryTerminalWriter::new();
+        let mut writer: Box<dyn BufferedWriter> = match self.format_type {
+            FormatType::JqueryTerminal => Box::new(JqueryTerminalWriter::new()),
+            FormatType::Html => Box::new(HtmlWriter::new()),
+        };
         let config = Config::default();
 
         let resolver = self.ctx.resolver();

+ 7 - 3
numbat-wasm/www/index.js

@@ -1,4 +1,4 @@
-import { setup_panic_hook, Numbat } from "numbat-wasm";
+import { setup_panic_hook, Numbat, FormatType } from "numbat-wasm";
 
 async function fetch_exchange_rates() {
   try {
@@ -16,9 +16,13 @@ async function fetch_exchange_rates() {
   }
 }
 
+function create_numbat_instance() {
+    return Numbat.new(true, FormatType.JqueryTerminal);
+}
+
 setup_panic_hook();
 
-var numbat = Numbat.new();
+var numbat = create_numbat_instance();
 var combined_input = "";
 
 // Load KeyboardEvent polyfill for old browsers
@@ -46,7 +50,7 @@ function interpret(input) {
     this.clear();
     var output = "";
   } else if (input_trimmed == "reset") {
-    numbat = Numbat.new();
+    numbat = create_numbat_instance();
     numbat.interpret("use units::currencies");
     combined_input = "";
     updateUrlQuery(null);