use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
use anyhow::Result;
use fs_extra::dir::{self, CopyOptions};
use crate::{codegen, Config};
use crate::Language;
pub fn check(config: &Config) -> Result<()> {
if std::env::var("INKJET_REDOWNLOAD_LANGS").is_ok() {
download_langs(config)?;
}
if std::env::var("INKJET_REBUILD_LANGS_MODULE").is_ok() {
generate_langs_module(&config.languages)?;
}
if std::env::var("INKJET_REBUILD_FEATURES").is_ok() {
generate_features_list(&config.languages)?;
}
if std::env::var("INKJET_REBUILD_THEMES").is_ok() {
generate_themes_module()?;
}
Ok(())
}
pub fn download_langs(config: &Config) -> Result<()> {
fs::remove_dir_all("languages")?;
fs::create_dir_all("languages/temp/helix_queries")?;
let languages = &config.languages;
Command::new("git")
.arg("clone")
.arg("https://github.com/helix-editor/helix")
.arg("languages/temp/helix_all")
.spawn()?
.wait()?;
Command::new("git")
.args(["reset", "--hard", &config.helix_sum])
.current_dir(
std::fs::canonicalize("./languages/temp/helix_all")?
)
.spawn()?
.wait()?;
dir::copy(
"languages/temp/helix_all/runtime/queries/",
"languages/temp/helix_queries",
&CopyOptions::new().content_only(true)
)?;
let downloads: Vec<_> = languages
.iter()
.map(Language::download)
.map(Result::unwrap)
.zip(languages)
.collect();
for (mut child, lang) in downloads {
child.wait()?;
println!("Finished downloading {}.", lang.name);
if let Some(hash) = &lang.hash {
let repo_dir = format!("languages/temp/{}", lang.name);
println!("Resetting {} onto {}...", lang.name, hash);
Command::new("git")
.current_dir(repo_dir)
.args(["reset", "--hard", hash])
.spawn()?
.wait()?;
}
if let Some(command) = &lang.command {
println!("Executing prep script for {}...", lang.name);
Command::new("sh")
.arg("-c")
.arg(command)
.spawn()?
.wait()?;
}
fs::create_dir_all(format!("languages/{}/queries", lang.name))?;
dir::copy(
format!("languages/temp/{}/src", lang.name),
format!("languages/{}", lang.name),
&CopyOptions::new(),
)?;
let query_path = match &lang.helix_path {
Some(path) => format!("languages/temp/helix_queries/{path}"),
None => format!("languages/temp/helix_queries/{}", lang.name)
};
let query_path = match lang.helix_override {
false => query_path,
true => format!("languages/temp/{}/queries", lang.name)
};
dir::copy(
query_path,
format!("languages/{}/queries", lang.name),
&CopyOptions::new().content_only(true),
)?;
let _ = fs::remove_file(format!("languages/{}/queries/textobjects.scm", lang.name));
let _ = fs::remove_file(format!("languages/{}/queries/indents.scm", lang.name));
let _ = fs::remove_file(format!("languages/{}/queries/folds.scm", lang.name));
let _ = fs::remove_file(format!("languages/{}/src/grammar.json", lang.name));
let _ = fs::remove_file(format!("languages/{}/src/node-types.json", lang.name));
println!("Finished extracting {}.", lang.name);
}
fs::remove_dir_all("languages/temp")?;
Ok(())
}
pub fn generate_langs_module(languages: &[Language]) -> Result<()> {
let module_start = quote::quote! {
#![allow(dead_code)]
#![allow(clippy::items_after_test_module)]
use tree_sitter_highlight::HighlightConfiguration;
};
let modules = languages
.iter()
.map(codegen::language_module_def);
let language_enum_def = codegen::languages_enum_def(languages);
let language_impl_def = codegen::languages_impl_def(languages);
let combined = quote::quote!{
#module_start
#(#modules)*
#language_enum_def
#language_impl_def
};
let combined = format!("{combined}");
let combined = syn::parse_file(&combined).unwrap();
let combined = prettyplease::unparse(&combined);
let mut file = File::create("src/languages.rs")?;
write!(&mut file, "{}", combined)?;
Ok(())
}
pub fn generate_features_list(languages: &[Language]) -> Result<()> {
let mut all_languages_buffer = String::new();
let mut features_buffer = String::new();
for lang in languages {
all_languages_buffer += &format!("\"language-{}\",\n", lang.name.replace('_', "-"));
features_buffer += &format!("language-{} = []\n", lang.name.replace('_', "-"));
}
let mut file = fs::File::create("features")?;
write!(
file,
"
all_languages = [
{all_languages_buffer}
]
{features_buffer}
"
)?;
Ok(())
}
pub fn generate_themes_module() -> Result<()> {
use std::ffi::OsStr;
use std::path::Path;
use proc_macro2::TokenStream;
let mut themes = vec![];
for entry in std::fs::read_dir("src/theme/vendored/data")? {
let entry = entry?;
let path = entry.path();
if path.extension() != Some( OsStr::new("toml") ) {
continue;
}
let stem = path
.file_stem()
.expect("File should have a stem")
.to_string_lossy()
.to_string();
themes.push(stem)
}
let mut file = File::create("src/theme/vendored/mod.rs")?;
let include_path = |query| -> TokenStream {
let path = format!("./data/{query}.toml");
let query = format!("include_str!(\"{}\")", &path);
query.parse().unwrap()
};
let module_start = quote::quote! {
#![allow(dead_code)]
};
let consts = themes
.iter()
.map(|t| {
let name = quote::format_ident!(
"{}",
t.replace('-', "_").to_uppercase()
);
let path = include_path(t);
quote::quote! {
pub const #name: &str = #path;
}
});
let tests = themes
.iter()
.map(|t| {
let name = quote::format_ident!(
"{}",
t.replace('-', "_").to_uppercase()
);
let path = include_path(t);
quote::quote! {
#[test]
fn #name() {
let data = #path;
Theme::from_helix(data).unwrap();
}
}
});
let combined = quote::quote!{
#module_start
#(#consts)*
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use crate::theme::*;
#(#tests)*
}
};
let combined = format!("{combined}");
let combined = syn::parse_file(&combined).unwrap();
let combined = prettyplease::unparse(&combined);
write!(&mut file, "{}", combined)?;
Ok(())
}