Fix ESLint linebreak-style errors by preserving line endings in LSP communication (#38773)

Closes https://github.com/zed-industries/zed/issues/38453

Current `Buffer` API only allows getting buffer text with `\n` line
breaks — even if the `\r\n` was used in the original file's text.

This it not correct in certain cases like LSP formatting, where language
servers need to have original document context for e.g. formatting
purposes.

Added new `Buffer` API, replaced all buffer LSP registration places with
the new one and added more tests.

Release Notes: 

- Fixed ESLint linebreak-style errors by preserving line endings in LSP
communication

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
Devdatta Talele
2025-10-23 19:11:48 +00:00
committed by GitHub
co-authored by Claude Kirill Bulatov
parent 55bc679c19
commit 435eab6896
7 changed files with 273 additions and 91 deletions
+10 -8
View File
@@ -2487,7 +2487,7 @@ impl LocalLspStore {
uri.clone(),
adapter.language_id(&language.name()),
0,
initial_snapshot.text(),
initial_snapshot.text_with_original_line_endings(),
);
vec![snapshot]
@@ -7522,6 +7522,7 @@ impl LspStore {
let previous_snapshot = buffer_snapshots.last()?;
let build_incremental_change = || {
let line_ending = next_snapshot.line_ending();
buffer
.edits_since::<Dimensions<PointUtf16, usize>>(
previous_snapshot.snapshot.version(),
@@ -7529,16 +7530,18 @@ impl LspStore {
.map(|edit| {
let edit_start = edit.new.start.0;
let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
let new_text = next_snapshot
.text_for_range(edit.new.start.1..edit.new.end.1)
.collect();
lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(
point_to_lsp(edit_start),
point_to_lsp(edit_end),
)),
range_length: None,
text: new_text,
// Collect changed text and preserve line endings.
// text_for_range returns chunks with normalized \n, so we need to
// convert to the buffer's actual line ending for LSP.
text: line_ending.into_string(
next_snapshot.text_for_range(edit.new.start.1..edit.new.end.1),
),
}
})
.collect()
@@ -7558,7 +7561,7 @@ impl LspStore {
vec![lsp::TextDocumentContentChangeEvent {
range: None,
range_length: None,
text: next_snapshot.text(),
text: next_snapshot.text_with_original_line_endings(),
}]
}
Some(lsp::TextDocumentSyncKind::INCREMENTAL) => build_incremental_change(),
@@ -10922,13 +10925,12 @@ impl LspStore {
let snapshot = versions.last().unwrap();
let version = snapshot.version;
let initial_snapshot = &snapshot.snapshot;
let uri = lsp::Uri::from_file_path(file.abs_path(cx)).unwrap();
language_server.register_buffer(
uri,
adapter.language_id(&language.name()),
version,
initial_snapshot.text(),
buffer_handle.read(cx).text_with_original_line_endings(),
);
buffer_paths_registered.push((buffer_id, file.abs_path(cx)));
local