Skip to content

Commit

Permalink
Add static Runnables (#8009)
Browse files Browse the repository at this point in the history
Part of #7108

This PR includes just the static runnables part. We went with **not**
having a dedicated panel for runnables.
This is just a 1st PR out of N, as we want to start exploring the
dynamic runnables front. Still, all that work is going to happen once
this gets merged.

Release Notes:

- Added initial, static Runnables support to Zed. Such runnables are defined in
`runnables.json` file (accessible via `zed: open runnables` action) and
they can be spawned with `runnables: spawn` action.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Pitor <pitor@zed.dev>
Co-authored-by: Beniamin <beniamin@zagan.be>
  • Loading branch information
4 people committed Feb 19, 2024
1 parent ca251ba commit f17d0b5
Show file tree
Hide file tree
Showing 30 changed files with 1,399 additions and 280 deletions.
48 changes: 48 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ members = [
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/runnable",
"crates/runnables_ui",
"crates/search",
"crates/semantic_index",
"crates/settings",
Expand Down Expand Up @@ -153,6 +155,8 @@ release_channel = { path = "crates/release_channel" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
runnable = { path = "crates/runnable" }
runnables_ui = { path = "crates/runnables_ui" }
search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
settings = { path = "crates/settings" }
Expand Down
1 change: 1 addition & 0 deletions assets/icons/play.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions assets/settings/initial_runnables.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Static runnables configuration.
//
// Example:
// {
// "label": "human-readable label for UI",
// "command": "bash",
// // rest of the parameters are optional
// "args": ["-c", "for i in {1..10}; do echo \"Second $i\"; sleep 1; done"],
// // Env overrides for the command, will be appended to the terminal's environment from the settings.
// "env": {"foo": "bar"},
// // Current working directory to spawn the command into, defaults to current project root.
// "cwd": "/path/to/working/directory",
// // Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`.
// "use_new_terminal": false,
// // Whether to allow multiple instances of the same runnable to be run, or rather wait for the existing ones to finish, defaults to `false`.
// "allow_concurrent_runs": false,
// },
//
{}
3 changes: 2 additions & 1 deletion crates/project/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ prettier.workspace = true
rand.workspace = true
regex.workspace = true
rpc.workspace = true
runnable.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
Expand All @@ -71,7 +72,7 @@ collections = { workspace = true, features = ["test-support"] }
ctor.workspace = true
db = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
fs = { workspace = true, features = ["test-support"] }
fs = { workspace = true, features = ["test-support"] }
git2.workspace = true
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
Expand Down
19 changes: 17 additions & 2 deletions crates/project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod lsp_command;
pub mod lsp_ext_command;
mod prettier_support;
pub mod project_settings;
mod runnable_inventory;
pub mod search;
pub mod terminals;
pub mod worktree;
Expand Down Expand Up @@ -57,7 +58,8 @@ use postage::watch;
use prettier_support::{DefaultPrettier, PrettierInstance};
use project_settings::{LspSettings, ProjectSettings};
use rand::prelude::*;
use rpc::{ErrorCode, ErrorExt};

use rpc::{ErrorCode, ErrorExt as _};
use search::SearchQuery;
use serde::Serialize;
use settings::{Settings, SettingsStore};
Expand Down Expand Up @@ -91,6 +93,7 @@ use util::{
pub use fs::*;
#[cfg(any(test, feature = "test-support"))]
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
pub use runnable_inventory::Inventory;
pub use worktree::*;

const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
Expand Down Expand Up @@ -153,6 +156,7 @@ pub struct Project {
default_prettier: DefaultPrettier,
prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
prettier_instances: HashMap<PathBuf, PrettierInstance>,
runnables: Model<Inventory>,
}

pub enum LanguageServerToQuery {
Expand Down Expand Up @@ -615,6 +619,8 @@ impl Project {
.detach();
let copilot_lsp_subscription =
Copilot::global(cx).map(|copilot| subscribe_for_copilot_events(&copilot, cx));
let runnables = Inventory::new(cx);

Self {
worktrees: Vec::new(),
buffer_ordered_messages_tx: tx,
Expand Down Expand Up @@ -665,6 +671,7 @@ impl Project {
default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(),
runnables,
}
})
}
Expand All @@ -688,7 +695,10 @@ impl Project {
.await?;
let this = cx.new_model(|cx| {
let replica_id = response.payload.replica_id as ReplicaId;

let runnables = Inventory::new(cx);
// BIG CAUTION NOTE: The order in which we initialize fields here matters and it should match what's done in Self::local.
// Otherwise, you might run into issues where worktree id on remote is different than what's on local host.
// That's because Worktree's identifier is entity id, which should probably be changed.
let mut worktrees = Vec::new();
for worktree in response.payload.worktrees {
let worktree =
Expand Down Expand Up @@ -770,6 +780,7 @@ impl Project {
default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(),
runnables,
};
this.set_role(role, cx);
for worktree in worktrees {
Expand Down Expand Up @@ -1052,6 +1063,10 @@ impl Project {
cx.notify();
}

pub fn runnable_inventory(&self) -> &Model<Inventory> {
&self.runnables
}

pub fn collaborators(&self) -> &HashMap<proto::PeerId, Collaborator> {
&self.collaborators
}
Expand Down
66 changes: 66 additions & 0 deletions crates/project/src/runnable_inventory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! Project-wide storage of the runnables available, capable of updating itself from the sources set.

use std::{path::Path, sync::Arc};

use gpui::{AppContext, Context, Model, ModelContext, Subscription};
use runnable::{Runnable, RunnableId, Source};

/// Inventory tracks available runnables for a given project.
pub struct Inventory {
sources: Vec<SourceInInventory>,
pub last_scheduled_runnable: Option<RunnableId>,
}

struct SourceInInventory {
source: Model<Box<dyn Source>>,
_subscription: Subscription,
}

impl Inventory {
pub(crate) fn new(cx: &mut AppContext) -> Model<Self> {
cx.new_model(|_| Self {
sources: Vec::new(),
last_scheduled_runnable: None,
})
}

/// Registers a new runnables source, that would be fetched for available runnables.
pub fn add_source(&mut self, source: Model<Box<dyn Source>>, cx: &mut ModelContext<Self>) {
let _subscription = cx.observe(&source, |_, _, cx| {
cx.notify();
});
let source = SourceInInventory {
source,
_subscription,
};
self.sources.push(source);
cx.notify();
}

/// Pulls its sources to list runanbles for the path given (up to the source to decide what to return for no path).
pub fn list_runnables(
&self,
path: Option<&Path>,
cx: &mut AppContext,
) -> Vec<Arc<dyn Runnable>> {
let mut runnables = Vec::new();
for source in &self.sources {
runnables.extend(
source
.source
.update(cx, |source, cx| source.runnables_for_path(path, cx)),
);
}
runnables
}

/// Returns the last scheduled runnable, if any of the sources contains one with the matching id.
pub fn last_scheduled_runnable(&self, cx: &mut AppContext) -> Option<Arc<dyn Runnable>> {
self.last_scheduled_runnable.as_ref().and_then(|id| {
// TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future.
self.list_runnables(None, cx)
.into_iter()
.find(|runnable| runnable.id() == id)
})
}
}

0 comments on commit f17d0b5

Please sign in to comment.