diff --git a/Cargo.lock b/Cargo.lock index 42f7ac5..b53fd9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -569,6 +580,7 @@ version = "0.2.0" dependencies = [ "anyhow", "async-trait", + "atty", "bytes", "clap", "crossterm", @@ -626,6 +638,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -990,7 +1011,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "log", "wasi", diff --git a/crates/gitnow/.gitignore b/crates/gitnow/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/crates/gitnow/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/crates/gitnow/Cargo.toml b/crates/gitnow/Cargo.toml index 365239e..22fa600 100644 --- a/crates/gitnow/Cargo.toml +++ b/crates/gitnow/Cargo.toml @@ -28,6 +28,7 @@ nucleo-matcher = "0.3.1" ratatui = "0.28.1" crossterm = { version = "0.28.0", features = ["event-stream"] } futures = "0.3.30" +atty = "0.2.14" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/crates/gitnow/include/shell/zsh.sh b/crates/gitnow/include/shell/zsh.sh new file mode 100644 index 0000000..5db2478 --- /dev/null +++ b/crates/gitnow/include/shell/zsh.sh @@ -0,0 +1,15 @@ +function git-now { + choice=$(gitnow "$@") + if [[ $? -ne 0 ]]; then + return $? + fi + + cd "$(echo "$choice" | tail --lines 1)" +} + +function gn { + git-now "$@" + if [[ $? -ne 0 ]]; then + return $? + fi +} diff --git a/crates/gitnow/src/commands/root.rs b/crates/gitnow/src/commands/root.rs index c39c844..f6531f0 100644 --- a/crates/gitnow/src/commands/root.rs +++ b/crates/gitnow/src/commands/root.rs @@ -88,16 +88,21 @@ impl RootCommand { if clone { let git_clone = self.app.git_clone(); - let mut wrap_cmd = - InlineCommand::new(format!("cloning: {}", repo.to_rel_path().display())); - let repo = repo.clone(); - wrap_cmd - .execute(move || async move { - git_clone.clone_repo(&repo, force_refresh).await?; + if atty::is(atty::Stream::Stdout) && shell { + let mut wrap_cmd = + InlineCommand::new(format!("cloning: {}", repo.to_rel_path().display())); + let repo = repo.clone(); + wrap_cmd + .execute(move || async move { + git_clone.clone_repo(&repo, force_refresh).await?; - Ok(()) - }) - .await?; + Ok(()) + }) + .await?; + } else { + eprintln!("cloning repository..."); + git_clone.clone_repo(&repo, force_refresh).await?; + } } else { tracing::info!("skipping clone for repo: {}", &repo.to_rel_path().display()); } diff --git a/crates/gitnow/src/commands/shell.rs b/crates/gitnow/src/commands/shell.rs index e69de29..fcac32a 100644 --- a/crates/gitnow/src/commands/shell.rs +++ b/crates/gitnow/src/commands/shell.rs @@ -0,0 +1,33 @@ +use zsh::ZshShell; + +pub mod zsh; + +#[derive(clap::Parser)] +pub struct Shell { + #[command(subcommand)] + shell: ShellSubcommands, +} + +impl Shell { + pub async fn execute(&mut self) -> anyhow::Result<()> { + self.shell.execute().await?; + + Ok(()) + } +} + +#[derive(clap::Subcommand)] +pub enum ShellSubcommands { + #[command()] + Zsh(ZshShell), +} + +impl ShellSubcommands { + pub async fn execute(&mut self) -> anyhow::Result<()> { + match self { + ShellSubcommands::Zsh(zsh) => zsh.execute().await?, + } + + Ok(()) + } +} diff --git a/crates/gitnow/src/commands/shell/zsh.rs b/crates/gitnow/src/commands/shell/zsh.rs new file mode 100644 index 0000000..0c389e6 --- /dev/null +++ b/crates/gitnow/src/commands/shell/zsh.rs @@ -0,0 +1,12 @@ +#[derive(clap::Parser)] +pub struct ZshShell {} + +const SCRIPT: &str = include_str!("../../../include/shell/zsh.sh"); + +impl ZshShell { + pub async fn execute(&mut self) -> anyhow::Result<()> { + println!("{}", SCRIPT); + + Ok(()) + } +} diff --git a/crates/gitnow/src/components/inline_command.rs b/crates/gitnow/src/components/inline_command.rs index 672e9ed..b090592 100644 --- a/crates/gitnow/src/components/inline_command.rs +++ b/crates/gitnow/src/components/inline_command.rs @@ -1,7 +1,14 @@ -use std::{io::Write, time::Duration}; +use std::{ + io::{stderr, Write}, + time::Duration, +}; use anyhow::Context; -use crossterm::event::{EventStream, KeyCode, KeyEventKind}; +use crossterm::{ + event::{EventStream, KeyCode, KeyEventKind}, + terminal::{enable_raw_mode, EnterAlternateScreen}, + ExecutableCommand, +}; use futures::{FutureExt, StreamExt}; use ratatui::{ crossterm, @@ -73,6 +80,8 @@ impl InlineCommand { drop(guard); + println!(); + Ok(()) } diff --git a/crates/gitnow/src/interactive.rs b/crates/gitnow/src/interactive.rs index 304ce90..02f03a8 100644 --- a/crates/gitnow/src/interactive.rs +++ b/crates/gitnow/src/interactive.rs @@ -1,4 +1,11 @@ +use std::io::stderr; + use app::App; +use crossterm::{ + terminal::{enable_raw_mode, EnterAlternateScreen}, + ExecutableCommand, +}; +use ratatui::{prelude::CrosstermBackend, Terminal}; use crate::git_provider::Repository; @@ -15,8 +22,14 @@ impl Interactive { &mut self, repositories: &[Repository], ) -> anyhow::Result> { - let terminal = ratatui::init(); + let backend = CrosstermBackend::new(std::io::stderr()); + let terminal = Terminal::new(backend)?; + + enable_raw_mode()?; + stderr().execute(EnterAlternateScreen)?; + let app_result = App::new(self.app, repositories).run(terminal); + ratatui::restore(); app_result @@ -37,10 +50,11 @@ mod app { use ratatui::{ crossterm::event::{self, Event, KeyCode}, layout::{Constraint, Layout}, + prelude::CrosstermBackend, style::{Style, Stylize}, text::{Line, Span}, widgets::{ListItem, ListState, Paragraph, StatefulWidget}, - DefaultTerminal, Frame, + Frame, Terminal, }; use crate::{ @@ -81,7 +95,10 @@ mod app { } } - pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result> { + pub fn run( + mut self, + mut terminal: Terminal>, + ) -> anyhow::Result> { self.update_matched_repos(); loop { @@ -99,14 +116,19 @@ mod app { self.update_matched_repos(); } } - KeyCode::Esc => return Ok(None), + KeyCode::Esc => { + terminal.clear()?; + return Ok(None); + } KeyCode::Enter => { if let Some(selected) = self.list.selected() { if let Some(repo) = self.matched_repos.get(selected).cloned() { + terminal.clear()?; return Ok(Some(repo)); } } + terminal.clear()?; return Ok(None); } KeyCode::Up => self.list.select_next(), diff --git a/crates/gitnow/src/main.rs b/crates/gitnow/src/main.rs index 3e838b3..c21ef01 100644 --- a/crates/gitnow/src/main.rs +++ b/crates/gitnow/src/main.rs @@ -4,8 +4,7 @@ use std::path::PathBuf; use anyhow::Context; use clap::{Parser, Subcommand}; -use commands::root::RootCommand; -use components::inline_command::InlineCommand; +use commands::{root::RootCommand, shell::Shell}; use config::Config; use tracing::level_filters::LevelFilter; use tracing_subscriber::EnvFilter; @@ -50,7 +49,7 @@ struct Command { #[derive(Subcommand)] enum Commands { - Hello {}, + Init(Shell), } const DEFAULT_CONFIG_PATH: &str = ".config/gitnow/gitnow.toml"; @@ -81,7 +80,7 @@ async fn main() -> anyhow::Result<()> { tracing::debug!("Starting cli"); match cli.command { - Some(_) => todo!(), + Some(Commands::Init(mut shell)) => shell.execute().await?, None => { RootCommand::new(app) .execute(