2024-02-21 12:56:43 +00:00
//! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file.
2024-02-19 16:41:43 +00:00
use std ::{
2024-02-28 23:18:13 +00:00
borrow ::Cow ,
2024-02-19 16:41:43 +00:00
path ::{ Path , PathBuf } ,
sync ::Arc ,
} ;
use collections ::HashMap ;
use futures ::StreamExt ;
use gpui ::{ AppContext , Context , Model , ModelContext , Subscription } ;
use schemars ::{ gen ::SchemaSettings , JsonSchema } ;
use serde ::{ Deserialize , Serialize } ;
use util ::ResultExt ;
2024-02-28 09:04:01 +00:00
use crate ::{ SpawnInTerminal , Task , TaskId , TaskSource } ;
2024-02-19 16:41:43 +00:00
use futures ::channel ::mpsc ::UnboundedReceiver ;
2024-02-21 14:43:56 +00:00
/// A single config file entry with the deserialized task definition.
#[ derive(Clone, Debug, PartialEq) ]
struct StaticTask {
id : TaskId ,
definition : Definition ,
}
impl Task for StaticTask {
fn exec ( & self , cwd : Option < PathBuf > ) -> Option < SpawnInTerminal > {
Some ( SpawnInTerminal {
id : self . id . clone ( ) ,
cwd ,
use_new_terminal : self . definition . use_new_terminal ,
allow_concurrent_runs : self . definition . allow_concurrent_runs ,
label : self . definition . label . clone ( ) ,
command : self . definition . command . clone ( ) ,
args : self . definition . args . clone ( ) ,
env : self . definition . env . clone ( ) ,
separate_shell : false ,
} )
}
fn name ( & self ) -> & str {
& self . definition . label
}
fn id ( & self ) -> & TaskId {
& self . id
}
fn cwd ( & self ) -> Option < & Path > {
self . definition . cwd . as_deref ( )
}
}
2024-02-21 12:56:43 +00:00
/// The source of tasks defined in a tasks config file.
2024-02-19 16:41:43 +00:00
pub struct StaticSource {
2024-02-21 12:56:43 +00:00
tasks : Vec < StaticTask > ,
2024-02-19 16:41:43 +00:00
_definitions : Model < TrackedFile < DefinitionProvider > > ,
_subscription : Subscription ,
}
2024-02-21 12:56:43 +00:00
/// Static task definition from the tasks config file.
2024-02-19 16:41:43 +00:00
#[ derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema) ]
pub ( crate ) struct Definition {
2024-02-21 12:56:43 +00:00
/// Human readable name of the task to display in the UI.
2024-02-19 16:41:43 +00:00
pub label : String ,
/// Executable command to spawn.
pub command : String ,
/// Arguments to the command.
#[ serde(default) ]
pub args : Vec < String > ,
/// Env overrides for the command, will be appended to the terminal's environment from the settings.
#[ serde(default) ]
pub env : HashMap < String , String > ,
/// Current working directory to spawn the command into, defaults to current project root.
#[ serde(default) ]
pub cwd : Option < PathBuf > ,
/// Whether to use a new terminal tab or reuse the existing one to spawn the process.
#[ serde(default) ]
pub use_new_terminal : bool ,
2024-02-21 12:56:43 +00:00
/// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish.
2024-02-19 16:41:43 +00:00
#[ serde(default) ]
pub allow_concurrent_runs : bool ,
}
2024-02-21 12:56:43 +00:00
/// A group of Tasks defined in a JSON file.
2024-02-19 16:41:43 +00:00
#[ derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema) ]
2024-02-21 13:30:16 +00:00
pub struct DefinitionProvider ( Vec < Definition > ) ;
2024-02-19 16:41:43 +00:00
impl DefinitionProvider {
2024-02-21 12:56:43 +00:00
/// Generates JSON schema of Tasks JSON definition format.
2024-02-19 16:41:43 +00:00
pub fn generate_json_schema ( ) -> serde_json_lenient ::Value {
let schema = SchemaSettings ::draft07 ( )
. with ( | settings | settings . option_add_null_type = false )
. into_generator ( )
. into_root_schema_for ::< Self > ( ) ;
serde_json_lenient ::to_value ( schema ) . unwrap ( )
}
}
/// A Wrapper around deserializable T that keeps track of it's contents
/// via a provided channel. Once T value changes, the observers of [`TrackedFile`] are
/// notified.
struct TrackedFile < T > {
parsed_contents : T ,
}
impl < T : for < ' a > Deserialize < ' a > + PartialEq + 'static > TrackedFile < T > {
fn new (
parsed_contents : T ,
mut tracker : UnboundedReceiver < String > ,
cx : & mut AppContext ,
) -> Model < Self > {
cx . new_model ( move | cx | {
cx . spawn ( | tracked_file , mut cx | async move {
2024-02-20 13:02:35 +00:00
while let Some ( new_contents ) = tracker . next ( ) . await {
if ! new_contents . trim ( ) . is_empty ( ) {
let Some ( new_contents ) =
serde_json_lenient ::from_str ( & new_contents ) . log_err ( )
else {
continue ;
2024-02-19 16:41:43 +00:00
} ;
2024-02-20 13:02:35 +00:00
tracked_file . update ( & mut cx , | tracked_file : & mut TrackedFile < T > , cx | {
if tracked_file . parsed_contents ! = new_contents {
tracked_file . parsed_contents = new_contents ;
cx . notify ( ) ;
} ;
} ) ? ;
}
2024-02-19 16:41:43 +00:00
}
anyhow ::Ok ( ( ) )
} )
. detach_and_log_err ( cx ) ;
Self { parsed_contents }
} )
}
fn get ( & self ) -> & T {
& self . parsed_contents
}
}
impl StaticSource {
2024-02-21 12:56:43 +00:00
/// Initializes the static source, reacting on tasks config changes.
2024-02-19 16:41:43 +00:00
pub fn new (
2024-02-28 23:18:13 +00:00
id_base : impl Into < Cow < 'static , str > > ,
2024-02-21 12:56:43 +00:00
tasks_file_tracker : UnboundedReceiver < String > ,
2024-02-19 16:41:43 +00:00
cx : & mut AppContext ,
2024-02-28 09:04:01 +00:00
) -> Model < Box < dyn TaskSource > > {
2024-02-21 12:56:43 +00:00
let definitions = TrackedFile ::new ( DefinitionProvider ::default ( ) , tasks_file_tracker , cx ) ;
2024-02-19 16:41:43 +00:00
cx . new_model ( | cx | {
2024-02-28 23:18:13 +00:00
let id_base = id_base . into ( ) ;
2024-02-19 16:41:43 +00:00
let _subscription = cx . observe (
& definitions ,
2024-02-28 23:18:13 +00:00
move | source : & mut Box < ( dyn TaskSource + 'static ) > , new_definitions , cx | {
2024-02-19 16:41:43 +00:00
if let Some ( static_source ) = source . as_any ( ) . downcast_mut ::< Self > ( ) {
2024-02-21 12:56:43 +00:00
static_source . tasks = new_definitions
2024-02-19 16:41:43 +00:00
. read ( cx )
. get ( )
2024-02-21 13:30:16 +00:00
. 0
2024-02-19 16:41:43 +00:00
. clone ( )
. into_iter ( )
. enumerate ( )
2024-02-28 23:18:13 +00:00
. map ( | ( i , definition ) | StaticTask {
id : TaskId ( format! ( " static_ {id_base} _ {i} _ {} " , definition . label ) ) ,
definition ,
} )
2024-02-19 16:41:43 +00:00
. collect ( ) ;
cx . notify ( ) ;
}
} ,
) ;
Box ::new ( Self {
2024-02-21 12:56:43 +00:00
tasks : Vec ::new ( ) ,
2024-02-19 16:41:43 +00:00
_definitions : definitions ,
_subscription ,
} )
} )
}
}
2024-02-28 09:04:01 +00:00
impl TaskSource for StaticSource {
2024-02-21 12:56:43 +00:00
fn tasks_for_path (
2024-02-19 16:41:43 +00:00
& mut self ,
_ : Option < & Path > ,
2024-02-28 09:04:01 +00:00
_ : & mut ModelContext < Box < dyn TaskSource > > ,
2024-02-21 12:56:43 +00:00
) -> Vec < Arc < dyn Task > > {
self . tasks
2024-02-19 16:41:43 +00:00
. clone ( )
. into_iter ( )
2024-02-21 12:56:43 +00:00
. map ( | task | Arc ::new ( task ) as Arc < dyn Task > )
2024-02-19 16:41:43 +00:00
. collect ( )
}
fn as_any ( & mut self ) -> & mut dyn std ::any ::Any {
self
}
}