mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-15 09:27:27 +00:00
indent on typing dot. fixes #439
This commit is contained in:
parent
3e42a15878
commit
f99398d9d5
4 changed files with 103 additions and 39 deletions
|
@ -12,41 +12,39 @@ macro_rules! ctry {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mod db;
|
|
||||||
mod imp;
|
|
||||||
mod completion;
|
mod completion;
|
||||||
|
mod db;
|
||||||
mod goto_defenition;
|
mod goto_defenition;
|
||||||
mod symbol_index;
|
mod imp;
|
||||||
pub mod mock_analysis;
|
pub mod mock_analysis;
|
||||||
mod runnables;
|
mod runnables;
|
||||||
|
mod symbol_index;
|
||||||
|
|
||||||
mod extend_selection;
|
mod extend_selection;
|
||||||
mod syntax_highlighting;
|
|
||||||
mod hover;
|
mod hover;
|
||||||
|
mod syntax_highlighting;
|
||||||
|
|
||||||
use std::{fmt, sync::Arc};
|
use std::{fmt, sync::Arc};
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use ra_syntax::{SmolStr, SourceFileNode, SyntaxKind, TextRange, TextUnit};
|
||||||
use ra_syntax::{SourceFileNode, TextRange, TextUnit, SmolStr, SyntaxKind};
|
|
||||||
use ra_text_edit::TextEdit;
|
use ra_text_edit::TextEdit;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use relative_path::RelativePathBuf;
|
use relative_path::RelativePathBuf;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use salsa::ParallelDatabase;
|
use salsa::ParallelDatabase;
|
||||||
|
|
||||||
use crate::symbol_index::{SymbolIndex, FileSymbol};
|
use crate::symbol_index::{FileSymbol, SymbolIndex};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
completion::{CompletionItem, CompletionItemKind, InsertText},
|
completion::{CompletionItem, CompletionItemKind, InsertText},
|
||||||
runnables::{Runnable, RunnableKind},
|
runnables::{Runnable, RunnableKind},
|
||||||
};
|
};
|
||||||
pub use ra_editor::{
|
|
||||||
Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity
|
|
||||||
};
|
|
||||||
pub use hir::FnSignatureInfo;
|
pub use hir::FnSignatureInfo;
|
||||||
|
pub use ra_editor::{Fold, FoldKind, HighlightedRange, LineIndex, Severity, StructureNode};
|
||||||
|
|
||||||
pub use ra_db::{
|
pub use ra_db::{
|
||||||
Canceled, Cancelable, FilePosition, FileRange, LocalSyntaxPtr,
|
Cancelable, Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, FilesDatabase,
|
||||||
CrateGraph, CrateId, SourceRootId, FileId, SyntaxDatabase, FilesDatabase
|
LocalSyntaxPtr, SourceRootId, SyntaxDatabase,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -346,7 +344,7 @@ impl Analysis {
|
||||||
let edit = ra_editor::on_enter(&file, position.offset)?;
|
let edit = ra_editor::on_enter(&file, position.offset)?;
|
||||||
Some(SourceChange::from_local_edit(position.file_id, edit))
|
Some(SourceChange::from_local_edit(position.file_id, edit))
|
||||||
}
|
}
|
||||||
/// Returns an edit which should be applied after `=` was typed. Primaraly,
|
/// Returns an edit which should be applied after `=` was typed. Primarily,
|
||||||
/// this works when adding `let =`.
|
/// this works when adding `let =`.
|
||||||
// FIXME: use a snippet completion instead of this hack here.
|
// FIXME: use a snippet completion instead of this hack here.
|
||||||
pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> {
|
pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> {
|
||||||
|
@ -354,6 +352,12 @@ impl Analysis {
|
||||||
let edit = ra_editor::on_eq_typed(&file, position.offset)?;
|
let edit = ra_editor::on_eq_typed(&file, position.offset)?;
|
||||||
Some(SourceChange::from_local_edit(position.file_id, edit))
|
Some(SourceChange::from_local_edit(position.file_id, edit))
|
||||||
}
|
}
|
||||||
|
/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
|
||||||
|
pub fn on_dot_typed(&self, position: FilePosition) -> Option<SourceChange> {
|
||||||
|
let file = self.db.source_file(position.file_id);
|
||||||
|
let edit = ra_editor::on_dot_typed(&file, position.offset)?;
|
||||||
|
Some(SourceChange::from_local_edit(position.file_id, edit))
|
||||||
|
}
|
||||||
/// Returns a tree representation of symbols in the file. Useful to draw a
|
/// Returns a tree representation of symbols in the file. Useful to draw a
|
||||||
/// file outline.
|
/// file outline.
|
||||||
pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> {
|
pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> {
|
||||||
|
|
|
@ -16,7 +16,7 @@ pub use self::{
|
||||||
line_index::{LineCol, LineIndex},
|
line_index::{LineCol, LineIndex},
|
||||||
line_index_utils::translate_offset_with_edit,
|
line_index_utils::translate_offset_with_edit,
|
||||||
structure::{file_structure, StructureNode},
|
structure::{file_structure, StructureNode},
|
||||||
typing::{join_lines, on_enter, on_eq_typed},
|
typing::{join_lines, on_enter, on_dot_typed, on_eq_typed},
|
||||||
diagnostics::diagnostics
|
diagnostics::diagnostics
|
||||||
};
|
};
|
||||||
use ra_text_edit::TextEditBuilder;
|
use ra_text_edit::TextEditBuilder;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
|
algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
|
||||||
ast,
|
ast,
|
||||||
|
@ -9,9 +10,8 @@ use ra_syntax::{
|
||||||
SyntaxNodeRef, TextRange, TextUnit,
|
SyntaxNodeRef, TextRange, TextUnit,
|
||||||
};
|
};
|
||||||
use ra_text_edit::text_utils::contains_offset_nonstrict;
|
use ra_text_edit::text_utils::contains_offset_nonstrict;
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::{find_node_at_offset, TextEditBuilder, LocalEdit};
|
use crate::{find_node_at_offset, LocalEdit, TextEditBuilder};
|
||||||
|
|
||||||
pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
|
pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
|
||||||
let range = if range.is_empty() {
|
let range = if range.is_empty() {
|
||||||
|
@ -136,6 +136,27 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_dot_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> {
|
||||||
|
let before_dot_offset = offset - TextUnit::of_char('.');
|
||||||
|
|
||||||
|
let whitespace = find_leaf_at_offset(file.syntax(), before_dot_offset)
|
||||||
|
.left_biased()
|
||||||
|
.and_then(ast::Whitespace::cast)?;
|
||||||
|
|
||||||
|
// whitespace found just left of the dot
|
||||||
|
// TODO: indent is always 4 spaces now. A better heuristic could look on the previous line(s)
|
||||||
|
let indent = " ".to_string();
|
||||||
|
|
||||||
|
let cursor_position = offset + TextUnit::of_str(&indent);;
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_newline(
|
fn remove_newline(
|
||||||
edit: &mut TextEditBuilder,
|
edit: &mut TextEditBuilder,
|
||||||
node: SyntaxNodeRef,
|
node: SyntaxNodeRef,
|
||||||
|
@ -283,7 +304,9 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test_utils::{add_cursor, check_action, extract_offset, extract_range, assert_eq_text};
|
use crate::test_utils::{
|
||||||
|
add_cursor, assert_eq_text, check_action, extract_offset, extract_range,
|
||||||
|
};
|
||||||
|
|
||||||
fn check_join_lines(before: &str, after: &str) {
|
fn check_join_lines(before: &str, after: &str) {
|
||||||
check_action(before, after, |file, offset| {
|
check_action(before, after, |file, offset| {
|
||||||
|
@ -614,6 +637,31 @@ fn foo() {
|
||||||
// ");
|
// ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_on_dot_typed() {
|
||||||
|
fn do_check(before: &str, after: &str) {
|
||||||
|
let (offset, before) = extract_offset(before);
|
||||||
|
let file = SourceFileNode::parse(&before);
|
||||||
|
let result = on_dot_typed(&file, offset).unwrap();
|
||||||
|
let actual = result.edit.apply(&before);
|
||||||
|
assert_eq_text!(after, &actual);
|
||||||
|
}
|
||||||
|
do_check(
|
||||||
|
r"
|
||||||
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
|
self.child_impl(db, name)
|
||||||
|
.<|>
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
|
||||||
|
self.child_impl(db, name)
|
||||||
|
.
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_on_enter() {
|
fn test_on_enter() {
|
||||||
fn apply_on_enter(before: &str) -> Option<String> {
|
fn apply_on_enter(before: &str) -> Option<String> {
|
||||||
|
|
|
@ -2,15 +2,16 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use gen_lsp_server::ErrorCode;
|
use gen_lsp_server::ErrorCode;
|
||||||
use languageserver_types::{
|
use languageserver_types::{
|
||||||
CodeActionResponse, Command, Diagnostic,
|
CodeActionResponse, Command, Diagnostic, DiagnosticSeverity, DocumentFormattingParams,
|
||||||
DiagnosticSeverity, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind,
|
DocumentHighlight, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind,
|
||||||
FoldingRangeParams, Location, MarkupContent, MarkupKind, MarkedString, Position,
|
FoldingRangeParams, Hover, HoverContents, Location, MarkedString, MarkupContent, MarkupKind,
|
||||||
PrepareRenameResponse, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit,
|
ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, RenameParams,
|
||||||
Range, WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover,
|
SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
|
||||||
HoverContents, DocumentFormattingParams, DocumentHighlight,
|
|
||||||
};
|
};
|
||||||
use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FileRange, FilePosition, Severity};
|
use ra_analysis::{
|
||||||
use ra_syntax::{TextUnit, text_utils::intersect};
|
FileId, FilePosition, FileRange, FoldKind, Query, RunnableKind, Severity, SourceChange,
|
||||||
|
};
|
||||||
|
use ra_syntax::{text_utils::intersect, TextUnit};
|
||||||
use ra_text_edit::text_utils::contains_offset_nonstrict;
|
use ra_text_edit::text_utils::contains_offset_nonstrict;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde_json::to_value;
|
use serde_json::to_value;
|
||||||
|
@ -92,7 +93,7 @@ pub fn handle_on_type_formatting(
|
||||||
world: ServerWorld,
|
world: ServerWorld,
|
||||||
params: req::DocumentOnTypeFormattingParams,
|
params: req::DocumentOnTypeFormattingParams,
|
||||||
) -> Result<Option<Vec<TextEdit>>> {
|
) -> Result<Option<Vec<TextEdit>>> {
|
||||||
if params.ch != "=" {
|
if params.ch != "=" || params.ch != "." {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,19 +103,30 @@ pub fn handle_on_type_formatting(
|
||||||
file_id,
|
file_id,
|
||||||
offset: params.position.conv_with(&line_index),
|
offset: params.position.conv_with(&line_index),
|
||||||
};
|
};
|
||||||
let edits = match world.analysis().on_eq_typed(position) {
|
|
||||||
None => return Ok(None),
|
let analysis: Vec<Box<Fn(FilePosition) -> Option<SourceChange>>> = vec![
|
||||||
Some(mut action) => action
|
Box::new(|pos| world.analysis().on_eq_typed(pos)),
|
||||||
.source_file_edits
|
Box::new(|pos| world.analysis().on_dot_typed(pos)),
|
||||||
.pop()
|
];
|
||||||
.unwrap()
|
|
||||||
.edit
|
// try all analysis until one succeeds
|
||||||
.as_atoms()
|
for ana in analysis {
|
||||||
.iter()
|
if let Some(mut action) = ana(position) {
|
||||||
.map_conv_with(&line_index)
|
return Ok(Some(
|
||||||
.collect(),
|
action
|
||||||
};
|
.source_file_edits
|
||||||
Ok(Some(edits))
|
.pop()
|
||||||
|
.unwrap()
|
||||||
|
.edit
|
||||||
|
.as_atoms()
|
||||||
|
.iter()
|
||||||
|
.map_conv_with(&line_index)
|
||||||
|
.collect(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_document_symbol(
|
pub fn handle_document_symbol(
|
||||||
|
|
Loading…
Reference in a new issue