diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 7863be8b93..8feb566c7e 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -60,12 +60,15 @@ pub use {tree_sitter_rust, tree_sitter_typescript}; pub use lsp::DiagnosticSeverity; lazy_static! { - pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new(); + 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, } @@ -107,11 +110,11 @@ pub struct Buffer { capability: Capability, } -/// An immutable, cheaply cloneable representation of a certain +/// 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]>, @@ -128,25 +131,33 @@ pub struct BufferSnapshot { /// 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 chracter. 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, } @@ -158,26 +169,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, @@ -209,77 +234,125 @@ 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 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. @@ -298,10 +371,13 @@ 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; } @@ -310,8 +386,10 @@ 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, @@ -392,11 +470,18 @@ pub struct BufferChunks<'a> { /// 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, } @@ -418,21 +503,14 @@ pub(crate) struct DiagnosticEndpoint { /// 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 { @@ -554,14 +632,17 @@ impl Buffer { 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, @@ -675,6 +756,7 @@ impl Buffer { .set_language_registry(language_registry); } + /// This method is called to signal that the buffer has been saved. pub fn did_save( &mut self, version: clock::Global, @@ -689,6 +771,7 @@ impl Buffer { cx.notify(); } + /// Reloads the contents of the buffer from disk. pub fn reload( &mut self, cx: &mut ModelContext, @@ -737,6 +820,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, @@ -763,6 +847,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; @@ -800,16 +886,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(); @@ -830,14 +920,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 @@ -848,26 +936,32 @@ 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 @@ -2377,7 +2471,7 @@ impl BufferSnapshot { 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) @@ -2385,12 +2479,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, @@ -2399,6 +2495,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; @@ -2443,6 +2540,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; @@ -2475,6 +2574,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; @@ -2543,11 +2643,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, @@ -2699,6 +2807,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, @@ -2755,6 +2865,7 @@ impl BufferSnapshot { }) } + /// Returns selections for remote peers intersecting the given range. #[allow(clippy::type_complexity)] pub fn remote_selections_in_range( &self, @@ -2793,6 +2904,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, @@ -2800,6 +2918,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, @@ -2807,6 +2927,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, @@ -2814,6 +2936,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, @@ -2843,6 +2966,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, @@ -2873,6 +2999,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, @@ -2885,22 +3012,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 { @@ -2913,10 +3045,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 } @@ -2926,7 +3060,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 { @@ -3004,6 +3138,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); @@ -3027,6 +3162,7 @@ impl<'a> BufferChunks<'a> { } } + /// The current byte offset in the buffer. pub fn offset(&self) -> usize { self.range.start } @@ -3179,7 +3315,6 @@ impl Default for Diagnostic { message: Default::default(), group_id: 0, is_primary: false, - is_valid: true, is_disk_based: false, is_unnecessary: false, } @@ -3187,6 +3322,7 @@ impl Default for Diagnostic { } impl IndentSize { + /// Returns an [IndentSize] representing the given spaces. pub fn spaces(len: u32) -> Self { Self { len, @@ -3194,6 +3330,7 @@ impl IndentSize { } } + /// Returns an [IndentSize] representing a tab. pub fn tab() -> Self { Self { len: 1, @@ -3201,10 +3338,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 => ' ', @@ -3212,6 +3351,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 => { @@ -3233,6 +3374,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, @@ -3241,12 +3384,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> { @@ -3272,6 +3416,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/proto.rs b/crates/language/src/proto.rs index 0497a4c459..d4b553de47 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -223,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, @@ -409,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, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 946e6af5ab..e02f19155b 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -3023,7 +3023,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 044b750ad9..fdaab47bc1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3912,7 +3912,6 @@ impl Project { message: diagnostic.message.clone(), group_id, is_primary: true, - is_valid: true, is_disk_based, is_unnecessary, }, @@ -3930,7 +3929,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 e423441a30..086deeef11 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1471,7 +1471,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 73ba70d5ad..685c7a28f9 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -681,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) @@ -711,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 = ccoerce_punctuation(har_kind(&scope, right), ignore_punctuation); left_kind != right_kind && left_kind != CharKind::Whitespace }); @@ -744,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' }); @@ -952,6 +952,14 @@ pub(crate) fn next_line_end( end_of_line(map, false, point) } +fn coerce_punctuation(kind: CharKind, treat_punctuation_as_word: bool) -> Self { + if treat_punctuation_as_word && kind == CharKind::Punctuation { + CharKind::Word + } else { + kind + } +} + #[cfg(test)] mod test {