mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-21 17:06:18 +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;
|
||||
pub use convert::IntoTy;
|
||||
pub use definition::{init_config, init_language_server_config};
|
||||
pub use toml::read_toml_config;
|
||||
use validator::StaticValidator;
|
||||
pub use validator::{regex_str_validator, ty_validator, IntegerRangeValidator, Ty, Validator};
|
||||
pub use value::{from_value, to_value, Value};
|
||||
@ -23,6 +24,7 @@
|
||||
mod definition;
|
||||
pub mod env;
|
||||
mod macros;
|
||||
mod toml;
|
||||
mod validator;
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
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))
|
||||
todo!()
|
||||
// crate::config::default_syntax_loader()
|
||||
}
|
||||
}
|
||||
|
||||
@ -717,6 +304,7 @@ fn load_query(&self, kind: &str) -> Option<Query> {
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct SoftWrap {
|
||||
|
@ -41,833 +41,13 @@
|
||||
|
||||
pub use helix_core::diagnostic::Severity;
|
||||
use helix_core::{
|
||||
auto_pairs::AutoPairs,
|
||||
syntax::{self, AutoPairConfig, IndentationHeuristic, LanguageServerFeature, SoftWrap},
|
||||
Change, LineEnding, Position, Selection, NATIVE_LINE_ENDING,
|
||||
syntax::{self, LanguageServerFeature},
|
||||
Change, Position, Selection,
|
||||
};
|
||||
use helix_dap as dap;
|
||||
use helix_lsp::lsp;
|
||||
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
use arc_swap::access::DynGuard;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Breakpoint {
|
||||
|
Loading…
Reference in New Issue
Block a user