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
|
||||
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
|
||||
|
||||
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")]
|
||||
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 live_config_reload: Option<bool>,
|
||||
pub print_events: bool,
|
||||
|
@ -241,8 +241,8 @@ impl Options {
|
|||
|
||||
if let Some(mut args) = matches.values_of("command") {
|
||||
// The following unwrap is guaranteed to succeed.
|
||||
// If 'command' exists it must also have a first item since
|
||||
// Arg::min_values(1) is set.
|
||||
// If `command` exists it must also have a first item since
|
||||
// `Arg::min_values(1)` is set.
|
||||
let command = String::from(args.next().unwrap());
|
||||
let args = args.map(String::from).collect();
|
||||
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::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)]
|
||||
pub struct Binding<T> {
|
||||
/// Modifier keys required to activate binding
|
||||
/// Modifier keys required to activate binding.
|
||||
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,
|
||||
|
||||
/// Terminal mode required to activate binding
|
||||
/// Terminal mode required to activate binding.
|
||||
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,
|
||||
|
||||
/// This property is used as part of the trigger detection code.
|
||||
|
@ -48,10 +48,10 @@ pub struct Binding<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>;
|
||||
|
||||
/// Bindings that are triggered by a mouse button
|
||||
/// Bindings that are triggered by a mouse button.
|
||||
pub type MouseBinding = Binding<MouseButton>;
|
||||
|
||||
impl<T: Eq> Binding<T> {
|
||||
|
@ -68,19 +68,19 @@ impl<T: Eq> Binding<T> {
|
|||
|
||||
#[inline]
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Completely empty modes match all modes
|
||||
// Completely empty modes match all modes.
|
||||
if (self.mode.is_empty() && self.notmode.is_empty())
|
||||
|| (binding.mode.is_empty() && binding.notmode.is_empty())
|
||||
{
|
||||
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.notmode == binding.notmode || self.notmode.intersects(binding.notmode))
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ pub enum Action {
|
|||
/// Toggle fullscreen.
|
||||
ToggleFullscreen,
|
||||
|
||||
/// Toggle simple fullscreen on macos.
|
||||
/// Toggle simple fullscreen on macOS.
|
||||
#[cfg(target_os = "macos")]
|
||||
ToggleSimpleFullscreen,
|
||||
|
||||
|
@ -452,8 +452,7 @@ pub fn default_key_bindings() -> Vec<KeyBinding> {
|
|||
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
|
||||
// modifiers_code != Shift
|
||||
// We're adding the following bindings with `Shift` manually above, so skipping them here.
|
||||
if modifiers_code != 2 {
|
||||
bindings.extend(bindings!(
|
||||
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)]
|
||||
pub fn platform_key_bindings() -> Vec<KeyBinding> {
|
||||
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
|
||||
/// impl below.
|
||||
|
|
|
@ -22,22 +22,22 @@ use crate::config::ui_config::UIConfig;
|
|||
|
||||
pub type Config = TermConfig<UIConfig>;
|
||||
|
||||
/// Result from config loading
|
||||
/// Result from config loading.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors occurring during config loading
|
||||
/// Errors occurring during config loading.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Config file not found
|
||||
/// Config file not found.
|
||||
NotFound,
|
||||
|
||||
/// Couldn't read $HOME environment variable
|
||||
/// Couldn't read $HOME environment variable.
|
||||
ReadingEnvHome(env::VarError),
|
||||
|
||||
/// io error reading file
|
||||
/// io error reading file.
|
||||
Io(io::Error),
|
||||
|
||||
/// Not valid yaml or missing parameters
|
||||
/// Not valid yaml or missing parameters.
|
||||
Yaml(serde_yaml::Error),
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ impl From<serde_yaml::Error> for Error {
|
|||
/// 4. $HOME/.alacritty.yml
|
||||
#[cfg(not(windows))]
|
||||
pub fn installed_config() -> Option<PathBuf> {
|
||||
// Try using XDG location by default
|
||||
// Try using XDG location by default.
|
||||
xdg::BaseDirectories::with_prefix("alacritty")
|
||||
.ok()
|
||||
.and_then(|xdg| xdg.find_config_file("alacritty.yml"))
|
||||
|
@ -107,12 +107,12 @@ pub fn installed_config() -> Option<PathBuf> {
|
|||
})
|
||||
.or_else(|| {
|
||||
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");
|
||||
if fallback.exists() {
|
||||
return Some(fallback);
|
||||
}
|
||||
// Fallback path: $HOME/.alacritty.yml
|
||||
// Fallback path: $HOME/.alacritty.yml.
|
||||
let fallback = PathBuf::from(&home).join(".alacritty.yml");
|
||||
if fallback.exists() {
|
||||
return Some(fallback);
|
||||
|
@ -146,7 +146,7 @@ pub fn reload_from(path: &PathBuf) -> Result<Config> {
|
|||
fn read_config(path: &PathBuf) -> Result<Config> {
|
||||
let mut contents = fs::read_to_string(path)?;
|
||||
|
||||
// Remove UTF-8 BOM
|
||||
// Remove UTF-8 BOM.
|
||||
if contents.starts_with('\u{FEFF}') {
|
||||
contents = contents.split_off(3);
|
||||
}
|
||||
|
|
|
@ -28,11 +28,11 @@ impl Monitor {
|
|||
watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher");
|
||||
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();
|
||||
parent.pop();
|
||||
|
||||
// Watch directory
|
||||
// Watch directory.
|
||||
watcher
|
||||
.watch(&parent, RecursiveMode::NonRecursive)
|
||||
.expect("watch alacritty.yml dir");
|
||||
|
|
|
@ -24,11 +24,11 @@ pub struct Mouse {
|
|||
#[serde(default)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct Url {
|
||||
// Program for opening links
|
||||
/// Program for opening links.
|
||||
#[serde(deserialize_with = "deserialize_launcher")]
|
||||
pub launcher: Option<CommandWrapper>,
|
||||
|
||||
// Modifier used to open links
|
||||
/// Modifier used to open links.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
modifiers: ModsWrapper,
|
||||
}
|
||||
|
@ -47,10 +47,10 @@ where
|
|||
{
|
||||
let default = Url::default().launcher;
|
||||
|
||||
// Deserialize to generic value
|
||||
// Deserialize to generic value.
|
||||
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() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
|
|
@ -11,11 +11,11 @@ pub struct UIConfig {
|
|||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub mouse: Mouse,
|
||||
|
||||
/// Keybindings
|
||||
/// Keybindings.
|
||||
#[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")]
|
||||
pub key_bindings: Vec<KeyBinding>,
|
||||
|
||||
/// Bindings for the mouse
|
||||
/// Bindings for the mouse.
|
||||
#[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")]
|
||||
pub mouse_bindings: Vec<MouseBinding>,
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ where
|
|||
{
|
||||
let values = Vec::<serde_yaml::Value>::deserialize(deserializer)?;
|
||||
|
||||
// Skip all invalid values
|
||||
// Skip all invalid values.
|
||||
let mut bindings = Vec::with_capacity(values.len());
|
||||
for value in values {
|
||||
match Binding::<T>::deserialize(value) {
|
||||
|
@ -74,7 +74,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// Remove matching default bindings
|
||||
// Remove matching default bindings.
|
||||
for binding in bindings.iter() {
|
||||
default.retain(|b| !b.triggers_match(binding));
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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;
|
||||
|
||||
|
@ -28,13 +28,13 @@ pub fn get_cursor_glyph(
|
|||
is_wide: bool,
|
||||
cursor_thickness: f64,
|
||||
) -> RasterizedGlyph {
|
||||
// Calculate the cell metrics
|
||||
// Calculate the cell metrics.
|
||||
let height = metrics.line_height as i32 + i32::from(offset_y);
|
||||
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);
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
// 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];
|
||||
|
||||
// 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: 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 {
|
||||
// Create a new rectangle that is at least one pixel wide
|
||||
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 {
|
||||
// Create a new box outline rectangle
|
||||
// Create a new box outline rectangle.
|
||||
let mut buf = Vec::with_capacity((width * height * 3) as usize);
|
||||
for y in 0..height {
|
||||
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) }
|
||||
}
|
||||
|
||||
// Returns a custom block cursor character
|
||||
/// Return a custom block cursor character.
|
||||
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];
|
||||
|
||||
// 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) }
|
||||
}
|
||||
|
|
|
@ -219,7 +219,7 @@ impl Display {
|
|||
// Update OpenGL projection.
|
||||
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;
|
||||
renderer.with_api(&config, &size_info, |api| {
|
||||
api.clear(background_color);
|
||||
|
@ -244,7 +244,7 @@ impl Display {
|
|||
|
||||
// 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.
|
||||
if let Some(position) = config.window.position {
|
||||
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::cmp::max;
|
||||
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) {
|
||||
self.terminal.scroll_display(scroll);
|
||||
|
||||
// Update selection
|
||||
// Update selection.
|
||||
if self.terminal.mode().contains(TermMode::VI)
|
||||
&& 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) {
|
||||
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);
|
||||
if let Some(selection) = self.terminal.selection_mut() {
|
||||
selection.update(point, side);
|
||||
|
@ -306,7 +306,7 @@ pub enum ClickState {
|
|||
TripleClick,
|
||||
}
|
||||
|
||||
/// State of the mouse
|
||||
/// State of the mouse.
|
||||
#[derive(Debug)]
|
||||
pub struct Mouse {
|
||||
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
|
||||
/// triggered.
|
||||
|
@ -364,10 +364,9 @@ pub struct 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
|
||||
/// pty.
|
||||
/// Takes a writer which is expected to be hooked up to the write end of a PTY.
|
||||
pub fn new(
|
||||
notifier: N,
|
||||
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());
|
||||
}
|
||||
|
||||
/// Handle events from glutin
|
||||
/// Handle events from glutin.
|
||||
///
|
||||
/// Doesn't take self mutably due to borrow checking.
|
||||
fn handle_event<T>(
|
||||
|
@ -546,11 +545,11 @@ impl<N: Notify + OnResize> Processor<N> {
|
|||
Event::DPRChanged(scale_factor, (width, height)) => {
|
||||
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 =
|
||||
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));
|
||||
|
||||
processor.ctx.size_info.dpr = scale_factor;
|
||||
|
@ -592,7 +591,7 @@ impl<N: Notify + OnResize> Processor<N> {
|
|||
WindowEvent::KeyboardInput { input, is_synthetic: false, .. } => {
|
||||
processor.key_input(input);
|
||||
if input.state == ElementState::Pressed {
|
||||
// Hide cursor while typing
|
||||
// Hide cursor while typing.
|
||||
if processor.ctx.config.ui_config.mouse.hide_when_typing {
|
||||
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 {
|
||||
match event {
|
||||
GlutinEvent::WindowEvent { event, .. } => match event {
|
||||
|
@ -709,7 +708,7 @@ impl<N: Notify + OnResize> Processor<N> {
|
|||
|
||||
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()
|
||||
> std::f64::EPSILON
|
||||
{
|
||||
|
@ -717,7 +716,7 @@ impl<N: Notify + OnResize> Processor<N> {
|
|||
}
|
||||
|
||||
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 {
|
||||
*processor.ctx.font_size = config.font.size;
|
||||
}
|
||||
|
@ -738,13 +737,13 @@ impl<N: Notify + OnResize> Processor<N> {
|
|||
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>) {
|
||||
if !self.config.debug.ref_test {
|
||||
return;
|
||||
}
|
||||
|
||||
// dump grid state
|
||||
// Dump grid state.
|
||||
let mut grid = terminal.grid().clone();
|
||||
grid.initialize_all(&Cell::default());
|
||||
grid.truncate();
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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
|
||||
//! 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.
|
||||
|
@ -50,7 +50,7 @@ use crate::event::{ClickState, Mouse};
|
|||
use crate::url::{Url, Urls};
|
||||
use crate::window::Window;
|
||||
|
||||
/// Font size change interval
|
||||
/// Font size change interval.
|
||||
pub const FONT_SIZE_STEP: f32 = 0.5;
|
||||
|
||||
/// Processes input from glutin.
|
||||
|
@ -100,7 +100,7 @@ trait Execute<T: EventListener> {
|
|||
}
|
||||
|
||||
impl<T, U: EventListener> Execute<U> for Binding<T> {
|
||||
/// Execute the action associate with this binding
|
||||
/// Execute the action associate with this binding.
|
||||
#[inline]
|
||||
fn execute<A: ActionContext<U>>(&self, ctx: &mut A) {
|
||||
self.action.execute(ctx)
|
||||
|
@ -116,7 +116,7 @@ impl Action {
|
|||
let cursor_point = ctx.terminal().vi_mode_cursor.point;
|
||||
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() {
|
||||
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::ResetFontSize => ctx.reset_font_size(),
|
||||
Action::ScrollPageUp => {
|
||||
// Move vi mode cursor
|
||||
// Move vi mode cursor.
|
||||
let term = ctx.terminal_mut();
|
||||
let scroll_lines = term.grid().num_lines().0 as isize;
|
||||
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);
|
||||
},
|
||||
Action::ScrollPageDown => {
|
||||
// Move vi mode cursor
|
||||
// Move vi mode cursor.
|
||||
let term = ctx.terminal_mut();
|
||||
let scroll_lines = -(term.grid().num_lines().0 as isize);
|
||||
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);
|
||||
},
|
||||
Action::ScrollHalfPageUp => {
|
||||
// Move vi mode cursor
|
||||
// Move vi mode cursor.
|
||||
let term = ctx.terminal_mut();
|
||||
let scroll_lines = term.grid().num_lines().0 as isize / 2;
|
||||
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));
|
||||
},
|
||||
Action::ScrollHalfPageDown => {
|
||||
// Move vi mode cursor
|
||||
// Move vi mode cursor.
|
||||
let term = ctx.terminal_mut();
|
||||
let scroll_lines = -(term.grid().num_lines().0 as isize / 2);
|
||||
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));
|
||||
},
|
||||
Action::ScrollLineUp => {
|
||||
// Move vi mode cursor
|
||||
// Move vi mode cursor.
|
||||
let term = ctx.terminal();
|
||||
if term.grid().display_offset() != term.grid().history_size()
|
||||
&& 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));
|
||||
},
|
||||
Action::ScrollLineDown => {
|
||||
// Move vi mode cursor
|
||||
// Move vi mode cursor.
|
||||
if ctx.terminal().grid().display_offset() != 0
|
||||
&& ctx.terminal().vi_mode_cursor.point.line.0 != 0
|
||||
{
|
||||
|
@ -240,14 +240,14 @@ impl<T: EventListener> Execute<T> for Action {
|
|||
Action::ScrollToTop => {
|
||||
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_motion(ViMotion::FirstOccupied);
|
||||
},
|
||||
Action::ScrollToBottom => {
|
||||
ctx.scroll(Scroll::Bottom);
|
||||
|
||||
// Move vi mode cursor
|
||||
// Move vi mode cursor.
|
||||
let term = ctx.terminal_mut();
|
||||
term.vi_mode_cursor.point.line = term.grid().num_lines() - 1;
|
||||
term.vi_motion(ViMotion::FirstOccupied);
|
||||
|
@ -314,7 +314,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
|||
let cell_changed =
|
||||
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
|
||||
&& self.ctx.mouse().cell_side == cell_side
|
||||
&& 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().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;
|
||||
|
||||
// Update mouse state and check for URL change
|
||||
// Update mouse state and check for URL change.
|
||||
let mouse_state = self.mouse_state();
|
||||
self.update_url_state(&mouse_state);
|
||||
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
|
||||
&& (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);
|
||||
|
||||
// Move vi mode cursor to mouse cursor position
|
||||
// Move vi mode cursor to mouse cursor position.
|
||||
if self.ctx.terminal().mode().contains(TermMode::VI) {
|
||||
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;
|
||||
|
||||
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
|
||||
{
|
||||
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) {
|
||||
// Calculate modifiers value
|
||||
// Calculate modifiers value.
|
||||
let mut mods = 0;
|
||||
let modifiers = self.ctx.modifiers();
|
||||
if modifiers.shift() {
|
||||
|
@ -445,7 +445,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
|||
mods += 16;
|
||||
}
|
||||
|
||||
// Report mouse events
|
||||
// Report mouse events.
|
||||
if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
|
||||
self.sgr_mouse_report(button + mods, 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) {
|
||||
// Handle mouse mode
|
||||
// Handle mouse mode.
|
||||
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
|
||||
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::Middle => 1,
|
||||
MouseButton::Right => 2,
|
||||
// Can't properly report more than three buttons.
|
||||
// Can't properly report more than three buttons..
|
||||
MouseButton::Other(_) => return,
|
||||
};
|
||||
|
||||
|
@ -472,19 +472,19 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
|||
} else if button == MouseButton::Left {
|
||||
self.on_left_click();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle left click selection and vi mode cursor movement.
|
||||
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 elapsed = now - self.ctx.mouse().last_click_timestamp;
|
||||
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 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);
|
||||
|
@ -507,12 +507,12 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
|||
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.clear_selection();
|
||||
|
||||
// Start new empty selection
|
||||
// Start new empty selection.
|
||||
if self.ctx.modifiers().ctrl() {
|
||||
self.ctx.start_selection(SelectionType::Block, point, side);
|
||||
} 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) {
|
||||
// Update Vi mode cursor position on click
|
||||
// Update Vi mode cursor position on click.
|
||||
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) => {
|
||||
match phase {
|
||||
TouchPhase::Started => {
|
||||
// Reset offset to zero
|
||||
// Reset offset to zero.
|
||||
self.ctx.mouse_mut().scroll_px = 0.;
|
||||
},
|
||||
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;
|
||||
|
||||
// Store absolute position of vi mode cursor
|
||||
// Store absolute position of vi mode cursor.
|
||||
let term = self.ctx.terminal();
|
||||
let absolute = term.visible_to_buffer(term.vi_mode_cursor.point);
|
||||
|
||||
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();
|
||||
term.vi_mode_cursor.point = term.grid().clamp_buffer_to_visible(absolute);
|
||||
term.vi_mode_cursor.point.col = absolute.col;
|
||||
|
||||
// Update selection
|
||||
// Update selection.
|
||||
if term.mode().contains(TermMode::VI) {
|
||||
let point = term.vi_mode_cursor.point;
|
||||
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 {
|
||||
self.ctx.clear_selection();
|
||||
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 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);
|
||||
|
@ -702,7 +702,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
|||
pub fn modifiers_input(&mut self, modifiers: ModifiersState) {
|
||||
*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();
|
||||
self.update_url_state(&mouse_state);
|
||||
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) {
|
||||
// Binding was triggered; run the action
|
||||
// Binding was triggered; run the action.
|
||||
let binding = binding.clone();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let mouse = self.ctx.mouse();
|
||||
mouse.inside_grid
|
||||
|
@ -833,7 +833,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
|||
|
||||
/// Location of the mouse cursor.
|
||||
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() {
|
||||
return MouseState::MessageBarButton;
|
||||
} 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();
|
||||
|
||||
// Check for URL at mouse cursor
|
||||
// Check for URL at mouse cursor.
|
||||
let mods = *self.ctx.modifiers();
|
||||
let highlighted_url = self.ctx.urls().highlighted(
|
||||
self.ctx.config(),
|
||||
|
@ -856,7 +856,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
|
|||
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 {
|
||||
MouseState::Mouse
|
||||
} else {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! Logging for alacritty.
|
||||
//! Logging for Alacritty.
|
||||
//!
|
||||
//! The main executable is supposed to call `initialize()` exactly once during
|
||||
//! startup. All logging messages are written to stdout, given that their
|
||||
|
@ -151,19 +151,19 @@ impl OnDemandLogFile {
|
|||
let mut path = env::temp_dir();
|
||||
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());
|
||||
|
||||
OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) }
|
||||
}
|
||||
|
||||
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() {
|
||||
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() {
|
||||
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
|
||||
// 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)]
|
||||
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
|
||||
#![cfg_attr(all(test, feature = "bench"), feature(test))]
|
||||
|
@ -83,41 +83,41 @@ fn main() {
|
|||
AttachConsole(ATTACH_PARENT_PROCESS);
|
||||
}
|
||||
|
||||
// Load command line options
|
||||
// Load command line options.
|
||||
let options = Options::new();
|
||||
|
||||
// Setup glutin event loop
|
||||
// Setup glutin event loop.
|
||||
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())
|
||||
.expect("Unable to initialize logger");
|
||||
|
||||
// Load configuration file
|
||||
// Load configuration file.
|
||||
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 = options.into_config(config);
|
||||
|
||||
// Update the log level from config
|
||||
// Update the log level from config.
|
||||
log::set_max_level(config.debug.log_level);
|
||||
|
||||
// Switch to home directory
|
||||
// Switch to home directory.
|
||||
#[cfg(target_os = "macos")]
|
||||
env::set_current_dir(dirs::home_dir().unwrap()).unwrap();
|
||||
// Set locale
|
||||
// Set locale.
|
||||
#[cfg(target_os = "macos")]
|
||||
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();
|
||||
|
||||
// Run alacritty
|
||||
// Run Alacritty.
|
||||
if let Err(err) = run(window_event_loop, config) {
|
||||
error!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Clean up logfile
|
||||
// Clean up logfile.
|
||||
if let Some(log_file) = log_file {
|
||||
if !persistent_logging && fs::remove_file(&log_file).is_ok() {
|
||||
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.
|
||||
fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), Box<dyn Error>> {
|
||||
info!("Welcome to Alacritty");
|
||||
|
@ -137,25 +137,25 @@ fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(),
|
|||
None => info!("No configuration file found"),
|
||||
}
|
||||
|
||||
// Set environment variables
|
||||
// Set environment variables.
|
||||
tty::setup_env(&config);
|
||||
|
||||
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.
|
||||
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)))]
|
||||
let clipboard = Clipboard::new(display.window.wayland_display());
|
||||
#[cfg(any(target_os = "macos", windows))]
|
||||
let clipboard = Clipboard::new();
|
||||
|
||||
// Create the terminal
|
||||
// Create the terminal.
|
||||
//
|
||||
// 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
|
||||
|
@ -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 = 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
|
||||
// reading/writing to the shell.
|
||||
#[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))]
|
||||
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
|
||||
// synchronized since the I/O loop updates the state, and the display
|
||||
// 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.
|
||||
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
|
||||
// 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()));
|
||||
}
|
||||
|
||||
// Setup storage for message UI
|
||||
// Setup storage for message UI.
|
||||
let message_buffer = MessageBuffer::new();
|
||||
|
||||
// Event processor
|
||||
// Event processor.
|
||||
let mut processor =
|
||||
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();
|
||||
|
||||
info!("Initialisation complete");
|
||||
|
||||
// Start event loop and block until shutdown
|
||||
// Start event loop and block until shutdown.
|
||||
processor.run(terminal, window_event_loop);
|
||||
|
||||
// This explicit drop is needed for Windows, ConPTY backend. Otherwise a deadlock can occur.
|
||||
// The cause:
|
||||
// - 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).
|
||||
// - Conpty is dropped when the last of processor and io_thread are dropped, because both of
|
||||
// them own an Arc<Conpty>.
|
||||
// - 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).
|
||||
// - ConPTY is dropped when the last of processor and io_thread are dropped, because both of
|
||||
// them own an Arc<ConPTY>.
|
||||
//
|
||||
// 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.
|
||||
// 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.
|
||||
//
|
||||
// FIXME: Change PTY API to enforce the correct drop order with the typesystem.
|
||||
drop(processor);
|
||||
|
||||
// Shutdown PTY parser event loop
|
||||
loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to pty event loop");
|
||||
// Shutdown PTY parser event loop.
|
||||
loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to PTY event loop");
|
||||
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();
|
||||
|
||||
// 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)]
|
||||
unsafe {
|
||||
FreeConsole();
|
||||
|
|
|
@ -42,13 +42,13 @@ use std::fmt::{self, Display, Formatter};
|
|||
|
||||
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_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_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 =
|
||||
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"));
|
||||
static TEXT_SHADER_V: &str =
|
||||
|
@ -58,12 +58,12 @@ static RECT_SHADER_F: &str =
|
|||
static RECT_SHADER_V: &str =
|
||||
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 {
|
||||
/// Load the rasterized glyph into GPU memory
|
||||
/// Load the rasterized glyph into GPU memory.
|
||||
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.
|
||||
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".
|
||||
#[derive(Debug)]
|
||||
pub struct TextShaderProgram {
|
||||
// Program id
|
||||
/// Program id.
|
||||
id: GLuint,
|
||||
|
||||
/// projection scale and offset uniform
|
||||
/// Projection scale and offset uniform.
|
||||
u_projection: GLint,
|
||||
|
||||
/// Cell dimensions (pixels)
|
||||
/// Cell dimensions (pixels).
|
||||
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,
|
||||
}
|
||||
|
||||
/// Rectangle drawing program
|
||||
/// Rectangle drawing program.
|
||||
///
|
||||
/// Uniforms are prefixed with "u"
|
||||
/// Uniforms are prefixed with "u".
|
||||
#[derive(Debug)]
|
||||
pub struct RectShaderProgram {
|
||||
// Program id
|
||||
/// Program id.
|
||||
id: GLuint,
|
||||
/// Rectangle color
|
||||
/// Rectangle color.
|
||||
u_color: GLint,
|
||||
}
|
||||
|
||||
|
@ -147,38 +147,39 @@ pub struct Glyph {
|
|||
uv_height: f32,
|
||||
}
|
||||
|
||||
/// Naïve glyph cache
|
||||
/// Naïve glyph cache.
|
||||
///
|
||||
/// Currently only keyed by `char`, and thus not possible to hold different
|
||||
/// representations of the same code point.
|
||||
pub struct GlyphCache {
|
||||
/// Cache of buffered glyphs
|
||||
/// Cache of buffered glyphs.
|
||||
cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
|
||||
|
||||
/// Cache of buffered cursor glyphs
|
||||
/// Cache of buffered cursor glyphs.
|
||||
cursor_cache: HashMap<CursorKey, Glyph, BuildHasherDefault<FnvHasher>>,
|
||||
|
||||
/// Rasterizer for loading new glyphs
|
||||
/// Rasterizer for loading new glyphs.
|
||||
rasterizer: Rasterizer,
|
||||
|
||||
/// regular font
|
||||
/// Regular font.
|
||||
font_key: FontKey,
|
||||
|
||||
/// bold font
|
||||
/// Bold font.
|
||||
bold_key: FontKey,
|
||||
|
||||
/// italic font
|
||||
/// Italic font.
|
||||
italic_key: FontKey,
|
||||
|
||||
/// bold italic font
|
||||
/// Bold italic font.
|
||||
bold_italic_key: FontKey,
|
||||
|
||||
/// font size
|
||||
/// Font size.
|
||||
font_size: font::Size,
|
||||
|
||||
/// glyph offset
|
||||
/// Glyph offset.
|
||||
glyph_offset: Delta<i8>,
|
||||
|
||||
/// 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(
|
||||
font: &config::Font,
|
||||
rasterizer: &mut Rasterizer,
|
||||
) -> Result<(FontKey, FontKey, FontKey, FontKey), font::Error> {
|
||||
let size = font.size;
|
||||
|
||||
// Load regular font
|
||||
// Load regular font.
|
||||
let regular_desc =
|
||||
Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal);
|
||||
|
||||
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| {
|
||||
if desc == regular_desc {
|
||||
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 = load_or_regular(bold_desc);
|
||||
|
||||
// Load italic font
|
||||
// Load italic font.
|
||||
let italic_desc =
|
||||
Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal);
|
||||
|
||||
let italic = load_or_regular(italic_desc);
|
||||
|
||||
// Load bold italic font
|
||||
// Load bold italic font.
|
||||
let bold_italic_desc =
|
||||
Self::make_desc(&font.bold_italic(), font::Slant::Italic, font::Weight::Bold);
|
||||
|
||||
|
@ -314,10 +315,10 @@ impl GlyphCache {
|
|||
dpr: f64,
|
||||
loader: &mut L,
|
||||
) -> Result<(), font::Error> {
|
||||
// Update dpi scaling
|
||||
// Update dpi scaling.
|
||||
self.rasterizer.update_dpr(dpr as f32);
|
||||
|
||||
// Recompute font keys
|
||||
// Recompute font keys.
|
||||
let (regular, bold, italic, bold_italic) =
|
||||
Self::compute_font_keys(&font, &mut self.rasterizer)?;
|
||||
|
||||
|
@ -350,7 +351,7 @@ impl GlyphCache {
|
|||
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> {
|
||||
let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?;
|
||||
let regular_desc =
|
||||
|
@ -379,7 +380,7 @@ impl GlyphCache {
|
|||
let padding_x = f64::from(config.window.padding.x) * 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_height = cell_height as u32 * dimensions.lines_u32();
|
||||
|
||||
|
@ -393,26 +394,26 @@ impl GlyphCache {
|
|||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
struct InstanceData {
|
||||
// coords
|
||||
// Coords.
|
||||
col: f32,
|
||||
row: f32,
|
||||
// glyph offset
|
||||
// Glyph offset.
|
||||
left: f32,
|
||||
top: f32,
|
||||
// glyph scale
|
||||
// Glyph scale.
|
||||
width: f32,
|
||||
height: f32,
|
||||
// uv offset
|
||||
// uv offset.
|
||||
uv_left: f32,
|
||||
uv_bot: f32,
|
||||
// uv scale
|
||||
// uv scale.
|
||||
uv_width: f32,
|
||||
uv_height: f32,
|
||||
// color
|
||||
// Color.
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
// background color
|
||||
// Background color.
|
||||
bg_r: f32,
|
||||
bg_g: f32,
|
||||
bg_b: f32,
|
||||
|
@ -556,7 +557,7 @@ impl QuadRenderer {
|
|||
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
|
||||
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::GenVertexArrays(1, &mut vao);
|
||||
|
@ -587,7 +588,7 @@ impl QuadRenderer {
|
|||
ptr::null(),
|
||||
gl::STREAM_DRAW,
|
||||
);
|
||||
// coords
|
||||
// Coords.
|
||||
gl::VertexAttribPointer(
|
||||
0,
|
||||
2,
|
||||
|
@ -598,7 +599,7 @@ impl QuadRenderer {
|
|||
);
|
||||
gl::EnableVertexAttribArray(0);
|
||||
gl::VertexAttribDivisor(0, 1);
|
||||
// glyphoffset
|
||||
// Glyph offset.
|
||||
gl::VertexAttribPointer(
|
||||
1,
|
||||
4,
|
||||
|
@ -609,7 +610,7 @@ impl QuadRenderer {
|
|||
);
|
||||
gl::EnableVertexAttribArray(1);
|
||||
gl::VertexAttribDivisor(1, 1);
|
||||
// uv
|
||||
// uv.
|
||||
gl::VertexAttribPointer(
|
||||
2,
|
||||
4,
|
||||
|
@ -620,7 +621,7 @@ impl QuadRenderer {
|
|||
);
|
||||
gl::EnableVertexAttribArray(2);
|
||||
gl::VertexAttribDivisor(2, 1);
|
||||
// color
|
||||
// Color.
|
||||
gl::VertexAttribPointer(
|
||||
3,
|
||||
3,
|
||||
|
@ -631,7 +632,7 @@ impl QuadRenderer {
|
|||
);
|
||||
gl::EnableVertexAttribArray(3);
|
||||
gl::VertexAttribDivisor(3, 1);
|
||||
// color
|
||||
// Background color.
|
||||
gl::VertexAttribPointer(
|
||||
4,
|
||||
4,
|
||||
|
@ -643,7 +644,7 @@ impl QuadRenderer {
|
|||
gl::EnableVertexAttribArray(4);
|
||||
gl::VertexAttribDivisor(4, 1);
|
||||
|
||||
// Rectangle setup
|
||||
// Rectangle setup.
|
||||
gl::GenVertexArrays(1, &mut rect_vao);
|
||||
gl::GenBuffers(1, &mut rect_vbo);
|
||||
gl::GenBuffers(1, &mut rect_ebo);
|
||||
|
@ -657,7 +658,7 @@ impl QuadRenderer {
|
|||
gl::STATIC_DRAW,
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
// Cleanup.
|
||||
gl::BindVertexArray(0);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
@ -715,24 +716,24 @@ impl QuadRenderer {
|
|||
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>) {
|
||||
// Swap to rectangle rendering program
|
||||
// Swap to rectangle rendering program.
|
||||
unsafe {
|
||||
// Swap program
|
||||
// Swap program.
|
||||
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);
|
||||
|
||||
// Change blending strategy
|
||||
// Change blending strategy.
|
||||
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::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo);
|
||||
|
||||
// Position
|
||||
// Position.
|
||||
gl::VertexAttribPointer(
|
||||
0,
|
||||
2,
|
||||
|
@ -744,17 +745,17 @@ impl QuadRenderer {
|
|||
gl::EnableVertexAttribArray(0);
|
||||
}
|
||||
|
||||
// Draw all the rects
|
||||
// Draw all the rects.
|
||||
for rect in rects {
|
||||
self.render_rect(&rect, props);
|
||||
}
|
||||
|
||||
// Deactivate rectangle program again
|
||||
// Deactivate rectangle program again.
|
||||
unsafe {
|
||||
// Reset blending strategy
|
||||
// Reset blending strategy.
|
||||
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::BindVertexArray(0);
|
||||
|
||||
|
@ -764,7 +765,7 @@ impl QuadRenderer {
|
|||
let height = props.height as i32;
|
||||
gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y);
|
||||
|
||||
// Disable program
|
||||
// Disable program.
|
||||
gl::UseProgram(0);
|
||||
}
|
||||
}
|
||||
|
@ -773,7 +774,7 @@ impl QuadRenderer {
|
|||
where
|
||||
F: FnOnce(RenderApi<'_, C>) -> T,
|
||||
{
|
||||
// Flush message queue
|
||||
// Flush message queue.
|
||||
if let Ok(Msg::ShaderReload) = self.rx.try_recv() {
|
||||
self.reload_shaders(props);
|
||||
}
|
||||
|
@ -855,7 +856,7 @@ impl QuadRenderer {
|
|||
}
|
||||
|
||||
pub fn resize(&mut self, size: &SizeInfo) {
|
||||
// viewport
|
||||
// Viewport.
|
||||
unsafe {
|
||||
gl::Viewport(
|
||||
size.padding_x as i32,
|
||||
|
@ -864,23 +865,23 @@ impl QuadRenderer {
|
|||
size.height as i32 - 2 * size.padding_y as i32,
|
||||
);
|
||||
|
||||
// update projection
|
||||
// Update projection.
|
||||
gl::UseProgram(self.program.id);
|
||||
self.program.update_projection(size.width, size.height, size.padding_x, size.padding_y);
|
||||
gl::UseProgram(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Render a rectangle
|
||||
//
|
||||
// This requires the rectangle program to be activated
|
||||
/// Render a rectangle.
|
||||
///
|
||||
/// This requires the rectangle program to be activated.
|
||||
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. {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate rectangle position
|
||||
// Calculate rectangle position.
|
||||
let center_x = size.width / 2.;
|
||||
let center_y = size.height / 2.;
|
||||
let x = (rect.x - center_x) / center_x;
|
||||
|
@ -889,10 +890,10 @@ impl QuadRenderer {
|
|||
let height = rect.height / center_y;
|
||||
|
||||
unsafe {
|
||||
// Setup vertices
|
||||
// Setup vertices.
|
||||
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::ARRAY_BUFFER,
|
||||
(size_of::<f32>() * vertices.len()) as _,
|
||||
|
@ -900,10 +901,10 @@ impl QuadRenderer {
|
|||
gl::STATIC_DRAW,
|
||||
);
|
||||
|
||||
// Color
|
||||
// Color.
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.batch.tex);
|
||||
|
@ -1006,14 +1007,14 @@ impl<'a, C> RenderApi<'a, C> {
|
|||
|
||||
#[inline]
|
||||
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 {
|
||||
self.render_batch();
|
||||
}
|
||||
|
||||
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() {
|
||||
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) {
|
||||
let chars = match cell.inner {
|
||||
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 glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| {
|
||||
self.load_glyph(&cursor::get_cursor_glyph(
|
||||
|
@ -1040,7 +1041,7 @@ impl<'a, C> RenderApi<'a, C> {
|
|||
RenderableCellContent::Chars(chars) => chars,
|
||||
};
|
||||
|
||||
// Get font key for cell
|
||||
// Get font key for cell.
|
||||
let font_key = match cell.flags & Flags::BOLD_ITALIC {
|
||||
Flags::BOLD_ITALIC => glyph_cache.bold_italic_key,
|
||||
Flags::ITALIC => glyph_cache.italic_key,
|
||||
|
@ -1048,25 +1049,25 @@ impl<'a, C> RenderApi<'a, C> {
|
|||
_ => 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) {
|
||||
[' '; cell::MAX_ZEROWIDTH_CHARS + 1]
|
||||
} else {
|
||||
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' {
|
||||
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);
|
||||
self.add_render_item(cell, glyph);
|
||||
|
||||
// Render zero-width characters
|
||||
// Render zero-width characters.
|
||||
for c in (&chars[1..]).iter().filter(|c| **c != ' ') {
|
||||
glyph_key.c = *c;
|
||||
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.
|
||||
#[inline]
|
||||
|
@ -1216,12 +1217,12 @@ impl TextShaderProgram {
|
|||
}
|
||||
|
||||
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) {
|
||||
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]
|
||||
// [height - 2 * padding_y, 0] to [-1, 1]
|
||||
let scale_x = 2. / (width - 2. * padding_x);
|
||||
|
@ -1276,7 +1277,7 @@ impl RectShaderProgram {
|
|||
gl::UseProgram(program);
|
||||
}
|
||||
|
||||
// get uniform locations
|
||||
// Get uniform locations.
|
||||
let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) };
|
||||
|
||||
let shader = Self { id: program, u_color };
|
||||
|
@ -1355,10 +1356,10 @@ fn create_shader(
|
|||
if success == GLint::from(gl::TRUE) {
|
||||
Ok(shader)
|
||||
} else {
|
||||
// Read log
|
||||
// Read log.
|
||||
let log = get_shader_info_log(shader);
|
||||
|
||||
// Cleanup
|
||||
// Cleanup.
|
||||
unsafe {
|
||||
gl::DeleteShader(shader);
|
||||
}
|
||||
|
@ -1368,60 +1369,60 @@ fn create_shader(
|
|||
}
|
||||
|
||||
fn get_program_info_log(program: GLuint) -> String {
|
||||
// Get expected log length
|
||||
// Get expected log length.
|
||||
let mut max_length: GLint = 0;
|
||||
unsafe {
|
||||
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 buf: Vec<u8> = Vec::with_capacity(max_length as usize);
|
||||
unsafe {
|
||||
gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
|
||||
}
|
||||
|
||||
// Build a string
|
||||
// Build a string.
|
||||
unsafe {
|
||||
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()
|
||||
}
|
||||
|
||||
fn get_shader_info_log(shader: GLuint) -> String {
|
||||
// Get expected log length
|
||||
// Get expected log length.
|
||||
let mut max_length: GLint = 0;
|
||||
unsafe {
|
||||
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 buf: Vec<u8> = Vec::with_capacity(max_length as usize);
|
||||
unsafe {
|
||||
gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
|
||||
}
|
||||
|
||||
// Build a string
|
||||
// Build a string.
|
||||
unsafe {
|
||||
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()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ShaderCreationError {
|
||||
/// Error reading file
|
||||
/// Error reading file.
|
||||
Io(io::Error),
|
||||
|
||||
/// Error compiling shader
|
||||
/// Error compiling shader.
|
||||
Compile(PathBuf, String),
|
||||
|
||||
/// Problem linking
|
||||
/// Problem linking.
|
||||
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:
|
||||
///
|
||||
|
@ -1472,13 +1473,13 @@ impl From<io::Error> for ShaderCreationError {
|
|||
/// ```
|
||||
#[derive(Debug)]
|
||||
struct Atlas {
|
||||
/// Texture id for this atlas
|
||||
/// Texture id for this atlas.
|
||||
id: GLuint,
|
||||
|
||||
/// Width of atlas
|
||||
/// Width of atlas.
|
||||
width: i32,
|
||||
|
||||
/// Height of atlas
|
||||
/// Height of atlas.
|
||||
height: i32,
|
||||
|
||||
/// Left-most free pixel in a row.
|
||||
|
@ -1487,21 +1488,21 @@ struct Atlas {
|
|||
/// in a row.
|
||||
row_extent: i32,
|
||||
|
||||
/// Baseline for glyphs in the current row
|
||||
/// Baseline for glyphs in the current row.
|
||||
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,
|
||||
}
|
||||
|
||||
/// Error that can happen when inserting a texture to the Atlas
|
||||
/// Error that can happen when inserting a texture to the Atlas.
|
||||
enum AtlasInsertError {
|
||||
/// Texture atlas is full
|
||||
/// Texture atlas is full.
|
||||
Full,
|
||||
|
||||
/// The glyph cannot fit within a single texture
|
||||
/// The glyph cannot fit within a single texture.
|
||||
GlyphTooLarge,
|
||||
}
|
||||
|
||||
|
@ -1541,7 +1542,7 @@ impl Atlas {
|
|||
self.row_tallest = 0;
|
||||
}
|
||||
|
||||
/// Insert a RasterizedGlyph into the texture atlas
|
||||
/// Insert a RasterizedGlyph into the texture atlas.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
glyph: &RasterizedGlyph,
|
||||
|
@ -1551,12 +1552,12 @@ impl Atlas {
|
|||
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) {
|
||||
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) {
|
||||
return Err(AtlasInsertError::Full);
|
||||
}
|
||||
|
@ -1565,7 +1566,7 @@ impl Atlas {
|
|||
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
|
||||
/// errors could still occur at this point if we were checking for them;
|
||||
|
@ -1580,7 +1581,7 @@ impl Atlas {
|
|||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, self.id);
|
||||
|
||||
// Load data into OpenGL
|
||||
// Load data into OpenGL.
|
||||
let (format, buf) = match &glyph.buf {
|
||||
BitmapBuffer::RGB(buf) => {
|
||||
colored = false;
|
||||
|
@ -1608,13 +1609,13 @@ impl Atlas {
|
|||
*active_tex = 0;
|
||||
}
|
||||
|
||||
// Update Atlas state
|
||||
// Update Atlas state.
|
||||
self.row_extent = offset_x + width;
|
||||
if height > self.row_tallest {
|
||||
self.row_tallest = height;
|
||||
}
|
||||
|
||||
// Generate UV coordinates
|
||||
// Generate UV coordinates.
|
||||
let uv_bot = offset_y as f32 / self.height as f32;
|
||||
let uv_left = offset_x as f32 / self.width 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 {
|
||||
let next_extent = self.row_extent + raw.width as i32;
|
||||
let enough_width = next_extent <= self.width;
|
||||
|
@ -1643,7 +1644,7 @@ impl Atlas {
|
|||
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> {
|
||||
let advance_to = self.row_baseline + self.row_tallest;
|
||||
if self.height - advance_to <= 0 {
|
||||
|
|
|
@ -80,7 +80,7 @@ impl RenderLine {
|
|||
_ => unimplemented!("Invalid flag for cell line drawing specified"),
|
||||
};
|
||||
|
||||
// Make sure lines are always visible
|
||||
// Make sure lines are always visible.
|
||||
height = height.max(1.);
|
||||
|
||||
let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height;
|
||||
|
@ -124,19 +124,19 @@ impl RenderLines {
|
|||
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 cell.fg == line.color
|
||||
&& cell.column == line.end.col + 1
|
||||
&& cell.line == line.end.line
|
||||
{
|
||||
// Update the length of the line
|
||||
// Update the length of the line.
|
||||
line.end = cell.into();
|
||||
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 };
|
||||
match self.inner.get_mut(flag) {
|
||||
Some(lines) => lines.push(line),
|
||||
|
|
|
@ -71,9 +71,9 @@ impl Urls {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
// Update tracked URLs
|
||||
/// Update tracked URLs.
|
||||
pub fn update(&mut self, num_cols: usize, cell: RenderableCell) {
|
||||
// Convert cell to character
|
||||
// Convert cell to character.
|
||||
let c = match cell.inner {
|
||||
RenderableCellContent::Chars(chars) => chars[0],
|
||||
RenderableCellContent::Cursor(_) => return,
|
||||
|
@ -82,14 +82,14 @@ impl Urls {
|
|||
let point: Point = cell.into();
|
||||
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 {
|
||||
self.reset();
|
||||
}
|
||||
|
||||
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 let UrlLocation::Url(_, mut end_offset) = self.state {
|
||||
if end_offset != 0 {
|
||||
|
@ -102,20 +102,20 @@ impl Urls {
|
|||
return;
|
||||
}
|
||||
|
||||
// Advance parser
|
||||
// Advance parser.
|
||||
let last_state = mem::replace(&mut self.state, self.locator.advance(c));
|
||||
match (self.state, last_state) {
|
||||
(UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => {
|
||||
// Create empty URL
|
||||
// Create empty URL.
|
||||
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) {
|
||||
let point = scheme_cell.into();
|
||||
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);
|
||||
},
|
||||
(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) {
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Extend the last URL
|
||||
/// Extend the last URL.
|
||||
fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) {
|
||||
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) {
|
||||
url.lines.last_mut().unwrap().end = end;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -156,13 +156,13 @@ impl Urls {
|
|||
mouse_mode: bool,
|
||||
selection: bool,
|
||||
) -> Option<Url> {
|
||||
// Require additional shift in mouse mode
|
||||
// Require additional shift in mouse mode.
|
||||
let mut required_mods = config.ui_config.mouse.url.mods();
|
||||
if mouse_mode {
|
||||
required_mods |= ModifiersState::SHIFT;
|
||||
}
|
||||
|
||||
// Make sure all prerequisites for highlighting are met
|
||||
// Make sure all prerequisites for highlighting are met.
|
||||
if selection
|
||||
|| !mouse.inside_grid
|
||||
|| 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)))]
|
||||
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)))]
|
||||
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)]
|
||||
const IDI_ICON: WORD = 0x101;
|
||||
|
||||
/// Window errors
|
||||
/// Window errors.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error creating the window
|
||||
/// Error creating the window.
|
||||
ContextCreation(glutin::CreationError),
|
||||
|
||||
/// Error dealing with fonts
|
||||
/// Error dealing with fonts.
|
||||
Font(font::Error),
|
||||
|
||||
/// Error manipulating the rendering context
|
||||
/// Error manipulating the rendering context.
|
||||
Context(glutin::ContextError),
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ fn create_gl_window(
|
|||
.with_hardware_acceleration(None)
|
||||
.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)? };
|
||||
|
||||
Ok(windowed_context)
|
||||
|
@ -164,7 +164,7 @@ pub struct Window {
|
|||
}
|
||||
|
||||
impl Window {
|
||||
/// Create a new window
|
||||
/// Create a new window.
|
||||
///
|
||||
/// This creates a window and fully initializes a window.
|
||||
pub fn new(
|
||||
|
@ -242,7 +242,7 @@ impl Window {
|
|||
self.window().set_visible(visibility);
|
||||
}
|
||||
|
||||
/// Set the window title
|
||||
/// Set the window title.
|
||||
#[inline]
|
||||
pub fn set_title(&self, title: &str) {
|
||||
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) {
|
||||
if visible != self.mouse_visible {
|
||||
self.mouse_visible = visible;
|
||||
|
@ -286,9 +286,9 @@ impl Window {
|
|||
.with_decorations(decorations)
|
||||
.with_maximized(window_config.startup_mode() == StartupMode::Maximized)
|
||||
.with_window_icon(icon.ok())
|
||||
// X11
|
||||
// X11.
|
||||
.with_class(class.instance.clone(), class.general.clone())
|
||||
// Wayland
|
||||
// Wayland.
|
||||
.with_app_id(class.instance.clone());
|
||||
|
||||
if let Some(ref val) = window_config.gtk_theme_variant {
|
||||
|
@ -378,7 +378,7 @@ impl Window {
|
|||
self.window().set_minimized(minimized);
|
||||
}
|
||||
|
||||
/// Toggle the window's fullscreen state
|
||||
/// Toggle the window's fullscreen state.
|
||||
pub fn toggle_fullscreen(&mut self) {
|
||||
self.set_fullscreen(self.window().fullscreen().is_none());
|
||||
}
|
||||
|
@ -417,7 +417,7 @@ impl Window {
|
|||
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))]
|
||||
pub fn update_ime_position<T>(&mut self, terminal: &Term<T>, size_info: &SizeInfo) {
|
||||
let point = terminal.cursor().point;
|
||||
|
@ -464,13 +464,13 @@ fn x_embed_window(window: &GlutinWindow, parent_id: c_ulong) {
|
|||
2,
|
||||
);
|
||||
|
||||
// Register new error handler
|
||||
// Register new 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);
|
||||
|
||||
// Drain errors and restore original error handler
|
||||
// Drain errors and restore original error handler.
|
||||
(xlib.XSync)(xlib_display as _, 0);
|
||||
(xlib.XSetErrorHandler)(old_handler);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! ANSI Terminal Stream Parsing
|
||||
//! ANSI Terminal Stream Parsing.
|
||||
use std::io;
|
||||
use std::str;
|
||||
|
||||
|
@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::index::{Column, Line};
|
||||
use crate::term::color::Rgb;
|
||||
|
||||
// Parse colors in XParseColor format
|
||||
/// Parse colors in XParseColor format.
|
||||
fn xparse_color(color: &[u8]) -> Option<Rgb> {
|
||||
if !color.is_empty() && color[0] == b'#' {
|
||||
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> {
|
||||
let colors = str::from_utf8(color).ok()?.split('/').collect::<Vec<_>>();
|
||||
|
||||
|
@ -41,7 +41,7 @@ fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
|
|||
return None;
|
||||
}
|
||||
|
||||
// Scale values instead of filling with `0`s
|
||||
// Scale values instead of filling with `0`s.
|
||||
let scale = |input: &str| {
|
||||
let max = u32::pow(16, input.len() as u32) - 1;
|
||||
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])? })
|
||||
}
|
||||
|
||||
// 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> {
|
||||
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 col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4;
|
||||
Some((col >> (4 * slice.len().saturating_sub(1))) as u8)
|
||||
|
@ -87,13 +87,13 @@ fn parse_number(input: &[u8]) -> Option<u8> {
|
|||
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 {
|
||||
state: ProcessorState,
|
||||
parser: vte::Parser,
|
||||
}
|
||||
|
||||
/// Internal state for VTE processor
|
||||
/// Internal state for VTE processor.
|
||||
struct ProcessorState {
|
||||
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> {
|
||||
/// Create a performer
|
||||
/// Create a performer.
|
||||
#[inline]
|
||||
pub fn new<'b>(
|
||||
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 {
|
||||
fn lines(&self) -> Line;
|
||||
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
|
||||
/// writing specific handler impls for tests far easier.
|
||||
pub trait Handler {
|
||||
/// OSC to set window title
|
||||
/// OSC to set window title.
|
||||
fn set_title(&mut self, _: Option<String>) {}
|
||||
|
||||
/// Set the cursor style
|
||||
/// Set the cursor style.
|
||||
fn set_cursor_style(&mut self, _: Option<CursorStyle>) {}
|
||||
|
||||
/// A character to be displayed
|
||||
/// A character to be displayed.
|
||||
fn input(&mut self, _c: char) {}
|
||||
|
||||
/// Set cursor to position
|
||||
/// Set cursor to position.
|
||||
fn goto(&mut self, _: Line, _: Column) {}
|
||||
|
||||
/// Set cursor to specific row
|
||||
/// Set cursor to specific row.
|
||||
fn goto_line(&mut self, _: Line) {}
|
||||
|
||||
/// Set cursor to specific column
|
||||
/// Set cursor to specific 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) {}
|
||||
|
||||
/// Move cursor up `rows`
|
||||
/// Move cursor up `rows`.
|
||||
fn move_up(&mut self, _: Line) {}
|
||||
|
||||
/// Move cursor down `rows`
|
||||
/// Move cursor down `rows`.
|
||||
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
|
||||
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) {}
|
||||
|
||||
/// Move cursor forward `cols`
|
||||
/// Move cursor forward `cols`.
|
||||
fn move_forward(&mut self, _: Column) {}
|
||||
|
||||
/// Move cursor backward `cols`
|
||||
/// Move cursor backward `cols`.
|
||||
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) {}
|
||||
|
||||
/// 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) {}
|
||||
|
||||
/// Put `count` tabs
|
||||
/// Put `count` tabs.
|
||||
fn put_tab(&mut self, _count: i64) {}
|
||||
|
||||
/// Backspace `count` characters
|
||||
/// Backspace `count` characters.
|
||||
fn backspace(&mut self) {}
|
||||
|
||||
/// Carriage return
|
||||
/// Carriage return.
|
||||
fn carriage_return(&mut self) {}
|
||||
|
||||
/// Linefeed
|
||||
/// Linefeed.
|
||||
fn linefeed(&mut self) {}
|
||||
|
||||
/// Ring the bell
|
||||
/// Ring the bell.
|
||||
///
|
||||
/// Hopefully this is never implemented
|
||||
/// Hopefully this is never implemented.
|
||||
fn bell(&mut self) {}
|
||||
|
||||
/// Substitute char under cursor
|
||||
/// Substitute char under cursor.
|
||||
fn substitute(&mut self) {}
|
||||
|
||||
/// Newline
|
||||
/// Newline.
|
||||
fn newline(&mut self) {}
|
||||
|
||||
/// Set current position as a tabstop
|
||||
/// Set current position as a tabstop.
|
||||
fn set_horizontal_tabstop(&mut self) {}
|
||||
|
||||
/// Scroll up `rows` rows
|
||||
/// Scroll up `rows` rows.
|
||||
fn scroll_up(&mut self, _: Line) {}
|
||||
|
||||
/// Scroll down `rows` rows
|
||||
/// Scroll down `rows` rows.
|
||||
fn scroll_down(&mut self, _: Line) {}
|
||||
|
||||
/// Insert `count` blank lines
|
||||
/// Insert `count` blank lines.
|
||||
fn insert_blank_lines(&mut self, _: Line) {}
|
||||
|
||||
/// Delete `count` lines
|
||||
/// Delete `count` lines.
|
||||
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,
|
||||
/// no mode flags)
|
||||
/// no mode flags).
|
||||
fn erase_chars(&mut self, _: Column) {}
|
||||
|
||||
/// Delete `count` chars
|
||||
/// Delete `count` chars.
|
||||
///
|
||||
/// Deleting a character is like the delete key on the keyboard - everything
|
||||
/// to the right of the deleted things is shifted left.
|
||||
fn delete_chars(&mut self, _: Column) {}
|
||||
|
||||
/// Move backward `count` tabs
|
||||
/// Move backward `count` tabs.
|
||||
fn move_backward_tabs(&mut self, _count: i64) {}
|
||||
|
||||
/// Move forward `count` tabs
|
||||
/// Move forward `count` tabs.
|
||||
fn move_forward_tabs(&mut self, _count: i64) {}
|
||||
|
||||
/// Save current cursor position
|
||||
/// Save current cursor position.
|
||||
fn save_cursor_position(&mut self) {}
|
||||
|
||||
/// Restore cursor position
|
||||
/// Restore cursor position.
|
||||
fn restore_cursor_position(&mut self) {}
|
||||
|
||||
/// Clear current line
|
||||
/// Clear current line.
|
||||
fn clear_line(&mut self, _mode: LineClearMode) {}
|
||||
|
||||
/// Clear screen
|
||||
/// Clear screen.
|
||||
fn clear_screen(&mut self, _mode: ClearMode) {}
|
||||
|
||||
/// Clear tab stops
|
||||
/// Clear tab stops.
|
||||
fn clear_tabs(&mut self, _mode: TabulationClearMode) {}
|
||||
|
||||
/// Reset terminal state
|
||||
/// Reset terminal state.
|
||||
fn reset_state(&mut self) {}
|
||||
|
||||
/// Reverse Index
|
||||
/// Reverse Index.
|
||||
///
|
||||
/// Move the active position to the same horizontal position on the
|
||||
/// preceding line. If the active position is at the top margin, a scroll
|
||||
/// down is performed
|
||||
/// down is performed.
|
||||
fn reverse_index(&mut self) {}
|
||||
|
||||
/// set a terminal attribute
|
||||
/// Set a terminal attribute.
|
||||
fn terminal_attribute(&mut self, _attr: Attr) {}
|
||||
|
||||
/// Set mode
|
||||
/// Set mode.
|
||||
fn set_mode(&mut self, _mode: Mode) {}
|
||||
|
||||
/// Unset mode
|
||||
/// Unset 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) {}
|
||||
|
||||
/// 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) {}
|
||||
|
||||
/// 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) {}
|
||||
|
||||
/// 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,
|
||||
/// 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) {}
|
||||
|
||||
/// 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
|
||||
/// later be 'invoked' by `set_active_charset`
|
||||
/// later be 'invoked' by `set_active_charset`.
|
||||
fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {}
|
||||
|
||||
/// Set an indexed color value
|
||||
/// Set an indexed color value.
|
||||
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) {}
|
||||
|
||||
/// Reset an indexed color to original value
|
||||
/// Reset an indexed color to original value.
|
||||
fn reset_color(&mut self, _: usize) {}
|
||||
|
||||
/// Set the clipboard
|
||||
/// Set the clipboard.
|
||||
fn set_clipboard(&mut self, _: u8, _: &[u8]) {}
|
||||
|
||||
/// Write clipboard data to child.
|
||||
|
@ -329,30 +329,30 @@ pub trait Handler {
|
|||
/// Run the decaln routine.
|
||||
fn decaln(&mut self) {}
|
||||
|
||||
/// Push a title onto the stack
|
||||
/// Push a title onto the stack.
|
||||
fn push_title(&mut self) {}
|
||||
|
||||
/// Pop the last title from the stack
|
||||
/// Pop the last title from the stack.
|
||||
fn pop_title(&mut self) {}
|
||||
}
|
||||
|
||||
/// Describes shape of cursor
|
||||
/// Describes shape of cursor.
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
|
||||
pub enum CursorStyle {
|
||||
/// Cursor is a block like `▒`
|
||||
/// Cursor is a block like `▒`.
|
||||
Block,
|
||||
|
||||
/// Cursor is an underscore like `_`
|
||||
/// Cursor is an underscore like `_`.
|
||||
Underline,
|
||||
|
||||
/// Cursor is a vertical bar `⎸`
|
||||
/// Cursor is a vertical bar `⎸`.
|
||||
Beam,
|
||||
|
||||
/// Cursor is a box like `☐`
|
||||
/// Cursor is a box like `☐`.
|
||||
#[serde(skip)]
|
||||
HollowBlock,
|
||||
|
||||
/// Invisible cursor
|
||||
/// Invisible cursor.
|
||||
#[serde(skip)]
|
||||
Hidden,
|
||||
}
|
||||
|
@ -363,15 +363,15 @@ impl Default for CursorStyle {
|
|||
}
|
||||
}
|
||||
|
||||
/// Terminal modes
|
||||
/// Terminal modes.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Mode {
|
||||
/// ?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 l -> reset 80 column font
|
||||
/// CSI ? 3 h -> set 132 column font.
|
||||
/// CSI ? 3 l -> reset 80 column font.
|
||||
///
|
||||
/// Additionally,
|
||||
///
|
||||
|
@ -380,9 +380,9 @@ pub enum Mode {
|
|||
/// * resets DECLRMM to unavailable
|
||||
/// * clears data from the status line (if set to host-writable)
|
||||
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 l` reset to replacement mode
|
||||
|
@ -421,9 +421,9 @@ pub enum 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> {
|
||||
let private = match intermediate {
|
||||
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)]
|
||||
pub enum LineClearMode {
|
||||
/// Clear right of cursor
|
||||
/// Clear right of cursor.
|
||||
Right,
|
||||
/// Clear left of cursor
|
||||
/// Clear left of cursor.
|
||||
Left,
|
||||
/// Clear entire line
|
||||
/// Clear entire line.
|
||||
All,
|
||||
}
|
||||
|
||||
/// Mode for clearing terminal
|
||||
/// Mode for clearing terminal.
|
||||
///
|
||||
/// Relative to cursor
|
||||
/// Relative to cursor.
|
||||
#[derive(Debug)]
|
||||
pub enum ClearMode {
|
||||
/// Clear below cursor
|
||||
/// Clear below cursor.
|
||||
Below,
|
||||
/// Clear above cursor
|
||||
/// Clear above cursor.
|
||||
Above,
|
||||
/// Clear entire terminal
|
||||
/// Clear entire terminal.
|
||||
All,
|
||||
/// Clear 'saved' lines (scrollback)
|
||||
/// Clear 'saved' lines (scrollback).
|
||||
Saved,
|
||||
}
|
||||
|
||||
/// Mode for clearing tab stops
|
||||
/// Mode for clearing tab stops.
|
||||
#[derive(Debug)]
|
||||
pub enum TabulationClearMode {
|
||||
/// Clear stop under cursor
|
||||
/// Clear stop under cursor.
|
||||
Current,
|
||||
/// Clear all stops
|
||||
/// Clear all stops.
|
||||
All,
|
||||
}
|
||||
|
||||
/// Standard colors
|
||||
/// Standard colors.
|
||||
///
|
||||
/// The order here matters since the enum should be castable to a `usize` for
|
||||
/// indexing a color list.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum NamedColor {
|
||||
/// Black
|
||||
/// Black.
|
||||
Black = 0,
|
||||
/// Red
|
||||
/// Red.
|
||||
Red,
|
||||
/// Green
|
||||
/// Green.
|
||||
Green,
|
||||
/// Yellow
|
||||
/// Yellow.
|
||||
Yellow,
|
||||
/// Blue
|
||||
/// Blue.
|
||||
Blue,
|
||||
/// Magenta
|
||||
/// Magenta.
|
||||
Magenta,
|
||||
/// Cyan
|
||||
/// Cyan.
|
||||
Cyan,
|
||||
/// White
|
||||
/// White.
|
||||
White,
|
||||
/// Bright black
|
||||
/// Bright black.
|
||||
BrightBlack,
|
||||
/// Bright red
|
||||
/// Bright red.
|
||||
BrightRed,
|
||||
/// Bright green
|
||||
/// Bright green.
|
||||
BrightGreen,
|
||||
/// Bright yellow
|
||||
/// Bright yellow.
|
||||
BrightYellow,
|
||||
/// Bright blue
|
||||
/// Bright blue.
|
||||
BrightBlue,
|
||||
/// Bright magenta
|
||||
/// Bright magenta.
|
||||
BrightMagenta,
|
||||
/// Bright cyan
|
||||
/// Bright cyan.
|
||||
BrightCyan,
|
||||
/// Bright white
|
||||
/// Bright white.
|
||||
BrightWhite,
|
||||
/// The foreground color
|
||||
/// The foreground color.
|
||||
Foreground = 256,
|
||||
/// The background color
|
||||
/// The background color.
|
||||
Background,
|
||||
/// Color for the cursor itself
|
||||
/// Color for the cursor itself.
|
||||
Cursor,
|
||||
/// Dim black
|
||||
/// Dim black.
|
||||
DimBlack,
|
||||
/// Dim red
|
||||
/// Dim red.
|
||||
DimRed,
|
||||
/// Dim green
|
||||
/// Dim green.
|
||||
DimGreen,
|
||||
/// Dim yellow
|
||||
/// Dim yellow.
|
||||
DimYellow,
|
||||
/// Dim blue
|
||||
/// Dim blue.
|
||||
DimBlue,
|
||||
/// Dim magenta
|
||||
/// Dim magenta.
|
||||
DimMagenta,
|
||||
/// Dim cyan
|
||||
/// Dim cyan.
|
||||
DimCyan,
|
||||
/// Dim white
|
||||
/// Dim white.
|
||||
DimWhite,
|
||||
/// The bright foreground color
|
||||
/// The bright foreground color.
|
||||
BrightForeground,
|
||||
/// Dim foreground
|
||||
/// Dim foreground.
|
||||
DimForeground,
|
||||
}
|
||||
|
||||
|
@ -623,55 +623,55 @@ pub enum Color {
|
|||
Indexed(u8),
|
||||
}
|
||||
|
||||
/// Terminal character attributes
|
||||
/// Terminal character attributes.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Attr {
|
||||
/// Clear all special abilities
|
||||
/// Clear all special abilities.
|
||||
Reset,
|
||||
/// Bold text
|
||||
/// Bold text.
|
||||
Bold,
|
||||
/// Dim or secondary color
|
||||
/// Dim or secondary color.
|
||||
Dim,
|
||||
/// Italic text
|
||||
/// Italic text.
|
||||
Italic,
|
||||
/// Underline text
|
||||
/// Underline text.
|
||||
Underline,
|
||||
/// Blink cursor slowly
|
||||
/// Blink cursor slowly.
|
||||
BlinkSlow,
|
||||
/// Blink cursor fast
|
||||
/// Blink cursor fast.
|
||||
BlinkFast,
|
||||
/// Invert colors
|
||||
/// Invert colors.
|
||||
Reverse,
|
||||
/// Do not display characters
|
||||
/// Do not display characters.
|
||||
Hidden,
|
||||
/// Strikeout text
|
||||
/// Strikeout text.
|
||||
Strike,
|
||||
/// Cancel bold
|
||||
/// Cancel bold.
|
||||
CancelBold,
|
||||
/// Cancel bold and dim
|
||||
/// Cancel bold and dim.
|
||||
CancelBoldDim,
|
||||
/// Cancel italic
|
||||
/// Cancel italic.
|
||||
CancelItalic,
|
||||
/// Cancel underline
|
||||
/// Cancel underline.
|
||||
CancelUnderline,
|
||||
/// Cancel blink
|
||||
/// Cancel blink.
|
||||
CancelBlink,
|
||||
/// Cancel inversion
|
||||
/// Cancel inversion.
|
||||
CancelReverse,
|
||||
/// Cancel text hiding
|
||||
/// Cancel text hiding.
|
||||
CancelHidden,
|
||||
/// Cancel strikeout
|
||||
/// Cancel strikeout.
|
||||
CancelStrike,
|
||||
/// Set indexed foreground color
|
||||
/// Set indexed foreground color.
|
||||
Foreground(Color),
|
||||
/// Set indexed background color
|
||||
/// Set indexed 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)]
|
||||
pub enum CharsetIndex {
|
||||
/// Default set, is designated as ASCII at startup
|
||||
/// Default set, is designated as ASCII at startup.
|
||||
G0,
|
||||
G1,
|
||||
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)]
|
||||
pub enum StandardCharset {
|
||||
Ascii,
|
||||
|
@ -741,7 +741,7 @@ where
|
|||
debug!("[unhandled unhook]");
|
||||
}
|
||||
|
||||
// TODO replace OSC parsing with parser combinators
|
||||
// TODO replace OSC parsing with parser combinators.
|
||||
#[inline]
|
||||
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||
let writer = &mut self.writer;
|
||||
|
@ -764,7 +764,7 @@ where
|
|||
}
|
||||
|
||||
match params[0] {
|
||||
// Set window title
|
||||
// Set window title.
|
||||
b"0" | b"2" => {
|
||||
if params.len() >= 2 {
|
||||
let title = params[1..]
|
||||
|
@ -780,11 +780,11 @@ where
|
|||
unhandled(params);
|
||||
},
|
||||
|
||||
// Set icon name
|
||||
// This is ignored, since alacritty has no concept of tabs
|
||||
// Set icon name.
|
||||
// This is ignored, since alacritty has no concept of tabs.
|
||||
b"1" => (),
|
||||
|
||||
// Set color index
|
||||
// Set color index.
|
||||
b"4" => {
|
||||
if params.len() > 1 && params.len() % 2 != 0 {
|
||||
for chunk in params[1..].chunks(2) {
|
||||
|
@ -799,16 +799,16 @@ where
|
|||
unhandled(params);
|
||||
},
|
||||
|
||||
// Get/set Foreground, Background, Cursor colors
|
||||
// Get/set Foreground, Background, Cursor colors.
|
||||
b"10" | b"11" | b"12" => {
|
||||
if params.len() >= 2 {
|
||||
if let Some(mut dynamic_code) = parse_number(params[0]) {
|
||||
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 index = NamedColor::Foreground as usize + offset;
|
||||
|
||||
// End of setting dynamic colors
|
||||
// End of setting dynamic colors.
|
||||
if index > NamedColor::Cursor as usize {
|
||||
unhandled(params);
|
||||
break;
|
||||
|
@ -834,7 +834,7 @@ where
|
|||
unhandled(params);
|
||||
},
|
||||
|
||||
// Set cursor style
|
||||
// Set cursor style.
|
||||
b"50" => {
|
||||
if params.len() >= 2
|
||||
&& params[1].len() >= 13
|
||||
|
@ -852,7 +852,7 @@ where
|
|||
unhandled(params);
|
||||
},
|
||||
|
||||
// Set clipboard
|
||||
// Set clipboard.
|
||||
b"52" => {
|
||||
if params.len() < 3 {
|
||||
return unhandled(params);
|
||||
|
@ -865,9 +865,9 @@ where
|
|||
}
|
||||
},
|
||||
|
||||
// Reset color index
|
||||
// Reset color index.
|
||||
b"104" => {
|
||||
// Reset all color indexes when no parameters are given
|
||||
// Reset all color indexes when no parameters are given.
|
||||
if params.len() == 1 {
|
||||
for i in 0..256 {
|
||||
self.handler.reset_color(i);
|
||||
|
@ -875,7 +875,7 @@ where
|
|||
return;
|
||||
}
|
||||
|
||||
// Reset color indexes given as parameters
|
||||
// Reset color indexes given as parameters.
|
||||
for param in ¶ms[1..] {
|
||||
match parse_number(param) {
|
||||
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),
|
||||
|
||||
// Reset background color
|
||||
// Reset background color.
|
||||
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),
|
||||
|
||||
_ => unhandled(params),
|
||||
|
@ -1066,7 +1066,7 @@ where
|
|||
handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize)
|
||||
},
|
||||
('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) {
|
||||
0 => None,
|
||||
1 | 2 => Some(CursorStyle::Block),
|
||||
|
@ -1138,7 +1138,7 @@ where
|
|||
(b'8', None) => self.handler.restore_cursor_position(),
|
||||
(b'=', None) => self.handler.set_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) => (),
|
||||
_ => unhandled!(),
|
||||
}
|
||||
|
@ -1146,12 +1146,10 @@ where
|
|||
}
|
||||
|
||||
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; // C-for initializer
|
||||
let mut i = 0;
|
||||
let mut attrs = Vec::with_capacity(parameters.len());
|
||||
loop {
|
||||
if i >= parameters.len() {
|
||||
// C-for condition
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1231,12 +1229,12 @@ fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
|
|||
|
||||
attrs.push(attr);
|
||||
|
||||
i += 1; // C-for expr
|
||||
i += 1;
|
||||
}
|
||||
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> {
|
||||
if attrs.len() < 2 {
|
||||
return None;
|
||||
|
@ -1244,7 +1242,7 @@ fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
|
|||
|
||||
match attrs[*i + 1] {
|
||||
2 => {
|
||||
// RGB color spec
|
||||
// RGB color spec.
|
||||
if attrs.len() < 5 {
|
||||
debug!("Expected RGB color spec; got {:?}", attrs);
|
||||
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).
|
||||
#[allow(non_snake_case)]
|
||||
pub mod C0 {
|
||||
/// Null filler, terminal should ignore this character
|
||||
/// Null filler, terminal should ignore this character.
|
||||
pub const NUL: u8 = 0x00;
|
||||
/// Start of Header
|
||||
/// Start of Header.
|
||||
pub const SOH: u8 = 0x01;
|
||||
/// Start of Text, implied end of header
|
||||
/// Start of Text, implied end of header.
|
||||
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;
|
||||
/// End of Transmission
|
||||
/// End of Transmission.
|
||||
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;
|
||||
/// Acknowledge, usually sent by terminal in response to ETX
|
||||
/// Acknowledge, usually sent by terminal in response to ETX.
|
||||
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;
|
||||
/// Backspace, can be used to define overstruck characters
|
||||
/// Backspace, can be used to define overstruck characters.
|
||||
pub const BS: u8 = 0x08;
|
||||
/// Horizontal Tabulation, move to next predetermined position
|
||||
/// Horizontal Tabulation, move to next predetermined position.
|
||||
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;
|
||||
/// Vertical Tabulation, move to next predetermined line
|
||||
/// Vertical Tabulation, move to next predetermined line.
|
||||
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;
|
||||
/// Carriage Return, move to first character of current line
|
||||
/// Carriage Return, move to first character of current line.
|
||||
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;
|
||||
/// 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;
|
||||
/// Data Link Escape, interpret next control character specially
|
||||
/// Data Link Escape, interpret next control character specially.
|
||||
pub const DLE: u8 = 0x10;
|
||||
/// (DC1) Terminal is allowed to resume transmitting
|
||||
/// (DC1) Terminal is allowed to resume transmitting.
|
||||
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;
|
||||
/// (DC2) Terminal must pause and refrain from transmitting
|
||||
/// (DC2) Terminal must pause and refrain from transmitting.
|
||||
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;
|
||||
/// Negative Acknowledge, used sometimes with ETX and ACK
|
||||
/// Negative Acknowledge, used sometimes with ETX and ACK.
|
||||
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;
|
||||
/// End of Transmission block
|
||||
/// End of Transmission block.
|
||||
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;
|
||||
/// End of Medium
|
||||
/// End of Medium.
|
||||
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;
|
||||
/// Prefix to an escape sequence
|
||||
/// Prefix to an escape sequence.
|
||||
pub const ESC: u8 = 0x1B;
|
||||
/// File Separator
|
||||
/// File Separator.
|
||||
pub const FS: u8 = 0x1C;
|
||||
/// Group Separator
|
||||
/// Group Separator.
|
||||
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;
|
||||
/// Unit Separator
|
||||
/// Unit Separator.
|
||||
pub const US: u8 = 0x1F;
|
||||
/// Delete, should be ignored by terminal
|
||||
/// Delete, should be ignored by terminal.
|
||||
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.
|
||||
#[cfg(test)]
|
||||
|
@ -1514,7 +1512,7 @@ mod tests {
|
|||
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]
|
||||
fn parse_zsh_startup() {
|
||||
static BYTES: &[u8] = &[
|
||||
|
|
|
@ -61,7 +61,7 @@ impl Clipboard {
|
|||
return Self::new_nop();
|
||||
}
|
||||
|
||||
// Use for tests and ref-tests
|
||||
// Use for tests and ref-tests.
|
||||
pub fn new_nop() -> Self {
|
||||
Self { clipboard: Box::new(NopClipboardContext::new().unwrap()), selection: None }
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ where
|
|||
index
|
||||
);
|
||||
|
||||
// Return value out of range to ignore this color
|
||||
// Return value out of range to ignore this color.
|
||||
Ok(0)
|
||||
} else {
|
||||
Ok(index)
|
||||
|
@ -68,7 +68,7 @@ where
|
|||
Err(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)
|
||||
},
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ fn default_foreground() -> Rgb {
|
|||
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)]
|
||||
pub struct AnsiColors {
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Deserializer};
|
|||
|
||||
use crate::config::{failure_default, LOG_TARGET_CONFIG};
|
||||
|
||||
/// Debugging options
|
||||
/// Debugging options.
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Debug {
|
||||
|
@ -13,15 +13,15 @@ pub struct Debug {
|
|||
#[serde(deserialize_with = "failure_default")]
|
||||
pub print_events: bool,
|
||||
|
||||
/// Keep the log file after quitting
|
||||
/// Keep the log file after quitting.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub persistent_logging: bool,
|
||||
|
||||
/// Should show render timer
|
||||
/// Should show render timer.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub render_timer: bool,
|
||||
|
||||
/// Record ref test
|
||||
/// Record ref test.
|
||||
#[serde(skip)]
|
||||
pub ref_test: bool,
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use serde::{Deserialize, Deserializer};
|
|||
use crate::config::DefaultTrueBool;
|
||||
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
|
||||
/// 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)]
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct Font {
|
||||
/// Normal font face
|
||||
/// Normal font face.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
normal: FontDescription,
|
||||
|
||||
/// Bold font face
|
||||
/// Bold font face.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
bold: SecondaryFontDescription,
|
||||
|
||||
/// Italic font face
|
||||
/// Italic font face.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
italic: SecondaryFontDescription,
|
||||
|
||||
/// Bold italic font face
|
||||
/// Bold italic font face.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
bold_italic: SecondaryFontDescription,
|
||||
|
||||
/// Font size in points
|
||||
/// Font size in points.
|
||||
#[serde(deserialize_with = "DeserializeSize::deserialize")]
|
||||
pub size: Size,
|
||||
|
||||
/// Extra spacing per character
|
||||
/// Extra spacing per character.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub offset: Delta<i8>,
|
||||
|
||||
/// Glyph offset within character cell
|
||||
/// Glyph offset within character cell.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub glyph_offset: Delta<i8>,
|
||||
|
||||
|
@ -68,27 +68,27 @@ impl Default for 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 {
|
||||
Font { size, ..self }
|
||||
}
|
||||
|
||||
// Get normal font description
|
||||
/// Get normal font description.
|
||||
pub fn normal(&self) -> &FontDescription {
|
||||
&self.normal
|
||||
}
|
||||
|
||||
// Get bold font description
|
||||
/// Get bold font description.
|
||||
pub fn bold(&self) -> FontDescription {
|
||||
self.bold.desc(&self.normal)
|
||||
}
|
||||
|
||||
// Get italic font description
|
||||
/// Get italic font description.
|
||||
pub fn italic(&self) -> FontDescription {
|
||||
self.italic.desc(&self.normal)
|
||||
}
|
||||
|
||||
// Get bold italic font description
|
||||
/// Get bold italic font description.
|
||||
pub fn bold_italic(&self) -> FontDescription {
|
||||
self.bold_italic.desc(&self.normal)
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ fn default_font_size() -> Size {
|
|||
Size::new(11.)
|
||||
}
|
||||
|
||||
/// Description of the normal font
|
||||
/// Description of the normal font.
|
||||
#[serde(default)]
|
||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||
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)]
|
||||
#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct SecondaryFontDescription {
|
||||
|
@ -198,7 +198,7 @@ impl DeserializeSize for Size {
|
|||
.deserialize_any(NumVisitor::<D> { _marker: PhantomData })
|
||||
.map(|v| Size::new(v as _));
|
||||
|
||||
// Use default font size as fallback
|
||||
// Use default font size as fallback.
|
||||
match size {
|
||||
Ok(size) => Ok(size),
|
||||
Err(err) => {
|
||||
|
|
|
@ -44,68 +44,68 @@ const DEFAULT_CURSOR_THICKNESS: f32 = 0.15;
|
|||
|
||||
pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>;
|
||||
|
||||
/// Top-level config type
|
||||
/// Top-level config type.
|
||||
#[derive(Debug, PartialEq, Default, Deserialize)]
|
||||
pub struct Config<T> {
|
||||
/// Pixel padding
|
||||
/// Pixel padding.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub padding: Option<Delta<u8>>,
|
||||
|
||||
/// TERM env variable
|
||||
/// TERM env variable.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub env: HashMap<String, String>,
|
||||
|
||||
/// Font configuration
|
||||
/// Font configuration.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
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")]
|
||||
draw_bold_text_with_bright_colors: bool,
|
||||
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
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")]
|
||||
background_opacity: Percentage,
|
||||
|
||||
/// Window configuration
|
||||
/// Window configuration.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub window: WindowConfig,
|
||||
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
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")]
|
||||
pub shell: Option<Shell<'static>>,
|
||||
|
||||
/// Path where config was loaded from
|
||||
/// Path where config was loaded from.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub config_path: Option<PathBuf>,
|
||||
|
||||
/// Visual bell configuration
|
||||
/// Visual bell configuration.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub visual_bell: VisualBellConfig,
|
||||
|
||||
/// Use dynamic title
|
||||
/// Use dynamic title.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
dynamic_title: DefaultTrueBool,
|
||||
|
||||
/// Live config reload
|
||||
/// Live config reload.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
live_config_reload: DefaultTrueBool,
|
||||
|
||||
/// How much scrolling history to keep
|
||||
/// How much scrolling history to keep.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub scrolling: Scrolling,
|
||||
|
||||
/// Cursor configuration
|
||||
/// Cursor configuration.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub cursor: Cursor,
|
||||
|
||||
/// Use WinPTY backend even if ConPTY is available
|
||||
/// Use WinPTY backend even if ConPTY is available.
|
||||
#[cfg(windows)]
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub winpty_backend: bool,
|
||||
|
@ -114,19 +114,19 @@ pub struct Config<T> {
|
|||
#[serde(default, deserialize_with = "failure_default")]
|
||||
alt_send_esc: DefaultTrueBool,
|
||||
|
||||
/// Shell startup directory
|
||||
/// Shell startup directory.
|
||||
#[serde(default, deserialize_with = "option_explicit_none")]
|
||||
pub working_directory: Option<PathBuf>,
|
||||
|
||||
/// Debug options
|
||||
/// Debug options.
|
||||
#[serde(default, deserialize_with = "failure_default")]
|
||||
pub debug: Debug,
|
||||
|
||||
/// Additional configuration options not directly required by the terminal
|
||||
/// Additional configuration options not directly required by the terminal.
|
||||
#[serde(flatten)]
|
||||
pub ui_config: T,
|
||||
|
||||
/// Remain open after child process exits
|
||||
/// Remain open after child process exits.
|
||||
#[serde(skip)]
|
||||
pub hold: bool,
|
||||
|
||||
|
@ -149,13 +149,13 @@ impl<T> Config<T> {
|
|||
self.draw_bold_text_with_bright_colors
|
||||
}
|
||||
|
||||
/// Should show render timer
|
||||
/// Should show render timer.
|
||||
#[inline]
|
||||
pub fn render_timer(&self) -> bool {
|
||||
self.render_timer.unwrap_or(self.debug.render_timer)
|
||||
}
|
||||
|
||||
/// Live config reload
|
||||
/// Live config reload.
|
||||
#[inline]
|
||||
pub fn live_config_reload(&self) -> bool {
|
||||
self.live_config_reload.0
|
||||
|
@ -200,13 +200,13 @@ impl<T> Config<T> {
|
|||
self.dynamic_title.0 = dynamic_title;
|
||||
}
|
||||
|
||||
/// Send escape sequences using the alt key
|
||||
/// Send escape sequences using the alt key.
|
||||
#[inline]
|
||||
pub fn alt_send_esc(&self) -> bool {
|
||||
self.alt_send_esc.0
|
||||
}
|
||||
|
||||
/// Keep the log file after quitting Alacritty
|
||||
/// Keep the log file after quitting Alacritty.
|
||||
#[inline]
|
||||
pub fn persistent_logging(&self) -> bool {
|
||||
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"))]
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
|
||||
pub struct Delta<T: Default + PartialEq + Eq> {
|
||||
/// Horizontal change
|
||||
/// Horizontal change.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub x: T,
|
||||
/// Vertical change
|
||||
/// Vertical change.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
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 {
|
||||
fn from(input: String) -> Self;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Deserializer};
|
|||
|
||||
use crate::config::{failure_default, LOG_TARGET_CONFIG, MAX_SCROLLBACK_LINES};
|
||||
|
||||
/// Struct for scrolling related settings
|
||||
/// Struct for scrolling related settings.
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct Scrolling {
|
||||
|
@ -34,7 +34,7 @@ impl Scrolling {
|
|||
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) {
|
||||
self.history = ScrollingHistory(history);
|
||||
}
|
||||
|
|
|
@ -8,15 +8,15 @@ use crate::term::color::Rgb;
|
|||
#[serde(default)]
|
||||
#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct VisualBellConfig {
|
||||
/// Visual bell animation function
|
||||
/// Visual bell animation function.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub animation: VisualBellAnimation,
|
||||
|
||||
/// Visual bell duration in milliseconds
|
||||
/// Visual bell duration in milliseconds.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub duration: u16,
|
||||
|
||||
/// Visual bell flash color
|
||||
/// Visual bell flash color.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub color: Rgb,
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ impl Default for VisualBellConfig {
|
|||
}
|
||||
|
||||
impl VisualBellConfig {
|
||||
/// Visual bell duration in milliseconds
|
||||
/// Visual bell duration in milliseconds.
|
||||
#[inline]
|
||||
pub fn duration(&self) -> Duration {
|
||||
Duration::from_millis(u64::from(self.duration))
|
||||
|
@ -43,15 +43,25 @@ impl VisualBellConfig {
|
|||
/// Penner's Easing Functions.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub enum VisualBellAnimation {
|
||||
Ease, // CSS
|
||||
EaseOut, // CSS
|
||||
EaseOutSine, // Penner
|
||||
EaseOutQuad, // Penner
|
||||
EaseOutCubic, // Penner
|
||||
EaseOutQuart, // Penner
|
||||
EaseOutQuint, // Penner
|
||||
EaseOutExpo, // Penner
|
||||
EaseOutCirc, // Penner
|
||||
// CSS animation.
|
||||
Ease,
|
||||
// CSS animation.
|
||||
EaseOut,
|
||||
// Penner animation.
|
||||
EaseOutSine,
|
||||
// Penner animation.
|
||||
EaseOutQuad,
|
||||
// Penner animation.
|
||||
EaseOutCubic,
|
||||
// Penner animation.
|
||||
EaseOutQuart,
|
||||
// Penner animation.
|
||||
EaseOutQuint,
|
||||
// Penner animation.
|
||||
EaseOutExpo,
|
||||
// Penner animation.
|
||||
EaseOutCirc,
|
||||
// Penner animation.
|
||||
Linear,
|
||||
}
|
||||
|
||||
|
|
|
@ -13,47 +13,47 @@ pub const DEFAULT_NAME: &str = "Alacritty";
|
|||
#[serde(default)]
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WindowConfig {
|
||||
/// Initial dimensions
|
||||
/// Initial dimensions.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub dimensions: Dimensions,
|
||||
|
||||
/// Initial position
|
||||
/// Initial position.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub position: Option<Delta<i32>>,
|
||||
|
||||
/// Pixel padding
|
||||
/// Pixel padding.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub padding: Delta<u8>,
|
||||
|
||||
/// Draw the window with title bar / borders
|
||||
/// Draw the window with title bar / borders.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub decorations: Decorations,
|
||||
|
||||
/// Spread out additional padding evenly
|
||||
/// Spread out additional padding evenly.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
pub dynamic_padding: bool,
|
||||
|
||||
/// Startup mode
|
||||
/// Startup mode.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
startup_mode: StartupMode,
|
||||
|
||||
/// Window title
|
||||
/// Window title.
|
||||
#[serde(default = "default_title")]
|
||||
pub title: String,
|
||||
|
||||
/// Window class
|
||||
/// Window class.
|
||||
#[serde(deserialize_with = "from_string_or_deserialize")]
|
||||
pub class: Class,
|
||||
|
||||
/// XEmbed parent
|
||||
/// XEmbed parent.
|
||||
#[serde(skip)]
|
||||
pub embed: Option<c_ulong>,
|
||||
|
||||
/// GTK theme variant
|
||||
/// GTK theme variant.
|
||||
#[serde(deserialize_with = "option_explicit_none")]
|
||||
pub gtk_theme_variant: Option<String>,
|
||||
|
||||
/// TODO: DEPRECATED
|
||||
// TODO: DEPRECATED
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
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)]
|
||||
#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct Dimensions {
|
||||
/// Window width in character columns
|
||||
/// Window width in character columns.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
columns: Column,
|
||||
|
||||
/// Window Height in character lines
|
||||
/// Window Height in character lines.
|
||||
#[serde(deserialize_with = "failure_default")]
|
||||
lines: Line,
|
||||
}
|
||||
|
@ -144,20 +144,20 @@ impl Dimensions {
|
|||
Dimensions { columns, lines }
|
||||
}
|
||||
|
||||
/// Get lines
|
||||
/// Get lines.
|
||||
#[inline]
|
||||
pub fn lines_u32(&self) -> u32 {
|
||||
self.lines.0 as u32
|
||||
}
|
||||
|
||||
/// Get columns
|
||||
/// Get columns.
|
||||
#[inline]
|
||||
pub fn columns_u32(&self) -> u32 {
|
||||
self.columns.0 as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Window class hint
|
||||
/// Window class hint.
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Class {
|
||||
|
|
|
@ -16,20 +16,20 @@ pub enum Event {
|
|||
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 {
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Types that are interested in when the display is resized
|
||||
/// Types that are interested in when the display is resized.
|
||||
pub trait OnResize {
|
||||
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 {
|
||||
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::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
|
@ -20,25 +20,25 @@ use crate::term::{SizeInfo, Term};
|
|||
use crate::tty;
|
||||
use crate::util::thread;
|
||||
|
||||
/// Max bytes to read from the PTY
|
||||
/// Max bytes to read from the PTY.
|
||||
const MAX_READ: usize = 0x10_000;
|
||||
|
||||
/// Messages that may be sent to the `EventLoop`
|
||||
/// Messages that may be sent to the `EventLoop`.
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
/// Data that should be written to the pty
|
||||
/// Data that should be written to the PTY.
|
||||
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,
|
||||
|
||||
/// Instruction to resize the pty
|
||||
/// Instruction to resize the PTY.
|
||||
Resize(SizeInfo),
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub struct EventLoop<T: tty::EventedPty, U: EventListener> {
|
||||
poll: mio::Poll,
|
||||
|
@ -57,7 +57,7 @@ struct Writing {
|
|||
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
|
||||
/// would otherwise be mutated on the `EventLoop` goes here.
|
||||
|
@ -152,7 +152,7 @@ where
|
|||
T: tty::EventedPty + event::OnResize + Send + 'static,
|
||||
U: EventListener + Send + 'static,
|
||||
{
|
||||
/// Create a new event loop
|
||||
/// Create a new event loop.
|
||||
pub fn new<V>(
|
||||
terminal: Arc<FairMutex<Term<U>>>,
|
||||
event_proxy: U,
|
||||
|
@ -176,9 +176,9 @@ where
|
|||
self.tx.clone()
|
||||
}
|
||||
|
||||
// Drain the channel
|
||||
//
|
||||
// Returns `false` when a shutdown message was received.
|
||||
/// Drain the channel.
|
||||
///
|
||||
/// Returns `false` when a shutdown message was received.
|
||||
fn drain_recv_channel(&mut self, state: &mut State) -> bool {
|
||||
while let Ok(msg) = self.rx.try_recv() {
|
||||
match msg {
|
||||
|
@ -191,7 +191,7 @@ where
|
|||
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]
|
||||
fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool {
|
||||
if !self.drain_recv_channel(state) {
|
||||
|
@ -234,18 +234,18 @@ where
|
|||
|
||||
// Get reference to terminal. Lock is acquired on initial
|
||||
// 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() {
|
||||
terminal = Some(self.terminal.lock());
|
||||
}
|
||||
let terminal = terminal.as_mut().unwrap();
|
||||
|
||||
// Run the parser
|
||||
// Run the parser.
|
||||
for byte in &buf[..got] {
|
||||
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 {
|
||||
break;
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ where
|
|||
}
|
||||
|
||||
if processed > 0 {
|
||||
// Queue terminal redraw
|
||||
// Queue terminal redraw.
|
||||
self.event_proxy.send_event(Event::Wakeup);
|
||||
}
|
||||
|
||||
|
@ -300,7 +300,7 @@ where
|
|||
}
|
||||
|
||||
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 buf = [0u8; MAX_READ];
|
||||
|
||||
|
@ -311,7 +311,7 @@ where
|
|||
let channel_token = tokens.next().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();
|
||||
|
||||
let mut events = Events::with_capacity(1024);
|
||||
|
@ -355,13 +355,14 @@ where
|
|||
#[cfg(unix)]
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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")]
|
||||
{
|
||||
// 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.
|
||||
// This sucks, but checking the process is either racy or
|
||||
// blocking.
|
||||
if e.kind() == ErrorKind::Other {
|
||||
if err.kind() == ErrorKind::Other {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
error!("Error reading from PTY in event loop: {}", e);
|
||||
error!("Error reading from PTY in event loop: {}", err);
|
||||
break 'event_loop;
|
||||
}
|
||||
}
|
||||
|
||||
if event.readiness().is_writable() {
|
||||
if let Err(e) = self.pty_write(&mut state) {
|
||||
error!("Error writing to PTY in event loop: {}", e);
|
||||
if let Err(err) = self.pty_write(&mut state) {
|
||||
error!("Error writing to PTY in event loop: {}", err);
|
||||
break 'event_loop;
|
||||
}
|
||||
}
|
||||
|
@ -390,16 +391,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// Register write interest if necessary
|
||||
// Register write interest if necessary.
|
||||
let mut interest = Ready::readable();
|
||||
if state.needs_write() {
|
||||
interest.insert(Ready::writable());
|
||||
}
|
||||
// Reregister with new interest
|
||||
// Reregister with new interest.
|
||||
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.pty.deregister(&self.poll);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo};
|
||||
|
@ -32,7 +32,7 @@ mod tests;
|
|||
mod storage;
|
||||
use self::storage::Storage;
|
||||
|
||||
/// Bidirection iterator
|
||||
/// Bidirectional iterator.
|
||||
pub trait BidirectionalIterator: Iterator {
|
||||
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> {
|
||||
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.cols.eq(&other.cols)
|
||||
&& self.lines.eq(&other.lines)
|
||||
|
@ -72,11 +72,11 @@ pub trait GridCell {
|
|||
/// Fast equality approximation.
|
||||
///
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// Represents the terminal display contents
|
||||
/// Represents the terminal display contents.
|
||||
///
|
||||
/// ```notrust
|
||||
/// ┌─────────────────────────┐ <-- 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 {
|
||||
Point::new(Line(0), Column(0))
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
/// Update the size of the scrollback history
|
||||
/// Update the size of the scrollback history.
|
||||
pub fn update_history(&mut self, history_size: usize) {
|
||||
let current_history_size = self.history_size();
|
||||
if current_history_size > history_size {
|
||||
|
@ -199,7 +199,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
cursor_pos: &mut Point,
|
||||
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 {
|
||||
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
|
||||
/// 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) {
|
||||
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.lines = new_line_count;
|
||||
|
||||
let history_size = self.history_size();
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -258,9 +258,9 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
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) {
|
||||
// Check if a row needs to be wrapped
|
||||
// Check if a row needs to be wrapped.
|
||||
let should_reflow = |row: &Row<T>| -> bool {
|
||||
let len = Column(row.len());
|
||||
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 reversed: Vec<Row<T>> = Vec::with_capacity(self.raw.len());
|
||||
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() {
|
||||
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() {
|
||||
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();
|
||||
if last_len >= 2
|
||||
&& !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));
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
// 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 = row.front_split_off(len - 1);
|
||||
|
||||
|
@ -312,28 +312,28 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
|
||||
if row.is_empty() {
|
||||
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));
|
||||
new_empty_lines += 1;
|
||||
} 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);
|
||||
}
|
||||
|
||||
// Don't push line into the new buffer
|
||||
// Don't push line into the new buffer.
|
||||
continue;
|
||||
} 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);
|
||||
}
|
||||
|
||||
reversed.push(row);
|
||||
}
|
||||
|
||||
// Add padding lines
|
||||
// Add padding 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());
|
||||
for mut row in reversed.drain(..).rev() {
|
||||
if row.len() < cols.0 {
|
||||
|
@ -348,18 +348,18 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
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) {
|
||||
let mut new_raw = Vec::with_capacity(self.raw.len());
|
||||
let mut buffered = None;
|
||||
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() {
|
||||
row.append_front(buffered);
|
||||
}
|
||||
|
||||
loop {
|
||||
// Check if reflowing shoud be performed
|
||||
// Check if reflowing should be performed.
|
||||
let mut wrapped = match row.shrink(cols) {
|
||||
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) {
|
||||
wrapped.insert(0, row[cols - 1]);
|
||||
|
||||
|
@ -377,7 +377,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
row[cols - 1] = spacer;
|
||||
}
|
||||
|
||||
// Remove wide char spacer before shrinking
|
||||
// Remove wide char spacer before shrinking.
|
||||
let len = wrapped.len();
|
||||
if (len == 1 || (len >= 2 && !wrapped[len - 2].flags().contains(Flags::WIDE_CHAR)))
|
||||
&& wrapped[len - 1].flags().contains(Flags::WIDE_CHAR_SPACER)
|
||||
|
@ -394,7 +394,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
|
||||
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()) {
|
||||
cell.flags_mut().insert(Flags::WRAPLINE);
|
||||
}
|
||||
|
@ -405,21 +405,21 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
.unwrap_or(false)
|
||||
&& 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() {
|
||||
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);
|
||||
break;
|
||||
} 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 {
|
||||
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();
|
||||
if occ < cols.0 {
|
||||
wrapped.append(&mut vec![*template; cols.0 - occ]);
|
||||
|
@ -435,7 +435,7 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
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
|
||||
/// 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.
|
||||
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);
|
||||
if required_scrolling > 0 {
|
||||
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);
|
||||
|
||||
// Now, restore any scroll region lines
|
||||
// Now, restore any scroll region lines.
|
||||
let lines = self.lines;
|
||||
for i in IndexRange(region.end..lines) {
|
||||
self.raw.swap_lines(i, i + positions);
|
||||
}
|
||||
|
||||
// Finally, reset recycled lines
|
||||
// Finally, reset recycled lines.
|
||||
for i in IndexRange(Line(0)..positions) {
|
||||
self.raw[i].reset(&template);
|
||||
}
|
||||
} else {
|
||||
// Rotate selection to track content
|
||||
// Rotate selection to track content.
|
||||
self.selection = self
|
||||
.selection
|
||||
.take()
|
||||
.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() {
|
||||
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.
|
||||
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;
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
// Finally, reset recycled lines
|
||||
// Finally, reset recycled lines.
|
||||
//
|
||||
// Recycled lines are just above the end of the scrolling region.
|
||||
for i in 0..*positions {
|
||||
self.raw[i + fixed_lines].reset(&template);
|
||||
}
|
||||
} else {
|
||||
// Rotate selection to track content
|
||||
// Rotate selection to track content.
|
||||
self.selection = self
|
||||
.selection
|
||||
.take()
|
||||
.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)) {
|
||||
self.raw.swap_lines(line, line + positions);
|
||||
}
|
||||
|
||||
// Clear reused lines
|
||||
// Clear reused lines.
|
||||
for line in IndexRange((region.end - positions)..region.end) {
|
||||
self.raw[line].reset(&template);
|
||||
}
|
||||
|
@ -575,23 +575,23 @@ impl<T: GridCell + PartialEq + Copy> Grid<T> {
|
|||
let positions = self.lines - iter.cur.line;
|
||||
let region = Line(0)..self.num_lines();
|
||||
|
||||
// Reset display offset
|
||||
// Reset display offset.
|
||||
self.display_offset = 0;
|
||||
|
||||
// Clear the viewport
|
||||
// Clear the viewport.
|
||||
self.scroll_up(®ion, positions, template);
|
||||
|
||||
// Reset rotated lines
|
||||
// Reset rotated lines.
|
||||
for i in positions.0..self.lines.0 {
|
||||
self.raw[i].reset(&template);
|
||||
}
|
||||
}
|
||||
|
||||
// Completely reset the grid state
|
||||
/// Completely reset the grid state.
|
||||
pub fn reset(&mut self, template: &T) {
|
||||
self.clear_history();
|
||||
|
||||
// Reset all visible lines
|
||||
// Reset all visible lines.
|
||||
for row in 0..self.raw.len() {
|
||||
self.raw[row].reset(template);
|
||||
}
|
||||
|
@ -620,11 +620,11 @@ impl<T> Grid<T> {
|
|||
|
||||
#[inline]
|
||||
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());
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn len(&self) -> usize {
|
||||
self.raw.len()
|
||||
|
@ -635,20 +635,20 @@ impl<T> Grid<T> {
|
|||
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]
|
||||
pub fn initialize_all(&mut self, template: &T)
|
||||
where
|
||||
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();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// This is used only for truncating before saving ref-tests
|
||||
/// This is used only for truncating before saving ref-tests.
|
||||
#[inline]
|
||||
pub fn truncate(&mut self) {
|
||||
self.raw.truncate();
|
||||
|
@ -666,7 +666,7 @@ impl<T> Grid<T> {
|
|||
}
|
||||
|
||||
pub struct GridIterator<'a, T> {
|
||||
/// Immutable grid reference
|
||||
/// Immutable grid reference.
|
||||
grid: &'a Grid<T>,
|
||||
|
||||
/// 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> {
|
||||
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> {
|
||||
type Output = Row<T>;
|
||||
|
||||
|
@ -772,22 +772,18 @@ impl<'point, T> IndexMut<&'point Point> for Grid<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// REGIONS
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// A subset of lines in the grid
|
||||
/// A subset of lines in the grid.
|
||||
///
|
||||
/// May be constructed using Grid::region(..)
|
||||
/// May be constructed using Grid::region(..).
|
||||
pub struct Region<'a, T> {
|
||||
start: Line,
|
||||
end: Line,
|
||||
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> {
|
||||
start: Line,
|
||||
end: Line,
|
||||
|
@ -795,7 +791,7 @@ pub struct 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) {
|
||||
for row in self {
|
||||
for item in row {
|
||||
|
@ -806,10 +802,10 @@ impl<'a, T> RegionMut<'a, T> {
|
|||
}
|
||||
|
||||
pub trait IndexRegion<I, T> {
|
||||
/// Get an immutable region of Self
|
||||
/// Get an immutable region of Self.
|
||||
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>;
|
||||
}
|
||||
|
||||
|
@ -921,11 +917,7 @@ impl<'a, T> Iterator for RegionIterMut<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// DISPLAY ITERATOR
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
/// Iterates over the visible area accounting for buffer transform
|
||||
/// Iterates over the visible area accounting for buffer transform.
|
||||
pub struct DisplayIter<'a, T> {
|
||||
grid: &'a Grid<T>,
|
||||
offset: usize,
|
||||
|
@ -974,7 +966,7 @@ impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> {
|
|||
column: self.col,
|
||||
});
|
||||
|
||||
// Update line/col to point to next item
|
||||
// Update line/col to point to next item.
|
||||
self.col += 1;
|
||||
if self.col == self.grid.num_cols() && self.offset != self.limit {
|
||||
self.offset -= 1;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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::ops::{Index, IndexMut};
|
||||
|
@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::grid::GridCell;
|
||||
use crate::index::Column;
|
||||
|
||||
/// A row in the grid
|
||||
/// A row in the grid.
|
||||
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Row<T> {
|
||||
inner: Vec<T>,
|
||||
|
@ -67,7 +67,7 @@ impl<T: Copy> Row<T> {
|
|||
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 index = new_row.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0);
|
||||
new_row.truncate(index);
|
||||
|
@ -91,14 +91,13 @@ impl<T: Copy> Row<T> {
|
|||
|
||||
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();
|
||||
if !self.inner[len - 1].fast_eq(template) {
|
||||
self.occ = len;
|
||||
}
|
||||
|
||||
// Reset every dirty in the row
|
||||
// let template = *template;
|
||||
// Reset every dirty in the row.
|
||||
for item in &mut self.inner[..self.occ] {
|
||||
*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> {
|
||||
type Output = [T];
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ pub struct Storage<T> {
|
|||
|
||||
impl<T: PartialEq> PartialEq for Storage<T> {
|
||||
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!(other.zero, 0);
|
||||
|
||||
|
@ -66,7 +66,7 @@ impl<T> Storage<T> {
|
|||
where
|
||||
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];
|
||||
|
||||
Storage { inner, zero: 0, visible_lines, len: visible_lines.0 }
|
||||
|
@ -77,11 +77,11 @@ impl<T> Storage<T> {
|
|||
where
|
||||
T: Clone,
|
||||
{
|
||||
// Number of lines the buffer needs to grow
|
||||
// Number of lines the buffer needs to grow.
|
||||
let growage = next - self.visible_lines;
|
||||
self.grow_lines(growage.0, template_row);
|
||||
|
||||
// Update visible lines
|
||||
// Update visible lines.
|
||||
self.visible_lines = next;
|
||||
}
|
||||
|
||||
|
@ -90,43 +90,43 @@ impl<T> Storage<T> {
|
|||
where
|
||||
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;
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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];
|
||||
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);
|
||||
}
|
||||
|
||||
// Update raw buffer length and zero offset
|
||||
// Update raw buffer length and zero offset.
|
||||
self.zero += new_growage;
|
||||
self.len += growage;
|
||||
}
|
||||
|
||||
/// Decrease the number of lines in the buffer.
|
||||
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;
|
||||
self.shrink_lines(shrinkage.0);
|
||||
|
||||
// Update visible lines
|
||||
// Update visible lines.
|
||||
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) {
|
||||
self.len -= shrinkage;
|
||||
|
||||
// Free memory
|
||||
// Free memory.
|
||||
if self.inner.len() > self.len + MAX_CACHE_SIZE {
|
||||
self.truncate();
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ impl<T> Storage<T> {
|
|||
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;
|
||||
|
||||
// Copy 1 qword at a time
|
||||
// Copy 1 qword at a time.
|
||||
//
|
||||
// The optimizer unrolls this loop and vectorizes it.
|
||||
let mut tmp: usize;
|
||||
|
@ -360,7 +360,7 @@ mod tests {
|
|||
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:
|
||||
/// 0: 0 <- Zero
|
||||
|
@ -406,7 +406,7 @@ mod tests {
|
|||
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:
|
||||
/// 0: -
|
||||
|
@ -419,7 +419,7 @@ mod tests {
|
|||
/// 3: 1
|
||||
#[test]
|
||||
fn grow_before_zero() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'-'),
|
||||
|
@ -431,10 +431,10 @@ mod tests {
|
|||
len: 3,
|
||||
};
|
||||
|
||||
// Grow buffer
|
||||
// Grow buffer.
|
||||
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 {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'-'),
|
||||
|
@ -452,7 +452,7 @@ mod tests {
|
|||
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:
|
||||
/// 0: 2
|
||||
|
@ -464,7 +464,7 @@ mod tests {
|
|||
/// 1: 1
|
||||
#[test]
|
||||
fn shrink_before_zero() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'2'),
|
||||
|
@ -476,10 +476,10 @@ mod tests {
|
|||
len: 3,
|
||||
};
|
||||
|
||||
// Shrink buffer
|
||||
// Shrink buffer.
|
||||
storage.shrink_visible_lines(Line(2));
|
||||
|
||||
// Make sure the result is correct
|
||||
// Make sure the result is correct.
|
||||
let expected = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'2'),
|
||||
|
@ -496,7 +496,7 @@ mod tests {
|
|||
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:
|
||||
/// 0: 0 <- Zero
|
||||
|
@ -508,7 +508,7 @@ mod tests {
|
|||
/// 2: 2 <- Hidden
|
||||
#[test]
|
||||
fn shrink_after_zero() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'0'),
|
||||
|
@ -520,10 +520,10 @@ mod tests {
|
|||
len: 3,
|
||||
};
|
||||
|
||||
// Shrink buffer
|
||||
// Shrink buffer.
|
||||
storage.shrink_visible_lines(Line(2));
|
||||
|
||||
// Make sure the result is correct
|
||||
// Make sure the result is correct.
|
||||
let expected = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'0'),
|
||||
|
@ -540,7 +540,7 @@ mod tests {
|
|||
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:
|
||||
/// 0: 4
|
||||
|
@ -558,7 +558,7 @@ mod tests {
|
|||
/// 5: 3 <- Hidden
|
||||
#[test]
|
||||
fn shrink_before_and_after_zero() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'4'),
|
||||
|
@ -573,10 +573,10 @@ mod tests {
|
|||
len: 6,
|
||||
};
|
||||
|
||||
// Shrink buffer
|
||||
// Shrink buffer.
|
||||
storage.shrink_visible_lines(Line(2));
|
||||
|
||||
// Make sure the result is correct
|
||||
// Make sure the result is correct.
|
||||
let expected = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'4'),
|
||||
|
@ -596,7 +596,7 @@ mod tests {
|
|||
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:
|
||||
/// 0: 4 <- Hidden
|
||||
|
@ -610,7 +610,7 @@ mod tests {
|
|||
/// 1: 1
|
||||
#[test]
|
||||
fn truncate_invisible_lines() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'4'),
|
||||
|
@ -625,10 +625,10 @@ mod tests {
|
|||
len: 2,
|
||||
};
|
||||
|
||||
// Truncate buffer
|
||||
// Truncate buffer.
|
||||
storage.truncate();
|
||||
|
||||
// Make sure the result is correct
|
||||
// Make sure the result is correct.
|
||||
let expected = Storage {
|
||||
inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')],
|
||||
zero: 0,
|
||||
|
@ -641,7 +641,7 @@ mod tests {
|
|||
assert_eq!(storage.len, expected.len);
|
||||
}
|
||||
|
||||
/// Truncate buffer only at the beginning
|
||||
/// Truncate buffer only at the beginning.
|
||||
///
|
||||
/// Before:
|
||||
/// 0: 1
|
||||
|
@ -652,7 +652,7 @@ mod tests {
|
|||
/// 0: 0 <- Zero
|
||||
#[test]
|
||||
fn truncate_invisible_lines_beginning() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'1'),
|
||||
|
@ -664,10 +664,10 @@ mod tests {
|
|||
len: 2,
|
||||
};
|
||||
|
||||
// Truncate buffer
|
||||
// Truncate buffer.
|
||||
storage.truncate();
|
||||
|
||||
// Make sure the result is correct
|
||||
// Make sure the result is correct.
|
||||
let expected = Storage {
|
||||
inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')],
|
||||
zero: 0,
|
||||
|
@ -680,7 +680,7 @@ mod tests {
|
|||
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:
|
||||
/// 0: 4
|
||||
|
@ -706,7 +706,7 @@ mod tests {
|
|||
/// 6: 3
|
||||
#[test]
|
||||
fn shrink_then_grow() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'4'),
|
||||
|
@ -721,10 +721,10 @@ mod tests {
|
|||
len: 6,
|
||||
};
|
||||
|
||||
// Shrink buffer
|
||||
// Shrink buffer.
|
||||
storage.shrink_lines(3);
|
||||
|
||||
// Make sure the result after shrinking is correct
|
||||
// Make sure the result after shrinking is correct.
|
||||
let shrinking_expected = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'4'),
|
||||
|
@ -742,10 +742,10 @@ mod tests {
|
|||
assert_eq!(storage.zero, shrinking_expected.zero);
|
||||
assert_eq!(storage.len, shrinking_expected.len);
|
||||
|
||||
// Grow buffer
|
||||
// Grow buffer.
|
||||
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 {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'4'),
|
||||
|
@ -767,7 +767,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn initialize() {
|
||||
// Setup storage area
|
||||
// Setup storage area.
|
||||
let mut storage = Storage {
|
||||
inner: vec![
|
||||
Row::new(Column(1), &'4'),
|
||||
|
@ -782,11 +782,11 @@ mod tests {
|
|||
len: 6,
|
||||
};
|
||||
|
||||
// Initialize additional lines
|
||||
// Initialize additional lines.
|
||||
let init_size = 3;
|
||||
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 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
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for the Grid
|
||||
//! Tests for the Grid.
|
||||
|
||||
use super::{BidirectionalIterator, Grid};
|
||||
use crate::grid::GridCell;
|
||||
|
@ -71,7 +71,7 @@ fn visible_to_buffer() {
|
|||
assert_eq!(point, Point::new(4, Column(3)));
|
||||
}
|
||||
|
||||
// Scroll up moves lines upwards
|
||||
// Scroll up moves lines upwards.
|
||||
#[test]
|
||||
fn scroll_up() {
|
||||
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(7)][Column(0)], 9);
|
||||
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(9)][Column(0)], 0); // was 1
|
||||
assert_eq!(grid[Line(9)][Column(0)], 0); // was 1.
|
||||
assert_eq!(grid[Line(9)].occ, 0);
|
||||
}
|
||||
|
||||
// Scroll down moves lines downwards
|
||||
// Scroll down moves lines downwards.
|
||||
#[test]
|
||||
fn scroll_down() {
|
||||
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);
|
||||
|
||||
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(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(2)][Column(0)], 0);
|
||||
assert_eq!(grid[Line(2)].occ, 1);
|
||||
|
@ -135,7 +135,7 @@ fn scroll_down() {
|
|||
assert_eq!(grid[Line(9)].occ, 1);
|
||||
}
|
||||
|
||||
// Test that GridIterator works
|
||||
// Test that GridIterator works.
|
||||
#[test]
|
||||
fn test_iter() {
|
||||
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(&4), iter.next());
|
||||
|
||||
// test linewrapping
|
||||
// Test line-wrapping.
|
||||
assert_eq!(Some(&5), iter.next());
|
||||
assert_eq!(Column(0), iter.point().col);
|
||||
assert_eq!(3, iter.point().line);
|
||||
|
@ -165,10 +165,10 @@ fn test_iter() {
|
|||
assert_eq!(Column(4), iter.point().col);
|
||||
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());
|
||||
|
||||
// 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) });
|
||||
assert_eq!(None, final_iter.next());
|
||||
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(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][Column(0)], 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(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
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for r in 0..2 {
|
||||
|
|
|
@ -12,9 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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::fmt;
|
||||
use std::ops::{self, Add, AddAssign, Deref, Range, Sub, SubAssign};
|
||||
|
@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::term::RenderableCell;
|
||||
|
||||
/// The side of a cell
|
||||
/// The side of a cell.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Side {
|
||||
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)]
|
||||
pub struct Point<L = Line> {
|
||||
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)]
|
||||
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)]
|
||||
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)]
|
||||
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 {
|
||||
($ty:ty, $target:ty) => {
|
||||
impl Deref for $ty {
|
||||
|
@ -308,7 +308,7 @@ macro_rules! sub {
|
|||
/// This exists because we can't implement Iterator on Range
|
||||
/// and the existing impl needs the unstable Step trait
|
||||
/// 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>);
|
||||
|
||||
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> {
|
||||
if by == $construct(0) { return None; }
|
||||
if start < end {
|
||||
// Note: We assume $t <= usize here
|
||||
// Note: We assume $t <= usize here.
|
||||
let diff = (end - start).0;
|
||||
let by = by.0;
|
||||
if diff % by > 0 {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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)]
|
||||
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
|
||||
#![cfg_attr(all(test, feature = "bench"), feature(test))]
|
||||
|
|
|
@ -31,7 +31,7 @@ pub fn set_locale_environment() {
|
|||
if !env_locale_ptr.is_null() {
|
||||
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" {
|
||||
debug!("Using environment locale: {}", env_locale);
|
||||
return;
|
||||
|
@ -40,13 +40,13 @@ pub fn set_locale_environment() {
|
|||
|
||||
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 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() {
|
||||
// Use fallback locale
|
||||
// Use fallback locale.
|
||||
debug!("Using fallback locale: {}", FALLBACK_LOCALE);
|
||||
|
||||
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);
|
||||
} else {
|
||||
// Use system locale
|
||||
// Use system locale.
|
||||
debug!("Using system locale: {}", 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
|
||||
// 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+):
|
||||
//
|
||||
// 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/1643026-languagecode?language=objc
|
||||
|
|
|
@ -42,18 +42,18 @@ impl Message {
|
|||
let max_lines = size_info.lines().saturating_sub(MIN_FREE_LINES);
|
||||
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 line = String::new();
|
||||
for c in self.text.trim().chars() {
|
||||
if c == '\n'
|
||||
|| line.len() == num_cols
|
||||
// Keep space in first line for button
|
||||
// Keep space in first line for button.
|
||||
|| (lines.is_empty()
|
||||
&& num_cols >= button_len
|
||||
&& 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') {
|
||||
let split = line.split_off(index + 1);
|
||||
line.pop();
|
||||
|
@ -71,7 +71,7 @@ impl Message {
|
|||
}
|
||||
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 {
|
||||
lines.truncate(max_lines);
|
||||
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 let Some(line) = lines.get_mut(0) {
|
||||
line.truncate(num_cols - button_len);
|
||||
|
@ -146,10 +146,10 @@ impl MessageBuffer {
|
|||
/// Remove the currently visible message.
|
||||
#[inline]
|
||||
pub fn pop(&mut self) {
|
||||
// Remove the message itself
|
||||
// Remove the message itself.
|
||||
let msg = self.messages.pop_front();
|
||||
|
||||
// Remove all duplicates
|
||||
// Remove all duplicates.
|
||||
if let Some(msg) = msg {
|
||||
self.messages = self.messages.drain(..).filter(|m| m != &msg).collect();
|
||||
}
|
||||
|
@ -373,7 +373,7 @@ mod tests {
|
|||
|
||||
message_buffer.remove_target("target");
|
||||
|
||||
// Count number of messages
|
||||
// Count number of messages.
|
||||
let mut num_messages = 0;
|
||||
while message_buffer.message().is_some() {
|
||||
num_messages += 1;
|
||||
|
@ -435,7 +435,7 @@ mod tests {
|
|||
|
||||
message_buffer.pop();
|
||||
|
||||
// Count number of messages
|
||||
// Count number of messages.
|
||||
let mut num_messages = 0;
|
||||
while message_buffer.message().is_some() {
|
||||
num_messages += 1;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! Rendering time meter
|
||||
//! Rendering time meter.
|
||||
//!
|
||||
//! Used to track rendering times and provide moving averages.
|
||||
//!
|
||||
|
@ -36,27 +36,27 @@ use std::time::{Duration, Instant};
|
|||
|
||||
const NUM_SAMPLES: usize = 10;
|
||||
|
||||
/// The meter
|
||||
/// The meter.
|
||||
#[derive(Default)]
|
||||
pub struct Meter {
|
||||
/// Track last 60 timestamps
|
||||
/// Track last 60 timestamps.
|
||||
times: [f64; NUM_SAMPLES],
|
||||
|
||||
/// Average sample time in microseconds
|
||||
/// Average sample time in microseconds.
|
||||
avg: f64,
|
||||
|
||||
/// Index of next time to update.
|
||||
/// Index of next time to update..
|
||||
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> {
|
||||
/// Reference to meter that created the sampler
|
||||
/// Reference to meter that created the sampler.
|
||||
meter: &'a mut Meter,
|
||||
|
||||
// When the sampler was created
|
||||
/// When the sampler was created.
|
||||
created_at: Instant,
|
||||
}
|
||||
|
||||
|
@ -78,22 +78,22 @@ impl<'a> Drop for Sampler<'a> {
|
|||
}
|
||||
|
||||
impl Meter {
|
||||
/// Create a meter
|
||||
/// Create a meter.
|
||||
pub fn new() -> Meter {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Get a sampler
|
||||
/// Get a sampler.
|
||||
pub fn sampler(&mut self) -> Sampler<'_> {
|
||||
Sampler::new(self)
|
||||
}
|
||||
|
||||
/// Get the current average sample duration in microseconds
|
||||
/// Get the current average sample duration in microseconds.
|
||||
pub fn average(&self) -> f64 {
|
||||
self.avg
|
||||
}
|
||||
|
||||
/// Add a sample
|
||||
/// Add a sample.
|
||||
///
|
||||
/// Used by Sampler::drop.
|
||||
fn add_sample(&mut self, sample: Duration) {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! ANSI Terminal Stream Parsing
|
||||
//! ANSI Terminal Stream Parsing.
|
||||
#[cfg(windows)]
|
||||
use crate::tty::windows::win32_string;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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
|
||||
//! finalized when the button is released. The selection should be cleared
|
||||
|
@ -117,7 +117,7 @@ impl Selection {
|
|||
scrolling_region: &Range<Line>,
|
||||
offset: isize,
|
||||
) -> 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_end = num_lines - scrolling_region.end.0;
|
||||
|
||||
|
@ -126,18 +126,18 @@ impl Selection {
|
|||
mem::swap(&mut start, &mut end);
|
||||
}
|
||||
|
||||
// Rotate start of selection
|
||||
// Rotate start of selection.
|
||||
if (start.point.line < region_start || region_start == num_lines)
|
||||
&& start.point.line >= region_end
|
||||
{
|
||||
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 {
|
||||
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 self.ty != SelectionType::Block {
|
||||
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)
|
||||
&& end.point.line >= region_end
|
||||
{
|
||||
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 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Clamp selection to end of region
|
||||
// Clamp selection to end of region.
|
||||
if end.point.line < region_end {
|
||||
if self.ty != SelectionType::Block {
|
||||
end.point.col = Column(num_cols - 1);
|
||||
|
@ -180,7 +180,7 @@ impl Selection {
|
|||
}
|
||||
|
||||
// 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.side == Side::Right
|
||||
&& end.side == Side::Left
|
||||
|
@ -228,13 +228,13 @@ impl Selection {
|
|||
let grid = term.grid();
|
||||
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);
|
||||
if Self::points_need_swap(start.point, end.point) {
|
||||
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 (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 {
|
||||
start.line < end.line || start.line == end.line && start.col > end.col
|
||||
}
|
||||
|
@ -258,14 +258,14 @@ impl Selection {
|
|||
is_block: bool,
|
||||
lines: usize,
|
||||
) -> Result<(Anchor, Anchor), ()> {
|
||||
// Clamp selection inside of grid to prevent OOB
|
||||
// Clamp selection inside of grid to prevent OOB.
|
||||
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 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// Clamp to grid if it is still partially visible
|
||||
// Clamp to grid if it is still partially visible.
|
||||
if !is_block {
|
||||
start.side = Side::Left;
|
||||
start.point.col = Column(0);
|
||||
|
@ -322,9 +322,9 @@ impl Selection {
|
|||
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 {
|
||||
// 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) {
|
||||
end.point.col = num_cols - 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 {
|
||||
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 {
|
||||
start.point = Point::new(start.point.line.saturating_sub(1), Column(0));
|
||||
}
|
||||
|
@ -351,18 +351,18 @@ impl Selection {
|
|||
return None;
|
||||
}
|
||||
|
||||
// Always go top-left -> bottom-right
|
||||
// Always go top-left -> bottom-right.
|
||||
if start.point.col > end.point.col {
|
||||
mem::swap(&mut start.side, &mut end.side);
|
||||
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 {
|
||||
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 {
|
||||
start.point.col += 1;
|
||||
}
|
||||
|
|
|
@ -12,32 +12,32 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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};
|
||||
|
||||
/// A fair mutex
|
||||
/// A fair mutex.
|
||||
///
|
||||
/// 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.
|
||||
pub struct FairMutex<T> {
|
||||
/// Data
|
||||
/// Data.
|
||||
data: Mutex<T>,
|
||||
/// Next-to-access
|
||||
/// Next-to-access.
|
||||
next: Mutex<()>,
|
||||
}
|
||||
|
||||
impl<T> FairMutex<T> {
|
||||
/// Create a new fair mutex
|
||||
/// Create a new fair mutex.
|
||||
pub fn new(data: T) -> FairMutex<T> {
|
||||
FairMutex { data: Mutex::new(data), next: Mutex::new(()) }
|
||||
}
|
||||
|
||||
/// Lock the mutex
|
||||
/// Lock the mutex.
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
// Must bind to a temporary or the lock will be freed before going
|
||||
// into data.lock()
|
||||
// into data.lock().
|
||||
let _next = self.next.lock();
|
||||
self.data.lock()
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use crate::ansi::{Color, NamedColor};
|
|||
use crate::grid::{self, GridCell};
|
||||
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;
|
||||
|
||||
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 {
|
||||
/// Calculate the occupied line length
|
||||
/// Calculate the occupied line length.
|
||||
fn line_length(&self) -> Column;
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ impl Cell {
|
|||
|
||||
#[inline]
|
||||
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() };
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ pub struct Rgb {
|
|||
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 {
|
||||
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
|
||||
/// serialize/deserialize impl for ref tests.
|
||||
|
@ -52,7 +52,7 @@ impl<'de> Deserialize<'de> for Rgb {
|
|||
{
|
||||
struct RgbVisitor;
|
||||
|
||||
// Used for deserializing reftests
|
||||
// Used for deserializing reftests.
|
||||
#[derive(Deserialize)]
|
||||
struct RgbDerivedDeser {
|
||||
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)?;
|
||||
|
||||
// Attempt to deserialize from struct form
|
||||
// Attempt to deserialize from struct form.
|
||||
if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) {
|
||||
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) {
|
||||
Ok(rgb) => Ok(rgb),
|
||||
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 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 {
|
||||
fn from(colors: &Colors) -> List {
|
||||
// Type inference fails without this annotation
|
||||
// Type inference fails without this annotation.
|
||||
let mut list = List([Rgb::default(); COUNT]);
|
||||
|
||||
list.fill_named(colors);
|
||||
|
@ -153,7 +153,7 @@ impl<'a> From<&'a Colors> for List {
|
|||
|
||||
impl List {
|
||||
pub fn fill_named(&mut self, colors: &Colors) {
|
||||
// Normals
|
||||
// Normals.
|
||||
self[ansi::NamedColor::Black] = colors.normal().black;
|
||||
self[ansi::NamedColor::Red] = colors.normal().red;
|
||||
self[ansi::NamedColor::Green] = colors.normal().green;
|
||||
|
@ -163,7 +163,7 @@ impl List {
|
|||
self[ansi::NamedColor::Cyan] = colors.normal().cyan;
|
||||
self[ansi::NamedColor::White] = colors.normal().white;
|
||||
|
||||
// Brights
|
||||
// Brights.
|
||||
self[ansi::NamedColor::BrightBlack] = colors.bright().black;
|
||||
self[ansi::NamedColor::BrightRed] = colors.bright().red;
|
||||
self[ansi::NamedColor::BrightGreen] = colors.bright().green;
|
||||
|
@ -175,14 +175,14 @@ impl List {
|
|||
self[ansi::NamedColor::BrightForeground] =
|
||||
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::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);
|
||||
|
||||
// Dims
|
||||
// Dims.
|
||||
self[ansi::NamedColor::DimForeground] =
|
||||
colors.primary.dim_foreground.unwrap_or(colors.primary.foreground * DIM_FACTOR);
|
||||
match colors.dim {
|
||||
|
@ -213,11 +213,11 @@ impl List {
|
|||
|
||||
pub fn fill_cube(&mut self, colors: &Colors) {
|
||||
let mut index: usize = 16;
|
||||
// Build colors
|
||||
// Build colors.
|
||||
for r in 0..6 {
|
||||
for g 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) =
|
||||
colors.indexed_colors.iter().find(|ic| ic.index == index as u8)
|
||||
{
|
||||
|
@ -241,10 +241,10 @@ impl List {
|
|||
let mut index: usize = 232;
|
||||
|
||||
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;
|
||||
|
||||
// Override colors 232..256 with the config (if present)
|
||||
// Override colors 232..256 with the config (if present).
|
||||
if let Some(indexed_color) =
|
||||
colors.indexed_colors.iter().find(|ic| ic.index == color_index)
|
||||
{
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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::ops::{Index, IndexMut, Range};
|
||||
use std::time::{Duration, Instant};
|
||||
|
@ -49,7 +49,7 @@ const TITLE_STACK_MAX_DEPTH: usize = 4096;
|
|||
/// Default tab interval, corresponding to terminfo `it` value.
|
||||
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
|
||||
/// points are two dimensional indices.
|
||||
|
@ -68,7 +68,7 @@ pub trait Search {
|
|||
|
||||
impl<T> Search for Term<T> {
|
||||
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);
|
||||
|
||||
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) {
|
||||
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();
|
||||
|
@ -92,7 +93,7 @@ impl<T> Search for Term<T> {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
let mut iter = self.grid.iter_from(point);
|
||||
|
@ -108,7 +109,8 @@ impl<T> Search for Term<T> {
|
|||
point = iter.point();
|
||||
|
||||
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>> {
|
||||
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)| {
|
||||
if open == &start_char {
|
||||
Some((true, *close))
|
||||
|
@ -158,16 +160,16 @@ impl<T> Search for Term<T> {
|
|||
let mut skip_pairs = 0;
|
||||
|
||||
loop {
|
||||
// Check the next cell
|
||||
// Check the next cell.
|
||||
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 {
|
||||
Some(cell) => cell.c,
|
||||
None => break,
|
||||
};
|
||||
|
||||
// Check if the bracket matches
|
||||
// Check if the bracket matches.
|
||||
if c == end_char && skip_pairs == 0 {
|
||||
return Some(iter.point());
|
||||
} else if c == start_char {
|
||||
|
@ -198,7 +200,7 @@ pub struct CursorKey {
|
|||
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
|
||||
/// 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> {
|
||||
/// Create the renderable cells iterator
|
||||
/// Create the renderable cells iterator.
|
||||
///
|
||||
/// The cursor and terminal mode are required for properly displaying the
|
||||
/// cursor.
|
||||
|
@ -236,18 +238,18 @@ impl<'a, C> RenderableCellsIter<'a, C> {
|
|||
(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_end = viewport_start + grid.num_lines().0;
|
||||
if span.end.line >= viewport_end || span.start.line < viewport_start {
|
||||
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 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);
|
||||
end.col = min(limit_end, end.col);
|
||||
|
||||
|
@ -271,7 +273,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
|
|||
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
|
||||
&& self.cursor.point == point
|
||||
&& (selection.start == point
|
||||
|
@ -283,7 +285,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Point itself is selected
|
||||
// Point itself is selected.
|
||||
if selection.contains(point.col, point.line) {
|
||||
return true;
|
||||
}
|
||||
|
@ -291,27 +293,27 @@ impl<'a, C> RenderableCellsIter<'a, C> {
|
|||
let num_cols = self.grid.num_cols().0;
|
||||
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) {
|
||||
let prevprev = point.sub(num_cols, 2);
|
||||
let prev = point.sub(num_cols, 1);
|
||||
let next = point.add(num_cols, 1);
|
||||
|
||||
// Check trailing spacer
|
||||
// Check trailing spacer.
|
||||
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[&prevprev].flags.contains(Flags::WIDE_CHAR)
|
||||
&& selection.contains(prev.col, prev.line))
|
||||
} 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);
|
||||
|
||||
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)
|
||||
} 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))
|
||||
}
|
||||
} else {
|
||||
|
@ -328,7 +330,7 @@ pub enum RenderableCellContent {
|
|||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct RenderableCell {
|
||||
/// A _Display_ line (not necessarily an _Active_ line)
|
||||
/// A _Display_ line (not necessarily an _Active_ line).
|
||||
pub line: Line,
|
||||
pub column: Column,
|
||||
pub inner: RenderableCellContent,
|
||||
|
@ -345,30 +347,30 @@ impl RenderableCell {
|
|||
cell: Indexed<Cell>,
|
||||
selected: bool,
|
||||
) -> Self {
|
||||
// Lookup RGB values
|
||||
// Lookup RGB values.
|
||||
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_alpha = Self::compute_bg_alpha(cell.bg);
|
||||
|
||||
let selection_background = config.colors.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_alpha = 1.0;
|
||||
} else if selected ^ cell.inverse() {
|
||||
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];
|
||||
bg_rgb = colors[NamedColor::Foreground];
|
||||
} else {
|
||||
// Invert cell fg and bg colors
|
||||
// Invert cell fg and bg colors.
|
||||
mem::swap(&mut fg_rgb, &mut bg_rgb);
|
||||
}
|
||||
|
||||
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) {
|
||||
fg_rgb = col;
|
||||
}
|
||||
|
@ -389,7 +391,7 @@ impl RenderableCell {
|
|||
Color::Spec(rgb) => rgb,
|
||||
Color::Named(ansi) => {
|
||||
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)
|
||||
if ansi == NamedColor::Foreground
|
||||
&& config.colors.primary.bright_foreground.is_none() =>
|
||||
|
@ -398,9 +400,9 @@ impl RenderableCell {
|
|||
},
|
||||
// Draw bold text in bright colors *and* contains bold flag.
|
||||
(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()],
|
||||
// None of the above, keep original color.
|
||||
// None of the above, keep original color..
|
||||
_ => colors[ansi],
|
||||
}
|
||||
},
|
||||
|
@ -443,7 +445,7 @@ impl RenderableCell {
|
|||
impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
|
||||
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
|
||||
/// (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);
|
||||
|
||||
// Handle cell below cursor
|
||||
// Handle cell below cursor.
|
||||
if self.cursor.rendered {
|
||||
let mut cell =
|
||||
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);
|
||||
} else {
|
||||
// Handle cursor
|
||||
// Handle cursor.
|
||||
self.cursor.rendered = true;
|
||||
|
||||
let buffer_point = self.grid.visible_to_buffer(self.cursor.point);
|
||||
|
@ -611,24 +613,24 @@ impl IndexMut<CharsetIndex> for Charsets {
|
|||
|
||||
#[derive(Default, Copy, Clone)]
|
||||
pub struct Cursor {
|
||||
/// The location of this cursor
|
||||
/// The location of this cursor.
|
||||
pub point: Point,
|
||||
|
||||
/// Template cell when using this cursor
|
||||
/// Template cell when using this cursor.
|
||||
template: Cell,
|
||||
|
||||
/// Currently configured graphic character sets
|
||||
/// Currently configured graphic character sets.
|
||||
charsets: Charsets,
|
||||
}
|
||||
|
||||
pub struct VisualBell {
|
||||
/// Visual bell animation
|
||||
/// Visual bell animation.
|
||||
animation: VisualBellAnimation,
|
||||
|
||||
/// Visual bell duration
|
||||
/// Visual bell 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>,
|
||||
}
|
||||
|
||||
|
@ -763,7 +765,7 @@ pub struct SizeInfo {
|
|||
/// Horizontal window padding.
|
||||
pub padding_y: f32,
|
||||
|
||||
/// DPI factor of the current window.
|
||||
/// DPR of the current window.
|
||||
#[serde(default)]
|
||||
pub dpr: f64,
|
||||
}
|
||||
|
@ -870,7 +872,7 @@ pub struct Term<T> {
|
|||
/// Style of the vi mode cursor.
|
||||
vi_mode_cursor_style: Option<CursorStyle>,
|
||||
|
||||
/// Clipboard access coupled to the active window
|
||||
/// Clipboard access coupled to the active window.
|
||||
clipboard: Clipboard,
|
||||
|
||||
/// 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() {
|
||||
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 {
|
||||
res += "\n";
|
||||
}
|
||||
|
@ -1046,7 +1048,7 @@ impl<T> Term<T> {
|
|||
let grid_line = &self.grid[line];
|
||||
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) {
|
||||
cols.start -= 1;
|
||||
}
|
||||
|
@ -1055,7 +1057,7 @@ impl<T> Term<T> {
|
|||
for col in IndexRange::from(cols.start..line_length) {
|
||||
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 self.tabs[col] {
|
||||
tab_mode = false;
|
||||
|
@ -1069,10 +1071,10 @@ impl<T> Term<T> {
|
|||
}
|
||||
|
||||
if !cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
|
||||
// Push cells primary character
|
||||
// Push cells primary character.
|
||||
text.push(cell.c);
|
||||
|
||||
// Push zero-width characters
|
||||
// Push zero-width characters.
|
||||
for c in (&cell.chars()[1..]).iter().take_while(|c| **c != ' ') {
|
||||
text.push(*c);
|
||||
}
|
||||
|
@ -1086,7 +1088,7 @@ impl<T> Term<T> {
|
|||
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()
|
||||
&& line_length.0 >= 2
|
||||
&& 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)
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// serializes the grid state to a file.
|
||||
|
@ -1111,13 +1113,13 @@ impl<T> Term<T> {
|
|||
&self.grid
|
||||
}
|
||||
|
||||
/// Mutable access for swapping out the grid during tests
|
||||
/// Mutable access for swapping out the grid during tests.
|
||||
#[cfg(test)]
|
||||
pub fn grid_mut(&mut self) -> &mut Grid<Cell> {
|
||||
&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
|
||||
/// background color. Cells with an alternate background color are
|
||||
|
@ -1128,7 +1130,7 @@ impl<T> Term<T> {
|
|||
RenderableCellsIter::new(&self, config, selection)
|
||||
}
|
||||
|
||||
/// Resize terminal to new dimensions
|
||||
/// Resize terminal to new dimensions.
|
||||
pub fn resize(&mut self, size: &SizeInfo) {
|
||||
let old_cols = self.grid.num_cols();
|
||||
let old_lines = self.grid.num_lines();
|
||||
|
@ -1159,11 +1161,11 @@ impl<T> Term<T> {
|
|||
let alt_cursor_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.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();
|
||||
|
||||
// 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.line = min(self.vi_mode_cursor.point.line, num_lines - 1);
|
||||
|
||||
// Recreate tabs list
|
||||
// Recreate tabs list.
|
||||
self.tabs.resize(self.grid.num_cols());
|
||||
}
|
||||
|
||||
|
@ -1201,7 +1203,7 @@ impl<T> Term<T> {
|
|||
mem::swap(&mut self.grid, &mut self.alt_grid);
|
||||
}
|
||||
|
||||
/// Scroll screen down
|
||||
/// Scroll screen down.
|
||||
///
|
||||
/// Text moves down; clear at bottom
|
||||
/// Expects origin to be in scroll range.
|
||||
|
@ -1225,7 +1227,7 @@ impl<T> Term<T> {
|
|||
trace!("Scrolling up relative: origin={}, lines={}", origin, lines);
|
||||
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() };
|
||||
self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &template);
|
||||
}
|
||||
|
@ -1234,11 +1236,11 @@ impl<T> Term<T> {
|
|||
where
|
||||
T: EventListener,
|
||||
{
|
||||
// Setting 132 column font makes no sense, but run the other side effects
|
||||
// Clear scrolling region
|
||||
// Setting 132 column font makes no sense, but run the other side effects.
|
||||
// Clear scrolling region.
|
||||
self.set_scrolling_region(1, self.grid.num_lines().0);
|
||||
|
||||
// Clear grid
|
||||
// Clear grid.
|
||||
let template = self.cursor.template;
|
||||
self.grid.region_mut(..).each(|c| c.reset(&template));
|
||||
}
|
||||
|
@ -1267,7 +1269,7 @@ impl<T> Term<T> {
|
|||
self.mode ^= TermMode::VI;
|
||||
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) {
|
||||
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));
|
||||
|
@ -1282,18 +1284,18 @@ impl<T> Term<T> {
|
|||
where
|
||||
T: EventListener,
|
||||
{
|
||||
// Require vi mode to be active
|
||||
// Require vi mode to be active.
|
||||
if !self.mode.contains(TermMode::VI) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move cursor
|
||||
// Move cursor.
|
||||
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);
|
||||
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() {
|
||||
selection.update(viewport_point, Side::Left);
|
||||
selection.include_all();
|
||||
|
@ -1348,7 +1350,7 @@ impl<T> Term<T> {
|
|||
fn renderable_cursor<C>(&self, config: &Config<C>) -> RenderableCursor {
|
||||
let vi_mode = self.mode.contains(TermMode::VI);
|
||||
|
||||
// Cursor position
|
||||
// Cursor position.
|
||||
let mut point = if vi_mode {
|
||||
self.vi_mode_cursor.point
|
||||
} else {
|
||||
|
@ -1357,7 +1359,7 @@ impl<T> Term<T> {
|
|||
point
|
||||
};
|
||||
|
||||
// Cursor shape
|
||||
// Cursor shape.
|
||||
let hidden = !self.mode.contains(TermMode::SHOW_CURSOR) || point.line >= self.lines();
|
||||
let cursor_style = if hidden && !vi_mode {
|
||||
point.line = Line(0);
|
||||
|
@ -1374,7 +1376,7 @@ impl<T> Term<T> {
|
|||
}
|
||||
};
|
||||
|
||||
// Cursor colors
|
||||
// Cursor colors.
|
||||
let (text_color, cursor_color) = if vi_mode {
|
||||
(config.vi_mode_cursor_text_color(), config.vi_mode_cursor_cursor_color())
|
||||
} else {
|
||||
|
@ -1382,7 +1384,7 @@ impl<T> Term<T> {
|
|||
(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 cell = self.grid[buffer_point.line][buffer_point.col];
|
||||
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> {
|
||||
/// A character to be displayed
|
||||
/// A character to be displayed.
|
||||
#[inline]
|
||||
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() {
|
||||
Some(width) => width,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Handle zero-width characters
|
||||
// Handle zero-width characters.
|
||||
if width == 0 {
|
||||
let mut col = self.cursor.point.col.0.saturating_sub(1);
|
||||
let line = self.cursor.point.line;
|
||||
|
@ -1437,14 +1439,14 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Move cursor to next line
|
||||
// Move cursor to next line.
|
||||
if self.input_needs_wrap {
|
||||
self.wrapline();
|
||||
}
|
||||
|
||||
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 {
|
||||
let line = self.cursor.point.line;
|
||||
let col = self.cursor.point.col;
|
||||
|
@ -1460,16 +1462,16 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
if width == 1 {
|
||||
self.write_at_cursor(c);
|
||||
} 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 {
|
||||
self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
|
||||
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);
|
||||
|
||||
// Write spacer to cell following the wide glyph
|
||||
// Write spacer to cell following the wide glyph.
|
||||
self.cursor.point.col += 1;
|
||||
self.write_at_cursor(' ').flags.insert(Flags::WIDE_CHAR_SPACER);
|
||||
}
|
||||
|
@ -1517,7 +1519,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
|
||||
#[inline]
|
||||
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);
|
||||
|
||||
|
@ -1608,7 +1610,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
/// Insert tab at cursor position.
|
||||
#[inline]
|
||||
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 {
|
||||
self.wrapline();
|
||||
return;
|
||||
|
@ -1636,7 +1638,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Backspace `count` characters
|
||||
/// Backspace `count` characters.
|
||||
#[inline]
|
||||
fn backspace(&mut self) {
|
||||
trace!("Backspace");
|
||||
|
@ -1646,7 +1648,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Carriage return
|
||||
/// Carriage return.
|
||||
#[inline]
|
||||
fn carriage_return(&mut self) {
|
||||
trace!("Carriage return");
|
||||
|
@ -1654,7 +1656,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
self.input_needs_wrap = false;
|
||||
}
|
||||
|
||||
/// Linefeed
|
||||
/// Linefeed.
|
||||
#[inline]
|
||||
fn linefeed(&mut self) {
|
||||
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]
|
||||
fn bell(&mut self) {
|
||||
trace!("Bell");
|
||||
|
@ -1679,7 +1681,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
trace!("[unimplemented] Substitute");
|
||||
}
|
||||
|
||||
/// Run LF/NL
|
||||
/// Run LF/NL.
|
||||
///
|
||||
/// LF/NL mode has some interesting history. According to ECMA-48 4th
|
||||
/// 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 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] {
|
||||
c.reset(&self.cursor.template);
|
||||
}
|
||||
|
@ -1767,7 +1769,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
fn delete_chars(&mut self, count: Column) {
|
||||
let cols = self.grid.num_cols();
|
||||
|
||||
// Ensure deleting within terminal bounds
|
||||
// Ensure deleting within terminal bounds.
|
||||
let count = min(count, cols);
|
||||
|
||||
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]
|
||||
fn set_color(&mut self, index: usize, color: Rgb) {
|
||||
trace!("Setting color[{}] = {:?}", index, color);
|
||||
|
@ -1866,7 +1868,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
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]
|
||||
fn dynamic_color_sequence<W: io::Write>(
|
||||
&mut self,
|
||||
|
@ -1884,7 +1886,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
let _ = writer.write_all(response.as_bytes());
|
||||
}
|
||||
|
||||
/// Reset the indexed color to original value
|
||||
/// Reset the indexed color to original value.
|
||||
#[inline]
|
||||
fn reset_color(&mut self, index: usize) {
|
||||
trace!("Resetting color[{}]", index);
|
||||
|
@ -1892,7 +1894,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
self.color_modified[index] = false;
|
||||
}
|
||||
|
||||
/// Set the clipboard
|
||||
/// Set the clipboard.
|
||||
#[inline]
|
||||
fn set_clipboard(&mut self, clipboard: u8, base64: &[u8]) {
|
||||
let clipboard_type = match clipboard {
|
||||
|
@ -1928,19 +1930,19 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
trace!("Clearing screen: {:?}", mode);
|
||||
let template = self.cursor.template;
|
||||
|
||||
// Remove active selections
|
||||
// Remove active selections.
|
||||
self.grid.selection = None;
|
||||
|
||||
match mode {
|
||||
ansi::ClearMode::Above => {
|
||||
// If clearing more than one line
|
||||
// If clearing more than one line.
|
||||
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
|
||||
.region_mut(..self.cursor.point.line)
|
||||
.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());
|
||||
for cell in &mut self.grid[self.cursor.point.line][..end] {
|
||||
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]
|
||||
fn reset_state(&mut self) {
|
||||
if self.alt {
|
||||
|
@ -2008,7 +2010,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
#[inline]
|
||||
fn reverse_index(&mut self) {
|
||||
trace!("Reversing index");
|
||||
// if cursor is at the top
|
||||
// If cursor is at the top.
|
||||
if self.cursor.point.line == self.scroll_region.start {
|
||||
self.scroll_down(Line(1));
|
||||
} else {
|
||||
|
@ -2016,7 +2018,7 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// set a terminal attribute
|
||||
/// Set a terminal attribute.
|
||||
#[inline]
|
||||
fn terminal_attribute(&mut self, attr: 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::CursorKeys => self.mode.insert(TermMode::APP_CURSOR),
|
||||
// Mouse protocols are mutually exlusive
|
||||
// Mouse protocols are mutually exclusive.
|
||||
ansi::Mode::ReportMouseClicks => {
|
||||
self.mode.remove(TermMode::MOUSE_MODE);
|
||||
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::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE),
|
||||
// Mouse encodings are mutually exlusive
|
||||
// Mouse encodings are mutually exclusive.
|
||||
ansi::Mode::SgrMouse => {
|
||||
self.mode.remove(TermMode::UTF8_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::Origin => self.mode.insert(TermMode::ORIGIN),
|
||||
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 => {
|
||||
trace!("... unimplemented mode");
|
||||
},
|
||||
|
@ -2415,7 +2417,7 @@ mod tests {
|
|||
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
|
||||
/// 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);
|
||||
|
||||
// Add one line of scrollback
|
||||
// Add one line of scrollback.
|
||||
term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default());
|
||||
|
||||
// Clear the history
|
||||
// Clear the history.
|
||||
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();
|
||||
scrolled_grid.scroll_display(Scroll::Top);
|
||||
|
||||
// Truncate grids for comparison
|
||||
// Truncate grids for comparison.
|
||||
scrolled_grid.truncate();
|
||||
term.grid.truncate();
|
||||
|
||||
|
@ -2492,14 +2494,14 @@ mod tests {
|
|||
};
|
||||
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 {
|
||||
term.newline();
|
||||
}
|
||||
assert_eq!(term.grid.history_size(), 10);
|
||||
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
|
||||
|
||||
// Increase visible lines
|
||||
// Increase visible lines.
|
||||
size.height = 30.;
|
||||
term.resize(&size);
|
||||
|
||||
|
@ -2520,21 +2522,21 @@ mod tests {
|
|||
};
|
||||
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 {
|
||||
term.newline();
|
||||
}
|
||||
assert_eq!(term.grid.history_size(), 10);
|
||||
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
|
||||
|
||||
// Enter alt screen
|
||||
// Enter alt screen.
|
||||
term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
|
||||
|
||||
// Increase visible lines
|
||||
// Increase visible lines.
|
||||
size.height = 30.;
|
||||
term.resize(&size);
|
||||
|
||||
// Leave alt screen
|
||||
// Leave alt screen.
|
||||
term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
|
||||
|
||||
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);
|
||||
|
||||
// Create 10 lines of scrollback
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..19 {
|
||||
term.newline();
|
||||
}
|
||||
assert_eq!(term.grid.history_size(), 10);
|
||||
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
|
||||
|
||||
// Increase visible lines
|
||||
// Increase visible lines.
|
||||
size.height = 5.;
|
||||
term.resize(&size);
|
||||
|
||||
|
@ -2582,21 +2584,21 @@ mod tests {
|
|||
};
|
||||
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 {
|
||||
term.newline();
|
||||
}
|
||||
assert_eq!(term.grid.history_size(), 10);
|
||||
assert_eq!(term.cursor.point, Point::new(Line(9), Column(0)));
|
||||
|
||||
// Enter alt screen
|
||||
// Enter alt screen.
|
||||
term.set_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
|
||||
|
||||
// Increase visible lines
|
||||
// Increase visible lines.
|
||||
size.height = 5.;
|
||||
term.resize(&size);
|
||||
|
||||
// Leave alt screen
|
||||
// Leave alt screen.
|
||||
term.unset_mode(ansi::Mode::SwapScreenAndSetRestoreCursor);
|
||||
|
||||
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);
|
||||
|
||||
// Title None by default
|
||||
// Title None by default.
|
||||
assert_eq!(term.title, None);
|
||||
|
||||
// Title can be set
|
||||
// Title can be set.
|
||||
term.set_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.set_title(Some("Next".into()));
|
||||
assert_eq!(term.title, Some("Next".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();
|
||||
assert_eq!(term.title, Some("Test".into()));
|
||||
assert!(term.title_stack.is_empty());
|
||||
|
||||
// Title stack doesn't grow infinitely
|
||||
// Title stack doesn't grow infinitely.
|
||||
for _ in 0..4097 {
|
||||
term.push_title();
|
||||
}
|
||||
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.reset_state();
|
||||
assert_eq!(term.title, None);
|
||||
assert!(term.title_stack.is_empty());
|
||||
|
||||
// Title stack pops back to default
|
||||
// Title stack pops back to default.
|
||||
term.title = None;
|
||||
term.push_title();
|
||||
term.set_title(Some("Test".into()));
|
||||
term.pop_title();
|
||||
assert_eq!(term.title, None);
|
||||
|
||||
// Title can be reset to default
|
||||
// Title can be reset to default.
|
||||
term.title = Some("Test".into());
|
||||
term.set_title(None);
|
||||
assert_eq!(term.title, None);
|
||||
|
@ -2681,10 +2683,10 @@ mod benches {
|
|||
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
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
// limitations under the License.
|
||||
//
|
||||
//! tty related functionality
|
||||
//! TTY related functionality.
|
||||
use std::{env, io};
|
||||
|
||||
use terminfo::Database;
|
||||
|
@ -52,14 +52,14 @@ pub trait EventedReadWrite {
|
|||
fn write_token(&self) -> mio::Token;
|
||||
}
|
||||
|
||||
/// Events concerning TTY child processes
|
||||
/// Events concerning TTY child processes.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ChildEvent {
|
||||
/// Indicates the child has exited
|
||||
/// Indicates the child has 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
|
||||
/// 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 {
|
||||
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.
|
||||
fn next_child_event(&mut self) -> Option<ChildEvent>;
|
||||
}
|
||||
|
||||
// Setup environment variables
|
||||
/// Setup environment variables.
|
||||
pub fn setup_env<C>(config: &Config<C>) {
|
||||
// Default to 'alacritty' terminfo if it is available, otherwise
|
||||
// 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" },
|
||||
);
|
||||
|
||||
// Advertise 24-bit color support
|
||||
// Advertise 24-bit color support.
|
||||
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");
|
||||
|
||||
// Set env vars from config
|
||||
// Set env vars from config.
|
||||
for (key, value) in config.env.iter() {
|
||||
env::set_var(key, value);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! tty related functionality
|
||||
//! TTY related functionality.
|
||||
|
||||
use crate::config::{Config, Shell};
|
||||
use crate::event::OnResize;
|
||||
|
@ -37,9 +37,9 @@ use std::process::{Child, Command, Stdio};
|
|||
use std::ptr;
|
||||
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);
|
||||
|
||||
macro_rules! die {
|
||||
|
@ -53,7 +53,7 @@ pub fn child_pid() -> 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) {
|
||||
let mut win_size = size;
|
||||
win_size.ws_xpixel = 0;
|
||||
|
@ -64,7 +64,7 @@ fn make_pty(size: winsize) -> (RawFd, RawFd) {
|
|||
(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) {
|
||||
let res = unsafe {
|
||||
// TIOSCTTY changes based on platform and the `ioctl` call is different
|
||||
|
@ -91,13 +91,13 @@ struct Passwd<'a> {
|
|||
shell: &'a str,
|
||||
}
|
||||
|
||||
/// Return a Passwd struct with pointers into the provided buf
|
||||
/// Return a Passwd struct with pointers into the provided buf.
|
||||
///
|
||||
/// # Unsafety
|
||||
///
|
||||
/// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen.
|
||||
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 res: *mut libc::passwd = ptr::null_mut();
|
||||
|
@ -117,10 +117,10 @@ fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> {
|
|||
die!("pw not found");
|
||||
}
|
||||
|
||||
// sanity check
|
||||
// Sanity check.
|
||||
assert_eq!(entry.pw_uid, uid);
|
||||
|
||||
// Build a borrowed Passwd struct
|
||||
// Build a borrowed Passwd struct.
|
||||
Passwd {
|
||||
name: unsafe { CStr::from_ptr(entry.pw_name).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,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let win_size = size.to_winsize();
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.stderr(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("USER", pw.name);
|
||||
builder.env("SHELL", pw.shell);
|
||||
|
@ -183,7 +183,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) ->
|
|||
|
||||
unsafe {
|
||||
builder.pre_exec(move || {
|
||||
// Create a new process group
|
||||
// Create a new process group.
|
||||
let err = libc::setsid();
|
||||
if err == -1 {
|
||||
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);
|
||||
|
||||
// No longer need slave/master fds
|
||||
// No longer need slave/master fds.
|
||||
libc::close(slave);
|
||||
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 {
|
||||
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");
|
||||
|
||||
match builder.spawn() {
|
||||
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);
|
||||
|
||||
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 {
|
||||
/// Get a `libc::winsize`
|
||||
/// Get a `libc::winsize`.
|
||||
fn to_winsize(&self) -> winsize;
|
||||
}
|
||||
|
||||
|
@ -350,7 +350,7 @@ impl<'a> ToWinsize for &'a SizeInfo {
|
|||
}
|
||||
|
||||
impl OnResize for Pty {
|
||||
/// Resize the pty
|
||||
/// Resize the PTY.
|
||||
///
|
||||
/// Tells the kernel that the window size changed with the new pixel
|
||||
/// dimensions and line/column counts.
|
||||
|
|
|
@ -106,10 +106,10 @@ mod tests {
|
|||
|
||||
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();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ use crate::tty::windows::{cmdline, win32_string, Pty};
|
|||
// done until a safety net is in place for versions of Windows
|
||||
// that do not support the ConPTY api, as such versions will
|
||||
// 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 defined symbols in kernel32 and also is the convention
|
||||
|
@ -58,7 +58,7 @@ struct ConptyApi {
|
|||
impl ConptyApi {
|
||||
/// Load the API or None if it cannot be found.
|
||||
pub fn new() -> Option<Self> {
|
||||
// Unsafe because windows API calls
|
||||
// Unsafe because windows API calls.
|
||||
unsafe {
|
||||
let hmodule = GetModuleHandleA("kernel32\0".as_ptr() as _);
|
||||
assert!(!hmodule.is_null());
|
||||
|
@ -80,7 +80,7 @@ impl ConptyApi {
|
|||
}
|
||||
}
|
||||
|
||||
/// RAII Pseudoconsole
|
||||
/// RAII Pseudoconsole.
|
||||
pub struct Conpty {
|
||||
pub handle: HPCON,
|
||||
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
|
||||
// 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) }
|
||||
}
|
||||
}
|
||||
|
||||
// The Conpty handle can be sent between threads.
|
||||
// The ConPTY handle can be sent between threads.
|
||||
unsafe impl Send for Conpty {}
|
||||
|
||||
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 =
|
||||
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 {
|
||||
(api.CreatePseudoConsole)(
|
||||
coord,
|
||||
|
@ -133,7 +133,7 @@ pub fn new<C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) ->
|
|||
|
||||
let mut success;
|
||||
|
||||
// Prepare child process startup info
|
||||
// Prepare child process startup info.
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 {
|
||||
success = UpdateProcThreadAttribute(
|
||||
startup_info_ex.lpAttributeList,
|
||||
0,
|
||||
22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
|
||||
22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE.
|
||||
pty_handle,
|
||||
mem::size_of::<HPCON>(),
|
||||
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() {
|
||||
panic!("Unable to spawn shell: {}", Error::last_os_error());
|
||||
}
|
||||
|
|
|
@ -51,9 +51,9 @@ pub struct Pty {
|
|||
// `conout` before `backend` will cause a deadlock.
|
||||
backend: PtyBackend,
|
||||
// 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
|
||||
// NamedPipe for the conout and conin handles
|
||||
// NamedPipe for the conout and conin handles.
|
||||
conout: EventedReadablePipe,
|
||||
conin: EventedWritablePipe,
|
||||
read_token: mio::Token,
|
||||
|
|
|
@ -31,25 +31,25 @@ use crate::tty::windows::{cmdline, Pty};
|
|||
pub use winpty::Winpty as Agent;
|
||||
|
||||
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();
|
||||
|
||||
wconfig.set_initial_size(size.cols().0 as i32, size.lines().0 as i32);
|
||||
wconfig.set_mouse_mode(&MouseMode::Auto);
|
||||
|
||||
// Start agent
|
||||
// Start agent.
|
||||
let mut agent = Winpty::open(&wconfig).unwrap();
|
||||
let (conin, conout) = (agent.conin_name(), agent.conout_name());
|
||||
|
||||
let cmdline = cmdline(&config);
|
||||
|
||||
// Spawn process
|
||||
// Spawn process.
|
||||
let spawnconfig = SpawnConfig::new(
|
||||
SpawnFlags::AUTO_SHUTDOWN | SpawnFlags::EXIT_AFTER_SHUTDOWN,
|
||||
None, // appname
|
||||
None, // appname.
|
||||
Some(&cmdline),
|
||||
config.working_directory.as_ref().map(|p| p.as_path()),
|
||||
None, // Env
|
||||
None, // Env.
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -24,9 +24,9 @@ use std::os::windows::process::CommandExt;
|
|||
#[cfg(windows)]
|
||||
use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW};
|
||||
|
||||
/// Threading utilities
|
||||
/// Threading utilities.
|
||||
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>
|
||||
where
|
||||
F: FnOnce() -> T + Send + 'static,
|
||||
|
|
|
@ -147,7 +147,7 @@ impl ViModeCursor {
|
|||
/// Get target cursor point for vim-like page movement.
|
||||
#[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 {
|
||||
// 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 max_scroll = term.grid().history_size() - term.grid().display_offset();
|
||||
max(0, lines - max_scroll as isize)
|
||||
|
@ -156,18 +156,18 @@ impl ViModeCursor {
|
|||
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;
|
||||
line -= overscroll;
|
||||
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 mut target_line = buffer_point.line as isize + lines;
|
||||
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;
|
||||
|
||||
// Move cursor
|
||||
// Move cursor.
|
||||
self.point = Point::new(Line(line as usize), col);
|
||||
|
||||
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 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 {
|
||||
let lines = point.line.saturating_sub(display_offset + lines.0 - 1);
|
||||
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> {
|
||||
let cols = term.grid().num_cols();
|
||||
|
||||
// Expand across wide cells
|
||||
// Expand across wide cells.
|
||||
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();
|
||||
|
||||
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
|
||||
} 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) {
|
||||
point.line -= 1;
|
||||
}
|
||||
|
||||
last_occupied_in_line(term, point.line).unwrap_or(point)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
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);
|
||||
|
||||
// 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)
|
||||
.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 {
|
||||
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() {
|
||||
if !is_wrap(term, Point::new(line, cols - 1)) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Fallback to the next non-empty cell
|
||||
// Fallback to the next non-empty cell.
|
||||
let mut line = point.line;
|
||||
occupied.unwrap_or_else(|| loop {
|
||||
if let Some(occupied) = first_occupied_in_line(term, line) {
|
||||
|
@ -265,9 +265,9 @@ fn semantic<T: EventListener>(
|
|||
left: bool,
|
||||
start: bool,
|
||||
) -> Point<usize> {
|
||||
// Expand semantically based on movement direction
|
||||
// Expand semantically based on movement direction.
|
||||
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];
|
||||
if term.semantic_escape_chars().contains(cell.c)
|
||||
&& !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);
|
||||
|
||||
// Move to word boundary
|
||||
// Move to word boundary.
|
||||
if left != start && !is_boundary(term, point, left) {
|
||||
point = expand_semantic(point);
|
||||
}
|
||||
|
||||
// Skip whitespace
|
||||
// Skip whitespace.
|
||||
let mut next_point = advance(term, point, left);
|
||||
while !is_boundary(term, point, left) && is_space(term, next_point) {
|
||||
point = next_point;
|
||||
next_point = advance(term, point, left);
|
||||
}
|
||||
|
||||
// Assure minimum movement of one cell
|
||||
// Assure minimum movement of one cell.
|
||||
if !is_boundary(term, point, left) {
|
||||
point = advance(term, point, left);
|
||||
}
|
||||
|
||||
// Move to word boundary
|
||||
// Move to word boundary.
|
||||
if left == start && !is_boundary(term, point, left) {
|
||||
point = expand_semantic(point);
|
||||
}
|
||||
|
@ -315,18 +315,18 @@ fn word<T: EventListener>(
|
|||
left: bool,
|
||||
start: bool,
|
||||
) -> Point<usize> {
|
||||
// Make sure we jump above wide chars
|
||||
// Make sure we jump above wide chars.
|
||||
point = expand_wide(term, point, left);
|
||||
|
||||
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);
|
||||
while !is_boundary(term, point, left) && is_space(term, next_point) {
|
||||
point = next_point;
|
||||
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);
|
||||
while !is_boundary(term, point, left) && !is_space(term, next_point) {
|
||||
point = next_point;
|
||||
|
@ -335,12 +335,12 @@ fn word<T: EventListener>(
|
|||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
point = advance(term, point, left);
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ fn ref_test(dir: &Path) {
|
|||
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();
|
||||
term_grid.initialize_all(&Cell::default());
|
||||
term_grid.truncate();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! Constants for bitmap byte order
|
||||
//! Constants for bitmap byte order.
|
||||
#![allow(non_upper_case_globals)]
|
||||
pub const kCGBitmapByteOrder32Little: u32 = 2 << 12;
|
||||
pub const kCGBitmapByteOrder32Big: u32 = 4 << 12;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! Font rendering based on CoreText
|
||||
//! Font rendering based on CoreText.
|
||||
#![allow(improper_ctypes)]
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
@ -49,7 +49,7 @@ use super::{
|
|||
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.
|
||||
#[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.
|
||||
pub struct Rasterizer {
|
||||
|
@ -86,16 +86,16 @@ pub struct Rasterizer {
|
|||
use_thin_strokes: bool,
|
||||
}
|
||||
|
||||
/// Errors occurring when using the core text rasterizer
|
||||
/// Errors occurring when using the core text rasterizer.
|
||||
#[derive(Debug)]
|
||||
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),
|
||||
|
||||
/// Couldn't find font matching description
|
||||
/// Couldn't find font matching description.
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
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> {
|
||||
// get loaded font
|
||||
// Get loaded font.
|
||||
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(|| {
|
||||
// then try fallbacks
|
||||
// Then try fallbacks.
|
||||
for fallback in &font.fallbacks {
|
||||
if let Some(result) = self.maybe_get_glyph(glyph, &fallback) {
|
||||
// found a fallback
|
||||
// Found a fallback.
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// no fallback, give up.
|
||||
// No fallback, give up.
|
||||
Err(Error::MissingGlyph(glyph.c))
|
||||
})
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ impl Rasterizer {
|
|||
let descriptors = descriptors_for_family(&desc.name[..]);
|
||||
for descriptor in descriptors {
|
||||
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 font = descriptor.to_font(scaled_size, true);
|
||||
return Ok(font);
|
||||
|
@ -221,7 +221,7 @@ impl Rasterizer {
|
|||
for descriptor in descriptors {
|
||||
let font = descriptor.to_font(scaled_size, true);
|
||||
if font.is_bold() == bold && font.is_italic() == italic {
|
||||
// Found the font we want
|
||||
// Found the font we want.
|
||||
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(
|
||||
&self,
|
||||
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)]
|
||||
pub enum FontOrientation {
|
||||
Default = kCTFontDefaultOrientation as isize,
|
||||
|
@ -268,7 +268,7 @@ impl Default for FontOrientation {
|
|||
}
|
||||
}
|
||||
|
||||
/// A font
|
||||
/// A font.
|
||||
#[derive(Clone)]
|
||||
pub struct Font {
|
||||
ct_font: CTFont,
|
||||
|
@ -278,9 +278,9 @@ pub struct Font {
|
|||
|
||||
unsafe impl Send for Font {}
|
||||
|
||||
/// List all family names
|
||||
/// List all family names.
|
||||
pub fn get_family_names() -> Vec<String> {
|
||||
// CFArray of CFStringRef
|
||||
// CFArray of CFStringRef.
|
||||
let names = ct_get_family_names();
|
||||
let mut owned_names = Vec::new();
|
||||
|
||||
|
@ -291,23 +291,23 @@ pub fn get_family_names() -> Vec<String> {
|
|||
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> {
|
||||
// convert language type &Vec<String> -> CFArray
|
||||
// Convert language type &Vec<String> -> CFArray.
|
||||
let langarr: CFArray<CFString> = {
|
||||
let tmp: Vec<CFString> =
|
||||
languages.iter().map(|language| CFString::new(&language)).collect();
|
||||
CFArray::from_CFTypes(&tmp)
|
||||
};
|
||||
|
||||
// CFArray of CTFontDescriptorRef (again)
|
||||
// CFArray of CTFontDescriptorRef (again).
|
||||
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()
|
||||
}
|
||||
|
||||
/// Get descriptors for family name
|
||||
/// Get descriptors for family name.
|
||||
pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
|
||||
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")
|
||||
});
|
||||
|
||||
// CFArray of CTFontDescriptorRef (i think)
|
||||
// CFArray of CTFontDescriptorRef (i think).
|
||||
let descriptors = ct_collection.get_descriptors();
|
||||
if let Some(descriptors) = descriptors {
|
||||
for descriptor in descriptors.iter() {
|
||||
|
@ -330,7 +330,7 @@ pub fn descriptors_for_family(family: &str) -> Vec<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 {
|
||||
let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size);
|
||||
let cg_font = ct_font.copy_to_CGFont();
|
||||
|
@ -342,7 +342,7 @@ impl Descriptor {
|
|||
.map(|descriptor| {
|
||||
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()])
|
||||
.into_iter()
|
||||
.filter(|desc| !desc.font_path.as_os_str().is_empty())
|
||||
|
@ -361,7 +361,7 @@ impl Descriptor {
|
|||
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 {
|
||||
cg_font: menlo.copy_to_CGFont(),
|
||||
ct_font: menlo,
|
||||
|
@ -380,7 +380,7 @@ impl Descriptor {
|
|||
}
|
||||
|
||||
impl Font {
|
||||
/// The the bounding rect of a glyph
|
||||
/// The the bounding rect of a glyph.
|
||||
pub fn bounding_rect_for_glyph(
|
||||
&self,
|
||||
orientation: FontOrientation,
|
||||
|
@ -488,7 +488,7 @@ impl Font {
|
|||
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);
|
||||
let context_rect = CGRect::new(
|
||||
&CGPoint::new(0.0, 0.0),
|
||||
|
@ -510,7 +510,7 @@ impl Font {
|
|||
cg_context.set_allows_antialiasing(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);
|
||||
let rasterization_origin =
|
||||
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> {
|
||||
// encode this char as utf-16
|
||||
// Encode this char as utf-16.
|
||||
let mut buf = [0; 2];
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
// always being a 0.
|
||||
let mut glyphs: [CGGlyph; 2] = [0; 2];
|
||||
|
@ -586,11 +586,11 @@ mod tests {
|
|||
assert!(!list.is_empty());
|
||||
println!("{:?}", list);
|
||||
|
||||
// Check to_font
|
||||
// Check to_font.
|
||||
let fonts = list.iter().map(|desc| desc.to_font(72., false)).collect::<Vec<_>>();
|
||||
|
||||
for font in fonts {
|
||||
// Get a glyph
|
||||
// Get a glyph.
|
||||
for c in &['a', 'b', 'c', 'd'] {
|
||||
let glyph = font.get_glyph(*c, 72., false).unwrap();
|
||||
|
||||
|
@ -599,7 +599,7 @@ mod tests {
|
|||
BitmapBuffer::RGBA(buf) => buf,
|
||||
};
|
||||
|
||||
// Debug the glyph.. sigh
|
||||
// Debug the glyph.. sigh.
|
||||
for row in 0..glyph.height {
|
||||
for col in 0..glyph.width {
|
||||
let index = ((glyph.width * 3 * row) + (col * 3)) as usize;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! Rasterization powered by DirectWrite
|
||||
//! Rasterization powered by DirectWrite.
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
|
@ -114,7 +114,7 @@ impl DirectWriteRasterizer {
|
|||
let idx = *face
|
||||
.get_glyph_indices(&[c as u32])
|
||||
.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)
|
||||
.ok_or_else(|| Error::MissingGlyph(c))?;
|
||||
|
||||
|
@ -184,7 +184,7 @@ impl crate::Rasterize for DirectWriteRasterizer {
|
|||
|
||||
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 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> {
|
||||
// Fast path if face is already loaded
|
||||
// Fast path if face is already loaded.
|
||||
if let Some(key) = self.keys.get(desc) {
|
||||
return Ok(*key);
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ impl crate::Rasterize for DirectWriteRasterizer {
|
|||
let font = match desc.style {
|
||||
Style::Description { weight, slant } => {
|
||||
// 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()))
|
||||
},
|
||||
Style::Specific(ref style) => {
|
||||
|
@ -332,7 +332,7 @@ fn get_current_locale() -> String {
|
|||
let mut buf = vec![0u16; LOCALE_NAME_MAX_LENGTH];
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ impl CharSetRef {
|
|||
|
||||
pub fn merge(&self, other: &CharSetRef) -> Result<bool, ()> {
|
||||
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 res = FcCharSetMerge(self.as_ptr() as _, other.as_ptr() as _, &mut value);
|
||||
if res == 0 {
|
||||
|
|
|
@ -24,7 +24,7 @@ foreign_type! {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
/// Get the current configuration
|
||||
/// Get the current configuration.
|
||||
pub fn get_current() -> &'static ConfigRef {
|
||||
unsafe { ConfigRef::from_ptr(FcConfigGetCurrent()) }
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ impl FontSet {
|
|||
FcFontSetList(
|
||||
config.as_ptr(),
|
||||
&mut source.as_ptr(),
|
||||
1, // nsets
|
||||
1, // nsets.
|
||||
pattern.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> {
|
||||
font_set: &'a FontSetRef,
|
||||
num_fonts: usize,
|
||||
|
|
|
@ -72,7 +72,7 @@ pub fn font_sort(config: &ConfigRef, pattern: &PatternRef) -> Option<FontSet> {
|
|||
let ptr = FcFontSort(
|
||||
config.as_ptr(),
|
||||
pattern.as_ptr(),
|
||||
1, // Trim font list
|
||||
1, // Trim font list.
|
||||
&mut charsets,
|
||||
&mut result,
|
||||
);
|
||||
|
@ -102,14 +102,14 @@ pub fn font_list(
|
|||
}
|
||||
}
|
||||
|
||||
/// Available font sets
|
||||
/// Available font sets.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SetName {
|
||||
System = FcSetSystem as isize,
|
||||
Application = FcSetApplication as isize,
|
||||
}
|
||||
|
||||
/// When matching, how to match
|
||||
/// When matching, how to match.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum MatchKind {
|
||||
Font = FcMatchFont as isize,
|
||||
|
@ -187,7 +187,7 @@ impl From<isize> for Width {
|
|||
}
|
||||
}
|
||||
|
||||
/// Subpixel geometry
|
||||
/// Subpixel geometry.
|
||||
#[derive(Debug)]
|
||||
pub enum Rgba {
|
||||
Unknown,
|
||||
|
@ -237,7 +237,7 @@ impl From<isize> for Rgba {
|
|||
}
|
||||
}
|
||||
|
||||
/// Hinting Style
|
||||
/// Hinting Style.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum HintStyle {
|
||||
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 {
|
||||
None,
|
||||
Default,
|
||||
|
|
|
@ -54,7 +54,7 @@ impl<'a> StringPropertyIter<'a> {
|
|||
};
|
||||
|
||||
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
|
||||
// borrowed out?
|
||||
|
@ -67,7 +67,7 @@ impl<'a> StringPropertyIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Iterator over integer properties
|
||||
/// Iterator over integer properties.
|
||||
pub struct BooleanPropertyIter<'a> {
|
||||
pattern: &'a PatternRef,
|
||||
object: &'a [u8],
|
||||
|
@ -99,7 +99,7 @@ impl<'a> BooleanPropertyIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Iterator over integer properties
|
||||
/// Iterator over integer properties.
|
||||
pub struct IntPropertyIter<'a> {
|
||||
pattern: &'a PatternRef,
|
||||
object: &'a [u8],
|
||||
|
@ -204,7 +204,7 @@ impl<'a> LcdFilterPropertyIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Iterator over integer properties
|
||||
/// Iterator over integer properties.
|
||||
pub struct DoublePropertyIter<'a> {
|
||||
pattern: &'a PatternRef,
|
||||
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 {
|
||||
($iter:ty => $item:ty) => {
|
||||
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 {
|
||||
($($iter:ty => $item:ty),*) => {
|
||||
$(
|
||||
|
@ -310,7 +310,7 @@ macro_rules! impl_derived_property_iter {
|
|||
}
|
||||
}
|
||||
|
||||
// Basic Iterators
|
||||
// Basic Iterators.
|
||||
impl_property_iter! {
|
||||
StringPropertyIter<'a> => &'a str,
|
||||
IntPropertyIter<'a> => isize,
|
||||
|
@ -318,7 +318,7 @@ impl_property_iter! {
|
|||
BooleanPropertyIter<'a> => bool
|
||||
}
|
||||
|
||||
// Derived Iterators
|
||||
// Derived Iterators.
|
||||
impl_derived_property_iter! {
|
||||
RgbaPropertyIter<'a> => Rgba,
|
||||
HintStylePropertyIter<'a> => HintStyle,
|
||||
|
@ -460,23 +460,23 @@ impl PatternRef {
|
|||
index() => b"index\0"
|
||||
}
|
||||
|
||||
// Prints the pattern to stdout
|
||||
//
|
||||
// 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
|
||||
// a `fmt::Debug` impl.
|
||||
/// Prints the pattern to stdout.
|
||||
///
|
||||
/// 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
|
||||
/// a `fmt::Debug` impl.
|
||||
pub fn print(&self) {
|
||||
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
|
||||
/// any existing list, otherwise it is inserted at the beginning.
|
||||
///
|
||||
/// # 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 {
|
||||
let value = CString::new(&value[..]).unwrap();
|
||||
let value = value.as_ptr();
|
||||
|
@ -556,9 +556,9 @@ impl PatternRef {
|
|||
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
|
||||
/// retained. That is, the CharSet can be safely dropped immediately
|
||||
/// after being added to the pattern.
|
||||
|
|
|
@ -46,7 +46,7 @@ impl FallbackFont {
|
|||
|
||||
impl FontKey {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
@ -121,18 +121,18 @@ impl Rasterize for FreeTypeRasterizer {
|
|||
let height = (full.size_metrics.height / 64) as f64;
|
||||
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 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.;
|
||||
|
||||
// Fallback for bitmap fonts which do not provide underline metrics
|
||||
// Fallback for bitmap fonts which do not provide underline metrics.
|
||||
if underline_position == 0. {
|
||||
underline_thickness = (descent.abs() / 5.).round();
|
||||
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) =
|
||||
match TrueTypeOS2Table::from_face(&mut (*face.ft_face).clone()) {
|
||||
Some(os2) => {
|
||||
|
@ -141,7 +141,7 @@ impl Rasterize for FreeTypeRasterizer {
|
|||
(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");
|
||||
let strikeout_position = height as f32 / 2. + descent;
|
||||
(strikeout_position, underline_thickness)
|
||||
|
@ -206,9 +206,9 @@ struct FullMetrics {
|
|||
}
|
||||
|
||||
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> {
|
||||
// Adjust for DPI
|
||||
// Adjust for DPR.
|
||||
let size = f64::from(size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.);
|
||||
|
||||
let config = fc::Config::get_current();
|
||||
|
@ -216,26 +216,26 @@ impl FreeTypeRasterizer {
|
|||
pattern.add_family(&desc.name);
|
||||
pattern.add_pixelsize(size);
|
||||
|
||||
// Add style to a pattern
|
||||
// Add style to a pattern.
|
||||
match desc.style {
|
||||
Style::Description { slant, weight } => {
|
||||
// Match nearest font
|
||||
// Match nearest font.
|
||||
pattern.set_weight(weight.into_fontconfig_type());
|
||||
pattern.set_slant(slant.into_fontconfig_type());
|
||||
},
|
||||
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);
|
||||
},
|
||||
}
|
||||
|
||||
// Hash requested pattern
|
||||
// Hash requested pattern.
|
||||
let hash = pattern.hash();
|
||||
|
||||
pattern.config_substitute(config, fc::MatchKind::Pattern);
|
||||
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 =
|
||||
fc::font_sort(&config, &pattern).ok_or_else(|| Error::MissingFont(desc.to_owned()))?;
|
||||
let mut matched_fonts = matched_fonts.into_iter();
|
||||
|
@ -243,24 +243,24 @@ impl FreeTypeRasterizer {
|
|||
let primary_font =
|
||||
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);
|
||||
|
||||
// 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());
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
self.face_from_pattern(&primary_font, primary_font_key)
|
||||
.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 empty_charset = CharSet::new();
|
||||
|
||||
|
@ -268,7 +268,7 @@ impl FreeTypeRasterizer {
|
|||
.map(|fallback_font| {
|
||||
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_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)?;
|
||||
if ft_face.has_color() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ impl FreeTypeRasterizer {
|
|||
fn load_face_with_glyph(&mut self, glyph: GlyphKey) -> Result<FontKey, Error> {
|
||||
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) {
|
||||
return Ok(glyph.font_key);
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ impl FreeTypeRasterizer {
|
|||
Some(face) => {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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 face = &self.faces[&font_key];
|
||||
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 {
|
||||
pixelsize_fixup_factor
|
||||
} 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)?;
|
||||
f64::from(pixelsize) / f64::from(metrics.y_ppem)
|
||||
};
|
||||
|
@ -465,7 +465,7 @@ impl FreeTypeRasterizer {
|
|||
(false, fc::HintStyle::None, _) => LoadFlag::NO_HINTING | LoadFlag::MONOCHROME,
|
||||
(false, ..) => LoadFlag::TARGET_MONO | LoadFlag::MONOCHROME,
|
||||
(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.
|
||||
//
|
||||
// According to the FreeType docs,
|
||||
|
@ -478,7 +478,7 @@ impl FreeTypeRasterizer {
|
|||
// In practice, this means we can have `FT_LOAD_TARGET_LIGHT` with
|
||||
// subpixel render modes like `FT_RENDER_MODE_LCD`. Libraries like
|
||||
// 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,
|
||||
// If LCD hinting is to be used, must select hintmedium or hintfull,
|
||||
// have AA enabled, and select a subpixel mode.
|
||||
|
@ -565,7 +565,7 @@ impl FreeTypeRasterizer {
|
|||
while count != 0 {
|
||||
let value = ((byte >> bit) & 1) * 255;
|
||||
// Push value 3x since result buffer should be 1 byte
|
||||
// per channel
|
||||
// per channel.
|
||||
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
|
||||
/// `fixup_factor`.
|
||||
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)) {
|
||||
(BitmapBuffer::RGBA(buffer), Some(Ordering::Less)) => buffer,
|
||||
_ => 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_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 mut downsampled_buffer = Vec::<u8>::with_capacity(target_width * target_height * 4);
|
||||
|
||||
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 source_line_start = (line_index * downsampling_step).round() as usize;
|
||||
let source_line_end = ((line_index + 1.) * downsampling_step).round() as usize;
|
||||
|
||||
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 source_column_start = (column_index * 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 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 {
|
||||
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((g / 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);
|
||||
|
||||
// Downscale the metrics
|
||||
// Downscale the metrics.
|
||||
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.width = target_width as i32;
|
||||
|
@ -688,19 +689,19 @@ fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> Ra
|
|||
bitmap_glyph
|
||||
}
|
||||
|
||||
/// Errors occurring when using the freetype rasterizer
|
||||
/// Errors occurring when using the freetype rasterizer.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error occurred within the FreeType library
|
||||
/// Error occurred within the FreeType library.
|
||||
FreeType(freetype::Error),
|
||||
|
||||
/// Couldn't find font matching description
|
||||
/// Couldn't find font matching description.
|
||||
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,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//! Compatibility layer for different font engines
|
||||
//! Compatibility layer for different font engines.
|
||||
//!
|
||||
//! CoreText is used on 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)]
|
||||
|
||||
|
@ -24,7 +24,7 @@ use std::fmt;
|
|||
use std::ops::{Add, Mul};
|
||||
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)))]
|
||||
pub mod ft;
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
|
@ -35,7 +35,7 @@ pub mod directwrite;
|
|||
#[cfg(windows)]
|
||||
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")]
|
||||
mod darwin;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -60,7 +60,7 @@ pub enum Weight {
|
|||
Bold,
|
||||
}
|
||||
|
||||
/// Style of font
|
||||
/// Style of font.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Style {
|
||||
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)]
|
||||
pub struct FontKey {
|
||||
token: u32,
|
||||
}
|
||||
|
||||
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 {
|
||||
static TOKEN: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
|
@ -117,23 +117,23 @@ pub struct GlyphKey {
|
|||
pub size: Size,
|
||||
}
|
||||
|
||||
/// Font size stored as integer
|
||||
/// Font size stored as integer.
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Size(i16);
|
||||
|
||||
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 {
|
||||
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]
|
||||
pub fn factor() -> f32 {
|
||||
2.0
|
||||
}
|
||||
|
||||
/// Get the f32 size in points
|
||||
/// Get the f32 size in points.
|
||||
pub fn as_f32_pts(self) -> f32 {
|
||||
f32::from(self.0) / Size::factor()
|
||||
}
|
||||
|
@ -215,23 +215,23 @@ pub struct Metrics {
|
|||
}
|
||||
|
||||
pub trait Rasterize {
|
||||
/// Errors occurring in Rasterize methods
|
||||
/// Errors occurring in Rasterize methods.
|
||||
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>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Get `Metrics` for the given `FontKey`
|
||||
/// Get `Metrics` for the given `FontKey`.
|
||||
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>;
|
||||
|
||||
/// Rasterize the glyph described by `GlyphKey`.
|
||||
/// Rasterize the glyph described by `GlyphKey`..
|
||||
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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue