Skip to content

Commit cd6bf14

Browse files
committed
pass the test
1 parent c18de0a commit cd6bf14

File tree

4 files changed

+116
-35
lines changed

4 files changed

+116
-35
lines changed

crates/ruff_annotate_snippets/src/renderer/display_list.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,22 @@ impl DisplaySet<'_> {
287287
let lineno_color = stylesheet.line_no();
288288
buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color);
289289
buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none);
290-
if let Some((col, row)) = pos {
291-
buffer.append(line_offset, ":", stylesheet.none);
292-
buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
293-
buffer.append(line_offset, ":", stylesheet.none);
294-
buffer.append(line_offset, row.to_string().as_str(), stylesheet.none);
290+
match pos {
291+
Some(Position::RowCol(row, col)) => {
292+
buffer.append(line_offset, ":", stylesheet.none);
293+
buffer.append(line_offset, row.to_string().as_str(), stylesheet.none);
294+
buffer.append(line_offset, ":", stylesheet.none);
295+
buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
296+
}
297+
Some(Position::Cell(cell, row, col)) => {
298+
buffer.append(line_offset, ":", stylesheet.none);
299+
buffer.append(line_offset, &format!("cell {cell}"), stylesheet.none);
300+
buffer.append(line_offset, ":", stylesheet.none);
301+
buffer.append(line_offset, row.to_string().as_str(), stylesheet.none);
302+
buffer.append(line_offset, ":", stylesheet.none);
303+
buffer.append(line_offset, col.to_string().as_str(), stylesheet.none);
304+
}
305+
None => {}
295306
}
296307
Ok(())
297308
}
@@ -871,6 +882,12 @@ impl DisplaySourceAnnotation<'_> {
871882
}
872883
}
873884

885+
#[derive(Debug, PartialEq)]
886+
pub(crate) enum Position {
887+
RowCol(usize, usize),
888+
Cell(usize, usize, usize),
889+
}
890+
874891
/// Raw line - a line which does not have the `lineno` part and is not considered
875892
/// a part of the snippet.
876893
#[derive(Debug, PartialEq)]
@@ -879,7 +896,7 @@ pub(crate) enum DisplayRawLine<'a> {
879896
/// slice in the project structure.
880897
Origin {
881898
path: &'a str,
882-
pos: Option<(usize, usize)>,
899+
pos: Option<Position>,
883900
header_type: DisplayHeaderType,
884901
},
885902

@@ -1171,13 +1188,15 @@ fn format_snippet<'m>(
11711188
"Non-empty file-level snippet that won't be rendered: {:?}",
11721189
snippet.source
11731190
);
1174-
let header = format_header(origin, main_range, &[], is_first);
1191+
let header = format_header(origin, main_range, &[], is_first, snippet.cell_index);
11751192
return DisplaySet {
11761193
display_lines: header.map_or_else(Vec::new, |header| vec![header]),
11771194
margin: Margin::new(0, 0, 0, 0, term_width, 0),
11781195
};
11791196
}
11801197

1198+
let cell_index = snippet.cell_index;
1199+
11811200
let mut body = format_body(
11821201
snippet,
11831202
need_empty_header,
@@ -1186,7 +1205,13 @@ fn format_snippet<'m>(
11861205
anonymized_line_numbers,
11871206
cut_indicator,
11881207
);
1189-
let header = format_header(origin, main_range, &body.display_lines, is_first);
1208+
let header = format_header(
1209+
origin,
1210+
main_range,
1211+
&body.display_lines,
1212+
is_first,
1213+
cell_index,
1214+
);
11901215

11911216
if let Some(header) = header {
11921217
body.display_lines.insert(0, header);
@@ -1206,6 +1231,7 @@ fn format_header<'a>(
12061231
main_range: Option<usize>,
12071232
body: &[DisplayLine<'_>],
12081233
is_first: bool,
1234+
cell_index: Option<usize>,
12091235
) -> Option<DisplayLine<'a>> {
12101236
let display_header = if is_first {
12111237
DisplayHeaderType::Initial
@@ -1240,9 +1266,15 @@ fn format_header<'a>(
12401266
}
12411267
}
12421268

