mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Merge #4607
4607: Less rust-analyzer specific onEnter r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
8686d0b0ac
11 changed files with 105 additions and 55 deletions
|
@ -309,7 +309,8 @@ impl Analysis {
|
|||
|
||||
/// Returns an edit which should be applied when opening a new line, fixing
|
||||
/// up minor stuff like continuing the comment.
|
||||
pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> {
|
||||
/// The edit will be a snippet (with `$0`).
|
||||
pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<TextEdit>> {
|
||||
self.with_db(|db| typing::on_enter(&db, position))
|
||||
}
|
||||
|
||||
|
|
|
@ -11,9 +11,7 @@ use ra_syntax::{
|
|||
};
|
||||
use ra_text_edit::TextEdit;
|
||||
|
||||
use crate::{SourceChange, SourceFileEdit};
|
||||
|
||||
pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> {
|
||||
pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
|
||||
let parse = db.parse(position.file_id);
|
||||
let file = parse.tree();
|
||||
let comment = file
|
||||
|
@ -41,9 +39,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
|
|||
let inserted = format!("\n{}{} $0", indent, prefix);
|
||||
let edit = TextEdit::insert(position.offset, inserted);
|
||||
|
||||
let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id });
|
||||
res.is_snippet = true;
|
||||
Some(res)
|
||||
Some(edit)
|
||||
}
|
||||
|
||||
fn followed_by_comment(comment: &ast::Comment) -> bool {
|
||||
|
@ -90,9 +86,8 @@ mod tests {
|
|||
let (analysis, file_id) = single_file(&before);
|
||||
let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
|
||||
|
||||
assert_eq!(result.source_file_edits.len(), 1);
|
||||
let mut actual = before.to_string();
|
||||
result.source_file_edits[0].edit.apply(&mut actual);
|
||||
result.apply(&mut actual);
|
||||
Some(actual)
|
||||
}
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
|
|||
experimental: Some(json!({
|
||||
"joinLines": true,
|
||||
"ssr": true,
|
||||
"onEnter": true,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,8 +102,8 @@ pub enum OnEnter {}
|
|||
|
||||
impl Request for OnEnter {
|
||||
type Params = lsp_types::TextDocumentPositionParams;
|
||||
type Result = Option<SnippetWorkspaceEdit>;
|
||||
const METHOD: &'static str = "rust-analyzer/onEnter";
|
||||
type Result = Option<Vec<SnippetTextEdit>>;
|
||||
const METHOD: &'static str = "experimental/onEnter";
|
||||
}
|
||||
|
||||
pub enum Runnables {}
|
||||
|
|
|
@ -174,13 +174,17 @@ pub fn handle_join_lines(
|
|||
pub fn handle_on_enter(
|
||||
world: WorldSnapshot,
|
||||
params: lsp_types::TextDocumentPositionParams,
|
||||
) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
|
||||
) -> Result<Option<Vec<lsp_ext::SnippetTextEdit>>> {
|
||||
let _p = profile("handle_on_enter");
|
||||
let position = from_proto::file_position(&world, params)?;
|
||||
match world.analysis().on_enter(position)? {
|
||||
None => Ok(None),
|
||||
Some(source_change) => to_proto::snippet_workspace_edit(&world, source_change).map(Some),
|
||||
}
|
||||
let edit = match world.analysis().on_enter(position)? {
|
||||
None => return Ok(None),
|
||||
Some(it) => it,
|
||||
};
|
||||
let line_index = world.analysis().file_line_index(position.file_id)?;
|
||||
let line_endings = world.file_line_endings(position.file_id);
|
||||
let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit);
|
||||
Ok(Some(edit))
|
||||
}
|
||||
|
||||
// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
|
||||
|
|
|
@ -135,6 +135,18 @@ pub(crate) fn text_edit_vec(
|
|||
text_edit.into_iter().map(|indel| self::text_edit(line_index, line_endings, indel)).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn snippet_text_edit_vec(
|
||||
line_index: &LineIndex,
|
||||
line_endings: LineEndings,
|
||||
is_snippet: bool,
|
||||
text_edit: TextEdit,
|
||||
) -> Vec<lsp_ext::SnippetTextEdit> {
|
||||
text_edit
|
||||
.into_iter()
|
||||
.map(|indel| self::snippet_text_edit(line_index, line_endings, is_snippet, indel))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn completion_item(
|
||||
line_index: &LineIndex,
|
||||
line_endings: LineEndings,
|
||||
|
|
|
@ -473,23 +473,14 @@ fn main() {{}}
|
|||
text_document: server.doc_id("src/m0.rs"),
|
||||
position: Position { line: 0, character: 5 },
|
||||
},
|
||||
json!({
|
||||
"documentChanges": [
|
||||
{
|
||||
"edits": [
|
||||
{
|
||||
json!([{
|
||||
"insertTextFormat": 2,
|
||||
"newText": "\n/// $0",
|
||||
"range": {
|
||||
"end": { "character": 5, "line": 0 },
|
||||
"start": { "character": 5, "line": 0 }
|
||||
}
|
||||
}
|
||||
],
|
||||
"textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
|
||||
}
|
||||
]
|
||||
}),
|
||||
}]),
|
||||
);
|
||||
let elapsed = start.elapsed();
|
||||
assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
|
||||
|
@ -519,23 +510,14 @@ version = \"0.0.0\"
|
|||
text_document: server.doc_id("src/main.rs"),
|
||||
position: Position { line: 0, character: 8 },
|
||||
},
|
||||
json!({
|
||||
"documentChanges": [
|
||||
{
|
||||
"edits": [
|
||||
{
|
||||
json!([{
|
||||
"insertTextFormat": 2,
|
||||
"newText": "\r\n/// $0",
|
||||
"range": {
|
||||
"end": { "line": 0, "character": 8 },
|
||||
"start": { "line": 0, "character": 8 }
|
||||
}
|
||||
}
|
||||
],
|
||||
"textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
|
||||
}
|
||||
]
|
||||
}),
|
||||
}]),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -138,6 +138,59 @@ fn main() {
|
|||
Currently this is left to editor's discretion, but it might be useful to specify on the server via snippets.
|
||||
However, it then becomes unclear how it works with multi cursor.
|
||||
|
||||
## On Enter
|
||||
|
||||
**Issue:** https://github.com/microsoft/language-server-protocol/issues/1001
|
||||
|
||||
**Server Capability:** `{ "onEnter": boolean }`
|
||||
|
||||
This request is send from client to server to handle <kbd>Enter</kbd> keypress.
|
||||
|
||||
**Method:** `experimental/onEnter`
|
||||
|
||||
**Request:**: `TextDocumentPositionParams`
|
||||
|
||||
**Response:**
|
||||
|
||||
```typescript
|
||||
SnippetTextEdit[]
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// Some /*cursor here*/ docs
|
||||
let x = 92;
|
||||
}
|
||||
```
|
||||
|
||||
`experimental/onEnter` returns the following snippet
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// Some
|
||||
// $0 docs
|
||||
let x = 92;
|
||||
}
|
||||
```
|
||||
|
||||
The primary goal of `onEnter` is to handle automatic indentation when opening a new line.
|
||||
This is not yet implemented.
|
||||
The secondary goal is to handle fixing up syntax, like continuing doc strings and comments, and escaping `\n` in string literals.
|
||||
|
||||
As proper cursor positioning is raison-d'etat for `onEnter`, it uses `SnippetTextEdit`.
|
||||
|
||||
### Unresolved Question
|
||||
|
||||
* How to deal with synchronicity of the request?
|
||||
One option is to require the client to block until the server returns the response.
|
||||
Another option is to do a OT-style merging of edits from client and server.
|
||||
A third option is to do a record-replay: client applies heuristic on enter immediatelly, then applies all user's keypresses.
|
||||
When the server is ready with the response, the client rollbacks all the changes and applies the recorded actions on top of the correct response.
|
||||
* How to deal with multiple carets?
|
||||
* Should we extend this to arbitrary typed events and not just `onEnter`?
|
||||
|
||||
## Structural Search Replace (SSR)
|
||||
|
||||
**Server Capability:** `{ "ssr": boolean }`
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as lc from 'vscode-languageclient';
|
|||
import * as ra from './rust-analyzer-api';
|
||||
|
||||
import { Ctx, Cmd } from './ctx';
|
||||
import { applySnippetWorkspaceEdit } from './snippets';
|
||||
import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { RunnableQuickPick, selectRunnable, createTask } from './run';
|
||||
import { AstInspector } from './ast_inspector';
|
||||
|
@ -102,7 +102,7 @@ export function onEnter(ctx: Ctx): Cmd {
|
|||
|
||||
if (!editor || !client) return false;
|
||||
|
||||
const change = await client.sendRequest(ra.onEnter, {
|
||||
const lcEdits = await client.sendRequest(ra.onEnter, {
|
||||
textDocument: { uri: editor.document.uri.toString() },
|
||||
position: client.code2ProtocolConverter.asPosition(
|
||||
editor.selection.active,
|
||||
|
@ -111,10 +111,10 @@ export function onEnter(ctx: Ctx): Cmd {
|
|||
// client.logFailedRequest(OnEnterRequest.type, error);
|
||||
return null;
|
||||
});
|
||||
if (!change) return false;
|
||||
if (!lcEdits) return false;
|
||||
|
||||
const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change);
|
||||
await applySnippetWorkspaceEdit(workspaceEdit);
|
||||
const edits = client.protocol2CodeConverter.asTextEdits(lcEdits);
|
||||
await applySnippetTextEdits(editor, edits);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,8 +67,7 @@ export interface JoinLinesParams {
|
|||
}
|
||||
export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], unknown>('experimental/joinLines');
|
||||
|
||||
|
||||
export const onEnter = request<lc.TextDocumentPositionParams, Option<lc.WorkspaceEdit>>("onEnter");
|
||||
export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], unknown>('experimental/onEnter');
|
||||
|
||||
export interface RunnablesParams {
|
||||
textDocument: lc.TextDocumentIdentifier;
|
||||
|
|
|
@ -8,7 +8,10 @@ export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
|
|||
|
||||
const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
|
||||
if (!editor) return;
|
||||
await applySnippetTextEdits(editor, edits);
|
||||
}
|
||||
|
||||
export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vscode.TextEdit[]) {
|
||||
let selection: vscode.Selection | undefined = undefined;
|
||||
let lineDelta = 0;
|
||||
await editor.edit((builder) => {
|
||||
|
|
Loading…
Reference in a new issue