mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 04:20:46 +00:00
Runnables: Add oneshot runnables (#8061)
/cc @SomeoneToIgnore Fixes #7460 and partially addresses #7108 Release Notes: - N/A
This commit is contained in:
parent
8a73bc4c7d
commit
2ec910f772
10 changed files with 163 additions and 17 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -7786,6 +7786,7 @@ dependencies = [
|
|||
"fuzzy",
|
||||
"gpui",
|
||||
"log",
|
||||
"menu",
|
||||
"picker",
|
||||
"project",
|
||||
"runnable",
|
||||
|
@ -8561,9 +8562,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Project-wide storage of the runnables available, capable of updating itself from the sources set.
|
||||
|
||||
use std::{path::Path, sync::Arc};
|
||||
use std::{any::TypeId, path::Path, sync::Arc};
|
||||
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||
use runnable::{Runnable, RunnableId, Source};
|
||||
|
@ -14,6 +14,7 @@ pub struct Inventory {
|
|||
struct SourceInInventory {
|
||||
source: Model<Box<dyn Source>>,
|
||||
_subscription: Subscription,
|
||||
type_id: TypeId,
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
|
@ -29,13 +30,29 @@ impl Inventory {
|
|||
let _subscription = cx.observe(&source, |_, _, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
let type_id = source.read(cx).type_id();
|
||||
let source = SourceInInventory {
|
||||
source,
|
||||
_subscription,
|
||||
type_id,
|
||||
};
|
||||
self.sources.push(source);
|
||||
cx.notify();
|
||||
}
|
||||
pub fn source<T: Source>(&self) -> Option<Model<Box<dyn Source>>> {
|
||||
let target_type_id = std::any::TypeId::of::<T>();
|
||||
self.sources.iter().find_map(
|
||||
|SourceInInventory {
|
||||
type_id, source, ..
|
||||
}| {
|
||||
if &target_type_id == type_id {
|
||||
Some(source.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// 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(
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::sync::Arc;
|
|||
/// Runnable identifier, unique within the application.
|
||||
/// Based on it, runnable reruns and terminal tabs are managed.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RunnableId(String);
|
||||
pub struct RunnableId(pub String);
|
||||
|
||||
/// Contains all information needed by Zed to spawn a new terminal tab for the given runnable.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -36,6 +36,8 @@ pub struct SpawnInTerminal {
|
|||
pub use_new_terminal: bool,
|
||||
/// Whether to allow multiple instances of the same runnable to be run, or rather wait for the existing ones to finish.
|
||||
pub allow_concurrent_runs: bool,
|
||||
/// Whether the command should be spawned in a separate shell instance.
|
||||
pub separate_shell: bool,
|
||||
}
|
||||
|
||||
/// Represents a short lived recipe of a runnable, whose main purpose
|
||||
|
|
|
@ -31,6 +31,7 @@ impl Runnable for StaticRunnable {
|
|||
command: self.definition.command.clone(),
|
||||
args: self.definition.args.clone(),
|
||||
env: self.definition.env.clone(),
|
||||
separate_shell: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ futures.workspace = true
|
|||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
runnable.workspace = true
|
||||
|
|
|
@ -2,11 +2,13 @@ use std::path::PathBuf;
|
|||
|
||||
use gpui::{AppContext, ViewContext, WindowContext};
|
||||
use modal::RunnablesModal;
|
||||
pub use oneshot_source::OneshotSource;
|
||||
use runnable::Runnable;
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
mod modal;
|
||||
mod oneshot_source;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(
|
||||
|
|
|
@ -2,8 +2,8 @@ use std::sync::Arc;
|
|||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, rems, DismissEvent, EventEmitter, FocusableView, InteractiveElement, Model,
|
||||
ParentElement, Render, SharedString, Styled, Subscription, Task, View, ViewContext,
|
||||
actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, InteractiveElement,
|
||||
Model, ParentElement, Render, SharedString, Styled, Subscription, Task, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
|
@ -13,7 +13,7 @@ use ui::{v_flex, HighlightedLabel, ListItem, ListItemSpacing, Selectable};
|
|||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::schedule_runnable;
|
||||
use crate::{schedule_runnable, OneshotSource};
|
||||
|
||||
actions!(runnables, [Spawn, Rerun]);
|
||||
|
||||
|
@ -25,6 +25,7 @@ pub(crate) struct RunnablesModalDelegate {
|
|||
selected_index: usize,
|
||||
placeholder_text: Arc<str>,
|
||||
workspace: WeakView<Workspace>,
|
||||
last_prompt: String,
|
||||
}
|
||||
|
||||
impl RunnablesModalDelegate {
|
||||
|
@ -36,8 +37,21 @@ impl RunnablesModalDelegate {
|
|||
matches: Vec::new(),
|
||||
selected_index: 0,
|
||||
placeholder_text: Arc::from("Select runnable..."),
|
||||
last_prompt: String::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_oneshot(&mut self, cx: &mut AppContext) -> Option<Arc<dyn Runnable>> {
|
||||
let oneshot_source = self
|
||||
.inventory
|
||||
.update(cx, |this, _| this.source::<OneshotSource>())?;
|
||||
oneshot_source.update(cx, |this, _| {
|
||||
let Some(this) = this.as_any().downcast_mut::<OneshotSource>() else {
|
||||
return None;
|
||||
};
|
||||
Some(this.spawn(self.last_prompt.clone()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RunnablesModal {
|
||||
|
@ -149,6 +163,7 @@ impl PickerDelegate for RunnablesModalDelegate {
|
|||
.update(&mut cx, |picker, _| {
|
||||
let delegate = &mut picker.delegate;
|
||||
delegate.matches = matches;
|
||||
delegate.last_prompt = query;
|
||||
|
||||
if delegate.matches.is_empty() {
|
||||
delegate.selected_index = 0;
|
||||
|
@ -161,14 +176,21 @@ impl PickerDelegate for RunnablesModalDelegate {
|
|||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<picker::Picker<Self>>) {
|
||||
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<picker::Picker<Self>>) {
|
||||
let current_match_index = self.selected_index();
|
||||
let Some(current_match) = self.matches.get(current_match_index) else {
|
||||
let Some(runnable) = secondary
|
||||
.then(|| self.spawn_oneshot(cx))
|
||||
.flatten()
|
||||
.or_else(|| {
|
||||
self.matches.get(current_match_index).map(|current_match| {
|
||||
let ix = current_match.candidate_id;
|
||||
self.candidates[ix].clone()
|
||||
})
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let ix = current_match.candidate_id;
|
||||
let runnable = &self.candidates[ix];
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
schedule_runnable(workspace, runnable.as_ref(), cx);
|
||||
|
|
79
crates/runnables_ui/src/oneshot_source.rs
Normal file
79
crates/runnables_ui/src/oneshot_source.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use gpui::{AppContext, Model};
|
||||
use runnable::{Runnable, RunnableId, Source};
|
||||
use ui::Context;
|
||||
|
||||
pub struct OneshotSource {
|
||||
runnables: Vec<Arc<dyn runnable::Runnable>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct OneshotRunnable {
|
||||
id: RunnableId,
|
||||
}
|
||||
|
||||
impl OneshotRunnable {
|
||||
fn new(prompt: String) -> Self {
|
||||
Self {
|
||||
id: RunnableId(prompt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Runnable for OneshotRunnable {
|
||||
fn id(&self) -> &runnable::RunnableId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.id.0
|
||||
}
|
||||
|
||||
fn cwd(&self) -> Option<&std::path::Path> {
|
||||
None
|
||||
}
|
||||
|
||||
fn exec(&self, cwd: Option<std::path::PathBuf>) -> Option<runnable::SpawnInTerminal> {
|
||||
if self.id().0.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(runnable::SpawnInTerminal {
|
||||
id: self.id().clone(),
|
||||
label: self.name().to_owned(),
|
||||
command: self.id().0.clone(),
|
||||
args: vec![],
|
||||
cwd,
|
||||
env: Default::default(),
|
||||
use_new_terminal: Default::default(),
|
||||
allow_concurrent_runs: Default::default(),
|
||||
separate_shell: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl OneshotSource {
|
||||
pub fn new(cx: &mut AppContext) -> Model<Box<dyn Source>> {
|
||||
cx.new_model(|_| Box::new(Self { runnables: vec![] }) as Box<dyn Source>)
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self, prompt: String) -> Arc<dyn runnable::Runnable> {
|
||||
let ret = Arc::new(OneshotRunnable::new(prompt));
|
||||
self.runnables.push(ret.clone());
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for OneshotSource {
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn runnables_for_path(
|
||||
&mut self,
|
||||
_path: Option<&std::path::Path>,
|
||||
_cx: &mut gpui::ModelContext<Box<dyn Source>>,
|
||||
) -> Vec<Arc<dyn runnable::Runnable>> {
|
||||
self.runnables.clone()
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use terminal::{
|
||||
terminal_settings::{TerminalDockPosition, TerminalSettings},
|
||||
terminal_settings::{Shell, TerminalDockPosition, TerminalSettings},
|
||||
SpawnRunnable,
|
||||
};
|
||||
use ui::{h_flex, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip};
|
||||
|
@ -300,13 +300,30 @@ impl TerminalPanel {
|
|||
spawn_in_terminal: &runnable::SpawnInTerminal,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let spawn_runnable = SpawnRunnable {
|
||||
let mut spawn_runnable = SpawnRunnable {
|
||||
id: spawn_in_terminal.id.clone(),
|
||||
label: spawn_in_terminal.label.clone(),
|
||||
command: spawn_in_terminal.command.clone(),
|
||||
args: spawn_in_terminal.args.clone(),
|
||||
env: spawn_in_terminal.env.clone(),
|
||||
};
|
||||
if spawn_in_terminal.separate_shell {
|
||||
let Some((shell, mut user_args)) = (match TerminalSettings::get_global(cx).shell.clone()
|
||||
{
|
||||
Shell::System => std::env::var("SHELL").ok().map(|shell| (shell, vec![])),
|
||||
Shell::Program(shell) => Some((shell, vec![])),
|
||||
Shell::WithArguments { program, args } => Some((program, args)),
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let command = std::mem::take(&mut spawn_runnable.command);
|
||||
let args = std::mem::take(&mut spawn_runnable.args);
|
||||
spawn_runnable.command = shell;
|
||||
user_args.extend(["-c".to_owned(), command]);
|
||||
user_args.extend(args);
|
||||
spawn_runnable.args = user_args;
|
||||
}
|
||||
let working_directory = spawn_in_terminal.cwd.clone();
|
||||
let allow_concurrent_runs = spawn_in_terminal.allow_concurrent_runs;
|
||||
let use_new_terminal = spawn_in_terminal.use_new_terminal;
|
||||
|
|
|
@ -23,6 +23,7 @@ use quick_action_bar::QuickActionBar;
|
|||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use rope::Rope;
|
||||
use runnable::static_source::StaticSource;
|
||||
use runnables_ui::OneshotSource;
|
||||
use search::project_search::ProjectSearchBar;
|
||||
use settings::{
|
||||
initial_local_settings_content, watch_config_file, KeymapFile, Settings, SettingsStore,
|
||||
|
@ -163,11 +164,14 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||
app_state.fs.clone(),
|
||||
paths::RUNNABLES.clone(),
|
||||
);
|
||||
let source = StaticSource::new(runnables_file_rx, cx);
|
||||
let static_source = StaticSource::new(runnables_file_rx, cx);
|
||||
let oneshot_source = OneshotSource::new(cx);
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project
|
||||
.runnable_inventory()
|
||||
.update(cx, |inventory, cx| inventory.add_source(source, cx))
|
||||
project.runnable_inventory().update(cx, |inventory, cx| {
|
||||
inventory.add_source(oneshot_source, cx);
|
||||
inventory.add_source(static_source, cx);
|
||||
})
|
||||
});
|
||||
}
|
||||
cx.spawn(|workspace_handle, mut cx| async move {
|
||||
|
|
Loading…
Reference in a new issue