From 40969ad4528bbb774e2b95dce0749b1dd3734ebf Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Wed, 15 Dec 2021 17:46:40 +0900 Subject: Partly fix latex highlights and add markup scope docs --- book/src/themes.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'book/src/themes.md') diff --git a/book/src/themes.md b/book/src/themes.md index fd3f5b1e..b6de7002 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -162,6 +162,20 @@ We use a similar set of scopes as - `namespace` +- `markup` + - `heading` + - `list` + - `unnumbered` + - `numbered` + - `bold` + - `italic` + - `underline` + - `link` + - `quote` + - `raw` + - `inline` + - `block` + #### Interface These scopes are used for theming the editor interface. -- cgit v1.2.3-70-g09d2 From 7c01d926536bcaacc92bbf4a2b1a06e96e6268a0 Mon Sep 17 00:00:00 2001 From: Gokul Soumya Date: Sun, 19 Dec 2021 00:05:43 +0530 Subject: Add link and quote queries for markdown - Rename markup.underline.link to markup.link.url - Add markup.link.label - Add markup.quote (The constructor theme scope was missing from the docs, so unrelated to this commit). --- book/src/themes.md | 6 ++++-- runtime/queries/latex/highlights.scm | 2 +- runtime/queries/markdown/highlights.scm | 7 ++++--- runtime/queries/svelte/highlights.scm | 4 ++-- theme.toml | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) (limited to 'book/src/themes.md') diff --git a/book/src/themes.md b/book/src/themes.md index b6de7002..fce2c0ca 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -105,6 +105,7 @@ We use a similar set of scopes as - `type` - Types - `builtin` - Primitive types provided by the language (`int`, `usize`) +- `constructor` - `constant` (TODO: constant.other.placeholder for %v) - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc) @@ -169,8 +170,9 @@ We use a similar set of scopes as - `numbered` - `bold` - `italic` - - `underline` - - `link` + - `link` + - `url` + - `label` - `quote` - `raw` - `inline` diff --git a/runtime/queries/latex/highlights.scm b/runtime/queries/latex/highlights.scm index 2e308f77..0a030b31 100644 --- a/runtime/queries/latex/highlights.scm +++ b/runtime/queries/latex/highlights.scm @@ -371,7 +371,7 @@ ((generic_command name:(generic_command_name) @_name . - arg: (_) @markup.underline.link) + arg: (_) @markup.link.url) (#match? @_name "^(\\\\url|\\\\href)$")) (ERROR) @error diff --git a/runtime/queries/markdown/highlights.scm b/runtime/queries/markdown/highlights.scm index a102719b..a0bd3462 100644 --- a/runtime/queries/markdown/highlights.scm +++ b/runtime/queries/markdown/highlights.scm @@ -10,15 +10,16 @@ (fenced_code_block) ] @markup.raw.block +(block_quote) @markup.quote + (code_span) @markup.raw.inline (emphasis) @markup.italic (strong_emphasis) @markup.bold -(link_destination) @markup.underline.link - -; (link_label) @markup.label ; TODO: rename +(link_destination) @markup.link.url +(link_label) @markup.link.label [ (list_marker_plus) diff --git a/runtime/queries/svelte/highlights.scm b/runtime/queries/svelte/highlights.scm index f9eef6b5..22b0c551 100644 --- a/runtime/queries/svelte/highlights.scm +++ b/runtime/queries/svelte/highlights.scm @@ -20,12 +20,12 @@ ((element (start_tag (tag_name) @_tag) (text) @markup.inline) (#match? @_tag "^(code|kbd)$")) -((element (start_tag (tag_name) @_tag) (text) @markup.underline.link) +((element (start_tag (tag_name) @_tag) (text) @markup.link.url) (#eq? @_tag "a")) ((attribute (attribute_name) @_attr - (quoted_attribute_value (attribute_value) @markup.underline.link)) + (quoted_attribute_value (attribute_value) @markup.link.url)) (#match? @_attr "^(href|src)$")) (tag_name) @tag diff --git a/theme.toml b/theme.toml index 0a79861e..b316e814 100644 --- a/theme.toml +++ b/theme.toml @@ -31,7 +31,7 @@ label = "honey" "markup.heading" = "lilac" "markup.bold" = { modifiers = ["bold"] } "markup.italic" = { modifiers = ["italic"] } -"markup.underline.link" = { fg = "silver", modifiers = ["underlined"] } +"markup.link.url" = { fg = "silver", modifiers = ["underlined"] } "markup.raw" = "almond" # TODO: diferentiate doc comment -- cgit v1.2.3-70-g09d2 From c1f4c0e67acc65a263440fce98514c9e6de1940d Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 22 Dec 2021 09:58:51 -0600 Subject: add new scopes to themes docs --- book/src/themes.md | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'book/src/themes.md') diff --git a/book/src/themes.md b/book/src/themes.md index fce2c0ca..8eee334b 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -178,6 +178,12 @@ We use a similar set of scopes as - `inline` - `block` +- `diff` - version control changes + - `plus` - additions + - `minus` - deletions + - `delta` - modifications + - `moved` - renamed or moved files/changes + #### Interface These scopes are used for theming the editor interface. -- cgit v1.2.3-70-g09d2 From 1af8dd9912d655cfc47979d40738ee4ebaa2521a Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 7 Jan 2022 19:14:31 +0100 Subject: Rework beginning of themes chapter The specifics of configuring themes has caused some confusion. Hopefully this will clarify things a little. --- book/src/themes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'book/src/themes.md') diff --git a/book/src/themes.md b/book/src/themes.md index 8eee334b..7474122f 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -1,14 +1,14 @@ # Themes -First you'll need to place selected themes in your `themes` directory (i.e `~/.config/helix/themes`), the directory might have to be created beforehand. +To use a theme add `theme = ""` to your [`config.toml`](./configuration.md) at the very top of the file before the first section or select it during runtime using `:theme `. -To use a custom theme add `theme = ` to your [`config.toml`](./configuration.md) or override it during runtime using `:theme `. +## Creating a theme -The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/runtime/themes). +Create a file with the name of your theme as file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes`). The directory might have to be created beforehand. -## Creating a theme +The names "default" and "base16_default" are reserved for the builtin themes and cannot be overridden by user defined themes. -First create a file with the name of your theme as file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes`). +The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/runtime/themes). Each line in the theme file is specified as below: -- cgit v1.2.3-70-g09d2 From b799b0d50e7848cc084d913afbdc0fb3a25c1b97 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Sat, 8 Jan 2022 09:27:50 -0600 Subject: capture markdown link text as markup.link.text (#1456) --- book/src/themes.md | 5 +++-- runtime/queries/markdown/highlights.scm | 5 +++++ theme.toml | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) (limited to 'book/src/themes.md') diff --git a/book/src/themes.md b/book/src/themes.md index 7474122f..29d3926e 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -171,8 +171,9 @@ We use a similar set of scopes as - `bold` - `italic` - `link` - - `url` - - `label` + - `url` - urls pointed to by links + - `label` - non-url link references + - `text` - url and image descriptions in links - `quote` - `raw` - `inline` diff --git a/runtime/queries/markdown/highlights.scm b/runtime/queries/markdown/highlights.scm index a0bd3462..f12254e9 100644 --- a/runtime/queries/markdown/highlights.scm +++ b/runtime/queries/markdown/highlights.scm @@ -21,6 +21,11 @@ (link_destination) @markup.link.url (link_label) @markup.link.label +[ + (link_text) + (image_description) +] @markup.link.text + [ (list_marker_plus) (list_marker_minus) diff --git a/theme.toml b/theme.toml index ca0b2805..d2c1fc32 100644 --- a/theme.toml +++ b/theme.toml @@ -32,6 +32,7 @@ label = "honey" "markup.bold" = { modifiers = ["bold"] } "markup.italic" = { modifiers = ["italic"] } "markup.link.url" = { fg = "silver", modifiers = ["underlined"] } +"markup.link.text" = "almond" "markup.raw" = "almond" "diff.plus" = "#35bf86" -- cgit v1.2.3-70-g09d2 From 5b45bdd80f1b2bd456d247ed6d2d95e9efb9d304 Mon Sep 17 00:00:00 2001 From: Eric Crosson Date: Mon, 3 Jan 2022 20:31:17 -0600 Subject: docs: document @keyword.control.exception scope As identified in [this GitHub comment](https://github.com/helix-editor/helix/pull/1433#discussion_r777786140) --- book/src/themes.md | 1 + 1 file changed, 1 insertion(+) (limited to 'book/src/themes.md') diff --git a/book/src/themes.md b/book/src/themes.md index 29d3926e..1325de8c 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -147,6 +147,7 @@ We use a similar set of scopes as - `repeat` - `for`, `while`, `loop` - `import` - `import`, `export` - `return` + - `exception` - `operator` - `or`, `in` - `directive` - Preprocessor directives (`#if` in C) - `function` - `fn`, `func` -- cgit v1.2.3-70-g09d2 From d49e5323f9230f3195d3ee4c5e682cd6d8c2cb1a Mon Sep 17 00:00:00 2001 From: CossonLeo Date: Mon, 24 Jan 2022 09:41:25 +0800 Subject: Use markup scopes for the Markdown component (#1363) --- book/src/themes.md | 12 ++ helix-term/src/commands.rs | 3 +- helix-term/src/ui/completion.rs | 9 +- helix-term/src/ui/markdown.rs | 313 +++++++++++++++++++++------------------- 4 files changed, 184 insertions(+), 153 deletions(-) (limited to 'book/src/themes.md') diff --git a/book/src/themes.md b/book/src/themes.md index 1325de8c..9abcfe8c 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -190,6 +190,18 @@ We use a similar set of scopes as These scopes are used for theming the editor interface. +- `markup` + - `normal` + - `completion` - for completion doc popup ui + - `hover` - for hover popup ui + - `heading` + - `completion` - for completion doc popup ui + - `hover` - for hover popup ui + - `raw` + - `inline` + - `completion` - for completion doc popup ui + - `hover` - for hover popup ui + | Key | Notes | | --- | --- | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fc0db6ed..7144ebb9 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5455,7 +5455,8 @@ fn hover(cx: &mut Context) { // skip if contents empty - let contents = ui::Markdown::new(contents, editor.syn_loader.clone()); + let contents = + ui::Markdown::new(contents, editor.syn_loader.clone()).style_group("hover"); let popup = Popup::new("hover", contents); if let Some(doc_popup) = compositor.find_id("hover") { *doc_popup = popup; diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index c9ed3b4a..35afe81e 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -304,6 +304,9 @@ impl Component for Completion { let cursor_pos = doc.selection(view.id).primary().cursor(text); let coords = helix_core::visual_coords_at_pos(text, cursor_pos, doc.tab_width()); let cursor_pos = (coords.row - view.offset.row) as u16; + + let markdown_ui = + |content, syn_loader| Markdown::new(content, syn_loader).style_group("completion"); let mut markdown_doc = match &option.documentation { Some(lsp::Documentation::String(contents)) | Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { @@ -311,7 +314,7 @@ impl Component for Completion { value: contents, })) => { // TODO: convert to wrapped text - Markdown::new( + markdown_ui( format!( "```{}\n{}\n```\n{}", language, @@ -326,7 +329,7 @@ impl Component for Completion { value: contents, })) => { // TODO: set language based on doc scope - Markdown::new( + markdown_ui( format!( "```{}\n{}\n```\n{}", language, @@ -340,7 +343,7 @@ impl Component for Completion { // TODO: copied from above // TODO: set language based on doc scope - Markdown::new( + markdown_ui( format!( "```{}\n{}\n```", language, diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 00da2c11..003266d3 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -21,6 +21,10 @@ pub struct Markdown { contents: String, config_loader: Arc, + + text_style: String, + block_style: String, + heading_style: String, } // TODO: pre-render and self reference via Pin @@ -31,121 +35,139 @@ impl Markdown { Self { contents, config_loader, + text_style: "markup.normal".into(), + block_style: "markup.raw.inline".into(), + heading_style: "markup.heading".into(), } } -} -fn parse<'a>( - contents: &'a str, - theme: Option<&Theme>, - loader: Arc, -) -> tui::text::Text<'a> { - // // also 2021-03-04T16:33:58.553 helix_lsp::transport [INFO] <- {"contents":{"kind":"markdown","value":"\n```rust\ncore::num\n```\n\n```rust\npub const fn saturating_sub(self, rhs:Self) ->Self\n```\n\n---\n\n```rust\n```"},"range":{"end":{"character":61,"line":101},"start":{"character":47,"line":101}}} - // let text = "\n```rust\ncore::iter::traits::iterator::Iterator\n```\n\n```rust\nfn collect>(self) -> B\nwhere\n Self: Sized,\n```\n\n---\n\nTransforms an iterator into a collection.\n\n`collect()` can take anything iterable, and turn it into a relevant\ncollection. This is one of the more powerful methods in the standard\nlibrary, used in a variety of contexts.\n\nThe most basic pattern in which `collect()` is used is to turn one\ncollection into another. You take a collection, call [`iter`](https://doc.rust-lang.org/nightly/core/iter/traits/iterator/trait.Iterator.html) on it,\ndo a bunch of transformations, and then `collect()` at the end.\n\n`collect()` can also create instances of types that are not typical\ncollections. For example, a [`String`](https://doc.rust-lang.org/nightly/core/iter/std/string/struct.String.html) can be built from [`char`](type@char)s,\nand an iterator of [`Result`](https://doc.rust-lang.org/nightly/core/result/enum.Result.html) items can be collected\ninto `Result, E>`. See the examples below for more.\n\nBecause `collect()` is so general, it can cause problems with type\ninference. As such, `collect()` is one of the few times you'll see\nthe syntax affectionately known as the 'turbofish': `::<>`. This\nhelps the inference algorithm understand specifically which collection\nyou're trying to collect into.\n\n# Examples\n\nBasic usage:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled: Vec = a.iter()\n .map(|&x| x * 2)\n .collect();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nNote that we needed the `: Vec` on the left-hand side. This is because\nwe could collect into, for example, a [`VecDeque`](https://doc.rust-lang.org/nightly/core/iter/std/collections/struct.VecDeque.html) instead:\n\n```rust\nuse std::collections::VecDeque;\n\nlet a = [1, 2, 3];\n\nlet doubled: VecDeque = a.iter().map(|&x| x * 2).collect();\n\nassert_eq!(2, doubled[0]);\nassert_eq!(4, doubled[1]);\nassert_eq!(6, doubled[2]);\n```\n\nUsing the 'turbofish' instead of annotating `doubled`:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled = a.iter().map(|x| x * 2).collect::>();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nBecause `collect()` only cares about what you're collecting into, you can\nstill use a partial type hint, `_`, with the turbofish:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled = a.iter().map(|x| x * 2).collect::>();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nUsing `collect()` to make a [`String`](https://doc.rust-lang.org/nightly/core/iter/std/string/struct.String.html):\n\n```rust\nlet chars = ['g', 'd', 'k', 'k', 'n'];\n\nlet hello: String = chars.iter()\n .map(|&x| x as u8)\n .map(|x| (x + 1) as char)\n .collect();\n\nassert_eq!(\"hello\", hello);\n```\n\nIf you have a list of [`Result`](https://doc.rust-lang.org/nightly/core/result/enum.Result.html)s, you can use `collect()` to\nsee if any of them failed:\n\n```rust\nlet results = [Ok(1), Err(\"nope\"), Ok(3), Err(\"bad\")];\n\nlet result: Result, &str> = results.iter().cloned().collect();\n\n// gives us the first error\nassert_eq!(Err(\"nope\"), result);\n\nlet results = [Ok(1), Ok(3)];\n\nlet result: Result, &str> = results.iter().cloned().collect();\n\n// gives us the list of answers\nassert_eq!(Ok(vec![1, 3]), result);\n```"; - - let mut options = Options::empty(); - options.insert(Options::ENABLE_STRIKETHROUGH); - let parser = Parser::new_ext(contents, options); - - // TODO: if possible, render links as terminal hyperlinks: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda - let mut tags = Vec::new(); - let mut spans = Vec::new(); - let mut lines = Vec::new(); - - fn to_span(text: pulldown_cmark::CowStr) -> Span { - use std::ops::Deref; - Span::raw::>(match text { - CowStr::Borrowed(s) => s.into(), - CowStr::Boxed(s) => s.to_string().into(), - CowStr::Inlined(s) => s.deref().to_owned().into(), - }) + pub fn style_group(mut self, suffix: &str) -> Self { + self.text_style = format!("markup.normal.{}", suffix); + self.block_style = format!("markup.raw.inline.{}", suffix); + self.heading_style = format!("markup.heading.{}", suffix); + self } - let text_style = theme.map(|theme| theme.get("ui.text")).unwrap_or_default(); - - // TODO: use better scopes for these, `markup.raw.block`, `markup.heading` - let code_style = theme - .map(|theme| theme.get("ui.text.focus")) - .unwrap_or_default(); // white - let heading_style = theme - .map(|theme| theme.get("ui.linenr.selected")) - .unwrap_or_default(); // lilac - - for event in parser { - match event { - Event::Start(tag) => tags.push(tag), - Event::End(tag) => { - tags.pop(); - match tag { - Tag::Heading(_, _, _) - | Tag::Paragraph - | Tag::CodeBlock(CodeBlockKind::Fenced(_)) => { - // whenever code block or paragraph closes, new line - let spans = std::mem::take(&mut spans); - if !spans.is_empty() { - lines.push(Spans::from(spans)); + fn parse(&self, theme: Option<&Theme>) -> tui::text::Text<'_> { + // // also 2021-03-04T16:33:58.553 helix_lsp::transport [INFO] <- {"contents":{"kind":"markdown","value":"\n```rust\ncore::num\n```\n\n```rust\npub const fn saturating_sub(self, rhs:Self) ->Self\n```\n\n---\n\n```rust\n```"},"range":{"end":{"character":61,"line":101},"start":{"character":47,"line":101}}} + // let text = "\n```rust\ncore::iter::traits::iterator::Iterator\n```\n\n```rust\nfn collect>(self) -> B\nwhere\n Self: Sized,\n```\n\n---\n\nTransforms an iterator into a collection.\n\n`collect()` can take anything iterable, and turn it into a relevant\ncollection. This is one of the more powerful methods in the standard\nlibrary, used in a variety of contexts.\n\nThe most basic pattern in which `collect()` is used is to turn one\ncollection into another. You take a collection, call [`iter`](https://doc.rust-lang.org/nightly/core/iter/traits/iterator/trait.Iterator.html) on it,\ndo a bunch of transformations, and then `collect()` at the end.\n\n`collect()` can also create instances of types that are not typical\ncollections. For example, a [`String`](https://doc.rust-lang.org/nightly/core/iter/std/string/struct.String.html) can be built from [`char`](type@char)s,\nand an iterator of [`Result`](https://doc.rust-lang.org/nightly/core/result/enum.Result.html) items can be collected\ninto `Result, E>`. See the examples below for more.\n\nBecause `collect()` is so general, it can cause problems with type\ninference. As such, `collect()` is one of the few times you'll see\nthe syntax affectionately known as the 'turbofish': `::<>`. This\nhelps the inference algorithm understand specifically which collection\nyou're trying to collect into.\n\n# Examples\n\nBasic usage:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled: Vec = a.iter()\n .map(|&x| x * 2)\n .collect();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nNote that we needed the `: Vec` on the left-hand side. This is because\nwe could collect into, for example, a [`VecDeque`](https://doc.rust-lang.org/nightly/core/iter/std/collections/struct.VecDeque.html) instead:\n\n```rust\nuse std::collections::VecDeque;\n\nlet a = [1, 2, 3];\n\nlet doubled: VecDeque = a.iter().map(|&x| x * 2).collect();\n\nassert_eq!(2, doubled[0]);\nassert_eq!(4, doubled[1]);\nassert_eq!(6, doubled[2]);\n```\n\nUsing the 'turbofish' instead of annotating `doubled`:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled = a.iter().map(|x| x * 2).collect::>();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nBecause `collect()` only cares about what you're collecting into, you can\nstill use a partial type hint, `_`, with the turbofish:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled = a.iter().map(|x| x * 2).collect::>();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nUsing `collect()` to make a [`String`](https://doc.rust-lang.org/nightly/core/iter/std/string/struct.String.html):\n\n```rust\nlet chars = ['g', 'd', 'k', 'k', 'n'];\n\nlet hello: String = chars.iter()\n .map(|&x| x as u8)\n .map(|x| (x + 1) as char)\n .collect();\n\nassert_eq!(\"hello\", hello);\n```\n\nIf you have a list of [`Result`](https://doc.rust-lang.org/nightly/core/result/enum.Result.html)s, you can use `collect()` to\nsee if any of them failed:\n\n```rust\nlet results = [Ok(1), Err(\"nope\"), Ok(3), Err(\"bad\")];\n\nlet result: Result, &str> = results.iter().cloned().collect();\n\n// gives us the first error\nassert_eq!(Err(\"nope\"), result);\n\nlet results = [Ok(1), Ok(3)];\n\nlet result: Result, &str> = results.iter().cloned().collect();\n\n// gives us the list of answers\nassert_eq!(Ok(vec![1, 3]), result);\n```"; + + let mut options = Options::empty(); + options.insert(Options::ENABLE_STRIKETHROUGH); + let parser = Parser::new_ext(&self.contents, options); + + // TODO: if possible, render links as terminal hyperlinks: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + let mut tags = Vec::new(); + let mut spans = Vec::new(); + let mut lines = Vec::new(); + + fn to_span(text: pulldown_cmark::CowStr) -> Span { + use std::ops::Deref; + Span::raw::>(match text { + CowStr::Borrowed(s) => s.into(), + CowStr::Boxed(s) => s.to_string().into(), + CowStr::Inlined(s) => s.deref().to_owned().into(), + }) + } + + macro_rules! get_theme { + ($s1: expr) => { + theme + .map(|theme| theme.try_get($s1.as_str())) + .flatten() + .unwrap_or_default() + }; + } + let text_style = get_theme!(self.text_style); + let code_style = get_theme!(self.block_style); + let heading_style = get_theme!(self.heading_style); + + for event in parser { + match event { + Event::Start(tag) => tags.push(tag), + Event::End(tag) => { + tags.pop(); + match tag { + Tag::Heading(_, _, _) + | Tag::Paragraph + | Tag::CodeBlock(CodeBlockKind::Fenced(_)) => { + // whenever code block or paragraph closes, new line + let spans = std::mem::take(&mut spans); + if !spans.is_empty() { + lines.push(Spans::from(spans)); + } + lines.push(Spans::default()); } - lines.push(Spans::default()); + _ => (), } - _ => (), } - } - Event::Text(text) => { - // TODO: temp workaround - if let Some(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = tags.last() { - if let Some(theme) = theme { - let rope = Rope::from(text.as_ref()); - let syntax = loader - .language_configuration_for_injection_string(language) - .and_then(|config| config.highlight_config(theme.scopes())) - .map(|config| Syntax::new(&rope, config, loader.clone())); - - if let Some(syntax) = syntax { - // if we have a syntax available, highlight_iter and generate spans - let mut highlights = Vec::new(); - - for event in syntax.highlight_iter(rope.slice(..), None, None) { - match event.unwrap() { - HighlightEvent::HighlightStart(span) => { - highlights.push(span); - } - HighlightEvent::HighlightEnd => { - highlights.pop(); - } - HighlightEvent::Source { start, end } => { - let style = match highlights.first() { - Some(span) => theme.get(&theme.scopes()[span.0]), - None => text_style, - }; - - // TODO: replace tabs with indentation - - let mut slice = &text[start..end]; - // TODO: do we need to handle all unicode line endings - // here, or is just '\n' okay? - while let Some(end) = slice.find('\n') { - // emit span up to newline - let text = &slice[..end]; - let text = text.replace('\t', " "); // replace tabs - let span = Span::styled(text, style); - spans.push(span); - - // truncate slice to after newline - slice = &slice[end + 1..]; - - // make a new line - let spans = std::mem::take(&mut spans); - lines.push(Spans::from(spans)); + Event::Text(text) => { + // TODO: temp workaround + if let Some(Tag::CodeBlock(CodeBlockKind::Fenced(language))) = tags.last() { + if let Some(theme) = theme { + let rope = Rope::from(text.as_ref()); + let syntax = self + .config_loader + .language_configuration_for_injection_string(language) + .and_then(|config| config.highlight_config(theme.scopes())) + .map(|config| { + Syntax::new(&rope, config, self.config_loader.clone()) + }); + + if let Some(syntax) = syntax { + // if we have a syntax available, highlight_iter and generate spans + let mut highlights = Vec::new(); + + for event in syntax.highlight_iter(rope.slice(..), None, None) { + match event.unwrap() { + HighlightEvent::HighlightStart(span) => { + highlights.push(span); } + HighlightEvent::HighlightEnd => { + highlights.pop(); + } + HighlightEvent::Source { start, end } => { + let style = match highlights.first() { + Some(span) => theme.get(&theme.scopes()[span.0]), + None => text_style, + }; + + // TODO: replace tabs with indentation + + let mut slice = &text[start..end]; + // TODO: do we need to handle all unicode line endings + // here, or is just '\n' okay? + while let Some(end) = slice.find('\n') { + // emit span up to newline + let text = &slice[..end]; + let text = text.replace('\t', " "); // replace tabs + let span = Span::styled(text, style); + spans.push(span); + + // truncate slice to after newline + slice = &slice[end + 1..]; + + // make a new line + let spans = std::mem::take(&mut spans); + lines.push(Spans::from(spans)); + } - // if there's anything left, emit it too - if !slice.is_empty() { - let span = - Span::styled(slice.replace('\t', " "), style); - spans.push(span); + // if there's anything left, emit it too + if !slice.is_empty() { + let span = Span::styled( + slice.replace('\t', " "), + style, + ); + spans.push(span); + } } } } + } else { + for line in text.lines() { + let span = Span::styled(line.to_string(), code_style); + lines.push(Spans::from(span)); + } } } else { for line in text.lines() { @@ -153,68 +175,60 @@ fn parse<'a>( lines.push(Spans::from(span)); } } + } else if let Some(Tag::Heading(_, _, _)) = tags.last() { + let mut span = to_span(text); + span.style = heading_style; + spans.push(span); } else { - for line in text.lines() { - let span = Span::styled(line.to_string(), code_style); - lines.push(Spans::from(span)); - } + let mut span = to_span(text); + span.style = text_style; + spans.push(span); } - } else if let Some(Tag::Heading(_, _, _)) = tags.last() { - let mut span = to_span(text); - span.style = heading_style; - spans.push(span); - } else { + } + Event::Code(text) | Event::Html(text) => { let mut span = to_span(text); - span.style = text_style; + span.style = code_style; spans.push(span); } + Event::SoftBreak | Event::HardBreak => { + // let spans = std::mem::replace(&mut spans, Vec::new()); + // lines.push(Spans::from(spans)); + spans.push(Span::raw(" ")); + } + Event::Rule => { + let mut span = Span::raw("---"); + span.style = code_style; + lines.push(Spans::from(span)); + lines.push(Spans::default()); + } + // TaskListMarker(bool) true if checked + _ => { + log::warn!("unhandled markdown event {:?}", event); + } } - Event::Code(text) | Event::Html(text) => { - let mut span = to_span(text); - span.style = code_style; - spans.push(span); - } - Event::SoftBreak | Event::HardBreak => { - // let spans = std::mem::replace(&mut spans, Vec::new()); - // lines.push(Spans::from(spans)); - spans.push(Span::raw(" ")); - } - Event::Rule => { - let mut span = Span::raw("---"); - span.style = code_style; - lines.push(Spans::from(span)); - lines.push(Spans::default()); - } - // TaskListMarker(bool) true if checked - _ => { - log::warn!("unhandled markdown event {:?}", event); - } + // build up a vec of Paragraph tui widgets } - // build up a vec of Paragraph tui widgets - } - if !spans.is_empty() { - lines.push(Spans::from(spans)); - } + if !spans.is_empty() { + lines.push(Spans::from(spans)); + } - // if last line is empty, remove it - if let Some(line) = lines.last() { - if line.0.is_empty() { - lines.pop(); + // if last line is empty, remove it + if let Some(line) = lines.last() { + if line.0.is_empty() { + lines.pop(); + } } - } - Text::from(lines) + Text::from(lines) + } } + impl Component for Markdown { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { use tui::widgets::{Paragraph, Widget, Wrap}; - let text = parse( - &self.contents, - Some(&cx.editor.theme), - self.config_loader.clone(), - ); + let text = self.parse(Some(&cx.editor.theme)); let par = Paragraph::new(text) .wrap(Wrap { trim: false }) @@ -232,7 +246,8 @@ impl Component for Markdown { if padding >= viewport.1 || padding >= viewport.0 { return None; } - let contents = parse(&self.contents, None, self.config_loader.clone()); + let contents = self.parse(None); + // TODO: account for tab width let max_text_width = (viewport.0 - padding).min(120); let mut text_width = 0; -- cgit v1.2.3-70-g09d2