mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-25 02:46:17 +04:00
replace serde structs with helix-config toml adapter
This commit is contained in:
parent
dcbe8496b9
commit
87266b0a07
@ -14,6 +14,7 @@
|
|||||||
use convert::ty_into_value;
|
use convert::ty_into_value;
|
||||||
pub use convert::IntoTy;
|
pub use convert::IntoTy;
|
||||||
pub use definition::{init_config, init_language_server_config};
|
pub use definition::{init_config, init_language_server_config};
|
||||||
|
pub use toml::read_toml_config;
|
||||||
use validator::StaticValidator;
|
use validator::StaticValidator;
|
||||||
pub use validator::{regex_str_validator, ty_validator, IntegerRangeValidator, Ty, Validator};
|
pub use validator::{regex_str_validator, ty_validator, IntegerRangeValidator, Ty, Validator};
|
||||||
pub use value::{from_value, to_value, Value};
|
pub use value::{from_value, to_value, Value};
|
||||||
@ -23,6 +24,7 @@
|
|||||||
mod definition;
|
mod definition;
|
||||||
pub mod env;
|
pub mod env;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
mod toml;
|
||||||
mod validator;
|
mod validator;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
69
helix-config/src/toml.rs
Normal file
69
helix-config/src/toml.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use crate::{Map, OptionManager, OptionRegistry, Value};
|
||||||
|
|
||||||
|
/// Inserts the config declaration from a map deserialized from toml into
|
||||||
|
/// options manager. Returns an error if any of theu config options are
|
||||||
|
/// invalid The convresion may not be exactly one-to-one to retain backwards
|
||||||
|
/// compatibility
|
||||||
|
pub fn read_toml_config(
|
||||||
|
config_entries: Map<Value>,
|
||||||
|
options: &OptionManager,
|
||||||
|
registry: &OptionRegistry,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut buf = String::new();
|
||||||
|
for (key, val) in config_entries {
|
||||||
|
if matches!(val, Value::Map(_)) {
|
||||||
|
buf.push_str(&key);
|
||||||
|
visit(&mut buf, val, options, registry)?;
|
||||||
|
buf.clear();
|
||||||
|
} else {
|
||||||
|
visit(&mut key.to_string(), val, options, registry)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit(
|
||||||
|
path: &mut String,
|
||||||
|
val: Value,
|
||||||
|
options: &OptionManager,
|
||||||
|
registry: &OptionRegistry,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
match &**path {
|
||||||
|
// don't descend
|
||||||
|
"auto-format" => {
|
||||||
|
// treat as unset
|
||||||
|
if Value::Bool(true) == val {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"auto-pairs" => return options.set("auto-pairs", val, registry),
|
||||||
|
"enviorment" => return options.set("enviorment", val, registry),
|
||||||
|
"config" => return options.set("config", val, registry),
|
||||||
|
"gutters" if matches!(val, Value::List(_)) => {
|
||||||
|
return options.set("gutters.layout", val, registry);
|
||||||
|
}
|
||||||
|
"gutters" if matches!(val, Value::List(_)) => {
|
||||||
|
return options.set("gutters.layout", val, registry);
|
||||||
|
}
|
||||||
|
"whitespace.render" if matches!(val, Value::String(_)) => {
|
||||||
|
return options.set("whitespace.render.default", val, registry);
|
||||||
|
}
|
||||||
|
"language-servers" => {
|
||||||
|
// merge list/map of language servers but if "only" and "except" are specified overwrite
|
||||||
|
return options.append("language-servers", val, registry, 0);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
if let Value::Map(val) = val {
|
||||||
|
let old_path_len = path.len();
|
||||||
|
for (key, val) in val.into_iter() {
|
||||||
|
path.push('.');
|
||||||
|
path.push_str(&key);
|
||||||
|
visit(path, val, options, registry)?;
|
||||||
|
path.truncate(old_path_len);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
options.set(&**path, val, registry)
|
||||||
|
}
|
||||||
|
}
|
@ -84,421 +84,8 @@ pub struct Configuration {
|
|||||||
|
|
||||||
impl Default for Configuration {
|
impl Default for Configuration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
crate::config::default_syntax_loader()
|
todo!()
|
||||||
}
|
// crate::config::default_syntax_loader()
|
||||||
}
|
|
||||||
|
|
||||||
// largely based on tree-sitter/cli/src/loader.rs
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
|
||||||
pub struct LanguageConfiguration {
|
|
||||||
#[serde(rename = "name")]
|
|
||||||
pub language_id: String, // c-sharp, rust, tsx
|
|
||||||
#[serde(rename = "language-id")]
|
|
||||||
// see the table under https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
|
|
||||||
pub language_server_language_id: Option<String>, // csharp, rust, typescriptreact, for the language-server
|
|
||||||
pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
|
|
||||||
#[serde(default)]
|
|
||||||
pub shebangs: Vec<String>, // interpreter(s) associated with language
|
|
||||||
#[serde(default)]
|
|
||||||
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
|
|
||||||
pub comment_token: Option<String>,
|
|
||||||
pub text_width: Option<usize>,
|
|
||||||
pub soft_wrap: Option<SoftWrap>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub auto_format: bool,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub formatter: Option<FormatterConfiguration>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub diagnostic_severity: Severity,
|
|
||||||
|
|
||||||
pub grammar: Option<String>, // tree-sitter grammar name, defaults to language_id
|
|
||||||
|
|
||||||
// content_regex
|
|
||||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]
|
|
||||||
pub injection_regex: Option<Regex>,
|
|
||||||
// first_line_regex
|
|
||||||
//
|
|
||||||
#[serde(skip)]
|
|
||||||
pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
|
|
||||||
// tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
|
|
||||||
#[serde(
|
|
||||||
default,
|
|
||||||
skip_serializing_if = "Vec::is_empty",
|
|
||||||
serialize_with = "serialize_lang_features",
|
|
||||||
deserialize_with = "deserialize_lang_features"
|
|
||||||
)]
|
|
||||||
pub language_servers: Vec<LanguageServerFeatures>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub indent: Option<IndentationConfiguration>,
|
|
||||||
|
|
||||||
#[serde(skip)]
|
|
||||||
pub(crate) indent_query: OnceCell<Option<Query>>,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub(crate) textobject_query: OnceCell<Option<TextObjectQuery>>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub debugger: Option<DebugAdapterConfig>,
|
|
||||||
|
|
||||||
/// Automatic insertion of pairs to parentheses, brackets,
|
|
||||||
/// etc. Defaults to true. Optionally, this can be a list of 2-tuples
|
|
||||||
/// to specify a list of characters to pair. This overrides the
|
|
||||||
/// global setting.
|
|
||||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_auto_pairs")]
|
|
||||||
pub auto_pairs: Option<AutoPairs>,
|
|
||||||
|
|
||||||
pub rulers: Option<Vec<u16>>, // if set, override editor's rulers
|
|
||||||
|
|
||||||
/// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
|
|
||||||
/// Falling back to the current working directory if none are configured.
|
|
||||||
pub workspace_lsp_roots: Option<Vec<PathBuf>>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub persistent_diagnostic_sources: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum FileType {
|
|
||||||
/// The extension of the file, either the `Path::extension` or the full
|
|
||||||
/// filename if the file does not have an extension.
|
|
||||||
Extension(String),
|
|
||||||
/// The suffix of a file. This is compared to a given file's absolute
|
|
||||||
/// path, so it can be used to detect files based on their directories.
|
|
||||||
Suffix(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for FileType {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
use serde::ser::SerializeMap;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
FileType::Extension(extension) => serializer.serialize_str(extension),
|
|
||||||
FileType::Suffix(suffix) => {
|
|
||||||
let mut map = serializer.serialize_map(Some(1))?;
|
|
||||||
map.serialize_entry("suffix", &suffix.replace(std::path::MAIN_SEPARATOR, "/"))?;
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for FileType {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::de::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct FileTypeVisitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for FileTypeVisitor {
|
|
||||||
type Value = FileType;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("string or table")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(FileType::Extension(value.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
|
||||||
where
|
|
||||||
M: serde::de::MapAccess<'de>,
|
|
||||||
{
|
|
||||||
match map.next_entry::<String, String>()? {
|
|
||||||
Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
|
|
||||||
suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
|
|
||||||
})),
|
|
||||||
Some((key, _value)) => Err(serde::de::Error::custom(format!(
|
|
||||||
"unknown key in `file-types` list: {}",
|
|
||||||
key
|
|
||||||
))),
|
|
||||||
None => Err(serde::de::Error::custom(
|
|
||||||
"expected a `suffix` key in the `file-types` entry",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(FileTypeVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum LanguageServerFeature {
|
|
||||||
Format,
|
|
||||||
GotoDeclaration,
|
|
||||||
GotoDefinition,
|
|
||||||
GotoTypeDefinition,
|
|
||||||
GotoReference,
|
|
||||||
GotoImplementation,
|
|
||||||
// Goto, use bitflags, combining previous Goto members?
|
|
||||||
SignatureHelp,
|
|
||||||
Hover,
|
|
||||||
DocumentHighlight,
|
|
||||||
Completion,
|
|
||||||
CodeAction,
|
|
||||||
WorkspaceCommand,
|
|
||||||
DocumentSymbols,
|
|
||||||
WorkspaceSymbols,
|
|
||||||
// Symbols, use bitflags, see above?
|
|
||||||
Diagnostics,
|
|
||||||
RenameSymbol,
|
|
||||||
InlayHints,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for LanguageServerFeature {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
use LanguageServerFeature::*;
|
|
||||||
let feature = match self {
|
|
||||||
Format => "format",
|
|
||||||
GotoDeclaration => "goto-declaration",
|
|
||||||
GotoDefinition => "goto-definition",
|
|
||||||
GotoTypeDefinition => "goto-type-definition",
|
|
||||||
GotoReference => "goto-type-definition",
|
|
||||||
GotoImplementation => "goto-implementation",
|
|
||||||
SignatureHelp => "signature-help",
|
|
||||||
Hover => "hover",
|
|
||||||
DocumentHighlight => "document-highlight",
|
|
||||||
Completion => "completion",
|
|
||||||
CodeAction => "code-action",
|
|
||||||
WorkspaceCommand => "workspace-command",
|
|
||||||
DocumentSymbols => "document-symbols",
|
|
||||||
WorkspaceSymbols => "workspace-symbols",
|
|
||||||
Diagnostics => "diagnostics",
|
|
||||||
RenameSymbol => "rename-symbol",
|
|
||||||
InlayHints => "inlay-hints",
|
|
||||||
};
|
|
||||||
write!(f, "{feature}",)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
|
|
||||||
enum LanguageServerFeatureConfiguration {
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
Features {
|
|
||||||
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
|
|
||||||
only_features: HashSet<LanguageServerFeature>,
|
|
||||||
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
|
|
||||||
except_features: HashSet<LanguageServerFeature>,
|
|
||||||
name: String,
|
|
||||||
},
|
|
||||||
Simple(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct LanguageServerFeatures {
|
|
||||||
pub name: String,
|
|
||||||
pub only: HashSet<LanguageServerFeature>,
|
|
||||||
pub excluded: HashSet<LanguageServerFeature>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LanguageServerFeatures {
|
|
||||||
pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
|
|
||||||
(self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_lang_features<'de, D>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<Vec<LanguageServerFeatures>, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?;
|
|
||||||
let res = raw
|
|
||||||
.into_iter()
|
|
||||||
.map(|config| match config {
|
|
||||||
LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures {
|
|
||||||
name,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
LanguageServerFeatureConfiguration::Features {
|
|
||||||
only_features,
|
|
||||||
except_features,
|
|
||||||
name,
|
|
||||||
} => LanguageServerFeatures {
|
|
||||||
name,
|
|
||||||
only: only_features,
|
|
||||||
excluded: except_features,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
fn serialize_lang_features<S>(
|
|
||||||
map: &Vec<LanguageServerFeatures>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let mut serializer = serializer.serialize_seq(Some(map.len()))?;
|
|
||||||
for features in map {
|
|
||||||
let features = if features.only.is_empty() && features.excluded.is_empty() {
|
|
||||||
LanguageServerFeatureConfiguration::Simple(features.name.to_owned())
|
|
||||||
} else {
|
|
||||||
LanguageServerFeatureConfiguration::Features {
|
|
||||||
only_features: features.only.clone(),
|
|
||||||
except_features: features.excluded.clone(),
|
|
||||||
name: features.name.to_owned(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
serializer.serialize_element(&features)?;
|
|
||||||
}
|
|
||||||
serializer.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct LanguageServerConfiguration {
|
|
||||||
pub command: String,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
||||||
pub args: Vec<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
||||||
pub environment: HashMap<String, String>,
|
|
||||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
|
|
||||||
pub config: Option<serde_json::Value>,
|
|
||||||
#[serde(default = "default_timeout")]
|
|
||||||
pub timeout: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct FormatterConfiguration {
|
|
||||||
pub command: String,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
||||||
pub args: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct AdvancedCompletion {
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub completion: Option<String>,
|
|
||||||
pub default: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", untagged)]
|
|
||||||
pub enum DebugConfigCompletion {
|
|
||||||
Named(String),
|
|
||||||
Advanced(AdvancedCompletion),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum DebugArgumentValue {
|
|
||||||
String(String),
|
|
||||||
Array(Vec<String>),
|
|
||||||
Boolean(bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct DebugTemplate {
|
|
||||||
pub name: String,
|
|
||||||
pub request: String,
|
|
||||||
pub completion: Vec<DebugConfigCompletion>,
|
|
||||||
pub args: HashMap<String, DebugArgumentValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct DebugAdapterConfig {
|
|
||||||
pub name: String,
|
|
||||||
pub transport: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub command: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub args: Vec<String>,
|
|
||||||
pub port_arg: Option<String>,
|
|
||||||
pub templates: Vec<DebugTemplate>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub quirks: DebuggerQuirks,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Different workarounds for adapters' differences
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DebuggerQuirks {
|
|
||||||
#[serde(default)]
|
|
||||||
pub absolute_paths: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct IndentationConfiguration {
|
|
||||||
#[serde(deserialize_with = "deserialize_tab_width")]
|
|
||||||
pub tab_width: usize,
|
|
||||||
pub unit: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How the indentation for a newly inserted line should be determined.
|
|
||||||
/// If the selected heuristic is not available (e.g. because the current
|
|
||||||
/// language has no tree-sitter indent queries), a simpler one will be used.
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum IndentationHeuristic {
|
|
||||||
/// Just copy the indentation of the line that the cursor is currently on.
|
|
||||||
Simple,
|
|
||||||
/// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
|
|
||||||
TreeSitter,
|
|
||||||
/// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
|
|
||||||
/// and the line before. Add this to the actual indentation level of the line before.
|
|
||||||
#[default]
|
|
||||||
Hybrid,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration for auto pairs
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
|
|
||||||
pub enum AutoPairConfig {
|
|
||||||
/// Enables or disables auto pairing. False means disabled. True means to use the default pairs.
|
|
||||||
Enable(bool),
|
|
||||||
|
|
||||||
/// The mappings of pairs.
|
|
||||||
Pairs(HashMap<char, char>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AutoPairConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
AutoPairConfig::Enable(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&AutoPairConfig> for Option<AutoPairs> {
|
|
||||||
fn from(auto_pair_config: &AutoPairConfig) -> Self {
|
|
||||||
match auto_pair_config {
|
|
||||||
AutoPairConfig::Enable(false) => None,
|
|
||||||
AutoPairConfig::Enable(true) => Some(AutoPairs::default()),
|
|
||||||
AutoPairConfig::Pairs(pairs) => Some(AutoPairs::new(pairs.iter())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AutoPairConfig> for Option<AutoPairs> {
|
|
||||||
fn from(auto_pairs_config: AutoPairConfig) -> Self {
|
|
||||||
(&auto_pairs_config).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for AutoPairConfig {
|
|
||||||
type Err = std::str::ParseBoolError;
|
|
||||||
|
|
||||||
// only do bool parsing for runtime setting
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let enable: bool = s.parse()?;
|
|
||||||
Ok(AutoPairConfig::Enable(enable))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,6 +304,7 @@ fn load_query(&self, kind: &str) -> Option<Query> {
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
pub struct SoftWrap {
|
pub struct SoftWrap {
|
||||||
|
@ -41,833 +41,13 @@
|
|||||||
|
|
||||||
pub use helix_core::diagnostic::Severity;
|
pub use helix_core::diagnostic::Severity;
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
auto_pairs::AutoPairs,
|
syntax::{self, LanguageServerFeature},
|
||||||
syntax::{self, AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap},
|
Change, Position, Selection,
|
||||||
Change, LineEnding, Position, Selection, NATIVE_LINE_ENDING,
|
|
||||||
};
|
};
|
||||||
use helix_dap as dap;
|
use helix_dap as dap;
|
||||||
use helix_lsp::lsp;
|
use helix_lsp::lsp;
|
||||||
|
|
||||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
use arc_swap::access::DynGuard;
|
||||||
|
|
||||||
use arc_swap::access::{DynAccess, DynGuard};
|
|
||||||
|
|
||||||
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let millis = u64::deserialize(deserializer)?;
|
|
||||||
Ok(Duration::from_millis(millis))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_duration_millis<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_u64(
|
|
||||||
duration
|
|
||||||
.as_millis()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| serde::ser::Error::custom("duration value overflowed u64"))?,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
|
||||||
pub struct GutterConfig {
|
|
||||||
/// Gutter Layout
|
|
||||||
pub layout: Vec<GutterType>,
|
|
||||||
/// Options specific to the "line-numbers" gutter
|
|
||||||
pub line_numbers: GutterLineNumbersConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for GutterConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
layout: vec![
|
|
||||||
GutterType::Diagnostics,
|
|
||||||
GutterType::Spacer,
|
|
||||||
GutterType::LineNumbers,
|
|
||||||
GutterType::Spacer,
|
|
||||||
GutterType::Diff,
|
|
||||||
],
|
|
||||||
line_numbers: GutterLineNumbersConfig::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<GutterType>> for GutterConfig {
|
|
||||||
fn from(x: Vec<GutterType>) -> Self {
|
|
||||||
GutterConfig {
|
|
||||||
layout: x,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_gutter_seq_or_struct<'de, D>(deserializer: D) -> Result<GutterConfig, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct GutterVisitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for GutterVisitor {
|
|
||||||
type Value = GutterConfig;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
formatter,
|
|
||||||
"an array of gutter names or a detailed gutter configuration"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::de::SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut gutters = Vec::new();
|
|
||||||
while let Some(gutter) = seq.next_element::<String>()? {
|
|
||||||
gutters.push(
|
|
||||||
gutter
|
|
||||||
.parse::<GutterType>()
|
|
||||||
.map_err(serde::de::Error::custom)?,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(gutters.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
|
|
||||||
where
|
|
||||||
M: serde::de::MapAccess<'de>,
|
|
||||||
{
|
|
||||||
let deserializer = serde::de::value::MapAccessDeserializer::new(map);
|
|
||||||
Deserialize::deserialize(deserializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(GutterVisitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
|
||||||
pub struct GutterLineNumbersConfig {
|
|
||||||
/// Minimum number of characters to use for line number gutter. Defaults to 3.
|
|
||||||
pub min_width: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for GutterLineNumbersConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { min_width: 3 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
|
||||||
pub struct FilePickerConfig {
|
|
||||||
/// IgnoreOptions
|
|
||||||
/// Enables ignoring hidden files.
|
|
||||||
/// Whether to hide hidden files in file picker and global search results. Defaults to true.
|
|
||||||
pub hidden: bool,
|
|
||||||
/// Enables following symlinks.
|
|
||||||
/// Whether to follow symbolic links in file picker and file or directory completions. Defaults to true.
|
|
||||||
pub follow_symlinks: bool,
|
|
||||||
/// Hides symlinks that point into the current directory. Defaults to true.
|
|
||||||
pub deduplicate_links: bool,
|
|
||||||
/// Enables reading ignore files from parent directories. Defaults to true.
|
|
||||||
pub parents: bool,
|
|
||||||
/// Enables reading `.ignore` files.
|
|
||||||
/// Whether to hide files listed in .ignore in file picker and global search results. Defaults to true.
|
|
||||||
pub ignore: bool,
|
|
||||||
/// Enables reading `.gitignore` files.
|
|
||||||
/// Whether to hide files listed in .gitignore in file picker and global search results. Defaults to true.
|
|
||||||
pub git_ignore: bool,
|
|
||||||
/// Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option.
|
|
||||||
/// Whether to hide files listed in global .gitignore in file picker and global search results. Defaults to true.
|
|
||||||
pub git_global: bool,
|
|
||||||
/// Enables reading `.git/info/exclude` files.
|
|
||||||
/// Whether to hide files listed in .git/info/exclude in file picker and global search results. Defaults to true.
|
|
||||||
pub git_exclude: bool,
|
|
||||||
/// WalkBuilder options
|
|
||||||
/// Maximum Depth to recurse directories in file picker and global search. Defaults to `None`.
|
|
||||||
pub max_depth: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FilePickerConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
hidden: true,
|
|
||||||
follow_symlinks: true,
|
|
||||||
deduplicate_links: true,
|
|
||||||
parents: true,
|
|
||||||
ignore: true,
|
|
||||||
git_ignore: true,
|
|
||||||
git_global: true,
|
|
||||||
git_exclude: true,
|
|
||||||
max_depth: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
|
||||||
pub struct Config {
|
|
||||||
/// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5.
|
|
||||||
pub scrolloff: usize,
|
|
||||||
/// Number of lines to scroll at once. Defaults to 3
|
|
||||||
pub scroll_lines: isize,
|
|
||||||
/// Mouse support. Defaults to true.
|
|
||||||
pub mouse: bool,
|
|
||||||
/// Shell to use for shell commands. Defaults to ["cmd", "/C"] on Windows and ["sh", "-c"] otherwise.
|
|
||||||
pub shell: Vec<String>,
|
|
||||||
/// Line number mode.
|
|
||||||
pub line_number: LineNumber,
|
|
||||||
/// Highlight the lines cursors are currently on. Defaults to false.
|
|
||||||
pub cursorline: bool,
|
|
||||||
/// Highlight the columns cursors are currently on. Defaults to false.
|
|
||||||
pub cursorcolumn: bool,
|
|
||||||
#[serde(deserialize_with = "deserialize_gutter_seq_or_struct")]
|
|
||||||
pub gutters: GutterConfig,
|
|
||||||
/// Middle click paste support. Defaults to true.
|
|
||||||
pub middle_click_paste: bool,
|
|
||||||
/// Automatic insertion of pairs to parentheses, brackets,
|
|
||||||
/// etc. Optionally, this can be a list of 2-tuples to specify a
|
|
||||||
/// global list of characters to pair. Defaults to true.
|
|
||||||
pub auto_pairs: AutoPairConfig,
|
|
||||||
/// Automatic auto-completion, automatically pop up without user trigger. Defaults to true.
|
|
||||||
pub auto_completion: bool,
|
|
||||||
/// Automatic formatting on save. Defaults to true.
|
|
||||||
pub auto_format: bool,
|
|
||||||
/// Automatic save on focus lost. Defaults to false.
|
|
||||||
pub auto_save: bool,
|
|
||||||
/// Set a global text_width
|
|
||||||
pub text_width: usize,
|
|
||||||
/// Time in milliseconds since last keypress before idle timers trigger.
|
|
||||||
/// Used for autocompletion, set to 0 for instant. Defaults to 250ms.
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_duration_millis",
|
|
||||||
deserialize_with = "deserialize_duration_millis"
|
|
||||||
)]
|
|
||||||
pub idle_timeout: Duration,
|
|
||||||
/// Whether to insert the completion suggestion on hover. Defaults to true.
|
|
||||||
pub preview_completion_insert: bool,
|
|
||||||
pub completion_trigger_len: u8,
|
|
||||||
/// Whether to instruct the LSP to replace the entire word when applying a completion
|
|
||||||
/// or to only insert new text
|
|
||||||
pub completion_replace: bool,
|
|
||||||
/// Whether to display infoboxes. Defaults to true.
|
|
||||||
pub auto_info: bool,
|
|
||||||
pub file_picker: FilePickerConfig,
|
|
||||||
/// Configuration of the statusline elements
|
|
||||||
pub statusline: StatusLineConfig,
|
|
||||||
/// Shape for cursor in each mode
|
|
||||||
pub cursor_shape: CursorShapeConfig,
|
|
||||||
/// Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. Defaults to `false`.
|
|
||||||
pub true_color: bool,
|
|
||||||
/// Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative. Defaults to `false`.
|
|
||||||
pub undercurl: bool,
|
|
||||||
/// Search configuration.
|
|
||||||
#[serde(default)]
|
|
||||||
pub search: SearchConfig,
|
|
||||||
pub lsp: LspConfig,
|
|
||||||
pub terminal: Option<TerminalConfig>,
|
|
||||||
/// Column numbers at which to draw the rulers. Defaults to `[]`, meaning no rulers.
|
|
||||||
pub rulers: Vec<u16>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub whitespace: WhitespaceConfig,
|
|
||||||
/// Persistently display open buffers along the top
|
|
||||||
pub bufferline: BufferLine,
|
|
||||||
/// Vertical indent width guides.
|
|
||||||
pub indent_guides: IndentGuidesConfig,
|
|
||||||
/// Whether to color modes with different colors. Defaults to `false`.
|
|
||||||
pub color_modes: bool,
|
|
||||||
pub soft_wrap: SoftWrap,
|
|
||||||
/// Workspace specific lsp ceiling dirs
|
|
||||||
pub workspace_lsp_roots: Vec<PathBuf>,
|
|
||||||
/// Which line ending to choose for new documents. Defaults to `native`. i.e. `crlf` on Windows, otherwise `lf`.
|
|
||||||
pub default_line_ending: LineEndingConfig,
|
|
||||||
/// Whether to automatically insert a trailing line-ending on write if missing. Defaults to `true`.
|
|
||||||
pub insert_final_newline: bool,
|
|
||||||
/// Enables smart tab
|
|
||||||
pub smart_tab: Option<SmartTabConfig>,
|
|
||||||
/// Draw border around popups.
|
|
||||||
pub popup_border: PopupBorderConfig,
|
|
||||||
/// Which indent heuristic to use when a new line is inserted
|
|
||||||
#[serde(default)]
|
|
||||||
pub indent_heuristic: IndentationHeuristic,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
|
|
||||||
#[serde(rename_all = "kebab-case", default)]
|
|
||||||
pub struct SmartTabConfig {
|
|
||||||
pub enable: bool,
|
|
||||||
pub supersede_menu: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SmartTabConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
SmartTabConfig {
|
|
||||||
enable: true,
|
|
||||||
supersede_menu: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
|
||||||
pub struct TerminalConfig {
|
|
||||||
pub command: String,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
||||||
pub args: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn get_terminal_provider() -> Option<TerminalConfig> {
|
|
||||||
use crate::env::binary_exists;
|
|
||||||
|
|
||||||
if binary_exists("wt") {
|
|
||||||
return Some(TerminalConfig {
|
|
||||||
command: "wt".to_string(),
|
|
||||||
args: vec![
|
|
||||||
"new-tab".to_string(),
|
|
||||||
"--title".to_string(),
|
|
||||||
"DEBUG".to_string(),
|
|
||||||
"cmd".to_string(),
|
|
||||||
"/C".to_string(),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(TerminalConfig {
|
|
||||||
command: "conhost".to_string(),
|
|
||||||
args: vec!["cmd".to_string(), "/C".to_string()],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(windows, target_os = "wasm32")))]
|
|
||||||
pub fn get_terminal_provider() -> Option<TerminalConfig> {
|
|
||||||
use crate::env::{binary_exists, env_var_is_set};
|
|
||||||
|
|
||||||
if env_var_is_set("TMUX") && binary_exists("tmux") {
|
|
||||||
return Some(TerminalConfig {
|
|
||||||
command: "tmux".to_string(),
|
|
||||||
args: vec!["split-window".to_string()],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if env_var_is_set("WEZTERM_UNIX_SOCKET") && binary_exists("wezterm") {
|
|
||||||
return Some(TerminalConfig {
|
|
||||||
command: "wezterm".to_string(),
|
|
||||||
args: vec!["cli".to_string(), "split-pane".to_string()],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
|
||||||
pub struct LspConfig {
|
|
||||||
/// Enables LSP
|
|
||||||
pub enable: bool,
|
|
||||||
/// Display LSP progress messages below statusline
|
|
||||||
pub display_messages: bool,
|
|
||||||
/// Enable automatic pop up of signature help (parameter hints)
|
|
||||||
pub auto_signature_help: bool,
|
|
||||||
/// Display docs under signature help popup
|
|
||||||
pub display_signature_help_docs: bool,
|
|
||||||
/// Display inlay hints
|
|
||||||
pub display_inlay_hints: bool,
|
|
||||||
/// Whether to enable snippet support
|
|
||||||
pub snippets: bool,
|
|
||||||
/// Whether to include declaration in the goto reference query
|
|
||||||
pub goto_reference_include_declaration: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LspConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
enable: true,
|
|
||||||
display_messages: false,
|
|
||||||
auto_signature_help: true,
|
|
||||||
display_signature_help_docs: true,
|
|
||||||
display_inlay_hints: false,
|
|
||||||
snippets: true,
|
|
||||||
goto_reference_include_declaration: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
|
||||||
pub struct SearchConfig {
|
|
||||||
/// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
|
|
||||||
pub smart_case: bool,
|
|
||||||
/// Whether the search should wrap after depleting the matches. Default to true.
|
|
||||||
pub wrap_around: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
|
||||||
pub struct StatusLineConfig {
|
|
||||||
pub left: Vec<StatusLineElement>,
|
|
||||||
pub center: Vec<StatusLineElement>,
|
|
||||||
pub right: Vec<StatusLineElement>,
|
|
||||||
pub separator: String,
|
|
||||||
pub mode: ModeConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for StatusLineConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
use StatusLineElement as E;
|
|
||||||
|
|
||||||
Self {
|
|
||||||
left: vec![
|
|
||||||
E::Mode,
|
|
||||||
E::Spinner,
|
|
||||||
E::FileName,
|
|
||||||
E::ReadOnlyIndicator,
|
|
||||||
E::FileModificationIndicator,
|
|
||||||
],
|
|
||||||
center: vec![],
|
|
||||||
right: vec![
|
|
||||||
E::Diagnostics,
|
|
||||||
E::Selections,
|
|
||||||
E::Register,
|
|
||||||
E::Position,
|
|
||||||
E::FileEncoding,
|
|
||||||
],
|
|
||||||
separator: String::from("│"),
|
|
||||||
mode: ModeConfig::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
|
||||||
pub struct ModeConfig {
|
|
||||||
pub normal: String,
|
|
||||||
pub insert: String,
|
|
||||||
pub select: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ModeConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
normal: String::from("NOR"),
|
|
||||||
insert: String::from("INS"),
|
|
||||||
select: String::from("SEL"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum StatusLineElement {
|
|
||||||
/// The editor mode (Normal, Insert, Visual/Selection)
|
|
||||||
Mode,
|
|
||||||
|
|
||||||
/// The LSP activity spinner
|
|
||||||
Spinner,
|
|
||||||
|
|
||||||
/// The file basename (the leaf of the open file's path)
|
|
||||||
FileBaseName,
|
|
||||||
|
|
||||||
/// The relative file path
|
|
||||||
FileName,
|
|
||||||
|
|
||||||
// The file modification indicator
|
|
||||||
FileModificationIndicator,
|
|
||||||
|
|
||||||
/// An indicator that shows `"[readonly]"` when a file cannot be written
|
|
||||||
ReadOnlyIndicator,
|
|
||||||
|
|
||||||
/// The file encoding
|
|
||||||
FileEncoding,
|
|
||||||
|
|
||||||
/// The file line endings (CRLF or LF)
|
|
||||||
FileLineEnding,
|
|
||||||
|
|
||||||
/// The file type (language ID or "text")
|
|
||||||
FileType,
|
|
||||||
|
|
||||||
/// A summary of the number of errors and warnings
|
|
||||||
Diagnostics,
|
|
||||||
|
|
||||||
/// A summary of the number of errors and warnings on file and workspace
|
|
||||||
WorkspaceDiagnostics,
|
|
||||||
|
|
||||||
/// The number of selections (cursors)
|
|
||||||
Selections,
|
|
||||||
|
|
||||||
/// The number of characters currently in primary selection
|
|
||||||
PrimarySelectionLength,
|
|
||||||
|
|
||||||
/// The cursor position
|
|
||||||
Position,
|
|
||||||
|
|
||||||
/// The separator string
|
|
||||||
Separator,
|
|
||||||
|
|
||||||
/// The cursor position as a percent of the total file
|
|
||||||
PositionPercentage,
|
|
||||||
|
|
||||||
/// The total line numbers of the current file
|
|
||||||
TotalLineNumbers,
|
|
||||||
|
|
||||||
/// A single space
|
|
||||||
Spacer,
|
|
||||||
|
|
||||||
/// Current version control information
|
|
||||||
VersionControl,
|
|
||||||
|
|
||||||
/// Indicator for selected register
|
|
||||||
Register,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor shape is read and used on every rendered frame and so needs
|
|
||||||
// to be fast. Therefore we avoid a hashmap and use an enum indexed array.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct CursorShapeConfig([CursorKind; 3]);
|
|
||||||
|
|
||||||
impl CursorShapeConfig {
|
|
||||||
pub fn from_mode(&self, mode: Mode) -> CursorKind {
|
|
||||||
self.get(mode as usize).copied().unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for CursorShapeConfig {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let m = HashMap::<Mode, CursorKind>::deserialize(deserializer)?;
|
|
||||||
let into_cursor = |mode: Mode| m.get(&mode).copied().unwrap_or_default();
|
|
||||||
Ok(CursorShapeConfig([
|
|
||||||
into_cursor(Mode::Normal),
|
|
||||||
into_cursor(Mode::Select),
|
|
||||||
into_cursor(Mode::Insert),
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for CursorShapeConfig {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let mut map = serializer.serialize_map(Some(self.len()))?;
|
|
||||||
let modes = [Mode::Normal, Mode::Select, Mode::Insert];
|
|
||||||
for mode in modes {
|
|
||||||
map.serialize_entry(&mode, &self.from_mode(mode))?;
|
|
||||||
}
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for CursorShapeConfig {
|
|
||||||
type Target = [CursorKind; 3];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CursorShapeConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self([CursorKind::Block; 3])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// bufferline render modes
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum BufferLine {
|
|
||||||
/// Don't render bufferline
|
|
||||||
#[default]
|
|
||||||
Never,
|
|
||||||
/// Always render
|
|
||||||
Always,
|
|
||||||
/// Only if multiple buffers are open
|
|
||||||
Multiple,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum LineNumber {
|
|
||||||
/// Show absolute line number
|
|
||||||
Absolute,
|
|
||||||
|
|
||||||
/// If focused and in normal/select mode, show relative line number to the primary cursor.
|
|
||||||
/// If unfocused or in insert mode, show absolute line number.
|
|
||||||
Relative,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for LineNumber {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().as_str() {
|
|
||||||
"absolute" | "abs" => Ok(Self::Absolute),
|
|
||||||
"relative" | "rel" => Ok(Self::Relative),
|
|
||||||
_ => anyhow::bail!("Line number can only be `absolute` or `relative`."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum GutterType {
|
|
||||||
/// Show diagnostics and other features like breakpoints
|
|
||||||
Diagnostics,
|
|
||||||
/// Show line numbers
|
|
||||||
LineNumbers,
|
|
||||||
/// Show one blank space
|
|
||||||
Spacer,
|
|
||||||
/// Highlight local changes
|
|
||||||
Diff,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for GutterType {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_lowercase().as_str() {
|
|
||||||
"diagnostics" => Ok(Self::Diagnostics),
|
|
||||||
"spacer" => Ok(Self::Spacer),
|
|
||||||
"line-numbers" => Ok(Self::LineNumbers),
|
|
||||||
"diff" => Ok(Self::Diff),
|
|
||||||
_ => anyhow::bail!(
|
|
||||||
"Gutter type can only be `diagnostics`, `spacer`, `line-numbers` or `diff`."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(default)]
|
|
||||||
pub struct WhitespaceConfig {
|
|
||||||
pub render: WhitespaceRender,
|
|
||||||
pub characters: WhitespaceCharacters,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WhitespaceConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
render: WhitespaceRender::Basic(WhitespaceRenderValue::None),
|
|
||||||
characters: WhitespaceCharacters::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged, rename_all = "kebab-case")]
|
|
||||||
pub enum WhitespaceRender {
|
|
||||||
Basic(WhitespaceRenderValue),
|
|
||||||
Specific {
|
|
||||||
default: Option<WhitespaceRenderValue>,
|
|
||||||
space: Option<WhitespaceRenderValue>,
|
|
||||||
nbsp: Option<WhitespaceRenderValue>,
|
|
||||||
tab: Option<WhitespaceRenderValue>,
|
|
||||||
newline: Option<WhitespaceRenderValue>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum WhitespaceRenderValue {
|
|
||||||
None,
|
|
||||||
// TODO
|
|
||||||
// Selection,
|
|
||||||
All,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WhitespaceRender {
|
|
||||||
pub fn space(&self) -> WhitespaceRenderValue {
|
|
||||||
match *self {
|
|
||||||
Self::Basic(val) => val,
|
|
||||||
Self::Specific { default, space, .. } => {
|
|
||||||
space.or(default).unwrap_or(WhitespaceRenderValue::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn nbsp(&self) -> WhitespaceRenderValue {
|
|
||||||
match *self {
|
|
||||||
Self::Basic(val) => val,
|
|
||||||
Self::Specific { default, nbsp, .. } => {
|
|
||||||
nbsp.or(default).unwrap_or(WhitespaceRenderValue::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn tab(&self) -> WhitespaceRenderValue {
|
|
||||||
match *self {
|
|
||||||
Self::Basic(val) => val,
|
|
||||||
Self::Specific { default, tab, .. } => {
|
|
||||||
tab.or(default).unwrap_or(WhitespaceRenderValue::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn newline(&self) -> WhitespaceRenderValue {
|
|
||||||
match *self {
|
|
||||||
Self::Basic(val) => val,
|
|
||||||
Self::Specific {
|
|
||||||
default, newline, ..
|
|
||||||
} => newline.or(default).unwrap_or(WhitespaceRenderValue::None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(default)]
|
|
||||||
pub struct WhitespaceCharacters {
|
|
||||||
pub space: char,
|
|
||||||
pub nbsp: char,
|
|
||||||
pub tab: char,
|
|
||||||
pub tabpad: char,
|
|
||||||
pub newline: char,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WhitespaceCharacters {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
space: '·', // U+00B7
|
|
||||||
nbsp: '⍽', // U+237D
|
|
||||||
tab: '→', // U+2192
|
|
||||||
newline: '⏎', // U+23CE
|
|
||||||
tabpad: ' ',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(default, rename_all = "kebab-case")]
|
|
||||||
pub struct IndentGuidesConfig {
|
|
||||||
pub render: bool,
|
|
||||||
pub character: char,
|
|
||||||
pub skip_levels: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for IndentGuidesConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
skip_levels: 0,
|
|
||||||
render: false,
|
|
||||||
character: '│',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Line ending configuration.
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum LineEndingConfig {
|
|
||||||
/// The platform's native line ending.
|
|
||||||
///
|
|
||||||
/// `crlf` on Windows, otherwise `lf`.
|
|
||||||
#[default]
|
|
||||||
Native,
|
|
||||||
/// Line feed.
|
|
||||||
LF,
|
|
||||||
/// Carriage return followed by line feed.
|
|
||||||
Crlf,
|
|
||||||
/// Form feed.
|
|
||||||
#[cfg(feature = "unicode-lines")]
|
|
||||||
FF,
|
|
||||||
/// Carriage return.
|
|
||||||
#[cfg(feature = "unicode-lines")]
|
|
||||||
CR,
|
|
||||||
/// Next line.
|
|
||||||
#[cfg(feature = "unicode-lines")]
|
|
||||||
Nel,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LineEndingConfig> for LineEnding {
|
|
||||||
fn from(line_ending: LineEndingConfig) -> Self {
|
|
||||||
match line_ending {
|
|
||||||
LineEndingConfig::Native => NATIVE_LINE_ENDING,
|
|
||||||
LineEndingConfig::LF => LineEnding::LF,
|
|
||||||
LineEndingConfig::Crlf => LineEnding::Crlf,
|
|
||||||
#[cfg(feature = "unicode-lines")]
|
|
||||||
LineEndingConfig::FF => LineEnding::FF,
|
|
||||||
#[cfg(feature = "unicode-lines")]
|
|
||||||
LineEndingConfig::CR => LineEnding::CR,
|
|
||||||
#[cfg(feature = "unicode-lines")]
|
|
||||||
LineEndingConfig::Nel => LineEnding::Nel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum PopupBorderConfig {
|
|
||||||
None,
|
|
||||||
All,
|
|
||||||
Popup,
|
|
||||||
Menu,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
scrolloff: 5,
|
|
||||||
scroll_lines: 3,
|
|
||||||
mouse: true,
|
|
||||||
shell: if cfg!(windows) {
|
|
||||||
vec!["cmd".to_owned(), "/C".to_owned()]
|
|
||||||
} else {
|
|
||||||
vec!["sh".to_owned(), "-c".to_owned()]
|
|
||||||
},
|
|
||||||
line_number: LineNumber::Absolute,
|
|
||||||
cursorline: false,
|
|
||||||
cursorcolumn: false,
|
|
||||||
gutters: GutterConfig::default(),
|
|
||||||
middle_click_paste: true,
|
|
||||||
auto_pairs: AutoPairConfig::default(),
|
|
||||||
auto_completion: true,
|
|
||||||
auto_format: true,
|
|
||||||
auto_save: false,
|
|
||||||
idle_timeout: Duration::from_millis(250),
|
|
||||||
preview_completion_insert: true,
|
|
||||||
completion_trigger_len: 2,
|
|
||||||
auto_info: true,
|
|
||||||
file_picker: FilePickerConfig::default(),
|
|
||||||
statusline: StatusLineConfig::default(),
|
|
||||||
cursor_shape: CursorShapeConfig::default(),
|
|
||||||
true_color: false,
|
|
||||||
undercurl: false,
|
|
||||||
search: SearchConfig::default(),
|
|
||||||
lsp: LspConfig::default(),
|
|
||||||
terminal: get_terminal_provider(),
|
|
||||||
rulers: Vec::new(),
|
|
||||||
whitespace: WhitespaceConfig::default(),
|
|
||||||
bufferline: BufferLine::default(),
|
|
||||||
indent_guides: IndentGuidesConfig::default(),
|
|
||||||
color_modes: false,
|
|
||||||
soft_wrap: SoftWrap {
|
|
||||||
enable: Some(false),
|
|
||||||
..SoftWrap::default()
|
|
||||||
},
|
|
||||||
text_width: 80,
|
|
||||||
completion_replace: false,
|
|
||||||
workspace_lsp_roots: Vec::new(),
|
|
||||||
default_line_ending: LineEndingConfig::default(),
|
|
||||||
insert_final_newline: true,
|
|
||||||
smart_tab: Some(SmartTabConfig::default()),
|
|
||||||
popup_border: PopupBorderConfig::None,
|
|
||||||
indent_heuristic: IndentationHeuristic::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SearchConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
wrap_around: true,
|
|
||||||
smart_case: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Breakpoint {
|
pub struct Breakpoint {
|
||||||
|
Loading…
Reference in New Issue
Block a user