"serde_json",
]
-[[package]]
-name = "term"
-version = "0.0.0"
-dependencies = [
- "core",
- "std",
-]
-
[[package]]
name = "term"
version = "0.6.1"
"panic_unwind",
"proc_macro",
"std",
- "term 0.0.0",
]
[[package]]
) -> ast::Path {
let mut idents = self.path.iter().map(|s| Ident::new(*s, span)).collect();
let lt = mk_lifetimes(cx, span, &self.lifetime);
- let tys: Vec<P<ast::Ty>> =
- self.params.iter().map(|t| t.to_ty(cx, span, self_ty, self_generics)).collect();
- let params = lt
- .into_iter()
- .map(GenericArg::Lifetime)
- .chain(tys.into_iter().map(GenericArg::Type))
- .collect();
+ let tys = self.params.iter().map(|t| t.to_ty(cx, span, self_ty, self_generics));
+ let params =
+ lt.into_iter().map(GenericArg::Lifetime).chain(tys.map(GenericArg::Type)).collect();
match self.kind {
PathKind::Global => cx.path_all(span, true, idents, params),
E0718: include_str!("./error_codes/E0718.md"),
E0719: include_str!("./error_codes/E0719.md"),
E0720: include_str!("./error_codes/E0720.md"),
+E0722: include_str!("./error_codes/E0722.md"),
E0724: include_str!("./error_codes/E0724.md"),
E0725: include_str!("./error_codes/E0725.md"),
E0727: include_str!("./error_codes/E0727.md"),
E0754: include_str!("./error_codes/E0754.md"),
E0755: include_str!("./error_codes/E0755.md"),
E0756: include_str!("./error_codes/E0756.md"),
+E0757: include_str!("./error_codes/E0757.md"),
E0758: include_str!("./error_codes/E0758.md"),
E0759: include_str!("./error_codes/E0759.md"),
E0760: include_str!("./error_codes/E0760.md"),
E0711, // a feature has been declared with conflicting stability attributes
E0717, // rustc_promotable without stability attribute
// E0721, // `await` keyword
- E0722, // Malformed `#[optimize]` attribute
// E0723, unstable feature in `const` context
E0726, // non-explicit (not `'_`) elided lifetime in unsupported position
// E0738, // Removed; errored on `#[track_caller] fn`s in `extern "Rust" { ... }`.
- E0757, // `#[ffi_const]` functions cannot be `#[ffi_pure]`
E0772, // `'static' obligation coming from `impl dyn Trait {}` or `impl Foo for dyn Bar {}`.
}
--- /dev/null
+The `optimize` attribute was malformed.
+
+Erroneous code example:
+
+```compile_fail,E0722
+#![feature(optimize_attribute)]
+
+#[optimize(something)] // error: invalid argument
+pub fn something() {}
+```
+
+The `#[optimize]` attribute should be used as follows:
+
+- `#[optimize(size)]` -- instructs the optimization pipeline to generate code
+ that's smaller rather than faster
+
+- `#[optimize(speed)]` -- instructs the optimization pipeline to generate code
+ that's faster rather than smaller
+
+For example:
+
+```
+#![feature(optimize_attribute)]
+
+#[optimize(size)]
+pub fn something() {}
+```
+
+See [RFC 2412] for more details.
+
+[RFC 2412]: https://rust-lang.github.io/rfcs/2412-optimize-attr.html
--- /dev/null
+A function was given both the `ffi_const` and `ffi_pure` attributes.
+
+Erroneous code example:
+
+```compile_fail,E0757
+#![feature(ffi_const, ffi_pure)]
+
+extern "C" {
+ #[ffi_const]
+ #[ffi_pure] // error: `#[ffi_const]` function cannot be `#[ffi_pure]`
+ pub fn square(num: i32) -> i32;
+}
+```
+
+As `ffi_const` provides stronger guarantees than `ffi_pure`, remove the
+`ffi_pure` attribute:
+
+```
+#![feature(ffi_const)]
+
+extern "C" {
+ #[ffi_const]
+ pub fn square(num: i32) -> i32;
+}
+```
+
+You can get more information about `const` and `pure` in the [GCC documentation
+on Common Function Attributes]. The unstable Rust Book has more information
+about [`ffi_const`] and [`ffi_pure`].
+
+[GCC documentation on Common Function Attributes]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
+[`ffi_const`]: https://doc.rust-lang.org/nightly/unstable-book/language-features/ffi-const.html
+[`ffi_pure`]: https://doc.rust-lang.org/nightly/unstable-book/language-features/ffi-pure.html
Node::Crate(_) | Node::Visibility(_) => None,
}
}
+
+ /// Returns `Constness::Const` when this node is a const fn/impl.
+ pub fn constness(&self) -> Constness {
+ match self {
+ Node::Item(Item {
+ kind: ItemKind::Fn(FnSig { header: FnHeader { constness, .. }, .. }, ..),
+ ..
+ })
+ | Node::TraitItem(TraitItem {
+ kind: TraitItemKind::Fn(FnSig { header: FnHeader { constness, .. }, .. }, ..),
+ ..
+ })
+ | Node::ImplItem(ImplItem {
+ kind: ImplItemKind::Fn(FnSig { header: FnHeader { constness, .. }, .. }, ..),
+ ..
+ })
+ | Node::Item(Item { kind: ItemKind::Impl(Impl { constness, .. }), .. }) => *constness,
+
+ _ => Constness::NotConst,
+ }
+ }
}
// Some nodes are used a lot. Make sure they don't unintentionally get bigger.
let new_lt = generics
.as_ref()
.and_then(|(parent_g, g)| {
- let possible: Vec<_> = (b'a'..=b'z').map(|c| format!("'{}", c as char)).collect();
+ let mut possible = (b'a'..=b'z').map(|c| format!("'{}", c as char));
let mut lts_names = g
.params
.iter()
);
}
let lts = lts_names.iter().map(|s| -> &str { &*s }).collect::<Vec<_>>();
- possible.into_iter().find(|candidate| !lts.contains(&candidate.as_str()))
+ possible.find(|candidate| !lts.contains(&candidate.as_str()))
})
.unwrap_or("'lt".to_string());
let add_lt_sugg = generics
use rustc_middle::ty::subst::Subst;
use rustc_middle::ty::{self, RegionVid, Ty};
use rustc_span::symbol::{kw, sym};
-use rustc_span::Span;
+use rustc_span::{BytePos, Span};
use crate::util::borrowck_errors;
} else {
"'_".to_string()
};
- let suggestion = if snippet.ends_with(';') {
+ let span = if snippet.ends_with(';') {
// `type X = impl Trait;`
- format!("{} + {};", &snippet[..snippet.len() - 1], suggestable_fr_name)
+ span.with_hi(span.hi() - BytePos(1))
} else {
- format!("{} + {}", snippet, suggestable_fr_name)
+ span
};
+ let suggestion = format!(" + {}", suggestable_fr_name);
+ let span = span.shrink_to_hi();
diag.span_suggestion(
span,
&format!(
permitted = true;
}
}
- let mut const_impls = true;
- tcx.for_each_relevant_impl(trait_id, substs.type_at(0), |imp| {
- if const_impls {
- if let hir::Constness::NotConst = tcx.impl_constness(imp) {
- const_impls = false;
+ if !permitted {
+ // if trait's impls are all const, permit the call.
+ let mut const_impls = true;
+ tcx.for_each_relevant_impl(trait_id, substs.type_at(0), |imp| {
+ if const_impls {
+ if let hir::Constness::NotConst = tcx.impl_constness(imp) {
+ const_impls = false;
+ }
}
+ });
+ if const_impls {
+ permitted = true;
}
- });
- if const_impls {
- permitted = true;
}
}
use rustc_infer::infer;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
-use rustc_middle::hir::map::blocks::FnLikeNode;
use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, Const, Ty, TyCtxt};
}
fn default_constness_for_trait_bounds(&self) -> hir::Constness {
- // FIXME: refactor this into a method
- let node = self.tcx.hir().get(self.body_id);
- if let Some(fn_like) = FnLikeNode::from_node(node) {
- fn_like.constness()
- } else {
- hir::Constness::NotConst
- }
+ self.tcx.hir().get(self.body_id).constness()
}
fn get_type_parameter_bounds(
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::weak_lang_items;
use rustc_hir::{GenericParamKind, HirId, Node};
-use rustc_middle::hir::map::blocks::FnLikeNode;
use rustc_middle::hir::map::Map;
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
use rustc_middle::mir::mono::Linkage;
}
fn default_constness_for_trait_bounds(&self) -> hir::Constness {
- if let Some(fn_like) = FnLikeNode::from_node(self.node()) {
- fn_like.constness()
- } else {
- hir::Constness::NotConst
- }
+ self.node().constness()
}
fn get_type_parameter_bounds(
///
/// # Example
///
-/// ```no_run
-/// use std::alloc::{GlobalAlloc, Layout, alloc};
+/// ```
+/// use std::alloc::{GlobalAlloc, Layout};
+/// use std::cell::UnsafeCell;
/// use std::ptr::null_mut;
+/// use std::sync::atomic::{
+/// AtomicUsize,
+/// Ordering::{Acquire, SeqCst},
+/// };
///
-/// struct MyAllocator;
-///
-/// unsafe impl GlobalAlloc for MyAllocator {
-/// unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { null_mut() }
-/// unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
+/// const ARENA_SIZE: usize = 128 * 1024;
+/// const MAX_SUPPORTED_ALIGN: usize = 4096;
+/// #[repr(C, align(4096))] // 4096 == MAX_SUPPORTED_ALIGN
+/// struct SimpleAllocator {
+/// arena: UnsafeCell<[u8; ARENA_SIZE]>,
+/// remaining: AtomicUsize, // we allocate from the top, counting down
/// }
///
/// #[global_allocator]
-/// static A: MyAllocator = MyAllocator;
+/// static ALLOCATOR: SimpleAllocator = SimpleAllocator {
+/// arena: UnsafeCell::new([0x55; ARENA_SIZE]),
+/// remaining: AtomicUsize::new(ARENA_SIZE),
+/// };
///
-/// fn main() {
-/// unsafe {
-/// assert!(alloc(Layout::new::<u32>()).is_null())
+/// unsafe impl Sync for SimpleAllocator {}
+///
+/// unsafe impl GlobalAlloc for SimpleAllocator {
+/// unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+/// let size = layout.size();
+/// let align = layout.align();
+///
+/// // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2.
+/// // So we can safely use a mask to ensure alignment without worrying about UB.
+/// let align_mask_to_round_down = !(align - 1);
+///
+/// if align > MAX_SUPPORTED_ALIGN {
+/// return null_mut();
+/// }
+///
+/// let mut allocated = 0;
+/// if self
+/// .remaining
+/// .fetch_update(SeqCst, SeqCst, |mut remaining| {
+/// if size > remaining {
+/// return None;
+/// }
+/// remaining -= size;
+/// remaining &= align_mask_to_round_down;
+/// allocated = remaining;
+/// Some(remaining)
+/// })
+/// .is_err()
+/// {
+/// return null_mut();
+/// };
+/// (self.arena.get() as *mut u8).add(allocated)
/// }
+/// unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
+/// }
+///
+/// fn main() {
+/// let _s = format!("allocating a string!");
+/// let currently = ALLOCATOR.remaining.load(Acquire);
+/// println!("allocated so far: {}", ARENA_SIZE - currently);
/// }
/// ```
///
use crate::fmt;
-use crate::iter::{DoubleEndedIterator, Fuse, FusedIterator, Iterator, Map};
+use crate::iter::{DoubleEndedIterator, Fuse, FusedIterator, Iterator, Map, TrustedLen};
use crate::ops::Try;
/// An iterator that maps each element to an iterator, and yields the elements
{
}
+#[unstable(feature = "trusted_len", issue = "37572")]
+unsafe impl<T, I, F, const N: usize> TrustedLen for FlatMap<I, [T; N], F>
+where
+ I: TrustedLen,
+ F: FnMut(I::Item) -> [T; N],
+{
+}
+
+#[unstable(feature = "trusted_len", issue = "37572")]
+unsafe impl<'a, T, I, F, const N: usize> TrustedLen for FlatMap<I, &'a [T; N], F>
+where
+ I: TrustedLen,
+ F: FnMut(I::Item) -> &'a [T; N],
+{
+}
+
+#[unstable(feature = "trusted_len", issue = "37572")]
+unsafe impl<'a, T, I, F, const N: usize> TrustedLen for FlatMap<I, &'a mut [T; N], F>
+where
+ I: TrustedLen,
+ F: FnMut(I::Item) -> &'a mut [T; N],
+{
+}
+
/// An iterator that flattens one level of nesting in an iterator of things
/// that can be turned into iterators.
///
{
}
+#[unstable(feature = "trusted_len", issue = "37572")]
+unsafe impl<I> TrustedLen for Flatten<I>
+where
+ I: TrustedLen,
+ <I as Iterator>::Item: TrustedConstSize,
+{
+}
+
/// Real logic of both `Flatten` and `FlatMap` which simply delegate to
/// this type.
#[derive(Clone, Debug)]
let (flo, fhi) = self.frontiter.as_ref().map_or((0, Some(0)), U::size_hint);
let (blo, bhi) = self.backiter.as_ref().map_or((0, Some(0)), U::size_hint);
let lo = flo.saturating_add(blo);
+
+ if let Some(fixed_size) = <<I as Iterator>::Item as ConstSizeIntoIterator>::size() {
+ let (lower, upper) = self.iter.size_hint();
+
+ let lower = lower.saturating_mul(fixed_size).saturating_add(lo);
+ let upper =
+ try { fhi?.checked_add(bhi?)?.checked_add(fixed_size.checked_mul(upper?)?)? };
+
+ return (lower, upper);
+ }
+
match (self.iter.size_hint(), fhi, bhi) {
((0, Some(0)), Some(a), Some(b)) => (lo, a.checked_add(b)),
_ => (lo, None),
init
}
}
+
+trait ConstSizeIntoIterator: IntoIterator {
+ // FIXME(#31844): convert to an associated const once specialization supports that
+ fn size() -> Option<usize>;
+}
+
+impl<T> ConstSizeIntoIterator for T
+where
+ T: IntoIterator,
+{
+ #[inline]
+ default fn size() -> Option<usize> {
+ None
+ }
+}
+
+impl<T, const N: usize> ConstSizeIntoIterator for [T; N] {
+ #[inline]
+ fn size() -> Option<usize> {
+ Some(N)
+ }
+}
+
+impl<T, const N: usize> ConstSizeIntoIterator for &[T; N] {
+ #[inline]
+ fn size() -> Option<usize> {
+ Some(N)
+ }
+}
+
+impl<T, const N: usize> ConstSizeIntoIterator for &mut [T; N] {
+ #[inline]
+ fn size() -> Option<usize> {
+ Some(N)
+ }
+}
+
+#[doc(hidden)]
+#[unstable(feature = "std_internals", issue = "none")]
+// FIXME(#20400): Instead of this helper trait there should be multiple impl TrustedLen for Flatten<>
+// blocks with different bounds on Iterator::Item but the compiler erroneously considers them overlapping
+pub unsafe trait TrustedConstSize: IntoIterator {}
+
+#[unstable(feature = "std_internals", issue = "none")]
+unsafe impl<T, const N: usize> TrustedConstSize for [T; N] {}
+#[unstable(feature = "std_internals", issue = "none")]
+unsafe impl<T, const N: usize> TrustedConstSize for &'_ [T; N] {}
+#[unstable(feature = "std_internals", issue = "none")]
+unsafe impl<T, const N: usize> TrustedConstSize for &'_ mut [T; N] {}
use super::*;
+use core::array;
use core::iter::*;
#[test]
assert_eq!(it.next(), None);
assert_eq!(it.next_back(), None);
}
+
+#[test]
+fn test_trusted_len_flatten() {
+ fn assert_trusted_len<T: TrustedLen>(_: &T) {}
+ let mut iter = array::IntoIter::new([[0; 3]; 4]).flatten();
+ assert_trusted_len(&iter);
+
+ assert_eq!(iter.size_hint(), (12, Some(12)));
+ iter.next();
+ assert_eq!(iter.size_hint(), (11, Some(11)));
+ iter.next_back();
+ assert_eq!(iter.size_hint(), (10, Some(10)));
+
+ let iter = array::IntoIter::new([[(); usize::MAX]; 1]).flatten();
+ assert_eq!(iter.size_hint(), (usize::MAX, Some(usize::MAX)));
+
+ let iter = array::IntoIter::new([[(); usize::MAX]; 2]).flatten();
+ assert_eq!(iter.size_hint(), (usize::MAX, None));
+
+ let mut a = [(); 10];
+ let mut b = [(); 10];
+
+ let iter = array::IntoIter::new([&mut a, &mut b]).flatten();
+ assert_trusted_len(&iter);
+ assert_eq!(iter.size_hint(), (20, Some(20)));
+ core::mem::drop(iter);
+
+ let iter = array::IntoIter::new([&a, &b]).flatten();
+ assert_trusted_len(&iter);
+ assert_eq!(iter.size_hint(), (20, Some(20)));
+
+ let iter = [(), (), ()].iter().flat_map(|_| [(); 1000]);
+ assert_trusted_len(&iter);
+ assert_eq!(iter.size_hint(), (3000, Some(3000)));
+
+ let iter = [(), ()].iter().flat_map(|_| &a);
+ assert_trusted_len(&iter);
+ assert_eq!(iter.size_hint(), (20, Some(20)));
+}
use crate::cell::{Cell, RefCell};
use crate::fmt;
-use crate::io::{self, BufReader, Initializer, IoSlice, IoSliceMut, LineWriter};
+use crate::io::{self, BufReader, Initializer, IoSlice, IoSliceMut, LineWriter, Lines, Split};
use crate::lazy::SyncOnceCell;
use crate::pin::Pin;
use crate::sync::atomic::{AtomicBool, Ordering};
pub fn into_locked(self) -> StdinLock<'static> {
self.lock_any()
}
+
+ /// Consumes this handle and returns an iterator over input lines.
+ ///
+ /// For detailed semantics of this method, see the documentation on
+ /// [`BufRead::lines`].
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// #![feature(stdin_forwarders)]
+ /// use std::io;
+ ///
+ /// let lines = io::stdin().lines();
+ /// for line in lines {
+ /// println!("got a line: {}", line.unwrap());
+ /// }
+ /// ```
+ #[unstable(feature = "stdin_forwarders", issue = "87096")]
+ pub fn lines(self) -> Lines<StdinLock<'static>> {
+ self.into_locked().lines()
+ }
+
+ /// Consumes this handle and returns an iterator over input bytes,
+ /// split at the specified byte value.
+ ///
+ /// For detailed semantics of this method, see the documentation on
+ /// [`BufRead::split`].
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// #![feature(stdin_forwarders)]
+ /// use std::io;
+ ///
+ /// let splits = io::stdin().split(b'-');
+ /// for split in splits {
+ /// println!("got a chunk: {}", String::from_utf8_lossy(&split.unwrap()));
+ /// }
+ /// ```
+ #[unstable(feature = "stdin_forwarders", issue = "87096")]
+ pub fn split(self, byte: u8) -> Split<StdinLock<'static>> {
+ self.into_locked().split(byte)
+ }
}
#[stable(feature = "std_debug", since = "1.16.0")]
use crate::ptr;
use crate::sync::atomic::{AtomicIsize, AtomicPtr, Ordering};
+ // The system-provided argc and argv, which we store in static memory
+ // here so that we can defer the work of parsing them until its actually
+ // needed.
+ //
+ // Note that we never mutate argv/argc, the argv array, or the argv
+ // strings, which allows the code in this file to be very simple.
static ARGC: AtomicIsize = AtomicIsize::new(0);
static ARGV: AtomicPtr<*const u8> = AtomicPtr::new(ptr::null_mut());
unsafe fn really_init(argc: isize, argv: *const *const u8) {
+ // These don't need to be ordered with each other or other stores,
+ // because they only hold the unmodified system-provide argv/argc.
ARGC.store(argc, Ordering::Relaxed);
ARGV.store(argv as *mut _, Ordering::Relaxed);
}
fn clone() -> Vec<OsString> {
unsafe {
- // Load ARGC and ARGV without a lock. If the store to either ARGV or
- // ARGC isn't visible yet, we'll return an empty argument list.
+ // Load ARGC and ARGV, which hold the unmodified system-provided
+ // argc/argv, so we can read the pointed-to memory without atomics
+ // or synchronization.
+ //
+ // If either ARGC or ARGV is still zero or null, then either there
+ // really are no arguments, or someone is asking for `args()`
+ // before initialization has completed, and we return an empty
+ // list.
let argv = ARGV.load(Ordering::Relaxed);
let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) };
(0..argc)
+++ /dev/null
-[package]
-authors = ["The Rust Project Developers"]
-name = "term"
-version = "0.0.0"
-edition = "2018"
-
-[dependencies]
-core = { path = "../core" }
-std = { path = "../std" }
+++ /dev/null
-//! Terminal formatting library.
-//!
-//! This crate provides the `Terminal` trait, which abstracts over an [ANSI
-//! Terminal][ansi] to provide color printing, among other things. There are two
-//! implementations, the `TerminfoTerminal`, which uses control characters from
-//! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
-//! API][win].
-//!
-//! # Examples
-//!
-//! ```no_run
-//! # #![feature(rustc_private)]
-//! extern crate term;
-//! use std::io::prelude::*;
-//!
-//! fn main() {
-//! let mut t = term::stdout().unwrap();
-//!
-//! t.fg(term::color::GREEN).unwrap();
-//! write!(t, "hello, ").unwrap();
-//!
-//! t.fg(term::color::RED).unwrap();
-//! writeln!(t, "world!").unwrap();
-//!
-//! assert!(t.reset().unwrap());
-//! }
-//! ```
-//!
-//! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
-//! [win]: https://docs.microsoft.com/en-us/windows/console/character-mode-applications
-//! [ti]: https://en.wikipedia.org/wiki/Terminfo
-
-#![doc(html_playground_url = "https://play.rust-lang.org/", test(attr(deny(warnings))))]
-#![deny(missing_docs)]
-#![cfg_attr(windows, feature(libc))]
-
-use std::io::prelude::*;
-use std::io::{self, Stderr, Stdout};
-
-pub use terminfo::TerminfoTerminal;
-#[cfg(windows)]
-pub use win::WinConsole;
-
-pub mod terminfo;
-
-#[cfg(windows)]
-mod win;
-
-/// Alias for stdout terminals.
-pub type StdoutTerminal = dyn Terminal<Output = Stdout> + Send;
-/// Alias for stderr terminals.
-pub type StderrTerminal = dyn Terminal<Output = Stderr> + Send;
-
-#[cfg(not(windows))]
-/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be
-/// opened.
-pub fn stdout() -> Option<Box<StdoutTerminal>> {
- TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box<StdoutTerminal>)
-}
-
-#[cfg(windows)]
-/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be
-/// opened.
-pub fn stdout() -> Option<Box<StdoutTerminal>> {
- TerminfoTerminal::new(io::stdout())
- .map(|t| Box::new(t) as Box<StdoutTerminal>)
- .or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box<StdoutTerminal>))
-}
-
-#[cfg(not(windows))]
-/// Returns a Terminal wrapping stderr, or None if a terminal couldn't be
-/// opened.
-pub fn stderr() -> Option<Box<StderrTerminal>> {
- TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box<StderrTerminal>)
-}
-
-#[cfg(windows)]
-/// Returns a Terminal wrapping stderr, or None if a terminal couldn't be
-/// opened.
-pub fn stderr() -> Option<Box<StderrTerminal>> {
- TerminfoTerminal::new(io::stderr())
- .map(|t| Box::new(t) as Box<StderrTerminal>)
- .or_else(|| WinConsole::new(io::stderr()).ok().map(|t| Box::new(t) as Box<StderrTerminal>))
-}
-
-/// Terminal color definitions
-#[allow(missing_docs)]
-pub mod color {
- /// Number for a terminal color
- pub type Color = u32;
-
- pub const BLACK: Color = 0;
- pub const RED: Color = 1;
- pub const GREEN: Color = 2;
- pub const YELLOW: Color = 3;
- pub const BLUE: Color = 4;
- pub const MAGENTA: Color = 5;
- pub const CYAN: Color = 6;
- pub const WHITE: Color = 7;
-
- pub const BRIGHT_BLACK: Color = 8;
- pub const BRIGHT_RED: Color = 9;
- pub const BRIGHT_GREEN: Color = 10;
- pub const BRIGHT_YELLOW: Color = 11;
- pub const BRIGHT_BLUE: Color = 12;
- pub const BRIGHT_MAGENTA: Color = 13;
- pub const BRIGHT_CYAN: Color = 14;
- pub const BRIGHT_WHITE: Color = 15;
-}
-
-/// Terminal attributes for use with term.attr().
-///
-/// Most attributes can only be turned on and must be turned off with term.reset().
-/// The ones that can be turned off explicitly take a boolean value.
-/// Color is also represented as an attribute for convenience.
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-pub enum Attr {
- /// Bold (or possibly bright) mode
- Bold,
- /// Dim mode, also called faint or half-bright. Often not supported
- Dim,
- /// Italics mode. Often not supported
- Italic(bool),
- /// Underline mode
- Underline(bool),
- /// Blink mode
- Blink,
- /// Standout mode. Often implemented as Reverse, sometimes coupled with Bold
- Standout(bool),
- /// Reverse mode, inverts the foreground and background colors
- Reverse,
- /// Secure mode, also called invis mode. Hides the printed text
- Secure,
- /// Convenience attribute to set the foreground color
- ForegroundColor(color::Color),
- /// Convenience attribute to set the background color
- BackgroundColor(color::Color),
-}
-
-/// A terminal with similar capabilities to an ANSI Terminal
-/// (foreground/background colors etc).
-pub trait Terminal: Write {
- /// The terminal's output writer type.
- type Output: Write;
-
- /// Sets the foreground color to the given color.
- ///
- /// If the color is a bright color, but the terminal only supports 8 colors,
- /// the corresponding normal color will be used instead.
- ///
- /// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
- /// if there was an I/O error.
- fn fg(&mut self, color: color::Color) -> io::Result<bool>;
-
- /// Sets the background color to the given color.
- ///
- /// If the color is a bright color, but the terminal only supports 8 colors,
- /// the corresponding normal color will be used instead.
- ///
- /// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
- /// if there was an I/O error.
- fn bg(&mut self, color: color::Color) -> io::Result<bool>;
-
- /// Sets the given terminal attribute, if supported. Returns `Ok(true)`
- /// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if
- /// there was an I/O error.
- fn attr(&mut self, attr: Attr) -> io::Result<bool>;
-
- /// Returns `true` if the given terminal attribute is supported.
- fn supports_attr(&self, attr: Attr) -> bool;
-
- /// Resets all terminal attributes and colors to their defaults.
- ///
- /// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there
- /// was an I/O error.
- ///
- /// *Note: This does not flush.*
- ///
- /// That means the reset command may get buffered so, if you aren't planning on doing anything
- /// else that might flush stdout's buffer (e.g., writing a line of text), you should flush after
- /// calling reset.
- fn reset(&mut self) -> io::Result<bool>;
-
- /// Gets an immutable reference to the stream inside
- fn get_ref(&self) -> &Self::Output;
-
- /// Gets a mutable reference to the stream inside
- fn get_mut(&mut self) -> &mut Self::Output;
-
- /// Returns the contained stream, destroying the `Terminal`
- fn into_inner(self) -> Self::Output
- where
- Self: Sized;
-}
+++ /dev/null
-//! Terminfo database interface.
-
-use std::collections::HashMap;
-use std::env;
-use std::error;
-use std::fmt;
-use std::fs::File;
-use std::io::{self, prelude::*, BufReader};
-use std::path::Path;
-
-use crate::color;
-use crate::Attr;
-use crate::Terminal;
-
-use parm::{expand, Param, Variables};
-use parser::compiled::{msys_terminfo, parse};
-use searcher::get_dbpath_for_term;
-
-/// A parsed terminfo database entry.
-#[derive(Debug)]
-pub struct TermInfo {
- /// Names for the terminal
- pub names: Vec<String>,
- /// Map of capability name to boolean value
- pub bools: HashMap<String, bool>,
- /// Map of capability name to numeric value
- pub numbers: HashMap<String, u32>,
- /// Map of capability name to raw (unexpanded) string
- pub strings: HashMap<String, Vec<u8>>,
-}
-
-/// A terminfo creation error.
-#[derive(Debug)]
-pub enum Error {
- /// TermUnset Indicates that the environment doesn't include enough information to find
- /// the terminfo entry.
- TermUnset,
- /// MalformedTerminfo indicates that parsing the terminfo entry failed.
- MalformedTerminfo(String),
- /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
- IoError(io::Error),
-}
-
-impl error::Error for Error {
- fn source(&self) -> Option<&(dyn error::Error + 'static)> {
- use Error::*;
- match self {
- IoError(e) => Some(e),
- _ => None,
- }
- }
-}
-
-impl fmt::Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- use Error::*;
- match *self {
- TermUnset => Ok(()),
- MalformedTerminfo(ref e) => e.fmt(f),
- IoError(ref e) => e.fmt(f),
- }
- }
-}
-
-impl TermInfo {
- /// Creates a TermInfo based on current environment.
- pub fn from_env() -> Result<TermInfo, Error> {
- let term = match env::var("TERM") {
- Ok(name) => TermInfo::from_name(&name),
- Err(..) => return Err(Error::TermUnset),
- };
-
- if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) {
- // msys terminal
- Ok(msys_terminfo())
- } else {
- term
- }
- }
-
- /// Creates a TermInfo for the named terminal.
- pub fn from_name(name: &str) -> Result<TermInfo, Error> {
- get_dbpath_for_term(name)
- .ok_or_else(|| {
- Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
- })
- .and_then(|p| TermInfo::from_path(&(*p)))
- }
-
- /// Parse the given TermInfo.
- pub fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
- Self::_from_path(path.as_ref())
- }
- // Keep the metadata small
- fn _from_path(path: &Path) -> Result<TermInfo, Error> {
- let file = File::open(path).map_err(Error::IoError)?;
- let mut reader = BufReader::new(file);
- parse(&mut reader, false).map_err(Error::MalformedTerminfo)
- }
-}
-
-pub mod searcher;
-
-/// TermInfo format parsing.
-pub mod parser {
- //! ncurses-compatible compiled terminfo format parsing (term(5))
- pub mod compiled;
-}
-pub mod parm;
-
-fn cap_for_attr(attr: Attr) -> &'static str {
- match attr {
- Attr::Bold => "bold",
- Attr::Dim => "dim",
- Attr::Italic(true) => "sitm",
- Attr::Italic(false) => "ritm",
- Attr::Underline(true) => "smul",
- Attr::Underline(false) => "rmul",
- Attr::Blink => "blink",
- Attr::Standout(true) => "smso",
- Attr::Standout(false) => "rmso",
- Attr::Reverse => "rev",
- Attr::Secure => "invis",
- Attr::ForegroundColor(_) => "setaf",
- Attr::BackgroundColor(_) => "setab",
- }
-}
-
-/// A Terminal that knows how many colors it supports, with a reference to its
-/// parsed Terminfo database record.
-pub struct TerminfoTerminal<T> {
- num_colors: u32,
- out: T,
- ti: TermInfo,
-}
-
-impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
- type Output = T;
- fn fg(&mut self, color: color::Color) -> io::Result<bool> {
- let color = self.dim_if_necessary(color);
- if self.num_colors > color {
- return self.apply_cap("setaf", &[Param::Number(color as i32)]);
- }
- Ok(false)
- }
-
- fn bg(&mut self, color: color::Color) -> io::Result<bool> {
- let color = self.dim_if_necessary(color);
- if self.num_colors > color {
- return self.apply_cap("setab", &[Param::Number(color as i32)]);
- }
- Ok(false)
- }
-
- fn attr(&mut self, attr: Attr) -> io::Result<bool> {
- match attr {
- Attr::ForegroundColor(c) => self.fg(c),
- Attr::BackgroundColor(c) => self.bg(c),
- _ => self.apply_cap(cap_for_attr(attr), &[]),
- }
- }
-
- fn supports_attr(&self, attr: Attr) -> bool {
- match attr {
- Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
- _ => {
- let cap = cap_for_attr(attr);
- self.ti.strings.get(cap).is_some()
- }
- }
- }
-
- fn reset(&mut self) -> io::Result<bool> {
- // are there any terminals that have color/attrs and not sgr0?
- // Try falling back to sgr, then op
- let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
- Some(op) => match expand(&op, &[], &mut Variables::new()) {
- Ok(cmd) => cmd,
- Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
- },
- None => return Ok(false),
- };
- self.out.write_all(&cmd).and(Ok(true))
- }
-
- fn get_ref(&self) -> &T {
- &self.out
- }
-
- fn get_mut(&mut self) -> &mut T {
- &mut self.out
- }
-
- fn into_inner(self) -> T
- where
- Self: Sized,
- {
- self.out
- }
-}
-
-impl<T: Write + Send> TerminfoTerminal<T> {
- /// Creates a new TerminfoTerminal with the given TermInfo and Write.
- pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
- let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab")
- {
- terminfo.numbers.get("colors").map_or(0, |&n| n)
- } else {
- 0
- };
-
- TerminfoTerminal { out, ti: terminfo, num_colors: nc }
- }
-
- /// Creates a new TerminfoTerminal for the current environment with the given Write.
- ///
- /// Returns `None` when the terminfo cannot be found or parsed.
- pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
- TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
- }
-
- fn dim_if_necessary(&self, color: color::Color) -> color::Color {
- if color >= self.num_colors && color >= 8 && color < 16 { color - 8 } else { color }
- }
-
- fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
- match self.ti.strings.get(cmd) {
- Some(cmd) => match expand(&cmd, params, &mut Variables::new()) {
- Ok(s) => self.out.write_all(&s).and(Ok(true)),
- Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
- },
- None => Ok(false),
- }
- }
-}
-
-impl<T: Write> Write for TerminfoTerminal<T> {
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- self.out.write(buf)
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.out.flush()
- }
-}
+++ /dev/null
-//! Parameterized string expansion
-
-use self::Param::*;
-use self::States::*;
-
-use std::iter::repeat;
-
-#[cfg(test)]
-mod tests;
-
-#[derive(Clone, Copy, PartialEq)]
-enum States {
- Nothing,
- Percent,
- SetVar,
- GetVar,
- PushParam,
- CharConstant,
- CharClose,
- IntConstant(i32),
- FormatPattern(Flags, FormatState),
- SeekIfElse(usize),
- SeekIfElsePercent(usize),
- SeekIfEnd(usize),
- SeekIfEndPercent(usize),
-}
-
-#[derive(Copy, PartialEq, Clone)]
-enum FormatState {
- Flags,
- Width,
- Precision,
-}
-
-/// Types of parameters a capability can use
-#[allow(missing_docs)]
-#[derive(Clone)]
-pub enum Param {
- Words(String),
- Number(i32),
-}
-
-/// Container for static and dynamic variable arrays
-pub struct Variables {
- /// Static variables A-Z
- sta_va: [Param; 26],
- /// Dynamic variables a-z
- dyn_va: [Param; 26],
-}
-
-impl Variables {
- /// Returns a new zero-initialized Variables
- pub fn new() -> Variables {
- Variables {
- sta_va: [
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- ],
- dyn_va: [
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- ],
- }
- }
-}
-
-/// Expand a parameterized capability
-///
-/// # Arguments
-/// * `cap` - string to expand
-/// * `params` - vector of params for %p1 etc
-/// * `vars` - Variables struct for %Pa etc
-///
-/// To be compatible with ncurses, `vars` should be the same between calls to `expand` for
-/// multiple capabilities for the same terminal.
-pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result<Vec<u8>, String> {
- let mut state = Nothing;
-
- // expanded cap will only rarely be larger than the cap itself
- let mut output = Vec::with_capacity(cap.len());
-
- let mut stack: Vec<Param> = Vec::new();
-
- // Copy parameters into a local vector for mutability
- let mut mparams = [
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- Number(0),
- ];
- for (dst, src) in mparams.iter_mut().zip(params.iter()) {
- *dst = (*src).clone();
- }
-
- for &c in cap.iter() {
- let cur = c as char;
- let mut old_state = state;
- match state {
- Nothing => {
- if cur == '%' {
- state = Percent;
- } else {
- output.push(c);
- }
- }
- Percent => {
- match cur {
- '%' => {
- output.push(c);
- state = Nothing
- }
- 'c' => {
- match stack.pop() {
- // if c is 0, use 0200 (128) for ncurses compatibility
- Some(Number(0)) => output.push(128u8),
- // Don't check bounds. ncurses just casts and truncates.
- Some(Number(c)) => output.push(c as u8),
- Some(_) => return Err("a non-char was used with %c".to_string()),
- None => return Err("stack is empty".to_string()),
- }
- }
- 'p' => state = PushParam,
- 'P' => state = SetVar,
- 'g' => state = GetVar,
- '\'' => state = CharConstant,
- '{' => state = IntConstant(0),
- 'l' => match stack.pop() {
- Some(Words(s)) => stack.push(Number(s.len() as i32)),
- Some(_) => return Err("a non-str was used with %l".to_string()),
- None => return Err("stack is empty".to_string()),
- },
- '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
- match (stack.pop(), stack.pop()) {
- (Some(Number(y)), Some(Number(x))) => stack.push(Number(match cur {
- '+' => x + y,
- '-' => x - y,
- '*' => x * y,
- '/' => x / y,
- '|' => x | y,
- '&' => x & y,
- '^' => x ^ y,
- 'm' => x % y,
- _ => unreachable!("All cases handled"),
- })),
- (Some(_), Some(_)) => {
- return Err(format!("non-numbers on stack with {}", cur));
- }
- _ => return Err("stack is empty".to_string()),
- }
- }
- '=' | '>' | '<' | 'A' | 'O' => match (stack.pop(), stack.pop()) {
- (Some(Number(y)), Some(Number(x))) => stack.push(Number(
- if match cur {
- '=' => x == y,
- '<' => x < y,
- '>' => x > y,
- 'A' => x > 0 && y > 0,
- 'O' => x > 0 || y > 0,
- _ => unreachable!(),
- } {
- 1
- } else {
- 0
- },
- )),
- (Some(_), Some(_)) => {
- return Err(format!("non-numbers on stack with {}", cur));
- }
- _ => return Err("stack is empty".to_string()),
- },
- '!' | '~' => match stack.pop() {
- Some(Number(x)) => stack.push(Number(match cur {
- '!' if x > 0 => 0,
- '!' => 1,
- '~' => !x,
- _ => unreachable!(),
- })),
- Some(_) => return Err(format!("non-numbers on stack with {}", cur)),
- None => return Err("stack is empty".to_string()),
- },
- 'i' => match (&mparams[0], &mparams[1]) {
- (&Number(x), &Number(y)) => {
- mparams[0] = Number(x + 1);
- mparams[1] = Number(y + 1);
- }
- _ => return Err("first two params not numbers with %i".to_string()),
- },
-
- // printf-style support for %doxXs
- 'd' | 'o' | 'x' | 'X' | 's' => {
- if let Some(arg) = stack.pop() {
- let flags = Flags::new();
- let res = format(arg, FormatOp::from_char(cur), flags)?;
- output.extend(res.iter().cloned());
- } else {
- return Err("stack is empty".to_string());
- }
- }
- ':' | '#' | ' ' | '.' | '0'..='9' => {
- let mut flags = Flags::new();
- let mut fstate = FormatState::Flags;
- match cur {
- ':' => (),
- '#' => flags.alternate = true,
- ' ' => flags.space = true,
- '.' => fstate = FormatState::Precision,
- '0'..='9' => {
- flags.width = cur as usize - '0' as usize;
- fstate = FormatState::Width;
- }
- _ => unreachable!(),
- }
- state = FormatPattern(flags, fstate);
- }
-
- // conditionals
- '?' => (),
- 't' => match stack.pop() {
- Some(Number(0)) => state = SeekIfElse(0),
- Some(Number(_)) => (),
- Some(_) => return Err("non-number on stack with conditional".to_string()),
- None => return Err("stack is empty".to_string()),
- },
- 'e' => state = SeekIfEnd(0),
- ';' => (),
- _ => return Err(format!("unrecognized format option {}", cur)),
- }
- }
- PushParam => {
- // params are 1-indexed
- stack.push(
- mparams[match cur.to_digit(10) {
- Some(d) => d as usize - 1,
- None => return Err("bad param number".to_string()),
- }]
- .clone(),
- );
- }
- SetVar => {
- if cur >= 'A' && cur <= 'Z' {
- if let Some(arg) = stack.pop() {
- let idx = (cur as u8) - b'A';
- vars.sta_va[idx as usize] = arg;
- } else {
- return Err("stack is empty".to_string());
- }
- } else if cur >= 'a' && cur <= 'z' {
- if let Some(arg) = stack.pop() {
- let idx = (cur as u8) - b'a';
- vars.dyn_va[idx as usize] = arg;
- } else {
- return Err("stack is empty".to_string());
- }
- } else {
- return Err("bad variable name in %P".to_string());
- }
- }
- GetVar => {
- if cur >= 'A' && cur <= 'Z' {
- let idx = (cur as u8) - b'A';
- stack.push(vars.sta_va[idx as usize].clone());
- } else if cur >= 'a' && cur <= 'z' {
- let idx = (cur as u8) - b'a';
- stack.push(vars.dyn_va[idx as usize].clone());
- } else {
- return Err("bad variable name in %g".to_string());
- }
- }
- CharConstant => {
- stack.push(Number(c as i32));
- state = CharClose;
- }
- CharClose => {
- if cur != '\'' {
- return Err("malformed character constant".to_string());
- }
- }
- IntConstant(i) => {
- if cur == '}' {
- stack.push(Number(i));
- state = Nothing;
- } else if let Some(digit) = cur.to_digit(10) {
- match i.checked_mul(10).and_then(|i_ten| i_ten.checked_add(digit as i32)) {
- Some(i) => {
- state = IntConstant(i);
- old_state = Nothing;
- }
- None => return Err("int constant too large".to_string()),
- }
- } else {
- return Err("bad int constant".to_string());
- }
- }
- FormatPattern(ref mut flags, ref mut fstate) => {
- old_state = Nothing;
- match (*fstate, cur) {
- (_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => {
- if let Some(arg) = stack.pop() {
- let res = format(arg, FormatOp::from_char(cur), *flags)?;
- output.extend(res.iter().cloned());
- // will cause state to go to Nothing
- old_state = FormatPattern(*flags, *fstate);
- } else {
- return Err("stack is empty".to_string());
- }
- }
- (FormatState::Flags, '#') => {
- flags.alternate = true;
- }
- (FormatState::Flags, '-') => {
- flags.left = true;
- }
- (FormatState::Flags, '+') => {
- flags.sign = true;
- }
- (FormatState::Flags, ' ') => {
- flags.space = true;
- }
- (FormatState::Flags, '0'..='9') => {
- flags.width = cur as usize - '0' as usize;
- *fstate = FormatState::Width;
- }
- (FormatState::Flags, '.') => {
- *fstate = FormatState::Precision;
- }
- (FormatState::Width, '0'..='9') => {
- let old = flags.width;
- flags.width = flags.width * 10 + (cur as usize - '0' as usize);
- if flags.width < old {
- return Err("format width overflow".to_string());
- }
- }
- (FormatState::Width, '.') => {
- *fstate = FormatState::Precision;
- }
- (FormatState::Precision, '0'..='9') => {
- let old = flags.precision;
- flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
- if flags.precision < old {
- return Err("format precision overflow".to_string());
- }
- }
- _ => return Err("invalid format specifier".to_string()),
- }
- }
- SeekIfElse(level) => {
- if cur == '%' {
- state = SeekIfElsePercent(level);
- }
- old_state = Nothing;
- }
- SeekIfElsePercent(level) => {
- if cur == ';' {
- if level == 0 {
- state = Nothing;
- } else {
- state = SeekIfElse(level - 1);
- }
- } else if cur == 'e' && level == 0 {
- state = Nothing;
- } else if cur == '?' {
- state = SeekIfElse(level + 1);
- } else {
- state = SeekIfElse(level);
- }
- }
- SeekIfEnd(level) => {
- if cur == '%' {
- state = SeekIfEndPercent(level);
- }
- old_state = Nothing;
- }
- SeekIfEndPercent(level) => {
- if cur == ';' {
- if level == 0 {
- state = Nothing;
- } else {
- state = SeekIfEnd(level - 1);
- }
- } else if cur == '?' {
- state = SeekIfEnd(level + 1);
- } else {
- state = SeekIfEnd(level);
- }
- }
- }
- if state == old_state {
- state = Nothing;
- }
- }
- Ok(output)
-}
-
-#[derive(Copy, PartialEq, Clone)]
-struct Flags {
- width: usize,
- precision: usize,
- alternate: bool,
- left: bool,
- sign: bool,
- space: bool,
-}
-
-impl Flags {
- fn new() -> Flags {
- Flags { width: 0, precision: 0, alternate: false, left: false, sign: false, space: false }
- }
-}
-
-#[derive(Copy, Clone)]
-enum FormatOp {
- Digit,
- Octal,
- LowerHex,
- UpperHex,
- String,
-}
-
-impl FormatOp {
- fn from_char(c: char) -> FormatOp {
- match c {
- 'd' => FormatOp::Digit,
- 'o' => FormatOp::Octal,
- 'x' => FormatOp::LowerHex,
- 'X' => FormatOp::UpperHex,
- 's' => FormatOp::String,
- _ => panic!("bad FormatOp char"),
- }
- }
- fn to_char(self) -> char {
- match self {
- FormatOp::Digit => 'd',
- FormatOp::Octal => 'o',
- FormatOp::LowerHex => 'x',
- FormatOp::UpperHex => 'X',
- FormatOp::String => 's',
- }
- }
-}
-
-fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
- let mut s = match val {
- Number(d) => {
- match op {
- FormatOp::Digit => {
- if flags.sign {
- format!("{:+01$}", d, flags.precision)
- } else if d < 0 {
- // C doesn't take sign into account in precision calculation.
- format!("{:01$}", d, flags.precision + 1)
- } else if flags.space {
- format!(" {:01$}", d, flags.precision)
- } else {
- format!("{:01$}", d, flags.precision)
- }
- }
- FormatOp::Octal => {
- if flags.alternate {
- // Leading octal zero counts against precision.
- format!("0{:01$o}", d, flags.precision.saturating_sub(1))
- } else {
- format!("{:01$o}", d, flags.precision)
- }
- }
- FormatOp::LowerHex => {
- if flags.alternate && d != 0 {
- format!("0x{:01$x}", d, flags.precision)
- } else {
- format!("{:01$x}", d, flags.precision)
- }
- }
- FormatOp::UpperHex => {
- if flags.alternate && d != 0 {
- format!("0X{:01$X}", d, flags.precision)
- } else {
- format!("{:01$X}", d, flags.precision)
- }
- }
- FormatOp::String => return Err("non-number on stack with %s".to_string()),
- }
- .into_bytes()
- }
- Words(s) => match op {
- FormatOp::String => {
- let mut s = s.into_bytes();
- if flags.precision > 0 && flags.precision < s.len() {
- s.truncate(flags.precision);
- }
- s
- }
- _ => return Err(format!("non-string on stack with %{}", op.to_char())),
- },
- };
- if flags.width > s.len() {
- let n = flags.width - s.len();
- if flags.left {
- s.extend(repeat(b' ').take(n));
- } else {
- let mut s_ = Vec::with_capacity(flags.width);
- s_.extend(repeat(b' ').take(n));
- s_.extend(s.into_iter());
- s = s_;
- }
- }
- Ok(s)
-}
+++ /dev/null
-use super::*;
-
-use std::result::Result::Ok;
-
-#[test]
-fn test_basic_setabf() {
- let s = b"\\E[48;5;%p1%dm";
- assert_eq!(
- expand(s, &[Number(1)], &mut Variables::new()).unwrap(),
- "\\E[48;5;1m".bytes().collect::<Vec<_>>()
- );
-}
-
-#[test]
-fn test_multiple_int_constants() {
- assert_eq!(
- expand(b"%{1}%{2}%d%d", &[], &mut Variables::new()).unwrap(),
- "21".bytes().collect::<Vec<_>>()
- );
-}
-
-#[test]
-fn test_op_i() {
- let mut vars = Variables::new();
- assert_eq!(
- expand(b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d", &[Number(1), Number(2), Number(3)], &mut vars),
- Ok("123233".bytes().collect::<Vec<_>>())
- );
- assert_eq!(
- expand(b"%p1%d%p2%d%i%p1%d%p2%d", &[], &mut vars),
- Ok("0011".bytes().collect::<Vec<_>>())
- );
-}
-
-#[test]
-fn test_param_stack_failure_conditions() {
- let mut varstruct = Variables::new();
- let vars = &mut varstruct;
- fn get_res(
- fmt: &str,
- cap: &str,
- params: &[Param],
- vars: &mut Variables,
- ) -> Result<Vec<u8>, String> {
- let mut u8v: Vec<_> = fmt.bytes().collect();
- u8v.extend(cap.as_bytes().iter().map(|&b| b));
- expand(&u8v, params, vars)
- }
-
- let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
- for &cap in caps.iter() {
- let res = get_res("", cap, &[], vars);
- assert!(res.is_err(), "Op {} succeeded incorrectly with 0 stack entries", cap);
- let p = if cap == "%s" || cap == "%l" { Words("foo".to_string()) } else { Number(97) };
- let res = get_res("%p1", cap, &[p], vars);
- assert!(res.is_ok(), "Op {} failed with 1 stack entry: {}", cap, res.unwrap_err());
- }
- let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
- for &cap in caps.iter() {
- let res = expand(cap.as_bytes(), &[], vars);
- assert!(res.is_err(), "Binop {} succeeded incorrectly with 0 stack entries", cap);
- let res = get_res("%{1}", cap, &[], vars);
- assert!(res.is_err(), "Binop {} succeeded incorrectly with 1 stack entry", cap);
- let res = get_res("%{1}%{2}", cap, &[], vars);
- assert!(res.is_ok(), "Binop {} failed with 2 stack entries: {}", cap, res.unwrap_err());
- }
-}
-
-#[test]
-fn test_push_bad_param() {
- assert!(expand(b"%pa", &[], &mut Variables::new()).is_err());
-}
-
-#[test]
-fn test_comparison_ops() {
- let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
- for &(op, bs) in v.iter() {
- let s = format!("%{{1}}%{{2}}%{}%d", op);
- let res = expand(s.as_bytes(), &[], &mut Variables::new());
- assert!(res.is_ok(), "{}", res.unwrap_err());
- assert_eq!(res.unwrap(), vec![b'0' + bs[0]]);
- let s = format!("%{{1}}%{{1}}%{}%d", op);
- let res = expand(s.as_bytes(), &[], &mut Variables::new());
- assert!(res.is_ok(), "{}", res.unwrap_err());
- assert_eq!(res.unwrap(), vec![b'0' + bs[1]]);
- let s = format!("%{{2}}%{{1}}%{}%d", op);
- let res = expand(s.as_bytes(), &[], &mut Variables::new());
- assert!(res.is_ok(), "{}", res.unwrap_err());
- assert_eq!(res.unwrap(), vec![b'0' + bs[2]]);
- }
-}
-
-#[test]
-fn test_conditionals() {
- let mut vars = Variables::new();
- let s = b"\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m";
- let res = expand(s, &[Number(1)], &mut vars);
- assert!(res.is_ok(), "{}", res.unwrap_err());
- assert_eq!(res.unwrap(), "\\E[31m".bytes().collect::<Vec<_>>());
- let res = expand(s, &[Number(8)], &mut vars);
- assert!(res.is_ok(), "{}", res.unwrap_err());
- assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::<Vec<_>>());
- let res = expand(s, &[Number(42)], &mut vars);
- assert!(res.is_ok(), "{}", res.unwrap_err());
- assert_eq!(res.unwrap(), "\\E[38;5;42m".bytes().collect::<Vec<_>>());
-}
-
-#[test]
-fn test_format() {
- let mut varstruct = Variables::new();
- let vars = &mut varstruct;
- assert_eq!(
- expand(
- b"%p1%s%p2%2s%p3%2s%p4%.2s",
- &[
- Words("foo".to_string()),
- Words("foo".to_string()),
- Words("f".to_string()),
- Words("foo".to_string())
- ],
- vars
- ),
- Ok("foofoo ffo".bytes().collect::<Vec<_>>())
- );
- assert_eq!(
- expand(b"%p1%:-4.2s", &[Words("foo".to_string())], vars),
- Ok("fo ".bytes().collect::<Vec<_>>())
- );
-
- assert_eq!(
- expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars),
- Ok("1001 1+1".bytes().collect::<Vec<_>>())
- );
- assert_eq!(
- expand(b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X", &[Number(15), Number(27)], vars),
- Ok("17017 001b0X001B".bytes().collect::<Vec<_>>())
- );
-}
+++ /dev/null
-#![allow(non_upper_case_globals, missing_docs)]
-
-//! ncurses-compatible compiled terminfo format parsing (term(5))
-
-use super::super::TermInfo;
-use std::collections::HashMap;
-use std::io;
-use std::io::prelude::*;
-
-#[cfg(test)]
-mod tests;
-
-// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
-
-#[rustfmt::skip]
-pub static boolfnames: &[&str] = &["auto_left_margin", "auto_right_margin",
- "no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
- "hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
- "memory_below", "move_insert_mode", "move_standout_mode", "over_strike", "status_line_esc_ok",
- "dest_tabs_magic_smso", "tilde_glitch", "transparent_underline", "xon_xoff", "needs_xon_xoff",
- "prtr_silent", "hard_cursor", "non_rev_rmcup", "no_pad_char", "non_dest_scroll_region",
- "can_change", "back_color_erase", "hue_lightness_saturation", "col_addr_glitch",
- "cr_cancels_micro_mode", "has_print_wheel", "row_addr_glitch", "semi_auto_right_margin",
- "cpi_changes_res", "lpi_changes_res", "backspaces_with_bs", "crt_no_scrolling",
- "no_correctly_working_cr", "gnu_has_meta_key", "linefeed_is_newline", "has_hardware_tabs",
- "return_does_clr_eol"];
-
-#[rustfmt::skip]
-pub static boolnames: &[&str] = &["bw", "am", "xsb", "xhp", "xenl", "eo",
- "gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
- "nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
- "xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
-
-#[rustfmt::skip]
-pub static numfnames: &[&str] = &[ "columns", "init_tabs", "lines",
- "lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
- "width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
- "maximum_windows", "max_colors", "max_pairs", "no_color_video", "buffer_capacity",
- "dot_vert_spacing", "dot_horz_spacing", "max_micro_address", "max_micro_jump", "micro_col_size",
- "micro_line_size", "number_of_pins", "output_res_char", "output_res_line",
- "output_res_horz_inch", "output_res_vert_inch", "print_rate", "wide_char_size", "buttons",
- "bit_image_entwining", "bit_image_type", "magic_cookie_glitch_ul", "carriage_return_delay",
- "new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
-
-#[rustfmt::skip]
-pub static numnames: &[&str] = &[ "cols", "it", "lines", "lm", "xmc", "pb",
- "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
- "spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
- "btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
-
-#[rustfmt::skip]
-pub static stringfnames: &[&str] = &[ "back_tab", "bell", "carriage_return",
- "change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
- "column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
- "cursor_invisible", "cursor_left", "cursor_mem_address", "cursor_normal", "cursor_right",
- "cursor_to_ll", "cursor_up", "cursor_visible", "delete_character", "delete_line",
- "dis_status_line", "down_half_line", "enter_alt_charset_mode", "enter_blink_mode",
- "enter_bold_mode", "enter_ca_mode", "enter_delete_mode", "enter_dim_mode", "enter_insert_mode",
- "enter_secure_mode", "enter_protected_mode", "enter_reverse_mode", "enter_standout_mode",
- "enter_underline_mode", "erase_chars", "exit_alt_charset_mode", "exit_attribute_mode",
- "exit_ca_mode", "exit_delete_mode", "exit_insert_mode", "exit_standout_mode",
- "exit_underline_mode", "flash_screen", "form_feed", "from_status_line", "init_1string",
- "init_2string", "init_3string", "init_file", "insert_character", "insert_line",
- "insert_padding", "key_backspace", "key_catab", "key_clear", "key_ctab", "key_dc", "key_dl",
- "key_down", "key_eic", "key_eol", "key_eos", "key_f0", "key_f1", "key_f10", "key_f2", "key_f3",
- "key_f4", "key_f5", "key_f6", "key_f7", "key_f8", "key_f9", "key_home", "key_ic", "key_il",
- "key_left", "key_ll", "key_npage", "key_ppage", "key_right", "key_sf", "key_sr", "key_stab",
- "key_up", "keypad_local", "keypad_xmit", "lab_f0", "lab_f1", "lab_f10", "lab_f2", "lab_f3",
- "lab_f4", "lab_f5", "lab_f6", "lab_f7", "lab_f8", "lab_f9", "meta_off", "meta_on", "newline",
- "pad_char", "parm_dch", "parm_delete_line", "parm_down_cursor", "parm_ich", "parm_index",
- "parm_insert_line", "parm_left_cursor", "parm_right_cursor", "parm_rindex", "parm_up_cursor",
- "pkey_key", "pkey_local", "pkey_xmit", "print_screen", "prtr_off", "prtr_on", "repeat_char",
- "reset_1string", "reset_2string", "reset_3string", "reset_file", "restore_cursor",
- "row_address", "save_cursor", "scroll_forward", "scroll_reverse", "set_attributes", "set_tab",
- "set_window", "tab", "to_status_line", "underline_char", "up_half_line", "init_prog", "key_a1",
- "key_a3", "key_b2", "key_c1", "key_c3", "prtr_non", "char_padding", "acs_chars", "plab_norm",
- "key_btab", "enter_xon_mode", "exit_xon_mode", "enter_am_mode", "exit_am_mode", "xon_character",
- "xoff_character", "ena_acs", "label_on", "label_off", "key_beg", "key_cancel", "key_close",
- "key_command", "key_copy", "key_create", "key_end", "key_enter", "key_exit", "key_find",
- "key_help", "key_mark", "key_message", "key_move", "key_next", "key_open", "key_options",
- "key_previous", "key_print", "key_redo", "key_reference", "key_refresh", "key_replace",
- "key_restart", "key_resume", "key_save", "key_suspend", "key_undo", "key_sbeg", "key_scancel",
- "key_scommand", "key_scopy", "key_screate", "key_sdc", "key_sdl", "key_select", "key_send",
- "key_seol", "key_sexit", "key_sfind", "key_shelp", "key_shome", "key_sic", "key_sleft",
- "key_smessage", "key_smove", "key_snext", "key_soptions", "key_sprevious", "key_sprint",
- "key_sredo", "key_sreplace", "key_sright", "key_srsume", "key_ssave", "key_ssuspend",
- "key_sundo", "req_for_input", "key_f11", "key_f12", "key_f13", "key_f14", "key_f15", "key_f16",
- "key_f17", "key_f18", "key_f19", "key_f20", "key_f21", "key_f22", "key_f23", "key_f24",
- "key_f25", "key_f26", "key_f27", "key_f28", "key_f29", "key_f30", "key_f31", "key_f32",
- "key_f33", "key_f34", "key_f35", "key_f36", "key_f37", "key_f38", "key_f39", "key_f40",
- "key_f41", "key_f42", "key_f43", "key_f44", "key_f45", "key_f46", "key_f47", "key_f48",
- "key_f49", "key_f50", "key_f51", "key_f52", "key_f53", "key_f54", "key_f55", "key_f56",
- "key_f57", "key_f58", "key_f59", "key_f60", "key_f61", "key_f62", "key_f63", "clr_bol",
- "clear_margins", "set_left_margin", "set_right_margin", "label_format", "set_clock",
- "display_clock", "remove_clock", "create_window", "goto_window", "hangup", "dial_phone",
- "quick_dial", "tone", "pulse", "flash_hook", "fixed_pause", "wait_tone", "user0", "user1",
- "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "orig_pair",
- "orig_colors", "initialize_color", "initialize_pair", "set_color_pair", "set_foreground",
- "set_background", "change_char_pitch", "change_line_pitch", "change_res_horz",
- "change_res_vert", "define_char", "enter_doublewide_mode", "enter_draft_quality",
- "enter_italics_mode", "enter_leftward_mode", "enter_micro_mode", "enter_near_letter_quality",
- "enter_normal_quality", "enter_shadow_mode", "enter_subscript_mode", "enter_superscript_mode",
- "enter_upward_mode", "exit_doublewide_mode", "exit_italics_mode", "exit_leftward_mode",
- "exit_micro_mode", "exit_shadow_mode", "exit_subscript_mode", "exit_superscript_mode",
- "exit_upward_mode", "micro_column_address", "micro_down", "micro_left", "micro_right",
- "micro_row_address", "micro_up", "order_of_pins", "parm_down_micro", "parm_left_micro",
- "parm_right_micro", "parm_up_micro", "select_char_set", "set_bottom_margin",
- "set_bottom_margin_parm", "set_left_margin_parm", "set_right_margin_parm", "set_top_margin",
- "set_top_margin_parm", "start_bit_image", "start_char_set_def", "stop_bit_image",
- "stop_char_set_def", "subscript_characters", "superscript_characters", "these_cause_cr",
- "zero_motion", "char_set_names", "key_mouse", "mouse_info", "req_mouse_pos", "get_mouse",
- "set_a_foreground", "set_a_background", "pkey_plab", "device_type", "code_set_init",
- "set0_des_seq", "set1_des_seq", "set2_des_seq", "set3_des_seq", "set_lr_margin",
- "set_tb_margin", "bit_image_repeat", "bit_image_newline", "bit_image_carriage_return",
- "color_names", "define_bit_image_region", "end_bit_image_region", "set_color_band",
- "set_page_length", "display_pc_char", "enter_pc_charset_mode", "exit_pc_charset_mode",
- "enter_scancode_mode", "exit_scancode_mode", "pc_term_options", "scancode_escape",
- "alt_scancode_esc", "enter_horizontal_hl_mode", "enter_left_hl_mode", "enter_low_hl_mode",
- "enter_right_hl_mode", "enter_top_hl_mode", "enter_vertical_hl_mode", "set_a_attributes",
- "set_pglen_inch", "termcap_init2", "termcap_reset", "linefeed_if_not_lf", "backspace_if_not_bs",
- "other_non_function_keys", "arrow_key_map", "acs_ulcorner", "acs_llcorner", "acs_urcorner",
- "acs_lrcorner", "acs_ltee", "acs_rtee", "acs_btee", "acs_ttee", "acs_hline", "acs_vline",
- "acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
-
-#[rustfmt::skip]
-pub static stringnames: &[&str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
- "_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
- "ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
- "dim", "smir", "invis", "prot", "rev", "smso", "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc",
- "rmir", "rmso", "rmul", "flash", "ff", "fsl", "is1", "is2", "is3", "if", "ich1", "il1", "ip",
- "kbs", "ktbc", "kclr", "kctab", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_",
- "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_",
- "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_",
- "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey",
- "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind",
- "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p",
- "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln",
- "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "kent", "kext", "kfnd", "khlp",
- "kmrk", "kmsg", "kmov", "knxt", "kopn", "kopt", "kprv", "kprt", "krdo", "kref", "krfr", "krpl",
- "krst", "kres", "ksav", "kspd", "kund", "kBEG", "kCAN", "kCMD", "kCPY", "kCRT", "_", "_",
- "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT",
- "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_",
- "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
- "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
- "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
- "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_",
- "_", "_", "_", "_", "_", "_", "_", "_", "_", "op", "oc", "initc", "initp", "scp", "setf",
- "setb", "cpi", "lpi", "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", "smicm", "snlq",
- "snrmq", "sshm", "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", "rshm", "rsubm",
- "rsupm", "rum", "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", "porder", "mcud", "mcub",
- "mcuf", "mcuu", "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", "smgtp", "sbim", "scsd",
- "rbim", "rcsd", "subcs", "supcs", "docr", "zerom", "csnm", "kmous", "minfo", "reqmp", "getm",
- "setaf", "setab", "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", "s3ds", "smglr", "smgtb",
- "birep", "binel", "bicr", "colornm", "defbi", "endbi", "setcolor", "slines", "dispc", "smpch",
- "rmpch", "smsc", "rmsc", "pctrm", "scesc", "scesa", "ehhlm", "elhlm", "elohlm", "erhlm",
- "ethlm", "evhlm", "sgr1", "slength", "OTi2", "OTrs", "OTnl", "OTbs", "OTko", "OTma", "OTG2",
- "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
- "box1"];
-
-fn read_le_u16(r: &mut dyn io::Read) -> io::Result<u16> {
- let mut b = [0; 2];
- r.read_exact(&mut b)?;
- Ok((b[0] as u16) | ((b[1] as u16) << 8))
-}
-
-fn read_le_u32(r: &mut dyn io::Read) -> io::Result<u32> {
- let mut b = [0; 4];
- r.read_exact(&mut b)?;
- Ok((b[0] as u32) | ((b[1] as u32) << 8) | ((b[2] as u32) << 16) | ((b[3] as u32) << 24))
-}
-
-fn read_byte(r: &mut dyn io::Read) -> io::Result<u8> {
- match r.bytes().next() {
- Some(s) => s,
- None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
- }
-}
-
-/// Parse a compiled terminfo entry, using long capability names if `longnames`
-/// is true
-pub fn parse(file: &mut dyn io::Read, longnames: bool) -> Result<TermInfo, String> {
- macro_rules! t( ($e:expr) => (
- match $e {
- Ok(e) => e,
- Err(e) => return Err(e.to_string())
- }
- ) );
-
- let (bnames, snames, nnames) = if longnames {
- (boolfnames, stringfnames, numfnames)
- } else {
- (boolnames, stringnames, numnames)
- };
-
- // Check magic number
- let magic = t!(read_le_u16(file));
-
- let extended = match magic {
- 0o0432 => false,
- 0o01036 => true,
- _ => return Err(format!("invalid magic number, found {:o}", magic)),
- };
-
- // According to the spec, these fields must be >= -1 where -1 means that the feature is not
- // supported. Using 0 instead of -1 works because we skip sections with length 0.
- macro_rules! read_nonneg {
- () => {{
- match t!(read_le_u16(file)) as i16 {
- n if n >= 0 => n as usize,
- -1 => 0,
- _ => return Err("incompatible file: length fields must be >= -1".to_string()),
- }
- }};
- }
-
- let names_bytes = read_nonneg!();
- let bools_bytes = read_nonneg!();
- let numbers_count = read_nonneg!();
- let string_offsets_count = read_nonneg!();
- let string_table_bytes = read_nonneg!();
-
- if names_bytes == 0 {
- return Err("incompatible file: names field must be at least 1 byte wide".to_string());
- }
-
- if bools_bytes > boolnames.len() {
- return Err("incompatible file: more booleans than expected".to_string());
- }
-
- if numbers_count > numnames.len() {
- return Err("incompatible file: more numbers than expected".to_string());
- }
-
- if string_offsets_count > stringnames.len() {
- return Err("incompatible file: more string offsets than expected".to_string());
- }
-
- // don't read NUL
- let mut bytes = Vec::new();
- t!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
- let names_str = match String::from_utf8(bytes) {
- Ok(s) => s,
- Err(_) => return Err("input not utf-8".to_string()),
- };
-
- let term_names: Vec<String> = names_str.split('|').map(|s| s.to_string()).collect();
- // consume NUL
- if t!(read_byte(file)) != b'\0' {
- return Err("incompatible file: missing null terminator for names section".to_string());
- }
-
- let bools_map: HashMap<String, bool> = t! {
- (0..bools_bytes).filter_map(|i| match read_byte(file) {
- Err(e) => Some(Err(e)),
- Ok(1) => Some(Ok((bnames[i].to_string(), true))),
- Ok(_) => None
- }).collect()
- };
-
- if (bools_bytes + names_bytes) % 2 == 1 {
- t!(read_byte(file)); // compensate for padding
- }
-
- let numbers_map: HashMap<String, u32> = t! {
- (0..numbers_count).filter_map(|i| {
- let number = if extended { read_le_u32(file) } else { read_le_u16(file).map(Into::into) };
-
- match number {
- Ok(0xFFFF) => None,
- Ok(n) => Some(Ok((nnames[i].to_string(), n))),
- Err(e) => Some(Err(e))
- }
- }).collect()
- };
-
- let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
- let string_offsets: Vec<u16> =
- t!((0..string_offsets_count).map(|_| read_le_u16(file)).collect());
-
- let mut string_table = Vec::new();
- t!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
-
- t!(string_offsets
- .into_iter()
- .enumerate()
- .filter(|&(_, offset)| {
- // non-entry
- offset != 0xFFFF
- })
- .map(|(i, offset)| {
- let offset = offset as usize;
-
- let name = if snames[i] == "_" { stringfnames[i] } else { snames[i] };
-
- if offset == 0xFFFE {
- // undocumented: FFFE indicates cap@, which means the capability is not present
- // unsure if the handling for this is correct
- return Ok((name.to_string(), Vec::new()));
- }
-
- // Find the offset of the NUL we want to go to
- let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0);
- match nulpos {
- Some(len) => {
- Ok((name.to_string(), string_table[offset..offset + len].to_vec()))
- }
- None => Err("invalid file: missing NUL in string_table".to_string()),
- }
- })
- .collect())
- } else {
- HashMap::new()
- };
-
- // And that's all there is to it
- Ok(TermInfo { names: term_names, bools: bools_map, numbers: numbers_map, strings: string_map })
-}
-
-/// Creates a dummy TermInfo struct for msys terminals
-pub fn msys_terminfo() -> TermInfo {
- let mut strings = HashMap::new();
- strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
- strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
- strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
- strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
-
- let mut numbers = HashMap::new();
- numbers.insert("colors".to_string(), 8);
-
- TermInfo {
- names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
- bools: HashMap::new(),
- numbers,
- strings,
- }
-}
+++ /dev/null
-use super::*;
-
-#[test]
-fn test_veclens() {
- assert_eq!(boolfnames.len(), boolnames.len());
- assert_eq!(numfnames.len(), numnames.len());
- assert_eq!(stringfnames.len(), stringnames.len());
-}
+++ /dev/null
-//! ncurses-compatible database discovery.
-//!
-//! Does not support hashed database, only filesystem!
-
-use std::env;
-use std::fs;
-use std::path::PathBuf;
-
-#[cfg(test)]
-mod tests;
-
-/// Return path to database entry for `term`
-#[allow(deprecated)]
-pub fn get_dbpath_for_term(term: &str) -> Option<PathBuf> {
- let mut dirs_to_search = Vec::new();
- let first_char = term.chars().next()?;
-
- // Find search directory
- if let Some(dir) = env::var_os("TERMINFO") {
- dirs_to_search.push(PathBuf::from(dir));
- }
-
- if let Ok(dirs) = env::var("TERMINFO_DIRS") {
- for i in dirs.split(':') {
- if i == "" {
- dirs_to_search.push(PathBuf::from("/usr/share/terminfo"));
- } else {
- dirs_to_search.push(PathBuf::from(i));
- }
- }
- } else {
- // Found nothing in TERMINFO_DIRS, use the default paths:
- // According to /etc/terminfo/README, after looking at
- // ~/.terminfo, ncurses will search /etc/terminfo, then
- // /lib/terminfo, and eventually /usr/share/terminfo.
- // On Haiku the database can be found at /boot/system/data/terminfo
- if let Some(mut homedir) = env::home_dir() {
- homedir.push(".terminfo");
- dirs_to_search.push(homedir)
- }
-
- dirs_to_search.push(PathBuf::from("/etc/terminfo"));
- dirs_to_search.push(PathBuf::from("/lib/terminfo"));
- dirs_to_search.push(PathBuf::from("/usr/share/terminfo"));
- dirs_to_search.push(PathBuf::from("/boot/system/data/terminfo"));
- }
-
- // Look for the terminal in all of the search directories
- for mut p in dirs_to_search {
- if fs::metadata(&p).is_ok() {
- p.push(&first_char.to_string());
- p.push(&term);
- if fs::metadata(&p).is_ok() {
- return Some(p);
- }
- p.pop();
- p.pop();
-
- // on some installations the dir is named after the hex of the char
- // (e.g., macOS)
- p.push(&format!("{:x}", first_char as usize));
- p.push(term);
- if fs::metadata(&p).is_ok() {
- return Some(p);
- }
- }
- }
- None
-}
+++ /dev/null
-use super::*;
-
-#[test]
-#[ignore = "buildbots don't have ncurses installed and I can't mock everything I need"]
-fn test_get_dbpath_for_term() {
- // woefully inadequate test coverage
- // note: current tests won't work with non-standard terminfo hierarchies (e.g., macOS's)
- use std::env;
- // FIXME (#9639): This needs to handle non-utf8 paths
- fn x(t: &str) -> String {
- let p = get_dbpath_for_term(t).expect("no terminfo entry found");
- p.to_str().unwrap().to_string()
- }
- assert!(x("screen") == "/usr/share/terminfo/s/screen");
- assert!(get_dbpath_for_term("") == None);
- env::set_var("TERMINFO_DIRS", ":");
- assert!(x("screen") == "/usr/share/terminfo/s/screen");
- env::remove_var("TERMINFO_DIRS");
-}
+++ /dev/null
-//! Windows console handling
-
-// FIXME (#13400): this is only a tiny fraction of the Windows console api
-
-use std::io;
-use std::io::prelude::*;
-
-use crate::color;
-use crate::Attr;
-use crate::Terminal;
-
-/// A Terminal implementation that uses the Win32 Console API.
-pub struct WinConsole<T> {
- buf: T,
- def_foreground: color::Color,
- def_background: color::Color,
- foreground: color::Color,
- background: color::Color,
-}
-
-type SHORT = i16;
-type WORD = u16;
-type DWORD = u32;
-type BOOL = i32;
-type HANDLE = *mut u8;
-
-#[allow(non_snake_case)]
-#[repr(C)]
-struct SMALL_RECT {
- Left: SHORT,
- Top: SHORT,
- Right: SHORT,
- Bottom: SHORT,
-}
-
-#[allow(non_snake_case)]
-#[repr(C)]
-struct COORD {
- X: SHORT,
- Y: SHORT,
-}
-
-#[allow(non_snake_case)]
-#[repr(C)]
-struct CONSOLE_SCREEN_BUFFER_INFO {
- dwSize: COORD,
- dwCursorPosition: COORD,
- wAttributes: WORD,
- srWindow: SMALL_RECT,
- dwMaximumWindowSize: COORD,
-}
-
-#[allow(non_snake_case)]
-#[link(name = "kernel32")]
-extern "system" {
- fn SetConsoleTextAttribute(handle: HANDLE, attr: WORD) -> BOOL;
- fn GetStdHandle(which: DWORD) -> HANDLE;
- fn GetConsoleScreenBufferInfo(handle: HANDLE, info: *mut CONSOLE_SCREEN_BUFFER_INFO) -> BOOL;
-}
-
-fn color_to_bits(color: color::Color) -> u16 {
- // magic numbers from mingw-w64's wincon.h
-
- let bits = match color % 8 {
- color::BLACK => 0,
- color::BLUE => 0x1,
- color::GREEN => 0x2,
- color::RED => 0x4,
- color::YELLOW => 0x2 | 0x4,
- color::MAGENTA => 0x1 | 0x4,
- color::CYAN => 0x1 | 0x2,
- color::WHITE => 0x1 | 0x2 | 0x4,
- _ => unreachable!(),
- };
-
- if color >= 8 { bits | 0x8 } else { bits }
-}
-
-fn bits_to_color(bits: u16) -> color::Color {
- let color = match bits & 0x7 {
- 0 => color::BLACK,
- 0x1 => color::BLUE,
- 0x2 => color::GREEN,
- 0x4 => color::RED,
- 0x6 => color::YELLOW,
- 0x5 => color::MAGENTA,
- 0x3 => color::CYAN,
- 0x7 => color::WHITE,
- _ => unreachable!(),
- };
-
- color | (u32::from(bits) & 0x8) // copy the hi-intensity bit
-}
-
-impl<T: Write + Send + 'static> WinConsole<T> {
- fn apply(&mut self) {
- let _unused = self.buf.flush();
- let mut accum: WORD = 0;
- accum |= color_to_bits(self.foreground);
- accum |= color_to_bits(self.background) << 4;
-
- unsafe {
- // Magic -11 means stdout, from
- // https://docs.microsoft.com/en-us/windows/console/getstdhandle
- //
- // You may be wondering, "but what about stderr?", and the answer
- // to that is that setting terminal attributes on the stdout
- // handle also sets them for stderr, since they go to the same
- // terminal! Admittedly, this is fragile, since stderr could be
- // redirected to a different console. This is good enough for
- // rustc though. See #13400.
- let out = GetStdHandle(-11i32 as DWORD);
- SetConsoleTextAttribute(out, accum);
- }
- }
-
- /// Returns `None` whenever the terminal cannot be created for some reason.
- pub fn new(out: T) -> io::Result<WinConsole<T>> {
- use std::mem::MaybeUninit;
-
- let fg;
- let bg;
- unsafe {
- let mut buffer_info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
- if GetConsoleScreenBufferInfo(GetStdHandle(-11i32 as DWORD), buffer_info.as_mut_ptr())
- != 0
- {
- let buffer_info = buffer_info.assume_init();
- fg = bits_to_color(buffer_info.wAttributes);
- bg = bits_to_color(buffer_info.wAttributes >> 4);
- } else {
- fg = color::WHITE;
- bg = color::BLACK;
- }
- }
- Ok(WinConsole {
- buf: out,
- def_foreground: fg,
- def_background: bg,
- foreground: fg,
- background: bg,
- })
- }
-}
-
-impl<T: Write> Write for WinConsole<T> {
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- self.buf.write(buf)
- }
-
- fn flush(&mut self) -> io::Result<()> {
- self.buf.flush()
- }
-}
-
-impl<T: Write + Send + 'static> Terminal for WinConsole<T> {
- type Output = T;
-
- fn fg(&mut self, color: color::Color) -> io::Result<bool> {
- self.foreground = color;
- self.apply();
-
- Ok(true)
- }
-
- fn bg(&mut self, color: color::Color) -> io::Result<bool> {
- self.background = color;
- self.apply();
-
- Ok(true)
- }
-
- fn attr(&mut self, attr: Attr) -> io::Result<bool> {
- match attr {
- Attr::ForegroundColor(f) => {
- self.foreground = f;
- self.apply();
- Ok(true)
- }
- Attr::BackgroundColor(b) => {
- self.background = b;
- self.apply();
- Ok(true)
- }
- _ => Ok(false),
- }
- }
-
- fn supports_attr(&self, attr: Attr) -> bool {
- // it claims support for underscore and reverse video, but I can't get
- // it to do anything -cmr
- match attr {
- Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true,
- _ => false,
- }
- }
-
- fn reset(&mut self) -> io::Result<bool> {
- self.foreground = self.def_foreground;
- self.background = self.def_background;
- self.apply();
-
- Ok(true)
- }
-
- fn get_ref(&self) -> &T {
- &self.buf
- }
-
- fn get_mut(&mut self) -> &mut T {
- &mut self.buf
- }
-
- fn into_inner(self) -> T
- where
- Self: Sized,
- {
- self.buf
- }
-}
[dependencies]
cfg-if = { version = "0.1.8", features = ['rustc-dep-of-std'] }
getopts = { version = "0.2.21", features = ['rustc-dep-of-std'] }
-term = { path = "../term" }
std = { path = "../std" }
core = { path = "../core" }
libc = { version = "0.2", default-features = false }
formatters::{JsonFormatter, JunitFormatter, OutputFormatter, PrettyFormatter, TerseFormatter},
helpers::{concurrency::get_concurrency, metrics::MetricMap},
options::{Options, OutputFormat},
- run_tests,
+ run_tests, term,
test_result::TestResult,
time::{TestExecTime, TestSuiteExecTime},
types::{NamePadding, TestDesc, TestDescAndFn},
use crate::{
bench::fmt_bench_samples,
console::{ConsoleTestState, OutputLocation},
+ term,
test_result::TestResult,
time,
types::TestDesc,
use crate::{
bench::fmt_bench_samples,
console::{ConsoleTestState, OutputLocation},
+ term,
test_result::TestResult,
time,
types::NamePadding,
#![crate_name = "test"]
#![unstable(feature = "test", issue = "50297")]
#![doc(test(attr(deny(warnings))))]
-#![cfg_attr(unix, feature(libc))]
+#![feature(libc)]
#![feature(rustc_private)]
#![feature(nll)]
#![feature(available_concurrency)]
mod helpers;
mod options;
pub mod stats;
+mod term;
mod test_result;
mod time;
mod types;
--- /dev/null
+//! Terminal formatting module.
+//!
+//! This module provides the `Terminal` trait, which abstracts over an [ANSI
+//! Terminal][ansi] to provide color printing, among other things. There are two
+//! implementations, the `TerminfoTerminal`, which uses control characters from
+//! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console
+//! API][win].
+//!
+//! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
+//! [win]: https://docs.microsoft.com/en-us/windows/console/character-mode-applications
+//! [ti]: https://en.wikipedia.org/wiki/Terminfo
+
+#![deny(missing_docs)]
+
+use std::io::{self, prelude::*};
+
+pub(crate) use terminfo::TerminfoTerminal;
+#[cfg(windows)]
+pub(crate) use win::WinConsole;
+
+pub(crate) mod terminfo;
+
+#[cfg(windows)]
+mod win;
+
+/// Alias for stdout terminals.
+pub(crate) type StdoutTerminal = dyn Terminal + Send;
+
+#[cfg(not(windows))]
+/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be
+/// opened.
+pub(crate) fn stdout() -> Option<Box<StdoutTerminal>> {
+ TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box<StdoutTerminal>)
+}
+
+#[cfg(windows)]
+/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be
+/// opened.
+pub(crate) fn stdout() -> Option<Box<StdoutTerminal>> {
+ TerminfoTerminal::new(io::stdout())
+ .map(|t| Box::new(t) as Box<StdoutTerminal>)
+ .or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box<StdoutTerminal>))
+}
+
+/// Terminal color definitions
+#[allow(missing_docs)]
+#[cfg_attr(not(windows), allow(dead_code))]
+pub(crate) mod color {
+ /// Number for a terminal color
+ pub(crate) type Color = u32;
+
+ pub(crate) const BLACK: Color = 0;
+ pub(crate) const RED: Color = 1;
+ pub(crate) const GREEN: Color = 2;
+ pub(crate) const YELLOW: Color = 3;
+ pub(crate) const BLUE: Color = 4;
+ pub(crate) const MAGENTA: Color = 5;
+ pub(crate) const CYAN: Color = 6;
+ pub(crate) const WHITE: Color = 7;
+}
+
+/// A terminal with similar capabilities to an ANSI Terminal
+/// (foreground/background colors etc).
+pub trait Terminal: Write {
+ /// Sets the foreground color to the given color.
+ ///
+ /// If the color is a bright color, but the terminal only supports 8 colors,
+ /// the corresponding normal color will be used instead.
+ ///
+ /// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)`
+ /// if there was an I/O error.
+ fn fg(&mut self, color: color::Color) -> io::Result<bool>;
+
+ /// Resets all terminal attributes and colors to their defaults.
+ ///
+ /// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there
+ /// was an I/O error.
+ ///
+ /// *Note: This does not flush.*
+ ///
+ /// That means the reset command may get buffered so, if you aren't planning on doing anything
+ /// else that might flush stdout's buffer (e.g., writing a line of text), you should flush after
+ /// calling reset.
+ fn reset(&mut self) -> io::Result<bool>;
+}
--- /dev/null
+//! Terminfo database interface.
+
+use std::collections::HashMap;
+use std::env;
+use std::error;
+use std::fmt;
+use std::fs::File;
+use std::io::{self, prelude::*, BufReader};
+use std::path::Path;
+
+use super::color;
+use super::Terminal;
+
+use parm::{expand, Param, Variables};
+use parser::compiled::{msys_terminfo, parse};
+use searcher::get_dbpath_for_term;
+
+/// A parsed terminfo database entry.
+#[derive(Debug)]
+pub(crate) struct TermInfo {
+ /// Names for the terminal
+ pub(crate) names: Vec<String>,
+ /// Map of capability name to boolean value
+ pub(crate) bools: HashMap<String, bool>,
+ /// Map of capability name to numeric value
+ pub(crate) numbers: HashMap<String, u32>,
+ /// Map of capability name to raw (unexpanded) string
+ pub(crate) strings: HashMap<String, Vec<u8>>,
+}
+
+/// A terminfo creation error.
+#[derive(Debug)]
+pub(crate) enum Error {
+ /// TermUnset Indicates that the environment doesn't include enough information to find
+ /// the terminfo entry.
+ TermUnset,
+ /// MalformedTerminfo indicates that parsing the terminfo entry failed.
+ MalformedTerminfo(String),
+ /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry.
+ IoError(io::Error),
+}
+
+impl error::Error for Error {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ use Error::*;
+ match self {
+ IoError(e) => Some(e),
+ _ => None,
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use Error::*;
+ match *self {
+ TermUnset => Ok(()),
+ MalformedTerminfo(ref e) => e.fmt(f),
+ IoError(ref e) => e.fmt(f),
+ }
+ }
+}
+
+impl TermInfo {
+ /// Creates a TermInfo based on current environment.
+ pub(crate) fn from_env() -> Result<TermInfo, Error> {
+ let term = match env::var("TERM") {
+ Ok(name) => TermInfo::from_name(&name),
+ Err(..) => return Err(Error::TermUnset),
+ };
+
+ if term.is_err() && env::var("MSYSCON").map_or(false, |s| "mintty.exe" == s) {
+ // msys terminal
+ Ok(msys_terminfo())
+ } else {
+ term
+ }
+ }
+
+ /// Creates a TermInfo for the named terminal.
+ pub(crate) fn from_name(name: &str) -> Result<TermInfo, Error> {
+ get_dbpath_for_term(name)
+ .ok_or_else(|| {
+ Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found"))
+ })
+ .and_then(|p| TermInfo::from_path(&(*p)))
+ }
+
+ /// Parse the given TermInfo.
+ pub(crate) fn from_path<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
+ Self::_from_path(path.as_ref())
+ }
+ // Keep the metadata small
+ fn _from_path(path: &Path) -> Result<TermInfo, Error> {
+ let file = File::open(path).map_err(Error::IoError)?;
+ let mut reader = BufReader::new(file);
+ parse(&mut reader, false).map_err(Error::MalformedTerminfo)
+ }
+}
+
+pub(crate) mod searcher;
+
+/// TermInfo format parsing.
+pub(crate) mod parser {
+ //! ncurses-compatible compiled terminfo format parsing (term(5))
+ pub(crate) mod compiled;
+}
+pub(crate) mod parm;
+
+/// A Terminal that knows how many colors it supports, with a reference to its
+/// parsed Terminfo database record.
+pub(crate) struct TerminfoTerminal<T> {
+ num_colors: u32,
+ out: T,
+ ti: TermInfo,
+}
+
+impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
+ fn fg(&mut self, color: color::Color) -> io::Result<bool> {
+ let color = self.dim_if_necessary(color);
+ if self.num_colors > color {
+ return self.apply_cap("setaf", &[Param::Number(color as i32)]);
+ }
+ Ok(false)
+ }
+
+ fn reset(&mut self) -> io::Result<bool> {
+ // are there any terminals that have color/attrs and not sgr0?
+ // Try falling back to sgr, then op
+ let cmd = match ["sgr0", "sgr", "op"].iter().find_map(|cap| self.ti.strings.get(*cap)) {
+ Some(op) => match expand(&op, &[], &mut Variables::new()) {
+ Ok(cmd) => cmd,
+ Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
+ },
+ None => return Ok(false),
+ };
+ self.out.write_all(&cmd).and(Ok(true))
+ }
+}
+
+impl<T: Write + Send> TerminfoTerminal<T> {
+ /// Creates a new TerminfoTerminal with the given TermInfo and Write.
+ pub(crate) fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
+ let nc = if terminfo.strings.contains_key("setaf") && terminfo.strings.contains_key("setab")
+ {
+ terminfo.numbers.get("colors").map_or(0, |&n| n)
+ } else {
+ 0
+ };
+
+ TerminfoTerminal { out, ti: terminfo, num_colors: nc }
+ }
+
+ /// Creates a new TerminfoTerminal for the current environment with the given Write.
+ ///
+ /// Returns `None` when the terminfo cannot be found or parsed.
+ pub(crate) fn new(out: T) -> Option<TerminfoTerminal<T>> {
+ TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok()
+ }
+
+ fn dim_if_necessary(&self, color: color::Color) -> color::Color {
+ if color >= self.num_colors && color >= 8 && color < 16 { color - 8 } else { color }
+ }
+
+ fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result<bool> {
+ match self.ti.strings.get(cmd) {
+ Some(cmd) => match expand(&cmd, params, &mut Variables::new()) {
+ Ok(s) => self.out.write_all(&s).and(Ok(true)),
+ Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
+ },
+ None => Ok(false),
+ }
+ }
+}
+
+impl<T: Write> Write for TerminfoTerminal<T> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.out.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.out.flush()
+ }
+}
--- /dev/null
+//! Parameterized string expansion
+
+use self::Param::*;
+use self::States::*;
+
+use std::iter::repeat;
+
+#[cfg(test)]
+mod tests;
+
+#[derive(Clone, Copy, PartialEq)]
+enum States {
+ Nothing,
+ Percent,
+ SetVar,
+ GetVar,
+ PushParam,
+ CharConstant,
+ CharClose,
+ IntConstant(i32),
+ FormatPattern(Flags, FormatState),
+ SeekIfElse(usize),
+ SeekIfElsePercent(usize),
+ SeekIfEnd(usize),
+ SeekIfEndPercent(usize),
+}
+
+#[derive(Copy, PartialEq, Clone)]
+enum FormatState {
+ Flags,
+ Width,
+ Precision,
+}
+
+/// Types of parameters a capability can use
+#[allow(missing_docs)]
+#[derive(Clone)]
+pub(crate) enum Param {
+ Number(i32),
+}
+
+/// Container for static and dynamic variable arrays
+pub(crate) struct Variables {
+ /// Static variables A-Z
+ sta_va: [Param; 26],
+ /// Dynamic variables a-z
+ dyn_va: [Param; 26],
+}
+
+impl Variables {
+ /// Returns a new zero-initialized Variables
+ pub(crate) fn new() -> Variables {
+ Variables {
+ sta_va: [
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ ],
+ dyn_va: [
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ ],
+ }
+ }
+}
+
+/// Expand a parameterized capability
+///
+/// # Arguments
+/// * `cap` - string to expand
+/// * `params` - vector of params for %p1 etc
+/// * `vars` - Variables struct for %Pa etc
+///
+/// To be compatible with ncurses, `vars` should be the same between calls to `expand` for
+/// multiple capabilities for the same terminal.
+pub(crate) fn expand(
+ cap: &[u8],
+ params: &[Param],
+ vars: &mut Variables,
+) -> Result<Vec<u8>, String> {
+ let mut state = Nothing;
+
+ // expanded cap will only rarely be larger than the cap itself
+ let mut output = Vec::with_capacity(cap.len());
+
+ let mut stack: Vec<Param> = Vec::new();
+
+ // Copy parameters into a local vector for mutability
+ let mut mparams = [
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ Number(0),
+ ];
+ for (dst, src) in mparams.iter_mut().zip(params.iter()) {
+ *dst = (*src).clone();
+ }
+
+ for &c in cap.iter() {
+ let cur = c as char;
+ let mut old_state = state;
+ match state {
+ Nothing => {
+ if cur == '%' {
+ state = Percent;
+ } else {
+ output.push(c);
+ }
+ }
+ Percent => {
+ match cur {
+ '%' => {
+ output.push(c);
+ state = Nothing
+ }
+ 'c' => {
+ match stack.pop() {
+ // if c is 0, use 0200 (128) for ncurses compatibility
+ Some(Number(0)) => output.push(128u8),
+ // Don't check bounds. ncurses just casts and truncates.
+ Some(Number(c)) => output.push(c as u8),
+ None => return Err("stack is empty".to_string()),
+ }
+ }
+ 'p' => state = PushParam,
+ 'P' => state = SetVar,
+ 'g' => state = GetVar,
+ '\'' => state = CharConstant,
+ '{' => state = IntConstant(0),
+ 'l' => match stack.pop() {
+ Some(_) => return Err("a non-str was used with %l".to_string()),
+ None => return Err("stack is empty".to_string()),
+ },
+ '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => {
+ match (stack.pop(), stack.pop()) {
+ (Some(Number(y)), Some(Number(x))) => stack.push(Number(match cur {
+ '+' => x + y,
+ '-' => x - y,
+ '*' => x * y,
+ '/' => x / y,
+ '|' => x | y,
+ '&' => x & y,
+ '^' => x ^ y,
+ 'm' => x % y,
+ _ => unreachable!("All cases handled"),
+ })),
+ _ => return Err("stack is empty".to_string()),
+ }
+ }
+ '=' | '>' | '<' | 'A' | 'O' => match (stack.pop(), stack.pop()) {
+ (Some(Number(y)), Some(Number(x))) => stack.push(Number(
+ if match cur {
+ '=' => x == y,
+ '<' => x < y,
+ '>' => x > y,
+ 'A' => x > 0 && y > 0,
+ 'O' => x > 0 || y > 0,
+ _ => unreachable!(),
+ } {
+ 1
+ } else {
+ 0
+ },
+ )),
+ _ => return Err("stack is empty".to_string()),
+ },
+ '!' | '~' => match stack.pop() {
+ Some(Number(x)) => stack.push(Number(match cur {
+ '!' if x > 0 => 0,
+ '!' => 1,
+ '~' => !x,
+ _ => unreachable!(),
+ })),
+ None => return Err("stack is empty".to_string()),
+ },
+ 'i' => match (&mparams[0], &mparams[1]) {
+ (&Number(x), &Number(y)) => {
+ mparams[0] = Number(x + 1);
+ mparams[1] = Number(y + 1);
+ }
+ },
+
+ // printf-style support for %doxXs
+ 'd' | 'o' | 'x' | 'X' | 's' => {
+ if let Some(arg) = stack.pop() {
+ let flags = Flags::new();
+ let res = format(arg, FormatOp::from_char(cur), flags)?;
+ output.extend(res.iter().cloned());
+ } else {
+ return Err("stack is empty".to_string());
+ }
+ }
+ ':' | '#' | ' ' | '.' | '0'..='9' => {
+ let mut flags = Flags::new();
+ let mut fstate = FormatState::Flags;
+ match cur {
+ ':' => (),
+ '#' => flags.alternate = true,
+ ' ' => flags.space = true,
+ '.' => fstate = FormatState::Precision,
+ '0'..='9' => {
+ flags.width = cur as usize - '0' as usize;
+ fstate = FormatState::Width;
+ }
+ _ => unreachable!(),
+ }
+ state = FormatPattern(flags, fstate);
+ }
+
+ // conditionals
+ '?' => (),
+ 't' => match stack.pop() {
+ Some(Number(0)) => state = SeekIfElse(0),
+ Some(Number(_)) => (),
+ None => return Err("stack is empty".to_string()),
+ },
+ 'e' => state = SeekIfEnd(0),
+ ';' => (),
+ _ => return Err(format!("unrecognized format option {}", cur)),
+ }
+ }
+ PushParam => {
+ // params are 1-indexed
+ stack.push(
+ mparams[match cur.to_digit(10) {
+ Some(d) => d as usize - 1,
+ None => return Err("bad param number".to_string()),
+ }]
+ .clone(),
+ );
+ }
+ SetVar => {
+ if cur >= 'A' && cur <= 'Z' {
+ if let Some(arg) = stack.pop() {
+ let idx = (cur as u8) - b'A';
+ vars.sta_va[idx as usize] = arg;
+ } else {
+ return Err("stack is empty".to_string());
+ }
+ } else if cur >= 'a' && cur <= 'z' {
+ if let Some(arg) = stack.pop() {
+ let idx = (cur as u8) - b'a';
+ vars.dyn_va[idx as usize] = arg;
+ } else {
+ return Err("stack is empty".to_string());
+ }
+ } else {
+ return Err("bad variable name in %P".to_string());
+ }
+ }
+ GetVar => {
+ if cur >= 'A' && cur <= 'Z' {
+ let idx = (cur as u8) - b'A';
+ stack.push(vars.sta_va[idx as usize].clone());
+ } else if cur >= 'a' && cur <= 'z' {
+ let idx = (cur as u8) - b'a';
+ stack.push(vars.dyn_va[idx as usize].clone());
+ } else {
+ return Err("bad variable name in %g".to_string());
+ }
+ }
+ CharConstant => {
+ stack.push(Number(c as i32));
+ state = CharClose;
+ }
+ CharClose => {
+ if cur != '\'' {
+ return Err("malformed character constant".to_string());
+ }
+ }
+ IntConstant(i) => {
+ if cur == '}' {
+ stack.push(Number(i));
+ state = Nothing;
+ } else if let Some(digit) = cur.to_digit(10) {
+ match i.checked_mul(10).and_then(|i_ten| i_ten.checked_add(digit as i32)) {
+ Some(i) => {
+ state = IntConstant(i);
+ old_state = Nothing;
+ }
+ None => return Err("int constant too large".to_string()),
+ }
+ } else {
+ return Err("bad int constant".to_string());
+ }
+ }
+ FormatPattern(ref mut flags, ref mut fstate) => {
+ old_state = Nothing;
+ match (*fstate, cur) {
+ (_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => {
+ if let Some(arg) = stack.pop() {
+ let res = format(arg, FormatOp::from_char(cur), *flags)?;
+ output.extend(res.iter().cloned());
+ // will cause state to go to Nothing
+ old_state = FormatPattern(*flags, *fstate);
+ } else {
+ return Err("stack is empty".to_string());
+ }
+ }
+ (FormatState::Flags, '#') => {
+ flags.alternate = true;
+ }
+ (FormatState::Flags, '-') => {
+ flags.left = true;
+ }
+ (FormatState::Flags, '+') => {
+ flags.sign = true;
+ }
+ (FormatState::Flags, ' ') => {
+ flags.space = true;
+ }
+ (FormatState::Flags, '0'..='9') => {
+ flags.width = cur as usize - '0' as usize;
+ *fstate = FormatState::Width;
+ }
+ (FormatState::Flags, '.') => {
+ *fstate = FormatState::Precision;
+ }
+ (FormatState::Width, '0'..='9') => {
+ let old = flags.width;
+ flags.width = flags.width * 10 + (cur as usize - '0' as usize);
+ if flags.width < old {
+ return Err("format width overflow".to_string());
+ }
+ }
+ (FormatState::Width, '.') => {
+ *fstate = FormatState::Precision;
+ }
+ (FormatState::Precision, '0'..='9') => {
+ let old = flags.precision;
+ flags.precision = flags.precision * 10 + (cur as usize - '0' as usize);
+ if flags.precision < old {
+ return Err("format precision overflow".to_string());
+ }
+ }
+ _ => return Err("invalid format specifier".to_string()),
+ }
+ }
+ SeekIfElse(level) => {
+ if cur == '%' {
+ state = SeekIfElsePercent(level);
+ }
+ old_state = Nothing;
+ }
+ SeekIfElsePercent(level) => {
+ if cur == ';' {
+ if level == 0 {
+ state = Nothing;
+ } else {
+ state = SeekIfElse(level - 1);
+ }
+ } else if cur == 'e' && level == 0 {
+ state = Nothing;
+ } else if cur == '?' {
+ state = SeekIfElse(level + 1);
+ } else {
+ state = SeekIfElse(level);
+ }
+ }
+ SeekIfEnd(level) => {
+ if cur == '%' {
+ state = SeekIfEndPercent(level);
+ }
+ old_state = Nothing;
+ }
+ SeekIfEndPercent(level) => {
+ if cur == ';' {
+ if level == 0 {
+ state = Nothing;
+ } else {
+ state = SeekIfEnd(level - 1);
+ }
+ } else if cur == '?' {
+ state = SeekIfEnd(level + 1);
+ } else {
+ state = SeekIfEnd(level);
+ }
+ }
+ }
+ if state == old_state {
+ state = Nothing;
+ }
+ }
+ Ok(output)
+}
+
+#[derive(Copy, PartialEq, Clone)]
+struct Flags {
+ width: usize,
+ precision: usize,
+ alternate: bool,
+ left: bool,
+ sign: bool,
+ space: bool,
+}
+
+impl Flags {
+ fn new() -> Flags {
+ Flags { width: 0, precision: 0, alternate: false, left: false, sign: false, space: false }
+ }
+}
+
+#[derive(Copy, Clone)]
+enum FormatOp {
+ Digit,
+ Octal,
+ LowerHex,
+ UpperHex,
+ String,
+}
+
+impl FormatOp {
+ fn from_char(c: char) -> FormatOp {
+ match c {
+ 'd' => FormatOp::Digit,
+ 'o' => FormatOp::Octal,
+ 'x' => FormatOp::LowerHex,
+ 'X' => FormatOp::UpperHex,
+ 's' => FormatOp::String,
+ _ => panic!("bad FormatOp char"),
+ }
+ }
+}
+
+fn format(val: Param, op: FormatOp, flags: Flags) -> Result<Vec<u8>, String> {
+ let mut s = match val {
+ Number(d) => {
+ match op {
+ FormatOp::Digit => {
+ if flags.sign {
+ format!("{:+01$}", d, flags.precision)
+ } else if d < 0 {
+ // C doesn't take sign into account in precision calculation.
+ format!("{:01$}", d, flags.precision + 1)
+ } else if flags.space {
+ format!(" {:01$}", d, flags.precision)
+ } else {
+ format!("{:01$}", d, flags.precision)
+ }
+ }
+ FormatOp::Octal => {
+ if flags.alternate {
+ // Leading octal zero counts against precision.
+ format!("0{:01$o}", d, flags.precision.saturating_sub(1))
+ } else {
+ format!("{:01$o}", d, flags.precision)
+ }
+ }
+ FormatOp::LowerHex => {
+ if flags.alternate && d != 0 {
+ format!("0x{:01$x}", d, flags.precision)
+ } else {
+ format!("{:01$x}", d, flags.precision)
+ }
+ }
+ FormatOp::UpperHex => {
+ if flags.alternate && d != 0 {
+ format!("0X{:01$X}", d, flags.precision)
+ } else {
+ format!("{:01$X}", d, flags.precision)
+ }
+ }
+ FormatOp::String => return Err("non-number on stack with %s".to_string()),
+ }
+ .into_bytes()
+ }
+ };
+ if flags.width > s.len() {
+ let n = flags.width - s.len();
+ if flags.left {
+ s.extend(repeat(b' ').take(n));
+ } else {
+ let mut s_ = Vec::with_capacity(flags.width);
+ s_.extend(repeat(b' ').take(n));
+ s_.extend(s.into_iter());
+ s = s_;
+ }
+ }
+ Ok(s)
+}
--- /dev/null
+use super::*;
+
+use std::result::Result::Ok;
+
+#[test]
+fn test_basic_setabf() {
+ let s = b"\\E[48;5;%p1%dm";
+ assert_eq!(
+ expand(s, &[Number(1)], &mut Variables::new()).unwrap(),
+ "\\E[48;5;1m".bytes().collect::<Vec<_>>()
+ );
+}
+
+#[test]
+fn test_multiple_int_constants() {
+ assert_eq!(
+ expand(b"%{1}%{2}%d%d", &[], &mut Variables::new()).unwrap(),
+ "21".bytes().collect::<Vec<_>>()
+ );
+}
+
+#[test]
+fn test_op_i() {
+ let mut vars = Variables::new();
+ assert_eq!(
+ expand(b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d", &[Number(1), Number(2), Number(3)], &mut vars),
+ Ok("123233".bytes().collect::<Vec<_>>())
+ );
+ assert_eq!(
+ expand(b"%p1%d%p2%d%i%p1%d%p2%d", &[], &mut vars),
+ Ok("0011".bytes().collect::<Vec<_>>())
+ );
+}
+
+#[test]
+fn test_param_stack_failure_conditions() {
+ let mut varstruct = Variables::new();
+ let vars = &mut varstruct;
+ fn get_res(
+ fmt: &str,
+ cap: &str,
+ params: &[Param],
+ vars: &mut Variables,
+ ) -> Result<Vec<u8>, String> {
+ let mut u8v: Vec<_> = fmt.bytes().collect();
+ u8v.extend(cap.as_bytes().iter().map(|&b| b));
+ expand(&u8v, params, vars)
+ }
+
+ let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"];
+ for &cap in caps.iter() {
+ let res = get_res("", cap, &[], vars);
+ assert!(res.is_err(), "Op {} succeeded incorrectly with 0 stack entries", cap);
+ if cap == "%s" || cap == "%l" {
+ continue;
+ }
+ let p = Number(97);
+ let res = get_res("%p1", cap, &[p], vars);
+ assert!(res.is_ok(), "Op {} failed with 1 stack entry: {}", cap, res.unwrap_err());
+ }
+ let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"];
+ for &cap in caps.iter() {
+ let res = expand(cap.as_bytes(), &[], vars);
+ assert!(res.is_err(), "Binop {} succeeded incorrectly with 0 stack entries", cap);
+ let res = get_res("%{1}", cap, &[], vars);
+ assert!(res.is_err(), "Binop {} succeeded incorrectly with 1 stack entry", cap);
+ let res = get_res("%{1}%{2}", cap, &[], vars);
+ assert!(res.is_ok(), "Binop {} failed with 2 stack entries: {}", cap, res.unwrap_err());
+ }
+}
+
+#[test]
+fn test_push_bad_param() {
+ assert!(expand(b"%pa", &[], &mut Variables::new()).is_err());
+}
+
+#[test]
+fn test_comparison_ops() {
+ let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])];
+ for &(op, bs) in v.iter() {
+ let s = format!("%{{1}}%{{2}}%{}%d", op);
+ let res = expand(s.as_bytes(), &[], &mut Variables::new());
+ assert!(res.is_ok(), "{}", res.unwrap_err());
+ assert_eq!(res.unwrap(), vec![b'0' + bs[0]]);
+ let s = format!("%{{1}}%{{1}}%{}%d", op);
+ let res = expand(s.as_bytes(), &[], &mut Variables::new());
+ assert!(res.is_ok(), "{}", res.unwrap_err());
+ assert_eq!(res.unwrap(), vec![b'0' + bs[1]]);
+ let s = format!("%{{2}}%{{1}}%{}%d", op);
+ let res = expand(s.as_bytes(), &[], &mut Variables::new());
+ assert!(res.is_ok(), "{}", res.unwrap_err());
+ assert_eq!(res.unwrap(), vec![b'0' + bs[2]]);
+ }
+}
+
+#[test]
+fn test_conditionals() {
+ let mut vars = Variables::new();
+ let s = b"\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m";
+ let res = expand(s, &[Number(1)], &mut vars);
+ assert!(res.is_ok(), "{}", res.unwrap_err());
+ assert_eq!(res.unwrap(), "\\E[31m".bytes().collect::<Vec<_>>());
+ let res = expand(s, &[Number(8)], &mut vars);
+ assert!(res.is_ok(), "{}", res.unwrap_err());
+ assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::<Vec<_>>());
+ let res = expand(s, &[Number(42)], &mut vars);
+ assert!(res.is_ok(), "{}", res.unwrap_err());
+ assert_eq!(res.unwrap(), "\\E[38;5;42m".bytes().collect::<Vec<_>>());
+}
+
+#[test]
+fn test_format() {
+ let mut varstruct = Variables::new();
+ let vars = &mut varstruct;
+
+ assert_eq!(
+ expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars),
+ Ok("1001 1+1".bytes().collect::<Vec<_>>())
+ );
+ assert_eq!(
+ expand(b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X", &[Number(15), Number(27)], vars),
+ Ok("17017 001b0X001B".bytes().collect::<Vec<_>>())
+ );
+}
--- /dev/null
+#![allow(non_upper_case_globals, missing_docs)]
+
+//! ncurses-compatible compiled terminfo format parsing (term(5))
+
+use super::super::TermInfo;
+use std::collections::HashMap;
+use std::io;
+use std::io::prelude::*;
+
+#[cfg(test)]
+mod tests;
+
+// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable.
+
+#[rustfmt::skip]
+pub(crate) static boolfnames: &[&str] = &["auto_left_margin", "auto_right_margin",
+ "no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type",
+ "hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above",
+ "memory_below", "move_insert_mode", "move_standout_mode", "over_strike", "status_line_esc_ok",
+ "dest_tabs_magic_smso", "tilde_glitch", "transparent_underline", "xon_xoff", "needs_xon_xoff",
+ "prtr_silent", "hard_cursor", "non_rev_rmcup", "no_pad_char", "non_dest_scroll_region",
+ "can_change", "back_color_erase", "hue_lightness_saturation", "col_addr_glitch",
+ "cr_cancels_micro_mode", "has_print_wheel", "row_addr_glitch", "semi_auto_right_margin",
+ "cpi_changes_res", "lpi_changes_res", "backspaces_with_bs", "crt_no_scrolling",
+ "no_correctly_working_cr", "gnu_has_meta_key", "linefeed_is_newline", "has_hardware_tabs",
+ "return_does_clr_eol"];
+
+#[rustfmt::skip]
+pub(crate) static boolnames: &[&str] = &["bw", "am", "xsb", "xhp", "xenl", "eo",
+ "gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon",
+ "nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy",
+ "xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"];
+
+#[rustfmt::skip]
+pub(crate) static numfnames: &[&str] = &[ "columns", "init_tabs", "lines",
+ "lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal",
+ "width_status_line", "num_labels", "label_height", "label_width", "max_attributes",
+ "maximum_windows", "max_colors", "max_pairs", "no_color_video", "buffer_capacity",
+ "dot_vert_spacing", "dot_horz_spacing", "max_micro_address", "max_micro_jump", "micro_col_size",
+ "micro_line_size", "number_of_pins", "output_res_char", "output_res_line",
+ "output_res_horz_inch", "output_res_vert_inch", "print_rate", "wide_char_size", "buttons",
+ "bit_image_entwining", "bit_image_type", "magic_cookie_glitch_ul", "carriage_return_delay",
+ "new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"];
+
+#[rustfmt::skip]
+pub(crate) static numnames: &[&str] = &[ "cols", "it", "lines", "lm", "xmc", "pb",
+ "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv",
+ "spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs",
+ "btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"];
+
+#[rustfmt::skip]
+pub(crate) static stringfnames: &[&str] = &[ "back_tab", "bell", "carriage_return",
+ "change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos",
+ "column_address", "command_character", "cursor_address", "cursor_down", "cursor_home",
+ "cursor_invisible", "cursor_left", "cursor_mem_address", "cursor_normal", "cursor_right",
+ "cursor_to_ll", "cursor_up", "cursor_visible", "delete_character", "delete_line",
+ "dis_status_line", "down_half_line", "enter_alt_charset_mode", "enter_blink_mode",
+ "enter_bold_mode", "enter_ca_mode", "enter_delete_mode", "enter_dim_mode", "enter_insert_mode",
+ "enter_secure_mode", "enter_protected_mode", "enter_reverse_mode", "enter_standout_mode",
+ "enter_underline_mode", "erase_chars", "exit_alt_charset_mode", "exit_attribute_mode",
+ "exit_ca_mode", "exit_delete_mode", "exit_insert_mode", "exit_standout_mode",
+ "exit_underline_mode", "flash_screen", "form_feed", "from_status_line", "init_1string",
+ "init_2string", "init_3string", "init_file", "insert_character", "insert_line",
+ "insert_padding", "key_backspace", "key_catab", "key_clear", "key_ctab", "key_dc", "key_dl",
+ "key_down", "key_eic", "key_eol", "key_eos", "key_f0", "key_f1", "key_f10", "key_f2", "key_f3",
+ "key_f4", "key_f5", "key_f6", "key_f7", "key_f8", "key_f9", "key_home", "key_ic", "key_il",
+ "key_left", "key_ll", "key_npage", "key_ppage", "key_right", "key_sf", "key_sr", "key_stab",
+ "key_up", "keypad_local", "keypad_xmit", "lab_f0", "lab_f1", "lab_f10", "lab_f2", "lab_f3",
+ "lab_f4", "lab_f5", "lab_f6", "lab_f7", "lab_f8", "lab_f9", "meta_off", "meta_on", "newline",
+ "pad_char", "parm_dch", "parm_delete_line", "parm_down_cursor", "parm_ich", "parm_index",
+ "parm_insert_line", "parm_left_cursor", "parm_right_cursor", "parm_rindex", "parm_up_cursor",
+ "pkey_key", "pkey_local", "pkey_xmit", "print_screen", "prtr_off", "prtr_on", "repeat_char",
+ "reset_1string", "reset_2string", "reset_3string", "reset_file", "restore_cursor",
+ "row_address", "save_cursor", "scroll_forward", "scroll_reverse", "set_attributes", "set_tab",
+ "set_window", "tab", "to_status_line", "underline_char", "up_half_line", "init_prog", "key_a1",
+ "key_a3", "key_b2", "key_c1", "key_c3", "prtr_non", "char_padding", "acs_chars", "plab_norm",
+ "key_btab", "enter_xon_mode", "exit_xon_mode", "enter_am_mode", "exit_am_mode", "xon_character",
+ "xoff_character", "ena_acs", "label_on", "label_off", "key_beg", "key_cancel", "key_close",
+ "key_command", "key_copy", "key_create", "key_end", "key_enter", "key_exit", "key_find",
+ "key_help", "key_mark", "key_message", "key_move", "key_next", "key_open", "key_options",
+ "key_previous", "key_print", "key_redo", "key_reference", "key_refresh", "key_replace",
+ "key_restart", "key_resume", "key_save", "key_suspend", "key_undo", "key_sbeg", "key_scancel",
+ "key_scommand", "key_scopy", "key_screate", "key_sdc", "key_sdl", "key_select", "key_send",
+ "key_seol", "key_sexit", "key_sfind", "key_shelp", "key_shome", "key_sic", "key_sleft",
+ "key_smessage", "key_smove", "key_snext", "key_soptions", "key_sprevious", "key_sprint",
+ "key_sredo", "key_sreplace", "key_sright", "key_srsume", "key_ssave", "key_ssuspend",
+ "key_sundo", "req_for_input", "key_f11", "key_f12", "key_f13", "key_f14", "key_f15", "key_f16",
+ "key_f17", "key_f18", "key_f19", "key_f20", "key_f21", "key_f22", "key_f23", "key_f24",
+ "key_f25", "key_f26", "key_f27", "key_f28", "key_f29", "key_f30", "key_f31", "key_f32",
+ "key_f33", "key_f34", "key_f35", "key_f36", "key_f37", "key_f38", "key_f39", "key_f40",
+ "key_f41", "key_f42", "key_f43", "key_f44", "key_f45", "key_f46", "key_f47", "key_f48",
+ "key_f49", "key_f50", "key_f51", "key_f52", "key_f53", "key_f54", "key_f55", "key_f56",
+ "key_f57", "key_f58", "key_f59", "key_f60", "key_f61", "key_f62", "key_f63", "clr_bol",
+ "clear_margins", "set_left_margin", "set_right_margin", "label_format", "set_clock",
+ "display_clock", "remove_clock", "create_window", "goto_window", "hangup", "dial_phone",
+ "quick_dial", "tone", "pulse", "flash_hook", "fixed_pause", "wait_tone", "user0", "user1",
+ "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "orig_pair",
+ "orig_colors", "initialize_color", "initialize_pair", "set_color_pair", "set_foreground",
+ "set_background", "change_char_pitch", "change_line_pitch", "change_res_horz",
+ "change_res_vert", "define_char", "enter_doublewide_mode", "enter_draft_quality",
+ "enter_italics_mode", "enter_leftward_mode", "enter_micro_mode", "enter_near_letter_quality",
+ "enter_normal_quality", "enter_shadow_mode", "enter_subscript_mode", "enter_superscript_mode",
+ "enter_upward_mode", "exit_doublewide_mode", "exit_italics_mode", "exit_leftward_mode",
+ "exit_micro_mode", "exit_shadow_mode", "exit_subscript_mode", "exit_superscript_mode",
+ "exit_upward_mode", "micro_column_address", "micro_down", "micro_left", "micro_right",
+ "micro_row_address", "micro_up", "order_of_pins", "parm_down_micro", "parm_left_micro",
+ "parm_right_micro", "parm_up_micro", "select_char_set", "set_bottom_margin",
+ "set_bottom_margin_parm", "set_left_margin_parm", "set_right_margin_parm", "set_top_margin",
+ "set_top_margin_parm", "start_bit_image", "start_char_set_def", "stop_bit_image",
+ "stop_char_set_def", "subscript_characters", "superscript_characters", "these_cause_cr",
+ "zero_motion", "char_set_names", "key_mouse", "mouse_info", "req_mouse_pos", "get_mouse",
+ "set_a_foreground", "set_a_background", "pkey_plab", "device_type", "code_set_init",
+ "set0_des_seq", "set1_des_seq", "set2_des_seq", "set3_des_seq", "set_lr_margin",
+ "set_tb_margin", "bit_image_repeat", "bit_image_newline", "bit_image_carriage_return",
+ "color_names", "define_bit_image_region", "end_bit_image_region", "set_color_band",
+ "set_page_length", "display_pc_char", "enter_pc_charset_mode", "exit_pc_charset_mode",
+ "enter_scancode_mode", "exit_scancode_mode", "pc_term_options", "scancode_escape",
+ "alt_scancode_esc", "enter_horizontal_hl_mode", "enter_left_hl_mode", "enter_low_hl_mode",
+ "enter_right_hl_mode", "enter_top_hl_mode", "enter_vertical_hl_mode", "set_a_attributes",
+ "set_pglen_inch", "termcap_init2", "termcap_reset", "linefeed_if_not_lf", "backspace_if_not_bs",
+ "other_non_function_keys", "arrow_key_map", "acs_ulcorner", "acs_llcorner", "acs_urcorner",
+ "acs_lrcorner", "acs_ltee", "acs_rtee", "acs_btee", "acs_ttee", "acs_hline", "acs_vline",
+ "acs_plus", "memory_lock", "memory_unlock", "box_chars_1"];
+
+#[rustfmt::skip]
+pub(crate) static stringnames: &[&str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear",
+ "_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1",
+ "ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc",
+ "dim", "smir", "invis", "prot", "rev", "smso", "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc",
+ "rmir", "rmso", "rmul", "flash", "ff", "fsl", "is1", "is2", "is3", "if", "ich1", "il1", "ip",
+ "kbs", "ktbc", "kclr", "kctab", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+ "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_",
+ "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_",
+ "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey",
+ "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind",
+ "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p",
+ "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln",
+ "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "kent", "kext", "kfnd", "khlp",
+ "kmrk", "kmsg", "kmov", "knxt", "kopn", "kopt", "kprv", "kprt", "krdo", "kref", "krfr", "krpl",
+ "krst", "kres", "ksav", "kspd", "kund", "kBEG", "kCAN", "kCMD", "kCPY", "kCRT", "_", "_",
+ "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT",
+ "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_",
+ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+ "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_",
+ "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_",
+ "_", "_", "_", "_", "_", "_", "_", "_", "_", "op", "oc", "initc", "initp", "scp", "setf",
+ "setb", "cpi", "lpi", "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", "smicm", "snlq",
+ "snrmq", "sshm", "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", "rshm", "rsubm",
+ "rsupm", "rum", "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", "porder", "mcud", "mcub",
+ "mcuf", "mcuu", "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", "smgtp", "sbim", "scsd",
+ "rbim", "rcsd", "subcs", "supcs", "docr", "zerom", "csnm", "kmous", "minfo", "reqmp", "getm",
+ "setaf", "setab", "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", "s3ds", "smglr", "smgtb",
+ "birep", "binel", "bicr", "colornm", "defbi", "endbi", "setcolor", "slines", "dispc", "smpch",
+ "rmpch", "smsc", "rmsc", "pctrm", "scesc", "scesa", "ehhlm", "elhlm", "elohlm", "erhlm",
+ "ethlm", "evhlm", "sgr1", "slength", "OTi2", "OTrs", "OTnl", "OTbs", "OTko", "OTma", "OTG2",
+ "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu",
+ "box1"];
+
+fn read_le_u16(r: &mut dyn io::Read) -> io::Result<u16> {
+ let mut b = [0; 2];
+ r.read_exact(&mut b)?;
+ Ok((b[0] as u16) | ((b[1] as u16) << 8))
+}
+
+fn read_le_u32(r: &mut dyn io::Read) -> io::Result<u32> {
+ let mut b = [0; 4];
+ r.read_exact(&mut b)?;
+ Ok((b[0] as u32) | ((b[1] as u32) << 8) | ((b[2] as u32) << 16) | ((b[3] as u32) << 24))
+}
+
+fn read_byte(r: &mut dyn io::Read) -> io::Result<u8> {
+ match r.bytes().next() {
+ Some(s) => s,
+ None => Err(io::Error::new(io::ErrorKind::Other, "end of file")),
+ }
+}
+
+/// Parse a compiled terminfo entry, using long capability names if `longnames`
+/// is true
+pub(crate) fn parse(file: &mut dyn io::Read, longnames: bool) -> Result<TermInfo, String> {
+ macro_rules! t( ($e:expr) => (
+ match $e {
+ Ok(e) => e,
+ Err(e) => return Err(e.to_string())
+ }
+ ) );
+
+ let (bnames, snames, nnames) = if longnames {
+ (boolfnames, stringfnames, numfnames)
+ } else {
+ (boolnames, stringnames, numnames)
+ };
+
+ // Check magic number
+ let magic = t!(read_le_u16(file));
+
+ let extended = match magic {
+ 0o0432 => false,
+ 0o01036 => true,
+ _ => return Err(format!("invalid magic number, found {:o}", magic)),
+ };
+
+ // According to the spec, these fields must be >= -1 where -1 means that the feature is not
+ // supported. Using 0 instead of -1 works because we skip sections with length 0.
+ macro_rules! read_nonneg {
+ () => {{
+ match t!(read_le_u16(file)) as i16 {
+ n if n >= 0 => n as usize,
+ -1 => 0,
+ _ => return Err("incompatible file: length fields must be >= -1".to_string()),
+ }
+ }};
+ }
+
+ let names_bytes = read_nonneg!();
+ let bools_bytes = read_nonneg!();
+ let numbers_count = read_nonneg!();
+ let string_offsets_count = read_nonneg!();
+ let string_table_bytes = read_nonneg!();
+
+ if names_bytes == 0 {
+ return Err("incompatible file: names field must be at least 1 byte wide".to_string());
+ }
+
+ if bools_bytes > boolnames.len() {
+ return Err("incompatible file: more booleans than expected".to_string());
+ }
+
+ if numbers_count > numnames.len() {
+ return Err("incompatible file: more numbers than expected".to_string());
+ }
+
+ if string_offsets_count > stringnames.len() {
+ return Err("incompatible file: more string offsets than expected".to_string());
+ }
+
+ // don't read NUL
+ let mut bytes = Vec::new();
+ t!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes));
+ let names_str = match String::from_utf8(bytes) {
+ Ok(s) => s,
+ Err(_) => return Err("input not utf-8".to_string()),
+ };
+
+ let term_names: Vec<String> = names_str.split('|').map(|s| s.to_string()).collect();
+ // consume NUL
+ if t!(read_byte(file)) != b'\0' {
+ return Err("incompatible file: missing null terminator for names section".to_string());
+ }
+
+ let bools_map: HashMap<String, bool> = t! {
+ (0..bools_bytes).filter_map(|i| match read_byte(file) {
+ Err(e) => Some(Err(e)),
+ Ok(1) => Some(Ok((bnames[i].to_string(), true))),
+ Ok(_) => None
+ }).collect()
+ };
+
+ if (bools_bytes + names_bytes) % 2 == 1 {
+ t!(read_byte(file)); // compensate for padding
+ }
+
+ let numbers_map: HashMap<String, u32> = t! {
+ (0..numbers_count).filter_map(|i| {
+ let number = if extended { read_le_u32(file) } else { read_le_u16(file).map(Into::into) };
+
+ match number {
+ Ok(0xFFFF) => None,
+ Ok(n) => Some(Ok((nnames[i].to_string(), n))),
+ Err(e) => Some(Err(e))
+ }
+ }).collect()
+ };
+
+ let string_map: HashMap<String, Vec<u8>> = if string_offsets_count > 0 {
+ let string_offsets: Vec<u16> =
+ t!((0..string_offsets_count).map(|_| read_le_u16(file)).collect());
+
+ let mut string_table = Vec::new();
+ t!(file.take(string_table_bytes as u64).read_to_end(&mut string_table));
+
+ t!(string_offsets
+ .into_iter()
+ .enumerate()
+ .filter(|&(_, offset)| {
+ // non-entry
+ offset != 0xFFFF
+ })
+ .map(|(i, offset)| {
+ let offset = offset as usize;
+
+ let name = if snames[i] == "_" { stringfnames[i] } else { snames[i] };
+
+ if offset == 0xFFFE {
+ // undocumented: FFFE indicates cap@, which means the capability is not present
+ // unsure if the handling for this is correct
+ return Ok((name.to_string(), Vec::new()));
+ }
+
+ // Find the offset of the NUL we want to go to
+ let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0);
+ match nulpos {
+ Some(len) => {
+ Ok((name.to_string(), string_table[offset..offset + len].to_vec()))
+ }
+ None => Err("invalid file: missing NUL in string_table".to_string()),
+ }
+ })
+ .collect())
+ } else {
+ HashMap::new()
+ };
+
+ // And that's all there is to it
+ Ok(TermInfo { names: term_names, bools: bools_map, numbers: numbers_map, strings: string_map })
+}
+
+/// Creates a dummy TermInfo struct for msys terminals
+pub(crate) fn msys_terminfo() -> TermInfo {
+ let mut strings = HashMap::new();
+ strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec());
+ strings.insert("bold".to_string(), b"\x1B[1m".to_vec());
+ strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec());
+ strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec());
+
+ let mut numbers = HashMap::new();
+ numbers.insert("colors".to_string(), 8);
+
+ TermInfo {
+ names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version
+ bools: HashMap::new(),
+ numbers,
+ strings,
+ }
+}
--- /dev/null
+use super::*;
+
+#[test]
+fn test_veclens() {
+ assert_eq!(boolfnames.len(), boolnames.len());
+ assert_eq!(numfnames.len(), numnames.len());
+ assert_eq!(stringfnames.len(), stringnames.len());
+}
--- /dev/null
+//! ncurses-compatible database discovery.
+//!
+//! Does not support hashed database, only filesystem!
+
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+
+#[cfg(test)]
+mod tests;
+
+/// Return path to database entry for `term`
+#[allow(deprecated)]
+pub(crate) fn get_dbpath_for_term(term: &str) -> Option<PathBuf> {
+ let mut dirs_to_search = Vec::new();
+ let first_char = term.chars().next()?;
+
+ // Find search directory
+ if let Some(dir) = env::var_os("TERMINFO") {
+ dirs_to_search.push(PathBuf::from(dir));
+ }
+
+ if let Ok(dirs) = env::var("TERMINFO_DIRS") {
+ for i in dirs.split(':') {
+ if i == "" {
+ dirs_to_search.push(PathBuf::from("/usr/share/terminfo"));
+ } else {
+ dirs_to_search.push(PathBuf::from(i));
+ }
+ }
+ } else {
+ // Found nothing in TERMINFO_DIRS, use the default paths:
+ // According to /etc/terminfo/README, after looking at
+ // ~/.terminfo, ncurses will search /etc/terminfo, then
+ // /lib/terminfo, and eventually /usr/share/terminfo.
+ // On Haiku the database can be found at /boot/system/data/terminfo
+ if let Some(mut homedir) = env::home_dir() {
+ homedir.push(".terminfo");
+ dirs_to_search.push(homedir)
+ }
+
+ dirs_to_search.push(PathBuf::from("/etc/terminfo"));
+ dirs_to_search.push(PathBuf::from("/lib/terminfo"));
+ dirs_to_search.push(PathBuf::from("/usr/share/terminfo"));
+ dirs_to_search.push(PathBuf::from("/boot/system/data/terminfo"));
+ }
+
+ // Look for the terminal in all of the search directories
+ for mut p in dirs_to_search {
+ if fs::metadata(&p).is_ok() {
+ p.push(&first_char.to_string());
+ p.push(&term);
+ if fs::metadata(&p).is_ok() {
+ return Some(p);
+ }
+ p.pop();
+ p.pop();
+
+ // on some installations the dir is named after the hex of the char
+ // (e.g., macOS)
+ p.push(&format!("{:x}", first_char as usize));
+ p.push(term);
+ if fs::metadata(&p).is_ok() {
+ return Some(p);
+ }
+ }
+ }
+ None
+}
--- /dev/null
+use super::*;
+
+#[test]
+#[ignore = "buildbots don't have ncurses installed and I can't mock everything I need"]
+fn test_get_dbpath_for_term() {
+ // woefully inadequate test coverage
+ // note: current tests won't work with non-standard terminfo hierarchies (e.g., macOS's)
+ use std::env;
+ // FIXME (#9639): This needs to handle non-utf8 paths
+ fn x(t: &str) -> String {
+ let p = get_dbpath_for_term(t).expect("no terminfo entry found");
+ p.to_str().unwrap().to_string()
+ }
+ assert!(x("screen") == "/usr/share/terminfo/s/screen");
+ assert!(get_dbpath_for_term("") == None);
+ env::set_var("TERMINFO_DIRS", ":");
+ assert!(x("screen") == "/usr/share/terminfo/s/screen");
+ env::remove_var("TERMINFO_DIRS");
+}
--- /dev/null
+//! Windows console handling
+
+// FIXME (#13400): this is only a tiny fraction of the Windows console api
+
+use std::io;
+use std::io::prelude::*;
+
+use super::color;
+use super::Terminal;
+
+/// A Terminal implementation that uses the Win32 Console API.
+pub(crate) struct WinConsole<T> {
+ buf: T,
+ def_foreground: color::Color,
+ def_background: color::Color,
+ foreground: color::Color,
+ background: color::Color,
+}
+
+type SHORT = i16;
+type WORD = u16;
+type DWORD = u32;
+type BOOL = i32;
+type HANDLE = *mut u8;
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct SMALL_RECT {
+ Left: SHORT,
+ Top: SHORT,
+ Right: SHORT,
+ Bottom: SHORT,
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct COORD {
+ X: SHORT,
+ Y: SHORT,
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+struct CONSOLE_SCREEN_BUFFER_INFO {
+ dwSize: COORD,
+ dwCursorPosition: COORD,
+ wAttributes: WORD,
+ srWindow: SMALL_RECT,
+ dwMaximumWindowSize: COORD,
+}
+
+#[allow(non_snake_case)]
+#[link(name = "kernel32")]
+extern "system" {
+ fn SetConsoleTextAttribute(handle: HANDLE, attr: WORD) -> BOOL;
+ fn GetStdHandle(which: DWORD) -> HANDLE;
+ fn GetConsoleScreenBufferInfo(handle: HANDLE, info: *mut CONSOLE_SCREEN_BUFFER_INFO) -> BOOL;
+}
+
+fn color_to_bits(color: color::Color) -> u16 {
+ // magic numbers from mingw-w64's wincon.h
+
+ let bits = match color % 8 {
+ color::BLACK => 0,
+ color::BLUE => 0x1,
+ color::GREEN => 0x2,
+ color::RED => 0x4,
+ color::YELLOW => 0x2 | 0x4,
+ color::MAGENTA => 0x1 | 0x4,
+ color::CYAN => 0x1 | 0x2,
+ color::WHITE => 0x1 | 0x2 | 0x4,
+ _ => unreachable!(),
+ };
+
+ if color >= 8 { bits | 0x8 } else { bits }
+}
+
+fn bits_to_color(bits: u16) -> color::Color {
+ let color = match bits & 0x7 {
+ 0 => color::BLACK,
+ 0x1 => color::BLUE,
+ 0x2 => color::GREEN,
+ 0x4 => color::RED,
+ 0x6 => color::YELLOW,
+ 0x5 => color::MAGENTA,
+ 0x3 => color::CYAN,
+ 0x7 => color::WHITE,
+ _ => unreachable!(),
+ };
+
+ color | (u32::from(bits) & 0x8) // copy the hi-intensity bit
+}
+
+impl<T: Write + Send + 'static> WinConsole<T> {
+ fn apply(&mut self) {
+ let _unused = self.buf.flush();
+ let mut accum: WORD = 0;
+ accum |= color_to_bits(self.foreground);
+ accum |= color_to_bits(self.background) << 4;
+
+ unsafe {
+ // Magic -11 means stdout, from
+ // https://docs.microsoft.com/en-us/windows/console/getstdhandle
+ //
+ // You may be wondering, "but what about stderr?", and the answer
+ // to that is that setting terminal attributes on the stdout
+ // handle also sets them for stderr, since they go to the same
+ // terminal! Admittedly, this is fragile, since stderr could be
+ // redirected to a different console. This is good enough for
+ // rustc though. See #13400.
+ let out = GetStdHandle(-11i32 as DWORD);
+ SetConsoleTextAttribute(out, accum);
+ }
+ }
+
+ /// Returns `None` whenever the terminal cannot be created for some reason.
+ pub(crate) fn new(out: T) -> io::Result<WinConsole<T>> {
+ use std::mem::MaybeUninit;
+
+ let fg;
+ let bg;
+ unsafe {
+ let mut buffer_info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
+ if GetConsoleScreenBufferInfo(GetStdHandle(-11i32 as DWORD), buffer_info.as_mut_ptr())
+ != 0
+ {
+ let buffer_info = buffer_info.assume_init();
+ fg = bits_to_color(buffer_info.wAttributes);
+ bg = bits_to_color(buffer_info.wAttributes >> 4);
+ } else {
+ fg = color::WHITE;
+ bg = color::BLACK;
+ }
+ }
+ Ok(WinConsole {
+ buf: out,
+ def_foreground: fg,
+ def_background: bg,
+ foreground: fg,
+ background: bg,
+ })
+ }
+}
+
+impl<T: Write> Write for WinConsole<T> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.buf.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.buf.flush()
+ }
+}
+
+impl<T: Write + Send + 'static> Terminal for WinConsole<T> {
+ fn fg(&mut self, color: color::Color) -> io::Result<bool> {
+ self.foreground = color;
+ self.apply();
+
+ Ok(true)
+ }
+
+ fn reset(&mut self) -> io::Result<bool> {
+ self.foreground = self.def_foreground;
+ self.background = self.def_background;
+ self.apply();
+
+ Ok(true)
+ }
+}
if ostype != "Linux":
return
- if not os.path.exists("/etc/NIXOS"):
+ # Use `/etc/os-release` instead of `/etc/NIXOS`.
+ # The latter one does not exist on NixOS when using tmpfs as root.
+ try:
+ with open("/etc/os-release", "r") as f:
+ if not any(line.strip() == "ID=nixos" for line in f):
+ return
+ except FileNotFoundError:
return
if os.path.exists("/lib"):
return
slow_submodules = self.get_toml('fast-submodules') == "false"
start_time = time()
if slow_submodules:
- print('Unconditionally updating all submodules')
+ print('Unconditionally updating submodules')
else:
print('Updating only changed submodules')
default_encoding = sys.getdefaultencoding()
- submodules = [s.split(' ', 1)[1] for s in subprocess.check_output(
- ["git", "config", "--file",
- os.path.join(self.rust_root, ".gitmodules"),
- "--get-regexp", "path"]
- ).decode(default_encoding).splitlines()]
+ # Only update submodules that are needed to build bootstrap. These are needed because Cargo
+ # currently requires everything in a workspace to be "locally present" when starting a
+ # build, and will give a hard error if any Cargo.toml files are missing.
+ # FIXME: Is there a way to avoid cloning these eagerly? Bootstrap itself doesn't need to
+ # share a workspace with any tools - maybe it could be excluded from the workspace?
+ # That will still require cloning the submodules the second you check the standard
+ # library, though...
+ # FIXME: Is there a way to avoid hard-coding the submodules required?
+ # WARNING: keep this in sync with the submodules hard-coded in bootstrap/lib.rs
+ submodules = [
+ "src/tools/rust-installer",
+ "src/tools/cargo",
+ "src/tools/rls",
+ "src/tools/miri",
+ "library/backtrace",
+ "library/stdarch"
+ ]
filtered_submodules = []
submodules_names = []
for module in submodules:
- # This is handled by native::Llvm in rustbuild, not here
- if module.endswith("llvm-project"):
- continue
check = self.check_submodule(module, slow_submodules)
filtered_submodules.append((module, check))
submodules_names.append(module)
use crate::tool::{prepare_tool_cargo, SourceType};
use crate::INTERNER;
use crate::{Compiler, Mode, Subcommand};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Std {
}
fn run(self, builder: &Builder<'_>) {
+ builder.update_submodule(&Path::new("library").join("stdarch"));
+
let target = self.target;
let compiler = builder.compiler(builder.top_stage, builder.config.build);
//! library.
//!
//! This module contains some of the real meat in the rustbuild build system
-//! which is where Cargo is used to compiler the standard library, libtest, and
+//! which is where Cargo is used to compile the standard library, libtest, and
//! compiler. This module is also responsible for assembling the sysroot as it
//! goes along from the output of the previous stage.
return;
}
+ builder.update_submodule(&Path::new("library").join("stdarch"));
+
let mut target_deps = builder.ensure(StartupObjects { compiler, target });
let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target);
use crate::tool::{self, prepare_tool_cargo, SourceType, Tool};
use crate::util::symlink_dir;
+macro_rules! submodule_helper {
+ ($path:expr, submodule) => {
+ $path
+ };
+ ($path:expr, submodule = $submodule:literal) => {
+ $submodule
+ };
+}
+
macro_rules! book {
- ($($name:ident, $path:expr, $book_name:expr;)+) => {
+ ($($name:ident, $path:expr, $book_name:expr $(, submodule $(= $submodule:literal)? )? ;)+) => {
$(
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct $name {
}
fn run(self, builder: &Builder<'_>) {
+ $(
+ let path = Path::new(submodule_helper!( $path, submodule $( = $submodule )? ));
+ builder.update_submodule(&path);
+ )?
builder.ensure(RustbookSrc {
target: self.target,
name: INTERNER.intern_str($book_name),
// NOTE: When adding a book here, make sure to ALSO build the book by
// adding a build step in `src/bootstrap/builder.rs`!
+// NOTE: Make sure to add the corresponding submodule when adding a new book.
+// FIXME: Make checking for a submodule automatic somehow (maybe by having a list of all submodules
+// and checking against it?).
book!(
- CargoBook, "src/tools/cargo/src/doc", "cargo";
- EditionGuide, "src/doc/edition-guide", "edition-guide";
- EmbeddedBook, "src/doc/embedded-book", "embedded-book";
- Nomicon, "src/doc/nomicon", "nomicon";
- Reference, "src/doc/reference", "reference";
- RustByExample, "src/doc/rust-by-example", "rust-by-example";
+ CargoBook, "src/tools/cargo/src/doc", "cargo", submodule = "src/tools/cargo";
+ EditionGuide, "src/doc/edition-guide", "edition-guide", submodule;
+ EmbeddedBook, "src/doc/embedded-book", "embedded-book", submodule;
+ Nomicon, "src/doc/nomicon", "nomicon", submodule;
+ Reference, "src/doc/reference", "reference", submodule;
+ RustByExample, "src/doc/rust-by-example", "rust-by-example", submodule;
RustdocBook, "src/doc/rustdoc", "rustdoc";
);
/// * Index page
/// * Redirect pages
fn run(self, builder: &Builder<'_>) {
+ let relative_path = Path::new("src").join("doc").join("book");
+ builder.update_submodule(&relative_path);
+
let compiler = self.compiler;
let target = self.target;
builder.ensure(RustbookSrc {
target,
name: INTERNER.intern_str("book"),
- src: INTERNER.intern_path(builder.src.join("src/doc/book")),
+ src: INTERNER.intern_path(builder.src.join(&relative_path)),
});
// building older edition redirects
builder.ensure(RustbookSrc {
target,
name: INTERNER.intern_string(format!("book/{}", edition)),
- src: INTERNER.intern_path(builder.src.join("src/doc/book").join(edition)),
+ src: INTERNER.intern_path(builder.src.join(&relative_path).join(edition)),
});
}
// build the redirect pages
builder.info(&format!("Documenting book redirect pages ({})", target));
- for file in t!(fs::read_dir(builder.src.join("src/doc/book/redirects"))) {
+ for file in t!(fs::read_dir(builder.src.join(&relative_path).join("redirects"))) {
let file = t!(file);
let path = file.path();
let path = path.to_str().unwrap();
slice::from_ref(&self.build.triple)
}
+ // modified from `check_submodule` and `update_submodule` in bootstrap.py
+ /// Given a path to the directory of a submodule, update it.
+ ///
+ /// `relative_path` should be relative to the root of the git repository, not an absolute path.
+ pub(crate) fn update_submodule(&self, relative_path: &Path) {
+ fn dir_is_empty(dir: &Path) -> bool {
+ t!(std::fs::read_dir(dir)).next().is_none()
+ }
+
+ if !self.config.submodules {
+ return;
+ }
+
+ let absolute_path = self.config.src.join(relative_path);
+
+ // NOTE: The check for the empty directory is here because when running x.py the first time,
+ // the submodule won't be checked out. Check it out now so we can build it.
+ if !channel::GitInfo::new(false, relative_path).is_git() && !dir_is_empty(&absolute_path) {
+ return;
+ }
+
+ // check_submodule
+ if self.config.fast_submodules {
+ let checked_out_hash = output(
+ Command::new("git").args(&["rev-parse", "HEAD"]).current_dir(&absolute_path),
+ );
+ // update_submodules
+ let recorded = output(
+ Command::new("git")
+ .args(&["ls-tree", "HEAD"])
+ .arg(relative_path)
+ .current_dir(&self.config.src),
+ );
+ let actual_hash = recorded
+ .split_whitespace()
+ .nth(2)
+ .unwrap_or_else(|| panic!("unexpected output `{}`", recorded));
+
+ // update_submodule
+ if actual_hash == checked_out_hash.trim_end() {
+ // already checked out
+ return;
+ }
+ }
+
+ println!("Updating submodule {}", relative_path.display());
+ self.run(
+ Command::new("git")
+ .args(&["submodule", "-q", "sync"])
+ .arg(relative_path)
+ .current_dir(&self.config.src),
+ );
+
+ // Try passing `--progress` to start, then run git again without if that fails.
+ let update = |progress: bool| {
+ let mut git = Command::new("git");
+ git.args(&["submodule", "update", "--init", "--recursive"]);
+ if progress {
+ git.arg("--progress");
+ }
+ git.arg(relative_path).current_dir(&self.config.src);
+ git
+ };
+ // NOTE: doesn't use `try_run` because this shouldn't print an error if it fails.
+ if !update(true).status().map_or(false, |status| status.success()) {
+ self.run(&mut update(false));
+ }
+
+ self.run(Command::new("git").args(&["reset", "-q", "--hard"]).current_dir(&absolute_path));
+ self.run(Command::new("git").args(&["clean", "-qdfx"]).current_dir(absolute_path));
+ }
+
+ /// If any submodule has been initialized already, sync it unconditionally.
+ /// This avoids contributors checking in a submodule change by accident.
+ pub fn maybe_update_submodules(&self) {
+ // WARNING: keep this in sync with the submodules hard-coded in bootstrap.py
+ const BOOTSTRAP_SUBMODULES: &[&str] = &[
+ "src/tools/rust-installer",
+ "src/tools/cargo",
+ "src/tools/rls",
+ "src/tools/miri",
+ "library/backtrace",
+ "library/stdarch",
+ ];
+ // Avoid running git when there isn't a git checkout.
+ if !self.config.submodules {
+ return;
+ }
+ let output = output(
+ Command::new("git")
+ .args(&["config", "--file"])
+ .arg(&self.config.src.join(".gitmodules"))
+ .args(&["--get-regexp", "path"]),
+ );
+ for line in output.lines() {
+ // Look for `submodule.$name.path = $path`
+ // Sample output: `submodule.src/rust-installer.path src/tools/rust-installer`
+ let submodule = Path::new(line.splitn(2, ' ').nth(1).unwrap());
+ // avoid updating submodules twice
+ if !BOOTSTRAP_SUBMODULES.iter().any(|&p| Path::new(p) == submodule)
+ && channel::GitInfo::new(false, submodule).is_git()
+ {
+ self.update_submodule(submodule);
+ }
+ }
+ }
+
/// Executes the entire build, as configured by the flags and configuration.
pub fn build(&mut self) {
unsafe {
job::setup(self);
}
- // If the LLVM submodule has been initialized already, sync it unconditionally. This avoids
- // contributors checking in a submodule change by accident.
- if self.in_tree_llvm_info.is_git() {
- native::update_llvm_submodule(self);
- }
+ self.maybe_update_submodules();
if let Subcommand::Format { check, paths } = &self.config.cmd {
return format::format(self, *check, &paths);
use crate::builder::{Builder, RunConfig, ShouldRun, Step};
use crate::config::TargetSelection;
use crate::util::{self, exe};
-use crate::{Build, GitRepo};
+use crate::GitRepo;
use build_helper::up_to_date;
pub struct Meta {
Err(Meta { stamp, build_llvm_config, out_dir, root: root.into() })
}
-// modified from `check_submodule` and `update_submodule` in bootstrap.py
-pub(crate) fn update_llvm_submodule(build: &Build) {
- let llvm_project = &Path::new("src").join("llvm-project");
-
- fn dir_is_empty(dir: &Path) -> bool {
- t!(std::fs::read_dir(dir)).next().is_none()
- }
-
- if !build.config.submodules {
- return;
- }
-
- // NOTE: The check for the empty directory is here because when running x.py
- // the first time, the llvm submodule won't be checked out. Check it out
- // now so we can build it.
- if !build.in_tree_llvm_info.is_git() && !dir_is_empty(&build.config.src.join(llvm_project)) {
- return;
- }
-
- // check_submodule
- if build.config.fast_submodules {
- let checked_out_hash = output(
- Command::new("git")
- .args(&["rev-parse", "HEAD"])
- .current_dir(build.config.src.join(llvm_project)),
- );
- // update_submodules
- let recorded = output(
- Command::new("git")
- .args(&["ls-tree", "HEAD"])
- .arg(llvm_project)
- .current_dir(&build.config.src),
- );
- let actual_hash = recorded
- .split_whitespace()
- .nth(2)
- .unwrap_or_else(|| panic!("unexpected output `{}`", recorded));
-
- // update_submodule
- if actual_hash == checked_out_hash.trim_end() {
- // already checked out
- return;
- }
- }
-
- println!("Updating submodule {}", llvm_project.display());
- build.run(
- Command::new("git")
- .args(&["submodule", "-q", "sync"])
- .arg(llvm_project)
- .current_dir(&build.config.src),
- );
-
- // Try passing `--progress` to start, then run git again without if that fails.
- let update = |progress: bool| {
- let mut git = Command::new("git");
- git.args(&["submodule", "update", "--init", "--recursive"]);
- if progress {
- git.arg("--progress");
- }
- git.arg(llvm_project).current_dir(&build.config.src);
- git
- };
- // NOTE: doesn't use `try_run` because this shouldn't print an error if it fails.
- if !update(true).status().map_or(false, |status| status.success()) {
- build.run(&mut update(false));
- }
-
- build.run(
- Command::new("git")
- .args(&["reset", "-q", "--hard"])
- .current_dir(build.config.src.join(llvm_project)),
- );
- build.run(
- Command::new("git")
- .args(&["clean", "-qdfx"])
- .current_dir(build.config.src.join(llvm_project)),
- );
-}
-
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Llvm {
pub target: TargetSelection,
Err(m) => m,
};
- if !builder.config.dry_run {
- update_llvm_submodule(builder);
- }
+ builder.update_submodule(&Path::new("src").join("llvm-project"));
if builder.config.llvm_link_shared
&& (target.contains("windows") || target.contains("apple-darwin"))
{
}
fn run(self, builder: &Builder<'_>) {
- let src = builder.src.join("src/doc/rustc-dev-guide");
+ let relative_path = Path::new("src").join("doc").join("rustc-dev-guide");
+ builder.update_submodule(&relative_path);
+
+ let src = builder.src.join(relative_path);
let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
let toolstate = if try_run(builder, rustbook_cmd.arg("linkcheck").arg(&src)) {
ToolState::TestPass
use std::collections::HashSet;
use std::env;
use std::fs;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use std::process::{exit, Command};
use build_helper::t;
$path:expr,
$tool_name:expr,
stable = $stable:expr,
- $(in_tree = $in_tree:expr,)*
+ $(in_tree = $in_tree:expr,)?
+ $(submodule = $submodule:literal,)?
$extra_deps:block;)+) => {
$(
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
#[allow(unused_mut)]
fn run(mut $sel, $builder: &Builder<'_>) -> Option<PathBuf> {
$extra_deps
+ $( $builder.update_submodule(&Path::new("src").join("tools").join($submodule)); )?
$builder.ensure(ToolBuild {
compiler: $sel.compiler,
target: $sel.target,
// Note: tools need to be also added to `Builder::get_step_descriptions` in `builder.rs`
// to make `./x.py build <tool>` work.
+// Note: Most submodule updates for tools are handled by bootstrap.py, since they're needed just to
+// invoke Cargo to build bootstrap. See the comment there for more details.
tool_extended!((self, builder),
Cargofmt, rustfmt, "src/tools/rustfmt", "cargo-fmt", stable=true, in_tree=true, {};
CargoClippy, clippy, "src/tools/clippy", "cargo-clippy", stable=true, in_tree=true, {};
};
RustDemangler, rust_demangler, "src/tools/rust-demangler", "rust-demangler", stable=false, in_tree=true, {};
Rustfmt, rustfmt, "src/tools/rustfmt", "rustfmt", stable=true, in_tree=true, {};
- RustAnalyzer, rust_analyzer, "src/tools/rust-analyzer/crates/rust-analyzer", "rust-analyzer", stable=false, {};
+ RustAnalyzer, rust_analyzer, "src/tools/rust-analyzer/crates/rust-analyzer", "rust-analyzer", stable=false, submodule="rust-analyzer", {};
);
impl<'a> Builder<'a> {
-Subproject commit a90f07f1e9a7fc75dc9105a6c6f16d5c13edceb0
+Subproject commit eac55314210519238652f12b30fec9daea61f7fe
-Subproject commit 5d57b3832f8d308a9f478ce0a69799548f27ad4d
+Subproject commit af696ce8ea526445590ae0ca66a8128d2a95a69a
-Subproject commit 506840eb73b0749336e1d5274e16d6393892ee82
+Subproject commit 09986cd352404eb4659db44613b27cac9aa652fc
-Subproject commit ab60513a3a5a0591e237fddff5d027a982648392
+Subproject commit 82d75cf423e4a7824fb36e73ccb18519d6900610
-Subproject commit 028f93a61500fe8f746ee7cc6b204ea6c9f42935
+Subproject commit 1db6bb483cc87ad3b424d9aba764fe622960a1be
-Subproject commit 60e282559104035985331645907c3d9f842312c5
+Subproject commit 93422c21baca585dc88357ec886a48f6ddc7d665
use clean::AttributesExt;
use std::cmp::Ordering;
+use std::fmt;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
n_fields > 12
}
-fn toggle_open(w: &mut Buffer, text: &str) {
+fn toggle_open(w: &mut Buffer, text: impl fmt::Display) {
write!(
w,
"<details class=\"rustdoc-toggle type-contents-toggle\">\
let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::<Vec<_>>();
let required = t.items.iter().filter(|m| m.is_ty_method()).collect::<Vec<_>>();
let provided = t.items.iter().filter(|m| m.is_method()).collect::<Vec<_>>();
+ let count_types = types.len();
+ let count_consts = consts.len();
+ let count_methods = required.len() + provided.len();
// Output the trait definition
wrap_into_docblock(w, |w| {
let mut toggle = false;
// If there are too many associated types, hide _everything_
- if should_hide_fields(types.len()) {
+ if should_hide_fields(count_types) {
toggle = true;
- toggle_open(w, "associated items");
+ toggle_open(
+ w,
+ format_args!("{} associated items", count_types + count_consts + count_methods),
+ );
}
for t in &types {
render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
// We also do this if the types + consts is large because otherwise we could
// render a bunch of types and _then_ a bunch of consts just because both were
// _just_ under the limit
- if !toggle && should_hide_fields(types.len() + consts.len()) {
+ if !toggle && should_hide_fields(count_types + count_consts) {
toggle = true;
- toggle_open(w, "associated constants and methods");
+ toggle_open(
+ w,
+ format_args!(
+ "{} associated constant{} and {} method{}",
+ count_consts,
+ pluralize(count_consts),
+ count_methods,
+ pluralize(count_methods),
+ ),
+ );
}
if !types.is_empty() && !consts.is_empty() {
w.write_str("\n");
render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait, cx);
w.write_str(";\n");
}
- if !toggle && should_hide_fields(required.len() + provided.len()) {
+ if !toggle && should_hide_fields(count_methods) {
toggle = true;
- toggle_open(w, "methods");
+ toggle_open(w, format_args!("{} methods", count_methods));
}
if !consts.is_empty() && !required.is_empty() {
w.write_str("\n");
w.write_str(" {}");
} else {
w.write_str(" {\n");
- let toggle = should_hide_fields(e.variants.len());
+ let count_variants = e.variants.len();
+ let toggle = should_hide_fields(count_variants);
if toggle {
- toggle_open(w, "variants");
+ toggle_open(w, format_args!("{} variants", count_variants));
}
for v in &e.variants {
w.write_str(" ");
use crate::clean::Variant;
if let clean::VariantItem(Variant::Struct(ref s)) = *variant.kind {
- toggle_open(w, "fields");
+ let count_fields = s.fields.len();
+ toggle_open(w, format_args!("{} field{}", count_fields, pluralize(count_fields)));
let variant_id = cx.derive_id(format!(
"{}.{}.fields",
ItemType::Variant,
fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count();
let toggle = should_hide_fields(count_fields);
if toggle {
- toggle_open(w, "fields");
+ toggle_open(w, format_args!("{} fields", count_fields));
}
for field in fields {
let has_visible_fields = count_fields > 0;
let toggle = should_hide_fields(count_fields);
if toggle {
- toggle_open(w, "fields");
+ toggle_open(w, format_args!("{} fields", count_fields));
}
for field in fields {
if let clean::StructFieldItem(ref ty) = *field.kind {
writeln!(w, "</div>");
}
+
+fn pluralize(count: usize) -> &'static str {
+ if count > 1 { "s" } else { "" }
+}
.docblock table {
margin: .5em 0;
width: calc(100% - 2px);
- border: 1px dashed;
+ overflow-x: auto;
+ display: block;
}
.docblock table td {
border-bottom-color: #5c6773;
}
-.docblock table, .docblock table td, .docblock table th {
+.docblock table td, .docblock table th {
border-color: #5c6773;
}
border-bottom-color: #DDD;
}
-.docblock table, .docblock table td, .docblock table th {
+.docblock table td, .docblock table th {
border-color: #ddd;
}
border-bottom-color: #ddd;
}
-.docblock table, .docblock table td, .docblock table th {
+.docblock table td, .docblock table th {
border-color: #ddd;
}
--- /dev/null
+// This test ensures that the type declaration content overflow is handled inside the <pre> directly.
+goto: file://|DOC_PATH|/lib2/long_table/struct.Foo.html
+// We set a fixed size so there is no chance of "random" resize.
+size: (1100, 800)
+// Logically, the ".docblock" and the "<p>" should have the same scroll width.
+compare-elements-property: (".top-doc .docblock", ".top-doc .docblock > p", ["scrollWidth"])
+assert-property: (".top-doc .docblock", {"scrollWidth": "816"})
+// However, since there is overflow in the <table>, its scroll width is bigger.
+assert-property: (".top-doc .docblock table", {"scrollWidth": "1573"})
pub trait ALongNameBecauseItHelpsTestingTheCurrentProblem: DerefMut<Target = u32>
+ From<u128> + Send + Sync + AsRef<str> + 'static {}
}
+
+pub mod long_table {
+ /// | This::is::a::kinda::very::long::header::number::one | This::is::a::kinda::very::long::header::number::two | This::is::a::kinda::very::long::header::number::one | This::is::a::kinda::very::long::header::number::two |
+ /// | ----------- | ----------- | ----------- | ----------- |
+ /// | This::is::a::kinda::long::content::number::one | This::is::a::kinda::very::long::content::number::two | This::is::a::kinda::long::content::number::one | This::is::a::kinda::very::long::content::number::two |
+ ///
+ /// I wanna sqdkfnqds f dsqf qds f dsqf dsq f dsq f qds f qds f qds f dsqq f dsf sqdf dsq fds f dsq f dq f ds fq sd fqds f dsq f sqd fsq df sd fdsqfqsd fdsq f dsq f dsqfd s dfq
+ pub struct Foo;
+}
+++ /dev/null
-#[cfg(test)]
-mod tests {
- #[test]
- fn it_works() {
- assert_eq!(2 + 2, 4);
- }
-}
// @has 'toggle_item_contents/struct.BigPubStruct.html'
// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
-// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show fields'
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 13 fields'
pub struct BigPubStruct {
pub a: usize,
pub b: usize,
// @has 'toggle_item_contents/union.BigUnion.html'
// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
-// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show fields'
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 13 fields'
pub union BigUnion {
pub a: usize,
pub b: usize,
// @has 'toggle_item_contents/enum.Enum.html'
// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
-// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show fields'
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 2 fields'
pub enum Enum {
A, B, C,
D {
}
}
+// @has 'toggle_item_contents/enum.EnumStructVariant.html'
+// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 1 field'
+pub enum EnumStructVariant {
+ A, B, C,
+ D {
+ a: u8,
+ }
+}
+
// @has 'toggle_item_contents/enum.LargeEnum.html'
// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
-// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show variants'
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 13 variants'
pub enum LargeEnum {
A, B, C, D, E, F(u8), G, H, I, J, K, L, M
}
// @has 'toggle_item_contents/trait.GinormousTrait.html'
// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
-// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show associated items'
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 16 associated items'
pub trait GinormousTrait {
type A;
type B;
// @has 'toggle_item_contents/trait.HugeTrait.html'
// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
-// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show associated constants and methods'
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 12 associated constants and 2 methods'
pub trait HugeTrait {
type A;
const M: usize = 1;
fn bar();
}
+// @has 'toggle_item_contents/trait.GiganticTrait.html'
+// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 1 associated constant and 1 method'
+pub trait GiganticTrait {
+ type A;
+ type B;
+ type C;
+ type D;
+ type E;
+ type F;
+ type G;
+ type H;
+ type I;
+ type J;
+ type K;
+ type L;
+ const M: usize = 1;
+ #[must_use]
+ fn foo();
+}
+
// @has 'toggle_item_contents/trait.BigTrait.html'
// @count - '//details[@class="rustdoc-toggle type-contents-toggle"]' 1
-// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show methods'
+// @has - '//details[@class="rustdoc-toggle type-contents-toggle"]' 'Show 14 methods'
pub trait BigTrait {
type A;
#[must_use]
error: aborting due to 6 previous errors
-For more information about this error, try `rustc --explain E0658`.
+Some errors have detailed explanations: E0658, E0722.
+For more information about an error, try `rustc --explain E0658`.
error: aborting due to previous error
+For more information about this error, try `rustc --explain E0757`.
help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as a bound
|
LL | fn elided(x: &i32) -> impl Copy + '_ { x }
- | ^^^^^^^^^^^^^^
+ | ^^^^
error: lifetime may not live long enough
--> $DIR/must_outlive_least_region_or_bound.rs:5:32
help: to allow this `impl Trait` to capture borrowed data with lifetime `'a`, add `'a` as a bound
|
LL | fn explicit<'a>(x: &'a i32) -> impl Copy + 'a { x }
- | ^^^^^^^^^^^^^^
+ | ^^^^
error: lifetime may not live long enough
--> $DIR/must_outlive_least_region_or_bound.rs:7:46
help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as a bound
|
LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> + '_ {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ | ^^^^
error: lifetime may not live long enough
--> $DIR/static-return-lifetime-infered.rs:9:37
help: to allow this `impl Trait` to capture borrowed data with lifetime `'a`, add `'a` as a bound
|
LL | fn iter_values<'a>(&'a self) -> impl Iterator<Item=u32> + 'a {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ | ^^^^
error: aborting due to 2 previous errors
--- /dev/null
+// check-pass
+#![feature(const_fn_trait_bound)]
+#![feature(const_trait_impl)]
+
+trait MyPartialEq {
+ fn eq(&self, other: &Self) -> bool;
+}
+
+impl<T: PartialEq> const MyPartialEq for T {
+ fn eq(&self, other: &Self) -> bool {
+ PartialEq::eq(self, other)
+ }
+}
+
+fn main() {}
help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as a bound
|
LL | async fn f(self: Pin<&Self>) -> impl Clone + '_ { self }
- | ^^^^^^^^^^^^^^^
+ | ^^^^
error: aborting due to previous error
help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as a bound
|
LL | fn f(self: Pin<&Self>) -> impl Clone + '_ { self }
- | ^^^^^^^^^^^^^^^
+ | ^^^^
error: aborting due to previous error
help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as a bound
|
LL | fn iter(&self) -> impl Iterator<Item = Box<dyn Foo>> + '_ {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ | ^^^^
error: lifetime may not live long enough
--> $DIR/trait-object-nested-in-impl-trait.rs:39:9
help: to allow this `impl Trait` to capture borrowed data with lifetime `'a`, add `'a` as a bound
|
LL | fn iter<'a>(&'a self) -> impl Iterator<Item = Box<dyn Foo>> + 'a {
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ | ^^^^
error: aborting due to 4 previous errors
-Subproject commit 27277d966b3cfa454d6dea7f724cb961c036251c
+Subproject commit 4e143fd131e0c16cefd008456e974236ca54e62e
-Subproject commit 250eff85c86b089b77005691b899cea739f7e0cb
+Subproject commit e2872a3f2a26154b91a6a6085d56016509803c61
-Subproject commit fe00358888a24c64878abc15f09b0e60e16db9d6
+Subproject commit ea105f9396a9dab68e71efb06016b7c76c83ba7c