Add ~/.tree-sitter/config.json file, init-config command
Right now this is just used for two things: * Specifying folders for locarting parsers to use with `tree-sitter parse` and `tree-sitter highlight` * Specifying colors to use for `tree-sitter-highlight`
This commit is contained in:
parent
858b4ba8ac
commit
1bad6dc41e
5 changed files with 212 additions and 43 deletions
69
cli/src/config.rs
Normal file
69
cli/src/config.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use super::highlight::Theme;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, fs, io};
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
#[serde(skip)]
|
||||
pub binary_directory: PathBuf,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "parser-directories")]
|
||||
pub parser_directories: Vec<PathBuf>,
|
||||
|
||||
#[serde(default)]
|
||||
pub theme: Theme,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn get_path(home_dir: &Path) -> PathBuf {
|
||||
env::var("TREE_SITTER_DIR")
|
||||
.map(|p| p.into())
|
||||
.unwrap_or_else(|_| home_dir.join(".tree-sitter"))
|
||||
}
|
||||
|
||||
pub fn load(home_dir: &Path) -> Self {
|
||||
let tree_sitter_dir = Self::get_path(home_dir);
|
||||
let config_path = tree_sitter_dir.join("config.json");
|
||||
let mut result = fs::read_to_string(&config_path)
|
||||
.map_err(drop)
|
||||
.and_then(|json| serde_json::from_str(&json).map_err(drop))
|
||||
.unwrap_or_else(|_| Self::default());
|
||||
result.init(home_dir, &tree_sitter_dir);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn save(&self, home_dir: &Path) -> io::Result<()> {
|
||||
let tree_sitter_dir = Self::get_path(home_dir);
|
||||
let config_path = tree_sitter_dir.join("config.json");
|
||||
let json = serde_json::to_string_pretty(self).expect("Failed to serialize config");
|
||||
fs::write(config_path, json)
|
||||
}
|
||||
|
||||
pub fn new(home_dir: &Path) -> Self {
|
||||
let tree_sitter_dir = Self::get_path(home_dir);
|
||||
let mut result = Self::default();
|
||||
result.init(home_dir, &tree_sitter_dir);
|
||||
result
|
||||
}
|
||||
|
||||
fn init(&mut self, home_dir: &Path, tree_sitter_dir: &Path) {
|
||||
if self.parser_directories.is_empty() {
|
||||
self.parser_directories = vec![
|
||||
home_dir.join("github"),
|
||||
home_dir.join("src"),
|
||||
home_dir.join("source"),
|
||||
]
|
||||
}
|
||||
|
||||
let binary_path = tree_sitter_dir.join("bin");
|
||||
self.binary_directory = binary_path;
|
||||
fs::create_dir_all(&self.binary_directory).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"Could not find or create parser binary directory {:?}. Error: {}",
|
||||
self.binary_directory, error
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ use crate::error::Result;
|
|||
use crate::loader::Loader;
|
||||
use ansi_term::{Color, Style};
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::Value;
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::{fmt, fs, io, path};
|
||||
use tree_sitter::{Language, PropertySheet};
|
||||
|
|
@ -21,24 +23,7 @@ pub struct Theme {
|
|||
impl Theme {
|
||||
pub fn load(path: &path::Path) -> io::Result<Self> {
|
||||
let json = fs::read_to_string(path)?;
|
||||
Ok(Self::new(&json))
|
||||
}
|
||||
|
||||
pub fn new(json: &str) -> Self {
|
||||
let mut ansi_styles = vec![None; 30];
|
||||
let mut css_styles = vec![None; 30];
|
||||
if let Ok(colors) = serde_json::from_str::<HashMap<Scope, Value>>(json) {
|
||||
for (scope, style_value) in colors {
|
||||
let mut style = Style::default();
|
||||
parse_style(&mut style, style_value);
|
||||
ansi_styles[scope as usize] = Some(style);
|
||||
css_styles[scope as usize] = Some(style_to_css(style));
|
||||
}
|
||||
}
|
||||
Self {
|
||||
ansi_styles,
|
||||
css_styles,
|
||||
}
|
||||
Ok(serde_json::from_str(&json).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn ansi_style(&self, scope: Scope) -> Option<&Style> {
|
||||
|
|
@ -50,9 +35,85 @@ impl Theme {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Theme {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Theme, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let scope_count = Scope::Unknown as usize + 1;
|
||||
let mut ansi_styles = vec![None; scope_count];
|
||||
let mut css_styles = vec![None; scope_count];
|
||||
if let Ok(colors) = HashMap::<Scope, Value>::deserialize(deserializer) {
|
||||
for (scope, style_value) in colors {
|
||||
let mut style = Style::default();
|
||||
parse_style(&mut style, style_value);
|
||||
ansi_styles[scope as usize] = Some(style);
|
||||
css_styles[scope as usize] = Some(style_to_css(style));
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
ansi_styles,
|
||||
css_styles,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Theme {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let entry_count = self.ansi_styles.iter().filter(|i| i.is_some()).count();
|
||||
let mut map = serializer.serialize_map(Some(entry_count))?;
|
||||
for (i, style) in self.ansi_styles.iter().enumerate() {
|
||||
let scope = Scope::from_usize(i).unwrap();
|
||||
if scope == Scope::Unknown {
|
||||
break;
|
||||
}
|
||||
if let Some(style) = style {
|
||||
let color = style.foreground.map(|color| match color {
|
||||
Color::Black => json!("black"),
|
||||
Color::Blue => json!("blue"),
|
||||
Color::Cyan => json!("cyan"),
|
||||
Color::Green => json!("green"),
|
||||
Color::Purple => json!("purple"),
|
||||
Color::Red => json!("red"),
|
||||
Color::White => json!("white"),
|
||||
Color::Yellow => json!("yellow"),
|
||||
Color::RGB(r, g, b) => json!(format!("#{:x?}{:x?}{:x?}", r, g, b)),
|
||||
Color::Fixed(n) => json!(n),
|
||||
});
|
||||
if style.is_bold || style.is_italic || style.is_underline {
|
||||
let mut entry = HashMap::new();
|
||||
if let Some(color) = color {
|
||||
entry.insert("color", color);
|
||||
}
|
||||
if style.is_bold {
|
||||
entry.insert("bold", Value::Bool(true));
|
||||
}
|
||||
if style.is_italic {
|
||||
entry.insert("italic", Value::Bool(true));
|
||||
}
|
||||
if style.is_underline {
|
||||
entry.insert("underline", Value::Bool(true));
|
||||
}
|
||||
map.serialize_entry(&scope, &entry)?;
|
||||
} else if let Some(color) = color {
|
||||
map.serialize_entry(&scope, &color)?;
|
||||
} else {
|
||||
map.serialize_entry(&scope, &Value::Null)?;
|
||||
}
|
||||
} else {
|
||||
map.serialize_entry(&scope, &Value::Null)?;
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Theme::new(
|
||||
serde_json::from_str(
|
||||
r#"
|
||||
{
|
||||
"attribute": {"color": 124, "italic": true},
|
||||
|
|
@ -71,11 +132,14 @@ impl Default for Theme {
|
|||
"punctuation.delimiter": 239,
|
||||
"string.special": 30,
|
||||
"string": 28,
|
||||
"tag": {"color": 18},
|
||||
"tag": 18,
|
||||
"type": 23,
|
||||
"type.builtin": {"color": 23, "bold": true},
|
||||
"variable.builtin": {"bold": true}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,9 +166,8 @@ fn parse_style(style: &mut Style, json: Value) {
|
|||
if let Value::Object(entries) = json {
|
||||
for (property_name, value) in entries {
|
||||
match property_name.as_str() {
|
||||
"italic" => *style = style.italic(),
|
||||
"bold" => *style = style.bold(),
|
||||
"dimmed" => *style = style.dimmed(),
|
||||
"italic" => *style = style.italic(),
|
||||
"underline" => *style = style.underline(),
|
||||
"color" => {
|
||||
if let Some(color) = parse_color(value) {
|
||||
|
|
@ -126,6 +189,7 @@ fn parse_color(json: Value) -> Option<Color> {
|
|||
_ => None,
|
||||
},
|
||||
Value::String(s) => match s.to_lowercase().as_str() {
|
||||
"black" => Some(Color::Black),
|
||||
"blue" => Some(Color::Blue),
|
||||
"cyan" => Some(Color::Cyan),
|
||||
"green" => Some(Color::Green),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod generate;
|
||||
pub mod highlight;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use std::fs;
|
|||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::usize;
|
||||
use tree_sitter_cli::{error, generate, highlight, loader, logger, parse, properties, test};
|
||||
use tree_sitter_cli::{
|
||||
config, error, generate, highlight, loader, logger, parse, properties, test,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = run() {
|
||||
|
|
@ -24,6 +26,7 @@ fn run() -> error::Result<()> {
|
|||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.author("Max Brunsfeld <maxbrunsfeld@gmail.com>")
|
||||
.about("Generates and tests parsers")
|
||||
.subcommand(SubCommand::with_name("init-config").about("Generate a default config file"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("generate")
|
||||
.about("Generate a parser")
|
||||
|
|
@ -77,19 +80,15 @@ fn run() -> error::Result<()> {
|
|||
)
|
||||
.get_matches();
|
||||
|
||||
let home_dir = dirs::home_dir().unwrap();
|
||||
let home_dir = dirs::home_dir().expect("Failed to read home directory");
|
||||
let current_dir = env::current_dir().unwrap();
|
||||
let config_dir = home_dir.join(".tree-sitter");
|
||||
let theme_path = config_dir.join("theme.json");
|
||||
let parsers_dir = config_dir.join("parsers");
|
||||
let config = config::Config::load(&home_dir);
|
||||
let mut loader = loader::Loader::new(config.binary_directory.clone());
|
||||
|
||||
// TODO - make configurable
|
||||
let parser_repo_paths = vec![home_dir.join("github")];
|
||||
|
||||
fs::create_dir_all(&parsers_dir).unwrap();
|
||||
let mut loader = loader::Loader::new(config_dir);
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("generate") {
|
||||
if matches.subcommand_matches("init-config").is_some() {
|
||||
let config = config::Config::new(&home_dir);
|
||||
config.save(&home_dir)?;
|
||||
} else if let Some(matches) = matches.subcommand_matches("generate") {
|
||||
if matches.is_present("log") {
|
||||
logger::init();
|
||||
}
|
||||
|
|
@ -127,7 +126,7 @@ fn run() -> error::Result<()> {
|
|||
let debug_graph = matches.is_present("debug-graph");
|
||||
let quiet = matches.is_present("quiet");
|
||||
let time = matches.is_present("time");
|
||||
loader.find_all_languages(&parser_repo_paths)?;
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
let paths = matches
|
||||
.values_of("path")
|
||||
.unwrap()
|
||||
|
|
@ -161,10 +160,9 @@ fn run() -> error::Result<()> {
|
|||
return Err(error::Error(String::new()));
|
||||
}
|
||||
} else if let Some(matches) = matches.subcommand_matches("highlight") {
|
||||
loader.find_all_languages(&parser_repo_paths)?;
|
||||
let theme = highlight::Theme::load(&theme_path).unwrap_or_default();
|
||||
let paths = matches.values_of("path").unwrap().into_iter();
|
||||
let html_mode = matches.is_present("html");
|
||||
loader.find_all_languages(&config.parser_directories)?;
|
||||
|
||||
if html_mode {
|
||||
println!("{}", highlight::HTML_HEADER);
|
||||
|
|
@ -182,7 +180,7 @@ fn run() -> error::Result<()> {
|
|||
|
||||
for path in paths {
|
||||
let path = Path::new(path);
|
||||
let (language, config) = match language_config {
|
||||
let (language, language_config) = match language_config {
|
||||
Some(v) => v,
|
||||
None => match loader.language_configuration_for_file_name(path)? {
|
||||
Some(v) => v,
|
||||
|
|
@ -193,12 +191,12 @@ fn run() -> error::Result<()> {
|
|||
},
|
||||
};
|
||||
|
||||
if let Some(sheet) = config.highlight_property_sheet(language)? {
|
||||
if let Some(sheet) = language_config.highlight_property_sheet(language)? {
|
||||
let source = fs::read(path)?;
|
||||
if html_mode {
|
||||
highlight::html(&loader, &theme, &source, language, sheet)?;
|
||||
highlight::html(&loader, &config.theme, &source, language, sheet)?;
|
||||
} else {
|
||||
highlight::ansi(&loader, &theme, &source, language, sheet)?;
|
||||
highlight::ansi(&loader, &config.theme, &source, language, sheet)?;
|
||||
}
|
||||
} else {
|
||||
return Err(error::Error(format!(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
mod escape;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_derive::*;
|
||||
use std::cmp;
|
||||
use std::fmt::Write;
|
||||
|
|
@ -742,6 +742,43 @@ impl<'de> Deserialize<'de> for Scope {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Scope {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Scope::Attribute => serializer.serialize_str("attribute"),
|
||||
Scope::Comment => serializer.serialize_str("comment"),
|
||||
Scope::Constant => serializer.serialize_str("constant"),
|
||||
Scope::ConstantBuiltin => serializer.serialize_str("constant.builtin"),
|
||||
Scope::Constructor => serializer.serialize_str("constructor"),
|
||||
Scope::ConstructorBuiltin => serializer.serialize_str("constructor.builtin"),
|
||||
Scope::Embedded => serializer.serialize_str("embedded"),
|
||||
Scope::Escape => serializer.serialize_str("escape"),
|
||||
Scope::Function => serializer.serialize_str("function"),
|
||||
Scope::FunctionBuiltin => serializer.serialize_str("function.builtin"),
|
||||
Scope::Keyword => serializer.serialize_str("keyword"),
|
||||
Scope::Number => serializer.serialize_str("number"),
|
||||
Scope::Operator => serializer.serialize_str("operator"),
|
||||
Scope::Property => serializer.serialize_str("property"),
|
||||
Scope::PropertyBuiltin => serializer.serialize_str("property.builtin"),
|
||||
Scope::Punctuation => serializer.serialize_str("punctuation"),
|
||||
Scope::PunctuationBracket => serializer.serialize_str("punctuation.bracket"),
|
||||
Scope::PunctuationDelimiter => serializer.serialize_str("punctuation.delimiter"),
|
||||
Scope::PunctuationSpecial => serializer.serialize_str("punctuation.special"),
|
||||
Scope::String => serializer.serialize_str("string"),
|
||||
Scope::StringSpecial => serializer.serialize_str("string.special"),
|
||||
Scope::Type => serializer.serialize_str("type"),
|
||||
Scope::TypeBuiltin => serializer.serialize_str("type.builtin"),
|
||||
Scope::Variable => serializer.serialize_str("variable"),
|
||||
Scope::VariableBuiltin => serializer.serialize_str("variable.builtin"),
|
||||
Scope::Tag => serializer.serialize_str("tag"),
|
||||
Scope::Unknown => serializer.serialize_str(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HTMLAttributeCallback<'a>: Fn(Scope) -> &'a str {}
|
||||
|
||||
pub fn highlight<'a, F>(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue