syntastica_parsers_dynamic/
lib.rs1#![doc = include_str!("../README.md")]
2#![cfg_attr(
3 feature = "docs",
4 cfg_attr(doc, doc = ::document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#))
5)]
6#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
7#![warn(rust_2018_idioms)]
8#![deny(missing_docs)]
9
10use std::{
11 borrow::Cow,
12 cell::RefCell,
13 collections::HashMap,
14 fmt::{self, Debug, Formatter},
15 marker::PhantomData,
16 path::PathBuf,
17};
18
19use anyhow::Result;
20use syntastica_core::language_set::{
21 FileType, HighlightConfiguration, LanguageSet, SupportedLanguage,
22};
23
24use loader::{Config, Loader};
25
26mod loader;
27
28#[derive(PartialEq, Eq, Hash, Clone)]
34pub struct Lang<'loader>(Box<str>, PhantomData<&'loader ()>);
35
36impl Debug for Lang<'_> {
37 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
38 f.debug_tuple("Lang").field(&self.0).finish()
39 }
40}
41
42impl<'loader> SupportedLanguage<'loader, LanguageLoader> for Lang<'loader> {
43 fn name(&self) -> Cow<'_, str> {
44 self.0.as_ref().into()
45 }
46
47 fn for_name(
48 name: impl AsRef<str>,
49 set: &'loader LanguageLoader,
50 ) -> syntastica_core::Result<Self> {
51 let name = name.as_ref();
52 if set.highlight_configs.borrow().contains_key(name) {
53 return Ok(Self(name.into(), PhantomData));
54 }
55
56 let (lang, lang_config) = set
57 .loader
58 .language_configuration_for_name(name)
59 .map_err(|err| syntastica_core::Error::Custom(err.to_string()))?
60 .ok_or_else(|| syntastica_core::Error::UnsupportedLanguage(name.to_string()))?;
61
62 let config_ref = lang_config
63 .highlight_config(lang, None)?
65 .ok_or_else(|| syntastica_core::Error::UnsupportedLanguage(name.to_string()))?;
66
67 let name = Box::<str>::from(name);
68 set.highlight_configs
69 .borrow_mut()
70 .insert(name.clone(), config_ref);
71 Ok(Self(name, PhantomData))
72 }
73
74 fn for_file_type(_file_type: FileType, _set: &'loader LanguageLoader) -> Option<Self> {
75 None
78 }
79
80 fn for_injection(name: impl AsRef<str>, set: &'loader LanguageLoader) -> Option<Self> {
81 let (lang, lang_config) = set
82 .loader
83 .language_configuration_for_injection_string(name.as_ref())
84 .ok()??;
85 let name = lang_config.language_name.as_str();
86
87 if set.highlight_configs.borrow().contains_key(name) {
88 return Some(Self(name.into(), PhantomData));
89 }
90
91 let config_ref = lang_config
92 .highlight_config(lang, None)
94 .ok()??;
95
96 let name = Box::<str>::from(name);
97 set.highlight_configs
98 .borrow_mut()
99 .insert(name.clone(), config_ref);
100 Some(Self(name, PhantomData))
101 }
102}
103
104pub struct LanguageLoader {
106 loader: Loader,
107 highlight_configs: RefCell<HashMap<Box<str>, &'static HighlightConfiguration>>,
108}
109
110impl LanguageLoader {
111 pub fn new(parser_search_dirs: Vec<PathBuf>) -> Result<Self> {
115 let mut loader = Loader::new()?;
116 loader.configure_highlights(syntastica_core::theme::THEME_KEYS);
117 loader.find_all_languages(&Config {
118 parser_directories: parser_search_dirs,
119 })?;
120 Ok(Self {
121 loader,
122 highlight_configs: RefCell::new(HashMap::new()),
123 })
124 }
125}
126
127impl<'loader> LanguageSet<'loader> for LanguageLoader {
128 type Language = Lang<'loader>;
129
130 fn get_language(
131 &self,
132 language: Self::Language,
133 ) -> syntastica_core::Result<&HighlightConfiguration> {
134 Ok(self
135 .highlight_configs
136 .borrow()
137 .get(&language.0)
138 .expect("Lang instances should initialize highlight configs"))
139 }
140}
141
142impl Drop for LanguageLoader {
143 fn drop(&mut self) {
144 for (_, config_ref) in self.highlight_configs.borrow_mut().drain() {
145 drop(unsafe { Box::from_raw(config_ref as *const _ as *mut HighlightConfiguration) });
148 }
149 }
150}