Merge branch 'main' into welcome2

This commit is contained in:
Mikayla 2023-11-28 15:38:51 -08:00
commit a41c857855
No known key found for this signature in database
88 changed files with 2768 additions and 2094 deletions

14
Cargo.lock generated
View File

@ -9487,6 +9487,7 @@ dependencies = [
"settings2",
"smol",
"theme2",
"ui2",
"util",
"workspace2",
]
@ -10198,6 +10199,15 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-uiua"
version = "0.3.3"
source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2#9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-vue"
version = "0.0.1"
@ -11085,6 +11095,7 @@ dependencies = [
"settings2",
"theme2",
"theme_selector2",
"ui2",
"util",
"workspace2",
]
@ -11660,6 +11671,7 @@ dependencies = [
"tree-sitter-svelte",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue",
"tree-sitter-yaml",
"unindent",
@ -11695,6 +11707,7 @@ dependencies = [
"auto_update2",
"backtrace",
"call2",
"channel2",
"chrono",
"cli",
"client2",
@ -11783,6 +11796,7 @@ dependencies = [
"tree-sitter-svelte",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue",
"tree-sitter-yaml",
"unindent",

View File

@ -197,6 +197,7 @@ tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"}
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00001 12L3.5 7.50001M8.00001 12L12.5 7.50001M8.00001 12L8.00001 3.00001" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 272 B

View File

@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.125 6.99344L6.35938 3.63281M3.125 6.99344L6.35938 10.3672M3.125 6.99344H11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 7.50001L8 3M3.5 7.50001L8 12M3.5 7.50001H12.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 248 B

View File

@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8906 7.00125L7.64062 3.64062M10.8906 7.00125L7.64062 10.375M10.8906 7.00125H3" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 7.5L8 12M12.5 7.5L8 3M12.5 7.5L3.5 7.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 242 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99999 3.00001L12.5 7.50001M7.99999 3.00001L3.49999 7.50001M7.99999 3.00001L7.99999 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 286 B

3
assets/icons/command.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 3.625C11 2.86561 11.6156 2.25 12.375 2.25C13.1344 2.25 13.75 2.86561 13.75 3.625C13.75 4.38401 13.135 4.99939 12.3761 5C12.3758 5 12.3754 5 12.375 5H11V3.625ZM9.75 5V3.625C9.75 2.17525 10.9253 1 12.375 1C13.8247 1 15 2.17525 15 3.625C15 4.98872 13.9601 6.10955 12.63 6.23777V6.25H12.3766C12.376 6.25 12.3755 6.25 12.375 6.25H11V9.75H12.375C13.8247 9.75 15 10.9253 15 12.375C15 13.8247 13.8247 15 12.375 15C11.0113 15 9.89045 13.9601 9.76223 12.63H9.75V12.3773L9.75 12.375V11H6.25V12.375C6.25 13.8247 5.07475 15 3.625 15C2.17525 15 1 13.8247 1 12.375C1 11.0113 2.03991 9.89045 3.37 9.76223V9.75H3.62274L3.625 9.75H5L5 6.25H3.625C2.17525 6.25 1 5.07475 1 3.625C1 2.17525 2.17525 1 3.625 1C4.98872 1 6.10955 2.03991 6.23777 3.37H6.25L6.25 5L9.75 5ZM9.75 6.25L6.25 6.25L6.25 9.75H9.75V6.25ZM3.625 11H5V12.375C5 13.1344 4.38439 13.75 3.625 13.75C2.86561 13.75 2.25 13.1344 2.25 12.375C2.25 11.6162 2.86472 11.0009 3.62336 11L3.625 11ZM11 12.3766C11.0009 13.1353 11.6162 13.75 12.375 13.75C13.1344 13.75 13.75 13.1344 13.75 12.375C13.75 11.6156 13.1344 11 12.375 11H11V12.375L11 12.3766ZM3.625 5C2.86561 5 2.25 4.38439 2.25 3.625C2.25 2.86561 2.86561 2.25 3.625 2.25C4.38439 2.25 5 2.86561 5 3.625V5H3.625Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

3
assets/icons/control.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 6.12488L7.64656 1.97853C7.84183 1.78328 8.1584 1.78329 8.35366 1.97854L12.5 6.12488" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 262 B

3
assets/icons/option.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.35606 1.005H1.62545C1.28002 1.005 1 1.28502 1 1.63044C1 1.97587 1.28002 2.25589 1.62545 2.25589L5.35606 2.25589C5.62311 2.25589 5.8607 2.42545 5.94752 2.67799L9.75029 13.7387C10.0108 14.4963 10.7235 15.005 11.5247 15.005H14.3746C14.72 15.005 15 14.725 15 14.3796C15 14.0341 14.72 13.7541 14.3746 13.7541H11.5247C11.2576 13.7541 11.02 13.5845 10.9332 13.332L7.13046 2.27128C6.86998 1.51366 6.15721 1.005 5.35606 1.005ZM14.3745 1.005H9.75125C9.40582 1.005 9.1258 1.28502 9.1258 1.63044C9.1258 1.97587 9.40582 2.25589 9.75125 2.25589L14.3745 2.25589C14.72 2.25589 15 1.97587 15 1.63044C15 1.28502 14.72 1.005 14.3745 1.005Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 792 B

3
assets/icons/return.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.375 1.63C8.375 1.28482 8.65482 1.005 9 1.005H12.375C13.8247 1.005 15 2.18025 15 3.63V7.625C15 9.07474 13.8247 10.25 12.375 10.25H3.13388L6.07194 13.1881C6.31602 13.4321 6.31602 13.8279 6.07194 14.0719C5.82786 14.316 5.43214 14.316 5.18806 14.0719L1.18306 10.0669C0.938981 9.82286 0.938981 9.42714 1.18306 9.18306L5.18306 5.18306C5.42714 4.93898 5.82286 4.93898 6.06694 5.18306C6.31102 5.42714 6.31102 5.82286 6.06694 6.06694L3.13388 9H12.375C13.1344 9 13.75 8.38439 13.75 7.625V3.63C13.75 2.87061 13.1344 2.255 12.375 2.255H9C8.65482 2.255 8.375 1.97518 8.375 1.63Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 737 B

3
assets/icons/shift.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.46475 7.99652L7.85304 2.15921C7.93223 2.07342 8.06777 2.07341 8.14696 2.15921L13.5352 7.99652C13.7126 8.18869 13.5763 8.5 13.3148 8.5H10.5V13.7C10.5 13.8657 10.3657 14 10.2 14H5.8C5.63431 14 5.5 13.8657 5.5 13.7V8.5H2.6852C2.42367 8.5 2.28737 8.18869 2.46475 7.99652Z" stroke="black" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
})
})
});

