rustdoc-search: search backend with partitioned suffix tree

This commit is contained in:
Michael Howell 2024-11-22 12:58:20 -07:00
parent c018ae5389
commit 8511e40e72
146 changed files with 9090 additions and 5057 deletions

View file

@ -4812,6 +4812,7 @@ dependencies = [
"serde_json",
"sha2",
"smallvec",
"stringdex",
"tempfile",
"threadpool",
"tracing",
@ -5225,6 +5226,15 @@ dependencies = [
"quote",
]
[[package]]
name = "stringdex"
version = "0.0.1-alpha4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2841fd43df5b1ff1b042e167068a1fe9b163dc93041eae56ab2296859013a9a0"
dependencies = [
"stacker",
]
[[package]]
name = "strsim"
version = "0.11.1"

View file

@ -1 +1 @@
8.6.0
8.57.1

View file

@ -15,6 +15,7 @@ import os.path
import re
import shlex
from collections import namedtuple
from pathlib import Path
try:
from html.parser import HTMLParser
@ -242,6 +243,11 @@ class CachedFiles(object):
return self.last_path
def get_absolute_path(self, path):
if "*" in path:
paths = list(Path(self.root).glob(path))
if len(paths) != 1:
raise FailedCheck("glob path does not resolve to one file")
path = str(paths[0])
return os.path.join(self.root, path)
def get_file(self, path):

View file

@ -21,6 +21,7 @@ rustdoc-json-types = { path = "../rustdoc-json-types" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
smallvec = "1.8.1"
stringdex = { version = "0.0.1-alpha4" }
tempfile = "3"
threadpool = "1.8.1"
tracing = "0.1"

View file

@ -10,6 +10,7 @@ fn main() {
"static/css/normalize.css",
"static/js/main.js",
"static/js/search.js",
"static/js/stringdex.js",
"static/js/settings.js",
"static/js/src-script.js",
"static/js/storage.js",

View file

@ -1,6 +1,5 @@
use std::mem;
use rustc_ast::join_path_syms;
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
use rustc_hir::StabilityLevel;
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
@ -574,7 +573,6 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
clean::ItemKind::ImportItem(import) => import.source.did.unwrap_or(item_def_id),
_ => item_def_id,
};
let path = join_path_syms(parent_path);
let impl_id = if let Some(ParentStackItem::Impl { item_id, .. }) = cache.parent_stack.last() {
item_id.as_def_id()
} else {
@ -593,11 +591,11 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
ty: item.type_(),
defid: Some(defid),
name,
path,
module_path: parent_path.to_vec(),
desc,
parent: parent_did,
parent_idx: None,
exact_path: None,
exact_module_path: None,
impl_id,
search_type,
aliases,

View file

@ -4,7 +4,7 @@ use std::fmt;
use rustc_hir::def::{CtorOf, DefKind, MacroKinds};
use rustc_span::hygiene::MacroKind;
use serde::{Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use crate::clean;
@ -68,6 +68,52 @@ impl Serialize for ItemType {
}
}
impl<'de> Deserialize<'de> for ItemType {
fn deserialize<D>(deserializer: D) -> Result<ItemType, D::Error>
where
D: Deserializer<'de>,
{
struct ItemTypeVisitor;
impl<'de> de::Visitor<'de> for ItemTypeVisitor {
type Value = ItemType;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "an integer between 0 and 25")
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<ItemType, E> {
Ok(match v {
0 => ItemType::Keyword,
1 => ItemType::Primitive,
2 => ItemType::Module,
3 => ItemType::ExternCrate,
4 => ItemType::Import,
5 => ItemType::Struct,
6 => ItemType::Enum,
7 => ItemType::Function,
8 => ItemType::TypeAlias,
9 => ItemType::Static,
10 => ItemType::Trait,
11 => ItemType::Impl,
12 => ItemType::TyMethod,
13 => ItemType::Method,
14 => ItemType::StructField,
15 => ItemType::Variant,
16 => ItemType::Macro,
17 => ItemType::AssocType,
18 => ItemType::Constant,
19 => ItemType::AssocConst,
20 => ItemType::Union,
21 => ItemType::ForeignType,
23 => ItemType::ProcAttribute,
24 => ItemType::ProcDerive,
25 => ItemType::TraitAlias,
_ => return Err(E::missing_field("unknown number")),
})
}
}
deserializer.deserialize_any(ItemTypeVisitor)
}
}
impl<'a> From<&'a clean::Item> for ItemType {
fn from(item: &'a clean::Item) -> ItemType {
let kind = match &item.kind {
@ -198,6 +244,10 @@ impl ItemType {
pub(crate) fn is_adt(&self) -> bool {
matches!(self, ItemType::Struct | ItemType::Union | ItemType::Enum)
}
/// Keep this the same as isFnLikeTy in search.js
pub(crate) fn is_fn_like(&self) -> bool {
matches!(self, ItemType::Function | ItemType::Method | ItemType::TyMethod)
}
}
impl fmt::Display for ItemType {

View file

@ -27,6 +27,7 @@ pub(crate) struct Layout {
pub(crate) struct Page<'a> {
pub(crate) title: &'a str,
pub(crate) short_title: &'a str,
pub(crate) css_class: &'a str,
pub(crate) root_path: &'a str,
pub(crate) static_root_path: Option<&'a str>,

View file

@ -204,6 +204,18 @@ impl<'tcx> Context<'tcx> {
if !is_module {
title.push_str(it.name.unwrap().as_str());
}
let short_title;
let short_title = if is_module {
let module_name = self.current.last().unwrap();
short_title = if it.is_crate() {
format!("Crate {module_name}")
} else {
format!("Module {module_name}")
};
&short_title[..]
} else {
it.name.as_ref().unwrap().as_str()
};
if !it.is_primitive() && !it.is_keyword() {
if !is_module {
title.push_str(" in ");
@ -240,6 +252,7 @@ impl<'tcx> Context<'tcx> {
root_path: &self.root_path(),
static_root_path: self.shared.static_root_path.as_deref(),
title: &title,
short_title,
description: &desc,
resource_suffix: &self.shared.resource_suffix,
rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo),
@ -617,6 +630,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let shared = &self.shared;
let mut page = layout::Page {
title: "List of all items in this crate",
short_title: "All",
css_class: "mod sys",
root_path: "../",
static_root_path: shared.static_root_path.as_deref(),

View file

@ -130,11 +130,11 @@ pub(crate) struct IndexItem {
pub(crate) ty: ItemType,
pub(crate) defid: Option<DefId>,
pub(crate) name: Symbol,
pub(crate) path: String,
pub(crate) module_path: Vec<Symbol>,
pub(crate) desc: String,
pub(crate) parent: Option<DefId>,
pub(crate) parent_idx: Option<isize>,
pub(crate) exact_path: Option<String>,
pub(crate) parent_idx: Option<usize>,
pub(crate) exact_module_path: Option<Vec<Symbol>>,
pub(crate) impl_id: Option<DefId>,
pub(crate) search_type: Option<IndexItemFunctionType>,
pub(crate) aliases: Box<[Symbol]>,
@ -150,6 +150,19 @@ struct RenderType {
}
impl RenderType {
fn size(&self) -> usize {
let mut size = 1;
if let Some(generics) = &self.generics {
size += generics.iter().map(RenderType::size).sum::<usize>();
}
if let Some(bindings) = &self.bindings {
for (_, constraints) in bindings.iter() {
size += 1;
size += constraints.iter().map(RenderType::size).sum::<usize>();
}
}
size
}
// Types are rendered as lists of lists, because that's pretty compact.
// The contents of the lists are always integers in self-terminating hex
// form, handled by `RenderTypeId::write_to_string`, so no commas are
@ -191,6 +204,62 @@ impl RenderType {
write_optional_id(self.id, string);
}
}
fn read_from_bytes(string: &[u8]) -> (RenderType, usize) {
let mut i = 0;
if string[i] == b'{' {
i += 1;
let (id, offset) = RenderTypeId::read_from_bytes(&string[i..]);
i += offset;
let generics = if string[i] == b'{' {
i += 1;
let mut generics = Vec::new();
while string[i] != b'}' {
let (ty, offset) = RenderType::read_from_bytes(&string[i..]);
i += offset;
generics.push(ty);
}
assert!(string[i] == b'}');
i += 1;
Some(generics)
} else {
None
};
let bindings = if string[i] == b'{' {
i += 1;
let mut bindings = Vec::new();
while string[i] == b'{' {
i += 1;
let (binding, boffset) = RenderTypeId::read_from_bytes(&string[i..]);
i += boffset;
let mut bconstraints = Vec::new();
assert!(string[i] == b'{');
i += 1;
while string[i] != b'}' {
let (constraint, coffset) = RenderType::read_from_bytes(&string[i..]);
i += coffset;
bconstraints.push(constraint);
}
assert!(string[i] == b'}');
i += 1;
bindings.push((binding.unwrap(), bconstraints));
assert!(string[i] == b'}');
i += 1;
}
assert!(string[i] == b'}');
i += 1;
Some(bindings)
} else {
None
};
assert!(string[i] == b'}');
i += 1;
(RenderType { id, generics, bindings }, i)
} else {
let (id, offset) = RenderTypeId::read_from_bytes(string);
i += offset;
(RenderType { id, generics: None, bindings: None }, i)
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -212,7 +281,20 @@ impl RenderTypeId {
RenderTypeId::Index(idx) => (*idx).try_into().unwrap(),
_ => panic!("must convert render types to indexes before serializing"),
};
search_index::encode::write_vlqhex_to_string(id, string);
search_index::encode::write_signed_vlqhex_to_string(id, string);
}
fn read_from_bytes(string: &[u8]) -> (Option<RenderTypeId>, usize) {
let Some((value, offset)) = search_index::encode::read_signed_vlqhex_from_string(string)
else {
return (None, 0);
};
let value = isize::try_from(value).unwrap();
let ty = match value {
..0 => Some(RenderTypeId::Index(value)),
0 => None,
1.. => Some(RenderTypeId::Index(value - 1)),
};
(ty, offset)
}
}
@ -226,12 +308,64 @@ pub(crate) struct IndexItemFunctionType {
}
impl IndexItemFunctionType {
fn write_to_string<'a>(
&'a self,
string: &mut String,
backref_queue: &mut VecDeque<&'a IndexItemFunctionType>,
) {
assert!(backref_queue.len() <= 16);
fn size(&self) -> usize {
self.inputs.iter().map(RenderType::size).sum::<usize>()
+ self.output.iter().map(RenderType::size).sum::<usize>()
+ self
.where_clause
.iter()
.map(|constraints| constraints.iter().map(RenderType::size).sum::<usize>())
.sum::<usize>()
}
fn read_from_string_without_param_names(string: &[u8]) -> (IndexItemFunctionType, usize) {
let mut i = 0;
if string[i] == b'`' {
return (
IndexItemFunctionType {
inputs: Vec::new(),
output: Vec::new(),
where_clause: Vec::new(),
param_names: Vec::new(),
},
1,
);
}
assert_eq!(b'{', string[i]);
i += 1;
fn read_args_from_string(string: &[u8]) -> (Vec<RenderType>, usize) {
let mut i = 0;
let mut params = Vec::new();
if string[i] == b'{' {
// multiple params
i += 1;
while string[i] != b'}' {
let (ty, offset) = RenderType::read_from_bytes(&string[i..]);
i += offset;
params.push(ty);
}
i += 1;
} else if string[i] != b'}' {
let (tyid, offset) = RenderTypeId::read_from_bytes(&string[i..]);
params.push(RenderType { id: tyid, generics: None, bindings: None });
i += offset;
}
(params, i)
}
let (inputs, offset) = read_args_from_string(&string[i..]);
i += offset;
let (output, offset) = read_args_from_string(&string[i..]);
i += offset;
let mut where_clause = Vec::new();
while string[i] != b'}' {
let (constraint, offset) = read_args_from_string(&string[i..]);
i += offset;
where_clause.push(constraint);
}
assert_eq!(b'}', string[i], "{} {}", String::from_utf8_lossy(&string), i);
i += 1;
(IndexItemFunctionType { inputs, output, where_clause, param_names: Vec::new() }, i)
}
fn write_to_string_without_param_names<'a>(&'a self, string: &mut String) {
// If we couldn't figure out a type, just write 0,
// which is encoded as `` ` `` (see RenderTypeId::write_to_string).
let has_missing = self
@ -241,18 +375,7 @@ impl IndexItemFunctionType {
.any(|i| i.id.is_none() && i.generics.is_none());
if has_missing {
string.push('`');
} else if let Some(idx) = backref_queue.iter().position(|other| *other == self) {
// The backref queue has 16 items, so backrefs use
// a single hexit, disjoint from the ones used for numbers.
string.push(
char::try_from('0' as u32 + u32::try_from(idx).unwrap())
.expect("last possible value is '?'"),
);
} else {
backref_queue.push_front(self);
if backref_queue.len() > 16 {
backref_queue.pop_back();
}
string.push('{');
match &self.inputs[..] {
[one] if one.generics.is_none() && one.bindings.is_none() => {

View file

@ -35,6 +35,7 @@ use crate::html::format::{
visibility_print_with_space,
};
use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
use crate::html::render::sidebar::filters;
use crate::html::render::{document_full, document_item_info};
use crate::html::url_parts_builder::UrlPartsBuilder;

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,4 @@
use base64::prelude::*;
pub(crate) fn write_vlqhex_to_string(n: i32, string: &mut String) {
pub(crate) fn write_signed_vlqhex_to_string(n: i32, string: &mut String) {
let (sign, magnitude): (bool, u32) =
if n >= 0 { (false, n.try_into().unwrap()) } else { (true, (-n).try_into().unwrap()) };
// zig-zag encoding
@ -37,206 +35,66 @@ pub(crate) fn write_vlqhex_to_string(n: i32, string: &mut String) {
}
}
// Used during bitmap encoding
enum Container {
/// number of ones, bits
Bits(Box<[u64; 1024]>),
/// list of entries
Array(Vec<u16>),
/// list of (start, len-1)
Run(Vec<(u16, u16)>),
pub fn read_signed_vlqhex_from_string(string: &[u8]) -> Option<(i32, usize)> {
let mut n = 0i32;
let mut i = 0;
while let Some(&c) = string.get(i) {
i += 1;
n = (n << 4) | i32::from(c & 0xF);
if c >= 96 {
// zig-zag encoding
let (sign, magnitude) = (n & 1, n >> 1);
let value = if sign == 0 { 1 } else { -1 } * magnitude;
return Some((value, i));
}
}
None
}
impl Container {
fn popcount(&self) -> u32 {
match self {
Container::Bits(bits) => bits.iter().copied().map(|x| x.count_ones()).sum(),
Container::Array(array) => {
array.len().try_into().expect("array can't be bigger than 2**32")
}
Container::Run(runs) => {
runs.iter().copied().map(|(_, lenm1)| u32::from(lenm1) + 1).sum()
}
pub fn write_postings_to_string(postings: &[Vec<u32>], buf: &mut Vec<u8>) {
for list in postings {
if list.is_empty() {
buf.push(0);
continue;
}
}
fn push(&mut self, value: u16) {
match self {
Container::Bits(bits) => bits[value as usize >> 6] |= 1 << (value & 0x3F),
Container::Array(array) => {
array.push(value);
if array.len() >= 4096 {
let array = std::mem::take(array);
*self = Container::Bits(Box::new([0; 1024]));
for value in array {
self.push(value);
}
}
let len_before = buf.len();
stringdex::internals::encode::write_bitmap_to_bytes(&list, &mut *buf).unwrap();
let len_after = buf.len();
if len_after - len_before > 1 + (4 * list.len()) && list.len() < 0x3a {
buf.truncate(len_before);
buf.push(list.len() as u8);
for &item in list {
buf.push(item as u8);
buf.push((item >> 8) as u8);
buf.push((item >> 16) as u8);
buf.push((item >> 24) as u8);
}
Container::Run(runs) => {
if let Some(r) = runs.last_mut()
&& r.0 + r.1 + 1 == value
{
r.1 += 1;
} else {
runs.push((value, 0));
}
}
}
}
fn try_make_run(&mut self) -> bool {
match self {
Container::Bits(bits) => {
let mut r: u64 = 0;
for (i, chunk) in bits.iter().copied().enumerate() {
let next_chunk =
i.checked_add(1).and_then(|i| bits.get(i)).copied().unwrap_or(0);
r += !chunk & u64::from((chunk << 1).count_ones());
r += !next_chunk & u64::from((chunk >> 63).count_ones());
}
if (2 + 4 * r) >= 8192 {
return false;
}
let bits = std::mem::replace(bits, Box::new([0; 1024]));
*self = Container::Run(Vec::new());
for (i, bits) in bits.iter().copied().enumerate() {
if bits == 0 {
continue;
}
for j in 0..64 {
let value = (u16::try_from(i).unwrap() << 6) | j;
if bits & (1 << j) != 0 {
self.push(value);
}
}
}
true
}
Container::Array(array) if array.len() <= 5 => false,
Container::Array(array) => {
let mut r = 0;
let mut prev = None;
for value in array.iter().copied() {
if value.checked_sub(1) != prev {
r += 1;
}
prev = Some(value);
}
if 2 + 4 * r >= 2 * array.len() + 2 {
return false;
}
let array = std::mem::take(array);
*self = Container::Run(Vec::new());
for value in array {
self.push(value);
}
true
}
Container::Run(_) => true,
}
}
}
// checked against roaring-rs in
// https://gitlab.com/notriddle/roaring-test
pub(crate) fn write_bitmap_to_bytes(
domain: &[u32],
mut out: impl std::io::Write,
) -> std::io::Result<()> {
// https://arxiv.org/pdf/1603.06549.pdf
let mut keys = Vec::<u16>::new();
let mut containers = Vec::<Container>::new();
let mut key: u16;
let mut domain_iter = domain.iter().copied().peekable();
let mut has_run = false;
while let Some(entry) = domain_iter.next() {
key = (entry >> 16).try_into().expect("shifted off the top 16 bits, so it should fit");
let value: u16 = (entry & 0x00_00_FF_FF).try_into().expect("AND 16 bits, so it should fit");
let mut container = Container::Array(vec![value]);
while let Some(entry) = domain_iter.peek().copied() {
let entry_key: u16 =
(entry >> 16).try_into().expect("shifted off the top 16 bits, so it should fit");
if entry_key != key {
break;
pub fn read_postings_from_string(postings: &mut Vec<Vec<u32>>, mut buf: &[u8]) {
use stringdex::internals::decode::RoaringBitmap;
while let Some(&c) = buf.get(0) {
if c < 0x3a {
buf = &buf[1..];
let mut slot = Vec::new();
for _ in 0..c {
slot.push(
(buf[0] as u32)
| ((buf[1] as u32) << 8)
| ((buf[2] as u32) << 16)
| ((buf[3] as u32) << 24),
);
buf = &buf[4..];
}
domain_iter.next().expect("peeking just succeeded");
container
.push((entry & 0x00_00_FF_FF).try_into().expect("AND 16 bits, so it should fit"));
}
keys.push(key);
has_run = container.try_make_run() || has_run;
containers.push(container);
}
// https://github.com/RoaringBitmap/RoaringFormatSpec
const SERIAL_COOKIE_NO_RUNCONTAINER: u32 = 12346;
const SERIAL_COOKIE: u32 = 12347;
const NO_OFFSET_THRESHOLD: u32 = 4;
let size: u32 = containers.len().try_into().unwrap();
let start_offset = if has_run {
out.write_all(&u32::to_le_bytes(SERIAL_COOKIE | ((size - 1) << 16)))?;
for set in containers.chunks(8) {
let mut b = 0;
for (i, container) in set.iter().enumerate() {
if matches!(container, &Container::Run(..)) {
b |= 1 << i;
}
}
out.write_all(&[b])?;
}
if size < NO_OFFSET_THRESHOLD {
4 + 4 * size + size.div_ceil(8)
postings.push(slot);
} else {
4 + 8 * size + size.div_ceil(8)
}
} else {
out.write_all(&u32::to_le_bytes(SERIAL_COOKIE_NO_RUNCONTAINER))?;
out.write_all(&u32::to_le_bytes(containers.len().try_into().unwrap()))?;
4 + 4 + 4 * size + 4 * size
};
for (&key, container) in keys.iter().zip(&containers) {
// descriptive header
let key: u32 = key.into();
let count: u32 = container.popcount() - 1;
out.write_all(&u32::to_le_bytes((count << 16) | key))?;
}
if !has_run || size >= NO_OFFSET_THRESHOLD {
// offset header
let mut starting_offset = start_offset;
for container in &containers {
out.write_all(&u32::to_le_bytes(starting_offset))?;
starting_offset += match container {
Container::Bits(_) => 8192u32,
Container::Array(array) => u32::try_from(array.len()).unwrap() * 2,
Container::Run(runs) => 2 + u32::try_from(runs.len()).unwrap() * 4,
};
let (bitmap, consumed_bytes_len) =
RoaringBitmap::from_bytes(buf).unwrap_or_else(|| (RoaringBitmap::default(), 0));
assert_ne!(consumed_bytes_len, 0);
postings.push(bitmap.to_vec());
buf = &buf[consumed_bytes_len..];
}
}
for container in &containers {
match container {
Container::Bits(bits) => {
for chunk in bits.iter() {
out.write_all(&u64::to_le_bytes(*chunk))?;
}
}
Container::Array(array) => {
for value in array.iter() {
out.write_all(&u16::to_le_bytes(*value))?;
}
}
Container::Run(runs) => {
out.write_all(&u16::to_le_bytes(runs.len().try_into().unwrap()))?;
for (start, lenm1) in runs.iter().copied() {
out.write_all(&u16::to_le_bytes(start))?;
out.write_all(&u16::to_le_bytes(lenm1))?;
}
}
}
}
Ok(())
}
pub(crate) fn bitmap_to_string(domain: &[u32]) -> String {
let mut buf = Vec::new();
let mut strbuf = String::new();
write_bitmap_to_bytes(domain, &mut buf).unwrap();
BASE64_STANDARD.encode_string(&buf, &mut strbuf);
strbuf
}

View file

@ -65,17 +65,17 @@ pub(crate) fn write_shared(
// Write shared runs within a flock; disable thread dispatching of IO temporarily.
let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
let SerializedSearchIndex { index, desc } = build_index(krate, &mut cx.shared.cache, tcx);
write_search_desc(cx, krate, &desc)?; // does not need to be merged
let search_index =
build_index(krate, &mut cx.shared.cache, tcx, &cx.dst, &cx.shared.resource_suffix)?;
let crate_name = krate.name(cx.tcx());
let crate_name = crate_name.as_str(); // rand
let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
let info = CrateInfo {
version: CrateInfoVersion::V1,
version: CrateInfoVersion::V2,
src_files_js: SourcesPart::get(cx, &crate_name_json)?,
search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
search_index,
all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
@ -141,7 +141,7 @@ pub(crate) fn write_not_crate_specific(
resource_suffix: &str,
include_sources: bool,
) -> Result<(), Error> {
write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
Ok(())
}
@ -151,13 +151,18 @@ fn write_rendered_cross_crate_info(
dst: &Path,
opt: &RenderOptions,
include_sources: bool,
resource_suffix: &str,
) -> Result<(), Error> {
let m = &opt.should_merge;
if opt.should_emit_crate() {
if include_sources {
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
}
write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, crates, m)?;
crates
.iter()
.fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
.sort()
.write_to(dst, resource_suffix)?;
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
}
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
@ -215,38 +220,12 @@ fn write_static_files(
Ok(())
}
/// Write the search description shards to disk
fn write_search_desc(
cx: &mut Context<'_>,
krate: &Crate,
search_desc: &[(usize, String)],
) -> Result<(), Error> {
let crate_name = krate.name(cx.tcx()).to_string();
let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap();
let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]);
if path.exists() {
try_err!(fs::remove_dir_all(&path), &path);
}
for (i, (_, part)) in search_desc.iter().enumerate() {
let filename = static_files::suffix_path(
&format!("{crate_name}-desc-{i}-.js"),
&cx.shared.resource_suffix,
);
let path = path.join(filename);
let part = OrderedJson::serialize(part).unwrap();
let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})");
create_parents(&path)?;
try_err!(fs::write(&path, part), &path);
}
Ok(())
}
/// Contains pre-rendered contents to insert into the CCI template
#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct CrateInfo {
version: CrateInfoVersion,
src_files_js: PartsAndLocations<SourcesPart>,
search_index_js: PartsAndLocations<SearchIndexPart>,
search_index: SerializedSearchIndex,
all_crates: PartsAndLocations<AllCratesPart>,
crates_index: PartsAndLocations<CratesIndexPart>,
trait_impl: PartsAndLocations<TraitAliasPart>,
@ -277,7 +256,7 @@ impl CrateInfo {
/// to provide better diagnostics about including an invalid file.
#[derive(Serialize, Deserialize, Clone, Debug)]
enum CrateInfoVersion {
V1,
V2,
}
/// Paths (relative to the doc root) and their pre-merge contents
@ -331,36 +310,6 @@ trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct SearchIndex;
type SearchIndexPart = Part<SearchIndex, EscapedJson>;
impl CciPart for SearchIndexPart {
type FileFormat = sorted_template::Js;
fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
&crate_info.search_index_js
}
}
impl SearchIndexPart {
fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
SortedTemplate::from_before_after(
r"var searchIndex = new Map(JSON.parse('[",
r"]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);",
)
}
fn get(
search_index: OrderedJson,
resource_suffix: &str,
) -> Result<PartsAndLocations<Self>, Error> {
let path = suffix_path("search-index.js", resource_suffix);
let search_index = EscapedJson::from(search_index);
Ok(PartsAndLocations::with(path, search_index))
}
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
struct AllCrates;
type AllCratesPart = Part<AllCrates, OrderedJson>;
@ -426,6 +375,7 @@ impl CratesIndexPart {
fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
let page = layout::Page {
title: "Index of crates",
short_title: "Crates",
css_class: "mod sys",
root_path: "./",
static_root_path: cx.shared.static_root_path.as_deref(),

View file

@ -29,14 +29,6 @@ fn sources_template() {
assert_eq!(but_last_line(&template.to_string()), r#"createSrcSidebar('["u","v"]');"#);
}
#[test]
fn sources_parts() {
let parts =
SearchIndexPart::get(OrderedJson::serialize(["foo", "bar"]).unwrap(), "suffix").unwrap();
assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js"));
assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#);
}
#[test]
fn all_crates_template() {
let mut template = AllCratesPart::blank();
@ -54,31 +46,6 @@ fn all_crates_parts() {
assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#);
}
#[test]
fn search_index_template() {
let mut template = SearchIndexPart::blank();
assert_eq!(
but_last_line(&template.to_string()),
r"var searchIndex = new Map(JSON.parse('[]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);"
);
template.append(EscapedJson::from(OrderedJson::serialize([1, 2]).unwrap()).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r"var searchIndex = new Map(JSON.parse('[[1,2]]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);"
);
template.append(EscapedJson::from(OrderedJson::serialize([4, 3]).unwrap()).to_string());
assert_eq!(
but_last_line(&template.to_string()),
r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]'));
if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
else if (window.initSearch) window.initSearch(searchIndex);"
);
}
#[test]
fn crates_index_part() {
let external_crates = ["bar".to_string(), "baz".to_string()];

View file

@ -230,6 +230,7 @@ impl SourceCollector<'_, '_> {
);
let page = layout::Page {
title: &title,
short_title: &src_fname.to_string_lossy(),
css_class: "src",
root_path: &root_path,
static_root_path: shared.static_root_path.as_deref(),

View file

@ -258,6 +258,17 @@ h1, h2, h3, h4 {
padding-bottom: 6px;
margin-bottom: 15px;
}
.search-results-main-heading {
grid-template-areas:
"main-heading-breadcrumbs main-heading-placeholder"
"main-heading-breadcrumbs main-heading-toolbar "
"main-heading-h1 main-heading-toolbar ";
}
.search-results-main-heading nav.sub {
grid-area: main-heading-h1;
align-items: end;
margin: 4px 0 8px 0;
}
.rustdoc-breadcrumbs {
grid-area: main-heading-breadcrumbs;
line-height: 1.25;
@ -265,6 +276,16 @@ h1, h2, h3, h4 {
position: relative;
z-index: 1;
}
.search-switcher {
grid-area: main-heading-breadcrumbs;
line-height: 1.5;
display: flex;
color: var(--main-color);
align-items: baseline;
white-space: nowrap;
padding-top: 8px;
min-height: 34px;
}
.rustdoc-breadcrumbs a {
padding: 5px 0 7px;
}
@ -305,7 +326,7 @@ h4.code-header {
#crate-search,
h1, h2, h3, h4, h5, h6,
.sidebar,
.mobile-topbar,
rustdoc-topbar,
.search-input,
.search-results .result-name,
.item-table dt > a,
@ -317,6 +338,7 @@ rustdoc-toolbar,
summary.hideme,
.scraped-example-list,
.rustdoc-breadcrumbs,
.search-switcher,
/* This selector is for the items listed in the "all items" page. */
ul.all-items {
font-family: "Fira Sans", Arial, NanumBarunGothic, sans-serif;
@ -329,7 +351,7 @@ a.anchor,
.rust a,
.sidebar h2 a,
.sidebar h3 a,
.mobile-topbar h2 a,
rustdoc-topbar h2 a,
h1 a,
.search-results a,
.search-results li,
@ -616,7 +638,7 @@ img {
color: var(--sidebar-resizer-active);
}
.sidebar, .mobile-topbar, .sidebar-menu-toggle,
.sidebar, rustdoc-topbar, .sidebar-menu-toggle,
#src-sidebar {
background-color: var(--sidebar-background-color);
}
@ -857,7 +879,7 @@ ul.block, .block li, .block ul {
margin-bottom: 1rem;
}
.mobile-topbar {
rustdoc-topbar {
display: none;
}
@ -1098,16 +1120,15 @@ div.where {
nav.sub {
flex-grow: 1;
flex-flow: row nowrap;
margin: 4px 0 0 0;
display: flex;
align-items: center;
align-items: start;
margin-top: 4px;
}
.search-form {
position: relative;
display: flex;
height: 34px;
flex-grow: 1;
margin-bottom: 4px;
}
.src nav.sub {
margin: 0 0 -10px 0;
@ -1208,27 +1229,14 @@ table,
margin-left: 0;
}
.search-results-title {
margin-top: 0;
white-space: nowrap;
/* flex layout allows shrinking the <select> appropriately if it becomes too large */
display: flex;
/* make things look like in a line, despite the fact that we're using a layout
with boxes (i.e. from the flex layout) */
align-items: baseline;
}
.search-results-title + .sub-heading {
color: var(--main-color);
display: flex;
align-items: baseline;
white-space: nowrap;
}
#crate-search-div {
/* ensures that 100% in properties of #crate-search-div:after
are relative to the size of this div */
position: relative;
/* allows this div (and with it the <select>-element "#crate-search") to be shrunk */
min-width: 0;
/* keep label text for switcher from moving down when this appears */
margin-top: -1px;
}
#crate-search {
padding: 0 23px 0 4px;
@ -1294,6 +1302,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
flex-grow: 1;
background-color: var(--button-background-color);
color: var(--search-color);
max-width: 100%;
}
.search-input:focus {
border-color: var(--search-input-focused-border-color);
@ -1459,14 +1468,14 @@ so that we can apply CSS-filters to change the arrow color in themes */
}
#settings.popover {
--popover-arrow-offset: 202px;
--popover-arrow-offset: 196px;
top: calc(100% - 16px);
}
/* use larger max-width for help popover, but not for help.html */
#help.popover {
max-width: 600px;
--popover-arrow-offset: 118px;
--popover-arrow-offset: 115px;
top: calc(100% - 16px);
}
@ -1929,10 +1938,12 @@ a.tooltip:hover::after {
color: inherit;
}
#search-tabs button:not(.selected) {
--search-tab-button-background: var(--search-tab-button-not-selected-background);
background-color: var(--search-tab-button-not-selected-background);
border-top-color: var(--search-tab-button-not-selected-border-top-color);
}
#search-tabs button:hover, #search-tabs button.selected {
--search-tab-button-background: var(--search-tab-button-selected-background);
background-color: var(--search-tab-button-selected-background);
border-top-color: var(--search-tab-button-selected-border-top-color);
}
@ -1941,6 +1952,73 @@ a.tooltip:hover::after {
font-size: 1rem;
font-variant-numeric: tabular-nums;
color: var(--search-tab-title-count-color);
position: relative;
}
#search-tabs .count.loading {
color: transparent;
}
.search-form.loading {
--search-tab-button-background: var(--button-background-color);
}
#search-tabs .count.loading::before,
.search-form.loading::before
{
width: 16px;
height: 16px;
border-radius: 16px;
background: radial-gradient(
var(--search-tab-button-background) 0 50%,
transparent 50% 100%
), conic-gradient(
var(--code-highlight-kw-color) 0deg 30deg,
var(--code-highlight-prelude-color) 30deg 60deg,
var(--code-highlight-number-color) 90deg 120deg,
var(--code-highlight-lifetime-color ) 120deg 150deg,
var(--code-highlight-comment-color) 150deg 180deg,
var(--code-highlight-self-color) 180deg 210deg,
var(--code-highlight-attribute-color) 210deg 240deg,
var(--code-highlight-literal-color) 210deg 240deg,
var(--code-highlight-macro-color) 240deg 270deg,
var(--code-highlight-question-mark-color) 270deg 300deg,
var(--code-highlight-prelude-val-color) 300deg 330deg,
var(--code-highlight-doc-comment-color) 330deg 360deg
);
content: "";
position: absolute;
left: 2px;
top: 2px;
animation: rotating 1.25s linear infinite;
}
#search-tabs .count.loading::after,
.search-form.loading::after
{
width: 18px;
height: 18px;
border-radius: 18px;
background: conic-gradient(
var(--search-tab-button-background) 0deg 180deg,
transparent 270deg 360deg
);
content: "";
position: absolute;
left: 1px;
top: 1px;
animation: rotating 0.66s linear infinite;
}
.search-form.loading::before {
left: auto;
right: 9px;
top: 8px;
}
.search-form.loading::after {
left: auto;
right: 8px;
top: 8px;
}
#search .error code {
@ -1974,7 +2052,7 @@ a.tooltip:hover::after {
border-bottom: 1px solid var(--border-color);
}
#settings-menu, #help-button, button#toggle-all-docs {
#search-button, .settings-menu, .help-menu, button#toggle-all-docs {
margin-left: var(--button-left-margin);
display: flex;
line-height: 1.25;
@ -1989,69 +2067,100 @@ a.tooltip:hover::after {
display: flex;
margin-right: 4px;
position: fixed;
margin-top: 25px;
left: 6px;
height: 34px;
width: 34px;
z-index: calc(var(--desktop-sidebar-z-index) + 1);
}
.hide-sidebar #sidebar-button {
left: 6px;
background-color: var(--main-background-color);
z-index: 1;
}
.src #sidebar-button {
margin-top: 0;
top: 8px;
left: 8px;
z-index: calc(var(--desktop-sidebar-z-index) + 1);
border-color: var(--border-color);
}
.hide-sidebar .src #sidebar-button {
position: static;
}
#settings-menu > a, #help-button > a, #sidebar-button > a, button#toggle-all-docs {
#search-button > a,
.settings-menu > a,
.help-menu > a,
#sidebar-button > a,
button#toggle-all-docs {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
#settings-menu > a, #help-button > a, button#toggle-all-docs {
#search-button > a,
.settings-menu > a,
.help-menu > a,
button#toggle-all-docs {
border: 1px solid transparent;
border-radius: var(--button-border-radius);
color: var(--main-color);
}
#settings-menu > a, #help-button > a, button#toggle-all-docs {
#search-button > a, .settings-menu > a, .help-menu > a, button#toggle-all-docs {
width: 80px;
border-radius: var(--toolbar-button-border-radius);
}
#settings-menu > a, #help-button > a {
#search-button > a, .settings-menu > a, .help-menu > a {
min-width: 0;
}
#sidebar-button > a {
background-color: var(--sidebar-background-color);
border: solid 1px transparent;
border-radius: var(--button-border-radius);
background-color: var(--button-background-color);
width: 33px;
}
#sidebar-button > a:hover, #sidebar-button > a:focus-visible {
background-color: var(--main-background-color);
.src #sidebar-button > a {
background-color: var(--sidebar-background-color);
border-color: var(--border-color);
}
#settings-menu > a:hover, #settings-menu > a:focus-visible,
#help-button > a:hover, #help-button > a:focus-visible,
#search-button > a:hover, #search-button > a:focus-visible,
.settings-menu > a:hover, .settings-menu > a:focus-visible,
.help-menu > a:hover, #help-menu > a:focus-visible,
#sidebar-button > a:hover, #sidebar-button > a:focus-visible,
#copy-path:hover, #copy-path:focus-visible,
button#toggle-all-docs:hover, button#toggle-all-docs:focus-visible {
border-color: var(--settings-button-border-focus);
text-decoration: none;
}
#settings-menu > a::before {
#search-button > a::before {
/* Magnifying glass */
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
width="18" height="18" viewBox="0 0 16 16">\
<circle r="5" cy="7" cx="7" style="fill:none;stroke:black;stroke-width:3"/><path \
d="M14.5,14.5 12,12" style="fill:none;stroke:black;stroke-width:3;stroke-linecap:round">\
</path><desc>Search</desc>\
</svg>');
width: 18px;
height: 18px;
filter: var(--settings-menu-filter);
}
.settings-menu > a::before {
/* Wheel <https://www.svgrepo.com/svg/384069/settings-cog-gear> */
content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\
<path d="M10.25,6c0-0.1243286-0.0261841-0.241333-0.0366211-0.362915l1.6077881-1.5545654l\
-1.25-2.1650391 c0,0-1.2674561,0.3625488-2.1323853,0.6099854c-0.2034912-0.1431885-0.421875\
-0.2639771-0.6494751-0.3701782L7.25,0h-2.5 c0,0-0.3214111,1.2857666-0.5393066,2.1572876\
C3.9830933,2.2634888,3.7647095,2.3842773,3.5612183,2.5274658L1.428833,1.9174805 \
l-1.25,2.1650391c0,0,0.9641113,0.9321899,1.6077881,1.5545654C1.7761841,5.758667,\
1.75,5.8756714,1.75,6 s0.0261841,0.241333,0.0366211,0.362915L0.178833,7.9174805l1.25,\
2.1650391l2.1323853-0.6099854 c0.2034912,0.1432495,0.421875,0.2639771,0.6494751,0.3701782\
L4.75,12h2.5l0.5393066-2.1572876 c0.2276001-0.1062012,0.4459839-0.2269287,0.6494751\
-0.3701782l2.1323853,0.6099854l1.25-2.1650391L10.2133789,6.362915 C10.2238159,6.241333,\
10.25,6.1243286,10.25,6z M6,7.5C5.1715698,7.5,4.5,6.8284302,4.5,6S5.1715698,4.5,6,4.5S7.5\
,5.1715698,7.5,6 S6.8284302,7.5,6,7.5z" fill="black"/></svg>');
<path d="m4.75 0s-0.32117 1.286-0.53906 2.1576c-0.2276 0.1062-0.44625 \
0.2266-0.64974 0.36979l-2.1328-0.60938-1.25 2.1641s0.9644 0.93231 1.6081 1.5547c-0.010437 \
0.12158-0.036458 0.23895-0.036458 0.36328s0.026021 0.2417 0.036458 0.36328l-1.6081 \
1.5547 1.25 2.1641 2.1328-0.60937c0.20349 0.14325 0.42214 0.26359 0.64974 0.36979l0.53906 \
2.1576h2.5l0.53906-2.1576c0.2276-0.1062 0.44625-0.22654 0.64974-0.36979l2.1328 0.60937 \
1.25-2.1641-1.6081-1.5547c0.010437-0.12158 0.036458-0.23895 \
0.036458-0.36328s-0.02602-0.2417-0.03646-0.36328l1.6081-1.5547-1.25-2.1641s-1.2679 \
0.36194-2.1328 0.60938c-0.20349-0.14319-0.42214-0.26359-0.64974-0.36979l-0.53906-2.1576\
zm1.25 2.5495c1.9058-2.877e-4 3.4508 1.5447 3.4505 3.4505 2.877e-4 1.9058-1.5447 3.4508-3.4505 \
3.4505-1.9058 2.877e-4 -3.4508-1.5447-3.4505-3.4505-2.877e-4 -1.9058 1.5447-3.4508 \
3.4505-3.4505z" fill="black"/>\
<circle cx="6" cy="6" r="1.75" fill="none" stroke="black" stroke-width="1"/></svg>');
width: 18px;
height: 18px;
filter: var(--settings-menu-filter);
@ -2067,36 +2176,51 @@ button#toggle-all-docs::before {
filter: var(--settings-menu-filter);
}
button#toggle-all-docs.will-expand::before {
/* Custom arrow icon */
content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\
<path d="M2,5l4,-4l4,4M2,7l4,4l4,-4" stroke="black" fill="none" stroke-width="2px"/></svg>');
}
#help-button > a::before {
/* Question mark with circle */
content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg" fill="none">\
<circle r="5.25" cx="6" cy="6" stroke-width="1.25" stroke="black"/>\
<text x="6" y="7" style="font:8px sans-serif;font-weight:1000" text-anchor="middle" \
dominant-baseline="middle" fill="black">?</text></svg>');
.help-menu > a::before {
/* Question mark with "circle" */
content: url('data:image/svg+xml,\
<svg width="18" height="18" enable-background="new 0 0 12 12" fill="none" \
version="1.1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> \
<path d="m6.007 0.6931c2.515 0 5.074 1.908 5.074 5.335 0 3.55-2.567 5.278-5.088 \
5.278-2.477 0-5.001-1.742-5.001-5.3 0-3.38 2.527-5.314 5.014-5.314z" stroke="black" \
stroke-width="1.5"/>\
<path d="m5.999 7.932c0.3111 0 0.7062 0.2915 0.7062 0.7257 0 0.5458-0.3951 \
0.8099-0.7081 0.8099-0.2973 0-0.7023-0.266-0.7023-0.7668 0-0.4695 0.3834-0.7688 \
0.7042-0.7688z" fill="black"/>\
<path d="m4.281 3.946c0.0312-0.03057 0.06298-0.06029 0.09528-0.08916 0.4833-0.432 1.084-0.6722 \
1.634-0.6722 1.141 0 1.508 1.043 1.221 1.621-0.2753 0.5542-1.061 0.5065-1.273 \
1.595-0.05728 0.2939 0.0134 0.9812 0.0134 1.205" fill="none" stroke="black" \
stroke-width="1.25"/>\
</svg>');
width: 18px;
height: 18px;
filter: var(--settings-menu-filter);
}
/* design hack to cope with "Help" being far shorter than "Settings" etc */
.help-menu > a {
width: 74px;
}
.help-menu > a > .label {
padding-right: 1px;
}
#toggle-all-docs:not(.will-expand) > .label {
padding-left: 1px;
}
#search-button > a::before,
button#toggle-all-docs::before,
#help-button > a::before,
#settings-menu > a::before {
.help-menu > a::before,
.settings-menu > a::before {
filter: var(--settings-menu-filter);
margin: 8px;
}
@media not (pointer: coarse) {
#search-button > a:hover::before,
button#toggle-all-docs:hover::before,
#help-button > a:hover::before,
#settings-menu > a:hover::before {
.help-menu > a:hover::before,
.settings-menu > a:hover::before {
filter: var(--settings-menu-hover-filter);
}
}
@ -2122,9 +2246,9 @@ rustdoc-toolbar span.label {
/* sidebar resizer image */
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" \
fill="none" stroke="black">\
<rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
<circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
<path d="m7.6121 3v16 M5.375 7.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>');
<rect x="1" y="2" width="20" height="18" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
<circle cx="4.375" cy="5.375" r="1" stroke-width=".75"/>\
<path d="m7.6121 4v14 M5.375 8.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>');
width: 22px;
height: 22px;
}
@ -2137,7 +2261,8 @@ rustdoc-toolbar span.label {
margin-left: 10px;
padding: 0;
padding-left: 2px;
border: 0;
border: solid 1px transparent;
border-radius: var(--button-border-radius);
font-size: 0;
}
#copy-path::before {
@ -2159,7 +2284,7 @@ rustdoc-toolbar span.label {
transform: rotate(360deg);
}
}
#settings-menu.rotate > a img {
.settings-menu.rotate > a img {
animation: rotating 2s linear infinite;
}
@ -2402,6 +2527,9 @@ However, it's not needed with smaller screen width because the doc/code block is
opacity: 0.75;
filter: var(--mobile-sidebar-menu-filter);
}
.src #sidebar-button > a:hover {
background: var(--main-background-color);
}
.sidebar-menu-toggle:hover::before,
.sidebar-menu-toggle:active::before,
.sidebar-menu-toggle:focus::before {
@ -2410,8 +2538,8 @@ However, it's not needed with smaller screen width because the doc/code block is
/* Media Queries */
/* Make sure all the buttons line wrap at the same time */
@media (max-width: 850px) {
/* Make sure all the buttons line wrap at the same time */
#search-tabs .count {
display: block;
}
@ -2421,6 +2549,81 @@ However, it's not needed with smaller screen width because the doc/code block is
.side-by-side > div {
width: auto;
}
/* Text label takes up too much space at this size. */
.main-heading {
grid-template-areas:
"main-heading-breadcrumbs main-heading-toolbar"
"main-heading-h1 main-heading-toolbar"
"main-heading-sub-heading main-heading-toolbar";
}
.search-results-main-heading {
display: grid;
grid-template-areas:
"main-heading-breadcrumbs main-heading-toolbar"
"main-heading-breadcrumbs main-heading-toolbar"
"main-heading-h1 main-heading-toolbar";
}
rustdoc-toolbar {
margin-top: -10px;
display: grid;
grid-template-areas:
"x settings help"
"search summary summary";
grid-template-rows: 35px 1fr;
}
.search-results-main-heading rustdoc-toolbar {
display: grid;
grid-template-areas:
"settings help"
"search search";
}
.search-results-main-heading #toggle-all-docs {
display: none;
}
rustdoc-toolbar .settings-menu span.label,
rustdoc-toolbar .help-menu span.label
{
display: none;
}
rustdoc-toolbar .settings-menu {
grid-area: settings;
}
rustdoc-toolbar .help-menu {
grid-area: help;
}
rustdoc-toolbar .settings-menu {
grid-area: settings;
}
rustdoc-toolbar #search-button {
grid-area: search;
}
rustdoc-toolbar #toggle-all-docs {
grid-area: summary;
}
rustdoc-toolbar .settings-menu,
rustdoc-toolbar .help-menu {
height: 35px;
}
rustdoc-toolbar .settings-menu > a,
rustdoc-toolbar .help-menu > a {
border-radius: 2px;
text-align: center;
width: 34px;
padding: 5px 0;
}
rustdoc-toolbar .settings-menu > a:before,
rustdoc-toolbar .help-menu > a:before {
margin: 0 4px;
}
#settings.popover {
top: 16px;
--popover-arrow-offset: 58px;
}
#help.popover {
top: 16px;
--popover-arrow-offset: 16px;
}
}
/*
@ -2435,7 +2638,7 @@ in src-script.js and main.js
/* When linking to an item with an `id` (for instance, by clicking a link in the sidebar,
or visiting a URL with a fragment like `#method.new`, we don't want the item to be obscured
by the topbar. Anything with an `id` gets scroll-margin-top equal to .mobile-topbar's size.
by the topbar. Anything with an `id` gets scroll-margin-top equal to rustdoc-topbar's size.
*/
*[id] {
scroll-margin-top: 45px;
@ -2451,18 +2654,32 @@ in src-script.js and main.js
visibility: hidden;
}
/* Text label takes up too much space at this size. */
rustdoc-toolbar span.label {
/* Pull settings and help up into the top bar. */
rustdoc-topbar span.label,
html:not(.hide-sidebar) .rustdoc:not(.src) rustdoc-toolbar .settings-menu > a,
html:not(.hide-sidebar) .rustdoc:not(.src) rustdoc-toolbar .help-menu > a
{
display: none;
}
#settings-menu > a, #help-button > a, button#toggle-all-docs {
rustdoc-topbar .settings-menu > a,
rustdoc-topbar .help-menu > a {
width: 33px;
line-height: 0;
}
rustdoc-topbar .settings-menu > a:hover,
rustdoc-topbar .help-menu > a:hover {
border: none;
background: var(--main-background-color);
border-radius: 0;
}
#settings.popover {
--popover-arrow-offset: 86px;
top: 32px;
--popover-arrow-offset: 48px;
}
#help.popover {
--popover-arrow-offset: 48px;
top: 32px;
--popover-arrow-offset: 12px;
}
.rustdoc {
@ -2471,13 +2688,13 @@ in src-script.js and main.js
display: block;
}
main {
html:not(.hide-sidebar) main {
padding-left: 15px;
padding-top: 0px;
}
/* Hide the logo and item name from the sidebar. Those are displayed
in the mobile-topbar instead. */
in the rustdoc-topbar instead. */
.sidebar .logo-container,
.sidebar .location,
.sidebar-resizer {
@ -2510,6 +2727,9 @@ in src-script.js and main.js
height: 100vh;
border: 0;
}
html .src main {
padding: 18px 0;
}
.src .search-form {
margin-left: 40px;
}
@ -2529,9 +2749,9 @@ in src-script.js and main.js
left: 0;
}
.mobile-topbar h2 {
rustdoc-topbar > h2 {
padding-bottom: 0;
margin: auto 0.5em auto auto;
margin: auto;
overflow: hidden;
/* Rare exception to specifying font sizes in rem. Since the topbar
height is specified in pixels, this also has to be specified in
@ -2540,32 +2760,34 @@ in src-script.js and main.js
font-size: 24px;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
}
.mobile-topbar .logo-container > img {
rustdoc-topbar .logo-container > img {
max-width: 35px;
max-height: 35px;
margin: 5px 0 5px 20px;
}
.mobile-topbar {
rustdoc-topbar {
display: flex;
flex-direction: row;
position: sticky;
z-index: 10;
font-size: 2rem;
height: 45px;
width: 100%;
left: 0;
top: 0;
}
.hide-sidebar .mobile-topbar {
.hide-sidebar rustdoc-topbar {
display: none;
}
.sidebar-menu-toggle {
width: 45px;
/* prevent flexbox shrinking */
width: 41px;
min-width: 41px;
border: none;
line-height: 0;
}
@ -2591,9 +2813,13 @@ in src-script.js and main.js
#sidebar-button > a::before {
content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
viewBox="0 0 22 22" fill="none" stroke="black">\
<rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
<circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
<path d="m3 7.375h16m0-3h-4" stroke-width="1.25"/></svg>');
<rect x="1" y="2" width="20" height="18" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
<g fill="black" stroke="none">\
<circle cx="4.375" cy="5.375" r="1" stroke-width=".75"/>\
<circle cx="17.375" cy="5.375" r="1" stroke-width=".75"/>\
<circle cx="14.375" cy="5.375" r="1" stroke-width=".75"/>\
</g>\
<path d="m3 8.375h16" stroke-width="1.25"/></svg>');
width: 22px;
height: 22px;
}
@ -3283,7 +3509,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
border-bottom: 1px solid rgba(242, 151, 24, 0.3);
}
:root[data-theme="ayu"] #settings-menu > a img,
:root[data-theme="ayu"] .settings-menu > a img,
:root[data-theme="ayu"] #sidebar-button > a::before {
filter: invert(100);
}

View file

@ -54,23 +54,6 @@ function showMain() {
window.rootPath = getVar("root-path");
window.currentCrate = getVar("current-crate");
function setMobileTopbar() {
// FIXME: It would be nicer to generate this text content directly in HTML,
// but with the current code it's hard to get the right information in the right place.
const mobileTopbar = document.querySelector(".mobile-topbar");
const locationTitle = document.querySelector(".sidebar h2.location");
if (mobileTopbar) {
const mobileTitle = document.createElement("h2");
mobileTitle.className = "location";
if (hasClass(document.querySelector(".rustdoc"), "crate")) {
mobileTitle.innerHTML = `Crate <a href="#">${window.currentCrate}</a>`;
} else if (locationTitle) {
mobileTitle.innerHTML = locationTitle.innerHTML;
}
mobileTopbar.appendChild(mobileTitle);
}
}
/**
* Gets the human-readable string for the virtual-key code of the
* given KeyboardEvent, ev.
@ -84,6 +67,7 @@ function setMobileTopbar() {
* So I guess you could say things are getting pretty interoperable.
*
* @param {KeyboardEvent} ev
* @returns {string}
*/
function getVirtualKey(ev) {
if ("key" in ev && typeof ev.key !== "undefined") {
@ -98,18 +82,8 @@ function getVirtualKey(ev) {
}
const MAIN_ID = "main-content";
const SETTINGS_BUTTON_ID = "settings-menu";
const ALTERNATIVE_DISPLAY_ID = "alternative-display";
const NOT_DISPLAYED_ID = "not-displayed";
const HELP_BUTTON_ID = "help-button";
function getSettingsButton() {
return document.getElementById(SETTINGS_BUTTON_ID);
}
function getHelpButton() {
return document.getElementById(HELP_BUTTON_ID);
}
// Returns the current URL without any query parameter or hash.
function getNakedUrl() {
@ -174,7 +148,7 @@ function getNotDisplayedElem() {
* contains the displayed element (there can be only one at the same time!). So basically, we switch
* elements between the two `<section>` elements.
*
* @param {HTMLElement|null} elemToDisplay
* @param {Element|null} elemToDisplay
*/
function switchDisplayedElement(elemToDisplay) {
const el = getAlternativeDisplayElem();
@ -239,14 +213,14 @@ function preLoadCss(cssUrl) {
document.head.append(script);
}
const settingsButton = getSettingsButton();
if (settingsButton) {
settingsButton.onclick = event => {
onEachLazy(document.querySelectorAll(".settings-menu"), settingsMenu => {
/** @param {MouseEvent} event */
settingsMenu.querySelector("a").onclick = event => {
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
window.hideAllModals(false);
addClass(getSettingsButton(), "rotate");
addClass(settingsMenu, "rotate");
event.preventDefault();
// Sending request for the CSS and the JS files at the same time so it will
// hopefully be loaded when the JS will generate the settings content.
@ -268,15 +242,42 @@ function preLoadCss(cssUrl) {
}
}, 0);
};
}
});
window.searchState = {
rustdocToolbar: document.querySelector("rustdoc-toolbar"),
loadingText: "Loading search results...",
// This will always be an HTMLInputElement, but tsc can't see that
// @ts-expect-error
input: document.getElementsByClassName("search-input")[0],
outputElement: () => {
inputElement: () => {
let el = document.getElementsByClassName("search-input")[0];
if (!el) {
const out = nonnull(nonnull(window.searchState.outputElement()).parentElement);
const hdr = document.createElement("div");
hdr.className = "main-heading search-results-main-heading";
const params = window.searchState.getQueryStringParams();
const autofocusParam = params.search === "" ? "autofocus" : "";
hdr.innerHTML = `<nav class="sub">
<form class="search-form loading">
<span></span> <!-- This empty span is a hacky fix for Safari: see #93184 -->
<input
${autofocusParam}
class="search-input"
name="search"
aria-label="Run search in the documentation"
autocomplete="off"
spellcheck="false"
placeholder="Type S or / to search, ? for more options…"
type="search">
</form>
</nav><div class="search-switcher"></div>`;
out.insertBefore(hdr, window.searchState.outputElement());
el = document.getElementsByClassName("search-input")[0];
}
if (el instanceof HTMLInputElement) {
return el;
}
return null;
},
containerElement: () => {
let el = document.getElementById("search");
if (!el) {
el = document.createElement("section");
@ -285,6 +286,19 @@ function preLoadCss(cssUrl) {
}
return el;
},
outputElement: () => {
const container = window.searchState.containerElement();
if (!container) {
return null;
}
let el = container.querySelector(".search-out");
if (!el) {
el = document.createElement("div");
el.className = "search-out";
container.appendChild(el);
}
return el;
},
title: document.title,
titleBeforeSearch: document.title,
timeout: null,
@ -303,25 +317,52 @@ function preLoadCss(cssUrl) {
}
},
isDisplayed: () => {
const outputElement = window.searchState.outputElement();
return !!outputElement &&
!!outputElement.parentElement &&
outputElement.parentElement.id === ALTERNATIVE_DISPLAY_ID;
const container = window.searchState.containerElement();
if (!container) {
return false;
}
return !!container.parentElement && container.parentElement.id ===
ALTERNATIVE_DISPLAY_ID;
},
// Sets the focus on the search bar at the top of the page
focus: () => {
window.searchState.input && window.searchState.input.focus();
const inputElement = window.searchState.inputElement();
window.searchState.showResults();
if (inputElement) {
inputElement.focus();
// Avoid glitch if something focuses the search button after clicking.
requestAnimationFrame(() => inputElement.focus());
}
},
// Removes the focus from the search bar.
defocus: () => {
window.searchState.input && window.searchState.input.blur();
nonnull(window.searchState.inputElement()).blur();
},
showResults: search => {
if (search === null || typeof search === "undefined") {
search = window.searchState.outputElement();
toggle: () => {
if (window.searchState.isDisplayed()) {
window.searchState.defocus();
window.searchState.hideResults();
} else {
window.searchState.focus();
}
switchDisplayedElement(search);
},
showResults: () => {
document.title = window.searchState.title;
if (window.searchState.isDisplayed()) {
return;
}
const search = window.searchState.containerElement();
switchDisplayedElement(search);
const btn = document.querySelector("#search-button a");
if (browserSupportsHistoryApi() && btn instanceof HTMLAnchorElement &&
window.searchState.getQueryStringParams().search === undefined
) {
history.pushState(null, "", btn.href);
}
const btnLabel = document.querySelector("#search-button a span.label");
if (btnLabel) {
btnLabel.innerHTML = "Exit";
}
},
removeQueryParameters: () => {
// We change the document title.
@ -334,6 +375,10 @@ function preLoadCss(cssUrl) {
switchDisplayedElement(null);
// We also remove the query parameter from the URL.
window.searchState.removeQueryParameters();
const btnLabel = document.querySelector("#search-button a span.label");
if (btnLabel) {
btnLabel.innerHTML = "Search";
}
},
getQueryStringParams: () => {
/** @type {Object.<any, string>} */
@ -348,11 +393,11 @@ function preLoadCss(cssUrl) {
return params;
},
setup: () => {
const search_input = window.searchState.input;
let searchLoaded = false;
const search_input = window.searchState.inputElement();
if (!search_input) {
return;
}
let searchLoaded = false;
// If you're browsing the nightly docs, the page might need to be refreshed for the
// search to work because the hash of the JS scripts might have changed.
function sendSearchForm() {
@ -363,21 +408,102 @@ function preLoadCss(cssUrl) {
if (!searchLoaded) {
searchLoaded = true;
// @ts-expect-error
loadScript(getVar("static-root-path") + getVar("search-js"), sendSearchForm);
loadScript(resourcePath("search-index", ".js"), sendSearchForm);
window.rr_ = data => {
// @ts-expect-error
window.searchIndex = data;
};
if (!window.StringdexOnload) {
window.StringdexOnload = [];
}
window.StringdexOnload.push(() => {
loadScript(
// @ts-expect-error
getVar("static-root-path") + getVar("search-js"),
sendSearchForm,
);
});
// @ts-expect-error
loadScript(getVar("static-root-path") + getVar("stringdex-js"), sendSearchForm);
loadScript(resourcePath("search.index/root", ".js"), sendSearchForm);
}
}
search_input.addEventListener("focus", () => {
window.searchState.origPlaceholder = search_input.placeholder;
search_input.placeholder = "Type your search here.";
loadSearch();
});
if (search_input.value !== "") {
loadSearch();
const btn = document.getElementById("search-button");
if (btn) {
btn.onclick = event => {
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
event.preventDefault();
window.searchState.toggle();
loadSearch();
};
}
// Push and pop states are used to add search results to the browser
// history.
if (browserSupportsHistoryApi()) {
// Store the previous <title> so we can revert back to it later.
const previousTitle = document.title;
window.addEventListener("popstate", e => {
const params = window.searchState.getQueryStringParams();
// Revert to the previous title manually since the History
// API ignores the title parameter.
document.title = previousTitle;
// Synchronize search bar with query string state and
// perform the search. This will empty the bar if there's
// nothing there, which lets you really go back to a
// previous state with nothing in the bar.
const inputElement = window.searchState.inputElement();
if (params.search !== undefined && inputElement !== null) {
loadSearch();
inputElement.value = params.search;
// Some browsers fire "onpopstate" for every page load
// (Chrome), while others fire the event only when actually
// popping a state (Firefox), which is why search() is
// called both here and at the end of the startSearch()
// function.
e.preventDefault();
window.searchState.showResults();
if (params.search === "") {
window.searchState.focus();
}
} else {
// When browsing back from search results the main page
// visibility must be reset.
window.searchState.hideResults();
}
});
}
// This is required in firefox to avoid this problem: Navigating to a search result
// with the keyboard, hitting enter, and then hitting back would take you back to
// the doc page, rather than the search that should overlay it.
// This was an interaction between the back-forward cache and our handlers
// that try to sync state between the URL and the search input. To work around it,
// do a small amount of re-init on page show.
window.onpageshow = () => {
const inputElement = window.searchState.inputElement();
const qSearch = window.searchState.getQueryStringParams().search;
if (qSearch !== undefined && inputElement !== null) {
if (inputElement.value === "") {
inputElement.value = qSearch;
}
window.searchState.showResults();
if (qSearch === "") {
loadSearch();
window.searchState.focus();
}
} else {
window.searchState.hideResults();
}
};
const params = window.searchState.getQueryStringParams();
if (params.search !== undefined) {
window.searchState.setLoadingSearch();
@ -386,13 +512,9 @@ function preLoadCss(cssUrl) {
},
setLoadingSearch: () => {
const search = window.searchState.outputElement();
if (!search) {
return;
}
search.innerHTML = "<h3 class=\"search-loading\">" +
window.searchState.loadingText +
"</h3>";
window.searchState.showResults(search);
nonnull(search).innerHTML = "<h3 class=\"search-loading\">" +
window.searchState.loadingText + "</h3>";
window.searchState.showResults();
},
descShards: new Map(),
loadDesc: async function({descShard, descIndex}) {
@ -1500,15 +1622,13 @@ function preLoadCss(cssUrl) {
// @ts-expect-error
function helpBlurHandler(event) {
// @ts-expect-error
if (!getHelpButton().contains(document.activeElement) &&
// @ts-expect-error
!getHelpButton().contains(event.relatedTarget) &&
// @ts-expect-error
!getSettingsButton().contains(document.activeElement) &&
// @ts-expect-error
!getSettingsButton().contains(event.relatedTarget)
) {
const isInPopover = onEachLazy(
document.querySelectorAll(".settings-menu, .help-menu"),
menu => {
return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);
},
);
if (!isInPopover) {
window.hidePopoverMenus();
}
}
@ -1571,10 +1691,9 @@ function preLoadCss(cssUrl) {
const container = document.createElement("div");
if (!isHelpPage) {
container.className = "popover";
container.className = "popover content";
}
container.id = "help";
container.style.display = "none";
const side_by_side = document.createElement("div");
side_by_side.className = "side-by-side";
@ -1590,17 +1709,16 @@ function preLoadCss(cssUrl) {
help_section.appendChild(container);
// @ts-expect-error
document.getElementById("main-content").appendChild(help_section);
container.style.display = "block";
} else {
const help_button = getHelpButton();
// @ts-expect-error
help_button.appendChild(container);
container.onblur = helpBlurHandler;
// @ts-expect-error
help_button.onblur = helpBlurHandler;
// @ts-expect-error
help_button.children[0].onblur = helpBlurHandler;
onEachLazy(document.getElementsByClassName("help-menu"), menu => {
if (menu.offsetWidth !== 0) {
menu.appendChild(container);
container.onblur = helpBlurHandler;
menu.onblur = helpBlurHandler;
menu.children[0].onblur = helpBlurHandler;
return true;
}
});
}
return container;
@ -1621,80 +1739,57 @@ function preLoadCss(cssUrl) {
* Hide all the popover menus.
*/
window.hidePopoverMenus = () => {
onEachLazy(document.querySelectorAll("rustdoc-toolbar .popover"), elem => {
onEachLazy(document.querySelectorAll(".settings-menu .popover"), elem => {
elem.style.display = "none";
});
const button = getHelpButton();
if (button) {
removeClass(button, "help-open");
}
onEachLazy(document.querySelectorAll(".help-menu .popover"), elem => {
elem.parentElement.removeChild(elem);
});
};
/**
* Returns the help menu element (not the button).
*
* @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
* built if it doesn't exist.
*
* @return {HTMLElement}
*/
function getHelpMenu(buildNeeded) {
// @ts-expect-error
let menu = getHelpButton().querySelector(".popover");
if (!menu && buildNeeded) {
menu = buildHelpMenu();
}
// @ts-expect-error
return menu;
}
/**
* Show the help popup menu.
*/
function showHelp() {
window.hideAllModals(false);
// Prevent `blur` events from being dispatched as a result of closing
// other modals.
const button = getHelpButton();
addClass(button, "help-open");
// @ts-expect-error
button.querySelector("a").focus();
const menu = getHelpMenu(true);
if (menu.style.display === "none") {
// @ts-expect-error
window.hideAllModals();
menu.style.display = "";
}
onEachLazy(document.querySelectorAll(".help-menu a"), menu => {
if (menu.offsetWidth !== 0) {
menu.focus();
return true;
}
});
buildHelpMenu();
}
const helpLink = document.querySelector(`#${HELP_BUTTON_ID} > a`);
if (isHelpPage) {
buildHelpMenu();
} else if (helpLink) {
helpLink.addEventListener("click", event => {
// By default, have help button open docs in a popover.
// If user clicks with a moderator, though, use default browser behavior,
// probably opening in a new window or tab.
if (!helpLink.contains(helpLink) ||
// @ts-expect-error
event.ctrlKey ||
// @ts-expect-error
event.altKey ||
// @ts-expect-error
event.metaKey) {
return;
}
event.preventDefault();
const menu = getHelpMenu(true);
const shouldShowHelp = menu.style.display === "none";
if (shouldShowHelp) {
showHelp();
} else {
window.hidePopoverMenus();
}
} else {
onEachLazy(document.querySelectorAll(".help-menu > a"), helpLink => {
helpLink.addEventListener(
"click",
/** @param {MouseEvent} event */
event => {
// By default, have help button open docs in a popover.
// If user clicks with a moderator, though, use default browser behavior,
// probably opening in a new window or tab.
if (event.ctrlKey ||
event.altKey ||
event.metaKey) {
return;
}
event.preventDefault();
if (document.getElementById("help")) {
window.hidePopoverMenus();
} else {
showHelp();
}
},
);
});
}
setMobileTopbar();
addSidebarItems();
addSidebarCrates();
onHashChange(null);
@ -1746,7 +1841,15 @@ function preLoadCss(cssUrl) {
// On larger, "desktop-sized" viewports (though that includes many
// tablets), it's fixed-position, appears in the left side margin,
// and it can be activated by resizing the sidebar into nothing.
const sidebarButton = document.getElementById("sidebar-button");
let sidebarButton = document.getElementById("sidebar-button");
const body = document.querySelector(".main-heading");
if (!sidebarButton && body) {
sidebarButton = document.createElement("div");
sidebarButton.id = "sidebar-button";
const path = `${window.rootPath}${window.currentCrate}/all.html`;
sidebarButton.innerHTML = `<a href="${path}" title="show sidebar"></a>`;
body.insertBefore(sidebarButton, body.firstChild);
}
if (sidebarButton) {
sidebarButton.addEventListener("click", e => {
removeClass(document.documentElement, "hide-sidebar");

View file

@ -2,6 +2,8 @@
// not put into the JavaScript we include as part of the documentation. It is used for
// type checking. See README.md in this directory for more info.
import { RoaringBitmap } from "./stringdex";
/* eslint-disable */
declare global {
/** Search engine data used by main.js and search.js */
@ -10,6 +12,17 @@ declare global {
declare function nonnull(x: T|null, msg: string|undefined);
/** Defined and documented in `storage.js` */
declare function nonundef(x: T|undefined, msg: string|undefined);
interface PromiseConstructor {
/**
* Polyfill
* @template T
*/
withResolvers: function(): {
"promise": Promise<T>,
"resolve": (function(T): void),
"reject": (function(any): void)
};
}
interface Window {
/** Make the current theme easy to find */
currentTheme: HTMLLinkElement|null;
@ -95,29 +108,28 @@ declare namespace rustdoc {
interface SearchState {
rustdocToolbar: HTMLElement|null;
loadingText: string;
input: HTMLInputElement|null;
inputElement: function(): HTMLInputElement|null;
containerElement: function(): Element|null;
title: string;
titleBeforeSearch: string;
timeout: number|null;
timeout: ReturnType<typeof setTimeout>|null;
currentTab: number;
focusedByTab: [number|null, number|null, number|null];
focusedByTab: [Element|null, Element|null, Element|null];
clearInputTimeout: function;
outputElement(): HTMLElement|null;
focus();
defocus();
// note: an optional param is not the same as
// a nullable/undef-able param.
showResults(elem?: HTMLElement|null);
removeQueryParameters();
hideResults();
getQueryStringParams(): Object.<any, string>;
origPlaceholder: string;
outputElement: function(): Element|null;
focus: function();
defocus: function();
toggle: function();
showResults: function();
removeQueryParameters: function();
hideResults: function();
getQueryStringParams: function(): Object.<any, string>;
setup: function();
setLoadingSearch();
descShards: Map<string, SearchDescShard[]>;
loadDesc: function({descShard: SearchDescShard, descIndex: number}): Promise<string|null>;
loadedDescShard(string, number, string);
isDisplayed(): boolean,
loadedDescShard: function(string, number, string);
isDisplayed: function(): boolean;
}
interface SearchDescShard {
@ -131,12 +143,13 @@ declare namespace rustdoc {
* A single parsed "atom" in a search query. For example,
*
* std::fmt::Formatter, Write -> Result<()>
*
* QueryElement {
* name: Result
* generics: [
* QueryElement
* name: ()
*
* QueryElement {
* name: Result
* generics: [
* QueryElement {
* name: ()
* }
* ]
* }
* QueryElement {
@ -156,14 +169,14 @@ declare namespace rustdoc {
normalizedPathLast: string,
generics: Array<QueryElement>,
bindings: Map<number, Array<QueryElement>>,
typeFilter: number|null,
typeFilter: number,
}
/**
* Same as QueryElement, but bindings and typeFilter support strings
*/
interface ParserQueryElement {
name: string|null,
name: string,
id: number|null,
fullPath: Array<string>,
pathWithoutLast: Array<string>,
@ -172,7 +185,7 @@ declare namespace rustdoc {
generics: Array<ParserQueryElement>,
bindings: Map<string, Array<ParserQueryElement>>,
bindingName: {name: string|null, generics: ParserQueryElement[]}|null,
typeFilter: number|string|null,
typeFilter: string|null,
}
/**
@ -215,35 +228,74 @@ declare namespace rustdoc {
/**
* An entry in the search index database.
*/
interface EntryData {
krate: number,
ty: ItemType,
modulePath: number?,
exactModulePath: number?,
parent: number?,
deprecated: boolean,
associatedItemDisambiguator: string?,
}
/**
* A path in the search index database
*/
interface PathData {
ty: ItemType,
modulePath: string,
exactModulePath: string?,
}
/**
* A function signature in the search index database
*
* Note that some non-function items (eg. constants, struct fields) have a function signature so they can appear in type-based search.
*/
interface FunctionData {
functionSignature: FunctionSearchType|null,
paramNames: string[],
elemCount: number,
}
/**
* A function signature in the search index database
*/
interface TypeData {
searchUnbox: boolean,
invertedFunctionSignatureIndex: RoaringBitmap[],
}
/**
* A search entry of some sort.
*/
interface Row {
crate: string,
descShard: SearchDescShard,
id: number,
// This is the name of the item. For doc aliases, if you want the name of the aliased
// item, take a look at `Row.original.name`.
crate: string,
ty: ItemType,
name: string,
normalizedName: string,
word: string,
paramNames: string[],
parent: ({ty: number, name: string, path: string, exactPath: string}|null|undefined),
path: string,
ty: number,
type: FunctionSearchType | null,
descIndex: number,
bitIndex: number,
implDisambiguator: String | null,
is_alias?: boolean,
original?: Row,
modulePath: string,
exactModulePath: string,
entry: EntryData?,
path: PathData?,
type: FunctionData?,
deprecated: boolean,
parent: { path: PathData, name: string}?,
}
type ItemType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26;
/**
* The viewmodel for the search engine results page.
*/
interface ResultsTable {
in_args: Array<ResultObject>,
returned: Array<ResultObject>,
others: Array<ResultObject>,
query: ParsedQuery,
in_args: AsyncGenerator<ResultObject>,
returned: AsyncGenerator<ResultObject>,
others: AsyncGenerator<ResultObject>,
query: ParsedQuery<rustdoc.ParserQueryElement>,
}
type Results = { max_dist?: number } & Map<number, ResultObject>
@ -252,25 +304,41 @@ declare namespace rustdoc {
* An annotated `Row`, used in the viewmodel.
*/
interface ResultObject {
desc: string,
desc: Promise<string|null>,
displayPath: string,
fullPath: string,
href: string,
id: number,
dist: number,
path_dist: number,
name: string,
normalizedName: string,
word: string,
index: number,
parent: (Object|undefined),
path: string,
ty: number,
parent: ({
path: string,
exactPath: string,
name: string,
ty: number,
}|undefined),
type?: FunctionSearchType,
paramNames?: string[],
displayTypeSignature: Promise<rustdoc.DisplayTypeSignature> | null,
item: Row,
dontValidate?: boolean,
is_alias: boolean,
alias?: string,
}
/**
* An annotated `Row`, used in the viewmodel.
*/
interface PlainResultObject {
id: number,
dist: number,
path_dist: number,
index: number,
elems: rustdoc.QueryElement[],
returned: rustdoc.QueryElement[],
is_alias: boolean,
alias?: string,
original?: rustdoc.Rlow,
}
/**
@ -364,7 +432,19 @@ declare namespace rustdoc {
* Numeric IDs are *ONE-indexed* into the paths array (`p`). Zero is used as a sentinel for `null`
* because `null` is four bytes while `0` is one byte.
*/
type RawFunctionType = number | [number, Array<RawFunctionType>];
type RawFunctionType = number | [number, Array<RawFunctionType>] | [number, Array<RawFunctionType>, Array<[RawFunctionType, RawFunctionType[]]>];
/**
* Utility typedef for deserializing compact JSON.
*
* R is the required part, O is the optional part, which goes afterward.
* For example, `ArrayWithOptionals<[A, B], [C, D]>` matches
* `[A, B] | [A, B, C] | [A, B, C, D]`.
*/
type ArrayWithOptionals<R extends any[], O extends any[]> =
O extends [infer First, ...infer Rest] ?
R | ArrayWithOptionals<[...R, First], Rest> :
R;
/**
* The type signature entry in the decoded search index.
@ -382,8 +462,8 @@ declare namespace rustdoc {
*/
interface FunctionType {
id: null|number,
ty: number|null,
name?: string,
ty: ItemType,
name: string|null,
path: string|null,
exactPath: string|null,
unboxFlag: boolean,
@ -403,70 +483,6 @@ declare namespace rustdoc {
bindings: Map<number, FingerprintableType[]>;
};
/**
* The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f`
* are arrays with the same length. `q`, `a`, and `c` use a sparse
* representation for compactness.
*
* `n[i]` contains the name of an item.
*
* `t[i]` contains the type of that item
* (as a string of characters that represent an offset in `itemTypes`).
*
* `d[i]` contains the description of that item.
*
* `q` contains the full paths of the items. For compactness, it is a set of
* (index, path) pairs used to create a map. If a given index `i` is
* not present, this indicates "same as the last index present".
*
* `i[i]` contains an item's parent, usually a module. For compactness,
* it is a set of indexes into the `p` array.
*
* `f` contains function signatures, or `0` if the item isn't a function.
* More information on how they're encoded can be found in rustc-dev-guide
*
* Functions are themselves encoded as arrays. The first item is a list of
* types representing the function's inputs, and the second list item is a list
* of types representing the function's output. Tuples are flattened.
* Types are also represented as arrays; the first item is an index into the `p`
* array, while the second is a list of types representing any generic parameters.
*
* b[i] contains an item's impl disambiguator. This is only present if an item
* is defined in an impl block and, the impl block's type has more than one associated
* item with the same name.
*
* `a` defines aliases with an Array of pairs: [name, offset], where `offset`
* points into the n/t/d/q/i/f arrays.
*
* `doc` contains the description of the crate.
*
* `p` is a list of path/type pairs. It is used for parents and function parameters.
* The first item is the type, the second is the name, the third is the visible path (if any) and
* the fourth is the canonical path used for deduplication (if any).
*
* `r` is the canonical path used for deduplication of re-exported items.
* It is not used for associated items like methods (that's the fourth element
* of `p`) but is used for modules items like free functions.
*
* `c` is an array of item indices that are deprecated.
*/
type RawSearchIndexCrate = {
doc: string,
a: { [key: string]: number[] },
n: Array<string>,
t: string,
D: string,
e: string,
q: Array<[number, string]>,
i: string,
f: string,
p: Array<[number, string] | [number, string, number] | [number, string, number, number] | [number, string, number, number, string]>,
b: Array<[number, String]>,
c: string,
r: Array<[number, number]>,
P: Array<[number, string]>,
};
type VlqData = VlqData[] | number;
/**

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,13 @@
// Local js definitions:
/* global getSettingValue, updateLocalStorage, updateTheme */
/* global addClass, removeClass, onEach, onEachLazy */
/* global MAIN_ID, getVar, getSettingsButton, getHelpButton, nonnull */
/* global MAIN_ID, getVar, nonnull */
"use strict";
(function() {
const isSettingsPage = window.location.pathname.endsWith("/settings.html");
/**
* @param {Element} elem
* @param {EventTarget|null} target
*/
function elemContainsTarget(elem, target) {
if (target instanceof Node) {
return elem.contains(target);
} else {
return false;
}
}
/**
* @overload {"theme"|"preferred-dark-theme"|"preferred-light-theme"}
* @param {string} settingName
@ -305,10 +293,12 @@
}
} else {
el.setAttribute("tabindex", "-1");
const settingsBtn = getSettingsButton();
if (settingsBtn !== null) {
settingsBtn.appendChild(el);
}
onEachLazy(document.querySelectorAll(".settings-menu"), menu => {
if (menu.offsetWidth !== 0) {
menu.appendChild(el);
return true;
}
});
}
return el;
}
@ -317,6 +307,15 @@
function displaySettings() {
settingsMenu.style.display = "";
onEachLazy(document.querySelectorAll(".settings-menu"), menu => {
if (menu.offsetWidth !== 0) {
if (!menu.contains(settingsMenu) && settingsMenu.parentElement) {
settingsMenu.parentElement.removeChild(settingsMenu);
menu.appendChild(settingsMenu);
}
return true;
}
});
onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => {
const val = getSettingValue(el.id);
const checked = val === "true";
@ -330,40 +329,37 @@
* @param {FocusEvent} event
*/
function settingsBlurHandler(event) {
const helpBtn = getHelpButton();
const settingsBtn = getSettingsButton();
const helpUnfocused = helpBtn === null ||
(!helpBtn.contains(document.activeElement) &&
!elemContainsTarget(helpBtn, event.relatedTarget));
const settingsUnfocused = settingsBtn === null ||
(!settingsBtn.contains(document.activeElement) &&
!elemContainsTarget(settingsBtn, event.relatedTarget));
if (helpUnfocused && settingsUnfocused) {
const isInPopover = onEachLazy(
document.querySelectorAll(".settings-menu, .help-menu"),
menu => {
return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);
},
);
if (!isInPopover) {
window.hidePopoverMenus();
}
}
if (!isSettingsPage) {
// We replace the existing "onclick" callback.
// These elements must exist, as (outside of the settings page)
// `settings.js` is only loaded after the settings button is clicked.
const settingsButton = nonnull(getSettingsButton());
const settingsMenu = nonnull(document.getElementById("settings"));
settingsButton.onclick = event => {
if (elemContainsTarget(settingsMenu, event.target)) {
return;
}
event.preventDefault();
const shouldDisplaySettings = settingsMenu.style.display === "none";
onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {
/** @param {MouseEvent} event */
settingsButton.querySelector("a").onclick = event => {
if (!(event.target instanceof Element) || settingsMenu.contains(event.target)) {
return;
}
event.preventDefault();
const shouldDisplaySettings = settingsMenu.style.display === "none";
window.hideAllModals(false);
if (shouldDisplaySettings) {
displaySettings();
}
};
settingsButton.onblur = settingsBlurHandler;
// the settings button should always have a link in it
nonnull(settingsButton.querySelector("a")).onblur = settingsBlurHandler;
window.hideAllModals(false);
if (shouldDisplaySettings) {
displaySettings();
}
};
settingsButton.onblur = settingsBlurHandler;
settingsButton.querySelector("a").onblur = settingsBlurHandler;
});
onEachLazy(settingsMenu.querySelectorAll("input"), el => {
el.onblur = settingsBlurHandler;
});
@ -377,6 +373,8 @@
if (!isSettingsPage) {
displaySettings();
}
removeClass(getSettingsButton(), "rotate");
onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {
removeClass(settingsButton, "rotate");
});
}, 0);
})();

View file

@ -7,6 +7,7 @@
/**
* @import * as rustdoc from "./rustdoc.d.ts";
* @import * as stringdex from "./stringdex.d.ts";
*/
const builtinThemes = ["light", "dark", "ayu"];
@ -172,7 +173,7 @@ function updateLocalStorage(name, value) {
} else {
window.localStorage.setItem("rustdoc-" + name, value);
}
} catch (e) {
} catch {
// localStorage is not accessible, do nothing
}
}
@ -189,7 +190,7 @@ function updateLocalStorage(name, value) {
function getCurrentValue(name) {
try {
return window.localStorage.getItem("rustdoc-" + name);
} catch (e) {
} catch {
return null;
}
}
@ -375,32 +376,6 @@ window.addEventListener("pageshow", ev => {
// That's also why this is in storage.js and not main.js.
//
// [parser]: https://html.spec.whatwg.org/multipage/parsing.html
class RustdocSearchElement extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const rootPath = getVar("root-path");
const currentCrate = getVar("current-crate");
this.innerHTML = `<nav class="sub">
<form class="search-form">
<span></span> <!-- This empty span is a hacky fix for Safari - See #93184 -->
<div id="sidebar-button" tabindex="-1">
<a href="${rootPath}${currentCrate}/all.html" title="show sidebar"></a>
</div>
<input
class="search-input"
name="search"
aria-label="Run search in the documentation"
autocomplete="off"
spellcheck="false"
placeholder="Type S or / to search, ? for more options…"
type="search">
</form>
</nav>`;
}
}
window.customElements.define("rustdoc-search", RustdocSearchElement);
class RustdocToolbarElement extends HTMLElement {
constructor() {
super();
@ -411,11 +386,15 @@ class RustdocToolbarElement extends HTMLElement {
return;
}
const rootPath = getVar("root-path");
const currentUrl = window.location.href.split("?")[0].split("#")[0];
this.innerHTML = `
<div id="settings-menu" tabindex="-1">
<div id="search-button" tabindex="-1">
<a href="${currentUrl}?search="><span class="label">Search</span></a>
</div>
<div class="settings-menu" tabindex="-1">
<a href="${rootPath}settings.html"><span class="label">Settings</span></a>
</div>
<div id="help-button" tabindex="-1">
<div class="help-menu" tabindex="-1">
<a href="${rootPath}help.html"><span class="label">Help</span></a>
</div>
<button id="toggle-all-docs"
@ -424,3 +403,31 @@ class="label">Summary</span></button>`;
}
}
window.customElements.define("rustdoc-toolbar", RustdocToolbarElement);
class RustdocTopBarElement extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const rootPath = getVar("root-path");
const tmplt = document.createElement("template");
tmplt.innerHTML = `
<slot name="sidebar-menu-toggle"></slot>
<slot></slot>
<slot name="settings-menu"></slot>
<slot name="help-menu"></slot>
`;
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(tmplt.content.cloneNode(true));
this.innerHTML += `
<button class="sidebar-menu-toggle" slot="sidebar-menu-toggle" title="show sidebar">
</button>
<div class="settings-menu" slot="settings-menu" tabindex="-1">
<a href="${rootPath}settings.html"><span class="label">Settings</span></a>
</div>
<div class="help-menu" slot="help-menu" tabindex="-1">
<a href="${rootPath}help.html"><span class="label">Help</span></a>
</div>
`;
}
}
window.customElements.define("rustdoc-topbar", RustdocTopBarElement);

View file

@ -0,0 +1,165 @@
export = stringdex;
declare namespace stringdex {
/**
* The client interface to Stringdex.
*/
interface Database {
getIndex(colname: string): SearchTree|undefined;
getData(colname: string): DataColumn|undefined;
}
/**
* A search index file.
*/
interface SearchTree {
trie(): Trie;
search(name: Uint8Array|string): Promise<Trie?>;
searchLev(name: Uint8Array|string): AsyncGenerator<Trie>;
}
/**
* A compressed node in the search tree.
*
* This object logically addresses two interleaved trees:
* a "prefix tree", and a "suffix tree". If you ask for
* generic matches, you get both, but if you ask for one
* that excludes suffix-only entries, you'll get prefixes
* alone.
*/
interface Trie {
matches(): RoaringBitmap;
substringMatches(): AsyncGenerator<RoaringBitmap>;
prefixMatches(): AsyncGenerator<RoaringBitmap>;
keys(): Uint8Array;
keysExcludeSuffixOnly(): Uint8Array;
children(): [number, Promise<Trie>][];
childrenExcludeSuffixOnly(): [number, Promise<Trie>][];
child(id: number): Promise<Trie>?;
}
/**
* The client interface to Stringdex.
*/
interface DataColumn {
isEmpty(id: number): boolean;
at(id: number): Promise<Uint8Array|undefined>;
length: number,
}
/**
* Callbacks for a host application and VFS backend.
*
* These functions are calleb with mostly-raw data,
* except the JSONP wrapper is removed. For example,
* a file with the contents `rr_('{"A":"B"}')` should,
* after being pulled in, result in the `rr_` callback
* being invoked.
*
* The success callbacks don't need to supply the name of
* the file that succeeded, but, if you want successful error
* reporting, you'll need to remember which files are
* in flight and report the filename as the first parameter.
*/
interface Callbacks {
/**
* Load the root of the search database
* @param {string} dataString
*/
rr_: function(string);
err_rr_: function(any);
/**
* Load a nodefile in the search tree.
* A node file may contain multiple nodes;
* each node has five fields, separated by newlines.
* @param {string} inputBase64
*/
rn_: function(string);
err_rn_: function(string, any);
/**
* Load a database column partition from a string
* @param {string} dataString
*/
rd_: function(string);
err_rd_: function(string, any);
/**
* Load a database column partition from base64
* @param {string} dataString
*/
rb_: function(string);
err_rb_: function(string, any);
};
/**
* Hooks that a VFS layer must provide for stringdex to load data.
*
* When the root is loaded, the Callbacks object is provided. These
* functions should result in callback functions being called with
* the contents of the file, or in error callbacks being invoked with
* the failed-to-load filename.
*/
interface Hooks {
/**
* The first function invoked as part of loading a search database.
* This function must, eventually, invoke `rr_` with the string
* representation of the root file (the function call wrapper,
* `rr_('` and `')`, must be removed).
*
* The supplied callbacks object is used to feed search data back
* to the search engine core. You have to store it, so that
* loadTreeByHash and loadDataByNameAndHash can use it.
*
* If this fails, either throw an exception, or call `err_rr_`
* with the error object.
*/
loadRoot: function(Callbacks);
/**
* Load a subtree file from the search index.
*
* If this function succeeds, call `rn_` on the callbacks
* object. If it fails, call `err_rn_(hashHex, error)`.
*
* @param {string} hashHex
*/
loadTreeByHash: function(string);
/**
* Load a column partition from the search database.
*
* If this function succeeds, call `rd_` or `rb_` on the callbacks
* object. If it fails, call `err_rd_(hashHex, error)`. or `err_rb_`.
* To determine which one, the wrapping function call in the js file
* specifies it.
*
* @param {string} columnName
* @param {string} hashHex
*/
loadDataByNameAndHash: function(string, string);
};
class RoaringBitmap {
constructor(array: Uint8Array|null, start?: number);
static makeSingleton(number: number);
static everything(): RoaringBitmap;
static empty(): RoaringBitmap;
isEmpty(): boolean;
union(that: RoaringBitmap): RoaringBitmap;
intersection(that: RoaringBitmap): RoaringBitmap;
contains(number: number): boolean;
entries(): Generator<number>;
first(): number|null;
consumed_len_bytes: number;
};
type Stringdex = {
/**
* Initialize Stringdex with VFS hooks.
* Returns a database that you can use.
*/
loadDatabase: function(Hooks): Promise<Database>,
};
const Stringdex: Stringdex;
const RoaringBitmap: Class<stringdex.RoaringBitmap>;
}
declare global {
interface Window {
Stringdex: stringdex.Stringdex;
RoaringBitmap: Class<stringdex.RoaringBitmap>;
StringdexOnload: Array<function(stringdex.Stringdex): any>?;
};
}

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,6 @@
"skipLibCheck": true
},
"typeAcquisition": {
"include": ["./rustdoc.d.ts"]
"include": ["./rustdoc.d.ts", "./stringdex.d.ts"]
}
}

View file

@ -80,6 +80,7 @@ static_files! {
normalize_css => "static/css/normalize.css",
main_js => "static/js/main.js",
search_js => "static/js/search.js",
stringdex_js => "static/js/stringdex.js",
settings_js => "static/js/settings.js",
src_script_js => "static/js/src-script.js",
storage_js => "static/js/storage.js",

View file

@ -29,6 +29,7 @@
data-rustdoc-version="{{rustdoc_version}}" {#+ #}
data-channel="{{rust_channel}}" {#+ #}
data-search-js="{{files.search_js}}" {#+ #}
data-stringdex-js="{{files.stringdex_js}}" {#+ #}
data-settings-js="{{files.settings_js}}" {#+ #}
> {# #}
<script src="{{static_root_path|safe}}{{files.storage_js}}"></script>
@ -72,18 +73,9 @@
<![endif]-->
{{ layout.external_html.before_content|safe }}
{% if page.css_class != "src" %}
<nav class="mobile-topbar"> {# #}
<button class="sidebar-menu-toggle" title="show sidebar"></button>
{% if !layout.logo.is_empty() || page.rust_logo %}
<a class="logo-container" href="{{page.root_path|safe}}{{display_krate_with_trailing_slash|safe}}index.html">
{% if page.rust_logo %}
<img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="">
{% else if !layout.logo.is_empty() %}
<img src="{{layout.logo}}" alt="">
{% endif %}
</a>
{% endif %}
</nav>
<rustdoc-topbar> {# #}
<h2><a href="#">{{page.short_title}}</a></h2> {# #}
</rustdoc-topbar>
{% endif %}
<nav class="sidebar">
{% if page.css_class != "src" %}
@ -117,9 +109,6 @@
<div class="sidebar-resizer" title="Drag to resize sidebar"></div> {# #}
<main>
{% if page.css_class != "src" %}<div class="width-limiter">{% endif %}
{# defined in storage.js to avoid duplicating complex UI across every page #}
{# and because the search form only works if JS is enabled anyway #}
<rustdoc-search></rustdoc-search> {# #}
<section id="main-content" class="content">{{ content|safe }}</section>
{% if page.css_class != "src" %}</div>{% endif %}
</main>

View file

@ -12,8 +12,8 @@
<h1>
{{typ}}
<span{% if item_type != "mod" +%} class="{{item_type}}"{% endif %}>
{{name}}
</span> {# #}
{{name|wrapped|safe}}
</span>&nbsp;{# #}
<button id="copy-path" title="Copy item path to clipboard"> {# #}
Copy item path {# #}
</button> {# #}

View file

@ -1,7 +1,8 @@
/* global globalThis */
const fs = require("fs");
const path = require("path");
const { isGeneratorObject } = require("util/types");
function arrayToCode(array) {
return array.map((value, index) => {
@ -45,23 +46,16 @@ function shouldIgnoreField(fieldName) {
}
function valueMapper(key, testOutput) {
const isAlias = testOutput["is_alias"];
let value = testOutput[key];
// To make our life easier, if there is a "parent" type, we add it to the path.
if (key === "path") {
if (testOutput["parent"] !== undefined) {
if (testOutput["parent"]) {
if (value.length > 0) {
value += "::" + testOutput["parent"]["name"];
} else {
value = testOutput["parent"]["name"];
}
} else if (testOutput["is_alias"]) {
value = valueMapper(key, testOutput["original"]);
}
} else if (isAlias && key === "alias") {
value = testOutput["name"];
} else if (isAlias && ["name"].includes(key)) {
value = testOutput["original"][key];
}
return value;
}
@ -237,7 +231,7 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
const ignore_order = loadedFile.ignore_order;
const exact_check = loadedFile.exact_check;
const results = await doSearch(query, loadedFile.FILTER_CRATE);
const { resultsTable } = await doSearch(query, loadedFile.FILTER_CRATE);
const error_text = [];
for (const key in expected) {
@ -247,37 +241,38 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
if (!Object.prototype.hasOwnProperty.call(expected, key)) {
continue;
}
if (!Object.prototype.hasOwnProperty.call(results, key)) {
if (!Object.prototype.hasOwnProperty.call(resultsTable, key)) {
error_text.push("==> Unknown key \"" + key + "\"");
break;
}
const entry = expected[key];
if (exact_check && entry.length !== results[key].length) {
if (exact_check && entry.length !== resultsTable[key].length) {
error_text.push(queryName + "==> Expected exactly " + entry.length +
" results but found " + results[key].length + " in '" + key + "'");
" results but found " + resultsTable[key].length + " in '" + key + "'");
}
let prev_pos = -1;
for (const [index, elem] of entry.entries()) {
const entry_pos = lookForEntry(elem, results[key]);
const entry_pos = lookForEntry(elem, resultsTable[key]);
if (entry_pos === -1) {
error_text.push(queryName + "==> Result not found in '" + key + "': '" +
JSON.stringify(elem) + "'");
// By default, we just compare the two first items.
let item_to_diff = 0;
if ((!ignore_order || exact_check) && index < results[key].length) {
if ((!ignore_order || exact_check) && index < resultsTable[key].length) {
item_to_diff = index;
}
error_text.push("Diff of first error:\n" +
betterLookingDiff(elem, results[key][item_to_diff]));
betterLookingDiff(elem, resultsTable[key][item_to_diff]));
} else if (exact_check === true && prev_pos + 1 !== entry_pos) {
error_text.push(queryName + "==> Exact check failed at position " + (prev_pos + 1) +
": expected '" + JSON.stringify(elem) + "' but found '" +
JSON.stringify(results[key][index]) + "'");
JSON.stringify(resultsTable[key][index]) + "'");
} else if (ignore_order === false && entry_pos < prev_pos) {
error_text.push(queryName + "==> '" + JSON.stringify(elem) + "' was supposed " +
"to be before '" + JSON.stringify(results[key][prev_pos]) + "'");
error_text.push(queryName + "==> '" +
JSON.stringify(elem) + "' was supposed to be before '" +
JSON.stringify(resultsTable[key][prev_pos]) + "'");
} else {
prev_pos = entry_pos;
}
@ -286,19 +281,20 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
return error_text;
}
async function runCorrections(query, corrections, getCorrections, loadedFile) {
const qc = await getCorrections(query, loadedFile.FILTER_CRATE);
async function runCorrections(query, corrections, doSearch, loadedFile) {
const { parsedQuery } = await doSearch(query, loadedFile.FILTER_CRATE);
const qc = parsedQuery.correction;
const error_text = [];
if (corrections === null) {
if (qc !== null) {
error_text.push(`==> expected = null, found = ${qc}`);
error_text.push(`==> [correction] expected = null, found = ${qc}`);
}
return error_text;
}
if (qc !== corrections.toLowerCase()) {
error_text.push(`==> expected = ${corrections}, found = ${qc}`);
if (qc.toLowerCase() !== corrections.toLowerCase()) {
error_text.push(`==> [correction] expected = ${corrections}, found = ${qc}`);
}
return error_text;
@ -320,7 +316,7 @@ function checkResult(error_text, loadedFile, displaySuccess) {
return 1;
}
async function runCheckInner(callback, loadedFile, entry, getCorrections, extra) {
async function runCheckInner(callback, loadedFile, entry, extra, doSearch) {
if (typeof entry.query !== "string") {
console.log("FAILED");
console.log("==> Missing `query` field");
@ -338,7 +334,7 @@ async function runCheckInner(callback, loadedFile, entry, getCorrections, extra)
error_text = await runCorrections(
entry.query,
entry.correction,
getCorrections,
doSearch,
loadedFile,
);
if (checkResult(error_text, loadedFile, false) !== 0) {
@ -348,16 +344,16 @@ async function runCheckInner(callback, loadedFile, entry, getCorrections, extra)
return true;
}
async function runCheck(loadedFile, key, getCorrections, callback) {
async function runCheck(loadedFile, key, doSearch, callback) {
const expected = loadedFile[key];
if (Array.isArray(expected)) {
for (const entry of expected) {
if (!await runCheckInner(callback, loadedFile, entry, getCorrections, true)) {
if (!await runCheckInner(callback, loadedFile, entry, true, doSearch)) {
return 1;
}
}
} else if (!await runCheckInner(callback, loadedFile, expected, getCorrections, false)) {
} else if (!await runCheckInner(callback, loadedFile, expected, false, doSearch)) {
return 1;
}
console.log("OK");
@ -368,7 +364,7 @@ function hasCheck(content, checkName) {
return content.startsWith(`const ${checkName}`) || content.includes(`\nconst ${checkName}`);
}
async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
async function runChecks(testFile, doSearch, parseQuery) {
let checkExpected = false;
let checkParsed = false;
let testFileContent = readFile(testFile);
@ -397,12 +393,12 @@ async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
let res = 0;
if (checkExpected) {
res += await runCheck(loadedFile, "EXPECTED", getCorrections, (query, expected, text) => {
res += await runCheck(loadedFile, "EXPECTED", doSearch, (query, expected, text) => {
return runSearch(query, expected, doSearch, loadedFile, text);
});
}
if (checkParsed) {
res += await runCheck(loadedFile, "PARSED", getCorrections, (query, expected, text) => {
res += await runCheck(loadedFile, "PARSED", doSearch, (query, expected, text) => {
return runParser(query, expected, parseQuery, text);
});
}
@ -416,71 +412,89 @@ async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
* @param {string} resource_suffix - Version number between filename and .js, e.g. "1.59.0"
* @returns {Object} - Object containing keys: `doSearch`, which runs a search
* with the loaded index and returns a table of results; `parseQuery`, which is the
* `parseQuery` function exported from the search module; and `getCorrections`, which runs
* `parseQuery` function exported from the search module, which runs
* a search but returns type name corrections instead of results.
*/
function loadSearchJS(doc_folder, resource_suffix) {
const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js");
const searchIndex = require(searchIndexJs);
globalThis.searchState = {
descShards: new Map(),
loadDesc: async function({descShard, descIndex}) {
if (descShard.promise === null) {
descShard.promise = new Promise((resolve, reject) => {
descShard.resolve = resolve;
const ds = descShard;
const fname = `${ds.crate}-desc-${ds.shard}-${resource_suffix}.js`;
fs.readFile(
`${doc_folder}/search.desc/${descShard.crate}/${fname}`,
(err, data) => {
if (err) {
reject(err);
} else {
eval(data.toString("utf8"));
}
},
);
});
}
const list = await descShard.promise;
return list[descIndex];
},
loadedDescShard: function(crate, shard, data) {
this.descShards.get(crate)[shard].resolve(data.split("\n"));
},
};
async function loadSearchJS(doc_folder, resource_suffix) {
const staticFiles = path.join(doc_folder, "static.files");
const stringdexJs = fs.readdirSync(staticFiles).find(f => f.match(/stringdex.*\.js$/));
const stringdexModule = require(path.join(staticFiles, stringdexJs));
const searchJs = fs.readdirSync(staticFiles).find(f => f.match(/search.*\.js$/));
const searchModule = require(path.join(staticFiles, searchJs));
searchModule.initSearch(searchIndex.searchIndex);
const docSearch = searchModule.docSearch;
globalThis.nonnull = (x, msg) => {
if (x === null) {
throw (msg || "unexpected null value!");
} else {
return x;
}
};
const { docSearch, DocSearch } = await searchModule.initSearch(
stringdexModule.Stringdex,
stringdexModule.RoaringBitmap,
{
loadRoot: callbacks => {
for (const key in callbacks) {
if (Object.hasOwn(callbacks, key)) {
globalThis[key] = callbacks[key];
}
}
const rootJs = readFile(path.join(doc_folder, "search.index/root" +
resource_suffix + ".js"));
eval(rootJs);
},
loadTreeByHash: hashHex => {
const shardJs = readFile(path.join(doc_folder, "search.index/" + hashHex + ".js"));
eval(shardJs);
},
loadDataByNameAndHash: (name, hashHex) => {
const shardJs = readFile(path.join(doc_folder, "search.index/" + name + "/" +
hashHex + ".js"));
eval(shardJs);
},
},
);
return {
doSearch: async function(queryStr, filterCrate, currentCrate) {
const result = await docSearch.execQuery(searchModule.parseQuery(queryStr),
filterCrate, currentCrate);
const parsedQuery = DocSearch.parseQuery(queryStr);
const result = await docSearch.execQuery(parsedQuery, filterCrate, currentCrate);
const resultsTable = {};
for (const tab in result) {
if (!Object.prototype.hasOwnProperty.call(result, tab)) {
continue;
}
if (!(result[tab] instanceof Array)) {
if (!isGeneratorObject(result[tab])) {
continue;
}
for (const entry of result[tab]) {
resultsTable[tab] = [];
for await (const entry of result[tab]) {
const flatEntry = Object.assign({
crate: entry.item.crate,
name: entry.item.name,
path: entry.item.modulePath,
exactPath: entry.item.exactModulePath,
ty: entry.item.ty,
}, entry);
for (const key in entry) {
if (!Object.prototype.hasOwnProperty.call(entry, key)) {
continue;
}
if (key === "displayTypeSignature" && entry.displayTypeSignature !== null) {
const {type, mappedNames, whereClause} =
await entry.displayTypeSignature;
entry.displayType = arrayToCode(type);
entry.displayMappedNames = [...mappedNames.entries()]
if (key === "desc" && entry.desc !== null) {
flatEntry.desc = await entry.desc;
} else if (key === "displayTypeSignature" &&
entry.displayTypeSignature !== null
) {
flatEntry.displayTypeSignature = await entry.displayTypeSignature;
const {
type,
mappedNames,
whereClause,
} = flatEntry.displayTypeSignature;
flatEntry.displayType = arrayToCode(type);
flatEntry.displayMappedNames = [...mappedNames.entries()]
.map(([name, qname]) => {
return `${name} = ${qname}`;
}).join(", ");
entry.displayWhereClause = [...whereClause.entries()]
flatEntry.displayWhereClause = [...whereClause.entries()]
.flatMap(([name, value]) => {
if (value.length === 0) {
return [];
@ -489,16 +503,12 @@ function loadSearchJS(doc_folder, resource_suffix) {
}).join(", ");
}
}
resultsTable[tab].push(flatEntry);
}
}
return result;
return { resultsTable, parsedQuery };
},
getCorrections: function(queryStr, filterCrate, currentCrate) {
const parsedQuery = searchModule.parseQuery(queryStr);
docSearch.execQuery(parsedQuery, filterCrate, currentCrate);
return parsedQuery.correction;
},
parseQuery: searchModule.parseQuery,
parseQuery: DocSearch.parseQuery,
};
}
@ -570,7 +580,7 @@ async function main(argv) {
return 1;
}
const parseAndSearch = loadSearchJS(
const parseAndSearch = await loadSearchJS(
opts["doc_folder"],
opts["resource_suffix"],
);
@ -579,14 +589,11 @@ async function main(argv) {
const doSearch = function(queryStr, filterCrate) {
return parseAndSearch.doSearch(queryStr, filterCrate, opts["crate_name"]);
};
const getCorrections = function(queryStr, filterCrate) {
return parseAndSearch.getCorrections(queryStr, filterCrate, opts["crate_name"]);
};
if (opts["test_file"].length !== 0) {
for (const file of opts["test_file"]) {
process.stdout.write(`Testing ${file} ... `);
errors += await runChecks(file, doSearch, parseAndSearch.parseQuery, getCorrections);
errors += await runChecks(file, doSearch, parseAndSearch.parseQuery);
}
} else if (opts["test_folder"].length !== 0) {
for (const file of fs.readdirSync(opts["test_folder"])) {
@ -595,7 +602,7 @@ async function main(argv) {
}
process.stdout.write(`Testing ${file} ... `);
errors += await runChecks(path.join(opts["test_folder"], file), doSearch,
parseAndSearch.parseQuery, getCorrections);
parseAndSearch.parseQuery);
}
}
return errors > 0 ? 1 : 0;

View file

@ -19,7 +19,7 @@ fn main() {
.args(&["--extend-css", "z.css"])
.input("x.rs")
.run();
assert!(path("invocation-only/search-index-xxx.js").exists());
assert!(path("invocation-only/search.index/root-xxx.js").exists());
assert!(path("invocation-only/crates-xxx.js").exists());
assert!(path("invocation-only/settings.html").exists());
assert!(path("invocation-only/x/all.html").exists());

View file

@ -15,7 +15,7 @@ fn main() {
rustdoc().input("foo.rs").out_dir(&bar_first).run();
diff()
.expected_file(foo_first.join("search-index.js"))
.actual_file(bar_first.join("search-index.js"))
.expected_file(foo_first.join("search.index/root.js"))
.actual_file(bar_first.join("search.index/root.js"))
.run();
}

View file

@ -5,25 +5,25 @@ include: "utils.goml"
// First we check we "hover".
move-cursor-to: ".example-wrap"
assert-css: (".example-wrap .copy-button", { "visibility": "visible" })
move-cursor-to: ".search-input"
move-cursor-to: "#search-button"
assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
// Now we check the click.
assert-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 0)
click: ".example-wrap"
move-cursor-to: ".search-input"
move-cursor-to: "#search-button"
// It should have a new class and be visible.
wait-for-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 1)
wait-for-css: (".example-wrap:not(:hover) .button-holder.keep-visible", { "visibility": "visible" })
// Clicking again will remove the class.
click: ".example-wrap"
move-cursor-to: ".search-input"
move-cursor-to: "rustdoc-toolbar #search-button"
assert-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 0)
assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
// Clicking on the "copy code" button shouldn't make the buttons stick.
click: ".example-wrap .copy-button"
move-cursor-to: ".search-input"
move-cursor-to: "#search-button"
assert-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 0)
assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
// Since we clicked on the copy button, the clipboard content should have been updated.

View file

@ -12,7 +12,7 @@ define-function: (
assert-count: (".example-wrap .copy-button", 1)
// We now ensure it's only displayed when the example is hovered.
assert-css: (".example-wrap .copy-button", { "visibility": "visible" })
move-cursor-to: ".search-input"
move-cursor-to: "rustdoc-toolbar #search-button"
assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
// Checking that the copy button has the same size as the "copy path" button.
compare-elements-size: (

View file

@ -1,4 +1,5 @@
// This test ensures that several clickable items actually have the pointer cursor.
include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/lib2/struct.Foo.html"
// the `[+]/[-]` button
@ -8,11 +9,7 @@ assert-css: ("#toggle-all-docs", {"cursor": "pointer"})
assert-css: ("#copy-path", {"cursor": "pointer"})
// the search tabs
write-into: (".search-input", "Foo")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "Foo"})
assert-css: ("#search-tabs > button", {"cursor": "pointer"})
// mobile sidebar toggle button

View file

@ -69,7 +69,7 @@ call-function: ("check-colors", {
// and make sure it goes away.
// First, open the settings menu.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-css: ("#settings", {"display": "block"})
@ -121,7 +121,7 @@ call-function: ("check-padding", {
define-function: ("check-line-numbers-existence", [], block {
assert-local-storage: {"rustdoc-line-numbers": "true" }
assert-false: ".example-line-numbers"
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
// Then, click the toggle button.
@ -137,7 +137,7 @@ define-function: ("check-line-numbers-existence", [], block {
// Line numbers should still be there.
assert-css: ("[data-nosnippet]", { "display": "block"})
// Closing settings menu.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for-css: ("#settings", {"display": "none"})
})
@ -168,7 +168,7 @@ assert: ".example-wrap > pre.rust"
assert-count: (".example-wrap", 2)
assert-count: (".example-wrap.digits-1", 2)
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
// Then, click the toggle button.

View file

@ -1,13 +1,10 @@
// This test ensures that the "Escape" shortcut is handled correctly based on the
// current content displayed.
include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// First, we check that the search results are hidden when the Escape key is pressed.
write-into: (".search-input", "test")
// To be SURE that the search will be run.
press-key: 'Enter'
wait-for: "#search h1" // The search element is empty before the first search
call-function: ("perform-search", {"query": "test"})
// Check that the currently displayed element is search.
wait-for: "#alternative-display #search"
assert-attribute: ("#main-content", {"class": "content hidden"})
assert-document-property: ({"URL": "index.html?search=test"}, ENDS_WITH)
press-key: "Escape"
@ -17,8 +14,8 @@ assert-false: "#alternative-display #search"
assert-attribute: ("#main-content", {"class": "content"})
assert-document-property: ({"URL": "index.html"}, [ENDS_WITH])
// Check that focusing the search input brings back the search results
focus: ".search-input"
// Check that clicking the search button brings back the search results
click: "#search-button"
wait-for: "#alternative-display #search"
assert-attribute: ("#main-content", {"class": "content hidden"})
assert-document-property: ({"URL": "index.html?search=test"}, ENDS_WITH)

View file

@ -8,7 +8,7 @@ assert-css: ("body", {"font-family": |serif_font|})
assert-css: ("p code", {"font-family": |serif_code_font|})
// We now switch to the sans serif font
click: "#settings-menu"
click: "main .settings-menu"
wait-for: "#sans-serif-fonts"
click: "#sans-serif-fonts"
@ -23,7 +23,7 @@ assert-css: ("body", {"font-family": |font|})
assert-css: ("p code", {"font-family": |code_font|})
// We switch back to the serif font
click: "#settings-menu"
click: "main .settings-menu"
wait-for: "#sans-serif-fonts"
click: "#sans-serif-fonts"

View file

@ -1,6 +1,7 @@
// Make sure search stores its data in `window`
// It needs to use a global to avoid racing on search-index.js and search.js
// https://github.com/rust-lang/rust/pull/118961
include: "utils.goml"
// URL query
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=sa'%3Bda'%3Bds"
@ -9,9 +10,7 @@ assert-window-property-false: {"searchIndex": null}
// Form input
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "Foo")
press-key: 'Enter'
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "Foo"})
assert-window-property-false: {"searchIndex": null}
// source sidebar

View file

@ -6,12 +6,12 @@ assert-css: ("#help", {"display": "block"})
assert-css: ("#help dd", {"font-size": "16px"})
assert-false: "#help-button > a"
assert-css: ("#help", {"display": "block"})
compare-elements-property: (".sub", "#help", ["offsetWidth"])
compare-elements-position: (".sub", "#help", ["x"])
compare-elements-property: (".main-heading", "#help", ["offsetWidth"])
compare-elements-position: (".main-heading", "#help", ["x"])
set-window-size: (500, 1000) // Try mobile next.
assert-css: ("#help", {"display": "block"})
compare-elements-property: (".sub", "#help", ["offsetWidth"])
compare-elements-position: (".sub", "#help", ["x"])
compare-elements-property: (".main-heading", "#help", ["offsetWidth"])
compare-elements-position: (".main-heading", "#help", ["x"])
// Checking the color of the elements of the help menu.
show-text: true
@ -54,19 +54,17 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a"
wait-for: "#search-tabs" // Waiting for the search.js to load.
set-window-size: (1000, 1000) // Only supported on desktop.
assert-false: "#help"
click: "#help-button > a"
click: "rustdoc-toolbar .help-menu > a"
assert-css: ("#help", {"display": "block"})
assert-css: ("#help dd", {"font-size": "16px"})
click: "#help-button > a"
assert-css: ("#help", {"display": "none"})
compare-elements-property-false: (".sub", "#help", ["offsetWidth"])
compare-elements-position-false: (".sub", "#help", ["x"])
click: "rustdoc-toolbar .help-menu > a"
assert-false: "#help"
// This test ensures that the "the rustdoc book" anchor link within the help popover works.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a"
wait-for: "#search-tabs" // Waiting for the search.js to load.
set-window-size: (1000, 1000) // Popover only appears when the screen width is >700px.
assert-false: "#help"
click: "#help-button > a"
click: "rustdoc-toolbar .help-menu > a"
click: "//*[@id='help']//a[text()='the rustdoc book']"
wait-for-document-property: ({"URL": "https://doc.rust-lang.org/"}, STARTS_WITH)

View file

@ -1,20 +1,19 @@
// Checks sidebar resizing stays synced with the setting
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
go-to: "file://" + |DOC_PATH| + "/settings.html"
set-window-size: (400, 600)
// Verify that the "hide" option is unchecked
click: "#settings-menu"
wait-for: "#settings"
assert-css: ("#settings", {"display": "block"})
assert-property: ("#hide-sidebar", {"checked": "false"})
assert-css: (".mobile-topbar", {"display": "flex"})
assert-css: ("rustdoc-topbar", {"display": "flex"})
// Toggle it
click: "#hide-sidebar"
assert-property: ("#hide-sidebar", {"checked": "true"})
assert-css: (".mobile-topbar", {"display": "none"})
assert-css: ("rustdoc-topbar", {"display": "none"})
// Toggle it again
click: "#hide-sidebar"
assert-property: ("#hide-sidebar", {"checked": "false"})
assert-css: (".mobile-topbar", {"display": "flex"})
assert-css: ("rustdoc-topbar", {"display": "flex"})

View file

@ -8,8 +8,3 @@ assert-property: (".sidebar-crate .logo-container", {"offsetWidth": "96", "offse
// offsetWidth = width of sidebar, offsetHeight = height + top padding
assert-property: (".sidebar-crate .logo-container img", {"offsetWidth": "48", "offsetHeight": 64})
assert-css: (".sidebar-crate .logo-container img", {"border-top-width": "16px", "margin-top": "-16px"})
set-window-size: (400, 600)
// offset = size + margin
assert-property: (".mobile-topbar .logo-container", {"offsetWidth": "55", "offsetHeight": 45})
assert-property: (".mobile-topbar .logo-container img", {"offsetWidth": "35", "offsetHeight": 35})

View file

@ -20,7 +20,7 @@ store-position: (
{"x": second_line_x, "y": second_line_y},
)
assert: |first_line_x| != |second_line_x| && |first_line_x| == 521 && |second_line_x| == 277
assert: |first_line_y| != |second_line_y| && |first_line_y| == 718 && |second_line_y| == 741
assert: |first_line_y| != |second_line_y| && |first_line_y| == 676 && |second_line_y| == 699
// Now we ensure that they're not rendered on the same line.
set-window-size: (1100, 800)

View file

@ -5,18 +5,18 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// First we change the title to make it big.
set-window-size: (350, 800)
// We ensure that the "format" of the title is the same as the one we'll use.
assert-text: (".mobile-topbar .location a", "test_docs")
assert-text: ("rustdoc-topbar h2 a", "Crate test_docs")
// We store the height we know is correct.
store-property: (".mobile-topbar .location", {"offsetHeight": height})
store-property: ("rustdoc-topbar h2", {"offsetHeight": height})
// We change the crate name to something longer.
set-text: (".mobile-topbar .location a", "cargo_packager_resource_resolver")
set-text: ("rustdoc-topbar h2 a", "cargo_packager_resource_resolver")
// And we check that the size remained the same.
assert-property: (".mobile-topbar .location", {"offsetHeight": |height|})
assert-property: ("rustdoc-topbar h2", {"offsetHeight": |height|})
// Now we check if it works for the non-crate pages as well.
go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
// We store the height we know is correct.
store-property: (".mobile-topbar .location", {"offsetHeight": height})
set-text: (".mobile-topbar .location a", "Something_incredibly_long_because")
store-property: ("rustdoc-topbar h2", {"offsetHeight": height})
set-text: ("rustdoc-topbar h2 a", "Something_incredibly_long_because")
// And we check that the size remained the same.
assert-property: (".mobile-topbar .location", {"offsetHeight": |height|})
assert-property: ("rustdoc-topbar h2", {"offsetHeight": |height|})

View file

@ -5,7 +5,7 @@ set-window-size: (400, 600)
set-font-size: 18
wait-for: 100 // wait a bit for the resize and the font-size change to be fully taken into account.
assert-property: (".mobile-topbar h2", {"offsetHeight": 33})
assert-property: ("rustdoc-topbar h2", {"offsetHeight": 33})
// On the settings page, the theme buttons should not line-wrap. Instead, they should
// all be placed as a group on a line below the setting name "Theme."

View file

@ -82,15 +82,6 @@ call-function: ("check-notable-tooltip-position", {
"i_x": 528,
})
// Checking on mobile now.
set-window-size: (650, 600)
wait-for-size: ("body", {"width": 650})
call-function: ("check-notable-tooltip-position-complete", {
"x": 26,
"i_x": 305,
"popover_x": 0,
})
// Now check the colors.
define-function: (
"check-colors",
@ -176,6 +167,15 @@ call-function: (
},
)
// Checking on mobile now.
set-window-size: (650, 600)
wait-for-size: ("body", {"width": 650})
call-function: ("check-notable-tooltip-position-complete", {
"x": 26,
"i_x": 305,
"popover_x": 0,
})
reload:
// Check that pressing escape works
@ -189,7 +189,7 @@ assert: "#method\.create_an_iterator_from_read .tooltip:focus"
// Check that clicking outside works.
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
assert-count: ("//*[@class='tooltip popover']", 1)
click: ".search-input"
click: ".main-heading h1"
assert-count: ("//*[@class='tooltip popover']", 0)
assert-false: "#method\.create_an_iterator_from_read .tooltip:focus"
@ -219,14 +219,14 @@ define-function: (
store-window-property: {"scrollY": scroll}
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
wait-for: "//*[@class='tooltip popover']"
click: "#settings-menu a"
click: ".main-heading h1"
}
)
// Now we check that the focus isn't given back to the wrong item when opening
// another popover.
call-function: ("setup-popup", {})
click: ".search-input"
click: ".main-heading h1"
// We ensure we didn't come back to the previous focused item.
assert-window-property-false: {"scrollY": |scroll|}
@ -251,7 +251,7 @@ reload:
assert-count: ("//*[@class='tooltip popover']", 0)
click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
assert-count: ("//*[@class='tooltip popover']", 1)
click: "#settings-menu a"
click: "rustdoc-toolbar .settings-menu a"
wait-for: "#settings"
assert-count: ("//*[@class='tooltip popover']", 0)
assert-false: "#method\.create_an_iterator_from_read .tooltip:focus"

View file

@ -3,33 +3,33 @@ include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=test"
wait-for: "#crate-search"
// First we check that the help menu doesn't exist yet.
assert-false: "#help-button .popover"
assert-false: "rustdoc-toolbar .help-menu .popover"
// Then we display the help menu.
click: "#help-button"
assert: "#help-button .popover"
assert-css: ("#help-button .popover", {"display": "block"})
click: "rustdoc-toolbar .help-menu"
assert: "rustdoc-toolbar .help-menu .popover"
assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
// Now we click somewhere else on the page to ensure it is handling the blur event
// correctly.
click: ".sidebar"
assert-css: ("#help-button .popover", {"display": "none"})
assert-false: "rustdoc-toolbar .help-menu .popover"
// Now we will check that we cannot have two "pocket menus" displayed at the same time.
click: "#help-button"
assert-css: ("#help-button .popover", {"display": "block"})
click: "#settings-menu"
assert-css: ("#help-button .popover", {"display": "none"})
assert-css: ("#settings-menu .popover", {"display": "block"})
click: "rustdoc-toolbar .help-menu"
assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
click: "rustdoc-toolbar .settings-menu"
assert-false: "rustdoc-toolbar .help-menu .popover"
assert-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
// Now the other way.
click: "#help-button"
assert-css: ("#help-button .popover", {"display": "block"})
assert-css: ("#settings-menu .popover", {"display": "none"})
click: "rustdoc-toolbar .help-menu"
assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
assert-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
// Now verify that clicking the help menu again closes it.
click: "#help-button"
assert-css: ("#help-button .popover", {"display": "none"})
assert-css: ("#settings-menu .popover", {"display": "none"})
click: "rustdoc-toolbar .help-menu"
assert-false: "rustdoc-toolbar .help-menu .popover"
assert-css: (".settings-menu .popover", {"display": "none"})
define-function: (
"check-popover-colors",
@ -37,13 +37,21 @@ define-function: (
block {
call-function: ("switch-theme", {"theme": |theme|})
click: "#help-button"
click: "rustdoc-toolbar .help-menu"
assert-css: (
"#help-button .popover",
"rustdoc-toolbar .help-menu .popover",
{"display": "block", "border-color": |border_color|},
)
compare-elements-css: ("#help-button .popover", "#help-button .top", ["border-color"])
compare-elements-css: ("#help-button .popover", "#help-button .bottom", ["border-color"])
compare-elements-css: (
"rustdoc-toolbar .help-menu .popover",
"rustdoc-toolbar .help-menu .top",
["border-color"],
)
compare-elements-css: (
"rustdoc-toolbar .help-menu .popover",
"rustdoc-toolbar .help-menu .bottom",
["border-color"],
)
}
)
@ -63,8 +71,21 @@ call-function: ("check-popover-colors", {
// Opening the mobile sidebar should close the settings popover.
set-window-size: (650, 600)
click: "#settings-menu a"
assert-css: ("#settings-menu .popover", {"display": "block"})
click: "rustdoc-topbar .settings-menu a"
assert-css: ("rustdoc-topbar .settings-menu .popover", {"display": "block"})
click: ".sidebar-menu-toggle"
assert: "//*[@class='sidebar shown']"
assert-css: ("#settings-menu .popover", {"display": "none"})
assert-css: ("rustdoc-topbar .settings-menu .popover", {"display": "none"})
// Opening the settings popover should close the sidebar.
click: ".settings-menu a"
assert-css: ("rustdoc-topbar .settings-menu .popover", {"display": "block"})
assert-false: "//*[@class='sidebar shown']"
// Opening the settings popover at start (which async loads stuff) should also close.
reload:
click: ".sidebar-menu-toggle"
assert: "//*[@class='sidebar shown']"
assert-false: "rustdoc-topbar .settings-menu .popover"
click: "rustdoc-topbar .settings-menu a"
assert-false: "//*[@class='sidebar shown']"
wait-for: "rustdoc-topbar .settings-menu .popover"

View file

@ -27,7 +27,7 @@ define-function: (
"color": |help_hover_color|,
})
// Moving the cursor to another item to not break next runs.
move-cursor-to: ".search-input"
move-cursor-to: "#search-button"
}
)

View file

@ -64,8 +64,8 @@ assert-size: (".more-scraped-examples .scraped-example .example-wrap", {
store-value: (offset_y, 4)
// First with desktop
assert-position: (".scraped-example", {"y": 256})
assert-position: (".scraped-example .prev", {"y": 256 + |offset_y|})
assert-position: (".scraped-example", {"y": 214})
assert-position: (".scraped-example .prev", {"y": 214 + |offset_y|})
// Gradient background should be at the top of the code block.
assert-css: (".scraped-example .example-wrap::before", {"top": "0px"})
@ -74,8 +74,8 @@ assert-css: (".scraped-example .example-wrap::after", {"bottom": "0px"})
// Then with mobile
set-window-size: (600, 600)
store-size: (".scraped-example .scraped-example-title", {"height": title_height})
assert-position: (".scraped-example", {"y": 291})
assert-position: (".scraped-example .prev", {"y": 291 + |offset_y| + |title_height|})
assert-position: (".scraped-example", {"y": 249})
assert-position: (".scraped-example .prev", {"y": 249 + |offset_y| + |title_height|})
define-function: (
"check_title_and_code_position",

View file

@ -25,7 +25,7 @@ define-function: (
// We put the toggle in the original state.
click: ".more-examples-toggle"
// Moving cursor away from the toggle line to prevent disrupting next test.
move-cursor-to: ".search-input"
move-cursor-to: "rustdoc-toolbar #search-button"
},
)

View file

@ -7,6 +7,7 @@ focus: ".search-input"
press-key: "Enter"
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-count: ("#search-tabs button", 1)
assert-count: (".search-results > a", 1)
@ -32,6 +33,7 @@ focus: ".search-input"
press-key: "Enter"
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-text: ("//div[@class='type-signature']", "F -> WhereWhitespace<T>")
assert-count: ("#search-tabs button", 1)
assert-count: (".search-results > a", 1)

View file

@ -1,101 +1,60 @@
// ignore-tidy-linelength
include: "utils.goml"
// Checks that the search tab result tell the user about corrections
// First, try a search-by-name
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write-into: (".search-input", "NotableStructWithLongNamr")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "NotableStructWithLongNamr"})
// Corrections aren't shown on the "In Names" tab.
assert: "#search-tabs button.selected:first-child"
assert-css: (".search-corrections", {
"display": "none"
})
assert-false: ".search-results:nth-child(1) .search-corrections"
// Corrections do get shown on the "In Parameters" tab.
click: "#search-tabs button:nth-child(2)"
assert: "#search-tabs button.selected:nth-child(2)"
assert-css: (".search-corrections", {
"display": "block"
})
assert-text: (
".search-corrections",
"Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
".search-results:nth-child(2) .search-corrections",
"Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"NotableStructWithLongName\" instead."
)
// Corrections do get shown on the "In Return Type" tab.
click: "#search-tabs button:nth-child(3)"
assert: "#search-tabs button.selected:nth-child(3)"
assert-css: (".search-corrections", {
"display": "block"
})
assert-text: (
".search-corrections",
"Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
".search-results:nth-child(3) .search-corrections",
"Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"NotableStructWithLongName\" instead."
)
// Now, explicit return values
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write-into: (".search-input", "-> NotableStructWithLongNamr")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "-> NotableStructWithLongNamr"})
assert-css: (".search-corrections", {
"display": "block"
})
assert-text: (
".search-corrections",
"Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
".search-results.active .search-corrections",
"Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"NotableStructWithLongName\" instead."
)
// Now, generic correction
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write-into: (".search-input", "NotableStructWithLongNamr, NotableStructWithLongNamr")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "NotableStructWithLongNamr, NotableStructWithLongNamr"})
assert-css: (".search-corrections", {
"display": "block"
})
assert-text: (
".search-corrections",
"Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
".search-failed.active .search-corrections",
"Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"NotableStructWithLongName\" instead."
)
// Now, generic correction plus error
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write-into: (".search-input", "Foo<NotableStructWithLongNamr>,y")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "Foo<NotableStructWithLongNamr>,y"})
assert-css: (".search-corrections", {
"display": "block"
})
assert-text: (
".search-corrections",
"Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
".search-failed.active .search-corrections",
"Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"NotableStructWithLongName\" instead."
)
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// Intentionally wrong spelling of "NotableStructWithLongName"
write-into: (".search-input", "generic:NotableStructWithLongNamr<x>,y")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "generic:NotableStructWithLongNamr<x>,y"})
assert-css: (".error", {
"display": "block"

View file

@ -8,6 +8,7 @@ define-function: (
[theme, error_background],
block {
call-function: ("switch-theme", {"theme": |theme|})
wait-for-false: "#search-tabs .count.loading"
wait-for: "#search .error code"
assert-css: ("#search .error code", {"background-color": |error_background|})
}

View file

@ -2,11 +2,7 @@
include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
show-text: true
write-into: (".search-input", "test")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "test"})
assert-text: ("#results .externcrate", "test_docs")
wait-for: "#crate-search"
@ -21,6 +17,7 @@ press-key: "ArrowDown"
press-key: "Enter"
// Waiting for the search results to appear...
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-document-property: ({"URL": "&filter-crate="}, CONTAINS)
// We check that there is no more "test_docs" appearing.
assert-false: "#results .externcrate"
@ -31,7 +28,8 @@ assert-property: ("#crate-search", {"value": "lib2"})
// crate filtering.
press-key: "Escape"
wait-for-css: ("#main-content", {"display": "block"})
focus: ".search-input"
click: "#search-button"
wait-for: ".search-input"
wait-for-css: ("#main-content", {"display": "none"})
// We check that there is no more "test_docs" appearing.
assert-false: "#results .externcrate"
@ -47,6 +45,7 @@ press-key: "ArrowUp"
press-key: "Enter"
// Waiting for the search results to appear...
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-property: ("#crate-search", {"value": "all crates"})
// Checking that the URL parameter is taken into account for crate filtering.
@ -56,8 +55,7 @@ assert-property: ("#crate-search", {"value": "lib2"})
assert-false: "#results .externcrate"
// Checking that the text for the "title" is correct (the "all crates" comes from the "<select>").
assert-text: (".search-results-title", "Results", STARTS_WITH)
assert-text: (".search-results-title + .sub-heading", " in all crates", STARTS_WITH)
assert-text: (".search-switcher", "Search results in all crates", STARTS_WITH)
// Checking the display of the crate filter.
// We start with the light theme.
@ -72,7 +70,7 @@ assert-css: ("#crate-search", {
})
// We now check the dark theme.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
click: "#theme-dark"
wait-for-css: ("#crate-search", {

View file

@ -2,6 +2,7 @@
include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=test"
wait-for: "#search-tabs" // Waiting for the search.js to load.
wait-for-false: "#search-tabs .count.loading"
show-text: true
define-function: (
@ -31,7 +32,7 @@ define-function: (
},
)
assert-css: (
"#help-button > a",
"rustdoc-toolbar .help-menu > a",
{
"color": |menu_button_a_color|,
"border-color": "transparent",
@ -39,9 +40,9 @@ define-function: (
},
)
// Hover help button.
move-cursor-to: "#help-button"
move-cursor-to: "rustdoc-toolbar .help-menu"
assert-css: (
"#help-button > a",
"rustdoc-toolbar .help-menu > a",
{
"color": |menu_button_a_color|,
"border-color": |menu_button_a_border_hover|,
@ -49,15 +50,15 @@ define-function: (
},
)
// Link color inside
click: "#help-button"
click: "rustdoc-toolbar .help-menu"
assert-css: (
"#help a",
"rustdoc-toolbar #help a",
{
"color": |menu_a_color|,
},
)
assert-css: (
"#settings-menu > a",
"rustdoc-toolbar .settings-menu > a",
{
"color": |menu_button_a_color|,
"border-color": "transparent",
@ -65,9 +66,9 @@ define-function: (
},
)
// Hover settings menu.
move-cursor-to: "#settings-menu"
move-cursor-to: "rustdoc-toolbar .settings-menu"
assert-css: (
"#settings-menu:hover > a",
"rustdoc-toolbar .settings-menu:hover > a",
{
"color": |menu_button_a_color|,
"border-color": |menu_button_a_border_hover|,
@ -120,8 +121,10 @@ call-function: (
// Check that search input correctly decodes form encoding.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a+b"
wait-for: "#search-tabs" // Waiting for the search.js to load.
wait-for-false: "#search-tabs .count.loading"
assert-property: (".search-input", { "value": "a b" })
// Check that literal + is not treated as space.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a%2Bb"
wait-for: "#search-tabs" // Waiting for the search.js to load.
wait-for-false: "#search-tabs .count.loading"
assert-property: (".search-input", { "value": "a+b" })

View file

@ -2,10 +2,13 @@
// The PR which fixed it is: https://github.com/rust-lang/rust/pull/81592
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
set-window-size: (463, 700)
// We first check that the search input isn't already focused.
assert-false: ("input.search-input:focus")
click: "input.search-input"
click: "#search-button"
wait-for: ".search-input"
assert: "input.search-input:focus"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
reload:
set-window-size: (750, 700)
click: "input.search-input"
assert: ("input.search-input:focus")
click: "#search-button"
wait-for: ".search-input"
assert: "input.search-input:focus"

View file

@ -1,28 +1,25 @@
// Checks that the search tab results work correctly with function signature syntax
// First, try a search-by-name
include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "Foo")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "Foo"})
// Now use the keyboard commands to switch to the third result.
press-key: "ArrowDown"
press-key: "ArrowDown"
press-key: "ArrowDown"
assert: ".search-results.active > a:focus:nth-of-type(3)"
wait-for: ".search-results.active > a:focus:nth-of-type(3)"
// Now switch to the second tab, then back to the first one, then arrow back up.
press-key: "ArrowRight"
assert: ".search-results.active:nth-of-type(2) > a:focus:nth-of-type(1)"
wait-for: ".search-results.active:nth-of-type(2) > a:focus:nth-of-type(1)"
press-key: "ArrowLeft"
assert: ".search-results.active:nth-of-type(1) > a:focus:nth-of-type(3)"
wait-for: ".search-results.active:nth-of-type(1) > a:focus:nth-of-type(3)"
press-key: "ArrowUp"
assert: ".search-results.active > a:focus:nth-of-type(2)"
wait-for: ".search-results.active > a:focus:nth-of-type(2)"
press-key: "ArrowUp"
assert: ".search-results.active > a:focus:nth-of-type(1)"
wait-for: ".search-results.active > a:focus:nth-of-type(1)"
press-key: "ArrowUp"
assert: ".search-input:focus"
wait-for: ".search-input:focus"
press-key: "ArrowDown"
assert: ".search-results.active > a:focus:nth-of-type(1)"
wait-for: ".search-results.active > a:focus:nth-of-type(1)"

View file

@ -6,10 +6,8 @@ call-function: ("switch-theme", {"theme": "dark"})
// First we check that the reexport has the correct ID and no background color.
assert-text: ("//*[@id='reexport.TheStdReexport']", "pub use ::std as TheStdReexport;")
assert-css: ("//*[@id='reexport.TheStdReexport']", {"background-color": "rgba(0, 0, 0, 0)"})
write-into: (".search-input", "TheStdReexport")
// To be SURE that the search will be run.
press-key: 'Enter'
wait-for: "//a[@class='result-import']"
call-function: ("perform-search", {"query": "TheStdReexport"})
assert: "//a[@class='result-import']"
assert-attribute: (
"//a[@class='result-import']",
{"href": "../test_docs/index.html#reexport.TheStdReexport"},
@ -21,9 +19,8 @@ wait-for-css: ("//*[@id='reexport.TheStdReexport']", {"background-color": "#494a
// We now check that the alias is working as well on the reexport.
// To be SURE that the search will be run.
press-key: 'Enter'
write-into: (".search-input", "AliasForTheStdReexport")
wait-for: "//a[@class='result-import']"
call-function: ("perform-search", {"query": "AliasForTheStdReexport"})
assert: "//a[@class='result-import']"
assert-text: (
"a.result-import .result-name",
"re-export AliasForTheStdReexport - see test_docs::TheStdReexport",

View file

@ -14,6 +14,7 @@ define-function: (
// Waiting for the search results to appear...
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-css: (
"#search-tabs > button > .count",
{"color": |count_color|},
@ -212,11 +213,7 @@ call-function: ("check-search-color", {
// Check the alias.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "thisisanalias")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "thisisanalias"})
define-function: (
"check-alias",

View file

@ -2,4 +2,5 @@
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=some_more_function"
// Waiting for the search results to appear...
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-text: (".search-results .desc code", "format!")

View file

@ -7,6 +7,7 @@ write-into: (".search-input", "test")
// To be SURE that the search will be run.
press-key: 'Enter'
wait-for: "#crate-search"
wait-for-false: "#search-tabs .count.loading"
// The width is returned by "getComputedStyle" which returns the exact number instead of the
// CSS rule which is "50%"...
assert-size: (".search-results div.desc", {"width": 248})
@ -34,6 +35,7 @@ assert: |new_width| < |width| - 10
// Check that if the search is too long on mobile, it'll go under the "typename".
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName"
wait-for: "#crate-search"
wait-for-false: "#search-tabs .count.loading"
compare-elements-position-near: (
".search-results .result-name .typename",
".search-results .result-name .path",
@ -51,7 +53,7 @@ set-window-size: (900, 900)
// First we check the current width, height and position.
assert-css: ("#crate-search", {"width": "159px"})
store-size: (".search-results-title", {
store-size: (".search-switcher", {
"height": search_results_title_height,
"width": search_results_title_width,
})
@ -64,8 +66,8 @@ set-text: (
)
// Then we compare again to confirm the height didn't change.
assert-size: ("#crate-search", {"width": 370})
assert-size: (".search-results-title", {
assert-size: ("#crate-search", {"width": 185})
assert-size: (".search-switcher", {
"height": |search_results_title_height|,
})
assert-css: ("#search", {"width": "640px"})
@ -79,6 +81,7 @@ define-function: (
block {
call-function: ("switch-theme", {"theme": |theme|})
wait-for: "#crate-search"
wait-for-false: "#search-tabs .count.loading"
assert-css: ("#crate-search", {"border": "1px solid " + |border|})
assert-css: ("#crate-search-div::after", {"filter": |filter|})
move-cursor-to: "#crate-search"

View file

@ -9,6 +9,7 @@ assert-text-false: (".main-heading h1", "Struct test_docs::FooCopy item path")
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=struct%3AFoo"
// Waiting for the search results to appear...
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-text-false: (".main-heading h1", "Struct test_docs::FooCopy item path")
// Ensure that the search results are displayed, not the "normal" content.
assert-css: ("#main-content", {"display": "none"})
@ -17,4 +18,4 @@ assert-css: ("#main-content", {"display": "none"})
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=struct%3AFoo&go_to_first=true"
// Waiting for the page to load...
wait-for-text: (".main-heading .rustdoc-breadcrumbs", "test_docs")
wait-for-text: (".main-heading h1", "Struct FooCopy item path")
wait-for-text: (".main-heading h1", "Struct Foo Copy item path")

View file

@ -1,15 +1,12 @@
// ignore-tidy-linelength
include: "utils.goml"
// Checks that, if a type has two methods with the same name, they both get
// linked correctly.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// This should link to the inherent impl
write-into: (".search-input", "ZyxwvutMethodDisambiguation -> bool")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "ZyxwvutMethodDisambiguation -> bool"})
// Check the disambiguated link.
assert-count: ("a.result-method", 1)
assert-attribute: ("a.result-method", {
@ -25,11 +22,7 @@ assert: "section:target"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
// This should link to the trait impl
write-into: (".search-input", "ZyxwvutMethodDisambiguation, usize -> usize")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "ZyxwvutMethodDisambiguation, usize -> usize"})
// Check the disambiguated link.
assert-count: ("a.result-method", 1)
assert-attribute: ("a.result-method", {
@ -47,6 +40,7 @@ assert: "section:target"
// impl block's disambiguator is also acted upon.
go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=MultiImplBlockStruct->bool"
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-count: ("a.result-method", 1)
assert-attribute: ("a.result-method", {
"href": "../lib2/another_mod/struct.MultiImplBlockStruct.html#impl-MultiImplBlockStruct/method.second_fn"
@ -56,6 +50,7 @@ wait-for: "details:has(summary > #impl-MultiImplBlockStruct-1) > div section[id=
go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=MultiImplBlockStruct->u32"
wait-for: "#search-tabs"
wait-for-false: "#search-tabs .count.loading"
assert-count: ("a.result-method", 1)
assert-attribute: ("a.result-method", {
"href": "../lib2/another_mod/struct.MultiImplBlockStruct.html#impl-MultiImplBlockTrait-for-MultiImplBlockStruct/method.second_fn"

View file

@ -1,8 +1,5 @@
// Checks that the "keyword" results have the expected text alongside them.
include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "for")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "for"})
assert-text: (".result-keyword .result-name", "keyword for")

View file

@ -1,11 +1,9 @@
// Checks that the search tab results work correctly with function signature syntax
// First, try a search-by-name
include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "Foo")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "Foo"})
assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
assert-text: ("#search-tabs > button:nth-of-type(1)", "In Names", STARTS_WITH)
assert: "input.search-input:focus"
@ -23,11 +21,7 @@ wait-for-attribute: ("#search-tabs > button:nth-of-type(3)", {"class": "selected
// Now try search-by-return
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "-> String")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "-> String"})
assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Return Types", STARTS_WITH)
assert: "input.search-input:focus"
@ -45,30 +39,18 @@ wait-for-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected
// Try with a search-by-return with no results
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "-> Something")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "-> Something"})
assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Return Types", STARTS_WITH)
// Try with a search-by-parameter
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "usize,pattern")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "usize,pattern"})
assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Parameters", STARTS_WITH)
// Try with a search-by-parameter-and-return
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
write-into: (".search-input", "pattern -> str")
// To be SURE that the search will be run.
press-key: 'Enter'
// Waiting for the search results to appear...
wait-for: "#search-tabs"
call-function: ("perform-search", {"query": "pattern -> str"})
assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Signatures", STARTS_WITH)

View file

@ -15,7 +15,8 @@ define-function: (
focus: ".search-input"
press-key: "Enter"
wait-for: "#search-tabs"
wait-for: "#search-tabs .count"
wait-for-false: "#search-tabs .count.loading"
assert-css: ("#search-tabs > button:not(.selected)", {
"background-color": |background|,
"border-bottom": |border_bottom|,

View file

@ -5,10 +5,7 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
store-value: (title, "test_docs - Rust")
assert-document-property: {"title": |title|}
write-into: (".search-input", "test")
// To be SURE that the search will be run.
press-key: 'Enter'
wait-for: "#crate-search"
call-function: ("perform-search", {"query": "test"})
assert-document-property: {"title": '"test" Search - Rust'}
@ -16,6 +13,7 @@ set-property: (".search-input", {"value": "another one"})
// To be SURE that the search will be run.
press-key: 'Enter'
wait-for: "#crate-search"
wait-for-false: "#search-tabs .count.loading"
assert-document-property: {"title": '"another one" Search - Rust'}

View file

@ -9,7 +9,7 @@ define-function: (
[storage_value, setting_attribute_value, toggle_attribute_value],
block {
assert-local-storage: {"rustdoc-auto-hide-large-items": |storage_value|}
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-property: ("#auto-hide-large-items", {"checked": |setting_attribute_value|})
assert-attribute: (".item-decl .type-contents-toggle", {"open": |toggle_attribute_value|})

View file

@ -6,7 +6,7 @@ define-function: (
[storage_value, setting_attribute_value, toggle_attribute_value],
block {
assert-local-storage: {"rustdoc-auto-hide-method-docs": |storage_value|}
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-property: ("#auto-hide-method-docs", {"checked": |setting_attribute_value|})
assert-attribute: (".toggle.method-toggle", {"open": |toggle_attribute_value|})

View file

@ -5,7 +5,7 @@ define-function: (
[storage_value, setting_attribute_value, toggle_attribute_value],
block {
assert-local-storage: {"rustdoc-auto-hide-trait-implementations": |storage_value|}
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-property: ("#auto-hide-trait-implementations", {"checked": |setting_attribute_value|})
assert-attribute: ("#trait-implementations-list > details", {"open": |toggle_attribute_value|}, ALL)

View file

@ -5,7 +5,7 @@ define-function: (
[storage_value, setting_attribute_value],
block {
assert-local-storage: {"rustdoc-go-to-only-result": |storage_value|}
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-property: ("#go-to-only-result", {"checked": |setting_attribute_value|})
}
@ -25,7 +25,7 @@ wait-for: "#search"
assert-document-property: ({"URL": "/lib2/index.html"}, CONTAINS)
// Now we change its value.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
click: "#go-to-only-result"
assert-local-storage: {"rustdoc-go-to-only-result": "true"}

View file

@ -9,7 +9,7 @@ define-function: (
[theme, filter],
block {
call-function: ("switch-theme", {"theme": |theme|})
assert-css: ("#settings-menu > a::before", {
assert-css: ("rustdoc-toolbar .settings-menu > a::before", {
"filter": |filter|,
"width": "18px",
"height": "18px",

View file

@ -5,7 +5,7 @@ show-text: true // needed when we check for colors below.
// First, we check that the settings page doesn't exist.
assert-false: "#settings"
// We now click on the settings button.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-css: ("#settings", {"display": "block"})
@ -13,11 +13,11 @@ assert-css: ("#settings", {"display": "block"})
store-css: (".setting-line", {"margin": setting_line_margin})
// Let's close it by clicking on the same button.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for-css: ("#settings", {"display": "none"})
// Let's check that pressing "ESCAPE" is closing it.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for-css: ("#settings", {"display": "block"})
press-key: "Escape"
wait-for-css: ("#settings", {"display": "none"})
@ -28,7 +28,7 @@ write: "test"
// To be SURE that the search will be run.
press-key: 'Enter'
wait-for: "#alternative-display #search"
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for-css: ("#settings", {"display": "block"})
// Ensure that the search is still displayed.
wait-for: "#alternative-display #search"
@ -41,7 +41,7 @@ set-local-storage: {"rustdoc-theme": "dark", "rustdoc-use-system-theme": "false"
// We reload the page so the local storage settings are being used.
reload:
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
// We check that the "Use system theme" is disabled.
@ -55,7 +55,7 @@ assert: "#preferred-light-theme.setting-line.hidden"
assert-property: ("#theme .setting-radio-choices #theme-dark", {"checked": "true"})
// Some style checks...
move-cursor-to: "#settings-menu > a"
move-cursor-to: "rustdoc-toolbar .settings-menu > a"
// First we check the "default" display for radio buttons.
assert-css: (
"#theme-dark",
@ -194,7 +194,7 @@ assert-css: (
"border-width": "2px",
},
)
move-cursor-to: "#settings-menu > a"
move-cursor-to: "rustdoc-toolbar .settings-menu > a"
// Let's now check with the focus for toggles.
focus: "#auto-hide-large-items"
assert-css: (
@ -273,43 +273,43 @@ assert-local-storage: {"rustdoc-disable-shortcuts": "true"}
press-key: "Escape"
press-key: "?"
assert-false: "#help-button .popover"
wait-for-css: ("#settings-menu .popover", {"display": "block"})
wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
// Now turn keyboard shortcuts back on, and see if they work.
click: "#disable-shortcuts"
assert-local-storage: {"rustdoc-disable-shortcuts": "false"}
press-key: "Escape"
press-key: "?"
wait-for-css: ("#help-button .popover", {"display": "block"})
assert-css: ("#settings-menu .popover", {"display": "none"})
wait-for-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
assert-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
// Now switch back to the settings popover, and make sure the keyboard
// shortcut works when a check box is selected.
click: "#settings-menu > a"
wait-for-css: ("#settings-menu .popover", {"display": "block"})
click: "rustdoc-toolbar .settings-menu > a"
wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
focus: "#auto-hide-large-items"
press-key: "?"
wait-for-css: ("#settings-menu .popover", {"display": "none"})
wait-for-css: ("#help-button .popover", {"display": "block"})
wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
wait-for-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
// Now switch back to the settings popover, and make sure the keyboard
// shortcut works when a check box is selected.
click: "#settings-menu > a"
wait-for-css: ("#settings-menu .popover", {"display": "block"})
wait-for-css: ("#help-button .popover", {"display": "none"})
click: "rustdoc-toolbar .settings-menu > a"
wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
assert-false: "rustdoc-toolbar .help-menu .popover"
focus: "#theme-system-preference"
press-key: "?"
wait-for-css: ("#settings-menu .popover", {"display": "none"})
wait-for-css: ("#help-button .popover", {"display": "block"})
wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
wait-for-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
// Now we go to the settings page to check that the CSS is loaded as expected.
go-to: "file://" + |DOC_PATH| + "/settings.html"
wait-for: "#settings"
assert-false: "#settings-menu"
assert-false: "rustdoc-toolbar .settings-menu"
assert-css: (".setting-radio", {"cursor": "pointer"})
assert-attribute-false: ("#settings", {"class": "popover"}, CONTAINS)
compare-elements-position: (".sub form", "#settings", ["x"])
compare-elements-position: (".main-heading", "#settings", ["x"])
// Check that setting-line has the same margin in this mode as in the popover.
assert-css: (".setting-line", {"margin": |setting_line_margin|})

View file

@ -8,9 +8,9 @@ press-key: "Escape"
assert-false: "input.search-input:focus"
// We now check for the help popup.
press-key: "?"
assert-css: ("#help-button .popover", {"display": "block"})
assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
press-key: "Escape"
assert-css: ("#help-button .popover", {"display": "none"})
assert-false: "rustdoc-toolbar .help-menu .popover"
// Checking doc collapse and expand.
// It should be displaying a "-":
assert-text: ("#toggle-all-docs", "Summary")

View file

@ -17,7 +17,7 @@ assert-css: (".sidebar", {"display": "block", "left": "-1000px"})
focus: ".sidebar-elems h3 a"
assert-css: (".sidebar", {"display": "block", "left": "0px"})
// When we tab out of the sidebar, close it.
focus: ".search-input"
focus: "#search-button"
assert-css: (".sidebar", {"display": "block", "left": "-1000px"})
// Open the sidebar menu.
@ -43,7 +43,7 @@ press-key: "Escape"
assert-css: (".sidebar", {"display": "block", "left": "-1000px"})
// Check that the topbar is visible
assert-property: (".mobile-topbar", {"clientHeight": "45"})
assert-property: ("rustdoc-topbar", {"clientHeight": "45"})
// Check that clicking an element from the sidebar scrolls to the right place
// so the target is not obscured by the topbar.
@ -54,7 +54,7 @@ assert-position: ("#method\.must_use", {"y": 46})
// Check that the bottom-most item on the sidebar menu can be scrolled fully into view.
click: ".sidebar-menu-toggle"
scroll-to: ".block.keyword li:nth-child(1)"
compare-elements-position-near: (".block.keyword li:nth-child(1)", ".mobile-topbar", {"y": 544})
compare-elements-position-near: (".block.keyword li:nth-child(1)", "rustdoc-topbar", {"y": 544})
// Now checking the background color of the sidebar.
// Close the sidebar menu.
@ -65,7 +65,7 @@ define-function: (
"check-colors",
[theme, color, background],
block {
call-function: ("switch-theme", {"theme": |theme|})
call-function: ("switch-theme-mobile", {"theme": |theme|})
reload:
// Open the sidebar menu.

View file

@ -2,7 +2,7 @@
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
assert-property: (".sidebar", {"clientWidth": "199"})
show-text: true
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-css: ("#settings", {"display": "block"})
// normal resizing
@ -12,7 +12,7 @@ assert-css: ("#settings", {"display": "none"})
// Now same thing, but for source code
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-css: ("#settings", {"display": "block"})
assert-property: (".sidebar", {"clientWidth": "49"})

View file

@ -4,7 +4,7 @@ assert-property: (".sidebar", {"clientWidth": "199"})
show-text: true
// Verify that the "hide" option is unchecked
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#settings"
assert-css: ("#settings", {"display": "block"})
assert-property: ("#hide-sidebar", {"checked": "false"})
@ -15,7 +15,7 @@ drag-and-drop: ((205, 100), (5, 100))
assert-css: (".sidebar", {"display": "none"})
// Verify that the "hide" option is checked
focus: "#settings-menu a"
focus: "rustdoc-toolbar .settings-menu a"
press-key: "Enter"
wait-for-css: ("#settings", {"display": "block"})
assert-property: ("#hide-sidebar", {"checked": "true"})
@ -24,28 +24,28 @@ wait-for-css: (".sidebar", {"display": "block"})
// Verify that hiding the sidebar hides the source sidebar
// and puts the button in static position mode on mobile
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
set-window-size: (600, 600)
focus: "#settings-menu a"
focus: "rustdoc-topbar .settings-menu a"
press-key: "Enter"
wait-for-css: ("#settings", {"display": "block"})
wait-for-css: ("#sidebar-button", {"position": "static"})
assert-property: ("#hide-sidebar", {"checked": "false"})
click: "#hide-sidebar"
wait-for-css: (".sidebar", {"display": "none"})
wait-for-css: ("#sidebar-button", {"position": "fixed"})
store-position: ("#sidebar-button", {
"y": sidebar_button_y,
"x": sidebar_button_x,
})
assert-property: ("#hide-sidebar", {"checked": "false"})
click: "#hide-sidebar"
wait-for-css: (".sidebar", {"display": "none"})
wait-for-css: ("#sidebar-button", {"position": "static"})
assert-position: ("#sidebar-button", {
"y": |sidebar_button_y|,
"x": |sidebar_button_x|,
})
assert-property: ("#hide-sidebar", {"checked": "true"})
press-key: "Escape"
// Clicking the sidebar button should work, and implicitly re-enable
// the persistent navigation bar
wait-for-css: ("#settings", {"display": "none"})
assert-position: ("#sidebar-button", {
"y": |sidebar_button_y|,
"x": |sidebar_button_x|,
})
click: "#sidebar-button"
wait-for-css: (".sidebar", {"display": "block"})

View file

@ -141,7 +141,7 @@ click: "#sidebar-button"
wait-for-css: (".src .sidebar > *", {"visibility": "hidden"})
// We scroll to line 117 to change the scroll position.
scroll-to: '//*[@id="117"]'
store-value: (y_offset, "2578")
store-value: (y_offset, "2567")
assert-window-property: {"pageYOffset": |y_offset|}
// Expanding the sidebar...
click: "#sidebar-button"

View file

@ -85,4 +85,4 @@ assert-false: ".src-sidebar-expanded"
assert: "nav.sidebar"
// Check that the topbar is not visible
assert-false: ".mobile-topbar"
assert-false: "rustdoc-topbar"

View file

@ -200,7 +200,7 @@ drag-and-drop: ((205, 100), (108, 100))
assert-position: (".sidebar-crate > h2 > a", {"x": -3})
// Check that the mobile sidebar and the source sidebar use the same icon.
store-css: (".mobile-topbar .sidebar-menu-toggle::before", {"content": image_url})
store-css: ("rustdoc-topbar .sidebar-menu-toggle::before", {"content": image_url})
// Then we go to a source page.
click: ".main-heading .src"
assert-css: ("#sidebar-button a::before", {"content": |image_url|})
@ -212,7 +212,7 @@ assert: |sidebar_background| != |sidebar_background_hover|
click: "#sidebar-button a"
wait-for: "html.src-sidebar-expanded"
assert-css: ("#sidebar-button a:hover", {"background-color": |sidebar_background_hover|})
move-cursor-to: "#settings-menu"
move-cursor-to: "#search-button"
assert-css: ("#sidebar-button a:not(:hover)", {"background-color": |sidebar_background|})
// Closing sidebar.
click: "#sidebar-button a"
@ -220,7 +220,7 @@ wait-for: "html:not(.src-sidebar-expanded)"
// Now we check the same when the sidebar button is moved alongside the search.
set-window-size: (500, 500)
store-css: ("#sidebar-button a:hover", {"background-color": not_sidebar_background_hover})
move-cursor-to: "#settings-menu"
move-cursor-to: "rustdoc-toolbar #search-button"
store-css: ("#sidebar-button a:not(:hover)", {"background-color": not_sidebar_background})
// The sidebar background is supposed to be the same as the main background.
assert-css: ("body", {"background-color": |not_sidebar_background|})

View file

@ -8,13 +8,13 @@ set-window-size: (600, 800)
assert-property: ("html", {"scrollTop": "0"})
click: '//a[text() = "barbar" and @href="#5-7"]'
assert-property: ("html", {"scrollTop": "206"})
assert-property: ("html", {"scrollTop": "195"})
click: '//a[text() = "bar" and @href="#28-36"]'
assert-property: ("html", {"scrollTop": "239"})
assert-property: ("html", {"scrollTop": "228"})
click: '//a[normalize-space() = "sub_fn" and @href="#2-4"]'
assert-property: ("html", {"scrollTop": "134"})
assert-property: ("html", {"scrollTop": "123"})
// We now check that clicking on lines doesn't change the scroll
// Extra information: the "sub_fn" function header is on line 1.
click: '//*[@id="6"]'
assert-property: ("html", {"scrollTop": "134"})
assert-property: ("html", {"scrollTop": "123"})

View file

@ -89,9 +89,9 @@ assert-css: ("a[data-nosnippet]", {"text-align": "right"}, ALL)
// do anything (and certainly not add a `#NaN` to the URL!).
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
// We use this assert-position to know where we will click.
assert-position: ("//*[@id='1']", {"x": 81, "y": 169})
// We click on the left of the "1" anchor but still in the `a[data-nosnippet]`.
click: (77, 163)
assert-position: ("//*[@id='1']", {"x": 81, "y": 141})
// We click on the left of the "1" anchor but still in the "src-line-number" `<pre>`.
click: (135, 77)
assert-document-property: ({"URL": "/lib.rs.html"}, ENDS_WITH)
// Checking the source code sidebar.
@ -156,27 +156,8 @@ call-function: ("check-sidebar-dir-entry", {
"y": |source_sidebar_title_y| + |source_sidebar_title_height| + 7,
})
// Check the search form
assert-css: ("nav.sub", {"flex-direction": "row"})
// The goal of this test is to ensure the search input is perfectly centered
// between the top of the page and the header.
// To check this, we maintain the invariant:
//
// offsetTop[nav.sub form] = offsetTop[#main-content] - offsetHeight[nav.sub form] - offsetTop[nav.sub form]
assert-position: ("nav.sub form", {"y": 15})
assert-property: ("nav.sub form", {"offsetHeight": 34})
assert-position: ("h1", {"y": 68})
// 15 = 64 - 34 - 15
// Now do the same check on moderately-sized, tablet mobile.
set-window-size: (700, 700)
assert-css: ("nav.sub", {"flex-direction": "row"})
assert-position: ("nav.sub form", {"y": 8})
assert-property: ("nav.sub form", {"offsetHeight": 34})
assert-position: ("h1", {"y": 54})
// 8 = 50 - 34 - 8
// Check the sidebar directory entries have a marker and spacing (tablet).
set-window-size: (700, 700)
store-property: (".src-sidebar-title", {
"offsetHeight": source_sidebar_title_height,
"offsetTop": source_sidebar_title_y,
@ -187,11 +168,8 @@ call-function: ("check-sidebar-dir-entry", {
"y": |source_sidebar_title_y| + |source_sidebar_title_height| + 7,
})
// Tiny, phone mobile gets a different display where the logo is stacked on top.
set-window-size: (450, 700)
assert-css: ("nav.sub", {"flex-direction": "column"})
// Check the sidebar directory entries have a marker and spacing (phone).
set-window-size: (450, 700)
store-property: (".src-sidebar-title", {
"offsetHeight": source_sidebar_title_height,
"offsetTop": source_sidebar_title_y,

View file

@ -13,7 +13,7 @@ define-function: (
)
store-size: (".rust code", {"width": width, "height": height})
click: "#settings-menu"
click: "main .settings-menu"
wait-for: "#settings"
call-function: ("click-code-wrapping", {"expected": "true"})
wait-for-size-false: (".rust code", {"width": |width|, "height": |height|})
@ -28,7 +28,7 @@ assert-size: (".rust code", {"width": |width|, "height": |height|})
// Now let's check in docs code examples.
go-to: "file://" + |DOC_PATH| + "/test_docs/trait_bounds/index.html"
click: "#settings-menu"
click: "main .settings-menu"
wait-for: "#settings"
store-property: (".example-wrap .rust code", {"scrollWidth": rust_width, "scrollHeight": rust_height})

View file

@ -7,7 +7,7 @@ store-value: (background_light, "white")
store-value: (background_dark, "#353535")
store-value: (background_ayu, "#0f1419")
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#theme-ayu"
click: "#theme-ayu"
// should be the ayu theme so let's check the color.
@ -75,7 +75,7 @@ store-value: (background_dark, "#353535")
store-value: (background_ayu, "#0f1419")
store-value: (background_custom_theme, "red")
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#theme-ayu"
click: "#theme-ayu"
// should be the ayu theme so let's check the color.

View file

@ -1,6 +1,6 @@
// Ensure that the theme picker always starts with the actual defaults.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#theme-system-preference"
assert: "#theme-system-preference:checked"
assert: "#preferred-light-theme-light:checked"
@ -16,7 +16,7 @@ set-local-storage: {
"rustdoc-theme": "ayu"
}
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
wait-for: "#theme-system-preference"
assert: "#theme-system-preference:checked"
assert: "#preferred-light-theme-light:checked"

View file

@ -13,4 +13,4 @@ assert-attribute-false: (".impl-items .toggle", {"open": ""})
// Click the "Trait" part of "impl Trait" and verify it navigates.
click: "#impl-Trait-for-Foo h3 a:first-of-type"
assert-text: (".main-heading .rustdoc-breadcrumbs", "lib2")
assert-text: (".main-heading h1", "Trait TraitCopy item path")
assert-text: (".main-heading h1", "Trait Trait Copy item path")

View file

@ -3,12 +3,12 @@
go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
set-window-size: (433, 600)
assert-attribute: (".top-doc", {"open": ""})
click: (4, 270) // This is the position of the top doc comment toggle
click: (4, 230) // This is the position of the top doc comment toggle
assert-attribute-false: (".top-doc", {"open": ""})
click: (4, 270)
click: (4, 230)
assert-attribute: (".top-doc", {"open": ""})
// To ensure that the toggle isn't over the text, we check that the toggle isn't clicked.
click: (3, 270)
click: (3, 230)
assert-attribute: (".top-doc", {"open": ""})
// Assert the position of the toggle on the top doc block.
@ -24,12 +24,12 @@ assert-position: (
// Now we do the same but with a little bigger width
set-window-size: (600, 600)
assert-attribute: (".top-doc", {"open": ""})
click: (4, 270) // New Y position since all search elements are back on one line.
click: (4, 230) // New Y position since all search elements are back on one line.
assert-attribute-false: (".top-doc", {"open": ""})
click: (4, 270)
click: (4, 230)
assert-attribute: (".top-doc", {"open": ""})
// To ensure that the toggle isn't over the text, we check that the toggle isn't clicked.
click: (3, 270)
click: (3, 230)
assert-attribute: (".top-doc", {"open": ""})
// Same check on trait items.

View file

@ -64,7 +64,7 @@ define-function: (
"filter": |filter|,
})
// moving the cursor somewhere else to not mess with next function calls.
move-cursor-to: ".search-input"
move-cursor-to: "#search-button"
},
)

View file

@ -47,27 +47,27 @@ assert-property: ("pre.item-decl", {"scrollWidth": "950"})
set-window-size: (600, 600)
go-to: "file://" + |DOC_PATH| + "/lib2/too_long/struct.SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName.html"
// It shouldn't have an overflow in the topbar either.
store-property: (".mobile-topbar", {"scrollWidth": scrollWidth})
assert-property: (".mobile-topbar", {"clientWidth": |scrollWidth|})
assert-css: (".mobile-topbar h2", {"overflow-x": "hidden"})
store-property: ("rustdoc-topbar", {"scrollWidth": scrollWidth})
assert-property: ("rustdoc-topbar", {"clientWidth": |scrollWidth|}, NEAR)
assert-css: ("rustdoc-topbar h2", {"overflow-x": "hidden"})
// Check that main heading and toolbar go side-by-side, both on desktop and on mobile.
set-window-size: (1100, 800)
go-to: "file://" + |DOC_PATH| + "/lib2/too_long/struct.SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName.html"
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 550})
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 300})
go-to: "file://" + |DOC_PATH| + "/lib2/index.html"
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 550})
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 300})
// On mobile, they always wrap.
set-window-size: (600, 600)
go-to: "file://" + |DOC_PATH| + "/lib2/too_long/struct.SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName.html"
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 200})
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 200})
go-to: "file://" + |DOC_PATH| + "/lib2/index.html"
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 200})
compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 200})
// Now we will check that the scrolling is working.
// First on an item with "hidden methods".

View file

@ -5,14 +5,47 @@ define-function: (
block {
// Set the theme.
// Open the settings menu.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
// Wait for the popover to appear...
wait-for: "#settings"
// Change the setting.
click: "#theme-"+ |theme|
// Close the popover.
click: "#settings-menu"
click: "rustdoc-toolbar .settings-menu"
// Ensure that the local storage was correctly updated.
assert-local-storage: {"rustdoc-theme": |theme|}
},
)
define-function: (
"switch-theme-mobile",
[theme],
block {
// Set the theme.
// Open the settings menu.
click: "rustdoc-topbar .settings-menu"
// Wait for the popover to appear...
wait-for: "#settings"
// Change the setting.
click: "#theme-"+ |theme|
// Close the popover.
click: "rustdoc-topbar .settings-menu"
// Ensure that the local storage was correctly updated.
assert-local-storage: {"rustdoc-theme": |theme|}
},
)
define-function: (
"perform-search",
[query],
block {
click: "#search-button"
wait-for: ".search-input"
write-into: (".search-input", |query|)
press-key: 'Enter'
// wait for the search to start
wait-for: "#search-tabs"
// then wait for it to finish
wait-for-false: "#search-tabs .count.loading"
}
)

View file

@ -6,5 +6,10 @@ const EXPECTED = {
'name': 'reference',
'desc': "References, <code>&amp;T</code> and <code>&amp;mut T</code>.",
},
{
'path': 'std::ops',
'name': 'BitAnd',
'desc': "The bitwise AND operator <code>&amp;</code>.",
},
],
};

View file

@ -1,9 +1,7 @@
const EXPECTED = {
'query': '+',
'others': [
{ 'path': 'std::ops', 'name': 'AddAssign' },
{ 'path': 'std::ops', 'name': 'Add' },
{ 'path': 'core::ops', 'name': 'AddAssign' },
{ 'path': 'core::ops', 'name': 'Add' },
{ 'path': 'std::ops', 'name': 'AddAssign' },
],
};

View file

@ -9,6 +9,6 @@ const EXPECTED = {
{ 'path': 'std::str', 'name': 'eq' },
],
'returned': [
{ 'path': 'std::string::String', 'name': 'add' },
{ 'path': 'std::string::String', 'name': 'new' },
],
};

View file

@ -20,12 +20,12 @@ const PARSED = [
pathLast: "c",
normalizedPathLast: "c",
generics: [],
typeFilter: -1,
typeFilter: null,
},
]
],
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -51,11 +51,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "c",
generics: [],
typeFilter: -1,
typeFilter: null,
}]
],
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -81,11 +81,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "never",
generics: [],
typeFilter: 1,
typeFilter: "primitive",
}]
],
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -111,11 +111,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "[]",
generics: [],
typeFilter: 1,
typeFilter: "primitive",
}]
],
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -147,14 +147,14 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "never",
generics: [],
typeFilter: 1,
typeFilter: "primitive",
},
],
typeFilter: 1,
typeFilter: "primitive",
}]
],
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -213,7 +213,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "c",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "X",
@ -221,12 +221,12 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
],
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,

View file

@ -406,10 +406,10 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
typeFilter: -1,
typeFilter: null,
},
{
name: "y",
@ -417,7 +417,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "y",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 2,
@ -440,7 +440,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "y",
@ -448,10 +448,10 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "y",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -468,7 +468,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "x",
@ -476,7 +476,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "y",
@ -484,7 +484,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "y",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 3,

View file

@ -7,7 +7,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "foo",
generics: [],
typeFilter: 7,
typeFilter: "fn",
}],
foundElems: 1,
userQuery: "fn:foo",
@ -22,7 +22,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "foo",
generics: [],
typeFilter: 6,
typeFilter: "enum",
}],
foundElems: 1,
userQuery: "enum : foo",
@ -45,7 +45,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "macro",
generics: [],
typeFilter: 16,
typeFilter: "macro",
}],
foundElems: 1,
userQuery: "macro!",
@ -60,7 +60,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "mac",
generics: [],
typeFilter: 16,
typeFilter: "macro",
}],
foundElems: 1,
userQuery: "macro:mac!",
@ -75,7 +75,7 @@ const PARSED = [
pathWithoutLast: ["a"],
pathLast: "mac",
generics: [],
typeFilter: 16,
typeFilter: "macro",
}],
foundElems: 1,
userQuery: "a::mac!",
@ -93,7 +93,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "foo",
generics: [],
typeFilter: 7,
typeFilter: "fn",
}],
error: null,
},
@ -114,10 +114,10 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "bar",
generics: [],
typeFilter: 7,
typeFilter: "fn",
}
],
typeFilter: 7,
typeFilter: "fn",
}],
error: null,
},
@ -138,7 +138,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "bar",
generics: [],
typeFilter: 7,
typeFilter: "fn",
},
{
name: "baz::fuzz",
@ -146,10 +146,10 @@ const PARSED = [
pathWithoutLast: ["baz"],
pathLast: "fuzz",
generics: [],
typeFilter: 6,
typeFilter: "enum",
},
],
typeFilter: 7,
typeFilter: "fn",
}],
error: null,
},

View file

@ -16,7 +16,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "u8",
@ -24,7 +24,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "u8",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 2,
@ -49,7 +49,7 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -82,7 +82,7 @@ const PARSED = [
],
},
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -122,7 +122,7 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,
@ -162,7 +162,7 @@ const PARSED = [
],
},
],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 1,

View file

@ -25,11 +25,11 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(-> F<P>)",
@ -53,11 +53,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(-> P)",
@ -81,11 +81,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(->,a)",
@ -113,7 +113,7 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
}],
bindings: [
[
@ -121,7 +121,7 @@ const PARSED = [
[],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(F<P> ->)",
@ -141,7 +141,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
bindings: [
[
@ -149,7 +149,7 @@ const PARSED = [
[],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(P ->)",
@ -169,7 +169,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
bindings: [
[
@ -177,7 +177,7 @@ const PARSED = [
[],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(,a->)",
@ -197,7 +197,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
bindings: [
[
@ -208,11 +208,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(aaaaa->a)",
@ -233,7 +233,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "b",
@ -241,7 +241,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
bindings: [
@ -253,11 +253,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(aaaaa, b -> a)",
@ -278,7 +278,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "b",
@ -286,7 +286,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
bindings: [
@ -298,11 +298,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: 1,
typeFilter: "primitive",
}],
foundElems: 1,
userQuery: "primitive:(aaaaa, b -> a)",
@ -318,7 +318,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "->",
@ -332,7 +332,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "b",
@ -340,7 +340,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
bindings: [
@ -352,11 +352,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: 10,
typeFilter: "trait",
}
],
foundElems: 2,
@ -390,11 +390,11 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "Fn () -> F<P>",
@ -418,11 +418,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "FnMut() -> P",
@ -446,11 +446,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "p",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "(FnMut() -> P)",
@ -478,7 +478,7 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
}],
bindings: [
[
@ -486,7 +486,7 @@ const PARSED = [
[],
],
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "Fn(F<P>)",
@ -507,7 +507,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "b",
@ -515,7 +515,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
bindings: [
@ -527,11 +527,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: 1,
typeFilter: "primitive",
}],
foundElems: 1,
userQuery: "primitive:fnonce(aaaaa, b) -> a",
@ -552,7 +552,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "b",
@ -560,7 +560,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: 0,
typeFilter: "keyword",
},
],
bindings: [
@ -572,11 +572,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: 10,
typeFilter: "trait",
}],
],
],
typeFilter: 1,
typeFilter: "primitive",
}],
foundElems: 1,
userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
@ -592,7 +592,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "x",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "fn",
@ -612,7 +612,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "aaaaa",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "b",
@ -620,7 +620,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
bindings: [
@ -632,11 +632,11 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
],
],
typeFilter: -1,
typeFilter: null,
},
],
bindings: [
@ -645,7 +645,7 @@ const PARSED = [
[],
]
],
typeFilter: 10,
typeFilter: "trait",
}
],
foundElems: 2,
@ -662,7 +662,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "b",
@ -675,7 +675,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "c",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
bindings: [
[
@ -683,7 +683,7 @@ const PARSED = [
[],
]
],
typeFilter: -1,
typeFilter: null,
}
],
foundElems: 2,

View file

@ -13,10 +13,10 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "never",
generics: [],
typeFilter: 1,
typeFilter: "primitive",
},
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "R<!>",
@ -31,7 +31,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "never",
generics: [],
typeFilter: 1,
typeFilter: "primitive",
}],
foundElems: 1,
userQuery: "!",
@ -46,7 +46,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "a",
generics: [],
typeFilter: 16,
typeFilter: "macro",
}],
foundElems: 1,
userQuery: "a!",
@ -77,7 +77,7 @@ const PARSED = [
pathWithoutLast: ["never"],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "!::b",
@ -122,10 +122,10 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "t",
generics: [],
typeFilter: -1,
typeFilter: null,
}
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "!::b<T>",

View file

@ -15,7 +15,7 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "R<P>",

View file

@ -7,7 +7,7 @@ const PARSED = [
pathWithoutLast: ["a"],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "A::B",
@ -22,7 +22,7 @@ const PARSED = [
pathWithoutLast: ["a"],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: 'a:: a',
@ -37,7 +37,7 @@ const PARSED = [
pathWithoutLast: ["a"],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: 'a ::a',
@ -52,7 +52,7 @@ const PARSED = [
pathWithoutLast: ["a"],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: 'a :: a',
@ -68,7 +68,7 @@ const PARSED = [
pathWithoutLast: ["a"],
pathLast: "b",
generics: [],
typeFilter: -1,
typeFilter: null,
},
{
name: "C",
@ -76,7 +76,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "c",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 2,
@ -101,7 +101,7 @@ const PARSED = [
generics: [],
},
],
typeFilter: -1,
typeFilter: null,
},
{
name: "C",
@ -109,7 +109,7 @@ const PARSED = [
pathWithoutLast: [],
pathLast: "c",
generics: [],
typeFilter: -1,
typeFilter: null,
},
],
foundElems: 2,
@ -125,7 +125,7 @@ const PARSED = [
pathWithoutLast: ["mod"],
pathLast: "a",
generics: [],
typeFilter: -1,
typeFilter: null,
}],
foundElems: 1,
userQuery: "mod::a",

Some files were not shown because too many files have changed in this diff Show more