Merge branch 'main' into project_search2
This commit is contained in:
commit
ddbd5cf2cb
145 changed files with 16798 additions and 12900 deletions
3
.github/workflows/release_nightly.yml
vendored
3
.github/workflows/release_nightly.yml
vendored
|
@ -81,12 +81,11 @@ jobs:
|
||||||
- name: Limit target directory size
|
- name: Limit target directory size
|
||||||
run: script/clear-target-dir-if-larger-than 100
|
run: script/clear-target-dir-if-larger-than 100
|
||||||
|
|
||||||
- name: Set release channel to nightly, add nightly prefix to the final version
|
- name: Set release channel to nightly
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
version=$(git rev-parse --short HEAD)
|
version=$(git rev-parse --short HEAD)
|
||||||
echo "Publishing version: ${version} on release channel nightly"
|
echo "Publishing version: ${version} on release channel nightly"
|
||||||
sed -i '' "s/version = \"\(.*\)\"/version = \"\1-nightly\"/" crates/zed2/Cargo.toml
|
|
||||||
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
echo "nightly" > crates/zed/RELEASE_CHANNEL
|
||||||
|
|
||||||
- name: Generate license file
|
- name: Generate license file
|
||||||
|
|
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -312,6 +312,15 @@ version = "1.0.75"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
@ -1145,6 +1154,7 @@ dependencies = [
|
||||||
"gpui2",
|
"gpui2",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"language2",
|
"language2",
|
||||||
|
"outline2",
|
||||||
"project2",
|
"project2",
|
||||||
"search2",
|
"search2",
|
||||||
"settings2",
|
"settings2",
|
||||||
|
@ -1743,7 +1753,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.30.0"
|
version = "0.30.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
@ -3162,6 +3172,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fast-srgb8"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -6263,6 +6279,28 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2e2f34147767aa758aa649415b50a69eeb46a67f9dc7db8011eeb3d84b351dc"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"fast-srgb8",
|
||||||
|
"palette_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette_derive"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7db010ec5ff3d4385e4f133916faacd9dad0f6a09394c92d825b3aed310fa0a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.37",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-tokio-ipc"
|
name = "parity-tokio-ipc"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -9696,6 +9734,7 @@ dependencies = [
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"json_comments",
|
"json_comments",
|
||||||
"log",
|
"log",
|
||||||
|
"palette",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
|
@ -10464,7 +10503,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter-vue"
|
name = "tree-sitter-vue"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58#9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"
|
source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=6608d9d60c386f19d80af7d8132322fa11199c42#6608d9d60c386f19d80af7d8132322fa11199c42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
|
|
|
@ -205,7 +205,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
|
||||||
tree-sitter-lua = "0.0.14"
|
tree-sitter-lua = "0.0.14"
|
||||||
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
|
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-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-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42"}
|
||||||
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
|
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -17,5 +17,8 @@
|
||||||
"file_name": "rose-pine-dawn.json",
|
"file_name": "rose-pine-dawn.json",
|
||||||
"appearance": "light"
|
"appearance": "light"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"syntax": {
|
||||||
|
"function": ["entity.name"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,7 @@ use ui::h_stack;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
use workspace::{item::ItemHandle, StatusItemView, Workspace};
|
||||||
|
|
||||||
actions!(ShowErrorMessage);
|
actions!(activity_indicator, [ShowErrorMessage]);
|
||||||
|
|
||||||
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
const DOWNLOAD_ICON: &str = "icons/download.svg";
|
||||||
const WARNING_ICON: &str = "icons/warning.svg";
|
const WARNING_ICON: &str = "icons/warning.svg";
|
||||||
|
|
|
@ -12,23 +12,26 @@ use chrono::{DateTime, Local};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{actions, AppContext};
|
use gpui::{actions, AppContext, SharedString};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
|
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc};
|
||||||
use util::paths::CONVERSATIONS_DIR;
|
use util::paths::CONVERSATIONS_DIR;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
NewConversation,
|
assistant,
|
||||||
Assist,
|
[
|
||||||
Split,
|
NewConversation,
|
||||||
CycleMessageRole,
|
Assist,
|
||||||
QuoteSelection,
|
Split,
|
||||||
ToggleFocus,
|
CycleMessageRole,
|
||||||
ResetKey,
|
QuoteSelection,
|
||||||
InlineAssist,
|
ToggleFocus,
|
||||||
ToggleIncludeConversation,
|
ResetKey,
|
||||||
ToggleRetrieveContext,
|
InlineAssist,
|
||||||
|
ToggleIncludeConversation,
|
||||||
|
ToggleRetrieveContext,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
@ -47,7 +50,7 @@ struct MessageMetadata {
|
||||||
enum MessageStatus {
|
enum MessageStatus {
|
||||||
Pending,
|
Pending,
|
||||||
Done,
|
Done,
|
||||||
Error(Arc<str>),
|
Error(SharedString),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
|
@ -1628,8 +1628,9 @@ impl Conversation {
|
||||||
metadata.status = MessageStatus::Done;
|
metadata.status = MessageStatus::Done;
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
metadata.status =
|
metadata.status = MessageStatus::Error(SharedString::from(
|
||||||
MessageStatus::Error(error.to_string().trim().into());
|
error.to_string().trim().to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -2273,7 +2274,7 @@ impl ConversationEditor {
|
||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
.id("error")
|
.id("error")
|
||||||
.tooltip(move |cx| Tooltip::text(&error, cx))
|
.tooltip(move |cx| Tooltip::text(error.clone(), cx))
|
||||||
.child(IconElement::new(Icon::XCircle)),
|
.child(IconElement::new(Icon::XCircle)),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -26,10 +26,13 @@ const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
|
||||||
|
|
||||||
//todo!(remove CheckThatAutoUpdaterWorks)
|
//todo!(remove CheckThatAutoUpdaterWorks)
|
||||||
actions!(
|
actions!(
|
||||||
Check,
|
auto_update,
|
||||||
DismissErrorMessage,
|
[
|
||||||
ViewReleaseNotes,
|
Check,
|
||||||
CheckThatAutoUpdaterWorks
|
DismissErrorMessage,
|
||||||
|
ViewReleaseNotes,
|
||||||
|
CheckThatAutoUpdaterWorks
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -85,15 +88,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
||||||
AutoUpdateSetting::register(cx);
|
AutoUpdateSetting::register(cx);
|
||||||
|
|
||||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||||
workspace
|
workspace.register_action(|_, action: &Check, cx| check(action, cx));
|
||||||
.register_action(|_, action: &Check, cx| check(action, cx))
|
|
||||||
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
|
|
||||||
let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
|
|
||||||
cx.spawn(|_, _cx| async move {
|
|
||||||
prompt.await.ok();
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
});
|
|
||||||
|
|
||||||
// @nate - code to trigger update notification on launch
|
// @nate - code to trigger update notification on launch
|
||||||
// workspace.show_notification(0, _cx, |cx| {
|
// workspace.show_notification(0, _cx, |cx| {
|
||||||
|
@ -130,9 +125,15 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(_: &Check, cx: &mut AppContext) {
|
pub fn check(_: &Check, cx: &mut ViewContext<Workspace>) {
|
||||||
if let Some(updater) = AutoUpdater::get(cx) {
|
if let Some(updater) = AutoUpdater::get(cx) {
|
||||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||||
|
} else {
|
||||||
|
drop(cx.prompt(
|
||||||
|
gpui::PromptLevel::Info,
|
||||||
|
"Auto-updates disabled for non-bundled app.",
|
||||||
|
&["Ok"],
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ search = { package = "search2", path = "../search2" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
theme = { package = "theme2", path = "../theme2" }
|
theme = { package = "theme2", path = "../theme2" }
|
||||||
workspace = { package = "workspace2", path = "../workspace2" }
|
workspace = { package = "workspace2", path = "../workspace2" }
|
||||||
# outline = { path = "../outline" }
|
outline = { package = "outline2", path = "../outline2" }
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
|
||||||
ViewContext, WeakView,
|
ViewContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{prelude::*, ButtonLike, ButtonStyle, Label};
|
use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{ItemEvent, ItemHandle},
|
item::{ItemEvent, ItemHandle},
|
||||||
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
|
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
@ -18,16 +19,14 @@ pub struct Breadcrumbs {
|
||||||
pane_focused: bool,
|
pane_focused: bool,
|
||||||
active_item: Option<Box<dyn ItemHandle>>,
|
active_item: Option<Box<dyn ItemHandle>>,
|
||||||
subscription: Option<Subscription>,
|
subscription: Option<Subscription>,
|
||||||
_workspace: WeakView<Workspace>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Breadcrumbs {
|
impl Breadcrumbs {
|
||||||
pub fn new(workspace: &Workspace) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pane_focused: false,
|
pane_focused: false,
|
||||||
active_item: Default::default(),
|
active_item: Default::default(),
|
||||||
subscription: Default::default(),
|
subscription: Default::default(),
|
||||||
_workspace: workspace.weak_handle(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,97 +61,24 @@ impl Render for Breadcrumbs {
|
||||||
Label::new("›").into_any_element()
|
Label::new("›").into_any_element()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let editor = active_item
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.map(|editor| editor.downgrade());
|
||||||
|
|
||||||
element.child(
|
element.child(
|
||||||
ButtonLike::new("toggle outline view")
|
ButtonLike::new("toggle outline view")
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.child(h_stack().gap_1().children(breadcrumbs))
|
.child(h_stack().gap_1().children(breadcrumbs))
|
||||||
// We disable the button when it is not focused
|
.on_click(move |_, cx| {
|
||||||
// due to ... @julia what was the reason again?
|
if let Some(editor) = editor.as_ref().and_then(|editor| editor.upgrade()) {
|
||||||
.disabled(!self.pane_focused)
|
outline::toggle(editor, &outline::Toggle, cx)
|
||||||
.on_click(move |_, _cx| {
|
}
|
||||||
todo!("outline::toggle");
|
})
|
||||||
// this.update(cx, |this, cx| {
|
.tooltip(|cx| Tooltip::for_action("Show symbol outline", &outline::Toggle, cx)),
|
||||||
// if let Some(workspace) = this.workspace.upgrade() {
|
|
||||||
// workspace.update(cx, |_workspace, _cx| {
|
|
||||||
// outline::toggle(workspace, &Default::default(), cx)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .ok();
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl View for Breadcrumbs {
|
|
||||||
// fn ui_name() -> &'static str {
|
|
||||||
// "Breadcrumbs"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
|
||||||
// let active_item = match &self.active_item {
|
|
||||||
// Some(active_item) => active_item,
|
|
||||||
// None => return Empty::new().into_any(),
|
|
||||||
// };
|
|
||||||
// let not_editor = active_item.downcast::<editor::Editor>().is_none();
|
|
||||||
|
|
||||||
// let theme = theme::current(cx).clone();
|
|
||||||
// let style = &theme.workspace.toolbar.breadcrumbs;
|
|
||||||
|
|
||||||
// let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
|
|
||||||
// Some(breadcrumbs) => breadcrumbs,
|
|
||||||
// None => return Empty::new().into_any(),
|
|
||||||
// }
|
|
||||||
// .into_iter()
|
|
||||||
// .map(|breadcrumb| {
|
|
||||||
// Text::new(
|
|
||||||
// breadcrumb.text,
|
|
||||||
// theme.workspace.toolbar.breadcrumbs.default.text.clone(),
|
|
||||||
// )
|
|
||||||
// .with_highlights(breadcrumb.highlights.unwrap_or_default())
|
|
||||||
// .into_any()
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let crumbs = Flex::row()
|
|
||||||
// .with_children(Itertools::intersperse_with(breadcrumbs, || {
|
|
||||||
// Label::new(" › ", style.default.text.clone()).into_any()
|
|
||||||
// }))
|
|
||||||
// .constrained()
|
|
||||||
// .with_height(theme.workspace.toolbar.breadcrumb_height)
|
|
||||||
// .contained();
|
|
||||||
|
|
||||||
// if not_editor || !self.pane_focused {
|
|
||||||
// return crumbs
|
|
||||||
// .with_style(style.default.container)
|
|
||||||
// .aligned()
|
|
||||||
// .left()
|
|
||||||
// .into_any();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
|
|
||||||
// let style = style.style_for(state);
|
|
||||||
// crumbs.with_style(style.container)
|
|
||||||
// })
|
|
||||||
// .on_click(MouseButton::Left, |_, this, cx| {
|
|
||||||
// if let Some(workspace) = this.workspace.upgrade(cx) {
|
|
||||||
// workspace.update(cx, |workspace, cx| {
|
|
||||||
// outline::toggle(workspace, &Default::default(), cx)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .with_tooltip::<Breadcrumbs>(
|
|
||||||
// 0,
|
|
||||||
// "Show symbol outline".to_owned(),
|
|
||||||
// Some(Box::new(outline::Toggle)),
|
|
||||||
// theme.tooltip.clone(),
|
|
||||||
// cx,
|
|
||||||
// )
|
|
||||||
// .aligned()
|
|
||||||
// .left()
|
|
||||||
// .into_any()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl ToolbarItemView for Breadcrumbs {
|
impl ToolbarItemView for Breadcrumbs {
|
||||||
fn set_active_pane_item(
|
fn set_active_pane_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -8,7 +8,8 @@ use collections::{hash_map, HashMap, HashSet};
|
||||||
use db::RELEASE_CHANNEL;
|
use db::RELEASE_CHANNEL;
|
||||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
|
||||||
|
WeakModel,
|
||||||
};
|
};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, ChannelVisibility},
|
proto::{self, ChannelVisibility},
|
||||||
|
@ -46,7 +47,7 @@ pub struct ChannelStore {
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub id: ChannelId,
|
pub id: ChannelId,
|
||||||
pub name: String,
|
pub name: SharedString,
|
||||||
pub visibility: proto::ChannelVisibility,
|
pub visibility: proto::ChannelVisibility,
|
||||||
pub role: proto::ChannelRole,
|
pub role: proto::ChannelRole,
|
||||||
pub unseen_note_version: Option<(u64, clock::Global)>,
|
pub unseen_note_version: Option<(u64, clock::Global)>,
|
||||||
|
@ -895,14 +896,16 @@ impl ChannelStore {
|
||||||
.channel_invitations
|
.channel_invitations
|
||||||
.binary_search_by_key(&channel.id, |c| c.id)
|
.binary_search_by_key(&channel.id, |c| c.id)
|
||||||
{
|
{
|
||||||
Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
|
Ok(ix) => {
|
||||||
|
Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
|
||||||
|
}
|
||||||
Err(ix) => self.channel_invitations.insert(
|
Err(ix) => self.channel_invitations.insert(
|
||||||
ix,
|
ix,
|
||||||
Arc::new(Channel {
|
Arc::new(Channel {
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
visibility: channel.visibility(),
|
visibility: channel.visibility(),
|
||||||
role: channel.role(),
|
role: channel.role(),
|
||||||
name: channel.name,
|
name: channel.name.into(),
|
||||||
unseen_note_version: None,
|
unseen_note_version: None,
|
||||||
unseen_message_id: None,
|
unseen_message_id: None,
|
||||||
parent_path: channel.parent_path,
|
parent_path: channel.parent_path,
|
||||||
|
|
|
@ -104,7 +104,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
|
|
||||||
existing_channel.visibility = channel_proto.visibility();
|
existing_channel.visibility = channel_proto.visibility();
|
||||||
existing_channel.role = channel_proto.role();
|
existing_channel.role = channel_proto.role();
|
||||||
existing_channel.name = channel_proto.name;
|
existing_channel.name = channel_proto.name.into();
|
||||||
} else {
|
} else {
|
||||||
self.channels_by_id.insert(
|
self.channels_by_id.insert(
|
||||||
channel_proto.id,
|
channel_proto.id,
|
||||||
|
@ -112,7 +112,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
||||||
id: channel_proto.id,
|
id: channel_proto.id,
|
||||||
visibility: channel_proto.visibility(),
|
visibility: channel_proto.visibility(),
|
||||||
role: channel_proto.role(),
|
role: channel_proto.role(),
|
||||||
name: channel_proto.name,
|
name: channel_proto.name.into(),
|
||||||
unseen_note_version: None,
|
unseen_note_version: None,
|
||||||
unseen_message_id: None,
|
unseen_message_id: None,
|
||||||
parent_path: channel_proto.parent_path,
|
parent_path: channel_proto.parent_path,
|
||||||
|
@ -146,11 +146,11 @@ fn channel_path_sorting_key<'a>(
|
||||||
let (parent_path, name) = channels_by_id
|
let (parent_path, name) = channels_by_id
|
||||||
.get(&id)
|
.get(&id)
|
||||||
.map_or((&[] as &[_], None), |channel| {
|
.map_or((&[] as &[_], None), |channel| {
|
||||||
(channel.parent_path.as_slice(), Some(channel.name.as_str()))
|
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
|
||||||
});
|
});
|
||||||
parent_path
|
parent_path
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_str()))
|
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
||||||
.chain(name)
|
.chain(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
||||||
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
|
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
|
||||||
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
actions!(SignIn, SignOut, Reconnect);
|
actions!(client, [SignIn, SignOut, Reconnect]);
|
||||||
|
|
||||||
pub fn init_settings(cx: &mut AppContext) {
|
pub fn init_settings(cx: &mut AppContext) {
|
||||||
TelemetrySettings::register(cx);
|
TelemetrySettings::register(cx);
|
||||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||||
default-run = "collab"
|
default-run = "collab"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.30.0"
|
version = "0.30.1"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -116,12 +116,13 @@ struct CreateUserResponse {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Panic {
|
struct Panic {
|
||||||
version: String,
|
version: String,
|
||||||
|
release_channel: String,
|
||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(panic))]
|
#[instrument(skip(panic))]
|
||||||
async fn trace_panic(panic: Json<Panic>) -> Result<()> {
|
async fn trace_panic(panic: Json<Panic>) -> Result<()> {
|
||||||
tracing::error!(version = %panic.version, text = %panic.text, "panic report");
|
tracing::error!(version = %panic.version, release_channel = %panic.release_channel, text = %panic.text, "panic report");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use call::ActiveCall;
|
||||||
use channel::{ChannelId, ChannelMembership, ChannelStore};
|
use channel::{ChannelId, ChannelMembership, ChannelStore};
|
||||||
use client::User;
|
use client::User;
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use gpui::{BackgroundExecutor, Model, TestAppContext};
|
use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, ChannelRole},
|
proto::{self, ChannelRole},
|
||||||
RECEIVE_TIMEOUT,
|
RECEIVE_TIMEOUT,
|
||||||
|
@ -46,13 +46,13 @@ async fn test_core_channels(
|
||||||
&[
|
&[
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".to_string(),
|
name: "channel-b".into(),
|
||||||
depth: 1,
|
depth: 1,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
},
|
},
|
||||||
|
@ -92,7 +92,7 @@ async fn test_core_channels(
|
||||||
cx_b,
|
cx_b,
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
}],
|
}],
|
||||||
|
@ -140,13 +140,13 @@ async fn test_core_channels(
|
||||||
&[
|
&[
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".to_string(),
|
name: "channel-b".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
|
@ -168,19 +168,19 @@ async fn test_core_channels(
|
||||||
&[
|
&[
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".to_string(),
|
name: "channel-b".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
depth: 1,
|
depth: 1,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_c_id,
|
id: channel_c_id,
|
||||||
name: "channel-c".to_string(),
|
name: "channel-c".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
depth: 2,
|
depth: 2,
|
||||||
},
|
},
|
||||||
|
@ -211,19 +211,19 @@ async fn test_core_channels(
|
||||||
&[
|
&[
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_b_id,
|
id: channel_b_id,
|
||||||
name: "channel-b".to_string(),
|
name: "channel-b".into(),
|
||||||
depth: 1,
|
depth: 1,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
id: channel_c_id,
|
id: channel_c_id,
|
||||||
name: "channel-c".to_string(),
|
name: "channel-c".into(),
|
||||||
depth: 2,
|
depth: 2,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
},
|
},
|
||||||
|
@ -245,7 +245,7 @@ async fn test_core_channels(
|
||||||
cx_a,
|
cx_a,
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
}],
|
}],
|
||||||
|
@ -255,7 +255,7 @@ async fn test_core_channels(
|
||||||
cx_b,
|
cx_b,
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
}],
|
}],
|
||||||
|
@ -278,7 +278,7 @@ async fn test_core_channels(
|
||||||
cx_a,
|
cx_a,
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a".to_string(),
|
name: "channel-a".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
}],
|
}],
|
||||||
|
@ -309,7 +309,7 @@ async fn test_core_channels(
|
||||||
cx_a,
|
cx_a,
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
id: channel_a_id,
|
id: channel_a_id,
|
||||||
name: "channel-a-renamed".to_string(),
|
name: "channel-a-renamed".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
}],
|
}],
|
||||||
|
@ -418,7 +418,7 @@ async fn test_channel_room(
|
||||||
cx_b,
|
cx_b,
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
id: zed_id,
|
id: zed_id,
|
||||||
name: "zed".to_string(),
|
name: "zed".into(),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
}],
|
}],
|
||||||
|
@ -680,7 +680,7 @@ async fn test_permissions_update_while_invited(
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust".to_string(),
|
name: "rust".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
@ -708,7 +708,7 @@ async fn test_permissions_update_while_invited(
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust".to_string(),
|
name: "rust".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
@ -747,7 +747,7 @@ async fn test_channel_rename(
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust-archive".to_string(),
|
name: "rust-archive".into(),
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
@ -759,7 +759,7 @@ async fn test_channel_rename(
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: rust_id,
|
id: rust_id,
|
||||||
name: "rust-archive".to_string(),
|
name: "rust-archive".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
@ -888,7 +888,7 @@ async fn test_lost_channel_creation(
|
||||||
&[ExpectedChannel {
|
&[ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "x".to_string(),
|
name: "x".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
|
@ -912,13 +912,13 @@ async fn test_lost_channel_creation(
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "x".to_string(),
|
name: "x".into(),
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
id: subchannel_id,
|
id: subchannel_id,
|
||||||
name: "subchannel".to_string(),
|
name: "subchannel".into(),
|
||||||
role: ChannelRole::Admin,
|
role: ChannelRole::Admin,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -943,13 +943,13 @@ async fn test_lost_channel_creation(
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: channel_id,
|
id: channel_id,
|
||||||
name: "x".to_string(),
|
name: "x".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
id: subchannel_id,
|
id: subchannel_id,
|
||||||
name: "subchannel".to_string(),
|
name: "subchannel".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1221,13 +1221,13 @@ async fn test_channel_membership_notifications(
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: zed_channel,
|
id: zed_channel,
|
||||||
name: "zed".to_string(),
|
name: "zed".into(),
|
||||||
role: ChannelRole::Guest,
|
role: ChannelRole::Guest,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
id: vim_channel,
|
id: vim_channel,
|
||||||
name: "vim".to_string(),
|
name: "vim".into(),
|
||||||
role: ChannelRole::Member,
|
role: ChannelRole::Member,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1250,13 +1250,13 @@ async fn test_channel_membership_notifications(
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
id: zed_channel,
|
id: zed_channel,
|
||||||
name: "zed".to_string(),
|
name: "zed".into(),
|
||||||
role: ChannelRole::Guest,
|
role: ChannelRole::Guest,
|
||||||
},
|
},
|
||||||
ExpectedChannel {
|
ExpectedChannel {
|
||||||
depth: 1,
|
depth: 1,
|
||||||
id: vim_channel,
|
id: vim_channel,
|
||||||
name: "vim".to_string(),
|
name: "vim".into(),
|
||||||
role: ChannelRole::Guest,
|
role: ChannelRole::Guest,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1476,7 +1476,7 @@ async fn test_channel_moving(
|
||||||
struct ExpectedChannel {
|
struct ExpectedChannel {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
id: ChannelId,
|
id: ChannelId,
|
||||||
name: String,
|
name: SharedString,
|
||||||
role: ChannelRole,
|
role: ChannelRole,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1515,7 +1515,7 @@ fn assert_channels(
|
||||||
.ordered_channels()
|
.ordered_channels()
|
||||||
.map(|(depth, channel)| ExpectedChannel {
|
.map(|(depth, channel)| ExpectedChannel {
|
||||||
depth,
|
depth,
|
||||||
name: channel.name.clone(),
|
name: channel.name.clone().into(),
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
role: channel.role,
|
role: channel.role,
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::db::ChannelRole;
|
||||||
use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
|
use super::{run_randomized_test, RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use gpui::{BackgroundExecutor, TestAppContext};
|
use gpui::{BackgroundExecutor, SharedString, TestAppContext};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -30,13 +30,13 @@ struct RandomChannelBufferTest;
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
enum ChannelBufferOperation {
|
enum ChannelBufferOperation {
|
||||||
JoinChannelNotes {
|
JoinChannelNotes {
|
||||||
channel_name: String,
|
channel_name: SharedString,
|
||||||
},
|
},
|
||||||
LeaveChannelNotes {
|
LeaveChannelNotes {
|
||||||
channel_name: String,
|
channel_name: SharedString,
|
||||||
},
|
},
|
||||||
EditChannelNotes {
|
EditChannelNotes {
|
||||||
channel_name: String,
|
channel_name: SharedString,
|
||||||
edits: Vec<(Range<usize>, Arc<str>)>,
|
edits: Vec<(Range<usize>, Arc<str>)>,
|
||||||
},
|
},
|
||||||
Noop,
|
Noop,
|
||||||
|
|
|
@ -26,7 +26,7 @@ use workspace::{
|
||||||
ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
|
ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(Deploy);
|
actions!(collab, [Deploy]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
register_followable_item::<ChannelView>(cx)
|
register_followable_item::<ChannelView>(cx)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,13 +3,14 @@ use client::UserId;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{AnchorRangeExt, Editor};
|
use editor::{AnchorRangeExt, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::ChildView, AnyElement, AsyncAppContext, Element, Entity, ModelHandle, Task, View,
|
AnyView, AsyncWindowContext, FocusableView, Model, Render, SharedString, Task, View,
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
ViewContext, WeakView,
|
||||||
};
|
};
|
||||||
use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
|
use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use workspace::item::ItemHandle;
|
||||||
|
|
||||||
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
|
@ -19,8 +20,8 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
pub editor: ViewHandle<Editor>,
|
pub editor: View<Editor>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
users: HashMap<String, UserId>,
|
users: HashMap<String, UserId>,
|
||||||
mentions: Vec<UserId>,
|
mentions: Vec<UserId>,
|
||||||
mentions_task: Option<Task<()>>,
|
mentions_task: Option<Task<()>>,
|
||||||
|
@ -30,8 +31,8 @@ pub struct MessageEditor {
|
||||||
impl MessageEditor {
|
impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
editor: ViewHandle<Editor>,
|
editor: View<Editor>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
@ -48,15 +49,13 @@ impl MessageEditor {
|
||||||
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
||||||
|
|
||||||
let markdown = language_registry.language_for_name("Markdown");
|
let markdown = language_registry.language_for_name("Markdown");
|
||||||
cx.app_context()
|
cx.spawn(|_, mut cx| async move {
|
||||||
.spawn(|mut cx| async move {
|
let markdown = markdown.await?;
|
||||||
let markdown = markdown.await?;
|
buffer.update(&mut cx, |buffer, cx| {
|
||||||
buffer.update(&mut cx, |buffer, cx| {
|
buffer.set_language(Some(markdown), cx)
|
||||||
buffer.set_language(Some(markdown), cx)
|
|
||||||
});
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
editor,
|
editor,
|
||||||
|
@ -71,7 +70,7 @@ impl MessageEditor {
|
||||||
pub fn set_channel(
|
pub fn set_channel(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
channel_name: Option<String>,
|
channel_name: Option<SharedString>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
@ -132,26 +131,28 @@ impl MessageEditor {
|
||||||
|
|
||||||
fn on_buffer_event(
|
fn on_buffer_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: Model<Buffer>,
|
||||||
event: &language::Event,
|
event: &language::Event,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let language::Event::Reparsed | language::Event::Edited = event {
|
if let language::Event::Reparsed | language::Event::Edited = event {
|
||||||
let buffer = buffer.read(cx).snapshot();
|
let buffer = buffer.read(cx).snapshot();
|
||||||
self.mentions_task = Some(cx.spawn(|this, cx| async move {
|
self.mentions_task = Some(cx.spawn(|this, cx| async move {
|
||||||
cx.background().timer(MENTIONS_DEBOUNCE_INTERVAL).await;
|
cx.background_executor()
|
||||||
|
.timer(MENTIONS_DEBOUNCE_INTERVAL)
|
||||||
|
.await;
|
||||||
Self::find_mentions(this, buffer, cx).await;
|
Self::find_mentions(this, buffer, cx).await;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_mentions(
|
async fn find_mentions(
|
||||||
this: WeakViewHandle<MessageEditor>,
|
this: WeakView<MessageEditor>,
|
||||||
buffer: BufferSnapshot,
|
buffer: BufferSnapshot,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncWindowContext,
|
||||||
) {
|
) {
|
||||||
let (buffer, ranges) = cx
|
let (buffer, ranges) = cx
|
||||||
.background()
|
.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
|
let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
|
||||||
(buffer, ranges)
|
(buffer, ranges)
|
||||||
|
@ -180,11 +181,7 @@ impl MessageEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.clear_highlights::<Self>(cx);
|
editor.clear_highlights::<Self>(cx);
|
||||||
editor.highlight_text::<Self>(
|
editor.highlight_text::<Self>(anchor_ranges, gpui::red().into(), cx)
|
||||||
anchor_ranges,
|
|
||||||
theme::current(cx).chat_panel.rich_text.mention_highlight,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mentions = mentioned_user_ids;
|
this.mentions = mentioned_user_ids;
|
||||||
|
@ -192,21 +189,17 @@ impl MessageEditor {
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Entity for MessageEditor {
|
pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
type Event = ();
|
self.editor.read(cx).focus_handle(cx)
|
||||||
}
|
|
||||||
|
|
||||||
impl View for MessageEditor {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
|
|
||||||
ChildView::new(&self.editor, cx).into_any()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
|
impl Render for MessageEditor {
|
||||||
if cx.is_self_focused() {
|
type Element = AnyView;
|
||||||
cx.focus(&self.editor);
|
|
||||||
}
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
|
self.editor.to_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +207,7 @@ impl View for MessageEditor {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use client::{Client, User, UserStore};
|
use client::{Client, User, UserStore};
|
||||||
use gpui::{TestAppContext, WindowHandle};
|
use gpui::{Context as _, TestAppContext, VisualContext as _};
|
||||||
use language::{Language, LanguageConfig};
|
use language::{Language, LanguageConfig};
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
@ -222,8 +215,17 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_message_editor(cx: &mut TestAppContext) {
|
async fn test_message_editor(cx: &mut TestAppContext) {
|
||||||
let editor = init_test(cx);
|
let language_registry = init_test(cx);
|
||||||
let editor = editor.root(cx);
|
|
||||||
|
let (editor, cx) = cx.add_window_view(|cx| {
|
||||||
|
MessageEditor::new(
|
||||||
|
language_registry,
|
||||||
|
ChannelStore::global(cx),
|
||||||
|
cx.build_view(|cx| Editor::auto_height(4, cx)),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
cx.executor().run_until_parked();
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.set_members(
|
editor.set_members(
|
||||||
|
@ -255,7 +257,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.foreground().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
|
cx.executor().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
let (text, ranges) = marked_text_ranges("Hello, «@a-b»! Have you met «@C_D»?", false);
|
let (text, ranges) = marked_text_ranges("Hello, «@a-b»! Have you met «@C_D»?", false);
|
||||||
|
@ -269,15 +271,14 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_test(cx: &mut TestAppContext) -> WindowHandle<MessageEditor> {
|
fn init_test(cx: &mut TestAppContext) -> Arc<LanguageRegistry> {
|
||||||
cx.foreground().forbid_parking();
|
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let http = FakeHttpClient::with_404_response();
|
let http = FakeHttpClient::with_404_response();
|
||||||
let client = Client::new(http.clone(), cx);
|
let client = Client::new(http.clone(), cx);
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx));
|
||||||
cx.set_global(SettingsStore::test(cx));
|
let settings = SettingsStore::test(cx);
|
||||||
theme::init((), cx);
|
cx.set_global(settings);
|
||||||
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
client::init(&client, cx);
|
client::init(&client, cx);
|
||||||
|
@ -292,16 +293,6 @@ mod tests {
|
||||||
},
|
},
|
||||||
Some(tree_sitter_markdown::language()),
|
Some(tree_sitter_markdown::language()),
|
||||||
)));
|
)));
|
||||||
|
language_registry
|
||||||
let editor = cx.add_window(|cx| {
|
|
||||||
MessageEditor::new(
|
|
||||||
language_registry,
|
|
||||||
ChannelStore::global(cx),
|
|
||||||
cx.add_view(|cx| Editor::auto_height(4, None, cx)),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
cx.foreground().run_until_parked();
|
|
||||||
editor
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ use theme::{ActiveTheme, ThemeSettings};
|
||||||
// channel_id: ChannelId,
|
// channel_id: ChannelId,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[derive(Action, PartialEq, Debug, Clone, Serialize, Deserialize)]
|
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct OpenChannelNotes {
|
pub struct OpenChannelNotes {
|
||||||
pub channel_id: ChannelId,
|
pub channel_id: ChannelId,
|
||||||
}
|
}
|
||||||
|
@ -122,15 +122,20 @@ pub struct OpenChannelNotes {
|
||||||
// to: ChannelId,
|
// to: ChannelId,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
impl_actions!(collab_panel, [OpenChannelNotes]);
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
ToggleFocus,
|
collab_panel,
|
||||||
Remove,
|
[
|
||||||
Secondary,
|
ToggleFocus,
|
||||||
CollapseSelectedChannel,
|
Remove,
|
||||||
ExpandSelectedChannel,
|
Secondary,
|
||||||
StartMoveChannel,
|
CollapseSelectedChannel,
|
||||||
MoveSelected,
|
ExpandSelectedChannel,
|
||||||
InsertSpace,
|
StartMoveChannel,
|
||||||
|
MoveSelected,
|
||||||
|
InsertSpace,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// impl_actions!(
|
// impl_actions!(
|
||||||
|
@ -169,12 +174,12 @@ use editor::Editor;
|
||||||
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
|
||||||
use fuzzy::{match_strings, StringMatchCandidate};
|
use fuzzy::{match_strings, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, img, overlay, point, prelude::*, px, rems, serde_json, size, Action,
|
actions, canvas, div, img, impl_actions, overlay, point, prelude::*, px, rems, serde_json,
|
||||||
AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter,
|
size, Action, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div,
|
||||||
FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement, Length, Model,
|
EventEmitter, FocusHandle, Focusable, FocusableView, Hsla, InteractiveElement, IntoElement,
|
||||||
MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render, RenderOnce,
|
Length, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Quad, Render,
|
||||||
ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, View, ViewContext,
|
RenderOnce, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, View,
|
||||||
VisualContext, WeakView,
|
ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use project::{Fs, Project};
|
use project::{Fs, Project};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
@ -192,6 +197,7 @@ use workspace::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::channel_view::ChannelView;
|
use crate::channel_view::ChannelView;
|
||||||
|
use crate::chat_panel::ChatPanel;
|
||||||
use crate::{face_pile::FacePile, CollaborationPanelSettings};
|
use crate::{face_pile::FacePile, CollaborationPanelSettings};
|
||||||
|
|
||||||
use self::channel_modal::ChannelModal;
|
use self::channel_modal::ChannelModal;
|
||||||
|
@ -852,7 +858,7 @@ impl CollabPanel {
|
||||||
.extend(channel_store.ordered_channels().enumerate().map(
|
.extend(channel_store.ordered_channels().enumerate().map(
|
||||||
|(ix, (_, channel))| StringMatchCandidate {
|
|(ix, (_, channel))| StringMatchCandidate {
|
||||||
id: ix,
|
id: ix,
|
||||||
string: channel.name.clone(),
|
string: channel.name.clone().into(),
|
||||||
char_bag: channel.name.chars().collect(),
|
char_bag: channel.name.chars().collect(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -2102,14 +2108,13 @@ impl CollabPanel {
|
||||||
};
|
};
|
||||||
cx.window_context().defer(move |cx| {
|
cx.window_context().defer(move |cx| {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
todo!();
|
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
||||||
// if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
panel.update(cx, |panel, cx| {
|
||||||
// panel.update(cx, |panel, cx| {
|
panel
|
||||||
// panel
|
.select_channel(channel_id, None, cx)
|
||||||
// .select_channel(channel_id, None, cx)
|
.detach_and_log_err(cx);
|
||||||
// .detach_and_log_err(cx);
|
});
|
||||||
// });
|
}
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2262,7 +2267,7 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(channel.name.as_str())
|
Some(channel.name.as_ref())
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(name) = channel_name {
|
if let Some(name) = channel_name {
|
||||||
|
@ -2603,9 +2608,14 @@ impl CollabPanel {
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(move |this, _, cx| {
|
||||||
|
this.join_channel_chat(channel_id, cx)
|
||||||
|
}))
|
||||||
|
.tooltip(|cx| {
|
||||||
|
Tooltip::text("Open channel chat", cx)
|
||||||
}),
|
}),
|
||||||
)
|
),
|
||||||
.tooltip(|cx| Tooltip::text("Open channel chat", cx)),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
|
|
@ -13,12 +13,16 @@ use picker::{Picker, PickerDelegate};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
|
use workspace::ModalView;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
SelectNextControl,
|
channel_modal,
|
||||||
ToggleMode,
|
[
|
||||||
ToggleMemberAdmin,
|
SelectNextControl,
|
||||||
RemoveMember
|
ToggleMode,
|
||||||
|
ToggleMemberAdmin,
|
||||||
|
RemoveMember
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// pub fn init(cx: &mut AppContext) {
|
// pub fn init(cx: &mut AppContext) {
|
||||||
|
@ -140,6 +144,7 @@ impl ChannelModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for ChannelModal {}
|
impl EventEmitter<DismissEvent> for ChannelModal {}
|
||||||
|
impl ModalView for ChannelModal {}
|
||||||
|
|
||||||
impl FocusableView for ChannelModal {
|
impl FocusableView for ChannelModal {
|
||||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::sync::Arc;
|
||||||
use theme::ActiveTheme as _;
|
use theme::ActiveTheme as _;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use util::{ResultExt as _, TryFutureExt};
|
use util::{ResultExt as _, TryFutureExt};
|
||||||
|
use workspace::ModalView;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
//Picker::<ContactFinderDelegate>::init(cx);
|
//Picker::<ContactFinderDelegate>::init(cx);
|
||||||
|
@ -95,6 +96,7 @@ pub struct ContactFinderDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for ContactFinder {}
|
impl EventEmitter<DismissEvent> for ContactFinder {}
|
||||||
|
impl ModalView for ContactFinder {}
|
||||||
|
|
||||||
impl FocusableView for ContactFinder {
|
impl FocusableView for ContactFinder {
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@ use std::{rc::Rc, sync::Arc};
|
||||||
use call::{report_call_event_for_room, ActiveCall, Room};
|
use call::{report_call_event_for_room, ActiveCall, Room};
|
||||||
pub use collab_panel::CollabPanel;
|
pub use collab_panel::CollabPanel;
|
||||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||||
|
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
|
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
|
||||||
WindowKind, WindowOptions,
|
WindowKind, WindowOptions,
|
||||||
|
@ -23,7 +24,10 @@ use settings::Settings;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall);
|
actions!(
|
||||||
|
collab,
|
||||||
|
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||||
|
);
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
CollaborationPanelSettings::register(cx);
|
CollaborationPanelSettings::register(cx);
|
||||||
|
@ -34,7 +38,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
collab_titlebar_item::init(cx);
|
collab_titlebar_item::init(cx);
|
||||||
collab_panel::init(cx);
|
collab_panel::init(cx);
|
||||||
channel_view::init(cx);
|
channel_view::init(cx);
|
||||||
// chat_panel::init(cx);
|
chat_panel::init(cx);
|
||||||
notifications::init(&app_state, cx);
|
notifications::init(&app_state, cx);
|
||||||
|
|
||||||
// cx.add_global_action(toggle_screen_sharing);
|
// cx.add_global_action(toggle_screen_sharing);
|
||||||
|
@ -157,6 +161,6 @@ fn notification_window_options(
|
||||||
// .into_any()
|
// .into_any()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
|
fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
|
||||||
// cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
|
cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
|
||||||
// }
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, AnyElement, Div, IntoElement as _, ParentElement as _, RenderOnce, Styled, WindowContext,
|
div, AnyElement, Div, ElementId, IntoElement, ParentElement as _, RenderOnce, Styled,
|
||||||
|
WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, IntoElement)]
|
||||||
pub struct FacePile {
|
pub struct FacePile {
|
||||||
pub faces: Vec<AnyElement>,
|
pub faces: Vec<AnyElement>,
|
||||||
}
|
}
|
||||||
|
@ -15,64 +16,15 @@ impl RenderOnce for FacePile {
|
||||||
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
|
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
|
||||||
let isnt_last = ix < player_count - 1;
|
let isnt_last = ix < player_count - 1;
|
||||||
|
|
||||||
div().when(isnt_last, |div| div.neg_mr_1()).child(player)
|
div()
|
||||||
|
.z_index((player_count - ix) as u32)
|
||||||
|
.when(isnt_last, |div| div.neg_mr_1())
|
||||||
|
.child(player)
|
||||||
});
|
});
|
||||||
div().p_1().flex().items_center().children(player_list)
|
div().p_1().flex().items_center().children(player_list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Element for FacePile {
|
|
||||||
// type State = ();
|
|
||||||
// fn layout(
|
|
||||||
// &mut self,
|
|
||||||
// state: Option<Self::State>,
|
|
||||||
// cx: &mut WindowContext,
|
|
||||||
// ) -> (LayoutId, Self::State) {
|
|
||||||
// let mut width = 0.;
|
|
||||||
// let mut max_height = 0.;
|
|
||||||
// let mut faces = Vec::with_capacity(self.faces.len());
|
|
||||||
// for face in &mut self.faces {
|
|
||||||
// let layout = face.layout(cx);
|
|
||||||
// width += layout.x();
|
|
||||||
// max_height = f32::max(max_height, layout.y());
|
|
||||||
// faces.push(layout);
|
|
||||||
// }
|
|
||||||
// width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
|
|
||||||
// (cx.request_layout(&Style::default(), faces), ())
|
|
||||||
// // (
|
|
||||||
// // Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
|
|
||||||
// // (),
|
|
||||||
// // ))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn paint(
|
|
||||||
// &mut self,
|
|
||||||
// bounds: RectF,
|
|
||||||
// visible_bounds: RectF,
|
|
||||||
// _layout: &mut Self::LayoutState,
|
|
||||||
// view: &mut V,
|
|
||||||
// cx: &mut ViewContext<V>,
|
|
||||||
// ) -> Self::PaintState {
|
|
||||||
// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
|
||||||
|
|
||||||
// let origin_y = bounds.upper_right().y();
|
|
||||||
// let mut origin_x = bounds.upper_right().x();
|
|
||||||
|
|
||||||
// for face in self.faces.iter_mut().rev() {
|
|
||||||
// let size = face.size();
|
|
||||||
// origin_x -= size.x();
|
|
||||||
// let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
|
|
||||||
|
|
||||||
// cx.scene().push_layer(None);
|
|
||||||
// face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
|
|
||||||
// cx.scene().pop_layer();
|
|
||||||
// origin_x += self.overlap;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl Extend<AnyElement> for FacePile {
|
impl Extend<AnyElement> for FacePile {
|
||||||
fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) {
|
fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) {
|
||||||
self.faces.extend(children);
|
self.faces.extend(children);
|
||||||
|
|
|
@ -3,9 +3,9 @@ use std::sync::Arc;
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
pub mod incoming_call_notification;
|
pub mod incoming_call_notification;
|
||||||
// pub mod project_shared_notification;
|
pub mod project_shared_notification;
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
incoming_call_notification::init(app_state, cx);
|
incoming_call_notification::init(app_state, cx);
|
||||||
//project_shared_notification::init(app_state, cx);
|
project_shared_notification::init(app_state, cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
height: px(64.),
|
height: px(64.),
|
||||||
};
|
};
|
||||||
|
|
||||||
for window in unique_screens {
|
for screen in unique_screens {
|
||||||
let options = notification_window_options(window, window_size);
|
let options = notification_window_options(screen, window_size);
|
||||||
let window = cx
|
let window = cx
|
||||||
.open_window(options, |cx| {
|
.open_window(options, |cx| {
|
||||||
cx.build_view(|_| {
|
cx.build_view(|_| {
|
||||||
|
@ -47,15 +47,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
notification_windows.push(window);
|
notification_windows.push(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
// for screen in cx.platform().screens() {
|
|
||||||
// let window = cx
|
|
||||||
// .add_window(notification_window_options(screen, window_size), |_| {
|
|
||||||
// IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
|
|
||||||
// });
|
|
||||||
|
|
||||||
// notification_windows.push(window);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -84,21 +75,22 @@ impl IncomingCallNotificationState {
|
||||||
let active_call = ActiveCall::global(cx);
|
let active_call = ActiveCall::global(cx);
|
||||||
if accept {
|
if accept {
|
||||||
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
|
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
|
||||||
|
let caller_user_id = self.call.calling_user.id;
|
||||||
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
|
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
|
||||||
let app_state = self.app_state.clone();
|
let app_state = self.app_state.clone();
|
||||||
let cx: &mut AppContext = cx;
|
let cx: &mut AppContext = cx;
|
||||||
cx.spawn(|cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
join.await?;
|
join.await?;
|
||||||
if let Some(_project_id) = initial_project_id {
|
if let Some(project_id) = initial_project_id {
|
||||||
cx.update(|_cx| {
|
cx.update(|cx| {
|
||||||
if let Some(_app_state) = app_state.upgrade() {
|
if let Some(app_state) = app_state.upgrade() {
|
||||||
// workspace::join_remote_project(
|
workspace::join_remote_project(
|
||||||
// project_id,
|
project_id,
|
||||||
// caller_user_id,
|
caller_user_id,
|
||||||
// app_state,
|
app_state,
|
||||||
// cx,
|
cx,
|
||||||
// )
|
)
|
||||||
// .detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
@ -138,135 +130,25 @@ impl IncomingCallNotification {
|
||||||
)))
|
)))
|
||||||
.child(self.render_buttons(cx)),
|
.child(self.render_buttons(cx)),
|
||||||
)
|
)
|
||||||
// let theme = &theme::current(cx).incoming_call_notification;
|
|
||||||
// let default_project = proto::ParticipantProject::default();
|
|
||||||
// let initial_project = self
|
|
||||||
// .call
|
|
||||||
// .initial_project
|
|
||||||
// .as_ref()
|
|
||||||
// .unwrap_or(&default_project);
|
|
||||||
// Flex::row()
|
|
||||||
// .with_children(self.call.calling_user.avatar.clone().map(|avatar| {
|
|
||||||
// Image::from_data(avatar)
|
|
||||||
// .with_style(theme.caller_avatar)
|
|
||||||
// .aligned()
|
|
||||||
// }))
|
|
||||||
// .with_child(
|
|
||||||
// Flex::column()
|
|
||||||
// .with_child(
|
|
||||||
// Label::new(
|
|
||||||
// self.call.calling_user.github_login.clone(),
|
|
||||||
// theme.caller_username.text.clone(),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.caller_username.container),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// Label::new(
|
|
||||||
// format!(
|
|
||||||
// "is sharing a project in Zed{}",
|
|
||||||
// if initial_project.worktree_root_names.is_empty() {
|
|
||||||
// ""
|
|
||||||
// } else {
|
|
||||||
// ":"
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// theme.caller_message.text.clone(),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.caller_message.container),
|
|
||||||
// )
|
|
||||||
// .with_children(if initial_project.worktree_root_names.is_empty() {
|
|
||||||
// None
|
|
||||||
// } else {
|
|
||||||
// Some(
|
|
||||||
// Label::new(
|
|
||||||
// initial_project.worktree_root_names.join(", "),
|
|
||||||
// theme.worktree_roots.text.clone(),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.worktree_roots.container),
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.caller_metadata)
|
|
||||||
// .aligned(),
|
|
||||||
// )
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.caller_container)
|
|
||||||
// .flex(1., true)
|
|
||||||
// .into_any()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||||
h_stack()
|
h_stack()
|
||||||
.child(
|
.child(Button::new("accept", "Accept").render(cx).on_click({
|
||||||
Button::new("accept", "Accept")
|
let state = self.state.clone();
|
||||||
.render(cx)
|
move |_, cx| state.respond(true, cx)
|
||||||
// .bg(green())
|
}))
|
||||||
.on_click({
|
.child(Button::new("decline", "Decline").render(cx).on_click({
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
move |_, cx| state.respond(true, cx)
|
move |_, cx| state.respond(false, cx)
|
||||||
}),
|
}))
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("decline", "Decline")
|
|
||||||
.render(cx)
|
|
||||||
// .bg(red())
|
|
||||||
.on_click({
|
|
||||||
let state = self.state.clone();
|
|
||||||
move |_, cx| state.respond(false, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
// enum Accept {}
|
|
||||||
// enum Decline {}
|
|
||||||
|
|
||||||
// let theme = theme::current(cx);
|
|
||||||
// Flex::column()
|
|
||||||
// .with_child(
|
|
||||||
// MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
|
|
||||||
// let theme = &theme.incoming_call_notification;
|
|
||||||
// Label::new("Accept", theme.accept_button.text.clone())
|
|
||||||
// .aligned()
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.accept_button.container)
|
|
||||||
// })
|
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
// .on_click(MouseButton::Left, |_, this, cx| {
|
|
||||||
// this.respond(true, cx);
|
|
||||||
// })
|
|
||||||
// .flex(1., true),
|
|
||||||
// )
|
|
||||||
// .with_child(
|
|
||||||
// MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
|
|
||||||
// let theme = &theme.incoming_call_notification;
|
|
||||||
// Label::new("Decline", theme.decline_button.text.clone())
|
|
||||||
// .aligned()
|
|
||||||
// .contained()
|
|
||||||
// .with_style(theme.decline_button.container)
|
|
||||||
// })
|
|
||||||
// .with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
// .on_click(MouseButton::Left, |_, this, cx| {
|
|
||||||
// this.respond(false, cx);
|
|
||||||
// })
|
|
||||||
// .flex(1., true),
|
|
||||||
// )
|
|
||||||
// .constrained()
|
|
||||||
// .with_width(theme.incoming_call_notification.button_width)
|
|
||||||
// .into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for IncomingCallNotification {
|
impl Render for IncomingCallNotification {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
div().bg(red()).flex_none().child(self.render_caller(cx))
|
div().bg(red()).flex_none().child(self.render_caller(cx))
|
||||||
// Flex::row()
|
|
||||||
// .with_child()
|
|
||||||
// .with_child(self.render_buttons(cx))
|
|
||||||
// .contained()
|
|
||||||
// .with_background_color(background)
|
|
||||||
// .expanded()
|
|
||||||
// .into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,11 @@ use call::{room, ActiveCall};
|
||||||
use client::User;
|
use client::User;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*,
|
px, AppContext, Div, Element, ParentElement, Render, RenderOnce, Size, Styled, ViewContext,
|
||||||
geometry::vector::vec2f,
|
VisualContext,
|
||||||
platform::{CursorStyle, MouseButton},
|
|
||||||
AppContext, Entity, View, ViewContext,
|
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use ui::{h_stack, v_stack, Avatar, Button, Clickable, Label};
|
||||||
use workspace::AppState;
|
use workspace::AppState;
|
||||||
|
|
||||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
|
@ -21,38 +20,54 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
project_id,
|
project_id,
|
||||||
worktree_root_names,
|
worktree_root_names,
|
||||||
} => {
|
} => {
|
||||||
let theme = &theme::current(cx).project_shared_notification;
|
let window_size = Size {
|
||||||
let window_size = vec2f(theme.window_width, theme.window_height);
|
width: px(380.),
|
||||||
|
height: px(64.),
|
||||||
|
};
|
||||||
|
|
||||||
for screen in cx.platform().screens() {
|
for screen in cx.displays() {
|
||||||
let window =
|
let options = notification_window_options(screen, window_size);
|
||||||
cx.add_window(notification_window_options(screen, window_size), |_| {
|
let window = cx.open_window(options, |cx| {
|
||||||
|
cx.build_view(|_| {
|
||||||
ProjectSharedNotification::new(
|
ProjectSharedNotification::new(
|
||||||
owner.clone(),
|
owner.clone(),
|
||||||
*project_id,
|
*project_id,
|
||||||
worktree_root_names.clone(),
|
worktree_root_names.clone(),
|
||||||
app_state.clone(),
|
app_state.clone(),
|
||||||
)
|
)
|
||||||
});
|
})
|
||||||
|
});
|
||||||
notification_windows
|
notification_windows
|
||||||
.entry(*project_id)
|
.entry(*project_id)
|
||||||
.or_insert(Vec::new())
|
.or_insert(Vec::new())
|
||||||
.push(window);
|
.push(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
room::Event::RemoteProjectUnshared { project_id }
|
room::Event::RemoteProjectUnshared { project_id }
|
||||||
| room::Event::RemoteProjectJoined { project_id }
|
| room::Event::RemoteProjectJoined { project_id }
|
||||||
| room::Event::RemoteProjectInvitationDiscarded { project_id } => {
|
| room::Event::RemoteProjectInvitationDiscarded { project_id } => {
|
||||||
if let Some(windows) = notification_windows.remove(&project_id) {
|
if let Some(windows) = notification_windows.remove(&project_id) {
|
||||||
for window in windows {
|
for window in windows {
|
||||||
window.remove(cx);
|
window
|
||||||
|
.update(cx, |_, cx| {
|
||||||
|
// todo!()
|
||||||
|
cx.remove_window();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
room::Event::Left => {
|
room::Event::Left => {
|
||||||
for (_, windows) in notification_windows.drain() {
|
for (_, windows) in notification_windows.drain() {
|
||||||
for window in windows {
|
for window in windows {
|
||||||
window.remove(cx);
|
window
|
||||||
|
.update(cx, |_, cx| {
|
||||||
|
// todo!()
|
||||||
|
cx.remove_window();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,116 +117,60 @@ impl ProjectSharedNotification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render_owner(&self) -> impl Element {
|
||||||
let theme = &theme::current(cx).project_shared_notification;
|
h_stack()
|
||||||
Flex::row()
|
.children(
|
||||||
.with_children(self.owner.avatar.clone().map(|avatar| {
|
self.owner
|
||||||
Image::from_data(avatar)
|
.avatar
|
||||||
.with_style(theme.owner_avatar)
|
.clone()
|
||||||
.aligned()
|
.map(|avatar| Avatar::data(avatar.clone())),
|
||||||
}))
|
)
|
||||||
.with_child(
|
.child(
|
||||||
Flex::column()
|
v_stack()
|
||||||
.with_child(
|
.child(Label::new(self.owner.github_login.clone()))
|
||||||
Label::new(
|
.child(Label::new(format!(
|
||||||
self.owner.github_login.clone(),
|
"is sharing a project in Zed{}",
|
||||||
theme.owner_username.text.clone(),
|
if self.worktree_root_names.is_empty() {
|
||||||
)
|
""
|
||||||
.contained()
|
} else {
|
||||||
.with_style(theme.owner_username.container),
|
":"
|
||||||
)
|
}
|
||||||
.with_child(
|
)))
|
||||||
Label::new(
|
.children(if self.worktree_root_names.is_empty() {
|
||||||
format!(
|
|
||||||
"is sharing a project in Zed{}",
|
|
||||||
if self.worktree_root_names.is_empty() {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
":"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
theme.message.text.clone(),
|
|
||||||
)
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.message.container),
|
|
||||||
)
|
|
||||||
.with_children(if self.worktree_root_names.is_empty() {
|
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(
|
Some(Label::new(self.worktree_root_names.join(", ")))
|
||||||
Label::new(
|
}),
|
||||||
self.worktree_root_names.join(", "),
|
|
||||||
theme.worktree_roots.text.clone(),
|
|
||||||
)
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.worktree_roots.container),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.owner_metadata)
|
|
||||||
.aligned(),
|
|
||||||
)
|
)
|
||||||
.contained()
|
|
||||||
.with_style(theme.owner_container)
|
|
||||||
.flex(1., true)
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||||
enum Open {}
|
let this = cx.view().clone();
|
||||||
enum Dismiss {}
|
v_stack()
|
||||||
|
.child(Button::new("open", "Open").render(cx).on_click({
|
||||||
let theme = theme::current(cx);
|
let this = this.clone();
|
||||||
Flex::column()
|
move |_, cx| {
|
||||||
.with_child(
|
this.update(cx, |this, cx| this.join(cx));
|
||||||
MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
|
}
|
||||||
let theme = &theme.project_shared_notification;
|
}))
|
||||||
Label::new("Open", theme.open_button.text.clone())
|
.child(
|
||||||
.aligned()
|
Button::new("dismiss", "Dismiss")
|
||||||
.contained()
|
.render(cx)
|
||||||
.with_style(theme.open_button.container)
|
.on_click(move |_, cx| {
|
||||||
})
|
this.update(cx, |this, cx| this.dismiss(cx));
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
}),
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
|
|
||||||
.flex(1., true),
|
|
||||||
)
|
)
|
||||||
.with_child(
|
|
||||||
MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
|
|
||||||
let theme = &theme.project_shared_notification;
|
|
||||||
Label::new("Dismiss", theme.dismiss_button.text.clone())
|
|
||||||
.aligned()
|
|
||||||
.contained()
|
|
||||||
.with_style(theme.dismiss_button.container)
|
|
||||||
})
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.on_click(MouseButton::Left, |_, this, cx| {
|
|
||||||
this.dismiss(cx);
|
|
||||||
})
|
|
||||||
.flex(1., true),
|
|
||||||
)
|
|
||||||
.constrained()
|
|
||||||
.with_width(theme.project_shared_notification.button_width)
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ProjectSharedNotification {
|
impl Render for ProjectSharedNotification {
|
||||||
type Event = ();
|
type Element = Div;
|
||||||
}
|
|
||||||
|
|
||||||
impl View for ProjectSharedNotification {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||||
fn ui_name() -> &'static str {
|
h_stack()
|
||||||
"ProjectSharedNotification"
|
.size_full()
|
||||||
}
|
.bg(gpui::red())
|
||||||
|
.child(self.render_owner())
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
|
.child(self.render_buttons(cx))
|
||||||
let background = theme::current(cx).project_shared_notification.background;
|
|
||||||
Flex::row()
|
|
||||||
.with_child(self.render_owner(cx))
|
|
||||||
.with_child(self.render_buttons(cx))
|
|
||||||
.contained()
|
|
||||||
.with_background_color(background)
|
|
||||||
.expanded()
|
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use collections::{CommandPaletteFilter, HashMap};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
||||||
Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
|
|
||||||
|
@ -16,16 +16,18 @@ use util::{
|
||||||
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
||||||
ResultExt,
|
ResultExt,
|
||||||
};
|
};
|
||||||
use workspace::Workspace;
|
use workspace::{ModalView, Workspace};
|
||||||
use zed_actions::OpenZedURL;
|
use zed_actions::OpenZedURL;
|
||||||
|
|
||||||
actions!(Toggle);
|
actions!(command_palette, [Toggle]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.set_global(HitCounts::default());
|
cx.set_global(HitCounts::default());
|
||||||
cx.observe_new_views(CommandPalette::register).detach();
|
cx.observe_new_views(CommandPalette::register).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ModalView for CommandPalette {}
|
||||||
|
|
||||||
pub struct CommandPalette {
|
pub struct CommandPalette {
|
||||||
picker: View<Picker<CommandPaletteDelegate>>,
|
picker: View<Picker<CommandPaletteDelegate>>,
|
||||||
}
|
}
|
||||||
|
@ -47,7 +49,7 @@ impl CommandPalette {
|
||||||
.available_actions()
|
.available_actions()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|action| {
|
.filter_map(|action| {
|
||||||
let name = gpui::remove_the_2(action.name());
|
let name = action.name();
|
||||||
let namespace = name.split("::").next().unwrap_or("malformed action name");
|
let namespace = name.split("::").next().unwrap_or("malformed action name");
|
||||||
if filter.is_some_and(|f| {
|
if filter.is_some_and(|f| {
|
||||||
f.hidden_namespaces.contains(namespace)
|
f.hidden_namespaces.contains(namespace)
|
||||||
|
@ -59,7 +61,6 @@ impl CommandPalette {
|
||||||
Some(Command {
|
Some(Command {
|
||||||
name: humanize_action_name(&name),
|
name: humanize_action_name(&name),
|
||||||
action,
|
action,
|
||||||
keystrokes: vec![], // todo!()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -108,7 +109,6 @@ pub struct CommandPaletteDelegate {
|
||||||
struct Command {
|
struct Command {
|
||||||
name: String,
|
name: String,
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
keystrokes: Vec<Keystroke>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Command {
|
impl Clone for Command {
|
||||||
|
@ -116,7 +116,6 @@ impl Clone for Command {
|
||||||
Self {
|
Self {
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
action: self.action.boxed_clone(),
|
action: self.action.boxed_clone(),
|
||||||
keystrokes: self.keystrokes.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,6 +226,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(CommandInterceptResult {
|
if let Some(CommandInterceptResult {
|
||||||
action,
|
action,
|
||||||
string,
|
string,
|
||||||
|
@ -242,7 +242,6 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
commands.push(Command {
|
commands.push(Command {
|
||||||
name: string.clone(),
|
name: string.clone(),
|
||||||
action,
|
action,
|
||||||
keystrokes: vec![],
|
|
||||||
});
|
});
|
||||||
matches.insert(
|
matches.insert(
|
||||||
0,
|
0,
|
||||||
|
@ -254,6 +253,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
picker
|
picker
|
||||||
.update(&mut cx, |picker, _| {
|
.update(&mut cx, |picker, _| {
|
||||||
let delegate = &mut picker.delegate;
|
let delegate = &mut picker.delegate;
|
||||||
|
@ -283,6 +283,8 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
}
|
}
|
||||||
let action_ix = self.matches[self.selected_ix].candidate_id;
|
let action_ix = self.matches[self.selected_ix].candidate_id;
|
||||||
let command = self.commands.swap_remove(action_ix);
|
let command = self.commands.swap_remove(action_ix);
|
||||||
|
self.matches.clear();
|
||||||
|
self.commands.clear();
|
||||||
cx.update_global(|hit_counts: &mut HitCounts, _| {
|
cx.update_global(|hit_counts: &mut HitCounts, _| {
|
||||||
*hit_counts.0.entry(command.name).or_default() += 1;
|
*hit_counts.0.entry(command.name).or_default() += 1;
|
||||||
});
|
});
|
||||||
|
@ -298,13 +300,8 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let Some(r#match) = self.matches.get(ix) else {
|
let r#match = self.matches.get(ix)?;
|
||||||
return None;
|
let command = self.commands.get(r#match.candidate_id)?;
|
||||||
};
|
|
||||||
let Some(command) = self.commands.get(r#match.candidate_id) else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(ix).inset(true).selected(selected).child(
|
ListItem::new(ix).inset(true).selected(selected).child(
|
||||||
h_stack()
|
h_stack()
|
||||||
|
@ -352,8 +349,7 @@ impl std::fmt::Debug for Command {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Command")
|
f.debug_struct("Command")
|
||||||
.field("name", &self.name)
|
.field("name", &self.name)
|
||||||
.field("keystrokes", &self.keystrokes)
|
.finish_non_exhaustive()
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,12 +34,15 @@ use util::{
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
Suggest,
|
copilot,
|
||||||
NextSuggestion,
|
[
|
||||||
PreviousSuggestion,
|
Suggest,
|
||||||
Reinstall,
|
NextSuggestion,
|
||||||
SignIn,
|
PreviousSuggestion,
|
||||||
SignOut
|
Reinstall,
|
||||||
|
SignIn,
|
||||||
|
SignOut
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
|
|
|
@ -43,7 +43,7 @@ use workspace::{
|
||||||
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
|
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(Deploy, ToggleWarnings);
|
actions!(diagnostics, [Deploy, ToggleWarnings]);
|
||||||
|
|
||||||
const CONTEXT_LINE_COUNT: u32 = 1;
|
const CONTEXT_LINE_COUNT: u32 = 1;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ mod link_go_to_definition;
|
||||||
mod mouse_context_menu;
|
mod mouse_context_menu;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
mod rust_analyzer_ext;
|
||||||
pub mod scroll;
|
pub mod scroll;
|
||||||
pub mod selections_collection;
|
pub mod selections_collection;
|
||||||
|
|
||||||
|
@ -300,6 +301,7 @@ actions!(
|
||||||
DeleteToEndOfLine,
|
DeleteToEndOfLine,
|
||||||
CutToEndOfLine,
|
CutToEndOfLine,
|
||||||
DuplicateLine,
|
DuplicateLine,
|
||||||
|
ExpandMacroRecursively,
|
||||||
MoveLineUp,
|
MoveLineUp,
|
||||||
MoveLineDown,
|
MoveLineDown,
|
||||||
JoinLines,
|
JoinLines,
|
||||||
|
@ -425,6 +427,8 @@ pub fn init_settings(cx: &mut AppContext) {
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
init_settings(cx);
|
init_settings(cx);
|
||||||
|
|
||||||
|
rust_analyzer_ext::apply_related_actions(cx);
|
||||||
cx.add_action(Editor::new_file);
|
cx.add_action(Editor::new_file);
|
||||||
cx.add_action(Editor::new_file_in_direction);
|
cx.add_action(Editor::new_file_in_direction);
|
||||||
cx.add_action(Editor::cancel);
|
cx.add_action(Editor::cancel);
|
||||||
|
|
98
crates/editor/src/rust_analyzer_ext.rs
Normal file
98
crates/editor/src/rust_analyzer_ext.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use gpui::{AppContext, Task, ViewContext};
|
||||||
|
use language::Language;
|
||||||
|
use multi_buffer::MultiBuffer;
|
||||||
|
use project::lsp_ext_command::ExpandMacro;
|
||||||
|
use text::ToPointUtf16;
|
||||||
|
|
||||||
|
use crate::{Editor, ExpandMacroRecursively};
|
||||||
|
|
||||||
|
pub fn apply_related_actions(cx: &mut AppContext) {
|
||||||
|
cx.add_async_action(expand_macro_recursively);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_macro_recursively(
|
||||||
|
editor: &mut Editor,
|
||||||
|
_: &ExpandMacroRecursively,
|
||||||
|
cx: &mut ViewContext<'_, '_, Editor>,
|
||||||
|
) -> Option<Task<anyhow::Result<()>>> {
|
||||||
|
if editor.selections.count() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let project = editor.project.as_ref()?;
|
||||||
|
let workspace = editor.workspace(cx)?;
|
||||||
|
let multibuffer = editor.buffer().read(cx);
|
||||||
|
|
||||||
|
let (trigger_anchor, rust_language, server_to_query, buffer) = editor
|
||||||
|
.selections
|
||||||
|
.disjoint_anchors()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|selection| selection.start == selection.end)
|
||||||
|
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
|
||||||
|
.filter_map(|(buffer_id, trigger_anchor)| {
|
||||||
|
let buffer = multibuffer.buffer(buffer_id)?;
|
||||||
|
let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
||||||
|
if !is_rust_language(&rust_language) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((trigger_anchor, rust_language, buffer))
|
||||||
|
})
|
||||||
|
.find_map(|(trigger_anchor, rust_language, buffer)| {
|
||||||
|
project
|
||||||
|
.read(cx)
|
||||||
|
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|(adapter, server)| {
|
||||||
|
if adapter.name.0.as_ref() == "rust-analyzer" {
|
||||||
|
Some((
|
||||||
|
trigger_anchor,
|
||||||
|
Arc::clone(&rust_language),
|
||||||
|
server.server_id(),
|
||||||
|
buffer.clone(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let project = project.clone();
|
||||||
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||||
|
let expand_macro_task = project.update(cx, |project, cx| {
|
||||||
|
project.request_lsp(
|
||||||
|
buffer,
|
||||||
|
project::LanguageServerToQuery::Other(server_to_query),
|
||||||
|
ExpandMacro { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Some(cx.spawn(|_, mut cx| async move {
|
||||||
|
let macro_expansion = expand_macro_task.await.context("expand macro")?;
|
||||||
|
if macro_expansion.is_empty() {
|
||||||
|
log::info!("Empty macro expansion for position {position:?}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = project.update(&mut cx, |project, cx| {
|
||||||
|
project.create_buffer(¯o_expansion.expansion, Some(rust_language), cx)
|
||||||
|
})?;
|
||||||
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
let buffer = cx.add_model(|cx| {
|
||||||
|
MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
|
||||||
|
});
|
||||||
|
workspace.add_item(
|
||||||
|
Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_rust_language(language: &Language) -> bool {
|
||||||
|
language.name().as_ref() == "Rust"
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ mod link_go_to_definition;
|
||||||
mod mouse_context_menu;
|
mod mouse_context_menu;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
mod rust_analyzer_ext;
|
||||||
pub mod scroll;
|
pub mod scroll;
|
||||||
pub mod selections_collection;
|
pub mod selections_collection;
|
||||||
|
|
||||||
|
@ -39,8 +40,8 @@ use futures::FutureExt;
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use git::diff_hunk_to_display;
|
use git::diff_hunk_to_display;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
|
actions, div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
|
||||||
AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
|
||||||
DispatchPhase, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures,
|
DispatchPhase, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontFeatures,
|
||||||
FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model,
|
FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model,
|
||||||
MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText,
|
MouseButton, ParentElement, Pixels, Render, RenderOnce, SharedString, Styled, StyledText,
|
||||||
|
@ -107,7 +108,7 @@ use ui::{
|
||||||
use ui::{prelude::*, IconSize};
|
use ui::{prelude::*, IconSize};
|
||||||
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{ItemEvent, ItemHandle},
|
item::{Item, ItemEvent, ItemHandle},
|
||||||
searchable::SearchEvent,
|
searchable::SearchEvent,
|
||||||
ItemNavHistory, Pane, SplitDirection, ViewId, Workspace,
|
ItemNavHistory, Pane, SplitDirection, ViewId, Workspace,
|
||||||
};
|
};
|
||||||
|
@ -185,82 +186,101 @@ pub fn render_parsed_markdown(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct SelectNext {
|
pub struct SelectNext {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replace_newest: bool,
|
pub replace_newest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct SelectPrevious {
|
pub struct SelectPrevious {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replace_newest: bool,
|
pub replace_newest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct SelectAllMatches {
|
pub struct SelectAllMatches {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replace_newest: bool,
|
pub replace_newest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct SelectToBeginningOfLine {
|
pub struct SelectToBeginningOfLine {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
stop_at_soft_wraps: bool,
|
stop_at_soft_wraps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct MovePageUp {
|
pub struct MovePageUp {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
center_cursor: bool,
|
center_cursor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct MovePageDown {
|
pub struct MovePageDown {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
center_cursor: bool,
|
center_cursor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct SelectToEndOfLine {
|
pub struct SelectToEndOfLine {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
stop_at_soft_wraps: bool,
|
stop_at_soft_wraps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct ToggleCodeActions {
|
pub struct ToggleCodeActions {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub deployed_from_indicator: bool,
|
pub deployed_from_indicator: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct ConfirmCompletion {
|
pub struct ConfirmCompletion {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub item_ix: Option<usize>,
|
pub item_ix: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct ConfirmCodeAction {
|
pub struct ConfirmCodeAction {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub item_ix: Option<usize>,
|
pub item_ix: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct ToggleComments {
|
pub struct ToggleComments {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub advance_downwards: bool,
|
pub advance_downwards: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct FoldAt {
|
pub struct FoldAt {
|
||||||
pub buffer_row: u32,
|
pub buffer_row: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||||
pub struct UnfoldAt {
|
pub struct UnfoldAt {
|
||||||
pub buffer_row: u32,
|
pub buffer_row: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_actions!(
|
||||||
|
editor,
|
||||||
|
[
|
||||||
|
SelectNext,
|
||||||
|
SelectPrevious,
|
||||||
|
SelectAllMatches,
|
||||||
|
SelectToBeginningOfLine,
|
||||||
|
MovePageUp,
|
||||||
|
MovePageDown,
|
||||||
|
SelectToEndOfLine,
|
||||||
|
ToggleCodeActions,
|
||||||
|
ConfirmCompletion,
|
||||||
|
ConfirmCodeAction,
|
||||||
|
ToggleComments,
|
||||||
|
FoldAt,
|
||||||
|
UnfoldAt
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum InlayId {
|
pub enum InlayId {
|
||||||
Suggestion(usize),
|
Suggestion(usize),
|
||||||
|
@ -277,121 +297,125 @@ impl InlayId {
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
AddSelectionAbove,
|
editor,
|
||||||
AddSelectionBelow,
|
[
|
||||||
Backspace,
|
AddSelectionAbove,
|
||||||
Cancel,
|
AddSelectionBelow,
|
||||||
ConfirmRename,
|
Backspace,
|
||||||
ContextMenuFirst,
|
Cancel,
|
||||||
ContextMenuLast,
|
ConfirmRename,
|
||||||
ContextMenuNext,
|
ContextMenuFirst,
|
||||||
ContextMenuPrev,
|
ContextMenuLast,
|
||||||
ConvertToKebabCase,
|
ContextMenuNext,
|
||||||
ConvertToLowerCamelCase,
|
ContextMenuPrev,
|
||||||
ConvertToLowerCase,
|
ConvertToKebabCase,
|
||||||
ConvertToSnakeCase,
|
ConvertToLowerCamelCase,
|
||||||
ConvertToTitleCase,
|
ConvertToLowerCase,
|
||||||
ConvertToUpperCamelCase,
|
ConvertToSnakeCase,
|
||||||
ConvertToUpperCase,
|
ConvertToTitleCase,
|
||||||
Copy,
|
ConvertToUpperCamelCase,
|
||||||
CopyHighlightJson,
|
ConvertToUpperCase,
|
||||||
CopyPath,
|
Copy,
|
||||||
CopyRelativePath,
|
CopyHighlightJson,
|
||||||
Cut,
|
CopyPath,
|
||||||
CutToEndOfLine,
|
CopyRelativePath,
|
||||||
Delete,
|
Cut,
|
||||||
DeleteLine,
|
CutToEndOfLine,
|
||||||
DeleteToBeginningOfLine,
|
Delete,
|
||||||
DeleteToEndOfLine,
|
DeleteLine,
|
||||||
DeleteToNextSubwordEnd,
|
DeleteToBeginningOfLine,
|
||||||
DeleteToNextWordEnd,
|
DeleteToEndOfLine,
|
||||||
DeleteToPreviousSubwordStart,
|
DeleteToNextSubwordEnd,
|
||||||
DeleteToPreviousWordStart,
|
DeleteToNextWordEnd,
|
||||||
DuplicateLine,
|
DeleteToPreviousSubwordStart,
|
||||||
FindAllReferences,
|
DeleteToPreviousWordStart,
|
||||||
Fold,
|
DuplicateLine,
|
||||||
FoldSelectedRanges,
|
ExpandMacroRecursively,
|
||||||
Format,
|
FindAllReferences,
|
||||||
GoToDefinition,
|
Fold,
|
||||||
GoToDefinitionSplit,
|
FoldSelectedRanges,
|
||||||
GoToDiagnostic,
|
Format,
|
||||||
GoToHunk,
|
GoToDefinition,
|
||||||
GoToPrevDiagnostic,
|
GoToDefinitionSplit,
|
||||||
GoToPrevHunk,
|
GoToDiagnostic,
|
||||||
GoToTypeDefinition,
|
GoToHunk,
|
||||||
GoToTypeDefinitionSplit,
|
GoToPrevDiagnostic,
|
||||||
HalfPageDown,
|
GoToPrevHunk,
|
||||||
HalfPageUp,
|
GoToTypeDefinition,
|
||||||
Hover,
|
GoToTypeDefinitionSplit,
|
||||||
Indent,
|
HalfPageDown,
|
||||||
JoinLines,
|
HalfPageUp,
|
||||||
LineDown,
|
Hover,
|
||||||
LineUp,
|
Indent,
|
||||||
MoveDown,
|
JoinLines,
|
||||||
MoveLeft,
|
LineDown,
|
||||||
MoveLineDown,
|
LineUp,
|
||||||
MoveLineUp,
|
MoveDown,
|
||||||
MoveRight,
|
MoveLeft,
|
||||||
MoveToBeginning,
|
MoveLineDown,
|
||||||
MoveToBeginningOfLine,
|
MoveLineUp,
|
||||||
MoveToEnclosingBracket,
|
MoveRight,
|
||||||
MoveToEnd,
|
MoveToBeginning,
|
||||||
MoveToEndOfLine,
|
MoveToBeginningOfLine,
|
||||||
MoveToEndOfParagraph,
|
MoveToEnclosingBracket,
|
||||||
MoveToNextSubwordEnd,
|
MoveToEnd,
|
||||||
MoveToNextWordEnd,
|
MoveToEndOfLine,
|
||||||
MoveToPreviousSubwordStart,
|
MoveToEndOfParagraph,
|
||||||
MoveToPreviousWordStart,
|
MoveToNextSubwordEnd,
|
||||||
MoveToStartOfParagraph,
|
MoveToNextWordEnd,
|
||||||
MoveUp,
|
MoveToPreviousSubwordStart,
|
||||||
Newline,
|
MoveToPreviousWordStart,
|
||||||
NewlineAbove,
|
MoveToStartOfParagraph,
|
||||||
NewlineBelow,
|
MoveUp,
|
||||||
NextScreen,
|
Newline,
|
||||||
OpenExcerpts,
|
NewlineAbove,
|
||||||
Outdent,
|
NewlineBelow,
|
||||||
PageDown,
|
NextScreen,
|
||||||
PageUp,
|
OpenExcerpts,
|
||||||
Paste,
|
Outdent,
|
||||||
Redo,
|
PageDown,
|
||||||
RedoSelection,
|
PageUp,
|
||||||
Rename,
|
Paste,
|
||||||
RestartLanguageServer,
|
Redo,
|
||||||
RevealInFinder,
|
RedoSelection,
|
||||||
ReverseLines,
|
Rename,
|
||||||
ScrollCursorBottom,
|
RestartLanguageServer,
|
||||||
ScrollCursorCenter,
|
RevealInFinder,
|
||||||
ScrollCursorTop,
|
ReverseLines,
|
||||||
SelectAll,
|
ScrollCursorBottom,
|
||||||
SelectDown,
|
ScrollCursorCenter,
|
||||||
SelectLargerSyntaxNode,
|
ScrollCursorTop,
|
||||||
SelectLeft,
|
SelectAll,
|
||||||
SelectLine,
|
SelectDown,
|
||||||
SelectRight,
|
SelectLargerSyntaxNode,
|
||||||
SelectSmallerSyntaxNode,
|
SelectLeft,
|
||||||
SelectToBeginning,
|
SelectLine,
|
||||||
SelectToEnd,
|
SelectRight,
|
||||||
SelectToEndOfParagraph,
|
SelectSmallerSyntaxNode,
|
||||||
SelectToNextSubwordEnd,
|
SelectToBeginning,
|
||||||
SelectToNextWordEnd,
|
SelectToEnd,
|
||||||
SelectToPreviousSubwordStart,
|
SelectToEndOfParagraph,
|
||||||
SelectToPreviousWordStart,
|
SelectToNextSubwordEnd,
|
||||||
SelectToStartOfParagraph,
|
SelectToNextWordEnd,
|
||||||
SelectUp,
|
SelectToPreviousSubwordStart,
|
||||||
ShowCharacterPalette,
|
SelectToPreviousWordStart,
|
||||||
ShowCompletions,
|
SelectToStartOfParagraph,
|
||||||
ShuffleLines,
|
SelectUp,
|
||||||
SortLinesCaseInsensitive,
|
ShowCharacterPalette,
|
||||||
SortLinesCaseSensitive,
|
ShowCompletions,
|
||||||
SplitSelectionIntoLines,
|
ShuffleLines,
|
||||||
Tab,
|
SortLinesCaseInsensitive,
|
||||||
TabPrev,
|
SortLinesCaseSensitive,
|
||||||
ToggleInlayHints,
|
SplitSelectionIntoLines,
|
||||||
ToggleSoftWrap,
|
Tab,
|
||||||
Transpose,
|
TabPrev,
|
||||||
Undo,
|
ToggleInlayHints,
|
||||||
UndoSelection,
|
ToggleSoftWrap,
|
||||||
UnfoldLines,
|
Transpose,
|
||||||
|
Undo,
|
||||||
|
UndoSelection,
|
||||||
|
UnfoldLines,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
enum DocumentHighlightRead {}
|
enum DocumentHighlightRead {}
|
||||||
|
@ -9319,7 +9343,6 @@ impl Render for Editor {
|
||||||
scrollbar_width: px(12.),
|
scrollbar_width: px(12.),
|
||||||
syntax: cx.theme().syntax().clone(),
|
syntax: cx.theme().syntax().clone(),
|
||||||
diagnostic_style: cx.theme().diagnostic_style(),
|
diagnostic_style: cx.theme().diagnostic_style(),
|
||||||
// TODO kb find `HighlightStyle` usages
|
|
||||||
// todo!("what about the rest of the highlight style parts?")
|
// todo!("what about the rest of the highlight style parts?")
|
||||||
inlays_style: HighlightStyle {
|
inlays_style: HighlightStyle {
|
||||||
color: Some(cx.theme().status().hint),
|
color: Some(cx.theme().status().hint),
|
||||||
|
|
|
@ -32,7 +32,7 @@ use gpui::{
|
||||||
Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
|
Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::language_settings::ShowWhitespaceSetting;
|
use language::{language_settings::ShowWhitespaceSetting, Language};
|
||||||
use multi_buffer::Anchor;
|
use multi_buffer::Anchor;
|
||||||
use project::{
|
use project::{
|
||||||
project_settings::{GitGutterSetting, ProjectSettings},
|
project_settings::{GitGutterSetting, ProjectSettings},
|
||||||
|
@ -135,11 +135,13 @@ impl EditorElement {
|
||||||
|
|
||||||
fn register_actions(&self, cx: &mut WindowContext) {
|
fn register_actions(&self, cx: &mut WindowContext) {
|
||||||
let view = &self.editor;
|
let view = &self.editor;
|
||||||
self.editor.update(cx, |editor, cx| {
|
view.update(cx, |editor, cx| {
|
||||||
for action in editor.editor_actions.iter() {
|
for action in editor.editor_actions.iter() {
|
||||||
(action)(cx)
|
(action)(cx)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
crate::rust_analyzer_ext::apply_related_actions(view, cx);
|
||||||
register_action(view, cx, Editor::move_left);
|
register_action(view, cx, Editor::move_left);
|
||||||
register_action(view, cx, Editor::move_right);
|
register_action(view, cx, Editor::move_right);
|
||||||
register_action(view, cx, Editor::move_down);
|
register_action(view, cx, Editor::move_down);
|
||||||
|
@ -385,17 +387,17 @@ impl EditorElement {
|
||||||
gutter_bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
stacking_order: &StackingOrder,
|
stacking_order: &StackingOrder,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) {
|
||||||
let mut click_count = event.click_count;
|
let mut click_count = event.click_count;
|
||||||
let modifiers = event.modifiers;
|
let modifiers = event.modifiers;
|
||||||
|
|
||||||
if gutter_bounds.contains_point(&event.position) {
|
if gutter_bounds.contains_point(&event.position) {
|
||||||
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
|
click_count = 3; // Simulate triple-click when clicking the gutter to select lines
|
||||||
} else if !text_bounds.contains_point(&event.position) {
|
} else if !text_bounds.contains_point(&event.position) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
if !cx.was_top_layer(&event.position, stacking_order) {
|
if !cx.was_top_layer(&event.position, stacking_order) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||||
|
@ -427,7 +429,7 @@ impl EditorElement {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_right_down(
|
fn mouse_right_down(
|
||||||
|
@ -436,9 +438,9 @@ impl EditorElement {
|
||||||
position_map: &PositionMap,
|
position_map: &PositionMap,
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) {
|
||||||
if !text_bounds.contains_point(&event.position) {
|
if !text_bounds.contains_point(&event.position) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||||
mouse_context_menu::deploy_context_menu(
|
mouse_context_menu::deploy_context_menu(
|
||||||
|
@ -447,7 +449,7 @@ impl EditorElement {
|
||||||
point_for_position.previous_valid,
|
point_for_position.previous_valid,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
true
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_up(
|
fn mouse_up(
|
||||||
|
@ -457,7 +459,7 @@ impl EditorElement {
|
||||||
text_bounds: Bounds<Pixels>,
|
text_bounds: Bounds<Pixels>,
|
||||||
stacking_order: &StackingOrder,
|
stacking_order: &StackingOrder,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) {
|
||||||
let end_selection = editor.has_pending_selection();
|
let end_selection = editor.has_pending_selection();
|
||||||
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
|
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
|
||||||
|
|
||||||
|
@ -479,10 +481,10 @@ impl EditorElement {
|
||||||
go_to_fetched_definition(editor, point, split, cx);
|
go_to_fetched_definition(editor, point, split, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
cx.stop_propagation();
|
||||||
|
} else if end_selection {
|
||||||
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
end_selection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_moved(
|
fn mouse_moved(
|
||||||
|
@ -493,7 +495,7 @@ impl EditorElement {
|
||||||
gutter_bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
stacking_order: &StackingOrder,
|
stacking_order: &StackingOrder,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) {
|
||||||
let modifiers = event.modifiers;
|
let modifiers = event.modifiers;
|
||||||
if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
|
if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
|
||||||
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
let point_for_position = position_map.point_for_position(text_bounds, event.position);
|
||||||
|
@ -562,11 +564,13 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
cx.stop_propagation();
|
||||||
} else {
|
} else {
|
||||||
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
|
update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
|
||||||
hover_at(editor, None, cx);
|
hover_at(editor, None, cx);
|
||||||
gutter_hovered && was_top
|
if gutter_hovered && was_top {
|
||||||
|
cx.stop_propagation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,9 +580,9 @@ impl EditorElement {
|
||||||
position_map: &PositionMap,
|
position_map: &PositionMap,
|
||||||
bounds: &InteractiveBounds,
|
bounds: &InteractiveBounds,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
) -> bool {
|
) {
|
||||||
if !bounds.visibly_contains(&event.position, cx) {
|
if !bounds.visibly_contains(&event.position, cx) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let line_height = position_map.line_height;
|
let line_height = position_map.line_height;
|
||||||
|
@ -602,8 +606,7 @@ impl EditorElement {
|
||||||
let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
|
let y = f32::from((scroll_position.y * line_height - delta.y) / line_height);
|
||||||
let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
|
let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
|
||||||
editor.scroll(scroll_position, axis, cx);
|
editor.scroll(scroll_position, axis, cx);
|
||||||
|
cx.stop_propagation();
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_background(
|
fn paint_background(
|
||||||
|
@ -749,44 +752,47 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
|
cx.with_z_index(1, |cx| {
|
||||||
if let Some(mut fold_indicator) = fold_indicator {
|
for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
|
||||||
let mut fold_indicator = fold_indicator.into_any_element();
|
if let Some(mut fold_indicator) = fold_indicator {
|
||||||
|
let mut fold_indicator = fold_indicator.into_any_element();
|
||||||
|
let available_space = size(
|
||||||
|
AvailableSpace::MinContent,
|
||||||
|
AvailableSpace::Definite(line_height * 0.55),
|
||||||
|
);
|
||||||
|
let fold_indicator_size = fold_indicator.measure(available_space, cx);
|
||||||
|
|
||||||
|
let position = point(
|
||||||
|
bounds.size.width - layout.gutter_padding,
|
||||||
|
ix as f32 * line_height - (scroll_top % line_height),
|
||||||
|
);
|
||||||
|
let centering_offset = point(
|
||||||
|
(layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width)
|
||||||
|
/ 2.,
|
||||||
|
(line_height - fold_indicator_size.height) / 2.,
|
||||||
|
);
|
||||||
|
let origin = bounds.origin + position + centering_offset;
|
||||||
|
fold_indicator.draw(origin, available_space, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(indicator) = layout.code_actions_indicator.take() {
|
||||||
|
let mut button = indicator.button.into_any_element();
|
||||||
let available_space = size(
|
let available_space = size(
|
||||||
AvailableSpace::MinContent,
|
AvailableSpace::MinContent,
|
||||||
AvailableSpace::Definite(line_height * 0.55),
|
AvailableSpace::Definite(line_height),
|
||||||
);
|
);
|
||||||
let fold_indicator_size = fold_indicator.measure(available_space, cx);
|
let indicator_size = button.measure(available_space, cx);
|
||||||
|
|
||||||
let position = point(
|
let mut x = Pixels::ZERO;
|
||||||
bounds.size.width - layout.gutter_padding,
|
let mut y = indicator.row as f32 * line_height - scroll_top;
|
||||||
ix as f32 * line_height - (scroll_top % line_height),
|
// Center indicator.
|
||||||
);
|
x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
|
||||||
let centering_offset = point(
|
y += (line_height - indicator_size.height) / 2.;
|
||||||
(layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width) / 2.,
|
|
||||||
(line_height - fold_indicator_size.height) / 2.,
|
button.draw(bounds.origin + point(x, y), available_space, cx);
|
||||||
);
|
|
||||||
let origin = bounds.origin + position + centering_offset;
|
|
||||||
fold_indicator.draw(origin, available_space, cx);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if let Some(indicator) = layout.code_actions_indicator.take() {
|
|
||||||
let mut button = indicator.button.into_any_element();
|
|
||||||
let available_space = size(
|
|
||||||
AvailableSpace::MinContent,
|
|
||||||
AvailableSpace::Definite(line_height),
|
|
||||||
);
|
|
||||||
let indicator_size = button.measure(available_space, cx);
|
|
||||||
|
|
||||||
let mut x = Pixels::ZERO;
|
|
||||||
let mut y = indicator.row as f32 * line_height - scroll_top;
|
|
||||||
// Center indicator.
|
|
||||||
x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
|
|
||||||
y += (line_height - indicator_size.height) / 2.;
|
|
||||||
|
|
||||||
button.draw(bounds.origin + point(x, y), available_space, cx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
|
fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
|
||||||
|
@ -824,8 +830,8 @@ impl EditorElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
let color = match status {
|
let color = match status {
|
||||||
DiffHunkStatus::Added => gpui::green(), // todo!("use the appropriate color")
|
DiffHunkStatus::Added => cx.theme().status().created,
|
||||||
DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the appropriate color")
|
DiffHunkStatus::Modified => cx.theme().status().modified,
|
||||||
|
|
||||||
//TODO: This rendering is entirely a horrible hack
|
//TODO: This rendering is entirely a horrible hack
|
||||||
DiffHunkStatus::Removed => {
|
DiffHunkStatus::Removed => {
|
||||||
|
@ -842,7 +848,7 @@ impl EditorElement {
|
||||||
cx.paint_quad(
|
cx.paint_quad(
|
||||||
highlight_bounds,
|
highlight_bounds,
|
||||||
Corners::all(1. * line_height),
|
Corners::all(1. * line_height),
|
||||||
gpui::red(), // todo!("use the right color")
|
cx.theme().status().deleted,
|
||||||
Edges::default(),
|
Edges::default(),
|
||||||
transparent_black(),
|
transparent_black(),
|
||||||
);
|
);
|
||||||
|
@ -1230,203 +1236,216 @@ impl EditorElement {
|
||||||
bounds.upper_right().x - self.style.scrollbar_width
|
bounds.upper_right().x - self.style.scrollbar_width
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn paint_scrollbar(
|
fn paint_scrollbar(
|
||||||
// &mut self,
|
&mut self,
|
||||||
// bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
// layout: &mut LayoutState,
|
layout: &mut LayoutState,
|
||||||
// editor: &Editor,
|
cx: &mut WindowContext,
|
||||||
// cx: &mut ViewContext<Editor>,
|
) {
|
||||||
// ) {
|
if layout.mode != EditorMode::Full {
|
||||||
// enum ScrollbarMouseHandlers {}
|
return;
|
||||||
// if layout.mode != EditorMode::Full {
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let style = &self.style.theme.scrollbar;
|
let top = bounds.origin.y;
|
||||||
|
let bottom = bounds.lower_left().y;
|
||||||
|
let right = bounds.lower_right().x;
|
||||||
|
let left = self.scrollbar_left(&bounds);
|
||||||
|
let row_range = layout.scrollbar_row_range.clone();
|
||||||
|
let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
|
||||||
|
|
||||||
// let top = bounds.min_y;
|
let mut height = bounds.size.height;
|
||||||
// let bottom = bounds.max_y;
|
let mut first_row_y_offset = px(0.0);
|
||||||
// let right = bounds.max_x;
|
|
||||||
// let left = self.scrollbar_left(&bounds);
|
|
||||||
// let row_range = &layout.scrollbar_row_range;
|
|
||||||
// let max_row = layout.max_row as f32 + (row_range.end - row_range.start);
|
|
||||||
|
|
||||||
// let mut height = bounds.height();
|
// Impose a minimum height on the scrollbar thumb
|
||||||
// let mut first_row_y_offset = 0.0;
|
let row_height = height / max_row;
|
||||||
|
let min_thumb_height = layout.position_map.line_height;
|
||||||
|
let thumb_height = (row_range.end - row_range.start) * row_height;
|
||||||
|
if thumb_height < min_thumb_height {
|
||||||
|
first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
|
||||||
|
height -= min_thumb_height - thumb_height;
|
||||||
|
}
|
||||||
|
|
||||||
// // Impose a minimum height on the scrollbar thumb
|
let y_for_row = |row: f32| -> Pixels { top + first_row_y_offset + row * row_height };
|
||||||
// let row_height = height / max_row;
|
|
||||||
// let min_thumb_height =
|
|
||||||
// style.min_height_factor * cx.font_cache.line_height(self.style.text.font_size);
|
|
||||||
// let thumb_height = (row_range.end - row_range.start) * row_height;
|
|
||||||
// if thumb_height < min_thumb_height {
|
|
||||||
// first_row_y_offset = (min_thumb_height - thumb_height) / 2.0;
|
|
||||||
// height -= min_thumb_height - thumb_height;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let y_for_row = |row: f32| -> f32 { top + first_row_y_offset + row * row_height };
|
let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
|
||||||
|
let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
|
||||||
|
let track_bounds = Bounds::from_corners(point(left, top), point(right, bottom));
|
||||||
|
let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
|
||||||
|
|
||||||
// let thumb_top = y_for_row(row_range.start) - first_row_y_offset;
|
if layout.show_scrollbars {
|
||||||
// let thumb_bottom = y_for_row(row_range.end) + first_row_y_offset;
|
cx.paint_quad(
|
||||||
// let track_bounds = Bounds::<Pixels>::from_points(point(left, top), point(right, bottom));
|
track_bounds,
|
||||||
// let thumb_bounds = Bounds::<Pixels>::from_points(point(left, thumb_top), point(right, thumb_bottom));
|
Corners::default(),
|
||||||
|
gpui::blue(), // todo!("style.track.background_color")
|
||||||
|
Edges::default(), // todo!("style.track.border")
|
||||||
|
transparent_black(), // todo!("style.track.border")
|
||||||
|
);
|
||||||
|
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
|
||||||
|
if layout.is_singleton && scrollbar_settings.selections {
|
||||||
|
let start_anchor = Anchor::min();
|
||||||
|
let end_anchor = Anchor::max();
|
||||||
|
let background_ranges = self
|
||||||
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
|
||||||
|
start_anchor..end_anchor,
|
||||||
|
&layout.position_map.snapshot,
|
||||||
|
50000,
|
||||||
|
);
|
||||||
|
for range in background_ranges {
|
||||||
|
let start_y = y_for_row(range.start().row() as f32);
|
||||||
|
let mut end_y = y_for_row(range.end().row() as f32);
|
||||||
|
if end_y - start_y < px(1.) {
|
||||||
|
end_y = start_y + px(1.);
|
||||||
|
}
|
||||||
|
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
|
||||||
|
cx.paint_quad(
|
||||||
|
bounds,
|
||||||
|
Corners::default(),
|
||||||
|
gpui::yellow(), // todo!("theme.editor.scrollbar")
|
||||||
|
Edges {
|
||||||
|
top: Pixels::ZERO,
|
||||||
|
right: px(1.),
|
||||||
|
bottom: Pixels::ZERO,
|
||||||
|
left: px(1.),
|
||||||
|
},
|
||||||
|
gpui::green(), // todo!("style.thumb.border.color")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if layout.show_scrollbars {
|
if layout.is_singleton && scrollbar_settings.git_diff {
|
||||||
// cx.paint_quad(Quad {
|
for hunk in layout
|
||||||
// bounds: track_bounds,
|
.position_map
|
||||||
// border: style.track.border.into(),
|
.snapshot
|
||||||
// background: style.track.background_color,
|
.buffer_snapshot
|
||||||
// ..Default::default()
|
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
||||||
// });
|
{
|
||||||
// let scrollbar_settings = settings::get::<EditorSettings>(cx).scrollbar;
|
let start_display = Point::new(hunk.buffer_range.start, 0)
|
||||||
// let theme = theme::current(cx);
|
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||||
// let scrollbar_theme = &theme.editor.scrollbar;
|
let end_display = Point::new(hunk.buffer_range.end, 0)
|
||||||
// if layout.is_singleton && scrollbar_settings.selections {
|
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||||
// let start_anchor = Anchor::min();
|
let start_y = y_for_row(start_display.row() as f32);
|
||||||
// let end_anchor = Anchor::max;
|
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||||
// let color = scrollbar_theme.selections;
|
y_for_row((end_display.row() + 1) as f32)
|
||||||
// let border = Border {
|
} else {
|
||||||
// width: 1.,
|
y_for_row((end_display.row()) as f32)
|
||||||
// color: style.thumb.border.color,
|
};
|
||||||
// overlay: false,
|
|
||||||
// top: false,
|
|
||||||
// right: true,
|
|
||||||
// bottom: false,
|
|
||||||
// left: true,
|
|
||||||
// };
|
|
||||||
// let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
|
|
||||||
// let start_y = y_for_row(start.row() as f32);
|
|
||||||
// let mut end_y = y_for_row(end.row() as f32);
|
|
||||||
// if end_y - start_y < 1. {
|
|
||||||
// end_y = start_y + 1.;
|
|
||||||
// }
|
|
||||||
// let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
|
|
||||||
|
|
||||||
// cx.paint_quad(Quad {
|
if end_y - start_y < px(1.) {
|
||||||
// bounds,
|
end_y = start_y + px(1.);
|
||||||
// background: Some(color),
|
}
|
||||||
// border: border.into(),
|
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
|
||||||
// corner_radii: style.thumb.corner_radii.into(),
|
|
||||||
// })
|
|
||||||
// };
|
|
||||||
// let background_ranges = editor
|
|
||||||
// .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
|
|
||||||
// start_anchor..end_anchor,
|
|
||||||
// &layout.position_map.snapshot,
|
|
||||||
// 50000,
|
|
||||||
// );
|
|
||||||
// for row in background_ranges {
|
|
||||||
// let start = row.start();
|
|
||||||
// let end = row.end();
|
|
||||||
// push_region(*start, *end);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if layout.is_singleton && scrollbar_settings.git_diff {
|
let color = match hunk.status() {
|
||||||
// let diff_style = scrollbar_theme.git.clone();
|
DiffHunkStatus::Added => gpui::green(), // todo!("use the right color")
|
||||||
// for hunk in layout
|
DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the right color")
|
||||||
// .position_map
|
DiffHunkStatus::Removed => gpui::red(), // todo!("use the right color")
|
||||||
// .snapshot
|
};
|
||||||
// .buffer_snapshot
|
cx.paint_quad(
|
||||||
// .git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
bounds,
|
||||||
// {
|
Corners::default(),
|
||||||
// let start_display = Point::new(hunk.buffer_range.start, 0)
|
color,
|
||||||
// .to_display_point(&layout.position_map.snapshot.display_snapshot);
|
Edges {
|
||||||
// let end_display = Point::new(hunk.buffer_range.end, 0)
|
top: Pixels::ZERO,
|
||||||
// .to_display_point(&layout.position_map.snapshot.display_snapshot);
|
right: px(1.),
|
||||||
// let start_y = y_for_row(start_display.row() as f32);
|
bottom: Pixels::ZERO,
|
||||||
// let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
left: px(1.),
|
||||||
// y_for_row((end_display.row() + 1) as f32)
|
},
|
||||||
// } else {
|
gpui::green(), // todo!("style.thumb.border.color")
|
||||||
// y_for_row((end_display.row()) as f32)
|
);
|
||||||
// };
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if end_y - start_y < 1. {
|
cx.paint_quad(
|
||||||
// end_y = start_y + 1.;
|
thumb_bounds,
|
||||||
// }
|
Corners::default(),
|
||||||
// let bounds = Bounds::<Pixels>::from_points(point(left, start_y), point(right, end_y));
|
gpui::black(), // todo!("style.thumb.background_color")
|
||||||
|
Edges {
|
||||||
|
top: Pixels::ZERO,
|
||||||
|
right: px(1.),
|
||||||
|
bottom: Pixels::ZERO,
|
||||||
|
left: px(1.),
|
||||||
|
},
|
||||||
|
gpui::green(), // todo!("style.thumb.border.color")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// let color = match hunk.status() {
|
let mouse_position = cx.mouse_position();
|
||||||
// DiffHunkStatus::Added => diff_style.inserted,
|
if track_bounds.contains_point(&mouse_position) {
|
||||||
// DiffHunkStatus::Modified => diff_style.modified,
|
cx.set_cursor_style(CursorStyle::Arrow);
|
||||||
// DiffHunkStatus::Removed => diff_style.deleted,
|
}
|
||||||
// };
|
|
||||||
|
|
||||||
// let border = Border {
|
cx.on_mouse_event({
|
||||||
// width: 1.,
|
let editor = self.editor.clone();
|
||||||
// color: style.thumb.border.color,
|
move |event: &MouseMoveEvent, phase, cx| {
|
||||||
// overlay: false,
|
if phase == DispatchPhase::Capture {
|
||||||
// top: false,
|
return;
|
||||||
// right: true,
|
}
|
||||||
// bottom: false,
|
|
||||||
// left: true,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// cx.paint_quad(Quad {
|
editor.update(cx, |editor, cx| {
|
||||||
// bounds,
|
if event.pressed_button == Some(MouseButton::Left)
|
||||||
// background: Some(color),
|
&& editor.scroll_manager.is_dragging_scrollbar()
|
||||||
// border: border.into(),
|
{
|
||||||
// corner_radii: style.thumb.corner_radii.into(),
|
let y = mouse_position.y;
|
||||||
// })
|
let new_y = event.position.y;
|
||||||
// }
|
if thumb_top < y && y < thumb_bottom {
|
||||||
// }
|
let mut position = editor.scroll_position(cx);
|
||||||
|
position.y += (new_y - y) * (max_row as f32) / height;
|
||||||
|
if position.y < 0.0 {
|
||||||
|
position.y = 0.0;
|
||||||
|
}
|
||||||
|
editor.set_scroll_position(position, cx);
|
||||||
|
}
|
||||||
|
cx.stop_propagation();
|
||||||
|
} else {
|
||||||
|
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||||
|
if track_bounds.contains_point(&event.position) {
|
||||||
|
editor.scroll_manager.show_scrollbar(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// cx.paint_quad(Quad {
|
if self.editor.read(cx).scroll_manager.is_dragging_scrollbar() {
|
||||||
// bounds: thumb_bounds,
|
cx.on_mouse_event({
|
||||||
// border: style.thumb.border.into(),
|
let editor = self.editor.clone();
|
||||||
// background: style.thumb.background_color,
|
move |event: &MouseUpEvent, phase, cx| {
|
||||||
// corner_radii: style.thumb.corner_radii.into(),
|
editor.update(cx, |editor, cx| {
|
||||||
// });
|
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||||
// }
|
cx.stop_propagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cx.on_mouse_event({
|
||||||
|
let editor = self.editor.clone();
|
||||||
|
move |event: &MouseDownEvent, phase, cx| {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
if track_bounds.contains_point(&event.position) {
|
||||||
|
editor.scroll_manager.set_is_dragging_scrollbar(true, cx);
|
||||||
|
|
||||||
// cx.scene().push_cursor_region(CursorRegion {
|
let y = event.position.y;
|
||||||
// bounds: track_bounds,
|
if y < thumb_top || thumb_bottom < y {
|
||||||
// style: CursorStyle::Arrow,
|
let center_row =
|
||||||
// });
|
((y - top) * max_row as f32 / height).round() as u32;
|
||||||
// let region_id = cx.view_id();
|
let top_row = center_row
|
||||||
// cx.scene().push_mouse_region(
|
.saturating_sub((row_range.end - row_range.start) as u32 / 2);
|
||||||
// MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
|
let mut position = editor.scroll_position(cx);
|
||||||
// .on_move(move |event, editor: &mut Editor, cx| {
|
position.y = top_row as f32;
|
||||||
// if event.pressed_button.is_none() {
|
editor.set_scroll_position(position, cx);
|
||||||
// editor.scroll_manager.show_scrollbar(cx);
|
} else {
|
||||||
// }
|
editor.scroll_manager.show_scrollbar(cx);
|
||||||
// })
|
}
|
||||||
// .on_down(MouseButton::Left, {
|
|
||||||
// let row_range = row_range.clone();
|
|
||||||
// move |event, editor: &mut Editor, cx| {
|
|
||||||
// let y = event.position.y;
|
|
||||||
// if y < thumb_top || thumb_bottom < y {
|
|
||||||
// let center_row = ((y - top) * max_row as f32 / height).round() as u32;
|
|
||||||
// let top_row = center_row
|
|
||||||
// .saturating_sub((row_range.end - row_range.start) as u32 / 2);
|
|
||||||
// let mut position = editor.scroll_position(cx);
|
|
||||||
// position.set_y(top_row as f32);
|
|
||||||
// editor.set_scroll_position(position, cx);
|
|
||||||
// } else {
|
|
||||||
// editor.scroll_manager.show_scrollbar(cx);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .on_drag(MouseButton::Left, {
|
|
||||||
// move |event, editor: &mut Editor, cx| {
|
|
||||||
// if event.end {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let y = event.prev_mouse_position.y;
|
cx.stop_propagation();
|
||||||
// let new_y = event.position.y;
|
}
|
||||||
// if thumb_top < y && y < thumb_bottom {
|
});
|
||||||
// let mut position = editor.scroll_position(cx);
|
}
|
||||||
// position.set_y(position.y + (new_y - y) * (max_row as f32) / height);
|
});
|
||||||
// if position.y < 0.0 {
|
}
|
||||||
// position.set_y(0.);
|
}
|
||||||
// }
|
|
||||||
// editor.set_scroll_position(position, cx);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn paint_highlighted_range(
|
fn paint_highlighted_range(
|
||||||
|
@ -2452,12 +2471,9 @@ impl EditorElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let handled = editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
|
Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
|
||||||
});
|
});
|
||||||
if handled {
|
|
||||||
cx.stop_propagation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2471,7 +2487,7 @@ impl EditorElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let handled = match event.button {
|
match event.button {
|
||||||
MouseButton::Left => editor.update(cx, |editor, cx| {
|
MouseButton::Left => editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_left_down(
|
Self::mouse_left_down(
|
||||||
editor,
|
editor,
|
||||||
|
@ -2486,12 +2502,8 @@ impl EditorElement {
|
||||||
MouseButton::Right => editor.update(cx, |editor, cx| {
|
MouseButton::Right => editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_right_down(editor, event, &position_map, text_bounds, cx)
|
Self::mouse_right_down(editor, event, &position_map, text_bounds, cx)
|
||||||
}),
|
}),
|
||||||
_ => false,
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
if handled {
|
|
||||||
cx.stop_propagation()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2501,7 +2513,7 @@ impl EditorElement {
|
||||||
let stacking_order = cx.stacking_order().clone();
|
let stacking_order = cx.stacking_order().clone();
|
||||||
|
|
||||||
move |event: &MouseUpEvent, phase, cx| {
|
move |event: &MouseUpEvent, phase, cx| {
|
||||||
let handled = editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_up(
|
Self::mouse_up(
|
||||||
editor,
|
editor,
|
||||||
event,
|
event,
|
||||||
|
@ -2511,10 +2523,6 @@ impl EditorElement {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if handled {
|
|
||||||
cx.stop_propagation()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
|
@ -2527,7 +2535,7 @@ impl EditorElement {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop_propogating = editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
Self::mouse_moved(
|
Self::mouse_moved(
|
||||||
editor,
|
editor,
|
||||||
event,
|
event,
|
||||||
|
@ -2538,10 +2546,6 @@ impl EditorElement {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if stop_propogating {
|
|
||||||
cx.stop_propagation()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2840,9 +2844,11 @@ impl Element for EditorElement {
|
||||||
cx.with_z_index(1, |cx| {
|
cx.with_z_index(1, |cx| {
|
||||||
cx.with_element_id(Some("editor_blocks"), |cx| {
|
cx.with_element_id(Some("editor_blocks"), |cx| {
|
||||||
self.paint_blocks(bounds, &mut layout, cx);
|
self.paint_blocks(bounds, &mut layout, cx);
|
||||||
})
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -2944,7 +2950,7 @@ impl PositionMap {
|
||||||
) -> PointForPosition {
|
) -> PointForPosition {
|
||||||
let scroll_position = self.snapshot.scroll_position();
|
let scroll_position = self.snapshot.scroll_position();
|
||||||
let position = position - text_bounds.origin;
|
let position = position - text_bounds.origin;
|
||||||
let y = position.y.max(px(0.)).min(self.size.width);
|
let y = position.y.max(px(0.)).min(self.size.height);
|
||||||
let x = position.x + (scroll_position.x * self.em_width);
|
let x = position.x + (scroll_position.x * self.em_width);
|
||||||
let row = (f32::from(y / self.line_height) + scroll_position.y) as u32;
|
let row = (f32::from(y / self.line_height) + scroll_position.y) as u32;
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
|
||||||
pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
|
pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.);
|
||||||
pub const HOVER_POPOVER_GAP: Pixels = px(10.);
|
pub const HOVER_POPOVER_GAP: Pixels = px(10.);
|
||||||
|
|
||||||
actions!(Hover);
|
actions!(editor, [Hover]);
|
||||||
|
|
||||||
/// Bindable action which uses the most recent selection head to trigger a hover
|
/// Bindable action which uses the most recent selection head to trigger a hover
|
||||||
pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
|
pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
|
||||||
|
|
119
crates/editor2/src/rust_analyzer_ext.rs
Normal file
119
crates/editor2/src/rust_analyzer_ext.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use gpui::{Context, Model, View, ViewContext, VisualContext, WindowContext};
|
||||||
|
use language::Language;
|
||||||
|
use multi_buffer::MultiBuffer;
|
||||||
|
use project::lsp_ext_command::ExpandMacro;
|
||||||
|
use text::ToPointUtf16;
|
||||||
|
|
||||||
|
use crate::{element::register_action, Editor, ExpandMacroRecursively};
|
||||||
|
|
||||||
|
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
||||||
|
let is_rust_related = editor.update(cx, |editor, cx| {
|
||||||
|
editor
|
||||||
|
.buffer()
|
||||||
|
.read(cx)
|
||||||
|
.all_buffers()
|
||||||
|
.iter()
|
||||||
|
.any(|b| match b.read(cx).language() {
|
||||||
|
Some(l) => is_rust_language(l),
|
||||||
|
None => false,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_rust_related {
|
||||||
|
register_action(editor, cx, expand_macro_recursively);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_macro_recursively(
|
||||||
|
editor: &mut Editor,
|
||||||
|
_: &ExpandMacroRecursively,
|
||||||
|
cx: &mut ViewContext<'_, Editor>,
|
||||||
|
) {
|
||||||
|
if editor.selections.count() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(project) = &editor.project else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(workspace) = editor.workspace() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let multibuffer = editor.buffer().read(cx);
|
||||||
|
|
||||||
|
let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
|
||||||
|
.selections
|
||||||
|
.disjoint_anchors()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|selection| selection.start == selection.end)
|
||||||
|
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
|
||||||
|
.filter_map(|(buffer_id, trigger_anchor)| {
|
||||||
|
let buffer = multibuffer.buffer(buffer_id)?;
|
||||||
|
let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
||||||
|
if !is_rust_language(&rust_language) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((trigger_anchor, rust_language, buffer))
|
||||||
|
})
|
||||||
|
.find_map(|(trigger_anchor, rust_language, buffer)| {
|
||||||
|
project
|
||||||
|
.read(cx)
|
||||||
|
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|(adapter, server)| {
|
||||||
|
if adapter.name.0.as_ref() == "rust-analyzer" {
|
||||||
|
Some((
|
||||||
|
trigger_anchor,
|
||||||
|
Arc::clone(&rust_language),
|
||||||
|
server.server_id(),
|
||||||
|
buffer.clone(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let project = project.clone();
|
||||||
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||||
|
let expand_macro_task = project.update(cx, |project, cx| {
|
||||||
|
project.request_lsp(
|
||||||
|
buffer,
|
||||||
|
project::LanguageServerToQuery::Other(server_to_query),
|
||||||
|
ExpandMacro { position },
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
cx.spawn(|editor, mut cx| async move {
|
||||||
|
let macro_expansion = expand_macro_task.await.context("expand macro")?;
|
||||||
|
if macro_expansion.is_empty() {
|
||||||
|
log::info!("Empty macro expansion for position {position:?}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = project.update(&mut cx, |project, cx| {
|
||||||
|
project.create_buffer(¯o_expansion.expansion, Some(rust_language), cx)
|
||||||
|
})??;
|
||||||
|
workspace.update(&mut cx, |workspace, cx| {
|
||||||
|
let buffer = cx.build_model(|cx| {
|
||||||
|
MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
|
||||||
|
});
|
||||||
|
workspace.add_item(
|
||||||
|
Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_rust_language(language: &Language) -> bool {
|
||||||
|
language.name().as_ref() == "Rust"
|
||||||
|
}
|
|
@ -136,6 +136,7 @@ pub struct ScrollManager {
|
||||||
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
|
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
|
||||||
show_scrollbars: bool,
|
show_scrollbars: bool,
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
|
dragging_scrollbar: bool,
|
||||||
visible_line_count: Option<f32>,
|
visible_line_count: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +149,7 @@ impl ScrollManager {
|
||||||
autoscroll_request: None,
|
autoscroll_request: None,
|
||||||
show_scrollbars: true,
|
show_scrollbars: true,
|
||||||
hide_scrollbar_task: None,
|
hide_scrollbar_task: None,
|
||||||
|
dragging_scrollbar: false,
|
||||||
last_autoscroll: None,
|
last_autoscroll: None,
|
||||||
visible_line_count: None,
|
visible_line_count: None,
|
||||||
}
|
}
|
||||||
|
@ -278,6 +280,17 @@ impl ScrollManager {
|
||||||
self.autoscroll_request.is_some()
|
self.autoscroll_request.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dragging_scrollbar(&self) -> bool {
|
||||||
|
self.dragging_scrollbar
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
|
||||||
|
if dragging != self.dragging_scrollbar {
|
||||||
|
self.dragging_scrollbar = dragging;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
||||||
if max < self.anchor.offset.x {
|
if max < self.anchor.offset.x {
|
||||||
self.anchor.offset.x = max;
|
self.anchor.offset.x = max;
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl Render for DeployFeedbackButton {
|
||||||
IconButton::new("give-feedback", Icon::Envelope)
|
IconButton::new("give-feedback", Icon::Envelope)
|
||||||
.style(ui::ButtonStyle::Subtle)
|
.style(ui::ButtonStyle::Subtle)
|
||||||
.selected(is_open)
|
.selected(is_open)
|
||||||
.tooltip(|cx| Tooltip::text("Give Feedback", cx))
|
.tooltip(|cx| Tooltip::text("Share Feedback", cx))
|
||||||
.on_click(|_, cx| {
|
.on_click(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(GiveFeedback));
|
cx.dispatch_action(Box::new(GiveFeedback));
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,15 +5,18 @@ use workspace::Workspace;
|
||||||
pub mod deploy_feedback_button;
|
pub mod deploy_feedback_button;
|
||||||
pub mod feedback_modal;
|
pub mod feedback_modal;
|
||||||
|
|
||||||
actions!(GiveFeedback, SubmitFeedback);
|
actions!(feedback, [GiveFeedback, SubmitFeedback]);
|
||||||
|
|
||||||
mod system_specs;
|
mod system_specs;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
CopySystemSpecsIntoClipboard,
|
zed,
|
||||||
FileBugReport,
|
[
|
||||||
RequestFeature,
|
CopySystemSpecsIntoClipboard,
|
||||||
OpenZedCommunityRepo
|
FileBugReport,
|
||||||
|
RequestFeature,
|
||||||
|
OpenZedCommunityRepo
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
|
|
@ -1,28 +1,38 @@
|
||||||
use std::{ops::RangeInclusive, sync::Arc};
|
use std::{ops::RangeInclusive, sync::Arc};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::{anyhow, bail};
|
||||||
use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::{Editor, EditorEvent};
|
use editor::{Editor, EditorEvent};
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, red, rems, serde_json, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
|
div, rems, serde_json, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
|
||||||
FocusableView, Model, PromptLevel, Render, Task, View, ViewContext,
|
Model, PromptLevel, Render, Task, View, ViewContext,
|
||||||
};
|
};
|
||||||
use isahc::Request;
|
use isahc::Request;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use ui::{prelude::*, Button, ButtonStyle, Label, Tooltip};
|
use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedCommunityRepo};
|
use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedCommunityRepo};
|
||||||
|
|
||||||
|
// For UI testing purposes
|
||||||
|
const SEND_SUCCESS_IN_DEV_MODE: bool = true;
|
||||||
|
|
||||||
|
// Temporary, until tests are in place
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const DEV_MODE: bool = true;
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
const DEV_MODE: bool = false;
|
||||||
|
|
||||||
const DATABASE_KEY_NAME: &str = "email_address";
|
const DATABASE_KEY_NAME: &str = "email_address";
|
||||||
const EMAIL_REGEX: &str = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
|
const EMAIL_REGEX: &str = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
|
||||||
const FEEDBACK_CHAR_LIMIT: RangeInclusive<usize> = 10..=5000;
|
const FEEDBACK_CHAR_LIMIT: RangeInclusive<i32> = 10..=5000;
|
||||||
const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
|
const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
|
||||||
"Feedback failed to submit, see error log for details.";
|
"Feedback failed to submit, see error log for details.";
|
||||||
|
|
||||||
|
@ -41,8 +51,9 @@ pub struct FeedbackModal {
|
||||||
system_specs: SystemSpecs,
|
system_specs: SystemSpecs,
|
||||||
feedback_editor: View<Editor>,
|
feedback_editor: View<Editor>,
|
||||||
email_address_editor: View<Editor>,
|
email_address_editor: View<Editor>,
|
||||||
character_count: usize,
|
awaiting_submission: bool,
|
||||||
pending_submission: bool,
|
user_submitted: bool,
|
||||||
|
character_count: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusableView for FeedbackModal {
|
impl FocusableView for FeedbackModal {
|
||||||
|
@ -52,6 +63,25 @@ impl FocusableView for FeedbackModal {
|
||||||
}
|
}
|
||||||
impl EventEmitter<DismissEvent> for FeedbackModal {}
|
impl EventEmitter<DismissEvent> for FeedbackModal {}
|
||||||
|
|
||||||
|
impl ModalView for FeedbackModal {
|
||||||
|
fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> {
|
||||||
|
if self.user_submitted {
|
||||||
|
self.set_user_submitted(false, cx);
|
||||||
|
return cx.spawn(|_, _| async { true });
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_feedback = self.feedback_editor.read(cx).text_option(cx).is_some();
|
||||||
|
|
||||||
|
if !has_feedback {
|
||||||
|
return cx.spawn(|_, _| async { true });
|
||||||
|
}
|
||||||
|
|
||||||
|
let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", &["Yes", "No"]);
|
||||||
|
|
||||||
|
cx.spawn(|_, _| async { answer.await.ok() == Some(0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FeedbackModal {
|
impl FeedbackModal {
|
||||||
pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||||
let _handle = cx.view().downgrade();
|
let _handle = cx.view().downgrade();
|
||||||
|
@ -104,6 +134,11 @@ impl FeedbackModal {
|
||||||
|
|
||||||
let feedback_editor = cx.build_view(|cx| {
|
let feedback_editor = cx.build_view(|cx| {
|
||||||
let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx);
|
let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx);
|
||||||
|
editor.set_placeholder_text(
|
||||||
|
"You can use markdown to organize your feedback wiht add code and links, or organize feedback.",
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
// editor.set_show_gutter(false, cx);
|
||||||
editor.set_vertical_scroll_margin(5, cx);
|
editor.set_vertical_scroll_margin(5, cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
@ -119,7 +154,7 @@ impl FeedbackModal {
|
||||||
.as_singleton()
|
.as_singleton()
|
||||||
.expect("Feedback editor is never a multi-buffer")
|
.expect("Feedback editor is never a multi-buffer")
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.len();
|
.len() as i32;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -131,7 +166,8 @@ impl FeedbackModal {
|
||||||
system_specs: system_specs.clone(),
|
system_specs: system_specs.clone(),
|
||||||
feedback_editor,
|
feedback_editor,
|
||||||
email_address_editor,
|
email_address_editor,
|
||||||
pending_submission: false,
|
awaiting_submission: false,
|
||||||
|
user_submitted: false,
|
||||||
character_count: 0,
|
character_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,37 +199,53 @@ impl FeedbackModal {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |feedback_editor, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
feedback_editor.set_pending_submission(true, cx);
|
this.set_awaiting_submission(true, cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
if let Err(error) =
|
let res =
|
||||||
FeedbackModal::submit_feedback(&feedback_text, email, client, specs).await
|
FeedbackModal::submit_feedback(&feedback_text, email, client, specs).await;
|
||||||
{
|
|
||||||
log::error!("{}", error);
|
match res {
|
||||||
this.update(&mut cx, |feedback_editor, cx| {
|
Ok(_) => {
|
||||||
let prompt = cx.prompt(
|
this.update(&mut cx, |this, cx| {
|
||||||
PromptLevel::Critical,
|
this.set_user_submitted(true, cx);
|
||||||
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
cx.emit(DismissEvent)
|
||||||
&["OK"],
|
|
||||||
);
|
|
||||||
cx.spawn(|_, _cx| async move {
|
|
||||||
prompt.await.ok();
|
|
||||||
})
|
})
|
||||||
.detach();
|
.ok();
|
||||||
feedback_editor.set_pending_submission(false, cx);
|
}
|
||||||
})
|
Err(error) => {
|
||||||
.log_err();
|
log::error!("{}", error);
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let prompt = cx.prompt(
|
||||||
|
PromptLevel::Critical,
|
||||||
|
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
||||||
|
&["OK"],
|
||||||
|
);
|
||||||
|
cx.spawn(|_, _cx| async move {
|
||||||
|
prompt.await.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
this.set_awaiting_submission(false, cx);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_pending_submission(&mut self, pending_submission: bool, cx: &mut ViewContext<Self>) {
|
fn set_awaiting_submission(&mut self, awaiting_submission: bool, cx: &mut ViewContext<Self>) {
|
||||||
self.pending_submission = pending_submission;
|
self.awaiting_submission = awaiting_submission;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_user_submitted(&mut self, user_submitted: bool, cx: &mut ViewContext<Self>) {
|
||||||
|
self.user_submitted = user_submitted;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +255,14 @@ impl FeedbackModal {
|
||||||
zed_client: Arc<Client>,
|
zed_client: Arc<Client>,
|
||||||
system_specs: SystemSpecs,
|
system_specs: SystemSpecs,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
if DEV_MODE {
|
||||||
|
if SEND_SUCCESS_IN_DEV_MODE {
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("Error submitting feedback"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL);
|
let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL);
|
||||||
let telemetry = zed_client.telemetry();
|
let telemetry = zed_client.telemetry();
|
||||||
let metrics_id = telemetry.metrics_id();
|
let metrics_id = telemetry.metrics_id();
|
||||||
|
@ -233,11 +293,8 @@ impl FeedbackModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Escape button calls dismiss
|
// TODO: Escape button calls dismiss
|
||||||
// TODO: Should do same as hitting cancel / clicking outside of modal
|
|
||||||
// Close immediately if no text in field
|
|
||||||
// Ask to close if text in the field
|
|
||||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,132 +308,128 @@ impl Render for FeedbackModal {
|
||||||
};
|
};
|
||||||
|
|
||||||
let valid_character_count = FEEDBACK_CHAR_LIMIT.contains(&self.character_count);
|
let valid_character_count = FEEDBACK_CHAR_LIMIT.contains(&self.character_count);
|
||||||
let characters_remaining =
|
|
||||||
if valid_character_count || self.character_count > *FEEDBACK_CHAR_LIMIT.end() {
|
|
||||||
*FEEDBACK_CHAR_LIMIT.end() as i32 - self.character_count as i32
|
|
||||||
} else {
|
|
||||||
self.character_count as i32 - *FEEDBACK_CHAR_LIMIT.start() as i32
|
|
||||||
};
|
|
||||||
|
|
||||||
let allow_submission =
|
let allow_submission =
|
||||||
valid_character_count && valid_email_address && !self.pending_submission;
|
valid_character_count && valid_email_address && !self.awaiting_submission;
|
||||||
|
|
||||||
let has_feedback = self.feedback_editor.read(cx).text_option(cx).is_some();
|
let submit_button_text = if self.awaiting_submission {
|
||||||
|
"Submitting..."
|
||||||
let submit_button_text = if self.pending_submission {
|
|
||||||
"Sending..."
|
|
||||||
} else {
|
} else {
|
||||||
"Send Feedback"
|
"Submit"
|
||||||
};
|
};
|
||||||
let dismiss = cx.listener(|_, _, cx| {
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
});
|
|
||||||
// TODO: get the "are you sure you want to dismiss?" prompt here working
|
|
||||||
let dismiss_prompt = cx.listener(|_, _, _| {
|
|
||||||
// let answer = cx.prompt(PromptLevel::Info, "Exit feedback?", &["Yes", "No"]);
|
|
||||||
// cx.spawn(|_, _| async move {
|
|
||||||
// let answer = answer.await.ok();
|
|
||||||
// if answer == Some(0) {
|
|
||||||
// cx.emit(DismissEvent);
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .detach();
|
|
||||||
});
|
|
||||||
let open_community_repo =
|
let open_community_repo =
|
||||||
cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo)));
|
cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo)));
|
||||||
|
|
||||||
// TODO: Nate UI pass
|
// Moved this here because providing it inline breaks rustfmt
|
||||||
|
let provide_an_email_address =
|
||||||
|
"Provide an email address if you want us to be able to reply.";
|
||||||
|
|
||||||
v_stack()
|
v_stack()
|
||||||
.elevation_3(cx)
|
.elevation_3(cx)
|
||||||
.key_context("GiveFeedback")
|
.key_context("GiveFeedback")
|
||||||
.on_action(cx.listener(Self::cancel))
|
.on_action(cx.listener(Self::cancel))
|
||||||
.min_w(rems(40.))
|
.min_w(rems(40.))
|
||||||
.max_w(rems(96.))
|
.max_w(rems(96.))
|
||||||
.border()
|
.h(rems(32.))
|
||||||
.border_color(red())
|
.p_4()
|
||||||
.h(rems(40.))
|
.gap_4()
|
||||||
.p_2()
|
.child(v_stack().child(
|
||||||
.gap_2()
|
// TODO: Add Headline component to `ui2`
|
||||||
|
div().text_xl().child("Share Feedback"),
|
||||||
|
))
|
||||||
.child(
|
.child(
|
||||||
v_stack().child(
|
Label::new(if self.character_count < *FEEDBACK_CHAR_LIMIT.start() {
|
||||||
div()
|
format!(
|
||||||
.size_full()
|
"Feedback must be at least {} characters.",
|
||||||
.child(Label::new("Give Feedback").color(Color::Default))
|
FEEDBACK_CHAR_LIMIT.start()
|
||||||
.child(Label::new("This editor supports markdown").color(Color::Muted)),
|
)
|
||||||
),
|
} else {
|
||||||
|
format!(
|
||||||
|
"Characters: {}",
|
||||||
|
*FEEDBACK_CHAR_LIMIT.end() - self.character_count
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.color(if valid_character_count {
|
||||||
|
Color::Success
|
||||||
|
} else {
|
||||||
|
Color::Error
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.p_2()
|
||||||
.border()
|
.border()
|
||||||
|
.rounded_md()
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.child(self.feedback_editor.clone()),
|
.child(self.feedback_editor.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
div().child(
|
|
||||||
Label::new(format!(
|
|
||||||
"Characters: {}",
|
|
||||||
characters_remaining
|
|
||||||
))
|
|
||||||
.when_else(
|
|
||||||
valid_character_count,
|
|
||||||
|this| this.color(Color::Success),
|
|
||||||
|this| this.color(Color::Error)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.child(
|
||||||
.border()
|
h_stack()
|
||||||
.border_color(cx.theme().colors().border)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.child(self.email_address_editor.clone())
|
.p_2()
|
||||||
)
|
.border()
|
||||||
.child(
|
.rounded_md()
|
||||||
h_stack()
|
.border_color(cx.theme().colors().border)
|
||||||
.justify_between()
|
.child(self.email_address_editor.clone()),
|
||||||
.gap_1()
|
|
||||||
.child(Button::new("community_repo", "Community Repo")
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.color(Color::Muted)
|
|
||||||
.on_click(open_community_repo)
|
|
||||||
)
|
)
|
||||||
.child(h_stack().justify_between().gap_1()
|
.child(
|
||||||
.child(
|
h_stack()
|
||||||
Button::new("cancel_feedback", "Cancel")
|
.justify_between()
|
||||||
.style(ButtonStyle::Subtle)
|
.gap_1()
|
||||||
.color(Color::Muted)
|
.child(
|
||||||
// TODO: replicate this logic when clicking outside the modal
|
Button::new("community_repo", "Community Repo")
|
||||||
// TODO: Will require somehow overriding the modal dismal default behavior
|
.style(ButtonStyle::Transparent)
|
||||||
.when_else(
|
.icon(Icon::ExternalLink)
|
||||||
has_feedback,
|
.icon_position(IconPosition::End)
|
||||||
|this| this.on_click(dismiss_prompt),
|
.icon_size(IconSize::Small)
|
||||||
|this| this.on_click(dismiss)
|
.on_click(open_community_repo),
|
||||||
)
|
)
|
||||||
)
|
.child(
|
||||||
.child(
|
h_stack()
|
||||||
Button::new("send_feedback", submit_button_text)
|
.gap_1()
|
||||||
.color(Color::Accent)
|
.child(
|
||||||
.style(ButtonStyle::Filled)
|
Button::new("cancel_feedback", "Cancel")
|
||||||
// TODO: Ensure that while submitting, "Sending..." is shown and disable the button
|
.style(ButtonStyle::Subtle)
|
||||||
// TODO: If submit errors: show popup with error, don't close modal, set text back to "Send Feedback", and re-enable button
|
.color(Color::Muted)
|
||||||
// TODO: If submit is successful, close the modal
|
.on_click(cx.listener(move |_, _, cx| {
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let _ = this.submit(cx);
|
this.update(&mut cx, |_, cx| {
|
||||||
}))
|
cx.emit(DismissEvent)
|
||||||
.tooltip(|cx| {
|
})
|
||||||
Tooltip::with_meta(
|
.ok();
|
||||||
"Submit feedback to the Zed team.",
|
})
|
||||||
None,
|
.detach();
|
||||||
"Provide an email address if you want us to be able to reply.",
|
})),
|
||||||
cx,
|
|
||||||
)
|
)
|
||||||
})
|
.child(
|
||||||
.when(!allow_submission, |this| this.disabled(true))
|
Button::new("send_feedback", submit_button_text)
|
||||||
),
|
.color(Color::Accent)
|
||||||
)
|
.style(ButtonStyle::Filled)
|
||||||
|
// TODO: Ensure that while submitting, "Sending..." is shown and disable the button
|
||||||
|
// TODO: If submit errors: show popup with error, don't close modal, set text back to "Submit", and re-enable button
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.submit(cx).detach();
|
||||||
|
}))
|
||||||
|
.tooltip(move |cx| {
|
||||||
|
Tooltip::with_meta(
|
||||||
|
"Submit feedback to the Zed team.",
|
||||||
|
None,
|
||||||
|
provide_an_email_address,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(!allow_submission, |this| this.disabled(true)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Maybe store email address whenever the modal is closed, versus just on submit, so users can remove it if they want without submitting
|
||||||
|
// TODO: Testing of various button states, dismissal prompts, etc.
|
||||||
|
|
|
@ -17,9 +17,11 @@ use std::{
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use ui::{prelude::*, HighlightedLabel, ListItem};
|
use ui::{prelude::*, HighlightedLabel, ListItem};
|
||||||
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
||||||
use workspace::Workspace;
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
actions!(Toggle);
|
actions!(file_finder, [Toggle]);
|
||||||
|
|
||||||
|
impl ModalView for FileFinder {}
|
||||||
|
|
||||||
pub struct FileFinder {
|
pub struct FileFinder {
|
||||||
picker: View<Picker<FileFinderDelegate>>,
|
picker: View<Picker<FileFinderDelegate>>,
|
||||||
|
|
|
@ -8,8 +8,9 @@ use text::{Bias, Point};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{h_stack, prelude::*, v_stack, Label};
|
use ui::{h_stack, prelude::*, v_stack, Label};
|
||||||
use util::paths::FILE_ROW_COLUMN_DELIMITER;
|
use util::paths::FILE_ROW_COLUMN_DELIMITER;
|
||||||
|
use workspace::ModalView;
|
||||||
|
|
||||||
actions!(Toggle);
|
actions!(go_to_line, [Toggle]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(GoToLine::register).detach();
|
cx.observe_new_views(GoToLine::register).detach();
|
||||||
|
@ -23,6 +24,8 @@ pub struct GoToLine {
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ModalView for GoToLine {}
|
||||||
|
|
||||||
impl FocusableView for GoToLine {
|
impl FocusableView for GoToLine {
|
||||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||||
self.line_editor.focus_handle(cx)
|
self.line_editor.focus_handle(cx)
|
||||||
|
|
|
@ -22,7 +22,7 @@ Actions are frequently unit structs, for which we have a macro. The above could
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
mod menu {
|
mod menu {
|
||||||
actions!(MoveUp, MoveDown);
|
actions!(gpui, [MoveUp, MoveDown]);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -3,34 +3,33 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
pub use no_action::NoAction;
|
pub use no_action::NoAction;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{
|
use std::any::{Any, TypeId};
|
||||||
any::{Any, TypeId},
|
|
||||||
ops::Deref,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Actions are used to implement keyboard-driven UI.
|
/// Actions are used to implement keyboard-driven UI.
|
||||||
/// When you declare an action, you can bind keys to the action in the keymap and
|
/// When you declare an action, you can bind keys to the action in the keymap and
|
||||||
/// listeners for that action in the element tree.
|
/// listeners for that action in the element tree.
|
||||||
///
|
///
|
||||||
/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
|
/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
|
||||||
/// action for each listed action name.
|
/// action for each listed action name in the given namespace.
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
|
/// actions!(editor, [MoveUp, MoveDown, MoveLeft, MoveRight, Newline]);
|
||||||
/// ```
|
/// ```
|
||||||
/// More complex data types can also be actions. If you annotate your type with the action derive macro
|
/// More complex data types can also be actions, providing they implement Clone, PartialEq,
|
||||||
/// it will be implemented and registered automatically.
|
/// and serde_derive::Deserialize.
|
||||||
|
/// Use `impl_actions!` to automatically implement the action in the given namespace.
|
||||||
/// ```
|
/// ```
|
||||||
/// #[derive(Clone, PartialEq, serde_derive::Deserialize, Action)]
|
/// #[derive(Clone, PartialEq, serde_derive::Deserialize)]
|
||||||
/// pub struct SelectNext {
|
/// pub struct SelectNext {
|
||||||
/// pub replace_newest: bool,
|
/// pub replace_newest: bool,
|
||||||
/// }
|
/// }
|
||||||
|
/// impl_actions!(editor, [SelectNext]);
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// If you want to control the behavior of the action trait manually, you can use the lower-level `#[register_action]`
|
/// If you want to control the behavior of the action trait manually, you can use the lower-level `#[register_action]`
|
||||||
/// macro, which only generates the code needed to register your action before `main`.
|
/// macro, which only generates the code needed to register your action before `main`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// #[gpui::register_action]
|
/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone)]
|
||||||
/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
|
|
||||||
/// pub struct Paste {
|
/// pub struct Paste {
|
||||||
/// pub content: SharedString,
|
/// pub content: SharedString,
|
||||||
/// }
|
/// }
|
||||||
|
@ -38,6 +37,7 @@ use std::{
|
||||||
/// impl gpui::Action for Paste {
|
/// impl gpui::Action for Paste {
|
||||||
/// ///...
|
/// ///...
|
||||||
/// }
|
/// }
|
||||||
|
/// register_action!(Paste);
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Action: 'static {
|
pub trait Action: 'static {
|
||||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||||
|
@ -56,7 +56,7 @@ pub trait Action: 'static {
|
||||||
impl std::fmt::Debug for dyn Action {
|
impl std::fmt::Debug for dyn Action {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("dyn Action")
|
f.debug_struct("dyn Action")
|
||||||
.field("type_name", &self.name())
|
.field("name", &self.name())
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ impl ActionRegistry {
|
||||||
for builder in __GPUI_ACTIONS {
|
for builder in __GPUI_ACTIONS {
|
||||||
let action = builder();
|
let action = builder();
|
||||||
//todo(remove)
|
//todo(remove)
|
||||||
let name: SharedString = remove_the_2(action.name).into();
|
let name: SharedString = action.name.into();
|
||||||
self.builders_by_name.insert(name.clone(), action.build);
|
self.builders_by_name.insert(name.clone(), action.build);
|
||||||
self.names_by_type_id.insert(action.type_id, name.clone());
|
self.names_by_type_id.insert(action.type_id, name.clone());
|
||||||
self.all_names.push(name);
|
self.all_names.push(name);
|
||||||
|
@ -139,11 +139,9 @@ impl ActionRegistry {
|
||||||
name: &str,
|
name: &str,
|
||||||
params: Option<serde_json::Value>,
|
params: Option<serde_json::Value>,
|
||||||
) -> Result<Box<dyn Action>> {
|
) -> Result<Box<dyn Action>> {
|
||||||
//todo(remove)
|
|
||||||
let name = remove_the_2(name);
|
|
||||||
let build_action = self
|
let build_action = self
|
||||||
.builders_by_name
|
.builders_by_name
|
||||||
.get(name.deref())
|
.get(name)
|
||||||
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
|
||||||
(build_action)(params.unwrap_or_else(|| json!({})))
|
(build_action)(params.unwrap_or_else(|| json!({})))
|
||||||
.with_context(|| format!("Attempting to build action {}", name))
|
.with_context(|| format!("Attempting to build action {}", name))
|
||||||
|
@ -155,36 +153,88 @@ impl ActionRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines unit structs that can be used as actions.
|
/// Defines unit structs that can be used as actions.
|
||||||
/// To use more complex data types as actions, annotate your type with the #[action] macro.
|
/// To use more complex data types as actions, use `impl_actions!`
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! actions {
|
macro_rules! actions {
|
||||||
() => {};
|
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||||
|
$(
|
||||||
|
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize)]
|
||||||
|
#[serde(crate = "gpui::serde")]
|
||||||
|
pub struct $name;
|
||||||
|
|
||||||
( $name:ident ) => {
|
gpui::__impl_action!($namespace, $name,
|
||||||
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize, gpui::Action)]
|
fn build(_: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||||
#[serde(crate = "gpui::serde")]
|
Ok(Box::new(Self))
|
||||||
pub struct $name;
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
( $name:ident, $($rest:tt)* ) => {
|
gpui::register_action!($name);
|
||||||
actions!($name);
|
)*
|
||||||
actions!($($rest)*);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo!(remove)
|
/// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize
|
||||||
pub fn remove_the_2(action_name: &str) -> String {
|
#[macro_export]
|
||||||
let mut separator_matches = action_name.rmatch_indices("::");
|
macro_rules! impl_actions {
|
||||||
separator_matches.next().unwrap();
|
($namespace:path, [ $($name:ident),* $(,)? ]) => {
|
||||||
let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
|
$(
|
||||||
// todo!() remove the 2 replacement when migration is done
|
gpui::__impl_action!($namespace, $name,
|
||||||
action_name[name_start_ix..]
|
fn build(value: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
|
||||||
.replace("2::", "::")
|
Ok(std::boxed::Box::new(gpui::serde_json::from_value::<Self>(value)?))
|
||||||
.to_string()
|
}
|
||||||
|
);
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! __impl_action {
|
||||||
|
($namespace:path, $name:ident, $build:item) => {
|
||||||
|
impl gpui::Action for $name {
|
||||||
|
fn name(&self) -> &'static str
|
||||||
|
{
|
||||||
|
concat!(
|
||||||
|
stringify!($namespace),
|
||||||
|
"::",
|
||||||
|
stringify!($name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo!() why is this needed in addition to name?
|
||||||
|
fn debug_name() -> &'static str
|
||||||
|
where
|
||||||
|
Self: ::std::marker::Sized
|
||||||
|
{
|
||||||
|
concat!(
|
||||||
|
stringify!($namespace),
|
||||||
|
"::",
|
||||||
|
stringify!($name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$build
|
||||||
|
|
||||||
|
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
|
||||||
|
action
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<Self>()
|
||||||
|
.map_or(false, |a| self == a)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> {
|
||||||
|
::std::boxed::Box::new(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mod no_action {
|
mod no_action {
|
||||||
use crate as gpui;
|
use crate as gpui;
|
||||||
|
|
||||||
actions!(NoAction);
|
actions!(zed, [NoAction]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ use smallvec::SmallVec;
|
||||||
use smol::future::FutureExt;
|
use smol::future::FutureExt;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub use test_context::*;
|
pub use test_context::*;
|
||||||
|
use time::UtcOffset;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||||
|
@ -536,6 +537,10 @@ impl AppContext {
|
||||||
self.platform.restart()
|
self.platform.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn local_timezone(&self) -> UtcOffset {
|
||||||
|
self.platform.local_timezone()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn push_effect(&mut self, effect: Effect) {
|
pub(crate) fn push_effect(&mut self, effect: Effect) {
|
||||||
match &effect {
|
match &effect {
|
||||||
Effect::Notify { emitter } => {
|
Effect::Notify { emitter } => {
|
||||||
|
@ -1110,6 +1115,10 @@ impl AppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_active_drag(&self) -> bool {
|
||||||
|
self.active_drag.is_some()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for AppContext {
|
impl Context for AppContext {
|
||||||
|
|
|
@ -69,24 +69,6 @@ pub trait IntoElement: Sized {
|
||||||
self.map(|this| if condition { then(this) } else { this })
|
self.map(|this| if condition { then(this) } else { this })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn when_else(
|
|
||||||
self,
|
|
||||||
condition: bool,
|
|
||||||
then: impl FnOnce(Self) -> Self,
|
|
||||||
otherwise: impl FnOnce(Self) -> Self,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
self.map(|this| {
|
|
||||||
if condition {
|
|
||||||
then(this)
|
|
||||||
} else {
|
|
||||||
otherwise(this)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
|
fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
|
|
496
crates/gpui2/src/elements/list.rs
Normal file
496
crates/gpui2/src/elements/list.rs
Normal file
|
@ -0,0 +1,496 @@
|
||||||
|
use crate::{
|
||||||
|
px, AnyElement, AvailableSpace, BorrowAppContext, DispatchPhase, Element, IntoElement, Pixels,
|
||||||
|
Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext,
|
||||||
|
};
|
||||||
|
use collections::VecDeque;
|
||||||
|
use refineable::Refineable as _;
|
||||||
|
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||||
|
use sum_tree::{Bias, SumTree};
|
||||||
|
|
||||||
|
pub fn list(state: ListState) -> List {
|
||||||
|
List {
|
||||||
|
state,
|
||||||
|
style: StyleRefinement::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct List {
|
||||||
|
state: ListState,
|
||||||
|
style: StyleRefinement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ListState(Rc<RefCell<StateInner>>);
|
||||||
|
|
||||||
|
struct StateInner {
|
||||||
|
last_layout_width: Option<Pixels>,
|
||||||
|
render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
|
||||||
|
items: SumTree<ListItem>,
|
||||||
|
logical_scroll_top: Option<ListOffset>,
|
||||||
|
alignment: ListAlignment,
|
||||||
|
overdraw: Pixels,
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum ListAlignment {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListScrollEvent {
|
||||||
|
pub visible_range: Range<usize>,
|
||||||
|
pub count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum ListItem {
|
||||||
|
Unrendered,
|
||||||
|
Rendered { height: Pixels },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
struct ListItemSummary {
|
||||||
|
count: usize,
|
||||||
|
rendered_count: usize,
|
||||||
|
unrendered_count: usize,
|
||||||
|
height: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct Count(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct RenderedCount(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct UnrenderedCount(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct Height(Pixels);
|
||||||
|
|
||||||
|
impl ListState {
|
||||||
|
pub fn new<F>(
|
||||||
|
element_count: usize,
|
||||||
|
orientation: ListAlignment,
|
||||||
|
overdraw: Pixels,
|
||||||
|
render_item: F,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
|
||||||
|
{
|
||||||
|
let mut items = SumTree::new();
|
||||||
|
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||||
|
Self(Rc::new(RefCell::new(StateInner {
|
||||||
|
last_layout_width: None,
|
||||||
|
render_item: Box::new(render_item),
|
||||||
|
items,
|
||||||
|
logical_scroll_top: None,
|
||||||
|
alignment: orientation,
|
||||||
|
overdraw,
|
||||||
|
scroll_handler: None,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&self, element_count: usize) {
|
||||||
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
state.logical_scroll_top = None;
|
||||||
|
state.items = SumTree::new();
|
||||||
|
state
|
||||||
|
.items
|
||||||
|
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn item_count(&self) -> usize {
|
||||||
|
self.0.borrow().items.summary().count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn splice(&self, old_range: Range<usize>, count: usize) {
|
||||||
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
|
||||||
|
if let Some(ListOffset {
|
||||||
|
item_ix,
|
||||||
|
offset_in_item,
|
||||||
|
}) = state.logical_scroll_top.as_mut()
|
||||||
|
{
|
||||||
|
if old_range.contains(item_ix) {
|
||||||
|
*item_ix = old_range.start;
|
||||||
|
*offset_in_item = px(0.);
|
||||||
|
} else if old_range.end <= *item_ix {
|
||||||
|
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut old_heights = state.items.cursor::<Count>();
|
||||||
|
let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
|
||||||
|
old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
|
||||||
|
|
||||||
|
new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
|
||||||
|
new_heights.append(old_heights.suffix(&()), &());
|
||||||
|
drop(old_heights);
|
||||||
|
state.items = new_heights;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scroll_handler(
|
||||||
|
&self,
|
||||||
|
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
|
||||||
|
) {
|
||||||
|
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logical_scroll_top(&self) -> ListOffset {
|
||||||
|
self.0.borrow().logical_scroll_top()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
|
||||||
|
let state = &mut *self.0.borrow_mut();
|
||||||
|
let item_count = state.items.summary().count;
|
||||||
|
if scroll_top.item_ix >= item_count {
|
||||||
|
scroll_top.item_ix = item_count;
|
||||||
|
scroll_top.offset_in_item = px(0.);
|
||||||
|
}
|
||||||
|
state.logical_scroll_top = Some(scroll_top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateInner {
|
||||||
|
fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
|
||||||
|
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
let start_y = cursor.start().height + scroll_top.offset_in_item;
|
||||||
|
cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
|
||||||
|
scroll_top.item_ix..cursor.start().count + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll(
|
||||||
|
&mut self,
|
||||||
|
scroll_top: &ListOffset,
|
||||||
|
height: Pixels,
|
||||||
|
delta: Point<Pixels>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) {
|
||||||
|
let scroll_max = (self.items.summary().height - height).max(px(0.));
|
||||||
|
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
|
||||||
|
.max(px(0.))
|
||||||
|
.min(scroll_max);
|
||||||
|
|
||||||
|
if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
|
||||||
|
self.logical_scroll_top = None;
|
||||||
|
} else {
|
||||||
|
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||||
|
cursor.seek(&Height(new_scroll_top), Bias::Right, &());
|
||||||
|
let item_ix = cursor.start().count;
|
||||||
|
let offset_in_item = new_scroll_top - cursor.start().height;
|
||||||
|
self.logical_scroll_top = Some(ListOffset {
|
||||||
|
item_ix,
|
||||||
|
offset_in_item,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.scroll_handler.is_some() {
|
||||||
|
let visible_range = self.visible_range(height, scroll_top);
|
||||||
|
self.scroll_handler.as_mut().unwrap()(
|
||||||
|
&ListScrollEvent {
|
||||||
|
visible_range,
|
||||||
|
count: self.items.summary().count,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logical_scroll_top(&self) -> ListOffset {
|
||||||
|
self.logical_scroll_top
|
||||||
|
.unwrap_or_else(|| match self.alignment {
|
||||||
|
ListAlignment::Top => ListOffset {
|
||||||
|
item_ix: 0,
|
||||||
|
offset_in_item: px(0.),
|
||||||
|
},
|
||||||
|
ListAlignment::Bottom => ListOffset {
|
||||||
|
item_ix: self.items.summary().count,
|
||||||
|
offset_in_item: px(0.),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
|
||||||
|
let mut cursor = self.items.cursor::<ListItemSummary>();
|
||||||
|
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
|
||||||
|
cursor.start().height + logical_scroll_top.offset_in_item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ListItem {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Unrendered => write!(f, "Unrendered"),
|
||||||
|
Self::Rendered { height, .. } => {
|
||||||
|
f.debug_struct("Rendered").field("height", height).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ListOffset {
|
||||||
|
pub item_ix: usize,
|
||||||
|
pub offset_in_item: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for List {
|
||||||
|
type State = ();
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
_state: Option<Self::State>,
|
||||||
|
cx: &mut crate::WindowContext,
|
||||||
|
) -> (crate::LayoutId, Self::State) {
|
||||||
|
let mut style = Style::default();
|
||||||
|
style.refine(&self.style);
|
||||||
|
let layout_id = cx.with_text_style(style.text_style().cloned(), |cx| {
|
||||||
|
cx.request_layout(&style, None)
|
||||||
|
});
|
||||||
|
(layout_id, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
self,
|
||||||
|
bounds: crate::Bounds<crate::Pixels>,
|
||||||
|
_state: &mut Self::State,
|
||||||
|
cx: &mut crate::WindowContext,
|
||||||
|
) {
|
||||||
|
let state = &mut *self.state.0.borrow_mut();
|
||||||
|
|
||||||
|
// If the width of the list has changed, invalidate all cached item heights
|
||||||
|
if state.last_layout_width != Some(bounds.size.width) {
|
||||||
|
state.items = SumTree::from_iter(
|
||||||
|
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
|
||||||
|
&(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_items = state.items.clone();
|
||||||
|
let mut measured_items = VecDeque::new();
|
||||||
|
let mut item_elements = VecDeque::new();
|
||||||
|
let mut rendered_height = px(0.);
|
||||||
|
let mut scroll_top = state.logical_scroll_top();
|
||||||
|
|
||||||
|
let available_item_space = Size {
|
||||||
|
width: AvailableSpace::Definite(bounds.size.width),
|
||||||
|
height: AvailableSpace::MinContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render items after the scroll top, including those in the trailing overdraw
|
||||||
|
let mut cursor = old_items.cursor::<Count>();
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
for (ix, item) in cursor.by_ref().enumerate() {
|
||||||
|
let visible_height = rendered_height - scroll_top.offset_in_item;
|
||||||
|
if visible_height >= bounds.size.height + state.overdraw {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the previously cached height if available
|
||||||
|
let mut height = if let ListItem::Rendered { height } = item {
|
||||||
|
Some(*height)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we're within the visible area or the height wasn't cached, render and measure the item's element
|
||||||
|
if visible_height < bounds.size.height || height.is_none() {
|
||||||
|
let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
|
||||||
|
let element_size = element.measure(available_item_space, cx);
|
||||||
|
height = Some(element_size.height);
|
||||||
|
if visible_height < bounds.size.height {
|
||||||
|
item_elements.push_back(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let height = height.unwrap();
|
||||||
|
rendered_height += height;
|
||||||
|
measured_items.push_back(ListItem::Rendered { height });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare to start walking upward from the item at the scroll top.
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
|
||||||
|
// If the rendered items do not fill the visible region, then adjust
|
||||||
|
// the scroll top upward.
|
||||||
|
if rendered_height - scroll_top.offset_in_item < bounds.size.height {
|
||||||
|
while rendered_height < bounds.size.height {
|
||||||
|
cursor.prev(&());
|
||||||
|
if cursor.item().is_some() {
|
||||||
|
let mut element = (state.render_item)(cursor.start().0, cx);
|
||||||
|
let element_size = element.measure(available_item_space, cx);
|
||||||
|
|
||||||
|
rendered_height += element_size.height;
|
||||||
|
measured_items.push_front(ListItem::Rendered {
|
||||||
|
height: element_size.height,
|
||||||
|
});
|
||||||
|
item_elements.push_front(element)
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll_top = ListOffset {
|
||||||
|
item_ix: cursor.start().0,
|
||||||
|
offset_in_item: rendered_height - bounds.size.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
match state.alignment {
|
||||||
|
ListAlignment::Top => {
|
||||||
|
scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
|
||||||
|
state.logical_scroll_top = Some(scroll_top);
|
||||||
|
}
|
||||||
|
ListAlignment::Bottom => {
|
||||||
|
scroll_top = ListOffset {
|
||||||
|
item_ix: cursor.start().0,
|
||||||
|
offset_in_item: rendered_height - bounds.size.height,
|
||||||
|
};
|
||||||
|
state.logical_scroll_top = None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure items in the leading overdraw
|
||||||
|
let mut leading_overdraw = scroll_top.offset_in_item;
|
||||||
|
while leading_overdraw < state.overdraw {
|
||||||
|
cursor.prev(&());
|
||||||
|
if let Some(item) = cursor.item() {
|
||||||
|
let height = if let ListItem::Rendered { height } = item {
|
||||||
|
*height
|
||||||
|
} else {
|
||||||
|
let mut element = (state.render_item)(cursor.start().0, cx);
|
||||||
|
element.measure(available_item_space, cx).height
|
||||||
|
};
|
||||||
|
|
||||||
|
leading_overdraw += height;
|
||||||
|
measured_items.push_front(ListItem::Rendered { height });
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
|
||||||
|
let mut cursor = old_items.cursor::<Count>();
|
||||||
|
let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
|
||||||
|
new_items.extend(measured_items, &());
|
||||||
|
cursor.seek(&Count(measured_range.end), Bias::Right, &());
|
||||||
|
new_items.append(cursor.suffix(&()), &());
|
||||||
|
|
||||||
|
// Paint the visible items
|
||||||
|
let mut item_origin = bounds.origin;
|
||||||
|
item_origin.y -= scroll_top.offset_in_item;
|
||||||
|
for mut item_element in item_elements {
|
||||||
|
let item_height = item_element.measure(available_item_space, cx).height;
|
||||||
|
item_element.draw(item_origin, available_item_space, cx);
|
||||||
|
item_origin.y += item_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.items = new_items;
|
||||||
|
state.last_layout_width = Some(bounds.size.width);
|
||||||
|
|
||||||
|
let list_state = self.state.clone();
|
||||||
|
let height = bounds.size.height;
|
||||||
|
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||||
|
if phase == DispatchPhase::Bubble {
|
||||||
|
list_state.0.borrow_mut().scroll(
|
||||||
|
&scroll_top,
|
||||||
|
height,
|
||||||
|
event.delta.pixel_delta(px(20.)),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoElement for List {
|
||||||
|
type Element = Self;
|
||||||
|
|
||||||
|
fn element_id(&self) -> Option<crate::ElementId> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_element(self) -> Self::Element {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Styled for List {
|
||||||
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
|
&mut self.style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Item for ListItem {
|
||||||
|
type Summary = ListItemSummary;
|
||||||
|
|
||||||
|
fn summary(&self) -> Self::Summary {
|
||||||
|
match self {
|
||||||
|
ListItem::Unrendered => ListItemSummary {
|
||||||
|
count: 1,
|
||||||
|
rendered_count: 0,
|
||||||
|
unrendered_count: 1,
|
||||||
|
height: px(0.),
|
||||||
|
},
|
||||||
|
ListItem::Rendered { height } => ListItemSummary {
|
||||||
|
count: 1,
|
||||||
|
rendered_count: 1,
|
||||||
|
unrendered_count: 0,
|
||||||
|
height: *height,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Summary for ListItemSummary {
|
||||||
|
type Context = ();
|
||||||
|
|
||||||
|
fn add_summary(&mut self, summary: &Self, _: &()) {
|
||||||
|
self.count += summary.count;
|
||||||
|
self.rendered_count += summary.rendered_count;
|
||||||
|
self.unrendered_count += summary.unrendered_count;
|
||||||
|
self.height += summary.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.rendered_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.unrendered_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
|
||||||
|
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
|
||||||
|
self.0 += summary.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Count {
|
||||||
|
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||||
|
self.0.partial_cmp(&other.count).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
|
||||||
|
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||||
|
self.0.partial_cmp(&other.height).unwrap()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod div;
|
mod div;
|
||||||
mod img;
|
mod img;
|
||||||
|
mod list;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
mod svg;
|
mod svg;
|
||||||
mod text;
|
mod text;
|
||||||
|
@ -9,6 +10,7 @@ mod uniform_list;
|
||||||
pub use canvas::*;
|
pub use canvas::*;
|
||||||
pub use div::*;
|
pub use div::*;
|
||||||
pub use img::*;
|
pub use img::*;
|
||||||
|
pub use list::*;
|
||||||
pub use overlay::*;
|
pub use overlay::*;
|
||||||
pub use svg::*;
|
pub use svg::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
|
@ -131,7 +131,7 @@ impl Element for UniformList {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let height = match available_space.height {
|
let height = match available_space.height {
|
||||||
AvailableSpace::Definite(x) => desired_height.min(x),
|
AvailableSpace::Definite(height) => desired_height.min(height),
|
||||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
||||||
desired_height
|
desired_height
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod action;
|
mod action;
|
||||||
mod app;
|
mod app;
|
||||||
|
|
||||||
mod assets;
|
mod assets;
|
||||||
mod color;
|
mod color;
|
||||||
mod element;
|
mod element;
|
||||||
|
@ -15,6 +16,7 @@ mod keymap;
|
||||||
mod platform;
|
mod platform;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
mod scene;
|
mod scene;
|
||||||
|
mod shared_string;
|
||||||
mod style;
|
mod style;
|
||||||
mod styled;
|
mod styled;
|
||||||
mod subscription;
|
mod subscription;
|
||||||
|
@ -57,6 +59,7 @@ pub use scene::*;
|
||||||
pub use serde;
|
pub use serde;
|
||||||
pub use serde_derive;
|
pub use serde_derive;
|
||||||
pub use serde_json;
|
pub use serde_json;
|
||||||
|
pub use shared_string::*;
|
||||||
pub use smallvec;
|
pub use smallvec;
|
||||||
pub use smol::Timer;
|
pub use smol::Timer;
|
||||||
pub use style::*;
|
pub use style::*;
|
||||||
|
@ -71,10 +74,9 @@ pub use util::arc_cow::ArcCow;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
use derive_more::{Deref, DerefMut};
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::BorrowMut,
|
||||||
};
|
};
|
||||||
use taffy::TaffyLayoutEngine;
|
use taffy::TaffyLayoutEngine;
|
||||||
|
|
||||||
|
@ -209,42 +211,3 @@ impl<T> Flatten<T> for Result<T> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
|
||||||
pub struct SharedString(ArcCow<'static, str>);
|
|
||||||
|
|
||||||
impl Default for SharedString {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(ArcCow::Owned("".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for SharedString {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Borrow<str> for SharedString {
|
|
||||||
fn borrow(&self) -> &str {
|
|
||||||
self.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for SharedString {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for SharedString {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Self(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -193,6 +193,12 @@ impl Deref for MouseExitEvent {
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
|
||||||
|
|
||||||
|
impl ExternalPaths {
|
||||||
|
pub fn paths(&self) -> &[PathBuf] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Render for ExternalPaths {
|
impl Render for ExternalPaths {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
|
@ -296,7 +302,7 @@ mod test {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(TestAction);
|
actions!(test, [TestAction]);
|
||||||
|
|
||||||
impl Render for TestView {
|
impl Render for TestView {
|
||||||
type Element = Stateful<Div>;
|
type Element = Stateful<Div>;
|
||||||
|
|
|
@ -149,13 +149,19 @@ impl DispatchTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available_actions(&self, target: DispatchNodeId) -> Vec<Box<dyn Action>> {
|
pub fn available_actions(&self, target: DispatchNodeId) -> Vec<Box<dyn Action>> {
|
||||||
let mut actions = Vec::new();
|
let mut actions = Vec::<Box<dyn Action>>::new();
|
||||||
for node_id in self.dispatch_path(target) {
|
for node_id in self.dispatch_path(target) {
|
||||||
let node = &self.nodes[node_id.0];
|
let node = &self.nodes[node_id.0];
|
||||||
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
for DispatchActionListener { action_type, .. } in &node.action_listeners {
|
||||||
// Intentionally silence these errors without logging.
|
if let Err(ix) = actions.binary_search_by_key(action_type, |a| a.as_any().type_id())
|
||||||
// If an action cannot be built by default, it's not available.
|
{
|
||||||
actions.extend(self.action_registry.build_action_type(action_type).ok());
|
// Intentionally silence these errors without logging.
|
||||||
|
// If an action cannot be built by default, it's not available.
|
||||||
|
let action = self.action_registry.build_action_type(action_type).ok();
|
||||||
|
if let Some(action) = action {
|
||||||
|
actions.insert(ix, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions
|
actions
|
||||||
|
|
|
@ -293,11 +293,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_actions_definition() {
|
fn test_actions_definition() {
|
||||||
{
|
{
|
||||||
actions!(A, B, C, D, E, F, G);
|
actions!(test, [A, B, C, D, E, F, G]);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
actions!(
|
actions!(
|
||||||
|
test,
|
||||||
|
[
|
||||||
A,
|
A,
|
||||||
B,
|
B,
|
||||||
C,
|
C,
|
||||||
|
@ -305,6 +307,7 @@ mod tests {
|
||||||
E,
|
E,
|
||||||
F,
|
F,
|
||||||
G, // Don't wrap, test the trailing comma
|
G, // Don't wrap, test the trailing comma
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,7 +325,7 @@ impl MetalRenderer {
|
||||||
.entry(tile.texture_id)
|
.entry(tile.texture_id)
|
||||||
.or_insert(Vec::new())
|
.or_insert(Vec::new())
|
||||||
.extend(path.vertices.iter().map(|vertex| PathVertex {
|
.extend(path.vertices.iter().map(|vertex| PathVertex {
|
||||||
xy_position: vertex.xy_position - path.bounds.origin
|
xy_position: vertex.xy_position - clipped_bounds.origin
|
||||||
+ tile.bounds.origin.map(Into::into),
|
+ tile.bounds.origin.map(Into::into),
|
||||||
st_position: vertex.st_position,
|
st_position: vertex.st_position,
|
||||||
content_mask: ContentMask {
|
content_mask: ContentMask {
|
||||||
|
@ -544,9 +544,10 @@ impl MetalRenderer {
|
||||||
if let Some((path, tile)) = paths_and_tiles.peek() {
|
if let Some((path, tile)) = paths_and_tiles.peek() {
|
||||||
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
|
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
|
||||||
prev_texture_id = Some(tile.texture_id);
|
prev_texture_id = Some(tile.texture_id);
|
||||||
|
let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
|
||||||
sprites.push(PathSprite {
|
sprites.push(PathSprite {
|
||||||
bounds: Bounds {
|
bounds: Bounds {
|
||||||
origin: path.bounds.origin.map(|p| p.floor()),
|
origin: origin.map(|p| p.floor()),
|
||||||
size: tile.bounds.size.map(Into::into),
|
size: tile.bounds.size.map(Into::into),
|
||||||
},
|
},
|
||||||
color: path.color,
|
color: path.color,
|
||||||
|
|
101
crates/gpui2/src/shared_string.rs
Normal file
101
crates/gpui2/src/shared_string.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{borrow::Borrow, sync::Arc};
|
||||||
|
use util::arc_cow::ArcCow;
|
||||||
|
|
||||||
|
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||||
|
pub struct SharedString(ArcCow<'static, str>);
|
||||||
|
|
||||||
|
impl Default for SharedString {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(ArcCow::Owned("".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for SharedString {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for SharedString {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for SharedString {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SharedString {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<String> for SharedString {
|
||||||
|
fn eq(&self, other: &String) -> bool {
|
||||||
|
self.as_ref() == other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<SharedString> for String {
|
||||||
|
fn eq(&self, other: &SharedString) -> bool {
|
||||||
|
self == other.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<str> for SharedString {
|
||||||
|
fn eq(&self, other: &str) -> bool {
|
||||||
|
self.as_ref() == other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq<&'a str> for SharedString {
|
||||||
|
fn eq(&self, other: &&'a str) -> bool {
|
||||||
|
self.as_ref() == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Arc<str>> for SharedString {
|
||||||
|
fn into(self) -> Arc<str> {
|
||||||
|
match self.0 {
|
||||||
|
ArcCow::Borrowed(borrowed) => Arc::from(borrowed),
|
||||||
|
ArcCow::Owned(owned) => owned.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for SharedString {
|
||||||
|
fn into(self) -> String {
|
||||||
|
self.0.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for SharedString {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for SharedString {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(SharedString::from(s))
|
||||||
|
}
|
||||||
|
}
|
|
@ -245,6 +245,13 @@ pub trait Styled: Sized {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the flex direction of the element to `column-reverse`.
|
||||||
|
/// [Docs](https://tailwindcss.com/docs/flex-direction#column-reverse)
|
||||||
|
fn flex_col_reverse(mut self) -> Self {
|
||||||
|
self.style().flex_direction = Some(FlexDirection::ColumnReverse);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the flex direction of the element to `row`.
|
/// Sets the flex direction of the element to `row`.
|
||||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#row)
|
/// [Docs](https://tailwindcss.com/docs/flex-direction#row)
|
||||||
fn flex_row(mut self) -> Self {
|
fn flex_row(mut self) -> Self {
|
||||||
|
@ -252,6 +259,13 @@ pub trait Styled: Sized {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the flex direction of the element to `row-reverse`.
|
||||||
|
/// [Docs](https://tailwindcss.com/docs/flex-direction#row-reverse)
|
||||||
|
fn flex_row_reverse(mut self) -> Self {
|
||||||
|
self.style().flex_direction = Some(FlexDirection::RowReverse);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
|
/// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
|
||||||
/// [Docs](https://tailwindcss.com/docs/flex#flex-1)
|
/// [Docs](https://tailwindcss.com/docs/flex#flex-1)
|
||||||
fn flex_1(mut self) -> Self {
|
fn flex_1(mut self) -> Self {
|
||||||
|
|
|
@ -4,12 +4,12 @@ use crate::{
|
||||||
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
|
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
|
||||||
EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
|
EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
|
||||||
ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model,
|
ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model,
|
||||||
ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
|
ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path,
|
||||||
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler,
|
Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||||
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
|
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
|
||||||
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
|
RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
|
||||||
Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline,
|
Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
|
||||||
UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -1269,10 +1269,9 @@ impl<'a> WindowContext<'a> {
|
||||||
cursor_offset: position,
|
cursor_offset: position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
InputEvent::MouseDown(MouseDownEvent {
|
InputEvent::MouseMove(MouseMoveEvent {
|
||||||
position,
|
position,
|
||||||
button: MouseButton::Left,
|
pressed_button: Some(MouseButton::Left),
|
||||||
click_count: 1,
|
|
||||||
modifiers: Modifiers::default(),
|
modifiers: Modifiers::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1285,6 +1284,7 @@ impl<'a> WindowContext<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
FileDropEvent::Submit { position } => {
|
FileDropEvent::Submit { position } => {
|
||||||
|
self.activate(true);
|
||||||
self.window.mouse_position = position;
|
self.window.mouse_position = position;
|
||||||
InputEvent::MouseUp(MouseUpEvent {
|
InputEvent::MouseUp(MouseUpEvent {
|
||||||
button: MouseButton::Left,
|
button: MouseButton::Left,
|
||||||
|
@ -2839,3 +2839,9 @@ impl From<(&'static str, usize)> for ElementId {
|
||||||
ElementId::NamedInteger(name.into(), id)
|
ElementId::NamedInteger(name.into(), id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(&'static str, u64)> for ElementId {
|
||||||
|
fn from((name, id): (&'static str, u64)) -> Self {
|
||||||
|
ElementId::NamedInteger(name.into(), id as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
|
use gpui2::{actions, impl_actions};
|
||||||
|
use gpui2_macros::register_action;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_derive() {
|
fn test_action_macros() {
|
||||||
use gpui2 as gpui;
|
use gpui2 as gpui;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, gpui2_macros::Action)]
|
actions!(test, [TestAction]);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Deserialize)]
|
||||||
struct AnotherTestAction;
|
struct AnotherTestAction;
|
||||||
|
|
||||||
#[gpui2_macros::register_action]
|
impl_actions!(test, [AnotherTestAction]);
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, gpui::serde_derive::Deserialize)]
|
#[derive(PartialEq, Clone, gpui::serde_derive::Deserialize)]
|
||||||
struct RegisterableAction {}
|
struct RegisterableAction {}
|
||||||
|
|
||||||
|
register_action!(RegisterableAction);
|
||||||
|
|
||||||
impl gpui::Action for RegisterableAction {
|
impl gpui::Action for RegisterableAction {
|
||||||
fn boxed_clone(&self) -> Box<dyn gpui::Action> {
|
fn boxed_clone(&self) -> Box<dyn gpui::Action> {
|
||||||
todo!()
|
todo!()
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
// Input:
|
|
||||||
//
|
|
||||||
// #[action]
|
|
||||||
// struct Foo {
|
|
||||||
// bar: String,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
//
|
|
||||||
// #[gpui::register_action]
|
|
||||||
// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
|
|
||||||
// struct Foo {
|
|
||||||
// bar: String,
|
|
||||||
// }
|
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{parse_macro_input, DeriveInput, Error};
|
|
||||||
|
|
||||||
use crate::register_action::register_action;
|
|
||||||
|
|
||||||
pub fn action(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
|
||||||
|
|
||||||
let name = &input.ident;
|
|
||||||
|
|
||||||
if input.generics.lt_token.is_some() {
|
|
||||||
return Error::new(name.span(), "Actions must be a concrete type")
|
|
||||||
.into_compile_error()
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_unit_struct = match input.data {
|
|
||||||
syn::Data::Struct(struct_data) => struct_data.fields.is_empty(),
|
|
||||||
syn::Data::Enum(_) => false,
|
|
||||||
syn::Data::Union(_) => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let build_impl = if is_unit_struct {
|
|
||||||
quote! {
|
|
||||||
Ok(std::boxed::Box::new(Self {}))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
Ok(std::boxed::Box::new(gpui::serde_json::from_value::<Self>(value)?))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let register_action = register_action(&name);
|
|
||||||
|
|
||||||
let output = quote! {
|
|
||||||
const _: fn() = || {
|
|
||||||
fn assert_impl<T: ?Sized + for<'a> gpui::serde::Deserialize<'a> + ::std::cmp::PartialEq + ::std::clone::Clone>() {}
|
|
||||||
assert_impl::<#name>();
|
|
||||||
};
|
|
||||||
|
|
||||||
impl gpui::Action for #name {
|
|
||||||
fn name(&self) -> &'static str
|
|
||||||
{
|
|
||||||
::std::any::type_name::<#name>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_name() -> &'static str
|
|
||||||
where
|
|
||||||
Self: ::std::marker::Sized
|
|
||||||
{
|
|
||||||
::std::any::type_name::<#name>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(value: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>>
|
|
||||||
where
|
|
||||||
Self: ::std::marker::Sized {
|
|
||||||
#build_impl
|
|
||||||
}
|
|
||||||
|
|
||||||
fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
|
|
||||||
action
|
|
||||||
.as_any()
|
|
||||||
.downcast_ref::<Self>()
|
|
||||||
.map_or(false, |a| self == a)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> {
|
|
||||||
::std::boxed::Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#register_action
|
|
||||||
};
|
|
||||||
|
|
||||||
TokenStream::from(output)
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
mod action;
|
|
||||||
mod derive_into_element;
|
mod derive_into_element;
|
||||||
mod register_action;
|
mod register_action;
|
||||||
mod style_helpers;
|
mod style_helpers;
|
||||||
|
@ -6,14 +5,9 @@ mod test;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
#[proc_macro_derive(Action)]
|
#[proc_macro]
|
||||||
pub fn action(input: TokenStream) -> TokenStream {
|
pub fn register_action(ident: TokenStream) -> TokenStream {
|
||||||
action::action(input)
|
register_action::register_action_macro(ident)
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
||||||
register_action::register_action_macro(attr, item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(IntoElement)]
|
#[proc_macro_derive(IntoElement)]
|
||||||
|
|
|
@ -14,47 +14,13 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Ident;
|
use proc_macro2::Ident;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{parse_macro_input, DeriveInput, Error};
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
pub fn register_action_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn register_action_macro(ident: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(item as DeriveInput);
|
let name = parse_macro_input!(ident as Ident);
|
||||||
let registration = register_action(&input.ident);
|
let registration = register_action(&name);
|
||||||
|
|
||||||
let has_action_derive = input
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.find(|attr| {
|
|
||||||
(|| {
|
|
||||||
let meta = attr.parse_meta().ok()?;
|
|
||||||
meta.path().is_ident("derive").then(|| match meta {
|
|
||||||
syn::Meta::Path(_) => None,
|
|
||||||
syn::Meta::NameValue(_) => None,
|
|
||||||
syn::Meta::List(list) => list
|
|
||||||
.nested
|
|
||||||
.iter()
|
|
||||||
.find(|list| match list {
|
|
||||||
syn::NestedMeta::Meta(meta) => meta.path().is_ident("Action"),
|
|
||||||
syn::NestedMeta::Lit(_) => false,
|
|
||||||
})
|
|
||||||
.map(|_| true),
|
|
||||||
})?
|
|
||||||
})()
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
if has_action_derive {
|
|
||||||
return Error::new(
|
|
||||||
input.ident.span(),
|
|
||||||
"The Action derive macro has already registered this action",
|
|
||||||
)
|
|
||||||
.into_compile_error()
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenStream::from(quote! {
|
TokenStream::from(quote! {
|
||||||
#input
|
|
||||||
|
|
||||||
#registration
|
#registration
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -78,7 +44,7 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn #action_builder_fn_name() -> gpui::ActionData {
|
fn #action_builder_fn_name() -> gpui::ActionData {
|
||||||
gpui::ActionData {
|
gpui::ActionData {
|
||||||
name: ::std::any::type_name::<#type_name>(),
|
name: <#type_name as gpui::Action>::debug_name(),
|
||||||
type_id: ::std::any::TypeId::of::<#type_name>(),
|
type_id: ::std::any::TypeId::of::<#type_name>(),
|
||||||
build: <#type_name as gpui::Action>::build,
|
build: <#type_name as gpui::Action>::build,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use gpui::{actions, AsyncAppContext};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
actions!(Install);
|
actions!(cli, [Install]);
|
||||||
|
|
||||||
pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
|
pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
|
||||||
let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
|
let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??;
|
||||||
|
|
|
@ -14,9 +14,9 @@ use project::Project;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{prelude::*, HighlightedLabel, ListItem};
|
use ui::{prelude::*, HighlightedLabel, ListItem};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::{ModalView, Workspace};
|
||||||
|
|
||||||
actions!(Toggle);
|
actions!(language_selector, [Toggle]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(LanguageSelector::register).detach();
|
cx.observe_new_views(LanguageSelector::register).detach();
|
||||||
|
@ -81,6 +81,7 @@ impl FocusableView for LanguageSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for LanguageSelector {}
|
impl EventEmitter<DismissEvent> for LanguageSelector {}
|
||||||
|
impl ModalView for LanguageSelector {}
|
||||||
|
|
||||||
pub struct LanguageSelectorDelegate {
|
pub struct LanguageSelectorDelegate {
|
||||||
language_selector: WeakView<LanguageSelector>,
|
language_selector: WeakView<LanguageSelector>,
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{Action, KeyBinding};
|
use gpui::{actions, KeyBinding};
|
||||||
use live_kit_client2::{
|
use live_kit_client2::{
|
||||||
LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
|
LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room,
|
||||||
};
|
};
|
||||||
use live_kit_server::token::{self, VideoGrant};
|
use live_kit_server::token::{self, VideoGrant};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use serde_derive::Deserialize;
|
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Action)]
|
actions!(live_kit_client, [Quit]);
|
||||||
struct Quit;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||||
|
|
|
@ -10,12 +10,15 @@ use gpui::actions;
|
||||||
pub fn init() {}
|
pub fn init() {}
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
Cancel,
|
menu,
|
||||||
Confirm,
|
[
|
||||||
SecondaryConfirm,
|
Cancel,
|
||||||
SelectPrev,
|
Confirm,
|
||||||
SelectNext,
|
SecondaryConfirm,
|
||||||
SelectFirst,
|
SelectPrev,
|
||||||
SelectLast,
|
SelectNext,
|
||||||
ShowContextMenu
|
SelectFirst,
|
||||||
|
SelectLast,
|
||||||
|
ShowContextMenu
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt,
|
display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt,
|
||||||
DisplayPoint, Editor, ToPoint,
|
DisplayPoint, Editor, EditorMode, ToPoint,
|
||||||
};
|
};
|
||||||
use fuzzy::StringMatch;
|
use fuzzy::StringMatch;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -20,29 +20,26 @@ use std::{
|
||||||
use theme::{color_alpha, ActiveTheme, ThemeSettings};
|
use theme::{color_alpha, ActiveTheme, ThemeSettings};
|
||||||
use ui::{prelude::*, ListItem};
|
use ui::{prelude::*, ListItem};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::ModalView;
|
||||||
|
|
||||||
actions!(Toggle);
|
actions!(outline, [Toggle]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(OutlineView::register).detach();
|
cx.observe_new_views(OutlineView::register).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
pub fn toggle(editor: View<Editor>, _: &Toggle, cx: &mut WindowContext) {
|
||||||
if let Some(editor) = workspace
|
let outline = editor
|
||||||
.active_item(cx)
|
.read(cx)
|
||||||
.and_then(|item| item.downcast::<Editor>())
|
.buffer()
|
||||||
{
|
.read(cx)
|
||||||
let outline = editor
|
.snapshot(cx)
|
||||||
.read(cx)
|
.outline(Some(&cx.theme().syntax()));
|
||||||
.buffer()
|
|
||||||
.read(cx)
|
|
||||||
.snapshot(cx)
|
|
||||||
.outline(Some(&cx.theme().syntax()));
|
|
||||||
|
|
||||||
if let Some(outline) = outline {
|
if let Some((workspace, outline)) = editor.read(cx).workspace().zip(outline) {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.toggle_modal(cx, |cx| OutlineView::new(outline, editor, cx));
|
workspace.toggle_modal(cx, |cx| OutlineView::new(outline, editor, cx));
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +54,7 @@ impl FocusableView for OutlineView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for OutlineView {}
|
impl EventEmitter<DismissEvent> for OutlineView {}
|
||||||
|
impl ModalView for OutlineView {}
|
||||||
|
|
||||||
impl Render for OutlineView {
|
impl Render for OutlineView {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
@ -67,8 +65,15 @@ impl Render for OutlineView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutlineView {
|
impl OutlineView {
|
||||||
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||||
workspace.register_action(toggle);
|
if editor.mode() == EditorMode::Full {
|
||||||
|
let handle = cx.view().downgrade();
|
||||||
|
editor.register_action(move |action, cx| {
|
||||||
|
if let Some(editor) = handle.upgrade() {
|
||||||
|
toggle(editor, action, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
|
@ -238,6 +243,7 @@ impl PickerDelegate for OutlineViewDelegate {
|
||||||
s.select_ranges([position..position])
|
s.select_ranges([position..position])
|
||||||
});
|
});
|
||||||
active_editor.highlight_rows(None);
|
active_editor.highlight_rows(None);
|
||||||
|
active_editor.focus(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub(crate) trait LspCommand: 'static + Sized {
|
pub trait LspCommand: 'static + Sized {
|
||||||
type Response: 'static + Default + Send;
|
type Response: 'static + Default + Send;
|
||||||
type LspRequest: 'static + Send + lsp::request::Request;
|
type LspRequest: 'static + Send + lsp::request::Request;
|
||||||
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
||||||
|
|
137
crates/project/src/lsp_ext_command.rs
Normal file
137
crates/project/src/lsp_ext_command.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
||||||
|
use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
|
||||||
|
use lsp::{LanguageServer, LanguageServerId};
|
||||||
|
use rpc::proto::{self, PeerId};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use text::{PointUtf16, ToPointUtf16};
|
||||||
|
|
||||||
|
use crate::{lsp_command::LspCommand, Project};
|
||||||
|
|
||||||
|
pub enum LspExpandMacro {}
|
||||||
|
|
||||||
|
impl lsp::request::Request for LspExpandMacro {
|
||||||
|
type Params = ExpandMacroParams;
|
||||||
|
type Result = Option<ExpandedMacro>;
|
||||||
|
const METHOD: &'static str = "rust-analyzer/expandMacro";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ExpandMacroParams {
|
||||||
|
pub text_document: lsp::TextDocumentIdentifier,
|
||||||
|
pub position: lsp::Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ExpandedMacro {
|
||||||
|
pub name: String,
|
||||||
|
pub expansion: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExpandedMacro {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.name.is_empty() && self.expansion.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExpandMacro {
|
||||||
|
pub position: PointUtf16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl LspCommand for ExpandMacro {
|
||||||
|
type Response = ExpandedMacro;
|
||||||
|
type LspRequest = LspExpandMacro;
|
||||||
|
type ProtoRequest = proto::LspExtExpandMacro;
|
||||||
|
|
||||||
|
fn to_lsp(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
_: &Buffer,
|
||||||
|
_: &Arc<LanguageServer>,
|
||||||
|
_: &AppContext,
|
||||||
|
) -> ExpandMacroParams {
|
||||||
|
ExpandMacroParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier {
|
||||||
|
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||||
|
},
|
||||||
|
position: point_to_lsp(self.position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn response_from_lsp(
|
||||||
|
self,
|
||||||
|
message: Option<ExpandedMacro>,
|
||||||
|
_: ModelHandle<Project>,
|
||||||
|
_: ModelHandle<Buffer>,
|
||||||
|
_: LanguageServerId,
|
||||||
|
_: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<ExpandedMacro> {
|
||||||
|
Ok(message
|
||||||
|
.map(|message| ExpandedMacro {
|
||||||
|
name: message.name,
|
||||||
|
expansion: message.expansion,
|
||||||
|
})
|
||||||
|
.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
|
||||||
|
proto::LspExtExpandMacro {
|
||||||
|
project_id,
|
||||||
|
buffer_id: buffer.remote_id(),
|
||||||
|
position: Some(language::proto::serialize_anchor(
|
||||||
|
&buffer.anchor_before(self.position),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_proto(
|
||||||
|
message: Self::ProtoRequest,
|
||||||
|
_: ModelHandle<Project>,
|
||||||
|
buffer: ModelHandle<Buffer>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let position = message
|
||||||
|
.position
|
||||||
|
.and_then(deserialize_anchor)
|
||||||
|
.context("invalid position")?;
|
||||||
|
Ok(Self {
|
||||||
|
position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_to_proto(
|
||||||
|
response: ExpandedMacro,
|
||||||
|
_: &mut Project,
|
||||||
|
_: PeerId,
|
||||||
|
_: &clock::Global,
|
||||||
|
_: &mut AppContext,
|
||||||
|
) -> proto::LspExtExpandMacroResponse {
|
||||||
|
proto::LspExtExpandMacroResponse {
|
||||||
|
name: response.name,
|
||||||
|
expansion: response.expansion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn response_from_proto(
|
||||||
|
self,
|
||||||
|
message: proto::LspExtExpandMacroResponse,
|
||||||
|
_: ModelHandle<Project>,
|
||||||
|
_: ModelHandle<Buffer>,
|
||||||
|
_: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<ExpandedMacro> {
|
||||||
|
Ok(ExpandedMacro {
|
||||||
|
name: message.name,
|
||||||
|
expansion: message.expansion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {
|
||||||
|
message.buffer_id
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
mod ignore;
|
mod ignore;
|
||||||
mod lsp_command;
|
pub mod lsp_command;
|
||||||
|
pub mod lsp_ext_command;
|
||||||
mod prettier_support;
|
mod prettier_support;
|
||||||
pub mod project_settings;
|
pub mod project_settings;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
@ -174,7 +175,7 @@ struct DelayedDebounced {
|
||||||
cancel_channel: Option<oneshot::Sender<()>>,
|
cancel_channel: Option<oneshot::Sender<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LanguageServerToQuery {
|
pub enum LanguageServerToQuery {
|
||||||
Primary,
|
Primary,
|
||||||
Other(LanguageServerId),
|
Other(LanguageServerId),
|
||||||
}
|
}
|
||||||
|
@ -626,6 +627,7 @@ impl Project {
|
||||||
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
||||||
client.add_model_request_handler(Self::handle_save_buffer);
|
client.add_model_request_handler(Self::handle_save_buffer);
|
||||||
client.add_model_message_handler(Self::handle_update_diff_base);
|
client.add_model_message_handler(Self::handle_update_diff_base);
|
||||||
|
client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(
|
pub fn local(
|
||||||
|
@ -5863,7 +5865,7 @@ impl Project {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_lsp<R: LspCommand>(
|
pub fn request_lsp<R: LspCommand>(
|
||||||
&self,
|
&self,
|
||||||
buffer_handle: ModelHandle<Buffer>,
|
buffer_handle: ModelHandle<Buffer>,
|
||||||
server: LanguageServerToQuery,
|
server: LanguageServerToQuery,
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub(crate) trait LspCommand: 'static + Sized + Send {
|
pub trait LspCommand: 'static + Sized + Send {
|
||||||
type Response: 'static + Default + Send;
|
type Response: 'static + Default + Send;
|
||||||
type LspRequest: 'static + Send + lsp::request::Request;
|
type LspRequest: 'static + Send + lsp::request::Request;
|
||||||
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
type ProtoRequest: 'static + Send + proto::RequestMessage;
|
||||||
|
|
137
crates/project2/src/lsp_ext_command.rs
Normal file
137
crates/project2/src/lsp_ext_command.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use gpui::{AppContext, AsyncAppContext, Model};
|
||||||
|
use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
|
||||||
|
use lsp::{LanguageServer, LanguageServerId};
|
||||||
|
use rpc::proto::{self, PeerId};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use text::{PointUtf16, ToPointUtf16};
|
||||||
|
|
||||||
|
use crate::{lsp_command::LspCommand, Project};
|
||||||
|
|
||||||
|
pub enum LspExpandMacro {}
|
||||||
|
|
||||||
|
impl lsp::request::Request for LspExpandMacro {
|
||||||
|
type Params = ExpandMacroParams;
|
||||||
|
type Result = Option<ExpandedMacro>;
|
||||||
|
const METHOD: &'static str = "rust-analyzer/expandMacro";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ExpandMacroParams {
|
||||||
|
pub text_document: lsp::TextDocumentIdentifier,
|
||||||
|
pub position: lsp::Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ExpandedMacro {
|
||||||
|
pub name: String,
|
||||||
|
pub expansion: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExpandedMacro {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.name.is_empty() && self.expansion.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExpandMacro {
|
||||||
|
pub position: PointUtf16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait(?Send)]
|
||||||
|
impl LspCommand for ExpandMacro {
|
||||||
|
type Response = ExpandedMacro;
|
||||||
|
type LspRequest = LspExpandMacro;
|
||||||
|
type ProtoRequest = proto::LspExtExpandMacro;
|
||||||
|
|
||||||
|
fn to_lsp(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
_: &Buffer,
|
||||||
|
_: &Arc<LanguageServer>,
|
||||||
|
_: &AppContext,
|
||||||
|
) -> ExpandMacroParams {
|
||||||
|
ExpandMacroParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier {
|
||||||
|
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||||
|
},
|
||||||
|
position: point_to_lsp(self.position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn response_from_lsp(
|
||||||
|
self,
|
||||||
|
message: Option<ExpandedMacro>,
|
||||||
|
_: Model<Project>,
|
||||||
|
_: Model<Buffer>,
|
||||||
|
_: LanguageServerId,
|
||||||
|
_: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<ExpandedMacro> {
|
||||||
|
Ok(message
|
||||||
|
.map(|message| ExpandedMacro {
|
||||||
|
name: message.name,
|
||||||
|
expansion: message.expansion,
|
||||||
|
})
|
||||||
|
.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
|
||||||
|
proto::LspExtExpandMacro {
|
||||||
|
project_id,
|
||||||
|
buffer_id: buffer.remote_id(),
|
||||||
|
position: Some(language::proto::serialize_anchor(
|
||||||
|
&buffer.anchor_before(self.position),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_proto(
|
||||||
|
message: Self::ProtoRequest,
|
||||||
|
_: Model<Project>,
|
||||||
|
buffer: Model<Buffer>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let position = message
|
||||||
|
.position
|
||||||
|
.and_then(deserialize_anchor)
|
||||||
|
.context("invalid position")?;
|
||||||
|
Ok(Self {
|
||||||
|
position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_to_proto(
|
||||||
|
response: ExpandedMacro,
|
||||||
|
_: &mut Project,
|
||||||
|
_: PeerId,
|
||||||
|
_: &clock::Global,
|
||||||
|
_: &mut AppContext,
|
||||||
|
) -> proto::LspExtExpandMacroResponse {
|
||||||
|
proto::LspExtExpandMacroResponse {
|
||||||
|
name: response.name,
|
||||||
|
expansion: response.expansion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn response_from_proto(
|
||||||
|
self,
|
||||||
|
message: proto::LspExtExpandMacroResponse,
|
||||||
|
_: Model<Project>,
|
||||||
|
_: Model<Buffer>,
|
||||||
|
_: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<ExpandedMacro> {
|
||||||
|
Ok(ExpandedMacro {
|
||||||
|
name: message.name,
|
||||||
|
expansion: message.expansion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {
|
||||||
|
message.buffer_id
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
mod ignore;
|
mod ignore;
|
||||||
mod lsp_command;
|
pub mod lsp_command;
|
||||||
|
pub mod lsp_ext_command;
|
||||||
mod prettier_support;
|
mod prettier_support;
|
||||||
pub mod project_settings;
|
pub mod project_settings;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
@ -172,7 +173,7 @@ struct DelayedDebounced {
|
||||||
cancel_channel: Option<oneshot::Sender<()>>,
|
cancel_channel: Option<oneshot::Sender<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LanguageServerToQuery {
|
pub enum LanguageServerToQuery {
|
||||||
Primary,
|
Primary,
|
||||||
Other(LanguageServerId),
|
Other(LanguageServerId),
|
||||||
}
|
}
|
||||||
|
@ -623,6 +624,7 @@ impl Project {
|
||||||
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
||||||
client.add_model_request_handler(Self::handle_save_buffer);
|
client.add_model_request_handler(Self::handle_save_buffer);
|
||||||
client.add_model_message_handler(Self::handle_update_diff_base);
|
client.add_model_message_handler(Self::handle_update_diff_base);
|
||||||
|
client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local(
|
pub fn local(
|
||||||
|
@ -5933,7 +5935,7 @@ impl Project {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_lsp<R: LspCommand>(
|
pub fn request_lsp<R: LspCommand>(
|
||||||
&self,
|
&self,
|
||||||
buffer_handle: Model<Buffer>,
|
buffer_handle: Model<Buffer>,
|
||||||
server: LanguageServerToQuery,
|
server: LanguageServerToQuery,
|
||||||
|
|
|
@ -103,23 +103,26 @@ pub struct EntryDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
ExpandSelectedEntry,
|
project_panel,
|
||||||
CollapseSelectedEntry,
|
[
|
||||||
CollapseAllEntries,
|
ExpandSelectedEntry,
|
||||||
NewDirectory,
|
CollapseSelectedEntry,
|
||||||
NewFile,
|
CollapseAllEntries,
|
||||||
Copy,
|
NewDirectory,
|
||||||
CopyPath,
|
NewFile,
|
||||||
CopyRelativePath,
|
Copy,
|
||||||
RevealInFinder,
|
CopyPath,
|
||||||
OpenInTerminal,
|
CopyRelativePath,
|
||||||
Cut,
|
RevealInFinder,
|
||||||
Paste,
|
OpenInTerminal,
|
||||||
Delete,
|
Cut,
|
||||||
Rename,
|
Paste,
|
||||||
Open,
|
Delete,
|
||||||
ToggleFocus,
|
Rename,
|
||||||
NewSearchInDirectory,
|
Open,
|
||||||
|
ToggleFocus,
|
||||||
|
NewSearchInDirectory,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init_settings(cx: &mut AppContext) {
|
pub fn init_settings(cx: &mut AppContext) {
|
||||||
|
|
1
crates/recent_projects2/src/projects.rs
Normal file
1
crates/recent_projects2/src/projects.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
gpui::actions!(projects, [OpenRecent]);
|
|
@ -1,9 +1,10 @@
|
||||||
mod highlighted_workspace_location;
|
mod highlighted_workspace_location;
|
||||||
|
mod projects;
|
||||||
|
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task,
|
AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, View,
|
||||||
View, ViewContext, WeakView,
|
ViewContext, WeakView,
|
||||||
};
|
};
|
||||||
use highlighted_workspace_location::HighlightedWorkspaceLocation;
|
use highlighted_workspace_location::HighlightedWorkspaceLocation;
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
@ -12,11 +13,11 @@ use std::sync::Arc;
|
||||||
use ui::{prelude::*, ListItem};
|
use ui::{prelude::*, ListItem};
|
||||||
use util::paths::PathExt;
|
use util::paths::PathExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
|
notifications::simple_message_notification::MessageNotification, ModalView, Workspace,
|
||||||
WORKSPACE_DB,
|
WorkspaceLocation, WORKSPACE_DB,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(OpenRecent);
|
pub use projects::OpenRecent;
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(RecentProjects::register).detach();
|
cx.observe_new_views(RecentProjects::register).detach();
|
||||||
|
@ -26,6 +27,8 @@ pub struct RecentProjects {
|
||||||
picker: View<Picker<RecentProjectsDelegate>>,
|
picker: View<Picker<RecentProjectsDelegate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ModalView for RecentProjects {}
|
||||||
|
|
||||||
impl RecentProjects {
|
impl RecentProjects {
|
||||||
fn new(delegate: RecentProjectsDelegate, cx: &mut ViewContext<Self>) -> Self {
|
fn new(delegate: RecentProjectsDelegate, cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
use std::{ops::Range, sync::Arc};
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{AnyElement, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
|
use gpui::{
|
||||||
|
AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement,
|
||||||
|
SharedString, StyledText, UnderlineStyle, WindowContext,
|
||||||
|
};
|
||||||
use language::{HighlightId, Language, LanguageRegistry};
|
use language::{HighlightId, Language, LanguageRegistry};
|
||||||
|
use std::{ops::Range, sync::Arc};
|
||||||
|
use theme::ActiveTheme;
|
||||||
use util::RangeExt;
|
use util::RangeExt;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Highlight {
|
pub enum Highlight {
|
||||||
|
Code,
|
||||||
Id(HighlightId),
|
Id(HighlightId),
|
||||||
Highlight(HighlightStyle),
|
Highlight(HighlightStyle),
|
||||||
Mention,
|
Mention,
|
||||||
|
@ -28,24 +31,10 @@ impl From<HighlightId> for Highlight {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RichText {
|
pub struct RichText {
|
||||||
pub text: String,
|
pub text: SharedString,
|
||||||
pub highlights: Vec<(Range<usize>, Highlight)>,
|
pub highlights: Vec<(Range<usize>, Highlight)>,
|
||||||
pub region_ranges: Vec<Range<usize>>,
|
pub link_ranges: Vec<Range<usize>>,
|
||||||
pub regions: Vec<RenderedRegion>,
|
pub link_urls: Arc<[String]>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum BackgroundKind {
|
|
||||||
Code,
|
|
||||||
/// A mention background for non-self user.
|
|
||||||
Mention,
|
|
||||||
SelfMention,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct RenderedRegion {
|
|
||||||
pub background_kind: Option<BackgroundKind>,
|
|
||||||
pub link_url: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows one to specify extra links to the rendered markdown, which can be used
|
/// Allows one to specify extra links to the rendered markdown, which can be used
|
||||||
|
@ -56,94 +45,71 @@ pub struct Mention {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RichText {
|
impl RichText {
|
||||||
pub fn element(
|
pub fn element(&self, id: ElementId, cx: &mut WindowContext) -> AnyElement {
|
||||||
&self,
|
let theme = cx.theme();
|
||||||
// syntax: Arc<SyntaxTheme>,
|
let code_background = theme.colors().surface_background;
|
||||||
// style: RichTextStyle,
|
|
||||||
// cx: &mut ViewContext<V>,
|
|
||||||
) -> AnyElement {
|
|
||||||
todo!();
|
|
||||||
|
|
||||||
// let mut region_id = 0;
|
InteractiveText::new(
|
||||||
// let view_id = cx.view_id();
|
id,
|
||||||
|
StyledText::new(self.text.clone()).with_highlights(
|
||||||
// let regions = self.regions.clone();
|
&cx.text_style(),
|
||||||
|
self.highlights.iter().map(|(range, highlight)| {
|
||||||
// enum Markdown {}
|
(
|
||||||
// Text::new(self.text.clone(), style.text.clone())
|
range.clone(),
|
||||||
// .with_highlights(
|
match highlight {
|
||||||
// self.highlights
|
Highlight::Code => HighlightStyle {
|
||||||
// .iter()
|
background_color: Some(code_background),
|
||||||
// .filter_map(|(range, highlight)| {
|
..Default::default()
|
||||||
// let style = match highlight {
|
},
|
||||||
// Highlight::Id(id) => id.style(&syntax)?,
|
Highlight::Id(id) => HighlightStyle {
|
||||||
// Highlight::Highlight(style) => style.clone(),
|
background_color: Some(code_background),
|
||||||
// Highlight::Mention => style.mention_highlight,
|
..id.style(&theme.syntax()).unwrap_or_default()
|
||||||
// Highlight::SelfMention => style.self_mention_highlight,
|
},
|
||||||
// };
|
Highlight::Highlight(highlight) => *highlight,
|
||||||
// Some((range.clone(), style))
|
Highlight::Mention => HighlightStyle {
|
||||||
// })
|
font_weight: Some(FontWeight::BOLD),
|
||||||
// .collect::<Vec<_>>(),
|
..Default::default()
|
||||||
// )
|
},
|
||||||
// .with_custom_runs(self.region_ranges.clone(), move |ix, bounds, cx| {
|
Highlight::SelfMention => HighlightStyle {
|
||||||
// region_id += 1;
|
font_weight: Some(FontWeight::BOLD),
|
||||||
// let region = regions[ix].clone();
|
..Default::default()
|
||||||
// if let Some(url) = region.link_url {
|
},
|
||||||
// cx.scene().push_cursor_region(CursorRegion {
|
},
|
||||||
// bounds,
|
)
|
||||||
// style: CursorStyle::PointingHand,
|
}),
|
||||||
// });
|
),
|
||||||
// cx.scene().push_mouse_region(
|
)
|
||||||
// MouseRegion::new::<Markdown>(view_id, region_id, bounds)
|
.on_click(self.link_ranges.clone(), {
|
||||||
// .on_click::<V, _>(MouseButton::Left, move |_, _, cx| {
|
let link_urls = self.link_urls.clone();
|
||||||
// cx.platform().open_url(&url)
|
move |ix, cx| cx.open_url(&link_urls[ix])
|
||||||
// }),
|
})
|
||||||
// );
|
.into_any_element()
|
||||||
// }
|
|
||||||
// if let Some(region_kind) = ®ion.background_kind {
|
|
||||||
// let background = match region_kind {
|
|
||||||
// BackgroundKind::Code => style.code_background,
|
|
||||||
// BackgroundKind::Mention => style.mention_background,
|
|
||||||
// BackgroundKind::SelfMention => style.self_mention_background,
|
|
||||||
// };
|
|
||||||
// if background.is_some() {
|
|
||||||
// cx.scene().push_quad(gpui::Quad {
|
|
||||||
// bounds,
|
|
||||||
// background,
|
|
||||||
// border: Default::default(),
|
|
||||||
// corner_radii: (2.0).into(),
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// .with_soft_wrap(true)
|
|
||||||
// .into_any()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_mention(
|
// pub fn add_mention(
|
||||||
&mut self,
|
// &mut self,
|
||||||
range: Range<usize>,
|
// range: Range<usize>,
|
||||||
is_current_user: bool,
|
// is_current_user: bool,
|
||||||
mention_style: HighlightStyle,
|
// mention_style: HighlightStyle,
|
||||||
) -> anyhow::Result<()> {
|
// ) -> anyhow::Result<()> {
|
||||||
if range.end > self.text.len() {
|
// if range.end > self.text.len() {
|
||||||
bail!(
|
// bail!(
|
||||||
"Mention in range {range:?} is outside of bounds for a message of length {}",
|
// "Mention in range {range:?} is outside of bounds for a message of length {}",
|
||||||
self.text.len()
|
// self.text.len()
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
if is_current_user {
|
// if is_current_user {
|
||||||
self.region_ranges.push(range.clone());
|
// self.region_ranges.push(range.clone());
|
||||||
self.regions.push(RenderedRegion {
|
// self.regions.push(RenderedRegion {
|
||||||
background_kind: Some(BackgroundKind::Mention),
|
// background_kind: Some(BackgroundKind::Mention),
|
||||||
link_url: None,
|
// link_url: None,
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
self.highlights
|
// self.highlights
|
||||||
.push((range, Highlight::Highlight(mention_style)));
|
// .push((range, Highlight::Highlight(mention_style)));
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_markdown_mut(
|
pub fn render_markdown_mut(
|
||||||
|
@ -151,7 +117,10 @@ pub fn render_markdown_mut(
|
||||||
mut mentions: &[Mention],
|
mut mentions: &[Mention],
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
language: Option<&Arc<Language>>,
|
language: Option<&Arc<Language>>,
|
||||||
data: &mut RichText,
|
text: &mut String,
|
||||||
|
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
||||||
|
link_ranges: &mut Vec<Range<usize>>,
|
||||||
|
link_urls: &mut Vec<String>,
|
||||||
) {
|
) {
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||||
|
|
||||||
|
@ -163,18 +132,18 @@ pub fn render_markdown_mut(
|
||||||
|
|
||||||
let options = Options::all();
|
let options = Options::all();
|
||||||
for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() {
|
for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() {
|
||||||
let prev_len = data.text.len();
|
let prev_len = text.len();
|
||||||
match event {
|
match event {
|
||||||
Event::Text(t) => {
|
Event::Text(t) => {
|
||||||
if let Some(language) = ¤t_language {
|
if let Some(language) = ¤t_language {
|
||||||
render_code(&mut data.text, &mut data.highlights, t.as_ref(), language);
|
render_code(text, highlights, t.as_ref(), language);
|
||||||
} else {
|
} else {
|
||||||
if let Some(mention) = mentions.first() {
|
if let Some(mention) = mentions.first() {
|
||||||
if source_range.contains_inclusive(&mention.range) {
|
if source_range.contains_inclusive(&mention.range) {
|
||||||
mentions = &mentions[1..];
|
mentions = &mentions[1..];
|
||||||
let range = (prev_len + mention.range.start - source_range.start)
|
let range = (prev_len + mention.range.start - source_range.start)
|
||||||
..(prev_len + mention.range.end - source_range.start);
|
..(prev_len + mention.range.end - source_range.start);
|
||||||
data.highlights.push((
|
highlights.push((
|
||||||
range.clone(),
|
range.clone(),
|
||||||
if mention.is_self_mention {
|
if mention.is_self_mention {
|
||||||
Highlight::SelfMention
|
Highlight::SelfMention
|
||||||
|
@ -182,19 +151,10 @@ pub fn render_markdown_mut(
|
||||||
Highlight::Mention
|
Highlight::Mention
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
data.region_ranges.push(range);
|
|
||||||
data.regions.push(RenderedRegion {
|
|
||||||
background_kind: Some(if mention.is_self_mention {
|
|
||||||
BackgroundKind::SelfMention
|
|
||||||
} else {
|
|
||||||
BackgroundKind::Mention
|
|
||||||
}),
|
|
||||||
link_url: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.text.push_str(t.as_ref());
|
text.push_str(t.as_ref());
|
||||||
let mut style = HighlightStyle::default();
|
let mut style = HighlightStyle::default();
|
||||||
if bold_depth > 0 {
|
if bold_depth > 0 {
|
||||||
style.font_weight = Some(FontWeight::BOLD);
|
style.font_weight = Some(FontWeight::BOLD);
|
||||||
|
@ -203,11 +163,8 @@ pub fn render_markdown_mut(
|
||||||
style.font_style = Some(FontStyle::Italic);
|
style.font_style = Some(FontStyle::Italic);
|
||||||
}
|
}
|
||||||
if let Some(link_url) = link_url.clone() {
|
if let Some(link_url) = link_url.clone() {
|
||||||
data.region_ranges.push(prev_len..data.text.len());
|
link_ranges.push(prev_len..text.len());
|
||||||
data.regions.push(RenderedRegion {
|
link_urls.push(link_url);
|
||||||
link_url: Some(link_url),
|
|
||||||
background_kind: None,
|
|
||||||
});
|
|
||||||
style.underline = Some(UnderlineStyle {
|
style.underline = Some(UnderlineStyle {
|
||||||
thickness: 1.0.into(),
|
thickness: 1.0.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -216,27 +173,25 @@ pub fn render_markdown_mut(
|
||||||
|
|
||||||
if style != HighlightStyle::default() {
|
if style != HighlightStyle::default() {
|
||||||
let mut new_highlight = true;
|
let mut new_highlight = true;
|
||||||
if let Some((last_range, last_style)) = data.highlights.last_mut() {
|
if let Some((last_range, last_style)) = highlights.last_mut() {
|
||||||
if last_range.end == prev_len
|
if last_range.end == prev_len
|
||||||
&& last_style == &Highlight::Highlight(style)
|
&& last_style == &Highlight::Highlight(style)
|
||||||
{
|
{
|
||||||
last_range.end = data.text.len();
|
last_range.end = text.len();
|
||||||
new_highlight = false;
|
new_highlight = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if new_highlight {
|
if new_highlight {
|
||||||
data.highlights
|
highlights.push((prev_len..text.len(), Highlight::Highlight(style)));
|
||||||
.push((prev_len..data.text.len(), Highlight::Highlight(style)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Code(t) => {
|
Event::Code(t) => {
|
||||||
data.text.push_str(t.as_ref());
|
text.push_str(t.as_ref());
|
||||||
data.region_ranges.push(prev_len..data.text.len());
|
|
||||||
if link_url.is_some() {
|
if link_url.is_some() {
|
||||||
data.highlights.push((
|
highlights.push((
|
||||||
prev_len..data.text.len(),
|
prev_len..text.len(),
|
||||||
Highlight::Highlight(HighlightStyle {
|
Highlight::Highlight(HighlightStyle {
|
||||||
underline: Some(UnderlineStyle {
|
underline: Some(UnderlineStyle {
|
||||||
thickness: 1.0.into(),
|
thickness: 1.0.into(),
|
||||||
|
@ -246,19 +201,19 @@ pub fn render_markdown_mut(
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
data.regions.push(RenderedRegion {
|
if let Some(link_url) = link_url.clone() {
|
||||||
background_kind: Some(BackgroundKind::Code),
|
link_ranges.push(prev_len..text.len());
|
||||||
link_url: link_url.clone(),
|
link_urls.push(link_url);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
Event::Start(tag) => match tag {
|
Event::Start(tag) => match tag {
|
||||||
Tag::Paragraph => new_paragraph(&mut data.text, &mut list_stack),
|
Tag::Paragraph => new_paragraph(text, &mut list_stack),
|
||||||
Tag::Heading(_, _, _) => {
|
Tag::Heading(_, _, _) => {
|
||||||
new_paragraph(&mut data.text, &mut list_stack);
|
new_paragraph(text, &mut list_stack);
|
||||||
bold_depth += 1;
|
bold_depth += 1;
|
||||||
}
|
}
|
||||||
Tag::CodeBlock(kind) => {
|
Tag::CodeBlock(kind) => {
|
||||||
new_paragraph(&mut data.text, &mut list_stack);
|
new_paragraph(text, &mut list_stack);
|
||||||
current_language = if let CodeBlockKind::Fenced(language) = kind {
|
current_language = if let CodeBlockKind::Fenced(language) = kind {
|
||||||
language_registry
|
language_registry
|
||||||
.language_for_name(language.as_ref())
|
.language_for_name(language.as_ref())
|
||||||
|
@ -278,18 +233,18 @@ pub fn render_markdown_mut(
|
||||||
let len = list_stack.len();
|
let len = list_stack.len();
|
||||||
if let Some((list_number, has_content)) = list_stack.last_mut() {
|
if let Some((list_number, has_content)) = list_stack.last_mut() {
|
||||||
*has_content = false;
|
*has_content = false;
|
||||||
if !data.text.is_empty() && !data.text.ends_with('\n') {
|
if !text.is_empty() && !text.ends_with('\n') {
|
||||||
data.text.push('\n');
|
text.push('\n');
|
||||||
}
|
}
|
||||||
for _ in 0..len - 1 {
|
for _ in 0..len - 1 {
|
||||||
data.text.push_str(" ");
|
text.push_str(" ");
|
||||||
}
|
}
|
||||||
if let Some(number) = list_number {
|
if let Some(number) = list_number {
|
||||||
data.text.push_str(&format!("{}. ", number));
|
text.push_str(&format!("{}. ", number));
|
||||||
*number += 1;
|
*number += 1;
|
||||||
*has_content = false;
|
*has_content = false;
|
||||||
} else {
|
} else {
|
||||||
data.text.push_str("- ");
|
text.push_str("- ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,8 +259,8 @@ pub fn render_markdown_mut(
|
||||||
Tag::List(_) => drop(list_stack.pop()),
|
Tag::List(_) => drop(list_stack.pop()),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::HardBreak => data.text.push('\n'),
|
Event::HardBreak => text.push('\n'),
|
||||||
Event::SoftBreak => data.text.push(' '),
|
Event::SoftBreak => text.push(' '),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,18 +272,35 @@ pub fn render_markdown(
|
||||||
language_registry: &Arc<LanguageRegistry>,
|
language_registry: &Arc<LanguageRegistry>,
|
||||||
language: Option<&Arc<Language>>,
|
language: Option<&Arc<Language>>,
|
||||||
) -> RichText {
|
) -> RichText {
|
||||||
let mut data = RichText {
|
// let mut data = RichText {
|
||||||
text: Default::default(),
|
// text: Default::default(),
|
||||||
highlights: Default::default(),
|
// highlights: Default::default(),
|
||||||
region_ranges: Default::default(),
|
// region_ranges: Default::default(),
|
||||||
regions: Default::default(),
|
// regions: Default::default(),
|
||||||
};
|
// };
|
||||||
|
|
||||||
render_markdown_mut(&block, mentions, language_registry, language, &mut data);
|
let mut text = String::new();
|
||||||
|
let mut highlights = Vec::new();
|
||||||
|
let mut link_ranges = Vec::new();
|
||||||
|
let mut link_urls = Vec::new();
|
||||||
|
render_markdown_mut(
|
||||||
|
&block,
|
||||||
|
mentions,
|
||||||
|
language_registry,
|
||||||
|
language,
|
||||||
|
&mut text,
|
||||||
|
&mut highlights,
|
||||||
|
&mut link_ranges,
|
||||||
|
&mut link_urls,
|
||||||
|
);
|
||||||
|
text.truncate(text.trim_end().len());
|
||||||
|
|
||||||
data.text = data.text.trim().to_string();
|
RichText {
|
||||||
|
text: SharedString::from(text),
|
||||||
data
|
link_urls: link_urls.into(),
|
||||||
|
link_ranges,
|
||||||
|
highlights,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_code(
|
pub fn render_code(
|
||||||
|
@ -339,11 +311,19 @@ pub fn render_code(
|
||||||
) {
|
) {
|
||||||
let prev_len = text.len();
|
let prev_len = text.len();
|
||||||
text.push_str(content);
|
text.push_str(content);
|
||||||
|
let mut offset = 0;
|
||||||
for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
|
for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
|
||||||
|
if range.start > offset {
|
||||||
|
highlights.push((prev_len + offset..prev_len + range.start, Highlight::Code));
|
||||||
|
}
|
||||||
highlights.push((
|
highlights.push((
|
||||||
prev_len + range.start..prev_len + range.end,
|
prev_len + range.start..prev_len + range.end,
|
||||||
Highlight::Id(highlight_id),
|
Highlight::Id(highlight_id),
|
||||||
));
|
));
|
||||||
|
offset = range.end;
|
||||||
|
}
|
||||||
|
if offset < content.len() {
|
||||||
|
highlights.push((prev_len + offset..prev_len + content.len(), Highlight::Code));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,9 @@ message Envelope {
|
||||||
GetNotifications get_notifications = 150;
|
GetNotifications get_notifications = 150;
|
||||||
GetNotificationsResponse get_notifications_response = 151;
|
GetNotificationsResponse get_notifications_response = 151;
|
||||||
DeleteNotification delete_notification = 152;
|
DeleteNotification delete_notification = 152;
|
||||||
MarkNotificationRead mark_notification_read = 153; // Current max
|
MarkNotificationRead mark_notification_read = 153;
|
||||||
|
LspExtExpandMacro lsp_ext_expand_macro = 154;
|
||||||
|
LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1619,3 +1621,14 @@ message Notification {
|
||||||
bool is_read = 6;
|
bool is_read = 6;
|
||||||
optional bool response = 7;
|
optional bool response = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LspExtExpandMacro {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 buffer_id = 2;
|
||||||
|
Anchor position = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LspExtExpandMacroResponse {
|
||||||
|
string name = 1;
|
||||||
|
string expansion = 2;
|
||||||
|
}
|
||||||
|
|
|
@ -280,6 +280,8 @@ messages!(
|
||||||
(UpdateWorktree, Foreground),
|
(UpdateWorktree, Foreground),
|
||||||
(UpdateWorktreeSettings, Foreground),
|
(UpdateWorktreeSettings, Foreground),
|
||||||
(UsersResponse, Foreground),
|
(UsersResponse, Foreground),
|
||||||
|
(LspExtExpandMacro, Background),
|
||||||
|
(LspExtExpandMacroResponse, Background),
|
||||||
);
|
);
|
||||||
|
|
||||||
request_messages!(
|
request_messages!(
|
||||||
|
@ -363,6 +365,7 @@ request_messages!(
|
||||||
(UpdateParticipantLocation, Ack),
|
(UpdateParticipantLocation, Ack),
|
||||||
(UpdateProject, Ack),
|
(UpdateProject, Ack),
|
||||||
(UpdateWorktree, Ack),
|
(UpdateWorktree, Ack),
|
||||||
|
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
@ -415,6 +418,7 @@ entity_messages!(
|
||||||
UpdateProjectCollaborator,
|
UpdateProjectCollaborator,
|
||||||
UpdateWorktree,
|
UpdateWorktree,
|
||||||
UpdateWorktreeSettings,
|
UpdateWorktreeSettings,
|
||||||
|
LspExtExpandMacro,
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
|
|
@ -178,7 +178,9 @@ message Envelope {
|
||||||
GetNotifications get_notifications = 150;
|
GetNotifications get_notifications = 150;
|
||||||
GetNotificationsResponse get_notifications_response = 151;
|
GetNotificationsResponse get_notifications_response = 151;
|
||||||
DeleteNotification delete_notification = 152;
|
DeleteNotification delete_notification = 152;
|
||||||
MarkNotificationRead mark_notification_read = 153; // Current max
|
MarkNotificationRead mark_notification_read = 153;
|
||||||
|
LspExtExpandMacro lsp_ext_expand_macro = 154;
|
||||||
|
LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1619,3 +1621,14 @@ message Notification {
|
||||||
bool is_read = 6;
|
bool is_read = 6;
|
||||||
optional bool response = 7;
|
optional bool response = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LspExtExpandMacro {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 buffer_id = 2;
|
||||||
|
Anchor position = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LspExtExpandMacroResponse {
|
||||||
|
string name = 1;
|
||||||
|
string expansion = 2;
|
||||||
|
}
|
||||||
|
|
|
@ -280,6 +280,8 @@ messages!(
|
||||||
(UpdateWorktree, Foreground),
|
(UpdateWorktree, Foreground),
|
||||||
(UpdateWorktreeSettings, Foreground),
|
(UpdateWorktreeSettings, Foreground),
|
||||||
(UsersResponse, Foreground),
|
(UsersResponse, Foreground),
|
||||||
|
(LspExtExpandMacro, Background),
|
||||||
|
(LspExtExpandMacroResponse, Background),
|
||||||
);
|
);
|
||||||
|
|
||||||
request_messages!(
|
request_messages!(
|
||||||
|
@ -363,6 +365,7 @@ request_messages!(
|
||||||
(UpdateParticipantLocation, Ack),
|
(UpdateParticipantLocation, Ack),
|
||||||
(UpdateProject, Ack),
|
(UpdateProject, Ack),
|
||||||
(UpdateWorktree, Ack),
|
(UpdateWorktree, Ack),
|
||||||
|
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
@ -415,6 +418,7 @@ entity_messages!(
|
||||||
UpdateProjectCollaborator,
|
UpdateProjectCollaborator,
|
||||||
UpdateWorktree,
|
UpdateWorktree,
|
||||||
UpdateWorktreeSettings,
|
UpdateWorktreeSettings,
|
||||||
|
LspExtExpandMacro,
|
||||||
);
|
);
|
||||||
|
|
||||||
entity_messages!(
|
entity_messages!(
|
||||||
|
|
|
@ -1075,8 +1075,7 @@ impl ProjectSearchView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-activate the most recently activated search or the most recent if it has been closed.
|
// Add another search tab to the workspace.
|
||||||
// If no search exists in the workspace, create a new one.
|
|
||||||
fn deploy(
|
fn deploy(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
_: &workspace::NewSearch,
|
_: &workspace::NewSearch,
|
||||||
|
@ -1087,19 +1086,6 @@ impl ProjectSearchView {
|
||||||
state.0.retain(|project, _| project.is_upgradable(cx))
|
state.0.retain(|project, _| project.is_upgradable(cx))
|
||||||
});
|
});
|
||||||
|
|
||||||
let active_search = cx
|
|
||||||
.global::<ActiveSearches>()
|
|
||||||
.0
|
|
||||||
.get(&workspace.project().downgrade());
|
|
||||||
|
|
||||||
let existing = active_search
|
|
||||||
.and_then(|active_search| {
|
|
||||||
workspace
|
|
||||||
.items_of_type::<ProjectSearchView>(cx)
|
|
||||||
.find(|search| search == active_search)
|
|
||||||
})
|
|
||||||
.or_else(|| workspace.item_of_type::<ProjectSearchView>(cx));
|
|
||||||
|
|
||||||
let query = workspace.active_item(cx).and_then(|item| {
|
let query = workspace.active_item(cx).and_then(|item| {
|
||||||
let editor = item.act_as::<Editor>(cx)?;
|
let editor = item.act_as::<Editor>(cx)?;
|
||||||
let query = editor.query_suggestion(cx);
|
let query = editor.query_suggestion(cx);
|
||||||
|
@ -1110,28 +1096,22 @@ impl ProjectSearchView {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let search = if let Some(existing) = existing {
|
let settings = cx
|
||||||
workspace.activate_item(&existing, cx);
|
.global::<ActiveSettings>()
|
||||||
existing
|
.0
|
||||||
|
.get(&workspace.project().downgrade());
|
||||||
|
|
||||||
|
let settings = if let Some(settings) = settings {
|
||||||
|
Some(settings.clone())
|
||||||
} else {
|
} else {
|
||||||
let settings = cx
|
None
|
||||||
.global::<ActiveSettings>()
|
|
||||||
.0
|
|
||||||
.get(&workspace.project().downgrade());
|
|
||||||
|
|
||||||
let settings = if let Some(settings) = settings {
|
|
||||||
Some(settings.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
|
||||||
let view = cx.add_view(|cx| ProjectSearchView::new(model, cx, settings));
|
|
||||||
|
|
||||||
workspace.add_item(Box::new(view.clone()), cx);
|
|
||||||
view
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
||||||
|
let search = cx.add_view(|cx| ProjectSearchView::new(model, cx, settings));
|
||||||
|
|
||||||
|
workspace.add_item(Box::new(search.clone()), cx);
|
||||||
|
|
||||||
search.update(cx, |search, cx| {
|
search.update(cx, |search, cx| {
|
||||||
if let Some(query) = query {
|
if let Some(query) = query {
|
||||||
search.set_query(&query, cx);
|
search.set_query(&query, cx);
|
||||||
|
@ -2306,29 +2286,86 @@ pub mod tests {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
|
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
|
||||||
});
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
let Some(search_view_2) = cx.read(|cx| {
|
||||||
|
workspace
|
||||||
|
.read(cx)
|
||||||
|
.active_pane()
|
||||||
|
.read(cx)
|
||||||
|
.active_item()
|
||||||
|
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||||
|
}) else {
|
||||||
|
panic!("Search view expected to appear after new search event trigger")
|
||||||
|
};
|
||||||
|
let search_view_id_2 = search_view_2.id();
|
||||||
|
assert_ne!(
|
||||||
|
search_view_2, search_view,
|
||||||
|
"New search view should be open after `workspace::NewSearch` event"
|
||||||
|
);
|
||||||
|
|
||||||
search_view.update(cx, |search_view, cx| {
|
search_view.update(cx, |search_view, cx| {
|
||||||
assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
|
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO", "First search view should not have an updated query");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
search_view
|
search_view
|
||||||
.results_editor
|
.results_editor
|
||||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
|
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
|
||||||
"Results should be unchanged after search view 2nd open in a row"
|
"Results of the first search view should not update too"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
search_view.query_editor.is_focused(cx),
|
!search_view.query_editor.is_focused(cx),
|
||||||
"Focus should be moved into query editor again after search view 2nd open in a row"
|
"Focus should be moved away from the first search view"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
search_view_2.update(cx, |search_view_2, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
search_view_2.query_editor.read(cx).text(cx),
|
||||||
|
"two",
|
||||||
|
"New search view should get the query from the text cursor was at during the event spawn (first search view's first result)"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
search_view_2
|
||||||
|
.results_editor
|
||||||
|
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
"",
|
||||||
|
"No search results should be in the 2nd view yet, as we did not spawn a search for it"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
search_view_2.query_editor.is_focused(cx),
|
||||||
|
"Focus should be moved into query editor fo the new window"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
search_view_2.update(cx, |search_view_2, cx| {
|
||||||
|
search_view_2
|
||||||
|
.query_editor
|
||||||
|
.update(cx, |query_editor, cx| query_editor.set_text("FOUR", cx));
|
||||||
|
search_view_2.search(cx);
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
search_view_2.update(cx, |search_view_2, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
search_view_2
|
||||||
|
.results_editor
|
||||||
|
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
"\n\nconst FOUR: usize = one::ONE + three::THREE;",
|
||||||
|
"New search view with the updated query should have new search results"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
search_view_2.results_editor.is_focused(cx),
|
||||||
|
"Search view with mismatching query should be focused after search results are available",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
|
window.dispatch_action(search_view_id_2, &ToggleFocus, &mut cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
search_view.update(cx, |search_view, cx| {
|
search_view_2.update(cx, |search_view_2, cx| {
|
||||||
assert!(
|
assert!(
|
||||||
search_view.results_editor.is_focused(cx),
|
search_view_2.results_editor.is_focused(cx),
|
||||||
"Search view with matching query should switch focus to the results editor after the toggle focus event",
|
"Search view with matching query should switch focus to the results editor after the toggle focus event",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,15 +10,15 @@ use collections::HashMap;
|
||||||
use editor::{Editor, EditorMode};
|
use editor::{Editor, EditorMode};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, red, Action, AppContext, Div, EventEmitter, FocusableView,
|
actions, div, impl_actions, red, Action, AppContext, Div, EventEmitter, FocusableView,
|
||||||
InteractiveElement as _, IntoElement, ParentElement as _, Render, Styled, Subscription, Task,
|
InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, Styled,
|
||||||
View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
Subscription, Task, View, ViewContext, VisualContext as _, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use ui::{h_stack, Clickable, Icon, IconButton, IconElement};
|
use ui::{h_stack, ButtonCommon, Clickable, Icon, IconButton, IconElement, Tooltip};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::ItemHandle,
|
item::ItemHandle,
|
||||||
|
@ -26,12 +26,14 @@ use workspace::{
|
||||||
ToolbarItemLocation, ToolbarItemView,
|
ToolbarItemLocation, ToolbarItemView,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize, Default, Action)]
|
#[derive(PartialEq, Clone, Deserialize)]
|
||||||
pub struct Deploy {
|
pub struct Deploy {
|
||||||
pub focus: bool,
|
pub focus: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(Dismiss, FocusEditor);
|
impl_actions!(buffer_search, [Deploy]);
|
||||||
|
|
||||||
|
actions!(buffer_search, [Dismiss, FocusEditor]);
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
UpdateLocation,
|
UpdateLocation,
|
||||||
|
@ -131,13 +133,7 @@ impl Render for BufferSearchBar {
|
||||||
let search_button_for_mode = |mode| {
|
let search_button_for_mode = |mode| {
|
||||||
let is_active = self.current_mode == mode;
|
let is_active = self.current_mode == mode;
|
||||||
|
|
||||||
render_search_mode_button(
|
render_search_mode_button(mode, is_active)
|
||||||
mode,
|
|
||||||
is_active,
|
|
||||||
cx.listener(move |this, _, cx| {
|
|
||||||
this.activate_search_mode(mode, cx);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
let search_option_button = |option| {
|
let search_option_button = |option| {
|
||||||
let is_active = self.search_options.contains(option);
|
let is_active = self.search_options.contains(option);
|
||||||
|
@ -163,23 +159,35 @@ impl Render for BufferSearchBar {
|
||||||
});
|
});
|
||||||
let should_show_replace_input = self.replace_enabled && supported_options.replacement;
|
let should_show_replace_input = self.replace_enabled && supported_options.replacement;
|
||||||
let replace_all = should_show_replace_input
|
let replace_all = should_show_replace_input
|
||||||
.then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll));
|
.then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll, "Replace all"));
|
||||||
let replace_next = should_show_replace_input
|
let replace_next = should_show_replace_input.then(|| {
|
||||||
.then(|| super::render_replace_button(ReplaceNext, ui::Icon::ReplaceNext));
|
super::render_replace_button(ReplaceNext, ui::Icon::ReplaceNext, "Replace next")
|
||||||
|
});
|
||||||
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
|
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
|
||||||
|
|
||||||
|
let mut key_context = KeyContext::default();
|
||||||
|
key_context.add("BufferSearchBar");
|
||||||
|
if in_replace {
|
||||||
|
key_context.add("in_replace");
|
||||||
|
}
|
||||||
|
|
||||||
h_stack()
|
h_stack()
|
||||||
.key_context("BufferSearchBar")
|
.key_context(key_context)
|
||||||
.on_action(cx.listener(Self::previous_history_query))
|
.on_action(cx.listener(Self::previous_history_query))
|
||||||
.on_action(cx.listener(Self::next_history_query))
|
.on_action(cx.listener(Self::next_history_query))
|
||||||
.on_action(cx.listener(Self::dismiss))
|
.on_action(cx.listener(Self::dismiss))
|
||||||
.on_action(cx.listener(Self::select_next_match))
|
.on_action(cx.listener(Self::select_next_match))
|
||||||
.on_action(cx.listener(Self::select_prev_match))
|
.on_action(cx.listener(Self::select_prev_match))
|
||||||
|
.on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
|
||||||
|
this.activate_search_mode(SearchMode::Regex, cx);
|
||||||
|
}))
|
||||||
|
.on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
|
||||||
|
this.activate_search_mode(SearchMode::Text, cx);
|
||||||
|
}))
|
||||||
.when(self.supported_options().replacement, |this| {
|
.when(self.supported_options().replacement, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_replace))
|
this.on_action(cx.listener(Self::toggle_replace))
|
||||||
.when(in_replace, |this| {
|
.when(in_replace, |this| {
|
||||||
this.key_context("in_replace")
|
this.on_action(cx.listener(Self::replace_next))
|
||||||
.on_action(cx.listener(Self::replace_next))
|
|
||||||
.on_action(cx.listener(Self::replace_all))
|
.on_action(cx.listener(Self::replace_all))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -238,21 +246,19 @@ impl Render for BufferSearchBar {
|
||||||
h_stack()
|
h_stack()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.child(self.render_action_button(cx))
|
.child(self.render_action_button())
|
||||||
.children(match_count)
|
.children(match_count)
|
||||||
.child(render_nav_button(
|
.child(render_nav_button(
|
||||||
ui::Icon::ChevronLeft,
|
ui::Icon::ChevronLeft,
|
||||||
self.active_match_index.is_some(),
|
self.active_match_index.is_some(),
|
||||||
cx.listener(move |this, _, cx| {
|
"Select previous match",
|
||||||
this.select_prev_match(&Default::default(), cx);
|
&SelectPrevMatch,
|
||||||
}),
|
|
||||||
))
|
))
|
||||||
.child(render_nav_button(
|
.child(render_nav_button(
|
||||||
ui::Icon::ChevronRight,
|
ui::Icon::ChevronRight,
|
||||||
self.active_match_index.is_some(),
|
self.active_match_index.is_some(),
|
||||||
cx.listener(move |this, _, cx| {
|
"Select next match",
|
||||||
this.select_next_match(&Default::default(), cx);
|
&SelectNextMatch,
|
||||||
}),
|
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -597,14 +603,10 @@ impl BufferSearchBar {
|
||||||
self.update_matches(cx)
|
self.update_matches(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_action_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render_action_button(&self) -> impl IntoElement {
|
||||||
// let tooltip_style = theme.tooltip.clone();
|
IconButton::new("select-all", ui::Icon::SelectAll)
|
||||||
|
.on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
|
||||||
// let style = theme.search.action_button.clone();
|
.tooltip(|cx| Tooltip::for_action("Select all matches", &SelectAllMatches, cx))
|
||||||
|
|
||||||
IconButton::new("select-all", ui::Icon::SelectAll).on_click(cx.listener(|this, _, cx| {
|
|
||||||
this.select_all_matches(&SelectAllMatches, cx);
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
use gpui::{Action, SharedString};
|
||||||
|
|
||||||
|
use crate::{ActivateRegexMode, ActivateSemanticMode, ActivateTextMode};
|
||||||
|
|
||||||
// TODO: Update the default search mode to get from config
|
// TODO: Update the default search mode to get from config
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
pub enum SearchMode {
|
pub enum SearchMode {
|
||||||
|
@ -15,6 +19,16 @@ impl SearchMode {
|
||||||
SearchMode::Regex => "Regex",
|
SearchMode::Regex => "Regex",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(crate) fn tooltip(&self) -> SharedString {
|
||||||
|
format!("Activate {} Mode", self.label()).into()
|
||||||
|
}
|
||||||
|
pub(crate) fn action(&self) -> Box<dyn Action> {
|
||||||
|
match self {
|
||||||
|
SearchMode::Text => ActivateTextMode.boxed_clone(),
|
||||||
|
SearchMode::Semantic => ActivateSemanticMode.boxed_clone(),
|
||||||
|
SearchMode::Regex => ActivateRegexMode.boxed_clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
|
pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode,
|
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode,
|
||||||
ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
|
NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
||||||
SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace,
|
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
|
||||||
ToggleWholeWord,
|
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -45,7 +44,10 @@ use workspace::{
|
||||||
WorkspaceId,
|
WorkspaceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(SearchInNew, ToggleFocus, NextField, ToggleFilters,);
|
actions!(
|
||||||
|
project_search,
|
||||||
|
[SearchInNew, ToggleFocus, NextField, ToggleFilters]
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
|
struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
|
||||||
|
@ -58,7 +60,9 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.set_global(ActiveSearches::default());
|
cx.set_global(ActiveSearches::default());
|
||||||
cx.set_global(ActiveSettings::default());
|
cx.set_global(ActiveSettings::default());
|
||||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||||
workspace.register_action(ProjectSearchView::deploy);
|
workspace
|
||||||
|
.register_action(ProjectSearchView::deploy)
|
||||||
|
.register_action(ProjectSearchBar::search_in_new);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -1007,8 +1011,7 @@ impl ProjectSearchView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-activate the most recently activated search or the most recent if it has been closed.
|
// Add another search tab to the workspace.
|
||||||
// If no search exists in the workspace, create a new one.
|
|
||||||
fn deploy(
|
fn deploy(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
_: &workspace::NewSearch,
|
_: &workspace::NewSearch,
|
||||||
|
@ -1019,20 +1022,6 @@ impl ProjectSearchView {
|
||||||
state.0.retain(|project, _| project.is_upgradable())
|
state.0.retain(|project, _| project.is_upgradable())
|
||||||
});
|
});
|
||||||
|
|
||||||
let active_search = cx
|
|
||||||
.global::<ActiveSearches>()
|
|
||||||
.0
|
|
||||||
.get(&workspace.project().downgrade())
|
|
||||||
.and_then(WeakView::upgrade);
|
|
||||||
|
|
||||||
let existing = active_search
|
|
||||||
.and_then(|active_search| {
|
|
||||||
workspace
|
|
||||||
.items_of_type::<ProjectSearchView>(cx)
|
|
||||||
.find(|search| search == &active_search)
|
|
||||||
})
|
|
||||||
.or_else(|| workspace.item_of_type::<ProjectSearchView>(cx));
|
|
||||||
|
|
||||||
let query = workspace.active_item(cx).and_then(|item| {
|
let query = workspace.active_item(cx).and_then(|item| {
|
||||||
let editor = item.act_as::<Editor>(cx)?;
|
let editor = item.act_as::<Editor>(cx)?;
|
||||||
let query = editor.query_suggestion(cx);
|
let query = editor.query_suggestion(cx);
|
||||||
|
@ -1043,28 +1032,22 @@ impl ProjectSearchView {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let search = if let Some(existing) = existing {
|
let settings = cx
|
||||||
workspace.activate_item(&existing, cx);
|
.global::<ActiveSettings>()
|
||||||
existing
|
.0
|
||||||
|
.get(&workspace.project().downgrade());
|
||||||
|
|
||||||
|
let settings = if let Some(settings) = settings {
|
||||||
|
Some(settings.clone())
|
||||||
} else {
|
} else {
|
||||||
let settings = cx
|
None
|
||||||
.global::<ActiveSettings>()
|
|
||||||
.0
|
|
||||||
.get(&workspace.project().downgrade());
|
|
||||||
|
|
||||||
let settings = if let Some(settings) = settings {
|
|
||||||
Some(settings.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let model = cx.build_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
|
||||||
let view = cx.build_view(|cx| ProjectSearchView::new(model, cx, settings));
|
|
||||||
|
|
||||||
workspace.add_item(Box::new(view.clone()), cx);
|
|
||||||
view
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let model = cx.build_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
|
||||||
|
let search = cx.build_view(|cx| ProjectSearchView::new(model, cx, settings));
|
||||||
|
|
||||||
|
workspace.add_item(Box::new(search.clone()), cx);
|
||||||
|
|
||||||
search.update(cx, |search, cx| {
|
search.update(cx, |search, cx| {
|
||||||
if let Some(query) = query {
|
if let Some(query) = query {
|
||||||
search.set_query(&query, cx);
|
search.set_query(&query, cx);
|
||||||
|
@ -2436,29 +2419,86 @@ pub mod tests {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
|
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
|
||||||
});
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
let Some(search_view_2) = cx.read(|cx| {
|
||||||
|
workspace
|
||||||
|
.read(cx)
|
||||||
|
.active_pane()
|
||||||
|
.read(cx)
|
||||||
|
.active_item()
|
||||||
|
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||||
|
}) else {
|
||||||
|
panic!("Search view expected to appear after new search event trigger")
|
||||||
|
};
|
||||||
|
let search_view_id_2 = search_view_2.id();
|
||||||
|
assert_ne!(
|
||||||
|
search_view_2, search_view,
|
||||||
|
"New search view should be open after `workspace::NewSearch` event"
|
||||||
|
);
|
||||||
|
|
||||||
search_view.update(cx, |search_view, cx| {
|
search_view.update(cx, |search_view, cx| {
|
||||||
assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
|
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO", "First search view should not have an updated query");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
search_view
|
search_view
|
||||||
.results_editor
|
.results_editor
|
||||||
.update(cx, |editor, cx| editor.display_text(cx)),
|
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
|
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
|
||||||
"Results should be unchanged after search view 2nd open in a row"
|
"Results of the first search view should not update too"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
search_view.query_editor.is_focused(cx),
|
!search_view.query_editor.is_focused(cx),
|
||||||
"Focus should be moved into query editor again after search view 2nd open in a row"
|
"Focus should be moved away from the first search view"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
search_view_2.update(cx, |search_view_2, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
search_view_2.query_editor.read(cx).text(cx),
|
||||||
|
"two",
|
||||||
|
"New search view should get the query from the text cursor was at during the event spawn (first search view's first result)"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
search_view_2
|
||||||
|
.results_editor
|
||||||
|
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
"",
|
||||||
|
"No search results should be in the 2nd view yet, as we did not spawn a search for it"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
search_view_2.query_editor.is_focused(cx),
|
||||||
|
"Focus should be moved into query editor fo the new window"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
search_view_2.update(cx, |search_view_2, cx| {
|
||||||
|
search_view_2
|
||||||
|
.query_editor
|
||||||
|
.update(cx, |query_editor, cx| query_editor.set_text("FOUR", cx));
|
||||||
|
search_view_2.search(cx);
|
||||||
|
});
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
search_view_2.update(cx, |search_view_2, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
search_view_2
|
||||||
|
.results_editor
|
||||||
|
.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
"\n\nconst FOUR: usize = one::ONE + three::THREE;",
|
||||||
|
"New search view with the updated query should have new search results"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
search_view_2.results_editor.is_focused(cx),
|
||||||
|
"Search view with mismatching query should be focused after search results are available",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
|
window.dispatch_action(search_view_id_2, &ToggleFocus, &mut cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
search_view.update(cx, |search_view, cx| {
|
search_view_id_2.update(cx, |search_view_2, cx| {
|
||||||
assert!(
|
assert!(
|
||||||
search_view.results_editor.is_focused(cx),
|
search_view_2.results_editor.is_focused(cx),
|
||||||
"Search view with matching query should switch focus to the results editor after the toggle focus event",
|
"Search view with matching query should switch focus to the results editor after the toggle focus event",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ pub use buffer_search::BufferSearchBar;
|
||||||
use gpui::{actions, Action, AppContext, IntoElement};
|
use gpui::{actions, Action, AppContext, IntoElement};
|
||||||
pub use mode::SearchMode;
|
pub use mode::SearchMode;
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, Tooltip};
|
||||||
use ui::{ButtonStyle, Icon, IconButton};
|
use ui::{ButtonStyle, Icon, IconButton};
|
||||||
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
//pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||||
// use theme::components::{
|
// use theme::components::{
|
||||||
|
@ -22,20 +22,23 @@ pub fn init(cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
CycleMode,
|
search,
|
||||||
ToggleWholeWord,
|
[
|
||||||
ToggleCaseSensitive,
|
CycleMode,
|
||||||
ToggleReplace,
|
ToggleWholeWord,
|
||||||
SelectNextMatch,
|
ToggleCaseSensitive,
|
||||||
SelectPrevMatch,
|
ToggleReplace,
|
||||||
SelectAllMatches,
|
SelectNextMatch,
|
||||||
NextHistoryQuery,
|
SelectPrevMatch,
|
||||||
PreviousHistoryQuery,
|
SelectAllMatches,
|
||||||
ActivateTextMode,
|
NextHistoryQuery,
|
||||||
ActivateSemanticMode,
|
PreviousHistoryQuery,
|
||||||
ActivateRegexMode,
|
ActivateTextMode,
|
||||||
ReplaceAll,
|
ActivateSemanticMode,
|
||||||
ReplaceNext,
|
ActivateRegexMode,
|
||||||
|
ReplaceAll,
|
||||||
|
ReplaceNext,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
@ -85,7 +88,7 @@ impl SearchOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_button(&self, active: bool) -> impl IntoElement {
|
pub fn as_button(&self, active: bool) -> impl IntoElement {
|
||||||
IconButton::new(0, self.icon())
|
IconButton::new(self.label(), self.icon())
|
||||||
.on_click({
|
.on_click({
|
||||||
let action = self.to_toggle_action();
|
let action = self.to_toggle_action();
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
|
@ -94,26 +97,38 @@ impl SearchOptions {
|
||||||
})
|
})
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.when(active, |button| button.style(ButtonStyle::Filled))
|
.when(active, |button| button.style(ButtonStyle::Filled))
|
||||||
|
.tooltip({
|
||||||
|
let action = self.to_toggle_action();
|
||||||
|
let label: SharedString = format!("Toggle {}", self.label()).into();
|
||||||
|
move |cx| Tooltip::for_action(label.clone(), &*action, cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_replace_button(active: bool) -> impl IntoElement {
|
fn toggle_replace_button(active: bool) -> impl IntoElement {
|
||||||
// todo: add toggle_replace button
|
// todo: add toggle_replace button
|
||||||
IconButton::new(0, Icon::Replace)
|
IconButton::new("buffer-search-bar-toggle-replace-button", Icon::Replace)
|
||||||
.on_click(|_, cx| {
|
.on_click(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(ToggleReplace));
|
cx.dispatch_action(Box::new(ToggleReplace));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.style(ButtonStyle::Subtle)
|
.style(ButtonStyle::Subtle)
|
||||||
.when(active, |button| button.style(ButtonStyle::Filled))
|
.when(active, |button| button.style(ButtonStyle::Filled))
|
||||||
|
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_replace_button(
|
fn render_replace_button(
|
||||||
action: impl Action + 'static + Send + Sync,
|
action: impl Action + 'static + Send + Sync,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
|
tooltip: &'static str,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
// todo: add tooltip
|
let id: SharedString = format!("search-replace-{}", action.name()).into();
|
||||||
IconButton::new(0, icon).on_click(move |_, cx| {
|
IconButton::new(id, icon)
|
||||||
cx.dispatch_action(action.boxed_clone());
|
.tooltip({
|
||||||
})
|
let action = action.boxed_clone();
|
||||||
|
move |cx| Tooltip::for_action(tooltip, &*action, cx)
|
||||||
|
})
|
||||||
|
.on_click(move |_, cx| {
|
||||||
|
cx.dispatch_action(action.boxed_clone());
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,36 @@
|
||||||
use gpui::{ClickEvent, IntoElement, WindowContext};
|
use gpui::{Action, IntoElement};
|
||||||
use ui::prelude::*;
|
use ui::{prelude::*, Tooltip};
|
||||||
use ui::{Button, IconButton};
|
use ui::{Button, IconButton};
|
||||||
|
|
||||||
use crate::mode::SearchMode;
|
use crate::mode::SearchMode;
|
||||||
|
|
||||||
pub(super) fn render_nav_button(
|
pub(super) fn render_nav_button(
|
||||||
icon: ui::Icon,
|
icon: ui::Icon,
|
||||||
_active: bool,
|
active: bool,
|
||||||
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
tooltip: &'static str,
|
||||||
|
action: &'static dyn Action,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
// let tooltip_style = cx.theme().tooltip.clone();
|
IconButton::new(
|
||||||
// let cursor_style = if active {
|
SharedString::from(format!("search-nav-button-{}", action.name())),
|
||||||
// CursorStyle::PointingHand
|
icon,
|
||||||
// } else {
|
)
|
||||||
// CursorStyle::default()
|
.on_click(|_, cx| cx.dispatch_action(action.boxed_clone()))
|
||||||
// };
|
.tooltip(move |cx| Tooltip::for_action(tooltip, action, cx))
|
||||||
// enum NavButton {}
|
.disabled(!active)
|
||||||
IconButton::new("search-nav-button", icon).on_click(on_click)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_search_mode_button(
|
pub(crate) fn render_search_mode_button(mode: SearchMode, is_active: bool) -> Button {
|
||||||
mode: SearchMode,
|
|
||||||
is_active: bool,
|
|
||||||
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
|
||||||
) -> Button {
|
|
||||||
Button::new(mode.label(), mode.label())
|
Button::new(mode.label(), mode.label())
|
||||||
.selected(is_active)
|
.selected(is_active)
|
||||||
.on_click(on_click)
|
.on_click({
|
||||||
|
let action = mode.action();
|
||||||
|
move |_, cx| {
|
||||||
|
cx.dispatch_action(action.boxed_clone());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.tooltip({
|
||||||
|
let action = mode.action();
|
||||||
|
let tooltip_text = mode.tooltip();
|
||||||
|
move |cx| Tooltip::for_action(tooltip_text.clone(), &*action, cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
|
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::BTreeMap;
|
use collections::BTreeMap;
|
||||||
use gpui::{actions, Action, AppContext, KeyBinding, SharedString};
|
use gpui::{Action, AppContext, KeyBinding, SharedString};
|
||||||
use schemars::{
|
use schemars::{
|
||||||
gen::{SchemaGenerator, SchemaSettings},
|
gen::{SchemaGenerator, SchemaSettings},
|
||||||
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
|
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
|
||||||
|
@ -137,10 +137,8 @@ impl KeymapFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actions!(NoAction);
|
|
||||||
|
|
||||||
fn no_action() -> Box<dyn gpui::Action> {
|
fn no_action() -> Box<dyn gpui::Action> {
|
||||||
NoAction.boxed_clone()
|
gpui::NoAction.boxed_clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
|
|
||||||
actions!(ActionA, ActionB, ActionC);
|
actions!(focus, [ActionA, ActionB, ActionC]);
|
||||||
|
|
||||||
pub struct FocusStory {
|
pub struct FocusStory {
|
||||||
child_1_focus: FocusHandle,
|
child_1_focus: FocusHandle,
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub enum ComponentStory {
|
||||||
ListHeader,
|
ListHeader,
|
||||||
ListItem,
|
ListItem,
|
||||||
Scroll,
|
Scroll,
|
||||||
|
Tab,
|
||||||
Text,
|
Text,
|
||||||
ZIndex,
|
ZIndex,
|
||||||
Picker,
|
Picker,
|
||||||
|
@ -53,6 +54,7 @@ impl ComponentStory {
|
||||||
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
|
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
|
||||||
Self::Scroll => ScrollStory::view(cx).into(),
|
Self::Scroll => ScrollStory::view(cx).into(),
|
||||||
Self::Text => TextStory::view(cx).into(),
|
Self::Text => TextStory::view(cx).into(),
|
||||||
|
Self::Tab => cx.build_view(|_| ui::TabStory).into(),
|
||||||
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
|
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
|
||||||
Self::Picker => PickerStory::new(cx).into(),
|
Self::Picker => PickerStory::new(cx).into(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ use std::{
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
|
actions, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke,
|
||||||
ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||||
Point, ScrollWheelEvent, Size, Task, TouchPhase,
|
Point, ScrollWheelEvent, Size, Task, TouchPhase,
|
||||||
};
|
};
|
||||||
|
@ -58,6 +58,19 @@ use gpui::{
|
||||||
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
|
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
actions!(
|
||||||
|
terminal,
|
||||||
|
[
|
||||||
|
Clear,
|
||||||
|
Copy,
|
||||||
|
Paste,
|
||||||
|
ShowCharacterPalette,
|
||||||
|
SearchTest,
|
||||||
|
SendText,
|
||||||
|
SendKeystroke,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
|
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
|
||||||
///Scroll multiplier that is set to 3 by default. This will be removed when I
|
///Scroll multiplier that is set to 3 by default. This will be removed when I
|
||||||
///Implement scroll bars.
|
///Implement scroll bars.
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
|
use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
black, div, point, px, red, relative, transparent_black, AnyElement, AsyncWindowContext,
|
black, div, point, px, red, relative, transparent_black, AnyElement, AsyncWindowContext,
|
||||||
AvailableSpace, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle,
|
AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font,
|
||||||
FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement,
|
FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState,
|
||||||
LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
|
IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels,
|
||||||
PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled,
|
PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled,
|
||||||
TextRun, TextStyle, TextSystem, UnderlineStyle, View, WhiteSpace, WindowContext,
|
TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::CursorShape;
|
use language::CursorShape;
|
||||||
|
@ -27,8 +27,6 @@ use ui::Tooltip;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::{fmt::Debug, ops::RangeInclusive};
|
use std::{fmt::Debug, ops::RangeInclusive};
|
||||||
|
|
||||||
use crate::TerminalView;
|
|
||||||
|
|
||||||
///The information generated during layout that is necessary for painting
|
///The information generated during layout that is necessary for painting
|
||||||
pub struct LayoutState {
|
pub struct LayoutState {
|
||||||
cells: Vec<LayoutCell>,
|
cells: Vec<LayoutCell>,
|
||||||
|
@ -149,7 +147,6 @@ impl LayoutRect {
|
||||||
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
|
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
|
||||||
pub struct TerminalElement {
|
pub struct TerminalElement {
|
||||||
terminal: Model<Terminal>,
|
terminal: Model<Terminal>,
|
||||||
terminal_view: View<TerminalView>,
|
|
||||||
focus: FocusHandle,
|
focus: FocusHandle,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
|
@ -168,7 +165,6 @@ impl StatefulInteractiveElement for TerminalElement {}
|
||||||
impl TerminalElement {
|
impl TerminalElement {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
terminal: Model<Terminal>,
|
terminal: Model<Terminal>,
|
||||||
terminal_view: View<TerminalView>,
|
|
||||||
focus: FocusHandle,
|
focus: FocusHandle,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
|
@ -176,7 +172,6 @@ impl TerminalElement {
|
||||||
) -> TerminalElement {
|
) -> TerminalElement {
|
||||||
TerminalElement {
|
TerminalElement {
|
||||||
terminal,
|
terminal,
|
||||||
terminal_view,
|
|
||||||
focused,
|
focused,
|
||||||
focus: focus.clone(),
|
focus: focus.clone(),
|
||||||
cursor_visible,
|
cursor_visible,
|
||||||
|
@ -474,6 +469,7 @@ impl TerminalElement {
|
||||||
.size_full()
|
.size_full()
|
||||||
.id("terminal-element")
|
.id("terminal-element")
|
||||||
.tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
|
.tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
|
||||||
|
.into_any_element()
|
||||||
});
|
});
|
||||||
|
|
||||||
let TerminalContent {
|
let TerminalContent {
|
||||||
|
@ -575,7 +571,7 @@ impl TerminalElement {
|
||||||
relative_highlighted_ranges,
|
relative_highlighted_ranges,
|
||||||
mode: *mode,
|
mode: *mode,
|
||||||
display_offset: *display_offset,
|
display_offset: *display_offset,
|
||||||
hyperlink_tooltip: None, // todo!(tooltips)
|
hyperlink_tooltip,
|
||||||
gutter,
|
gutter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -643,13 +639,11 @@ impl TerminalElement {
|
||||||
let connection = connection.clone();
|
let connection = connection.clone();
|
||||||
let focus = focus.clone();
|
let focus = focus.clone();
|
||||||
move |e, cx| {
|
move |e, cx| {
|
||||||
if e.pressed_button.is_some() {
|
if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() {
|
||||||
if focus.is_focused(cx) {
|
connection.update(cx, |terminal, cx| {
|
||||||
connection.update(cx, |terminal, cx| {
|
terminal.mouse_drag(e, origin, bounds);
|
||||||
terminal.mouse_drag(e, origin, bounds);
|
cx.notify();
|
||||||
cx.notify();
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -776,18 +770,11 @@ impl Element for TerminalElement {
|
||||||
(layout_id, interactive_state)
|
(layout_id, interactive_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext<'_>) {
|
||||||
mut self,
|
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
state: &mut Self::State,
|
|
||||||
cx: &mut WindowContext<'_>,
|
|
||||||
) {
|
|
||||||
let mut layout = self.compute_layout(bounds, cx);
|
let mut layout = self.compute_layout(bounds, cx);
|
||||||
|
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
|
|
||||||
let dispatch_context = self.terminal_view.read(cx).dispatch_context(cx);
|
|
||||||
self.interactivity().key_context = Some(dispatch_context);
|
|
||||||
cx.paint_quad(
|
cx.paint_quad(
|
||||||
bounds,
|
bounds,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
@ -806,7 +793,28 @@ impl Element for TerminalElement {
|
||||||
.map(|cursor| cursor.bounding_rect(origin)),
|
.map(|cursor| cursor.bounding_rect(origin)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx);
|
let terminal_focus_handle = self.focus.clone();
|
||||||
|
let terminal_handle = self.terminal.clone();
|
||||||
|
let mut this: TerminalElement = self
|
||||||
|
.register_mouse_listeners(origin, layout.mode, bounds, cx)
|
||||||
|
.drag_over::<ExternalPaths>(|style| {
|
||||||
|
// todo!() why does not it work? z-index of elements?
|
||||||
|
style.bg(cx.theme().colors().ghost_element_hover)
|
||||||
|
})
|
||||||
|
.on_drop::<ExternalPaths>(move |external_paths, cx| {
|
||||||
|
cx.focus(&terminal_focus_handle);
|
||||||
|
let mut new_text = external_paths
|
||||||
|
.read(cx)
|
||||||
|
.paths()
|
||||||
|
.iter()
|
||||||
|
.map(|path| format!(" {path:?}"))
|
||||||
|
.join("");
|
||||||
|
new_text.push(' ');
|
||||||
|
terminal_handle.update(cx, |terminal, _| {
|
||||||
|
// todo!() long paths are not displayed properly albeit the text is there
|
||||||
|
terminal.paste(&new_text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let interactivity = mem::take(&mut this.interactivity);
|
let interactivity = mem::take(&mut this.interactivity);
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ use anyhow::Result;
|
||||||
|
|
||||||
const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel";
|
const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel";
|
||||||
|
|
||||||
actions!(ToggleFocus);
|
actions!(terminal_view, [ToggleFocus]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.observe_new_views(
|
cx.observe_new_views(
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue