diff --git a/TODO.md b/TODO.md index 9f20ebb25..67b4df976 100644 --- a/TODO.md +++ b/TODO.md @@ -17,7 +17,6 @@ - [x] retain horiz when moving vertically - [w] retain horiz when moving via ctrl-u/d - [x] deindent -- [ ] ensure_cursor_in_view always before rendering? or always in app after event process? - [x] update lsp on redo/undo - [ ] Implement marks (superset of Selection/Range) - [ ] ctrl-v/ctrl-x on file picker diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index 77e877e4c..45998c8cd 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -506,4 +506,71 @@ impl Client { Ok(response) } + + // formatting + + pub async fn text_document_formatting( + &self, + text_document: lsp::TextDocumentIdentifier, + options: lsp::FormattingOptions, + ) -> anyhow::Result> { + let capabilities = self.capabilities.as_ref().unwrap(); // TODO: needs post init + + // check if we're able to format + let _capabilities = match capabilities.document_formatting_provider { + Some(lsp::OneOf::Left(true)) => (), + Some(lsp::OneOf::Right(_)) => (), + // None | Some(false) + _ => return Ok(Vec::new()), + }; + // TODO: return err::unavailable so we can fall back to tree sitter formatting + + let params = lsp::DocumentFormattingParams { + text_document, + options, + // TODO: support these tokens + work_done_progress_params: lsp::WorkDoneProgressParams { + work_done_token: None, + }, + }; + + let response = self.request::(params).await?; + + Ok(response.unwrap_or_default()) + } + + pub async fn text_document_range_formatting( + &self, + text_document: lsp::TextDocumentIdentifier, + range: lsp::Range, + options: lsp::FormattingOptions, + ) -> anyhow::Result> { + let capabilities = self.capabilities.as_ref().unwrap(); // TODO: needs post init + + log::info!("{:?}", capabilities.document_range_formatting_provider); + // check if we're able to format + let _capabilities = match capabilities.document_range_formatting_provider { + Some(lsp::OneOf::Left(true)) => (), + Some(lsp::OneOf::Right(_)) => (), + // None | Some(false) + _ => return Ok(Vec::new()), + }; + // TODO: return err::unavailable so we can fall back to tree sitter formatting + + let params = lsp::DocumentRangeFormattingParams { + text_document, + range, + options, + // TODO: support these tokens + work_done_progress_params: lsp::WorkDoneProgressParams { + work_done_token: None, + }, + }; + + let response = self + .request::(params) + .await?; + + Ok(response.unwrap_or_default()) + } } diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index a51a11fce..93bc06b54 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -28,7 +28,7 @@ pub enum Error { pub mod util { use super::*; - use helix_core::{RopeSlice, State, Transaction}; + use helix_core::{Range, RopeSlice, State, Transaction}; pub fn lsp_pos_to_pos(doc: RopeSlice, pos: lsp::Position) -> usize { let line = doc.line_to_char(pos.line as usize); @@ -43,6 +43,13 @@ pub mod util { lsp::Position::new(line as u32, col as u32) } + pub fn range_to_lsp_range(doc: RopeSlice, range: Range) -> lsp::Range { + let start = pos_to_lsp_pos(doc, range.from()); + let end = pos_to_lsp_pos(doc, range.to()); + + lsp::Range::new(start, end) + } + pub fn generate_transaction_from_edits( state: &State, edits: Vec, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c5c2ecf18..898981aed 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -888,6 +888,47 @@ pub fn unindent(cx: &mut Context) { doc.append_changes_to_history(); } +pub fn format_selections(cx: &mut Context) { + use helix_lsp::lsp; + let doc = cx.doc(); + + // via lsp if available + // else via tree-sitter indentation calculations + + // TODO: blocking here is not ideal + + let ranges: Vec = doc + .selection() + .ranges() + .iter() + .map(|range| { + helix_lsp::util::range_to_lsp_range(doc.text().slice(..), doc.selection().primary()) + }) + .collect(); + + for range in ranges { + let language_server = match doc.language_server.as_ref() { + Some(language_server) => language_server, + None => return, + }; + + // TODO: handle fails + // TODO: concurrent map + let edits = smol::block_on(language_server.text_document_range_formatting( + doc.identifier(), + range, + lsp::FormattingOptions::default(), + )) + .unwrap_or_default(); + + let transaction = helix_lsp::util::generate_transaction_from_edits(&doc.state, edits); + + doc.apply(&transaction); + } + + doc.append_changes_to_history(); +} + // pub fn save(cx: &mut Context) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index ac4a2bf24..589740fc3 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -178,6 +178,7 @@ pub fn default() -> Keymaps { vec![key!('p')] => commands::paste, vec![key!('>')] => commands::indent, vec![key!('<')] => commands::unindent, + vec![key!('=')] => commands::format_selections, vec![key!(':')] => commands::command_mode, vec![Key { code: KeyCode::Esc,