@@ -287,7 +287,7 @@ impl Buffer {
288 S: AsRef<str>,
288 S: AsRef<str>,
290− self.set_string_truncated(x, y, string, width, style, false, false)
290+ self.set_string_truncated_at_end(x, y, string.as_ref(), width, style)
293 /// Print at most the first `width` characters of a string if enough space is available
293 /// Print at most the first `width` characters of a string if enough space is available
@@ -295,19 +295,16 @@ impl Buffer {
295 /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string
295 /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string
296 /// instead of the end.
296 /// instead of the end.
297 #[allow(clippy::too_many_arguments)]
297 #[allow(clippy::too_many_arguments)]
298− pub fn set_string_truncated<S>(
298+ pub fn set_string_truncated(
299 &mut self,
299 &mut self,
302− string: S,
302+ string: &str,
303 width: usize,
303 width: usize,
304− style: Style,
304+ style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style
305 ellipsis: bool,
305 ellipsis: bool,
306 truncate_start: bool,
306 truncate_start: bool,
307− ) -> (u16, u16)
307+ ) -> (u16, u16) {
311 // prevent panic if out of range
308 // prevent panic if out of range
312 if !self.in_bounds(x, y) || width == 0 {
309 if !self.in_bounds(x, y) || width == 0 {
313 return (x, y);
310 return (x, y);
@@ -316,10 +313,10 @@ impl Buffer {
316 let mut index = self.index_of(x, y);
313 let mut index = self.index_of(x, y);
317 let mut x_offset = x as usize;
314 let mut x_offset = x as usize;
318 let width = if ellipsis { width - 1 } else { width };
315 let width = if ellipsis { width - 1 } else { width };
319− let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
316+ let graphemes = string.grapheme_indices(true);
320 let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
317 let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
321 if !truncate_start {
318 if !truncate_start {
322− for s in graphemes {
319+ for (byte_offset, s) in graphemes {
323 let width = s.width();
320 let width = s.width();
324 if width == 0 {
321 if width == 0 {
325 continue;
322 continue;
@@ -331,7 +328,7 @@ impl Buffer {
333 self.content[index].set_symbol(s);
330 self.content[index].set_symbol(s);
334− self.content[index].set_style(style);
331+ self.content[index].set_style(style(byte_offset));
335 // Reset following cells if multi-width (they would be hidden by the grapheme),
332 // Reset following cells if multi-width (they would be hidden by the grapheme),
336 for i in index + 1..index + width {
333 for i in index + 1..index + width {
337 self.content[i].reset();
334 self.content[i].reset();
@@ -339,14 +336,14 @@ impl Buffer {
339 index += width;
336 index += width;
340 x_offset += width;
337 x_offset += width;
342− if ellipsis && x_offset - (x as usize) < string.as_ref().width() {
339+ if ellipsis && x_offset - (x as usize) < string.width() {
343 self.content[index].set_symbol("…");
340 self.content[index].set_symbol("…");
346 let mut start_index = self.index_of(x, y);
343 let mut start_index = self.index_of(x, y);
347 let mut index = self.index_of(max_offset as u16, y);
344 let mut index = self.index_of(max_offset as u16, y);
349− let total_width = string.as_ref().width();
346+ let total_width = string.width();
350 let truncated = total_width > width;
347 let truncated = total_width > width;
351 if ellipsis && truncated {
348 if ellipsis && truncated {
352 self.content[start_index].set_symbol("…");
349 self.content[start_index].set_symbol("…");
@@ -355,7 +352,7 @@ impl Buffer {
355 if !truncated {
352 if !truncated {
356 index -= width - total_width;
353 index -= width - total_width;
358− for s in graphemes.rev() {
355+ for (byte_offset, s) in graphemes.rev() {
359 let width = s.width();
356 let width = s.width();
360 if width == 0 {
357 if width == 0 {
361 continue;
358 continue;
@@ -365,7 +362,7 @@ impl Buffer {
367 self.content[start].set_symbol(s);
364 self.content[start].set_symbol(s);
368− self.content[start].set_style(style);
365+ self.content[start].set_style(style(byte_offset));
369 for i in start + 1..index {
366 for i in start + 1..index {
370 self.content[i].reset();
367 self.content[i].reset();
@@ -375,6 +372,49 @@ impl Buffer {
375 (x_offset as u16, y)
372 (x_offset as u16, y)
375+ /// Print at most the first `width` characters of a string if enough space is available
376+ /// until the end of the line.
377+ pub fn set_string_truncated_at_end(
385+ // prevent panic if out of range
386+ if !self.in_bounds(x, y) {
390+ let mut index = self.index_of(x, y);
391+ let mut x_offset = x as usize;
392+ let max_x_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
394+ for s in string.graphemes(true) {
395+ let width = s.width();
399+ // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we
400+ // change dimensions to usize or u32 and someone resizes the terminal to 1x2^32.
401+ if width > max_x_offset.saturating_sub(x_offset) {
405+ self.content[index].set_symbol(s);
406+ self.content[index].set_style(style);
407+ // Reset following cells if multi-width (they would be hidden by the grapheme),
408+ for i in index + 1..index + width {
409+ self.content[i].reset();
415+ (x_offset as u16, y)
378 pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
418 pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
379 let mut remaining_width = width;
419 let mut remaining_width = width;
380 let mut x = x;
420 let mut x = x;