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:
co-authored by
Claude
Kirill Bulatov
parent
55bc679c19
commit
435eab6896
+19
-77
@@ -20,11 +20,9 @@ use operation_queue::OperationQueue;
|
||||
pub use patch::Patch;
|
||||
use postage::{oneshot, prelude::*};
|
||||
|
||||
use regex::Regex;
|
||||
pub use rope::*;
|
||||
pub use selection::*;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering, Reverse},
|
||||
fmt::Display,
|
||||
future::Future,
|
||||
@@ -32,7 +30,7 @@ use std::{
|
||||
num::NonZeroU64,
|
||||
ops::{self, Deref, Range, Sub},
|
||||
str,
|
||||
sync::{Arc, LazyLock},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
pub use subscription::*;
|
||||
@@ -43,9 +41,6 @@ use undo_map::UndoMap;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use util::RandomCharIter;
|
||||
|
||||
static LINE_SEPARATORS_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\r\n|\r").expect("Failed to create LINE_SEPARATORS_REGEX"));
|
||||
|
||||
pub type TransactionId = clock::Lamport;
|
||||
|
||||
pub struct Buffer {
|
||||
@@ -2019,10 +2014,24 @@ impl BufferSnapshot {
|
||||
start..position
|
||||
}
|
||||
|
||||
/// Returns the buffer's text as a String.
|
||||
///
|
||||
/// Note: This always uses `\n` as the line separator, regardless of the buffer's
|
||||
/// actual line ending setting. For LSP communication or other cases where you need
|
||||
/// to preserve the original line endings, use [`Self::text_with_original_line_endings`] instead.
|
||||
pub fn text(&self) -> String {
|
||||
self.visible_text.to_string()
|
||||
}
|
||||
|
||||
/// Returns the buffer's text with line same endings as in buffer's file.
|
||||
///
|
||||
/// Unlike [`Self::text`] which always uses `\n`, this method formats the text using
|
||||
/// the buffer's actual line ending setting (Unix `\n` or Windows `\r\n`).
|
||||
pub fn text_with_original_line_endings(&self) -> String {
|
||||
self.visible_text
|
||||
.to_string_with_line_ending(self.line_ending)
|
||||
}
|
||||
|
||||
pub fn line_ending(&self) -> LineEnding {
|
||||
self.line_ending
|
||||
}
|
||||
@@ -2126,6 +2135,10 @@ impl BufferSnapshot {
|
||||
self.visible_text.reversed_bytes_in_range(start..end)
|
||||
}
|
||||
|
||||
/// Returns the text in the given range.
|
||||
///
|
||||
/// Note: This always uses `\n` as the line separator, regardless of the buffer's
|
||||
/// actual line ending setting.
|
||||
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Chunks<'_> {
|
||||
let start = range.start.to_offset(self);
|
||||
let end = range.end.to_offset(self);
|
||||
@@ -3246,77 +3259,6 @@ impl FromAnchor for usize {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum LineEnding {
|
||||
Unix,
|
||||
Windows,
|
||||
}
|
||||
|
||||
impl Default for LineEnding {
|
||||
fn default() -> Self {
|
||||
#[cfg(unix)]
|
||||
return Self::Unix;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
return Self::Windows;
|
||||
}
|
||||
}
|
||||
|
||||
impl LineEnding {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
LineEnding::Unix => "\n",
|
||||
LineEnding::Windows => "\r\n",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
LineEnding::Unix => "LF",
|
||||
LineEnding::Windows => "CRLF",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect(text: &str) -> Self {
|
||||
let mut max_ix = cmp::min(text.len(), 1000);
|
||||
while !text.is_char_boundary(max_ix) {
|
||||
max_ix -= 1;
|
||||
}
|
||||
|
||||
if let Some(ix) = text[..max_ix].find(['\n']) {
|
||||
if ix > 0 && text.as_bytes()[ix - 1] == b'\r' {
|
||||
Self::Windows
|
||||
} else {
|
||||
Self::Unix
|
||||
}
|
||||
} else {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize(text: &mut String) {
|
||||
if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") {
|
||||
*text = replaced;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_arc(text: Arc<str>) -> Arc<str> {
|
||||
if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
|
||||
replaced.into()
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_cow(text: Cow<str>) -> Cow<str> {
|
||||
if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") {
|
||||
replaced.into()
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub mod debug {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user