@@ -73,11 +73,11 @@ impl Default for Configuration {
73pub struct LanguageConfiguration {
73pub struct LanguageConfiguration {
74 #[serde(rename = "name")]
74 #[serde(rename = "name")]
75 pub language_id: String, // c-sharp, rust
75 pub language_id: String, // c-sharp, rust
76− pub scope: String, // source.rust
76+ pub scope: String, // source.rust
77− pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
77+ pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
78 #[serde(default)]
78 #[serde(default)]
79 pub shebangs: Vec<String>, // interpreter(s) associated with language
79 pub shebangs: Vec<String>, // interpreter(s) associated with language
80− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
80+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
81 pub comment_token: Option<String>,
81 pub comment_token: Option<String>,
82 pub max_line_length: Option<usize>,
82 pub max_line_length: Option<usize>,
@@ -125,6 +125,78 @@ pub struct LanguageConfiguration {
125 pub rulers: Option<Vec<u16>>, // if set, override editor's rulers
125 pub rulers: Option<Vec<u16>>, // if set, override editor's rulers
128+#[derive(Debug, PartialEq, Eq, Hash)]
130+ /// The extension of the file, either the `Path::extension` or the full
131+ /// filename if the file does not have an extension.
133+ /// The suffix of a file. This is compared to a given file's absolute
134+ /// path, so it can be used to detect files based on their directories.
138+impl Serialize for FileType {
139+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141+ S: serde::Serializer,
143+ use serde::ser::SerializeMap;
146+ FileType::Extension(extension) => serializer.serialize_str(extension),
147+ FileType::Suffix(suffix) => {
148+ let mut map = serializer.serialize_map(Some(1))?;
149+ map.serialize_entry("suffix", &suffix.replace(std::path::MAIN_SEPARATOR, "/"))?;
156+impl<'de> Deserialize<'de> for FileType {
157+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159+ D: serde::de::Deserializer<'de>,
161+ struct FileTypeVisitor;
163+ impl<'de> serde::de::Visitor<'de> for FileTypeVisitor {
164+ type Value = FileType;
166+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
167+ formatter.write_str("string or table")
170+ fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
172+ E: serde::de::Error,
174+ Ok(FileType::Extension(value.to_string()))
177+ fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
179+ M: serde::de::MapAccess<'de>,
181+ match map.next_entry::<String, String>()? {
182+ Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix(
183+ suffix.replace('/', &std::path::MAIN_SEPARATOR.to_string()),
185+ Some((key, _value)) => Err(serde::de::Error::custom(format!(
186+ "unknown key in `file-types` list: {}",
189+ None => Err(serde::de::Error::custom(
190+ "expected a `suffix` key in the `file-types` entry",
196+ deserializer.deserialize_any(FileTypeVisitor)
128#[derive(Debug, Serialize, Deserialize)]
200#[derive(Debug, Serialize, Deserialize)]
129#[serde(rename_all = "kebab-case")]
201#[serde(rename_all = "kebab-case")]
130pub struct LanguageServerConfiguration {
202pub struct LanguageServerConfiguration {
@@ -454,7 +526,8 @@ impl LanguageConfiguration {
454pub struct Loader {
526pub struct Loader {
455 // highlight_names ?
527 // highlight_names ?
456 language_configs: Vec<Arc<LanguageConfiguration>>,
528 language_configs: Vec<Arc<LanguageConfiguration>>,
457− language_config_ids_by_file_type: HashMap<String, usize>, // Vec<usize>
529+ language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize>
530+ language_config_ids_by_suffix: HashMap<String, usize>,
458 language_config_ids_by_shebang: HashMap<String, usize>,
531 language_config_ids_by_shebang: HashMap<String, usize>,
460 scopes: ArcSwap<Vec<String>>,
533 scopes: ArcSwap<Vec<String>>,
@@ -464,7 +537,8 @@ impl Loader {
464 pub fn new(config: Configuration) -> Self {
537 pub fn new(config: Configuration) -> Self {
465 let mut loader = Self {
538 let mut loader = Self {
466 language_configs: Vec::new(),
539 language_configs: Vec::new(),
467− language_config_ids_by_file_type: HashMap::new(),
540+ language_config_ids_by_extension: HashMap::new(),
541+ language_config_ids_by_suffix: HashMap::new(),
468 language_config_ids_by_shebang: HashMap::new(),
542 language_config_ids_by_shebang: HashMap::new(),
469 scopes: ArcSwap::from_pointee(Vec::new()),
543 scopes: ArcSwap::from_pointee(Vec::new()),
@@ -475,10 +549,14 @@ impl Loader {
476 for file_type in &config.file_types {
550 for file_type in &config.file_types {
477 // entry().or_insert(Vec::new).push(language_id);
551 // entry().or_insert(Vec::new).push(language_id);
478− let file_type = file_type.replace('/', &std::path::MAIN_SEPARATOR.to_string());
552+ match file_type {
479− loader
553+ FileType::Extension(extension) => loader
480− .language_config_ids_by_file_type
554+ .language_config_ids_by_extension
555+ .insert(extension.clone(), language_id),
556+ FileType::Suffix(suffix) => loader
557+ .language_config_ids_by_suffix
481− .insert(file_type, language_id);
558+ .insert(suffix.clone(), language_id),
483 for shebang in &config.shebangs {
561 for shebang in &config.shebangs {
@@ -498,14 +576,14 @@ impl Loader {
498 let configuration_id = path
576 let configuration_id = path
499 .file_name()
577 .file_name()
500 .and_then(|n| n.to_str())
578 .and_then(|n| n.to_str())
501− .and_then(|file_name| self.language_config_ids_by_file_type.get(file_name))
579+ .and_then(|file_name| self.language_config_ids_by_extension.get(file_name))
502 .or_else(|| {
580 .or_else(|| {
503 path.extension()
581 path.extension()
504 .and_then(|extension| extension.to_str())
582 .and_then(|extension| extension.to_str())
505− .and_then(|extension| self.language_config_ids_by_file_type.get(extension))
583+ .and_then(|extension| self.language_config_ids_by_extension.get(extension))
507 .or_else(|| {
585 .or_else(|| {
508− self.language_config_ids_by_file_type
586+ self.language_config_ids_by_suffix
510 .find_map(|(file_type, id)| {
588 .find_map(|(file_type, id)| {
511 if path.to_str()?.ends_with(file_type) {
589 if path.to_str()?.ends_with(file_type) {