Skip to content

Commit 7a964ff

Browse files
authored
Don't rely on relative path for docs preprocessor (zed-industries#16883)
Reapplies zed-industries#16700 with a corrected command. Now it no longer relies on a relative path. Thanks @maxdeviant for the quick help 🙏 Release Notes: - N/A
1 parent a87076e commit 7a964ff

File tree

16 files changed

+639
-24
lines changed

16 files changed

+639
-24
lines changed

Cargo.lock

Lines changed: 256 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ members = [
2424
"crates/db",
2525
"crates/dev_server_projects",
2626
"crates/diagnostics",
27+
"crates/docs_preprocessor",
2728
"crates/editor",
2829
"crates/extension",
2930
"crates/extension_api",
@@ -165,7 +166,7 @@ members = [
165166
# Tooling
166167
#
167168

168-
"tooling/xtask",
169+
"tooling/xtask"
169170
]
170171
default-members = ["crates/zed"]
171172

crates/docs_preprocessor/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "docs_preprocessor"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
license = "GPL-3.0-or-later"
7+
8+
[dependencies]
9+
anyhow.workspace = true
10+
clap.workspace = true
11+
mdbook = "0.4.40"
12+
serde.workspace = true
13+
serde_json.workspace = true
14+
settings.workspace = true
15+
regex.workspace = true
16+
util.workspace = true
17+
18+
[lints]
19+
workspace = true
20+
21+
[lib]
22+
path = "src/docs_preprocessor.rs"
23+
24+
[[bin]]
25+
name = "docs_preprocessor"
26+
path = "src/main.rs"

crates/docs_preprocessor/LICENSE-GPL

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../LICENSE-GPL
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use anyhow::Result;
2+
use mdbook::book::{Book, BookItem};
3+
use mdbook::errors::Error;
4+
use mdbook::preprocess::{Preprocessor, PreprocessorContext as MdBookContext};
5+
use settings::KeymapFile;
6+
use std::sync::Arc;
7+
use util::asset_str;
8+
9+
mod templates;
10+
11+
use templates::{ActionTemplate, KeybindingTemplate, Template};
12+
13+
pub struct PreprocessorContext {
14+
macos_keymap: Arc<KeymapFile>,
15+
linux_keymap: Arc<KeymapFile>,
16+
}
17+
18+
impl PreprocessorContext {
19+
pub fn new() -> Result<Self> {
20+
let macos_keymap = Arc::new(load_keymap("keymaps/default-macos.json")?);
21+
let linux_keymap = Arc::new(load_keymap("keymaps/default-linux.json")?);
22+
Ok(Self {
23+
macos_keymap,
24+
linux_keymap,
25+
})
26+
}
27+
28+
pub fn find_binding(&self, os: &str, action: &str) -> Option<String> {
29+
let keymap = match os {
30+
"macos" => &self.macos_keymap,
31+
"linux" => &self.linux_keymap,
32+
_ => return None,
33+
};
34+
35+
keymap.blocks().iter().find_map(|block| {
36+
block.bindings().iter().find_map(|(keystroke, a)| {
37+
if a.to_string() == action {
38+
Some(keystroke.to_string())
39+
} else {
40+
None
41+
}
42+
})
43+
})
44+
}
45+
}
46+
47+
fn load_keymap(asset_path: &str) -> Result<KeymapFile> {
48+
let content = asset_str::<settings::SettingsAssets>(asset_path);
49+
KeymapFile::parse(content.as_ref())
50+
}
51+
52+
pub struct ZedDocsPreprocessor {
53+
context: PreprocessorContext,
54+
templates: Vec<Box<dyn Template>>,
55+
}
56+
57+
impl ZedDocsPreprocessor {
58+
pub fn new() -> Result<Self> {
59+
let context = PreprocessorContext::new()?;
60+
let templates: Vec<Box<dyn Template>> = vec![
61+
Box::new(KeybindingTemplate::new()),
62+
Box::new(ActionTemplate::new()),
63+
];
64+
Ok(Self { context, templates })
65+
}
66+
67+
fn process_content(&self, content: &str) -> String {
68+
let mut processed = content.to_string();
69+
for template in &self.templates {
70+
processed = template.process(&self.context, &processed);
71+
}
72+
processed
73+
}
74+
}
75+
76+
impl Preprocessor for ZedDocsPreprocessor {
77+
fn name(&self) -> &str {
78+
"zed-docs-preprocessor"
79+
}
80+
81+
fn run(&self, _ctx: &MdBookContext, mut book: Book) -> Result<Book, Error> {
82+
book.for_each_mut(|item| {
83+
if let BookItem::Chapter(chapter) = item {
84+
chapter.content = self.process_content(&chapter.content);
85+
}
86+
});
87+
Ok(book)
88+
}
89+
90+
fn supports_renderer(&self, renderer: &str) -> bool {
91+
renderer != "not-supported"
92+
}
93+
}

crates/docs_preprocessor/src/main.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use anyhow::{Context, Result};
2+
use clap::{Arg, ArgMatches, Command};
3+
use docs_preprocessor::ZedDocsPreprocessor;
4+
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
5+
use std::io::{self, Read};
6+
use std::process;
7+
8+
pub fn make_app() -> Command {
9+
Command::new("zed-docs-preprocessor")
10+
.about("Preprocesses Zed Docs content to provide rich action & keybinding support and more")
11+
.subcommand(
12+
Command::new("supports")
13+
.arg(Arg::new("renderer").required(true))
14+
.about("Check whether a renderer is supported by this preprocessor"),
15+
)
16+
}
17+
18+
fn main() -> Result<()> {
19+
let matches = make_app().get_matches();
20+
21+
let preprocessor =
22+
ZedDocsPreprocessor::new().context("Failed to create ZedDocsPreprocessor")?;
23+
24+
if let Some(sub_args) = matches.subcommand_matches("supports") {
25+
handle_supports(&preprocessor, sub_args);
26+
} else {
27+
handle_preprocessing(&preprocessor)?;
28+
}
29+
30+
Ok(())
31+
}
32+
33+
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<()> {
34+
let mut stdin = io::stdin();
35+
let mut input = String::new();
36+
stdin.read_to_string(&mut input)?;
37+
38+
let (ctx, book) = CmdPreprocessor::parse_input(input.as_bytes())?;
39+
40+
let processed_book = pre.run(&ctx, book)?;
41+
42+
serde_json::to_writer(io::stdout(), &processed_book)?;
43+
44+
Ok(())
45+
}
46+
47+
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> ! {
48+
let renderer = sub_args
49+
.get_one::<String>("renderer")
50+
.expect("Required argument");
51+
let supported = pre.supports_renderer(renderer);
52+
53+
if supported {
54+
process::exit(0);
55+
} else {
56+
process::exit(1);
57+
}
58+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use crate::PreprocessorContext;
2+
use regex::Regex;
3+
use std::collections::HashMap;
4+
5+
mod action;
6+
mod keybinding;
7+
8+
pub use action::*;
9+
pub use keybinding::*;
10+
11+
pub trait Template {
12+
fn key(&self) -> &'static str;
13+
fn regex(&self) -> Regex;
14+
fn parse_args(&self, args: &str) -> HashMap<String, String>;
15+
fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> String;
16+
17+
fn process(&self, context: &PreprocessorContext, content: &str) -> String {
18+
self.regex()
19+
.replace_all(content, |caps: &regex::Captures| {
20+
let args = self.parse_args(&caps[1]);
21+
self.render(context, &args)
22+
})
23+
.into_owned()
24+
}
25+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use crate::PreprocessorContext;
2+
use regex::Regex;
3+
use std::collections::HashMap;
4+
5+
use super::Template;
6+
7+
pub struct ActionTemplate;
8+
9+
impl ActionTemplate {
10+
pub fn new() -> Self {
11+
ActionTemplate
12+
}
13+
}
14+
15+
impl Template for ActionTemplate {
16+
fn key(&self) -> &'static str {
17+
"action"
18+
}
19+
20+
fn regex(&self) -> Regex {
21+
Regex::new(&format!(r"\{{#{}(.*?)\}}", self.key())).unwrap()
22+
}
23+
24+
fn parse_args(&self, args: &str) -> HashMap<String, String> {
25+
let mut map = HashMap::new();
26+
map.insert("name".to_string(), args.trim().to_string());
27+
map
28+
}
29+
30+
fn render(&self, _context: &PreprocessorContext, args: &HashMap<String, String>) -> String {
31+
let name = args.get("name").map(String::as_str).unwrap_or_default();
32+
33+
let formatted_name = name
34+
.chars()
35+
.enumerate()
36+
.map(|(i, c)| {
37+
if i > 0 && c.is_uppercase() {
38+
format!(" {}", c.to_lowercase())
39+
} else {
40+
c.to_string()
41+
}
42+
})
43+
.collect::<String>()
44+
.trim()
45+
.to_string()
46+
.replace("::", ":");
47+
48+
format!("<code class=\"hljs\">{}</code>", formatted_name)
49+
}
50+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::PreprocessorContext;
2+
use regex::Regex;
3+
use std::collections::HashMap;
4+
5+
use super::Template;
6+
7+
pub struct KeybindingTemplate;
8+
9+
impl KeybindingTemplate {
10+
pub fn new() -> Self {
11+
KeybindingTemplate
12+
}
13+
}
14+
15+
impl Template for KeybindingTemplate {
16+
fn key(&self) -> &'static str {
17+
"kb"
18+
}
19+
20+
fn regex(&self) -> Regex {
21+
Regex::new(&format!(r"\{{#{}(.*?)\}}", self.key())).unwrap()
22+
}
23+
24+
fn parse_args(&self, args: &str) -> HashMap<String, String> {
25+
let mut map = HashMap::new();
26+
map.insert("action".to_string(), args.trim().to_string());
27+
map
28+
}
29+
30+
fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> String {
31+
let action = args.get("action").map(String::as_str).unwrap_or("");
32+
let macos_binding = context.find_binding("macos", action).unwrap_or_default();
33+
let linux_binding = context.find_binding("linux", action).unwrap_or_default();
34+
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
35+
}
36+
}

crates/settings/src/keymap_file.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,34 @@ pub struct KeymapBlock {
2222
bindings: BTreeMap<String, KeymapAction>,
2323
}
2424

25+
impl KeymapBlock {
26+
pub fn context(&self) -> Option<&str> {
27+
self.context.as_deref()
28+
}
29+
30+
pub fn bindings(&self) -> &BTreeMap<String, KeymapAction> {
31+
&self.bindings
32+
}
33+
}
34+
2535
#[derive(Debug, Deserialize, Default, Clone)]
2636
#[serde(transparent)]
2737
pub struct KeymapAction(Value);
2838

39+
impl ToString for KeymapAction {
40+
fn to_string(&self) -> String {
41+
match &self.0 {
42+
Value::String(s) => s.clone(),
43+
Value::Array(arr) => arr
44+
.iter()
45+
.map(|v| v.to_string())
46+
.collect::<Vec<_>>()
47+
.join(", "),
48+
_ => self.0.to_string(),
49+
}
50+
}
51+
}
52+
2953
impl JsonSchema for KeymapAction {
3054
fn schema_name() -> String {
3155
"KeymapAction".into()
@@ -135,6 +159,10 @@ impl KeymapFile {
135159

136160
serde_json::to_value(root_schema).unwrap()
137161
}
162+
163+
pub fn blocks(&self) -> &[KeymapBlock] {
164+
&self.0
165+
}
138166
}
139167

140168
fn no_action() -> Box<dyn gpui::Action> {

0 commit comments

Comments
 (0)