From fb2e66f61b5e3d7bbc2e26841868dfb3c95ebd56 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 18 Jun 2026 07:54:13 -0300 Subject: [PATCH] markdown_preview: Add max-width setting (#59512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces a markdown preview max-width setting in which we allow to limit by a given pixel number how wide the content within the preview tab should go. Before this, content would render edge-to-edge and if you're reading long markdown docs with no split panes, having it be full width like that was a horrible reading experience. So, right now, you can control that through the `zed://settings/markdown_preview.max_width` setting. Screenshot 2026-06-18 at 1  24@2x Release Notes: - Added setting to control max-width of content within the markdown preview. --- assets/settings/default.json | 10 +++ .../markdown_preview/src/markdown_preview.rs | 1 + .../src/markdown_preview_settings.rs | 22 +++++++ .../src/markdown_preview_view.rs | 10 ++- crates/settings/src/vscode_import.rs | 1 + .../settings_content/src/settings_content.rs | 20 ++++++ crates/settings_ui/src/page_data.rs | 61 ++++++++++++++++++- 7 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 crates/markdown_preview/src/markdown_preview_settings.rs diff --git a/assets/settings/default.json b/assets/settings/default.json index 6e4fba26a8..7841c0e3eb 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -104,6 +104,16 @@ // The unit for image file sizes: "binary" (KiB, MiB) or decimal (KB, MB) "unit": "binary", }, + // Markdown preview settings + "markdown_preview": { + // Whether to limit the width of the rendered markdown content. When + // enabled, content is constrained to `max_width` and centered + // horizontally within the preview pane. + "limit_content_width": true, + // The maximum width, in pixels, of the rendered markdown content when + // limit_content_width is enabled. + "max_width": 800, + }, // Determines the modifier to be used to add multiple cursors with the mouse. The open hover link mouse gestures will adapt such that it do not conflict with the multicursor modifier. // // 1. Maps to `Alt` on Linux and Windows and to `Option` on MacOS: diff --git a/crates/markdown_preview/src/markdown_preview.rs b/crates/markdown_preview/src/markdown_preview.rs index b16b18c24c..caf516b212 100644 --- a/crates/markdown_preview/src/markdown_preview.rs +++ b/crates/markdown_preview/src/markdown_preview.rs @@ -1,6 +1,7 @@ use gpui::{App, actions}; use workspace::Workspace; +pub mod markdown_preview_settings; pub mod markdown_preview_view; pub use zed_actions::preview::markdown::{OpenPreview, OpenPreviewToTheSide}; diff --git a/crates/markdown_preview/src/markdown_preview_settings.rs b/crates/markdown_preview/src/markdown_preview_settings.rs new file mode 100644 index 0000000000..bb624ad2cb --- /dev/null +++ b/crates/markdown_preview/src/markdown_preview_settings.rs @@ -0,0 +1,22 @@ +use gpui::{Pixels, px}; +use settings::{RegisterSetting, Settings}; + +/// The settings for the markdown preview. +#[derive(Clone, Copy, Debug, Default, RegisterSetting)] +pub struct MarkdownPreviewSettings { + /// The maximum width of the rendered markdown content, or `None` to render + /// content edge to edge. + pub max_width: Option, +} + +impl Settings for MarkdownPreviewSettings { + fn from_settings(content: &settings::SettingsContent) -> Self { + let content = content.markdown_preview.clone().unwrap_or_default(); + let max_width = if content.limit_content_width.unwrap_or(true) { + content.max_width.map(px) + } else { + None + }; + Self { max_width } + } +} diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index d1638e2fdb..2183eb557a 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -32,6 +32,7 @@ use workspace::searchable::{ }; use workspace::{ItemId, Pane, Workspace, WorkspaceId, delete_unloaded_items}; +use crate::markdown_preview_settings::MarkdownPreviewSettings; use crate::{ OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide, ScrollDown, ScrollDownByItem, }; @@ -1132,7 +1133,8 @@ impl Render for MarkdownPreviewView { let markdown_element = self.render_markdown_element(&preview_theme, window, cx); let markdown = self.markdown.clone(); - right_click_menu("markdown-preview-context-menu") + let max_width = MarkdownPreviewSettings::get_global(cx).max_width; + let content = right_click_menu("markdown-preview-context-menu") .trigger(move |_, _, _| markdown_element) .menu(move |window, cx| { let focus = window.focused(cx); @@ -1148,7 +1150,11 @@ impl Render for MarkdownPreviewView { }) }) }) - }) + }); + div() + .w_full() + .when_some(max_width, |this, max_width| this.max_w(max_width).mx_auto()) + .child(content) }), ) .vertical_scrollbar_for(&self.scroll_handle, window, cx) diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 83b5c45487..f9ef797b33 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -194,6 +194,7 @@ impl VsCodeSettings { helix_mode: None, hide_mouse: None, image_viewer: None, + markdown_preview: None, journal: None, language_models: None, line_indicator_format: None, diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index d8fa006554..fcb0ddcc65 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -178,6 +178,9 @@ pub struct SettingsContent { /// The settings for the image viewer. pub image_viewer: Option, + /// The settings for the markdown preview. + pub markdown_preview: Option, + pub repl: Option, /// Whether or not to enable Helix mode. @@ -1094,6 +1097,23 @@ pub enum LineIndicatorFormat { Long, } +/// The settings for the markdown preview. +#[with_fallible_options] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)] +pub struct MarkdownPreviewSettingsContent { + /// Whether to limit the width of the rendered markdown content. When + /// enabled, content is constrained to `max_width` and centered + /// horizontally within the preview pane, for optimal readability. + /// + /// Default: true + pub limit_content_width: Option, + /// The maximum width, in pixels, of the rendered markdown content when + /// `limit_content_width` is enabled. + /// + /// Default: 800 + pub max_width: Option, +} + /// The settings for the image viewer. #[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)] diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index a86c26c91a..12c1816d6d 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -9654,7 +9654,7 @@ fn language_settings_data() -> Box<[SettingsPageItem]> { ] } - fn global_only_miscellaneous_sub_section() -> [SettingsPageItem; 3] { + fn global_only_miscellaneous_sub_section() -> [SettingsPageItem; 4] { [ SettingsPageItem::SettingItem(SettingItem { title: "Image Viewer", @@ -9675,6 +9675,65 @@ fn language_settings_data() -> Box<[SettingsPageItem]> { metadata: None, files: USER, }), + SettingsPageItem::DynamicItem(DynamicItem { + discriminant: SettingItem { + files: USER, + title: "Limit Markdown Preview Width", + description: "Whether to constrain the markdown preview content to a maximum width, centering it when the pane is wider, for optimal readability.", + field: Box::new(SettingField:: { + organization_override: None, + json_path: Some("markdown_preview.limit_content_width"), + pick: |settings_content| { + settings_content + .markdown_preview + .as_ref()? + .limit_content_width + .as_ref() + }, + write: |settings_content, value, _| { + settings_content + .markdown_preview + .get_or_insert_default() + .limit_content_width = value; + }, + }), + metadata: None, + }, + pick_discriminant: |settings_content| { + let enabled = settings_content + .markdown_preview + .as_ref()? + .limit_content_width + .unwrap_or(true); + Some(if enabled { 1 } else { 0 }) + }, + fields: vec![ + vec![], + vec![SettingItem { + files: USER, + title: "Max Width", + description: "Maximum content width in pixels. Content will be centered when the pane is wider than this value.", + field: Box::new(SettingField { + organization_override: None, + json_path: Some("markdown_preview.max_width"), + pick: |settings_content| { + settings_content + .markdown_preview + .as_ref()? + .max_width + .as_ref() + }, + write: |settings_content, value, _| { + settings_content + .markdown_preview + .get_or_insert_default() + .max_width = value; + }, + }), + metadata: None, + }], + ], + }), SettingsPageItem::SettingItem(SettingItem { title: "Auto Replace Emoji Shortcode", description: "Whether to automatically replace emoji shortcodes with emoji characters.",