git_ui: Add Git Panel settings (#23132)
This PR adds settings for the Git Panel. The new settings include: | Setting | Description | Default | |---------|-------------|---------| | `git_panel.button` | Toggle visibility of the Git Panel button in the status bar | `true` | | `git_panel.dock` | Choose where to dock the Git Panel | `"left"` | | `git_panel.default_width` | Set the default width of the Git Panel in pixels | `360` | | `git_panel.status_style` | Select how Git status is displayed | `"icon"` | | `git_panel.scrollbar.show` | Configure scrollbar behavior | Inherits from editor settings | Example usage: ```json "git_panel": { "button": true, "dock": "left", "default_width": 360, "status_style": "icon", "scrollbar": { "show": "auto" } } ``` Release Notes: - N/A
This commit is contained in:
parent
a67709629b
commit
78fd5b5f02
5 changed files with 166 additions and 73 deletions
|
@ -503,7 +503,17 @@
|
||||||
// Where to the git panel. Can be 'left' or 'right'.
|
// Where to the git panel. Can be 'left' or 'right'.
|
||||||
"dock": "left",
|
"dock": "left",
|
||||||
// Default width of the git panel.
|
// Default width of the git panel.
|
||||||
"default_width": 360
|
"default_width": 360,
|
||||||
|
// Style of the git status indicator in the panel.
|
||||||
|
//
|
||||||
|
// Default: icon
|
||||||
|
"status_style": "icon",
|
||||||
|
"scrollbar": {
|
||||||
|
// When to show the scrollbar in the git panel.
|
||||||
|
//
|
||||||
|
// Default: inherits editor scrollbar settings
|
||||||
|
"show": null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"message_editor": {
|
"message_editor": {
|
||||||
// Whether to automatically replace emoji shortcodes with emoji characters.
|
// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
use crate::git_panel_settings::StatusStyle;
|
||||||
use crate::{first_repository_in_project, first_worktree_repository};
|
use crate::{first_repository_in_project, first_worktree_repository};
|
||||||
use crate::{
|
use crate::{
|
||||||
git_status_icon, settings::GitPanelSettings, CommitAllChanges, CommitChanges, GitState,
|
git_panel_settings::GitPanelSettings, git_status_icon, CommitAllChanges, CommitChanges,
|
||||||
GitViewMode, RevertAll, StageAll, ToggleStaged, UnstageAll,
|
GitState, GitViewMode, RevertAll, StageAll, ToggleStaged, UnstageAll,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::Editor;
|
use editor::scroll::ScrollbarAutoHide;
|
||||||
|
use editor::{Editor, EditorSettings, ShowScrollbar};
|
||||||
use git::repository::{GitFileStatus, RepoPath};
|
use git::repository::{GitFileStatus, RepoPath};
|
||||||
use git::status::GitStatusPair;
|
use git::status::GitStatusPair;
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
|
@ -313,7 +315,7 @@ impl GitPanel {
|
||||||
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
|
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
|
||||||
scroll_handle,
|
scroll_handle,
|
||||||
selected_entry: None,
|
selected_entry: None,
|
||||||
show_scrollbar: !Self::should_autohide_scrollbar(cx),
|
show_scrollbar: false,
|
||||||
hide_scrollbar_task: None,
|
hide_scrollbar_task: None,
|
||||||
rebuild_requested,
|
rebuild_requested,
|
||||||
commit_editor,
|
commit_editor,
|
||||||
|
@ -322,6 +324,7 @@ impl GitPanel {
|
||||||
project,
|
project,
|
||||||
};
|
};
|
||||||
git_panel.schedule_update();
|
git_panel.schedule_update();
|
||||||
|
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
|
||||||
git_panel
|
git_panel
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -376,19 +379,38 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_show_scrollbar(_cx: &AppContext) -> bool {
|
fn show_scrollbar(&self, cx: &mut ViewContext<Self>) -> ShowScrollbar {
|
||||||
// TODO: plug into settings
|
GitPanelSettings::get_global(cx)
|
||||||
true
|
.scrollbar
|
||||||
|
.show
|
||||||
|
.unwrap_or_else(|| EditorSettings::get_global(cx).scrollbar.show)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_autohide_scrollbar(_cx: &AppContext) -> bool {
|
fn should_show_scrollbar(&self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
// TODO: plug into settings
|
let show = self.show_scrollbar(cx);
|
||||||
true
|
match show {
|
||||||
|
ShowScrollbar::Auto => true,
|
||||||
|
ShowScrollbar::System => true,
|
||||||
|
ShowScrollbar::Always => true,
|
||||||
|
ShowScrollbar::Never => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_autohide_scrollbar(&self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
|
let show = self.show_scrollbar(cx);
|
||||||
|
match show {
|
||||||
|
ShowScrollbar::Auto => true,
|
||||||
|
ShowScrollbar::System => cx
|
||||||
|
.try_global::<ScrollbarAutoHide>()
|
||||||
|
.map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
|
||||||
|
ShowScrollbar::Always => false,
|
||||||
|
ShowScrollbar::Never => true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_scrollbar(&mut self, cx: &mut ViewContext<Self>) {
|
fn hide_scrollbar(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
if !Self::should_autohide_scrollbar(cx) {
|
if !self.should_autohide_scrollbar(cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.hide_scrollbar_task = Some(cx.spawn(|panel, mut cx| async move {
|
self.hide_scrollbar_task = Some(cx.spawn(|panel, mut cx| async move {
|
||||||
|
@ -960,15 +982,26 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<Stateful<Div>> {
|
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<Stateful<Div>> {
|
||||||
if !Self::should_show_scrollbar(cx)
|
let scroll_bar_style = self.show_scrollbar(cx);
|
||||||
|
let show_container = matches!(scroll_bar_style, ShowScrollbar::Always);
|
||||||
|
|
||||||
|
if !self.should_show_scrollbar(cx)
|
||||||
|| !(self.show_scrollbar || self.scrollbar_state.is_dragging())
|
|| !(self.show_scrollbar || self.scrollbar_state.is_dragging())
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
|
.id("git-panel-vertical-scroll")
|
||||||
.occlude()
|
.occlude()
|
||||||
.id("project-panel-vertical-scroll")
|
.flex_none()
|
||||||
|
.h_full()
|
||||||
|
.cursor_default()
|
||||||
|
.when(show_container, |this| this.pl_1().px_1p5())
|
||||||
|
.when(!show_container, |this| {
|
||||||
|
this.absolute().right_1().top_1().bottom_1().w(px(12.))
|
||||||
|
})
|
||||||
.on_mouse_move(cx.listener(|_, _, cx| {
|
.on_mouse_move(cx.listener(|_, _, cx| {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
cx.stop_propagation()
|
cx.stop_propagation()
|
||||||
|
@ -995,13 +1028,6 @@ impl GitPanel {
|
||||||
.on_scroll_wheel(cx.listener(|_, _, cx| {
|
.on_scroll_wheel(cx.listener(|_, _, cx| {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}))
|
}))
|
||||||
.h_full()
|
|
||||||
.absolute()
|
|
||||||
.right_1()
|
|
||||||
.top_1()
|
|
||||||
.bottom_1()
|
|
||||||
.w(px(12.))
|
|
||||||
.cursor_default()
|
|
||||||
.children(Scrollbar::vertical(
|
.children(Scrollbar::vertical(
|
||||||
// percentage as f32..end_offset as f32,
|
// percentage as f32..end_offset as f32,
|
||||||
self.scrollbar_state.clone(),
|
self.scrollbar_state.clone(),
|
||||||
|
@ -1042,9 +1068,26 @@ impl GitPanel {
|
||||||
let state = self.git_state.clone();
|
let state = self.git_state.clone();
|
||||||
let repo_path = entry_details.repo_path.clone();
|
let repo_path = entry_details.repo_path.clone();
|
||||||
let selected = self.selected_entry == Some(ix);
|
let selected = self.selected_entry == Some(ix);
|
||||||
|
let status_style = GitPanelSettings::get_global(cx).status_style;
|
||||||
// TODO revisit, maybe use a different status here?
|
// TODO revisit, maybe use a different status here?
|
||||||
let status = entry_details.status.combined();
|
let status = entry_details.status.combined();
|
||||||
|
|
||||||
|
let mut label_color = cx.theme().colors().text;
|
||||||
|
if status_style == StatusStyle::LabelColor {
|
||||||
|
label_color = match status {
|
||||||
|
GitFileStatus::Added => cx.theme().status().created,
|
||||||
|
GitFileStatus::Modified => cx.theme().status().modified,
|
||||||
|
GitFileStatus::Conflict => cx.theme().status().conflict,
|
||||||
|
GitFileStatus::Deleted => cx.theme().colors().text_disabled,
|
||||||
|
// TODO: Should we even have this here?
|
||||||
|
GitFileStatus::Untracked => cx.theme().colors().text_placeholder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_color = matches!(status, GitFileStatus::Deleted)
|
||||||
|
.then_some(cx.theme().colors().text_disabled)
|
||||||
|
.unwrap_or(cx.theme().colors().text_muted);
|
||||||
|
|
||||||
let entry_id = ElementId::Name(format!("entry_{}", entry_details.display_name).into());
|
let entry_id = ElementId::Name(format!("entry_{}", entry_details.display_name).into());
|
||||||
let checkbox_id =
|
let checkbox_id =
|
||||||
ElementId::Name(format!("checkbox_{}", entry_details.display_name).into());
|
ElementId::Name(format!("checkbox_{}", entry_details.display_name).into());
|
||||||
|
@ -1125,21 +1168,19 @@ impl GitPanel {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(git_status_icon(status))
|
.when(status_style == StatusStyle::Icon, |this| {
|
||||||
|
this.child(git_status_icon(status))
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.when(status == GitFileStatus::Deleted, |this| {
|
.text_color(label_color)
|
||||||
this.text_color(cx.theme().colors().text_disabled)
|
.when(status == GitFileStatus::Deleted, |this| this.line_through())
|
||||||
.line_through()
|
|
||||||
})
|
|
||||||
.when_some(repo_path.parent(), |this, parent| {
|
.when_some(repo_path.parent(), |this, parent| {
|
||||||
let parent_str = parent.to_string_lossy();
|
let parent_str = parent.to_string_lossy();
|
||||||
if !parent_str.is_empty() {
|
if !parent_str.is_empty() {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.when(status != GitFileStatus::Deleted, |this| {
|
.text_color(path_color)
|
||||||
this.text_color(cx.theme().colors().text_muted)
|
|
||||||
})
|
|
||||||
.child(format!("{}/", parent_str)),
|
.child(format!("{}/", parent_str)),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
83
crates/git_ui/src/git_panel_settings.rs
Normal file
83
crates/git_ui/src/git_panel_settings.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use editor::ShowScrollbar;
|
||||||
|
use gpui::Pixels;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use settings::{Settings, SettingsSources};
|
||||||
|
use workspace::dock::DockPosition;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
pub struct ScrollbarSettingsContent {
|
||||||
|
/// When to show the scrollbar in the git panel.
|
||||||
|
///
|
||||||
|
/// Default: inherits editor scrollbar settings
|
||||||
|
pub show: Option<Option<ShowScrollbar>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
pub struct ScrollbarSettings {
|
||||||
|
pub show: Option<ShowScrollbar>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
// Style of the git status indicator in the panel.
|
||||||
|
//
|
||||||
|
// Default: icon
|
||||||
|
pub enum StatusStyleContent {
|
||||||
|
Icon,
|
||||||
|
LabelColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum StatusStyle {
|
||||||
|
#[default]
|
||||||
|
Icon,
|
||||||
|
LabelColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
|
pub struct GitPanelSettingsContent {
|
||||||
|
/// Whether to show the panel button in the status bar.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub button: Option<bool>,
|
||||||
|
/// Where to dock the panel.
|
||||||
|
///
|
||||||
|
/// Default: left
|
||||||
|
pub dock: Option<DockPosition>,
|
||||||
|
/// Default width of the panel in pixels.
|
||||||
|
///
|
||||||
|
/// Default: 360
|
||||||
|
pub default_width: Option<f32>,
|
||||||
|
/// How entry statuses are displayed.
|
||||||
|
///
|
||||||
|
/// Default: icon
|
||||||
|
pub status_style: Option<StatusStyle>,
|
||||||
|
/// How and when the scrollbar should be displayed.
|
||||||
|
///
|
||||||
|
/// Default: inherits editor scrollbar settings
|
||||||
|
pub scrollbar: Option<ScrollbarSettings>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct GitPanelSettings {
|
||||||
|
pub button: bool,
|
||||||
|
pub dock: DockPosition,
|
||||||
|
pub default_width: Pixels,
|
||||||
|
pub status_style: StatusStyle,
|
||||||
|
pub scrollbar: ScrollbarSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings for GitPanelSettings {
|
||||||
|
const KEY: Option<&'static str> = Some("git_panel");
|
||||||
|
|
||||||
|
type FileContent = GitPanelSettingsContent;
|
||||||
|
|
||||||
|
fn load(
|
||||||
|
sources: SettingsSources<Self::FileContent>,
|
||||||
|
_: &mut gpui::AppContext,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
sources.json_merge()
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ use ::settings::Settings;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{future::FusedFuture, select, FutureExt};
|
use futures::{future::FusedFuture, select, FutureExt};
|
||||||
use git::repository::{GitFileStatus, GitRepository, RepoPath};
|
use git::repository::{GitFileStatus, GitRepository, RepoPath};
|
||||||
|
use git_panel_settings::GitPanelSettings;
|
||||||
use gpui::{actions, AppContext, Context, Global, Hsla, Model, ModelContext};
|
use gpui::{actions, AppContext, Context, Global, Hsla, Model, ModelContext};
|
||||||
use project::{Project, WorktreeId};
|
use project::{Project, WorktreeId};
|
||||||
use settings::GitPanelSettings;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::{
|
use std::{
|
||||||
pin::{pin, Pin},
|
pin::{pin, Pin},
|
||||||
|
@ -16,7 +16,7 @@ use ui::{Color, Icon, IconName, IntoElement, SharedString};
|
||||||
use worktree::RepositoryEntry;
|
use worktree::RepositoryEntry;
|
||||||
|
|
||||||
pub mod git_panel;
|
pub mod git_panel;
|
||||||
mod settings;
|
mod git_panel_settings;
|
||||||
|
|
||||||
const GIT_TASK_DEBOUNCE: Duration = Duration::from_millis(50);
|
const GIT_TASK_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
use gpui::Pixels;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use settings::{Settings, SettingsSources};
|
|
||||||
use workspace::dock::DockPosition;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct GitPanelSettings {
|
|
||||||
pub button: bool,
|
|
||||||
pub dock: DockPosition,
|
|
||||||
pub default_width: Pixels,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
|
||||||
pub struct PanelSettingsContent {
|
|
||||||
/// Whether to show the panel button in the status bar.
|
|
||||||
///
|
|
||||||
/// Default: true
|
|
||||||
pub button: Option<bool>,
|
|
||||||
/// Where to dock the panel.
|
|
||||||
///
|
|
||||||
/// Default: left
|
|
||||||
pub dock: Option<DockPosition>,
|
|
||||||
/// Default width of the panel in pixels.
|
|
||||||
///
|
|
||||||
/// Default: 360
|
|
||||||
pub default_width: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Settings for GitPanelSettings {
|
|
||||||
const KEY: Option<&'static str> = Some("git_panel");
|
|
||||||
|
|
||||||
type FileContent = PanelSettingsContent;
|
|
||||||
|
|
||||||
fn load(
|
|
||||||
sources: SettingsSources<Self::FileContent>,
|
|
||||||
_: &mut gpui::AppContext,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
sources.json_merge()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue