build: Simplify build graph (#47253)

Piotr Osiewicz and Zed Zippy created

- **title_bar: Extract platform_title_bar from title_bar**
- **file_finder no longer depends on search and agent_servers no longer
depend on language_models**

Release Notes:

- N/A

---------

Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>

Change summary

Cargo.lock                                                        | 71 
Cargo.toml                                                        |  4 
crates/acp_thread/Cargo.toml                                      |  4 
crates/acp_thread/src/acp_thread.rs                               |  2 
crates/acp_thread/src/diff.rs                                     |  8 
crates/agent_servers/Cargo.toml                                   |  3 
crates/agent_servers/src/e2e_tests.rs                             |  6 
crates/agent_servers/src/gemini.rs                                | 30 
crates/agent_ui/Cargo.toml                                        |  2 
crates/agent_ui/src/acp/thread_view.rs                            |  1 
crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs | 23 
crates/agent_ui/src/inline_prompt_editor.rs                       |  6 
crates/agent_ui/src/text_thread_editor.rs                         |  1 
crates/collab/Cargo.toml                                          |  1 
crates/collab/src/tests/debug_panel_tests.rs                      | 14 
crates/collab/src/tests/editor_tests.rs                           | 12 
crates/collab_ui/src/collab_panel/contact_finder.rs               |  2 
crates/component_preview/src/component_preview.rs                 |  2 
crates/debugger_ui/src/new_process_modal.rs                       |  2 
crates/debugger_ui/src/tests/debugger_panel.rs                    | 16 
crates/editor/Cargo.toml                                          |  1 
crates/editor/src/actions.rs                                      |  4 
crates/editor/src/editor.rs                                       | 94 
crates/file_finder/Cargo.toml                                     |  4 
crates/file_finder/src/file_finder.rs                             | 13 
crates/git_ui/Cargo.toml                                          |  1 
crates/git_ui/src/branch_picker.rs                                |  5 
crates/git_ui/src/git_panel.rs                                    |  4 
crates/inspector_ui/Cargo.toml                                    |  2 
crates/inspector_ui/src/inspector.rs                              |  2 
crates/keymap_editor/src/keymap_editor.rs                         | 32 
crates/language_models/src/provider/google.rs                     | 19 
crates/language_models/src/provider/ollama.rs                     |  2 
crates/language_selector/Cargo.toml                               |  2 
crates/language_selector/src/language_selector.rs                 |  5 
crates/multi_buffer/src/multi_buffer.rs                           |  8 
crates/open_path_prompt/Cargo.toml                                | 33 
crates/open_path_prompt/LICENSE-GPL                               |  1 
crates/open_path_prompt/src/file_finder_settings.rs               |  0 
crates/open_path_prompt/src/open_path_prompt.rs                   | 17 
crates/open_path_prompt/src/open_path_prompt_tests.rs             |  4 
crates/picker/Cargo.toml                                          |  3 
crates/picker/src/head.rs                                         | 31 
crates/picker/src/picker.rs                                       | 67 
crates/platform_title_bar/Cargo.toml                              | 25 
crates/platform_title_bar/LICENSE-GPL                             |  1 
crates/platform_title_bar/src/platform_title_bar.rs               | 11 
crates/platform_title_bar/src/platforms.rs                        |  0 
crates/platform_title_bar/src/platforms/platform_linux.rs         |  0 
crates/platform_title_bar/src/platforms/platform_mac.rs           |  0 
crates/platform_title_bar/src/platforms/platform_windows.rs       |  0 
crates/platform_title_bar/src/system_window_tabs.rs               |  0 
crates/recent_projects/Cargo.toml                                 |  2 
crates/recent_projects/src/remote_servers.rs                      |  7 
crates/rules_library/Cargo.toml                                   |  5 
crates/rules_library/src/rules_library.rs                         | 11 
crates/search/Cargo.toml                                          |  1 
crates/search/src/search.rs                                       |  3 
crates/settings_ui/Cargo.toml                                     |  4 
crates/settings_ui/src/components.rs                              |  2 
crates/settings_ui/src/components/number_field.rs                 | 31 
crates/settings_ui/src/settings_ui.rs                             |  8 
crates/snippets_ui/Cargo.toml                                     |  2 
crates/snippets_ui/src/snippets_ui.rs                             |  2 
crates/title_bar/Cargo.toml                                       |  1 
crates/title_bar/src/title_bar.rs                                 | 13 
crates/toolchain_selector/Cargo.toml                              |  2 
crates/toolchain_selector/src/toolchain_selector.rs               | 10 
crates/ui_input/Cargo.toml                                        |  4 
crates/ui_input/src/input_field.rs                                | 96 
crates/ui_input/src/ui_input.rs                                   | 36 
crates/zed_actions/src/lib.rs                                     | 23 
72 files changed, 531 insertions(+), 333 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -14,7 +14,6 @@ dependencies = [
  "buffer_diff",
  "chrono",
  "collections",
- "editor",
  "env_logger 0.11.8",
  "file_icons",
  "futures 0.3.31",
@@ -25,6 +24,7 @@ dependencies = [
  "language",
  "language_model",
  "markdown",
+ "multi_buffer",
  "parking_lot",
  "portable-pty",
  "project",
@@ -38,6 +38,7 @@ dependencies = [
  "telemetry",
  "tempfile",
  "terminal",
+ "text",
  "ui",
  "url",
  "urlencoding",
@@ -272,17 +273,18 @@ dependencies = [
  "chrono",
  "client",
  "collections",
+ "credentials_provider",
  "env_logger 0.11.8",
  "feature_flags",
  "fs",
  "futures 0.3.31",
+ "google_ai",
  "gpui",
  "gpui_tokio",
  "http_client",
  "indoc",
  "language",
  "language_model",
- "language_models",
  "libc",
  "log",
  "nix 0.29.0",
@@ -3370,6 +3372,7 @@ dependencies = [
  "uuid",
  "workspace",
  "worktree",
+ "zed_actions",
  "zlog",
 ]
 
@@ -5519,6 +5522,7 @@ dependencies = [
  "tree-sitter-typescript",
  "tree-sitter-yaml",
  "ui",
+ "ui_input",
  "unicode-script",
  "unicode-segmentation",
  "unicode-width",
@@ -6224,11 +6228,10 @@ dependencies = [
  "gpui",
  "language",
  "menu",
+ "open_path_prompt",
  "picker",
  "pretty_assertions",
  "project",
- "schemars",
- "search",
  "serde",
  "serde_json",
  "settings",
@@ -6237,6 +6240,7 @@ dependencies = [
  "ui",
  "util",
  "workspace",
+ "zed_actions",
  "zlog",
 ]
 
@@ -7271,6 +7275,7 @@ dependencies = [
  "time_format",
  "tracing",
  "ui",
+ "ui_input",
  "unindent",
  "util",
  "watch",
@@ -8432,11 +8437,11 @@ dependencies = [
  "fuzzy",
  "gpui",
  "language",
+ "platform_title_bar",
  "project",
  "serde_json",
  "serde_json_lenient",
  "theme",
- "title_bar",
  "ui",
  "util",
  "util_macros",
@@ -9123,11 +9128,11 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "editor",
- "file_finder",
  "file_icons",
  "fuzzy",
  "gpui",
  "language",
+ "open_path_prompt",
  "picker",
  "project",
  "settings",
@@ -11136,6 +11141,27 @@ dependencies = [
  "thiserror 2.0.17",
 ]
 
+[[package]]
+name = "open_path_prompt"
+version = "0.1.0"
+dependencies = [
+ "editor",
+ "file_icons",
+ "futures 0.3.31",
+ "fuzzy",
+ "gpui",
+ "picker",
+ "project",
+ "schemars",
+ "serde",
+ "serde_json",
+ "settings",
+ "theme",
+ "ui",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "open_router"
 version = "0.1.0"
@@ -12159,7 +12185,9 @@ dependencies = [
  "serde_json",
  "theme",
  "ui",
+ "ui_input",
  "workspace",
+ "zed_actions",
 ]
 
 [[package]]
@@ -12254,6 +12282,20 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
 
+[[package]]
+name = "platform_title_bar"
+version = "0.1.0"
+dependencies = [
+ "gpui",
+ "settings",
+ "smallvec",
+ "theme",
+ "ui",
+ "windows 0.61.3",
+ "workspace",
+ "zed_actions",
+]
+
 [[package]]
 name = "plist"
 version = "1.8.0"
@@ -13389,7 +13431,6 @@ dependencies = [
  "editor",
  "extension",
  "extension_host",
- "file_finder",
  "fs",
  "futures 0.3.31",
  "fuzzy",
@@ -13401,6 +13442,7 @@ dependencies = [
  "markdown",
  "menu",
  "node_runtime",
+ "open_path_prompt",
  "ordered-float 2.10.1",
  "paths",
  "picker",
@@ -14057,14 +14099,15 @@ dependencies = [
  "log",
  "menu",
  "picker",
+ "platform_title_bar",
  "prompt_store",
  "release_channel",
  "rope",
  "serde",
  "settings",
  "theme",
- "title_bar",
  "ui",
+ "ui_input",
  "util",
  "workspace",
  "zed_actions",
@@ -15060,6 +15103,7 @@ dependencies = [
  "assets",
  "bm25",
  "client",
+ "component",
  "copilot_ui",
  "edit_prediction",
  "editor",
@@ -15077,6 +15121,7 @@ dependencies = [
  "node_runtime",
  "paths",
  "picker",
+ "platform_title_bar",
  "pretty_assertions",
  "project",
  "recent_projects",
@@ -15092,7 +15137,6 @@ dependencies = [
  "theme",
  "title_bar",
  "ui",
- "ui_input",
  "util",
  "workspace",
  "zed_actions",
@@ -15389,11 +15433,11 @@ dependencies = [
 name = "snippets_ui"
 version = "0.1.0"
 dependencies = [
- "file_finder",
  "file_icons",
  "fuzzy",
  "gpui",
  "language",
+ "open_path_prompt",
  "paths",
  "picker",
  "settings",
@@ -17051,6 +17095,7 @@ dependencies = [
  "http_client",
  "menu",
  "notifications",
+ "platform_title_bar",
  "pretty_assertions",
  "project",
  "recent_projects",
@@ -17325,12 +17370,12 @@ dependencies = [
  "anyhow",
  "convert_case 0.8.0",
  "editor",
- "file_finder",
  "futures 0.3.31",
  "fuzzy",
  "gpui",
  "language",
  "menu",
+ "open_path_prompt",
  "picker",
  "project",
  "ui",
@@ -18000,11 +18045,7 @@ name = "ui_input"
 version = "0.1.0"
 dependencies = [
  "component",
- "editor",
  "gpui",
- "menu",
- "settings",
- "theme",
  "ui",
 ]
 

Cargo.toml 🔗

@@ -125,6 +125,7 @@ members = [
     "crates/ollama",
     "crates/onboarding",
     "crates/open_ai",
+    "crates/open_path_prompt",
     "crates/open_router",
     "crates/outline",
     "crates/outline_panel",
@@ -187,6 +188,7 @@ members = [
     "crates/theme_importer",
     "crates/theme_selector",
     "crates/time_format",
+    "crates/platform_title_bar",
     "crates/title_bar",
     "crates/toolchain_selector",
     "crates/ui",
@@ -363,6 +365,7 @@ notifications = { path = "crates/notifications" }
 ollama = { path = "crates/ollama" }
 onboarding = { path = "crates/onboarding" }
 open_ai = { path = "crates/open_ai" }
+open_path_prompt = { path = "crates/open_path_prompt" }
 open_router = { path = "crates/open_router", features = ["schemars"] }
 outline = { path = "crates/outline" }
 outline_panel = { path = "crates/outline_panel" }
@@ -420,6 +423,7 @@ theme = { path = "crates/theme" }
 theme_extension = { path = "crates/theme_extension" }
 theme_selector = { path = "crates/theme_selector" }
 time_format = { path = "crates/time_format" }
+platform_title_bar = { path = "crates/platform_title_bar" }
 title_bar = { path = "crates/title_bar" }
 toolchain_selector = { path = "crates/toolchain_selector" }
 ui = { path = "crates/ui" }

crates/acp_thread/Cargo.toml 🔗

@@ -24,7 +24,7 @@ anyhow.workspace = true
 buffer_diff.workspace = true
 chrono.workspace = true
 collections.workspace = true
-editor.workspace = true
+multi_buffer.workspace = true
 file_icons.workspace = true
 futures.workspace = true
 gpui.workspace = true
@@ -44,6 +44,7 @@ smol.workspace = true
 task.workspace = true
 telemetry.workspace = true
 terminal.workspace = true
+text.workspace = true
 ui.workspace = true
 url.workspace = true
 util.workspace = true
@@ -52,7 +53,6 @@ watch.workspace = true
 urlencoding.workspace = true
 
 [dev-dependencies]
-editor = { workspace = true, features = ["test-support"] }
 env_logger.workspace = true
 gpui = { workspace = true, "features" = ["test-support"] }
 indoc.workspace = true

crates/acp_thread/src/acp_thread.rs 🔗

@@ -39,7 +39,6 @@ pub use terminal::*;
 use action_log::{ActionLog, ActionLogTelemetry};
 use agent_client_protocol::{self as acp};
 use anyhow::{Context as _, Result, anyhow};
-use editor::Bias;
 use futures::{FutureExt, channel::oneshot, future::BoxFuture};
 use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
 use itertools::Itertools;
@@ -54,6 +53,7 @@ use std::process::ExitStatus;
 use std::rc::Rc;
 use std::time::{Duration, Instant};
 use std::{fmt::Display, mem, path::PathBuf, sync::Arc};
+use text::Bias;
 use ui::App;
 use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle};
 use uuid::Uuid;

crates/acp_thread/src/diff.rs 🔗

@@ -1,11 +1,11 @@
 use anyhow::Result;
 use buffer_diff::BufferDiff;
-use editor::{MultiBuffer, PathKey, multibuffer_context_lines};
 use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
 use itertools::Itertools;
 use language::{
     Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,
 };
+use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};
 use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
 use util::ResultExt;
 
@@ -63,7 +63,7 @@ impl Diff {
                         PathKey::for_buffer(&buffer, cx),
                         buffer.clone(),
                         hunk_ranges,
-                        multibuffer_context_lines(cx),
+                        excerpt_context_lines(cx),
                         cx,
                     );
                     multibuffer.add_diff(diff, cx);
@@ -300,7 +300,7 @@ impl PendingDiff {
                         path_key,
                         buffer,
                         ranges,
-                        multibuffer_context_lines(cx),
+                        excerpt_context_lines(cx),
                         cx,
                     );
                     multibuffer.add_diff(buffer_diff.clone(), cx);
@@ -326,7 +326,7 @@ impl PendingDiff {
                 PathKey::for_buffer(&self.new_buffer, cx),
                 self.new_buffer.clone(),
                 ranges,
-                multibuffer_context_lines(cx),
+                excerpt_context_lines(cx),
                 cx,
             );
             let end = multibuffer.len(cx);

crates/agent_servers/Cargo.toml 🔗

@@ -32,10 +32,11 @@ fs.workspace = true
 futures.workspace = true
 gpui.workspace = true
 gpui_tokio = { workspace = true, optional = true }
+credentials_provider.workspace = true
+google_ai.workspace = true
 http_client.workspace = true
 indoc.workspace = true
 language_model.workspace = true
-language_models.workspace = true
 log.workspace = true
 project.workspace = true
 release_channel.workspace = true

crates/agent_servers/src/e2e_tests.rs 🔗

@@ -2,7 +2,7 @@ use crate::{AgentServer, AgentServerDelegate};
 use acp_thread::{AcpThread, AgentThreadEntry, ToolCall, ToolCallStatus};
 use agent_client_protocol as acp;
 use futures::{FutureExt, StreamExt, channel::mpsc, select};
-use gpui::{AppContext, Entity, TestAppContext};
+use gpui::{Entity, TestAppContext};
 use indoc::indoc;
 #[cfg(test)]
 use project::agent_server_store::BuiltinAgentServerSettings;
@@ -410,9 +410,7 @@ pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
         let http_client = reqwest_client::ReqwestClient::user_agent("agent tests").unwrap();
         cx.set_http_client(Arc::new(http_client));
         let client = client::Client::production(cx);
-        let user_store = cx.new(|cx| client::UserStore::new(client.clone(), cx));
-        language_model::init(client.clone(), cx);
-        language_models::init(user_store, client, cx);
+        language_model::init(client, cx);
 
         #[cfg(test)]
         project::agent_server_store::AllAgentServersSettings::override_global(

crates/agent_servers/src/gemini.rs 🔗

@@ -4,11 +4,33 @@ use std::{any::Any, path::Path};
 use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
 use acp_thread::AgentConnection;
 use anyhow::{Context as _, Result};
+use credentials_provider::CredentialsProvider;
 use gpui::{App, AppContext as _, SharedString, Task};
-use language_models::provider::google::GoogleLanguageModelProvider;
+use language_model::{ApiKey, EnvVar};
 use project::agent_server_store::{AllAgentServersSettings, GEMINI_NAME};
 use settings::SettingsStore;
 
+const GEMINI_API_KEY_VAR_NAME: &str = "GEMINI_API_KEY";
+const GOOGLE_AI_API_KEY_VAR_NAME: &str = "GOOGLE_AI_API_KEY";
+
+fn api_key_for_gemini_cli(cx: &mut App) -> Task<Result<String>> {
+    let env_var = EnvVar::new(GEMINI_API_KEY_VAR_NAME.into())
+        .or(EnvVar::new(GOOGLE_AI_API_KEY_VAR_NAME.into()));
+    if let Some(key) = env_var.value {
+        return Task::ready(Ok(key));
+    }
+    let credentials_provider = <dyn CredentialsProvider>::global(cx);
+    let api_url = google_ai::API_URL.to_string();
+    cx.spawn(async move |cx| {
+        Ok(
+            ApiKey::load_from_system_keychain(&api_url, credentials_provider.as_ref(), cx)
+                .await?
+                .key()
+                .to_string(),
+        )
+    })
+}
+
 #[derive(Clone)]
 pub struct Gemini;
 
@@ -46,11 +68,7 @@ impl AgentServer for Gemini {
         cx.spawn(async move |cx| {
             extra_env.insert("SURFACE".to_owned(), "zed".to_owned());
 
-            if let Some(api_key) = cx
-                .update(GoogleLanguageModelProvider::api_key_for_gemini_cli)
-                .await
-                .ok()
-            {
+            if let Some(api_key) = cx.update(api_key_for_gemini_cli).await.ok() {
                 extra_env.insert("GEMINI_API_KEY".into(), api_key);
             }
             let (command, root_dir, login) = store

crates/agent_ui/Cargo.toml 🔗

@@ -23,7 +23,6 @@ test-support = [
     "reqwest_client",
     "workspace/test-support",
     "agent/test-support",
-    "rules_library/test-support"
 ]
 unit-eval = []
 
@@ -128,7 +127,6 @@ editor = { workspace = true, features = ["test-support"] }
 eval_utils.workspace = true
 gpui = { workspace = true, "features" = ["test-support"] }
 git_ui = { workspace = true, features = ["test-support"] }
-rules_library = { workspace = true, features = ["test-support"] }
 indoc.workspace = true
 language = { workspace = true, "features" = ["test-support"] }
 languages = { workspace = true, features = ["test-support"] }

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -9539,6 +9539,7 @@ pub(crate) mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             theme::init(theme::LoadThemes::JustBase, cx);
+            editor::init(cx);
             release_channel::init(semver::Version::new(0, 0, 0), cx);
             prompt_store::init(cx)
         });

crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs 🔗

@@ -18,7 +18,7 @@ use workspace::{ModalView, Workspace};
 
 fn single_line_input(
     label: impl Into<SharedString>,
-    placeholder: impl Into<SharedString>,
+    placeholder: &str,
     text: Option<&str>,
     tab_index: isize,
     window: &mut Window,
@@ -31,9 +31,7 @@ fn single_line_input(
             .tab_stop(true);
 
         if let Some(text) = text {
-            input
-                .editor()
-                .update(cx, |editor, cx| editor.set_text(text, window, cx));
+            input.set_text(text, window, cx);
         }
         input
     })
@@ -721,9 +719,7 @@ mod tests {
         cx.update(|window, cx| {
             let model_input = ModelInput::new(0, window, cx);
             model_input.name.update(cx, |input, cx| {
-                input.editor().update(cx, |editor, cx| {
-                    editor.set_text("somemodel", window, cx);
-                });
+                input.set_text("somemodel", window, cx);
             });
             assert_eq!(
                 model_input.capabilities.supports_tools,
@@ -762,9 +758,7 @@ mod tests {
         cx.update(|window, cx| {
             let mut model_input = ModelInput::new(0, window, cx);
             model_input.name.update(cx, |input, cx| {
-                input.editor().update(cx, |editor, cx| {
-                    editor.set_text("somemodel", window, cx);
-                });
+                input.set_text("somemodel", window, cx);
             });
 
             model_input.capabilities.supports_tools = ToggleState::Unselected;
@@ -789,9 +783,7 @@ mod tests {
         cx.update(|window, cx| {
             let mut model_input = ModelInput::new(0, window, cx);
             model_input.name.update(cx, |input, cx| {
-                input.editor().update(cx, |editor, cx| {
-                    editor.set_text("somemodel", window, cx);
-                });
+                input.set_text("somemodel", window, cx);
             });
 
             model_input.capabilities.supports_tools = ToggleState::Selected;
@@ -817,6 +809,7 @@ mod tests {
             theme::init(theme::LoadThemes::JustBase, cx);
 
             language_model::init_settings(cx);
+            editor::init(cx);
         });
 
         let fs = FakeFs::new(cx.executor());
@@ -837,9 +830,7 @@ mod tests {
     ) -> Option<SharedString> {
         fn set_text(input: &Entity<InputField>, text: &str, window: &mut Window, cx: &mut App) {
             input.update(cx, |input, cx| {
-                input.editor().update(cx, |editor, cx| {
-                    editor.set_text(text, window, cx);
-                });
+                input.set_text(text, window, cx);
             });
         }
 

crates/agent_ui/src/inline_prompt_editor.rs 🔗

@@ -8,7 +8,6 @@ use editor::display_map::{CreaseId, EditorMargins};
 use editor::{AnchorRangeExt as _, MultiBufferOffset, ToOffset as _};
 use editor::{
     ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
-    actions::{MoveDown, MoveUp},
 };
 use fs::Fs;
 use gpui::{
@@ -31,7 +30,10 @@ use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
 use uuid::Uuid;
 use workspace::notifications::NotificationId;
 use workspace::{Toast, Workspace};
-use zed_actions::agent::ToggleModelSelector;
+use zed_actions::{
+    agent::ToggleModelSelector,
+    editor::{MoveDown, MoveUp},
+};
 
 use crate::agent_model_selector::AgentModelSelector;
 use crate::buffer_codegen::{BufferCodegen, CodegenAlternative};

crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -3542,6 +3542,7 @@ mod tests {
     fn init_test(cx: &mut App) {
         let settings_store = SettingsStore::test(cx);
         prompt_store::init(cx);
+        editor::init(cx);
         LanguageModelRegistry::test(cx);
         cx.set_global(settings_store);
 

crates/collab/Cargo.toml 🔗

@@ -130,4 +130,5 @@ unindent.workspace = true
 util.workspace = true
 workspace = { workspace = true, features = ["test-support"] }
 worktree = { workspace = true, features = ["test-support"] }
+zed_actions.workspace = true
 zlog.workspace = true

crates/collab/src/tests/debug_panel_tests.rs 🔗

@@ -933,8 +933,8 @@ async fn test_updated_breakpoints_send_to_dap(
         .unwrap();
 
     editor_b.update_in(remote_cx, |editor, window, cx| {
-        editor.move_down(&editor::actions::MoveDown, window, cx);
-        editor.move_down(&editor::actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
     });
 
@@ -972,8 +972,8 @@ async fn test_updated_breakpoints_send_to_dap(
 
     // remove the breakpoint that client B added
     editor_a.update_in(host_cx, |editor, window, cx| {
-        editor.move_down(&editor::actions::MoveDown, window, cx);
-        editor.move_down(&editor::actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
     });
 
@@ -1029,7 +1029,7 @@ async fn test_updated_breakpoints_send_to_dap(
     // Add our own breakpoint now
     editor_a.update_in(host_cx, |editor, window, cx| {
         editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
-        editor.move_up(&editor::actions::MoveUp, window, cx);
+        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
         editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
     });
 
@@ -1751,9 +1751,9 @@ async fn test_ignore_breakpoints(
     //     .unwrap();
 
     // local_editor.update_in(host_cx, |editor, window, cx| {
-    //     editor.move_down(&editor::actions::MoveDown, window, cx);
+    //     editor.move_down(&zed_actions::editor::MoveDown, window, cx);
     //     editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx); // Line 2
-    //     editor.move_down(&editor::actions::MoveDown, window, cx);
+    //     editor.move_down(&zed_actions::editor::MoveDown, window, cx);
     //     editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
     //     // Line 3
     // });

crates/collab/src/tests/editor_tests.rs 🔗

@@ -4185,8 +4185,8 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
 
     // Client B adds breakpoint on line(2)
     editor_b.update_in(cx_b, |editor, window, cx| {
-        editor.move_down(&editor::actions::MoveDown, window, cx);
-        editor.move_down(&editor::actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
     });
 
@@ -4214,8 +4214,8 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
 
     // Client A removes last added breakpoint from client B
     editor_a.update_in(cx_a, |editor, window, cx| {
-        editor.move_down(&editor::actions::MoveDown, window, cx);
-        editor.move_down(&editor::actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
     });
 
@@ -4243,8 +4243,8 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
 
     // Client B removes first added breakpoint by client A
     editor_b.update_in(cx_b, |editor, window, cx| {
-        editor.move_up(&editor::actions::MoveUp, window, cx);
-        editor.move_up(&editor::actions::MoveUp, window, cx);
+        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
+        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
         editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, window, cx);
     });
 

crates/collab_ui/src/collab_panel/contact_finder.rs 🔗

@@ -28,7 +28,7 @@ impl ContactFinder {
 
     pub fn set_query(&mut self, query: String, window: &mut Window, cx: &mut Context<Self>) {
         self.picker.update(cx, |picker, cx| {
-            picker.set_query(query, window, cx);
+            picker.set_query(&query, window, cx);
         });
     }
 }

crates/debugger_ui/src/new_process_modal.rs 🔗

@@ -848,7 +848,7 @@ impl ConfigureMode {
     fn load(&mut self, cwd: PathBuf, window: &mut Window, cx: &mut App) {
         self.cwd.update(cx, |input_field, cx| {
             if input_field.is_empty(cx) {
-                input_field.set_text(cwd.to_string_lossy(), window, cx);
+                input_field.set_text(&cwd.to_string_lossy(), window, cx);
             }
         });
     }

crates/debugger_ui/src/tests/debugger_panel.rs 🔗

@@ -1180,7 +1180,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
     });
 
     editor.update_in(cx, |editor, window, cx| {
-        editor.move_down(&actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
     });
 
@@ -1218,7 +1218,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
     });
 
     editor.update_in(cx, |editor, window, cx| {
-        editor.move_up(&actions::MoveUp, window, cx);
+        editor.move_up(&zed_actions::editor::MoveUp, window, cx);
         editor.insert("new text\n", window, cx);
     });
 
@@ -1312,18 +1312,18 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
     });
 
     first_editor.update_in(cx, |editor, window, cx| {
-        editor.move_down(&actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
-        editor.move_down(&actions::MoveDown, window, cx);
-        editor.move_down(&actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
     });
 
     second_editor.update_in(cx, |editor, window, cx| {
         editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
-        editor.move_down(&actions::MoveDown, window, cx);
-        editor.move_down(&actions::MoveDown, window, cx);
-        editor.move_down(&actions::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
+        editor.move_down(&zed_actions::editor::MoveDown, window, cx);
         editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
     });
 

crates/editor/Cargo.toml 🔗

@@ -90,6 +90,7 @@ unicode-segmentation.workspace = true
 unicode-script.workspace = true
 unindent = { workspace = true, optional = true }
 ui.workspace = true
+ui_input.workspace = true
 url.workspace = true
 util.workspace = true
 uuid.workspace = true

crates/editor/src/actions.rs 🔗

@@ -602,8 +602,6 @@ actions!(
         LineDown,
         /// Moves cursor up one line.
         LineUp,
-        /// Moves cursor down.
-        MoveDown,
         /// Moves cursor left.
         MoveLeft,
         /// Moves the current line down.
@@ -642,8 +640,6 @@ actions!(
         MoveToStartOfLargerSyntaxNode,
         /// Moves cursor to the end of the next larger syntax node.
         MoveToEndOfLargerSyntaxNode,
-        /// Moves cursor up.
-        MoveUp,
         /// Inserts a new line and moves cursor to it.
         Newline,
         /// Inserts a new line above the current line.

crates/editor/src/editor.rs 🔗

@@ -108,7 +108,7 @@ use gpui::{
     Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
     AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
     DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
-    Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
+    Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
     MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
     Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled, Subscription, Task,
     TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
@@ -203,6 +203,7 @@ use ui::{
     Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
     IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
 };
+use ui_input::ErasedEditor;
 use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
 use workspace::{
     CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
@@ -212,6 +213,7 @@ use workspace::{
     notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
     searchable::{CollapseDirection, SearchEvent},
 };
+use zed_actions::editor::{MoveDown, MoveUp};
 
 use crate::{
     code_context_menus::CompletionsMenuSource,
@@ -372,6 +374,12 @@ pub fn init(cx: &mut App) {
             .detach();
         }
     });
+    _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
+        Arc::new(ErasedEditorImpl(
+            cx.new(|cx| Editor::single_line(window, cx)),
+        )) as Arc<dyn ErasedEditor>
+    });
+    _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
 }
 
 pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
@@ -27717,6 +27725,90 @@ impl<T: InvalidationRegion> InvalidationStack<T> {
     }
 }
 
+#[derive(Clone)]
+struct ErasedEditorImpl(Entity<Editor>);
+
+impl ui_input::ErasedEditor for ErasedEditorImpl {
+    fn text(&self, cx: &App) -> String {
+        self.0.read(cx).text(cx)
+    }
+
+    fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
+        self.0.update(cx, |this, cx| {
+            this.set_text(text, window, cx);
+        })
+    }
+
+    fn clear(&self, window: &mut Window, cx: &mut App) {
+        self.0.update(cx, |this, cx| this.clear(window, cx));
+    }
+
+    fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
+        self.0.update(cx, |this, cx| {
+            this.set_placeholder_text(text, window, cx);
+        });
+    }
+
+    fn focus_handle(&self, cx: &App) -> FocusHandle {
+        self.0.read(cx).focus_handle(cx)
+    }
+
+    fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
+        let settings = ThemeSettings::get_global(cx);
+        let theme_color = cx.theme().colors();
+
+        let text_style = TextStyle {
+            font_family: settings.ui_font.family.clone(),
+            font_features: settings.ui_font.features.clone(),
+            font_size: rems(0.875).into(),
+            font_weight: settings.buffer_font.weight,
+            font_style: FontStyle::Normal,
+            line_height: relative(1.2),
+            color: theme_color.text,
+            ..Default::default()
+        };
+        let editor_style = EditorStyle {
+            background: theme_color.ghost_element_background,
+            local_player: cx.theme().players().local(),
+            syntax: cx.theme().syntax().clone(),
+            text: text_style,
+            ..Default::default()
+        };
+        EditorElement::new(&self.0, editor_style).into_any()
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        &self.0
+    }
+
+    fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
+        self.0.update(cx, |editor, cx| {
+            let editor_offset = editor.buffer().read(cx).len(cx);
+            editor.change_selections(
+                SelectionEffects::scroll(Autoscroll::Next),
+                window,
+                cx,
+                |s| s.select_ranges(Some(editor_offset..editor_offset)),
+            );
+        });
+    }
+
+    fn subscribe(
+        &self,
+        mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> Subscription {
+        window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
+            let event = match event {
+                EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
+                EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
+                _ => return,
+            };
+            (callback)(event, window, cx);
+        })
+    }
+}
 impl<T> Default for InvalidationStack<T> {
     fn default() -> Self {
         Self(Default::default())

crates/file_finder/Cargo.toml 🔗

@@ -21,10 +21,9 @@ futures.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
 menu.workspace = true
+open_path_prompt.workspace = true
 picker.workspace = true
 project.workspace = true
-schemars.workspace = true
-search.workspace = true
 settings.workspace = true
 serde.workspace = true
 text.workspace = true
@@ -32,6 +31,7 @@ theme.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true
+zed_actions.workspace = true
 
 [dev-dependencies]
 ctor.workspace = true

crates/file_finder/src/file_finder.rs 🔗

@@ -1,17 +1,11 @@
 #[cfg(test)]
 mod file_finder_tests;
-#[cfg(test)]
-mod open_path_prompt_tests;
-
-pub mod file_finder_settings;
-mod open_path_prompt;
 
 use futures::future::join_all;
 pub use open_path_prompt::OpenPathDelegate;
 
 use collections::HashMap;
 use editor::Editor;
-use file_finder_settings::{FileFinderSettings, FileFinderWidth};
 use file_icons::FileIcons;
 use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
 use gpui::{
@@ -19,12 +13,14 @@ use gpui::{
     KeyContext, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task, WeakEntity,
     Window, actions, rems,
 };
-use open_path_prompt::OpenPathPrompt;
+use open_path_prompt::{
+    OpenPathPrompt,
+    file_finder_settings::{FileFinderSettings, FileFinderWidth},
+};
 use picker::{Picker, PickerDelegate};
 use project::{
     PathMatchCandidateSet, Project, ProjectPath, WorktreeId, worktree_store::WorktreeStore,
 };
-use search::ToggleIncludeIgnored;
 use settings::Settings;
 use std::{
     borrow::Cow,
@@ -51,6 +47,7 @@ use workspace::{
     ModalView, OpenOptions, OpenVisible, SplitDirection, Workspace, item::PreviewTabsSettings,
     notifications::NotifyResultExt, pane,
 };
+use zed_actions::search::ToggleIncludeIgnored;
 
 actions!(
     file_finder,

crates/git_ui/Cargo.toml 🔗

@@ -57,6 +57,7 @@ theme.workspace = true
 time.workspace = true
 time_format.workspace = true
 ui.workspace = true
+ui_input.workspace = true
 util.workspace = true
 watch.workspace = true
 workspace.workspace = true

crates/git_ui/src/branch_picker.rs 🔗

@@ -20,6 +20,7 @@ use ui::{
     Divider, HighlightedLabel, KeyBinding, ListHeader, ListItem, ListItemSpacing, Tooltip,
     prelude::*,
 };
+use ui_input::ErasedEditor;
 use util::ResultExt;
 use workspace::notifications::DetachAndPromptErr;
 use workspace::{ModalView, Workspace};
@@ -611,11 +612,12 @@ impl PickerDelegate for BranchListDelegate {
 
     fn render_editor(
         &self,
-        editor: &Entity<Editor>,
+        editor: &Arc<dyn ErasedEditor>,
         _window: &mut Window,
         _cx: &mut Context<Picker<Self>>,
     ) -> Div {
         let focus_handle = self.focus_handle.clone();
+        let editor = editor.as_any().downcast_ref::<Entity<Editor>>().unwrap();
 
         v_flex()
             .when(
@@ -1325,6 +1327,7 @@ mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             theme::init(theme::LoadThemes::JustBase, cx);
+            editor::init(cx);
         });
     }
 

crates/git_ui/src/git_panel.rs 🔗

@@ -4271,10 +4271,10 @@ impl GitPanel {
                     .child(
                         div()
                             .pr_2p5()
-                            .on_action(|&editor::actions::MoveUp, _, cx| {
+                            .on_action(|&zed_actions::editor::MoveUp, _, cx| {
                                 cx.stop_propagation();
                             })
-                            .on_action(|&editor::actions::MoveDown, _, cx| {
+                            .on_action(|&zed_actions::editor::MoveDown, _, cx| {
                                 cx.stop_propagation();
                             })
                             .child(EditorElement::new(&self.commit_editor, panel_editor_style)),

crates/inspector_ui/Cargo.toml 🔗

@@ -18,11 +18,11 @@ editor.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
 language.workspace = true
+platform_title_bar.workspace = true
 project.workspace = true
 serde_json.workspace = true
 serde_json_lenient.workspace = true
 theme.workspace = true
-title_bar.workspace = true
 ui.workspace = true
 util.workspace = true
 util_macros.workspace = true

crates/inspector_ui/src/inspector.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::{Context as _, anyhow};
 use gpui::{App, DivInspectorState, Inspector, InspectorElementId, IntoElement, Window};
+use platform_title_bar::PlatformTitleBar;
 use std::{cell::OnceCell, path::Path, sync::Arc};
-use title_bar::platform_title_bar::PlatformTitleBar;
 use ui::{Label, Tooltip, prelude::*};
 use util::{ResultExt as _, command::new_smol_command};
 use workspace::AppState;

crates/keymap_editor/src/keymap_editor.rs 🔗

@@ -2301,12 +2301,15 @@ impl KeybindingEditorModal {
                 .context()
                 .and_then(KeybindContextString::local)
             {
-                input.editor().update(cx, |editor, cx| {
-                    editor.set_text(context.clone(), window, cx);
-                });
+                input.set_text(&context, window, cx);
             }
 
-            let editor_entity = input.editor().clone();
+            let editor_entity = input.editor();
+            let editor_entity = editor_entity
+                .as_any()
+                .downcast_ref::<Entity<Editor>>()
+                .unwrap()
+                .clone();
             let workspace = workspace.clone();
             cx.spawn(async move |_input_handle, cx| {
                 let contexts = cx
@@ -2350,7 +2353,12 @@ impl KeybindingEditorModal {
                     .label("Action")
                     .label_size(LabelSize::Default);
 
-                input.editor().update(cx, |editor, _cx| {
+                let editor_entity = input.editor();
+                let editor_entity = editor_entity
+                    .as_any()
+                    .downcast_ref::<Entity<Editor>>()
+                    .unwrap();
+                editor_entity.update(cx, |editor, _cx| {
                     editor.set_completion_provider(Some(std::rc::Rc::new(
                         ActionCompletionProvider::new(actions, humanized_names),
                     )));
@@ -2417,7 +2425,7 @@ impl KeybindingEditorModal {
             return;
         };
 
-        let action_name_str = action_editor.read(cx).editor.read(cx).text(cx);
+        let action_name_str = action_editor.read(cx).text(cx);
         let current_action = self.action_name_to_static.get(&action_name_str).copied();
 
         if current_action == self.selected_action_name {
@@ -2498,7 +2506,7 @@ impl KeybindingEditorModal {
 
     fn get_selected_action_name(&self, cx: &App) -> anyhow::Result<&'static str> {
         if let Some(selector) = self.action_editor.as_ref() {
-            let action_name_str = selector.read(cx).editor.read(cx).text(cx);
+            let action_name_str = selector.read(cx).text(cx);
 
             if action_name_str.is_empty() {
                 anyhow::bail!("Action name is required");
@@ -2544,7 +2552,7 @@ impl KeybindingEditorModal {
     fn validate_context(&self, cx: &App) -> anyhow::Result<Option<String>> {
         let new_context = self
             .context_editor
-            .read_with(cx, |input, cx| input.editor().read(cx).text(cx));
+            .read_with(cx, |input, cx| input.text(cx));
         let Some(context) = new_context.is_empty().not().then_some(new_context) else {
             return Ok(None);
         };
@@ -2717,10 +2725,18 @@ impl KeybindingEditorModal {
         self.action_editor.as_ref().is_some_and(|action_editor| {
             let focus_handle = action_editor.read(cx).focus_handle(cx);
             let editor_entity = action_editor.read(cx).editor();
+            let editor_entity = editor_entity
+                .as_any()
+                .downcast_ref::<Entity<Editor>>()
+                .unwrap();
             is_editor_showing_completions(&focus_handle, editor_entity)
         }) || {
             let focus_handle = self.context_editor.read(cx).focus_handle(cx);
             let editor_entity = self.context_editor.read(cx).editor();
+            let editor_entity = editor_entity
+                .as_any()
+                .downcast_ref::<Entity<Editor>>()
+                .unwrap();
             is_editor_showing_completions(&focus_handle, editor_entity)
         } || self
             .action_arguments_editor

crates/language_models/src/provider/google.rs 🔗

@@ -1,6 +1,5 @@
 use anyhow::{Context as _, Result};
 use collections::BTreeMap;
-use credentials_provider::CredentialsProvider;
 use futures::{FutureExt, Stream, StreamExt, future::BoxFuture};
 use google_ai::{
     FunctionDeclaration, GenerateContentResponse, GoogleModelMode, Part, SystemInstruction,
@@ -32,7 +31,7 @@ use ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};
 use ui_input::InputField;
 use util::ResultExt;
 
-use language_model::{ApiKey, ApiKeyState};
+use language_model::ApiKeyState;
 
 const PROVIDER_ID: LanguageModelProviderId = language_model::GOOGLE_PROVIDER_ID;
 const PROVIDER_NAME: LanguageModelProviderName = language_model::GOOGLE_PROVIDER_NAME;
@@ -117,22 +116,6 @@ impl GoogleLanguageModelProvider {
         })
     }
 
-    pub fn api_key_for_gemini_cli(cx: &mut App) -> Task<Result<String>> {
-        if let Some(key) = API_KEY_ENV_VAR.value.clone() {
-            return Task::ready(Ok(key));
-        }
-        let credentials_provider = <dyn CredentialsProvider>::global(cx);
-        let api_url = Self::api_url(cx).to_string();
-        cx.spawn(async move |cx| {
-            Ok(
-                ApiKey::load_from_system_keychain(&api_url, credentials_provider.as_ref(), cx)
-                    .await?
-                    .key()
-                    .to_string(),
-            )
-        })
-    }
-
     fn settings(cx: &App) -> &GoogleSettings {
         &crate::AllLanguageModelSettings::get_global(cx).google
     }

crates/language_models/src/provider/ollama.rs 🔗

@@ -613,7 +613,7 @@ impl ConfigurationView {
 
         let api_url_editor = cx.new(|cx| {
             let input = InputField::new(window, cx, OLLAMA_API_URL).label("API URL");
-            input.set_text(OllamaLanguageModelProvider::api_url(cx), window, cx);
+            input.set_text(&OllamaLanguageModelProvider::api_url(cx), window, cx);
             input
         });
 

crates/language_selector/Cargo.toml 🔗

@@ -15,11 +15,11 @@ doctest = false
 [dependencies]
 anyhow.workspace = true
 editor.workspace = true
-file_finder.workspace = true
 file_icons.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
 language.workspace = true
+open_path_prompt.workspace = true
 picker.workspace = true
 project.workspace = true
 settings.workspace = true

crates/language_selector/src/language_selector.rs 🔗

@@ -3,14 +3,13 @@ mod active_buffer_language;
 pub use active_buffer_language::ActiveBufferLanguage;
 use anyhow::Context as _;
 use editor::Editor;
-use file_finder::file_finder_settings::FileFinderSettings;
-use file_icons::FileIcons;
 use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
 use gpui::{
     App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ParentElement,
     Render, Styled, WeakEntity, Window, actions,
 };
 use language::{Buffer, LanguageMatcher, LanguageName, LanguageRegistry};
+use open_path_prompt::file_finder_settings::FileFinderSettings;
 use picker::{Picker, PickerDelegate};
 use project::Project;
 use settings::Settings;
@@ -176,7 +175,7 @@ impl LanguageSelectorDelegate {
         matcher
             .path_suffixes
             .iter()
-            .find_map(|extension| FileIcons::get_icon(Path::new(extension), cx))
+            .find_map(|extension| file_icons::FileIcons::get_icon(Path::new(extension), cx))
             .map(Icon::from_path)
             .map(|icon| icon.color(Color::Muted))
     }

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -44,7 +44,7 @@ use std::{
     ops::{self, AddAssign, ControlFlow, Range, RangeBounds, Sub, SubAssign},
     rc::Rc,
     str,
-    sync::Arc,
+    sync::{Arc, OnceLock},
     time::Duration,
 };
 use sum_tree::{Bias, Cursor, Dimension, Dimensions, SumTree, TreeMap};
@@ -59,6 +59,12 @@ use ztracing::instrument;
 
 pub use self::path_key::PathKey;
 
+pub static EXCERPT_CONTEXT_LINES: OnceLock<fn(&App) -> u32> = OnceLock::new();
+
+pub fn excerpt_context_lines(cx: &App) -> u32 {
+    EXCERPT_CONTEXT_LINES.get().map(|f| f(cx)).unwrap_or(2)
+}
+
 #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
 pub struct ExcerptId(u32);
 

crates/open_path_prompt/Cargo.toml 🔗

@@ -0,0 +1,33 @@
+[package]
+name = "open_path_prompt"
+version = "0.1.0"
+publish.workspace = true
+edition.workspace = true
+license = "GPL-3.0-or-later"
+
+[dependencies]
+file_icons.workspace = true
+futures.workspace = true
+fuzzy.workspace = true
+gpui.workspace = true
+picker.workspace = true
+project.workspace = true
+schemars.workspace = true
+serde.workspace = true
+settings.workspace = true
+ui.workspace = true
+util.workspace = true
+workspace.workspace = true
+
+[dev-dependencies]
+editor = {workspace = true, features = ["test-support"]}
+gpui = {workspace = true, features = ["test-support"]}
+serde_json.workspace = true
+theme = {workspace = true, features = ["test-support"]}
+workspace = {workspace = true, features = ["test-support"]}
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/open_path_prompt.rs"

crates/file_finder/src/open_path_prompt.rs → crates/open_path_prompt/src/open_path_prompt.rs 🔗

@@ -1,4 +1,9 @@
-use crate::file_finder_settings::FileFinderSettings;
+pub mod file_finder_settings;
+
+#[cfg(test)]
+mod open_path_prompt_tests;
+
+use file_finder_settings::FileFinderSettings;
 use file_icons::FileIcons;
 use futures::channel::oneshot;
 use fuzzy::{CharBag, StringMatch, StringMatchCandidate};
@@ -21,7 +26,7 @@ use util::{
 };
 use workspace::Workspace;
 
-pub(crate) struct OpenPathPrompt;
+pub struct OpenPathPrompt;
 
 pub struct OpenPathDelegate {
     tx: Option<oneshot::Sender<Option<Vec<PathBuf>>>>,
@@ -184,7 +189,7 @@ struct CandidateInfo {
 }
 
 impl OpenPathPrompt {
-    pub(crate) fn register(
+    pub fn register(
         workspace: &mut Workspace,
         _window: Option<&mut Window>,
         _: &mut Context<Workspace>,
@@ -196,7 +201,7 @@ impl OpenPathPrompt {
         }));
     }
 
-    pub(crate) fn register_new_path(
+    pub fn register_new_path(
         workspace: &mut Workspace,
         _window: Option<&mut Window>,
         _: &mut Context<Workspace>,
@@ -220,7 +225,7 @@ impl OpenPathPrompt {
             let delegate = OpenPathDelegate::new(tx, lister.clone(), creating_path, cx);
             let picker = Picker::uniform_list(delegate, window, cx).width(rems(34.));
             let query = lister.default_query(cx);
-            picker.set_query(query, window, cx);
+            picker.set_query(&query, window, cx);
             picker
         });
     }
@@ -930,7 +935,7 @@ fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String)
 mod tests {
     use util::paths::PathStyle;
 
-    use crate::open_path_prompt::get_dir_and_suffix;
+    use super::get_dir_and_suffix;
 
     #[test]
     fn test_get_dir_and_suffix_with_windows_style() {

crates/file_finder/src/open_path_prompt_tests.rs → crates/open_path_prompt/src/open_path_prompt_tests.rs 🔗

@@ -347,7 +347,7 @@ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
     cx.update(|cx| {
         let state = AppState::test(cx);
         theme::init(theme::LoadThemes::JustBase, cx);
-        super::init(cx);
+
         editor::init(cx);
         state
     })
@@ -370,7 +370,7 @@ fn build_open_path_prompt(
                     .width(rems(34.))
                     .modal(false);
                 let query = lister.default_query(cx);
-                picker.set_query(query, window, cx);
+                picker.set_query(&query, window, cx);
                 picker
             })
         }),

crates/picker/Cargo.toml 🔗

@@ -17,14 +17,15 @@ test-support = []
 
 [dependencies]
 anyhow.workspace = true
-editor.workspace = true
 gpui.workspace = true
 menu.workspace = true
 schemars.workspace = true
 serde.workspace = true
 theme.workspace = true
 ui.workspace = true
+ui_input.workspace = true
 workspace.workspace = true
+zed_actions.workspace = true
 
 [dev-dependencies]
 ctor.workspace = true

crates/picker/src/head.rs 🔗

@@ -1,13 +1,13 @@
 use std::sync::Arc;
 
-use editor::{Editor, EditorEvent};
 use gpui::{App, Entity, FocusHandle, Focusable, prelude::*};
 use ui::prelude::*;
+use ui_input::{ErasedEditor, ErasedEditorEvent};
 
 /// The head of a [`Picker`](crate::Picker).
 pub(crate) enum Head {
     /// Picker has an editor that allows the user to filter the list.
-    Editor(Entity<Editor>),
+    Editor(Arc<dyn ErasedEditor>),
 
     /// Picker has no head, it's just a list of items.
     Empty(Entity<EmptyHead>),
@@ -16,17 +16,28 @@ pub(crate) enum Head {
 impl Head {
     pub fn editor<V: 'static>(
         placeholder_text: Arc<str>,
-        edit_handler: impl FnMut(&mut V, &Entity<Editor>, &EditorEvent, &mut Window, &mut Context<V>)
-        + 'static,
+        mut edit_handler: impl FnMut(&mut V, &ErasedEditorEvent, &mut Window, &mut Context<V>) + 'static,
         window: &mut Window,
         cx: &mut Context<V>,
     ) -> Self {
-        let editor = cx.new(|cx| {
-            let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text(placeholder_text.as_ref(), window, cx);
-            editor
-        });
-        cx.subscribe_in(&editor, window, edit_handler).detach();
+        let editor = (ui_input::ERASED_EDITOR_FACTORY.get().unwrap())(window, cx);
+
+        editor.set_placeholder_text(placeholder_text.as_ref(), window, cx);
+        let this = cx.weak_entity();
+        editor
+            .subscribe(
+                Box::new(move |event, window, cx| {
+                    this.update(cx, |this, cx| (edit_handler)(this, &event, window, cx))
+                        .ok();
+                }),
+                window,
+                cx,
+            )
+            .detach();
+        // cx.subscribe_in(&editor, window, |v, _, event, window, cx| {
+        //     edit_handler(v, event, window, cx);
+        // })
+        // .detach();
         Self::Editor(editor)
     }
 

crates/picker/src/picker.rs 🔗

@@ -3,16 +3,12 @@ pub mod highlighted_match_with_paths;
 pub mod popover_menu;
 
 use anyhow::Result;
-use editor::{
-    Editor, SelectionEffects,
-    actions::{MoveDown, MoveUp},
-    scroll::Autoscroll,
-};
+
 use gpui::{
-    Action, AnyElement, App, Bounds, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
-    FocusHandle, Focusable, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent,
-    Pixels, Render, ScrollStrategy, Task, UniformListScrollHandle, Window, actions, canvas, div,
-    list, prelude::*, uniform_list,
+    Action, AnyElement, App, Bounds, ClickEvent, Context, DismissEvent, EventEmitter, FocusHandle,
+    Focusable, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent, Pixels, Render,
+    ScrollStrategy, Task, UniformListScrollHandle, Window, actions, canvas, div, list, prelude::*,
+    uniform_list,
 };
 use head::Head;
 use schemars::JsonSchema;
@@ -25,7 +21,9 @@ use ui::{
     Color, Divider, DocumentationAside, DocumentationSide, Label, ListItem, ListItemSpacing,
     ScrollAxes, Scrollbars, WithScrollbar, prelude::*, utils::WithRemSize, v_flex,
 };
+use ui_input::{ErasedEditor, ErasedEditorEvent};
 use workspace::{ModalView, item::Settings};
+use zed_actions::editor::{MoveDown, MoveUp};
 
 enum ElementContainer {
     List(ListState),
@@ -195,9 +193,9 @@ pub trait PickerDelegate: Sized + 'static {
 
     fn render_editor(
         &self,
-        editor: &Entity<Editor>,
-        _window: &mut Window,
-        _cx: &mut Context<Picker<Self>>,
+        editor: &Arc<dyn ErasedEditor>,
+        window: &mut Window,
+        cx: &mut Context<Picker<Self>>,
     ) -> Div {
         v_flex()
             .when(
@@ -210,7 +208,7 @@ pub trait PickerDelegate: Sized + 'static {
                     .flex_none()
                     .h_9()
                     .px_2p5()
-                    .child(editor.clone()),
+                    .child(editor.render(window, cx)),
             )
             .when(
                 self.editor_position() == PickerEditorPosition::Start,
@@ -480,7 +478,7 @@ impl<D: PickerDelegate> Picker<D> {
             .delegate
             .select_history(Direction::Down, &query, window, cx)
         {
-            self.set_query(query, window, cx);
+            self.set_query(&query, window, cx);
             return;
         }
         let count = self.delegate.match_count();
@@ -507,7 +505,7 @@ impl<D: PickerDelegate> Picker<D> {
             .delegate
             .select_history(Direction::Up, &query, window, cx)
         {
-            self.set_query(query, window, cx);
+            self.set_query(&query, window, cx);
             return;
         }
         let count = self.delegate.match_count();
@@ -606,7 +604,7 @@ impl<D: PickerDelegate> Picker<D> {
         cx: &mut Context<Self>,
     ) {
         if let Some(new_query) = self.delegate.confirm_completion(self.query(cx), window, cx) {
-            self.set_query(new_query, window, cx);
+            self.set_query(&new_query, window, cx);
         } else {
             cx.propagate()
         }
@@ -627,7 +625,7 @@ impl<D: PickerDelegate> Picker<D> {
 
     fn do_confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Self>) {
         if let Some(update_query) = self.delegate.confirm_update_query(window, cx) {
-            self.set_query(update_query, window, cx);
+            self.set_query(&update_query, window, cx);
             self.set_selected_index(0, Some(Direction::Down), false, window, cx);
         } else {
             self.delegate.confirm(secondary, window, cx)
@@ -636,8 +634,7 @@ impl<D: PickerDelegate> Picker<D> {
 
     fn on_input_editor_event(
         &mut self,
-        _: &Entity<Editor>,
-        event: &editor::EditorEvent,
+        event: &ErasedEditorEvent,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -645,16 +642,15 @@ impl<D: PickerDelegate> Picker<D> {
             panic!("unexpected call");
         };
         match event {
-            editor::EditorEvent::BufferEdited => {
-                let query = editor.read(cx).text(cx);
+            ErasedEditorEvent::BufferEdited => {
+                let query = editor.text(cx);
                 self.update_matches(query, window, cx);
             }
-            editor::EditorEvent::Blurred => {
+            ErasedEditorEvent::Blurred => {
                 if self.is_modal && window.is_window_active() {
                     self.cancel(&menu::Cancel, window, cx);
                 }
             }
-            _ => {}
         }
     }
 
@@ -667,14 +663,13 @@ impl<D: PickerDelegate> Picker<D> {
         }
     }
 
-    pub fn refresh_placeholder(&mut self, window: &mut Window, cx: &mut App) {
+    pub fn refresh_placeholder(&mut self, window: &mut Window, cx: &mut Context<Self>) {
         match &self.head {
             Head::Editor(editor) => {
                 let placeholder = self.delegate.placeholder_text(window, cx);
-                editor.update(cx, |editor, cx| {
-                    editor.set_placeholder_text(placeholder.as_ref(), window, cx);
-                    cx.notify();
-                });
+
+                editor.set_placeholder_text(placeholder.as_ref(), window, cx);
+                cx.notify();
             }
             Head::Empty(_) => {}
         }
@@ -730,23 +725,15 @@ impl<D: PickerDelegate> Picker<D> {
 
     pub fn query(&self, cx: &App) -> String {
         match &self.head {
-            Head::Editor(editor) => editor.read(cx).text(cx),
+            Head::Editor(editor) => editor.text(cx),
             Head::Empty(_) => "".to_string(),
         }
     }
 
-    pub fn set_query(&self, query: impl Into<Arc<str>>, window: &mut Window, cx: &mut App) {
+    pub fn set_query(&self, query: &str, window: &mut Window, cx: &mut App) {
         if let Head::Editor(editor) = &self.head {
-            editor.update(cx, |editor, cx| {
-                editor.set_text(query, window, cx);
-                let editor_offset = editor.buffer().read(cx).len(cx);
-                editor.change_selections(
-                    SelectionEffects::scroll(Autoscroll::Next),
-                    window,
-                    cx,
-                    |s| s.select_ranges(Some(editor_offset..editor_offset)),
-                );
-            });
+            editor.set_text(query, window, cx);
+            editor.move_selection_to_end(window, cx);
         }
     }
 

crates/platform_title_bar/Cargo.toml 🔗

@@ -0,0 +1,25 @@
+[package]
+name = "platform_title_bar"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/platform_title_bar.rs"
+doctest = false
+
+[dependencies]
+gpui.workspace = true
+settings.workspace = true
+smallvec.workspace = true
+theme.workspace = true
+ui.workspace = true
+workspace.workspace = true
+zed_actions.workspace = true
+
+[target.'cfg(windows)'.dependencies]
+windows.workspace = true

crates/title_bar/src/platform_title_bar.rs → crates/platform_title_bar/src/platform_title_bar.rs 🔗

@@ -1,3 +1,6 @@
+mod platforms;
+mod system_window_tabs;
+
 use gpui::{
     AnyElement, Context, Decorations, Entity, Hsla, InteractiveElement, IntoElement, MouseButton,
     ParentElement, Pixels, StatefulInteractiveElement, Styled, Window, WindowControlArea, div, px,
@@ -11,6 +14,10 @@ use crate::{
     system_window_tabs::SystemWindowTabs,
 };
 
+pub use system_window_tabs::{
+    DraggedWindowTab, MergeAllWindows, MoveTabToNewWindow, ShowNextWindowTab, ShowPreviousWindowTab,
+};
+
 pub struct PlatformTitleBar {
     id: ElementId,
     platform_style: PlatformStyle,
@@ -62,6 +69,10 @@ impl PlatformTitleBar {
     {
         self.children = children.into_iter().collect();
     }
+
+    pub fn init(cx: &mut App) {
+        SystemWindowTabs::init(cx);
+    }
 }
 
 impl Render for PlatformTitleBar {

crates/recent_projects/Cargo.toml 🔗

@@ -24,7 +24,6 @@ db.workspace = true
 dev_container.workspace = true
 editor.workspace = true
 extension_host.workspace = true
-file_finder.workspace = true
 futures.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
@@ -33,6 +32,7 @@ log.workspace = true
 markdown.workspace = true
 menu.workspace = true
 node_runtime.workspace = true
+open_path_prompt.workspace = true
 ordered-float.workspace = true
 paths.workspace = true
 picker.workspace = true

crates/recent_projects/src/remote_servers.rs 🔗

@@ -7,7 +7,7 @@ use crate::{
 };
 use dev_container::start_dev_container;
 use editor::Editor;
-use file_finder::OpenPathDelegate;
+
 use futures::{FutureExt, channel::oneshot, future::Shared};
 use gpui::{
     AnyElement, App, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, EventEmitter,
@@ -16,6 +16,7 @@ use gpui::{
 };
 use language::Point;
 use log::{debug, info};
+use open_path_prompt::OpenPathDelegate;
 use paths::{global_ssh_config_file, user_ssh_config_file};
 use picker::Picker;
 use project::{Fs, Project};
@@ -222,13 +223,13 @@ impl ProjectPicker {
     ) -> Entity<Self> {
         let (tx, rx) = oneshot::channel();
         let lister = project::DirectoryLister::Project(project.clone());
-        let delegate = file_finder::OpenPathDelegate::new(tx, lister, false, cx);
+        let delegate = open_path_prompt::OpenPathDelegate::new(tx, lister, false, cx);
 
         let picker = cx.new(|cx| {
             let picker = Picker::uniform_list(delegate, window, cx)
                 .width(rems(34.))
                 .modal(false);
-            picker.set_query(home_dir.to_string(), window, cx);
+            picker.set_query(&home_dir.to_string(), window, cx);
             picker
         });
 

crates/rules_library/Cargo.toml 🔗

@@ -11,8 +11,6 @@ workspace = true
 [lib]
 path = "src/rules_library.rs"
 
-[features]
-test-support = ["title_bar/test-support"]
 
 [dependencies]
 anyhow.workspace = true
@@ -24,14 +22,15 @@ language_model.workspace = true
 log.workspace = true
 menu.workspace = true
 picker.workspace = true
+platform_title_bar.workspace = true
 prompt_store.workspace = true
 release_channel.workspace = true
 rope.workspace = true
 serde.workspace = true
 settings.workspace = true
 theme.workspace = true
-title_bar.workspace = true
 ui.workspace = true
+ui_input.workspace = true
 util.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true

crates/rules_library/src/rules_library.rs 🔗

@@ -12,6 +12,7 @@ use language_model::{
     ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
 };
 use picker::{Picker, PickerDelegate};
+use platform_title_bar::PlatformTitleBar;
 use release_channel::ReleaseChannel;
 use rope::Rope;
 use settings::Settings;
@@ -20,8 +21,8 @@ use std::sync::Arc;
 use std::sync::atomic::AtomicBool;
 use std::time::Duration;
 use theme::ThemeSettings;
-use title_bar::platform_title_bar::PlatformTitleBar;
 use ui::{Divider, ListItem, ListItemSpacing, ListSubHeader, Tooltip, prelude::*};
+use ui_input::ErasedEditor;
 use util::{ResultExt, TryFutureExt};
 use workspace::{Workspace, WorkspaceSettings, client_side_decorations};
 use zed_actions::assistant::InlineAssist;
@@ -445,10 +446,12 @@ impl PickerDelegate for RulePickerDelegate {
 
     fn render_editor(
         &self,
-        editor: &Entity<Editor>,
+        editor: &Arc<dyn ErasedEditor>,
         _: &mut Window,
         cx: &mut Context<Picker<Self>>,
     ) -> Div {
+        let editor = editor.as_any().downcast_ref::<Entity<Editor>>().unwrap();
+
         h_flex()
             .py_1()
             .px_1p5()
@@ -983,7 +986,7 @@ impl RulesLibrary {
 
     fn move_down_from_title(
         &mut self,
-        _: &editor::actions::MoveDown,
+        _: &zed_actions::editor::MoveDown,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -996,7 +999,7 @@ impl RulesLibrary {
 
     fn move_up_from_body(
         &mut self,
-        _: &editor::actions::MoveUp,
+        _: &zed_actions::editor::MoveUp,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {

crates/search/Cargo.toml 🔗

@@ -58,4 +58,3 @@ workspace = { workspace = true, features = ["test-support"] }
 
 [package.metadata.cargo-machete]
 ignored = ["tracing"]
-

crates/search/src/search.rs 🔗

@@ -8,6 +8,7 @@ use ui::{ButtonStyle, IconButton, IconButtonShape};
 use ui::{Tooltip, prelude::*};
 use workspace::notifications::NotificationId;
 use workspace::{Toast, Workspace};
+pub use zed_actions::search::ToggleIncludeIgnored;
 
 pub use search_status_button::SEARCH_ICON;
 
@@ -33,8 +34,6 @@ actions!(
         ToggleWholeWord,
         /// Toggles case-sensitive search.
         ToggleCaseSensitive,
-        /// Toggles searching in ignored files.
-        ToggleIncludeIgnored,
         /// Toggles regular expression mode.
         ToggleRegex,
         /// Toggles the replace interface.

crates/settings_ui/Cargo.toml 🔗

@@ -18,6 +18,7 @@ test-support = []
 [dependencies]
 anyhow.workspace = true
 bm25 = "2.3.2"
+component.workspace = true
 copilot_ui.workspace = true
 edit_prediction.workspace = true
 editor.workspace = true
@@ -34,6 +35,7 @@ log.workspace = true
 menu.workspace = true
 paths.workspace = true
 picker.workspace = true
+platform_title_bar.workspace = true
 project.workspace = true
 release_channel.workspace = true
 schemars.workspace = true
@@ -43,8 +45,6 @@ settings.workspace = true
 strum.workspace = true
 telemetry.workspace = true
 theme.workspace = true
-title_bar.workspace = true
-ui_input.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/settings_ui/src/components.rs 🔗

@@ -2,6 +2,7 @@ mod dropdown;
 mod font_picker;
 mod icon_theme_picker;
 mod input_field;
+mod number_field;
 mod section_items;
 mod theme_picker;
 
@@ -9,5 +10,6 @@ pub use dropdown::*;
 pub use font_picker::font_picker;
 pub use icon_theme_picker::icon_theme_picker;
 pub use input_field::*;
+pub use number_field::*;
 pub use section_items::*;
 pub use theme_picker::theme_picker;

crates/ui_input/src/number_field.rs → crates/settings_ui/src/components/number_field.rs 🔗

@@ -5,7 +5,7 @@ use std::{
     str::FromStr,
 };
 
-use editor::{Editor, actions::MoveDown, actions::MoveUp};
+use editor::Editor;
 use gpui::{
     ClickEvent, Entity, FocusHandle, Focusable, FontWeight, Modifiers, TextAlign,
     TextStyleRefinement, WeakEntity,
@@ -16,6 +16,7 @@ use settings::{
     MinimumContrast,
 };
 use ui::prelude::*;
+use zed_actions::editor::{MoveDown, MoveUp};
 
 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum NumberFieldMode {
@@ -316,26 +317,6 @@ impl<T: NumberFieldType> NumberField<T> {
         }
     }
 
-    pub fn format(mut self, format: impl FnOnce(&T) -> String + 'static) -> Self {
-        self.format = Box::new(format);
-        self
-    }
-
-    pub fn small_step(mut self, step: T) -> Self {
-        self.small_step = step;
-        self
-    }
-
-    pub fn normal_step(mut self, step: T) -> Self {
-        self.step = step;
-        self
-    }
-
-    pub fn large_step(mut self, step: T) -> Self {
-        self.large_step = step;
-        self
-    }
-
     pub fn min(mut self, min: T) -> Self {
         self.min_value = min;
         self
@@ -351,14 +332,6 @@ impl<T: NumberFieldType> NumberField<T> {
         self
     }
 
-    pub fn on_reset(
-        mut self,
-        on_reset: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
-    ) -> Self {
-        self.on_reset = Some(Box::new(on_reset));
-        self
-    }
-
     pub fn tab_index(mut self, tab_index: isize) -> Self {
         self.tab_index = Some(tab_index);
         self

crates/settings_ui/src/settings_ui.rs 🔗

@@ -14,6 +14,7 @@ use gpui::{
 };
 
 use language::Buffer;
+use platform_title_bar::PlatformTitleBar;
 use project::{Project, ProjectPath, Worktree, WorktreeId};
 use release_channel::ReleaseChannel;
 use schemars::JsonSchema;
@@ -32,20 +33,19 @@ use std::{
     time::Duration,
 };
 use theme::ThemeSettings;
-use title_bar::platform_title_bar::PlatformTitleBar;
 use ui::{
     Banner, ContextMenu, Divider, DropdownMenu, DropdownStyle, IconButtonShape, KeyBinding,
     KeybindingHint, PopoverMenu, Scrollbars, Switch, Tooltip, TreeViewItem, WithScrollbar,
     prelude::*,
 };
-use ui_input::{NumberField, NumberFieldMode, NumberFieldType};
+
 use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
 use workspace::{AppState, OpenOptions, OpenVisible, Workspace, client_side_decorations};
 use zed_actions::{OpenProjectSettings, OpenSettings, OpenSettingsAt};
 
 use crate::components::{
-    EnumVariantDropdown, SettingsInputField, SettingsSectionHeader, font_picker, icon_theme_picker,
-    theme_picker,
+    EnumVariantDropdown, NumberField, NumberFieldMode, NumberFieldType, SettingsInputField,
+    SettingsSectionHeader, font_picker, icon_theme_picker, theme_picker,
 };
 
 const NAVBAR_CONTAINER_TAB_INDEX: isize = 0;

crates/snippets_ui/Cargo.toml 🔗

@@ -12,11 +12,11 @@ workspace = true
 path = "src/snippets_ui.rs"
 
 [dependencies]
-file_finder.workspace = true
 file_icons.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
 language.workspace = true
+open_path_prompt.workspace = true
 paths.workspace = true
 picker.workspace = true
 settings.workspace = true

crates/snippets_ui/src/snippets_ui.rs 🔗

@@ -1,4 +1,3 @@
-use file_finder::file_finder_settings::FileFinderSettings;
 use file_icons::FileIcons;
 use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
 use gpui::{
@@ -6,6 +5,7 @@ use gpui::{
     WeakEntity, Window, actions,
 };
 use language::{LanguageMatcher, LanguageName, LanguageRegistry};
+use open_path_prompt::file_finder_settings::FileFinderSettings;
 use paths::snippets_dir;
 use picker::{Picker, PickerDelegate};
 use settings::Settings;

crates/title_bar/Cargo.toml 🔗

@@ -31,6 +31,7 @@ test-support = [
 [dependencies]
 anyhow.workspace = true
 auto_update.workspace = true
+platform_title_bar.workspace = true
 call.workspace = true
 channel.workspace = true
 chrono.workspace = true

crates/title_bar/src/title_bar.rs 🔗

@@ -1,19 +1,16 @@
 mod application_menu;
 pub mod collab;
 mod onboarding_banner;
-pub mod platform_title_bar;
-mod platforms;
 mod project_dropdown;
-mod system_window_tabs;
 mod title_bar_settings;
 
 #[cfg(feature = "stories")]
 mod stories;
 
-use crate::{
-    application_menu::{ApplicationMenu, show_menus},
-    platform_title_bar::PlatformTitleBar,
-    system_window_tabs::SystemWindowTabs,
+use crate::application_menu::{ApplicationMenu, show_menus};
+pub use platform_title_bar::{
+    self, DraggedWindowTab, MergeAllWindows, MoveTabToNewWindow, PlatformTitleBar,
+    ShowNextWindowTab, ShowPreviousWindowTab,
 };
 
 #[cfg(not(target_os = "macos"))]
@@ -69,7 +66,7 @@ actions!(
 );
 
 pub fn init(cx: &mut App) {
-    SystemWindowTabs::init(cx);
+    platform_title_bar::PlatformTitleBar::init(cx);
 
     cx.observe_new(|workspace: &mut Workspace, window, cx| {
         let Some(window) = window else {

crates/toolchain_selector/Cargo.toml 🔗

@@ -9,12 +9,12 @@ license = "GPL-3.0-or-later"
 anyhow.workspace = true
 convert_case.workspace = true
 editor.workspace = true
-file_finder.workspace = true
 futures.workspace = true
 fuzzy.workspace = true
 gpui.workspace = true
 language.workspace = true
 menu.workspace = true
+open_path_prompt.workspace = true
 picker.workspace = true
 project.workspace = true
 ui.workspace = true

crates/toolchain_selector/src/toolchain_selector.rs 🔗

@@ -4,7 +4,6 @@ pub use active_toolchain::ActiveToolchain;
 use anyhow::Context as _;
 use convert_case::Casing as _;
 use editor::Editor;
-use file_finder::OpenPathDelegate;
 use futures::channel::oneshot;
 use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
 use gpui::{
@@ -13,6 +12,7 @@ use gpui::{
     actions, pulsating_between,
 };
 use language::{Language, LanguageName, Toolchain, ToolchainScope};
+use open_path_prompt::OpenPathDelegate;
 use picker::{Picker, PickerDelegate};
 use project::{DirectoryLister, Project, ProjectPath, Toolchains, WorktreeId};
 use std::{
@@ -82,7 +82,7 @@ enum PathInputState {
 
 enum AddState {
     Path {
-        picker: Entity<Picker<file_finder::OpenPathDelegate>>,
+        picker: Entity<Picker<open_path_prompt::OpenPathDelegate>>,
         error: Option<Arc<str>>,
         input_state: PathInputState,
         _subscription: Subscription,
@@ -226,11 +226,7 @@ impl AddToolchainState {
                                 Self::create_path_browser_delegate(this.project.clone(), cx);
                             picker.update(cx, |picker, cx| {
                                 *picker = Picker::uniform_list(delegate, window, cx);
-                                picker.set_query(
-                                    Arc::from(path.to_string_lossy().as_ref()),
-                                    window,
-                                    cx,
-                                );
+                                picker.set_query(path.to_string_lossy().as_ref(), window, cx);
                             });
                             *input_state = Self::wait_for_path(rx, window, cx);
                             this.focus_handle(cx).focus(window, cx);

crates/ui_input/Cargo.toml 🔗

@@ -13,11 +13,7 @@ path = "src/ui_input.rs"
 
 [dependencies]
 component.workspace = true
-editor.workspace = true
 gpui.workspace = true
-menu.workspace = true
-settings.workspace = true
-theme.workspace = true
 ui.workspace = true
 
 [features]

crates/ui_input/src/input_field.rs 🔗

@@ -1,11 +1,12 @@
 use component::{example_group, single_example};
-use editor::{Editor, EditorElement, EditorStyle};
-use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, Length, TextStyle};
-use settings::Settings;
+
+use gpui::{App, FocusHandle, Focusable, Hsla, Length};
 use std::sync::Arc;
-use theme::ThemeSettings;
+
 use ui::prelude::*;
 
+use crate::ErasedEditor;
+
 pub struct InputFieldStyle {
     text_color: Hsla,
     background_color: Hsla,
@@ -25,16 +26,12 @@ pub struct InputField {
     label_size: LabelSize,
     /// The placeholder text for the text field.
     placeholder: SharedString,
-    /// Exposes the underlying [`Entity<Editor>`] to allow for customizing the editor beyond the provided API.
-    ///
-    /// This likely will only be public in the short term, ideally the API will be expanded to cover necessary use cases.
-    pub editor: Entity<Editor>,
+
+    editor: Arc<dyn ErasedEditor>,
     /// An optional icon that is displayed at the start of the text field.
     ///
     /// For example, a magnifying glass icon in a search field.
     start_icon: Option<IconName>,
-    /// Whether the text field is disabled.
-    disabled: bool,
     /// The minimum width of for the input
     min_width: Length,
     /// The tab index for keyboard navigation order.
@@ -50,22 +47,19 @@ impl Focusable for InputField {
 }
 
 impl InputField {
-    pub fn new(window: &mut Window, cx: &mut App, placeholder: impl Into<SharedString>) -> Self {
-        let placeholder_text = placeholder.into();
-
-        let editor = cx.new(|cx| {
-            let mut input = Editor::single_line(window, cx);
-            input.set_placeholder_text(&placeholder_text, window, cx);
-            input
-        });
+    pub fn new(window: &mut Window, cx: &mut App, placeholder_text: &str) -> Self {
+        let editor_factory = crate::ERASED_EDITOR_FACTORY
+            .get()
+            .expect("ErasedEditorFactory to be initialized");
+        let editor = (editor_factory)(window, cx);
+        editor.set_placeholder_text(placeholder_text, window, cx);
 
         Self {
             label: None,
             label_size: LabelSize::Small,
-            placeholder: placeholder_text,
+            placeholder: SharedString::new(placeholder_text),
             editor,
             start_icon: None,
-            disabled: false,
             min_width: px(192.).into(),
             tab_index: None,
             tab_stop: true,
@@ -102,77 +96,39 @@ impl InputField {
         self
     }
 
-    pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
-        self.disabled = disabled;
-        self.editor
-            .update(cx, |editor, _| editor.set_read_only(disabled))
-    }
-
     pub fn is_empty(&self, cx: &App) -> bool {
-        self.editor().read(cx).text(cx).trim().is_empty()
+        self.editor().text(cx).trim().is_empty()
     }
 
-    pub fn editor(&self) -> &Entity<Editor> {
+    pub fn editor(&self) -> &Arc<dyn ErasedEditor> {
         &self.editor
     }
 
     pub fn text(&self, cx: &App) -> String {
-        self.editor().read(cx).text(cx)
+        self.editor().text(cx)
     }
 
     pub fn clear(&self, window: &mut Window, cx: &mut App) {
-        self.editor()
-            .update(cx, |editor, cx| editor.clear(window, cx))
+        self.editor().clear(window, cx)
     }
 
-    pub fn set_text(&self, text: impl Into<Arc<str>>, window: &mut Window, cx: &mut App) {
-        self.editor()
-            .update(cx, |editor, cx| editor.set_text(text, window, cx))
+    pub fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
+        self.editor().set_text(text, window, cx)
     }
 }
 
 impl Render for InputField {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let editor = self.editor.clone();
-        let settings = ThemeSettings::get_global(cx);
+
         let theme_color = cx.theme().colors();
 
-        let mut style = InputFieldStyle {
+        let style = InputFieldStyle {
             text_color: theme_color.text,
             background_color: theme_color.editor_background,
             border_color: theme_color.border_variant,
         };
 
-        if self.disabled {
-            style.text_color = theme_color.text_disabled;
-            style.background_color = theme_color.editor_background;
-            style.border_color = theme_color.border_disabled;
-        }
-
-        // if self.error_message.is_some() {
-        //     style.text_color = cx.theme().status().error;
-        //     style.border_color = cx.theme().status().error_border
-        // }
-
-        let text_style = TextStyle {
-            font_family: settings.ui_font.family.clone(),
-            font_features: settings.ui_font.features.clone(),
-            font_size: rems(0.875).into(),
-            font_weight: settings.buffer_font.weight,
-            font_style: FontStyle::Normal,
-            line_height: relative(1.2),
-            color: style.text_color,
-            ..Default::default()
-        };
-
-        let editor_style = EditorStyle {
-            background: theme_color.ghost_element_background,
-            local_player: cx.theme().players().local(),
-            syntax: cx.theme().syntax().clone(),
-            text: text_style,
-            ..Default::default()
-        };
-
         let focus_handle = self.editor.focus_handle(cx);
 
         let configured_handle = if let Some(tab_index) = self.tab_index {
@@ -191,11 +147,7 @@ impl Render for InputField {
                 this.child(
                     Label::new(label)
                         .size(self.label_size)
-                        .color(if self.disabled {
-                            Color::Disabled
-                        } else {
-                            Color::Default
-                        }),
+                        .color(Color::Default),
                 )
             })
             .child(
@@ -220,7 +172,7 @@ impl Render for InputField {
                         this.gap_1()
                             .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
                     })
-                    .child(EditorElement::new(&self.editor, editor_style)),
+                    .child(self.editor.render(window, cx)),
             )
     }
 }

crates/ui_input/src/ui_input.rs 🔗

@@ -3,7 +3,39 @@
 //! It can't be located in the `ui` crate because it depends on `editor`.
 //!
 mod input_field;
-mod number_field;
 
+use std::{
+    any::Any,
+    sync::{Arc, OnceLock},
+};
+
+use gpui::{FocusHandle, Subscription};
 pub use input_field::*;
-pub use number_field::*;
+use ui::{AnyElement, App, Window};
+
+pub trait ErasedEditor: 'static {
+    fn text(&self, cx: &App) -> String;
+    fn set_text(&self, text: &str, window: &mut Window, cx: &mut App);
+    fn clear(&self, window: &mut Window, cx: &mut App);
+    fn set_placeholder_text(&self, text: &str, window: &mut Window, _: &mut App);
+    fn move_selection_to_end(&self, window: &mut Window, _: &mut App);
+
+    fn focus_handle(&self, cx: &App) -> FocusHandle;
+
+    fn subscribe(
+        &self,
+        callback: Box<dyn FnMut(ErasedEditorEvent, &mut Window, &mut App) + 'static>,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> Subscription;
+    fn render(&self, window: &mut Window, cx: &App) -> AnyElement;
+    fn as_any(&self) -> &dyn Any;
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ErasedEditorEvent {
+    BufferEdited,
+    Blurred,
+}
+pub static ERASED_EDITOR_FACTORY: OnceLock<fn(&mut Window, &mut App) -> Arc<dyn ErasedEditor>> =
+    OnceLock::new();

crates/zed_actions/src/lib.rs 🔗

@@ -182,6 +182,19 @@ pub struct ResetAllZoom {
     pub persist: bool,
 }
 
+pub mod editor {
+    use gpui::actions;
+    actions!(
+        editor,
+        [
+            /// Moves cursor up.
+            MoveUp,
+            /// Moves cursor down.
+            MoveDown,
+        ]
+    );
+}
+
 pub mod dev {
     use gpui::actions;
 
@@ -340,6 +353,16 @@ pub mod icon_theme_selector {
     }
 }
 
+pub mod search {
+    use gpui::actions;
+    actions!(
+        search,
+        [
+            /// Toggles searching in ignored files.
+            ToggleIncludeIgnored
+        ]
+    );
+}
 pub mod settings_profile_selector {
     use gpui::Action;
     use schemars::JsonSchema;