Remove 2 suffix for language selector, project panel, recent_projects, copilot_button, breadcrumbs, activity_indicator

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-01-03 10:47:33 -08:00
parent 252694390a
commit 292b3397ab
37 changed files with 1297 additions and 6979 deletions

View file

@ -1,32 +1,31 @@
use anyhow::Result;
use context_menu::{ContextMenu, ContextMenuItem};
use copilot::{Copilot, SignOut, Status};
use editor::{scroll::autoscroll::Autoscroll, Editor};
use fs::Fs;
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View,
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
div, Action, AnchorCorner, AppContext, AsyncWindowContext, Entity, IntoElement, ParentElement,
Render, Subscription, View, ViewContext, WeakView, WindowContext,
};
use language::{
language_settings::{self, all_language_settings, AllLanguageSettings},
File, Language,
};
use settings::{update_settings_file, SettingsStore};
use settings::{update_settings_file, Settings, SettingsStore};
use std::{path::Path, sync::Arc};
use util::{paths, ResultExt};
use workspace::{
create_and_open_local_file, item::ItemHandle,
notifications::simple_message_notification::OsOpen, StatusItemView, Toast, Workspace,
create_and_open_local_file,
item::ItemHandle,
ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip},
StatusItemView, Toast, Workspace,
};
use zed_actions::OpenBrowser;
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
const COPILOT_STARTING_TOAST_ID: usize = 1337;
const COPILOT_ERROR_TOAST_ID: usize = 1338;
pub struct CopilotButton {
popup_menu: ViewHandle<ContextMenu>,
editor_subscription: Option<(Subscription, usize)>,
editor_enabled: Option<bool>,
language: Option<Arc<Language>>,
@ -34,25 +33,15 @@ pub struct CopilotButton {
fs: Arc<dyn Fs>,
}
impl Entity for CopilotButton {
type Event = ();
}
impl View for CopilotButton {
fn ui_name() -> &'static str {
"CopilotButton"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
impl Render for CopilotButton {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let all_language_settings = all_language_settings(None, cx);
if !all_language_settings.copilot.feature_enabled {
return Empty::new().into_any();
return div();
}
let theme = theme::current(cx).clone();
let active = self.popup_menu.read(cx).visible();
let Some(copilot) = Copilot::global(cx) else {
return Empty::new().into_any();
return div();
};
let status = copilot.read(cx).status();
@ -60,59 +49,26 @@ impl View for CopilotButton {
.editor_enabled
.unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
Stack::new()
.with_child(
MouseEventHandler::new::<Self, _>(0, cx, {
let theme = theme.clone();
let status = status.clone();
move |state, _cx| {
let style = theme
.workspace
.status_bar
.panel_buttons
.button
.in_state(active)
.style_for(state);
let icon = match status {
Status::Error(_) => Icon::CopilotError,
Status::Authorized => {
if enabled {
Icon::Copilot
} else {
Icon::CopilotDisabled
}
}
_ => Icon::CopilotInit,
};
Flex::row()
.with_child(
Svg::new({
match status {
Status::Error(_) => "icons/copilot_error.svg",
Status::Authorized => {
if enabled {
"icons/copilot.svg"
} else {
"icons/copilot_disabled.svg"
}
}
_ => "icons/copilot_init.svg",
}
})
.with_color(style.icon_color)
.constrained()
.with_width(style.icon_size)
.aligned()
.into_any_named("copilot-icon"),
)
.constrained()
.with_height(style.icon_size)
.contained()
.with_style(style.container)
}
})
.with_cursor_style(CursorStyle::PointingHand)
.on_down(MouseButton::Left, |_, this, cx| {
this.popup_menu.update(cx, |menu, _| menu.delay_cancel());
})
.on_click(MouseButton::Left, {
let status = status.clone();
move |_, this, cx| match status {
Status::Authorized => this.deploy_copilot_menu(cx),
Status::Error(ref e) => {
if let Some(workspace) = cx.root_view().clone().downcast::<Workspace>()
{
workspace.update(cx, |workspace, cx| {
if let Status::Error(e) = status {
return div().child(
IconButton::new("copilot-error", icon)
.icon_size(IconSize::Small)
.on_click(cx.listener(move |_, _, cx| {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
workspace
.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(
COPILOT_ERROR_TOAST_ID,
@ -132,43 +88,40 @@ impl View for CopilotButton {
),
cx,
);
});
}
})
.ok();
}
_ => this.deploy_copilot_start_menu(cx),
}))
.tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
);
}
let this = cx.view().clone();
div().child(
popover_menu("copilot")
.menu(move |cx| match status {
Status::Authorized => {
Some(this.update(cx, |this, cx| this.build_copilot_menu(cx)))
}
_ => Some(this.update(cx, |this, cx| this.build_copilot_start_menu(cx))),
})
.with_tooltip::<Self>(
0,
"GitHub Copilot",
None,
theme.tooltip.clone(),
cx,
.anchor(AnchorCorner::BottomRight)
.trigger(
IconButton::new("copilot-icon", icon)
.tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
),
)
.with_child(ChildView::new(&self.popup_menu, cx).aligned().top().right())
.into_any()
)
}
}
impl CopilotButton {
pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
let button_view_id = cx.view_id();
let menu = cx.add_view(|cx| {
let mut menu = ContextMenu::new(button_view_id, cx);
menu.set_position_mode(OverlayPositionMode::Local);
menu
});
cx.observe(&menu, |_, _, cx| cx.notify()).detach();
Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
cx.observe_global::<SettingsStore, _>(move |_, cx| cx.notify())
cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
.detach();
Self {
popup_menu: menu,
editor_subscription: None,
editor_enabled: None,
language: None,
@ -177,108 +130,91 @@ impl CopilotButton {
}
}
pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) {
let mut menu_options = Vec::with_capacity(2);
pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
let fs = self.fs.clone();
menu_options.push(ContextMenuItem::handler("Sign In", |cx| {
initiate_sign_in(cx)
}));
menu_options.push(ContextMenuItem::handler("Disable Copilot", move |cx| {
hide_copilot(fs.clone(), cx)
}));
self.popup_menu.update(cx, |menu, cx| {
menu.toggle(
Default::default(),
AnchorCorner::BottomRight,
menu_options,
cx,
);
});
ContextMenu::build(cx, |menu, _| {
menu.entry("Sign In", None, initiate_sign_in).entry(
"Disable Copilot",
None,
move |cx| hide_copilot(fs.clone(), cx),
)
})
}
pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
pub fn build_copilot_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
let fs = self.fs.clone();
let mut menu_options = Vec::with_capacity(8);
if let Some(language) = self.language.clone() {
let fs = fs.clone();
let language_enabled = language_settings::language_settings(Some(&language), None, cx)
.show_copilot_suggestions;
menu_options.push(ContextMenuItem::handler(
format!(
"{} Suggestions for {}",
if language_enabled { "Hide" } else { "Show" },
language.name()
),
move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
));
}
return ContextMenu::build(cx, move |mut menu, cx| {
if let Some(language) = self.language.clone() {
let fs = fs.clone();
let language_enabled =
language_settings::language_settings(Some(&language), None, cx)
.show_copilot_suggestions;
let settings = settings::get::<AllLanguageSettings>(cx);
menu = menu.entry(
format!(
"{} Suggestions for {}",
if language_enabled { "Hide" } else { "Show" },
language.name()
),
None,
move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
);
}
if let Some(file) = &self.file {
let path = file.path().clone();
let path_enabled = settings.copilot_enabled_for_path(&path);
menu_options.push(ContextMenuItem::handler(
format!(
"{} Suggestions for This Path",
if path_enabled { "Hide" } else { "Show" }
),
move |cx| {
if let Some(workspace) = cx.root_view().clone().downcast::<Workspace>() {
let workspace = workspace.downgrade();
cx.spawn(|_, cx| {
configure_disabled_globs(
workspace,
path_enabled.then_some(path.clone()),
cx,
)
})
.detach_and_log_err(cx);
}
let settings = AllLanguageSettings::get_global(cx);
if let Some(file) = &self.file {
let path = file.path().clone();
let path_enabled = settings.copilot_enabled_for_path(&path);
menu = menu.entry(
format!(
"{} Suggestions for This Path",
if path_enabled { "Hide" } else { "Show" }
),
None,
move |cx| {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
if let Ok(workspace) = workspace.root_view(cx) {
let workspace = workspace.downgrade();
cx.spawn(|cx| {
configure_disabled_globs(
workspace,
path_enabled.then_some(path.clone()),
cx,
)
})
.detach_and_log_err(cx);
}
}
},
);
}
let globally_enabled = settings.copilot_enabled(None, None);
menu.entry(
if globally_enabled {
"Hide Suggestions for All Files"
} else {
"Show Suggestions for All Files"
},
));
}
let globally_enabled = settings.copilot_enabled(None, None);
menu_options.push(ContextMenuItem::handler(
if globally_enabled {
"Hide Suggestions for All Files"
} else {
"Show Suggestions for All Files"
},
move |cx| toggle_copilot_globally(fs.clone(), cx),
));
menu_options.push(ContextMenuItem::Separator);
let icon_style = theme::current(cx).copilot.out_link_icon.clone();
menu_options.push(ContextMenuItem::action(
move |state: &mut MouseState, style: &theme::ContextMenuItem| {
Flex::row()
.with_child(Label::new("Copilot Settings", style.label.clone()))
.with_child(theme::ui::icon(icon_style.style_for(state)))
.align_children_center()
.into_any()
},
OsOpen::new(COPILOT_SETTINGS_URL),
));
menu_options.push(ContextMenuItem::action("Sign Out", SignOut));
self.popup_menu.update(cx, |menu, cx| {
menu.toggle(
Default::default(),
AnchorCorner::BottomRight,
menu_options,
cx,
);
None,
move |cx| toggle_copilot_globally(fs.clone(), cx),
)
.separator()
.link(
"Copilot Settings",
OpenBrowser {
url: COPILOT_SETTINGS_URL.to_string(),
}
.boxed_clone(),
)
.action("Sign Out", SignOut.boxed_clone())
});
}
pub fn update_enabled(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
pub fn update_enabled(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let suggestion_anchor = editor.selections.newest_anchor().start;
@ -299,8 +235,10 @@ impl CopilotButton {
impl StatusItemView for CopilotButton {
fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
self.editor_subscription =
Some((cx.observe(&editor, Self::update_enabled), editor.id()));
self.editor_subscription = Some((
cx.observe(&editor, Self::update_enabled),
editor.entity_id().as_u64() as usize,
));
self.update_enabled(editor, cx);
} else {
self.language = None;
@ -312,9 +250,9 @@ impl StatusItemView for CopilotButton {
}
async fn configure_disabled_globs(
workspace: WeakViewHandle<Workspace>,
workspace: WeakView<Workspace>,
path_to_disable: Option<Arc<Path>>,
mut cx: AsyncAppContext,
mut cx: AsyncWindowContext,
) -> Result<()> {
let settings_editor = workspace
.update(&mut cx, |_, cx| {
@ -396,20 +334,23 @@ fn initiate_sign_in(cx: &mut WindowContext) {
match status {
Status::Starting { task } => {
let Some(workspace) = cx.root_view().clone().downcast::<Workspace>() else {
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
return;
};
workspace.update(cx, |workspace, cx| {
let Ok(workspace) = workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."),
cx,
)
});
let workspace = workspace.downgrade();
);
workspace.weak_handle()
}) else {
return;
};
cx.spawn(|mut cx| async move {
task.await;
if let Some(copilot) = cx.read(Copilot::global) {
if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
workspace
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
Status::Authorized => workspace.show_toast(