term: more attribute support
This commit is contained in:
parent
7485476caa
commit
3567b79e1d
28
userspace/Cargo.lock
generated
28
userspace/Cargo.lock
generated
@ -2422,6 +2422,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
@ -2728,7 +2737,9 @@ dependencies = [
|
||||
"log",
|
||||
"logsink",
|
||||
"rusttype",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2832,11 +2843,26 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@ -2845,6 +2871,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
@ -38,6 +38,7 @@ sha2 = { version = "0.10.8" }
|
||||
chrono = { version = "0.4.31", default-features = false }
|
||||
postcard = { version = "1.1.1", features = ["alloc"] }
|
||||
tui = { version = "0.19.0", default-features = false }
|
||||
toml = "0.8.20"
|
||||
|
||||
raqote = { version = "0.8.3", default-features = false }
|
||||
|
||||
|
BIN
userspace/etc/fonts/fixed-bold-italic.ttf
Normal file
BIN
userspace/etc/fonts/fixed-bold-italic.ttf
Normal file
Binary file not shown.
BIN
userspace/etc/fonts/fixed-bold.ttf
Normal file
BIN
userspace/etc/fonts/fixed-bold.ttf
Normal file
Binary file not shown.
BIN
userspace/etc/fonts/fixed-italic.ttf
Normal file
BIN
userspace/etc/fonts/fixed-italic.ttf
Normal file
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
fixed-regular.ttf (Liberation Mono):
|
||||
fixed-***.ttf (Liberation Mono):
|
||||
|
||||
Digitized data copyright (c) 2010 Google Corporation
|
||||
with Reserved Font Arimo, Tinos and Cousine.
|
||||
|
0
userspace/etc/term.toml
Normal file
0
userspace/etc/term.toml
Normal file
@ -123,7 +123,7 @@ impl RawTerminal for Stdout {
|
||||
}
|
||||
|
||||
fn raw_set_cursor_style(&mut self, style: CursorStyle) -> io::Result<()> {
|
||||
// TODO yggdrasil support for cursor styles
|
||||
// TODO term does not support spaces in ctl-seqs
|
||||
#[cfg(not(target_os = "yggdrasil"))]
|
||||
{
|
||||
match style {
|
||||
@ -133,7 +133,10 @@ impl RawTerminal for Stdout {
|
||||
}
|
||||
#[cfg(target_os = "yggdrasil")]
|
||||
{
|
||||
let _ = style;
|
||||
match style {
|
||||
CursorStyle::Default => self.write_all(b"\x1B[0q")?,
|
||||
CursorStyle::Line => self.write_all(b"\x1B[6q")?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ impl tui::backend::Backend for Term {
|
||||
if bold {
|
||||
self.stdout.raw_set_style(1)?;
|
||||
} else {
|
||||
self.stdout.raw_set_style(0)?;
|
||||
self.stdout.raw_set_style(22)?;
|
||||
}
|
||||
self.stdout.raw_set_color(3, color)?;
|
||||
} else {
|
||||
|
@ -426,6 +426,15 @@ impl Buffer {
|
||||
}
|
||||
|
||||
pub fn display(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
|
||||
match self.mode {
|
||||
Mode::Normal => {
|
||||
term.set_cursor_style(CursorStyle::Default)?;
|
||||
}
|
||||
Mode::Insert => {
|
||||
term.set_cursor_style(CursorStyle::Line)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (row, line) in self
|
||||
.lines
|
||||
.iter()
|
||||
|
@ -1,23 +1,34 @@
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
fn main() {
|
||||
let mut threads = vec![];
|
||||
for i in 0..4 {
|
||||
let jh = thread::Builder::new()
|
||||
.name(format!("tst-thread-{i}"))
|
||||
.spawn(move || {
|
||||
let current = thread::current();
|
||||
for _ in 0..100 {
|
||||
println!("Hi from thread {:?}", current.name());
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
const COLORS: &[(usize, &str)] = &[
|
||||
(1, "Red"),
|
||||
(2, "Green"),
|
||||
(3, "Yellow"),
|
||||
(4, "Blue"),
|
||||
(5, "Magenta"),
|
||||
(6, "Cyan"),
|
||||
(7, "White"),
|
||||
];
|
||||
|
||||
threads.push(jh);
|
||||
println!(
|
||||
"{:<8} \x1B[1m{:<8}\x1B[22m \x1B[3m{:<8} \x1B[1m{:<8}\x1B[0m",
|
||||
"Normal", "Bold", "Italic", "Bold/it"
|
||||
);
|
||||
for &(color, text) in COLORS {
|
||||
print!("\x1B[3{color}m");
|
||||
// Normal
|
||||
print!("{text:<8} ");
|
||||
// Bold
|
||||
print!("\x1B[1m");
|
||||
print!("{text:<8} ");
|
||||
print!("\x1B[22m");
|
||||
// Italic
|
||||
print!("\x1B[3m");
|
||||
print!("{text:<8} ");
|
||||
// Bold/italic
|
||||
print!("\x1B[1m");
|
||||
print!("{text:<8}");
|
||||
println!("\x1B[0m");
|
||||
}
|
||||
|
||||
for thread in threads.into_iter() {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
println!("\x1B[4m\x1B[1mUnderlined\x1B[0m, \x1B[9m\x1B[3mStrikethrough\x1B[0m? \x1B[4m\x1B[9mBOTH!!!\x1B[0m");
|
||||
}
|
||||
|
@ -12,5 +12,7 @@ logsink.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
clap.workspace = true
|
||||
serde.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
rusttype = "0.9.3"
|
||||
|
@ -1,19 +1,15 @@
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(usize)]
|
||||
pub enum Color {
|
||||
Black = 0,
|
||||
Red = 1,
|
||||
Green = 2,
|
||||
Yellow = 3,
|
||||
Blue = 4,
|
||||
Magenta = 5,
|
||||
Cyan = 6,
|
||||
White = 7,
|
||||
}
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use serde::{
|
||||
de::{Unexpected, Visitor},
|
||||
Deserialize,
|
||||
};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct DisplayColor {
|
||||
pub struct Color {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
@ -23,90 +19,86 @@ pub struct DisplayColor {
|
||||
pub struct CellAttributes {
|
||||
pub fg: Color,
|
||||
pub bg: Color,
|
||||
pub bright: bool,
|
||||
pub bold: bool,
|
||||
pub italic: bool,
|
||||
pub underlined: bool,
|
||||
pub strikethrough: bool,
|
||||
}
|
||||
|
||||
impl DisplayColor {
|
||||
pub const BLACK: Self = Self::new(0, 0, 0);
|
||||
pub const WHITE: Self = Self::new(255, 255, 255);
|
||||
pub const DARK_GRAY: Self = Self::new(60, 60, 60);
|
||||
pub const LIGHT_GRAY: Self = Self::new(127, 127, 127);
|
||||
|
||||
pub const DARK_RED: Self = Self::new(160, 0, 0);
|
||||
pub const DARK_GREEN: Self = Self::new(0, 160, 0);
|
||||
pub const DARK_BLUE: Self = Self::new(0, 0, 160);
|
||||
|
||||
pub const DARK_YELLOW: Self = Self::new(160, 160, 0);
|
||||
pub const DARK_MAGENTA: Self = Self::new(160, 0, 160);
|
||||
pub const DARK_CYAN: Self = Self::new(0, 160, 160);
|
||||
|
||||
pub const LIGHT_RED: Self = Self::new(255, 0, 0);
|
||||
pub const LIGHT_GREEN: Self = Self::new(0, 255, 0);
|
||||
pub const LIGHT_BLUE: Self = Self::new(0, 0, 255);
|
||||
|
||||
pub const LIGHT_YELLOW: Self = Self::new(255, 255, 0);
|
||||
pub const LIGHT_MAGENTA: Self = Self::new(255, 0, 255);
|
||||
pub const LIGHT_CYAN: Self = Self::new(0, 255, 255);
|
||||
|
||||
impl Color {
|
||||
pub const fn new(r: u8, g: u8, b: u8) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
|
||||
pub const fn from_u32(v: u32) -> Self {
|
||||
let r = (v >> 16) as u8;
|
||||
let g = (v >> 8) as u8;
|
||||
let b = v as u8;
|
||||
Self { r, g, b }
|
||||
}
|
||||
|
||||
pub const fn to_u32(self) -> u32 {
|
||||
0xFF000000 | ((self.r as u32) << 16) | ((self.g as u32) << 8) | (self.b as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn from_esc(v: u32) -> Self {
|
||||
match v {
|
||||
0 => Self::Black,
|
||||
1 => Self::Red,
|
||||
2 => Self::Green,
|
||||
3 => Self::Yellow,
|
||||
4 => Self::Blue,
|
||||
5 => Self::Magenta,
|
||||
6 => Self::Cyan,
|
||||
7 => Self::White,
|
||||
_ => Self::Black,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_display(self, bright: bool) -> DisplayColor {
|
||||
if bright {
|
||||
BRIGHT_COLOR_MAP[self as usize]
|
||||
pub fn from_escape(config: &Config, bold: bool, esc: u32) -> Option<Self> {
|
||||
let map = if bold {
|
||||
&config.colors.bold
|
||||
} else {
|
||||
DIM_COLOR_MAP[self as usize]
|
||||
}
|
||||
&config.colors.dim
|
||||
};
|
||||
map.lookup_escape(esc)
|
||||
}
|
||||
|
||||
// pub fn to_rgba(self, bright: bool) -> SolidSource {
|
||||
// if bright {
|
||||
// BRIGHT_COLOR_MAP[self as usize]
|
||||
// } else {
|
||||
// COLOR_MAP[self as usize]
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
const DIM_COLOR_MAP: &[DisplayColor] = &[
|
||||
DisplayColor::BLACK,
|
||||
DisplayColor::DARK_RED,
|
||||
DisplayColor::DARK_GREEN,
|
||||
DisplayColor::DARK_YELLOW,
|
||||
DisplayColor::DARK_BLUE,
|
||||
DisplayColor::DARK_MAGENTA,
|
||||
DisplayColor::DARK_CYAN,
|
||||
DisplayColor::LIGHT_GRAY,
|
||||
];
|
||||
impl FromStr for Color {
|
||||
type Err = &'static str;
|
||||
|
||||
const BRIGHT_COLOR_MAP: &[DisplayColor] = &[
|
||||
DisplayColor::DARK_GRAY,
|
||||
DisplayColor::LIGHT_RED,
|
||||
DisplayColor::LIGHT_GREEN,
|
||||
DisplayColor::LIGHT_YELLOW,
|
||||
DisplayColor::LIGHT_BLUE,
|
||||
DisplayColor::LIGHT_MAGENTA,
|
||||
DisplayColor::LIGHT_CYAN,
|
||||
DisplayColor::WHITE,
|
||||
];
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let Some(s) = s.strip_prefix("#") else {
|
||||
return Err("Color should start with a `#`");
|
||||
};
|
||||
if s.len() != 6 {
|
||||
return Err("Color should be 6 hex digits");
|
||||
}
|
||||
let value = u32::from_str_radix(s, 16).map_err(|_| "Invalid color value")?;
|
||||
Ok(Color::from_u32(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Color {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct V;
|
||||
|
||||
impl<'de> Visitor<'de> for V {
|
||||
type Value = Color;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "a hex color in format `#XXYYZZ`")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
if let Ok(color) = Color::from_str(v) {
|
||||
Ok(color)
|
||||
} else {
|
||||
Err(E::invalid_value(Unexpected::Str(v), &"#XXYYZZ"))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
self.visit_str(&v)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(V)
|
||||
}
|
||||
}
|
||||
|
115
userspace/term/src/config.rs
Normal file
115
userspace/term/src/config.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::attr::Color;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct FontConfig {
|
||||
pub regular: PathBuf,
|
||||
pub italic: PathBuf,
|
||||
pub bold: PathBuf,
|
||||
pub bold_italic: PathBuf,
|
||||
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ColorsGroupConfig {
|
||||
pub red: Color,
|
||||
pub green: Color,
|
||||
pub blue: Color,
|
||||
pub yellow: Color,
|
||||
pub magenta: Color,
|
||||
pub cyan: Color,
|
||||
pub white: Color,
|
||||
pub black: Color,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ColorsConfig {
|
||||
pub bold: ColorsGroupConfig,
|
||||
pub dim: ColorsGroupConfig,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct Config {
|
||||
pub fonts: FontConfig,
|
||||
pub colors: ColorsConfig,
|
||||
}
|
||||
|
||||
impl Default for FontConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
regular: PathBuf::from("/etc/fonts/fixed-regular.ttf"),
|
||||
italic: PathBuf::from("/etc/fonts/fixed-italic.ttf"),
|
||||
bold: PathBuf::from("/etc/fonts/fixed-bold.ttf"),
|
||||
bold_italic: PathBuf::from("/etc/fonts/fixed-bold-italic.ttf"),
|
||||
|
||||
size: 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ColorsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dim: ColorsGroupConfig {
|
||||
black: Color::from_u32(0x000000),
|
||||
white: Color::from_u32(0xCCCCCC),
|
||||
red: Color::from_u32(0xCC0000),
|
||||
green: Color::from_u32(0x00CC00),
|
||||
blue: Color::from_u32(0x0000CC),
|
||||
yellow: Color::from_u32(0xCCCC00),
|
||||
magenta: Color::from_u32(0xCC00CC),
|
||||
cyan: Color::from_u32(0x00CCCC),
|
||||
},
|
||||
bold: ColorsGroupConfig {
|
||||
black: Color::from_u32(0x666666),
|
||||
white: Color::from_u32(0xFFFFFF),
|
||||
red: Color::from_u32(0xFF0000),
|
||||
green: Color::from_u32(0x00FF00),
|
||||
blue: Color::from_u32(0x0000FF),
|
||||
yellow: Color::from_u32(0xFFFF00),
|
||||
magenta: Color::from_u32(0xFF00FF),
|
||||
cyan: Color::from_u32(0x00FFFF),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorsGroupConfig {
|
||||
pub fn lookup_escape(&self, esc: u32) -> Option<Color> {
|
||||
match esc {
|
||||
0 => Some(self.black),
|
||||
1 => Some(self.red),
|
||||
2 => Some(self.green),
|
||||
3 => Some(self.yellow),
|
||||
4 => Some(self.blue),
|
||||
5 => Some(self.magenta),
|
||||
6 => Some(self.cyan),
|
||||
7 => Some(self.white),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load_or_default<P: AsRef<Path>>(path: P) -> Self {
|
||||
Self::load(path).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn load<P: AsRef<Path>>(path: P) -> Option<Self> {
|
||||
let path = path.as_ref();
|
||||
let data = fs::read_to_string(path)
|
||||
.inspect_err(|error| log::warn!("{path:?}: {error}"))
|
||||
.ok()?;
|
||||
let this = toml::from_str(&data)
|
||||
.inspect_err(|error| log::warn!("{path:?}: {error}"))
|
||||
.ok()?;
|
||||
Some(this)
|
||||
}
|
||||
}
|
@ -1,10 +1,18 @@
|
||||
use std::{fs, path::Path};
|
||||
use std::{fs, path::Path, sync::Arc};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::{error::Error, CONFIG};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Fonts {
|
||||
pub regular: Arc<TrueTypeFont<'static>>,
|
||||
pub italic: Arc<TrueTypeFont<'static>>,
|
||||
pub bold: Arc<TrueTypeFont<'static>>,
|
||||
pub bold_italic: Arc<TrueTypeFont<'static>>,
|
||||
}
|
||||
|
||||
pub trait Font {
|
||||
fn layout(&self) -> &FontLayout;
|
||||
fn map_glyph<F: FnMut(usize, usize, f32)>(&mut self, ch: char, mapper: F);
|
||||
fn map_glyph<F: FnMut(usize, usize, f32)>(&self, ch: char, mapper: F);
|
||||
}
|
||||
|
||||
pub struct TrueTypeFont<'a> {
|
||||
@ -43,7 +51,7 @@ impl Font for TrueTypeFont<'_> {
|
||||
&self.layout
|
||||
}
|
||||
|
||||
fn map_glyph<F: FnMut(usize, usize, f32)>(&mut self, ch: char, mut mapper: F) {
|
||||
fn map_glyph<F: FnMut(usize, usize, f32)>(&self, ch: char, mut mapper: F) {
|
||||
let glyph = self
|
||||
.inner
|
||||
.glyph(ch)
|
||||
@ -68,3 +76,31 @@ pub struct FontLayout {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl Fonts {
|
||||
pub fn from_config() -> Result<Self, Error> {
|
||||
let config = &*CONFIG;
|
||||
let regular = Arc::new(TrueTypeFont::load(
|
||||
&config.fonts.regular,
|
||||
config.fonts.size,
|
||||
)?);
|
||||
let italic = TrueTypeFont::load(&config.fonts.italic, config.fonts.size)
|
||||
.map(Arc::new)
|
||||
.inspect_err(|error| log::error!("{}: {error}", config.fonts.italic.display()))
|
||||
.unwrap_or_else(|_| regular.clone());
|
||||
let bold = TrueTypeFont::load(&config.fonts.bold, config.fonts.size)
|
||||
.map(Arc::new)
|
||||
.inspect_err(|error| log::error!("{}: {error}", config.fonts.bold.display()))
|
||||
.unwrap_or_else(|_| regular.clone());
|
||||
let bold_italic = TrueTypeFont::load(&config.fonts.bold_italic, config.fonts.size)
|
||||
.map(Arc::new)
|
||||
.inspect_err(|error| log::error!("{}: {error}", config.fonts.bold_italic.display()))
|
||||
.unwrap_or_else(|_| italic.clone());
|
||||
Ok(Self {
|
||||
regular,
|
||||
italic,
|
||||
bold,
|
||||
bold_italic,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
mem,
|
||||
os::{
|
||||
fd::{self, AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
||||
yggdrasil::{
|
||||
@ -15,17 +16,16 @@ use std::{
|
||||
rt::io::device,
|
||||
},
|
||||
},
|
||||
path::PathBuf,
|
||||
process::{Child, Command, ExitCode, Stdio},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
Arc, LazyLock, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use config::Config;
|
||||
use error::Error;
|
||||
use font::{Font, TrueTypeFont};
|
||||
use font::{Font, Fonts};
|
||||
use libcolors::{
|
||||
application::{
|
||||
window::{EventOutcome, Window},
|
||||
@ -34,21 +34,23 @@ use libcolors::{
|
||||
event::KeyModifiers,
|
||||
input::Key,
|
||||
};
|
||||
use state::{Cursor, State};
|
||||
use state::{Cursor, CursorStyle, GridCell, State};
|
||||
|
||||
pub mod attr;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod font;
|
||||
pub mod state;
|
||||
|
||||
struct DrawState<F: Font> {
|
||||
struct DrawState {
|
||||
width: usize,
|
||||
force_redraw: bool,
|
||||
focus_changed: bool,
|
||||
focused: bool,
|
||||
old_cursor: Cursor,
|
||||
old_cursor_style: CursorStyle,
|
||||
|
||||
font: F,
|
||||
fonts: Fonts,
|
||||
}
|
||||
|
||||
pub struct Terminal<'a> {
|
||||
@ -63,31 +65,92 @@ pub struct Terminal<'a> {
|
||||
shell: Child,
|
||||
}
|
||||
|
||||
impl<F: Font> DrawState<F> {
|
||||
pub fn new(font: F, width: usize) -> Self {
|
||||
impl DrawState {
|
||||
pub fn new(fonts: Fonts, width: usize) -> Self {
|
||||
Self {
|
||||
width,
|
||||
font,
|
||||
fonts,
|
||||
force_redraw: true,
|
||||
focus_changed: false,
|
||||
focused: true,
|
||||
old_cursor_style: CursorStyle::Block,
|
||||
old_cursor: Cursor { row: 0, col: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, dt: &mut [u32], state: &mut State) {
|
||||
fn draw_character(
|
||||
dt: &mut [u32],
|
||||
cx: usize,
|
||||
cy: usize,
|
||||
fw: usize,
|
||||
fh: usize,
|
||||
stride: usize,
|
||||
cell: &GridCell,
|
||||
fonts: &Fonts,
|
||||
invert: bool,
|
||||
) {
|
||||
fn blend(x: u8, y: u8, f: f32) -> u8 {
|
||||
let v = f * (x as f32) + (1.0 - f) * (y as f32);
|
||||
v as u8
|
||||
}
|
||||
|
||||
let default_fg = state.default_attributes.fg.to_display(false).to_u32();
|
||||
let default_bg = state.default_attributes.bg.to_display(false).to_u32();
|
||||
let font_layout = self.font.layout();
|
||||
let mut bg = cell.attrs.bg;
|
||||
let mut fg = cell.attrs.fg;
|
||||
if invert {
|
||||
mem::swap(&mut bg, &mut fg);
|
||||
}
|
||||
|
||||
// Fill cell
|
||||
for y in 0..fh {
|
||||
let off = (cy + y) * stride + cx;
|
||||
dt[off..off + fw].fill(bg.to_u32());
|
||||
}
|
||||
|
||||
if cell.char == '\0' {
|
||||
return;
|
||||
}
|
||||
|
||||
let c = cell.char as char;
|
||||
let font = match (cell.attrs.bold, cell.attrs.italic) {
|
||||
(true, true) => &fonts.bold_italic,
|
||||
(false, true) => &fonts.italic,
|
||||
(true, false) => &fonts.bold,
|
||||
(false, false) => &fonts.regular,
|
||||
};
|
||||
|
||||
font.map_glyph(c, |x, y, v| {
|
||||
let v = v.min(1.0);
|
||||
let r = blend(fg.r, bg.r, v);
|
||||
let g = blend(fg.g, bg.g, v);
|
||||
let b = blend(fg.b, bg.b, v);
|
||||
let color = (b as u32) | ((g as u32) << 8) | ((r as u32) << 16) | 0xFF000000;
|
||||
|
||||
dt[(cy + y) * stride + cx + x] = color;
|
||||
});
|
||||
|
||||
if cell.attrs.underlined {
|
||||
let s = (cy + fh - 1) * stride + cx;
|
||||
let d = &mut dt[s..s + fw];
|
||||
d.fill(fg.to_u32());
|
||||
}
|
||||
if cell.attrs.strikethrough {
|
||||
let s = (cy + fh / 2) * stride + cx;
|
||||
let d = &mut dt[s..s + fw];
|
||||
d.fill(fg.to_u32());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, dt: &mut [u32], state: &mut State) {
|
||||
let default_fg = state.default_attributes.fg.to_u32();
|
||||
let default_bg = state.default_attributes.bg.to_u32();
|
||||
let font_layout = self.fonts.regular.layout();
|
||||
let fw = font_layout.width;
|
||||
let fh = font_layout.height;
|
||||
|
||||
let cursor_dirty = self.old_cursor != state.cursor;
|
||||
let cursor_dirty = (self.old_cursor != state.cursor)
|
||||
|| (self.old_cursor_style != state.cursor_style)
|
||||
|| state.cursor_dirty;
|
||||
state.cursor_dirty = false;
|
||||
|
||||
if self.force_redraw {
|
||||
dt.fill(default_bg);
|
||||
@ -99,54 +162,53 @@ impl<F: Font> DrawState<F> {
|
||||
|
||||
let scroll = state.adjust_scroll();
|
||||
let cursor_visible = scroll == 0 && state.cursor_visible;
|
||||
|
||||
state.buffer.visible_rows_mut(scroll, |i, row| {
|
||||
let cy = i * fh;
|
||||
|
||||
for (j, cell) in row.cells().enumerate() {
|
||||
let bg = cell.attrs.bg.to_display(false);
|
||||
let fg = cell.attrs.fg.to_display(cell.attrs.bright);
|
||||
|
||||
let cx = j * fw;
|
||||
|
||||
// Fill cell
|
||||
for y in 0..fh {
|
||||
let off = (cy + y) * self.width + cx;
|
||||
dt[off..off + fw].fill(bg.to_u32());
|
||||
}
|
||||
|
||||
if cell.char == '\0' {
|
||||
continue;
|
||||
}
|
||||
|
||||
let c = cell.char as char;
|
||||
self.font.map_glyph(c, |x, y, v| {
|
||||
let v = (v * 2.0).min(1.0);
|
||||
let r = blend(fg.r, bg.r, v);
|
||||
let g = blend(fg.g, bg.g, v);
|
||||
let b = blend(fg.b, bg.b, v);
|
||||
let color = (b as u32) | ((g as u32) << 8) | ((r as u32) << 16) | 0xFF000000;
|
||||
|
||||
dt[(cy + y) * self.width + cx + x] = color;
|
||||
});
|
||||
Self::draw_character(dt, cx, cy, fw, fh, self.width, cell, &self.fonts, false);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO check if there's a character under cursor
|
||||
if cursor_visible {
|
||||
if cursor_visible && cursor_dirty {
|
||||
let cx = state.cursor.col * fw;
|
||||
let cy = state.cursor.row * fh;
|
||||
|
||||
// Fill block cursor
|
||||
for y in 0..fh {
|
||||
let off = (cy + y) * self.width + cx;
|
||||
dt[off..off + fw].fill(default_fg);
|
||||
}
|
||||
// Character under cursor
|
||||
let cell = state.buffer.cell(state.cursor.row, state.cursor.col);
|
||||
|
||||
if !self.focused {
|
||||
// Remove cursor center
|
||||
for y in 1..fh - 1 {
|
||||
let off = (cy + y) * self.width + cx + 1;
|
||||
dt[off..off + fw - 2].fill(default_bg);
|
||||
let fg = cell.attrs.fg.to_u32();
|
||||
|
||||
match state.cursor_style {
|
||||
CursorStyle::Bar if self.focused => {
|
||||
for y in 0..fh {
|
||||
let off = (cy + y) * self.width + cx;
|
||||
dt[off] = fg;
|
||||
}
|
||||
}
|
||||
CursorStyle::Underline if self.focused => {
|
||||
let off = (cy + fh - 1) * self.width + cx;
|
||||
dt[off..off + fw].fill(fg);
|
||||
}
|
||||
// Block, focused
|
||||
CursorStyle::Block if self.focused => {
|
||||
Self::draw_character(dt, cx, cy, fw, fh, self.width, cell, &self.fonts, true);
|
||||
}
|
||||
// Block, not focused (default not focused)
|
||||
_ => {
|
||||
Self::draw_character(dt, cx, cy, fw, fh, self.width, cell, &self.fonts, false);
|
||||
|
||||
// Draw outline
|
||||
for y in 0..fh {
|
||||
let off = (cy + y) * self.width + cx;
|
||||
if y == 0 || y == fh - 1 {
|
||||
dt[off..off + fw].fill(fg);
|
||||
} else {
|
||||
dt[off] = default_fg;
|
||||
dt[off + fw - 1] = fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +221,9 @@ impl<F: Font> DrawState<F> {
|
||||
}
|
||||
|
||||
impl Terminal<'_> {
|
||||
pub fn new<F: Font + 'static>(font: F) -> Result<Self, Error> {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let fonts = Fonts::from_config()?;
|
||||
|
||||
let mut app = Application::new()?;
|
||||
let mut window = Window::new(&app)?;
|
||||
let mut poll = PollChannel::new()?;
|
||||
@ -167,7 +231,7 @@ impl Terminal<'_> {
|
||||
let width = window.width() as usize;
|
||||
let height = window.height() as usize;
|
||||
|
||||
let font_layout = font.layout();
|
||||
let font_layout = fonts.regular.layout();
|
||||
|
||||
let rows = height / font_layout.height;
|
||||
let columns = width / font_layout.width;
|
||||
@ -179,7 +243,7 @@ impl Terminal<'_> {
|
||||
// TODO I hate this
|
||||
let pty_master = Arc::new(Mutex::new(pty_master));
|
||||
let state = Arc::new(Mutex::new(State::new(columns, rows)));
|
||||
let draw_state = Arc::new(Mutex::new(DrawState::new(font, width)));
|
||||
let draw_state = Arc::new(Mutex::new(DrawState::new(fonts, width)));
|
||||
|
||||
let state_c = state.clone();
|
||||
let draw_state_c = draw_state.clone();
|
||||
@ -191,7 +255,7 @@ impl Terminal<'_> {
|
||||
|
||||
let width = width as usize;
|
||||
let height = height as usize;
|
||||
let font_layout = ds.font.layout();
|
||||
let font_layout = ds.fonts.regular.layout();
|
||||
let rows = height / font_layout.height;
|
||||
let columns = width / font_layout.width;
|
||||
|
||||
@ -241,9 +305,11 @@ impl Terminal<'_> {
|
||||
need_redraw = s.scroll_end();
|
||||
}
|
||||
(KeyModifiers::CTRL, Key::Char(b'l')) => {
|
||||
s.clear();
|
||||
if !s.alternate {
|
||||
s.clear();
|
||||
}
|
||||
pty_master.write_all(&[0x0C]).unwrap();
|
||||
need_redraw = true;
|
||||
need_redraw = !s.alternate;
|
||||
}
|
||||
(KeyModifiers::SHIFT, Key::PageUp) => {
|
||||
need_redraw = s.scroll_up();
|
||||
@ -379,33 +445,18 @@ impl Terminal<'_> {
|
||||
}
|
||||
|
||||
static ABORT: AtomicBool = AtomicBool::new(false);
|
||||
static CONFIG: LazyLock<Config> = LazyLock::new(|| Config::load_or_default("/etc/term.conf"));
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
#[clap(
|
||||
long,
|
||||
help = "TTF font to use",
|
||||
default_value = "/etc/fonts/fixed-regular.ttf"
|
||||
)]
|
||||
regular_font: PathBuf,
|
||||
#[clap(
|
||||
long,
|
||||
help = "Font height in pixels (only for TTF fonts)",
|
||||
default_value_t = 16
|
||||
)]
|
||||
font_size: usize,
|
||||
}
|
||||
|
||||
fn run(args: &Args) -> Result<ExitCode, Error> {
|
||||
let font = TrueTypeFont::load(&args.regular_font, args.font_size)?;
|
||||
let term = Terminal::new(font)?;
|
||||
fn run() -> Result<ExitCode, Error> {
|
||||
LazyLock::force(&CONFIG);
|
||||
let term = Terminal::new()?;
|
||||
Ok(term.run())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args = Args::parse();
|
||||
logsink::setup_logging(false);
|
||||
match run(&args) {
|
||||
|
||||
match run() {
|
||||
Ok(code) => code,
|
||||
Err(error) => {
|
||||
log::error!("{error}");
|
||||
|
@ -1,6 +1,9 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::attr::{CellAttributes, Color};
|
||||
use crate::{
|
||||
attr::{CellAttributes, Color},
|
||||
CONFIG,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct GridCell {
|
||||
@ -44,6 +47,13 @@ struct Utf8Decoder {
|
||||
len: usize,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum CursorStyle {
|
||||
Block,
|
||||
Underline,
|
||||
Bar,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub buffer: Buffer,
|
||||
|
||||
@ -58,8 +68,11 @@ pub struct State {
|
||||
#[allow(unused)]
|
||||
saved_cursor: Option<Cursor>,
|
||||
|
||||
pub cursor_style: CursorStyle,
|
||||
pub default_attributes: CellAttributes,
|
||||
pub attributes: CellAttributes,
|
||||
pub fg_index: Option<u32>,
|
||||
pub cursor_dirty: bool,
|
||||
}
|
||||
|
||||
impl Utf8Decoder {
|
||||
@ -87,22 +100,25 @@ impl GridCell {
|
||||
Self { char, attrs }
|
||||
}
|
||||
|
||||
pub fn empty(bg: Color) -> Self {
|
||||
pub fn empty(fg: Color, bg: Color) -> Self {
|
||||
Self {
|
||||
char: '\0',
|
||||
attrs: CellAttributes {
|
||||
fg: Color::Black,
|
||||
fg,
|
||||
bg,
|
||||
bright: false,
|
||||
bold: false,
|
||||
italic: false,
|
||||
underlined: false,
|
||||
strikethrough: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GridRow {
|
||||
pub fn new(width: usize, bg: Color) -> Self {
|
||||
pub fn new(width: usize, fg: Color, bg: Color) -> Self {
|
||||
Self {
|
||||
cols: vec![GridCell::empty(bg); width],
|
||||
cols: vec![GridCell::empty(fg, bg); width],
|
||||
dirty: true,
|
||||
}
|
||||
}
|
||||
@ -124,26 +140,26 @@ impl GridRow {
|
||||
self.cols.iter()
|
||||
}
|
||||
|
||||
fn clear(&mut self, bg: Color) {
|
||||
self.cols.fill(GridCell::empty(bg));
|
||||
fn clear(&mut self, fg: Color, bg: Color) {
|
||||
self.cols.fill(GridCell::empty(fg, bg));
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
fn erase_to_right(&mut self, start: usize, bg: Color) {
|
||||
self.cols[start..].fill(GridCell::empty(bg));
|
||||
fn erase_to_right(&mut self, start: usize, fg: Color, bg: Color) {
|
||||
self.cols[start..].fill(GridCell::empty(fg, bg));
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
fn resize(&mut self, width: usize, bg: Color) {
|
||||
self.cols.resize(width, GridCell::empty(bg));
|
||||
fn resize(&mut self, width: usize, fg: Color, bg: Color) {
|
||||
self.cols.resize(width, GridCell::empty(fg, bg));
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn new(width: usize, height: usize, bg: Color) -> Self {
|
||||
pub fn new(width: usize, height: usize, fg: Color, bg: Color) -> Self {
|
||||
Self {
|
||||
rows: vec![GridRow::new(width, bg); height],
|
||||
rows: vec![GridRow::new(width, fg, bg); height],
|
||||
scrollback: VecDeque::new(),
|
||||
scrollback_limit: 1024,
|
||||
width,
|
||||
@ -151,8 +167,8 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, bg: Color) {
|
||||
self.rows.fill(GridRow::new(self.width, bg));
|
||||
pub fn clear(&mut self, fg: Color, bg: Color) {
|
||||
self.rows.fill(GridRow::new(self.width, fg, bg));
|
||||
}
|
||||
|
||||
pub fn iter_rows_mut<F: FnMut(usize, &mut GridRow)>(&mut self, scroll: usize, mut handler: F) {
|
||||
@ -185,10 +201,10 @@ impl Buffer {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: usize, height: usize, bg: Color) {
|
||||
self.rows.resize(height, GridRow::new(width, bg));
|
||||
pub fn resize(&mut self, width: usize, height: usize, fg: Color, bg: Color) {
|
||||
self.rows.resize(height, GridRow::new(width, fg, bg));
|
||||
for row in self.rows.iter_mut() {
|
||||
row.resize(width, bg);
|
||||
row.resize(width, fg, bg);
|
||||
}
|
||||
|
||||
self.width = width;
|
||||
@ -200,7 +216,11 @@ impl Buffer {
|
||||
self.rows[cur.row].dirty = true;
|
||||
}
|
||||
|
||||
pub fn scroll_once(&mut self, bg: Color) {
|
||||
pub fn cell(&self, row: usize, col: usize) -> &GridCell {
|
||||
&self.rows[row].cols[col]
|
||||
}
|
||||
|
||||
pub fn scroll_once(&mut self, fg: Color, bg: Color) {
|
||||
self.scrollback.push_front(self.rows[0].clone());
|
||||
if self.scrollback.len() >= self.scrollback_limit {
|
||||
self.scrollback.pop_back();
|
||||
@ -210,11 +230,11 @@ impl Buffer {
|
||||
self.rows[i - 1] = self.rows[i].clone();
|
||||
self.rows[i - 1].dirty = true;
|
||||
}
|
||||
self.rows[self.height - 1] = GridRow::new(self.width, bg);
|
||||
self.rows[self.height - 1] = GridRow::new(self.width, fg, bg);
|
||||
}
|
||||
|
||||
pub fn erase_row(&mut self, row: usize, bg: Color) {
|
||||
self.rows[row].clear(bg);
|
||||
pub fn erase_row(&mut self, row: usize, fg: Color, bg: Color) {
|
||||
self.rows[row].clear(fg, bg);
|
||||
}
|
||||
|
||||
pub fn set_row_dirty(&mut self, row: usize) {
|
||||
@ -231,14 +251,18 @@ impl Buffer {
|
||||
|
||||
impl State {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
let config = &*CONFIG;
|
||||
let default_attributes = CellAttributes {
|
||||
fg: Color::White,
|
||||
bg: Color::Black,
|
||||
bright: false,
|
||||
fg: config.colors.dim.white,
|
||||
bg: config.colors.dim.black,
|
||||
bold: false,
|
||||
italic: false,
|
||||
underlined: false,
|
||||
strikethrough: false,
|
||||
};
|
||||
|
||||
Self {
|
||||
buffer: Buffer::new(width, height, default_attributes.bg),
|
||||
buffer: Buffer::new(width, height, default_attributes.fg, default_attributes.bg),
|
||||
utf8_decode: Utf8Decoder::default(),
|
||||
|
||||
esc_args: Vec::new(),
|
||||
@ -252,17 +276,34 @@ impl State {
|
||||
|
||||
default_attributes,
|
||||
attributes: default_attributes,
|
||||
fg_index: Some(7),
|
||||
cursor_style: CursorStyle::Block,
|
||||
cursor_dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_fg_color(&mut self) {
|
||||
if let Some(fg_index) = self.fg_index {
|
||||
self.attributes.fg = Color::from_escape(&*CONFIG, self.attributes.bold, fg_index)
|
||||
.unwrap_or(self.default_attributes.fg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.buffer.clear(self.attributes.bg);
|
||||
self.buffer
|
||||
.clear(self.default_attributes.fg, self.attributes.bg);
|
||||
self.cursor_dirty = true;
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: usize, height: usize) {
|
||||
self.buffer
|
||||
.resize(width, height, self.default_attributes.bg);
|
||||
self.buffer.resize(
|
||||
width,
|
||||
height,
|
||||
self.default_attributes.fg,
|
||||
self.default_attributes.bg,
|
||||
);
|
||||
self.scroll = 0;
|
||||
self.cursor_dirty = true;
|
||||
|
||||
if self.cursor.row >= height {
|
||||
self.cursor.row = height - 1;
|
||||
@ -326,7 +367,8 @@ impl State {
|
||||
}
|
||||
|
||||
while self.cursor.row >= self.buffer.height {
|
||||
self.buffer.scroll_once(self.default_attributes.bg);
|
||||
self.buffer
|
||||
.scroll_once(self.default_attributes.fg, self.default_attributes.bg);
|
||||
self.cursor.row -= 1;
|
||||
redraw = true;
|
||||
}
|
||||
@ -358,10 +400,22 @@ impl State {
|
||||
1049 => {
|
||||
// Leave alternate mode
|
||||
self.alternate = false;
|
||||
self.clear();
|
||||
self.cursor = Cursor { col: 0, row: 0 };
|
||||
self.cursor_dirty = true;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
'q' => {
|
||||
match self.esc_args.get(0).copied().unwrap_or(0) {
|
||||
3 | 4 => self.cursor_style = CursorStyle::Underline,
|
||||
5 | 6 => self.cursor_style = CursorStyle::Bar,
|
||||
_ => self.cursor_style = CursorStyle::Block,
|
||||
}
|
||||
self.cursor_dirty = true;
|
||||
true
|
||||
}
|
||||
|
||||
// Move back one character
|
||||
'D' => {
|
||||
@ -375,29 +429,79 @@ impl State {
|
||||
}
|
||||
// Character attributes
|
||||
'm' => match self.esc_args[0] {
|
||||
// Reset
|
||||
0 => {
|
||||
self.attributes = self.default_attributes;
|
||||
self.fg_index = Some(7);
|
||||
false
|
||||
}
|
||||
// Bold
|
||||
1 => {
|
||||
self.attributes.bright = true;
|
||||
self.attributes.bold = true;
|
||||
self.update_fg_color();
|
||||
false
|
||||
}
|
||||
// Faint (should be decreased intensity, but here just normal)
|
||||
2 => {
|
||||
self.attributes.bold = false;
|
||||
self.update_fg_color();
|
||||
false
|
||||
}
|
||||
// Italicized
|
||||
3 => {
|
||||
self.attributes.italic = true;
|
||||
false
|
||||
}
|
||||
// Underlined
|
||||
4 => {
|
||||
self.attributes.underlined = true;
|
||||
false
|
||||
}
|
||||
// Strikethrough
|
||||
9 => {
|
||||
self.attributes.strikethrough = true;
|
||||
false
|
||||
}
|
||||
// Normal (neither bold nor faint)
|
||||
22 => {
|
||||
self.attributes.bold = false;
|
||||
self.update_fg_color();
|
||||
false
|
||||
}
|
||||
// Not italicized
|
||||
23 => {
|
||||
self.attributes.italic = false;
|
||||
false
|
||||
}
|
||||
// Not underlined
|
||||
24 => {
|
||||
self.attributes.underlined = false;
|
||||
false
|
||||
}
|
||||
// Not strikethrough
|
||||
29 => {
|
||||
self.attributes.strikethrough = false;
|
||||
false
|
||||
}
|
||||
// Foreground color
|
||||
30..=39 => {
|
||||
let vt_color = self.esc_args[0] % 10;
|
||||
if vt_color == 9 {
|
||||
self.attributes.fg = Color::Black;
|
||||
self.fg_index = Some(7);
|
||||
} else {
|
||||
self.attributes.fg = Color::from_esc(vt_color);
|
||||
self.fg_index = Some(vt_color);
|
||||
}
|
||||
self.update_fg_color();
|
||||
false
|
||||
}
|
||||
// Background color
|
||||
40..=49 => {
|
||||
let vt_color = self.esc_args[0] % 10;
|
||||
if vt_color == 9 {
|
||||
self.attributes.bg = Color::Black;
|
||||
self.attributes.bg = self.default_attributes.bg;
|
||||
} else {
|
||||
self.attributes.bg = Color::from_esc(vt_color);
|
||||
self.attributes.bg = Color::from_escape(&*CONFIG, false, vt_color)
|
||||
.unwrap_or(self.default_attributes.bg);
|
||||
}
|
||||
false
|
||||
}
|
||||
@ -413,6 +517,7 @@ impl State {
|
||||
row: row as _,
|
||||
col: col as _,
|
||||
};
|
||||
self.cursor_dirty = true;
|
||||
|
||||
true
|
||||
}
|
||||
@ -420,6 +525,7 @@ impl State {
|
||||
'H' => {
|
||||
self.buffer.set_row_dirty(self.cursor.row);
|
||||
self.cursor = Cursor { row: 0, col: 0 };
|
||||
self.cursor_dirty = true;
|
||||
true
|
||||
}
|
||||
// Clear rows/columns/screen
|
||||
@ -430,7 +536,9 @@ impl State {
|
||||
1 => false,
|
||||
// Erase all
|
||||
2 => {
|
||||
self.buffer.clear(self.attributes.bg);
|
||||
self.buffer
|
||||
.clear(self.default_attributes.fg, self.attributes.bg);
|
||||
self.cursor_dirty = true;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
@ -438,13 +546,20 @@ impl State {
|
||||
'K' => match self.esc_args[0] {
|
||||
// Erase to right
|
||||
0 => {
|
||||
self.buffer.rows[self.cursor.row]
|
||||
.erase_to_right(self.cursor.col, self.attributes.bg);
|
||||
self.buffer.rows[self.cursor.row].erase_to_right(
|
||||
self.cursor.col,
|
||||
self.default_attributes.fg,
|
||||
self.attributes.bg,
|
||||
);
|
||||
true
|
||||
}
|
||||
// Erase All
|
||||
2 => {
|
||||
self.buffer.erase_row(self.cursor.row, self.attributes.bg);
|
||||
self.buffer.erase_row(
|
||||
self.cursor.row,
|
||||
self.default_attributes.fg,
|
||||
self.attributes.bg,
|
||||
);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
|
Loading…
x
Reference in New Issue
Block a user