feat: Add on-typing handler for left angle
Only advertise this feature in the server capabilities when the client supports SnippetTextEdit. Close #11398. Co-authored-by: unexge <unexge@gmail.com>
This commit is contained in:
parent
636d4880c4
commit
3bb02f2329
@ -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};
|
||||
@ -32,7 +32,7 @@ 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,
|
||||
@ -92,7 +92,8 @@ fn on_char_typed_inner(
|
||||
match char_typed {
|
||||
'.' => conv(on_dot_typed(&file.tree(), offset)),
|
||||
'=' => conv(on_eq_typed(&file.tree(), offset)),
|
||||
'>' => conv(on_arrow_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!(),
|
||||
}
|
||||
@ -312,8 +313,40 @@ 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;
|
||||
}
|
||||
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;
|
||||
@ -335,6 +368,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());
|
||||
@ -879,6 +918,169 @@ 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 regression_629() {
|
||||
type_char_noop(
|
||||
|
@ -56,7 +56,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
|
||||
},
|
||||
document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: "=".to_string(),
|
||||
more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]),
|
||||
more_trigger_character: Some(more_trigger_character(&config)),
|
||||
}),
|
||||
selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
|
||||
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
|
||||
@ -189,3 +189,11 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn more_trigger_character(config: &Config) -> Vec<String> {
|
||||
let mut res = vec![".".to_string(), ">".to_string(), "{".to_string()];
|
||||
if config.snippet_cap() {
|
||||
res.push("<".to_string());
|
||||
}
|
||||
res
|
||||
}
|
||||
|
@ -1070,6 +1070,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snippet_cap(&self) -> bool {
|
||||
self.experimental("snippetTextEdit")
|
||||
}
|
||||
|
||||
pub fn assist(&self) -> AssistConfig {
|
||||
AssistConfig {
|
||||
snippet_cap: SnippetCap::new(self.experimental("snippetTextEdit")),
|
||||
|
Loading…
x
Reference in New Issue
Block a user