diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 75296423a5933..39e7d2497eb16 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -220,6 +220,7 @@ gpui::actions!( Fold, FoldSelectedRanges, Format, + FormatSelection, GoToDefinition, GoToDefinitionSplit, GoToDeclaration, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1b3bf2f1bd06..55885e05b3479 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9969,6 +9969,19 @@ impl Editor { Some(self.perform_format(project, FormatTrigger::Manual, cx)) } + fn format_selection( + &mut self, + _: &FormatSelection, + cx: &mut ViewContext, + ) -> Option>> { + let project = match &self.project { + Some(project) => project.clone(), + None => return None, + }; + + Some(self.perform_format(project, FormatTrigger::Manual, cx)) + } + fn perform_format( &mut self, project: Model, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 12f030602b989..280f620d3cb17 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -371,6 +371,13 @@ impl EditorElement { cx.propagate(); } }); + register_action(view, cx, |editor, action, cx| { + if let Some(task) = editor.format_selection(action, cx) { + task.detach_and_log_err(cx); + } else { + cx.propagate(); + } + }); register_action(view, cx, Editor::restart_language_server); register_action(view, cx, Editor::cancel_language_server_work); register_action(view, cx, Editor::show_character_palette); diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index ddaced9fa11dc..a0d213e82db71 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,5 +1,6 @@ use std::ops::Range; +use crate::actions::FormatSelection; use crate::{ actions::Format, selections_collection::SelectionsCollection, Copy, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EditorMode, FindAllReferences, GoToDeclaration, @@ -8,6 +9,7 @@ use crate::{ }; use gpui::prelude::FluentBuilder; use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext}; +use text::PointUtf16; use workspace::OpenInTerminal; #[derive(Debug)] @@ -156,6 +158,13 @@ pub fn deploy_context_menu( s.set_pending_anchor_range(anchor..anchor, SelectMode::Character); }); } + let selections = editor + .selections + .all::(cx) + .iter() + .filter(|s| s.start != s.end) + .collect::>() + .is_empty(); let focus = cx.focused(); ui::ContextMenu::build(cx, |menu, _cx| { @@ -169,6 +178,9 @@ pub fn deploy_context_menu( .separator() .action("Rename Symbol", Box::new(Rename)) .action("Format Buffer", Box::new(Format)) + .when(!selections, |builder| { + builder.action("Format Selection", Box::new(FormatSelection)) + }) .action( "Code Actions", Box::new(ToggleCodeActions { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 3661b4b182f89..9e374ee8ece5e 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -33,8 +33,8 @@ use language::{ proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic, DiagnosticEntry, DiagnosticSet, Documentation, File as _, Language, LanguageRegistry, - LanguageServerName, LocalFile, LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16, - TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, + LanguageServerName, LocalFile, LspAdapterDelegate, Patch, PendingLanguageServer, Point, + PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use lsp::{ CompletionContext, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, @@ -64,7 +64,7 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, time::{Duration, Instant}, }; -use text::{Anchor, BufferId, LineEnding}; +use text::{Anchor, BufferId, LineEnding, Selection}; use util::{ debug_panic, defer, maybe, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, }; @@ -807,6 +807,7 @@ impl LspStore { abs_path: &Path, language_server: &Arc, settings: &LanguageSettings, + selections: Vec>, cx: &mut AsyncAppContext, ) -> Result, String)>> { let uri = lsp::Url::from_file_path(abs_path) @@ -817,22 +818,49 @@ impl LspStore { let formatting_provider = capabilities.document_formatting_provider.as_ref(); let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); - let lsp_edits = if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) { - language_server - .request::(lsp::DocumentFormattingParams { - text_document, - options: lsp_command::lsp_formatting_options(settings), - work_done_progress_params: Default::default(), - }) - .await? - } else if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) { - let buffer_start = lsp::Position::new(0, 0); - let buffer_end = buffer.update(cx, |b, _| point_to_lsp(b.max_point_utf16()))?; + let lsp_edits = if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) + { + let ranges = if selections.is_empty() { + let buffer_start = lsp::Position::new(0, 0); + let buffer_end = buffer.update(cx, |b, _| point_to_lsp(b.max_point_utf16()))?; + vec![(buffer_start, buffer_end)] + } else { + selections + .into_iter() + .map(|selection| { + let selection_start = selection.start; + let selection_end = selection.end; + ( + lsp::Position::new(selection_start.row, selection_start.column), + lsp::Position::new(selection_end.row, selection_end.column), + ) + }) + .collect() + }; + + let mut text_edits = vec![]; + for (buffer_start, buffer_end) in ranges { + language_server + .request::(lsp::DocumentRangeFormattingParams { + text_document: text_document.clone(), + range: lsp::Range::new(buffer_start, buffer_end), + options: lsp_command::lsp_formatting_options(settings), + work_done_progress_params: Default::default(), + }) + .await? + .map(|mut edits| text_edits.append(&mut edits)); + } + if text_edits.is_empty() { + None + } else { + Some(text_edits) + } + } else if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) { + println!("formatting all"); language_server - .request::(lsp::DocumentRangeFormattingParams { + .request::(lsp::DocumentFormattingParams { text_document, - range: lsp::Range::new(buffer_start, buffer_end), options: lsp_command::lsp_formatting_options(settings), work_done_progress_params: Default::default(), })