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