Server side of SnippetTextEdit

This commit is contained in:
Aleksey Kladov 2020-05-18 00:11:40 +02:00
parent a752853350
commit 2bf6b16a7f
9 changed files with 200 additions and 119 deletions

View file

@ -208,16 +208,6 @@ impl AssistBuilder {
pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
self.edit.replace(range, replace_with.into())
}
/// Append specified `text` at the given `offset`
pub(crate) fn replace_snippet(
&mut self,
_cap: SnippetCap,
range: TextRange,
replace_with: impl Into<String>,
) {
self.is_snippet = true;
self.edit.replace(range, replace_with.into())
}
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
}

View file

@ -3,9 +3,11 @@ pub(crate) mod to_proto;
use std::{collections::HashMap, sync::Arc};
use lsp_types::{CodeActionOrCommand, Diagnostic, Range};
use lsp_types::{Diagnostic, Range};
use ra_ide::FileId;
use crate::lsp_ext;
pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>;
#[derive(Debug, Default, Clone)]
@ -18,13 +20,13 @@ pub struct DiagnosticCollection {
#[derive(Debug, Clone)]
pub struct Fix {
pub range: Range,
pub action: CodeActionOrCommand,
pub action: lsp_ext::CodeAction,
}
#[derive(Debug)]
pub enum DiagnosticTask {
ClearCheck,
AddCheck(FileId, Diagnostic, Vec<CodeActionOrCommand>),
AddCheck(FileId, Diagnostic, Vec<lsp_ext::CodeAction>),
SetNative(FileId, Vec<Diagnostic>),
}
@ -38,7 +40,7 @@ impl DiagnosticCollection {
&mut self,
file_id: FileId,
diagnostic: Diagnostic,
fixes: Vec<CodeActionOrCommand>,
fixes: Vec<lsp_ext::CodeAction>,
) {
let diagnostics = self.check.entry(file_id).or_default();
for existing_diagnostic in diagnostics.iter() {

View file

@ -68,9 +68,9 @@ expression: diag
kind: Some(
"quickfix",
),
diagnostics: None,
command: None,
edit: Some(
WorkspaceEdit {
SnippetWorkspaceEdit {
changes: Some(
{
"file:///test/src/main.rs": [
@ -106,8 +106,6 @@ expression: diag
document_changes: None,
},
),
command: None,
is_preferred: None,
},
],
},

View file

@ -53,9 +53,9 @@ expression: diag
kind: Some(
"quickfix",
),
diagnostics: None,
command: None,
edit: Some(
WorkspaceEdit {
SnippetWorkspaceEdit {
changes: Some(
{
"file:///test/driver/subcommand/repl.rs": [
@ -78,8 +78,6 @@ expression: diag
document_changes: None,
},
),
command: None,
is_preferred: None,
},
],
},

View file

@ -7,13 +7,13 @@ use std::{
};
use lsp_types::{
CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag,
Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit,
Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
NumberOrString, Position, Range, TextEdit, Url,
};
use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion};
use stdx::format_to;
use crate::Result;
use crate::{lsp_ext, Result};
/// Converts a Rust level string to a LSP severity
fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
@ -110,7 +110,7 @@ fn is_deprecated(rd: &ra_flycheck::Diagnostic) -> bool {
enum MappedRustChildDiagnostic {
Related(DiagnosticRelatedInformation),
SuggestedFix(CodeAction),
SuggestedFix(lsp_ext::CodeAction),
MessageLine(String),
}
@ -143,13 +143,15 @@ fn map_rust_child_diagnostic(
message: rd.message.clone(),
})
} else {
MappedRustChildDiagnostic::SuggestedFix(CodeAction {
MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction {
title: rd.message.clone(),
kind: Some("quickfix".to_string()),
diagnostics: None,
edit: Some(WorkspaceEdit::new(edit_map)),
edit: Some(lsp_ext::SnippetWorkspaceEdit {
// FIXME: there's no good reason to use edit_map here....
changes: Some(edit_map),
document_changes: None,
}),
command: None,
is_preferred: None,
})
}
}
@ -158,7 +160,7 @@ fn map_rust_child_diagnostic(
pub(crate) struct MappedRustDiagnostic {
pub location: Location,
pub diagnostic: Diagnostic,
pub fixes: Vec<CodeAction>,
pub fixes: Vec<lsp_ext::CodeAction>,
}
/// Converts a Rust root diagnostic to LSP form

View file

@ -1,6 +1,6 @@
//! rust-analyzer extensions to the LSP.
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf};
use lsp_types::request::Request;
use lsp_types::{Location, Position, Range, TextDocumentIdentifier};
@ -137,7 +137,7 @@ pub struct Runnable {
#[serde(rename_all = "camelCase")]
pub struct SourceChange {
pub label: String,
pub workspace_edit: lsp_types::WorkspaceEdit,
pub workspace_edit: SnippetWorkspaceEdit,
pub cursor_position: Option<lsp_types::TextDocumentPositionParams>,
}
@ -183,3 +183,52 @@ pub struct SsrParams {
pub query: String,
pub parse_only: bool,
}
pub enum CodeActionRequest {}
impl Request for CodeActionRequest {
type Params = lsp_types::CodeActionParams;
type Result = Option<Vec<CodeAction>>;
const METHOD: &'static str = "textDocument/codeAction";
}
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
pub struct CodeAction {
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<lsp_types::Command>,
#[serde(skip_serializing_if = "Option::is_none")]
pub edit: Option<SnippetWorkspaceEdit>,
}
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SnippetWorkspaceEdit {
pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>,
pub document_changes: Option<Vec<SnippetDocumentChangeOperation>>,
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[serde(untagged, rename_all = "lowercase")]
pub enum SnippetDocumentChangeOperation {
Op(lsp_types::ResourceOp),
Edit(SnippetTextDocumentEdit),
}
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SnippetTextDocumentEdit {
pub text_document: lsp_types::VersionedTextDocumentIdentifier,
pub edits: Vec<SnippetTextEdit>,
}
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SnippetTextEdit {
pub range: Range,
pub new_text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub insert_text_format: Option<lsp_types::InsertTextFormat>,
}

View file

@ -518,6 +518,7 @@ fn on_request(
.on::<lsp_ext::ParentModule>(handlers::handle_parent_module)?
.on::<lsp_ext::Runnables>(handlers::handle_runnables)?
.on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
.on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
.on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
.on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
@ -525,7 +526,6 @@ fn on_request(
.on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)?
.on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)?
.on::<lsp_types::request::Completion>(handlers::handle_completion)?
.on::<lsp_types::request::CodeActionRequest>(handlers::handle_code_action)?
.on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)?
.on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)?
.on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)?

View file

@ -11,12 +11,11 @@ use lsp_server::ErrorCode;
use lsp_types::{
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams,
SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier,
TextEdit, Url, WorkspaceEdit,
CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location,
MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams,
SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, Url, WorkspaceEdit,
};
use ra_ide::{
Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
@ -585,9 +584,8 @@ pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Optio
None => return Ok(None),
Some(it) => it.info,
};
let source_change = to_proto::source_change(&world, source_change)?;
Ok(Some(source_change.workspace_edit))
let workspace_edit = to_proto::workspace_edit(&world, source_change)?;
Ok(Some(workspace_edit))
}
pub fn handle_references(
@ -696,14 +694,21 @@ pub fn handle_formatting(
pub fn handle_code_action(
world: WorldSnapshot,
params: lsp_types::CodeActionParams,
) -> Result<Option<CodeActionResponse>> {
) -> Result<Option<Vec<lsp_ext::CodeAction>>> {
let _p = profile("handle_code_action");
// We intentionally don't support command-based actions, as those either
// requires custom client-code anyway, or requires server-initiated edits.
// Server initiated edits break causality, so we avoid those as well.
if !world.config.client_caps.code_action_literals {
return Ok(None);
}
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let range = from_proto::text_range(&line_index, params.range);
let diagnostics = world.analysis().diagnostics(file_id)?;
let mut res = CodeActionResponse::default();
let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
let fixes_from_diagnostics = diagnostics
.into_iter()
@ -713,22 +718,9 @@ pub fn handle_code_action(
for source_edit in fixes_from_diagnostics {
let title = source_edit.label.clone();
let edit = to_proto::source_change(&world, source_edit)?;
let command = Command {
title,
command: "rust-analyzer.applySourceChange".to_string(),
arguments: Some(vec![to_value(edit).unwrap()]),
};
let action = CodeAction {
title: command.title.clone(),
kind: None,
diagnostics: None,
edit: None,
command: Some(command),
is_preferred: None,
};
res.push(action.into());
let edit = to_proto::snippet_workspace_edit(&world, source_edit)?;
let action = lsp_ext::CodeAction { title, kind: None, edit: Some(edit), command: None };
res.push(action);
}
for fix in world.check_fixes.get(&file_id).into_iter().flatten() {
@ -748,8 +740,13 @@ pub fn handle_code_action(
.entry(label.to_owned())
.or_insert_with(|| {
let idx = res.len();
let dummy = Command::new(String::new(), String::new(), None);
res.push(dummy.into());
let dummy = lsp_ext::CodeAction {
title: String::new(),
kind: None,
command: None,
edit: None,
};
res.push(dummy);
(idx, Vec::new())
})
.1
@ -777,35 +774,10 @@ pub fn handle_code_action(
command: "rust-analyzer.selectAndApplySourceChange".to_string(),
arguments: Some(vec![serde_json::Value::Array(arguments)]),
});
res[idx] = CodeAction {
title,
kind: None,
diagnostics: None,
edit: None,
command,
is_preferred: None,
}
.into();
res[idx] = lsp_ext::CodeAction { title, kind: None, edit: None, command };
}
}
// If the client only supports commands then filter the list
// and remove and actions that depend on edits.
if !world.config.client_caps.code_action_literals {
// FIXME: use drain_filter once it hits stable.
res = res
.into_iter()
.filter_map(|it| match it {
cmd @ lsp_types::CodeActionOrCommand::Command(_) => Some(cmd),
lsp_types::CodeActionOrCommand::CodeAction(action) => match action.command {
Some(cmd) if action.edit.is_none() => {
Some(lsp_types::CodeActionOrCommand::Command(cmd))
}
_ => None,
},
})
.collect();
}
Ok(Some(res))
}

View file

@ -112,6 +112,22 @@ pub(crate) fn text_edit(
lsp_types::TextEdit { range, new_text }
}
pub(crate) fn snippet_text_edit(
line_index: &LineIndex,
line_endings: LineEndings,
is_snippet: bool,
indel: Indel,
) -> lsp_ext::SnippetTextEdit {
let text_edit = text_edit(line_index, line_endings, indel);
let insert_text_format =
if is_snippet { Some(lsp_types::InsertTextFormat::Snippet) } else { None };
lsp_ext::SnippetTextEdit {
range: text_edit.range,
new_text: text_edit.new_text,
insert_text_format,
}
}
pub(crate) fn text_edit_vec(
line_index: &LineIndex,
line_endings: LineEndings,
@ -441,10 +457,11 @@ pub(crate) fn goto_definition_response(
}
}
pub(crate) fn text_document_edit(
pub(crate) fn snippet_text_document_edit(
world: &WorldSnapshot,
is_snippet: bool,
source_file_edit: SourceFileEdit,
) -> Result<lsp_types::TextDocumentEdit> {
) -> Result<lsp_ext::SnippetTextDocumentEdit> {
let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?;
let line_index = world.analysis().file_line_index(source_file_edit.file_id)?;
let line_endings = world.file_line_endings(source_file_edit.file_id);
@ -452,9 +469,9 @@ pub(crate) fn text_document_edit(
.edit
.as_indels()
.iter()
.map(|it| text_edit(&line_index, line_endings, it.clone()))
.map(|it| snippet_text_edit(&line_index, line_endings, is_snippet, it.clone()))
.collect();
Ok(lsp_types::TextDocumentEdit { text_document, edits })
Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits })
}
pub(crate) fn resource_op(
@ -500,20 +517,70 @@ pub(crate) fn source_change(
})
}
};
let mut document_changes: Vec<lsp_types::DocumentChangeOperation> = Vec::new();
let label = source_change.label.clone();
let workspace_edit = self::snippet_workspace_edit(world, source_change)?;
Ok(lsp_ext::SourceChange { label, workspace_edit, cursor_position })
}
pub(crate) fn snippet_workspace_edit(
world: &WorldSnapshot,
source_change: SourceChange,
) -> Result<lsp_ext::SnippetWorkspaceEdit> {
let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new();
for op in source_change.file_system_edits {
let op = resource_op(&world, op)?;
document_changes.push(lsp_types::DocumentChangeOperation::Op(op));
document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op));
}
for edit in source_change.source_file_edits {
let edit = text_document_edit(&world, edit)?;
document_changes.push(lsp_types::DocumentChangeOperation::Edit(edit));
let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?;
document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit));
}
let workspace_edit =
lsp_ext::SnippetWorkspaceEdit { changes: None, document_changes: Some(document_changes) };
Ok(workspace_edit)
}
pub(crate) fn workspace_edit(
world: &WorldSnapshot,
source_change: SourceChange,
) -> Result<lsp_types::WorkspaceEdit> {
assert!(!source_change.is_snippet);
snippet_workspace_edit(world, source_change).map(|it| it.into())
}
impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit {
fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit {
lsp_types::WorkspaceEdit {
changes: None,
document_changes: snippet_workspace_edit.document_changes.map(|changes| {
lsp_types::DocumentChanges::Operations(
changes
.into_iter()
.map(|change| match change {
lsp_ext::SnippetDocumentChangeOperation::Op(op) => {
lsp_types::DocumentChangeOperation::Op(op)
}
lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => {
lsp_types::DocumentChangeOperation::Edit(
lsp_types::TextDocumentEdit {
text_document: edit.text_document,
edits: edit
.edits
.into_iter()
.map(|edit| lsp_types::TextEdit {
range: edit.range,
new_text: edit.new_text,
})
.collect(),
},
)
}
})
.collect(),
)
}),
}
}
let workspace_edit = lsp_types::WorkspaceEdit {
changes: None,
document_changes: Some(lsp_types::DocumentChanges::Operations(document_changes)),
};
Ok(lsp_ext::SourceChange { label: source_change.label, workspace_edit, cursor_position })
}
pub fn call_hierarchy_item(
@ -571,22 +638,25 @@ fn main() <fold>{
}
}
pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_types::CodeAction> {
let source_change = source_change(&world, assist.source_change)?;
let arg = serde_json::to_value(source_change)?;
let title = assist.label;
let command = lsp_types::Command {
title: title.clone(),
command: "rust-analyzer.applySourceChange".to_string(),
arguments: Some(vec![arg]),
};
pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_ext::CodeAction> {
let res = if assist.source_change.is_snippet {
lsp_ext::CodeAction {
title: assist.label,
kind: Some(String::new()),
edit: Some(snippet_workspace_edit(world, assist.source_change)?),
command: None,
}
} else {
let source_change = source_change(&world, assist.source_change)?;
let arg = serde_json::to_value(source_change)?;
let title = assist.label;
let command = lsp_types::Command {
title: title.clone(),
command: "rust-analyzer.applySourceChange".to_string(),
arguments: Some(vec![arg]),
};
Ok(lsp_types::CodeAction {
title,
kind: Some(String::new()),
diagnostics: None,
edit: None,
command: Some(command),
is_preferred: None,
})
lsp_ext::CodeAction { title, kind: Some(String::new()), edit: None, command: Some(command) }
};
Ok(res)
}