cosmic-terminal/src/terminal.rs

861 lines
28 KiB
Rust
Raw Normal View History

2023-12-20 13:31:10 -07:00
use alacritty_terminal::{
event::{Event, EventListener, Notify, OnResize, WindowSize},
2023-12-21 09:53:31 -07:00
event_loop::{EventLoop, Msg, Notifier},
2023-12-20 13:31:10 -07:00
grid::Dimensions,
2024-01-11 16:20:15 -07:00
index::{Boundary, Column, Direction, Line, Point, Side},
2023-12-22 15:08:24 -07:00
selection::{Selection, SelectionType},
2023-12-20 13:31:10 -07:00
sync::FairMutex,
term::{
cell::Flags,
color::{self, Colors},
2024-01-11 14:32:11 -07:00
search::RegexSearch,
2024-02-05 21:01:16 -07:00
viewport_to_point, Config, TermDamage, TermMode,
2023-12-20 13:31:10 -07:00
},
tty::{self, Options},
vte::ansi::{Color, NamedColor, Rgb},
Term,
2023-12-20 13:31:10 -07:00
};
use cosmic::{
iced::advanced::graphics::text::font_system,
2024-01-30 08:59:19 +01:00
iced::mouse::ScrollDelta,
widget::{pane_grid, segmented_button},
};
2023-12-20 13:31:10 -07:00
use cosmic_text::{
Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, Metrics, Shaping, Weight, Wrap,
2023-12-20 13:31:10 -07:00
};
use indexmap::IndexSet;
2023-12-20 13:31:10 -07:00
use std::{
borrow::Cow,
2023-12-22 14:31:01 -07:00
collections::HashMap,
2023-12-20 13:31:10 -07:00
mem,
sync::{
atomic::{AtomicU32, Ordering},
Arc, Weak,
},
2023-12-20 13:31:10 -07:00
time::Instant,
};
use tokio::sync::mpsc;
2023-12-20 14:26:31 -07:00
pub use alacritty_terminal::grid::Scroll as TerminalScroll;
2024-01-30 08:59:19 +01:00
use crate::{config::Config as AppConfig, mouse_reporter::MouseReporter};
2023-12-20 13:31:10 -07:00
#[derive(Clone, Copy, Debug)]
pub struct Size {
2023-12-21 15:09:18 -07:00
pub width: u32,
pub height: u32,
pub cell_width: f32,
pub cell_height: f32,
2023-12-20 13:31:10 -07:00
}
impl Dimensions for Size {
fn total_lines(&self) -> usize {
self.screen_lines()
}
fn screen_lines(&self) -> usize {
((self.height as f32) / self.cell_height).floor() as usize
}
fn columns(&self) -> usize {
((self.width as f32) / self.cell_width).floor() as usize
}
}
impl From<Size> for WindowSize {
fn from(size: Size) -> Self {
Self {
num_lines: size.screen_lines() as u16,
num_cols: size.columns() as u16,
cell_width: size.cell_width as u16,
cell_height: size.cell_height as u16,
}
}
}
#[derive(Clone)]
2023-12-21 15:09:18 -07:00
pub struct EventProxy(
pane_grid::Pane,
2023-12-20 13:31:10 -07:00
segmented_button::Entity,
mpsc::Sender<(pane_grid::Pane, segmented_button::Entity, Event)>,
2023-12-20 13:31:10 -07:00
);
impl EventListener for EventProxy {
fn send_event(&self, event: Event) {
//TODO: handle error
let _ = self.2.blocking_send((self.0, self.1, event));
2023-12-20 13:31:10 -07:00
}
}
fn as_bright(mut color: Color) -> Color {
if let Color::Named(named) = color {
color = Color::Named(named.to_bright());
}
color
}
fn as_dim(mut color: Color) -> Color {
if let Color::Named(named) = color {
color = Color::Named(named.to_dim());
}
color
}
pub static WINDOW_BG_COLOR: AtomicU32 = AtomicU32::new(0xFF000000);
2023-12-20 13:31:10 -07:00
fn convert_color(colors: &Colors, color: Color) -> cosmic_text::Color {
let rgb = match color {
Color::Named(named_color) => match colors[named_color] {
Some(rgb) => rgb,
None => match named_color {
NamedColor::Background => {
// Allow using an unset background
return cosmic_text::Color(WINDOW_BG_COLOR.load(Ordering::SeqCst));
}
_ => {
log::warn!("missing named color {:?}", named_color);
Rgb::default()
}
},
2023-12-20 13:31:10 -07:00
},
Color::Spec(rgb) => rgb,
Color::Indexed(index) => match colors[index as usize] {
Some(rgb) => rgb,
None => {
log::warn!("missing indexed color {}", index);
Rgb::default()
}
},
};
cosmic_text::Color::rgb(rgb.r, rgb.g, rgb.b)
}
type TabModel = segmented_button::Model<segmented_button::SingleSelect>;
pub struct TerminalPaneGrid {
pub panes: pane_grid::State<TabModel>,
pub panes_created: usize,
pub focus: pane_grid::Pane,
}
impl TerminalPaneGrid {
pub fn new(model: TabModel) -> Self {
let (panes, pane) = pane_grid::State::new(model);
let mut terminal_ids = HashMap::new();
terminal_ids.insert(pane, cosmic::widget::Id::unique());
Self {
panes,
panes_created: 1,
focus: pane,
}
}
pub fn active(&self) -> Option<&TabModel> {
self.panes.get(self.focus)
}
pub fn active_mut(&mut self) -> Option<&mut TabModel> {
self.panes.get_mut(self.focus)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Metadata {
pub bg: cosmic_text::Color,
pub underline_color: cosmic_text::Color,
pub flags: Flags,
}
impl Metadata {
fn new(bg: cosmic_text::Color, underline_color: cosmic_text::Color) -> Self {
let flags = Flags::empty();
2024-01-17 16:41:35 +01:00
Self {
bg,
underline_color,
flags,
}
}
fn with_underline_color(self, underline_color: cosmic_text::Color) -> Self {
2024-01-17 16:41:35 +01:00
Self {
underline_color,
..self
}
}
fn with_flags(self, flags: Flags) -> Self {
Self { flags, ..self }
}
}
2023-12-20 13:31:10 -07:00
pub struct Terminal {
default_attrs: Attrs<'static>,
buffer: Arc<Buffer>,
size: Size,
2023-12-21 15:09:18 -07:00
pub term: Arc<FairMutex<Term<EventProxy>>>,
2023-12-20 13:31:10 -07:00
colors: Colors,
dim_font_weight: Weight,
bold_font_weight: Weight,
use_bright_bold: bool,
2023-12-20 13:31:10 -07:00
notifier: Notifier,
2023-12-22 15:00:50 -07:00
pub context_menu: Option<cosmic::iced::Point>,
pub needs_update: bool,
2024-01-11 14:31:20 -07:00
search_regex_opt: Option<RegexSearch>,
search_value: String,
pub metadata_set: IndexSet<Metadata>,
2024-01-30 08:59:19 +01:00
mouse_reporter: MouseReporter,
2023-12-20 13:31:10 -07:00
}
impl Terminal {
//TODO: error handling
pub fn new(
pane: pane_grid::Pane,
2023-12-20 13:31:10 -07:00
entity: segmented_button::Entity,
event_tx: mpsc::Sender<(pane_grid::Pane, segmented_button::Entity, Event)>,
config: Config,
options: Options,
app_config: &AppConfig,
colors: Colors,
2023-12-20 13:31:10 -07:00
) -> Self {
let font_stretch = app_config.typed_font_stretch();
let font_weight = app_config.font_weight;
let dim_font_weight = app_config.dim_font_weight;
let bold_font_weight = app_config.bold_font_weight;
let use_bright_bold = app_config.use_bright_bold;
2023-12-20 13:31:10 -07:00
let metrics = Metrics::new(14.0, 20.0);
let default_bg = convert_color(&colors, Color::Named(NamedColor::Background));
let default_fg = convert_color(&colors, Color::Named(NamedColor::Foreground));
let mut metadata_set = IndexSet::new();
let default_metada = Metadata::new(default_bg, default_fg);
let (default_metada_idx, _) = metadata_set.insert_full(default_metada);
2023-12-20 13:31:10 -07:00
//TODO: set color to default fg
let default_attrs = Attrs::new()
.family(Family::Monospace)
.weight(Weight(font_weight))
.stretch(font_stretch)
.color(default_fg)
.metadata(default_metada_idx);
2023-12-20 13:31:10 -07:00
let mut buffer = Buffer::new_empty(metrics);
let (cell_width, cell_height) = {
let mut font_system = font_system().write().unwrap();
2024-02-05 15:59:07 +01:00
let font_system = font_system.raw();
buffer.set_wrap(font_system, Wrap::None);
2023-12-20 13:31:10 -07:00
// Use size of space to determine cell size
2024-02-05 15:59:07 +01:00
buffer.set_text(font_system, " ", default_attrs, Shaping::Advanced);
let layout = buffer.line_layout(font_system, 0).unwrap();
let w = layout[0].w;
buffer.set_monospace_width(font_system, Some(w));
(w, metrics.line_height)
2023-12-20 13:31:10 -07:00
};
2023-12-21 09:53:31 -07:00
let size = Size {
2023-12-20 13:31:10 -07:00
width: (80.0 * cell_width).ceil() as u32,
height: (24.0 * cell_height).ceil() as u32,
cell_width,
cell_height,
};
let event_proxy = EventProxy(pane, entity, event_tx);
2023-12-20 13:31:10 -07:00
let term = Arc::new(FairMutex::new(Term::new(
config,
2023-12-20 13:31:10 -07:00
&size,
event_proxy.clone(),
)));
let window_id = 0;
let pty = tty::new(&options, size.into(), window_id).unwrap();
2023-12-20 13:31:10 -07:00
let pty_event_loop = EventLoop::new(term.clone(), event_proxy, pty, options.hold, false);
2023-12-20 13:31:10 -07:00
let notifier = Notifier(pty_event_loop.channel());
2023-12-21 09:53:31 -07:00
let _pty_join_handle = pty_event_loop.spawn();
2023-12-20 13:31:10 -07:00
Self {
colors,
dim_font_weight: Weight(dim_font_weight),
bold_font_weight: Weight(bold_font_weight),
use_bright_bold,
2023-12-20 13:31:10 -07:00
default_attrs,
buffer: Arc::new(buffer),
size,
term,
notifier,
2023-12-22 15:00:50 -07:00
context_menu: None,
needs_update: true,
2024-01-11 14:31:20 -07:00
search_regex_opt: None,
search_value: String::new(),
metadata_set,
2024-01-30 08:59:19 +01:00
mouse_reporter: Default::default(),
2023-12-20 13:31:10 -07:00
}
}
pub fn buffer_weak(&self) -> Weak<Buffer> {
Arc::downgrade(&self.buffer)
}
/// Get the internal [`Buffer`]
pub fn with_buffer<F: FnOnce(&Buffer) -> T, T>(&self, f: F) -> T {
f(&self.buffer)
}
/// Get the internal [`Buffer`], mutably
pub fn with_buffer_mut<F: FnOnce(&mut Buffer) -> T, T>(&mut self, f: F) -> T {
f(Arc::make_mut(&mut self.buffer))
}
pub fn colors(&self) -> &Colors {
&self.colors
}
pub fn default_attrs(&self) -> &Attrs<'static> {
&self.default_attrs
}
pub fn size(&self) -> Size {
self.size
}
pub fn redraw(&self) -> bool {
self.buffer.redraw()
}
pub fn set_redraw(&mut self, redraw: bool) {
self.with_buffer_mut(|buffer| buffer.set_redraw(redraw));
}
2023-12-20 15:01:47 -07:00
pub fn input_no_scroll<I: Into<Cow<'static, [u8]>>>(&self, input: I) {
2023-12-20 13:31:10 -07:00
self.notifier.notify(input);
}
2023-12-20 15:01:47 -07:00
pub fn input_scroll<I: Into<Cow<'static, [u8]>>>(&self, input: I) {
self.input_no_scroll(input);
self.scroll(TerminalScroll::Bottom);
}
2023-12-21 22:13:17 -07:00
pub fn paste(&self, value: String) {
// This code is ported from alacritty
let bracketed_paste = {
let term = self.term.lock();
term.mode().contains(TermMode::BRACKETED_PASTE)
};
if bracketed_paste {
self.input_no_scroll(&b"\x1b[200~"[..]);
self.input_no_scroll(value.replace('\x1b', "").into_bytes());
self.input_scroll(&b"\x1b[201~"[..]);
} else {
// In non-bracketed (ie: normal) mode, terminal applications cannot distinguish
// pasted data from keystrokes.
// In theory, we should construct the keystrokes needed to produce the data we are
// pasting... since that's neither practical nor sensible (and probably an impossible
// task to solve in a general way), we'll just replace line breaks (windows and unix
// style) with a single carriage return (\r, which is what the Enter key produces).
self.input_scroll(value.replace("\r\n", "\r").replace('\n', "\r").into_bytes());
}
}
2023-12-20 19:54:18 -07:00
pub fn resize(&mut self, width: u32, height: u32) {
2023-12-20 13:31:10 -07:00
if width != self.size.width || height != self.size.height {
let instant = Instant::now();
self.size.width = width;
self.size.height = height;
self.notifier.on_resize(self.size.into());
self.term.lock().resize(self.size);
self.with_buffer_mut(|buffer| {
let mut font_system = font_system().write().unwrap();
2023-12-20 20:13:12 -07:00
buffer.set_size(font_system.raw(), width as f32, height as f32);
2023-12-20 13:31:10 -07:00
});
log::debug!("resize {:?}", instant.elapsed());
}
}
2023-12-20 14:26:31 -07:00
pub fn scroll(&self, scroll: TerminalScroll) {
self.term.lock().scroll_display(scroll);
}
2023-12-20 14:57:44 -07:00
pub fn scroll_to(&self, ratio: f32) {
let mut term = self.term.lock();
let grid = term.grid();
let total = grid.history_size() + grid.screen_lines();
let old_display_offset = grid.display_offset() as i32;
let new_display_offset =
((total as f32) * (1.0 - ratio)) as i32 - grid.screen_lines() as i32;
term.scroll_display(TerminalScroll::Delta(
new_display_offset - old_display_offset,
));
}
pub fn scrollbar(&self) -> Option<(f32, f32)> {
2023-12-20 14:26:31 -07:00
let term = self.term.lock();
let grid = term.grid();
if grid.history_size() > 0 {
let total = grid.history_size() + grid.screen_lines();
let start = total - grid.display_offset() - grid.screen_lines();
let end = total - grid.display_offset();
Some((
(start as f32) / (total as f32),
(end as f32) / (total as f32),
))
} else {
None
}
2023-12-20 14:26:31 -07:00
}
2024-01-11 14:31:20 -07:00
pub fn search(&mut self, value: &str, forwards: bool) {
//TODO: set max lines, run in thread?
{
let mut term = self.term.lock();
if self.search_value != value {
match RegexSearch::new(value) {
Ok(search_regex) => {
self.search_regex_opt = Some(search_regex);
self.search_value = value.to_string();
term.selection = None;
}
Err(err) => {
log::warn!("failed to parse regex {:?}: {}", value, err);
return;
}
}
}
let search_regex = match &mut self.search_regex_opt {
Some(some) => some,
None => return,
};
// Determine search origin
2024-01-11 16:20:15 -07:00
let grid = term.grid();
2024-01-11 14:31:20 -07:00
let search_origin = match term
.selection
.as_ref()
.and_then(|selection| selection.to_range(&term))
{
Some(range) => {
2024-01-11 16:20:15 -07:00
//TODO: determine correct search_origin, along with side below
2024-01-11 14:31:20 -07:00
if forwards {
2024-01-11 16:20:15 -07:00
range.end.add(grid, Boundary::Grid, 1)
2024-01-11 14:31:20 -07:00
} else {
2024-01-11 16:20:15 -07:00
range.start.sub(grid, Boundary::Grid, 1)
2024-01-11 14:31:20 -07:00
}
}
None => {
if forwards {
Point::new(Line(-(grid.history_size() as i32)), Column(0))
} else {
Point::new(
Line(grid.screen_lines() as i32 - 1),
Column(grid.columns() - 1),
)
}
}
};
// Find next search match
2024-02-05 15:59:07 +01:00
if let Some(search_match) = term.search_next(
2024-01-11 14:31:20 -07:00
search_regex,
search_origin,
if forwards {
Direction::Right
} else {
Direction::Left
},
2024-01-11 16:20:15 -07:00
//TODO: determine correct side, along with search_origin above
2024-01-11 14:31:20 -07:00
if forwards { Side::Left } else { Side::Right },
None,
) {
2024-02-05 15:59:07 +01:00
// Scroll to match
if forwards {
term.scroll_to_point(*search_match.end());
} else {
term.scroll_to_point(*search_match.start());
2024-01-11 14:31:20 -07:00
}
2024-02-05 15:59:07 +01:00
// Set selection to match
let mut selection =
Selection::new(SelectionType::Simple, *search_match.start(), Side::Left);
selection.update(*search_match.end(), Side::Right);
term.selection = Some(selection);
2024-01-11 14:31:20 -07:00
}
}
self.update();
}
2023-12-22 15:08:24 -07:00
pub fn select_all(&mut self) {
{
let mut term = self.term.lock();
let grid = term.grid();
let start = Point::new(Line(-(grid.history_size() as i32)), Column(0));
2023-12-22 16:08:43 -07:00
let end = Point::new(
Line(grid.screen_lines() as i32 - 1),
Column(grid.columns() - 1),
);
2023-12-22 15:08:24 -07:00
let mut selection = Selection::new(SelectionType::Lines, start, Side::Left);
selection.update(end, Side::Right);
term.selection = Some(selection);
}
self.update();
}
2024-01-09 10:16:32 -07:00
pub fn set_config(
&mut self,
config: &AppConfig,
2024-01-09 10:16:32 -07:00
themes: &HashMap<String, Colors>,
zoom_adj: i8,
) {
2023-12-22 14:31:01 -07:00
let mut update_cell_size = false;
let mut update = false;
if self.default_attrs.stretch != config.typed_font_stretch() {
self.default_attrs = self.default_attrs.stretch(config.typed_font_stretch());
update_cell_size = true;
}
if self.default_attrs.weight.0 != config.font_weight {
self.default_attrs = self.default_attrs.weight(Weight(config.font_weight));
update_cell_size = true;
}
if self.dim_font_weight.0 != config.dim_font_weight {
self.dim_font_weight = Weight(config.dim_font_weight);
update_cell_size = true;
}
if self.bold_font_weight.0 != config.font_weight {
self.bold_font_weight = Weight(config.bold_font_weight);
update_cell_size = true;
}
if self.use_bright_bold != config.use_bright_bold {
self.use_bright_bold = config.use_bright_bold;
update_cell_size = true;
}
let metrics = config.metrics(zoom_adj);
2023-12-22 14:31:01 -07:00
if metrics != self.buffer.metrics() {
{
let mut font_system = font_system().write().unwrap();
self.with_buffer_mut(|buffer| buffer.set_metrics(font_system.raw(), metrics));
}
update_cell_size = true;
}
if let Some(colors) = themes.get(config.syntax_theme()) {
let mut changed = false;
for i in 0..color::COUNT {
if self.colors[i] != colors[i] {
self.colors[i] = colors[i];
changed = true;
}
}
if changed {
self.metadata_set.clear();
2024-02-05 15:59:07 +01:00
let default_bg = convert_color(colors, Color::Named(NamedColor::Background));
let default_fg = convert_color(colors, Color::Named(NamedColor::Foreground));
let default_metadata = Metadata::new(default_bg, default_fg);
let (default_metadata_idx, _) = self.metadata_set.insert_full(default_metadata);
2023-12-22 14:31:01 -07:00
self.default_attrs = Attrs::new()
.family(Family::Monospace)
.weight(Weight(config.font_weight))
.stretch(config.typed_font_stretch())
.color(default_fg)
.metadata(default_metadata_idx);
2023-12-22 14:31:01 -07:00
update = true;
}
}
if update_cell_size {
self.update_cell_size();
} else if update {
self.update();
}
}
pub fn update_cell_size(&mut self) {
let default_attrs = self.default_attrs;
let (cell_width, cell_height) = {
let mut font_system = font_system().write().unwrap();
self.with_buffer_mut(|buffer| {
buffer.set_wrap(font_system.raw(), Wrap::None);
// Use size of space to determine cell size
buffer.set_text(font_system.raw(), " ", default_attrs, Shaping::Advanced);
let layout = buffer.line_layout(font_system.raw(), 0).unwrap();
let w = layout[0].w;
buffer.set_monospace_width(font_system.raw(), Some(w));
(w, buffer.metrics().line_height)
2023-12-22 14:31:01 -07:00
})
};
let old_size = self.size;
self.size = Size {
width: 0,
height: 0,
cell_width,
cell_height,
};
self.resize(old_size.width, old_size.height);
self.update();
}
2023-12-20 13:31:10 -07:00
pub fn update(&mut self) -> bool {
// LEFTTORIGHT ISOLATE character.
// This will be added to the beginning of lines to force the shaper to treat detected RTL
// lines as LTR. RTL text would still be rendered correctly. But this fixes the wrong
// behavior of it being aligned to the right.
const LRI: char = '\u{2066}';
2023-12-20 13:31:10 -07:00
let instant = Instant::now();
// Only keep default
self.metadata_set.truncate(1);
2023-12-20 13:31:10 -07:00
//TODO: is redraw needed after all events?
//TODO: use LineDamageBounds
{
2023-12-21 09:53:31 -07:00
let buffer = Arc::make_mut(&mut self.buffer);
2023-12-20 13:31:10 -07:00
2023-12-20 14:26:31 -07:00
let mut line_i = 0;
let mut last_point = None;
let mut text = String::from(LRI);
2023-12-20 13:31:10 -07:00
let mut attrs_list = AttrsList::new(self.default_attrs);
{
2024-02-05 21:01:16 -07:00
let mut term = self.term.lock();
//TODO: use damage?
match term.damage() {
TermDamage::Full => {}
TermDamage::Partial(_damage_lines) => {}
}
term.reset_damage();
2023-12-21 21:37:39 -07:00
let grid = term.grid();
2023-12-20 13:31:10 -07:00
for indexed in grid.display_iter() {
2023-12-20 14:26:31 -07:00
if indexed.point.line != last_point.unwrap_or(indexed.point).line {
2023-12-20 13:31:10 -07:00
while line_i >= buffer.lines.len() {
buffer.lines.push(BufferLine::new(
"",
AttrsList::new(self.default_attrs),
Shaping::Advanced,
));
buffer.set_redraw(true);
}
2024-02-05 21:01:16 -07:00
if buffer.lines[line_i].set_text(text.clone(), attrs_list.clone()) {
2023-12-20 13:31:10 -07:00
buffer.set_redraw(true);
}
2023-12-20 14:26:31 -07:00
line_i += 1;
2023-12-20 13:31:10 -07:00
text.clear();
text.push(LRI);
2023-12-20 13:31:10 -07:00
attrs_list.clear_spans();
}
//TODO: use indexed.point.column?
//TODO: skip leading spacer?
if indexed.cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
// Skip wide spacers (cells after wide characters)
continue;
}
let start = text.len();
2024-02-05 21:01:16 -07:00
// Tab skip/stop is handled by alacritty_terminal
text.push(match indexed.cell.c {
'\t' => ' ',
c => c,
});
2023-12-20 13:31:10 -07:00
if let Some(zerowidth) = indexed.cell.zerowidth() {
for &c in zerowidth {
text.push(c);
}
}
let end = text.len();
let mut attrs = self.default_attrs;
let cell_fg = if indexed.cell.flags.contains(Flags::DIM) {
as_dim(indexed.cell.fg)
} else if self.use_bright_bold && indexed.cell.flags.contains(Flags::BOLD) {
as_bright(indexed.cell.fg)
} else {
indexed.cell.fg
};
let (mut fg, mut bg) = if indexed.cell.flags.contains(Flags::INVERSE) {
(
convert_color(&self.colors, indexed.cell.bg),
convert_color(&self.colors, cell_fg),
)
} else {
(
convert_color(&self.colors, cell_fg),
convert_color(&self.colors, indexed.cell.bg),
)
};
2023-12-21 21:37:39 -07:00
if indexed.cell.flags.contains(Flags::HIDDEN) {
fg = bg;
}
2023-12-21 21:37:39 -07:00
// Change color if cursor
2023-12-20 13:31:10 -07:00
if indexed.point == grid.cursor.point {
2023-12-21 21:37:39 -07:00
//TODO: better handling of cursor
if term.mode().contains(TermMode::SHOW_CURSOR) {
mem::swap(&mut fg, &mut bg);
} else {
fg = bg;
}
2023-12-20 13:31:10 -07:00
}
2023-12-21 21:37:39 -07:00
// Change color if selected
if let Some(selection) = &term.selection {
if let Some(range) = selection.to_range(&term) {
if range.contains(indexed.point) {
//TODO: better handling of selection
mem::swap(&mut fg, &mut bg);
}
}
}
// Convert foreground to linear
2024-01-17 13:41:17 -07:00
attrs = attrs.color(fg);
2024-01-17 16:41:35 +01:00
let underline_color = indexed
.cell
.underline_color()
.map(|c| convert_color(&self.colors, c))
.unwrap_or(fg);
let metadata = Metadata::new(bg, fg)
.with_flags(indexed.cell.flags)
.with_underline_color(underline_color);
let (meta_idx, _) = self.metadata_set.insert_full(metadata);
attrs = attrs.metadata(meta_idx);
2023-12-20 13:31:10 -07:00
//TODO: more flags
if indexed.cell.flags.contains(Flags::BOLD) {
attrs = attrs.weight(self.bold_font_weight);
} else if indexed.cell.flags.contains(Flags::DIM) {
// if DIM and !BOLD
attrs = attrs.weight(self.dim_font_weight);
2023-12-20 13:31:10 -07:00
}
if indexed.cell.flags.contains(Flags::ITALIC) {
//TODO: automatically use fake italic
attrs = attrs.cache_key_flags(CacheKeyFlags::FAKE_ITALIC);
2023-12-20 13:31:10 -07:00
}
if attrs != attrs_list.defaults() {
attrs_list.add_span(start..end, attrs);
}
2023-12-20 14:26:31 -07:00
last_point = Some(indexed.point);
2023-12-20 13:31:10 -07:00
}
}
//TODO: do not repeat!
while line_i >= buffer.lines.len() {
buffer.lines.push(BufferLine::new(
"",
AttrsList::new(self.default_attrs),
Shaping::Advanced,
));
buffer.set_redraw(true);
}
if buffer.lines[line_i].set_text(text, attrs_list) {
buffer.set_redraw(true);
}
2023-12-20 14:26:31 -07:00
line_i += 1;
2023-12-20 13:31:10 -07:00
2023-12-20 14:26:31 -07:00
if buffer.lines.len() != line_i {
buffer.lines.truncate(line_i);
2023-12-20 13:31:10 -07:00
buffer.set_redraw(true);
}
{
let mut font_system = font_system().write().unwrap();
buffer.shape_until_scroll(font_system.raw(), true);
}
}
log::debug!("buffer update {:?}", instant.elapsed());
self.buffer.redraw()
}
2023-12-21 22:21:01 -07:00
pub fn viewport_to_point(&self, point: Point<usize>) -> Point {
let term = self.term.lock();
viewport_to_point(term.grid().display_offset(), point)
}
2024-01-30 08:59:19 +01:00
pub fn report_mouse(
&mut self,
event: cosmic::iced::Event,
modifiers: &cosmic::iced::keyboard::Modifiers,
x: u32,
y: u32,
) {
let term_lock = self.term.lock();
let mode = term_lock.mode();
2024-02-05 15:59:07 +01:00
#[allow(clippy::collapsible_else_if)]
2024-01-30 08:59:19 +01:00
if mode.contains(TermMode::SGR_MOUSE) {
if let Some(code) = self.mouse_reporter.sgr_mouse_code(event, modifiers, x, y) {
self.input_no_scroll(code)
}
} else {
if let Some(code) = self.mouse_reporter.normal_mouse_code(
event,
modifiers,
mode.contains(TermMode::UTF8_MOUSE),
x,
y,
) {
self.input_no_scroll(code)
}
}
}
2024-01-31 15:43:20 +01:00
pub fn scroll_mouse(
&mut self,
delta: ScrollDelta,
modifiers: &cosmic::iced::keyboard::Modifiers,
x: u32,
y: u32,
) {
let term_lock = self.term.lock();
let mode = term_lock.mode();
if mode.contains(TermMode::SGR_MOUSE) {
self.mouse_reporter.report_sgr_mouse_wheel_scroll(
self,
self.size().cell_width,
self.size().cell_height,
delta,
modifiers,
x,
y,
);
} else {
self.mouse_reporter.report_mouse_wheel_as_arrows(
self,
self.size().cell_width,
self.size().cell_height,
delta,
);
}
2024-01-30 08:59:19 +01:00
}
2023-12-20 13:31:10 -07:00
}
impl Drop for Terminal {
fn drop(&mut self) {
// Ensure shutdown on terminal drop
2024-02-05 15:59:07 +01:00
self.notifier.0.send(Msg::Shutdown);
2023-12-20 13:31:10 -07:00
}
}