use std::path::PathBuf;
use anyhow::{Result, bail};
use dirs::{config_dir, data_local_dir};
use harper_core::{Dialect, linting::LintGroupConfig, parsers::MarkdownOptions};
use resolve_path::PathResolveExt;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[serde(rename_all = "camelCase")]
pub enum DiagnosticSeverity {
Error,
Warning,
Information,
Hint,
}
impl DiagnosticSeverity {
pub fn to_lsp(self) -> tower_lsp::lsp_types::DiagnosticSeverity {
match self {
DiagnosticSeverity::Error => tower_lsp::lsp_types::DiagnosticSeverity::ERROR,
DiagnosticSeverity::Warning => tower_lsp::lsp_types::DiagnosticSeverity::WARNING,
DiagnosticSeverity::Information => {
tower_lsp::lsp_types::DiagnosticSeverity::INFORMATION
}
DiagnosticSeverity::Hint => tower_lsp::lsp_types::DiagnosticSeverity::HINT,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CodeActionConfig {
pub force_stable: bool,
}
impl CodeActionConfig {
pub fn from_lsp_config(value: Value) -> Result<Self> {
let mut base = CodeActionConfig::default();
let Value::Object(value) = value else {
bail!("The code action configuration must be an object.");
};
if let Some(force_stable_val) = value.get("ForceStable") {
let Value::Bool(force_stable) = force_stable_val else {
bail!("ForceStable must be a boolean value.");
};
base.force_stable = *force_stable;
};
Ok(base)
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub user_dict_path: PathBuf,
pub file_dict_path: PathBuf,
pub stats_path: PathBuf,
pub lint_config: LintGroupConfig,
pub diagnostic_severity: DiagnosticSeverity,
pub code_action_config: CodeActionConfig,
pub isolate_english: bool,
pub markdown_options: MarkdownOptions,
pub dialect: Dialect,
}
impl Config {
pub fn from_lsp_config(value: Value) -> Result<Self> {
let mut base = Config::default();
let Value::Object(value) = value else {
bail!("Settings must be an object.");
};
let Some(Value::Object(value)) = value.get("harper-ls") else {
bail!("Settings must contain a \"harper-ls\" key.");
};
if let Some(v) = value.get("userDictPath") {
if !v.is_string() {
bail!("userDict path must be a string.");
}
let path = v.as_str().unwrap();
if !path.is_empty() {
base.user_dict_path = path.try_resolve()?.to_path_buf();
}
}
if let Some(v) = value.get("fileDictPath") {
if !v.is_string() {
bail!("fileDict path must be a string.");
}
let path = v.as_str().unwrap();
if !path.is_empty() {
base.file_dict_path = path.try_resolve()?.to_path_buf();
}
}
if let Some(v) = value.get("statsPath") {
if let Value::String(path) = v {
base.file_dict_path = path.try_resolve()?.to_path_buf();
} else {
bail!("fileDict path must be a string.");
}
}
if let Some(v) = value.get("linters") {
base.lint_config = serde_json::from_value(v.clone())?;
}
if let Some(v) = value.get("diagnosticSeverity") {
base.diagnostic_severity = serde_json::from_value(v.clone())?;
}
if let Some(v) = value.get("dialect") {
base.dialect = serde_json::from_value(v.clone())?;
}
if let Some(v) = value.get("codeActions") {
base.code_action_config = CodeActionConfig::from_lsp_config(v.clone())?;
}
if let Some(v) = value.get("isolateEnglish") {
if let Value::Bool(v) = v {
base.isolate_english = *v;
} else {
bail!("isolateEnglish path must be a boolean.");
}
}
if let Some(v) = value.get("markdown") {
if let Some(v) = v.get("IgnoreLinkTitle") {
base.markdown_options.ignore_link_title = serde_json::from_value(v.clone())?;
}
}
Ok(base)
}
}
impl Default for Config {
fn default() -> Self {
Self {
user_dict_path: config_dir().unwrap().join("harper-ls/dictionary.txt"),
file_dict_path: data_local_dir()
.unwrap()
.join("harper-ls/file_dictionaries/"),
stats_path: data_local_dir().unwrap().join("harper-ls/stats.txt"),
lint_config: LintGroupConfig::default(),
diagnostic_severity: DiagnosticSeverity::Hint,
code_action_config: CodeActionConfig::default(),
isolate_english: false,
markdown_options: MarkdownOptions::default(),
dialect: Dialect::American,
}
}
}