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.

docs: update docs (WIP)

Tony Wu 4966da90 318ffa61

+214 -197
+3
Cargo.lock
··· 327 327 "anstyle", 328 328 "clap_lex", 329 329 "strsim", 330 + "terminal_size", 331 + "unicase", 332 + "unicode-width 0.2.0", 330 333 ] 331 334 332 335 [[package]]
+2 -2
crates/mdbook-permalinks/src/vcs.rs
··· 133 133 /// 134 134 /// Encoding characters are always in uppercase [^2]. 135 135 /// 136 - /// [^1]: https://url.spec.whatwg.org/#path-percent-encode-set 137 - /// [^2]: https://url.spec.whatwg.org/#percent-encode 136 + /// [^1]: <https://url.spec.whatwg.org/#path-percent-encode-set> 137 + /// [^2]: <https://url.spec.whatwg.org/#percent-encode> 138 138 macro_rules! encoded_param { 139 139 ($param:literal) => { 140 140 concat!("%7B", $param, "%7D")
+1 -1
crates/mdbook-rustdoc-links/src/cache.rs
··· 14 14 15 15 use mdbookkit::log_debug; 16 16 17 - use super::{Resolver, env::Environment, link::ItemLinks, page::Pages, url::UrlToPath}; 17 + use crate::{env::Environment, link::ItemLinks, page::Pages, resolver::Resolver, url::UrlToPath}; 18 18 19 19 #[allow(async_fn_in_trait)] 20 20 pub trait Cache: DeserializeOwned + Serialize {
+1 -1
crates/mdbook-rustdoc-links/src/client.rs
··· 31 31 32 32 use mdbookkit::{log_debug, log_warning, logging::spinner}; 33 33 34 - use super::{ 34 + use crate::{ 35 35 env::Environment, 36 36 link::ItemLinks, 37 37 sync::{EventSampler, EventSampling},
+1 -1
crates/mdbook-rustdoc-links/src/env.rs
··· 15 15 16 16 use mdbookkit::{error::OnWarning, markdown::default_markdown_options}; 17 17 18 - use super::markdown; 18 + use crate::markdown; 19 19 20 20 /// Configuration for the preprocessor. 21 21 ///
+1 -1
crates/mdbook-rustdoc-links/src/link.rs
··· 8 8 9 9 use mdbookkit::log_trace; 10 10 11 - use super::{env::EmitConfig, item::Item}; 11 + use crate::{env::EmitConfig, item::Item}; 12 12 13 13 pub mod diagnostic; 14 14
+16 -142
crates/mdbook-rustdoc-links/src/main.rs
··· 1 - use std::{borrow::Borrow, collections::HashMap, hash::Hash, io::Write, sync::Arc}; 1 + use std::{collections::HashMap, io::Write}; 2 2 3 - use anyhow::{Context, Result}; 3 + use anyhow::{ 4 + Context, 5 + // not shadowing Result because it is linked from docs 6 + Result as Result2, 7 + }; 4 8 use clap::{Parser, Subcommand}; 5 9 use console::colors_enabled_stderr; 6 10 use log::LevelFilter; 7 - use lsp_types::Position; 8 11 use mdbook_preprocessor::PreprocessorContext; 9 12 use tap::{Pipe, TapFallible}; 10 - use tokio::task::JoinSet; 11 13 12 14 use mdbookkit::{ 13 15 book::{BookConfigHelper, BookHelper, book_from_stdin, string_from_stdin}, 14 16 diagnostics::Issue, 15 - log_debug, log_warning, 16 - logging::{ConsoleLogger, is_logging, spinner}, 17 - styled, 17 + log_warning, 18 + logging::{ConsoleLogger, is_logging}, 18 19 }; 19 20 20 21 use self::{ 21 22 cache::{Cache, FileCache}, 22 23 client::Client, 23 24 env::{Config, Environment, RustAnalyzer}, 24 - item::Item, 25 - link::ItemLinks, 26 25 page::Pages, 27 - url::UrlToPath, 26 + resolver::Resolver, 28 27 }; 29 28 30 29 mod cache; ··· 34 33 mod link; 35 34 mod markdown; 36 35 mod page; 36 + mod resolver; 37 37 mod sync; 38 38 #[cfg(test)] 39 39 mod tests; 40 40 mod url; 41 41 42 - /// Type that can provide links. 43 - /// 44 - /// Resolvers should modify the provided [`Pages`] in place. 45 - /// 46 - /// This is currently an abstraction over two sources of links: 47 - /// 48 - /// - [`Client`], which invokes rust-analyzer 49 - /// - [`Cache`] implementations 50 - /// 51 - /// [`Cache`]: crate::bin::rustdoc_link::cache::Cache 52 - trait Resolver { 53 - async fn resolve<K>(&self, pages: &mut Pages<'_, K>) -> Result<()> 54 - where 55 - K: Eq + Hash; 56 - } 57 - 58 - impl Resolver for Client { 59 - async fn resolve<K>(&self, pages: &mut Pages<'_, K>) -> Result<()> 60 - where 61 - K: Eq + Hash, 62 - { 63 - let request = pages.items(); 64 - 65 - if request.is_empty() { 66 - return Ok(()); 67 - } 68 - 69 - let main = std::fs::read_to_string(self.env().entrypoint.to_path()?)?; 70 - 71 - let (context, request) = { 72 - let mut context = format!("{main}\nfn {UNIQUE_ID} () {{\n"); 73 - 74 - let line = context.chars().filter(|&c| c == '\n').count(); 75 - 76 - let request = request 77 - .iter() 78 - .scan(line, |line, (key, item)| { 79 - build(&mut context, line, item).map(|cursors| (key.clone(), cursors)) 80 - }) 81 - .collect::<Vec<_>>(); 82 - 83 - fn build(context: &mut String, line: &mut usize, item: &Item) -> Option<Vec<Position>> { 84 - use std::fmt::Write; 85 - let _ = writeln!(context, "{}", item.stmt); 86 - let cursors = item 87 - .cursor 88 - .as_ref() 89 - .iter() 90 - .map(|&col| Position::new(*line as _, col as _)) 91 - .collect::<Vec<_>>(); 92 - *line += 1; 93 - Some(cursors) 94 - } 95 - 96 - context.push('}'); 97 - 98 - (context, request) 99 - }; 100 - 101 - log::debug!("request context\n\n{context}\n"); 102 - 103 - let document = self 104 - .open(self.env().entrypoint.clone(), context) 105 - .await? 106 - .pipe(Arc::new); 107 - 108 - spinner().create("resolve", Some(request.len() as _)); 109 - 110 - let tasks: JoinSet<Option<(String, ItemLinks)>> = request 111 - .into_iter() 112 - .map(|(key, pos)| { 113 - let key = key.to_string(); 114 - let doc = document.clone(); 115 - resolve(doc, key, pos) 116 - }) 117 - .collect(); 118 - 119 - async fn resolve( 120 - doc: Arc<client::OpenDocument>, 121 - key: String, 122 - pos: Vec<Position>, 123 - ) -> Option<(String, ItemLinks)> { 124 - let _task = spinner().task("resolve", &key); 125 - for p in pos { 126 - let resolved = doc 127 - .resolve(p) 128 - .await 129 - .with_context(|| format!("{p:?}")) 130 - .context("failed to resolve symbol:") 131 - .tap_err(log_debug!()) 132 - .ok(); 133 - if let Some(resolved) = resolved { 134 - return Some((key, resolved)); 135 - } 136 - } 137 - None 138 - } 139 - 140 - let resolved = tasks 141 - .join_all() 142 - .await 143 - .into_iter() 144 - .flatten() 145 - .collect::<HashMap<_, _>>(); 146 - 147 - spinner().finish("resolve", styled!(("done").green())); 148 - 149 - pages.apply(&resolved); 150 - 151 - Ok(()) 152 - } 153 - } 154 - 155 - impl<K> Resolver for HashMap<K, ItemLinks> 156 - where 157 - K: Borrow<str> + Eq + Hash, 158 - { 159 - async fn resolve<P>(&self, pages: &mut Pages<'_, P>) -> Result<()> 160 - where 161 - P: Eq + Hash, 162 - { 163 - pages.apply(self); 164 - Ok(()) 165 - } 166 - } 167 - 168 42 #[tokio::main] 169 - async fn main() -> Result<()> { 43 + async fn main() -> Result2<()> { 170 44 ConsoleLogger::install(PREPROCESSOR_NAME); 171 45 match Program::parse().command { 172 46 Some(Command::Supports { .. }) => Ok(()), ··· 203 77 Describe, 204 78 } 205 79 206 - async fn mdbook() -> Result<()> { 80 + async fn mdbook() -> Result2<()> { 207 81 let (ctx, mut book) = book_from_stdin().context("failed to read from mdbook")?; 208 82 209 83 let config = config(&ctx).context("failed to read preprocessor config from book.toml")?; ··· 275 149 Ok(()) 276 150 } 277 151 278 - async fn markdown(config: Config) -> Result<()> { 152 + async fn markdown(config: Config) -> Result2<()> { 279 153 let client = Environment::new(config) 280 154 .context("failed to initialize")? 281 155 .pipe(Client::new); ··· 321 195 Ok(()) 322 196 } 323 197 324 - fn which() -> Result<()> { 198 + fn which() -> Result2<()> { 325 199 let env = Environment::new(Default::default())?; 326 200 327 201 match env.which() { ··· 337 211 } 338 212 339 213 #[cfg(feature = "_testing")] 340 - fn describe() -> Result<()> { 214 + fn describe() -> Result2<()> { 341 215 print!("{}", mdbookkit::docs::describe_preprocessor::<Config>()?); 342 216 Ok(()) 343 217 } 344 218 345 - fn config(ctx: &PreprocessorContext) -> Result<Config> { 219 + fn config(ctx: &PreprocessorContext) -> Result2<Config> { 346 220 let mut config = ctx.config.preprocessor::<Config>(PREPROCESSOR_NAME)?; 347 221 348 222 if let Some(path) = config.manifest_dir {
+1 -1
crates/mdbook-rustdoc-links/src/markdown.rs
··· 19 19 /// 20 20 /// Links that are "broken" that aren't actually doc links won't show up in the output, 21 21 /// because the preprocessor ignores links that cannot be parsed and is capable of 22 - /// emitting only changed links, see [`PatchStream`][crate::markdown::PatchStream]. 22 + /// emitting only changed links, see [`PatchStream`][mdbookkit::markdown::PatchStream]. 23 23 pub struct ItemLinks; 24 24 25 25 impl ItemLinks {
+1 -1
crates/mdbook-rustdoc-links/src/page.rs
··· 11 11 12 12 use mdbookkit::markdown::{PatchStream, Spanned}; 13 13 14 - use super::{ 14 + use crate::{ 15 15 env::EmitConfig, 16 16 item::Item, 17 17 link::{ItemLinks, Link, LinkState},
+3 -1
crates/mdbook-rustdoc-links/src/page/diagnostic.rs
··· 2 2 3 3 use mdbookkit::diagnostics::{Diagnostics, ReportBuilder}; 4 4 5 - use super::{super::link::diagnostic::LinkDiagnostic, Pages}; 5 + use crate::link::diagnostic::LinkDiagnostic; 6 + 7 + use super::Pages; 6 8 7 9 impl<'a, K: Eq + Hash> Pages<'a, K> { 8 10 pub fn diagnostics(&self) -> Vec<PageDiagnostics<'a, K>>
+143
crates/mdbook-rustdoc-links/src/resolver.rs
··· 1 + use std::{borrow::Borrow, collections::HashMap, hash::Hash, sync::Arc}; 2 + 3 + use anyhow::{Context, Result}; 4 + use lsp_types::Position; 5 + use tap::{Pipe, TapFallible}; 6 + use tokio::task::JoinSet; 7 + 8 + use mdbookkit::{log_debug, logging::spinner, styled}; 9 + 10 + use crate::{ 11 + UNIQUE_ID, 12 + client::{Client, OpenDocument}, 13 + item::Item, 14 + link::ItemLinks, 15 + page::Pages, 16 + url::UrlToPath, 17 + }; 18 + 19 + /// Type that can provide links. 20 + /// 21 + /// Resolvers should modify the provided [`Pages`] in place. 22 + /// 23 + /// This is currently an abstraction over two sources of links: 24 + /// 25 + /// - [`Client`], which invokes rust-analyzer 26 + /// - [`Cache`] implementations 27 + /// 28 + /// [`Cache`]: crate::cache::Cache 29 + pub trait Resolver { 30 + async fn resolve<K>(&self, pages: &mut Pages<'_, K>) -> Result<()> 31 + where 32 + K: Eq + Hash; 33 + } 34 + 35 + impl Resolver for Client { 36 + async fn resolve<K>(&self, pages: &mut Pages<'_, K>) -> Result<()> 37 + where 38 + K: Eq + Hash, 39 + { 40 + let request = pages.items(); 41 + 42 + if request.is_empty() { 43 + return Ok(()); 44 + } 45 + 46 + let main = std::fs::read_to_string(self.env().entrypoint.to_path()?)?; 47 + 48 + let (context, request) = { 49 + let mut context = format!("{main}\nfn {UNIQUE_ID} () {{\n"); 50 + 51 + let line = context.chars().filter(|&c| c == '\n').count(); 52 + 53 + let request = request 54 + .iter() 55 + .scan(line, |line, (key, item)| { 56 + build(&mut context, line, item).map(|cursors| (key.clone(), cursors)) 57 + }) 58 + .collect::<Vec<_>>(); 59 + 60 + fn build(context: &mut String, line: &mut usize, item: &Item) -> Option<Vec<Position>> { 61 + use std::fmt::Write; 62 + let _ = writeln!(context, "{}", item.stmt); 63 + let cursors = item 64 + .cursor 65 + .as_ref() 66 + .iter() 67 + .map(|&col| Position::new(*line as _, col as _)) 68 + .collect::<Vec<_>>(); 69 + *line += 1; 70 + Some(cursors) 71 + } 72 + 73 + context.push('}'); 74 + 75 + (context, request) 76 + }; 77 + 78 + log::debug!("request context\n\n{context}\n"); 79 + 80 + let document = self 81 + .open(self.env().entrypoint.clone(), context) 82 + .await? 83 + .pipe(Arc::new); 84 + 85 + spinner().create("resolve", Some(request.len() as _)); 86 + 87 + let tasks: JoinSet<Option<(String, ItemLinks)>> = request 88 + .into_iter() 89 + .map(|(key, pos)| { 90 + let key = key.to_string(); 91 + let doc = document.clone(); 92 + resolve(doc, key, pos) 93 + }) 94 + .collect(); 95 + 96 + async fn resolve( 97 + doc: Arc<OpenDocument>, 98 + key: String, 99 + pos: Vec<Position>, 100 + ) -> Option<(String, ItemLinks)> { 101 + let _task = spinner().task("resolve", &key); 102 + for p in pos { 103 + let resolved = doc 104 + .resolve(p) 105 + .await 106 + .with_context(|| format!("{p:?}")) 107 + .context("failed to resolve symbol:") 108 + .tap_err(log_debug!()) 109 + .ok(); 110 + if let Some(resolved) = resolved { 111 + return Some((key, resolved)); 112 + } 113 + } 114 + None 115 + } 116 + 117 + let resolved = tasks 118 + .join_all() 119 + .await 120 + .into_iter() 121 + .flatten() 122 + .collect::<HashMap<_, _>>(); 123 + 124 + spinner().finish("resolve", styled!(("done").green())); 125 + 126 + pages.apply(&resolved); 127 + 128 + Ok(()) 129 + } 130 + } 131 + 132 + impl<K> Resolver for HashMap<K, ItemLinks> 133 + where 134 + K: Borrow<str> + Eq + Hash, 135 + { 136 + async fn resolve<P>(&self, pages: &mut Pages<'_, P>) -> Result<()> 137 + where 138 + P: Eq + Hash, 139 + { 140 + pages.apply(self); 141 + Ok(()) 142 + } 143 + }
+1 -1
crates/mdbook-rustdoc-links/src/tests.rs
··· 7 7 use mdbookkit::{portable_snapshots, test_document, testing::TestDocument}; 8 8 9 9 use crate::{ 10 - Resolver, 11 10 client::Client, 12 11 env::{Config, Environment}, 13 12 page::Pages, 13 + resolver::Resolver, 14 14 }; 15 15 16 16 struct TestOutput {
+1 -1
crates/mdbookkit/src/diagnostics.rs
··· 33 33 fn level(&self) -> Level; 34 34 } 35 35 36 - /// A collection of [`Problem`]s associated with a Markdown file. 36 + /// A collection of [`IssueItem`]s associated with a Markdown file. 37 37 pub struct Diagnostics<'a, K, P> { 38 38 text: &'a str, 39 39 name: K,
+4
crates/mdbookkit/src/lib.rs
··· 17 17 pub mod markdown; 18 18 #[cfg(feature = "_testing")] 19 19 pub mod testing; 20 + 21 + // referenced in docs 22 + #[doc(hidden)] 23 + pub use diagnostics::Diagnostics;
+1 -5
crates/mdbookkit/src/markdown.rs
··· 13 13 /// [`pulldown_cmark::Event`] stream, whitespace is NOT preserved. This is problematic 14 14 /// for mdBook preprocessors, because preprocessors downstream may need to work on 15 15 /// syntax that is whitespace-sensitive. Normalizing all whitespace could cause such 16 - /// usage to no longer be recognized. An example is [`mdbook-alerts`][alerts] 17 - /// which works on GitHub's ["alerts"][gh-alerts] syntax. 18 - /// 19 - /// [alerts]: https://crates.io/crates/mdbook-alerts 20 - /// [gh-alerts]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts 16 + /// usage to no longer be recognized. 21 17 pub struct PatchStream<'a, S> { 22 18 source: &'a str, 23 19 stream: S,
+1 -1
docs/Cargo.toml
··· 11 11 12 12 [dependencies] 13 13 anyhow = { workspace = true } 14 - clap = { workspace = true } 14 + clap = { workspace = true, features = ["unstable-doc"] } 15 15 env_logger = { workspace = true } 16 16 gix-url = { version = "0.30.0" } 17 17 log = { workspace = true }
+6
docs/src/lib.rs
··· 1 + pub use anyhow::Context; 1 2 3 + pub use mdbookkit::Diagnostics; 4 + 5 + pub mod error { 6 + pub use mdbookkit::error::is_ci; 7 + }
+8 -19
docs/src/rustdoc-link/caching.md
··· 1 1 # Caching 2 2 3 3 By default, `mdbook-rustdoc-link` spawns a fresh `rust-analyzer` process every time it 4 - is run. rust-analyzer then reindexes your entire project before resolving links. 4 + runs. rust-analyzer then reindexes your entire project before resolving links. 5 5 6 - This significantly impacts the responsiveness of `mdbook serve` — it is as if for every 7 - live reload, you had to reopen your editor, and it gets even worse the more dependencies 8 - your project has. 9 - 10 - To mitigate this, there is an experimental caching feature, disabled by default. 6 + This makes the `mdbook serve` command significantly slower, more so if your project 7 + contains a large number of dependencies. It is as if for every live reload, you had to 8 + reopen your editor. 11 9 12 - <details class="toc" open> 13 - <summary>Sections</summary> 14 - 15 - - [Enabling caching](#enabling-caching) 16 - - [How it works](#how-it-works) 17 - - [Help wanted 🙌](#help-wanted-) 18 - - [Cache priming and progress tracking](#cache-priming-and-progress-tracking) 19 - - [Using `ra-multiplex`](#using-ra-multiplex) 20 - - [Postscript](#postscript) 21 - 22 - </details> 10 + To mitigate this, there is an experimental caching feature. The feature is disabled by 11 + default. 23 12 24 13 ## Enabling caching 25 14 ··· 53 42 > [!NOTE] 54 43 > 55 44 > The following are implementation details. See 56 - > [rustdoc_link/cache.rs](/crates/mdbookkit/src/bin/rustdoc_link/cache.rs). 45 + > [cache.rs](/crates/mdbook-rustdoc-links/src/cache.rs). 57 46 58 47 The effectiveness of this mechanism is based on the following assumptions: 59 48 ··· 97 86 cache priming, before actually sending out external docs requests. This requires parsing 98 87 non-structured log messages that rust-analyzer sends out and some debouncing/throttling 99 88 logic, which is not ideal, see 100 - [client.rs](/crates/mdbookkit/src/bin/rustdoc_link/client.rs#L142). 89 + [client.rs](/crates/mdbook-rustdoc-links/src/client.rs#L153). 101 90 102 91 Not waiting for indexing to finish and sending out requests too early causes 103 92 rust-analyzer to respond with empty results.
+1 -1
docs/src/rustdoc-link/continuous-integration.md
··· 73 73 74 74 [^ci-true]: 75 75 Specifically, when `CI` is anything other than `""`, `"0"`, or `"false"`. The logic 76 - is encapsulated in the [`is_ci`][crate::env::is_ci] function. 76 + is encapsulated in the [`is_ci`][crate::error::is_ci] function. 77 77 78 78 [^stderr]: 79 79 Specifically, when stderr is redirected to something that isn't a terminal, such as
+17 -17
docs/src/rustdoc-link/name-resolution.md
··· 12 12 An item must be **in scope in the entrypoint** for the proprocessor to generate a link 13 13 for it. 14 14 15 - Let's say you have the following as your `lib.rs`: 15 + For example, with the following as `lib.rs`: 16 16 17 17 <figure> 18 18 19 19 ```rs 20 20 use anyhow::Context; 21 - /// Type that can provide links. 22 - pub trait Resolver {} 23 - mod env { 24 - /// Options for the preprocessor. 25 - pub struct Config {} 21 + pub struct Diagnostics {} 22 + mod error { 23 + pub fn is_ci() {} 26 24 } 27 25 ``` 28 26 ··· 31 29 Items in the entrypoint can be linked to with just their names: 32 30 33 31 > ```md 34 - > [`Resolver`] — Type that can provide links. 32 + > [`Diagnostics`] encapsulates possible issues detected within Markdown sources. 35 33 > 36 - > This crate also uses the [`Context`] trait from [`anyhow`]. 34 + > This crate uses the [`Context`] trait from [`anyhow`]. 37 35 > ``` 38 36 > 39 - > [`Resolver`] — Type that can provide links. 37 + > [`Diagnostics`] encapsulates possible issues detected within Markdown sources. 40 38 > 41 - > This crate also uses the [`Context`] trait from [`anyhow`]. 39 + > This crate uses the [`Context`] trait from [`anyhow`]. 42 40 43 - This includes items from the prelude (unless you are using `#![no_implicit_prelude]`): 41 + This includes items from the prelude: 44 42 45 43 > ```md 46 44 > [`FromIterator`] is in the prelude starting from Rust 2021. ··· 52 50 from others in your Markdown source, you can write `crate::*`: 53 51 54 52 > ```md 55 - > [Configurations](configuration.md) for the preprocessor is defined in the 56 - > [`Config`][crate::env::Config] type. 53 + > The [`is_ci`][crate::error::is_ci] function detects whether the preprocessor is 54 + > running in a [continuous integration](continuous-integration.md) environment, such 55 + > that warnings may be promoted to errors. 57 56 > ``` 58 57 > 59 - > [Configurations](configuration.md) for the preprocessor is defined in the 60 - > [`Config`][crate::env::Config] type. 58 + > The [`is_ci`][crate::error::is_ci] function detects whether the preprocessor is 59 + > running in a [continuous integration](continuous-integration.md) environment, such 60 + > that warnings may be promoted to errors. 61 61 62 62 For everything else, provide its full path, as if you were writing a `use` declaration: 63 63 64 64 > ```md 65 - > [`JoinSet`][tokio::task::JoinSet] is analogous to `asyncio.as_completed`. 65 + > [`JoinSet`][tokio::task::JoinSet] is analogous to Python's `asyncio.as_completed`. 66 66 > ``` 67 67 > 68 - > [`JoinSet`][tokio::task::JoinSet] is analogous to `asyncio.as_completed`. 68 + > [`JoinSet`][tokio::task::JoinSet] is analogous to Python's `asyncio.as_completed`. 69 69 70 70 > [!TIP] 71 71 >