Add a setting for ensuring a single final newline on save
This commit is contained in:
parent
7faa0da5c7
commit
a890b8f3b7
5 changed files with 88 additions and 20 deletions
|
@ -51,8 +51,12 @@
|
||||||
// 3. Position the dock full screen over the entire workspace"
|
// 3. Position the dock full screen over the entire workspace"
|
||||||
// "default_dock_anchor": "expanded"
|
// "default_dock_anchor": "expanded"
|
||||||
"default_dock_anchor": "right",
|
"default_dock_anchor": "right",
|
||||||
// Whether or not to remove trailing whitespace from lines before saving.
|
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||||
|
// before saving it.
|
||||||
"remove_trailing_whitespace_on_save": true,
|
"remove_trailing_whitespace_on_save": true,
|
||||||
|
// Whether or not to ensure there's a single newline at the end of a buffer
|
||||||
|
// when saving it.
|
||||||
|
"ensure_final_newline_on_save": true,
|
||||||
// Whether or not to perform a buffer format before saving
|
// Whether or not to perform a buffer format before saving
|
||||||
"format_on_save": "on",
|
"format_on_save": "on",
|
||||||
// How to perform a buffer format. This setting can take two values:
|
// How to perform a buffer format. This setting can take two values:
|
||||||
|
|
|
@ -4314,12 +4314,13 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Set up a buffer white some trailing whitespace.
|
// Set up a buffer white some trailing whitespace and no trailing newline.
|
||||||
cx.set_state(
|
cx.set_state(
|
||||||
&[
|
&[
|
||||||
"one ", //
|
"one ", //
|
||||||
"twoˇ", //
|
"twoˇ", //
|
||||||
"three ", //
|
"three ", //
|
||||||
|
"four", //
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
);
|
);
|
||||||
|
@ -4348,7 +4349,8 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
|
cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
|
||||||
let buffer_changes = buffer_changes.clone();
|
let buffer_changes = buffer_changes.clone();
|
||||||
move |_, _| {
|
move |_, _| {
|
||||||
// When formatting is requested, trailing whitespace has already been stripped.
|
// When formatting is requested, trailing whitespace has already been stripped,
|
||||||
|
// and the trailing newline has already been added.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&buffer_changes.lock()[1..],
|
&buffer_changes.lock()[1..],
|
||||||
&[
|
&[
|
||||||
|
@ -4360,6 +4362,10 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
|
lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
|
||||||
"".into()
|
"".into()
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
|
||||||
|
"\n".into()
|
||||||
|
),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4379,8 +4385,9 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// After formatting the buffer, the trailing whitespace is stripped and the
|
// After formatting the buffer, the trailing whitespace is stripped,
|
||||||
// edits provided by the language server have been applied.
|
// a newline is appended, and the edits provided by the language server
|
||||||
|
// have been applied.
|
||||||
format.await.unwrap();
|
format.await.unwrap();
|
||||||
cx.assert_editor_state(
|
cx.assert_editor_state(
|
||||||
&[
|
&[
|
||||||
|
@ -4389,18 +4396,21 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
"twoˇ", //
|
"twoˇ", //
|
||||||
"", //
|
"", //
|
||||||
"three", //
|
"three", //
|
||||||
|
"four", //
|
||||||
|
"", //
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Undoing the formatting undoes both the trailing whitespace removal and the
|
// Undoing the formatting undoes the trailing whitespace removal, the
|
||||||
// LSP edits.
|
// trailing newline, and the LSP edits.
|
||||||
cx.update_buffer(|buffer, cx| buffer.undo(cx));
|
cx.update_buffer(|buffer, cx| buffer.undo(cx));
|
||||||
cx.assert_editor_state(
|
cx.assert_editor_state(
|
||||||
&[
|
&[
|
||||||
"one ", //
|
"one ", //
|
||||||
"twoˇ", //
|
"twoˇ", //
|
||||||
"three ", //
|
"three ", //
|
||||||
|
"four", //
|
||||||
]
|
]
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1156,6 +1156,8 @@ impl Buffer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawn 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<Diff> {
|
pub fn remove_trailing_whitespace(&self, cx: &AppContext) -> Task<Diff> {
|
||||||
let old_text = self.as_rope().clone();
|
let old_text = self.as_rope().clone();
|
||||||
let line_ending = self.line_ending();
|
let line_ending = self.line_ending();
|
||||||
|
@ -1174,6 +1176,27 @@ impl Buffer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure that the buffer ends with a single newline character, and
|
||||||
|
/// no other whitespace.
|
||||||
|
pub fn ensure_final_newline(&mut self, cx: &mut ModelContext<Self>) {
|
||||||
|
let len = self.len();
|
||||||
|
let mut offset = len;
|
||||||
|
for chunk in self.as_rope().reversed_chunks_in_range(0..len) {
|
||||||
|
let non_whitespace_len = chunk
|
||||||
|
.trim_end_matches(|c: char| c.is_ascii_whitespace())
|
||||||
|
.len();
|
||||||
|
offset -= chunk.len();
|
||||||
|
offset += non_whitespace_len;
|
||||||
|
if non_whitespace_len != 0 {
|
||||||
|
if offset == len - 1 && chunk.get(non_whitespace_len..) == Some("\n") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.edit([(offset..len, "\n")], None, cx);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
|
pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
|
||||||
if self.version == diff.base_version {
|
if self.version == diff.base_version {
|
||||||
self.apply_non_conflicting_portion_of_diff(diff, cx)
|
self.apply_non_conflicting_portion_of_diff(diff, cx)
|
||||||
|
@ -1182,6 +1205,9 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply 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_non_conflicting_portion_of_diff(
|
pub fn apply_non_conflicting_portion_of_diff(
|
||||||
&mut self,
|
&mut self,
|
||||||
diff: Diff,
|
diff: Diff,
|
||||||
|
|
|
@ -2887,18 +2887,23 @@ impl Project {
|
||||||
|
|
||||||
let mut project_transaction = ProjectTransaction::default();
|
let mut project_transaction = ProjectTransaction::default();
|
||||||
for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
|
for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
|
||||||
let (format_on_save, remove_trailing_whitespace, formatter, tab_size) = buffer
|
let (
|
||||||
.read_with(&cx, |buffer, cx| {
|
format_on_save,
|
||||||
let settings = cx.global::<Settings>();
|
remove_trailing_whitespace,
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
ensure_final_newline,
|
||||||
(
|
formatter,
|
||||||
settings.format_on_save(language_name.as_deref()),
|
tab_size,
|
||||||
settings
|
) = buffer.read_with(&cx, |buffer, cx| {
|
||||||
.remove_trailing_whitespace_on_save(language_name.as_deref()),
|
let settings = cx.global::<Settings>();
|
||||||
settings.formatter(language_name.as_deref()),
|
let language_name = buffer.language().map(|language| language.name());
|
||||||
settings.tab_size(language_name.as_deref()),
|
(
|
||||||
)
|
settings.format_on_save(language_name.as_deref()),
|
||||||
});
|
settings.remove_trailing_whitespace_on_save(language_name.as_deref()),
|
||||||
|
settings.ensure_final_newline_on_save(language_name.as_deref()),
|
||||||
|
settings.formatter(language_name.as_deref()),
|
||||||
|
settings.tab_size(language_name.as_deref()),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let whitespace_transaction_id = if remove_trailing_whitespace {
|
let whitespace_transaction_id = if remove_trailing_whitespace {
|
||||||
let diff = buffer
|
let diff = buffer
|
||||||
|
@ -2906,7 +2911,19 @@ impl Project {
|
||||||
.await;
|
.await;
|
||||||
buffer.update(&mut cx, move |buffer, cx| {
|
buffer.update(&mut cx, move |buffer, cx| {
|
||||||
buffer.finalize_last_transaction();
|
buffer.finalize_last_transaction();
|
||||||
buffer.apply_non_conflicting_portion_of_diff(diff, cx)
|
buffer.start_transaction();
|
||||||
|
buffer.apply_non_conflicting_portion_of_diff(diff, cx);
|
||||||
|
if ensure_final_newline {
|
||||||
|
buffer.ensure_final_newline(cx);
|
||||||
|
}
|
||||||
|
buffer.end_transaction(cx)
|
||||||
|
})
|
||||||
|
} else if ensure_final_newline {
|
||||||
|
buffer.update(&mut cx, move |buffer, cx| {
|
||||||
|
buffer.finalize_last_transaction();
|
||||||
|
buffer.start_transaction();
|
||||||
|
buffer.ensure_final_newline(cx);
|
||||||
|
buffer.end_transaction(cx)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -95,6 +95,7 @@ pub struct EditorSettings {
|
||||||
pub preferred_line_length: Option<u32>,
|
pub preferred_line_length: Option<u32>,
|
||||||
pub format_on_save: Option<FormatOnSave>,
|
pub format_on_save: Option<FormatOnSave>,
|
||||||
pub remove_trailing_whitespace_on_save: Option<bool>,
|
pub remove_trailing_whitespace_on_save: Option<bool>,
|
||||||
|
pub ensure_final_newline_on_save: Option<bool>,
|
||||||
pub formatter: Option<Formatter>,
|
pub formatter: Option<Formatter>,
|
||||||
pub enable_language_server: Option<bool>,
|
pub enable_language_server: Option<bool>,
|
||||||
}
|
}
|
||||||
|
@ -365,6 +366,9 @@ impl Settings {
|
||||||
remove_trailing_whitespace_on_save: required(
|
remove_trailing_whitespace_on_save: required(
|
||||||
defaults.editor.remove_trailing_whitespace_on_save,
|
defaults.editor.remove_trailing_whitespace_on_save,
|
||||||
),
|
),
|
||||||
|
ensure_final_newline_on_save: required(
|
||||||
|
defaults.editor.ensure_final_newline_on_save,
|
||||||
|
),
|
||||||
format_on_save: required(defaults.editor.format_on_save),
|
format_on_save: required(defaults.editor.format_on_save),
|
||||||
formatter: required(defaults.editor.formatter),
|
formatter: required(defaults.editor.formatter),
|
||||||
enable_language_server: required(defaults.editor.enable_language_server),
|
enable_language_server: required(defaults.editor.enable_language_server),
|
||||||
|
@ -470,6 +474,12 @@ impl Settings {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
|
||||||
|
self.language_setting(language, |settings| {
|
||||||
|
settings.ensure_final_newline_on_save.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
|
pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
|
||||||
self.language_setting(language, |settings| settings.format_on_save.clone())
|
self.language_setting(language, |settings| settings.format_on_save.clone())
|
||||||
}
|
}
|
||||||
|
@ -569,6 +579,7 @@ impl Settings {
|
||||||
soft_wrap: Some(SoftWrap::None),
|
soft_wrap: Some(SoftWrap::None),
|
||||||
preferred_line_length: Some(80),
|
preferred_line_length: Some(80),
|
||||||
remove_trailing_whitespace_on_save: Some(true),
|
remove_trailing_whitespace_on_save: Some(true),
|
||||||
|
ensure_final_newline_on_save: Some(true),
|
||||||
format_on_save: Some(FormatOnSave::On),
|
format_on_save: Some(FormatOnSave::On),
|
||||||
formatter: Some(Formatter::LanguageServer),
|
formatter: Some(Formatter::LanguageServer),
|
||||||
enable_language_server: Some(true),
|
enable_language_server: Some(true),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue