use crate::{
env::terminal_size,
message::tabs::CreateTabRequest,
prelude::*,
state::{
tabs::ActiveTabsState,
workspace::{WorkspaceState, WorkspaceTab},
},
utils::await_state,
};
use anyhow::anyhow;
use std::{collections::HashMap, sync::Arc};
use std::{env, path::PathBuf};
use tab_api::tab::{normalize_name, CreateTabMetadata};
pub struct CreateTabService {
_request_tab: Lifeline,
}
impl Service for CreateTabService {
type Bus = TabBus;
type Lifeline = anyhow::Result<Self>;
fn spawn(bus: &Self::Bus) -> Self::Lifeline {
let mut rx = bus.rx::<CreateTabRequest>()?;
let mut rx_tabs_state = bus.rx::<Option<ActiveTabsState>>()?.into_inner();
let mut rx_workspace = bus.rx::<Option<WorkspaceState>>()?.into_inner();
let mut tx_websocket = bus.tx::<Request>()?;
let _request_tab = Self::try_task("request_tab", async move {
while let Some(request) = rx.recv().await {
match request {
CreateTabRequest::Named(name) => {
let tab_exists = await_state(&mut rx_tabs_state)
.await?
.tabs
.values()
.find(|tab| tab.name == name)
.is_some();
if !tab_exists {
let workspace = await_state(&mut rx_workspace).await?;
Self::create_named(name, workspace.tabs, &mut tx_websocket).await?;
}
}
}
}
Ok(())
});
Ok(Self { _request_tab })
}
}
impl CreateTabService {
pub async fn create_named(
name: String,
workspace: Arc<Vec<WorkspaceTab>>,
tx_websocket: &mut impl Sender<Request>,
) -> anyhow::Result<()> {
let name = normalize_name(name.as_str());
let workspace_tab = workspace.iter().find(|tab| tab.name == name);
let dimensions = terminal_size()?;
let shell = Self::compute_shell(workspace_tab);
let directory = Self::compute_directory(workspace_tab)?;
let env = Self::compute_env(workspace_tab);
let metadata = CreateTabMetadata {
name: Self::compute_name(workspace_tab, name.as_str()),
doc: workspace_tab.map(|tab| tab.doc.clone()).flatten(),
dir: directory
.to_str()
.ok_or_else(|| {
anyhow!(
"The working directory {} could not be parsed as a string",
directory.to_string_lossy()
)
})?
.to_string(),
env,
dimensions,
shell,
};
let request = Request::CreateTab(metadata);
tx_websocket.send(request).await?;
Ok(())
}
fn compute_shell(tab: Option<&WorkspaceTab>) -> String {
if let Some(tab) = tab {
if let Some(ref shell) = tab.shell {
return shell.clone();
}
}
std::env::var("SHELL").unwrap_or("/usr/bin/env bash".to_string())
}
fn compute_env(tab: Option<&WorkspaceTab>) -> HashMap<String, String> {
let mut env = tab
.map(|tab| tab.env.clone())
.flatten()
.unwrap_or(HashMap::with_capacity(0));
Self::copy_env(&mut env, "TERM");
Self::copy_env(&mut env, "TERMINFO");
Self::copy_env(&mut env, "COLORTERM");
env
}
fn copy_env(env_map: &mut HashMap<String, String>, var: &str) {
let var = var.to_string();
if !env_map.contains_key(&var) {
if let Ok(value) = env::var(&var) {
env_map.insert(var.clone(), value);
}
}
}
fn compute_directory(tab: Option<&WorkspaceTab>) -> anyhow::Result<PathBuf> {
if let Some(tab) = tab {
if tab.directory.exists() {
return Ok(tab.directory.clone());
} else {
warn!(
"The configured working directory for '{}' was not found: {}",
tab.name,
tab.directory.as_path().to_string_lossy()
);
}
}
std::env::current_dir().map_err(|err| err.into())
}
fn compute_name(tab: Option<&WorkspaceTab>, name: &str) -> String {
if let Some(tab) = tab {
tab.name.clone()
} else {
name.to_string()
}
}
}