1269+
let pos = if let Some(cell) = cell_index {
1270+
Position::Cell(cell, line_offset, col)
1271+
} else {
1272+
Position::RowCol(line_offset, col)
1273+
};
1274+
12431275
return Some(DisplayLine::Raw(DisplayRawLine::Origin {
12441276
path,
1245-
pos: Some((line_offset, col)),
1277+
pos: Some(pos),
12461278
header_type: display_header,
12471279
}));
12481280
}

crates/ruff_annotate_snippets/src/snippet.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ pub struct Snippet<'a> {
6565
pub(crate) annotations: Vec<Annotation<'a>>,
6666

6767
pub(crate) fold: bool,
68+
69+
/// The optional cell index in a Jupyter notebook, used for reporting source locations along
70+
/// with the ranges on `annotations`.
71+
pub(crate) cell_index: Option<usize>,
6872
}
6973

7074
impl<'a> Snippet<'a> {
@@ -75,6 +79,7 @@ impl<'a> Snippet<'a> {
7579
source,
7680
annotations: vec![],
7781
fold: false,
82+
cell_index: None,
7883
}
7984
}
8085

@@ -103,6 +108,12 @@ impl<'a> Snippet<'a> {
103108
self.fold = fold;
104109
self
105110
}
111+
112+
/// Attach a Jupyter notebook cell index.
113+
pub fn cell_index(mut self, index: Option<usize>) -> Self {
114+
self.cell_index = index;
115+
self
116+
}
106117
}
107118

108119
/// An annotation for a [`Snippet`].

