Skip to content
Merged
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
15 changes: 15 additions & 0 deletions crates/edit/src/collections/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ impl<'a> BString<'a> {
res
}

/// Constructs a [`BString`] from ptr, length, and capacity.
///
/// # Safety
///
/// If you pass invalid arguments, this code will `rm -rf` your disk.
#[inline]
pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> Self {
unsafe { Self { vec: BVec::from_raw_parts(buf, length, capacity) } }
}

#[inline]
pub fn into_raw_parts(self) -> (*mut u8, usize, usize) {
self.vec.into_raw_parts()
}

/// Length in bytes, not characters.
#[inline]
pub fn len(&self) -> usize {
Expand Down
22 changes: 22 additions & 0 deletions crates/edit/src/collections/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,28 @@ impl<'a, T> BVec<'a, T> {
unsafe { Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.cap) }
}

/// Constructs a [`BVec`] from ptr, length, and capacity.
///
/// # Safety
///
/// If you pass invalid arguments, this code will `rm -rf` your disk.
#[inline]
pub unsafe fn from_raw_parts(buf: *mut T, length: usize, capacity: usize) -> Self {
Self {
ptr: unsafe { NonNull::new_unchecked(buf) },
len: length,
cap: capacity,
_marker: PhantomData,
#[cfg(debug_assertions)]
alloc: None,
}
}

#[inline]
pub fn into_raw_parts(mut self) -> (*mut T, usize, usize) {
(self.as_mut_ptr(), self.len(), self.capacity())
}

/// Number of initialized elements.
#[inline]
pub fn len(&self) -> usize {
Expand Down
34 changes: 32 additions & 2 deletions crates/edit/src/framebuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@ impl Framebuffer {
/// Renders the framebuffer contents accumulated since the
/// last call to `flip()` and returns them serialized as VT.
pub fn render<'a>(&mut self, arena: &'a Arena) -> BString<'a> {
// DEC private mode 2026, Synchronized Output, lets us avoid flickering / tearing.
const SYNCHRONIZED_OUTPUT_ENABLE: &str = "\x1b[?2026h";
const SYNCHRONIZED_OUTPUT_DISABLE: &str = "\x1b[?2026l";
const OUTPUT_PREFIX_LEN: usize = SYNCHRONIZED_OUTPUT_ENABLE.len();

let idx = self.frame_counter & 1;
// Borrows the front/back buffers without letting Rust know that we have a reference to self.
// SAFETY: Well this is certainly correct, but whether Rust and its strict rules likes it is another question.
Expand Down Expand Up @@ -500,6 +505,8 @@ impl Framebuffer {
let mut last_fg = u64::MAX;
let mut last_attr = Attributes::None;

result.push_str(arena, SYNCHRONIZED_OUTPUT_ENABLE);

for y in 0..front.text.size.height {
// SAFETY: The only thing that changes the size of these containers,
// is the reset() method and it always resets front/back to the same size.
Expand Down Expand Up @@ -527,7 +534,10 @@ impl Framebuffer {
let mut cfg = MeasurementConfig::new(&line_bytes);
let mut chunk_end = 0;

if result.is_empty() {
// Since we don't retain the `last_attr` between frames and start with
// `last_attr = None` we have to reset all attributes to get a clean
// initial state. This only applies if we print any text, of course.
if result.len() <= OUTPUT_PREFIX_LEN {
result.push_str(arena, "\x1b[m");
}
arena_write_fmt!(arena, result, "\x1b[{};1H", y + 1);
Expand Down Expand Up @@ -600,7 +610,7 @@ impl Framebuffer {
// If the cursor has changed since the last frame we naturally need to update it,
// but this also applies if the code above wrote to the screen,
// as it uses CUP sequences to reposition the cursor for writing.
if !result.is_empty() || back.cursor != front.cursor {
if result.len() > OUTPUT_PREFIX_LEN || back.cursor != front.cursor {
if back.cursor.pos.x >= 0 && back.cursor.pos.y >= 0 {
// CUP to the cursor position.
// DECSCUSR to set the cursor style.
Expand All @@ -619,6 +629,26 @@ impl Framebuffer {
}
}

if !result.is_empty() {
if result.len() <= 512 {
// Synchronized Output carries a certain overhead in most terminals that implement it.
// It can be slightly beneficial to avoid this. The problem is that there's no amount of
// bytes where a write into a TTY is rendered atomically. But IME, 512 bytes is pretty safe.
//
// Although, there may be a part in this story where the code primarily exists, because I found
// the thought of cutting off the head of the string while keeping its spare capacity hilarious.
unsafe {
let (mut ptr, mut len, mut cap) = result.into_raw_parts();
ptr = ptr.add(SYNCHRONIZED_OUTPUT_ENABLE.len());
len -= SYNCHRONIZED_OUTPUT_ENABLE.len();
cap -= SYNCHRONIZED_OUTPUT_ENABLE.len();
result = BString::from_raw_parts(ptr, len, cap);
}
} else {
result.push_str(arena, SYNCHRONIZED_OUTPUT_DISABLE);
}
}

result
}

Expand Down
Loading