Compare commits
2 Commits
tasks-moda
...
preserve-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7e68a043e | ||
|
|
79ee03f1a1 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -8067,7 +8067,6 @@ name = "recent_projects"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
@@ -10153,7 +10152,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"menu",
|
||||
"parking_lot",
|
||||
"picker",
|
||||
"project",
|
||||
"schemars",
|
||||
@@ -10161,7 +10159,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"task",
|
||||
"text",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"ui",
|
||||
|
||||
@@ -1625,6 +1625,7 @@ impl Conversation {
|
||||
slash_command_registry,
|
||||
};
|
||||
this.set_language(cx);
|
||||
this.ensure_trailing_user_message(cx);
|
||||
this.reparse_edit_suggestions(cx);
|
||||
this.count_remaining_tokens(cx);
|
||||
this
|
||||
@@ -1704,13 +1705,33 @@ impl Conversation {
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if *event == language::Event::Edited {
|
||||
self.count_remaining_tokens(cx);
|
||||
self.ensure_trailing_user_message(cx);
|
||||
self.reparse_edit_suggestions(cx);
|
||||
self.reparse_slash_command_calls(cx);
|
||||
self.count_remaining_tokens(cx);
|
||||
cx.emit(ConversationEvent::MessagesEdited);
|
||||
}
|
||||
}
|
||||
|
||||
/// If we find a valid trailing message that isn't a user message, then add one
|
||||
fn ensure_trailing_user_message(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let mut trailing_id = None;
|
||||
for MessageAnchor { id, start } in self.message_anchors.iter().rev() {
|
||||
if start.is_valid(self.buffer.read(cx)) {
|
||||
if self.messages_metadata[id].role == Role::User {
|
||||
return;
|
||||
} else {
|
||||
trailing_id = Some(*id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(id) = trailing_id {
|
||||
self.insert_message_after(id, Role::User, MessageStatus::Done, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let request = self.to_completion_request(cx);
|
||||
self.pending_token_count = cx.spawn(|this, mut cx| {
|
||||
@@ -2207,6 +2228,7 @@ impl Conversation {
|
||||
for id in ids {
|
||||
if let Some(metadata) = self.messages_metadata.get_mut(&id) {
|
||||
metadata.role.cycle();
|
||||
self.ensure_trailing_user_message(cx);
|
||||
cx.emit(ConversationEvent::MessagesEdited);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -6958,7 +6958,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
||||
cx.assert_editor_state(indoc!(
|
||||
"fn a() {
|
||||
// dog();
|
||||
catˇ();
|
||||
ˇcat();
|
||||
}"
|
||||
));
|
||||
|
||||
@@ -6974,7 +6974,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
||||
});
|
||||
cx.assert_editor_state(indoc!(
|
||||
"fn a() {
|
||||
// «dog()ˇ»;
|
||||
«// dog()ˇ»;
|
||||
cat();
|
||||
}"
|
||||
));
|
||||
@@ -6992,7 +6992,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
||||
cx.assert_editor_state(indoc!(
|
||||
"fn a() {
|
||||
// dog();
|
||||
catˇ(ˇ);
|
||||
ˇcat(ˇ);
|
||||
}"
|
||||
));
|
||||
|
||||
@@ -7008,7 +7008,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
||||
});
|
||||
cx.assert_editor_state(indoc!(
|
||||
"fn a() {
|
||||
// ˇdˇog«()ˇ»;
|
||||
ˇ// dˇog«()ˇ»;
|
||||
cat();
|
||||
}"
|
||||
));
|
||||
|
||||
@@ -534,20 +534,14 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.collection.disjoint = Arc::from_iter(selections.into_iter().map(|selection| {
|
||||
let end_bias = if selection.end > selection.start {
|
||||
Bias::Left
|
||||
} else {
|
||||
Bias::Right
|
||||
};
|
||||
Selection {
|
||||
self.collection.disjoint =
|
||||
Arc::from_iter(selections.into_iter().map(|selection| Selection {
|
||||
id: selection.id,
|
||||
start: buffer.anchor_after(selection.start),
|
||||
end: buffer.anchor_at(selection.end, end_bias),
|
||||
start: buffer.anchor_before(selection.start),
|
||||
end: buffer.anchor_before(selection.end),
|
||||
reversed: selection.reversed,
|
||||
goal: selection.goal,
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
self.collection.pending = None;
|
||||
self.selections_changed = true;
|
||||
|
||||
@@ -49,11 +49,6 @@ 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 {
|
||||
@@ -64,11 +59,4 @@ 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, CompletionProvider, Editor};
|
||||
use editor::{scroll::Autoscroll, Editor};
|
||||
use gpui::{
|
||||
actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent,
|
||||
DismissEvent, EventEmitter, FocusHandle, FocusableView, Length, ListState, MouseButton,
|
||||
@@ -166,17 +166,6 @@ 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,7 +14,6 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
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;
|
||||
@@ -49,9 +47,9 @@ pub struct DevServerProjects {
|
||||
_dev_server_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
struct CreateDevServer {
|
||||
creating: Option<Task<()>>,
|
||||
creating: bool,
|
||||
dev_server_id: Option<DevServerId>,
|
||||
access_token: Option<String>,
|
||||
manual_setup: bool,
|
||||
@@ -314,77 +312,95 @@ impl DevServerProjects {
|
||||
});
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let store = dev_server_projects::Store::global(cx);
|
||||
|
||||
let task = cx
|
||||
.spawn({
|
||||
|this, mut cx| async move {
|
||||
let result = dev_server.await;
|
||||
cx.spawn({
|
||||
let access_token = access_token.clone();
|
||||
|this, mut cx| async move {
|
||||
let result = dev_server.await;
|
||||
|
||||
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();
|
||||
})?;
|
||||
match result {
|
||||
Ok(dev_server) => {
|
||||
if let Some(ssh_connection_string) = ssh_connection_string {
|
||||
|
||||
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| {
|
||||
let access_token = access_token.clone();
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.focus_handle.focus(cx);
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: None,
|
||||
creating: true,
|
||||
dev_server_id: Some(DevServerId(dev_server.dev_server_id)),
|
||||
access_token: Some(dev_server.access_token),
|
||||
manual_setup,
|
||||
});
|
||||
access_token: Some(access_token.unwrap_or(dev_server.access_token.clone())),
|
||||
manual_setup: false,
|
||||
});
|
||||
cx.notify();
|
||||
})?;
|
||||
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 terminal_panel = workspace
|
||||
.update(&mut cx, |workspace, cx| workspace.panel::<TerminalPanel>(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.with_context(|| anyhow::anyhow!("No terminal panel"))?;
|
||||
|
||||
return Err(e);
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.prompt_err("Failed to create server", cx, |_, _| None);
|
||||
|
||||
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);
|
||||
|
||||
self.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: Some(task),
|
||||
creating: true,
|
||||
dev_server_id: existing_id,
|
||||
access_token,
|
||||
manual_setup,
|
||||
@@ -486,7 +502,7 @@ impl DevServerProjects {
|
||||
self.create_dev_server_project(create_project.dev_server_id, cx);
|
||||
}
|
||||
Mode::CreateDevServer(state) => {
|
||||
if state.creating.is_none() || state.dev_server_id.is_some() {
|
||||
if !state.creating {
|
||||
self.create_or_update_dev_server(
|
||||
state.manual_setup,
|
||||
state.dev_server_id,
|
||||
@@ -563,7 +579,7 @@ impl DevServerProjects {
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
dev_server_id: Some(dev_server_id),
|
||||
creating: None,
|
||||
creating: false,
|
||||
access_token: None,
|
||||
manual_setup,
|
||||
});
|
||||
@@ -698,14 +714,16 @@ impl DevServerProjects {
|
||||
}
|
||||
|
||||
fn render_create_dev_server(
|
||||
&self,
|
||||
state: &CreateDevServer,
|
||||
&mut self,
|
||||
state: CreateDevServer,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
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 CreateDevServer {
|
||||
creating,
|
||||
dev_server_id,
|
||||
access_token,
|
||||
manual_setup,
|
||||
} = state.clone();
|
||||
|
||||
let status = dev_server_id
|
||||
.map(|id| self.dev_server_store.read(cx).dev_server_status(id))
|
||||
@@ -751,11 +769,13 @@ impl DevServerProjects {
|
||||
Label::new("Connect via SSH (default)"),
|
||||
!manual_setup,
|
||||
cx.listener({
|
||||
let state = state.clone();
|
||||
move |this, _, cx| {
|
||||
if let Mode::CreateDevServer(CreateDevServer{ manual_setup, .. }) = &mut this.mode {
|
||||
*manual_setup = false;
|
||||
}
|
||||
cx.notify()
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
manual_setup: false,
|
||||
..state.clone()
|
||||
});
|
||||
cx.notify()
|
||||
}
|
||||
}),
|
||||
))
|
||||
@@ -764,11 +784,13 @@ impl DevServerProjects {
|
||||
Label::new("Manual Setup"),
|
||||
manual_setup,
|
||||
cx.listener({
|
||||
let state = state.clone();
|
||||
move |this, _, cx| {
|
||||
if let Mode::CreateDevServer(CreateDevServer{ manual_setup, .. }) = &mut this.mode {
|
||||
*manual_setup = true;
|
||||
}
|
||||
cx.notify()
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
manual_setup: true,
|
||||
..state.clone()
|
||||
});
|
||||
cx.notify()
|
||||
}}),
|
||||
)))
|
||||
.when(dev_server_id.is_none(), |el| {
|
||||
@@ -816,10 +838,10 @@ impl DevServerProjects {
|
||||
cx.notify();
|
||||
}))
|
||||
} else {
|
||||
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"} })
|
||||
Button::new("create-dev-server", if manual_setup { "Create"} else { "Connect"})
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.disabled(creating && dev_server_id.is_none())
|
||||
.disabled(creating)
|
||||
.on_click(cx.listener({
|
||||
let access_token = access_token.clone();
|
||||
move |this, _, cx| {
|
||||
@@ -984,115 +1006,18 @@ 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, cx).into_any_element()
|
||||
}
|
||||
Mode::CreateDevServer(state) => self
|
||||
.render_create_dev_server(state.clone(), 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,7 +1,5 @@
|
||||
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};
|
||||
@@ -19,7 +17,6 @@ use serde::Deserialize;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, ButtonLike, IconWithIndicator, Indicator, KeyBinding, ListItem,
|
||||
@@ -316,59 +313,73 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
}
|
||||
}
|
||||
SerializedWorkspaceLocation::DevServer(dev_server_project) => {
|
||||
let store = dev_server_projects::Store::global(cx);
|
||||
let Some(project_id) = store.read(cx)
|
||||
let store = dev_server_projects::Store::global(cx).read(cx);
|
||||
let Some(project_id) = store
|
||||
.dev_server_project(dev_server_project.id)
|
||||
.and_then(|p| p.project_id)
|
||||
else {
|
||||
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?;
|
||||
|
||||
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(())
|
||||
})
|
||||
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 {
|
||||
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);
|
||||
})?;
|
||||
}
|
||||
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(())
|
||||
})
|
||||
}
|
||||
};
|
||||
open_dev_server_project(replace_current_window, project_id, cx)
|
||||
} else {
|
||||
Task::ready(Err(anyhow::anyhow!("App state not found")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
.detach_and_log_err(cx);
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
@@ -535,7 +546,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
.when_some(KeyBinding::for_action(&OpenRemote, cx), |button, key| {
|
||||
button.child(key)
|
||||
})
|
||||
.child(Label::new("New remote project…").color(Color::Muted))
|
||||
.child(Label::new("Connect…").color(Color::Muted))
|
||||
.on_click(|_, cx| cx.dispatch_action(OpenRemote.boxed_clone())),
|
||||
)
|
||||
.child(
|
||||
@@ -544,7 +555,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
KeyBinding::for_action(&workspace::Open, cx),
|
||||
|button, key| button.child(key),
|
||||
)
|
||||
.child(Label::new("Open local folder…").color(Color::Muted))
|
||||
.child(Label::new("Open folder…").color(Color::Muted))
|
||||
.on_click(|_, cx| cx.dispatch_action(workspace::Open.boxed_clone())),
|
||||
)
|
||||
.into_any(),
|
||||
@@ -552,51 +563,6 @@ 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,11 +5,10 @@ pub mod static_source;
|
||||
mod task_template;
|
||||
mod vscode_format;
|
||||
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use collections::{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};
|
||||
@@ -121,7 +120,7 @@ impl ResolvedTask {
|
||||
}
|
||||
|
||||
/// Variables, available for use in [`TaskContext`] when a Zed's [`TaskTemplate`] gets resolved into a [`ResolvedTask`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
|
||||
pub enum VariableName {
|
||||
/// An absolute path of the currently opened file.
|
||||
File,
|
||||
@@ -135,6 +134,8 @@ 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>),
|
||||
@@ -164,6 +165,7 @@ 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}"),
|
||||
}
|
||||
}
|
||||
@@ -171,7 +173,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(BTreeMap<VariableName, String>);
|
||||
pub struct TaskVariables(HashMap<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.
|
||||
@@ -197,42 +199,14 @@ 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(BTreeMap::from_iter(iter))
|
||||
Self(HashMap::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,11 +14,9 @@ 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,7 +10,6 @@ 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, modal_completions::TaskVariablesCompletionProvider};
|
||||
use crate::active_item_selection_properties;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusableView,
|
||||
@@ -139,14 +139,11 @@ 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);
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
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))
|
||||
.ok();
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -34,142 +34,6 @@ 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,13 +512,6 @@ 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,
|
||||
@@ -531,12 +524,12 @@ impl<R> DetachAndPromptErr for Task<anyhow::Result<R>>
|
||||
where
|
||||
R: 'static,
|
||||
{
|
||||
fn prompt_err(
|
||||
fn detach_and_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 {
|
||||
@@ -550,14 +543,6 @@ where
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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();
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ use env_logger::Builder;
|
||||
use fs::RealFs;
|
||||
use futures::{future, StreamExt};
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::{
|
||||
App, AppContext, AsyncAppContext, Context, Global, Task, UpdateGlobal as _, VisualContext,
|
||||
};
|
||||
use gpui::{App, AppContext, AsyncAppContext, Context, Global, Task, VisualContext};
|
||||
use image_viewer;
|
||||
use language::LanguageRegistry;
|
||||
use log::LevelFilter;
|
||||
@@ -40,7 +38,11 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
|
||||
use util::{maybe, parse_env_output, paths, with_clone, ResultExt, TryFutureExt};
|
||||
use util::{
|
||||
maybe, parse_env_output,
|
||||
paths::{self},
|
||||
ResultExt, TryFutureExt,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
|
||||
use workspace::{AppState, WorkspaceSettings, WorkspaceStore};
|
||||
@@ -258,11 +260,13 @@ fn main() {
|
||||
let session_id = Uuid::new_v4().to_string();
|
||||
reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone());
|
||||
|
||||
let (open_listener, mut open_rx) = OpenListener::new();
|
||||
let (listener, mut open_rx) = OpenListener::new();
|
||||
let listener = Arc::new(listener);
|
||||
let open_listener = listener.clone();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if crate::zed::listen_for_cli_connections(open_listener.clone()).is_err() {
|
||||
if crate::zed::listen_for_cli_connections(listener.clone()).is_err() {
|
||||
println!("zed is already running");
|
||||
return;
|
||||
}
|
||||
@@ -313,7 +317,7 @@ fn main() {
|
||||
})
|
||||
};
|
||||
|
||||
app.on_open_urls(with_clone!(open_listener, move |urls| open_listener.open_urls(urls)));
|
||||
app.on_open_urls(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())
|
||||
{
|
||||
@@ -334,7 +338,7 @@ fn main() {
|
||||
GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
|
||||
git_hosting_providers::init(cx);
|
||||
|
||||
OpenListener::set_global(cx, open_listener.clone());
|
||||
OpenListener::set_global(listener.clone(), cx);
|
||||
|
||||
settings::init(cx);
|
||||
handle_settings_file_changes(user_settings_file_rx, cx);
|
||||
@@ -392,7 +396,7 @@ fn main() {
|
||||
.collect();
|
||||
|
||||
if !urls.is_empty() {
|
||||
open_listener.open_urls(urls)
|
||||
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,
|
||||
ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
|
||||
TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
|
||||
};
|
||||
pub use open_listener::*;
|
||||
|
||||
|
||||
@@ -90,19 +90,30 @@ impl OpenRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenListener(UnboundedSender<Vec<String>>);
|
||||
pub struct OpenListener {
|
||||
tx: UnboundedSender<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Global for OpenListener {}
|
||||
struct GlobalOpenListener(Arc<OpenListener>);
|
||||
|
||||
impl Global for GlobalOpenListener {}
|
||||
|
||||
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.0
|
||||
self.tx
|
||||
.unbounded_send(urls)
|
||||
.map_err(|_| anyhow!("no listener for open requests"))
|
||||
.log_err();
|
||||
@@ -110,7 +121,7 @@ impl OpenListener {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn listen_for_cli_connections(opener: OpenListener) -> Result<()> {
|
||||
pub fn listen_for_cli_connections(opener: Arc<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