Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ ego-tree = "0.6.2"
members = ["tui", "core"]
resolver = "2"

[patch.crates-io]
vt100 = { path = "vt100" }

[profile.release]
opt-level = "z"
debug = false
Expand Down
2 changes: 1 addition & 1 deletion tui/src/running_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ impl RunningCommand {
let buffer = mutex.as_ref().unwrap();
parser.process(buffer);
// Adjust the screen content based on the scroll offset
parser.set_scrollback(self.scroll_offset);
parser.screen_mut().set_scrollback(self.scroll_offset);
parser.screen().clone()
}

Expand Down
29 changes: 29 additions & 0 deletions vt100/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "vt100"
version = "0.15.2"
authors = ["Jesse Luehrs <doy@tozt.net>"]
edition = "2021"

description = "Library for parsing terminal data"
homepage = "https://github.com/doy/vt100-rust"
repository = "https://github.com/doy/vt100-rust"
readme = "README.md"
keywords = ["terminal", "vt100"]
categories = ["command-line-interface", "encoding"]
license = "MIT"
include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"]

[dependencies]
itoa = "1.0.9"
log = "0.4.19"
unicode-width = "0.1.10"
vte = "0.11.1"

[dev-dependencies]
nix = "0.26.2"
quickcheck = "1.0"
rand = "0.8"
serde = { version = "1.0.182", features = ["derive"] }
serde_json = "1.0.104"
terminal_size = "0.2.6"
vte = "0.11.1"
21 changes: 21 additions & 0 deletions vt100/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 Jesse Luehrs

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
128 changes: 128 additions & 0 deletions vt100/src/attrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::term::BufWrite as _;

/// Represents a foreground or background color for cells.
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum Color {
/// The default terminal color.
Default,

/// An indexed terminal color.
Idx(u8),

/// An RGB terminal color. The parameters are (red, green, blue).
Rgb(u8, u8, u8),
}

impl Default for Color {
fn default() -> Self {
Self::Default
}
}

const TEXT_MODE_BOLD: u8 = 0b0000_0001;
const TEXT_MODE_ITALIC: u8 = 0b0000_0010;
const TEXT_MODE_UNDERLINE: u8 = 0b0000_0100;
const TEXT_MODE_INVERSE: u8 = 0b0000_1000;

#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Attrs {
pub fgcolor: Color,
pub bgcolor: Color,
pub mode: u8,
}

impl Attrs {
pub fn bold(&self) -> bool {
self.mode & TEXT_MODE_BOLD != 0
}

pub fn set_bold(&mut self, bold: bool) {
if bold {
self.mode |= TEXT_MODE_BOLD;
} else {
self.mode &= !TEXT_MODE_BOLD;
}
}

pub fn italic(&self) -> bool {
self.mode & TEXT_MODE_ITALIC != 0
}

pub fn set_italic(&mut self, italic: bool) {
if italic {
self.mode |= TEXT_MODE_ITALIC;
} else {
self.mode &= !TEXT_MODE_ITALIC;
}
}

pub fn underline(&self) -> bool {
self.mode & TEXT_MODE_UNDERLINE != 0
}

pub fn set_underline(&mut self, underline: bool) {
if underline {
self.mode |= TEXT_MODE_UNDERLINE;
} else {
self.mode &= !TEXT_MODE_UNDERLINE;
}
}

pub fn inverse(&self) -> bool {
self.mode & TEXT_MODE_INVERSE != 0
}

pub fn set_inverse(&mut self, inverse: bool) {
if inverse {
self.mode |= TEXT_MODE_INVERSE;
} else {
self.mode &= !TEXT_MODE_INVERSE;
}
}

pub fn write_escape_code_diff(
&self,
contents: &mut Vec<u8>,
other: &Self,
) {
if self != other && self == &Self::default() {
crate::term::ClearAttrs.write_buf(contents);
return;
}

let attrs = crate::term::Attrs::default();

let attrs = if self.fgcolor == other.fgcolor {
attrs
} else {
attrs.fgcolor(self.fgcolor)
};
let attrs = if self.bgcolor == other.bgcolor {
attrs
} else {
attrs.bgcolor(self.bgcolor)
};
let attrs = if self.bold() == other.bold() {
attrs
} else {
attrs.bold(self.bold())
};
let attrs = if self.italic() == other.italic() {
attrs
} else {
attrs.italic(self.italic())
};
let attrs = if self.underline() == other.underline() {
attrs
} else {
attrs.underline(self.underline())
};
let attrs = if self.inverse() == other.inverse() {
attrs
} else {
attrs.inverse(self.inverse())
};

attrs.write_buf(contents);
}
}
16 changes: 16 additions & 0 deletions vt100/src/callbacks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// This trait is used with `Parser::process_cb` to handle extra escape
/// sequences that don't have an impact on the terminal screen directly.
pub trait Callbacks {
/// This callback is called when the terminal requests an audible bell
/// (typically with `^G`).
fn audible_bell(&mut self, _: &mut crate::Screen) {}
/// This callback is called when the terminal requests an visual bell
/// (typically with `\eg`).
fn visual_bell(&mut self, _: &mut crate::Screen) {}
/// This callback is called when the terminal requests a resize
/// (typically with `\e[8;<rows>;<cols>t`).
fn resize(&mut self, _: &mut crate::Screen, _request: (u16, u16)) {}
/// This callback is called when the terminal receives invalid input
/// (such as an invalid UTF-8 character or an unused control character).
fn error(&mut self, _: &mut crate::Screen) {}
}
166 changes: 166 additions & 0 deletions vt100/src/cell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use unicode_width::UnicodeWidthChar as _;

