Skip to content

Commit fc99036

Browse files
authored
Merge pull request #46 from magurotuna/check-display-width
Fix display of annotation for double width characters
2 parents 73d6000 + fbdab5c commit fc99036

File tree

2 files changed

+188
-21
lines changed

2 files changed

+188
-21
lines changed

Diff for: src/display_list/from_snippet.rs

+65-21
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,23 @@ fn format_body(
293293
let mut body = vec![];
294294
let mut current_line = slice.line_start;
295295
let mut current_index = 0;
296-
let mut line_index_ranges = vec![];
296+
let mut line_info = vec![];
297+
298+
struct LineInfo {
299+
line_start_index: usize,
300+
line_end_index: usize,
301+
// How many spaces each character in the line take up when displayed
302+
char_widths: Vec<usize>,
303+
}
297304

298305
for (line, end_line) in CursorLines::new(slice.source) {
299306
let line_length = line.chars().count();
300307
let line_range = (current_index, current_index + line_length);
308+
let char_widths = line
309+
.chars()
310+
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
311+
.chain(std::iter::once(1)) // treat the end of line as signle-width
312+
.collect::<Vec<_>>();
301313
body.push(DisplayLine::Source {
302314
lineno: Some(current_line),
303315
inline_marks: vec![],
@@ -306,16 +318,28 @@ fn format_body(
306318
range: line_range,
307319
},
308320
});
309-
line_index_ranges.push(line_range);
321+
line_info.push(LineInfo {
322+
line_start_index: line_range.0,
323+
line_end_index: line_range.1,
324+
char_widths,
325+
});
310326
current_line += 1;
311327
current_index += line_length + end_line as usize;
312328
}
313329

314330
let mut annotation_line_count = 0;
315331
let mut annotations = slice.annotations;
316-
for (idx, (line_start, line_end)) in line_index_ranges.into_iter().enumerate() {
332+
for (
333+
idx,
334+
LineInfo {
335+
line_start_index,
336+
line_end_index,
337+
char_widths,
338+
},
339+
) in line_info.into_iter().enumerate()
340+
{
317341
let margin_left = margin
318-
.map(|m| m.left(line_end - line_start))
342+
.map(|m| m.left(line_end_index - line_start_index))
319343
.unwrap_or_default();
320344
// It would be nice to use filter_drain here once it's stable.
321345
annotations = annotations
@@ -328,15 +352,22 @@ fn format_body(
328352
_ => DisplayAnnotationType::from(annotation.annotation_type),
329353
};
330354
match annotation.range {
331-
(start, _) if start > line_end => true,
355+
(start, _) if start > line_end_index => true,
332356
(start, end)
333-
if start >= line_start && end <= line_end
334-
|| start == line_end && end - start <= 1 =>
357+
if start >= line_start_index && end <= line_end_index
358+
|| start == line_end_index && end - start <= 1 =>
335359
{
336-
let range = (
337-
(start - line_start) - margin_left,
338-
(end - line_start) - margin_left,
339-
);
360+
let annotation_start_col = char_widths
361+
.iter()
362+
.take(start - line_start_index)
363+
.sum::<usize>()
364+
- margin_left;
365+
let annotation_end_col = char_widths
366+
.iter()
367+
.take(end - line_start_index)
368+
.sum::<usize>()
369+
- margin_left;
370+
let range = (annotation_start_col, annotation_end_col);
340371
body.insert(
341372
body_idx + 1,
342373
DisplayLine::Source {
@@ -359,8 +390,12 @@ fn format_body(
359390
annotation_line_count += 1;
360391
false
361392
}
362-
(start, end) if start >= line_start && start <= line_end && end > line_end => {
363-
if start - line_start == 0 {
393+
(start, end)
394+
if start >= line_start_index
395+
&& start <= line_end_index
396+
&& end > line_end_index =>
397+
{
398+
if start - line_start_index == 0 {
364399
if let DisplayLine::Source {
365400
ref mut inline_marks,
366401
..
@@ -374,7 +409,11 @@ fn format_body(
374409
});
375410
}
376411
} else {
377-
let range = (start - line_start, start - line_start + 1);
412+
let annotation_start_col = char_widths
413+
.iter()
414+
.take(start - line_start_index)
415+
.sum::<usize>();
416+
let range = (annotation_start_col, annotation_start_col + 1);
378417
body.insert(
379418
body_idx + 1,
380419
DisplayLine::Source {
@@ -398,7 +437,7 @@ fn format_body(
398437
}
399438
true
400439
}
401-
(start, end) if start < line_start && end > line_end => {
440+
(start, end) if start < line_start_index && end > line_end_index => {
402441
if let DisplayLine::Source {
403442
ref mut inline_marks,
404443
..
@@ -413,7 +452,11 @@ fn format_body(
413452
}
414453
true
415454
}
416-
(start, end) if start < line_start && end >= line_start && end <= line_end => {
455+
(start, end)
456+
if start < line_start_index
457+
&& end >= line_start_index
458+
&& end <= line_end_index =>
459+
{
417460
if let DisplayLine::Source {
418461
ref mut inline_marks,
419462
..
@@ -427,11 +470,12 @@ fn format_body(
427470
});
428471
}
429472

430-
let end_mark = (end - line_start).saturating_sub(1);
431-
let range = (
432-
end_mark - margin_left,
433-
(end_mark + 1) - margin_left,
434-
);
473+
let end_mark = char_widths
474+
.iter()
475+
.take(end - line_start_index)
476+
.sum::<usize>()
477+
.saturating_sub(1);
478+
let range = (end_mark - margin_left, (end_mark + 1) - margin_left);
435479
body.insert(
436480
body_idx + 1,
437481
DisplayLine::Source {

Diff for: tests/formatter.rs

+123
Original file line numberDiff line numberDiff line change
@@ -550,3 +550,126 @@ fn test_i_29() {
550550

551551
assert_eq!(DisplayList::from(snippets).to_string(), expected);
552552
}
553+
554+
#[test]
555+
fn test_point_to_double_width_characters() {
556+
let snippets = Snippet {
557+
slices: vec![snippet::Slice {
558+
source: "こんにちは、世界",
559+
line_start: 1,
560+
origin: Some("<current file>"),
561+
annotations: vec![snippet::SourceAnnotation {
562+
range: (6, 8),
563+
label: "world",
564+
annotation_type: snippet::AnnotationType::Error,
565+
}],
566+
fold: false,
567+
}],
568+
title: None,
569+
footer: vec![],
570+
opt: Default::default(),
571+
};
572+
573+
let expected = r#" --> <current file>:1:7
574+
|
575+
1 | こんにちは、世界
576+
| ^^^^ world
577+
|"#;
578+
579+
assert_eq!(DisplayList::from(snippets).to_string(), expected);
580+
}
581+
582+
#[test]
583+
fn test_point_to_double_width_characters_across_lines() {
584+
let snippets = Snippet {
585+
slices: vec![snippet::Slice {
586+
source: "おはよう\nございます",
587+
line_start: 1,
588+
origin: Some("<current file>"),
589+
annotations: vec![snippet::SourceAnnotation {
590+
range: (2, 8),
591+
label: "Good morning",
592+
annotation_type: snippet::AnnotationType::Error,
593+
}],
594+
fold: false,
595+
}],
596+
title: None,
597+
footer: vec![],
598+
opt: Default::default(),
599+
};
600+
601+
let expected = r#" --> <current file>:1:3
602+
|
603+
1 | おはよう
604+
| _____^
605+
2 | | ございます
606+
| |______^ Good morning
607+
|"#;
608+
609+
assert_eq!(DisplayList::from(snippets).to_string(), expected);
610+
}
611+
612+
#[test]
613+
fn test_point_to_double_width_characters_multiple() {
614+
let snippets = Snippet {
615+
slices: vec![snippet::Slice {
616+
source: "お寿司\n食べたい🍣",
617+
line_start: 1,
618+
origin: Some("<current file>"),
619+
annotations: vec![
620+
snippet::SourceAnnotation {
621+
range: (0, 3),
622+
label: "Sushi1",
623+
annotation_type: snippet::AnnotationType::Error,
624+
},
625+
snippet::SourceAnnotation {
626+
range: (6, 8),
627+
label: "Sushi2",
628+
annotation_type: snippet::AnnotationType::Note,
629+
},
630+
],
631+
fold: false,
632+
}],
633+
title: None,
634+
footer: vec![],
635+
opt: Default::default(),
636+
};
637+
638+
let expected = r#" --> <current file>:1:1
639+
|
640+
1 | お寿司
641+
| ^^^^^^ Sushi1
642+
2 | 食べたい🍣
643+
| ---- note: Sushi2
644+
|"#;
645+
646+
assert_eq!(DisplayList::from(snippets).to_string(), expected);
647+
}
648+
649+
#[test]
650+
fn test_point_to_double_width_characters_mixed() {
651+
let snippets = Snippet {
652+
slices: vec![snippet::Slice {
653+
source: "こんにちは、新しいWorld!",
654+
line_start: 1,
655+
origin: Some("<current file>"),
656+
annotations: vec![snippet::SourceAnnotation {
657+
range: (6, 14),
658+
label: "New world",
659+
annotation_type: snippet::AnnotationType::Error,
660+
}],
661+
fold: false,
662+
}],
663+
title: None,
664+
footer: vec![],
665+
opt: Default::default(),
666+
};
667+
668+
let expected = r#" --> <current file>:1:7
669+
|
670+
1 | こんにちは、新しいWorld!
671+
| ^^^^^^^^^^^ New world
672+
|"#;
673+
674+
assert_eq!(DisplayList::from(snippets).to_string(), expected);
675+
}

0 commit comments

Comments
 (0)