@@ -9,8 +9,8 @@ use helix_core::{
99 object, pos_at_coords,
1010 regex::{self, Regex},
1111 register::Register,
12− search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
13− RopeSlice, Selection, SmallVec, Tendril, Transaction,
12+ search, selection, surround, textobject, Change, LineEnding, Position, Range, Rope,
13+ RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction,
1414};
1515
1616use helix_view::{
@@ -215,6 +215,7 @@ impl Command {
215215 append_mode,
216216 command_mode,
217217 file_picker,
218+ code_action,
218219 buffer_picker,
219220 symbol_picker,
220221 last_picker,
@@ -2092,6 +2093,120 @@ fn symbol_picker(cx: &mut Context) {
20922093 )
20932094}
20942095
2096+pub fn code_action(cx: &mut Context) {
2097+ let (view, doc) = current!(cx.editor);
2098+
2099+ let language_server = match doc.language_server() {
2100+ Some(language_server) => language_server,
2101+ None => return,
2102+ };
2103+
2104+ let range = range_to_lsp_range(
2105+ doc.text(),
2106+ doc.selection(view.id).primary(),
2107+ language_server.offset_encoding(),
2108+ );
2109+
2110+ let future = language_server.code_actions(doc.identifier(), range);
2111+ let offset_encoding = language_server.offset_encoding();
2112+
2113+ cx.callback(
2114+ future,
2115+ move |_editor: &mut Editor,
2116+ compositor: &mut Compositor,
2117+ response: Option<lsp::CodeActionResponse>| {
2118+ if let Some(actions) = response {
2119+ let picker = Picker::new(
2120+ actions,
2121+ |action| match action {
2122+ lsp::CodeActionOrCommand::CodeAction(action) => {
2123+ action.title.as_str().into()
2124+ }
2125+ lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
2126+ },
2127+ move |editor, code_action, _action| match code_action {
2128+ lsp::CodeActionOrCommand::Command(command) => {
2129+ log::debug!("code action command: {:?}", command);
2130+ editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2131+ }
2132+ lsp::CodeActionOrCommand::CodeAction(code_action) => {
2133+ log::debug!("code action: {:?}", code_action);
2134+ if let Some(ref workspace_edit) = code_action.edit {
2135+ apply_workspace_edit(editor, offset_encoding, workspace_edit)
2136+ }
2137+ }
2138+ },
2139+ );
2140+ compositor.push(Box::new(picker))
2141+ }
2142+ },
2143+ )
2144+}
2145+
2146+fn apply_workspace_edit(
2147+ editor: &mut Editor,
2148+ offset_encoding: OffsetEncoding,
2149+ workspace_edit: &lsp::WorkspaceEdit,
2150+) {
2151+ let edits_to_transaction = |doc: &Rope, edits: &Vec<&lsp::TextEdit>| {
2152+ let lsp_pos_to_pos = |lsp_pos| lsp_pos_to_pos(&doc, lsp_pos, offset_encoding).unwrap();
2153+ let changes = edits.iter().map(|edit| -> Change {
2154+ log::debug!("text edit: {:?}", edit);
2155+ // This clone probably could be optimized if Picker::new would give T instead of &T
2156+ let text_replacement = Tendril::from(edit.new_text.clone());
2157+ (
2158+ lsp_pos_to_pos(edit.range.start),
2159+ lsp_pos_to_pos(edit.range.end),
2160+ Some(text_replacement),
2161+ )
2162+ });
2163+ Transaction::change(doc, changes)
2164+ };
2165+
2166+ if let Some(ref changes) = workspace_edit.changes {
2167+ log::debug!("workspace changes: {:?}", changes);
2168+ editor.set_error(String::from("Handling workspace changesis not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2169+ return;
2170+ // 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
2171+ // TODO: find some example that uses workspace changes, and test it
2172+ // for (url, edits) in changes.iter() {
2173+ // let file_path = url.origin().ascii_serialization();
2174+ // let file_path = std::path::PathBuf::from(file_path);
2175+ // let file = std::fs::File::open(file_path).unwrap();
2176+ // let mut text = Rope::from_reader(file).unwrap();
2177+ // let transaction = edits_to_changes(&text, edits);
2178+ // transaction.apply(&mut text);
2179+ // }
2180+ }
2181+
2182+ if let Some(ref document_changes) = workspace_edit.document_changes {
2183+ match document_changes {
2184+ lsp::DocumentChanges::Edits(document_edits) => {
2185+ for document_edit in document_edits {
2186+ let (view, doc) = current!(editor);
2187+ assert_eq!(doc.url().unwrap(), document_edit.text_document.uri);
2188+ let edits = document_edit
2189+ .edits
2190+ .iter()
2191+ .map(|edit| match edit {
2192+ lsp::OneOf::Left(text_edit) => text_edit,
2193+ lsp::OneOf::Right(annotated_text_edit) => {
2194+ &annotated_text_edit.text_edit
2195+ }
2196+ })
2197+ .collect();
2198+ let transaction = edits_to_transaction(doc.text(), &edits);
2199+ doc.apply(&transaction, view.id);
2200+ }
2201+ }
2202+ lsp::DocumentChanges::Operations(operations) => {
2203+ log::debug!("document changes - operations: {:?}", operations);
2204+ editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
2205+ }
2206+ }
2207+ }
2208+}
2209+
20952210fn last_picker(cx: &mut Context) {
20962211 // TODO: last picker does not seemed to work well with buffer_picker
20972212 cx.callback = Some(Box::new(|compositor: &mut Compositor| {
@@ -3781,6 +3896,8 @@ mode_info! {
37813896 "P" => paste_clipboard_before,
37823897 /// replace selections with clipboard
37833898 "R" => replace_selections_with_clipboard,
3899+ /// perform code action
3900+ "a" => code_action,
37843901 /// keep primary selection
37853902 "space" => keep_primary_selection,
37863903}