diff --git a/pyrefly/lib/state/lsp.rs b/pyrefly/lib/state/lsp.rs index 36c608b46d..2a1ff5dc78 100644 --- a/pyrefly/lib/state/lsp.rs +++ b/pyrefly/lib/state/lsp.rs @@ -4344,13 +4344,20 @@ impl<'a> Transaction<'a> { let source = module.lined_buffer().contents(); let mut offset = TextSize::from(0); - for line in source.lines() { + for line_with_ending in source.split_inclusive('\n') { + let line_without_lf = line_with_ending + .strip_suffix('\n') + .unwrap_or(line_with_ending); + let line = line_without_lf + .strip_suffix('\r') + .unwrap_or(line_without_lf); if let Some(comment_pos) = pyrefly_python::ignore::find_comment_start_in_line(line) { let comment_start = offset + TextSize::from(comment_pos as u32); let comment_end = offset + TextSize::from(line.len() as u32); ranges.push(TextRange::new(comment_start, comment_end)); } - offset += TextSize::from((line.len() + 1) as u32); + offset += TextSize::try_from(line_with_ending.len()) + .expect("source line length must fit in TextSize"); } ranges diff --git a/pyrefly/lib/test/lsp/completion.rs b/pyrefly/lib/test/lsp/completion.rs index 86ecf45885..ea9d86e484 100644 --- a/pyrefly/lib/test/lsp/completion.rs +++ b/pyrefly/lib/test/lsp/completion.rs @@ -3765,6 +3765,32 @@ x = sys.version ); } +#[test] +fn completion_before_comment_with_crlf_line_endings() { + let code = concat!( + "class Foo:\r\n", + " x: int\r\n", + "foo = Foo()\r\n", + "foo.\r\n", + "# comment\r\n", + ); + let (handles, state) = mk_multi_file_state(&[("main", code)], Require::Exports, false); + let handle = handles.get("main").unwrap(); + let position = TextSize::try_from( + code.find("foo.\r\n").expect("completion line must exist") + "foo.".len(), + ) + .expect("completion position must fit in TextSize"); + let completions = + state + .transaction() + .completion(handle, position, ImportFormat::Absolute, true, None); + + assert!( + completions.iter().any(|item| item.label == "x"), + "Expected attribute completions before a comment, got {completions:?}" + ); +} + #[test] fn completion_sorts_incompatible_call_argument_last() { let code = r#"