feat: code actions - document edits (#478)
* wip: Code actions * fix(term): use current macro instead Context::context * feat(lsp): set code_action capabilities * feat(term): set SPC-a to code_action * feat(term): wip on applying code actions * deps: `cargo update` * feat(term): applying code actions edits * fix(term): cleanup of apply_edit * fix(term): applying edits as a whole thing instead one by one * refactor(term): move apply_edits below * fix(term): improve unimplemented messages for further investigation * fix(term): change code action command comment Co-authored-by: Ivan Tham <pickfire@riseup.net> * fix(term): add matching `}` * fix(term): cleanup, todo!() on workspace edit * fix(term): remove unrelated workspace_symbol_picker * fix(term): apply cargo-clippy suggestions * fix(term): replace todo!'s with editor.set_error Co-authored-by: Blaž Hrastnik <blaz@mxxn.io> Co-authored-by: Ivan Tham <pickfire@riseup.net>
This commit is contained in:
parent
bda4f5c1cd
commit
48e344a2a8
3 changed files with 178 additions and 14 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -422,9 +422,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -460,9 +460,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
@ -494,9 +494,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
version = "0.2.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
@ -662,9 +662,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
|||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
|
||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
|
@ -994,9 +994,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
|
||||
checksum = "4ac2e1d4bd0f75279cfd5a076e0d578bbf02c22b7c39e766c437dd49b3ec43e0"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
@ -1029,9 +1029,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37"
|
||||
checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -247,6 +247,26 @@ impl Client {
|
|||
content_format: Some(vec![lsp::MarkupKind::Markdown]),
|
||||
..Default::default()
|
||||
}),
|
||||
code_action: Some(lsp::CodeActionClientCapabilities {
|
||||
code_action_literal_support: Some(lsp::CodeActionLiteralSupport {
|
||||
code_action_kind: lsp::CodeActionKindLiteralSupport {
|
||||
value_set: [
|
||||
lsp::CodeActionKind::EMPTY,
|
||||
lsp::CodeActionKind::QUICKFIX,
|
||||
lsp::CodeActionKind::REFACTOR,
|
||||
lsp::CodeActionKind::REFACTOR_EXTRACT,
|
||||
lsp::CodeActionKind::REFACTOR_INLINE,
|
||||
lsp::CodeActionKind::REFACTOR_REWRITE,
|
||||
lsp::CodeActionKind::SOURCE,
|
||||
lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
|
||||
]
|
||||
.iter()
|
||||
.map(|kind| kind.as_str().to_string())
|
||||
.collect(),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
window: Some(lsp::WindowClientCapabilities {
|
||||
|
@ -713,4 +733,31 @@ impl Client {
|
|||
|
||||
self.call::<lsp::request::DocumentSymbolRequest>(params)
|
||||
}
|
||||
|
||||
// empty string to get all symbols
|
||||
pub fn workspace_symbols(&self, query: String) -> impl Future<Output = Result<Value>> {
|
||||
let params = lsp::WorkspaceSymbolParams {
|
||||
query,
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams::default(),
|
||||
partial_result_params: lsp::PartialResultParams::default(),
|
||||
};
|
||||
|
||||
self.call::<lsp::request::WorkspaceSymbol>(params)
|
||||
}
|
||||
|
||||
pub fn code_actions(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
range: lsp::Range,
|
||||
) -> impl Future<Output = Result<Value>> {
|
||||
let params = lsp::CodeActionParams {
|
||||
text_document,
|
||||
range,
|
||||
context: lsp::CodeActionContext::default(),
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams::default(),
|
||||
partial_result_params: lsp::PartialResultParams::default(),
|
||||
};
|
||||
|
||||
self.call::<lsp::request::CodeActionRequest>(params)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ use helix_core::{
|
|||
object, pos_at_coords,
|
||||
regex::{self, Regex},
|
||||
register::Register,
|
||||
search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
|
||||
RopeSlice, Selection, SmallVec, Tendril, Transaction,
|
||||
search, selection, surround, textobject, Change, LineEnding, Position, Range, Rope,
|
||||
RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction,
|
||||
};
|
||||
|
||||
use helix_view::{
|
||||
|
@ -215,6 +215,7 @@ impl Command {
|
|||
append_mode,
|
||||
command_mode,
|
||||
file_picker,
|
||||
code_action,
|
||||
buffer_picker,
|
||||
symbol_picker,
|
||||
last_picker,
|
||||
|
@ -2092,6 +2093,120 @@ fn symbol_picker(cx: &mut Context) {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn code_action(cx: &mut Context) {
|
||||
let (view, doc) = current!(cx.editor);
|
||||
|
||||
let language_server = match doc.language_server() {
|
||||
Some(language_server) => language_server,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let range = range_to_lsp_range(
|
||||
doc.text(),
|
||||
doc.selection(view.id).primary(),
|
||||
language_server.offset_encoding(),
|
||||
);
|
||||
|
||||
let future = language_server.code_actions(doc.identifier(), range);
|
||||
let offset_encoding = language_server.offset_encoding();
|
||||
|
||||
cx.callback(
|
||||
future,
|
||||
move |_editor: &mut Editor,
|
||||
compositor: &mut Compositor,
|
||||
response: Option<lsp::CodeActionResponse>| {
|
||||
if let Some(actions) = response {
|
||||
let picker = Picker::new(
|
||||
actions,
|
||||
|action| match action {
|
||||
lsp::CodeActionOrCommand::CodeAction(action) => {
|
||||
action.title.as_str().into()
|
||||
}
|
||||
lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
|
||||
},
|
||||
move |editor, code_action, _action| match code_action {
|
||||
lsp::CodeActionOrCommand::Command(command) => {
|
||||
log::debug!("code action command: {:?}", command);
|
||||
editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
|
||||
}
|
||||
lsp::CodeActionOrCommand::CodeAction(code_action) => {
|
||||
log::debug!("code action: {:?}", code_action);
|
||||
if let Some(ref workspace_edit) = code_action.edit {
|
||||
apply_workspace_edit(editor, offset_encoding, workspace_edit)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
compositor.push(Box::new(picker))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_workspace_edit(
|
||||
editor: &mut Editor,
|
||||
offset_encoding: OffsetEncoding,
|
||||
workspace_edit: &lsp::WorkspaceEdit,
|
||||
) {
|
||||
let edits_to_transaction = |doc: &Rope, edits: &Vec<&lsp::TextEdit>| {
|
||||
let lsp_pos_to_pos = |lsp_pos| lsp_pos_to_pos(&doc, lsp_pos, offset_encoding).unwrap();
|
||||
let changes = edits.iter().map(|edit| -> Change {
|
||||
log::debug!("text edit: {:?}", edit);
|
||||
// This clone probably could be optimized if Picker::new would give T instead of &T
|
||||
let text_replacement = Tendril::from(edit.new_text.clone());
|
||||
(
|
||||
lsp_pos_to_pos(edit.range.start),
|
||||
lsp_pos_to_pos(edit.range.end),
|
||||
Some(text_replacement),
|
||||
)
|
||||
});
|
||||
Transaction::change(doc, changes)
|
||||
};
|
||||
|
||||
if let Some(ref changes) = workspace_edit.changes {
|
||||
log::debug!("workspace changes: {:?}", changes);
|
||||
editor.set_error(String::from("Handling workspace changesis not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
|
||||
return;
|
||||
// Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
|
||||
// TODO: find some example that uses workspace changes, and test it
|
||||
// for (url, edits) in changes.iter() {
|
||||
// let file_path = url.origin().ascii_serialization();
|
||||
// let file_path = std::path::PathBuf::from(file_path);
|
||||
// let file = std::fs::File::open(file_path).unwrap();
|
||||
// let mut text = Rope::from_reader(file).unwrap();
|
||||
// let transaction = edits_to_changes(&text, edits);
|
||||
// transaction.apply(&mut text);
|
||||
// }
|
||||
}
|
||||
|
||||
if let Some(ref document_changes) = workspace_edit.document_changes {
|
||||
match document_changes {
|
||||
lsp::DocumentChanges::Edits(document_edits) => {
|
||||
for document_edit in document_edits {
|
||||
let (view, doc) = current!(editor);
|
||||
assert_eq!(doc.url().unwrap(), document_edit.text_document.uri);
|
||||
let edits = document_edit
|
||||
.edits
|
||||
.iter()
|
||||
.map(|edit| match edit {
|
||||
lsp::OneOf::Left(text_edit) => text_edit,
|
||||
lsp::OneOf::Right(annotated_text_edit) => {
|
||||
&annotated_text_edit.text_edit
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let transaction = edits_to_transaction(doc.text(), &edits);
|
||||
doc.apply(&transaction, view.id);
|
||||
}
|
||||
}
|
||||
lsp::DocumentChanges::Operations(operations) => {
|
||||
log::debug!("document changes - operations: {:?}", operations);
|
||||
editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn last_picker(cx: &mut Context) {
|
||||
// TODO: last picker does not seemed to work well with buffer_picker
|
||||
cx.callback = Some(Box::new(|compositor: &mut Compositor| {
|
||||
|
@ -3781,6 +3896,8 @@ mode_info! {
|
|||
"P" => paste_clipboard_before,
|
||||
/// replace selections with clipboard
|
||||
"R" => replace_selections_with_clipboard,
|
||||
/// perform code action
|
||||
"a" => code_action,
|
||||
/// keep primary selection
|
||||
"space" => keep_primary_selection,
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue