use std::fs;
use std::io;
use std::path::PathBuf;
use std::process::Command;
use crate::embedded::{EXTENSOES, Extensao};
use crate::prompt::Estilo;
use crate::vsix::{Manifesto, empacotar};
pub struct Editor {
pub comando: String,
pub nome: &'static str,
}
const CANDIDATOS: &[(&str, &str)] = &[
("code", "VS Code"),
("code-insiders", "VS Code Insiders"),
("cursor", "Cursor"),
("codium", "VSCodium"),
("windsurf", "Windsurf"),
];
pub fn detectar() -> Vec<Editor> {
let Some(path) = std::env::var_os("PATH") else {
return Vec::new();
};
let dirs: Vec<PathBuf> = std::env::split_paths(&path).collect();
let sufixos: &[&str] = if cfg!(windows) {
&[".cmd", ".exe", ".bat", ""]
} else {
&[""]
};
CANDIDATOS
.iter()
.filter(|(cmd, _)| {
dirs.iter().any(|d| {
sufixos
.iter()
.any(|s| d.join(format!("{cmd}{s}")).is_file())
})
})
.map(|(cmd, nome)| Editor {
comando: (*cmd).to_string(),
nome,
})
.collect()
}
pub fn disponiveis() -> bool {
!EXTENSOES.is_empty()
}
pub fn nomes() -> Vec<String> {
EXTENSOES
.iter()
.filter_map(|ext| {
let m = manifesto(ext).ok()?;
Some(format!("{} {}", m.id(), m.versao))
})
.collect()
}
pub fn instalar(editor: &Editor, e: &Estilo) -> io::Result<usize> {
if EXTENSOES.is_empty() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"esta build não tem extensões embutidas",
));
}
let saida_dir = std::env::temp_dir().join("glacier-extensions");
fs::create_dir_all(&saida_dir)?;
let mut instaladas = 0;
for ext in EXTENSOES {
let manifesto = match manifesto(ext) {
Ok(m) => m,
Err(erro) => {
println!(" {} {}: {erro}", e.vermelho("✘"), ext.origem);
continue;
}
};
let vsix = saida_dir.join(format!("{}-{}.vsix", manifesto.nome, manifesto.versao));
fs::write(&vsix, empacotar(&manifesto, ext.arquivos))?;
let resultado = Command::new(&editor.comando)
.arg("--install-extension")
.arg(&vsix)
.arg("--force")
.output();
match resultado {
Ok(saida) if saida.status.success() => {
println!(
" {} {} {}",
e.verde("✔"),
manifesto.id(),
e.fraco(&manifesto.versao)
);
let _ = fs::remove_file(&vsix);
instaladas += 1;
}
Ok(saida) => {
let motivo = String::from_utf8_lossy(&saida.stderr);
println!(
" {} {}: {}",
e.vermelho("✘"),
manifesto.id(),
primeira_linha(motivo.trim())
);
println!(
" {}",
e.fraco(&format!("o .vsix ficou em {}", vsix.display()))
);
}
Err(erro) => {
println!(" {} {}: {erro}", e.vermelho("✘"), manifesto.id());
println!(
" {}",
e.fraco(&format!("o .vsix ficou em {}", vsix.display()))
);
}
}
}
Ok(instaladas)
}
fn manifesto(ext: &Extensao) -> io::Result<Manifesto> {
let package_json = ext
.arquivos
.iter()
.find(|(rel, _)| *rel == "package.json")
.map(|(_, bytes)| *bytes)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!("extensão '{}' sem package.json", ext.origem),
)
})?;
Manifesto::ler(package_json)
}
fn primeira_linha(s: &str) -> &str {
s.lines().find(|l| !l.trim().is_empty()).unwrap_or("falhou")
}