@@ -106,12 +106,12 @@ pub fn find_hovered_hint_part(
hovered_offset: InlayOffset,
) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
if hovered_offset >= hint_start {
- let mut hovered_character = hovered_offset - hint_start;
+ let mut offset_in_hint = hovered_offset - hint_start;
let mut part_start = hint_start;
for part in label_parts {
- let part_len = part.value.chars().count();
- if hovered_character > part_len {
- hovered_character -= part_len;
+ let part_len = part.value.len();
+ if offset_in_hint >= part_len {
+ offset_in_hint -= part_len;
part_start.0 += part_len;
} else {
let part_end = InlayOffset(part_start.0 + part_len);
@@ -1907,4 +1907,98 @@ mod tests {
);
});
}
+
+ #[test]
+ fn test_find_hovered_hint_part_with_multibyte_characters() {
+ use crate::display_map::InlayOffset;
+ use multi_buffer::MultiBufferOffset;
+ use project::InlayHintLabelPart;
+
+ // Test with multi-byte UTF-8 character "→" (3 bytes, 1 character)
+ let label = "→ app/Livewire/UserProfile.php";
+ let label_parts = vec![InlayHintLabelPart {
+ value: label.to_string(),
+ tooltip: None,
+ location: None,
+ }];
+
+ let hint_start = InlayOffset(MultiBufferOffset(100));
+
+ // Verify the label has more bytes than characters (due to "→")
+ assert_eq!(label.len(), 32); // bytes
+ assert_eq!(label.chars().count(), 30); // characters
+
+ // Test hovering at the last byte (should find the part)
+ let last_byte_offset = InlayOffset(MultiBufferOffset(100 + label.len() - 1));
+ let result = find_hovered_hint_part(label_parts.clone(), hint_start, last_byte_offset);
+ assert!(
+ result.is_some(),
+ "Should find part when hovering at last byte"
+ );
+ let (part, range) = result.unwrap();
+ assert_eq!(part.value, label);
+ assert_eq!(range.start, hint_start);
+ assert_eq!(range.end, InlayOffset(MultiBufferOffset(100 + label.len())));
+
+ // Test hovering at the first byte of "→" (byte 0)
+ let first_byte_offset = InlayOffset(MultiBufferOffset(100));
+ let result = find_hovered_hint_part(label_parts.clone(), hint_start, first_byte_offset);
+ assert!(
+ result.is_some(),
+ "Should find part when hovering at first byte"
+ );
+
+ // Test hovering in the middle of "→" (byte 1, still part of the arrow character)
+ let mid_arrow_offset = InlayOffset(MultiBufferOffset(101));
+ let result = find_hovered_hint_part(label_parts, hint_start, mid_arrow_offset);
+ assert!(
+ result.is_some(),
+ "Should find part when hovering in middle of multi-byte char"
+ );
+
+ // Test with multiple parts containing multi-byte characters
+ // Part ranges are [start, end) - start inclusive, end exclusive
+ // "→ " occupies bytes [0, 4), "path" occupies bytes [4, 8)
+ let parts = vec![
+ InlayHintLabelPart {
+ value: "→ ".to_string(), // 4 bytes (3 + 1)
+ tooltip: None,
+ location: None,
+ },
+ InlayHintLabelPart {
+ value: "path".to_string(), // 4 bytes
+ tooltip: None,
+ location: None,
+ },
+ ];
+
+ // Hover at byte 3 (last byte of "→ ", the space character)
+ let arrow_last_byte = InlayOffset(MultiBufferOffset(100 + 3));
+ let result = find_hovered_hint_part(parts.clone(), hint_start, arrow_last_byte);
+ assert!(result.is_some(), "Should find first part at its last byte");
+ let (part, range) = result.unwrap();
+ assert_eq!(part.value, "→ ");
+ assert_eq!(
+ range,
+ InlayOffset(MultiBufferOffset(100))..InlayOffset(MultiBufferOffset(104))
+ );
+
+ // Hover at byte 4 (first byte of "path", at the boundary)
+ let path_start_offset = InlayOffset(MultiBufferOffset(100 + 4));
+ let result = find_hovered_hint_part(parts.clone(), hint_start, path_start_offset);
+ assert!(result.is_some(), "Should find second part at boundary");
+ let (part, _) = result.unwrap();
+ assert_eq!(part.value, "path");
+
+ // Hover at byte 7 (last byte of "path")
+ let path_end_offset = InlayOffset(MultiBufferOffset(100 + 7));
+ let result = find_hovered_hint_part(parts, hint_start, path_end_offset);
+ assert!(result.is_some(), "Should find second part at last byte");
+ let (part, range) = result.unwrap();
+ assert_eq!(part.value, "path");
+ assert_eq!(
+ range,
+ InlayOffset(MultiBufferOffset(104))..InlayOffset(MultiBufferOffset(108))
+ );
+ }
}