Phases 0-1 + Sprint A: module split, CLI layer, org-refresh fix, hygiene
- LICENSE (MIT) + README truth-pass; docs/cli.md CLI contract - CLI subcommands (src/cli_cmds/) per "TUI for users, CLI for users+AI" - Hub/detail refactor: keymap dispatch, modal split, action enum, BgMsg bus - Fix: org repos were never refreshed (owner==username match); refresh now parallel — 49 repos in ~11s, surfaced 70 previously invisible PRs - Fix: 3 date bugs in detail/git.rs (format-local, Local::now, 1y month math) - Stash indicator + [m] actions, session persistence, gh CLI token fallback - Legacy UI modules removed; plan/ docs (roadmap, audit, debate, handoff)
This commit is contained in:
1 parent
5787b14880
commit
0468c032c6
100 files changed
+11614
-9840
No files matched your search
@@ -1,3 +1,7 @@
|
||||
//! Clipboard writers: a configured-command variant (`copy_to_clipboard`,
|
||||
//! used when the user has set `clipboard_cmd` in config) and an
|
||||
//! auto-detecting fallback (`copy_auto`) that tries the common Linux tools.
|
||||
|
||||
use std::io::Write as _;
|
||||
|
||||
/// Copy text using a configured command string (e.g. "wl-copy" or "xclip -selection clipboard").
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
//! Thin wrappers around invoking `git` as a subprocess against a given
|
||||
//! repo path. Used instead of a git library for commands where shelling out
|
||||
//! to the user's actual `git` binary (with their real config, credential
|
||||
//! helpers, hooks) is exactly the desired behavior.
|
||||
|
||||
/// Runs `git <args>` against `path`, inheriting stdio so output reaches the
|
||||
/// terminal directly. On failure, prints a short summary instead of
|
||||
/// propagating an error — most call sites treat a failed git command as
|
||||
/// "nothing changed" rather than something to abort the TUI over.
|
||||
pub fn git_run(path: &std::path::Path, args: &[&str]) {
|
||||
match std::process::Command::new("git").arg("-C").arg(path).args(args).status() {
|
||||
Ok(s) if s.success() => {}
|
||||
@@ -6,6 +15,9 @@ pub fn git_run(path: &std::path::Path, args: &[&str]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `git_run`, but silent and returns success/failure instead of
|
||||
/// printing — used where the caller wants to branch on the result rather
|
||||
/// than just report it.
|
||||
pub fn git_run_ok(path: &std::path::Path, args: &[&str]) -> bool {
|
||||
std::process::Command::new("git").arg("-C").arg(path).args(args)
|
||||
.status().map(|s| s.success()).unwrap_or(false)
|
||||
|
||||
@@ -1,7 +1,39 @@
|
||||
//! Small, dependency-free helpers shared across the rest of the crate:
|
||||
//! recursive file walking, opening paths in the system's default app, and
|
||||
//! detecting which editor/clipboard tools are actually installed. Clipboard
|
||||
//! and URL-rewriting logic each get their own submodule since both have
|
||||
//! enough provider/platform-specific branching to be worth isolating.
|
||||
|
||||
pub mod clipboard;
|
||||
pub mod git;
|
||||
pub mod url;
|
||||
|
||||
/// Directories never walked when scanning a repo's source files — vendored
|
||||
/// or generated content that must never be touched by rename/find-usages
|
||||
/// (the whole reason codeintel uses tree-sitter over naive find/replace is
|
||||
/// to stay correct, but staying out of dependency code entirely is the
|
||||
/// first line of defense). Mirrors the exclusion list `legacy::refactor`
|
||||
/// already used for the TODO scanner.
|
||||
pub const EXCLUDED_DIRS: &[&str] = &[
|
||||
".git", "target", "node_modules", ".next", "dist", "build", ".venv", "vendor",
|
||||
];
|
||||
|
||||
/// Recursively visits every file under `root`, skipping `EXCLUDED_DIRS`.
|
||||
pub fn walk_files<F: FnMut(&std::path::Path)>(root: &std::path::Path, cb: &mut F) {
|
||||
let Ok(entries) = std::fs::read_dir(root) else { return };
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
let name = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
if !EXCLUDED_DIRS.contains(&name.as_ref()) {
|
||||
walk_files(&path, cb);
|
||||
}
|
||||
} else {
|
||||
cb(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Open a file, directory, or URL with the system default application.
|
||||
pub fn open_path(target: &str) {
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -11,3 +43,36 @@ pub fn open_path(target: &str) {
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||
let _ = std::process::Command::new("xdg-open").arg(target).spawn();
|
||||
}
|
||||
|
||||
/// Whether `cmd` resolves to something on PATH, used to filter the
|
||||
/// editor/clipboard-tool detection lists down to what's actually installed.
|
||||
fn command_exists(cmd: &str) -> bool {
|
||||
#[cfg(target_os = "windows")]
|
||||
let finder = "where";
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let finder = "which";
|
||||
std::process::Command::new(finder).arg(cmd)
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Editors actually present on this machine's PATH, in a sensible default order.
|
||||
pub fn detect_editors() -> Vec<String> {
|
||||
["code", "nvim", "vim", "nano", "hx", "micro", "subl", "zed"]
|
||||
.into_iter()
|
||||
.filter(|c| command_exists(c))
|
||||
.map(str::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Clipboard tools actually present on this machine's PATH.
|
||||
pub fn detect_clipboard_tools() -> Vec<String> {
|
||||
let mut found = vec![];
|
||||
for cmd in &["wl-copy", "xclip", "pbcopy"] {
|
||||
if command_exists(cmd) {
|
||||
found.push(if *cmd == "xclip" { "xclip -selection clipboard".to_string() } else { cmd.to_string() });
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
+8
-1
@@ -1,7 +1,14 @@
|
||||
//! Converts git remote URLs (SSH or HTTPS) into the browser-facing web URL
|
||||
//! for the same repo, used by every "open in browser" action.
|
||||
|
||||
/// Rewrites `git_url` (`git@host:owner/repo.git` or an HTTPS clone URL) into
|
||||
/// `https://host/owner/repo`. Already-HTTPS URLs are returned with just the
|
||||
/// `.git` suffix trimmed; anything that doesn't look like either form is
|
||||
/// returned unchanged rather than guessed at.
|
||||
pub fn to_web_url(git_url: &str) -> String {
|
||||
if git_url.starts_with("http") { return git_url.trim_end_matches(".git").to_string(); }
|
||||
if let Some(colon) = git_url.rfind(':') {
|
||||
let host = git_url[..colon].split('@').last().unwrap_or("");
|
||||
let host = git_url[..colon].split('@').next_back().unwrap_or("");
|
||||
let path = git_url[colon + 1..].trim_end_matches(".git");
|
||||
return format!("https://{}/{}", host, path);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user