1use super::scope::*;
2use super::syntax_definition::*;
3use super::ParsingError;
4
5#[cfg(feature = "metadata")]
6use super::metadata::{LoadMetadata, Metadata, RawMetadataEntry};
7
8#[cfg(feature = "yaml-load")]
9use super::super::LoadingError;
10
11use std::collections::{BTreeSet, HashMap, HashSet};
12use std::fs::File;
13use std::io::{self, BufRead, BufReader};
14use std::mem;
15use std::path::Path;
16use std::sync::{Arc, Mutex};
17
18use super::regex::{Regex, RegexCache};
19use crate::parsing::syntax_definition::ContextId;
20use once_cell::sync::OnceCell;
21use serde_derive::{Deserialize, Serialize};
22
23#[cfg(feature = "regex-census")]
24#[path = "regex_census.rs"]
25mod regex_census;
26#[cfg(feature = "regex-census")]
27pub use regex_census::*;
28
29#[derive(Debug, Serialize, Deserialize)]
39pub struct SyntaxSet {
40 #[serde(deserialize_with = "deserialize_syntaxes")]
41 syntaxes: Vec<SyntaxReference>,
42 path_syntaxes: Vec<(String, usize)>,
44
45 #[serde(skip_serializing, skip_deserializing, default = "OnceCell::new")]
46 first_line_cache: OnceCell<FirstLineCache>,
47 #[cfg(feature = "metadata")]
52 #[serde(skip, default)]
53 pub(crate) metadata: Metadata,
54}
55
56#[derive(Clone, Debug, Serialize, Deserialize)]
60pub struct SyntaxReference {
61 pub name: String,
62 pub file_extensions: Vec<String>,
63 pub scope: Scope,
64 pub first_line_match: Option<String>,
65 pub hidden: bool,
66 #[serde(serialize_with = "ordered_map")]
67 pub variables: HashMap<String, String>,
68 #[serde(skip)]
69 pub(crate) lazy_contexts: OnceCell<LazyContexts>,
70 #[serde(skip)]
71 regex_cache: Option<Arc<Mutex<RegexCache>>>,
72 pub(crate) serialized_lazy_contexts: Vec<u8>,
73}
74
75fn deserialize_syntaxes<'de, D>(deserializer: D) -> Result<Vec<SyntaxReference>, D::Error>
76where
77 D: serde::Deserializer<'de>,
78{
79 let mut syntaxes = <Vec<SyntaxReference> as serde::Deserialize>::deserialize(deserializer)?;
80 let cache = Arc::new(Mutex::new(RegexCache::default()));
81 for syntax in &mut syntaxes {
82 syntax.regex_cache = Some(Arc::clone(&cache));
83 }
84 Ok(syntaxes)
85}
86
87#[derive(Clone, Debug, Serialize, Deserialize)]
89pub(crate) struct LazyContexts {
90 #[serde(serialize_with = "ordered_map")]
91 pub(crate) context_ids: HashMap<String, ContextId>,
92 pub(crate) contexts: Vec<Context>,
93}
94
95#[derive(Clone, Default)]
105pub struct SyntaxSetBuilder {
106 syntaxes: Vec<SyntaxDefinition>,
107 path_syntaxes: Vec<(String, usize)>,
108 #[cfg(feature = "metadata")]
109 raw_metadata: LoadMetadata,
110
111 #[cfg(feature = "metadata")]
115 existing_metadata: Option<Metadata>,
116}
117
118#[cfg(feature = "yaml-load")]
119fn load_syntax_file(
120 p: &Path,
121 lines_include_newline: bool,
122) -> Result<SyntaxDefinition, LoadingError> {
123 let s = std::fs::read_to_string(p)?;
124
125 SyntaxDefinition::load_from_str(
126 &s,
127 lines_include_newline,
128 p.file_stem().and_then(|x| x.to_str()),
129 )
130 .map_err(|e| LoadingError::ParseSyntax(e, format!("{}", p.display())))
131}
132
133impl Clone for SyntaxSet {
134 fn clone(&self) -> SyntaxSet {
135 SyntaxSet {
136 syntaxes: self.syntaxes.clone(),
137 path_syntaxes: self.path_syntaxes.clone(),
138 first_line_cache: OnceCell::new(),
140 #[cfg(feature = "metadata")]
141 metadata: self.metadata.clone(),
142 }
143 }
144}
145
146impl Default for SyntaxSet {
147 fn default() -> Self {
148 SyntaxSet {
149 syntaxes: Vec::new(),
150 path_syntaxes: Vec::new(),
151 first_line_cache: OnceCell::new(),
152 #[cfg(feature = "metadata")]
153 metadata: Metadata::default(),
154 }
155 }
156}
157
158impl SyntaxSet {
159 pub fn new() -> SyntaxSet {
160 SyntaxSet::default()
161 }
162
163 #[cfg(feature = "yaml-load")]
173 pub fn load_from_folder<P: AsRef<Path>>(folder: P) -> Result<SyntaxSet, LoadingError> {
174 let mut builder = SyntaxSetBuilder::new();
175 builder.add_from_folder(folder, false)?;
176 Ok(builder.build())
177 }
178
179 pub fn syntaxes(&self) -> &[SyntaxReference] {
181 &self.syntaxes[..]
182 }
183
184 #[cfg(feature = "metadata")]
185 pub fn set_metadata(&mut self, metadata: Metadata) {
186 self.metadata = metadata;
187 }
188
189 #[cfg(feature = "metadata")]
191 pub fn metadata(&self) -> &Metadata {
192 &self.metadata
193 }
194
195 pub fn find_syntax_by_scope(&self, scope: Scope) -> Option<&SyntaxReference> {
201 self.syntaxes.iter().rev().find(|&s| s.scope == scope)
202 }
203
204 pub fn find_syntax_by_name<'a>(&'a self, name: &str) -> Option<&'a SyntaxReference> {
205 self.syntaxes.iter().rev().find(|&s| name == s.name)
206 }
207
208 pub fn find_syntax_by_extension<'a>(&'a self, extension: &str) -> Option<&'a SyntaxReference> {
209 self.syntaxes.iter().rev().find(|&s| {
210 s.file_extensions
211 .iter()
212 .any(|e| e.eq_ignore_ascii_case(extension))
213 })
214 }
215
216 pub fn find_syntax_by_token<'a>(&'a self, s: &str) -> Option<&'a SyntaxReference> {
221 {
222 let ext_res = self.find_syntax_by_extension(s);
223 if ext_res.is_some() {
224 return ext_res;
225 }
226 }
227 self.syntaxes
228 .iter()
229 .rev()
230 .find(|&syntax| syntax.name.eq_ignore_ascii_case(s))
231 }
232
233 pub fn find_syntax_by_first_line<'a>(&'a self, s: &str) -> Option<&'a SyntaxReference> {
238 let s = s.strip_prefix("\u{feff}").unwrap_or(s); let cache = self.first_line_cache();
240 for &(ref reg, i) in cache.regexes.iter().rev() {
241 if reg.search(s, 0, s.len(), None) {
242 return Some(&self.syntaxes[i]);
243 }
244 }
245 None
246 }
247
248 pub fn find_syntax_by_path<'a>(&'a self, path: &str) -> Option<&'a SyntaxReference> {
256 let mut slash_path = "/".to_string();
257 slash_path.push_str(path);
258 self.path_syntaxes
259 .iter()
260 .rev()
261 .find(|t| t.0.ends_with(&slash_path) || t.0 == path)
262 .map(|&(_, i)| &self.syntaxes[i])
263 }
264
265 pub fn find_syntax_for_file<P: AsRef<Path>>(
284 &self,
285 path_obj: P,
286 ) -> io::Result<Option<&SyntaxReference>> {
287 let path: &Path = path_obj.as_ref();
288 let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
289 let extension = path.extension().and_then(|x| x.to_str()).unwrap_or("");
290 let ext_syntax = self
291 .find_syntax_by_extension(file_name)
292 .or_else(|| self.find_syntax_by_extension(extension));
293 let line_syntax = if ext_syntax.is_none() {
294 let mut line = String::new();
295 let f = File::open(path)?;
296 let mut line_reader = BufReader::new(&f);
297 line_reader.read_line(&mut line)?;
298 self.find_syntax_by_first_line(&line)
299 } else {
300 None
301 };
302 let syntax = ext_syntax.or(line_syntax);
303 Ok(syntax)
304 }
305
306 pub fn find_syntax_plain_text(&self) -> &SyntaxReference {
326 self.find_syntax_by_name("Plain Text")
327 .expect("All syntax sets ought to have a plain text syntax")
328 }
329
330 pub fn into_builder(self) -> SyntaxSetBuilder {
336 #[cfg(feature = "metadata")]
337 let SyntaxSet {
338 syntaxes,
339 path_syntaxes,
340 metadata,
341 ..
342 } = self;
343 #[cfg(not(feature = "metadata"))]
344 let SyntaxSet {
345 syntaxes,
346 path_syntaxes,
347 ..
348 } = self;
349
350 let mut context_map = HashMap::new();
351 for (syntax_index, syntax) in syntaxes.iter().enumerate() {
352 for (context_index, context) in syntax.contexts().iter().enumerate() {
353 context_map.insert(
354 ContextId {
355 syntax_index,
356 context_index,
357 },
358 context.clone(),
359 );
360 }
361 }
362
363 let mut builder_syntaxes = Vec::with_capacity(syntaxes.len());
364
365 for syntax in syntaxes {
366 let SyntaxReference {
367 name,
368 file_extensions,
369 scope,
370 first_line_match,
371 hidden,
372 variables,
373 serialized_lazy_contexts,
374 ..
375 } = syntax;
376
377 let lazy_contexts = LazyContexts::deserialize(&serialized_lazy_contexts[..]);
378 let mut builder_contexts = HashMap::with_capacity(lazy_contexts.context_ids.len());
379 for (name, context_id) in lazy_contexts.context_ids {
380 if let Some(context) = context_map.remove(&context_id) {
381 builder_contexts.insert(name, context);
382 }
383 }
384
385 let syntax_definition = SyntaxDefinition {
386 name,
387 file_extensions,
388 scope,
389 first_line_match,
390 hidden,
391 variables,
392 contexts: builder_contexts,
393 };
394 builder_syntaxes.push(syntax_definition);
395 }
396
397 SyntaxSetBuilder {
398 syntaxes: builder_syntaxes,
399 path_syntaxes,
400 #[cfg(feature = "metadata")]
401 existing_metadata: Some(metadata),
402 #[cfg(feature = "metadata")]
403 raw_metadata: LoadMetadata::default(),
404 }
405 }
406
407 #[inline(always)]
408 pub(crate) fn get_context(&self, context_id: &ContextId) -> Result<&Context, ParsingError> {
409 let syntax = &self
410 .syntaxes
411 .get(context_id.syntax_index)
412 .ok_or(ParsingError::MissingContext(*context_id))?;
413 syntax
414 .contexts()
415 .get(context_id.context_index)
416 .ok_or(ParsingError::MissingContext(*context_id))
417 }
418
419 fn first_line_cache(&self) -> &FirstLineCache {
420 self.first_line_cache
421 .get_or_init(|| FirstLineCache::new(self.syntaxes()))
422 }
423
424 pub fn find_unlinked_contexts(&self) -> BTreeSet<String> {
425 let SyntaxSet { syntaxes, .. } = self;
426
427 let mut unlinked_contexts = BTreeSet::new();
428
429 for syntax in syntaxes {
430 let SyntaxReference { name, scope, .. } = syntax;
431
432 for context in syntax.contexts() {
433 Self::find_unlinked_contexts_in_context(
434 name,
435 scope,
436 context,
437 &mut unlinked_contexts,
438 );
439 }
440 }
441 unlinked_contexts
442 }
443
444 fn find_unlinked_contexts_in_context(
445 name: &str,
446 scope: &Scope,
447 context: &Context,
448 unlinked_contexts: &mut BTreeSet<String>,
449 ) {
450 for pattern in context.patterns.iter() {
451 let maybe_refs_to_check = match pattern {
452 Pattern::Match(match_pat) => match &match_pat.operation {
453 MatchOperation::Push(context_refs) => Some(context_refs),
454 MatchOperation::Set(context_refs) => Some(context_refs),
455 _ => None,
456 },
457 _ => None,
458 };
459 for context_ref in maybe_refs_to_check.into_iter().flatten() {
460 match context_ref {
461 ContextReference::Direct(_) => {}
462 _ => {
463 unlinked_contexts.insert(format!(
464 "Syntax '{}' with scope '{}' has unresolved context reference {:?}",
465 name, scope, &context_ref
466 ));
467 }
468 }
469 }
470 }
471 }
472}
473
474impl SyntaxReference {
475 pub(crate) fn context_ids(&self) -> &HashMap<String, ContextId> {
476 &self.lazy_contexts().context_ids
477 }
478
479 fn contexts(&self) -> &[Context] {
480 &self.lazy_contexts().contexts
481 }
482
483 fn lazy_contexts(&self) -> &LazyContexts {
484 self.lazy_contexts.get_or_init(|| {
485 #[cfg(feature = "syntax-timing")]
486 let _timing =
487 super::syntax_timing::Initializer::new(super::syntax_timing::Phase::Grammar);
488 let mut contexts = LazyContexts::deserialize(&self.serialized_lazy_contexts[..]);
489 if let Some(cache) = &self.regex_cache {
490 contexts.share_regexes(&mut cache.lock().unwrap());
491 } else {
492 contexts.share_regexes(&mut RegexCache::default());
493 }
494 contexts
495 })
496 }
497}
498
499impl LazyContexts {
500 fn share_regexes(&mut self, cache: &mut RegexCache) {
501 for context in &mut self.contexts {
502 for pattern in &mut context.patterns {
503 if let Pattern::Match(pattern) = pattern {
504 cache.share(&mut pattern.regex);
505 }
506 }
507 }
508 }
509
510 fn deserialize(data: &[u8]) -> LazyContexts {
511 crate::dumps::from_reader(data).expect("data is not corrupt or out of sync with the code")
512 }
513}
514
515impl SyntaxSetBuilder {
516 pub fn new() -> SyntaxSetBuilder {
517 SyntaxSetBuilder::default()
518 }
519
520 pub fn add(&mut self, syntax: SyntaxDefinition) {
522 self.syntaxes.push(syntax);
523 }
524
525 pub fn syntaxes(&self) -> &[SyntaxDefinition] {
527 &self.syntaxes[..]
528 }
529
530 #[cfg(feature = "yaml-load")]
535 pub fn add_plain_text_syntax(&mut self) {
536 let s = "---\nname: Plain Text\nfile_extensions: [txt]\nscope: text.plain\ncontexts: \
537 {main: []}";
538 let syn = SyntaxDefinition::load_from_str(s, false, None).unwrap();
539 self.syntaxes.push(syn);
540 }
541
542 #[cfg(feature = "yaml-load")]
556 pub fn add_from_folder<P: AsRef<Path>>(
557 &mut self,
558 folder: P,
559 lines_include_newline: bool,
560 ) -> Result<(), LoadingError> {
561 for entry in crate::utils::walk_dir(folder).sort_by(|a, b| a.file_name().cmp(b.file_name()))
562 {
563 let entry = entry.map_err(LoadingError::WalkDir)?;
564 if entry
565 .path()
566 .extension()
567 .is_some_and(|e| e == "sublime-syntax")
568 {
569 let syntax = load_syntax_file(entry.path(), lines_include_newline)?;
570 if let Some(path_str) = entry.path().to_str() {
571 let path = Path::new(path_str);
574 let path_parts: Vec<_> = path.iter().map(|c| c.to_str().unwrap()).collect();
575 self.path_syntaxes
576 .push((path_parts.join("/").to_string(), self.syntaxes.len()));
577 }
578 self.syntaxes.push(syntax);
579 }
580
581 #[cfg(feature = "metadata")]
582 {
583 if entry.path().extension() == Some("tmPreferences".as_ref()) {
584 match RawMetadataEntry::load(entry.path()) {
585 Ok(meta) => self.raw_metadata.add_raw(meta),
586 Err(_err) => (),
587 }
588 }
589 }
590 }
591
592 Ok(())
593 }
594
595 pub fn build(self) -> SyntaxSet {
617 #[cfg(not(feature = "metadata"))]
618 let SyntaxSetBuilder {
619 syntaxes: syntax_definitions,
620 path_syntaxes,
621 } = self;
622 #[cfg(feature = "metadata")]
623 let SyntaxSetBuilder {
624 syntaxes: syntax_definitions,
625 path_syntaxes,
626 raw_metadata,
627 existing_metadata,
628 } = self;
629
630 let mut syntaxes = Vec::with_capacity(syntax_definitions.len());
631 let regex_cache = Arc::new(Mutex::new(RegexCache::default()));
632 let mut all_context_ids = Vec::new();
633 let mut all_contexts = vec![Vec::new(); syntax_definitions.len()];
634
635 for (syntax_index, syntax_definition) in syntax_definitions.into_iter().enumerate() {
636 let SyntaxDefinition {
637 name,
638 file_extensions,
639 scope,
640 first_line_match,
641 hidden,
642 variables,
643 contexts,
644 } = syntax_definition;
645
646 let mut context_ids = HashMap::new();
647
648 let mut contexts: Vec<(String, Context)> = contexts.into_iter().collect();
649 contexts.sort_unstable_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b));
654 for (name, context) in contexts {
655 let context_index = all_contexts[syntax_index].len();
656 context_ids.insert(
657 name,
658 ContextId {
659 syntax_index,
660 context_index,
661 },
662 );
663 all_contexts[syntax_index].push(context);
664 }
665
666 let syntax = SyntaxReference {
667 name,
668 file_extensions,
669 scope,
670 first_line_match,
671 hidden,
672 variables,
673 lazy_contexts: OnceCell::new(),
674 regex_cache: Some(Arc::clone(®ex_cache)),
675 serialized_lazy_contexts: Vec::new(), };
677 syntaxes.push(syntax);
678 all_context_ids.push(context_ids);
679 }
680
681 let mut found_more_backref_includes = true;
682 for (syntax_index, _syntax) in syntaxes.iter().enumerate() {
683 let mut no_prototype = HashSet::new();
684 let prototype = all_context_ids[syntax_index].get("prototype");
685 if let Some(prototype_id) = prototype {
686 Self::recursively_mark_no_prototype(
688 prototype_id,
689 &all_context_ids[syntax_index],
690 &all_contexts,
691 &mut no_prototype,
692 );
693 }
694
695 for context_id in all_context_ids[syntax_index].values() {
696 let context = &mut all_contexts[context_id.syntax_index][context_id.context_index];
697 if let Some(prototype_id) = prototype {
698 if context.meta_include_prototype && !no_prototype.contains(context_id) {
699 context.prototype = Some(*prototype_id);
700 }
701 }
702 Self::link_context(context, syntax_index, &all_context_ids, &syntaxes);
703
704 if context.uses_backrefs {
705 found_more_backref_includes = true;
706 }
707 }
708 }
709
710 while found_more_backref_includes {
718 found_more_backref_includes = false;
719 for syntax_index in 0..syntaxes.len() {
722 for context_index in 0..all_contexts[syntax_index].len() {
723 let context = &all_contexts[syntax_index][context_index];
724 if !context.uses_backrefs && context.patterns.iter().any(|pattern| {
725 matches!(pattern, Pattern::Include(ContextReference::Direct(id)) if all_contexts[id.syntax_index][id.context_index].uses_backrefs)
726 }) {
727 let context = &mut all_contexts[syntax_index][context_index];
728 context.uses_backrefs = true;
729 found_more_backref_includes = true;
731 }
732 }
733 }
734 }
735
736 #[cfg(feature = "metadata")]
737 let metadata = match existing_metadata {
738 Some(existing) => existing.merged_with_raw(raw_metadata),
739 None => raw_metadata.into(),
740 };
741
742 for syntax in &mut syntaxes {
747 let lazy_contexts = LazyContexts {
748 context_ids: all_context_ids.remove(0),
749 contexts: all_contexts.remove(0),
750 };
751
752 syntax.serialized_lazy_contexts = crate::dumps::dump_binary(&lazy_contexts);
753 }
754
755 SyntaxSet {
756 syntaxes,
757 path_syntaxes,
758 first_line_cache: OnceCell::new(),
759 #[cfg(feature = "metadata")]
760 metadata,
761 }
762 }
763
764 fn recursively_mark_no_prototype(
767 context_id: &ContextId,
768 syntax_context_ids: &HashMap<String, ContextId>,
769 all_contexts: &[Vec<Context>],
770 no_prototype: &mut HashSet<ContextId>,
771 ) {
772 let first_time = no_prototype.insert(*context_id);
773 if !first_time {
774 return;
775 }
776
777 for pattern in &all_contexts[context_id.syntax_index][context_id.context_index].patterns {
778 match *pattern {
779 Pattern::Match(ref match_pat) => {
782 let maybe_context_refs = match match_pat.operation {
783 MatchOperation::Push(ref context_refs)
784 | MatchOperation::Set(ref context_refs) => Some(context_refs),
785 MatchOperation::Pop | MatchOperation::None => None,
786 };
787 if let Some(context_refs) = maybe_context_refs {
788 for context_ref in context_refs.iter() {
789 match context_ref {
790 ContextReference::Inline(ref s)
791 | ContextReference::Named(ref s) => {
792 if let Some(i) = syntax_context_ids.get(s) {
793 Self::recursively_mark_no_prototype(
794 i,
795 syntax_context_ids,
796 all_contexts,
797 no_prototype,
798 );
799 }
800 }
801 ContextReference::Direct(ref id) => {
802 Self::recursively_mark_no_prototype(
803 id,
804 syntax_context_ids,
805 all_contexts,
806 no_prototype,
807 );
808 }
809 _ => (),
810 }
811 }
812 }
813 }
814 Pattern::Include(ref reference) => match reference {
815 ContextReference::Named(ref s) => {
816 if let Some(id) = syntax_context_ids.get(s) {
817 Self::recursively_mark_no_prototype(
818 id,
819 syntax_context_ids,
820 all_contexts,
821 no_prototype,
822 );
823 }
824 }
825 ContextReference::Direct(ref id) => {
826 Self::recursively_mark_no_prototype(
827 id,
828 syntax_context_ids,
829 all_contexts,
830 no_prototype,
831 );
832 }
833 _ => (),
834 },
835 }
836 }
837 }
838
839 fn link_context(
840 context: &mut Context,
841 syntax_index: usize,
842 all_context_ids: &[HashMap<String, ContextId>],
843 syntaxes: &[SyntaxReference],
844 ) {
845 for pattern in &mut context.patterns {
846 match *pattern {
847 Pattern::Match(ref mut match_pat) => {
848 Self::link_match_pat(match_pat, syntax_index, all_context_ids, syntaxes)
849 }
850 Pattern::Include(ref mut context_ref) => {
851 Self::link_ref(context_ref, syntax_index, all_context_ids, syntaxes)
852 }
853 }
854 }
855 }
856
857 fn link_ref(
858 context_ref: &mut ContextReference,
859 syntax_index: usize,
860 all_context_ids: &[HashMap<String, ContextId>],
861 syntaxes: &[SyntaxReference],
862 ) {
863 use super::syntax_definition::ContextReference::*;
865 let linked_context_id = match *context_ref {
866 Named(ref s) | Inline(ref s) => {
867 if s == "$top_level_main" {
871 all_context_ids[syntax_index].get("main")
872 } else {
873 all_context_ids[syntax_index].get(s)
874 }
875 }
876 ByScope {
877 scope,
878 ref sub_context,
879 with_escape,
880 } => Self::with_plain_text_fallback(
881 all_context_ids,
882 syntaxes,
883 with_escape,
884 Self::find_id(sub_context, all_context_ids, syntaxes, |index_and_syntax| {
885 index_and_syntax.1.scope == scope
886 }),
887 ),
888 File {
889 ref name,
890 ref sub_context,
891 with_escape,
892 } => Self::with_plain_text_fallback(
893 all_context_ids,
894 syntaxes,
895 with_escape,
896 Self::find_id(sub_context, all_context_ids, syntaxes, |index_and_syntax| {
897 &index_and_syntax.1.name == name
898 }),
899 ),
900 Direct(_) => None,
901 };
902 if let Some(context_id) = linked_context_id {
903 let mut new_ref = Direct(*context_id);
904 mem::swap(context_ref, &mut new_ref);
905 }
906 }
907
908 fn with_plain_text_fallback<'a>(
909 all_context_ids: &'a [HashMap<String, ContextId>],
910 syntaxes: &'a [SyntaxReference],
911 with_escape: bool,
912 context_id: Option<&'a ContextId>,
913 ) -> Option<&'a ContextId> {
914 context_id.or_else(|| {
915 if with_escape {
916 Self::find_id(&None, all_context_ids, syntaxes, |index_and_syntax| {
923 index_and_syntax.1.name == "Plain Text"
924 })
925 } else {
926 None
927 }
928 })
929 }
930
931 fn find_id<'a>(
932 sub_context: &Option<String>,
933 all_context_ids: &'a [HashMap<String, ContextId>],
934 syntaxes: &'a [SyntaxReference],
935 predicate: impl FnMut(&(usize, &SyntaxReference)) -> bool,
936 ) -> Option<&'a ContextId> {
937 let context_name = sub_context.as_ref().map_or("main", |x| &**x);
938 syntaxes
939 .iter()
940 .enumerate()
941 .rev()
942 .find(predicate)
943 .and_then(|index_and_syntax| all_context_ids[index_and_syntax.0].get(context_name))
944 }
945
946 fn link_match_pat(
947 match_pat: &mut MatchPattern,
948 syntax_index: usize,
949 all_context_ids: &[HashMap<String, ContextId>],
950 syntaxes: &[SyntaxReference],
951 ) {
952 let maybe_context_refs = match match_pat.operation {
953 MatchOperation::Push(ref mut context_refs)
954 | MatchOperation::Set(ref mut context_refs) => Some(context_refs),
955 MatchOperation::Pop | MatchOperation::None => None,
956 };
957 if let Some(context_refs) = maybe_context_refs {
958 for context_ref in context_refs.iter_mut() {
959 Self::link_ref(context_ref, syntax_index, all_context_ids, syntaxes);
960 }
961 }
962 if let Some(ref mut context_ref) = match_pat.with_prototype {
963 Self::link_ref(context_ref, syntax_index, all_context_ids, syntaxes);
964 }
965 }
966}
967
968#[derive(Debug)]
969struct FirstLineCache {
970 regexes: Vec<(Regex, usize)>,
972}
973
974impl FirstLineCache {
975 fn new(syntaxes: &[SyntaxReference]) -> FirstLineCache {
976 let mut regexes = Vec::new();
977 for (i, syntax) in syntaxes.iter().enumerate() {
978 if let Some(ref reg_str) = syntax.first_line_match {
979 let reg = Regex::new(reg_str.into());
980 regexes.push((reg, i));
981 }
982 }
983 FirstLineCache { regexes }
984 }
985}
986
987#[cfg(feature = "yaml-load")]
988#[cfg(test)]
989mod tests {
990 use super::*;
991 use crate::{
992 parsing::{syntax_definition, ParseState, Scope},
993 utils::testdata,
994 };
995 use std::collections::HashMap;
996
997 #[test]
998 fn can_load() {
999 let mut builder = testdata::PACKAGES_SYN_SET.to_owned().into_builder();
1000
1001 let cmake_dummy_syntax = SyntaxDefinition {
1002 name: "CMake".to_string(),
1003 file_extensions: vec!["CMakeLists.txt".to_string(), "cmake".to_string()],
1004 scope: Scope::new("source.cmake").unwrap(),
1005 first_line_match: None,
1006 hidden: false,
1007 variables: HashMap::new(),
1008 contexts: HashMap::new(),
1009 };
1010
1011 builder.add(cmake_dummy_syntax);
1012 builder.add_plain_text_syntax();
1013
1014 let ps = builder.build();
1015
1016 assert_eq!(
1017 &ps.find_syntax_by_first_line("#!/usr/bin/env node")
1018 .unwrap()
1019 .name,
1020 "JavaScript"
1021 );
1022 let rails_scope = Scope::new("source.ruby.rails").unwrap();
1023 let syntax = ps.find_syntax_by_name("Ruby on Rails").unwrap();
1024 ps.find_syntax_plain_text();
1025 assert_eq!(&ps.find_syntax_by_extension("rake").unwrap().name, "Ruby");
1026 assert_eq!(&ps.find_syntax_by_extension("RAKE").unwrap().name, "Ruby");
1027 assert_eq!(&ps.find_syntax_by_token("ruby").unwrap().name, "Ruby");
1028 assert_eq!(
1029 &ps.find_syntax_by_first_line("lol -*- Mode: C -*- such line")
1030 .unwrap()
1031 .name,
1032 "C"
1033 );
1034 assert_eq!(
1035 &ps.find_syntax_for_file("testdata/parser.rs")
1036 .unwrap()
1037 .unwrap()
1038 .name,
1039 "Rust"
1040 );
1041 assert_eq!(
1042 &ps.find_syntax_for_file("testdata/test_first_line.test")
1043 .expect("Error finding syntax for file")
1044 .expect("No syntax found for file")
1045 .name,
1046 "Ruby"
1047 );
1048 assert_eq!(
1049 &ps.find_syntax_for_file(".bashrc").unwrap().unwrap().name,
1050 "Bourne Again Shell (bash)"
1051 );
1052 assert_eq!(
1053 &ps.find_syntax_for_file("CMakeLists.txt")
1054 .unwrap()
1055 .unwrap()
1056 .name,
1057 "CMake"
1058 );
1059 assert_eq!(
1060 &ps.find_syntax_for_file("test.cmake").unwrap().unwrap().name,
1061 "CMake"
1062 );
1063 assert_eq!(
1064 &ps.find_syntax_for_file("Rakefile").unwrap().unwrap().name,
1065 "Ruby"
1066 );
1067 assert!(&ps.find_syntax_by_first_line("derp derp hi lol").is_none());
1068 assert_eq!(
1069 &ps.find_syntax_by_path("Packages/Rust/Rust.sublime-syntax")
1070 .unwrap()
1071 .name,
1072 "Rust"
1073 );
1074 assert_eq!(syntax.scope, rails_scope);
1076 let main_context = ps
1078 .get_context(&syntax.context_ids()["main"])
1079 .expect("#[cfg(test)]");
1080 let count = syntax_definition::context_iter(&ps, main_context).count();
1081 assert_eq!(count, 109);
1082 }
1083
1084 #[test]
1085 fn can_clone() {
1086 let cloned_syntax_set = {
1087 let mut builder = SyntaxSetBuilder::new();
1088 builder.add(syntax_a());
1089 builder.add(syntax_b());
1090
1091 let syntax_set_original = builder.build();
1092 #[allow(clippy::redundant_clone)] syntax_set_original.clone()
1094 };
1096
1097 let syntax = cloned_syntax_set.find_syntax_by_extension("a").unwrap();
1098 let mut parse_state = ParseState::new(syntax);
1099 let ops = parse_state
1100 .parse_line("a go_b b", &cloned_syntax_set)
1101 .expect("#[cfg(test)]");
1102 let expected = (7, ScopeStackOp::Push(Scope::new("b").unwrap()));
1103 assert_ops_contain(&ops, &expected);
1104 }
1105
1106 #[test]
1107 fn can_list_added_syntaxes() {
1108 let mut builder = SyntaxSetBuilder::new();
1109 builder.add(syntax_a());
1110 builder.add(syntax_b());
1111 let syntaxes = builder.syntaxes();
1112
1113 assert_eq!(syntaxes.len(), 2);
1114 assert_eq!(syntaxes[0].name, "A");
1115 assert_eq!(syntaxes[1].name, "B");
1116 }
1117
1118 #[test]
1119 fn can_add_more_syntaxes_with_builder() {
1120 let syntax_set_original = {
1121 let mut builder = SyntaxSetBuilder::new();
1122 builder.add(syntax_a());
1123 builder.add(syntax_b());
1124 builder.build()
1125 };
1126
1127 let mut builder = syntax_set_original.into_builder();
1128
1129 let syntax_c = SyntaxDefinition::load_from_str(
1130 r#"
1131 name: C
1132 scope: source.c
1133 file_extensions: [c]
1134 contexts:
1135 main:
1136 - match: 'c'
1137 scope: c
1138 - match: 'go_a'
1139 push: scope:source.a#main
1140 "#,
1141 true,
1142 None,
1143 )
1144 .unwrap();
1145
1146 builder.add(syntax_c);
1147
1148 let syntax_set = builder.build();
1149
1150 let syntax = syntax_set.find_syntax_by_extension("c").unwrap();
1151 let mut parse_state = ParseState::new(syntax);
1152 let ops = parse_state
1153 .parse_line("c go_a a go_b b", &syntax_set)
1154 .expect("#[cfg(test)]");
1155 let expected = (14, ScopeStackOp::Push(Scope::new("b").unwrap()));
1156 assert_ops_contain(&ops, &expected);
1157 }
1158
1159 #[test]
1160 fn falls_back_to_plain_text_when_embedded_scope_is_missing() {
1161 test_plain_text_fallback(
1162 r#"
1163 name: Z
1164 scope: source.z
1165 file_extensions: [z]
1166 contexts:
1167 main:
1168 - match: 'z'
1169 scope: z
1170 - match: 'go_x'
1171 embed: scope:does.not.exist
1172 escape: 'leave_x'
1173 "#,
1174 );
1175 }
1176
1177 #[test]
1178 fn falls_back_to_plain_text_when_embedded_file_is_missing() {
1179 test_plain_text_fallback(
1180 r#"
1181 name: Z
1182 scope: source.z
1183 file_extensions: [z]
1184 contexts:
1185 main:
1186 - match: 'z'
1187 scope: z
1188 - match: 'go_x'
1189 embed: DoesNotExist.sublime-syntax
1190 escape: 'leave_x'
1191 "#,
1192 );
1193 }
1194
1195 fn test_plain_text_fallback(syntax_definition: &str) {
1196 let syntax = SyntaxDefinition::load_from_str(syntax_definition, true, None).unwrap();
1197
1198 let mut builder = SyntaxSetBuilder::new();
1199 builder.add_plain_text_syntax();
1200 builder.add(syntax);
1201 let syntax_set = builder.build();
1202
1203 let syntax = syntax_set.find_syntax_by_extension("z").unwrap();
1204 let mut parse_state = ParseState::new(syntax);
1205 let ops = parse_state
1206 .parse_line("z go_x x leave_x z", &syntax_set)
1207 .unwrap();
1208 let expected_ops = vec![
1209 (0, ScopeStackOp::Push(Scope::new("source.z").unwrap())),
1210 (0, ScopeStackOp::Push(Scope::new("z").unwrap())),
1211 (1, ScopeStackOp::Pop(1)),
1212 (6, ScopeStackOp::Push(Scope::new("text.plain").unwrap())),
1213 (9, ScopeStackOp::Pop(1)),
1214 (17, ScopeStackOp::Push(Scope::new("z").unwrap())),
1215 (18, ScopeStackOp::Pop(1)),
1216 ];
1217 assert_eq!(ops, expected_ops);
1218 }
1219
1220 #[test]
1221 fn can_find_unlinked_contexts() {
1222 let syntax_set = {
1223 let mut builder = SyntaxSetBuilder::new();
1224 builder.add(syntax_a());
1225 builder.add(syntax_b());
1226 builder.build()
1227 };
1228
1229 let unlinked_contexts = syntax_set.find_unlinked_contexts();
1230 assert_eq!(unlinked_contexts.len(), 0);
1231
1232 let syntax_set = {
1233 let mut builder = SyntaxSetBuilder::new();
1234 builder.add(syntax_a());
1235 builder.build()
1236 };
1237
1238 let unlinked_contexts: Vec<String> =
1239 syntax_set.find_unlinked_contexts().into_iter().collect();
1240 assert_eq!(unlinked_contexts.len(), 1);
1241 assert_eq!(unlinked_contexts[0], "Syntax 'A' with scope 'source.a' has unresolved context reference ByScope { scope: <source.b>, sub_context: Some(\"main\"), with_escape: false }");
1242 }
1243
1244 #[test]
1245 fn can_use_in_multiple_threads() {
1246 use rayon::prelude::*;
1247
1248 let syntax_set = {
1249 let mut builder = SyntaxSetBuilder::new();
1250 builder.add(syntax_a());
1251 builder.add(syntax_b());
1252 builder.build()
1253 };
1254
1255 let lines = vec!["a a a", "a go_b b", "go_b b", "go_b b b"];
1256
1257 let results: Vec<Vec<(usize, ScopeStackOp)>> = lines
1258 .par_iter()
1259 .map(|line| {
1260 let syntax = syntax_set.find_syntax_by_extension("a").unwrap();
1261 let mut parse_state = ParseState::new(syntax);
1262 parse_state
1263 .parse_line(line, &syntax_set)
1264 .expect("#[cfg(test)]")
1265 })
1266 .collect();
1267
1268 assert_ops_contain(
1269 &results[0],
1270 &(4, ScopeStackOp::Push(Scope::new("a").unwrap())),
1271 );
1272 assert_ops_contain(
1273 &results[1],
1274 &(7, ScopeStackOp::Push(Scope::new("b").unwrap())),
1275 );
1276 assert_ops_contain(
1277 &results[2],
1278 &(5, ScopeStackOp::Push(Scope::new("b").unwrap())),
1279 );
1280 assert_ops_contain(
1281 &results[3],
1282 &(8, ScopeStackOp::Push(Scope::new("b").unwrap())),
1283 );
1284 }
1285
1286 #[test]
1287 fn is_sync() {
1288 check_sync::<SyntaxSet>();
1289 }
1290
1291 #[test]
1292 fn is_send() {
1293 check_send::<SyntaxSet>();
1294 }
1295
1296 #[test]
1297 fn can_override_syntaxes() {
1298 let syntax_set = {
1299 let mut builder = SyntaxSetBuilder::new();
1300 builder.add(syntax_a());
1301 builder.add(syntax_b());
1302
1303 let syntax_a2 = SyntaxDefinition::load_from_str(
1304 r#"
1305 name: A improved
1306 scope: source.a
1307 file_extensions: [a]
1308 first_line_match: syntax\s+a
1309 contexts:
1310 main:
1311 - match: a
1312 scope: a2
1313 - match: go_b
1314 push: scope:source.b#main
1315 "#,
1316 true,
1317 None,
1318 )
1319 .unwrap();
1320
1321 builder.add(syntax_a2);
1322
1323 let syntax_c = SyntaxDefinition::load_from_str(
1324 r#"
1325 name: C
1326 scope: source.c
1327 file_extensions: [c]
1328 first_line_match: syntax\s+.*
1329 contexts:
1330 main:
1331 - match: c
1332 scope: c
1333 - match: go_a
1334 push: scope:source.a#main
1335 "#,
1336 true,
1337 None,
1338 )
1339 .unwrap();
1340
1341 builder.add(syntax_c);
1342
1343 builder.build()
1344 };
1345
1346 let mut syntax = syntax_set.find_syntax_by_extension("a").unwrap();
1347 assert_eq!(syntax.name, "A improved");
1348 syntax = syntax_set
1349 .find_syntax_by_scope(Scope::new("source.a").unwrap())
1350 .unwrap();
1351 assert_eq!(syntax.name, "A improved");
1352 syntax = syntax_set.find_syntax_by_first_line("syntax a").unwrap();
1353 assert_eq!(syntax.name, "C");
1354
1355 let mut parse_state = ParseState::new(syntax);
1356 let ops = parse_state
1357 .parse_line("c go_a a", &syntax_set)
1358 .expect("msg");
1359 let expected = (7, ScopeStackOp::Push(Scope::new("a2").unwrap()));
1360 assert_ops_contain(&ops, &expected);
1361 }
1362
1363 #[test]
1364 fn can_parse_issue219() {
1365 let syntax_set = SyntaxSet::load_defaults_newlines().into_builder().build();
1370 let syntax = syntax_set.find_syntax_by_extension("yaml").unwrap();
1371
1372 let mut parse_state = ParseState::new(syntax);
1373 let ops = parse_state
1374 .parse_line("# test\n", &syntax_set)
1375 .expect("#[cfg(test)]");
1376 let expected = (
1377 0,
1378 ScopeStackOp::Push(Scope::new("comment.line.number-sign.yaml").unwrap()),
1379 );
1380 assert_ops_contain(&ops, &expected);
1381 }
1382
1383 #[test]
1384 fn no_prototype_for_contexts_included_from_prototype() {
1385 let mut builder = SyntaxSetBuilder::new();
1386 let syntax = SyntaxDefinition::load_from_str(
1387 r#"
1388 name: Test Prototype
1389 scope: source.test
1390 file_extensions: [test]
1391 contexts:
1392 prototype:
1393 - include: included_from_prototype
1394 main:
1395 - match: main
1396 - match: other
1397 push: other
1398 other:
1399 - match: o
1400 included_from_prototype:
1401 - match: p
1402 scope: p
1403 "#,
1404 true,
1405 None,
1406 )
1407 .unwrap();
1408 builder.add(syntax);
1409 let ss = builder.build();
1410
1411 assert_prototype_only_on(&["main", "other"], &ss, &ss.syntaxes()[0]);
1414
1415 let rebuilt = ss.into_builder().build();
1419 assert_prototype_only_on(&["main", "other"], &rebuilt, &rebuilt.syntaxes()[0]);
1420 }
1421
1422 #[test]
1423 fn no_prototype_for_contexts_inline_in_prototype() {
1424 let mut builder = SyntaxSetBuilder::new();
1425 let syntax = SyntaxDefinition::load_from_str(
1426 r#"
1427 name: Test Prototype
1428 scope: source.test
1429 file_extensions: [test]
1430 contexts:
1431 prototype:
1432 - match: p
1433 push:
1434 - match: p2
1435 main:
1436 - match: main
1437 "#,
1438 true,
1439 None,
1440 )
1441 .unwrap();
1442 builder.add(syntax);
1443 let ss = builder.build();
1444
1445 assert_prototype_only_on(&["main"], &ss, &ss.syntaxes()[0]);
1446
1447 let rebuilt = ss.into_builder().build();
1448 assert_prototype_only_on(&["main"], &rebuilt, &rebuilt.syntaxes()[0]);
1449 }
1450
1451 #[test]
1452 fn find_syntax_set_from_line_with_bom() {
1453 let syntax_set = SyntaxSet::load_defaults_newlines();
1455 let syntax_ref = syntax_set
1456 .find_syntax_by_first_line("\u{feff}<?xml version=\"1.0\"?>")
1457 .unwrap();
1458 assert_eq!(syntax_ref.name, "XML");
1459 }
1460
1461 fn assert_ops_contain(ops: &[(usize, ScopeStackOp)], expected: &(usize, ScopeStackOp)) {
1462 assert!(
1463 ops.contains(expected),
1464 "expected operations to contain {:?}: {:?}",
1465 expected,
1466 ops
1467 );
1468 }
1469
1470 fn assert_prototype_only_on(
1471 expected: &[&str],
1472 syntax_set: &SyntaxSet,
1473 syntax: &SyntaxReference,
1474 ) {
1475 for (name, id) in syntax.context_ids() {
1476 if name == "__main" || name == "__start" {
1477 continue;
1479 }
1480 let context = syntax_set.get_context(id).expect("#[cfg(test)]");
1481 if expected.contains(&name.as_str()) {
1482 assert!(
1483 context.prototype.is_some(),
1484 "Expected context {} to have prototype",
1485 name
1486 );
1487 } else {
1488 assert!(
1489 context.prototype.is_none(),
1490 "Expected context {} to not have prototype",
1491 name
1492 );
1493 }
1494 }
1495 }
1496
1497 fn check_send<T: Send>() {}
1498
1499 fn check_sync<T: Sync>() {}
1500
1501 fn syntax_a() -> SyntaxDefinition {
1502 SyntaxDefinition::load_from_str(
1503 r#"
1504 name: A
1505 scope: source.a
1506 file_extensions: [a]
1507 contexts:
1508 main:
1509 - match: 'a'
1510 scope: a
1511 - match: 'go_b'
1512 push: scope:source.b#main
1513 "#,
1514 true,
1515 None,
1516 )
1517 .unwrap()
1518 }
1519
1520 fn syntax_b() -> SyntaxDefinition {
1521 SyntaxDefinition::load_from_str(
1522 r#"
1523 name: B
1524 scope: source.b
1525 file_extensions: [b]
1526 contexts:
1527 main:
1528 - match: 'b'
1529 scope: b
1530 "#,
1531 true,
1532 None,
1533 )
1534 .unwrap()
1535 }
1536}