Highlight code, start code tooltip

This commit is contained in:
Adrian Woźniak 2020-08-13 23:44:37 +02:00
parent 331acd4574
commit 8deab1c2d5
8 changed files with 330 additions and 59 deletions

81
Cargo.lock generated
View File

@ -548,6 +548,21 @@ dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3"
[[package]]
name = "bitflags"
version = "1.2.1"
@ -1077,6 +1092,16 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fancy-regex"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae91abf6555234338687bb47913978d275539235fcb77ba9863b779090b42b14"
dependencies = [
"bit-set",
"regex",
]
[[package]]
name = "fast_chemail"
version = "0.9.6"
@ -1693,8 +1718,10 @@ dependencies = [
"futures 0.1.29",
"jirs-data",
"js-sys",
"lazy_static",
"seed",
"serde",
"syntect",
"uuid 0.8.1",
"wasm-bindgen",
"wasm-bindgen-test",
@ -1777,6 +1804,15 @@ version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
[[package]]
name = "line-wrap"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
dependencies = [
"safemem",
]
[[package]]
name = "linked-hash-map"
version = "0.5.3"
@ -2247,6 +2283,20 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
[[package]]
name = "plist"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b336d94e8e4ce29bf15bba393164629764744c567e8ad306cc1fdd0119967fd"
dependencies = [
"base64 0.12.3",
"chrono",
"indexmap",
"line-wrap",
"serde",
"xml-rs",
]
[[package]]
name = "ppv-lite86"
version = "0.2.8"
@ -3009,6 +3059,28 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "syntect"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b57a45fdcf4891bc79f635be5c559210a4cfa464891f969724944c713282eedb"
dependencies = [
"bincode",
"bitflags",
"fancy-regex",
"flate2",
"fnv",
"lazy_static",
"lazycell",
"plist",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
"walkdir",
"yaml-rust",
]
[[package]]
name = "tempfile"
version = "3.1.0"
@ -3696,6 +3768,15 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
[[package]]
name = "yaml-rust"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zeroize"
version = "1.1.0"

View File

@ -11,7 +11,7 @@ license = "MPL-2.0"
[lib]
crate-type = ["cdylib", "rlib"]
name = "jirs_client"
path = "./src/lib.rs"
path = "src/lib.rs"
[profile.dev]
opt-level = 0 # Use slightly better optimizations.
@ -32,6 +32,9 @@ futures = "^0.1.26"
comrak = "*"
wee_alloc = "*"
lazy_static = "*"
syntect = { version = "*", default-features = false, features = ["default-fancy"] }
[dependencies.wasm-bindgen]
version = "0.2.66"
features = ["enable-interning"]

125
jirs-client/src/elements.rs Normal file
View File

@ -0,0 +1,125 @@
use syntect::easy::HighlightLines;
use syntect::highlighting::{FontStyle, Style};
use wasm_bindgen::prelude::*;
#[wasm_bindgen(final, js_name = JirsCodeBuilder)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JirsCodeBuilder {}
#[wasm_bindgen]
impl JirsCodeBuilder {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {}
}
#[wasm_bindgen]
pub fn hi(&mut self, lang: &str, line: &str) -> String {
let syntax = match crate::hi::SYNTAX_SET.find_syntax_by_name(lang) {
Some(s) => s,
_ => {
return line.to_string();
}
};
let mut h = HighlightLines::new(syntax, &crate::hi::THEME_SET.themes["base16-ocean-dark"]); // inspired-github
let tokens = h.highlight(line, &crate::hi::SYNTAX_SET);
let parts: Vec<String> = tokens
.into_iter()
.map(|(style, token)| {
let Style {
foreground: f,
background: b,
font_style,
} = style;
let fs = if font_style == FontStyle::BOLD {
"font-weight: bold"
} else if font_style == FontStyle::ITALIC {
"font-style: italic"
} else {
"font-decoration: underline"
};
let f = format!("rgba({}, {}, {}, {})", f.r, f.g, f.b, f.a);
let b = format!("rgba({}, {}, {}, {})", b.r, b.g, b.b, b.a);
format!(
r#"<span style="color: {f};background:{b}; {fs}">{t}</span>"#,
t = if token.is_empty() { "&nbsp;" } else { token },
f = f,
b = b,
fs = fs
)
})
.collect();
parts.join("")
}
}
pub fn define() {
create_custom_element(
"jirs-code-view",
"JirsCodeView",
r#"
<style>
:host { display: block; border: 1px solid black; background: rgba(43, 48, 59, 255); padding: 1rem; }
:host { margin-left: 400px; }
#view span { white-space: pre; }
</style>
<div id='view'></div>
"#,
);
}
fn create_custom_element(tag: &str, name: &str, html: &str) {
let source = format!(
r#"
class {name} extends HTMLElement {{
static RUNTIME = Symbol();
static SHADOW = Symbol();
static get observedAttributes() {{ return ['lang']; }}
constructor() {{
super();
this[ {name} . SHADOW] = this.attachShadow({{ 'mode': 'closed' }});
this[ {name} . SHADOW].innerHTML = `{html}`;
}}
connectedCallback() {{
this[ {name} . RUNTIME] = new JirsCodeBuilder();
const view = this[ {name} . SHADOW].querySelector('#view');
view.innerHTML = '';
const lang = this.getAttribute('lang') || '';
setTimeout(() => {{
const hi = () => {{
const line = code.shift();
if (line === undefined) return;
const s = this[ {name} . RUNTIME].hi(lang, line);
view.innerHTML += `${{s}}<br />`;
setTimeout(() => hi(), 10);
}};
hi();
}}, 10);
}}
disconnectedCallback() {{
this[ {name} . RUNTIME].free();
}}
attributeChangedCallback(name, oldV, newV) {{
}}
}}
customElements.define( '{tag}', {name});
"#,
name = name,
tag = tag,
html = html,
);
{
use seed::*;
match js_sys::eval(source.as_str()) {
Ok(_v) => (),
Err(e) => error!(e),
};
};
}

