Merge branch 'main' into welcome2
This commit is contained in:
@@ -84,8 +84,8 @@ impl Settings for AutoUpdateSetting {
|
||||
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
|
||||
AutoUpdateSetting::register(cx);
|
||||
|
||||
cx.observe_new_views(|wokrspace: &mut Workspace, _cx| {
|
||||
wokrspace
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace
|
||||
.register_action(|_, action: &Check, cx| check(action, cx))
|
||||
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
|
||||
let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
|
||||
@@ -94,6 +94,11 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
// @nate - code to trigger update notification on launch
|
||||
// workspace.show_notification(0, _cx, |cx| {
|
||||
// cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
|
||||
// });
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -131,7 +136,7 @@ pub fn check(_: &Check, cx: &mut AppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
|
||||
pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
|
||||
if let Some(auto_updater) = AutoUpdater::get(cx) {
|
||||
let auto_updater = auto_updater.read(cx);
|
||||
let server_url = &auto_updater.server_url;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use gpui::{
|
||||
div, DismissEvent, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext,
|
||||
div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
|
||||
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
|
||||
};
|
||||
use menu::Cancel;
|
||||
use util::channel::ReleaseChannel;
|
||||
use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
|
||||
|
||||
pub struct UpdateNotification {
|
||||
_version: SemanticVersion,
|
||||
version: SemanticVersion,
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for UpdateNotification {}
|
||||
@@ -12,77 +14,43 @@ impl EventEmitter<DismissEvent> for UpdateNotification {}
|
||||
impl Render for UpdateNotification {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||
div().child("Updated zed!")
|
||||
// let theme = theme::current(cx).clone();
|
||||
// let theme = &theme.update_notification;
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||
let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||
|
||||
// let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||
|
||||
// MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
|
||||
// Flex::column()
|
||||
// .with_child(
|
||||
// Flex::row()
|
||||
// .with_child(
|
||||
// Text::new(
|
||||
// format!("Updated to {app_name} {}", self.version),
|
||||
// theme.message.text.clone(),
|
||||
// )
|
||||
// .contained()
|
||||
// .with_style(theme.message.container)
|
||||
// .aligned()
|
||||
// .top()
|
||||
// .left()
|
||||
// .flex(1., true),
|
||||
// )
|
||||
// .with_child(
|
||||
// MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
|
||||
// let style = theme.dismiss_button.style_for(state);
|
||||
// Svg::new("icons/x.svg")
|
||||
// .with_color(style.color)
|
||||
// .constrained()
|
||||
// .with_width(style.icon_width)
|
||||
// .aligned()
|
||||
// .contained()
|
||||
// .with_style(style.container)
|
||||
// .constrained()
|
||||
// .with_width(style.button_width)
|
||||
// .with_height(style.button_width)
|
||||
// })
|
||||
// .with_padding(Padding::uniform(5.))
|
||||
// .on_click(MouseButton::Left, move |_, this, cx| {
|
||||
// this.dismiss(&Default::default(), cx)
|
||||
// })
|
||||
// .aligned()
|
||||
// .constrained()
|
||||
// .with_height(cx.font_cache().line_height(theme.message.text.font_size))
|
||||
// .aligned()
|
||||
// .top()
|
||||
// .flex_float(),
|
||||
// ),
|
||||
// )
|
||||
// .with_child({
|
||||
// let style = theme.action_message.style_for(state);
|
||||
// Text::new("View the release notes", style.text.clone())
|
||||
// .contained()
|
||||
// .with_style(style.container)
|
||||
// })
|
||||
// .contained()
|
||||
// })
|
||||
// .with_cursor_style(CursorStyle::PointingHand)
|
||||
// .on_click(MouseButton::Left, |_, _, cx| {
|
||||
// crate::view_release_notes(&Default::default(), cx)
|
||||
// })
|
||||
// .into_any_named("update notification")
|
||||
v_stack()
|
||||
.elevation_3(cx)
|
||||
.p_4()
|
||||
.child(
|
||||
h_stack()
|
||||
.justify_between()
|
||||
.child(Label::new(format!(
|
||||
"Updated to {app_name} {}",
|
||||
self.version
|
||||
)))
|
||||
.child(
|
||||
div()
|
||||
.id("cancel")
|
||||
.child(IconElement::new(Icon::Close))
|
||||
.cursor_pointer()
|
||||
.on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("notes")
|
||||
.child(Label::new("View the release notes"))
|
||||
.cursor_pointer()
|
||||
.on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl UpdateNotification {
|
||||
pub fn new(version: SemanticVersion) -> Self {
|
||||
Self { _version: version }
|
||||
Self { version }
|
||||
}
|
||||
|
||||
pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(DismissEvent::Dismiss);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,9 +660,12 @@ impl CallHandler for Call {
|
||||
self.active_call.as_ref().map(|call| {
|
||||
call.0.update(cx, |this, cx| {
|
||||
this.room().map(|room| {
|
||||
room.update(cx, |this, cx| {
|
||||
this.toggle_mute(cx).log_err();
|
||||
let room = room.clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
|
||||
.await
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::participant::{LocalParticipant, ParticipantLocation, RemoteParticipant};
|
||||
use crate::{
|
||||
call_settings::CallSettings,
|
||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use audio::{Audio, Sound};
|
||||
use client::{
|
||||
@@ -18,6 +21,7 @@ use live_kit_client::{
|
||||
};
|
||||
use postage::{sink::Sink, stream::Stream, watch};
|
||||
use project::Project;
|
||||
use settings::Settings as _;
|
||||
use std::{future::Future, mem, sync::Arc, time::Duration};
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
|
||||
@@ -328,10 +332,8 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mute_on_join(_cx: &AppContext) -> bool {
|
||||
// todo!() po: This should be uncommented, though then unmuting does not work
|
||||
false
|
||||
//CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
||||
pub fn mute_on_join(cx: &AppContext) -> bool {
|
||||
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
||||
}
|
||||
|
||||
fn from_join_response(
|
||||
@@ -1265,7 +1267,6 @@ impl Room {
|
||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
||||
.await
|
||||
};
|
||||
|
||||
let publication = publish_track.await;
|
||||
this.upgrade()
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
|
||||
+1467
-1295
File diff suppressed because it is too large
Load Diff
@@ -1,37 +1,34 @@
|
||||
use client::{ContactRequestStatus, User, UserStore};
|
||||
use gpui::{
|
||||
elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle,
|
||||
div, img, svg, AnyElement, AppContext, DismissEvent, Div, Entity, EventEmitter, FocusHandle,
|
||||
FocusableView, Img, IntoElement, Model, ParentElement as _, Render, Styled, Task, View,
|
||||
ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate, PickerEvent};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
use util::TryFutureExt;
|
||||
use workspace::Modal;
|
||||
use theme::ActiveTheme as _;
|
||||
use ui::{h_stack, v_stack, Label};
|
||||
use util::{ResultExt as _, TryFutureExt};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
Picker::<ContactFinderDelegate>::init(cx);
|
||||
cx.add_action(ContactFinder::dismiss)
|
||||
//Picker::<ContactFinderDelegate>::init(cx);
|
||||
//cx.add_action(ContactFinder::dismiss)
|
||||
}
|
||||
|
||||
pub struct ContactFinder {
|
||||
picker: ViewHandle<Picker<ContactFinderDelegate>>,
|
||||
picker: View<Picker<ContactFinderDelegate>>,
|
||||
has_focus: bool,
|
||||
}
|
||||
|
||||
impl ContactFinder {
|
||||
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let picker = cx.add_view(|cx| {
|
||||
Picker::new(
|
||||
ContactFinderDelegate {
|
||||
user_store,
|
||||
potential_contacts: Arc::from([]),
|
||||
selected_index: 0,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
|
||||
});
|
||||
|
||||
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
|
||||
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let delegate = ContactFinderDelegate {
|
||||
parent: cx.view().downgrade(),
|
||||
user_store,
|
||||
potential_contacts: Arc::from([]),
|
||||
selected_index: 0,
|
||||
};
|
||||
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
|
||||
|
||||
Self {
|
||||
picker,
|
||||
@@ -41,105 +38,72 @@ impl ContactFinder {
|
||||
|
||||
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
|
||||
self.picker.update(cx, |picker, cx| {
|
||||
picker.set_query(query, cx);
|
||||
// todo!()
|
||||
// picker.set_query(query, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ContactFinder {
|
||||
type Event = PickerEvent;
|
||||
}
|
||||
|
||||
impl View for ContactFinder {
|
||||
fn ui_name() -> &'static str {
|
||||
"ContactFinder"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let full_theme = &theme::current(cx);
|
||||
let theme = &full_theme.collab_panel.tabbed_modal;
|
||||
|
||||
fn render_mode_button(
|
||||
text: &'static str,
|
||||
theme: &theme::TabbedModal,
|
||||
_cx: &mut ViewContext<ContactFinder>,
|
||||
) -> AnyElement<ContactFinder> {
|
||||
let contained_text = &theme.tab_button.active_state().default;
|
||||
Label::new(text, contained_text.text.clone())
|
||||
.contained()
|
||||
.with_style(contained_text.container.clone())
|
||||
.into_any()
|
||||
impl Render for ContactFinder {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render_mode_button(text: &'static str) -> AnyElement {
|
||||
Label::new(text).into_any_element()
|
||||
}
|
||||
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Label::new("Contacts", theme.title.text.clone())
|
||||
.contained()
|
||||
.with_style(theme.title.container.clone()),
|
||||
)
|
||||
.with_child(Flex::row().with_children([render_mode_button(
|
||||
"Invite new contacts",
|
||||
&theme,
|
||||
cx,
|
||||
)]))
|
||||
.expanded()
|
||||
.contained()
|
||||
.with_style(theme.header),
|
||||
v_stack()
|
||||
.child(
|
||||
v_stack()
|
||||
.child(Label::new("Contacts"))
|
||||
.child(h_stack().children([render_mode_button("Invite new contacts")]))
|
||||
.bg(cx.theme().colors().element_background),
|
||||
)
|
||||
.with_child(
|
||||
ChildView::new(&self.picker, cx)
|
||||
.contained()
|
||||
.with_style(theme.body),
|
||||
)
|
||||
.constrained()
|
||||
.with_max_height(theme.max_height)
|
||||
.with_max_width(theme.max_width)
|
||||
.contained()
|
||||
.with_style(theme.modal)
|
||||
.into_any()
|
||||
.child(self.picker.clone())
|
||||
.w_96()
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
self.has_focus = true;
|
||||
if cx.is_self_focused() {
|
||||
cx.focus(&self.picker)
|
||||
}
|
||||
}
|
||||
// fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
// self.has_focus = true;
|
||||
// if cx.is_self_focused() {
|
||||
// cx.focus(&self.picker)
|
||||
// }
|
||||
// }
|
||||
|
||||
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
|
||||
self.has_focus = false;
|
||||
}
|
||||
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
|
||||
// self.has_focus = false;
|
||||
// }
|
||||
|
||||
type Element = Div;
|
||||
}
|
||||
|
||||
impl Modal for ContactFinder {
|
||||
fn has_focus(&self) -> bool {
|
||||
self.has_focus
|
||||
}
|
||||
// impl Modal for ContactFinder {
|
||||
// fn has_focus(&self) -> bool {
|
||||
// self.has_focus
|
||||
// }
|
||||
|
||||
fn dismiss_on_event(event: &Self::Event) -> bool {
|
||||
match event {
|
||||
PickerEvent::Dismiss => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
// fn dismiss_on_event(event: &Self::Event) -> bool {
|
||||
// match event {
|
||||
// PickerEvent::Dismiss => true,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct ContactFinderDelegate {
|
||||
parent: WeakView<ContactFinder>,
|
||||
potential_contacts: Arc<[Arc<User>]>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
user_store: Model<UserStore>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl PickerDelegate for ContactFinderDelegate {
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
impl EventEmitter<DismissEvent> for ContactFinder {}
|
||||
|
||||
impl FocusableView for ContactFinder {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for ContactFinderDelegate {
|
||||
type ListItem = Div;
|
||||
fn match_count(&self) -> usize {
|
||||
self.potential_contacts.len()
|
||||
}
|
||||
@@ -152,6 +116,10 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let search_users = self
|
||||
.user_store
|
||||
@@ -161,7 +129,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
async {
|
||||
let potential_contacts = search_users.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
picker.delegate_mut().potential_contacts = potential_contacts.into();
|
||||
picker.delegate.potential_contacts = potential_contacts.into();
|
||||
cx.notify();
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
@@ -191,19 +159,18 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
cx.emit(PickerEvent::Dismiss);
|
||||
//cx.emit(PickerEvent::Dismiss);
|
||||
self.parent
|
||||
.update(cx, |_, cx| cx.emit(DismissEvent::Dismiss))
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
mouse_state: &mut MouseState,
|
||||
selected: bool,
|
||||
cx: &gpui::AppContext,
|
||||
) -> AnyElement<Picker<Self>> {
|
||||
let full_theme = &theme::current(cx);
|
||||
let theme = &full_theme.collab_panel.contact_finder;
|
||||
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let user = &self.potential_contacts[ix];
|
||||
let request_status = self.user_store.read(cx).contact_request_status(user);
|
||||
|
||||
@@ -214,48 +181,47 @@ impl PickerDelegate for ContactFinderDelegate {
|
||||
ContactRequestStatus::RequestSent => Some("icons/x.svg"),
|
||||
ContactRequestStatus::RequestAccepted => None,
|
||||
};
|
||||
let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
|
||||
&theme.disabled_contact_button
|
||||
} else {
|
||||
&theme.contact_button
|
||||
};
|
||||
let style = tabbed_modal
|
||||
.picker
|
||||
.item
|
||||
.in_state(selected)
|
||||
.style_for(mouse_state);
|
||||
Flex::row()
|
||||
.with_children(user.avatar.clone().map(|avatar| {
|
||||
Image::from_data(avatar)
|
||||
.with_style(theme.contact_avatar)
|
||||
.aligned()
|
||||
.left()
|
||||
}))
|
||||
.with_child(
|
||||
Label::new(user.github_login.clone(), style.label.clone())
|
||||
.contained()
|
||||
.with_style(theme.contact_username)
|
||||
.aligned()
|
||||
.left(),
|
||||
)
|
||||
.with_children(icon_path.map(|icon_path| {
|
||||
Svg::new(icon_path)
|
||||
.with_color(button_style.color)
|
||||
.constrained()
|
||||
.with_width(button_style.icon_width)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(button_style.container)
|
||||
.constrained()
|
||||
.with_width(button_style.button_width)
|
||||
.with_height(button_style.button_width)
|
||||
.aligned()
|
||||
.flex_float()
|
||||
}))
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.constrained()
|
||||
.with_height(tabbed_modal.row_height)
|
||||
.into_any()
|
||||
dbg!(icon_path);
|
||||
Some(
|
||||
div()
|
||||
.flex_1()
|
||||
.justify_between()
|
||||
.children(user.avatar.clone().map(|avatar| img().data(avatar)))
|
||||
.child(Label::new(user.github_login.clone()))
|
||||
.children(icon_path.map(|icon_path| svg().path(icon_path))),
|
||||
)
|
||||
// Flex::row()
|
||||
// .with_children(user.avatar.clone().map(|avatar| {
|
||||
// Image::from_data(avatar)
|
||||
// .with_style(theme.contact_avatar)
|
||||
// .aligned()
|
||||
// .left()
|
||||
// }))
|
||||
// .with_child(
|
||||
// Label::new(user.github_login.clone(), style.label.clone())
|
||||
// .contained()
|
||||
// .with_style(theme.contact_username)
|
||||
// .aligned()
|
||||
// .left(),
|
||||
// )
|
||||
// .with_children(icon_path.map(|icon_path| {
|
||||
// Svg::new(icon_path)
|
||||
// .with_color(button_style.color)
|
||||
// .constrained()
|
||||
// .with_width(button_style.icon_width)
|
||||
// .aligned()
|
||||
// .contained()
|
||||
// .with_style(button_style.container)
|
||||
// .constrained()
|
||||
// .with_width(button_style.button_width)
|
||||
// .with_height(button_style.button_width)
|
||||
// .aligned()
|
||||
// .flex_float()
|
||||
// }))
|
||||
// .contained()
|
||||
// .with_style(style.container)
|
||||
// .constrained()
|
||||
// .with_height(tabbed_modal.row_height)
|
||||
// .into_any()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ use project::Project;
|
||||
use theme::ActiveTheme;
|
||||
use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
|
||||
use crate::face_pile::FacePile;
|
||||
|
||||
@@ -290,11 +290,13 @@ impl Render for CollabTitlebarItem {
|
||||
} else {
|
||||
this.child(Button::new("Sign in").on_click(move |_, cx| {
|
||||
let client = client.clone();
|
||||
cx.spawn(move |cx| async move {
|
||||
client.authenticate_and_connect(true, &cx).await?;
|
||||
Ok::<(), anyhow::Error>(())
|
||||
cx.spawn(move |mut cx| async move {
|
||||
client
|
||||
.authenticate_and_connect(true, &cx)
|
||||
.await
|
||||
.notify_async_err(&mut cx);
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
.detach();
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,8 +3,8 @@ use gpui::{
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FacePile {
|
||||
faces: Vec<AnyElement>,
|
||||
pub struct FacePile {
|
||||
pub faces: Vec<AnyElement>,
|
||||
}
|
||||
|
||||
impl RenderOnce for FacePile {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use collections::{CommandPaletteFilter, HashMap};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, div, prelude::*, Action, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
|
||||
FocusHandle, FocusableView, Keystroke, ParentElement, Render, Styled, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
};
|
||||
use picker::{simple_picker_match, Picker, PickerDelegate};
|
||||
use std::{
|
||||
cmp::{self, Reverse},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding};
|
||||
use collections::{CommandPaletteFilter, HashMap};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
||||
Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
|
||||
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
|
||||
use util::{
|
||||
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
||||
ResultExt,
|
||||
@@ -81,7 +81,7 @@ impl Render for CommandPalette {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
v_stack().w_96().child(self.picker.clone())
|
||||
v_stack().min_w_96().child(self.picker.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,8 @@ impl CommandPaletteDelegate {
|
||||
}
|
||||
|
||||
impl PickerDelegate for CommandPaletteDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Execute a command...".into()
|
||||
}
|
||||
@@ -292,24 +294,26 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> AnyElement {
|
||||
) -> Option<Self::ListItem> {
|
||||
let Some(r#match) = self.matches.get(ix) else {
|
||||
return div().into_any();
|
||||
return None;
|
||||
};
|
||||
let Some(command) = self.commands.get(r#match.candidate_id) else {
|
||||
return div().into_any();
|
||||
return None;
|
||||
};
|
||||
|
||||
simple_picker_match(selected, cx, |cx| {
|
||||
h_stack()
|
||||
.justify_between()
|
||||
.child(HighlightedLabel::new(
|
||||
command.name.clone(),
|
||||
r#match.positions.clone(),
|
||||
))
|
||||
.children(KeyBinding::for_action(&*command.action, cx))
|
||||
.into_any()
|
||||
})
|
||||
Some(
|
||||
ListItem::new(ix).inset(true).selected(selected).child(
|
||||
h_stack()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(HighlightedLabel::new(
|
||||
command.name.clone(),
|
||||
r#match.positions.clone(),
|
||||
))
|
||||
.children(KeyBinding::for_action(&*command.action, cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1273,6 +1273,13 @@ impl CompletionsMenu {
|
||||
multiline_docs.map(|div| {
|
||||
div.id("multiline_docs")
|
||||
.max_h(max_height)
|
||||
.flex_1()
|
||||
.px_1p5()
|
||||
.py_1()
|
||||
.min_w(px(260.))
|
||||
.max_w(px(640.))
|
||||
.w(px(500.))
|
||||
.text_ui()
|
||||
.overflow_y_scroll()
|
||||
// Prevent a mouse down on documentation from being propagated to the editor,
|
||||
// because that would move the cursor.
|
||||
@@ -1327,13 +1334,18 @@ impl CompletionsMenu {
|
||||
|
||||
div()
|
||||
.id(mat.candidate_id)
|
||||
.min_w(px(300.))
|
||||
.max_w(px(700.))
|
||||
.min_w(px(220.))
|
||||
.max_w(px(540.))
|
||||
.whitespace_nowrap()
|
||||
.overflow_hidden()
|
||||
.bg(gpui::green())
|
||||
.hover(|style| style.bg(gpui::blue()))
|
||||
.when(item_ix == selected_item, |div| div.bg(gpui::red()))
|
||||
.text_ui()
|
||||
.px_1()
|
||||
.rounded(px(4.))
|
||||
.bg(cx.theme().colors().ghost_element_background)
|
||||
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
||||
.when(item_ix == selected_item, |div| {
|
||||
div.bg(cx.theme().colors().ghost_element_selected)
|
||||
})
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |editor, event, cx| {
|
||||
|
||||
@@ -22,10 +22,11 @@ use collections::{BTreeMap, HashMap};
|
||||
use gpui::{
|
||||
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
|
||||
BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
|
||||
ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, IntoElement, LineLayout,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
|
||||
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
|
||||
TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
|
||||
ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
|
||||
IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
||||
StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
|
||||
ViewContext, WeakView, WindowContext, WrappedLine,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::language_settings::ShowWhitespaceSetting;
|
||||
@@ -316,6 +317,7 @@ impl EditorElement {
|
||||
position_map: &PositionMap,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
gutter_bounds: Bounds<Pixels>,
|
||||
stacking_order: &StackingOrder,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
let mut click_count = event.click_count;
|
||||
@@ -326,6 +328,9 @@ impl EditorElement {
|
||||
} else if !text_bounds.contains_point(&event.position) {
|
||||
return false;
|
||||
}
|
||||
if !cx.was_top_layer(&event.position, stacking_order) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||
let position = point_for_position.previous_valid;
|
||||
@@ -384,6 +389,7 @@ impl EditorElement {
|
||||
event: &MouseUpEvent,
|
||||
position_map: &PositionMap,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
stacking_order: &StackingOrder,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
let end_selection = editor.has_pending_selection();
|
||||
@@ -396,6 +402,7 @@ impl EditorElement {
|
||||
if !pending_nonempty_selections
|
||||
&& event.modifiers.command
|
||||
&& text_bounds.contains_point(&event.position)
|
||||
&& cx.was_top_layer(&event.position, stacking_order)
|
||||
{
|
||||
let point = position_map.point_for_position(text_bounds, event.position);
|
||||
let could_be_inlay = point.as_valid().is_none();
|
||||
@@ -418,6 +425,7 @@ impl EditorElement {
|
||||
position_map: &PositionMap,
|
||||
text_bounds: Bounds<Pixels>,
|
||||
gutter_bounds: Bounds<Pixels>,
|
||||
stacking_order: &StackingOrder,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
let modifiers = event.modifiers;
|
||||
@@ -457,10 +465,12 @@ impl EditorElement {
|
||||
|
||||
let text_hovered = text_bounds.contains_point(&event.position);
|
||||
let gutter_hovered = gutter_bounds.contains_point(&event.position);
|
||||
let was_top = cx.was_top_layer(&event.position, stacking_order);
|
||||
|
||||
editor.set_gutter_hovered(gutter_hovered, cx);
|
||||
|
||||
// Don't trigger hover popover if mouse is hovering over context menu
|
||||
if text_hovered {
|
||||
if text_hovered && was_top {
|
||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||
|
||||
match point_for_position.as_valid() {
|
||||
@@ -490,7 +500,7 @@ impl EditorElement {
|
||||
} else {
|
||||
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
|
||||
hover_at(editor, None, cx);
|
||||
gutter_hovered
|
||||
gutter_hovered && was_top
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,10 +508,10 @@ impl EditorElement {
|
||||
editor: &mut Editor,
|
||||
event: &ScrollWheelEvent,
|
||||
position_map: &PositionMap,
|
||||
bounds: Bounds<Pixels>,
|
||||
bounds: &InteractiveBounds,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> bool {
|
||||
if !bounds.contains_point(&event.position) {
|
||||
if !bounds.visibly_contains(&event.position, cx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2282,10 +2292,15 @@ impl EditorElement {
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
|
||||
let interactive_bounds = InteractiveBounds {
|
||||
bounds: bounds.intersect(&cx.content_mask().bounds),
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
|
||||
cx.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
let editor = self.editor.clone();
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase != DispatchPhase::Bubble {
|
||||
@@ -2293,7 +2308,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
let should_cancel = editor.update(cx, |editor, cx| {
|
||||
Self::scroll(editor, event, &position_map, bounds, cx)
|
||||
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
|
||||
});
|
||||
if should_cancel {
|
||||
cx.stop_propagation();
|
||||
@@ -2304,6 +2319,7 @@ impl EditorElement {
|
||||
cx.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
let editor = self.editor.clone();
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase != DispatchPhase::Bubble {
|
||||
@@ -2311,7 +2327,15 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
let should_cancel = editor.update(cx, |editor, cx| {
|
||||
Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx)
|
||||
Self::mouse_down(
|
||||
editor,
|
||||
event,
|
||||
&position_map,
|
||||
text_bounds,
|
||||
gutter_bounds,
|
||||
&stacking_order,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if should_cancel {
|
||||
@@ -2323,9 +2347,18 @@ impl EditorElement {
|
||||
cx.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
let editor = self.editor.clone();
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
|
||||
move |event: &MouseUpEvent, phase, cx| {
|
||||
let should_cancel = editor.update(cx, |editor, cx| {
|
||||
Self::mouse_up(editor, event, &position_map, text_bounds, cx)
|
||||
Self::mouse_up(
|
||||
editor,
|
||||
event,
|
||||
&position_map,
|
||||
text_bounds,
|
||||
&stacking_order,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if should_cancel {
|
||||
@@ -2351,13 +2384,23 @@ impl EditorElement {
|
||||
cx.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
let editor = self.editor.clone();
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
|
||||
move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
|
||||
let stop_propogating = editor.update(cx, |editor, cx| {
|
||||
Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx)
|
||||
Self::mouse_moved(
|
||||
editor,
|
||||
event,
|
||||
&position_map,
|
||||
text_bounds,
|
||||
gutter_bounds,
|
||||
&stacking_order,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if stop_propogating {
|
||||
@@ -2617,9 +2660,11 @@ impl Element for EditorElement {
|
||||
// We call with_z_index to establish a new stacking context.
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
// Paint mouse listeners first, so any elements we paint on top of the editor
|
||||
// Paint mouse listeners at z-index 0 so any elements we paint on top of the editor
|
||||
// take precedence.
|
||||
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
|
||||
cx.with_z_index(0, |cx| {
|
||||
self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx);
|
||||
});
|
||||
let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx);
|
||||
cx.handle_input(&focus_handle, input_handler);
|
||||
|
||||
|
||||
@@ -483,9 +483,6 @@ impl InfoPopover {
|
||||
// Prevent a mouse move on the popover from being propagated to the editor,
|
||||
// because that would dismiss the popover.
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
// Prevent a mouse down on the popover from being propagated to the editor,
|
||||
// because that would move the cursor.
|
||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||
.child(crate::render_parsed_markdown(
|
||||
"content",
|
||||
&self.parsed_content,
|
||||
|
||||
@@ -2,9 +2,8 @@ use collections::HashMap;
|
||||
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
|
||||
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
|
||||
use gpui::{
|
||||
actions, div, AnyElement, AppContext, DismissEvent, Div, Element, EventEmitter, FocusHandle,
|
||||
FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Task,
|
||||
View, ViewContext, VisualContext, WeakView,
|
||||
actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Model,
|
||||
ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
||||
@@ -16,8 +15,7 @@ use std::{
|
||||
},
|
||||
};
|
||||
use text::Point;
|
||||
use theme::ActiveTheme;
|
||||
use ui::{v_stack, HighlightedLabel, StyledExt};
|
||||
use ui::{v_stack, HighlightedLabel, ListItem};
|
||||
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -530,6 +528,8 @@ impl FileFinderDelegate {
|
||||
}
|
||||
|
||||
impl PickerDelegate for FileFinderDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Search project files...".into()
|
||||
}
|
||||
@@ -709,31 +709,22 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> AnyElement {
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = self
|
||||
.matches
|
||||
.get(ix)
|
||||
.expect("Invalid matches state: no element for index {ix}");
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors();
|
||||
|
||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||
self.labels_for_match(path_match, cx, ix);
|
||||
|
||||
div()
|
||||
.px_1()
|
||||
.text_color(colors.text)
|
||||
.text_ui()
|
||||
.bg(colors.ghost_element_background)
|
||||
.rounded_md()
|
||||
.when(selected, |this| this.bg(colors.ghost_element_selected))
|
||||
.hover(|this| this.bg(colors.ghost_element_hover))
|
||||
.child(
|
||||
Some(
|
||||
ListItem::new(ix).inset(true).selected(selected).child(
|
||||
v_stack()
|
||||
.child(HighlightedLabel::new(file_name, file_name_positions))
|
||||
.child(HighlightedLabel::new(full_path, full_path_positions)),
|
||||
)
|
||||
.into_any()
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ use crate::{
|
||||
BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
|
||||
IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
|
||||
SharedString, Size, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext,
|
||||
SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility,
|
||||
WindowContext,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use refineable::Refineable;
|
||||
@@ -84,7 +85,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||
move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.contains_point(&event.position)
|
||||
&& bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx)
|
||||
}
|
||||
@@ -99,7 +100,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||
) -> Self {
|
||||
self.interactivity().mouse_down_listeners.push(Box::new(
|
||||
move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx)
|
||||
}
|
||||
},
|
||||
@@ -117,7 +118,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.contains_point(&event.position)
|
||||
&& bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx)
|
||||
}
|
||||
@@ -132,7 +133,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||
self.interactivity()
|
||||
.mouse_up_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx)
|
||||
}
|
||||
}));
|
||||
@@ -145,7 +146,8 @@ pub trait InteractiveElement: Sized + Element {
|
||||
) -> Self {
|
||||
self.interactivity().mouse_down_listeners.push(Box::new(
|
||||
move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx)
|
||||
}
|
||||
},
|
||||
@@ -163,7 +165,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& event.button == button
|
||||
&& !bounds.contains_point(&event.position)
|
||||
&& !bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx);
|
||||
}
|
||||
@@ -177,7 +179,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||
) -> Self {
|
||||
self.interactivity().mouse_move_listeners.push(Box::new(
|
||||
move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx);
|
||||
}
|
||||
},
|
||||
@@ -191,7 +193,7 @@ pub trait InteractiveElement: Sized + Element {
|
||||
) -> Self {
|
||||
self.interactivity().scroll_wheel_listeners.push(Box::new(
|
||||
move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx);
|
||||
}
|
||||
},
|
||||
@@ -526,15 +528,15 @@ pub type FocusListeners = SmallVec<[FocusListener; 2]>;
|
||||
pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
|
||||
|
||||
pub type MouseDownListener =
|
||||
Box<dyn Fn(&MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
pub type MouseUpListener =
|
||||
Box<dyn Fn(&MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
|
||||
pub type MouseMoveListener =
|
||||
Box<dyn Fn(&MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
|
||||
pub type ScrollWheelListener =
|
||||
Box<dyn Fn(&ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
|
||||
pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
|
||||
|
||||
@@ -719,6 +721,18 @@ pub struct Interactivity {
|
||||
pub tooltip_builder: Option<TooltipBuilder>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InteractiveBounds {
|
||||
pub bounds: Bounds<Pixels>,
|
||||
pub stacking_order: StackingOrder,
|
||||
}
|
||||
|
||||
impl InteractiveBounds {
|
||||
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||
self.bounds.contains_point(point) && cx.was_top_layer(&point, &self.stacking_order)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interactivity {
|
||||
pub fn layout(
|
||||
&mut self,
|
||||
@@ -755,34 +769,52 @@ impl Interactivity {
|
||||
) {
|
||||
let style = self.compute_style(Some(bounds), element_state, cx);
|
||||
|
||||
if style
|
||||
.background
|
||||
.as_ref()
|
||||
.is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
|
||||
{
|
||||
cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds))
|
||||
}
|
||||
|
||||
let interactive_bounds = Rc::new(InteractiveBounds {
|
||||
bounds: bounds.intersect(&cx.content_mask().bounds),
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
});
|
||||
|
||||
if let Some(mouse_cursor) = style.mouse_cursor {
|
||||
let hovered = bounds.contains_point(&cx.mouse_position());
|
||||
let mouse_position = &cx.mouse_position();
|
||||
let hovered = interactive_bounds.visibly_contains(mouse_position, cx);
|
||||
if hovered {
|
||||
cx.set_cursor_style(mouse_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
for listener in self.mouse_down_listeners.drain(..) {
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
listener(event, &bounds, phase, cx);
|
||||
listener(event, &*interactive_bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for listener in self.mouse_up_listeners.drain(..) {
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||
listener(event, &bounds, phase, cx);
|
||||
listener(event, &*interactive_bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for listener in self.mouse_move_listeners.drain(..) {
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
listener(event, &bounds, phase, cx);
|
||||
listener(event, &*interactive_bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for listener in self.scroll_wheel_listeners.drain(..) {
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||
listener(event, &bounds, phase, cx);
|
||||
listener(event, &*interactive_bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -792,6 +824,7 @@ impl Interactivity {
|
||||
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
||||
|
||||
if let Some(group_bounds) = hover_group_bounds {
|
||||
// todo!() needs cx.was_top_layer
|
||||
let hovered = group_bounds.contains_point(&cx.mouse_position());
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
@@ -805,10 +838,11 @@ impl Interactivity {
|
||||
if self.hover_style.is_some()
|
||||
|| (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
|
||||
{
|
||||
let hovered = bounds.contains_point(&cx.mouse_position());
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
let hovered = interactive_bounds.visibly_contains(&cx.mouse_position(), cx);
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
if bounds.contains_point(&event.position) != hovered {
|
||||
if interactive_bounds.visibly_contains(&event.position, cx) != hovered {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
@@ -817,8 +851,11 @@ impl Interactivity {
|
||||
|
||||
if cx.active_drag.is_some() {
|
||||
let drop_listeners = mem::take(&mut self.drop_listeners);
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.visibly_contains(&event.position, &cx)
|
||||
{
|
||||
if let Some(drag_state_type) =
|
||||
cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
|
||||
{
|
||||
@@ -847,6 +884,7 @@ impl Interactivity {
|
||||
if let Some(mouse_down) = mouse_down {
|
||||
if let Some(drag_listener) = drag_listener {
|
||||
let active_state = element_state.clicked_state.clone();
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if cx.active_drag.is_some() {
|
||||
@@ -854,7 +892,7 @@ impl Interactivity {
|
||||
cx.notify();
|
||||
}
|
||||
} else if phase == DispatchPhase::Bubble
|
||||
&& bounds.contains_point(&event.position)
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
&& (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
|
||||
{
|
||||
*active_state.borrow_mut() = ElementClickedState::default();
|
||||
@@ -867,8 +905,11 @@ impl Interactivity {
|
||||
});
|
||||
}
|
||||
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
let mouse_click = ClickEvent {
|
||||
down: mouse_down.clone(),
|
||||
up: event.clone(),
|
||||
@@ -881,8 +922,11 @@ impl Interactivity {
|
||||
cx.notify();
|
||||
});
|
||||
} else {
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
*pending_mouse_down.borrow_mut() = Some(event.clone());
|
||||
cx.notify();
|
||||
}
|
||||
@@ -893,13 +937,14 @@ impl Interactivity {
|
||||
if let Some(hover_listener) = self.hover_listener.take() {
|
||||
let was_hovered = element_state.hover_state.clone();
|
||||
let has_mouse_down = element_state.pending_mouse_down.clone();
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
let is_hovered =
|
||||
bounds.contains_point(&event.position) && has_mouse_down.borrow().is_none();
|
||||
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
|
||||
&& has_mouse_down.borrow().is_none();
|
||||
let mut was_hovered = was_hovered.borrow_mut();
|
||||
|
||||
if is_hovered != was_hovered.clone() {
|
||||
@@ -914,14 +959,15 @@ impl Interactivity {
|
||||
if let Some(tooltip_builder) = self.tooltip_builder.take() {
|
||||
let active_tooltip = element_state.active_tooltip.clone();
|
||||
let pending_mouse_down = element_state.pending_mouse_down.clone();
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
|
||||
let is_hovered =
|
||||
bounds.contains_point(&event.position) && pending_mouse_down.borrow().is_none();
|
||||
let is_hovered = interactive_bounds.visibly_contains(&event.position, cx)
|
||||
&& pending_mouse_down.borrow().is_none();
|
||||
if !is_hovered {
|
||||
active_tooltip.borrow_mut().take();
|
||||
return;
|
||||
@@ -979,11 +1025,12 @@ impl Interactivity {
|
||||
.group_active_style
|
||||
.as_ref()
|
||||
.and_then(|group_active| GroupBounds::get(&group_active.group, cx));
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |down: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
let group = active_group_bounds
|
||||
.map_or(false, |bounds| bounds.contains_point(&down.position));
|
||||
let element = bounds.contains_point(&down.position);
|
||||
let element = interactive_bounds.visibly_contains(&down.position, cx);
|
||||
if group || element {
|
||||
*active_state.borrow_mut() = ElementClickedState { group, element };
|
||||
cx.notify();
|
||||
@@ -1000,9 +1047,12 @@ impl Interactivity {
|
||||
.clone();
|
||||
let line_height = cx.line_height();
|
||||
let scroll_max = (content_size - bounds.size).max(&Size::default());
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
|
||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
let mut scroll_offset = scroll_offset.borrow_mut();
|
||||
let old_scroll_offset = *scroll_offset;
|
||||
let delta = event.delta.pixel_delta(line_height);
|
||||
@@ -1098,19 +1148,21 @@ impl Interactivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
// if self.hover_style.is_some() {
|
||||
if bounds.contains_point(&mouse_position) {
|
||||
// eprintln!("div hovered {bounds:?} {mouse_position:?}");
|
||||
style.refine(&self.hover_style);
|
||||
} else {
|
||||
// eprintln!("div NOT hovered {bounds:?} {mouse_position:?}");
|
||||
if self.hover_style.is_some() {
|
||||
if bounds
|
||||
.intersect(&cx.content_mask().bounds)
|
||||
.contains_point(&mouse_position)
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
style.refine(&self.hover_style);
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
if let Some(drag) = cx.active_drag.take() {
|
||||
for (state_type, group_drag_style) in &self.group_drag_over_styles {
|
||||
if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
|
||||
if *state_type == drag.view.entity_type()
|
||||
// todo!() needs to handle cx.content_mask() and cx.is_top()
|
||||
&& group_bounds.contains_point(&mouse_position)
|
||||
{
|
||||
style.refine(&group_drag_style.style);
|
||||
@@ -1120,7 +1172,10 @@ impl Interactivity {
|
||||
|
||||
for (state_type, drag_over_style) in &self.drag_over_styles {
|
||||
if *state_type == drag.view.entity_type()
|
||||
&& bounds.contains_point(&mouse_position)
|
||||
&& bounds
|
||||
.intersect(&cx.content_mask().bounds)
|
||||
.contains_point(&mouse_position)
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
style.refine(drag_over_style);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ use util::ResultExt;
|
||||
|
||||
/// A global stacking order, which is created by stacking successive z-index values.
|
||||
/// Each z-index will always be interpreted in the context of its parent z-index.
|
||||
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)]
|
||||
pub(crate) struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
|
||||
#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)]
|
||||
pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
|
||||
|
||||
/// Represents the two different phases when dispatching events.
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
|
||||
@@ -243,7 +243,8 @@ pub(crate) struct Frame {
|
||||
pub(crate) dispatch_tree: DispatchTree,
|
||||
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
||||
pub(crate) scene_builder: SceneBuilder,
|
||||
z_index_stack: StackingOrder,
|
||||
pub(crate) depth_map: Vec<(StackingOrder, Bounds<Pixels>)>,
|
||||
pub(crate) z_index_stack: StackingOrder,
|
||||
content_mask_stack: Vec<ContentMask<Pixels>>,
|
||||
element_offset_stack: Vec<Point<Pixels>>,
|
||||
}
|
||||
@@ -257,6 +258,7 @@ impl Frame {
|
||||
focus_listeners: Vec::new(),
|
||||
scene_builder: SceneBuilder::default(),
|
||||
z_index_stack: StackingOrder::default(),
|
||||
depth_map: Default::default(),
|
||||
content_mask_stack: Vec::new(),
|
||||
element_offset_stack: Vec::new(),
|
||||
}
|
||||
@@ -806,6 +808,32 @@ impl<'a> WindowContext<'a> {
|
||||
result
|
||||
}
|
||||
|
||||
/// Called during painting to track which z-index is on top at each pixel position
|
||||
pub fn add_opaque_layer(&mut self, bounds: Bounds<Pixels>) {
|
||||
let stacking_order = self.window.current_frame.z_index_stack.clone();
|
||||
let depth_map = &mut self.window.current_frame.depth_map;
|
||||
match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(&level)) {
|
||||
Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the top-most opaque layer painted over this point was part of the
|
||||
/// same layer as the given stacking order.
|
||||
pub fn was_top_layer(&self, point: &Point<Pixels>, level: &StackingOrder) -> bool {
|
||||
for (stack, bounds) in self.window.previous_frame.depth_map.iter() {
|
||||
if bounds.contains_point(point) {
|
||||
return level.starts_with(stack) || stack.starts_with(level);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Called during painting to get the current stacking order.
|
||||
pub fn stacking_order(&self) -> &StackingOrder {
|
||||
&self.window.current_frame.z_index_stack
|
||||
}
|
||||
|
||||
/// Paint one or more drop shadows into the scene for the current frame at the current z-index.
|
||||
pub fn paint_shadows(
|
||||
&mut self,
|
||||
@@ -1153,6 +1181,7 @@ impl<'a> WindowContext<'a> {
|
||||
frame.mouse_listeners.values_mut().for_each(Vec::clear);
|
||||
frame.focus_listeners.clear();
|
||||
frame.dispatch_tree.clear();
|
||||
frame.depth_map.clear();
|
||||
}
|
||||
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
|
||||
@@ -73,6 +73,7 @@ impl RealNodeRuntime {
|
||||
let npm_file = node_dir.join("bin/npm");
|
||||
|
||||
let result = Command::new(&node_binary)
|
||||
.env_clear()
|
||||
.arg(npm_file)
|
||||
.arg("--version")
|
||||
.stdin(Stdio::null())
|
||||
@@ -149,6 +150,7 @@ impl NodeRuntime for RealNodeRuntime {
|
||||
}
|
||||
|
||||
let mut command = Command::new(node_binary);
|
||||
command.env_clear();
|
||||
command.env("PATH", env_path);
|
||||
command.arg(npm_file).arg(subcommand);
|
||||
command.args(["--cache".into(), installation_path.join("cache")]);
|
||||
@@ -200,11 +202,11 @@ impl NodeRuntime for RealNodeRuntime {
|
||||
&[
|
||||
name,
|
||||
"--json",
|
||||
"-fetch-retry-mintimeout",
|
||||
"--fetch-retry-mintimeout",
|
||||
"2000",
|
||||
"-fetch-retry-maxtimeout",
|
||||
"--fetch-retry-maxtimeout",
|
||||
"5000",
|
||||
"-fetch-timeout",
|
||||
"--fetch-timeout",
|
||||
"5000",
|
||||
],
|
||||
)
|
||||
@@ -229,11 +231,11 @@ impl NodeRuntime for RealNodeRuntime {
|
||||
|
||||
let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
|
||||
arguments.extend_from_slice(&[
|
||||
"-fetch-retry-mintimeout",
|
||||
"--fetch-retry-mintimeout",
|
||||
"2000",
|
||||
"-fetch-retry-maxtimeout",
|
||||
"--fetch-retry-maxtimeout",
|
||||
"5000",
|
||||
"-fetch-timeout",
|
||||
"--fetch-timeout",
|
||||
"5000",
|
||||
]);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ pub struct Picker<D: PickerDelegate> {
|
||||
}
|
||||
|
||||
pub trait PickerDelegate: Sized + 'static {
|
||||
type ListItem: IntoElement;
|
||||
fn match_count(&self) -> usize;
|
||||
fn selected_index(&self) -> usize;
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
|
||||
@@ -31,7 +32,7 @@ pub trait PickerDelegate: Sized + 'static {
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> AnyElement;
|
||||
) -> Option<Self::ListItem>;
|
||||
}
|
||||
|
||||
impl<D: PickerDelegate> FocusableView for Picker<D> {
|
||||
@@ -113,7 +114,6 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
dbg!("canceling!");
|
||||
self.delegate.dismissed(cx);
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(picker.delegate.render_match(
|
||||
.children(picker.delegate.render_match(
|
||||
ix,
|
||||
ix == selected_index,
|
||||
cx,
|
||||
|
||||
@@ -10,9 +10,8 @@ use anyhow::{anyhow, Result};
|
||||
use gpui::{
|
||||
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
|
||||
ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
|
||||
IntoElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel,
|
||||
Render, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||
Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful, Styled,
|
||||
Task, UniformListScrollHandle, View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||
};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::{
|
||||
@@ -30,7 +29,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::ActiveTheme as _;
|
||||
use ui::{h_stack, v_stack, IconElement, Label};
|
||||
use ui::{v_stack, IconElement, Label, ListItem};
|
||||
use unicase::UniCase;
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
@@ -1335,13 +1334,19 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_entry_visual_element(
|
||||
details: &EntryDetails,
|
||||
editor: Option<&View<Editor>>,
|
||||
padding: Pixels,
|
||||
fn render_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
details: EntryDetails,
|
||||
// dragged_entry_destination: &mut Option<Arc<Path>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Div {
|
||||
) -> ListItem {
|
||||
let kind = details.kind;
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
let show_editor = details.is_editing && !details.is_processing;
|
||||
let is_selected = self
|
||||
.selection
|
||||
.map_or(false, |selection| selection.entry_id == entry_id);
|
||||
|
||||
let theme = cx.theme();
|
||||
let filename_text_color = details
|
||||
@@ -1354,14 +1359,17 @@ impl ProjectPanel {
|
||||
})
|
||||
.unwrap_or(theme.status().info);
|
||||
|
||||
h_stack()
|
||||
ListItem::new(entry_id.to_proto() as usize)
|
||||
.indent_level(details.depth)
|
||||
.indent_step_size(px(settings.indent_size))
|
||||
.selected(is_selected)
|
||||
.child(if let Some(icon) = &details.icon {
|
||||
div().child(IconElement::from_path(icon.to_string()))
|
||||
} else {
|
||||
div()
|
||||
})
|
||||
.child(
|
||||
if let (Some(editor), true) = (editor, show_editor) {
|
||||
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
|
||||
div().w_full().child(editor.clone())
|
||||
} else {
|
||||
div()
|
||||
@@ -1370,33 +1378,6 @@ impl ProjectPanel {
|
||||
}
|
||||
.ml_1(),
|
||||
)
|
||||
.pl(padding)
|
||||
}
|
||||
|
||||
fn render_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
details: EntryDetails,
|
||||
// dragged_entry_destination: &mut Option<Arc<Path>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Stateful<Div> {
|
||||
let kind = details.kind;
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
const INDENT_SIZE: Pixels = px(16.0);
|
||||
let padding = INDENT_SIZE + details.depth as f32 * px(settings.indent_size);
|
||||
let show_editor = details.is_editing && !details.is_processing;
|
||||
let is_selected = self
|
||||
.selection
|
||||
.map_or(false, |selection| selection.entry_id == entry_id);
|
||||
|
||||
Self::render_entry_visual_element(&details, Some(&self.filename_editor), padding, cx)
|
||||
.id(entry_id.to_proto() as usize)
|
||||
.w_full()
|
||||
.cursor_pointer()
|
||||
.when(is_selected, |this| {
|
||||
this.bg(cx.theme().colors().element_selected)
|
||||
})
|
||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||
.on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| {
|
||||
if !show_editor {
|
||||
if kind.is_dir() {
|
||||
@@ -1410,12 +1391,9 @@ impl ProjectPanel {
|
||||
}
|
||||
}
|
||||
}))
|
||||
.on_mouse_down(
|
||||
MouseButton::Right,
|
||||
cx.listener(move |this, event: &MouseDownEvent, cx| {
|
||||
this.deploy_context_menu(event.position, entry_id, cx);
|
||||
}),
|
||||
)
|
||||
.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| {
|
||||
this.deploy_context_menu(event.position, entry_id, cx);
|
||||
}))
|
||||
// .on_drop::<ProjectEntryId>(|this, event, cx| {
|
||||
// this.move_entry(
|
||||
// *dragged_entry,
|
||||
|
||||
@@ -33,7 +33,6 @@ impl Render for FocusStory {
|
||||
let theme = cx.theme();
|
||||
let color_1 = theme.status().created;
|
||||
let color_2 = theme.status().modified;
|
||||
let color_3 = theme.status().deleted;
|
||||
let color_4 = theme.status().conflict;
|
||||
let color_5 = theme.status().ignored;
|
||||
let color_6 = theme.status().renamed;
|
||||
@@ -42,10 +41,10 @@ impl Render for FocusStory {
|
||||
.id("parent")
|
||||
.focusable()
|
||||
.key_context("parent")
|
||||
.on_action(cx.listener(|_, action: &ActionA, cx| {
|
||||
.on_action(cx.listener(|_, _action: &ActionA, _cx| {
|
||||
println!("Action A dispatched on parent");
|
||||
}))
|
||||
.on_action(cx.listener(|_, action: &ActionB, cx| {
|
||||
.on_action(cx.listener(|_, _action: &ActionB, _cx| {
|
||||
println!("Action B dispatched on parent");
|
||||
}))
|
||||
.on_focus(cx.listener(|_, _, _| println!("Parent focused")))
|
||||
@@ -61,7 +60,7 @@ impl Render for FocusStory {
|
||||
div()
|
||||
.track_focus(&self.child_1_focus)
|
||||
.key_context("child-1")
|
||||
.on_action(cx.listener(|_, action: &ActionB, cx| {
|
||||
.on_action(cx.listener(|_, _action: &ActionB, _cx| {
|
||||
println!("Action B dispatched on child 1 during");
|
||||
}))
|
||||
.w_full()
|
||||
@@ -83,7 +82,7 @@ impl Render for FocusStory {
|
||||
div()
|
||||
.track_focus(&self.child_2_focus)
|
||||
.key_context("child-2")
|
||||
.on_action(cx.listener(|_, action: &ActionC, cx| {
|
||||
.on_action(cx.listener(|_, _action: &ActionC, _cx| {
|
||||
println!("Action C dispatched on child 2");
|
||||
}))
|
||||
.w_full()
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct KitchenSinkStory;
|
||||
|
||||
impl KitchenSinkStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.build_view(|cx| Self)
|
||||
cx.build_view(|_cx| Self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
div, prelude::*, AnyElement, Div, KeyBinding, Render, SharedString, Styled, Task, View,
|
||||
WindowContext,
|
||||
div, prelude::*, Div, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
use theme2::ActiveTheme;
|
||||
use ui::{Label, ListItem};
|
||||
|
||||
pub struct PickerStory {
|
||||
picker: View<Picker<Delegate>>,
|
||||
@@ -37,6 +37,8 @@ impl Delegate {
|
||||
}
|
||||
|
||||
impl PickerDelegate for Delegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.candidates.len()
|
||||
}
|
||||
@@ -49,27 +51,20 @@ impl PickerDelegate for Delegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||
) -> AnyElement {
|
||||
let colors = cx.theme().colors();
|
||||
_cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let Some(candidate_ix) = self.matches.get(ix) else {
|
||||
return div().into_any();
|
||||
return None;
|
||||
};
|
||||
// TASK: Make StringMatchCandidate::string a SharedString
|
||||
let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
|
||||
|
||||
div()
|
||||
.text_color(colors.text)
|
||||
.when(selected, |s| {
|
||||
s.border_l_10().border_color(colors.terminal_ansi_yellow)
|
||||
})
|
||||
.hover(|style| {
|
||||
style
|
||||
.bg(colors.element_active)
|
||||
.text_color(colors.text_accent)
|
||||
})
|
||||
.child(candidate)
|
||||
.into_any()
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.selected(selected)
|
||||
.child(Label::new(candidate)),
|
||||
)
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
@@ -81,7 +76,7 @@ impl PickerDelegate for Delegate {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, secondary: bool, _cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
let candidate_ix = self.matches[self.selected_ix];
|
||||
let candidate = self.candidates[candidate_ix].string.clone();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ pub struct ScrollStory;
|
||||
|
||||
impl ScrollStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
|
||||
cx.build_view(|cx| ScrollStory)
|
||||
cx.build_view(|_cx| ScrollStory)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ pub struct TextStory;
|
||||
|
||||
impl TextStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.build_view(|cx| Self)
|
||||
cx.build_view(|_cx| Self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl Render for TextStory {
|
||||
cx.text_style().to_run(18),
|
||||
]),
|
||||
)
|
||||
.on_click(vec![2..4, 1..3, 7..9], |range_ix, cx| {
|
||||
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||
println!("Clicked range {range_ix}");
|
||||
})
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct ZIndexStory;
|
||||
impl Render for ZIndexStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container().child(Story::title("z-index")).child(
|
||||
div()
|
||||
.flex()
|
||||
@@ -84,7 +84,7 @@ struct ZIndexExample {
|
||||
impl RenderOnce for ZIndexExample {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
div()
|
||||
.relative()
|
||||
.size_full()
|
||||
|
||||
@@ -18,6 +18,7 @@ pub enum ComponentStory {
|
||||
ContextMenu,
|
||||
Focus,
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
Keybinding,
|
||||
Label,
|
||||
@@ -37,6 +38,7 @@ impl ComponentStory {
|
||||
Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
|
||||
Self::Focus => FocusStory::view(cx).into(),
|
||||
Self::Icon => cx.build_view(|_| ui::IconStory).into(),
|
||||
Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),
|
||||
Self::Input => cx.build_view(|_| ui::InputStory).into(),
|
||||
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
|
||||
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
mod assets;
|
||||
mod stories;
|
||||
mod story_selector;
|
||||
@@ -70,7 +68,7 @@ fn main() {
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
|
||||
let window = cx.open_window(
|
||||
let _window = cx.open_window(
|
||||
WindowOptions {
|
||||
bounds: WindowBounds::Fixed(Bounds {
|
||||
origin: Default::default(),
|
||||
@@ -104,7 +102,7 @@ impl StoryWrapper {
|
||||
impl Render for StoryWrapper {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
|
||||
@@ -23,15 +23,15 @@ impl ThemeColors {
|
||||
surface_background: neutral().light().step_2(),
|
||||
background: neutral().light().step_1(),
|
||||
element_background: neutral().light().step_3(),
|
||||
element_hover: neutral().light().step_4(),
|
||||
element_active: neutral().light().step_5(),
|
||||
element_selected: neutral().light().step_5(),
|
||||
element_hover: neutral().light_alpha().step_4(),
|
||||
element_active: neutral().light_alpha().step_5(),
|
||||
element_selected: neutral().light_alpha().step_5(),
|
||||
element_disabled: neutral().light_alpha().step_3(),
|
||||
drop_target_background: blue().light_alpha().step_2(),
|
||||
ghost_element_background: system.transparent,
|
||||
ghost_element_hover: neutral().light().step_4(),
|
||||
ghost_element_active: neutral().light().step_5(),
|
||||
ghost_element_selected: neutral().light().step_5(),
|
||||
ghost_element_hover: neutral().light_alpha().step_4(),
|
||||
ghost_element_active: neutral().light_alpha().step_5(),
|
||||
ghost_element_selected: neutral().light_alpha().step_5(),
|
||||
ghost_element_disabled: neutral().light_alpha().step_3(),
|
||||
text: yellow().light().step_9(),
|
||||
text_muted: neutral().light().step_11(),
|
||||
@@ -95,15 +95,15 @@ impl ThemeColors {
|
||||
surface_background: neutral().dark().step_2(),
|
||||
background: neutral().dark().step_1(),
|
||||
element_background: neutral().dark().step_3(),
|
||||
element_hover: neutral().dark().step_4(),
|
||||
element_active: neutral().dark().step_5(),
|
||||
element_selected: neutral().dark().step_5(),
|
||||
element_hover: neutral().dark_alpha().step_4(),
|
||||
element_active: neutral().dark_alpha().step_5(),
|
||||
element_selected: neutral().dark_alpha().step_5(),
|
||||
element_disabled: neutral().dark_alpha().step_3(),
|
||||
drop_target_background: blue().dark_alpha().step_2(),
|
||||
ghost_element_background: system.transparent,
|
||||
ghost_element_hover: neutral().dark().step_4(),
|
||||
ghost_element_active: neutral().dark().step_5(),
|
||||
ghost_element_selected: neutral().dark().step_5(),
|
||||
ghost_element_hover: neutral().dark_alpha().step_4(),
|
||||
ghost_element_active: neutral().dark_alpha().step_5(),
|
||||
ghost_element_selected: neutral().dark_alpha().step_5(),
|
||||
ghost_element_disabled: neutral().dark_alpha().step_3(),
|
||||
text: neutral().dark().step_12(),
|
||||
text_muted: neutral().dark().step_11(),
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn one_family() -> ThemeFamily {
|
||||
pub(crate) fn one_dark() -> Theme {
|
||||
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
|
||||
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
|
||||
let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
|
||||
let elevated_surface = hsla(225. / 360., 12. / 100., 17. / 100., 1.);
|
||||
|
||||
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
|
||||
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
|
||||
@@ -48,7 +48,7 @@ pub(crate) fn one_dark() -> Theme {
|
||||
elevated_surface_background: elevated_surface,
|
||||
surface_background: bg,
|
||||
background: bg,
|
||||
element_background: elevated_surface,
|
||||
element_background: hsla(223.0 / 360., 13. / 100., 21. / 100., 1.0),
|
||||
element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
|
||||
element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
|
||||
element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
|
||||
|
||||
@@ -13,6 +13,7 @@ editor = { package = "editor2", path = "../editor2" }
|
||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||
fs = { package = "fs2", path = "../fs2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
ui = { package = "ui2", path = "../ui2" }
|
||||
picker = { package = "picker2", path = "../picker2" }
|
||||
theme = { package = "theme2", path = "../theme2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
|
||||
@@ -2,19 +2,16 @@ use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, div, AnyElement, AppContext, DismissEvent, Element, EventEmitter, FocusableView,
|
||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, View,
|
||||
ViewContext, VisualContext, WeakView,
|
||||
actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
|
||||
SharedString, View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use settings::{update_settings_file, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
|
||||
use ui::ListItem;
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
ui::{HighlightedLabel, StyledExt},
|
||||
Workspace,
|
||||
};
|
||||
use workspace::{ui::HighlightedLabel, Workspace};
|
||||
|
||||
actions!(Toggle, Reload);
|
||||
|
||||
@@ -160,6 +157,8 @@ impl ThemeSelectorDelegate {
|
||||
}
|
||||
|
||||
impl PickerDelegate for ThemeSelectorDelegate {
|
||||
type ListItem = ui::ListItem;
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Select Theme...".into()
|
||||
}
|
||||
@@ -260,24 +259,18 @@ impl PickerDelegate for ThemeSelectorDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> AnyElement {
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors();
|
||||
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let theme_match = &self.matches[ix];
|
||||
div()
|
||||
.px_1()
|
||||
.text_color(colors.text)
|
||||
.text_ui()
|
||||
.bg(colors.ghost_element_background)
|
||||
.rounded_md()
|
||||
.when(selected, |this| this.bg(colors.ghost_element_selected))
|
||||
.hover(|this| this.bg(colors.ghost_element_hover))
|
||||
.child(HighlightedLabel::new(
|
||||
theme_match.string.clone(),
|
||||
theme_match.positions.clone(),
|
||||
))
|
||||
.into_any()
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.selected(selected)
|
||||
.child(HighlightedLabel::new(
|
||||
theme_match.string.clone(),
|
||||
theme_match.positions.clone(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ gpui = { package = "gpui2", path = "../gpui2" }
|
||||
itertools = { version = "0.11.0", optional = true }
|
||||
menu = { package = "menu2", path = "../menu2"}
|
||||
serde.workspace = true
|
||||
settings2 = { path = "../settings2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
smallvec.workspace = true
|
||||
story = { path = "../story", optional = true }
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
theme2 = { path = "../theme2" }
|
||||
theme = { package = "theme2", path = "../theme2" }
|
||||
rand = "0.8"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -49,6 +49,12 @@ impl Avatar {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source(src: ImageSource) -> Self {
|
||||
Self {
|
||||
src,
|
||||
shape: Shape::Circle,
|
||||
}
|
||||
}
|
||||
pub fn shape(mut self, shape: Shape) -> Self {
|
||||
self.shape = shape;
|
||||
self
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use gpui::{div, prelude::*, Div, Element, ElementId, IntoElement, Styled, WindowContext};
|
||||
|
||||
use theme2::ActiveTheme;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Color, Icon, IconElement, Selection};
|
||||
|
||||
pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
|
||||
|
||||
@@ -264,7 +264,7 @@ impl<M: ManagedView> Element for MenuHandle<M> {
|
||||
|
||||
let new_menu = (builder)(cx);
|
||||
let menu2 = menu.clone();
|
||||
cx.subscribe(&new_menu, move |modal, e, cx| match e {
|
||||
cx.subscribe(&new_menu, move |_modal, e, cx| match e {
|
||||
&DismissEvent::Dismiss => {
|
||||
*menu2.borrow_mut() = None;
|
||||
cx.notify();
|
||||
|
||||
@@ -49,17 +49,4 @@ impl Divider {
|
||||
self.inset = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> impl Element {
|
||||
div()
|
||||
.map(|this| match self.direction {
|
||||
DividerDirection::Horizontal => {
|
||||
this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
|
||||
}
|
||||
DividerDirection::Vertical => {
|
||||
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
|
||||
}
|
||||
})
|
||||
.bg(cx.theme().colors().border_variant)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ pub enum IconSize {
|
||||
pub enum Icon {
|
||||
Ai,
|
||||
ArrowLeft,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
ArrowRight,
|
||||
ArrowUpRight,
|
||||
AtSign,
|
||||
@@ -61,6 +63,7 @@ pub enum Icon {
|
||||
Mic,
|
||||
MicMute,
|
||||
Plus,
|
||||
Public,
|
||||
Quote,
|
||||
Replace,
|
||||
ReplaceAll,
|
||||
@@ -71,6 +74,11 @@ pub enum Icon {
|
||||
Terminal,
|
||||
WholeWord,
|
||||
XCircle,
|
||||
Command,
|
||||
Control,
|
||||
Shift,
|
||||
Option,
|
||||
Return,
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
@@ -79,6 +87,8 @@ impl Icon {
|
||||
Icon::Ai => "icons/ai.svg",
|
||||
Icon::ArrowLeft => "icons/arrow_left.svg",
|
||||
Icon::ArrowRight => "icons/arrow_right.svg",
|
||||
Icon::ArrowUp => "icons/arrow_up.svg",
|
||||
Icon::ArrowDown => "icons/arrow_down.svg",
|
||||
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
|
||||
Icon::AtSign => "icons/at-sign.svg",
|
||||
Icon::AudioOff => "icons/speaker-off.svg",
|
||||
@@ -125,6 +135,7 @@ impl Icon {
|
||||
Icon::Mic => "icons/mic.svg",
|
||||
Icon::MicMute => "icons/mic-mute.svg",
|
||||
Icon::Plus => "icons/plus.svg",
|
||||
Icon::Public => "icons/public.svg",
|
||||
Icon::Quote => "icons/quote.svg",
|
||||
Icon::Replace => "icons/replace.svg",
|
||||
Icon::ReplaceAll => "icons/replace_all.svg",
|
||||
@@ -135,6 +146,11 @@ impl Icon {
|
||||
Icon::Terminal => "icons/terminal.svg",
|
||||
Icon::WholeWord => "icons/word_search.svg",
|
||||
Icon::XCircle => "icons/error.svg",
|
||||
Icon::Command => "icons/command.svg",
|
||||
Icon::Control => "icons/control.svg",
|
||||
Icon::Shift => "icons/shift.svg",
|
||||
Icon::Option => "icons/option.svg",
|
||||
Icon::Return => "icons/return.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,8 +167,8 @@ impl RenderOnce for IconElement {
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let svg_size = match self.size {
|
||||
IconSize::Small => rems(0.75),
|
||||
IconSize::Medium => rems(0.9375),
|
||||
IconSize::Small => rems(14. / 16.),
|
||||
IconSize::Medium => rems(16. / 16.),
|
||||
};
|
||||
|
||||
svg()
|
||||
@@ -189,17 +205,4 @@ impl IconElement {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> impl Element {
|
||||
let svg_size = match self.size {
|
||||
IconSize::Small => rems(0.75),
|
||||
IconSize::Medium => rems(0.9375),
|
||||
};
|
||||
|
||||
svg()
|
||||
.size(svg_size)
|
||||
.flex_none()
|
||||
.path(self.path)
|
||||
.text_color(self.color.color(cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,13 @@ impl RenderOnce for IconButton {
|
||||
_ => self.color,
|
||||
};
|
||||
|
||||
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
|
||||
let (mut bg_color, bg_active_color) = match self.variant {
|
||||
ButtonVariant::Filled => (
|
||||
cx.theme().colors().element_background,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
ButtonVariant::Ghost => (
|
||||
cx.theme().colors().ghost_element_background,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
};
|
||||
@@ -67,7 +65,8 @@ impl RenderOnce for IconButton {
|
||||
}
|
||||
}
|
||||
|
||||
button
|
||||
// HACK: Add an additional identified element wrapper to fix tooltips not showing up.
|
||||
div().id(self.id.clone()).child(button)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +123,6 @@ impl IconButton {
|
||||
}
|
||||
|
||||
pub fn action(self, action: Box<dyn Action>) -> Self {
|
||||
self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
use gpui::{Action, Div, IntoElement};
|
||||
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
|
||||
use gpui::{relative, rems, Action, Div, IntoElement, Keystroke};
|
||||
|
||||
#[derive(IntoElement, Clone)]
|
||||
pub struct KeyBinding {
|
||||
@@ -14,19 +14,35 @@ impl RenderOnce for KeyBinding {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
div()
|
||||
.flex()
|
||||
h_stack()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
|
||||
div()
|
||||
.flex()
|
||||
.gap_1()
|
||||
let key_icon = Self::icon_for_key(&keystroke);
|
||||
|
||||
h_stack()
|
||||
.flex_none()
|
||||
.gap_0p5()
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.p_0p5()
|
||||
.rounded_sm()
|
||||
.when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
|
||||
.when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
|
||||
.when(keystroke.modifiers.alt, |el| el.child(Key::new("⌥")))
|
||||
.when(keystroke.modifiers.command, |el| el.child(Key::new("⌘")))
|
||||
.when(keystroke.modifiers.shift, |el| el.child(Key::new("⇧")))
|
||||
.child(Key::new(keystroke.key.clone()))
|
||||
.when(keystroke.modifiers.control, |el| {
|
||||
el.child(KeyIcon::new(Icon::Control))
|
||||
})
|
||||
.when(keystroke.modifiers.alt, |el| {
|
||||
el.child(KeyIcon::new(Icon::Option))
|
||||
})
|
||||
.when(keystroke.modifiers.command, |el| {
|
||||
el.child(KeyIcon::new(Icon::Command))
|
||||
})
|
||||
.when(keystroke.modifiers.shift, |el| {
|
||||
el.child(KeyIcon::new(Icon::Shift))
|
||||
})
|
||||
.when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
|
||||
.when(key_icon.is_none(), |el| {
|
||||
el.child(Key::new(keystroke.key.to_uppercase().clone()))
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -39,6 +55,22 @@ impl KeyBinding {
|
||||
Some(Self::new(key_binding))
|
||||
}
|
||||
|
||||
fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
|
||||
let mut icon: Option<Icon> = None;
|
||||
|
||||
if keystroke.key == "left".to_string() {
|
||||
icon = Some(Icon::ArrowLeft);
|
||||
} else if keystroke.key == "right".to_string() {
|
||||
icon = Some(Icon::ArrowRight);
|
||||
} else if keystroke.key == "up".to_string() {
|
||||
icon = Some(Icon::ArrowUp);
|
||||
} else if keystroke.key == "down".to_string() {
|
||||
icon = Some(Icon::ArrowDown);
|
||||
}
|
||||
|
||||
icon
|
||||
}
|
||||
|
||||
pub fn new(key_binding: gpui::KeyBinding) -> Self {
|
||||
Self { key_binding }
|
||||
}
|
||||
@@ -53,13 +85,18 @@ impl RenderOnce for Key {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let single_char = self.key.len() == 1;
|
||||
|
||||
div()
|
||||
.px_2()
|
||||
.py_0()
|
||||
.rounded_md()
|
||||
.text_ui_sm()
|
||||
.when(single_char, |el| {
|
||||
el.w(rems(14. / 16.)).flex().flex_none().justify_center()
|
||||
})
|
||||
.when(!single_char, |el| el.px_0p5())
|
||||
.h(rems(14. / 16.))
|
||||
.text_ui()
|
||||
.line_height(relative(1.))
|
||||
.text_color(cx.theme().colors().text)
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.child(self.key.clone())
|
||||
}
|
||||
}
|
||||
@@ -69,3 +106,24 @@ impl Key {
|
||||
Self { key: key.into() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct KeyIcon {
|
||||
icon: Icon,
|
||||
}
|
||||
|
||||
impl RenderOnce for KeyIcon {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
div()
|
||||
.w(rems(14. / 16.))
|
||||
.child(IconElement::new(self.icon).size(IconSize::Small))
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyIcon {
|
||||
pub fn new(icon: Icon) -> Self {
|
||||
Self { icon }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
use crate::styled_ext::StyledExt;
|
||||
use gpui::{relative, Div, Hsla, IntoElement, StyledText, TextRun, WindowContext};
|
||||
use gpui::{relative, Div, IntoElement, StyledText, TextRun, WindowContext};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
|
||||
pub enum LabelSize {
|
||||
@@ -182,9 +182,3 @@ impl HighlightedLabel {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A run of text that receives the same style.
|
||||
struct Run {
|
||||
pub text: String,
|
||||
pub color: Hsla,
|
||||
}
|
||||
|
||||
+117
-174
@@ -1,25 +1,19 @@
|
||||
use gpui::{
|
||||
div, px, AnyElement, ClickEvent, Div, IntoElement, Stateful, StatefulInteractiveElement,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
div, px, AnyElement, ClickEvent, Div, ImageSource, IntoElement, MouseButton, MouseDownEvent,
|
||||
Pixels, Stateful, StatefulInteractiveElement,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
|
||||
disclosure_control, h_stack, v_stack, Avatar, Icon, IconButton, IconElement, IconSize, Label,
|
||||
Toggle,
|
||||
};
|
||||
use crate::{prelude::*, GraphicSlot};
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, PartialEq)]
|
||||
pub enum ListItemVariant {
|
||||
/// The list item extends to the far left and right of the list.
|
||||
FullWidth,
|
||||
#[default]
|
||||
Inset,
|
||||
}
|
||||
|
||||
pub enum ListHeaderMeta {
|
||||
// TODO: These should be IconButtons
|
||||
Tools(Vec<Icon>),
|
||||
Tools(Vec<IconButton>),
|
||||
// TODO: This should be a button
|
||||
Button(Label),
|
||||
Text(Label),
|
||||
@@ -30,8 +24,39 @@ pub struct ListHeader {
|
||||
label: SharedString,
|
||||
left_icon: Option<Icon>,
|
||||
meta: Option<ListHeaderMeta>,
|
||||
variant: ListItemVariant,
|
||||
toggle: Toggle,
|
||||
inset: bool,
|
||||
}
|
||||
|
||||
impl ListHeader {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
left_icon: None,
|
||||
meta: None,
|
||||
inset: false,
|
||||
toggle: Toggle::NotToggleable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||
self.toggle = toggle;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||
self.left_icon = left_icon;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn right_button(self, button: IconButton) -> Self {
|
||||
self.meta(Some(ListHeaderMeta::Tools(vec![button])))
|
||||
}
|
||||
|
||||
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
|
||||
self.meta = meta;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ListHeader {
|
||||
@@ -45,11 +70,7 @@ impl RenderOnce for ListHeader {
|
||||
h_stack()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.children(icons.into_iter().map(|i| {
|
||||
IconElement::new(i)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small)
|
||||
})),
|
||||
.children(icons.into_iter().map(|i| i.color(Color::Muted))),
|
||||
),
|
||||
Some(ListHeaderMeta::Button(label)) => div().child(label),
|
||||
Some(ListHeaderMeta::Text(label)) => div().child(label),
|
||||
@@ -63,7 +84,7 @@ impl RenderOnce for ListHeader {
|
||||
.child(
|
||||
div()
|
||||
.h_5()
|
||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
.when(self.inset, |this| this.px_2())
|
||||
.flex()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
@@ -92,98 +113,11 @@ impl RenderOnce for ListHeader {
|
||||
}
|
||||
}
|
||||
|
||||
impl ListHeader {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
left_icon: None,
|
||||
meta: None,
|
||||
variant: ListItemVariant::default(),
|
||||
toggle: Toggle::NotToggleable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||
self.toggle = toggle;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
|
||||
self.left_icon = left_icon;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
|
||||
self.meta = meta;
|
||||
self
|
||||
}
|
||||
|
||||
// before_ship!("delete")
|
||||
// fn render<V: 'static>(self, cx: &mut WindowContext) -> impl Element<V> {
|
||||
// let disclosure_control = disclosure_control(self.toggle);
|
||||
|
||||
// let meta = match self.meta {
|
||||
// Some(ListHeaderMeta::Tools(icons)) => div().child(
|
||||
// h_stack()
|
||||
// .gap_2()
|
||||
// .items_center()
|
||||
// .children(icons.into_iter().map(|i| {
|
||||
// IconElement::new(i)
|
||||
// .color(TextColor::Muted)
|
||||
// .size(IconSize::Small)
|
||||
// })),
|
||||
// ),
|
||||
// Some(ListHeaderMeta::Button(label)) => div().child(label),
|
||||
// Some(ListHeaderMeta::Text(label)) => div().child(label),
|
||||
// None => div(),
|
||||
// };
|
||||
|
||||
// h_stack()
|
||||
// .w_full()
|
||||
// .bg(cx.theme().colors().surface_background)
|
||||
// // TODO: Add focus state
|
||||
// // .when(self.state == InteractionState::Focused, |this| {
|
||||
// // this.border()
|
||||
// // .border_color(cx.theme().colors().border_focused)
|
||||
// // })
|
||||
// .relative()
|
||||
// .child(
|
||||
// div()
|
||||
// .h_5()
|
||||
// .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
// .flex()
|
||||
// .flex_1()
|
||||
// .items_center()
|
||||
// .justify_between()
|
||||
// .w_full()
|
||||
// .gap_1()
|
||||
// .child(
|
||||
// h_stack()
|
||||
// .gap_1()
|
||||
// .child(
|
||||
// div()
|
||||
// .flex()
|
||||
// .gap_1()
|
||||
// .items_center()
|
||||
// .children(self.left_icon.map(|i| {
|
||||
// IconElement::new(i)
|
||||
// .color(TextColor::Muted)
|
||||
// .size(IconSize::Small)
|
||||
// }))
|
||||
// .child(Label::new(self.label.clone()).color(TextColor::Muted)),
|
||||
// )
|
||||
// .child(disclosure_control),
|
||||
// )
|
||||
// .child(meta),
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(IntoElement, Clone)]
|
||||
pub struct ListSubHeader {
|
||||
label: SharedString,
|
||||
left_icon: Option<Icon>,
|
||||
variant: ListItemVariant,
|
||||
inset: bool,
|
||||
}
|
||||
|
||||
impl ListSubHeader {
|
||||
@@ -191,7 +125,7 @@ impl ListSubHeader {
|
||||
Self {
|
||||
label: label.into(),
|
||||
left_icon: None,
|
||||
variant: ListItemVariant::default(),
|
||||
inset: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,11 +138,11 @@ impl ListSubHeader {
|
||||
impl RenderOnce for ListSubHeader {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
h_stack().flex_1().w_full().relative().py_1().child(
|
||||
div()
|
||||
.h_6()
|
||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
.when(self.inset, |this| this.px_2())
|
||||
.flex()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
@@ -231,26 +165,19 @@ impl RenderOnce for ListSubHeader {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum ListEntrySize {
|
||||
#[default]
|
||||
Small,
|
||||
Medium,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ListItem {
|
||||
id: ElementId,
|
||||
disabled: bool,
|
||||
selected: bool,
|
||||
// TODO: Reintroduce this
|
||||
// disclosure_control_style: DisclosureControlVisibility,
|
||||
indent_level: u32,
|
||||
indent_level: usize,
|
||||
indent_step_size: Pixels,
|
||||
left_slot: Option<GraphicSlot>,
|
||||
overflow: OverflowStyle,
|
||||
size: ListEntrySize,
|
||||
toggle: Toggle,
|
||||
variant: ListItemVariant,
|
||||
inset: bool,
|
||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_secondary_mouse_down: Option<Rc<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
@@ -258,14 +185,14 @@ impl ListItem {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
disabled: false,
|
||||
selected: false,
|
||||
indent_level: 0,
|
||||
indent_step_size: px(12.),
|
||||
left_slot: None,
|
||||
overflow: OverflowStyle::Hidden,
|
||||
size: ListEntrySize::default(),
|
||||
toggle: Toggle::NotToggleable,
|
||||
variant: ListItemVariant::default(),
|
||||
on_click: Default::default(),
|
||||
inset: false,
|
||||
on_click: None,
|
||||
on_secondary_mouse_down: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
@@ -275,21 +202,39 @@ impl ListItem {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn variant(mut self, variant: ListItemVariant) -> Self {
|
||||
self.variant = variant;
|
||||
pub fn on_secondary_mouse_down(
|
||||
mut self,
|
||||
handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_secondary_mouse_down = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn indent_level(mut self, indent_level: u32) -> Self {
|
||||
pub fn inset(mut self, inset: bool) -> Self {
|
||||
self.inset = inset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn indent_level(mut self, indent_level: usize) -> Self {
|
||||
self.indent_level = indent_level;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
|
||||
self.indent_step_size = indent_step_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn toggle(mut self, toggle: Toggle) -> Self {
|
||||
self.toggle = toggle;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
|
||||
self.left_slot = Some(left_content);
|
||||
self
|
||||
@@ -300,15 +245,10 @@ impl ListItem {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self {
|
||||
pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
|
||||
self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(mut self, size: ListEntrySize) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ListItem {
|
||||
@@ -323,61 +263,64 @@ impl RenderOnce for ListItem {
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::uri(src))),
|
||||
Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::source(src))),
|
||||
Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::uri(src))),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let sized_item = match self.size {
|
||||
ListEntrySize::Small => div().h_6(),
|
||||
ListEntrySize::Medium => div().h_7(),
|
||||
};
|
||||
div()
|
||||
.id(self.id)
|
||||
.relative()
|
||||
.hover(|mut style| {
|
||||
style.background = Some(cx.theme().colors().editor_background.into());
|
||||
style
|
||||
})
|
||||
.on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |event, cx| {
|
||||
if let Some(on_click) = &on_click {
|
||||
(on_click)(event, cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
// TODO: Add focus state
|
||||
// .when(self.state == InteractionState::Focused, |this| {
|
||||
// this.border()
|
||||
// .border_color(cx.theme().colors().border_focused)
|
||||
// })
|
||||
.when(self.inset, |this| this.rounded_md())
|
||||
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
|
||||
.active(|style| style.bg(cx.theme().colors().ghost_element_active))
|
||||
.when(self.selected, |this| {
|
||||
this.bg(cx.theme().colors().ghost_element_selected)
|
||||
})
|
||||
.when_some(self.on_click.clone(), |this, on_click| {
|
||||
this.on_click(move |event, cx| {
|
||||
// HACK: GPUI currently fires `on_click` with any mouse button,
|
||||
// but we only care about the left button.
|
||||
if event.down.button == MouseButton::Left {
|
||||
(on_click)(event, cx)
|
||||
}
|
||||
})
|
||||
})
|
||||
.when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
|
||||
this.on_mouse_down(MouseButton::Right, move |event, cx| {
|
||||
(on_mouse_down)(event, cx)
|
||||
})
|
||||
})
|
||||
.child(
|
||||
sized_item
|
||||
.when(self.variant == ListItemVariant::Inset, |this| this.px_2())
|
||||
// .ml(rems(0.75 * self.indent_level as f32))
|
||||
.children((0..self.indent_level).map(|_| {
|
||||
div()
|
||||
.w(px(4.))
|
||||
.h_full()
|
||||
.flex()
|
||||
.justify_center()
|
||||
.group_hover("", |style| style.bg(cx.theme().colors().border_focused))
|
||||
.child(
|
||||
h_stack()
|
||||
.child(div().w_px().h_full())
|
||||
.child(div().w_px().h_full().bg(cx.theme().colors().border)),
|
||||
)
|
||||
}))
|
||||
div()
|
||||
.when(self.inset, |this| this.px_2())
|
||||
.ml(self.indent_level as f32 * self.indent_step_size)
|
||||
.flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.relative()
|
||||
.child(disclosure_control(self.toggle))
|
||||
.children(left_content)
|
||||
.children(self.children),
|
||||
.children(self.children)
|
||||
// HACK: We need to attach the `on_click` handler to the child element in order to have the click
|
||||
// event actually fire.
|
||||
// Once this is fixed in GPUI we can remove this and rely on the `on_click` handler set above on the
|
||||
// outer `div`.
|
||||
.id("on_click_hack")
|
||||
.when_some(self.on_click, |this, on_click| {
|
||||
this.on_click(move |event, cx| {
|
||||
// HACK: GPUI currently fires `on_click` with any mouse button,
|
||||
// but we only care about the left button.
|
||||
if event.down.button == MouseButton::Left {
|
||||
(on_click)(event, cx)
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -418,7 +361,7 @@ pub struct List {
|
||||
impl RenderOnce for List {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||
let list_content = match (self.children.is_empty(), self.toggle) {
|
||||
(false, _) => div().children(self.children),
|
||||
(true, Toggle::Toggled(false)) => div(),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use gpui::{
|
||||
AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
|
||||
div, AnyElement, Div, Element, ElementId, IntoElement, ParentElement, RenderOnce, Styled,
|
||||
WindowContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{v_stack, StyledExt};
|
||||
use crate::prelude::*;
|
||||
use crate::v_stack;
|
||||
|
||||
/// A popover is used to display a menu or show some options.
|
||||
///
|
||||
@@ -43,22 +44,16 @@ impl RenderOnce for Popover {
|
||||
type Rendered = Div;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
v_stack()
|
||||
.relative()
|
||||
.elevation_2(cx)
|
||||
.p_1()
|
||||
.children(self.children)
|
||||
div()
|
||||
.flex()
|
||||
.gap_1()
|
||||
.child(v_stack().elevation_2(cx).px_1().children(self.children))
|
||||
.when_some(self.aside, |this, aside| {
|
||||
// TODO: This will statically position the aside to the top right of the popover.
|
||||
// We should update this to use gpui2::overlay avoid collisions with the window edges.
|
||||
this.child(
|
||||
v_stack()
|
||||
.top_0()
|
||||
.left_full()
|
||||
.ml_1()
|
||||
.absolute()
|
||||
.elevation_2(cx)
|
||||
.p_1()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.px_1()
|
||||
.child(aside),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use gpui::SharedString;
|
||||
use gpui::{ImageSource, SharedString};
|
||||
|
||||
use crate::Icon;
|
||||
|
||||
@@ -9,6 +9,6 @@ use crate::Icon;
|
||||
/// Can be filled with a []
|
||||
pub enum GraphicSlot {
|
||||
Icon(Icon),
|
||||
Avatar(SharedString),
|
||||
Avatar(ImageSource),
|
||||
PublicActor(SharedString),
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ mod button;
|
||||
mod checkbox;
|
||||
mod context_menu;
|
||||
mod icon;
|
||||
mod icon_button;
|
||||
mod input;
|
||||
mod keybinding;
|
||||
mod label;
|
||||
@@ -13,6 +14,7 @@ pub use button::*;
|
||||
pub use checkbox::*;
|
||||
pub use context_menu::*;
|
||||
pub use icon::*;
|
||||
pub use icon_button::*;
|
||||
pub use input::*;
|
||||
pub use keybinding::*;
|
||||
pub use label::*;
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct AvatarStory;
|
||||
impl Render for AvatarStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<Avatar>())
|
||||
.child(Story::label("Default"))
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct ButtonStory;
|
||||
impl Render for ButtonStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let states = InteractionState::iter();
|
||||
|
||||
Story::container()
|
||||
@@ -139,7 +139,7 @@ impl Render for ButtonStory {
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(|_, cx| println!("Button clicked.")),
|
||||
.on_click(|_, _cx| println!("Button clicked.")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ fn build_menu(cx: &mut WindowContext, header: impl Into<SharedString>) -> View<C
|
||||
ContextMenu::build(cx, |menu, _| {
|
||||
menu.header(header)
|
||||
.separator()
|
||||
.entry("Print current time", |v, cx| {
|
||||
.entry("Print current time", |_event, cx| {
|
||||
println!("dispatching PrintCurrentTime action");
|
||||
cx.dispatch_action(PrintCurrentDate.boxed_clone())
|
||||
})
|
||||
.entry("Print best foot", |v, cx| {
|
||||
.entry("Print best foot", |_event, cx| {
|
||||
cx.dispatch_action(PrintBestFood.boxed_clone())
|
||||
})
|
||||
})
|
||||
@@ -25,7 +25,7 @@ pub struct ContextMenuStory;
|
||||
impl Render for ContextMenuStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.on_action(|_: &PrintCurrentDate, _| {
|
||||
println!("printing unix time!");
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct IconStory;
|
||||
impl Render for IconStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let icons = Icon::iter();
|
||||
|
||||
Story::container()
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use gpui::{Div, Render};
|
||||
use story::Story;
|
||||
|
||||
use crate::{prelude::*, Tooltip};
|
||||
use crate::{Icon, IconButton};
|
||||
|
||||
pub struct IconButtonStory;
|
||||
|
||||
impl Render for IconButtonStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<IconButton>())
|
||||
.child(Story::label("Default"))
|
||||
.child(div().w_8().child(IconButton::new("icon_a", Icon::Hash)))
|
||||
.child(Story::label("With `on_click`"))
|
||||
.child(
|
||||
div()
|
||||
.w_8()
|
||||
.child(
|
||||
IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| {
|
||||
println!("Clicked!");
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(Story::label("With `tooltip`"))
|
||||
.child(
|
||||
div().w_8().child(
|
||||
IconButton::new("with_tooltip", Icon::MessageBubbles)
|
||||
.tooltip(|cx| Tooltip::text("Open messages", cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ pub struct InputStory;
|
||||
impl Render for InputStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<Input>())
|
||||
.child(Story::label("Default"))
|
||||
|
||||
@@ -16,7 +16,7 @@ pub fn binding(key: &str) -> gpui::KeyBinding {
|
||||
impl Render for KeybindingStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
|
||||
|
||||
Story::container()
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct LabelStory;
|
||||
impl Render for LabelStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<Label>())
|
||||
.child(Story::label("Default"))
|
||||
|
||||
@@ -9,7 +9,7 @@ pub struct ListItemStory;
|
||||
impl Render for ListItemStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<ListItem>())
|
||||
.child(Story::label("Default"))
|
||||
@@ -22,5 +22,13 @@ impl Render for ListItemStory {
|
||||
println!("Clicked!");
|
||||
}),
|
||||
)
|
||||
.child(Story::label("With `on_secondary_mouse_down`"))
|
||||
.child(
|
||||
ListItem::new("with_on_secondary_mouse_down").on_secondary_mouse_down(
|
||||
|_event, _cx| {
|
||||
println!("Right mouse down!");
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use gpui::{overlay, Action, AnyView, IntoElement, Overlay, Render, VisualContext};
|
||||
use settings2::Settings;
|
||||
use theme2::{ActiveTheme, ThemeSettings};
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt};
|
||||
@@ -13,7 +13,7 @@ pub struct Tooltip {
|
||||
|
||||
impl Tooltip {
|
||||
pub fn text(title: impl Into<SharedString>, cx: &mut WindowContext) -> AnyView {
|
||||
cx.build_view(|cx| Self {
|
||||
cx.build_view(|_cx| Self {
|
||||
title: title.into(),
|
||||
meta: None,
|
||||
key_binding: None,
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use gpui::{
|
||||
|
||||
pub use crate::StyledExt;
|
||||
pub use crate::{ButtonVariant, Color};
|
||||
pub use theme2::ActiveTheme;
|
||||
pub use theme::ActiveTheme;
|
||||
|
||||
use strum::EnumIter;
|
||||
|
||||
@@ -16,12 +16,6 @@ pub enum IconSide {
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumIter)]
|
||||
pub enum OverflowStyle {
|
||||
Hidden,
|
||||
Wrap,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
|
||||
pub enum InteractionState {
|
||||
/// An element that is enabled and not hovered, active, focused, or disabled.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use gpui::{Styled, WindowContext};
|
||||
use theme2::ActiveTheme;
|
||||
use gpui::{px, Styled, WindowContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{ElevationIndex, UITextSize};
|
||||
|
||||
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
||||
this.bg(cx.theme().colors().elevated_surface_background)
|
||||
.z_index(index.z_index())
|
||||
.rounded_lg()
|
||||
.rounded(px(8.))
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.shadow(index.shadow())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use gpui::{Hsla, WindowContext};
|
||||
use theme2::ActiveTheme;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
pub enum Color {
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
#![doc = include_str!("../docs/hello-world.md")]
|
||||
#![doc = include_str!("../docs/building-ui.md")]
|
||||
#![doc = include_str!("../docs/todo.md")]
|
||||
// TODO: Fix warnings instead of supressing.
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
mod components;
|
||||
pub mod prelude;
|
||||
|
||||
@@ -16,55 +16,54 @@ fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
|
||||
fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
|
||||
let suffix = if distance < 0 { " from now" } else { " ago" };
|
||||
|
||||
let d = distance.abs();
|
||||
let distance = distance.abs();
|
||||
|
||||
let minutes = d / 60;
|
||||
let hours = d / 3600;
|
||||
let days = d / 86400;
|
||||
let months = d / 2592000;
|
||||
let years = d / 31536000;
|
||||
let minutes = distance / 60;
|
||||
let hours = distance / 3_600;
|
||||
let days = distance / 86_400;
|
||||
let months = distance / 2_592_000;
|
||||
|
||||
let string = if d < 5 && include_seconds {
|
||||
let string = if distance < 5 && include_seconds {
|
||||
"less than 5 seconds".to_string()
|
||||
} else if d < 10 && include_seconds {
|
||||
} else if distance < 10 && include_seconds {
|
||||
"less than 10 seconds".to_string()
|
||||
} else if d < 20 && include_seconds {
|
||||
} else if distance < 20 && include_seconds {
|
||||
"less than 20 seconds".to_string()
|
||||
} else if d < 40 && include_seconds {
|
||||
} else if distance < 40 && include_seconds {
|
||||
"half a minute".to_string()
|
||||
} else if d < 60 && include_seconds {
|
||||
} else if distance < 60 && include_seconds {
|
||||
"less than a minute".to_string()
|
||||
} else if d < 90 && include_seconds {
|
||||
} else if distance < 90 && include_seconds {
|
||||
"1 minute".to_string()
|
||||
} else if d < 30 {
|
||||
} else if distance < 30 {
|
||||
"less than a minute".to_string()
|
||||
} else if d < 90 {
|
||||
} else if distance < 90 {
|
||||
"1 minute".to_string()
|
||||
} else if d < 2700 {
|
||||
} else if distance < 2_700 {
|
||||
format!("{} minutes", minutes)
|
||||
} else if d < 5400 {
|
||||
} else if distance < 5_400 {
|
||||
"about 1 hour".to_string()
|
||||
} else if d < 86400 {
|
||||
} else if distance < 86_400 {
|
||||
format!("about {} hours", hours)
|
||||
} else if d < 172800 {
|
||||
} else if distance < 172_800 {
|
||||
"1 day".to_string()
|
||||
} else if d < 2592000 {
|
||||
} else if distance < 2_592_000 {
|
||||
format!("{} days", days)
|
||||
} else if d < 5184000 {
|
||||
} else if distance < 5_184_000 {
|
||||
"about 1 month".to_string()
|
||||
} else if d < 7776000 {
|
||||
} else if distance < 7_776_000 {
|
||||
"about 2 months".to_string()
|
||||
} else if d < 31540000 {
|
||||
} else if distance < 31_540_000 {
|
||||
format!("{} months", months)
|
||||
} else if d < 39425000 {
|
||||
} else if distance < 39_425_000 {
|
||||
"about 1 year".to_string()
|
||||
} else if d < 55195000 {
|
||||
} else if distance < 55_195_000 {
|
||||
"over 1 year".to_string()
|
||||
} else if d < 63080000 {
|
||||
} else if distance < 63_080_000 {
|
||||
"almost 2 years".to_string()
|
||||
} else {
|
||||
let years = d / 31536000;
|
||||
let remaining_months = (d % 31536000) / 2592000;
|
||||
let years = distance / 31_536_000;
|
||||
let remaining_months = (distance % 31_536_000) / 2_592_000;
|
||||
|
||||
if remaining_months < 3 {
|
||||
format!("about {} years", years)
|
||||
@@ -76,7 +75,7 @@ fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> St
|
||||
};
|
||||
|
||||
if add_suffix {
|
||||
return format!("{}{}", string, suffix);
|
||||
format!("{}{}", string, suffix)
|
||||
} else {
|
||||
string
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ editor = { package = "editor2", path = "../editor2" }
|
||||
fs = { package = "fs2", path = "../fs2" }
|
||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
ui = { package = "ui2", path = "../ui2" }
|
||||
db = { package = "db2", path = "../db2" }
|
||||
install_cli = { package = "install_cli2", path = "../install_cli2" }
|
||||
project = { package = "project2", path = "../project2" }
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use super::base_keymap_setting::BaseKeymap;
|
||||
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, AppContext, DismissEvent, EventEmitter, FocusableView, IntoElement, Render, Task,
|
||||
actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
|
||||
View, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use picker::{simple_picker_match, Picker, PickerDelegate};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::Fs;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::sync::Arc;
|
||||
use ui::ListItem;
|
||||
use util::ResultExt;
|
||||
use workspace::{ui::HighlightedLabel, Workspace};
|
||||
|
||||
@@ -97,6 +98,8 @@ impl BaseKeymapSelectorDelegate {
|
||||
}
|
||||
|
||||
impl PickerDelegate for BaseKeymapSelectorDelegate {
|
||||
type ListItem = ui::ListItem;
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Select a base keymap...".into()
|
||||
}
|
||||
@@ -188,13 +191,18 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||
) -> gpui::AnyElement {
|
||||
_cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let keymap_match = &self.matches[ix];
|
||||
|
||||
simple_picker_match(selected, cx, |_cx| {
|
||||
HighlightedLabel::new(keymap_match.string.clone(), keymap_match.positions.clone())
|
||||
.into_any_element()
|
||||
})
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.selected(selected)
|
||||
.inset(true)
|
||||
.child(HighlightedLabel::new(
|
||||
keymap_match.string.clone(),
|
||||
keymap_match.positions.clone(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +95,6 @@ impl Render for ModalLayer {
|
||||
.track_focus(&active_modal.focus_handle)
|
||||
.child(
|
||||
h_stack()
|
||||
// needed to prevent mouse events leaking to the
|
||||
// UI below. // todo! for gpui3.
|
||||
.on_any_mouse_down(|_, cx| cx.stop_propagation())
|
||||
.on_any_mouse_up(|_, cx| cx.stop_propagation())
|
||||
.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||
this.hide_modal(cx);
|
||||
}))
|
||||
|
||||
@@ -2640,12 +2640,11 @@ impl Workspace {
|
||||
.flex_col()
|
||||
.justify_end()
|
||||
.gap_2()
|
||||
.children(self.notifications.iter().map(|(_, _, notification)| {
|
||||
div()
|
||||
.on_any_mouse_down(|_, cx| cx.stop_propagation())
|
||||
.on_any_mouse_up(|_, cx| cx.stop_propagation())
|
||||
.child(notification.to_any())
|
||||
})),
|
||||
.children(
|
||||
self.notifications
|
||||
.iter()
|
||||
.map(|(_, _, notification)| notification.to_any()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ tree-sitter-lua.workspace = true
|
||||
tree-sitter-nix.workspace = true
|
||||
tree-sitter-nu.workspace = true
|
||||
tree-sitter-vue.workspace = true
|
||||
tree-sitter-uiua.workspace = true
|
||||
|
||||
url = "2.2"
|
||||
urlencoding = "2.1.2"
|
||||
|
||||
@@ -17,6 +17,7 @@ mod json;
|
||||
#[cfg(feature = "plugin_runtime")]
|
||||
mod language_plugin;
|
||||
mod lua;
|
||||
mod nu;
|
||||
mod php;
|
||||
mod python;
|
||||
mod ruby;
|
||||
@@ -24,6 +25,7 @@ mod rust;
|
||||
mod svelte;
|
||||
mod tailwind;
|
||||
mod typescript;
|
||||
mod uiua;
|
||||
mod vue;
|
||||
mod yaml;
|
||||
|
||||
@@ -210,12 +212,21 @@ pub fn init(
|
||||
language("elm", tree_sitter_elm::language(), vec![]);
|
||||
language("glsl", tree_sitter_glsl::language(), vec![]);
|
||||
language("nix", tree_sitter_nix::language(), vec![]);
|
||||
language("nu", tree_sitter_nu::language(), vec![]);
|
||||
language(
|
||||
"nu",
|
||||
tree_sitter_nu::language(),
|
||||
vec![Arc::new(nu::NuLanguageServer {})],
|
||||
);
|
||||
language(
|
||||
"vue",
|
||||
tree_sitter_vue::language(),
|
||||
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
|
||||
);
|
||||
language(
|
||||
"uiua",
|
||||
tree_sitter_uiua::language(),
|
||||
vec![Arc::new(uiua::UiuaLanguageServer {})],
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{CodeLabel, Language, LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
|
||||
pub struct NuLanguageServer;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for NuLanguageServer {
|
||||
async fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("nu".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"nu"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(()))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_version: Box<dyn 'static + Send + Any>,
|
||||
_container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
Err(anyhow!(
|
||||
"nu v0.87.0 or greater must be installed and available in your $PATH"
|
||||
))
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "nu".into(),
|
||||
arguments: vec!["--lsp".into()],
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
return Some(CodeLabel {
|
||||
runs: language
|
||||
.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
|
||||
text: completion.label.clone(),
|
||||
filter_range: 0..completion.label.len(),
|
||||
});
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
name: &str,
|
||||
_: lsp::SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&name.into(), 0..name.len()),
|
||||
text: name.to_string(),
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
pub struct UiuaLanguageServer;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for UiuaLanguageServer {
|
||||
async fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("uiua".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"uiua"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(()))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_version: Box<dyn 'static + Send + Any>,
|
||||
_container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
Err(anyhow!(
|
||||
"uiua must be installed and available in your $PATH"
|
||||
))
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "uiua".into(),
|
||||
arguments: vec!["lsp".into()],
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
name = "Uiua"
|
||||
path_suffixes = ["ua"]
|
||||
line_comment = "# "
|
||||
autoclose_before = ")]}\""
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = false },
|
||||
{ start = "[", end = "]", close = true, newline = false },
|
||||
{ start = "(", end = ")", close = true, newline = false },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
(openParen)
|
||||
(closeParen)
|
||||
(openCurly)
|
||||
(closeCurly)
|
||||
(openBracket)
|
||||
(closeBracket)
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
(branchSeparator)
|
||||
(underscore)
|
||||
] @constructor
|
||||
; ] @punctuation.delimiter
|
||||
|
||||
[ (character) ] @constant.character
|
||||
[ (comment) ] @comment
|
||||
[ (constant) ] @constant.numeric
|
||||
[ (identifier) ] @variable
|
||||
[ (leftArrow) ] @keyword
|
||||
[ (function) ] @function
|
||||
[ (modifier1) ] @operator
|
||||
[ (modifier2) ] @operator
|
||||
[ (number) ] @constant.numeric
|
||||
[ (placeHolder) ] @special
|
||||
[ (otherConstant) ] @string.special
|
||||
[ (signature) ] @type
|
||||
[ (system) ] @function.builtin
|
||||
[ (tripleMinus) ] @module
|
||||
|
||||
; planet
|
||||
[
|
||||
"id"
|
||||
"identity"
|
||||
"∘"
|
||||
"dip"
|
||||
"⊙"
|
||||
"gap"
|
||||
"⋅"
|
||||
] @tag
|
||||
|
||||
[
|
||||
(string)
|
||||
(multiLineString)
|
||||
] @string
|
||||
|
||||
; [
|
||||
; (deprecated)
|
||||
; (identifierDeprecated)
|
||||
; ] @warning
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
(array)
|
||||
] @indent
|
||||
@@ -21,7 +21,7 @@ audio = { package = "audio2", path = "../audio2" }
|
||||
auto_update = { package = "auto_update2", path = "../auto_update2" }
|
||||
# breadcrumbs = { path = "../breadcrumbs" }
|
||||
call = { package = "call2", path = "../call2" }
|
||||
# channel = { path = "../channel" }
|
||||
channel = { package = "channel2", path = "../channel2" }
|
||||
cli = { path = "../cli" }
|
||||
collab_ui = { package = "collab_ui2", path = "../collab_ui2" }
|
||||
collections = { path = "../collections" }
|
||||
@@ -136,6 +136,7 @@ tree-sitter-lua.workspace = true
|
||||
tree-sitter-nix.workspace = true
|
||||
tree-sitter-nu.workspace = true
|
||||
tree-sitter-vue.workspace = true
|
||||
tree-sitter-uiua.workspace = true
|
||||
|
||||
url = "2.2"
|
||||
urlencoding = "2.1.2"
|
||||
|
||||
@@ -18,6 +18,7 @@ mod json;
|
||||
#[cfg(feature = "plugin_runtime")]
|
||||
mod language_plugin;
|
||||
mod lua;
|
||||
mod nu;
|
||||
mod php;
|
||||
mod python;
|
||||
mod ruby;
|
||||
@@ -25,6 +26,7 @@ mod rust;
|
||||
mod svelte;
|
||||
mod tailwind;
|
||||
mod typescript;
|
||||
mod uiua;
|
||||
mod vue;
|
||||
mod yaml;
|
||||
|
||||
@@ -211,12 +213,21 @@ pub fn init(
|
||||
language("elm", tree_sitter_elm::language(), vec![]);
|
||||
language("glsl", tree_sitter_glsl::language(), vec![]);
|
||||
language("nix", tree_sitter_nix::language(), vec![]);
|
||||
language("nu", tree_sitter_nu::language(), vec![]);
|
||||
language(
|
||||
"nu",
|
||||
tree_sitter_nu::language(),
|
||||
vec![Arc::new(nu::NuLanguageServer {})],
|
||||
);
|
||||
language(
|
||||
"vue",
|
||||
tree_sitter_vue::language(),
|
||||
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
|
||||
);
|
||||
language(
|
||||
"uiua",
|
||||
tree_sitter_uiua::language(),
|
||||
vec![Arc::new(uiua::UiuaLanguageServer {})],
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
pub struct NuLanguageServer;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for NuLanguageServer {
|
||||
async fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("nu".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"nu"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(()))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_version: Box<dyn 'static + Send + Any>,
|
||||
_container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
Err(anyhow!(
|
||||
"nu v0.87.0 or greater must be installed and available in your $PATH"
|
||||
))
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "nu".into(),
|
||||
arguments: vec!["--lsp".into()],
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
pub struct UiuaLanguageServer;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for UiuaLanguageServer {
|
||||
async fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("uiua".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"uiua"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(()))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_version: Box<dyn 'static + Send + Any>,
|
||||
_container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
Err(anyhow!(
|
||||
"uiua must be installed and available in your $PATH"
|
||||
))
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "uiua".into(),
|
||||
arguments: vec!["lsp".into()],
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
name = "Uiua"
|
||||
path_suffixes = ["ua"]
|
||||
line_comment = "# "
|
||||
autoclose_before = ")]}\""
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = false},
|
||||
{ start = "[", end = "]", close = true, newline = false },
|
||||
{ start = "(", end = ")", close = true, newline = false },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
(openParen)
|
||||
(closeParen)
|
||||
(openCurly)
|
||||
(closeCurly)
|
||||
(openBracket)
|
||||
(closeBracket)
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
(branchSeparator)
|
||||
(underscore)
|
||||
] @constructor
|
||||
; ] @punctuation.delimiter
|
||||
|
||||
[ (character) ] @constant.character
|
||||
[ (comment) ] @comment
|
||||
[ (constant) ] @constant.numeric
|
||||
[ (identifier) ] @variable
|
||||
[ (leftArrow) ] @keyword
|
||||
[ (function) ] @function
|
||||
[ (modifier1) ] @operator
|
||||
[ (modifier2) ] @operator
|
||||
[ (number) ] @constant.numeric
|
||||
[ (placeHolder) ] @special
|
||||
[ (otherConstant) ] @string.special
|
||||
[ (signature) ] @type
|
||||
[ (system) ] @function.builtin
|
||||
[ (tripleMinus) ] @module
|
||||
|
||||
; planet
|
||||
[
|
||||
"id"
|
||||
"identity"
|
||||
"∘"
|
||||
"dip"
|
||||
"⊙"
|
||||
"gap"
|
||||
"⋅"
|
||||
] @tag
|
||||
|
||||
[
|
||||
(string)
|
||||
(multiLineString)
|
||||
] @string
|
||||
|
||||
; [
|
||||
; (deprecated)
|
||||
; (identifierDeprecated)
|
||||
; ] @warning
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
(array)
|
||||
] @indent
|
||||
@@ -188,7 +188,7 @@ fn main() {
|
||||
let app_state = Arc::new(AppState {
|
||||
languages,
|
||||
client: client.clone(),
|
||||
user_store,
|
||||
user_store: user_store.clone(),
|
||||
fs,
|
||||
build_window_options,
|
||||
call_factory: call::Call::new,
|
||||
@@ -208,7 +208,7 @@ fn main() {
|
||||
// outline::init(cx);
|
||||
// project_symbols::init(cx);
|
||||
project_panel::init(Assets, cx);
|
||||
// channel::init(&client, user_store.clone(), cx);
|
||||
channel::init(&client, user_store.clone(), cx);
|
||||
// diagnostics::init(cx);
|
||||
search::init(cx);
|
||||
// semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
|
||||
|
||||
Reference in New Issue
Block a user