mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 22:01:37 +00:00
make on dot typed actually work
This commit is contained in:
parent
dd122145b5
commit
4aa632761f
3 changed files with 120 additions and 124 deletions
|
@ -15,10 +15,11 @@ use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode,
|
Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode,
|
||||||
algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset},
|
algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset},
|
||||||
ast::{self, AstToken},
|
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::formatting::leading_indent;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
flip_comma::flip_comma,
|
flip_comma::flip_comma,
|
||||||
add_derive::add_derive,
|
add_derive::add_derive,
|
||||||
|
@ -165,7 +166,7 @@ impl AssistBuilder {
|
||||||
}
|
}
|
||||||
fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) {
|
fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) {
|
||||||
let mut replace_with = replace_with.into();
|
let mut replace_with = replace_with.into();
|
||||||
if let Some(indent) = calc_indent(node) {
|
if let Some(indent) = leading_indent(node) {
|
||||||
replace_with = reindent(&replace_with, indent)
|
replace_with = reindent(&replace_with, indent)
|
||||||
}
|
}
|
||||||
self.replace(node.range(), replace_with)
|
self.replace(node.range(), replace_with)
|
||||||
|
@ -182,12 +183,6 @@ impl AssistBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_indent(node: &SyntaxNode) -> Option<&str> {
|
|
||||||
let prev = node.prev_sibling()?;
|
|
||||||
let ws_text = ast::Whitespace::cast(prev)?.text();
|
|
||||||
ws_text.rfind('\n').map(|pos| &ws_text[pos + 1..])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reindent(text: &str, indent: &str) -> String {
|
fn reindent(text: &str, indent: &str) -> String {
|
||||||
let indent = format!("\n{}", indent);
|
let indent = format!("\n{}", indent);
|
||||||
text.lines().intersperse(&indent).collect()
|
text.lines().intersperse(&indent).collect()
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
ast, AstNode,
|
AstNode,
|
||||||
SyntaxNode, SyntaxKind::*,
|
SyntaxNode, SyntaxKind::*,
|
||||||
|
ast::{self, AstToken},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// If the node is on the begining of the line, calculate indent.
|
||||||
|
pub(crate) fn leading_indent(node: &SyntaxNode) -> Option<&str> {
|
||||||
|
let prev = node.prev_sibling()?;
|
||||||
|
let ws_text = ast::Whitespace::cast(prev)?.text();
|
||||||
|
ws_text.rfind('\n').map(|pos| &ws_text[pos + 1..])
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> {
|
pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> {
|
||||||
let expr = block.expr()?;
|
let expr = block.expr()?;
|
||||||
if expr.syntax().text().contains('\n') {
|
if expr.syntax().text().contains('\n') {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
|
AstNode, SourceFile, SyntaxKind::*,
|
||||||
|
SyntaxNode, TextUnit, TextRange,
|
||||||
algo::{find_node_at_offset, find_leaf_at_offset, LeafAtOffset},
|
algo::{find_node_at_offset, find_leaf_at_offset, LeafAtOffset},
|
||||||
ast,
|
ast::{self, AstToken},
|
||||||
AstNode, Direction, SourceFile, SyntaxKind::*,
|
|
||||||
SyntaxNode, TextUnit,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{LocalEdit, TextEditBuilder};
|
use crate::{LocalEdit, TextEditBuilder, formatting::leading_indent};
|
||||||
|
|
||||||
pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> {
|
pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> {
|
||||||
let comment = find_leaf_at_offset(file.syntax(), offset)
|
let comment = find_leaf_at_offset(file.syntax(), offset)
|
||||||
|
@ -53,20 +53,21 @@ fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> {
|
||||||
Some(&text[pos..])
|
Some(&text[pos..])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> {
|
pub fn on_eq_typed(file: &SourceFile, eq_offset: TextUnit) -> Option<LocalEdit> {
|
||||||
let let_stmt: &ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
|
assert_eq!(file.syntax().text().char_at(eq_offset), Some('='));
|
||||||
|
let let_stmt: &ast::LetStmt = find_node_at_offset(file.syntax(), eq_offset)?;
|
||||||
if let_stmt.has_semi() {
|
if let_stmt.has_semi() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Some(expr) = let_stmt.initializer() {
|
if let Some(expr) = let_stmt.initializer() {
|
||||||
let expr_range = expr.syntax().range();
|
let expr_range = expr.syntax().range();
|
||||||
if expr_range.contains(offset) && offset != expr_range.start() {
|
if expr_range.contains(eq_offset) && eq_offset != expr_range.start() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if file
|
if file
|
||||||
.syntax()
|
.syntax()
|
||||||
.text()
|
.text()
|
||||||
.slice(offset..expr_range.start())
|
.slice(eq_offset..expr_range.start())
|
||||||
.contains('\n')
|
.contains('\n')
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
|
@ -84,54 +85,44 @@ pub fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> {
|
pub fn on_dot_typed(file: &SourceFile, dot_offset: TextUnit) -> Option<LocalEdit> {
|
||||||
let before_dot_offset = offset - TextUnit::of_char('.');
|
assert_eq!(file.syntax().text().char_at(dot_offset), Some('.'));
|
||||||
|
|
||||||
let whitespace = find_leaf_at_offset(file.syntax(), before_dot_offset).left_biased()?;
|
let whitespace = find_leaf_at_offset(file.syntax(), dot_offset)
|
||||||
|
.left_biased()
|
||||||
|
.and_then(ast::Whitespace::cast)?;
|
||||||
|
|
||||||
// find whitespace just left of the dot
|
let current_indent = {
|
||||||
ast::Whitespace::cast(whitespace)?;
|
let text = whitespace.text();
|
||||||
|
let newline = text.rfind('\n')?;
|
||||||
|
&text[newline + 1..]
|
||||||
|
};
|
||||||
|
let current_indent_len = TextUnit::of_str(current_indent);
|
||||||
|
|
||||||
// make sure there is a method call
|
// Make sure dot is a part of call chain
|
||||||
let method_call = whitespace
|
let field_expr = whitespace
|
||||||
.siblings(Direction::Prev)
|
.syntax()
|
||||||
// first is whitespace
|
.parent()
|
||||||
.skip(1)
|
.and_then(ast::FieldExpr::cast)?;
|
||||||
.next()?;
|
let prev_indent = leading_indent(field_expr.syntax())?;
|
||||||
|
let target_indent = format!(" {}", prev_indent);
|
||||||
ast::MethodCallExpr::cast(method_call)?;
|
let target_indent_len = TextUnit::of_str(&target_indent);
|
||||||
|
if current_indent_len == target_indent_len {
|
||||||
// find how much the _method call is indented
|
return None;
|
||||||
let method_chain_indent = method_call
|
|
||||||
.parent()?
|
|
||||||
.siblings(Direction::Prev)
|
|
||||||
.skip(1)
|
|
||||||
.next()?
|
|
||||||
.leaf_text()
|
|
||||||
.map(|x| last_line_indent_in_whitespace(x))?;
|
|
||||||
|
|
||||||
let current_indent = TextUnit::of_str(last_line_indent_in_whitespace(whitespace.leaf_text()?));
|
|
||||||
// TODO: indent is always 4 spaces now. A better heuristic could look on the previous line(s)
|
|
||||||
|
|
||||||
let target_indent = TextUnit::of_str(method_chain_indent) + TextUnit::from_usize(4);
|
|
||||||
|
|
||||||
let diff = target_indent - current_indent;
|
|
||||||
|
|
||||||
let indent = "".repeat(diff.to_usize());
|
|
||||||
|
|
||||||
let cursor_position = offset + diff;
|
|
||||||
let mut edit = TextEditBuilder::default();
|
|
||||||
edit.insert(before_dot_offset, indent);
|
|
||||||
Some(LocalEdit {
|
|
||||||
label: "indent dot".to_string(),
|
|
||||||
edit: edit.finish(),
|
|
||||||
cursor_position: Some(cursor_position),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
let mut edit = TextEditBuilder::default();
|
||||||
/// Finds the last line in the whitespace
|
edit.replace(
|
||||||
fn last_line_indent_in_whitespace(ws: &str) -> &str {
|
TextRange::from_to(dot_offset - current_indent_len, dot_offset),
|
||||||
ws.split('\n').last().unwrap_or("")
|
target_indent.into(),
|
||||||
|
);
|
||||||
|
let res = LocalEdit {
|
||||||
|
label: "reindent dot".to_string(),
|
||||||
|
edit: edit.finish(),
|
||||||
|
cursor_position: Some(
|
||||||
|
dot_offset + target_indent_len - current_indent_len + TextUnit::of_char('.'),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -162,7 +153,7 @@ mod tests {
|
||||||
do_check(
|
do_check(
|
||||||
r"
|
r"
|
||||||
fn foo() {
|
fn foo() {
|
||||||
let foo =<|> 1 + 1
|
let foo <|>= 1 + 1
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
|
@ -189,9 +180,11 @@ fn foo() {
|
||||||
fn do_check(before: &str, after: &str) {
|
fn do_check(before: &str, after: &str) {
|
||||||
let (offset, before) = extract_offset(before);
|
let (offset, before) = extract_offset(before);
|
||||||
let file = SourceFile::parse(&before);
|
let file = SourceFile::parse(&before);
|
||||||
if let Some(result) = on_eq_typed(&file, offset) {
|
if let Some(result) = on_dot_typed(&file, offset) {
|
||||||
let actual = result.edit.apply(&before);
|
let actual = result.edit.apply(&before);
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
|
} else {
|
||||||
|
assert_eq_text!(&before, after)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// indent if continuing chain call
|
// indent if continuing chain call
|
||||||
|
@ -199,7 +192,7 @@ fn foo() {
|
||||||
r"
|
r"
|
||||||
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
self.child_impl(db, name)
|
self.child_impl(db, name)
|
||||||
.<|>
|
<|>.
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
|
@ -215,7 +208,7 @@ fn foo() {
|
||||||
r"
|
r"
|
||||||
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
self.child_impl(db, name)
|
self.child_impl(db, name)
|
||||||
.<|>
|
<|>.
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
|
@ -232,7 +225,7 @@ fn foo() {
|
||||||
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
self.child_impl(db, name)
|
self.child_impl(db, name)
|
||||||
.first()
|
.first()
|
||||||
.<|>
|
<|>.
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
|
@ -250,7 +243,7 @@ fn foo() {
|
||||||
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
self.child_impl(db, name)
|
self.child_impl(db, name)
|
||||||
.first()
|
.first()
|
||||||
.<|>
|
<|>.
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
|
@ -266,7 +259,7 @@ fn foo() {
|
||||||
do_check(
|
do_check(
|
||||||
r"
|
r"
|
||||||
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
.<|>
|
<|>.
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
|
@ -281,7 +274,7 @@ fn foo() {
|
||||||
r"
|
r"
|
||||||
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
self.child_impl(db, name)
|
self.child_impl(db, name)
|
||||||
.<|>
|
<|>.
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue