toolkit for mdBook [mirror of my GitHub repo] docs.tonywu.dev/mdbookkit/
permalinks rust-analyzer mdbook
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: somewhat improve error reporting

+359 -267
+2
.github/workflows/ci.yml
··· 1 + # TODO: still need cross platform E2E testing 2 + 1 3 name: CI 2 4 3 5 on:
+8 -28
Cargo.lock
··· 636 636 ] 637 637 638 638 [[package]] 639 - name = "faster-hex" 640 - version = "0.9.0" 641 - source = "registry+https://github.com/rust-lang/crates.io-index" 642 - checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" 643 - dependencies = [ 644 - "serde", 645 - ] 646 - 647 - [[package]] 648 639 name = "fastrand" 649 640 version = "2.3.0" 650 641 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 865 856 866 857 [[package]] 867 858 name = "gix-features" 868 - version = "0.40.0" 859 + version = "0.41.0" 869 860 source = "registry+https://github.com/rust-lang/crates.io-index" 870 - checksum = "8bfdd4838a8d42bd482c9f0cb526411d003ee94cc7c7b08afe5007329c71d554" 861 + checksum = "729b7e708352a35b2b37ab39cbc7a2b9d22f8386808a10b6ea7dd4cd1cf817cd" 871 862 dependencies = [ 872 - "gix-hash", 873 863 "gix-trace", 874 864 "libc", 875 865 ] 876 866 877 867 [[package]] 878 - name = "gix-hash" 879 - version = "0.16.0" 880 - source = "registry+https://github.com/rust-lang/crates.io-index" 881 - checksum = "e81c5ec48649b1821b3ed066a44efb95f1a268b35c1d91295e61252539fbe9f8" 882 - dependencies = [ 883 - "faster-hex", 884 - "thiserror 2.0.12", 885 - ] 886 - 887 - [[package]] 888 868 name = "gix-path" 889 - version = "0.10.14" 869 + version = "0.10.15" 890 870 source = "registry+https://github.com/rust-lang/crates.io-index" 891 - checksum = "c40f12bb65a8299be0cfb90fe718e3be236b7a94b434877012980863a883a99f" 871 + checksum = "f910668e2f6b2a55ff35a1f04df88a1a049f7b868507f4cbeeaa220eaba7be87" 892 872 dependencies = [ 893 873 "bstr", 894 874 "gix-trace", ··· 905 885 906 886 [[package]] 907 887 name = "gix-url" 908 - version = "0.29.0" 888 + version = "0.30.0" 909 889 source = "registry+https://github.com/rust-lang/crates.io-index" 910 - checksum = "29218c768b53dd8f116045d87fec05b294c731a4b2bdd257eeca2084cc150b13" 890 + checksum = "48dfe23f93f1ddb84977d80bb0dd7aa09d1bf5d5afc0c9b6820cccacc25ae860" 911 891 dependencies = [ 912 892 "bstr", 913 893 "gix-features", ··· 1515 1495 1516 1496 [[package]] 1517 1497 name = "mdbook" 1518 - version = "0.4.47" 1498 + version = "0.4.48" 1519 1499 source = "registry+https://github.com/rust-lang/crates.io-index" 1520 - checksum = "7e1a8fe3a4a01f28dab245c474cb7b95ccb4d3d2f17a5419a3d949f474c45e84" 1500 + checksum = "8b6fbb4ac2d9fd7aa987c3510309ea3c80004a968d063c42f0d34fea070817c1" 1521 1501 dependencies = [ 1522 1502 "anyhow", 1523 1503 "chrono",
+2 -2
Cargo.toml
··· 24 24 env_logger = "0.11.6" 25 25 insta = { version = "1.40.0", features = ["yaml"] } 26 26 log = "0.4.26" 27 - mdbook = { version = "0.4.47", default-features = false } 27 + mdbook = { version = "0.4.48", default-features = false } 28 28 miette = { version = "7.5.0", features = [ 29 29 "fancy-no-backtrace", 30 30 ], default-features = false } ··· 42 42 util-testing = { path = "utils/testing" } 43 43 44 44 [workspace.metadata.bin] 45 - mdbook = { version = "0.4.47" } 45 + mdbook = { version = "0.4.48" } 46 46 mdbook-alerts = { version = "0.7.0" }
+1 -1
crates/mdbookkit/Cargo.toml
··· 31 31 dirs = { version = "6.0.0", optional = true } 32 32 env_logger = { workspace = true, optional = true } 33 33 git2 = { version = "0.20.1", default-features = false, optional = true } 34 - gix-url = { version = "0.29.0", optional = true } 34 + gix-url = { version = "0.30.0", optional = true } 35 35 indicatif = { version = "0.17.11", optional = true } 36 36 log = { workspace = true } 37 37 lsp-types = { version = "0.95.0", optional = true }
+2 -2
crates/mdbookkit/src/bin/rustdoc_link/cache.rs
··· 14 14 15 15 use crate::log_debug; 16 16 17 - use super::{env::Environment, link::ItemLinks, page::Pages, Resolver}; 17 + use super::{env::Environment, link::ItemLinks, page::Pages, url::UrlToPath, Resolver}; 18 18 19 19 #[allow(async_fn_in_trait)] 20 20 pub trait Cache: DeserializeOwned + Serialize { ··· 156 156 } 157 157 158 158 async fn read_dep(url: Url) -> Result<(String, String)> { 159 - let content = tokio::fs::read_to_string(&url.path()).await?; 159 + let content = tokio::fs::read_to_string(&url.to_path()?).await?; 160 160 Ok((url.to_string(), content)) 161 161 } 162 162
+16 -8
crates/mdbookkit/src/bin/rustdoc_link/client.rs
··· 38 38 env::Environment, 39 39 link::ItemLinks, 40 40 sync::{EventSampler, EventSampling}, 41 + url::UrlToPath, 41 42 }; 42 43 43 44 /// LSP client to talk to rust-analyzer. ··· 77 78 Ok(opened) 78 79 } 79 80 80 - pub async fn stop(self) -> Result<Environment> { 81 + pub async fn stop(self) -> Environment { 81 82 if let Some(server) = self.server.into_inner() { 82 - server.dispose().await?; 83 + server 84 + .dispose() 85 + .await 86 + .context("failed to properly stop rust-analyzer") 87 + .tap_err(log_warning!()) 88 + .ok(); 83 89 } 84 - Ok(self.env) 90 + self.env 85 91 } 86 92 87 93 pub async fn drop(self: Arc<Self>) -> Result<Environment> { 88 94 let Some(this) = Arc::into_inner(self) else { 89 95 bail!("attempted to shutdown a client that is still referenced") 90 96 }; 91 - this.stop().await 97 + Ok(this.stop().await) 92 98 } 93 99 } 94 100 ··· 120 126 percent_indexed: Option<u32>, 121 127 } 122 128 123 - fn on_progress(state: &mut State, progress: ProgressParams) { 129 + fn probe_progress(state: &mut State, progress: ProgressParams) { 124 130 match indexing_progress(&progress) { 125 131 Some(WorkDoneProgress::Begin(begin)) => { 126 132 state.percent_indexed = Some(0); ··· 147 153 let spurious = if let Some(msg) = &report.message { 148 154 // HACK: invalidate progress reports that say "0/1 (crate name)" 149 155 // because RA isn't actually indexing everything at this point 150 - msg.contains("0/1") 156 + msg.starts_with("0/1 (") 151 157 } else { 152 158 // also ignore indexing runs that went from 0 to a 100 153 159 *indexed == 0 && update == 100 154 160 }; 155 161 156 162 if spurious { 163 + log::debug!("ignoring spurious rust-analyzer progress"); 157 164 state.percent_indexed = None; 158 165 return; 159 166 } ··· 196 203 197 204 router 198 205 .notification::<Progress>(|state, progress| { 199 - on_progress(state, progress); 206 + log::trace!("{progress:#?}"); 207 + probe_progress(state, progress); 200 208 ControlFlow::Continue(()) 201 209 }) 202 210 .notification::<PublishDiagnostics>(|_, diagnostics| { ··· 221 229 222 230 let proc = env 223 231 .command()? 224 - .current_dir(env.crate_dir.path()) 232 + .current_dir(env.crate_dir.to_path()?) 225 233 .stdin(Stdio::piped()) 226 234 .stdout(Stdio::piped()) 227 235 .stderr(Stdio::inherit())
+30 -29
crates/mdbookkit/src/bin/rustdoc_link/env.rs
··· 4 4 sync::Arc, 5 5 }; 6 6 7 - use anyhow::{anyhow, Context, Result}; 7 + use anyhow::{anyhow, bail, Context, Result}; 8 8 use cargo_toml::{Manifest, Product}; 9 9 #[cfg(feature = "common-cli")] 10 10 use clap::ValueHint; ··· 140 140 .manifest_dir 141 141 .clone() 142 142 .map(Ok) 143 - .unwrap_or_else(std::env::current_dir)? 144 - .canonicalize()?; 143 + .unwrap_or_else(std::env::current_dir) 144 + .context("failed to get the current working directory")? 145 + .canonicalize() 146 + .context("failed to resolve `manifest-dir` to a path")?; 145 147 146 148 let (crate_dir, entrypoint) = { 147 - let manifest_path = LocateProject::package(&cwd)?.root; 149 + let manifest_path = LocateProject::package(&cwd) 150 + .context("preprocessor requires a Cargo project to run rust-analyzer") 151 + .context("failed to determine the current Cargo project")? 152 + .root; 148 153 149 - let manifest = { 150 - let mut manifest = Manifest::from_path(&manifest_path)?; 151 - manifest.complete_from_path(&manifest_path)?; 152 - manifest 153 - }; 154 + let manifest = Manifest::from_path(&manifest_path) 155 + .and_then(|mut m| m.complete_from_path(&manifest_path).and(Ok(m))) 156 + .context("failed to read Cargo.toml")?; 154 157 155 158 let crate_dir = manifest_path 156 159 .parent() ··· 169 172 let entry = crate_dir.join(bin)?; 170 173 Ok((crate_dir, entry)) 171 174 } else { 172 - Err(anyhow!( 175 + let err = Err(anyhow!( 173 176 "help: resolved Cargo.toml is {}", 174 177 manifest_path.display() 175 - )) 176 - .pipe(|r| { 177 - if manifest.workspace.is_some() { 178 - r.context("help: to use in a workspace, set `manifest-dir` option to root of a member crate") 179 - } else { 180 - r 181 - } 182 - }) 178 + )); 179 + if manifest.workspace.is_some() { 180 + err.context("help: to use in a workspace, set `manifest-dir` option to root of a member crate") 181 + } else { 182 + err 183 + } 183 184 .context("Cargo.toml does not have any lib or bin target") 184 185 } 185 186 }?; 186 187 187 - let source_dir = LocateProject::workspace(cwd)? 188 + let source_dir = LocateProject::workspace(cwd) 189 + .context("failed to locate the current Cargo project")? 188 190 .root 189 191 .parent() 190 192 .unwrap() ··· 294 296 295 297 impl LocateProject { 296 298 fn package<P: AsRef<Path>>(cwd: P) -> Result<Self> { 297 - std::process::Command::new(env!("CARGO")) 299 + std::process::Command::new("cargo") 298 300 .arg("locate-project") 299 301 .arg("--message-format=json") 300 302 .current_dir(cwd) 301 - .output()? 302 - .pipe(Self::parse) 303 + .output() 304 + .context("failed to run `cargo locate-project`, is cargo installed?") 305 + .and_then(Self::parse) 303 306 } 304 307 305 308 fn workspace<P: AsRef<Path>>(cwd: P) -> Result<Self> { 306 - std::process::Command::new(env!("CARGO")) 309 + std::process::Command::new("cargo") 307 310 .arg("locate-project") 308 311 .arg("--message-format=json") 309 312 .arg("--workspace") 310 313 .current_dir(cwd) 311 - .output()? 312 - .pipe(Self::parse) 314 + .output() 315 + .context("failed to run `cargo locate-project`, is cargo installed?") 316 + .and_then(Self::parse) 313 317 } 314 318 315 319 fn parse(output: std::process::Output) -> Result<Self> { ··· 318 322 .pipe(|outout| serde_json::from_str::<Self>(&outout))? 319 323 .pipe(Ok) 320 324 } else { 321 - anyhow!(String::from_utf8_lossy(&output.stderr).into_owned()) 322 - .context("help: a Cargo project is needed to run rust-analyzer in") 323 - .context("failed to locate a Cargo project") 324 - .pipe(Err) 325 + bail!(String::from_utf8_lossy(&output.stderr).into_owned()); 325 326 } 326 327 } 327 328 }
+56 -43
crates/mdbookkit/src/bin/rustdoc_link/main.rs
··· 1 - use std::{ 2 - collections::HashMap, 3 - io::{Read, Write}, 4 - }; 1 + use std::{collections::HashMap, io::Write}; 5 2 6 - use anyhow::Result; 3 + use anyhow::{Context, Result}; 7 4 use clap::{Parser, Subcommand}; 8 5 use console::colors_enabled_stderr; 9 6 use log::LevelFilter; 7 + use mdbook::preprocess::PreprocessorContext; 10 8 use tap::{Pipe, TapFallible}; 11 9 12 10 use mdbookkit::{ ··· 17 15 }, 18 16 diagnostics::Issue, 19 17 env::{ 20 - book_from_stdin, config_from_book, for_each_chapter_mut, iter_chapters, smart_punctuation, 18 + book_from_stdin, book_into_stdout, config_from_book, for_each_chapter_mut, iter_chapters, 19 + smart_punctuation, string_from_stdin, 21 20 }, 22 21 log_warning, 23 22 logging::{is_logging, ConsoleLogger}, ··· 54 53 } 55 54 56 55 async fn mdbook() -> Result<()> { 57 - let (context, mut book) = book_from_stdin()?; 58 - 59 - let config = { 60 - let mut config = config_from_book::<Config>(&context.config, "rustdoc-link")?; 61 - 62 - if let Some(path) = config.manifest_dir { 63 - config.manifest_dir = Some(context.root.join(path)) 64 - } else { 65 - config.manifest_dir = Some(context.root.clone()) 66 - } 67 - 68 - if let Some(path) = config.cache_dir { 69 - config.cache_dir = Some(context.root.join(path)) 70 - } 56 + let (context, mut book) = book_from_stdin().context("failed to parse book content")?; 71 57 72 - config.smart_punctuation = smart_punctuation(&context.config); 58 + let config = config(&context).context("failed to read preprocessor config from book.toml")?; 73 59 74 - config 75 - }; 76 - 77 - let client = Client::new(Environment::new(config)?); 60 + let client = Environment::new(config) 61 + .context("failed to initialize `mdbook-rustdoc-link`")? 62 + .pipe(Client::new); 78 63 79 64 let cached = FileCache::load(client.env()).await.ok(); 80 65 ··· 82 67 83 68 for (path, ch) in iter_chapters(&book) { 84 69 let stream = client.env().markdown(&ch.content).into_offset_iter(); 85 - content.read(path.clone(), &ch.content, stream)?; 70 + content 71 + .read(path.clone(), &ch.content, stream) 72 + .with_context(|| path.display().to_string()) 73 + .context("failed to parse Markdown source:")?; 86 74 } 87 75 88 76 if let Some(cached) = cached { 89 77 cached.resolve(&mut content).await.ok(); 90 78 } 91 79 92 - client.resolve(&mut content).await?; 80 + client 81 + .resolve(&mut content) 82 + .await 83 + .context("failed to resolve some links")?; 93 84 94 85 let mut result = iter_chapters(&book) 95 86 .filter_map(|(path, _)| { 96 - content 87 + let output = content 97 88 .emit(path, &client.env().emit_config()) 98 89 .tap_err(log_warning!()) 99 - .ok() 100 - .map(|output| (path.clone(), output.to_string())) 90 + .ok()?; 91 + Some((path.clone(), output.to_string())) 101 92 }) 102 93 .collect::<HashMap<_, _>>(); 103 94 104 - let env = client.stop().await?; 95 + let env = client.stop().await; 105 96 106 97 let status = content 107 98 .reporter() ··· 123 114 } 124 115 }); 125 116 126 - let output = serde_json::to_string(&book)?; 127 - std::io::stdout().write_all(output.as_bytes())?; 117 + book_into_stdout(&book)?; 128 118 129 119 env.config.fail_on_warnings.check(status.level())?; 130 120 131 121 Ok(()) 132 122 } 133 123 134 - async fn markdown(options: Config) -> Result<()> { 135 - let client = Client::new(Environment::new(options)?); 124 + async fn markdown(config: Config) -> Result<()> { 125 + let client = Environment::new(config) 126 + .context("failed to initialize")? 127 + .pipe(Client::new); 136 128 137 - let source = Vec::new() 138 - .pipe(|mut buf| std::io::stdin().read_to_end(&mut buf).and(Ok(buf)))? 139 - .pipe(String::from_utf8)?; 129 + let source = string_from_stdin().context("failed to read Markdown source from stdin")?; 140 130 141 131 let stream = client.env().markdown(&source).into_offset_iter(); 142 132 143 - let mut content = Pages::one(&source, stream)?; 133 + let mut content = Pages::one(&source, stream).context("failed to parse Markdown source")?; 144 134 145 135 if let Ok(cached) = FileCache::load(client.env()).await { 146 136 cached.resolve(&mut content).await.ok(); 147 137 } 148 138 149 - client.resolve(&mut content).await?; 139 + client 140 + .resolve(&mut content) 141 + .await 142 + .context("failed to resolve some links")?; 150 143 151 - let env = client.stop().await?; 144 + let env = client.stop().await; 152 145 153 146 let status = content 154 147 .reporter() ··· 164 157 FileCache::save(&env, &content).await.ok(); 165 158 } 166 159 167 - let output = content.get(&env.emit_config())?.to_string(); 168 - std::io::stdout().write_all(output.as_bytes())?; 160 + content 161 + .get(&env.emit_config()) 162 + .map(|emit| emit.to_string()) 163 + .and_then(|output| Ok(std::io::stdout().write_all(output.as_bytes())?))?; 169 164 170 165 env.config.fail_on_warnings.check(status.level())?; 171 166 172 167 Ok(()) 173 168 } 169 + 170 + fn config(context: &PreprocessorContext) -> Result<Config> { 171 + let mut config = config_from_book::<Config>(&context.config, "rustdoc-link")?; 172 + 173 + if let Some(path) = config.manifest_dir { 174 + config.manifest_dir = Some(context.root.join(path)) 175 + } else { 176 + config.manifest_dir = Some(context.root.clone()) 177 + } 178 + 179 + if let Some(path) = config.cache_dir { 180 + config.cache_dir = Some(context.root.join(path)) 181 + } 182 + 183 + config.smart_punctuation = smart_punctuation(&context.config); 184 + 185 + Ok(config) 186 + }
+5 -3
crates/mdbookkit/src/bin/rustdoc_link/mod.rs
··· 12 12 mod markdown; 13 13 mod page; 14 14 mod sync; 15 + mod url; 15 16 16 17 #[cfg(feature = "rustdoc-link")] 17 18 pub mod cache; ··· 19 20 use crate::{log_debug, logging::spinner, styled}; 20 21 21 22 pub use self::{client::Client, page::Pages}; 22 - use self::{item::Item, link::ItemLinks}; 23 + use self::{item::Item, link::ItemLinks, url::UrlToPath}; 23 24 24 25 /// Type that can provide links. 25 26 /// ··· 49 50 return Ok(()); 50 51 } 51 52 52 - let main = std::fs::read_to_string(self.env().entrypoint.path())?; 53 + let main = std::fs::read_to_string(self.env().entrypoint.to_path()?)?; 53 54 54 55 let (context, request) = { 55 56 let mut context = format!("{main}\nfn {UNIQUE_ID} () {{\n"); ··· 109 110 let resolved = doc 110 111 .resolve(p) 111 112 .await 112 - .with_context(|| format!("error resolving {p:?}")) 113 + .with_context(|| format!("{p:?}")) 114 + .context("failed to resolve symbol:") 113 115 .tap_err(log_debug!()) 114 116 .ok(); 115 117 if let Some(resolved) = resolved {
+18
crates/mdbookkit/src/bin/rustdoc_link/url.rs
··· 1 + use std::path::PathBuf; 2 + 3 + use anyhow::{bail, Result}; 4 + use lsp_types::Url; 5 + 6 + /// [`Url::to_file_path()`] with an actual [`std::error::Error`]. 7 + pub trait UrlToPath { 8 + fn to_path(&self) -> Result<PathBuf>; 9 + } 10 + 11 + impl UrlToPath for Url { 12 + fn to_path(&self) -> Result<PathBuf> { 13 + match self.to_file_path() { 14 + Ok(path) => Ok(path), 15 + Err(()) => bail!("failed to convert {self} to a file path"), 16 + } 17 + } 18 + }
+20 -3
crates/mdbookkit/src/env.rs
··· 1 + use std::io::Read; 2 + 1 3 use anyhow::{anyhow, Result}; 2 4 use serde::Deserialize; 3 5 use tap::Pipe; ··· 47 49 } 48 50 } 49 51 52 + pub fn string_from_stdin() -> Result<String> { 53 + Vec::new() 54 + .pipe(|mut buf| std::io::stdin().read_to_end(&mut buf).and(Ok(buf)))? 55 + .pipe(|buf| Ok(String::from_utf8(buf)?)) 56 + } 57 + 50 58 #[cfg(feature = "common-cli")] 51 59 pub use book::*; 52 60 #[cfg(feature = "common-cli")] 53 61 mod book { 54 - use std::{io::Read, path::PathBuf}; 62 + use std::{ 63 + io::{Read, Write}, 64 + path::PathBuf, 65 + }; 55 66 56 67 use anyhow::{Context, Result}; 57 68 use mdbook::{ ··· 74 85 T: DeserializeOwned + Default, 75 86 { 76 87 if let Some(config) = config.get_preprocessor(name) { 77 - T::deserialize(toml::Value::Table(config.clone())) 78 - .context("failed to read preprocessor config from book.toml")? 88 + T::deserialize(toml::Value::Table(config.clone()))? 79 89 } else { 80 90 Default::default() 81 91 } ··· 110 120 let Some(path) = &ch.source_path else { return }; 111 121 func(path.clone(), ch) 112 122 }); 123 + } 124 + 125 + pub fn book_into_stdout(book: &Book) -> Result<()> { 126 + serde_json::to_string(&book) 127 + .context("failed to serialize book") 128 + .and_then(|output| Ok(std::io::stdout().write_all(output.as_bytes())?)) 129 + .context("failed to write book to stdout") 113 130 } 114 131 }
-3
crates/mdbookkit/tests/snaps/rustdoc_link/rustdoc-link.snap
··· 1 1 --- 2 2 source: crates/mdbookkit/tests/rustdoc_link.rs 3 - assertion_line: 31 4 3 expression: output 5 4 --- 6 5 # mdbook-rustdoc-link ··· 81 80 82 81 This project is released under the [Apache 2.0 License](/LICENSE-APACHE.md) and the 83 82 [MIT License](/LICENSE-MIT.md). 84 - 85 - --- 86 83 87 84 [^1]: Text adapted from [<cite>A Tour of The Rust Standard Library</cite>][tour] 88 85
-2
crates/mdbookkit/tests/snaps/rustdoc_link/supported-syntax.snap
··· 252 252 253 253 <!-- prettier-ignore-end --> 254 254 255 - --- 256 - 257 255 [^1]: 258 256 rust-analyzer's ability to generate links for enum variants like `Option::Some` was 259 257 improved only somewhat recently: before
+13 -15
crates/mdbookkit/tests/snaps/rustdoc_link/supported-syntax.stderr.snap
··· 265 265 248 │ 266 266 249 │ <!-- prettier-ignore-end --> 267 267 250 │ 268 - 251 │ --- 269 - 252 │ 270 - 253 │ [^1]: 271 - 254 │ rust-analyzer's ability to generate links for enum variants like `Option::Some` was 272 - 255 │ improved only somewhat recently: before 273 - 256 │ [#19246](https://github.com/rust-lang/rust-analyzer/pull/19246), links for variants 274 - 257 │ and associated items may only point to the types themselves. If linking to such 275 - 258 │ items doesn't seem to work for you, be sure to upgrade to a newer rust-analyzer 276 - 259 │ first! 277 - 260 │ 278 - 261 │ [^2]: 279 - 262 │ As of rust-analyzer <ra-version>(version)</ra-version>, links generated for macros 280 - 263 │ don't always work. Examples include [`std::format!`] (seen above) and 268 + 251 │ [^1]: 269 + 252 │ rust-analyzer's ability to generate links for enum variants like `Option::Some` was 270 + 253 │ improved only somewhat recently: before 271 + 254 │ [#19246](https://github.com/rust-lang/rust-analyzer/pull/19246), links for variants 272 + 255 │ and associated items may only point to the types themselves. If linking to such 273 + 256 │ items doesn't seem to work for you, be sure to upgrade to a newer rust-analyzer 274 + 257 │ first! 275 + 258 │ 276 + 259 │ [^2]: 277 + 260 │ As of rust-analyzer <ra-version>(version)</ra-version>, links generated for macros 278 + 261 │ don't always work. Examples include [`std::format!`] (seen above) and 281 279 · ────────┬─────── 282 280 · ╰── https://doc.rust-lang.org/stable/alloc/macros/macro.format.html 283 - 264 │ [`tokio::main!`]. For more info, see [Known issues](known-issues.md#macros). 281 + 262 │ [`tokio::main!`]. For more info, see [Known issues](known-issues.md#macros). 284 282 · ────────┬─────── 285 283 · ╰── https://docs.rs/tokio-macros/2.5.0/tokio_macros/macro.main.html 286 - 265 │ 284 + 263 │ 287 285 ╰────
+8 -3
deno.json
··· 1 1 { 2 - "tasks": { 3 - "format": "deno run -A npm:prettier --write docs/app docs/src" 2 + "compilerOptions": { 3 + "lib": ["esnext", "dom", "deno.ns"], 4 + "strict": true 4 5 }, 6 + "exclude": ["node_modules", "build", "dist", "target", "docs/app/dist.js"], 5 7 "imports": { 6 8 "esbuild": "npm:esbuild@^0.25.1", 7 9 "prettier": "npm:prettier@^3.5.3" 8 10 }, 9 - "nodeModulesDir": "auto" 11 + "nodeModulesDir": "auto", 12 + "tasks": { 13 + "format": "deno run -A npm:prettier --write docs/app docs/src" 14 + } 10 15 }
+1
docs/.gitignore
··· 1 1 /build 2 2 /app/dist.css 3 + /app/dist.js 3 4 /src/app
+1 -1
docs/Cargo.toml
··· 12 12 [dependencies] 13 13 anyhow = { workspace = true } 14 14 clap = { workspace = true } 15 - gix-url = { version = "0.29.0" } 15 + gix-url = { version = "0.30.0" } 16 16 miette = { workspace = true } 17 17 serde = { workspace = true } 18 18 serde_json = { workspace = true }
+32 -7
docs/app/build/build.ts
··· 4 4 5 5 const relpath = (path: string) => new URL(path, import.meta.url).pathname; 6 6 7 + try { 8 + await Deno.remove(relpath("../../src/app"), { recursive: true }); 9 + } catch { 10 + // recursive: true is supposed to make it not throw ... 11 + } 12 + 7 13 const built = await esbuild.build({ 8 14 bundle: true, 15 + format: "esm", 9 16 target: ["chrome93", "firefox93", "safari15", "es2020"], 10 17 platform: "browser", 11 18 plugins: [remoteCSS()], 12 - entryPoints: [relpath("../main.css")], 19 + entryPoints: [relpath("../main.js"), relpath("../main.css")], 13 20 outdir: relpath("../../src/app"), 14 21 entryNames: "[name]-[hash]", 15 22 metafile: true, 16 23 logLevel: Deno.env.get("CI") ? "info" : undefined, 17 24 }); 18 25 19 - // generate an `app/dist.css` for mdBook that actually imports the bundle 26 + // generate `app/dist.css` and `app/dist.js` for mdBook that actually imports the bundle 27 + 28 + const css: string[] = []; 29 + const esm: string[] = []; 30 + 31 + for (const [path, file] of Object.entries(built.metafile.outputs)) { 32 + if (file.entryPoint) { 33 + const name = JSON.stringify(`./${pathlib.basename(path)}`); 34 + switch (path.split(".").pop()) { 35 + case "css": 36 + css.push(`@import url(${name});`); 37 + break; 38 + case "js": 39 + esm.push(`import(${name});`); 40 + break; 41 + default: 42 + break; 43 + } 44 + } 45 + } 46 + 47 + await Deno.writeTextFile(relpath("../dist.css"), css.join("\n") + "\n"); 20 48 21 49 await Deno.writeTextFile( 22 - relpath("../dist.css"), 23 - Object.entries(built.metafile.outputs) 24 - .filter(([, file]) => file.entryPoint) 25 - .map(([path]) => `@import url(${JSON.stringify(pathlib.basename(path))});`) 26 - .join("\n"), 50 + relpath("../dist.js"), 51 + `document.addEventListener("DOMContentLoaded", () => { ${esm.join("\n")} });`, 27 52 ); 28 53 29 54 function remoteCSS(): esbuild.Plugin {
+26 -20
docs/app/main.css
··· 208 208 } 209 209 210 210 .footnote-definition { 211 - display: grid; 212 - grid-template-columns: max-content auto; 213 - gap: 0.5rem; 214 - margin-block: 0.5rem; 215 - 216 - .footnote-definition-label { 217 - line-height: 1.8; 211 + > p { 212 + line-height: 1.6; 218 213 } 219 214 220 - > * { 221 - display: block; 222 - margin: 0; 215 + > li:target::before { 216 + background-color: #ffb45420; 217 + outline: 0.5rem solid #ffb45420; 218 + border: none; 219 + border-radius: 0; 220 + top: 0; 221 + bottom: 0; 222 + left: -24px; 223 + right: 0; 223 224 } 225 + } 224 226 225 - > p { 226 - line-height: 1.6; 227 - } 227 + :not(h1, h2, h3, h4, h5, h6, .footnote-definition > li):target { 228 + background-color: #ffb45420; 229 + outline: 0.5rem solid #ffb45420; 228 230 229 - > *:not(.footnote-definition-label) { 230 - grid-column-start: 2; 231 + &.footnote-reference { 232 + background-color: #ffb45440; 233 + outline: 0.25rem solid #ffb45440; 234 + border-radius: 0; 231 235 } 232 236 } 233 237 ··· 237 241 margin: 2rem 0; 238 242 background-color: #3d444d; 239 243 border: 0; 240 - } 241 - 242 - :not(h1, h2, h3, h4, h5, h6):target { 243 - background-color: #ffb45420; 244 - outline: 0.5rem solid #ffb45420; 245 244 } 246 245 247 246 img, ··· 407 406 408 407 > blockquote { 409 408 padding-inline-start: calc(20px - 0.25rem); 409 + } 410 + 411 + .footnote-definition > li:target::before { 412 + top: 0; 413 + bottom: 0; 414 + left: -40px; 415 + width: 100vw; 410 416 } 411 417 412 418 > .mdbook-alerts {
+8
docs/app/main.ts
··· 1 + document 2 + .querySelectorAll<HTMLElement>(".footnote-definition a[href^='#fr-']") 3 + .forEach((elem) => { 4 + elem.style.marginInlineEnd = "2px"; 5 + if (elem.textContent) { 6 + elem.textContent = elem.textContent.replaceAll("↩", "↩\ufe0e"); 7 + } 8 + });
+1
docs/book.toml
··· 15 15 16 16 [output.html] 17 17 additional-css = ["app/dist.css"] 18 + additional-js = ["app/dist.js"] 18 19 default-theme = "ayu" 19 20 git-repository-icon = "fa-github" 20 21 git-repository-url = "https://github.com/tonywu6/mdbookkit"
-2
docs/src/rustdoc-link.md
··· 77 77 This project is released under the [Apache 2.0 License](/LICENSE-APACHE.md) and the 78 78 [MIT License](/LICENSE-MIT.md). 79 79 80 - --- 81 - 82 80 [^1]: Text adapted from [<cite>A Tour of The Rust Standard Library</cite>][tour] 83 81 84 82 <!-- prettier-ignore-start -->
+6 -13
docs/src/rustdoc-link/caching.md
··· 94 94 95 95 Furthermore, the preprocessor relies on the LSP [Work Done 96 96 Progress][lsp-work-done-progress] notifications to know when rust-analyzer has finished 97 - cache priming before actually sending out external docs requests. Because rust-analyzer 98 - seems to automatically reindex multiple times, a "cooldown" mechanism was implemented, 99 - to wait for a hard-coded 500ms after rust-analyzer reports indexing done, before 100 - continuing. 97 + cache priming, before actually sending out external docs requests. This requires parsing 98 + non-structured log messages that rust-analyzer sends out and some debouncing/throttling 99 + logic, which is not ideal, see 100 + [client.rs](/crates/mdbookkit/src/bin/rustdoc_link/client.rs#L129-L132). 101 101 102 - Not doing either of these things causes many links to fail to resolve. 102 + Not waiting for indexing to finish and sending out requests too early causes 103 + rust-analyzer to respond with empty results. 103 104 104 105 **Questions**: 105 106 ··· 142 143 languages like Rust, and `mdbook` has an explicitly non-incremental architecture, 143 144 perfect for rendering Markdown. This makes them somewhat challenging to work well 144 145 together in a live-reload scenario. 145 - 146 - > [!NOTE] 147 - > 148 - > The above is entirely my understanding of how the two projects work, which may have 149 - > gross misconceptions or misuse of words. [Feedback of any kind is very welcome 150 - > :)][gh-issues] 151 - 152 - --- 153 146 154 147 [^1]: 155 148 It was mentioned that the [recently updated, salsa-ified rust-analyzer][salsa]
-2
docs/src/rustdoc-link/supported-syntax.md
··· 248 248 249 249 <!-- prettier-ignore-end --> 250 250 251 - --- 252 - 253 251 [^1]: 254 252 rust-analyzer's ability to generate links for enum variants like `Option::Some` was 255 253 improved only somewhat recently: before
-14
tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - "target": "ESNext", 4 - "lib": ["ESNext"], 5 - "module": "ESNext", 6 - "moduleResolution": "bundler", 7 - "moduleDetection": "force", 8 - "allowJs": true, 9 - "checkJs": true, 10 - "strict": true, 11 - "noEmit": true 12 - }, 13 - "exclude": ["node_modules", "build", "dist", "target"] 14 - }