Extend style guideline documentation
This commit is contained in:
parent
04f0bcaf54
commit
81ce93574f
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(®ular_desc, size)?;
|
let regular = rasterizer.load_font(®ular_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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ¶ms[1..] {
|
for param in ¶ms[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 ¶ms[1..] {
|
for param in ¶ms[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] = &[
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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(®ion, positions, template);
|
self.scroll_up(®ion, 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;
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -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')];
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue