From c631b585a7358d1569a051f2529ecaae222e95cd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 16 Aug 2018 00:23:22 +0300 Subject: matching brace --- Cargo.toml | 4 ++- code/package.json | 5 ++++ code/src/extension.ts | 21 ++++++++++++++++ crates/libeditor/src/lib.rs | 26 ++++++++++++++++++-- crates/libeditor/tests/test.rs | 43 ++++++++++++++++++++++++++------- crates/server/src/conv.rs | 8 ++++++ crates/server/src/main_loop/handlers.rs | 21 +++++++++++++++- crates/server/src/main_loop/mod.rs | 4 +++ crates/server/src/req.rs | 17 ++++++++++++- 9 files changed, 135 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9748fd578..5cfc064b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ [workspace] members = [ "crates/*" ] -exclude = [ "crates/indxr" ] + +[profile.release] +debug = true diff --git a/code/package.json b/code/package.json index 8563ba73c..fd3b8e423 100644 --- a/code/package.json +++ b/code/package.json @@ -36,6 +36,11 @@ { "command": "libsyntax-rust.extendSelection", "title": "Rust Extend Selection" + }, + { + "command": "libsyntax-rust.matchingBrace", + "key": "ctrl+shift+m", + "title": "Rust Matching Brace" } ], "keybindings": [ diff --git a/code/src/extension.ts b/code/src/extension.ts index bb724539d..afcbccf63 100644 --- a/code/src/extension.ts +++ b/code/src/extension.ts @@ -35,6 +35,22 @@ export function activate(context: vscode.ExtensionContext) { return new vscode.Selection(r.start, r.end) }) }) + registerCommand('libsyntax-rust.matchingBrace', async () => { + let editor = vscode.window.activeTextEditor + if (editor == null || editor.document.languageId != "rust") return + let request: FindMatchingBraceParams = { + textDocument: { uri: editor.document.uri.toString() }, + offsets: editor.selections.map((s) => { + return client.code2ProtocolConverter.asPosition(s.active) + }) + } + let response = await client.sendRequest("m/findMatchingBrace", request) + editor.selections = editor.selections.map((sel, idx) => { + let active = client.protocol2CodeConverter.asPosition(response[idx]) + let anchor = sel.isEmpty ? active : sel.anchor + return new vscode.Selection(anchor, active) + }) + }) dispose(vscode.workspace.registerTextDocumentContentProvider( 'libsyntax-rust', @@ -184,6 +200,11 @@ interface ExtendSelectionResult { selections: lc.Range[]; } +interface FindMatchingBraceParams { + textDocument: lc.TextDocumentIdentifier; + offsets: lc.Position[]; +} + interface PublishDecorationsParams { uri: string, decorations: Decoration[], diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs index 9e44f5d92..28da457d1 100644 --- a/crates/libeditor/src/lib.rs +++ b/crates/libeditor/src/lib.rs @@ -12,8 +12,8 @@ mod code_actions; use libsyntax2::{ ast::{self, NameOwner}, AstNode, - algo::walk, - SyntaxKind::*, + algo::{walk, find_leaf_at_offset}, + SyntaxKind::{self, *}, }; pub use libsyntax2::{File, TextRange, TextUnit}; pub use self::{ @@ -52,6 +52,28 @@ pub fn parse(text: &str) -> ast::File { ast::File::parse(text) } +pub fn matching_brace(file: &ast::File, offset: TextUnit) -> Option { + const BRACES: &[SyntaxKind] = &[ + L_CURLY, R_CURLY, + L_BRACK, R_BRACK, + L_PAREN, R_PAREN, + L_ANGLE, R_ANGLE, + ]; + let syntax = file.syntax(); + let syntax = syntax.as_ref(); + let (brace_node, brace_idx) = find_leaf_at_offset(syntax, offset) + .filter_map(|node| { + let idx = BRACES.iter().position(|&brace| brace == node.kind())?; + Some((node, idx)) + }) + .next()?; + let parent = brace_node.parent()?; + let matching_kind = BRACES[brace_idx ^ 1]; + let matching_node = parent.children() + .find(|node| node.kind() == matching_kind)?; + Some(matching_node.range().start()) +} + pub fn highlight(file: &ast::File) -> Vec { let syntax = file.syntax(); let mut res = Vec::new(); diff --git a/crates/libeditor/tests/test.rs b/crates/libeditor/tests/test.rs index 7063425ce..d5df9d0cc 100644 --- a/crates/libeditor/tests/test.rs +++ b/crates/libeditor/tests/test.rs @@ -9,7 +9,7 @@ use itertools::Itertools; use libeditor::{ File, TextUnit, TextRange, ActionResult, CursorPosition, highlight, runnables, extend_selection, file_structure, - flip_comma, add_derive, + flip_comma, add_derive, matching_brace, }; #[test] @@ -119,6 +119,25 @@ fn test_add_derive() { ) } +#[test] +fn test_matching_brace() { + fn do_check(before: &str, after: &str) { + let (pos, before) = extract_cursor(before); + let file = file(&before); + let new_pos = match matching_brace(&file, pos) { + None => pos, + Some(pos) => pos, + }; + let actual = add_cursor(&before, new_pos); + assert_eq_text!(after, &actual); + } + + do_check( + "struct Foo { a: i32, }<|>", + "struct Foo <|>{ a: i32, }", + ); +} + fn file(text: &str) -> File { File::parse(text) } @@ -138,16 +157,12 @@ fn check_action Option>( let file = file(&before); let result = f(&file, before_cursor_pos).expect("code action is not applicable"); let actual = result.edit.apply(&before); - let actual_cursor_pos: u32 = match result.cursor_position { + let actual_cursor_pos = match result.cursor_position { CursorPosition::Same => result.edit.apply_to_offset(before_cursor_pos).unwrap(), CursorPosition::Offset(off) => off, - }.into(); - let actual_cursor_pos = actual_cursor_pos as usize; - let mut actual_with_cursor = String::new(); - actual_with_cursor.push_str(&actual[..actual_cursor_pos]); - actual_with_cursor.push_str("<|>"); - actual_with_cursor.push_str(&actual[actual_cursor_pos..]); - assert_eq_text!(after, &actual_with_cursor); + }; + let actual = add_cursor(&actual, actual_cursor_pos); + assert_eq_text!(after, &actual); } fn extract_cursor(text: &str) -> (TextUnit, String) { @@ -162,3 +177,13 @@ fn extract_cursor(text: &str) -> (TextUnit, String) { let cursor_pos = TextUnit::from(cursor_pos as u32); (cursor_pos, new_text) } + +fn add_cursor(text: &str, offset: TextUnit) -> String { + let offset: u32 = offset.into(); + let offset: usize = offset as usize; + let mut res = String::new(); + res.push_str(&text[..offset]); + res.push_str("<|>"); + res.push_str(&text[offset..]); + res +} diff --git a/crates/server/src/conv.rs b/crates/server/src/conv.rs index bbe512ece..b3709ccaf 100644 --- a/crates/server/src/conv.rs +++ b/crates/server/src/conv.rs @@ -117,6 +117,14 @@ impl ConvWith for AtomEdit { } } +impl ConvWith for Option { + type Ctx = ::Ctx; + type Output = Option<::Output>; + fn conv_with(self, ctx: &Self::Ctx) -> Self::Output { + self.map(|x| ConvWith::conv_with(x, ctx)) + } +} + impl<'a> TryConvWith for &'a Url { type Ctx = PathMap; type Output = FileId; diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs index 078abfbfa..d7b78b4fa 100644 --- a/crates/server/src/main_loop/handlers.rs +++ b/crates/server/src/main_loop/handlers.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use languageserver_types::{ Diagnostic, DiagnosticSeverity, Url, DocumentSymbol, Command, TextDocumentIdentifier, WorkspaceEdit, - SymbolInformation, + SymbolInformation, Position, }; use libanalysis::{World, Query}; use libeditor; @@ -42,6 +42,25 @@ pub fn handle_extend_selection( Ok(req::ExtendSelectionResult { selections }) } +pub fn handle_find_matching_brace( + world: World, + path_map: PathMap, + params: req::FindMatchingBraceParams, +) -> Result> { + let file_id = params.text_document.try_conv_with(&path_map)?; + let file = world.file_syntax(file_id)?; + let line_index = world.file_line_index(file_id)?; + let res = params.offsets + .into_iter() + .map_conv_with(&line_index) + .map(|offset| { + libeditor::matching_brace(&file, offset).unwrap_or(offset) + }) + .map_conv_with(&line_index) + .collect(); + Ok(res) +} + pub fn handle_document_symbol( world: World, path_map: PathMap, diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs index 2a31297be..4d5dfb437 100644 --- a/crates/server/src/main_loop/mod.rs +++ b/crates/server/src/main_loop/mod.rs @@ -26,6 +26,7 @@ use { handle_execute_command, handle_workspace_symbol, handle_goto_definition, + handle_find_matching_brace, }, }; @@ -148,6 +149,9 @@ fn on_request( handle_request_on_threadpool::( &mut req, pool, path_map, world, sender, handle_extend_selection, )?; + handle_request_on_threadpool::( + &mut req, pool, path_map, world, sender, handle_find_matching_brace, + )?; handle_request_on_threadpool::( &mut req, pool, path_map, world, sender, handle_document_symbol, )?; diff --git a/crates/server/src/req.rs b/crates/server/src/req.rs index 17ef10e43..c3efc7489 100644 --- a/crates/server/src/req.rs +++ b/crates/server/src/req.rs @@ -1,5 +1,5 @@ use serde::{ser::Serialize, de::DeserializeOwned}; -use languageserver_types::{TextDocumentIdentifier, Range, Url}; +use languageserver_types::{TextDocumentIdentifier, Range, Url, Position}; use url_serde; pub use languageserver_types::{ @@ -65,6 +65,21 @@ pub struct ExtendSelectionResult { pub selections: Vec, } +pub enum FindMatchingBrace {} + +impl Request for FindMatchingBrace { + type Params = FindMatchingBraceParams; + type Result = Vec; + const METHOD: &'static str = "m/findMatchingBrace"; +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct FindMatchingBraceParams { + pub text_document: TextDocumentIdentifier, + pub offsets: Vec, +} + pub enum PublishDecorations {} impl Notification for PublishDecorations { -- cgit v1.2.3