Repository: zed-industries/zed
Pull request #30456: Fix deprecation warning text being covered by right dock
Author: login: kmpeeduwee
Labels: name: cla-signed
description: The user has signed the Contributor License Agreement
Created: 2025-05-10T10:09:50Z | Merged: 2025-05-11T21:54:21Z

Closes [#30378](https://github.com/zed-industries/zed/issues/30378)

Release Notes:

- Fixed deprecation warning text being covered by right dock



Diff:
diff --git a/Cargo.lock b/Cargo.lock
index 8eb98330d7aa89..4ee96c3319e2c0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -18593,6 +18593,7 @@ dependencies = [
  "languages",
  "libc",
  "log",
+ "markdown",
  "markdown_preview",
  "menu",
  "migrator",
diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml
index 9d7f6e7a22a001..062c4173b689c1 100644
--- a/crates/zed/Cargo.toml
+++ b/crates/zed/Cargo.toml
@@ -78,6 +78,7 @@ languages = { workspace = true, features = ["load-grammars"] }
 libc.workspace = true
 log.workspace = true
 markdown_preview.workspace = true
+markdown.workspace = true
 menu.workspace = true
 migrator.workspace = true
 mimalloc = { version = "0.1", optional = true }
diff --git a/crates/zed/src/zed/migrate.rs b/crates/zed/src/zed/migrate.rs
index 50c94fcc4338aa..118b71c89f3969 100644
--- a/crates/zed/src/zed/migrate.rs
+++ b/crates/zed/src/zed/migrate.rs
@@ -2,12 +2,14 @@ use anyhow::{Context as _, Result};
 use editor::Editor;
 use fs::Fs;
 use migrator::{migrate_keymap, migrate_settings};
-use settings::{KeymapFile, SettingsStore};
+use settings::{KeymapFile, Settings, SettingsStore};
 use util::ResultExt;
 
 use std::sync::Arc;
 
-use gpui::{Entity, EventEmitter, Global, Task};
+use gpui::{Entity, EventEmitter, Global, Task, TextStyle, TextStyleRefinement};
+use markdown::{Markdown, MarkdownElement, MarkdownStyle};
+use theme::ThemeSettings;
 use ui::prelude::*;
 use workspace::item::ItemHandle;
 use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace};
@@ -21,6 +23,7 @@ pub enum MigrationType {
 pub struct MigrationBanner {
     migration_type: Option<MigrationType>,
     should_migrate_task: Option<Task<()>>,
+    markdown: Option<Entity<Markdown>>,
 }
 
 pub enum MigrationEvent {
@@ -63,22 +66,7 @@ impl MigrationBanner {
         Self {
             migration_type: None,
             should_migrate_task: None,
-        }
-    }
-
-    fn backup_file_name(&self) -> String {
-        match self.migration_type {
-            Some(MigrationType::Keymap) => paths::keymap_backup_file()
-                .file_name()
-                .unwrap_or_default()
-                .to_string_lossy()
-                .into_owned(),
-            Some(MigrationType::Settings) => paths::settings_backup_file()
-                .file_name()
-                .unwrap_or_default()
-                .to_string_lossy()
-                .into_owned(),
-            None => String::new(),
+            markdown: None,
         }
     }
 
@@ -100,6 +88,48 @@ impl MigrationBanner {
             }
         }
     }
+
+    fn show(&mut self, cx: &mut Context<Self>) {
+        let (file_type, backup_file_name) = match self.migration_type {
+            Some(MigrationType::Keymap) => (
+                "keymap",
+                paths::keymap_backup_file()
+                    .file_name()
+                    .unwrap_or_default()
+                    .to_string_lossy()
+                    .into_owned(),
+            ),
+            Some(MigrationType::Settings) => (
+                "settings",
+                paths::settings_backup_file()
+                    .file_name()
+                    .unwrap_or_default()
+                    .to_string_lossy()
+                    .into_owned(),
+            ),
+            None => return,
+        };
+
+        let migration_text = format!(
+            "Your {} file uses deprecated settings which can be \
+            automatically updated. A backup will be saved to `{}`",
+            file_type, backup_file_name
+        );
+
+        self.markdown = Some(cx.new(|cx| Markdown::new(migration_text.into(), None, None, cx)));
+
+        cx.emit(ToolbarItemEvent::ChangeLocation(
+            ToolbarItemLocation::Secondary,
+        ));
+        cx.notify();
+    }
+
+    fn reset(&mut self, cx: &mut Context<Self>) {
+        self.should_migrate_task.take();
+        self.migration_type.take();
+        self.markdown.take();
+        cx.notify();
+    }
 }
 
 impl EventEmitter<ToolbarItemEvent> for MigrationBanner {}
@@ -111,8 +141,8 @@ impl ToolbarItemView for MigrationBanner {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> ToolbarItemLocation {
-        cx.notify();
-        self.should_migrate_task.take();
+        self.reset(cx);
+
         let Some(target) = active_pane_item
             .and_then(|item| item.act_as::<Editor>(cx))
             .and_then(|editor| editor.update(cx, |editor, cx| editor.target_file_abs_path(cx)))
@@ -126,11 +156,8 @@ impl ToolbarItemView for MigrationBanner {
             let should_migrate = should_migrate_keymap(fs);
             self.should_migrate_task = Some(cx.spawn_in(window, async move |this, cx| {
                 if let Ok(true) = should_migrate.await {
-                    this.update(cx, |_, cx| {
-                        cx.emit(ToolbarItemEvent::ChangeLocation(
-                            ToolbarItemLocation::Secondary,
-                        ));
-                        cx.notify();
+                    this.update(cx, |this, cx| {
+                        this.show(cx);
                     })
                     .log_err();
                 }
@@ -141,11 +168,8 @@ impl ToolbarItemView for MigrationBanner {
             let should_migrate = should_migrate_settings(fs);
             self.should_migrate_task = Some(cx.spawn_in(window, async move |this, cx| {
                 if let Ok(true) = should_migrate.await {
-                    this.update(cx, |_, cx| {
-                        cx.emit(ToolbarItemEvent::ChangeLocation(
-                            ToolbarItemLocation::Secondary,
-                        ));
-                        cx.notify();
+                    this.update(cx, |this, cx| {
+                        this.show(cx);
                     })
                     .log_err();
                 }
@@ -159,54 +183,54 @@ impl ToolbarItemView for MigrationBanner {
 impl Render for MigrationBanner {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let migration_type = self.migration_type;
-        let file_type = match migration_type {
-            Some(MigrationType::Keymap) => "keymap",
-            Some(MigrationType::Settings) => "settings",
-            None => "",
-        };
-        let backup_file_name = self.backup_file_name();
-
+        let settings = ThemeSettings::get_global(cx);
+        let ui_font_family = settings.ui_font.family.clone();
+        let line_height = settings.ui_font_size(cx) * 1.3;
         h_flex()
             .py_1()
             .pl_2()
             .pr_1()
-            .flex_wrap()
             .justify_between()
             .bg(cx.theme().status().info_background.opacity(0.6))
             .border_1()
             .border_color(cx.theme().colors().border_variant)
             .rounded_sm()
-            .overflow_hidden()
             .child(
                 h_flex()
                     .gap_2()
+                    .overflow_hidden()
                     .child(
                         Icon::new(IconName::Warning)
                             .size(IconSize::XSmall)
                             .color(Color::Warning),
                     )
                     .child(
-                        h_flex()
-                            .gap_0p5()
-                            .child(
-                                Label::new(format!(
-                                    "Your {} file uses deprecated settings which can be \
-                                    automatically updated. A backup will be saved to",
-                                    file_type
-                                ))
-                                .color(Color::Default),
-                            )
-                            .child(
-                                div()
-                                    .px_1()
-                                    .bg(cx.theme().colors().background)
-                                    .rounded_xs()
-                                    .child(
-                                        Label::new(backup_file_name)
-                                            .buffer_font(cx)
-                                            .size(LabelSize::Small),
-                                    ),
-                            ),
+                        div()
+                            .overflow_hidden()
+                            .text_size(TextSize::Default.rems(cx))
+                            .max_h(2 * line_height)
+                            .when_some(self.markdown.as_ref(), |this, markdown| {
+                                this.child(
+                                    MarkdownElement::new(
+                                        markdown.clone(),
+                                        MarkdownStyle {
+                                            base_text_style: TextStyle {
+                                                color: cx.theme().colors().text,
+                                                font_family: ui_font_family,
+                                                ..Default::default()
+                                            },
+                                            inline_code: TextStyleRefinement {
+                                                background_color: Some(
+                                                    cx.theme().colors().background,
+                                                ),
+                                                ..Default::default()
+                                            },
+                                            ..Default::default()
+                                        },
+                                    )
+                                    .into_any_element(),
+                                )
+                            }),
                     ),
             )
             .child(


Reviews:
user: login: smitbarmase
body: Thanks, this looks far better. I just added a few nitpicks.

But before that, I'm worried about the case where the pane width is extremely low and the banner seems to take all the height of the pane. I suppose this can be fixed by implementing `Element` and adding some conditional logic at paint stage by checking layout to hide this banner based on height. 

Ref: https://github.com/zed-industries/zed/blob/e9026d6a933db29c50548f540b07166d400b2b5f/crates/editor/src/element.rs#L6991

Not sure if you are willing to do that, as it seems like a lot of work. Let me know if you'd like me to pick up from here. I would be happy to do so.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-11T07:56:01Z
user: login: smitbarmase
body: I found a simpler fix to handle that case than what I suggested earlier.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-11T21:52:12Z

Review comments:
diff_hunk: @@ -122,6 +147,7 @@ impl ToolbarItemView for MigrationBanner {
 
         if &target == paths::keymap_file() {
             self.migration_type = Some(MigrationType::Keymap);
+            self.update_banner_text(cx);
path: crates/zed/src/zed/migrate.rs
user: login: smitbarmase
body: Do we need to update the banner text before the `should_migrate.await` check? I think this should be done later when the `ChangeLocation` event is emitted.
created_at: 2025-05-11T07:36:06Z
author_association: MEMBER
diff_hunk: @@ -159,54 +186,51 @@ impl ToolbarItemView for MigrationBanner {
 impl Render for MigrationBanner {
     fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let migration_type = self.migration_type;
-        let file_type = match migration_type {
-            Some(MigrationType::Keymap) => "keymap",
-            Some(MigrationType::Settings) => "settings",
-            None => "",
-        };
-        let backup_file_name = self.backup_file_name();
+        let settings = ThemeSettings::get_global(cx);
+        let ui_font_family = settings.ui_font.family.clone();
 
         h_flex()
             .py_1()
             .pl_2()
             .pr_1()
-            .flex_wrap()
             .justify_between()
             .bg(cx.theme().status().info_background.opacity(0.6))
             .border_1()
             .border_color(cx.theme().colors().border_variant)
             .rounded_sm()
-            .overflow_hidden()
             .child(
                 h_flex()
                     .gap_2()
+                    .overflow_hidden()
                     .child(
                         Icon::new(IconName::Warning)
                             .size(IconSize::XSmall)
                             .color(Color::Warning),
                     )
                     .child(
-                        h_flex()
-                            .gap_0p5()
-                            .child(
-                                Label::new(format!(
-                                    "Your {} file uses deprecated settings which can be \
-                                    automatically updated. A backup will be saved to",
-                                    file_type
-                                ))
-                                .color(Color::Default),
-                            )
-                            .child(
-                                div()
-                                    .px_1()
-                                    .bg(cx.theme().colors().background)
-                                    .rounded_xs()
-                                    .child(
-                                        Label::new(backup_file_name)
-                                            .buffer_font(cx)
-                                            .size(LabelSize::Small),
-                                    ),
-                            ),
+                        div()
+                            .overflow_hidden()
+                            .text_size(TextSize::Default.rems(cx))
+                            .child(if let Some(markdown_entity) = &self.markdown_entity {
path: crates/zed/src/zed/migrate.rs
user: login: smitbarmase
body: We can use the method `.when_some` instead of an if-else statement. That way, you don't have to specify an else block, and it's cleaner.
created_at: 2025-05-11T07:38:15Z
author_association: MEMBER
diff_hunk: @@ -100,6 +104,27 @@ impl MigrationBanner {
             }
         }
     }
+
+    fn update_banner_text(&mut self, cx: &mut Context<Self>) {
+        let file_type = match self.migration_type {
+            Some(MigrationType::Keymap) => "keymap",
+            Some(MigrationType::Settings) => "settings",
+            None => return,
+        };
+
+        let backup_file_name = self.backup_file_name();
+        let migration_text = format!(
+            "Your {} file uses deprecated settings which can be \
+            automatically updated. A backup will be saved to `{}`",
+            file_type,
+            format!("\u{00A0}{}\u{00A0}", backup_file_name)
path: crates/zed/src/zed/migrate.rs
user: login: smitbarmase
body: I suppose you added this to prevent whitespace wrapping, but it looks like it's still happening for end whitespace at `settings.json`. This seems like a GPUI bug, but for now I think it's okay to have no whitespace at all in the codeblock. That's what we do in other places.

```suggestion
            backup_file_name)
```
created_at: 2025-05-11T07:46:41Z
author_association: MEMBER

PR comments:
user: login: cla-bot[bot]
created_at: 2025-05-10T10:09:52Z
body: We require contributors to sign our Contributor License Agreement, and we don't have @kmpeeduwee on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'.
author_association: NONE
user: login: kmpeeduwee
created_at: 2025-05-10T10:11:31Z
body: @cla-bot check
author_association: CONTRIBUTOR
user: login: cla-bot[bot]
created_at: 2025-05-10T10:11:34Z
body: The cla-bot has been summoned, and re-checked this pull request!
author_association: NONE
user: login: smitbarmase
created_at: 2025-05-10T10:46:18Z
body: Thank you, but I also noticed `settings_backup.json` isn't in line with the above text. I think this is because we are using two Label components which don't nicely fit into each other. Maybe we should use a Markdown component?

<img width="562" alt="image" src="https://github.com/user-attachments/assets/5157732f-ad49-4e19-8a1b-0d031160909d" />

author_association: MEMBER
user: login: kmpeeduwee
created_at: 2025-05-10T13:24:26Z
body: > Thank you, but I also noticed `settings_backup.json` isn't in line with the above text. I think this is because we are using two Label components which don't nicely fit into each other. Maybe we should use a Markdown component?
> <img alt="image" width="562" src="https://private-user-images.githubusercontent.com/37347831/442408192-5157732f-ad49-4e19-8a1b-0d031160909d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY4ODM2MDUsIm5iZiI6MTc0Njg4MzMwNSwicGF0aCI6Ii8zNzM0NzgzMS80NDI0MDgxOTItNTE1NzczMmYtYWQ0OS00ZTE5LThhMWItMGQwMzExNjA5MDlkLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MTAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTEwVDEzMjE0NVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBlZGFhOWEyNjg1YjM5M2Q0NWY1MTQ5YmViMmJmMWQxMDgyMzM5MjUwMWMyNjM3NDEyYzU4ZTEzY2MxNGE0NGQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.fS-s6gy5U2NvVOF28oIsnlRP4bh10G66uHeNo4YoXNA">


Yes, thanks for the suggestion! It turned out much better this way! I've pushed the changes. Issues:

- I couldn't find how to change the background for settings_backup.json, it's now slightly different from the original
- My fix in the code seems a bit cumbersome
author_association: CONTRIBUTOR
user: login: smitbarmase
created_at: 2025-05-10T20:30:45Z
body: It works as intended. Nice work, thanks for picking it up. I think for the issues you mentioned:

- We should use `Markdown::new` and `MarkdownElement::new` to render, it will make it less cumbersome.
- If the default code style doesn't match what we need, you can use the `code_block_renderer` method on it. Maybe take a look at the styling used in other parts for consistency.
author_association: MEMBER
user: login: kmpeeduwee
created_at: 2025-05-11T02:48:41Z
body: > It works as intended. Nice work, thanks for picking it up. I think for the issues you mentioned:
> 
>     * We should use `Markdown::new` and `MarkdownElement::new` to render, it will make it less cumbersome.
> 
>     * If the default code style doesn't match what we need, you can use the `code_block_renderer` method on it. Maybe take a look at the styling used in other parts for consistency.

Thank you so much for your help! It really is much better now. I've pushed the changes :)
author_association: CONTRIBUTOR
user: login: kmpeeduwee
created_at: 2025-05-11T11:22:43Z
body: @smitbarmase Thanks a lot for the helpful review — it really helped me!

I agree that implementing the Element logic sounds like a bigger change, and I’m probably not ready to tackle that at the moment. I’ve addressed all the other comments you left and pushed the fixes. Let me know if there’s anything else I should look into!
author_association: CONTRIBUTOR
user: login: smitbarmase
created_at: 2025-05-11T12:59:58Z
body: Thank you so much for your work @kmpeeduwee, I'm gonna take it from here.
author_association: MEMBER
user: login: zed-industries-bot
created_at: 2025-05-11T20:41:01Z
body: 
<!--
  0 failure: 
  0 warning: 
  1 messages
  
  DangerID: danger-id-Danger;
-->



<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true">Messages</th>
    </tr>
  </thead>
  <tbody><tr>
      <td>:book:</td>
      <td>

  This PR includes links to the following GitHub Issues: #30378
If this PR aims to close an issue, please include a `Closes #ISSUE` line at the top of the PR body.
  </td>
    </tr>
  </tbody>
</table>


<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against f9aed24f11aff9dfc7ab85889bf40508d0d60969
</p>

author_association: CONTRIBUTOR