From 2b956fd3a83313cee37ff179eae843bc88dd572a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 9 Oct 2018 16:00:20 +0300 Subject: Add on-enter handler Now, typing doc comments is much more pleasant --- crates/ra_analysis/src/lib.rs | 6 ++ crates/ra_editor/src/lib.rs | 2 +- crates/ra_editor/src/typing.rs | 101 ++++++++++++++++++++++++- crates/ra_lsp_server/src/conv.rs | 41 +++++++++- crates/ra_lsp_server/src/main_loop/handlers.rs | 14 ++++ crates/ra_lsp_server/src/main_loop/mod.rs | 1 + crates/ra_lsp_server/src/req.rs | 8 ++ 7 files changed, 169 insertions(+), 4 deletions(-) (limited to 'crates') diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index b4c7db476..f6ceb7eb2 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs @@ -184,6 +184,12 @@ impl Analysis { let file = self.imp.file_syntax(file_id); SourceChange::from_local_edit(file_id, "join lines", ra_editor::join_lines(&file, range)) } + pub fn on_enter(&self, file_id: FileId, offset: TextUnit) -> Option { + let file = self.imp.file_syntax(file_id); + let edit = ra_editor::on_enter(&file, offset)?; + let res = SourceChange::from_local_edit(file_id, "on enter", edit); + Some(res) + } pub fn on_eq_typed(&self, file_id: FileId, offset: TextUnit) -> Option { let file = self.imp.file_syntax(file_id); Some(SourceChange::from_local_edit(file_id, "add semicolon", ra_editor::on_eq_typed(&file, offset)?)) diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index 2a801f7da..fe0045378 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs @@ -35,7 +35,7 @@ pub use self::{ flip_comma, add_derive, add_impl, introduce_variable, }, - typing::{join_lines, on_eq_typed}, + typing::{join_lines, on_eq_typed, on_enter}, completion::{scope_completion, CompletionItem}, folding_ranges::{Fold, FoldKind, folding_ranges} }; diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs index 512076941..3384389d1 100644 --- a/crates/ra_editor/src/typing.rs +++ b/crates/ra_editor/src/typing.rs @@ -4,7 +4,7 @@ use ra_syntax::{ TextUnit, TextRange, SyntaxNodeRef, File, AstNode, SyntaxKind, ast, algo::{ - find_covering_node, + find_covering_node, find_leaf_at_offset, LeafAtOffset, }, text_utils::{intersect, contains_offset_nonstrict}, SyntaxKind::*, @@ -56,6 +56,58 @@ pub fn join_lines(file: &File, range: TextRange) -> LocalEdit { } } +pub fn on_enter(file: &File, offset: TextUnit) -> Option { + let comment = find_leaf_at_offset(file.syntax(), offset).left_biased().filter(|it| it.kind() == COMMENT)?; + let prefix = comment_preffix(comment)?; + if offset < comment.range().start() + TextUnit::of_str(prefix) { + return None; + } + + let indent = node_indent(file, comment)?; + let inserted = format!("\n{}{}", indent, prefix); + let cursor_position = offset + TextUnit::of_str(&inserted); + let mut edit = EditBuilder::new(); + edit.insert(offset, inserted); + Some(LocalEdit { + edit: edit.finish(), + cursor_position: Some(cursor_position), + }) +} + +fn comment_preffix(comment: SyntaxNodeRef) -> Option<&'static str> { + let text = comment.leaf_text().unwrap(); + let res = if text.starts_with("///") { + "/// " + } else if text.starts_with("//!") { + "//! " + } else if text.starts_with("//") { + "// " + } else { + return None; + }; + Some(res) +} + +fn node_indent<'a>(file: &'a File, node: SyntaxNodeRef) -> Option<&'a str> { + let ws = match find_leaf_at_offset(file.syntax(), node.range().start()) { + LeafAtOffset::Between(l, r) => { + assert!(r == node); + l + } + LeafAtOffset::Single(n) => { + assert!(n == node); + return Some("") + } + LeafAtOffset::None => unreachable!(), + }; + if ws.kind() != WHITESPACE { + return None; + } + let text = ws.leaf_text().unwrap(); + let pos = text.as_str().rfind('\n').map(|it| it + 1).unwrap_or(0); + Some(&text[pos..]) +} + pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option { let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; if let_stmt.has_semi() { @@ -187,7 +239,7 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { #[cfg(test)] mod tests { use super::*; - use test_utils::{check_action, extract_range, extract_offset}; + use test_utils::{check_action, extract_range, extract_offset, add_cursor}; fn check_join_lines(before: &str, after: &str) { check_action(before, after, |file, offset| { @@ -344,4 +396,49 @@ fn foo() { // } // "); } + + #[test] + fn test_on_enter() { + fn apply_on_enter(before: &str) -> Option { + let (offset, before) = extract_offset(before); + let file = File::parse(&before); + let result = on_enter(&file, offset)?; + let actual = result.edit.apply(&before); + let actual = add_cursor(&actual, result.cursor_position.unwrap()); + Some(actual) + } + + fn do_check(before: &str, after: &str) { + let actual = apply_on_enter(before).unwrap(); + assert_eq_text!(after, &actual); + } + + fn do_check_noop(text: &str) { + assert!(apply_on_enter(text).is_none()) + } + + do_check(r" +/// Some docs<|> +fn foo() { +} +", r" +/// Some docs +/// <|> +fn foo() { +} +"); + do_check(r" +impl S { + /// Some<|> docs. + fn foo() {} +} +", r" +impl S { + /// Some + /// <|> docs. + fn foo() {} +} +"); + do_check_noop(r"<|>//! docz"); + } } diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 759e5e914..08a656569 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs @@ -190,9 +190,13 @@ impl TryConvWith for SourceChange { None => None, Some(pos) => { let line_index = world.analysis().file_line_index(pos.file_id); + let edits = self.source_file_edits.iter().find(|it| it.file_id == pos.file_id) + .map(|it| it.edits.as_slice()).unwrap_or(&[]); + let line_col = translate_offset_with_edit(&*line_index, pos.offset, edits); + let position = Position::new(line_col.line as u64, u32::from(line_col.col) as u64); Some(TextDocumentPositionParams { text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?), - position: pos.offset.conv_with(&line_index), + position, }) } }; @@ -207,6 +211,41 @@ impl TryConvWith for SourceChange { } } +// HACK: we should translate offset to line/column using linde_index *with edits applied*. +// A naive version of this function would be to apply `edits` to the original text, +// construct a new line index and use that, but it would be slow. +// +// Writing fast & correct version is issue #105, let's use a quick hack in the meantime +fn translate_offset_with_edit( + pre_edit_index: &LineIndex, + offset: TextUnit, + edits: &[AtomEdit], +) -> LineCol { + let fallback = pre_edit_index.line_col(offset); + let edit = match edits.first() { + None => return fallback, + Some(edit) => edit + }; + let end_offset = edit.delete.start() + TextUnit::of_str(&edit.insert); + if !(edit.delete.start() <= offset && offset <= end_offset) { + return fallback + } + let rel_offset = offset - edit.delete.start(); + let in_edit_line_col = LineIndex::new(&edit.insert).line_col(rel_offset); + let edit_line_col = pre_edit_index.line_col(edit.delete.start()); + if in_edit_line_col.line == 0 { + LineCol { + line: edit_line_col.line, + col: edit_line_col.col + in_edit_line_col.col, + } + } else { + LineCol { + line: edit_line_col.line + in_edit_line_col.line, + col: in_edit_line_col.col, + } + } +} + impl TryConvWith for SourceFileEdit { type Ctx = ServerWorld; type Output = TextDocumentEdit; diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 79a54183e..725036cc7 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -77,6 +77,20 @@ pub fn handle_join_lines( .try_conv_with(&world) } +pub fn handle_on_enter( + world: ServerWorld, + params: req::TextDocumentPositionParams, + _token: JobToken, +) -> Result> { + let file_id = params.text_document.try_conv_with(&world)?; + let line_index = world.analysis().file_line_index(file_id); + let offset = params.position.conv_with(&line_index); + match world.analysis().on_enter(file_id, offset) { + None => Ok(None), + Some(edit) => Ok(Some(edit.try_conv_with(&world)?)) + } +} + pub fn handle_on_type_formatting( world: ServerWorld, params: req::DocumentOnTypeFormattingParams, diff --git a/crates/ra_lsp_server/src/main_loop/mod.rs b/crates/ra_lsp_server/src/main_loop/mod.rs index 47a9b202e..53c6f1dff 100644 --- a/crates/ra_lsp_server/src/main_loop/mod.rs +++ b/crates/ra_lsp_server/src/main_loop/mod.rs @@ -244,6 +244,7 @@ fn on_request( .on::(handlers::handle_extend_selection)? .on::(handlers::handle_find_matching_brace)? .on::(handlers::handle_join_lines)? + .on::(handlers::handle_on_enter)? .on::(handlers::handle_on_type_formatting)? .on::(handlers::handle_document_symbol)? .on::(handlers::handle_workspace_symbol)? diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index 4af61dbbd..458c79ea9 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -119,6 +119,14 @@ pub struct JoinLinesParams { pub range: Range, } +pub enum OnEnter {} + +impl Request for OnEnter { + type Params = TextDocumentPositionParams; + type Result = Option; + const METHOD: &'static str = "m/onEnter"; +} + pub enum Runnables {} impl Request for Runnables { -- cgit v1.2.3