Extend style guideline documentation

This commit is contained in:
Christian Duerr 2020-05-05 22:50:23 +00:00 committed by GitHub
parent 04f0bcaf54
commit 81ce93574f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1224 additions and 1218 deletions

View File

@ -99,6 +99,13 @@ All Alacritty changes are automatically verified by CI to conform to its rustfmt
build is failing because of formatting issues, you can install rustfmt using `rustup component add build is failing because of formatting issues, you can install rustfmt using `rustup component add
rustfmt` and then format all code using `cargo fmt`. rustfmt` and then format all code using `cargo fmt`.
Unless otherwise specified, Alacritty follows the Rust compiler's style guidelines:
https://rust-lang.github.io/api-guidelines
All comments should be fully punctuated with a trailing period. This applies both to regular and
documentation comments.
# Release Process # Release Process
Alacritty's release process aims to provide stable and well tested releases without having to hold Alacritty's release process aims to provide stable and well tested releases without having to hold

View File

@ -30,7 +30,7 @@ const CONFIG_PATH: &str = "%APPDATA%\\alacritty\\alacritty.yml";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const CONFIG_PATH: &str = "$HOME/.config/alacritty/alacritty.yml"; const CONFIG_PATH: &str = "$HOME/.config/alacritty/alacritty.yml";
/// Options specified on the command line /// Options specified on the command line.
pub struct Options { pub struct Options {
pub live_config_reload: Option<bool>, pub live_config_reload: Option<bool>,
pub print_events: bool, pub print_events: bool,
@ -241,8 +241,8 @@ impl Options {
if let Some(mut args) = matches.values_of("command") { if let Some(mut args) = matches.values_of("command") {
// The following unwrap is guaranteed to succeed. // The following unwrap is guaranteed to succeed.
// If 'command' exists it must also have a first item since // If `command` exists it must also have a first item since
// Arg::min_values(1) is set. // `Arg::min_values(1)` is set.
let command = String::from(args.next().unwrap()); let command = String::from(args.next().unwrap());
let args = args.map(String::from).collect(); let args = args.map(String::from).collect();
options.command = Some(Shell::new_with_args(command, args)); options.command = Some(Shell::new_with_args(command, args));

View File

@ -25,21 +25,21 @@ use serde_yaml::Value as SerdeValue;
use alacritty_terminal::term::TermMode; use alacritty_terminal::term::TermMode;
use alacritty_terminal::vi_mode::ViMotion; use alacritty_terminal::vi_mode::ViMotion;
/// Describes a state and action to take in that state /// Describes a state and action to take in that state.
/// ///
/// This is the shared component of `MouseBinding` and `KeyBinding` /// This is the shared component of `MouseBinding` and `KeyBinding`.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Binding<T> { pub struct Binding<T> {
/// Modifier keys required to activate binding /// Modifier keys required to activate binding.
pub mods: ModifiersState, pub mods: ModifiersState,
/// String to send to pty if mods and mode match /// String to send to PTY if mods and mode match.
pub action: Action, pub action: Action,
/// Terminal mode required to activate binding /// Terminal mode required to activate binding.
pub mode: TermMode, pub mode: TermMode,
/// excluded terminal modes where the binding won't be activated /// excluded terminal modes where the binding won't be activated.
pub notmode: TermMode, pub notmode: TermMode,
/// This property is used as part of the trigger detection code. /// This property is used as part of the trigger detection code.
@ -48,10 +48,10 @@ pub struct Binding<T> {
pub trigger: T, pub trigger: T,
} }
/// Bindings that are triggered by a keyboard key /// Bindings that are triggered by a keyboard key.
pub type KeyBinding = Binding<Key>; pub type KeyBinding = Binding<Key>;
/// Bindings that are triggered by a mouse button /// Bindings that are triggered by a mouse button.
pub type MouseBinding = Binding<MouseButton>; pub type MouseBinding = Binding<MouseButton>;
impl<T: Eq> Binding<T> { impl<T: Eq> Binding<T> {
@ -68,19 +68,19 @@ impl<T: Eq> Binding<T> {
#[inline] #[inline]
pub fn triggers_match(&self, binding: &Binding<T>) -> bool { pub fn triggers_match(&self, binding: &Binding<T>) -> bool {
// Check the binding's key and modifiers // Check the binding's key and modifiers.
if self.trigger != binding.trigger || self.mods != binding.mods { if self.trigger != binding.trigger || self.mods != binding.mods {
return false; return false;
} }
// Completely empty modes match all modes // Completely empty modes match all modes.
if (self.mode.is_empty() && self.notmode.is_empty()) if (self.mode.is_empty() && self.notmode.is_empty())
|| (binding.mode.is_empty() && binding.notmode.is_empty()) || (binding.mode.is_empty() && binding.notmode.is_empty())
{ {
return true; return true;
} }
// Check for intersection (equality is required since empty does not intersect itself) // Check for intersection (equality is required since empty does not intersect itself).
(self.mode == binding.mode || self.mode.intersects(binding.mode)) (self.mode == binding.mode || self.mode.intersects(binding.mode))
&& (self.notmode == binding.notmode || self.notmode.intersects(binding.notmode)) && (self.notmode == binding.notmode || self.notmode.intersects(binding.notmode))
} }
@ -171,7 +171,7 @@ pub enum Action {
/// Toggle fullscreen. /// Toggle fullscreen.
ToggleFullscreen, ToggleFullscreen,
/// Toggle simple fullscreen on macos. /// Toggle simple fullscreen on macOS.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
ToggleSimpleFullscreen, ToggleSimpleFullscreen,
@ -452,8 +452,7 @@ pub fn default_key_bindings() -> Vec<KeyBinding> {
F20, mods, ~TermMode::VI; Action::Esc(format!("\x1b[34;{}~", modifiers_code)); F20, mods, ~TermMode::VI; Action::Esc(format!("\x1b[34;{}~", modifiers_code));
)); ));
// We're adding the following bindings with `Shift` manually above, so skipping them here // We're adding the following bindings with `Shift` manually above, so skipping them here.
// modifiers_code != Shift
if modifiers_code != 2 { if modifiers_code != 2 {
bindings.extend(bindings!( bindings.extend(bindings!(
KeyBinding; KeyBinding;
@ -525,7 +524,7 @@ pub fn platform_key_bindings() -> Vec<KeyBinding> {
) )
} }
// Don't return any bindings for tests since they are commented-out by default // Don't return any bindings for tests since they are commented-out by default.
#[cfg(test)] #[cfg(test)]
pub fn platform_key_bindings() -> Vec<KeyBinding> { pub fn platform_key_bindings() -> Vec<KeyBinding> {
vec![] vec![]
@ -956,7 +955,7 @@ impl CommandWrapper {
} }
} }
/// Newtype for implementing deserialize on glutin Mods /// Newtype for implementing deserialize on glutin Mods.
/// ///
/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the /// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
/// impl below. /// impl below.

View File

@ -22,22 +22,22 @@ use crate::config::ui_config::UIConfig;
pub type Config = TermConfig<UIConfig>; pub type Config = TermConfig<UIConfig>;
/// Result from config loading /// Result from config loading.
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// Errors occurring during config loading /// Errors occurring during config loading.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Config file not found /// Config file not found.
NotFound, NotFound,
/// Couldn't read $HOME environment variable /// Couldn't read $HOME environment variable.
ReadingEnvHome(env::VarError), ReadingEnvHome(env::VarError),
/// io error reading file /// io error reading file.
Io(io::Error), Io(io::Error),
/// Not valid yaml or missing parameters /// Not valid yaml or missing parameters.
Yaml(serde_yaml::Error), Yaml(serde_yaml::Error),
} }
@ -96,7 +96,7 @@ impl From<serde_yaml::Error> for Error {
/// 4. $HOME/.alacritty.yml /// 4. $HOME/.alacritty.yml
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn installed_config() -> Option<PathBuf> { pub fn installed_config() -> Option<PathBuf> {
// Try using XDG location by default // Try using XDG location by default.
xdg::BaseDirectories::with_prefix("alacritty") xdg::BaseDirectories::with_prefix("alacritty")
.ok() .ok()
.and_then(|xdg| xdg.find_config_file("alacritty.yml")) .and_then(|xdg| xdg.find_config_file("alacritty.yml"))
@ -107,12 +107,12 @@ pub fn installed_config() -> Option<PathBuf> {
}) })
.or_else(|| { .or_else(|| {
if let Ok(home) = env::var("HOME") { if let Ok(home) = env::var("HOME") {
// Fallback path: $HOME/.config/alacritty/alacritty.yml // Fallback path: $HOME/.config/alacritty/alacritty.yml.
let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml");
if fallback.exists() { if fallback.exists() {
return Some(fallback); return Some(fallback);
} }
// Fallback path: $HOME/.alacritty.yml // Fallback path: $HOME/.alacritty.yml.
let fallback = PathBuf::from(&home).join(".alacritty.yml"); let fallback = PathBuf::from(&home).join(".alacritty.yml");
if fallback.exists() { if fallback.exists() {
return Some(fallback); return Some(fallback);
@ -146,7 +146,7 @@ pub fn reload_from(path: &PathBuf) -> Result<Config> {
fn read_config(path: &PathBuf) -> Result<Config> { fn read_config(path: &PathBuf) -> Result<Config> {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
// Remove UTF-8 BOM // Remove UTF-8 BOM.
if contents.starts_with('\u{FEFF}') { if contents.starts_with('\u{FEFF}') {
contents = contents.split_off(3); contents = contents.split_off(3);
} }

View File

@ -28,11 +28,11 @@ impl Monitor {
watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher");
let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path");
// Get directory of config // Get directory of config.
let mut parent = config_path.clone(); let mut parent = config_path.clone();
parent.pop(); parent.pop();
// Watch directory // Watch directory.
watcher watcher
.watch(&parent, RecursiveMode::NonRecursive) .watch(&parent, RecursiveMode::NonRecursive)
.expect("watch alacritty.yml dir"); .expect("watch alacritty.yml dir");

View File

@ -24,11 +24,11 @@ pub struct Mouse {
#[serde(default)] #[serde(default)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Url { pub struct Url {
// Program for opening links /// Program for opening links.
#[serde(deserialize_with = "deserialize_launcher")] #[serde(deserialize_with = "deserialize_launcher")]
pub launcher: Option<CommandWrapper>, pub launcher: Option<CommandWrapper>,
// Modifier used to open links /// Modifier used to open links.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
modifiers: ModsWrapper, modifiers: ModsWrapper,
} }
@ -47,10 +47,10 @@ where
{ {
let default = Url::default().launcher; let default = Url::default().launcher;
// Deserialize to generic value // Deserialize to generic value.
let val = serde_yaml::Value::deserialize(deserializer)?; let val = serde_yaml::Value::deserialize(deserializer)?;
// Accept `None` to disable the launcher // Accept `None` to disable the launcher.
if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() {
return Ok(None); return Ok(None);
} }

View File

@ -11,11 +11,11 @@ pub struct UIConfig {
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub mouse: Mouse, pub mouse: Mouse,
/// Keybindings /// Keybindings.
#[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")]
pub key_bindings: Vec<KeyBinding>, pub key_bindings: Vec<KeyBinding>,
/// Bindings for the mouse /// Bindings for the mouse.
#[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")]
pub mouse_bindings: Vec<MouseBinding>, pub mouse_bindings: Vec<MouseBinding>,
} }
@ -63,7 +63,7 @@ where
{ {
let values = Vec::<serde_yaml::Value>::deserialize(deserializer)?; let values = Vec::<serde_yaml::Value>::deserialize(deserializer)?;
// Skip all invalid values // Skip all invalid values.
let mut bindings = Vec::with_capacity(values.len()); let mut bindings = Vec::with_capacity(values.len());
for value in values { for value in values {
match Binding::<T>::deserialize(value) { match Binding::<T>::deserialize(value) {
@ -74,7 +74,7 @@ where
} }
} }
// Remove matching default bindings // Remove matching default bindings.
for binding in bindings.iter() { for binding in bindings.iter() {
default.retain(|b| !b.triggers_match(binding)); default.retain(|b| !b.triggers_match(binding));
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Helpers for creating different cursor glyphs from font metrics //! Helpers for creating different cursor glyphs from font metrics.
use std::cmp; use std::cmp;
@ -28,13 +28,13 @@ pub fn get_cursor_glyph(
is_wide: bool, is_wide: bool,
cursor_thickness: f64, cursor_thickness: f64,
) -> RasterizedGlyph { ) -> RasterizedGlyph {
// Calculate the cell metrics // Calculate the cell metrics.
let height = metrics.line_height as i32 + i32::from(offset_y); let height = metrics.line_height as i32 + i32::from(offset_y);
let mut width = metrics.average_advance as i32 + i32::from(offset_x); let mut width = metrics.average_advance as i32 + i32::from(offset_x);
let line_width = cmp::max((cursor_thickness * f64::from(width)).round() as i32, 1); let line_width = cmp::max((cursor_thickness * f64::from(width)).round() as i32, 1);
// Double the cursor width if it's above a double-width glyph // Double the cursor width if it's above a double-width glyph.
if is_wide { if is_wide {
width *= 2; width *= 2;
} }
@ -48,12 +48,12 @@ pub fn get_cursor_glyph(
} }
} }
// Returns a custom underline cursor character /// Return a custom underline cursor character.
pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyph { pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyph {
// Create a new rectangle, the height is relative to the font width // Create a new rectangle, the height is relative to the font width.
let buf = vec![255u8; (width * line_width * 3) as usize]; let buf = vec![255u8; (width * line_width * 3) as usize];
// Create a custom glyph with the rectangle data attached to it // Create a custom glyph with the rectangle data attached to it.
RasterizedGlyph { RasterizedGlyph {
c: ' ', c: ' ',
top: line_width, top: line_width,
@ -64,7 +64,7 @@ pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyp
} }
} }
// Returns a custom beam cursor character /// Return a custom beam cursor character.
pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph { pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph {
// Create a new rectangle that is at least one pixel wide // Create a new rectangle that is at least one pixel wide
let buf = vec![255u8; (line_width * height * 3) as usize]; let buf = vec![255u8; (line_width * height * 3) as usize];
@ -80,9 +80,9 @@ pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph {
} }
} }
// Returns a custom box cursor character /// Returns a custom box cursor character.
pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> RasterizedGlyph { pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> RasterizedGlyph {
// Create a new box outline rectangle // Create a new box outline rectangle.
let mut buf = Vec::with_capacity((width * height * 3) as usize); let mut buf = Vec::with_capacity((width * height * 3) as usize);
for y in 0..height { for y in 0..height {
for x in 0..width { for x in 0..width {
@ -98,15 +98,15 @@ pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> Rasteri
} }
} }
// Create a custom glyph with the rectangle data attached to it // Create a custom glyph with the rectangle data attached to it.
RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) } RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) }
} }
// Returns a custom block cursor character /// Return a custom block cursor character.
pub fn get_block_cursor_glyph(height: i32, width: i32) -> RasterizedGlyph { pub fn get_block_cursor_glyph(height: i32, width: i32) -> RasterizedGlyph {
// Create a completely filled glyph // Create a completely filled glyph.
let buf = vec![255u8; (width * height * 3) as usize]; let buf = vec![255u8; (width * height * 3) as usize];
// Create a custom glyph with the rectangle data attached to it // Create a custom glyph with the rectangle data attached to it.
RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) } RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) }
} }

View File

@ -219,7 +219,7 @@ impl Display {
// Update OpenGL projection. // Update OpenGL projection.
renderer.resize(&size_info); renderer.resize(&size_info);
// Call `clear` before showing the window, to make sure the surface is initialized. // Clear screen.
let background_color = config.colors.primary.background; let background_color = config.colors.primary.background;
renderer.with_api(&config, &size_info, |api| { renderer.with_api(&config, &size_info, |api| {
api.clear(background_color); api.clear(background_color);
@ -244,7 +244,7 @@ impl Display {
// Set window position. // Set window position.
// //
// TODO: replace `set_position` with `with_position` once available // TODO: replace `set_position` with `with_position` once available.
// Upstream issue: https://github.com/rust-windowing/winit/issues/806. // Upstream issue: https://github.com/rust-windowing/winit/issues/806.
if let Some(position) = config.window.position { if let Some(position) = config.window.position {
window.set_outer_position(PhysicalPosition::from((position.x, position.y))); window.set_outer_position(PhysicalPosition::from((position.x, position.y)));

View File

@ -1,4 +1,4 @@
//! Process window events //! Process window events.
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::max; use std::cmp::max;
use std::env; use std::env;
@ -91,7 +91,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
fn scroll(&mut self, scroll: Scroll) { fn scroll(&mut self, scroll: Scroll) {
self.terminal.scroll_display(scroll); self.terminal.scroll_display(scroll);
// Update selection // Update selection.
if self.terminal.mode().contains(TermMode::VI) if self.terminal.mode().contains(TermMode::VI)
&& self.terminal.selection().as_ref().map(|s| s.is_empty()) != Some(true) && self.terminal.selection().as_ref().map(|s| s.is_empty()) != Some(true)
{ {
@ -125,7 +125,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
fn update_selection(&mut self, point: Point, side: Side) { fn update_selection(&mut self, point: Point, side: Side) {
let point = self.terminal.visible_to_buffer(point); let point = self.terminal.visible_to_buffer(point);
// Update selection if one exists // Update selection if one exists.
let vi_mode = self.terminal.mode().contains(TermMode::VI); let vi_mode = self.terminal.mode().contains(TermMode::VI);
if let Some(selection) = self.terminal.selection_mut() { if let Some(selection) = self.terminal.selection_mut() {
selection.update(point, side); selection.update(point, side);
@ -306,7 +306,7 @@ pub enum ClickState {
TripleClick, TripleClick,
} }
/// State of the mouse /// State of the mouse.
#[derive(Debug)] #[derive(Debug)]
pub struct Mouse { pub struct Mouse {
pub x: usize, pub x: usize,
@ -346,7 +346,7 @@ impl Default for Mouse {
} }
} }
/// The event processor /// The event processor.
/// ///
/// Stores some state from received events and dispatches actions when they are /// Stores some state from received events and dispatches actions when they are
/// triggered. /// triggered.
@ -364,10 +364,9 @@ pub struct Processor<N> {
} }
impl<N: Notify + OnResize> Processor<N> { impl<N: Notify + OnResize> Processor<N> {
/// Create a new event processor /// Create a new event processor.
/// ///
/// Takes a writer which is expected to be hooked up to the write end of a /// Takes a writer which is expected to be hooked up to the write end of a PTY.
/// pty.
pub fn new( pub fn new(
notifier: N, notifier: N,
message_buffer: MessageBuffer, message_buffer: MessageBuffer,
@ -528,11 +527,11 @@ impl<N: Notify + OnResize> Processor<N> {
} }
}); });
// Write ref tests to disk // Write ref tests to disk.
self.write_ref_test_results(&terminal.lock()); self.write_ref_test_results(&terminal.lock());
} }
/// Handle events from glutin /// Handle events from glutin.
/// ///
/// Doesn't take self mutably due to borrow checking. /// Doesn't take self mutably due to borrow checking.
fn handle_event<T>( fn handle_event<T>(
@ -546,11 +545,11 @@ impl<N: Notify + OnResize> Processor<N> {
Event::DPRChanged(scale_factor, (width, height)) => { Event::DPRChanged(scale_factor, (width, height)) => {
let display_update_pending = &mut processor.ctx.display_update_pending; let display_update_pending = &mut processor.ctx.display_update_pending;
// Push current font to update its DPR // Push current font to update its DPR.
display_update_pending.font = display_update_pending.font =
Some(processor.ctx.config.font.clone().with_size(*processor.ctx.font_size)); Some(processor.ctx.config.font.clone().with_size(*processor.ctx.font_size));
// Resize to event's dimensions, since no resize event is emitted on Wayland // Resize to event's dimensions, since no resize event is emitted on Wayland.
display_update_pending.dimensions = Some(PhysicalSize::new(width, height)); display_update_pending.dimensions = Some(PhysicalSize::new(width, height));
processor.ctx.size_info.dpr = scale_factor; processor.ctx.size_info.dpr = scale_factor;
@ -592,7 +591,7 @@ impl<N: Notify + OnResize> Processor<N> {
WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => { WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => {
processor.key_input(input); processor.key_input(input);
if input.state == ElementState::Pressed { if input.state == ElementState::Pressed {
// Hide cursor while typing // Hide cursor while typing.
if processor.ctx.config.ui_config.mouse.hide_when_typing { if processor.ctx.config.ui_config.mouse.hide_when_typing {
processor.ctx.window.set_mouse_visible(false); processor.ctx.window.set_mouse_visible(false);
} }
@ -667,7 +666,7 @@ impl<N: Notify + OnResize> Processor<N> {
} }
} }
/// Check if an event is irrelevant and can be skipped /// Check if an event is irrelevant and can be skipped.
fn skip_event(event: &GlutinEvent<Event>) -> bool { fn skip_event(event: &GlutinEvent<Event>) -> bool {
match event { match event {
GlutinEvent::WindowEvent { event, .. } => match event { GlutinEvent::WindowEvent { event, .. } => match event {
@ -709,7 +708,7 @@ impl<N: Notify + OnResize> Processor<N> {
processor.ctx.terminal.update_config(&config); processor.ctx.terminal.update_config(&config);
// Reload cursor if we've changed its thickness // Reload cursor if we've changed its thickness.
if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs() if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs()
> std::f64::EPSILON > std::f64::EPSILON
{ {
@ -717,7 +716,7 @@ impl<N: Notify + OnResize> Processor<N> {
} }
if processor.ctx.config.font != config.font { if processor.ctx.config.font != config.font {
// Do not update font size if it has been changed at runtime // Do not update font size if it has been changed at runtime.
if *processor.ctx.font_size == processor.ctx.config.font.size { if *processor.ctx.font_size == processor.ctx.config.font.size {
*processor.ctx.font_size = config.font.size; *processor.ctx.font_size = config.font.size;
} }
@ -738,13 +737,13 @@ impl<N: Notify + OnResize> Processor<N> {
processor.ctx.terminal.dirty = true; processor.ctx.terminal.dirty = true;
} }
// Write the ref test results to the disk // Write the ref test results to the disk.
pub fn write_ref_test_results<T>(&self, terminal: &Term<T>) { pub fn write_ref_test_results<T>(&self, terminal: &Term<T>) {
if !self.config.debug.ref_test { if !self.config.debug.ref_test {
return; return;
} }
// dump grid state // Dump grid state.
let mut grid = terminal.grid().clone(); let mut grid = terminal.grid().clone();
grid.initialize_all(&Cell::default()); grid.initialize_all(&Cell::default());
grid.truncate(); grid.truncate();

View File

@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Handle input from glutin //! Handle input from glutin.
//! //!
//! Certain key combinations should send some escape sequence back to the pty. //! Certain key combinations should send some escape sequence back to the PTY.
//! In order to figure that out, state about which modifier keys are pressed //! In order to figure that out, state about which modifier keys are pressed
//! needs to be tracked. Additionally, we need a bit of a state machine to //! needs to be tracked. Additionally, we need a bit of a state machine to
//! determine what to do when a non-modifier key is pressed. //! determine what to do when a non-modifier key is pressed.
@ -50,7 +50,7 @@ use crate::event::{ClickState, Mouse};
use crate::url::{Url, Urls}; use crate::url::{Url, Urls};
use crate::window::Window; use crate::window::Window;
/// Font size change interval /// Font size change interval.
pub const FONT_SIZE_STEP: f32 = 0.5; pub const FONT_SIZE_STEP: f32 = 0.5;
/// Processes input from glutin. /// Processes input from glutin.
@ -100,7 +100,7 @@ trait Execute<T: EventListener> {
} }
impl<T, U: EventListener> Execute<U> for Binding<T> { impl<T, U: EventListener> Execute<U> for Binding<T> {
/// Execute the action associate with this binding /// Execute the action associate with this binding.
#[inline] #[inline]
fn execute<A: ActionContext<U>>(&self, ctx: &mut A) { fn execute<A: ActionContext<U>>(&self, ctx: &mut A) {
self.action.execute(ctx) self.action.execute(ctx)
@ -116,7 +116,7 @@ impl Action {
let cursor_point = ctx.terminal().vi_mode_cursor.point; let cursor_point = ctx.terminal().vi_mode_cursor.point;
ctx.toggle_selection(ty, cursor_point, Side::Left); ctx.toggle_selection(ty, cursor_point, Side::Left);
// Make sure initial selection is not empty // Make sure initial selection is not empty.
if let Some(selection) = ctx.terminal_mut().selection_mut() { if let Some(selection) = ctx.terminal_mut().selection_mut() {
selection.include_all(); selection.include_all();
} }
@ -185,7 +185,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::DecreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP * -1.), Action::DecreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP * -1.),
Action::ResetFontSize => ctx.reset_font_size(), Action::ResetFontSize => ctx.reset_font_size(),
Action::ScrollPageUp => { Action::ScrollPageUp => {
// Move vi mode cursor // Move vi mode cursor.
let term = ctx.terminal_mut(); let term = ctx.terminal_mut();
let scroll_lines = term.grid().num_lines().0 as isize; let scroll_lines = term.grid().num_lines().0 as isize;
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
@ -193,7 +193,7 @@ impl<T: EventListener> Execute<T> for Action {
ctx.scroll(Scroll::PageUp); ctx.scroll(Scroll::PageUp);
}, },
Action::ScrollPageDown => { Action::ScrollPageDown => {
// Move vi mode cursor // Move vi mode cursor.
let term = ctx.terminal_mut(); let term = ctx.terminal_mut();
let scroll_lines = -(term.grid().num_lines().0 as isize); let scroll_lines = -(term.grid().num_lines().0 as isize);
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
@ -201,7 +201,7 @@ impl<T: EventListener> Execute<T> for Action {
ctx.scroll(Scroll::PageDown); ctx.scroll(Scroll::PageDown);
}, },
Action::ScrollHalfPageUp => { Action::ScrollHalfPageUp => {
// Move vi mode cursor // Move vi mode cursor.
let term = ctx.terminal_mut(); let term = ctx.terminal_mut();
let scroll_lines = term.grid().num_lines().0 as isize / 2; let scroll_lines = term.grid().num_lines().0 as isize / 2;
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
@ -209,7 +209,7 @@ impl<T: EventListener> Execute<T> for Action {
ctx.scroll(Scroll::Lines(scroll_lines)); ctx.scroll(Scroll::Lines(scroll_lines));
}, },
Action::ScrollHalfPageDown => { Action::ScrollHalfPageDown => {
// Move vi mode cursor // Move vi mode cursor.
let term = ctx.terminal_mut(); let term = ctx.terminal_mut();
let scroll_lines = -(term.grid().num_lines().0 as isize / 2); let scroll_lines = -(term.grid().num_lines().0 as isize / 2);
term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines); term.vi_mode_cursor = term.vi_mode_cursor.scroll(term, scroll_lines);
@ -217,7 +217,7 @@ impl<T: EventListener> Execute<T> for Action {
ctx.scroll(Scroll::Lines(scroll_lines)); ctx.scroll(Scroll::Lines(scroll_lines));
}, },
Action::ScrollLineUp => { Action::ScrollLineUp => {
// Move vi mode cursor // Move vi mode cursor.
let term = ctx.terminal(); let term = ctx.terminal();
if term.grid().display_offset() != term.grid().history_size() if term.grid().display_offset() != term.grid().history_size()
&& term.vi_mode_cursor.point.line + 1 != term.grid().num_lines() && term.vi_mode_cursor.point.line + 1 != term.grid().num_lines()
@ -228,7 +228,7 @@ impl<T: EventListener> Execute<T> for Action {
ctx.scroll(Scroll::Lines(1)); ctx.scroll(Scroll::Lines(1));
}, },
Action::ScrollLineDown => { Action::ScrollLineDown => {
// Move vi mode cursor // Move vi mode cursor.
if ctx.terminal().grid().display_offset() != 0 if ctx.terminal().grid().display_offset() != 0
&& ctx.terminal().vi_mode_cursor.point.line.0 != 0 && ctx.terminal().vi_mode_cursor.point.line.0 != 0
{ {
@ -240,14 +240,14 @@ impl<T: EventListener> Execute<T> for Action {
Action::ScrollToTop => { Action::ScrollToTop => {
ctx.scroll(Scroll::Top); ctx.scroll(Scroll::Top);
// Move vi mode cursor // Move vi mode cursor.
ctx.terminal_mut().vi_mode_cursor.point.line = Line(0); ctx.terminal_mut().vi_mode_cursor.point.line = Line(0);
ctx.terminal_mut().vi_motion(ViMotion::FirstOccupied); ctx.terminal_mut().vi_motion(ViMotion::FirstOccupied);
}, },
Action::ScrollToBottom => { Action::ScrollToBottom => {
ctx.scroll(Scroll::Bottom); ctx.scroll(Scroll::Bottom);
// Move vi mode cursor // Move vi mode cursor.
let term = ctx.terminal_mut(); let term = ctx.terminal_mut();
term.vi_mode_cursor.point.line = term.grid().num_lines() - 1; term.vi_mode_cursor.point.line = term.grid().num_lines() - 1;
term.vi_motion(ViMotion::FirstOccupied); term.vi_motion(ViMotion::FirstOccupied);
@ -314,7 +314,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
let cell_changed = let cell_changed =
point.line != self.ctx.mouse().line || point.col != self.ctx.mouse().column; point.line != self.ctx.mouse().line || point.col != self.ctx.mouse().column;
// If the mouse hasn't changed cells, do nothing // If the mouse hasn't changed cells, do nothing.
if !cell_changed if !cell_changed
&& self.ctx.mouse().cell_side == cell_side && self.ctx.mouse().cell_side == cell_side
&& self.ctx.mouse().inside_grid == inside_grid && self.ctx.mouse().inside_grid == inside_grid
@ -327,10 +327,10 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.ctx.mouse_mut().line = point.line; self.ctx.mouse_mut().line = point.line;
self.ctx.mouse_mut().column = point.col; self.ctx.mouse_mut().column = point.col;
// Don't launch URLs if mouse has moved // Don't launch URLs if mouse has moved.
self.ctx.mouse_mut().block_url_launcher = true; self.ctx.mouse_mut().block_url_launcher = true;
// Update mouse state and check for URL change // Update mouse state and check for URL change.
let mouse_state = self.mouse_state(); let mouse_state = self.mouse_state();
self.update_url_state(&mouse_state); self.update_url_state(&mouse_state);
self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
@ -339,10 +339,10 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
if self.ctx.mouse().left_button_state == ElementState::Pressed if self.ctx.mouse().left_button_state == ElementState::Pressed
&& (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
{ {
// Treat motion over message bar like motion over the last line // Treat motion over message bar like motion over the last line.
let line = min(point.line, last_term_line); let line = min(point.line, last_term_line);
// Move vi mode cursor to mouse cursor position // Move vi mode cursor to mouse cursor position.
if self.ctx.terminal().mode().contains(TermMode::VI) { if self.ctx.terminal().mode().contains(TermMode::VI) {
self.ctx.terminal_mut().vi_mode_cursor.point = point; self.ctx.terminal_mut().vi_mode_cursor.point = point;
} }
@ -377,7 +377,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
let end_of_grid = size_info.width - size_info.padding_x - additional_padding; let end_of_grid = size_info.width - size_info.padding_x - additional_padding;
if cell_x > half_cell_width if cell_x > half_cell_width
// Edge case when mouse leaves the window // Edge case when mouse leaves the window.
|| x as f32 >= end_of_grid || x as f32 >= end_of_grid
{ {
Side::Right Side::Right
@ -432,7 +432,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
} }
fn mouse_report(&mut self, button: u8, state: ElementState) { fn mouse_report(&mut self, button: u8, state: ElementState) {
// Calculate modifiers value // Calculate modifiers value.
let mut mods = 0; let mut mods = 0;
let modifiers = self.ctx.modifiers(); let modifiers = self.ctx.modifiers();
if modifiers.shift() { if modifiers.shift() {
@ -445,7 +445,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
mods += 16; mods += 16;
} }
// Report mouse events // Report mouse events.
if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) { if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
self.sgr_mouse_report(button + mods, state); self.sgr_mouse_report(button + mods, state);
} else if let ElementState::Released = state { } else if let ElementState::Released = state {
@ -456,7 +456,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
} }
fn on_mouse_press(&mut self, button: MouseButton) { fn on_mouse_press(&mut self, button: MouseButton) {
// Handle mouse mode // Handle mouse mode.
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
self.ctx.mouse_mut().click_state = ClickState::None; self.ctx.mouse_mut().click_state = ClickState::None;
@ -464,7 +464,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
MouseButton::Left => 0, MouseButton::Left => 0,
MouseButton::Middle => 1, MouseButton::Middle => 1,
MouseButton::Right => 2, MouseButton::Right => 2,
// Can't properly report more than three buttons. // Can't properly report more than three buttons..
MouseButton::Other(_) => return, MouseButton::Other(_) => return,
}; };
@ -472,19 +472,19 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
} else if button == MouseButton::Left { } else if button == MouseButton::Left {
self.on_left_click(); self.on_left_click();
} else { } else {
// Do nothing when using buttons other than LMB // Do nothing when using buttons other than LMB.
self.ctx.mouse_mut().click_state = ClickState::None; self.ctx.mouse_mut().click_state = ClickState::None;
} }
} }
/// Handle left click selection and vi mode cursor movement. /// Handle left click selection and vi mode cursor movement.
fn on_left_click(&mut self) { fn on_left_click(&mut self) {
// Calculate time since the last click to handle double/triple clicks in normal mode // Calculate time since the last click to handle double/triple clicks in normal mode.
let now = Instant::now(); let now = Instant::now();
let elapsed = now - self.ctx.mouse().last_click_timestamp; let elapsed = now - self.ctx.mouse().last_click_timestamp;
self.ctx.mouse_mut().last_click_timestamp = now; self.ctx.mouse_mut().last_click_timestamp = now;
// Load mouse point, treating message bar and padding as closest cell // Load mouse point, treating message bar and padding as closest cell.
let mouse = self.ctx.mouse(); let mouse = self.ctx.mouse();
let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y); let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y);
point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1); point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1);
@ -507,12 +507,12 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
ClickState::TripleClick ClickState::TripleClick
} }
_ => { _ => {
// Don't launch URLs if this click cleared the selection // Don't launch URLs if this click cleared the selection.
self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty(); self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty();
self.ctx.clear_selection(); self.ctx.clear_selection();
// Start new empty selection // Start new empty selection.
if self.ctx.modifiers().ctrl() { if self.ctx.modifiers().ctrl() {
self.ctx.start_selection(SelectionType::Block, point, side); self.ctx.start_selection(SelectionType::Block, point, side);
} else { } else {
@ -523,9 +523,9 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
}, },
}; };
// Move vi mode cursor to mouse position // Move vi mode cursor to mouse position.
if self.ctx.terminal().mode().contains(TermMode::VI) { if self.ctx.terminal().mode().contains(TermMode::VI) {
// Update Vi mode cursor position on click // Update Vi mode cursor position on click.
self.ctx.terminal_mut().vi_mode_cursor.point = point; self.ctx.terminal_mut().vi_mode_cursor.point = point;
} }
} }
@ -557,7 +557,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
MouseScrollDelta::PixelDelta(lpos) => { MouseScrollDelta::PixelDelta(lpos) => {
match phase { match phase {
TouchPhase::Started => { TouchPhase::Started => {
// Reset offset to zero // Reset offset to zero.
self.ctx.mouse_mut().scroll_px = 0.; self.ctx.mouse_mut().scroll_px = 0.;
}, },
TouchPhase::Moved => { TouchPhase::Moved => {
@ -613,18 +613,18 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
let lines = self.ctx.mouse().scroll_px / height; let lines = self.ctx.mouse().scroll_px / height;
// Store absolute position of vi mode cursor // Store absolute position of vi mode cursor.
let term = self.ctx.terminal(); let term = self.ctx.terminal();
let absolute = term.visible_to_buffer(term.vi_mode_cursor.point); let absolute = term.visible_to_buffer(term.vi_mode_cursor.point);
self.ctx.scroll(Scroll::Lines(lines as isize)); self.ctx.scroll(Scroll::Lines(lines as isize));
// Try to restore vi mode cursor position, to keep it above its previous content // Try to restore vi mode cursor position, to keep it above its previous content.
let term = self.ctx.terminal_mut(); let term = self.ctx.terminal_mut();
term.vi_mode_cursor.point = term.grid().clamp_buffer_to_visible(absolute); term.vi_mode_cursor.point = term.grid().clamp_buffer_to_visible(absolute);
term.vi_mode_cursor.point.col = absolute.col; term.vi_mode_cursor.point.col = absolute.col;
// Update selection // Update selection.
if term.mode().contains(TermMode::VI) { if term.mode().contains(TermMode::VI) {
let point = term.vi_mode_cursor.point; let point = term.vi_mode_cursor.point;
if !self.ctx.selection_is_empty() { if !self.ctx.selection_is_empty() {
@ -653,12 +653,12 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
_ => (), _ => (),
} }
// Skip normal mouse events if the message bar has been clicked // Skip normal mouse events if the message bar has been clicked.
if self.message_close_at_cursor() && state == ElementState::Pressed { if self.message_close_at_cursor() && state == ElementState::Pressed {
self.ctx.clear_selection(); self.ctx.clear_selection();
self.ctx.pop_message(); self.ctx.pop_message();
// Reset cursor when message bar height changed or all messages are gone // Reset cursor when message bar height changed or all messages are gone.
let size = self.ctx.size_info(); let size = self.ctx.size_info();
let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0; let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0;
let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0); let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0);
@ -702,7 +702,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
pub fn modifiers_input(&mut self, modifiers: ModifiersState) { pub fn modifiers_input(&mut self, modifiers: ModifiersState) {
*self.ctx.modifiers() = modifiers; *self.ctx.modifiers() = modifiers;
// Update mouse state and check for URL change // Update mouse state and check for URL change.
let mouse_state = self.mouse_state(); let mouse_state = self.mouse_state();
self.update_url_state(&mouse_state); self.update_url_state(&mouse_state);
self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
@ -762,16 +762,16 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
}; };
if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &key) { if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &key) {
// Binding was triggered; run the action // Binding was triggered; run the action.
let binding = binding.clone(); let binding = binding.clone();
binding.execute(&mut self.ctx); binding.execute(&mut self.ctx);
// Don't suppress when there has been a `ReceiveChar` action // Don't suppress when there has been a `ReceiveChar` action.
*suppress_chars.get_or_insert(true) &= binding.action != Action::ReceiveChar; *suppress_chars.get_or_insert(true) &= binding.action != Action::ReceiveChar;
} }
} }
// Don't suppress char if no bindings were triggered // Don't suppress char if no bindings were triggered.
*self.ctx.suppress_chars() = suppress_chars.unwrap_or(false); *self.ctx.suppress_chars() = suppress_chars.unwrap_or(false);
} }
@ -787,7 +787,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
for i in 0..self.ctx.config().ui_config.mouse_bindings.len() { for i in 0..self.ctx.config().ui_config.mouse_bindings.len() {
let mut binding = self.ctx.config().ui_config.mouse_bindings[i].clone(); let mut binding = self.ctx.config().ui_config.mouse_bindings[i].clone();
// Require shift for all modifiers when mouse mode is active // Require shift for all modifiers when mouse mode is active.
if mouse_mode { if mouse_mode {
binding.mods |= ModifiersState::SHIFT; binding.mods |= ModifiersState::SHIFT;
} }
@ -803,7 +803,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.ctx.mouse().line >= self.ctx.terminal().grid().num_lines() self.ctx.mouse().line >= self.ctx.terminal().grid().num_lines()
} }
/// Whether the point is over the message bar's close button /// Whether the point is over the message bar's close button.
fn message_close_at_cursor(&self) -> bool { fn message_close_at_cursor(&self) -> bool {
let mouse = self.ctx.mouse(); let mouse = self.ctx.mouse();
mouse.inside_grid mouse.inside_grid
@ -833,7 +833,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
/// Location of the mouse cursor. /// Location of the mouse cursor.
fn mouse_state(&mut self) -> MouseState { fn mouse_state(&mut self) -> MouseState {
// Check message bar before URL to ignore URLs in the message bar // Check message bar before URL to ignore URLs in the message bar.
if self.message_close_at_cursor() { if self.message_close_at_cursor() {
return MouseState::MessageBarButton; return MouseState::MessageBarButton;
} else if self.message_at_cursor() { } else if self.message_at_cursor() {
@ -842,7 +842,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
let mouse_mode = self.ctx.mouse_mode(); let mouse_mode = self.ctx.mouse_mode();
// Check for URL at mouse cursor // Check for URL at mouse cursor.
let mods = *self.ctx.modifiers(); let mods = *self.ctx.modifiers();
let highlighted_url = self.ctx.urls().highlighted( let highlighted_url = self.ctx.urls().highlighted(
self.ctx.config(), self.ctx.config(),
@ -856,7 +856,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
return MouseState::Url(url); return MouseState::Url(url);
} }
// Check mouse mode if location is not special // Check mouse mode if location is not special.
if !self.ctx.modifiers().shift() && mouse_mode { if !self.ctx.modifiers().shift() && mouse_mode {
MouseState::Mouse MouseState::Mouse
} else { } else {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Logging for alacritty. //! Logging for Alacritty.
//! //!
//! The main executable is supposed to call `initialize()` exactly once during //! The main executable is supposed to call `initialize()` exactly once during
//! startup. All logging messages are written to stdout, given that their //! startup. All logging messages are written to stdout, given that their
@ -151,19 +151,19 @@ impl OnDemandLogFile {
let mut path = env::temp_dir(); let mut path = env::temp_dir();
path.push(format!("Alacritty-{}.log", process::id())); path.push(format!("Alacritty-{}.log", process::id()));
// Set log path as an environment variable // Set log path as an environment variable.
env::set_var(ALACRITTY_LOG_ENV, path.as_os_str()); env::set_var(ALACRITTY_LOG_ENV, path.as_os_str());
OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) } OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) }
} }
fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> { fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> {
// Allow to recreate the file if it has been deleted at runtime // Allow to recreate the file if it has been deleted at runtime.
if self.file.is_some() && !self.path.as_path().exists() { if self.file.is_some() && !self.path.as_path().exists() {
self.file = None; self.file = None;
} }
// Create the file if it doesn't exist yet // Create the file if it doesn't exist yet.
if self.file.is_none() { if self.file.is_none() {
let file = OpenOptions::new().append(true).create(true).open(&self.path); let file = OpenOptions::new().append(true).create(true).open(&self.path);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Alacritty - The GPU Enhanced Terminal //! Alacritty - The GPU Enhanced Terminal.
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))]
#![cfg_attr(all(test, feature = "bench"), feature(test))] #![cfg_attr(all(test, feature = "bench"), feature(test))]
@ -83,41 +83,41 @@ fn main() {
AttachConsole(ATTACH_PARENT_PROCESS); AttachConsole(ATTACH_PARENT_PROCESS);
} }
// Load command line options // Load command line options.
let options = Options::new(); let options = Options::new();
// Setup glutin event loop // Setup glutin event loop.
let window_event_loop = GlutinEventLoop::<Event>::with_user_event(); let window_event_loop = GlutinEventLoop::<Event>::with_user_event();
// Initialize the logger as soon as possible as to capture output from other subsystems // Initialize the logger as soon as possible as to capture output from other subsystems.
let log_file = logging::initialize(&options, window_event_loop.create_proxy()) let log_file = logging::initialize(&options, window_event_loop.create_proxy())
.expect("Unable to initialize logger"); .expect("Unable to initialize logger");
// Load configuration file // Load configuration file.
let config_path = options.config_path().or_else(config::installed_config); let config_path = options.config_path().or_else(config::installed_config);
let config = config_path.map(config::load_from).unwrap_or_else(Config::default); let config = config_path.map(config::load_from).unwrap_or_else(Config::default);
let config = options.into_config(config); let config = options.into_config(config);
// Update the log level from config // Update the log level from config.
log::set_max_level(config.debug.log_level); log::set_max_level(config.debug.log_level);
// Switch to home directory // Switch to home directory.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
env::set_current_dir(dirs::home_dir().unwrap()).unwrap(); env::set_current_dir(dirs::home_dir().unwrap()).unwrap();
// Set locale // Set locale.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
locale::set_locale_environment(); locale::set_locale_environment();
// Store if log file should be deleted before moving config // Store if log file should be deleted before moving config.
let persistent_logging = config.persistent_logging(); let persistent_logging = config.persistent_logging();
// Run alacritty // Run Alacritty.
if let Err(err) = run(window_event_loop, config) { if let Err(err) = run(window_event_loop, config) {
error!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", err); error!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", err);
std::process::exit(1); std::process::exit(1);
} }
// Clean up logfile // Clean up logfile.
if let Some(log_file) = log_file { if let Some(log_file) = log_file {
if !persistent_logging && fs::remove_file(&log_file).is_ok() { if !persistent_logging && fs::remove_file(&log_file).is_ok() {
let _ = writeln!(io::stdout(), "Deleted log file at \"{}\"", log_file.display()); let _ = writeln!(io::stdout(), "Deleted log file at \"{}\"", log_file.display());
@ -125,9 +125,9 @@ fn main() {
} }
} }
/// Run Alacritty /// Run Alacritty.
/// ///
/// Creates a window, the terminal state, pty, I/O event loop, input processor, /// Creates a window, the terminal state, PTY, I/O event loop, input processor,
/// config change monitor, and runs the main display loop. /// config change monitor, and runs the main display loop.
fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), Box<dyn Error>> { fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), Box<dyn Error>> {
info!("Welcome to Alacritty"); info!("Welcome to Alacritty");
@ -137,25 +137,25 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(),
None => info!("No configuration file found"), None => info!("No configuration file found"),
} }
// Set environment variables // Set environment variables.
tty::setup_env(&config); tty::setup_env(&config);
let event_proxy = EventProxy::new(window_event_loop.create_proxy()); let event_proxy = EventProxy::new(window_event_loop.create_proxy());
// Create a display // Create a display.
// //
// The display manages a window and can draw the terminal. // The display manages a window and can draw the terminal.
let display = Display::new(&config, &window_event_loop)?; let display = Display::new(&config, &window_event_loop)?;
info!("PTY Dimensions: {:?} x {:?}", display.size_info.lines(), display.size_info.cols()); info!("PTY dimensions: {:?} x {:?}", display.size_info.lines(), display.size_info.cols());
// Create new native clipboard // Create new native clipboard.
#[cfg(not(any(target_os = "macos", windows)))] #[cfg(not(any(target_os = "macos", windows)))]
let clipboard = Clipboard::new(display.window.wayland_display()); let clipboard = Clipboard::new(display.window.wayland_display());
#[cfg(any(target_os = "macos", windows))] #[cfg(any(target_os = "macos", windows))]
let clipboard = Clipboard::new(); let clipboard = Clipboard::new();
// Create the terminal // Create the terminal.
// //
// This object contains all of the state about what's being displayed. It's // This object contains all of the state about what's being displayed. It's
// wrapped in a clonable mutex since both the I/O loop and display need to // wrapped in a clonable mutex since both the I/O loop and display need to
@ -163,9 +163,9 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(),
let terminal = Term::new(&config, &display.size_info, clipboard, event_proxy.clone()); let terminal = Term::new(&config, &display.size_info, clipboard, event_proxy.clone());
let terminal = Arc::new(FairMutex::new(terminal)); let terminal = Arc::new(FairMutex::new(terminal));
// Create the pty // Create the PTY.
// //
// The pty forks a process to run the shell on the slave side of the // The PTY forks a process to run the shell on the slave side of the
// pseudoterminal. A file descriptor for the master side is retained for // pseudoterminal. A file descriptor for the master side is retained for
// reading/writing to the shell. // reading/writing to the shell.
#[cfg(not(any(target_os = "macos", windows)))] #[cfg(not(any(target_os = "macos", windows)))]
@ -173,9 +173,9 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(),
#[cfg(any(target_os = "macos", windows))] #[cfg(any(target_os = "macos", windows))]
let pty = tty::new(&config, &display.size_info, None); let pty = tty::new(&config, &display.size_info, None);
// Create the pseudoterminal I/O loop // Create the pseudoterminal I/O loop.
// //
// pty I/O is ran on another thread as to not occupy cycles used by the // PTY I/O is ran on another thread as to not occupy cycles used by the
// renderer and input processing. Note that access to the terminal state is // renderer and input processing. Note that access to the terminal state is
// synchronized since the I/O loop updates the state, and the display // synchronized since the I/O loop updates the state, and the display
// consumes it periodically. // consumes it periodically.
@ -185,7 +185,7 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(),
// to be sent to the pty loop and ultimately written to the pty. // to be sent to the pty loop and ultimately written to the pty.
let loop_tx = event_loop.channel(); let loop_tx = event_loop.channel();
// Create a config monitor when config was loaded from path // Create a config monitor when config was loaded from path.
// //
// The monitor watches the config file for changes and reloads it. Pending // The monitor watches the config file for changes and reloads it. Pending
// config changes are processed in the main loop. // config changes are processed in the main loop.
@ -193,42 +193,42 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(),
config.config_path.as_ref().map(|path| Monitor::new(path, event_proxy.clone())); config.config_path.as_ref().map(|path| Monitor::new(path, event_proxy.clone()));
} }
// Setup storage for message UI // Setup storage for message UI.
let message_buffer = MessageBuffer::new(); let message_buffer = MessageBuffer::new();
// Event processor // Event processor.
let mut processor = let mut processor =
Processor::new(event_loop::Notifier(loop_tx.clone()), message_buffer, config, display); Processor::new(event_loop::Notifier(loop_tx.clone()), message_buffer, config, display);
// Kick off the I/O thread // Kick off the I/O thread.
let io_thread = event_loop.spawn(); let io_thread = event_loop.spawn();
info!("Initialisation complete"); info!("Initialisation complete");
// Start event loop and block until shutdown // Start event loop and block until shutdown.
processor.run(terminal, window_event_loop); processor.run(terminal, window_event_loop);
// This explicit drop is needed for Windows, ConPTY backend. Otherwise a deadlock can occur. // This explicit drop is needed for Windows, ConPTY backend. Otherwise a deadlock can occur.
// The cause: // The cause:
// - Drop for Conpty will deadlock if the conout pipe has already been dropped. // - Drop for ConPTY will deadlock if the conout pipe has already been dropped.
// - The conout pipe is dropped when the io_thread is joined below (io_thread owns pty). // - The conout pipe is dropped when the io_thread is joined below (io_thread owns PTY).
// - Conpty is dropped when the last of processor and io_thread are dropped, because both of // - ConPTY is dropped when the last of processor and io_thread are dropped, because both of
// them own an Arc<Conpty>. // them own an Arc<ConPTY>.
// //
// The fix is to ensure that processor is dropped first. That way, when io_thread (i.e. pty) // The fix is to ensure that processor is dropped first. That way, when io_thread (i.e. PTY)
// is dropped, it can ensure Conpty is dropped before the conout pipe in the pty drop order. // is dropped, it can ensure ConPTY is dropped before the conout pipe in the PTY drop order.
// //
// FIXME: Change PTY API to enforce the correct drop order with the typesystem. // FIXME: Change PTY API to enforce the correct drop order with the typesystem.
drop(processor); drop(processor);
// Shutdown PTY parser event loop // Shutdown PTY parser event loop.
loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to pty event loop"); loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to PTY event loop");
io_thread.join().expect("join io thread"); io_thread.join().expect("join io thread");
// FIXME patch notify library to have a shutdown method // FIXME patch notify library to have a shutdown method.
// config_reloader.join().ok(); // config_reloader.join().ok();
// Without explicitly detaching the console cmd won't redraw it's prompt // Without explicitly detaching the console cmd won't redraw it's prompt.
#[cfg(windows)] #[cfg(windows)]
unsafe { unsafe {
FreeConsole(); FreeConsole();

View File

@ -42,13 +42,13 @@ use std::fmt::{self, Display, Formatter};
pub mod rects; pub mod rects;
// Shader paths for live reload // Shader paths for live reload.
static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"); static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl");
static TEXT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl"); static TEXT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl");
static RECT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl"); static RECT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl");
static RECT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl"); static RECT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl");
// Shader source which is used when live-shader-reload feature is disable // Shader source which is used when live-shader-reload feature is disable.
static TEXT_SHADER_F: &str = static TEXT_SHADER_F: &str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl")); include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"));
static TEXT_SHADER_V: &str = static TEXT_SHADER_V: &str =
@ -58,12 +58,12 @@ static RECT_SHADER_F: &str =
static RECT_SHADER_V: &str = static RECT_SHADER_V: &str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl")); include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl"));
/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory /// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
pub trait LoadGlyph { pub trait LoadGlyph {
/// Load the rasterized glyph into GPU memory /// Load the rasterized glyph into GPU memory.
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph; fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
/// Clear any state accumulated from previous loaded glyphs /// Clear any state accumulated from previous loaded glyphs.
/// ///
/// This can, for instance, be used to reset the texture Atlas. /// This can, for instance, be used to reset the texture Atlas.
fn clear(&mut self); fn clear(&mut self);
@ -102,34 +102,34 @@ impl From<ShaderCreationError> for Error {
} }
} }
/// Text drawing program /// Text drawing program.
/// ///
/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". /// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
#[derive(Debug)] #[derive(Debug)]
pub struct TextShaderProgram { pub struct TextShaderProgram {
// Program id /// Program id.
id: GLuint, id: GLuint,
/// projection scale and offset uniform /// Projection scale and offset uniform.
u_projection: GLint, u_projection: GLint,
/// Cell dimensions (pixels) /// Cell dimensions (pixels).
u_cell_dim: GLint, u_cell_dim: GLint,
/// Background pass flag /// Background pass flag.
/// ///
/// Rendering is split into two passes; 1 for backgrounds, and one for text /// Rendering is split into two passes; 1 for backgrounds, and one for text.
u_background: GLint, u_background: GLint,
} }
/// Rectangle drawing program /// Rectangle drawing program.
/// ///
/// Uniforms are prefixed with "u" /// Uniforms are prefixed with "u".
#[derive(Debug)] #[derive(Debug)]
pub struct RectShaderProgram { pub struct RectShaderProgram {
// Program id /// Program id.
id: GLuint, id: GLuint,
/// Rectangle color /// Rectangle color.
u_color: GLint, u_color: GLint,
} }
@ -147,38 +147,39 @@ pub struct Glyph {
uv_height: f32, uv_height: f32,
} }
/// Naïve glyph cache /// Naïve glyph cache.
/// ///
/// Currently only keyed by `char`, and thus not possible to hold different /// Currently only keyed by `char`, and thus not possible to hold different
/// representations of the same code point. /// representations of the same code point.
pub struct GlyphCache { pub struct GlyphCache {
/// Cache of buffered glyphs /// Cache of buffered glyphs.
cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>, cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
/// Cache of buffered cursor glyphs /// Cache of buffered cursor glyphs.
cursor_cache: HashMap<CursorKey, Glyph, BuildHasherDefault<FnvHasher>>, cursor_cache: HashMap<CursorKey, Glyph, BuildHasherDefault<FnvHasher>>,
/// Rasterizer for loading new glyphs /// Rasterizer for loading new glyphs.
rasterizer: Rasterizer, rasterizer: Rasterizer,
/// regular font /// Regular font.
font_key: FontKey, font_key: FontKey,
/// bold font /// Bold font.
bold_key: FontKey, bold_key: FontKey,
/// italic font /// Italic font.
italic_key: FontKey, italic_key: FontKey,
/// bold italic font /// Bold italic font.
bold_italic_key: FontKey, bold_italic_key: FontKey,
/// font size /// Font size.
font_size: font::Size, font_size: font::Size,
/// glyph offset /// Glyph offset.
glyph_offset: Delta<i8>, glyph_offset: Delta<i8>,
/// Font metrics.
metrics: font::Metrics, metrics: font::Metrics,
} }
@ -225,20 +226,20 @@ impl GlyphCache {
} }
} }
/// Computes font keys for (Regular, Bold, Italic, Bold Italic) /// Computes font keys for (Regular, Bold, Italic, Bold Italic).
fn compute_font_keys( fn compute_font_keys(
font: &config::Font, font: &config::Font,
rasterizer: &mut Rasterizer, rasterizer: &mut Rasterizer,
) -> Result<(FontKey, FontKey, FontKey, FontKey), font::Error> { ) -> Result<(FontKey, FontKey, FontKey, FontKey), font::Error> {
let size = font.size; let size = font.size;
// Load regular font // Load regular font.
let regular_desc = let regular_desc =
Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal);
let regular = rasterizer.load_font(&regular_desc, size)?; let regular = rasterizer.load_font(&regular_desc, size)?;
// helper to load a description if it is not the regular_desc // Helper to load a description if it is not the `regular_desc`.
let mut load_or_regular = |desc: FontDesc| { let mut load_or_regular = |desc: FontDesc| {
if desc == regular_desc { if desc == regular_desc {
regular regular
@ -247,18 +248,18 @@ impl GlyphCache {
} }
}; };
// Load bold font // Load bold font.
let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold); let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold);
let bold = load_or_regular(bold_desc); let bold = load_or_regular(bold_desc);
// Load italic font // Load italic font.
let italic_desc = let italic_desc =
Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal); Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal);
let italic = load_or_regular(italic_desc); let italic = load_or_regular(italic_desc);
// Load bold italic font // Load bold italic font.
let bold_italic_desc = let bold_italic_desc =
Self::make_desc(&font.bold_italic(), font::Slant::Italic, font::Weight::Bold); Self::make_desc(&font.bold_italic(), font::Slant::Italic, font::Weight::Bold);
@ -314,10 +315,10 @@ impl GlyphCache {
dpr: f64, dpr: f64,
loader: &mut L, loader: &mut L,
) -> Result<(), font::Error> { ) -> Result<(), font::Error> {
// Update dpi scaling // Update dpi scaling.
self.rasterizer.update_dpr(dpr as f32); self.rasterizer.update_dpr(dpr as f32);
// Recompute font keys // Recompute font keys.
let (regular, bold, italic, bold_italic) = let (regular, bold, italic, bold_italic) =
Self::compute_font_keys(&font, &mut self.rasterizer)?; Self::compute_font_keys(&font, &mut self.rasterizer)?;
@ -350,7 +351,7 @@ impl GlyphCache {
self.load_glyphs_for_font(self.bold_italic_key, loader); self.load_glyphs_for_font(self.bold_italic_key, loader);
} }
// Calculate font metrics without access to a glyph cache /// Calculate font metrics without access to a glyph cache.
pub fn static_metrics(font: Font, dpr: f64) -> Result<font::Metrics, font::Error> { pub fn static_metrics(font: Font, dpr: f64) -> Result<font::Metrics, font::Error> {
let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?; let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?;
let regular_desc = let regular_desc =
@ -379,7 +380,7 @@ impl GlyphCache {
let padding_x = f64::from(config.window.padding.x) * dpr; let padding_x = f64::from(config.window.padding.x) * dpr;
let padding_y = f64::from(config.window.padding.y) * dpr; let padding_y = f64::from(config.window.padding.y) * dpr;
// Calculate new size based on cols/lines specified in config // Calculate new size based on cols/lines specified in config.
let grid_width = cell_width as u32 * dimensions.columns_u32(); let grid_width = cell_width as u32 * dimensions.columns_u32();
let grid_height = cell_height as u32 * dimensions.lines_u32(); let grid_height = cell_height as u32 * dimensions.lines_u32();
@ -393,26 +394,26 @@ impl GlyphCache {
#[derive(Debug)] #[derive(Debug)]
#[repr(C)] #[repr(C)]
struct InstanceData { struct InstanceData {
// coords // Coords.
col: f32, col: f32,
row: f32, row: f32,
// glyph offset // Glyph offset.
left: f32, left: f32,
top: f32, top: f32,
// glyph scale // Glyph scale.
width: f32, width: f32,
height: f32, height: f32,
// uv offset // uv offset.
uv_left: f32, uv_left: f32,
uv_bot: f32, uv_bot: f32,
// uv scale // uv scale.
uv_width: f32, uv_width: f32,
uv_height: f32, uv_height: f32,
// color // Color.
r: f32, r: f32,
g: f32, g: f32,
b: f32, b: f32,
// background color // Background color.
bg_r: f32, bg_r: f32,
bg_g: f32, bg_g: f32,
bg_b: f32, bg_b: f32,
@ -556,7 +557,7 @@ impl QuadRenderer {
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
gl::Enable(gl::MULTISAMPLE); gl::Enable(gl::MULTISAMPLE);
// Disable depth mask, as the renderer never uses depth tests // Disable depth mask, as the renderer never uses depth tests.
gl::DepthMask(gl::FALSE); gl::DepthMask(gl::FALSE);
gl::GenVertexArrays(1, &mut vao); gl::GenVertexArrays(1, &mut vao);
@ -587,7 +588,7 @@ impl QuadRenderer {
ptr::null(), ptr::null(),
gl::STREAM_DRAW, gl::STREAM_DRAW,
); );
// coords // Coords.
gl::VertexAttribPointer( gl::VertexAttribPointer(
0, 0,
2, 2,
@ -598,7 +599,7 @@ impl QuadRenderer {
); );
gl::EnableVertexAttribArray(0); gl::EnableVertexAttribArray(0);
gl::VertexAttribDivisor(0, 1); gl::VertexAttribDivisor(0, 1);
// glyphoffset // Glyph offset.
gl::VertexAttribPointer( gl::VertexAttribPointer(
1, 1,
4, 4,
@ -609,7 +610,7 @@ impl QuadRenderer {
); );
gl::EnableVertexAttribArray(1); gl::EnableVertexAttribArray(1);
gl::VertexAttribDivisor(1, 1); gl::VertexAttribDivisor(1, 1);
// uv // uv.
gl::VertexAttribPointer( gl::VertexAttribPointer(
2, 2,
4, 4,
@ -620,7 +621,7 @@ impl QuadRenderer {
); );
gl::EnableVertexAttribArray(2); gl::EnableVertexAttribArray(2);
gl::VertexAttribDivisor(2, 1); gl::VertexAttribDivisor(2, 1);
// color // Color.
gl::VertexAttribPointer( gl::VertexAttribPointer(
3, 3,
3, 3,
@ -631,7 +632,7 @@ impl QuadRenderer {
); );
gl::EnableVertexAttribArray(3); gl::EnableVertexAttribArray(3);
gl::VertexAttribDivisor(3, 1); gl::VertexAttribDivisor(3, 1);
// color // Background color.
gl::VertexAttribPointer( gl::VertexAttribPointer(
4, 4,
4, 4,
@ -643,7 +644,7 @@ impl QuadRenderer {
gl::EnableVertexAttribArray(4); gl::EnableVertexAttribArray(4);
gl::VertexAttribDivisor(4, 1); gl::VertexAttribDivisor(4, 1);
// Rectangle setup // Rectangle setup.
gl::GenVertexArrays(1, &mut rect_vao); gl::GenVertexArrays(1, &mut rect_vao);
gl::GenBuffers(1, &mut rect_vbo); gl::GenBuffers(1, &mut rect_vbo);
gl::GenBuffers(1, &mut rect_ebo); gl::GenBuffers(1, &mut rect_ebo);
@ -657,7 +658,7 @@ impl QuadRenderer {
gl::STATIC_DRAW, gl::STATIC_DRAW,
); );
// Cleanup // Cleanup.
gl::BindVertexArray(0); gl::BindVertexArray(0);
gl::BindBuffer(gl::ARRAY_BUFFER, 0); gl::BindBuffer(gl::ARRAY_BUFFER, 0);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
@ -715,24 +716,24 @@ impl QuadRenderer {
Ok(renderer) Ok(renderer)
} }
// Draw all rectangles simultaneously to prevent excessive program swaps /// Draw all rectangles simultaneously to prevent excessive program swaps.
pub fn draw_rects(&mut self, props: &term::SizeInfo, rects: Vec<RenderRect>) { pub fn draw_rects(&mut self, props: &term::SizeInfo, rects: Vec<RenderRect>) {
// Swap to rectangle rendering program // Swap to rectangle rendering program.
unsafe { unsafe {
// Swap program // Swap program.
gl::UseProgram(self.rect_program.id); gl::UseProgram(self.rect_program.id);
// Remove padding from viewport // Remove padding from viewport.
gl::Viewport(0, 0, props.width as i32, props.height as i32); gl::Viewport(0, 0, props.width as i32, props.height as i32);
// Change blending strategy // Change blending strategy.
gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE); gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE);
// Setup data and buffers // Setup data and buffers.
gl::BindVertexArray(self.rect_vao); gl::BindVertexArray(self.rect_vao);
gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo); gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo);
// Position // Position.
gl::VertexAttribPointer( gl::VertexAttribPointer(
0, 0,
2, 2,
@ -744,17 +745,17 @@ impl QuadRenderer {
gl::EnableVertexAttribArray(0); gl::EnableVertexAttribArray(0);
} }
// Draw all the rects // Draw all the rects.
for rect in rects { for rect in rects {
self.render_rect(&rect, props); self.render_rect(&rect, props);
} }
// Deactivate rectangle program again // Deactivate rectangle program again.
unsafe { unsafe {
// Reset blending strategy // Reset blending strategy.
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
// Reset data and buffers // Reset data and buffers.
gl::BindBuffer(gl::ARRAY_BUFFER, 0); gl::BindBuffer(gl::ARRAY_BUFFER, 0);
gl::BindVertexArray(0); gl::BindVertexArray(0);
@ -764,7 +765,7 @@ impl QuadRenderer {
let height = props.height as i32; let height = props.height as i32;
gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y);
// Disable program // Disable program.
gl::UseProgram(0); gl::UseProgram(0);
} }
} }
@ -773,7 +774,7 @@ impl QuadRenderer {
where where
F: FnOnce(RenderApi<'_, C>) -> T, F: FnOnce(RenderApi<'_, C>) -> T,
{ {
// Flush message queue // Flush message queue.
if let Ok(Msg::ShaderReload) = self.rx.try_recv() { if let Ok(Msg::ShaderReload) = self.rx.try_recv() {
self.reload_shaders(props); self.reload_shaders(props);
} }
@ -855,7 +856,7 @@ impl QuadRenderer {
} }
pub fn resize(&mut self, size: &SizeInfo) { pub fn resize(&mut self, size: &SizeInfo) {
// viewport // Viewport.
unsafe { unsafe {
gl::Viewport( gl::Viewport(
size.padding_x as i32, size.padding_x as i32,
@ -864,23 +865,23 @@ impl QuadRenderer {
size.height as i32 - 2 * size.padding_y as i32, size.height as i32 - 2 * size.padding_y as i32,
); );
// update projection // Update projection.
gl::UseProgram(self.program.id); gl::UseProgram(self.program.id);
self.program.update_projection(size.width, size.height, size.padding_x, size.padding_y); self.program.update_projection(size.width, size.height, size.padding_x, size.padding_y);
gl::UseProgram(0); gl::UseProgram(0);
} }
} }
// Render a rectangle /// Render a rectangle.
// ///
// This requires the rectangle program to be activated /// This requires the rectangle program to be activated.
fn render_rect(&mut self, rect: &RenderRect, size: &term::SizeInfo) { fn render_rect(&mut self, rect: &RenderRect, size: &term::SizeInfo) {
// Do nothing when alpha is fully transparent // Do nothing when alpha is fully transparent.
if rect.alpha == 0. { if rect.alpha == 0. {
return; return;
} }
// Calculate rectangle position // Calculate rectangle position.
let center_x = size.width / 2.; let center_x = size.width / 2.;
let center_y = size.height / 2.; let center_y = size.height / 2.;
let x = (rect.x - center_x) / center_x; let x = (rect.x - center_x) / center_x;
@ -889,10 +890,10 @@ impl QuadRenderer {
let height = rect.height / center_y; let height = rect.height / center_y;
unsafe { unsafe {
// Setup vertices // Setup vertices.
let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y]; let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y];
// Load vertex data into array buffer // Load vertex data into array buffer.
gl::BufferData( gl::BufferData(
gl::ARRAY_BUFFER, gl::ARRAY_BUFFER,
(size_of::<f32>() * vertices.len()) as _, (size_of::<f32>() * vertices.len()) as _,
@ -900,10 +901,10 @@ impl QuadRenderer {
gl::STATIC_DRAW, gl::STATIC_DRAW,
); );
// Color // Color.
self.rect_program.set_color(rect.color, rect.alpha); self.rect_program.set_color(rect.color, rect.alpha);
// Draw the rectangle // Draw the rectangle.
gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null()); gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null());
} }
} }
@ -939,7 +940,7 @@ impl<'a, C> RenderApi<'a, C> {
); );
} }
// Bind texture if necessary // Bind texture if necessary.
if *self.active_tex != self.batch.tex { if *self.active_tex != self.batch.tex {
unsafe { unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.batch.tex); gl::BindTexture(gl::TEXTURE_2D, self.batch.tex);
@ -1006,14 +1007,14 @@ impl<'a, C> RenderApi<'a, C> {
#[inline] #[inline]
fn add_render_item(&mut self, cell: RenderableCell, glyph: &Glyph) { fn add_render_item(&mut self, cell: RenderableCell, glyph: &Glyph) {
// Flush batch if tex changing // Flush batch if tex changing.
if !self.batch.is_empty() && self.batch.tex != glyph.tex_id { if !self.batch.is_empty() && self.batch.tex != glyph.tex_id {
self.render_batch(); self.render_batch();
} }
self.batch.add_item(cell, glyph); self.batch.add_item(cell, glyph);
// Render batch and clear if it's full // Render batch and clear if it's full.
if self.batch.full() { if self.batch.full() {
self.render_batch(); self.render_batch();
} }
@ -1022,7 +1023,7 @@ impl<'a, C> RenderApi<'a, C> {
pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) { pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) {
let chars = match cell.inner { let chars = match cell.inner {
RenderableCellContent::Cursor(cursor_key) => { RenderableCellContent::Cursor(cursor_key) => {
// Raw cell pixel buffers like cursors don't need to go through font lookup // Raw cell pixel buffers like cursors don't need to go through font lookup.
let metrics = glyph_cache.metrics; let metrics = glyph_cache.metrics;
let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| { let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| {
self.load_glyph(&cursor::get_cursor_glyph( self.load_glyph(&cursor::get_cursor_glyph(
@ -1040,7 +1041,7 @@ impl<'a, C> RenderApi<'a, C> {
RenderableCellContent::Chars(chars) => chars, RenderableCellContent::Chars(chars) => chars,
}; };
// Get font key for cell // Get font key for cell.
let font_key = match cell.flags & Flags::BOLD_ITALIC { let font_key = match cell.flags & Flags::BOLD_ITALIC {
Flags::BOLD_ITALIC => glyph_cache.bold_italic_key, Flags::BOLD_ITALIC => glyph_cache.bold_italic_key,
Flags::ITALIC => glyph_cache.italic_key, Flags::ITALIC => glyph_cache.italic_key,
@ -1048,25 +1049,25 @@ impl<'a, C> RenderApi<'a, C> {
_ => glyph_cache.font_key, _ => glyph_cache.font_key,
}; };
// Don't render text of HIDDEN cells // Don't render text of HIDDEN cells.
let mut chars = if cell.flags.contains(Flags::HIDDEN) { let mut chars = if cell.flags.contains(Flags::HIDDEN) {
[' '; cell::MAX_ZEROWIDTH_CHARS + 1] [' '; cell::MAX_ZEROWIDTH_CHARS + 1]
} else { } else {
chars chars
}; };
// Render tabs as spaces in case the font doesn't support it // Render tabs as spaces in case the font doesn't support it.
if chars[0] == '\t' { if chars[0] == '\t' {
chars[0] = ' '; chars[0] = ' ';
} }
let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] }; let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] };
// Add cell to batch // Add cell to batch.
let glyph = glyph_cache.get(glyph_key, self); let glyph = glyph_cache.get(glyph_key, self);
self.add_render_item(cell, glyph); self.add_render_item(cell, glyph);
// Render zero-width characters // Render zero-width characters.
for c in (&chars[1..]).iter().filter(|c| **c != ' ') { for c in (&chars[1..]).iter().filter(|c| **c != ' ') {
glyph_key.c = *c; glyph_key.c = *c;
let mut glyph = *glyph_cache.get(glyph_key, self); let mut glyph = *glyph_cache.get(glyph_key, self);
@ -1083,7 +1084,7 @@ impl<'a, C> RenderApi<'a, C> {
} }
} }
/// Load a glyph into a texture atlas /// Load a glyph into a texture atlas.
/// ///
/// If the current atlas is full, a new one will be created. /// If the current atlas is full, a new one will be created.
#[inline] #[inline]
@ -1216,12 +1217,12 @@ impl TextShaderProgram {
} }
fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) { fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) {
// Bounds check // Bounds check.
if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) { if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) {
return; return;
} }
// Compute scale and offset factors, from pixel to ndc space. Y is inverted // Compute scale and offset factors, from pixel to ndc space. Y is inverted.
// [0, width - 2 * padding_x] to [-1, 1] // [0, width - 2 * padding_x] to [-1, 1]
// [height - 2 * padding_y, 0] to [-1, 1] // [height - 2 * padding_y, 0] to [-1, 1]
let scale_x = 2. / (width - 2. * padding_x); let scale_x = 2. / (width - 2. * padding_x);
@ -1276,7 +1277,7 @@ impl RectShaderProgram {
gl::UseProgram(program); gl::UseProgram(program);
} }
// get uniform locations // Get uniform locations.
let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) }; let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) };
let shader = Self { id: program, u_color }; let shader = Self { id: program, u_color };
@ -1355,10 +1356,10 @@ fn create_shader(
if success == GLint::from(gl::TRUE) { if success == GLint::from(gl::TRUE) {
Ok(shader) Ok(shader)
} else { } else {
// Read log // Read log.
let log = get_shader_info_log(shader); let log = get_shader_info_log(shader);
// Cleanup // Cleanup.
unsafe { unsafe {
gl::DeleteShader(shader); gl::DeleteShader(shader);
} }
@ -1368,60 +1369,60 @@ fn create_shader(
} }
fn get_program_info_log(program: GLuint) -> String { fn get_program_info_log(program: GLuint) -> String {
// Get expected log length // Get expected log length.
let mut max_length: GLint = 0; let mut max_length: GLint = 0;
unsafe { unsafe {
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length); gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length);
} }
// Read the info log // Read the info log.
let mut actual_length: GLint = 0; let mut actual_length: GLint = 0;
let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
unsafe { unsafe {
gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
} }
// Build a string // Build a string.
unsafe { unsafe {
buf.set_len(actual_length as usize); buf.set_len(actual_length as usize);
} }
// XXX should we expect opengl to return garbage? // XXX should we expect OpenGL to return garbage?
String::from_utf8(buf).unwrap() String::from_utf8(buf).unwrap()
} }
fn get_shader_info_log(shader: GLuint) -> String { fn get_shader_info_log(shader: GLuint) -> String {
// Get expected log length // Get expected log length.
let mut max_length: GLint = 0; let mut max_length: GLint = 0;
unsafe { unsafe {
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length); gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length);
} }
// Read the info log // Read the info log.
let mut actual_length: GLint = 0; let mut actual_length: GLint = 0;
let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
unsafe { unsafe {
gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
} }
// Build a string // Build a string.
unsafe { unsafe {
buf.set_len(actual_length as usize); buf.set_len(actual_length as usize);
} }
// XXX should we expect opengl to return garbage? // XXX should we expect OpenGL to return garbage?
String::from_utf8(buf).unwrap() String::from_utf8(buf).unwrap()
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ShaderCreationError { pub enum ShaderCreationError {
/// Error reading file /// Error reading file.
Io(io::Error), Io(io::Error),
/// Error compiling shader /// Error compiling shader.
Compile(PathBuf, String), Compile(PathBuf, String),
/// Problem linking /// Problem linking.
Link(String), Link(String),
} }
@ -1452,7 +1453,7 @@ impl From<io::Error> for ShaderCreationError {
} }
} }
/// Manages a single texture atlas /// Manages a single texture atlas.
/// ///
/// The strategy for filling an atlas looks roughly like this: /// The strategy for filling an atlas looks roughly like this:
/// ///
@ -1472,13 +1473,13 @@ impl From<io::Error> for ShaderCreationError {
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
struct Atlas { struct Atlas {
/// Texture id for this atlas /// Texture id for this atlas.
id: GLuint, id: GLuint,
/// Width of atlas /// Width of atlas.
width: i32, width: i32,
/// Height of atlas /// Height of atlas.
height: i32, height: i32,
/// Left-most free pixel in a row. /// Left-most free pixel in a row.
@ -1487,21 +1488,21 @@ struct Atlas {
/// in a row. /// in a row.
row_extent: i32, row_extent: i32,
/// Baseline for glyphs in the current row /// Baseline for glyphs in the current row.
row_baseline: i32, row_baseline: i32,
/// Tallest glyph in current row /// Tallest glyph in current row.
/// ///
/// This is used as the advance when end of row is reached /// This is used as the advance when end of row is reached.
row_tallest: i32, row_tallest: i32,
} }
/// Error that can happen when inserting a texture to the Atlas /// Error that can happen when inserting a texture to the Atlas.
enum AtlasInsertError { enum AtlasInsertError {
/// Texture atlas is full /// Texture atlas is full.
Full, Full,
/// The glyph cannot fit within a single texture /// The glyph cannot fit within a single texture.
GlyphTooLarge, GlyphTooLarge,
} }
@ -1541,7 +1542,7 @@ impl Atlas {
self.row_tallest = 0; self.row_tallest = 0;
} }
/// Insert a RasterizedGlyph into the texture atlas /// Insert a RasterizedGlyph into the texture atlas.
pub fn insert( pub fn insert(
&mut self, &mut self,
glyph: &RasterizedGlyph, glyph: &RasterizedGlyph,
@ -1551,12 +1552,12 @@ impl Atlas {
return Err(AtlasInsertError::GlyphTooLarge); return Err(AtlasInsertError::GlyphTooLarge);
} }
// If there's not enough room in current row, go onto next one // If there's not enough room in current row, go onto next one.
if !self.room_in_row(glyph) { if !self.room_in_row(glyph) {
self.advance_row()?; self.advance_row()?;
} }
// If there's still not room, there's nothing that can be done here. // If there's still not room, there's nothing that can be done here..
if !self.room_in_row(glyph) { if !self.room_in_row(glyph) {
return Err(AtlasInsertError::Full); return Err(AtlasInsertError::Full);
} }
@ -1565,7 +1566,7 @@ impl Atlas {
Ok(self.insert_inner(glyph, active_tex)) Ok(self.insert_inner(glyph, active_tex))
} }
/// Insert the glyph without checking for room /// Insert the glyph without checking for room.
/// ///
/// Internal function for use once atlas has been checked for space. GL /// Internal function for use once atlas has been checked for space. GL
/// errors could still occur at this point if we were checking for them; /// errors could still occur at this point if we were checking for them;
@ -1580,7 +1581,7 @@ impl Atlas {
unsafe { unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.id); gl::BindTexture(gl::TEXTURE_2D, self.id);
// Load data into OpenGL // Load data into OpenGL.
let (format, buf) = match &glyph.buf { let (format, buf) = match &glyph.buf {
BitmapBuffer::RGB(buf) => { BitmapBuffer::RGB(buf) => {
colored = false; colored = false;
@ -1608,13 +1609,13 @@ impl Atlas {
*active_tex = 0; *active_tex = 0;
} }
// Update Atlas state // Update Atlas state.
self.row_extent = offset_x + width; self.row_extent = offset_x + width;
if height > self.row_tallest { if height > self.row_tallest {
self.row_tallest = height; self.row_tallest = height;
} }
// Generate UV coordinates // Generate UV coordinates.
let uv_bot = offset_y as f32 / self.height as f32; let uv_bot = offset_y as f32 / self.height as f32;
let uv_left = offset_x as f32 / self.width as f32; let uv_left = offset_x as f32 / self.width as f32;
let uv_height = height as f32 / self.height as f32; let uv_height = height as f32 / self.height as f32;
@ -1634,7 +1635,7 @@ impl Atlas {
} }
} }
/// Check if there's room in the current row for given glyph /// Check if there's room in the current row for given glyph.
fn room_in_row(&self, raw: &RasterizedGlyph) -> bool { fn room_in_row(&self, raw: &RasterizedGlyph) -> bool {
let next_extent = self.row_extent + raw.width as i32; let next_extent = self.row_extent + raw.width as i32;
let enough_width = next_extent <= self.width; let enough_width = next_extent <= self.width;
@ -1643,7 +1644,7 @@ impl Atlas {
enough_width && enough_height enough_width && enough_height
} }
/// Mark current row as finished and prepare to insert into the next row /// Mark current row as finished and prepare to insert into the next row.
fn advance_row(&mut self) -> Result<(), AtlasInsertError> { fn advance_row(&mut self) -> Result<(), AtlasInsertError> {
let advance_to = self.row_baseline + self.row_tallest; let advance_to = self.row_baseline + self.row_tallest;
if self.height - advance_to <= 0 { if self.height - advance_to <= 0 {

View File

@ -80,7 +80,7 @@ impl RenderLine {
_ => unimplemented!("Invalid flag for cell line drawing specified"), _ => unimplemented!("Invalid flag for cell line drawing specified"),
}; };
// Make sure lines are always visible // Make sure lines are always visible.
height = height.max(1.); height = height.max(1.);
let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height; let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height;
@ -124,19 +124,19 @@ impl RenderLines {
continue; continue;
} }
// Check if there's an active line // Check if there's an active line.
if let Some(line) = self.inner.get_mut(flag).and_then(|lines| lines.last_mut()) { if let Some(line) = self.inner.get_mut(flag).and_then(|lines| lines.last_mut()) {
if cell.fg == line.color if cell.fg == line.color
&& cell.column == line.end.col + 1 && cell.column == line.end.col + 1
&& cell.line == line.end.line && cell.line == line.end.line
{ {
// Update the length of the line // Update the length of the line.
line.end = cell.into(); line.end = cell.into();
continue; continue;
} }
} }
// Start new line if there currently is none // Start new line if there currently is none.
let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg }; let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg };
match self.inner.get_mut(flag) { match self.inner.get_mut(flag) {
Some(lines) => lines.push(line), Some(lines) => lines.push(line),

View File

@ -71,9 +71,9 @@ impl Urls {
Self::default() Self::default()
} }
// Update tracked URLs /// Update tracked URLs.
pub fn update(&mut self, num_cols: usize, cell: RenderableCell) { pub fn update(&mut self, num_cols: usize, cell: RenderableCell) {
// Convert cell to character // Convert cell to character.
let c = match cell.inner { let c = match cell.inner {
RenderableCellContent::Chars(chars) => chars[0], RenderableCellContent::Chars(chars) => chars[0],
RenderableCellContent::Cursor(_) => return, RenderableCellContent::Cursor(_) => return,
@ -82,14 +82,14 @@ impl Urls {
let point: Point = cell.into(); let point: Point = cell.into();
let end = point; let end = point;
// Reset URL when empty cells have been skipped // Reset URL when empty cells have been skipped.
if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point { if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point {
self.reset(); self.reset();
} }
self.last_point = Some(end); self.last_point = Some(end);
// Extend current state if a wide char spacer is encountered // Extend current state if a wide char spacer is encountered.
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
if let UrlLocation::Url(_, mut end_offset) = self.state { if let UrlLocation::Url(_, mut end_offset) = self.state {
if end_offset != 0 { if end_offset != 0 {
@ -102,20 +102,20 @@ impl Urls {
return; return;
} }
// Advance parser // Advance parser.
let last_state = mem::replace(&mut self.state, self.locator.advance(c)); let last_state = mem::replace(&mut self.state, self.locator.advance(c));
match (self.state, last_state) { match (self.state, last_state) {
(UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => { (UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => {
// Create empty URL // Create empty URL.
self.urls.push(Url { lines: Vec::new(), end_offset, num_cols }); self.urls.push(Url { lines: Vec::new(), end_offset, num_cols });
// Push schemes into URL // Push schemes into URL.
for scheme_cell in self.scheme_buffer.split_off(0) { for scheme_cell in self.scheme_buffer.split_off(0) {
let point = scheme_cell.into(); let point = scheme_cell.into();
self.extend_url(point, point, scheme_cell.fg, end_offset); self.extend_url(point, point, scheme_cell.fg, end_offset);
} }
// Push the new cell into URL // Push the new cell into URL.
self.extend_url(point, end, cell.fg, end_offset); self.extend_url(point, end, cell.fg, end_offset);
}, },
(UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => { (UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => {
@ -126,24 +126,24 @@ impl Urls {
_ => (), _ => (),
} }
// Reset at un-wrapped linebreak // Reset at un-wrapped linebreak.
if cell.column.0 + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) { if cell.column.0 + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) {
self.reset(); self.reset();
} }
} }
// Extend the last URL /// Extend the last URL.
fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) { fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) {
let url = self.urls.last_mut().unwrap(); let url = self.urls.last_mut().unwrap();
// If color changed, we need to insert a new line // If color changed, we need to insert a new line.
if url.lines.last().map(|last| last.color) == Some(color) { if url.lines.last().map(|last| last.color) == Some(color) {
url.lines.last_mut().unwrap().end = end; url.lines.last_mut().unwrap().end = end;
} else { } else {
url.lines.push(RenderLine { color, start, end }); url.lines.push(RenderLine { color, start, end });
} }
// Update excluded cells at the end of the URL // Update excluded cells at the end of the URL.
url.end_offset = end_offset; url.end_offset = end_offset;
} }
@ -156,13 +156,13 @@ impl Urls {
mouse_mode: bool, mouse_mode: bool,
selection: bool, selection: bool,
) -> Option<Url> { ) -> Option<Url> {
// Require additional shift in mouse mode // Require additional shift in mouse mode.
let mut required_mods = config.ui_config.mouse.url.mods(); let mut required_mods = config.ui_config.mouse.url.mods();
if mouse_mode { if mouse_mode {
required_mods |= ModifiersState::SHIFT; required_mods |= ModifiersState::SHIFT;
} }
// Make sure all prerequisites for highlighting are met // Make sure all prerequisites for highlighting are met.
if selection if selection
|| !mouse.inside_grid || !mouse.inside_grid
|| config.ui_config.mouse.url.launcher.is_none() || config.ui_config.mouse.url.launcher.is_none()

View File

@ -61,24 +61,24 @@ use wayland_client::{Attached, EventQueue, Proxy};
#[cfg(not(any(target_os = "macos", windows)))] #[cfg(not(any(target_os = "macos", windows)))]
use wayland_client::protocol::wl_surface::WlSurface; use wayland_client::protocol::wl_surface::WlSurface;
// It's required to be in this directory due to the `windows.rc` file // It's required to be in this directory due to the `windows.rc` file.
#[cfg(not(any(target_os = "macos", windows)))] #[cfg(not(any(target_os = "macos", windows)))]
static WINDOW_ICON: &[u8] = include_bytes!("../../extra/windows/alacritty.ico"); static WINDOW_ICON: &[u8] = include_bytes!("../../extra/windows/alacritty.ico");
// This should match the definition of IDI_ICON from `windows.rc` // This should match the definition of IDI_ICON from `windows.rc`.
#[cfg(windows)] #[cfg(windows)]
const IDI_ICON: WORD = 0x101; const IDI_ICON: WORD = 0x101;
/// Window errors /// Window errors.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Error creating the window /// Error creating the window.
ContextCreation(glutin::CreationError), ContextCreation(glutin::CreationError),
/// Error dealing with fonts /// Error dealing with fonts.
Font(font::Error), Font(font::Error),
/// Error manipulating the rendering context /// Error manipulating the rendering context.
Context(glutin::ContextError), Context(glutin::ContextError),
} }
@ -140,7 +140,7 @@ fn create_gl_window(
.with_hardware_acceleration(None) .with_hardware_acceleration(None)
.build_windowed(window, event_loop)?; .build_windowed(window, event_loop)?;
// Make the context current so OpenGL operations can run // Make the context current so OpenGL operations can run.
let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? }; let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? };
Ok(windowed_context) Ok(windowed_context)
@ -164,7 +164,7 @@ pub struct Window {
} }
impl Window { impl Window {
/// Create a new window /// Create a new window.
/// ///
/// This creates a window and fully initializes a window. /// This creates a window and fully initializes a window.
pub fn new( pub fn new(
@ -242,7 +242,7 @@ impl Window {
self.window().set_visible(visibility); self.window().set_visible(visibility);
} }
/// Set the window title /// Set the window title.
#[inline] #[inline]
pub fn set_title(&self, title: &str) { pub fn set_title(&self, title: &str) {
self.window().set_title(title); self.window().set_title(title);
@ -256,7 +256,7 @@ impl Window {
} }
} }
/// Set mouse cursor visible /// Set mouse cursor visible.
pub fn set_mouse_visible(&mut self, visible: bool) { pub fn set_mouse_visible(&mut self, visible: bool) {
if visible != self.mouse_visible { if visible != self.mouse_visible {
self.mouse_visible = visible; self.mouse_visible = visible;
@ -286,9 +286,9 @@ impl Window {
.with_decorations(decorations) .with_decorations(decorations)
.with_maximized(window_config.startup_mode() == StartupMode::Maximized) .with_maximized(window_config.startup_mode() == StartupMode::Maximized)
.with_window_icon(icon.ok()) .with_window_icon(icon.ok())
// X11 // X11.
.with_class(class.instance.clone(), class.general.clone()) .with_class(class.instance.clone(), class.general.clone())
// Wayland // Wayland.
.with_app_id(class.instance.clone()); .with_app_id(class.instance.clone());
if let Some(ref val) = window_config.gtk_theme_variant { if let Some(ref val) = window_config.gtk_theme_variant {
@ -378,7 +378,7 @@ impl Window {
self.window().set_minimized(minimized); self.window().set_minimized(minimized);
} }
/// Toggle the window's fullscreen state /// Toggle the window's fullscreen state.
pub fn toggle_fullscreen(&mut self) { pub fn toggle_fullscreen(&mut self) {
self.set_fullscreen(self.window().fullscreen().is_none()); self.set_fullscreen(self.window().fullscreen().is_none());
} }
@ -417,7 +417,7 @@ impl Window {
self.window().set_wayland_theme(AlacrittyWaylandTheme::new(colors)); self.window().set_wayland_theme(AlacrittyWaylandTheme::new(colors));
} }
/// Adjust the IME editor position according to the new location of the cursor /// Adjust the IME editor position according to the new location of the cursor.
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn update_ime_position<T>(&mut self, terminal: &Term<T>, size_info: &SizeInfo) { pub fn update_ime_position<T>(&mut self, terminal: &Term<T>, size_info: &SizeInfo) {
let point = terminal.cursor().point; let point = terminal.cursor().point;
@ -464,13 +464,13 @@ fn x_embed_window(window: &GlutinWindow, parent_id: c_ulong) {
2, 2,
); );
// Register new error handler // Register new error handler.
let old_handler = (xlib.XSetErrorHandler)(Some(xembed_error_handler)); let old_handler = (xlib.XSetErrorHandler)(Some(xembed_error_handler));
// Check for the existence of the target before attempting reparenting // Check for the existence of the target before attempting reparenting.
(xlib.XReparentWindow)(xlib_display as _, xlib_window as _, parent_id, 0, 0); (xlib.XReparentWindow)(xlib_display as _, xlib_window as _, parent_id, 0, 0);
// Drain errors and restore original error handler // Drain errors and restore original error handler.
(xlib.XSync)(xlib_display as _, 0); (xlib.XSync)(xlib_display as _, 0);
(xlib.XSetErrorHandler)(old_handler); (xlib.XSetErrorHandler)(old_handler);
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! ANSI Terminal Stream Parsing //! ANSI Terminal Stream Parsing.
use std::io; use std::io;
use std::str; use std::str;
@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
use crate::index::{Column, Line}; use crate::index::{Column, Line};
use crate::term::color::Rgb; use crate::term::color::Rgb;
// Parse colors in XParseColor format /// Parse colors in XParseColor format.
fn xparse_color(color: &[u8]) -> Option<Rgb> { fn xparse_color(color: &[u8]) -> Option<Rgb> {
if !color.is_empty() && color[0] == b'#' { if !color.is_empty() && color[0] == b'#' {
parse_legacy_color(&color[1..]) parse_legacy_color(&color[1..])
@ -33,7 +33,7 @@ fn xparse_color(color: &[u8]) -> Option<Rgb> {
} }
} }
// Parse colors in `rgb:r(rrr)/g(ggg)/b(bbb)` format /// Parse colors in `rgb:r(rrr)/g(ggg)/b(bbb)` format.
fn parse_rgb_color(color: &[u8]) -> Option<Rgb> { fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
let colors = str::from_utf8(color).ok()?.split('/').collect::<Vec<_>>(); let colors = str::from_utf8(color).ok()?.split('/').collect::<Vec<_>>();
@ -41,7 +41,7 @@ fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
return None; return None;
} }
// Scale values instead of filling with `0`s // Scale values instead of filling with `0`s.
let scale = |input: &str| { let scale = |input: &str| {
let max = u32::pow(16, input.len() as u32) - 1; let max = u32::pow(16, input.len() as u32) - 1;
let value = u32::from_str_radix(input, 16).ok()?; let value = u32::from_str_radix(input, 16).ok()?;
@ -51,11 +51,11 @@ fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
Some(Rgb { r: scale(colors[0])?, g: scale(colors[1])?, b: scale(colors[2])? }) Some(Rgb { r: scale(colors[0])?, g: scale(colors[1])?, b: scale(colors[2])? })
} }
// Parse colors in `#r(rrr)g(ggg)b(bbb)` format /// Parse colors in `#r(rrr)g(ggg)b(bbb)` format.
fn parse_legacy_color(color: &[u8]) -> Option<Rgb> { fn parse_legacy_color(color: &[u8]) -> Option<Rgb> {
let item_len = color.len() / 3; let item_len = color.len() / 3;
// Truncate/Fill to two byte precision // Truncate/Fill to two byte precision.
let color_from_slice = |slice: &[u8]| { let color_from_slice = |slice: &[u8]| {
let col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4; let col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4;
Some((col >> (4 * slice.len().saturating_sub(1))) as u8) Some((col >> (4 * slice.len().saturating_sub(1))) as u8)
@ -87,13 +87,13 @@ fn parse_number(input: &[u8]) -> Option<u8> {
Some(num) Some(num)
} }
/// The processor wraps a `vte::Parser` to ultimately call methods on a Handler /// The processor wraps a `vte::Parser` to ultimately call methods on a Handler.
pub struct Processor { pub struct Processor {
state: ProcessorState, state: ProcessorState,
parser: vte::Parser, parser: vte::Parser,
} }
/// Internal state for VTE processor /// Internal state for VTE processor.
struct ProcessorState { struct ProcessorState {
preceding_char: Option<char>, preceding_char: Option<char>,
} }
@ -109,7 +109,7 @@ struct Performer<'a, H: Handler + TermInfo, W: io::Write> {
} }
impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> { impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> {
/// Create a performer /// Create a performer.
#[inline] #[inline]
pub fn new<'b>( pub fn new<'b>(
state: &'b mut ProcessorState, state: &'b mut ProcessorState,
@ -142,185 +142,185 @@ impl Processor {
} }
} }
/// Trait that provides properties of terminal /// Trait that provides properties of terminal.
pub trait TermInfo { pub trait TermInfo {
fn lines(&self) -> Line; fn lines(&self) -> Line;
fn cols(&self) -> Column; fn cols(&self) -> Column;
} }
/// Type that handles actions from the parser /// Type that handles actions from the parser.
/// ///
/// XXX Should probably not provide default impls for everything, but it makes /// XXX Should probably not provide default impls for everything, but it makes
/// writing specific handler impls for tests far easier. /// writing specific handler impls for tests far easier.
pub trait Handler { pub trait Handler {
/// OSC to set window title /// OSC to set window title.
fn set_title(&mut self, _: Option<String>) {} fn set_title(&mut self, _: Option<String>) {}
/// Set the cursor style /// Set the cursor style.
fn set_cursor_style(&mut self, _: Option<CursorStyle>) {} fn set_cursor_style(&mut self, _: Option<CursorStyle>) {}
/// A character to be displayed /// A character to be displayed.
fn input(&mut self, _c: char) {} fn input(&mut self, _c: char) {}
/// Set cursor to position /// Set cursor to position.
fn goto(&mut self, _: Line, _: Column) {} fn goto(&mut self, _: Line, _: Column) {}
/// Set cursor to specific row /// Set cursor to specific row.
fn goto_line(&mut self, _: Line) {} fn goto_line(&mut self, _: Line) {}
/// Set cursor to specific column /// Set cursor to specific column.
fn goto_col(&mut self, _: Column) {} fn goto_col(&mut self, _: Column) {}
/// Insert blank characters in current line starting from cursor /// Insert blank characters in current line starting from cursor.
fn insert_blank(&mut self, _: Column) {} fn insert_blank(&mut self, _: Column) {}
/// Move cursor up `rows` /// Move cursor up `rows`.
fn move_up(&mut self, _: Line) {} fn move_up(&mut self, _: Line) {}
/// Move cursor down `rows` /// Move cursor down `rows`.
fn move_down(&mut self, _: Line) {} fn move_down(&mut self, _: Line) {}
/// Identify the terminal (should write back to the pty stream) /// Identify the terminal (should write back to the pty stream).
/// ///
/// TODO this should probably return an io::Result /// TODO this should probably return an io::Result
fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {} fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {}
// Report device status /// Report device status.
fn device_status<W: io::Write>(&mut self, _: &mut W, _: usize) {} fn device_status<W: io::Write>(&mut self, _: &mut W, _: usize) {}
/// Move cursor forward `cols` /// Move cursor forward `cols`.
fn move_forward(&mut self, _: Column) {} fn move_forward(&mut self, _: Column) {}
/// Move cursor backward `cols` /// Move cursor backward `cols`.
fn move_backward(&mut self, _: Column) {} fn move_backward(&mut self, _: Column) {}
/// Move cursor down `rows` and set to column 1 /// Move cursor down `rows` and set to column 1.
fn move_down_and_cr(&mut self, _: Line) {} fn move_down_and_cr(&mut self, _: Line) {}
/// Move cursor up `rows` and set to column 1 /// Move cursor up `rows` and set to column 1.
fn move_up_and_cr(&mut self, _: Line) {} fn move_up_and_cr(&mut self, _: Line) {}
/// Put `count` tabs /// Put `count` tabs.
fn put_tab(&mut self, _count: i64) {} fn put_tab(&mut self, _count: i64) {}
/// Backspace `count` characters /// Backspace `count` characters.
fn backspace(&mut self) {} fn backspace(&mut self) {}
/// Carriage return /// Carriage return.
fn carriage_return(&mut self) {} fn carriage_return(&mut self) {}
/// Linefeed /// Linefeed.
fn linefeed(&mut self) {} fn linefeed(&mut self) {}
/// Ring the bell /// Ring the bell.
/// ///
/// Hopefully this is never implemented /// Hopefully this is never implemented.
fn bell(&mut self) {} fn bell(&mut self) {}
/// Substitute char under cursor /// Substitute char under cursor.
fn substitute(&mut self) {} fn substitute(&mut self) {}
/// Newline /// Newline.
fn newline(&mut self) {} fn newline(&mut self) {}
/// Set current position as a tabstop /// Set current position as a tabstop.
fn set_horizontal_tabstop(&mut self) {} fn set_horizontal_tabstop(&mut self) {}
/// Scroll up `rows` rows /// Scroll up `rows` rows.
fn scroll_up(&mut self, _: Line) {} fn scroll_up(&mut self, _: Line) {}
/// Scroll down `rows` rows /// Scroll down `rows` rows.
fn scroll_down(&mut self, _: Line) {} fn scroll_down(&mut self, _: Line) {}
/// Insert `count` blank lines /// Insert `count` blank lines.
fn insert_blank_lines(&mut self, _: Line) {} fn insert_blank_lines(&mut self, _: Line) {}
/// Delete `count` lines /// Delete `count` lines.
fn delete_lines(&mut self, _: Line) {} fn delete_lines(&mut self, _: Line) {}
/// Erase `count` chars in current line following cursor /// Erase `count` chars in current line following cursor.
/// ///
/// Erase means resetting to the default state (default colors, no content, /// Erase means resetting to the default state (default colors, no content,
/// no mode flags) /// no mode flags).
fn erase_chars(&mut self, _: Column) {} fn erase_chars(&mut self, _: Column) {}
/// Delete `count` chars /// Delete `count` chars.
/// ///
/// Deleting a character is like the delete key on the keyboard - everything /// Deleting a character is like the delete key on the keyboard - everything
/// to the right of the deleted things is shifted left. /// to the right of the deleted things is shifted left.
fn delete_chars(&mut self, _: Column) {} fn delete_chars(&mut self, _: Column) {}
/// Move backward `count` tabs /// Move backward `count` tabs.
fn move_backward_tabs(&mut self, _count: i64) {} fn move_backward_tabs(&mut self, _count: i64) {}
/// Move forward `count` tabs /// Move forward `count` tabs.
fn move_forward_tabs(&mut self, _count: i64) {} fn move_forward_tabs(&mut self, _count: i64) {}
/// Save current cursor position /// Save current cursor position.
fn save_cursor_position(&mut self) {} fn save_cursor_position(&mut self) {}
/// Restore cursor position /// Restore cursor position.
fn restore_cursor_position(&mut self) {} fn restore_cursor_position(&mut self) {}
/// Clear current line /// Clear current line.
fn clear_line(&mut self, _mode: LineClearMode) {} fn clear_line(&mut self, _mode: LineClearMode) {}
/// Clear screen /// Clear screen.
fn clear_screen(&mut self, _mode: ClearMode) {} fn clear_screen(&mut self, _mode: ClearMode) {}
/// Clear tab stops /// Clear tab stops.
fn clear_tabs(&mut self, _mode: TabulationClearMode) {} fn clear_tabs(&mut self, _mode: TabulationClearMode) {}
/// Reset terminal state /// Reset terminal state.
fn reset_state(&mut self) {} fn reset_state(&mut self) {}
/// Reverse Index /// Reverse Index.
/// ///
/// Move the active position to the same horizontal position on the /// Move the active position to the same horizontal position on the
/// preceding line. If the active position is at the top margin, a scroll /// preceding line. If the active position is at the top margin, a scroll
/// down is performed /// down is performed.
fn reverse_index(&mut self) {} fn reverse_index(&mut self) {}
/// set a terminal attribute /// Set a terminal attribute.
fn terminal_attribute(&mut self, _attr: Attr) {} fn terminal_attribute(&mut self, _attr: Attr) {}
/// Set mode /// Set mode.
fn set_mode(&mut self, _mode: Mode) {} fn set_mode(&mut self, _mode: Mode) {}
/// Unset mode /// Unset mode.
fn unset_mode(&mut self, _: Mode) {} fn unset_mode(&mut self, _: Mode) {}
/// DECSTBM - Set the terminal scrolling region /// DECSTBM - Set the terminal scrolling region.
fn set_scrolling_region(&mut self, _top: usize, _bottom: usize) {} fn set_scrolling_region(&mut self, _top: usize, _bottom: usize) {}
/// DECKPAM - Set keypad to applications mode (ESCape instead of digits) /// DECKPAM - Set keypad to applications mode (ESCape instead of digits).
fn set_keypad_application_mode(&mut self) {} fn set_keypad_application_mode(&mut self) {}
/// DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq) /// DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq).
fn unset_keypad_application_mode(&mut self) {} fn unset_keypad_application_mode(&mut self) {}
/// Set one of the graphic character sets, G0 to G3, as the active charset. /// Set one of the graphic character sets, G0 to G3, as the active charset.
/// ///
/// 'Invoke' one of G0 to G3 in the GL area. Also referred to as shift in, /// 'Invoke' one of G0 to G3 in the GL area. Also referred to as shift in,
/// shift out and locking shift depending on the set being activated /// shift out and locking shift depending on the set being activated.
fn set_active_charset(&mut self, _: CharsetIndex) {} fn set_active_charset(&mut self, _: CharsetIndex) {}
/// Assign a graphic character set to G0, G1, G2 or G3 /// Assign a graphic character set to G0, G1, G2 or G3.
/// ///
/// 'Designate' a graphic character set as one of G0 to G3, so that it can /// 'Designate' a graphic character set as one of G0 to G3, so that it can
/// later be 'invoked' by `set_active_charset` /// later be 'invoked' by `set_active_charset`.
fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {} fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {}
/// Set an indexed color value /// Set an indexed color value.
fn set_color(&mut self, _: usize, _: Rgb) {} fn set_color(&mut self, _: usize, _: Rgb) {}
/// Write a foreground/background color escape sequence with the current color /// Write a foreground/background color escape sequence with the current color.
fn dynamic_color_sequence<W: io::Write>(&mut self, _: &mut W, _: u8, _: usize, _: &str) {} fn dynamic_color_sequence<W: io::Write>(&mut self, _: &mut W, _: u8, _: usize, _: &str) {}
/// Reset an indexed color to original value /// Reset an indexed color to original value.
fn reset_color(&mut self, _: usize) {} fn reset_color(&mut self, _: usize) {}
/// Set the clipboard /// Set the clipboard.
fn set_clipboard(&mut self, _: u8, _: &[u8]) {} fn set_clipboard(&mut self, _: u8, _: &[u8]) {}
/// Write clipboard data to child. /// Write clipboard data to child.
@ -329,30 +329,30 @@ pub trait Handler {
/// Run the decaln routine. /// Run the decaln routine.
fn decaln(&mut self) {} fn decaln(&mut self) {}
/// Push a title onto the stack /// Push a title onto the stack.
fn push_title(&mut self) {} fn push_title(&mut self) {}
/// Pop the last title from the stack /// Pop the last title from the stack.
fn pop_title(&mut self) {} fn pop_title(&mut self) {}
} }
/// Describes shape of cursor /// Describes shape of cursor.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)] #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
pub enum CursorStyle { pub enum CursorStyle {
/// Cursor is a block like `▒` /// Cursor is a block like `▒`.
Block, Block,
/// Cursor is an underscore like `_` /// Cursor is an underscore like `_`.
Underline, Underline,
/// Cursor is a vertical bar `⎸` /// Cursor is a vertical bar `⎸`.
Beam, Beam,
/// Cursor is a box like `☐` /// Cursor is a box like `☐`.
#[serde(skip)] #[serde(skip)]
HollowBlock, HollowBlock,
/// Invisible cursor /// Invisible cursor.
#[serde(skip)] #[serde(skip)]
Hidden, Hidden,
} }
@ -363,15 +363,15 @@ impl Default for CursorStyle {
} }
} }
/// Terminal modes /// Terminal modes.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum Mode { pub enum Mode {
/// ?1 /// ?1
CursorKeys = 1, CursorKeys = 1,
/// Select 80 or 132 columns per page /// Select 80 or 132 columns per page.
/// ///
/// CSI ? 3 h -> set 132 column font /// CSI ? 3 h -> set 132 column font.
/// CSI ? 3 l -> reset 80 column font /// CSI ? 3 l -> reset 80 column font.
/// ///
/// Additionally, /// Additionally,
/// ///
@ -380,9 +380,9 @@ pub enum Mode {
/// * resets DECLRMM to unavailable /// * resets DECLRMM to unavailable
/// * clears data from the status line (if set to host-writable) /// * clears data from the status line (if set to host-writable)
DECCOLM = 3, DECCOLM = 3,
/// IRM Insert Mode /// IRM Insert Mode.
/// ///
/// NB should be part of non-private mode enum /// NB should be part of non-private mode enum.
/// ///
/// * `CSI 4 h` change to insert mode /// * `CSI 4 h` change to insert mode
/// * `CSI 4 l` reset to replacement mode /// * `CSI 4 l` reset to replacement mode
@ -421,9 +421,9 @@ pub enum Mode {
} }
impl Mode { impl Mode {
/// Create mode from a primitive /// Create mode from a primitive.
/// ///
/// TODO lots of unhandled values.. /// TODO lots of unhandled values.
pub fn from_primitive(intermediate: Option<&u8>, num: i64) -> Option<Mode> { pub fn from_primitive(intermediate: Option<&u8>, num: i64) -> Option<Mode> {
let private = match intermediate { let private = match intermediate {
Some(b'?') => true, Some(b'?') => true,
@ -463,106 +463,106 @@ impl Mode {
} }
} }
/// Mode for clearing line /// Mode for clearing line.
/// ///
/// Relative to cursor /// Relative to cursor.
#[derive(Debug)] #[derive(Debug)]
pub enum LineClearMode { pub enum LineClearMode {
/// Clear right of cursor /// Clear right of cursor.
Right, Right,
/// Clear left of cursor /// Clear left of cursor.
Left, Left,
/// Clear entire line /// Clear entire line.
All, All,
} }
/// Mode for clearing terminal /// Mode for clearing terminal.
/// ///
/// Relative to cursor /// Relative to cursor.
#[derive(Debug)] #[derive(Debug)]
pub enum ClearMode { pub enum ClearMode {
/// Clear below cursor /// Clear below cursor.
Below, Below,
/// Clear above cursor /// Clear above cursor.
Above, Above,
/// Clear entire terminal /// Clear entire terminal.
All, All,
/// Clear 'saved' lines (scrollback) /// Clear 'saved' lines (scrollback).
Saved, Saved,
} }
/// Mode for clearing tab stops /// Mode for clearing tab stops.
#[derive(Debug)] #[derive(Debug)]
pub enum TabulationClearMode { pub enum TabulationClearMode {
/// Clear stop under cursor /// Clear stop under cursor.
Current, Current,
/// Clear all stops /// Clear all stops.
All, All,
} }
/// Standard colors /// Standard colors.
/// ///
/// The order here matters since the enum should be castable to a `usize` for /// The order here matters since the enum should be castable to a `usize` for
/// indexing a color list. /// indexing a color list.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum NamedColor { pub enum NamedColor {
/// Black /// Black.
Black = 0, Black = 0,
/// Red /// Red.
Red, Red,
/// Green /// Green.
Green, Green,
/// Yellow /// Yellow.
Yellow, Yellow,
/// Blue /// Blue.
Blue, Blue,
/// Magenta /// Magenta.
Magenta, Magenta,
/// Cyan /// Cyan.
Cyan, Cyan,
/// White /// White.
White, White,
/// Bright black /// Bright black.
BrightBlack, BrightBlack,
/// Bright red /// Bright red.
BrightRed, BrightRed,
/// Bright green /// Bright green.
BrightGreen, BrightGreen,
/// Bright yellow /// Bright yellow.
BrightYellow, BrightYellow,
/// Bright blue /// Bright blue.
BrightBlue, BrightBlue,
/// Bright magenta /// Bright magenta.
BrightMagenta, BrightMagenta,
/// Bright cyan /// Bright cyan.
BrightCyan, BrightCyan,
/// Bright white /// Bright white.
BrightWhite, BrightWhite,
/// The foreground color /// The foreground color.
Foreground = 256, Foreground = 256,
/// The background color /// The background color.
Background, Background,
/// Color for the cursor itself /// Color for the cursor itself.
Cursor, Cursor,
/// Dim black /// Dim black.
DimBlack, DimBlack,
/// Dim red /// Dim red.
DimRed, DimRed,
/// Dim green /// Dim green.
DimGreen, DimGreen,
/// Dim yellow /// Dim yellow.
DimYellow, DimYellow,
/// Dim blue /// Dim blue.
DimBlue, DimBlue,
/// Dim magenta /// Dim magenta.
DimMagenta, DimMagenta,
/// Dim cyan /// Dim cyan.
DimCyan, DimCyan,
/// Dim white /// Dim white.
DimWhite, DimWhite,
/// The bright foreground color /// The bright foreground color.
BrightForeground, BrightForeground,
/// Dim foreground /// Dim foreground.
DimForeground, DimForeground,
} }
@ -623,55 +623,55 @@ pub enum Color {
Indexed(u8), Indexed(u8),
} }
/// Terminal character attributes /// Terminal character attributes.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum Attr { pub enum Attr {
/// Clear all special abilities /// Clear all special abilities.
Reset, Reset,
/// Bold text /// Bold text.
Bold, Bold,
/// Dim or secondary color /// Dim or secondary color.
Dim, Dim,
/// Italic text /// Italic text.
Italic, Italic,
/// Underline text /// Underline text.
Underline, Underline,
/// Blink cursor slowly /// Blink cursor slowly.
BlinkSlow, BlinkSlow,
/// Blink cursor fast /// Blink cursor fast.
BlinkFast, BlinkFast,
/// Invert colors /// Invert colors.
Reverse, Reverse,
/// Do not display characters /// Do not display characters.
Hidden, Hidden,
/// Strikeout text /// Strikeout text.
Strike, Strike,
/// Cancel bold /// Cancel bold.
CancelBold, CancelBold,
/// Cancel bold and dim /// Cancel bold and dim.
CancelBoldDim, CancelBoldDim,
/// Cancel italic /// Cancel italic.
CancelItalic, CancelItalic,
/// Cancel underline /// Cancel underline.
CancelUnderline, CancelUnderline,
/// Cancel blink /// Cancel blink.
CancelBlink, CancelBlink,
/// Cancel inversion /// Cancel inversion.
CancelReverse, CancelReverse,
/// Cancel text hiding /// Cancel text hiding.
CancelHidden, CancelHidden,
/// Cancel strikeout /// Cancel strikeout.
CancelStrike, CancelStrike,
/// Set indexed foreground color /// Set indexed foreground color.
Foreground(Color), Foreground(Color),
/// Set indexed background color /// Set indexed background color.
Background(Color), Background(Color),
} }
/// Identifiers which can be assigned to a graphic character set /// Identifiers which can be assigned to a graphic character set.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CharsetIndex { pub enum CharsetIndex {
/// Default set, is designated as ASCII at startup /// Default set, is designated as ASCII at startup.
G0, G0,
G1, G1,
G2, G2,
@ -684,7 +684,7 @@ impl Default for CharsetIndex {
} }
} }
/// Standard or common character sets which can be designated as G0-G3 /// Standard or common character sets which can be designated as G0-G3.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StandardCharset { pub enum StandardCharset {
Ascii, Ascii,
@ -741,7 +741,7 @@ where
debug!("[unhandled unhook]"); debug!("[unhandled unhook]");
} }
// TODO replace OSC parsing with parser combinators // TODO replace OSC parsing with parser combinators.
#[inline] #[inline]
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
let writer = &mut self.writer; let writer = &mut self.writer;
@ -764,7 +764,7 @@ where
} }
match params[0] { match params[0] {
// Set window title // Set window title.
b"0" | b"2" => { b"0" | b"2" => {
if params.len() >= 2 { if params.len() >= 2 {
let title = params[1..] let title = params[1..]
@ -780,11 +780,11 @@ where
unhandled(params); unhandled(params);
}, },
// Set icon name // Set icon name.
// This is ignored, since alacritty has no concept of tabs // This is ignored, since alacritty has no concept of tabs.
b"1" => (), b"1" => (),
// Set color index // Set color index.
b"4" => { b"4" => {
if params.len() > 1 && params.len() % 2 != 0 { if params.len() > 1 && params.len() % 2 != 0 {
for chunk in params[1..].chunks(2) { for chunk in params[1..].chunks(2) {
@ -799,16 +799,16 @@ where
unhandled(params); unhandled(params);
}, },
// Get/set Foreground, Background, Cursor colors // Get/set Foreground, Background, Cursor colors.
b"10" | b"11" | b"12" => { b"10" | b"11" | b"12" => {
if params.len() >= 2 { if params.len() >= 2 {
if let Some(mut dynamic_code) = parse_number(params[0]) { if let Some(mut dynamic_code) = parse_number(params[0]) {
for param in &params[1..] { for param in &params[1..] {
// 10 is the first dynamic color, also the foreground // 10 is the first dynamic color, also the foreground.
let offset = dynamic_code as usize - 10; let offset = dynamic_code as usize - 10;
let index = NamedColor::Foreground as usize + offset; let index = NamedColor::Foreground as usize + offset;
// End of setting dynamic colors // End of setting dynamic colors.
if index > NamedColor::Cursor as usize { if index > NamedColor::Cursor as usize {
unhandled(params); unhandled(params);
break; break;
@ -834,7 +834,7 @@ where
unhandled(params); unhandled(params);
}, },
// Set cursor style // Set cursor style.
b"50" => { b"50" => {
if params.len() >= 2 if params.len() >= 2
&& params[1].len() >= 13 && params[1].len() >= 13
@ -852,7 +852,7 @@ where
unhandled(params); unhandled(params);
}, },
// Set clipboard // Set clipboard.
b"52" => { b"52" => {
if params.len() < 3 { if params.len() < 3 {
return unhandled(params); return unhandled(params);
@ -865,9 +865,9 @@ where
} }
}, },
// Reset color index // Reset color index.
b"104" => { b"104" => {
// Reset all color indexes when no parameters are given // Reset all color indexes when no parameters are given.
if params.len() == 1 { if params.len() == 1 {
for i in 0..256 { for i in 0..256 {
self.handler.reset_color(i); self.handler.reset_color(i);
@ -875,7 +875,7 @@ where
return; return;
} }
// Reset color indexes given as parameters // Reset color indexes given as parameters.
for param in &params[1..] { for param in &params[1..] {
match parse_number(param) { match parse_number(param) {
Some(index) => self.handler.reset_color(index as usize), Some(index) => self.handler.reset_color(index as usize),
@ -884,13 +884,13 @@ where
} }
}, },
// Reset foreground color // Reset foreground color.
b"110" => self.handler.reset_color(NamedColor::Foreground as usize), b"110" => self.handler.reset_color(NamedColor::Foreground as usize),
// Reset background color // Reset background color.
b"111" => self.handler.reset_color(NamedColor::Background as usize), b"111" => self.handler.reset_color(NamedColor::Background as usize),
// Reset text cursor color // Reset text cursor color.
b"112" => self.handler.reset_color(NamedColor::Cursor as usize), b"112" => self.handler.reset_color(NamedColor::Cursor as usize),
_ => unhandled(params), _ => unhandled(params),
@ -1066,7 +1066,7 @@ where
handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize) handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize)
}, },
('q', Some(b' ')) => { ('q', Some(b' ')) => {
// DECSCUSR (CSI Ps SP q) -- Set Cursor Style // DECSCUSR (CSI Ps SP q) -- Set Cursor Style.
let style = match arg_or_default!(idx: 0, default: 0) { let style = match arg_or_default!(idx: 0, default: 0) {
0 => None, 0 => None,
1 | 2 => Some(CursorStyle::Block), 1 | 2 => Some(CursorStyle::Block),
@ -1138,7 +1138,7 @@ where
(b'8', None) => self.handler.restore_cursor_position(), (b'8', None) => self.handler.restore_cursor_position(),
(b'=', None) => self.handler.set_keypad_application_mode(), (b'=', None) => self.handler.set_keypad_application_mode(),
(b'>', None) => self.handler.unset_keypad_application_mode(), (b'>', None) => self.handler.unset_keypad_application_mode(),
// String terminator, do nothing (parser handles as string terminator) // String terminator, do nothing (parser handles as string terminator).
(b'\\', None) => (), (b'\\', None) => (),
_ => unhandled!(), _ => unhandled!(),
} }
@ -1146,12 +1146,10 @@ where
} }
fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> { fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
// Sometimes a C-style for loop is just what you need let mut i = 0;
let mut i = 0; // C-for initializer
let mut attrs = Vec::with_capacity(parameters.len()); let mut attrs = Vec::with_capacity(parameters.len());
loop { loop {
if i >= parameters.len() { if i >= parameters.len() {
// C-for condition
break; break;
} }
@ -1231,12 +1229,12 @@ fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
attrs.push(attr); attrs.push(attr);
i += 1; // C-for expr i += 1;
} }
attrs attrs
} }
/// Parse a color specifier from list of attributes /// Parse a color specifier from list of attributes.
fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> { fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
if attrs.len() < 2 { if attrs.len() < 2 {
return None; return None;
@ -1244,7 +1242,7 @@ fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
match attrs[*i + 1] { match attrs[*i + 1] {
2 => { 2 => {
// RGB color spec // RGB color spec.
if attrs.len() < 5 { if attrs.len() < 5 {
debug!("Expected RGB color spec; got {:?}", attrs); debug!("Expected RGB color spec; got {:?}", attrs);
return None; return None;
@ -1290,75 +1288,75 @@ fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
/// C0 set of 7-bit control characters (from ANSI X3.4-1977). /// C0 set of 7-bit control characters (from ANSI X3.4-1977).
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub mod C0 { pub mod C0 {
/// Null filler, terminal should ignore this character /// Null filler, terminal should ignore this character.
pub const NUL: u8 = 0x00; pub const NUL: u8 = 0x00;
/// Start of Header /// Start of Header.
pub const SOH: u8 = 0x01; pub const SOH: u8 = 0x01;
/// Start of Text, implied end of header /// Start of Text, implied end of header.
pub const STX: u8 = 0x02; pub const STX: u8 = 0x02;
/// End of Text, causes some terminal to respond with ACK or NAK /// End of Text, causes some terminal to respond with ACK or NAK.
pub const ETX: u8 = 0x03; pub const ETX: u8 = 0x03;
/// End of Transmission /// End of Transmission.
pub const EOT: u8 = 0x04; pub const EOT: u8 = 0x04;
/// Enquiry, causes terminal to send ANSWER-BACK ID /// Enquiry, causes terminal to send ANSWER-BACK ID.
pub const ENQ: u8 = 0x05; pub const ENQ: u8 = 0x05;
/// Acknowledge, usually sent by terminal in response to ETX /// Acknowledge, usually sent by terminal in response to ETX.
pub const ACK: u8 = 0x06; pub const ACK: u8 = 0x06;
/// Bell, triggers the bell, buzzer, or beeper on the terminal /// Bell, triggers the bell, buzzer, or beeper on the terminal.
pub const BEL: u8 = 0x07; pub const BEL: u8 = 0x07;
/// Backspace, can be used to define overstruck characters /// Backspace, can be used to define overstruck characters.
pub const BS: u8 = 0x08; pub const BS: u8 = 0x08;
/// Horizontal Tabulation, move to next predetermined position /// Horizontal Tabulation, move to next predetermined position.
pub const HT: u8 = 0x09; pub const HT: u8 = 0x09;
/// Linefeed, move to same position on next line (see also NL) /// Linefeed, move to same position on next line (see also NL).
pub const LF: u8 = 0x0A; pub const LF: u8 = 0x0A;
/// Vertical Tabulation, move to next predetermined line /// Vertical Tabulation, move to next predetermined line.
pub const VT: u8 = 0x0B; pub const VT: u8 = 0x0B;
/// Form Feed, move to next form or page /// Form Feed, move to next form or page.
pub const FF: u8 = 0x0C; pub const FF: u8 = 0x0C;
/// Carriage Return, move to first character of current line /// Carriage Return, move to first character of current line.
pub const CR: u8 = 0x0D; pub const CR: u8 = 0x0D;
/// Shift Out, switch to G1 (other half of character set) /// Shift Out, switch to G1 (other half of character set).
pub const SO: u8 = 0x0E; pub const SO: u8 = 0x0E;
/// Shift In, switch to G0 (normal half of character set) /// Shift In, switch to G0 (normal half of character set).
pub const SI: u8 = 0x0F; pub const SI: u8 = 0x0F;
/// Data Link Escape, interpret next control character specially /// Data Link Escape, interpret next control character specially.
pub const DLE: u8 = 0x10; pub const DLE: u8 = 0x10;
/// (DC1) Terminal is allowed to resume transmitting /// (DC1) Terminal is allowed to resume transmitting.
pub const XON: u8 = 0x11; pub const XON: u8 = 0x11;
/// Device Control 2, causes ASR-33 to activate paper-tape reader /// Device Control 2, causes ASR-33 to activate paper-tape reader.
pub const DC2: u8 = 0x12; pub const DC2: u8 = 0x12;
/// (DC2) Terminal must pause and refrain from transmitting /// (DC2) Terminal must pause and refrain from transmitting.
pub const XOFF: u8 = 0x13; pub const XOFF: u8 = 0x13;
/// Device Control 4, causes ASR-33 to deactivate paper-tape reader /// Device Control 4, causes ASR-33 to deactivate paper-tape reader.
pub const DC4: u8 = 0x14; pub const DC4: u8 = 0x14;
/// Negative Acknowledge, used sometimes with ETX and ACK /// Negative Acknowledge, used sometimes with ETX and ACK.
pub const NAK: u8 = 0x15; pub const NAK: u8 = 0x15;
/// Synchronous Idle, used to maintain timing in Sync communication /// Synchronous Idle, used to maintain timing in Sync communication.
pub const SYN: u8 = 0x16; pub const SYN: u8 = 0x16;
/// End of Transmission block /// End of Transmission block.
pub const ETB: u8 = 0x17; pub const ETB: u8 = 0x17;
/// Cancel (makes VT100 abort current escape sequence if any) /// Cancel (makes VT100 abort current escape sequence if any).
pub const CAN: u8 = 0x18; pub const CAN: u8 = 0x18;
/// End of Medium /// End of Medium.
pub const EM: u8 = 0x19; pub const EM: u8 = 0x19;
/// Substitute (VT100 uses this to display parity errors) /// Substitute (VT100 uses this to display parity errors).
pub const SUB: u8 = 0x1A; pub const SUB: u8 = 0x1A;
/// Prefix to an escape sequence /// Prefix to an escape sequence.
pub const ESC: u8 = 0x1B; pub const ESC: u8 = 0x1B;
/// File Separator /// File Separator.
pub const FS: u8 = 0x1C; pub const FS: u8 = 0x1C;
/// Group Separator /// Group Separator.
pub const GS: u8 = 0x1D; pub const GS: u8 = 0x1D;
/// Record Separator (sent by VT132 in block-transfer mode) /// Record Separator (sent by VT132 in block-transfer mode).
pub const RS: u8 = 0x1E; pub const RS: u8 = 0x1E;
/// Unit Separator /// Unit Separator.
pub const US: u8 = 0x1F; pub const US: u8 = 0x1F;
/// Delete, should be ignored by terminal /// Delete, should be ignored by terminal.
pub const DEL: u8 = 0x7f; pub const DEL: u8 = 0x7f;
} }
// Tests for parsing escape sequences // Tests for parsing escape sequences.
// //
// Byte sequences used in these tests are recording of pty stdout. // Byte sequences used in these tests are recording of pty stdout.
#[cfg(test)] #[cfg(test)]
@ -1514,7 +1512,7 @@ mod tests {
assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec)))); assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec))));
} }
/// No exactly a test; useful for debugging /// No exactly a test; useful for debugging.
#[test] #[test]
fn parse_zsh_startup() { fn parse_zsh_startup() {
static BYTES: &[u8] = &[ static BYTES: &[u8] = &[

View File

@ -61,7 +61,7 @@ impl Clipboard {
return Self::new_nop(); return Self::new_nop();
} }
// Use for tests and ref-tests // Use for tests and ref-tests.
pub fn new_nop() -> Self { pub fn new_nop() -> Self {
Self { clipboard: Box::new(NopClipboardContext::new().unwrap()), selection: None } Self { clipboard: Box::new(NopClipboardContext::new().unwrap()), selection: None }
} }

View File

@ -59,7 +59,7 @@ where
index index
); );
// Return value out of range to ignore this color // Return value out of range to ignore this color.
Ok(0) Ok(0)
} else { } else {
Ok(index) Ok(index)
@ -68,7 +68,7 @@ where
Err(err) => { Err(err) => {
error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring setting", err); error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring setting", err);
// Return value out of range to ignore this color // Return value out of range to ignore this color.
Ok(0) Ok(0)
}, },
} }
@ -124,7 +124,7 @@ fn default_foreground() -> Rgb {
Rgb { r: 0xea, g: 0xea, b: 0xea } Rgb { r: 0xea, g: 0xea, b: 0xea }
} }
/// The 8-colors sections of config /// The 8-colors sections of config.
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] #[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct AnsiColors { pub struct AnsiColors {
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Deserializer};
use crate::config::{failure_default, LOG_TARGET_CONFIG}; use crate::config::{failure_default, LOG_TARGET_CONFIG};
/// Debugging options /// Debugging options.
#[serde(default)] #[serde(default)]
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Debug { pub struct Debug {
@ -13,15 +13,15 @@ pub struct Debug {
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub print_events: bool, pub print_events: bool,
/// Keep the log file after quitting /// Keep the log file after quitting.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub persistent_logging: bool, pub persistent_logging: bool,
/// Should show render timer /// Should show render timer.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub render_timer: bool, pub render_timer: bool,
/// Record ref test /// Record ref test.
#[serde(skip)] #[serde(skip)]
pub ref_test: bool, pub ref_test: bool,
} }

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Deserializer};
use crate::config::DefaultTrueBool; use crate::config::DefaultTrueBool;
use crate::config::{failure_default, Delta, LOG_TARGET_CONFIG}; use crate::config::{failure_default, Delta, LOG_TARGET_CONFIG};
/// Font config /// Font config.
/// ///
/// Defaults are provided at the level of this struct per platform, but not per /// Defaults are provided at the level of this struct per platform, but not per
/// field in this struct. It might be nice in the future to have defaults for /// field in this struct. It might be nice in the future to have defaults for
@ -18,31 +18,31 @@ use crate::config::{failure_default, Delta, LOG_TARGET_CONFIG};
#[serde(default)] #[serde(default)]
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub struct Font { pub struct Font {
/// Normal font face /// Normal font face.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
normal: FontDescription, normal: FontDescription,
/// Bold font face /// Bold font face.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
bold: SecondaryFontDescription, bold: SecondaryFontDescription,
/// Italic font face /// Italic font face.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
italic: SecondaryFontDescription, italic: SecondaryFontDescription,
/// Bold italic font face /// Bold italic font face.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
bold_italic: SecondaryFontDescription, bold_italic: SecondaryFontDescription,
/// Font size in points /// Font size in points.
#[serde(deserialize_with = "DeserializeSize::deserialize")] #[serde(deserialize_with = "DeserializeSize::deserialize")]
pub size: Size, pub size: Size,
/// Extra spacing per character /// Extra spacing per character.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub offset: Delta<i8>, pub offset: Delta<i8>,
/// Glyph offset within character cell /// Glyph offset within character cell.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub glyph_offset: Delta<i8>, pub glyph_offset: Delta<i8>,
@ -68,27 +68,27 @@ impl Default for Font {
} }
impl Font { impl Font {
/// Get a font clone with a size modification /// Get a font clone with a size modification.
pub fn with_size(self, size: Size) -> Font { pub fn with_size(self, size: Size) -> Font {
Font { size, ..self } Font { size, ..self }
} }
// Get normal font description /// Get normal font description.
pub fn normal(&self) -> &FontDescription { pub fn normal(&self) -> &FontDescription {
&self.normal &self.normal
} }
// Get bold font description /// Get bold font description.
pub fn bold(&self) -> FontDescription { pub fn bold(&self) -> FontDescription {
self.bold.desc(&self.normal) self.bold.desc(&self.normal)
} }
// Get italic font description /// Get italic font description.
pub fn italic(&self) -> FontDescription { pub fn italic(&self) -> FontDescription {
self.italic.desc(&self.normal) self.italic.desc(&self.normal)
} }
// Get bold italic font description /// Get bold italic font description.
pub fn bold_italic(&self) -> FontDescription { pub fn bold_italic(&self) -> FontDescription {
self.bold_italic.desc(&self.normal) self.bold_italic.desc(&self.normal)
} }
@ -108,7 +108,7 @@ fn default_font_size() -> Size {
Size::new(11.) Size::new(11.)
} }
/// Description of the normal font /// Description of the normal font.
#[serde(default)] #[serde(default)]
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub struct FontDescription { pub struct FontDescription {
@ -132,7 +132,7 @@ impl Default for FontDescription {
} }
} }
/// Description of the italic and bold font /// Description of the italic and bold font.
#[serde(default)] #[serde(default)]
#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)]
pub struct SecondaryFontDescription { pub struct SecondaryFontDescription {
@ -198,7 +198,7 @@ impl DeserializeSize for Size {
.deserialize_any(NumVisitor::<D> { _marker: PhantomData }) .deserialize_any(NumVisitor::<D> { _marker: PhantomData })
.map(|v| Size::new(v as _)); .map(|v| Size::new(v as _));
// Use default font size as fallback // Use default font size as fallback.
match size { match size {
Ok(size) => Ok(size), Ok(size) => Ok(size),
Err(err) => { Err(err) => {

View File

@ -44,68 +44,68 @@ const DEFAULT_CURSOR_THICKNESS: f32 = 0.15;
pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>; pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>;
/// Top-level config type /// Top-level config type.
#[derive(Debug, PartialEq, Default, Deserialize)] #[derive(Debug, PartialEq, Default, Deserialize)]
pub struct Config<T> { pub struct Config<T> {
/// Pixel padding /// Pixel padding.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub padding: Option<Delta<u8>>, pub padding: Option<Delta<u8>>,
/// TERM env variable /// TERM env variable.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub env: HashMap<String, String>, pub env: HashMap<String, String>,
/// Font configuration /// Font configuration.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub font: Font, pub font: Font,
/// Should draw bold text with brighter colors instead of bold font /// Should draw bold text with brighter colors instead of bold font.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
draw_bold_text_with_bright_colors: bool, draw_bold_text_with_bright_colors: bool,
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub colors: Colors, pub colors: Colors,
/// Background opacity from 0.0 to 1.0 /// Background opacity from 0.0 to 1.0.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
background_opacity: Percentage, background_opacity: Percentage,
/// Window configuration /// Window configuration.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub window: WindowConfig, pub window: WindowConfig,
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub selection: Selection, pub selection: Selection,
/// Path to a shell program to run on startup /// Path to a shell program to run on startup.
#[serde(default, deserialize_with = "from_string_or_deserialize")] #[serde(default, deserialize_with = "from_string_or_deserialize")]
pub shell: Option<Shell<'static>>, pub shell: Option<Shell<'static>>,
/// Path where config was loaded from /// Path where config was loaded from.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub config_path: Option<PathBuf>, pub config_path: Option<PathBuf>,
/// Visual bell configuration /// Visual bell configuration.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub visual_bell: VisualBellConfig, pub visual_bell: VisualBellConfig,
/// Use dynamic title /// Use dynamic title.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
dynamic_title: DefaultTrueBool, dynamic_title: DefaultTrueBool,
/// Live config reload /// Live config reload.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
live_config_reload: DefaultTrueBool, live_config_reload: DefaultTrueBool,
/// How much scrolling history to keep /// How much scrolling history to keep.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub scrolling: Scrolling, pub scrolling: Scrolling,
/// Cursor configuration /// Cursor configuration.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub cursor: Cursor, pub cursor: Cursor,
/// Use WinPTY backend even if ConPTY is available /// Use WinPTY backend even if ConPTY is available.
#[cfg(windows)] #[cfg(windows)]
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub winpty_backend: bool, pub winpty_backend: bool,
@ -114,19 +114,19 @@ pub struct Config<T> {
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
alt_send_esc: DefaultTrueBool, alt_send_esc: DefaultTrueBool,
/// Shell startup directory /// Shell startup directory.
#[serde(default, deserialize_with = "option_explicit_none")] #[serde(default, deserialize_with = "option_explicit_none")]
pub working_directory: Option<PathBuf>, pub working_directory: Option<PathBuf>,
/// Debug options /// Debug options.
#[serde(default, deserialize_with = "failure_default")] #[serde(default, deserialize_with = "failure_default")]
pub debug: Debug, pub debug: Debug,
/// Additional configuration options not directly required by the terminal /// Additional configuration options not directly required by the terminal.
#[serde(flatten)] #[serde(flatten)]
pub ui_config: T, pub ui_config: T,
/// Remain open after child process exits /// Remain open after child process exits.
#[serde(skip)] #[serde(skip)]
pub hold: bool, pub hold: bool,
@ -149,13 +149,13 @@ impl<T> Config<T> {
self.draw_bold_text_with_bright_colors self.draw_bold_text_with_bright_colors
} }
/// Should show render timer /// Should show render timer.
#[inline] #[inline]
pub fn render_timer(&self) -> bool { pub fn render_timer(&self) -> bool {
self.render_timer.unwrap_or(self.debug.render_timer) self.render_timer.unwrap_or(self.debug.render_timer)
} }
/// Live config reload /// Live config reload.
#[inline] #[inline]
pub fn live_config_reload(&self) -> bool { pub fn live_config_reload(&self) -> bool {
self.live_config_reload.0 self.live_config_reload.0
@ -200,13 +200,13 @@ impl<T> Config<T> {
self.dynamic_title.0 = dynamic_title; self.dynamic_title.0 = dynamic_title;
} }
/// Send escape sequences using the alt key /// Send escape sequences using the alt key.
#[inline] #[inline]
pub fn alt_send_esc(&self) -> bool { pub fn alt_send_esc(&self) -> bool {
self.alt_send_esc.0 self.alt_send_esc.0
} }
/// Keep the log file after quitting Alacritty /// Keep the log file after quitting Alacritty.
#[inline] #[inline]
pub fn persistent_logging(&self) -> bool { pub fn persistent_logging(&self) -> bool {
self.persistent_logging.unwrap_or(self.debug.persistent_logging) self.persistent_logging.unwrap_or(self.debug.persistent_logging)
@ -316,14 +316,14 @@ impl FromString for Option<Shell<'_>> {
} }
} }
/// A delta for a point in a 2 dimensional plane /// A delta for a point in a 2 dimensional plane.
#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))] #[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
pub struct Delta<T: Default + PartialEq + Eq> { pub struct Delta<T: Default + PartialEq + Eq> {
/// Horizontal change /// Horizontal change.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub x: T, pub x: T,
/// Vertical change /// Vertical change.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub y: T, pub y: T,
} }
@ -407,7 +407,7 @@ where
}) })
} }
// Used over From<String>, to allow implementation for foreign types // Used over From<String>, to allow implementation for foreign types.
pub trait FromString { pub trait FromString {
fn from(input: String) -> Self; fn from(input: String) -> Self;
} }

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Deserializer};
use crate::config::{failure_default, LOG_TARGET_CONFIG, MAX_SCROLLBACK_LINES}; use crate::config::{failure_default, LOG_TARGET_CONFIG, MAX_SCROLLBACK_LINES};
/// Struct for scrolling related settings /// Struct for scrolling related settings.
#[serde(default)] #[serde(default)]
#[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)] #[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct Scrolling { pub struct Scrolling {
@ -34,7 +34,7 @@ impl Scrolling {
self.faux_multiplier.map(|sm| sm.0) self.faux_multiplier.map(|sm| sm.0)
} }
// Update the history size, used in ref tests // Update the history size, used in ref tests.
pub fn set_history(&mut self, history: u32) { pub fn set_history(&mut self, history: u32) {
self.history = ScrollingHistory(history); self.history = ScrollingHistory(history);
} }

View File

@ -8,15 +8,15 @@ use crate::term::color::Rgb;
#[serde(default)] #[serde(default)]
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] #[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct VisualBellConfig { pub struct VisualBellConfig {
/// Visual bell animation function /// Visual bell animation function.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub animation: VisualBellAnimation, pub animation: VisualBellAnimation,
/// Visual bell duration in milliseconds /// Visual bell duration in milliseconds.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub duration: u16, pub duration: u16,
/// Visual bell flash color /// Visual bell flash color.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub color: Rgb, pub color: Rgb,
} }
@ -32,7 +32,7 @@ impl Default for VisualBellConfig {
} }
impl VisualBellConfig { impl VisualBellConfig {
/// Visual bell duration in milliseconds /// Visual bell duration in milliseconds.
#[inline] #[inline]
pub fn duration(&self) -> Duration { pub fn duration(&self) -> Duration {
Duration::from_millis(u64::from(self.duration)) Duration::from_millis(u64::from(self.duration))
@ -43,15 +43,25 @@ impl VisualBellConfig {
/// Penner's Easing Functions. /// Penner's Easing Functions.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
pub enum VisualBellAnimation { pub enum VisualBellAnimation {
Ease, // CSS // CSS animation.
EaseOut, // CSS Ease,
EaseOutSine, // Penner // CSS animation.
EaseOutQuad, // Penner EaseOut,
EaseOutCubic, // Penner // Penner animation.
EaseOutQuart, // Penner EaseOutSine,
EaseOutQuint, // Penner // Penner animation.
EaseOutExpo, // Penner EaseOutQuad,
EaseOutCirc, // Penner // Penner animation.
EaseOutCubic,
// Penner animation.
EaseOutQuart,
// Penner animation.
EaseOutQuint,
// Penner animation.
EaseOutExpo,
// Penner animation.
EaseOutCirc,
// Penner animation.
Linear, Linear,
} }

View File

@ -13,47 +13,47 @@ pub const DEFAULT_NAME: &str = "Alacritty";
#[serde(default)] #[serde(default)]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct WindowConfig { pub struct WindowConfig {
/// Initial dimensions /// Initial dimensions.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub dimensions: Dimensions, pub dimensions: Dimensions,
/// Initial position /// Initial position.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub position: Option<Delta<i32>>, pub position: Option<Delta<i32>>,
/// Pixel padding /// Pixel padding.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub padding: Delta<u8>, pub padding: Delta<u8>,
/// Draw the window with title bar / borders /// Draw the window with title bar / borders.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub decorations: Decorations, pub decorations: Decorations,
/// Spread out additional padding evenly /// Spread out additional padding evenly.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub dynamic_padding: bool, pub dynamic_padding: bool,
/// Startup mode /// Startup mode.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
startup_mode: StartupMode, startup_mode: StartupMode,
/// Window title /// Window title.
#[serde(default = "default_title")] #[serde(default = "default_title")]
pub title: String, pub title: String,
/// Window class /// Window class.
#[serde(deserialize_with = "from_string_or_deserialize")] #[serde(deserialize_with = "from_string_or_deserialize")]
pub class: Class, pub class: Class,
/// XEmbed parent /// XEmbed parent.
#[serde(skip)] #[serde(skip)]
pub embed: Option<c_ulong>, pub embed: Option<c_ulong>,
/// GTK theme variant /// GTK theme variant.
#[serde(deserialize_with = "option_explicit_none")] #[serde(deserialize_with = "option_explicit_none")]
pub gtk_theme_variant: Option<String>, pub gtk_theme_variant: Option<String>,
/// TODO: DEPRECATED // TODO: DEPRECATED
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
pub start_maximized: Option<bool>, pub start_maximized: Option<bool>,
} }
@ -124,17 +124,17 @@ impl Default for Decorations {
} }
} }
/// Window Dimensions /// Window Dimensions.
/// ///
/// Newtype to avoid passing values incorrectly /// Newtype to avoid passing values incorrectly.
#[serde(default)] #[serde(default)]
#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] #[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
pub struct Dimensions { pub struct Dimensions {
/// Window width in character columns /// Window width in character columns.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
columns: Column, columns: Column,
/// Window Height in character lines /// Window Height in character lines.
#[serde(deserialize_with = "failure_default")] #[serde(deserialize_with = "failure_default")]
lines: Line, lines: Line,
} }
@ -144,20 +144,20 @@ impl Dimensions {
Dimensions { columns, lines } Dimensions { columns, lines }
} }
/// Get lines /// Get lines.
#[inline] #[inline]
pub fn lines_u32(&self) -> u32 { pub fn lines_u32(&self) -> u32 {
self.lines.0 as u32 self.lines.0 as u32
} }
/// Get columns /// Get columns.
#[inline] #[inline]
pub fn columns_u32(&self) -> u32 { pub fn columns_u32(&self) -> u32 {
self.columns.0 as u32 self.columns.0 as u32
} }
} }
/// Window class hint /// Window class hint.
#[serde(default)] #[serde(default)]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Class { pub struct Class {

View File

@ -16,20 +16,20 @@ pub enum Event {
Exit, Exit,
} }
/// Byte sequences are sent to a `Notify` in response to some events /// Byte sequences are sent to a `Notify` in response to some events.
pub trait Notify { pub trait Notify {
/// Notify that an escape sequence should be written to the pty /// Notify that an escape sequence should be written to the PTY.
/// ///
/// TODO this needs to be able to error somehow /// TODO this needs to be able to error somehow.
fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, _: B); fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, _: B);
} }
/// Types that are interested in when the display is resized /// Types that are interested in when the display is resized.
pub trait OnResize { pub trait OnResize {
fn on_resize(&mut self, size: &SizeInfo); fn on_resize(&mut self, size: &SizeInfo);
} }
/// Event Loop for notifying the renderer about terminal events /// Event Loop for notifying the renderer about terminal events.
pub trait EventListener { pub trait EventListener {
fn send_event(&self, event: Event); fn send_event(&self, event: Event);
} }

View File

@ -1,4 +1,4 @@
//! The main event loop which performs I/O on the pseudoterminal //! The main event loop which performs I/O on the pseudoterminal.
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fs::File; use std::fs::File;
@ -20,25 +20,25 @@ use crate::term::{SizeInfo, Term};
use crate::tty; use crate::tty;
use crate::util::thread; use crate::util::thread;
/// Max bytes to read from the PTY /// Max bytes to read from the PTY.
const MAX_READ: usize = 0x10_000; const MAX_READ: usize = 0x10_000;
/// Messages that may be sent to the `EventLoop` /// Messages that may be sent to the `EventLoop`.
#[derive(Debug)] #[derive(Debug)]
pub enum Msg { pub enum Msg {
/// Data that should be written to the pty /// Data that should be written to the PTY.
Input(Cow<'static, [u8]>), Input(Cow<'static, [u8]>),
/// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down.
Shutdown, Shutdown,
/// Instruction to resize the pty /// Instruction to resize the PTY.
Resize(SizeInfo), Resize(SizeInfo),
} }
/// The main event!.. loop. /// The main event!.. loop.
/// ///
/// Handles all the pty I/O and runs the pty parser which updates terminal /// Handles all the PTY I/O and runs the PTY parser which updates terminal
/// state. /// state.
pub struct EventLoop<T: tty::EventedPty, U: EventListener> { pub struct EventLoop<T: tty::EventedPty, U: EventListener> {
poll: mio::Poll, poll: mio::Poll,
@ -57,7 +57,7 @@ struct Writing {
written: usize, written: usize,
} }
/// All of the mutable state needed to run the event loop /// All of the mutable state needed to run the event loop.
/// ///
/// Contains list of items to write, current write state, etc. Anything that /// Contains list of items to write, current write state, etc. Anything that
/// would otherwise be mutated on the `EventLoop` goes here. /// would otherwise be mutated on the `EventLoop` goes here.
@ -152,7 +152,7 @@ where
T: tty::EventedPty + event::OnResize + Send + 'static, T: tty::EventedPty + event::OnResize + Send + 'static,
U: EventListener + Send + 'static, U: EventListener + Send + 'static,
{ {
/// Create a new event loop /// Create a new event loop.
pub fn new<V>( pub fn new<V>(
terminal: Arc<FairMutex<Term<U>>>, terminal: Arc<FairMutex<Term<U>>>,
event_proxy: U, event_proxy: U,
@ -176,9 +176,9 @@ where
self.tx.clone() self.tx.clone()
} }
// Drain the channel /// Drain the channel.
// ///
// Returns `false` when a shutdown message was received. /// Returns `false` when a shutdown message was received.
fn drain_recv_channel(&mut self, state: &mut State) -> bool { fn drain_recv_channel(&mut self, state: &mut State) -> bool {
while let Ok(msg) = self.rx.try_recv() { while let Ok(msg) = self.rx.try_recv() {
match msg { match msg {
@ -191,7 +191,7 @@ where
true true
} }
// Returns a `bool` indicating whether or not the event loop should continue running /// Returns a `bool` indicating whether or not the event loop should continue running.
#[inline] #[inline]
fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool { fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool {
if !self.drain_recv_channel(state) { if !self.drain_recv_channel(state) {
@ -234,18 +234,18 @@ where
// Get reference to terminal. Lock is acquired on initial // Get reference to terminal. Lock is acquired on initial
// iteration and held until there's no bytes left to parse // iteration and held until there's no bytes left to parse
// or we've reached MAX_READ. // or we've reached `MAX_READ`.
if terminal.is_none() { if terminal.is_none() {
terminal = Some(self.terminal.lock()); terminal = Some(self.terminal.lock());
} }
let terminal = terminal.as_mut().unwrap(); let terminal = terminal.as_mut().unwrap();
// Run the parser // Run the parser.
for byte in &buf[..got] { for byte in &buf[..got] {
state.parser.advance(&mut **terminal, *byte, &mut self.pty.writer()); state.parser.advance(&mut **terminal, *byte, &mut self.pty.writer());
} }
// Exit if we've processed enough bytes // Exit if we've processed enough bytes.
if processed > MAX_READ { if processed > MAX_READ {
break; break;
} }
@ -260,7 +260,7 @@ where
} }
if processed > 0 { if processed > 0 {
// Queue terminal redraw // Queue terminal redraw.
self.event_proxy.send_event(Event::Wakeup); self.event_proxy.send_event(Event::Wakeup);
} }
@ -300,7 +300,7 @@ where
} }
pub fn spawn(mut self) -> thread::JoinHandle<(Self, State)> { pub fn spawn(mut self) -> thread::JoinHandle<(Self, State)> {
thread::spawn_named("pty reader", move || { thread::spawn_named("PTY reader", move || {
let mut state = State::default(); let mut state = State::default();
let mut buf = [0u8; MAX_READ]; let mut buf = [0u8; MAX_READ];
@ -311,7 +311,7 @@ where
let channel_token = tokens.next().unwrap(); let channel_token = tokens.next().unwrap();
self.poll.register(&self.rx, channel_token, Ready::readable(), poll_opts).unwrap(); self.poll.register(&self.rx, channel_token, Ready::readable(), poll_opts).unwrap();
// Register TTY through EventedRW interface // Register TTY through EventedRW interface.
self.pty.register(&self.poll, &mut tokens, Ready::readable(), poll_opts).unwrap(); self.pty.register(&self.poll, &mut tokens, Ready::readable(), poll_opts).unwrap();
let mut events = Events::with_capacity(1024); let mut events = Events::with_capacity(1024);
@ -355,13 +355,14 @@ where
#[cfg(unix)] #[cfg(unix)]
{ {
if UnixReady::from(event.readiness()).is_hup() { if UnixReady::from(event.readiness()).is_hup() {
// don't try to do I/O on a dead PTY // Don't try to do I/O on a dead PTY.
continue; continue;
} }
} }
if event.readiness().is_readable() { if event.readiness().is_readable() {
if let Err(e) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) { if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut())
{
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
// On Linux, a `read` on the master side of a PTY can fail // On Linux, a `read` on the master side of a PTY can fail
@ -369,19 +370,19 @@ where
// just loop back round for the inevitable `Exited` event. // just loop back round for the inevitable `Exited` event.
// This sucks, but checking the process is either racy or // This sucks, but checking the process is either racy or
// blocking. // blocking.
if e.kind() == ErrorKind::Other { if err.kind() == ErrorKind::Other {
continue; continue;
} }
} }
error!("Error reading from PTY in event loop: {}", e); error!("Error reading from PTY in event loop: {}", err);
break 'event_loop; break 'event_loop;
} }
} }
if event.readiness().is_writable() { if event.readiness().is_writable() {
if let Err(e) = self.pty_write(&mut state) { if let Err(err) = self.pty_write(&mut state) {
error!("Error writing to PTY in event loop: {}", e); error!("Error writing to PTY in event loop: {}", err);
break 'event_loop; break 'event_loop;
} }
} }
@ -390,16 +391,16 @@ where
} }
} }
// Register write interest if necessary // Register write interest if necessary.
let mut interest = Ready::readable(); let mut interest = Ready::readable();
if state.needs_write() { if state.needs_write() {
interest.insert(Ready::writable()); interest.insert(Ready::writable());
} }
// Reregister with new interest // Reregister with new interest.
self.pty.reregister(&self.poll, interest, poll_opts).unwrap(); self.pty.reregister(&self.poll, interest, poll_opts).unwrap();
} }
// The evented instances are not dropped here so deregister them explicitly // The evented instances are not dropped here so deregister them explicitly.
let _ = self.poll.deregister(&self.rx); let _ = self.poll.deregister(&self.rx);
let _ = self.pty.deregister(&self.poll); let _ = self.pty.deregister(&self.poll);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! A specialized 2d grid implementation optimized for use in a terminal. //! A specialized 2D grid implementation optimized for use in a terminal.
use std::cmp::{max, min, Ordering}; use std::cmp::{max, min, Ordering};
use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo}; use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo};
@ -32,7 +32,7 @@ mod tests;
mod storage; mod storage;
use self::storage::Storage; use self::storage::Storage;
/// Bidirection iterator /// Bidirectional iterator.
pub trait BidirectionalIterator: Iterator { pub trait BidirectionalIterator: Iterator {
fn prev(&mut self) -> Option<Self::Item>; fn prev(&mut self) -> Option<Self::Item>;
} }
@ -55,7 +55,7 @@ impl<T> Deref for Indexed<T> {
impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> { impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// Compare struct fields and check result of grid comparison // Compare struct fields and check result of grid comparison.
self.raw.eq(&other.raw) self.raw.eq(&other.raw)
&& self.cols.eq(&other.cols) && self.cols.eq(&other.cols)
&& self.lines.eq(&other.lines) && self.lines.eq(&other.lines)
@ -72,11 +72,11 @@ pub trait GridCell {
/// Fast equality approximation. /// Fast equality approximation.
/// ///
/// This is a faster alternative to [`PartialEq`], /// This is a faster alternative to [`PartialEq`],
/// but might report inequal cells as equal. /// but might report unequal cells as equal.
fn fast_eq(&self, other: Self) -> bool; fn fast_eq(&self, other: Self) -> bool;
} }
/// Represents the terminal display contents /// Represents the terminal display contents.
/// ///
/// ```notrust /// ```notrust
/// ┌─────────────────────────┐ <-- max_scroll_limit + lines /// ┌─────────────────────────┐ <-- max_scroll_limit + lines
@ -152,7 +152,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
} else if point.line >= self.display_offset + self.lines.0 { } else if point.line >= self.display_offset + self.lines.0 {
Point::new(Line(0), Column(0)) Point::new(Line(0), Column(0))
} else { } else {
// Since edgecases are handled, conversion is identical as visible to buffer // Since edge-cases are handled, conversion is identical as visible to buffer.
self.visible_to_buffer(point.into()).into() self.visible_to_buffer(point.into()).into()
} }
} }
@ -162,7 +162,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, col: point.col } Point { line: self.lines.0 + self.display_offset - point.line.0 - 1, col: point.col }
} }
/// Update the size of the scrollback history /// Update the size of the scrollback history.
pub fn update_history(&mut self, history_size: usize) { pub fn update_history(&mut self, history_size: usize) {
let current_history_size = self.history_size(); let current_history_size = self.history_size();
if current_history_size > history_size { if current_history_size > history_size {
@ -199,7 +199,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
cursor_pos: &mut Point, cursor_pos: &mut Point,
template: &T, template: &T,
) { ) {
// Check that there's actually work to do and return early if not // Check that there's actually work to do and return early if not.
if lines == self.lines && cols == self.cols { if lines == self.lines && cols == self.cols {
return; return;
} }
@ -231,7 +231,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
} }
} }
/// Add lines to the visible area /// Add lines to the visible area.
/// ///
/// Alacritty keeps the cursor at the bottom of the terminal as long as there /// Alacritty keeps the cursor at the bottom of the terminal as long as there
/// is scrollback available. Once scrollback is exhausted, new lines are /// is scrollback available. Once scrollback is exhausted, new lines are
@ -239,18 +239,18 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
fn grow_lines(&mut self, new_line_count: Line, cursor_pos: &mut Point, template: &T) { fn grow_lines(&mut self, new_line_count: Line, cursor_pos: &mut Point, template: &T) {
let lines_added = new_line_count - self.lines; let lines_added = new_line_count - self.lines;
// Need to "resize" before updating buffer // Need to "resize" before updating buffer.
self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template));
self.lines = new_line_count; self.lines = new_line_count;
let history_size = self.history_size(); let history_size = self.history_size();
let from_history = min(history_size, lines_added.0); let from_history = min(history_size, lines_added.0);
// Move cursor down for all lines pulled from history // Move cursor down for all lines pulled from history.
cursor_pos.line += from_history; cursor_pos.line += from_history;
if from_history != lines_added.0 { if from_history != lines_added.0 {
// Move existing lines up for every line that couldn't be pulled from history // Move existing lines up for every line that couldn't be pulled from history.
self.scroll_up(&(Line(0)..new_line_count), lines_added - from_history, template); self.scroll_up(&(Line(0)..new_line_count), lines_added - from_history, template);
} }
@ -258,9 +258,9 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
self.display_offset = self.display_offset.saturating_sub(*lines_added); self.display_offset = self.display_offset.saturating_sub(*lines_added);
} }
// Grow number of columns in each row, reflowing if necessary /// Grow number of columns in each row, reflowing if necessary.
fn grow_cols(&mut self, reflow: bool, cols: Column, cursor_pos: &mut Point, template: &T) { fn grow_cols(&mut self, reflow: bool, cols: Column, cursor_pos: &mut Point, template: &T) {
// Check if a row needs to be wrapped // Check if a row needs to be wrapped.
let should_reflow = |row: &Row<T>| -> bool { let should_reflow = |row: &Row<T>| -> bool {
let len = Column(row.len()); let len = Column(row.len());
reflow && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE) reflow && len < cols && row[len - 1].flags().contains(Flags::WRAPLINE)
@ -269,7 +269,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
let mut new_empty_lines = 0; let mut new_empty_lines = 0;
let mut reversed: Vec<Row<T>> = Vec::with_capacity(self.raw.len()); let mut reversed: Vec<Row<T>> = Vec::with_capacity(self.raw.len());
for (i, mut row) in self.raw.drain().enumerate().rev() { for (i, mut row) in self.raw.drain().enumerate().rev() {
// Check if reflowing shoud be performed // Check if reflowing should be performed.
let last_row = match reversed.last_mut() { let last_row = match reversed.last_mut() {
Some(last_row) if should_reflow(last_row) => last_row, Some(last_row) if should_reflow(last_row) => last_row,
_ => { _ => {
@ -278,12 +278,12 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
}, },
}; };
// Remove wrap flag before appending additional cells // Remove wrap flag before appending additional cells.
if let Some(cell) = last_row.last_mut() { if let Some(cell) = last_row.last_mut() {
cell.flags_mut().remove(Flags::WRAPLINE); cell.flags_mut().remove(Flags::WRAPLINE);
} }
// Remove leading spacers when reflowing wide char to the previous line // Remove leading spacers when reflowing wide char to the previous line.
let last_len = last_row.len(); let last_len = last_row.len();
if last_len >= 2 if last_len >= 2
&& !last_row[Column(last_len - 2)].flags().contains(Flags::WIDE_CHAR) && !last_row[Column(last_len - 2)].flags().contains(Flags::WIDE_CHAR)
@ -292,10 +292,10 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
last_row.shrink(Column(last_len - 1)); last_row.shrink(Column(last_len - 1));
} }
// Append as many cells from the next line as possible // Append as many cells from the next line as possible.
let len = min(row.len(), cols.0 - last_row.len()); let len = min(row.len(), cols.0 - last_row.len());
// Insert leading spacer when there's not enough room for reflowing wide char // Insert leading spacer when there's not enough room for reflowing wide char.
let mut cells = if row[Column(len - 1)].flags().contains(Flags::WIDE_CHAR) { let mut cells = if row[Column(len - 1)].flags().contains(Flags::WIDE_CHAR) {
let mut cells = row.front_split_off(len - 1); let mut cells = row.front_split_off(len - 1);
@ -312,28 +312,28 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
if row.is_empty() { if row.is_empty() {
if i + reversed.len() < self.lines.0 { if i + reversed.len() < self.lines.0 {
// Add new line and move lines up if we can't pull from history // Add new line and move lines up if we can't pull from history.
cursor_pos.line = Line(cursor_pos.line.saturating_sub(1)); cursor_pos.line = Line(cursor_pos.line.saturating_sub(1));
new_empty_lines += 1; new_empty_lines += 1;
} else if i < self.display_offset { } else if i < self.display_offset {
// Keep viewport in place if line is outside of the visible area // Keep viewport in place if line is outside of the visible area.
self.display_offset = self.display_offset.saturating_sub(1); self.display_offset = self.display_offset.saturating_sub(1);
} }
// Don't push line into the new buffer // Don't push line into the new buffer.
continue; continue;
} else if let Some(cell) = last_row.last_mut() { } else if let Some(cell) = last_row.last_mut() {
// Set wrap flag if next line still has cells // Set wrap flag if next line still has cells.
cell.flags_mut().insert(Flags::WRAPLINE); cell.flags_mut().insert(Flags::WRAPLINE);
} }
reversed.push(row); reversed.push(row);
} }
// Add padding lines // Add padding lines.
reversed.append(&mut vec![Row::new(cols, template); new_empty_lines]); reversed.append(&mut vec![Row::new(cols, template); new_empty_lines]);
// Fill remaining cells and reverse iterator // Fill remaining cells and reverse iterator.
let mut new_raw = Vec::with_capacity(reversed.len()); let mut new_raw = Vec::with_capacity(reversed.len());
for mut row in reversed.drain(..).rev() { for mut row in reversed.drain(..).rev() {
if row.len() < cols.0 { if row.len() < cols.0 {
@ -348,18 +348,18 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
self.cols = cols; self.cols = cols;
} }
// Shrink number of columns in each row, reflowing if necessary /// Shrink number of columns in each row, reflowing if necessary.
fn shrink_cols(&mut self, reflow: bool, cols: Column, template: &T) { fn shrink_cols(&mut self, reflow: bool, cols: Column, template: &T) {
let mut new_raw = Vec::with_capacity(self.raw.len()); let mut new_raw = Vec::with_capacity(self.raw.len());
let mut buffered = None; let mut buffered = None;
for (i, mut row) in self.raw.drain().enumerate().rev() { for (i, mut row) in self.raw.drain().enumerate().rev() {
// Append lines left over from previous row // Append lines left over from previous row.
if let Some(buffered) = buffered.take() { if let Some(buffered) = buffered.take() {
row.append_front(buffered); row.append_front(buffered);
} }
loop { loop {
// Check if reflowing shoud be performed // Check if reflowing should be performed.
let mut wrapped = match row.shrink(cols) { let mut wrapped = match row.shrink(cols) {
Some(wrapped) if reflow => wrapped, Some(wrapped) if reflow => wrapped,
_ => { _ => {
@ -368,7 +368,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
}, },
}; };
// Insert spacer if a wide char would be wrapped into the last column // Insert spacer if a wide char would be wrapped into the last column.
if row.len() >= cols.0 && row[cols - 1].flags().contains(Flags::WIDE_CHAR) { if row.len() >= cols.0 && row[cols - 1].flags().contains(Flags::WIDE_CHAR) {
wrapped.insert(0, row[cols - 1]); wrapped.insert(0, row[cols - 1]);
@ -377,7 +377,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
row[cols - 1] = spacer; row[cols - 1] = spacer;
} }
// Remove wide char spacer before shrinking // Remove wide char spacer before shrinking.
let len = wrapped.len(); let len = wrapped.len();
if (len == 1 || (len >= 2 && !wrapped[len - 2].flags().contains(Flags::WIDE_CHAR))) if (len == 1 || (len >= 2 && !wrapped[len - 2].flags().contains(Flags::WIDE_CHAR)))
&& wrapped[len - 1].flags().contains(Flags::WIDE_CHAR_SPACER) && wrapped[len - 1].flags().contains(Flags::WIDE_CHAR_SPACER)
@ -394,7 +394,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
new_raw.push(row); new_raw.push(row);
// Set line as wrapped if cells got removed // Set line as wrapped if cells got removed.
if let Some(cell) = new_raw.last_mut().and_then(|r| r.last_mut()) { if let Some(cell) = new_raw.last_mut().and_then(|r| r.last_mut()) {
cell.flags_mut().insert(Flags::WRAPLINE); cell.flags_mut().insert(Flags::WRAPLINE);
} }
@ -405,21 +405,21 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
.unwrap_or(false) .unwrap_or(false)
&& wrapped.len() < cols.0 && wrapped.len() < cols.0
{ {
// Make sure previous wrap flag doesn't linger around // Make sure previous wrap flag doesn't linger around.
if let Some(cell) = wrapped.last_mut() { if let Some(cell) = wrapped.last_mut() {
cell.flags_mut().remove(Flags::WRAPLINE); cell.flags_mut().remove(Flags::WRAPLINE);
} }
// Add removed cells to start of next row // Add removed cells to start of next row.
buffered = Some(wrapped); buffered = Some(wrapped);
break; break;
} else { } else {
// Make sure viewport doesn't move if line is outside of the visible area // Make sure viewport doesn't move if line is outside of the visible area.
if i < self.display_offset { if i < self.display_offset {
self.display_offset = min(self.display_offset + 1, self.max_scroll_limit); self.display_offset = min(self.display_offset + 1, self.max_scroll_limit);
} }
// Make sure new row is at least as long as new width // Make sure new row is at least as long as new width.
let occ = wrapped.len(); let occ = wrapped.len();
if occ < cols.0 { if occ < cols.0 {
wrapped.append(&mut vec![*template; cols.0 - occ]); wrapped.append(&mut vec![*template; cols.0 - occ]);
@ -435,7 +435,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
self.cols = cols; self.cols = cols;
} }
/// Remove lines from the visible area /// Remove lines from the visible area.
/// ///
/// The behavior in Terminal.app and iTerm.app is to keep the cursor at the /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the
/// bottom of the screen. This is achieved by pushing history "out the top" /// bottom of the screen. This is achieved by pushing history "out the top"
@ -443,7 +443,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
/// ///
/// Alacritty takes the same approach. /// Alacritty takes the same approach.
fn shrink_lines(&mut self, target: Line, cursor_pos: &mut Point, template: &T) { fn shrink_lines(&mut self, target: Line, cursor_pos: &mut Point, template: &T) {
// Scroll up to keep cursor inside the window // Scroll up to keep cursor inside the window.
let required_scrolling = (cursor_pos.line + 1).saturating_sub(target.0); let required_scrolling = (cursor_pos.line + 1).saturating_sub(target.0);
if required_scrolling > 0 { if required_scrolling > 0 {
self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling), template); self.scroll_up(&(Line(0)..self.lines), Line(required_scrolling), template);
@ -476,24 +476,24 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
self.decrease_scroll_limit(*positions); self.decrease_scroll_limit(*positions);
// Now, restore any scroll region lines // Now, restore any scroll region lines.
let lines = self.lines; let lines = self.lines;
for i in IndexRange(region.end..lines) { for i in IndexRange(region.end..lines) {
self.raw.swap_lines(i, i + positions); self.raw.swap_lines(i, i + positions);
} }
// Finally, reset recycled lines // Finally, reset recycled lines.
for i in IndexRange(Line(0)..positions) { for i in IndexRange(Line(0)..positions) {
self.raw[i].reset(&template); self.raw[i].reset(&template);
} }
} else { } else {
// Rotate selection to track content // Rotate selection to track content.
self.selection = self self.selection = self
.selection .selection
.take() .take()
.and_then(|s| s.rotate(num_lines, num_cols, region, -(*positions as isize))); .and_then(|s| s.rotate(num_lines, num_cols, region, -(*positions as isize)));
// Subregion rotation // Subregion rotation.
for line in IndexRange((region.start + positions)..region.end).rev() { for line in IndexRange((region.start + positions)..region.end).rev() {
self.raw.swap_lines(line, line - positions); self.raw.swap_lines(line, line - positions);
} }
@ -504,7 +504,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
} }
} }
/// scroll_up moves lines at the bottom towards the top /// Move lines at the bottom towards the top.
/// ///
/// This is the performance-sensitive part of scrolling. /// This is the performance-sensitive part of scrolling.
pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: &T) { pub fn scroll_up(&mut self, region: &Range<Line>, positions: Line, template: &T) {
@ -512,7 +512,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
let num_cols = self.num_cols().0; let num_cols = self.num_cols().0;
if region.start == Line(0) { if region.start == Line(0) {
// Update display offset when not pinned to active area // Update display offset when not pinned to active area.
if self.display_offset != 0 { if self.display_offset != 0 {
self.display_offset = min(self.display_offset + *positions, self.max_scroll_limit); self.display_offset = min(self.display_offset + *positions, self.max_scroll_limit);
} }
@ -537,25 +537,25 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
self.raw.swap(i, i + *positions); self.raw.swap(i, i + *positions);
} }
// Finally, reset recycled lines // Finally, reset recycled lines.
// //
// Recycled lines are just above the end of the scrolling region. // Recycled lines are just above the end of the scrolling region.
for i in 0..*positions { for i in 0..*positions {
self.raw[i + fixed_lines].reset(&template); self.raw[i + fixed_lines].reset(&template);
} }
} else { } else {
// Rotate selection to track content // Rotate selection to track content.
self.selection = self self.selection = self
.selection .selection
.take() .take()
.and_then(|s| s.rotate(num_lines, num_cols, region, *positions as isize)); .and_then(|s| s.rotate(num_lines, num_cols, region, *positions as isize));
// Subregion rotation // Subregion rotation.
for line in IndexRange(region.start..(region.end - positions)) { for line in IndexRange(region.start..(region.end - positions)) {
self.raw.swap_lines(line, line + positions); self.raw.swap_lines(line, line + positions);
} }
// Clear reused lines // Clear reused lines.
for line in IndexRange((region.end - positions)..region.end) { for line in IndexRange((region.end - positions)..region.end) {
self.raw[line].reset(&template); self.raw[line].reset(&template);
} }
@ -575,23 +575,23 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
let positions = self.lines - iter.cur.line; let positions = self.lines - iter.cur.line;
let region = Line(0)..self.num_lines(); let region = Line(0)..self.num_lines();
// Reset display offset // Reset display offset.
self.display_offset = 0; self.display_offset = 0;
// Clear the viewport // Clear the viewport.
self.scroll_up(&region, positions, template); self.scroll_up(&region, positions, template);
// Reset rotated lines // Reset rotated lines.
for i in positions.0..self.lines.0 { for i in positions.0..self.lines.0 {
self.raw[i].reset(&template); self.raw[i].reset(&template);
} }
} }
// Completely reset the grid state /// Completely reset the grid state.
pub fn reset(&mut self, template: &T) { pub fn reset(&mut self, template: &T) {
self.clear_history(); self.clear_history();
// Reset all visible lines // Reset all visible lines.
for row in 0..self.raw.len() { for row in 0..self.raw.len() {
self.raw[row].reset(template); self.raw[row].reset(template);
} }
@ -620,11 +620,11 @@ impl<T> Grid<T> {
#[inline] #[inline]
pub fn clear_history(&mut self) { pub fn clear_history(&mut self) {
// Explicitly purge all lines from history // Explicitly purge all lines from history.
self.raw.shrink_lines(self.history_size()); self.raw.shrink_lines(self.history_size());
} }
/// Total number of lines in the buffer, this includes scrollback + visible lines /// Total number of lines in the buffer, this includes scrollback + visible lines.
#[inline] #[inline]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.raw.len() self.raw.len()
@ -635,20 +635,20 @@ impl<T> Grid<T> {
self.raw.len() - *self.lines self.raw.len() - *self.lines
} }
/// This is used only for initializing after loading ref-tests /// This is used only for initializing after loading ref-tests.
#[inline] #[inline]
pub fn initialize_all(&mut self, template: &T) pub fn initialize_all(&mut self, template: &T)
where where
T: Copy + GridCell, T: Copy + GridCell,
{ {
// Remove all cached lines to clear them of any content // Remove all cached lines to clear them of any content.
self.truncate(); self.truncate();
// Initialize everything with empty new lines // Initialize everything with empty new lines.
self.raw.initialize(self.max_scroll_limit - self.history_size(), template, self.cols); self.raw.initialize(self.max_scroll_limit - self.history_size(), template, self.cols);
} }
/// This is used only for truncating before saving ref-tests /// This is used only for truncating before saving ref-tests.
#[inline] #[inline]
pub fn truncate(&mut self) { pub fn truncate(&mut self) {
self.raw.truncate(); self.raw.truncate();
@ -666,7 +666,7 @@ impl<T> Grid<T> {
} }
pub struct GridIterator<'a, T> { pub struct GridIterator<'a, T> {
/// Immutable grid reference /// Immutable grid reference.
grid: &'a Grid<T>, grid: &'a Grid<T>,
/// Current position of the iterator within the grid. /// Current position of the iterator within the grid.
@ -722,7 +722,7 @@ impl<'a, T> BidirectionalIterator for GridIterator<'a, T> {
} }
} }
/// Index active region by line /// Index active region by line.
impl<T> Index<Line> for Grid<T> { impl<T> Index<Line> for Grid<T> {
type Output = Row<T>; type Output = Row<T>;
@ -732,7 +732,7 @@ impl<T> Index<Line> for Grid<T> {
} }
} }
/// Index with buffer offset /// Index with buffer offset.
impl<T> Index<usize> for Grid<T> { impl<T> Index<usize> for Grid<T> {
type Output = Row<T>; type Output = Row<T>;
@ -772,22 +772,18 @@ impl<'point, T> IndexMut<&'point Point> for Grid<T> {
} }
} }
// ------------------------------------------------------------------------------------------------- /// A subset of lines in the grid.
// REGIONS
// -------------------------------------------------------------------------------------------------
/// A subset of lines in the grid
/// ///
/// May be constructed using Grid::region(..) /// May be constructed using Grid::region(..).
pub struct Region<'a, T> { pub struct Region<'a, T> {
start: Line, start: Line,
end: Line, end: Line,
raw: &'a Storage<T>, raw: &'a Storage<T>,
} }
/// A mutable subset of lines in the grid /// A mutable subset of lines in the grid.
/// ///
/// May be constructed using Grid::region_mut(..) /// May be constructed using Grid::region_mut(..).
pub struct RegionMut<'a, T> { pub struct RegionMut<'a, T> {
start: Line, start: Line,
end: Line, end: Line,
@ -795,7 +791,7 @@ pub struct RegionMut<'a, T> {
} }
impl<'a, T> RegionMut<'a, T> { impl<'a, T> RegionMut<'a, T> {
/// Call the provided function for every item in this region /// Call the provided function for every item in this region.
pub fn each<F: Fn(&mut T)>(self, func: F) { pub fn each<F: Fn(&mut T)>(self, func: F) {
for row in self { for row in self {
for item in row { for item in row {
@ -806,10 +802,10 @@ impl<'a, T> RegionMut<'a, T> {
} }
pub trait IndexRegion<I, T> { pub trait IndexRegion<I, T> {
/// Get an immutable region of Self /// Get an immutable region of Self.
fn region(&self, _: I) -> Region<'_, T>; fn region(&self, _: I) -> Region<'_, T>;
/// Get a mutable region of Self /// Get a mutable region of Self.
fn region_mut(&mut self, _: I) -> RegionMut<'_, T>; fn region_mut(&mut self, _: I) -> RegionMut<'_, T>;
} }
@ -921,11 +917,7 @@ impl<'a, T> Iterator for RegionIterMut<'a, T> {
} }
} }
// ------------------------------------------------------------------------------------------------- /// Iterates over the visible area accounting for buffer transform.
// DISPLAY ITERATOR
// -------------------------------------------------------------------------------------------------
/// Iterates over the visible area accounting for buffer transform
pub struct DisplayIter<'a, T> { pub struct DisplayIter<'a, T> {
grid: &'a Grid<T>, grid: &'a Grid<T>,
offset: usize, offset: usize,
@ -974,7 +966,7 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> {
column: self.col, column: self.col,
}); });
// Update line/col to point to next item // Update line/col to point to next item.
self.col += 1; self.col += 1;
if self.col == self.grid.num_cols() && self.offset != self.limit { if self.col == self.grid.num_cols() && self.offset != self.limit {
self.offset -= 1; self.offset -= 1;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Defines the Row type which makes up lines in the grid //! Defines the Row type which makes up lines in the grid.
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
use crate::grid::GridCell; use crate::grid::GridCell;
use crate::index::Column; use crate::index::Column;
/// A row in the grid /// A row in the grid.
#[derive(Default, Clone, Debug, Serialize, Deserialize)] #[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct Row<T> { pub struct Row<T> {
inner: Vec<T>, inner: Vec<T>,
@ -67,7 +67,7 @@ impl<T: Copy> Row<T> {
return None; return None;
} }
// Split off cells for a new row // Split off cells for a new row.
let mut new_row = self.inner.split_off(cols.0); let mut new_row = self.inner.split_off(cols.0);
let index = new_row.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0); let index = new_row.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0);
new_row.truncate(index); new_row.truncate(index);
@ -91,14 +91,13 @@ impl<T: Copy> Row<T> {
let template = *template; let template = *template;
// Mark all cells as dirty if template cell changed // Mark all cells as dirty if template cell changed.
let len = self.inner.len(); let len = self.inner.len();
if !self.inner[len - 1].fast_eq(template) { if !self.inner[len - 1].fast_eq(template) {
self.occ = len; self.occ = len;
} }
// Reset every dirty in the row // Reset every dirty in the row.
// let template = *template;
for item in &mut self.inner[..self.occ] { for item in &mut self.inner[..self.occ] {
*item = template; *item = template;
} }
@ -193,10 +192,6 @@ impl<T> IndexMut<Column> for Row<T> {
} }
} }
// -----------------------------------------------------------------------------
// Index ranges of columns
// -----------------------------------------------------------------------------
impl<T> Index<Range<Column>> for Row<T> { impl<T> Index<Range<Column>> for Row<T> {
type Output = [T]; type Output = [T];

View File

@ -52,7 +52,7 @@ pub struct Storage<T> {
impl<T: PartialEq> PartialEq for Storage<T> { impl<T: PartialEq> PartialEq for Storage<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// Both storage buffers need to be truncated and zeroed // Both storage buffers need to be truncated and zeroed.
assert_eq!(self.zero, 0); assert_eq!(self.zero, 0);
assert_eq!(other.zero, 0); assert_eq!(other.zero, 0);
@ -66,7 +66,7 @@ impl<T> Storage<T> {
where where
T: Clone, T: Clone,
{ {
// Initialize visible lines, the scrollback buffer is initialized dynamically // Initialize visible lines, the scrollback buffer is initialized dynamically.
let inner = vec![template; visible_lines.0]; let inner = vec![template; visible_lines.0];
Storage { inner, zero: 0, visible_lines, len: visible_lines.0 } Storage { inner, zero: 0, visible_lines, len: visible_lines.0 }
@ -77,11 +77,11 @@ impl<T> Storage<T> {
where where
T: Clone, T: Clone,
{ {
// Number of lines the buffer needs to grow // Number of lines the buffer needs to grow.
let growage = next - self.visible_lines; let growage = next - self.visible_lines;
self.grow_lines(growage.0, template_row); self.grow_lines(growage.0, template_row);
// Update visible lines // Update visible lines.
self.visible_lines = next; self.visible_lines = next;
} }
@ -90,43 +90,43 @@ impl<T> Storage<T> {
where where
T: Clone, T: Clone,
{ {
// Only grow if there are not enough lines still hidden // Only grow if there are not enough lines still hidden.
let mut new_growage = 0; let mut new_growage = 0;
if growage > (self.inner.len() - self.len) { if growage > (self.inner.len() - self.len) {
// Lines to grow additionally to invisible lines // Lines to grow additionally to invisible lines.
new_growage = growage - (self.inner.len() - self.len); new_growage = growage - (self.inner.len() - self.len);
// Split off the beginning of the raw inner buffer // Split off the beginning of the raw inner buffer.
let mut start_buffer = self.inner.split_off(self.zero); let mut start_buffer = self.inner.split_off(self.zero);
// Insert new template rows at the end of the raw inner buffer // Insert new template rows at the end of the raw inner buffer.
let mut new_lines = vec![template_row; new_growage]; let mut new_lines = vec![template_row; new_growage];
self.inner.append(&mut new_lines); self.inner.append(&mut new_lines);
// Add the start to the raw inner buffer again // Add the start to the raw inner buffer again.
self.inner.append(&mut start_buffer); self.inner.append(&mut start_buffer);
} }
// Update raw buffer length and zero offset // Update raw buffer length and zero offset.
self.zero += new_growage; self.zero += new_growage;
self.len += growage; self.len += growage;
} }
/// Decrease the number of lines in the buffer. /// Decrease the number of lines in the buffer.
pub fn shrink_visible_lines(&mut self, next: Line) { pub fn shrink_visible_lines(&mut self, next: Line) {
// Shrink the size without removing any lines // Shrink the size without removing any lines.
let shrinkage = self.visible_lines - next; let shrinkage = self.visible_lines - next;
self.shrink_lines(shrinkage.0); self.shrink_lines(shrinkage.0);
// Update visible lines // Update visible lines.
self.visible_lines = next; self.visible_lines = next;
} }
// Shrink the number of lines in the buffer /// Shrink the number of lines in the buffer.
pub fn shrink_lines(&mut self, shrinkage: usize) { pub fn shrink_lines(&mut self, shrinkage: usize) {
self.len -= shrinkage; self.len -= shrinkage;
// Free memory // Free memory.
if self.inner.len() > self.len + MAX_CACHE_SIZE { if self.inner.len() > self.len + MAX_CACHE_SIZE {
self.truncate(); self.truncate();
} }
@ -209,7 +209,7 @@ impl<T> Storage<T> {
let a_ptr = self.inner.as_mut_ptr().add(a) as *mut usize; let a_ptr = self.inner.as_mut_ptr().add(a) as *mut usize;
let b_ptr = self.inner.as_mut_ptr().add(b) as *mut usize; let b_ptr = self.inner.as_mut_ptr().add(b) as *mut usize;
// Copy 1 qword at a time // Copy 1 qword at a time.
// //
// The optimizer unrolls this loop and vectorizes it. // The optimizer unrolls this loop and vectorizes it.
let mut tmp: usize; let mut tmp: usize;
@ -360,7 +360,7 @@ mod tests {
assert_eq!(storage.zero, 2); assert_eq!(storage.zero, 2);
} }
/// Grow the buffer one line at the end of the buffer /// Grow the buffer one line at the end of the buffer.
/// ///
/// Before: /// Before:
/// 0: 0 <- Zero /// 0: 0 <- Zero
@ -406,7 +406,7 @@ mod tests {
assert_eq!(storage.len, expected.len); assert_eq!(storage.len, expected.len);
} }
/// Grow the buffer one line at the start of the buffer /// Grow the buffer one line at the start of the buffer.
/// ///
/// Before: /// Before:
/// 0: - /// 0: -
@ -419,7 +419,7 @@ mod tests {
/// 3: 1 /// 3: 1
#[test] #[test]
fn grow_before_zero() { fn grow_before_zero() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'-'), Row::new(Column(1), &'-'),
@ -431,10 +431,10 @@ mod tests {
len: 3, len: 3,
}; };
// Grow buffer // Grow buffer.
storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-')); storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-'));
// Make sure the result is correct // Make sure the result is correct.
let expected = Storage { let expected = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'-'), Row::new(Column(1), &'-'),
@ -452,7 +452,7 @@ mod tests {
assert_eq!(storage.len, expected.len); assert_eq!(storage.len, expected.len);
} }
/// Shrink the buffer one line at the start of the buffer /// Shrink the buffer one line at the start of the buffer.
/// ///
/// Before: /// Before:
/// 0: 2 /// 0: 2
@ -464,7 +464,7 @@ mod tests {
/// 1: 1 /// 1: 1
#[test] #[test]
fn shrink_before_zero() { fn shrink_before_zero() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'2'), Row::new(Column(1), &'2'),
@ -476,10 +476,10 @@ mod tests {
len: 3, len: 3,
}; };
// Shrink buffer // Shrink buffer.
storage.shrink_visible_lines(Line(2)); storage.shrink_visible_lines(Line(2));
// Make sure the result is correct // Make sure the result is correct.
let expected = Storage { let expected = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'2'), Row::new(Column(1), &'2'),
@ -496,7 +496,7 @@ mod tests {
assert_eq!(storage.len, expected.len); assert_eq!(storage.len, expected.len);
} }
/// Shrink the buffer one line at the end of the buffer /// Shrink the buffer one line at the end of the buffer.
/// ///
/// Before: /// Before:
/// 0: 0 <- Zero /// 0: 0 <- Zero
@ -508,7 +508,7 @@ mod tests {
/// 2: 2 <- Hidden /// 2: 2 <- Hidden
#[test] #[test]
fn shrink_after_zero() { fn shrink_after_zero() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'0'), Row::new(Column(1), &'0'),
@ -520,10 +520,10 @@ mod tests {
len: 3, len: 3,
}; };
// Shrink buffer // Shrink buffer.
storage.shrink_visible_lines(Line(2)); storage.shrink_visible_lines(Line(2));
// Make sure the result is correct // Make sure the result is correct.
let expected = Storage { let expected = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'0'), Row::new(Column(1), &'0'),
@ -540,7 +540,7 @@ mod tests {
assert_eq!(storage.len, expected.len); assert_eq!(storage.len, expected.len);
} }
/// Shrink the buffer at the start and end of the buffer /// Shrink the buffer at the start and end of the buffer.
/// ///
/// Before: /// Before:
/// 0: 4 /// 0: 4
@ -558,7 +558,7 @@ mod tests {
/// 5: 3 <- Hidden /// 5: 3 <- Hidden
#[test] #[test]
fn shrink_before_and_after_zero() { fn shrink_before_and_after_zero() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'4'), Row::new(Column(1), &'4'),
@ -573,10 +573,10 @@ mod tests {
len: 6, len: 6,
}; };
// Shrink buffer // Shrink buffer.
storage.shrink_visible_lines(Line(2)); storage.shrink_visible_lines(Line(2));
// Make sure the result is correct // Make sure the result is correct.
let expected = Storage { let expected = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'4'), Row::new(Column(1), &'4'),
@ -596,7 +596,7 @@ mod tests {
assert_eq!(storage.len, expected.len); assert_eq!(storage.len, expected.len);
} }
/// Check that when truncating all hidden lines are removed from the raw buffer /// Check that when truncating all hidden lines are removed from the raw buffer.
/// ///
/// Before: /// Before:
/// 0: 4 <- Hidden /// 0: 4 <- Hidden
@ -610,7 +610,7 @@ mod tests {
/// 1: 1 /// 1: 1
#[test] #[test]
fn truncate_invisible_lines() { fn truncate_invisible_lines() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'4'), Row::new(Column(1), &'4'),
@ -625,10 +625,10 @@ mod tests {
len: 2, len: 2,
}; };
// Truncate buffer // Truncate buffer.
storage.truncate(); storage.truncate();
// Make sure the result is correct // Make sure the result is correct.
let expected = Storage { let expected = Storage {
inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')],
zero: 0, zero: 0,
@ -641,7 +641,7 @@ mod tests {
assert_eq!(storage.len, expected.len); assert_eq!(storage.len, expected.len);
} }
/// Truncate buffer only at the beginning /// Truncate buffer only at the beginning.
/// ///
/// Before: /// Before:
/// 0: 1 /// 0: 1
@ -652,7 +652,7 @@ mod tests {
/// 0: 0 <- Zero /// 0: 0 <- Zero
#[test] #[test]
fn truncate_invisible_lines_beginning() { fn truncate_invisible_lines_beginning() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'1'), Row::new(Column(1), &'1'),
@ -664,10 +664,10 @@ mod tests {
len: 2, len: 2,
}; };
// Truncate buffer // Truncate buffer.
storage.truncate(); storage.truncate();
// Make sure the result is correct // Make sure the result is correct.
let expected = Storage { let expected = Storage {
inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')],
zero: 0, zero: 0,
@ -680,7 +680,7 @@ mod tests {
assert_eq!(storage.len, expected.len); assert_eq!(storage.len, expected.len);
} }
/// First shrink the buffer and then grow it again /// First shrink the buffer and then grow it again.
/// ///
/// Before: /// Before:
/// 0: 4 /// 0: 4
@ -706,7 +706,7 @@ mod tests {
/// 6: 3 /// 6: 3
#[test] #[test]
fn shrink_then_grow() { fn shrink_then_grow() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'4'), Row::new(Column(1), &'4'),
@ -721,10 +721,10 @@ mod tests {
len: 6, len: 6,
}; };
// Shrink buffer // Shrink buffer.
storage.shrink_lines(3); storage.shrink_lines(3);
// Make sure the result after shrinking is correct // Make sure the result after shrinking is correct.
let shrinking_expected = Storage { let shrinking_expected = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'4'), Row::new(Column(1), &'4'),
@ -742,10 +742,10 @@ mod tests {
assert_eq!(storage.zero, shrinking_expected.zero); assert_eq!(storage.zero, shrinking_expected.zero);
assert_eq!(storage.len, shrinking_expected.len); assert_eq!(storage.len, shrinking_expected.len);
// Grow buffer // Grow buffer.
storage.grow_lines(4, Row::new(Column(1), &'-')); storage.grow_lines(4, Row::new(Column(1), &'-'));
// Make sure the result after shrinking is correct // Make sure the result after shrinking is correct.
let growing_expected = Storage { let growing_expected = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'4'), Row::new(Column(1), &'4'),
@ -767,7 +767,7 @@ mod tests {
#[test] #[test]
fn initialize() { fn initialize() {
// Setup storage area // Setup storage area.
let mut storage = Storage { let mut storage = Storage {
inner: vec![ inner: vec![
Row::new(Column(1), &'4'), Row::new(Column(1), &'4'),
@ -782,11 +782,11 @@ mod tests {
len: 6, len: 6,
}; };
// Initialize additional lines // Initialize additional lines.
let init_size = 3; let init_size = 3;
storage.initialize(init_size, &'-', Column(1)); storage.initialize(init_size, &'-', Column(1));
// Make sure the lines are present and at the right location // Make sure the lines are present and at the right location.
let expected_init_size = std::cmp::max(init_size, MAX_CACHE_SIZE); let expected_init_size = std::cmp::max(init_size, MAX_CACHE_SIZE);
let mut expected_inner = vec![Row::new(Column(1), &'4'), Row::new(Column(1), &'5')]; let mut expected_inner = vec![Row::new(Column(1), &'4'), Row::new(Column(1), &'5')];

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Tests for the Grid //! Tests for the Grid.
use super::{BidirectionalIterator, Grid}; use super::{BidirectionalIterator, Grid};
use crate::grid::GridCell; use crate::grid::GridCell;
@ -71,7 +71,7 @@ fn visible_to_buffer() {
assert_eq!(point, Point::new(4, Column(3))); assert_eq!(point, Point::new(4, Column(3)));
} }
// Scroll up moves lines upwards // Scroll up moves lines upwards.
#[test] #[test]
fn scroll_up() { fn scroll_up() {
let mut grid = Grid::new(Line(10), Column(1), 0, 0); let mut grid = Grid::new(Line(10), Column(1), 0, 0);
@ -97,13 +97,13 @@ fn scroll_up() {
assert_eq!(grid[Line(6)].occ, 1); assert_eq!(grid[Line(6)].occ, 1);
assert_eq!(grid[Line(7)][Column(0)], 9); assert_eq!(grid[Line(7)][Column(0)], 9);
assert_eq!(grid[Line(7)].occ, 1); assert_eq!(grid[Line(7)].occ, 1);
assert_eq!(grid[Line(8)][Column(0)], 0); // was 0 assert_eq!(grid[Line(8)][Column(0)], 0); // was 0.
assert_eq!(grid[Line(8)].occ, 0); assert_eq!(grid[Line(8)].occ, 0);
assert_eq!(grid[Line(9)][Column(0)], 0); // was 1 assert_eq!(grid[Line(9)][Column(0)], 0); // was 1.
assert_eq!(grid[Line(9)].occ, 0); assert_eq!(grid[Line(9)].occ, 0);
} }
// Scroll down moves lines downwards // Scroll down moves lines downwards.
#[test] #[test]
fn scroll_down() { fn scroll_down() {
let mut grid = Grid::new(Line(10), Column(1), 0, 0); let mut grid = Grid::new(Line(10), Column(1), 0, 0);
@ -113,9 +113,9 @@ fn scroll_down() {
grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0); grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0);
assert_eq!(grid[Line(0)][Column(0)], 0); // was 8 assert_eq!(grid[Line(0)][Column(0)], 0); // was 8.
assert_eq!(grid[Line(0)].occ, 0); assert_eq!(grid[Line(0)].occ, 0);
assert_eq!(grid[Line(1)][Column(0)], 0); // was 9 assert_eq!(grid[Line(1)][Column(0)], 0); // was 9.
assert_eq!(grid[Line(1)].occ, 0); assert_eq!(grid[Line(1)].occ, 0);
assert_eq!(grid[Line(2)][Column(0)], 0); assert_eq!(grid[Line(2)][Column(0)], 0);
assert_eq!(grid[Line(2)].occ, 1); assert_eq!(grid[Line(2)].occ, 1);
@ -135,7 +135,7 @@ fn scroll_down() {
assert_eq!(grid[Line(9)].occ, 1); assert_eq!(grid[Line(9)].occ, 1);
} }
// Test that GridIterator works // Test that GridIterator works.
#[test] #[test]
fn test_iter() { fn test_iter() {
let mut grid = Grid::new(Line(5), Column(5), 0, 0); let mut grid = Grid::new(Line(5), Column(5), 0, 0);
@ -156,7 +156,7 @@ fn test_iter() {
assert_eq!(Some(&3), iter.next()); assert_eq!(Some(&3), iter.next());
assert_eq!(Some(&4), iter.next()); assert_eq!(Some(&4), iter.next());
// test linewrapping // Test line-wrapping.
assert_eq!(Some(&5), iter.next()); assert_eq!(Some(&5), iter.next());
assert_eq!(Column(0), iter.point().col); assert_eq!(Column(0), iter.point().col);
assert_eq!(3, iter.point().line); assert_eq!(3, iter.point().line);
@ -165,10 +165,10 @@ fn test_iter() {
assert_eq!(Column(4), iter.point().col); assert_eq!(Column(4), iter.point().col);
assert_eq!(4, iter.point().line); assert_eq!(4, iter.point().line);
// Make sure iter.cell() returns the current iterator position // Make sure iter.cell() returns the current iterator position.
assert_eq!(&4, iter.cell()); assert_eq!(&4, iter.cell());
// test that iter ends at end of grid // Test that iter ends at end of grid.
let mut final_iter = grid.iter_from(Point { line: 0, col: Column(4) }); let mut final_iter = grid.iter_from(Point { line: 0, col: Column(4) });
assert_eq!(None, final_iter.next()); assert_eq!(None, final_iter.next());
assert_eq!(Some(&23), final_iter.prev()); assert_eq!(Some(&23), final_iter.prev());
@ -282,7 +282,7 @@ fn grow_reflow() {
assert_eq!(grid[1][Column(1)], cell('2')); assert_eq!(grid[1][Column(1)], cell('2'));
assert_eq!(grid[1][Column(2)], cell('3')); assert_eq!(grid[1][Column(2)], cell('3'));
// Make sure rest of grid is empty // Make sure rest of grid is empty.
assert_eq!(grid[0].len(), 3); assert_eq!(grid[0].len(), 3);
assert_eq!(grid[0][Column(0)], Cell::default()); assert_eq!(grid[0][Column(0)], Cell::default());
assert_eq!(grid[0][Column(1)], Cell::default()); assert_eq!(grid[0][Column(1)], Cell::default());
@ -311,7 +311,7 @@ fn grow_reflow_multiline() {
assert_eq!(grid[2][Column(4)], cell('5')); assert_eq!(grid[2][Column(4)], cell('5'));
assert_eq!(grid[2][Column(5)], cell('6')); assert_eq!(grid[2][Column(5)], cell('6'));
// Make sure rest of grid is empty // Make sure rest of grid is empty.
// https://github.com/rust-lang/rust-clippy/issues/3788 // https://github.com/rust-lang/rust-clippy/issues/3788
#[allow(clippy::needless_range_loop)] #[allow(clippy::needless_range_loop)]
for r in 0..2 { for r in 0..2 {

View File

@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Line and Column newtypes for strongly typed tty/grid/terminal APIs //! Line and Column newtypes for strongly typed tty/grid/terminal APIs.
/// Indexing types and implementations for Grid and Line /// Indexing types and implementations for Grid and Line.
use std::cmp::{Ord, Ordering}; use std::cmp::{Ord, Ordering};
use std::fmt; use std::fmt;
use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign}; use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign};
@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
use crate::term::RenderableCell; use crate::term::RenderableCell;
/// The side of a cell /// The side of a cell.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Side { pub enum Side {
Left, Left,
@ -39,7 +39,7 @@ impl Side {
} }
} }
/// Index in the grid using row, column notation /// Index in the grid using row, column notation.
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)]
pub struct Point<L = Line> { pub struct Point<L = Line> {
pub line: L, pub line: L,
@ -149,9 +149,9 @@ impl From<RenderableCell> for Point<Line> {
} }
} }
/// A line /// A line.
/// ///
/// Newtype to avoid passing values incorrectly /// Newtype to avoid passing values incorrectly.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Line(pub usize); pub struct Line(pub usize);
@ -161,9 +161,9 @@ impl fmt::Display for Line {
} }
} }
/// A column /// A column.
/// ///
/// Newtype to avoid passing values incorrectly /// Newtype to avoid passing values incorrectly.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Column(pub usize); pub struct Column(pub usize);
@ -173,9 +173,9 @@ impl fmt::Display for Column {
} }
} }
/// A linear index /// A linear index.
/// ///
/// Newtype to avoid passing values incorrectly /// Newtype to avoid passing values incorrectly.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Linear(pub usize); pub struct Linear(pub usize);
@ -238,7 +238,7 @@ macro_rules! forward_ref_binop {
}; };
} }
/// Macro for deriving deref /// Macro for deriving deref.
macro_rules! deref { macro_rules! deref {
($ty:ty, $target:ty) => { ($ty:ty, $target:ty) => {
impl Deref for $ty { impl Deref for $ty {
@ -308,7 +308,7 @@ macro_rules! sub {
/// This exists because we can't implement Iterator on Range /// This exists because we can't implement Iterator on Range
/// and the existing impl needs the unstable Step trait /// and the existing impl needs the unstable Step trait
/// This should be removed and replaced with a Step impl /// This should be removed and replaced with a Step impl
/// in the ops macro when `step_by` is stabilized /// in the ops macro when `step_by` is stabilized.
pub struct IndexRange<T>(pub Range<T>); pub struct IndexRange<T>(pub Range<T>);
impl<T> From<Range<T>> for IndexRange<T> { impl<T> From<Range<T>> for IndexRange<T> {
@ -329,7 +329,7 @@ macro_rules! ops {
fn steps_between(start: $ty, end: $ty, by: $ty) -> Option<usize> { fn steps_between(start: $ty, end: $ty, by: $ty) -> Option<usize> {
if by == $construct(0) { return None; } if by == $construct(0) { return None; }
if start < end { if start < end {
// Note: We assume $t <= usize here // Note: We assume $t <= usize here.
let diff = (end - start).0; let diff = (end - start).0;
let by = by.0; let by = by.0;
if diff % by > 0 { if diff % by > 0 {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Alacritty - The GPU Enhanced Terminal //! Alacritty - The GPU Enhanced Terminal.
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))]
#![cfg_attr(all(test, feature = "bench"), feature(test))] #![cfg_attr(all(test, feature = "bench"), feature(test))]

View File

@ -31,7 +31,7 @@ pub fn set_locale_environment() {
if !env_locale_ptr.is_null() { if !env_locale_ptr.is_null() {
let env_locale = unsafe { CStr::from_ptr(env_locale_ptr).to_string_lossy() }; let env_locale = unsafe { CStr::from_ptr(env_locale_ptr).to_string_lossy() };
// Assume `C` locale means unchanged, since it is the default anyways // Assume `C` locale means unchanged, since it is the default anyways.
if env_locale != "C" { if env_locale != "C" {
debug!("Using environment locale: {}", env_locale); debug!("Using environment locale: {}", env_locale);
return; return;
@ -40,13 +40,13 @@ pub fn set_locale_environment() {
let system_locale = system_locale(); let system_locale = system_locale();
// Set locale to system locale // Set locale to system locale.
let system_locale_c = CString::new(system_locale.clone()).expect("nul byte in system locale"); let system_locale_c = CString::new(system_locale.clone()).expect("nul byte in system locale");
let lc_all = unsafe { setlocale(LC_ALL, system_locale_c.as_ptr()) }; let lc_all = unsafe { setlocale(LC_ALL, system_locale_c.as_ptr()) };
// Check if system locale was valid or not // Check if system locale was valid or not.
if lc_all.is_null() { if lc_all.is_null() {
// Use fallback locale // Use fallback locale.
debug!("Using fallback locale: {}", FALLBACK_LOCALE); debug!("Using fallback locale: {}", FALLBACK_LOCALE);
let fallback_locale_c = CString::new(FALLBACK_LOCALE).unwrap(); let fallback_locale_c = CString::new(FALLBACK_LOCALE).unwrap();
@ -54,7 +54,7 @@ pub fn set_locale_environment() {
env::set_var("LC_CTYPE", FALLBACK_LOCALE); env::set_var("LC_CTYPE", FALLBACK_LOCALE);
} else { } else {
// Use system locale // Use system locale.
debug!("Using system locale: {}", system_locale); debug!("Using system locale: {}", system_locale);
env::set_var("LC_ALL", system_locale); env::set_var("LC_ALL", system_locale);
@ -71,6 +71,7 @@ fn system_locale() -> String {
// `localeIdentifier` returns extra metadata with the locale (including currency and // `localeIdentifier` returns extra metadata with the locale (including currency and
// collator) on newer versions of macOS. This is not a valid locale, so we use // collator) on newer versions of macOS. This is not a valid locale, so we use
// `languageCode` and `countryCode`, if they're available (macOS 10.12+): // `languageCode` and `countryCode`, if they're available (macOS 10.12+):
//
// https://developer.apple.com/documentation/foundation/nslocale/1416263-localeidentifier?language=objc // https://developer.apple.com/documentation/foundation/nslocale/1416263-localeidentifier?language=objc
// https://developer.apple.com/documentation/foundation/nslocale/1643060-countrycode?language=objc // https://developer.apple.com/documentation/foundation/nslocale/1643060-countrycode?language=objc
// https://developer.apple.com/documentation/foundation/nslocale/1643026-languagecode?language=objc // https://developer.apple.com/documentation/foundation/nslocale/1643026-languagecode?language=objc

View File

@ -42,18 +42,18 @@ impl Message {
let max_lines = size_info.lines().saturating_sub(MIN_FREE_LINES); let max_lines = size_info.lines().saturating_sub(MIN_FREE_LINES);
let button_len = CLOSE_BUTTON_TEXT.len(); let button_len = CLOSE_BUTTON_TEXT.len();
// Split line to fit the screen // Split line to fit the screen.
let mut lines = Vec::new(); let mut lines = Vec::new();
let mut line = String::new(); let mut line = String::new();
for c in self.text.trim().chars() { for c in self.text.trim().chars() {
if c == '\n' if c == '\n'
|| line.len() == num_cols || line.len() == num_cols
// Keep space in first line for button // Keep space in first line for button.
|| (lines.is_empty() || (lines.is_empty()
&& num_cols >= button_len && num_cols >= button_len
&& line.len() == num_cols.saturating_sub(button_len + CLOSE_BUTTON_PADDING)) && line.len() == num_cols.saturating_sub(button_len + CLOSE_BUTTON_PADDING))
{ {
// Attempt to wrap on word boundaries // Attempt to wrap on word boundaries.
if let (Some(index), true) = (line.rfind(char::is_whitespace), c != '\n') { if let (Some(index), true) = (line.rfind(char::is_whitespace), c != '\n') {
let split = line.split_off(index + 1); let split = line.split_off(index + 1);
line.pop(); line.pop();
@ -71,7 +71,7 @@ impl Message {
} }
lines.push(Self::pad_text(line, num_cols)); lines.push(Self::pad_text(line, num_cols));
// Truncate output if it's too long // Truncate output if it's too long.
if lines.len() > max_lines { if lines.len() > max_lines {
lines.truncate(max_lines); lines.truncate(max_lines);
if TRUNCATED_MESSAGE.len() <= num_cols { if TRUNCATED_MESSAGE.len() <= num_cols {
@ -81,7 +81,7 @@ impl Message {
} }
} }
// Append close button to first line // Append close button to first line.
if button_len <= num_cols { if button_len <= num_cols {
if let Some(line) = lines.get_mut(0) { if let Some(line) = lines.get_mut(0) {
line.truncate(num_cols - button_len); line.truncate(num_cols - button_len);
@ -146,10 +146,10 @@ impl MessageBuffer {
/// Remove the currently visible message. /// Remove the currently visible message.
#[inline] #[inline]
pub fn pop(&mut self) { pub fn pop(&mut self) {
// Remove the message itself // Remove the message itself.
let msg = self.messages.pop_front(); let msg = self.messages.pop_front();
// Remove all duplicates // Remove all duplicates.
if let Some(msg) = msg { if let Some(msg) = msg {
self.messages = self.messages.drain(..).filter(|m| m != &msg).collect(); self.messages = self.messages.drain(..).filter(|m| m != &msg).collect();
} }
@ -373,7 +373,7 @@ mod tests {
message_buffer.remove_target("target"); message_buffer.remove_target("target");
// Count number of messages // Count number of messages.
let mut num_messages = 0; let mut num_messages = 0;
while message_buffer.message().is_some() { while message_buffer.message().is_some() {
num_messages += 1; num_messages += 1;
@ -435,7 +435,7 @@ mod tests {
message_buffer.pop(); message_buffer.pop();
// Count number of messages // Count number of messages.
let mut num_messages = 0; let mut num_messages = 0;
while message_buffer.message().is_some() { while message_buffer.message().is_some() {
num_messages += 1; num_messages += 1;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Rendering time meter //! Rendering time meter.
//! //!
//! Used to track rendering times and provide moving averages. //! Used to track rendering times and provide moving averages.
//! //!
@ -36,27 +36,27 @@ use std::time::{Duration, Instant};
const NUM_SAMPLES: usize = 10; const NUM_SAMPLES: usize = 10;
/// The meter /// The meter.
#[derive(Default)] #[derive(Default)]
pub struct Meter { pub struct Meter {
/// Track last 60 timestamps /// Track last 60 timestamps.
times: [f64; NUM_SAMPLES], times: [f64; NUM_SAMPLES],
/// Average sample time in microseconds /// Average sample time in microseconds.
avg: f64, avg: f64,
/// Index of next time to update. /// Index of next time to update..
index: usize, index: usize,
} }
/// Sampler /// Sampler.
/// ///
/// Samplers record how long they are "alive" for and update the meter on drop. /// Samplers record how long they are "alive" for and update the meter on drop..
pub struct Sampler<'a> { pub struct Sampler<'a> {
/// Reference to meter that created the sampler /// Reference to meter that created the sampler.
meter: &'a mut Meter, meter: &'a mut Meter,
// When the sampler was created /// When the sampler was created.
created_at: Instant, created_at: Instant,
} }
@ -78,22 +78,22 @@ impl<'a> Drop for Sampler<'a> {
} }
impl Meter { impl Meter {
/// Create a meter /// Create a meter.
pub fn new() -> Meter { pub fn new() -> Meter {
Default::default() Default::default()
} }
/// Get a sampler /// Get a sampler.
pub fn sampler(&mut self) -> Sampler<'_> { pub fn sampler(&mut self) -> Sampler<'_> {
Sampler::new(self) Sampler::new(self)
} }
/// Get the current average sample duration in microseconds /// Get the current average sample duration in microseconds.
pub fn average(&self) -> f64 { pub fn average(&self) -> f64 {
self.avg self.avg
} }
/// Add a sample /// Add a sample.
/// ///
/// Used by Sampler::drop. /// Used by Sampler::drop.
fn add_sample(&mut self, sample: Duration) { fn add_sample(&mut self, sample: Duration) {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! ANSI Terminal Stream Parsing //! ANSI Terminal Stream Parsing.
#[cfg(windows)] #[cfg(windows)]
use crate::tty::windows::win32_string; use crate::tty::windows::win32_string;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! State management for a selection in the grid //! State management for a selection in the grid.
//! //!
//! A selection should start when the mouse is clicked, and it should be //! A selection should start when the mouse is clicked, and it should be
//! finalized when the button is released. The selection should be cleared //! finalized when the button is released. The selection should be cleared
@ -117,7 +117,7 @@ impl Selection {
scrolling_region: &Range<Line>, scrolling_region: &Range<Line>,
offset: isize, offset: isize,
) -> Option<Selection> { ) -> Option<Selection> {
// Convert scrolling region from viewport to buffer coordinates // Convert scrolling region from viewport to buffer coordinates.
let region_start = num_lines - scrolling_region.start.0; let region_start = num_lines - scrolling_region.start.0;
let region_end = num_lines - scrolling_region.end.0; let region_end = num_lines - scrolling_region.end.0;
@ -126,18 +126,18 @@ impl Selection {
mem::swap(&mut start, &mut end); mem::swap(&mut start, &mut end);
} }
// Rotate start of selection // Rotate start of selection.
if (start.point.line < region_start || region_start == num_lines) if (start.point.line < region_start || region_start == num_lines)
&& start.point.line >= region_end && start.point.line >= region_end
{ {
start.point.line = usize::try_from(start.point.line as isize + offset).unwrap_or(0); start.point.line = usize::try_from(start.point.line as isize + offset).unwrap_or(0);
// If end is within the same region, delete selection once start rotates out // If end is within the same region, delete selection once start rotates out.
if start.point.line < region_end && end.point.line >= region_end { if start.point.line < region_end && end.point.line >= region_end {
return None; return None;
} }
// Clamp selection to start of region // Clamp selection to start of region.
if start.point.line >= region_start && region_start != num_lines { if start.point.line >= region_start && region_start != num_lines {
if self.ty != SelectionType::Block { if self.ty != SelectionType::Block {
start.point.col = Column(0); start.point.col = Column(0);
@ -147,18 +147,18 @@ impl Selection {
} }
} }
// Rotate end of selection // Rotate end of selection.
if (end.point.line < region_start || region_start == num_lines) if (end.point.line < region_start || region_start == num_lines)
&& end.point.line >= region_end && end.point.line >= region_end
{ {
end.point.line = usize::try_from(end.point.line as isize + offset).unwrap_or(0); end.point.line = usize::try_from(end.point.line as isize + offset).unwrap_or(0);
// Delete selection if end has overtaken the start // Delete selection if end has overtaken the start.
if end.point.line > start.point.line { if end.point.line > start.point.line {
return None; return None;
} }
// Clamp selection to end of region // Clamp selection to end of region.
if end.point.line < region_end { if end.point.line < region_end {
if self.ty != SelectionType::Block { if self.ty != SelectionType::Block {
end.point.col = Column(num_cols - 1); end.point.col = Column(num_cols - 1);
@ -180,7 +180,7 @@ impl Selection {
} }
// Simple selection is empty when the points are identical // Simple selection is empty when the points are identical
// or two adjacent cells have the sides right -> left // or two adjacent cells have the sides right -> left.
start == end start == end
|| (start.side == Side::Right || (start.side == Side::Right
&& end.side == Side::Left && end.side == Side::Left
@ -228,13 +228,13 @@ impl Selection {
let grid = term.grid(); let grid = term.grid();
let num_cols = grid.num_cols(); let num_cols = grid.num_cols();
// Order start above the end // Order start above the end.
let (mut start, mut end) = (self.region.start, self.region.end); let (mut start, mut end) = (self.region.start, self.region.end);
if Self::points_need_swap(start.point, end.point) { if Self::points_need_swap(start.point, end.point) {
mem::swap(&mut start, &mut end); mem::swap(&mut start, &mut end);
} }
// Clamp to inside the grid buffer // Clamp to inside the grid buffer.
let is_block = self.ty == SelectionType::Block; let is_block = self.ty == SelectionType::Block;
let (start, end) = Self::grid_clamp(start, end, is_block, grid.len()).ok()?; let (start, end) = Self::grid_clamp(start, end, is_block, grid.len()).ok()?;
@ -246,7 +246,7 @@ impl Selection {
} }
} }
// Bring start and end points in the correct order /// Bring start and end points in the correct order.
fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool { fn points_need_swap(start: Point<usize>, end: Point<usize>) -> bool {
start.line < end.line || start.line == end.line && start.col > end.col start.line < end.line || start.line == end.line && start.col > end.col
} }
@ -258,14 +258,14 @@ impl Selection {
is_block: bool, is_block: bool,
lines: usize, lines: usize,
) -> Result<(Anchor, Anchor), ()> { ) -> Result<(Anchor, Anchor), ()> {
// Clamp selection inside of grid to prevent OOB // Clamp selection inside of grid to prevent OOB.
if start.point.line >= lines { if start.point.line >= lines {
// Remove selection if it is fully out of the grid // Remove selection if it is fully out of the grid.
if end.point.line >= lines { if end.point.line >= lines {
return Err(()); return Err(());
} }
// Clamp to grid if it is still partially visible // Clamp to grid if it is still partially visible.
if !is_block { if !is_block {
start.side = Side::Left; start.side = Side::Left;
start.point.col = Column(0); start.point.col = Column(0);
@ -322,9 +322,9 @@ impl Selection {
return None; return None;
} }
// Remove last cell if selection ends to the left of a cell // Remove last cell if selection ends to the left of a cell.
if end.side == Side::Left && start.point != end.point { if end.side == Side::Left && start.point != end.point {
// Special case when selection ends to left of first cell // Special case when selection ends to left of first cell.
if end.point.col == Column(0) { if end.point.col == Column(0) {
end.point.col = num_cols - 1; end.point.col = num_cols - 1;
end.point.line += 1; end.point.line += 1;
@ -333,11 +333,11 @@ impl Selection {
} }
} }
// Remove first cell if selection starts at the right of a cell // Remove first cell if selection starts at the right of a cell.
if start.side == Side::Right && start.point != end.point { if start.side == Side::Right && start.point != end.point {
start.point.col += 1; start.point.col += 1;
// Wrap to next line when selection starts to the right of last column // Wrap to next line when selection starts to the right of last column.
if start.point.col == num_cols { if start.point.col == num_cols {
start.point = Point::new(start.point.line.saturating_sub(1), Column(0)); start.point = Point::new(start.point.line.saturating_sub(1), Column(0));
} }
@ -351,18 +351,18 @@ impl Selection {
return None; return None;
} }
// Always go top-left -> bottom-right // Always go top-left -> bottom-right.
if start.point.col > end.point.col { if start.point.col > end.point.col {
mem::swap(&mut start.side, &mut end.side); mem::swap(&mut start.side, &mut end.side);
mem::swap(&mut start.point.col, &mut end.point.col); mem::swap(&mut start.point.col, &mut end.point.col);
} }
// Remove last cell if selection ends to the left of a cell // Remove last cell if selection ends to the left of a cell.
if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 { if end.side == Side::Left && start.point != end.point && end.point.col.0 > 0 {
end.point.col -= 1; end.point.col -= 1;
} }
// Remove first cell if selection starts at the right of a cell // Remove first cell if selection starts at the right of a cell.
if start.side == Side::Right && start.point != end.point { if start.side == Side::Right && start.point != end.point {
start.point.col += 1; start.point.col += 1;
} }

View File

@ -12,32 +12,32 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//! Synchronization types //! Synchronization types.
//! //!
//! Most importantly, a fair mutex is included //! Most importantly, a fair mutex is included.
use parking_lot::{Mutex, MutexGuard}; use parking_lot::{Mutex, MutexGuard};
/// A fair mutex /// A fair mutex.
/// ///
/// Uses an extra lock to ensure that if one thread is waiting that it will get /// Uses an extra lock to ensure that if one thread is waiting that it will get
/// the lock before a single thread can re-lock it. /// the lock before a single thread can re-lock it.
pub struct FairMutex<T> { pub struct FairMutex<T> {
/// Data /// Data.
data: Mutex<T>, data: Mutex<T>,
/// Next-to-access /// Next-to-access.
next: Mutex<()>, next: Mutex<()>,
} }
impl<T> FairMutex<T> { impl<T> FairMutex<T> {
/// Create a new fair mutex /// Create a new fair mutex.
pub fn new(data: T) -> FairMutex<T> { pub fn new(data: T) -> FairMutex<T> {
FairMutex { data: Mutex::new(data), next: Mutex::new(()) } FairMutex { data: Mutex::new(data), next: Mutex::new(()) }
} }
/// Lock the mutex /// Lock the mutex.
pub fn lock(&self) -> MutexGuard<'_, T> { pub fn lock(&self) -> MutexGuard<'_, T> {
// Must bind to a temporary or the lock will be freed before going // Must bind to a temporary or the lock will be freed before going
// into data.lock() // into data.lock().
let _next = self.next.lock(); let _next = self.next.lock();
self.data.lock() self.data.lock()
} }

View File

@ -19,7 +19,7 @@ use crate::ansi::{Color, NamedColor};
use crate::grid::{self, GridCell}; use crate::grid::{self, GridCell};
use crate::index::Column; use crate::index::Column;
// Maximum number of zerowidth characters which will be stored per cell. /// Maximum number of zerowidth characters which will be stored per cell.
pub const MAX_ZEROWIDTH_CHARS: usize = 5; pub const MAX_ZEROWIDTH_CHARS: usize = 5;
bitflags! { bitflags! {
@ -92,9 +92,9 @@ impl GridCell for Cell {
} }
} }
/// Get the length of occupied cells in a line /// Get the length of occupied cells in a line.
pub trait LineLength { pub trait LineLength {
/// Calculate the occupied line length /// Calculate the occupied line length.
fn line_length(&self) -> Column; fn line_length(&self) -> Column;
} }
@ -139,7 +139,7 @@ impl Cell {
#[inline] #[inline]
pub fn reset(&mut self, template: &Cell) { pub fn reset(&mut self, template: &Cell) {
// memcpy template to self // memcpy template to self.
*self = Cell { c: template.c, bg: template.bg, ..Cell::default() }; *self = Cell { c: template.c, bg: template.bg, ..Cell::default() };
} }

View File

@ -24,7 +24,7 @@ pub struct Rgb {
pub b: u8, pub b: u8,
} }
// a multiply function for Rgb, as the default dim is just *2/3 // A multiply function for Rgb, as the default dim is just *2/3.
impl Mul<f32> for Rgb { impl Mul<f32> for Rgb {
type Output = Rgb; type Output = Rgb;
@ -41,7 +41,7 @@ impl Mul<f32> for Rgb {
} }
} }
/// Deserialize an Rgb from a hex string /// Deserialize an Rgb from a hex string.
/// ///
/// This is *not* the deserialize impl for Rgb since we want a symmetric /// This is *not* the deserialize impl for Rgb since we want a symmetric
/// serialize/deserialize impl for ref tests. /// serialize/deserialize impl for ref tests.
@ -52,7 +52,7 @@ impl<'de> Deserialize<'de> for Rgb {
{ {
struct RgbVisitor; struct RgbVisitor;
// Used for deserializing reftests // Used for deserializing reftests.
#[derive(Deserialize)] #[derive(Deserialize)]
struct RgbDerivedDeser { struct RgbDerivedDeser {
r: u8, r: u8,
@ -80,15 +80,15 @@ impl<'de> Deserialize<'de> for Rgb {
} }
} }
// Return an error if the syntax is incorrect // Return an error if the syntax is incorrect.
let value = serde_yaml::Value::deserialize(deserializer)?; let value = serde_yaml::Value::deserialize(deserializer)?;
// Attempt to deserialize from struct form // Attempt to deserialize from struct form.
if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) { if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) {
return Ok(Rgb { r, g, b }); return Ok(Rgb { r, g, b });
} }
// Deserialize from hex notation (either 0xff00ff or #ff00ff) // Deserialize from hex notation (either 0xff00ff or #ff00ff).
match value.deserialize_str(RgbVisitor) { match value.deserialize_str(RgbVisitor) {
Ok(rgb) => Ok(rgb), Ok(rgb) => Ok(rgb),
Err(err) => { Err(err) => {
@ -128,7 +128,7 @@ impl FromStr for Rgb {
} }
} }
/// List of indexed colors /// List of indexed colors.
/// ///
/// The first 16 entries are the standard ansi named colors. Items 16..232 are /// The first 16 entries are the standard ansi named colors. Items 16..232 are
/// the color cube. Items 233..256 are the grayscale ramp. Item 256 is /// the color cube. Items 233..256 are the grayscale ramp. Item 256 is
@ -140,7 +140,7 @@ pub struct List([Rgb; COUNT]);
impl<'a> From<&'a Colors> for List { impl<'a> From<&'a Colors> for List {
fn from(colors: &Colors) -> List { fn from(colors: &Colors) -> List {
// Type inference fails without this annotation // Type inference fails without this annotation.
let mut list = List([Rgb::default(); COUNT]); let mut list = List([Rgb::default(); COUNT]);
list.fill_named(colors); list.fill_named(colors);
@ -153,7 +153,7 @@ impl<'a> From<&'a Colors> for List {
impl List { impl List {
pub fn fill_named(&mut self, colors: &Colors) { pub fn fill_named(&mut self, colors: &Colors) {
// Normals // Normals.
self[ansi::NamedColor::Black] = colors.normal().black; self[ansi::NamedColor::Black] = colors.normal().black;
self[ansi::NamedColor::Red] = colors.normal().red; self[ansi::NamedColor::Red] = colors.normal().red;
self[ansi::NamedColor::Green] = colors.normal().green; self[ansi::NamedColor::Green] = colors.normal().green;
@ -163,7 +163,7 @@ impl List {
self[ansi::NamedColor::Cyan] = colors.normal().cyan; self[ansi::NamedColor::Cyan] = colors.normal().cyan;
self[ansi::NamedColor::White] = colors.normal().white; self[ansi::NamedColor::White] = colors.normal().white;
// Brights // Brights.
self[ansi::NamedColor::BrightBlack] = colors.bright().black; self[ansi::NamedColor::BrightBlack] = colors.bright().black;
self[ansi::NamedColor::BrightRed] = colors.bright().red; self[ansi::NamedColor::BrightRed] = colors.bright().red;
self[ansi::NamedColor::BrightGreen] = colors.bright().green; self[ansi::NamedColor::BrightGreen] = colors.bright().green;
@ -175,14 +175,14 @@ impl List {
self[ansi::NamedColor::BrightForeground] = self[ansi::NamedColor::BrightForeground] =
colors.primary.bright_foreground.unwrap_or(colors.primary.foreground); colors.primary.bright_foreground.unwrap_or(colors.primary.foreground);
// Foreground and background // Foreground and background.
self[ansi::NamedColor::Foreground] = colors.primary.foreground; self[ansi::NamedColor::Foreground] = colors.primary.foreground;
self[ansi::NamedColor::Background] = colors.primary.background; self[ansi::NamedColor::Background] = colors.primary.background;
// Background for custom cursor colors // Background for custom cursor colors.
self[ansi::NamedColor::Cursor] = colors.cursor.cursor.unwrap_or_else(Rgb::default); self[ansi::NamedColor::Cursor] = colors.cursor.cursor.unwrap_or_else(Rgb::default);
// Dims // Dims.
self[ansi::NamedColor::DimForeground] = self[ansi::NamedColor::DimForeground] =
colors.primary.dim_foreground.unwrap_or(colors.primary.foreground * DIM_FACTOR); colors.primary.dim_foreground.unwrap_or(colors.primary.foreground * DIM_FACTOR);
match colors.dim { match colors.dim {
@ -213,11 +213,11 @@ impl List {
pub fn fill_cube(&mut self, colors: &Colors) { pub fn fill_cube(&mut self, colors: &Colors) {
let mut index: usize = 16; let mut index: usize = 16;
// Build colors // Build colors.
for r in 0..6 { for r in 0..6 {
for g in 0..6 { for g in 0..6 {
for b in 0..6 { for b in 0..6 {
// Override colors 16..232 with the config (if present) // Override colors 16..232 with the config (if present).
if let Some(indexed_color) = if let Some(indexed_color) =
colors.indexed_colors.iter().find(|ic| ic.index == index as u8) colors.indexed_colors.iter().find(|ic| ic.index == index as u8)
{ {
@ -241,10 +241,10 @@ impl List {
let mut index: usize = 232; let mut index: usize = 232;
for i in 0..24 { for i in 0..24 {
// Index of the color is number of named colors + number of cube colors + i // Index of the color is number of named colors + number of cube colors + i.
let color_index = 16 + 216 + i; let color_index = 16 + 216 + i;
// Override colors 232..256 with the config (if present) // Override colors 232..256 with the config (if present).
if let Some(indexed_color) = if let Some(indexed_color) =
colors.indexed_colors.iter().find(|ic| ic.index == color_index) colors.indexed_colors.iter().find(|ic| ic.index == color_index)
{ {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Exports the `Term` type which is a high-level API for the Grid //! Exports the `Term` type which is a high-level API for the Grid.
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::ops::{Index, IndexMut, Range}; use std::ops::{Index, IndexMut, Range};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -49,7 +49,7 @@ const TITLE_STACK_MAX_DEPTH: usize = 4096;
/// Default tab interval, corresponding to terminfo `it` value. /// Default tab interval, corresponding to terminfo `it` value.
const INITIAL_TABSTOPS: usize = 8; const INITIAL_TABSTOPS: usize = 8;
/// A type that can expand a given point to a region /// A type that can expand a given point to a region.
/// ///
/// Usually this is implemented for some 2-D array type since /// Usually this is implemented for some 2-D array type since
/// points are two dimensional indices. /// points are two dimensional indices.
@ -68,7 +68,7 @@ pub trait Search {
impl<T> Search for Term<T> { impl<T> Search for Term<T> {
fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> { fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> {
// Limit the starting point to the last line in the history // Limit the starting point to the last line in the history.
point.line = min(point.line, self.grid.len() - 1); point.line = min(point.line, self.grid.len() - 1);
let mut iter = self.grid.iter_from(point); let mut iter = self.grid.iter_from(point);
@ -82,7 +82,8 @@ impl<T> Search for Term<T> {
} }
if iter.point().col == last_col && !cell.flags.contains(Flags::WRAPLINE) { if iter.point().col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char // Cut off if on new line or hit escape char.
break;
} }
point = iter.point(); point = iter.point();
@ -92,7 +93,7 @@ impl<T> Search for Term<T> {
} }
fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> { fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> {
// Limit the starting point to the last line in the history // Limit the starting point to the last line in the history.
point.line = min(point.line, self.grid.len() - 1); point.line = min(point.line, self.grid.len() - 1);
let mut iter = self.grid.iter_from(point); let mut iter = self.grid.iter_from(point);
@ -108,7 +109,8 @@ impl<T> Search for Term<T> {
point = iter.point(); point = iter.point();
if point.col == last_col && !cell.flags.contains(Flags::WRAPLINE) { if point.col == last_col && !cell.flags.contains(Flags::WRAPLINE) {
break; // cut off if on new line or hit escape char // Cut off if on new line or hit escape char.
break;
} }
} }
@ -140,7 +142,7 @@ impl<T> Search for Term<T> {
fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> { fn bracket_search(&self, point: Point<usize>) -> Option<Point<usize>> {
let start_char = self.grid[point.line][point.col].c; let start_char = self.grid[point.line][point.col].c;
// Find the matching bracket we're looking for // Find the matching bracket we're looking for.
let (forwards, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| { let (forwards, end_char) = BRACKET_PAIRS.iter().find_map(|(open, close)| {
if open == &start_char { if open == &start_char {
Some((true, *close)) Some((true, *close))
@ -158,16 +160,16 @@ impl<T> Search for Term<T> {
let mut skip_pairs = 0; let mut skip_pairs = 0;
loop { loop {
// Check the next cell // Check the next cell.
let cell = if forwards { iter.next() } else { iter.prev() }; let cell = if forwards { iter.next() } else { iter.prev() };
// Break if there are no more cells // Break if there are no more cells.
let c = match cell { let c = match cell {
Some(cell) => cell.c, Some(cell) => cell.c,
None => break, None => break,
}; };
// Check if the bracket matches // Check if the bracket matches.
if c == end_char && skip_pairs == 0 { if c == end_char && skip_pairs == 0 {
return Some(iter.point()); return Some(iter.point());
} else if c == start_char { } else if c == start_char {
@ -198,7 +200,7 @@ pub struct CursorKey {
pub is_wide: bool, pub is_wide: bool,
} }
/// Iterator that yields cells needing render /// Iterator that yields cells needing render.
/// ///
/// Yields cells that require work to be displayed (that is, not a an empty /// Yields cells that require work to be displayed (that is, not a an empty
/// background cell). Additionally, this manages some state of the grid only /// background cell). Additionally, this manages some state of the grid only
@ -216,7 +218,7 @@ pub struct RenderableCellsIter<'a, C> {
} }
impl<'a, C> RenderableCellsIter<'a, C> { impl<'a, C> RenderableCellsIter<'a, C> {
/// Create the renderable cells iterator /// Create the renderable cells iterator.
/// ///
/// The cursor and terminal mode are required for properly displaying the /// The cursor and terminal mode are required for properly displaying the
/// cursor. /// cursor.
@ -236,18 +238,18 @@ impl<'a, C> RenderableCellsIter<'a, C> {
(Column(0), grid.num_cols() - 1) (Column(0), grid.num_cols() - 1)
}; };
// Do not render completely offscreen selection // Do not render completely offscreen selection.
let viewport_start = grid.display_offset(); let viewport_start = grid.display_offset();
let viewport_end = viewport_start + grid.num_lines().0; let viewport_end = viewport_start + grid.num_lines().0;
if span.end.line >= viewport_end || span.start.line < viewport_start { if span.end.line >= viewport_end || span.start.line < viewport_start {
return None; return None;
} }
// Get on-screen lines of the selection's locations // Get on-screen lines of the selection's locations.
let mut start = grid.clamp_buffer_to_visible(span.start); let mut start = grid.clamp_buffer_to_visible(span.start);
let mut end = grid.clamp_buffer_to_visible(span.end); let mut end = grid.clamp_buffer_to_visible(span.end);
// Trim start/end with partially visible block selection // Trim start/end with partially visible block selection.
start.col = max(limit_start, start.col); start.col = max(limit_start, start.col);
end.col = min(limit_end, end.col); end.col = min(limit_end, end.col);
@ -271,7 +273,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
None => return false, None => return false,
}; };
// Do not invert block cursor at selection boundaries // Do not invert block cursor at selection boundaries.
if self.cursor.key.style == CursorStyle::Block if self.cursor.key.style == CursorStyle::Block
&& self.cursor.point == point && self.cursor.point == point
&& (selection.start == point && (selection.start == point
@ -283,7 +285,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
return false; return false;
} }
// Point itself is selected // Point itself is selected.
if selection.contains(point.col, point.line) { if selection.contains(point.col, point.line) {
return true; return true;
} }
@ -291,27 +293,27 @@ impl<'a, C> RenderableCellsIter<'a, C> {
let num_cols = self.grid.num_cols().0; let num_cols = self.grid.num_cols().0;
let cell = self.grid[&point]; let cell = self.grid[&point];
// Check if wide char's spacers are selected // Check if wide char's spacers are selected.
if cell.flags.contains(Flags::WIDE_CHAR) { if cell.flags.contains(Flags::WIDE_CHAR) {
let prevprev = point.sub(num_cols, 2); let prevprev = point.sub(num_cols, 2);
let prev = point.sub(num_cols, 1); let prev = point.sub(num_cols, 1);
let next = point.add(num_cols, 1); let next = point.add(num_cols, 1);
// Check trailing spacer // Check trailing spacer.
selection.contains(next.col, next.line) selection.contains(next.col, next.line)
// Check line-wrapping, leading spacer // Check line-wrapping, leading spacer.
|| (self.grid[&prev].flags.contains(Flags::WIDE_CHAR_SPACER) || (self.grid[&prev].flags.contains(Flags::WIDE_CHAR_SPACER)
&& !self.grid[&prevprev].flags.contains(Flags::WIDE_CHAR) && !self.grid[&prevprev].flags.contains(Flags::WIDE_CHAR)
&& selection.contains(prev.col, prev.line)) && selection.contains(prev.col, prev.line))
} else if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { } else if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
// Check if spacer's wide char is selected // Check if spacer's wide char is selected.
let prev = point.sub(num_cols, 1); let prev = point.sub(num_cols, 1);
if self.grid[&prev].flags.contains(Flags::WIDE_CHAR) { if self.grid[&prev].flags.contains(Flags::WIDE_CHAR) {
// Check previous cell for trailing spacer // Check previous cell for trailing spacer.
self.is_selected(prev) self.is_selected(prev)
} else { } else {
// Check next cell for line-wrapping, leading spacer // Check next cell for line-wrapping, leading spacer.
self.is_selected(point.add(num_cols, 1)) self.is_selected(point.add(num_cols, 1))
} }
} else { } else {
@ -328,7 +330,7 @@ pub enum RenderableCellContent {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct RenderableCell { pub struct RenderableCell {
/// A _Display_ line (not necessarily an _Active_ line) /// A _Display_ line (not necessarily an _Active_ line).
pub line: Line, pub line: Line,
pub column: Column, pub column: Column,
pub inner: RenderableCellContent, pub inner: RenderableCellContent,
@ -345,30 +347,30 @@ impl RenderableCell {
cell: Indexed<Cell>, cell: Indexed<Cell>,
selected: bool, selected: bool,
) -> Self { ) -> Self {
// Lookup RGB values // Lookup RGB values.
let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags); let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags);
let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg); let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg);
let mut bg_alpha = Self::compute_bg_alpha(cell.bg); let mut bg_alpha = Self::compute_bg_alpha(cell.bg);
let selection_background = config.colors.selection.background; let selection_background = config.colors.selection.background;
if let (true, Some(col)) = (selected, selection_background) { if let (true, Some(col)) = (selected, selection_background) {
// Override selection background with config colors // Override selection background with config colors.
bg_rgb = col; bg_rgb = col;
bg_alpha = 1.0; bg_alpha = 1.0;
} else if selected ^ cell.inverse() { } else if selected ^ cell.inverse() {
if fg_rgb == bg_rgb && !cell.flags.contains(Flags::HIDDEN) { if fg_rgb == bg_rgb && !cell.flags.contains(Flags::HIDDEN) {
// Reveal inversed text when fg/bg is the same // Reveal inversed text when fg/bg is the same.
fg_rgb = colors[NamedColor::Background]; fg_rgb = colors[NamedColor::Background];
bg_rgb = colors[NamedColor::Foreground]; bg_rgb = colors[NamedColor::Foreground];
} else { } else {
// Invert cell fg and bg colors // Invert cell fg and bg colors.
mem::swap(&mut fg_rgb, &mut bg_rgb); mem::swap(&mut fg_rgb, &mut bg_rgb);
} }
bg_alpha = 1.0; bg_alpha = 1.0;
} }
// Override selection text with config colors // Override selection text with config colors.
if let (true, Some(col)) = (selected, config.colors.selection.text) { if let (true, Some(col)) = (selected, config.colors.selection.text) {
fg_rgb = col; fg_rgb = col;
} }
@ -389,7 +391,7 @@ impl RenderableCell {
Color::Spec(rgb) => rgb, Color::Spec(rgb) => rgb,
Color::Named(ansi) => { Color::Named(ansi) => {
match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) { match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) {
// If no bright foreground is set, treat it like the BOLD flag doesn't exist // If no bright foreground is set, treat it like the BOLD flag doesn't exist.
(_, Flags::DIM_BOLD) (_, Flags::DIM_BOLD)
if ansi == NamedColor::Foreground if ansi == NamedColor::Foreground
&& config.colors.primary.bright_foreground.is_none() => && config.colors.primary.bright_foreground.is_none() =>
@ -398,9 +400,9 @@ impl RenderableCell {
}, },
// Draw bold text in bright colors *and* contains bold flag. // Draw bold text in bright colors *and* contains bold flag.
(true, Flags::BOLD) => colors[ansi.to_bright()], (true, Flags::BOLD) => colors[ansi.to_bright()],
// Cell is marked as dim and not bold // Cell is marked as dim and not bold.
(_, Flags::DIM) | (false, Flags::DIM_BOLD) => colors[ansi.to_dim()], (_, Flags::DIM) | (false, Flags::DIM_BOLD) => colors[ansi.to_dim()],
// None of the above, keep original color. // None of the above, keep original color..
_ => colors[ansi], _ => colors[ansi],
} }
}, },
@ -443,7 +445,7 @@ impl RenderableCell {
impl<'a, C> Iterator for RenderableCellsIter<'a, C> { impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
type Item = RenderableCell; type Item = RenderableCell;
/// Gets the next renderable cell /// Gets the next renderable cell.
/// ///
/// Skips empty (background) cells and applies any flags to the cell state /// Skips empty (background) cells and applies any flags to the cell state
/// (eg. invert fg and bg colors). /// (eg. invert fg and bg colors).
@ -455,7 +457,7 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
{ {
let selected = self.is_selected(self.cursor.point); let selected = self.is_selected(self.cursor.point);
// Handle cell below cursor // Handle cell below cursor.
if self.cursor.rendered { if self.cursor.rendered {
let mut cell = let mut cell =
RenderableCell::new(self.config, self.colors, self.inner.next()?, selected); RenderableCell::new(self.config, self.colors, self.inner.next()?, selected);
@ -470,7 +472,7 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
return Some(cell); return Some(cell);
} else { } else {
// Handle cursor // Handle cursor.
self.cursor.rendered = true; self.cursor.rendered = true;
let buffer_point = self.grid.visible_to_buffer(self.cursor.point); let buffer_point = self.grid.visible_to_buffer(self.cursor.point);
@ -611,24 +613,24 @@ impl IndexMut<CharsetIndex> for Charsets {
#[derive(Default, Copy, Clone)] #[derive(Default, Copy, Clone)]
pub struct Cursor { pub struct Cursor {
/// The location of this cursor /// The location of this cursor.
pub point: Point, pub point: Point,
/// Template cell when using this cursor /// Template cell when using this cursor.
template: Cell, template: Cell,
/// Currently configured graphic character sets /// Currently configured graphic character sets.
charsets: Charsets, charsets: Charsets,
} }
pub struct VisualBell { pub struct VisualBell {
/// Visual bell animation /// Visual bell animation.
animation: VisualBellAnimation, animation: VisualBellAnimation,
/// Visual bell duration /// Visual bell duration.
duration: Duration, duration: Duration,
/// The last time the visual bell rang, if at all /// The last time the visual bell rang, if at all.
start_time: Option<Instant>, start_time: Option<Instant>,
} }
@ -763,7 +765,7 @@ pub struct SizeInfo {
/// Horizontal window padding. /// Horizontal window padding.
pub padding_y: f32, pub padding_y: f32,
/// DPI factor of the current window. /// DPR of the current window.
#[serde(default)] #[serde(default)]
pub dpr: f64, pub dpr: f64,
} }
@ -870,7 +872,7 @@ pub struct Term<T> {
/// Style of the vi mode cursor. /// Style of the vi mode cursor.
vi_mode_cursor_style: Option<CursorStyle>, vi_mode_cursor_style: Option<CursorStyle>,
/// Clipboard access coupled to the active window /// Clipboard access coupled to the active window.
clipboard: Clipboard, clipboard: Clipboard,
/// Proxy for sending events to the event loop. /// Proxy for sending events to the event loop.
@ -1007,7 +1009,7 @@ impl<T> Term<T> {
for line in (end.line + 1..=start.line).rev() { for line in (end.line + 1..=start.line).rev() {
res += &self.line_to_string(line, start.col..end.col, start.col.0 != 0); res += &self.line_to_string(line, start.col..end.col, start.col.0 != 0);
// If the last column is included, newline is appended automatically // If the last column is included, newline is appended automatically.
if end.col != self.cols() - 1 { if end.col != self.cols() - 1 {
res += "\n"; res += "\n";
} }
@ -1046,7 +1048,7 @@ impl<T> Term<T> {
let grid_line = &self.grid[line]; let grid_line = &self.grid[line];
let line_length = min(grid_line.line_length(), cols.end + 1); let line_length = min(grid_line.line_length(), cols.end + 1);
// Include wide char when trailing spacer is selected // Include wide char when trailing spacer is selected.
if grid_line[cols.start].flags.contains(Flags::WIDE_CHAR_SPACER) { if grid_line[cols.start].flags.contains(Flags::WIDE_CHAR_SPACER) {
cols.start -= 1; cols.start -= 1;
} }
@ -1055,7 +1057,7 @@ impl<T> Term<T> {
for col in IndexRange::from(cols.start..line_length) { for col in IndexRange::from(cols.start..line_length) {
let cell = grid_line[col]; let cell = grid_line[col];
// Skip over cells until next tab-stop once a tab was found // Skip over cells until next tab-stop once a tab was found.
if tab_mode { if tab_mode {
if self.tabs[col] { if self.tabs[col] {
tab_mode = false; tab_mode = false;
@ -1069,10 +1071,10 @@ impl<T> Term<T> {
} }
if !cell.flags.contains(Flags::WIDE_CHAR_SPACER) { if !cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
// Push cells primary character // Push cells primary character.
text.push(cell.c); text.push(cell.c);
// Push zero-width characters // Push zero-width characters.
for c in (&cell.chars()[1..]).iter().take_while(|c| **c != ' ') { for c in (&cell.chars()[1..]).iter().take_while(|c| **c != ' ') {
text.push(*c); text.push(*c);
} }
@ -1086,7 +1088,7 @@ impl<T> Term<T> {
text.push('\n'); text.push('\n');
} }
// If wide char is not part of the selection, but leading spacer is, include it // If wide char is not part of the selection, but leading spacer is, include it.
if line_length == self.grid.num_cols() if line_length == self.grid.num_cols()
&& line_length.0 >= 2 && line_length.0 >= 2
&& grid_line[line_length - 1].flags.contains(Flags::WIDE_CHAR_SPACER) && grid_line[line_length - 1].flags.contains(Flags::WIDE_CHAR_SPACER)
@ -1103,7 +1105,7 @@ impl<T> Term<T> {
self.grid.visible_to_buffer(point) self.grid.visible_to_buffer(point)
} }
/// Access to the raw grid data structure /// Access to the raw grid data structure.
/// ///
/// This is a bit of a hack; when the window is closed, the event processor /// This is a bit of a hack; when the window is closed, the event processor
/// serializes the grid state to a file. /// serializes the grid state to a file.
@ -1111,13 +1113,13 @@ impl<T> Term<T> {
&self.grid &self.grid
} }
/// Mutable access for swapping out the grid during tests /// Mutable access for swapping out the grid during tests.
#[cfg(test)] #[cfg(test)]
pub fn grid_mut(&mut self) -> &mut Grid<Cell> { pub fn grid_mut(&mut self) -> &mut Grid<Cell> {
&mut self.grid &mut self.grid
} }
/// Iterate over the *renderable* cells in the terminal /// Iterate over the *renderable* cells in the terminal.
/// ///
/// A renderable cell is any cell which has content other than the default /// A renderable cell is any cell which has content other than the default
/// background color. Cells with an alternate background color are /// background color. Cells with an alternate background color are
@ -1128,7 +1130,7 @@ impl<T> Term<T> {
RenderableCellsIter::new(&self, config, selection) RenderableCellsIter::new(&self, config, selection)
} }
/// Resize terminal to new dimensions /// Resize terminal to new dimensions.
pub fn resize(&mut self, size: &SizeInfo) { pub fn resize(&mut self, size: &SizeInfo) {
let old_cols = self.grid.num_cols(); let old_cols = self.grid.num_cols();
let old_lines = self.grid.num_lines(); let old_lines = self.grid.num_lines();
@ -1159,11 +1161,11 @@ impl<T> Term<T> {
let alt_cursor_point = let alt_cursor_point =
if is_alt { &mut self.cursor_save.point } else { &mut self.cursor_save_alt.point }; if is_alt { &mut self.cursor_save.point } else { &mut self.cursor_save_alt.point };
// Resize grids to new size // Resize grids to new size.
self.grid.resize(!is_alt, num_lines, num_cols, &mut self.cursor.point, &Cell::default()); self.grid.resize(!is_alt, num_lines, num_cols, &mut self.cursor.point, &Cell::default());
self.alt_grid.resize(is_alt, num_lines, num_cols, alt_cursor_point, &Cell::default()); self.alt_grid.resize(is_alt, num_lines, num_cols, alt_cursor_point, &Cell::default());
// Reset scrolling region to new size // Reset scrolling region to new size.
self.scroll_region = Line(0)..self.grid.num_lines(); self.scroll_region = Line(0)..self.grid.num_lines();
// Ensure cursors are in-bounds. // Ensure cursors are in-bounds.
@ -1176,7 +1178,7 @@ impl<T> Term<T> {
self.vi_mode_cursor.point.col = min(self.vi_mode_cursor.point.col, num_cols - 1); self.vi_mode_cursor.point.col = min(self.vi_mode_cursor.point.col, num_cols - 1);
self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1); self.vi_mode_cursor.point.line = min(self.vi_mode_cursor.point.line, num_lines - 1);
// Recreate tabs list // Recreate tabs list.
self.tabs.resize(self.grid.num_cols()); self.tabs.resize(self.grid.num_cols());
} }
@ -1201,7 +1203,7 @@ impl<T> Term<T> {
mem::swap(&mut self.grid, &mut self.alt_grid); mem::swap(&mut self.grid, &mut self.alt_grid);
} }
/// Scroll screen down /// Scroll screen down.
/// ///
/// Text moves down; clear at bottom /// Text moves down; clear at bottom
/// Expects origin to be in scroll range. /// Expects origin to be in scroll range.
@ -1225,7 +1227,7 @@ impl<T> Term<T> {
trace!("Scrolling up relative: origin={}, lines={}", origin, lines); trace!("Scrolling up relative: origin={}, lines={}", origin, lines);
let lines = min(lines, self.scroll_region.end - self.scroll_region.start); let lines = min(lines, self.scroll_region.end - self.scroll_region.start);
// Scroll from origin to bottom less number of lines // Scroll from origin to bottom less number of lines.
let template = Cell { bg: self.cursor.template.bg, ..Cell::default() }; let template = Cell { bg: self.cursor.template.bg, ..Cell::default() };
self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &template); self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &template);
} }
@ -1234,11 +1236,11 @@ impl<T> Term<T> {
where where
T: EventListener, T: EventListener,
{ {
// Setting 132 column font makes no sense, but run the other side effects // Setting 132 column font makes no sense, but run the other side effects.
// Clear scrolling region // Clear scrolling region.
self.set_scrolling_region(1, self.grid.num_lines().0); self.set_scrolling_region(1, self.grid.num_lines().0);
// Clear grid // Clear grid.
let template = self.cursor.template; let template = self.cursor.template;
self.grid.region_mut(..).each(|c| c.reset(&template)); self.grid.region_mut(..).each(|c| c.reset(&template));
} }
@ -1267,7 +1269,7 @@ impl<T> Term<T> {
self.mode ^= TermMode::VI; self.mode ^= TermMode::VI;
self.grid.selection = None; self.grid.selection = None;
// Reset vi mode cursor position to match primary cursor // Reset vi mode cursor position to match primary cursor.
if self.mode.contains(TermMode::VI) { if self.mode.contains(TermMode::VI) {
let line = min(self.cursor.point.line + self.grid.display_offset(), self.lines() - 1); let line = min(self.cursor.point.line + self.grid.display_offset(), self.lines() - 1);
self.vi_mode_cursor = ViModeCursor::new(Point::new(line, self.cursor.point.col)); self.vi_mode_cursor = ViModeCursor::new(Point::new(line, self.cursor.point.col));
@ -1282,18 +1284,18 @@ impl<T> Term<T> {
where where
T: EventListener, T: EventListener,
{ {
// Require vi mode to be active // Require vi mode to be active.
if !self.mode.contains(TermMode::VI) { if !self.mode.contains(TermMode::VI) {
return; return;
} }
// Move cursor // Move cursor.
self.vi_mode_cursor = self.vi_mode_cursor.motion(self, motion); self.vi_mode_cursor = self.vi_mode_cursor.motion(self, motion);
// Update selection if one is active // Update selection if one is active.
let viewport_point = self.visible_to_buffer(self.vi_mode_cursor.point); let viewport_point = self.visible_to_buffer(self.vi_mode_cursor.point);
if let Some(selection) = &mut self.grid.selection { if let Some(selection) = &mut self.grid.selection {
// Do not extend empty selections started by single mouse click // Do not extend empty selections started by single mouse click.
if !selection.is_empty() { if !selection.is_empty() {
selection.update(viewport_point, Side::Left); selection.update(viewport_point, Side::Left);
selection.include_all(); selection.include_all();
@ -1348,7 +1350,7 @@ impl<T> Term<T> {
fn renderable_cursor<C>(&self, config: &Config<C>) -> RenderableCursor { fn renderable_cursor<C>(&self, config: &Config<C>) -> RenderableCursor {
let vi_mode = self.mode.contains(TermMode::VI); let vi_mode = self.mode.contains(TermMode::VI);
// Cursor position // Cursor position.
let mut point = if vi_mode { let mut point = if vi_mode {
self.vi_mode_cursor.point self.vi_mode_cursor.point
} else { } else {
@ -1357,7 +1359,7 @@ impl<T> Term<T> {
point point
}; };
// Cursor shape // Cursor shape.
let hidden = !self.mode.contains(TermMode::SHOW_CURSOR) || point.line >= self.lines(); let hidden = !self.mode.contains(TermMode::SHOW_CURSOR) || point.line >= self.lines();
let cursor_style = if hidden && !vi_mode { let cursor_style = if hidden && !vi_mode {
point.line = Line(0); point.line = Line(0);
@ -1374,7 +1376,7 @@ impl<T> Term<T> {
} }
}; };
// Cursor colors // Cursor colors.
let (text_color, cursor_color) = if vi_mode { let (text_color, cursor_color) = if vi_mode {
(config.vi_mode_cursor_text_color(), config.vi_mode_cursor_cursor_color()) (config.vi_mode_cursor_text_color(), config.vi_mode_cursor_cursor_color())
} else { } else {
@ -1382,7 +1384,7 @@ impl<T> Term<T> {
(config.cursor_text_color(), cursor_cursor_color) (config.cursor_text_color(), cursor_cursor_color)
}; };
// Expand across wide cell when inside wide char or spacer // Expand across wide cell when inside wide char or spacer.
let buffer_point = self.visible_to_buffer(point); let buffer_point = self.visible_to_buffer(point);
let cell = self.grid[buffer_point.line][buffer_point.col]; let cell = self.grid[buffer_point.line][buffer_point.col];
let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER) let is_wide = if cell.flags.contains(Flags::WIDE_CHAR_SPACER)
@ -1417,16 +1419,16 @@ impl<T> TermInfo for Term<T> {
} }
impl<T: EventListener> Handler for Term<T> { impl<T: EventListener> Handler for Term<T> {
/// A character to be displayed /// A character to be displayed.
#[inline] #[inline]
fn input(&mut self, c: char) { fn input(&mut self, c: char) {
// Number of cells the char will occupy // Number of cells the char will occupy.
let width = match c.width() { let width = match c.width() {
Some(width) => width, Some(width) => width,
None => return, None => return,
}; };
// Handle zero-width characters // Handle zero-width characters.
if width == 0 { if width == 0 {
let mut col = self.cursor.point.col.0.saturating_sub(1); let mut col = self.cursor.point.col.0.saturating_sub(1);
let line = self.cursor.point.line; let line = self.cursor.point.line;
@ -1437,14 +1439,14 @@ impl<T: EventListener> Handler for Term<T> {
return; return;
} }
// Move cursor to next line // Move cursor to next line.
if self.input_needs_wrap { if self.input_needs_wrap {
self.wrapline(); self.wrapline();
} }
let num_cols = self.grid.num_cols(); let num_cols = self.grid.num_cols();
// If in insert mode, first shift cells to the right // If in insert mode, first shift cells to the right.
if self.mode.contains(TermMode::INSERT) && self.cursor.point.col + width < num_cols { if self.mode.contains(TermMode::INSERT) && self.cursor.point.col + width < num_cols {
let line = self.cursor.point.line; let line = self.cursor.point.line;
let col = self.cursor.point.col; let col = self.cursor.point.col;
@ -1460,16 +1462,16 @@ impl<T: EventListener> Handler for Term<T> {
if width == 1 { if width == 1 {
self.write_at_cursor(c); self.write_at_cursor(c);
} else { } else {
// Insert extra placeholder before wide char if glyph doesn't fit in this row anymore // Insert extra placeholder before wide char if glyph doesn't fit in this row anymore.
if self.cursor.point.col + 1 >= num_cols { if self.cursor.point.col + 1 >= num_cols {
self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER); self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
self.wrapline(); self.wrapline();
} }
// Write full width glyph to current cursor cell // Write full width glyph to current cursor cell.
self.write_at_cursor(c).flags.insert(Flags::WIDE_CHAR); self.write_at_cursor(c).flags.insert(Flags::WIDE_CHAR);
// Write spacer to cell following the wide glyph // Write spacer to cell following the wide glyph.
self.cursor.point.col += 1; self.cursor.point.col += 1;
self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER); self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
} }
@ -1517,7 +1519,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline] #[inline]
fn insert_blank(&mut self, count: Column) { fn insert_blank(&mut self, count: Column) {
// Ensure inserting within terminal bounds // Ensure inserting within terminal bounds.
let count = min(count, self.grid.num_cols() - self.cursor.point.col); let count = min(count, self.grid.num_cols() - self.cursor.point.col);
@ -1608,7 +1610,7 @@ impl<T: EventListener> Handler for Term<T> {
/// Insert tab at cursor position. /// Insert tab at cursor position.
#[inline] #[inline]
fn put_tab(&mut self, mut count: i64) { fn put_tab(&mut self, mut count: i64) {
// A tab after the last column is the same as a linebreak // A tab after the last column is the same as a linebreak.
if self.input_needs_wrap { if self.input_needs_wrap {
self.wrapline(); self.wrapline();
return; return;
@ -1636,7 +1638,7 @@ impl<T: EventListener> Handler for Term<T> {
} }
} }
/// Backspace `count` characters /// Backspace `count` characters.
#[inline] #[inline]
fn backspace(&mut self) { fn backspace(&mut self) {
trace!("Backspace"); trace!("Backspace");
@ -1646,7 +1648,7 @@ impl<T: EventListener> Handler for Term<T> {
} }
} }
/// Carriage return /// Carriage return.
#[inline] #[inline]
fn carriage_return(&mut self) { fn carriage_return(&mut self) {
trace!("Carriage return"); trace!("Carriage return");
@ -1654,7 +1656,7 @@ impl<T: EventListener> Handler for Term<T> {
self.input_needs_wrap = false; self.input_needs_wrap = false;
} }
/// Linefeed /// Linefeed.
#[inline] #[inline]
fn linefeed(&mut self) { fn linefeed(&mut self) {
trace!("Linefeed"); trace!("Linefeed");
@ -1666,7 +1668,7 @@ impl<T: EventListener> Handler for Term<T> {
} }
} }
/// Set current position as a tabstop /// Set current position as a tabstop.
#[inline] #[inline]
fn bell(&mut self) { fn bell(&mut self) {
trace!("Bell"); trace!("Bell");
@ -1679,7 +1681,7 @@ impl<T: EventListener> Handler for Term<T> {
trace!("[unimplemented] Substitute"); trace!("[unimplemented] Substitute");
} }
/// Run LF/NL /// Run LF/NL.
/// ///
/// LF/NL mode has some interesting history. According to ECMA-48 4th /// LF/NL mode has some interesting history. According to ECMA-48 4th
/// edition, in LINE FEED mode, /// edition, in LINE FEED mode,
@ -1757,7 +1759,7 @@ impl<T: EventListener> Handler for Term<T> {
let end = min(start + count, self.grid.num_cols()); let end = min(start + count, self.grid.num_cols());
let row = &mut self.grid[self.cursor.point.line]; let row = &mut self.grid[self.cursor.point.line];
// Cleared cells have current background color set // Cleared cells have current background color set.
for c in &mut row[start..end] { for c in &mut row[start..end] {
c.reset(&self.cursor.template); c.reset(&self.cursor.template);
} }
@ -1767,7 +1769,7 @@ impl<T: EventListener> Handler for Term<T> {
fn delete_chars(&mut self, count: Column) { fn delete_chars(&mut self, count: Column) {
let cols = self.grid.num_cols(); let cols = self.grid.num_cols();
// Ensure deleting within terminal bounds // Ensure deleting within terminal bounds.
let count = min(count, cols); let count = min(count, cols);
let start = self.cursor.point.col; let start = self.cursor.point.col;
@ -1858,7 +1860,7 @@ impl<T: EventListener> Handler for Term<T> {
} }
} }
/// Set the indexed color value /// Set the indexed color value.
#[inline] #[inline]
fn set_color(&mut self, index: usize, color: Rgb) { fn set_color(&mut self, index: usize, color: Rgb) {
trace!("Setting color[{}] = {:?}", index, color); trace!("Setting color[{}] = {:?}", index, color);
@ -1866,7 +1868,7 @@ impl<T: EventListener> Handler for Term<T> {
self.color_modified[index] = true; self.color_modified[index] = true;
} }
/// Write a foreground/background color escape sequence with the current color /// Write a foreground/background color escape sequence with the current color.
#[inline] #[inline]
fn dynamic_color_sequence<W: io::Write>( fn dynamic_color_sequence<W: io::Write>(
&mut self, &mut self,
@ -1884,7 +1886,7 @@ impl<T: EventListener> Handler for Term<T> {
let _ = writer.write_all(response.as_bytes()); let _ = writer.write_all(response.as_bytes());
} }
/// Reset the indexed color to original value /// Reset the indexed color to original value.
#[inline] #[inline]
fn reset_color(&mut self, index: usize) { fn reset_color(&mut self, index: usize) {
trace!("Resetting color[{}]", index); trace!("Resetting color[{}]", index);
@ -1892,7 +1894,7 @@ impl<T: EventListener> Handler for Term<T> {
self.color_modified[index] = false; self.color_modified[index] = false;
} }
/// Set the clipboard /// Set the clipboard.
#[inline] #[inline]
fn set_clipboard(&mut self, clipboard: u8, base64: &[u8]) { fn set_clipboard(&mut self, clipboard: u8, base64: &[u8]) {
let clipboard_type = match clipboard { let clipboard_type = match clipboard {
@ -1928,19 +1930,19 @@ impl<T: EventListener> Handler for Term<T> {
trace!("Clearing screen: {:?}", mode); trace!("Clearing screen: {:?}", mode);
let template = self.cursor.template; let template = self.cursor.template;
// Remove active selections // Remove active selections.
self.grid.selection = None; self.grid.selection = None;
match mode { match mode {
ansi::ClearMode::Above => { ansi::ClearMode::Above => {
// If clearing more than one line // If clearing more than one line.
if self.cursor.point.line > Line(1) { if self.cursor.point.line > Line(1) {
// Fully clear all lines before the current line // Fully clear all lines before the current line.
self.grid self.grid
.region_mut(..self.cursor.point.line) .region_mut(..self.cursor.point.line)
.each(|cell| cell.reset(&template)); .each(|cell| cell.reset(&template));
} }
// Clear up to the current column in the current line // Clear up to the current column in the current line.
let end = min(self.cursor.point.col + 1, self.grid.num_cols()); let end = min(self.cursor.point.col + 1, self.grid.num_cols());
for cell in &mut self.grid[self.cursor.point.line][..end] { for cell in &mut self.grid[self.cursor.point.line][..end] {
cell.reset(&template); cell.reset(&template);
@ -1982,7 +1984,7 @@ impl<T: EventListener> Handler for Term<T> {
} }
} }
// Reset all important fields in the term struct /// Reset all important fields in the term struct.
#[inline] #[inline]
fn reset_state(&mut self) { fn reset_state(&mut self) {
if self.alt { if self.alt {
@ -2008,7 +2010,7 @@ impl<T: EventListener> Handler for Term<T> {
#[inline] #[inline]
fn reverse_index(&mut self) { fn reverse_index(&mut self) {
trace!("Reversing index"); trace!("Reversing index");
// if cursor is at the top // If cursor is at the top.
if self.cursor.point.line == self.scroll_region.start { if self.cursor.point.line == self.scroll_region.start {
self.scroll_down(Line(1)); self.scroll_down(Line(1));
} else { } else {
@ -2016,7 +2018,7 @@ impl<T: EventListener> Handler for Term<T> {
} }
} }
/// set a terminal attribute /// Set a terminal attribute.
#[inline] #[inline]
fn terminal_attribute(&mut self, attr: Attr) { fn terminal_attribute(&mut self, attr: Attr) {
trace!("Setting attribute: {:?}", attr); trace!("Setting attribute: {:?}", attr);
@ -2062,7 +2064,7 @@ impl<T: EventListener> Handler for Term<T> {
}, },
ansi::Mode::ShowCursor => self.mode.insert(TermMode::SHOW_CURSOR), ansi::Mode::ShowCursor => self.mode.insert(TermMode::SHOW_CURSOR),
ansi::Mode::CursorKeys => self.mode.insert(TermMode::APP_CURSOR), ansi::Mode::CursorKeys => self.mode.insert(TermMode::APP_CURSOR),
// Mouse protocols are mutually exlusive // Mouse protocols are mutually exclusive.
ansi::Mode::ReportMouseClicks => { ansi::Mode::ReportMouseClicks => {
self.mode.remove(TermMode::MOUSE_MODE); self.mode.remove(TermMode::MOUSE_MODE);
self.mode.insert(TermMode::MOUSE_REPORT_CLICK); self.mode.insert(TermMode::MOUSE_REPORT_CLICK);
@ -2080,7 +2082,7 @@ impl<T: EventListener> Handler for Term<T> {
}, },
ansi::Mode::ReportFocusInOut => self.mode.insert(TermMode::FOCUS_IN_OUT), ansi::Mode::ReportFocusInOut => self.mode.insert(TermMode::FOCUS_IN_OUT),
ansi::Mode::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE), ansi::Mode::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE),
// Mouse encodings are mutually exlusive // Mouse encodings are mutually exclusive.
ansi::Mode::SgrMouse => { ansi::Mode::SgrMouse => {
self.mode.remove(TermMode::UTF8_MOUSE); self.mode.remove(TermMode::UTF8_MOUSE);
self.mode.insert(TermMode::SGR_MOUSE); self.mode.insert(TermMode::SGR_MOUSE);
@ -2094,7 +2096,7 @@ impl<T: EventListener> Handler for Term<T> {
ansi::Mode::LineFeedNewLine => self.mode.insert(TermMode::LINE_FEED_NEW_LINE), ansi::Mode::LineFeedNewLine => self.mode.insert(TermMode::LINE_FEED_NEW_LINE),
ansi::Mode::Origin => self.mode.insert(TermMode::ORIGIN), ansi::Mode::Origin => self.mode.insert(TermMode::ORIGIN),
ansi::Mode::DECCOLM => self.deccolm(), ansi::Mode::DECCOLM => self.deccolm(),
ansi::Mode::Insert => self.mode.insert(TermMode::INSERT), // heh ansi::Mode::Insert => self.mode.insert(TermMode::INSERT),
ansi::Mode::BlinkingCursor => { ansi::Mode::BlinkingCursor => {
trace!("... unimplemented mode"); trace!("... unimplemented mode");
}, },
@ -2415,7 +2417,7 @@ mod tests {
assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into())); assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into()));
} }
/// Check that the grid can be serialized back and forth losslessly /// Check that the grid can be serialized back and forth losslessly.
/// ///
/// This test is in the term module as opposed to the grid since we want to /// This test is in the term module as opposed to the grid since we want to
/// test this property with a T=Cell. /// test this property with a T=Cell.
@ -2462,17 +2464,17 @@ mod tests {
}; };
let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
// Add one line of scrollback // Add one line of scrollback.
term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default()); term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default());
// Clear the history // Clear the history.
term.clear_screen(ansi::ClearMode::Saved); term.clear_screen(ansi::ClearMode::Saved);
// Make sure that scrolling does not change the grid // Make sure that scrolling does not change the grid.
let mut scrolled_grid = term.grid.clone(); let mut scrolled_grid = term.grid.clone();
scrolled_grid.scroll_display(Scroll::Top); scrolled_grid.scroll_display(Scroll::Top);
// Truncate grids for comparison // Truncate grids for comparison.
scrolled_grid.truncate(); scrolled_grid.truncate();
term.grid.truncate(); term.grid.truncate();
@ -2492,14 +2494,14 @@ mod tests {
}; };
let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
// Create 10 lines of scrollback // Create 10 lines of scrollback.
for _ in 0..19 { for _ in 0..19 {
term.newline(); term.newline();
} }
assert_eq!(term.grid.history_size(), 10); assert_eq!(term.grid.history_size(), 10);
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
// Increase visible lines // Increase visible lines.
size.height = 30.; size.height = 30.;
term.resize(&size); term.resize(&size);
@ -2520,21 +2522,21 @@ mod tests {
}; };
let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
// Create 10 lines of scrollback // Create 10 lines of scrollback.
for _ in 0..19 { for _ in 0..19 {
term.newline(); term.newline();
} }
assert_eq!(term.grid.history_size(), 10); assert_eq!(term.grid.history_size(), 10);
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
// Enter alt screen // Enter alt screen.
term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
// Increase visible lines // Increase visible lines.
size.height = 30.; size.height = 30.;
term.resize(&size); term.resize(&size);
// Leave alt screen // Leave alt screen.
term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
assert_eq!(term.grid().history_size(), 0); assert_eq!(term.grid().history_size(), 0);
@ -2554,14 +2556,14 @@ mod tests {
}; };
let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
// Create 10 lines of scrollback // Create 10 lines of scrollback.
for _ in 0..19 { for _ in 0..19 {
term.newline(); term.newline();
} }
assert_eq!(term.grid.history_size(), 10); assert_eq!(term.grid.history_size(), 10);
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
// Increase visible lines // Increase visible lines.
size.height = 5.; size.height = 5.;
term.resize(&size); term.resize(&size);
@ -2582,21 +2584,21 @@ mod tests {
}; };
let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
// Create 10 lines of scrollback // Create 10 lines of scrollback.
for _ in 0..19 { for _ in 0..19 {
term.newline(); term.newline();
} }
assert_eq!(term.grid.history_size(), 10); assert_eq!(term.grid.history_size(), 10);
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0))); assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
// Enter alt screen // Enter alt screen.
term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
// Increase visible lines // Increase visible lines.
size.height = 5.; size.height = 5.;
term.resize(&size); term.resize(&size);
// Leave alt screen // Leave alt screen.
term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor); term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
assert_eq!(term.grid().history_size(), 15); assert_eq!(term.grid().history_size(), 15);
@ -2616,44 +2618,44 @@ mod tests {
}; };
let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock); let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
// Title None by default // Title None by default.
assert_eq!(term.title, None); assert_eq!(term.title, None);
// Title can be set // Title can be set.
term.set_title(Some("Test".into())); term.set_title(Some("Test".into()));
assert_eq!(term.title, Some("Test".into())); assert_eq!(term.title, Some("Test".into()));
// Title can be pushed onto stack // Title can be pushed onto stack.
term.push_title(); term.push_title();
term.set_title(Some("Next".into())); term.set_title(Some("Next".into()));
assert_eq!(term.title, Some("Next".into())); assert_eq!(term.title, Some("Next".into()));
assert_eq!(term.title_stack.get(0).unwrap(), &Some("Test".into())); assert_eq!(term.title_stack.get(0).unwrap(), &Some("Test".into()));
// Title can be popped from stack and set as the window title // Title can be popped from stack and set as the window title.
term.pop_title(); term.pop_title();
assert_eq!(term.title, Some("Test".into())); assert_eq!(term.title, Some("Test".into()));
assert!(term.title_stack.is_empty()); assert!(term.title_stack.is_empty());
// Title stack doesn't grow infinitely // Title stack doesn't grow infinitely.
for _ in 0..4097 { for _ in 0..4097 {
term.push_title(); term.push_title();
} }
assert_eq!(term.title_stack.len(), 4096); assert_eq!(term.title_stack.len(), 4096);
// Title and title stack reset when terminal state is reset // Title and title stack reset when terminal state is reset.
term.push_title(); term.push_title();
term.reset_state(); term.reset_state();
assert_eq!(term.title, None); assert_eq!(term.title, None);
assert!(term.title_stack.is_empty()); assert!(term.title_stack.is_empty());
// Title stack pops back to default // Title stack pops back to default.
term.title = None; term.title = None;
term.push_title(); term.push_title();
term.set_title(Some("Test".into())); term.set_title(Some("Test".into()));
term.pop_title(); term.pop_title();
assert_eq!(term.title, None); assert_eq!(term.title, None);
// Title can be reset to default // Title can be reset to default.
term.title = Some("Test".into()); term.title = Some("Test".into());
term.set_title(None); term.set_title(None);
assert_eq!(term.title, None); assert_eq!(term.title, None);
@ -2681,10 +2683,10 @@ mod benches {
fn send_event(&self, _event: Event) {} fn send_event(&self, _event: Event) {}
} }
/// Benchmark for the renderable cells iterator /// Benchmark for the renderable cells iterator.
/// ///
/// The renderable cells iterator yields cells that require work to be /// The renderable cells iterator yields cells that require work to be
/// displayed (that is, not a an empty background cell). This benchmark /// displayed (that is, not an empty background cell). This benchmark
/// measures how long it takes to process the whole iterator. /// measures how long it takes to process the whole iterator.
/// ///
/// When this benchmark was first added, it averaged ~78usec on my macbook /// When this benchmark was first added, it averaged ~78usec on my macbook

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! tty related functionality //! TTY related functionality.
use std::{env, io}; use std::{env, io};
use terminfo::Database; use terminfo::Database;
@ -52,14 +52,14 @@ pub trait EventedReadWrite {
fn write_token(&self) -> mio::Token; fn write_token(&self) -> mio::Token;
} }
/// Events concerning TTY child processes /// Events concerning TTY child processes.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ChildEvent { pub enum ChildEvent {
/// Indicates the child has exited /// Indicates the child has exited.
Exited, Exited,
} }
/// A pseudoterminal (or PTY) /// A pseudoterminal (or PTY).
/// ///
/// This is a refinement of EventedReadWrite that also provides a channel through which we can be /// This is a refinement of EventedReadWrite that also provides a channel through which we can be
/// notified if the PTY child process does something we care about (other than writing to the TTY). /// notified if the PTY child process does something we care about (other than writing to the TTY).
@ -67,13 +67,13 @@ pub enum ChildEvent {
pub trait EventedPty: EventedReadWrite { pub trait EventedPty: EventedReadWrite {
fn child_event_token(&self) -> mio::Token; fn child_event_token(&self) -> mio::Token;
/// Tries to retrieve an event /// Tries to retrieve an event.
/// ///
/// Returns `Some(event)` on success, or `None` if there are no events to retrieve. /// Returns `Some(event)` on success, or `None` if there are no events to retrieve.
fn next_child_event(&mut self) -> Option<ChildEvent>; fn next_child_event(&mut self) -> Option<ChildEvent>;
} }
// Setup environment variables /// Setup environment variables.
pub fn setup_env<C>(config: &Config<C>) { pub fn setup_env<C>(config: &Config<C>) {
// Default to 'alacritty' terminfo if it is available, otherwise // Default to 'alacritty' terminfo if it is available, otherwise
// default to 'xterm-256color'. May be overridden by user's config // default to 'xterm-256color'. May be overridden by user's config
@ -83,13 +83,13 @@ pub fn setup_env<C>(config: &Config<C>) {
if Database::from_name("alacritty").is_ok() { "alacritty" } else { "xterm-256color" }, if Database::from_name("alacritty").is_ok() { "alacritty" } else { "xterm-256color" },
); );
// Advertise 24-bit color support // Advertise 24-bit color support.
env::set_var("COLORTERM", "truecolor"); env::set_var("COLORTERM", "truecolor");
// Prevent child processes from inheriting startup notification env // Prevent child processes from inheriting startup notification env.
env::remove_var("DESKTOP_STARTUP_ID"); env::remove_var("DESKTOP_STARTUP_ID");
// Set env vars from config // Set env vars from config.
for (key, value) in config.env.iter() { for (key, value) in config.env.iter() {
env::set_var(key, value); env::set_var(key, value);
} }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! tty related functionality //! TTY related functionality.
use crate::config::{Config, Shell}; use crate::config::{Config, Shell};
use crate::event::OnResize; use crate::event::OnResize;
@ -37,9 +37,9 @@ use std::process::{Child, Command, Stdio};
use std::ptr; use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
/// Process ID of child process /// Process ID of child process.
/// ///
/// Necessary to put this in static storage for `sigchld` to have access /// Necessary to put this in static storage for `SIGCHLD` to have access.
static PID: AtomicUsize = AtomicUsize::new(0); static PID: AtomicUsize = AtomicUsize::new(0);
macro_rules! die { macro_rules! die {
@ -53,7 +53,7 @@ pub fn child_pid() -> pid_t {
PID.load(Ordering::Relaxed) as pid_t PID.load(Ordering::Relaxed) as pid_t
} }
/// Get raw fds for master/slave ends of a new pty /// Get raw fds for master/slave ends of a new PTY.
fn make_pty(size: winsize) -> (RawFd, RawFd) { fn make_pty(size: winsize) -> (RawFd, RawFd) {
let mut win_size = size; let mut win_size = size;
win_size.ws_xpixel = 0; win_size.ws_xpixel = 0;
@ -64,7 +64,7 @@ fn make_pty(size: winsize) -> (RawFd, RawFd) {
(ends.master, ends.slave) (ends.master, ends.slave)
} }
/// Really only needed on BSD, but should be fine elsewhere /// Really only needed on BSD, but should be fine elsewhere.
fn set_controlling_terminal(fd: c_int) { fn set_controlling_terminal(fd: c_int) {
let res = unsafe { let res = unsafe {
// TIOSCTTY changes based on platform and the `ioctl` call is different // TIOSCTTY changes based on platform and the `ioctl` call is different
@ -91,13 +91,13 @@ struct Passwd<'a> {
shell: &'a str, shell: &'a str,
} }
/// Return a Passwd struct with pointers into the provided buf /// Return a Passwd struct with pointers into the provided buf.
/// ///
/// # Unsafety /// # Unsafety
/// ///
/// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen. /// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen.
fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> { fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> {
// Create zeroed passwd struct // Create zeroed passwd struct.
let mut entry: MaybeUninit<libc::passwd> = MaybeUninit::uninit(); let mut entry: MaybeUninit<libc::passwd> = MaybeUninit::uninit();
let mut res: *mut libc::passwd = ptr::null_mut(); let mut res: *mut libc::passwd = ptr::null_mut();
@ -117,10 +117,10 @@ fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> {
die!("pw not found"); die!("pw not found");
} }
// sanity check // Sanity check.
assert_eq!(entry.pw_uid, uid); assert_eq!(entry.pw_uid, uid);
// Build a borrowed Passwd struct // Build a borrowed Passwd struct.
Passwd { Passwd {
name: unsafe { CStr::from_ptr(entry.pw_name).to_str().unwrap() }, name: unsafe { CStr::from_ptr(entry.pw_name).to_str().unwrap() },
passwd: unsafe { CStr::from_ptr(entry.pw_passwd).to_str().unwrap() }, passwd: unsafe { CStr::from_ptr(entry.pw_passwd).to_str().unwrap() },
@ -140,7 +140,7 @@ pub struct Pty {
signals_token: mio::Token, signals_token: mio::Token,
} }
/// Create a new tty and return a handle to interact with it. /// Create a new TTY and return a handle to interact with it.
pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty { pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty {
let win_size = size.to_winsize(); let win_size = size.to_winsize();
let mut buf = [0; 1024]; let mut buf = [0; 1024];
@ -163,15 +163,15 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) ->
builder.arg(arg); builder.arg(arg);
} }
// Setup child stdin/stdout/stderr as slave fd of pty // Setup child stdin/stdout/stderr as slave fd of PTY.
// Ownership of fd is transferred to the Stdio structs and will be closed by them at the end of // Ownership of fd is transferred to the Stdio structs and will be closed by them at the end of
// this scope. (It is not an issue that the fd is closed three times since File::drop ignores // this scope. (It is not an issue that the fd is closed three times since File::drop ignores
// error on libc::close.) // error on libc::close.).
builder.stdin(unsafe { Stdio::from_raw_fd(slave) }); builder.stdin(unsafe { Stdio::from_raw_fd(slave) });
builder.stderr(unsafe { Stdio::from_raw_fd(slave) }); builder.stderr(unsafe { Stdio::from_raw_fd(slave) });
builder.stdout(unsafe { Stdio::from_raw_fd(slave) }); builder.stdout(unsafe { Stdio::from_raw_fd(slave) });
// Setup shell environment // Setup shell environment.
builder.env("LOGNAME", pw.name); builder.env("LOGNAME", pw.name);
builder.env("USER", pw.name); builder.env("USER", pw.name);
builder.env("SHELL", pw.shell); builder.env("SHELL", pw.shell);
@ -183,7 +183,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) ->
unsafe { unsafe {
builder.pre_exec(move || { builder.pre_exec(move || {
// Create a new process group // Create a new process group.
let err = libc::setsid(); let err = libc::setsid();
if err == -1 { if err == -1 {
die!("Failed to set session id: {}", io::Error::last_os_error()); die!("Failed to set session id: {}", io::Error::last_os_error());
@ -191,7 +191,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) ->
set_controlling_terminal(slave); set_controlling_terminal(slave);
// No longer need slave/master fds // No longer need slave/master fds.
libc::close(slave); libc::close(slave);
libc::close(master); libc::close(master);
@ -206,17 +206,17 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) ->
}); });
} }
// Handle set working directory option // Handle set working directory option.
if let Some(dir) = &config.working_directory { if let Some(dir) = &config.working_directory {
builder.current_dir(dir); builder.current_dir(dir);
} }
// Prepare signal handling before spawning child // Prepare signal handling before spawning child.
let signals = Signals::new(&[sighook::SIGCHLD]).expect("error preparing signal handling"); let signals = Signals::new(&[sighook::SIGCHLD]).expect("error preparing signal handling");
match builder.spawn() { match builder.spawn() {
Ok(child) => { Ok(child) => {
// Remember child PID so other modules can use it // Remember child PID so other modules can use it.
PID.store(child.id() as usize, Ordering::Relaxed); PID.store(child.id() as usize, Ordering::Relaxed);
unsafe { unsafe {
@ -332,9 +332,9 @@ impl EventedPty for Pty {
} }
} }
/// Types that can produce a `libc::winsize` /// Types that can produce a `libc::winsize`.
pub trait ToWinsize { pub trait ToWinsize {
/// Get a `libc::winsize` /// Get a `libc::winsize`.
fn to_winsize(&self) -> winsize; fn to_winsize(&self) -> winsize;
} }
@ -350,7 +350,7 @@ impl<'a> ToWinsize for &'a SizeInfo {
} }
impl OnResize for Pty { impl OnResize for Pty {
/// Resize the pty /// Resize the PTY.
/// ///
/// Tells the kernel that the window size changed with the new pixel /// Tells the kernel that the window size changed with the new pixel
/// dimensions and line/column counts. /// dimensions and line/column counts.

View File

@ -106,10 +106,10 @@ mod tests {
child.kill().unwrap(); child.kill().unwrap();
// Poll for the event or fail with timeout if nothing has been sent // Poll for the event or fail with timeout if nothing has been sent.
poll.poll(&mut events, Some(WAIT_TIMEOUT)).unwrap(); poll.poll(&mut events, Some(WAIT_TIMEOUT)).unwrap();
assert_eq!(events.iter().next().unwrap().token(), child_events_token); assert_eq!(events.iter().next().unwrap().token(), child_events_token);
// Verify that at least one `ChildEvent::Exited` was received // Verify that at least one `ChildEvent::Exited` was received.
assert_eq!(child_exit_watcher.event_rx().try_recv(), Ok(ChildEvent::Exited)); assert_eq!(child_exit_watcher.event_rx().try_recv(), Ok(ChildEvent::Exited));
} }
} }

View File

@ -42,7 +42,7 @@ use crate::tty::windows::{cmdline, win32_string, Pty};
// done until a safety net is in place for versions of Windows // done until a safety net is in place for versions of Windows
// that do not support the ConPTY api, as such versions will // that do not support the ConPTY api, as such versions will
// pass unit testing - but fail to actually function. // pass unit testing - but fail to actually function.
/// Dynamically-loaded Pseudoconsole API from kernel32.dll /// Dynamically-loaded Pseudoconsole API from kernel32.dll.
/// ///
/// The field names are deliberately PascalCase as this matches /// The field names are deliberately PascalCase as this matches
/// the defined symbols in kernel32 and also is the convention /// the defined symbols in kernel32 and also is the convention
@ -58,7 +58,7 @@ struct ConptyApi {
impl ConptyApi { impl ConptyApi {
/// Load the API or None if it cannot be found. /// Load the API or None if it cannot be found.
pub fn new() -> Option<Self> { pub fn new() -> Option<Self> {
// Unsafe because windows API calls // Unsafe because windows API calls.
unsafe { unsafe {
let hmodule = GetModuleHandleA("kernel32\0".as_ptr() as _); let hmodule = GetModuleHandleA("kernel32\0".as_ptr() as _);
assert!(!hmodule.is_null()); assert!(!hmodule.is_null());
@ -80,7 +80,7 @@ impl ConptyApi {
} }
} }
/// RAII Pseudoconsole /// RAII Pseudoconsole.
pub struct Conpty { pub struct Conpty {
pub handle: HPCON, pub handle: HPCON,
api: ConptyApi, api: ConptyApi,
@ -91,12 +91,12 @@ impl Drop for Conpty {
// XXX: This will block until the conout pipe is drained. Will cause a deadlock if the // XXX: This will block until the conout pipe is drained. Will cause a deadlock if the
// conout pipe has already been dropped by this point. // conout pipe has already been dropped by this point.
// //
// See PR #3084 and https://docs.microsoft.com/en-us/windows/console/closepseudoconsole // See PR #3084 and https://docs.microsoft.com/en-us/windows/console/closepseudoconsole.
unsafe { (self.api.ClosePseudoConsole)(self.handle) } unsafe { (self.api.ClosePseudoConsole)(self.handle) }
} }
} }
// The Conpty handle can be sent between threads. // The ConPTY handle can be sent between threads.
unsafe impl Send for Conpty {} unsafe impl Send for Conpty {}
pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Option<Pty> { pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Option<Pty> {
@ -118,7 +118,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) ->
let coord = let coord =
coord_from_sizeinfo(size).expect("Overflow when creating initial size on pseudoconsole"); coord_from_sizeinfo(size).expect("Overflow when creating initial size on pseudoconsole");
// Create the Pseudo Console, using the pipes // Create the Pseudo Console, using the pipes.
let result = unsafe { let result = unsafe {
(api.CreatePseudoConsole)( (api.CreatePseudoConsole)(
coord, coord,
@ -133,7 +133,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) ->
let mut success; let mut success;
// Prepare child process startup info // Prepare child process startup info.
let mut size: SIZE_T = 0; let mut size: SIZE_T = 0;
@ -145,7 +145,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) ->
startup_info_ex.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32; startup_info_ex.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32;
// Setting this flag but leaving all the handles as default (null) ensures the // Setting this flag but leaving all the handles as default (null) ensures the
// pty process does not inherit any handles from this Alacritty process. // PTY process does not inherit any handles from this Alacritty process.
startup_info_ex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; startup_info_ex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the appropriately sized thread attribute list. // Create the appropriately sized thread attribute list.
@ -185,12 +185,12 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) ->
} }
} }
// Set thread attribute list's Pseudo Console to the specified ConPTY // Set thread attribute list's Pseudo Console to the specified ConPTY.
unsafe { unsafe {
success = UpdateProcThreadAttribute( success = UpdateProcThreadAttribute(
startup_info_ex.lpAttributeList, startup_info_ex.lpAttributeList,
0, 0,
22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE.
pty_handle, pty_handle,
mem::size_of::<HPCON>(), mem::size_of::<HPCON>(),
ptr::null_mut(), ptr::null_mut(),
@ -242,7 +242,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) ->
}) })
} }
// Panic with the last os error as message // Panic with the last os error as message.
fn panic_shell_spawn() { fn panic_shell_spawn() {
panic!("Unable to spawn shell: {}", Error::last_os_error()); panic!("Unable to spawn shell: {}", Error::last_os_error());
} }

View File

@ -51,9 +51,9 @@ pub struct Pty {
// `conout` before `backend` will cause a deadlock. // `conout` before `backend` will cause a deadlock.
backend: PtyBackend, backend: PtyBackend,
// TODO: It's on the roadmap for the Conpty API to support Overlapped I/O. // TODO: It's on the roadmap for the Conpty API to support Overlapped I/O.
// See https://github.com/Microsoft/console/issues/262 // See https://github.com/Microsoft/console/issues/262.
// When support for that lands then it should be possible to use // When support for that lands then it should be possible to use
// NamedPipe for the conout and conin handles // NamedPipe for the conout and conin handles.
conout: EventedReadablePipe, conout: EventedReadablePipe,
conin: EventedWritablePipe, conin: EventedWritablePipe,
read_token: mio::Token, read_token: mio::Token,

View File

@ -31,25 +31,25 @@ use crate::tty::windows::{cmdline, Pty};
pub use winpty::Winpty as Agent; pub use winpty::Winpty as Agent;
pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Pty { pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Pty {
// Create config // Create config.
let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap(); let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap();
wconfig.set_initial_size(size.cols().0 as i32, size.lines().0 as i32); wconfig.set_initial_size(size.cols().0 as i32, size.lines().0 as i32);
wconfig.set_mouse_mode(&MouseMode::Auto); wconfig.set_mouse_mode(&MouseMode::Auto);
// Start agent // Start agent.
let mut agent = Winpty::open(&wconfig).unwrap(); let mut agent = Winpty::open(&wconfig).unwrap();
let (conin, conout) = (agent.conin_name(), agent.conout_name()); let (conin, conout) = (agent.conin_name(), agent.conout_name());
let cmdline = cmdline(&config); let cmdline = cmdline(&config);
// Spawn process // Spawn process.
let spawnconfig = SpawnConfig::new( let spawnconfig = SpawnConfig::new(
SpawnFlags::AUTO_SHUTDOWN | SpawnFlags::EXIT_AFTER_SHUTDOWN, SpawnFlags::AUTO_SHUTDOWN | SpawnFlags::EXIT_AFTER_SHUTDOWN,
None, // appname None, // appname.
Some(&cmdline), Some(&cmdline),
config.working_directory.as_ref().map(|p| p.as_path()), config.working_directory.as_ref().map(|p| p.as_path()),
None, // Env None, // Env.
) )
.unwrap(); .unwrap();

View File

@ -24,9 +24,9 @@ use std::os::windows::process::CommandExt;
#[cfg(windows)] #[cfg(windows)]
use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW}; use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW};
/// Threading utilities /// Threading utilities.
pub mod thread { pub mod thread {
/// Like `thread::spawn`, but with a `name` argument /// Like `thread::spawn`, but with a `name` argument.
pub fn spawn_named<F, T, S>(name: S, f: F) -> ::std::thread::JoinHandle<T> pub fn spawn_named<F, T, S>(name: S, f: F) -> ::std::thread::JoinHandle<T>
where where
F: FnOnce() -> T + Send + 'static, F: FnOnce() -> T + Send + 'static,

View File

@ -147,7 +147,7 @@ impl ViModeCursor {
/// Get target cursor point for vim-like page movement. /// Get target cursor point for vim-like page movement.
#[must_use = "this returns the result of the operation, without modifying the original"] #[must_use = "this returns the result of the operation, without modifying the original"]
pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: isize) -> Self { pub fn scroll<T: EventListener>(mut self, term: &Term<T>, lines: isize) -> Self {
// Check number of lines the cursor needs to be moved // Check number of lines the cursor needs to be moved.
let overscroll = if lines > 0 { let overscroll = if lines > 0 {
let max_scroll = term.grid().history_size() - term.grid().display_offset(); let max_scroll = term.grid().history_size() - term.grid().display_offset();
max(0, lines - max_scroll as isize) max(0, lines - max_scroll as isize)
@ -156,18 +156,18 @@ impl ViModeCursor {
min(0, lines + max_scroll as isize) min(0, lines + max_scroll as isize)
}; };
// Clamp movement to within visible region // Clamp movement to within visible region.
let mut line = self.point.line.0 as isize; let mut line = self.point.line.0 as isize;
line -= overscroll; line -= overscroll;
line = max(0, min(term.grid().num_lines().0 as isize - 1, line)); line = max(0, min(term.grid().num_lines().0 as isize - 1, line));
// Find the first occupied cell after scrolling has been performed // Find the first occupied cell after scrolling has been performed.
let buffer_point = term.visible_to_buffer(self.point); let buffer_point = term.visible_to_buffer(self.point);
let mut target_line = buffer_point.line as isize + lines; let mut target_line = buffer_point.line as isize + lines;
target_line = max(0, min(term.grid().len() as isize - 1, target_line)); target_line = max(0, min(term.grid().len() as isize - 1, target_line));
let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().col; let col = first_occupied_in_line(term, target_line as usize).unwrap_or_default().col;
// Move cursor // Move cursor.
self.point = Point::new(Line(line as usize), col); self.point = Point::new(Line(line as usize), col);
self self
@ -179,7 +179,7 @@ fn scroll_to_point<T: EventListener>(term: &mut Term<T>, point: Point<usize>) {
let display_offset = term.grid().display_offset(); let display_offset = term.grid().display_offset();
let lines = term.grid().num_lines(); let lines = term.grid().num_lines();
// Scroll once the top/bottom has been reached // Scroll once the top/bottom has been reached.
if point.line >= display_offset + lines.0 { if point.line >= display_offset + lines.0 {
let lines = point.line.saturating_sub(display_offset + lines.0 - 1); let lines = point.line.saturating_sub(display_offset + lines.0 - 1);
term.scroll_display(Scroll::Lines(lines as isize)); term.scroll_display(Scroll::Lines(lines as isize));
@ -193,24 +193,24 @@ fn scroll_to_point<T: EventListener>(term: &mut Term<T>, point: Point<usize>) {
fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
let cols = term.grid().num_cols(); let cols = term.grid().num_cols();
// Expand across wide cells // Expand across wide cells.
point = expand_wide(term, point, false); point = expand_wide(term, point, false);
// Find last non-empty cell in the current line // Find last non-empty cell in the current line.
let occupied = last_occupied_in_line(term, point.line).unwrap_or_default(); let occupied = last_occupied_in_line(term, point.line).unwrap_or_default();
if point.col < occupied.col { if point.col < occupied.col {
// Jump to last occupied cell when not already at or beyond it // Jump to last occupied cell when not already at or beyond it.
occupied occupied
} else if is_wrap(term, point) { } else if is_wrap(term, point) {
// Jump to last occupied cell across linewraps // Jump to last occupied cell across linewraps.
while point.line > 0 && is_wrap(term, point) { while point.line > 0 && is_wrap(term, point) {
point.line -= 1; point.line -= 1;
} }
last_occupied_in_line(term, point.line).unwrap_or(point) last_occupied_in_line(term, point.line).unwrap_or(point)
} else { } else {
// Jump to last column when beyond the last occupied cell // Jump to last column when beyond the last occupied cell.
Point::new(point.line, cols - 1) Point::new(point.line, cols - 1)
} }
} }
@ -219,18 +219,18 @@ fn last<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> { fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
let cols = term.grid().num_cols(); let cols = term.grid().num_cols();
// Expand left across wide chars, since we're searching lines left to right // Expand left across wide chars, since we're searching lines left to right.
point = expand_wide(term, point, true); point = expand_wide(term, point, true);
// Find first non-empty cell in current line // Find first non-empty cell in current line.
let occupied = first_occupied_in_line(term, point.line) let occupied = first_occupied_in_line(term, point.line)
.unwrap_or_else(|| Point::new(point.line, cols - 1)); .unwrap_or_else(|| Point::new(point.line, cols - 1));
// Jump across wrapped lines if we're already at this line's first occupied cell // Jump across wrapped lines if we're already at this line's first occupied cell.
if point == occupied { if point == occupied {
let mut occupied = None; let mut occupied = None;
// Search for non-empty cell in previous lines // Search for non-empty cell in previous lines.
for line in (point.line + 1)..term.grid().len() { for line in (point.line + 1)..term.grid().len() {
if !is_wrap(term, Point::new(line, cols - 1)) { if !is_wrap(term, Point::new(line, cols - 1)) {
break; break;
@ -239,7 +239,7 @@ fn first_occupied<T>(term: &Term<T>, mut point: Point<usize>) -> Point<usize> {
occupied = first_occupied_in_line(term, line).or(occupied); occupied = first_occupied_in_line(term, line).or(occupied);
} }
// Fallback to the next non-empty cell // Fallback to the next non-empty cell.
let mut line = point.line; let mut line = point.line;
occupied.unwrap_or_else(|| loop { occupied.unwrap_or_else(|| loop {
if let Some(occupied) = first_occupied_in_line(term, line) { if let Some(occupied) = first_occupied_in_line(term, line) {
@ -265,9 +265,9 @@ fn semantic<T: EventListener>(
left: bool, left: bool,
start: bool, start: bool,
) -> Point<usize> { ) -> Point<usize> {
// Expand semantically based on movement direction // Expand semantically based on movement direction.
let expand_semantic = |point: Point<usize>| { let expand_semantic = |point: Point<usize>| {
// Do not expand when currently on a semantic escape char // Do not expand when currently on a semantic escape char.
let cell = term.grid()[point.line][point.col]; let cell = term.grid()[point.line][point.col];
if term.semantic_escape_chars().contains(cell.c) if term.semantic_escape_chars().contains(cell.c)
&& !cell.flags.contains(Flags::WIDE_CHAR_SPACER) && !cell.flags.contains(Flags::WIDE_CHAR_SPACER)
@ -280,27 +280,27 @@ fn semantic<T: EventListener>(
} }
}; };
// Make sure we jump above wide chars // Make sure we jump above wide chars.
point = expand_wide(term, point, left); point = expand_wide(term, point, left);
// Move to word boundary // Move to word boundary.
if left != start && !is_boundary(term, point, left) { if left != start && !is_boundary(term, point, left) {
point = expand_semantic(point); point = expand_semantic(point);
} }
// Skip whitespace // Skip whitespace.
let mut next_point = advance(term, point, left); let mut next_point = advance(term, point, left);
while !is_boundary(term, point, left) && is_space(term, next_point) { while !is_boundary(term, point, left) && is_space(term, next_point) {
point = next_point; point = next_point;
next_point = advance(term, point, left); next_point = advance(term, point, left);
} }
// Assure minimum movement of one cell // Assure minimum movement of one cell.
if !is_boundary(term, point, left) { if !is_boundary(term, point, left) {
point = advance(term, point, left); point = advance(term, point, left);
} }
// Move to word boundary // Move to word boundary.
if left == start && !is_boundary(term, point, left) { if left == start && !is_boundary(term, point, left) {
point = expand_semantic(point); point = expand_semantic(point);
} }
@ -315,18 +315,18 @@ fn word<T: EventListener>(
left: bool, left: bool,
start: bool, start: bool,
) -> Point<usize> { ) -> Point<usize> {
// Make sure we jump above wide chars // Make sure we jump above wide chars.
point = expand_wide(term, point, left); point = expand_wide(term, point, left);
if left == start { if left == start {
// Skip whitespace until right before a word // Skip whitespace until right before a word.
let mut next_point = advance(term, point, left); let mut next_point = advance(term, point, left);
while !is_boundary(term, point, left) && is_space(term, next_point) { while !is_boundary(term, point, left) && is_space(term, next_point) {
point = next_point; point = next_point;
next_point = advance(term, point, left); next_point = advance(term, point, left);
} }
// Skip non-whitespace until right inside word boundary // Skip non-whitespace until right inside word boundary.
let mut next_point = advance(term, point, left); let mut next_point = advance(term, point, left);
while !is_boundary(term, point, left) && !is_space(term, next_point) { while !is_boundary(term, point, left) && !is_space(term, next_point) {
point = next_point; point = next_point;
@ -335,12 +335,12 @@ fn word<T: EventListener>(
} }
if left != start { if left != start {
// Skip non-whitespace until just beyond word // Skip non-whitespace until just beyond word.
while !is_boundary(term, point, left) && !is_space(term, point) { while !is_boundary(term, point, left) && !is_space(term, point) {
point = advance(term, point, left); point = advance(term, point, left);
} }
// Skip whitespace until right inside word boundary // Skip whitespace until right inside word boundary.
while !is_boundary(term, point, left) && is_space(term, point) { while !is_boundary(term, point, left) && is_space(term, point) {
point = advance(term, point, left); point = advance(term, point, left);
} }

View File

@ -105,7 +105,7 @@ fn ref_test(dir: &Path) {
parser.advance(&mut terminal, byte, &mut io::sink()); parser.advance(&mut terminal, byte, &mut io::sink());
} }
// Truncate invisible lines from the grid // Truncate invisible lines from the grid.
let mut term_grid = terminal.grid().clone(); let mut term_grid = terminal.grid().clone();
term_grid.initialize_all(&Cell::default()); term_grid.initialize_all(&Cell::default());
term_grid.truncate(); term_grid.truncate();

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Constants for bitmap byte order //! Constants for bitmap byte order.
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
pub const kCGBitmapByteOrder32Little: u32 = 2 << 12; pub const kCGBitmapByteOrder32Little: u32 = 2 << 12;
pub const kCGBitmapByteOrder32Big: u32 = 4 << 12; pub const kCGBitmapByteOrder32Big: u32 = 4 << 12;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Font rendering based on CoreText //! Font rendering based on CoreText.
#![allow(improper_ctypes)] #![allow(improper_ctypes)]
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
@ -49,7 +49,7 @@ use super::{
BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight, BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight,
}; };
/// Font descriptor /// Font descriptor.
/// ///
/// The descriptor provides data about a font and supports creating a font. /// The descriptor provides data about a font and supports creating a font.
#[derive(Debug)] #[derive(Debug)]
@ -76,7 +76,7 @@ impl Descriptor {
} }
} }
/// Rasterizer, the main type exported by this package /// Rasterizer, the main type exported by this package.
/// ///
/// Given a fontdesc, can rasterize fonts. /// Given a fontdesc, can rasterize fonts.
pub struct Rasterizer { pub struct Rasterizer {
@ -86,16 +86,16 @@ pub struct Rasterizer {
use_thin_strokes: bool, use_thin_strokes: bool,
} }
/// Errors occurring when using the core text rasterizer /// Errors occurring when using the core text rasterizer.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Tried to rasterize a glyph but it was not available /// Tried to rasterize a glyph but it was not available.
MissingGlyph(char), MissingGlyph(char),
/// Couldn't find font matching description /// Couldn't find font matching description.
MissingFont(FontDesc), MissingFont(FontDesc),
/// Requested an operation with a FontKey that isn't known to the rasterizer /// Requested an operation with a FontKey that isn't known to the rasterizer.
FontNotLoaded, FontNotLoaded,
} }
@ -136,7 +136,7 @@ impl crate::Rasterize for Rasterizer {
}) })
} }
/// Get metrics for font specified by FontKey /// Get metrics for font specified by FontKey.
fn metrics(&self, key: FontKey, _size: Size) -> Result<Metrics, Error> { fn metrics(&self, key: FontKey, _size: Size) -> Result<Metrics, Error> {
let font = self.fonts.get(&key).ok_or(Error::FontNotLoaded)?; let font = self.fonts.get(&key).ok_or(Error::FontNotLoaded)?;
@ -156,21 +156,21 @@ impl crate::Rasterize for Rasterizer {
}) })
} }
/// Get rasterized glyph for given glyph key /// Get rasterized glyph for given glyph key.
fn get_glyph(&mut self, glyph: GlyphKey) -> Result<RasterizedGlyph, Error> { fn get_glyph(&mut self, glyph: GlyphKey) -> Result<RasterizedGlyph, Error> {
// get loaded font // Get loaded font.
let font = self.fonts.get(&glyph.font_key).ok_or(Error::FontNotLoaded)?; let font = self.fonts.get(&glyph.font_key).ok_or(Error::FontNotLoaded)?;
// first try the font itself as a direct hit // First try the font itself as a direct hit.
self.maybe_get_glyph(glyph, font).unwrap_or_else(|| { self.maybe_get_glyph(glyph, font).unwrap_or_else(|| {
// then try fallbacks // Then try fallbacks.
for fallback in &font.fallbacks { for fallback in &font.fallbacks {
if let Some(result) = self.maybe_get_glyph(glyph, &fallback) { if let Some(result) = self.maybe_get_glyph(glyph, &fallback) {
// found a fallback // Found a fallback.
return result; return result;
} }
} }
// no fallback, give up. // No fallback, give up.
Err(Error::MissingGlyph(glyph.c)) Err(Error::MissingGlyph(glyph.c))
}) })
} }
@ -190,7 +190,7 @@ impl Rasterizer {
let descriptors = descriptors_for_family(&desc.name[..]); let descriptors = descriptors_for_family(&desc.name[..]);
for descriptor in descriptors { for descriptor in descriptors {
if descriptor.style_name == style { if descriptor.style_name == style {
// Found the font we want // Found the font we want.
let scaled_size = f64::from(size.as_f32_pts()) * f64::from(self.device_pixel_ratio); let scaled_size = f64::from(size.as_f32_pts()) * f64::from(self.device_pixel_ratio);
let font = descriptor.to_font(scaled_size, true); let font = descriptor.to_font(scaled_size, true);
return Ok(font); return Ok(font);
@ -221,7 +221,7 @@ impl Rasterizer {
for descriptor in descriptors { for descriptor in descriptors {
let font = descriptor.to_font(scaled_size, true); let font = descriptor.to_font(scaled_size, true);
if font.is_bold() == bold && font.is_italic() == italic { if font.is_bold() == bold && font.is_italic() == italic {
// Found the font we want // Found the font we want.
return Ok(font); return Ok(font);
} }
} }
@ -238,7 +238,7 @@ impl Rasterizer {
} }
} }
// Helper to try and get a glyph for a given font. Used for font fallback. /// Helper to try and get a glyph for a given font. Used for font fallback.
fn maybe_get_glyph( fn maybe_get_glyph(
&self, &self,
glyph: GlyphKey, glyph: GlyphKey,
@ -254,7 +254,7 @@ impl Rasterizer {
} }
} }
/// Specifies the intended rendering orientation of the font for obtaining glyph metrics /// Specifies the intended rendering orientation of the font for obtaining glyph metrics.
#[derive(Debug)] #[derive(Debug)]
pub enum FontOrientation { pub enum FontOrientation {
Default = kCTFontDefaultOrientation as isize, Default = kCTFontDefaultOrientation as isize,
@ -268,7 +268,7 @@ impl Default for FontOrientation {
} }
} }
/// A font /// A font.
#[derive(Clone)] #[derive(Clone)]
pub struct Font { pub struct Font {
ct_font: CTFont, ct_font: CTFont,
@ -278,9 +278,9 @@ pub struct Font {
unsafe impl Send for Font {} unsafe impl Send for Font {}
/// List all family names /// List all family names.
pub fn get_family_names() -> Vec<String> { pub fn get_family_names() -> Vec<String> {
// CFArray of CFStringRef // CFArray of CFStringRef.
let names = ct_get_family_names(); let names = ct_get_family_names();
let mut owned_names = Vec::new(); let mut owned_names = Vec::new();
@ -291,23 +291,23 @@ pub fn get_family_names() -> Vec<String> {
owned_names owned_names
} }
/// Return fallback descriptors for font/language list /// Return fallback descriptors for font/language list.
fn cascade_list_for_languages(ct_font: &CTFont, languages: &[String]) -> Vec<Descriptor> { fn cascade_list_for_languages(ct_font: &CTFont, languages: &[String]) -> Vec<Descriptor> {
// convert language type &Vec<String> -> CFArray // Convert language type &Vec<String> -> CFArray.
let langarr: CFArray<CFString> = { let langarr: CFArray<CFString> = {
let tmp: Vec<CFString> = let tmp: Vec<CFString> =
languages.iter().map(|language| CFString::new(&language)).collect(); languages.iter().map(|language| CFString::new(&language)).collect();
CFArray::from_CFTypes(&tmp) CFArray::from_CFTypes(&tmp)
}; };
// CFArray of CTFontDescriptorRef (again) // CFArray of CTFontDescriptorRef (again).
let list = ct_cascade_list_for_languages(ct_font, &langarr); let list = ct_cascade_list_for_languages(ct_font, &langarr);
// convert CFArray to Vec<Descriptor> // Convert CFArray to Vec<Descriptor>.
list.into_iter().map(|fontdesc| Descriptor::new(fontdesc.clone())).collect() list.into_iter().map(|fontdesc| Descriptor::new(fontdesc.clone())).collect()
} }
/// Get descriptors for family name /// Get descriptors for family name.
pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> { pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
let mut out = Vec::new(); let mut out = Vec::new();
@ -318,7 +318,7 @@ pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
create_for_family("Menlo").expect("Menlo exists") create_for_family("Menlo").expect("Menlo exists")
}); });
// CFArray of CTFontDescriptorRef (i think) // CFArray of CTFontDescriptorRef (i think).
let descriptors = ct_collection.get_descriptors(); let descriptors = ct_collection.get_descriptors();
if let Some(descriptors) = descriptors { if let Some(descriptors) = descriptors {
for descriptor in descriptors.iter() { for descriptor in descriptors.iter() {
@ -330,7 +330,7 @@ pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
} }
impl Descriptor { impl Descriptor {
/// Create a Font from this descriptor /// Create a Font from this descriptor.
pub fn to_font(&self, size: f64, load_fallbacks: bool) -> Font { pub fn to_font(&self, size: f64, load_fallbacks: bool) -> Font {
let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size); let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size);
let cg_font = ct_font.copy_to_CGFont(); let cg_font = ct_font.copy_to_CGFont();
@ -342,7 +342,7 @@ impl Descriptor {
.map(|descriptor| { .map(|descriptor| {
let menlo = ct_new_from_descriptor(&descriptor.ct_descriptor, size); let menlo = ct_new_from_descriptor(&descriptor.ct_descriptor, size);
// TODO fixme, hardcoded en for english // TODO fixme, hardcoded en for english.
let mut fallbacks = cascade_list_for_languages(&menlo, &["en".to_owned()]) let mut fallbacks = cascade_list_for_languages(&menlo, &["en".to_owned()])
.into_iter() .into_iter()
.filter(|desc| !desc.font_path.as_os_str().is_empty()) .filter(|desc| !desc.font_path.as_os_str().is_empty())
@ -361,7 +361,7 @@ impl Descriptor {
fallbacks.push(descriptor.to_font(size, false)) fallbacks.push(descriptor.to_font(size, false))
}; };
// Include Menlo in the fallback list as well // Include Menlo in the fallback list as well.
fallbacks.insert(0, Font { fallbacks.insert(0, Font {
cg_font: menlo.copy_to_CGFont(), cg_font: menlo.copy_to_CGFont(),
ct_font: menlo, ct_font: menlo,
@ -380,7 +380,7 @@ impl Descriptor {
} }
impl Font { impl Font {
/// The the bounding rect of a glyph /// The the bounding rect of a glyph.
pub fn bounding_rect_for_glyph( pub fn bounding_rect_for_glyph(
&self, &self,
orientation: FontOrientation, orientation: FontOrientation,
@ -488,7 +488,7 @@ impl Font {
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
); );
// Give the context an opaque, black background // Give the context an opaque, black background.
cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0); cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
let context_rect = CGRect::new( let context_rect = CGRect::new(
&CGPoint::new(0.0, 0.0), &CGPoint::new(0.0, 0.0),
@ -510,7 +510,7 @@ impl Font {
cg_context.set_allows_antialiasing(true); cg_context.set_allows_antialiasing(true);
cg_context.set_should_antialias(true); cg_context.set_should_antialias(true);
// Set fill color to white for drawing the glyph // Set fill color to white for drawing the glyph.
cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
let rasterization_origin = let rasterization_origin =
CGPoint { x: f64::from(-rasterized_left), y: f64::from(rasterized_descent) }; CGPoint { x: f64::from(-rasterized_left), y: f64::from(rasterized_descent) };
@ -540,15 +540,15 @@ impl Font {
} }
fn glyph_index(&self, character: char) -> Option<u32> { fn glyph_index(&self, character: char) -> Option<u32> {
// encode this char as utf-16 // Encode this char as utf-16.
let mut buf = [0; 2]; let mut buf = [0; 2];
let encoded: &[u16] = character.encode_utf16(&mut buf); let encoded: &[u16] = character.encode_utf16(&mut buf);
// and use the utf-16 buffer to get the index // And use the utf-16 buffer to get the index.
self.glyph_index_utf16(encoded) self.glyph_index_utf16(encoded)
} }
fn glyph_index_utf16(&self, encoded: &[u16]) -> Option<u32> { fn glyph_index_utf16(&self, encoded: &[u16]) -> Option<u32> {
// output buffer for the glyph. for non-BMP glyphs, like // Output buffer for the glyph. for non-BMP glyphs, like
// emojis, this will be filled with two chars the second // emojis, this will be filled with two chars the second
// always being a 0. // always being a 0.
let mut glyphs: [CGGlyph; 2] = [0; 2]; let mut glyphs: [CGGlyph; 2] = [0; 2];
@ -586,11 +586,11 @@ mod tests {
assert!(!list.is_empty()); assert!(!list.is_empty());
println!("{:?}", list); println!("{:?}", list);
// Check to_font // Check to_font.
let fonts = list.iter().map(|desc| desc.to_font(72., false)).collect::<Vec<_>>(); let fonts = list.iter().map(|desc| desc.to_font(72., false)).collect::<Vec<_>>();
for font in fonts { for font in fonts {
// Get a glyph // Get a glyph.
for c in &['a', 'b', 'c', 'd'] { for c in &['a', 'b', 'c', 'd'] {
let glyph = font.get_glyph(*c, 72., false).unwrap(); let glyph = font.get_glyph(*c, 72., false).unwrap();
@ -599,7 +599,7 @@ mod tests {
BitmapBuffer::RGBA(buf) => buf, BitmapBuffer::RGBA(buf) => buf,
}; };
// Debug the glyph.. sigh // Debug the glyph.. sigh.
for row in 0..glyph.height { for row in 0..glyph.height {
for col in 0..glyph.width { for col in 0..glyph.width {
let index = ((glyph.width * 3 * row) + (col * 3)) as usize; let index = ((glyph.width * 3 * row) + (col * 3)) as usize;

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Rasterization powered by DirectWrite //! Rasterization powered by DirectWrite.
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::OsString; use std::ffi::OsString;
@ -114,7 +114,7 @@ impl DirectWriteRasterizer {
let idx = *face let idx = *face
.get_glyph_indices(&[c as u32]) .get_glyph_indices(&[c as u32])
.first() .first()
// DirectWrite returns 0 if the glyph does not exist in the font // DirectWrite returns 0 if the glyph does not exist in the font.
.filter(|glyph_index| **glyph_index != 0) .filter(|glyph_index| **glyph_index != 0)
.ok_or_else(|| Error::MissingGlyph(c))?; .ok_or_else(|| Error::MissingGlyph(c))?;
@ -184,7 +184,7 @@ impl crate::Rasterize for DirectWriteRasterizer {
let line_height = f64::from(ascent - descent + line_gap); let line_height = f64::from(ascent - descent + line_gap);
// Since all monospace characters have the same width, we use `!` for horizontal metrics // Since all monospace characters have the same width, we use `!` for horizontal metrics.
let c = '!'; let c = '!';
let glyph_index = self.get_glyph_index(face, c)?; let glyph_index = self.get_glyph_index(face, c)?;
@ -205,7 +205,7 @@ impl crate::Rasterize for DirectWriteRasterizer {
} }
fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result<FontKey, Error> { fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result<FontKey, Error> {
// Fast path if face is already loaded // Fast path if face is already loaded.
if let Some(key) = self.keys.get(desc) { if let Some(key) = self.keys.get(desc) {
return Ok(*key); return Ok(*key);
} }
@ -218,7 +218,7 @@ impl crate::Rasterize for DirectWriteRasterizer {
let font = match desc.style { let font = match desc.style {
Style::Description { weight, slant } => { Style::Description { weight, slant } => {
// This searches for the "best" font - should mean we don't have to worry about // This searches for the "best" font - should mean we don't have to worry about
// fallbacks if our exact desired weight/style isn't available // fallbacks if our exact desired weight/style isn't available.
Ok(family.get_first_matching_font(weight.into(), FontStretch::Normal, slant.into())) Ok(family.get_first_matching_font(weight.into(), FontStretch::Normal, slant.into()))
}, },
Style::Specific(ref style) => { Style::Specific(ref style) => {
@ -332,7 +332,7 @@ fn get_current_locale() -> String {
let mut buf = vec![0u16; LOCALE_NAME_MAX_LENGTH]; let mut buf = vec![0u16; LOCALE_NAME_MAX_LENGTH];
let len = unsafe { GetUserDefaultLocaleName(buf.as_mut_ptr(), buf.len() as i32) as usize }; let len = unsafe { GetUserDefaultLocaleName(buf.as_mut_ptr(), buf.len() as i32) as usize };
// `len` includes null byte, which we don't need in Rust // `len` includes null byte, which we don't need in Rust.
OsString::from_wide(&buf[..len - 1]).into_string().expect("Locale not valid unicode") OsString::from_wide(&buf[..len - 1]).into_string().expect("Locale not valid unicode")
} }

View File

@ -70,7 +70,7 @@ impl CharSetRef {
pub fn merge(&self, other: &CharSetRef) -> Result<bool, ()> { pub fn merge(&self, other: &CharSetRef) -> Result<bool, ()> {
unsafe { unsafe {
// Value is just an indicator whether something was added or not // Value is just an indicator whether something was added or not.
let mut value: FcBool = 0; let mut value: FcBool = 0;
let res = FcCharSetMerge(self.as_ptr() as _, other.as_ptr() as _, &mut value); let res = FcCharSetMerge(self.as_ptr() as _, other.as_ptr() as _, &mut value);
if res == 0 { if res == 0 {

View File

@ -24,7 +24,7 @@ foreign_type! {
} }
impl Config { impl Config {
/// Get the current configuration /// Get the current configuration.
pub fn get_current() -> &'static ConfigRef { pub fn get_current() -> &'static ConfigRef {
unsafe { ConfigRef::from_ptr(FcConfigGetCurrent()) } unsafe { ConfigRef::from_ptr(FcConfigGetCurrent()) }
} }

View File

@ -39,7 +39,7 @@ impl FontSet {
FcFontSetList( FcFontSetList(
config.as_ptr(), config.as_ptr(),
&mut source.as_ptr(), &mut source.as_ptr(),
1, // nsets 1, // nsets.
pattern.as_ptr(), pattern.as_ptr(),
objects.as_ptr(), objects.as_ptr(),
) )
@ -48,7 +48,7 @@ impl FontSet {
} }
} }
/// Iterator over a font set /// Iterator over a font set.
pub struct Iter<'a> { pub struct Iter<'a> {
font_set: &'a FontSetRef, font_set: &'a FontSetRef,
num_fonts: usize, num_fonts: usize,

View File

@ -72,7 +72,7 @@ pub fn font_sort(config: &ConfigRef, pattern: &PatternRef) -> Option<FontSet> {
let ptr = FcFontSort( let ptr = FcFontSort(
config.as_ptr(), config.as_ptr(),
pattern.as_ptr(), pattern.as_ptr(),
1, // Trim font list 1, // Trim font list.
&mut charsets, &mut charsets,
&mut result, &mut result,
); );
@ -102,14 +102,14 @@ pub fn font_list(
} }
} }
/// Available font sets /// Available font sets.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum SetName { pub enum SetName {
System = FcSetSystem as isize, System = FcSetSystem as isize,
Application = FcSetApplication as isize, Application = FcSetApplication as isize,
} }
/// When matching, how to match /// When matching, how to match.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum MatchKind { pub enum MatchKind {
Font = FcMatchFont as isize, Font = FcMatchFont as isize,
@ -187,7 +187,7 @@ impl From<isize> for Width {
} }
} }
/// Subpixel geometry /// Subpixel geometry.
#[derive(Debug)] #[derive(Debug)]
pub enum Rgba { pub enum Rgba {
Unknown, Unknown,
@ -237,7 +237,7 @@ impl From<isize> for Rgba {
} }
} }
/// Hinting Style /// Hinting Style.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum HintStyle { pub enum HintStyle {
None, None,
@ -257,7 +257,7 @@ impl fmt::Display for HintStyle {
} }
} }
/// Lcd filter, used to reduce color fringing with subpixel rendering /// Lcd filter, used to reduce color fringing with subpixel rendering.
pub enum LcdFilter { pub enum LcdFilter {
None, None,
Default, Default,

View File

@ -54,7 +54,7 @@ impl<'a> StringPropertyIter<'a> {
}; };
if result == FcResultMatch { if result == FcResultMatch {
// Transmute here is to extend lifetime of the str to that of the iterator // Transmute here is to extend lifetime of the str to that of the iterator.
// //
// Potential unsafety? What happens if the pattern is modified while this ptr is // Potential unsafety? What happens if the pattern is modified while this ptr is
// borrowed out? // borrowed out?
@ -67,7 +67,7 @@ impl<'a> StringPropertyIter<'a> {
} }
} }
/// Iterator over integer properties /// Iterator over integer properties.
pub struct BooleanPropertyIter<'a> { pub struct BooleanPropertyIter<'a> {
pattern: &'a PatternRef, pattern: &'a PatternRef,
object: &'a [u8], object: &'a [u8],
@ -99,7 +99,7 @@ impl<'a> BooleanPropertyIter<'a> {
} }
} }
/// Iterator over integer properties /// Iterator over integer properties.
pub struct IntPropertyIter<'a> { pub struct IntPropertyIter<'a> {
pattern: &'a PatternRef, pattern: &'a PatternRef,
object: &'a [u8], object: &'a [u8],
@ -204,7 +204,7 @@ impl<'a> LcdFilterPropertyIter<'a> {
} }
} }
/// Iterator over integer properties /// Iterator over integer properties.
pub struct DoublePropertyIter<'a> { pub struct DoublePropertyIter<'a> {
pattern: &'a PatternRef, pattern: &'a PatternRef,
object: &'a [u8], object: &'a [u8],
@ -236,7 +236,7 @@ impl<'a> DoublePropertyIter<'a> {
} }
} }
/// Implement debug for a property iterator /// Implement debug for a property iterator.
macro_rules! impl_property_iter_debug { macro_rules! impl_property_iter_debug {
($iter:ty => $item:ty) => { ($iter:ty => $item:ty) => {
impl<'a> fmt::Debug for $iter { impl<'a> fmt::Debug for $iter {
@ -260,7 +260,7 @@ macro_rules! impl_property_iter_debug {
}; };
} }
/// Implement Iterator and Debug for a property iterator /// Implement Iterator and Debug for a property iterator.
macro_rules! impl_property_iter { macro_rules! impl_property_iter {
($($iter:ty => $item:ty),*) => { ($($iter:ty => $item:ty),*) => {
$( $(
@ -310,7 +310,7 @@ macro_rules! impl_derived_property_iter {
} }
} }
// Basic Iterators // Basic Iterators.
impl_property_iter! { impl_property_iter! {
StringPropertyIter<'a> => &'a str, StringPropertyIter<'a> => &'a str,
IntPropertyIter<'a> => isize, IntPropertyIter<'a> => isize,
@ -318,7 +318,7 @@ impl_property_iter! {
BooleanPropertyIter<'a> => bool BooleanPropertyIter<'a> => bool
} }
// Derived Iterators // Derived Iterators.
impl_derived_property_iter! { impl_derived_property_iter! {
RgbaPropertyIter<'a> => Rgba, RgbaPropertyIter<'a> => Rgba,
HintStylePropertyIter<'a> => HintStyle, HintStylePropertyIter<'a> => HintStyle,
@ -460,23 +460,23 @@ impl PatternRef {
index() => b"index\0" index() => b"index\0"
} }
// Prints the pattern to stdout /// Prints the pattern to stdout.
// ///
// FontConfig doesn't expose a way to iterate over all members of a pattern; /// FontConfig doesn't expose a way to iterate over all members of a pattern;
// instead, we just defer to FcPatternPrint. Otherwise, this could have been /// instead, we just defer to FcPatternPrint. Otherwise, this could have been
// a `fmt::Debug` impl. /// a `fmt::Debug` impl.
pub fn print(&self) { pub fn print(&self) {
unsafe { FcPatternPrint(self.as_ptr()) } unsafe { FcPatternPrint(self.as_ptr()) }
} }
/// Add a string value to the pattern /// Add a string value to the pattern.
/// ///
/// If the returned value is `true`, the value is added at the end of /// If the returned value is `true`, the value is added at the end of
/// any existing list, otherwise it is inserted at the beginning. /// any existing list, otherwise it is inserted at the beginning.
/// ///
/// # Unsafety /// # Unsafety
/// ///
/// `object` is not checked to be a valid null-terminated string /// `object` is not checked to be a valid null-terminated string.
unsafe fn add_string(&mut self, object: &[u8], value: &str) -> bool { unsafe fn add_string(&mut self, object: &[u8], value: &str) -> bool {
let value = CString::new(&value[..]).unwrap(); let value = CString::new(&value[..]).unwrap();
let value = value.as_ptr(); let value = value.as_ptr();
@ -556,9 +556,9 @@ impl PatternRef {
unsafe { PatternHash(FcPatternHash(self.as_ptr())) } unsafe { PatternHash(FcPatternHash(self.as_ptr())) }
} }
/// Add charset to the pattern /// Add charset to the pattern.
/// ///
/// The referenced charset is copied by fontconfig internally using /// The referenced charset is copied by Fontconfig internally using
/// FcValueSave so that no references to application provided memory are /// FcValueSave so that no references to application provided memory are
/// retained. That is, the CharSet can be safely dropped immediately /// retained. That is, the CharSet can be safely dropped immediately
/// after being added to the pattern. /// after being added to the pattern.

View File

@ -46,7 +46,7 @@ impl FallbackFont {
impl FontKey { impl FontKey {
fn from_pattern_hashes(lhs: PatternHash, rhs: PatternHash) -> Self { fn from_pattern_hashes(lhs: PatternHash, rhs: PatternHash) -> Self {
// XOR two hashes to get a font ID // XOR two hashes to get a font ID.
Self { token: lhs.0.rotate_left(1) ^ rhs.0 } Self { token: lhs.0.rotate_left(1) ^ rhs.0 }
} }
} }
@ -121,18 +121,18 @@ impl Rasterize for FreeTypeRasterizer {
let height = (full.size_metrics.height / 64) as f64; let height = (full.size_metrics.height / 64) as f64;
let descent = (full.size_metrics.descender / 64) as f32; let descent = (full.size_metrics.descender / 64) as f32;
// Get underline position and thickness in device pixels // Get underline position and thickness in device pixels.
let x_scale = full.size_metrics.x_scale as f32 / 65536.0; let x_scale = full.size_metrics.x_scale as f32 / 65536.0;
let mut underline_position = f32::from(face.ft_face.underline_position()) * x_scale / 64.; let mut underline_position = f32::from(face.ft_face.underline_position()) * x_scale / 64.;
let mut underline_thickness = f32::from(face.ft_face.underline_thickness()) * x_scale / 64.; let mut underline_thickness = f32::from(face.ft_face.underline_thickness()) * x_scale / 64.;
// Fallback for bitmap fonts which do not provide underline metrics // Fallback for bitmap fonts which do not provide underline metrics.
if underline_position == 0. { if underline_position == 0. {
underline_thickness = (descent.abs() / 5.).round(); underline_thickness = (descent.abs() / 5.).round();
underline_position = descent / 2.; underline_position = descent / 2.;
} }
// Get strikeout position and thickness in device pixels // Get strikeout position and thickness in device pixels.
let (strikeout_position, strikeout_thickness) = let (strikeout_position, strikeout_thickness) =
match TrueTypeOS2Table::from_face(&mut (*face.ft_face).clone()) { match TrueTypeOS2Table::from_face(&mut (*face.ft_face).clone()) {
Some(os2) => { Some(os2) => {
@ -141,7 +141,7 @@ impl Rasterize for FreeTypeRasterizer {
(strikeout_position, strikeout_thickness) (strikeout_position, strikeout_thickness)
}, },
_ => { _ => {
// Fallback if font doesn't provide info about strikeout // Fallback if font doesn't provide info about strikeout.
trace!("Using fallback strikeout metrics"); trace!("Using fallback strikeout metrics");
let strikeout_position = height as f32 / 2. + descent; let strikeout_position = height as f32 / 2. + descent;
(strikeout_position, underline_thickness) (strikeout_position, underline_thickness)
@ -206,9 +206,9 @@ struct FullMetrics {
} }
impl FreeTypeRasterizer { impl FreeTypeRasterizer {
/// Load a font face according to `FontDesc` /// Load a font face according to `FontDesc`.
fn get_face(&mut self, desc: &FontDesc, size: Size) -> Result<FontKey, Error> { fn get_face(&mut self, desc: &FontDesc, size: Size) -> Result<FontKey, Error> {
// Adjust for DPI // Adjust for DPR.
let size = f64::from(size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.); let size = f64::from(size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.);
let config = fc::Config::get_current(); let config = fc::Config::get_current();
@ -216,26 +216,26 @@ impl FreeTypeRasterizer {
pattern.add_family(&desc.name); pattern.add_family(&desc.name);
pattern.add_pixelsize(size); pattern.add_pixelsize(size);
// Add style to a pattern // Add style to a pattern.
match desc.style { match desc.style {
Style::Description { slant, weight } => { Style::Description { slant, weight } => {
// Match nearest font // Match nearest font.
pattern.set_weight(weight.into_fontconfig_type()); pattern.set_weight(weight.into_fontconfig_type());
pattern.set_slant(slant.into_fontconfig_type()); pattern.set_slant(slant.into_fontconfig_type());
}, },
Style::Specific(ref style) => { Style::Specific(ref style) => {
// If a name was specified, try and load specifically that font // If a name was specified, try and load specifically that font.
pattern.add_style(style); pattern.add_style(style);
}, },
} }
// Hash requested pattern // Hash requested pattern.
let hash = pattern.hash(); let hash = pattern.hash();
pattern.config_substitute(config, fc::MatchKind::Pattern); pattern.config_substitute(config, fc::MatchKind::Pattern);
pattern.default_substitute(); pattern.default_substitute();
// Get font list using pattern. First font is the primary one while the rest are fallbacks // Get font list using pattern. First font is the primary one while the rest are fallbacks.
let matched_fonts = let matched_fonts =
fc::font_sort(&config, &pattern).ok_or_else(|| Error::MissingFont(desc.to_owned()))?; fc::font_sort(&config, &pattern).ok_or_else(|| Error::MissingFont(desc.to_owned()))?;
let mut matched_fonts = matched_fonts.into_iter(); let mut matched_fonts = matched_fonts.into_iter();
@ -243,24 +243,24 @@ impl FreeTypeRasterizer {
let primary_font = let primary_font =
matched_fonts.next().ok_or_else(|| Error::MissingFont(desc.to_owned()))?; matched_fonts.next().ok_or_else(|| Error::MissingFont(desc.to_owned()))?;
// We should render patterns to get values like `pixelsizefixupfactor` // We should render patterns to get values like `pixelsizefixupfactor`.
let primary_font = pattern.render_prepare(config, primary_font); let primary_font = pattern.render_prepare(config, primary_font);
// Hash pattern together with request pattern to include requested font size in the hash // Hash pattern together with request pattern to include requested font size in the hash.
let primary_font_key = FontKey::from_pattern_hashes(hash, primary_font.hash()); let primary_font_key = FontKey::from_pattern_hashes(hash, primary_font.hash());
// Return if we already have the same primary font // Return if we already have the same primary font.
if self.fallback_lists.contains_key(&primary_font_key) { if self.fallback_lists.contains_key(&primary_font_key) {
return Ok(primary_font_key); return Ok(primary_font_key);
} }
// Load font if we haven't loaded it yet // Load font if we haven't loaded it yet.
if !self.faces.contains_key(&primary_font_key) { if !self.faces.contains_key(&primary_font_key) {
self.face_from_pattern(&primary_font, primary_font_key) self.face_from_pattern(&primary_font, primary_font_key)
.and_then(|pattern| pattern.ok_or_else(|| Error::MissingFont(desc.to_owned())))?; .and_then(|pattern| pattern.ok_or_else(|| Error::MissingFont(desc.to_owned())))?;
} }
// Coverage for fallback fonts // Coverage for fallback fonts.
let coverage = CharSet::new(); let coverage = CharSet::new();
let empty_charset = CharSet::new(); let empty_charset = CharSet::new();
@ -268,7 +268,7 @@ impl FreeTypeRasterizer {
.map(|fallback_font| { .map(|fallback_font| {
let charset = fallback_font.get_charset().unwrap_or(&empty_charset); let charset = fallback_font.get_charset().unwrap_or(&empty_charset);
// Use original pattern to preserve loading flags // Use original pattern to preserve loading flags.
let fallback_font = pattern.render_prepare(config, fallback_font); let fallback_font = pattern.render_prepare(config, fallback_font);
let fallback_font_key = FontKey::from_pattern_hashes(hash, fallback_font.hash()); let fallback_font_key = FontKey::from_pattern_hashes(hash, fallback_font.hash());
@ -299,7 +299,7 @@ impl FreeTypeRasterizer {
let mut ft_face = self.library.new_face(&ft_face_location.path, ft_face_location.index)?; let mut ft_face = self.library.new_face(&ft_face_location.path, ft_face_location.index)?;
if ft_face.has_color() { if ft_face.has_color() {
unsafe { unsafe {
// Select the colored bitmap size to use from the array of available sizes // Select the colored bitmap size to use from the array of available sizes.
freetype_sys::FT_Select_Size(ft_face.raw_mut(), 0); freetype_sys::FT_Select_Size(ft_face.raw_mut(), 0);
} }
} }
@ -370,7 +370,7 @@ impl FreeTypeRasterizer {
fn load_face_with_glyph(&mut self, glyph: GlyphKey) -> Result<FontKey, Error> { fn load_face_with_glyph(&mut self, glyph: GlyphKey) -> Result<FontKey, Error> {
let fallback_list = self.fallback_lists.get(&glyph.font_key).unwrap(); let fallback_list = self.fallback_lists.get(&glyph.font_key).unwrap();
// Check whether glyph is presented in any fallback font // Check whether glyph is presented in any fallback font.
if !fallback_list.coverage.has_char(glyph.c) { if !fallback_list.coverage.has_char(glyph.c) {
return Ok(glyph.font_key); return Ok(glyph.font_key);
} }
@ -382,7 +382,7 @@ impl FreeTypeRasterizer {
Some(face) => { Some(face) => {
let index = face.ft_face.get_char_index(glyph.c as usize); let index = face.ft_face.get_char_index(glyph.c as usize);
// We found something in a current face, so let's use it // We found something in a current face, so let's use it.
if index != 0 { if index != 0 {
return Ok(font_key); return Ok(font_key);
} }
@ -400,12 +400,12 @@ impl FreeTypeRasterizer {
} }
} }
// You can hit this return, if you're failing to get charset from a pattern // You can hit this return, if you're failing to get charset from a pattern.
Ok(glyph.font_key) Ok(glyph.font_key)
} }
fn get_rendered_glyph(&mut self, glyph_key: GlyphKey) -> Result<RasterizedGlyph, Error> { fn get_rendered_glyph(&mut self, glyph_key: GlyphKey) -> Result<RasterizedGlyph, Error> {
// Render a normal character if it's not a cursor // Render a normal character if it's not a cursor.
let font_key = self.face_for_glyph(glyph_key)?; let font_key = self.face_for_glyph(glyph_key)?;
let face = &self.faces[&font_key]; let face = &self.faces[&font_key];
let index = face.ft_face.get_char_index(glyph_key.c as usize); let index = face.ft_face.get_char_index(glyph_key.c as usize);
@ -442,7 +442,7 @@ impl FreeTypeRasterizer {
let fixup_factor = if let Some(pixelsize_fixup_factor) = face.pixelsize_fixup_factor { let fixup_factor = if let Some(pixelsize_fixup_factor) = face.pixelsize_fixup_factor {
pixelsize_fixup_factor pixelsize_fixup_factor
} else { } else {
// Fallback if user has bitmap scaling disabled // Fallback if user has bitmap scaling disabled.
let metrics = face.ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?; let metrics = face.ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?;
f64::from(pixelsize) / f64::from(metrics.y_ppem) f64::from(pixelsize) / f64::from(metrics.y_ppem)
}; };
@ -465,7 +465,7 @@ impl FreeTypeRasterizer {
(false, fc::HintStyle::None, _) => LoadFlag::NO_HINTING | LoadFlag::MONOCHROME, (false, fc::HintStyle::None, _) => LoadFlag::NO_HINTING | LoadFlag::MONOCHROME,
(false, ..) => LoadFlag::TARGET_MONO | LoadFlag::MONOCHROME, (false, ..) => LoadFlag::TARGET_MONO | LoadFlag::MONOCHROME,
(true, fc::HintStyle::None, _) => LoadFlag::NO_HINTING | LoadFlag::TARGET_NORMAL, (true, fc::HintStyle::None, _) => LoadFlag::NO_HINTING | LoadFlag::TARGET_NORMAL,
// hintslight does *not* use LCD hinting even when a subpixel mode // `hintslight` does *not* use LCD hinting even when a subpixel mode
// is selected. // is selected.
// //
// According to the FreeType docs, // According to the FreeType docs,
@ -478,7 +478,7 @@ impl FreeTypeRasterizer {
// In practice, this means we can have `FT_LOAD_TARGET_LIGHT` with // In practice, this means we can have `FT_LOAD_TARGET_LIGHT` with
// subpixel render modes like `FT_RENDER_MODE_LCD`. Libraries like // subpixel render modes like `FT_RENDER_MODE_LCD`. Libraries like
// cairo take the same approach and consider `hintslight` to always // cairo take the same approach and consider `hintslight` to always
// prefer `FT_LOAD_TARGET_LIGHT` // prefer `FT_LOAD_TARGET_LIGHT`.
(true, fc::HintStyle::Slight, _) => LoadFlag::TARGET_LIGHT, (true, fc::HintStyle::Slight, _) => LoadFlag::TARGET_LIGHT,
// If LCD hinting is to be used, must select hintmedium or hintfull, // If LCD hinting is to be used, must select hintmedium or hintfull,
// have AA enabled, and select a subpixel mode. // have AA enabled, and select a subpixel mode.
@ -565,7 +565,7 @@ impl FreeTypeRasterizer {
while count != 0 { while count != 0 {
let value = ((byte >> bit) & 1) * 255; let value = ((byte >> bit) & 1) * 255;
// Push value 3x since result buffer should be 1 byte // Push value 3x since result buffer should be 1 byte
// per channel // per channel.
res.push(value); res.push(value);
res.push(value); res.push(value);
res.push(value); res.push(value);
@ -623,7 +623,7 @@ impl FreeTypeRasterizer {
/// This will take the `bitmap_glyph` as input and return the glyph's content downscaled by /// This will take the `bitmap_glyph` as input and return the glyph's content downscaled by
/// `fixup_factor`. /// `fixup_factor`.
fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> RasterizedGlyph { fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> RasterizedGlyph {
// Only scale colored buffers which are bigger than required // Only scale colored buffers which are bigger than required.
let bitmap_buffer = match (&bitmap_glyph.buf, fixup_factor.partial_cmp(&1.0)) { let bitmap_buffer = match (&bitmap_glyph.buf, fixup_factor.partial_cmp(&1.0)) {
(BitmapBuffer::RGBA(buffer), Some(Ordering::Less)) => buffer, (BitmapBuffer::RGBA(buffer), Some(Ordering::Less)) => buffer,
_ => return bitmap_glyph, _ => return bitmap_glyph,
@ -635,19 +635,20 @@ fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> Ra
let target_width = (bitmap_width as f64 * fixup_factor) as usize; let target_width = (bitmap_width as f64 * fixup_factor) as usize;
let target_height = (bitmap_height as f64 * fixup_factor) as usize; let target_height = (bitmap_height as f64 * fixup_factor) as usize;
// Number of pixels in the input buffer, per pixel in the output buffer // Number of pixels in the input buffer, per pixel in the output buffer.
let downsampling_step = 1.0 / fixup_factor; let downsampling_step = 1.0 / fixup_factor;
let mut downsampled_buffer = Vec::<u8>::with_capacity(target_width * target_height * 4); let mut downsampled_buffer = Vec::<u8>::with_capacity(target_width * target_height * 4);
for line_index in 0..target_height { for line_index in 0..target_height {
// Get the first and last line which will be consolidated in the current output pixel // Get the first and last line which will be consolidated in the current output pixel.
let line_index = line_index as f64; let line_index = line_index as f64;
let source_line_start = (line_index * downsampling_step).round() as usize; let source_line_start = (line_index * downsampling_step).round() as usize;
let source_line_end = ((line_index + 1.) * downsampling_step).round() as usize; let source_line_end = ((line_index + 1.) * downsampling_step).round() as usize;
for column_index in 0..target_width { for column_index in 0..target_width {
// Get the first and last column which will be consolidated in the current output pixel // Get the first and last column which will be consolidated in the current output
// pixel.
let column_index = column_index as f64; let column_index = column_index as f64;
let source_column_start = (column_index * downsampling_step).round() as usize; let source_column_start = (column_index * downsampling_step).round() as usize;
let source_column_end = ((column_index + 1.) * downsampling_step).round() as usize; let source_column_end = ((column_index + 1.) * downsampling_step).round() as usize;
@ -655,7 +656,7 @@ fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> Ra
let (mut r, mut g, mut b, mut a) = (0u32, 0u32, 0u32, 0u32); let (mut r, mut g, mut b, mut a) = (0u32, 0u32, 0u32, 0u32);
let mut pixels_picked: u32 = 0; let mut pixels_picked: u32 = 0;
// Consolidate all pixels within the source rectangle into a single averaged pixel // Consolidate all pixels within the source rectangle into a single averaged pixel.
for source_line in source_line_start..source_line_end { for source_line in source_line_start..source_line_end {
let source_pixel_index = source_line * bitmap_width; let source_pixel_index = source_line * bitmap_width;
@ -669,7 +670,7 @@ fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> Ra
} }
} }
// Add a single pixel to the output buffer for the downscaled source rectangle // Add a single pixel to the output buffer for the downscaled source rectangle.
downsampled_buffer.push((r / pixels_picked) as u8); downsampled_buffer.push((r / pixels_picked) as u8);
downsampled_buffer.push((g / pixels_picked) as u8); downsampled_buffer.push((g / pixels_picked) as u8);
downsampled_buffer.push((b / pixels_picked) as u8); downsampled_buffer.push((b / pixels_picked) as u8);
@ -679,7 +680,7 @@ fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> Ra
bitmap_glyph.buf = BitmapBuffer::RGBA(downsampled_buffer); bitmap_glyph.buf = BitmapBuffer::RGBA(downsampled_buffer);
// Downscale the metrics // Downscale the metrics.
bitmap_glyph.top = (f64::from(bitmap_glyph.top) * fixup_factor) as i32; bitmap_glyph.top = (f64::from(bitmap_glyph.top) * fixup_factor) as i32;
bitmap_glyph.left = (f64::from(bitmap_glyph.left) * fixup_factor) as i32; bitmap_glyph.left = (f64::from(bitmap_glyph.left) * fixup_factor) as i32;
bitmap_glyph.width = target_width as i32; bitmap_glyph.width = target_width as i32;
@ -688,19 +689,19 @@ fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> Ra
bitmap_glyph bitmap_glyph
} }
/// Errors occurring when using the freetype rasterizer /// Errors occurring when using the freetype rasterizer.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Error occurred within the FreeType library /// Error occurred within the FreeType library.
FreeType(freetype::Error), FreeType(freetype::Error),
/// Couldn't find font matching description /// Couldn't find font matching description.
MissingFont(FontDesc), MissingFont(FontDesc),
/// Tried to get size metrics from a Face that didn't have a size /// Tried to get size metrics from a Face that didn't have a size.
MissingSizeMetrics, MissingSizeMetrics,
/// Requested an operation with a FontKey that isn't known to the rasterizer /// Requested an operation with a FontKey that isn't known to the rasterizer.
FontNotLoaded, FontNotLoaded,
} }

View File

@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
//! Compatibility layer for different font engines //! Compatibility layer for different font engines.
//! //!
//! CoreText is used on Mac OS. //! CoreText is used on Mac OS.
//! FreeType is used on everything that's not Mac OS. //! FreeType is used on everything that's not Mac OS.
//! Eventually, ClearType support will be available for windows //! Eventually, ClearType support will be available for windows.
#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)]
@ -24,7 +24,7 @@ use std::fmt;
use std::ops::{Add, Mul}; use std::ops::{Add, Mul};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
// If target isn't macos or windows, reexport everything from ft // If target isn't macos or windows, reexport everything from ft.
#[cfg(not(any(target_os = "macos", windows)))] #[cfg(not(any(target_os = "macos", windows)))]
pub mod ft; pub mod ft;
#[cfg(not(any(target_os = "macos", windows)))] #[cfg(not(any(target_os = "macos", windows)))]
@ -35,7 +35,7 @@ pub mod directwrite;
#[cfg(windows)] #[cfg(windows)]
pub use directwrite::{DirectWriteRasterizer as Rasterizer, Error}; pub use directwrite::{DirectWriteRasterizer as Rasterizer, Error};
// If target is macos, reexport everything from darwin // If target is macos, reexport everything from darwin.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod darwin; mod darwin;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -60,7 +60,7 @@ pub enum Weight {
Bold, Bold,
} }
/// Style of font /// Style of font.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Style { pub enum Style {
Specific(String), Specific(String),
@ -93,16 +93,16 @@ impl fmt::Display for FontDesc {
} }
} }
/// Identifier for a Font for use in maps/etc /// Identifier for a Font for use in maps/etc.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct FontKey { pub struct FontKey {
token: u32, token: u32,
} }
impl FontKey { impl FontKey {
/// Get next font key for given size /// Get next font key for given size.
/// ///
/// The generated key will be globally unique /// The generated key will be globally unique.
pub fn next() -> FontKey { pub fn next() -> FontKey {
static TOKEN: AtomicUsize = AtomicUsize::new(0); static TOKEN: AtomicUsize = AtomicUsize::new(0);
@ -117,23 +117,23 @@ pub struct GlyphKey {
pub size: Size, pub size: Size,
} }
/// Font size stored as integer /// Font size stored as integer.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Size(i16); pub struct Size(i16);
impl Size { impl Size {
/// Create a new `Size` from a f32 size in points /// Create a new `Size` from a f32 size in points.
pub fn new(size: f32) -> Size { pub fn new(size: f32) -> Size {
Size((size * Size::factor()) as i16) Size((size * Size::factor()) as i16)
} }
/// Scale factor between font "Size" type and point size /// Scale factor between font "Size" type and point size.
#[inline] #[inline]
pub fn factor() -> f32 { pub fn factor() -> f32 {
2.0 2.0
} }
/// Get the f32 size in points /// Get the f32 size in points.
pub fn as_f32_pts(self) -> f32 { pub fn as_f32_pts(self) -> f32 {
f32::from(self.0) / Size::factor() f32::from(self.0) / Size::factor()
} }
@ -215,23 +215,23 @@ pub struct Metrics {
} }
pub trait Rasterize { pub trait Rasterize {
/// Errors occurring in Rasterize methods /// Errors occurring in Rasterize methods.
type Err: ::std::error::Error + Send + Sync + 'static; type Err: ::std::error::Error + Send + Sync + 'static;
/// Create a new Rasterizer /// Create a new Rasterizer.
fn new(device_pixel_ratio: f32, use_thin_strokes: bool) -> Result<Self, Self::Err> fn new(device_pixel_ratio: f32, use_thin_strokes: bool) -> Result<Self, Self::Err>
where where
Self: Sized; Self: Sized;
/// Get `Metrics` for the given `FontKey` /// Get `Metrics` for the given `FontKey`.
fn metrics(&self, _: FontKey, _: Size) -> Result<Metrics, Self::Err>; fn metrics(&self, _: FontKey, _: Size) -> Result<Metrics, Self::Err>;
/// Load the font described by `FontDesc` and `Size` /// Load the font described by `FontDesc` and `Size`.
fn load_font(&mut self, _: &FontDesc, _: Size) -> Result<FontKey, Self::Err>; fn load_font(&mut self, _: &FontDesc, _: Size) -> Result<FontKey, Self::Err>;
/// Rasterize the glyph described by `GlyphKey`. /// Rasterize the glyph described by `GlyphKey`..
fn get_glyph(&mut self, _: GlyphKey) -> Result<RasterizedGlyph, Self::Err>; fn get_glyph(&mut self, _: GlyphKey) -> Result<RasterizedGlyph, Self::Err>;
/// Update the Rasterizer's DPI factor /// Update the Rasterizer's DPI factor.
fn update_dpr(&mut self, device_pixel_ratio: f32); fn update_dpr(&mut self, device_pixel_ratio: f32);
} }