text_view: Add syntax highlight to CodeBlock. (#684)

<img width="1712" alt="image"
src="https://github.com/user-attachments/assets/e7539df4-20db-431b-9bbc-52e08e9de062"
/>
<img width="1712" alt="image"
src="https://github.com/user-attachments/assets/85d52319-1029-4306-8629-ec0321d0571b"
/>
This commit is contained in:
Jason Lee 2025-03-04 21:50:50 +08:00 committed by GitHub
parent 8c4884d881
commit c3f08d52b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1547 additions and 158 deletions

99
Cargo.lock generated
View file

@ -543,6 +543,15 @@ dependencies = [
"simd-abstraction",
]
[[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.69.5"
@ -552,7 +561,7 @@ dependencies = [
"bitflags 2.6.0",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"log",
@ -575,7 +584,7 @@ dependencies = [
"bitflags 2.6.0",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"itertools 0.13.0",
"log",
"prettyplease",
"proc-macro2",
@ -2657,6 +2666,7 @@ dependencies = [
"serde_json",
"smallvec",
"smol 1.3.0",
"syntect",
"unicode-segmentation",
"usvg",
"uuid",
@ -3482,6 +3492,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
@ -4035,7 +4051,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro-crate 3.2.0",
"proc-macro2",
"quote",
"syn 2.0.96",
@ -4283,6 +4299,28 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "onig"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
dependencies = [
"bitflags 1.3.2",
"libc",
"once_cell",
"onig_sys",
]
[[package]]
name = "onig_sys"
version = "69.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "oo7"
version = "0.4.0"
@ -4700,6 +4738,19 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "plist"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
dependencies = [
"base64",
"indexmap 2.7.1",
"quick-xml 0.32.0",
"serde",
"time",
]
[[package]]
name = "png"
version = "0.17.14"
@ -4919,6 +4970,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2"
dependencies = [
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.36.2"
@ -6409,6 +6469,28 @@ dependencies = [
"futures-core",
]
[[package]]
name = "syntect"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
dependencies = [
"bincode",
"bitflags 1.3.2",
"flate2",
"fnv",
"once_cell",
"onig",
"plist",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
"thiserror 1.0.66",
"walkdir",
"yaml-rust",
]
[[package]]
name = "sys-locale"
version = "0.3.1"
@ -7350,7 +7432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
dependencies = [
"proc-macro2",
"quick-xml",
"quick-xml 0.36.2",
"quote",
]
@ -8067,6 +8149,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "yazi"
version = "0.1.6"

View file

@ -36,7 +36,7 @@ UI components for building fantastic desktop application by using [GPUI](https:/
- Sidebar
- Breadcrumb
- Badge
- TextView (Markdown, Simple HTML) to native rendering.
- TextView (Markdown, Simple HTML) to native rendering, syntax highlighting.
## Showcase

View file

@ -11,6 +11,23 @@ This is an additional demonstration paragraph in English demonstrating more cont
这是一个中文演示段落,用于展示更多的 [Markdown GFM](https://github.github.com/gfm/) 内容。您可以在此尝试使用使用**粗体**、*斜体*和`代码`等样式。これは日本語のデモ段落です。Markdown の多言語サポートを示すためのテキストが含まれています。例えば、、**ボールド**、_イタリック_、および`コード`のスタイルなどを試すことができます。
### Code block
```rust
struct Repository {
/// Name of the repository.
name: String,
}
fn main() {
let _ = Repository {
name: "GPUI Component".to_string(),
};
println!("Hello, World!");
}
```
---
## Heading for [Links](https://www.google.com)
@ -61,14 +78,6 @@ Here is a link to [Google](https://www.google.com), and another to [Rust](https:
- [ ] Task 2, going to do something if there is a long text that needs to be wrapped to the next line.
- [ ] Task 3
#### Heading for Code
```rust
fn main() {
println!("Hello, World!");
}
```
### HTML
#### Paragraph and Text

View file

@ -1,5 +1,12 @@
use std::rc::Rc;
use gpui::*;
use gpui_component::{input::TextInput, text::TextView, ActiveTheme as _};
use gpui_component::{
highlighter::HighlightTheme,
input::TextInput,
text::{TextView, TextViewStyle},
ActiveTheme as _,
};
use story::Assets;
pub struct Example {
@ -39,6 +46,12 @@ impl Example {
impl Render for Example {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = if cx.theme().mode.is_dark() {
HighlightTheme::default_dark()
} else {
HighlightTheme::default_light()
};
div()
.flex()
.flex_row()
@ -61,10 +74,14 @@ impl Render for Example {
.p_5()
.flex_1()
.overflow_y_scroll()
.child(TextView::markdown(
"preview",
self.text_input.read(cx).text(),
)),
.child(
TextView::markdown("preview", self.text_input.read(cx).text()).style(
TextViewStyle {
highlight_theme: Rc::new(theme),
..Default::default()
},
),
),
)
}
}

View file

@ -47,6 +47,7 @@ markup5ever_rcdom = "0.3.0"
minify-html = "0.15.0"
# Calendar
chrono = "0.4.38"
syntect = { version = "5.2", features = ["plist-load"] }
[dev-dependencies]
indoc = "2"

View file

@ -0,0 +1,150 @@
use gpui::{HighlightStyle, Hsla, SharedString};
use std::{
ops::Range,
sync::{Arc, LazyLock},
};
use syntect::{highlighting, parsing};
static SYNTAXES: LazyLock<parsing::SyntaxSet> =
LazyLock::new(parsing::SyntaxSet::load_defaults_nonewlines);
static DEFAULT_LIGHT: LazyLock<Arc<highlighting::Theme>> = LazyLock::new(|| {
let mut cursor = std::io::Cursor::new(include_bytes!("./themes/light.tmTheme"));
Arc::new(highlighting::ThemeSet::load_from_reader(&mut cursor).unwrap())
});
static DEFAULT_DARK: LazyLock<Arc<highlighting::Theme>> = LazyLock::new(|| {
let mut cursor = std::io::Cursor::new(include_bytes!("./themes/dark.tmTheme"));
Arc::new(highlighting::ThemeSet::load_from_reader(&mut cursor).unwrap())
});
/// Represents a theme for syntax highlighting.
#[derive(Debug, Clone, PartialEq)]
pub struct HighlightTheme {
name: SharedString,
inner: Arc<highlighting::Theme>,
}
impl HighlightTheme {
/// Default light theme.
pub fn default_light() -> Self {
Self {
name: "default-light".into(),
inner: DEFAULT_LIGHT.clone(),
}
}
/// Default dark theme.
pub fn default_dark() -> Self {
Self {
name: "default-dark".into(),
inner: DEFAULT_DARK.clone(),
}
}
/// Parse a theme from a string (tmTheme)
pub fn parse(name: &str, theme_str: &str) -> anyhow::Result<Self> {
let mut cursor = std::io::Cursor::new(theme_str);
let theme = highlighting::ThemeSet::load_from_reader(&mut cursor)?;
Ok(Self {
name: SharedString::from(name.to_string()),
inner: Arc::new(theme),
})
}
}
/// Inspired by the `iced` crate's `Highlighter` struct.
///
/// https://github.com/iced-rs/iced/blob/master/highlighter/src/lib.rs#L24
pub struct Highlighter<'a> {
syntax: &'static parsing::SyntaxReference,
highlighter: highlighting::Highlighter<'a>,
}
impl<'a> Highlighter<'a> {
pub fn new(lang: Option<&str>, theme: &'a HighlightTheme) -> Self {
let syntax = lang
.and_then(|lang| SYNTAXES.find_syntax_by_token(&lang))
.unwrap_or_else(|| SYNTAXES.find_syntax_plain_text());
let highlighter = highlighting::Highlighter::new(&theme.inner);
Self {
syntax,
highlighter,
}
}
/// Highlight a text and returns a vector of ranges and highlight styles
pub fn highlight(&self, text: &str) -> Vec<(Range<usize>, HighlightStyle)> {
let mut parser = parsing::ParseState::new(self.syntax);
let mut stack = parsing::ScopeStack::new();
let ops = parser.parse_line(text, &SYNTAXES).unwrap_or_default();
ScopeRangeIterator {
ops,
line_length: text.len(),
index: 0,
last_str_index: 0,
}
.filter_map(move |(range, scope)| {
let _ = stack.apply(&scope);
if range.is_empty() {
return None;
} else {
let style_mod = self.highlighter.style_mod_for_stack(&stack.scopes);
let mut style = HighlightStyle::default();
style.color = style_mod.foreground.map(color_to_hsla);
style.background_color = style_mod.background.map(color_to_hsla);
Some((range, style))
}
})
.collect()
}
}
fn color_to_hsla(color: highlighting::Color) -> Hsla {
gpui::Rgba {
r: color.r as f32 / 255.,
g: color.g as f32 / 255.,
b: color.b as f32 / 255.,
a: color.a as f32 / 100.,
}
.into()
}
struct ScopeRangeIterator {
ops: Vec<(usize, parsing::ScopeStackOp)>,
line_length: usize,
index: usize,
last_str_index: usize,
}
impl Iterator for ScopeRangeIterator {
type Item = (std::ops::Range<usize>, parsing::ScopeStackOp);
fn next(&mut self) -> Option<Self::Item> {
if self.index > self.ops.len() {
return None;
}
let next_str_i = if self.index == self.ops.len() {
self.line_length
} else {
self.ops[self.index].0
};
let range = self.last_str_index..next_str_i;
self.last_str_index = next_str_i;
let op = if self.index == 0 {
parsing::ScopeStackOp::Noop
} else {
self.ops[self.index - 1].1.clone()
};
self.index += 1;
Some((range, op))
}
}

View file

@ -0,0 +1,3 @@
mod highlight;
pub use highlight::*;

View file

@ -0,0 +1 @@
https://github.com/textmate/themes.tmbundle/tree/master/Themes

View file

@ -0,0 +1,597 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>author</key>
<string>Michael Sheets</string>
<key>gutterSettings</key>
<dict>
<key>background</key>
<string>#2F2F31</string>
<key>divider</key>
<string>#414143</string>
<key>foreground</key>
<string>#8F8F8F</string>
<key>selectionBackground</key>
<string>#414143</string>
<key>selectionBorder</key>
<string>#484848</string>
<key>selectionForeground</key>
<string>#BABABA</string>
</dict>
<key>name</key>
<string>Twilight</string>
<key>semanticClass</key>
<string>theme.dark.twilight</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#181818</string>
<key>caret</key>
<string>#A7A7A7</string>
<key>foreground</key>
<string>#F8F8F8</string>
<key>invisibles</key>
<string>#FFFFFF40</string>
<key>lineHighlight</key>
<string>#FFFFFF08</string>
<key>selection</key>
<string>#DDF0FF33</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Foldings</string>
<key>scope</key>
<string>deco.folding</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#5A6A65</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#5F5A60</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Constant</string>
<key>scope</key>
<string>constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CF6A4C</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Entity</string>
<key>scope</key>
<string>entity</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#9B703F</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword</string>
<key>scope</key>
<string>keyword</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#CDA869</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage</string>
<key>scope</key>
<string>storage</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#F9EE98</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#8F9D6A</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Support</string>
<key>scope</key>
<string>support</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#9B859D</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7587A6</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid Deprecated</string>
<key>scope</key>
<string>invalid.deprecated</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic underline</string>
<key>foreground</key>
<string>#D2A8A1</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid Illegal</string>
<key>scope</key>
<string>invalid.illegal</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#562D56BF</string>
<key>foreground</key>
<string>#F8F8F8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>-----------------------------------</string>
<key>settings</key>
<dict/>
</dict>
<dict>
<key>name</key>
<string>♦ Embedded Source</string>
<key>scope</key>
<string>meta.embedded.block, punctuation.whitespace.embedded</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#B0B3BA14</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ Embedded Source (Bright)</string>
<key>scope</key>
<string>meta.embedded.line</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#B1B3BA21</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ Entity inherited-class</string>
<key>scope</key>
<string>entity.other.inherited-class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#9B5C2E</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ String embedded-source</string>
<key>scope</key>
<string>string meta.embedded</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#DAEFA3</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ String constant</string>
<key>scope</key>
<string>string constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#DDF2A4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ String.regexp</string>
<key>scope</key>
<string>string.regexp</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#E9C062</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ String.regexp.«special»</string>
<key>scope</key>
<string>string.regexp constant.character.escape, string.regexp source.ruby.embedded, string.regexp string.regexp.arbitrary-repetition</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CF7D34</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ String variable</string>
<key>scope</key>
<string>string variable</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8A9A95</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ Support.function</string>
<key>scope</key>
<string>support.function</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#DAD085</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>♦ Support.constant</string>
<key>scope</key>
<string>support.constant</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#CF6A4C</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>c C/C++ Preprocessor Line</string>
<key>scope</key>
<string>meta.preprocessor.c, source.swift meta.preprocessor</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8996A8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>c C/C++ Preprocessor Directive</string>
<key>scope</key>
<string>meta.preprocessor.c keyword, source.swift meta.preprocessor keyword</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#AFC4DB</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>✘ Doctype/XML Processing</string>
<key>scope</key>
<string>meta.tag.metadata.doctype, meta.tag.metadata.doctype entity, meta.tag.metadata.doctype string, meta.tag.metadata.processing.xml, meta.tag.metadata.processing.xml entity, meta.tag.metadata.processing.xml string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#494949</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>✘ Meta.tag.«all»</string>
<key>scope</key>
<string>declaration.tag, declaration.tag entity, meta.tag, meta.tag entity</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#AC885B</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>✘ Meta.tag.inline</string>
<key>scope</key>
<string>declaration.tag.inline, declaration.tag.inline entity, meta.tag.inline, meta.tag.inline entity</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#E0C589</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css tag-name</string>
<key>scope</key>
<string>meta.selector.css entity.name.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CDA869</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css:pseudo-class</string>
<key>scope</key>
<string>meta.selector.css entity.other.attribute-name.tag.pseudo-class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8F9D6A</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css#id</string>
<key>scope</key>
<string>meta.selector.css entity.other.attribute-name.id</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8B98AB</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css.class</string>
<key>scope</key>
<string>meta.selector.css entity.other.attribute-name.class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#9B703F</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css property-name:</string>
<key>scope</key>
<string>support.type.property-name.css</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#C5AF75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css property-value;</string>
<key>scope</key>
<string>meta.property-group support.constant.property-value.css, meta.property-value support.constant.property-value.css</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#F9EE98</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css @at-rule</string>
<key>scope</key>
<string>meta.preprocessor.at-rule keyword.control.at-rule</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8693A5</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css additional-constants</string>
<key>scope</key>
<string>meta.property-value support.constant.named-color.css, meta.property-value constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CA7840</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>§ css constructor.argument</string>
<key>scope</key>
<string>meta.constructor.argument.css</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#8F9D6A</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>⎇ diff.header</string>
<key>scope</key>
<string>meta.diff, meta.diff.header, meta.separator, meta.diff.range, meta.diff.index</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#0E2231</string>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#F8F8F8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>⎇ diff.deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#420E09</string>
<key>foreground</key>
<string>#F8F8F8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>⎇ diff.changed</string>
<key>scope</key>
<string>markup.changed</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#4A410D</string>
<key>foreground</key>
<string>#F8F8F8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>⎇ diff.inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#253B22</string>
<key>foreground</key>
<string>#F8F8F8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup: List</string>
<key>scope</key>
<string>markup.list</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#F9EE98</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup: Heading</string>
<key>scope</key>
<string>markup.heading</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#CF6A4C</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup: Raw Block</string>
<key>scope</key>
<string>markup.raw.block</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#B0B3BA14</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Test: 1</string>
<key>scope</key>
<string>meta.test1</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#0E2231C5</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Test: 2</string>
<key>scope</key>
<string>meta.test2</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#420E0990</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Test: 3</string>
<key>scope</key>
<string>meta.test3</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#4A410D90</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Test: 4</string>
<key>scope</key>
<string>meta.test4</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#253B2290</string>
</dict>
</dict>
</array>
<key>uuid</key>
<string>766026CB-703D-4610-B070-8DE07D967C5F</string>
</dict>
</plist>

View file

@ -0,0 +1,467 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>author</key>
<string>Chris Thomas</string>
<key>name</key>
<string>Mac Classic</string>
<key>semanticClass</key>
<string>theme.light.mac-classic</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#FFFFFF</string>
<key>caret</key>
<string>#000000</string>
<key>foreground</key>
<string>#000000</string>
<key>invisibles</key>
<string>#BFBFBF</string>
<key>lineHighlight</key>
<string>#00000012</string>
<key>selection</key>
<string>#4D97FF54</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#0066FF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Foldings</string>
<key>scope</key>
<string>deco.folding</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#BFBFBF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword</string>
<key>scope</key>
<string>keyword, storage</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#0000FF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Number</string>
<key>scope</key>
<string>constant.numeric</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#0000CD</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>User-defined constant</string>
<key>scope</key>
<string>constant</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#C5060B</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Built-in constant</string>
<key>scope</key>
<string>constant.language</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#585CF6</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable.language, variable.other</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#318495</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#036A07</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String interpolation</string>
<key>scope</key>
<string>constant.character.escape, string meta.embedded</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#26B31A</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Preprocessor line</string>
<key>scope</key>
<string>meta.preprocessor</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#1A921C</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Preprocessor directive</string>
<key>scope</key>
<string>keyword.control.import</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#0C450D</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function name</string>
<key>scope</key>
<string>entity.name.function, support.function.any-method</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#0000A2</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Type name</string>
<key>scope</key>
<string>entity.name.type</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>underline</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Inherited class name</string>
<key>scope</key>
<string>entity.other.inherited-class</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function parameter</string>
<key>scope</key>
<string>variable.parameter</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function argument and result types</string>
<key>scope</key>
<string>storage.type.method</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#70727E</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Section</string>
<key>scope</key>
<string>meta.section entity.name.section, declaration.section entity.name.section</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library function</string>
<key>scope</key>
<string>support.function</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#3C4C72</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library object</string>
<key>scope</key>
<string>support.class, support.type</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#6D79DE</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library constant</string>
<key>scope</key>
<string>support.constant</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#06960E</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Library variable</string>
<key>scope</key>
<string>support.variable</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#21439C</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>JS: Operator</string>
<key>scope</key>
<string>keyword.operator.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#687687</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid</string>
<key>scope</key>
<string>invalid</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#990000</string>
<key>caret</key>
<string>#FFFFFF</string>
<key>foreground</key>
<string>#FFFFFF</string>
<key>selection</key>
<string>#4D97FF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid trailing whitespace</string>
<key>scope</key>
<string>invalid.deprecated.trailing-whitespace</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#FFD0D0</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Embedded source</string>
<key>scope</key>
<string>text source, string.unquoted, meta.embedded</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#0000000D</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Embedded embedded source</string>
<key>scope</key>
<string>text source string.unquoted, text source text source</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#0000000F</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup XML declaration</string>
<key>scope</key>
<string>meta.tag.preprocessor.xml</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#68685B</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup DOCTYPE</string>
<key>scope</key>
<string>meta.tag.metadata.doctype, meta.tag.metadata.doctype entity, meta.tag.metadata.doctype string, meta.tag.metadata.processing.xml, meta.tag.metadata.processing.xml entity, meta.tag.metadata.processing.xml string</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#888888</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup DTD</string>
<key>scope</key>
<string>meta.tag.metadata.doctype string.quoted</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup tag</string>
<key>scope</key>
<string>meta.tag, declaration.tag</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#1C02FF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup name of tag</string>
<key>scope</key>
<string>entity.name.tag</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup tag attribute</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup: Heading</string>
<key>scope</key>
<string>markup.heading</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
<key>foreground</key>
<string>#0C07FF</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup: Quote</string>
<key>scope</key>
<string>markup.quote</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#000000</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup: List</string>
<key>scope</key>
<string>markup.list</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#B90690</string>
</dict>
</dict>
</array>
<key>uuid</key>
<string>71D40D9D-AE48-11D9-920A-000D93589AF6</string>
</dict>
</plist>

View file

@ -25,6 +25,7 @@ pub mod dock;
pub mod drawer;
pub mod dropdown;
pub mod form;
pub mod highlighter;
pub mod history;
pub mod indicator;
pub mod input;

View file

@ -1,14 +1,14 @@
use std::ops::Range;
use gpui::{
div, img, prelude::FluentBuilder as _, px, relative, rems, App, DefiniteLength, ElementId,
FontStyle, FontWeight, Half, HighlightStyle, InteractiveElement as _, InteractiveText,
IntoElement, Length, ObjectFit, ParentElement, RenderOnce, SharedString, SharedUri, Styled,
StyledImage as _, StyledText, Window,
div, img, prelude::FluentBuilder as _, px, relative, rems, AnyElement, App, DefiniteLength,
ElementId, FontStyle, FontWeight, Half, HighlightStyle, InteractiveElement as _,
InteractiveText, IntoElement, Length, ObjectFit, ParentElement, Rems, RenderOnce, SharedString,
SharedUri, Styled, StyledImage as _, StyledText, Window,
};
use markdown::mdast;
use crate::{h_flex, v_flex, ActiveTheme as _, Icon, IconName};
use crate::{h_flex, highlighter::Highlighter, v_flex, ActiveTheme as _, Icon, IconName};
use super::{utils::list_item_prefix, TextViewStyle};
@ -199,6 +199,28 @@ impl Paragraph {
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CodeBlock {
code: SharedString,
lang: Option<SharedString>,
styles: Vec<(Range<usize>, HighlightStyle)>,
}
impl CodeBlock {
pub fn new(
code: SharedString,
lang: Option<SharedString>,
text_view_style: &TextViewStyle,
) -> Self {
let highlight = Highlighter::new(
lang.as_ref().map(|v| v.as_ref()),
text_view_style.highlight_theme.as_ref(),
);
let styles = highlight.highlight(code.as_ref());
Self { code, lang, styles }
}
}
/// Ref:
/// https://ui.shadcn.com/docs/components/typography
#[allow(unused)]
@ -224,10 +246,7 @@ pub enum Node {
/// Whether the list item is checked, if None, it's not a checkbox
checked: Option<bool>,
},
CodeBlock {
code: SharedString,
lang: Option<SharedString>,
},
CodeBlock(CodeBlock),
Table(Table),
Break {
html: bool,
@ -548,6 +567,24 @@ impl Node {
}
}
fn render_codeblock(
code_block: CodeBlock,
mb: Rems,
_: &mut Window,
cx: &mut App,
) -> AnyElement {
div()
.mb(mb)
.p_3()
.rounded(cx.theme().radius)
.bg(cx.theme().secondary)
.font_family("Menlo, Monaco, Consolas, monospace")
.text_size(rems(0.875))
.relative()
.child(StyledText::new(code_block.code.clone()).with_highlights(code_block.styles))
.into_any_element()
}
pub(crate) fn render(
self,
list_state: Option<ListState>,
@ -634,15 +671,7 @@ impl Node {
items
})
.into_any_element(),
Node::CodeBlock { code, .. } => div()
.mb(mb)
.rounded(cx.theme().radius)
.bg(cx.theme().secondary)
.p_3()
.text_size(rems(0.875))
.relative()
.child(code)
.into_any_element(),
Node::CodeBlock(code_block) => Self::render_codeblock(code_block, mb, window, cx),
Node::Table { .. } => Self::render_table(&self, window, cx).into_any_element(),
Node::Divider => div()
.bg(cx.theme().border)
@ -765,8 +794,12 @@ impl Node {
.join("\n")
)
}
Node::CodeBlock { code, lang } => {
format!("```{}\n{}\n```", lang.clone().unwrap_or_default(), code)
Node::CodeBlock(code_block) => {
format!(
"```{}\n{}\n```",
code_block.lang.clone().unwrap_or_default(),
code_block.code
)
}
Node::Table(table) => {
let header = table

View file

@ -10,7 +10,9 @@ use markdown::{
use crate::v_flex;
use super::{
element::{self, ImageNode, InlineTextStyle, LinkMark, Paragraph, Span, Table, TableRow},
element::{
self, CodeBlock, ImageNode, InlineTextStyle, LinkMark, Paragraph, Span, Table, TableRow,
},
html::parse_html,
TextViewStyle,
};
@ -54,18 +56,20 @@ impl MarkdownElement {
pub struct MarkdownState {
raw: SharedString,
root: Option<Result<element::Node, SharedString>>,
style: TextViewStyle,
}
impl MarkdownState {
fn parse_if_needed(&mut self, new_text: SharedString) {
let is_changed = self.raw != new_text;
fn parse_if_needed(&mut self, new_text: SharedString, style: &TextViewStyle) {
let is_changed = self.raw != new_text || self.style != *style;
if self.root.is_some() && !is_changed {
return;
}
self.raw = new_text;
self.root = Some(parse_markdown(&self.raw));
self.root = Some(parse_markdown(&self.raw, &style));
self.style = style.clone();
}
}
@ -93,7 +97,7 @@ impl Element for MarkdownElement {
) -> (gpui::LayoutId, Self::RequestLayoutState) {
window.with_element_state(id.unwrap(), |state, window| {
let mut state: MarkdownState = state.unwrap_or_default();
state.parse_if_needed(self.text.clone());
state.parse_if_needed(self.text.clone(), &self.style);
let root = state
.root
@ -143,9 +147,9 @@ impl Element for MarkdownElement {
}
/// Parse Markdown into a tree of nodes.
fn parse_markdown(raw: &str) -> Result<element::Node, SharedString> {
fn parse_markdown(raw: &str, style: &TextViewStyle) -> Result<element::Node, SharedString> {
markdown::to_mdast(&raw, &ParseOptions::gfm())
.map(|n| n.into())
.map(|n| ast_to_node(n, style))
.map_err(|e| e.to_string().into())
}
@ -335,137 +339,144 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
text
}
impl From<mdast::Node> for element::Node {
fn from(value: Node) -> Self {
match value {
Node::Root(val) => {
let children = val.children.into_iter().map(|c| c.into()).collect();
element::Node::Root { children }
}
Node::Paragraph(val) => {
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
fn ast_to_node(value: mdast::Node, style: &TextViewStyle) -> element::Node {
match value {
Node::Root(val) => {
let children = val
.children
.into_iter()
.map(|c| ast_to_node(c, style))
.collect();
element::Node::Root { children }
}
Node::Paragraph(val) => {
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Paragraph(paragraph)
}
Node::Blockquote(val) => {
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Paragraph(paragraph)
}
Node::Blockquote(val) => {
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Blockquote(paragraph)
element::Node::Blockquote(paragraph)
}
Node::List(list) => {
let children = list
.children
.into_iter()
.map(|c| ast_to_node(c, style))
.collect();
element::Node::List {
ordered: list.ordered,
children,
}
Node::List(list) => {
let children = list.children.into_iter().map(|c| c.into()).collect();
element::Node::List {
ordered: list.ordered,
children,
}
}
Node::ListItem(val) => {
let children = val
.children
.into_iter()
.map(|c| ast_to_node(c, style))
.collect();
element::Node::ListItem {
children,
spread: val.spread,
checked: val.checked,
}
Node::ListItem(val) => {
let children = val.children.into_iter().map(|c| c.into()).collect();
element::Node::ListItem {
children,
spread: val.spread,
checked: val.checked,
}
}
Node::Break(_) => element::Node::Break { html: false },
Node::Code(raw) => element::Node::CodeBlock {
code: raw.value.into(),
lang: raw.lang.map(|s| s.into()),
},
Node::Heading(val) => {
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
}
Node::Break(_) => element::Node::Break { html: false },
Node::Code(raw) => element::Node::CodeBlock(CodeBlock::new(
raw.value.into(),
raw.lang.map(|s| s.into()),
style,
)),
Node::Heading(val) => {
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Heading {
level: val.depth,
children: paragraph,
}
element::Node::Heading {
level: val.depth,
children: paragraph,
}
Node::Math(val) => element::Node::CodeBlock {
code: val.value.into(),
lang: Some("math".into()),
},
Node::Html(val) => match parse_html(&val.value) {
Ok(el) => el,
Err(err) => {
if cfg!(debug_assertions) {
eprintln!("[markdown] error parsing html: {:#?}", err);
}
element::Node::Paragraph(val.value.into())
}
},
Node::MdxFlowExpression(val) => element::Node::CodeBlock {
code: val.value.into(),
lang: Some("mdx".into()),
},
Node::Yaml(val) => element::Node::CodeBlock {
code: val.value.into(),
lang: Some("yaml".into()),
},
Node::Toml(val) => element::Node::CodeBlock {
code: val.value.into(),
lang: Some("toml".into()),
},
Node::MdxJsxTextElement(val) => {
println!("MdxJsxTextElement: {:#?}", val);
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Paragraph(paragraph)
}
Node::MdxJsxFlowElement(val) => {
println!("MdxJsxFlowElement: {:#?}", val);
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Paragraph(paragraph)
}
Node::ThematicBreak(_) => element::Node::Divider,
Node::Table(val) => {
let mut table = Table::default();
table.column_aligns = val
.align
.clone()
.into_iter()
.map(|align| align.into())
.collect();
val.children.iter().for_each(|c| {
if let Node::TableRow(row) = c {
parse_table_row(&mut table, row);
}
});
element::Node::Table(table)
}
_ => {
}
Node::Math(val) => element::Node::CodeBlock(CodeBlock::new(val.value.into(), None, style)),
Node::Html(val) => match parse_html(&val.value) {
Ok(el) => el,
Err(err) => {
if cfg!(debug_assertions) {
eprintln!("[markdown] unsupported node: {:#?}", value);
eprintln!("[markdown] error parsing html: {:#?}", err);
}
element::Node::Unknown
element::Node::Paragraph(val.value.into())
}
},
Node::MdxFlowExpression(val) => {
element::Node::CodeBlock(CodeBlock::new(val.value.into(), Some("mdx".into()), style))
}
Node::Yaml(val) => {
element::Node::CodeBlock(CodeBlock::new(val.value.into(), Some("yml".into()), style))
}
Node::Toml(val) => {
element::Node::CodeBlock(CodeBlock::new(val.value.into(), Some("toml".into()), style))
}
Node::MdxJsxTextElement(val) => {
println!("MdxJsxTextElement: {:#?}", val);
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Paragraph(paragraph)
}
Node::MdxJsxFlowElement(val) => {
println!("MdxJsxFlowElement: {:#?}", val);
let mut paragraph = Paragraph::default();
val.children.iter().for_each(|c| {
parse_paragraph(&mut paragraph, c);
});
element::Node::Paragraph(paragraph)
}
Node::ThematicBreak(_) => element::Node::Divider,
Node::Table(val) => {
let mut table = Table::default();
table.column_aligns = val
.align
.clone()
.into_iter()
.map(|align| align.into())
.collect();
val.children.iter().for_each(|c| {
if let Node::TableRow(row) = c {
parse_table_row(&mut table, row);
}
});
element::Node::Table(table)
}
_ => {
if cfg!(debug_assertions) {
eprintln!("[markdown] unsupported node: {:#?}", value);
}
element::Node::Unknown
}
}
}
#[cfg(test)]
mod tests {
use crate::text::TextViewStyle;
use super::parse_markdown;
#[test]
fn test_parse_br() {
let raw = "Row 1<br/>Row 2<br>[Link](https://github.com)";
let node = parse_markdown(&raw).unwrap();
let node = parse_markdown(&raw, &TextViewStyle::default()).unwrap();
assert_eq!(
node.to_markdown(),
"Row 1\nRow 2\n[Link](https://github.com)"

View file

@ -1,5 +1,9 @@
use std::rc::Rc;
use gpui::{px, rems, App, ElementId, IntoElement, Pixels, Rems, RenderOnce, SharedString, Window};
use crate::highlighter::HighlightTheme;
use super::{html::HtmlElement, markdown::MarkdownElement};
/// A text view that can render Markdown or HTML.
@ -65,12 +69,14 @@ impl RenderOnce for Text {
}
/// TextViewStyle used to customize the style for [`TextView`].
#[derive(Copy, Clone)]
#[derive(Clone, PartialEq)]
pub struct TextViewStyle {
/// Gap of each paragraphs, default is 1 rem.
pub paragraph_gap: Rems,
/// Base font size for headings, default is 14px.
pub heading_base_font_size: Pixels,
/// Highlight theme for code blocks. Default: [`HighlightTheme::default_light()`]
pub highlight_theme: Rc<HighlightTheme>,
}
impl Default for TextViewStyle {
@ -78,6 +84,7 @@ impl Default for TextViewStyle {
Self {
paragraph_gap: rems(1.),
heading_base_font_size: px(14.),
highlight_theme: Rc::new(HighlightTheme::default_light()),
}
}
}

View file

@ -541,8 +541,9 @@ impl Theme {
impl From<ThemeColor> for Theme {
fn from(colors: ThemeColor) -> Self {
let mode = ThemeMode::default();
Theme {
mode: ThemeMode::default(),
mode,
transparent: Hsla::transparent_black(),
font_size: px(16.),
font_family: if cfg!(target_os = "macos") {