diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 08210875b8..ce8b5281f0 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -10,7 +10,7 @@ use crate::{ markdown::parse_markdown, outline::OutlineItem, syntax_map::{ - SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, + SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches, SyntaxSnapshot, ToTreeSitterPoint, }, CodeLabel, LanguageScope, Outline, @@ -42,7 +42,13 @@ use std::{ }; use sum_tree::TreeMap; use text::operation_queue::OperationQueue; -pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, *}; +use text::*; +pub use text::{ + Anchor, Bias, Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Edit, OffsetRangeExt, + OffsetUtf16, Patch, Point, PointUtf16, Rope, RopeFingerprint, Selection, SelectionGoal, + Subscription, TextDimension, TextSummary, ToOffset, ToOffsetUtf16, ToPoint, ToPointUtf16, + Transaction, TransactionId, Unclipped, +}; use theme::SyntaxTheme; #[cfg(any(test, feature = "test-support"))] use util::RandomCharIter; @@ -54,15 +60,22 @@ pub use {tree_sitter_rust, tree_sitter_typescript}; pub use lsp::DiagnosticSeverity; lazy_static! { + /// A label for the background task spawned by the buffer to compute + /// a diff against the contents of its file. pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new(); } +/// Indicate whether a [Buffer] has permissions to edit. #[derive(PartialEq, Clone, Copy, Debug)] pub enum Capability { + /// The buffer is a mutable replica. ReadWrite, + /// The buffer is a read-only replica. ReadOnly, } +/// An in-memory representation of a source code file, including its text, +/// syntax trees, git status, and diagnostics. pub struct Buffer { text: TextBuffer, diff_base: Option, @@ -99,9 +112,11 @@ pub struct Buffer { capability: Capability, } +/// An immutable, cheaply cloneable representation of a fixed +/// state of a buffer. pub struct BufferSnapshot { text: text::BufferSnapshot, - pub git_diff: git::diff::BufferDiff, + git_diff: git::diff::BufferDiff, pub(crate) syntax: SyntaxSnapshot, file: Option>, diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>, @@ -114,25 +129,37 @@ pub struct BufferSnapshot { parse_count: usize, } +/// The kind and amount of indentation in a particular line. For now, +/// assumes that indentation is all the same character. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub struct IndentSize { + /// The number of bytes that comprise the indentation. pub len: u32, + /// The kind of whitespace used for indentation. pub kind: IndentKind, } +/// A whitespace character that's used for indentation. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum IndentKind { + /// An ASCII space character. #[default] Space, + /// An ASCII tab character. Tab, } +/// The shape of a selection cursor. #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] pub enum CursorShape { + /// A vertical bar #[default] Bar, + /// A block that surrounds the following character Block, + /// An underline that runs along the following character Underscore, + /// A box drawn around the following character Hollow, } @@ -144,25 +171,40 @@ struct SelectionSet { lamport_timestamp: clock::Lamport, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct GroupId { - source: Arc, - id: usize, -} - +/// A diagnostic associated with a certain range of a buffer. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Diagnostic { + /// The name of the service that produced this diagnostic. pub source: Option, + /// A machine-readable code that identifies this diagnostic. pub code: Option, + /// Whether this diagnostic is a hint, warning, or error. pub severity: DiagnosticSeverity, + /// The human-readable message associated with this diagnostic. pub message: String, + /// An id that identifies the group to which this diagnostic belongs. + /// + /// When a language server produces a diagnostic with + /// one or more associated diagnostics, those diagnostics are all + /// assigned a single group id. pub group_id: usize, - pub is_valid: bool, + /// Whether this diagnostic is the primary diagnostic for its group. + /// + /// In a given group, the primary diagnostic is the top-level diagnostic + /// returned by the language server. The non-primary diagnostics are the + /// associated diagnostics. pub is_primary: bool, + /// Whether this diagnostic is considered to originate from an analysis of + /// files on disk, as opposed to any unsaved buffer contents. This is a + /// property of a given diagnostic source, and is configured for a given + /// language server via the [LspAdapter::disk_based_diagnostic_sources] method + /// for the language server. pub is_disk_based: bool, + /// Whether this diagnostic marks unnecessary code. pub is_unnecessary: bool, } +/// TODO - move this into the `project` crate and make it private. pub async fn prepare_completion_documentation( documentation: &lsp::Documentation, language_registry: &Arc, @@ -194,77 +236,127 @@ pub async fn prepare_completion_documentation( } } +/// Documentation associated with a [Completion]. #[derive(Clone, Debug)] pub enum Documentation { + /// There is no documentation for this completion. Undocumented, + /// A single line of documentation. SingleLine(String), + /// Multiple lines of plain text documentation. MultiLinePlainText(String), + /// Markdown documentation. MultiLineMarkdown(ParsedMarkdown), } +/// A completion provided by a language server #[derive(Clone, Debug)] pub struct Completion { + /// The range of the buffer that will be replaced. pub old_range: Range, + /// The new text that will be inserted. pub new_text: String, + /// A label for this completion that is shown in the menu. pub label: CodeLabel, + /// The id of the language server that produced this completion. pub server_id: LanguageServerId, + /// The documentation for this completion. pub documentation: Option, + /// The raw completion provided by the language server. pub lsp_completion: lsp::CompletionItem, } +/// A code action provided by a language server. #[derive(Clone, Debug)] pub struct CodeAction { + /// The id of the language server that produced this code action. pub server_id: LanguageServerId, + /// The range of the buffer where this code action is applicable. pub range: Range, + /// The raw code action provided by the language server. pub lsp_action: lsp::CodeAction, } +/// An operation used to synchronize this buffer with its other replicas. #[derive(Clone, Debug, PartialEq)] pub enum Operation { + /// A text operation. Buffer(text::Operation), + /// An update to the buffer's diagnostics. UpdateDiagnostics { + /// The id of the language server that produced the new diagnostics. server_id: LanguageServerId, + /// The diagnostics. diagnostics: Arc<[DiagnosticEntry]>, + /// The buffer's lamport timestamp. lamport_timestamp: clock::Lamport, }, + /// An update to the most recent selections in this buffer. UpdateSelections { + /// The selections. selections: Arc<[Selection]>, + /// The buffer's lamport timestamp. lamport_timestamp: clock::Lamport, + /// Whether the selections are in 'line mode'. line_mode: bool, + /// The [CursorShape] associated with these selections. cursor_shape: CursorShape, }, + /// An update to the characters that should trigger autocompletion + /// for this buffer. UpdateCompletionTriggers { + /// The characters that trigger autocompletion. triggers: Vec, + /// The buffer's lamport timestamp. lamport_timestamp: clock::Lamport, }, } +/// An event that occurs in a buffer. #[derive(Clone, Debug, PartialEq)] pub enum Event { + /// The buffer was changed in a way that must be + /// propagated to its other replicas. Operation(Operation), + /// The buffer was edited. Edited, + /// The buffer's `dirty` bit changed. DirtyChanged, + /// The buffer was saved. Saved, + /// The buffer's file was changed on disk. FileHandleChanged, + /// The buffer was reloaded. Reloaded, + /// The buffer's diff_base changed. DiffBaseChanged, + /// The buffer's language was changed. LanguageChanged, + /// The buffer's syntax trees were updated. Reparsed, + /// The buffer's diagnostics were updated. DiagnosticsUpdated, + /// The buffer gained or lost editing capabilities. CapabilityChanged, + /// The buffer was explicitly requested to close. Closed, } +/// The file associated with a buffer. pub trait File: Send + Sync { + /// Returns the [LocalFile] associated with this file, if the + /// file is local. fn as_local(&self) -> Option<&dyn LocalFile>; + /// Returns whether this file is local. fn is_local(&self) -> bool { self.as_local().is_some() } + /// Returns the file's mtime. fn mtime(&self) -> SystemTime; /// Returns the path of this file relative to the worktree's root directory. @@ -283,19 +375,25 @@ pub trait File: Send + Sync { /// This is needed for looking up project-specific settings. fn worktree_id(&self) -> usize; + /// Returns whether the file has been deleted. fn is_deleted(&self) -> bool; + /// Converts this file into an [Any] trait object. fn as_any(&self) -> &dyn Any; + /// Converts this file into a protobuf message. fn to_proto(&self) -> rpc::proto::File; } +/// The file associated with a buffer, in the case where the file is on the local disk. pub trait LocalFile: File { /// Returns the absolute path of this file. fn abs_path(&self, cx: &AppContext) -> PathBuf; + /// Loads the file's contents from disk. fn load(&self, cx: &AppContext) -> Task>; + /// Called when the buffer is reloaded from disk. fn buffer_reloaded( &self, buffer_id: u64, @@ -307,6 +405,10 @@ pub trait LocalFile: File { ); } +/// The auto-indent behavior associated with an editing operation. +/// For some editing operations, each affected line of text has its +/// indentation recomputed. For other operations, the entire block +/// of edited text is adjusted uniformly. #[derive(Clone, Debug)] pub enum AutoindentMode { /// Indent each line of inserted text. @@ -354,6 +456,8 @@ struct BufferChunkHighlights<'a> { highlight_maps: Vec, } +/// An iterator that yields chunks of a buffer's text, along with their +/// syntax highlights and diagnostic status. pub struct BufferChunks<'a> { range: Range, chunks: text::Chunks<'a>, @@ -366,16 +470,26 @@ pub struct BufferChunks<'a> { highlights: Option>, } +/// A chunk of a buffer's text, along with its syntax highlight and +/// diagnostic status. #[derive(Clone, Copy, Debug, Default)] pub struct Chunk<'a> { + /// The text of the chunk. pub text: &'a str, + /// The syntax highlighting style of the chunk. pub syntax_highlight_id: Option, + /// The highlight style that has been applied to this chunk in + /// the editor. pub highlight_style: Option, + /// The severity of diagnostic associated with this chunk, if any. pub diagnostic_severity: Option, + /// Whether this chunk of text is marked as unnecessary. pub is_unnecessary: bool, + /// Whether this chunk of text was originally a tab character. pub is_tab: bool, } +/// A set of edits to a given version of a buffer, computed asynchronously. pub struct Diff { pub(crate) base_version: clock::Global, line_ending: LineEnding, @@ -390,24 +504,19 @@ pub(crate) struct DiagnosticEndpoint { is_unnecessary: bool, } +/// A class of characters, used for characterizing a run of text. #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] pub enum CharKind { + /// Whitespace. Whitespace, + /// Punctuation. Punctuation, + /// Word. 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 { + /// Create a new buffer with the given base text. pub fn new>(replica_id: ReplicaId, id: u64, base_text: T) -> Self { Self::build( TextBuffer::new(replica_id, id, base_text.into()), @@ -417,6 +526,7 @@ impl Buffer { ) } + /// Create a new buffer that is a replica of a remote buffer. pub fn remote( remote_id: u64, replica_id: ReplicaId, @@ -431,6 +541,8 @@ impl Buffer { ) } + /// Create a new buffer that is a replica of a remote buffer, populating its + /// state from the given protobuf message. pub fn from_proto( replica_id: ReplicaId, capability: Capability, @@ -457,6 +569,7 @@ impl Buffer { Ok(this) } + /// Serialize the buffer's state to a protobuf message. pub fn to_proto(&self) -> proto::BufferState { proto::BufferState { id: self.remote_id(), @@ -470,6 +583,7 @@ impl Buffer { } } + /// Serialize as protobufs all of the changes to the buffer since the given version. pub fn serialize_ops( &self, since: Option, @@ -516,19 +630,23 @@ impl Buffer { }) } + /// Assign a language to the buffer, returning the buffer. pub fn with_language(mut self, language: Arc, cx: &mut ModelContext) -> Self { self.set_language(Some(language), cx); self } + /// Returns the [Capability] of this buffer. pub fn capability(&self) -> Capability { self.capability } + /// Whether this buffer can only be read. pub fn read_only(&self) -> bool { self.capability == Capability::ReadOnly } + /// Builds a [Buffer] with the given underlying [TextBuffer], diff base, [File] and [Capability]. pub fn build( buffer: TextBuffer, diff_base: Option, @@ -573,6 +691,8 @@ impl Buffer { } } + /// Retrieve a snapshot of the buffer's current state. This is computationally + /// cheap, and allows reading from the buffer on a background thread. pub fn snapshot(&self) -> BufferSnapshot { let text = self.text.snapshot(); let mut syntax_map = self.syntax_map.lock(); @@ -595,30 +715,38 @@ impl Buffer { } } - pub fn as_text_snapshot(&self) -> &text::BufferSnapshot { + #[cfg(test)] + pub(crate) fn as_text_snapshot(&self) -> &text::BufferSnapshot { &self.text } + /// Retrieve a snapshot of the buffer's raw text, without any + /// language-related state like the syntax tree or diagnostics. pub fn text_snapshot(&self) -> text::BufferSnapshot { self.text.snapshot() } + /// The file associated with the buffer, if any. pub fn file(&self) -> Option<&Arc> { self.file.as_ref() } + /// The version of the buffer that was last saved or reloaded from disk. pub fn saved_version(&self) -> &clock::Global { &self.saved_version } + /// The fingerprint of the buffer's text when the buffer was last saved or reloaded from disk. pub fn saved_version_fingerprint(&self) -> RopeFingerprint { self.file_fingerprint } + /// The mtime of the buffer's file when the buffer was last saved or reloaded from disk. pub fn saved_mtime(&self) -> SystemTime { self.saved_mtime } + /// Assign a language to the buffer. pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { self.syntax_map.lock().clear(); self.language = language; @@ -626,17 +754,21 @@ impl Buffer { cx.emit(Event::LanguageChanged); } + /// Assign a language registry to the buffer. This allows the buffer to retrieve + /// other languages if parts of the buffer are written in different languages. pub fn set_language_registry(&mut self, language_registry: Arc) { self.syntax_map .lock() .set_language_registry(language_registry); } + /// Assign the buffer a new [Capability]. pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext) { self.capability = capability; cx.emit(Event::CapabilityChanged) } + /// This method is called to signal that the buffer has been saved. pub fn did_save( &mut self, version: clock::Global, @@ -651,6 +783,7 @@ impl Buffer { cx.notify(); } + /// Reloads the contents of the buffer from disk. pub fn reload( &mut self, cx: &mut ModelContext, @@ -699,6 +832,7 @@ impl Buffer { rx } + /// This method is called to signal that the buffer has been reloaded. pub fn did_reload( &mut self, version: clock::Global, @@ -725,6 +859,8 @@ impl Buffer { cx.notify(); } + /// Updates the [File] backing this buffer. This should be called when + /// the file has changed or has been deleted. pub fn file_updated(&mut self, new_file: Arc, cx: &mut ModelContext) { let mut file_changed = false; @@ -762,16 +898,20 @@ impl Buffer { } } + /// Returns the current diff base, see [Buffer::set_diff_base]. pub fn diff_base(&self) -> Option<&str> { self.diff_base.as_deref() } + /// Sets the text that will be used to compute a Git diff + /// against the buffer text. pub fn set_diff_base(&mut self, diff_base: Option, cx: &mut ModelContext) { self.diff_base = diff_base; self.git_diff_recalc(cx); cx.emit(Event::DiffBaseChanged); } + /// Recomputes the Git diff status. pub fn git_diff_recalc(&mut self, cx: &mut ModelContext) -> Option> { let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc let snapshot = self.snapshot(); @@ -792,14 +932,12 @@ impl Buffer { })) } - pub fn close(&mut self, cx: &mut ModelContext) { - cx.emit(Event::Closed); - } - + /// Returns the primary [Language] assigned to this [Buffer]. pub fn language(&self) -> Option<&Arc> { self.language.as_ref() } + /// Returns the [Language] at the given location. pub fn language_at(&self, position: D) -> Option> { let offset = position.to_offset(self); self.syntax_map @@ -810,31 +948,39 @@ impl Buffer { .or_else(|| self.language.clone()) } + /// The number of times the buffer was parsed. pub fn parse_count(&self) -> usize { self.parse_count } + /// The number of times selections were updated. pub fn selections_update_count(&self) -> usize { self.selections_update_count } + /// The number of times diagnostics were updated. pub fn diagnostics_update_count(&self) -> usize { self.diagnostics_update_count } + /// The number of times the underlying file was updated. pub fn file_update_count(&self) -> usize { self.file_update_count } + /// The number of times the git diff status was updated. pub fn git_diff_update_count(&self) -> usize { self.git_diff_update_count } + /// Whether the buffer is being parsed in the background. #[cfg(any(test, feature = "test-support"))] pub fn is_parsing(&self) -> bool { self.parsing_in_background } + /// Indicates whether the buffer contains any regions that may be + /// written in a language that hasn't been loaded yet. pub fn contains_unknown_injections(&self) -> bool { self.syntax_map.lock().contains_unknown_injections() } @@ -941,6 +1087,7 @@ impl Buffer { cx.notify(); } + /// Assign to the buffer a set of diagnostics created by a given language server. pub fn update_diagnostics( &mut self, server_id: LanguageServerId, @@ -1170,9 +1317,9 @@ impl Buffer { 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`. + /// 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, @@ -1207,6 +1354,8 @@ impl Buffer { } } + /// Spawns a background task that asynchronously computes a `Diff` between the buffer's text + /// and the given new text. pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task { let old_text = self.as_rope().clone(); let base_version = self.version(); @@ -1278,7 +1427,7 @@ impl Buffer { }) } - /// Spawn a background task that searches the buffer for any whitespace + /// Spawns a background task that searches the buffer for any whitespace /// at the ends of a lines, and returns a `Diff` that removes that whitespace. pub fn remove_trailing_whitespace(&self, cx: &AppContext) -> Task { let old_text = self.as_rope().clone(); @@ -1298,7 +1447,7 @@ impl Buffer { }) } - /// Ensure that the buffer ends with a single newline character, and + /// Ensures that the buffer ends with a single newline character, and /// no other whitespace. pub fn ensure_final_newline(&mut self, cx: &mut ModelContext) { let len = self.len(); @@ -1319,7 +1468,7 @@ impl Buffer { self.edit([(offset..len, "\n")], None, cx); } - /// Apply a diff to the buffer. If the buffer has changed since the given diff was + /// Applies a diff to the buffer. If the buffer has changed since the given diff was /// calculated, then adjust the diff to account for those changes, and discard any /// parts of the diff that conflict with those changes. pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext) -> Option { @@ -1358,11 +1507,14 @@ impl Buffer { self.end_transaction(cx) } + /// Checks if the buffer has unsaved changes. pub fn is_dirty(&self) -> bool { self.file_fingerprint != self.as_rope().fingerprint() || self.file.as_ref().map_or(false, |file| file.is_deleted()) } + /// Checks if the buffer and its file have both changed since the buffer + /// was last saved or reloaded. pub fn has_conflict(&self) -> bool { self.file_fingerprint != self.as_rope().fingerprint() && self @@ -1371,14 +1523,23 @@ impl Buffer { .map_or(false, |file| file.mtime() > self.saved_mtime) } + /// Gets a [`Subscription`] that tracks all of the changes to the buffer's text. pub fn subscribe(&mut self) -> Subscription { self.text.subscribe() } + /// Starts a transaction, if one is not already in-progress. When undoing or + /// redoing edits, all of the edits performed within a transaction are undone + /// or redone together. pub fn start_transaction(&mut self) -> Option { self.start_transaction_at(Instant::now()) } + /// Starts a transaction, providing the current time. Subsequent transactions + /// that occur within a short period of time will be grouped together. This + /// is controlled by the buffer's undo grouping duration. + /// + /// See [`Buffer::set_group_interval`]. pub fn start_transaction_at(&mut self, now: Instant) -> Option { self.transaction_depth += 1; if self.was_dirty_before_starting_transaction.is_none() { @@ -1387,10 +1548,16 @@ impl Buffer { self.text.start_transaction_at(now) } + /// Terminates the current transaction, if this is the outermost transaction. pub fn end_transaction(&mut self, cx: &mut ModelContext) -> Option { self.end_transaction_at(Instant::now(), cx) } + /// Terminates the current transaction, providing the current time. Subsequent transactions + /// that occur within a short period of time will be grouped together. This + /// is controlled by the buffer's undo grouping duration. + /// + /// See [`Buffer::set_group_interval`]. pub fn end_transaction_at( &mut self, now: Instant, @@ -1411,26 +1578,33 @@ impl Buffer { } } + /// Manually add a transaction to the buffer's undo history. pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) { self.text.push_transaction(transaction, now); } + /// Prevent the last transaction from being grouped with any subsequent transactions, + /// even if they occur with the buffer's undo grouping duration. pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> { self.text.finalize_last_transaction() } + /// Manually group all changes since a given transaction. pub fn group_until_transaction(&mut self, transaction_id: TransactionId) { self.text.group_until_transaction(transaction_id); } + /// Manually remove a transaction from the buffer's undo history pub fn forget_transaction(&mut self, transaction_id: TransactionId) { self.text.forget_transaction(transaction_id); } + /// Manually merge two adjacent transactions in the buffer's undo history. pub fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { self.text.merge_transactions(transaction, destination); } + /// Waits for the buffer to receive operations with the given timestamps. pub fn wait_for_edits( &mut self, edit_ids: impl IntoIterator, @@ -1438,6 +1612,7 @@ impl Buffer { self.text.wait_for_edits(edit_ids) } + /// Waits for the buffer to receive the operations necessary for resolving the given anchors. pub fn wait_for_anchors( &mut self, anchors: impl IntoIterator, @@ -1445,14 +1620,18 @@ impl Buffer { self.text.wait_for_anchors(anchors) } + /// Waits for the buffer to receive operations up to the given version. pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future> { self.text.wait_for_version(version) } + /// Forces all futures returned by [`Buffer::wait_for_version`], [`Buffer::wait_for_edits`], or + /// [`Buffer::wait_for_version`] to resolve with an error. pub fn give_up_waiting(&mut self) { self.text.give_up_waiting(); } + /// Stores a set of selections that should be broadcasted to all of the buffer's replicas. pub fn set_active_selections( &mut self, selections: Arc<[Selection]>, @@ -1481,6 +1660,8 @@ impl Buffer { ); } + /// Clears the selections, so that other replicas of the buffer do not see any selections for + /// this replica. pub fn remove_active_selections(&mut self, cx: &mut ModelContext) { if self .remote_selections @@ -1491,6 +1672,7 @@ impl Buffer { } } + /// Replaces the buffer's entire text. pub fn set_text(&mut self, text: T, cx: &mut ModelContext) -> Option where T: Into>, @@ -1499,6 +1681,15 @@ impl Buffer { self.edit([(0..self.len(), text)], None, cx) } + /// Applies the given edits to the buffer. Each edit is specified as a range of text to + /// delete, and a string of text to insert at that location. + /// + /// If an [`AutoindentMode`] is provided, then the buffer will enqueue an auto-indent + /// request for the edited ranges, which will be processed when the buffer finishes + /// parsing. + /// + /// Parsing takes place at the end of a transaction, and may compute synchronously + /// or asynchronously, depending on the changes. pub fn edit( &mut self, edits_iter: I, @@ -1632,6 +1823,7 @@ impl Buffer { cx.notify(); } + /// Applies the given remote operations to the buffer. pub fn apply_ops>( &mut self, ops: I, @@ -1779,11 +1971,13 @@ impl Buffer { cx.emit(Event::Operation(operation)); } + /// Removes the selections for a given peer. pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext) { self.remote_selections.remove(&replica_id); cx.notify(); } + /// Undoes the most recent transaction. pub fn undo(&mut self, cx: &mut ModelContext) -> Option { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); @@ -1797,6 +1991,7 @@ impl Buffer { } } + /// Manually undoes a specific transaction in the buffer's undo history. pub fn undo_transaction( &mut self, transaction_id: TransactionId, @@ -1813,6 +2008,7 @@ impl Buffer { } } + /// Manually undoes all changes after a given transaction in the buffer's undo history. pub fn undo_to_transaction( &mut self, transaction_id: TransactionId, @@ -1832,6 +2028,7 @@ impl Buffer { undone } + /// Manually redoes a specific transaction in the buffer's redo history. pub fn redo(&mut self, cx: &mut ModelContext) -> Option { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); @@ -1845,6 +2042,7 @@ impl Buffer { } } + /// Manually undoes all changes until a given transaction in the buffer's redo history. pub fn redo_to_transaction( &mut self, transaction_id: TransactionId, @@ -1864,6 +2062,7 @@ impl Buffer { redone } + /// Override current completion triggers with the user-provided completion triggers. 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(); @@ -1877,11 +2076,14 @@ impl Buffer { cx.notify(); } + /// Returns a list of strings which trigger a completion menu for this language. + /// Usually this is driven by LSP server which returns a list of trigger characters for completions. pub fn completion_triggers(&self) -> &[String] { &self.completion_triggers } } +#[doc(hidden)] #[cfg(any(test, feature = "test-support"))] impl Buffer { pub fn edit_via_marked_text( @@ -1954,10 +2156,12 @@ impl Deref for Buffer { } impl BufferSnapshot { + /// Returns [`IndentSize`] for a given line that respects user settings and /// language preferences. pub fn indent_size_for_line(&self, row: u32) -> IndentSize { indent_size_for_line(self, row) } - + /// Returns [`IndentSize`] for a given position that respects user settings + /// and language preferences. pub fn language_indent_size_at(&self, position: T, cx: &AppContext) -> IndentSize { let settings = language_settings(self.language_at(position), self.file(), cx); if settings.hard_tabs { @@ -1967,6 +2171,8 @@ impl BufferSnapshot { } } + /// Retrieve the suggested indent size for all of the given rows. The unit of indentation + /// is passed in as `single_indent_size`. pub fn suggested_indents( &self, rows: impl Iterator, @@ -2213,6 +2419,10 @@ impl BufferSnapshot { None } + /// Iterates over chunks of text in the given range of the buffer. Text is chunked + /// in an arbitrary way due to being stored in a [`rope::Rope`]. The text is also + /// returned in chunks where each chunk has a single syntax highlighting style and + /// diagnostic status. pub fn chunks(&self, range: Range, language_aware: bool) -> BufferChunks { let range = range.start.to_offset(self)..range.end.to_offset(self); @@ -2249,7 +2459,9 @@ impl BufferSnapshot { BufferChunks::new(self.text.as_rope(), range, syntax, diagnostic_endpoints) } - pub fn for_each_line(&self, range: Range, mut callback: impl FnMut(u32, &str)) { + /// Invokes the given callback for each line of text in the given range of the buffer. + /// Uses callback to avoid allocating a string for each line. + 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 @@ -2268,11 +2480,12 @@ impl BufferSnapshot { } } - pub fn syntax_layers(&self) -> impl Iterator + '_ { + /// Iterates over every [`SyntaxLayer`] in the buffer. + pub fn syntax_layers(&self) -> impl Iterator + '_ { self.syntax.layers_for_range(0..self.len(), &self.text) } - pub fn syntax_layer_at(&self, position: D) -> Option { + fn syntax_layer_at(&self, position: D) -> Option { let offset = position.to_offset(self); self.syntax .layers_for_range(offset..offset, &self.text) @@ -2280,12 +2493,14 @@ impl BufferSnapshot { .last() } + /// Returns the [Language] at the given location. pub fn language_at(&self, position: D) -> Option<&Arc> { self.syntax_layer_at(position) .map(|info| info.language) .or(self.language.as_ref()) } + /// Returns the settings for the language at the given location. pub fn settings_at<'a, D: ToOffset>( &self, position: D, @@ -2294,6 +2509,7 @@ impl BufferSnapshot { language_settings(self.language_at(position), self.file.as_ref(), cx) } + /// Returns the [LanguageScope] at the given location. pub fn language_scope_at(&self, position: D) -> Option { let offset = position.to_offset(self); let mut scope = None; @@ -2338,6 +2554,8 @@ impl BufferSnapshot { }) } + /// Returns a tuple of the range and character kind of the word + /// surrounding the given position. pub fn surrounding_word(&self, start: T) -> (Range, Option) { let mut start = start.to_offset(self); let mut end = start; @@ -2370,6 +2588,7 @@ impl BufferSnapshot { (start..end, word_kind) } + /// Returns the range for the closes syntax node enclosing the given range. 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; @@ -2438,11 +2657,19 @@ impl BufferSnapshot { result } + /// Returns the outline for the buffer. + /// + /// This method allows passing an optional [SyntaxTheme] to + /// syntax-highlight the returned symbols. pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option> { self.outline_items_containing(0..self.len(), true, theme) .map(Outline::new) } + /// Returns all the symbols that contain the given position. + /// + /// This method allows passing an optional [SyntaxTheme] to + /// syntax-highlight the returned symbols. pub fn symbols_containing( &self, position: T, @@ -2594,6 +2821,8 @@ impl BufferSnapshot { Some(items) } + /// For each grammar in the language, runs the provided + /// [tree_sitter::Query] against the given range. pub fn matches( &self, range: Range, @@ -2650,6 +2879,7 @@ impl BufferSnapshot { }) } + /// Returns selections for remote peers intersecting the given range. #[allow(clippy::type_complexity)] pub fn remote_selections_in_range( &self, @@ -2688,6 +2918,13 @@ impl BufferSnapshot { }) } + /// Whether the buffer contains any git changes. + pub fn has_git_diff(&self) -> bool { + !self.git_diff.is_empty() + } + + /// Returns all the Git diff hunks intersecting the given + /// row range. pub fn git_diff_hunks_in_row_range<'a>( &'a self, range: Range, @@ -2695,6 +2932,8 @@ impl BufferSnapshot { self.git_diff.hunks_in_row_range(range, self) } + /// Returns all the Git diff hunks intersecting the given + /// range. pub fn git_diff_hunks_intersecting_range<'a>( &'a self, range: Range, @@ -2702,6 +2941,8 @@ impl BufferSnapshot { self.git_diff.hunks_intersecting_range(range, self) } + /// Returns all the Git diff hunks intersecting the given + /// range, in reverse order. pub fn git_diff_hunks_intersecting_range_rev<'a>( &'a self, range: Range, @@ -2709,6 +2950,7 @@ impl BufferSnapshot { self.git_diff.hunks_intersecting_range_rev(range, self) } + /// Returns all the diagnostics intersecting the given range. pub fn diagnostics_in_range<'a, T, O>( &'a self, search_range: Range, @@ -2738,6 +2980,9 @@ impl BufferSnapshot { }) } + /// Returns all the diagnostic groups associated with the given + /// language server id. If no language server id is provided, + /// all diagnostics groups are returned. pub fn diagnostic_groups( &self, language_server_id: Option, @@ -2768,6 +3013,7 @@ impl BufferSnapshot { groups } + /// Returns an iterator over the diagnostics for the given group. pub fn diagnostic_group<'a, O>( &'a self, group_id: usize, @@ -2780,22 +3026,27 @@ impl BufferSnapshot { .flat_map(move |(_, set)| set.group(group_id, self)) } + /// The number of times diagnostics were updated. pub fn diagnostics_update_count(&self) -> usize { self.diagnostics_update_count } + /// The number of times the buffer was parsed. pub fn parse_count(&self) -> usize { self.parse_count } + /// The number of times selections were updated. pub fn selections_update_count(&self) -> usize { self.selections_update_count } + /// Returns a snapshot of underlying file. pub fn file(&self) -> Option<&Arc> { self.file.as_ref() } + /// Resolves the file path (relative to the worktree root) associated with the underlying file. 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 { @@ -2808,10 +3059,12 @@ impl BufferSnapshot { } } + /// The number of times the underlying file was updated. pub fn file_update_count(&self) -> usize { self.file_update_count } + /// The number of times the git diff status was updated. pub fn git_diff_update_count(&self) -> usize { self.git_diff_update_count } @@ -2821,7 +3074,7 @@ 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 { +fn indent_size_for_text(text: impl Iterator) -> IndentSize { let mut result = IndentSize::spaces(0); for c in text { let kind = match c { @@ -2899,6 +3152,7 @@ impl<'a> BufferChunks<'a> { } } + /// Seeks to the given byte offset in the buffer. pub fn seek(&mut self, offset: usize) { self.range.start = offset; self.chunks.seek(self.range.start); @@ -2922,6 +3176,7 @@ impl<'a> BufferChunks<'a> { } } + /// The current byte offset in the buffer. pub fn offset(&self) -> usize { self.range.start } @@ -3074,7 +3329,6 @@ impl Default for Diagnostic { message: Default::default(), group_id: 0, is_primary: false, - is_valid: true, is_disk_based: false, is_unnecessary: false, } @@ -3082,6 +3336,7 @@ impl Default for Diagnostic { } impl IndentSize { + /// Returns an [IndentSize] representing the given spaces. pub fn spaces(len: u32) -> Self { Self { len, @@ -3089,6 +3344,7 @@ impl IndentSize { } } + /// Returns an [IndentSize] representing a tab. pub fn tab() -> Self { Self { len: 1, @@ -3096,10 +3352,12 @@ impl IndentSize { } } + /// An iterator over the characters represented by this [IndentSize]. pub fn chars(&self) -> impl Iterator { iter::repeat(self.char()).take(self.len as usize) } + /// The character representation of this [IndentSize]. pub fn char(&self) -> char { match self.kind { IndentKind::Space => ' ', @@ -3107,6 +3365,8 @@ impl IndentSize { } } + /// Consumes the current [IndentSize] and returns a new one that has + /// been shrunk or enlarged by the given size along the given direction. pub fn with_delta(mut self, direction: Ordering, size: IndentSize) -> Self { match direction { Ordering::Less => { @@ -3128,6 +3388,8 @@ impl IndentSize { } impl Completion { + /// A key that can be used to sort completions when displaying + /// them to the user. pub fn sort_key(&self) -> (usize, &str) { let kind_key = match self.lsp_completion.kind { Some(lsp::CompletionItemKind::VARIABLE) => 0, @@ -3136,12 +3398,13 @@ impl Completion { (kind_key, &self.label.text[self.label.filter_range.clone()]) } + /// Whether this completion is a snippet. pub fn is_snippet(&self) -> bool { self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET) } } -pub fn contiguous_ranges( +pub(crate) fn contiguous_ranges( values: impl Iterator, max_len: usize, ) -> impl Iterator> { @@ -3167,6 +3430,9 @@ pub fn contiguous_ranges( }) } +/// Returns the [CharKind] for the given character. When a scope is provided, +/// the function checks if the character is considered a word character +/// based on the language scope's word character settings. pub fn char_kind(scope: &Option, c: char) -> CharKind { if c.is_whitespace() { return CharKind::Whitespace; diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index f269fce88d..48e3bd78c7 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -9,20 +9,36 @@ use std::{ use sum_tree::{self, Bias, SumTree}; use text::{Anchor, FromAnchor, PointUtf16, ToOffset}; +/// A set of diagnostics associated with a given buffer, provided +/// by a single language server. +/// +/// The diagnostics are stored in a [SumTree], which allows this struct +/// to be cheaply copied, and allows for efficient retrieval of the +/// diagnostics that intersect a given range of the buffer. #[derive(Clone, Debug, Default)] pub struct DiagnosticSet { diagnostics: SumTree>, } +/// A single diagnostic in a set. Generic over its range type, because +/// the diagnostics are stored internally as [Anchor]s, but can be +/// resolved to different coordinates types like [usize] byte offsets or +/// [Point]s. #[derive(Clone, Debug, PartialEq, Eq)] pub struct DiagnosticEntry { + /// The range of the buffer where the diagnostic applies. pub range: Range, + /// The information about the diagnostic. pub diagnostic: Diagnostic, } +/// A group of related diagnostics, ordered by their start position +/// in the buffer. #[derive(Debug)] pub struct DiagnosticGroup { + /// The diagnostics. pub entries: Vec>, + /// The index into `entries` where the primary diagnostic is stored. pub primary_ix: usize, } @@ -36,7 +52,8 @@ pub struct Summary { } impl DiagnosticEntry { - // Used to provide diagnostic context to lsp codeAction request + /// Returns a raw LSP diagnostic ssed to provide diagnostic context to lsp + /// codeAction request pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic { let code = self .diagnostic @@ -53,6 +70,8 @@ impl DiagnosticEntry { } impl DiagnosticSet { + /// Constructs a [DiagnosticSet] from a sequence of entries, ordered by + /// their position in the buffer. pub fn from_sorted_entries(iter: I, buffer: &text::BufferSnapshot) -> Self where I: IntoIterator>, @@ -62,6 +81,7 @@ impl DiagnosticSet { } } + /// Constructs a [DiagnosticSet] from a sequence of entries in an arbitrary order. pub fn new(iter: I, buffer: &text::BufferSnapshot) -> Self where I: IntoIterator>, @@ -80,14 +100,18 @@ impl DiagnosticSet { } } + /// Returns the number of diagnostics in the set. pub fn len(&self) -> usize { self.diagnostics.summary().count } + /// Returns an iterator over the diagnostic entries in the set. pub fn iter(&self) -> impl Iterator> { self.diagnostics.iter() } + /// Returns an iterator over the diagnostic entries that intersect the + /// given range of the buffer. pub fn range<'a, T, O>( &'a self, range: Range, @@ -134,6 +158,7 @@ impl DiagnosticSet { }) } + /// Adds all of this set's diagnostic groups to the given output vector. pub fn groups( &self, language_server_id: LanguageServerId, @@ -173,6 +198,8 @@ impl DiagnosticSet { }); } + /// Returns all of the diagnostics in a particular diagnostic group, + /// in order of their position in the buffer. pub fn group<'a, O: FromAnchor>( &'a self, group_id: usize, @@ -183,6 +210,7 @@ impl DiagnosticSet { .map(|entry| entry.resolve(buffer)) } } + impl sum_tree::Item for DiagnosticEntry { type Summary = Summary; @@ -198,6 +226,7 @@ impl sum_tree::Item for DiagnosticEntry { } impl DiagnosticEntry { + /// Converts the [DiagnosticEntry] to a different buffer coordinate type. pub fn resolve(&self, buffer: &text::BufferSnapshot) -> DiagnosticEntry { DiagnosticEntry { range: O::from_anchor(&self.range.start, buffer) diff --git a/crates/language/src/highlight_map.rs b/crates/language/src/highlight_map.rs index 270ac259c9..8829eb94ac 100644 --- a/crates/language/src/highlight_map.rs +++ b/crates/language/src/highlight_map.rs @@ -11,7 +11,7 @@ pub struct HighlightId(pub u32); const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); impl HighlightMap { - pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { + pub(crate) fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self { // For each capture name in the highlight query, find the longest // key in the theme's syntax styles that matches all of the // dot-separated components of the capture name. @@ -51,7 +51,7 @@ impl HighlightMap { } impl HighlightId { - pub fn is_default(&self) -> bool { + pub(crate) fn is_default(&self) -> bool { *self == DEFAULT_SYNTAX_HIGHLIGHT_ID } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 57b76eddad..7d44250a0f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1,3 +1,11 @@ +//! The `language` crate provides a large chunk of Zed's language-related +//! features (the other big contributors being project and lsp crates that revolve around LSP features). +//! Namely, this crate: +//! - Provides [`Language`], [`Grammar`] and [`LanguageRegistry`] types that +//! use Tree-sitter to provide syntax highlighting to the editor; note though that `language` doesn't perform the highlighting by itself. It only maps ranges in a buffer to colors. Treesitter is also used for buffer outlines (lists of symbols in a buffer) +//! - Exposes [`LanguageConfig`] that describes how constructs (like brackets or line comments) should be handled by the editor for a source file of a particular language. +//! +//! Notably we do *not* assign a single language to a single file; in real world a single file can consist of multiple programming languages - HTML is a good example of that - and `language` crate tends to reflect that status quo in it's API. mod buffer; mod diagnostic_set; mod highlight_map; @@ -54,10 +62,13 @@ pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; pub use lsp::LanguageServerId; pub use outline::{Outline, OutlineItem}; -pub use syntax_map::{OwnedSyntaxLayerInfo, SyntaxLayerInfo}; +pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer}; pub use text::LineEnding; pub use tree_sitter::{Parser, Tree}; +/// Initializes the `language` crate. +/// +/// This should be called before making use of items from the create. pub fn init(cx: &mut AppContext) { language_settings::init(cx); } @@ -90,7 +101,9 @@ thread_local! { } lazy_static! { - pub static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default(); + pub(crate) static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default(); + /// A shared grammar for plain text, exposed for reuse by downstream crates. + #[doc(hidden)] pub static ref PLAIN_TEXT: Arc = Arc::new(Language::new( LanguageConfig { name: "Plain Text".into(), @@ -100,10 +113,14 @@ lazy_static! { )); } +/// Types that represent a position in a buffer, and can be converted into +/// an LSP position, to send to a language server. pub trait ToLspPosition { + /// Converts the value into an LSP position. fn to_lsp_position(self) -> lsp::Position; } +/// A name of a language server. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); @@ -239,6 +256,8 @@ impl CachedLspAdapter { } } +/// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application +// e.g. to display a notification or fetch data from the web. pub trait LspAdapterDelegate: Send + Sync { fn show_notification(&self, message: &str, cx: &mut AppContext); fn http_client(&self) -> Arc; @@ -284,6 +303,10 @@ pub trait LspAdapter: 'static + Send + Sync { delegate: &dyn LspAdapterDelegate, ) -> Option; + /// Returns true if a language server can be reinstalled. + /// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is false. + /// Implementations that rely on software already installed on user's system + /// should have [`can_be_reinstalled`] return false. fn can_be_reinstalled(&self) -> bool { true } @@ -295,6 +318,9 @@ pub trait LspAdapter: 'static + Send + Sync { fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} + /// A callback called for each [`lsp_types::CompletionItem`] obtained from LSP server. + /// Some LspAdapter implementations might want to modify the obtained item to + /// change how it's displayed. async fn process_completion(&self, _: &mut lsp::CompletionItem) {} async fn label_for_completion( @@ -314,6 +340,7 @@ pub trait LspAdapter: 'static + Send + Sync { None } + /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp_types::InitializeParams`] async fn initialization_options(&self) -> Option { None } @@ -322,6 +349,7 @@ pub trait LspAdapter: 'static + Send + Sync { futures::future::ready(serde_json::json!({})).boxed() } + /// Returns a list of code actions supported by a given LspAdapter fn code_action_kinds(&self) -> Option> { Some(vec![ CodeActionKind::EMPTY, @@ -358,36 +386,59 @@ pub struct CodeLabel { #[derive(Clone, Deserialize)] pub struct LanguageConfig { + /// Human-readable name of the language. pub name: Arc, + // The name of the grammar in a WASM bundle (experimental). pub grammar_name: Option>, + /// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`. pub path_suffixes: Vec, + /// List of bracket types in a language. pub brackets: BracketPairConfig, + /// A regex pattern that determines whether the language should be assigned to a file or not. #[serde(default, deserialize_with = "deserialize_regex")] pub first_line_pattern: Option, + /// If set to true, auto indentation uses last non empty line to determine + /// the indentation level for a new line. #[serde(default = "auto_indent_using_last_non_empty_line_default")] pub auto_indent_using_last_non_empty_line: bool, + /// A regex that is used to determine whether the indentation level should be + /// increased in the following line. #[serde(default, deserialize_with = "deserialize_regex")] pub increase_indent_pattern: Option, + /// A regex that is used to determine whether the indentation level should be + /// decreased in the following line. #[serde(default, deserialize_with = "deserialize_regex")] pub decrease_indent_pattern: Option, + /// A list of characters that trigger the automatic insertion of a closing + /// bracket when they immediately precede the point where an opening + /// bracket is inserted. #[serde(default)] pub autoclose_before: String, - #[serde(default)] - pub line_comment: Option>, + /// A placeholder used internally by Semantic Index. #[serde(default)] pub collapsed_placeholder: String, + /// A line comment string that is inserted in e.g. `toggle comments` action. + #[serde(default)] + pub line_comment: Option>, + /// Starting and closing characters of a block comment. #[serde(default)] pub block_comment: Option<(Arc, Arc)>, + /// A list of language servers that are allowed to run on subranges of a given language. #[serde(default)] pub scope_opt_in_language_servers: Vec, #[serde(default)] pub overrides: HashMap, + /// A list of characters that Zed should treat as word characters for the + /// purpose of features that operate on word boundaries, like 'move to next word end' + /// or a whole-word search in buffer search. #[serde(default)] pub word_characters: HashSet, + /// The name of a Prettier parser that should be used for this language. #[serde(default)] pub prettier_parser_name: Option, } +/// Tree-sitter language queries for a given language. #[derive(Debug, Default)] pub struct LanguageQueries { pub highlights: Option>, @@ -399,6 +450,9 @@ pub struct LanguageQueries { pub overrides: Option>, } +/// Represents a language for the given range. Some languages (e.g. HTML) +/// interleave several languages together, thus a single buffer might actually contain +/// several nested scopes. #[derive(Clone, Debug)] pub struct LanguageScope { language: Arc, @@ -458,9 +512,9 @@ impl Default for LanguageConfig { block_comment: Default::default(), scope_opt_in_language_servers: Default::default(), overrides: Default::default(), - collapsed_placeholder: Default::default(), word_characters: Default::default(), prettier_parser_name: None, + collapsed_placeholder: Default::default(), } } } @@ -478,6 +532,7 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result, D } } +#[doc(hidden)] #[cfg(any(test, feature = "test-support"))] pub struct FakeLspAdapter { pub name: &'static str, @@ -489,9 +544,16 @@ pub struct FakeLspAdapter { pub prettier_plugins: Vec<&'static str>, } +/// Configuration of handling bracket pairs for a given language. +/// +/// This struct includes settings for defining which pairs of characters are considered brackets and +/// also specifies any language-specific scopes where these pairs should be ignored for bracket matching purposes. #[derive(Clone, Debug, Default)] pub struct BracketPairConfig { + /// A list of character pairs that should be treated as brackets in the context of a given language. pub pairs: Vec, + /// A list of tree-sitter scopes for which a given bracket should not be active. + /// N-th entry in `[Self::disabled_scopes_by_bracket_ix]` contains a list of disabled scopes for an n-th entry in `[Self::pairs]` pub disabled_scopes_by_bracket_ix: Vec>, } @@ -523,11 +585,18 @@ impl<'de> Deserialize<'de> for BracketPairConfig { } } +/// Describes a single bracket pair and how an editor should react to e.g. inserting +/// an opening bracket or to a newline character insertion inbetween `start` and `end` characters. #[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct BracketPair { + /// Starting substring for a bracket. pub start: String, + /// Ending substring for a bracket. pub end: String, + /// True if `end` should be automatically inserted right after `start` characters. pub close: bool, + /// True if an extra newline should be inserted while the cursor is in the middle + /// of that bracket pair. pub newline: bool, } @@ -1641,6 +1710,8 @@ impl LanguageScope { self.language.config.collapsed_placeholder.as_ref() } + /// Returns line prefix that is inserted in e.g. line continuations or + /// in `toggle comments` action. pub fn line_comment_prefix(&self) -> Option<&Arc> { Override::as_option( self.config_override().map(|o| &o.line_comment), @@ -1656,6 +1727,11 @@ impl LanguageScope { .map(|e| (&e.0, &e.1)) } + /// Returns a list of language-specific word characters. + /// + /// By default, Zed treats alphanumeric characters (and '_') as word characters for + /// the purpose of actions like 'move to next word end` or whole-word search. + /// It additionally accounts for language's additional word characters. pub fn word_characters(&self) -> Option<&HashSet> { Override::as_option( self.config_override().map(|o| &o.word_characters), @@ -1663,6 +1739,8 @@ impl LanguageScope { ) } + /// Returns a list of bracket pairs for a given language with an additional + /// piece of information about whether the particular bracket pair is currently active for a given language. pub fn brackets(&self) -> impl Iterator { let mut disabled_ids = self .config_override() diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 5359d184d6..292e2ad9dc 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,3 +1,5 @@ +//! Provides `language`-related settings. + use crate::{File, Language}; use anyhow::Result; use collections::{HashMap, HashSet}; @@ -11,10 +13,12 @@ use serde::{Deserialize, Serialize}; use settings::Settings; use std::{num::NonZeroU32, path::Path, sync::Arc}; +/// Initializes the language settings. pub fn init(cx: &mut AppContext) { AllLanguageSettings::register(cx); } +/// Returns the settings for the specified language from the provided file. pub fn language_settings<'a>( language: Option<&Arc>, file: Option<&Arc>, @@ -24,6 +28,7 @@ pub fn language_settings<'a>( all_language_settings(file, cx).language(language_name.as_deref()) } +/// Returns the settings for all languages from the provided file. pub fn all_language_settings<'a>( file: Option<&Arc>, cx: &'a AppContext, @@ -32,51 +37,89 @@ pub fn all_language_settings<'a>( AllLanguageSettings::get(location, cx) } +/// The settings for all languages. #[derive(Debug, Clone)] pub struct AllLanguageSettings { + /// The settings for GitHub Copilot. pub copilot: CopilotSettings, defaults: LanguageSettings, languages: HashMap, LanguageSettings>, } +/// The settings for a particular language. #[derive(Debug, Clone, Deserialize)] pub struct LanguageSettings { + /// How many columns a tab should occupy. pub tab_size: NonZeroU32, + /// Whether to indent lines using tab characters, as opposed to multiple + /// spaces. pub hard_tabs: bool, + /// How to soft-wrap long lines of text. pub soft_wrap: SoftWrap, + /// The column at which to soft-wrap lines, for buffers where soft-wrap + /// is enabled. pub preferred_line_length: u32, + /// Whether to show wrap guides in the editor. Setting this to true will + /// show a guide at the 'preferred_line_length' value if softwrap is set to + /// 'preferred_line_length', and will show any additional guides as specified + /// by the 'wrap_guides' setting. pub show_wrap_guides: bool, + /// Character counts at which to show wrap guides in the editor. pub wrap_guides: Vec, + /// Whether or not to perform a buffer format before saving. pub format_on_save: FormatOnSave, + /// Whether or not to remove any trailing whitespace from lines of a buffer + /// before saving it. pub remove_trailing_whitespace_on_save: bool, + /// Whether or not to ensure there's a single newline at the end of a buffer + /// when saving it. pub ensure_final_newline_on_save: bool, + /// How to perform a buffer format. pub formatter: Formatter, + /// Zed's Prettier integration settings. + /// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if + /// the project has no other Prettier installed. pub prettier: HashMap, + /// Whether to use language servers to provide code intelligence. pub enable_language_server: bool, + /// Controls whether Copilot provides suggestion immediately (true) + /// or waits for a `copilot::Toggle` (false). pub show_copilot_suggestions: bool, + /// Whether to show tabs and spaces in the editor. pub show_whitespaces: ShowWhitespaceSetting, + /// Whether to start a new line with a comment when a previous line is a comment as well. pub extend_comment_on_newline: bool, + /// Inlay hint related settings. pub inlay_hints: InlayHintSettings, } +/// The settings for [GitHub Copilot](https://github.com/features/copilot). #[derive(Clone, Debug, Default)] pub struct CopilotSettings { + /// Whether Copilot is enabled. pub feature_enabled: bool, + /// A list of globs representing files that Copilot should be disabled for. pub disabled_globs: Vec, } +/// The settings for all languages. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct AllLanguageSettingsContent { + /// The settings for enabling/disabling features. #[serde(default)] pub features: Option, + /// The settings for GitHub Copilot. #[serde(default)] pub copilot: Option, + /// The default language settings. #[serde(flatten)] pub defaults: LanguageSettingsContent, + /// The settings for individual languages. #[serde(default, alias = "language_overrides")] pub languages: HashMap, LanguageSettingsContent>, } +/// The settings for a particular language. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct LanguageSettingsContent { /// How many columns a tab should occupy. @@ -138,7 +181,7 @@ pub struct LanguageSettingsContent { pub formatter: Option, /// Zed's Prettier integration settings. /// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if - /// project has no other Prettier installed. + /// the project has no other Prettier installed. /// /// Default: {} #[serde(default)] @@ -148,7 +191,7 @@ pub struct LanguageSettingsContent { /// Default: true #[serde(default)] pub enable_language_server: Option, - /// Controls whether copilot provides suggestion immediately (true) + /// Controls whether Copilot provides suggestion immediately (true) /// or waits for a `copilot::Toggle` (false). /// /// Default: true @@ -167,18 +210,23 @@ pub struct LanguageSettingsContent { pub inlay_hints: Option, } +/// The contents of the GitHub Copilot settings. #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct CopilotSettingsContent { + /// A list of globs representing files that Copilot should be disabled for. #[serde(default)] pub disabled_globs: Option>, } +/// The settings for enabling/disabling features. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct FeaturesContent { + /// Whether the GitHub Copilot feature is enabled. pub copilot: Option, } +/// Controls the soft-wrapping behavior in the editor. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum SoftWrap { @@ -190,29 +238,38 @@ pub enum SoftWrap { PreferredLineLength, } +/// Controls the behavior of formatting files when they are saved. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum FormatOnSave { + /// Files should be formatted on save. On, + /// Files should not be formatted on save. Off, + /// Files should be formatted using the current language server. LanguageServer, + /// The external program to use to format the files on save. External { + /// The external program to run. command: Arc, + /// The arguments to pass to the program. arguments: Arc<[String]>, }, } +/// Controls how whitespace should be displayedin the editor. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ShowWhitespaceSetting { - /// Draw tabs and spaces only for the selected text. + /// Draw whitespace only for the selected text. Selection, - /// Do not draw any tabs or spaces + /// Do not draw any tabs or spaces. None, - /// Draw all invisible symbols + /// Draw all invisible symbols. All, } +/// Controls which formatter should be used when formatting code. #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Formatter { @@ -226,11 +283,14 @@ pub enum Formatter { Prettier, /// Format code using an external command. External { + /// The external program to run. command: Arc, + /// The arguments to pass to the program. arguments: Arc<[String]>, }, } +/// The settings for inlay hints. #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct InlayHintSettings { /// Global switch to toggle hints on and off. @@ -238,10 +298,19 @@ pub struct InlayHintSettings { /// Default: false #[serde(default)] pub enabled: bool, + /// Whether type hints should be shown. + /// + /// Default: true #[serde(default = "default_true")] pub show_type_hints: bool, + /// Whether parameter hints should be shown. + /// + /// Default: true #[serde(default = "default_true")] pub show_parameter_hints: bool, + /// Whether other hints should be shown. + /// + /// Default: true #[serde(default = "default_true")] pub show_other_hints: bool, } @@ -251,6 +320,7 @@ fn default_true() -> bool { } impl InlayHintSettings { + /// Returns the kinds of inlay hints that are enabled based on the settings. pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { let mut kinds = HashSet::default(); if self.show_type_hints { @@ -267,6 +337,7 @@ impl InlayHintSettings { } impl AllLanguageSettings { + /// Returns the [`LanguageSettings`] for the language with the specified name. pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings { if let Some(name) = language_name { if let Some(overrides) = self.languages.get(name) { @@ -276,6 +347,7 @@ impl AllLanguageSettings { &self.defaults } + /// Returns whether GitHub Copilot is enabled for the given path. pub fn copilot_enabled_for_path(&self, path: &Path) -> bool { !self .copilot @@ -284,6 +356,7 @@ impl AllLanguageSettings { .any(|glob| glob.is_match(path)) } + /// Returns whether GitHub Copilot is enabled for the given language and path. pub fn copilot_enabled(&self, language: Option<&Arc>, path: Option<&Path>) -> bool { if !self.copilot.feature_enabled { return false; @@ -300,13 +373,20 @@ impl AllLanguageSettings { } } +/// The kind of an inlay hint. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum InlayHintKind { + /// An inlay hint for a type. Type, + /// An inlay hint for a parameter. Parameter, } impl InlayHintKind { + /// Returns the [`InlayHintKind`] from the given name. + /// + /// Returns `None` if `name` does not match any of the expected + /// string representations. pub fn from_name(name: &str) -> Option { match name { "type" => Some(InlayHintKind::Type), @@ -315,6 +395,7 @@ impl InlayHintKind { } } + /// Returns the name of this [`InlayHintKind`]. pub fn name(&self) -> &'static str { match self { InlayHintKind::Type => "type", diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index df75b610ef..863f38667f 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -1,3 +1,5 @@ +//! Provides Markdown-related constructs. + use std::sync::Arc; use std::{ops::Range, path::PathBuf}; @@ -5,21 +7,30 @@ use crate::{HighlightId, Language, LanguageRegistry}; use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle}; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag}; +/// Parsed Markdown content. #[derive(Debug, Clone)] pub struct ParsedMarkdown { + /// The Markdown text. pub text: String, + /// The list of highlights contained in the Markdown document. pub highlights: Vec<(Range, MarkdownHighlight)>, + /// The regions of the various ranges in the Markdown document. pub region_ranges: Vec>, + /// The regions of the Markdown document. pub regions: Vec, } +/// A run of highlighted Markdown text. #[derive(Debug, Clone, PartialEq, Eq)] pub enum MarkdownHighlight { + /// A styled Markdown highlight. Style(MarkdownHighlightStyle), + /// A highlighted code block. Code(HighlightId), } impl MarkdownHighlight { + /// Converts this [`MarkdownHighlight`] to a [`HighlightStyle`]. pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option { match self { MarkdownHighlight::Style(style) => { @@ -48,23 +59,39 @@ impl MarkdownHighlight { } } +/// The style for a Markdown highlight. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct MarkdownHighlightStyle { + /// Whether the text should be italicized. pub italic: bool, + /// Whether the text should be underlined. pub underline: bool, + /// The weight of the text. pub weight: FontWeight, } +/// A parsed region in a Markdown document. #[derive(Debug, Clone)] pub struct ParsedRegion { + /// Whether the region is a code block. pub code: bool, + /// The link contained in this region, if it has one. pub link: Option, } +/// A Markdown link. #[derive(Debug, Clone)] pub enum Link { - Web { url: String }, - Path { path: PathBuf }, + /// A link to a webpage. + Web { + /// The URL of the webpage. + url: String, + }, + /// A link to a path on the filesystem. + Path { + /// The path to the item. + path: PathBuf, + }, } impl Link { @@ -82,6 +109,7 @@ impl Link { } } +/// Parses a string of Markdown. pub async fn parse_markdown( markdown: &str, language_registry: &Arc, @@ -111,6 +139,7 @@ pub async fn parse_markdown( } } +/// Parses a Markdown block. pub async fn parse_markdown_block( markdown: &str, language_registry: &Arc, @@ -261,6 +290,7 @@ pub async fn parse_markdown_block( } } +/// Appends a highlighted run of text to the provided `text` buffer. pub fn highlight_code( text: &mut String, highlights: &mut Vec<(Range, MarkdownHighlight)>, @@ -275,6 +305,7 @@ pub fn highlight_code( } } +/// Appends a new paragraph to the provided `text` buffer. pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option, bool)>) { let mut is_subsequent_paragraph_of_list = false; if let Some((_, has_content)) = list_stack.last_mut() { diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index df1a3c629e..014b32676a 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -2,6 +2,7 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{BackgroundExecutor, HighlightStyle}; use std::ops::Range; +/// An outline of all the symbols contained in a buffer. #[derive(Debug)] pub struct Outline { pub items: Vec>, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 957f4ee7fb..d4b553de47 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -1,3 +1,5 @@ +//! Handles conversions of `language` items to and from the [`rpc`] protocol. + use crate::{ diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, Language, @@ -11,15 +13,18 @@ use text::*; pub use proto::{BufferState, Operation}; +/// Serializes a [`RopeFingerprint`] to be sent over RPC. pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String { fingerprint.to_hex() } +/// Deserializes a [`RopeFingerprint`] from the RPC representation. pub fn deserialize_fingerprint(fingerprint: &str) -> Result { RopeFingerprint::from_hex(fingerprint) .map_err(|error| anyhow!("invalid fingerprint: {}", error)) } +/// Deserializes a `[text::LineEnding]` from the RPC representation. pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding { match message { proto::LineEnding::Unix => text::LineEnding::Unix, @@ -27,6 +32,7 @@ pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding { } } +/// Serializes a [`text::LineEnding`] to be sent over RPC. pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding { match message { text::LineEnding::Unix => proto::LineEnding::Unix, @@ -34,6 +40,7 @@ pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding { } } +/// Serializes a [`crate::Operation`] to be sent over RPC. pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation { proto::Operation { variant: Some(match operation { @@ -96,6 +103,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation { } } +/// Serializes an [`operation::EditOperation`] to be sent over RPC. pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit { proto::operation::Edit { replica_id: operation.timestamp.replica_id as u32, @@ -110,6 +118,7 @@ pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation:: } } +/// Serializes an entry in the undo map to be sent over RPC. pub fn serialize_undo_map_entry( (edit_id, counts): (&clock::Lamport, &[(clock::Lamport, u32)]), ) -> proto::UndoMapEntry { @@ -127,6 +136,7 @@ pub fn serialize_undo_map_entry( } } +/// Splits the given list of operations into chunks. pub fn split_operations( mut operations: Vec, ) -> impl Iterator> { @@ -152,10 +162,12 @@ pub fn split_operations( }) } +/// Serializes selections to be sent over RPC. pub fn serialize_selections(selections: &Arc<[Selection]>) -> Vec { selections.iter().map(serialize_selection).collect() } +/// Serializes a [`Selection`] to be sent over RPC. pub fn serialize_selection(selection: &Selection) -> proto::Selection { proto::Selection { id: selection.id as u64, @@ -171,6 +183,7 @@ pub fn serialize_selection(selection: &Selection) -> proto::Selection { } } +/// Serializes a [`CursorShape`] to be sent over RPC. pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape { match cursor_shape { CursorShape::Bar => proto::CursorShape::CursorBar, @@ -180,6 +193,7 @@ pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape } } +/// Deserializes a [`CursorShape`] from the RPC representation. pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape { match cursor_shape { proto::CursorShape::CursorBar => CursorShape::Bar, @@ -189,6 +203,7 @@ pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape } } +/// Serializes a list of diagnostics to be sent over RPC. pub fn serialize_diagnostics<'a>( diagnostics: impl IntoIterator>, ) -> Vec { @@ -208,7 +223,7 @@ pub fn serialize_diagnostics<'a>( } as i32, group_id: entry.diagnostic.group_id as u64, is_primary: entry.diagnostic.is_primary, - is_valid: entry.diagnostic.is_valid, + is_valid: true, code: entry.diagnostic.code.clone(), is_disk_based: entry.diagnostic.is_disk_based, is_unnecessary: entry.diagnostic.is_unnecessary, @@ -216,6 +231,7 @@ pub fn serialize_diagnostics<'a>( .collect() } +/// Serializes an [`Anchor`] to be sent over RPC. pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor { proto::Anchor { replica_id: anchor.timestamp.replica_id as u32, @@ -230,6 +246,7 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor { } // This behavior is currently copied in the collab database, for snapshotting channel notes +/// Deserializes an [`crate::Operation`] from the RPC representation. pub fn deserialize_operation(message: proto::Operation) -> Result { Ok( match message @@ -312,6 +329,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result EditOperation { EditOperation { timestamp: clock::Lamport { @@ -324,6 +342,7 @@ pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation } } +/// Deserializes an entry in the undo map from the RPC representation. pub fn deserialize_undo_map_entry( entry: proto::UndoMapEntry, ) -> (clock::Lamport, Vec<(clock::Lamport, u32)>) { @@ -348,6 +367,7 @@ pub fn deserialize_undo_map_entry( ) } +/// Deserializes selections from the RPC representation. pub fn deserialize_selections(selections: Vec) -> Arc<[Selection]> { Arc::from( selections @@ -357,6 +377,7 @@ pub fn deserialize_selections(selections: Vec) -> Arc<[Selecti ) } +/// Deserializes a [`Selection`] from the RPC representation. pub fn deserialize_selection(selection: proto::Selection) -> Option> { Some(Selection { id: selection.id as usize, @@ -367,6 +388,7 @@ pub fn deserialize_selection(selection: proto::Selection) -> Option, ) -> Arc<[DiagnosticEntry]> { @@ -387,7 +409,6 @@ pub fn deserialize_diagnostics( message: diagnostic.message, group_id: diagnostic.group_id as usize, code: diagnostic.code, - is_valid: diagnostic.is_valid, is_primary: diagnostic.is_primary, is_disk_based: diagnostic.is_disk_based, is_unnecessary: diagnostic.is_unnecessary, @@ -397,6 +418,7 @@ pub fn deserialize_diagnostics( .collect() } +/// Deserializes an [`Anchor`] from the RPC representation. pub fn deserialize_anchor(anchor: proto::Anchor) -> Option { Some(Anchor { timestamp: clock::Lamport { @@ -412,6 +434,7 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option { }) } +/// Returns a `[clock::Lamport`] timestamp for the given [`proto::Operation`]. pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option { let replica_id; let value; @@ -444,6 +467,7 @@ pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option proto::Completion { proto::Completion { old_start: Some(serialize_anchor(&completion.old_range.start)), @@ -454,6 +478,7 @@ pub fn serialize_completion(completion: &Completion) -> proto::Completion { } } +/// Deserializes a [`Completion`] from the RPC representation. pub async fn deserialize_completion( completion: proto::Completion, language: Option>, @@ -488,6 +513,7 @@ pub async fn deserialize_completion( }) } +/// Serializes a [`CodeAction`] to be sent over RPC. pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction { proto::CodeAction { server_id: action.server_id.0 as u64, @@ -497,6 +523,7 @@ pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction { } } +/// Deserializes a [`CodeAction`] from the RPC representation. pub fn deserialize_code_action(action: proto::CodeAction) -> Result { let start = action .start @@ -514,6 +541,7 @@ pub fn deserialize_code_action(action: proto::CodeAction) -> Result }) } +/// Serializes a [`Transaction`] to be sent over RPC. pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction { proto::Transaction { id: Some(serialize_timestamp(transaction.id)), @@ -527,6 +555,7 @@ pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction { } } +/// Deserializes a [`Transaction`] from the RPC representation. pub fn deserialize_transaction(transaction: proto::Transaction) -> Result { Ok(Transaction { id: deserialize_timestamp( @@ -543,6 +572,7 @@ pub fn deserialize_transaction(transaction: proto::Transaction) -> Result proto::LamportTimestamp { proto::LamportTimestamp { replica_id: timestamp.replica_id as u32, @@ -550,6 +580,7 @@ pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp } } +/// Deserializes a [`clock::Lamport`] timestamp from the RPC representation. pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lamport { clock::Lamport { replica_id: timestamp.replica_id as ReplicaId, @@ -557,6 +588,7 @@ pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lampo } } +/// Serializes a range of [`FullOffset`]s to be sent over RPC. pub fn serialize_range(range: &Range) -> proto::Range { proto::Range { start: range.start.0 as u64, @@ -564,10 +596,12 @@ pub fn serialize_range(range: &Range) -> proto::Range { } } +/// Deserializes a range of [`FullOffset`]s from the RPC representation. pub fn deserialize_range(range: proto::Range) -> Range { FullOffset(range.start as usize)..FullOffset(range.end as usize) } +/// Deserializes a clock version from the RPC representation. pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global { let mut version = clock::Global::new(); for entry in message { @@ -579,6 +613,7 @@ pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global version } +/// Serializes a clock version to be sent over RPC. pub fn serialize_version(version: &clock::Global) -> Vec { version .iter() diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index b5e243e7ff..32a77b1a66 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -29,7 +29,7 @@ pub struct SyntaxMap { #[derive(Clone, Default)] pub struct SyntaxSnapshot { - layers: SumTree, + layers: SumTree, parsed_version: clock::Global, interpolated_version: clock::Global, language_registry_version: usize, @@ -84,7 +84,7 @@ struct SyntaxMapMatchesLayer<'a> { } #[derive(Clone)] -struct SyntaxLayer { +struct SyntaxLayerEntry { depth: usize, range: Range, content: SyntaxLayerContent, @@ -117,17 +117,22 @@ impl SyntaxLayerContent { } } +/// A layer of syntax highlighting, corresponding to a single syntax +/// tree in a particular language. #[derive(Debug)] -pub struct SyntaxLayerInfo<'a> { - pub depth: usize, +pub struct SyntaxLayer<'a> { + /// The language for this layer. pub language: &'a Arc, + depth: usize, tree: &'a Tree, offset: (usize, tree_sitter::Point), } +/// A layer of syntax highlighting. Like [SyntaxLayer], but holding +/// owned data instead of references. #[derive(Clone)] -pub struct OwnedSyntaxLayerInfo { - pub depth: usize, +pub struct OwnedSyntaxLayer { + /// The language for this layer. pub language: Arc, tree: tree_sitter::Tree, offset: (usize, tree_sitter::Point), @@ -691,7 +696,7 @@ impl SyntaxSnapshot { }; layers.push( - SyntaxLayer { + SyntaxLayerEntry { depth: step.depth, range: step.range, content, @@ -741,7 +746,7 @@ impl SyntaxSnapshot { SyntaxMapCaptures::new( range.clone(), text, - [SyntaxLayerInfo { + [SyntaxLayer { language, tree, depth: 0, @@ -781,7 +786,7 @@ impl SyntaxSnapshot { } #[cfg(test)] - pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec { + pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec { self.layers_for_range(0..buffer.len(), buffer).collect() } @@ -789,7 +794,7 @@ impl SyntaxSnapshot { &'a self, range: Range, buffer: &'a BufferSnapshot, - ) -> impl 'a + Iterator { + ) -> impl 'a + Iterator { let start_offset = range.start.to_offset(buffer); let end_offset = range.end.to_offset(buffer); let start = buffer.anchor_before(start_offset); @@ -813,7 +818,7 @@ impl SyntaxSnapshot { let layer_start_offset = layer.range.start.to_offset(buffer); let layer_start_point = layer.range.start.to_point(buffer).to_ts_point(); - info = Some(SyntaxLayerInfo { + info = Some(SyntaxLayer { tree, language, depth: layer.depth, @@ -842,7 +847,7 @@ impl<'a> SyntaxMapCaptures<'a> { fn new( range: Range, text: &'a Rope, - layers: impl Iterator>, + layers: impl Iterator>, query: fn(&Grammar) -> Option<&Query>, ) -> Self { let mut result = Self { @@ -964,7 +969,7 @@ impl<'a> SyntaxMapMatches<'a> { fn new( range: Range, text: &'a Rope, - layers: impl Iterator>, + layers: impl Iterator>, query: fn(&Grammar) -> Option<&Query>, ) -> Self { let mut result = Self::default(); @@ -1436,23 +1441,25 @@ fn insert_newlines_between_ranges( } } -impl OwnedSyntaxLayerInfo { +impl OwnedSyntaxLayer { + /// Returns the root syntax node for this layer. pub fn node(&self) -> Node { self.tree .root_node_with_offset(self.offset.0, self.offset.1) } } -impl<'a> SyntaxLayerInfo<'a> { - pub fn to_owned(&self) -> OwnedSyntaxLayerInfo { - OwnedSyntaxLayerInfo { +impl<'a> SyntaxLayer<'a> { + /// Returns an owned version of this layer. + pub fn to_owned(&self) -> OwnedSyntaxLayer { + OwnedSyntaxLayer { tree: self.tree.clone(), offset: self.offset, - depth: self.depth, language: self.language.clone(), } } + /// Returns the root node for this layer. pub fn node(&self) -> Node<'a> { self.tree .root_node_with_offset(self.offset.0, self.offset.1) @@ -1564,7 +1571,7 @@ impl ChangeRegionSet { ) } - fn intersects(&self, layer: &SyntaxLayer, text: &BufferSnapshot) -> bool { + fn intersects(&self, layer: &SyntaxLayerEntry, text: &BufferSnapshot) -> bool { for region in &self.0 { if region.depth < layer.depth { continue; @@ -1675,7 +1682,7 @@ impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> } } -impl sum_tree::Item for SyntaxLayer { +impl sum_tree::Item for SyntaxLayerEntry { type Summary = SyntaxLayerSummary; fn summary(&self) -> Self::Summary { @@ -1690,7 +1697,7 @@ impl sum_tree::Item for SyntaxLayer { } } -impl std::fmt::Debug for SyntaxLayer { +impl std::fmt::Debug for SyntaxLayerEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SyntaxLayer") .field("depth", &self.depth) diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index be677b215b..082e77fc36 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -5,7 +5,7 @@ use gpui::{ MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext, }; -use language::{Buffer, OwnedSyntaxLayerInfo}; +use language::{Buffer, OwnedSyntaxLayer}; use std::{mem, ops::Range}; use theme::ActiveTheme; use tree_sitter::{Node, TreeCursor}; @@ -54,7 +54,7 @@ struct EditorState { struct BufferState { buffer: Model, excerpt_id: ExcerptId, - active_layer: Option, + active_layer: Option, } impl SyntaxTreeView { @@ -477,7 +477,7 @@ impl SyntaxTreeToolbarItemView { }) } - fn render_header(active_layer: &OwnedSyntaxLayerInfo) -> ButtonLike { + fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike { ButtonLike::new("syntax tree header") .child(Label::new(active_layer.language.name())) .child(Label::new(format_node_range(active_layer.node()))) diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 9f78480136..1abbcb710f 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -3028,7 +3028,7 @@ impl MultiBufferSnapshot { pub fn has_git_diffs(&self) -> bool { for excerpt in self.excerpts.iter() { - if !excerpt.buffer.git_diff.is_empty() { + if excerpt.buffer.has_git_diff() { return true; } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c5dc88d447..2088fcbdaa 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3910,7 +3910,6 @@ impl Project { message: diagnostic.message.clone(), group_id, is_primary: true, - is_valid: true, is_disk_based, is_unnecessary, }, @@ -3928,7 +3927,6 @@ impl Project { message: info.message.clone(), group_id, is_primary: false, - is_valid: true, is_disk_based, is_unnecessary: false, }, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index dd5d77440e..941053be53 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1472,7 +1472,10 @@ message Diagnostic { optional string code = 6; uint64 group_id = 7; bool is_primary = 8; + + // TODO: remove this field bool is_valid = 9; + bool is_disk_based = 10; bool is_unnecessary = 11; diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 6215e4c16c..63c0c2a192 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -11,6 +11,7 @@ use workspace::Workspace; use crate::{ normal::normal_motion, state::{Mode, Operator}, + utils::coerce_punctuation, visual::visual_motion, Vim, }; @@ -680,8 +681,8 @@ pub(crate) fn next_word_start( for _ in 0..times { let mut crossed_newline = false; point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| { - let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation); - let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation); + let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); + let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); let at_newline = right == '\n'; let found = (left_kind != right_kind && right_kind != CharKind::Whitespace) @@ -710,8 +711,8 @@ fn next_word_end( *point.column_mut() = 0; } point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| { - let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation); - let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation); + let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); + let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); left_kind != right_kind && left_kind != CharKind::Whitespace }); @@ -743,8 +744,8 @@ fn previous_word_start( // cursor because the newline is checked only once. point = movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| { - let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation); - let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation); + let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); + let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); (left_kind != right_kind && !right.is_whitespace()) || left == '\n' }); diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index 86b7703846..52de1f7e0a 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -1,4 +1,10 @@ -use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim}; +use crate::{ + motion::Motion, + object::Object, + state::Mode, + utils::{coerce_punctuation, copy_selections_content}, + Vim, +}; use editor::{ display_map::DisplaySnapshot, movement::{self, FindRange, TextLayoutDetails}, @@ -102,9 +108,9 @@ fn expand_changed_word_selection( if in_word { selection.end = movement::find_boundary(map, selection.end, FindRange::MultiLine, |left, right| { - let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation); + let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); let right_kind = - char_kind(&scope, right).coerce_punctuation(ignore_punctuation); + coerce_punctuation(char_kind(&scope, right), ignore_punctuation); left_kind != right_kind && left_kind != CharKind::Whitespace }); diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index f65ed763b9..49406cf992 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -10,7 +10,10 @@ use language::{char_kind, CharKind, Selection}; use serde::Deserialize; use workspace::Workspace; -use crate::{motion::right, normal::normal_object, state::Mode, visual::visual_object, Vim}; +use crate::{ + motion::right, normal::normal_object, state::Mode, utils::coerce_punctuation, + visual::visual_object, Vim, +}; #[derive(Copy, Clone, Debug, PartialEq)] pub enum Object { @@ -213,14 +216,14 @@ fn in_word( right(map, relative_to, 1), movement::FindRange::SingleLine, |left, right| { - char_kind(&scope, left).coerce_punctuation(ignore_punctuation) - != char_kind(&scope, right).coerce_punctuation(ignore_punctuation) + coerce_punctuation(char_kind(&scope, left), ignore_punctuation) + != coerce_punctuation(char_kind(&scope, right), ignore_punctuation) }, ); let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| { - char_kind(&scope, left).coerce_punctuation(ignore_punctuation) - != char_kind(&scope, right).coerce_punctuation(ignore_punctuation) + coerce_punctuation(char_kind(&scope, left), ignore_punctuation) + != coerce_punctuation(char_kind(&scope, right), ignore_punctuation) }); Some(start..end) @@ -283,15 +286,15 @@ fn around_next_word( right(map, relative_to, 1), FindRange::SingleLine, |left, right| { - char_kind(&scope, left).coerce_punctuation(ignore_punctuation) - != char_kind(&scope, right).coerce_punctuation(ignore_punctuation) + coerce_punctuation(char_kind(&scope, left), ignore_punctuation) + != coerce_punctuation(char_kind(&scope, right), ignore_punctuation) }, ); let mut word_found = false; let end = movement::find_boundary(map, relative_to, FindRange::MultiLine, |left, right| { - let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation); - let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation); + let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation); + let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation); let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n'; diff --git a/crates/vim/src/utils.rs b/crates/vim/src/utils.rs index 797e94b0fa..0ff857af9c 100644 --- a/crates/vim/src/utils.rs +++ b/crates/vim/src/utils.rs @@ -1,6 +1,6 @@ use editor::{ClipboardSelection, Editor}; use gpui::{AppContext, ClipboardItem}; -use language::Point; +use language::{CharKind, Point}; pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) { let selections = editor.selections.all_adjusted(cx); @@ -48,3 +48,11 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut App cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); } + +pub fn coerce_punctuation(kind: CharKind, treat_punctuation_as_word: bool) -> CharKind { + if treat_punctuation_as_word && kind == CharKind::Punctuation { + CharKind::Word + } else { + kind + } +}