Skip to content

Commit

Permalink
Auto merge of rust-lang#11830 - nemethf:on-type-formatting, r=nemethf
Browse files Browse the repository at this point in the history
On typing handler for angle brackets(<) with snippets

I implemented my idea in rust-lang#11398 in "cargo cult programming"-style without actually know what I'm doing, so feedback is welcome.  The PR is split into two commits to ease the review.  I used `@unexge's` original prototype, which forms the basis of the PR.
  • Loading branch information
bors committed May 22, 2022
2 parents 3de03d4 + f7c963c commit 90236dd
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 21 deletions.
330 changes: 319 additions & 11 deletions crates/ide/src/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ use ide_db::{
RootDatabase,
};
use syntax::{
algo::find_node_at_offset,
algo::{ancestors_at_offset, find_node_at_offset},
ast::{self, edit::IndentLevel, AstToken},
AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize,
AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, T,
};

use text_edit::{Indel, TextEdit};
Expand All @@ -32,7 +32,12 @@ use crate::SourceChange;
pub(crate) use on_enter::on_enter;

// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
pub(crate) const TRIGGER_CHARS: &str = ".=>{";
pub(crate) const TRIGGER_CHARS: &str = ".=<>{";

struct ExtendedTextEdit {
edit: TextEdit,
is_snippet: bool,
}

// Feature: On Typing Assists
//
Expand Down Expand Up @@ -68,23 +73,30 @@ pub(crate) fn on_char_typed(
return None;
}
let edit = on_char_typed_inner(file, position.offset, char_typed)?;
Some(SourceChange::from_text_edit(position.file_id, edit))
let mut sc = SourceChange::from_text_edit(position.file_id, edit.edit);
sc.is_snippet = edit.is_snippet;
Some(sc)
}

fn on_char_typed_inner(
file: &Parse<SourceFile>,
offset: TextSize,
char_typed: char,
) -> Option<TextEdit> {
) -> Option<ExtendedTextEdit> {
if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
return None;
}
match char_typed {
'.' => on_dot_typed(&file.tree(), offset),
'=' => on_eq_typed(&file.tree(), offset),
'>' => on_arrow_typed(&file.tree(), offset),
'{' => on_opening_brace_typed(file, offset),
return match char_typed {
'.' => conv(on_dot_typed(&file.tree(), offset)),
'=' => conv(on_eq_typed(&file.tree(), offset)),
'<' => on_left_angle_typed(&file.tree(), offset),
'>' => conv(on_right_angle_typed(&file.tree(), offset)),
'{' => conv(on_opening_brace_typed(file, offset)),
_ => unreachable!(),
};

fn conv(text_edit: Option<TextEdit>) -> Option<ExtendedTextEdit> {
Some(ExtendedTextEdit { edit: text_edit?, is_snippet: false })
}
}

Expand Down Expand Up @@ -302,8 +314,49 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
}

/// Add closing `>` for generic arguments/parameters.
fn on_left_angle_typed(file: &SourceFile, offset: TextSize) -> Option<ExtendedTextEdit> {
let file_text = file.syntax().text();
if !stdx::always!(file_text.char_at(offset) == Some('<')) {
return None;
}

// Find the next non-whitespace char in the line.
let mut next_offset = offset + TextSize::of('<');
while file_text.char_at(next_offset) == Some(' ') {
next_offset += TextSize::of(' ')
}
if file_text.char_at(next_offset) == Some('>') {
return None;
}

let range = TextRange::at(offset, TextSize::of('<'));
if let Some(t) = file.syntax().token_at_offset(offset).left_biased() {
if T![impl] == t.kind() {
return Some(ExtendedTextEdit {
edit: TextEdit::replace(range, "<$0>".to_string()),
is_snippet: true,
});
}
}

if ancestors_at_offset(file.syntax(), offset)
.find(|n| {
ast::GenericParamList::can_cast(n.kind()) || ast::GenericArgList::can_cast(n.kind())
})
.is_some()
{
return Some(ExtendedTextEdit {
edit: TextEdit::replace(range, "<$0>".to_string()),
is_snippet: true,
});
}

None
}

/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
let file_text = file.syntax().text();
if !stdx::always!(file_text.char_at(offset) == Some('>')) {
return None;
Expand All @@ -325,6 +378,12 @@ mod tests {

use super::*;

impl ExtendedTextEdit {
fn apply(&self, text: &mut String) {
self.edit.apply(text);
}
}

fn do_type_char(char_typed: char, before: &str) -> Option<String> {
let (offset, mut before) = extract_offset(before);
let edit = TextEdit::insert(offset, char_typed.to_string());
Expand Down Expand Up @@ -869,6 +928,255 @@ use some::pa$0th::to::Item;
);
}

#[test]
fn adds_closing_angle_bracket_for_generic_args() {
type_char(
'<',
r#"
fn foo() {
bar::$0
}
"#,
r#"
fn foo() {
bar::<$0>
}
"#,
);

type_char(
'<',
r#"
fn foo(bar: &[u64]) {
bar.iter().collect::$0();
}
"#,
r#"
fn foo(bar: &[u64]) {
bar.iter().collect::<$0>();
}
"#,
);
}

#[test]
fn adds_closing_angle_bracket_for_generic_params() {
type_char(
'<',
r#"
fn foo$0() {}
"#,
r#"
fn foo<$0>() {}
"#,
);
type_char(
'<',
r#"
fn foo$0
"#,
r#"
fn foo<$0>
"#,
);
type_char(
'<',
r#"
struct Foo$0 {}
"#,
r#"
struct Foo<$0> {}
"#,
);
type_char(
'<',
r#"
struct Foo$0();
"#,
r#"
struct Foo<$0>();
"#,
);
type_char(
'<',
r#"
struct Foo$0
"#,
r#"
struct Foo<$0>
"#,
);
type_char(
'<',
r#"
enum Foo$0
"#,
r#"
enum Foo<$0>
"#,
);
type_char(
'<',
r#"
trait Foo$0
"#,
r#"
trait Foo<$0>
"#,
);
type_char(
'<',
r#"
type Foo$0 = Bar;
"#,
r#"
type Foo<$0> = Bar;
"#,
);
type_char(
'<',
r#"
impl$0 Foo {}
"#,
r#"
impl<$0> Foo {}
"#,
);
type_char(
'<',
r#"
impl<T> Foo$0 {}
"#,
r#"
impl<T> Foo<$0> {}
"#,
);
type_char(
'<',
r#"
impl Foo$0 {}
"#,
r#"
impl Foo<$0> {}
"#,
);
}

#[test]
fn dont_add_closing_angle_bracket_for_comparison() {
type_char_noop(
'<',
r#"
fn main() {
42$0
}
"#,
);
type_char_noop(
'<',
r#"
fn main() {
42 $0
}
"#,
);
type_char_noop(
'<',
r#"
fn main() {
let foo = 42;
foo $0
}
"#,
);
}

#[test]
fn dont_add_closing_angle_bracket_if_it_is_already_there() {
type_char_noop(
'<',
r#"
fn foo() {
bar::$0>
}
"#,
);
type_char_noop(
'<',
r#"
fn foo(bar: &[u64]) {
bar.iter().collect::$0 >();
}
"#,
);
type_char_noop(
'<',
r#"
fn foo$0>() {}
"#,
);
type_char_noop(
'<',
r#"
fn foo$0>
"#,
);
type_char_noop(
'<',
r#"
struct Foo$0> {}
"#,
);
type_char_noop(
'<',
r#"
struct Foo$0>();
"#,
);
type_char_noop(
'<',
r#"
struct Foo$0>
"#,
);
type_char_noop(
'<',
r#"
enum Foo$0>
"#,
);
type_char_noop(
'<',
r#"
trait Foo$0>
"#,
);
type_char_noop(
'<',
r#"
type Foo$0> = Bar;
"#,
);
type_char_noop(
'<',
r#"
impl$0> Foo {}
"#,
);
type_char_noop(
'<',
r#"
impl<T> Foo$0> {}
"#,
);
type_char_noop(
'<',
r#"
impl Foo$0> {}
"#,
);
}

#[test]
fn regression_629() {
type_char_noop(
Expand Down
Loading

0 comments on commit 90236dd

Please sign in to comment.