Compare commits

...

15 Commits

Author SHA1 Message Date
Mikayla
e19f6416d4 Merge branch 'main' into tab-bar-settings 2024-04-26 14:32:57 -07:00
Andrew Lygin
6fb6cd3c5c Merge branch 'main' into tab-bar-settings 2024-04-21 08:15:22 +03:00
Andrew Lygin
0875257852 Merge branch 'main' into tab-bar-settings 2024-04-17 21:40:03 +03:00
Andrew Lygin
eb97c311c8 Move the placement setting to tab_bar 2024-04-13 18:23:09 +03:00
Andrew Lygin
6422fdea9b Merge branch 'main' into tab-bar-settings 2024-04-13 10:25:23 +03:00
Andrew Lygin
9d684d7856 Resolve conflicts 2024-03-23 16:30:10 +03:00
Andrew Lygin
93978f6017 Fix tests 2024-03-23 16:08:05 +03:00
Andrew Lygin
120ead9429 Fix tests 2024-03-23 16:08:05 +03:00
Andrew Lygin
99a0356a56 Fix tests 2024-03-23 16:08:05 +03:00
Andrew Lygin
94858caff5 Fix typo 2024-03-23 16:08:05 +03:00
Andrew Lygin
ca9645c2bf Move tab bar placement setting to the "tabs" section 2024-03-23 16:08:05 +03:00
Andrew Lygin
b81e8971df Fix default tab bar placement 2024-03-23 15:51:57 +03:00
Andrew Lygin
ed2651d62a Documentation for the editor tab bar settings 2024-03-23 15:51:56 +03:00
Andrew Lygin
b8a6fc316f Support tab bar rendering when it's at the bottom 2024-03-23 15:51:56 +03:00
Andrew Lygin
2fa9220f44 Settings for the editor tab bar placement 2024-03-23 15:46:35 +03:00
15 changed files with 259 additions and 36 deletions

View File

@@ -312,6 +312,16 @@
"autosave": "off",
// Settings related to the editor's tab bar.
"tab_bar": {
// Where to show the tab bar in the editor.
// This setting can take three values:
//
// 1. Show tab bar at the top of the editor (default):
// "top"
// 2. Show tab bar at the bottom of the editor:
// "bottom"
// 3. Don't show the tab bar:
// "no"
"placement": "top",
// Whether or not to show the navigation history buttons.
"show_nav_history_buttons": true
},

View File

