Merge branch 'main' into v0.173.x

Joseph T. Lyons created

Change summary

crates/editor/src/editor.rs                      | 448 +++--------------
crates/editor/src/element.rs                     | 280 +++++-----
crates/editor/src/scroll.rs                      |  10 
crates/editor/src/scroll/autoscroll.rs           |  23 
crates/git_ui/src/project_diff.rs                |  53 +
crates/theme/src/settings.rs                     | 155 +++++
crates/theme_selector/src/icon_theme_selector.rs |   8 
crates/ui/src/traits/styled_ext.rs               |   4 
crates/workspace/src/workspace.rs                |   1 
9 files changed, 419 insertions(+), 563 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -520,296 +520,15 @@ pub enum MenuInlineCompletionsPolicy {
     ByProvider,
 }
 
-// TODO az do we need this?
-#[derive(Clone)]
 pub enum EditPredictionPreview {
     /// Modifier is not pressed
     Inactive,
-    /// Modifier pressed, animating to active
-    MovingTo {
-        animation: Range<Instant>,
-        scroll_position_at_start: Option<gpui::Point<f32>>,
-        target_point: DisplayPoint,
-    },
-    Arrived {
-        scroll_position_at_start: Option<gpui::Point<f32>>,
-        scroll_position_at_arrival: Option<gpui::Point<f32>>,
-        target_point: Option<DisplayPoint>,
-    },
-    /// Modifier released, animating from active
-    MovingFrom {
-        animation: Range<Instant>,
-        target_point: DisplayPoint,
+    /// Modifier pressed
+    Active {
+        previous_scroll_position: Option<ScrollAnchor>,
     },
 }
 
-impl EditPredictionPreview {
-    fn start(
-        &mut self,
-        completion: &InlineCompletion,
-        snapshot: &EditorSnapshot,
-        cursor: DisplayPoint,
-    ) -> bool {
-        if matches!(self, Self::MovingTo { .. } | Self::Arrived { .. }) {
-            return false;
-        }
-        (*self, _) = Self::start_now(completion, snapshot, cursor);
-        true
-    }
-
-    fn restart(
-        &mut self,
-        completion: &InlineCompletion,
-        snapshot: &EditorSnapshot,
-        cursor: DisplayPoint,
-    ) -> bool {
-        match self {
-            Self::Inactive => false,
-            Self::MovingTo { target_point, .. }
-            | Self::Arrived {
-                target_point: Some(target_point),
-                ..
-            } => {
-                let (new_preview, new_target_point) = Self::start_now(completion, snapshot, cursor);
-
-                if new_target_point != Some(*target_point) {
-                    *self = new_preview;
-                    return true;
-                }
-
-                false
-            }
-            Self::Arrived {
-                target_point: None, ..
-            } => {
-                let (new_preview, _) = Self::start_now(completion, snapshot, cursor);
-
-                *self = new_preview;
-                true
-            }
-            Self::MovingFrom { .. } => false,
-        }
-    }
-
-    fn start_now(
-        completion: &InlineCompletion,
-        snapshot: &EditorSnapshot,
-        cursor: DisplayPoint,
-    ) -> (Self, Option<DisplayPoint>) {
-        let now = Instant::now();
-        match completion {
-            InlineCompletion::Edit { .. } => (
-                Self::Arrived {
-                    target_point: None,
-                    scroll_position_at_start: None,
-                    scroll_position_at_arrival: None,
-                },
-                None,
-            ),
-            InlineCompletion::Move { target, .. } => {
-                let target_point = target.to_display_point(&snapshot.display_snapshot);
-                let duration = Self::animation_duration(cursor, target_point);
-
-                (
-                    Self::MovingTo {
-                        animation: now..now + duration,
-                        scroll_position_at_start: Some(snapshot.scroll_position()),
-                        target_point,
-                    },
-                    Some(target_point),
-                )
-            }
-        }
-    }
-
-    fn animation_duration(a: DisplayPoint, b: DisplayPoint) -> Duration {
-        const SPEED: f32 = 8.0;
-
-        let row_diff = b.row().0.abs_diff(a.row().0);
-        let column_diff = b.column().abs_diff(a.column());
-        let distance = ((row_diff.pow(2) + column_diff.pow(2)) as f32).sqrt();
-        Duration::from_millis((distance * SPEED) as u64)
-    }
-
-    fn end(
-        &mut self,
-        cursor: DisplayPoint,
-        scroll_pixel_position: gpui::Point<Pixels>,
-        window: &mut Window,
-        cx: &mut Context<Editor>,
-    ) -> bool {
-        let (scroll_position, target_point) = match self {
-            Self::MovingTo {
-                scroll_position_at_start,
-                target_point,
-                ..
-            }
-            | Self::Arrived {
-                scroll_position_at_start,
-                scroll_position_at_arrival: None,
-                target_point: Some(target_point),
-                ..
-            } => (*scroll_position_at_start, target_point),
-            Self::Arrived {
-                scroll_position_at_start,
-                scroll_position_at_arrival: Some(scroll_at_arrival),
-                target_point: Some(target_point),
-            } => {
-                const TOLERANCE: f32 = 4.0;
-
-                let diff = *scroll_at_arrival - scroll_pixel_position.map(|p| p.0);
-
-                if diff.x.abs() < TOLERANCE && diff.y.abs() < TOLERANCE {
-                    (*scroll_position_at_start, target_point)
-                } else {
-                    (None, target_point)
-                }
-            }
-            Self::Arrived {
-                target_point: None, ..
-            } => {
-                *self = Self::Inactive;
-                return true;
-            }
-            Self::MovingFrom { .. } | Self::Inactive => return false,
-        };
-
-        let now = Instant::now();
-        let duration = Self::animation_duration(cursor, *target_point);
-        let target_point = *target_point;
-
-        *self = Self::MovingFrom {
-            animation: now..now + duration,
-            target_point,
-        };
-
-        if let Some(scroll_position) = scroll_position {
-            cx.spawn_in(window, |editor, mut cx| async move {
-                smol::Timer::after(duration).await;
-                editor
-                    .update_in(&mut cx, |editor, window, cx| {
-                        if let Self::MovingFrom { .. } | Self::Inactive =
-                            editor.edit_prediction_preview
-                        {
-                            editor.set_scroll_position(scroll_position, window, cx)
-                        }
-                    })
-                    .log_err();
-            })
-            .detach();
-        }
-
-        true
-    }
-
-    /// Whether the preview is active or we are animating to or from it.
-    fn is_active(&self) -> bool {
-        matches!(
-            self,
-            Self::MovingTo { .. } | Self::Arrived { .. } | Self::MovingFrom { .. }
-        )
-    }
-
-    /// Returns true if the preview is active, not cancelled, and the animation is settled.
-    fn is_active_settled(&self) -> bool {
-        matches!(self, Self::Arrived { .. })
-    }
-
-    #[allow(clippy::too_many_arguments)]
-    fn move_state(
-        &mut self,
-        snapshot: &EditorSnapshot,
-        visible_row_range: Range<DisplayRow>,
-        line_layouts: &[LineWithInvisibles],
-        scroll_pixel_position: gpui::Point<Pixels>,
-        line_height: Pixels,
-        target: Anchor,
-        cursor: Option<DisplayPoint>,
-    ) -> Option<EditPredictionMoveState> {
-        let delta = match self {
-            Self::Inactive => return None,
-            Self::Arrived { .. } => 1.,
-            Self::MovingTo {
-                animation,
-                scroll_position_at_start: original_scroll_position,
-                target_point,
-            } => {
-                let now = Instant::now();
-                if animation.end < now {
-                    *self = Self::Arrived {
-                        scroll_position_at_start: *original_scroll_position,
-                        scroll_position_at_arrival: Some(scroll_pixel_position.map(|p| p.0)),
-                        target_point: Some(*target_point),
-                    };
-                    1.0
-                } else {
-                    (now - animation.start).as_secs_f32()
-                        / (animation.end - animation.start).as_secs_f32()
-                }
-            }
-            Self::MovingFrom { animation, .. } => {
-                let now = Instant::now();
-                if animation.end < now {
-                    *self = Self::Inactive;
-                    return None;
-                } else {
-                    let delta = (now - animation.start).as_secs_f32()
-                        / (animation.end - animation.start).as_secs_f32();
-                    1.0 - delta
-                }
-            }
-        };
-
-        let cursor = cursor?;
-
-        if !visible_row_range.contains(&cursor.row()) {
-            return None;
-        }
-
-        let target_position = target.to_display_point(&snapshot.display_snapshot);
-
-        if !visible_row_range.contains(&target_position.row()) {
-            return None;
-        }
-
-        let target_row_layout =
-            &line_layouts[target_position.row().minus(visible_row_range.start) as usize];
-        let target_column = target_position.column() as usize;
-
-        let target_character_x = target_row_layout.x_for_index(target_column);
-
-        let target_x = target_character_x - scroll_pixel_position.x;
-        let target_y =
-            (target_position.row().as_f32() - scroll_pixel_position.y / line_height) * line_height;
-
-        let origin_x = line_layouts[cursor.row().minus(visible_row_range.start) as usize]
-            .x_for_index(cursor.column() as usize);
-        let origin_y =
-            (cursor.row().as_f32() - scroll_pixel_position.y / line_height) * line_height;
-
-        let delta = 1.0 - (-10.0 * delta).exp2();
-
-        let x = origin_x + (target_x - origin_x) * delta;
-        let y = origin_y + (target_y - origin_y) * delta;
-
-        Some(EditPredictionMoveState {
-            delta,
-            position: point(x, y),
-        })
-    }
-}
-
-pub(crate) struct EditPredictionMoveState {
-    delta: f32,
-    position: gpui::Point<Pixels>,
-}
-
-impl EditPredictionMoveState {
-    pub fn is_animation_completed(&self) -> bool {
-        self.delta >= 1.
-    }
-}
-
 #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
 struct EditorActionId(usize);
 
@@ -1786,6 +1505,15 @@ impl Editor {
     }
 
     fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
+        self.key_context_internal(self.has_active_inline_completion(), window, cx)
+    }
+
+    fn key_context_internal(
+        &self,
+        has_active_edit_prediction: bool,
+        window: &Window,
+        cx: &App,
+    ) -> KeyContext {
         let mut key_context = KeyContext::new_with_defaults();
         key_context.add("Editor");
         let mode = match self.mode {
@@ -1836,7 +1564,7 @@ impl Editor {
             key_context.set("extension", extension.to_string());
         }
 
-        if self.has_active_inline_completion() {
+        if has_active_edit_prediction {
             key_context.add("copilot_suggestion");
             key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
 
@@ -1857,12 +1585,10 @@ impl Editor {
         window: &Window,
         cx: &App,
     ) -> AcceptEditPredictionBinding {
-        let mut context = self.key_context(window, cx);
-        context.add(EDIT_PREDICTION_KEY_CONTEXT);
-
+        let key_context = self.key_context_internal(true, window, cx);
         AcceptEditPredictionBinding(
             window
-                .bindings_for_action_in_context(&AcceptEditPrediction, context)
+                .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
                 .into_iter()
                 .rev()
                 .next(),
@@ -5067,6 +4793,13 @@ impl Editor {
         self.snippet_stack.is_empty() && self.edit_predictions_enabled()
     }
 
+    pub fn edit_prediction_preview_is_active(&self) -> bool {
+        matches!(
+            self.edit_prediction_preview,
+            EditPredictionPreview::Active { .. }
+        )
+    }
+
     pub fn inline_completions_enabled(&self, cx: &App) -> bool {
         let cursor = self.selections.newest_anchor().head();
         if let Some((buffer, cursor_position)) =
@@ -5232,10 +4965,40 @@ impl Editor {
         match &active_inline_completion.completion {
             InlineCompletion::Move { target, .. } => {
                 let target = *target;
-                // Note that this is also done in vim's handler of the Tab action.
-                self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
-                    selections.select_anchor_ranges([target..target]);
-                });
+
+                if let Some(position_map) = &self.last_position_map {
+                    if position_map
+                        .visible_row_range
+                        .contains(&target.to_display_point(&position_map.snapshot).row())
+                        || !self.edit_prediction_requires_modifier()
+                    {
+                        // Note that this is also done in vim's handler of the Tab action.
+                        self.change_selections(
+                            Some(Autoscroll::newest()),
+                            window,
+                            cx,
+                            |selections| {
+                                selections.select_anchor_ranges([target..target]);
+                            },
+                        );
+                        self.clear_row_highlights::<EditPredictionPreview>();
+
+                        self.edit_prediction_preview = EditPredictionPreview::Active {
+                            previous_scroll_position: None,
+                        };
+                    } else {
+                        self.edit_prediction_preview = EditPredictionPreview::Active {
+                            previous_scroll_position: Some(position_map.snapshot.scroll_anchor),
+                        };
+                        self.highlight_rows::<EditPredictionPreview>(
+                            target..target,
+                            cx.theme().colors().editor_highlighted_line_background,
+                            true,
+                            cx,
+                        );
+                        self.request_autoscroll(Autoscroll::fit(), cx);
+                    }
+                }
             }
             InlineCompletion::Edit { edits, .. } => {
                 if let Some(provider) = self.edit_prediction_provider() {
@@ -5403,7 +5166,7 @@ impl Editor {
     /// like we are not previewing and the LSP autocomplete menu is visible
     /// or we are in `when_holding_modifier` mode.
     pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
-        if self.edit_prediction_preview.is_active()
+        if self.edit_prediction_preview_is_active()
             || !self.show_edit_predictions_in_menu()
             || !self.edit_predictions_enabled()
         {
@@ -5425,7 +5188,7 @@ impl Editor {
         cx: &mut Context<Self>,
     ) {
         if self.show_edit_predictions_in_menu() {
-            self.update_edit_prediction_preview(&modifiers, position_map, window, cx);
+            self.update_edit_prediction_preview(&modifiers, window, cx);
         }
 
         let mouse_position = window.mouse_position();
@@ -5445,7 +5208,6 @@ impl Editor {
     fn update_edit_prediction_preview(
         &mut self,
         modifiers: &Modifiers,
-        position_map: &PositionMap,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -5455,37 +5217,34 @@ impl Editor {
         };
 
         if &accept_keystroke.modifiers == modifiers {
-            let Some(completion) = self.active_inline_completion.as_ref() else {
-                return;
-            };
-
-            if !self.edit_prediction_requires_modifier() && !self.has_visible_completions_menu() {
-                return;
-            }
-
-            let transitioned = self.edit_prediction_preview.start(
-                &completion.completion,
-                &position_map.snapshot,
-                self.selections
-                    .newest_anchor()
-                    .head()
-                    .to_display_point(&position_map.snapshot),
-            );
+            if matches!(
+                self.edit_prediction_preview,
+                EditPredictionPreview::Inactive
+            ) {
+                self.edit_prediction_preview = EditPredictionPreview::Active {
+                    previous_scroll_position: None,
+                };
 
-            if transitioned {
-                self.request_autoscroll(Autoscroll::fit(), cx);
                 self.update_visible_inline_completion(window, cx);
                 cx.notify();
             }
-        } else if self.edit_prediction_preview.end(
-            self.selections
-                .newest_anchor()
-                .head()
-                .to_display_point(&position_map.snapshot),
-            position_map.scroll_pixel_position,
-            window,
-            cx,
-        ) {
+        } else if let EditPredictionPreview::Active {
+            previous_scroll_position,
+        } = self.edit_prediction_preview
+        {
+            if let (Some(previous_scroll_position), Some(position_map)) =
+                (previous_scroll_position, self.last_position_map.as_ref())
+            {
+                self.set_scroll_position(
+                    previous_scroll_position
+                        .scroll_position(&position_map.snapshot.display_snapshot),
+                    window,
+                    cx,
+                );
+            }
+
+            self.edit_prediction_preview = EditPredictionPreview::Inactive;
+            self.clear_row_highlights::<EditPredictionPreview>();
             self.update_visible_inline_completion(window, cx);
             cx.notify();
         }
@@ -5493,7 +5252,7 @@ impl Editor {
 
     fn update_visible_inline_completion(
         &mut self,
-        window: &mut Window,
+        _window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Option<()> {
         let selection = self.selections.newest_anchor();
@@ -5643,15 +5402,6 @@ impl Editor {
             ));
 
         self.stale_inline_completion_in_menu = None;
-        let editor_snapshot = self.snapshot(window, cx);
-        if self.edit_prediction_preview.restart(
-            &completion,
-            &editor_snapshot,
-            cursor.to_display_point(&editor_snapshot),
-        ) {
-            self.request_autoscroll(Autoscroll::fit(), cx);
-        }
-
         self.active_inline_completion = Some(InlineCompletionState {
             inlay_ids,
             completion,
@@ -5879,7 +5629,7 @@ impl Editor {
     }
 
     pub fn context_menu_visible(&self) -> bool {
-        !self.edit_prediction_preview.is_active()
+        !self.edit_prediction_preview_is_active()
             && self
                 .context_menu
                 .borrow()
@@ -5990,12 +5740,12 @@ impl Editor {
                                     Icon::new(IconName::ZedPredictUp)
                                 },
                             )
-                            .child(Label::new("Hold"))
+                            .child(Label::new("Hold").size(LabelSize::Small))
                             .children(ui::render_modifiers(
                                 &accept_keystroke.modifiers,
                                 PlatformStyle::platform(),
                                 Some(Color::Default),
-                                None,
+                                Some(IconSize::Small.rems().into()),
                                 true,
                             ))
                             .into_any(),
@@ -6047,15 +5797,16 @@ impl Editor {
                 .max_w(max_width)
                 .flex_1()
                 .px_2()
-                .py_1()
                 .elevation_2(cx)
                 .border_color(cx.theme().colors().border)
-                .child(completion)
-                .child(ui::Divider::vertical())
+                .child(div().py_1().overflow_hidden().child(completion))
                 .child(
                     h_flex()
                         .h_full()
+                        .border_l_1()
+                        .border_color(cx.theme().colors().border)
                         .gap_1()
+                        .py_1()
                         .pl_2()
                         .child(
                             h_flex()
@@ -14056,23 +13807,6 @@ impl Editor {
         }
     }
 
-    pub fn previewing_edit_prediction_move(
-        &mut self,
-    ) -> Option<(Anchor, &mut EditPredictionPreview)> {
-        if !self.edit_prediction_preview.is_active() {
-            return None;
-        };
-
-        self.active_inline_completion
-            .as_ref()
-            .and_then(|completion| match completion.completion {
-                InlineCompletion::Move { target, .. } => {
-                    Some((target, &mut self.edit_prediction_preview))
-                }
-                _ => None,
-            })
-    }
-
     pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
         (self.read_only(cx) || self.blink_manager.read(cx).visible())
             && self.focus_handle.is_focused(window)
@@ -14905,7 +14639,7 @@ impl Editor {
     }
 
     pub fn has_visible_completions_menu(&self) -> bool {
-        !self.edit_prediction_preview.is_active()
+        !self.edit_prediction_preview_is_active()
             && self.context_menu.borrow().as_ref().map_or(false, |menu| {
                 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
             })

crates/editor/src/element.rs 🔗

@@ -16,12 +16,12 @@ use crate::{
     mouse_context_menu::{self, MenuPosition, MouseContextMenu},
     scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
     AcceptEditPrediction, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint,
-    DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode,
-    EditPredictionPreview, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
-    ExpandExcerpts, FocusedBlock, GoToHunk, GoToPrevHunk, GutterDimensions, HalfPageDown,
-    HalfPageUp, HandleInput, HoveredCursor, InlineCompletion, JumpData, LineDown, LineUp,
-    OpenExcerpts, PageDown, PageUp, Point, RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase,
-    Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
+    DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
+    EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
+    GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
+    InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
+    RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
+    StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
     EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
     GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
 };
@@ -1116,7 +1116,6 @@ impl EditorElement {
         em_width: Pixels,
         em_advance: Pixels,
         autoscroll_containing_element: bool,
-        newest_selection_head: Option<DisplayPoint>,
         window: &mut Window,
         cx: &mut App,
     ) -> Vec<CursorLayout> {
@@ -1124,29 +1123,7 @@ impl EditorElement {
         let cursor_layouts = self.editor.update(cx, |editor, cx| {
             let mut cursors = Vec::new();
 
-            let previewing_move =
-                if let Some((target, preview)) = editor.previewing_edit_prediction_move() {
-                    cursors.extend(self.layout_edit_prediction_preview_cursor(
-                        snapshot,
-                        visible_display_row_range.clone(),
-                        line_layouts,
-                        content_origin,
-                        scroll_pixel_position,
-                        line_height,
-                        em_advance,
-                        preview,
-                        target,
-                        newest_selection_head,
-                        window,
-                        cx,
-                    ));
-
-                    true
-                } else {
-                    false
-                };
-
-            let show_local_cursors = !previewing_move && editor.show_local_cursors(window, cx);
+            let show_local_cursors = editor.show_local_cursors(window, cx);
 
             for (player_color, selections) in selections {
                 for selection in selections {
@@ -1288,50 +1265,6 @@ impl EditorElement {
         cursor_layouts
     }
 
-    #[allow(clippy::too_many_arguments)]
-    fn layout_edit_prediction_preview_cursor(
-        &self,
-        snapshot: &EditorSnapshot,
-        visible_row_range: Range<DisplayRow>,
-        line_layouts: &[LineWithInvisibles],
-        content_origin: gpui::Point<Pixels>,
-        scroll_pixel_position: gpui::Point<Pixels>,
-        line_height: Pixels,
-        em_advance: Pixels,
-        preview: &mut EditPredictionPreview,
-        target: Anchor,
-        cursor: Option<DisplayPoint>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Option<CursorLayout> {
-        let state = preview.move_state(
-            snapshot,
-            visible_row_range,
-            line_layouts,
-            scroll_pixel_position,
-            line_height,
-            target,
-            cursor,
-        )?;
-
-        if !state.is_animation_completed() {
-            window.request_animation_frame();
-        }
-
-        let mut cursor = CursorLayout {
-            color: self.style.local_player.cursor,
-            block_width: em_advance,
-            origin: state.position,
-            line_height,
-            shape: CursorShape::Bar,
-            block_text: None,
-            cursor_name: None,
-        };
-
-        cursor.layout(content_origin, None, window, cx);
-        Some(cursor)
-    }
-
     fn layout_scrollbars(
         &self,
         snapshot: &EditorSnapshot,
@@ -3607,6 +3540,7 @@ impl EditorElement {
     fn layout_edit_prediction_popover(
         &self,
         text_bounds: &Bounds<Pixels>,
+        content_origin: gpui::Point<Pixels>,
         editor_snapshot: &EditorSnapshot,
         visible_row_range: Range<DisplayRow>,
         scroll_top: f32,
@@ -3631,61 +3565,120 @@ impl EditorElement {
         }
 
         // Adjust text origin for horizontal scrolling (in some cases here)
-        let start_point =
-            text_bounds.origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
+        let start_point = content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
 
         // Clamp left offset after extreme scrollings
         let clamp_start = |point: gpui::Point<Pixels>| gpui::Point {
-            x: point.x.max(text_bounds.origin.x),
+            x: point.x.max(content_origin.x),
             y: point.y,
         };
 
         match &active_inline_completion.completion {
             InlineCompletion::Move { target, .. } => {
-                if editor.edit_prediction_requires_modifier() {
-                    let cursor_position =
-                        target.to_display_point(&editor_snapshot.display_snapshot);
+                let target_display_point = target.to_display_point(editor_snapshot);
 
-                    if !editor.edit_prediction_preview.is_active_settled()
-                        || !visible_row_range.contains(&cursor_position.row())
-                    {
+                if editor.edit_prediction_requires_modifier() {
+                    if !editor.edit_prediction_preview_is_active() {
                         return None;
                     }
 
-                    let accept_keybind = editor.accept_edit_prediction_keybind(window, cx);
-                    let accept_keystroke = accept_keybind.keystroke()?;
+                    if target_display_point.row() < visible_row_range.start {
+                        let mut element = inline_completion_accept_indicator(
+                            "Scroll",
+                            Some(IconName::ArrowUp),
+                            editor,
+                            window,
+                            cx,
+                        )?
+                        .into_any();
+
+                        element.layout_as_root(AvailableSpace::min_size(), window, cx);
+
+                        let cursor = newest_selection_head?;
+                        let cursor_row_layout = line_layouts
+                            .get(cursor.row().minus(visible_row_range.start) as usize)?;
+                        let cursor_column = cursor.column() as usize;
+
+                        let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
+
+                        const PADDING_Y: Pixels = px(12.);
 
-                    let mut element = div()
-                        .px_2()
-                        .py_1()
-                        .elevation_2(cx)
-                        .border_color(cx.theme().colors().border)
-                        .rounded_br(px(0.))
-                        .child(Label::new(accept_keystroke.key.clone()).buffer_font(cx))
+                        let origin = start_point + point(cursor_character_x, PADDING_Y);
+
+                        element.prepaint_at(origin, window, cx);
+                        return Some(element);
+                    } else if target_display_point.row() >= visible_row_range.end {
+                        let mut element = inline_completion_accept_indicator(
+                            "Scroll",
+                            Some(IconName::ArrowDown),
+                            editor,
+                            window,
+                            cx,
+                        )?
                         .into_any();
 
-                    let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
+                        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
 
-                    let cursor_row_layout = &line_layouts
-                        [cursor_position.row().minus(visible_row_range.start) as usize];
-                    let cursor_column = cursor_position.column() as usize;
+                        let cursor = newest_selection_head?;
+                        let cursor_row_layout = line_layouts
+                            .get(cursor.row().minus(visible_row_range.start) as usize)?;
+                        let cursor_column = cursor.column() as usize;
 
-                    let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
-                    let target_y = (cursor_position.row().as_f32()
-                        - scroll_pixel_position.y / line_height)
-                        * line_height;
+                        let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
+                        const PADDING_Y: Pixels = px(12.);
 
-                    let offset = point(
-                        cursor_character_x - size.width,
-                        target_y - size.height - PADDING_Y,
-                    );
+                        let origin = start_point
+                            + point(
+                                cursor_character_x,
+                                text_bounds.size.height - size.height - PADDING_Y,
+                            );
 
-                    element.prepaint_at(text_bounds.origin + offset, window, cx);
+                        element.prepaint_at(origin, window, cx);
+                        return Some(element);
+                    } else {
+                        const POLE_WIDTH: Pixels = px(2.);
+
+                        let mut element = v_flex()
+                            .child(
+                                inline_completion_accept_indicator(
+                                    "Jump", None, editor, window, cx,
+                                )?
+                                .rounded_br(px(0.))
+                                .rounded_tr(px(0.))
+                                .border_r_2(),
+                            )
+                            .child(
+                                div()
+                                    .w(POLE_WIDTH)
+                                    .bg(cx.theme().colors().text_accent.opacity(0.8))
+                                    .h(line_height),
+                            )
+                            .items_end()
+                            .into_any();
+
+                        let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
 
-                    return Some(element);
+                        let line_layout =
+                            line_layouts
+                                .get(target_display_point.row().minus(visible_row_range.start)
+                                    as usize)?;
+                        let target_column = target_display_point.column() as usize;
+
+                        let target_x = line_layout.x_for_index(target_column);
+                        let target_y = (target_display_point.row().as_f32() * line_height)
+                            - scroll_pixel_position.y;
+
+                        let origin = clamp_start(
+                            start_point + point(target_x, target_y)
+                                - point(size.width - POLE_WIDTH, size.height - line_height),
+                        );
+
+                        element.prepaint_at(origin, window, cx);
+
+                        return Some(element);
+                    }
                 }
 
-                let target_display_point = target.to_display_point(editor_snapshot);
                 if target_display_point.row().as_f32() < scroll_top {
                     let mut element = inline_completion_accept_indicator(
                         "Jump to Edit",
@@ -3693,7 +3686,8 @@ impl EditorElement {
                         editor,
                         window,
                         cx,
-                    )?;
+                    )?
+                    .into_any();
                     let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
                     let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y);
 
@@ -3706,7 +3700,8 @@ impl EditorElement {
                         editor,
                         window,
                         cx,
-                    )?;
+                    )?
+                    .into_any();
                     let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
                     let offset = point(
                         (text_bounds.size.width - size.width) / 2.,
@@ -3722,7 +3717,8 @@ impl EditorElement {
                         editor,
                         window,
                         cx,
-                    )?;
+                    )?
+                    .into_any();
                     let target_line_end = DisplayPoint::new(
                         target_display_point.row(),
                         editor_snapshot.line_len(target_display_point.row()),
@@ -3782,7 +3778,8 @@ impl EditorElement {
                             Some((
                                 inline_completion_accept_indicator(
                                     "Accept", None, editor, window, cx,
-                                )?,
+                                )?
+                                .into_any(),
                                 editor.display_to_pixel_point(
                                     target_line_end,
                                     editor_snapshot,
@@ -5805,7 +5802,7 @@ fn inline_completion_accept_indicator(
     editor: &Editor,
     window: &mut Window,
     cx: &App,
-) -> Option<AnyElement> {
+) -> Option<Div> {
     let accept_binding = editor.accept_edit_prediction_keybind(window, cx);
     let accept_keystroke = accept_binding.keystroke()?;
 
@@ -5815,7 +5812,7 @@ fn inline_completion_accept_indicator(
         .text_size(TextSize::XSmall.rems(cx))
         .text_color(cx.theme().colors().text)
         .gap_1()
-        .when(!editor.edit_prediction_preview.is_active(), |parent| {
+        .when(!editor.edit_prediction_preview_is_active(), |parent| {
             parent.children(ui::render_modifiers(
                 &accept_keystroke.modifiers,
                 PlatformStyle::platform(),
@@ -5826,33 +5823,34 @@ fn inline_completion_accept_indicator(
         })
         .child(accept_keystroke.key.clone());
 
-    let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
-    let accent_color = cx.theme().colors().text_accent;
-    let editor_bg_color = cx.theme().colors().editor_background;
+    let colors = cx.theme().colors();
+
+    let accent_color = colors.text_accent;
+    let editor_bg_color = colors.editor_background;
     let bg_color = editor_bg_color.blend(accent_color.opacity(0.2));
+    let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
 
-    Some(
-        h_flex()
-            .py_0p5()
-            .pl_1()
-            .pr(padding_right)
-            .gap_1()
-            .bg(bg_color)
-            .border_1()
-            .border_color(cx.theme().colors().text_accent.opacity(0.8))
-            .rounded_md()
-            .shadow_sm()
-            .child(accept_key)
-            .child(Label::new(label).size(LabelSize::Small))
-            .when_some(icon, |element, icon| {
-                element.child(
-                    div()
-                        .mt(px(1.5))
-                        .child(Icon::new(icon).size(IconSize::Small)),
-                )
-            })
-            .into_any(),
-    )
+    let result = h_flex()
+        .gap_1()
+        .border_1()
+        .rounded_md()
+        .shadow_sm()
+        .bg(bg_color)
+        .border_color(colors.text_accent.opacity(0.8))
+        .py_0p5()
+        .pl_1()
+        .pr(padding_right)
+        .child(accept_key)
+        .child(Label::new(label).size(LabelSize::Small))
+        .when_some(icon, |element, icon| {
+            element.child(
+                div()
+                    .mt(px(1.5))
+                    .child(Icon::new(icon).size(IconSize::Small)),
+            )
+        });
+
+    Some(result)
 }
 
 pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
@@ -7357,7 +7355,7 @@ impl Element for EditorElement {
                     let visible_row_range = start_row..end_row;
                     let non_visible_cursors = cursors
                         .iter()
-                        .any(move |c| !visible_row_range.contains(&c.0.row()));
+                        .any(|c| !visible_row_range.contains(&c.0.row()));
 
                     let visible_cursors = self.layout_visible_cursors(
                         &snapshot,
@@ -7373,7 +7371,6 @@ impl Element for EditorElement {
                         em_width,
                         em_advance,
                         autoscroll_containing_element,
-                        newest_selection_head,
                         window,
                         cx,
                     );
@@ -7527,6 +7524,7 @@ impl Element for EditorElement {
 
                     let inline_completion_popover = self.layout_edit_prediction_popover(
                         &text_hitbox.bounds,
+                        content_origin,
                         &snapshot,
                         start_row..end_row,
                         scroll_position.y,
@@ -7598,6 +7596,7 @@ impl Element for EditorElement {
 
                     let position_map = Rc::new(PositionMap {
                         size: bounds.size,
+                        visible_row_range,
                         scroll_pixel_position,
                         scroll_max,
                         line_layouts,
@@ -7991,6 +7990,7 @@ pub(crate) struct PositionMap {
     pub scroll_max: gpui::Point<f32>,
     pub em_width: Pixels,
     pub em_advance: Pixels,
+    pub visible_row_range: Range<DisplayRow>,
     pub line_layouts: Vec<LineWithInvisibles>,
     pub snapshot: EditorSnapshot,
     pub text_hitbox: Hitbox,

crates/editor/src/scroll.rs 🔗

@@ -3,6 +3,7 @@ pub(crate) mod autoscroll;
 pub(crate) mod scroll_amount;
 
 use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
+use crate::EditPredictionPreview;
 use crate::{
     display_map::{DisplaySnapshot, ToDisplayPoint},
     hover_popover::hide_hover,
@@ -495,6 +496,15 @@ impl Editor {
         hide_hover(self, cx);
         let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
 
+        if let EditPredictionPreview::Active {
+            previous_scroll_position,
+        } = &mut self.edit_prediction_preview
+        {
+            if !autoscroll {
+                previous_scroll_position.take();
+            }
+        }
+
         self.scroll_manager.set_scroll_position(
             scroll_position,
             &display_map,

crates/editor/src/scroll/autoscroll.rs 🔗

@@ -145,29 +145,6 @@ impl Editor {
                 target_top = newest_selection_top;
                 target_bottom = newest_selection_top + 1.;
             }
-
-            if self.edit_prediction_preview.is_active() {
-                if let Some(completion) = self.active_inline_completion.as_ref() {
-                    match completion.completion {
-                        crate::InlineCompletion::Edit { .. } => {}
-                        crate::InlineCompletion::Move { target, .. } => {
-                            let target_row = target.to_display_point(&display_map).row().as_f32();
-
-                            if target_row < target_top {
-                                target_top = target_row;
-                            } else if target_row >= target_bottom {
-                                target_bottom = target_row + 1.;
-                            }
-
-                            let selections_fit = target_bottom - target_top <= visible_lines;
-                            if !selections_fit {
-                                target_top = target_row;
-                                target_bottom = target_row + 1.;
-                            }
-                        }
-                    }
-                }
-            }
         }
 
         let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {

crates/git_ui/src/project_diff.rs 🔗

@@ -335,6 +335,22 @@ impl ProjectDiff {
                 cx,
             );
         });
+        if self.multibuffer.read(cx).is_empty()
+            && self
+                .editor
+                .read(cx)
+                .focus_handle(cx)
+                .contains_focused(window, cx)
+        {
+            self.focus_handle.focus(window);
+        } else if self.focus_handle.contains_focused(window, cx)
+            && !self.multibuffer.read(cx).is_empty()
+        {
+            self.editor.update(cx, |editor, cx| {
+                editor.focus_handle(cx).focus(window);
+                editor.move_to_beginning(&Default::default(), window, cx);
+            });
+        }
         if self.pending_scroll.as_ref() == Some(&path_key) {
             self.scroll_to_path(path_key, window, cx);
         }
@@ -365,8 +381,12 @@ impl ProjectDiff {
 impl EventEmitter<EditorEvent> for ProjectDiff {}
 
 impl Focusable for ProjectDiff {
-    fn focus_handle(&self, _: &App) -> FocusHandle {
-        self.focus_handle.clone()
+    fn focus_handle(&self, cx: &App) -> FocusHandle {
+        if self.multibuffer.read(cx).is_empty() {
+            self.focus_handle.clone()
+        } else {
+            self.editor.focus_handle(cx)
+        }
     }
 }
 
@@ -537,22 +557,17 @@ impl Item for ProjectDiff {
 impl Render for ProjectDiff {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let is_empty = self.multibuffer.read(cx).is_empty();
-        if is_empty {
-            div()
-                .bg(cx.theme().colors().editor_background)
-                .flex()
-                .items_center()
-                .justify_center()
-                .size_full()
-                .child(Label::new("No uncommitted changes"))
-        } else {
-            div()
-                .bg(cx.theme().colors().editor_background)
-                .flex()
-                .items_center()
-                .justify_center()
-                .size_full()
-                .child(self.editor.clone())
-        }
+
+        div()
+            .track_focus(&self.focus_handle)
+            .bg(cx.theme().colors().editor_background)
+            .flex()
+            .items_center()
+            .justify_center()
+            .size_full()
+            .when(is_empty, |el| {
+                el.child(Label::new("No uncommitted changes"))
+            })
+            .when(!is_empty, |el| el.child(self.editor.clone()))
     }
 }

crates/theme/src/settings.rs 🔗

@@ -112,7 +112,6 @@ pub struct ThemeSettings {
     /// The terminal font family can be overridden using it's own setting.
     pub buffer_line_height: BufferLineHeight,
     /// The current theme selection.
-    /// TODO: Document this further
     pub theme_selection: Option<ThemeSelection>,
     /// The active theme.
     pub active_theme: Arc<Theme>,
@@ -120,6 +119,8 @@ pub struct ThemeSettings {
     ///
     /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
     pub theme_overrides: Option<ThemeStyleContent>,
+    /// The current icon theme selection.
+    pub icon_theme_selection: Option<IconThemeSelection>,
     /// The active icon theme.
     pub active_icon_theme: Arc<IconTheme>,
     /// The density of the UI.
@@ -167,25 +168,28 @@ impl ThemeSettings {
 
     /// Reloads the current icon theme.
     ///
-    /// Reads the [`ThemeSettings`] to know which icon theme should be loaded.
+    /// Reads the [`ThemeSettings`] to know which icon theme should be loaded,
+    /// taking into account the current [`SystemAppearance`].
     pub fn reload_current_icon_theme(cx: &mut App) {
         let mut theme_settings = ThemeSettings::get_global(cx).clone();
+        let system_appearance = SystemAppearance::global(cx);
 
-        let active_theme = theme_settings.active_icon_theme.clone();
-        let mut icon_theme_name = active_theme.name.as_ref();
+        if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.clone() {
+            let mut icon_theme_name = icon_theme_selection.icon_theme(*system_appearance);
 
-        // If the selected theme doesn't exist, fall back to the default theme.
-        let theme_registry = ThemeRegistry::global(cx);
-        if theme_registry
-            .get_icon_theme(icon_theme_name)
-            .ok()
-            .is_none()
-        {
-            icon_theme_name = DEFAULT_ICON_THEME_NAME;
-        };
+            // If the selected icon theme doesn't exist, fall back to the default theme.
+            let theme_registry = ThemeRegistry::global(cx);
+            if theme_registry
+                .get_icon_theme(icon_theme_name)
+                .ok()
+                .is_none()
+            {
+                icon_theme_name = DEFAULT_ICON_THEME_NAME;
+            };
 
-        if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
-            ThemeSettings::override_global(theme_settings, cx);
+            if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
+                ThemeSettings::override_global(theme_settings, cx);
+            }
         }
     }
 }
@@ -299,6 +303,55 @@ impl ThemeSelection {
     }
 }
 
+fn icon_theme_name_ref(_: &mut SchemaGenerator) -> Schema {
+    Schema::new_ref("#/definitions/IconThemeName".into())
+}
+
+/// Represents the selection of an icon theme, which can be either static or dynamic.
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(untagged)]
+pub enum IconThemeSelection {
+    /// A static icon theme selection, represented by a single icon theme name.
+    Static(#[schemars(schema_with = "icon_theme_name_ref")] String),
+    /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
+    Dynamic {
+        /// The mode used to determine which theme to use.
+        #[serde(default)]
+        mode: ThemeMode,
+        /// The icon theme to use for light mode.
+        #[schemars(schema_with = "icon_theme_name_ref")]
+        light: String,
+        /// The icon theme to use for dark mode.
+        #[schemars(schema_with = "icon_theme_name_ref")]
+        dark: String,
+    },
+}
+
+impl IconThemeSelection {
+    /// Returns the icon theme name based on the given [`Appearance`].
+    pub fn icon_theme(&self, system_appearance: Appearance) -> &str {
+        match self {
+            Self::Static(theme) => theme,
+            Self::Dynamic { mode, light, dark } => match mode {
+                ThemeMode::Light => light,
+                ThemeMode::Dark => dark,
+                ThemeMode::System => match system_appearance {
+                    Appearance::Light => light,
+                    Appearance::Dark => dark,
+                },
+            },
+        }
+    }
+
+    /// Returns the [`ThemeMode`] for the [`IconThemeSelection`].
+    pub fn mode(&self) -> Option<ThemeMode> {
+        match self {
+            IconThemeSelection::Static(_) => None,
+            IconThemeSelection::Dynamic { mode, .. } => Some(*mode),
+        }
+    }
+}
+
 /// Settings for rendering text in UI and text buffers.
 #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 pub struct ThemeSettingsContent {
@@ -344,7 +397,7 @@ pub struct ThemeSettingsContent {
     pub theme: Option<ThemeSelection>,
     /// The name of the icon theme to use.
     #[serde(default)]
-    pub icon_theme: Option<String>,
+    pub icon_theme: Option<IconThemeSelection>,
 
     /// UNSTABLE: Expect many elements to be broken.
     ///
@@ -393,6 +446,27 @@ impl ThemeSettingsContent {
         }
     }
 
+    /// Sets the icon theme for the given appearance to the icon theme with the specified name.
+    pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
+        if let Some(selection) = self.icon_theme.as_mut() {
+            let icon_theme_to_update = match selection {
+                IconThemeSelection::Static(theme) => theme,
+                IconThemeSelection::Dynamic { mode, light, dark } => match mode {
+                    ThemeMode::Light => light,
+                    ThemeMode::Dark => dark,
+                    ThemeMode::System => match appearance {
+                        Appearance::Light => light,
+                        Appearance::Dark => dark,
+                    },
+                },
+            };
+
+            *icon_theme_to_update = icon_theme_name.to_string();
+        } else {
+            self.theme = Some(ThemeSelection::Static(icon_theme_name.to_string()));
+        }
+    }
+
     /// Sets the mode for the theme.
     pub fn set_mode(&mut self, mode: ThemeMode) {
         if let Some(selection) = self.theme.as_mut() {
@@ -419,6 +493,27 @@ impl ThemeSettingsContent {
                 dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
             });
         }
+
+        if let Some(selection) = self.icon_theme.as_mut() {
+            match selection {
+                IconThemeSelection::Static(icon_theme) => {
+                    // If the icon theme was previously set to a single static
+                    // theme, we don't know whether it was a light or dark
+                    // theme, so we just use it for both.
+                    self.icon_theme = Some(IconThemeSelection::Dynamic {
+                        mode,
+                        light: icon_theme.clone(),
+                        dark: icon_theme.clone(),
+                    });
+                }
+                IconThemeSelection::Dynamic {
+                    mode: mode_to_update,
+                    ..
+                } => *mode_to_update = mode,
+            }
+        } else {
+            self.icon_theme = Some(IconThemeSelection::Static(DEFAULT_ICON_THEME_NAME.into()));
+        }
     }
 }
 
@@ -588,10 +683,15 @@ impl settings::Settings for ThemeSettings {
                 .or(themes.get(&zed_default_dark().name))
                 .unwrap(),
             theme_overrides: None,
+            icon_theme_selection: defaults.icon_theme.clone(),
             active_icon_theme: defaults
                 .icon_theme
                 .as_ref()
-                .and_then(|name| themes.get_icon_theme(name).ok())
+                .and_then(|selection| {
+                    themes
+                        .get_icon_theme(selection.icon_theme(*system_appearance))
+                        .ok()
+                })
                 .unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()),
             ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
             unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
@@ -647,8 +747,12 @@ impl settings::Settings for ThemeSettings {
             this.apply_theme_overrides();
 
             if let Some(value) = &value.icon_theme {
-                if let Some(icon_theme) = themes.get_icon_theme(value).log_err() {
-                    this.active_icon_theme = icon_theme.clone();
+                this.icon_theme_selection = Some(value.clone());
+
+                let icon_theme_name = value.icon_theme(*system_appearance);
+
+                if let Some(icon_theme) = themes.get_icon_theme(icon_theme_name).log_err() {
+                    this.active_icon_theme = icon_theme;
                 }
             }
 
@@ -689,8 +793,21 @@ impl settings::Settings for ThemeSettings {
             ..Default::default()
         };
 
+        let icon_theme_names = ThemeRegistry::global(cx)
+            .list_icon_themes()
+            .into_iter()
+            .map(|icon_theme| Value::String(icon_theme.name.to_string()))
+            .collect();
+
+        let icon_theme_name_schema = SchemaObject {
+            instance_type: Some(InstanceType::String.into()),
+            enum_values: Some(icon_theme_names),
+            ..Default::default()
+        };
+
         root_schema.definitions.extend([
             ("ThemeName".into(), theme_name_schema.into()),
+            ("IconThemeName".into(), icon_theme_name_schema.into()),
             ("FontFamilies".into(), params.font_family_schema()),
             ("FontFallbacks".into(), params.font_fallback_schema()),
         ]);

crates/theme_selector/src/icon_theme_selector.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
 use picker::{Picker, PickerDelegate};
 use settings::{update_settings_file, Settings as _, SettingsStore};
 use std::sync::Arc;
-use theme::{IconTheme, ThemeMeta, ThemeRegistry, ThemeSettings};
+use theme::{Appearance, IconTheme, ThemeMeta, ThemeRegistry, ThemeSettings};
 use ui::{prelude::*, v_flex, ListItem, ListItemSpacing};
 use util::ResultExt;
 use workspace::{ui::HighlightedLabel, ModalView};
@@ -151,7 +151,7 @@ impl PickerDelegate for IconThemeSelectorDelegate {
     fn confirm(
         &mut self,
         _: bool,
-        _window: &mut Window,
+        window: &mut Window,
         cx: &mut Context<Picker<IconThemeSelectorDelegate>>,
     ) {
         self.selection_completed = true;
@@ -165,8 +165,10 @@ impl PickerDelegate for IconThemeSelectorDelegate {
             value = theme_name
         );
 
+        let appearance = Appearance::from(window.appearance());
+
         update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
-            settings.icon_theme = Some(theme_name.to_string());
+            settings.set_icon_theme(theme_name.to_string(), appearance);
         });
 
         self.selector

crates/ui/src/traits/styled_ext.rs 🔗

@@ -3,7 +3,7 @@ use gpui::{hsla, App, Styled};
 use crate::prelude::*;
 use crate::ElevationIndex;
 
-fn elevated<E: Styled>(this: E, cx: &mut App, index: ElevationIndex) -> E {
+fn elevated<E: Styled>(this: E, cx: &App, index: ElevationIndex) -> E {
     this.bg(cx.theme().colors().elevated_surface_background)
         .rounded_lg()
         .border_1()
@@ -54,7 +54,7 @@ pub trait StyledExt: Styled + Sized {
     /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
     ///
     /// Examples: Notifications, Palettes, Detached/Floating Windows, Detached/Floating Panels
-    fn elevation_2(self, cx: &mut App) -> Self {
+    fn elevation_2(self, cx: &App) -> Self {
         elevated(self, cx, ElevationIndex::ElevatedSurface)
     }
 

crates/workspace/src/workspace.rs 🔗

@@ -1082,6 +1082,7 @@ impl Workspace {
                 *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 
                 ThemeSettings::reload_current_theme(cx);
+                ThemeSettings::reload_current_icon_theme(cx);
             }),
             cx.on_release(move |this, cx| {
                 this.app_state.workspace_store.update(cx, move |store, _| {