diff --git a/Cargo.lock b/Cargo.lock index 6f05194594..e889dabd5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1388,7 +1388,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "gpui_util", "indexmap", @@ -2086,7 +2086,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "proc-macro2", "quote", @@ -3376,7 +3376,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "accesskit", "anyhow", @@ -3728,7 +3728,7 @@ dependencies = [ [[package]] name = "gpui_apple" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "block", @@ -3751,7 +3751,7 @@ dependencies = [ [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "accesskit", "accesskit_unix", @@ -3797,7 +3797,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "accesskit", "accesskit_macos", @@ -3843,7 +3843,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3854,7 +3854,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "console_error_panic_hook", "gpui", @@ -3867,7 +3867,7 @@ dependencies = [ [[package]] name = "gpui_shared_string" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "schemars", "serde", @@ -3877,7 +3877,7 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "log", @@ -3887,7 +3887,7 @@ dependencies = [ [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "async-channel", @@ -3912,7 +3912,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "bytemuck", @@ -3938,7 +3938,7 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "accesskit", "accesskit_windows", @@ -4300,7 +4300,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "async-compression", @@ -4320,7 +4320,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "log", "rustls", @@ -5676,7 +5676,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "bindgen", @@ -6746,7 +6746,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "collections", "serde", @@ -7828,7 +7828,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "derive_refineable", ] @@ -7889,7 +7889,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "bytes", @@ -8398,7 +8398,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "async-task", "backtrace", @@ -9082,7 +9082,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "heapless", "log", @@ -10610,7 +10610,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "perf", "quote", @@ -12639,7 +12639,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "anyhow", "chrono", @@ -12684,7 +12684,7 @@ dependencies = [ [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" dependencies = [ "tracing", "tracing-subscriber", @@ -12695,7 +12695,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" [[package]] name = "zune-core" diff --git a/Cargo.toml b/Cargo.toml index 527d355a3a..401efdc6a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,12 +48,12 @@ gpui-wry = { path = "crates/webview", version = "0.6.0" } gpui-component-shell = { path = "crates/component-shell", version = "0.6.0" } story = { path = "crates/story" } -gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } -gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } -gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } -gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } -reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } -sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } +gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } +gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } +gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } +gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } +reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } +sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } reqwest = { package = "zed-reqwest", git = "https://github.com/zed-industries/reqwest.git", rev = "33bc764aa15ff7b200bf7c93bd96e24878d53e14", version = "0.12.15-zed", default-features = false, features = [ "charset", "http2", @@ -159,4 +159,4 @@ ignored = ["log", "anyhow", "serde"] [workspace.metadata.theorem-fork] upstream = "https://github.com/longbridge/gpui-kit" upstream_tag = "v0.6.0" -zed_rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" +zed_rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" diff --git a/_typos.toml b/_typos.toml index 3e996300cc..78a0e7621d 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,5 +1,11 @@ [default] -extend-ignore-re = ['\\u\{[0-9A-Fa-f]+\}'] +extend-ignore-re = [ + '\\u\{[0-9A-Fa-f]+\}', + # Ordered Git patch object IDs are immutable hex, not prose. + '(?m)^index [0-9a-f]+\.\.[0-9a-f]+(?: [0-7]+)?$', + # The exact screen-capture package identifier in the dependency patches. + 'zed-scap', +] [default.extend-words] # Correct Italian singular of "command". diff --git a/crates/base/src/input/base/kind.rs b/crates/base/src/input/base/kind.rs index 26c28d535f..6637e80030 100644 --- a/crates/base/src/input/base/kind.rs +++ b/crates/base/src/input/base/kind.rs @@ -185,6 +185,10 @@ pub trait InputModeKind: sealed::Sealed + Sized + 'static { /// Drops decorations and hover state when the text is replaced wholesale. fn reset_annotations(_state: &mut InputBaseState) {} + /// Drops presentation ranges after an authoritative whole-text replacement. + /// Existing mode behavior is unchanged unless a mode opts into this hook. + fn reset_document_presentation(_state: &mut InputBaseState) {} + /// Slides decoration ranges along with an edit. fn adjust_annotations( _state: &mut InputBaseState, @@ -313,12 +317,6 @@ impl InputModeKind for InputMode { /// them would cost more in dispatch than it saves. type Extras = (); } -impl InputModeKind for TextareaMode { - const MULTI_LINE: bool = true; - - /// Ordinary multi-line text needs nothing beyond the shared engine. - type Extras = (); -} // `EditorMode`'s implementation lives with the editor code, next to the // language features it dispatches to. diff --git a/crates/base/src/input/base/state.rs b/crates/base/src/input/base/state.rs index ab5397b347..2c776da421 100644 --- a/crates/base/src/input/base/state.rs +++ b/crates/base/src/input/base/state.rs @@ -2,7 +2,6 @@ //! //! Based on the `Input` example from the `gpui` crate. //! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs -use gpui::TextAlign; use gpui::{ Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, @@ -10,6 +9,7 @@ use gpui::{ Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _, px, }; +use gpui::{TextAlign, TextInputConfiguration}; use ropey::{Rope, RopeSlice}; use serde::Deserialize; use std::borrow::Cow; @@ -320,6 +320,7 @@ pub struct InputBaseState { pub(super) masked: bool, pub(super) clean_on_escape: bool, pub(super) submit_on_enter: bool, + text_input_configuration: TextInputConfiguration, pub(super) show_whitespaces: bool, /// This flag tells the renderer to prefer the end of the current visual line. pub(crate) cursor_line_end_affinity: bool, @@ -637,6 +638,7 @@ impl InputBaseState { masked: false, clean_on_escape: false, submit_on_enter: false, + text_input_configuration: TextInputConfiguration::default(), show_whitespaces: false, loading: false, pattern: None, @@ -927,6 +929,7 @@ impl InputBaseState { this.undo_manager.pending_intent = Some(EditIntent::Atomic); let range = 0..this.text.chars().map(|c| c.len_utf16()).sum(); this.replace_text_in_range_silent(Some(range), &text, window, cx); + M::reset_document_presentation(this); this.reset_highlighter(cx); }); } @@ -1036,6 +1039,26 @@ impl InputBaseState { cx.notify(); } + /// Configure the focused platform input session's assistance and action key. + /// This only changes platform presentation; Enter keeps its configured + /// input/submit behavior. Defaults remain GPUI's disabled assistance. + pub fn input_configuration(mut self, configuration: TextInputConfiguration) -> Self { + self.text_input_configuration = configuration; + self + } + + /// Update the active input session without replacing its text or selection. + pub fn set_input_configuration( + &mut self, + configuration: TextInputConfiguration, + cx: &mut Context, + ) { + if self.text_input_configuration != configuration { + self.text_input_configuration = configuration; + cx.notify(); + } + } + /// Set whether to show whitespace characters. #[doc(hidden)] pub fn show_whitespaces(mut self, show: bool) -> Self { @@ -2686,6 +2709,14 @@ impl InputBaseState { } impl EntityInputHandler for InputBaseState { + fn text_input_configuration( + &mut self, + _window: &mut Window, + _cx: &mut Context, + ) -> TextInputConfiguration { + self.text_input_configuration.clone() + } + fn text_for_range( &mut self, range_utf16: Range, @@ -3275,6 +3306,54 @@ mod tests { } } + #[gpui::test] + fn platform_input_configuration_preserves_default_and_forwards_changes( + cx: &mut TestAppContext, + ) { + cx.update(crate::init); + let plain = InputView::build(cx, |state| state); + plain + .window_handle + .update(cx, |_, window, cx| { + plain.input.update(cx, |state, cx| { + assert_eq!( + state.text_input_configuration(window, cx), + TextInputConfiguration::default() + ); + }); + }) + .unwrap(); + let search = InputView::build(cx, |state| { + state.input_configuration(TextInputConfiguration { + input_action: gpui::TextInputAction::Search, + ..Default::default() + }) + }); + search + .window_handle + .update(cx, |_, window, cx| { + search.input.update(cx, |state, cx| { + assert_eq!( + state.text_input_configuration(window, cx).input_action, + gpui::TextInputAction::Search + ); + state.set_value("retained draft", window, cx); + state.set_selected_range(2..5, cx); + let changed = TextInputConfiguration { + input_action: gpui::TextInputAction::Send, + autocorrect: true, + suggestions: true, + autocapitalize: gpui::Autocapitalize::Sentences, + }; + state.set_input_configuration(changed.clone(), cx); + assert_eq!(state.text_input_configuration(window, cx), changed); + assert_eq!(state.text().to_string(), "retained draft"); + assert_eq!(state.selected_range(), 2..5); + }); + }) + .unwrap(); + } + #[gpui::test] fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) { cx.update(crate::init); diff --git a/crates/base/src/input/editor/decorations.rs b/crates/base/src/input/editor/decorations.rs index dc4b393cce..231546fc2f 100644 --- a/crates/base/src/input/editor/decorations.rs +++ b/crates/base/src/input/editor/decorations.rs @@ -158,7 +158,7 @@ impl DecorationCollections { } } -fn adjust_range_for_edit( +pub(crate) fn adjust_range_for_edit( range: &Range, edited_range: &Range, inserted_len: usize, @@ -204,7 +204,7 @@ fn adjust_range_for_edit( start..end } -fn normalize(text: &Rope, decorations: Vec) -> Vec { +pub(crate) fn normalize(text: &Rope, decorations: Vec) -> Vec { decorations .into_iter() .filter_map(|decoration| { diff --git a/crates/base/src/input/editor/mod.rs b/crates/base/src/input/editor/mod.rs index d4e16e3da3..ce32baaeb2 100644 --- a/crates/base/src/input/editor/mod.rs +++ b/crates/base/src/input/editor/mod.rs @@ -5,7 +5,7 @@ use super::{EditorMode, InputBaseState, InputModeKind}; /// State for source-code editing. /// /// This is the shared editing engine in its code-editor kind. Languages, line -/// numbers, folding, indent guides, diagnostics, decorations, and the LSP +/// numbers, folding, indent guides, diagnostics, and the LSP /// providers exist on this kind only, so an ordinary input or textarea never /// exposes them. pub type EditorState = InputBaseState; diff --git a/crates/base/src/input/mod.rs b/crates/base/src/input/mod.rs index e8d9c2b2fb..0a4ddeb23a 100644 --- a/crates/base/src/input/mod.rs +++ b/crates/base/src/input/mod.rs @@ -96,4 +96,4 @@ pub use rope_ext::{InputEdit, Point, RopeExt, RopeLines}; pub use ropey::Rope; pub use search::{SearchMatcher, SearchSession}; pub use state::*; -pub use textarea::{Textarea, TextareaState}; +pub use textarea::{Textarea, TextareaExtras, TextareaState}; diff --git a/crates/base/src/input/textarea/mod.rs b/crates/base/src/input/textarea/mod.rs index 341d108e06..79299b20f3 100644 --- a/crates/base/src/input/textarea/mod.rs +++ b/crates/base/src/input/textarea/mod.rs @@ -1,6 +1,6 @@ -use gpui::{App, Entity, IntoElement, RenderOnce, Window}; +use gpui::{App, Context, Entity, IntoElement, RenderOnce, Window}; -use super::{InputBaseState, TextareaMode}; +use super::{InputBaseState, InputExtras, InputModeKind, TextDecoration, TextareaMode}; /// State for editing ordinary multi-line text. /// @@ -28,3 +28,106 @@ impl RenderOnce for Textarea { self.state } } + +/// Presentation-only ranges for ordinary multiline prose. Empty by default; +/// this does not add syntax parsing, diagnostics, folding, or an LSP. +#[derive(Default)] +pub struct TextareaExtras { + decorations: Vec, +} + +impl InputExtras for TextareaExtras { + fn decoration_layers(&self) -> Vec<&[TextDecoration]> { + vec![&self.decorations] + } +} + +impl InputModeKind for TextareaMode { + const MULTI_LINE: bool = true; + type Extras = TextareaExtras; + + fn reset_annotations(state: &mut InputBaseState) { + state.extras.decorations.clear(); + } + + fn reset_document_presentation(state: &mut InputBaseState) { + state.extras.decorations.clear(); + } + + fn adjust_annotations( + state: &mut InputBaseState, + range: &std::ops::Range, + new_len: usize, + ) { + state.extras.decorations.retain_mut(|decoration| { + decoration.range = + super::decorations::adjust_range_for_edit(&decoration.range, range, new_len); + !decoration.range.is_empty() + }); + } +} + +impl TextareaState { + /// Replace presentation styles without replacing text, selection, or IME + /// marked text. UTF-8 ranges use the same clipping and edit adjustment as + /// editor decorations. Applications retain authority over the styles. + pub fn set_text_decorations( + &mut self, + decorations: Vec, + cx: &mut Context, + ) { + let decorations = super::decorations::normalize(&self.text, decorations); + if self.extras.decorations != decorations { + self.extras.decorations = decorations; + cx.notify(); + } + } + + /// The normalized presentation styles currently painted by this textarea. + pub fn text_decorations(&self) -> &[TextDecoration] { + &self.extras.decorations + } +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::{EntityInputHandler as _, HighlightStyle, TestAppContext}; + + #[gpui::test] + fn textarea_decorations_preserve_text_selection_and_composition(cx: &mut TestAppContext) { + cx.update(crate::init); + let window = cx.add_window(|window, cx| TextareaState::new(window, cx)); + window + .update(cx, |state, window, cx| { + assert!(state.text_decorations().is_empty()); + assert!(!state.is_code_editor()); + state.set_value("héllo world", window, cx); + state.set_selected_range(1..4, cx); + let value = state.value(); + let selection = state.selected_range(); + let bold = HighlightStyle { + font_weight: Some(gpui::FontWeight::BOLD), + ..Default::default() + }; + state.set_text_decorations(vec![TextDecoration::new(2..4, bold)], cx); + assert_eq!(state.value(), value); + assert_eq!(state.selected_range(), selection); + assert_eq!(state.text_decorations(), &[TextDecoration::new(1..4, bold)]); + state.set_selected_range(0..0, cx); + state.replace_and_mark_text_in_range(None, "界", Some(1..1), window, cx); + let marked = state.marked_text_range(window, cx); + let value = state.value(); + let selection = state.selected_range(); + assert!(marked.is_some()); + assert_eq!(state.text_decorations()[0].range, 4..7); + state.set_text_decorations(vec![TextDecoration::new(4..7, bold)], cx); + assert_eq!(state.value(), value); + assert_eq!(state.selected_range(), selection); + assert_eq!(state.marked_text_range(window, cx), marked); + state.set_value("replacement", window, cx); + assert!(state.text_decorations().is_empty()); + }) + .expect("textarea remains mounted"); + } +} diff --git a/crates/base/src/text/inline.rs b/crates/base/src/text/inline.rs index 09abe59c8b..c0711f85aa 100644 --- a/crates/base/src/text/inline.rs +++ b/crates/base/src/text/inline.rs @@ -1,15 +1,17 @@ use gpui::Corners; use std::{ + collections::BTreeMap, ops::Range, rc::Rc, sync::{Arc, Mutex}, }; use gpui::{ - App, BorderStyle, Bounds, ClickEvent, CursorStyle, Edges, Element, ElementId, GlobalElementId, - Half, HighlightStyle, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, LayoutId, - MouseButton, MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, - SharedString, StyledText, TextLayout, Window, point, px, quad, + AnyElement, App, AvailableSpace, BorderStyle, Bounds, ClickEvent, CursorStyle, Edges, Element, + ElementId, GlobalElementId, Half, HighlightStyle, Hitbox, HitboxBehavior, InspectorElementId, + InteractiveElement as _, IntoElement, LayoutId, MouseButton, MouseClickEvent, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, StatefulInteractiveElement as _, + Styled as _, StyledText, TextLayout, Window, div, point, px, quad, size, }; use crate::{ @@ -19,7 +21,10 @@ use crate::{ text::node::LinkMark, text::selection::word_range_at, text::state::LineSpan, - text::text_view::{LinkClickHandlerFn, handle_link_click}, + text::text_view::{ + LinkClickHandlerFn, LinkFragment, LinkFragmentDecoratorFn, LinkUnderline, LinkUnderlineFn, + handle_link_click, + }, }; /// A inline element used to render a inline text and support selectable. @@ -32,6 +37,9 @@ pub(super) struct Inline { highlights: Vec<(Range, HighlightStyle)>, styled_text: StyledText, link_click_handler: Option>, + link_fragment_decorator: Option>, + link_source_offset: usize, + link_underline: Option>, state: Arc>, } @@ -72,10 +80,132 @@ impl Inline { text: text.clone(), styled_text: StyledText::new(text), link_click_handler, + link_fragment_decorator: None, + link_source_offset: 0, + link_underline: None, state, } } + pub(super) fn link_with( + mut self, + decorator: Option>, + source_offset: usize, + ) -> Self { + self.link_fragment_decorator = decorator; + self.link_source_offset = source_offset; + self + } + + pub(super) fn link_underline(mut self, style: Option>) -> Self { + self.link_underline = style; + self + } + + fn link_elements( + &self, + global_id: Option<&GlobalElementId>, + window: &mut Window, + cx: &mut App, + ) -> Vec { + let Some(decorator) = &self.link_fragment_decorator else { + return Vec::new(); + }; + let mut focus_handles = window + .with_element_state::, _>( + global_id.expect("Inline has a stable id"), + |retained, _| { + let state = retained.unwrap_or_default(); + (state.clone(), state) + }, + ); + let mut retained_ids = Vec::new(); + let layout = self.styled_text.layout(); + let mut elements = Vec::new(); + for (link_ix, (range, link)) in self.links.iter().enumerate() { + for (part_ix, (fragment_range, bounds)) in link_fragment_bounds(&layout, range.clone()) + .into_iter() + .enumerate() + { + let fragment = LinkFragment { + id: format!( + "link-{}-{:?}-{link_ix}-{part_ix}", + self.link_source_offset, self.id + ) + .into(), + url: link.url.clone(), + text: self.text[fragment_range].to_string().into(), + }; + let focus = focus_handles + .entry(fragment.id.clone()) + .or_insert_with(|| cx.focus_handle()) + .clone(); + retained_ids.push(fragment.id.clone()); + let click_focus = focus.clone(); + let handler = self.link_click_handler.clone(); + let url = link.url.clone(); + let text_view_state = GlobalState::global(cx).text_view_state().cloned(); + let click = move |event: &ClickEvent, window: &mut Window, cx: &mut App| { + if !matches!(event, ClickEvent::Keyboard(_)) + && text_view_state + .as_ref() + .is_some_and(|state| state.read(cx).has_selection(cx)) + { + return; + } + TextSelection::end(window, cx); + // The selection layer focuses its text participant on + // mouse down. A completed link click focuses the actual + // fragment, while a drag keeps the text selection focus. + click_focus.focus(window, cx); + // Selection queues its focus callback on mouse down. A + // semantic press can deliver down and up in one update, + // so that queued callback would otherwise run after this + // completed click and take focus back from the link. + // Correct only that participant's focus; navigation may + // have intentionally focused a different control. + if let Some(state) = &text_view_state { + let selection_focus = state.read(cx).focus_handle().clone(); + let completed_focus = click_focus.clone(); + window.defer(cx, move |window, cx| { + if selection_focus.is_focused(window) { + completed_focus.focus(window, cx); + } + }); + } + cx.stop_propagation(); + handle_link_click(&handler, url.clone(), event.clone(), window, cx); + }; + let target = div() + .id(fragment.id.clone()) + .tab_index(0) + .track_focus(&focus) + .w(bounds.size.width) + .h(bounds.size.height) + .cursor_pointer() + .on_click(click.clone()) + .on_aux_click(click); + let mut target = decorator(&fragment, target, window, cx).into_any_element(); + target.prepaint_as_root( + bounds.origin, + size( + AvailableSpace::Definite(bounds.size.width), + AvailableSpace::Definite(bounds.size.height), + ), + window, + cx, + ); + elements.push(target); + } + } + focus_handles.retain(|id, _| retained_ids.contains(id)); + window.with_element_state::, _>( + global_id.expect("Inline has a stable id"), + |_, _| ((), focus_handles), + ); + elements + } + /// Get link at given mouse position. fn link_for_position( layout: &TextLayout, @@ -338,7 +468,7 @@ impl IntoElement for Inline { impl Element for Inline { type RequestLayoutState = (); - type PrepaintState = Hitbox; + type PrepaintState = (Hitbox, Vec); fn id(&self) -> Option { Some(self.id.clone()) @@ -357,9 +487,34 @@ impl Element for Inline { ) -> (LayoutId, Self::RequestLayoutState) { let text_style = window.text_style(); + let overrides = self + .links + .iter() + .filter_map(|(range, link)| { + let style = self + .link_underline + .as_ref() + .map(|style| style(&link.url, cx)) + .unwrap_or_default(); + (style != LinkUnderline::Solid).then(|| { + ( + range.clone(), + HighlightStyle { + underline: Some(gpui::UnderlineStyle { + thickness: px(0.), + ..Default::default() + }), + ..Default::default() + }, + ) + }) + }) + .collect::>(); + let highlights = + gpui::combine_highlights(self.highlights.clone(), overrides).collect::>(); let mut runs = Vec::new(); let mut ix = 0; - for (range, highlight) in self.highlights.iter() { + for (range, highlight) in &highlights { if ix < range.start { runs.push(text_style.clone().to_run(range.start - ix)); } @@ -408,7 +563,7 @@ impl Element for Inline { } let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal); - hitbox + (hitbox, self.link_elements(id, window, cx)) } fn paint( @@ -422,7 +577,7 @@ impl Element for Inline { cx: &mut App, ) { let current_view = window.current_view(); - let hitbox = prepaint; + let (hitbox, link_elements) = prepaint; let Ok(mut state) = self.state.lock() else { return; }; @@ -431,6 +586,31 @@ impl Element for Inline { self.styled_text .paint(global_id, None, bounds, &mut (), &mut (), window, cx); + for (range, link) in self.links.iter() { + if self + .link_underline + .as_ref() + .map(|style| style(&link.url, cx)) + != Some(LinkUnderline::Dotted) + { + continue; + } + let color = self + .highlights + .iter() + .find_map(|(span, style)| { + (span.start <= range.start && range.start < span.end) + .then_some(style.color) + .flatten() + }) + .unwrap_or(window.text_style().color); + for (_, fragment) in link_fragment_bounds(&text_layout, range.clone()) { + for dot in dotted_underline_bounds(fragment) { + window.paint_quad(gpui::fill(dot, color)); + } + } + } + // layout selections let (is_selectable, is_selection, selection) = self.layout_selections(&text_layout, &bounds, window, cx); @@ -541,7 +721,7 @@ impl Element for Inline { } }); - if !is_selection { + if !is_selection && self.link_fragment_decorator.is_none() { // click to open link window.on_mouse_event({ let links = self.links.clone(); @@ -581,7 +761,73 @@ impl Element for Inline { } }); } + // These are the actual link targets, positioned by the same glyph + // layout painted above. Their click handlers replace the legacy hit + // test only when the caller requested a decorator. + for element in link_elements { + element.paint(window, cx); + } + } +} + +/// Split at actual hard/soft line boundaries. Widths come from shaped glyph +/// advances, including the final glyph before a wrap, never an estimated font +/// width or a union spanning multiple lines. +fn link_fragment_bounds( + layout: &TextLayout, + range: Range, +) -> Vec<(Range, Bounds)> { + let mut fragments = Vec::new(); + let mut line_start = 0; + let mut line_y = layout.bounds().top(); + for line in layout.line_layouts() { + let boundaries = std::iter::once(0) + .chain(line.wrap_boundaries.iter().map(|boundary| { + line.unwrapped_layout.runs[boundary.run_ix].glyphs[boundary.glyph_ix].index + })) + .chain(std::iter::once(line.len())) + .collect::>(); + for (row_ix, row) in boundaries.windows(2).enumerate() { + let start = range.start.max(line_start + row[0]); + let end = range.end.min(line_start + row[1]); + if start >= end { + continue; + } + // position_for_index deliberately gives a wrap boundary the + // previous line's caret affinity. A visual fragment starts on the + // next row, so use the actual wrap row and shaped x advances. + let origin = point( + layout.bounds().left() + line.unwrapped_layout.x_for_index(start - line_start) + - line.unwrapped_layout.x_for_index(row[0]), + line_y + layout.line_height() * row_ix, + ); + let width = line.unwrapped_layout.x_for_index(end - line_start) + - line.unwrapped_layout.x_for_index(start - line_start); + if width > px(0.) { + fragments.push(( + start..end, + Bounds::new(origin, size(width, layout.line_height())), + )); + } + } + line_start += line.len() + 1; + line_y += line.size(layout.line_height()).height; + } + fragments +} + +/// One-pixel dots separated by two clear pixels on each measured fragment. +fn dotted_underline_bounds(fragment: Bounds) -> Vec> { + let mut dots = Vec::new(); + let mut x = fragment.left(); + while x < fragment.right() { + dots.push(Bounds::new( + point(x, fragment.bottom() - px(2.)), + size(px(1.).min(fragment.right() - x), px(1.)), + )); + x += px(3.); } + dots } fn selection_for_multi_click( diff --git a/crates/base/src/text/inline_flow.rs b/crates/base/src/text/inline_flow.rs index 0170601415..e36c716ab7 100644 --- a/crates/base/src/text/inline_flow.rs +++ b/crates/base/src/text/inline_flow.rs @@ -11,7 +11,9 @@ use gpui::{ WhiteSpace, Window, img, point, prelude::FluentBuilder as _, px, relative, size, }; -use crate::text::text_view::{LinkClickHandlerFn, handle_link_click}; +use crate::text::text_view::{ + LinkClickHandlerFn, LinkFragmentDecoratorFn, LinkUnderlineFn, handle_link_click, +}; use super::{ inline::{Inline, InlineState}, @@ -25,6 +27,9 @@ pub(super) struct InlineFlow { id: ElementId, items: Vec, link_click_handler: Option>, + link_fragment_decorator: Option>, + link_source_offset: usize, + link_underline: Option>, } pub(super) enum InlineFlowItem { @@ -111,9 +116,27 @@ impl InlineFlow { id: id.into(), items, link_click_handler, + link_fragment_decorator: None, + link_source_offset: 0, + link_underline: None, } } + pub(super) fn link_with( + mut self, + decorator: Option>, + source_offset: usize, + ) -> Self { + self.link_fragment_decorator = decorator; + self.link_source_offset = source_offset; + self + } + + pub(super) fn link_underline(mut self, style: Option>) -> Self { + self.link_underline = style; + self + } + fn image_element( ix: usize, url: &SharedUri, @@ -286,6 +309,11 @@ impl Element for InlineFlow { highlights, self.link_click_handler.clone(), ) + .link_with( + self.link_fragment_decorator.clone(), + self.link_source_offset + source_range.start, + ) + .link_underline(self.link_underline.clone()) .into_any_element(); element.prepaint_as_root( bounds.origin + origin, diff --git a/crates/base/src/text/node.rs b/crates/base/src/text/node.rs index a008ccc4de..34133f457f 100644 --- a/crates/base/src/text/node.rs +++ b/crates/base/src/text/node.rs @@ -17,8 +17,8 @@ use crate::{ StyledExt, h_flex, scrollable_mask::horizontal_scroll_area, text::{ - CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, MarkdownExtensions, - MarkdownNode, TableActionsFn, + CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, LinkFragmentDecoratorFn, + LinkUnderlineFn, MarkdownExtensions, MarkdownNode, TableActionsFn, document::NodeRenderOptions, inline::{Inline, InlineState}, inline_flow::{InlineFlow, InlineFlowItem}, @@ -1336,6 +1336,8 @@ pub(crate) struct NodeContext { pub(crate) code_block_actions: Option>, pub(crate) code_block_highlighter: Option>, pub(crate) table_actions: Option>, + pub(crate) link_underline: Option>, + pub(crate) link_fragment_decorator: Option>, pub(crate) link_click_handler: Option>, pub(crate) markdown_extensions: Arc, } @@ -1365,6 +1367,11 @@ impl Paragraph { self.inline_flow_items(node_cx, cx), node_cx.link_click_handler.clone(), ) + .link_with( + node_cx.link_fragment_decorator.clone(), + node_cx.offset + span.unwrap_or_default().start, + ) + .link_underline(node_cx.link_underline.clone()) .into_any_element(); } @@ -1393,6 +1400,11 @@ impl Paragraph { highlights.clone(), node_cx.link_click_handler.clone(), ) + .link_with( + node_cx.link_fragment_decorator.clone(), + node_cx.offset + span.unwrap_or_default().start, + ) + .link_underline(node_cx.link_underline.clone()) .into_any_element(), ); } @@ -1508,6 +1520,11 @@ impl Paragraph { highlights, node_cx.link_click_handler.clone(), ) + .link_with( + node_cx.link_fragment_decorator.clone(), + node_cx.offset + span.unwrap_or_default().start, + ) + .link_underline(node_cx.link_underline.clone()) .into_any_element(), ); } diff --git a/crates/base/src/text/state.rs b/crates/base/src/text/state.rs index 8e08c75ec7..073184475a 100644 --- a/crates/base/src/text/state.rs +++ b/crates/base/src/text/state.rs @@ -17,8 +17,8 @@ use crate::{ async_util::{Receiver, Sender, unbounded}, input::{self, SelectAll}, text::{ - CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, MarkdownExtensions, - TableActionsFn, TextViewStyle, + CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, LinkFragmentDecoratorFn, + LinkUnderlineFn, MarkdownExtensions, TableActionsFn, TextViewStyle, document::ParsedDocument, format, node::{self, NodeContext}, @@ -103,6 +103,8 @@ pub struct TextViewState { pub(super) code_block_actions: Option>, pub(super) code_block_highlighter: Option>, pub(super) table_actions: Option>, + pub(super) link_underline: Option>, + pub(super) link_fragment_decorator: Option>, pub(super) link_click_handler: Option>, pub(super) markdown_extensions: Arc, @@ -203,6 +205,8 @@ impl TextViewState { code_block_highlighter: None, table_actions: None, link_click_handler: None, + link_fragment_decorator: None, + link_underline: None, markdown_extensions: Arc::default(), is_selecting: false, auto_scroll: AutoScroll::default(), @@ -613,6 +617,8 @@ impl Render for TextViewState { node_cx.code_block_highlighter = self.code_block_highlighter.clone(); node_cx.table_actions = self.table_actions.clone(); node_cx.link_click_handler = self.link_click_handler.clone(); + node_cx.link_fragment_decorator = self.link_fragment_decorator.clone(); + node_cx.link_underline = self.link_underline.clone(); node_cx.markdown_extensions = self.markdown_extensions.clone(); node_cx.style = self.text_view_style.clone(); diff --git a/crates/base/src/text/text_view.rs b/crates/base/src/text/text_view.rs index 535ee643e4..2857e1c1fa 100644 --- a/crates/base/src/text/text_view.rs +++ b/crates/base/src/text/text_view.rs @@ -73,6 +73,34 @@ impl TextViewDefaults { pub(crate) type TableActionsFn = dyn Fn(&TableData, &mut Window, &mut App) -> AnyElement + Send + Sync; +/// A measured visual fragment of a parsed text link. Wrapped links publish one +/// fragment per visual line, never a rectangle covering the intervening prose. +#[derive(Clone, Debug)] +pub struct LinkFragment { + /// Stable fragment identity within the containing text view. + pub id: SharedString, + /// The URL resolved by the existing Markdown/HTML parser. + pub url: SharedString, + /// The visible text of this fragment, without Markdown syntax. + pub text: SharedString, +} + +/// Presentation of a parsed text link. The default is the existing solid +/// underline; dotted suggestions keep the same glyph layout and hit target. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum LinkUnderline { + #[default] + Solid, + Dotted, + None, +} + +pub(crate) type LinkUnderlineFn = dyn Fn(&SharedString, &App) -> LinkUnderline + Send + Sync; + +pub(crate) type LinkFragmentDecoratorFn = dyn Fn(&LinkFragment, gpui::Stateful, &mut Window, &mut App) -> gpui::Stateful + + Send + + Sync; + pub(crate) type LinkClickHandlerFn = dyn Fn(&SharedString, &ClickEvent, &mut Window, &mut App) + Send + Sync; @@ -128,6 +156,8 @@ pub struct TextView { code_block_highlighter: Option>, table_actions: Option>, link_click_handler: Option>, + link_fragment_decorator: Option>, + link_underline: Option>, markdown_extensions: Arc, } @@ -172,6 +202,8 @@ impl TextView { code_block_highlighter: None, table_actions: None, link_click_handler: None, + link_fragment_decorator: None, + link_underline: None, markdown_extensions: Arc::default(), } } @@ -193,6 +225,8 @@ impl TextView { code_block_highlighter: None, table_actions: None, link_click_handler: None, + link_fragment_decorator: None, + link_underline: None, markdown_extensions: Arc::default(), } } @@ -214,6 +248,8 @@ impl TextView { code_block_highlighter: None, table_actions: None, link_click_handler: None, + link_fragment_decorator: None, + link_underline: None, markdown_extensions: Arc::default(), } } @@ -333,6 +369,37 @@ impl TextView { self } + /// Selects link underline presentation without changing text or layout. + /// Unspecified views retain their existing solid underline. + pub fn link_underline(mut self, style: F) -> Self + where + F: Fn(&SharedString, &App) -> LinkUnderline + Send + Sync + 'static, + { + self.link_underline = Some(Arc::new(style)); + self + } + + /// Decorates each actual, measured text-link target after its ordinary + /// click handler is installed. This can publish accessibility, focus and + /// hover behavior on the same fragment. The parser, text layout and text + /// selection remain owned by TextView. The decorator must preserve the + /// supplied target's dimensions and existing click handler. + pub fn link_with(mut self, decorator: F) -> Self + where + F: Fn( + &LinkFragment, + gpui::Stateful, + &mut Window, + &mut App, + ) -> gpui::Stateful + + Send + + Sync + + 'static, + { + self.link_fragment_decorator = Some(Arc::new(decorator)); + self + } + /// Replace the Markdown extension registry. pub fn markdown_extensions(mut self, extensions: MarkdownExtensions) -> Self { self.markdown_extensions = Arc::new(extensions); @@ -554,6 +621,8 @@ impl Element for TextView { state.code_block_highlighter = code_block_highlighter.clone(); state.table_actions = self.table_actions.clone(); state.link_click_handler = self.link_click_handler.clone(); + state.link_fragment_decorator = self.link_fragment_decorator.clone(); + state.link_underline = self.link_underline.clone(); state.set_markdown_extensions(self.markdown_extensions.clone(), cx); state.selectable = self.selectable; state.selection_format = self.selection_format; @@ -630,16 +699,14 @@ impl Element for TextView { if let Ok(mut line_spans) = state.read(cx).line_spans.lock() { line_spans.clear(); } - // Descendant `Inline`s report their line spans through the state - // stack during prepaint (in addition to the paint-time push below). - GlobalState::global_mut(cx) - .text_view_state_stack - .push(state.clone()); } + // Measured link targets retain this same selection authority. Clamped + // descendants additionally report their line spans through the stack. + GlobalState::global_mut(cx) + .text_view_state_stack + .push(state.clone()); request_layout.element.prepaint(window, cx); - if max_lines_active { - GlobalState::global_mut(cx).text_view_state_stack.pop(); - } + GlobalState::global_mut(cx).text_view_state_stack.pop(); let mut clip_bottom = None; if max_lines_active { diff --git a/crates/component/src/sidebar/menu.rs b/crates/component/src/sidebar/menu.rs index cbaa60cab8..586ccd2b8d 100644 --- a/crates/component/src/sidebar/menu.rs +++ b/crates/component/src/sidebar/menu.rs @@ -8,9 +8,9 @@ use crate::{ v_flex, }; use gpui::{ - AnyElement, App, ClickEvent, ElementId, InteractiveElement as _, IntoElement, - ParentElement as _, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, - Window, div, percentage, prelude::FluentBuilder, + AnyElement, App, ClickEvent, Div, ElementId, InteractiveElement as _, IntoElement, + ParentElement as _, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, + Styled, Window, div, percentage, prelude::FluentBuilder, }; use std::rc::Rc; @@ -101,6 +101,7 @@ pub struct SidebarMenuItem { click_to_toggle: bool, children: Vec, suffix: Option AnyElement + 'static>>, + item_with: Option) -> Stateful
>>, disabled: bool, context_menu: Option PopupMenu + 'static>>, } @@ -119,6 +120,7 @@ impl SidebarMenuItem { click_to_toggle: false, children: Vec::new(), suffix: None, + item_with: None, disabled: false, context_menu: None, } @@ -196,6 +198,26 @@ impl SidebarMenuItem { self } + /// Decorate the actual clickable row, excluding its expanded submenu. + /// + /// The existing click, open, hover and tooltip behavior is retained. + /// Repeated hooks compose in call order, supporting accessibility and + /// measured-row adapters without a second interactive wrapper. They run + /// before the optional context menu. + pub fn item_with( + mut self, + decorate: impl Fn(Stateful
) -> Stateful
+ 'static, + ) -> Self { + let previous = self.item_with.take(); + self.item_with = Some(Rc::new(move |row| { + decorate(match &previous { + Some(previous) => previous(row), + None => row, + }) + })); + self + } + /// Set disabled flat for menu item. pub fn disable(mut self, disable: bool) -> Self { self.disabled = disable; @@ -369,6 +391,10 @@ impl SidebarItem for SidebarMenuItem { this } }) + .map(|this| match self.item_with { + Some(decorate) => decorate(this), + None => this, + }) .map(|this| { if let Some(context_menu) = self.context_menu { this.context_menu(move |menu, window, cx| { diff --git a/crates/component/src/sidebar/mod.rs b/crates/component/src/sidebar/mod.rs index 09c686bdb8..95a4d3d3c6 100644 --- a/crates/component/src/sidebar/mod.rs +++ b/crates/component/src/sidebar/mod.rs @@ -305,6 +305,7 @@ pub struct SidebarToggleButton { collapsed: bool, side: Side, on_click: Option>, + button_with: Option Button>>, } impl SidebarToggleButton { @@ -315,9 +316,17 @@ impl SidebarToggleButton { collapsed: false, side: Side::Left, on_click: None, + button_with: None, } } + /// Decorate the retained button after its icon, label and click handler + /// have been bound. The default leaves the existing button unchanged. + pub fn button_with(mut self, decorate: impl FnOnce(Button) -> Button + 'static) -> Self { + self.button_with = Some(Box::new(decorate)); + self + } + /// Set the side of the toggle button. /// /// Default is `Side::Left`. @@ -372,6 +381,10 @@ impl RenderOnce for SidebarToggleButton { false => t!("Sidebar.Collapse"), }) .icon(Icon::new(icon).size_4()) + .map(|button| match self.button_with { + Some(decorate) => decorate(button), + None => button, + }) } } diff --git a/crates/component/src/status_bar.rs b/crates/component/src/status_bar.rs index 44dff59464..1cfe330665 100644 --- a/crates/component/src/status_bar.rs +++ b/crates/component/src/status_bar.rs @@ -23,7 +23,7 @@ use crate::{ActiveTheme, StyledExt, h_flex}; /// otherwise (only `right`, or neither — like a plain container). /// /// ``` -/// use gpui_kit::component::status_bar::StatusBar; +/// use gpui_component::status_bar::StatusBar; /// /// let _ = StatusBar::new().left("Ln 1, Col 1").right("UTF-8"); /// ``` diff --git a/crates/component/src/text/compat.rs b/crates/component/src/text/compat.rs index a59bb5fb82..6ffcfe546a 100644 --- a/crates/component/src/text/compat.rs +++ b/crates/component/src/text/compat.rs @@ -107,6 +107,31 @@ impl TextView { self.inner = self.inner.on_link_click(f); self } + /// Selects Base's solid, dotted or absent underline on the existing text. + pub fn link_underline(mut self, style: F) -> Self + where + F: Fn(&SharedString, &App) -> gpui_base::text::LinkUnderline + Send + Sync + 'static, + { + self.inner = self.inner.link_underline(style); + self + } + /// Decorates the actual measured Base link targets, preserving its parser, + /// selection and click behavior. See [`gpui_base::TextView::link_with`]. + pub fn link_with(mut self, decorator: F) -> Self + where + F: Fn( + &gpui_base::text::LinkFragment, + gpui::Stateful, + &mut Window, + &mut App, + ) -> gpui::Stateful + + Send + + Sync + + 'static, + { + self.inner = self.inner.link_with(decorator); + self + } /// Sets which Markdown extensions the parser accepts. pub fn markdown_extensions(mut self, value: MarkdownExtensions) -> Self { self.inner = self.inner.markdown_extensions(value); diff --git a/crates/component/src/text/mod.rs b/crates/component/src/text/mod.rs index d594bd57e0..dee556464f 100644 --- a/crates/component/src/text/mod.rs +++ b/crates/component/src/text/mod.rs @@ -7,8 +7,9 @@ pub use compat::{ Text, TextView, TextViewLayoutState, TextViewPlugin, TextViewPrepaintState, html, markdown, }; pub use gpui_base::text::{ - MarkdownBlockParserFn, MarkdownBlockRenderFn, MarkdownExtensions, MarkdownNode, - MarkdownParseContext, MarkdownPlugin, SelectionFormat, TableData, TextViewState, markdown_ast, + LinkFragment, LinkUnderline, MarkdownBlockParserFn, MarkdownBlockRenderFn, MarkdownExtensions, + MarkdownNode, MarkdownParseContext, MarkdownPlugin, SelectionFormat, TableData, TextViewState, + markdown_ast, }; pub use style::TextViewStyle; diff --git a/crates/component/src/title_bar.rs b/crates/component/src/title_bar.rs index 45b091bb37..32254a479f 100644 --- a/crates/component/src/title_bar.rs +++ b/crates/component/src/title_bar.rs @@ -70,8 +70,8 @@ impl TitleBar { /// [`crate::TitleBar`], so the title bar owns dragging and double clicking itself: /// /// ```no_run - /// # use gpui_kit::WindowOptions; - /// # use gpui_kit::component::TitleBar; + /// # use gpui::WindowOptions; + /// # use gpui_component::TitleBar; /// let options = WindowOptions { /// window_min_size: None, /// ..TitleBar::window_options() diff --git a/patches/0012-gpui-web-ime-integration-pin.patch b/patches/0012-gpui-web-ime-integration-pin.patch new file mode 100644 index 0000000000..fa355c578e --- /dev/null +++ b/patches/0012-gpui-web-ime-integration-pin.patch @@ -0,0 +1,271 @@ +From 50cefb3f0d63df293440bc38cdc9081e0ae0d304 Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Fri, 4 Sep 2026 23:54:13 -0400 +Subject: [PATCH] fix(gpui): align the Kit with the published web IME repair + +--- + Cargo.lock | 50 +++++++++++++++++++++++++------------------------- + Cargo.toml | 14 +++++++------- + 2 files changed, 32 insertions(+), 32 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 6f051945..f1763794 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -1388,7 +1388,7 @@ dependencies = [ + [[package]] + name = "collections" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "gpui_util", + "indexmap", +@@ -2086,7 +2086,7 @@ dependencies = [ + [[package]] + name = "derive_refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "proc-macro2", + "quote", +@@ -3376,7 +3376,7 @@ dependencies = [ + [[package]] + name = "gpui" + version = "0.2.2" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "accesskit", + "anyhow", +@@ -3728,7 +3728,7 @@ dependencies = [ + [[package]] + name = "gpui_apple" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "block", +@@ -3751,7 +3751,7 @@ dependencies = [ + [[package]] + name = "gpui_linux" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "accesskit", + "accesskit_unix", +@@ -3797,7 +3797,7 @@ dependencies = [ + [[package]] + name = "gpui_macos" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "accesskit", + "accesskit_macos", +@@ -3843,7 +3843,7 @@ dependencies = [ + [[package]] + name = "gpui_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "heck 0.5.0", + "proc-macro2", +@@ -3854,7 +3854,7 @@ dependencies = [ + [[package]] + name = "gpui_platform" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "console_error_panic_hook", + "gpui", +@@ -3867,7 +3867,7 @@ dependencies = [ + [[package]] + name = "gpui_shared_string" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "schemars", + "serde", +@@ -3877,7 +3877,7 @@ dependencies = [ + [[package]] + name = "gpui_util" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "log", +@@ -3887,7 +3887,7 @@ dependencies = [ + [[package]] + name = "gpui_web" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "async-channel", +@@ -3912,7 +3912,7 @@ dependencies = [ + [[package]] + name = "gpui_wgpu" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "bytemuck", +@@ -3938,7 +3938,7 @@ dependencies = [ + [[package]] + name = "gpui_windows" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "accesskit", + "accesskit_windows", +@@ -4300,7 +4300,7 @@ dependencies = [ + [[package]] + name = "http_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "async-compression", +@@ -4320,7 +4320,7 @@ dependencies = [ + [[package]] + name = "http_client_tls" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "log", + "rustls", +@@ -5676,7 +5676,7 @@ dependencies = [ + [[package]] + name = "media" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "bindgen", +@@ -6746,7 +6746,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] + name = "perf" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "collections", + "serde", +@@ -7828,7 +7828,7 @@ dependencies = [ + [[package]] + name = "refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "derive_refineable", + ] +@@ -7889,7 +7889,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] + name = "reqwest_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "bytes", +@@ -8398,7 +8398,7 @@ dependencies = [ + [[package]] + name = "scheduler" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "async-task", + "backtrace", +@@ -9082,7 +9082,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] + name = "sum_tree" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "heapless", + "log", +@@ -10610,7 +10610,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] + name = "util_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "perf", + "quote", +@@ -12639,7 +12639,7 @@ dependencies = [ + [[package]] + name = "zlog" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "anyhow", + "chrono", +@@ -12684,7 +12684,7 @@ dependencies = [ + [[package]] + name = "ztracing" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + dependencies = [ + "tracing", + "tracing-subscriber", +@@ -12695,7 +12695,7 @@ dependencies = [ + [[package]] + name = "ztracing_macro" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=0a3e29a42a1646cb9f50df076e2aa6c9fda85868#0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" + + [[package]] + name = "zune-core" +diff --git a/Cargo.toml b/Cargo.toml +index 527d355a..922d40aa 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -48,12 +48,12 @@ gpui-wry = { path = "crates/webview", version = "0.6.0" } + gpui-component-shell = { path = "crates/component-shell", version = "0.6.0" } + story = { path = "crates/story" } + +-gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } +-gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } +-gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } +-gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } +-reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } +-sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" } ++gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } ++gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } ++gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } ++gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } ++reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } ++sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } + reqwest = { package = "zed-reqwest", git = "https://github.com/zed-industries/reqwest.git", rev = "33bc764aa15ff7b200bf7c93bd96e24878d53e14", version = "0.12.15-zed", default-features = false, features = [ + "charset", + "http2", +@@ -159,4 +159,4 @@ ignored = ["log", "anyhow", "serde"] + [workspace.metadata.theorem-fork] + upstream = "https://github.com/longbridge/gpui-kit" + upstream_tag = "v0.6.0" +-zed_rev = "0a3e29a42a1646cb9f50df076e2aa6c9fda85868" ++zed_rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" +-- +2.53.0 + diff --git a/patches/0013-gpui-linux-portal-compatible-pin.patch b/patches/0013-gpui-linux-portal-compatible-pin.patch new file mode 100644 index 0000000000..707659aeb4 --- /dev/null +++ b/patches/0013-gpui-linux-portal-compatible-pin.patch @@ -0,0 +1,272 @@ +From 83e668cdea5c095a917d762752dc8f261c5f4a99 Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Sat, 5 Sep 2026 00:04:00 -0400 +Subject: [PATCH] fix(gpui): carry the compatible Linux portal dependency with + the web pin + +--- + Cargo.lock | 50 +++++++++++++++++++++++++------------------------- + Cargo.toml | 14 +++++++------- + 2 files changed, 32 insertions(+), 32 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index f1763794..60b1ebd0 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -1388,7 +1388,7 @@ dependencies = [ + [[package]] + name = "collections" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "gpui_util", + "indexmap", +@@ -2086,7 +2086,7 @@ dependencies = [ + [[package]] + name = "derive_refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "proc-macro2", + "quote", +@@ -3376,7 +3376,7 @@ dependencies = [ + [[package]] + name = "gpui" + version = "0.2.2" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "accesskit", + "anyhow", +@@ -3728,7 +3728,7 @@ dependencies = [ + [[package]] + name = "gpui_apple" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "block", +@@ -3751,7 +3751,7 @@ dependencies = [ + [[package]] + name = "gpui_linux" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "accesskit", + "accesskit_unix", +@@ -3797,7 +3797,7 @@ dependencies = [ + [[package]] + name = "gpui_macos" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "accesskit", + "accesskit_macos", +@@ -3843,7 +3843,7 @@ dependencies = [ + [[package]] + name = "gpui_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "heck 0.5.0", + "proc-macro2", +@@ -3854,7 +3854,7 @@ dependencies = [ + [[package]] + name = "gpui_platform" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "console_error_panic_hook", + "gpui", +@@ -3867,7 +3867,7 @@ dependencies = [ + [[package]] + name = "gpui_shared_string" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "schemars", + "serde", +@@ -3877,7 +3877,7 @@ dependencies = [ + [[package]] + name = "gpui_util" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "log", +@@ -3887,7 +3887,7 @@ dependencies = [ + [[package]] + name = "gpui_web" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "async-channel", +@@ -3912,7 +3912,7 @@ dependencies = [ + [[package]] + name = "gpui_wgpu" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "bytemuck", +@@ -3938,7 +3938,7 @@ dependencies = [ + [[package]] + name = "gpui_windows" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "accesskit", + "accesskit_windows", +@@ -4300,7 +4300,7 @@ dependencies = [ + [[package]] + name = "http_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "async-compression", +@@ -4320,7 +4320,7 @@ dependencies = [ + [[package]] + name = "http_client_tls" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "log", + "rustls", +@@ -5676,7 +5676,7 @@ dependencies = [ + [[package]] + name = "media" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "bindgen", +@@ -6746,7 +6746,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] + name = "perf" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "collections", + "serde", +@@ -7828,7 +7828,7 @@ dependencies = [ + [[package]] + name = "refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "derive_refineable", + ] +@@ -7889,7 +7889,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] + name = "reqwest_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "bytes", +@@ -8398,7 +8398,7 @@ dependencies = [ + [[package]] + name = "scheduler" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "async-task", + "backtrace", +@@ -9082,7 +9082,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] + name = "sum_tree" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "heapless", + "log", +@@ -10610,7 +10610,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] + name = "util_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "perf", + "quote", +@@ -12639,7 +12639,7 @@ dependencies = [ + [[package]] + name = "zlog" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "anyhow", + "chrono", +@@ -12684,7 +12684,7 @@ dependencies = [ + [[package]] + name = "ztracing" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + dependencies = [ + "tracing", + "tracing-subscriber", +@@ -12695,7 +12695,7 @@ dependencies = [ + [[package]] + name = "ztracing_macro" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=3deff0ede14d47839928be8e2f837ba80d0388fd#3deff0ede14d47839928be8e2f837ba80d0388fd" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" + + [[package]] + name = "zune-core" +diff --git a/Cargo.toml b/Cargo.toml +index 922d40aa..7ea05b4e 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -48,12 +48,12 @@ gpui-wry = { path = "crates/webview", version = "0.6.0" } + gpui-component-shell = { path = "crates/component-shell", version = "0.6.0" } + story = { path = "crates/story" } + +-gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } +-gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } +-gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } +-gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } +-reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } +-sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" } ++gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } ++gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } ++gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } ++gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } ++reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } ++sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } + reqwest = { package = "zed-reqwest", git = "https://github.com/zed-industries/reqwest.git", rev = "33bc764aa15ff7b200bf7c93bd96e24878d53e14", version = "0.12.15-zed", default-features = false, features = [ + "charset", + "http2", +@@ -159,4 +159,4 @@ ignored = ["log", "anyhow", "serde"] + [workspace.metadata.theorem-fork] + upstream = "https://github.com/longbridge/gpui-kit" + upstream_tag = "v0.6.0" +-zed_rev = "3deff0ede14d47839928be8e2f837ba80d0388fd" ++zed_rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" +-- +2.53.0 + diff --git a/patches/0014-gpui-desktop-keyboard-focus-pin.patch b/patches/0014-gpui-desktop-keyboard-focus-pin.patch new file mode 100644 index 0000000000..054de913c5 --- /dev/null +++ b/patches/0014-gpui-desktop-keyboard-focus-pin.patch @@ -0,0 +1,271 @@ +From 4f50f50b2d8e7a73cdf9e622aba8a68469af1136 Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Sat, 5 Sep 2026 01:17:54 -0400 +Subject: [PATCH] fix(gpui): carry the desktop keyboard focus prerequisite + +--- + Cargo.lock | 50 +++++++++++++++++++++++++------------------------- + Cargo.toml | 14 +++++++------- + 2 files changed, 32 insertions(+), 32 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 60b1ebd0..3b939a04 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -1388,7 +1388,7 @@ dependencies = [ + [[package]] + name = "collections" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "gpui_util", + "indexmap", +@@ -2086,7 +2086,7 @@ dependencies = [ + [[package]] + name = "derive_refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "proc-macro2", + "quote", +@@ -3376,7 +3376,7 @@ dependencies = [ + [[package]] + name = "gpui" + version = "0.2.2" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "accesskit", + "anyhow", +@@ -3728,7 +3728,7 @@ dependencies = [ + [[package]] + name = "gpui_apple" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "block", +@@ -3751,7 +3751,7 @@ dependencies = [ + [[package]] + name = "gpui_linux" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "accesskit", + "accesskit_unix", +@@ -3797,7 +3797,7 @@ dependencies = [ + [[package]] + name = "gpui_macos" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "accesskit", + "accesskit_macos", +@@ -3843,7 +3843,7 @@ dependencies = [ + [[package]] + name = "gpui_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "heck 0.5.0", + "proc-macro2", +@@ -3854,7 +3854,7 @@ dependencies = [ + [[package]] + name = "gpui_platform" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "console_error_panic_hook", + "gpui", +@@ -3867,7 +3867,7 @@ dependencies = [ + [[package]] + name = "gpui_shared_string" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "schemars", + "serde", +@@ -3877,7 +3877,7 @@ dependencies = [ + [[package]] + name = "gpui_util" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "log", +@@ -3887,7 +3887,7 @@ dependencies = [ + [[package]] + name = "gpui_web" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "async-channel", +@@ -3912,7 +3912,7 @@ dependencies = [ + [[package]] + name = "gpui_wgpu" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "bytemuck", +@@ -3938,7 +3938,7 @@ dependencies = [ + [[package]] + name = "gpui_windows" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "accesskit", + "accesskit_windows", +@@ -4300,7 +4300,7 @@ dependencies = [ + [[package]] + name = "http_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "async-compression", +@@ -4320,7 +4320,7 @@ dependencies = [ + [[package]] + name = "http_client_tls" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "log", + "rustls", +@@ -5676,7 +5676,7 @@ dependencies = [ + [[package]] + name = "media" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "bindgen", +@@ -6746,7 +6746,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] + name = "perf" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "collections", + "serde", +@@ -7828,7 +7828,7 @@ dependencies = [ + [[package]] + name = "refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "derive_refineable", + ] +@@ -7889,7 +7889,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] + name = "reqwest_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "bytes", +@@ -8398,7 +8398,7 @@ dependencies = [ + [[package]] + name = "scheduler" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "async-task", + "backtrace", +@@ -9082,7 +9082,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] + name = "sum_tree" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "heapless", + "log", +@@ -10610,7 +10610,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] + name = "util_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "perf", + "quote", +@@ -12639,7 +12639,7 @@ dependencies = [ + [[package]] + name = "zlog" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "anyhow", + "chrono", +@@ -12684,7 +12684,7 @@ dependencies = [ + [[package]] + name = "ztracing" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + dependencies = [ + "tracing", + "tracing-subscriber", +@@ -12695,7 +12695,7 @@ dependencies = [ + [[package]] + name = "ztracing_macro" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=4c2af96e84e1cf66696d3542450db75a6dadb8eb#4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" + + [[package]] + name = "zune-core" +diff --git a/Cargo.toml b/Cargo.toml +index 7ea05b4e..019f9f91 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -48,12 +48,12 @@ gpui-wry = { path = "crates/webview", version = "0.6.0" } + gpui-component-shell = { path = "crates/component-shell", version = "0.6.0" } + story = { path = "crates/story" } + +-gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } +-gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } +-gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } +-gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } +-reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } +-sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" } ++gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } ++gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } ++gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } ++gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } ++reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } ++sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } + reqwest = { package = "zed-reqwest", git = "https://github.com/zed-industries/reqwest.git", rev = "33bc764aa15ff7b200bf7c93bd96e24878d53e14", version = "0.12.15-zed", default-features = false, features = [ + "charset", + "http2", +@@ -159,4 +159,4 @@ ignored = ["log", "anyhow", "serde"] + [workspace.metadata.theorem-fork] + upstream = "https://github.com/longbridge/gpui-kit" + upstream_tag = "v0.6.0" +-zed_rev = "4c2af96e84e1cf66696d3542450db75a6dadb8eb" ++zed_rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" +-- +2.53.0 + diff --git a/patches/0015-platform-input-configuration.patch b/patches/0015-platform-input-configuration.patch new file mode 100644 index 0000000000..a5de8ddfe4 --- /dev/null +++ b/patches/0015-platform-input-configuration.patch @@ -0,0 +1,145 @@ +From 41d78525001d551124010a5766dd305c55e60e57 Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Sat, 5 Sep 2026 04:50:34 -0400 +Subject: [PATCH] feat(input): forward platform input configuration + +--- + crates/base/src/input/base/state.rs | 80 ++++++++++++++++++++++++++++- + 1 file changed, 79 insertions(+), 1 deletion(-) + +diff --git a/crates/base/src/input/base/state.rs b/crates/base/src/input/base/state.rs +index ab5397b3..9a1d831b 100644 +--- a/crates/base/src/input/base/state.rs ++++ b/crates/base/src/input/base/state.rs +@@ -2,7 +2,6 @@ + //! + //! Based on the `Input` example from the `gpui` crate. + //! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs +-use gpui::TextAlign; + use gpui::{ + Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler, + EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding, +@@ -10,6 +9,7 @@ use gpui::{ + Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription, + UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _, px, + }; ++use gpui::{TextAlign, TextInputConfiguration}; + use ropey::{Rope, RopeSlice}; + use serde::Deserialize; + use std::borrow::Cow; +@@ -320,6 +320,7 @@ pub struct InputBaseState { + pub(super) masked: bool, + pub(super) clean_on_escape: bool, + pub(super) submit_on_enter: bool, ++ text_input_configuration: TextInputConfiguration, + pub(super) show_whitespaces: bool, + /// This flag tells the renderer to prefer the end of the current visual line. + pub(crate) cursor_line_end_affinity: bool, +@@ -637,6 +638,7 @@ impl InputBaseState { + masked: false, + clean_on_escape: false, + submit_on_enter: false, ++ text_input_configuration: TextInputConfiguration::default(), + show_whitespaces: false, + loading: false, + pattern: None, +@@ -1036,6 +1038,26 @@ impl InputBaseState { + cx.notify(); + } + ++ /// Configure the focused platform input session's assistance and action key. ++ /// This only changes platform presentation; Enter keeps its configured ++ /// input/submit behavior. Defaults remain GPUI's disabled assistance. ++ pub fn input_configuration(mut self, configuration: TextInputConfiguration) -> Self { ++ self.text_input_configuration = configuration; ++ self ++ } ++ ++ /// Update the active input session without replacing its text or selection. ++ pub fn set_input_configuration( ++ &mut self, ++ configuration: TextInputConfiguration, ++ cx: &mut Context, ++ ) { ++ if self.text_input_configuration != configuration { ++ self.text_input_configuration = configuration; ++ cx.notify(); ++ } ++ } ++ + /// Set whether to show whitespace characters. + #[doc(hidden)] + pub fn show_whitespaces(mut self, show: bool) -> Self { +@@ -2686,6 +2708,14 @@ impl InputBaseState { + } + + impl EntityInputHandler for InputBaseState { ++ fn text_input_configuration( ++ &mut self, ++ _window: &mut Window, ++ _cx: &mut Context, ++ ) -> TextInputConfiguration { ++ self.text_input_configuration.clone() ++ } ++ + fn text_for_range( + &mut self, + range_utf16: Range, +@@ -3275,6 +3305,54 @@ mod tests { + } + } + ++ #[gpui::test] ++ fn platform_input_configuration_preserves_default_and_forwards_changes( ++ cx: &mut TestAppContext, ++ ) { ++ cx.update(crate::init); ++ let plain = InputView::build(cx, |state| state); ++ plain ++ .window_handle ++ .update(cx, |_, window, cx| { ++ plain.input.update(cx, |state, cx| { ++ assert_eq!( ++ state.text_input_configuration(window, cx), ++ TextInputConfiguration::default() ++ ); ++ }); ++ }) ++ .unwrap(); ++ let search = InputView::build(cx, |state| { ++ state.input_configuration(TextInputConfiguration { ++ input_action: gpui::TextInputAction::Search, ++ ..Default::default() ++ }) ++ }); ++ search ++ .window_handle ++ .update(cx, |_, window, cx| { ++ search.input.update(cx, |state, cx| { ++ assert_eq!( ++ state.text_input_configuration(window, cx).input_action, ++ gpui::TextInputAction::Search ++ ); ++ state.set_value("retained draft", window, cx); ++ state.set_selected_range(2..5, cx); ++ let changed = TextInputConfiguration { ++ input_action: gpui::TextInputAction::Send, ++ autocorrect: true, ++ suggestions: true, ++ autocapitalize: gpui::Autocapitalize::Sentences, ++ }; ++ state.set_input_configuration(changed.clone(), cx); ++ assert_eq!(state.text_input_configuration(window, cx), changed); ++ assert_eq!(state.text().to_string(), "retained draft"); ++ assert_eq!(state.selected_range(), 2..5); ++ }); ++ }) ++ .unwrap(); ++ } ++ + #[gpui::test] + fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) { + cx.update(crate::init); +-- +2.53.0 + diff --git a/patches/0016-patch-spelling-identifiers.patch b/patches/0016-patch-spelling-identifiers.patch new file mode 100644 index 0000000000..dcf716ab05 --- /dev/null +++ b/patches/0016-patch-spelling-identifiers.patch @@ -0,0 +1,29 @@ +From a3292163a868b4c81514e658c1d3c06edfd0927b Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Sat, 5 Sep 2026 05:10:49 -0400 +Subject: [PATCH] fix(ci): recognize Git patch hashes and capture package names + +--- + _typos.toml | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/_typos.toml b/_typos.toml +index 3e996300..e9abe518 100644 +--- a/_typos.toml ++++ b/_typos.toml +@@ -1,5 +1,11 @@ + [default] +-extend-ignore-re = ['\\u\{[0-9A-Fa-f]+\}'] ++extend-ignore-re = [ ++ '\\u\{[0-9A-Fa-f]+\}', ++ # Ordered Git patch object IDs are immutable hex, not prose. ++ '(?m)^index [0-9a-f]+\.\.[0-9a-f]+(?: [0-7]+)?$', ++ # The exact screen-capture package identifier in the dependency patches. ++ '\bzed-scap\b', ++] + + [default.extend-words] + # Correct Italian singular of "command". +-- +2.53.0 + diff --git a/patches/0017-patch-pattern-spelling.patch b/patches/0017-patch-pattern-spelling.patch new file mode 100644 index 0000000000..03e6498535 --- /dev/null +++ b/patches/0017-patch-pattern-spelling.patch @@ -0,0 +1,26 @@ +From bd326291bb967274acd50cd6ece9935b93172463 Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Sat, 5 Sep 2026 05:23:00 -0400 +Subject: [PATCH] fix(ci): recognize the capture name inside its own patch + pattern + +--- + _typos.toml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/_typos.toml b/_typos.toml +index e9abe518..78a0e762 100644 +--- a/_typos.toml ++++ b/_typos.toml +@@ -4,7 +4,7 @@ extend-ignore-re = [ + # Ordered Git patch object IDs are immutable hex, not prose. + '(?m)^index [0-9a-f]+\.\.[0-9a-f]+(?: [0-7]+)?$', + # The exact screen-capture package identifier in the dependency patches. +- '\bzed-scap\b', ++ 'zed-scap', + ] + + [default.extend-words] +-- +2.53.0 + diff --git a/patches/0018-textarea-presentation-styles.patch b/patches/0018-textarea-presentation-styles.patch new file mode 100644 index 0000000000..e06efc088f --- /dev/null +++ b/patches/0018-textarea-presentation-styles.patch @@ -0,0 +1,205 @@ +diff --git a/crates/base/src/input/base/kind.rs b/crates/base/src/input/base/kind.rs +index 26c28d53..393b49bd 100644 +--- a/crates/base/src/input/base/kind.rs ++++ b/crates/base/src/input/base/kind.rs +@@ -313,12 +313,6 @@ impl InputModeKind for InputMode { + /// them would cost more in dispatch than it saves. + type Extras = (); + } +-impl InputModeKind for TextareaMode { +- const MULTI_LINE: bool = true; +- +- /// Ordinary multi-line text needs nothing beyond the shared engine. +- type Extras = (); +-} + // `EditorMode`'s implementation lives with the editor code, next to the + // language features it dispatches to. + +diff --git a/crates/base/src/input/editor/decorations.rs b/crates/base/src/input/editor/decorations.rs +index dc4b393c..231546fc 100644 +--- a/crates/base/src/input/editor/decorations.rs ++++ b/crates/base/src/input/editor/decorations.rs +@@ -158,7 +158,7 @@ impl DecorationCollections { + } + } + +-fn adjust_range_for_edit( ++pub(crate) fn adjust_range_for_edit( + range: &Range, + edited_range: &Range, + inserted_len: usize, +@@ -204,7 +204,7 @@ fn adjust_range_for_edit( + start..end + } + +-fn normalize(text: &Rope, decorations: Vec) -> Vec { ++pub(crate) fn normalize(text: &Rope, decorations: Vec) -> Vec { + decorations + .into_iter() + .filter_map(|decoration| { +diff --git a/crates/base/src/input/editor/mod.rs b/crates/base/src/input/editor/mod.rs +index d4e16e3d..ce32baae 100644 +--- a/crates/base/src/input/editor/mod.rs ++++ b/crates/base/src/input/editor/mod.rs +@@ -5,7 +5,7 @@ use super::{EditorMode, InputBaseState, InputModeKind}; + /// State for source-code editing. + /// + /// This is the shared editing engine in its code-editor kind. Languages, line +-/// numbers, folding, indent guides, diagnostics, decorations, and the LSP ++/// numbers, folding, indent guides, diagnostics, and the LSP + /// providers exist on this kind only, so an ordinary input or textarea never + /// exposes them. + pub type EditorState = InputBaseState; +diff --git a/crates/base/src/input/mod.rs b/crates/base/src/input/mod.rs +index e8d9c2b2..0a4ddeb2 100644 +--- a/crates/base/src/input/mod.rs ++++ b/crates/base/src/input/mod.rs +@@ -96,4 +96,4 @@ pub use rope_ext::{InputEdit, Point, RopeExt, RopeLines}; + pub use ropey::Rope; + pub use search::{SearchMatcher, SearchSession}; + pub use state::*; +-pub use textarea::{Textarea, TextareaState}; ++pub use textarea::{Textarea, TextareaExtras, TextareaState}; +diff --git a/crates/base/src/input/textarea/mod.rs b/crates/base/src/input/textarea/mod.rs +index 341d108e..57e32e58 100644 +--- a/crates/base/src/input/textarea/mod.rs ++++ b/crates/base/src/input/textarea/mod.rs +@@ -1,6 +1,6 @@ +-use gpui::{App, Entity, IntoElement, RenderOnce, Window}; ++use gpui::{App, Context, Entity, IntoElement, RenderOnce, Window}; + +-use super::{InputBaseState, TextareaMode}; ++use super::{InputBaseState, InputExtras, InputModeKind, TextDecoration, TextareaMode}; + + /// State for editing ordinary multi-line text. + /// +@@ -28,3 +28,101 @@ impl RenderOnce for Textarea { + self.state + } + } ++ ++/// Presentation-only ranges for ordinary multiline prose. Empty by default; ++/// this does not add syntax parsing, diagnostics, folding, or an LSP. ++#[derive(Default)] ++pub struct TextareaExtras { ++ decorations: Vec, ++} ++ ++impl InputExtras for TextareaExtras { ++ fn decoration_layers(&self) -> Vec<&[TextDecoration]> { ++ vec![&self.decorations] ++ } ++} ++ ++impl InputModeKind for TextareaMode { ++ const MULTI_LINE: bool = true; ++ type Extras = TextareaExtras; ++ ++ fn reset_annotations(state: &mut InputBaseState) { ++ state.extras.decorations.clear(); ++ } ++ ++ fn adjust_annotations( ++ state: &mut InputBaseState, ++ range: &std::ops::Range, ++ new_len: usize, ++ ) { ++ state.extras.decorations.retain_mut(|decoration| { ++ decoration.range = ++ super::decorations::adjust_range_for_edit(&decoration.range, range, new_len); ++ !decoration.range.is_empty() ++ }); ++ } ++} ++ ++impl TextareaState { ++ /// Replace presentation styles without replacing text, selection, or IME ++ /// marked text. UTF-8 ranges use the same clipping and edit adjustment as ++ /// editor decorations. Applications retain authority over the styles. ++ pub fn set_text_decorations( ++ &mut self, ++ decorations: Vec, ++ cx: &mut Context, ++ ) { ++ let decorations = super::decorations::normalize(&self.text, decorations); ++ if self.extras.decorations != decorations { ++ self.extras.decorations = decorations; ++ cx.notify(); ++ } ++ } ++ ++ /// The normalized presentation styles currently painted by this textarea. ++ pub fn text_decorations(&self) -> &[TextDecoration] { ++ &self.extras.decorations ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use gpui::{AppContext as _, EntityInputHandler as _, HighlightStyle, TestAppContext}; ++ ++ #[gpui::test] ++ fn textarea_decorations_preserve_text_selection_and_composition(cx: &mut TestAppContext) { ++ let window = cx.add_window(|window, cx| TextareaState::new(window, cx)); ++ window ++ .update(cx, |state, window, cx| { ++ assert!(state.text_decorations().is_empty()); ++ assert!(!state.is_code_editor()); ++ state.set_value("héllo world", window, cx); ++ state.set_selected_range(1..4, cx); ++ let value = state.value(); ++ let selection = state.selected_range(); ++ let bold = HighlightStyle { ++ font_weight: Some(gpui::FontWeight::BOLD), ++ ..Default::default() ++ }; ++ state.set_text_decorations(vec![TextDecoration::new(2..4, bold)], cx); ++ assert_eq!(state.value(), value); ++ assert_eq!(state.selected_range(), selection); ++ assert_eq!(state.text_decorations(), &[TextDecoration::new(1..4, bold)]); ++ state.set_selected_range(0..0, cx); ++ state.replace_and_mark_text_in_range(None, "界", Some(1..1), window, cx); ++ let marked = state.marked_text_range(window, cx); ++ let value = state.value(); ++ let selection = state.selected_range(); ++ assert!(marked.is_some()); ++ assert_eq!(state.text_decorations()[0].range, 4..7); ++ state.set_text_decorations(vec![TextDecoration::new(4..7, bold)], cx); ++ assert_eq!(state.value(), value); ++ assert_eq!(state.selected_range(), selection); ++ assert_eq!(state.marked_text_range(window, cx), marked); ++ state.set_value("replacement", window, cx); ++ assert!(state.text_decorations().is_empty()); ++ }) ++ .expect("textarea remains mounted"); ++ } ++} +diff --git a/crates/component/src/status_bar.rs b/crates/component/src/status_bar.rs +index 44dff594..1cfe3306 100644 +--- a/crates/component/src/status_bar.rs ++++ b/crates/component/src/status_bar.rs +@@ -23,7 +23,7 @@ use crate::{ActiveTheme, StyledExt, h_flex}; + /// otherwise (only `right`, or neither — like a plain container). + /// + /// ``` +-/// use gpui_kit::component::status_bar::StatusBar; ++/// use gpui_component::status_bar::StatusBar; + /// + /// let _ = StatusBar::new().left("Ln 1, Col 1").right("UTF-8"); + /// ``` +diff --git a/crates/component/src/title_bar.rs b/crates/component/src/title_bar.rs +index 45b091bb..32254a47 100644 +--- a/crates/component/src/title_bar.rs ++++ b/crates/component/src/title_bar.rs +@@ -70,8 +70,8 @@ impl TitleBar { + /// [`crate::TitleBar`], so the title bar owns dragging and double clicking itself: + /// + /// ```no_run +- /// # use gpui_kit::WindowOptions; +- /// # use gpui_kit::component::TitleBar; ++ /// # use gpui::WindowOptions; ++ /// # use gpui_component::TitleBar; + /// let options = WindowOptions { + /// window_min_size: None, + /// ..TitleBar::window_options() diff --git a/patches/0019-textarea-test-initialization.patch b/patches/0019-textarea-test-initialization.patch new file mode 100644 index 0000000000..7d0668403f --- /dev/null +++ b/patches/0019-textarea-test-initialization.patch @@ -0,0 +1,17 @@ +diff --git a/crates/base/src/input/textarea/mod.rs b/crates/base/src/input/textarea/mod.rs +index 57e32e58..371d33a2 100644 +--- a/crates/base/src/input/textarea/mod.rs ++++ b/crates/base/src/input/textarea/mod.rs +@@ -88,10 +88,11 @@ impl TextareaState { + #[cfg(test)] + mod tests { + use super::*; +- use gpui::{AppContext as _, EntityInputHandler as _, HighlightStyle, TestAppContext}; ++ use gpui::{EntityInputHandler as _, HighlightStyle, TestAppContext}; + + #[gpui::test] + fn textarea_decorations_preserve_text_selection_and_composition(cx: &mut TestAppContext) { ++ cx.update(crate::init); + let window = cx.add_window(|window, cx| TextareaState::new(window, cx)); + window + .update(cx, |state, window, cx| { diff --git a/patches/0020-textarea-replacement-presentation.patch b/patches/0020-textarea-replacement-presentation.patch new file mode 100644 index 0000000000..3315ea499c --- /dev/null +++ b/patches/0020-textarea-replacement-presentation.patch @@ -0,0 +1,42 @@ +diff --git a/crates/base/src/input/base/kind.rs b/crates/base/src/input/base/kind.rs +index 393b49bd..6637e800 100644 +--- a/crates/base/src/input/base/kind.rs ++++ b/crates/base/src/input/base/kind.rs +@@ -185,6 +185,10 @@ pub trait InputModeKind: sealed::Sealed + Sized + 'static { + /// Drops decorations and hover state when the text is replaced wholesale. + fn reset_annotations(_state: &mut InputBaseState) {} + ++ /// Drops presentation ranges after an authoritative whole-text replacement. ++ /// Existing mode behavior is unchanged unless a mode opts into this hook. ++ fn reset_document_presentation(_state: &mut InputBaseState) {} ++ + /// Slides decoration ranges along with an edit. + fn adjust_annotations( + _state: &mut InputBaseState, +diff --git a/crates/base/src/input/base/state.rs b/crates/base/src/input/base/state.rs +index 9a1d831b..2c776da4 100644 +--- a/crates/base/src/input/base/state.rs ++++ b/crates/base/src/input/base/state.rs +@@ -929,6 +929,7 @@ impl InputBaseState { + this.undo_manager.pending_intent = Some(EditIntent::Atomic); + let range = 0..this.text.chars().map(|c| c.len_utf16()).sum(); + this.replace_text_in_range_silent(Some(range), &text, window, cx); ++ M::reset_document_presentation(this); + this.reset_highlighter(cx); + }); + } +diff --git a/crates/base/src/input/textarea/mod.rs b/crates/base/src/input/textarea/mod.rs +index 371d33a2..79299b20 100644 +--- a/crates/base/src/input/textarea/mod.rs ++++ b/crates/base/src/input/textarea/mod.rs +@@ -50,6 +50,10 @@ impl InputModeKind for TextareaMode { + state.extras.decorations.clear(); + } + ++ fn reset_document_presentation(state: &mut InputBaseState) { ++ state.extras.decorations.clear(); ++ } ++ + fn adjust_annotations( + state: &mut InputBaseState, + range: &std::ops::Range, diff --git a/patches/0021-gpui-selected-replacement-pin.patch b/patches/0021-gpui-selected-replacement-pin.patch new file mode 100644 index 0000000000..e39b2b55c7 --- /dev/null +++ b/patches/0021-gpui-selected-replacement-pin.patch @@ -0,0 +1,41 @@ +From abf772c47825061b7099ac0d8eb62e4a930d4f8e Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Sat, 5 Sep 2026 06:30:03 -0400 +Subject: [PATCH] fix(gpui): pin browser selected replacement repair + +--- + Cargo.toml | 14 +++++++------- + 1 file changed, 7 insertions(+), 7 deletions(-) + +diff --git a/Cargo.toml b/Cargo.toml +index 019f9f91..401efdc6 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -48,12 +48,12 @@ gpui-wry = { path = "crates/webview", version = "0.6.0" } + gpui-component-shell = { path = "crates/component-shell", version = "0.6.0" } + story = { path = "crates/story" } + +-gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } +-gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } +-gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } +-gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } +-reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } +-sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" } ++gpui = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } ++gpui_platform = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1", features = ["font-kit", "x11", "wayland", "runtime_shaders"] } ++gpui_web = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } ++gpui_macros = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } ++reqwest_client = { git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } ++sum-tree = { package = "sum_tree", git = "https://github.com/Travis-Gilbert/zed", rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" } + reqwest = { package = "zed-reqwest", git = "https://github.com/zed-industries/reqwest.git", rev = "33bc764aa15ff7b200bf7c93bd96e24878d53e14", version = "0.12.15-zed", default-features = false, features = [ + "charset", + "http2", +@@ -159,4 +159,4 @@ ignored = ["log", "anyhow", "serde"] + [workspace.metadata.theorem-fork] + upstream = "https://github.com/longbridge/gpui-kit" + upstream_tag = "v0.6.0" +-zed_rev = "2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++zed_rev = "de1d65ee70ac21a1ef02a500151a96d96ff937e1" +-- +2.53.0 + diff --git a/patches/0022-gpui-selected-replacement-lock.patch b/patches/0022-gpui-selected-replacement-lock.patch new file mode 100644 index 0000000000..ff1321e86e --- /dev/null +++ b/patches/0022-gpui-selected-replacement-lock.patch @@ -0,0 +1,241 @@ +From 2fed4e7bd5192d93de116e8f5701f40671e895e0 Mon Sep 17 00:00:00 2001 +From: Travis Gilbert <1travisgilbert@gmail.com> +Date: Sat, 5 Sep 2026 06:30:38 -0400 +Subject: [PATCH] fix(gpui): synchronize selected replacement lock sources + +--- + Cargo.lock | 50 +++++++++++++++++++++++++------------------------- + 1 file changed, 25 insertions(+), 25 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 3b939a04..e889dabd 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -1388,7 +1388,7 @@ dependencies = [ + [[package]] + name = "collections" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "gpui_util", + "indexmap", +@@ -2086,7 +2086,7 @@ dependencies = [ + [[package]] + name = "derive_refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "proc-macro2", + "quote", +@@ -3376,7 +3376,7 @@ dependencies = [ + [[package]] + name = "gpui" + version = "0.2.2" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "accesskit", + "anyhow", +@@ -3728,7 +3728,7 @@ dependencies = [ + [[package]] + name = "gpui_apple" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "block", +@@ -3751,7 +3751,7 @@ dependencies = [ + [[package]] + name = "gpui_linux" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "accesskit", + "accesskit_unix", +@@ -3797,7 +3797,7 @@ dependencies = [ + [[package]] + name = "gpui_macos" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "accesskit", + "accesskit_macos", +@@ -3843,7 +3843,7 @@ dependencies = [ + [[package]] + name = "gpui_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "heck 0.5.0", + "proc-macro2", +@@ -3854,7 +3854,7 @@ dependencies = [ + [[package]] + name = "gpui_platform" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "console_error_panic_hook", + "gpui", +@@ -3867,7 +3867,7 @@ dependencies = [ + [[package]] + name = "gpui_shared_string" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "schemars", + "serde", +@@ -3877,7 +3877,7 @@ dependencies = [ + [[package]] + name = "gpui_util" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "log", +@@ -3887,7 +3887,7 @@ dependencies = [ + [[package]] + name = "gpui_web" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "async-channel", +@@ -3912,7 +3912,7 @@ dependencies = [ + [[package]] + name = "gpui_wgpu" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "bytemuck", +@@ -3938,7 +3938,7 @@ dependencies = [ + [[package]] + name = "gpui_windows" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "accesskit", + "accesskit_windows", +@@ -4300,7 +4300,7 @@ dependencies = [ + [[package]] + name = "http_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "async-compression", +@@ -4320,7 +4320,7 @@ dependencies = [ + [[package]] + name = "http_client_tls" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "log", + "rustls", +@@ -5676,7 +5676,7 @@ dependencies = [ + [[package]] + name = "media" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "bindgen", +@@ -6746,7 +6746,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] + name = "perf" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "collections", + "serde", +@@ -7828,7 +7828,7 @@ dependencies = [ + [[package]] + name = "refineable" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "derive_refineable", + ] +@@ -7889,7 +7889,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] + name = "reqwest_client" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "bytes", +@@ -8398,7 +8398,7 @@ dependencies = [ + [[package]] + name = "scheduler" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "async-task", + "backtrace", +@@ -9082,7 +9082,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] + name = "sum_tree" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "heapless", + "log", +@@ -10610,7 +10610,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] + name = "util_macros" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "perf", + "quote", +@@ -12639,7 +12639,7 @@ dependencies = [ + [[package]] + name = "zlog" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "anyhow", + "chrono", +@@ -12684,7 +12684,7 @@ dependencies = [ + [[package]] + name = "ztracing" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + dependencies = [ + "tracing", + "tracing-subscriber", +@@ -12695,7 +12695,7 @@ dependencies = [ + [[package]] + name = "ztracing_macro" + version = "0.1.0" +-source = "git+https://github.com/Travis-Gilbert/zed?rev=2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4#2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4" ++source = "git+https://github.com/Travis-Gilbert/zed?rev=de1d65ee70ac21a1ef02a500151a96d96ff937e1#de1d65ee70ac21a1ef02a500151a96d96ff937e1" + + [[package]] + name = "zune-core" +-- +2.53.0 + diff --git a/patches/0023-sidebar-semantic-decoration.patch b/patches/0023-sidebar-semantic-decoration.patch new file mode 100644 index 0000000000..99ae39d716 --- /dev/null +++ b/patches/0023-sidebar-semantic-decoration.patch @@ -0,0 +1,105 @@ +diff --git a/crates/component/src/sidebar/menu.rs b/crates/component/src/sidebar/menu.rs +index cbaa60ca..7ca8ca05 100644 +--- a/crates/component/src/sidebar/menu.rs ++++ b/crates/component/src/sidebar/menu.rs +@@ -8,9 +8,9 @@ use crate::{ + v_flex, + }; + use gpui::{ +- AnyElement, App, ClickEvent, ElementId, InteractiveElement as _, IntoElement, +- ParentElement as _, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, +- Window, div, percentage, prelude::FluentBuilder, ++ AnyElement, App, ClickEvent, Div, ElementId, InteractiveElement as _, IntoElement, ++ ParentElement as _, SharedString, Stateful, StatefulInteractiveElement as _, StyleRefinement, ++ Styled, Window, div, percentage, prelude::FluentBuilder, + }; + use std::rc::Rc; + +@@ -101,6 +101,7 @@ pub struct SidebarMenuItem { + click_to_toggle: bool, + children: Vec, + suffix: Option AnyElement + 'static>>, ++ item_with: Option) -> Stateful
>>, + disabled: bool, + context_menu: Option PopupMenu + 'static>>, + } +@@ -119,6 +120,7 @@ impl SidebarMenuItem { + click_to_toggle: false, + children: Vec::new(), + suffix: None, ++ item_with: None, + disabled: false, + context_menu: None, + } +@@ -196,6 +198,19 @@ impl SidebarMenuItem { + self + } + ++ /// Decorate the actual clickable row, excluding its expanded submenu. ++ /// ++ /// The existing click, open, hover and tooltip behavior is retained. This ++ /// hook supports accessibility and measured-row adapters without adding a ++ /// second interactive wrapper. It runs before the optional context menu. ++ pub fn item_with( ++ mut self, ++ decorate: impl Fn(Stateful
) -> Stateful
+ 'static, ++ ) -> Self { ++ self.item_with = Some(Rc::new(decorate)); ++ self ++ } ++ + /// Set disabled flat for menu item. + pub fn disable(mut self, disable: bool) -> Self { + self.disabled = disable; +@@ -369,6 +384,10 @@ impl SidebarItem for SidebarMenuItem { + this + } + }) ++ .map(|this| match self.item_with { ++ Some(decorate) => decorate(this), ++ None => this, ++ }) + .map(|this| { + if let Some(context_menu) = self.context_menu { + this.context_menu(move |menu, window, cx| { +diff --git a/crates/component/src/sidebar/mod.rs b/crates/component/src/sidebar/mod.rs +index 09c686bd..95a4d3d3 100644 +--- a/crates/component/src/sidebar/mod.rs ++++ b/crates/component/src/sidebar/mod.rs +@@ -305,6 +305,7 @@ pub struct SidebarToggleButton { + collapsed: bool, + side: Side, + on_click: Option>, ++ button_with: Option Button>>, + } + + impl SidebarToggleButton { +@@ -315,9 +316,17 @@ impl SidebarToggleButton { + collapsed: false, + side: Side::Left, + on_click: None, ++ button_with: None, + } + } + ++ /// Decorate the retained button after its icon, label and click handler ++ /// have been bound. The default leaves the existing button unchanged. ++ pub fn button_with(mut self, decorate: impl FnOnce(Button) -> Button + 'static) -> Self { ++ self.button_with = Some(Box::new(decorate)); ++ self ++ } ++ + /// Set the side of the toggle button. + /// + /// Default is `Side::Left`. +@@ -372,6 +381,10 @@ impl RenderOnce for SidebarToggleButton { + false => t!("Sidebar.Collapse"), + }) + .icon(Icon::new(icon).size_4()) ++ .map(|button| match self.button_with { ++ Some(decorate) => decorate(button), ++ None => button, ++ }) + } + } + diff --git a/patches/0024-compose-sidebar-row-decorations.patch b/patches/0024-compose-sidebar-row-decorations.patch new file mode 100644 index 0000000000..ae104b13eb --- /dev/null +++ b/patches/0024-compose-sidebar-row-decorations.patch @@ -0,0 +1,30 @@ +diff --git a/crates/component/src/sidebar/menu.rs b/crates/component/src/sidebar/menu.rs +index 7ca8ca05..586ccd2b 100644 +--- a/crates/component/src/sidebar/menu.rs ++++ b/crates/component/src/sidebar/menu.rs +@@ -200,14 +200,21 @@ impl SidebarMenuItem { + + /// Decorate the actual clickable row, excluding its expanded submenu. + /// +- /// The existing click, open, hover and tooltip behavior is retained. This +- /// hook supports accessibility and measured-row adapters without adding a +- /// second interactive wrapper. It runs before the optional context menu. ++ /// The existing click, open, hover and tooltip behavior is retained. ++ /// Repeated hooks compose in call order, supporting accessibility and ++ /// measured-row adapters without a second interactive wrapper. They run ++ /// before the optional context menu. + pub fn item_with( + mut self, + decorate: impl Fn(Stateful
) -> Stateful
+ 'static, + ) -> Self { +- self.item_with = Some(Rc::new(decorate)); ++ let previous = self.item_with.take(); ++ self.item_with = Some(Rc::new(move |row| { ++ decorate(match &previous { ++ Some(previous) => previous(row), ++ None => row, ++ }) ++ })); + self + } + diff --git a/patches/0025-measured-text-link-decoration.patch b/patches/0025-measured-text-link-decoration.patch new file mode 100644 index 0000000000..d911af6a74 --- /dev/null +++ b/patches/0025-measured-text-link-decoration.patch @@ -0,0 +1,723 @@ +diff --git a/crates/base/src/text/inline.rs b/crates/base/src/text/inline.rs +index 09abe59c..c0711f85 100644 +--- a/crates/base/src/text/inline.rs ++++ b/crates/base/src/text/inline.rs +@@ -1,15 +1,17 @@ + use gpui::Corners; + use std::{ ++ collections::BTreeMap, + ops::Range, + rc::Rc, + sync::{Arc, Mutex}, + }; + + use gpui::{ +- App, BorderStyle, Bounds, ClickEvent, CursorStyle, Edges, Element, ElementId, GlobalElementId, +- Half, HighlightStyle, Hitbox, HitboxBehavior, InspectorElementId, IntoElement, LayoutId, +- MouseButton, MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, +- SharedString, StyledText, TextLayout, Window, point, px, quad, ++ AnyElement, App, AvailableSpace, BorderStyle, Bounds, ClickEvent, CursorStyle, Edges, Element, ++ ElementId, GlobalElementId, Half, HighlightStyle, Hitbox, HitboxBehavior, InspectorElementId, ++ InteractiveElement as _, IntoElement, LayoutId, MouseButton, MouseClickEvent, MouseDownEvent, ++ MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, StatefulInteractiveElement as _, ++ Styled as _, StyledText, TextLayout, Window, div, point, px, quad, size, + }; + + use crate::{ +@@ -19,7 +21,10 @@ use crate::{ + text::node::LinkMark, + text::selection::word_range_at, + text::state::LineSpan, +- text::text_view::{LinkClickHandlerFn, handle_link_click}, ++ text::text_view::{ ++ LinkClickHandlerFn, LinkFragment, LinkFragmentDecoratorFn, LinkUnderline, LinkUnderlineFn, ++ handle_link_click, ++ }, + }; + + /// A inline element used to render a inline text and support selectable. +@@ -32,6 +37,9 @@ pub(super) struct Inline { + highlights: Vec<(Range, HighlightStyle)>, + styled_text: StyledText, + link_click_handler: Option>, ++ link_fragment_decorator: Option>, ++ link_source_offset: usize, ++ link_underline: Option>, + + state: Arc>, + } +@@ -72,10 +80,132 @@ impl Inline { + text: text.clone(), + styled_text: StyledText::new(text), + link_click_handler, ++ link_fragment_decorator: None, ++ link_source_offset: 0, ++ link_underline: None, + state, + } + } + ++ pub(super) fn link_with( ++ mut self, ++ decorator: Option>, ++ source_offset: usize, ++ ) -> Self { ++ self.link_fragment_decorator = decorator; ++ self.link_source_offset = source_offset; ++ self ++ } ++ ++ pub(super) fn link_underline(mut self, style: Option>) -> Self { ++ self.link_underline = style; ++ self ++ } ++ ++ fn link_elements( ++ &self, ++ global_id: Option<&GlobalElementId>, ++ window: &mut Window, ++ cx: &mut App, ++ ) -> Vec { ++ let Some(decorator) = &self.link_fragment_decorator else { ++ return Vec::new(); ++ }; ++ let mut focus_handles = window ++ .with_element_state::, _>( ++ global_id.expect("Inline has a stable id"), ++ |retained, _| { ++ let state = retained.unwrap_or_default(); ++ (state.clone(), state) ++ }, ++ ); ++ let mut retained_ids = Vec::new(); ++ let layout = self.styled_text.layout(); ++ let mut elements = Vec::new(); ++ for (link_ix, (range, link)) in self.links.iter().enumerate() { ++ for (part_ix, (fragment_range, bounds)) in link_fragment_bounds(&layout, range.clone()) ++ .into_iter() ++ .enumerate() ++ { ++ let fragment = LinkFragment { ++ id: format!( ++ "link-{}-{:?}-{link_ix}-{part_ix}", ++ self.link_source_offset, self.id ++ ) ++ .into(), ++ url: link.url.clone(), ++ text: self.text[fragment_range].to_string().into(), ++ }; ++ let focus = focus_handles ++ .entry(fragment.id.clone()) ++ .or_insert_with(|| cx.focus_handle()) ++ .clone(); ++ retained_ids.push(fragment.id.clone()); ++ let click_focus = focus.clone(); ++ let handler = self.link_click_handler.clone(); ++ let url = link.url.clone(); ++ let text_view_state = GlobalState::global(cx).text_view_state().cloned(); ++ let click = move |event: &ClickEvent, window: &mut Window, cx: &mut App| { ++ if !matches!(event, ClickEvent::Keyboard(_)) ++ && text_view_state ++ .as_ref() ++ .is_some_and(|state| state.read(cx).has_selection(cx)) ++ { ++ return; ++ } ++ TextSelection::end(window, cx); ++ // The selection layer focuses its text participant on ++ // mouse down. A completed link click focuses the actual ++ // fragment, while a drag keeps the text selection focus. ++ click_focus.focus(window, cx); ++ // Selection queues its focus callback on mouse down. A ++ // semantic press can deliver down and up in one update, ++ // so that queued callback would otherwise run after this ++ // completed click and take focus back from the link. ++ // Correct only that participant's focus; navigation may ++ // have intentionally focused a different control. ++ if let Some(state) = &text_view_state { ++ let selection_focus = state.read(cx).focus_handle().clone(); ++ let completed_focus = click_focus.clone(); ++ window.defer(cx, move |window, cx| { ++ if selection_focus.is_focused(window) { ++ completed_focus.focus(window, cx); ++ } ++ }); ++ } ++ cx.stop_propagation(); ++ handle_link_click(&handler, url.clone(), event.clone(), window, cx); ++ }; ++ let target = div() ++ .id(fragment.id.clone()) ++ .tab_index(0) ++ .track_focus(&focus) ++ .w(bounds.size.width) ++ .h(bounds.size.height) ++ .cursor_pointer() ++ .on_click(click.clone()) ++ .on_aux_click(click); ++ let mut target = decorator(&fragment, target, window, cx).into_any_element(); ++ target.prepaint_as_root( ++ bounds.origin, ++ size( ++ AvailableSpace::Definite(bounds.size.width), ++ AvailableSpace::Definite(bounds.size.height), ++ ), ++ window, ++ cx, ++ ); ++ elements.push(target); ++ } ++ } ++ focus_handles.retain(|id, _| retained_ids.contains(id)); ++ window.with_element_state::, _>( ++ global_id.expect("Inline has a stable id"), ++ |_, _| ((), focus_handles), ++ ); ++ elements ++ } ++ + /// Get link at given mouse position. + fn link_for_position( + layout: &TextLayout, +@@ -338,7 +468,7 @@ impl IntoElement for Inline { + + impl Element for Inline { + type RequestLayoutState = (); +- type PrepaintState = Hitbox; ++ type PrepaintState = (Hitbox, Vec); + + fn id(&self) -> Option { + Some(self.id.clone()) +@@ -357,9 +487,34 @@ impl Element for Inline { + ) -> (LayoutId, Self::RequestLayoutState) { + let text_style = window.text_style(); + ++ let overrides = self ++ .links ++ .iter() ++ .filter_map(|(range, link)| { ++ let style = self ++ .link_underline ++ .as_ref() ++ .map(|style| style(&link.url, cx)) ++ .unwrap_or_default(); ++ (style != LinkUnderline::Solid).then(|| { ++ ( ++ range.clone(), ++ HighlightStyle { ++ underline: Some(gpui::UnderlineStyle { ++ thickness: px(0.), ++ ..Default::default() ++ }), ++ ..Default::default() ++ }, ++ ) ++ }) ++ }) ++ .collect::>(); ++ let highlights = ++ gpui::combine_highlights(self.highlights.clone(), overrides).collect::>(); + let mut runs = Vec::new(); + let mut ix = 0; +- for (range, highlight) in self.highlights.iter() { ++ for (range, highlight) in &highlights { + if ix < range.start { + runs.push(text_style.clone().to_run(range.start - ix)); + } +@@ -408,7 +563,7 @@ impl Element for Inline { + } + + let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal); +- hitbox ++ (hitbox, self.link_elements(id, window, cx)) + } + + fn paint( +@@ -422,7 +577,7 @@ impl Element for Inline { + cx: &mut App, + ) { + let current_view = window.current_view(); +- let hitbox = prepaint; ++ let (hitbox, link_elements) = prepaint; + let Ok(mut state) = self.state.lock() else { + return; + }; +@@ -431,6 +586,31 @@ impl Element for Inline { + self.styled_text + .paint(global_id, None, bounds, &mut (), &mut (), window, cx); + ++ for (range, link) in self.links.iter() { ++ if self ++ .link_underline ++ .as_ref() ++ .map(|style| style(&link.url, cx)) ++ != Some(LinkUnderline::Dotted) ++ { ++ continue; ++ } ++ let color = self ++ .highlights ++ .iter() ++ .find_map(|(span, style)| { ++ (span.start <= range.start && range.start < span.end) ++ .then_some(style.color) ++ .flatten() ++ }) ++ .unwrap_or(window.text_style().color); ++ for (_, fragment) in link_fragment_bounds(&text_layout, range.clone()) { ++ for dot in dotted_underline_bounds(fragment) { ++ window.paint_quad(gpui::fill(dot, color)); ++ } ++ } ++ } ++ + // layout selections + let (is_selectable, is_selection, selection) = + self.layout_selections(&text_layout, &bounds, window, cx); +@@ -541,7 +721,7 @@ impl Element for Inline { + } + }); + +- if !is_selection { ++ if !is_selection && self.link_fragment_decorator.is_none() { + // click to open link + window.on_mouse_event({ + let links = self.links.clone(); +@@ -581,7 +761,73 @@ impl Element for Inline { + } + }); + } ++ // These are the actual link targets, positioned by the same glyph ++ // layout painted above. Their click handlers replace the legacy hit ++ // test only when the caller requested a decorator. ++ for element in link_elements { ++ element.paint(window, cx); ++ } ++ } ++} ++ ++/// Split at actual hard/soft line boundaries. Widths come from shaped glyph ++/// advances, including the final glyph before a wrap, never an estimated font ++/// width or a union spanning multiple lines. ++fn link_fragment_bounds( ++ layout: &TextLayout, ++ range: Range, ++) -> Vec<(Range, Bounds)> { ++ let mut fragments = Vec::new(); ++ let mut line_start = 0; ++ let mut line_y = layout.bounds().top(); ++ for line in layout.line_layouts() { ++ let boundaries = std::iter::once(0) ++ .chain(line.wrap_boundaries.iter().map(|boundary| { ++ line.unwrapped_layout.runs[boundary.run_ix].glyphs[boundary.glyph_ix].index ++ })) ++ .chain(std::iter::once(line.len())) ++ .collect::>(); ++ for (row_ix, row) in boundaries.windows(2).enumerate() { ++ let start = range.start.max(line_start + row[0]); ++ let end = range.end.min(line_start + row[1]); ++ if start >= end { ++ continue; ++ } ++ // position_for_index deliberately gives a wrap boundary the ++ // previous line's caret affinity. A visual fragment starts on the ++ // next row, so use the actual wrap row and shaped x advances. ++ let origin = point( ++ layout.bounds().left() + line.unwrapped_layout.x_for_index(start - line_start) ++ - line.unwrapped_layout.x_for_index(row[0]), ++ line_y + layout.line_height() * row_ix, ++ ); ++ let width = line.unwrapped_layout.x_for_index(end - line_start) ++ - line.unwrapped_layout.x_for_index(start - line_start); ++ if width > px(0.) { ++ fragments.push(( ++ start..end, ++ Bounds::new(origin, size(width, layout.line_height())), ++ )); ++ } ++ } ++ line_start += line.len() + 1; ++ line_y += line.size(layout.line_height()).height; ++ } ++ fragments ++} ++ ++/// One-pixel dots separated by two clear pixels on each measured fragment. ++fn dotted_underline_bounds(fragment: Bounds) -> Vec> { ++ let mut dots = Vec::new(); ++ let mut x = fragment.left(); ++ while x < fragment.right() { ++ dots.push(Bounds::new( ++ point(x, fragment.bottom() - px(2.)), ++ size(px(1.).min(fragment.right() - x), px(1.)), ++ )); ++ x += px(3.); + } ++ dots + } + + fn selection_for_multi_click( +diff --git a/crates/base/src/text/inline_flow.rs b/crates/base/src/text/inline_flow.rs +index 01706014..e36c716a 100644 +--- a/crates/base/src/text/inline_flow.rs ++++ b/crates/base/src/text/inline_flow.rs +@@ -11,7 +11,9 @@ use gpui::{ + WhiteSpace, Window, img, point, prelude::FluentBuilder as _, px, relative, size, + }; + +-use crate::text::text_view::{LinkClickHandlerFn, handle_link_click}; ++use crate::text::text_view::{ ++ LinkClickHandlerFn, LinkFragmentDecoratorFn, LinkUnderlineFn, handle_link_click, ++}; + + use super::{ + inline::{Inline, InlineState}, +@@ -25,6 +27,9 @@ pub(super) struct InlineFlow { + id: ElementId, + items: Vec, + link_click_handler: Option>, ++ link_fragment_decorator: Option>, ++ link_source_offset: usize, ++ link_underline: Option>, + } + + pub(super) enum InlineFlowItem { +@@ -111,9 +116,27 @@ impl InlineFlow { + id: id.into(), + items, + link_click_handler, ++ link_fragment_decorator: None, ++ link_source_offset: 0, ++ link_underline: None, + } + } + ++ pub(super) fn link_with( ++ mut self, ++ decorator: Option>, ++ source_offset: usize, ++ ) -> Self { ++ self.link_fragment_decorator = decorator; ++ self.link_source_offset = source_offset; ++ self ++ } ++ ++ pub(super) fn link_underline(mut self, style: Option>) -> Self { ++ self.link_underline = style; ++ self ++ } ++ + fn image_element( + ix: usize, + url: &SharedUri, +@@ -286,6 +309,11 @@ impl Element for InlineFlow { + highlights, + self.link_click_handler.clone(), + ) ++ .link_with( ++ self.link_fragment_decorator.clone(), ++ self.link_source_offset + source_range.start, ++ ) ++ .link_underline(self.link_underline.clone()) + .into_any_element(); + element.prepaint_as_root( + bounds.origin + origin, +diff --git a/crates/base/src/text/node.rs b/crates/base/src/text/node.rs +index a008ccc4..34133f45 100644 +--- a/crates/base/src/text/node.rs ++++ b/crates/base/src/text/node.rs +@@ -17,8 +17,8 @@ use crate::{ + StyledExt, h_flex, + scrollable_mask::horizontal_scroll_area, + text::{ +- CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, MarkdownExtensions, +- MarkdownNode, TableActionsFn, ++ CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, LinkFragmentDecoratorFn, ++ LinkUnderlineFn, MarkdownExtensions, MarkdownNode, TableActionsFn, + document::NodeRenderOptions, + inline::{Inline, InlineState}, + inline_flow::{InlineFlow, InlineFlowItem}, +@@ -1336,6 +1336,8 @@ pub(crate) struct NodeContext { + pub(crate) code_block_actions: Option>, + pub(crate) code_block_highlighter: Option>, + pub(crate) table_actions: Option>, ++ pub(crate) link_underline: Option>, ++ pub(crate) link_fragment_decorator: Option>, + pub(crate) link_click_handler: Option>, + pub(crate) markdown_extensions: Arc, + } +@@ -1365,6 +1367,11 @@ impl Paragraph { + self.inline_flow_items(node_cx, cx), + node_cx.link_click_handler.clone(), + ) ++ .link_with( ++ node_cx.link_fragment_decorator.clone(), ++ node_cx.offset + span.unwrap_or_default().start, ++ ) ++ .link_underline(node_cx.link_underline.clone()) + .into_any_element(); + } + +@@ -1393,6 +1400,11 @@ impl Paragraph { + highlights.clone(), + node_cx.link_click_handler.clone(), + ) ++ .link_with( ++ node_cx.link_fragment_decorator.clone(), ++ node_cx.offset + span.unwrap_or_default().start, ++ ) ++ .link_underline(node_cx.link_underline.clone()) + .into_any_element(), + ); + } +@@ -1508,6 +1520,11 @@ impl Paragraph { + highlights, + node_cx.link_click_handler.clone(), + ) ++ .link_with( ++ node_cx.link_fragment_decorator.clone(), ++ node_cx.offset + span.unwrap_or_default().start, ++ ) ++ .link_underline(node_cx.link_underline.clone()) + .into_any_element(), + ); + } +diff --git a/crates/base/src/text/state.rs b/crates/base/src/text/state.rs +index 8e08c75e..07318447 100644 +--- a/crates/base/src/text/state.rs ++++ b/crates/base/src/text/state.rs +@@ -17,8 +17,8 @@ use crate::{ + async_util::{Receiver, Sender, unbounded}, + input::{self, SelectAll}, + text::{ +- CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, MarkdownExtensions, +- TableActionsFn, TextViewStyle, ++ CodeBlockActionsFn, CodeBlockHighlighterFn, LinkClickHandlerFn, LinkFragmentDecoratorFn, ++ LinkUnderlineFn, MarkdownExtensions, TableActionsFn, TextViewStyle, + document::ParsedDocument, + format, + node::{self, NodeContext}, +@@ -103,6 +103,8 @@ pub struct TextViewState { + pub(super) code_block_actions: Option>, + pub(super) code_block_highlighter: Option>, + pub(super) table_actions: Option>, ++ pub(super) link_underline: Option>, ++ pub(super) link_fragment_decorator: Option>, + pub(super) link_click_handler: Option>, + pub(super) markdown_extensions: Arc, + +@@ -203,6 +205,8 @@ impl TextViewState { + code_block_highlighter: None, + table_actions: None, + link_click_handler: None, ++ link_fragment_decorator: None, ++ link_underline: None, + markdown_extensions: Arc::default(), + is_selecting: false, + auto_scroll: AutoScroll::default(), +@@ -613,6 +617,8 @@ impl Render for TextViewState { + node_cx.code_block_highlighter = self.code_block_highlighter.clone(); + node_cx.table_actions = self.table_actions.clone(); + node_cx.link_click_handler = self.link_click_handler.clone(); ++ node_cx.link_fragment_decorator = self.link_fragment_decorator.clone(); ++ node_cx.link_underline = self.link_underline.clone(); + node_cx.markdown_extensions = self.markdown_extensions.clone(); + node_cx.style = self.text_view_style.clone(); + +diff --git a/crates/base/src/text/text_view.rs b/crates/base/src/text/text_view.rs +index 535ee643..2857e1c1 100644 +--- a/crates/base/src/text/text_view.rs ++++ b/crates/base/src/text/text_view.rs +@@ -73,6 +73,34 @@ impl TextViewDefaults { + pub(crate) type TableActionsFn = + dyn Fn(&TableData, &mut Window, &mut App) -> AnyElement + Send + Sync; + ++/// A measured visual fragment of a parsed text link. Wrapped links publish one ++/// fragment per visual line, never a rectangle covering the intervening prose. ++#[derive(Clone, Debug)] ++pub struct LinkFragment { ++ /// Stable fragment identity within the containing text view. ++ pub id: SharedString, ++ /// The URL resolved by the existing Markdown/HTML parser. ++ pub url: SharedString, ++ /// The visible text of this fragment, without Markdown syntax. ++ pub text: SharedString, ++} ++ ++/// Presentation of a parsed text link. The default is the existing solid ++/// underline; dotted suggestions keep the same glyph layout and hit target. ++#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] ++pub enum LinkUnderline { ++ #[default] ++ Solid, ++ Dotted, ++ None, ++} ++ ++pub(crate) type LinkUnderlineFn = dyn Fn(&SharedString, &App) -> LinkUnderline + Send + Sync; ++ ++pub(crate) type LinkFragmentDecoratorFn = dyn Fn(&LinkFragment, gpui::Stateful, &mut Window, &mut App) -> gpui::Stateful ++ + Send ++ + Sync; ++ + pub(crate) type LinkClickHandlerFn = + dyn Fn(&SharedString, &ClickEvent, &mut Window, &mut App) + Send + Sync; + +@@ -128,6 +156,8 @@ pub struct TextView { + code_block_highlighter: Option>, + table_actions: Option>, + link_click_handler: Option>, ++ link_fragment_decorator: Option>, ++ link_underline: Option>, + markdown_extensions: Arc, + } + +@@ -172,6 +202,8 @@ impl TextView { + code_block_highlighter: None, + table_actions: None, + link_click_handler: None, ++ link_fragment_decorator: None, ++ link_underline: None, + markdown_extensions: Arc::default(), + } + } +@@ -193,6 +225,8 @@ impl TextView { + code_block_highlighter: None, + table_actions: None, + link_click_handler: None, ++ link_fragment_decorator: None, ++ link_underline: None, + markdown_extensions: Arc::default(), + } + } +@@ -214,6 +248,8 @@ impl TextView { + code_block_highlighter: None, + table_actions: None, + link_click_handler: None, ++ link_fragment_decorator: None, ++ link_underline: None, + markdown_extensions: Arc::default(), + } + } +@@ -333,6 +369,37 @@ impl TextView { + self + } + ++ /// Selects link underline presentation without changing text or layout. ++ /// Unspecified views retain their existing solid underline. ++ pub fn link_underline(mut self, style: F) -> Self ++ where ++ F: Fn(&SharedString, &App) -> LinkUnderline + Send + Sync + 'static, ++ { ++ self.link_underline = Some(Arc::new(style)); ++ self ++ } ++ ++ /// Decorates each actual, measured text-link target after its ordinary ++ /// click handler is installed. This can publish accessibility, focus and ++ /// hover behavior on the same fragment. The parser, text layout and text ++ /// selection remain owned by TextView. The decorator must preserve the ++ /// supplied target's dimensions and existing click handler. ++ pub fn link_with(mut self, decorator: F) -> Self ++ where ++ F: Fn( ++ &LinkFragment, ++ gpui::Stateful, ++ &mut Window, ++ &mut App, ++ ) -> gpui::Stateful ++ + Send ++ + Sync ++ + 'static, ++ { ++ self.link_fragment_decorator = Some(Arc::new(decorator)); ++ self ++ } ++ + /// Replace the Markdown extension registry. + pub fn markdown_extensions(mut self, extensions: MarkdownExtensions) -> Self { + self.markdown_extensions = Arc::new(extensions); +@@ -554,6 +621,8 @@ impl Element for TextView { + state.code_block_highlighter = code_block_highlighter.clone(); + state.table_actions = self.table_actions.clone(); + state.link_click_handler = self.link_click_handler.clone(); ++ state.link_fragment_decorator = self.link_fragment_decorator.clone(); ++ state.link_underline = self.link_underline.clone(); + state.set_markdown_extensions(self.markdown_extensions.clone(), cx); + state.selectable = self.selectable; + state.selection_format = self.selection_format; +@@ -630,16 +699,14 @@ impl Element for TextView { + if let Ok(mut line_spans) = state.read(cx).line_spans.lock() { + line_spans.clear(); + } +- // Descendant `Inline`s report their line spans through the state +- // stack during prepaint (in addition to the paint-time push below). +- GlobalState::global_mut(cx) +- .text_view_state_stack +- .push(state.clone()); + } ++ // Measured link targets retain this same selection authority. Clamped ++ // descendants additionally report their line spans through the stack. ++ GlobalState::global_mut(cx) ++ .text_view_state_stack ++ .push(state.clone()); + request_layout.element.prepaint(window, cx); +- if max_lines_active { +- GlobalState::global_mut(cx).text_view_state_stack.pop(); +- } ++ GlobalState::global_mut(cx).text_view_state_stack.pop(); + + let mut clip_bottom = None; + if max_lines_active { +diff --git a/crates/component/src/text/compat.rs b/crates/component/src/text/compat.rs +index a59bb5fb..6ffcfe54 100644 +--- a/crates/component/src/text/compat.rs ++++ b/crates/component/src/text/compat.rs +@@ -107,6 +107,31 @@ impl TextView { + self.inner = self.inner.on_link_click(f); + self + } ++ /// Selects Base's solid, dotted or absent underline on the existing text. ++ pub fn link_underline(mut self, style: F) -> Self ++ where ++ F: Fn(&SharedString, &App) -> gpui_base::text::LinkUnderline + Send + Sync + 'static, ++ { ++ self.inner = self.inner.link_underline(style); ++ self ++ } ++ /// Decorates the actual measured Base link targets, preserving its parser, ++ /// selection and click behavior. See [`gpui_base::TextView::link_with`]. ++ pub fn link_with(mut self, decorator: F) -> Self ++ where ++ F: Fn( ++ &gpui_base::text::LinkFragment, ++ gpui::Stateful, ++ &mut Window, ++ &mut App, ++ ) -> gpui::Stateful ++ + Send ++ + Sync ++ + 'static, ++ { ++ self.inner = self.inner.link_with(decorator); ++ self ++ } + /// Sets which Markdown extensions the parser accepts. + pub fn markdown_extensions(mut self, value: MarkdownExtensions) -> Self { + self.inner = self.inner.markdown_extensions(value); +diff --git a/crates/component/src/text/mod.rs b/crates/component/src/text/mod.rs +index d594bd57..dee55646 100644 +--- a/crates/component/src/text/mod.rs ++++ b/crates/component/src/text/mod.rs +@@ -7,8 +7,9 @@ pub use compat::{ + Text, TextView, TextViewLayoutState, TextViewPlugin, TextViewPrepaintState, html, markdown, + }; + pub use gpui_base::text::{ +- MarkdownBlockParserFn, MarkdownBlockRenderFn, MarkdownExtensions, MarkdownNode, +- MarkdownParseContext, MarkdownPlugin, SelectionFormat, TableData, TextViewState, markdown_ast, ++ LinkFragment, LinkUnderline, MarkdownBlockParserFn, MarkdownBlockRenderFn, MarkdownExtensions, ++ MarkdownNode, MarkdownParseContext, MarkdownPlugin, SelectionFormat, TableData, TextViewState, ++ markdown_ast, + }; + pub use style::TextViewStyle; + diff --git a/patches/README.md b/patches/README.md index 815515b0fb..fb3f0e778e 100644 --- a/patches/README.md +++ b/patches/README.md @@ -28,3 +28,107 @@ used by Rust 1.90. The third replaces post-1.90 standard-library profiling and UTF-8 boundary helpers with equivalent local implementations. The final patch uses the stable atomic update primitive for text-selection scope allocation, then advances the Zed pin to include action-profiler compatibility as well. + +The web integration pin selects the published textarea IME/accessibility repair +and its corrected workspace lock edge. Every GPUI family dependency continues +to resolve from the same immutable Theorem Zed revision. + +The Linux portal follow-up advances the Zed source to its compatible ASHPD +patch. This consumer retains its existing ASHPD 0.13.10 lock entry, which +already declares a Rust 1.87 minimum; no consumer registry dependency changes. + +The desktop keyboard-focus follow-up moves only the seven GPUI manifest pins +and 25 lock source revisions to Zed `2f7f1da474f6a8ab0f1f61ce35a1a2278ee31db4`. +It retains the registry dependency graph, including ASHPD 0.13.10. The pinned +web window keeps desktop shortcuts on its read-only IME event target after +an editor closes while preserving coarse-pointer keyboard dismissal. + +The platform input configuration patch completes the v0.6.0 API migration: +`InputBaseState::input_configuration` and `set_input_configuration` forward +GPUI's actual `TextInputConfiguration` through `EntityInputHandler`. Defaults +remain unchanged. Search fields can request the existing `Search` action key +without restoring the retired text-input-hints API. Changing configuration +retains text and selection. This is a preceding input prerequisite; the +Theorem DATA conversion does not add a private input fork. + +Validation of this addition: locked Wasm `gpui-base --lib` check passes. The +native `platform_input_configuration_preserves_default_and_forwards_changes` +regression covers default, Search builder, and changed Send/assistance settings. +It passed in the Linux workspace test job at source head `1636a905`; the +subsequent spelling configuration leaves that source unchanged. This branch +remains a draft requiring review before merge. + +The spelling follow-up recognizes only complete Git `index` hash lines and +the exact `zed-scap` package identifier inside the ordered patches. It retains +source and prose spelling checks; no application or runtime code changes. + +The final spelling follow-up recognizes the exact capture package name even +inside its escaped regex spelling in patch 0016. The exact CI typos-cli 1.50.1 +reproduces that failure before this change and scans the entire final checkout +without errors afterward. Runtime and dependency source remain unchanged. + +The textarea presentation patch adds `set_text_decorations` and +`text_decorations` to the ordinary multiline input. It reuses the existing +normalized UTF-8 range and edit adjustment rules, paints through the existing +decoration renderer, and leaves syntax parsing/LSP confined to the code editor. +Its default presentation remains empty. A native regression retains text, +selection, and active IME composition while styles change; native execution +is pending CI for this source. The locked Wasm library check passes. + +Two existing runnable component doctests now import their actual owning crates +(`gpui_component` and `gpui`) instead of the undeclared facade `gpui_kit`. +The original examples and assertions remain executable. This draft still +requires review before merge. + +The textarea regression uses the real Kit initializer before mounting the input. +Its test-only unused trait import, rejected by CI's warnings-as-errors gate, is +removed. No runtime behavior or assertions change in this follow-up. + +The native textarea test reached all text/selection/IME assertions and exposed +stale styles after `set_value`. The replacement hook clears only the new +textarea presentation ranges; its default is a no-op, preserving existing +editor behavior. The same regression and all its assertions are retained. + +The selected-replacement pin follows Zed's actual browser IME repair. It changes +only the seven GPUI manifest revisions and 25 lock source revisions; registry +packages remain unchanged. The replacement diff retains matching text inside +the selected range and fresh editor anchors, with UTF-16 boundary coverage. +The Wikia browser oracle remains the consumer's actual DOM/document gate. + +The lock follow-up synchronizes the 25 source URLs with that manifest pin. +The initial guard counted URL lines rather than both revision occurrences in +each URL and refused the lock write. Both exact revision occurrences now match; +no package version, checksum, or dependency edge changes. + +The sidebar decoration patch exposes the existing clickable menu row through +`SidebarMenuItem::item_with` and the retained toggle button through +`SidebarToggleButton::button_with`. The row hook excludes expanded submenus; +the button hook runs after the component's icon, label and click handler are +bound. Both default to no decoration, preserving existing mechanics. They +allow the downstream semantic adapter to publish the actual activation +geometry without a second role or interactive wrapper. The ordered source +series replay and locked Wasm component library check pass; native measured +pointer/semantic parity is a consumer +gate. This draft requires review. + +Row decorations compose in call order. A native tab-stop decoration therefore +survives the subsequent semantic adapter: pinned GPUI delivers Enter and Space +through the row's existing click closure, including its submenu behavior. +No second key handler or interactive wrapper is installed. The actual consumer +AGPUI library, copied by hash into a bounded temporary workspace and bound to +this staged Kit source, passes all 159 native tests. Its new measured test +covers the parent row, expanded submenu exclusion, toggle button, pointer and +semantic activation, both keyboard keys, and disabled/read-only inertness. +The original activation, naming and Store source assertions also pass without +new exemptions. Locked consumer verification follows adoption of this exact +source revision; staged-source proof is not a browser deployment result. + +The text-link decoration patch exposes each real parsed and measured line +fragment through `TextView::link_with`, preserving the original click handler +and source parser. `link_underline` selects solid, dotted, or absent underline +without reparsing text in the consumer. The default remains solid. Fragment +focus survives the selection layer's deferred mouse-down callback without +overriding focus chosen by navigation. The actual AGPUI consumer tests cover +wrapped pointer targets, semantic activation, Enter and Space, and drag +selection with the real TextSelectionLayer. Native interaction passed; browser +link and deployed application verification remain consumer gates. diff --git a/patches/series b/patches/series index 425180c6c6..3c1ab613e3 100644 --- a/patches/series +++ b/patches/series @@ -9,3 +9,17 @@ 0009-rust-1.90-atomics.patch 0010-rust-1.90-final-zed-pin.patch 0011-feat-settings-add-controlled-navigation-state.patch +0012-gpui-web-ime-integration-pin.patch +0013-gpui-linux-portal-compatible-pin.patch +0014-gpui-desktop-keyboard-focus-pin.patch +0015-platform-input-configuration.patch +0016-patch-spelling-identifiers.patch +0017-patch-pattern-spelling.patch +0018-textarea-presentation-styles.patch +0019-textarea-test-initialization.patch +0020-textarea-replacement-presentation.patch +0021-gpui-selected-replacement-pin.patch +0022-gpui-selected-replacement-lock.patch +0023-sidebar-semantic-decoration.patch +0024-compose-sidebar-row-decorations.patch +0025-measured-text-link-decoration.patch