diff --git a/CHANGELOG.md b/CHANGELOG.md index 38c08dd..fb6b51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ @@ -9,6 +9,7 @@ SPDX-License-Identifier: MIT - Add `o` command for opening a documentation item to the tui viewer. - Add tests for Rust 1.48.0, 1.49.0, 1.50.0, 1.51.0, 1.52.0 and 1.52.1. +- Add support for the new search index format introduced in Rust 1.52.0. ## v0.4.1 (2020-10-11) diff --git a/src/index.rs b/src/index.rs index daf0ab3..54282f4 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2020 Robin Krahl +// SPDX-FileCopyrightText: 2020-2021 Robin Krahl // SPDX-License-Identifier: MIT //! Search index for a documentation source. @@ -6,14 +6,17 @@ //! The search index is read from the `search-index.js` file generated by rustdoc. It contains a //! list of items grouped by their crate. //! -//! For details on the format of the search index, see the `html/render.rs` file in `librustdoc`. -//! Note that the format of the search index changed in April 2020 (Rust 1.44.0) with commit -//! b4fb3069ce82f61f84a9487d17fb96389d55126a. We only support the new format as the old format is -//! much harder to parse. +//! For details on the format of the search index, see the `html/render/mod.rs` (previously +//! `html/render.rs`) file in `librustdoc`. Note that the format of the search index changed in +//! April 2020 (Rust 1.44.0) with commit b4fb3069ce82f61f84a9487d17fb96389d55126a. We only support +//! the new format as the old format is much harder to parse. //! //! For details on the generation of the search index, see the `html/render/cache.rs` file in //! `librustdoc`. +mod v1_44; +mod v1_52; + use std::collections; use std::fmt; use std::fs; @@ -57,18 +60,37 @@ struct Data { crates: collections::HashMap, } -#[derive(Debug, Default, PartialEq, serde::Deserialize)] +#[derive(Debug, Default, PartialEq)] struct CrateData { - #[serde(rename = "i")] items: Vec, - #[serde(rename = "p")] paths: Vec<(usize, String)>, } +impl<'de> serde::Deserialize<'de> for CrateData { + fn deserialize>(deserializer: D) -> Result { + CrateDataVersions::deserialize(deserializer).map(From::from) + } +} + +#[derive(Debug, PartialEq, serde::Deserialize)] +#[serde(untagged)] +enum CrateDataVersions { + V1_44(v1_44::CrateData), + V1_52(v1_52::CrateData), +} + +impl From for CrateData { + fn from(versions: CrateDataVersions) -> Self { + match versions { + CrateDataVersions::V1_44(data) => data.into(), + CrateDataVersions::V1_52(data) => data.into(), + } + } +} + #[derive(Debug, PartialEq, serde_tuple::Deserialize_tuple)] struct ItemData { - #[serde(deserialize_with = "deserialize_item_type")] - ty: doc::ItemType, + ty: ItemType, name: String, path: String, desc: String, @@ -76,41 +98,56 @@ struct ItemData { _ignored: serde_json::Value, } -fn deserialize_item_type<'de, D>(d: D) -> Result -where - D: serde::de::Deserializer<'de>, -{ - use doc::ItemType; - use serde::de::{Deserialize, Error}; - - match u8::deserialize(d)? { - 0 => Ok(ItemType::Module), - 1 => Ok(ItemType::ExternCrate), - 2 => Ok(ItemType::Import), - 3 => Ok(ItemType::Struct), - 4 => Ok(ItemType::Enum), - 5 => Ok(ItemType::Function), - 6 => Ok(ItemType::Typedef), - 7 => Ok(ItemType::Static), - 8 => Ok(ItemType::Trait), - 9 => Ok(ItemType::Impl), - 10 => Ok(ItemType::TyMethod), - 11 => Ok(ItemType::Method), - 12 => Ok(ItemType::StructField), - 13 => Ok(ItemType::Variant), - 14 => Ok(ItemType::Macro), - 15 => Ok(ItemType::Primitive), - 16 => Ok(ItemType::AssocType), - 17 => Ok(ItemType::Constant), - 18 => Ok(ItemType::AssocConst), - 19 => Ok(ItemType::Union), - 20 => Ok(ItemType::ForeignType), - 21 => Ok(ItemType::Keyword), - 22 => Ok(ItemType::OpaqueTy), - 23 => Ok(ItemType::ProcAttribute), - 24 => Ok(ItemType::ProcDerive), - 25 => Ok(ItemType::TraitAlias), - _ => Err(D::Error::custom("Unexpected item type")), +#[derive(Clone, Copy, Debug, PartialEq)] +struct ItemType(doc::ItemType); + +impl From for ItemType { + fn from(ty: doc::ItemType) -> Self { + Self(ty) + } +} + +impl From for doc::ItemType { + fn from(ty: ItemType) -> Self { + ty.0 + } +} + +impl<'de> serde::Deserialize<'de> for ItemType { + fn deserialize>(deserializer: D) -> Result { + use doc::ItemType; + use serde::de::Error; + + match u8::deserialize(deserializer)? { + 0 => Ok(ItemType::Module), + 1 => Ok(ItemType::ExternCrate), + 2 => Ok(ItemType::Import), + 3 => Ok(ItemType::Struct), + 4 => Ok(ItemType::Enum), + 5 => Ok(ItemType::Function), + 6 => Ok(ItemType::Typedef), + 7 => Ok(ItemType::Static), + 8 => Ok(ItemType::Trait), + 9 => Ok(ItemType::Impl), + 10 => Ok(ItemType::TyMethod), + 11 => Ok(ItemType::Method), + 12 => Ok(ItemType::StructField), + 13 => Ok(ItemType::Variant), + 14 => Ok(ItemType::Macro), + 15 => Ok(ItemType::Primitive), + 16 => Ok(ItemType::AssocType), + 17 => Ok(ItemType::Constant), + 18 => Ok(ItemType::AssocConst), + 19 => Ok(ItemType::Union), + 20 => Ok(ItemType::ForeignType), + 21 => Ok(ItemType::Keyword), + 22 => Ok(ItemType::OpaqueTy), + 23 => Ok(ItemType::ProcAttribute), + 24 => Ok(ItemType::ProcDerive), + 25 => Ok(ItemType::TraitAlias), + _ => Err(D::Error::custom("Unexpected item type")), + } + .map(Self) } } @@ -185,7 +222,8 @@ impl Index { &item.path }; - if item.ty == doc::ItemType::AssocType { + let ty = doc::ItemType::from(item.ty); + if ty == doc::ItemType::AssocType { continue; } @@ -201,7 +239,7 @@ impl Index { log::info!("Found index match '{}'", full_name); matches.push(IndexItem { name: full_name, - ty: item.ty, + ty, description: item.desc.clone(), }); } @@ -241,7 +279,7 @@ mod tests { let mut expected: Data = Default::default(); let mut krate: CrateData = Default::default(); krate.items.push(ItemData { - ty: ItemType::Module, + ty: ItemType::Module.into(), name: "name".to_owned(), path: "path".to_owned(), desc: "desc".to_owned(), @@ -277,7 +315,7 @@ mod tests { assert_eq!(empty, index.find(&"NodeDataReff".to_owned().into())); }); - with_rustdoc(">=1.50.0,<1.52.0", |_, path| { + with_rustdoc(">=1.50.0", |_, path| { let index = Index::load(path.join("search-index.js")).unwrap().unwrap(); let empty: Vec = Vec::new(); @@ -296,7 +334,7 @@ mod tests { assert_eq!(empty, index.find(&"NodeDataReff".to_owned().into())); }); - with_rustdoc(">=1.44.0,<1.52.0", |_, path| { + with_rustdoc(">=1.44.0", |_, path| { let index = Index::load(path.join("search-index.js")).unwrap().unwrap(); let empty: Vec = Vec::new(); diff --git a/src/index/v1_44.rs b/src/index/v1_44.rs new file mode 100644 index 0000000..86cf7f9 --- /dev/null +++ b/src/index/v1_44.rs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2021 Robin Krahl +// SPDX-License-Identifier: MIT + +//! Search index format as of Rust 1.44.0. +//! +//! This module contains data structures specific to the search index format introduced with Rust +//! 1.44.0 (commit [b4fb3069ce82f61f84a9487d17fb96389d55126a][]). +//! +//! [b4fb3069ce82f61f84a9487d17fb96389d55126a]: https://github.com/rust-lang/rust/commit/b4fb3069ce82f61f84a9487d17fb96389d55126a + +#[derive(Debug, Default, PartialEq, serde::Deserialize)] +pub struct CrateData { + #[serde(rename = "i")] + items: Vec, + #[serde(rename = "p")] + paths: Vec<(usize, String)>, +} + +impl From for super::CrateData { + fn from(data: CrateData) -> Self { + Self { + items: data.items, + paths: data.paths, + } + } +} diff --git a/src/index/v1_52.rs b/src/index/v1_52.rs new file mode 100644 index 0000000..a5d0191 --- /dev/null +++ b/src/index/v1_52.rs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2021 Robin Krahl +// SPDX-License-Identifier: MIT + +//! Search index format as of Rust 1.52.0. +//! +//! This module contains data structures specific to the search index format introduced with Rust +//! 1.52.0 (commit [3934dd1b3e7514959202de6ca0d2636bcae21830][]). +//! +//! [3934dd1b3e7514959202de6ca0d2636bcae21830]: https://github.com/rust-lang/rust/commit/3934dd1b3e7514959202de6ca0d2636bcae21830 + +#[derive(Debug, Default, PartialEq, serde::Deserialize)] +pub struct CrateData { + #[serde(rename = "t")] + item_types: Vec, + #[serde(rename = "n")] + item_names: Vec, + #[serde(rename = "q")] + item_paths: Vec, + #[serde(rename = "d")] + item_descs: Vec, + #[serde(rename = "i")] + item_parents: Vec, + #[serde(rename = "p")] + paths: Vec<(usize, String)>, +} + +impl From for super::CrateData { + fn from(data: CrateData) -> Self { + let items = data + .item_types + .into_iter() + .zip(data.item_names.into_iter()) + .zip(data.item_paths.into_iter()) + .zip(data.item_descs.into_iter()) + .zip(data.item_parents.into_iter()) + .map(|((((ty, name), path), desc), parent)| super::ItemData { + ty, + name, + path, + desc, + parent: match parent { + 0 => None, + parent => Some(parent - 1), + }, + _ignored: Default::default(), + }) + .collect(); + Self { + items, + paths: data.paths, + } + } +} diff --git a/src/main.rs b/src/main.rs index 55ea6fc..d7ccc91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2020 Robin Krahl +// SPDX-FileCopyrightText: 2020-2021 Robin Krahl // SPDX-License-Identifier: MIT //! rusty-man is a command-line viewer for documentation generated by `rustdoc`. @@ -27,9 +27,9 @@ //! For details on the structure of the HTML files and the search index, you have to look at the //! `html::render` module in the `librustdoc` source code. //! -//! Note that the format of the search index changed in a recent Rust version (> 1.40 and <= 1.44). -//! We don’t support the old index format. As the format of the HTML files is not specified, -//! rusty-man might not work with new Rust versions that change the documentation format. +//! Note that the format of the search index changed in Rust 1.44. We don’t support the old index +//! format. As the format of the HTML files is not specified, rusty-man might not work with new +//! Rust versions that change the documentation format. // We have to disable some clippy lints as our MSRV is 1.40: #![allow( diff --git a/src/viewer/tui/mod.rs b/src/viewer/tui/mod.rs index b5d681f..6daf2f3 100644 --- a/src/viewer/tui/mod.rs +++ b/src/viewer/tui/mod.rs @@ -224,7 +224,7 @@ fn create_cursive( cursive.add_global_callback('o', open_doc_dialog); let mut theme = theme::Theme { - shadow: false, + shadow: false, ..Default::default() }; theme.palette[theme::PaletteColor::Background] = theme::Color::TerminalDefault;