Cargo.lock 🔗
@@ -1623,6 +1623,7 @@ dependencies = [
"theme",
"util",
"workspace",
+ "zed-actions",
]
[[package]]
@@ -10213,6 +10214,7 @@ name = "zed-actions"
version = "0.1.0"
dependencies = [
"gpui",
+ "serde",
]
[[package]]
Conrad Irwin created
Cargo.lock | 2
crates/collab/src/db/queries/channels.rs | 2
crates/command_palette/Cargo.toml | 1
crates/command_palette/src/command_palette.rs | 17 +
crates/workspace/src/workspace.rs | 1
crates/zed-actions/Cargo.toml | 1
crates/zed-actions/src/lib.rs | 15 +
crates/zed/src/main.rs | 206 --------------------
crates/zed/src/open_listener.rs | 202 ++++++++++++++++++++
crates/zed/src/zed.rs | 6
10 files changed, 245 insertions(+), 208 deletions(-)
@@ -1623,6 +1623,7 @@ dependencies = [
"theme",
"util",
"workspace",
+ "zed-actions",
]
[[package]]
@@ -10213,6 +10214,7 @@ name = "zed-actions"
version = "0.1.0"
dependencies = [
"gpui",
+ "serde",
]
[[package]]
@@ -979,7 +979,7 @@ impl Database {
})
}
- /// Returns the channel ancestors, include itself, deepest first
+ /// Returns the channel ancestors in arbitrary order
pub async fn get_channel_ancestors(
&self,
channel_id: ChannelId,
@@ -19,6 +19,7 @@ settings = { path = "../settings" }
util = { path = "../util" }
theme = { path = "../theme" }
workspace = { path = "../workspace" }
+zed-actions = { path = "../zed-actions" }
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }
@@ -6,8 +6,12 @@ use gpui::{
};
use picker::{Picker, PickerDelegate, PickerEvent};
use std::cmp::{self, Reverse};
-use util::ResultExt;
+use util::{
+ channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL, RELEASE_CHANNEL_NAME},
+ ResultExt,
+};
use workspace::Workspace;
+use zed_actions::OpenZedURL;
pub fn init(cx: &mut AppContext) {
cx.add_action(toggle_command_palette);
@@ -167,13 +171,22 @@ impl PickerDelegate for CommandPaletteDelegate {
)
.await
};
- let intercept_result = cx.read(|cx| {
+ let mut intercept_result = cx.read(|cx| {
if cx.has_global::<CommandPaletteInterceptor>() {
cx.global::<CommandPaletteInterceptor>()(&query, cx)
} else {
None
}
});
+ if *RELEASE_CHANNEL == ReleaseChannel::Dev {
+ if parse_zed_link(&query).is_some() {
+ intercept_result = Some(CommandInterceptResult {
+ action: OpenZedURL { url: query.clone() }.boxed_clone(),
+ string: query.clone(),
+ positions: vec![],
+ })
+ }
+ }
if let Some(CommandInterceptResult {
action,
string,
@@ -288,6 +288,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
cx.add_global_action(restart);
cx.add_async_action(Workspace::save_all);
cx.add_action(Workspace::add_folder_to_project);
+
cx.add_action(
|workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
let pane = workspace.active_pane().clone();
@@ -8,3 +8,4 @@ publish = false
[dependencies]
gpui = { path = "../gpui" }
+serde.workspace = true
@@ -1,4 +1,7 @@
-use gpui::actions;
+use std::sync::Arc;
+
+use gpui::{actions, impl_actions};
+use serde::Deserialize;
actions!(
zed,
@@ -26,3 +29,13 @@ actions!(
ResetDatabase,
]
);
+
+#[derive(Deserialize, Clone, PartialEq)]
+pub struct OpenBrowser {
+ pub url: Arc<str>,
+}
+#[derive(Deserialize, Clone, PartialEq)]
+pub struct OpenZedURL {
+ pub url: String,
+}
+impl_actions!(zed, [OpenBrowser, OpenZedURL]);
@@ -3,22 +3,16 @@
use anyhow::{anyhow, Context, Result};
use backtrace::Backtrace;
-use cli::{
- ipc::{self, IpcSender},
- CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME,
-};
+use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{
self, Client, TelemetrySettings, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN,
};
use db::kvp::KEY_VALUE_STORE;
-use editor::{scroll::autoscroll::Autoscroll, Editor};
-use futures::{
- channel::{mpsc, oneshot},
- FutureExt, SinkExt, StreamExt,
-};
+use editor::Editor;
+use futures::StreamExt;
use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task};
use isahc::{config::Configurable, Request};
-use language::{LanguageRegistry, Point};
+use language::LanguageRegistry;
use log::LevelFilter;
use node_runtime::RealNodeRuntime;
use parking_lot::Mutex;
@@ -28,7 +22,6 @@ use settings::{default_settings, handle_settings_file_changes, watch_config_file
use simplelog::ConfigBuilder;
use smol::process::Command;
use std::{
- collections::HashMap,
env,
ffi::OsStr,
fs::OpenOptions,
@@ -42,11 +35,9 @@ use std::{
thread,
time::{Duration, SystemTime, UNIX_EPOCH},
};
-use sum_tree::Bias;
use util::{
channel::{parse_zed_link, ReleaseChannel},
http::{self, HttpClient},
- paths::PathLikeWithPosition,
};
use uuid::Uuid;
use welcome::{show_welcome_experience, FIRST_OPEN};
@@ -58,12 +49,9 @@ use zed::{
assets::Assets,
build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
only_instance::{ensure_only_instance, IsOnlyInstance},
+ open_listener::{handle_cli_connection, OpenListener, OpenRequest},
};
-use crate::open_listener::{OpenListener, OpenRequest};
-
-mod open_listener;
-
fn main() {
let http = http::client();
init_paths();
@@ -113,6 +101,7 @@ fn main() {
app.run(move |cx| {
cx.set_global(*RELEASE_CHANNEL);
+ cx.set_global(listener.clone());
let mut store = SettingsStore::default();
store
@@ -729,189 +718,6 @@ async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()>
#[cfg(not(debug_assertions))]
fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
-fn connect_to_cli(
- server_name: &str,
-) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
- let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
- .context("error connecting to cli")?;
- let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
- let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
-
- handshake_tx
- .send(IpcHandshake {
- requests: request_tx,
- responses: response_rx,
- })
- .context("error sending ipc handshake")?;
-
- let (mut async_request_tx, async_request_rx) =
- futures::channel::mpsc::channel::<CliRequest>(16);
- thread::spawn(move || {
- while let Ok(cli_request) = request_rx.recv() {
- if smol::block_on(async_request_tx.send(cli_request)).is_err() {
- break;
- }
- }
- Ok::<_, anyhow::Error>(())
- });
-
- Ok((async_request_rx, response_tx))
-}
-
-async fn handle_cli_connection(
- (mut requests, responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
- app_state: Arc<AppState>,
- mut cx: AsyncAppContext,
-) {
- if let Some(request) = requests.next().await {
- match request {
- CliRequest::Open { paths, wait } => {
- let mut caret_positions = HashMap::new();
-
- let paths = if paths.is_empty() {
- workspace::last_opened_workspace_paths()
- .await
- .map(|location| location.paths().to_vec())
- .unwrap_or_default()
- } else {
- paths
- .into_iter()
- .filter_map(|path_with_position_string| {
- let path_with_position = PathLikeWithPosition::parse_str(
- &path_with_position_string,
- |path_str| {
- Ok::<_, std::convert::Infallible>(
- Path::new(path_str).to_path_buf(),
- )
- },
- )
- .expect("Infallible");
- let path = path_with_position.path_like;
- if let Some(row) = path_with_position.row {
- if path.is_file() {
- let row = row.saturating_sub(1);
- let col =
- path_with_position.column.unwrap_or(0).saturating_sub(1);
- caret_positions.insert(path.clone(), Point::new(row, col));
- }
- }
- Some(path)
- })
- .collect()
- };
-
- let mut errored = false;
- match cx
- .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
- .await
- {
- Ok((workspace, items)) => {
- let mut item_release_futures = Vec::new();
-
- for (item, path) in items.into_iter().zip(&paths) {
- match item {
- Some(Ok(item)) => {
- if let Some(point) = caret_positions.remove(path) {
- if let Some(active_editor) = item.downcast::<Editor>() {
- active_editor
- .downgrade()
- .update(&mut cx, |editor, cx| {
- let snapshot =
- editor.snapshot(cx).display_snapshot;
- let point = snapshot
- .buffer_snapshot
- .clip_point(point, Bias::Left);
- editor.change_selections(
- Some(Autoscroll::center()),
- cx,
- |s| s.select_ranges([point..point]),
- );
- })
- .log_err();
- }
- }
-
- let released = oneshot::channel();
- cx.update(|cx| {
- item.on_release(
- cx,
- Box::new(move |_| {
- let _ = released.0.send(());
- }),
- )
- .detach();
- });
- item_release_futures.push(released.1);
- }
- Some(Err(err)) => {
- responses
- .send(CliResponse::Stderr {
- message: format!("error opening {:?}: {}", path, err),
- })
- .log_err();
- errored = true;
- }
- None => {}
- }
- }
-
- if wait {
- let background = cx.background();
- let wait = async move {
- if paths.is_empty() {
- let (done_tx, done_rx) = oneshot::channel();
- if let Some(workspace) = workspace.upgrade(&cx) {
- let _subscription = cx.update(|cx| {
- cx.observe_release(&workspace, move |_, _| {
- let _ = done_tx.send(());
- })
- });
- drop(workspace);
- let _ = done_rx.await;
- }
- } else {
- let _ =
- futures::future::try_join_all(item_release_futures).await;
- };
- }
- .fuse();
- futures::pin_mut!(wait);
-
- loop {
- // Repeatedly check if CLI is still open to avoid wasting resources
- // waiting for files or workspaces to close.
- let mut timer = background.timer(Duration::from_secs(1)).fuse();
- futures::select_biased! {
- _ = wait => break,
- _ = timer => {
- if responses.send(CliResponse::Ping).is_err() {
- break;
- }
- }
- }
- }
- }
- }
- Err(error) => {
- errored = true;
- responses
- .send(CliResponse::Stderr {
- message: format!("error opening {:?}: {}", paths, error),
- })
- .log_err();
- }
- }
-
- responses
- .send(CliResponse::Exit {
- status: i32::from(errored),
- })
- .log_err();
- }
- }
- }
-}
-
pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
&[
("Go to file", &file_finder::Toggle),
@@ -1,15 +1,26 @@
-use anyhow::anyhow;
+use anyhow::{anyhow, Context, Result};
+use cli::{ipc, IpcHandshake};
use cli::{ipc::IpcSender, CliRequest, CliResponse};
-use futures::channel::mpsc;
+use editor::scroll::autoscroll::Autoscroll;
+use editor::Editor;
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use futures::channel::{mpsc, oneshot};
+use futures::{FutureExt, SinkExt, StreamExt};
+use gpui::AsyncAppContext;
+use language::{Bias, Point};
+use std::collections::HashMap;
use std::ffi::OsStr;
use std::os::unix::prelude::OsStrExt;
+use std::path::Path;
use std::sync::atomic::Ordering;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
use std::{path::PathBuf, sync::atomic::AtomicBool};
use util::channel::parse_zed_link;
+use util::paths::PathLikeWithPosition;
use util::ResultExt;
-
-use crate::connect_to_cli;
+use workspace::AppState;
pub enum OpenRequest {
Paths {
@@ -96,3 +107,186 @@ impl OpenListener {
Some(OpenRequest::Paths { paths })
}
}
+
+fn connect_to_cli(
+ server_name: &str,
+) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
+ let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
+ .context("error connecting to cli")?;
+ let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
+ let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
+
+ handshake_tx
+ .send(IpcHandshake {
+ requests: request_tx,
+ responses: response_rx,
+ })
+ .context("error sending ipc handshake")?;
+
+ let (mut async_request_tx, async_request_rx) =
+ futures::channel::mpsc::channel::<CliRequest>(16);
+ thread::spawn(move || {
+ while let Ok(cli_request) = request_rx.recv() {
+ if smol::block_on(async_request_tx.send(cli_request)).is_err() {
+ break;
+ }
+ }
+ Ok::<_, anyhow::Error>(())
+ });
+
+ Ok((async_request_rx, response_tx))
+}
+
+pub async fn handle_cli_connection(
+ (mut requests, responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
+ app_state: Arc<AppState>,
+ mut cx: AsyncAppContext,
+) {
+ if let Some(request) = requests.next().await {
+ match request {
+ CliRequest::Open { paths, wait } => {
+ let mut caret_positions = HashMap::new();
+
+ let paths = if paths.is_empty() {
+ workspace::last_opened_workspace_paths()
+ .await
+ .map(|location| location.paths().to_vec())
+ .unwrap_or_default()
+ } else {
+ paths
+ .into_iter()
+ .filter_map(|path_with_position_string| {
+ let path_with_position = PathLikeWithPosition::parse_str(
+ &path_with_position_string,
+ |path_str| {
+ Ok::<_, std::convert::Infallible>(
+ Path::new(path_str).to_path_buf(),
+ )
+ },
+ )
+ .expect("Infallible");
+ let path = path_with_position.path_like;
+ if let Some(row) = path_with_position.row {
+ if path.is_file() {
+ let row = row.saturating_sub(1);
+ let col =
+ path_with_position.column.unwrap_or(0).saturating_sub(1);
+ caret_positions.insert(path.clone(), Point::new(row, col));
+ }
+ }
+ Some(path)
+ })
+ .collect()
+ };
+
+ let mut errored = false;
+ match cx
+ .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
+ .await
+ {
+ Ok((workspace, items)) => {
+ let mut item_release_futures = Vec::new();
+
+ for (item, path) in items.into_iter().zip(&paths) {
+ match item {
+ Some(Ok(item)) => {
+ if let Some(point) = caret_positions.remove(path) {
+ if let Some(active_editor) = item.downcast::<Editor>() {
+ active_editor
+ .downgrade()
+ .update(&mut cx, |editor, cx| {
+ let snapshot =
+ editor.snapshot(cx).display_snapshot;
+ let point = snapshot
+ .buffer_snapshot
+ .clip_point(point, Bias::Left);
+ editor.change_selections(
+ Some(Autoscroll::center()),
+ cx,
+ |s| s.select_ranges([point..point]),
+ );
+ })
+ .log_err();
+ }
+ }
+
+ let released = oneshot::channel();
+ cx.update(|cx| {
+ item.on_release(
+ cx,
+ Box::new(move |_| {
+ let _ = released.0.send(());
+ }),
+ )
+ .detach();
+ });
+ item_release_futures.push(released.1);
+ }
+ Some(Err(err)) => {
+ responses
+ .send(CliResponse::Stderr {
+ message: format!("error opening {:?}: {}", path, err),
+ })
+ .log_err();
+ errored = true;
+ }
+ None => {}
+ }
+ }
+
+ if wait {
+ let background = cx.background();
+ let wait = async move {
+ if paths.is_empty() {
+ let (done_tx, done_rx) = oneshot::channel();
+ if let Some(workspace) = workspace.upgrade(&cx) {
+ let _subscription = cx.update(|cx| {
+ cx.observe_release(&workspace, move |_, _| {
+ let _ = done_tx.send(());
+ })
+ });
+ drop(workspace);
+ let _ = done_rx.await;
+ }
+ } else {
+ let _ =
+ futures::future::try_join_all(item_release_futures).await;
+ };
+ }
+ .fuse();
+ futures::pin_mut!(wait);
+
+ loop {
+ // Repeatedly check if CLI is still open to avoid wasting resources
+ // waiting for files or workspaces to close.
+ let mut timer = background.timer(Duration::from_secs(1)).fuse();
+ futures::select_biased! {
+ _ = wait => break,
+ _ = timer => {
+ if responses.send(CliResponse::Ping).is_err() {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ Err(error) => {
+ errored = true;
+ responses
+ .send(CliResponse::Stderr {
+ message: format!("error opening {:?}: {}", paths, error),
+ })
+ .log_err();
+ }
+ }
+
+ responses
+ .send(CliResponse::Exit {
+ status: i32::from(errored),
+ })
+ .log_err();
+ }
+ }
+ }
+}
@@ -2,6 +2,7 @@ pub mod assets;
pub mod languages;
pub mod menus;
pub mod only_instance;
+pub mod open_listener;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
@@ -28,6 +29,7 @@ use gpui::{
AppContext, AsyncAppContext, Task, ViewContext, WeakViewHandle,
};
pub use lsp;
+use open_listener::OpenListener;
pub use project;
use project_panel::ProjectPanel;
use quick_action_bar::QuickActionBar;
@@ -87,6 +89,10 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
},
);
cx.add_global_action(quit);
+ cx.add_global_action(move |action: &OpenZedURL, cx| {
+ cx.global::<Arc<OpenListener>>()
+ .open_urls(vec![action.url.clone()])
+ });
cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
theme::adjust_font_size(cx, |size| *size += 1.0)