diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a66d92ceb8b0b6fa1631d0636294fbe3bd42af06..f9ffd835245ebb2ac7df8e8b7b667a1501c254fd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -124,8 +124,9 @@ use language::{ AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow, BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape, DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, - IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point, - Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery, + IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt, + OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId, + TreeSitterOptions, WordsQuery, language_settings::{ self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings, language_settings, @@ -4790,205 +4791,51 @@ impl Editor { let end = selection.end; let selection_is_empty = start == end; let language_scope = buffer.language_scope_at(start); - let ( - comment_delimiter, - doc_delimiter, - insert_extra_newline, - indent_on_newline, - indent_on_extra_newline, - ) = if let Some(language) = &language_scope { - let mut insert_extra_newline = - insert_extra_newline_brackets(&buffer, start..end, language) - || insert_extra_newline_tree_sitter(&buffer, start..end); - - // Comment extension on newline is allowed only for cursor selections - let comment_delimiter = maybe!({ - if !selection_is_empty { - return None; - } - - if !multi_buffer.language_settings(cx).extend_comment_on_newline { - return None; - } - - let delimiters = language.line_comment_prefixes(); - let max_len_of_delimiter = - delimiters.iter().map(|delimiter| delimiter.len()).max()?; - let (snapshot, range) = - buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?; - - let num_of_whitespaces = snapshot - .chars_for_range(range.clone()) - .take_while(|c| c.is_whitespace()) - .count(); - let comment_candidate = snapshot - .chars_for_range(range.clone()) - .skip(num_of_whitespaces) - .take(max_len_of_delimiter) - .collect::(); - let (delimiter, trimmed_len) = delimiters - .iter() - .filter_map(|delimiter| { - let prefix = delimiter.trim_end(); - if comment_candidate.starts_with(prefix) { - Some((delimiter, prefix.len())) - } else { - None - } - }) - .max_by_key(|(_, len)| *len)?; - - if let Some(BlockCommentConfig { - start: block_start, .. - }) = language.block_comment() - { - let block_start_trimmed = block_start.trim_end(); - if block_start_trimmed.starts_with(delimiter.trim_end()) { - let line_content = snapshot - .chars_for_range(range) - .skip(num_of_whitespaces) - .take(block_start_trimmed.len()) - .collect::(); - - if line_content.starts_with(block_start_trimmed) { - return None; - } + let (comment_delimiter, doc_delimiter, newline_formatting) = + if let Some(language) = &language_scope { + let mut newline_formatting = + NewlineFormatting::new(&buffer, start..end, language); + + // Comment extension on newline is allowed only for cursor selections + let comment_delimiter = maybe!({ + if !selection_is_empty { + return None; } - } - let cursor_is_placed_after_comment_marker = - num_of_whitespaces + trimmed_len <= start_point.column as usize; - if cursor_is_placed_after_comment_marker { - Some(delimiter.clone()) - } else { - None - } - }); - - let mut indent_on_newline = IndentSize::spaces(0); - let mut indent_on_extra_newline = IndentSize::spaces(0); - - let doc_delimiter = maybe!({ - if !selection_is_empty { - return None; - } - - if !multi_buffer.language_settings(cx).extend_comment_on_newline { - return None; - } - - let BlockCommentConfig { - start: start_tag, - end: end_tag, - prefix: delimiter, - tab_size: len, - } = language.documentation_comment()?; - let is_within_block_comment = buffer - .language_scope_at(start_point) - .is_some_and(|scope| scope.override_name() == Some("comment")); - if !is_within_block_comment { - return None; - } - - let (snapshot, range) = - buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?; - - let num_of_whitespaces = snapshot - .chars_for_range(range.clone()) - .take_while(|c| c.is_whitespace()) - .count(); - - // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time. - let column = start_point.column; - let cursor_is_after_start_tag = { - let start_tag_len = start_tag.len(); - let start_tag_line = snapshot - .chars_for_range(range.clone()) - .skip(num_of_whitespaces) - .take(start_tag_len) - .collect::(); - if start_tag_line.starts_with(start_tag.as_ref()) { - num_of_whitespaces + start_tag_len <= column as usize - } else { - false + if !multi_buffer.language_settings(cx).extend_comment_on_newline + { + return None; } - }; - let cursor_is_after_delimiter = { - let delimiter_trim = delimiter.trim_end(); - let delimiter_line = snapshot - .chars_for_range(range.clone()) - .skip(num_of_whitespaces) - .take(delimiter_trim.len()) - .collect::(); - if delimiter_line.starts_with(delimiter_trim) { - num_of_whitespaces + delimiter_trim.len() <= column as usize - } else { - false - } - }; + return comment_delimiter_for_newline( + &start_point, + &buffer, + language, + ); + }); - let cursor_is_before_end_tag_if_exists = { - let mut char_position = 0u32; - let mut end_tag_offset = None; - - 'outer: for chunk in snapshot.text_for_range(range) { - if let Some(byte_pos) = chunk.find(&**end_tag) { - let chars_before_match = - chunk[..byte_pos].chars().count() as u32; - end_tag_offset = - Some(char_position + chars_before_match); - break 'outer; - } - char_position += chunk.chars().count() as u32; + let doc_delimiter = maybe!({ + if !selection_is_empty { + return None; } - if let Some(end_tag_offset) = end_tag_offset { - let cursor_is_before_end_tag = column <= end_tag_offset; - if cursor_is_after_start_tag { - if cursor_is_before_end_tag { - insert_extra_newline = true; - } - let cursor_is_at_start_of_end_tag = - column == end_tag_offset; - if cursor_is_at_start_of_end_tag { - indent_on_extra_newline.len = *len; - } - } - cursor_is_before_end_tag - } else { - true + if !multi_buffer.language_settings(cx).extend_comment_on_newline + { + return None; } - }; - if (cursor_is_after_start_tag || cursor_is_after_delimiter) - && cursor_is_before_end_tag_if_exists - { - if cursor_is_after_start_tag { - indent_on_newline.len = *len; - } - Some(delimiter.clone()) - } else { - None - } - }); + return documentation_delimiter_for_newline( + &start_point, + &buffer, + language, + &mut newline_formatting, + ); + }); - ( - comment_delimiter, - doc_delimiter, - insert_extra_newline, - indent_on_newline, - indent_on_extra_newline, - ) - } else { - ( - None, - None, - false, - IndentSize::default(), - IndentSize::default(), - ) - }; + (comment_delimiter, doc_delimiter, newline_formatting) + } else { + (None, None, NewlineFormatting::default()) + }; let prevent_auto_indent = doc_delimiter.is_some(); let delimiter = comment_delimiter.or(doc_delimiter); @@ -4998,28 +4845,28 @@ impl Editor { let mut new_text = String::with_capacity( 1 + capacity_for_delimiter + existing_indent.len as usize - + indent_on_newline.len as usize - + indent_on_extra_newline.len as usize, + + newline_formatting.indent_on_newline.len as usize + + newline_formatting.indent_on_extra_newline.len as usize, ); new_text.push('\n'); new_text.extend(existing_indent.chars()); - new_text.extend(indent_on_newline.chars()); + new_text.extend(newline_formatting.indent_on_newline.chars()); if let Some(delimiter) = &delimiter { new_text.push_str(delimiter); } - if insert_extra_newline { + if newline_formatting.insert_extra_newline { new_text.push('\n'); new_text.extend(existing_indent.chars()); - new_text.extend(indent_on_extra_newline.chars()); + new_text.extend(newline_formatting.indent_on_extra_newline.chars()); } let anchor = buffer.anchor_after(end); let new_selection = selection.map(|_| anchor); ( ((start..end, new_text), prevent_auto_indent), - (insert_extra_newline, new_selection), + (newline_formatting.insert_extra_newline, new_selection), ) }) .unzip() @@ -23507,76 +23354,256 @@ struct CompletionEdit { snippet: Option, } -fn insert_extra_newline_brackets( +fn comment_delimiter_for_newline( + start_point: &Point, buffer: &MultiBufferSnapshot, - range: Range, - language: &language::LanguageScope, -) -> bool { - let leading_whitespace_len = buffer - .reversed_chars_at(range.start) - .take_while(|c| c.is_whitespace() && *c != '\n') - .map(|c| c.len_utf8()) - .sum::(); - let trailing_whitespace_len = buffer - .chars_at(range.end) - .take_while(|c| c.is_whitespace() && *c != '\n') - .map(|c| c.len_utf8()) - .sum::(); - let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len; - - language.brackets().any(|(pair, enabled)| { - let pair_start = pair.start.trim_end(); - let pair_end = pair.end.trim_start(); - - enabled - && pair.newline - && buffer.contains_str_at(range.end, pair_end) - && buffer.contains_str_at( - range.start.saturating_sub_usize(pair_start.len()), - pair_start, - ) - }) + language: &LanguageScope, +) -> Option> { + let delimiters = language.line_comment_prefixes(); + let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?; + let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?; + + let num_of_whitespaces = snapshot + .chars_for_range(range.clone()) + .take_while(|c| c.is_whitespace()) + .count(); + let comment_candidate = snapshot + .chars_for_range(range.clone()) + .skip(num_of_whitespaces) + .take(max_len_of_delimiter) + .collect::(); + let (delimiter, trimmed_len) = delimiters + .iter() + .filter_map(|delimiter| { + let prefix = delimiter.trim_end(); + if comment_candidate.starts_with(prefix) { + Some((delimiter, prefix.len())) + } else { + None + } + }) + .max_by_key(|(_, len)| *len)?; + + if let Some(BlockCommentConfig { + start: block_start, .. + }) = language.block_comment() + { + let block_start_trimmed = block_start.trim_end(); + if block_start_trimmed.starts_with(delimiter.trim_end()) { + let line_content = snapshot + .chars_for_range(range) + .skip(num_of_whitespaces) + .take(block_start_trimmed.len()) + .collect::(); + + if line_content.starts_with(block_start_trimmed) { + return None; + } + } + } + + let cursor_is_placed_after_comment_marker = + num_of_whitespaces + trimmed_len <= start_point.column as usize; + if cursor_is_placed_after_comment_marker { + Some(delimiter.clone()) + } else { + None + } } -fn insert_extra_newline_tree_sitter( +fn documentation_delimiter_for_newline( + start_point: &Point, buffer: &MultiBufferSnapshot, - range: Range, -) -> bool { - let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() { - [(buffer, range, _)] => (*buffer, range.clone()), - _ => return false, + language: &LanguageScope, + newline_formatting: &mut NewlineFormatting, +) -> Option> { + let BlockCommentConfig { + start: start_tag, + end: end_tag, + prefix: delimiter, + tab_size: len, + } = language.documentation_comment()?; + let is_within_block_comment = buffer + .language_scope_at(*start_point) + .is_some_and(|scope| scope.override_name() == Some("comment")); + if !is_within_block_comment { + return None; + } + + let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?; + + let num_of_whitespaces = snapshot + .chars_for_range(range.clone()) + .take_while(|c| c.is_whitespace()) + .count(); + + // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time. + let column = start_point.column; + let cursor_is_after_start_tag = { + let start_tag_len = start_tag.len(); + let start_tag_line = snapshot + .chars_for_range(range.clone()) + .skip(num_of_whitespaces) + .take(start_tag_len) + .collect::(); + if start_tag_line.starts_with(start_tag.as_ref()) { + num_of_whitespaces + start_tag_len <= column as usize + } else { + false + } }; - let pair = { - let mut result: Option> = None; - for pair in buffer - .all_bracket_ranges(range.start.0..range.end.0) - .filter(move |pair| { - pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0 - }) - { - let len = pair.close_range.end - pair.open_range.start; + let cursor_is_after_delimiter = { + let delimiter_trim = delimiter.trim_end(); + let delimiter_line = snapshot + .chars_for_range(range.clone()) + .skip(num_of_whitespaces) + .take(delimiter_trim.len()) + .collect::(); + if delimiter_line.starts_with(delimiter_trim) { + num_of_whitespaces + delimiter_trim.len() <= column as usize + } else { + false + } + }; - if let Some(existing) = &result { - let existing_len = existing.close_range.end - existing.open_range.start; - if len > existing_len { - continue; + let cursor_is_before_end_tag_if_exists = { + let mut char_position = 0u32; + let mut end_tag_offset = None; + + 'outer: for chunk in snapshot.text_for_range(range) { + if let Some(byte_pos) = chunk.find(&**end_tag) { + let chars_before_match = chunk[..byte_pos].chars().count() as u32; + end_tag_offset = Some(char_position + chars_before_match); + break 'outer; + } + char_position += chunk.chars().count() as u32; + } + + if let Some(end_tag_offset) = end_tag_offset { + let cursor_is_before_end_tag = column <= end_tag_offset; + if cursor_is_after_start_tag { + if cursor_is_before_end_tag { + newline_formatting.insert_extra_newline = true; + } + let cursor_is_at_start_of_end_tag = column == end_tag_offset; + if cursor_is_at_start_of_end_tag { + newline_formatting.indent_on_extra_newline.len = *len; } } + cursor_is_before_end_tag + } else { + true + } + }; - result = Some(pair); + if (cursor_is_after_start_tag || cursor_is_after_delimiter) + && cursor_is_before_end_tag_if_exists + { + if cursor_is_after_start_tag { + newline_formatting.indent_on_newline.len = *len; } + Some(delimiter.clone()) + } else { + None + } +} - result - }; - let Some(pair) = pair else { - return false; - }; - pair.newline_only - && buffer - .chars_for_range(pair.open_range.end..range.start.0) - .chain(buffer.chars_for_range(range.end.0..pair.close_range.start)) - .all(|c| c.is_whitespace() && c != '\n') +#[derive(Debug, Default)] +struct NewlineFormatting { + insert_extra_newline: bool, + indent_on_newline: IndentSize, + indent_on_extra_newline: IndentSize, +} + +impl NewlineFormatting { + fn new( + buffer: &MultiBufferSnapshot, + range: Range, + language: &LanguageScope, + ) -> Self { + Self { + insert_extra_newline: Self::insert_extra_newline_brackets( + buffer, + range.clone(), + language, + ) || Self::insert_extra_newline_tree_sitter(buffer, range), + indent_on_newline: IndentSize::spaces(0), + indent_on_extra_newline: IndentSize::spaces(0), + } + } + + fn insert_extra_newline_brackets( + buffer: &MultiBufferSnapshot, + range: Range, + language: &language::LanguageScope, + ) -> bool { + let leading_whitespace_len = buffer + .reversed_chars_at(range.start) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); + let trailing_whitespace_len = buffer + .chars_at(range.end) + .take_while(|c| c.is_whitespace() && *c != '\n') + .map(|c| c.len_utf8()) + .sum::(); + let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len; + + language.brackets().any(|(pair, enabled)| { + let pair_start = pair.start.trim_end(); + let pair_end = pair.end.trim_start(); + + enabled + && pair.newline + && buffer.contains_str_at(range.end, pair_end) + && buffer.contains_str_at( + range.start.saturating_sub_usize(pair_start.len()), + pair_start, + ) + }) + } + + fn insert_extra_newline_tree_sitter( + buffer: &MultiBufferSnapshot, + range: Range, + ) -> bool { + let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() { + [(buffer, range, _)] => (*buffer, range.clone()), + _ => return false, + }; + let pair = { + let mut result: Option> = None; + + for pair in buffer + .all_bracket_ranges(range.start.0..range.end.0) + .filter(move |pair| { + pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0 + }) + { + let len = pair.close_range.end - pair.open_range.start; + + if let Some(existing) = &result { + let existing_len = existing.close_range.end - existing.open_range.start; + if len > existing_len { + continue; + } + } + + result = Some(pair); + } + + result + }; + let Some(pair) = pair else { + return false; + }; + pair.newline_only + && buffer + .chars_for_range(pair.open_range.end..range.start.0) + .chain(buffer.chars_for_range(range.end.0..pair.close_range.start)) + .all(|c| c.is_whitespace() && c != '\n') + } } fn update_uncommitted_diff_for_buffer(