2
0

add plugin manager to ais (add/ls/rm)

This commit is contained in:
ai
2026-04-03 12:21:18 +00:00
parent 8da8c5b6d7
commit 554f015c9e
2 changed files with 141 additions and 3 deletions

7
.vimrc
View File

@@ -33,6 +33,13 @@ inoremap <C-j> <Esc>
nnoremap Q :q!<CR>
vnoremap v V
" load plugins from ~/.vim/plugged/
for s:dir in glob('~/.vim/plugged/*', 0, 1)
if isdirectory(s:dir)
exe 'set rtp+=' . s:dir
endif
endfor
syntax on
filetype plugin indent on
au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$") | exe "normal! g`\"" | endif

View File

@@ -1,6 +1,6 @@
// ais - ai selector
// unified fuzzy finder: directories, files, history
// usage: ais [cd|file|hist] or pipe: echo "a\nb" | ais
// unified fuzzy finder + plugin manager
// usage: ais [cd|file|hist|plugin] or pipe: echo "a\nb" | ais
use std::collections::HashSet;
use std::env;
@@ -11,6 +11,16 @@ fn main() {
let args: Vec<String> = env::args().collect();
let mode = args.get(1).map(|s| s.as_str()).unwrap_or("cd");
match mode {
"plugin" | "p" => {
let sub = args.get(2).map(|s| s.as_str()).unwrap_or("ls");
let rest: Vec<&str> = args.iter().skip(3).map(|s| s.as_str()).collect();
plugin_cmd(sub, &rest);
return;
}
_ => {}
}
let items = if !is_tty_stdin() {
read_stdin_lines()
} else {
@@ -19,7 +29,7 @@ fn main() {
"file" | "f" => get_files(),
"hist" | "h" => get_history(),
_ => {
eprintln!("usage: ais [cd|file|hist]");
eprintln!("usage: ais [cd|file|hist|plugin]");
return;
}
}
@@ -34,6 +44,125 @@ fn main() {
}
}
// --- plugin manager ---
fn plugin_dir() -> String {
let home = env::var("HOME").unwrap_or_default();
format!("{}/.vim/plugged", home)
}
fn plugin_cmd(sub: &str, args: &[&str]) {
match sub {
"add" | "a" => {
if args.is_empty() {
eprintln!("usage: ais plugin add <user/repo>");
return;
}
plugin_add(args[0]);
}
"rm" | "r" => plugin_rm(args.first().copied()),
"ls" | "l" | "" => plugin_ls(),
_ => eprintln!("usage: ais plugin [add|ls|rm]"),
}
}
fn plugin_add(name: &str) {
let dir = plugin_dir();
std::fs::create_dir_all(&dir).ok();
// accept "user/repo" or full github url
let (url, repo_name) = if name.contains("://") {
let repo = name.rsplit('/').next().unwrap_or(name);
let repo = repo.strip_suffix(".git").unwrap_or(repo);
(name.to_string(), repo.to_string())
} else {
let repo = name.split('/').last().unwrap_or(name);
(format!("https://github.com/{}.git", name), repo.to_string())
};
let dest = format!("{}/{}", dir, repo_name);
if std::path::Path::new(&dest).exists() {
eprintln!("{} already installed", repo_name);
return;
}
eprintln!("installing {} ...", repo_name);
let status = Command::new("git")
.args(["clone", "--depth", "1", &url, &dest])
.status();
match status {
Ok(s) if s.success() => eprintln!("ok: {}", repo_name),
_ => eprintln!("failed: {}", url),
}
}
fn plugin_ls() {
let dir = plugin_dir();
let entries = match std::fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => {
eprintln!("no plugins");
return;
}
};
for entry in entries.flatten() {
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
let name = entry.file_name().to_string_lossy().to_string();
if !name.starts_with('.') {
println!("{}", name);
}
}
}
}
fn plugin_rm(name: Option<&str>) {
let dir = plugin_dir();
let target = if let Some(n) = name {
n.to_string()
} else {
// fuzzy select from installed plugins
let entries = match std::fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => {
eprintln!("no plugins");
return;
}
};
let plugins: Vec<String> = entries
.flatten()
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
.map(|e| e.file_name().to_string_lossy().to_string())
.filter(|n| !n.starts_with('.'))
.collect();
if plugins.is_empty() {
eprintln!("no plugins");
return;
}
match fuzzy_select(&plugins, "rm plugin") {
Some(s) => s,
None => return,
}
};
let path = format!("{}/{}", dir, target);
if !std::path::Path::new(&path).exists() {
eprintln!("{} not found", target);
return;
}
match std::fs::remove_dir_all(&path) {
Ok(_) => eprintln!("removed: {}", target),
Err(e) => eprintln!("failed: {}", e),
}
}
// --- selectors ---
fn is_tty_stdin() -> bool {
extern "C" {
fn isatty(fd: i32) -> i32;
@@ -123,6 +252,8 @@ fn read_stdin_lines() -> Vec<String> {
.collect()
}
// --- fuzzy select TUI ---
fn fuzzy_match(item: &str, query: &str) -> bool {
if query.is_empty() {
return true;