tab_command/
lib.rs

1use std::time::Duration;
2
3use clap::ArgMatches;
4use semver::Version;
5
6use crate::prelude::*;
7use service::{main::*, terminal::disable_raw_mode, terminal::reset_terminal_state};
8
9use simplelog::{TermLogger, TerminalMode};
10
11use crate::bus::MainBus;
12use message::main::{MainRecv, MainShutdown};
13
14use lifeline::dyn_bus::DynBus;
15use tab_api::{config::DaemonConfig, launch::*, log::get_level, tab::normalize_name};
16use tab_websocket::resource::connection::WebsocketResource;
17
18mod bus;
19mod env;
20mod message;
21mod prelude;
22mod service;
23mod state;
24mod utils;
25
26pub fn command_main(args: ArgMatches, tab_version: &'static str) -> anyhow::Result<i32> {
27    TermLogger::init(
28        get_level().unwrap_or(LevelFilter::Warn),
29        simplelog::ConfigBuilder::new()
30            .set_time_format_str("%H:%M:%S%.3f CMD")
31            .build(),
32        TerminalMode::Stderr,
33    )
34    .unwrap();
35
36    info!("tab-command runtime starting");
37
38    let mut runtime = tokio::runtime::Builder::new()
39        .threaded_scheduler()
40        .enable_io()
41        .enable_time()
42        .build()
43        .unwrap();
44
45    let result = runtime.block_on(async { main_async(args, tab_version).await });
46
47    info!("tab-command runtime stopped");
48    runtime.shutdown_timeout(Duration::from_millis(25));
49
50    let code = result?;
51
52    info!("tab-command runtime stopped");
53
54    Ok(code)
55}
56
57async fn main_async(matches: ArgMatches<'_>, tab_version: &'static str) -> anyhow::Result<i32> {
58    let check_workspace = matches.is_present("CHECK-WORKSPACE");
59    let close_completion = matches.is_present("AUTOCOMPLETE-CLOSE-TAB");
60    let close_tabs = matches.values_of("CLOSE-TAB");
61    let completion = matches.is_present("AUTOCOMPLETE-TAB");
62    let disconnect_tabs = matches.values_of("DISCONNECT-TAB");
63    let select_tab = matches.value_of("TAB-NAME");
64    let shutdown = matches.is_present("SHUTDOWN");
65
66    let spawn_result = spawn(tab_version).await;
67
68    if let Err(e) = spawn_result {
69        error!("Failed to initialize the tab command: {}", e);
70        return Err(e);
71    }
72
73    let (mut tx, rx_shutdown, _service) = spawn_result.unwrap();
74
75    debug!("Parsing CLI arguments...");
76    if shutdown {
77        info!("CLI Match: GlobalShutdown");
78        tx.send(MainRecv::GlobalShutdown).await?;
79    } else if completion {
80        info!("CLI Match: AutocompleteTab");
81        tx.send(MainRecv::AutocompleteTab).await?;
82    } else if close_completion {
83        info!("CLI Match: AutocompleteCloseTab");
84        tx.send(MainRecv::AutocompleteCloseTab).await?;
85    } else if check_workspace {
86        info!("CLI Match: CheckWorkspace");
87        tx.send(MainRecv::CheckWorkspace).await?;
88    } else if matches.is_present("LIST") {
89        info!("CLI Match: ListTabs");
90        tx.send(MainRecv::ListTabs).await?;
91    } else if let Some(tab) = select_tab {
92        info!("CLI Match: SelectTab({})", &tab);
93        tx.send(MainRecv::SelectTab(tab.to_string())).await?;
94    } else if let Some(tabs) = close_tabs {
95        info!("CLI Match: CloseTabs({:?})", &tabs);
96        let tabs: Vec<String> = tabs.map(normalize_name).collect();
97        tx.send(MainRecv::CloseTabs(tabs)).await?;
98    } else if let Some(tabs) = disconnect_tabs {
99        info!("CLI Match: DisconnectTabs({:?})", &tabs);
100        let tabs: Vec<String> = tabs.map(normalize_name).collect();
101        tx.send(MainRecv::DisconnectTabs(tabs)).await?;
102    } else {
103        info!("CLI Match: SelectInteractive");
104        tx.send(MainRecv::SelectInteractive).await?;
105    }
106
107    let exit = wait_for_shutdown(rx_shutdown).await;
108    disable_raw_mode();
109    reset_terminal_state();
110
111    debug!("tab-command shutdown.");
112
113    Ok(exit.0)
114}
115
116async fn spawn(
117    tab_version: &'static str,
118) -> anyhow::Result<(
119    impl Sender<MainRecv>,
120    impl Receiver<MainShutdown>,
121    MainService,
122)> {
123    let daemon_file = launch_daemon().await?;
124    validate_daemon(&daemon_file, tab_version);
125    let ws_url = format!("ws://127.0.0.1:{}/cli", daemon_file.port);
126
127    debug!("daemon is ready");
128
129    let bus = MainBus::default();
130    bus.capacity::<Request>(128)?;
131    bus.capacity::<Response>(256)?;
132
133    let websocket =
134        tab_websocket::connect_authorized(ws_url, daemon_file.auth_token.clone()).await?;
135    let websocket = WebsocketResource(websocket);
136    bus.store_resource(websocket);
137
138    info!("Launching MainService");
139    let service = MainService::spawn(&bus)?;
140
141    let tx = bus.tx::<MainRecv>()?;
142    let main_shutdown = bus.rx::<MainShutdown>()?;
143
144    debug!("Main spawn complete");
145
146    Ok((tx, main_shutdown, service))
147}
148
149fn validate_daemon(config: &DaemonConfig, tab_version: &'static str) {
150    let executable = std::env::current_exe()
151        .ok()
152        .map(|path| path.to_str().map(str::to_string))
153        .flatten();
154
155    let tab_version = Version::parse(tab_version).ok();
156    let daemon_version = config
157        .tab_version
158        .as_ref()
159        .map(String::as_str)
160        .map(Version::parse)
161        .map(Result::ok)
162        .flatten();
163
164    if let (Some(tab_version), Some(daemon_version)) = (tab_version, daemon_version) {
165        if tab_version.major != daemon_version.major || tab_version.minor != daemon_version.minor {
166            eprintln!("Warning: The tab command (v{}) has a different version than the running daemon (v{})", tab_version, daemon_version);
167            eprintln!(
168                "  You should run `tab --shutdown` to terminate your tabs and relaunch the daemon."
169            );
170
171            return;
172        }
173    }
174
175    if let (Some(executable), Some(daemon_exec)) = (&executable, &config.executable) {
176        if executable != daemon_exec {
177            eprintln!(
178                "Warning: The tab command has a different executable path than the running daemon."
179            );
180            eprintln!("  You may want to run `tab --shutdown` to terminate your tabs and relaunch the daemon.");
181
182            eprintln!("  Tab command: {}", executable);
183            eprintln!("  Daemon command: {}", daemon_exec);
184            return;
185        }
186    }
187}