diff --git a/Cargo.lock b/Cargo.lock index c0f6751b895e28bf577d894581a14cadf91362c2..e345736295613bb050ee03282b4057e9c9d2bc61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,6 +404,7 @@ dependencies = [ "language_model", "languages", "log", + "lsp", "markdown", "menu", "multi_buffer", diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 9f715d822474d28874ad017e45305b3b5d02e995..9e61eee18aaf8acc9dc74442832ba1ea9e1c2b79 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -51,6 +51,7 @@ indoc.workspace = true language.workspace = true language_model.workspace = true log.workspace = true +lsp.workspace = true markdown.workspace = true menu.workspace = true multi_buffer.workspace = true diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index f2428c3a2e94cf1ddcb0de291f999dd08f8f2291..9c117e66653e93a74af956b44d572eca43056af5 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -12,8 +12,9 @@ use editor::{ BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, ToDisplayPoint, }, - Anchor, AnchorRangeExt, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, - ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, + Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode, + EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, MultiBuffer, MultiBufferSnapshot, + ToOffset as _, ToPoint, }; use feature_flags::{FeatureFlagAppExt as _, ZedPro}; use fs::Fs; @@ -35,6 +36,7 @@ use language_model::{ }; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; +use project::{CodeAction, ProjectTransaction}; use rope::Rope; use settings::{Settings, SettingsStore}; use smol::future::FutureExt; @@ -49,10 +51,11 @@ use std::{ time::{Duration, Instant}, }; use terminal_view::terminal_panel::TerminalPanel; +use text::{OffsetRangeExt, ToPoint as _}; use theme::ThemeSettings; use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip}; use util::{RangeExt, ResultExt}; -use workspace::{notifications::NotificationId, Toast, Workspace}; +use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace}; pub fn init( fs: Arc, @@ -129,8 +132,10 @@ impl InlineAssistant { } pub fn register_workspace(&mut self, workspace: &View, cx: &mut WindowContext) { - cx.subscribe(workspace, |_, event, cx| { - Self::update_global(cx, |this, cx| this.handle_workspace_event(event, cx)); + cx.subscribe(workspace, |workspace, event, cx| { + Self::update_global(cx, |this, cx| { + this.handle_workspace_event(workspace, event, cx) + }); }) .detach(); @@ -150,19 +155,49 @@ impl InlineAssistant { .detach(); } - fn handle_workspace_event(&mut self, event: &workspace::Event, cx: &mut WindowContext) { - // When the user manually saves an editor, automatically accepts all finished transformations. - if let workspace::Event::UserSavedItem { item, .. } = event { - if let Some(editor) = item.upgrade().and_then(|item| item.act_as::(cx)) { - if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { - for assist_id in editor_assists.assist_ids.clone() { - let assist = &self.assists[&assist_id]; - if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { - self.finish_assist(assist_id, false, cx) + fn handle_workspace_event( + &mut self, + workspace: View, + event: &workspace::Event, + cx: &mut WindowContext, + ) { + match event { + workspace::Event::UserSavedItem { item, .. } => { + // When the user manually saves an editor, automatically accepts all finished transformations. + if let Some(editor) = item.upgrade().and_then(|item| item.act_as::(cx)) { + if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) { + for assist_id in editor_assists.assist_ids.clone() { + let assist = &self.assists[&assist_id]; + if let CodegenStatus::Done = assist.codegen.read(cx).status(cx) { + self.finish_assist(assist_id, false, cx) + } } } } } + workspace::Event::ItemAdded { item } => { + self.register_workspace_item(&workspace, item.as_ref(), cx); + } + _ => (), + } + } + + fn register_workspace_item( + &mut self, + workspace: &View, + item: &dyn ItemHandle, + cx: &mut WindowContext, + ) { + if let Some(editor) = item.act_as::(cx) { + editor.update(cx, |editor, cx| { + editor.push_code_action_provider( + Arc::new(AssistantCodeActionProvider { + editor: cx.view().downgrade(), + workspace: workspace.downgrade(), + }), + cx, + ); + }); } } @@ -332,6 +367,7 @@ impl InlineAssistant { mut range: Range, initial_prompt: String, initial_transaction_id: Option, + focus: bool, workspace: Option>, assistant_panel: Option<&View>, cx: &mut WindowContext, @@ -404,6 +440,11 @@ impl InlineAssistant { assist_group.assist_ids.push(assist_id); editor_assists.assist_ids.push(assist_id); self.assist_groups.insert(assist_group_id, assist_group); + + if focus { + self.focus_assist(assist_id, cx); + } + assist_id } @@ -3289,6 +3330,132 @@ where } } +struct AssistantCodeActionProvider { + editor: WeakView, + workspace: WeakView, +} + +impl CodeActionProvider for AssistantCodeActionProvider { + fn code_actions( + &self, + buffer: &Model, + range: Range, + cx: &mut WindowContext, + ) -> Task>> { + let snapshot = buffer.read(cx).snapshot(); + let mut range = range.to_point(&snapshot); + + // Expand the range to line boundaries. + range.start.column = 0; + range.end.column = snapshot.line_len(range.end.row); + + let mut has_diagnostics = false; + for diagnostic in snapshot.diagnostics_in_range::<_, Point>(range.clone(), false) { + range.start = cmp::min(range.start, diagnostic.range.start); + range.end = cmp::max(range.end, diagnostic.range.end); + has_diagnostics = true; + } + if has_diagnostics { + if let Some(symbols_containing_start) = snapshot.symbols_containing(range.start, None) { + if let Some(symbol) = symbols_containing_start.last() { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); + } + } + + if let Some(symbols_containing_end) = snapshot.symbols_containing(range.end, None) { + if let Some(symbol) = symbols_containing_end.last() { + range.start = cmp::min(range.start, symbol.range.start.to_point(&snapshot)); + range.end = cmp::max(range.end, symbol.range.end.to_point(&snapshot)); + } + } + + Task::ready(Ok(vec![CodeAction { + server_id: language::LanguageServerId(0), + range: snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end), + lsp_action: lsp::CodeAction { + title: "Fix with Assistant".into(), + ..Default::default() + }, + }])) + } else { + Task::ready(Ok(Vec::new())) + } + } + + fn apply_code_action( + &self, + buffer: Model, + action: CodeAction, + excerpt_id: ExcerptId, + _push_to_history: bool, + cx: &mut WindowContext, + ) -> Task> { + let editor = self.editor.clone(); + let workspace = self.workspace.clone(); + cx.spawn(|mut cx| async move { + let editor = editor.upgrade().context("editor was released")?; + let range = editor + .update(&mut cx, |editor, cx| { + editor.buffer().update(cx, |multibuffer, cx| { + let buffer = buffer.read(cx); + let multibuffer_snapshot = multibuffer.read(cx); + + let old_context_range = + multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?; + let mut new_context_range = old_context_range.clone(); + if action + .range + .start + .cmp(&old_context_range.start, buffer) + .is_lt() + { + new_context_range.start = action.range.start; + } + if action.range.end.cmp(&old_context_range.end, buffer).is_gt() { + new_context_range.end = action.range.end; + } + drop(multibuffer_snapshot); + + if new_context_range != old_context_range { + multibuffer.resize_excerpt(excerpt_id, new_context_range, cx); + } + + let multibuffer_snapshot = multibuffer.read(cx); + Some( + multibuffer_snapshot + .anchor_in_excerpt(excerpt_id, action.range.start)? + ..multibuffer_snapshot + .anchor_in_excerpt(excerpt_id, action.range.end)?, + ) + }) + })? + .context("invalid range")?; + let assistant_panel = workspace.update(&mut cx, |workspace, cx| { + workspace + .panel::(cx) + .context("assistant panel was released") + })??; + + cx.update_global(|assistant: &mut InlineAssistant, cx| { + let assist_id = assistant.suggest_assist( + &editor, + range, + "Fix Diagnostics".into(), + None, + true, + Some(workspace), + Some(&assistant_panel), + cx, + ); + assistant.start_assist(assist_id, cx); + })?; + + Ok(ProjectTransaction::default()) + }) + } +} + fn prefixes(text: &str) -> impl Iterator { (0..text.len() - 1).map(|ix| &text[..ix + 1]) } diff --git a/crates/assistant/src/workflow.rs b/crates/assistant/src/workflow.rs index 75c65ed0a78e42b2a4b71ee94652912d9afe57f0..8a770e21aa7caac2883ace80fae8c633f63b492f 100644 --- a/crates/assistant/src/workflow.rs +++ b/crates/assistant/src/workflow.rs @@ -187,6 +187,7 @@ impl WorkflowSuggestion { suggestion_range, initial_prompt, initial_transaction_id, + false, Some(workspace.clone()), Some(assistant_panel), cx, diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index cdcf69cf7e9ace1c5d00b73891100dad73681f59..a81166bb00ceecd46370d8cae230115b98403d4e 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -53,6 +53,7 @@ async fn test_sharing_an_ssh_remote_project( let (project_a, worktree_id) = client_a .build_ssh_project("/code/project1", client_ssh, cx_a) .await; + executor.run_until_parked(); // User A shares the remote project. let active_call_a = cx_a.read(ActiveCall::global); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b1a3d95a0da780d8a8179166354af9cef7d364eb..cbc272d995213cd543ca65a5b4b9576b2a9d7b10 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -68,7 +68,7 @@ use element::LineWithInvisibles; pub use element::{ CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition, }; -use futures::FutureExt; +use futures::{future, FutureExt}; use fuzzy::{StringMatch, StringMatchCandidate}; use git::blame::GitBlame; use git::diff_hunk_to_display; @@ -569,8 +569,8 @@ pub struct Editor { find_all_references_task_sources: Vec, next_completion_id: CompletionId, completion_documentation_pre_resolve_debounce: DebouncedDelay, - available_code_actions: Option<(Location, Arc<[CodeAction]>)>, - code_actions_task: Option>, + available_code_actions: Option<(Location, Arc<[AvailableCodeAction]>)>, + code_actions_task: Option>>, document_highlights_task: Option>, linked_editing_range_task: Option>>, linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges, @@ -590,6 +590,7 @@ pub struct Editor { gutter_hovered: bool, hovered_link_state: Option, inline_completion_provider: Option, + code_action_providers: Vec>, active_inline_completion: Option, // enable_inline_completions is a switch that Vim can use to disable // inline completions based on its mode. @@ -1360,10 +1361,16 @@ impl CompletionsMenu { } } +struct AvailableCodeAction { + excerpt_id: ExcerptId, + action: CodeAction, + provider: Arc, +} + #[derive(Clone)] struct CodeActionContents { tasks: Option>, - actions: Option>, + actions: Option>, } impl CodeActionContents { @@ -1395,9 +1402,11 @@ impl CodeActionContents { .map(|(kind, task)| CodeActionsItem::Task(kind.clone(), task.clone())) }) .chain(self.actions.iter().flat_map(|actions| { - actions - .iter() - .map(|action| CodeActionsItem::CodeAction(action.clone())) + actions.iter().map(|available| CodeActionsItem::CodeAction { + excerpt_id: available.excerpt_id, + action: available.action.clone(), + provider: available.provider.clone(), + }) })) } fn get(&self, index: usize) -> Option { @@ -1410,10 +1419,13 @@ impl CodeActionContents { .cloned() .map(|(kind, task)| CodeActionsItem::Task(kind, task)) } else { - actions - .get(index - tasks.templates.len()) - .cloned() - .map(CodeActionsItem::CodeAction) + actions.get(index - tasks.templates.len()).map(|available| { + CodeActionsItem::CodeAction { + excerpt_id: available.excerpt_id, + action: available.action.clone(), + provider: available.provider.clone(), + } + }) } } (Some(tasks), None) => tasks @@ -1421,7 +1433,15 @@ impl CodeActionContents { .get(index) .cloned() .map(|(kind, task)| CodeActionsItem::Task(kind, task)), - (None, Some(actions)) => actions.get(index).cloned().map(CodeActionsItem::CodeAction), + (None, Some(actions)) => { + actions + .get(index) + .map(|available| CodeActionsItem::CodeAction { + excerpt_id: available.excerpt_id, + action: available.action.clone(), + provider: available.provider.clone(), + }) + } (None, None) => None, } } @@ -1431,7 +1451,11 @@ impl CodeActionContents { #[derive(Clone)] enum CodeActionsItem { Task(TaskSourceKind, ResolvedTask), - CodeAction(CodeAction), + CodeAction { + excerpt_id: ExcerptId, + action: CodeAction, + provider: Arc, + }, } impl CodeActionsItem { @@ -1442,14 +1466,14 @@ impl CodeActionsItem { Some(task) } fn as_code_action(&self) -> Option<&CodeAction> { - let Self::CodeAction(action) = self else { + let Self::CodeAction { action, .. } = self else { return None; }; Some(action) } fn label(&self) -> String { match self { - Self::CodeAction(action) => action.lsp_action.title.clone(), + Self::CodeAction { action, .. } => action.lsp_action.title.clone(), Self::Task(_, task) => task.resolved_label.clone(), } } @@ -1588,7 +1612,9 @@ impl CodeActionsMenu { .enumerate() .max_by_key(|(_, action)| match action { CodeActionsItem::Task(_, task) => task.resolved_label.chars().count(), - CodeActionsItem::CodeAction(action) => action.lsp_action.title.chars().count(), + CodeActionsItem::CodeAction { action, .. } => { + action.lsp_action.title.chars().count() + } }) .map(|(ix, _)| ix), ) @@ -1864,6 +1890,11 @@ impl Editor { None }; + let mut code_action_providers = Vec::new(); + if let Some(project) = project.clone() { + code_action_providers.push(Arc::new(project) as Arc<_>); + } + let mut this = Self { focus_handle, show_cursor_when_unfocused: false, @@ -1915,6 +1946,7 @@ impl Editor { next_completion_id: 0, completion_documentation_pre_resolve_debounce: DebouncedDelay::new(), next_inlay_id: 0, + code_action_providers, available_code_actions: Default::default(), code_actions_task: Default::default(), document_highlights_task: Default::default(), @@ -4553,7 +4585,7 @@ impl Editor { let action = action.clone(); cx.spawn(|editor, mut cx| async move { while let Some(prev_task) = task { - prev_task.await; + prev_task.await.log_err(); task = editor.update(&mut cx, |this, _| this.code_actions_task.take())?; } @@ -4727,17 +4759,16 @@ impl Editor { Some(Task::ready(Ok(()))) }) } - CodeActionsItem::CodeAction(action) => { - let apply_code_actions = workspace - .read(cx) - .project() - .clone() - .update(cx, |project, cx| { - project.apply_code_action(buffer, action, true, cx) - }); + CodeActionsItem::CodeAction { + excerpt_id, + action, + provider, + } => { + let apply_code_action = + provider.apply_code_action(buffer, action, excerpt_id, true, cx); let workspace = workspace.downgrade(); Some(cx.spawn(|editor, cx| async move { - let project_transaction = apply_code_actions.await?; + let project_transaction = apply_code_action.await?; Self::open_project_transaction( &editor, workspace, @@ -4835,8 +4866,16 @@ impl Editor { Ok(()) } + pub fn push_code_action_provider( + &mut self, + provider: Arc, + cx: &mut ViewContext, + ) { + self.code_action_providers.push(provider); + self.refresh_code_actions(cx); + } + fn refresh_code_actions(&mut self, cx: &mut ViewContext) -> Option<()> { - let project = self.project.clone()?; let buffer = self.buffer.read(cx); let newest_selection = self.selections.newest_anchor().clone(); let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?; @@ -4850,13 +4889,30 @@ impl Editor { .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT) .await; - let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| { - project.code_actions(&start_buffer, start..end, cx) - }) { - code_actions.await - } else { - Vec::new() - }; + let (providers, tasks) = this.update(&mut cx, |this, cx| { + let providers = this.code_action_providers.clone(); + let tasks = this + .code_action_providers + .iter() + .map(|provider| provider.code_actions(&start_buffer, start..end, cx)) + .collect::>(); + (providers, tasks) + })?; + + let mut actions = Vec::new(); + for (provider, provider_actions) in + providers.into_iter().zip(future::join_all(tasks).await) + { + if let Some(provider_actions) = provider_actions.log_err() { + actions.extend(provider_actions.into_iter().map(|action| { + AvailableCodeAction { + excerpt_id: newest_selection.start.excerpt_id, + action, + provider: provider.clone(), + } + })); + } + } this.update(&mut cx, |this, cx| { this.available_code_actions = if actions.is_empty() { @@ -4872,7 +4928,6 @@ impl Editor { }; cx.notify(); }) - .log_err(); })); None } @@ -9685,7 +9740,7 @@ impl Editor { }) .context("location tasks preparation")?; - let locations = futures::future::join_all(location_tasks) + let locations = future::join_all(location_tasks) .await .into_iter() .filter_map(|location| location.transpose()) @@ -12574,6 +12629,48 @@ pub trait CompletionProvider { } } +pub trait CodeActionProvider { + fn code_actions( + &self, + buffer: &Model, + range: Range, + cx: &mut WindowContext, + ) -> Task>>; + + fn apply_code_action( + &self, + buffer_handle: Model, + action: CodeAction, + excerpt_id: ExcerptId, + push_to_history: bool, + cx: &mut WindowContext, + ) -> Task>; +} + +impl CodeActionProvider for Model { + fn code_actions( + &self, + buffer: &Model, + range: Range, + cx: &mut WindowContext, + ) -> Task>> { + self.update(cx, |project, cx| project.code_actions(buffer, range, cx)) + } + + fn apply_code_action( + &self, + buffer_handle: Model, + action: CodeAction, + _excerpt_id: ExcerptId, + push_to_history: bool, + cx: &mut WindowContext, + ) -> Task> { + self.update(cx, |project, cx| { + project.apply_code_action(buffer_handle, action, push_to_history, cx) + }) + } +} + fn snippet_completions( project: &Project, buffer: &Model, diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index b909e63271c069fcaaf2d4441e7dee6014bb7504..3035892d7a17f23ee8db80ef4fdb3ad593e755ae 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -407,7 +407,11 @@ impl BackgroundExecutor { /// How many CPUs are available to the dispatcher. pub fn num_cpus(&self) -> usize { - num_cpus::get() + #[cfg(any(test, feature = "test-support"))] + return 4; + + #[cfg(not(any(test, feature = "test-support")))] + return num_cpus::get(); } /// Whether we're on the main thread. diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index d406f9bfaf6ac2478052d6d72a327b5354d506b4..0df196bb9829dc29d9b7231ce9cc6c355ec7c9c2 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1810,6 +1810,69 @@ impl MultiBuffer { self.as_singleton().unwrap().read(cx).is_parsing() } + pub fn resize_excerpt( + &mut self, + id: ExcerptId, + range: Range, + cx: &mut ModelContext, + ) { + self.sync(cx); + + let snapshot = self.snapshot(cx); + let locator = snapshot.excerpt_locator_for_id(id); + let mut new_excerpts = SumTree::default(); + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&()); + let mut edits = Vec::>::new(); + + let prefix = cursor.slice(&Some(locator), Bias::Left, &()); + new_excerpts.append(prefix, &()); + + let mut excerpt = cursor.item().unwrap().clone(); + let old_text_len = excerpt.text_summary.len; + + excerpt.range.context.start = range.start; + excerpt.range.context.end = range.end; + excerpt.max_buffer_row = range.end.to_point(&excerpt.buffer).row; + + excerpt.text_summary = excerpt + .buffer + .text_summary_for_range(excerpt.range.context.clone()); + + let new_start_offset = new_excerpts.summary().text.len; + let old_start_offset = cursor.start().1; + let edit = Edit { + old: old_start_offset..old_start_offset + old_text_len, + new: new_start_offset..new_start_offset + excerpt.text_summary.len, + }; + + if let Some(last_edit) = edits.last_mut() { + if last_edit.old.end == edit.old.start { + last_edit.old.end = edit.old.end; + last_edit.new.end = edit.new.end; + } else { + edits.push(edit); + } + } else { + edits.push(edit); + } + + new_excerpts.push(excerpt, &()); + + cursor.next(&()); + + new_excerpts.append(cursor.suffix(&()), &()); + + drop(cursor); + self.snapshot.borrow_mut().excerpts = new_excerpts; + + self.subscriptions.publish_mut(edits); + cx.emit(Event::Edited { + singleton_buffer_edited: false, + }); + cx.emit(Event::ExcerptsExpanded { ids: vec![id] }); + cx.notify(); + } + pub fn expand_excerpts( &mut self, ids: impl IntoIterator, @@ -3139,6 +3202,10 @@ impl MultiBufferSnapshot { None } + pub fn context_range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option> { + Some(self.excerpt(excerpt_id)?.range.context.clone()) + } + pub fn can_resolve(&self, anchor: &Anchor) -> bool { if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { true diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 4506fcc6feb430813e3eec951fd815ef0f7f3e60..b2920bc791c47de23302db75dad2011b5f975beb 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -1431,7 +1431,7 @@ impl LspStore { buffer_handle: &Model, range: Range, cx: &mut ModelContext, - ) -> Task> { + ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { let request_task = upstream_client.request(proto::MultiLspQuery { buffer_id: buffer_handle.read(cx).remote_id().into(), @@ -1451,14 +1451,11 @@ impl LspStore { let buffer = buffer_handle.clone(); cx.spawn(|weak_project, cx| async move { let Some(project) = weak_project.upgrade() else { - return Vec::new(); + return Ok(Vec::new()); }; - join_all( - request_task - .await - .log_err() - .map(|response| response.responses) - .unwrap_or_default() + let responses = request_task.await?.responses; + let actions = join_all( + responses .into_iter() .filter_map(|lsp_response| match lsp_response.response? { proto::lsp_response::Response::GetCodeActionsResponse(response) => { @@ -1470,7 +1467,7 @@ impl LspStore { } }) .map(|code_actions_response| { - let response = GetCodeActions { + GetCodeActions { range: range.clone(), kinds: None, } @@ -1479,14 +1476,17 @@ impl LspStore { project.clone(), buffer.clone(), cx.clone(), - ); - async move { response.await.log_err().unwrap_or_default() } + ) }), ) - .await - .into_iter() - .flatten() - .collect() + .await; + + Ok(actions + .into_iter() + .collect::>>>()? + .into_iter() + .flatten() + .collect()) }) } else { let all_actions_task = self.request_multiple_lsp_locally( @@ -1498,7 +1498,9 @@ impl LspStore { }, cx, ); - cx.spawn(|_, _| async move { all_actions_task.await.into_iter().flatten().collect() }) + cx.spawn( + |_, _| async move { Ok(all_actions_task.await.into_iter().flatten().collect()) }, + ) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0c54a16187a4a6151bbf00ef343695057ced1f72..b1347c6d063f25df4a77e955b86bb44671da5ed9 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3247,7 +3247,7 @@ impl Project { buffer_handle: &Model, range: Range, cx: &mut ModelContext, - ) -> Task> { + ) -> Task>> { let buffer = buffer_handle.read(cx); let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); self.lsp_store.update(cx, |lsp_store, cx| { diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index d0d67f0cda4a40835175adb7788a0737bf1e3dc6..a7d2e6766c233013babdfbbef47e3b2873e53b44 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2708,7 +2708,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { .next() .await; - let action = actions.await[0].clone(); + let action = actions.await.unwrap()[0].clone(); let apply = project.update(cx, |project, cx| { project.apply_code_action(buffer.clone(), action, true, cx) }); @@ -5046,6 +5046,7 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) { vec!["TailwindServer code action", "TypeScriptServer code action"], code_actions_task .await + .unwrap() .into_iter() .map(|code_action| code_action.lsp_action.title) .sorted() diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index fac3c55bf45506e407a83028ad050e5dc5de507d..d5b719a657628e13d5fe95953d5651a70a8c9064 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2745,7 +2745,7 @@ pub mod tests { search_view .results_editor .update(cx, |editor, cx| editor.display_text(cx)), - "\n\n\nconst ONE: usize = 1;\n\n\n\n\nconst TWO: usize = one::ONE + one::ONE;\n", + "\n\n\nconst TWO: usize = one::ONE + one::ONE;\n\n\n\n\nconst ONE: usize = 1;\n", "New search in directory should have a filter that matches a certain directory" ); }) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1fbeab38a2e8b4363066d2f359a56c8aeaadd58f..92bfc8c5c56d4da7888eafe3426097c5c694a676 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -675,7 +675,9 @@ impl DelayedDebouncedEditAction { pub enum Event { PaneAdded(View), PaneRemoved, - ItemAdded, + ItemAdded { + item: Box, + }, ItemRemoved, ActiveItemChanged, UserSavedItem { @@ -2984,7 +2986,9 @@ impl Workspace { match event { pane::Event::AddItem { item } => { item.added_to_pane(self, pane, cx); - cx.emit(Event::ItemAdded); + cx.emit(Event::ItemAdded { + item: item.boxed_clone(), + }); } pane::Event::Split(direction) => { self.split_and_clone(pane, *direction, cx);