use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt;
use std::ops::{Deref, Index, RangeFull};
use std::os::raw::c_char;
use std::str;
#[derive(Debug)]
pub struct UiBuffer {
pub buffer: Vec<u8>,
pub max_len: usize,
}
impl UiBuffer {
pub const fn new(max_len: usize) -> Self {
Self {
buffer: Vec::new(),
max_len,
}
}
pub fn scratch_txt(&mut self, txt: impl AsRef<str>) -> *const std::os::raw::c_char {
self.refresh_buffer();
let start_of_substr = self.push(txt);
unsafe { self.offset(start_of_substr) }
}
pub fn scratch_txt_opt(&mut self, txt: Option<impl AsRef<str>>) -> *const std::os::raw::c_char {
match txt {
Some(v) => self.scratch_txt(v),
None => std::ptr::null(),
}
}
pub fn scratch_txt_two(
&mut self,
txt_0: impl AsRef<str>,
txt_1: impl AsRef<str>,
) -> (*const std::os::raw::c_char, *const std::os::raw::c_char) {
self.refresh_buffer();
let first_offset = self.push(txt_0);
let second_offset = self.push(txt_1);
unsafe { (self.offset(first_offset), self.offset(second_offset)) }
}
pub fn scratch_txt_with_opt(
&mut self,
txt_0: impl AsRef<str>,
txt_1: Option<impl AsRef<str>>,
) -> (*const std::os::raw::c_char, *const std::os::raw::c_char) {
match txt_1 {
Some(value) => self.scratch_txt_two(txt_0, value),
None => (self.scratch_txt(txt_0), std::ptr::null()),
}
}
pub fn refresh_buffer(&mut self) {
if self.buffer.len() > self.max_len {
self.buffer.clear();
}
}
pub unsafe fn offset(&self, pos: usize) -> *const std::os::raw::c_char {
unsafe { self.buffer.as_ptr().add(pos) as *const _ }
}
pub fn push(&mut self, txt: impl AsRef<str>) -> usize {
let txt = txt.as_ref();
let len = self.buffer.len();
let bytes = txt.as_bytes();
if bytes.contains(&0) {
self.buffer
.extend(bytes.iter().map(|&b| if b == 0 { b'?' } else { b }));
} else {
self.buffer.extend(bytes);
}
self.buffer.push(b'\0');
len
}
}
thread_local! {
static TLS_SCRATCH: RefCell<UiBuffer> = RefCell::new(UiBuffer::new(1024));
}
pub(crate) fn tls_scratch_txt(txt: impl AsRef<str>) -> *const c_char {
TLS_SCRATCH.with(|buf| buf.borrow_mut().scratch_txt(txt))
}
pub fn with_scratch_txt<R>(txt: impl AsRef<str>, f: impl FnOnce(*const c_char) -> R) -> R {
TLS_SCRATCH.with(|buf| {
let mut buf = buf.borrow_mut();
let ptr = buf.scratch_txt(txt);
f(ptr)
})
}
pub(crate) fn tls_scratch_txt_two(
txt_0: impl AsRef<str>,
txt_1: impl AsRef<str>,
) -> (*const c_char, *const c_char) {
TLS_SCRATCH.with(|buf| buf.borrow_mut().scratch_txt_two(txt_0, txt_1))
}
pub fn with_scratch_txt_two<R>(
txt_0: impl AsRef<str>,
txt_1: impl AsRef<str>,
f: impl FnOnce(*const c_char, *const c_char) -> R,
) -> R {
TLS_SCRATCH.with(|buf| {
let mut buf = buf.borrow_mut();
let (a, b) = buf.scratch_txt_two(txt_0, txt_1);
f(a, b)
})
}
pub fn with_scratch_txt_three<R>(
txt_0: impl AsRef<str>,
txt_1: impl AsRef<str>,
txt_2: impl AsRef<str>,
f: impl FnOnce(*const c_char, *const c_char, *const c_char) -> R,
) -> R {
TLS_SCRATCH.with(|buf| {
let mut buf = buf.borrow_mut();
buf.refresh_buffer();
let o0 = buf.push(txt_0);
let o1 = buf.push(txt_1);
let o2 = buf.push(txt_2);
unsafe { f(buf.offset(o0), buf.offset(o1), buf.offset(o2)) }
})
}
pub fn with_scratch_txt_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
TLS_SCRATCH.with(|buf| {
let mut buf = buf.borrow_mut();
buf.refresh_buffer();
let total_bytes: usize = txts.iter().map(|s| s.len() + 1).sum();
buf.buffer.reserve(total_bytes);
let mut offsets: Vec<usize> = Vec::with_capacity(txts.len());
for &s in txts {
offsets.push(buf.push(s));
}
let mut ptrs: Vec<*const c_char> = Vec::with_capacity(txts.len());
for off in offsets {
ptrs.push(unsafe { buf.offset(off) });
}
f(&ptrs)
})
}
pub fn with_scratch_txt_slice_with_opt<R>(
txts: &[&str],
txt_opt: Option<&str>,
f: impl FnOnce(&[*const c_char], *const c_char) -> R,
) -> R {
TLS_SCRATCH.with(|buf| {
let mut buf = buf.borrow_mut();
buf.refresh_buffer();
let total_bytes: usize = txts.iter().map(|s| s.len() + 1).sum::<usize>()
+ txt_opt.map(|s| s.len() + 1).unwrap_or(0);
buf.buffer.reserve(total_bytes);
let mut offsets: Vec<usize> = Vec::with_capacity(txts.len());
for &s in txts {
offsets.push(buf.push(s));
}
let opt_off = txt_opt.map(|s| buf.push(s));
let mut ptrs: Vec<*const c_char> = Vec::with_capacity(txts.len());
for off in offsets {
ptrs.push(unsafe { buf.offset(off) });
}
let opt_ptr = match opt_off {
Some(off) => unsafe { buf.offset(off) },
None => std::ptr::null(),
};
f(&ptrs, opt_ptr)
})
}
#[derive(Clone, Hash, Ord, Eq, PartialOrd, PartialEq)]
pub struct ImString(pub(crate) Vec<u8>);
impl ImString {
pub fn new<T: Into<String>>(value: T) -> ImString {
let value = value.into();
assert!(!value.contains('\0'), "ImString contained null byte");
unsafe {
let mut s = ImString::from_utf8_unchecked(value.into_bytes());
s.refresh_len();
s
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> ImString {
let mut v = Vec::with_capacity(capacity + 1);
v.push(b'\0');
ImString(v)
}
#[inline]
pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> ImString {
v.push(b'\0');
ImString(v)
}
#[inline]
pub unsafe fn from_utf8_with_nul_unchecked(v: Vec<u8>) -> ImString {
ImString(v)
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
self.0.push(b'\0');
}
#[inline]
pub fn push(&mut self, ch: char) {
let mut buf = [0; 4];
self.push_str(ch.encode_utf8(&mut buf));
}
#[inline]
pub fn push_str(&mut self, string: &str) {
assert!(!string.contains('\0'), "ImString contained null byte");
self.0.pop();
self.0.extend(string.bytes());
self.0.push(b'\0');
unsafe {
self.refresh_len();
}
}
#[inline]
pub fn capacity(&self) -> usize {
self.0.capacity() - 1
}
#[inline]
pub fn capacity_with_nul(&self) -> usize {
self.0.capacity()
}
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional);
}
pub fn reserve_exact(&mut self, additional: usize) {
self.0.reserve_exact(additional);
}
#[inline]
pub fn as_ptr(&self) -> *const c_char {
self.0.as_ptr() as *const c_char
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut c_char {
self.0.as_mut_ptr() as *mut c_char
}
pub(crate) fn ensure_buf_size(&mut self, buf_size: usize) {
if self.0.len() < buf_size {
self.0.resize(buf_size, 0);
} else if self.0.len() > buf_size {
self.0.truncate(buf_size);
if let Some(last) = self.0.last_mut() {
*last = 0;
} else {
self.0.push(0);
}
} else if let Some(last) = self.0.last_mut() {
*last = 0;
}
}
pub unsafe fn refresh_len(&mut self) {
if let Some(pos) = self.0.iter().position(|&b| b == 0) {
self.0.truncate(pos + 1);
} else {
self.0.push(0);
}
}
pub fn len(&self) -> usize {
self.0.len().saturating_sub(1)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.0[..self.len()]) }
}
}
impl Default for ImString {
fn default() -> Self {
ImString::with_capacity(0)
}
}
impl fmt::Display for ImString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.to_str(), f)
}
}
impl fmt::Debug for ImString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.to_str(), f)
}
}
impl Deref for ImString {
type Target = str;
fn deref(&self) -> &str {
self.to_str()
}
}
impl AsRef<str> for ImString {
fn as_ref(&self) -> &str {
self.to_str()
}
}
impl From<String> for ImString {
fn from(s: String) -> ImString {
ImString::new(s)
}
}
impl From<&str> for ImString {
fn from(s: &str) -> ImString {
ImString::new(s)
}
}
impl Index<RangeFull> for ImString {
type Output = str;
fn index(&self, _index: RangeFull) -> &str {
self.to_str()
}
}
pub type ImStr<'a> = Cow<'a, str>;
#[macro_export]
macro_rules! im_str {
($e:expr) => {{ $crate::ImString::new($e) }};
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn im_string_ensure_buf_size_resizes_and_nul_terminates() {
let mut s = ImString::new("abc");
s.ensure_buf_size(16);
assert_eq!(s.0.len(), 16);
assert_eq!(&s.0[..3], b"abc");
assert_eq!(s.0[3], 0);
assert!(s.0[4..].iter().all(|&b| b == 0));
}
#[test]
fn im_string_refresh_len_does_not_scan_spare_capacity() {
let mut v = vec![b'x'; 16];
v[..4].copy_from_slice(b"abcd");
v[10] = 0;
v.truncate(4);
let mut s = ImString(v);
unsafe { s.refresh_len() };
assert_eq!(s.to_str(), "abcd");
assert_eq!(s.0.last().copied(), Some(0));
}
#[test]
fn ui_buffer_push_appends_nul() {
let mut buf = UiBuffer::new(1024);
let start = buf.push("abc");
assert_eq!(start, 0);
assert_eq!(&buf.buffer, b"abc\0");
}
#[test]
fn ui_buffer_sanitizes_interior_nul() {
let mut buf = UiBuffer::new(1024);
let ptr = buf.scratch_txt("a\0b");
let s = unsafe { CStr::from_ptr(ptr) }.to_str().unwrap();
assert_eq!(s, "a?b");
}
#[test]
fn tls_scratch_txt_is_nul_terminated() {
let ptr = tls_scratch_txt("hello");
let s = unsafe { CStr::from_ptr(ptr) }.to_str().unwrap();
assert_eq!(s, "hello");
}
#[test]
fn tls_scratch_txt_two_returns_two_valid_strings() {
let (a_ptr, b_ptr) = tls_scratch_txt_two("a", "bcd");
let a = unsafe { CStr::from_ptr(a_ptr) }.to_str().unwrap();
let b = unsafe { CStr::from_ptr(b_ptr) }.to_str().unwrap();
assert_eq!(a, "a");
assert_eq!(b, "bcd");
}
#[test]
fn with_scratch_txt_slice_returns_sequential_pointers() {
with_scratch_txt_slice(&["a", "bc", "def"], |ptrs| {
assert_eq!(ptrs.len(), 3);
let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
let c = unsafe { CStr::from_ptr(ptrs[2]) }.to_str().unwrap();
assert_eq!(a, "a");
assert_eq!(b, "bc");
assert_eq!(c, "def");
let ab = (ptrs[1] as usize) - (ptrs[0] as usize);
let bc = (ptrs[2] as usize) - (ptrs[1] as usize);
assert_eq!(ab, "a".len() + 1);
assert_eq!(bc, "bc".len() + 1);
});
}
#[test]
fn with_scratch_txt_slice_with_opt_returns_null_for_none() {
with_scratch_txt_slice_with_opt(&["a", "bc"], None, |ptrs, opt_ptr| {
assert_eq!(ptrs.len(), 2);
assert!(opt_ptr.is_null());
let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
assert_eq!(a, "a");
assert_eq!(b, "bc");
});
}
#[test]
fn with_scratch_txt_slice_with_opt_appends_opt_string() {
with_scratch_txt_slice_with_opt(&["a", "bc"], Some("fmt"), |ptrs, opt_ptr| {
assert_eq!(ptrs.len(), 2);
assert!(!opt_ptr.is_null());
let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
let fmt = unsafe { CStr::from_ptr(opt_ptr) }.to_str().unwrap();
assert_eq!(a, "a");
assert_eq!(b, "bc");
assert_eq!(fmt, "fmt");
let ab = (ptrs[1] as usize) - (ptrs[0] as usize);
let bf = (opt_ptr as usize) - (ptrs[1] as usize);
assert_eq!(ab, "a".len() + 1);
assert_eq!(bf, "bc".len() + 1);
});
}
#[test]
#[should_panic(expected = "null byte")]
fn imstring_new_rejects_interior_nul() {
let _ = ImString::new("a\0b");
}
#[test]
#[should_panic(expected = "null byte")]
fn imstring_push_str_rejects_interior_nul() {
let mut s = ImString::new("a");
s.push_str("b\0c");
}
}