Binary file not shown.

View File

@ -0,0 +1,9 @@
use lazy_static::lazy_static;
use syntect::dumps::from_binary;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
lazy_static! {
pub static ref SYNTAX_SET: SyntaxSet = from_binary(include_bytes!("./newlines.packdump"));
pub static ref THEME_SET: ThemeSet = from_binary(include_bytes!("./all.themedump"));
}

Binary file not shown.

View File

@ -15,7 +15,9 @@ use crate::shared::{go_to_board, go_to_login, styled_tooltip};
use crate::ws::{flush_queue, open_socket, read_incoming, send_ws_msg};
mod changes;
pub mod elements;
mod fields;
pub mod hi;
mod invite;
mod modal;
mod model;
@ -261,6 +263,7 @@ pub static mut WS_URL: String = String::new();
#[wasm_bindgen]
pub fn render(host_url: String, ws_url: String) {
elements::define();
unsafe {
HOST_URL = host_url;
WS_URL = ws_url;

View File

@ -70,12 +70,17 @@ pub enum RteMsg {
RemoveFormat,
Subscript,
Superscript,
// table
TableSetVisibility(bool),
TableSetRows(u16),
TableSetColumns(u16),
TableSetVisibility(bool),
InsertTable { rows: u16, cols: u16 },
ChangeIndent(RteIndentMsg),
// code
InsertCode(bool),
RequestFocus(uuid::Uuid),
}
@ -139,6 +144,8 @@ impl RteMsg {
RteMsg::Subscript => Some(ExecCommand::new("subscript")),
RteMsg::Superscript => Some(ExecCommand::new("superscript")),
RteMsg::InsertTable { .. } => None,
// code
RteMsg::InsertCode(_) => None,
// indent
RteMsg::ChangeIndent(RteIndentMsg::Increase) => Some(ExecCommand::new("indent")),
@ -171,11 +178,18 @@ pub struct StyledRteTableState {
pub cols: u16,
}
#[derive(Debug, Clone)]
pub struct StyledRteCodeState {
pub visible: bool,
pub lang: String,
}
#[derive(Debug)]
pub struct StyledRteState {
pub value: String,
pub field_id: FieldId,
pub table_tooltip: StyledRteTableState,
pub code_tooltip: StyledRteCodeState,
range: Option<web_sys::Range>,
identifier: uuid::Uuid,
}
@ -190,6 +204,10 @@ impl StyledRteState {
rows: 3,
cols: 3,
},
code_tooltip: StyledRteCodeState {
visible: false,
lang: "".to_string(),
},
range: None,
identifier: uuid::Uuid::new_v4(),
}
@ -217,6 +235,12 @@ impl StyledRteState {
self.schedule_focus(orders);
}
_ => match m {
RteMsg::InsertCode(b) => {
if *b {
self.store_range();
}
self.code_tooltip.visible = *b;
}
RteMsg::TableSetRows(n) => {
self.table_tooltip.rows = *n;
}
@ -772,41 +796,48 @@ fn second_row(values: &StyledRte) -> Node<Msg> {
None as Option<Msg>
}),
);*/
let mut table_button = {
let field_id = values.field_id.clone();
let mut table_button = styled_rte_button(
styled_rte_button(
"Table",
Icon::Table,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
Some(Msg::Rte(RteMsg::TableSetVisibility(true), field_id))
}),
);
)
};
table_button.add_child(table_tooltip);
let paragraph_button = {
let field_id = values.field_id.clone();
let paragraph_button = styled_rte_button(
styled_rte_button(
"Paragraph",
Icon::Paragraph,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
Some(Msg::Rte(RteMsg::InsertParagraph, field_id))
}),
);
// let field_id = values.field_id.clone();
// let code_alt_button = styled_rte_button(
// "Insert code",
// Icon::CodeAlt,
// mouse_ev(Ev::Click, move |ev| {
// ev.prevent_default();
// None as Option<Msg>
// }),
// );
)
};
let code_alt_button = {
let field_id = values.field_id.clone();
styled_rte_button(
"Insert code",
Icon::CodeAlt,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
Some(Msg::Rte(RteMsg::InsertCode(true), field_id))
}),
)
};
div![
class!["group insert"],
paragraph_button,
table_button,
// code_alt_button,
code_alt_button,
listing_dots,
listing_number,
// sub_listing_button,
@ -931,3 +962,22 @@ fn styled_rte_button(title: &str, icon: Icon, handler: EventHandler<Msg>) -> Nod
button
]
}
fn insert_code() -> Node<Msg> {
let mut languages: Vec<&str> = crate::hi::SYNTAX_SET
.syntaxes()
.iter()
.map(|s| s.name.as_str())
.collect();
languages.sort();
let options: Vec<Node<Msg>> = languages
.into_iter()
.map(|name| option![attrs![At::Value => name], name])
.collect();
seed::select![options]
}
pub fn code_to_tag(code: &str) -> Node<Msg> {
custom!["jirs-code-view", code]
}