Extract `SizeInfo` from alacritty_terminal
The `SizeInfo` is a SizeInfo used for rendering, which contains information about padding, and such, however all the terminal need is number of visible lines and columns.
This commit is contained in:
parent
851dbc328e
commit
673710487a
|
@ -2,9 +2,9 @@
|
|||
|
||||
use alacritty_terminal::ansi::CursorShape;
|
||||
use alacritty_terminal::term::color::Rgb;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::display::content::RenderableCursor;
|
||||
use crate::display::SizeInfo;
|
||||
use crate::renderer::rects::RenderRect;
|
||||
|
||||
/// Trait for conversion into the iterator.
|
||||
|
|
|
@ -3,7 +3,9 @@ use std::iter::Peekable;
|
|||
|
||||
use glutin::Rect;
|
||||
|
||||
use alacritty_terminal::term::{LineDamageBounds, SizeInfo, TermDamageIterator};
|
||||
use alacritty_terminal::term::{LineDamageBounds, TermDamageIterator};
|
||||
|
||||
use crate::display::SizeInfo;
|
||||
|
||||
/// Iterator which converts `alacritty_terminal` damage information into renderer damaged rects.
|
||||
pub struct RenderDamageIterator<'a> {
|
||||
|
|
|
@ -16,6 +16,7 @@ use glutin::window::CursorIcon;
|
|||
use glutin::Rect as DamageRect;
|
||||
use log::{debug, info};
|
||||
use parking_lot::MutexGuard;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||
use wayland_client::EventQueue;
|
||||
|
@ -24,15 +25,13 @@ use crossfont::{self, Rasterize, Rasterizer};
|
|||
|
||||
use alacritty_terminal::ansi::NamedColor;
|
||||
use alacritty_terminal::config::MAX_SCROLLBACK_LINES;
|
||||
use alacritty_terminal::event::{EventListener, OnResize};
|
||||
use alacritty_terminal::grid::Dimensions as _;
|
||||
use alacritty_terminal::event::{EventListener, OnResize, WindowSize};
|
||||
use alacritty_terminal::grid::Dimensions as TermDimensions;
|
||||
use alacritty_terminal::index::{Column, Direction, Line, Point};
|
||||
use alacritty_terminal::selection::{Selection, SelectionRange};
|
||||
use alacritty_terminal::term::cell::Flags;
|
||||
use alacritty_terminal::term::color::Rgb;
|
||||
use alacritty_terminal::term::{
|
||||
SizeInfo, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES,
|
||||
};
|
||||
use alacritty_terminal::term::{Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES};
|
||||
|
||||
use crate::config::font::Font;
|
||||
#[cfg(not(windows))]
|
||||
|
@ -135,6 +134,166 @@ impl From<glutin::ContextError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
/// Terminal size info.
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
|
||||
pub struct SizeInfo<T = f32> {
|
||||
/// Terminal window width.
|
||||
width: T,
|
||||
|
||||
/// Terminal window height.
|
||||
height: T,
|
||||
|
||||
/// Width of individual cell.
|
||||
cell_width: T,
|
||||
|
||||
/// Height of individual cell.
|
||||
cell_height: T,
|
||||
|
||||
/// Horizontal window padding.
|
||||
padding_x: T,
|
||||
|
||||
/// Vertical window padding.
|
||||
padding_y: T,
|
||||
|
||||
/// Number of lines in the viewport.
|
||||
screen_lines: usize,
|
||||
|
||||
/// Number of columns in the viewport.
|
||||
columns: usize,
|
||||
}
|
||||
|
||||
impl From<SizeInfo<f32>> for SizeInfo<u32> {
|
||||
fn from(size_info: SizeInfo<f32>) -> Self {
|
||||
Self {
|
||||
width: size_info.width as u32,
|
||||
height: size_info.height as u32,
|
||||
cell_width: size_info.cell_width as u32,
|
||||
cell_height: size_info.cell_height as u32,
|
||||
padding_x: size_info.padding_x as u32,
|
||||
padding_y: size_info.padding_y as u32,
|
||||
screen_lines: size_info.screen_lines,
|
||||
columns: size_info.screen_lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SizeInfo<f32>> for WindowSize {
|
||||
fn from(size_info: SizeInfo<f32>) -> Self {
|
||||
Self {
|
||||
num_cols: size_info.columns() as u16,
|
||||
num_lines: size_info.screen_lines() as u16,
|
||||
cell_width: size_info.cell_width() as u16,
|
||||
cell_height: size_info.cell_width() as u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Copy> SizeInfo<T> {
|
||||
#[inline]
|
||||
pub fn width(&self) -> T {
|
||||
self.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> T {
|
||||
self.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cell_width(&self) -> T {
|
||||
self.cell_width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cell_height(&self) -> T {
|
||||
self.cell_height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn padding_x(&self) -> T {
|
||||
self.padding_x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn padding_y(&self) -> T {
|
||||
self.padding_y
|
||||
}
|
||||
}
|
||||
|
||||
impl SizeInfo<f32> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
width: f32,
|
||||
height: f32,
|
||||
cell_width: f32,
|
||||
cell_height: f32,
|
||||
mut padding_x: f32,
|
||||
mut padding_y: f32,
|
||||
dynamic_padding: bool,
|
||||
) -> SizeInfo {
|
||||
if dynamic_padding {
|
||||
padding_x = Self::dynamic_padding(padding_x.floor(), width, cell_width);
|
||||
padding_y = Self::dynamic_padding(padding_y.floor(), height, cell_height);
|
||||
}
|
||||
|
||||
let lines = (height - 2. * padding_y) / cell_height;
|
||||
let screen_lines = cmp::max(lines as usize, MIN_SCREEN_LINES);
|
||||
|
||||
let columns = (width - 2. * padding_x) / cell_width;
|
||||
let columns = cmp::max(columns as usize, MIN_COLUMNS);
|
||||
|
||||
SizeInfo {
|
||||
width,
|
||||
height,
|
||||
cell_width,
|
||||
cell_height,
|
||||
padding_x: padding_x.floor(),
|
||||
padding_y: padding_y.floor(),
|
||||
screen_lines,
|
||||
columns,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reserve_lines(&mut self, count: usize) {
|
||||
self.screen_lines = cmp::max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES);
|
||||
}
|
||||
|
||||
/// Check if coordinates are inside the terminal grid.
|
||||
///
|
||||
/// The padding, message bar or search are not counted as part of the grid.
|
||||
#[inline]
|
||||
pub fn contains_point(&self, x: usize, y: usize) -> bool {
|
||||
x <= (self.padding_x + self.columns as f32 * self.cell_width) as usize
|
||||
&& x > self.padding_x as usize
|
||||
&& y <= (self.padding_y + self.screen_lines as f32 * self.cell_height) as usize
|
||||
&& y > self.padding_y as usize
|
||||
}
|
||||
|
||||
/// Calculate padding to spread it evenly around the terminal content.
|
||||
#[inline]
|
||||
fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
|
||||
padding + ((dimension - 2. * padding) % cell_dimension) / 2.
|
||||
}
|
||||
}
|
||||
|
||||
impl TermDimensions for SizeInfo {
|
||||
#[inline]
|
||||
fn columns(&self) -> usize {
|
||||
self.columns
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn screen_lines(&self) -> usize {
|
||||
self.screen_lines
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_lines(&self) -> usize {
|
||||
self.screen_lines()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, PartialEq)]
|
||||
pub struct DisplayUpdate {
|
||||
pub dirty: bool,
|
||||
|
@ -456,7 +615,7 @@ impl Display {
|
|||
self.size_info.reserve_lines(message_bar_lines + search_lines);
|
||||
|
||||
// Resize PTY.
|
||||
pty_resize_handle.on_resize(&self.size_info);
|
||||
pty_resize_handle.on_resize(self.size_info.into());
|
||||
|
||||
// Resize terminal.
|
||||
terminal.resize(self.size_info);
|
||||
|
|
|
@ -50,10 +50,10 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
|||
use winapi::shared::minwindef::WORD;
|
||||
|
||||
use alacritty_terminal::index::Point;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::config::window::{Decorations, Identity, WindowConfig};
|
||||
use crate::config::UiConfig;
|
||||
use crate::display::SizeInfo;
|
||||
use crate::gl;
|
||||
|
||||
/// Window icon for `_NET_WM_ICON` property.
|
||||
|
|
|
@ -32,7 +32,7 @@ use alacritty_terminal::grid::{Dimensions, Scroll};
|
|||
use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side};
|
||||
use alacritty_terminal::selection::{Selection, SelectionType};
|
||||
use alacritty_terminal::term::search::{Match, RegexSearch};
|
||||
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
|
||||
use alacritty_terminal::term::{ClipboardType, Term, TermMode};
|
||||
|
||||
use crate::cli::{Options as CliOptions, WindowOptions};
|
||||
use crate::clipboard::Clipboard;
|
||||
|
@ -43,7 +43,7 @@ use crate::daemon::foreground_process_path;
|
|||
use crate::daemon::spawn_daemon;
|
||||
use crate::display::hint::HintMatch;
|
||||
use crate::display::window::Window;
|
||||
use crate::display::{self, Display};
|
||||
use crate::display::{self, Display, SizeInfo};
|
||||
use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
|
||||
use crate::message_bar::{Message, MessageBuffer};
|
||||
use crate::scheduler::{Scheduler, TimerId, Topic};
|
||||
|
@ -1093,6 +1093,10 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
|
|||
.unwrap_or(self.ctx.display.colors[index]);
|
||||
self.ctx.write_to_pty(format(color).into_bytes());
|
||||
},
|
||||
TerminalEvent::TextAreaSizeRequest(format) => {
|
||||
let text = format(self.ctx.size_info().into());
|
||||
self.ctx.write_to_pty(text.into_bytes());
|
||||
},
|
||||
TerminalEvent::PtyWrite(text) => self.ctx.write_to_pty(text.into_bytes()),
|
||||
TerminalEvent::MouseCursorDirty => self.reset_mouse_cursor(),
|
||||
TerminalEvent::Exit => (),
|
||||
|
|
|
@ -27,14 +27,14 @@ use alacritty_terminal::grid::{Dimensions, Scroll};
|
|||
use alacritty_terminal::index::{Boundary, Column, Direction, Point, Side};
|
||||
use alacritty_terminal::selection::SelectionType;
|
||||
use alacritty_terminal::term::search::Match;
|
||||
use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode};
|
||||
use alacritty_terminal::term::{ClipboardType, Term, TermMode};
|
||||
use alacritty_terminal::vi_mode::ViMotion;
|
||||
|
||||
use crate::clipboard::Clipboard;
|
||||
use crate::config::{Action, BindingMode, Key, MouseAction, SearchAction, UiConfig, ViAction};
|
||||
use crate::display::hint::HintMatch;
|
||||
use crate::display::window::Window;
|
||||
use crate::display::Display;
|
||||
use crate::display::{Display, SizeInfo};
|
||||
use crate::event::{ClickState, Event, EventType, Mouse, TYPING_SEARCH_DELAY};
|
||||
use crate::message_bar::{self, Message};
|
||||
use crate::scheduler::{Scheduler, TimerId, Topic};
|
||||
|
@ -1110,7 +1110,7 @@ mod tests {
|
|||
false,
|
||||
);
|
||||
|
||||
let mut terminal = Term::new(&cfg.terminal_config, size, MockEventProxy);
|
||||
let mut terminal = Term::new(&cfg.terminal_config, &size, MockEventProxy);
|
||||
|
||||
let mut mouse = Mouse {
|
||||
click_state: $initial_state,
|
||||
|
|
|
@ -3,7 +3,8 @@ use std::collections::VecDeque;
|
|||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use alacritty_terminal::grid::Dimensions;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::display::SizeInfo;
|
||||
|
||||
pub const CLOSE_BUTTON_TEXT: &str = "[X]";
|
||||
const CLOSE_BUTTON_PADDING: usize = 1;
|
||||
|
@ -186,7 +187,7 @@ impl MessageBuffer {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
use crate::display::SizeInfo;
|
||||
|
||||
#[test]
|
||||
fn appends_close_button() {
|
||||
|
|
|
@ -7,9 +7,9 @@ use log::info;
|
|||
use alacritty_terminal::index::Point;
|
||||
use alacritty_terminal::term::cell::Flags;
|
||||
use alacritty_terminal::term::color::Rgb;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::display::content::RenderableCell;
|
||||
use crate::display::SizeInfo;
|
||||
use crate::gl;
|
||||
use crate::renderer::rects::{RectRenderer, RenderRect};
|
||||
use crate::renderer::shader::ShaderError;
|
||||
|
|
|
@ -7,9 +7,9 @@ use alacritty_terminal::grid::Dimensions;
|
|||
use alacritty_terminal::index::{Column, Point};
|
||||
use alacritty_terminal::term::cell::Flags;
|
||||
use alacritty_terminal::term::color::Rgb;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::display::content::RenderableCell;
|
||||
use crate::display::SizeInfo;
|
||||
use crate::gl;
|
||||
use crate::gl::types::*;
|
||||
use crate::renderer::shader::{ShaderError, ShaderProgram, ShaderVersion};
|
||||
|
|
|
@ -5,9 +5,9 @@ use crossfont::RasterizedGlyph;
|
|||
use log::info;
|
||||
|
||||
use alacritty_terminal::term::cell::Flags;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::display::content::RenderableCell;
|
||||
use crate::display::SizeInfo;
|
||||
use crate::gl;
|
||||
use crate::gl::types::*;
|
||||
use crate::renderer::shader::{ShaderProgram, ShaderVersion};
|
||||
|
|
|
@ -5,9 +5,9 @@ use crossfont::RasterizedGlyph;
|
|||
use log::info;
|
||||
|
||||
use alacritty_terminal::term::cell::Flags;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::display::content::RenderableCell;
|
||||
use crate::display::SizeInfo;
|
||||
use crate::gl;
|
||||
use crate::gl::types::*;
|
||||
use crate::renderer::shader::{ShaderProgram, ShaderVersion};
|
||||
|
|
|
@ -2,9 +2,9 @@ use bitflags::bitflags;
|
|||
use crossfont::{GlyphKey, RasterizedGlyph};
|
||||
|
||||
use alacritty_terminal::term::cell::Flags;
|
||||
use alacritty_terminal::term::SizeInfo;
|
||||
|
||||
use crate::display::content::RenderableCell;
|
||||
use crate::display::SizeInfo;
|
||||
use crate::gl;
|
||||
use crate::gl::types::*;
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ use alacritty_terminal::event_loop::{EventLoop as PtyEventLoop, Msg, Notifier};
|
|||
use alacritty_terminal::grid::{Dimensions, Scroll};
|
||||
use alacritty_terminal::index::Direction;
|
||||
use alacritty_terminal::sync::FairMutex;
|
||||
use alacritty_terminal::term::test::TermSize;
|
||||
use alacritty_terminal::term::{Term, TermMode};
|
||||
use alacritty_terminal::tty;
|
||||
|
||||
|
@ -98,7 +99,7 @@ impl WindowContext {
|
|||
// 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
|
||||
// access it.
|
||||
let terminal = Term::new(&config.terminal_config, display.size_info, event_proxy.clone());
|
||||
let terminal = Term::new(&config.terminal_config, &display.size_info, event_proxy.clone());
|
||||
let terminal = Arc::new(FairMutex::new(terminal));
|
||||
|
||||
// Create the PTY.
|
||||
|
@ -106,7 +107,7 @@ impl WindowContext {
|
|||
// 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.
|
||||
let pty = tty::new(&pty_config, &display.size_info, display.window.x11_window_id())?;
|
||||
let pty = tty::new(&pty_config, display.size_info.into(), display.window.x11_window_id())?;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let master_fd = pty.file().as_raw_fd();
|
||||
|
@ -350,7 +351,9 @@ impl WindowContext {
|
|||
|
||||
let serialized_grid = json::to_string(&grid).expect("serialize grid");
|
||||
|
||||
let serialized_size = json::to_string(&self.display.size_info).expect("serialize size");
|
||||
let size_info = &self.display.size_info;
|
||||
let size = TermSize::new(size_info.columns(), size_info.screen_lines());
|
||||
let serialized_size = json::to_string(&size).expect("serialize size");
|
||||
|
||||
let serialized_config = format!("{{\"history_size\":{}}}", grid.history_size());
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::term::color::Rgb;
|
||||
use crate::term::{ClipboardType, SizeInfo};
|
||||
use crate::term::ClipboardType;
|
||||
|
||||
/// Terminal event.
|
||||
///
|
||||
|
@ -38,6 +38,9 @@ pub enum Event {
|
|||
/// Write some text to the PTY.
|
||||
PtyWrite(String),
|
||||
|
||||
/// Request to write the text area size.
|
||||
TextAreaSizeRequest(Arc<dyn Fn(WindowSize) -> String + Sync + Send + 'static>),
|
||||
|
||||
/// Cursor blinking state has changed.
|
||||
CursorBlinkingChange,
|
||||
|
||||
|
@ -56,6 +59,7 @@ impl Debug for Event {
|
|||
match self {
|
||||
Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text),
|
||||
Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty),
|
||||
Event::TextAreaSizeRequest(_) => write!(f, "TextAreaSizeRequest"),
|
||||
Event::ColorRequest(index, _) => write!(f, "ColorRequest({})", index),
|
||||
Event::PtyWrite(text) => write!(f, "PtyWrite({})", text),
|
||||
Event::Title(title) => write!(f, "Title({})", title),
|
||||
|
@ -77,9 +81,17 @@ pub trait Notify {
|
|||
fn notify<B: Into<Cow<'static, [u8]>>>(&self, _: B);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct WindowSize {
|
||||
pub num_lines: u16,
|
||||
pub num_cols: u16,
|
||||
pub cell_width: u16,
|
||||
pub cell_height: u16,
|
||||
}
|
||||
|
||||
/// Types that are interested in when the display is resized.
|
||||
pub trait OnResize {
|
||||
fn on_resize(&mut self, size: &SizeInfo);
|
||||
fn on_resize(&mut self, window_size: WindowSize);
|
||||
}
|
||||
|
||||
/// Event Loop for notifying the renderer about terminal events.
|
||||
|
|
|
@ -15,9 +15,9 @@ use mio::unix::UnixReady;
|
|||
use mio::{self, Events, PollOpt, Ready};
|
||||
use mio_extras::channel::{self, Receiver, Sender};
|
||||
|
||||
use crate::event::{self, Event, EventListener};
|
||||
use crate::event::{self, Event, EventListener, WindowSize};
|
||||
use crate::sync::FairMutex;
|
||||
use crate::term::{SizeInfo, Term};
|
||||
use crate::term::Term;
|
||||
use crate::{ansi, thread, tty};
|
||||
|
||||
/// Max bytes to read from the PTY before forced terminal synchronization.
|
||||
|
@ -36,7 +36,7 @@ pub enum Msg {
|
|||
Shutdown,
|
||||
|
||||
/// Instruction to resize the PTY.
|
||||
Resize(SizeInfo),
|
||||
Resize(WindowSize),
|
||||
}
|
||||
|
||||
/// The main event!.. loop.
|
||||
|
@ -78,8 +78,8 @@ impl event::Notify for Notifier {
|
|||
}
|
||||
|
||||
impl event::OnResize for Notifier {
|
||||
fn on_resize(&mut self, size: &SizeInfo) {
|
||||
let _ = self.0.send(Msg::Resize(*size));
|
||||
fn on_resize(&mut self, window_size: WindowSize) {
|
||||
let _ = self.0.send(Msg::Resize(window_size));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ where
|
|||
while let Ok(msg) = self.rx.try_recv() {
|
||||
match msg {
|
||||
Msg::Input(input) => state.write_list.push_back(input),
|
||||
Msg::Resize(size) => self.pty.on_resize(&size),
|
||||
Msg::Resize(window_size) => self.pty.on_resize(window_size),
|
||||
Msg::Shutdown => return false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -397,11 +397,12 @@ mod tests {
|
|||
|
||||
use crate::config::Config;
|
||||
use crate::index::{Column, Point, Side};
|
||||
use crate::term::{SizeInfo, Term};
|
||||
use crate::term::test::TermSize;
|
||||
use crate::term::Term;
|
||||
|
||||
fn term(height: usize, width: usize) -> Term<()> {
|
||||
let size = SizeInfo::new(width as f32, height as f32, 1.0, 1.0, 0.0, 0.0, false);
|
||||
Term::new(&Config::default(), size, ())
|
||||
let size = TermSize::new(width, height);
|
||||
Term::new(&Config::default(), &size, ())
|
||||
}
|
||||
|
||||
/// Test case of single cell selection.
|
||||
|
|
|
@ -6,7 +6,6 @@ use std::{cmp, mem, ptr, slice, str};
|
|||
|
||||
use bitflags::bitflags;
|
||||
use log::{debug, trace};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::ansi::{
|
||||
|
@ -74,155 +73,6 @@ impl Default for TermMode {
|
|||
}
|
||||
}
|
||||
|
||||
/// Terminal size info.
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
|
||||
pub struct SizeInfo<T = f32> {
|
||||
/// Terminal window width.
|
||||
width: T,
|
||||
|
||||
/// Terminal window height.
|
||||
height: T,
|
||||
|
||||
/// Width of individual cell.
|
||||
cell_width: T,
|
||||
|
||||
/// Height of individual cell.
|
||||
cell_height: T,
|
||||
|
||||
/// Horizontal window padding.
|
||||
padding_x: T,
|
||||
|
||||
/// Vertical window padding.
|
||||
padding_y: T,
|
||||
|
||||
/// Number of lines in the viewport.
|
||||
screen_lines: usize,
|
||||
|
||||
/// Number of columns in the viewport.
|
||||
columns: usize,
|
||||
}
|
||||
|
||||
impl From<SizeInfo<f32>> for SizeInfo<u32> {
|
||||
fn from(size_info: SizeInfo<f32>) -> Self {
|
||||
Self {
|
||||
width: size_info.width as u32,
|
||||
height: size_info.height as u32,
|
||||
cell_width: size_info.cell_width as u32,
|
||||
cell_height: size_info.cell_height as u32,
|
||||
padding_x: size_info.padding_x as u32,
|
||||
padding_y: size_info.padding_y as u32,
|
||||
screen_lines: size_info.screen_lines,
|
||||
columns: size_info.screen_lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Copy> SizeInfo<T> {
|
||||
#[inline]
|
||||
pub fn width(&self) -> T {
|
||||
self.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> T {
|
||||
self.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cell_width(&self) -> T {
|
||||
self.cell_width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cell_height(&self) -> T {
|
||||
self.cell_height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn padding_x(&self) -> T {
|
||||
self.padding_x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn padding_y(&self) -> T {
|
||||
self.padding_y
|
||||
}
|
||||
}
|
||||
|
||||
impl SizeInfo<f32> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
width: f32,
|
||||
height: f32,
|
||||
cell_width: f32,
|
||||
cell_height: f32,
|
||||
mut padding_x: f32,
|
||||
mut padding_y: f32,
|
||||
dynamic_padding: bool,
|
||||
) -> SizeInfo {
|
||||
if dynamic_padding {
|
||||
padding_x = Self::dynamic_padding(padding_x.floor(), width, cell_width);
|
||||
padding_y = Self::dynamic_padding(padding_y.floor(), height, cell_height);
|
||||
}
|
||||
|
||||
let lines = (height - 2. * padding_y) / cell_height;
|
||||
let screen_lines = cmp::max(lines as usize, MIN_SCREEN_LINES);
|
||||
|
||||
let columns = (width - 2. * padding_x) / cell_width;
|
||||
let columns = cmp::max(columns as usize, MIN_COLUMNS);
|
||||
|
||||
SizeInfo {
|
||||
width,
|
||||
height,
|
||||
cell_width,
|
||||
cell_height,
|
||||
padding_x: padding_x.floor(),
|
||||
padding_y: padding_y.floor(),
|
||||
screen_lines,
|
||||
columns,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reserve_lines(&mut self, count: usize) {
|
||||
self.screen_lines = cmp::max(self.screen_lines.saturating_sub(count), MIN_SCREEN_LINES);
|
||||
}
|
||||
|
||||
/// Check if coordinates are inside the terminal grid.
|
||||
///
|
||||
/// The padding, message bar or search are not counted as part of the grid.
|
||||
#[inline]
|
||||
pub fn contains_point(&self, x: usize, y: usize) -> bool {
|
||||
x <= (self.padding_x + self.columns as f32 * self.cell_width) as usize
|
||||
&& x > self.padding_x as usize
|
||||
&& y <= (self.padding_y + self.screen_lines as f32 * self.cell_height) as usize
|
||||
&& y > self.padding_y as usize
|
||||
}
|
||||
|
||||
/// Calculate padding to spread it evenly around the terminal content.
|
||||
#[inline]
|
||||
fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
|
||||
padding + ((dimension - 2. * padding) % cell_dimension) / 2.
|
||||
}
|
||||
}
|
||||
|
||||
impl Dimensions for SizeInfo {
|
||||
#[inline]
|
||||
fn columns(&self) -> usize {
|
||||
self.columns
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn screen_lines(&self) -> usize {
|
||||
self.screen_lines
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_lines(&self) -> usize {
|
||||
self.screen_lines()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct LineDamageBounds {
|
||||
/// Damaged line number.
|
||||
|
@ -431,10 +281,6 @@ pub struct Term<T> {
|
|||
/// term is set.
|
||||
title_stack: Vec<Option<String>>,
|
||||
|
||||
/// Information about cell dimensions.
|
||||
cell_width: usize,
|
||||
cell_height: usize,
|
||||
|
||||
/// Information about damaged cells.
|
||||
damage: TermDamageState,
|
||||
}
|
||||
|
@ -462,9 +308,9 @@ impl<T> Term<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new(config: &Config, size: SizeInfo, event_proxy: T) -> Term<T> {
|
||||
let num_cols = size.columns;
|
||||
let num_lines = size.screen_lines;
|
||||
pub fn new<D: Dimensions>(config: &Config, dimensions: &D, event_proxy: T) -> Term<T> {
|
||||
let num_cols = dimensions.columns();
|
||||
let num_lines = dimensions.screen_lines();
|
||||
|
||||
let history_size = config.scrolling.history() as usize;
|
||||
let grid = Grid::new(num_lines, num_cols, history_size);
|
||||
|
@ -495,8 +341,6 @@ impl<T> Term<T> {
|
|||
title: None,
|
||||
title_stack: Vec::new(),
|
||||
selection: None,
|
||||
cell_width: size.cell_width as usize,
|
||||
cell_height: size.cell_height as usize,
|
||||
damage,
|
||||
}
|
||||
}
|
||||
|
@ -717,15 +561,12 @@ impl<T> Term<T> {
|
|||
}
|
||||
|
||||
/// Resize terminal to new dimensions.
|
||||
pub fn resize(&mut self, size: SizeInfo) {
|
||||
self.cell_width = size.cell_width as usize;
|
||||
self.cell_height = size.cell_height as usize;
|
||||
|
||||
pub fn resize<S: Dimensions>(&mut self, size: S) {
|
||||
let old_cols = self.columns();
|
||||
let old_lines = self.screen_lines();
|
||||
|
||||
let num_cols = size.columns;
|
||||
let num_lines = size.screen_lines;
|
||||
let num_cols = size.columns();
|
||||
let num_lines = size.screen_lines();
|
||||
|
||||
if old_cols == num_cols && old_lines == num_lines {
|
||||
debug!("Term::resize dimensions unchanged");
|
||||
|
@ -2077,10 +1918,11 @@ impl<T: EventListener> Handler for Term<T> {
|
|||
|
||||
#[inline]
|
||||
fn text_area_size_pixels(&mut self) {
|
||||
let width = self.cell_width * self.columns();
|
||||
let height = self.cell_height * self.screen_lines();
|
||||
let text = format!("\x1b[4;{};{}t", height, width);
|
||||
self.event_proxy.send_event(Event::PtyWrite(text));
|
||||
self.event_proxy.send_event(Event::TextAreaSizeRequest(Arc::new(move |window_size| {
|
||||
let height = window_size.num_lines * window_size.cell_height;
|
||||
let width = window_size.num_cols * window_size.cell_width;
|
||||
format!("\x1b[4;{};{}t", height, width)
|
||||
})));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -2217,11 +2059,38 @@ impl<'a> RenderableContent<'a> {
|
|||
pub mod test {
|
||||
use super::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::index::Column;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TermSize {
|
||||
pub columns: usize,
|
||||
pub screen_lines: usize,
|
||||
}
|
||||
|
||||
impl TermSize {
|
||||
pub fn new(columns: usize, screen_lines: usize) -> Self {
|
||||
Self { columns, screen_lines }
|
||||
}
|
||||
}
|
||||
|
||||
impl Dimensions for TermSize {
|
||||
fn total_lines(&self) -> usize {
|
||||
self.screen_lines()
|
||||
}
|
||||
|
||||
fn screen_lines(&self) -> usize {
|
||||
self.screen_lines
|
||||
}
|
||||
|
||||
fn columns(&self) -> usize {
|
||||
self.columns
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a terminal from its content as string.
|
||||
///
|
||||
/// A `\n` will break line and `\r\n` will break line without wrapping.
|
||||
|
@ -2250,8 +2119,8 @@ pub mod test {
|
|||
.unwrap_or(0);
|
||||
|
||||
// Create terminal with the appropriate dimensions.
|
||||
let size = SizeInfo::new(num_cols as f32, lines.len() as f32, 1., 1., 0., 0., false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(num_cols, lines.len());
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Fill terminal with content.
|
||||
for (line, text) in lines.iter().enumerate() {
|
||||
|
@ -2291,11 +2160,12 @@ mod tests {
|
|||
use crate::index::{Column, Point, Side};
|
||||
use crate::selection::{Selection, SelectionType};
|
||||
use crate::term::cell::{Cell, Flags};
|
||||
use crate::term::test::TermSize;
|
||||
|
||||
#[test]
|
||||
fn scroll_display_page_up() {
|
||||
let size = SizeInfo::new(5., 10., 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(5, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 11 lines of scrollback.
|
||||
for _ in 0..20 {
|
||||
|
@ -2320,8 +2190,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn scroll_display_page_down() {
|
||||
let size = SizeInfo::new(5., 10., 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(5, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 11 lines of scrollback.
|
||||
for _ in 0..20 {
|
||||
|
@ -2350,8 +2220,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn simple_selection_works() {
|
||||
let size = SizeInfo::new(5., 5., 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(5, 5);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
let grid = term.grid_mut();
|
||||
for i in 0..4 {
|
||||
if i == 1 {
|
||||
|
@ -2396,8 +2266,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn semantic_selection_works() {
|
||||
let size = SizeInfo::new(5., 3., 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(5, 3);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
let mut grid: Grid<Cell> = Grid::new(3, 5, 0);
|
||||
for i in 0..5 {
|
||||
for j in 0..2 {
|
||||
|
@ -2444,8 +2314,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn line_selection_works() {
|
||||
let size = SizeInfo::new(5., 1., 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(5, 1);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
let mut grid: Grid<Cell> = Grid::new(1, 5, 0);
|
||||
for i in 0..5 {
|
||||
grid[Line(0)][Column(i)].c = 'a';
|
||||
|
@ -2465,8 +2335,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn block_selection_works() {
|
||||
let size = SizeInfo::new(5., 5., 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(5, 5);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
let grid = term.grid_mut();
|
||||
for i in 1..4 {
|
||||
grid[Line(i)][Column(0)].c = '"';
|
||||
|
@ -2521,8 +2391,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn input_line_drawing_character() {
|
||||
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(7, 17);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
let cursor = Point::new(Line(0), Column(0));
|
||||
term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing);
|
||||
term.input('a');
|
||||
|
@ -2532,8 +2402,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn clearing_viewport_keeps_history_position() {
|
||||
let size = SizeInfo::new(10.0, 20.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(10, 20);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..29 {
|
||||
|
@ -2553,8 +2423,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn clearing_viewport_with_vi_mode_keeps_history_position() {
|
||||
let size = SizeInfo::new(10.0, 20.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(10, 20);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..29 {
|
||||
|
@ -2579,8 +2449,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn clearing_scrollback_resets_display_offset() {
|
||||
let size = SizeInfo::new(10.0, 20.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(10, 20);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..29 {
|
||||
|
@ -2600,8 +2470,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn clearing_scrollback_sets_vi_cursor_into_viewport() {
|
||||
let size = SizeInfo::new(10.0, 20.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(10, 20);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..29 {
|
||||
|
@ -2626,8 +2496,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn clear_saved_lines() {
|
||||
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(7, 17);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Add one line of scrollback.
|
||||
term.grid.scroll_up(&(Line(0)..Line(1)), 1);
|
||||
|
@ -2648,8 +2518,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn vi_cursor_keep_pos_on_scrollback_buffer() {
|
||||
let size = SizeInfo::new(5., 10., 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(5, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 11 lines of scrollback.
|
||||
for _ in 0..20 {
|
||||
|
@ -2668,8 +2538,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn grow_lines_updates_active_cursor_pos() {
|
||||
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let mut size = TermSize::new(100, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..19 {
|
||||
|
@ -2688,8 +2558,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn grow_lines_updates_inactive_cursor_pos() {
|
||||
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let mut size = TermSize::new(100, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..19 {
|
||||
|
@ -2714,8 +2584,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn shrink_lines_updates_active_cursor_pos() {
|
||||
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let mut size = TermSize::new(100, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..19 {
|
||||
|
@ -2734,8 +2604,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn shrink_lines_updates_inactive_cursor_pos() {
|
||||
let mut size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let mut size = TermSize::new(100, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Create 10 lines of scrollback.
|
||||
for _ in 0..19 {
|
||||
|
@ -2760,8 +2630,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn damage_public_usage() {
|
||||
let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(10, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
// Reset terminal for partial damage tests since it's initialized as fully damaged.
|
||||
term.reset_damage();
|
||||
|
||||
|
@ -2840,8 +2710,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn damage_cursor_movements() {
|
||||
let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(10, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
let num_cols = term.columns();
|
||||
// Reset terminal for partial damage tests since it's initialized as fully damaged.
|
||||
term.reset_damage();
|
||||
|
@ -2938,8 +2808,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn damage_vi_movements() {
|
||||
let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(10, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
let num_cols = term.columns();
|
||||
// Reset terminal for partial damage tests since it's initialized as fully damaged.
|
||||
term.reset_damage();
|
||||
|
@ -2970,8 +2840,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn full_damage() {
|
||||
let size = SizeInfo::new(100.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(100, 10);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
assert!(term.damage.is_fully_damaged);
|
||||
for _ in 0..20 {
|
||||
|
@ -3049,15 +2919,15 @@ mod tests {
|
|||
assert!(term.damage.is_fully_damaged);
|
||||
term.reset_damage();
|
||||
|
||||
let size = SizeInfo::new(10.0, 10.0, 1.0, 1.0, 0.0, 0.0, false);
|
||||
let size = TermSize::new(10, 10);
|
||||
term.resize(size);
|
||||
assert!(term.damage.is_fully_damaged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_title() {
|
||||
let size = SizeInfo::new(21.0, 51.0, 3.0, 3.0, 0.0, 0.0, false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(7, 17);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
|
||||
// Title None by default.
|
||||
assert_eq!(term.title, None);
|
||||
|
|
|
@ -510,8 +510,7 @@ mod tests {
|
|||
|
||||
use crate::config::Config;
|
||||
use crate::index::{Column, Line};
|
||||
use crate::term::test::mock_term;
|
||||
use crate::term::SizeInfo;
|
||||
use crate::term::test::{mock_term, TermSize};
|
||||
|
||||
#[test]
|
||||
fn regex_right() {
|
||||
|
@ -810,8 +809,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn wide_without_spacer() {
|
||||
let size = SizeInfo::new(2., 2., 1., 1., 0., 0., false);
|
||||
let mut term = Term::new(&Config::default(), size, ());
|
||||
let size = TermSize::new(2, 2);
|
||||
let mut term = Term::new(&Config::default(), &size, ());
|
||||
term.grid[Line(0)][Column(0)].c = 'x';
|
||||
term.grid[Line(0)][Column(1)].c = '字';
|
||||
term.grid[Line(0)][Column(1)].flags = Flags::WIDE_CHAR;
|
||||
|
|
|
@ -22,9 +22,7 @@ use signal_hook::consts as sigconsts;
|
|||
use signal_hook_mio::v0_6::Signals;
|
||||
|
||||
use crate::config::{Program, PtyConfig};
|
||||
use crate::event::OnResize;
|
||||
use crate::grid::Dimensions;
|
||||
use crate::term::SizeInfo;
|
||||
use crate::event::{OnResize, WindowSize};
|
||||
use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
|
||||
|
||||
macro_rules! die {
|
||||
|
@ -36,11 +34,11 @@ macro_rules! die {
|
|||
|
||||
/// 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;
|
||||
win_size.ws_ypixel = 0;
|
||||
let mut window_size = size;
|
||||
window_size.ws_xpixel = 0;
|
||||
window_size.ws_ypixel = 0;
|
||||
|
||||
let ends = openpty(Some(&win_size), None).expect("openpty failed");
|
||||
let ends = openpty(Some(&window_size), None).expect("openpty failed");
|
||||
|
||||
(ends.master, ends.slave)
|
||||
}
|
||||
|
@ -137,8 +135,8 @@ fn default_shell(pw: &Passwd<'_>) -> Program {
|
|||
}
|
||||
|
||||
/// Create a new TTY and return a handle to interact with it.
|
||||
pub fn new(config: &PtyConfig, size: &SizeInfo, window_id: Option<usize>) -> Result<Pty> {
|
||||
let (master, slave) = make_pty(size.to_winsize());
|
||||
pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: Option<usize>) -> Result<Pty> {
|
||||
let (master, slave) = make_pty(window_size.to_winsize());
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
if let Ok(mut termios) = termios::tcgetattr(master) {
|
||||
|
@ -229,7 +227,7 @@ pub fn new(config: &PtyConfig, size: &SizeInfo, window_id: Option<usize>) -> Res
|
|||
signals,
|
||||
signals_token: mio::Token::from(0),
|
||||
};
|
||||
pty.on_resize(size);
|
||||
pty.on_resize(window_size);
|
||||
Ok(pty)
|
||||
},
|
||||
Err(err) => Err(Error::new(
|
||||
|
@ -347,8 +345,8 @@ impl OnResize for Pty {
|
|||
///
|
||||
/// Tells the kernel that the window size changed with the new pixel
|
||||
/// dimensions and line/column counts.
|
||||
fn on_resize(&mut self, size: &SizeInfo) {
|
||||
let win = size.to_winsize();
|
||||
fn on_resize(&mut self, window_size: WindowSize) {
|
||||
let win = window_size.to_winsize();
|
||||
|
||||
let res = unsafe { libc::ioctl(self.file.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) };
|
||||
|
||||
|
@ -361,17 +359,17 @@ impl OnResize for Pty {
|
|||
/// Types that can produce a `libc::winsize`.
|
||||
pub trait ToWinsize {
|
||||
/// Get a `libc::winsize`.
|
||||
fn to_winsize(&self) -> winsize;
|
||||
fn to_winsize(self) -> winsize;
|
||||
}
|
||||
|
||||
impl<'a> ToWinsize for &'a SizeInfo {
|
||||
fn to_winsize(&self) -> winsize {
|
||||
winsize {
|
||||
ws_row: self.screen_lines() as libc::c_ushort,
|
||||
ws_col: self.columns() as libc::c_ushort,
|
||||
ws_xpixel: self.width() as libc::c_ushort,
|
||||
ws_ypixel: self.height() as libc::c_ushort,
|
||||
}
|
||||
impl ToWinsize for WindowSize {
|
||||
fn to_winsize(self) -> winsize {
|
||||
let ws_row = self.num_lines as libc::c_ushort;
|
||||
let ws_col = self.num_cols as libc::c_ushort;
|
||||
|
||||
let ws_xpixel = ws_col * self.cell_width as libc::c_ushort;
|
||||
let ws_ypixel = ws_row * self.cell_height as libc::c_ushort;
|
||||
winsize { ws_row, ws_col, ws_xpixel, ws_ypixel }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::io::Error;
|
||||
use std::os::windows::io::IntoRawHandle;
|
||||
use std::{i16, mem, ptr};
|
||||
use std::{mem, ptr};
|
||||
|
||||
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
|
||||
use winapi::shared::basetsd::{PSIZE_T, SIZE_T};
|
||||
|
@ -16,9 +16,7 @@ use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTF_USESTDHANDLES, ST
|
|||
use winapi::um::wincontypes::{COORD, HPCON};
|
||||
|
||||
use crate::config::PtyConfig;
|
||||
use crate::event::OnResize;
|
||||
use crate::grid::Dimensions;
|
||||
use crate::term::SizeInfo;
|
||||
use crate::event::{OnResize, WindowSize};
|
||||
use crate::tty::windows::child::ChildExitWatcher;
|
||||
use crate::tty::windows::{cmdline, win32_string, Pty};
|
||||
|
||||
|
@ -40,7 +38,7 @@ impl Drop for Conpty {
|
|||
// The ConPTY handle can be sent between threads.
|
||||
unsafe impl Send for Conpty {}
|
||||
|
||||
pub fn new(config: &PtyConfig, size: &SizeInfo) -> Option<Pty> {
|
||||
pub fn new(config: &PtyConfig, window_size: WindowSize) -> Option<Pty> {
|
||||
let mut pty_handle = 0 as HPCON;
|
||||
|
||||
// Passing 0 as the size parameter allows the "system default" buffer
|
||||
|
@ -50,13 +48,10 @@ pub fn new(config: &PtyConfig, size: &SizeInfo) -> Option<Pty> {
|
|||
let (conout, conout_pty_handle) = miow::pipe::anonymous(0).unwrap();
|
||||
let (conin_pty_handle, conin) = miow::pipe::anonymous(0).unwrap();
|
||||
|
||||
let coord =
|
||||
coord_from_sizeinfo(size).expect("Overflow when creating initial size on pseudoconsole");
|
||||
|
||||
// Create the Pseudo Console, using the pipes.
|
||||
let result = unsafe {
|
||||
CreatePseudoConsole(
|
||||
coord,
|
||||
window_size.into(),
|
||||
conin_pty_handle.into_raw_handle(),
|
||||
conout_pty_handle.into_raw_handle(),
|
||||
0,
|
||||
|
@ -174,22 +169,16 @@ fn panic_shell_spawn() {
|
|||
}
|
||||
|
||||
impl OnResize for Conpty {
|
||||
fn on_resize(&mut self, sizeinfo: &SizeInfo) {
|
||||
if let Some(coord) = coord_from_sizeinfo(sizeinfo) {
|
||||
let result = unsafe { ResizePseudoConsole(self.handle, coord) };
|
||||
assert_eq!(result, S_OK);
|
||||
}
|
||||
fn on_resize(&mut self, window_size: WindowSize) {
|
||||
let result = unsafe { ResizePseudoConsole(self.handle, window_size.into()) };
|
||||
assert_eq!(result, S_OK);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to build a COORD from a SizeInfo, returning None in overflow cases.
|
||||
fn coord_from_sizeinfo(size: &SizeInfo) -> Option<COORD> {
|
||||
let lines = size.screen_lines();
|
||||
let columns = size.columns();
|
||||
|
||||
if columns <= i16::MAX as usize && lines <= i16::MAX as usize {
|
||||
Some(COORD { X: columns as i16, Y: lines as i16 })
|
||||
} else {
|
||||
None
|
||||
impl From<WindowSize> for COORD {
|
||||
fn from(window_size: WindowSize) -> Self {
|
||||
let lines = window_size.num_lines;
|
||||
let columns = window_size.num_cols;
|
||||
COORD { X: columns as i16, Y: lines as i16 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ use std::os::windows::ffi::OsStrExt;
|
|||
use std::sync::mpsc::TryRecvError;
|
||||
|
||||
use crate::config::{Program, PtyConfig};
|
||||
use crate::event::OnResize;
|
||||
use crate::term::SizeInfo;
|
||||
use crate::event::{OnResize, WindowSize};
|
||||
use crate::tty::windows::child::ChildExitWatcher;
|
||||
use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
|
||||
|
||||
|
@ -28,8 +27,9 @@ pub struct Pty {
|
|||
child_watcher: ChildExitWatcher,
|
||||
}
|
||||
|
||||
pub fn new(config: &PtyConfig, size: &SizeInfo, _window_id: Option<usize>) -> Result<Pty> {
|
||||
conpty::new(config, size).ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty"))
|
||||
pub fn new(config: &PtyConfig, window_size: WindowSize, _window_id: Option<usize>) -> Result<Pty> {
|
||||
conpty::new(config, window_size)
|
||||
.ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty"))
|
||||
}
|
||||
|
||||
impl Pty {
|
||||
|
@ -160,8 +160,8 @@ impl EventedPty for Pty {
|
|||
}
|
||||
|
||||
impl OnResize for Pty {
|
||||
fn on_resize(&mut self, size: &SizeInfo) {
|
||||
self.backend.on_resize(size)
|
||||
fn on_resize(&mut self, window_size: WindowSize) {
|
||||
self.backend.on_resize(window_size)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -381,11 +381,12 @@ mod tests {
|
|||
use crate::ansi::Handler;
|
||||
use crate::config::Config;
|
||||
use crate::index::{Column, Line};
|
||||
use crate::term::{SizeInfo, Term};
|
||||
use crate::term::test::TermSize;
|
||||
use crate::term::Term;
|
||||
|
||||
fn term() -> Term<()> {
|
||||
let size = SizeInfo::new(20., 20., 1.0, 1.0, 0.0, 0.0, false);
|
||||
Term::new(&Config::default(), size, ())
|
||||
let size = TermSize::new(20, 20);
|
||||
Term::new(&Config::default(), &size, ())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -11,7 +11,8 @@ use alacritty_terminal::event::{Event, EventListener};
|
|||
use alacritty_terminal::grid::{Dimensions, Grid};
|
||||
use alacritty_terminal::index::{Column, Line};
|
||||
use alacritty_terminal::term::cell::Cell;
|
||||
use alacritty_terminal::term::{SizeInfo, Term};
|
||||
use alacritty_terminal::term::test::TermSize;
|
||||
use alacritty_terminal::term::Term;
|
||||
|
||||
macro_rules! ref_tests {
|
||||
($($name:ident)*) => {
|
||||
|
@ -98,14 +99,14 @@ fn ref_test(dir: &Path) {
|
|||
let serialized_grid = fs::read_to_string(dir.join("grid.json")).unwrap();
|
||||
let serialized_cfg = fs::read_to_string(dir.join("config.json")).unwrap();
|
||||
|
||||
let size: SizeInfo = json::from_str(&serialized_size).unwrap();
|
||||
let size: TermSize = json::from_str(&serialized_size).unwrap();
|
||||
let grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap();
|
||||
let ref_config: RefConfig = json::from_str(&serialized_cfg).unwrap();
|
||||
|
||||
let mut config = Config::default();
|
||||
config.scrolling.set_history(ref_config.history_size);
|
||||
|
||||
let mut terminal = Term::new(&config, size, Mock);
|
||||
let mut terminal = Term::new(&config, &size, Mock);
|
||||
let mut parser = ansi::Processor::new();
|
||||
|
||||
for byte in recording {
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1916.0,"height":1054.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":106,"screen_lines":30}
|
||||
{"columns":106,"screen_lines":30}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":662.0,"height":708.0,"cell_width":10.0,"cell_height":22.0,"padding_x":0.0,"padding_y":0.0,"columns":66,"screen_lines":32}
|
||||
{"columns":66,"screen_lines":32}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":939.0,"height":1020.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":6.0,"columns":116,"screen_lines":63}
|
||||
{"columns":116,"screen_lines":63}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1262.0,"height":690.0,"cell_width":9.0,"cell_height":18.0,"padding_x":0.0,"padding_y":0.0,"screen_lines":38,"columns":140}
|
||||
{"screen_lines":38,"columns":140}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1916.0,"height":1054.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":106,"screen_lines":30}
|
||||
{"columns":106,"screen_lines":30}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1916.0,"height":1054.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":106,"screen_lines":30}
|
||||
{"columns":106,"screen_lines":30}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1916.0,"height":1054.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":106,"screen_lines":30}
|
||||
{"columns":106,"screen_lines":30}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":939.0,"height":503.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":3.0,"columns":116,"screen_lines":31}
|
||||
{"columns":116,"screen_lines":31}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1916.0,"height":1054.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":106,"screen_lines":30}
|
||||
{"columns":106,"screen_lines":30}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1916.0,"height":1054.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":106,"screen_lines":30}
|
||||
{"columns":106,"screen_lines":30}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":939.0,"height":503.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":3.0,"columns":116,"screen_lines":31}
|
||||
{"columns":116,"screen_lines":31}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1040.0,"cell_width":11.0,"cell_height":22.0,"padding_x":0.0,"padding_y":0.0,"columns":172,"screen_lines":47}
|
||||
{"columns":172,"screen_lines":47}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1259.0,"height":683.0,"cell_width":9.0,"cell_height":19.0,"padding_x":4.0,"padding_y":9.0,"columns":139,"screen_lines":35}
|
||||
{"columns":139,"screen_lines":35}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1259.0,"height":683.0,"cell_width":9.0,"cell_height":19.0,"padding_x":4.0,"padding_y":9.0,"columns":139,"screen_lines":35}
|
||||
{"columns":139,"screen_lines":35}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1840.0,"height":1040.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":102,"screen_lines":29}
|
||||
{"columns":102,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":70.0,"height":63.0,"cell_width":7.0,"cell_height":21.0,"padding_x":0.0,"padding_y":0.0,"columns":10,"screen_lines":3}
|
||||
{"columns":10,"screen_lines":3}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1259.0,"height":683.0,"cell_width":9.0,"cell_height":19.0,"padding_x":4.0,"padding_y":9.0,"columns":139,"screen_lines":35}
|
||||
{"columns":139,"screen_lines":35}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":939.0,"height":1020.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":6.0,"columns":116,"screen_lines":63}
|
||||
{"columns":116,"screen_lines":63}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":948.0,"height":1041.0,"cell_width":7.0,"cell_height":16.0,"padding_x":5.0,"padding_y":8.0,"screen_lines":64,"columns":134}
|
||||
{"screen_lines":64,"columns":134}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1916.0,"height":2121.0,"cell_width":11.0,"cell_height":22.0,"padding_x":0.0,"padding_y":0.0,"columns":174,"screen_lines":96}
|
||||
{"columns":174,"screen_lines":96}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1040.0,"cell_width":11.0,"cell_height":22.0,"padding_x":0.0,"padding_y":0.0,"columns":172,"screen_lines":47}
|
||||
{"columns":172,"screen_lines":47}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":644.0,"height":412.0,"cell_width":8.0,"cell_height":17.0,"padding_x":0.0,"padding_y":0.0,"columns":80,"screen_lines":24}
|
||||
{"columns":80,"screen_lines":24}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":939.0,"height":503.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":3.0,"columns":116,"screen_lines":31}
|
||||
{"columns":116,"screen_lines":31}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1259.0,"height":683.0,"cell_width":9.0,"cell_height":19.0,"padding_x":4.0,"padding_y":9.0,"columns":139,"screen_lines":35}
|
||||
{"columns":139,"screen_lines":35}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1259.0,"height":683.0,"cell_width":9.0,"cell_height":19.0,"padding_x":4.0,"padding_y":9.0,"screen_lines":35,"columns":139}
|
||||
{"screen_lines":35,"columns":139}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"width":1900.0,"height":1038.0,"cell_width":18.0,"cell_height":35.0,"padding_x":0.0,"padding_y":0.0,"columns":105,"screen_lines":29}
|
||||
{"columns":105,"screen_lines":29}
|
||||
|
|
Loading…
Reference in New Issue