1use std::borrow::Cow;
24use std::cell::RefCell;
25use std::fmt;
26use std::ops::{Deref, Index, RangeFull};
27use std::os::raw::c_char;
28use std::str;
29
30#[derive(Debug)]
32pub struct UiBuffer {
33 pub buffer: Vec<u8>,
34 pub max_len: usize,
35}
36
37impl UiBuffer {
38 pub const fn new(max_len: usize) -> Self {
40 Self {
41 buffer: Vec::new(),
42 max_len,
43 }
44 }
45
46 pub fn scratch_txt(&mut self, txt: impl AsRef<str>) -> *const std::os::raw::c_char {
50 self.refresh_buffer();
51
52 let start_of_substr = self.push(txt);
53 unsafe { self.offset(start_of_substr) }
54 }
55
56 pub fn scratch_txt_opt(&mut self, txt: Option<impl AsRef<str>>) -> *const std::os::raw::c_char {
58 match txt {
59 Some(v) => self.scratch_txt(v),
60 None => std::ptr::null(),
61 }
62 }
63
64 pub fn scratch_txt_two(
66 &mut self,
67 txt_0: impl AsRef<str>,
68 txt_1: impl AsRef<str>,
69 ) -> (*const std::os::raw::c_char, *const std::os::raw::c_char) {
70 self.refresh_buffer();
71
72 let first_offset = self.push(txt_0);
73 let second_offset = self.push(txt_1);
74
75 unsafe { (self.offset(first_offset), self.offset(second_offset)) }
76 }
77
78 pub fn scratch_txt_with_opt(
80 &mut self,
81 txt_0: impl AsRef<str>,
82 txt_1: Option<impl AsRef<str>>,
83 ) -> (*const std::os::raw::c_char, *const std::os::raw::c_char) {
84 match txt_1 {
85 Some(value) => self.scratch_txt_two(txt_0, value),
86 None => (self.scratch_txt(txt_0), std::ptr::null()),
87 }
88 }
89
90 pub fn refresh_buffer(&mut self) {
93 if self.buffer.len() > self.max_len {
94 self.buffer.clear();
95 }
96 }
97
98 pub unsafe fn offset(&self, pos: usize) -> *const std::os::raw::c_char {
104 unsafe { self.buffer.as_ptr().add(pos) as *const _ }
105 }
106
107 pub fn push(&mut self, txt: impl AsRef<str>) -> usize {
110 let txt = txt.as_ref();
111 let len = self.buffer.len();
112 let bytes = txt.as_bytes();
113 if bytes.contains(&0) {
114 self.buffer
115 .extend(bytes.iter().map(|&b| if b == 0 { b'?' } else { b }));
116 } else {
117 self.buffer.extend(bytes);
118 }
119 self.buffer.push(b'\0');
120
121 len
122 }
123}
124
125thread_local! {
126 static TLS_SCRATCH: RefCell<UiBuffer> = RefCell::new(UiBuffer::new(1024));
127}
128
129pub(crate) fn tls_scratch_txt(txt: impl AsRef<str>) -> *const c_char {
136 TLS_SCRATCH.with(|buf| buf.borrow_mut().scratch_txt(txt))
137}
138
139pub fn with_scratch_txt<R>(txt: impl AsRef<str>, f: impl FnOnce(*const c_char) -> R) -> R {
144 TLS_SCRATCH.with(|buf| {
145 let mut buf = buf.borrow_mut();
146 let ptr = buf.scratch_txt(txt);
147 f(ptr)
148 })
149}
150
151pub(crate) fn tls_scratch_txt_two(
153 txt_0: impl AsRef<str>,
154 txt_1: impl AsRef<str>,
155) -> (*const c_char, *const c_char) {
156 TLS_SCRATCH.with(|buf| buf.borrow_mut().scratch_txt_two(txt_0, txt_1))
157}
158
159pub fn with_scratch_txt_two<R>(
164 txt_0: impl AsRef<str>,
165 txt_1: impl AsRef<str>,
166 f: impl FnOnce(*const c_char, *const c_char) -> R,
167) -> R {
168 TLS_SCRATCH.with(|buf| {
169 let mut buf = buf.borrow_mut();
170 let (a, b) = buf.scratch_txt_two(txt_0, txt_1);
171 f(a, b)
172 })
173}
174
175pub fn with_scratch_txt_three<R>(
180 txt_0: impl AsRef<str>,
181 txt_1: impl AsRef<str>,
182 txt_2: impl AsRef<str>,
183 f: impl FnOnce(*const c_char, *const c_char, *const c_char) -> R,
184) -> R {
185 TLS_SCRATCH.with(|buf| {
186 let mut buf = buf.borrow_mut();
187 buf.refresh_buffer();
188 let o0 = buf.push(txt_0);
189 let o1 = buf.push(txt_1);
190 let o2 = buf.push(txt_2);
191 unsafe { f(buf.offset(o0), buf.offset(o1), buf.offset(o2)) }
192 })
193}
194
195pub fn with_scratch_txt_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
200 TLS_SCRATCH.with(|buf| {
201 let mut buf = buf.borrow_mut();
202 buf.refresh_buffer();
203
204 let total_bytes: usize = txts.iter().map(|s| s.len() + 1).sum();
205 buf.buffer.reserve(total_bytes);
206
207 let mut offsets: Vec<usize> = Vec::with_capacity(txts.len());
208 for &s in txts {
209 offsets.push(buf.push(s));
210 }
211
212 let mut ptrs: Vec<*const c_char> = Vec::with_capacity(txts.len());
213 for off in offsets {
214 ptrs.push(unsafe { buf.offset(off) });
215 }
216
217 f(&ptrs)
218 })
219}
220
221pub fn with_scratch_txt_slice_with_opt<R>(
227 txts: &[&str],
228 txt_opt: Option<&str>,
229 f: impl FnOnce(&[*const c_char], *const c_char) -> R,
230) -> R {
231 TLS_SCRATCH.with(|buf| {
232 let mut buf = buf.borrow_mut();
233 buf.refresh_buffer();
234
235 let total_bytes: usize = txts.iter().map(|s| s.len() + 1).sum::<usize>()
236 + txt_opt.map(|s| s.len() + 1).unwrap_or(0);
237 buf.buffer.reserve(total_bytes);
238
239 let mut offsets: Vec<usize> = Vec::with_capacity(txts.len());
240 for &s in txts {
241 offsets.push(buf.push(s));
242 }
243
244 let opt_off = txt_opt.map(|s| buf.push(s));
245
246 let mut ptrs: Vec<*const c_char> = Vec::with_capacity(txts.len());
247 for off in offsets {
248 ptrs.push(unsafe { buf.offset(off) });
249 }
250
251 let opt_ptr = match opt_off {
252 Some(off) => unsafe { buf.offset(off) },
253 None => std::ptr::null(),
254 };
255
256 f(&ptrs, opt_ptr)
257 })
258}
259
260#[derive(Clone, Hash, Ord, Eq, PartialOrd, PartialEq)]
262pub struct ImString(pub(crate) Vec<u8>);
263
264impl ImString {
265 pub fn new<T: Into<String>>(value: T) -> ImString {
267 let value = value.into();
268 assert!(!value.contains('\0'), "ImString contained null byte");
269 unsafe {
270 let mut s = ImString::from_utf8_unchecked(value.into_bytes());
271 s.refresh_len();
272 s
273 }
274 }
275
276 #[inline]
278 pub fn with_capacity(capacity: usize) -> ImString {
279 let mut v = Vec::with_capacity(capacity + 1);
280 v.push(b'\0');
281 ImString(v)
282 }
283
284 #[inline]
291 pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> ImString {
292 v.push(b'\0');
293 ImString(v)
294 }
295
296 #[inline]
303 pub unsafe fn from_utf8_with_nul_unchecked(v: Vec<u8>) -> ImString {
304 ImString(v)
305 }
306
307 #[inline]
309 pub fn clear(&mut self) {
310 self.0.clear();
311 self.0.push(b'\0');
312 }
313
314 #[inline]
316 pub fn push(&mut self, ch: char) {
317 let mut buf = [0; 4];
318 self.push_str(ch.encode_utf8(&mut buf));
319 }
320
321 #[inline]
323 pub fn push_str(&mut self, string: &str) {
324 assert!(!string.contains('\0'), "ImString contained null byte");
325 self.0.pop();
326 self.0.extend(string.bytes());
327 self.0.push(b'\0');
328 unsafe {
329 self.refresh_len();
330 }
331 }
332
333 #[inline]
335 pub fn capacity(&self) -> usize {
336 self.0.capacity() - 1
337 }
338
339 #[inline]
341 pub fn capacity_with_nul(&self) -> usize {
342 self.0.capacity()
343 }
344
345 pub fn reserve(&mut self, additional: usize) {
350 self.0.reserve(additional);
351 }
352
353 pub fn reserve_exact(&mut self, additional: usize) {
356 self.0.reserve_exact(additional);
357 }
358
359 #[inline]
361 pub fn as_ptr(&self) -> *const c_char {
362 self.0.as_ptr() as *const c_char
363 }
364
365 #[inline]
369 pub fn as_mut_ptr(&mut self) -> *mut c_char {
370 self.0.as_mut_ptr() as *mut c_char
371 }
372
373 pub(crate) fn ensure_buf_size(&mut self, buf_size: usize) {
378 if self.0.len() < buf_size {
379 self.0.resize(buf_size, 0);
380 } else if self.0.len() > buf_size {
381 self.0.truncate(buf_size);
382 if let Some(last) = self.0.last_mut() {
383 *last = 0;
384 } else {
385 self.0.push(0);
386 }
387 } else if let Some(last) = self.0.last_mut() {
388 *last = 0;
389 }
390 }
391
392 pub unsafe fn refresh_len(&mut self) {
403 if let Some(pos) = self.0.iter().position(|&b| b == 0) {
404 self.0.truncate(pos + 1);
405 return;
406 }
407
408 let cap = self.0.capacity();
409 if cap == 0 {
410 self.0.push(0);
411 return;
412 }
413
414 let ptr = self.0.as_ptr();
415 let bytes = unsafe { std::slice::from_raw_parts(ptr, cap) };
416 let pos = bytes.iter().position(|&b| b == 0).unwrap_or(cap - 1);
417 if pos == cap - 1 && bytes[pos] != 0 {
418 unsafe {
419 *self.0.as_mut_ptr().add(cap - 1) = 0;
420 }
421 }
422 unsafe {
423 self.0.set_len(pos + 1);
424 }
425 }
426
427 pub fn len(&self) -> usize {
429 self.0.len().saturating_sub(1)
430 }
431
432 pub fn is_empty(&self) -> bool {
434 self.len() == 0
435 }
436
437 pub fn to_str(&self) -> &str {
439 unsafe { str::from_utf8_unchecked(&self.0[..self.len()]) }
440 }
441}
442
443impl Default for ImString {
444 fn default() -> Self {
445 ImString::with_capacity(0)
446 }
447}
448
449impl fmt::Display for ImString {
450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451 fmt::Display::fmt(self.to_str(), f)
452 }
453}
454
455impl fmt::Debug for ImString {
456 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 fmt::Debug::fmt(self.to_str(), f)
458 }
459}
460
461impl Deref for ImString {
462 type Target = str;
463 fn deref(&self) -> &str {
464 self.to_str()
465 }
466}
467
468impl AsRef<str> for ImString {
469 fn as_ref(&self) -> &str {
470 self.to_str()
471 }
472}
473
474impl From<String> for ImString {
475 fn from(s: String) -> ImString {
476 ImString::new(s)
477 }
478}
479
480impl From<&str> for ImString {
481 fn from(s: &str) -> ImString {
482 ImString::new(s)
483 }
484}
485
486impl Index<RangeFull> for ImString {
487 type Output = str;
488 fn index(&self, _index: RangeFull) -> &str {
489 self.to_str()
490 }
491}
492
493pub type ImStr<'a> = Cow<'a, str>;
495
496#[macro_export]
498macro_rules! im_str {
499 ($e:expr) => {{ $crate::ImString::new($e) }};
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use std::ffi::CStr;
506
507 #[test]
508 fn im_string_ensure_buf_size_resizes_and_nul_terminates() {
509 let mut s = ImString::new("abc");
510 s.ensure_buf_size(16);
511 assert_eq!(s.0.len(), 16);
512 assert_eq!(&s.0[..3], b"abc");
513 assert_eq!(s.0[3], 0);
514 assert!(s.0[4..].iter().all(|&b| b == 0));
515 }
516
517 #[test]
518 fn im_string_refresh_len_scans_capacity_when_len_has_no_nul() {
519 let mut v = vec![b'x'; 16];
520 v[..4].copy_from_slice(b"abcd");
521 v[10] = 0;
522 v.truncate(4);
523
524 let mut s = ImString(v);
525 unsafe { s.refresh_len() };
526 assert_eq!(s.to_str(), "abcdxxxxxx");
527 assert_eq!(s.0.last().copied(), Some(0));
528 }
529
530 #[test]
531 fn ui_buffer_push_appends_nul() {
532 let mut buf = UiBuffer::new(1024);
533 let start = buf.push("abc");
534 assert_eq!(start, 0);
535 assert_eq!(&buf.buffer, b"abc\0");
536 }
537
538 #[test]
539 fn ui_buffer_sanitizes_interior_nul() {
540 let mut buf = UiBuffer::new(1024);
541 let ptr = buf.scratch_txt("a\0b");
542 let s = unsafe { CStr::from_ptr(ptr) }.to_str().unwrap();
543 assert_eq!(s, "a?b");
544 }
545
546 #[test]
547 fn tls_scratch_txt_is_nul_terminated() {
548 let ptr = tls_scratch_txt("hello");
549 let s = unsafe { CStr::from_ptr(ptr) }.to_str().unwrap();
550 assert_eq!(s, "hello");
551 }
552
553 #[test]
554 fn tls_scratch_txt_two_returns_two_valid_strings() {
555 let (a_ptr, b_ptr) = tls_scratch_txt_two("a", "bcd");
556 let a = unsafe { CStr::from_ptr(a_ptr) }.to_str().unwrap();
557 let b = unsafe { CStr::from_ptr(b_ptr) }.to_str().unwrap();
558 assert_eq!(a, "a");
559 assert_eq!(b, "bcd");
560 }
561
562 #[test]
563 fn with_scratch_txt_slice_returns_sequential_pointers() {
564 with_scratch_txt_slice(&["a", "bc", "def"], |ptrs| {
565 assert_eq!(ptrs.len(), 3);
566
567 let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
568 let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
569 let c = unsafe { CStr::from_ptr(ptrs[2]) }.to_str().unwrap();
570 assert_eq!(a, "a");
571 assert_eq!(b, "bc");
572 assert_eq!(c, "def");
573
574 let ab = (ptrs[1] as usize) - (ptrs[0] as usize);
575 let bc = (ptrs[2] as usize) - (ptrs[1] as usize);
576 assert_eq!(ab, "a".len() + 1);
577 assert_eq!(bc, "bc".len() + 1);
578 });
579 }
580
581 #[test]
582 fn with_scratch_txt_slice_with_opt_returns_null_for_none() {
583 with_scratch_txt_slice_with_opt(&["a", "bc"], None, |ptrs, opt_ptr| {
584 assert_eq!(ptrs.len(), 2);
585 assert!(opt_ptr.is_null());
586
587 let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
588 let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
589 assert_eq!(a, "a");
590 assert_eq!(b, "bc");
591 });
592 }
593
594 #[test]
595 fn with_scratch_txt_slice_with_opt_appends_opt_string() {
596 with_scratch_txt_slice_with_opt(&["a", "bc"], Some("fmt"), |ptrs, opt_ptr| {
597 assert_eq!(ptrs.len(), 2);
598 assert!(!opt_ptr.is_null());
599
600 let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
601 let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
602 let fmt = unsafe { CStr::from_ptr(opt_ptr) }.to_str().unwrap();
603 assert_eq!(a, "a");
604 assert_eq!(b, "bc");
605 assert_eq!(fmt, "fmt");
606
607 let ab = (ptrs[1] as usize) - (ptrs[0] as usize);
608 let bf = (opt_ptr as usize) - (ptrs[1] as usize);
609 assert_eq!(ab, "a".len() + 1);
610 assert_eq!(bf, "bc".len() + 1);
611 });
612 }
613
614 #[test]
615 #[should_panic(expected = "null byte")]
616 fn imstring_new_rejects_interior_nul() {
617 let _ = ImString::new("a\0b");
618 }
619
620 #[test]
621 #[should_panic(expected = "null byte")]
622 fn imstring_push_str_rejects_interior_nul() {
623 let mut s = ImString::new("a");
624 s.push_str("b\0c");
625 }
626}