╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │363 }; │ │364 // the cut lands wherever the limit falls, so a truncated message keeps │ │365 // only its whole lines: a line break can't fall inside a codepoint, and │ │366 - // the widgets need valid utf-8. a first line past the limit keeps none. │ │367 - const text = if (truncated) message.items[0 .. std.mem.lastIndexOfScalar(u8, message.items, '\n') orelse 0] else message.items; │ │366 + // the widgets need valid utf-8. a first line past the limit is instead │ │367 + // cut at the last codepoint boundary. │ │368 + const text = if (truncated) │ │369 + (if (std.mem.lastIndexOfScalar(u8, message.items, '\n')) |nl| message.items[0..nl] else trimIncompleteCodepoint(message.items))│ │370 + else │ │371 + message.items; │ │372 return .{ std.mem.trim(u8, text, " \t\r\n"), truncated }; │ │373 } │ │374 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │375 +/// drop any incomplete UTF data from the end of a byte array │ │376 +fn trimIncompleteCodepoint(bytes: []const u8) []const u8 { │ │377 + var i = bytes.len; │ │378 + while (i > 0 and bytes.len - i < 4) { │ │379 + i -= 1; │ │380 + const byte = bytes[i]; │ │381 + if (byte & 0b1100_0000 == 0b1000_0000) continue; // continuation byte │ │382 + const seq_len = std.unicode.utf8ByteSequenceLength(byte) catch return bytes; │ │383 + return if (i + seq_len > bytes.len) bytes[0..i] else bytes; │ │384 + } │ │385 + return bytes; │ │386 +} │ │387 + │ │388 // a focusable, word-wrapping box holding a commit message. `bottom_label` │ │389 // marks a message the page only shows part of ("" when it's whole). │ │390 fn messageBox(allocator: std.mem.Allocator, message: []const u8, bottom_label: []const u8) !wgt.TextBox(ui.Widget) {│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