pub use crate::{ diagnostic_set::DiagnosticSet, highlight_map::{HighlightId, HighlightMap}, proto, BracketPair, Grammar, Language, LanguageConfig, LanguageRegistry, PLAIN_TEXT, }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, outline::OutlineItem, syntax_map::{ SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint, }, CodeLabel, Outline, }; use anyhow::{anyhow, Result}; use clock::ReplicaId; use fs::LineEnding; use futures::FutureExt as _; use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task}; use parking_lot::Mutex; use settings::Settings; use similar::{ChangeTag, TextDiff}; use smol::future::yield_now; use std::{ any::Any, cmp::{self, Ordering}, collections::BTreeMap, ffi::OsStr, future::Future, iter::{self, Iterator, Peekable}, mem, ops::{Deref, Range}, path::{Path, PathBuf}, str, sync::Arc, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, vec, }; use sum_tree::TreeMap; use text::operation_queue::OperationQueue; pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Operation as _, *}; use theme::SyntaxTheme; #[cfg(any(test, feature = "test-support"))] use util::RandomCharIter; use util::TryFutureExt as _; #[cfg(any(test, feature = "test-support"))] pub use {tree_sitter_rust, tree_sitter_typescript}; pub use lsp::DiagnosticSeverity; struct GitDiffStatus { diff: git::diff::BufferDiff, update_in_progress: bool, update_requested: bool, } pub struct Buffer { text: TextBuffer, diff_base: Option, git_diff_status: GitDiffStatus, file: Option>, saved_version: clock::Global, saved_version_fingerprint: String, saved_mtime: SystemTime, transaction_depth: usize, was_dirty_before_starting_transaction: Option, language: Option>, autoindent_requests: Vec>, pending_autoindent: Option>, sync_parse_timeout: Duration, syntax_map: Mutex, parsing_in_background: bool, parse_count: usize, diagnostics: DiagnosticSet, remote_selections: TreeMap, selections_update_count: usize, diagnostics_update_count: usize, diagnostics_timestamp: clock::Lamport, file_update_count: usize, git_diff_update_count: usize, completion_triggers: Vec, completion_triggers_timestamp: clock::Lamport, deferred_ops: OperationQueue, } pub struct BufferSnapshot { text: text::BufferSnapshot, pub git_diff: git::diff::BufferDiff, pub(crate) syntax: SyntaxSnapshot, file: Option>, diagnostics: DiagnosticSet, diagnostics_update_count: usize, file_update_count: usize, git_diff_update_count: usize, remote_selections: TreeMap, selections_update_count: usize, language: Option>, parse_count: usize, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub struct IndentSize { pub len: u32, pub kind: IndentKind, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum IndentKind { #[default] Space, Tab, } #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] pub enum CursorShape { #[default] Bar, Block, Underscore, Hollow, } #[derive(Clone, Debug)] struct SelectionSet { line_mode: bool, cursor_shape: CursorShape, selections: Arc<[Selection]>, lamport_timestamp: clock::Lamport, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct GroupId { source: Arc, id: usize, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Diagnostic { pub code: Option, pub severity: DiagnosticSeverity, pub message: String, pub group_id: usize, pub is_valid: bool, pub is_primary: bool, pub is_disk_based: bool, pub is_unnecessary: bool, } #[derive(Clone, Debug)] pub struct Completion { pub old_range: Range, pub new_text: String, pub label: CodeLabel, pub lsp_completion: lsp::CompletionItem, } #[derive(Clone, Debug)] pub struct CodeAction { pub range: Range, pub lsp_action: lsp::CodeAction, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Operation { Buffer(text::Operation), UpdateDiagnostics { diagnostics: Arc<[DiagnosticEntry]>, lamport_timestamp: clock::Lamport, }, UpdateSelections { selections: Arc<[Selection]>, lamport_timestamp: clock::Lamport, line_mode: bool, cursor_shape: CursorShape, }, UpdateCompletionTriggers { triggers: Vec, lamport_timestamp: clock::Lamport, }, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { Operation(Operation), Edited, DirtyChanged, Saved, FileHandleChanged, Reloaded, Reparsed, DiagnosticsUpdated, Closed, } pub trait File: Send + Sync { fn as_local(&self) -> Option<&dyn LocalFile>; fn is_local(&self) -> bool { self.as_local().is_some() } fn mtime(&self) -> SystemTime; /// Returns the path of this file relative to the worktree's root directory. fn path(&self) -> &Arc; /// Returns the path of this file relative to the worktree's parent directory (this means it /// includes the name of the worktree's root folder). fn full_path(&self, cx: &AppContext) -> PathBuf; /// Returns the last component of this handle's absolute path. If this handle refers to the root /// of its worktree, then this method will return the name of the worktree itself. fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr; fn is_deleted(&self) -> bool; fn save( &self, buffer_id: u64, text: Rope, version: clock::Global, line_ending: LineEnding, cx: &mut MutableAppContext, ) -> Task>; fn as_any(&self) -> &dyn Any; fn to_proto(&self) -> rpc::proto::File; } pub trait LocalFile: File { /// Returns the absolute path of this file. fn abs_path(&self, cx: &AppContext) -> PathBuf; fn load(&self, cx: &AppContext) -> Task>; fn buffer_reloaded( &self, buffer_id: u64, version: &clock::Global, fingerprint: String, line_ending: LineEnding, mtime: SystemTime, cx: &mut MutableAppContext, ); } #[derive(Clone, Debug)] pub enum AutoindentMode { /// Indent each line of inserted text. EachLine, /// Apply the same indentation adjustment to all of the lines /// in a given insertion. Block { /// The original indentation level of the first line of each /// insertion, if it has been copied. original_indent_columns: Vec, }, } #[derive(Clone)] struct AutoindentRequest { before_edit: BufferSnapshot, entries: Vec, is_block_mode: bool, } #[derive(Clone)] struct AutoindentRequestEntry { /// A range of the buffer whose indentation should be adjusted. range: Range, /// Whether or not these lines should be considered brand new, for the /// purpose of auto-indent. When text is not new, its indentation will /// only be adjusted if the suggested indentation level has *changed* /// since the edit was made. first_line_is_new: bool, indent_size: IndentSize, original_indent_column: Option, } #[derive(Debug)] struct IndentSuggestion { basis_row: u32, delta: Ordering, } struct BufferChunkHighlights<'a> { captures: SyntaxMapCaptures<'a>, next_capture: Option>, stack: Vec<(usize, HighlightId)>, highlight_maps: Vec, } pub struct BufferChunks<'a> { range: Range, chunks: text::Chunks<'a>, diagnostic_endpoints: Peekable>, error_depth: usize, warning_depth: usize, information_depth: usize, hint_depth: usize, unnecessary_depth: usize, highlights: Option>, } #[derive(Clone, Copy, Debug, Default)] pub struct Chunk<'a> { pub text: &'a str, pub syntax_highlight_id: Option, pub highlight_style: Option, pub diagnostic_severity: Option, pub is_unnecessary: bool, } pub struct Diff { base_version: clock::Global, line_ending: LineEnding, edits: Vec<(Range, Arc)>, } #[derive(Clone, Copy)] pub(crate) struct DiagnosticEndpoint { offset: usize, is_start: bool, severity: DiagnosticSeverity, is_unnecessary: bool, } #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] pub enum CharKind { Punctuation, Whitespace, Word, } impl CharKind { pub fn coerce_punctuation(self, treat_punctuation_as_word: bool) -> Self { if treat_punctuation_as_word && self == CharKind::Punctuation { CharKind::Word } else { self } } } impl Buffer { pub fn new>( replica_id: ReplicaId, base_text: T, cx: &mut ModelContext, ) -> Self { Self::build( TextBuffer::new(replica_id, cx.model_id() as u64, base_text.into()), None, None, ) } pub fn from_file>( replica_id: ReplicaId, base_text: T, diff_base: Option, file: Arc, cx: &mut ModelContext, ) -> Self { Self::build( TextBuffer::new(replica_id, cx.model_id() as u64, base_text.into()), diff_base.map(|h| h.into().into_boxed_str().into()), Some(file), ) } pub fn from_proto( replica_id: ReplicaId, message: proto::BufferState, file: Option>, ) -> Result { let buffer = TextBuffer::new(replica_id, message.id, message.base_text); let mut this = Self::build( buffer, message.diff_base.map(|text| text.into_boxed_str().into()), file, ); this.text.set_line_ending(proto::deserialize_line_ending( rpc::proto::LineEnding::from_i32(message.line_ending) .ok_or_else(|| anyhow!("missing line_ending"))?, )); Ok(this) } pub fn to_proto(&self) -> proto::BufferState { proto::BufferState { id: self.remote_id(), file: self.file.as_ref().map(|f| f.to_proto()), base_text: self.base_text().to_string(), diff_base: self.diff_base.as_ref().map(|h| h.to_string()), line_ending: proto::serialize_line_ending(self.line_ending()) as i32, } } pub fn serialize_ops(&self, cx: &AppContext) -> Task> { let mut operations = Vec::new(); operations.extend(self.deferred_ops.iter().map(proto::serialize_operation)); operations.extend(self.remote_selections.iter().map(|(_, set)| { proto::serialize_operation(&Operation::UpdateSelections { selections: set.selections.clone(), lamport_timestamp: set.lamport_timestamp, line_mode: set.line_mode, cursor_shape: set.cursor_shape, }) })); operations.push(proto::serialize_operation(&Operation::UpdateDiagnostics { diagnostics: self.diagnostics.iter().cloned().collect(), lamport_timestamp: self.diagnostics_timestamp, })); operations.push(proto::serialize_operation( &Operation::UpdateCompletionTriggers { triggers: self.completion_triggers.clone(), lamport_timestamp: self.completion_triggers_timestamp, }, )); let text_operations = self.text.operations().clone(); cx.background().spawn(async move { operations.extend( text_operations .iter() .map(|(_, op)| proto::serialize_operation(&Operation::Buffer(op.clone()))), ); operations.sort_unstable_by_key(proto::lamport_timestamp_for_operation); operations }) } pub fn with_language(mut self, language: Arc, cx: &mut ModelContext) -> Self { self.set_language(Some(language), cx); self } fn build(buffer: TextBuffer, diff_base: Option, file: Option>) -> Self { let saved_mtime = if let Some(file) = file.as_ref() { file.mtime() } else { UNIX_EPOCH }; Self { saved_mtime, saved_version: buffer.version(), saved_version_fingerprint: buffer.as_rope().fingerprint(), transaction_depth: 0, was_dirty_before_starting_transaction: None, text: buffer, diff_base, git_diff_status: GitDiffStatus { diff: git::diff::BufferDiff::new(), update_in_progress: false, update_requested: false, }, file, syntax_map: Mutex::new(SyntaxMap::new()), parsing_in_background: false, parse_count: 0, sync_parse_timeout: Duration::from_millis(1), autoindent_requests: Default::default(), pending_autoindent: Default::default(), language: None, remote_selections: Default::default(), selections_update_count: 0, diagnostics: Default::default(), diagnostics_update_count: 0, diagnostics_timestamp: Default::default(), file_update_count: 0, git_diff_update_count: 0, completion_triggers: Default::default(), completion_triggers_timestamp: Default::default(), deferred_ops: OperationQueue::new(), } } pub fn snapshot(&self) -> BufferSnapshot { let text = self.text.snapshot(); let mut syntax_map = self.syntax_map.lock(); syntax_map.interpolate(&text); let syntax = syntax_map.snapshot(); BufferSnapshot { text, syntax, git_diff: self.git_diff_status.diff.clone(), file: self.file.clone(), remote_selections: self.remote_selections.clone(), diagnostics: self.diagnostics.clone(), diagnostics_update_count: self.diagnostics_update_count, file_update_count: self.file_update_count, git_diff_update_count: self.git_diff_update_count, language: self.language.clone(), parse_count: self.parse_count, selections_update_count: self.selections_update_count, } } pub fn as_text_snapshot(&self) -> &text::BufferSnapshot { &self.text } pub fn text_snapshot(&self) -> text::BufferSnapshot { self.text.snapshot() } pub fn file(&self) -> Option<&dyn File> { self.file.as_deref() } pub fn save( &mut self, cx: &mut ModelContext, ) -> Task> { let file = if let Some(file) = self.file.as_ref() { file } else { return Task::ready(Err(anyhow!("buffer has no file"))); }; let text = self.as_rope().clone(); let version = self.version(); let save = file.save( self.remote_id(), text, version, self.line_ending(), cx.as_mut(), ); cx.spawn(|this, mut cx| async move { let (version, fingerprint, mtime) = save.await?; this.update(&mut cx, |this, cx| { this.did_save(version.clone(), fingerprint.clone(), mtime, None, cx); }); Ok((version, fingerprint, mtime)) }) } pub fn saved_version(&self) -> &clock::Global { &self.saved_version } pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { self.syntax_map.lock().clear(); self.language = language; self.reparse(cx); } pub fn set_language_registry(&mut self, language_registry: Arc) { self.syntax_map .lock() .set_language_registry(language_registry); } pub fn did_save( &mut self, version: clock::Global, fingerprint: String, mtime: SystemTime, new_file: Option>, cx: &mut ModelContext, ) { self.saved_version = version; self.saved_version_fingerprint = fingerprint; self.saved_mtime = mtime; if let Some(new_file) = new_file { self.file = Some(new_file); self.file_update_count += 1; } cx.emit(Event::Saved); cx.notify(); } pub fn reload(&mut self, cx: &mut ModelContext) -> Task>> { cx.spawn(|this, mut cx| async move { if let Some((new_mtime, new_text)) = this.read_with(&cx, |this, cx| { let file = this.file.as_ref()?.as_local()?; Some((file.mtime(), file.load(cx))) }) { let new_text = new_text.await?; let diff = this .read_with(&cx, |this, cx| this.diff(new_text, cx)) .await; this.update(&mut cx, |this, cx| { if let Some(transaction) = this.apply_diff(diff, cx).cloned() { this.did_reload( this.version(), this.as_rope().fingerprint(), this.line_ending(), new_mtime, cx, ); Ok(Some(transaction)) } else { Ok(None) } }) } else { Ok(None) } }) } pub fn did_reload( &mut self, version: clock::Global, fingerprint: String, line_ending: LineEnding, mtime: SystemTime, cx: &mut ModelContext, ) { self.saved_version = version; self.saved_version_fingerprint = fingerprint; 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.saved_mtime, cx, ); } self.git_diff_recalc(cx); cx.emit(Event::Reloaded); cx.notify(); } pub fn file_updated( &mut self, new_file: Arc, cx: &mut ModelContext, ) -> Task<()> { let old_file = if let Some(file) = self.file.as_ref() { file } else { return Task::ready(()); }; let mut file_changed = false; let mut task = Task::ready(()); if new_file.path() != old_file.path() { file_changed = true; } if new_file.is_deleted() { if !old_file.is_deleted() { file_changed = true; if !self.is_dirty() { cx.emit(Event::DirtyChanged); } } } else { let new_mtime = new_file.mtime(); if new_mtime != old_file.mtime() { file_changed = true; if !self.is_dirty() { let reload = self.reload(cx).log_err().map(drop); task = cx.foreground().spawn(reload); } } } if file_changed { self.file_update_count += 1; cx.emit(Event::FileHandleChanged); cx.notify(); } self.file = Some(new_file); task } #[cfg(any(test, feature = "test-support"))] pub fn diff_base(&self) -> Option<&str> { self.diff_base.as_deref() } pub fn set_diff_base(&mut self, diff_base: Option, cx: &mut ModelContext) { self.diff_base = diff_base; self.git_diff_recalc(cx); } pub fn needs_git_diff_recalc(&self) -> bool { self.git_diff_status.diff.needs_update(self) } pub fn git_diff_recalc(&mut self, cx: &mut ModelContext) { if self.git_diff_status.update_in_progress { self.git_diff_status.update_requested = true; return; } if let Some(diff_base) = &self.diff_base { let snapshot = self.snapshot(); let diff_base = diff_base.clone(); let mut diff = self.git_diff_status.diff.clone(); let diff = cx.background().spawn(async move { diff.update(&diff_base, &snapshot).await; diff }); cx.spawn_weak(|this, mut cx| async move { let buffer_diff = diff.await; if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { this.git_diff_status.diff = buffer_diff; this.git_diff_update_count += 1; cx.notify(); this.git_diff_status.update_in_progress = false; if this.git_diff_status.update_requested { this.git_diff_recalc(cx); } }) } }) .detach() } else { let snapshot = self.snapshot(); self.git_diff_status.diff.clear(&snapshot); self.git_diff_update_count += 1; cx.notify(); } } pub fn close(&mut self, cx: &mut ModelContext) { cx.emit(Event::Closed); } pub fn language(&self) -> Option<&Arc> { self.language.as_ref() } pub fn language_at(&self, position: D) -> Option> { let offset = position.to_offset(self); self.syntax_map .lock() .layers_for_range(offset..offset, &self.text) .last() .map(|info| info.language.clone()) .or_else(|| self.language.clone()) } pub fn parse_count(&self) -> usize { self.parse_count } pub fn selections_update_count(&self) -> usize { self.selections_update_count } pub fn diagnostics_update_count(&self) -> usize { self.diagnostics_update_count } pub fn file_update_count(&self) -> usize { self.file_update_count } pub fn git_diff_update_count(&self) -> usize { self.git_diff_update_count } #[cfg(any(test, feature = "test-support"))] pub fn is_parsing(&self) -> bool { self.parsing_in_background } #[cfg(test)] pub fn set_sync_parse_timeout(&mut self, timeout: Duration) { self.sync_parse_timeout = timeout; } fn reparse(&mut self, cx: &mut ModelContext) { if self.parsing_in_background { return; } let language = if let Some(language) = self.language.clone() { language } else { return; }; let text = self.text_snapshot(); let parsed_version = self.version(); let mut syntax_map = self.syntax_map.lock(); syntax_map.interpolate(&text); let language_registry = syntax_map.language_registry(); let mut syntax_snapshot = syntax_map.snapshot(); let syntax_map_version = syntax_map.parsed_version(); drop(syntax_map); let parse_task = cx.background().spawn({ let language = language.clone(); async move { syntax_snapshot.reparse(&syntax_map_version, &text, language_registry, language); syntax_snapshot } }); match cx .background() .block_with_timeout(self.sync_parse_timeout, parse_task) { Ok(new_syntax_snapshot) => { self.did_finish_parsing(new_syntax_snapshot, parsed_version, cx); return; } Err(parse_task) => { self.parsing_in_background = true; cx.spawn(move |this, mut cx| async move { let new_syntax_map = parse_task.await; this.update(&mut cx, move |this, cx| { let grammar_changed = this.language.as_ref().map_or(true, |current_language| { !Arc::ptr_eq(&language, current_language) }); let parse_again = this.version.changed_since(&parsed_version) || grammar_changed; this.did_finish_parsing(new_syntax_map, parsed_version, cx); this.parsing_in_background = false; if parse_again { this.reparse(cx); } }); }) .detach(); } } } fn did_finish_parsing( &mut self, syntax_snapshot: SyntaxSnapshot, version: clock::Global, cx: &mut ModelContext, ) { self.parse_count += 1; self.syntax_map.lock().did_parse(syntax_snapshot, version); self.request_autoindent(cx); cx.emit(Event::Reparsed); cx.notify(); } pub fn update_diagnostics(&mut self, diagnostics: DiagnosticSet, cx: &mut ModelContext) { let lamport_timestamp = self.text.lamport_clock.tick(); let op = Operation::UpdateDiagnostics { diagnostics: diagnostics.iter().cloned().collect(), lamport_timestamp, }; self.apply_diagnostic_update(diagnostics, lamport_timestamp, cx); self.send_operation(op, cx); } fn request_autoindent(&mut self, cx: &mut ModelContext) { if let Some(indent_sizes) = self.compute_autoindents() { let indent_sizes = cx.background().spawn(indent_sizes); match cx .background() .block_with_timeout(Duration::from_micros(500), indent_sizes) { Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx), Err(indent_sizes) => { self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move { let indent_sizes = indent_sizes.await; this.update(&mut cx, |this, cx| { this.apply_autoindents(indent_sizes, cx); }); })); } } } else { self.autoindent_requests.clear(); } } fn compute_autoindents(&self) -> Option>> { let max_rows_between_yields = 100; let snapshot = self.snapshot(); if snapshot.syntax.is_empty() || self.autoindent_requests.is_empty() { return None; } let autoindent_requests = self.autoindent_requests.clone(); Some(async move { let mut indent_sizes = BTreeMap::new(); for request in autoindent_requests { // Resolve each edited range to its row in the current buffer and in the // buffer before this batch of edits. let mut row_ranges = Vec::new(); let mut old_to_new_rows = BTreeMap::new(); let mut language_indent_sizes_by_new_row = Vec::new(); for entry in &request.entries { let position = entry.range.start; let new_row = position.to_point(&snapshot).row; let new_end_row = entry.range.end.to_point(&snapshot).row + 1; language_indent_sizes_by_new_row.push((new_row, entry.indent_size)); if !entry.first_line_is_new { let old_row = position.to_point(&request.before_edit).row; old_to_new_rows.insert(old_row, new_row); } row_ranges.push((new_row..new_end_row, entry.original_indent_column)); } // Build a map containing the suggested indentation for each of the edited lines // with respect to the state of the buffer before these edits. This map is keyed // by the rows for these lines in the current state of the buffer. let mut old_suggestions = BTreeMap::::default(); let old_edited_ranges = contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields); let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable(); let mut language_indent_size = IndentSize::default(); for old_edited_range in old_edited_ranges { let suggestions = request .before_edit .suggest_autoindents(old_edited_range.clone()) .into_iter() .flatten(); for (old_row, suggestion) in old_edited_range.zip(suggestions) { if let Some(suggestion) = suggestion { let new_row = *old_to_new_rows.get(&old_row).unwrap(); // Find the indent size based on the language for this row. while let Some((row, size)) = language_indent_sizes.peek() { if *row > new_row { break; } language_indent_size = *size; language_indent_sizes.next(); } let suggested_indent = old_to_new_rows .get(&suggestion.basis_row) .and_then(|from_row| old_suggestions.get(from_row).copied()) .unwrap_or_else(|| { request .before_edit .indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); old_suggestions.insert(new_row, suggested_indent); } } yield_now().await; } // In block mode, only compute indentation suggestions for the first line // of each insertion. Otherwise, compute suggestions for every inserted line. let new_edited_row_ranges = contiguous_ranges( row_ranges.iter().flat_map(|(range, _)| { if request.is_block_mode { range.start..range.start + 1 } else { range.clone() } }), max_rows_between_yields, ); // Compute new suggestions for each line, but only include them in the result // if they differ from the old suggestion for that line. let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable(); let mut language_indent_size = IndentSize::default(); for new_edited_row_range in new_edited_row_ranges { let suggestions = snapshot .suggest_autoindents(new_edited_row_range.clone()) .into_iter() .flatten(); for (new_row, suggestion) in new_edited_row_range.zip(suggestions) { if let Some(suggestion) = suggestion { // Find the indent size based on the language for this row. while let Some((row, size)) = language_indent_sizes.peek() { if *row > new_row { break; } language_indent_size = *size; language_indent_sizes.next(); } let suggested_indent = indent_sizes .get(&suggestion.basis_row) .copied() .unwrap_or_else(|| { snapshot.indent_size_for_line(suggestion.basis_row) }) .with_delta(suggestion.delta, language_indent_size); if old_suggestions .get(&new_row) .map_or(true, |old_indentation| { suggested_indent != *old_indentation }) { indent_sizes.insert(new_row, suggested_indent); } } } yield_now().await; } // For each block of inserted text, adjust the indentation of the remaining // lines of the block by the same amount as the first line was adjusted. if request.is_block_mode { for (row_range, original_indent_column) in row_ranges .into_iter() .filter_map(|(range, original_indent_column)| { if range.len() > 1 { Some((range, original_indent_column?)) } else { None } }) { let new_indent = indent_sizes .get(&row_range.start) .copied() .unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start)); let delta = new_indent.len as i64 - original_indent_column as i64; if delta != 0 { for row in row_range.skip(1) { indent_sizes.entry(row).or_insert_with(|| { let mut size = snapshot.indent_size_for_line(row); if size.kind == new_indent.kind { match delta.cmp(&0) { Ordering::Greater => size.len += delta as u32, Ordering::Less => { size.len = size.len.saturating_sub(-delta as u32) } Ordering::Equal => {} } } size }); } } } } } indent_sizes }) } fn apply_autoindents( &mut self, indent_sizes: BTreeMap, cx: &mut ModelContext, ) { self.autoindent_requests.clear(); let edits: Vec<_> = indent_sizes .into_iter() .filter_map(|(row, indent_size)| { let current_size = indent_size_for_line(self, row); Self::edit_for_indent_size_adjustment(row, current_size, indent_size) }) .collect(); self.edit(edits, None, cx); } // Create a minimal edit that will cause the the given row to be indented // with the given size. After applying this edit, the length of the line // will always be at least `new_size.len`. pub fn edit_for_indent_size_adjustment( row: u32, current_size: IndentSize, new_size: IndentSize, ) -> Option<(Range, String)> { if new_size.kind != current_size.kind { Some(( Point::new(row, 0)..Point::new(row, current_size.len), iter::repeat(new_size.char()) .take(new_size.len as usize) .collect::(), )) } else { match new_size.len.cmp(¤t_size.len) { Ordering::Greater => { let point = Point::new(row, 0); Some(( point..point, iter::repeat(new_size.char()) .take((new_size.len - current_size.len) as usize) .collect::(), )) } Ordering::Less => Some(( Point::new(row, 0)..Point::new(row, current_size.len - new_size.len), String::new(), )), Ordering::Equal => None, } } } pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task { 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); LineEnding::normalize(&mut new_text); let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str()); let mut edits = Vec::new(); let mut offset = 0; let empty: Arc = "".into(); for change in diff.iter_all_changes() { let value = change.value(); let end_offset = offset + value.len(); match change.tag() { ChangeTag::Equal => { offset = end_offset; } ChangeTag::Delete => { edits.push((offset..end_offset, empty.clone())); offset = end_offset; } ChangeTag::Insert => { edits.push((offset..offset, value.into())); } } } Diff { base_version, line_ending, edits, } }) } pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext) -> Option<&Transaction> { if self.version == diff.base_version { self.finalize_last_transaction(); self.start_transaction(); self.text.set_line_ending(diff.line_ending); self.edit(diff.edits, None, cx); if self.end_transaction(cx).is_some() { self.finalize_last_transaction() } else { None } } else { None } } pub fn is_dirty(&self) -> bool { self.saved_version_fingerprint != self.as_rope().fingerprint() || self.file.as_ref().map_or(false, |file| file.is_deleted()) } pub fn has_conflict(&self) -> bool { self.saved_version_fingerprint != self.as_rope().fingerprint() && self .file .as_ref() .map_or(false, |file| file.mtime() > self.saved_mtime) } pub fn subscribe(&mut self) -> Subscription { self.text.subscribe() } pub fn start_transaction(&mut self) -> Option { self.start_transaction_at(Instant::now()) } pub fn start_transaction_at(&mut self, now: Instant) -> Option { self.transaction_depth += 1; if self.was_dirty_before_starting_transaction.is_none() { self.was_dirty_before_starting_transaction = Some(self.is_dirty()); } self.text.start_transaction_at(now) } pub fn end_transaction(&mut self, cx: &mut ModelContext) -> Option { self.end_transaction_at(Instant::now(), cx) } pub fn end_transaction_at( &mut self, now: Instant, cx: &mut ModelContext, ) -> Option { assert!(self.transaction_depth > 0); self.transaction_depth -= 1; let was_dirty = if self.transaction_depth == 0 { self.was_dirty_before_starting_transaction.take().unwrap() } else { false }; if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) { self.did_edit(&start_version, was_dirty, cx); Some(transaction_id) } else { None } } pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) { self.text.push_transaction(transaction, now); } pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> { self.text.finalize_last_transaction() } pub fn group_until_transaction(&mut self, transaction_id: TransactionId) { self.text.group_until_transaction(transaction_id); } pub fn forget_transaction(&mut self, transaction_id: TransactionId) { self.text.forget_transaction(transaction_id); } pub fn wait_for_edits( &mut self, edit_ids: impl IntoIterator, ) -> impl Future { self.text.wait_for_edits(edit_ids) } pub fn wait_for_anchors<'a>( &mut self, anchors: impl IntoIterator, ) -> impl Future { self.text.wait_for_anchors(anchors) } pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future { self.text.wait_for_version(version) } pub fn set_active_selections( &mut self, selections: Arc<[Selection]>, line_mode: bool, cursor_shape: CursorShape, cx: &mut ModelContext, ) { let lamport_timestamp = self.text.lamport_clock.tick(); self.remote_selections.insert( self.text.replica_id(), SelectionSet { selections: selections.clone(), lamport_timestamp, line_mode, cursor_shape, }, ); self.send_operation( Operation::UpdateSelections { selections, line_mode, lamport_timestamp, cursor_shape, }, cx, ); } pub fn remove_active_selections(&mut self, cx: &mut ModelContext) { self.set_active_selections(Arc::from([]), false, Default::default(), cx); } pub fn set_text(&mut self, text: T, cx: &mut ModelContext) -> Option where T: Into>, { self.edit([(0..self.len(), text)], None, cx) } pub fn edit( &mut self, edits_iter: I, autoindent_mode: Option, cx: &mut ModelContext, ) -> Option where I: IntoIterator, T)>, S: ToOffset, T: Into>, { // Skip invalid edits and coalesce contiguous ones. let mut edits: Vec<(Range, Arc)> = Vec::new(); for (range, new_text) in edits_iter { let mut range = range.start.to_offset(self)..range.end.to_offset(self); if range.start > range.end { mem::swap(&mut range.start, &mut range.end); } let new_text = new_text.into(); if !new_text.is_empty() || !range.is_empty() { if let Some((prev_range, prev_text)) = edits.last_mut() { if prev_range.end >= range.start { prev_range.end = cmp::max(prev_range.end, range.end); *prev_text = format!("{prev_text}{new_text}").into(); } else { edits.push((range, new_text)); } } else { edits.push((range, new_text)); } } } if edits.is_empty() { return None; } self.start_transaction(); self.pending_autoindent.take(); let autoindent_request = autoindent_mode .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode))); let edit_operation = self.text.edit(edits.iter().cloned()); let edit_id = edit_operation.local_timestamp(); if let Some((before_edit, mode)) = autoindent_request { let (start_columns, is_block_mode) = match mode { AutoindentMode::Block { original_indent_columns: start_columns, } => (start_columns, true), AutoindentMode::EachLine => (Default::default(), false), }; let mut delta = 0isize; let entries = edits .into_iter() .enumerate() .zip(&edit_operation.as_edit().unwrap().new_text) .map(|((ix, (range, _)), new_text)| { let new_text_len = new_text.len(); let old_start = range.start.to_point(&before_edit); let new_start = (delta + range.start as isize) as usize; delta += new_text_len as isize - (range.end as isize - range.start as isize); let mut range_of_insertion_to_indent = 0..new_text_len; let mut first_line_is_new = false; let mut start_column = None; // When inserting an entire line at the beginning of an existing line, // treat the insertion as new. if new_text.contains('\n') && old_start.column <= before_edit.indent_size_for_line(old_start.row).len { first_line_is_new = true; } // When inserting text starting with a newline, avoid auto-indenting the // previous line. if new_text[range_of_insertion_to_indent.clone()].starts_with('\n') { range_of_insertion_to_indent.start += 1; first_line_is_new = true; } // Avoid auto-indenting after the insertion. if is_block_mode { start_column = start_columns.get(ix).copied(); if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') { range_of_insertion_to_indent.end -= 1; } } AutoindentRequestEntry { first_line_is_new, original_indent_column: start_column, indent_size: before_edit.language_indent_size_at(range.start, cx), range: self.anchor_before(new_start + range_of_insertion_to_indent.start) ..self.anchor_after(new_start + range_of_insertion_to_indent.end), } }) .collect(); self.autoindent_requests.push(Arc::new(AutoindentRequest { before_edit, entries, is_block_mode, })); } self.end_transaction(cx); self.send_operation(Operation::Buffer(edit_operation), cx); Some(edit_id) } fn did_edit( &mut self, old_version: &clock::Global, was_dirty: bool, cx: &mut ModelContext, ) { if self.edits_since::(old_version).next().is_none() { return; } self.reparse(cx); cx.emit(Event::Edited); if was_dirty != self.is_dirty() { cx.emit(Event::DirtyChanged); } cx.notify(); } pub fn apply_ops>( &mut self, ops: I, cx: &mut ModelContext, ) -> Result<()> { self.pending_autoindent.take(); let was_dirty = self.is_dirty(); let old_version = self.version.clone(); let mut deferred_ops = Vec::new(); let buffer_ops = ops .into_iter() .filter_map(|op| match op { Operation::Buffer(op) => Some(op), _ => { if self.can_apply_op(&op) { self.apply_op(op, cx); } else { deferred_ops.push(op); } None } }) .collect::>(); self.text.apply_ops(buffer_ops)?; self.deferred_ops.insert(deferred_ops); self.flush_deferred_ops(cx); self.did_edit(&old_version, was_dirty, cx); // Notify independently of whether the buffer was edited as the operations could include a // selection update. cx.notify(); Ok(()) } fn flush_deferred_ops(&mut self, cx: &mut ModelContext) { let mut deferred_ops = Vec::new(); for op in self.deferred_ops.drain().iter().cloned() { if self.can_apply_op(&op) { self.apply_op(op, cx); } else { deferred_ops.push(op); } } self.deferred_ops.insert(deferred_ops); } fn can_apply_op(&self, operation: &Operation) -> bool { match operation { Operation::Buffer(_) => { unreachable!("buffer operations should never be applied at this layer") } Operation::UpdateDiagnostics { diagnostics: diagnostic_set, .. } => diagnostic_set.iter().all(|diagnostic| { self.text.can_resolve(&diagnostic.range.start) && self.text.can_resolve(&diagnostic.range.end) }), Operation::UpdateSelections { selections, .. } => selections .iter() .all(|s| self.can_resolve(&s.start) && self.can_resolve(&s.end)), Operation::UpdateCompletionTriggers { .. } => true, } } fn apply_op(&mut self, operation: Operation, cx: &mut ModelContext) { match operation { Operation::Buffer(_) => { unreachable!("buffer operations should never be applied at this layer") } Operation::UpdateDiagnostics { diagnostics: diagnostic_set, lamport_timestamp, } => { let snapshot = self.snapshot(); self.apply_diagnostic_update( DiagnosticSet::from_sorted_entries(diagnostic_set.iter().cloned(), &snapshot), lamport_timestamp, cx, ); } Operation::UpdateSelections { selections, lamport_timestamp, line_mode, cursor_shape, } => { if let Some(set) = self.remote_selections.get(&lamport_timestamp.replica_id) { if set.lamport_timestamp > lamport_timestamp { return; } } self.remote_selections.insert( lamport_timestamp.replica_id, SelectionSet { selections, lamport_timestamp, line_mode, cursor_shape, }, ); self.text.lamport_clock.observe(lamport_timestamp); self.selections_update_count += 1; } Operation::UpdateCompletionTriggers { triggers, lamport_timestamp, } => { self.completion_triggers = triggers; self.text.lamport_clock.observe(lamport_timestamp); } } } fn apply_diagnostic_update( &mut self, diagnostics: DiagnosticSet, lamport_timestamp: clock::Lamport, cx: &mut ModelContext, ) { if lamport_timestamp > self.diagnostics_timestamp { self.diagnostics = diagnostics; self.diagnostics_timestamp = lamport_timestamp; self.diagnostics_update_count += 1; self.text.lamport_clock.observe(lamport_timestamp); cx.notify(); cx.emit(Event::DiagnosticsUpdated); } } fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext) { cx.emit(Event::Operation(operation)); } pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext) { self.remote_selections.remove(&replica_id); cx.notify(); } pub fn undo(&mut self, cx: &mut ModelContext) -> Option { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); if let Some((transaction_id, operation)) = self.text.undo() { self.send_operation(Operation::Buffer(operation), cx); self.did_edit(&old_version, was_dirty, cx); Some(transaction_id) } else { None } } pub fn undo_to_transaction( &mut self, transaction_id: TransactionId, cx: &mut ModelContext, ) -> bool { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); let operations = self.text.undo_to_transaction(transaction_id); let undone = !operations.is_empty(); for operation in operations { self.send_operation(Operation::Buffer(operation), cx); } if undone { self.did_edit(&old_version, was_dirty, cx) } undone } pub fn redo(&mut self, cx: &mut ModelContext) -> Option { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); if let Some((transaction_id, operation)) = self.text.redo() { self.send_operation(Operation::Buffer(operation), cx); self.did_edit(&old_version, was_dirty, cx); Some(transaction_id) } else { None } } pub fn redo_to_transaction( &mut self, transaction_id: TransactionId, cx: &mut ModelContext, ) -> bool { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); let operations = self.text.redo_to_transaction(transaction_id); let redone = !operations.is_empty(); for operation in operations { self.send_operation(Operation::Buffer(operation), cx); } if redone { self.did_edit(&old_version, was_dirty, cx) } redone } pub fn set_completion_triggers(&mut self, triggers: Vec, cx: &mut ModelContext) { self.completion_triggers = triggers.clone(); self.completion_triggers_timestamp = self.text.lamport_clock.tick(); self.send_operation( Operation::UpdateCompletionTriggers { triggers, lamport_timestamp: self.completion_triggers_timestamp, }, cx, ); cx.notify(); } pub fn completion_triggers(&self) -> &[String] { &self.completion_triggers } } #[cfg(any(test, feature = "test-support"))] impl Buffer { pub fn set_group_interval(&mut self, group_interval: Duration) { self.text.set_group_interval(group_interval); } pub fn randomly_edit( &mut self, rng: &mut T, old_range_count: usize, cx: &mut ModelContext, ) where T: rand::Rng, { let mut edits: Vec<(Range, String)> = Vec::new(); let mut last_end = None; for _ in 0..old_range_count { if last_end.map_or(false, |last_end| last_end >= self.len()) { break; } let new_start = last_end.map_or(0, |last_end| last_end + 1); let mut range = self.random_byte_range(new_start, rng); if rng.gen_bool(0.2) { mem::swap(&mut range.start, &mut range.end); } last_end = Some(range.end); let new_text_len = rng.gen_range(0..10); let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); edits.push((range, new_text)); } log::info!("mutating buffer {} with {:?}", self.replica_id(), edits); self.edit(edits, None, cx); } pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng, cx: &mut ModelContext) { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); let ops = self.text.randomly_undo_redo(rng); if !ops.is_empty() { for op in ops { self.send_operation(Operation::Buffer(op), cx); self.did_edit(&old_version, was_dirty, cx); } } } } impl Entity for Buffer { type Event = Event; } impl Deref for Buffer { type Target = TextBuffer; fn deref(&self) -> &Self::Target { &self.text } } impl BufferSnapshot { pub fn indent_size_for_line(&self, row: u32) -> IndentSize { indent_size_for_line(self, row) } pub fn language_indent_size_at(&self, position: T, cx: &AppContext) -> IndentSize { let language_name = self.language_at(position).map(|language| language.name()); let settings = cx.global::(); if settings.hard_tabs(language_name.as_deref()) { IndentSize::tab() } else { IndentSize::spaces(settings.tab_size(language_name.as_deref()).get()) } } pub fn suggested_indents( &self, rows: impl Iterator, single_indent_size: IndentSize, ) -> BTreeMap { let mut result = BTreeMap::new(); for row_range in contiguous_ranges(rows, 10) { let suggestions = match self.suggest_autoindents(row_range.clone()) { Some(suggestions) => suggestions, _ => break, }; for (row, suggestion) in row_range.zip(suggestions) { let indent_size = if let Some(suggestion) = suggestion { result .get(&suggestion.basis_row) .copied() .unwrap_or_else(|| self.indent_size_for_line(suggestion.basis_row)) .with_delta(suggestion.delta, single_indent_size) } else { self.indent_size_for_line(row) }; result.insert(row, indent_size); } } result } fn suggest_autoindents( &self, row_range: Range, ) -> Option> + '_> { let config = &self.language.as_ref()?.config; let prev_non_blank_row = self.prev_non_blank_row(row_range.start); // Find the suggested indentation ranges based on the syntax tree. let start = Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0); let end = Point::new(row_range.end, 0); let range = (start..end).to_offset(&self.text); let mut matches = self.syntax.matches(range, &self.text, |grammar| { Some(&grammar.indents_config.as_ref()?.query) }); let indent_configs = matches .grammars() .iter() .map(|grammar| grammar.indents_config.as_ref().unwrap()) .collect::>(); let mut indent_ranges = Vec::>::new(); let mut outdent_positions = Vec::::new(); while let Some(mat) = matches.peek() { let mut start: Option = None; let mut end: Option = None; let config = &indent_configs[mat.grammar_index]; for capture in mat.captures { if capture.index == config.indent_capture_ix { start.get_or_insert(Point::from_ts_point(capture.node.start_position())); end.get_or_insert(Point::from_ts_point(capture.node.end_position())); } else if Some(capture.index) == config.start_capture_ix { start = Some(Point::from_ts_point(capture.node.end_position())); } else if Some(capture.index) == config.end_capture_ix { end = Some(Point::from_ts_point(capture.node.start_position())); } else if Some(capture.index) == config.outdent_capture_ix { outdent_positions.push(Point::from_ts_point(capture.node.start_position())); } } matches.advance(); if let Some((start, end)) = start.zip(end) { if start.row == end.row { continue; } let range = start..end; match indent_ranges.binary_search_by_key(&range.start, |r| r.start) { Err(ix) => indent_ranges.insert(ix, range), Ok(ix) => { let prev_range = &mut indent_ranges[ix]; prev_range.end = prev_range.end.max(range.end); } } } } outdent_positions.sort(); for outdent_position in outdent_positions { // find the innermost indent range containing this outdent_position // set its end to the outdent position if let Some(range_to_truncate) = indent_ranges .iter_mut() .filter(|indent_range| indent_range.contains(&outdent_position)) .last() { range_to_truncate.end = outdent_position; } } // Find the suggested indentation increases and decreased based on regexes. let mut indent_change_rows = Vec::<(u32, Ordering)>::new(); self.for_each_line( Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0) ..Point::new(row_range.end, 0), |row, line| { if config .decrease_indent_pattern .as_ref() .map_or(false, |regex| regex.is_match(line)) { indent_change_rows.push((row, Ordering::Less)); } if config .increase_indent_pattern .as_ref() .map_or(false, |regex| regex.is_match(line)) { indent_change_rows.push((row + 1, Ordering::Greater)); } }, ); let mut indent_changes = indent_change_rows.into_iter().peekable(); let mut prev_row = if config.auto_indent_using_last_non_empty_line { prev_non_blank_row.unwrap_or(0) } else { row_range.start.saturating_sub(1) }; let mut prev_row_start = Point::new(prev_row, self.indent_size_for_line(prev_row).len); Some(row_range.map(move |row| { let row_start = Point::new(row, self.indent_size_for_line(row).len); let mut indent_from_prev_row = false; let mut outdent_from_prev_row = false; let mut outdent_to_row = u32::MAX; while let Some((indent_row, delta)) = indent_changes.peek() { match indent_row.cmp(&row) { Ordering::Equal => match delta { Ordering::Less => outdent_from_prev_row = true, Ordering::Greater => indent_from_prev_row = true, _ => {} }, Ordering::Greater => break, Ordering::Less => {} } indent_changes.next(); } for range in &indent_ranges { if range.start.row >= row { break; } if range.start.row == prev_row && range.end > row_start { indent_from_prev_row = true; } if range.end > prev_row_start && range.end <= row_start { outdent_to_row = outdent_to_row.min(range.start.row); } } let suggestion = if outdent_to_row == prev_row || (outdent_from_prev_row && indent_from_prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, }) } else if indent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Greater, }) } else if outdent_to_row < prev_row { Some(IndentSuggestion { basis_row: outdent_to_row, delta: Ordering::Equal, }) } else if outdent_from_prev_row { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Less, }) } else if config.auto_indent_using_last_non_empty_line || !self.is_line_blank(prev_row) { Some(IndentSuggestion { basis_row: prev_row, delta: Ordering::Equal, }) } else { None }; prev_row = row; prev_row_start = row_start; suggestion })) } fn prev_non_blank_row(&self, mut row: u32) -> Option { while row > 0 { row -= 1; if !self.is_line_blank(row) { return Some(row); } } None } pub fn chunks(&self, range: Range, language_aware: bool) -> BufferChunks { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut syntax = None; let mut diagnostic_endpoints = Vec::new(); if language_aware { let captures = self.syntax.captures(range.clone(), &self.text, |grammar| { grammar.highlights_query.as_ref() }); let highlight_maps = captures .grammars() .into_iter() .map(|grammar| grammar.highlight_map()) .collect(); syntax = Some((captures, highlight_maps)); for entry in self.diagnostics_in_range::<_, usize>(range.clone(), false) { diagnostic_endpoints.push(DiagnosticEndpoint { offset: entry.range.start, is_start: true, severity: entry.diagnostic.severity, is_unnecessary: entry.diagnostic.is_unnecessary, }); diagnostic_endpoints.push(DiagnosticEndpoint { offset: entry.range.end, is_start: false, severity: entry.diagnostic.severity, is_unnecessary: entry.diagnostic.is_unnecessary, }); } diagnostic_endpoints .sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start)); } BufferChunks::new(self.text.as_rope(), range, syntax, diagnostic_endpoints) } pub fn for_each_line(&self, range: Range, mut callback: impl FnMut(u32, &str)) { let mut line = String::new(); let mut row = range.start.row; for chunk in self .as_rope() .chunks_in_range(range.to_offset(self)) .chain(["\n"]) { for (newline_ix, text) in chunk.split('\n').enumerate() { if newline_ix > 0 { callback(row, &line); row += 1; line.clear(); } line.push_str(text); } } } pub fn language_at(&self, position: D) -> Option<&Arc> { let offset = position.to_offset(self); self.syntax .layers_for_range(offset..offset, &self.text) .filter(|l| l.node.end_byte() > offset) .last() .map(|info| info.language) .or(self.language.as_ref()) } pub fn surrounding_word(&self, start: T) -> (Range, Option) { let mut start = start.to_offset(self); let mut end = start; let mut next_chars = self.chars_at(start).peekable(); let mut prev_chars = self.reversed_chars_at(start).peekable(); let word_kind = cmp::max( prev_chars.peek().copied().map(char_kind), next_chars.peek().copied().map(char_kind), ); for ch in prev_chars { if Some(char_kind(ch)) == word_kind && ch != '\n' { start -= ch.len_utf8(); } else { break; } } for ch in next_chars { if Some(char_kind(ch)) == word_kind && ch != '\n' { end += ch.len_utf8(); } else { break; } } (start..end, word_kind) } pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut result: Option> = None; 'outer: for layer in self.syntax.layers_for_range(range.clone(), &self.text) { let mut cursor = layer.node.walk(); // Descend to the first leaf that touches the start of the range, // and if the range is non-empty, extends beyond the start. while cursor.goto_first_child_for_byte(range.start).is_some() { if !range.is_empty() && cursor.node().end_byte() == range.start { cursor.goto_next_sibling(); } } // Ascend to the smallest ancestor that strictly contains the range. loop { let node_range = cursor.node().byte_range(); if node_range.start <= range.start && node_range.end >= range.end && node_range.len() > range.len() { break; } if !cursor.goto_parent() { continue 'outer; } } let left_node = cursor.node(); let mut layer_result = left_node.byte_range(); // For an empty range, try to find another node immediately to the right of the range. if left_node.end_byte() == range.start { let mut right_node = None; while !cursor.goto_next_sibling() { if !cursor.goto_parent() { break; } } while cursor.node().start_byte() == range.start { right_node = Some(cursor.node()); if !cursor.goto_first_child() { break; } } // If there is a candidate node on both sides of the (empty) range, then // decide between the two by favoring a named node over an anonymous token. // If both nodes are the same in that regard, favor the right one. if let Some(right_node) = right_node { if right_node.is_named() || !left_node.is_named() { layer_result = right_node.byte_range(); } } } if let Some(previous_result) = &result { if previous_result.len() < layer_result.len() { continue; } } result = Some(layer_result); } result } pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option> { self.outline_items_containing(0..self.len(), theme) .map(Outline::new) } pub fn symbols_containing( &self, position: T, theme: Option<&SyntaxTheme>, ) -> Option>> { let position = position.to_offset(self); let mut items = self.outline_items_containing( position.saturating_sub(1)..self.len().min(position + 1), theme, )?; let mut prev_depth = None; items.retain(|item| { let result = prev_depth.map_or(true, |prev_depth| item.depth > prev_depth); prev_depth = Some(item.depth); result }); Some(items) } fn outline_items_containing( &self, range: Range, theme: Option<&SyntaxTheme>, ) -> Option>> { let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| { grammar.outline_config.as_ref().map(|c| &c.query) }); let configs = matches .grammars() .iter() .map(|g| g.outline_config.as_ref().unwrap()) .collect::>(); let mut chunks = self.chunks(0..self.len(), true); let mut stack = Vec::>::new(); let mut items = Vec::new(); while let Some(mat) = matches.peek() { let config = &configs[mat.grammar_index]; let item_node = mat.captures.iter().find_map(|cap| { if cap.index == config.item_capture_ix { Some(cap.node) } else { None } })?; let item_range = item_node.byte_range(); if item_range.end < range.start || item_range.start > range.end { matches.advance(); continue; } // TODO - move later, after processing captures let mut text = String::new(); let mut name_ranges = Vec::new(); let mut highlight_ranges = Vec::new(); for capture in mat.captures { let node_is_name; if capture.index == config.name_capture_ix { node_is_name = true; } else if Some(capture.index) == config.context_capture_ix { node_is_name = false; } else { continue; } let range = capture.node.start_byte()..capture.node.end_byte(); if !text.is_empty() { text.push(' '); } if node_is_name { let mut start = text.len(); let end = start + range.len(); // When multiple names are captured, then the matcheable text // includes the whitespace in between the names. if !name_ranges.is_empty() { start -= 1; } name_ranges.push(start..end); } let mut offset = range.start; chunks.seek(offset); for mut chunk in chunks.by_ref() { if chunk.text.len() > range.end - offset { chunk.text = &chunk.text[0..(range.end - offset)]; offset = range.end; } else { offset += chunk.text.len(); } let style = chunk .syntax_highlight_id .zip(theme) .and_then(|(highlight, theme)| highlight.style(theme)); if let Some(style) = style { let start = text.len(); let end = start + chunk.text.len(); highlight_ranges.push((start..end, style)); } text.push_str(chunk.text); if offset >= range.end { break; } } } matches.advance(); while stack.last().map_or(false, |prev_range| { prev_range.start > item_range.start || prev_range.end < item_range.end }) { stack.pop(); } stack.push(item_range.clone()); items.push(OutlineItem { depth: stack.len() - 1, range: self.anchor_after(item_range.start)..self.anchor_before(item_range.end), text, highlight_ranges, name_ranges, }) } Some(items) } pub fn enclosing_bracket_ranges( &self, range: Range, ) -> Option<(Range, Range)> { // Find bracket pairs that *inclusively* contain the given range. let range = range.start.to_offset(self)..range.end.to_offset(self); let mut matches = self.syntax.matches( range.start.saturating_sub(1)..self.len().min(range.end + 1), &self.text, |grammar| grammar.brackets_config.as_ref().map(|c| &c.query), ); let configs = matches .grammars() .iter() .map(|grammar| grammar.brackets_config.as_ref().unwrap()) .collect::>(); // Get the ranges of the innermost pair of brackets. let mut result: Option<(Range, Range)> = None; while let Some(mat) = matches.peek() { let mut open = None; let mut close = None; let config = &configs[mat.grammar_index]; for capture in mat.captures { if capture.index == config.open_capture_ix { open = Some(capture.node.byte_range()); } else if capture.index == config.close_capture_ix { close = Some(capture.node.byte_range()); } } matches.advance(); let Some((open, close)) = open.zip(close) else { continue }; if open.start > range.start || close.end < range.end { continue; } let len = close.end - open.start; if let Some((existing_open, existing_close)) = &result { let existing_len = existing_close.end - existing_open.start; if len > existing_len { continue; } } result = Some((open, close)); } result } #[allow(clippy::type_complexity)] pub fn remote_selections_in_range( &self, range: Range, ) -> impl Iterator< Item = ( ReplicaId, bool, CursorShape, impl Iterator> + '_, ), > + '_ { self.remote_selections .iter() .filter(|(replica_id, set)| { **replica_id != self.text.replica_id() && !set.selections.is_empty() }) .map(move |(replica_id, set)| { let start_ix = match set.selections.binary_search_by(|probe| { probe.end.cmp(&range.start, self).then(Ordering::Greater) }) { Ok(ix) | Err(ix) => ix, }; let end_ix = match set.selections.binary_search_by(|probe| { probe.start.cmp(&range.end, self).then(Ordering::Less) }) { Ok(ix) | Err(ix) => ix, }; ( *replica_id, set.line_mode, set.cursor_shape, set.selections[start_ix..end_ix].iter(), ) }) } pub fn git_diff_hunks_in_range<'a>( &'a self, query_row_range: Range, reversed: bool, ) -> impl 'a + Iterator> { self.git_diff .hunks_in_range(query_row_range, self, reversed) } pub fn diagnostics_in_range<'a, T, O>( &'a self, search_range: Range, reversed: bool, ) -> impl 'a + Iterator> where T: 'a + Clone + ToOffset, O: 'a + FromAnchor, { self.diagnostics.range(search_range, self, true, reversed) } pub fn diagnostic_groups(&self) -> Vec> { let mut groups = Vec::new(); self.diagnostics.groups(&mut groups, self); groups } pub fn diagnostic_group<'a, O>( &'a self, group_id: usize, ) -> impl 'a + Iterator> where O: 'a + FromAnchor, { self.diagnostics.group(group_id, self) } pub fn diagnostics_update_count(&self) -> usize { self.diagnostics_update_count } pub fn parse_count(&self) -> usize { self.parse_count } pub fn selections_update_count(&self) -> usize { self.selections_update_count } pub fn file(&self) -> Option<&dyn File> { self.file.as_deref() } pub fn resolve_file_path(&self, cx: &AppContext, include_root: bool) -> Option { if let Some(file) = self.file() { if file.path().file_name().is_none() || include_root { Some(file.full_path(cx)) } else { Some(file.path().to_path_buf()) } } else { None } } pub fn file_update_count(&self) -> usize { self.file_update_count } pub fn git_diff_update_count(&self) -> usize { self.git_diff_update_count } } pub fn indent_size_for_line(text: &text::BufferSnapshot, row: u32) -> IndentSize { indent_size_for_text(text.chars_at(Point::new(row, 0))) } pub fn indent_size_for_text(text: impl Iterator) -> IndentSize { let mut result = IndentSize::spaces(0); for c in text { let kind = match c { ' ' => IndentKind::Space, '\t' => IndentKind::Tab, _ => break, }; if result.len == 0 { result.kind = kind; } result.len += 1; } result } impl Clone for BufferSnapshot { fn clone(&self) -> Self { Self { text: self.text.clone(), git_diff: self.git_diff.clone(), syntax: self.syntax.clone(), file: self.file.clone(), remote_selections: self.remote_selections.clone(), diagnostics: self.diagnostics.clone(), selections_update_count: self.selections_update_count, diagnostics_update_count: self.diagnostics_update_count, file_update_count: self.file_update_count, git_diff_update_count: self.git_diff_update_count, language: self.language.clone(), parse_count: self.parse_count, } } } impl Deref for BufferSnapshot { type Target = text::BufferSnapshot; fn deref(&self) -> &Self::Target { &self.text } } unsafe impl<'a> Send for BufferChunks<'a> {} impl<'a> BufferChunks<'a> { pub(crate) fn new( text: &'a Rope, range: Range, syntax: Option<(SyntaxMapCaptures<'a>, Vec)>, diagnostic_endpoints: Vec, ) -> Self { let mut highlights = None; if let Some((captures, highlight_maps)) = syntax { highlights = Some(BufferChunkHighlights { captures, next_capture: None, stack: Default::default(), highlight_maps, }) } let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable(); let chunks = text.chunks_in_range(range.clone()); BufferChunks { range, chunks, diagnostic_endpoints, error_depth: 0, warning_depth: 0, information_depth: 0, hint_depth: 0, unnecessary_depth: 0, highlights, } } pub fn seek(&mut self, offset: usize) { self.range.start = offset; self.chunks.seek(self.range.start); if let Some(highlights) = self.highlights.as_mut() { highlights .stack .retain(|(end_offset, _)| *end_offset > offset); if let Some(capture) = &highlights.next_capture { if offset >= capture.node.start_byte() { let next_capture_end = capture.node.end_byte(); if offset < next_capture_end { highlights.stack.push(( next_capture_end, highlights.highlight_maps[capture.grammar_index].get(capture.index), )); } highlights.next_capture.take(); } } highlights.captures.set_byte_range(self.range.clone()); } } pub fn offset(&self) -> usize { self.range.start } fn update_diagnostic_depths(&mut self, endpoint: DiagnosticEndpoint) { let depth = match endpoint.severity { DiagnosticSeverity::ERROR => &mut self.error_depth, DiagnosticSeverity::WARNING => &mut self.warning_depth, DiagnosticSeverity::INFORMATION => &mut self.information_depth, DiagnosticSeverity::HINT => &mut self.hint_depth, _ => return, }; if endpoint.is_start { *depth += 1; } else { *depth -= 1; } if endpoint.is_unnecessary { if endpoint.is_start { self.unnecessary_depth += 1; } else { self.unnecessary_depth -= 1; } } } fn current_diagnostic_severity(&self) -> Option { if self.error_depth > 0 { Some(DiagnosticSeverity::ERROR) } else if self.warning_depth > 0 { Some(DiagnosticSeverity::WARNING) } else if self.information_depth > 0 { Some(DiagnosticSeverity::INFORMATION) } else if self.hint_depth > 0 { Some(DiagnosticSeverity::HINT) } else { None } } fn current_code_is_unnecessary(&self) -> bool { self.unnecessary_depth > 0 } } impl<'a> Iterator for BufferChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { let mut next_capture_start = usize::MAX; let mut next_diagnostic_endpoint = usize::MAX; if let Some(highlights) = self.highlights.as_mut() { while let Some((parent_capture_end, _)) = highlights.stack.last() { if *parent_capture_end <= self.range.start { highlights.stack.pop(); } else { break; } } if highlights.next_capture.is_none() { highlights.next_capture = highlights.captures.next(); } while let Some(capture) = highlights.next_capture.as_ref() { if self.range.start < capture.node.start_byte() { next_capture_start = capture.node.start_byte(); break; } else { let highlight_id = highlights.highlight_maps[capture.grammar_index].get(capture.index); highlights .stack .push((capture.node.end_byte(), highlight_id)); highlights.next_capture = highlights.captures.next(); } } } while let Some(endpoint) = self.diagnostic_endpoints.peek().copied() { if endpoint.offset <= self.range.start { self.update_diagnostic_depths(endpoint); self.diagnostic_endpoints.next(); } else { next_diagnostic_endpoint = endpoint.offset; break; } } if let Some(chunk) = self.chunks.peek() { let chunk_start = self.range.start; let mut chunk_end = (self.chunks.offset() + chunk.len()) .min(next_capture_start) .min(next_diagnostic_endpoint); let mut highlight_id = None; if let Some(highlights) = self.highlights.as_ref() { if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() { chunk_end = chunk_end.min(*parent_capture_end); highlight_id = Some(*parent_highlight_id); } } let slice = &chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()]; self.range.start = chunk_end; if self.range.start == self.chunks.offset() + chunk.len() { self.chunks.next().unwrap(); } Some(Chunk { text: slice, syntax_highlight_id: highlight_id, highlight_style: None, diagnostic_severity: self.current_diagnostic_severity(), is_unnecessary: self.current_code_is_unnecessary(), }) } else { None } } } impl operation_queue::Operation for Operation { fn lamport_timestamp(&self) -> clock::Lamport { match self { Operation::Buffer(_) => { unreachable!("buffer operations should never be deferred at this layer") } Operation::UpdateDiagnostics { lamport_timestamp, .. } | Operation::UpdateSelections { lamport_timestamp, .. } | Operation::UpdateCompletionTriggers { lamport_timestamp, .. } => *lamport_timestamp, } } } impl Default for Diagnostic { fn default() -> Self { Self { code: None, severity: DiagnosticSeverity::ERROR, message: Default::default(), group_id: 0, is_primary: false, is_valid: true, is_disk_based: false, is_unnecessary: false, } } } impl IndentSize { pub fn spaces(len: u32) -> Self { Self { len, kind: IndentKind::Space, } } pub fn tab() -> Self { Self { len: 1, kind: IndentKind::Tab, } } pub fn chars(&self) -> impl Iterator { iter::repeat(self.char()).take(self.len as usize) } pub fn char(&self) -> char { match self.kind { IndentKind::Space => ' ', IndentKind::Tab => '\t', } } pub fn with_delta(mut self, direction: Ordering, size: IndentSize) -> Self { match direction { Ordering::Less => { if self.kind == size.kind && self.len >= size.len { self.len -= size.len; } } Ordering::Equal => {} Ordering::Greater => { if self.len == 0 { self = size; } else if self.kind == size.kind { self.len += size.len; } } } self } } impl Completion { pub fn sort_key(&self) -> (usize, &str) { let kind_key = match self.lsp_completion.kind { Some(lsp::CompletionItemKind::VARIABLE) => 0, _ => 1, }; (kind_key, &self.label.text[self.label.filter_range.clone()]) } pub fn is_snippet(&self) -> bool { self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET) } } pub fn contiguous_ranges( values: impl Iterator, max_len: usize, ) -> impl Iterator> { let mut values = values; let mut current_range: Option> = None; std::iter::from_fn(move || loop { if let Some(value) = values.next() { if let Some(range) = &mut current_range { if value == range.end && range.len() < max_len { range.end += 1; continue; } } let prev_range = current_range.clone(); current_range = Some(value..(value + 1)); if prev_range.is_some() { return prev_range; } } else { return current_range.take(); } }) } pub fn char_kind(c: char) -> CharKind { if c.is_whitespace() { CharKind::Whitespace } else if c.is_alphanumeric() || c == '_' { CharKind::Word } else { CharKind::Punctuation } }