View File

@ -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"))?

File diff suppressed because it is too large Load Diff

View File

@ -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()
}
}

View File

@ -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();
}))
}
})

View File

@ -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 {

View File

@ -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)),
),
)
}
}

View File

@ -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| {

View File

@ -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);

View File

@ -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,

View File

@ -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()
),
)
}
}

View File

@ -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);
}

View File

@ -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.

View File

@ -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",
]);

View File

@ -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,

View File

@ -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,

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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();

View File

@ -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)
}
}

View File

@ -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}");
})
)

View File

@ -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()

View File

@ -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(),

View File

@ -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()

View File

@ -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(),

View File

@ -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),

View File

@ -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" }

View File

@ -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(),
)),
)
}
}

View File

@ -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]

View File

@ -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

View File

@ -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>;

View File

@ -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();

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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()))
}
}

View File

@ -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 }
}
}

View File

@ -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,
}

View File

@ -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(),

View File

@ -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),
)
})

View File

@ -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),
}

View File

@ -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::*;

View File

@ -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"))

View File

@ -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.")),
)
}
}

View File

@ -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!");

View File

@ -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()

View File

@ -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)),
),
)
}
}

View File

@ -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"))

View File

@ -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()

View File

@ -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"))

View File

@ -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!");
},
),
)
}
}

View File

@ -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,

View File

@ -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.

View File

@ -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())

View File

@ -1,5 +1,5 @@
use gpui::{Hsla, WindowContext};
use theme2::ActiveTheme;
use theme::ActiveTheme;
#[derive(Default, PartialEq, Copy, Clone)]
pub enum Color {

View File

@ -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;

View File

@ -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
}

View File

@ -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" }

View File

@ -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(),
)),
)
}
}

View File

@ -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);
}))

View File

@ -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()),
),
)
}
}

View File

@ -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"

View File

@ -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"))]

View File

@ -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(),
})
}
}

View File

@ -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
}
}

View File

@ -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"] },
]

View File

@ -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

View File

@ -0,0 +1,3 @@
[
(array)
] @indent

View File

@ -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"

View File

@ -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"))]

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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"] },
]

View File

@ -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

View File

@ -0,0 +1,3 @@
[
(array)
] @indent

View File

@ -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);