Skip to content

Commit

Permalink
Merge pull request #262 from gjtorikian/classy-highlighting
Browse files Browse the repository at this point in the history
Classy highlighting
  • Loading branch information
gjtorikian authored Dec 2, 2023
2 parents d395309 + 79044af commit 8b5f285
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 123 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
"rust-analyzer.checkOnSave.command": "clippy",
"[ruby]": {
"editor.defaultFormatter": "Shopify.ruby-lsp"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
67 changes: 48 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,26 @@ providing further niceties.

#### Syntax Highlighter Plugin

The library comes with [a set of pre-existing themes](https://docs.rs/syntect/5.0.0/syntect/highlighting/struct.ThemeSet.html#implementations) for highlighting code:

- `"base16-ocean.dark"`
- `"base16-eighties.dark"`
- `"base16-mocha.dark"`
- `"base16-ocean.light"`
- `"InspiredGitHub"`
- `"Solarized (dark)"`
- `"Solarized (light)"`

````ruby
code = <<~CODE
```ruby
def hello
puts "hello"
puts "hello"
end
```
CODE

# pass in a theme name from a pre-existing set
puts Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: "InspiredGitHub" } })

# <pre style="background-color:#ffffff;" lang="ruby"><code>
Expand All @@ -123,32 +135,49 @@ puts Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: "Inspire
# </code></pre>
````

To disable this plugin, pass `nil`:
By default, the plugin uses the `"base16-ocean.dark"` theme to syntax highlight code.

To disable this plugin, set the value to `nil`:

````ruby
code = <<~CODE
```ruby
def hello
puts "hello"
end
```
CODE

```ruby
Commonmarker.to_html(code, plugins: { syntax_highlighter: nil })
# or
Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: nil } })
```

You can also provide a `path` to a directory containing `.tmtheme` files to load:
# <pre lang="ruby"><code>def hello
# puts &quot;hello&quot;
# end
# </code></pre>
````

To output CSS classes instead of `style` attributes, set the `theme` key to `""`:

```ruby
````ruby
code = <<~CODE
```ruby
def hello
puts "hello"
end
CODE

Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: "Monokai", path: "./themes" } })
```
Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: "" } })

##### Available themes
# <pre class="syntax-highlighting"><code><span class="source ruby"><span class="meta function ruby"><span class="keyword control def ruby">def</span></span><span class="meta function ruby"> # <span class="entity name function ruby">hello</span></span>
# <span class="support function builtin ruby">puts</span> <span class="string quoted double ruby"><span class="punctuation definition string begin ruby">&quot;</span>hello<span class="punctuation definition string end ruby">&quot;</span></span>
# <span class="keyword control ruby">end</span>\n</span></code></pre>
````

Here's [a list of themes available by default](https://docs.rs/syntect/5.0.0/syntect/highlighting/struct.ThemeSet.html#implementations):
To use a custom theme, you can provide a `path` to a directory containing `.tmtheme` files to load:

- `"base16-ocean.dark"`
- `"base16-eighties.dark"`
- `"base16-mocha.dark"`
- `"base16-ocean.light"`
- `"InspiredGitHub"`
- `"Solarized (dark)"`
- `"Solarized (light)"`
```ruby
Commonmarker.to_html(code, plugins: { syntax_highlighter: { theme: "Monokai", path: "./themes" } })
```

## Output formats

Expand Down
142 changes: 72 additions & 70 deletions ext/commonmarker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ use options::iterate_options_hash;

mod plugins;
use plugins::{
syntax_highlighting::{
fetch_syntax_highlighter_path, fetch_syntax_highlighter_theme,
SYNTAX_HIGHLIGHTER_PLUGIN_DEFAULT_THEME,
},
syntax_highlighting::{fetch_syntax_highlighter_path, fetch_syntax_highlighter_theme},
SYNTAX_HIGHLIGHTER_PLUGIN,
};

Expand Down Expand Up @@ -57,78 +54,83 @@ fn commonmark_to_html(args: &[Value]) -> Result<String, magnus::Error> {

let theme = match rb_plugins.get(Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN)) {
Some(syntax_highlighter_options) => {
fetch_syntax_highlighter_theme(syntax_highlighter_options)?
}
None => SYNTAX_HIGHLIGHTER_PLUGIN_DEFAULT_THEME.to_string(), // no `syntax_highlighter:` defined
};

let path = match rb_plugins.get(Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN)) {
Some(syntax_highlighter_options) => {
fetch_syntax_highlighter_path(syntax_highlighter_options)?
match fetch_syntax_highlighter_theme(syntax_highlighter_options) {
Ok(theme) => theme,
Err(e) => {
return Err(e);
}
}
}
None => PathBuf::from("".to_string()), // no `syntax_highlighter:` defined
None => None, // no `syntax_highlighter:` defined
};

if !path.eq(&PathBuf::from("".to_string())) && !path.exists() {
return Err(Error::new(
exception::arg_error(),
"path does not exist".to_string(),
));
}

if theme.is_empty() && path.exists() {
return Err(Error::new(
exception::arg_error(),
"`path` also needs `theme` passed into the `syntax_highlighter`",
));
}
if path.exists() && !path.is_dir() {
return Err(Error::new(
exception::arg_error(),
"`path` needs to be a directory",
));
}

if path.exists() {
let builder = SyntectAdapterBuilder::new();
let mut ts = ThemeSet::load_defaults();

match ts.add_from_folder(&path) {
Ok(_) => {}
Err(e) => {
return Err(Error::new(
exception::arg_error(),
format!("failed to load theme set from path: {e}"),
));
match theme {
None => syntax_highlighter = None,
Some(theme) => {
if theme.is_empty() {
// no theme? uss css classes
adapter = SyntectAdapter::new(None);
syntax_highlighter = Some(&adapter);
} else {
let path = match rb_plugins.get(Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN)) {
Some(syntax_highlighter_options) => {
fetch_syntax_highlighter_path(syntax_highlighter_options)?
}
None => PathBuf::from("".to_string()), // no `syntax_highlighter:` defined
};

if path.exists() {
if !path.is_dir() {
return Err(Error::new(
exception::arg_error(),
"`path` needs to be a directory",
));
}

let builder = SyntectAdapterBuilder::new();
let mut ts = ThemeSet::load_defaults();

match ts.add_from_folder(&path) {
Ok(_) => {}
Err(e) => {
return Err(Error::new(
exception::arg_error(),
format!("failed to load theme set from path: {e}"),
));
}
}

// check if the theme exists in the dir
match ts.themes.get(&theme) {
Some(theme) => theme,
None => {
return Err(Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
));
}
};

adapter = builder.theme_set(ts).theme(&theme).build();

syntax_highlighter = Some(&adapter);
} else {
// no path? default theme lookup
ThemeSet::load_defaults()
.themes
.get(&theme)
.ok_or_else(|| {
Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
)
})?;
adapter = SyntectAdapter::new(Some(&theme));
syntax_highlighter = Some(&adapter);
}
}
}

ts.themes.get(&theme).ok_or_else(|| {
Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
)
})?;

adapter = builder.theme_set(ts).theme(&theme).build();

syntax_highlighter = Some(&adapter);
} else if theme.is_empty() || theme == "none" {
syntax_highlighter = None;
} else {
ThemeSet::load_defaults()
.themes
.get(&theme)
.ok_or_else(|| {
Error::new(
exception::arg_error(),
format!("theme `{}` does not exist", theme),
)
})?;
adapter = SyntectAdapter::new(Some(&theme));
syntax_highlighter = Some(&adapter);
}

comrak_plugins.render.codefence_syntax_highlighter = syntax_highlighter;

Ok(markdown_to_html_with_plugins(
Expand Down
33 changes: 24 additions & 9 deletions ext/commonmarker/src/plugins/syntax_highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,43 @@ use crate::EMPTY_STR;

pub const SYNTAX_HIGHLIGHTER_PLUGIN_THEME_KEY: &str = "theme";
pub const SYNTAX_HIGHLIGHTER_PLUGIN_PATH_KEY: &str = "path";
pub const SYNTAX_HIGHLIGHTER_PLUGIN_DEFAULT_THEME: &str = "base16-ocean.dark";

pub fn fetch_syntax_highlighter_theme(value: Value) -> Result<String, magnus::Error> {
pub fn fetch_syntax_highlighter_theme(value: Value) -> Result<Option<String>, magnus::Error> {
if value.is_nil() {
// `syntax_highlighter: nil`
return Ok(EMPTY_STR.to_string());
return Ok(None);
}

let syntax_highlighter_plugin: RHash = match TryConvert::try_convert(value) {
Ok(plugin) => plugin, // `syntax_highlighter: { theme: "<something>" }`
Err(e) => {
// not a hash!
return Err(e);
}
};

if syntax_highlighter_plugin.is_nil() || syntax_highlighter_plugin.is_empty() {
return Err(magnus::Error::new(
magnus::exception::type_error(),
"theme cannot be blank hash",
));
}

let syntax_highlighter_plugin: RHash = TryConvert::try_convert(value)?;
let theme_key = Symbol::new(SYNTAX_HIGHLIGHTER_PLUGIN_THEME_KEY);

match syntax_highlighter_plugin.get(theme_key) {
Some(theme) => {
if theme.is_nil() {
// `syntax_highlighter: { theme: nil }`
return Ok(EMPTY_STR.to_string());
return Err(magnus::Error::new(
magnus::exception::type_error(),
"theme cannot be nil",
));
}
Ok(theme.try_convert::<String>()?)
Ok(TryConvert::try_convert(theme)?)
}
None => {
// `syntax_highlighter: { }`
Ok(EMPTY_STR.to_string())
// `syntax_highlighter: { theme: nil }`
Ok(None)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/commonmarker/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Commonmarker
VERSION = "1.0.0.pre11"
VERSION = "1.0.0.pre12"
end
Loading

0 comments on commit 8b5f285

Please sign in to comment.