|
|
@ -9,8 +9,8 @@ use helix_core::{
|
|
|
|
object, pos_at_coords,
|
|
|
|
object, pos_at_coords,
|
|
|
|
regex::{self, Regex},
|
|
|
|
regex::{self, Regex},
|
|
|
|
register::Register,
|
|
|
|
register::Register,
|
|
|
|
search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
|
|
|
|
search, selection, surround, textobject, Change, LineEnding, Position, Range, Rope,
|
|
|
|
RopeSlice, Selection, SmallVec, Tendril, Transaction,
|
|
|
|
RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
use helix_view::{
|
|
|
|
use helix_view::{
|
|
|
@ -215,6 +215,7 @@ impl Command {
|
|
|
|
append_mode,
|
|
|
|
append_mode,
|
|
|
|
command_mode,
|
|
|
|
command_mode,
|
|
|
|
file_picker,
|
|
|
|
file_picker,
|
|
|
|
|
|
|
|
code_action,
|
|
|
|
buffer_picker,
|
|
|
|
buffer_picker,
|
|
|
|
symbol_picker,
|
|
|
|
symbol_picker,
|
|
|
|
last_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) {
|
|
|
|
fn last_picker(cx: &mut Context) {
|
|
|
|
// TODO: last picker does not seemed to work well with buffer_picker
|
|
|
|
// TODO: last picker does not seemed to work well with buffer_picker
|
|
|
|
cx.callback = Some(Box::new(|compositor: &mut Compositor| {
|
|
|
|
cx.callback = Some(Box::new(|compositor: &mut Compositor| {
|
|
|
@ -3781,6 +3896,8 @@ mode_info! {
|
|
|
|
"P" => paste_clipboard_before,
|
|
|
|
"P" => paste_clipboard_before,
|
|
|
|
/// replace selections with clipboard
|
|
|
|
/// replace selections with clipboard
|
|
|
|
"R" => replace_selections_with_clipboard,
|
|
|
|
"R" => replace_selections_with_clipboard,
|
|
|
|
|
|
|
|
/// perform code action
|
|
|
|
|
|
|
|
"a" => code_action,
|
|
|
|
/// keep primary selection
|
|
|
|
/// keep primary selection
|
|
|
|
"space" => keep_primary_selection,
|
|
|
|
"space" => keep_primary_selection,
|
|
|
|
}
|
|
|
|
}
|
|
|
|