@@ -287,7 +287,7 @@ impl Buffer {
287287 where
288288 S: AsRef<str>,
289289 {
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)
291291 }
292292
293293 /// Print at most the first `width` characters of a string if enough space is available
@@ -295,19 +295,16 @@ impl Buffer {
295295 /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string
296296 /// instead of the end.
297297 #[allow(clippy::too_many_arguments)]
298− pub fn set_string_truncated<S>(
298+ pub fn set_string_truncated(
299299 &mut self,
300300 x: u16,
301301 y: u16,
302− string: S,
302+ string: &str,
303303 width: usize,
304− style: Style,
304+ style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style
305305 ellipsis: bool,
306306 truncate_start: bool,
307− ) -> (u16, u16)
308− where
309− S: AsRef<str>,
310− {
307+ ) -> (u16, u16) {
311308 // prevent panic if out of range
312309 if !self.in_bounds(x, y) || width == 0 {
313310 return (x, y);
@@ -316,10 +313,10 @@ impl Buffer {
316313 let mut index = self.index_of(x, y);
317314 let mut x_offset = x as usize;
318315 let width = if ellipsis { width - 1 } else { width };
319− let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
316+ let graphemes = string.grapheme_indices(true);
320317 let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
321318 if !truncate_start {
322− for s in graphemes {
319+ for (byte_offset, s) in graphemes {
323320 let width = s.width();
324321 if width == 0 {
325322 continue;
@@ -331,7 +328,7 @@ impl Buffer {
331328 }
332329
333330 self.content[index].set_symbol(s);
334− self.content[index].set_style(style);
331+ self.content[index].set_style(style(byte_offset));
335332 // Reset following cells if multi-width (they would be hidden by the grapheme),
336333 for i in index + 1..index + width {
337334 self.content[i].reset();
@@ -339,14 +336,14 @@ impl Buffer {
339336 index += width;
340337 x_offset += width;
341338 }
342− if ellipsis && x_offset - (x as usize) < string.as_ref().width() {
339+ if ellipsis && x_offset - (x as usize) < string.width() {
343340 self.content[index].set_symbol("…");
344341 }
345342 } else {
346343 let mut start_index = self.index_of(x, y);
347344 let mut index = self.index_of(max_offset as u16, y);
348345
349− let total_width = string.as_ref().width();
346+ let total_width = string.width();
350347 let truncated = total_width > width;
351348 if ellipsis && truncated {
352349 self.content[start_index].set_symbol("…");
@@ -355,7 +352,7 @@ impl Buffer {
355352 if !truncated {
356353 index -= width - total_width;
357354 }
358− for s in graphemes.rev() {
355+ for (byte_offset, s) in graphemes.rev() {
359356 let width = s.width();
360357 if width == 0 {
361358 continue;
@@ -365,7 +362,7 @@ impl Buffer {
365362 break;
366363 }
367364 self.content[start].set_symbol(s);
368− self.content[start].set_style(style);
365+ self.content[start].set_style(style(byte_offset));
369366 for i in start + 1..index {
370367 self.content[i].reset();
371368 }
@@ -375,6 +372,49 @@ impl Buffer {
375372 (x_offset as u16, y)
376373 }
377374
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(
378+ &mut self,
379+ x: u16,
380+ y: u16,
381+ string: &str,
382+ width: usize,
383+ style: Style,
384+ ) -> (u16, u16) {
385+ // prevent panic if out of range
386+ if !self.in_bounds(x, y) {
387+ return (x, y);
388+ }
389+
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));
393+
394+ for s in string.graphemes(true) {
395+ let width = s.width();
396+ if width == 0 {
397+ continue;
398+ }
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) {
402+ break;
403+ }
404+
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();
410+ }
411+ index += width;
412+ x_offset += width;
413+ }
414+
415+ (x_offset as u16, y)
416+ }
417+
378418 pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) {
379419 let mut remaining_width = width;
380420 let mut x = x;