diff --git a/Cargo.lock b/Cargo.lock index db9e0e3..f6b6707 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,15 +88,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bindgen" version = "0.58.1" @@ -856,17 +847,16 @@ dependencies = [ "im", "iter-set", "kurbo", - "memchr", "once_cell", "open", "prost", "prost-reflect", "rand", + "regex", "rustls", "serde", "serde-transcode", "serde_json", - "syntect", "tokio", "tokio-stream", "tonic", @@ -1180,15 +1170,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "line-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] - [[package]] name = "lock_api" version = "0.4.5" @@ -1374,28 +1355,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" -[[package]] -name = "onig" -version = "6.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ddfe2c93bb389eea6e6d713306880c7f6dcc99a75b659ce145d962c861b225" -dependencies = [ - "bitflags", - "lazy_static", - "libc", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd3eee045c84695b53b20255bb7317063df090b68e18bfac0abb6c39cf7f33e" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "open" version = "2.0.2" @@ -1525,8 +1484,7 @@ checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" [[package]] name = "piet" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c14a2944b6da638045428a9a7901b77bea62bf430d2b9d4d7146acce96e14a15" +source = "git+https://github.com/linebender/piet?branch=master#587fdea0d9af35622e8d0bc99c49627349e83d79" dependencies = [ "kurbo", "unic-bidi", @@ -1647,20 +1605,6 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" -[[package]] -name = "plist" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225" -dependencies = [ - "base64", - "indexmap", - "line-wrap", - "serde", - "time 0.3.6", - "xml-rs", -] - [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1955,21 +1899,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "schannel" version = "0.1.19" @@ -2188,27 +2117,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "syntect" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b20815bbe80ee0be06e6957450a841185fcf690fe0178f14d77a05ce2caa031" -dependencies = [ - "bincode", - "bitflags", - "flate2", - "fnv", - "lazy_static", - "lazycell", - "onig", - "plist", - "regex-syntax", - "serde", - "serde_derive", - "serde_json", - "walkdir", -] - [[package]] name = "system-deps" version = "3.2.0" @@ -2290,7 +2198,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d54b9298e05179c335de2b9645d061255bcd5155f843b3e328d2cfe0a5b413" dependencies = [ - "itoa 1.0.1", "libc", "num_threads", ] @@ -2801,17 +2708,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - [[package]] name = "want" version = "0.3.0" @@ -2990,12 +2886,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "xmlparser" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 17a8924..cc76a94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,12 @@ fs-err = "2.6.0" futures = "0.3.19" http = "0.2.6" iter-set = "2.0.2" -memchr = "2.4.1" once_cell = "1.9.0" open = "2.0.2" prost = "0.9.0" prost-reflect = { version = "0.5.1", features = ["serde"] } rand = "0.8.4" +regex = "1.5.4" rustls = { version = "0.19.1", features = ["dangerous_configuration"] } serde-transcode = "1.1.1" serde_json = "1.0.74" @@ -54,11 +54,6 @@ features = ["transport", "tls", "tls-roots", "compression"] version = "1.15.0" features = ["rt-multi-thread", "sync", "fs"] -[dependencies.syntect] -version = "4.6.0" -default-features = false -features = ["parsing", "regex-onig", "assets", "dump-load"] - [dependencies.serde] version = "1.0.133" features = ["derive", "rc"] @@ -78,3 +73,6 @@ anyhow = "1.0.52" version = "6.0.0" default-features = false features = ["git"] + +[patch.crates-io] +piet = { git = "https://github.com/linebender/piet", branch = "master" } diff --git a/assets/theme.tmTheme b/assets/theme.tmTheme deleted file mode 100644 index d0448ba..0000000 --- a/assets/theme.tmTheme +++ /dev/null @@ -1,95 +0,0 @@ - - - - - name - My theme - settings - - - settings - - background - #223643 - caret - #fbf9f0 - foreground - #fbf9f0 - gutter - #1a2243 - invisibles - #579fb3 - lineHighlight - #579fb312 - misspelling - #b00020 - selection - #67747a - selectionBorder - #579fb3 - - - - - - name - String - scope - string - settings - - foreground - #ee9065 - - - - name - Invalid - scope - invalid - settings - - background - #b00020 - - - - - - name - JSON keys - scope - meta.structure.dictionary.key.json string.quoted.double.json - settings - - foreground - #579fb3 - - - - name - JSON constants - scope - constant.language.json - settings - - foreground - #62677a - - - - name - JSON numbers - scope - constant.numeric - settings - - foreground - #e85f24 - - - - uuid - 497270fc-8ad5-4fc9-a40c-7b8b06cde8da - - diff --git a/src/json/highlight.rs b/src/json/highlight.rs index a5d0188..cb1d36a 100644 --- a/src/json/highlight.rs +++ b/src/json/highlight.rs @@ -1,47 +1,227 @@ -use std::{io, iter, ops::Range}; +use std::{ops::Range, str::Chars}; -use memchr::Memchr; +use druid::piet::TextAttribute; use once_cell::sync::Lazy; -use syntect::highlighting::{ - self, HighlightState, Highlighter, RangedHighlightIterator, Theme, ThemeSet, -}; -use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}; - -static SYNTAX_SET: Lazy = Lazy::new(SyntaxSet::load_defaults_newlines); -static JSON_SYNTAX: Lazy<&'static SyntaxReference> = - Lazy::new(|| SYNTAX_SET.find_syntax_by_token("json").unwrap()); -static THEME: Lazy = Lazy::new(|| { - ThemeSet::load_from_reader(&mut io::Cursor::new(include_bytes!( - "../../assets/theme.tmTheme" - ))) - .unwrap() -}); -static THEME_HIGHLIGHTER: Lazy> = Lazy::new(|| Highlighter::new(&THEME)); - -pub fn get_styles(text: &str) -> Vec<(highlighting::Style, Range)> { - let mut result = Vec::new(); +use regex::Regex; - let mut highlight_state = HighlightState::new(&THEME_HIGHLIGHTER, ScopeStack::new()); - let mut parse_state = ParseState::new(&JSON_SYNTAX); +use crate::theme::color; - for (start, line) in iter_lines(text) { - let ops = parse_state.parse_line(line, &SYNTAX_SET); - result.extend( - RangedHighlightIterator::new(&mut highlight_state, &ops, line, &THEME_HIGHLIGHTER) - .map(|(style, _, range)| (style, (start + range.start)..(start + range.end))), - ); +pub fn get_styles(text: &str) -> Vec<(TextAttribute, Range)> { + let mut result = Vec::new(); + Highlighter { + text: text.chars(), + styles: &mut result, + pos: 0, } + .highlight_document(); + result.sort_unstable_by_key(|(_, range)| range.start); result } -fn iter_lines(text: &str) -> impl Iterator { - Memchr::new(b'\n', text.as_bytes()) - .map(|idx| idx + 1) - .chain(iter::once(text.len())) - .scan(0, move |start, end| { - let range = *start..end; - *start = end; - Some((range.start, &text[range])) - }) +struct Highlighter<'a> { + text: Chars<'a>, + pos: usize, + styles: &'a mut Vec<(TextAttribute, Range)>, +} + +impl<'a> Highlighter<'a> { + fn highlight_document(&mut self) { + self.highlight_value(); + self.skip_whitespace(); + while self.peek().is_some() { + self.highlight_invalid(); + } + } + + fn highlight_value(&mut self) { + self.skip_whitespace(); + + if let Some(ch) = self.peek() { + match ch { + '[' => self.highlight_array(), + '{' => self.highlight_object(), + 'f' | 'n' | 't' => self.highlight_constant(), + '-' | '0'..='9' => self.highlight_number(), + '"' => self.highlight_string(false), + _ => { + self.highlight_invalid(); + } + } + } + } + + fn highlight_array(&mut self) { + self.bump(); + self.skip_whitespace(); + + let mut expecting_end = true; + while let Some(ch) = self.peek() { + match ch { + ']' if expecting_end => { + self.bump(); + break; + } + _ => { + self.highlight_value(); + self.skip_whitespace(); + expecting_end = !self.skip_char(','); + self.skip_whitespace(); + } + } + } + } + + fn highlight_object(&mut self) { + self.bump(); + self.skip_whitespace(); + + let mut expecting_end = true; + while let Some(ch) = self.peek() { + match ch { + '}' if expecting_end => { + self.bump(); + break; + } + '"' => { + self.highlight_string(true); + self.skip_whitespace(); + self.skip_char(':'); + self.highlight_value(); + self.skip_whitespace(); + expecting_end = !self.skip_char(','); + self.skip_whitespace(); + } + _ => { + self.highlight_invalid(); + } + } + } + } + + fn highlight_constant(&mut self) { + static CONSTANT: Lazy = Lazy::new(|| Regex::new("^(?:false|true|null)").unwrap()); + + if let Some(range) = self.skip_pattern(&CONSTANT) { + self.styles.push(( + TextAttribute::TextColor(color::active(color::ACCENT, color::TEXT)), + range, + )); + } + } + + fn highlight_number(&mut self) { + static NUMBER: Lazy = + Lazy::new(|| Regex::new(r#"^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?"#).unwrap()); + + if let Some(range) = self.skip_pattern(&NUMBER) { + self.styles + .push((TextAttribute::TextColor(color::BOLD_ACCENT), range)); + } + } + + fn highlight_string(&mut self, object_key: bool) { + static STRING_ESCAPE: Lazy = + Lazy::new(|| Regex::new(r#"^\\(:?["\\/bfnrt]|u[0-9a-fA-F]{4})"#).unwrap()); + + let start = self.pos(); + + self.bump(); + + while let Some(ch) = self.peek() { + match ch { + '"' => { + self.bump(); + break; + } + '\\' => { + if self.skip_pattern(&STRING_ESCAPE).is_none() { + self.highlight_invalid(); + } + } + _ => self.bump(), + } + } + + let end = self.pos(); + + let color = if object_key { + color::SUBTLE_ACCENT + } else { + color::active(color::BOLD_ACCENT, color::TEXT) + }; + self.styles + .push((TextAttribute::TextColor(color), start..end)); + } + + fn highlight_invalid(&mut self) { + let start = self.pos(); + self.bump(); + let end = self.pos(); + + match self.styles.last_mut() { + Some((TextAttribute::TextColor(color::ERROR), range)) if range.end == start => { + range.end = end; + } + _ => { + self.styles + .push((TextAttribute::TextColor(color::ERROR), start..end)); + } + } + } + + fn skip_pattern(&mut self, pattern: &Regex) -> Option> { + if let Some(m) = pattern.find(self.text.as_str()) { + Some(self.advance(m.range().len())) + } else { + self.highlight_invalid(); + None + } + } + + fn skip_whitespace(&mut self) { + while let Some(ch) = self.peek() { + if ch.is_ascii_whitespace() { + self.bump(); + } else { + break; + } + } + } + + fn skip_char(&mut self, expected: char) -> bool { + if let Some(ch) = self.peek() { + if ch == expected { + self.bump(); + return true; + } + } + false + } + + fn peek(&mut self) -> Option { + self.text.as_str().chars().next() + } + + fn bump(&mut self) { + let ch = self + .text + .next() + .expect("bump called without peek returning Some"); + self.pos += ch.len_utf8(); + } + + fn pos(&mut self) -> usize { + self.pos + } + + fn advance(&mut self, n: usize) -> Range { + let start = self.pos; + self.pos += n; + let end = self.pos; + + self.text = self.text.as_str()[n..].chars(); + + start..end + } } diff --git a/src/json/mod.rs b/src/json/mod.rs index 8f71707..9aeab3c 100644 --- a/src/json/mod.rs +++ b/src/json/mod.rs @@ -11,25 +11,17 @@ use std::sync::Arc; use anyhow::Result; use druid::{ - piet::{ - self, FontStyle, FontWeight, PietTextLayoutBuilder, TextAttribute, TextLayoutBuilder, - TextStorage as _, - }, + piet::{self, PietTextLayoutBuilder, TextAttribute, TextLayoutBuilder, TextStorage as _}, text::{EditableText, StringCursor, TextStorage}, Data, }; -use syntect::highlighting; #[derive(Debug, Clone)] pub struct JsonText { // Original data, present if this JSON has been shortened. original_data: Option>, data: Arc, - styles: Arc<[(highlighting::Style, Range)]>, -} - -fn color(c: highlighting::Color) -> druid::Color { - druid::Color::rgba8(c.r, c.g, c.b, c.a) + styles: Arc<[(TextAttribute, Range)]>, } fn prettify(s: &str) -> Option { @@ -139,28 +131,8 @@ impl TextStorage for JsonText { mut builder: PietTextLayoutBuilder, _env: &druid::Env, ) -> PietTextLayoutBuilder { - for (ref style, ref range) in self.styles.iter() { - builder = builder.range_attribute( - range.clone(), - TextAttribute::TextColor(color(style.foreground)), - ); - - if style.font_style.contains(highlighting::FontStyle::BOLD) { - builder = - builder.range_attribute(range.clone(), TextAttribute::Weight(FontWeight::BOLD)); - } - - if style.font_style.contains(highlighting::FontStyle::ITALIC) { - builder = - builder.range_attribute(range.clone(), TextAttribute::Style(FontStyle::Italic)); - } - - if style - .font_style - .contains(highlighting::FontStyle::UNDERLINE) - { - builder = builder.range_attribute(range.clone(), TextAttribute::Underline(true)); - } + for (ref attribute, ref range) in self.styles.iter() { + builder = builder.range_attribute(range.clone(), attribute.clone()); } builder }