Skip to content

Commit

Permalink
Rust docs: have a macro to make link to the Slint doc
Browse files Browse the repository at this point in the history
  • Loading branch information
ogoffart committed Dec 12, 2024
1 parent 356376d commit c0f346f
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 18 deletions.
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ path = [
"biome.json",
"cspell.json",
"docs/search/scraper-config.json",
"internal/core-macros/link-data.json",
"package.json",
"pnpm-lock.yaml",
"rustfmt.toml",
Expand Down
1 change: 1 addition & 0 deletions api/rs/build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ default = []

[dependencies]
i-slint-compiler = { workspace = true, features = ["default", "rust", "display-diagnostics", "software-renderer", "bundle-translations"] }
i-slint-core-macros = { workspace = true }

spin_on = { workspace = true }
thiserror = "1"
Expand Down
1 change: 1 addition & 0 deletions api/rs/slint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ backend-android-activity-05 = ["i-slint-backend-android-activity/native-activity
i-slint-core = { workspace = true }
slint-macros = { workspace = true }
i-slint-backend-selector = { workspace = true }
i-slint-core-macros = { workspace = true }

const-field-offset = { version = "0.1.2", path = "../../../helper_crates/const-field-offset" }
document-features = { version = "0.2.0", optional = true }
Expand Down
3 changes: 2 additions & 1 deletion api/rs/slint/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,15 @@ pub mod mcu {
}
}

#[i_slint_core_macros::slint_doc]
pub mod cargo_features {
//! # Feature flags and backend selection.
//! Use the following feature flags in your Cargo.toml to enable additional features.
//!
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
//!
//! More information about the backend and renderers is available in the
#![doc = concat!("[Slint Documentation](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint/src/advanced/backends_and_renderers.html)")]
//![Slint Documentation](slint:backends_and_renderers)")]
use crate::*;
}

Expand Down
16 changes: 10 additions & 6 deletions api/rs/slint/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
This crate is the main entry point for embedding user interfaces designed with
[Slint](https://slint.rs/) in Rust programs.
*/
#![doc = concat!("If you are new to Slint, start with the [Walk-through **tutorial**](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint/src/quickstart)")]
#![doc = i_slint_core_macros::slint_doc_str!("If you are new to Slint, start with the [Walk-through **tutorial**](slint:quickstart)")]
/*! If you are already familiar with Slint, the following topics provide related information.
## Topics
*/
#![doc = concat!("- [The Slint Language Documentation](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint)")]
#![doc = i_slint_core_macros::slint_doc_str!("- [The Slint Language Documentation](slint:index)")]
/*! - [Type mappings between .slint and Rust](docs::type_mappings)
- [Feature flags and backend selection](docs::cargo_features)
- [Slint on Microcontrollers](docs::mcu)
Expand All @@ -30,7 +30,7 @@ of including them in Rust:
- The `.slint` code is [inline in a macro](#the-slint-code-in-a-macro).
- The `.slint` code in [external files compiled with `build.rs`](#the-slint-code-in-external-files-is-compiled-with-buildrs)
*/
#![doc = concat!(" - The `.slint` code is loaded dynamically at run-time from the file system, by using the [interpreter API](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint_interpreter/).")]
#![doc = i_slint_core_macros::slint_doc_str!(" - The `.slint` code is loaded dynamically at run-time from the file system, by using the [interpreter API](slint:rust:slint_interpreter/).")]
/*!
With the first two methods, the markup code is translated to Rust code and each component is turned into a Rust
Expand Down Expand Up @@ -59,8 +59,9 @@ fn main() {
### The .slint code in external files is compiled with `build.rs`
When your design becomes bigger in terms of markup code, you may want move it to a dedicated*/
#![doc = concat!("`.slint` file. It's also possible to split a `.slint` file into multiple files using [modules](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint/src/language/syntax/modules.html).")]
When your design becomes bigger in terms of markup code, you may want move it to a dedicated
`.slint` file. */
#![doc = i_slint_core_macros::slint_doc_str!("It's also possible to split a `.slint` file into multiple files using [modules](slint:modules).")]
/*!Use a [build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) to compile
your main `.slint` file:
Expand Down Expand Up @@ -173,7 +174,7 @@ To run an async function or a future, use [`spawn_local()`].
## Exported Global singletons
*/
#![doc = concat!("When you export a [global singleton](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint/src/language/syntax/globals.html) from the main file,")]
#![doc = i_slint_core_macros::slint_doc_str!("When you export a [global singleton](slint:globals) from the main file,")]
/*! it is also generated with the exported name. Like the main component, the generated struct have
inherent method to access the properties and callback:
Expand Down Expand Up @@ -358,6 +359,7 @@ macro_rules! include_modules {
};
}

#[i_slint_core_macros::slint_doc]
/// Initialize translations when using the `gettext` feature.
///
/// Call this in your main function with the path where translations are located.
Expand All @@ -371,6 +373,8 @@ macro_rules! include_modules {
/// `locale` is a locale name (e.g., `en`, `en_GB`, `fr`), and
/// `crate` is the package name obtained from the `CARGO_PKG_NAME` environment variable.
///
/// See also the [Translation documentation](slint:translations).
///
/// ### Example
/// ```rust
/// fn main() {
Expand Down
4 changes: 3 additions & 1 deletion docs/astro/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,5 +194,7 @@ type LinkMapType = {
};
};

import linkMapData from "./link-data.json" assert { type: "json" };
import linkMapData from "../../../../internal/core-macros/link-data.json" assert {
type: "json",
};
export const linkMap: Readonly<LinkMapType> = linkMapData;
2 changes: 1 addition & 1 deletion internal/backends/winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ cfg_aliases = { workspace = true }
slint = { path = "../../../api/rs/slint", default-features = false, features = ["std", "compat-1-2", "backend-winit", "renderer-software", "raw-window-handle-06"] }

[package.metadata.docs.rs]
features = ["wayland", "renderer-software"]
features = ["wayland", "renderer-software", "raw-window-handle-06"]
21 changes: 15 additions & 6 deletions internal/backends/winit/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,9 @@ impl BackendBuilder {
}
}

#[doc = concat!("This struct implements the Slint Platform trait. Use this in conjunction with [`slint::platform::set_platform`](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/platform/fn.set_platform.html) to initialize.")]
#[i_slint_core_macros::slint_doc]
/// This struct implements the Slint Platform trait.
/// Use this in conjunction with [`slint::platform::set_platform`](slint:rust:slint/platform/fn.set_platform.html) to initialize.
/// Slint to use winit for all windowing system interaction.
///
/// ```rust,no_run
Expand Down Expand Up @@ -352,14 +354,19 @@ pub struct Backend {
}

impl Backend {
#[doc = concat!("Creates a new winit backend with the default renderer that's compiled in. See the [backend documentation](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/index.html#backends) for")]
/// details on how to select the default renderer.
#[i_slint_core_macros::slint_doc]
/// Creates a new winit backend with the default renderer that's compiled in.
///
/// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
pub fn new() -> Result<Self, PlatformError> {
Self::builder().build()
}

#[doc = concat!("Creates a new winit backend with the renderer specified by name. See the [backend documentation](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/index.html#backends) for")]
/// details on how to select the default renderer.
#[i_slint_core_macros::slint_doc]
/// Creates a new winit backend with the renderer specified by name.
///
/// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer.
///
/// If the renderer name is `None` or the name is not recognized, the default renderer is selected.
pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
let mut builder = Self::builder();
Expand Down Expand Up @@ -523,7 +530,9 @@ mod private {
pub trait WinitWindowAccessorSealed {}
}

#[doc = concat!("This helper trait can be used to obtain access to the [`winit::window::Window`] for a given [`slint::Window`](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/struct.window).")]
#[i_slint_core_macros::slint_doc]
/// This helper trait can be used to obtain access to the [`winit::window::Window`] for a given
/// [`slint::Window`](slint:rust:slint/struct.window).")]
pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
/// Returns true if a [`winit::window::Window`] exists for this window. This is the case if the window is
/// backed by this winit backend.
Expand Down
3 changes: 2 additions & 1 deletion internal/core-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ path = "lib.rs"

[dependencies]
quote = "1.0"
syn = "2.0"
syn = { version = "2.0", features = ["full", "visit-mut"] }
serde_json = { workspace = true }
28 changes: 28 additions & 0 deletions internal/core-macros/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

// Copyright © SixtyFPS GmbH <[email protected]>

#![doc = include_str!("README.md")]
#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;

mod slint_doc;

/// This derive macro is used with structures in the run-time library that are meant
/// to be exposed to the language. The structure is introspected for properties and fields
/// marked with the `rtti_field` attribute and generates run-time type information for use
Expand Down Expand Up @@ -179,3 +184,26 @@ fn callback_arg(ty: &syn::Type) -> Option<(&syn::Type, Option<&syn::Type>)> {
pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}

/// To be applied on any item that has documentation comment, it will convert link to `slint:Foo` to the link from the
/// documentation map from link-data.json
#[proc_macro_attribute]
pub fn slint_doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
use syn::visit_mut::VisitMut;
let mut visitor = slint_doc::Visitor::new();
let mut item = syn::parse_macro_input!(item as syn::Item);
visitor.visit_item_mut(&mut item);
assert!(visitor.1, "No slint link found");
quote!(#item).into()
}

/// Same as `slint_doc` but for string literals instead of doc coments (useful for crate level documentation that cannot have an attribute)
#[proc_macro]
pub fn slint_doc_str(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::LitStr);
let mut doc = input.value();
let mut visitor = slint_doc::Visitor::new();
visitor.process_string(&mut doc);
assert!(visitor.1, "No slint link found");
quote!(#doc).into()
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,24 @@
},
"WinitBackend": {
"href": "/guide/backends-and-renderers/backend_winit/"
},
"translations": {
"href": "/guide/development/translations"
},
"backends_and_renderers": {
"href": "/guide/backends-and-renderers/backends_and_renderers/"
},
"globals": {
"href": "/guide/language/coding/globals/"
},
"quickstart": {
"href": "/tutorial/quickstart/"
},
"modules": {
"href": "/guide/language/coding/file/#modules"
},
"index": {
"href": "/"
}
}

}
97 changes: 97 additions & 0 deletions internal/core-macros/slint_doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

// Copyright © SixtyFPS GmbH <[email protected]>

pub struct Visitor(serde_json::Value, pub bool);

impl syn::visit_mut::VisitMut for Visitor {
fn visit_attribute_mut(&mut self, i: &mut syn::Attribute) {
if i.meta.path().is_ident("doc") {
if let syn::Meta::NameValue(syn::MetaNameValue {
value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. }),
..
}) = &mut i.meta
{
let mut doc = lit.value();
self.process_string(&mut doc);
*lit = syn::LitStr::new(&doc, lit.span());
}
}
}
}

impl Visitor {
pub fn new() -> Self {
let link_path = concat!(env!("CARGO_MANIFEST_DIR"), "/link-data.json");
let link_data = std::fs::read_to_string(link_path).expect("Failed to read link-data.json");
let link_data: serde_json::Value =
serde_json::from_str(&link_data).expect("Failed to parse link-data.json");
Self(link_data, false)
}

pub fn process_string(&mut self, doc: &mut String) {
const NEEDLE: &str = "slint:";
let mut begin = 0;
// search for all occurrences of "slint:foo" and replace it with the link from link-data.json
while let Some(pos) = doc[begin..].find(NEEDLE).map(|x| x + begin) {
if doc[pos..].starts_with("slint::") {
begin = pos + NEEDLE.len();
continue;
}
let end = doc[pos + NEEDLE.len()..]
.find([' ', '\n', ']', ')'])
.expect("Failed to find end of link");
let link = &doc[pos + NEEDLE.len()..][..end];
let dst = if let Some(rust_link) = link.strip_prefix("rust:") {
format!(
"https://releases.slint.dev/{}/docs/rust/{rust_link}",
env!("CARGO_PKG_VERSION"),
)
} else if let Some(dst) = self.0.get(link) {
let dst = dst
.get("href")
.expect("Missing href in link-data.json")
.as_str()
.expect("invalid string in link-data.json");
format!("https://releases.slint.dev/{}/docs/slint{dst}", env!("CARGO_PKG_VERSION"),)
} else {
panic!("Unknown link {}", link);
};
doc.replace_range(pos..pos + NEEDLE.len() + link.len(), &dst);
begin = pos + dst.len();
self.1 = true;
}
}
}

#[test]
fn test_slint_doc() {
let mut visitor = Visitor::new();

let mut string = r"
Test [SomeLink](slint:index)
Not in a link: slint:index xxx
slint::index is not a link
slint:index is a link
rust link: slint:rust:foobar
"
.to_owned();

visitor.process_string(&mut string);
assert!(visitor.1);
assert_eq!(
string,
format!(
r"
Test [SomeLink](https://releases.slint.dev/{0}/docs/slint/)
Not in a link: https://releases.slint.dev/{0}/docs/slint/ xxx
slint::index is not a link
https://releases.slint.dev/{0}/docs/slint/ is a link
rust link: https://releases.slint.dev/{0}/docs/rust/foobar
",
env!("CARGO_PKG_VERSION")
)
);
}
3 changes: 2 additions & 1 deletion internal/core/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ impl Window {

pub use crate::SharedString;

#[i_slint_core_macros::slint_doc]
/// This trait is used to obtain references to global singletons exported in `.slint`
/// markup. Alternatively, you can use [`ComponentHandle::global`] to obtain access.
///
Expand Down Expand Up @@ -734,7 +735,7 @@ pub use crate::SharedString;
/// Palette::get(&app).set_foreground_color(slint::Color::from_rgb_u8(255, 255, 255));
/// ```
///
#[doc = concat!("See also the [language documentation for global singletons](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/slint/src/reference/globals.html) for more information.")]
/// See also the [language documentation for global singletons](slint:globals) for more information.
///
/// **Note:** Only globals that are exported or re-exported from the main .slint file will
/// be exposed in the API
Expand Down
3 changes: 3 additions & 0 deletions internal/core/translations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,13 @@ fn index_for_locale(languages: &[&'static str]) -> Option<usize> {
})
}

#[i_slint_core_macros::slint_doc]
/// Select the current translation language when using bundled translations.
/// This function requires that the application's `.slint` file was compiled with bundled translations..
/// It must be called after creating the first component.
/// Returns `Ok` if the language was selected; [`SelectBundledTranslationError`] otherwise.
///
/// See also the [Translation documentation](slint:translations).
pub fn select_bundled_translation(language: &str) -> Result<(), SelectBundledTranslationError> {
crate::context::GLOBAL_CONTEXT.with(|ctx| {
let Some(ctx) = ctx.get() else {
Expand Down

0 comments on commit c0f346f

Please sign in to comment.