indent on typing dot. fixes #439

This commit is contained in:
Simon Vandel Sillesen 2019-01-06 00:58:03 +01:00
parent 3e42a15878
commit f99398d9d5
4 changed files with 103 additions and 39 deletions

View file

@ -12,41 +12,39 @@ macro_rules! ctry {
};
}
mod db;
mod imp;
mod completion;
mod db;
mod goto_defenition;
mod symbol_index;
mod imp;
pub mod mock_analysis;
mod runnables;
mod symbol_index;
mod extend_selection;
mod syntax_highlighting;
mod hover;
mod syntax_highlighting;
use std::{fmt, sync::Arc};
use rustc_hash::FxHashMap;
use ra_syntax::{SourceFileNode, TextRange, TextUnit, SmolStr, SyntaxKind};
use ra_syntax::{SmolStr, SourceFileNode, SyntaxKind, TextRange, TextUnit};
use ra_text_edit::TextEdit;
use rayon::prelude::*;
use relative_path::RelativePathBuf;
use rustc_hash::FxHashMap;
use salsa::ParallelDatabase;
use crate::symbol_index::{SymbolIndex, FileSymbol};
use crate::symbol_index::{FileSymbol, SymbolIndex};
pub use crate::{
completion::{CompletionItem, CompletionItemKind, InsertText},
runnables::{Runnable, RunnableKind},
};
pub use ra_editor::{
Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity
};
pub use hir::FnSignatureInfo;
pub use ra_editor::{Fold, FoldKind, HighlightedRange, LineIndex, Severity, StructureNode};
pub use ra_db::{
Canceled, Cancelable, FilePosition, FileRange, LocalSyntaxPtr,
CrateGraph, CrateId, SourceRootId, FileId, SyntaxDatabase, FilesDatabase
Cancelable, Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, FilesDatabase,
LocalSyntaxPtr, SourceRootId, SyntaxDatabase,
};
#[derive(Default)]
@ -346,7 +344,7 @@ impl Analysis {
let edit = ra_editor::on_enter(&file, position.offset)?;
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 =`.
// FIXME: use a snippet completion instead of this hack here.
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)?;
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
/// file outline.
pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> {

View file

@ -16,7 +16,7 @@ pub use self::{
line_index::{LineCol, LineIndex},
line_index_utils::translate_offset_with_edit,
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
};
use ra_text_edit::TextEditBuilder;

View file

@ -1,5 +1,6 @@
use std::mem;
use itertools::Itertools;
use ra_syntax::{
algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
ast,
@ -9,9 +10,8 @@ use ra_syntax::{
SyntaxNodeRef, TextRange, TextUnit,
};
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 {
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(
edit: &mut TextEditBuilder,
node: SyntaxNodeRef,
@ -283,7 +304,9 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {
#[cfg(test)]
mod tests {
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) {
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]
fn test_on_enter() {
fn apply_on_enter(before: &str) -> Option<String> {

View file

@ -2,15 +2,16 @@ use std::collections::HashMap;
use gen_lsp_server::ErrorCode;
use languageserver_types::{
CodeActionResponse, Command, Diagnostic,
DiagnosticSeverity, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind,
FoldingRangeParams, Location, MarkupContent, MarkupKind, MarkedString, Position,
PrepareRenameResponse, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit,
Range, WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover,
HoverContents, DocumentFormattingParams, DocumentHighlight,
CodeActionResponse, Command, Diagnostic, DiagnosticSeverity, DocumentFormattingParams,
DocumentHighlight, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind,
FoldingRangeParams, Hover, HoverContents, Location, MarkedString, MarkupContent, MarkupKind,
ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, RenameParams,
SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
};
use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FileRange, FilePosition, Severity};
use ra_syntax::{TextUnit, text_utils::intersect};
use ra_analysis::{
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 rustc_hash::FxHashMap;
use serde_json::to_value;
@ -92,7 +93,7 @@ pub fn handle_on_type_formatting(
world: ServerWorld,
params: req::DocumentOnTypeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
if params.ch != "=" {
if params.ch != "=" || params.ch != "." {
return Ok(None);
}
@ -102,19 +103,30 @@ pub fn handle_on_type_formatting(
file_id,
offset: params.position.conv_with(&line_index),
};
let edits = match world.analysis().on_eq_typed(position) {
None => return Ok(None),
Some(mut action) => action
.source_file_edits
.pop()
.unwrap()
.edit
.as_atoms()
.iter()
.map_conv_with(&line_index)
.collect(),
};
Ok(Some(edits))
let analysis: Vec<Box<Fn(FilePosition) -> Option<SourceChange>>> = vec![
Box::new(|pos| world.analysis().on_eq_typed(pos)),
Box::new(|pos| world.analysis().on_dot_typed(pos)),
];
// try all analysis until one succeeds
for ana in analysis {
if let Some(mut action) = ana(position) {
return Ok(Some(
action
.source_file_edits
.pop()
.unwrap()
.edit
.as_atoms()
.iter()
.map_conv_with(&line_index)
.collect(),
));
}
}
return Ok(None);
}
pub fn handle_document_symbol(