diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 5ac002d82f0c..d983cd910023 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -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> {
+ /// The edit will be a snippet (with `$0`).
+ pub fn on_enter(&self, position: FilePosition) -> Cancelable > {
self.with_db(|db| typing::on_enter(&db, position))
}
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index e7d64b4f68c2..4a239bc67deb 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -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 {
+pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option {
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 bool {
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 780fc93174fa..d55cbb15fe88 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -85,6 +85,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
experimental: Some(json!({
"joinLines": true,
"ssr": true,
+ "onEnter": true,
})),
}
}
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 52e4fcbecae5..1cce1baa4552 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -102,8 +102,8 @@ pub enum OnEnter {}
impl Request for OnEnter {
type Params = lsp_types::TextDocumentPositionParams;
- type Result = Option;
- const METHOD: &'static str = "rust-analyzer/onEnter";
+ type Result = Option>;
+ const METHOD: &'static str = "experimental/onEnter";
}
pub enum Runnables {}
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index d73107968123..a13a0e1f523b 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -174,13 +174,17 @@ pub fn handle_join_lines(
pub fn handle_on_enter(
world: WorldSnapshot,
params: lsp_types::TextDocumentPositionParams,
-) -> Result> {
+) -> Result >> {
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`.
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 81a347247cb6..39d58f1e01bd 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.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 {
+ 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,
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 55035cfae183..0cf009175cfd 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -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:** TBA
+
+**Server Capability:** `{ "onEnter": boolean }`
+
+This request is send from client to server to handle Enter 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 }`
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 573af5aa580d..e080301405dd 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -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;
}
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts
index 900c5cd5bce5..c10c0fa78942 100644
--- a/editors/code/src/rust-analyzer-api.ts
+++ b/editors/code/src/rust-analyzer-api.ts
@@ -67,8 +67,7 @@ export interface JoinLinesParams {
}
export const joinLines = new lc.RequestType('experimental/joinLines');
-
-export const onEnter = request>("onEnter");
+export const onEnter = new lc.RequestType('experimental/onEnter');
export interface RunnablesParams {
textDocument: lc.TextDocumentIdentifier;
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index 794530162dc0..bcb3f2cc7613 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -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) => {