add support of feature flag for runnables #4464

Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
Benjamin Coenen 2020-05-21 10:53:29 +02:00
commit a7c8aa7c60
130 changed files with 2984 additions and 1980 deletions

View file

@ -105,7 +105,7 @@ pub fn analysis_bench(
if is_completion {
let options = CompletionConfig::default();
let res = do_work(&mut host, file_id, |analysis| {
analysis.completions(file_position, &options)
analysis.completions(&options, file_position)
});
if verbosity.is_verbose() {
println!("\n{:#?}", res);

View file

@ -11,7 +11,7 @@ use std::{ffi::OsString, path::PathBuf};
use lsp_types::ClientCapabilities;
use ra_flycheck::FlycheckConfig;
use ra_ide::{CompletionConfig, InlayHintsConfig};
use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig};
use ra_project_model::CargoConfig;
use serde::Deserialize;
@ -32,7 +32,38 @@ pub struct Config {
pub inlay_hints: InlayHintsConfig,
pub completion: CompletionConfig,
pub assist: AssistConfig,
pub call_info_full: bool,
pub lens: LensConfig,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LensConfig {
pub run: bool,
pub debug: bool,
pub impementations: bool,
}
impl Default for LensConfig {
fn default() -> Self {
Self { run: true, debug: true, impementations: true }
}
}
impl LensConfig {
pub const NO_LENS: LensConfig = Self { run: false, debug: false, impementations: false };
pub fn any(&self) -> bool {
self.impementations || self.runnable()
}
pub fn none(&self) -> bool {
!self.any()
}
pub fn runnable(&self) -> bool {
self.run || self.debug
}
}
#[derive(Debug, Clone)]
@ -106,7 +137,9 @@ impl Default for Config {
add_call_argument_snippets: true,
..CompletionConfig::default()
},
assist: AssistConfig::default(),
call_info_full: true,
lens: LensConfig::default(),
}
}
}
@ -196,6 +229,16 @@ impl Config {
set(value, "/completion/addCallArgumentSnippets", &mut self.completion.add_call_argument_snippets);
set(value, "/callInfo/full", &mut self.call_info_full);
let mut lens_enabled = true;
set(value, "/lens/enable", &mut lens_enabled);
if lens_enabled {
set(value, "/lens/run", &mut self.lens.run);
set(value, "/lens/debug", &mut self.lens.debug);
set(value, "/lens/implementations", &mut self.lens.impementations);
} else {
self.lens = LensConfig::NO_LENS;
}
log::info!("Config::update() = {:#?}", self);
fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
@ -232,6 +275,7 @@ impl Config {
{
self.client_caps.code_action_literals = value;
}
self.completion.allow_snippets(false);
if let Some(completion) = &doc_caps.completion {
if let Some(completion_item) = &completion.completion_item {
@ -247,5 +291,12 @@ impl Config {
self.client_caps.work_done_progress = value;
}
}
self.assist.allow_snippets(false);
if let Some(experimental) = &caps.experimental {
let enable =
experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true);
self.assist.allow_snippets(enable);
}
}
}

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,54 @@ 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 {
#[serde(skip_serializing_if = "Option::is_none")]
pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>,
#[serde(skip_serializing_if = "Option::is_none")]
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,
@ -476,7 +475,7 @@ pub fn handle_completion(
return Ok(None);
}
let items = match world.analysis().completions(position, &world.config.completion)? {
let items = match world.analysis().completions(&world.config.completion, position)? {
None => return Ok(None),
Some(items) => items,
};
@ -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() {
@ -740,14 +732,21 @@ pub fn handle_code_action(
}
let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default();
for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
for assist in
world.analysis().assists(&world.config.assist, FileRange { file_id, range })?.into_iter()
{
match &assist.group_label {
Some(label) => grouped_assists
.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
@ -775,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))
}
@ -812,88 +786,108 @@ pub fn handle_code_lens(
params: lsp_types::CodeLensParams,
) -> Result<Option<Vec<CodeLens>>> {
let _p = profile("handle_code_lens");
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let mut lenses: Vec<CodeLens> = Default::default();
let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
// Gather runnables
for runnable in world.analysis().runnables(file_id)? {
let title = match &runnable.kind {
RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "\u{fe0e} Run Test",
RunnableKind::DocTest { .. } => "\u{fe0e} Run Doctest",
RunnableKind::Bench { .. } => "Run Bench",
RunnableKind::Bin => {
// Do not suggest binary run on other target than binary
match &cargo_spec {
Some(spec) => match spec.target_kind {
TargetKind::Bin => "Run",
_ => continue,
},
None => continue,
}
}
}
.to_string();
let mut r = to_lsp_runnable(&world, file_id, runnable)?;
let lens = CodeLens {
range: r.range,
command: Some(Command {
title,
command: "rust-analyzer.runSingle".into(),
arguments: Some(vec![to_value(&r).unwrap()]),
}),
data: None,
};
lenses.push(lens);
if r.args[0] == "run" {
r.args[0] = "build".into();
} else {
r.args.push("--no-run".into());
}
let debug_lens = CodeLens {
range: r.range,
command: Some(Command {
title: "Debug".into(),
command: "rust-analyzer.debugSingle".into(),
arguments: Some(vec![to_value(r).unwrap()]),
}),
data: None,
};
lenses.push(debug_lens);
if world.config.lens.none() {
// early return before any db query!
return Ok(Some(lenses));
}
// Handle impls
lenses.extend(
world
.analysis()
.file_structure(file_id)?
.into_iter()
.filter(|it| match it.kind {
SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
_ => false,
})
.map(|it| {
let range = to_proto::range(&line_index, it.node_range);
let pos = range.start;
let lens_params = lsp_types::request::GotoImplementationParams {
text_document_position_params: lsp_types::TextDocumentPositionParams::new(
params.text_document.clone(),
pos,
),
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
};
CodeLens {
range,
command: None,
data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
}
}),
);
let file_id = from_proto::file_id(&world, &params.text_document.uri)?;
let line_index = world.analysis().file_line_index(file_id)?;
let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?;
if world.config.lens.runnable() {
// Gather runnables
for runnable in world.analysis().runnables(file_id)? {
let (run_title, debugee) = match &runnable.kind {
RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => {
("\u{fe0e} Run Test", true)
}
RunnableKind::DocTest { .. } => {
// cargo does not support -no-run for doctests
("\u{fe0e} Run Doctest", false)
}
RunnableKind::Bench { .. } => {
// Nothing wrong with bench debugging
("Run Bench", true)
}
RunnableKind::Bin => {
// Do not suggest binary run on other target than binary
match &cargo_spec {
Some(spec) => match spec.target_kind {
TargetKind::Bin => ("Run", true),
_ => continue,
},
None => continue,
}
}
};
let mut r = to_lsp_runnable(&world, file_id, runnable)?;
if world.config.lens.run {
let lens = CodeLens {
range: r.range,
command: Some(Command {
title: run_title.to_string(),
command: "rust-analyzer.runSingle".into(),
arguments: Some(vec![to_value(&r).unwrap()]),
}),
data: None,
};
lenses.push(lens);
}
if debugee && world.config.lens.debug {
if r.args[0] == "run" {
r.args[0] = "build".into();
} else {
r.args.push("--no-run".into());
}
let debug_lens = CodeLens {
range: r.range,
command: Some(Command {
title: "Debug".into(),
command: "rust-analyzer.debugSingle".into(),
arguments: Some(vec![to_value(r).unwrap()]),
}),
data: None,
};
lenses.push(debug_lens);
}
}
}
if world.config.lens.impementations {
// Handle impls
lenses.extend(
world
.analysis()
.file_structure(file_id)?
.into_iter()
.filter(|it| match it.kind {
SyntaxKind::TRAIT_DEF | SyntaxKind::STRUCT_DEF | SyntaxKind::ENUM_DEF => true,
_ => false,
})
.map(|it| {
let range = to_proto::range(&line_index, it.node_range);
let pos = range.start;
let lens_params = lsp_types::request::GotoImplementationParams {
text_document_position_params: lsp_types::TextDocumentPositionParams::new(
params.text_document.clone(),
pos,
),
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
};
CodeLens {
range,
command: None,
data: Some(to_value(CodeLensResolveData::Impls(lens_params)).unwrap()),
}
}),
);
}
Ok(Some(lenses))
}

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,26 @@ 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.cursor_position.is_none() {
lsp_ext::CodeAction {
title: assist.label,
kind: Some(String::new()),
edit: Some(snippet_workspace_edit(world, assist.source_change)?),
command: None,
}
} else {
assert!(!assist.source_change.is_snippet);
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)
}

View file

@ -333,29 +333,17 @@ fn main() {}
partial_result_params: PartialResultParams::default(),
work_done_progress_params: WorkDoneProgressParams::default(),
},
json!([
{
"command": {
"arguments": [
json!([{
"edit": {
"documentChanges": [
{
"cursorPosition": null,
"label": "Create module",
"workspaceEdit": {
"documentChanges": [
{
"kind": "create",
"uri": "file:///[..]/src/bar.rs"
}
]
}
"kind": "create",
"uri": "file:///[..]/src/bar.rs"
}
],
"command": "rust-analyzer.applySourceChange",
"title": "Create module"
]
},
"title": "Create module"
}
]),
}]),
);
server.request::<CodeActionRequest>(
@ -416,29 +404,17 @@ fn main() {{}}
partial_result_params: PartialResultParams::default(),
work_done_progress_params: WorkDoneProgressParams::default(),
},
json!([
{
"command": {
"arguments": [
json!([{
"edit": {
"documentChanges": [
{
"cursorPosition": null,
"label": "Create module",
"workspaceEdit": {
"documentChanges": [
{
"kind": "create",
"uri": "file:///[..]/src/bar.rs"
}
]
}
"kind": "create",
"uri": "file://[..]/src/bar.rs"
}
],
"command": "rust-analyzer.applySourceChange",
"title": "Create module"
]
},
"title": "Create module"
}
]),
}]),
);
server.request::<CodeActionRequest>(