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
}