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) {
399 if let Some(pos) = self.0.iter().position(|&b| b == 0) {
400 self.0.truncate(pos + 1);
401 } else {
402 self.0.push(0);
403 }
404 }
405
406 pub fn len(&self) -> usize {
408 self.0.len().saturating_sub(1)
409 }
410
411 pub fn is_empty(&self) -> bool {
413 self.len() == 0
414 }
415
416 pub fn to_str(&self) -> &str {
418 unsafe { str::from_utf8_unchecked(&self.0[..self.len()]) }
419 }
420}
421
422impl Default for ImString {
423 fn default() -> Self {
424 ImString::with_capacity(0)
425 }
426}
427
428impl fmt::Display for ImString {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 fmt::Display::fmt(self.to_str(), f)
431 }
432}
433
434impl fmt::Debug for ImString {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 fmt::Debug::fmt(self.to_str(), f)
437 }
438}
439
440impl Deref for ImString {
441 type Target = str;
442 fn deref(&self) -> &str {
443 self.to_str()
444 }
445}
446
447impl AsRef<str> for ImString {
448 fn as_ref(&self) -> &str {
449 self.to_str()
450 }
451}
452
453impl From<String> for ImString {
454 fn from(s: String) -> ImString {
455 ImString::new(s)
456 }
457}
458
459impl From<&str> for ImString {
460 fn from(s: &str) -> ImString {
461 ImString::new(s)
462 }
463}
464
465impl Index<RangeFull> for ImString {
466 type Output = str;
467 fn index(&self, _index: RangeFull) -> &str {
468 self.to_str()
469 }
470}
471
472pub type ImStr<'a> = Cow<'a, str>;
474
475#[macro_export]
477macro_rules! im_str {
478 ($e:expr) => {{ $crate::ImString::new($e) }};
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use std::ffi::CStr;
485
486 #[test]
487 fn im_string_ensure_buf_size_resizes_and_nul_terminates() {
488 let mut s = ImString::new("abc");
489 s.ensure_buf_size(16);
490 assert_eq!(s.0.len(), 16);
491 assert_eq!(&s.0[..3], b"abc");
492 assert_eq!(s.0[3], 0);
493 assert!(s.0[4..].iter().all(|&b| b == 0));
494 }
495
496 #[test]
497 fn im_string_refresh_len_does_not_scan_spare_capacity() {
498 let mut v = vec![b'x'; 16];
499 v[..4].copy_from_slice(b"abcd");
500 v[10] = 0;
501 v.truncate(4);
502
503 let mut s = ImString(v);
504 unsafe { s.refresh_len() };
505 assert_eq!(s.to_str(), "abcd");
506 assert_eq!(s.0.last().copied(), Some(0));
507 }
508
509 #[test]
510 fn ui_buffer_push_appends_nul() {
511 let mut buf = UiBuffer::new(1024);
512 let start = buf.push("abc");
513 assert_eq!(start, 0);
514 assert_eq!(&buf.buffer, b"abc\0");
515 }
516
517 #[test]
518 fn ui_buffer_sanitizes_interior_nul() {
519 let mut buf = UiBuffer::new(1024);
520 let ptr = buf.scratch_txt("a\0b");
521 let s = unsafe { CStr::from_ptr(ptr) }.to_str().unwrap();
522 assert_eq!(s, "a?b");
523 }
524
525 #[test]
526 fn tls_scratch_txt_is_nul_terminated() {
527 let ptr = tls_scratch_txt("hello");
528 let s = unsafe { CStr::from_ptr(ptr) }.to_str().unwrap();
529 assert_eq!(s, "hello");
530 }
531
532 #[test]
533 fn tls_scratch_txt_two_returns_two_valid_strings() {
534 let (a_ptr, b_ptr) = tls_scratch_txt_two("a", "bcd");
535 let a = unsafe { CStr::from_ptr(a_ptr) }.to_str().unwrap();
536 let b = unsafe { CStr::from_ptr(b_ptr) }.to_str().unwrap();
537 assert_eq!(a, "a");
538 assert_eq!(b, "bcd");
539 }
540
541 #[test]
542 fn with_scratch_txt_slice_returns_sequential_pointers() {
543 with_scratch_txt_slice(&["a", "bc", "def"], |ptrs| {
544 assert_eq!(ptrs.len(), 3);
545
546 let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
547 let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
548 let c = unsafe { CStr::from_ptr(ptrs[2]) }.to_str().unwrap();
549 assert_eq!(a, "a");
550 assert_eq!(b, "bc");
551 assert_eq!(c, "def");
552
553 let ab = (ptrs[1] as usize) - (ptrs[0] as usize);
554 let bc = (ptrs[2] as usize) - (ptrs[1] as usize);
555 assert_eq!(ab, "a".len() + 1);
556 assert_eq!(bc, "bc".len() + 1);
557 });
558 }
559
560 #[test]
561 fn with_scratch_txt_slice_with_opt_returns_null_for_none() {
562 with_scratch_txt_slice_with_opt(&["a", "bc"], None, |ptrs, opt_ptr| {
563 assert_eq!(ptrs.len(), 2);
564 assert!(opt_ptr.is_null());
565
566 let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
567 let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
568 assert_eq!(a, "a");
569 assert_eq!(b, "bc");
570 });
571 }
572
573 #[test]
574 fn with_scratch_txt_slice_with_opt_appends_opt_string() {
575 with_scratch_txt_slice_with_opt(&["a", "bc"], Some("fmt"), |ptrs, opt_ptr| {
576 assert_eq!(ptrs.len(), 2);
577 assert!(!opt_ptr.is_null());
578
579 let a = unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap();
580 let b = unsafe { CStr::from_ptr(ptrs[1]) }.to_str().unwrap();
581 let fmt = unsafe { CStr::from_ptr(opt_ptr) }.to_str().unwrap();
582 assert_eq!(a, "a");
583 assert_eq!(b, "bc");
584 assert_eq!(fmt, "fmt");
585
586 let ab = (ptrs[1] as usize) - (ptrs[0] as usize);
587 let bf = (opt_ptr as usize) - (ptrs[1] as usize);
588 assert_eq!(ab, "a".len() + 1);
589 assert_eq!(bf, "bc".len() + 1);
590 });
591 }
592
593 #[test]
594 #[should_panic(expected = "null byte")]
595 fn imstring_new_rejects_interior_nul() {
596 let _ = ImString::new("a\0b");
597 }
598
599 #[test]
600 #[should_panic(expected = "null byte")]
601 fn imstring_push_str_rejects_interior_nul() {
602 let mut s = ImString::new("a");
603 s.push_str("b\0c");
604 }
605}