crates/ruff_db/src/diagnostic/render.rs

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ impl<'a> ResolvedDiagnostic<'a> {
243243
.filter_map(|ann| {
244244
let path = ann.span.file.path(resolver);
245245
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
246-
ResolvedAnnotation::new(path, &diagnostic_source, ann)
246+
ResolvedAnnotation::new(path, &diagnostic_source, ann, resolver)
247247
})
248248
.collect();
249249

@@ -299,7 +299,7 @@ impl<'a> ResolvedDiagnostic<'a> {
299299
.filter_map(|ann| {
300300
let path = ann.span.file.path(resolver);
301301
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
302-
ResolvedAnnotation::new(path, &diagnostic_source, ann)
302+
ResolvedAnnotation::new(path, &diagnostic_source, ann, resolver)
303303
})
304304
.collect();
305305
ResolvedDiagnostic {
@@ -394,6 +394,7 @@ struct ResolvedAnnotation<'a> {
394394
message: Option<&'a str>,
395395
is_primary: bool,
396396
is_file_level: bool,
397+
notebook_index: Option<NotebookIndex>,
397398
}
398399

399400
impl<'a> ResolvedAnnotation<'a> {
@@ -406,6 +407,7 @@ impl<'a> ResolvedAnnotation<'a> {
406407
path: &'a str,
407408
diagnostic_source: &DiagnosticSource,
408409
ann: &'a Annotation,
410+
resolver: &'a dyn FileResolver,
409411
) -> Option<ResolvedAnnotation<'a>> {
410412
let source = diagnostic_source.as_source_code();
411413
let (range, line_start, line_end) = match (ann.span.range(), ann.message.is_some()) {
@@ -431,6 +433,7 @@ impl<'a> ResolvedAnnotation<'a> {
431433
(range, line_start, line_end)
432434
}
433435
};
436+
434437
Some(ResolvedAnnotation {
435438
path,
436439
diagnostic_source: diagnostic_source.clone(),
@@ -440,6 +443,7 @@ impl<'a> ResolvedAnnotation<'a> {
440443
message: ann.get_message(),
441444
is_primary: ann.is_primary,
442445
is_file_level: ann.is_file_level,
446+
notebook_index: resolver.notebook_index(&ann.span.file),
443447
})
444448
}
445449
}
@@ -568,6 +572,11 @@ struct RenderableSnippet<'r> {
568572
/// Whether this snippet contains at least one primary
569573
/// annotation.
570574
has_primary: bool,
575+
/// The cell index in a Jupyter notebook, if this snippet refers to a notebook.
576+
///
577+
/// This is used for rendering annotations with offsets like `cell 1:2:3` instead of simple row
578+
/// and column numbers.
579+
cell_index: Option<usize>,
571580
}
572581

573582
impl<'r> RenderableSnippet<'r> {
@@ -595,19 +604,43 @@ impl<'r> RenderableSnippet<'r> {
595604
"creating a renderable snippet requires a non-zero number of annotations",
596605
);
597606
let diagnostic_source = &anns[0].diagnostic_source;
607+
let notebook_index = anns[0].notebook_index.as_ref();
598608
let source = diagnostic_source.as_source_code();
599609
let has_primary = anns.iter().any(|ann| ann.is_primary);
600610

601-
let line_start = context_before(
602-
&source,
603-
context,
604-
anns.iter().map(|ann| ann.line_start).min().unwrap(),
605-
);
606-
let line_end = context_after(
607-
&source,
608-
context,
609-
anns.iter().map(|ann| ann.line_end).max().unwrap(),
610-
);
611+
let content_start_index = anns.iter().map(|ann| ann.line_start).min().unwrap();
612+
let mut line_start = context_before(&source, context, content_start_index);
613+
614+
let start = source.line_column(anns[0].range.start());
615+
let cell_index = notebook_index
616+
.map(|notebook_index| notebook_index.cell(start.line).unwrap_or_default().get());
617+
618+
// If we're working with a Jupyter Notebook, skip the lines which are
619+
// outside of the cell containing the diagnostic.
620+
if let Some(index) = notebook_index {
621+
let content_start_cell = index.cell(content_start_index).unwrap_or(OneIndexed::MIN);
622+
while line_start < content_start_index {
623+
if index.cell(line_start).unwrap_or(OneIndexed::MIN) == content_start_cell {
624+
break;
625+
}
626+
line_start = line_start.saturating_add(1);
627+
}
628+
}
629+
630+
let content_end_index = anns.iter().map(|ann| ann.line_end).max().unwrap();
631+
let mut line_end = context_after(&source, context, content_end_index);
632+
633+
// If we're working with a Jupyter Notebook, skip the lines which are
634+
// outside of the cell containing the diagnostic.
635+
if let Some(index) = notebook_index {
636+
let content_end_cell = index.cell(content_end_index).unwrap_or(OneIndexed::MIN);
637+
while line_end > content_end_index {
638+
if index.cell(line_end).unwrap_or(OneIndexed::MIN) == content_end_cell {
639+
break;
640+
}
641+
line_end = line_end.saturating_sub(1);
642+
}
643+
}
611644

612645
let snippet_start = source.line_start(line_start);
613646
let snippet_end = source.line_end(line_end);
@@ -625,11 +658,18 @@ impl<'r> RenderableSnippet<'r> {
625658
annotations,
626659
} = replace_unprintable(snippet, annotations).fix_up_empty_spans_after_line_terminator();
627660

661+
let line_start = notebook_index.map_or(line_start, |notebook_index| {
662+
notebook_index
663+
.cell_row(line_start)
664+
.unwrap_or(OneIndexed::MIN)
665+
});
666+
628667
RenderableSnippet {
629668
snippet,
630669
line_start,
631670
annotations,
632671
has_primary,
672+
cell_index,
633673
}
634674
}
635675

@@ -643,6 +683,7 @@ impl<'r> RenderableSnippet<'r> {
643683
.iter()
644684
.map(RenderableAnnotation::to_annotate),
645685
)
686+
.cell_index(self.cell_index)
646687
}
647688
}
648689

crates/ruff_db/src/diagnostic/render/full.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -300,31 +300,28 @@ print()
300300
1 | # cell 1
301301
2 | import os
302302
| ^^
303-
3 | # cell 2
304-
4 | import math
305303
|
306304
help: Remove unused import: `os`
307305
308306
error[unused-import]: `math` imported but unused
309307
--> notebook.ipynb:cell 2:2:8
310308
|
311-
2 | import os
312-
3 | # cell 2
313-
4 | import math
309+
1 | # cell 2
310+
2 | import math
314311
| ^^^^
315-
5 |
316-
6 | print('hello world')
312+
3 |
313+
4 | print('hello world')
317314
|
318315
help: Remove unused import: `math`
319316
320317
error[unused-variable]: Local variable `x` is assigned to but never used
321-
--> notebook.ipynb:cell 3:4:5
322-
|
323-
8 | def foo():
324-
9 | print()
325-
10 | x = 1
326-
| ^
327-
|
318+
--> notebook.ipynb:cell 3:4:5
319+
|
320+
2 | def foo():
321+
3 | print()
322+
4 | x = 1
323+
| ^
324+
|
328325
help: Remove assignment to unused variable `x`
329326
");
330327
}

0 commit comments

Comments
 (0)