syntastica_core/
language_set.rs

1//! Defines the [`LanguageSet`] trait and some related types.
2//!
3//! Also re-exports [`syntastica_highlight::HighlightConfiguration`], [`tree_sitter::Language`],
4//! and [`tft::FileType`].
5
6use std::{borrow::Cow, path::Path};
7
8pub use syntastica_highlight::HighlightConfiguration;
9pub use tft::FileType;
10
11pub use crate::ts_runtime::Language;
12
13mod union;
14
15pub use union::*;
16
17/// A language included in a [`LanguageSet`].
18///
19/// Instances can be obtained with [`for_name`](SupportedLanguage::for_name),
20/// [`for_file_type`](SupportedLanguage::for_file_type), and
21/// [`for_injection`](SupportedLanguage::for_injection).
22pub trait SupportedLanguage<'set, S>: Sized {
23    /// Get the name for this language.
24    ///
25    /// Passing the output of this function to [`for_name`](SupportedLanguage::for_name) must
26    /// always result in an [`Ok`] value.
27    // TODO: should this function really be part of the trait?
28    fn name(&self) -> Cow<'_, str>;
29
30    /// Get the language with the given name.
31    ///
32    /// If no language for that name exists, implementations should return an
33    /// [`UnsupportedLanguage`](crate::Error::UnsupportedLanguage) error.
34    ///
35    /// `syntastica` itself does not provide a list of valid language names, but the
36    /// [official parser collections](https://rubixdev.github.io/syntastica/syntastica/#parser-collections)
37    /// do. However, every string that may be returned by [`name`](SupportedLanguage::name) must
38    /// result in an [`Ok`] value.
39    fn for_name(name: impl AsRef<str>, set: &'set S) -> Result<Self, crate::Error>;
40
41    /// Find a language based on the given [`FileType`].
42    ///
43    /// Implementations should return the language that supports the given file type, if there is
44    /// any.
45    fn for_file_type(file_type: FileType, set: &'set S) -> Option<Self>;
46
47    /// Find a language for an injection.
48    ///
49    /// The passed `name` could be any string that somehow identifies a language. This very much
50    /// depends on the source of the injection. For example, in fenced code blocks in markdown, the
51    /// text after the opening `` ``` `` is passed to this function.
52    ///
53    /// Note that this function does not get called, if [`for_name`](SupportedLanguage::for_name)
54    /// was able to return a language for `name`.
55    ///
56    /// The default implementation tries to detect a [`FileType`] using `name` as both a filename
57    /// and a file extension, and passes that to
58    /// [`for_file_type`](SupportedLanguage::for_file_type).
59    fn for_injection(name: impl AsRef<str>, set: &'set S) -> Option<Self> {
60        let name = name.as_ref();
61        // try to detect a file type, once using the name as a full path and once using it as the
62        // extension, and if one is found, pass it to `self.for_file_type`
63        tft::try_detect(Path::new(name), "")
64            .or_else(|| tft::try_detect(Path::new(&format!("file.{name}")), ""))
65            .and_then(|ft| Self::for_file_type(ft, set))
66    }
67}
68
69/// Describes a type that is able to provide tree-sitter parsers and queries.
70///
71/// All official syntastica parser collections provide a type called `LanguageSetImpl` which
72/// implements this trait. See
73/// [the project overview](https://rubixdev.github.io/syntastica/syntastica/#parser-collections)
74/// for more information on that.
75pub trait LanguageSet<'s>: Sized {
76    /// A type identifying a language that is included in this set.
77    ///
78    /// The given type should usually be an enum of all included languages.
79    type Language: SupportedLanguage<'s, Self>;
80
81    /// Get the [`HighlightConfiguration`] for a given [language](LanguageSet::Language).
82    ///
83    /// **The returned [`HighlightConfiguration`] _must_ be
84    /// [configured](HighlightConfiguration::configure) with
85    /// [`THEME_KEYS`](crate::theme::THEME_KEYS).**
86    ///
87    /// The function is given an instance of [`Self::Language`] and as such should not need to
88    /// return an [`Error::UnsupportedLanguage`](crate::Error::UnsupportedLanguage) error.
89    fn get_language(
90        &self,
91        language: Self::Language,
92    ) -> Result<&HighlightConfiguration, crate::Error>;
93}