Strip carriage returns from all text in text::Buffer
* Moving the logic from Rope to text::Buffer makes it easier to keep the Rope in sync with the fragment tree. * Removing carriage return characters is lossier, but is much simpler than incrementally maintaining the invariant that there are no carriage returns followed by newlines. We may want to do something smarter in the future. Co-authored-by: Keith Simmons <keith@zed.dev>
This commit is contained in:
parent
116fa92e84
commit
7e9beaf4bb
9 changed files with 132 additions and 100 deletions
|
@ -53,7 +53,6 @@ pub struct Buffer {
|
|||
saved_version: clock::Global,
|
||||
saved_version_fingerprint: String,
|
||||
saved_mtime: SystemTime,
|
||||
line_ending: LineEnding,
|
||||
transaction_depth: usize,
|
||||
was_dirty_before_starting_transaction: Option<bool>,
|
||||
language: Option<Arc<Language>>,
|
||||
|
@ -98,12 +97,6 @@ pub enum IndentKind {
|
|||
Tab,
|
||||
}
|
||||
|
||||
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
|
||||
pub enum LineEnding {
|
||||
Unix,
|
||||
Windows,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SelectionSet {
|
||||
line_mode: bool,
|
||||
|
@ -319,12 +312,9 @@ impl Buffer {
|
|||
base_text: T,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let base_text = base_text.into();
|
||||
let line_ending = LineEnding::detect(&base_text);
|
||||
Self::build(
|
||||
TextBuffer::new(replica_id, cx.model_id() as u64, base_text),
|
||||
TextBuffer::new(replica_id, cx.model_id() as u64, base_text.into()),
|
||||
None,
|
||||
line_ending,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -334,12 +324,9 @@ impl Buffer {
|
|||
file: Arc<dyn File>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let base_text = base_text.into();
|
||||
let line_ending = LineEnding::detect(&base_text);
|
||||
Self::build(
|
||||
TextBuffer::new(replica_id, cx.model_id() as u64, base_text),
|
||||
TextBuffer::new(replica_id, cx.model_id() as u64, base_text.into()),
|
||||
Some(file),
|
||||
line_ending,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -350,9 +337,11 @@ impl Buffer {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<Self> {
|
||||
let buffer = TextBuffer::new(replica_id, message.id, message.base_text);
|
||||
let line_ending = proto::LineEnding::from_i32(message.line_ending)
|
||||
.ok_or_else(|| anyhow!("missing line_ending"))?;
|
||||
let mut this = Self::build(buffer, file, LineEnding::from_proto(line_ending));
|
||||
let mut this = Self::build(buffer, file);
|
||||
this.text.set_line_ending(proto::deserialize_line_ending(
|
||||
proto::LineEnding::from_i32(message.line_ending)
|
||||
.ok_or_else(|| anyhow!("missing line_ending"))?,
|
||||
));
|
||||
let ops = message
|
||||
.operations
|
||||
.into_iter()
|
||||
|
@ -417,7 +406,7 @@ impl Buffer {
|
|||
diagnostics: proto::serialize_diagnostics(self.diagnostics.iter()),
|
||||
diagnostics_timestamp: self.diagnostics_timestamp.value,
|
||||
completion_triggers: self.completion_triggers.clone(),
|
||||
line_ending: self.line_ending.to_proto() as i32,
|
||||
line_ending: proto::serialize_line_ending(self.line_ending()) as i32,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,7 +415,7 @@ impl Buffer {
|
|||
self
|
||||
}
|
||||
|
||||
fn build(buffer: TextBuffer, file: Option<Arc<dyn File>>, line_ending: LineEnding) -> Self {
|
||||
fn build(buffer: TextBuffer, file: Option<Arc<dyn File>>) -> Self {
|
||||
let saved_mtime;
|
||||
if let Some(file) = file.as_ref() {
|
||||
saved_mtime = file.mtime();
|
||||
|
@ -442,7 +431,6 @@ impl Buffer {
|
|||
was_dirty_before_starting_transaction: None,
|
||||
text: buffer,
|
||||
file,
|
||||
line_ending,
|
||||
syntax_tree: Mutex::new(None),
|
||||
parsing_in_background: false,
|
||||
parse_count: 0,
|
||||
|
@ -503,7 +491,7 @@ impl Buffer {
|
|||
self.remote_id(),
|
||||
text,
|
||||
version,
|
||||
self.line_ending,
|
||||
self.line_ending(),
|
||||
cx.as_mut(),
|
||||
);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
|
@ -559,7 +547,7 @@ impl Buffer {
|
|||
this.did_reload(
|
||||
this.version(),
|
||||
this.as_rope().fingerprint(),
|
||||
this.line_ending,
|
||||
this.line_ending(),
|
||||
new_mtime,
|
||||
cx,
|
||||
);
|
||||
|
@ -584,14 +572,14 @@ impl Buffer {
|
|||
) {
|
||||
self.saved_version = version;
|
||||
self.saved_version_fingerprint = fingerprint;
|
||||
self.line_ending = line_ending;
|
||||
self.text.set_line_ending(line_ending);
|
||||
self.saved_mtime = mtime;
|
||||
if let Some(file) = self.file.as_ref().and_then(|f| f.as_local()) {
|
||||
file.buffer_reloaded(
|
||||
self.remote_id(),
|
||||
&self.saved_version,
|
||||
self.saved_version_fingerprint.clone(),
|
||||
self.line_ending,
|
||||
self.line_ending(),
|
||||
self.saved_mtime,
|
||||
cx,
|
||||
);
|
||||
|
@ -970,13 +958,13 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn diff(&self, new_text: String, cx: &AppContext) -> Task<Diff> {
|
||||
pub(crate) fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
|
||||
let old_text = self.as_rope().clone();
|
||||
let base_version = self.version();
|
||||
cx.background().spawn(async move {
|
||||
let old_text = old_text.to_string();
|
||||
let line_ending = LineEnding::detect(&new_text);
|
||||
let new_text = new_text.replace("\r\n", "\n").replace('\r', "\n");
|
||||
LineEnding::strip_carriage_returns(&mut new_text);
|
||||
let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_str())
|
||||
.iter_all_changes()
|
||||
.map(|c| (c.tag(), c.value().len()))
|
||||
|
@ -999,7 +987,7 @@ impl Buffer {
|
|||
if self.version == diff.base_version {
|
||||
self.finalize_last_transaction();
|
||||
self.start_transaction();
|
||||
self.line_ending = diff.line_ending;
|
||||
self.text.set_line_ending(diff.line_ending);
|
||||
let mut offset = diff.start_offset;
|
||||
for (tag, len) in diff.changes {
|
||||
let range = offset..(offset + len);
|
||||
|
@ -1514,10 +1502,6 @@ impl Buffer {
|
|||
pub fn completion_triggers(&self) -> &[String] {
|
||||
&self.completion_triggers
|
||||
}
|
||||
|
||||
pub fn line_ending(&self) -> LineEnding {
|
||||
self.line_ending
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
@ -2538,52 +2522,6 @@ impl std::ops::SubAssign for IndentSize {
|
|||
}
|
||||
}
|
||||
|
||||
impl LineEnding {
|
||||
pub fn from_proto(style: proto::LineEnding) -> Self {
|
||||
match style {
|
||||
proto::LineEnding::Unix => Self::Unix,
|
||||
proto::LineEnding::Windows => Self::Windows,
|
||||
}
|
||||
}
|
||||
|
||||
fn detect(text: &str) -> Self {
|
||||
let text = &text[..cmp::min(text.len(), 1000)];
|
||||
if let Some(ix) = text.find('\n') {
|
||||
if ix == 0 || text.as_bytes()[ix - 1] != b'\r' {
|
||||
Self::Unix
|
||||
} else {
|
||||
Self::Windows
|
||||
}
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
LineEnding::Unix => "\n",
|
||||
LineEnding::Windows => "\r\n",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_proto(self) -> proto::LineEnding {
|
||||
match self {
|
||||
LineEnding::Unix => proto::LineEnding::Unix,
|
||||
LineEnding::Windows => proto::LineEnding::Windows,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LineEnding {
|
||||
fn default() -> Self {
|
||||
#[cfg(unix)]
|
||||
return Self::Unix;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
return Self::Windows;
|
||||
}
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
pub fn sort_key(&self) -> (usize, &str) {
|
||||
let kind_key = match self.lsp_completion.kind {
|
||||
|
|
|
@ -11,6 +11,20 @@ use text::*;
|
|||
|
||||
pub use proto::{Buffer, BufferState, LineEnding, SelectionSet};
|
||||
|
||||
pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
|
||||
match message {
|
||||
LineEnding::Unix => text::LineEnding::Unix,
|
||||
LineEnding::Windows => text::LineEnding::Windows,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding {
|
||||
match message {
|
||||
text::LineEnding::Unix => proto::LineEnding::Unix,
|
||||
text::LineEnding::Windows => proto::LineEnding::Windows,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
||||
proto::Operation {
|
||||
variant: Some(match operation {
|
||||
|
|
|
@ -421,7 +421,7 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
|
|||
async fn search<'a>(
|
||||
outline: &'a Outline<Anchor>,
|
||||
query: &str,
|
||||
cx: &gpui::TestAppContext,
|
||||
cx: &'a gpui::TestAppContext,
|
||||
) -> Vec<(&'a str, Vec<usize>)> {
|
||||
let matches = cx
|
||||
.read(|cx| outline.search(query, cx.background().clone()))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue