Allow agent and terminal panels to be either flexible or fixed (#52694)

Max Brunsfeld created

This PR adds the ability to change both the terminal and agent panels
between fixed and flexible sizing using the status bar button right
click menu. The value persists in your settings, similar to the dock
position.

I've also slightly tweaked the styling of the "Dock Left" and "Dock
Right" items in the right-click menu, adding the current value as an
item with a check beside it, to make it clear that it's a selectable
option.

Release Notes:

- N/A

Change summary

assets/settings/default.json                |   8 
crates/agent/src/tool_permissions.rs        |   1 
crates/agent_settings/src/agent_settings.rs |   2 
crates/agent_ui/src/agent_panel.rs          |  15 +
crates/agent_ui/src/agent_ui.rs             |   1 
crates/settings/src/vscode_import.rs        |   1 
crates/settings_content/src/agent.rs        |   8 
crates/settings_content/src/terminal.rs     |   4 
crates/terminal/src/terminal_settings.rs    |   2 
crates/terminal_view/src/terminal_panel.rs  |  14 +
crates/workspace/src/dock.rs                | 176 +++++++++++++++++--
crates/workspace/src/workspace.rs           | 203 +++++++++++++++++-----
12 files changed, 364 insertions(+), 71 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -943,6 +943,10 @@
     "button": true,
     // Where to dock the agent panel. Can be 'left', 'right' or 'bottom'.
     "dock": "right",
+    // Whether the agent panel should use flexible (proportional) sizing.
+    //
+    // Default: true
+    "flexible": true,
     // Where to position the sidebar. Can be 'left', 'right', or 'follow_agent'.
     "sidebar_side": "follow_agent",
     // Default width when the agent panel is docked to the left or right.
@@ -1651,6 +1655,10 @@
     "shell": "system",
     // Where to dock terminals panel. Can be `left`, `right`, `bottom`.
     "dock": "bottom",
+    // Whether the terminal panel should use flexible (proportional) sizing.
+    //
+    // Default: true
+    "flexible": true,
     // Default width when the terminal is docked to the left or right.
     "default_width": 640,
     // Default height when the terminal is docked to the bottom.

crates/agent/src/tool_permissions.rs 🔗

@@ -571,6 +571,7 @@ mod tests {
             enabled: true,
             button: true,
             dock: DockPosition::Right,
+            flexible: true,
             default_width: px(300.),
             default_height: px(600.),
             default_model: None,

crates/agent_settings/src/agent_settings.rs 🔗

@@ -27,6 +27,7 @@ pub struct AgentSettings {
     pub enabled: bool,
     pub button: bool,
     pub dock: DockPosition,
+    pub flexible: bool,
     pub sidebar_side: SidebarDockPosition,
     pub default_width: Pixels,
     pub default_height: Pixels,
@@ -424,6 +425,7 @@ impl Settings for AgentSettings {
             sidebar_side: agent.sidebar_side.unwrap(),
             default_width: px(agent.default_width.unwrap()),
             default_height: px(agent.default_height.unwrap()),
+            flexible: agent.flexible.unwrap(),
             default_model: Some(agent.default_model.unwrap()),
             inline_assistant_model: agent.inline_assistant_model,
             inline_assistant_use_streaming_tools: agent

crates/agent_ui/src/agent_panel.rs 🔗

@@ -3157,10 +3157,23 @@ impl Panel for AgentPanel {
         }
     }
 
-    fn supports_flexible_size(&self, _window: &Window, _cx: &App) -> bool {
+    fn supports_flexible_size(&self) -> bool {
         true
     }
 
+    fn has_flexible_size(&self, _window: &Window, cx: &App) -> bool {
+        AgentSettings::get_global(cx).flexible
+    }
+
+    fn set_flexible_size(&mut self, flexible: bool, _window: &mut Window, cx: &mut Context<Self>) {
+        settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
+            settings
+                .agent
+                .get_or_insert_default()
+                .set_flexible_size(flexible);
+        });
+    }
+
     fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
         if active
             && matches!(self.active_view, ActiveView::Uninitialized)

crates/agent_ui/src/agent_ui.rs 🔗

@@ -647,6 +647,7 @@ mod tests {
             enabled: true,
             button: true,
             dock: DockPosition::Right,
+            flexible: true,
             default_width: px(300.),
             default_height: px(600.),
             default_model: None,

crates/settings_content/src/agent.rs 🔗

@@ -109,6 +109,10 @@ pub struct AgentSettingsContent {
     ///
     /// Default: right
     pub dock: Option<DockPosition>,
+    /// Whether the agent panel should use flexible (proportional) sizing.
+    ///
+    /// Default: true
+    pub flexible: Option<bool>,
     /// Where to position the sidebar.
     ///
     /// Default: follow_agent
@@ -230,6 +234,10 @@ impl AgentSettingsContent {
         self.sidebar_side = Some(position);
     }
 
+    pub fn set_flexible_size(&mut self, flexible: bool) {
+        self.flexible = Some(flexible);
+    }
+
     pub fn set_model(&mut self, language_model: LanguageModelSelection) {
         self.default_model = Some(language_model)
     }

crates/settings_content/src/terminal.rs 🔗

@@ -129,6 +129,10 @@ pub struct TerminalSettingsContent {
     /// Default: true
     pub button: Option<bool>,
     pub dock: Option<TerminalDockPosition>,
+    /// Whether the terminal panel should use flexible (proportional) sizing.
+    ///
+    /// Default: true
+    pub flexible: Option<bool>,
     /// Default width when the terminal is docked to the left or right.
     ///
     /// Default: 640

crates/terminal/src/terminal_settings.rs 🔗

@@ -40,6 +40,7 @@ pub struct TerminalSettings {
     pub keep_selection_on_copy: bool,
     pub button: bool,
     pub dock: TerminalDockPosition,
+    pub flexible: bool,
     pub default_width: Pixels,
     pub default_height: Pixels,
     pub detect_venv: VenvSettings,
@@ -110,6 +111,7 @@ impl settings::Settings for TerminalSettings {
             dock: user_content.dock.unwrap(),
             default_width: px(user_content.default_width.unwrap()),
             default_height: px(user_content.default_height.unwrap()),
+            flexible: user_content.flexible.unwrap(),
             detect_venv: project_content.detect_venv.unwrap(),
             scroll_multiplier: user_content.scroll_multiplier.unwrap(),
             max_scroll_history_lines: user_content.max_scroll_history_lines,

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -1574,6 +1574,20 @@ impl Panel for TerminalPanel {
         }
     }
 
+    fn supports_flexible_size(&self) -> bool {
+        true
+    }
+
+    fn has_flexible_size(&self, _window: &Window, cx: &App) -> bool {
+        TerminalSettings::get_global(cx).flexible
+    }
+
+    fn set_flexible_size(&mut self, flexible: bool, _window: &mut Window, cx: &mut Context<Self>) {
+        settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
+            settings.terminal.get_or_insert_default().flexible = Some(flexible);
+        });
+    }
+
     fn is_zoomed(&self, _window: &Window, cx: &App) -> bool {
         self.active_pane.read(cx).is_zoomed()
     }

crates/workspace/src/dock.rs 🔗

@@ -42,9 +42,19 @@ pub trait Panel: Focusable + EventEmitter<PanelEvent> + Render + Sized {
         PanelSizeState::default()
     }
     fn size_state_changed(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
-    fn supports_flexible_size(&self, _window: &Window, _cx: &App) -> bool {
+    fn supports_flexible_size(&self) -> bool {
         false
     }
+    fn has_flexible_size(&self, _window: &Window, _cx: &App) -> bool {
+        false
+    }
+    fn set_flexible_size(
+        &mut self,
+        _flexible: bool,
+        _window: &mut Window,
+        _cx: &mut Context<Self>,
+    ) {
+    }
     fn icon(&self, window: &Window, cx: &App) -> Option<ui::IconName>;
     fn icon_tooltip(&self, window: &Window, cx: &App) -> Option<&'static str>;
     fn toggle_action(&self) -> Box<dyn Action>;
@@ -89,7 +99,9 @@ pub trait PanelHandle: Send + Sync {
     fn default_size(&self, window: &Window, cx: &App) -> Pixels;
     fn initial_size_state(&self, window: &Window, cx: &App) -> PanelSizeState;
     fn size_state_changed(&self, window: &mut Window, cx: &mut App);
-    fn supports_flexible_size(&self, window: &Window, cx: &App) -> bool;
+    fn supports_flexible_size(&self, cx: &App) -> bool;
+    fn has_flexible_size(&self, window: &Window, cx: &App) -> bool;
+    fn set_flexible_size(&self, flexible: bool, window: &mut Window, cx: &mut App);
     fn icon(&self, window: &Window, cx: &App) -> Option<ui::IconName>;
     fn icon_tooltip(&self, window: &Window, cx: &App) -> Option<&'static str>;
     fn toggle_action(&self, window: &Window, cx: &App) -> Box<dyn Action>;
@@ -176,8 +188,16 @@ where
         self.update(cx, |this, cx| this.size_state_changed(window, cx))
     }
 
-    fn supports_flexible_size(&self, window: &Window, cx: &App) -> bool {
-        self.read(cx).supports_flexible_size(window, cx)
+    fn supports_flexible_size(&self, cx: &App) -> bool {
+        self.read(cx).supports_flexible_size()
+    }
+
+    fn has_flexible_size(&self, window: &Window, cx: &App) -> bool {
+        self.read(cx).has_flexible_size(window, cx)
+    }
+
+    fn set_flexible_size(&self, flexible: bool, window: &mut Window, cx: &mut App) {
+        self.update(cx, |this, cx| this.set_flexible_size(flexible, window, cx))
     }
 
     fn icon(&self, window: &Window, cx: &App) -> Option<ui::IconName> {
@@ -292,7 +312,7 @@ impl DockPosition {
 pub struct PanelSizeState {
     pub size: Option<Pixels>,
     #[serde(default)]
-    pub flexible_size_ratio: Option<f32>,
+    pub flex: Option<f32>,
 }
 
 struct PanelEntry {
@@ -840,10 +860,48 @@ impl Dock {
         }
     }
 
+    pub fn toggle_panel_flexible_size(
+        &mut self,
+        panel: &dyn PanelHandle,
+        current_size: Option<Pixels>,
+        current_flex: Option<f32>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let Some(entry) = self
+            .panel_entries
+            .iter_mut()
+            .find(|entry| entry.panel.panel_id() == panel.panel_id())
+        else {
+            return;
+        };
+        let currently_flexible = entry.panel.has_flexible_size(window, cx);
+        if currently_flexible {
+            entry.size_state.size = current_size;
+        } else {
+            entry.size_state.flex = current_flex;
+        }
+        let panel_key = entry.panel.panel_key();
+        let size_state = entry.size_state;
+        let workspace = self.workspace.clone();
+        entry
+            .panel
+            .set_flexible_size(!currently_flexible, window, cx);
+        entry.panel.size_state_changed(window, cx);
+        cx.defer(move |cx| {
+            if let Some(workspace) = workspace.upgrade() {
+                workspace.update(cx, |workspace, cx| {
+                    workspace.persist_panel_size_state(panel_key, size_state, cx);
+                });
+            }
+        });
+        cx.notify();
+    }
+
     pub fn resize_active_panel(
         &mut self,
         size: Option<Pixels>,
-        ratio: Option<f32>,
+        flex: Option<f32>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -852,8 +910,9 @@ impl Dock {
         {
             let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round());
 
-            if entry.panel.supports_flexible_size(window, cx) {
-                entry.size_state.flexible_size_ratio = ratio;
+            let use_flex = entry.panel.has_flexible_size(window, cx);
+            if use_flex {
+                entry.size_state.flex = flex;
             } else {
                 entry.size_state.size = size;
             }
@@ -876,7 +935,7 @@ impl Dock {
     pub fn resize_all_panels(
         &mut self,
         size: Option<Pixels>,
-        ratio: Option<f32>,
+        flex: Option<f32>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -884,8 +943,9 @@ impl Dock {
 
         for entry in &mut self.panel_entries {
             let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round());
-            if entry.panel.supports_flexible_size(window, cx) {
-                entry.size_state.flexible_size_ratio = ratio;
+            let use_flex = entry.panel.has_flexible_size(window, cx);
+            if use_flex {
+                entry.size_state.flex = flex;
             } else {
                 entry.size_state.size = size;
             }
@@ -925,7 +985,8 @@ impl Dock {
     pub fn clamp_panel_size(&mut self, max_size: Pixels, window: &Window, cx: &mut App) {
         let max_size = (max_size - RESIZE_HANDLE_SIZE).abs();
         for entry in &mut self.panel_entries {
-            if entry.panel.supports_flexible_size(window, cx) {
+            let use_flexible = entry.panel.has_flexible_size(window, cx);
+            if use_flexible {
                 continue;
             }
 
@@ -1087,6 +1148,8 @@ impl Render for PanelButtons {
             DockPosition::Bottom | DockPosition::Right => (Corner::BottomRight, Corner::TopRight),
         };
 
+        let dock_entity = self.dock.clone();
+        let workspace = dock.workspace.clone();
         let mut buttons: Vec<_> = dock
             .panel_entries
             .iter()
@@ -1102,6 +1165,10 @@ impl Render for PanelButtons {
                     .log_err()?;
                 let name = entry.panel.persistent_name();
                 let panel = entry.panel.clone();
+                let supports_flexible = panel.supports_flexible_size(cx);
+                let currently_flexible = panel.has_flexible_size(window, cx);
+                let dock_for_menu = dock_entity.clone();
+                let workspace_for_menu = workspace.clone();
 
                 let is_active_button = Some(i) == active_index && is_open;
                 let (action, tooltip) = if is_active_button {
@@ -1130,20 +1197,76 @@ impl Render for PanelButtons {
                             ];
 
                             ContextMenu::build(window, cx, |mut menu, _, cx| {
+                                let mut has_position_entries = false;
                                 for position in POSITIONS {
-                                    if position != dock_position
-                                        && panel.position_is_valid(position, cx)
-                                    {
+                                    if panel.position_is_valid(position, cx) {
+                                        let is_current = position == dock_position;
                                         let panel = panel.clone();
-                                        menu = menu.entry(
+                                        menu = menu.toggleable_entry(
                                             format!("Dock {}", position.label()),
+                                            is_current,
+                                            IconPosition::Start,
                                             None,
                                             move |window, cx| {
-                                                panel.set_position(position, window, cx);
+                                                if !is_current {
+                                                    panel.set_position(position, window, cx);
+                                                }
                                             },
-                                        )
+                                        );
+                                        has_position_entries = true;
                                     }
                                 }
+                                if supports_flexible {
+                                    if has_position_entries {
+                                        menu = menu.separator();
+                                    }
+                                    let panel_for_flex = panel.clone();
+                                    let dock_for_flex = dock_for_menu.clone();
+                                    let workspace_for_flex = workspace_for_menu.clone();
+                                    menu = menu.toggleable_entry(
+                                        "Flex Width",
+                                        currently_flexible,
+                                        IconPosition::Start,
+                                        None,
+                                        move |window, cx| {
+                                            if !currently_flexible {
+                                                if let Some(ws) = workspace_for_flex.upgrade() {
+                                                    ws.update(cx, |workspace, cx| {
+                                                        workspace.toggle_dock_panel_flexible_size(
+                                                            &dock_for_flex,
+                                                            panel_for_flex.as_ref(),
+                                                            window,
+                                                            cx,
+                                                        );
+                                                    });
+                                                }
+                                            }
+                                        },
+                                    );
+                                    let panel_for_fixed = panel.clone();
+                                    let dock_for_fixed = dock_for_menu.clone();
+                                    let workspace_for_fixed = workspace_for_menu.clone();
+                                    menu = menu.toggleable_entry(
+                                        "Fixed Width",
+                                        !currently_flexible,
+                                        IconPosition::Start,
+                                        None,
+                                        move |window, cx| {
+                                            if currently_flexible {
+                                                if let Some(ws) = workspace_for_fixed.upgrade() {
+                                                    ws.update(cx, |workspace, cx| {
+                                                        workspace.toggle_dock_panel_flexible_size(
+                                                            &dock_for_fixed,
+                                                            panel_for_fixed.as_ref(),
+                                                            window,
+                                                            cx,
+                                                        );
+                                                    });
+                                                }
+                                            }
+                                        },
+                                    );
+                                }
                                 menu
                             })
                         })
@@ -1290,14 +1413,27 @@ pub mod test {
         fn initial_size_state(&self, _window: &Window, _: &App) -> PanelSizeState {
             PanelSizeState {
                 size: None,
-                flexible_size_ratio: None,
+                flex: None,
             }
         }
 
-        fn supports_flexible_size(&self, _window: &Window, _: &App) -> bool {
+        fn supports_flexible_size(&self) -> bool {
             self.flexible
         }
 
+        fn has_flexible_size(&self, _window: &Window, _: &App) -> bool {
+            self.flexible
+        }
+
+        fn set_flexible_size(
+            &mut self,
+            flexible: bool,
+            _window: &mut Window,
+            _cx: &mut Context<Self>,
+        ) {
+            self.flexible = flexible;
+        }
+
         fn icon(&self, _window: &Window, _: &App) -> Option<ui::IconName> {
             None
         }

crates/workspace/src/workspace.rs 🔗

@@ -150,7 +150,7 @@ pub use workspace_settings::{
 };
 use zed_actions::{Spawn, feedback::FileBugReport, theme::ToggleMode};
 
-use crate::{item::ItemBufferKind, notifications::NotificationId};
+use crate::{dock::PanelSizeState, item::ItemBufferKind, notifications::NotificationId};
 use crate::{
     persistence::{
         SerializedAxis,
@@ -2201,6 +2201,22 @@ impl Workspace {
         did_set
     }
 
+    pub fn toggle_dock_panel_flexible_size(
+        &self,
+        dock: &Entity<Dock>,
+        panel: &dyn PanelHandle,
+        window: &mut Window,
+        cx: &mut App,
+    ) {
+        let position = dock.read(cx).position();
+        let current_size = self.dock_size(&dock.read(cx), window, cx);
+        let current_flex =
+            current_size.and_then(|size| self.dock_flex_for_size(position, size, window, cx));
+        dock.update(cx, |dock, cx| {
+            dock.toggle_panel_flexible_size(panel, current_size, current_flex, window, cx);
+        });
+    }
+
     fn dock_size(&self, dock: &Dock, window: &Window, cx: &App) -> Option<Pixels> {
         let panel = dock.active_panel()?;
         let size_state = dock
@@ -2208,15 +2224,30 @@ impl Workspace {
             .unwrap_or_default();
         let position = dock.position();
 
+        let use_flex = panel.has_flexible_size(window, cx);
+
         if position.axis() == Axis::Horizontal
-            && panel.supports_flexible_size(window, cx)
-            && let Some(ratio) = size_state
-                .flexible_size_ratio
-                .or_else(|| self.default_flexible_dock_ratio(position))
-            && let Some(available_width) =
-                self.available_width_for_horizontal_dock(position, window, cx)
+            && use_flex
+            && let Some(flex) = size_state.flex.or_else(|| self.default_dock_flex(position))
         {
-            return Some((available_width * ratio.clamp(0.0, 1.0)).max(RESIZE_HANDLE_SIZE));
+            let workspace_width = self.bounds.size.width;
+            if workspace_width <= Pixels::ZERO {
+                return None;
+            }
+            let flex = flex.max(0.001);
+            let opposite = self.opposite_dock_panel_and_size_state(position, window, cx);
+            if let Some(opposite_flex) = opposite.as_ref().and_then(|(_, s)| s.flex) {
+                // Both docks are flex items sharing the full workspace width.
+                let total_flex = flex + 1.0 + opposite_flex;
+                return Some((flex / total_flex * workspace_width).max(RESIZE_HANDLE_SIZE));
+            } else {
+                // Opposite dock is fixed-width; flex items share (W - fixed).
+                let opposite_fixed = opposite
+                    .map(|(panel, s)| s.size.unwrap_or_else(|| panel.default_size(window, cx)))
+                    .unwrap_or_default();
+                let available = (workspace_width - opposite_fixed).max(RESIZE_HANDLE_SIZE);
+                return Some((flex / (flex + 1.0) * available).max(RESIZE_HANDLE_SIZE));
+            }
         }
 
         Some(
@@ -2226,7 +2257,7 @@ impl Workspace {
         )
     }
 
-    pub fn flexible_dock_ratio_for_size(
+    pub fn dock_flex_for_size(
         &self,
         position: DockPosition,
         size: Pixels,
@@ -2237,45 +2268,55 @@ impl Workspace {
             return None;
         }
 
-        let available_width = self.available_width_for_horizontal_dock(position, window, cx)?;
-        let available_width = available_width.max(RESIZE_HANDLE_SIZE);
-        Some((size / available_width).clamp(0.0, 1.0))
+        let workspace_width = self.bounds.size.width;
+        if workspace_width <= Pixels::ZERO {
+            return None;
+        }
+
+        let opposite = self.opposite_dock_panel_and_size_state(position, window, cx);
+        if let Some(opposite_flex) = opposite.as_ref().and_then(|(_, s)| s.flex) {
+            let size = size.clamp(px(0.), workspace_width - px(1.));
+            Some((size * (1.0 + opposite_flex) / (workspace_width - size)).max(0.0))
+        } else {
+            let opposite_width = opposite
+                .map(|(panel, s)| s.size.unwrap_or_else(|| panel.default_size(window, cx)))
+                .unwrap_or_default();
+            let available = (workspace_width - opposite_width).max(RESIZE_HANDLE_SIZE);
+            let remaining = (available - size).max(px(1.));
+            Some((size / remaining).max(0.0))
+        }
     }
 
-    fn available_width_for_horizontal_dock(
+    fn opposite_dock_panel_and_size_state(
         &self,
         position: DockPosition,
         window: &Window,
         cx: &App,
-    ) -> Option<Pixels> {
-        let workspace_width = self.bounds.size.width;
-        if workspace_width <= Pixels::ZERO {
-            return None;
-        }
-
+    ) -> Option<(Arc<dyn PanelHandle>, PanelSizeState)> {
         let opposite_position = match position {
             DockPosition::Left => DockPosition::Right,
             DockPosition::Right => DockPosition::Left,
             DockPosition::Bottom => return None,
         };
 
-        let opposite_width = self
-            .dock_at_position(opposite_position)
-            .read(cx)
-            .stored_active_panel_size(window, cx)
-            .unwrap_or(Pixels::ZERO);
-
-        Some((workspace_width - opposite_width).max(RESIZE_HANDLE_SIZE))
+        let opposite_dock = self.dock_at_position(opposite_position).read(cx);
+        let panel = opposite_dock.visible_panel()?;
+        let mut size_state = opposite_dock
+            .stored_panel_size_state(panel.as_ref())
+            .unwrap_or_default();
+        if size_state.flex.is_none() && panel.has_flexible_size(window, cx) {
+            size_state.flex = self.default_dock_flex(opposite_position);
+        }
+        Some((panel.clone(), size_state))
     }
 
-    pub fn default_flexible_dock_ratio(&self, position: DockPosition) -> Option<f32> {
+    pub fn default_dock_flex(&self, position: DockPosition) -> Option<f32> {
         if position.axis() != Axis::Horizontal {
             return None;
         }
 
         let pane = self.last_active_center_pane.clone()?.upgrade()?;
-        let pane_fraction = self.center.width_fraction_for_pane(&pane).unwrap_or(1.0);
-        Some((pane_fraction / (1.0 + pane_fraction)).clamp(0.0, 1.0))
+        Some(self.center.width_fraction_for_pane(&pane).unwrap_or(1.0))
     }
 
     pub fn is_edited(&self) -> bool {
@@ -2301,7 +2342,7 @@ impl Workspace {
                     load_legacy_panel_size(T::panel_key(), dock_position, self, cx).map(|size| {
                         let state = dock::PanelSizeState {
                             size: Some(size),
-                            flexible_size_ratio: None,
+                            flex: None,
                         };
                         self.persist_panel_size_state(T::panel_key(), state, cx);
                         state
@@ -7277,13 +7318,16 @@ impl Workspace {
         if let Some(panel) = dock.visible_panel() {
             let size_state = dock.stored_panel_size_state(panel.as_ref());
             if position.axis() == Axis::Horizontal {
-                if let Some(ratio) = size_state
-                    .and_then(|state| state.flexible_size_ratio)
-                    .or_else(|| self.default_flexible_dock_ratio(position))
-                    && panel.supports_flexible_size(window, cx)
-                {
-                    let ratio = ratio.clamp(0.001, 0.999);
-                    let grow = ratio / (1.0 - ratio);
+                let use_flexible = panel.has_flexible_size(window, cx);
+                let flex_grow = if use_flexible {
+                    size_state
+                        .and_then(|state| state.flex)
+                        .or_else(|| self.default_dock_flex(position))
+                } else {
+                    None
+                };
+                if let Some(grow) = flex_grow {
+                    let grow = grow.max(0.001);
                     let style = container.style();
                     style.flex_grow = Some(grow);
                     style.flex_shrink = Some(1.0);
@@ -7399,15 +7443,15 @@ impl Workspace {
             }
         });
 
-        let ratio = self.flexible_dock_ratio_for_size(DockPosition::Left, size, window, cx);
+        let flex_grow = self.dock_flex_for_size(DockPosition::Left, size, window, cx);
         self.left_dock.update(cx, |left_dock, cx| {
             if WorkspaceSettings::get_global(cx)
                 .resize_all_panels_in_dock
                 .contains(&DockPosition::Left)
             {
-                left_dock.resize_all_panels(Some(size), ratio, window, cx);
+                left_dock.resize_all_panels(Some(size), flex_grow, window, cx);
             } else {
-                left_dock.resize_active_panel(Some(size), ratio, window, cx);
+                left_dock.resize_active_panel(Some(size), flex_grow, window, cx);
             }
         });
     }
@@ -7423,15 +7467,15 @@ impl Workspace {
                 size = workspace_width - left_dock_size
             }
         });
-        let ratio = self.flexible_dock_ratio_for_size(DockPosition::Right, size, window, cx);
+        let flex_grow = self.dock_flex_for_size(DockPosition::Right, size, window, cx);
         self.right_dock.update(cx, |right_dock, cx| {
             if WorkspaceSettings::get_global(cx)
                 .resize_all_panels_in_dock
                 .contains(&DockPosition::Right)
             {
-                right_dock.resize_all_panels(Some(size), ratio, window, cx);
+                right_dock.resize_all_panels(Some(size), flex_grow, window, cx);
             } else {
-                right_dock.resize_active_panel(Some(size), ratio, window, cx);
+                right_dock.resize_active_panel(Some(size), flex_grow, window, cx);
             }
         });
     }
@@ -12346,8 +12390,11 @@ mod tests {
             assert_eq!(
                 right_dock
                     .stored_panel_size_state(flexible_panel.as_ref())
-                    .and_then(|size_state| size_state.flexible_size_ratio),
-                Some(resized_width.to_f64() as f32 / workspace.bounds.size.width.to_f64() as f32)
+                    .and_then(|size_state| size_state.flex),
+                Some(
+                    resized_width.to_f64() as f32
+                        / (workspace.bounds.size.width - resized_width).to_f64() as f32
+                )
             );
         });
 
@@ -12481,9 +12528,7 @@ mod tests {
                 persisted.size, None,
                 "flexible panel should not persist a redundant pixel size"
             );
-            let original_ratio = persisted
-                .flexible_size_ratio
-                .expect("flexible panel ratio should be persisted");
+            let original_ratio = persisted.flex.expect("panel's flex should be persisted");
 
             // Remove the panel and re-add: both size and ratio should be restored.
             workspace.update_in(cx, |workspace, window, cx| {
@@ -12504,9 +12549,9 @@ mod tests {
                     "re-added flexible panel should not have a persisted pixel size"
                 );
                 assert_eq!(
-                    size_state.flexible_size_ratio,
+                    size_state.flex,
                     Some(original_ratio),
-                    "re-added flexible panel should restore persisted ratio"
+                    "re-added flexible panel should restore persisted flex"
                 );
             });
         }
@@ -12599,6 +12644,64 @@ mod tests {
                 "flexible left panel should shrink proportionally as the right dock takes space"
             );
         });
+
+        // Step 4: Toggle the right dock's panel to flexible. Now both docks use
+        // flex sizing and the workspace width is divided among left-flex, center
+        // (implicit flex 1.0), and right-flex.
+        workspace.update_in(cx, |workspace, window, cx| {
+            let right_dock = workspace.right_dock().clone();
+            let right_panel = right_dock
+                .read(cx)
+                .visible_panel()
+                .expect("right dock should have a visible panel")
+                .clone();
+            workspace.toggle_dock_panel_flexible_size(
+                &right_dock,
+                right_panel.as_ref(),
+                window,
+                cx,
+            );
+
+            let right_dock = right_dock.read(cx);
+            let right_panel = right_dock
+                .visible_panel()
+                .expect("right dock should still have a visible panel");
+            assert!(
+                right_panel.has_flexible_size(window, cx),
+                "right panel should now be flexible"
+            );
+
+            let right_size_state = right_dock
+                .stored_panel_size_state(right_panel.as_ref())
+                .expect("right panel should have a stored size state after toggling");
+            let right_flex = right_size_state
+                .flex
+                .expect("right panel should have a flex value after toggling");
+
+            let left_dock = workspace.left_dock().read(cx);
+            let left_width = workspace
+                .dock_size(&left_dock, window, cx)
+                .expect("left dock should still have an active panel");
+            let right_width = workspace
+                .dock_size(&right_dock, window, cx)
+                .expect("right dock should still have an active panel");
+
+            let left_flex = workspace
+                .default_dock_flex(DockPosition::Left)
+                .expect("left dock should have a default flex");
+
+            let total_flex = left_flex + 1.0 + right_flex;
+            let expected_left = left_flex / total_flex * workspace.bounds.size.width;
+            let expected_right = right_flex / total_flex * workspace.bounds.size.width;
+            assert_eq!(
+                left_width, expected_left,
+                "flexible left panel should share workspace width via flex ratios"
+            );
+            assert_eq!(
+                right_width, expected_right,
+                "flexible right panel should share workspace width via flex ratios"
+            );
+        });
     }
 
     struct TestModal(FocusHandle);