Skip to content

Commit

Permalink
cleanup, better hotkeys, handle coming back from editor
Browse files Browse the repository at this point in the history
  • Loading branch information
robo-corg committed Aug 26, 2024
1 parent c7fecc4 commit f2ead38
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 87 deletions.
21 changes: 18 additions & 3 deletions .github/workflows/rust_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,25 @@ jobs:
matrix:
toolchain:
- stable
- beta
- nightly
steps:
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}

- name: Install rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy, rustfmt

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}

- run: cargo fmt --all -- --check
- run: cargo clippy --all-targets --all-features -- -D warnings
- run: cargo build --verbose
- run: cargo test --verbose

33 changes: 31 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ name = "porgi"
version = "0.1.0"
edition = "2021"

description = "A simple cli TUI for finding and organizing your code projects"
description = "A simple cli TUI for finding and organizing code projects"
authors = ["Andrew McHarg <amcharg@gmail.com>"]

rust-version = "1.80"
repository = "https://github.com/robo-corg/porgi"
license = "MIT"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -21,7 +24,7 @@ ignore = "0.4.22"
ratatui = { version = "0.26.2", features = ["serde"] }
serde = { version = "1.0.200", features = ["derive"] }
shellexpand = "3.1.0"
tokio = { version = "1.39.3", features = ["fs", "macros", "rt", "rt-multi-thread", "sync"] }
tokio = { version = "1.39.3", features = ["fs", "macros", "process", "rt", "rt-multi-thread", "sync"] }
tokio-stream = { version = "0.1.15", features = ["fs"] }
toml = "0.8.12"
which = "6.0.3"
7 changes: 2 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct Config {
pub opener: ProjectOpener,
}

fn must_exist<'a>(p: &'a PathBuf) -> Option<&'a PathBuf> {
fn must_exist(p: &PathBuf) -> Option<&PathBuf> {
if p.exists() {
Some(p)
} else {
Expand All @@ -34,10 +34,7 @@ impl Config {
if config_dir == config_in_homedir {
config_dir.into_iter().collect()
} else {
config_dir
.into_iter()
.chain(config_in_homedir.into_iter())
.collect()
config_dir.into_iter().chain(config_in_homedir).collect()
}
}

Expand Down
125 changes: 68 additions & 57 deletions src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::Poll;

use eyre::Result;
use eyre::{anyhow, Context};
use eyre::{OptionExt, Result};
use futures::{future, stream, FutureExt, Stream, StreamExt, TryStreamExt};
use ignore::WalkBuilder;
use serde::Deserialize;
use tokio::process;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio_stream::wrappers::{ReadDirStream, ReceiverStream};
use which::which;
Expand All @@ -21,33 +22,9 @@ pub(crate) type ProjectKey = PathBuf;

pub(crate) enum ProjectEvent {
Add(Project),
Update(ProjectKey, std::time::SystemTime),
Update(ProjectKey, std::time::SystemTime, usize),
}

#[derive(Debug, Deserialize)]
pub(crate) struct CargoMeta {
name: String,
version: String,
authors: Vec<String>,
description: Option<String>,
license: Option<String>,
repository: Option<String>,
dependencies: Vec<String>,
dev_dependencies: Vec<String>,
}

#[derive(Debug)]
pub(crate) enum PackageMeta {
Cargo(CargoMeta),
}

// pub(crate) struct ProjectDirInfo {
// pub(crate) parent: Option<usize>,
// pub(crate) modified: std::time::SystemTime,
// pub(crate) immediate_child_count: usize,
// pub(crate) total_child_count: usize,
// }

#[derive(Debug, Default)]
pub(crate) struct ProjectStore {
project_by_key: HashMap<ProjectKey, usize>,
Expand Down Expand Up @@ -102,18 +79,18 @@ impl Index<usize> for ProjectStore {
pub(crate) struct Project {
pub(crate) name: String,
pub(crate) path: PathBuf,
pub(crate) git: bool,
pub(crate) package: Vec<PackageMeta>,
pub(crate) readme: Option<String>,
pub(crate) modified: std::time::SystemTime,
pub(crate) file_count: usize,
}

impl Project {
pub fn from_path(_config: &Config, path: PathBuf) -> Result<Self> {
let git = path.join(".git").exists();
let package = Vec::new();
let name = path.file_name().unwrap().to_string_lossy().to_string();
let name = path
.file_name()
.ok_or_eyre("Project path does not have a name")?
.to_string_lossy()
.to_string();

let readme_path = path.join("README.md");
let readme = if readme_path.exists() {
Expand All @@ -127,8 +104,6 @@ impl Project {
Ok(Project {
name,
path,
git,
package,
readme,
modified,
file_count,
Expand Down Expand Up @@ -182,18 +157,18 @@ impl ProjectLoader {

let walker = tokio::spawn(async move {
walker_rx_stream
.map::<Result<PathBuf>, _>(|p| Ok(p))
.map::<Result<PathBuf>, _>(Ok)
.try_for_each_concurrent(8, move |path| {
let config = config.clone();
let tx = tx.clone();
async move {
let summary_path = path.clone();
let (modified, _file_count) = tokio::task::spawn_blocking(move || {
let (modified, file_count) = tokio::task::spawn_blocking(move || {
get_file_summary(config.as_ref(), &summary_path)
})
.await??;

tx.send(ProjectEvent::Update(path.to_owned(), modified))
tx.send(ProjectEvent::Update(path.to_owned(), modified, file_count))
.await?;
Ok(())
}
Expand Down Expand Up @@ -268,64 +243,100 @@ impl Stream for ProjectLoader {
}
}

#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum AddPathToArgs {
#[default]
Auto,
Last,
Never,
}

#[derive(Debug, Deserialize)]
pub struct Command {
args: Vec<String>,
#[serde(default = "ProjectOpener::chdir_default")]
chdir: bool,
#[serde(default)]
add_path_to_args: AddPathToArgs,
}

#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ProjectOpener {
#[default]
Auto,
Code,
Editor,
Command(Vec<String>),
Command(Command),
}

impl ProjectOpener {
pub(crate) fn open(&self, project: &Project) -> Result<()> {
fn chdir_default() -> bool {
true
}

pub(crate) async fn open(&self, project: &Project) -> Result<()> {
match self {
ProjectOpener::Auto => {
if which("code").is_ok() {
Self::open_code(project)
Self::open_code(project).await
} else if std::env::var("EDITOR").is_ok() {
Self::open_editor(project)
Self::open_editor(project).await
} else {
Err(anyhow!("vscode not found nor was an editor set"))
}
}
ProjectOpener::Code => Self::open_code(project),
ProjectOpener::Editor => Self::open_editor(project),
ProjectOpener::Command(cmd) => Self::open_command(project, cmd),
ProjectOpener::Code => Self::open_code(project).await,
ProjectOpener::Editor => Self::open_editor(project).await,
ProjectOpener::Command(cmd) => Self::open_command(project, cmd).await,
}
}

pub(crate) fn open_code(project: &Project) -> Result<()> {
let mut child = std::process::Command::new("code")
.arg(&project.path)
.spawn()?;
pub(crate) async fn open_code(project: &Project) -> Result<()> {
let mut child = process::Command::new("code").arg(&project.path).spawn()?;

child.wait()?;
child.wait().await?;

Ok(())
}

pub(crate) fn open_editor(project: &Project) -> Result<()> {
pub(crate) async fn open_editor(project: &Project) -> Result<()> {
let editor =
std::env::var("EDITOR").wrap_err("Could not read EDITOR environment variable")?;

let mut child = std::process::Command::new(&editor)
let mut child = process::Command::new(&editor)
.current_dir(&project.path)
.arg(&project.path)
.spawn()?;

child.wait()?;
child.wait().await?;

Ok(())
}

pub(crate) fn open_command(project: &Project, cmd: &[String]) -> Result<()> {
let mut child = std::process::Command::new(&cmd[0])
.args(&cmd[1..])
.arg(&project.path)
.spawn()?;
pub(crate) async fn open_command(project: &Project, cmd: &Command) -> Result<()> {
let mut proc: process::Command = process::Command::new(&cmd.args[0]);

proc.args(&cmd.args[1..]);

match cmd.add_path_to_args {
AddPathToArgs::Auto => {
proc.arg(&project.path);
}
AddPathToArgs::Last => {
proc.arg(&project.path);
}
AddPathToArgs::Never => {}
}

if cmd.chdir {
proc.current_dir(&project.path);
}

let mut child = proc.spawn()?;

child.wait()?;
child.wait().await?;

Ok(())
}
Expand Down
Loading

0 comments on commit f2ead38

Please sign in to comment.