const CODEPOINTS_IN_CELL: usize = 6;

/// Represents a single terminal cell.
#[derive(Clone, Debug, Eq)]
pub struct Cell {
contents: [char; CODEPOINTS_IN_CELL],
len: u8,
attrs: crate::attrs::Attrs,
}

impl PartialEq<Self> for Cell {
fn eq(&self, other: &Self) -> bool {
if self.len != other.len {
return false;
}
if self.attrs != other.attrs {
return false;
}
let len = self.len();
// self.len() always returns a valid value
self.contents[..len] == other.contents[..len]
}
}

impl Cell {
pub(crate) fn new() -> Self {
Self {
contents: Default::default(),
len: 0,
attrs: crate::attrs::Attrs::default(),
}
}

#[inline]
fn len(&self) -> usize {
usize::from(self.len & 0x0f)
}

pub(crate) fn set(&mut self, c: char, a: crate::attrs::Attrs) {
self.contents[0] = c;
self.len = 1;
// strings in this context should always be an arbitrary character
// followed by zero or more zero-width characters, so we should only
// have to look at the first character
self.set_wide(c.width().unwrap_or(1) > 1);
self.attrs = a;
}

pub(crate) fn append(&mut self, c: char) {
let len = self.len();
if len >= CODEPOINTS_IN_CELL {
return;
}
if len == 0 {
// 0 is always less than 6
self.contents[0] = ' ';
self.len += 1;
}

let len = self.len();
// we already checked that len < CODEPOINTS_IN_CELL
self.contents[len] = c;
self.len += 1;
}

pub(crate) fn clear(&mut self, attrs: crate::attrs::Attrs) {
self.len = 0;
self.attrs = attrs;
}

/// Returns the text contents of the cell.
///
/// Can include multiple unicode characters if combining characters are
/// used, but will contain at most one character with a non-zero character
/// width.
#[must_use]
pub fn contents(&self) -> String {
let mut s = String::with_capacity(CODEPOINTS_IN_CELL * 4);
for c in self.contents.iter().take(self.len()) {
s.push(*c);
}
s
}

/// Returns whether the cell contains any text data.
#[must_use]
pub fn has_contents(&self) -> bool {
self.len > 0
}

/// Returns whether the text data in the cell represents a wide character.
#[must_use]
pub fn is_wide(&self) -> bool {
self.len & 0x80 == 0x80
}

/// Returns whether the cell contains the second half of a wide character
/// (in other words, whether the previous cell in the row contains a wide
/// character)
#[must_use]
pub fn is_wide_continuation(&self) -> bool {
self.len & 0x40 == 0x40
}

fn set_wide(&mut self, wide: bool) {
if wide {
self.len |= 0x80;
} else {
self.len &= 0x7f;
}
}

pub(crate) fn set_wide_continuation(&mut self, wide: bool) {
if wide {
self.len |= 0x40;
} else {
self.len &= 0xbf;
}
}

pub(crate) fn attrs(&self) -> &crate::attrs::Attrs {
&self.attrs
}

/// Returns the foreground color of the cell.
#[must_use]
pub fn fgcolor(&self) -> crate::Color {
self.attrs.fgcolor
}

/// Returns the background color of the cell.
#[must_use]
pub fn bgcolor(&self) -> crate::Color {
self.attrs.bgcolor
}

/// Returns whether the cell should be rendered with the bold text
/// attribute.
#[must_use]
pub fn bold(&self) -> bool {
self.attrs.bold()
}

/// Returns whether the cell should be rendered with the italic text
/// attribute.
#[must_use]
pub fn italic(&self) -> bool {
self.attrs.italic()
}

/// Returns whether the cell should be rendered with the underlined text
/// attribute.
#[must_use]
pub fn underline(&self) -> bool {
self.attrs.underline()
}

/// Returns whether the cell should be rendered with the inverse text
/// attribute.
#[must_use]
pub fn inverse(&self) -> bool {
self.attrs.inverse()
}
}
Loading