Skip to content
Open
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
32 changes: 13 additions & 19 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ icy_sixel = { version = "^0.5.0" }
serde = { version = "^1.0", optional = true, features = ["derive"] }
base64-simd = { version = "0.8" }
rand = { version = "^0.8.5" }
ratatui = { version = "^0.30.0", default-features = false, features = [] }
ratatui = { git = "https://github.com/ratatui/ratatui", branch = "main", default-features = false, features = [] }
thiserror = { version = "1.0.59" }
tokio = { version = "1.45.1", default-features = false, optional = true, features = ["sync"]}

Expand Down
8 changes: 7 additions & 1 deletion src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
num::NonZeroUsize,
};

use image::{DynamicImage, ImageBuffer, Rgba, imageops};
use ratatui::{buffer::Buffer, layout::Rect};
use ratatui::{
buffer::{Buffer, CellDiffOption},
layout::Rect,
};

use self::{
halfblocks::Halfblocks,
Expand All @@ -23,6 +27,8 @@ pub mod iterm2;
pub mod kitty;
pub mod sixel;

const UNIT_WIDTH: CellDiffOption = CellDiffOption::ForcedWidth(NonZeroUsize::new(1).unwrap());

trait ProtocolTrait: Send + Sync {
/// Render the currently resized and encoded data to the buffer.
fn render(&self, area: Rect, buf: &mut Buffer);
Expand Down
27 changes: 15 additions & 12 deletions src/protocol/iterm2.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
//! ITerm2 protocol implementation.
use image::DynamicImage;
use ratatui::{buffer::Buffer, layout::Rect};
use std::{cmp::min, fmt::Write, io::Cursor};

use crate::{Result, picker::cap_parser::Parser};
use image::DynamicImage;
use ratatui::{
buffer::{Buffer, CellDiffOption},
layout::Rect,
};

use crate::{Result, picker::cap_parser::Parser, protocol::UNIT_WIDTH};

use super::{ProtocolTrait, StatefulProtocolTrait};

Expand Down Expand Up @@ -82,19 +86,18 @@ fn render(rect: Rect, data: &str, area: Rect, buf: &mut Buffer, overdraw: bool)
Some(r) => r,
};

buf.cell_mut(render_area).map(|cell| cell.set_symbol(data));

for x in (render_area.left() + 1)..render_area.right() {
if let Some(cell) = buf.cell_mut((x, render_area.top())) {
cell.set_skip(true);
}
if let Some(cell) = buf.cell_mut(render_area) {
cell.set_symbol(data).set_diff_option(UNIT_WIDTH);
}

// Skip entire area
for y in (render_area.top() + 1)..render_area.bottom() {
// Skip entire area (except first cell)
for y in render_area.top()..render_area.bottom() {
for x in render_area.left()..render_area.right() {
if x == render_area.left() && y == render_area.top() {
continue;
}
if let Some(cell) = buf.cell_mut((x, y)) {
cell.set_skip(true);
cell.set_diff_option(CellDiffOption::Skip);
}
}
}
Expand Down
84 changes: 28 additions & 56 deletions src/protocol/kitty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::Write;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use crate::protocol::UNIT_WIDTH;
use crate::{Result, picker::cap_parser::Parser};
use image::DynamicImage;
use ratatui::{buffer::Buffer, layout::Rect};
Expand Down Expand Up @@ -113,6 +114,11 @@ impl StatefulProtocolTrait for StatefulKitty {
}
}

// Save cursor position, which is the only way to save the current foreground color.
// This is the reason why we need to restore the cursor and move 1 column to the right.
const SAVE_CURSOR: &str = "\x1b[s";
const RESTORE_CURSOR: &str = "\x1b[u\x1b[1C";

fn render(
area: Rect,
rect: Rect,
Expand All @@ -121,72 +127,38 @@ fn render(
mut seq: Option<&str>,
) {
let full_width = area.width.min(rect.width);
let width_usize = usize::from(full_width);

let estimated_placeholder_row_size = id_color.len() +
30 + // diacritics
(width_usize * 4) +
30; // restore cursor dance
let estimated_transmit_row_size =
estimated_placeholder_row_size + if let Some(seq) = seq { seq.len() } else { 0 };
let mut symbol = String::with_capacity(estimated_transmit_row_size);

let row_diacritics: String = std::iter::repeat_n('\u{10EEEE}', width_usize - 1).collect();

// Restore saved cursor position including color, and now we have to move back to
// the end of the area.
let right = area.width - 1;
let down = area.height - 1;
let restore_cursor = format!("\x1b[u\x1b[{right}C\x1b[{down}B");

// Clamp to effectively 297, the number of placeholders in the Kitty protocol.
// Anything beyond would just render the something that's wrong, so skip.
let height = area.height.min(rect.height).min(DIACRITICS.len() as u16);
let width = full_width.min(DIACRITICS.len() as u16);

let mut symbol = String::new();
for y in 0..height {
// Draw each line of unicode placeholders but all into the first cell.
// I couldn't work out actually drawing into each cell of the buffer so
// that `.set_skip(true)` would be made unnecessary. Maybe some other escape
// sequence gets sneaked in somehow.
// It could also be made so that each cell starts and ends its own escape sequence
// with the image id, but maybe that's worse.
symbol.clear();
if y == 1 {
symbol.shrink_to(estimated_placeholder_row_size);
}
for x in 0..width {
symbol.clear();

// Prepend transmit sequence to first cell only
if x == 0 && y == 0 {
if let Some(seq) = seq.take() {
symbol.push_str(seq);
}
}

// If not transmitted in previous renders, only transmit once at the
// first line.
if let Some(seq) = seq.take() {
symbol.push_str(seq);
}
// Each placeholder has full diacritics: row, column, id_extra
write!(
symbol,
"{SAVE_CURSOR}{id_color}\u{10EEEE}{}{}{}{RESTORE_CURSOR}",
diacritic(y),
diacritic(x),
diacritic(*id_extra)
)
.unwrap();

// Save cursor position, including fg color which is what we want, and start the unicode
// placeholder sequence
write!(
symbol,
"\x1b[s{id_color}\u{10EEEE}{}{}{}",
diacritic(y),
diacritic(0),
diacritic(*id_extra)
)
.unwrap();

// Add entire row with positions
// Use inherited diacritic values
symbol.push_str(&row_diacritics);

for x in 1..full_width {
// Skip or something may overwrite it
if let Some(cell) = buf.cell_mut((area.left() + x, area.top() + y)) {
cell.set_skip(true);
cell.set_symbol(&symbol).set_diff_option(UNIT_WIDTH);
}
}

symbol.push_str(&restore_cursor);

if let Some(cell) = buf.cell_mut((area.left(), area.top() + y)) {
cell.set_symbol(&symbol);
}
}
}

Expand Down
23 changes: 14 additions & 9 deletions src/protocol/sixel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
//! [`icy_sixel`]: https://github.com/mkrueger/icy_sixel
//! [supports]: https://arewesixelyet.com
//! [Sixel]: https://en.wikipedia.org/wiki/Sixel
use std::{cmp::min, fmt::Write};

use icy_sixel::{EncodeOptions, sixel_encode};
use image::DynamicImage;
use ratatui::{buffer::Buffer, layout::Rect};
use std::{cmp::min, fmt::Write};
use ratatui::{
buffer::{Buffer, CellDiffOption},
layout::Rect,
};

use super::{ProtocolTrait, StatefulProtocolTrait};
use crate::{Result, errors::Errors, picker::cap_parser::Parser};
use crate::{Result, errors::Errors, picker::cap_parser::Parser, protocol::UNIT_WIDTH};

// Fixed sixel protocol
#[derive(Clone, Default)]
Expand Down Expand Up @@ -109,17 +113,18 @@ fn render(rect: Rect, data: &str, area: Rect, buf: &mut Buffer, overdraw: bool)
Some(r) => r,
};

buf.cell_mut(render_area).map(|cell| cell.set_symbol(data));
let mut skip_first = false;
if let Some(cell) = buf.cell_mut(render_area) {
cell.set_symbol(data).set_diff_option(UNIT_WIDTH);
}

// Skip entire area
// Skip entire area (except first cell)
for y in render_area.top()..render_area.bottom() {
for x in render_area.left()..render_area.right() {
if !skip_first {
skip_first = true;
if x == render_area.left() && y == render_area.top() {
continue;
}
buf.cell_mut((x, y)).map(|cell| cell.set_skip(true));
buf.cell_mut((x, y))
.map(|cell| cell.set_diff_option(CellDiffOption::Skip));
}
}
}
Expand Down
Loading