Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/syntax/config.rs')
| -rw-r--r-- | helix-core/src/syntax/config.rs | 632 |
1 files changed, 0 insertions, 632 deletions
diff --git a/helix-core/src/syntax/config.rs b/helix-core/src/syntax/config.rs deleted file mode 100644 index d2e03078..00000000 --- a/helix-core/src/syntax/config.rs +++ /dev/null @@ -1,632 +0,0 @@ -use crate::{auto_pairs::AutoPairs, diagnostic::Severity, Language}; - -use globset::GlobSet; -use helix_stdx::rope; -use serde::{ser::SerializeSeq as _, Deserialize, Serialize}; - -use std::{ - collections::{HashMap, HashSet}, - fmt::{self, Display}, - num::NonZeroU8, - path::PathBuf, - str::FromStr, -}; - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct Configuration { - pub language: Vec<LanguageConfiguration>, - #[serde(default)] - pub language_server: HashMap<String, LanguageServerConfiguration>, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct LanguageConfiguration { - #[serde(skip)] - pub(super) language: Option<Language>, - - #[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 scope: String, // source.rust - 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> - #[serde( - default, - skip_serializing, - deserialize_with = "from_comment_tokens", - alias = "comment-token" - )] - pub comment_tokens: Option<Vec<String>>, - #[serde( - default, - skip_serializing, - deserialize_with = "from_block_comment_tokens" - )] - pub block_comment_tokens: Option<Vec<BlockCommentToken>>, - 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>, - - /// If set, overrides `editor.path-completion`. - pub path_completion: Option<bool>, - /// If set, overrides `editor.word-completion`. - pub word_completion: Option<WordCompletion>, - - #[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<rope::Regex>, - // first_line_regex - // - #[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_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>, - /// Overrides the `editor.rainbow-brackets` config key for the language. - pub rainbow_brackets: Option<bool>, -} - -impl LanguageConfiguration { - pub fn language(&self) -> Language { - // This value must be set by `super::Loader::new`. - self.language.unwrap() - } -} - -#[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), - /// A Unix-style path glob. This is compared to the file's absolute path, so - /// it can be used to detect files based on their directories. If the glob - /// is not an absolute path and does not already start with a glob pattern, - /// a glob pattern will be prepended to it. - Glob(globset::Glob), -} - -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::Glob(glob) => { - let mut map = serializer.serialize_map(Some(1))?; - map.serialize_entry("glob", glob.glob())?; - 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, mut glob)) if key == "glob" => { - // If the glob isn't an absolute path or already starts - // with a glob pattern, add a leading glob so we - // properly match relative paths. - if !glob.starts_with('/') && !glob.starts_with("*/") { - glob.insert_str(0, "*/"); - } - - globset::Glob::new(glob.as_str()) - .map(FileType::Glob) - .map_err(|err| { - serde::de::Error::custom(format!("invalid `glob` pattern: {}", err)) - }) - } - 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) - } -} - -fn from_comment_tokens<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error> -where - D: serde::Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(untagged)] - enum CommentTokens { - Multiple(Vec<String>), - Single(String), - } - Ok( - Option::<CommentTokens>::deserialize(deserializer)?.map(|tokens| match tokens { - CommentTokens::Single(val) => vec![val], - CommentTokens::Multiple(vals) => vals, - }), - ) -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct BlockCommentToken { - pub start: String, - pub end: String, -} - -impl Default for BlockCommentToken { - fn default() -> Self { - BlockCommentToken { - start: "/*".to_string(), - end: "*/".to_string(), - } - } -} - -fn from_block_comment_tokens<'de, D>( - deserializer: D, -) -> Result<Option<Vec<BlockCommentToken>>, D::Error> -where - D: serde::Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(untagged)] - enum BlockCommentTokens { - Multiple(Vec<BlockCommentToken>), - Single(BlockCommentToken), - } - Ok( - Option::<BlockCommentTokens>::deserialize(deserializer)?.map(|tokens| match tokens { - BlockCommentTokens::Single(val) => vec![val], - BlockCommentTokens::Multiple(vals) => vals, - }), - ) -} - -#[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, - PullDiagnostics, - RenameSymbol, - InlayHints, - DocumentColors, -} - -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-reference", - 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", - PullDiagnostics => "pull-diagnostics", - RenameSymbol => "rename-symbol", - InlayHints => "inlay-hints", - DocumentColors => "document-colors", - }; - 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() -} - -fn deserialize_required_root_patterns<'de, D>(deserializer: D) -> Result<Option<GlobSet>, D::Error> -where - D: serde::Deserializer<'de>, -{ - let patterns = Vec::<String>::deserialize(deserializer)?; - if patterns.is_empty() { - return Ok(None); - } - let mut builder = globset::GlobSetBuilder::new(); - for pattern in patterns { - let glob = globset::Glob::new(&pattern).map_err(serde::de::Error::custom)?; - builder.add(glob); - } - builder.build().map(Some).map_err(serde::de::Error::custom) -} - -#[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, - #[serde( - default, - skip_serializing, - deserialize_with = "deserialize_required_root_patterns" - )] - pub required_root_patterns: Option<GlobSet>, -} - -#[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), - Table(HashMap<String, String>), -} - -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct DebugTemplate { - pub name: String, - pub request: String, - #[serde(default)] - 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)) - } -} - -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] -pub struct SoftWrap { - /// Soft wrap lines that exceed viewport width. Default to off - // NOTE: Option on purpose because the struct is shared between language config and global config. - // By default the option is None so that the language config falls back to the global config unless explicitly set. - pub enable: Option<bool>, - /// Maximum space left free at the end of the line. - /// This space is used to wrap text at word boundaries. If that is not possible within this limit - /// the word is simply split at the end of the line. - /// - /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views. - /// - /// Default to 20 - pub max_wrap: Option<u16>, - /// Maximum number of indentation that can be carried over from the previous line when softwrapping. - /// If a line is indented further then this limit it is rendered at the start of the viewport instead. - /// - /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views. - /// - /// Default to 40 - pub max_indent_retain: Option<u16>, - /// Indicator placed at the beginning of softwrapped lines - /// - /// Defaults to ↪ - pub wrap_indicator: Option<String>, - /// Softwrap at `text_width` instead of viewport width if it is shorter - pub wrap_at_text_width: Option<bool>, -} - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] -pub struct WordCompletion { - pub enable: Option<bool>, - pub trigger_length: Option<NonZeroU8>, -} - -fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<rope::Regex>, D::Error> -where - D: serde::Deserializer<'de>, -{ - Option::<String>::deserialize(deserializer)? - .map(|buf| rope::Regex::new(&buf).map_err(serde::de::Error::custom)) - .transpose() -} - -fn deserialize_lsp_config<'de, D>(deserializer: D) -> Result<Option<serde_json::Value>, D::Error> -where - D: serde::Deserializer<'de>, -{ - Option::<toml::Value>::deserialize(deserializer)? - .map(|toml| toml.try_into().map_err(serde::de::Error::custom)) - .transpose() -} - -fn deserialize_tab_width<'de, D>(deserializer: D) -> Result<usize, D::Error> -where - D: serde::Deserializer<'de>, -{ - usize::deserialize(deserializer).and_then(|n| { - if n > 0 && n <= 16 { - Ok(n) - } else { - Err(serde::de::Error::custom( - "tab width must be a value from 1 to 16 inclusive", - )) - } - }) -} - -pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error> -where - D: serde::Deserializer<'de>, -{ - Ok(Option::<AutoPairConfig>::deserialize(deserializer)?.and_then(AutoPairConfig::into)) -} - -fn default_timeout() -> u64 { - 20 -} |