mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 04:23:25 +00:00
matching brace
This commit is contained in:
parent
aa0d344581
commit
c631b585a7
9 changed files with 135 additions and 14 deletions
|
@ -1,3 +1,5 @@
|
|||
[workspace]
|
||||
members = [ "crates/*" ]
|
||||
exclude = [ "crates/indxr" ]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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<lc.Position[]>("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[],
|
||||
|
|
|
@ -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<TextUnit> {
|
||||
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<HighlightedRange> {
|
||||
let syntax = file.syntax();
|
||||
let mut res = Vec::new();
|
||||
|
|
|
@ -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<F: Fn(&File, TextUnit) -> Option<ActionResult>>(
|
|||
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
|
||||
}
|
||||
|
|
|
@ -117,6 +117,14 @@ impl ConvWith for AtomEdit {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: ConvWith> ConvWith for Option<T> {
|
||||
type Ctx = <T as ConvWith>::Ctx;
|
||||
type Output = Option<<T as ConvWith>::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;
|
||||
|
|
|
@ -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<Vec<Position>> {
|
||||
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,
|
||||
|
|
|
@ -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::<req::ExtendSelection>(
|
||||
&mut req, pool, path_map, world, sender, handle_extend_selection,
|
||||
)?;
|
||||
handle_request_on_threadpool::<req::FindMatchingBrace>(
|
||||
&mut req, pool, path_map, world, sender, handle_find_matching_brace,
|
||||
)?;
|
||||
handle_request_on_threadpool::<req::DocumentSymbolRequest>(
|
||||
&mut req, pool, path_map, world, sender, handle_document_symbol,
|
||||
)?;
|
||||
|
|
|
@ -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<Range>,
|
||||
}
|
||||
|
||||
pub enum FindMatchingBrace {}
|
||||
|
||||
impl Request for FindMatchingBrace {
|
||||
type Params = FindMatchingBraceParams;
|
||||
type Result = Vec<Position>;
|
||||
const METHOD: &'static str = "m/findMatchingBrace";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FindMatchingBraceParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
pub offsets: Vec<Position>,
|
||||
}
|
||||
|
||||
pub enum PublishDecorations {}
|
||||
|
||||
impl Notification for PublishDecorations {
|
||||
|
|
Loading…
Reference in a new issue