@@ -630,6 +630,7 @@ mod tests {
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
Project::init_settings(cx);
language::init(cx);

View File

@@ -1821,6 +1821,7 @@ pub mod tests {
cx.set_global(settings);
language::init(cx);
crate::init(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
theme::init(LoadThemes::JustBase, cx);
cx.update_global::<SettingsStore, _>(|store, cx| {

View File

@@ -130,12 +130,11 @@ use ui::{
Tooltip,
};
use util::{defer, maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::item::ItemHandle;
use workspace::notifications::NotificationId;
use workspace::{
searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, WorkspaceId,
item::ItemHandle, notifications::NotificationId, searchable::SearchEvent, ItemNavHistory,
OpenInTerminal, OpenTerminal, SplitDirection, TabBarPlacement, TabBarSettings, Toast, ViewId,
Workspace, WorkspaceId,
};
use workspace::{OpenInTerminal, OpenTerminal, Toast};
use crate::hover_links::find_url;
@@ -419,6 +418,7 @@ pub struct Editor {
hovered_cursors: HashMap<HoveredCursor, Task<()>>,
pub show_local_selections: bool,
mode: EditorMode,
tab_bar_placement: TabBarPlacement,
show_breadcrumbs: bool,
show_gutter: bool,
show_wrap_guides: Option<bool>,
@@ -1453,6 +1453,7 @@ impl Editor {
blink_manager: blink_manager.clone(),
show_local_selections: true,
mode,
tab_bar_placement: TabBarSettings::get_global(cx).placement,
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
show_gutter: mode == EditorMode::Full,
show_wrap_guides: None,
@@ -9620,6 +9621,7 @@ impl Editor {
let editor_settings = EditorSettings::get_global(cx);
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
self.tab_bar_placement = TabBarSettings::get_global(cx).placement;
if self.mode == EditorMode::Full {
let inline_blame_enabled = ProjectSettings::get_global(cx).git.inline_blame_enabled();

View File

@@ -19,7 +19,10 @@ use language::{
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use workspace::item::{ItemSettings, TabContentParams};
use workspace::{
item::{TabContentParams, TabsSettings},
TabBarPlacement,
};
use std::{
borrow::Cow,
@@ -596,7 +599,7 @@ impl Item for Editor {
}
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
let label_color = if ItemSettings::get_global(cx).git_status {
let label_color = if TabsSettings::get_global(cx).git_status {
self.buffer()
.read(cx)
.as_singleton()
@@ -799,6 +802,10 @@ impl Item for Editor {
self.pixel_position_of_newest_cursor
}
fn tab_bar_placement(&self) -> TabBarPlacement {
self.tab_bar_placement
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
if self.show_breadcrumbs {
ToolbarItemLocation::PrimaryLeft

View File

@@ -1032,6 +1032,7 @@ mod tests {
theme::init(theme::LoadThemes::JustBase, cx);
language::init(cx);
crate::init(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
}
}

View File

@@ -108,6 +108,7 @@ fn init_test(cx: &mut gpui::TestAppContext) {
release_channel::init("0.0.0", cx);
language::init(cx);
client::init_settings(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
editor::init_settings(cx);
});

View File

@@ -1106,7 +1106,7 @@ mod tests {
let store = settings::SettingsStore::test(cx);
cx.set_global(store);
editor::init(cx);
workspace::init_settings(cx);
language::init(cx);
Project::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);

View File

@@ -1,10 +1,11 @@
use std::cmp::Ordering;
use crate::TabBarPlacement;
use crate::{prelude::*, BASE_REM_SIZE_IN_PX};
use gpui::{AnyElement, IntoElement, Stateful};
use smallvec::SmallVec;
use crate::{prelude::*, BASE_REM_SIZE_IN_PX};
/// The position of a [`Tab`] within a list of tabs.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TabPosition {
@@ -30,6 +31,7 @@ pub enum TabCloseSide {
pub struct Tab {
div: Stateful<Div>,
selected: bool,
tab_bar_placement: TabBarPlacement,
position: TabPosition,
close_side: TabCloseSide,
start_slot: Option<AnyElement>,
@@ -45,6 +47,7 @@ impl Tab {
.id(id.clone())
.debug_selector(|| format!("TAB-{}", id)),
selected: false,
tab_bar_placement: TabBarPlacement::Top,
position: TabPosition::First,
close_side: TabCloseSide::End,
start_slot: None,
@@ -57,6 +60,11 @@ impl Tab {
const CONTENT_HEIGHT_IN_REMS: f32 = 28. / BASE_REM_SIZE_IN_PX;
pub fn tab_bar_placement(mut self, tab_bar_placement: TabBarPlacement) -> Self {
self.tab_bar_placement = tab_bar_placement;
self
}
pub fn position(mut self, position: TabPosition) -> Self {
self.position = position;
self
@@ -117,6 +125,9 @@ impl RenderOnce for Tab {
),
};
let placement_top = self.tab_bar_placement == TabBarPlacement::Top;
let placement_bottom = self.tab_bar_placement == TabBarPlacement::Bottom;
self.div
.h(rems(Self::CONTAINER_HEIGHT_IN_REMS))
.bg(tab_bg)
@@ -124,21 +135,46 @@ impl RenderOnce for Tab {
.map(|this| match self.position {
TabPosition::First => {
if self.selected {
this.pl_px().border_r().pb_px()
this.pl_px()
.border_r()
.when(placement_top, Styled::pb_px)
.when(placement_bottom, Styled::pt_px)
} else {
this.pl_px().pr_px().border_b()
this.pl_px()
.pr_px()
.when(placement_top, Styled::border_b)
.when(placement_bottom, Styled::border_t)
}
}
TabPosition::Last => {
if self.selected {
this.border_l().border_r().pb_px()
this.border_l()
.border_r()
.when(placement_top, Styled::pb_px)
.when(placement_bottom, Styled::pt_px)
} else {
this.pr_px().pl_px().border_b().border_r()
this.pr_px()
.pl_px()
.border_r()
.when(placement_top, Styled::border_b)
.when(placement_bottom, Styled::border_t)
}
}
TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(),
TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(),
TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(),
TabPosition::Middle(Ordering::Equal) => this
.border_l()
.border_r()
.when(placement_top, Styled::pb_px)
.when(placement_bottom, Styled::pt_px),
TabPosition::Middle(Ordering::Less) => this
.border_l()
.pr_px()
.when(placement_top, Styled::border_b)
.when(placement_bottom, Styled::border_t),
TabPosition::Middle(Ordering::Greater) => this
.border_r()
.pl_px()
.when(placement_top, Styled::border_b)
.when(placement_bottom, Styled::border_t),
})
.cursor_pointer()
.child(

View File

@@ -3,9 +3,19 @@ use smallvec::SmallVec;
use crate::prelude::*;
/// Placement of the tab bar in relation to the main content area.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TabBarPlacement {
/// On top.
Top,
/// At the bottom.
Bottom,
}
#[derive(IntoElement)]
pub struct TabBar {
id: ElementId,
placement: TabBarPlacement,
start_children: SmallVec<[AnyElement; 2]>,
children: SmallVec<[AnyElement; 2]>,
end_children: SmallVec<[AnyElement; 2]>,
@@ -16,6 +26,7 @@ impl TabBar {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
placement: TabBarPlacement::Top,
start_children: SmallVec::new(),
children: SmallVec::new(),
end_children: SmallVec::new(),
@@ -23,6 +34,11 @@ impl TabBar {
}
}
pub fn placement(mut self, placement: TabBarPlacement) -> Self {
self.placement = placement;
self
}
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
self.scroll_handle = Some(scroll_handle);
self
@@ -90,6 +106,9 @@ impl ParentElement for TabBar {
impl RenderOnce for TabBar {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let placement_top = self.placement == TabBarPlacement::Top;
let placement_bottom = self.placement == TabBarPlacement::Bottom;
div()
.id(self.id)
.group("tab_bar")
@@ -104,7 +123,8 @@ impl RenderOnce for TabBar {
.flex_none()
.gap_1()
.px_1()
.border_b()
.when(placement_top, Styled::border_b)
.when(placement_bottom, Styled::border_t)
.border_r()
.border_color(cx.theme().colors().border)
.children(self.start_children),
@@ -122,7 +142,8 @@ impl RenderOnce for TabBar {
.top_0()
.left_0()
.size_full()
.border_b()
.when(placement_top, Styled::border_b)
.when(placement_bottom, Styled::border_t)
.border_color(cx.theme().colors().border),
)
.child(
@@ -142,7 +163,8 @@ impl RenderOnce for TabBar {
.flex_none()
.gap_1()
.px_1()
.border_b()
.when(placement_top, Styled::border_b)
.when(placement_bottom, Styled::border_t)
.border_l()
.border_color(cx.theme().colors().border)
.children(self.end_children),

View File

@@ -2,7 +2,7 @@ use crate::{
pane::{self, Pane},
persistence::model::ItemId,
searchable::SearchableItemHandle,
workspace_settings::{AutosaveSetting, WorkspaceSettings},
workspace_settings::{AutosaveSetting, TabBarPlacement, WorkspaceSettings},
DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
ViewId, Workspace, WorkspaceId,
};
@@ -36,7 +36,7 @@ use ui::Element as _;
pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
#[derive(Deserialize)]
pub struct ItemSettings {
pub struct TabsSettings {
pub git_status: bool,
pub close_position: ClosePosition,
}
@@ -65,10 +65,10 @@ impl ClosePosition {
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ItemSettingsContent {
pub struct TabsSettingsContent {
/// Whether to show the Git file status on a tab item.
///
/// Default: true
/// Default: false
git_status: Option<bool>,
/// Position of the close button in a tab.
///
@@ -89,10 +89,10 @@ pub struct PreviewTabsSettingsContent {
enable_preview_from_file_finder: Option<bool>,
}
impl Settings for ItemSettings {
impl Settings for TabsSettings {
const KEY: Option<&'static str> = Some("tabs");
type FileContent = ItemSettingsContent;
type FileContent = TabsSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
@@ -225,6 +225,10 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
None
}
fn tab_bar_placement(&self) -> TabBarPlacement {
TabBarPlacement::Top
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
ToolbarItemLocation::Hidden
}
@@ -320,6 +324,7 @@ pub trait ItemHandle: 'static + Send {
callback: Box<dyn FnOnce(&mut AppContext) + Send>,
) -> gpui::Subscription;
fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
fn tab_bar_placement(&self, cx: &AppContext) -> TabBarPlacement;
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
fn serialized_item_kind(&self) -> Option<&'static str>;
@@ -678,6 +683,10 @@ impl<T: Item> ItemHandle for View<T> {
self.read(cx).as_searchable(self)
}
fn tab_bar_placement(&self, cx: &AppContext) -> TabBarPlacement {
self.read(cx).tab_bar_placement()
}
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
self.read(cx).breadcrumb_location()
}

View File

@@ -1,10 +1,10 @@
use crate::{
item::{
ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings, TabContentParams,
ClosePosition, Item, ItemHandle, PreviewTabsSettings, TabContentParams, TabsSettings,
WeakItemHandle,
},
toolbar::Toolbar,
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
workspace_settings::{AutosaveSetting, TabBarPlacement, TabBarSettings, WorkspaceSettings},
NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible,
SplitDirection, ToggleZoom, Workspace,
};
@@ -1423,6 +1423,7 @@ impl Pane {
ix: usize,
item: &Box<dyn ItemHandle>,
detail: usize,
tab_bar_placement: ui::TabBarPlacement,
cx: &mut ViewContext<'_, Pane>,
) -> impl IntoElement {
let is_active = ix == self.active_item_index;
@@ -1439,7 +1440,7 @@ impl Pane {
},
cx,
);
let close_side = &ItemSettings::get_global(cx).close_position;
let close_side = &TabsSettings::get_global(cx).close_position;
let indicator = render_item_indicator(item.boxed_clone(), cx);
let item_id = item.item_id();
let is_first_item = ix == 0;
@@ -1447,6 +1448,7 @@ impl Pane {
let position_relative_to_active_item = ix.cmp(&self.active_item_index);
let tab = Tab::new(ix)
.tab_bar_placement(tab_bar_placement)
.position(if is_first_item {
TabPosition::First
} else if is_last_item {
@@ -1659,8 +1661,20 @@ impl Pane {
})
}
fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
fn need_tab_bar_at(&self, placement: TabBarPlacement, cx: &mut ViewContext<'_, Pane>) -> bool {
let Some(item) = self.active_item() else {
return false;
};
item.tab_bar_placement(cx) == placement
}
fn render_tab_bar(
&mut self,
placement: ui::TabBarPlacement,
cx: &mut ViewContext<'_, Pane>,
) -> impl IntoElement {
TabBar::new("tab_bar")
.placement(placement)
.track_scroll(self.tab_bar_scroll_handle.clone())
.when(
self.display_nav_history_buttons.unwrap_or_default(),
@@ -1704,7 +1718,7 @@ impl Pane {
.iter()
.enumerate()
.zip(tab_details(&self.items, cx))
.map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
.map(|((ix, item), detail)| self.render_tab(ix, item, detail, placement, cx)),
)
.child(
div()
@@ -2042,8 +2056,8 @@ impl Render for Pane {
}
}),
)
.when(self.active_item().is_some(), |pane| {
pane.child(self.render_tab_bar(cx))
.when(self.need_tab_bar_at(TabBarPlacement::Top, cx), |pane| {
pane.child(self.render_tab_bar(ui::TabBarPlacement::Top, cx))
})
.child({
let has_worktrees = self.project.read(cx).worktrees().next().is_some();
@@ -2113,6 +2127,9 @@ impl Render for Pane {
}),
)
})
.when(self.need_tab_bar_at(TabBarPlacement::Bottom, cx), |pane| {
pane.child(self.render_tab_bar(ui::TabBarPlacement::Bottom, cx))
})
.on_mouse_down(
MouseButton::Navigate(NavigationDirection::Back),
cx.listener(|pane, _, cx| {

View File

@@ -33,8 +33,8 @@ use gpui::{
Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions,
};
use item::{
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
ProjectItem,
FollowableItem, FollowableItemHandle, Item, ItemHandle, PreviewTabsSettings, ProjectItem,
TabsSettings,
};
use itertools::Itertools;
use language::{LanguageRegistry, Rope};
@@ -85,7 +85,7 @@ use ui::{
use util::{maybe, ResultExt};
use uuid::Uuid;
pub use workspace_settings::{
AutosaveSetting, RestoreOnStartupBehaviour, TabBarSettings, WorkspaceSettings,
AutosaveSetting, RestoreOnStartupBehaviour, TabBarPlacement, TabBarSettings, WorkspaceSettings,
};
use crate::notifications::NotificationId;
@@ -265,7 +265,8 @@ impl Column for WorkspaceId {
}
pub fn init_settings(cx: &mut AppContext) {
WorkspaceSettings::register(cx);
ItemSettings::register(cx);
TabsSettings::register(cx);
TabsSettings::register(cx);
PreviewTabsSettings::register(cx);
TabBarSettings::register(cx);
}

View File

@@ -58,13 +58,30 @@ pub struct WorkspaceSettingsContent {
pub drop_target_size: Option<f32>,
}
/// The tab bar placement in a pane.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TabBarPlacement {
/// Don't show tab bar.
No,
/// Place tab bar on top of the pane.
Top,
/// Place tab bar at the bottom of the pane.
Bottom,
}
#[derive(Deserialize)]
pub struct TabBarSettings {
pub placement: TabBarPlacement,
pub show_nav_history_buttons: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct TabBarSettingsContent {
/// Where to place tab bar in the editor.
///
/// Default: top
pub placement: Option<TabBarPlacement>,
/// Whether or not to show the navigation history buttons in the tab bar.
///
/// Default: true

View File

@@ -304,6 +304,104 @@ List of `string` values
`boolean` values
## Editor Tab Bar
- Description: Settings related to the editor's tab bar.
- Settings: `tab_bar`
- Default:
```json
"tab_bar": {
"placement": "top",
"show_nav_history_buttons": true
}
```
### Placement
- Description: Where to place the editor tab bar.
- Setting: `placement`
- Default: `top`
**Options**
1. Place the tab bar on top of the editor:
```json
{
"placement": "top"
}
```
2. Place the tab bar at the bottom of the editor:
```json
{
"placement": "bottom"
}
```
3. Hide the tab bar:
```json
{
"placement": "no"
}
```
### Navigation History Buttons
- Description: Whether to show the navigation history buttons.
- Setting: `show_nav_history_buttons`
- Default: `true`
**Options**
`boolean` values
## Editor Tabs
- Description: Configuration for the editor tabs.
- Setting: `tabs`
- Default:
```json
"tabs": {
"close_position": "right",
"git_status": false
},
```
### Close Position
- Description: Where to display close button within a tab.
- Setting: `close_position`
- Default: `right`
**Options**
1. Display the close button on the right:
```json
{
"close_position": "right"
}
```
2. Display the close button on the left:
```json
{
"close_position": "left"
}
```
### Git Status
- Description: Whether or not to show Git file status in tab.
- Setting: `git_status`
- Default: `false`
## Editor Toolbar
- Description: Whether or not to show various elements in the editor toolbar.