mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-01 15:03:10 +03:00
Merge pull request #1246 from zed-industries/python-autoindent
Fix Python auto-indent using new auto-indent features
This commit is contained in:
commit
6cf9514e00
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -2397,6 +2397,7 @@ dependencies = [
|
||||
"parking_lot 0.11.2",
|
||||
"postage",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -2408,6 +2409,7 @@ dependencies = [
|
||||
"theme",
|
||||
"tree-sitter",
|
||||
"tree-sitter-json 0.19.0",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"unindent",
|
||||
@ -5214,9 +5216,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.20.7"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "549a9faf45679ad50b7f603253635598cf5e007d8ceb806a23f95355938f76a0"
|
||||
checksum = "268bf3e3ca0c09e5d21b59c2638e12cb6dcf7ea2681250a696a2d0936cb57ba0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
@ -5281,9 +5283,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-python"
|
||||
version = "0.20.1"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713170684ba94376b784b0c6dd23693461e15f96a806ed1848e40996e3cda7c7"
|
||||
checksum = "dda114f58048f5059dcf158aff691dffb8e113e6d2b50d94263fd68711975287"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
|
@ -40,6 +40,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
parking_lot = "0.11.1"
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
rand = { version = "0.8.3", optional = true }
|
||||
regex = "1.5"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
similar = "1.3"
|
||||
@ -61,5 +62,6 @@ env_logger = "0.9"
|
||||
rand = "0.8.3"
|
||||
tree-sitter-json = "*"
|
||||
tree-sitter-rust = "*"
|
||||
tree-sitter-python = "*"
|
||||
tree-sitter-typescript = "*"
|
||||
unindent = "0.1.7"
|
||||
|
@ -237,7 +237,7 @@ struct AutoindentRequest {
|
||||
#[derive(Debug)]
|
||||
struct IndentSuggestion {
|
||||
basis_row: u32,
|
||||
indent: bool,
|
||||
delta: Ordering,
|
||||
}
|
||||
|
||||
pub(crate) struct TextProvider<'a>(pub(crate) &'a Rope);
|
||||
@ -812,19 +812,23 @@ impl Buffer {
|
||||
.into_iter()
|
||||
.flatten();
|
||||
for (old_row, suggestion) in old_edited_range.zip(suggestions) {
|
||||
let mut suggested_indent = old_to_new_rows
|
||||
.get(&suggestion.basis_row)
|
||||
.and_then(|from_row| old_suggestions.get(from_row).copied())
|
||||
.unwrap_or_else(|| {
|
||||
request
|
||||
.before_edit
|
||||
.indent_size_for_line(suggestion.basis_row)
|
||||
});
|
||||
if suggestion.indent {
|
||||
suggested_indent += request.indent_size;
|
||||
if let Some(suggestion) = suggestion {
|
||||
let mut suggested_indent = old_to_new_rows
|
||||
.get(&suggestion.basis_row)
|
||||
.and_then(|from_row| old_suggestions.get(from_row).copied())
|
||||
.unwrap_or_else(|| {
|
||||
request
|
||||
.before_edit
|
||||
.indent_size_for_line(suggestion.basis_row)
|
||||
});
|
||||
if suggestion.delta.is_gt() {
|
||||
suggested_indent += request.indent_size;
|
||||
} else if suggestion.delta.is_lt() {
|
||||
suggested_indent -= request.indent_size;
|
||||
}
|
||||
old_suggestions
|
||||
.insert(*old_to_new_rows.get(&old_row).unwrap(), suggested_indent);
|
||||
}
|
||||
old_suggestions
|
||||
.insert(*old_to_new_rows.get(&old_row).unwrap(), suggested_indent);
|
||||
}
|
||||
yield_now().await;
|
||||
}
|
||||
@ -839,18 +843,26 @@ impl Buffer {
|
||||
.into_iter()
|
||||
.flatten();
|
||||
for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
|
||||
let mut suggested_indent = indent_sizes
|
||||
.get(&suggestion.basis_row)
|
||||
.copied()
|
||||
.unwrap_or_else(|| snapshot.indent_size_for_line(suggestion.basis_row));
|
||||
if suggestion.indent {
|
||||
suggested_indent += request.indent_size;
|
||||
}
|
||||
if old_suggestions
|
||||
.get(&new_row)
|
||||
.map_or(true, |old_indentation| suggested_indent != *old_indentation)
|
||||
{
|
||||
indent_sizes.insert(new_row, suggested_indent);
|
||||
if let Some(suggestion) = suggestion {
|
||||
let mut suggested_indent = indent_sizes
|
||||
.get(&suggestion.basis_row)
|
||||
.copied()
|
||||
.unwrap_or_else(|| {
|
||||
snapshot.indent_size_for_line(suggestion.basis_row)
|
||||
});
|
||||
if suggestion.delta.is_gt() {
|
||||
suggested_indent += request.indent_size;
|
||||
} else if suggestion.delta.is_lt() {
|
||||
suggested_indent -= request.indent_size;
|
||||
}
|
||||
if old_suggestions
|
||||
.get(&new_row)
|
||||
.map_or(true, |old_indentation| {
|
||||
suggested_indent != *old_indentation
|
||||
})
|
||||
{
|
||||
indent_sizes.insert(new_row, suggested_indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
yield_now().await;
|
||||
@ -870,16 +882,20 @@ impl Buffer {
|
||||
.into_iter()
|
||||
.flatten();
|
||||
for (row, suggestion) in inserted_row_range.zip(suggestions) {
|
||||
let mut suggested_indent = indent_sizes
|
||||
.get(&suggestion.basis_row)
|
||||
.copied()
|
||||
.unwrap_or_else(|| {
|
||||
snapshot.indent_size_for_line(suggestion.basis_row)
|
||||
});
|
||||
if suggestion.indent {
|
||||
suggested_indent += request.indent_size;
|
||||
if let Some(suggestion) = suggestion {
|
||||
let mut suggested_indent = indent_sizes
|
||||
.get(&suggestion.basis_row)
|
||||
.copied()
|
||||
.unwrap_or_else(|| {
|
||||
snapshot.indent_size_for_line(suggestion.basis_row)
|
||||
});
|
||||
if suggestion.delta.is_gt() {
|
||||
suggested_indent += request.indent_size;
|
||||
} else if suggestion.delta.is_lt() {
|
||||
suggested_indent -= request.indent_size;
|
||||
}
|
||||
indent_sizes.insert(row, suggested_indent);
|
||||
}
|
||||
indent_sizes.insert(row, suggested_indent);
|
||||
}
|
||||
yield_now().await;
|
||||
}
|
||||
@ -1551,10 +1567,13 @@ impl BufferSnapshot {
|
||||
fn suggest_autoindents<'a>(
|
||||
&'a self,
|
||||
row_range: Range<u32>,
|
||||
) -> Option<impl Iterator<Item = IndentSuggestion> + 'a> {
|
||||
// Get the "indentation ranges" that intersect this row range.
|
||||
let grammar = self.grammar()?;
|
||||
) -> Option<impl Iterator<Item = Option<IndentSuggestion>> + 'a> {
|
||||
let language = self.language.as_ref()?;
|
||||
let grammar = language.grammar.as_ref()?;
|
||||
let config = &language.config;
|
||||
let prev_non_blank_row = self.prev_non_blank_row(row_range.start);
|
||||
|
||||
// Find the suggested indentation ranges based on the syntax tree.
|
||||
let indents_query = grammar.indents_query.as_ref()?;
|
||||
let mut query_cursor = QueryCursorHandle::new();
|
||||
let indent_capture_ix = indents_query.capture_index_for_name("indent");
|
||||
@ -1563,6 +1582,7 @@ impl BufferSnapshot {
|
||||
Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).to_ts_point()
|
||||
..Point::new(row_range.end, 0).to_ts_point(),
|
||||
);
|
||||
|
||||
let mut indentation_ranges = Vec::<Range<Point>>::new();
|
||||
for mat in query_cursor.matches(
|
||||
indents_query,
|
||||
@ -1596,48 +1616,98 @@ impl BufferSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
let mut prev_row = prev_non_blank_row.unwrap_or(0);
|
||||
// Find the suggested indentation increases and decreased based on regexes.
|
||||
let mut indent_changes = Vec::<(u32, Ordering)>::new();
|
||||
self.for_each_line(
|
||||
Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0)
|
||||
..Point::new(row_range.end, 0),
|
||||
|row, line| {
|
||||
if config
|
||||
.decrease_indent_pattern
|
||||
.as_ref()
|
||||
.map_or(false, |regex| regex.is_match(line))
|
||||
{
|
||||
indent_changes.push((row, Ordering::Less));
|
||||
}
|
||||
if config
|
||||
.increase_indent_pattern
|
||||
.as_ref()
|
||||
.map_or(false, |regex| regex.is_match(line))
|
||||
{
|
||||
indent_changes.push((row + 1, Ordering::Greater));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let mut indent_changes = indent_changes.into_iter().peekable();
|
||||
let mut prev_row = row_range.start.saturating_sub(1);
|
||||
let mut prev_row_start = Point::new(prev_row, self.indent_size_for_line(prev_row).len);
|
||||
Some(row_range.map(move |row| {
|
||||
let row_start = Point::new(row, self.indent_size_for_line(row).len);
|
||||
|
||||
let mut indent_from_prev_row = false;
|
||||
let mut outdent_from_prev_row = false;
|
||||
let mut outdent_to_row = u32::MAX;
|
||||
|
||||
while let Some((indent_row, delta)) = indent_changes.peek() {
|
||||
if *indent_row == row {
|
||||
match delta {
|
||||
Ordering::Less => outdent_from_prev_row = true,
|
||||
Ordering::Greater => indent_from_prev_row = true,
|
||||
_ => {}
|
||||
}
|
||||
} else if *indent_row > row {
|
||||
break;
|
||||
}
|
||||
indent_changes.next();
|
||||
}
|
||||
|
||||
for range in &indentation_ranges {
|
||||
if range.start.row >= row {
|
||||
break;
|
||||
}
|
||||
|
||||
if range.start.row == prev_row && range.end > row_start {
|
||||
indent_from_prev_row = true;
|
||||
}
|
||||
if range.end.row >= prev_row && range.end <= row_start {
|
||||
if range.end > prev_row_start && range.end <= row_start {
|
||||
outdent_to_row = outdent_to_row.min(range.start.row);
|
||||
}
|
||||
}
|
||||
|
||||
let suggestion = if outdent_to_row == prev_row {
|
||||
IndentSuggestion {
|
||||
let suggestion = if outdent_to_row == prev_row
|
||||
|| (outdent_from_prev_row && indent_from_prev_row)
|
||||
{
|
||||
Some(IndentSuggestion {
|
||||
basis_row: prev_row,
|
||||
indent: false,
|
||||
}
|
||||
delta: Ordering::Equal,
|
||||
})
|
||||
} else if indent_from_prev_row {
|
||||
IndentSuggestion {
|
||||
Some(IndentSuggestion {
|
||||
basis_row: prev_row,
|
||||
indent: true,
|
||||
}
|
||||
delta: Ordering::Greater,
|
||||
})
|
||||
} else if outdent_to_row < prev_row {
|
||||
IndentSuggestion {
|
||||
Some(IndentSuggestion {
|
||||
basis_row: outdent_to_row,
|
||||
indent: false,
|
||||
}
|
||||
} else {
|
||||
IndentSuggestion {
|
||||
delta: Ordering::Equal,
|
||||
})
|
||||
} else if outdent_from_prev_row {
|
||||
Some(IndentSuggestion {
|
||||
basis_row: prev_row,
|
||||
indent: false,
|
||||
}
|
||||
delta: Ordering::Less,
|
||||
})
|
||||
} else if config.auto_indent_using_last_non_empty_line || !self.is_line_blank(prev_row)
|
||||
{
|
||||
Some(IndentSuggestion {
|
||||
basis_row: prev_row,
|
||||
delta: Ordering::Equal,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
prev_row = row;
|
||||
prev_row_start = row_start;
|
||||
suggestion
|
||||
}))
|
||||
}
|
||||
@ -1690,6 +1760,25 @@ impl BufferSnapshot {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn for_each_line<'a>(&'a self, range: Range<Point>, mut callback: impl FnMut(u32, &str)) {
|
||||
let mut line = String::new();
|
||||
let mut row = range.start.row;
|
||||
for chunk in self
|
||||
.as_rope()
|
||||
.chunks_in_range(range.to_offset(self))
|
||||
.chain(["\n"])
|
||||
{
|
||||
for (newline_ix, text) in chunk.split('\n').enumerate() {
|
||||
if newline_ix > 0 {
|
||||
callback(row, &line);
|
||||
row += 1;
|
||||
line.clear();
|
||||
}
|
||||
line.push_str(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language(&self) -> Option<&Arc<Language>> {
|
||||
self.language.as_ref()
|
||||
}
|
||||
@ -2411,6 +2500,14 @@ impl std::ops::AddAssign for IndentSize {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign for IndentSize {
|
||||
fn sub_assign(&mut self, other: IndentSize) {
|
||||
if self.kind == other.kind && self.len >= other.len {
|
||||
self.len -= other.len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
pub fn sort_key(&self) -> (usize, &str) {
|
||||
let kind_key = match self.lsp_completion.kind {
|
||||
|
@ -17,7 +17,8 @@ use gpui::{MutableAppContext, Task};
|
||||
use highlight_map::HighlightMap;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use serde::Deserialize;
|
||||
use regex::Regex;
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
any::Any,
|
||||
@ -49,10 +50,7 @@ lazy_static! {
|
||||
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: "Plain Text".into(),
|
||||
path_suffixes: Default::default(),
|
||||
brackets: Default::default(),
|
||||
autoclose_before: Default::default(),
|
||||
line_comment: None,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
@ -123,6 +121,12 @@ pub struct LanguageConfig {
|
||||
pub name: Arc<str>,
|
||||
pub path_suffixes: Vec<String>,
|
||||
pub brackets: Vec<BracketPair>,
|
||||
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
|
||||
pub auto_indent_using_last_non_empty_line: bool,
|
||||
#[serde(default, deserialize_with = "deserialize_regex")]
|
||||
pub increase_indent_pattern: Option<Regex>,
|
||||
#[serde(default, deserialize_with = "deserialize_regex")]
|
||||
pub decrease_indent_pattern: Option<Regex>,
|
||||
#[serde(default)]
|
||||
pub autoclose_before: String,
|
||||
pub line_comment: Option<String>,
|
||||
@ -134,12 +138,28 @@ impl Default for LanguageConfig {
|
||||
name: "".into(),
|
||||
path_suffixes: Default::default(),
|
||||
brackets: Default::default(),
|
||||
auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
|
||||
increase_indent_pattern: Default::default(),
|
||||
decrease_indent_pattern: Default::default(),
|
||||
autoclose_before: Default::default(),
|
||||
line_comment: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_indent_using_last_non_empty_line_default() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D::Error> {
|
||||
let source = Option::<String>::deserialize(d)?;
|
||||
if let Some(source) = source {
|
||||
Ok(Some(regex::Regex::new(&source).map_err(de::Error::custom)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct FakeLspAdapter {
|
||||
pub name: &'static str,
|
||||
|
@ -87,14 +87,14 @@ tempdir = { version = "0.3.7" }
|
||||
thiserror = "1.0.29"
|
||||
tiny_http = "0.8"
|
||||
toml = "0.5"
|
||||
tree-sitter = "0.20.7"
|
||||
tree-sitter = "0.20.8"
|
||||
tree-sitter-c = "0.20.1"
|
||||
tree-sitter-cpp = "0.20.0"
|
||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" }
|
||||
tree-sitter-rust = "0.20.1"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-python = "0.20.1"
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
|
||||
tree-sitter-typescript = "0.20.1"
|
||||
url = "2.2"
|
||||
|
@ -256,3 +256,41 @@ impl super::LspAdapter for CLspAdapter {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::MutableAppContext;
|
||||
use language::{Buffer, IndentSize};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_c_autoindent(cx: &mut MutableAppContext) {
|
||||
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
let language = crate::languages::language("c", tree_sitter_c::language(), None);
|
||||
|
||||
cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
|
||||
let size = IndentSize::spaces(2);
|
||||
|
||||
// empty function
|
||||
buffer.edit_with_autoindent([(0..0, "int main() {}")], size, cx);
|
||||
|
||||
// indent inside braces
|
||||
let ix = buffer.len() - 1;
|
||||
buffer.edit_with_autoindent([(ix..ix, "\n\n")], size, cx);
|
||||
assert_eq!(buffer.text(), "int main() {\n \n}");
|
||||
|
||||
// indent body of single-statement if statement
|
||||
let ix = buffer.len() - 2;
|
||||
buffer.edit_with_autoindent([(ix..ix, "if (a)\nb;")], size, cx);
|
||||
assert_eq!(buffer.text(), "int main() {\n if (a)\n b;\n}");
|
||||
|
||||
// indent inside field expression
|
||||
let ix = buffer.len() - 3;
|
||||
buffer.edit_with_autoindent([(ix..ix, "\n.c")], size, cx);
|
||||
assert_eq!(buffer.text(), "int main() {\n if (a)\n b\n .c;\n}");
|
||||
|
||||
buffer
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
[
|
||||
(field_expression)
|
||||
(assignment_expression)
|
||||
(field_expression)
|
||||
(assignment_expression)
|
||||
(if_statement)
|
||||
(for_statement)
|
||||
] @indent
|
||||
|
||||
(_ "{" "}" @end) @indent
|
||||
|
@ -151,3 +151,103 @@ impl LspAdapter for PythonLspAdapter {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{ModelContext, MutableAppContext};
|
||||
use language::{Buffer, IndentSize};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_python_autoindent(cx: &mut MutableAppContext) {
|
||||
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
let language = crate::languages::language("python", tree_sitter_python::language(), None);
|
||||
|
||||
cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
|
||||
let size = IndentSize::spaces(2);
|
||||
let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
|
||||
let ix = buffer.len();
|
||||
buffer.edit_with_autoindent([(ix..ix, text)], size, cx);
|
||||
};
|
||||
|
||||
// indent after "def():"
|
||||
append(&mut buffer, "def a():\n", cx);
|
||||
assert_eq!(buffer.text(), "def a():\n ");
|
||||
|
||||
// preserve indent after blank line
|
||||
append(&mut buffer, "\n ", cx);
|
||||
assert_eq!(buffer.text(), "def a():\n \n ");
|
||||
|
||||
// indent after "if"
|
||||
append(&mut buffer, "if a:\n ", cx);
|
||||
assert_eq!(buffer.text(), "def a():\n \n if a:\n ");
|
||||
|
||||
// preserve indent after statement
|
||||
append(&mut buffer, "b()\n", cx);
|
||||
assert_eq!(buffer.text(), "def a():\n \n if a:\n b()\n ");
|
||||
|
||||
// preserve indent after statement
|
||||
append(&mut buffer, "else", cx);
|
||||
assert_eq!(buffer.text(), "def a():\n \n if a:\n b()\n else");
|
||||
|
||||
// dedent "else""
|
||||
append(&mut buffer, ":", cx);
|
||||
assert_eq!(buffer.text(), "def a():\n \n if a:\n b()\n else:");
|
||||
|
||||
// indent lines after else
|
||||
append(&mut buffer, "\n", cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"def a():\n \n if a:\n b()\n else:\n "
|
||||
);
|
||||
|
||||
// indent after an open paren. the closing paren is not indented
|
||||
// because there is another token before it on the same line.
|
||||
append(&mut buffer, "foo(\n1)", cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"def a():\n \n if a:\n b()\n else:\n foo(\n 1)"
|
||||
);
|
||||
|
||||
// dedent the closing paren if it is shifted to the beginning of the line
|
||||
let argument_ix = buffer.text().find("1").unwrap();
|
||||
buffer.edit_with_autoindent([(argument_ix..argument_ix + 1, "")], size, cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"def a():\n \n if a:\n b()\n else:\n foo(\n )"
|
||||
);
|
||||
|
||||
// preserve indent after the close paren
|
||||
append(&mut buffer, "\n", cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"def a():\n \n if a:\n b()\n else:\n foo(\n )\n "
|
||||
);
|
||||
|
||||
// manually outdent the last line
|
||||
let end_whitespace_ix = buffer.len() - 4;
|
||||
buffer.edit_with_autoindent([(end_whitespace_ix..buffer.len(), "")], size, cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"def a():\n \n if a:\n b()\n else:\n foo(\n )\n"
|
||||
);
|
||||
|
||||
// preserve the newly reduced indentation on the next newline
|
||||
append(&mut buffer, "\n", cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"def a():\n \n if a:\n b()\n else:\n foo(\n )\n\n"
|
||||
);
|
||||
|
||||
// reset to a simple if statement
|
||||
buffer.edit([(0..buffer.len(), "if a:\n b(\n )")], cx);
|
||||
|
||||
// dedent "else" on the line after a closing paren
|
||||
append(&mut buffer, "\n else:\n", cx);
|
||||
assert_eq!(buffer.text(), "if a:\n b(\n )\nelse:\n ");
|
||||
|
||||
buffer
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,3 +9,7 @@ brackets = [
|
||||
{ start = "\"", end = "\"", close = true, newline = false },
|
||||
{ start = "'", end = "'", close = false, newline = false },
|
||||
]
|
||||
|
||||
auto_indent_using_last_non_empty_line = false
|
||||
increase_indent_pattern = ":$"
|
||||
decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:"
|
@ -1,4 +1,3 @@
|
||||
(_ (block)) @indent
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
||||
|
@ -270,7 +270,7 @@ impl LspAdapter for RustLspAdapter {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::languages::{language, LspAdapter};
|
||||
use gpui::color::Color;
|
||||
use gpui::{color::Color, MutableAppContext};
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
#[test]
|
||||
@ -432,4 +432,42 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_rust_autoindent(cx: &mut MutableAppContext) {
|
||||
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
|
||||
let language = crate::languages::language("rust", tree_sitter_rust::language(), None);
|
||||
|
||||
cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(language), cx);
|
||||
let size = IndentSize::spaces(2);
|
||||
|
||||
// start with empty function
|
||||
buffer.edit_with_autoindent([(0..0, "fn a() {}")], size, cx);
|
||||
|
||||
// indent between braces
|
||||
let ix = buffer.len() - 1;
|
||||
buffer.edit_with_autoindent([(ix..ix, "\n\n")], size, cx);
|
||||
assert_eq!(buffer.text(), "fn a() {\n \n}");
|
||||
|
||||
// indent field expression
|
||||
let ix = buffer.len() - 2;
|
||||
buffer.edit_with_autoindent([(ix..ix, "b\n.c")], size, cx);
|
||||
assert_eq!(buffer.text(), "fn a() {\n b\n .c\n}");
|
||||
|
||||
// indent chained field expression preceded by blank line
|
||||
let ix = buffer.len() - 2;
|
||||
buffer.edit_with_autoindent([(ix..ix, "\n\n.d")], size, cx);
|
||||
assert_eq!(buffer.text(), "fn a() {\n b\n .c\n \n .d\n}");
|
||||
|
||||
// dedent line after the field expression
|
||||
let ix = buffer.len() - 2;
|
||||
buffer.edit_with_autoindent([(ix..ix, ";\ne")], size, cx);
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"fn a() {\n b\n .c\n \n .d;\n e\n}"
|
||||
);
|
||||
buffer
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user