on master help sign in
commands: = as range formatting (via lsp)
1ae7c43Blaž Hrastnik
TODO.md−1
@@ -17,7 +17,6 @@
17- [x] retain horiz when moving vertically
17- [x] retain horiz when moving vertically
18- [w] retain horiz when moving via ctrl-u/d
18- [w] retain horiz when moving via ctrl-u/d
19- [x] deindent
19- [x] deindent
20−- [ ] ensure_cursor_in_view always before rendering? or always in app after event process?
21- [x] update lsp on redo/undo
20- [x] update lsp on redo/undo
22- [ ] Implement marks (superset of Selection/Range)
21- [ ] Implement marks (superset of Selection/Range)
23- [ ] ctrl-v/ctrl-x on file picker
22- [ ] ctrl-v/ctrl-x on file picker
helix-lsp/src/client.rs+67
@@ -506,4 +506,71 @@ impl Client {
506
506
507 Ok(response)
507 Ok(response)
508 }
508 }
509+
510+ // formatting
511+
512+ pub async fn text_document_formatting(
513+ &self,
514+ text_document: lsp::TextDocumentIdentifier,
515+ options: lsp::FormattingOptions,
516+ ) -> anyhow::Result<Vec<lsp::TextEdit>> {
517+ let capabilities = self.capabilities.as_ref().unwrap(); // TODO: needs post init
518+
519+ // check if we're able to format
520+ let _capabilities = match capabilities.document_formatting_provider {
521+ Some(lsp::OneOf::Left(true)) => (),
522+ Some(lsp::OneOf::Right(_)) => (),
523+ // None | Some(false)
524+ _ => return Ok(Vec::new()),
525+ };
526+ // TODO: return err::unavailable so we can fall back to tree sitter formatting
527+
528+ let params = lsp::DocumentFormattingParams {
529+ text_document,
530+ options,
531+ // TODO: support these tokens
532+ work_done_progress_params: lsp::WorkDoneProgressParams {
533+ work_done_token: None,
534+ },
535+ };
536+
537+ let response = self.request::<lsp::request::Formatting>(params).await?;
538+
539+ Ok(response.unwrap_or_default())
540+ }
541+
542+ pub async fn text_document_range_formatting(
543+ &self,
544+ text_document: lsp::TextDocumentIdentifier,
545+ range: lsp::Range,
546+ options: lsp::FormattingOptions,
547+ ) -> anyhow::Result<Vec<lsp::TextEdit>> {
548+ let capabilities = self.capabilities.as_ref().unwrap(); // TODO: needs post init
549+
550+ log::info!("{:?}", capabilities.document_range_formatting_provider);
551+ // check if we're able to format
552+ let _capabilities = match capabilities.document_range_formatting_provider {
553+ Some(lsp::OneOf::Left(true)) => (),
554+ Some(lsp::OneOf::Right(_)) => (),
555+ // None | Some(false)
556+ _ => return Ok(Vec::new()),
557+ };
558+ // TODO: return err::unavailable so we can fall back to tree sitter formatting
559+
560+ let params = lsp::DocumentRangeFormattingParams {
561+ text_document,
562+ range,
563+ options,
564+ // TODO: support these tokens
565+ work_done_progress_params: lsp::WorkDoneProgressParams {
566+ work_done_token: None,
567+ },
568+ };
569+
570+ let response = self
571+ .request::<lsp::request::RangeFormatting>(params)
572+ .await?;
573+
574+ Ok(response.unwrap_or_default())
575+ }
509}
576}
helix-lsp/src/lib.rs+8−1
@@ -28,7 +28,7 @@ pub enum Error {
28
28
29pub mod util {
29pub mod util {
30 use super::*;
30 use super::*;
31− use helix_core::{RopeSlice, State, Transaction};
31+ use helix_core::{Range, RopeSlice, State, Transaction};
32
32
33 pub fn lsp_pos_to_pos(doc: RopeSlice, pos: lsp::Position) -> usize {
33 pub fn lsp_pos_to_pos(doc: RopeSlice, pos: lsp::Position) -> usize {
34 let line = doc.line_to_char(pos.line as usize);
34 let line = doc.line_to_char(pos.line as usize);
@@ -43,6 +43,13 @@ pub mod util {
43 lsp::Position::new(line as u32, col as u32)
43 lsp::Position::new(line as u32, col as u32)
44 }
44 }
45
45
46+ pub fn range_to_lsp_range(doc: RopeSlice, range: Range) -> lsp::Range {
47+ let start = pos_to_lsp_pos(doc, range.from());
48+ let end = pos_to_lsp_pos(doc, range.to());
49+
50+ lsp::Range::new(start, end)
51+ }
52+
46 pub fn generate_transaction_from_edits(
53 pub fn generate_transaction_from_edits(
47 state: &State,
54 state: &State,
48 edits: Vec<lsp::TextEdit>,
55 edits: Vec<lsp::TextEdit>,
helix-term/src/commands.rs+41
@@ -888,6 +888,47 @@ pub fn unindent(cx: &mut Context) {
888 doc.append_changes_to_history();
888 doc.append_changes_to_history();
889}
889}
890
890
891+pub fn format_selections(cx: &mut Context) {
892+ use helix_lsp::lsp;
893+ let doc = cx.doc();
894+
895+ // via lsp if available
896+ // else via tree-sitter indentation calculations
897+
898+ // TODO: blocking here is not ideal
899+
900+ let ranges: Vec<lsp::Range> = doc
901+ .selection()
902+ .ranges()
903+ .iter()
904+ .map(|range| {
905+ helix_lsp::util::range_to_lsp_range(doc.text().slice(..), doc.selection().primary())
906+ })
907+ .collect();
908+
909+ for range in ranges {
910+ let language_server = match doc.language_server.as_ref() {
911+ Some(language_server) => language_server,
912+ None => return,
913+ };
914+
915+ // TODO: handle fails
916+ // TODO: concurrent map
917+ let edits = smol::block_on(language_server.text_document_range_formatting(
918+ doc.identifier(),
919+ range,
920+ lsp::FormattingOptions::default(),
921+ ))
922+ .unwrap_or_default();
923+
924+ let transaction = helix_lsp::util::generate_transaction_from_edits(&doc.state, edits);
925+
926+ doc.apply(&transaction);
927+ }
928+
929+ doc.append_changes_to_history();
930+}
931+
891//
932//
892
933
893pub fn save(cx: &mut Context) {
934pub fn save(cx: &mut Context) {
helix-term/src/keymap.rs+1
@@ -178,6 +178,7 @@ pub fn default() -> Keymaps {
178 vec![key!('p')] => commands::paste,
178 vec![key!('p')] => commands::paste,
179 vec![key!('>')] => commands::indent,
179 vec![key!('>')] => commands::indent,
180 vec![key!('<')] => commands::unindent,
180 vec![key!('<')] => commands::unindent,
181+ vec![key!('=')] => commands::format_selections,
181 vec![key!(':')] => commands::command_mode,
182 vec![key!(':')] => commands::command_mode,
182 vec![Key {
183 vec![Key {
183 code: KeyCode::Esc,
184 code: KeyCode::Esc,