diff --git a/.github/workflows/issue_response.yml b/.github/workflows/issue_response.yml index f45ee7c4cf4032..d18b66fe47fd78 100644 --- a/.github/workflows/issue_response.yml +++ b/.github/workflows/issue_response.yml @@ -29,5 +29,5 @@ jobs: - name: Run Issue Response run: pnpm run --dir script/issue_response start env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_RESPONSE_GITHUB_TOKEN: ${{ secrets.ISSUE_RESPONSE_GITHUB_TOKEN }} SLACK_ISSUE_RESPONSE_WEBHOOK_URL: ${{ secrets.SLACK_ISSUE_RESPONSE_WEBHOOK_URL }} diff --git a/Cargo.lock b/Cargo.lock index 1c5048b9708ad3..9dc428614b1563 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7066,6 +7066,7 @@ dependencies = [ "serde_json", "smol", "strum", + "thiserror 1.0.69", "ui", "util", ] @@ -7077,6 +7078,7 @@ dependencies = [ "feature_flags", "gpui", "language_model", + "log", "picker", "proto", "ui", @@ -7765,7 +7767,6 @@ dependencies = [ "anyhow", "assets", "env_logger 0.11.6", - "futures 0.3.31", "gpui", "language", "languages", @@ -13601,6 +13602,7 @@ dependencies = [ "serde_repr", "settings", "strum", + "thiserror 1.0.69", "util", "uuid", ] diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 75090203be5b3d..6e9a2dc406845e 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -767,13 +767,7 @@ } }, { - "context": "FileFinder", - "bindings": { - "ctrl": "file_finder::ToggleMenu" - } - }, - { - "context": "FileFinder && !menu_open", + "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", "bindings": { "ctrl-shift-p": "file_finder::SelectPrev", "ctrl-j": "pane::SplitDown", @@ -782,15 +776,6 @@ "ctrl-l": "pane::SplitRight" } }, - { - "context": "FileFinder && menu_open", - "bindings": { - "j": "pane::SplitDown", - "k": "pane::SplitUp", - "h": "pane::SplitLeft", - "l": "pane::SplitRight" - } - }, { "context": "TabSwitcher", "bindings": { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index bdfa44f1fe7c3b..b5df32657aadd3 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -793,14 +793,7 @@ } }, { - "context": "FileFinder", - "use_key_equivalents": true, - "bindings": { - "cmd": "file_finder::ToggleMenu" - } - }, - { - "context": "FileFinder && !menu_open", + "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", "use_key_equivalents": true, "bindings": { "cmd-shift-p": "file_finder::SelectPrev", @@ -810,16 +803,6 @@ "cmd-l": "pane::SplitRight" } }, - { - "context": "FileFinder && menu_open", - "use_key_equivalents": true, - "bindings": { - "j": "pane::SplitDown", - "k": "pane::SplitUp", - "h": "pane::SplitLeft", - "l": "pane::SplitRight" - } - }, { "context": "TabSwitcher", "use_key_equivalents": true, diff --git a/assets/themes/gruvbox/gruvbox.json b/assets/themes/gruvbox/gruvbox.json index fb6bef7dc282dc..51085b61cab592 100644 --- a/assets/themes/gruvbox/gruvbox.json +++ b/assets/themes/gruvbox/gruvbox.json @@ -105,16 +105,6 @@ "terminal.ansi.bright_white": "#fbf1c7ff", "terminal.ansi.dim_white": "#b0a189ff", "link_text.hover": "#83a598ff", - "version_control.added": "#b7bb26ff", - "version_control.added_background": "#b7bb2614", - "version_control.deleted": "#fb4a35ff", - "version_control.deleted_background": "#fb4a3514", - "version_control.modified": "#f9bd2fff", - "version_control.modified_background": "#f9bd2f14", - "version_control.renamed": "#83a598ff", - "version_control.conflict": "#f9bd2fff", - "version_control.conflict_background": "#f9bd2f14", - "version_control.ignored": "#998b78ff", "conflict": "#f9bd2fff", "conflict.background": "#572e10ff", "conflict.border": "#754916ff", @@ -500,16 +490,6 @@ "terminal.ansi.bright_white": "#fbf1c7ff", "terminal.ansi.dim_white": "#b0a189ff", "link_text.hover": "#83a598ff", - "version_control.added": "#b7bb26ff", - "version_control.added_background": "#b7bb2614", - "version_control.deleted": "#fb4a35ff", - "version_control.deleted_background": "#fb4a3514", - "version_control.modified": "#f9bd2fff", - "version_control.modified_background": "#f9bd2f14", - "version_control.renamed": "#83a598ff", - "version_control.conflict": "#f9bd2fff", - "version_control.conflict_background": "#f9bd2f14", - "version_control.ignored": "#998b78ff", "conflict": "#f9bd2fff", "conflict.background": "#572e10ff", "conflict.border": "#754916ff", @@ -895,16 +875,6 @@ "terminal.ansi.bright_white": "#fbf1c7ff", "terminal.ansi.dim_white": "#b0a189ff", "link_text.hover": "#83a598ff", - "version_control.added": "#b7bb26ff", - "version_control.added_background": "#b7bb2614", - "version_control.deleted": "#fb4a35ff", - "version_control.deleted_background": "#fb4a3514", - "version_control.modified": "#f9bd2fff", - "version_control.modified_background": "#f9bd2f14", - "version_control.renamed": "#83a598ff", - "version_control.conflict": "#f9bd2fff", - "version_control.conflict_background": "#f9bd2f14", - "version_control.ignored": "#998b78ff", "conflict": "#f9bd2fff", "conflict.background": "#572e10ff", "conflict.border": "#754916ff", @@ -1290,16 +1260,6 @@ "terminal.ansi.bright_white": "#282828ff", "terminal.ansi.dim_white": "#73675eff", "link_text.hover": "#0b6678ff", - "version_control.added": "#79740eff", - "version_control.added_background": "#79740e14", - "version_control.deleted": "#9d0006ff", - "version_control.deleted_background": "#9d000614", - "version_control.modified": "#b57614ff", - "version_control.modified_background": "#b5761414", - "version_control.renamed": "#076678ff", - "version_control.conflict": "#b57614ff", - "version_control.conflict_background": "#b5761414", - "version_control.ignored": "#928374ff", "conflict": "#b57615ff", "conflict.background": "#f5e2d0ff", "conflict.border": "#ebccabff", @@ -1685,16 +1645,6 @@ "terminal.ansi.bright_white": "#282828ff", "terminal.ansi.dim_white": "#73675eff", "link_text.hover": "#0b6678ff", - "version_control.added": "#79740eff", - "version_control.added_background": "#79740e14", - "version_control.deleted": "#9d0006ff", - "version_control.deleted_background": "#9d000614", - "version_control.modified": "#b57614ff", - "version_control.modified_background": "#b5761414", - "version_control.renamed": "#076678ff", - "version_control.conflict": "#b57614ff", - "version_control.conflict_background": "#b5761414", - "version_control.ignored": "#928374ff", "conflict": "#b57615ff", "conflict.background": "#f5e2d0ff", "conflict.border": "#ebccabff", @@ -2080,16 +2030,6 @@ "terminal.ansi.bright_white": "#282828ff", "terminal.ansi.dim_white": "#73675eff", "link_text.hover": "#0b6678ff", - "version_control.added": "#79740eff", - "version_control.added_background": "#79740e14", - "version_control.deleted": "#9d0006ff", - "version_control.deleted_background": "#9d000614", - "version_control.modified": "#b57614ff", - "version_control.modified_background": "#b5761414", - "version_control.renamed": "#076678ff", - "version_control.conflict": "#b57614ff", - "version_control.conflict_background": "#b5761414", - "version_control.ignored": "#928374ff", "conflict": "#b57615ff", "conflict.background": "#f5e2d0ff", "conflict.border": "#ebccabff", diff --git a/assets/themes/one/one.json b/assets/themes/one/one.json index 1cd031c84b5f9d..f2ee8fdde99bdf 100644 --- a/assets/themes/one/one.json +++ b/assets/themes/one/one.json @@ -96,16 +96,6 @@ "terminal.ansi.bright_white": "#dce0e5ff", "terminal.ansi.dim_white": "#575d65ff", "link_text.hover": "#74ade8ff", - "version_control.added": "#a1c181ff", - "version_control.added_background": "#a1c18114", - "version_control.deleted": "#d07277ff", - "version_control.deleted_background": "#d0727714", - "version_control.modified": "#dec184ff", - "version_control.modified_background": "#dec18414", - "version_control.renamed": "#74ade8ff", - "version_control.conflict": "#dec184ff", - "version_control.conflict_background": "#dec18414", - "version_control.ignored": "#878a98ff", "conflict": "#dec184ff", "conflict.background": "#dec1841a", "conflict.border": "#5d4c2fff", diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 585f809e25187c..ecf2e2c4216e85 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -20,7 +20,9 @@ use gpui::{ Subscription, Task, UpdateGlobal, WeakEntity, }; use language::LanguageRegistry; -use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID}; +use language_model::{ + AuthenticateError, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID, +}; use project::Project; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary}; use search::{buffer_search::DivRegistrar, BufferSearchBar}; @@ -1156,7 +1158,10 @@ impl AssistantPanel { .map_or(false, |provider| provider.is_authenticated(cx)) } - fn authenticate(&mut self, cx: &mut Context) -> Option>> { + fn authenticate( + &mut self, + cx: &mut Context, + ) -> Option>> { LanguageModelRegistry::read_global(cx) .active_provider() .map_or(None, |provider| Some(provider.authenticate(cx))) diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index d58e13ac2351e1..4d018b0910a172 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1610,6 +1610,7 @@ impl Render for PromptEditor { cx, ) }, + gpui::Corner::TopRight, )) .map(|el| { let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else { @@ -3012,7 +3013,7 @@ impl CodegenAlternative { let executor = cx.background_executor().clone(); let message_id = message_id.clone(); let line_based_stream_diff: Task> = - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut response_latency = None; let request_start = Instant::now(); let diff = async { @@ -3326,8 +3327,7 @@ impl CodegenAlternative { cx.spawn(|codegen, mut cx| async move { let (deleted_row_ranges, inserted_row_ranges) = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let old_text = old_snapshot .text_for_range( Point::new(old_range.start.row, 0) diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index ca605ed613765e..7091b50cad8bb7 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -662,6 +662,7 @@ impl Render for PromptEditor { cx, ) }, + gpui::Corner::TopRight, )) .children( if let CodegenStatus::Error(error) = &self.codegen.read(cx).status { @@ -1149,7 +1150,7 @@ impl Codegen { let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1); - let task = cx.background_executor().spawn({ + let task = cx.background_spawn({ let message_id = message_id.clone(); let executor = cx.background_executor().clone(); async move { diff --git a/crates/assistant2/src/active_thread.rs b/crates/assistant2/src/active_thread.rs index f479f755a1e770..bf1a31158ccd90 100644 --- a/crates/assistant2/src/active_thread.rs +++ b/crates/assistant2/src/active_thread.rs @@ -179,7 +179,7 @@ impl ActiveThread { let markdown = cx.new(|cx| { Markdown::new( - text, + text.into(), markdown_style, Some(self.language_registry.clone()), None, diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index 0d1303ea90d51f..30127a59d5826b 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -66,6 +66,7 @@ pub fn init( cx: &mut App, ) { AssistantSettings::register(cx); + thread_store::init(cx); assistant_panel::init(cx); inline_assistant::init( diff --git a/crates/assistant2/src/assistant_model_selector.rs b/crates/assistant2/src/assistant_model_selector.rs index 0308757e59298d..2769a9e5ca1eb0 100644 --- a/crates/assistant2/src/assistant_model_selector.rs +++ b/crates/assistant2/src/assistant_model_selector.rs @@ -61,13 +61,9 @@ impl Render for AssistantModelSelector { h_flex() .gap_0p5() .child( - div().max_w_32().child( - Label::new(model_name) - .size(LabelSize::Small) - .color(Color::Muted) - .text_ellipsis() - .into_any_element(), - ), + Label::new(model_name) + .size(LabelSize::Small) + .color(Color::Muted), ) .child( Icon::new(IconName::ChevronDown) @@ -84,6 +80,7 @@ impl Render for AssistantModelSelector { cx, ) }, + gpui::Corner::BottomRight, ) .with_handle(self.menu_handle.clone()) } diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index d8788d187e5753..f20436146e3830 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use anyhow::{anyhow, Result}; use assistant_context_editor::{ - make_lsp_adapter_delegate, AssistantPanelDelegate, ConfigurationError, ContextEditor, - ContextHistory, SlashCommandCompletionProvider, + make_lsp_adapter_delegate, render_remaining_tokens, AssistantPanelDelegate, ConfigurationError, + ContextEditor, ContextHistory, SlashCommandCompletionProvider, }; use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_slash_command::SlashCommandWorkingSet; @@ -323,6 +323,9 @@ impl AssistantPanel { } fn open_history(&mut self, window: &mut Window, cx: &mut Context) { + self.thread_store + .update(cx, |thread_store, cx| thread_store.reload(cx)) + .detach_and_log_err(cx); self.active_view = ActiveView::History; self.history.focus_handle(cx).focus(window); cx.notify(); @@ -478,6 +481,10 @@ impl AssistantPanel { .update(cx, |this, cx| this.delete_thread(thread_id, cx)) .detach_and_log_err(cx); } + + pub(crate) fn active_context_editor(&self) -> Option> { + self.context_editor.clone() + } } impl Focusable for AssistantPanel { @@ -635,20 +642,33 @@ impl AssistantPanel { .border_color(cx.theme().colors().border) .child( h_flex() - .child(Label::new(title)) - .when(sub_title.is_some(), |this| { - this.child( - h_flex() - .pl_1p5() - .gap_1p5() - .child( - Label::new("/") - .size(LabelSize::Small) - .color(Color::Disabled) - .alpha(0.5), + .w_full() + .gap_1() + .justify_between() + .child( + h_flex() + .child(Label::new(title)) + .when(sub_title.is_some(), |this| { + this.child( + h_flex() + .pl_1p5() + .gap_1p5() + .child( + Label::new("/") + .size(LabelSize::Small) + .color(Color::Disabled) + .alpha(0.5), + ) + .child(Label::new(sub_title.unwrap())), ) - .child(Label::new(sub_title.unwrap())), - ) + }), + ) + .children(if matches!(self.active_view, ActiveView::PromptEditor) { + self.context_editor + .as_ref() + .and_then(|editor| render_remaining_tokens(editor, cx)) + } else { + None }), ) .child( diff --git a/crates/assistant2/src/buffer_codegen.rs b/crates/assistant2/src/buffer_codegen.rs index 6c3e67f8de8697..9c36cc69dcb829 100644 --- a/crates/assistant2/src/buffer_codegen.rs +++ b/crates/assistant2/src/buffer_codegen.rs @@ -493,7 +493,7 @@ impl CodegenAlternative { let executor = cx.background_executor().clone(); let message_id = message_id.clone(); let line_based_stream_diff: Task> = - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut response_latency = None; let request_start = Instant::now(); let diff = async { @@ -807,8 +807,7 @@ impl CodegenAlternative { cx.spawn(|codegen, mut cx| async move { let (deleted_row_ranges, inserted_row_ranges) = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let old_text = old_snapshot .text_for_range( Point::new(old_range.start.row, 0) diff --git a/crates/assistant2/src/context_picker/fetch_context_picker.rs b/crates/assistant2/src/context_picker/fetch_context_picker.rs index c16beeea7a009d..d8acfe0918342e 100644 --- a/crates/assistant2/src/context_picker/fetch_context_picker.rs +++ b/crates/assistant2/src/context_picker/fetch_context_picker.rs @@ -208,8 +208,7 @@ impl PickerDelegate for FetchContextPickerDelegate { let confirm_behavior = self.confirm_behavior; cx.spawn_in(window, |this, mut cx| async move { let text = cx - .background_executor() - .spawn(Self::build_message(http_client, url.clone())) + .background_spawn(Self::build_message(http_client, url.clone())) .await?; this.update_in(&mut cx, |this, window, cx| { diff --git a/crates/assistant2/src/context_picker/file_context_picker.rs b/crates/assistant2/src/context_picker/file_context_picker.rs index 757249dbea59c1..cab317a1ea14d7 100644 --- a/crates/assistant2/src/context_picker/file_context_picker.rs +++ b/crates/assistant2/src/context_picker/file_context_picker.rs @@ -288,8 +288,11 @@ impl PickerDelegate for FileContextPickerDelegate { editor.insert("\n", window, cx); // Needed to end the fold + let file_icon = FileIcons::get_icon(&Path::new(&full_path), cx) + .unwrap_or_else(|| SharedString::new("")); + let placeholder = FoldPlaceholder { - render: render_fold_icon_button(IconName::File, file_name.into()), + render: render_fold_icon_button(file_icon, file_name.into()), ..Default::default() }; @@ -459,15 +462,27 @@ pub fn render_file_context_entry( } fn render_fold_icon_button( - icon: IconName, + icon: SharedString, label: SharedString, ) -> Arc, &mut Window, &mut App) -> AnyElement> { Arc::new(move |fold_id, _fold_range, _window, _cx| { ButtonLike::new(fold_id) .style(ButtonStyle::Filled) .layer(ElevationIndex::ElevatedSurface) - .child(Icon::new(icon)) - .child(Label::new(label.clone()).single_line()) + .child( + h_flex() + .gap_1() + .child( + Icon::from_path(icon.clone()) + .size(IconSize::Small) + .color(Color::Muted), + ) + .child( + Label::new(label.clone()) + .size(LabelSize::Small) + .single_line(), + ), + ) .into_any_element() }) } diff --git a/crates/assistant2/src/context_picker/thread_context_picker.rs b/crates/assistant2/src/context_picker/thread_context_picker.rs index e0dd0cef088c93..2b42543ba0ec92 100644 --- a/crates/assistant2/src/context_picker/thread_context_picker.rs +++ b/crates/assistant2/src/context_picker/thread_context_picker.rs @@ -123,7 +123,7 @@ impl PickerDelegate for ThreadContextPickerDelegate { }; let executor = cx.background_executor().clone(); - let search_task = cx.background_executor().spawn(async move { + let search_task = cx.background_spawn(async move { if query.is_empty() { threads } else { diff --git a/crates/assistant2/src/context_store.rs b/crates/assistant2/src/context_store.rs index e979f5e24dbbbd..f527f5a7d30017 100644 --- a/crates/assistant2/src/context_store.rs +++ b/crates/assistant2/src/context_store.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::{anyhow, bail, Result}; use collections::{BTreeMap, HashMap, HashSet}; use futures::{self, future, Future, FutureExt}; -use gpui::{App, AsyncApp, Context, Entity, SharedString, Task, WeakEntity}; +use gpui::{App, AppContext as _, AsyncApp, Context, Entity, SharedString, Task, WeakEntity}; use language::Buffer; use project::{ProjectPath, Worktree}; use rope::Rope; @@ -456,9 +456,7 @@ fn collect_buffer_info_and_text( }; // Important to collect version at the same time as content so that staleness logic is correct. let content = buffer.as_rope().clone(); - let text_task = cx - .background_executor() - .spawn(async move { to_fenced_codeblock(&path, content) }); + let text_task = cx.background_spawn(async move { to_fenced_codeblock(&path, content) }); (buffer_info, text_task) } diff --git a/crates/assistant2/src/inline_assistant.rs b/crates/assistant2/src/inline_assistant.rs index 1f2189df82d1f2..6d76526d0698fd 100644 --- a/crates/assistant2/src/inline_assistant.rs +++ b/crates/assistant2/src/inline_assistant.rs @@ -228,8 +228,12 @@ impl InlineAssistant { return; } - let Some(inline_assist_target) = Self::resolve_inline_assist_target(workspace, window, cx) - else { + let Some(inline_assist_target) = Self::resolve_inline_assist_target( + workspace, + workspace.panel::(cx), + window, + cx, + ) else { return; }; @@ -1383,6 +1387,7 @@ impl InlineAssistant { fn resolve_inline_assist_target( workspace: &mut Workspace, + assistant_panel: Option>, window: &mut Window, cx: &mut App, ) -> Option { @@ -1402,7 +1407,20 @@ impl InlineAssistant { } } - if let Some(workspace_editor) = workspace + let context_editor = assistant_panel + .and_then(|panel| panel.read(cx).active_context_editor()) + .and_then(|editor| { + let editor = &editor.read(cx).editor().clone(); + if editor.read(cx).is_focused(window) { + Some(editor.clone()) + } else { + None + } + }); + + if let Some(context_editor) = context_editor { + Some(InlineAssistTarget::Editor(context_editor)) + } else if let Some(workspace_editor) = workspace .active_item(cx) .and_then(|item| item.act_as::(cx)) { diff --git a/crates/assistant2/src/terminal_codegen.rs b/crates/assistant2/src/terminal_codegen.rs index 0bd0bfb0411c79..c9b6a541080449 100644 --- a/crates/assistant2/src/terminal_codegen.rs +++ b/crates/assistant2/src/terminal_codegen.rs @@ -1,7 +1,7 @@ use crate::inline_prompt_editor::CodegenStatus; use client::telemetry::Telemetry; use futures::{channel::mpsc, SinkExt, StreamExt}; -use gpui::{App, Context, Entity, EventEmitter, Task}; +use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task}; use language_model::{LanguageModelRegistry, LanguageModelRequest}; use language_models::report_assistant_event; use std::{sync::Arc, time::Instant}; @@ -53,7 +53,7 @@ impl TerminalCodegen { let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1); - let task = cx.background_executor().spawn({ + let task = cx.background_spawn({ let message_id = message_id.clone(); let executor = cx.background_executor().clone(); async move { diff --git a/crates/assistant2/src/thread_store.rs b/crates/assistant2/src/thread_store.rs index 3e7ee82da91713..58caad37b6ac47 100644 --- a/crates/assistant2/src/thread_store.rs +++ b/crates/assistant2/src/thread_store.rs @@ -9,7 +9,9 @@ use context_server::manager::ContextServerManager; use context_server::{ContextServerFactoryRegistry, ContextServerTool}; use futures::future::{self, BoxFuture, Shared}; use futures::FutureExt as _; -use gpui::{prelude::*, App, BackgroundExecutor, Context, Entity, SharedString, Task}; +use gpui::{ + prelude::*, App, BackgroundExecutor, Context, Entity, Global, ReadGlobal, SharedString, Task, +}; use heed::types::SerdeBincode; use heed::Database; use language_model::Role; @@ -19,6 +21,10 @@ use util::ResultExt as _; use crate::thread::{MessageId, Thread, ThreadId}; +pub fn init(cx: &mut App) { + ThreadsDatabase::init(cx); +} + pub struct ThreadStore { #[allow(unused)] project: Entity, @@ -26,7 +32,6 @@ pub struct ThreadStore { context_server_manager: Entity, context_server_tool_ids: HashMap, Vec>, threads: Vec, - database_future: Shared, Arc>>>, } impl ThreadStore { @@ -41,24 +46,12 @@ impl ThreadStore { ContextServerManager::new(context_server_factory_registry, project.clone(), cx) }); - let executor = cx.background_executor().clone(); - let database_future = executor - .spawn({ - let executor = executor.clone(); - let database_path = paths::support_dir().join("threads/threads-db.0.mdb"); - async move { ThreadsDatabase::new(database_path, executor) } - }) - .then(|result| future::ready(result.map(Arc::new).map_err(Arc::new))) - .boxed() - .shared(); - let this = Self { project, tools, context_server_manager, context_server_tool_ids: HashMap::default(), threads: Vec::new(), - database_future, }; this.register_context_server_handlers(cx); this.reload(cx).detach_and_log_err(cx); @@ -94,7 +87,7 @@ impl ThreadStore { cx: &mut Context, ) -> Task>> { let id = id.clone(); - let database_future = self.database_future.clone(); + let database_future = ThreadsDatabase::global_future(cx); cx.spawn(|this, mut cx| async move { let database = database_future.await.map_err(|err| anyhow!(err))?; let thread = database @@ -127,7 +120,7 @@ impl ThreadStore { (id, thread) }); - let database_future = self.database_future.clone(); + let database_future = ThreadsDatabase::global_future(cx); cx.spawn(|this, mut cx| async move { let database = database_future.await.map_err(|err| anyhow!(err))?; database.save_thread(metadata, thread).await?; @@ -138,7 +131,7 @@ impl ThreadStore { pub fn delete_thread(&mut self, id: &ThreadId, cx: &mut Context) -> Task> { let id = id.clone(); - let database_future = self.database_future.clone(); + let database_future = ThreadsDatabase::global_future(cx); cx.spawn(|this, mut cx| async move { let database = database_future.await.map_err(|err| anyhow!(err))?; database.delete_thread(id.clone()).await?; @@ -149,8 +142,8 @@ impl ThreadStore { }) } - fn reload(&self, cx: &mut Context) -> Task> { - let database_future = self.database_future.clone(); + pub fn reload(&self, cx: &mut Context) -> Task> { + let database_future = ThreadsDatabase::global_future(cx); cx.spawn(|this, mut cx| async move { let threads = database_future .await @@ -253,13 +246,40 @@ pub struct SavedMessage { pub text: String, } -struct ThreadsDatabase { +struct GlobalThreadsDatabase( + Shared, Arc>>>, +); + +impl Global for GlobalThreadsDatabase {} + +pub(crate) struct ThreadsDatabase { executor: BackgroundExecutor, env: heed::Env, threads: Database, SerdeBincode>, } impl ThreadsDatabase { + fn global_future( + cx: &mut App, + ) -> Shared, Arc>>> { + GlobalThreadsDatabase::global(cx).0.clone() + } + + fn init(cx: &mut App) { + let executor = cx.background_executor().clone(); + let database_future = executor + .spawn({ + let executor = executor.clone(); + let database_path = paths::support_dir().join("threads/threads-db.0.mdb"); + async move { ThreadsDatabase::new(database_path, executor) } + }) + .then(|result| future::ready(result.map(Arc::new).map_err(Arc::new))) + .boxed() + .shared(); + + cx.set_global(GlobalThreadsDatabase(database_future)); + } + pub fn new(path: PathBuf, executor: BackgroundExecutor) -> Result { std::fs::create_dir_all(&path)?; diff --git a/crates/assistant_context_editor/src/context.rs b/crates/assistant_context_editor/src/context.rs index 10d6f220088cfa..e17c57a1f0ff7e 100644 --- a/crates/assistant_context_editor/src/context.rs +++ b/crates/assistant_context_editor/src/context.rs @@ -849,7 +849,7 @@ impl AssistantContext { .collect::>(); context_ops.extend(self.pending_ops.iter().cloned()); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let buffer_ops = buffer_ops.await; context_ops.sort_unstable_by_key(|op| op.timestamp()); buffer_ops @@ -1190,11 +1190,14 @@ impl AssistantContext { let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else { return; }; + let debounce = self.token_count.is_some(); self.pending_token_count = cx.spawn(|this, mut cx| { async move { - cx.background_executor() - .timer(Duration::from_millis(200)) - .await; + if debounce { + cx.background_executor() + .timer(Duration::from_millis(200)) + .await; + } let token_count = cx.update(|cx| model.count_tokens(request, cx))?.await?; this.update(&mut cx, |this, cx| { diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 685a185a6cd96c..f42be0fe04dc5a 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -2404,13 +2404,9 @@ impl ContextEditor { h_flex() .gap_0p5() .child( - div().max_w_32().child( - Label::new(model_name) - .size(LabelSize::Small) - .color(Color::Muted) - .text_ellipsis() - .into_any_element(), - ), + Label::new(model_name) + .size(LabelSize::Small) + .color(Color::Muted), ) .child( Icon::new(IconName::ChevronDown) @@ -2427,6 +2423,7 @@ impl ContextEditor { cx, ) }, + gpui::Corner::BottomLeft, ) .with_handle(self.language_model_selector_menu_handle.clone()) } @@ -2867,7 +2864,6 @@ impl EventEmitter for ContextEditor {} impl Render for ContextEditor { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let provider = LanguageModelRegistry::read_global(cx).active_provider(); - let accept_terms = if self.show_accept_terms { provider.as_ref().and_then(|provider| { provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx) @@ -3250,48 +3246,58 @@ impl ContextEditorToolbarItem { model_summary_editor, } } +} - fn render_remaining_tokens(&self, cx: &mut Context) -> Option { - let context = &self - .active_context_editor - .as_ref()? - .upgrade()? - .read(cx) - .context; - let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? { - TokenState::NoTokensLeft { - max_token_count, - token_count, - } => (Color::Error, token_count, max_token_count), - TokenState::HasMoreTokens { - max_token_count, - token_count, - over_warn_threshold, - } => { - let color = if over_warn_threshold { - Color::Warning - } else { - Color::Muted - }; - (color, token_count, max_token_count) - } - }; - Some( - h_flex() - .gap_0p5() - .child( - Label::new(humanize_token_count(token_count)) - .size(LabelSize::Small) - .color(token_count_color), - ) - .child(Label::new("/").size(LabelSize::Small).color(Color::Muted)) - .child( - Label::new(humanize_token_count(max_token_count)) - .size(LabelSize::Small) - .color(Color::Muted), - ), - ) - } +pub fn render_remaining_tokens( + context_editor: &Entity, + cx: &App, +) -> Option { + let context = &context_editor.read(cx).context; + + let (token_count_color, token_count, max_token_count, tooltip) = match token_state(context, cx)? + { + TokenState::NoTokensLeft { + max_token_count, + token_count, + } => ( + Color::Error, + token_count, + max_token_count, + Some("Token Limit Reached"), + ), + TokenState::HasMoreTokens { + max_token_count, + token_count, + over_warn_threshold, + } => { + let (color, tooltip) = if over_warn_threshold { + (Color::Warning, Some("Token Limit is Close to Exhaustion")) + } else { + (Color::Muted, None) + }; + (color, token_count, max_token_count, tooltip) + } + }; + + Some( + h_flex() + .id("token-count") + .gap_0p5() + .child( + Label::new(humanize_token_count(token_count)) + .size(LabelSize::Small) + .color(token_count_color), + ) + .child(Label::new("/").size(LabelSize::Small).color(Color::Muted)) + .child( + Label::new(humanize_token_count(max_token_count)) + .size(LabelSize::Small) + .color(Color::Muted), + ) + .when_some(tooltip, |element, tooltip| { + element.tooltip(Tooltip::text(tooltip)) + }), + ) } impl Render for ContextEditorToolbarItem { @@ -3334,7 +3340,12 @@ impl Render for ContextEditorToolbarItem { // scan_items_remaining // .map(|remaining_items| format!("Files to scan: {}", remaining_items)) // }) - .children(self.render_remaining_tokens(cx)); + .children( + self.active_context_editor + .as_ref() + .and_then(|editor| editor.upgrade()) + .and_then(|editor| render_remaining_tokens(&editor, cx)), + ); h_flex() .px_0p5() diff --git a/crates/assistant_context_editor/src/context_store.rs b/crates/assistant_context_editor/src/context_store.rs index c359ffb358011f..c14087c9688d61 100644 --- a/crates/assistant_context_editor/src/context_store.rs +++ b/crates/assistant_context_editor/src/context_store.rs @@ -265,19 +265,18 @@ impl ContextStore { local_versions.push(context.version(cx).to_proto(context_id.clone())); let client = this.client.clone(); let project_id = envelope.payload.project_id; - cx.background_executor() - .spawn(async move { - let operations = operations.await; - for operation in operations { - client.send(proto::UpdateContext { - project_id, - context_id: context_id.to_proto(), - operation: Some(operation), - })?; - } - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + cx.background_spawn(async move { + let operations = operations.await; + for operation in operations { + client.send(proto::UpdateContext { + project_id, + context_id: context_id.to_proto(), + operation: Some(operation), + })?; + } + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } } @@ -401,8 +400,7 @@ impl ContextStore { ) })?; let operations = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { context_proto .operations .into_iter() @@ -436,7 +434,7 @@ impl ContextStore { let languages = self.languages.clone(); let project = self.project.clone(); let telemetry = self.telemetry.clone(); - let load = cx.background_executor().spawn({ + let load = cx.background_spawn({ let path = path.clone(); async move { let saved_context = fs.load(&path).await?; @@ -539,8 +537,7 @@ impl ContextStore { ) })?; let operations = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { context_proto .operations .into_iter() @@ -693,7 +690,7 @@ impl ContextStore { pub fn search(&self, query: String, cx: &App) -> Task> { let metadata = self.contexts_metadata.clone(); let executor = cx.background_executor().clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { if query.is_empty() { metadata } else { diff --git a/crates/assistant_context_editor/src/patch.rs b/crates/assistant_context_editor/src/patch.rs index 1557d7456791f5..e46c420fc559f9 100644 --- a/crates/assistant_context_editor/src/patch.rs +++ b/crates/assistant_context_editor/src/patch.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Context as _, Result}; use collections::HashMap; use editor::ProposedChangesEditor; use futures::{future, TryFutureExt as _}; -use gpui::{App, AsyncApp, Entity, SharedString}; +use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString}; use language::{AutoindentMode, Buffer, BufferSnapshot}; use project::{Project, ProjectPath}; use std::{cmp, ops::Range, path::Path, sync::Arc}; @@ -258,8 +258,7 @@ impl AssistantEdit { let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?; let suggestion = cx - .background_executor() - .spawn(async move { kind.resolve(&snapshot) }) + .background_spawn(async move { kind.resolve(&snapshot) }) .await; Ok((buffer, suggestion)) @@ -547,7 +546,7 @@ impl Eq for AssistantPatch {} #[cfg(test)] mod tests { use super::*; - use gpui::{App, AppContext as _}; + use gpui::App; use language::{ language_settings::AllLanguageSettings, Language, LanguageConfig, LanguageMatcher, }; diff --git a/crates/assistant_context_editor/src/slash_command.rs b/crates/assistant_context_editor/src/slash_command.rs index f52bf8d89acb18..691f39bc7516de 100644 --- a/crates/assistant_context_editor/src/slash_command.rs +++ b/crates/assistant_context_editor/src/slash_command.rs @@ -4,10 +4,10 @@ pub use assistant_slash_command::SlashCommand; use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet}; use editor::{CompletionProvider, Editor}; use fuzzy::{match_strings, StringMatchCandidate}; -use gpui::{App, Context, Entity, Task, WeakEntity, Window}; -use language::{Anchor, Buffer, CompletionDocumentation, LanguageServerId, ToPoint}; +use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity, Window}; +use language::{Anchor, Buffer, LanguageServerId, ToPoint}; use parking_lot::Mutex; -use project::CompletionIntent; +use project::{lsp_store::CompletionDocumentation, CompletionIntent}; use rope::Point; use std::{ cell::RefCell, @@ -121,7 +121,7 @@ impl SlashCommandCompletionProvider { Some(project::Completion { old_range: name_range.clone(), documentation: Some(CompletionDocumentation::SingleLine( - command.description(), + command.description().into(), )), new_text, label: command.label(cx), @@ -162,7 +162,7 @@ impl SlashCommandCompletionProvider { let editor = self.editor.clone(); let workspace = self.workspace.clone(); let arguments = arguments.to_vec(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { Ok(completions .await? .into_iter() diff --git a/crates/assistant_context_editor/src/slash_command_picker.rs b/crates/assistant_context_editor/src/slash_command_picker.rs index 3bdc3160300eca..9ef7eee905afb1 100644 --- a/crates/assistant_context_editor/src/slash_command_picker.rs +++ b/crates/assistant_context_editor/src/slash_command_picker.rs @@ -102,8 +102,7 @@ impl PickerDelegate for SlashCommandDelegate { let all_commands = self.all_commands.clone(); cx.spawn_in(window, |this, mut cx| async move { let filtered_commands = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { if query.is_empty() { all_commands } else { diff --git a/crates/assistant_slash_command/src/extension_slash_command.rs b/crates/assistant_slash_command/src/extension_slash_command.rs index 1718a750e4ef24..e56c4314aa28c4 100644 --- a/crates/assistant_slash_command/src/extension_slash_command.rs +++ b/crates/assistant_slash_command/src/extension_slash_command.rs @@ -103,7 +103,7 @@ impl SlashCommand for ExtensionSlashCommand { ) -> Task>> { let command = self.command.clone(); let arguments = arguments.to_owned(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let completions = self .extension .complete_slash_command_argument(command, arguments) @@ -135,7 +135,7 @@ impl SlashCommand for ExtensionSlashCommand { ) -> Task { let command = self.command.clone(); let arguments = arguments.to_owned(); - let output = cx.background_executor().spawn(async move { + let output = cx.background_spawn(async move { let delegate = delegate.map(|delegate| Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _); let output = self diff --git a/crates/assistant_slash_commands/src/auto_command.rs b/crates/assistant_slash_commands/src/auto_command.rs index 6b9c777ff716d3..967666aaa4f248 100644 --- a/crates/assistant_slash_commands/src/auto_command.rs +++ b/crates/assistant_slash_commands/src/auto_command.rs @@ -82,7 +82,7 @@ impl SlashCommand for AutoCommand { project_index.flush_summary_backlogs(cx) })?; - cx.background_executor().spawn(task).await; + cx.background_spawn(task).await; anyhow::Ok(Vec::new()) }) @@ -129,7 +129,7 @@ impl SlashCommand for AutoCommand { // so you don't have to write it again. let original_prompt = argument.to_string(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let commands = task.await?; let mut prompt = String::new(); @@ -285,57 +285,56 @@ async fn commands_for_summaries( }) .collect::>(); - cx.background_executor() - .spawn(async move { - let futures = completion_streams - .into_iter() - .enumerate() - .map(|(ix, (stream, tx))| async move { - let start = std::time::Instant::now(); - let events = stream.await?; - log::info!("Time taken for awaiting /await chunk stream #{ix}: {:?}", start.elapsed()); - - let completion: String = events - .filter_map(|event| async { - if let Ok(LanguageModelCompletionEvent::Text(text)) = event { - Some(text) - } else { - None - } - }) - .collect() - .await; - - log::info!("Time taken for all /auto chunks to come back for #{ix}: {:?}", start.elapsed()); - - for line in completion.split('\n') { - if let Some(first_space) = line.find(' ') { - let command = &line[..first_space].trim(); - let arg = &line[first_space..].trim(); - - tx.send(CommandToRun { - name: command.to_string(), - arg: arg.to_string(), - }) - .await?; - } else if !line.trim().is_empty() { - // All slash-commands currently supported in context inference need a space for the argument. - log::warn!( - "Context inference returned a non-blank line that contained no spaces (meaning no argument for the slash command): {:?}", - line - ); + cx.background_spawn(async move { + let futures = completion_streams + .into_iter() + .enumerate() + .map(|(ix, (stream, tx))| async move { + let start = std::time::Instant::now(); + let events = stream.await?; + log::info!("Time taken for awaiting /await chunk stream #{ix}: {:?}", start.elapsed()); + + let completion: String = events + .filter_map(|event| async { + if let Ok(LanguageModelCompletionEvent::Text(text)) = event { + Some(text) + } else { + None } + }) + .collect() + .await; + + log::info!("Time taken for all /auto chunks to come back for #{ix}: {:?}", start.elapsed()); + + for line in completion.split('\n') { + if let Some(first_space) = line.find(' ') { + let command = &line[..first_space].trim(); + let arg = &line[first_space..].trim(); + + tx.send(CommandToRun { + name: command.to_string(), + arg: arg.to_string(), + }) + .await?; + } else if !line.trim().is_empty() { + // All slash-commands currently supported in context inference need a space for the argument. + log::warn!( + "Context inference returned a non-blank line that contained no spaces (meaning no argument for the slash command): {:?}", + line + ); } + } - anyhow::Ok(()) - }) - .collect::>(); + anyhow::Ok(()) + }) + .collect::>(); - let _ = futures::future::try_join_all(futures).await.log_err(); + let _ = futures::future::try_join_all(futures).await.log_err(); - let duration = all_start.elapsed(); - eprintln!("All futures completed in {:?}", duration); - }) + let duration = all_start.elapsed(); + eprintln!("All futures completed in {:?}", duration); + }) .await; drop(tx); // Close the channel so that rx.collect() won't hang. This is safe because all futures have completed. diff --git a/crates/assistant_slash_commands/src/cargo_workspace_command.rs b/crates/assistant_slash_commands/src/cargo_workspace_command.rs index 157c2063ed29b5..196aa98fd93cf5 100644 --- a/crates/assistant_slash_commands/src/cargo_workspace_command.rs +++ b/crates/assistant_slash_commands/src/cargo_workspace_command.rs @@ -132,7 +132,7 @@ impl SlashCommand for CargoWorkspaceSlashCommand { let project = workspace.project().clone(); let fs = workspace.project().read(cx).fs().clone(); let path = Self::path_to_cargo_toml(project, cx); - let output = cx.background_executor().spawn(async move { + let output = cx.background_spawn(async move { let path = path.with_context(|| "Cargo.toml not found")?; Self::build_message(fs, &path).await }); diff --git a/crates/assistant_slash_commands/src/default_command.rs b/crates/assistant_slash_commands/src/default_command.rs index 6881f89a9e2279..82d47bf11fac34 100644 --- a/crates/assistant_slash_commands/src/default_command.rs +++ b/crates/assistant_slash_commands/src/default_command.rs @@ -54,7 +54,7 @@ impl SlashCommand for DefaultSlashCommand { cx: &mut App, ) -> Task { let store = PromptStore::global(cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let store = store.await?; let prompts = store.default_prompt_metadata(); diff --git a/crates/assistant_slash_commands/src/delta_command.rs b/crates/assistant_slash_commands/src/delta_command.rs index 0cbd508d1982e5..3199b426d47a5d 100644 --- a/crates/assistant_slash_commands/src/delta_command.rs +++ b/crates/assistant_slash_commands/src/delta_command.rs @@ -86,7 +86,7 @@ impl SlashCommand for DeltaSlashCommand { } } - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut output = SlashCommandOutput::default(); let mut changes_detected = false; diff --git a/crates/assistant_slash_commands/src/diagnostics_command.rs b/crates/assistant_slash_commands/src/diagnostics_command.rs index 076fb3b4cc38e3..7152a91a615686 100644 --- a/crates/assistant_slash_commands/src/diagnostics_command.rs +++ b/crates/assistant_slash_commands/src/diagnostics_command.rs @@ -129,7 +129,7 @@ impl SlashCommand for DiagnosticsSlashCommand { let paths = self.search_paths(query.clone(), cancellation_flag.clone(), &workspace, cx); let executor = cx.background_executor().clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut matches: Vec = paths .await .into_iter() diff --git a/crates/assistant_slash_commands/src/docs_command.rs b/crates/assistant_slash_commands/src/docs_command.rs index 523534db92969a..2114a9b9fda478 100644 --- a/crates/assistant_slash_commands/src/docs_command.rs +++ b/crates/assistant_slash_commands/src/docs_command.rs @@ -176,7 +176,7 @@ impl SlashCommand for DocsSlashCommand { .provider() .ok_or_else(|| anyhow!("no docs provider specified")) .and_then(|provider| IndexedDocsStore::try_global(provider, cx)); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { fn build_completions(items: Vec) -> Vec { items .into_iter() @@ -284,7 +284,7 @@ impl SlashCommand for DocsSlashCommand { let args = DocsSlashCommandArgs::parse(arguments); let executor = cx.background_executor().clone(); - let task = cx.background_executor().spawn({ + let task = cx.background_spawn({ let store = args .provider() .ok_or_else(|| anyhow!("no docs provider specified")) diff --git a/crates/assistant_slash_commands/src/fetch_command.rs b/crates/assistant_slash_commands/src/fetch_command.rs index 142773891b4f61..8188766e7250bf 100644 --- a/crates/assistant_slash_commands/src/fetch_command.rs +++ b/crates/assistant_slash_commands/src/fetch_command.rs @@ -151,7 +151,7 @@ impl SlashCommand for FetchSlashCommand { let http_client = workspace.read(cx).client().http_client(); let url = argument.to_string(); - let text = cx.background_executor().spawn({ + let text = cx.background_spawn({ let url = url.clone(); async move { Self::build_message(http_client, &url).await } }); diff --git a/crates/assistant_slash_commands/src/file_command.rs b/crates/assistant_slash_commands/src/file_command.rs index 71a73768459865..051a0f289f1b89 100644 --- a/crates/assistant_slash_commands/src/file_command.rs +++ b/crates/assistant_slash_commands/src/file_command.rs @@ -156,7 +156,7 @@ impl SlashCommand for FileSlashCommand { cx, ); let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { Ok(paths .await .into_iter() diff --git a/crates/assistant_slash_commands/src/project_command.rs b/crates/assistant_slash_commands/src/project_command.rs index a15dc86e280f6e..34e61fe1807c6d 100644 --- a/crates/assistant_slash_commands/src/project_command.rs +++ b/crates/assistant_slash_commands/src/project_command.rs @@ -130,49 +130,48 @@ impl SlashCommand for ProjectSlashCommand { let results = SemanticDb::load_results(results, &fs, &cx).await?; - cx.background_executor() - .spawn(async move { - let mut output = "Project context:\n".to_string(); - let mut sections = Vec::new(); - - for (ix, query) in search_queries.into_iter().enumerate() { - let start_ix = output.len(); - writeln!(&mut output, "Results for {query}:").unwrap(); - let mut has_results = false; - for result in &results { - if result.query_index == ix { - add_search_result_section(result, &mut output, &mut sections); - has_results = true; - } - } - if has_results { - sections.push(SlashCommandOutputSection { - range: start_ix..output.len(), - icon: IconName::MagnifyingGlass, - label: query.into(), - metadata: None, - }); - output.push('\n'); - } else { - output.truncate(start_ix); + cx.background_spawn(async move { + let mut output = "Project context:\n".to_string(); + let mut sections = Vec::new(); + + for (ix, query) in search_queries.into_iter().enumerate() { + let start_ix = output.len(); + writeln!(&mut output, "Results for {query}:").unwrap(); + let mut has_results = false; + for result in &results { + if result.query_index == ix { + add_search_result_section(result, &mut output, &mut sections); + has_results = true; } } - - sections.push(SlashCommandOutputSection { - range: 0..output.len(), - icon: IconName::Book, - label: "Project context".into(), - metadata: None, - }); - - Ok(SlashCommandOutput { - text: output, - sections, - run_commands_in_text: true, + if has_results { + sections.push(SlashCommandOutputSection { + range: start_ix..output.len(), + icon: IconName::MagnifyingGlass, + label: query.into(), + metadata: None, + }); + output.push('\n'); + } else { + output.truncate(start_ix); } - .to_event_stream()) - }) - .await + } + + sections.push(SlashCommandOutputSection { + range: 0..output.len(), + icon: IconName::Book, + label: "Project context".into(), + metadata: None, + }); + + Ok(SlashCommandOutput { + text: output, + sections, + run_commands_in_text: true, + } + .to_event_stream()) + }) + .await }) } } diff --git a/crates/assistant_slash_commands/src/prompt_command.rs b/crates/assistant_slash_commands/src/prompt_command.rs index 930a7fb732f589..5d5f44958f8a44 100644 --- a/crates/assistant_slash_commands/src/prompt_command.rs +++ b/crates/assistant_slash_commands/src/prompt_command.rs @@ -43,7 +43,7 @@ impl SlashCommand for PromptSlashCommand { ) -> Task>> { let store = PromptStore::global(cx); let query = arguments.to_owned().join(" "); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let prompts = store.await?.search(query).await; Ok(prompts .into_iter() @@ -77,7 +77,7 @@ impl SlashCommand for PromptSlashCommand { let store = PromptStore::global(cx); let title = SharedString::from(title.clone()); - let prompt = cx.background_executor().spawn({ + let prompt = cx.background_spawn({ let title = title.clone(); async move { let store = store.await?; diff --git a/crates/assistant_slash_commands/src/search_command.rs b/crates/assistant_slash_commands/src/search_command.rs index 6b75d57de09451..8005de0bc9d2a9 100644 --- a/crates/assistant_slash_commands/src/search_command.rs +++ b/crates/assistant_slash_commands/src/search_command.rs @@ -119,8 +119,7 @@ impl SlashCommand for SearchSlashCommand { let loaded_results = SemanticDb::load_results(results, &fs, &cx).await?; let output = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let mut text = format!("Search results for {query}:\n"); let mut sections = Vec::new(); for loaded_result in &loaded_results { diff --git a/crates/assistant_slash_commands/src/streaming_example_command.rs b/crates/assistant_slash_commands/src/streaming_example_command.rs index ed7797843a05b1..ac6b5a5f7e5ae4 100644 --- a/crates/assistant_slash_commands/src/streaming_example_command.rs +++ b/crates/assistant_slash_commands/src/streaming_example_command.rs @@ -63,56 +63,55 @@ impl SlashCommand for StreamingExampleSlashCommand { cx: &mut App, ) -> Task { let (events_tx, events_rx) = mpsc::unbounded(); - cx.background_executor() - .spawn(async move { - events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection { - icon: IconName::FileRust, - label: "Section 1".into(), - metadata: None, - }))?; - events_tx.unbounded_send(Ok(SlashCommandEvent::Content( - SlashCommandContent::Text { - text: "Hello".into(), - run_commands_in_text: false, - }, - )))?; - events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?; - + cx.background_spawn(async move { + events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection { + icon: IconName::FileRust, + label: "Section 1".into(), + metadata: None, + }))?; + events_tx.unbounded_send(Ok(SlashCommandEvent::Content( + SlashCommandContent::Text { + text: "Hello".into(), + run_commands_in_text: false, + }, + )))?; + events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?; + + Timer::after(Duration::from_secs(1)).await; + + events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection { + icon: IconName::FileRust, + label: "Section 2".into(), + metadata: None, + }))?; + events_tx.unbounded_send(Ok(SlashCommandEvent::Content( + SlashCommandContent::Text { + text: "World".into(), + run_commands_in_text: false, + }, + )))?; + events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?; + + for n in 1..=10 { Timer::after(Duration::from_secs(1)).await; events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection { - icon: IconName::FileRust, - label: "Section 2".into(), + icon: IconName::StarFilled, + label: format!("Section {n}").into(), metadata: None, }))?; events_tx.unbounded_send(Ok(SlashCommandEvent::Content( SlashCommandContent::Text { - text: "World".into(), + text: "lorem ipsum ".repeat(n).trim().into(), run_commands_in_text: false, }, )))?; events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?; + } - for n in 1..=10 { - Timer::after(Duration::from_secs(1)).await; - - events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection { - icon: IconName::StarFilled, - label: format!("Section {n}").into(), - metadata: None, - }))?; - events_tx.unbounded_send(Ok(SlashCommandEvent::Content( - SlashCommandContent::Text { - text: "lorem ipsum ".repeat(n).trim().into(), - run_commands_in_text: false, - }, - )))?; - events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?; - } - - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); Task::ready(Ok(events_rx.boxed())) } diff --git a/crates/assistant_slash_commands/src/symbols_command.rs b/crates/assistant_slash_commands/src/symbols_command.rs index d44d92058dba04..533c561b137a57 100644 --- a/crates/assistant_slash_commands/src/symbols_command.rs +++ b/crates/assistant_slash_commands/src/symbols_command.rs @@ -4,7 +4,7 @@ use assistant_slash_command::{ SlashCommandResult, }; use editor::Editor; -use gpui::{Task, WeakEntity}; +use gpui::{AppContext as _, Task, WeakEntity}; use language::{BufferSnapshot, LspAdapterDelegate}; use std::sync::Arc; use std::{path::Path, sync::atomic::AtomicBool}; @@ -69,7 +69,7 @@ impl SlashCommand for OutlineSlashCommand { let snapshot = buffer.read(cx).snapshot(); let path = snapshot.resolve_file_path(cx, true); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let outline = snapshot .outline(None) .context("no symbols for active tab")?; diff --git a/crates/assistant_slash_commands/src/tab_command.rs b/crates/assistant_slash_commands/src/tab_command.rs index 6ff495bdbeec85..111ea05e4c09ea 100644 --- a/crates/assistant_slash_commands/src/tab_command.rs +++ b/crates/assistant_slash_commands/src/tab_command.rs @@ -152,7 +152,7 @@ impl SlashCommand for TabSlashCommand { cx, ); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut output = SlashCommandOutput::default(); for (full_path, buffer, _) in tab_items_search.await? { append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err(); @@ -212,74 +212,73 @@ fn tab_items_for_queries( })??; let background_executor = cx.background_executor().clone(); - cx.background_executor() - .spawn(async move { - open_buffers.sort_by_key(|(_, _, timestamp)| *timestamp); - if empty_query - || queries - .iter() - .any(|query| query == ALL_TABS_COMPLETION_ITEM) - { - return Ok(open_buffers); - } - - let matched_items = if strict_match { - let match_candidates = open_buffers - .iter() - .enumerate() - .filter_map(|(id, (full_path, ..))| { - let path_string = full_path.as_deref()?.to_string_lossy().to_string(); - Some((id, path_string)) - }) - .fold(HashMap::default(), |mut candidates, (id, path_string)| { - candidates - .entry(path_string) - .or_insert_with(Vec::new) - .push(id); - candidates - }); + cx.background_spawn(async move { + open_buffers.sort_by_key(|(_, _, timestamp)| *timestamp); + if empty_query + || queries + .iter() + .any(|query| query == ALL_TABS_COMPLETION_ITEM) + { + return Ok(open_buffers); + } - queries - .iter() - .filter_map(|query| match_candidates.get(query)) - .flatten() - .copied() - .filter_map(|id| open_buffers.get(id)) - .cloned() - .collect() - } else { - let match_candidates = open_buffers - .iter() - .enumerate() - .filter_map(|(id, (full_path, ..))| { - let path_string = full_path.as_deref()?.to_string_lossy().to_string(); - Some(fuzzy::StringMatchCandidate::new(id, &path_string)) - }) - .collect::>(); - let mut processed_matches = HashSet::default(); - let file_queries = queries.iter().map(|query| { - fuzzy::match_strings( - &match_candidates, - query, - true, - usize::MAX, - &cancel, - background_executor.clone(), - ) + let matched_items = if strict_match { + let match_candidates = open_buffers + .iter() + .enumerate() + .filter_map(|(id, (full_path, ..))| { + let path_string = full_path.as_deref()?.to_string_lossy().to_string(); + Some((id, path_string)) + }) + .fold(HashMap::default(), |mut candidates, (id, path_string)| { + candidates + .entry(path_string) + .or_insert_with(Vec::new) + .push(id); + candidates }); - join_all(file_queries) - .await - .into_iter() - .flatten() - .filter(|string_match| processed_matches.insert(string_match.candidate_id)) - .filter_map(|string_match| open_buffers.get(string_match.candidate_id)) - .cloned() - .collect() - }; - Ok(matched_items) - }) - .await + queries + .iter() + .filter_map(|query| match_candidates.get(query)) + .flatten() + .copied() + .filter_map(|id| open_buffers.get(id)) + .cloned() + .collect() + } else { + let match_candidates = open_buffers + .iter() + .enumerate() + .filter_map(|(id, (full_path, ..))| { + let path_string = full_path.as_deref()?.to_string_lossy().to_string(); + Some(fuzzy::StringMatchCandidate::new(id, &path_string)) + }) + .collect::>(); + let mut processed_matches = HashSet::default(); + let file_queries = queries.iter().map(|query| { + fuzzy::match_strings( + &match_candidates, + query, + true, + usize::MAX, + &cancel, + background_executor.clone(), + ) + }); + + join_all(file_queries) + .await + .into_iter() + .flatten() + .filter(|string_match| processed_matches.insert(string_match.candidate_id)) + .filter_map(|string_match| open_buffers.get(string_match.candidate_id)) + .cloned() + .collect() + }; + Ok(matched_items) + }) + .await }) } diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 8994f7993ce9c0..c1dbf2f6869138 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -513,7 +513,7 @@ impl AutoUpdater { should_show: bool, cx: &App, ) -> Task> { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { if should_show { KEY_VALUE_STORE .write_kvp( @@ -531,7 +531,7 @@ impl AutoUpdater { } pub fn should_show_update_notification(&self, cx: &App) -> Task> { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { Ok(KEY_VALUE_STORE .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)? .is_some()) diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index dc8ce87a924d5e..18d88129feecb0 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -1,6 +1,6 @@ use futures::{channel::oneshot, future::OptionFuture}; use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; -use gpui::{App, AsyncApp, Context, Entity, EventEmitter}; +use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter}; use language::{Language, LanguageRegistry}; use rope::Rope; use std::{cmp, future::Future, iter, ops::Range, sync::Arc}; @@ -615,11 +615,9 @@ impl BufferDiff { cx, ) }); - let base_text_snapshot = cx - .background_executor() - .spawn(OptionFuture::from(base_text_snapshot)); + let base_text_snapshot = cx.background_spawn(OptionFuture::from(base_text_snapshot)); - let hunks = cx.background_executor().spawn({ + let hunks = cx.background_spawn({ let buffer = buffer.clone(); async move { compute_hunks(diff_base, buffer) } }); @@ -641,7 +639,7 @@ impl BufferDiff { .clone() .map(|buffer| buffer.as_rope().clone()), ); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { BufferDiffInner { hunks: compute_hunks(diff_base, buffer), base_text: diff_base_buffer, @@ -998,7 +996,7 @@ mod tests { use std::fmt::Write as _; use super::*; - use gpui::{AppContext as _, TestAppContext}; + use gpui::TestAppContext; use rand::{rngs::StdRng, Rng as _}; use text::{Buffer, BufferId, Rope}; use unindent::Unindent as _; diff --git a/crates/call/src/cross_platform/mod.rs b/crates/call/src/cross_platform/mod.rs index d361dd9dced854..429e7ee288e1e8 100644 --- a/crates/call/src/cross_platform/mod.rs +++ b/crates/call/src/cross_platform/mod.rs @@ -241,7 +241,7 @@ impl ActiveCall { }) .shared(); self.pending_room_creation = Some(room.clone()); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { room.await.map_err(|err| anyhow!("{:?}", err))?; anyhow::Ok(()) }) @@ -278,7 +278,7 @@ impl ActiveCall { }; let client = self.client.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { client .request(proto::CancelCall { room_id, diff --git a/crates/call/src/cross_platform/room.rs b/crates/call/src/cross_platform/room.rs index c8e8e8cbf852a7..250e33b02734de 100644 --- a/crates/call/src/cross_platform/room.rs +++ b/crates/call/src/cross_platform/room.rs @@ -13,7 +13,7 @@ use client::{ use collections::{BTreeMap, HashMap, HashSet}; use fs::Fs; use futures::{FutureExt, StreamExt}; -use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity}; +use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity}; use language::LanguageRegistry; #[cfg(not(all(target_os = "windows", target_env = "gnu")))] use livekit::{ @@ -255,7 +255,7 @@ impl Room { fn app_will_quit(&mut self, cx: &mut Context) -> impl Future { let task = if self.status.is_online() { let leave = self.leave_internal(cx); - Some(cx.background_executor().spawn(async move { + Some(cx.background_spawn(async move { leave.await.log_err(); })) } else { @@ -322,7 +322,7 @@ impl Room { self.clear_state(cx); let leave_room = self.client.request(proto::LeaveRoom {}); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { leave_room.await?; anyhow::Ok(()) }) @@ -1248,7 +1248,7 @@ impl Room { }; cx.notify(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { client .request(proto::UpdateParticipantLocation { room_id, @@ -1373,11 +1373,10 @@ impl Room { match publication { Ok(publication) => { if canceled { - cx.background_executor() - .spawn(async move { - participant.unpublish_track(&publication.sid()).await - }) - .detach_and_log_err(cx) + cx.background_spawn(async move { + participant.unpublish_track(&publication.sid()).await + }) + .detach_and_log_err(cx) } else { if live_kit.muted_by_user || live_kit.deafened { publication.mute(); @@ -1465,11 +1464,10 @@ impl Room { match publication { Ok(publication) => { if canceled { - cx.background_executor() - .spawn(async move { - participant.unpublish_track(&publication.sid()).await - }) - .detach() + cx.background_spawn(async move { + participant.unpublish_track(&publication.sid()).await + }) + .detach() } else { live_kit.screen_track = LocalTrack::Published { track_publication: publication, @@ -1561,9 +1559,10 @@ impl Room { { let local_participant = live_kit.room.local_participant(); let sid = track_publication.sid(); - cx.background_executor() - .spawn(async move { local_participant.unpublish_track(&sid).await }) - .detach_and_log_err(cx); + cx.background_spawn( + async move { local_participant.unpublish_track(&sid).await }, + ) + .detach_and_log_err(cx); cx.notify(); } @@ -1722,13 +1721,12 @@ impl LiveKitRoom { } let participant = self.room.local_participant(); - cx.background_executor() - .spawn(async move { - for sid in tracks_to_unpublish { - participant.unpublish_track(&sid).await.log_err(); - } - }) - .detach(); + cx.background_spawn(async move { + for sid in tracks_to_unpublish { + participant.unpublish_track(&sid).await.log_err(); + } + }) + .detach(); } } diff --git a/crates/call/src/macos/mod.rs b/crates/call/src/macos/mod.rs index e6fb8c3cfb6e7e..d7aef97327b14c 100644 --- a/crates/call/src/macos/mod.rs +++ b/crates/call/src/macos/mod.rs @@ -234,7 +234,7 @@ impl ActiveCall { }) .shared(); self.pending_room_creation = Some(room.clone()); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { room.await.map_err(|err| anyhow!("{:?}", err))?; anyhow::Ok(()) }) @@ -271,7 +271,7 @@ impl ActiveCall { }; let client = self.client.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { client .request(proto::CancelCall { room_id, diff --git a/crates/call/src/macos/room.rs b/crates/call/src/macos/room.rs index d7f95dd2386db4..6c9dd1ba500455 100644 --- a/crates/call/src/macos/room.rs +++ b/crates/call/src/macos/room.rs @@ -311,7 +311,7 @@ impl Room { fn app_will_quit(&mut self, cx: &mut Context) -> impl Future { let task = if self.status.is_online() { let leave = self.leave_internal(cx); - Some(cx.background_executor().spawn(async move { + Some(cx.background_spawn(async move { leave.await.log_err(); })) } else { @@ -378,7 +378,7 @@ impl Room { self.clear_state(cx); let leave_room = self.client.request(proto::LeaveRoom {}); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { leave_room.await?; anyhow::Ok(()) }) @@ -1268,7 +1268,7 @@ impl Room { }; cx.notify(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { client .request(proto::UpdateParticipantLocation { room_id, @@ -1385,9 +1385,7 @@ impl Room { live_kit.room.unpublish_track(publication); } else { if live_kit.muted_by_user || live_kit.deafened { - cx.background_executor() - .spawn(publication.set_mute(true)) - .detach(); + cx.background_spawn(publication.set_mute(true)).detach(); } live_kit.microphone_track = LocalTrack::Published { track_publication: publication, diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index bb11487a0ef072..2e128748921fc1 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -514,8 +514,7 @@ impl ChannelStore { } } }; - cx.background_executor() - .spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) }) + cx.background_spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) }) } pub fn is_channel_admin(&self, channel_id: ChannelId) -> bool { @@ -781,7 +780,7 @@ impl ChannelStore { cx: &mut Context, ) -> Task> { let client = self.client.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { client .request(proto::RespondToChannelInvite { channel_id: channel_id.0, @@ -975,21 +974,18 @@ impl ChannelStore { if let Some(operations) = operations { let client = this.client.clone(); - cx.background_executor() - .spawn(async move { - let operations = operations.await; - for chunk in - language::proto::split_operations(operations) - { - client - .send(proto::UpdateChannelBuffer { - channel_id: channel_id.0, - operations: chunk, - }) - .ok(); - } - }) - .detach(); + cx.background_spawn(async move { + let operations = operations.await; + for chunk in language::proto::split_operations(operations) { + client + .send(proto::UpdateChannelBuffer { + channel_id: channel_id.0, + operations: chunk, + }) + .ok(); + } + }) + .detach(); return true; } } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 280d3c2d94b091..1b2d007f5420eb 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -19,7 +19,7 @@ use futures::{ channel::oneshot, future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, TryFutureExt as _, TryStreamExt, }; -use gpui::{actions, App, AsyncApp, Entity, Global, Task, WeakEntity}; +use gpui::{actions, App, AppContext as _, AsyncApp, Entity, Global, Task, WeakEntity}; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use parking_lot::RwLock; use postage::watch; @@ -1064,7 +1064,7 @@ impl Client { let rpc_url = self.rpc_url(http, release_channel); let system_id = self.telemetry.system_id(); let metrics_id = self.telemetry.metrics_id(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { use HttpOrHttps::*; #[derive(Debug)] @@ -1743,7 +1743,7 @@ mod tests { use crate::test::FakeServer; use clock::FakeSystemClock; - use gpui::{AppContext as _, BackgroundExecutor, TestAppContext}; + use gpui::{BackgroundExecutor, TestAppContext}; use http_client::FakeHttpClient; use parking_lot::Mutex; use proto::TypedEnvelope; @@ -1806,7 +1806,7 @@ mod tests { // Time out when client tries to connect. client.override_authenticate(move |cx| { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { Ok(Credentials { user_id, access_token: "token".into(), @@ -1814,7 +1814,7 @@ mod tests { }) }); client.override_establish_connection(|_, cx| { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { future::pending::<()>().await; unreachable!() }) @@ -1848,7 +1848,7 @@ mod tests { // Time out when re-establishing the connection. server.allow_connections(); client.override_establish_connection(|_, cx| { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { future::pending::<()>().await; unreachable!() }) @@ -1887,7 +1887,7 @@ mod tests { move |cx| { let auth_count = auth_count.clone(); let dropped_auth_count = dropped_auth_count.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { *auth_count.lock() += 1; let _drop = util::defer(move || *dropped_auth_count.lock() += 1); future::pending::<()>().await; diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index b0528f0747cb91..309779049e82f8 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -5,7 +5,7 @@ use anyhow::Result; use clock::SystemClock; use futures::channel::mpsc; use futures::{Future, StreamExt}; -use gpui::{App, BackgroundExecutor, Task}; +use gpui::{App, AppContext as _, BackgroundExecutor, Task}; use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request}; use parking_lot::Mutex; use release_channel::ReleaseChannel; @@ -219,18 +219,17 @@ impl Telemetry { })); Self::log_file_path(); - cx.background_executor() - .spawn({ - let state = state.clone(); - let os_version = os_version(); - state.lock().os_version = Some(os_version.clone()); - async move { - if let Some(tempfile) = File::create(Self::log_file_path()).log_err() { - state.lock().log_file = Some(tempfile); - } + cx.background_spawn({ + let state = state.clone(); + let os_version = os_version(); + state.lock().os_version = Some(os_version.clone()); + async move { + if let Some(tempfile) = File::create(Self::log_file_path()).log_err() { + state.lock().log_file = Some(tempfile); } - }) - .detach(); + } + }) + .detach(); cx.observe_global::({ let state = state.clone(); @@ -252,17 +251,16 @@ impl Telemetry { let (tx, mut rx) = mpsc::unbounded(); ::telemetry::init(tx); - cx.background_executor() - .spawn({ - let this = Arc::downgrade(&this); - async move { - while let Some(event) = rx.next().await { - let Some(state) = this.upgrade() else { break }; - state.report_event(Event::Flexible(event)) - } + cx.background_spawn({ + let this = Arc::downgrade(&this); + async move { + while let Some(event) = rx.next().await { + let Some(state) = this.upgrade() else { break }; + state.report_event(Event::Flexible(event)) } - }) - .detach(); + } + }) + .detach(); // We should only ever have one instance of Telemetry, leak the subscription to keep it alive // rather than store in TelemetryState, complicating spawn as subscriptions are not Send diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs index 4b158b6ab1ef5e..83781248ad9ebb 100644 --- a/crates/client/src/test.rs +++ b/crates/client/src/test.rs @@ -85,7 +85,7 @@ impl FakeServer { Connection::in_memory(cx.background_executor().clone()); let (connection_id, io, incoming) = peer.add_test_connection(server_conn, cx.background_executor().clone()); - cx.background_executor().spawn(io).detach(); + cx.background_spawn(io).detach(); { let mut state = state.lock(); state.connection_id = Some(connection_id); diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 8da497a6788118..96a6fe5e6c652b 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -244,18 +244,17 @@ impl TestServer { .await .expect("retrieving user failed") .unwrap(); - cx.background_executor() - .spawn(server.handle_connection( - server_conn, - client_name, - Principal::User(user), - ZedVersion(SemanticVersion::new(1, 0, 0)), - None, - None, - Some(connection_id_tx), - Executor::Deterministic(cx.background_executor().clone()), - )) - .detach(); + cx.background_spawn(server.handle_connection( + server_conn, + client_name, + Principal::User(user), + ZedVersion(SemanticVersion::new(1, 0, 0)), + None, + None, + Some(connection_id_tx), + Executor::Deterministic(cx.background_executor().clone()), + )) + .detach(); let connection_id = connection_id_rx.await.map_err(|e| { EstablishConnectionError::Other(anyhow!( "{} (is server shutting down?)", diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 58eb2b576654b0..773a2de9f29594 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -201,8 +201,7 @@ impl ChatPanel { ) -> Task>> { cx.spawn(|mut cx| async move { let serialized_panel = if let Some(panel) = cx - .background_executor() - .spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) }) + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(CHAT_PANEL_KEY) }) .await .log_err() .flatten() @@ -227,7 +226,7 @@ impl ChatPanel { fn serialize(&mut self, cx: &mut Context) { let width = self.width; - self.pending_serialization = cx.background_executor().spawn( + self.pending_serialization = cx.background_spawn( async move { KEY_VALUE_STORE .write_kvp( diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 4f0959b575e161..026bc28c5c92b5 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -454,8 +454,7 @@ impl MessageEditor { mut cx: AsyncWindowContext, ) { let (buffer, ranges) = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let ranges = MENTIONS_SEARCH.search(&buffer, None).await; (buffer, ranges) }) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 062b9dd81d5caf..e8d65ac9237188 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -319,8 +319,7 @@ impl CollabPanel { mut cx: AsyncWindowContext, ) -> anyhow::Result> { let serialized_panel = cx - .background_executor() - .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) }) + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) }) .await .map_err(|_| anyhow::anyhow!("Failed to read collaboration panel from key value store")) .log_err() @@ -351,7 +350,7 @@ impl CollabPanel { fn serialize(&mut self, cx: &mut Context) { let width = self.width; let collapsed_channels = self.collapsed_channels.clone(); - self.pending_serialization = cx.background_executor().spawn( + self.pending_serialization = cx.background_spawn( async move { KEY_VALUE_STORE .write_kvp( diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index eafe7eaf6e6410..50845af5ba63bd 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -183,8 +183,7 @@ impl NotificationPanel { ) -> Task>> { cx.spawn(|mut cx| async move { let serialized_panel = if let Some(panel) = cx - .background_executor() - .spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) }) + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(NOTIFICATION_PANEL_KEY) }) .await .log_err() .flatten() @@ -209,7 +208,7 @@ impl NotificationPanel { fn serialize(&mut self, cx: &mut Context) { let width = self.width; - self.pending_serialization = cx.background_executor().spawn( + self.pending_serialization = cx.background_spawn( async move { KEY_VALUE_STORE .write_kvp( diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index a431b36736d1c0..cdf1d68ac87af4 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -281,7 +281,7 @@ impl PickerDelegate for CommandPaletteDelegate { query = alias.to_string(); } let (mut tx, mut rx) = postage::dispatch::channel(1); - let task = cx.background_executor().spawn({ + let task = cx.background_spawn({ let mut commands = self.all_commands.clone(); let hit_counts = cx.global::().clone(); let executor = cx.background_executor().clone(); diff --git a/crates/context_server/src/client.rs b/crates/context_server/src/client.rs index 021b7389f5f8e5..4e0b799fb88cc4 100644 --- a/crates/context_server/src/client.rs +++ b/crates/context_server/src/client.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context as _, Result}; use collections::HashMap; use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, FutureExt}; -use gpui::{AsyncApp, BackgroundExecutor, Task}; +use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Task}; use parking_lot::Mutex; use postage::barrier; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -192,7 +192,7 @@ impl Client { let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task); stdout.or(stderr) }); - let output_task = cx.background_executor().spawn({ + let output_task = cx.background_spawn({ Self::handle_output( stdin, outbound_rx, diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 362e7d3821480a..5edc0d5954329a 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -234,8 +234,7 @@ impl RegisteredBuffer { let new_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot()).ok()?; let content_changes = cx - .background_executor() - .spawn({ + .background_spawn({ let new_snapshot = new_snapshot.clone(); async move { new_snapshot @@ -588,8 +587,7 @@ impl Copilot { } }; - cx.background_executor() - .spawn(task.map_err(|err| anyhow!("{:?}", err))) + cx.background_spawn(task.map_err(|err| anyhow!("{:?}", err))) } else { // If we're downloading, wait until download is finished // If we're in a stuck state, display to the user @@ -601,7 +599,7 @@ impl Copilot { self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx); if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server { let server = server.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { server .request::(request::SignOutParams {}) .await?; @@ -631,7 +629,7 @@ impl Copilot { cx.notify(); - cx.background_executor().spawn(start_task) + cx.background_spawn(start_task) } pub fn language_server(&self) -> Option<&Arc> { @@ -813,7 +811,7 @@ impl Copilot { .request::(request::NotifyAcceptedParams { uuid: completion.uuid.clone(), }); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { request.await?; Ok(()) }) @@ -837,7 +835,7 @@ impl Copilot { .map(|completion| completion.uuid.clone()) .collect(), }); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { request.await?; Ok(()) }) @@ -884,7 +882,7 @@ impl Copilot { .map(|file| file.path().to_path_buf()) .unwrap_or_default(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let (version, snapshot) = snapshot.await?; let result = lsp .request::(request::GetCompletionsParams { diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index b825855dbe0c51..0b1e5767a48dc2 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -4,7 +4,7 @@ pub mod query; // Re-export pub use anyhow; use anyhow::Context as _; -use gpui::App; +use gpui::{App, AppContext}; pub use indoc::indoc; pub use paths::database_dir; pub use smol; @@ -192,8 +192,7 @@ pub fn write_and_log(cx: &App, db_write: impl FnOnce() -> F + Send + 'static) where F: Future> + Send, { - cx.background_executor() - .spawn(async move { db_write().await.log_err() }) + cx.background_spawn(async move { db_write().await.log_err() }) .detach() } diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index a967750cccbbb6..0176651f2fd0fa 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -1,14 +1,16 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - div, px, uniform_list, AnyElement, BackgroundExecutor, Div, Entity, FontWeight, + div, px, uniform_list, AnyElement, BackgroundExecutor, Div, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText, - UniformListScrollHandle, WeakEntity, + UniformListScrollHandle, }; use language::Buffer; -use language::{CodeLabel, CompletionDocumentation}; +use language::CodeLabel; use lsp::LanguageServerId; +use markdown::Markdown; use multi_buffer::{Anchor, ExcerptId}; use ordered_float::OrderedFloat; +use project::lsp_store::CompletionDocumentation; use project::{CodeAction, Completion, TaskSourceKind}; use std::{ @@ -21,12 +23,12 @@ use std::{ use task::ResolvedTask; use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled}; use util::ResultExt; -use workspace::Workspace; +use crate::hover_popover::{hover_markdown_style, open_markdown_url}; use crate::{ actions::{ConfirmCodeAction, ConfirmCompletion}, - render_parsed_markdown, split_words, styled_runs_for_code_label, CodeActionProvider, - CompletionId, CompletionProvider, DisplayRow, Editor, EditorStyle, ResolvedTasks, + split_words, styled_runs_for_code_label, CodeActionProvider, CompletionId, CompletionProvider, + DisplayRow, Editor, EditorStyle, ResolvedTasks, }; pub const MENU_GAP: Pixels = px(4.); @@ -137,17 +139,27 @@ impl CodeContextMenu { } pub fn render_aside( - &self, - style: &EditorStyle, + &mut self, + editor: &Editor, max_size: Size, - workspace: Option>, + window: &mut Window, cx: &mut Context, ) -> Option { match self { - CodeContextMenu::Completions(menu) => menu.render_aside(style, max_size, workspace, cx), + CodeContextMenu::Completions(menu) => menu.render_aside(editor, max_size, window, cx), CodeContextMenu::CodeActions(_) => None, } } + + pub fn focused(&self, window: &mut Window, cx: &mut Context) -> bool { + match self { + CodeContextMenu::Completions(completions_menu) => completions_menu + .markdown_element + .as_ref() + .is_some_and(|markdown| markdown.focus_handle(cx).contains_focused(window, cx)), + CodeContextMenu::CodeActions(_) => false, + } + } } pub enum ContextMenuOrigin { @@ -169,6 +181,7 @@ pub struct CompletionsMenu { resolve_completions: bool, show_completion_documentation: bool, last_rendered_range: Rc>>>, + markdown_element: Option>, } impl CompletionsMenu { @@ -199,6 +212,7 @@ impl CompletionsMenu { scroll_handle: UniformListScrollHandle::new(), resolve_completions: true, last_rendered_range: RefCell::new(None).into(), + markdown_element: None, } } @@ -255,6 +269,7 @@ impl CompletionsMenu { resolve_completions: false, show_completion_documentation: false, last_rendered_range: RefCell::new(None).into(), + markdown_element: None, } } @@ -556,10 +571,10 @@ impl CompletionsMenu { } fn render_aside( - &self, - style: &EditorStyle, + &mut self, + editor: &Editor, max_size: Size, - workspace: Option>, + window: &mut Window, cx: &mut Context, ) -> Option { if !self.show_completion_documentation { @@ -571,17 +586,35 @@ impl CompletionsMenu { .documentation .as_ref()? { - CompletionDocumentation::MultiLinePlainText(text) => { - div().child(SharedString::from(text.clone())) + CompletionDocumentation::MultiLinePlainText(text) => div().child(text.clone()), + CompletionDocumentation::MultiLineMarkdown(parsed) if !parsed.is_empty() => { + let markdown = self.markdown_element.get_or_insert_with(|| { + cx.new(|cx| { + let languages = editor + .workspace + .as_ref() + .and_then(|(workspace, _)| workspace.upgrade()) + .map(|workspace| workspace.read(cx).app_state().languages.clone()); + let language = editor + .language_at(self.initial_position, cx) + .map(|l| l.name().to_proto()); + Markdown::new( + SharedString::default(), + hover_markdown_style(window, cx), + languages, + language, + window, + cx, + ) + .copy_code_block_buttons(false) + .open_url(open_markdown_url) + }) + }); + markdown.update(cx, |markdown, cx| { + markdown.reset(parsed.clone(), window, cx); + }); + div().child(markdown.clone()) } - CompletionDocumentation::MultiLineMarkdown(parsed) if !parsed.text.is_empty() => div() - .child(render_parsed_markdown( - "completions_markdown", - parsed, - &style, - workspace, - cx, - )), CompletionDocumentation::MultiLineMarkdown(_) => return None, CompletionDocumentation::SingleLine(_) => return None, CompletionDocumentation::Undocumented => return None, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 77eab5881cb965..629a7df8e07459 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -171,7 +171,7 @@ impl WrapMap { let text_system = cx.text_system().clone(); let (font, font_size) = self.font_with_size.clone(); - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { let mut line_wrapper = text_system.line_wrapper(font, font_size); let tab_snapshot = new_snapshot.tab_snapshot.clone(); let range = TabPoint::zero()..tab_snapshot.max_point(); @@ -255,7 +255,7 @@ impl WrapMap { let mut snapshot = self.snapshot.clone(); let text_system = cx.text_system().clone(); let (font, font_size) = self.font_with_size.clone(); - let update_task = cx.background_executor().spawn(async move { + let update_task = cx.background_spawn(async move { let mut edits = Patch::default(); let mut line_wrapper = text_system.line_wrapper(font, font_size); for (tab_snapshot, tab_edits) in pending_edits { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ad968a409e3185..c47832b48e17c4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -80,13 +80,13 @@ use code_context_menus::{ use git::blame::GitBlame; use gpui::{ div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation, - AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Bounds, ClickEvent, - ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, Entity, EntityInputHandler, - EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, - HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton, MouseDownEvent, - PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, - Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, - WeakEntity, WeakFocusHandle, Window, + AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Background, Bounds, + ClickEvent, ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, Entity, + EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, + Global, HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton, + MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, + StyledText, Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, + UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight}; @@ -100,9 +100,9 @@ use itertools::Itertools; use language::{ language_settings::{self, all_language_settings, language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, - CompletionDocumentation, CursorShape, Diagnostic, DiskState, EditPredictionsMode, EditPreview, - HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, - SelectionGoal, TextObject, TransactionId, TreeSitterOptions, + CursorShape, Diagnostic, DiskState, EditPredictionsMode, EditPreview, HighlightedText, + IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, + TransactionId, TreeSitterOptions, }; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; @@ -142,7 +142,7 @@ use project::{ breakpoint_store::{Breakpoint, BreakpointKind}, dap_store::DapStore, }, - lsp_store::{FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, + lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, project_settings::{GitGutterSetting, ProjectSettings}, CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction, TaskSourceKind, @@ -216,6 +216,14 @@ pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration: pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction"; pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict"; +const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers { + alt: true, + shift: true, + control: false, + platform: false, + function: false, +}; + pub fn render_parsed_markdown( element_id: impl Into, parsed: &language::ParsedMarkdown, @@ -2229,6 +2237,7 @@ impl Editor { cx.emit(SearchEvent::ActiveMatchChanged) } if local + && self.is_singleton(cx) && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None { if let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) { @@ -2249,7 +2258,7 @@ impl Editor { .collect(); DB.save_editor_selections(editor_id, workspace_id, selections) .await - .context("persisting editor selections") + .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}")) .log_err(); }); } @@ -4807,29 +4816,27 @@ impl Editor { self.clear_background_highlights::(cx); return; } - if self.selections.count() != 1 || self.selections.line_mode { - self.clear_background_highlights::(cx); - return; - } - let selection = self.selections.newest::(cx); - if selection.is_empty() || selection.start.row != selection.end.row { - self.clear_background_highlights::(cx); - return; - } let debounce = EditorSettings::get_global(cx).selection_highlight_debounce; self.selection_highlight_task = Some(cx.spawn_in(window, |editor, mut cx| async move { cx.background_executor() .timer(Duration::from_millis(debounce)) .await; - let Some(matches_task) = editor - .read_with(&mut cx, |editor, cx| { + let Some(Some(matches_task)) = editor + .update_in(&mut cx, |editor, _, cx| { + if editor.selections.count() != 1 || editor.selections.line_mode { + editor.clear_background_highlights::(cx); + return None; + } + let selection = editor.selections.newest::(cx); + if selection.is_empty() || selection.start.row != selection.end.row { + editor.clear_background_highlights::(cx); + return None; + } let buffer = editor.buffer().read(cx).snapshot(cx); - cx.background_executor().spawn(async move { + Some(cx.background_spawn(async move { let mut ranges = Vec::new(); - let buffer_ranges = - vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]; let query = buffer.text_for_range(selection.range()).collect::(); - for range in buffer_ranges { + for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] { for (search_buffer, search_range, excerpt_id) in buffer.range_to_buffer_ranges(range) { @@ -4862,7 +4869,7 @@ impl Editor { } } ranges - }) + })) }) .log_err() else { @@ -5154,6 +5161,7 @@ impl Editor { .contains(&target.to_display_point(&position_map.snapshot).row()) || !self.edit_prediction_requires_modifier() { + self.unfold_ranges(&[target..target], true, false, cx); // Note that this is also done in vim's handler of the Tab action. self.change_selections( Some(Autoscroll::newest()), @@ -5375,6 +5383,8 @@ impl Editor { self.update_edit_prediction_preview(&modifiers, window, cx); } + self.update_selection_mode(&modifiers, position_map, window, cx); + let mouse_position = window.mouse_position(); if !position_map.text_hitbox.is_hovered(window) { return; @@ -5389,6 +5399,32 @@ impl Editor { ) } + fn update_selection_mode( + &mut self, + modifiers: &Modifiers, + position_map: &PositionMap, + window: &mut Window, + cx: &mut Context, + ) { + if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() { + return; + } + + let mouse_position = window.mouse_position(); + let point_for_position = position_map.point_for_position(mouse_position); + let position = point_for_position.previous_valid; + + self.select( + SelectPhase::BeginColumnar { + position, + reset: false, + goal_column: point_for_position.exact_unclipped.column(), + }, + window, + cx, + ); + } + fn update_edit_prediction_preview( &mut self, modifiers: &Modifiers, @@ -6453,19 +6489,14 @@ impl Editor { } fn render_context_menu_aside( - &self, - style: &EditorStyle, + &mut self, max_size: Size, + window: &mut Window, cx: &mut Context, ) -> Option { - self.context_menu.borrow().as_ref().and_then(|menu| { + self.context_menu.borrow_mut().as_mut().and_then(|menu| { if menu.visible() { - menu.render_aside( - style, - max_size, - self.workspace.as_ref().map(|(w, _)| w.clone()), - cx, - ) + menu.render_aside(self, max_size, window, cx) } else { None } @@ -10684,13 +10715,12 @@ impl Editor { return; } let new_rows = - cx.background_executor() - .spawn({ - let snapshot = display_snapshot.clone(); - async move { - Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max()) - } - }) + cx.background_spawn({ + let snapshot = display_snapshot.clone(); + async move { + Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max()) + } + }) .await; let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone()); @@ -11478,7 +11508,7 @@ impl Editor { HoverLink::InlayHint(lsp_location, server_id) => { let computation = self.compute_target_location(lsp_location, server_id, window, cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let location = computation.await?; Ok(TargetTaskResult::Location(location)) }) @@ -14311,14 +14341,14 @@ impl Editor { &self, window: &mut Window, cx: &mut App, - ) -> BTreeMap { + ) -> BTreeMap { let snapshot = self.snapshot(window, cx); let mut used_highlight_orders = HashMap::default(); self.highlighted_rows .iter() .flat_map(|(_, highlighted_rows)| highlighted_rows.iter()) .fold( - BTreeMap::::new(), + BTreeMap::::new(), |mut unique_rows, highlight| { let start = highlight.range.start.to_display_point(&snapshot); let end = highlight.range.end.to_display_point(&snapshot); @@ -14335,7 +14365,7 @@ impl Editor { used_highlight_orders.entry(row).or_insert(highlight.index); if highlight.index >= *used_index { *used_index = highlight.index; - unique_rows.insert(DisplayRow(row), highlight.color); + unique_rows.insert(DisplayRow(row), highlight.color.into()); } } unique_rows @@ -15463,8 +15493,14 @@ impl Editor { if !self.hover_state.focused(window, cx) { hide_hover(self, cx); } - - self.hide_context_menu(window, cx); + if !self + .context_menu + .borrow() + .as_ref() + .is_some_and(|context_menu| context_menu.focused(window, cx)) + { + self.hide_context_menu(window, cx); + } self.discard_inline_completion(false, cx); cx.emit(EditorEvent::Blurred); cx.notify(); @@ -15599,7 +15635,9 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None { + if !self.is_singleton(cx) + || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None + { return; } let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() else { @@ -16124,7 +16162,7 @@ fn snippet_completions( let scope = language.map(|language| language.default_scope()); let executor = cx.background_executor().clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let classifier = CharClassifier::new(scope).for_completion(true); let mut last_word = chars .chars() @@ -16211,7 +16249,7 @@ fn snippet_completions( documentation: snippet .description .clone() - .map(CompletionDocumentation::SingleLine), + .map(|description| CompletionDocumentation::SingleLine(description.into())), lsp_completion: lsp::CompletionItem { label: snippet.prefix.first().unwrap().clone(), kind: Some(CompletionItemKind::SNIPPET), @@ -16254,7 +16292,7 @@ impl CompletionProvider for Entity { self.update(cx, |project, cx| { let snippets = snippet_completions(project, buffer, buffer_position, cx); let project_completions = project.completions(buffer, buffer_position, options, cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut completions = project_completions.await?; let snippets_completions = snippets.await?; completions.extend(snippets_completions); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ea5c2786466a53..277bb629866b98 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -21,8 +21,9 @@ use crate::{ GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt, - ToPoint, ToggleFold, ToggleStagedSelectedDiffHunks, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, - GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, + ToPoint, ToggleFold, ToggleStagedSelectedDiffHunks, COLUMNAR_SELECTION_MODIFIERS, + CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, }; use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus}; use client::ParticipantIndex; @@ -31,7 +32,7 @@ use file_icons::FileIcons; use git::{blame::BlameEntry, Oid}; use gpui::{ anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, pattern_slash, - point, px, quad, relative, size, solid_color, svg, transparent_black, Action, AnyElement, App, + point, px, quad, relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Keystroke, Length, @@ -92,7 +93,6 @@ enum DisplayDiffHunk { display_row_range: Range, multi_buffer_range: Range, status: DiffHunkStatus, - contains_expanded: bool, }, } @@ -520,6 +520,7 @@ impl EditorElement { if editor.hover_state.focused(window, cx) { return; } + editor.handle_modifiers_changed(event.modifiers, &position_map, window, cx); }) } @@ -599,7 +600,7 @@ impl EditorElement { let point_for_position = position_map.point_for_position(event.position); let position = point_for_position.previous_valid; - if modifiers.shift && modifiers.alt { + if modifiers == COLUMNAR_SELECTION_MODIFIERS { editor.select( SelectPhase::BeginColumnar { position, @@ -1584,11 +1585,6 @@ impl EditorElement { if hunk_display_end.column() > 0 { end_row.0 += 1; } - let start_row = hunk_display_start.row(); - let contains_expanded = snapshot - .row_infos(start_row) - .take(end_row.0 as usize - start_row.0 as usize) - .any(|row_info| row_info.diff_status.is_some()); DisplayDiffHunk::Unfolded { status: hunk.status(), diff_base_byte_range: hunk.diff_base_byte_range, @@ -1598,7 +1594,6 @@ impl EditorElement { hunk.buffer_id, hunk.buffer_range, ), - contains_expanded, } }; @@ -3525,9 +3520,11 @@ impl EditorElement { available_within_viewport.right - px(1.), MENU_ASIDE_MAX_WIDTH, ); - let Some(mut aside) = - self.render_context_menu_aside(size(max_width, max_height - POPOVER_Y_PADDING), cx) - else { + let Some(mut aside) = self.render_context_menu_aside( + size(max_width, max_height - POPOVER_Y_PADDING), + window, + cx, + ) else { return; }; aside.layout_as_root(AvailableSpace::min_size(), window, cx); @@ -3549,7 +3546,7 @@ impl EditorElement { ), ) - POPOVER_Y_PADDING, ); - let Some(mut aside) = self.render_context_menu_aside(max_size, cx) else { + let Some(mut aside) = self.render_context_menu_aside(max_size, window, cx) else { return; }; let actual_size = aside.layout_as_root(AvailableSpace::min_size(), window, cx); @@ -3590,7 +3587,7 @@ impl EditorElement { // Skip drawing if it doesn't fit anywhere. if let Some((aside, position)) = positioned_aside { - window.defer_draw(aside, position, 1); + window.defer_draw(aside, position, 2); } } @@ -3611,14 +3608,14 @@ impl EditorElement { fn render_context_menu_aside( &self, max_size: Size, - + window: &mut Window, cx: &mut App, ) -> Option { if max_size.width < px(100.) || max_size.height < px(12.) { None } else { self.editor.update(cx, |editor, cx| { - editor.render_context_menu_aside(&self.style, max_size, cx) + editor.render_context_menu_aside(max_size, window, cx) }) } } @@ -4423,7 +4420,7 @@ impl EditorElement { window.paint_quad(fill(Bounds { origin, size }, color)); }; - let mut current_paint: Option<(Hsla, Range)> = None; + let mut current_paint: Option<(gpui::Background, Range)> = None; for (&new_row, &new_background) in &layout.highlighted_rows { match &mut current_paint { Some((current_background, current_range)) => { @@ -4620,17 +4617,11 @@ impl EditorElement { } } - fn paint_diff_hunk_gutter_indicators( - layout: &mut EditorLayout, - window: &mut Window, - cx: &mut App, - ) { + fn paint_diff_hunks(layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { if layout.display_hunks.is_empty() { return; } - let corners = Corners::all(px(0.)); - let line_height = layout.position_map.line_height; window.paint_layer(layout.gutter_hitbox.bounds, |window| { for (hunk, hitbox) in &layout.display_hunks { @@ -4644,41 +4635,36 @@ impl EditorElement { ); Some(( hunk_bounds, - cx.theme().colors().version_control_modified.opacity(0.7), - corners, + cx.theme().status().modified, + Corners::all(px(0.)), &DiffHunkSecondaryStatus::None, - false, )) } DisplayDiffHunk::Unfolded { status, display_row_range, - contains_expanded, .. } => hitbox.as_ref().map(|hunk_hitbox| match status { DiffHunkStatus::Added(secondary_status) => ( hunk_hitbox.bounds, - cx.theme().colors().version_control_added.opacity(0.7), - corners, + cx.theme().status().created, + Corners::all(px(0.)), secondary_status, - *contains_expanded, ), DiffHunkStatus::Modified(secondary_status) => ( hunk_hitbox.bounds, - cx.theme().colors().version_control_modified.opacity(0.7), - corners, + cx.theme().status().modified, + Corners::all(px(0.)), secondary_status, - *contains_expanded, ), DiffHunkStatus::Removed(secondary_status) if !display_row_range.is_empty() => { ( hunk_hitbox.bounds, - cx.theme().colors().version_control_deleted.opacity(0.7), - corners, + cx.theme().status().deleted, + Corners::all(px(0.)), secondary_status, - *contains_expanded, ) } DiffHunkStatus::Removed(secondary_status) => ( @@ -4689,34 +4675,23 @@ impl EditorElement { ), size(hunk_hitbox.size.width * px(2.), hunk_hitbox.size.height), ), - cx.theme().colors().version_control_deleted.opacity(0.7), + cx.theme().status().deleted, Corners::all(1. * line_height), secondary_status, - *contains_expanded, ), }), }; - if let Some(( - hunk_bounds, - background_color, - corner_radii, - secondary_status, - contains_expanded, - )) = hunk_to_paint + if let Some((hunk_bounds, mut background_color, corner_radii, secondary_status)) = + hunk_to_paint { - let background = if *secondary_status != DiffHunkSecondaryStatus::None - && contains_expanded - { - pattern_slash(background_color, line_height.0 / 2.5) - } else { - solid_color(background_color) - }; - + if *secondary_status != DiffHunkSecondaryStatus::None { + background_color.a *= 0.6; + } window.paint_quad(quad( hunk_bounds, corner_radii, - background, + background_color, Edges::default(), transparent_black(), )); @@ -4847,7 +4822,7 @@ impl EditorElement { ) }); if show_git_gutter { - Self::paint_diff_hunk_gutter_indicators(layout, window, cx) + Self::paint_diff_hunks(layout, window, cx) } let highlight_width = 0.275 * layout.position_map.line_height; @@ -5387,8 +5362,7 @@ impl EditorElement { Some(cx.spawn_in(window, |editor, mut cx| async move { let scrollbar_size = scrollbar_layout.hitbox.size; let scrollbar_markers = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let max_point = snapshot.display_snapshot.buffer_snapshot.max_point(); let mut marker_quads = Vec::new(); if scrollbar_settings.git_diff { @@ -5406,15 +5380,9 @@ impl EditorElement { end_display_row.0 -= 1; } let color = match &hunk.status() { - DiffHunkStatus::Added(_) => { - theme.colors().version_control_added - } - DiffHunkStatus::Modified(_) => { - theme.colors().version_control_modified - } - DiffHunkStatus::Removed(_) => { - theme.colors().version_control_deleted - } + DiffHunkStatus::Added(_) => theme.status().created, + DiffHunkStatus::Modified(_) => theme.status().modified, + DiffHunkStatus::Removed(_) => theme.status().deleted, }; ColoredRange { start: start_display_row, @@ -6997,17 +6965,39 @@ impl Element for EditorElement { ) }; - let mut highlighted_rows = self - .editor - .update(cx, |editor, cx| editor.highlighted_display_rows(window, cx)); + let (mut highlighted_rows, distinguish_unstaged_hunks) = + self.editor.update(cx, |editor, cx| { + ( + editor.highlighted_display_rows(window, cx), + editor.distinguish_unstaged_diff_hunks, + ) + }); for (ix, row_info) in row_infos.iter().enumerate() { let background = match row_info.diff_status { - Some(DiffHunkStatus::Added(_)) => { - cx.theme().colors().version_control_added_background + Some(DiffHunkStatus::Added(secondary_status)) => { + let color = style.status.created_background; + match secondary_status { + DiffHunkSecondaryStatus::HasSecondaryHunk + | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk + if distinguish_unstaged_hunks => + { + pattern_slash(color, line_height.0 / 4.0) + } + _ => color.into(), + } } - Some(DiffHunkStatus::Removed(_)) => { - cx.theme().colors().version_control_deleted_background + Some(DiffHunkStatus::Removed(secondary_status)) => { + let color = style.status.deleted_background; + match secondary_status { + DiffHunkSecondaryStatus::HasSecondaryHunk + | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk + if distinguish_unstaged_hunks => + { + pattern_slash(color, line_height.0 / 4.0) + } + _ => color.into(), + } } _ => continue, }; @@ -7891,7 +7881,7 @@ pub struct EditorLayout { indent_guides: Option>, visible_display_row_range: Range, active_rows: BTreeMap, - highlighted_rows: BTreeMap, + highlighted_rows: BTreeMap, line_elements: SmallVec<[AnyElement; 1]>, line_numbers: Arc>, display_hunks: Vec<(DisplayDiffHunk, Option)>, diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index d8ff8c359fc024..268738ab25d739 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -4,7 +4,7 @@ use git::{ blame::{Blame, BlameEntry}, parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid, }; -use gpui::{App, Context, Entity, Subscription, Task}; +use gpui::{App, AppContext as _, Context, Entity, Subscription, Task}; use http_client::HttpClient; use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown}; use multi_buffer::RowInfo; @@ -360,8 +360,7 @@ impl GitBlame { self.task = cx.spawn(|this, mut cx| async move { let result = cx - .background_executor() - .spawn({ + .background_spawn({ let snapshot = snapshot.clone(); async move { let Some(Blame { @@ -549,7 +548,7 @@ async fn parse_markdown(text: &str, language_registry: &Arc) - #[cfg(test)] mod tests { use super::*; - use gpui::{AppContext as _, Context}; + use gpui::Context; use language::{Point, Rope}; use project::FakeFs; use rand::prelude::*; diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 128ee45341682e..bec413329a2b4d 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,7 +1,7 @@ use crate::{ display_map::{invisibles::is_invisible, InlayOffset, ToDisplayPoint}, hover_links::{InlayHighlight, RangeInEditor}, - scroll::ScrollAmount, + scroll::{Autoscroll, ScrollAmount}, Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot, Hover, }; @@ -18,12 +18,14 @@ use markdown::{Markdown, MarkdownStyle}; use multi_buffer::ToOffset; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart}; use settings::Settings; -use std::rc::Rc; use std::{borrow::Cow, cell::RefCell}; use std::{ops::Range, sync::Arc, time::Duration}; +use std::{path::PathBuf, rc::Rc}; use theme::ThemeSettings; use ui::{prelude::*, theme_is_transparent, Scrollbar, ScrollbarState}; +use url::Url; use util::TryFutureExt; +use workspace::Workspace; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; @@ -356,7 +358,15 @@ fn show_hover( }, ..Default::default() }; - Markdown::new_text(text, markdown_style.clone(), None, None, window, cx) + Markdown::new_text( + SharedString::new(text), + markdown_style.clone(), + None, + None, + window, + cx, + ) + .open_url(open_markdown_url) }) .ok(); @@ -558,69 +568,122 @@ async fn parse_blocks( let rendered_block = cx .new_window_entity(|window, cx| { - let settings = ThemeSettings::get_global(cx); - let ui_font_family = settings.ui_font.family.clone(); - let ui_font_fallbacks = settings.ui_font.fallbacks.clone(); - let buffer_font_family = settings.buffer_font.family.clone(); - let buffer_font_fallbacks = settings.buffer_font.fallbacks.clone(); - - let mut base_text_style = window.text_style(); - base_text_style.refine(&TextStyleRefinement { - font_family: Some(ui_font_family.clone()), - font_fallbacks: ui_font_fallbacks, - color: Some(cx.theme().colors().editor_foreground), - ..Default::default() - }); - - let markdown_style = MarkdownStyle { - base_text_style, - code_block: StyleRefinement::default().my(rems(1.)).font_buffer(cx), - inline_code: TextStyleRefinement { - background_color: Some(cx.theme().colors().background), - font_family: Some(buffer_font_family), - font_fallbacks: buffer_font_fallbacks, - ..Default::default() - }, - rule_color: cx.theme().colors().border, - block_quote_border_color: Color::Muted.color(cx), - block_quote: TextStyleRefinement { - color: Some(Color::Muted.color(cx)), - ..Default::default() - }, - link: TextStyleRefinement { - color: Some(cx.theme().colors().editor_foreground), - underline: Some(gpui::UnderlineStyle { - thickness: px(1.), - color: Some(cx.theme().colors().editor_foreground), - wavy: false, - }), - ..Default::default() - }, - syntax: cx.theme().syntax().clone(), - selection_background_color: { cx.theme().players().local().selection }, - - heading: StyleRefinement::default() - .font_weight(FontWeight::BOLD) - .text_base() - .mt(rems(1.)) - .mb_0(), - }; - Markdown::new( - combined_text, - markdown_style.clone(), + combined_text.into(), + hover_markdown_style(window, cx), Some(language_registry.clone()), fallback_language_name, window, cx, ) .copy_code_block_buttons(false) + .open_url(open_markdown_url) }) .ok(); rendered_block } +pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { + let settings = ThemeSettings::get_global(cx); + let ui_font_family = settings.ui_font.family.clone(); + let ui_font_fallbacks = settings.ui_font.fallbacks.clone(); + let buffer_font_family = settings.buffer_font.family.clone(); + let buffer_font_fallbacks = settings.buffer_font.fallbacks.clone(); + + let mut base_text_style = window.text_style(); + base_text_style.refine(&TextStyleRefinement { + font_family: Some(ui_font_family.clone()), + font_fallbacks: ui_font_fallbacks, + color: Some(cx.theme().colors().editor_foreground), + ..Default::default() + }); + MarkdownStyle { + base_text_style, + code_block: StyleRefinement::default().my(rems(1.)).font_buffer(cx), + inline_code: TextStyleRefinement { + background_color: Some(cx.theme().colors().background), + font_family: Some(buffer_font_family), + font_fallbacks: buffer_font_fallbacks, + ..Default::default() + }, + rule_color: cx.theme().colors().border, + block_quote_border_color: Color::Muted.color(cx), + block_quote: TextStyleRefinement { + color: Some(Color::Muted.color(cx)), + ..Default::default() + }, + link: TextStyleRefinement { + color: Some(cx.theme().colors().editor_foreground), + underline: Some(gpui::UnderlineStyle { + thickness: px(1.), + color: Some(cx.theme().colors().editor_foreground), + wavy: false, + }), + ..Default::default() + }, + syntax: cx.theme().syntax().clone(), + selection_background_color: { cx.theme().players().local().selection }, + + heading: StyleRefinement::default() + .font_weight(FontWeight::BOLD) + .text_base() + .mt(rems(1.)) + .mb_0(), + } +} + +pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App) { + if let Ok(uri) = Url::parse(&link) { + if uri.scheme() == "file" { + if let Some(workspace) = window.root::().flatten() { + workspace.update(cx, |workspace, cx| { + let task = + workspace.open_abs_path(PathBuf::from(uri.path()), false, window, cx); + + cx.spawn_in(window, |_, mut cx| async move { + let item = task.await?; + // Ruby LSP uses URLs with #L1,1-4,4 + // we'll just take the first number and assume it's a line number + let Some(fragment) = uri.fragment() else { + return anyhow::Ok(()); + }; + let mut accum = 0u32; + for c in fragment.chars() { + if c >= '0' && c <= '9' && accum < u32::MAX / 2 { + accum *= 10; + accum += c as u32 - '0' as u32; + } else if accum > 0 { + break; + } + } + if accum == 0 { + return Ok(()); + } + let Some(editor) = cx.update(|_, cx| item.act_as::(cx))? else { + return Ok(()); + }; + editor.update_in(&mut cx, |editor, window, cx| { + editor.change_selections( + Some(Autoscroll::fit()), + window, + cx, + |selections| { + selections.select_ranges([text::Point::new(accum - 1, 0) + ..text::Point::new(accum - 1, 0)]); + }, + ); + }) + }) + .detach_and_log_err(cx); + }); + return; + } + } + } + cx.open_url(&link); +} + #[derive(Default, Debug)] pub struct HoverState { pub info_popovers: Vec, diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 03dac81d653be3..63d1deec8d2900 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -1,7 +1,7 @@ use collections::{HashMap, HashSet}; use git::diff::DiffHunkStatus; use gpui::{ - Action, AppContext, Corner, CursorStyle, Focusable as _, Hsla, Model, MouseButton, + Action, AppContext as _, Corner, CursorStyle, Focusable as _, Hsla, Model, MouseButton, Subscription, Task, }; use language::{Buffer, BufferId, Point}; @@ -372,7 +372,7 @@ impl Editor { self.diff_map .hunk_update_tasks - .insert(None, cx.background_executor().spawn(new_toggle_task)); + .insert(None, cx.background_spawn(new_toggle_task)); } pub(super) fn expand_diff_hunk( @@ -1089,10 +1089,9 @@ impl Editor { .ok(); }); - diff_map.hunk_update_tasks.insert( - Some(buffer_id), - cx.background_executor().spawn(new_sync_task), - ); + diff_map + .hunk_update_tasks + .insert(Some(buffer_id), cx.background_spawn(new_sync_task)); } fn go_to_subsequent_hunk( diff --git a/crates/editor/src/indent_guides.rs b/crates/editor/src/indent_guides.rs index 719fadce49ce78..7068a94ac4211a 100644 --- a/crates/editor/src/indent_guides.rs +++ b/crates/editor/src/indent_guides.rs @@ -1,7 +1,7 @@ use std::{ops::Range, time::Duration}; use collections::HashSet; -use gpui::{App, Context, Task, Window}; +use gpui::{App, AppContext as _, Context, Task, Window}; use language::language_settings::language_settings; use multi_buffer::{IndentGuide, MultiBufferRow}; use text::{LineIndent, Point}; @@ -102,9 +102,7 @@ impl Editor { let snapshot = snapshot.clone(); - let task = cx - .background_executor() - .spawn(resolve_indented_range(snapshot, cursor_row)); + let task = cx.background_spawn(resolve_indented_range(snapshot, cursor_row)); // Try to resolve the indent in a short amount of time, otherwise move it to a background task. match cx @@ -115,7 +113,7 @@ impl Editor { Err(future) => { state.pending_refresh = Some(cx.spawn_in(window, |editor, mut cx| async move { - let result = cx.background_executor().spawn(future).await; + let result = cx.background_spawn(future).await; editor .update(&mut cx, |editor, _| { editor.active_indent_guides_state.active_indent_range = result; diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index e6789c3a3abf85..39186b2b2815dc 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -19,7 +19,7 @@ use crate::{ use anyhow::Context as _; use clock::Global; use futures::future; -use gpui::{AsyncApp, Context, Entity, Task, Window}; +use gpui::{AppContext as _, AsyncApp, Context, Entity, Task, Window}; use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; use parking_lot::RwLock; use project::{InlayHint, ResolveState}; @@ -996,19 +996,17 @@ fn fetch_and_update_hints( let background_task_buffer_snapshot = buffer_snapshot.clone(); let background_fetch_range = fetch_range.clone(); - let new_update = cx - .background_executor() - .spawn(async move { - calculate_hint_updates( - query.excerpt_id, - invalidate, - background_fetch_range, - new_hints, - &background_task_buffer_snapshot, - cached_excerpt_hints, - &visible_hints, - ) - }) + let new_update = cx.background_spawn(async move { + calculate_hint_updates( + query.excerpt_id, + invalidate, + background_fetch_range, + new_hints, + &background_task_buffer_snapshot, + cached_excerpt_hints, + &visible_hints, + ) + }) .await; if let Some(new_update) = new_update { log::debug!( diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 7f40092f0b71ff..24f94027ca5cfa 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1073,6 +1073,9 @@ impl SerializableItem for Editor { buffer.set_language(Some(language), cx); } buffer.set_text(contents, cx); + if let Some(entry) = buffer.peek_undo_stack() { + buffer.forget_transaction(entry.transaction_id()); + } })?; cx.update(|window, cx| { @@ -1127,6 +1130,9 @@ impl SerializableItem for Editor { ); } buffer.set_text(buffer_text, cx); + if let Some(entry) = buffer.peek_undo_stack() { + buffer.forget_transaction(entry.transaction_id()); + } })?; } @@ -1219,28 +1225,27 @@ impl SerializableItem for Editor { let snapshot = buffer.read(cx).snapshot(); Some(cx.spawn_in(window, |_this, cx| async move { - cx.background_executor() - .spawn(async move { - let (contents, language) = if serialize_dirty_buffers && is_dirty { - let contents = snapshot.text(); - let language = snapshot.language().map(|lang| lang.name().to_string()); - (Some(contents), language) - } else { - (None, None) - }; + cx.background_spawn(async move { + let (contents, language) = if serialize_dirty_buffers && is_dirty { + let contents = snapshot.text(); + let language = snapshot.language().map(|lang| lang.name().to_string()); + (Some(contents), language) + } else { + (None, None) + }; - let editor = SerializedEditor { - abs_path, - contents, - language, - mtime, - }; - DB.save_serialized_editor(item_id, workspace_id, editor) - .await - .context("failed to save serialized editor") - }) - .await - .context("failed to save contents of buffer")?; + let editor = SerializedEditor { + abs_path, + contents, + language, + mtime, + }; + DB.save_serialized_editor(item_id, workspace_id, editor) + .await + .context("failed to save serialized editor") + }) + .await + .context("failed to save contents of buffer")?; Ok(()) })) @@ -1534,7 +1539,7 @@ impl SearchableItem for Editor { ranges.iter().cloned().collect::>() }); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut ranges = Vec::new(); let search_within_ranges = if search_within_ranges.is_empty() { diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index e8d2ed05d434b6..d8e9331c4b4ca7 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -247,7 +247,7 @@ impl EditorDb { r#" DELETE FROM editor_selections WHERE editor_id = ?1 AND workspace_id = ?2; -INSERT INTO editor_selections (editor_id, workspace_id, start, end) +INSERT OR IGNORE INTO editor_selections (editor_id, workspace_id, start, end) VALUES {placeholders}; "# ); diff --git a/crates/editor/src/tasks.rs b/crates/editor/src/tasks.rs index 13e3299961cd64..8444849b5b4121 100644 --- a/crates/editor/src/tasks.rs +++ b/crates/editor/src/tasks.rs @@ -1,6 +1,6 @@ use crate::Editor; -use gpui::{App, Task as AsyncTask, Window}; +use gpui::{App, AppContext as _, Task as AsyncTask, Window}; use project::Location; use task::{TaskContext, TaskVariables, VariableName}; use text::{ToOffset, ToPoint}; @@ -88,7 +88,6 @@ pub fn task_context( }; editor.update(cx, |editor, cx| { let context_task = task_context_with_editor(editor, window, cx); - cx.background_executor() - .spawn(async move { context_task.await.unwrap_or_default() }) + cx.background_spawn(async move { context_task.await.unwrap_or_default() }) }) } diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs index 1e3a44cac06351..513011d08314d4 100644 --- a/crates/extension/src/extension_host_proxy.rs +++ b/crates/extension/src/extension_host_proxy.rs @@ -96,6 +96,8 @@ impl ExtensionHostProxy { } pub trait ExtensionThemeProxy: Send + Sync + 'static { + fn set_extensions_loaded(&self); + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>>; fn remove_user_themes(&self, themes: Vec); @@ -123,6 +125,14 @@ pub trait ExtensionThemeProxy: Send + Sync + 'static { } impl ExtensionThemeProxy for ExtensionHostProxy { + fn set_extensions_loaded(&self) { + let Some(proxy) = self.theme_proxy.read().clone() else { + return; + }; + + proxy.set_extensions_loaded() + } + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>> { let Some(proxy) = self.theme_proxy.read().clone() else { return Task::ready(Ok(Vec::new())); diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index a6f62991361e19..9fc61b10234c22 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -324,10 +324,7 @@ impl ExtensionStore { load_initial_extensions.await; let mut index_changed = false; - let mut debounce_timer = cx - .background_executor() - .spawn(futures::future::pending()) - .fuse(); + let mut debounce_timer = cx.background_spawn(futures::future::pending()).fuse(); loop { select_biased! { _ = debounce_timer => { @@ -370,7 +367,7 @@ impl ExtensionStore { // Watch the installed extensions directory for changes. Whenever changes are // detected, rebuild the extension index, and load/unload any extensions that // have been added, removed, or modified. - this.tasks.push(cx.background_executor().spawn({ + this.tasks.push(cx.background_spawn({ let fs = this.fs.clone(); let reload_tx = this.reload_tx.clone(); let installed_dir = this.installed_dir.clone(); @@ -886,20 +883,19 @@ impl ExtensionStore { } }); - cx.background_executor() - .spawn({ - let extension_source_path = extension_source_path.clone(); - async move { - builder - .compile_extension( - &extension_source_path, - &mut extension_manifest, - CompileExtensionOptions { release: false }, - ) - .await - } - }) - .await?; + cx.background_spawn({ + let extension_source_path = extension_source_path.clone(); + async move { + builder + .compile_extension( + &extension_source_path, + &mut extension_manifest, + CompileExtensionOptions { release: false }, + ) + .await + } + }) + .await?; let output_path = &extensions_dir.join(extension_id.as_ref()); if let Some(metadata) = fs.metadata(output_path).await? { @@ -937,7 +933,7 @@ impl ExtensionStore { }; cx.notify(); - let compile = cx.background_executor().spawn(async move { + let compile = cx.background_spawn(async move { let mut manifest = ExtensionManifest::load(fs, &path).await?; builder .compile_extension( @@ -1192,35 +1188,33 @@ impl ExtensionStore { cx.emit(Event::ExtensionsUpdated); cx.spawn(|this, mut cx| async move { - cx.background_executor() - .spawn({ - let fs = fs.clone(); - async move { - for theme_path in themes_to_add.into_iter() { - proxy - .load_user_theme(theme_path, fs.clone()) - .await - .log_err(); - } + cx.background_spawn({ + let fs = fs.clone(); + async move { + for theme_path in themes_to_add.into_iter() { + proxy + .load_user_theme(theme_path, fs.clone()) + .await + .log_err(); + } - for (icon_theme_path, icons_root_path) in icon_themes_to_add.into_iter() { + for (icon_theme_path, icons_root_path) in icon_themes_to_add.into_iter() { + proxy + .load_icon_theme(icon_theme_path, icons_root_path, fs.clone()) + .await + .log_err(); + } + + for snippets_path in &snippets_to_add { + if let Some(snippets_contents) = fs.load(snippets_path).await.log_err() { proxy - .load_icon_theme(icon_theme_path, icons_root_path, fs.clone()) - .await + .register_snippet(snippets_path, &snippets_contents) .log_err(); } - - for snippets_path in &snippets_to_add { - if let Some(snippets_contents) = fs.load(snippets_path).await.log_err() - { - proxy - .register_snippet(snippets_path, &snippets_contents) - .log_err(); - } - } } - }) - .await; + } + }) + .await; let mut wasm_extensions = Vec::new(); for extension in extension_entries { @@ -1290,6 +1284,7 @@ impl ExtensionStore { } this.wasm_extensions.extend(wasm_extensions); + this.proxy.set_extensions_loaded(); this.proxy.reload_current_theme(cx); this.proxy.reload_current_icon_theme(cx); }) @@ -1303,7 +1298,7 @@ impl ExtensionStore { let extensions_dir = self.installed_dir.clone(); let index_path = self.index_path.clone(); let proxy = self.proxy.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let start_time = Instant::now(); let mut index = ExtensionIndex::default(); @@ -1493,7 +1488,7 @@ impl ExtensionStore { return Task::ready(Err(anyhow!("extension no longer installed"))); }; let fs = self.fs.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { const EXTENSION_TOML: &str = "extension.toml"; const EXTENSION_WASM: &str = "extension.wasm"; const CONFIG_TOML: &str = "config.toml"; diff --git a/crates/feedback/src/system_specs.rs b/crates/feedback/src/system_specs.rs index 0bd16d22addf45..2d9a742140d23b 100644 --- a/crates/feedback/src/system_specs.rs +++ b/crates/feedback/src/system_specs.rs @@ -1,5 +1,5 @@ use client::telemetry; -use gpui::{App, Task, Window}; +use gpui::{App, AppContext as _, Task, Window}; use human_bytes::human_bytes; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use serde::Serialize; @@ -44,7 +44,7 @@ impl SystemSpecs { None }; - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let os_version = telemetry::os_version(); SystemSpecs { app_version, diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 627093244d70ab..c15f965db08600 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -34,7 +34,7 @@ use std::{ }; use text::Point; use ui::{ - prelude::*, ContextMenu, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, + prelude::*, ContextMenu, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, }; use util::{paths::PathWithPosition, post_inc, ResultExt}; @@ -127,7 +127,7 @@ impl FileFinder { let abs_path = abs_path?; if project.is_local() { let fs = fs.clone(); - Some(cx.background_executor().spawn(async move { + Some(cx.background_spawn(async move { if fs.is_file(&abs_path).await { Some(FoundPath::new(project_path, Some(abs_path))) } else { @@ -1448,11 +1448,7 @@ impl PickerDelegate for FileFinderDelegate { ) } - fn render_footer( - &self, - window: &mut Window, - cx: &mut Context>, - ) -> Option { + fn render_footer(&self, _: &mut Window, cx: &mut Context>) -> Option { let context = self.focus_handle.clone(); Some( h_flex() @@ -1463,11 +1459,9 @@ impl PickerDelegate for FileFinderDelegate { .border_t_1() .border_color(cx.theme().colors().border_variant) .child( - Button::new("open-selection", "Open") - .key_binding(KeyBinding::for_action(&menu::Confirm, window, cx)) - .on_click(|_, window, cx| { - window.dispatch_action(menu::Confirm.boxed_clone(), cx) - }), + Button::new("open-selection", "Open").on_click(|_, window, cx| { + window.dispatch_action(menu::Confirm.boxed_clone(), cx) + }), ) .child( PopoverMenu::new("menu-popover") @@ -1475,14 +1469,8 @@ impl PickerDelegate for FileFinderDelegate { .attach(gpui::Corner::TopRight) .anchor(gpui::Corner::BottomRight) .trigger( - Button::new("actions-trigger", "Split Options") - .selected_label_color(Color::Accent) - .key_binding(KeyBinding::for_action_in( - &ToggleMenu, - &context, - window, - cx, - )), + Button::new("actions-trigger", "Split…") + .selected_label_color(Color::Accent), ) .menu({ move |window, cx| { diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 24e4b18ca1447f..2c976a7ee0fdb5 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -337,7 +337,7 @@ impl GitPanel { fn serialize(&mut self, cx: &mut Context) { let width = self.width; - self.pending_serialization = cx.background_executor().spawn( + self.pending_serialization = cx.background_spawn( async move { KEY_VALUE_STORE .write_kvp( @@ -1055,8 +1055,7 @@ impl GitPanel { let task = if self.has_staged_changes() { // Repository serializes all git operations, so we can just send a commit immediately let commit_task = active_repository.read(cx).commit(message.into(), None); - cx.background_executor() - .spawn(async move { commit_task.await? }) + cx.background_spawn(async move { commit_task.await? }) } else { let changed_files = self .entries diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 87098c374c0cc9..682eb5a28d378c 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -7,7 +7,7 @@ use editor::{scroll::Autoscroll, Editor, EditorEvent, ToPoint}; use feature_flags::FeatureFlagViewExt; use futures::StreamExt; use gpui::{ - actions, AnyElement, AnyView, App, AppContext, AsyncWindowContext, Entity, EventEmitter, + actions, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, }; use language::{Anchor, Buffer, Capability, OffsetRangeExt, Point}; diff --git a/crates/git_ui/src/repository_selector.rs b/crates/git_ui/src/repository_selector.rs index 8c27c605194ba9..36af1189b5fddd 100644 --- a/crates/git_ui/src/repository_selector.rs +++ b/crates/git_ui/src/repository_selector.rs @@ -177,8 +177,7 @@ impl PickerDelegate for RepositorySelectorDelegate { cx.spawn_in(window, |this, mut cx| async move { let filtered_repositories = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { if query.is_empty() { all_repositories } else { diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index e3adc8ded9d7f6..daec9440a4eba3 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -607,25 +607,6 @@ impl Default for Background { } } -impl Background { - /// Gets the color of the background if there is one. - pub fn color(&self) -> Option { - match self.tag { - BackgroundTag::Solid => Some(self.solid), - BackgroundTag::LinearGradient => None, - BackgroundTag::PatternSlash => Some(self.solid), - } - } -} - -/// Creates a background with a solid color -pub fn solid_color(color: impl Into) -> Background { - Background { - solid: color.into(), - ..Default::default() - } -} - /// Creates a hash pattern background pub fn pattern_slash(color: Hsla, thickness: f32) -> Background { Background { diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index f96d3d1721fa86..f2edf35a5ae641 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -414,7 +414,7 @@ impl Modifiers { } } - /// Returns [`Modifiers`] with just control. + /// Returns [`Modifiers`] with just alt. pub fn alt() -> Modifiers { Modifiers { alt: true, diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index a440ae680e03d2..75f53248cb4150 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -252,7 +252,7 @@ impl SerializableItem for ImageView { let workspace_id = workspace.database_id()?; let image_path = self.image_item.read(cx).file.as_local()?.abs_path(cx); - Some(cx.background_executor().spawn({ + Some(cx.background_spawn({ async move { IMAGE_VIEWER .save_image_path(item_id, workspace_id, image_path) diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 5482f7a097c81b..6afcced5f6a812 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -2,7 +2,7 @@ use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; use editor::scroll::Autoscroll; use editor::Editor; -use gpui::{actions, App, Context, Window}; +use gpui::{actions, App, AppContext as _, Context, Window}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; @@ -87,7 +87,7 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap let now = now.time(); let entry_heading = heading_entry(now, &settings.hour_format); - let create_entry = cx.background_executor().spawn(async move { + let create_entry = cx.background_spawn(async move { std::fs::create_dir_all(month_dir)?; OpenOptions::new() .create(true) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 8d40d2dd49905c..022031a0468962 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -7,7 +7,6 @@ pub use crate::{ use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, language_settings::{language_settings, LanguageSettings}, - markdown::parse_markdown, outline::OutlineItem, syntax_map::{ SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatch, @@ -231,50 +230,6 @@ pub struct Diagnostic { pub data: Option, } -/// TODO - move this into the `project` crate and make it private. -pub async fn prepare_completion_documentation( - documentation: &lsp::Documentation, - language_registry: &Arc, - language: Option>, -) -> CompletionDocumentation { - match documentation { - lsp::Documentation::String(text) => { - if text.lines().count() <= 1 { - CompletionDocumentation::SingleLine(text.clone()) - } else { - CompletionDocumentation::MultiLinePlainText(text.clone()) - } - } - - lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { - lsp::MarkupKind::PlainText => { - if value.lines().count() <= 1 { - CompletionDocumentation::SingleLine(value.clone()) - } else { - CompletionDocumentation::MultiLinePlainText(value.clone()) - } - } - - lsp::MarkupKind::Markdown => { - let parsed = parse_markdown(value, Some(language_registry), language).await; - CompletionDocumentation::MultiLineMarkdown(parsed) - } - }, - } -} - -#[derive(Clone, Debug)] -pub enum CompletionDocumentation { - /// There is no documentation for this completion. - Undocumented, - /// A single line of documentation. - SingleLine(String), - /// Multiple lines of plain text documentation. - MultiLinePlainText(String), - /// Markdown documentation. - MultiLineMarkdown(ParsedMarkdown), -} - /// An operation used to synchronize this buffer with its other replicas. #[derive(Clone, Debug, PartialEq)] pub enum Operation { @@ -940,7 +895,7 @@ impl Buffer { } let text_operations = self.text.operations().clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let since = since.unwrap_or_default(); operations.extend( text_operations @@ -1135,7 +1090,7 @@ impl Buffer { let old_snapshot = self.text.snapshot(); let mut branch_buffer = self.text.branch(); let mut syntax_snapshot = self.syntax_map.lock().snapshot(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { if !edits.is_empty() { if let Some(language) = language.clone() { syntax_snapshot.reparse(&old_snapshot, registry.clone(), language); @@ -1499,7 +1454,7 @@ impl Buffer { let mut syntax_snapshot = syntax_map.snapshot(); drop(syntax_map); - let parse_task = cx.background_executor().spawn({ + let parse_task = cx.background_spawn({ let language = language.clone(); let language_registry = language_registry.clone(); async move { @@ -1578,7 +1533,7 @@ impl Buffer { fn request_autoindent(&mut self, cx: &mut Context) { if let Some(indent_sizes) = self.compute_autoindents() { - let indent_sizes = cx.background_executor().spawn(indent_sizes); + let indent_sizes = cx.background_spawn(indent_sizes); match cx .background_executor() .block_with_timeout(Duration::from_micros(500), indent_sizes) @@ -1907,7 +1862,7 @@ impl Buffer { let old_text = self.as_rope().clone(); let line_ending = self.line_ending(); let base_version = self.version(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let ranges = trailing_whitespace_ranges(&old_text); let empty = Arc::::from(""); Diff { diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 68f82f349b5b60..f6b5940028a311 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -942,6 +942,8 @@ impl LanguageRegistry { binary: lsp::LanguageServerBinary, cx: gpui::AsyncApp, ) -> Option { + use gpui::AppContext as _; + let mut state = self.state.write(); let fake_entry = state.fake_server_entries.get_mut(&name)?; let (server, mut fake_server) = lsp::FakeLanguageServer::new( @@ -958,17 +960,16 @@ impl LanguageRegistry { } let tx = fake_entry.tx.clone(); - cx.background_executor() - .spawn(async move { - if fake_server - .try_receive_notification::() - .await - .is_some() - { - tx.unbounded_send(fake_server.clone()).ok(); - } - }) - .detach(); + cx.background_spawn(async move { + if fake_server + .try_receive_notification::() + .await + .is_some() + { + tx.unbounded_send(fake_server.clone()).ok(); + } + }) + .detach(); Some(server) } diff --git a/crates/language_model/Cargo.toml b/crates/language_model/Cargo.toml index 7b570a54f7853d..51f205dced72e5 100644 --- a/crates/language_model/Cargo.toml +++ b/crates/language_model/Cargo.toml @@ -38,6 +38,7 @@ serde.workspace = true serde_json.workspace = true smol.workspace = true strum.workspace = true +thiserror.workspace = true ui.workspace = true util.workspace = true diff --git a/crates/language_model/src/fake_provider.rs b/crates/language_model/src/fake_provider.rs index c3eca1a2f43584..a955638b2187c8 100644 --- a/crates/language_model/src/fake_provider.rs +++ b/crates/language_model/src/fake_provider.rs @@ -1,6 +1,6 @@ use crate::{ - LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId, + LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, }; use futures::{channel::mpsc, future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; @@ -54,7 +54,7 @@ impl LanguageModelProvider for FakeLanguageModelProvider { true } - fn authenticate(&self, _: &mut App) -> Task> { + fn authenticate(&self, _: &mut App) -> Task> { Task::ready(Ok(())) } diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index 4cdf4325724109..6219fda7397b65 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -21,6 +21,7 @@ use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt; use std::{future::Future, sync::Arc}; +use thiserror::Error; use ui::IconName; pub const ZED_CLOUD_PROVIDER_ID: &str = "zed.dev"; @@ -231,6 +232,15 @@ pub trait LanguageModelTool: 'static + DeserializeOwned + JsonSchema { fn description() -> String; } +/// An error that occurred when trying to authenticate the language model provider. +#[derive(Debug, Error)] +pub enum AuthenticateError { + #[error("credentials not found")] + CredentialsNotFound, + #[error(transparent)] + Other(#[from] anyhow::Error), +} + pub trait LanguageModelProvider: 'static { fn id(&self) -> LanguageModelProviderId; fn name(&self) -> LanguageModelProviderName; @@ -240,7 +250,7 @@ pub trait LanguageModelProvider: 'static { fn provided_models(&self, cx: &App) -> Vec>; fn load_model(&self, _model: Arc, _cx: &App) {} fn is_authenticated(&self, cx: &App) -> bool; - fn authenticate(&self, cx: &mut App) -> Task>; + fn authenticate(&self, cx: &mut App) -> Task>; fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView; fn must_accept_terms(&self, _cx: &App) -> bool { false diff --git a/crates/language_model/src/request.rs b/crates/language_model/src/request.rs index a5cf54297f6336..507e8b42072a56 100644 --- a/crates/language_model/src/request.rs +++ b/crates/language_model/src/request.rs @@ -3,7 +3,9 @@ use std::io::{Cursor, Write}; use crate::role::Role; use crate::LanguageModelToolUse; use base64::write::EncoderWriter; -use gpui::{point, size, App, DevicePixels, Image, ObjectFit, RenderImage, Size, Task}; +use gpui::{ + point, size, App, AppContext as _, DevicePixels, Image, ObjectFit, RenderImage, Size, Task, +}; use image::{codecs::png::PngEncoder, imageops::resize, DynamicImage, ImageDecoder}; use serde::{Deserialize, Serialize}; use ui::{px, SharedString}; @@ -30,7 +32,7 @@ const ANTHROPIC_SIZE_LIMT: f32 = 1568.; impl LanguageModelImage { pub fn from_image(data: Image, cx: &mut App) -> Task> { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { match data.format() { gpui::ImageFormat::Png | gpui::ImageFormat::Jpeg diff --git a/crates/language_model_selector/Cargo.toml b/crates/language_model_selector/Cargo.toml index 41c24248aba7be..e4b8b7256e5a3a 100644 --- a/crates/language_model_selector/Cargo.toml +++ b/crates/language_model_selector/Cargo.toml @@ -15,6 +15,7 @@ path = "src/language_model_selector.rs" feature_flags.workspace = true gpui.workspace = true language_model.workspace = true +log.workspace = true picker.workspace = true proto.workspace = true ui.workspace = true diff --git a/crates/language_model_selector/src/language_model_selector.rs b/crates/language_model_selector/src/language_model_selector.rs index 5ae69916085fbe..cd03a6b1f56a98 100644 --- a/crates/language_model_selector/src/language_model_selector.rs +++ b/crates/language_model_selector/src/language_model_selector.rs @@ -2,10 +2,12 @@ use std::sync::Arc; use feature_flags::ZedPro; use gpui::{ - Action, AnyElement, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, - Subscription, Task, WeakEntity, + Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, + Focusable, Subscription, Task, WeakEntity, +}; +use language_model::{ + AuthenticateError, LanguageModel, LanguageModelAvailability, LanguageModelRegistry, }; -use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry}; use picker::{Picker, PickerDelegate}; use proto::Plan; use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger}; @@ -20,6 +22,7 @@ pub struct LanguageModelSelector { /// The task used to update the picker's matches when there is a change to /// the language model registry. update_matches_task: Option>, + _authenticate_all_providers_task: Task<()>, _subscriptions: Vec, } @@ -37,18 +40,20 @@ impl LanguageModelSelector { on_model_changed: on_model_changed.clone(), all_models: all_models.clone(), filtered_models: all_models, - selected_index: 0, + selected_index: Self::get_active_model_index(cx), }; let picker = cx.new(|cx| { Picker::uniform_list(delegate, window, cx) .show_scrollbar(true) + .width(rems(20.)) .max_height(Some(rems(20.).into())) }); LanguageModelSelector { picker, update_matches_task: None, + _authenticate_all_providers_task: Self::authenticate_all_providers(cx), _subscriptions: vec![cx.subscribe_in( &LanguageModelRegistry::global(cx), window, @@ -79,6 +84,56 @@ impl LanguageModelSelector { } } + /// Authenticates all providers in the [`LanguageModelRegistry`]. + /// + /// We do this so that we can populate the language selector with all of the + /// models from the configured providers. + fn authenticate_all_providers(cx: &mut App) -> Task<()> { + let authenticate_all_providers = LanguageModelRegistry::global(cx) + .read(cx) + .providers() + .iter() + .map(|provider| (provider.id(), provider.name(), provider.authenticate(cx))) + .collect::>(); + + cx.spawn(|_cx| async move { + for (provider_id, provider_name, authenticate_task) in authenticate_all_providers { + if let Err(err) = authenticate_task.await { + if matches!(err, AuthenticateError::CredentialsNotFound) { + // Since we're authenticating these providers in the + // background for the purposes of populating the + // language selector, we don't care about providers + // where the credentials are not found. + } else { + // Some providers have noisy failure states that we + // don't want to spam the logs with every time the + // language model selector is initialized. + // + // Ideally these should have more clear failure modes + // that we know are safe to ignore here, like what we do + // with `CredentialsNotFound` above. + match provider_id.0.as_ref() { + "lmstudio" | "ollama" => { + // LM Studio and Ollama both make fetch requests to the local APIs to determine if they are "authenticated". + // + // These fail noisily, so we don't log them. + } + "copilot_chat" => { + // Copilot Chat returns an error if Copilot is not enabled, so we don't log those errors. + } + _ => { + log::error!( + "Failed to authenticate provider: {}: {err}", + provider_name.0 + ); + } + } + } + } + } + }) + } + fn all_models(cx: &App) -> Vec { LanguageModelRegistry::global(cx) .read(cx) @@ -100,6 +155,16 @@ impl LanguageModelSelector { }) .collect::>() } + + fn get_active_model_index(cx: &App) -> usize { + let active_model = LanguageModelRegistry::read_global(cx).active_model(); + Self::all_models(cx) + .iter() + .position(|model_info| { + Some(model_info.model.id()) == active_model.as_ref().map(|model| model.id()) + }) + .unwrap_or(0) + } } impl EventEmitter for LanguageModelSelector {} @@ -126,6 +191,7 @@ where trigger: T, tooltip: TT, handle: Option>, + anchor: Corner, } impl LanguageModelSelectorPopoverMenu @@ -137,12 +203,14 @@ where language_model_selector: Entity, trigger: T, tooltip: TT, + anchor: Corner, ) -> Self { Self { language_model_selector, trigger, tooltip, handle: None, + anchor, } } @@ -163,7 +231,7 @@ where PopoverMenu::new("model-switcher") .menu(move |_window, _cx| Some(language_model_selector.clone())) .trigger_with_tooltip(self.trigger, self.tooltip) - .anchor(gpui::Corner::BottomRight) + .anchor(self.anchor) .when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle)) .offset(gpui::Point { x: px(0.0), @@ -216,9 +284,9 @@ impl PickerDelegate for LanguageModelPickerDelegate { let all_models = self.all_models.clone(); let current_index = self.selected_index; - let llm_registry = LanguageModelRegistry::global(cx); + let language_model_registry = LanguageModelRegistry::global(cx); - let configured_providers = llm_registry + let configured_providers = language_model_registry .read(cx) .providers() .iter() @@ -228,8 +296,7 @@ impl PickerDelegate for LanguageModelPickerDelegate { cx.spawn_in(window, |this, mut cx| async move { let filtered_models = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let displayed_models = if configured_providers.is_empty() { all_models } else { @@ -361,7 +428,7 @@ impl PickerDelegate for LanguageModelPickerDelegate { .items_center() .gap_1p5() .pl_0p5() - .min_w(px(240.)) + .w(px(240.)) .child( div().max_w_40().child( Label::new(model_info.model.name().0.clone()).text_ellipsis(), @@ -388,7 +455,7 @@ impl PickerDelegate for LanguageModelPickerDelegate { }), ), ) - .end_slot(div().when(is_selected, |this| { + .end_slot(div().pr_3().when(is_selected, |this| { this.child( Icon::new(IconName::Check) .color(Color::Accent) diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index 7a09d03eecb418..e990868b9cf837 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -10,8 +10,8 @@ use gpui::{ }; use http_client::HttpClient; use language_model::{ - LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, + LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, }; use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason}; @@ -105,34 +105,38 @@ impl State { self.api_key.is_some() } - fn authenticate(&self, cx: &mut Context) -> Task> { + fn authenticate(&self, cx: &mut Context) -> Task> { if self.is_authenticated() { - Task::ready(Ok(())) - } else { - let api_url = AllLanguageModelSettings::get_global(cx) - .anthropic - .api_url - .clone(); + return Task::ready(Ok(())); + } - cx.spawn(|this, mut cx| async move { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) - { - (api_key, true) - } else { - let (_, api_key) = cx - .update(|cx| cx.read_credentials(&api_url))? - .await? - .ok_or_else(|| anyhow!("credentials not found"))?; - (String::from_utf8(api_key)?, false) - }; + let api_url = AllLanguageModelSettings::get_global(cx) + .anthropic + .api_url + .clone(); - this.update(&mut cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - }) - }) - } + cx.spawn(|this, mut cx| async move { + let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) { + (api_key, true) + } else { + let (_, api_key) = cx + .update(|cx| cx.read_credentials(&api_url))? + .await? + .ok_or(AuthenticateError::CredentialsNotFound)?; + ( + String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, + false, + ) + }; + + this.update(&mut cx, |this, cx| { + this.api_key = Some(api_key); + this.api_key_from_env = from_env; + cx.notify(); + })?; + + Ok(()) + }) } } @@ -226,7 +230,7 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider { self.state.read(cx).is_authenticated() } - fn authenticate(&self, cx: &mut App) -> Task> { + fn authenticate(&self, cx: &mut App) -> Task> { self.state.update(cx, |state, cx| state.authenticate(cx)) } @@ -252,54 +256,53 @@ pub fn count_anthropic_tokens( request: LanguageModelRequest, cx: &App, ) -> BoxFuture<'static, Result> { - cx.background_executor() - .spawn(async move { - let messages = request.messages; - let mut tokens_from_images = 0; - let mut string_messages = Vec::with_capacity(messages.len()); + cx.background_spawn(async move { + let messages = request.messages; + let mut tokens_from_images = 0; + let mut string_messages = Vec::with_capacity(messages.len()); - for message in messages { - use language_model::MessageContent; + for message in messages { + use language_model::MessageContent; - let mut string_contents = String::new(); + let mut string_contents = String::new(); - for content in message.content { - match content { - MessageContent::Text(text) => { - string_contents.push_str(&text); - } - MessageContent::Image(image) => { - tokens_from_images += image.estimate_tokens(); - } - MessageContent::ToolUse(_tool_use) => { - // TODO: Estimate token usage from tool uses. - } - MessageContent::ToolResult(tool_result) => { - string_contents.push_str(&tool_result.content); - } + for content in message.content { + match content { + MessageContent::Text(text) => { + string_contents.push_str(&text); + } + MessageContent::Image(image) => { + tokens_from_images += image.estimate_tokens(); + } + MessageContent::ToolUse(_tool_use) => { + // TODO: Estimate token usage from tool uses. + } + MessageContent::ToolResult(tool_result) => { + string_contents.push_str(&tool_result.content); } } + } - if !string_contents.is_empty() { - string_messages.push(tiktoken_rs::ChatCompletionRequestMessage { - role: match message.role { - Role::User => "user".into(), - Role::Assistant => "assistant".into(), - Role::System => "system".into(), - }, - content: Some(string_contents), - name: None, - function_call: None, - }); - } + if !string_contents.is_empty() { + string_messages.push(tiktoken_rs::ChatCompletionRequestMessage { + role: match message.role { + Role::User => "user".into(), + Role::Assistant => "assistant".into(), + Role::System => "system".into(), + }, + content: Some(string_contents), + name: None, + function_call: None, + }); } + } - // Tiktoken doesn't yet support these models, so we manually use the - // same tokenizer as GPT-4. - tiktoken_rs::num_tokens_from_messages("gpt-4", &string_messages) - .map(|tokens| tokens + tokens_from_images) - }) - .boxed() + // Tiktoken doesn't yet support these models, so we manually use the + // same tokenizer as GPT-4. + tiktoken_rs::num_tokens_from_messages("gpt-4", &string_messages) + .map(|tokens| tokens + tokens_from_images) + }) + .boxed() } impl AnthropicModel { diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 27d0e125965909..05544f40db27e3 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -17,9 +17,10 @@ use gpui::{ }; use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode}; use language_model::{ - CloudModel, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, LanguageModelName, - LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, - LanguageModelProviderTosView, LanguageModelRequest, RateLimiter, ZED_CLOUD_PROVIDER_ID, + AuthenticateError, CloudModel, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, + LanguageModelName, LanguageModelProviderId, LanguageModelProviderName, + LanguageModelProviderState, LanguageModelProviderTosView, LanguageModelRequest, RateLimiter, + ZED_CLOUD_PROVIDER_ID, }; use language_model::{ LanguageModelAvailability, LanguageModelCompletionEvent, LanguageModelProvider, @@ -363,7 +364,7 @@ impl LanguageModelProvider for CloudLanguageModelProvider { !self.state.read(cx).is_signed_out() } - fn authenticate(&self, _cx: &mut App) -> Task> { + fn authenticate(&self, _cx: &mut App) -> Task> { Task::ready(Ok(())) } diff --git a/crates/language_models/src/provider/copilot_chat.rs b/crates/language_models/src/provider/copilot_chat.rs index 6efac131e9b821..1c4a4273ac4f95 100644 --- a/crates/language_models/src/provider/copilot_chat.rs +++ b/crates/language_models/src/provider/copilot_chat.rs @@ -15,8 +15,8 @@ use gpui::{ Task, Transformation, }; use language_model::{ - LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId, + LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, }; use settings::SettingsStore; @@ -104,26 +104,28 @@ impl LanguageModelProvider for CopilotChatLanguageModelProvider { self.state.read(cx).is_authenticated(cx) } - fn authenticate(&self, cx: &mut App) -> Task> { - let result = if self.is_authenticated(cx) { - Ok(()) - } else if let Some(copilot) = Copilot::global(cx) { - let error_msg = match copilot.read(cx).status() { - Status::Disabled => anyhow::anyhow!("Copilot must be enabled for Copilot Chat to work. Please enable Copilot and try again."), - Status::Error(e) => anyhow::anyhow!(format!("Received the following error while signing into Copilot: {e}")), - Status::Starting { task: _ } => anyhow::anyhow!("Copilot is still starting, please wait for Copilot to start then try again"), - Status::Unauthorized => anyhow::anyhow!("Unable to authorize with Copilot. Please make sure that you have an active Copilot and Copilot Chat subscription."), - Status::Authorized => return Task::ready(Ok(())), - Status::SignedOut => anyhow::anyhow!("You have signed out of Copilot. Please sign in to Copilot and try again."), - Status::SigningIn { prompt: _ } => anyhow::anyhow!("Still signing into Copilot..."), - }; - Err(error_msg) - } else { - Err(anyhow::anyhow!( + fn authenticate(&self, cx: &mut App) -> Task> { + if self.is_authenticated(cx) { + return Task::ready(Ok(())); + }; + + let Some(copilot) = Copilot::global(cx) else { + return Task::ready( Err(anyhow!( "Copilot must be enabled for Copilot Chat to work. Please enable Copilot and try again." - )) + ).into())); }; - Task::ready(result) + + let err = match copilot.read(cx).status() { + Status::Authorized => return Task::ready(Ok(())), + Status::Disabled => anyhow!("Copilot must be enabled for Copilot Chat to work. Please enable Copilot and try again."), + Status::Error(err) => anyhow!(format!("Received the following error while signing into Copilot: {err}")), + Status::Starting { task: _ } => anyhow!("Copilot is still starting, please wait for Copilot to start then try again"), + Status::Unauthorized => anyhow!("Unable to authorize with Copilot. Please make sure that you have an active Copilot and Copilot Chat subscription."), + Status::SignedOut => anyhow!("You have signed out of Copilot. Please sign in to Copilot and try again."), + Status::SigningIn { prompt: _ } => anyhow!("Still signing into Copilot..."), + }; + + Task::ready(Err(err.into())) } fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView { diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index 17604b35d80c33..9a65273d12a17c 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -1,14 +1,15 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use collections::BTreeMap; use editor::{Editor, EditorElement, EditorStyle}; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; use gpui::{ - AnyView, AppContext, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace, + AnyView, AppContext as _, AsyncApp, Entity, FontStyle, Subscription, Task, TextStyle, + WhiteSpace, }; use http_client::HttpClient; use language_model::{ - LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId, + LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, }; use schemars::JsonSchema; @@ -82,33 +83,38 @@ impl State { }) } - fn authenticate(&self, cx: &mut Context) -> Task> { + fn authenticate(&self, cx: &mut Context) -> Task> { if self.is_authenticated() { - Task::ready(Ok(())) - } else { - let api_url = AllLanguageModelSettings::get_global(cx) - .deepseek - .api_url - .clone(); - - cx.spawn(|this, mut cx| async move { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) { - (api_key, true) - } else { - let (_, api_key) = cx - .update(|cx| cx.read_credentials(&api_url))? - .await? - .ok_or_else(|| anyhow!("credentials not found"))?; - (String::from_utf8(api_key)?, false) - }; - - this.update(&mut cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - }) - }) + return Task::ready(Ok(())); } + + let api_url = AllLanguageModelSettings::get_global(cx) + .deepseek + .api_url + .clone(); + + cx.spawn(|this, mut cx| async move { + let (api_key, from_env) = if let Ok(api_key) = std::env::var(DEEPSEEK_API_KEY_VAR) { + (api_key, true) + } else { + let (_, api_key) = cx + .update(|cx| cx.read_credentials(&api_url))? + .await? + .ok_or(AuthenticateError::CredentialsNotFound)?; + ( + String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, + false, + ) + }; + + this.update(&mut cx, |this, cx| { + this.api_key = Some(api_key); + this.api_key_from_env = from_env; + cx.notify(); + })?; + + Ok(()) + }) } } @@ -187,7 +193,7 @@ impl LanguageModelProvider for DeepSeekLanguageModelProvider { self.state.read(cx).is_authenticated() } - fn authenticate(&self, cx: &mut App) -> Task> { + fn authenticate(&self, cx: &mut App) -> Task> { self.state.update(cx, |state, cx| state.authenticate(cx)) } @@ -269,26 +275,25 @@ impl LanguageModel for DeepSeekLanguageModel { request: LanguageModelRequest, cx: &App, ) -> BoxFuture<'static, Result> { - cx.background_executor() - .spawn(async move { - let messages = request - .messages - .into_iter() - .map(|message| tiktoken_rs::ChatCompletionRequestMessage { - role: match message.role { - Role::User => "user".into(), - Role::Assistant => "assistant".into(), - Role::System => "system".into(), - }, - content: Some(message.string_contents()), - name: None, - function_call: None, - }) - .collect::>(); + cx.background_spawn(async move { + let messages = request + .messages + .into_iter() + .map(|message| tiktoken_rs::ChatCompletionRequestMessage { + role: match message.role { + Role::User => "user".into(), + Role::Assistant => "assistant".into(), + Role::System => "system".into(), + }, + content: Some(message.string_contents()), + name: None, + function_call: None, + }) + .collect::>(); - tiktoken_rs::num_tokens_from_messages("gpt-4", &messages) - }) - .boxed() + tiktoken_rs::num_tokens_from_messages("gpt-4", &messages) + }) + .boxed() } fn stream_completion( @@ -322,6 +327,7 @@ impl LanguageModel for DeepSeekLanguageModel { } .boxed() } + fn use_any_tool( &self, request: LanguageModelRequest, diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index 2054b686dcb455..d7a6e8ba2a4928 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use collections::BTreeMap; use editor::{Editor, EditorElement, EditorStyle}; use futures::{future::BoxFuture, FutureExt, StreamExt}; @@ -7,7 +7,7 @@ use gpui::{ AnyView, App, AsyncApp, Context, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace, }; use http_client::HttpClient; -use language_model::LanguageModelCompletionEvent; +use language_model::{AuthenticateError, LanguageModelCompletionEvent}; use language_model::{ LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, @@ -85,34 +85,38 @@ impl State { }) } - fn authenticate(&self, cx: &mut Context) -> Task> { + fn authenticate(&self, cx: &mut Context) -> Task> { if self.is_authenticated() { - Task::ready(Ok(())) - } else { - let api_url = AllLanguageModelSettings::get_global(cx) - .google - .api_url - .clone(); + return Task::ready(Ok(())); + } - cx.spawn(|this, mut cx| async move { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(GOOGLE_AI_API_KEY_VAR) - { - (api_key, true) - } else { - let (_, api_key) = cx - .update(|cx| cx.read_credentials(&api_url))? - .await? - .ok_or_else(|| anyhow!("credentials not found"))?; - (String::from_utf8(api_key)?, false) - }; + let api_url = AllLanguageModelSettings::get_global(cx) + .google + .api_url + .clone(); - this.update(&mut cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - }) - }) - } + cx.spawn(|this, mut cx| async move { + let (api_key, from_env) = if let Ok(api_key) = std::env::var(GOOGLE_AI_API_KEY_VAR) { + (api_key, true) + } else { + let (_, api_key) = cx + .update(|cx| cx.read_credentials(&api_url))? + .await? + .ok_or(AuthenticateError::CredentialsNotFound)?; + ( + String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, + false, + ) + }; + + this.update(&mut cx, |this, cx| { + this.api_key = Some(api_key); + this.api_key_from_env = from_env; + cx.notify(); + })?; + + Ok(()) + }) } } @@ -194,7 +198,7 @@ impl LanguageModelProvider for GoogleLanguageModelProvider { self.state.read(cx).is_authenticated() } - fn authenticate(&self, cx: &mut App) -> Task> { + fn authenticate(&self, cx: &mut App) -> Task> { self.state.update(cx, |state, cx| state.authenticate(cx)) } @@ -330,28 +334,27 @@ pub fn count_google_tokens( ) -> BoxFuture<'static, Result> { // We couldn't use the GoogleLanguageModelProvider to count tokens because the github copilot doesn't have the access to google_ai directly. // So we have to use tokenizer from tiktoken_rs to count tokens. - cx.background_executor() - .spawn(async move { - let messages = request - .messages - .into_iter() - .map(|message| tiktoken_rs::ChatCompletionRequestMessage { - role: match message.role { - Role::User => "user".into(), - Role::Assistant => "assistant".into(), - Role::System => "system".into(), - }, - content: Some(message.string_contents()), - name: None, - function_call: None, - }) - .collect::>(); + cx.background_spawn(async move { + let messages = request + .messages + .into_iter() + .map(|message| tiktoken_rs::ChatCompletionRequestMessage { + role: match message.role { + Role::User => "user".into(), + Role::Assistant => "assistant".into(), + Role::System => "system".into(), + }, + content: Some(message.string_contents()), + name: None, + function_call: None, + }) + .collect::>(); - // Tiktoken doesn't yet support these models, so we manually use the - // same tokenizer as GPT-4. - tiktoken_rs::num_tokens_from_messages("gpt-4", &messages) - }) - .boxed() + // Tiktoken doesn't yet support these models, so we manually use the + // same tokenizer as GPT-4. + tiktoken_rs::num_tokens_from_messages("gpt-4", &messages) + }) + .boxed() } struct ConfigurationView { diff --git a/crates/language_models/src/provider/lmstudio.rs b/crates/language_models/src/provider/lmstudio.rs index 9096c3fb32c803..76832a44e195ef 100644 --- a/crates/language_models/src/provider/lmstudio.rs +++ b/crates/language_models/src/provider/lmstudio.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; use gpui::{AnyView, App, AsyncApp, Context, Subscription, Task}; use http_client::HttpClient; -use language_model::LanguageModelCompletionEvent; +use language_model::{AuthenticateError, LanguageModelCompletionEvent}; use language_model::{ LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, @@ -90,12 +90,13 @@ impl State { self.fetch_model_task.replace(task); } - fn authenticate(&mut self, cx: &mut Context) -> Task> { + fn authenticate(&mut self, cx: &mut Context) -> Task> { if self.is_authenticated() { - Task::ready(Ok(())) - } else { - self.fetch_models(cx) + return Task::ready(Ok(())); } + + let fetch_models_task = self.fetch_models(cx); + cx.spawn(|_this, _cx| async move { Ok(fetch_models_task.await?) }) } } @@ -201,7 +202,7 @@ impl LanguageModelProvider for LmStudioLanguageModelProvider { self.state.read(cx).is_authenticated() } - fn authenticate(&self, cx: &mut App) -> Task> { + fn authenticate(&self, cx: &mut App) -> Task> { self.state.update(cx, |state, cx| state.authenticate(cx)) } diff --git a/crates/language_models/src/provider/mistral.rs b/crates/language_models/src/provider/mistral.rs index eb239858a7aeea..a5cc3dac16235e 100644 --- a/crates/language_models/src/provider/mistral.rs +++ b/crates/language_models/src/provider/mistral.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use collections::BTreeMap; use editor::{Editor, EditorElement, EditorStyle}; use futures::{future::BoxFuture, FutureExt, StreamExt}; @@ -7,8 +7,8 @@ use gpui::{ }; use http_client::HttpClient; use language_model::{ - LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId, + LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, }; @@ -88,31 +88,36 @@ impl State { }) } - fn authenticate(&self, cx: &mut Context) -> Task> { + fn authenticate(&self, cx: &mut Context) -> Task> { if self.is_authenticated() { - Task::ready(Ok(())) - } else { - let api_url = AllLanguageModelSettings::get_global(cx) - .mistral - .api_url - .clone(); - cx.spawn(|this, mut cx| async move { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(MISTRAL_API_KEY_VAR) { - (api_key, true) - } else { - let (_, api_key) = cx - .update(|cx| cx.read_credentials(&api_url))? - .await? - .ok_or_else(|| anyhow!("credentials not found"))?; - (String::from_utf8(api_key)?, false) - }; - this.update(&mut cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - }) - }) + return Task::ready(Ok(())); } + + let api_url = AllLanguageModelSettings::get_global(cx) + .mistral + .api_url + .clone(); + cx.spawn(|this, mut cx| async move { + let (api_key, from_env) = if let Ok(api_key) = std::env::var(MISTRAL_API_KEY_VAR) { + (api_key, true) + } else { + let (_, api_key) = cx + .update(|cx| cx.read_credentials(&api_url))? + .await? + .ok_or(AuthenticateError::CredentialsNotFound)?; + ( + String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, + false, + ) + }; + this.update(&mut cx, |this, cx| { + this.api_key = Some(api_key); + this.api_key_from_env = from_env; + cx.notify(); + })?; + + Ok(()) + }) } } @@ -196,7 +201,7 @@ impl LanguageModelProvider for MistralLanguageModelProvider { self.state.read(cx).is_authenticated() } - fn authenticate(&self, cx: &mut App) -> Task> { + fn authenticate(&self, cx: &mut App) -> Task> { self.state.update(cx, |state, cx| state.authenticate(cx)) } @@ -281,26 +286,25 @@ impl LanguageModel for MistralLanguageModel { request: LanguageModelRequest, cx: &App, ) -> BoxFuture<'static, Result> { - cx.background_executor() - .spawn(async move { - let messages = request - .messages - .into_iter() - .map(|message| tiktoken_rs::ChatCompletionRequestMessage { - role: match message.role { - Role::User => "user".into(), - Role::Assistant => "assistant".into(), - Role::System => "system".into(), - }, - content: Some(message.string_contents()), - name: None, - function_call: None, - }) - .collect::>(); + cx.background_spawn(async move { + let messages = request + .messages + .into_iter() + .map(|message| tiktoken_rs::ChatCompletionRequestMessage { + role: match message.role { + Role::User => "user".into(), + Role::Assistant => "assistant".into(), + Role::System => "system".into(), + }, + content: Some(message.string_contents()), + name: None, + function_call: None, + }) + .collect::>(); - tiktoken_rs::num_tokens_from_messages("gpt-4", &messages) - }) - .boxed() + tiktoken_rs::num_tokens_from_messages("gpt-4", &messages) + }) + .boxed() } fn stream_completion( diff --git a/crates/language_models/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs index a1acf5b695d7c3..a982eb3aa794ed 100644 --- a/crates/language_models/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, bail, Result}; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; use gpui::{AnyView, App, AsyncApp, Context, Subscription, Task}; use http_client::HttpClient; -use language_model::LanguageModelCompletionEvent; +use language_model::{AuthenticateError, LanguageModelCompletionEvent}; use language_model::{ LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, @@ -95,12 +95,13 @@ impl State { self.fetch_model_task.replace(task); } - fn authenticate(&mut self, cx: &mut Context) -> Task> { + fn authenticate(&mut self, cx: &mut Context) -> Task> { if self.is_authenticated() { - Task::ready(Ok(())) - } else { - self.fetch_models(cx) + return Task::ready(Ok(())); } + + let fetch_models_task = self.fetch_models(cx); + cx.spawn(|_this, _cx| async move { Ok(fetch_models_task.await?) }) } } @@ -207,7 +208,7 @@ impl LanguageModelProvider for OllamaLanguageModelProvider { self.state.read(cx).is_authenticated() } - fn authenticate(&self, cx: &mut App) -> Task> { + fn authenticate(&self, cx: &mut App) -> Task> { self.state.update(cx, |state, cx| state.authenticate(cx)) } diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs index a6797f7e6eebd6..765eae9b15d890 100644 --- a/crates/language_models/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use collections::BTreeMap; use editor::{Editor, EditorElement, EditorStyle}; use futures::{future::BoxFuture, FutureExt, StreamExt}; @@ -7,8 +7,8 @@ use gpui::{ }; use http_client::HttpClient; use language_model::{ - LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId, + LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, }; use open_ai::{ @@ -89,31 +89,36 @@ impl State { }) } - fn authenticate(&self, cx: &mut Context) -> Task> { + fn authenticate(&self, cx: &mut Context) -> Task> { if self.is_authenticated() { - Task::ready(Ok(())) - } else { - let api_url = AllLanguageModelSettings::get_global(cx) - .openai - .api_url - .clone(); - cx.spawn(|this, mut cx| async move { - let (api_key, from_env) = if let Ok(api_key) = std::env::var(OPENAI_API_KEY_VAR) { - (api_key, true) - } else { - let (_, api_key) = cx - .update(|cx| cx.read_credentials(&api_url))? - .await? - .ok_or_else(|| anyhow!("credentials not found"))?; - (String::from_utf8(api_key)?, false) - }; - this.update(&mut cx, |this, cx| { - this.api_key = Some(api_key); - this.api_key_from_env = from_env; - cx.notify(); - }) - }) + return Task::ready(Ok(())); } + + let api_url = AllLanguageModelSettings::get_global(cx) + .openai + .api_url + .clone(); + cx.spawn(|this, mut cx| async move { + let (api_key, from_env) = if let Ok(api_key) = std::env::var(OPENAI_API_KEY_VAR) { + (api_key, true) + } else { + let (_, api_key) = cx + .update(|cx| cx.read_credentials(&api_url))? + .await? + .ok_or(AuthenticateError::CredentialsNotFound)?; + ( + String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, + false, + ) + }; + this.update(&mut cx, |this, cx| { + this.api_key = Some(api_key); + this.api_key_from_env = from_env; + cx.notify(); + })?; + + Ok(()) + }) } } @@ -197,7 +202,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider { self.state.read(cx).is_authenticated() } - fn authenticate(&self, cx: &mut App) -> Task> { + fn authenticate(&self, cx: &mut App) -> Task> { self.state.update(cx, |state, cx| state.authenticate(cx)) } @@ -343,34 +348,31 @@ pub fn count_open_ai_tokens( model: open_ai::Model, cx: &App, ) -> BoxFuture<'static, Result> { - cx.background_executor() - .spawn(async move { - let messages = request - .messages - .into_iter() - .map(|message| tiktoken_rs::ChatCompletionRequestMessage { - role: match message.role { - Role::User => "user".into(), - Role::Assistant => "assistant".into(), - Role::System => "system".into(), - }, - content: Some(message.string_contents()), - name: None, - function_call: None, - }) - .collect::>(); - - match model { - open_ai::Model::Custom { .. } - | open_ai::Model::O1Mini - | open_ai::Model::O1 - | open_ai::Model::O3Mini => { - tiktoken_rs::num_tokens_from_messages("gpt-4", &messages) - } - _ => tiktoken_rs::num_tokens_from_messages(model.id(), &messages), - } - }) - .boxed() + cx.background_spawn(async move { + let messages = request + .messages + .into_iter() + .map(|message| tiktoken_rs::ChatCompletionRequestMessage { + role: match message.role { + Role::User => "user".into(), + Role::Assistant => "assistant".into(), + Role::System => "system".into(), + }, + content: Some(message.string_contents()), + name: None, + function_call: None, + }) + .collect::>(); + + match model { + open_ai::Model::Custom { .. } + | open_ai::Model::O1Mini + | open_ai::Model::O1 + | open_ai::Model::O3Mini => tiktoken_rs::num_tokens_from_messages("gpt-4", &messages), + _ => tiktoken_rs::num_tokens_from_messages(model.id(), &messages), + } + }) + .boxed() } struct ConfigurationView { diff --git a/crates/languages/src/go/highlights.scm b/crates/languages/src/go/highlights.scm index b9ce232112fb52..609c49c13f53cf 100644 --- a/crates/languages/src/go/highlights.scm +++ b/crates/languages/src/go/highlights.scm @@ -1,3 +1,5 @@ +(identifier) @variable + (type_identifier) @type (field_identifier) @variable.member diff --git a/crates/languages/src/javascript/highlights.scm b/crates/languages/src/javascript/highlights.scm index 8ae208d4cde1de..711e2e5d48759f 100644 --- a/crates/languages/src/javascript/highlights.scm +++ b/crates/languages/src/javascript/highlights.scm @@ -59,8 +59,8 @@ ; Literals -(this) @variable.special -(super) @variable.special +(this) @keyword +(super) @keyword [ (null) diff --git a/crates/languages/src/tsx/highlights.scm b/crates/languages/src/tsx/highlights.scm index 26cf5c207b2cd4..323f58c5577f30 100644 --- a/crates/languages/src/tsx/highlights.scm +++ b/crates/languages/src/tsx/highlights.scm @@ -62,8 +62,8 @@ ; Literals -(this) @variable.special -(super) @variable.special +(this) @keyword +(super) @keyword [ (null) diff --git a/crates/languages/src/typescript/highlights.scm b/crates/languages/src/typescript/highlights.scm index f4104a504dedca..091ddfed9d02e9 100644 --- a/crates/languages/src/typescript/highlights.scm +++ b/crates/languages/src/typescript/highlights.scm @@ -77,8 +77,8 @@ ; Literals -(this) @variable.special -(super) @variable.special +(this) @keyword +(super) @keyword [ (null) diff --git a/crates/livekit_client/examples/test_app.rs b/crates/livekit_client/examples/test_app.rs index c0ea6dd8559075..458badd5e31ae1 100644 --- a/crates/livekit_client/examples/test_app.rs +++ b/crates/livekit_client/examples/test_app.rs @@ -302,11 +302,10 @@ impl LivekitWindow { if let Some(track) = self.screen_share_track.take() { self.screen_share_stream.take(); let participant = self.room.local_participant(); - cx.background_executor() - .spawn(async move { - participant.unpublish_track(&track.sid()).await.unwrap(); - }) - .detach(); + cx.background_spawn(async move { + participant.unpublish_track(&track.sid()).await.unwrap(); + }) + .detach(); cx.notify(); } else { let participant = self.room.local_participant(); diff --git a/crates/livekit_client/src/remote_video_track_view.rs b/crates/livekit_client/src/remote_video_track_view.rs index 520c5b4e901788..13e5cfd8bb8cfe 100644 --- a/crates/livekit_client/src/remote_video_track_view.rs +++ b/crates/livekit_client/src/remote_video_track_view.rs @@ -1,7 +1,9 @@ use crate::track::RemoteVideoTrack; use anyhow::Result; use futures::StreamExt as _; -use gpui::{AppContext, Context, Empty, Entity, EventEmitter, IntoElement, Render, Task, Window}; +use gpui::{ + AppContext as _, Context, Empty, Entity, EventEmitter, IntoElement, Render, Task, Window, +}; pub struct RemoteVideoTrackView { track: RemoteVideoTrack, diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d0d8bc799236c2..15eb3b0f96bc38 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -6,7 +6,7 @@ pub use lsp_types::*; use anyhow::{anyhow, Context as _, Result}; use collections::HashMap; use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt}; -use gpui::{App, AsyncApp, BackgroundExecutor, SharedString, Task}; +use gpui::{App, AppContext as _, AsyncApp, BackgroundExecutor, SharedString, Task}; use notification::DidChangeWorkspaceFolders; use parking_lot::{Mutex, RwLock}; use postage::{barrier, prelude::Stream}; @@ -448,7 +448,7 @@ impl LanguageServer { let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task); stdout.or(stderr) }); - let output_task = cx.background_executor().spawn({ + let output_task = cx.background_spawn({ Self::handle_output( stdin, outbound_rx, diff --git a/crates/markdown/Cargo.toml b/crates/markdown/Cargo.toml index 5fe64ece7be9e7..0fffd5e0918346 100644 --- a/crates/markdown/Cargo.toml +++ b/crates/markdown/Cargo.toml @@ -20,7 +20,6 @@ test-support = [ [dependencies] anyhow.workspace = true -futures.workspace = true gpui.workspace = true language.workspace = true linkify.workspace = true @@ -34,7 +33,7 @@ util.workspace = true assets.workspace = true env_logger.workspace = true gpui = { workspace = true, features = ["test-support"] } -languages.workspace = true +languages = { workspace = true, features = ["load-grammars"] } node_runtime.workspace = true settings = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] } diff --git a/crates/markdown/examples/markdown.rs b/crates/markdown/examples/markdown.rs index 0745020943822b..f4eafc5e1fd773 100644 --- a/crates/markdown/examples/markdown.rs +++ b/crates/markdown/examples/markdown.rs @@ -15,84 +15,12 @@ const MARKDOWN_EXAMPLE: &str = r#" ## Headings Headings are created by adding one or more `#` symbols before your heading text. The number of `#` you use will determine the size of the heading. -```rust -gpui::window::ViewContext -impl<'a, V> ViewContext<'a, V> -pub fn on_blur(&mut self, handle: &FocusHandle, listener: impl FnMut(&mut V, &mut iewContext) + 'static) -> Subscription -where - // Bounds from impl: - V: 'static, ``` +function a(b: T) { -## Emphasis -Emphasis can be added with italics or bold. *This text will be italic*. _This will also be italic_ - -## Lists - -### Unordered Lists -Unordered lists use asterisks `*`, plus `+`, or minus `-` as list markers. - -* Item 1 -* Item 2 - * Item 2a - * Item 2b - -### Ordered Lists -Ordered lists use numbers followed by a period. - -1. Item 1 -2. Item 2 -3. Item 3 - 1. Item 3a - 2. Item 3b - -## Links -Links are created using the format [http://zed.dev](https://zed.dev). - -They can also be detected automatically, for example https://zed.dev/blog. - -They may contain dollar signs: - -[https://svelte.dev/docs/svelte/$state](https://svelte.dev/docs/svelte/$state) - -https://svelte.dev/docs/svelte/$state - -## Images -Images are like links, but with an exclamation mark `!` in front. - -```markdown -![This is an image](/images/logo.png) -``` - -## Code -Inline `code` can be wrapped with backticks `` ` ``. - -```markdown -Inline `code` has `back-ticks around` it. -``` - -Code blocks can be created by indenting lines by four spaces or with triple backticks ```. - -```javascript -function test() { - console.log("notice the blank line before this function?"); } ``` -## Blockquotes -Blockquotes are created with `>`. - -> This is a blockquote. - -## Horizontal Rules -Horizontal rules are created using three or more asterisks `***`, dashes `---`, or underscores `___`. - -## Line breaks -This is a -\ -line break! - ---- Remember, markdown processors may have slight differences and extensions, so always refer to the specific documentation or guides relevant to your platform or editor for the best practices and additional features. "#; @@ -161,7 +89,7 @@ pub fn main() { }; MarkdownExample::new( - MARKDOWN_EXAMPLE.to_string(), + MARKDOWN_EXAMPLE.into(), markdown_style, language_registry, window, @@ -179,14 +107,22 @@ struct MarkdownExample { impl MarkdownExample { pub fn new( - text: String, + text: SharedString, style: MarkdownStyle, language_registry: Arc, window: &mut Window, cx: &mut App, ) -> Self { - let markdown = - cx.new(|cx| Markdown::new(text, style, Some(language_registry), None, window, cx)); + let markdown = cx.new(|cx| { + Markdown::new( + text, + style, + Some(language_registry), + Some("TypeScript".to_string()), + window, + cx, + ) + }); Self { markdown } } } diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index d6190c43dbed3a..11cbda57eebaef 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -1,7 +1,6 @@ pub mod parser; use crate::parser::CodeBlockKind; -use futures::FutureExt; use gpui::{ actions, point, quad, AnyElement, App, Bounds, ClipboardItem, CursorStyle, DispatchPhase, Edges, Entity, FocusHandle, Focusable, FontStyle, FontWeight, GlobalElementId, Hitbox, Hsla, @@ -12,7 +11,7 @@ use gpui::{ use language::{Language, LanguageRegistry, Rope}; use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd}; -use std::{iter, mem, ops::Range, rc::Rc, sync::Arc}; +use std::{collections::HashMap, iter, mem, ops::Range, rc::Rc, sync::Arc}; use theme::SyntaxTheme; use ui::{prelude::*, Tooltip}; use util::{ResultExt, TryFutureExt}; @@ -49,7 +48,7 @@ impl Default for MarkdownStyle { } pub struct Markdown { - source: String, + source: SharedString, selection: Selection, pressed_link: Option, autoscroll_request: Option, @@ -60,6 +59,7 @@ pub struct Markdown { focus_handle: FocusHandle, language_registry: Option>, fallback_code_block_language: Option, + open_url: Option>, options: Options, } @@ -73,7 +73,7 @@ actions!(markdown, [Copy]); impl Markdown { pub fn new( - source: String, + source: SharedString, style: MarkdownStyle, language_registry: Option>, fallback_code_block_language: Option, @@ -97,13 +97,24 @@ impl Markdown { parse_links_only: false, copy_code_block_buttons: true, }, + open_url: None, }; this.parse(window, cx); this } + pub fn open_url( + self, + open_url: impl Fn(SharedString, &mut Window, &mut App) + 'static, + ) -> Self { + Self { + open_url: Some(Box::new(open_url)), + ..self + } + } + pub fn new_text( - source: String, + source: SharedString, style: MarkdownStyle, language_registry: Option>, fallback_code_block_language: Option, @@ -127,6 +138,7 @@ impl Markdown { parse_links_only: true, copy_code_block_buttons: true, }, + open_url: None, }; this.parse(window, cx); this @@ -137,11 +149,11 @@ impl Markdown { } pub fn append(&mut self, text: &str, window: &mut Window, cx: &mut Context) { - self.source.push_str(text); + self.source = SharedString::new(self.source.to_string() + text); self.parse(window, cx); } - pub fn reset(&mut self, source: String, window: &mut Window, cx: &mut Context) { + pub fn reset(&mut self, source: SharedString, window: &mut Window, cx: &mut Context) { if source == self.source() { return; } @@ -176,17 +188,38 @@ impl Markdown { return; } - let text = self.source.clone(); + let source = self.source.clone(); let parse_text_only = self.options.parse_links_only; - let parsed = cx.background_executor().spawn(async move { - let text = SharedString::from(text); - let events = match parse_text_only { - true => Arc::from(parse_links_only(text.as_ref())), - false => Arc::from(parse_markdown(text.as_ref())), - }; + let language_registry = self.language_registry.clone(); + let fallback = self.fallback_code_block_language.clone(); + let parsed = cx.background_spawn(async move { + if parse_text_only { + return anyhow::Ok(ParsedMarkdown { + events: Arc::from(parse_links_only(source.as_ref())), + source, + languages: HashMap::default(), + }); + } + let (events, language_names) = parse_markdown(&source); + let mut languages = HashMap::with_capacity(language_names.len()); + for name in language_names { + if let Some(registry) = language_registry.as_ref() { + let language = if !name.is_empty() { + registry.language_for_name(&name) + } else if let Some(fallback) = &fallback { + registry.language_for_name(fallback) + } else { + continue; + }; + if let Ok(language) = language.await { + languages.insert(name, language); + } + } + } anyhow::Ok(ParsedMarkdown { - source: text, - events, + source, + events: Arc::from(events), + languages, }) }); @@ -217,12 +250,7 @@ impl Markdown { impl Render for Markdown { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - MarkdownElement::new( - cx.entity().clone(), - self.style.clone(), - self.language_registry.clone(), - self.fallback_code_block_language.clone(), - ) + MarkdownElement::new(cx.entity().clone(), self.style.clone()) } } @@ -270,6 +298,7 @@ impl Selection { pub struct ParsedMarkdown { source: SharedString, events: Arc<[(Range, MarkdownEvent)]>, + languages: HashMap>, } impl ParsedMarkdown { @@ -285,61 +314,11 @@ impl ParsedMarkdown { pub struct MarkdownElement { markdown: Entity, style: MarkdownStyle, - language_registry: Option>, - fallback_code_block_language: Option, } impl MarkdownElement { - fn new( - markdown: Entity, - style: MarkdownStyle, - language_registry: Option>, - fallback_code_block_language: Option, - ) -> Self { - Self { - markdown, - style, - language_registry, - fallback_code_block_language, - } - } - - fn load_language( - &self, - name: &str, - window: &mut Window, - cx: &mut App, - ) -> Option> { - let language_test = self.language_registry.as_ref()?.language_for_name(name); - - let language_name = match language_test.now_or_never() { - Some(Ok(_)) => String::from(name), - Some(Err(_)) if !name.is_empty() && self.fallback_code_block_language.is_some() => { - self.fallback_code_block_language.clone().unwrap() - } - _ => String::new(), - }; - - let language = self - .language_registry - .as_ref()? - .language_for_name(language_name.as_str()) - .map(|language| language.ok()) - .shared(); - - match language.clone().now_or_never() { - Some(language) => language, - None => { - let markdown = self.markdown.downgrade(); - window - .spawn(cx, |mut cx| async move { - language.await; - markdown.update(&mut cx, |_, cx| cx.notify()) - }) - .detach_and_log_err(cx); - None - } - } + fn new(markdown: Entity, style: MarkdownStyle) -> Self { + Self { markdown, style } } fn paint_selection( @@ -452,7 +431,7 @@ impl MarkdownElement { pending: true, }; window.focus(&markdown.focus_handle); - window.prevent_default() + window.prevent_default(); } cx.notify(); @@ -492,11 +471,15 @@ impl MarkdownElement { }); self.on_mouse_event(window, cx, { let rendered_text = rendered_text.clone(); - move |markdown, event: &MouseUpEvent, phase, _, cx| { + move |markdown, event: &MouseUpEvent, phase, window, cx| { if phase.bubble() { if let Some(pressed_link) = markdown.pressed_link.take() { if Some(&pressed_link) == rendered_text.link_for_position(event.position) { - cx.open_url(&pressed_link.destination_url); + if let Some(open_url) = markdown.open_url.as_mut() { + open_url(pressed_link.destination_url, window, cx); + } else { + cx.open_url(&pressed_link.destination_url); + } } } } else if markdown.selection.pending { @@ -617,7 +600,7 @@ impl Element for MarkdownElement { } MarkdownTag::CodeBlock(kind) => { let language = if let CodeBlockKind::Fenced(language) = kind { - self.load_language(language.as_ref(), window, cx) + parsed_markdown.languages.get(language).cloned() } else { None }; diff --git a/crates/markdown/src/parser.rs b/crates/markdown/src/parser.rs index 9e69f3192e76c2..29fe24d2621352 100644 --- a/crates/markdown/src/parser.rs +++ b/crates/markdown/src/parser.rs @@ -2,15 +2,16 @@ use gpui::SharedString; use linkify::LinkFinder; pub use pulldown_cmark::TagEnd as MarkdownTagEnd; use pulldown_cmark::{Alignment, HeadingLevel, LinkType, MetadataBlockKind, Options, Parser}; -use std::ops::Range; +use std::{collections::HashSet, ops::Range}; -pub fn parse_markdown(text: &str) -> Vec<(Range, MarkdownEvent)> { +pub fn parse_markdown(text: &str) -> (Vec<(Range, MarkdownEvent)>, HashSet) { let mut options = Options::all(); options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST); options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS); options.remove(pulldown_cmark::Options::ENABLE_MATH); let mut events = Vec::new(); + let mut languages = HashSet::new(); let mut within_link = false; let mut within_metadata = false; for (pulldown_event, mut range) in Parser::new_ext(text, options).into_offset_iter() { @@ -27,6 +28,11 @@ pub fn parse_markdown(text: &str) -> Vec<(Range, MarkdownEvent)> { match tag { pulldown_cmark::Tag::Link { .. } => within_link = true, pulldown_cmark::Tag::MetadataBlock { .. } => within_metadata = true, + pulldown_cmark::Tag::CodeBlock(pulldown_cmark::CodeBlockKind::Fenced( + ref language, + )) => { + languages.insert(SharedString::from(language.to_string())); + } _ => {} } events.push((range, MarkdownEvent::Start(tag.into()))) @@ -102,7 +108,7 @@ pub fn parse_markdown(text: &str) -> Vec<(Range, MarkdownEvent)> { pulldown_cmark::Event::InlineMath(_) | pulldown_cmark::Event::DisplayMath(_) => {} } } - events + (events, languages) } pub fn parse_links_only(mut text: &str) -> Vec<(Range, MarkdownEvent)> { diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index 382a42f19cfed2..acedf9e583ddcb 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -383,7 +383,7 @@ impl MarkdownPreviewView { (contents, file_location) })?; - let parsing_task = cx.background_executor().spawn(async move { + let parsing_task = cx.background_spawn(async move { parse_markdown(&contents, file_location, Some(language_registry)).await }); let contents = parsing_task.await; diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 5dbc806eaa1ba3..9db29352bf4523 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -13,7 +13,7 @@ use buffer_diff::{ use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; use futures::{channel::mpsc, SinkExt}; -use gpui::{App, Context, Entity, EntityId, EventEmitter, Task}; +use gpui::{App, AppContext as _, Context, Entity, EntityId, EventEmitter, Task}; use itertools::Itertools; use language::{ language_settings::{language_settings, IndentGuideSettings, LanguageSettings}, @@ -51,9 +51,6 @@ use text::{ use theme::SyntaxTheme; use util::post_inc; -#[cfg(any(test, feature = "test-support"))] -use gpui::AppContext as _; - const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -1580,20 +1577,19 @@ impl MultiBuffer { buffer_ids.push(buffer_id); - cx.background_executor() - .spawn({ - let mut excerpt_ranges_tx = excerpt_ranges_tx.clone(); - - async move { - let (excerpt_ranges, counts) = - build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); - excerpt_ranges_tx - .send((buffer_id, buffer.clone(), ranges, excerpt_ranges, counts)) - .await - .ok(); - } - }) - .detach() + cx.background_spawn({ + let mut excerpt_ranges_tx = excerpt_ranges_tx.clone(); + + async move { + let (excerpt_ranges, counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + excerpt_ranges_tx + .send((buffer_id, buffer.clone(), ranges, excerpt_ranges, counts)) + .await + .ok(); + } + }) + .detach() } cx.spawn(move |this, mut cx| async move { diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index d6f97fe906ddac..1b0b48ac0084a7 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -171,7 +171,7 @@ impl SearchState { }) .collect(), highlight_search_match_tx, - _search_match_highlighter: cx.background_executor().spawn(async move { + _search_match_highlighter: cx.background_spawn(async move { while let Ok(highlight_arguments) = highlight_search_match_rx.recv().await { let needs_init = highlight_arguments.search_data.get().is_none(); let search_data = highlight_arguments.search_data.get_or_init(|| { @@ -681,8 +681,7 @@ impl OutlinePanel { mut cx: AsyncWindowContext, ) -> anyhow::Result> { let serialized_panel = cx - .background_executor() - .spawn(async move { KEY_VALUE_STORE.read_kvp(OUTLINE_PANEL_KEY) }) + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(OUTLINE_PANEL_KEY) }) .await .context("loading outline panel") .log_err() @@ -849,7 +848,7 @@ impl OutlinePanel { fn serialize(&mut self, cx: &mut Context) { let width = self.width; let active = Some(self.active); - self.pending_serialization = cx.background_executor().spawn( + self.pending_serialization = cx.background_spawn( async move { KEY_VALUE_STORE .write_kvp( @@ -2631,8 +2630,7 @@ impl OutlinePanel { new_depth_map, new_children_count, )) = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let mut processed_external_buffers = HashSet::default(); let mut new_worktree_entries = BTreeMap::>::default(); @@ -3224,8 +3222,7 @@ impl OutlinePanel { (buffer_id, excerpt_id), cx.spawn_in(window, |outline_panel, mut cx| async move { let fetched_outlines = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { buffer_snapshot .outline_items_containing( excerpt_range.context, diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 93af53a69db83e..b67d4a9cc6625a 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -350,7 +350,7 @@ impl RemoteBufferStore { fn open_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Task>> { let project_id = self.project_id; let client = self.upstream_client.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let response = client .request(proto::OpenUnstagedDiff { project_id, @@ -370,7 +370,7 @@ impl RemoteBufferStore { let project_id = self.project_id; let client = self.upstream_client.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let response = client .request(proto::OpenUncommittedDiff { project_id, @@ -406,9 +406,7 @@ impl RemoteBufferStore { return Ok(buffer); } - cx.background_executor() - .spawn(async move { rx.await? }) - .await + cx.background_spawn(async move { rx.await? }).await }) } @@ -847,8 +845,7 @@ impl LocalBufferStore { let snapshot = worktree_handle.update(&mut cx, |tree, _| tree.as_local().unwrap().snapshot())?; let diff_bases_changes_by_buffer = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { diff_state_updates .into_iter() .filter_map( @@ -1133,8 +1130,7 @@ impl LocalBufferStore { cx.spawn(move |_, mut cx| async move { let loaded = load_file.await?; let text_buffer = cx - .background_executor() - .spawn(async move { text::Buffer::new(0, buffer_id, loaded.text) }) + .background_spawn(async move { text::Buffer::new(0, buffer_id, loaded.text) }) .await; cx.insert_entity(reservation, |_| { Buffer::build(text_buffer, Some(loaded.file), Capability::ReadWrite) @@ -1369,8 +1365,7 @@ impl BufferStore { } }; - cx.background_executor() - .spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) + cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) } pub fn open_unstaged_diff( @@ -1410,8 +1405,7 @@ impl BufferStore { } }; - cx.background_executor() - .spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) + cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) } pub fn open_uncommitted_diff( @@ -1431,7 +1425,7 @@ impl BufferStore { BufferStoreState::Local(this) => { let committed_text = this.load_committed_text(&buffer, cx); let staged_text = this.load_staged_text(&buffer, cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let committed_text = committed_text.await?; let staged_text = staged_text.await?; let diff_bases_change = if committed_text == staged_text { @@ -1467,8 +1461,7 @@ impl BufferStore { } }; - cx.background_executor() - .spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) + cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) } async fn open_diff_internal( @@ -1609,7 +1602,7 @@ impl BufferStore { anyhow::Ok(Some((repo, relative_path, content))) }); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let Some((repo, relative_path, content)) = blame_params? else { return Ok(None); }; @@ -2133,24 +2126,23 @@ impl BufferStore { }) .log_err(); - cx.background_executor() - .spawn( - async move { - let operations = operations.await; - for chunk in split_operations(operations) { - client - .request(proto::UpdateBuffer { - project_id, - buffer_id: buffer_id.into(), - operations: chunk, - }) - .await?; - } - anyhow::Ok(()) + cx.background_spawn( + async move { + let operations = operations.await; + for chunk in split_operations(operations) { + client + .request(proto::UpdateBuffer { + project_id, + buffer_id: buffer_id.into(), + operations: chunk, + }) + .await?; } - .log_err(), - ) - .detach(); + anyhow::Ok(()) + } + .log_err(), + ) + .detach(); } } Ok(response) @@ -2585,27 +2577,26 @@ impl BufferStore { if client.send(initial_state).log_err().is_some() { let client = client.clone(); - cx.background_executor() - .spawn(async move { - let mut chunks = split_operations(operations).peekable(); - while let Some(chunk) = chunks.next() { - let is_last = chunks.peek().is_none(); - client.send(proto::CreateBufferForPeer { - project_id, - peer_id: Some(peer_id), - variant: Some(proto::create_buffer_for_peer::Variant::Chunk( - proto::BufferChunk { - buffer_id: buffer_id.into(), - operations: chunk, - is_last, - }, - )), - })?; - } - anyhow::Ok(()) - }) - .await - .log_err(); + cx.background_spawn(async move { + let mut chunks = split_operations(operations).peekable(); + while let Some(chunk) = chunks.next() { + let is_last = chunks.peek().is_none(); + client.send(proto::CreateBufferForPeer { + project_id, + peer_id: Some(peer_id), + variant: Some(proto::create_buffer_for_peer::Variant::Chunk( + proto::BufferChunk { + buffer_id: buffer_id.into(), + operations: chunk, + is_last, + }, + )), + })?; + } + anyhow::Ok(()) + }) + .await + .log_err(); } Ok(()) }) diff --git a/crates/project/src/environment.rs b/crates/project/src/environment.rs index dce26fee3d811b..b6965abf7a1e6f 100644 --- a/crates/project/src/environment.rs +++ b/crates/project/src/environment.rs @@ -135,8 +135,7 @@ impl ProjectEnvironment { cx.spawn(|this, mut cx| async move { let (mut shell_env, error_message) = cx - .background_executor() - .spawn({ + .background_spawn({ let worktree_abs_path = worktree_abs_path.clone(); async move { load_worktree_shell_environment(&worktree_abs_path, &load_direnv).await diff --git a/crates/project/src/git.rs b/crates/project/src/git.rs index 4be911deafd875..505faba60ce7b6 100644 --- a/crates/project/src/git.rs +++ b/crates/project/src/git.rs @@ -11,8 +11,8 @@ use git::{ status::{GitSummary, TrackedSummary}, }; use gpui::{ - App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task, - WeakEntity, + App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, + Task, WeakEntity, }; use language::{Buffer, LanguageRegistry}; use rpc::proto::{git_reset, ToProto}; @@ -242,10 +242,7 @@ impl GitStore { mpsc::unbounded::<(Message, oneshot::Sender>)>(); cx.spawn(|_, cx| async move { while let Some((msg, respond)) = update_receiver.next().await { - let result = cx - .background_executor() - .spawn(Self::process_git_msg(msg)) - .await; + let result = cx.background_spawn(Self::process_git_msg(msg)).await; respond.send(result).ok(); } }) @@ -841,15 +838,14 @@ impl Repository { match self.git_repo.clone() { GitRepo::Local(git_repository) => { let commit = commit.to_string(); - cx.background_executor() - .spawn(async move { git_repository.show(&commit) }) + cx.background_spawn(async move { git_repository.show(&commit) }) } GitRepo::Remote { project_id, client, worktree_id, work_directory_id, - } => cx.background_executor().spawn(async move { + } => cx.background_spawn(async move { let resp = client .request(proto::GitShow { project_id: project_id.0, diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 4aa42e57ddac4b..73700e55327add 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -404,7 +404,7 @@ impl ImageStore { } }; - cx.background_executor().spawn(async move { + cx.background_spawn(async move { Self::wait_for_loading_image(loading_watch) .await .map_err(|e| e.cloned()) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index cdfed79a4b1912..d32ef75283c6a6 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -27,7 +27,8 @@ use futures::{ }; use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder}; use gpui::{ - App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, PromptLevel, Task, WeakEntity, + App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, PromptLevel, SharedString, Task, + WeakEntity, }; use http_client::HttpClient; use itertools::Itertools as _; @@ -35,13 +36,12 @@ use language::{ language_settings::{ language_settings, FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, }, - markdown, point_to_lsp, prepare_completion_documentation, + point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Bias, BinaryStatus, Buffer, BufferSnapshot, CachedLspAdapter, - CodeLabel, CompletionDocumentation, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, - File as _, Language, LanguageRegistry, LanguageToolchainStore, LocalFile, LspAdapter, - LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, - Unclipped, + CodeLabel, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, File as _, Language, + LanguageRegistry, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, + PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use lsp::{ notification::DidRenameFiles, CodeActionKind, CompletionContext, DiagnosticSeverity, @@ -2146,7 +2146,7 @@ impl LocalLspStore { cx: &mut Context, ) -> Task, String)>>> { let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let snapshot = snapshot?; let mut lsp_edits = lsp_edits .into_iter() @@ -3295,16 +3295,15 @@ impl LspStore { } } else if let Some((upstream_client, upstream_project_id)) = self.upstream_client() { let buffer_id = buffer.read(cx).remote_id().to_proto(); - cx.background_executor() - .spawn(async move { - upstream_client - .request(proto::RegisterBufferWithLanguageServers { - project_id: upstream_project_id, - buffer_id, - }) - .await - }) - .detach(); + cx.background_spawn(async move { + upstream_client + .request(proto::RegisterBufferWithLanguageServers { + project_id: upstream_project_id, + buffer_id, + }) + .await + }) + .detach(); } else { panic!("oops!"); } @@ -4218,14 +4217,8 @@ impl LspStore { cx.foreground_executor().spawn(async move { let completions = task.await?; let mut result = Vec::new(); - populate_labels_for_completions( - completions, - &language_registry, - language, - lsp_adapter, - &mut result, - ) - .await; + populate_labels_for_completions(completions, language, lsp_adapter, &mut result) + .await; Ok(result) }) } else if let Some(local) = self.as_local() { @@ -4274,7 +4267,6 @@ impl LspStore { if let Ok(new_completions) = task.await { populate_labels_for_completions( new_completions, - &language_registry, language.clone(), lsp_adapter, &mut completions, @@ -4298,7 +4290,6 @@ impl LspStore { cx: &mut Context, ) -> Task> { let client = self.upstream_client(); - let language_registry = self.languages.clone(); let buffer_id = buffer.read(cx).remote_id(); let buffer_snapshot = buffer.read(cx).snapshot(); @@ -4316,7 +4307,6 @@ impl LspStore { completions.clone(), completion_index, client.clone(), - language_registry.clone(), ) .await .log_err() @@ -4357,7 +4347,6 @@ impl LspStore { &buffer_snapshot, completions.clone(), completion_index, - language_registry.clone(), ) .await .log_err(); @@ -4433,22 +4422,14 @@ impl LspStore { snapshot: &BufferSnapshot, completions: Rc>>, completion_index: usize, - language_registry: Arc, ) -> Result<()> { let completion_item = completions.borrow()[completion_index] .lsp_completion .clone(); - if let Some(lsp_documentation) = completion_item.documentation.as_ref() { - let documentation = language::prepare_completion_documentation( - lsp_documentation, - &language_registry, - snapshot.language().cloned(), - ) - .await; - + if let Some(lsp_documentation) = completion_item.documentation.clone() { let mut completions = completions.borrow_mut(); let completion = &mut completions[completion_index]; - completion.documentation = Some(documentation); + completion.documentation = Some(lsp_documentation.into()); } else { let mut completions = completions.borrow_mut(); let completion = &mut completions[completion_index]; @@ -4501,7 +4482,6 @@ impl LspStore { completions: Rc>>, completion_index: usize, client: AnyProtoClient, - language_registry: Arc, ) -> Result<()> { let lsp_completion = { let completion = &completions.borrow()[completion_index]; @@ -4528,14 +4508,11 @@ impl LspStore { let documentation = if response.documentation.is_empty() { CompletionDocumentation::Undocumented } else if response.documentation_is_markdown { - CompletionDocumentation::MultiLineMarkdown( - markdown::parse_markdown(&response.documentation, Some(&language_registry), None) - .await, - ) + CompletionDocumentation::MultiLineMarkdown(response.documentation.into()) } else if response.documentation.lines().count() <= 1 { - CompletionDocumentation::SingleLine(response.documentation) + CompletionDocumentation::SingleLine(response.documentation.into()) } else { - CompletionDocumentation::MultiLinePlainText(response.documentation) + CompletionDocumentation::MultiLinePlainText(response.documentation.into()) }; let mut completions = completions.borrow_mut(); @@ -6720,7 +6697,7 @@ impl LspStore { return Err(anyhow!("No language server {id}")); }; - Ok(cx.background_executor().spawn(async move { + Ok(cx.background_spawn(async move { let can_resolve = server .capabilities() .completion_provider @@ -7388,9 +7365,7 @@ impl LspStore { .map(|b| b.read(cx).remote_id().to_proto()) .collect(), }); - cx.background_executor() - .spawn(request) - .detach_and_log_err(cx); + cx.background_spawn(request).detach_and_log_err(cx); } else { let Some(local) = self.as_local_mut() else { return; @@ -7419,9 +7394,7 @@ impl LspStore { .collect::>(); cx.spawn(|this, mut cx| async move { - cx.background_executor() - .spawn(futures::future::join_all(tasks)) - .await; + cx.background_spawn(futures::future::join_all(tasks)).await; this.update(&mut cx, |this, cx| { for buffer in buffers { this.register_buffer_with_language_servers(&buffer, true, cx); @@ -7750,9 +7723,7 @@ impl LspStore { }, )), }); - cx.background_executor() - .spawn(request) - .detach_and_log_err(cx); + cx.background_spawn(request).detach_and_log_err(cx); } else if let Some(local) = self.as_local() { let servers = buffers .into_iter() @@ -7808,9 +7779,7 @@ impl LspStore { ), ), }); - cx.background_executor() - .spawn(request) - .detach_and_log_err(cx); + cx.background_spawn(request).detach_and_log_err(cx); } } @@ -8082,7 +8051,6 @@ fn remove_empty_hover_blocks(mut hover: Hover) -> Option { async fn populate_labels_for_completions( mut new_completions: Vec, - language_registry: &Arc, language: Option>, lsp_adapter: Option>, completions: &mut Vec, @@ -8107,8 +8075,8 @@ async fn populate_labels_for_completions( .zip(lsp_completions) .zip(labels.into_iter().chain(iter::repeat(None))) { - let documentation = if let Some(docs) = &lsp_completion.documentation { - Some(prepare_completion_documentation(docs, language_registry, language.clone()).await) + let documentation = if let Some(docs) = lsp_completion.documentation.clone() { + Some(docs.into()) } else { None }; @@ -8499,6 +8467,46 @@ impl DiagnosticSummary { } } +#[derive(Clone, Debug)] +pub enum CompletionDocumentation { + /// There is no documentation for this completion. + Undocumented, + /// A single line of documentation. + SingleLine(SharedString), + /// Multiple lines of plain text documentation. + MultiLinePlainText(SharedString), + /// Markdown documentation. + MultiLineMarkdown(SharedString), +} + +impl From for CompletionDocumentation { + fn from(docs: lsp::Documentation) -> Self { + match docs { + lsp::Documentation::String(text) => { + if text.lines().count() <= 1 { + CompletionDocumentation::SingleLine(text.into()) + } else { + CompletionDocumentation::MultiLinePlainText(text.into()) + } + } + + lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind { + lsp::MarkupKind::PlainText => { + if value.lines().count() <= 1 { + CompletionDocumentation::SingleLine(value.into()) + } else { + CompletionDocumentation::MultiLinePlainText(value.into()) + } + } + + lsp::MarkupKind::Markdown => { + CompletionDocumentation::MultiLineMarkdown(value.into()) + } + }, + } + } +} + fn glob_literal_prefix(glob: &Path) -> PathBuf { glob.components() .take_while(|component| match component { diff --git a/crates/project/src/prettier_store.rs b/crates/project/src/prettier_store.rs index fdb76f81b22188..91da792ae3827e 100644 --- a/crates/project/src/prettier_store.rs +++ b/crates/project/src/prettier_store.rs @@ -12,7 +12,7 @@ use futures::{ stream::FuturesUnordered, FutureExt, }; -use gpui::{AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity}; +use gpui::{AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity}; use language::{ language_settings::{Formatter, LanguageSettings, SelectedFormatter}, Buffer, LanguageRegistry, LocalFile, @@ -121,8 +121,7 @@ impl PrettierStore { let installed_prettiers = self.prettier_instances.keys().cloned().collect(); cx.spawn(|lsp_store, mut cx| async move { match cx - .background_executor() - .spawn(async move { + .background_spawn(async move { Prettier::locate_prettier_installation( fs.as_ref(), &installed_prettiers, @@ -234,8 +233,7 @@ impl PrettierStore { .unwrap_or_default(); cx.spawn(|lsp_store, mut cx| async move { match cx - .background_executor() - .spawn(async move { + .background_spawn(async move { Prettier::locate_prettier_ignore( fs.as_ref(), &prettier_ignores, @@ -483,31 +481,30 @@ impl PrettierStore { })) .collect::>(); - cx.background_executor() - .spawn(async move { - let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| { - async move { - if let Some(instance) = prettier_instance.prettier { - match instance.await { - Ok(prettier) => { - prettier.clear_cache().log_err().await; - }, - Err(e) => { - match prettier_path { - Some(prettier_path) => log::error!( - "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}" - ), - None => log::error!( - "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}" - ), - } - }, - } + cx.background_spawn(async move { + let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| { + async move { + if let Some(instance) = prettier_instance.prettier { + match instance.await { + Ok(prettier) => { + prettier.clear_cache().log_err().await; + }, + Err(e) => { + match prettier_path { + Some(prettier_path) => log::error!( + "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}" + ), + None => log::error!( + "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}" + ), + } + }, } } - })) - .await; - }) + } + })) + .await; + }) .detach(); } } @@ -539,7 +536,7 @@ impl PrettierStore { }) { Some(locate_from) => { let installed_prettiers = self.prettier_instances.keys().cloned().collect(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { Prettier::locate_prettier_installation( fs.as_ref(), &installed_prettiers, @@ -631,13 +628,12 @@ impl PrettierStore { })?; if needs_install { let installed_plugins = new_plugins.clone(); - cx.background_executor() - .spawn(async move { - install_prettier_packages(fs.as_ref(), new_plugins, node).await?; - // Save the server file last, so the reinstall need could be determined by the absence of the file. - save_prettier_server_file(fs.as_ref()).await?; - anyhow::Ok(()) - }) + cx.background_spawn(async move { + install_prettier_packages(fs.as_ref(), new_plugins, node).await?; + // Save the server file last, so the reinstall need could be determined by the absence of the file. + save_prettier_server_file(fs.as_ref()).await?; + anyhow::Ok(()) + }) .await .context("prettier & plugins install") .map_err(Arc::new)?; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8cc6da5c97c73a..9072571c2be56c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -73,15 +73,15 @@ use gpui::{ use itertools::Itertools; use language::{ language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent, Capability, - CodeLabel, CompletionDocumentation, File as _, Language, LanguageName, LanguageRegistry, - PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList, Transaction, Unclipped, + CodeLabel, File as _, Language, LanguageName, LanguageRegistry, PointUtf16, ToOffset, + ToPointUtf16, Toolchain, ToolchainList, Transaction, Unclipped, }; use lsp::{ CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServerId, LanguageServerName, MessageActionItem, }; use lsp_command::*; -use lsp_store::{LspFormatTarget, OpenLspBufferHandle}; +use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle}; use node_runtime::NodeRuntime; use parking_lot::Mutex; pub use prettier_store::PrettierStore; @@ -595,7 +595,7 @@ impl DirectoryLister { } DirectoryLister::Local(fs) => { let fs = fs.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut results = vec![]; let expanded = shellexpand::tilde(&path); let query = Path::new(expanded.as_ref()); @@ -1275,13 +1275,12 @@ impl Project { .read(cx) .shutdown_processes(Some(proto::ShutdownRemoteServer {})); - cx.background_executor() - .spawn(async move { - if let Some(shutdown) = shutdown { - shutdown.await; - } - }) - .detach() + cx.background_spawn(async move { + if let Some(shutdown) = shutdown { + shutdown.await; + } + }) + .detach() } match &self.client_state { @@ -3337,7 +3336,7 @@ impl Project { let buffer = buffer.clone(); let query = query.clone(); let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?; - chunk_results.push(cx.background_executor().spawn(async move { + chunk_results.push(cx.background_spawn(async move { let ranges = query .search(&snapshot, None) .await @@ -3576,7 +3575,7 @@ impl Project { cx: &mut Context, ) -> Task> { let resolve_task = self.resolve_abs_path(path, cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let resolved_path = resolve_task.await; resolved_path.filter(|path| path.is_file()) }) @@ -3590,7 +3589,7 @@ impl Project { if self.is_local() { let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned()); let fs = self.fs.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let path = expanded.as_path(); let metadata = fs.metadata(path).await.ok().flatten(); @@ -3608,7 +3607,7 @@ impl Project { project_id: SSH_PROJECT_ID, path: request_path.to_proto(), }); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let response = request.await.log_err()?; if response.exists { Some(ResolvedPath::AbsPath { @@ -3689,7 +3688,7 @@ impl Project { }; let response = session.read(cx).proto_client().request(request); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let response = response.await?; Ok(response.entries.into_iter().map(PathBuf::from).collect()) }) @@ -4128,8 +4127,7 @@ impl Project { if let Some(remote_id) = this.remote_id() { let mut payload = envelope.payload.clone(); payload.project_id = remote_id; - cx.background_executor() - .spawn(this.client.request(payload)) + cx.background_spawn(this.client.request(payload)) .detach_and_log_err(cx); } this.buffer_store.clone() @@ -4146,8 +4144,7 @@ impl Project { if let Some(ssh) = &this.ssh_client { let mut payload = envelope.payload.clone(); payload.project_id = SSH_PROJECT_ID; - cx.background_executor() - .spawn(ssh.read(cx).proto_client().request(payload)) + cx.background_spawn(ssh.read(cx).proto_client().request(payload)) .detach_and_log_err(cx); } this.buffer_store.clone() @@ -4368,7 +4365,7 @@ impl Project { if let Some(buffer) = this.buffer_for_id(buffer_id, cx) { let operations = buffer.read(cx).serialize_ops(Some(remote_version), cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let operations = operations.await; for chunk in split_operations(operations) { client @@ -4391,12 +4388,11 @@ impl Project { // Any incomplete buffers have open requests waiting. Request that the host sends // creates these buffers for us again to unblock any waiting futures. for id in incomplete_buffer_ids { - cx.background_executor() - .spawn(client.request(proto::OpenBufferById { - project_id, - id: id.into(), - })) - .detach(); + cx.background_spawn(client.request(proto::OpenBufferById { + project_id, + id: id.into(), + })) + .detach(); } futures::future::join_all(send_updates_for_buffers) diff --git a/crates/project/src/project_tree.rs b/crates/project/src/project_tree.rs index 4f13a0c7ec8592..8e73c8c7d97b1c 100644 --- a/crates/project/src/project_tree.rs +++ b/crates/project/src/project_tree.rs @@ -13,7 +13,7 @@ use std::{ }; use collections::HashMap; -use gpui::{App, AppContext, Context, Entity, EventEmitter, Subscription}; +use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription}; use language::{CachedLspAdapter, LspAdapterDelegate}; use lsp::LanguageServerName; use path_trie::{LabelPresence, RootPathTrie, TriePath}; diff --git a/crates/project/src/project_tree/server_tree.rs b/crates/project/src/project_tree/server_tree.rs index 138b30627b9234..fa1221637e4989 100644 --- a/crates/project/src/project_tree/server_tree.rs +++ b/crates/project/src/project_tree/server_tree.rs @@ -15,7 +15,7 @@ use std::{ }; use collections::{HashMap, IndexMap}; -use gpui::{App, AppContext, Entity, Subscription}; +use gpui::{App, AppContext as _, Entity, Subscription}; use itertools::Itertools; use language::{ language_settings::AllLanguageSettings, Attach, LanguageName, LanguageRegistry, diff --git a/crates/project/src/toolchain_store.rs b/crates/project/src/toolchain_store.rs index d4af70e41f8457..8464eff6a13fc5 100644 --- a/crates/project/src/toolchain_store.rs +++ b/crates/project/src/toolchain_store.rs @@ -325,16 +325,15 @@ impl LocalToolchainStore { .ok()? .await; - cx.background_executor() - .spawn(async move { - let language = registry - .language_for_name(language_name.as_ref()) - .await - .ok()?; - let toolchains = language.toolchain_lister()?; - Some(toolchains.list(root.to_path_buf(), project_env).await) - }) - .await + cx.background_spawn(async move { + let language = registry + .language_for_name(language_name.as_ref()) + .await + .ok()?; + let toolchains = language.toolchain_lister()?; + Some(toolchains.list(root.to_path_buf(), project_env).await) + }) + .await }) } pub(crate) fn active_toolchain( diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index 73b49775e64bd4..e2f8541161253d 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -13,7 +13,9 @@ use futures::{ FutureExt, SinkExt, }; use git::repository::Branch; -use gpui::{App, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity}; +use gpui::{ + App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity, +}; use postage::oneshot; use rpc::{ proto::{self, FromProto, ToProto, SSH_PROJECT_ID}, @@ -179,8 +181,7 @@ impl WorktreeStore { Task::ready(Ok((tree, relative_path))) } else { let worktree = self.create_worktree(abs_path, visible, cx); - cx.background_executor() - .spawn(async move { Ok((worktree.await?, PathBuf::new())) }) + cx.background_spawn(async move { Ok((worktree.await?, PathBuf::new())) }) } } @@ -679,7 +680,7 @@ impl WorktreeStore { let (output_tx, output_rx) = smol::channel::bounded(64); let (matching_paths_tx, matching_paths_rx) = smol::channel::unbounded(); - let input = cx.background_executor().spawn({ + let input = cx.background_spawn({ let fs = fs.clone(); let query = query.clone(); async move { @@ -696,7 +697,7 @@ impl WorktreeStore { } }); const MAX_CONCURRENT_FILE_SCANS: usize = 64; - let filters = cx.background_executor().spawn(async move { + let filters = cx.background_spawn(async move { let fs = &fs; let query = &query; executor @@ -712,25 +713,24 @@ impl WorktreeStore { }) .await; }); - cx.background_executor() - .spawn(async move { - let mut matched = 0; - while let Ok(mut receiver) = output_rx.recv().await { - let Some(path) = receiver.next().await else { - continue; - }; - let Ok(_) = matching_paths_tx.send(path).await else { - break; - }; - matched += 1; - if matched == limit { - break; - } + cx.background_spawn(async move { + let mut matched = 0; + while let Ok(mut receiver) = output_rx.recv().await { + let Some(path) = receiver.next().await else { + continue; + }; + let Ok(_) = matching_paths_tx.send(path).await else { + break; + }; + matched += 1; + if matched == limit { + break; } - drop(input); - drop(filters); - }) - .detach(); + } + drop(input); + drop(filters); + }) + .detach(); matching_paths_rx } @@ -934,7 +934,7 @@ impl WorktreeStore { }), }); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let response = request.await?; let branches = response @@ -1021,7 +1021,7 @@ impl WorktreeStore { branch_name: new_branch, }); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { request.await?; Ok(()) }) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 1c040320e5f150..dc05cbaed5328e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -544,8 +544,7 @@ impl ProjectPanel { mut cx: AsyncWindowContext, ) -> Result> { let serialized_panel = cx - .background_executor() - .spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) }) + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(PROJECT_PANEL_KEY) }) .await .map_err(|e| anyhow!("Failed to load project panel: {}", e)) .log_err() @@ -627,7 +626,7 @@ impl ProjectPanel { fn serialize(&mut self, cx: &mut Context) { let width = self.width; - self.pending_serialization = cx.background_executor().spawn( + self.pending_serialization = cx.background_spawn( async move { KEY_VALUE_STORE .write_kvp( diff --git a/crates/prompt_library/src/prompt_library.rs b/crates/prompt_library/src/prompt_library.rs index a3ec59cad71b2a..b129a8ccbe51f5 100644 --- a/crates/prompt_library/src/prompt_library.rs +++ b/crates/prompt_library/src/prompt_library.rs @@ -218,8 +218,7 @@ impl PickerDelegate for PromptPickerDelegate { let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id); cx.spawn_in(window, |this, mut cx| async move { let (matches, selected_index) = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let matches = search.await; let selected_index = prev_prompt_id diff --git a/crates/prompt_library/src/prompts.rs b/crates/prompt_library/src/prompts.rs index f8a453d113ecd0..4fafbce2a3a955 100644 --- a/crates/prompt_library/src/prompts.rs +++ b/crates/prompt_library/src/prompts.rs @@ -2,7 +2,7 @@ use anyhow::Result; use assets::Assets; use fs::Fs; use futures::StreamExt; -use gpui::{App, AssetSource}; +use gpui::{App, AppContext as _, AssetSource}; use handlebars::{Handlebars, RenderError}; use language::{BufferSnapshot, LanguageName, Point}; use parking_lot::Mutex; @@ -103,103 +103,102 @@ impl PromptBuilder { handlebars: Arc>>, ) { let templates_dir = paths::prompt_overrides_dir(params.repo_path.as_deref()); - params.cx.background_executor() - .spawn(async move { - let Some(parent_dir) = templates_dir.parent() else { - return; - }; - - let mut found_dir_once = false; - loop { - // Check if the templates directory exists and handle its status - // If it exists, log its presence and check if it's a symlink - // If it doesn't exist: - // - Log that we're using built-in prompts - // - Check if it's a broken symlink and log if so - // - Set up a watcher to detect when it's created - // After the first check, set the `found_dir_once` flag - // This allows us to avoid logging when looping back around after deleting the prompt overrides directory. - let dir_status = params.fs.is_dir(&templates_dir).await; - let symlink_status = params.fs.read_link(&templates_dir).await.ok(); - if dir_status { - let mut log_message = format!("Prompt template overrides directory found at {}", templates_dir.display()); + params.cx.background_spawn(async move { + let Some(parent_dir) = templates_dir.parent() else { + return; + }; + + let mut found_dir_once = false; + loop { + // Check if the templates directory exists and handle its status + // If it exists, log its presence and check if it's a symlink + // If it doesn't exist: + // - Log that we're using built-in prompts + // - Check if it's a broken symlink and log if so + // - Set up a watcher to detect when it's created + // After the first check, set the `found_dir_once` flag + // This allows us to avoid logging when looping back around after deleting the prompt overrides directory. + let dir_status = params.fs.is_dir(&templates_dir).await; + let symlink_status = params.fs.read_link(&templates_dir).await.ok(); + if dir_status { + let mut log_message = format!("Prompt template overrides directory found at {}", templates_dir.display()); + if let Some(target) = symlink_status { + log_message.push_str(" -> "); + log_message.push_str(&target.display().to_string()); + } + log::info!("{}.", log_message); + } else { + if !found_dir_once { + log::info!("No prompt template overrides directory found at {}. Using built-in prompts.", templates_dir.display()); if let Some(target) = symlink_status { - log_message.push_str(" -> "); - log_message.push_str(&target.display().to_string()); - } - log::info!("{}.", log_message); - } else { - if !found_dir_once { - log::info!("No prompt template overrides directory found at {}. Using built-in prompts.", templates_dir.display()); - if let Some(target) = symlink_status { - log::info!("Symlink found pointing to {}, but target is invalid.", target.display()); - } + log::info!("Symlink found pointing to {}, but target is invalid.", target.display()); } + } - if params.fs.is_dir(parent_dir).await { - let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await; - while let Some(changed_paths) = changes.next().await { - if changed_paths.iter().any(|p| &p.path == &templates_dir) { - let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display()); - if let Ok(target) = params.fs.read_link(&templates_dir).await { - log_message.push_str(" -> "); - log_message.push_str(&target.display().to_string()); - } - log::info!("{}.", log_message); - break; + if params.fs.is_dir(parent_dir).await { + let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await; + while let Some(changed_paths) = changes.next().await { + if changed_paths.iter().any(|p| &p.path == &templates_dir) { + let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display()); + if let Ok(target) = params.fs.read_link(&templates_dir).await { + log_message.push_str(" -> "); + log_message.push_str(&target.display().to_string()); } + log::info!("{}.", log_message); + break; } - } else { - return; } + } else { + return; } + } - found_dir_once = true; + found_dir_once = true; - // Initial scan of the prompt overrides directory - if let Ok(mut entries) = params.fs.read_dir(&templates_dir).await { - while let Some(Ok(file_path)) = entries.next().await { - if file_path.to_string_lossy().ends_with(".hbs") { - if let Ok(content) = params.fs.load(&file_path).await { - let file_name = file_path.file_stem().unwrap().to_string_lossy(); - log::debug!("Registering prompt template override: {}", file_name); - handlebars.lock().register_template_string(&file_name, content).log_err(); - } + // Initial scan of the prompt overrides directory + if let Ok(mut entries) = params.fs.read_dir(&templates_dir).await { + while let Some(Ok(file_path)) = entries.next().await { + if file_path.to_string_lossy().ends_with(".hbs") { + if let Ok(content) = params.fs.load(&file_path).await { + let file_name = file_path.file_stem().unwrap().to_string_lossy(); + log::debug!("Registering prompt template override: {}", file_name); + handlebars.lock().register_template_string(&file_name, content).log_err(); } } } + } - // Watch both the parent directory and the template overrides directory: - // - Monitor the parent directory to detect if the template overrides directory is deleted. - // - Monitor the template overrides directory to re-register templates when they change. - // Combine both watch streams into a single stream. - let (parent_changes, parent_watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await; - let (changes, watcher) = params.fs.watch(&templates_dir, Duration::from_secs(1)).await; - let mut combined_changes = futures::stream::select(changes, parent_changes); - - while let Some(changed_paths) = combined_changes.next().await { - if changed_paths.iter().any(|p| &p.path == &templates_dir) { - if !params.fs.is_dir(&templates_dir).await { - log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates."); - Self::register_built_in_templates(&mut handlebars.lock()).log_err(); - break; - } + // Watch both the parent directory and the template overrides directory: + // - Monitor the parent directory to detect if the template overrides directory is deleted. + // - Monitor the template overrides directory to re-register templates when they change. + // Combine both watch streams into a single stream. + let (parent_changes, parent_watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await; + let (changes, watcher) = params.fs.watch(&templates_dir, Duration::from_secs(1)).await; + let mut combined_changes = futures::stream::select(changes, parent_changes); + + while let Some(changed_paths) = combined_changes.next().await { + if changed_paths.iter().any(|p| &p.path == &templates_dir) { + if !params.fs.is_dir(&templates_dir).await { + log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates."); + Self::register_built_in_templates(&mut handlebars.lock()).log_err(); + break; } - for event in changed_paths { - if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") { - log::info!("Reloading prompt template override: {}", event.path.display()); - if let Some(content) = params.fs.load(&event.path).await.log_err() { - let file_name = event.path.file_stem().unwrap().to_string_lossy(); - handlebars.lock().register_template_string(&file_name, content).log_err(); - } + } + for event in changed_paths { + if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") { + log::info!("Reloading prompt template override: {}", event.path.display()); + if let Some(content) = params.fs.load(&event.path).await.log_err() { + let file_name = event.path.file_stem().unwrap().to_string_lossy(); + handlebars.lock().register_template_string(&file_name, content).log_err(); } } } - - drop(watcher); - drop(parent_watcher); } - }) + + drop(watcher); + drop(parent_watcher); + } + }) .detach(); } diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 249f9db116b60c..97f7106d1b5ad3 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -208,7 +208,7 @@ impl SshPrompt { ..Default::default() }; let markdown = - cx.new(|cx| Markdown::new_text(prompt, markdown_style, None, None, window, cx)); + cx.new(|cx| Markdown::new_text(prompt.into(), markdown_style, None, None, window, cx)); self.prompt = Some((markdown, tx)); self.status_message.take(); window.focus(&self.editor.focus_handle(cx)); diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index ca15f13fa55caa..51549ae5e063a9 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -17,7 +17,7 @@ use futures::{ select, select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _, }; use gpui::{ - App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Global, + App, AppContext as _, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Global, SemanticVersion, Task, WeakEntity, }; use itertools::Itertools; @@ -1158,12 +1158,11 @@ impl SshRemoteClient { c.connections.insert( opts.clone(), ConnectionPoolEntry::Connecting( - cx.background_executor() - .spawn({ - let connection = connection.clone(); - async move { Ok(connection.clone()) } - }) - .shared(), + cx.background_spawn({ + let connection = connection.clone(); + async move { Ok(connection.clone()) } + }) + .shared(), ), ); }) @@ -1358,7 +1357,7 @@ impl RemoteConnection for SshRemoteConnection { )) .output(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let output = output.await?; if !output.status.success() { @@ -1679,14 +1678,14 @@ impl SshRemoteConnection { let mut stderr_buffer = Vec::new(); let mut stderr_offset = 0; - let stdin_task = cx.background_executor().spawn(async move { + let stdin_task = cx.background_spawn(async move { while let Some(outgoing) = outgoing_rx.next().await { write_message(&mut child_stdin, &mut stdin_buffer, outgoing).await?; } anyhow::Ok(()) }); - let stdout_task = cx.background_executor().spawn({ + let stdout_task = cx.background_spawn({ let mut connection_activity_tx = connection_activity_tx.clone(); async move { loop { @@ -1711,7 +1710,7 @@ impl SshRemoteConnection { } }); - let stderr_task: Task> = cx.background_executor().spawn(async move { + let stderr_task: Task> = cx.background_spawn(async move { loop { stderr_buffer.resize(stderr_offset + 1024, 0); @@ -2449,7 +2448,7 @@ mod fake { }, select_biased, FutureExt, SinkExt, StreamExt, }; - use gpui::{App, AsyncApp, SemanticVersion, Task, TestAppContext}; + use gpui::{App, AppContext as _, AsyncApp, SemanticVersion, Task, TestAppContext}; use release_channel::ReleaseChannel; use rpc::proto::Envelope; @@ -2533,7 +2532,7 @@ mod fake { &self.server_cx.get(cx), ); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { loop { select_biased! { server_to_client = server_outgoing_rx.next().fuse() => { diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 28a08b2b6b063d..072c1161b485ce 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -263,8 +263,7 @@ impl HeadlessProject { operation, is_local: true, } => cx - .background_executor() - .spawn(self.session.request(proto::UpdateBuffer { + .background_spawn(self.session.request(proto::UpdateBuffer { project_id: SSH_PROJECT_ID, buffer_id: buffer.read(cx).remote_id().to_proto(), operations: vec![serialize_operation(operation)], @@ -325,15 +324,14 @@ impl HeadlessProject { message: prompt.message.clone(), }); let prompt = prompt.clone(); - cx.background_executor() - .spawn(async move { - let response = request.await?; - if let Some(action_response) = response.action_response { - prompt.respond(action_response as usize).await; - } - anyhow::Ok(()) - }) - .detach(); + cx.background_spawn(async move { + let response = request.await?; + if let Some(action_response) = response.action_response { + prompt.respond(action_response as usize).await; + } + anyhow::Ok(()) + }) + .detach(); } _ => {} } diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 88ffd1bbf63a2e..def19f5ab36ca5 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -311,7 +311,7 @@ fn start_server( let mut output_buffer = Vec::new(); let (mut stdin_msg_tx, mut stdin_msg_rx) = mpsc::unbounded::(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { while let Ok(msg) = read_message(&mut stdin_stream, &mut input_buffer).await { if let Err(_) = stdin_msg_tx.send(msg).await { break; @@ -487,8 +487,7 @@ pub fn execute_run( handle_panic_requests(&project, &session); - cx.background_executor() - .spawn(async move { cleanup_old_binaries() }) + cx.background_spawn(async move { cleanup_old_binaries() }) .detach(); mem::forget(project); diff --git a/crates/repl/src/kernels/native_kernel.rs b/crates/repl/src/kernels/native_kernel.rs index e68b102be85a7d..7170b43234db76 100644 --- a/crates/repl/src/kernels/native_kernel.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -5,7 +5,7 @@ use futures::{ stream::{SelectAll, StreamExt}, AsyncBufReadExt as _, SinkExt as _, }; -use gpui::{App, Entity, EntityId, Task, Window}; +use gpui::{App, AppContext as _, Entity, EntityId, Task, Window}; use jupyter_protocol::{ connection_info::{ConnectionInfo, Transport}, ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply, @@ -211,7 +211,7 @@ impl NativeRunningKernel { futures::channel::mpsc::channel(100); let (mut shell_request_tx, mut shell_request_rx) = futures::channel::mpsc::channel(100); - let routing_task = cx.background_executor().spawn({ + let routing_task = cx.background_spawn({ async move { while let Some(message) = request_rx.next().await { match message.content { @@ -229,7 +229,7 @@ impl NativeRunningKernel { } }); - let shell_task = cx.background_executor().spawn({ + let shell_task = cx.background_spawn({ async move { while let Some(message) = shell_request_rx.next().await { shell_socket.send(message).await.ok(); @@ -240,7 +240,7 @@ impl NativeRunningKernel { } }); - let control_task = cx.background_executor().spawn({ + let control_task = cx.background_spawn({ async move { while let Some(message) = control_request_rx.next().await { control_socket.send(message).await.ok(); diff --git a/crates/repl/src/kernels/remote_kernels.rs b/crates/repl/src/kernels/remote_kernels.rs index 9df8b867d9838c..6c8d8e2b26e9ab 100644 --- a/crates/repl/src/kernels/remote_kernels.rs +++ b/crates/repl/src/kernels/remote_kernels.rs @@ -1,5 +1,5 @@ use futures::{channel::mpsc, SinkExt as _}; -use gpui::{App, Entity, Task, Window}; +use gpui::{App, AppContext as _, Entity, Task, Window}; use http_client::{AsyncBody, HttpClient, Request}; use jupyter_protocol::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply}; @@ -189,7 +189,7 @@ impl RemoteRunningKernel { let (request_tx, mut request_rx) = futures::channel::mpsc::channel::(100); - let routing_task = cx.background_executor().spawn({ + let routing_task = cx.background_spawn({ async move { while let Some(message) = request_rx.next().await { w.send(message).await.ok(); diff --git a/crates/repl/src/notebook/cell.rs b/crates/repl/src/notebook/cell.rs index 41f820a99e4509..7658a106e0eacd 100644 --- a/crates/repl/src/notebook/cell.rs +++ b/crates/repl/src/notebook/cell.rs @@ -134,8 +134,7 @@ impl Cell { cx.spawn_in(window, |this, mut cx| async move { let parsed_markdown = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { parse_markdown(&source, None, Some(languages)).await }) .await; diff --git a/crates/repl/src/outputs/markdown.rs b/crates/repl/src/outputs/markdown.rs index 08517aa421341f..b29c9c82ae8ec2 100644 --- a/crates/repl/src/outputs/markdown.rs +++ b/crates/repl/src/outputs/markdown.rs @@ -19,9 +19,8 @@ impl MarkdownView { pub fn from(text: String, cx: &mut Context) -> Self { let task = cx.spawn(|markdown_view, mut cx| { let text = text.clone(); - let parsed = cx - .background_executor() - .spawn(async move { parse_markdown(&text, None, None).await }); + let parsed = + cx.background_spawn(async move { parse_markdown(&text, None, None).await }); async move { let content = parsed.await; diff --git a/crates/repl/src/outputs/plain.rs b/crates/repl/src/outputs/plain.rs index 2c467b57a720b1..f78b0f2d4c9af6 100644 --- a/crates/repl/src/outputs/plain.rs +++ b/crates/repl/src/outputs/plain.rs @@ -22,7 +22,7 @@ use alacritty_terminal::{ term::Config, vte::ansi::Processor, }; -use gpui::{canvas, size, ClipboardItem, Entity, FontStyle, TextStyle, WhiteSpace}; +use gpui::{canvas, size, Bounds, ClipboardItem, Entity, FontStyle, TextStyle, WhiteSpace}; use language::Buffer; use settings::Settings as _; use terminal_view::terminal_element::TerminalElement; @@ -85,7 +85,7 @@ pub fn text_style(window: &mut Window, cx: &mut App) -> TextStyle { } /// Returns the default terminal size for the terminal output. -pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalSize { +pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalBounds { let text_style = text_style(window, cx); let text_system = window.text_system(); @@ -106,10 +106,13 @@ pub fn terminal_size(window: &mut Window, cx: &mut App) -> terminal::TerminalSiz let width = columns as f32 * cell_width; let height = num_lines as f32 * window.line_height(); - terminal::TerminalSize { + terminal::TerminalBounds { cell_width, line_height, - size: size(width, height), + bounds: Bounds { + origin: gpui::Point::default(), + size: size(width, height), + }, } } @@ -277,10 +280,10 @@ impl Render for TerminalOutput { for rect in rects { rect.paint( bounds.origin, - &terminal::TerminalSize { + &terminal::TerminalBounds { cell_width, line_height: text_line_height, - size: bounds.size, + bounds, }, window, ); @@ -289,10 +292,10 @@ impl Render for TerminalOutput { for cell in cells { cell.paint( bounds.origin, - &terminal::TerminalSize { + &terminal::TerminalBounds { cell_width, line_height: text_line_height, - size: bounds.size, + bounds, }, bounds, window, diff --git a/crates/repl/src/repl_store.rs b/crates/repl/src/repl_store.rs index df467bd2f01e57..7293c8483df3ea 100644 --- a/crates/repl/src/repl_store.rs +++ b/crates/repl/src/repl_store.rs @@ -164,7 +164,7 @@ impl ReplStore { let remote_kernel_specifications = self.get_remote_kernel_specifications(cx); - let all_specs = cx.background_executor().spawn(async move { + let all_specs = cx.background_spawn(async move { let mut all_specs = local_kernel_specifications .await? .into_iter() diff --git a/crates/semantic_index/src/embedding_index.rs b/crates/semantic_index/src/embedding_index.rs index 85a0390a22697a..6c3c8a40ffe335 100644 --- a/crates/semantic_index/src/embedding_index.rs +++ b/crates/semantic_index/src/embedding_index.rs @@ -10,7 +10,7 @@ use fs::Fs; use fs::MTime; use futures::{stream::StreamExt, FutureExt as _}; use futures_batch::ChunksTimeoutStreamExt; -use gpui::{App, Entity, Task}; +use gpui::{App, AppContext as _, Entity, Task}; use heed::types::{SerdeBincode, Str}; use language::LanguageRegistry; use log; @@ -102,7 +102,7 @@ impl EmbeddingIndex { let db_connection = self.db_connection.clone(); let db = self.db; let entries_being_indexed = self.entry_ids_being_indexed.clone(); - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { let txn = db_connection .read_txn() .context("failed to create read transaction")?; @@ -185,7 +185,7 @@ impl EmbeddingIndex { let (updated_entries_tx, updated_entries_rx) = channel::bounded(512); let (deleted_entry_ranges_tx, deleted_entry_ranges_rx) = channel::bounded(128); let entries_being_indexed = self.entry_ids_being_indexed.clone(); - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { for (path, entry_id, status) in updated_entries.iter() { match status { project::PathChange::Added @@ -278,7 +278,7 @@ impl EmbeddingIndex { ) -> EmbedFiles { let embedding_provider = embedding_provider.clone(); let (embedded_files_tx, embedded_files_rx) = channel::bounded(512); - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { let mut chunked_file_batches = pin!(chunked_files.chunks_timeout(512, Duration::from_secs(2))); while let Some(chunked_files) = chunked_file_batches.next().await { @@ -361,7 +361,7 @@ impl EmbeddingIndex { let db_connection = self.db_connection.clone(); let db = self.db; - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut deleted_entry_ranges = pin!(deleted_entry_ranges); let mut embedded_files = pin!(embedded_files); loop { @@ -397,7 +397,7 @@ impl EmbeddingIndex { pub fn paths(&self, cx: &App) -> Task>>> { let connection = self.db_connection.clone(); let db = self.db; - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let tx = connection .read_txn() .context("failed to create read transaction")?; @@ -413,7 +413,7 @@ impl EmbeddingIndex { pub fn chunks_for_path(&self, path: Arc, cx: &App) -> Task>> { let connection = self.db_connection.clone(); let db = self.db; - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let tx = connection .read_txn() .context("failed to create read transaction")?; diff --git a/crates/semantic_index/src/project_index.rs b/crates/semantic_index/src/project_index.rs index 027b1ac3d62ea7..c99832c7af4679 100644 --- a/crates/semantic_index/src/project_index.rs +++ b/crates/semantic_index/src/project_index.rs @@ -7,7 +7,9 @@ use anyhow::{anyhow, Context as _, Result}; use collections::HashMap; use fs::Fs; use futures::FutureExt; -use gpui::{App, Context, Entity, EntityId, EventEmitter, Subscription, Task, WeakEntity}; +use gpui::{ + App, AppContext as _, Context, Entity, EntityId, EventEmitter, Subscription, Task, WeakEntity, +}; use language::LanguageRegistry; use log; use project::{Project, Worktree, WorktreeId}; @@ -250,7 +252,7 @@ impl ProjectIndex { let worktree_id = index.worktree().read(cx).id(); let db_connection = index.db_connection().clone(); let db = *index.embedding_index().db(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let txn = db_connection .read_txn() .context("failed to create read transaction")?; @@ -432,7 +434,7 @@ impl ProjectIndex { let file_digest_db = summary_index.file_digest_db(); let summary_db = summary_index.summary_db(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let txn = db_connection .read_txn() .context("failed to create db read transaction")?; @@ -519,8 +521,9 @@ impl ProjectIndex { index .read_with(&cx, |index, cx| { - cx.background_executor() - .spawn(index.summary_index().flush_backlog(worktree_abs_path, cx)) + cx.background_spawn( + index.summary_index().flush_backlog(worktree_abs_path, cx), + ) })? .await }) diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 9345965ccd7274..0ae076b40f21e7 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -42,8 +42,7 @@ impl SemanticDb { cx: &mut AsyncApp, ) -> Result { let db_connection = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { std::fs::create_dir_all(&db_path)?; unsafe { heed::EnvOpenOptions::new() @@ -432,8 +431,7 @@ mod tests { let worktree = search_result.worktree.read(cx); let entry_abs_path = worktree.abs_path().join(&search_result.path); let fs = project.read(cx).fs().clone(); - cx.background_executor() - .spawn(async move { fs.load(&entry_abs_path).await.unwrap() }) + cx.background_spawn(async move { fs.load(&entry_abs_path).await.unwrap() }) }) .await; diff --git a/crates/semantic_index/src/summary_index.rs b/crates/semantic_index/src/summary_index.rs index 55f940e54a0d79..69168acc6de32c 100644 --- a/crates/semantic_index/src/summary_index.rs +++ b/crates/semantic_index/src/summary_index.rs @@ -3,7 +3,7 @@ use arrayvec::ArrayString; use fs::{Fs, MTime}; use futures::{stream::StreamExt, TryFutureExt}; use futures_batch::ChunksTimeoutStreamExt; -use gpui::{App, Entity, Task}; +use gpui::{App, AppContext as _, Entity, Task}; use heed::{ types::{SerdeBincode, Str}, RoTxn, @@ -254,7 +254,7 @@ impl SummaryIndex { let db_connection = self.db_connection.clone(); let db = self.summary_db; let (needs_summary_tx, needs_summary_rx) = channel::bounded(512); - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { let mut might_need_summary = pin!(might_need_summary); while let Some(file) = might_need_summary.next().await { let tx = db_connection @@ -291,7 +291,7 @@ impl SummaryIndex { let db_connection = self.db_connection.clone(); let digest_db = self.file_digest_db; let backlog = Arc::clone(&self.backlog); - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { let txn = db_connection .read_txn() .context("failed to create read transaction")?; @@ -368,7 +368,7 @@ impl SummaryIndex { let db_connection = self.db_connection.clone(); let digest_db = self.file_digest_db; let backlog = Arc::clone(&self.backlog); - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { let txn = db_connection .read_txn() .context("failed to create read transaction")?; @@ -538,7 +538,7 @@ impl SummaryIndex { .available_models(cx) .find(|model| &model.id() == &summary_model_id) else { - return cx.background_executor().spawn(async move { + return cx.background_spawn(async move { Err(anyhow!("Couldn't find the preferred summarization model ({:?}) in the language registry's available models", summary_model_id)) }); }; @@ -566,31 +566,30 @@ impl SummaryIndex { let code_len = code.len(); cx.spawn(|cx| async move { let stream = model.stream_completion(request, &cx); - cx.background_executor() - .spawn(async move { - let answer: String = stream - .await? - .filter_map(|event| async { - if let Ok(LanguageModelCompletionEvent::Text(text)) = event { - Some(text) - } else { - None - } - }) - .collect() - .await; + cx.background_spawn(async move { + let answer: String = stream + .await? + .filter_map(|event| async { + if let Ok(LanguageModelCompletionEvent::Text(text)) = event { + Some(text) + } else { + None + } + }) + .collect() + .await; - log::info!( - "It took {:?} to summarize {:?} bytes of code.", - start.elapsed(), - code_len - ); + log::info!( + "It took {:?} to summarize {:?} bytes of code.", + start.elapsed(), + code_len + ); - log::debug!("Summary was: {:?}", &answer); + log::debug!("Summary was: {:?}", &answer); - Ok(answer) - }) - .await + Ok(answer) + }) + .await // TODO if summarization failed, put it back in the backlog! }) @@ -604,7 +603,7 @@ impl SummaryIndex { let db_connection = self.db_connection.clone(); let digest_db = self.file_digest_db; let summary_db = self.summary_db; - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut summaries = pin!(summaries.chunks_timeout(4096, Duration::from_secs(2))); while let Some(summaries) = summaries.next().await { let mut txn = db_connection.write_txn()?; @@ -650,7 +649,7 @@ impl SummaryIndex { backlog.drain().collect() }; - let task = cx.background_executor().spawn(async move { + let task = cx.background_spawn(async move { tx.send(needs_summary).await?; Ok(()) }); diff --git a/crates/semantic_index/src/worktree_index.rs b/crates/semantic_index/src/worktree_index.rs index 44eeed9dfb10c4..f4ec4a5c6d4f1b 100644 --- a/crates/semantic_index/src/worktree_index.rs +++ b/crates/semantic_index/src/worktree_index.rs @@ -52,8 +52,7 @@ impl WorktreeIndex { cx.spawn(|mut cx| async move { let entries_being_indexed = Arc::new(IndexingEntrySet::new(status_tx)); let (embedding_index, summary_index) = cx - .background_executor() - .spawn({ + .background_spawn({ let entries_being_indexed = Arc::clone(&entries_being_indexed); let db_connection = db_connection.clone(); async move { diff --git a/crates/session/src/session.rs b/crates/session/src/session.rs index 66945e9425f34f..d195d04db2a412 100644 --- a/crates/session/src/session.rs +++ b/crates/session/src/session.rs @@ -1,7 +1,7 @@ use std::time::Duration; use db::kvp::KEY_VALUE_STORE; -use gpui::{AnyWindowHandle, Context, Subscription, Task, WindowId}; +use gpui::{AnyWindowHandle, AppContext as _, Context, Subscription, Task, WindowId}; use util::ResultExt; use uuid::Uuid; @@ -88,7 +88,7 @@ impl AppSession { fn app_will_quit(&mut self, cx: &mut Context) -> Task<()> { if let Some(windows) = cx.window_stack() { - cx.background_executor().spawn(store_window_stack(windows)) + cx.background_spawn(store_window_stack(windows)) } else { Task::ready(()) } diff --git a/crates/task/src/static_source.rs b/crates/task/src/static_source.rs index bcb1fb337329aa..f53c35e25a1b2b 100644 --- a/crates/task/src/static_source.rs +++ b/crates/task/src/static_source.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use futures::{channel::mpsc::UnboundedSender, StreamExt}; -use gpui::App; +use gpui::{App, AppContext}; use parking_lot::RwLock; use serde::Deserialize; use util::ResultExt; @@ -33,35 +33,34 @@ impl TrackedFile { T: for<'a> Deserialize<'a> + Default + Send, { let parsed_contents: Arc> = Arc::default(); - cx.background_executor() - .spawn({ - let parsed_contents = parsed_contents.clone(); - async move { - while let Some(new_contents) = tracker.next().await { - if Arc::strong_count(&parsed_contents) == 1 { - // We're no longer being observed. Stop polling. - break; - } - if !new_contents.trim().is_empty() { - let Some(new_contents) = - serde_json_lenient::from_str::(&new_contents).log_err() - else { - continue; - }; - let mut contents = parsed_contents.write(); - if *contents != new_contents { - *contents = new_contents; - if notification_outlet.unbounded_send(()).is_err() { - // Whoever cared about contents is not around anymore. - break; - } + cx.background_spawn({ + let parsed_contents = parsed_contents.clone(); + async move { + while let Some(new_contents) = tracker.next().await { + if Arc::strong_count(&parsed_contents) == 1 { + // We're no longer being observed. Stop polling. + break; + } + if !new_contents.trim().is_empty() { + let Some(new_contents) = + serde_json_lenient::from_str::(&new_contents).log_err() + else { + continue; + }; + let mut contents = parsed_contents.write(); + if *contents != new_contents { + *contents = new_contents; + if notification_outlet.unbounded_send(()).is_err() { + // Whoever cared about contents is not around anymore. + break; } } } - anyhow::Ok(()) } - }) - .detach_and_log_err(cx); + anyhow::Ok(()) + } + }) + .detach_and_log_err(cx); Self { parsed_contents } } @@ -75,42 +74,41 @@ impl TrackedFile { T: Default + Send, { let parsed_contents: Arc> = Arc::default(); - cx.background_executor() - .spawn({ - let parsed_contents = parsed_contents.clone(); - - async move { - while let Some(new_contents) = tracker.next().await { - if Arc::strong_count(&parsed_contents) == 1 { - // We're no longer being observed. Stop polling. - break; - } - - if !new_contents.trim().is_empty() { - let Some(new_contents) = - serde_json_lenient::from_str::(&new_contents).log_err() - else { - continue; - }; - let Some(new_contents) = new_contents.try_into().log_err() else { - continue; - }; + cx.background_spawn({ + let parsed_contents = parsed_contents.clone(); + async move { + while let Some(new_contents) = tracker.next().await { + if Arc::strong_count(&parsed_contents) == 1 { + // We're no longer being observed. Stop polling. + break; + } - let mut contents = parsed_contents.write(); - if *contents != new_contents { - *contents = new_contents; - if notification_outlet.unbounded_send(()).is_err() { - // Whoever cared about contents is not around anymore. - break; - } + if !new_contents.trim().is_empty() { + let Some(new_contents) = + serde_json_lenient::from_str::(&new_contents).log_err() + else { + continue; + }; + let Some(new_contents) = new_contents.try_into().log_err() else { + continue; + }; + let mut contents = parsed_contents.write(); + if *contents != new_contents { + *contents = new_contents; + if notification_outlet.unbounded_send(()).is_err() { + // Whoever cared about contents is not around anymore. + break; } } } - anyhow::Ok(()) } - }) - .detach_and_log_err(cx); - Self { parsed_contents } + anyhow::Ok(()) + } + }) + .detach_and_log_err(cx); + Self { + parsed_contents: Default::default(), + } } } diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 27965b3c268751..bd91009a5633f3 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -6,9 +6,9 @@ use alacritty_terminal::grid::Dimensions; /// with modifications for our circumstances use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point as AlacPoint, Side}; use alacritty_terminal::term::TermMode; -use gpui::{px, Modifiers, MouseButton, MouseMoveEvent, Pixels, Point, ScrollWheelEvent}; +use gpui::{px, Modifiers, MouseButton, Pixels, Point, ScrollWheelEvent}; -use crate::TerminalSize; +use crate::TerminalBounds; enum MouseFormat { Sgr, @@ -42,14 +42,12 @@ enum AlacMouseButton { } impl AlacMouseButton { - fn from_move(e: &MouseMoveEvent) -> Self { - match e.pressed_button { - Some(b) => match b { - gpui::MouseButton::Left => AlacMouseButton::LeftMove, - gpui::MouseButton::Middle => AlacMouseButton::MiddleMove, - gpui::MouseButton::Right => AlacMouseButton::RightMove, - gpui::MouseButton::Navigate(_) => AlacMouseButton::Other, - }, + fn from_move_button(e: Option) -> Self { + match e { + Some(gpui::MouseButton::Left) => AlacMouseButton::LeftMove, + Some(gpui::MouseButton::Middle) => AlacMouseButton::MiddleMove, + Some(gpui::MouseButton::Right) => AlacMouseButton::RightMove, + Some(gpui::MouseButton::Navigate(_)) => AlacMouseButton::Other, None => AlacMouseButton::NoneMove, } } @@ -134,34 +132,37 @@ pub fn mouse_button_report( } } -pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) -> Option> { - let button = AlacMouseButton::from_move(e); +pub fn mouse_moved_report( + point: AlacPoint, + button: Option, + modifiers: Modifiers, + mode: TermMode, +) -> Option> { + let button = AlacMouseButton::from_move_button(button); if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { //Only drags are reported in drag mode, so block NoneMove. if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, AlacMouseButton::NoneMove) { None } else { - mouse_report( - point, - button, - true, - e.modifiers, - MouseFormat::from_mode(mode), - ) + mouse_report(point, button, true, modifiers, MouseFormat::from_mode(mode)) } } else { None } } -pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { +pub fn grid_point( + pos: Point, + cur_size: TerminalBounds, + display_offset: usize, +) -> AlacPoint { grid_point_and_side(pos, cur_size, display_offset).0 } pub fn grid_point_and_side( pos: Point, - cur_size: TerminalSize, + cur_size: TerminalBounds, display_offset: usize, ) -> (AlacPoint, Side) { let mut col = GridCol((pos.x / cur_size.cell_width) as usize); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index e55c553ae226e7..cc96b9f8528678 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -58,9 +58,10 @@ use std::{ use thiserror::Error; use gpui::{ - actions, black, px, AnyWindowHandle, App, Bounds, ClipboardItem, Context, EventEmitter, Hsla, - Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, - Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase, + actions, black, px, AnyWindowHandle, App, AppContext as _, Bounds, ClipboardItem, Context, + EventEmitter, Hsla, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase, + Window, }; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; @@ -131,7 +132,7 @@ pub enum MaybeNavigationTarget { #[derive(Clone)] enum InternalEvent { - Resize(TerminalSize), + Resize(TerminalBounds), Clear, // FocusNextMatch, Scroll(AlacScroll), @@ -161,35 +162,35 @@ pub fn init(cx: &mut App) { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct TerminalSize { +pub struct TerminalBounds { pub cell_width: Pixels, pub line_height: Pixels, - pub size: Size, + pub bounds: Bounds, } -impl TerminalSize { - pub fn new(line_height: Pixels, cell_width: Pixels, size: Size) -> Self { - TerminalSize { +impl TerminalBounds { + pub fn new(line_height: Pixels, cell_width: Pixels, bounds: Bounds) -> Self { + TerminalBounds { cell_width, line_height, - size, + bounds, } } pub fn num_lines(&self) -> usize { - (self.size.height / self.line_height).floor() as usize + (self.bounds.size.height / self.line_height).floor() as usize } pub fn num_columns(&self) -> usize { - (self.size.width / self.cell_width).floor() as usize + (self.bounds.size.width / self.cell_width).floor() as usize } pub fn height(&self) -> Pixels { - self.size.height + self.bounds.size.height } pub fn width(&self) -> Pixels { - self.size.width + self.bounds.size.width } pub fn cell_width(&self) -> Pixels { @@ -201,21 +202,24 @@ impl TerminalSize { } } -impl Default for TerminalSize { +impl Default for TerminalBounds { fn default() -> Self { - TerminalSize::new( + TerminalBounds::new( DEBUG_LINE_HEIGHT, DEBUG_CELL_WIDTH, - Size { - width: DEBUG_TERMINAL_WIDTH, - height: DEBUG_TERMINAL_HEIGHT, + Bounds { + origin: Point::default(), + size: Size { + width: DEBUG_TERMINAL_WIDTH, + height: DEBUG_TERMINAL_HEIGHT, + }, }, ) } } -impl From for WindowSize { - fn from(val: TerminalSize) -> Self { +impl From for WindowSize { + fn from(val: TerminalBounds) -> Self { WindowSize { num_lines: val.num_lines() as u16, num_cols: val.num_columns() as u16, @@ -225,7 +229,7 @@ impl From for WindowSize { } } -impl Dimensions for TerminalSize { +impl Dimensions for TerminalBounds { /// Note: this is supposed to be for the back buffer's length, /// but we exclusively use it to resize the terminal, which does not /// use this method. We still have to implement it for the trait though, @@ -407,7 +411,7 @@ impl TerminalBuilder { //Set up the terminal... let mut term = Term::new( config.clone(), - &TerminalSize::default(), + &TerminalBounds::default(), ZedListener(events_tx.clone()), ); @@ -421,7 +425,7 @@ impl TerminalBuilder { //Setup the pty... let pty = match tty::new( &pty_options, - TerminalSize::default().into(), + TerminalBounds::default().into(), window.window_id().as_u64(), ) { Ok(pty) => pty, @@ -464,11 +468,9 @@ impl TerminalBuilder { pty_info, breadcrumb_text: String::new(), scroll_px: px(0.), - last_mouse_position: None, next_link_id: 0, selection_phase: SelectionPhase::Ended, - secondary_pressed: false, - hovered_word: false, + // hovered_word: false, url_regex: RegexSearch::new(URL_REGEX).unwrap(), word_regex: RegexSearch::new(WORD_REGEX).unwrap(), vi_mode_enabled: false, @@ -571,7 +573,7 @@ pub struct TerminalContent { pub selection: Option, pub cursor: RenderableCursor, pub cursor_char: char, - pub size: TerminalSize, + pub terminal_bounds: TerminalBounds, pub last_hovered_word: Option, } @@ -595,7 +597,7 @@ impl Default for TerminalContent { point: AlacPoint::new(Line(0), Column(0)), }, cursor_char: Default::default(), - size: Default::default(), + terminal_bounds: Default::default(), last_hovered_word: None, } } @@ -615,8 +617,6 @@ pub struct Terminal { events: VecDeque, /// This is only used for mouse mode cell change detection last_mouse: Option<(AlacPoint, AlacDirection)>, - /// This is only used for terminal hovered word checking - last_mouse_position: Option>, pub matches: Vec>, pub last_content: TerminalContent, pub selection_head: Option, @@ -627,8 +627,6 @@ pub struct Terminal { scroll_px: Pixels, next_link_id: usize, selection_phase: SelectionPhase, - secondary_pressed: bool, - hovered_word: bool, url_regex: RegexSearch, word_regex: RegexSearch, task: Option, @@ -700,7 +698,7 @@ impl Terminal { } AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()), AlacTermEvent::TextAreaSizeRequest(format) => { - self.write_to_pty(format(self.last_content.size.into())) + self.write_to_pty(format(self.last_content.terminal_bounds.into())) } AlacTermEvent::CursorBlinkingChange => { let terminal = self.term.lock(); @@ -749,18 +747,20 @@ impl Terminal { &mut self, event: &InternalEvent, term: &mut Term, + window: &mut Window, cx: &mut Context, ) { match event { - InternalEvent::Resize(mut new_size) => { - new_size.size.height = cmp::max(new_size.line_height, new_size.height()); - new_size.size.width = cmp::max(new_size.cell_width, new_size.width()); + InternalEvent::Resize(mut new_bounds) => { + new_bounds.bounds.size.height = + cmp::max(new_bounds.line_height, new_bounds.height()); + new_bounds.bounds.size.width = cmp::max(new_bounds.cell_width, new_bounds.width()); - self.last_content.size = new_size; + self.last_content.terminal_bounds = new_bounds; - self.pty_tx.0.send(Msg::Resize(new_size.into())).ok(); + self.pty_tx.0.send(Msg::Resize(new_bounds.into())).ok(); - term.resize(new_size); + term.resize(new_bounds); } InternalEvent::Clear => { // Clear back buffer @@ -796,7 +796,7 @@ impl Terminal { } InternalEvent::Scroll(scroll) => { term.scroll_display(*scroll); - self.refresh_hovered_word(); + self.refresh_hovered_word(window); if self.vi_mode_enabled { match *scroll { @@ -852,7 +852,7 @@ impl Terminal { if let Some(mut selection) = term.selection.take() { let (point, side) = grid_point_and_side( *position, - self.last_content.size, + self.last_content.terminal_bounds, term.grid().display_offset(), ); @@ -876,7 +876,7 @@ impl Terminal { } InternalEvent::ScrollToAlacPoint(point) => { term.scroll_to_point(*point); - self.refresh_hovered_word(); + self.refresh_hovered_word(window); } InternalEvent::ToggleViMode => { self.vi_mode_enabled = !self.vi_mode_enabled; @@ -890,7 +890,7 @@ impl Terminal { let point = grid_point( *position, - self.last_content.size, + self.last_content.terminal_bounds, term.grid().display_offset(), ) .grid_clamp(term, Boundary::Grid); @@ -980,13 +980,9 @@ impl Terminal { cx, ); } - self.hovered_word = true; } None => { - if self.hovered_word { - cx.emit(Event::NewNavigationTarget(None)); - } - self.hovered_word = false; + cx.emit(Event::NewNavigationTarget(None)); } } } @@ -1018,6 +1014,7 @@ impl Terminal { id: self.next_link_id(), }); cx.emit(Event::NewNavigationTarget(Some(navigation_target))); + cx.notify() } fn next_link_id(&mut self) -> usize { @@ -1137,9 +1134,9 @@ impl Terminal { } ///Resize the terminal and the PTY. - pub fn set_size(&mut self, new_size: TerminalSize) { - if self.last_content.size != new_size { - self.events.push_back(InternalEvent::Resize(new_size)) + pub fn set_size(&mut self, new_bounds: TerminalBounds) { + if self.last_content.terminal_bounds != new_bounds { + self.events.push_back(InternalEvent::Resize(new_bounds)) } } @@ -1203,8 +1200,8 @@ impl Terminal { if let Some(motion) = motion { let cursor = self.last_content.cursor.point; let cursor_pos = Point { - x: cursor.column.0 as f32 * self.last_content.size.cell_width, - y: cursor.line.0 as f32 * self.last_content.size.line_height, + x: cursor.column.0 as f32 * self.last_content.terminal_bounds.cell_width, + y: cursor.line.0 as f32 * self.last_content.terminal_bounds.line_height, }; self.events .push_back(InternalEvent::UpdateSelection(cursor_pos)); @@ -1218,11 +1215,11 @@ impl Terminal { "b" if keystroke.modifiers.control => Some(AlacScroll::PageUp), "f" if keystroke.modifiers.control => Some(AlacScroll::PageDown), "d" if keystroke.modifiers.control => { - let amount = self.last_content.size.line_height().to_f64() as i32 / 2; + let amount = self.last_content.terminal_bounds.line_height().to_f64() as i32 / 2; Some(AlacScroll::Delta(-amount)) } "u" if keystroke.modifiers.control => { - let amount = self.last_content.size.line_height().to_f64() as i32 / 2; + let amount = self.last_content.terminal_bounds.line_height().to_f64() as i32 / 2; Some(AlacScroll::Delta(amount)) } _ => None, @@ -1280,13 +1277,22 @@ impl Terminal { } } - pub fn try_modifiers_change(&mut self, modifiers: &Modifiers) -> bool { - let changed = self.secondary_pressed != modifiers.secondary(); - if !self.secondary_pressed && modifiers.secondary() { - self.refresh_hovered_word(); + pub fn try_modifiers_change( + &mut self, + modifiers: &Modifiers, + window: &Window, + cx: &mut Context, + ) { + if self + .last_content + .terminal_bounds + .bounds + .contains(&window.mouse_position()) + && modifiers.secondary() + { + self.refresh_hovered_word(window); } - self.secondary_pressed = modifiers.secondary(); - changed + cx.notify(); } ///Paste text into the terminal @@ -1300,12 +1306,12 @@ impl Terminal { self.input(paste_text); } - pub fn sync(&mut self, cx: &mut Context) { + pub fn sync(&mut self, window: &mut Window, cx: &mut Context) { let term = self.term.clone(); let mut terminal = term.lock_unfair(); //Note that the ordering of events matters for event processing while let Some(e) = self.events.pop_front() { - self.process_terminal_event(&e, &mut terminal, cx) + self.process_terminal_event(&e, &mut terminal, window, cx) } self.last_content = Self::make_content(&terminal, &self.last_content); @@ -1334,7 +1340,7 @@ impl Terminal { selection: content.selection, cursor: content.cursor, cursor_char: term.grid()[content.cursor.point].c, - size: last_content.size, + terminal_bounds: last_content.terminal_bounds, last_hovered_word: last_content.last_hovered_word.clone(), } } @@ -1371,7 +1377,6 @@ impl Terminal { } pub fn focus_out(&mut self) { - self.last_mouse_position = None; if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) { self.write_to_pty("\x1b[O".to_string()); } @@ -1398,44 +1403,48 @@ impl Terminal { self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift } - pub fn mouse_move(&mut self, e: &MouseMoveEvent, origin: Point) { - let position = e.position - origin; - self.last_mouse_position = Some(position); + pub fn mouse_move(&mut self, e: &MouseMoveEvent, cx: &mut Context) { + let position = e.position - self.last_content.terminal_bounds.bounds.origin; if self.mouse_mode(e.modifiers.shift) { let (point, side) = grid_point_and_side( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); if self.mouse_changed(point, side) { - if let Some(bytes) = mouse_moved_report(point, e, self.last_content.mode) { + if let Some(bytes) = + mouse_moved_report(point, e.pressed_button, e.modifiers, self.last_content.mode) + { self.pty_tx.notify(bytes); } } - } else if self.secondary_pressed { - self.word_from_position(Some(position)); + } else if e.modifiers.secondary() { + self.word_from_position(e.position); } + cx.notify(); } - fn word_from_position(&mut self, position: Option>) { + fn word_from_position(&mut self, position: Point) { if self.selection_phase == SelectionPhase::Selecting { self.last_content.last_hovered_word = None; - } else if let Some(position) = position { - self.events - .push_back(InternalEvent::FindHyperlink(position, false)); + } else if self.last_content.terminal_bounds.bounds.contains(&position) { + self.events.push_back(InternalEvent::FindHyperlink( + position - self.last_content.terminal_bounds.bounds.origin, + false, + )); + } else { + self.last_content.last_hovered_word = None; } } pub fn mouse_drag( &mut self, e: &MouseMoveEvent, - origin: Point, region: Bounds, + cx: &mut Context, ) { - let position = e.position - origin; - self.last_mouse_position = Some(position); - + let position = e.position - self.last_content.terminal_bounds.bounds.origin; if !self.mouse_mode(e.modifiers.shift) { self.selection_phase = SelectionPhase::Selecting; // Alacritty has the same ordering, of first updating the selection @@ -1450,18 +1459,21 @@ impl Terminal { None => return, }; - let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32; + let scroll_lines = + (scroll_delta / self.last_content.terminal_bounds.line_height) as i32; self.events .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines))); } + + cx.notify(); } } fn drag_line_delta(&self, e: &MouseMoveEvent, region: Bounds) -> Option { //TODO: Why do these need to be doubled? Probably the same problem that the IME has - let top = region.origin.y + (self.last_content.size.line_height * 2.); - let bottom = region.bottom_left().y - (self.last_content.size.line_height * 2.); + let top = region.origin.y + (self.last_content.terminal_bounds.line_height * 2.); + let bottom = region.bottom_left().y - (self.last_content.terminal_bounds.line_height * 2.); let scroll_delta = if e.position.y < top { (top - e.position.y).pow(1.1) } else if e.position.y > bottom { @@ -1472,16 +1484,11 @@ impl Terminal { Some(scroll_delta) } - pub fn mouse_down( - &mut self, - e: &MouseDownEvent, - origin: Point, - _cx: &mut Context, - ) { - let position = e.position - origin; + pub fn mouse_down(&mut self, e: &MouseDownEvent, _cx: &mut Context) { + let position = e.position - self.last_content.terminal_bounds.bounds.origin; let point = grid_point( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1494,10 +1501,9 @@ impl Terminal { } else { match e.button { MouseButton::Left => { - let position = e.position - origin; let (point, side) = grid_point_and_side( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1509,6 +1515,12 @@ impl Terminal { _ => None, }; + if selection_type == Some(SelectionType::Simple) && e.modifiers.shift { + self.events + .push_back(InternalEvent::UpdateSelection(position)); + return; + } + let selection = selection_type .map(|selection_type| Selection::new(selection_type, point, side)); @@ -1529,14 +1541,14 @@ impl Terminal { } } - pub fn mouse_up(&mut self, e: &MouseUpEvent, origin: Point, cx: &Context) { + pub fn mouse_up(&mut self, e: &MouseUpEvent, cx: &Context) { let setting = TerminalSettings::get_global(cx); - let position = e.position - origin; + let position = e.position - self.last_content.terminal_bounds.bounds.origin; if self.mouse_mode(e.modifiers.shift) { let point = grid_point( position, - self.last_content.size, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1552,10 +1564,11 @@ impl Terminal { //Hyperlinks if self.selection_phase == SelectionPhase::Ended { - let mouse_cell_index = content_index_for_mouse(position, &self.last_content.size); + let mouse_cell_index = + content_index_for_mouse(position, &self.last_content.terminal_bounds); if let Some(link) = self.last_content.cells[mouse_cell_index].hyperlink() { cx.open_url(link.uri()); - } else if self.secondary_pressed { + } else if e.modifiers.secondary() { self.events .push_back(InternalEvent::FindHyperlink(position, true)); } @@ -1567,14 +1580,14 @@ impl Terminal { } ///Scroll the terminal - pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Point) { + pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent) { let mouse_mode = self.mouse_mode(e.shift); if let Some(scroll_lines) = self.determine_scroll_lines(e, mouse_mode) { if mouse_mode { let point = grid_point( - e.position - origin, - self.last_content.size, + e.position - self.last_content.terminal_bounds.bounds.origin, + self.last_content.terminal_bounds, self.last_content.display_offset, ); @@ -1599,13 +1612,13 @@ impl Terminal { } } - fn refresh_hovered_word(&mut self) { - self.word_from_position(self.last_mouse_position); + fn refresh_hovered_word(&mut self, window: &Window) { + self.word_from_position(window.mouse_position()); } fn determine_scroll_lines(&mut self, e: &ScrollWheelEvent, mouse_mode: bool) -> Option { let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER }; - let line_height = self.last_content.size.line_height; + let line_height = self.last_content.terminal_bounds.line_height; match e.touch_phase { /* Reset scroll state on started */ TouchPhase::Started => { @@ -1622,7 +1635,7 @@ impl Terminal { // Whenever we hit the edges, reset our stored scroll to 0 // so we can respond to changes in direction quickly - self.scroll_px %= self.last_content.size.height(); + self.scroll_px %= self.last_content.terminal_bounds.height(); Some(new_offset - old_offset) } @@ -1636,7 +1649,7 @@ impl Terminal { cx: &Context, ) -> Task>> { let term = self.term.clone(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let term = term.lock(); all_search_matches(&term, &mut searcher).collect() @@ -1717,10 +1730,6 @@ impl Terminal { } } - pub fn can_navigate_to_selected_word(&self) -> bool { - self.secondary_pressed && self.hovered_word - } - pub fn task(&self) -> Option<&TaskState> { self.task.as_ref() } @@ -1906,12 +1915,12 @@ fn all_search_matches<'a, T>( RegexIter::new(start, end, AlacDirection::Right, term, regex) } -fn content_index_for_mouse(pos: Point, size: &TerminalSize) -> usize { - let col = (pos.x / size.cell_width()).round() as usize; - let clamped_col = min(col, size.columns() - 1); - let row = (pos.y / size.line_height()).round() as usize; - let clamped_row = min(row, size.screen_lines() - 1); - clamped_row * size.columns() + clamped_col +fn content_index_for_mouse(pos: Point, terminal_bounds: &TerminalBounds) -> usize { + let col = (pos.x / terminal_bounds.cell_width()).round() as usize; + let clamped_col = min(col, terminal_bounds.columns() - 1); + let row = (pos.y / terminal_bounds.line_height()).round() as usize; + let clamped_row = min(row, terminal_bounds.screen_lines() - 1); + clamped_row * terminal_bounds.columns() + clamped_col } /// Converts an 8 bit ANSI color to its GPUI equivalent. @@ -2004,11 +2013,11 @@ mod tests { index::{Column, Line, Point as AlacPoint}, term::cell::Cell, }; - use gpui::{point, size, Pixels}; + use gpui::{bounds, point, size, Pixels, Point}; use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng}; use crate::{ - content_index_for_mouse, rgb_for_index, IndexedCell, TerminalContent, TerminalSize, + content_index_for_mouse, rgb_for_index, IndexedCell, TerminalBounds, TerminalContent, }; #[test] @@ -2030,12 +2039,15 @@ mod tests { let viewport_cells = rng.gen_range(15..20); let cell_size = rng.gen_range(5 * PRECISION..20 * PRECISION) as f32 / PRECISION as f32; - let size = crate::TerminalSize { + let size = crate::TerminalBounds { cell_width: Pixels::from(cell_size), line_height: Pixels::from(cell_size), - size: size( - Pixels::from(cell_size * (viewport_cells as f32)), - Pixels::from(cell_size * (viewport_cells as f32)), + bounds: bounds( + Point::default(), + size( + Pixels::from(cell_size * (viewport_cells as f32)), + Pixels::from(cell_size * (viewport_cells as f32)), + ), ), }; @@ -2055,7 +2067,8 @@ mod tests { Pixels::from(row as f32 * cell_size + row_offset), ); - let content_index = content_index_for_mouse(mouse_pos, &content.size); + let content_index = + content_index_for_mouse(mouse_pos, &content.terminal_bounds); let mouse_cell = content.cells[content_index].c; let real_cell = cells[row][col]; @@ -2069,10 +2082,13 @@ mod tests { fn test_mouse_to_cell_clamp() { let mut rng = thread_rng(); - let size = crate::TerminalSize { + let size = crate::TerminalBounds { cell_width: Pixels::from(10.), line_height: Pixels::from(10.), - size: size(Pixels::from(100.), Pixels::from(100.)), + bounds: bounds( + Point::default(), + size(Pixels::from(100.), Pixels::from(100.)), + ), }; let cells = get_cells(size, &mut rng); @@ -2081,7 +2097,7 @@ mod tests { assert_eq!( content.cells[content_index_for_mouse( point(Pixels::from(-10.), Pixels::from(-10.)), - &content.size, + &content.terminal_bounds, )] .c, cells[0][0] @@ -2089,14 +2105,14 @@ mod tests { assert_eq!( content.cells[content_index_for_mouse( point(Pixels::from(1000.), Pixels::from(1000.)), - &content.size, + &content.terminal_bounds, )] .c, cells[9][9] ); } - fn get_cells(size: TerminalSize, rng: &mut ThreadRng) -> Vec> { + fn get_cells(size: TerminalBounds, rng: &mut ThreadRng) -> Vec> { let mut cells = Vec::new(); for _ in 0..((size.height() / size.line_height()) as usize) { @@ -2111,7 +2127,10 @@ mod tests { cells } - fn convert_cells_to_content(size: TerminalSize, cells: &[Vec]) -> TerminalContent { + fn convert_cells_to_content( + terminal_bounds: TerminalBounds, + cells: &[Vec], + ) -> TerminalContent { let mut ic = Vec::new(); for (index, row) in cells.iter().enumerate() { @@ -2128,7 +2147,7 @@ mod tests { TerminalContent { cells: ic, - size, + terminal_bounds, ..Default::default() } } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 9a7a0e79d39e9d..26b1e7ac03fdc4 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -21,7 +21,7 @@ use terminal::{ }, }, terminal_settings::TerminalSettings, - HoveredWord, IndexedCell, Terminal, TerminalContent, TerminalSize, + HoveredWord, IndexedCell, Terminal, TerminalBounds, TerminalContent, }; use theme::{ActiveTheme, Theme, ThemeSettings}; use ui::{ParentElement, Tooltip}; @@ -40,7 +40,7 @@ pub struct LayoutState { relative_highlighted_ranges: Vec<(RangeInclusive, Hsla)>, cursor: Option, background_color: Hsla, - dimensions: TerminalSize, + dimensions: TerminalBounds, mode: TermMode, display_offset: usize, hyperlink_tooltip: Option, @@ -86,7 +86,7 @@ impl LayoutCell { pub fn paint( &self, origin: Point, - dimensions: &TerminalSize, + dimensions: &TerminalBounds, _visible_bounds: Bounds, window: &mut Window, cx: &mut App, @@ -130,7 +130,7 @@ impl LayoutRect { } } - pub fn paint(&self, origin: Point, dimensions: &TerminalSize, window: &mut Window) { + pub fn paint(&self, origin: Point, dimensions: &TerminalBounds, window: &mut Window) { let position = { let alac_point = self.point; point( @@ -313,7 +313,7 @@ impl TerminalElement { /// the same position for sequential indexes. Use em_width instead fn shape_cursor( cursor_point: DisplayCursor, - size: TerminalSize, + size: TerminalBounds, text_fragment: &ShapedLine, ) -> Option<(Point, Pixels)> { if cursor_point.line() < size.total_lines() as i32 { @@ -412,27 +412,20 @@ impl TerminalElement { fn generic_button_handler( connection: Entity, - origin: Point, focus_handle: FocusHandle, - f: impl Fn(&mut Terminal, Point, &E, &mut Context), + f: impl Fn(&mut Terminal, &E, &mut Context), ) -> impl Fn(&E, &mut Window, &mut App) { move |event, window, cx| { window.focus(&focus_handle); connection.update(cx, |terminal, cx| { - f(terminal, origin, event, cx); + f(terminal, event, cx); cx.notify(); }) } } - fn register_mouse_listeners( - &mut self, - origin: Point, - mode: TermMode, - hitbox: &Hitbox, - window: &mut Window, - ) { + fn register_mouse_listeners(&mut self, mode: TermMode, hitbox: &Hitbox, window: &mut Window) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); @@ -442,29 +435,26 @@ impl TerminalElement { move |e, window, cx| { window.focus(&focus); terminal.update(cx, |terminal, cx| { - terminal.mouse_down(e, origin, cx); + terminal.mouse_down(e, cx); cx.notify(); }) } }); window.on_mouse_event({ - let focus = self.focus.clone(); let terminal = self.terminal.clone(); let hitbox = hitbox.clone(); + let focus = focus.clone(); move |e: &MouseMoveEvent, phase, window, cx| { - if phase != DispatchPhase::Bubble || !focus.is_focused(window) { + if phase != DispatchPhase::Bubble { return; } - if e.pressed_button.is_some() && !cx.has_active_drag() { + if e.pressed_button.is_some() && !cx.has_active_drag() && focus.is_focused(window) { let hovered = hitbox.is_hovered(window); terminal.update(cx, |terminal, cx| { - if terminal.selection_started() { - terminal.mouse_drag(e, origin, hitbox.bounds); - cx.notify(); - } else if hovered { - terminal.mouse_drag(e, origin, hitbox.bounds); + if terminal.selection_started() || hovered { + terminal.mouse_drag(e, hitbox.bounds, cx); cx.notify(); } }) @@ -472,8 +462,7 @@ impl TerminalElement { if hitbox.is_hovered(window) { terminal.update(cx, |terminal, cx| { - terminal.mouse_move(e, origin); - cx.notify(); + terminal.mouse_move(e, cx); }) } } @@ -483,10 +472,9 @@ impl TerminalElement { MouseButton::Left, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_up(e, cx); }, ), ); @@ -494,10 +482,9 @@ impl TerminalElement { MouseButton::Middle, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_down(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_down(e, cx); }, ), ); @@ -506,7 +493,7 @@ impl TerminalElement { move |e, _, cx| { terminal_view .update(cx, |terminal_view, cx| { - terminal_view.scroll_wheel(e, origin, cx); + terminal_view.scroll_wheel(e, cx); cx.notify(); }) .ok(); @@ -520,10 +507,9 @@ impl TerminalElement { MouseButton::Right, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_down(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_down(e, cx); }, ), ); @@ -531,23 +517,17 @@ impl TerminalElement { MouseButton::Right, TerminalElement::generic_button_handler( terminal.clone(), - origin, focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(e, origin, cx); + move |terminal, e, cx| { + terminal.mouse_up(e, cx); }, ), ); self.interactivity.on_mouse_up( MouseButton::Middle, - TerminalElement::generic_button_handler( - terminal, - origin, - focus, - move |terminal, origin, e, cx| { - terminal.mouse_up(e, origin, cx); - }, - ), + TerminalElement::generic_button_handler(terminal, focus, move |terminal, e, cx| { + terminal.mouse_up(e, cx); + }), ); } } @@ -705,7 +685,10 @@ impl Element for TerminalElement { size.width = cell_width * 2.0; } - TerminalSize::new(line_height, cell_width, size) + let mut origin = bounds.origin; + origin.x += gutter; + + TerminalBounds::new(line_height, cell_width, Bounds { origin, size }) }; let search_matches = self.terminal.read(cx).matches.clone(); @@ -714,9 +697,11 @@ impl Element for TerminalElement { let last_hovered_word = self.terminal.update(cx, |terminal, cx| { terminal.set_size(dimensions); - terminal.sync(cx); + terminal.sync(window, cx); + if self.can_navigate_to_selected_word - && terminal.can_navigate_to_selected_word() + && window.modifiers().secondary() + && bounds.contains(&window.mouse_position()) { terminal.last_content.last_hovered_word.clone() } else { @@ -898,7 +883,7 @@ impl Element for TerminalElement { workspace: self.workspace.clone(), }; - self.register_mouse_listeners(origin, layout.mode, &layout.hitbox, window); + self.register_mouse_listeners(layout.mode, &layout.hitbox, window); if self.can_navigate_to_selected_word && layout.last_hovered_word.is_some() { window.set_cursor_style(gpui::CursorStyle::PointingHand, &layout.hitbox); } else { @@ -924,12 +909,9 @@ impl Element for TerminalElement { return; } - let handled = this - .update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); - - if handled { - window.refresh(); - } + this.update(cx, |term, cx| { + term.try_modifiers_change(&event.modifiers, window, cx) + }); } }); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 823c4804852104..24a3c4b9f71331 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -222,8 +222,7 @@ impl TerminalPanel { mut cx: AsyncWindowContext, ) -> Result> { let serialized_panel = cx - .background_executor() - .spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) + .background_spawn(async move { KEY_VALUE_STORE.read_kvp(TERMINAL_PANEL_KEY) }) .await .log_err() .flatten() @@ -742,25 +741,24 @@ impl TerminalPanel { )) }) .ok()?; - cx.background_executor() - .spawn( - async move { - KEY_VALUE_STORE - .write_kvp( - TERMINAL_PANEL_KEY.into(), - serde_json::to_string(&SerializedTerminalPanel { - items, - active_item_id: None, - height, - width, - })?, - ) - .await?; - anyhow::Ok(()) - } - .log_err(), - ) - .await; + cx.background_spawn( + async move { + KEY_VALUE_STORE + .write_kvp( + TERMINAL_PANEL_KEY.into(), + serde_json::to_string(&SerializedTerminalPanel { + items, + active_item_id: None, + height, + width, + })?, + ) + .await?; + anyhow::Ok(()) + } + .log_err(), + ) + .await; Some(()) }); } diff --git a/crates/terminal_view/src/terminal_scrollbar.rs b/crates/terminal_view/src/terminal_scrollbar.rs index e86ebdf5583e0b..01b91007145141 100644 --- a/crates/terminal_view/src/terminal_scrollbar.rs +++ b/crates/terminal_view/src/terminal_scrollbar.rs @@ -19,7 +19,7 @@ struct ScrollHandleState { impl ScrollHandleState { fn new(terminal: &Terminal) -> Self { Self { - line_height: terminal.last_content().size.line_height, + line_height: terminal.last_content().terminal_bounds.line_height, total_lines: terminal.total_lines(), viewport_lines: terminal.viewport_lines(), display_offset: terminal.last_content().display_offset, diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 836b126564e237..3a31ca1fec1aad 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -23,7 +23,7 @@ use terminal::{ terminal_settings::{self, CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory}, Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown, ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal, - TerminalSize, ToggleViMode, + TerminalBounds, ToggleViMode, }; use terminal_element::{is_blank, TerminalElement}; use terminal_panel::TerminalPanel; @@ -101,7 +101,7 @@ pub struct BlockProperties { pub struct BlockContext<'a, 'b> { pub window: &'a mut Window, pub context: &'b mut App, - pub dimensions: TerminalSize, + pub dimensions: TerminalBounds, } ///A terminal view, maintains the PTY's file handles and communicates with the terminal @@ -342,7 +342,7 @@ impl TerminalView { return Pixels::ZERO; }; - let line_height = terminal.last_content().size.line_height; + let line_height = terminal.last_content().terminal_bounds.line_height; let mut terminal_lines = terminal.total_lines(); let viewport_lines = terminal.viewport_lines(); if terminal.total_lines() == terminal.viewport_lines() { @@ -366,16 +366,11 @@ impl TerminalView { max_scroll_top_in_lines as f32 * line_height } - fn scroll_wheel( - &mut self, - event: &ScrollWheelEvent, - origin: gpui::Point, - cx: &mut Context, - ) { + fn scroll_wheel(&mut self, event: &ScrollWheelEvent, cx: &mut Context) { let terminal_content = self.terminal.read(cx).last_content(); if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { - let line_height = terminal_content.size.line_height; + let line_height = terminal_content.terminal_bounds.line_height; let y_delta = event.delta.pixel_delta(line_height).y; if y_delta < Pixels::ZERO || self.scroll_top > Pixels::ZERO { self.scroll_top = cmp::max( @@ -387,8 +382,7 @@ impl TerminalView { } } - self.terminal - .update(cx, |term, _| term.scroll_wheel(event, origin)); + self.terminal.update(cx, |term, _| term.scroll_wheel(event)); } fn scroll_line_up(&mut self, _: &ScrollLineUp, _: &mut Window, cx: &mut Context) { @@ -397,7 +391,7 @@ impl TerminalView { && terminal_content.display_offset == 0 && self.scroll_top > Pixels::ZERO { - let line_height = terminal_content.size.line_height; + let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::max(self.scroll_top - line_height, Pixels::ZERO); return; } @@ -411,7 +405,7 @@ impl TerminalView { if self.block_below_cursor.is_some() && terminal_content.display_offset == 0 { let max_scroll_top = self.max_scroll_top(cx); if self.scroll_top < max_scroll_top { - let line_height = terminal_content.size.line_height; + let line_height = terminal_content.terminal_bounds.line_height; self.scroll_top = cmp::min(self.scroll_top + line_height, max_scroll_top); } return; @@ -425,7 +419,12 @@ impl TerminalView { if self.scroll_top == Pixels::ZERO { self.terminal.update(cx, |term, _| term.scroll_page_up()); } else { - let line_height = self.terminal.read(cx).last_content.size.line_height(); + let line_height = self + .terminal + .read(cx) + .last_content + .terminal_bounds + .line_height(); let visible_block_lines = (self.scroll_top / line_height) as usize; let viewport_lines = self.terminal.read(cx).viewport_lines(); let visible_content_lines = viewport_lines - visible_block_lines; @@ -866,7 +865,8 @@ fn subscribe_for_terminal_events( } } None => false, - } + }; + cx.notify() } Event::Open(maybe_navigation_target) => match maybe_navigation_target { @@ -976,7 +976,7 @@ fn possible_open_paths_metadata( potential_paths: HashSet, cx: &mut Context, ) -> Task> { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let mut canonical_paths = HashSet::default(); for path in potential_paths { if let Ok(canonical) = fs.canonicalize(&path).await { @@ -1378,9 +1378,12 @@ impl Item for TerminalView { ) { if self.terminal().read(cx).task().is_none() { if let Some((new_id, old_id)) = workspace.database_id().zip(self.workspace_id) { - cx.background_executor() - .spawn(TERMINAL_DB.update_workspace_id(new_id, old_id, cx.entity_id().as_u64())) - .detach(); + cx.background_spawn(TERMINAL_DB.update_workspace_id( + new_id, + old_id, + cx.entity_id().as_u64(), + )) + .detach(); } self.workspace_id = workspace.database_id(); } @@ -1421,7 +1424,7 @@ impl SerializableItem for TerminalView { } if let Some((cwd, workspace_id)) = terminal.working_directory().zip(self.workspace_id) { - Some(cx.background_executor().spawn(async move { + Some(cx.background_spawn(async move { TERMINAL_DB .save_working_directory(item_id, workspace_id, cwd) .await diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index e0a811335a62e4..05535eae8827fb 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -36,6 +36,7 @@ serde_json_lenient.workspace = true serde_repr.workspace = true settings.workspace = true strum.workspace = true +thiserror.workspace = true util.workspace = true uuid.workspace = true diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 41f8e7389bad4e..46698e86746ba7 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -138,11 +138,11 @@ impl ThemeColors { terminal_ansi_dim_white: neutral().light().step_11(), link_text_hover: orange().light().step_10(), version_control_added: ADDED_COLOR, - version_control_added_background: ADDED_COLOR.opacity(0.08), + version_control_added_background: ADDED_COLOR.opacity(0.1), version_control_deleted: REMOVED_COLOR, - version_control_deleted_background: REMOVED_COLOR.opacity(0.08), + version_control_deleted_background: REMOVED_COLOR.opacity(0.1), version_control_modified: MODIFIED_COLOR, - version_control_modified_background: MODIFIED_COLOR.opacity(0.08), + version_control_modified_background: MODIFIED_COLOR.opacity(0.1), version_control_renamed: MODIFIED_COLOR, version_control_conflict: orange().light().step_12(), version_control_conflict_background: orange().light().step_12().opacity(0.1), diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index f90b6df559a299..43328aaad2a4a5 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -1,13 +1,14 @@ use std::sync::Arc; use std::{fmt::Debug, path::Path}; -use anyhow::{anyhow, Context as _, Result}; +use anyhow::{Context as _, Result}; use collections::HashMap; use derive_more::{Deref, DerefMut}; use fs::Fs; use futures::StreamExt; use gpui::{App, AssetSource, Global, SharedString}; use parking_lot::RwLock; +use thiserror::Error; use util::ResultExt; use crate::{ @@ -25,6 +26,16 @@ pub struct ThemeMeta { pub appearance: Appearance, } +/// An error indicating that the theme with the given name was not found. +#[derive(Debug, Error, Clone)] +#[error("theme not found: {0}")] +pub struct ThemeNotFoundError(pub SharedString); + +/// An error indicating that the icon theme with the given name was not found. +#[derive(Debug, Error, Clone)] +#[error("icon theme not found: {0}")] +pub struct IconThemeNotFoundError(pub SharedString); + /// The global [`ThemeRegistry`]. /// /// This newtype exists for obtaining a unique [`TypeId`](std::any::TypeId) when @@ -39,6 +50,8 @@ impl Global for GlobalThemeRegistry {} struct ThemeRegistryState { themes: HashMap>, icon_themes: HashMap>, + /// Whether the extensions have been loaded yet. + extensions_loaded: bool, } /// The registry for themes. @@ -71,6 +84,7 @@ impl ThemeRegistry { state: RwLock::new(ThemeRegistryState { themes: HashMap::default(), icon_themes: HashMap::default(), + extensions_loaded: false, }), assets, }; @@ -89,6 +103,16 @@ impl ThemeRegistry { registry } + /// Returns whether the extensions have been loaded. + pub fn extensions_loaded(&self) -> bool { + self.state.read().extensions_loaded + } + + /// Sets the flag indicating that the extensions have loaded. + pub fn set_extensions_loaded(&self) { + self.state.write().extensions_loaded = true; + } + fn insert_theme_families(&self, families: impl IntoIterator) { for family in families.into_iter() { self.insert_themes(family.themes); @@ -145,12 +169,12 @@ impl ThemeRegistry { } /// Returns the theme with the given name. - pub fn get(&self, name: &str) -> Result> { + pub fn get(&self, name: &str) -> Result, ThemeNotFoundError> { self.state .read() .themes .get(name) - .ok_or_else(|| anyhow!("theme not found: {}", name)) + .ok_or_else(|| ThemeNotFoundError(name.to_string().into())) .cloned() } @@ -209,7 +233,7 @@ impl ThemeRegistry { } /// Returns the default icon theme. - pub fn default_icon_theme(&self) -> Result> { + pub fn default_icon_theme(&self) -> Result, IconThemeNotFoundError> { self.get_icon_theme(DEFAULT_ICON_THEME_NAME) } @@ -227,12 +251,12 @@ impl ThemeRegistry { } /// Returns the icon theme with the specified name. - pub fn get_icon_theme(&self, name: &str) -> Result> { + pub fn get_icon_theme(&self, name: &str) -> Result, IconThemeNotFoundError> { self.state .read() .icon_themes .get(name) - .ok_or_else(|| anyhow!("icon theme not found: {name}")) + .ok_or_else(|| IconThemeNotFoundError(name.to_string().into())) .cloned() } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index baa2130d8560f8..37359271157045 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -1,7 +1,7 @@ use crate::fallback_themes::zed_default_dark; use crate::{ - Appearance, IconTheme, SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent, - DEFAULT_ICON_THEME_NAME, + Appearance, IconTheme, IconThemeNotFoundError, SyntaxTheme, Theme, ThemeNotFoundError, + ThemeRegistry, ThemeStyleContent, DEFAULT_ICON_THEME_NAME, }; use anyhow::Result; use derive_more::{Deref, DerefMut}; @@ -157,7 +157,11 @@ impl ThemeSettings { // If the selected theme doesn't exist, fall back to a default theme // based on the system appearance. let theme_registry = ThemeRegistry::global(cx); - if theme_registry.get(theme_name).ok().is_none() { + if let Err(err @ ThemeNotFoundError(_)) = theme_registry.get(theme_name) { + if theme_registry.extensions_loaded() { + log::error!("{err}"); + } + theme_name = Self::default_theme(*system_appearance); }; @@ -180,11 +184,13 @@ impl ThemeSettings { // If the selected icon theme doesn't exist, fall back to the default theme. let theme_registry = ThemeRegistry::global(cx); - if theme_registry - .get_icon_theme(icon_theme_name) - .ok() - .is_none() + if let Err(err @ IconThemeNotFoundError(_)) = + theme_registry.get_icon_theme(icon_theme_name) { + if theme_registry.extensions_loaded() { + log::error!("{err}"); + } + icon_theme_name = DEFAULT_ICON_THEME_NAME; }; @@ -578,9 +584,14 @@ impl ThemeSettings { let mut new_theme = None; - if let Some(theme) = themes.get(theme).log_err() { - self.active_theme = theme.clone(); - new_theme = Some(theme); + match themes.get(theme) { + Ok(theme) => { + self.active_theme = theme.clone(); + new_theme = Some(theme); + } + Err(err @ ThemeNotFoundError(_)) => { + log::error!("{err}"); + } } self.apply_theme_overrides(); @@ -838,8 +849,15 @@ impl settings::Settings for ThemeSettings { let theme_name = value.theme(*system_appearance); - if let Some(theme) = themes.get(theme_name).log_err() { - this.active_theme = theme; + match themes.get(theme_name) { + Ok(theme) => { + this.active_theme = theme; + } + Err(err @ ThemeNotFoundError(_)) => { + if themes.extensions_loaded() { + log::error!("{err}"); + } + } } } @@ -851,8 +869,15 @@ impl settings::Settings for ThemeSettings { let icon_theme_name = value.icon_theme(*system_appearance); - if let Some(icon_theme) = themes.get_icon_theme(icon_theme_name).log_err() { - this.active_icon_theme = icon_theme; + match themes.get_icon_theme(icon_theme_name) { + Ok(icon_theme) => { + this.active_icon_theme = icon_theme; + } + Err(err @ IconThemeNotFoundError(_)) => { + if themes.extensions_loaded() { + log::error!("{err}"); + } + } } } diff --git a/crates/theme_extension/src/theme_extension.rs b/crates/theme_extension/src/theme_extension.rs index 83903da6c69147..b9c6ed6d4b9c5b 100644 --- a/crates/theme_extension/src/theme_extension.rs +++ b/crates/theme_extension/src/theme_extension.rs @@ -24,6 +24,10 @@ struct ThemeRegistryProxy { } impl ExtensionThemeProxy for ThemeRegistryProxy { + fn set_extensions_loaded(&self) { + self.theme_registry.set_extensions_loaded(); + } + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>> { self.executor.spawn(async move { let themes = theme::read_user_theme(&theme_path, fs).await?; diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index aaca1b02106713..d64591707c4441 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -330,17 +330,22 @@ impl PickerDelegate for ThemeSelectorDelegate { ) -> Option { Some( h_flex() - .w_full() .p_2() + .w_full() + .justify_between() .gap_2() .border_t_1() .border_color(cx.theme().colors().border_variant) .child( - Button::new("docs", "Theme Docs").on_click(cx.listener(|_, _, _, cx| { - cx.open_url("https://zed.dev/docs/themes"); - })), + Button::new("docs", "View Theme Docs") + .icon(IconName::ArrowUpRight) + .icon_position(IconPosition::End) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .on_click(cx.listener(|_, _, _, cx| { + cx.open_url("https://zed.dev/docs/themes"); + })), ) - .child(div().flex_grow()) .child( Button::new("more-themes", "Install Themes").on_click(cx.listener({ move |_, _, window, cx| { diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 4a484abffa0b70..93e60b4ae0006b 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -7,7 +7,7 @@ use editor::{ scroll::Autoscroll, Bias, Editor, ToPoint, }; -use gpui::{actions, impl_internal_actions, Action, App, Context, Global, Window}; +use gpui::{actions, impl_internal_actions, Action, App, AppContext as _, Context, Global, Window}; use itertools::Itertools; use language::Point; use multi_buffer::MultiBufferRow; @@ -1185,8 +1185,7 @@ impl OnMatchingLines { .clip_point(Point::new(range.end.0 + 1, 0), Bias::Left); cx.spawn_in(window, |editor, mut cx| async move { let new_selections = cx - .background_executor() - .spawn(async move { + .background_spawn(async move { let mut line = String::new(); let mut new_selections = Vec::new(); let chunks = snapshot @@ -1514,22 +1513,20 @@ impl ShellExec { if let Some(mut stdin) = running.stdin.take() { if let Some(snapshot) = input_snapshot { let range = range.clone(); - cx.background_executor() - .spawn(async move { - for chunk in snapshot.text_for_range(range) { - if stdin.write_all(chunk.as_bytes()).log_err().is_none() { - return; - } + cx.background_spawn(async move { + for chunk in snapshot.text_for_range(range) { + if stdin.write_all(chunk.as_bytes()).log_err().is_none() { + return; } - stdin.flush().log_err(); - }) - .detach(); + } + stdin.flush().log_err(); + }) + .detach(); } }; let output = cx - .background_executor() - .spawn(async move { running.wait_with_output() }) + .background_spawn(async move { running.wait_with_output() }) .await; let Some(output) = output.log_err() else { diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 9218c6dff65871..4523ec802cc26b 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -1546,7 +1546,9 @@ fn surrounding_markers( } // Adjust closing.start to exclude whitespace after a newline, if present if let Some(end) = last_newline_end { - closing.start = end; + if end > opening.end { + closing.start = end; + } } } diff --git a/crates/workspace/src/shared_screen/cross_platform.rs b/crates/workspace/src/shared_screen/cross_platform.rs index 9c0bee56a2f677..376140622a25ca 100644 --- a/crates/workspace/src/shared_screen/cross_platform.rs +++ b/crates/workspace/src/shared_screen/cross_platform.rs @@ -5,7 +5,7 @@ use crate::{ use call::{RemoteVideoTrack, RemoteVideoTrackView}; use client::{proto::PeerId, User}; use gpui::{ - div, AppContext, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, + div, AppContext as _, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, ParentElement, Render, SharedString, Styled, }; use std::sync::Arc; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2bb07a1cc34b8c..ab1accb9cfbc5f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4356,8 +4356,7 @@ impl Workspace { self.update_active_view_for_followers(window, cx); if let Some(database_id) = self.database_id { - cx.background_executor() - .spawn(persistence::DB.update_timestamp(database_id)) + cx.background_spawn(persistence::DB.update_timestamp(database_id)) .detach(); } } else { @@ -4653,8 +4652,7 @@ impl Workspace { if let Ok(Some(task)) = this.update_in(cx, |workspace, window, cx| { item.serialize(workspace, false, window, cx) }) { - cx.background_executor() - .spawn(async move { task.await.log_err() }) + cx.background_spawn(async move { task.await.log_err() }) .detach(); } } @@ -5017,8 +5015,7 @@ impl Workspace { ) { self.centered_layout = !self.centered_layout; if let Some(database_id) = self.database_id() { - cx.background_executor() - .spawn(DB.set_centered_layout(database_id, self.centered_layout)) + cx.background_spawn(DB.set_centered_layout(database_id, self.centered_layout)) .detach_and_log_err(cx); } cx.notify(); @@ -6275,7 +6272,7 @@ fn serialize_ssh_project( Option, )>, > { - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let serialized_ssh_project = persistence::DB .get_or_create_ssh_project( connection_options.host.clone(), diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 1cf9f29b128b43..c99bcd43562d16 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -791,23 +791,22 @@ impl Worktree { // Apply updates to a separate snapshot in a background task, then // send them to a foreground task which updates the model. - cx.background_executor() - .spawn(async move { - while let Some(update) = background_updates_rx.next().await { + cx.background_spawn(async move { + while let Some(update) = background_updates_rx.next().await { + { + let mut lock = background_snapshot.lock(); + if let Err(error) = lock + .0 + .apply_remote_update(update.clone(), &settings.file_scan_inclusions) { - let mut lock = background_snapshot.lock(); - if let Err(error) = lock - .0 - .apply_remote_update(update.clone(), &settings.file_scan_inclusions) - { - log::error!("error applying worktree update: {}", error); - } - lock.1.push(update); + log::error!("error applying worktree update: {}", error); } - snapshot_updated_tx.send(()).await.ok(); + lock.1.push(update); } - }) - .detach(); + snapshot_updated_tx.send(()).await.ok(); + } + }) + .detach(); // On the foreground task, update to the latest snapshot and notify // any update observer of all updates that led to that snapshot. @@ -991,7 +990,7 @@ impl Worktree { Worktree::Local(this) => { let path = Arc::from(path); let snapshot = this.snapshot(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { if let Some(repo) = snapshot.repository_for_path(&path) { if let Some(repo_path) = repo.relativize(&path).log_err() { if let Some(git_repo) = @@ -1015,7 +1014,7 @@ impl Worktree { Worktree::Local(this) => { let path = Arc::from(path); let snapshot = this.snapshot(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { if let Some(repo) = snapshot.repository_for_path(&path) { if let Some(repo_path) = repo.relativize(&path).log_err() { if let Some(git_repo) = @@ -1431,7 +1430,7 @@ impl LocalWorktree { let git_hosting_provider_registry = GitHostingProviderRegistry::try_global(cx); let settings = self.settings.clone(); let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded(); - let background_scanner = cx.background_executor().spawn({ + let background_scanner = cx.background_spawn({ let abs_path = snapshot.abs_path.as_path().to_path_buf(); let background = cx.background_executor().clone(); async move { @@ -1668,7 +1667,7 @@ impl LocalWorktree { let is_private = self.is_path_private(path.as_ref()); let worktree = cx.weak_entity(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { let abs_path = abs_path?; let content = fs.load_bytes(&abs_path).await?; @@ -1774,7 +1773,7 @@ impl LocalWorktree { let path_excluded = self.settings.is_path_excluded(&abs_path); let fs = self.fs.clone(); let task_abs_path = abs_path.clone(); - let write = cx.background_executor().spawn(async move { + let write = cx.background_spawn(async move { if is_dir { fs.create_dir(&task_abs_path) .await @@ -1838,7 +1837,7 @@ impl LocalWorktree { return Task::ready(Err(anyhow!("invalid path {path:?}"))); }; - let write = cx.background_executor().spawn({ + let write = cx.background_spawn({ let fs = fs.clone(); let abs_path = abs_path.clone(); async move { fs.save(&abs_path, &text, line_ending).await } @@ -1890,7 +1889,7 @@ impl LocalWorktree { let abs_path = self.absolutize(&entry.path); let fs = self.fs.clone(); - let delete = cx.background_executor().spawn(async move { + let delete = cx.background_spawn(async move { if entry.is_file() { if trash { fs.trash_file(&abs_path?, Default::default()).await?; @@ -1964,7 +1963,7 @@ impl LocalWorktree { let abs_path = abs_new_path.clone(); let fs = self.fs.clone(); let case_sensitive = self.fs_case_sensitive; - let rename = cx.background_executor().spawn(async move { + let rename = cx.background_spawn(async move { let abs_old_path = abs_old_path?; let abs_new_path = abs_new_path; @@ -2033,7 +2032,7 @@ impl LocalWorktree { }; let abs_new_path = self.absolutize(&new_path); let fs = self.fs.clone(); - let copy = cx.background_executor().spawn(async move { + let copy = cx.background_spawn(async move { copy_recursive( fs.as_ref(), &abs_old_path?, @@ -2085,27 +2084,26 @@ impl LocalWorktree { .collect::>(); cx.spawn(|this, cx| async move { - cx.background_executor() - .spawn(async move { - for (source, target) in paths { - copy_recursive( - fs.as_ref(), - &source, - &target, - fs::CopyOptions { - overwrite: overwrite_existing_files, - ..Default::default() - }, - ) - .await - .with_context(|| { - anyhow!("Failed to copy file from {source:?} to {target:?}") - })?; - } - Ok::<(), anyhow::Error>(()) - }) - .await - .log_err(); + cx.background_spawn(async move { + for (source, target) in paths { + copy_recursive( + fs.as_ref(), + &source, + &target, + fs::CopyOptions { + overwrite: overwrite_existing_files, + ..Default::default() + }, + ) + .await + .with_context(|| { + anyhow!("Failed to copy file from {source:?} to {target:?}") + })?; + } + Ok::<(), anyhow::Error>(()) + }) + .await + .log_err(); let mut refresh = cx.read_entity( &this.upgrade().with_context(|| "Dropped worktree")?, |this, _| { @@ -2117,13 +2115,12 @@ impl LocalWorktree { }, )??; - cx.background_executor() - .spawn(async move { - refresh.next().await; - Ok::<(), anyhow::Error>(()) - }) - .await - .log_err(); + cx.background_spawn(async move { + refresh.next().await; + Ok::<(), anyhow::Error>(()) + }) + .await + .log_err(); let this = this.upgrade().with_context(|| "Dropped worktree")?; cx.read_entity(&this, |this, _| { @@ -2142,7 +2139,7 @@ impl LocalWorktree { ) -> Option>> { let path = self.entry_for_id(entry_id)?.path.clone(); let mut refresh = self.refresh_entries_for_paths(vec![path]); - Some(cx.background_executor().spawn(async move { + Some(cx.background_spawn(async move { refresh.next().await; Ok(()) })) @@ -2155,7 +2152,7 @@ impl LocalWorktree { ) -> Option>> { let path = self.entry_for_id(entry_id).unwrap().path.clone(); let mut rx = self.add_path_prefix_to_scan(path.clone()); - Some(cx.background_executor().spawn(async move { + Some(cx.background_spawn(async move { rx.next().await; Ok(()) })) @@ -2229,7 +2226,7 @@ impl LocalWorktree { .ok(); let worktree_id = cx.entity_id().as_u64(); - let _maintain_remote_snapshot = cx.background_executor().spawn(async move { + let _maintain_remote_snapshot = cx.background_spawn(async move { let mut is_first = true; while let Some((snapshot, entry_changes, repo_changes)) = snapshots_rx.next().await { let update = if is_first { @@ -3719,16 +3716,14 @@ impl language::LocalFile for File { let worktree = self.worktree.read(cx).as_local().unwrap(); let abs_path = worktree.absolutize(&self.path); let fs = worktree.fs.clone(); - cx.background_executor() - .spawn(async move { fs.load(&abs_path?).await }) + cx.background_spawn(async move { fs.load(&abs_path?).await }) } fn load_bytes(&self, cx: &App) -> Task>> { let worktree = self.worktree.read(cx).as_local().unwrap(); let abs_path = worktree.absolutize(&self.path); let fs = worktree.fs.clone(); - cx.background_executor() - .spawn(async move { fs.load_bytes(&abs_path?).await }) + cx.background_spawn(async move { fs.load_bytes(&abs_path?).await }) } } diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 2a704c62e898ef..405c1b752bb67f 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -11,7 +11,7 @@ use git::{ }, GITIGNORE, }; -use gpui::{BorrowAppContext, Context, Task, TestAppContext}; +use gpui::{AppContext as _, BorrowAppContext, Context, Task, TestAppContext}; use parking_lot::Mutex; use postage::stream::Stream; use pretty_assertions::assert_eq; @@ -1954,7 +1954,7 @@ fn randomly_mutate_worktree( new_path ); let task = worktree.rename_entry(entry.id, new_path, cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { task.await?.to_included().unwrap(); Ok(()) }) @@ -1969,7 +1969,7 @@ fn randomly_mutate_worktree( child_path, ); let task = worktree.create_entry(child_path, is_dir, cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { task.await?; Ok(()) }) @@ -1977,7 +1977,7 @@ fn randomly_mutate_worktree( log::info!("overwriting file {:?} ({})", entry.path, entry.id.0); let task = worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { task.await?; Ok(()) }) diff --git a/crates/zed/src/reliability.rs b/crates/zed/src/reliability.rs index dfd1ba8064ada6..614401b1493907 100644 --- a/crates/zed/src/reliability.rs +++ b/crates/zed/src/reliability.rs @@ -4,7 +4,7 @@ use backtrace::{self, Backtrace}; use chrono::Utc; use client::{telemetry, TelemetrySettings}; use db::kvp::KEY_VALUE_STORE; -use gpui::{App, SemanticVersion}; +use gpui::{App, AppContext as _, SemanticVersion}; use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method}; use paths::{crashes_dir, crashes_retired_dir}; use project::Project; @@ -205,35 +205,34 @@ pub fn init( ssh_client.update(cx, |client, cx| { if TelemetrySettings::get_global(cx).diagnostics { let request = client.proto_client().request(proto::GetPanicFiles {}); - cx.background_executor() - .spawn(async move { - let panic_files = request.await?; - for file in panic_files.file_contents { - let panic: Option = serde_json::from_str(&file) - .log_err() - .or_else(|| { - file.lines() - .next() - .and_then(|line| serde_json::from_str(line).ok()) - }) - .unwrap_or_else(|| { - log::error!("failed to deserialize panic file {:?}", file); - None - }); - - if let Some(mut panic) = panic { - panic.session_id = session_id.clone(); - panic.system_id = system_id.clone(); - panic.installation_id = installation_id.clone(); - - upload_panic(&http_client, &panic_report_url, panic, &mut None) - .await?; - } + cx.background_spawn(async move { + let panic_files = request.await?; + for file in panic_files.file_contents { + let panic: Option = serde_json::from_str(&file) + .log_err() + .or_else(|| { + file.lines() + .next() + .and_then(|line| serde_json::from_str(line).ok()) + }) + .unwrap_or_else(|| { + log::error!("failed to deserialize panic file {:?}", file); + None + }); + + if let Some(mut panic) = panic { + panic.session_id = session_id.clone(); + panic.system_id = system_id.clone(); + panic.installation_id = installation_id.clone(); + + upload_panic(&http_client, &panic_report_url, panic, &mut None) + .await?; } + } - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); } }) } @@ -450,18 +449,17 @@ fn upload_panics_and_crashes( cx: &App, ) { let telemetry_settings = *client::TelemetrySettings::get_global(cx); - cx.background_executor() - .spawn(async move { - let most_recent_panic = - upload_previous_panics(http.clone(), &panic_report_url, telemetry_settings) - .await - .log_err() - .flatten(); - upload_previous_crashes(http, most_recent_panic, installation_id, telemetry_settings) + cx.background_spawn(async move { + let most_recent_panic = + upload_previous_panics(http.clone(), &panic_report_url, telemetry_settings) .await .log_err() - }) - .detach() + .flatten(); + upload_previous_crashes(http, most_recent_panic, installation_id, telemetry_settings) + .await + .log_err() + }) + .detach() } /// Uploads panics via `zed.dev`. diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 17d4581d2df9bd..3addd0ecbb43b7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -954,7 +954,7 @@ fn install_cli( Some(LINUX_PROMPT_DETAIL), &["Ok"], ); - cx.background_executor().spawn(prompt).detach(); + cx.background_spawn(prompt).detach(); return Ok(()); } let path = install_cli::install_cli(cx.deref()) @@ -1333,7 +1333,7 @@ fn show_markdown_app_notification( ) where F: 'static + Send + Sync + Fn(&mut Window, &mut Context), { - let parsed_markdown = cx.background_executor().spawn(async move { + let parsed_markdown = cx.background_spawn(async move { let file_location_directory = None; let language_registry = None; markdown_preview::markdown_parser::parse_markdown( diff --git a/crates/zed/src/zed/inline_completion_registry.rs b/crates/zed/src/zed/inline_completion_registry.rs index 21351265447c5d..3e5107d1c373d9 100644 --- a/crates/zed/src/zed/inline_completion_registry.rs +++ b/crates/zed/src/zed/inline_completion_registry.rs @@ -3,7 +3,7 @@ use collections::HashMap; use copilot::{Copilot, CopilotCompletionProvider}; use editor::{Editor, EditorMode}; use feature_flags::{FeatureFlagAppExt, PredictEditsFeatureFlag}; -use gpui::{AnyWindowHandle, App, AppContext, Context, Entity, WeakEntity}; +use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity}; use language::language_settings::{all_language_settings, EditPredictionProvider}; use settings::SettingsStore; use std::{cell::RefCell, rc::Rc, sync::Arc}; diff --git a/crates/zed/src/zed/linux_prompts.rs b/crates/zed/src/zed/linux_prompts.rs index 01b1e53aa8eade..e838c8b029df41 100644 --- a/crates/zed/src/zed/linux_prompts.rs +++ b/crates/zed/src/zed/linux_prompts.rs @@ -1,7 +1,7 @@ use gpui::{ div, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, InteractiveElement, IntoElement, ParentElement, PromptHandle, PromptLevel, PromptResponse, - Refineable, Render, RenderablePromptHandle, Styled, TextStyleRefinement, Window, + Refineable, Render, RenderablePromptHandle, SharedString, Styled, TextStyleRefinement, Window, }; use markdown::{Markdown, MarkdownStyle}; use settings::Settings; @@ -48,7 +48,14 @@ pub fn fallback_prompt_renderer( selection_background_color: { cx.theme().players().local().selection }, ..Default::default() }; - Markdown::new(text.to_string(), markdown_style, None, None, window, cx) + Markdown::new( + SharedString::new(text), + markdown_style, + None, + None, + window, + cx, + ) }) }), } diff --git a/crates/zeta/src/zeta.rs b/crates/zeta/src/zeta.rs index 30cab4b91b2ca9..22feffcf9449a1 100644 --- a/crates/zeta/src/zeta.rs +++ b/crates/zeta/src/zeta.rs @@ -419,8 +419,7 @@ impl Zeta { } let values = cx - .background_executor() - .spawn({ + .background_spawn({ let snapshot = snapshot.clone(); let path = path.clone(); async move { @@ -813,8 +812,7 @@ and then another let output_excerpt: Arc = output_excerpt.into(); let edits: Arc<[(Range, String)]> = cx - .background_executor() - .spawn({ + .background_spawn({ let output_excerpt = output_excerpt.clone(); let editable_range = editable_range.clone(); let snapshot = snapshot.clone(); @@ -1127,7 +1125,7 @@ impl LicenseDetectionWatcher { .map(|file| worktree.load_file(file, cx)) .collect::>(); - cx.background_executor().spawn(async move { + cx.background_spawn(async move { for loaded_file in loaded_files.into_iter() { let Ok(loaded_file) = loaded_file.await else { continue; diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index f729dd3db06f87..b863425b942b4b 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -483,7 +483,7 @@ List of `string` values - Description: The debounce delay before querying highlights based on the selected text. - Setting: `selection_highlight_debounce` -- Default: `75` +- Default: `50` ## LSP Highlight Debounce diff --git a/script/issue_response/main.js b/script/issue_response/main.js index da6cc1eb341392..783eea51876624 100644 --- a/script/issue_response/main.js +++ b/script/issue_response/main.js @@ -9,7 +9,9 @@ import { IncomingWebhook } from "@slack/webhook"; const SECTION_BLOCK_TEXT_LIMIT = 3000; async function main() { - const octokit = new Octokit({ auth: process.env["GITHUB_TOKEN"] }); + const octokit = new Octokit({ + auth: process.env["ISSUE_RESPONSE_GITHUB_TOKEN"], + }); if (!process.env["SLACK_ISSUE_RESPONSE_WEBHOOK_URL"]) { throw new Error("SLACK_ISSUE_RESPONSE_WEBHOOK_URL is not set"); @@ -21,8 +23,9 @@ async function main() { const owner = "zed-industries"; const repo = "zed"; - const staff = await octokit.paginate(octokit.rest.orgs.listMembers, { + const staff = await octokit.paginate(octokit.rest.teams.listMembersInOrg, { org: owner, + team_slug: "staff", per_page: 100, }); let staffHandles = staff.map((member) => member.login);