1use super::*;
16use std::{borrow::Cow, fmt, ops::Range};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum HighlightError {
25 GrammarLoad {
27 language: Language,
29 message: String,
31 },
32 Query {
34 language: Language,
36 row: usize,
38 column: usize,
40 offset: usize,
42 kind: HighlightQueryErrorKind,
44 message: String,
46 },
47 Parse {
49 language: Language,
51 },
52 InvalidEdit {
57 start_byte: usize,
59 old_end_byte: usize,
61 new_end_byte: usize,
63 old_len: usize,
65 new_len: usize,
67 },
68}
69
70impl HighlightError {
71 #[cfg(feature = "runtime")]
72 fn grammar_load(language: Language, error: arborium_tree_sitter::LanguageError) -> Self {
73 Self::GrammarLoad {
74 language,
75 message: error.to_string(),
76 }
77 }
78
79 #[cfg(feature = "runtime")]
80 fn query(language: Language, error: arborium_tree_sitter::QueryError) -> Self {
81 Self::Query {
82 language,
83 row: error.row,
84 column: error.column,
85 offset: error.offset,
86 kind: error.kind.into(),
87 message: error.message,
88 }
89 }
90}
91
92impl fmt::Display for HighlightError {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 Self::GrammarLoad { language, message } => {
96 write!(
97 f,
98 "failed to load grammar for {}: {}",
99 language.slug(),
100 message
101 )
102 }
103 Self::Query {
104 language,
105 row,
106 column,
107 kind,
108 message,
109 ..
110 } => {
111 write!(
112 f,
113 "query error for {} at {}:{} ({}): {}",
114 language.slug(),
115 row + 1,
116 column + 1,
117 kind,
118 message
119 )
120 }
121 Self::Parse { language } => {
122 write!(f, "tree-sitter parse failed for {}", language.slug())
123 }
124 Self::InvalidEdit {
125 start_byte,
126 old_end_byte,
127 new_end_byte,
128 old_len,
129 new_len,
130 } => {
131 write!(
132 f,
133 "invalid edit: start_byte {start_byte}, old_end_byte {old_end_byte}, \
134 new_end_byte {new_end_byte} (old_len {old_len}, new_len {new_len})"
135 )
136 }
137 }
138 }
139}
140
141impl std::error::Error for HighlightError {}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145#[non_exhaustive]
146pub enum HighlightQueryErrorKind {
147 Syntax,
149 NodeType,
151 Field,
153 Capture,
155 Predicate,
157 Structure,
159 Language,
161}
162
163impl fmt::Display for HighlightQueryErrorKind {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 let kind = match self {
166 Self::Syntax => "syntax",
167 Self::NodeType => "node type",
168 Self::Field => "field",
169 Self::Capture => "capture",
170 Self::Predicate => "predicate",
171 Self::Structure => "structure",
172 Self::Language => "language",
173 };
174 f.write_str(kind)
175 }
176}
177
178#[cfg(feature = "runtime")]
179impl From<arborium_tree_sitter::QueryErrorKind> for HighlightQueryErrorKind {
180 fn from(kind: arborium_tree_sitter::QueryErrorKind) -> Self {
181 match kind {
182 arborium_tree_sitter::QueryErrorKind::Syntax => Self::Syntax,
183 arborium_tree_sitter::QueryErrorKind::NodeType => Self::NodeType,
184 arborium_tree_sitter::QueryErrorKind::Field => Self::Field,
185 arborium_tree_sitter::QueryErrorKind::Capture => Self::Capture,
186 arborium_tree_sitter::QueryErrorKind::Predicate => Self::Predicate,
187 arborium_tree_sitter::QueryErrorKind::Structure => Self::Structure,
188 arborium_tree_sitter::QueryErrorKind::Language => Self::Language,
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct HighlightedSource {
203 source: Cow<'static, str>,
204 language: Language,
205 spans: Cow<'static, [HighlightSpan]>,
206}
207
208impl HighlightedSource {
209 #[cfg(feature = "runtime")]
210 pub(crate) fn from_owned_parts(
211 source: String,
212 language: Language,
213 spans: Vec<HighlightSpan>,
214 ) -> Self {
215 Self {
216 source: Cow::Owned(source),
217 language,
218 spans: Cow::Owned(spans),
219 }
220 }
221
222 pub const fn from_static_parts(
233 source: &'static str,
234 language: Language,
235 spans: &'static [HighlightSpan],
236 ) -> Self {
237 Self {
238 source: Cow::Borrowed(source),
239 language,
240 spans: Cow::Borrowed(spans),
241 }
242 }
243
244 #[cfg(feature = "runtime")]
245 pub(crate) fn plaintext(source: impl Into<Cow<'static, str>>, language: Language) -> Self {
246 Self {
247 source: source.into(),
248 language,
249 spans: Cow::Borrowed(&[]),
250 }
251 }
252
253 pub fn source(&self) -> &str {
262 self.source.as_ref()
263 }
264
265 pub const fn language(&self) -> Language {
274 self.language
275 }
276
277 pub fn spans(&self) -> &[HighlightSpan] {
286 self.spans.as_ref()
287 }
288
289 pub fn segments(&self) -> Vec<HighlightSegment<'_>> {
298 highlighted_segments(self.source(), self.spans())
299 }
300
301 pub(crate) fn trimmed_segments(&self) -> Vec<HighlightSegment<'_>> {
302 highlighted_segments(self.source().trim_end_matches('\n'), self.spans())
303 }
304
305 pub fn lines(&self) -> Vec<Vec<HighlightSegment<'_>>> {
317 highlighted_lines(self.source(), self.spans())
318 }
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub struct HighlightSpan {
330 start: u32,
331 end: u32,
332 tag: &'static str,
333}
334
335impl HighlightSpan {
336 pub const fn new(range: Range<u32>, tag: &'static str) -> Self {
344 Self {
345 start: range.start,
346 end: range.end,
347 tag,
348 }
349 }
350
351 pub const fn from_offsets(start: u32, end: u32, tag: &'static str) -> Self {
359 Self { start, end, tag }
360 }
361
362 pub const fn start(self) -> u32 {
369 self.start
370 }
371
372 pub const fn end(self) -> u32 {
379 self.end
380 }
381
382 pub const fn range(self) -> Range<u32> {
389 self.start..self.end
390 }
391
392 pub const fn tag(self) -> &'static str {
399 self.tag
400 }
401
402 #[cfg(feature = "runtime")]
403 pub(crate) fn set_end(&mut self, end: u32) {
404 self.end = end;
405 }
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416pub struct HighlightSegment<'a> {
417 text: &'a str,
418 tag: Option<&'static str>,
419}
420
421impl<'a> HighlightSegment<'a> {
422 pub const fn new(text: &'a str, tag: Option<&'static str>) -> Self {
429 Self { text, tag }
430 }
431
432 pub const fn text(self) -> &'a str {
439 self.text
440 }
441
442 pub const fn tag(self) -> Option<&'static str> {
449 self.tag
450 }
451}
452
453fn highlighted_segments<'a>(source: &'a str, spans: &[HighlightSpan]) -> Vec<HighlightSegment<'a>> {
454 if spans.is_empty() {
455 return vec![HighlightSegment::new(source, None)];
456 }
457
458 let mut spans = spans.to_vec();
459 spans.sort_by(|a, b| a.start.cmp(&b.start).then_with(|| b.end.cmp(&a.end)));
460
461 let mut events = Vec::with_capacity(spans.len() * 2);
462 for (index, span) in spans.iter().enumerate() {
463 events.push((span.start, true, index));
464 events.push((span.end, false, index));
465 }
466 events.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
467
468 let mut segments = Vec::new();
469 let mut last_pos = 0;
470 let mut stack: Vec<usize> = Vec::new();
471
472 for (pos, is_start, span_index) in events {
473 let pos = pos as usize;
474 if pos > last_pos && pos <= source.len() {
475 segments.push(HighlightSegment::new(
476 &source[last_pos..pos],
477 stack.last().map(|&i| spans[i].tag),
478 ));
479 last_pos = pos;
480 }
481
482 if is_start {
483 stack.push(span_index);
484 } else if let Some(index) = stack.iter().rposition(|&i| i == span_index) {
485 stack.remove(index);
486 }
487 }
488
489 if last_pos < source.len() {
490 segments.push(HighlightSegment::new(
491 &source[last_pos..],
492 stack.last().map(|&i| spans[i].tag),
493 ));
494 }
495
496 segments
497}
498
499fn highlighted_lines<'a>(
500 source: &'a str,
501 spans: &[HighlightSpan],
502) -> Vec<Vec<HighlightSegment<'a>>> {
503 let mut lines = vec![Vec::new()];
504
505 for segment in highlighted_segments(source, spans) {
506 push_line_segments(&mut lines, segment);
507 }
508
509 lines
510}
511
512fn push_line_segments<'a>(
513 lines: &mut Vec<Vec<HighlightSegment<'a>>>,
514 segment: HighlightSegment<'a>,
515) {
516 let mut text = segment.text;
517
518 loop {
519 if let Some(newline) = text.find('\n') {
520 let before_newline = &text[..newline];
521 if !before_newline.is_empty() {
522 lines
523 .last_mut()
524 .unwrap()
525 .push(HighlightSegment::new(before_newline, segment.tag));
526 }
527 lines.push(Vec::new());
528 text = &text[newline + 1..];
529 } else {
530 if !text.is_empty() {
531 lines
532 .last_mut()
533 .unwrap()
534 .push(HighlightSegment::new(text, segment.tag));
535 }
536 break;
537 }
538 }
539}
540
541#[derive(Props, Clone, PartialEq)]
548pub struct TokenSpanProps {
549 #[props(into)]
551 pub text: String,
552 pub tag: &'static str,
554}
555
556#[component]
567pub fn TokenSpan(props: TokenSpanProps) -> Element {
568 let class = format!("a-{}", props.tag);
569 rsx! {
570 span {
571 class,
572 "{props.text}"
573 }
574 }
575}
576
577#[cfg(feature = "runtime")]
598#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
599pub struct Buffer {
600 parser: arborium_tree_sitter::Parser,
601 cursor: arborium_tree_sitter::QueryCursor,
602 language: Language,
603 incremental: IncrementalGrammar,
604 tree: arborium_tree_sitter::Tree,
605 source: String,
606 spans: Vec<HighlightSpan>,
607}
608
609#[cfg(feature = "runtime")]
610struct IncrementalGrammar {
611 query: arborium_tree_sitter::Query,
612}
613
614#[cfg(feature = "runtime")]
615#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
616impl Buffer {
617 pub fn new(language: Language, source: impl ToString) -> Result<Self, HighlightError> {
626 let source = source.to_string();
627 let (mut parser, incremental) = Self::parser_for(language)?;
628 let mut cursor = arborium_tree_sitter::QueryCursor::new();
629 let (tree, spans) = Self::parse_source(
630 language,
631 &mut parser,
632 &incremental.query,
633 &mut cursor,
634 &source,
635 None,
636 )?;
637
638 Ok(Self {
639 parser,
640 cursor,
641 language,
642 incremental,
643 tree,
644 source,
645 spans,
646 })
647 }
648
649 pub fn replace(&mut self, source: impl ToString) -> Result<(), HighlightError> {
663 let source = source.to_string();
664 let (tree, spans) = Self::parse_source(
665 self.language,
666 &mut self.parser,
667 &self.incremental.query,
668 &mut self.cursor,
669 &source,
670 None,
671 )?;
672
673 self.source = source;
674 self.tree = tree;
675 self.spans = spans;
676 Ok(())
677 }
678
679 pub fn edit(
700 &mut self,
701 edit: SourceEdit,
702 new_source: impl ToString,
703 ) -> Result<(), HighlightError> {
704 let new_source: String = new_source.to_string();
705 let input_edit = edit.into_input_edit(&self.source, &new_source)?;
706
707 let mut old_tree = self.tree.clone();
708 old_tree.edit(&input_edit);
709 let (tree, spans) = Self::parse_source(
710 self.language,
711 &mut self.parser,
712 &self.incremental.query,
713 &mut self.cursor,
714 &new_source,
715 Some(&old_tree),
716 )?;
717
718 self.source = new_source;
719 self.tree = tree;
720 self.spans = spans;
721 Ok(())
722 }
723
724 pub fn set_language(&mut self, language: Language) -> Result<(), HighlightError> {
736 if self.language == language {
737 return Ok(());
738 }
739 let (mut parser, incremental) = Self::parser_for(language)?;
740 let (tree, spans) = Self::parse_source(
741 language,
742 &mut parser,
743 &incremental.query,
744 &mut self.cursor,
745 &self.source,
746 None,
747 )?;
748
749 self.parser = parser;
750 self.incremental = incremental;
751 self.language = language;
752 self.tree = tree;
753 self.spans = spans;
754 Ok(())
755 }
756
757 pub fn source(&self) -> &str {
759 &self.source
760 }
761
762 pub const fn language(&self) -> Language {
764 self.language
765 }
766
767 pub fn spans(&self) -> &[HighlightSpan] {
769 &self.spans
770 }
771
772 pub fn segments(&self) -> Vec<HighlightSegment<'_>> {
774 highlighted_segments(&self.source, &self.spans)
775 }
776
777 pub fn lines(&self) -> Vec<Vec<HighlightSegment<'_>>> {
779 highlighted_lines(&self.source, &self.spans)
780 }
781
782 pub fn highlighted(&self) -> HighlightedSource {
787 HighlightedSource::from_owned_parts(self.source.clone(), self.language, self.spans.clone())
788 }
789
790 fn parser_for(
791 language: Language,
792 ) -> Result<(arborium_tree_sitter::Parser, IncrementalGrammar), HighlightError> {
793 let mut parser = arborium_tree_sitter::Parser::new();
794 let (language_fn, highlights_query) = grammar_for(language);
795 let ts_language: arborium_tree_sitter::Language = language_fn.into();
796 if let Err(error) = parser.set_language(&ts_language) {
797 return Err(HighlightError::grammar_load(language, error));
798 }
799
800 match arborium_tree_sitter::Query::new(&ts_language, highlights_query) {
801 Ok(query) => Ok((parser, IncrementalGrammar { query })),
802 Err(error) => Err(HighlightError::query(language, error)),
803 }
804 }
805
806 fn parse_source(
807 language: Language,
808 parser: &mut arborium_tree_sitter::Parser,
809 query: &arborium_tree_sitter::Query,
810 cursor: &mut arborium_tree_sitter::QueryCursor,
811 source: &str,
812 old_tree: Option<&arborium_tree_sitter::Tree>,
813 ) -> Result<(arborium_tree_sitter::Tree, Vec<HighlightSpan>), HighlightError> {
814 match parser.parse(source, old_tree) {
815 Some(tree) => {
816 let spans = collect_spans(query, cursor, &tree, source);
817 Ok((tree, spans))
818 }
819 None => Err(HighlightError::Parse { language }),
820 }
821 }
822}
823
824#[cfg(feature = "runtime")]
825fn collect_spans(
826 query: &arborium_tree_sitter::Query,
827 cursor: &mut arborium_tree_sitter::QueryCursor,
828 tree: &arborium_tree_sitter::Tree,
829 source: &str,
830) -> Vec<HighlightSpan> {
831 use arborium_tree_sitter::StreamingIterator;
832
833 let bytes = source.as_bytes();
834 let capture_names = query.capture_names();
835 let mut raw: Vec<RawHighlightSpan> = Vec::new();
836
837 let mut matches = cursor.matches(query, tree.root_node(), bytes);
838 while let Some(m) = matches.next() {
839 for capture in m.captures {
840 let name = capture_names[capture.index as usize];
841 if name.starts_with('_') || name.starts_with("injection.") {
842 continue;
843 }
844 raw.push(RawHighlightSpan {
845 start: capture.node.start_byte() as u32,
846 end: capture.node.end_byte() as u32,
847 tag: arborium_theme::tag_for_capture(name),
848 pattern_index: m.pattern_index as u32,
849 });
850 }
851 }
852
853 normalize_spans(raw)
854}
855
856#[cfg(feature = "runtime")]
857fn grammar_for(language: Language) -> (arborium_tree_sitter::LanguageFn, &'static str) {
858 match language {
861 Language::Rust => (
862 arborium::lang_rust::language(),
863 arborium::lang_rust::HIGHLIGHTS_QUERY,
864 ),
865 #[cfg(feature = "lang-ada")]
866 Language::Ada => (
867 arborium::lang_ada::language(),
868 arborium::lang_ada::HIGHLIGHTS_QUERY,
869 ),
870 #[cfg(feature = "lang-agda")]
871 Language::Agda => (
872 arborium::lang_agda::language(),
873 arborium::lang_agda::HIGHLIGHTS_QUERY,
874 ),
875 #[cfg(feature = "lang-asciidoc")]
876 Language::Asciidoc => (
877 arborium::lang_asciidoc::language(),
878 arborium::lang_asciidoc::HIGHLIGHTS_QUERY,
879 ),
880 #[cfg(feature = "lang-asm")]
881 Language::Asm => (
882 arborium::lang_asm::language(),
883 arborium::lang_asm::HIGHLIGHTS_QUERY,
884 ),
885 #[cfg(feature = "lang-awk")]
886 Language::Awk => (
887 arborium::lang_awk::language(),
888 arborium::lang_awk::HIGHLIGHTS_QUERY,
889 ),
890 #[cfg(feature = "lang-bash")]
891 Language::Bash => (
892 arborium::lang_bash::language(),
893 arborium::lang_bash::HIGHLIGHTS_QUERY,
894 ),
895 #[cfg(feature = "lang-batch")]
896 Language::Batch => (
897 arborium::lang_batch::language(),
898 arborium::lang_batch::HIGHLIGHTS_QUERY,
899 ),
900 #[cfg(feature = "lang-c")]
901 Language::C => (
902 arborium::lang_c::language(),
903 arborium::lang_c::HIGHLIGHTS_QUERY,
904 ),
905 #[cfg(feature = "lang-c-sharp")]
906 Language::CSharp => (
907 arborium::lang_c_sharp::language(),
908 arborium::lang_c_sharp::HIGHLIGHTS_QUERY,
909 ),
910 #[cfg(feature = "lang-caddy")]
911 Language::Caddy => (
912 arborium::lang_caddy::language(),
913 arborium::lang_caddy::HIGHLIGHTS_QUERY,
914 ),
915 #[cfg(feature = "lang-capnp")]
916 Language::Capnp => (
917 arborium::lang_capnp::language(),
918 arborium::lang_capnp::HIGHLIGHTS_QUERY,
919 ),
920 #[cfg(feature = "lang-cedar")]
921 Language::Cedar => (
922 arborium::lang_cedar::language(),
923 arborium::lang_cedar::HIGHLIGHTS_QUERY,
924 ),
925 #[cfg(feature = "lang-cedarschema")]
926 Language::CedarSchema => (
927 arborium::lang_cedarschema::language(),
928 arborium::lang_cedarschema::HIGHLIGHTS_QUERY,
929 ),
930 #[cfg(feature = "lang-clojure")]
931 Language::Clojure => (
932 arborium::lang_clojure::language(),
933 arborium::lang_clojure::HIGHLIGHTS_QUERY,
934 ),
935 #[cfg(feature = "lang-cmake")]
936 Language::CMake => (
937 arborium::lang_cmake::language(),
938 arborium::lang_cmake::HIGHLIGHTS_QUERY,
939 ),
940 #[cfg(feature = "lang-cobol")]
941 Language::Cobol => (
942 arborium::lang_cobol::language(),
943 arborium::lang_cobol::HIGHLIGHTS_QUERY,
944 ),
945 #[cfg(feature = "lang-commonlisp")]
946 Language::CommonLisp => (
947 arborium::lang_commonlisp::language(),
948 arborium::lang_commonlisp::HIGHLIGHTS_QUERY,
949 ),
950 #[cfg(feature = "lang-cpp")]
951 Language::Cpp => (
952 arborium::lang_cpp::language(),
953 &arborium::lang_cpp::HIGHLIGHTS_QUERY,
954 ),
955 #[cfg(feature = "lang-css")]
956 Language::Css => (
957 arborium::lang_css::language(),
958 arborium::lang_css::HIGHLIGHTS_QUERY,
959 ),
960 #[cfg(feature = "lang-d")]
961 Language::D => (
962 arborium::lang_d::language(),
963 arborium::lang_d::HIGHLIGHTS_QUERY,
964 ),
965 #[cfg(feature = "lang-dart")]
966 Language::Dart => (
967 arborium::lang_dart::language(),
968 arborium::lang_dart::HIGHLIGHTS_QUERY,
969 ),
970 #[cfg(feature = "lang-devicetree")]
971 Language::DeviceTree => (
972 arborium::lang_devicetree::language(),
973 arborium::lang_devicetree::HIGHLIGHTS_QUERY,
974 ),
975 #[cfg(feature = "lang-diff")]
976 Language::Diff => (
977 arborium::lang_diff::language(),
978 arborium::lang_diff::HIGHLIGHTS_QUERY,
979 ),
980 #[cfg(feature = "lang-dockerfile")]
981 Language::Dockerfile => (
982 arborium::lang_dockerfile::language(),
983 arborium::lang_dockerfile::HIGHLIGHTS_QUERY,
984 ),
985 #[cfg(feature = "lang-dot")]
986 Language::Dot => (
987 arborium::lang_dot::language(),
988 arborium::lang_dot::HIGHLIGHTS_QUERY,
989 ),
990 #[cfg(feature = "lang-elisp")]
991 Language::Elisp => (
992 arborium::lang_elisp::language(),
993 arborium::lang_elisp::HIGHLIGHTS_QUERY,
994 ),
995 #[cfg(feature = "lang-elixir")]
996 Language::Elixir => (
997 arborium::lang_elixir::language(),
998 arborium::lang_elixir::HIGHLIGHTS_QUERY,
999 ),
1000 #[cfg(feature = "lang-elm")]
1001 Language::Elm => (
1002 arborium::lang_elm::language(),
1003 arborium::lang_elm::HIGHLIGHTS_QUERY,
1004 ),
1005 #[cfg(feature = "lang-erlang")]
1006 Language::Erlang => (
1007 arborium::lang_erlang::language(),
1008 arborium::lang_erlang::HIGHLIGHTS_QUERY,
1009 ),
1010 #[cfg(feature = "lang-fish")]
1011 Language::Fish => (
1012 arborium::lang_fish::language(),
1013 arborium::lang_fish::HIGHLIGHTS_QUERY,
1014 ),
1015 #[cfg(feature = "lang-fsharp")]
1016 Language::FSharp => (
1017 arborium::lang_fsharp::language(),
1018 arborium::lang_fsharp::HIGHLIGHTS_QUERY,
1019 ),
1020 #[cfg(feature = "lang-gleam")]
1021 Language::Gleam => (
1022 arborium::lang_gleam::language(),
1023 arborium::lang_gleam::HIGHLIGHTS_QUERY,
1024 ),
1025 #[cfg(feature = "lang-glsl")]
1026 Language::Glsl => (
1027 arborium::lang_glsl::language(),
1028 &arborium::lang_glsl::HIGHLIGHTS_QUERY,
1029 ),
1030 #[cfg(feature = "lang-go")]
1031 Language::Go => (
1032 arborium::lang_go::language(),
1033 arborium::lang_go::HIGHLIGHTS_QUERY,
1034 ),
1035 #[cfg(feature = "lang-graphql")]
1036 Language::GraphQL => (
1037 arborium::lang_graphql::language(),
1038 arborium::lang_graphql::HIGHLIGHTS_QUERY,
1039 ),
1040 #[cfg(feature = "lang-groovy")]
1041 Language::Groovy => (
1042 arborium::lang_groovy::language(),
1043 arborium::lang_groovy::HIGHLIGHTS_QUERY,
1044 ),
1045 #[cfg(feature = "lang-haskell")]
1046 Language::Haskell => (
1047 arborium::lang_haskell::language(),
1048 arborium::lang_haskell::HIGHLIGHTS_QUERY,
1049 ),
1050 #[cfg(feature = "lang-hcl")]
1051 Language::Hcl => (
1052 arborium::lang_hcl::language(),
1053 arborium::lang_hcl::HIGHLIGHTS_QUERY,
1054 ),
1055 #[cfg(feature = "lang-hlsl")]
1056 Language::Hlsl => (
1057 arborium::lang_hlsl::language(),
1058 &arborium::lang_hlsl::HIGHLIGHTS_QUERY,
1059 ),
1060 #[cfg(feature = "lang-html")]
1061 Language::Html => (
1062 arborium::lang_html::language(),
1063 arborium::lang_html::HIGHLIGHTS_QUERY,
1064 ),
1065 #[cfg(feature = "lang-idris")]
1066 Language::Idris => (
1067 arborium::lang_idris::language(),
1068 arborium::lang_idris::HIGHLIGHTS_QUERY,
1069 ),
1070 #[cfg(feature = "lang-ini")]
1071 Language::Ini => (
1072 arborium::lang_ini::language(),
1073 arborium::lang_ini::HIGHLIGHTS_QUERY,
1074 ),
1075 #[cfg(feature = "lang-java")]
1076 Language::Java => (
1077 arborium::lang_java::language(),
1078 arborium::lang_java::HIGHLIGHTS_QUERY,
1079 ),
1080 #[cfg(feature = "lang-javascript")]
1081 Language::JavaScript => (
1082 arborium::lang_javascript::language(),
1083 arborium::lang_javascript::HIGHLIGHTS_QUERY,
1084 ),
1085 #[cfg(feature = "lang-jinja2")]
1086 Language::Jinja2 => (
1087 arborium::lang_jinja2::language(),
1088 arborium::lang_jinja2::HIGHLIGHTS_QUERY,
1089 ),
1090 #[cfg(feature = "lang-jq")]
1091 Language::Jq => (
1092 arborium::lang_jq::language(),
1093 arborium::lang_jq::HIGHLIGHTS_QUERY,
1094 ),
1095 #[cfg(feature = "lang-json")]
1096 Language::Json => (
1097 arborium::lang_json::language(),
1098 arborium::lang_json::HIGHLIGHTS_QUERY,
1099 ),
1100 #[cfg(feature = "lang-julia")]
1101 Language::Julia => (
1102 arborium::lang_julia::language(),
1103 arborium::lang_julia::HIGHLIGHTS_QUERY,
1104 ),
1105 #[cfg(feature = "lang-kotlin")]
1106 Language::Kotlin => (
1107 arborium::lang_kotlin::language(),
1108 arborium::lang_kotlin::HIGHLIGHTS_QUERY,
1109 ),
1110 #[cfg(feature = "lang-lean")]
1111 Language::Lean => (
1112 arborium::lang_lean::language(),
1113 arborium::lang_lean::HIGHLIGHTS_QUERY,
1114 ),
1115 #[cfg(feature = "lang-lua")]
1116 Language::Lua => (
1117 arborium::lang_lua::language(),
1118 arborium::lang_lua::HIGHLIGHTS_QUERY,
1119 ),
1120 #[cfg(feature = "lang-markdown")]
1121 Language::Markdown => (
1122 arborium::lang_markdown::language(),
1123 arborium::lang_markdown::HIGHLIGHTS_QUERY,
1124 ),
1125 #[cfg(feature = "lang-matlab")]
1126 Language::Matlab => (
1127 arborium::lang_matlab::language(),
1128 arborium::lang_matlab::HIGHLIGHTS_QUERY,
1129 ),
1130 #[cfg(feature = "lang-meson")]
1131 Language::Meson => (
1132 arborium::lang_meson::language(),
1133 arborium::lang_meson::HIGHLIGHTS_QUERY,
1134 ),
1135 #[cfg(feature = "lang-nginx")]
1136 Language::Nginx => (
1137 arborium::lang_nginx::language(),
1138 arborium::lang_nginx::HIGHLIGHTS_QUERY,
1139 ),
1140 #[cfg(feature = "lang-ninja")]
1141 Language::Ninja => (
1142 arborium::lang_ninja::language(),
1143 arborium::lang_ninja::HIGHLIGHTS_QUERY,
1144 ),
1145 #[cfg(feature = "lang-nix")]
1146 Language::Nix => (
1147 arborium::lang_nix::language(),
1148 arborium::lang_nix::HIGHLIGHTS_QUERY,
1149 ),
1150 #[cfg(feature = "lang-objc")]
1151 Language::ObjectiveC => (
1152 arborium::lang_objc::language(),
1153 &arborium::lang_objc::HIGHLIGHTS_QUERY,
1154 ),
1155 #[cfg(feature = "lang-ocaml")]
1156 Language::OCaml => (
1157 arborium::lang_ocaml::language(),
1158 arborium::lang_ocaml::HIGHLIGHTS_QUERY,
1159 ),
1160 #[cfg(feature = "lang-perl")]
1161 Language::Perl => (
1162 arborium::lang_perl::language(),
1163 arborium::lang_perl::HIGHLIGHTS_QUERY,
1164 ),
1165 #[cfg(feature = "lang-php")]
1166 Language::Php => (
1167 arborium::lang_php::language(),
1168 arborium::lang_php::HIGHLIGHTS_QUERY,
1169 ),
1170 #[cfg(feature = "lang-postscript")]
1171 Language::PostScript => (
1172 arborium::lang_postscript::language(),
1173 arborium::lang_postscript::HIGHLIGHTS_QUERY,
1174 ),
1175 #[cfg(feature = "lang-powershell")]
1176 Language::PowerShell => (
1177 arborium::lang_powershell::language(),
1178 arborium::lang_powershell::HIGHLIGHTS_QUERY,
1179 ),
1180 #[cfg(feature = "lang-prolog")]
1181 Language::Prolog => (
1182 arborium::lang_prolog::language(),
1183 arborium::lang_prolog::HIGHLIGHTS_QUERY,
1184 ),
1185 #[cfg(feature = "lang-python")]
1186 Language::Python => (
1187 arborium::lang_python::language(),
1188 arborium::lang_python::HIGHLIGHTS_QUERY,
1189 ),
1190 #[cfg(feature = "lang-query")]
1191 Language::Query => (
1192 arborium::lang_query::language(),
1193 arborium::lang_query::HIGHLIGHTS_QUERY,
1194 ),
1195 #[cfg(feature = "lang-r")]
1196 Language::R => (
1197 arborium::lang_r::language(),
1198 arborium::lang_r::HIGHLIGHTS_QUERY,
1199 ),
1200 #[cfg(feature = "lang-rego")]
1201 Language::Rego => (
1202 arborium::lang_rego::language(),
1203 arborium::lang_rego::HIGHLIGHTS_QUERY,
1204 ),
1205 #[cfg(feature = "lang-rescript")]
1206 Language::Rescript => (
1207 arborium::lang_rescript::language(),
1208 arborium::lang_rescript::HIGHLIGHTS_QUERY,
1209 ),
1210 #[cfg(feature = "lang-ron")]
1211 Language::Ron => (
1212 arborium::lang_ron::language(),
1213 arborium::lang_ron::HIGHLIGHTS_QUERY,
1214 ),
1215 #[cfg(feature = "lang-ruby")]
1216 Language::Ruby => (
1217 arborium::lang_ruby::language(),
1218 arborium::lang_ruby::HIGHLIGHTS_QUERY,
1219 ),
1220 #[cfg(feature = "lang-scala")]
1221 Language::Scala => (
1222 arborium::lang_scala::language(),
1223 arborium::lang_scala::HIGHLIGHTS_QUERY,
1224 ),
1225 #[cfg(feature = "lang-scheme")]
1226 Language::Scheme => (
1227 arborium::lang_scheme::language(),
1228 arborium::lang_scheme::HIGHLIGHTS_QUERY,
1229 ),
1230 #[cfg(feature = "lang-scss")]
1231 Language::Scss => (
1232 arborium::lang_scss::language(),
1233 &arborium::lang_scss::HIGHLIGHTS_QUERY,
1234 ),
1235 #[cfg(feature = "lang-solidity")]
1236 Language::Solidity => (
1237 arborium::lang_solidity::language(),
1238 arborium::lang_solidity::HIGHLIGHTS_QUERY,
1239 ),
1240 #[cfg(feature = "lang-sparql")]
1241 Language::Sparql => (
1242 arborium::lang_sparql::language(),
1243 arborium::lang_sparql::HIGHLIGHTS_QUERY,
1244 ),
1245 #[cfg(feature = "lang-sql")]
1246 Language::Sql => (
1247 arborium::lang_sql::language(),
1248 arborium::lang_sql::HIGHLIGHTS_QUERY,
1249 ),
1250 #[cfg(feature = "lang-ssh-config")]
1251 Language::SshConfig => (
1252 arborium::lang_ssh_config::language(),
1253 arborium::lang_ssh_config::HIGHLIGHTS_QUERY,
1254 ),
1255 #[cfg(feature = "lang-starlark")]
1256 Language::Starlark => (
1257 arborium::lang_starlark::language(),
1258 arborium::lang_starlark::HIGHLIGHTS_QUERY,
1259 ),
1260 #[cfg(feature = "lang-styx")]
1261 Language::Styx => (
1262 arborium::lang_styx::language(),
1263 arborium::lang_styx::HIGHLIGHTS_QUERY,
1264 ),
1265 #[cfg(feature = "lang-svelte")]
1266 Language::Svelte => (
1267 arborium::lang_svelte::language(),
1268 &arborium::lang_svelte::HIGHLIGHTS_QUERY,
1269 ),
1270 #[cfg(feature = "lang-swift")]
1271 Language::Swift => (
1272 arborium::lang_swift::language(),
1273 arborium::lang_swift::HIGHLIGHTS_QUERY,
1274 ),
1275 #[cfg(feature = "lang-textproto")]
1276 Language::Textproto => (
1277 arborium::lang_textproto::language(),
1278 arborium::lang_textproto::HIGHLIGHTS_QUERY,
1279 ),
1280 #[cfg(feature = "lang-thrift")]
1281 Language::Thrift => (
1282 arborium::lang_thrift::language(),
1283 arborium::lang_thrift::HIGHLIGHTS_QUERY,
1284 ),
1285 #[cfg(feature = "lang-tlaplus")]
1286 Language::TlaPlus => (
1287 arborium::lang_tlaplus::language(),
1288 arborium::lang_tlaplus::HIGHLIGHTS_QUERY,
1289 ),
1290 #[cfg(feature = "lang-toml")]
1291 Language::Toml => (
1292 arborium::lang_toml::language(),
1293 arborium::lang_toml::HIGHLIGHTS_QUERY,
1294 ),
1295 #[cfg(feature = "lang-tsx")]
1296 Language::Tsx => (
1297 arborium::lang_tsx::language(),
1298 &arborium::lang_tsx::HIGHLIGHTS_QUERY,
1299 ),
1300 #[cfg(feature = "lang-typescript")]
1301 Language::TypeScript => (
1302 arborium::lang_typescript::language(),
1303 &arborium::lang_typescript::HIGHLIGHTS_QUERY,
1304 ),
1305 #[cfg(feature = "lang-typst")]
1306 Language::Typst => (
1307 arborium::lang_typst::language(),
1308 arborium::lang_typst::HIGHLIGHTS_QUERY,
1309 ),
1310 #[cfg(feature = "lang-uiua")]
1311 Language::Uiua => (
1312 arborium::lang_uiua::language(),
1313 arborium::lang_uiua::HIGHLIGHTS_QUERY,
1314 ),
1315 #[cfg(feature = "lang-vb")]
1316 Language::VisualBasic => (
1317 arborium::lang_vb::language(),
1318 arborium::lang_vb::HIGHLIGHTS_QUERY,
1319 ),
1320 #[cfg(feature = "lang-verilog")]
1321 Language::Verilog => (
1322 arborium::lang_verilog::language(),
1323 arborium::lang_verilog::HIGHLIGHTS_QUERY,
1324 ),
1325 #[cfg(feature = "lang-vhdl")]
1326 Language::Vhdl => (
1327 arborium::lang_vhdl::language(),
1328 arborium::lang_vhdl::HIGHLIGHTS_QUERY,
1329 ),
1330 #[cfg(feature = "lang-vim")]
1331 Language::Vim => (
1332 arborium::lang_vim::language(),
1333 arborium::lang_vim::HIGHLIGHTS_QUERY,
1334 ),
1335 #[cfg(feature = "lang-vue")]
1336 Language::Vue => (
1337 arborium::lang_vue::language(),
1338 &arborium::lang_vue::HIGHLIGHTS_QUERY,
1339 ),
1340 #[cfg(feature = "lang-wit")]
1341 Language::Wit => (
1342 arborium::lang_wit::language(),
1343 arborium::lang_wit::HIGHLIGHTS_QUERY,
1344 ),
1345 #[cfg(feature = "lang-x86asm")]
1346 Language::X86Asm => (
1347 arborium::lang_x86asm::language(),
1348 arborium::lang_x86asm::HIGHLIGHTS_QUERY,
1349 ),
1350 #[cfg(feature = "lang-xml")]
1351 Language::Xml => (
1352 arborium::lang_xml::language(),
1353 arborium::lang_xml::HIGHLIGHTS_QUERY,
1354 ),
1355 #[cfg(feature = "lang-yaml")]
1356 Language::Yaml => (
1357 arborium::lang_yaml::language(),
1358 arborium::lang_yaml::HIGHLIGHTS_QUERY,
1359 ),
1360 #[cfg(feature = "lang-yuri")]
1361 Language::Yuri => (
1362 arborium::lang_yuri::language(),
1363 arborium::lang_yuri::HIGHLIGHTS_QUERY,
1364 ),
1365 #[cfg(feature = "lang-zig")]
1366 Language::Zig => (
1367 arborium::lang_zig::language(),
1368 arborium::lang_zig::HIGHLIGHTS_QUERY,
1369 ),
1370 #[cfg(feature = "lang-zsh")]
1371 Language::Zsh => (
1372 arborium::lang_zsh::language(),
1373 arborium::lang_zsh::HIGHLIGHTS_QUERY,
1374 ),
1375 }
1376}
1377
1378#[cfg(feature = "runtime")]
1391#[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
1392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1393pub struct SourceEdit {
1394 pub start_byte: usize,
1396 pub old_end_byte: usize,
1398 pub new_end_byte: usize,
1400}
1401
1402#[cfg(feature = "runtime")]
1403impl SourceEdit {
1404 fn into_input_edit(
1405 self,
1406 old_source: &str,
1407 new_source: &str,
1408 ) -> Result<arborium_tree_sitter::InputEdit, HighlightError> {
1409 if self.start_byte > self.old_end_byte
1410 || self.start_byte > self.new_end_byte
1411 || self.old_end_byte > old_source.len()
1412 || self.new_end_byte > new_source.len()
1413 || !old_source.is_char_boundary(self.start_byte)
1414 || !old_source.is_char_boundary(self.old_end_byte)
1415 || !new_source.is_char_boundary(self.start_byte)
1416 || !new_source.is_char_boundary(self.new_end_byte)
1417 {
1418 return Err(HighlightError::InvalidEdit {
1419 start_byte: self.start_byte,
1420 old_end_byte: self.old_end_byte,
1421 new_end_byte: self.new_end_byte,
1422 old_len: old_source.len(),
1423 new_len: new_source.len(),
1424 });
1425 }
1426
1427 Ok(arborium_tree_sitter::InputEdit {
1428 start_byte: self.start_byte,
1429 old_end_byte: self.old_end_byte,
1430 new_end_byte: self.new_end_byte,
1431 start_position: byte_to_point(old_source, self.start_byte),
1432 old_end_position: byte_to_point(old_source, self.old_end_byte),
1433 new_end_position: byte_to_point(new_source, self.new_end_byte),
1434 })
1435 }
1436}
1437
1438#[cfg(feature = "runtime")]
1439fn byte_to_point(text: &str, byte: usize) -> arborium_tree_sitter::Point {
1440 let prefix = &text.as_bytes()[..byte];
1441 let last_newline = prefix.iter().rposition(|&b| b == b'\n');
1442 let row = prefix.iter().filter(|&&b| b == b'\n').count();
1443 let column = match last_newline {
1444 Some(pos) => byte - pos - 1,
1445 None => byte,
1446 };
1447 arborium_tree_sitter::Point { row, column }
1448}
1449
1450#[component]
1462pub fn CodeThemeStyles(theme: CodeTheme) -> Element {
1463 let shared_theme_css = Theme::THEME_CSS;
1464
1465 match theme.stylesheets() {
1466 CodeThemeStylesheets::Fixed(stylesheet) => {
1467 let theme_asset = stylesheet.asset;
1468 let theme_key = stylesheet.class;
1469
1470 rsx! {
1471 document::Stylesheet { href: shared_theme_css }
1472 {rsx!{document::Stylesheet { key: "{theme_key}", href: theme_asset }}}
1473 }
1474 }
1475 CodeThemeStylesheets::System { light, dark } => {
1476 let light_asset = light.asset;
1477 let dark_asset = dark.asset;
1478 let light_key = light.class;
1479 let dark_key = dark.class;
1480
1481 rsx! {
1482 document::Stylesheet { href: shared_theme_css }
1483 {rsx!{document::Stylesheet { key: "{light_key}", href: light_asset }}}
1484 {rsx!{document::Stylesheet { key: "{dark_key}", href: dark_asset }}}
1485 }
1486 }
1487 }
1488}
1489
1490#[cfg(all(test, feature = "runtime"))]
1491mod buffer_tests {
1492 use super::*;
1493
1494 fn span_ranges(spans: &[HighlightSpan]) -> Vec<(u32, u32, &'static str)> {
1495 spans
1496 .iter()
1497 .map(|s| (s.start(), s.end(), s.tag()))
1498 .collect()
1499 }
1500
1501 fn batch_spans(source: &str, language: Language) -> Vec<HighlightSpan> {
1502 let snapshot: HighlightedSource = SourceCode::new(language, source.to_owned()).into();
1503 snapshot.spans().to_vec()
1504 }
1505
1506 #[test]
1507 fn new_matches_batch_path() {
1508 let source = "fn main() { let x = 1; }";
1509 let buffer = Buffer::new(Language::Rust, source).unwrap();
1510
1511 assert_eq!(buffer.language(), Language::Rust);
1512 assert_eq!(
1513 span_ranges(buffer.spans()),
1514 span_ranges(&batch_spans(source, Language::Rust)),
1515 );
1516 }
1517
1518 #[test]
1519 fn edit_with_explicit_source_edit() {
1520 let mut buffer = Buffer::new(Language::Rust, "fn main() { let x = 1; }").unwrap();
1521 let updated = "fn main() { let x = 12; }";
1522 buffer
1523 .edit(
1524 SourceEdit {
1525 start_byte: 21,
1526 old_end_byte: 21,
1527 new_end_byte: 22,
1528 },
1529 updated,
1530 )
1531 .unwrap();
1532
1533 assert_eq!(buffer.source(), updated);
1534 assert_eq!(
1535 span_ranges(buffer.spans()),
1536 span_ranges(&batch_spans(updated, Language::Rust)),
1537 );
1538 }
1539
1540 #[test]
1541 fn malformed_edit_returns_typed_error_and_leaves_state_unchanged() {
1542 let mut buffer = Buffer::new(Language::Rust, "fn main() { let x = 1; }").unwrap();
1543 let previous_spans = buffer.spans().to_vec();
1544 let updated = "fn main() { let x = 12; }";
1545
1546 assert_eq!(
1547 buffer.edit(
1548 SourceEdit {
1550 start_byte: 21,
1551 old_end_byte: 999,
1552 new_end_byte: 22,
1553 },
1554 updated,
1555 ),
1556 Err(HighlightError::InvalidEdit {
1557 start_byte: 21,
1558 old_end_byte: 999,
1559 new_end_byte: 22,
1560 old_len: "fn main() { let x = 1; }".len(),
1561 new_len: updated.len(),
1562 }),
1563 );
1564 assert_eq!(buffer.source(), "fn main() { let x = 1; }");
1565 assert_eq!(buffer.spans(), previous_spans.as_slice());
1566 }
1567
1568 #[test]
1569 fn semantic_edit_mismatch_is_not_validated() {
1570 let mut buffer = Buffer::new(Language::Rust, "fn main() { let x = 1; }").unwrap();
1571 let updated = "fn main() { let y = 1; }";
1572
1573 buffer
1574 .edit(
1575 SourceEdit {
1578 start_byte: 21,
1579 old_end_byte: 21,
1580 new_end_byte: 22,
1581 },
1582 updated,
1583 )
1584 .unwrap();
1585
1586 assert_eq!(buffer.source(), updated);
1587 }
1588
1589 #[test]
1590 fn replace_drops_cached_tree() {
1591 let mut buffer = Buffer::new(Language::Rust, "fn main() { 1 }").unwrap();
1592 let updated = "fn main() { 2 }";
1593 buffer.replace(updated).unwrap();
1594
1595 assert_eq!(buffer.source(), updated);
1596 assert_eq!(
1597 span_ranges(buffer.spans()),
1598 span_ranges(&batch_spans(updated, Language::Rust)),
1599 );
1600 }
1601
1602 #[test]
1603 fn set_language_reparses() {
1604 let mut buffer = Buffer::new(Language::Rust, "fn main() {}").unwrap();
1605 let rust_spans = buffer.spans().to_vec();
1606
1607 buffer.set_language(Language::Rust).unwrap();
1610 assert_eq!(buffer.spans(), rust_spans.as_slice());
1611 }
1612
1613 #[test]
1614 fn byte_to_point_counts_rows_and_columns() {
1615 use arborium_tree_sitter::Point;
1616 assert_eq!(byte_to_point("abc", 2), Point { row: 0, column: 2 });
1617 assert_eq!(byte_to_point("ab\ncd\nef", 6), Point { row: 2, column: 0 });
1618 assert_eq!(byte_to_point("ab\ncd\nef", 8), Point { row: 2, column: 2 });
1619 }
1620}