editor: Improve JSDoc extend comment on newline to follow convention (#30800)
Follow up for https://github.com/zed-industries/zed/pull/30768 This PR makes JSDoc auto comment on new line lot better by: - Inserting delimiters regardless of whether previous delimiters have trailing spaces or not - When on start tag, auto-indenting both prefix and end tag upon new line This makes it correct as per convention out of the box. No need to manually adjust spaces on every new line. https://github.com/user-attachments/assets/81b8e05a-fe8a-4459-9e90-c8a3d70a51a2 Release Notes: - Improved JSDoc auto-commenting on newline which now correctly indents as per convention.
This commit is contained in:
parent
cc3a28a8e8
commit
18d39e3f81
6 changed files with 264 additions and 139 deletions
|
@ -107,9 +107,9 @@ pub use items::MAX_TAB_TITLE_LEN;
|
|||
use itertools::Itertools;
|
||||
use language::{
|
||||
AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||
CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
|
||||
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
|
||||
TransactionId, TreeSitterOptions, WordsQuery,
|
||||
CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
|
||||
EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
|
||||
Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
|
||||
language_settings::{
|
||||
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||
all_language_settings, language_settings,
|
||||
|
@ -3912,7 +3912,7 @@ impl Editor {
|
|||
pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
|
||||
let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let multi_buffer = this.buffer.read(cx);
|
||||
let buffer = multi_buffer.snapshot(cx);
|
||||
|
@ -3920,22 +3920,26 @@ impl Editor {
|
|||
.iter()
|
||||
.map(|selection| {
|
||||
let start_point = selection.start.to_point(&buffer);
|
||||
let mut indent =
|
||||
let mut existing_indent =
|
||||
buffer.indent_size_for_line(MultiBufferRow(start_point.row));
|
||||
indent.len = cmp::min(indent.len, start_point.column);
|
||||
existing_indent.len = cmp::min(existing_indent.len, start_point.column);
|
||||
let start = selection.start;
|
||||
let end = selection.end;
|
||||
let selection_is_empty = start == end;
|
||||
let language_scope = buffer.language_scope_at(start);
|
||||
let (comment_delimiter, insert_extra_newline) = if let Some(language) =
|
||||
&language_scope
|
||||
{
|
||||
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 mut comment_delimiter = maybe!({
|
||||
let comment_delimiter = maybe!({
|
||||
if !selection_is_empty {
|
||||
return None;
|
||||
}
|
||||
|
@ -3975,128 +3979,181 @@ impl Editor {
|
|||
}
|
||||
});
|
||||
|
||||
if comment_delimiter.is_none() {
|
||||
comment_delimiter = maybe!({
|
||||
if !selection_is_empty {
|
||||
return 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 DocumentationConfig {
|
||||
start: start_tag,
|
||||
end: end_tag,
|
||||
prefix: delimiter,
|
||||
tab_size: len,
|
||||
} = language.documentation()?;
|
||||
|
||||
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 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::<String>();
|
||||
if start_tag_line.starts_with(start_tag.as_ref()) {
|
||||
num_of_whitespaces + start_tag_len
|
||||
<= start_point.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::<String>();
|
||||
if delimiter_line.starts_with(delimiter_trim) {
|
||||
num_of_whitespaces + delimiter_trim.len()
|
||||
<= start_point.column as usize
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let doc_block = language.documentation_block();
|
||||
let doc_block_prefix = doc_block.first()?;
|
||||
let doc_block_suffix = doc_block.last()?;
|
||||
|
||||
let doc_comment_prefix =
|
||||
language.documentation_comment_prefix()?;
|
||||
|
||||
let (snapshot, range) = buffer
|
||||
.buffer_line_for_row(MultiBufferRow(start_point.row))?;
|
||||
|
||||
let cursor_is_after_prefix = {
|
||||
let doc_block_prefix_len = doc_block_prefix.len();
|
||||
let max_len_of_delimiter = std::cmp::max(
|
||||
doc_comment_prefix.len(),
|
||||
doc_block_prefix_len,
|
||||
);
|
||||
let index_of_first_non_whitespace = snapshot
|
||||
.chars_for_range(range.clone())
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.count();
|
||||
let doc_line_candidate = snapshot
|
||||
.chars_for_range(range.clone())
|
||||
.skip(index_of_first_non_whitespace)
|
||||
.take(max_len_of_delimiter)
|
||||
.collect::<String>();
|
||||
if doc_line_candidate.starts_with(doc_block_prefix.as_ref())
|
||||
{
|
||||
index_of_first_non_whitespace + doc_block_prefix_len
|
||||
<= start_point.column as usize
|
||||
} else if doc_line_candidate
|
||||
.starts_with(doc_comment_prefix.as_ref())
|
||||
{
|
||||
index_of_first_non_whitespace + doc_comment_prefix.len()
|
||||
<= start_point.column as usize
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let cursor_is_before_suffix_if_exits = {
|
||||
let whitespace_char_from_last = snapshot
|
||||
.reversed_chars_for_range(range.clone())
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.count();
|
||||
let mut line_rev_iter = snapshot
|
||||
.reversed_chars_for_range(range)
|
||||
.skip(whitespace_char_from_last);
|
||||
let suffix_exists = doc_block_suffix
|
||||
.chars()
|
||||
.rev()
|
||||
.all(|char| line_rev_iter.next() == Some(char));
|
||||
if suffix_exists {
|
||||
let max_point =
|
||||
snapshot.line_len(start_point.row) as usize;
|
||||
let cursor_is_before_suffix = whitespace_char_from_last
|
||||
+ doc_block_suffix.len()
|
||||
+ start_point.column as usize
|
||||
<= max_point;
|
||||
if cursor_is_before_suffix {
|
||||
let cursor_is_before_end_tag_if_exists = {
|
||||
let num_of_whitespaces_rev = snapshot
|
||||
.reversed_chars_for_range(range.clone())
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.count();
|
||||
let mut line_iter = snapshot
|
||||
.reversed_chars_for_range(range)
|
||||
.skip(num_of_whitespaces_rev);
|
||||
let end_tag_exists = end_tag
|
||||
.chars()
|
||||
.rev()
|
||||
.all(|char| line_iter.next() == Some(char));
|
||||
if end_tag_exists {
|
||||
let max_point = snapshot.line_len(start_point.row) as usize;
|
||||
let ordering = (num_of_whitespaces_rev
|
||||
+ end_tag.len()
|
||||
+ start_point.column as usize)
|
||||
.cmp(&max_point);
|
||||
let cursor_is_before_end_tag =
|
||||
ordering != Ordering::Greater;
|
||||
if cursor_is_after_start_tag {
|
||||
if cursor_is_before_end_tag {
|
||||
insert_extra_newline = true;
|
||||
}
|
||||
cursor_is_before_suffix
|
||||
} else {
|
||||
true
|
||||
let cursor_is_at_start_of_end_tag =
|
||||
ordering == Ordering::Equal;
|
||||
if cursor_is_at_start_of_end_tag {
|
||||
indent_on_extra_newline.len = (*len).into();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if cursor_is_after_prefix && cursor_is_before_suffix_if_exits {
|
||||
Some(doc_comment_prefix.clone())
|
||||
cursor_is_before_end_tag
|
||||
} else {
|
||||
None
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
(comment_delimiter, insert_extra_newline)
|
||||
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).into();
|
||||
}
|
||||
Some(delimiter.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
(
|
||||
comment_delimiter,
|
||||
doc_delimiter,
|
||||
insert_extra_newline,
|
||||
indent_on_newline,
|
||||
indent_on_extra_newline,
|
||||
)
|
||||
} else {
|
||||
(None, false)
|
||||
(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
IndentSize::default(),
|
||||
IndentSize::default(),
|
||||
)
|
||||
};
|
||||
|
||||
let capacity_for_delimiter = comment_delimiter
|
||||
.as_deref()
|
||||
.map(str::len)
|
||||
.unwrap_or_default();
|
||||
let mut new_text =
|
||||
String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
|
||||
new_text.push('\n');
|
||||
new_text.extend(indent.chars());
|
||||
let prevent_auto_indent = doc_delimiter.is_some();
|
||||
let delimiter = comment_delimiter.or(doc_delimiter);
|
||||
|
||||
if let Some(delimiter) = &comment_delimiter {
|
||||
let capacity_for_delimiter =
|
||||
delimiter.as_deref().map(str::len).unwrap_or_default();
|
||||
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,
|
||||
);
|
||||
new_text.push('\n');
|
||||
new_text.extend(existing_indent.chars());
|
||||
new_text.extend(indent_on_newline.chars());
|
||||
|
||||
if let Some(delimiter) = &delimiter {
|
||||
new_text.push_str(delimiter);
|
||||
}
|
||||
|
||||
if insert_extra_newline {
|
||||
new_text.push('\n');
|
||||
new_text.extend(indent.chars());
|
||||
new_text.extend(existing_indent.chars());
|
||||
new_text.extend(indent_on_extra_newline.chars());
|
||||
}
|
||||
|
||||
let anchor = buffer.anchor_after(end);
|
||||
let new_selection = selection.map(|_| anchor);
|
||||
(
|
||||
(start..end, new_text),
|
||||
((start..end, new_text), prevent_auto_indent),
|
||||
(insert_extra_newline, new_selection),
|
||||
)
|
||||
})
|
||||
.unzip()
|
||||
};
|
||||
|
||||
this.edit_with_autoindent(edits, cx);
|
||||
let mut auto_indent_edits = Vec::new();
|
||||
let mut edits = Vec::new();
|
||||
for (edit, prevent_auto_indent) in edits_with_flags {
|
||||
if prevent_auto_indent {
|
||||
edits.push(edit);
|
||||
} else {
|
||||
auto_indent_edits.push(edit);
|
||||
}
|
||||
}
|
||||
if !edits.is_empty() {
|
||||
this.edit(edits, cx);
|
||||
}
|
||||
if !auto_indent_edits.is_empty() {
|
||||
this.edit_with_autoindent(auto_indent_edits, cx);
|
||||
}
|
||||
|
||||
let buffer = this.buffer.read(cx).snapshot(cx);
|
||||
let new_selections = selection_fixup_info
|
||||
let new_selections = selection_info
|
||||
.into_iter()
|
||||
.map(|(extra_newline_inserted, new_selection)| {
|
||||
let mut cursor = new_selection.end.to_point(&buffer);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue