Compare commits
4 Commits
preserve-t
...
tasks-moda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d895f53337 | ||
|
|
3eb0418bda | ||
|
|
8b57d6d4c6 | ||
|
|
af8641ce5b |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -8067,6 +8067,7 @@ name = "recent_projects"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
@@ -10152,6 +10153,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"menu",
|
||||
"parking_lot",
|
||||
"picker",
|
||||
"project",
|
||||
"schemars",
|
||||
@@ -10159,6 +10161,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"task",
|
||||
"text",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"ui",
|
||||
|
||||
@@ -49,6 +49,11 @@ pub trait UpdateGlobal {
|
||||
where
|
||||
C: BorrowAppContext,
|
||||
F: FnOnce(&mut Self, &mut C) -> R;
|
||||
|
||||
/// Set the global instance of the implementing type.
|
||||
fn set_global<C>(cx: &mut C, global: Self)
|
||||
where
|
||||
C: BorrowAppContext;
|
||||
}
|
||||
|
||||
impl<T: Global> UpdateGlobal for T {
|
||||
@@ -59,4 +64,11 @@ impl<T: Global> UpdateGlobal for T {
|
||||
{
|
||||
cx.update_global(update)
|
||||
}
|
||||
|
||||
fn set_global<C>(cx: &mut C, global: Self)
|
||||
where
|
||||
C: BorrowAppContext,
|
||||
{
|
||||
cx.set_global(global)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use editor::{scroll::Autoscroll, Editor};
|
||||
use editor::{scroll::Autoscroll, CompletionProvider, Editor};
|
||||
use gpui::{
|
||||
actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent,
|
||||
DismissEvent, EventEmitter, FocusHandle, FocusableView, Length, ListState, MouseButton,
|
||||
@@ -166,6 +166,17 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
Self::new(delegate, ContainerKind::List, head, cx)
|
||||
}
|
||||
|
||||
/// Adds a completion provider for this pickers query editor, if it has one.
|
||||
pub fn with_completions_provider(
|
||||
self,
|
||||
provider: Box<dyn CompletionProvider>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> Self {
|
||||
if let Head::Editor(editor) = &self.head {
|
||||
editor.update(cx, |this, _| this.set_completion_provider(provider))
|
||||
}
|
||||
self
|
||||
}
|
||||
fn new(delegate: D, container: ContainerKind, head: Head, cx: &mut ViewContext<Self>) -> Self {
|
||||
let mut this = Self {
|
||||
delegate,
|
||||
|
||||
@@ -14,6 +14,7 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Context;
|
||||
use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerProjectId};
|
||||
use editor::Editor;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use feature_flags::FeatureFlagViewExt;
|
||||
use gpui::AsyncWindowContext;
|
||||
use gpui::Subscription;
|
||||
use gpui::Task;
|
||||
use gpui::WeakView;
|
||||
@@ -47,9 +49,9 @@ pub struct DevServerProjects {
|
||||
_dev_server_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Default)]
|
||||
struct CreateDevServer {
|
||||
creating: bool,
|
||||
creating: Option<Task<()>>,
|
||||
dev_server_id: Option<DevServerId>,
|
||||
access_token: Option<String>,
|
||||
manual_setup: bool,
|
||||
@@ -312,95 +314,77 @@ impl DevServerProjects {
|
||||
});
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let store = dev_server_projects::Store::global(cx);
|
||||
|
||||
cx.spawn({
|
||||
let access_token = access_token.clone();
|
||||
|this, mut cx| async move {
|
||||
let result = dev_server.await;
|
||||
let task = cx
|
||||
.spawn({
|
||||
|this, mut cx| async move {
|
||||
let result = dev_server.await;
|
||||
|
||||
match result {
|
||||
Ok(dev_server) => {
|
||||
if let Some(ssh_connection_string) = ssh_connection_string {
|
||||
match result {
|
||||
Ok(dev_server) => {
|
||||
if let Some(ssh_connection_string) = ssh_connection_string {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Mode::CreateDevServer(CreateDevServer {
|
||||
access_token,
|
||||
dev_server_id,
|
||||
..
|
||||
}) = &mut this.mode
|
||||
{
|
||||
access_token.replace(dev_server.access_token.clone());
|
||||
dev_server_id
|
||||
.replace(DevServerId(dev_server.dev_server_id));
|
||||
}
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
let access_token = access_token.clone();
|
||||
this.update(&mut cx, |this, cx| {
|
||||
spawn_ssh_task(
|
||||
workspace
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("workspace dropped"))?,
|
||||
store,
|
||||
DevServerId(dev_server.dev_server_id),
|
||||
ssh_connection_string,
|
||||
dev_server.access_token.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.focus_handle.focus(cx);
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: true,
|
||||
creating: None,
|
||||
dev_server_id: Some(DevServerId(dev_server.dev_server_id)),
|
||||
access_token: Some(access_token.unwrap_or(dev_server.access_token.clone())),
|
||||
manual_setup: false,
|
||||
});
|
||||
access_token: Some(dev_server.access_token),
|
||||
manual_setup,
|
||||
});
|
||||
cx.notify();
|
||||
})?;
|
||||
let terminal_panel = workspace
|
||||
.update(&mut cx, |workspace, cx| workspace.panel::<TerminalPanel>(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.with_context(|| anyhow::anyhow!("No terminal panel"))?;
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: None,
|
||||
dev_server_id: existing_id,
|
||||
access_token: None,
|
||||
manual_setup,
|
||||
});
|
||||
cx.notify()
|
||||
})
|
||||
.log_err();
|
||||
|
||||
let command = "sh".to_string();
|
||||
let args = vec!["-x".to_string(),"-c".to_string(),
|
||||
format!(r#"~/.local/bin/zed -v >/dev/stderr || (curl -sSL https://zed.dev/install.sh || wget -qO- https://zed.dev/install.sh) | bash && ~/.local/bin/zed --dev-server-token {}"#, dev_server.access_token)];
|
||||
|
||||
let terminal = terminal_panel.update(&mut cx, |terminal_panel, cx| {
|
||||
terminal_panel.spawn_in_new_terminal(
|
||||
SpawnInTerminal {
|
||||
id: task::TaskId("ssh-remote".into()),
|
||||
full_label: "Install zed over ssh".into(),
|
||||
label: "Install zed over ssh".into(),
|
||||
command,
|
||||
args,
|
||||
command_label: ssh_connection_string.clone(),
|
||||
cwd: Some(TerminalWorkDir::Ssh { ssh_command: ssh_connection_string, path: None }),
|
||||
env: Default::default(),
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: false,
|
||||
reveal: RevealStrategy::Always,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?.await?;
|
||||
|
||||
terminal.update(&mut cx, |terminal, cx| {
|
||||
terminal.wait_for_completed_task(cx)
|
||||
})?.await;
|
||||
|
||||
// There's a race-condition between the task completing successfully, and the server sending us the online status. Make it less likely we'll show the error state.
|
||||
if this.update(&mut cx, |this, cx| {
|
||||
this.dev_server_store.read(cx).dev_server_status(DevServerId(dev_server.dev_server_id))
|
||||
})? == DevServerStatus::Offline {
|
||||
cx.background_executor().timer(Duration::from_millis(200)).await
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.focus_handle.focus(cx);
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: false,
|
||||
dev_server_id: Some(DevServerId(dev_server.dev_server_id)),
|
||||
access_token: Some(dev_server.access_token),
|
||||
manual_setup,
|
||||
});
|
||||
cx.notify();
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer { creating:false, dev_server_id: existing_id, access_token: None, manual_setup });
|
||||
cx.notify()
|
||||
})
|
||||
.log_err();
|
||||
|
||||
return Err(e)
|
||||
}
|
||||
}
|
||||
}})
|
||||
.detach_and_prompt_err("Failed to create server", cx, |_, _| None);
|
||||
}
|
||||
})
|
||||
.prompt_err("Failed to create server", cx, |_, _| None);
|
||||
|
||||
self.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: true,
|
||||
creating: Some(task),
|
||||
dev_server_id: existing_id,
|
||||
access_token,
|
||||
manual_setup,
|
||||
@@ -502,7 +486,7 @@ impl DevServerProjects {
|
||||
self.create_dev_server_project(create_project.dev_server_id, cx);
|
||||
}
|
||||
Mode::CreateDevServer(state) => {
|
||||
if !state.creating {
|
||||
if state.creating.is_none() || state.dev_server_id.is_some() {
|
||||
self.create_or_update_dev_server(
|
||||
state.manual_setup,
|
||||
state.dev_server_id,
|
||||
@@ -579,7 +563,7 @@ impl DevServerProjects {
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
dev_server_id: Some(dev_server_id),
|
||||
creating: false,
|
||||
creating: None,
|
||||
access_token: None,
|
||||
manual_setup,
|
||||
});
|
||||
@@ -714,16 +698,14 @@ impl DevServerProjects {
|
||||
}
|
||||
|
||||
fn render_create_dev_server(
|
||||
&mut self,
|
||||
state: CreateDevServer,
|
||||
&self,
|
||||
state: &CreateDevServer,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let CreateDevServer {
|
||||
creating,
|
||||
dev_server_id,
|
||||
access_token,
|
||||
manual_setup,
|
||||
} = state.clone();
|
||||
let creating = state.creating.is_some();
|
||||
let dev_server_id = state.dev_server_id;
|
||||
let access_token = state.access_token.clone();
|
||||
let manual_setup = state.manual_setup;
|
||||
|
||||
let status = dev_server_id
|
||||
.map(|id| self.dev_server_store.read(cx).dev_server_status(id))
|
||||
@@ -769,13 +751,11 @@ impl DevServerProjects {
|
||||
Label::new("Connect via SSH (default)"),
|
||||
!manual_setup,
|
||||
cx.listener({
|
||||
let state = state.clone();
|
||||
move |this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
manual_setup: false,
|
||||
..state.clone()
|
||||
});
|
||||
cx.notify()
|
||||
if let Mode::CreateDevServer(CreateDevServer{ manual_setup, .. }) = &mut this.mode {
|
||||
*manual_setup = false;
|
||||
}
|
||||
cx.notify()
|
||||
}
|
||||
}),
|
||||
))
|
||||
@@ -784,13 +764,11 @@ impl DevServerProjects {
|
||||
Label::new("Manual Setup"),
|
||||
manual_setup,
|
||||
cx.listener({
|
||||
let state = state.clone();
|
||||
move |this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
manual_setup: true,
|
||||
..state.clone()
|
||||
});
|
||||
cx.notify()
|
||||
if let Mode::CreateDevServer(CreateDevServer{ manual_setup, .. }) = &mut this.mode {
|
||||
*manual_setup = true;
|
||||
}
|
||||
cx.notify()
|
||||
}}),
|
||||
)))
|
||||
.when(dev_server_id.is_none(), |el| {
|
||||
@@ -838,10 +816,10 @@ impl DevServerProjects {
|
||||
cx.notify();
|
||||
}))
|
||||
} else {
|
||||
Button::new("create-dev-server", if manual_setup { "Create"} else { "Connect"})
|
||||
Button::new("create-dev-server", if manual_setup { if dev_server_id.is_some() { "Update" } else { "Create"} } else { if dev_server_id.is_some() { "Reconnect" } else { "Connect"} })
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.disabled(creating)
|
||||
.disabled(creating && dev_server_id.is_none())
|
||||
.on_click(cx.listener({
|
||||
let access_token = access_token.clone();
|
||||
move |this, _, cx| {
|
||||
@@ -1006,18 +984,115 @@ impl Render for DevServerProjects {
|
||||
.on_mouse_down_out(cx.listener(|this, _, cx| {
|
||||
if matches!(this.mode, Mode::Default(None)) {
|
||||
cx.emit(DismissEvent)
|
||||
} else {
|
||||
this.focus_handle(cx).focus(cx);
|
||||
cx.stop_propagation()
|
||||
}
|
||||
}))
|
||||
.w(rems(34.))
|
||||
.max_h(rems(40.))
|
||||
.child(match &self.mode {
|
||||
Mode::Default(_) => self.render_default(cx).into_any_element(),
|
||||
Mode::CreateDevServer(state) => self
|
||||
.render_create_dev_server(state.clone(), cx)
|
||||
.into_any_element(),
|
||||
Mode::CreateDevServer(state) => {
|
||||
self.render_create_dev_server(state, cx).into_any_element()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reconnect_to_dev_server(
|
||||
workspace: View<Workspace>,
|
||||
dev_server: DevServer,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let Some(ssh_connection_string) = dev_server.ssh_connection_string else {
|
||||
return Task::ready(Err(anyhow!("can't reconnect, no ssh_connection_string")));
|
||||
};
|
||||
let dev_server_store = dev_server_projects::Store::global(cx);
|
||||
let get_access_token = dev_server_store.update(cx, |store, cx| {
|
||||
store.regenerate_dev_server_token(dev_server.id, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let access_token = get_access_token.await?.access_token;
|
||||
|
||||
spawn_ssh_task(
|
||||
workspace,
|
||||
dev_server_store,
|
||||
dev_server.id,
|
||||
ssh_connection_string.to_string(),
|
||||
access_token,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn spawn_ssh_task(
|
||||
workspace: View<Workspace>,
|
||||
dev_server_store: Model<dev_server_projects::Store>,
|
||||
dev_server_id: DevServerId,
|
||||
ssh_connection_string: String,
|
||||
access_token: String,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> anyhow::Result<()> {
|
||||
let terminal_panel = workspace
|
||||
.update(cx, |workspace, cx| workspace.panel::<TerminalPanel>(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.with_context(|| anyhow!("No terminal panel"))?;
|
||||
|
||||
let command = "sh".to_string();
|
||||
let args = vec![
|
||||
"-x".to_string(),
|
||||
"-c".to_string(),
|
||||
format!(
|
||||
r#"~/.local/bin/zed -v >/dev/stderr || (curl -sSL https://zed.dev/install.sh || wget -qO- https://zed.dev/install.sh) | bash && ~/.local/bin/zed --dev-server-token {}"#,
|
||||
access_token
|
||||
),
|
||||
];
|
||||
|
||||
let ssh_connection_string = ssh_connection_string.to_string();
|
||||
|
||||
let terminal = terminal_panel
|
||||
.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.spawn_in_new_terminal(
|
||||
SpawnInTerminal {
|
||||
id: task::TaskId("ssh-remote".into()),
|
||||
full_label: "Install zed over ssh".into(),
|
||||
label: "Install zed over ssh".into(),
|
||||
command,
|
||||
args,
|
||||
command_label: ssh_connection_string.clone(),
|
||||
cwd: Some(TerminalWorkDir::Ssh {
|
||||
ssh_command: ssh_connection_string,
|
||||
path: None,
|
||||
}),
|
||||
env: Default::default(),
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: false,
|
||||
reveal: RevealStrategy::Always,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
terminal
|
||||
.update(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
|
||||
.await;
|
||||
|
||||
// There's a race-condition between the task completing successfully, and the server sending us the online status. Make it less likely we'll show the error state.
|
||||
if dev_server_store.update(cx, |this, _| this.dev_server_status(dev_server_id))?
|
||||
== DevServerStatus::Offline
|
||||
{
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(200))
|
||||
.await
|
||||
}
|
||||
|
||||
if dev_server_store.update(cx, |this, _| this.dev_server_status(dev_server_id))?
|
||||
== DevServerStatus::Offline
|
||||
{
|
||||
return Err(anyhow!("couldn't reconnect"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
mod dev_servers;
|
||||
|
||||
use client::ProjectId;
|
||||
use dev_servers::reconnect_to_dev_server;
|
||||
pub use dev_servers::DevServerProjects;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
@@ -17,6 +19,7 @@ use serde::Deserialize;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, ButtonLike, IconWithIndicator, Indicator, KeyBinding, ListItem,
|
||||
@@ -313,73 +316,59 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
}
|
||||
}
|
||||
SerializedWorkspaceLocation::DevServer(dev_server_project) => {
|
||||
let store = dev_server_projects::Store::global(cx).read(cx);
|
||||
let Some(project_id) = store
|
||||
let store = dev_server_projects::Store::global(cx);
|
||||
let Some(project_id) = store.read(cx)
|
||||
.dev_server_project(dev_server_project.id)
|
||||
.and_then(|p| p.project_id)
|
||||
else {
|
||||
let dev_server_name = dev_server_project.dev_server_name.clone();
|
||||
return cx.spawn(|workspace, mut cx| async move {
|
||||
let response =
|
||||
cx.prompt(gpui::PromptLevel::Warning,
|
||||
"Dev Server is offline",
|
||||
Some(format!("Cannot connect to {}. To debug open the remote project settings.", dev_server_name).as_str()),
|
||||
&["Ok", "Open Settings"]
|
||||
).await?;
|
||||
if response == 1 {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let handle = cx.view().downgrade();
|
||||
workspace.toggle_modal(cx, |cx| DevServerProjects::new(cx, handle))
|
||||
})?;
|
||||
} else {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
RecentProjects::open(workspace, true, cx);
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
if let Some(app_state) = AppState::global(cx).upgrade() {
|
||||
let handle = if replace_current_window {
|
||||
cx.window_handle().downcast::<Workspace>()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let server = store.read(cx).dev_server_for_project(dev_server_project.id);
|
||||
if server.is_some_and(|server| server.ssh_connection_string.is_some()) {
|
||||
let reconnect = reconnect_to_dev_server(cx.view().clone(), server.unwrap().clone(), cx);
|
||||
let id = dev_server_project.id;
|
||||
return cx.spawn(|workspace, mut cx| async move {
|
||||
reconnect.await?;
|
||||
|
||||
if let Some(handle) = handle {
|
||||
cx.spawn(move |workspace, mut cx| async move {
|
||||
let continue_replacing = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.
|
||||
prepare_to_close(true, cx)
|
||||
})?
|
||||
.await?;
|
||||
if continue_replacing {
|
||||
workspace
|
||||
.update(&mut cx, |_workspace, cx| {
|
||||
workspace::join_dev_server_project(project_id, app_state, Some(handle), cx)
|
||||
})?
|
||||
.await?;
|
||||
cx.background_executor().timer(Duration::from_millis(1000)).await;
|
||||
|
||||
if let Some(project_id) = store.update(&mut cx, |store, _| {
|
||||
store.dev_server_project(id)
|
||||
.and_then(|p| p.project_id)
|
||||
})? {
|
||||
workspace.update(&mut cx, move |_, cx| {
|
||||
open_dev_server_project(replace_current_window, project_id, cx)
|
||||
})?.await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
else {
|
||||
let task =
|
||||
workspace::join_dev_server_project(project_id, app_state, None, cx);
|
||||
cx.spawn(|_, _| async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
let dev_server_name = dev_server_project.dev_server_name.clone();
|
||||
return cx.spawn(|workspace, mut cx| async move {
|
||||
let response =
|
||||
cx.prompt(gpui::PromptLevel::Warning,
|
||||
"Dev Server is offline",
|
||||
Some(format!("Cannot connect to {}. To debug open the remote project settings.", dev_server_name).as_str()),
|
||||
&["Ok", "Open Settings"]
|
||||
).await?;
|
||||
if response == 1 {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let handle = cx.view().downgrade();
|
||||
workspace.toggle_modal(cx, |cx| DevServerProjects::new(cx, handle))
|
||||
})?;
|
||||
} else {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
RecentProjects::open(workspace, true, cx);
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Task::ready(Err(anyhow::anyhow!("App state not found")))
|
||||
}
|
||||
}
|
||||
};
|
||||
open_dev_server_project(replace_current_window, project_id, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
.detach_and_log_err(cx);
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
@@ -546,7 +535,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
.when_some(KeyBinding::for_action(&OpenRemote, cx), |button, key| {
|
||||
button.child(key)
|
||||
})
|
||||
.child(Label::new("Connect…").color(Color::Muted))
|
||||
.child(Label::new("New remote project…").color(Color::Muted))
|
||||
.on_click(|_, cx| cx.dispatch_action(OpenRemote.boxed_clone())),
|
||||
)
|
||||
.child(
|
||||
@@ -555,7 +544,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
KeyBinding::for_action(&workspace::Open, cx),
|
||||
|button, key| button.child(key),
|
||||
)
|
||||
.child(Label::new("Open folder…").color(Color::Muted))
|
||||
.child(Label::new("Open local folder…").color(Color::Muted))
|
||||
.on_click(|_, cx| cx.dispatch_action(workspace::Open.boxed_clone())),
|
||||
)
|
||||
.into_any(),
|
||||
@@ -563,6 +552,51 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_dev_server_project(
|
||||
replace_current_window: bool,
|
||||
project_id: ProjectId,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
if let Some(app_state) = AppState::global(cx).upgrade() {
|
||||
let handle = if replace_current_window {
|
||||
cx.window_handle().downcast::<Workspace>()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(handle) = handle {
|
||||
cx.spawn(move |workspace, mut cx| async move {
|
||||
let continue_replacing = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.prepare_to_close(true, cx)
|
||||
})?
|
||||
.await?;
|
||||
if continue_replacing {
|
||||
workspace
|
||||
.update(&mut cx, |_workspace, cx| {
|
||||
workspace::join_dev_server_project(
|
||||
project_id,
|
||||
app_state,
|
||||
Some(handle),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
let task = workspace::join_dev_server_project(project_id, app_state, None, cx);
|
||||
cx.spawn(|_, _| async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Task::ready(Err(anyhow::anyhow!("App state not found")))
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the highlighted text for the name and path
|
||||
fn highlights_for_path(
|
||||
path: &Path,
|
||||
|
||||
@@ -5,10 +5,11 @@ pub mod static_source;
|
||||
mod task_template;
|
||||
mod vscode_format;
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use gpui::SharedString;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
pub use task_template::{RevealStrategy, TaskTemplate, TaskTemplates};
|
||||
@@ -120,7 +121,7 @@ impl ResolvedTask {
|
||||
}
|
||||
|
||||
/// Variables, available for use in [`TaskContext`] when a Zed's [`TaskTemplate`] gets resolved into a [`ResolvedTask`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub enum VariableName {
|
||||
/// An absolute path of the currently opened file.
|
||||
File,
|
||||
@@ -134,8 +135,6 @@ pub enum VariableName {
|
||||
Column,
|
||||
/// Text from the latest selection.
|
||||
SelectedText,
|
||||
/// The symbol selected by the symbol tagging system, specifically the @run capture in a runnables.scm
|
||||
RunnableSymbol,
|
||||
/// Custom variable, provided by the plugin or other external source.
|
||||
/// Will be printed with `ZED_` prefix to avoid potential conflicts with other variables.
|
||||
Custom(Cow<'static, str>),
|
||||
@@ -165,7 +164,6 @@ impl std::fmt::Display for VariableName {
|
||||
Self::Row => write!(f, "{ZED_VARIABLE_NAME_PREFIX}ROW"),
|
||||
Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
|
||||
Self::SelectedText => write!(f, "{ZED_VARIABLE_NAME_PREFIX}SELECTED_TEXT"),
|
||||
Self::RunnableSymbol => write!(f, "{ZED_VARIABLE_NAME_PREFIX}RUNNABLE_SYMBOL"),
|
||||
Self::Custom(s) => write!(f, "{ZED_VARIABLE_NAME_PREFIX}CUSTOM_{s}"),
|
||||
}
|
||||
}
|
||||
@@ -173,7 +171,7 @@ impl std::fmt::Display for VariableName {
|
||||
|
||||
/// Container for predefined environment variables that describe state of Zed at the time the task was spawned.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
||||
pub struct TaskVariables(HashMap<VariableName, String>);
|
||||
pub struct TaskVariables(BTreeMap<VariableName, String>);
|
||||
|
||||
impl TaskVariables {
|
||||
/// Inserts another variable into the container, overwriting the existing one if it already exists — in this case, the old value is returned.
|
||||
@@ -199,14 +197,42 @@ impl TaskVariables {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns iterator over names of all set task variables.
|
||||
pub fn keys(&self) -> impl Iterator<Item = &VariableName> {
|
||||
self.0.keys()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(VariableName, String)> for TaskVariables {
|
||||
fn from_iter<T: IntoIterator<Item = (VariableName, String)>>(iter: T) -> Self {
|
||||
Self(HashMap::from_iter(iter))
|
||||
Self(BTreeMap::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for VariableName {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let without_prefix = s.strip_prefix(ZED_VARIABLE_NAME_PREFIX).ok_or(())?;
|
||||
let value = match without_prefix {
|
||||
"FILE" => Self::File,
|
||||
"WORKTREE_ROOT" => Self::WorktreeRoot,
|
||||
"SYMBOL" => Self::Symbol,
|
||||
"SELECTED_TEXT" => Self::SelectedText,
|
||||
"ROW" => Self::Row,
|
||||
"COLUMN" => Self::Column,
|
||||
_ => {
|
||||
if let Some(custom_name) = without_prefix.strip_prefix("CUSTOM_") {
|
||||
Self::Custom(Cow::Owned(custom_name.to_owned()))
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
/// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function).
|
||||
/// Keeps all Zed-related state inside, used to produce a resolved task out of its template.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
||||
|
||||
@@ -14,9 +14,11 @@ file_icons.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
task.workspace = true
|
||||
text.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -10,6 +10,7 @@ use workspace::tasks::schedule_task;
|
||||
use workspace::{tasks::schedule_resolved_task, Workspace};
|
||||
|
||||
mod modal;
|
||||
mod modal_completions;
|
||||
mod settings;
|
||||
|
||||
pub use modal::Spawn;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::active_item_selection_properties;
|
||||
use crate::{active_item_selection_properties, modal_completions::TaskVariablesCompletionProvider};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusableView,
|
||||
@@ -139,11 +139,14 @@ impl TasksModal {
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let provider = TaskVariablesCompletionProvider::new(task_context.task_variables.clone());
|
||||
|
||||
let picker = cx.new_view(|cx| {
|
||||
Picker::uniform_list(
|
||||
TasksModalDelegate::new(inventory, task_context, workspace),
|
||||
cx,
|
||||
)
|
||||
.with_completions_provider(Box::new(provider), cx)
|
||||
});
|
||||
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
|
||||
138
crates/tasks_ui/src/modal_completions.rs
Normal file
138
crates/tasks_ui/src/modal_completions.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use editor::CompletionProvider;
|
||||
use fuzzy::{CharBag, StringMatchCandidate};
|
||||
use gpui::{AppContext, Model, Task};
|
||||
use language::{CodeLabel, Documentation, LanguageServerId};
|
||||
use parking_lot::RwLock;
|
||||
use task::{TaskVariables, VariableName};
|
||||
use text::{Anchor, ToOffset};
|
||||
use ui::ViewContext;
|
||||
|
||||
pub(crate) struct TaskVariablesCompletionProvider {
|
||||
task_variables: Arc<TaskVariables>,
|
||||
pub(crate) names: Arc<[StringMatchCandidate]>,
|
||||
}
|
||||
|
||||
impl TaskVariablesCompletionProvider {
|
||||
pub(crate) fn new(variables: TaskVariables) -> Self {
|
||||
let names = variables
|
||||
.keys()
|
||||
.enumerate()
|
||||
.map(|(index, name)| {
|
||||
let name = name.to_string();
|
||||
StringMatchCandidate {
|
||||
id: index,
|
||||
char_bag: CharBag::from(name.as_str()),
|
||||
string: name,
|
||||
}
|
||||
})
|
||||
.collect::<Arc<[_]>>();
|
||||
Self {
|
||||
names,
|
||||
task_variables: Arc::new(variables),
|
||||
}
|
||||
}
|
||||
fn current_query(
|
||||
buffer: &Model<language::Buffer>,
|
||||
position: language::Anchor,
|
||||
cx: &AppContext,
|
||||
) -> Option<String> {
|
||||
let mut has_trigger_character = false;
|
||||
let reversed_query = buffer
|
||||
.read(cx)
|
||||
.reversed_chars_for_range(Anchor::MIN..position)
|
||||
.take_while(|c| {
|
||||
let is_trigger = *c == '$';
|
||||
if is_trigger {
|
||||
has_trigger_character = true;
|
||||
}
|
||||
!is_trigger && (*c == '_' || c.is_ascii_alphanumeric())
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
has_trigger_character.then(|| reversed_query.chars().rev().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletionProvider for TaskVariablesCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
buffer: &Model<language::Buffer>,
|
||||
buffer_position: text::Anchor,
|
||||
cx: &mut ViewContext<editor::Editor>,
|
||||
) -> gpui::Task<gpui::Result<Vec<project::Completion>>> {
|
||||
let Some(current_query) = Self::current_query(buffer, buffer_position, cx) else {
|
||||
return Task::ready(Ok(vec![]));
|
||||
};
|
||||
let buffer = buffer.read(cx);
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
let offset = buffer_position.to_offset(&buffer_snapshot);
|
||||
let starting_offset = offset - current_query.len();
|
||||
let starting_anchor = buffer.anchor_before(starting_offset);
|
||||
let executor = cx.background_executor().clone();
|
||||
let names = self.names.clone();
|
||||
let variables = self.task_variables.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let matches = fuzzy::match_strings(
|
||||
&names,
|
||||
¤t_query,
|
||||
true,
|
||||
100,
|
||||
&Default::default(),
|
||||
executor,
|
||||
)
|
||||
.await;
|
||||
// Find all variables starting with this
|
||||
Ok(matches
|
||||
.into_iter()
|
||||
.filter_map(|hit| {
|
||||
let variable_key = VariableName::from_str(&hit.string).ok()?;
|
||||
let value_of_var = variables.get(&variable_key)?.to_owned();
|
||||
Some(project::Completion {
|
||||
old_range: starting_anchor..buffer_position,
|
||||
new_text: hit.string.clone(),
|
||||
label: CodeLabel::plain(hit.string, None),
|
||||
documentation: Some(Documentation::SingleLine(value_of_var)),
|
||||
server_id: LanguageServerId(0), // TODO: Make this optional or something?
|
||||
lsp_completion: Default::default(), // TODO: Make this optional or something?
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
_buffer: Model<language::Buffer>,
|
||||
_completion_indices: Vec<usize>,
|
||||
_completions: Arc<RwLock<Box<[project::Completion]>>>,
|
||||
_cx: &mut ViewContext<editor::Editor>,
|
||||
) -> gpui::Task<gpui::Result<bool>> {
|
||||
Task::ready(Ok(true))
|
||||
}
|
||||
|
||||
fn apply_additional_edits_for_completion(
|
||||
&self,
|
||||
_buffer: Model<language::Buffer>,
|
||||
_completion: project::Completion,
|
||||
_push_to_history: bool,
|
||||
_cx: &mut ViewContext<editor::Editor>,
|
||||
) -> gpui::Task<gpui::Result<Option<language::Transaction>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
buffer: &Model<language::Buffer>,
|
||||
position: language::Anchor,
|
||||
text: &str,
|
||||
_trigger_in_words: bool,
|
||||
cx: &mut ViewContext<editor::Editor>,
|
||||
) -> bool {
|
||||
if text == "$" {
|
||||
return true;
|
||||
}
|
||||
Self::current_query(buffer, position, cx).is_some()
|
||||
}
|
||||
}
|
||||
@@ -283,7 +283,7 @@ impl TerminalView {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
|
||||
.log_err();
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,142 @@ macro_rules! debug_panic {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! with_clone {
|
||||
($i:ident, move ||$l:expr) => {{
|
||||
let $i = $i.clone();
|
||||
move || {
|
||||
$l
|
||||
}
|
||||
}};
|
||||
($i:ident, move |$($k:pat_param),*|$l:expr) => {{
|
||||
let $i = $i.clone();
|
||||
move |$( $k ),*| {
|
||||
$l
|
||||
}
|
||||
}};
|
||||
|
||||
(($($i:ident),+), move ||$l:expr) => {{
|
||||
let ($($i),+) = ($($i.clone()),+);
|
||||
move || {
|
||||
$l
|
||||
}
|
||||
}};
|
||||
(($($i:ident),+), move |$($k:pat_param),*|$l:expr) => {{
|
||||
let ($($i),+) = ($($i.clone()),+);
|
||||
move |$( $k ),*| {
|
||||
$l
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
mod test_with_clone {
|
||||
|
||||
// If this test compiles, it works
|
||||
#[test]
|
||||
fn test() {
|
||||
let x = "String".to_string();
|
||||
let y = std::sync::Arc::new(5);
|
||||
|
||||
fn no_arg(f: impl FnOnce()) {
|
||||
f()
|
||||
}
|
||||
|
||||
no_arg(with_clone!(x, move || {
|
||||
drop(x);
|
||||
}));
|
||||
|
||||
no_arg(with_clone!((x, y), move || {
|
||||
drop(x);
|
||||
drop(y);
|
||||
}));
|
||||
|
||||
fn one_arg(f: impl FnOnce(usize)) {
|
||||
f(1)
|
||||
}
|
||||
|
||||
one_arg(with_clone!(x, move |_| {
|
||||
drop(x);
|
||||
}));
|
||||
one_arg(with_clone!((x, y), move |b| {
|
||||
drop(x);
|
||||
drop(y);
|
||||
println!("{}", b);
|
||||
}));
|
||||
|
||||
fn two_arg(f: impl FnOnce(usize, bool)) {
|
||||
f(5, true)
|
||||
}
|
||||
|
||||
two_arg(with_clone!((x, y), move |a, b| {
|
||||
drop(x);
|
||||
drop(y);
|
||||
println!("{}{}", a, b)
|
||||
}));
|
||||
two_arg(with_clone!((x, y), move |a, _| {
|
||||
drop(x);
|
||||
drop(y);
|
||||
println!("{}", a)
|
||||
}));
|
||||
two_arg(with_clone!((x, y), move |_, b| {
|
||||
drop(x);
|
||||
drop(y);
|
||||
println!("{}", b)
|
||||
}));
|
||||
|
||||
struct Example {
|
||||
z: usize,
|
||||
}
|
||||
|
||||
fn destructuring_example(f: impl FnOnce(Example)) {
|
||||
f(Example { z: 10 })
|
||||
}
|
||||
|
||||
destructuring_example(with_clone!(x, move |Example { z }| {
|
||||
drop(x);
|
||||
println!("{}", z);
|
||||
}));
|
||||
|
||||
let a_long_variable_1 = "".to_string();
|
||||
let a_long_variable_2 = "".to_string();
|
||||
let a_long_variable_3 = "".to_string();
|
||||
let a_long_variable_4 = "".to_string();
|
||||
two_arg(with_clone!(
|
||||
(
|
||||
x,
|
||||
y,
|
||||
a_long_variable_1,
|
||||
a_long_variable_2,
|
||||
a_long_variable_3,
|
||||
a_long_variable_4
|
||||
),
|
||||
move |a, b| {
|
||||
drop(x);
|
||||
drop(y);
|
||||
drop(a_long_variable_1);
|
||||
drop(a_long_variable_2);
|
||||
drop(a_long_variable_3);
|
||||
drop(a_long_variable_4);
|
||||
println!("{}{}", a, b)
|
||||
}
|
||||
));
|
||||
|
||||
fn single_expression_body(f: impl FnOnce(usize) -> usize) -> usize {
|
||||
f(20)
|
||||
}
|
||||
|
||||
let _result = single_expression_body(with_clone!(y, move |z| *y + z));
|
||||
|
||||
// Explicitly move all variables
|
||||
drop(x);
|
||||
drop(y);
|
||||
drop(a_long_variable_1);
|
||||
drop(a_long_variable_2);
|
||||
drop(a_long_variable_3);
|
||||
drop(a_long_variable_4);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncate(s: &str, max_chars: usize) -> &str {
|
||||
match s.char_indices().nth(max_chars) {
|
||||
None => s,
|
||||
|
||||
@@ -512,6 +512,13 @@ where
|
||||
}
|
||||
|
||||
pub trait DetachAndPromptErr {
|
||||
fn prompt_err(
|
||||
self,
|
||||
msg: &str,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&anyhow::Error, &mut WindowContext) -> Option<String> + 'static,
|
||||
) -> Task<()>;
|
||||
|
||||
fn detach_and_prompt_err(
|
||||
self,
|
||||
msg: &str,
|
||||
@@ -524,12 +531,12 @@ impl<R> DetachAndPromptErr for Task<anyhow::Result<R>>
|
||||
where
|
||||
R: 'static,
|
||||
{
|
||||
fn detach_and_prompt_err(
|
||||
fn prompt_err(
|
||||
self,
|
||||
msg: &str,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&anyhow::Error, &mut WindowContext) -> Option<String> + 'static,
|
||||
) {
|
||||
) -> Task<()> {
|
||||
let msg = msg.to_owned();
|
||||
cx.spawn(|mut cx| async move {
|
||||
if let Err(err) = self.await {
|
||||
@@ -543,6 +550,14 @@ where
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn detach_and_prompt_err(
|
||||
self,
|
||||
msg: &str,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&anyhow::Error, &mut WindowContext) -> Option<String> + 'static,
|
||||
) {
|
||||
self.prompt_err(msg, cx, f).detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ use env_logger::Builder;
|
||||
use fs::RealFs;
|
||||
use futures::{future, StreamExt};
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::{App, AppContext, AsyncAppContext, Context, Global, Task, VisualContext};
|
||||
use gpui::{
|
||||
App, AppContext, AsyncAppContext, Context, Global, Task, UpdateGlobal as _, VisualContext,
|
||||
};
|
||||
use image_viewer;
|
||||
use language::LanguageRegistry;
|
||||
use log::LevelFilter;
|
||||
@@ -38,11 +40,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
|
||||
use util::{
|
||||
maybe, parse_env_output,
|
||||
paths::{self},
|
||||
ResultExt, TryFutureExt,
|
||||
};
|
||||
use util::{maybe, parse_env_output, paths, with_clone, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
|
||||
use workspace::{AppState, WorkspaceSettings, WorkspaceStore};
|
||||
@@ -260,13 +258,11 @@ fn main() {
|
||||
let session_id = Uuid::new_v4().to_string();
|
||||
reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
||||
|
||||
let (listener, mut open_rx) = OpenListener::new();
|
||||
let listener = Arc::new(listener);
|
||||
let open_listener = listener.clone();
|
||||
let (open_listener, mut open_rx) = OpenListener::new();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if crate::zed::listen_for_cli_connections(listener.clone()).is_err() {
|
||||
if crate::zed::listen_for_cli_connections(open_listener.clone()).is_err() {
|
||||
println!("zed is already running");
|
||||
return;
|
||||
}
|
||||
@@ -317,7 +313,7 @@ fn main() {
|
||||
})
|
||||
};
|
||||
|
||||
app.on_open_urls(move |urls| open_listener.open_urls(urls));
|
||||
app.on_open_urls(with_clone!(open_listener, move |urls| open_listener.open_urls(urls)));
|
||||
app.on_reopen(move |cx| {
|
||||
if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
|
||||
{
|
||||
@@ -338,7 +334,7 @@ fn main() {
|
||||
GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
|
||||
git_hosting_providers::init(cx);
|
||||
|
||||
OpenListener::set_global(listener.clone(), cx);
|
||||
OpenListener::set_global(cx, open_listener.clone());
|
||||
|
||||
settings::init(cx);
|
||||
handle_settings_file_changes(user_settings_file_rx, cx);
|
||||
@@ -396,7 +392,7 @@ fn main() {
|
||||
.collect();
|
||||
|
||||
if !urls.is_empty() {
|
||||
listener.open_urls(urls)
|
||||
open_listener.open_urls(urls)
|
||||
}
|
||||
|
||||
match open_rx
|
||||
|
||||
@@ -11,7 +11,7 @@ use collections::VecDeque;
|
||||
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
|
||||
use gpui::{
|
||||
actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PromptLevel,
|
||||
TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
|
||||
ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
|
||||
};
|
||||
pub use open_listener::*;
|
||||
|
||||
|
||||
@@ -90,30 +90,19 @@ impl OpenRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpenListener {
|
||||
tx: UnboundedSender<Vec<String>>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct OpenListener(UnboundedSender<Vec<String>>);
|
||||
|
||||
struct GlobalOpenListener(Arc<OpenListener>);
|
||||
|
||||
impl Global for GlobalOpenListener {}
|
||||
impl Global for OpenListener {}
|
||||
|
||||
impl OpenListener {
|
||||
pub fn global(cx: &AppContext) -> Arc<Self> {
|
||||
cx.global::<GlobalOpenListener>().0.clone()
|
||||
}
|
||||
|
||||
pub fn set_global(listener: Arc<OpenListener>, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalOpenListener(listener))
|
||||
}
|
||||
|
||||
pub fn new() -> (Self, UnboundedReceiver<Vec<String>>) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
(OpenListener { tx }, rx)
|
||||
(OpenListener(tx), rx)
|
||||
}
|
||||
|
||||
pub fn open_urls(&self, urls: Vec<String>) {
|
||||
self.tx
|
||||
self.0
|
||||
.unbounded_send(urls)
|
||||
.map_err(|_| anyhow!("no listener for open requests"))
|
||||
.log_err();
|
||||
@@ -121,7 +110,7 @@ impl OpenListener {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn listen_for_cli_connections(opener: Arc<OpenListener>) -> Result<()> {
|
||||
pub fn listen_for_cli_connections(opener: OpenListener) -> Result<()> {
|
||||
use release_channel::RELEASE_CHANNEL_NAME;
|
||||
use std::os::{linux::net::SocketAddrExt, unix::net::SocketAddr, unix::net::UnixDatagram};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user