1#![doc = include_str!("readme.md")]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum HighlightKind {
5 Keyword,
7 String,
9 Number,
11 Comment,
13 Identifier,
15}
16
17pub trait Highlighter {
19 fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)>;
21}
22
23pub struct PhpHighlighter {
25 pub use_parser: bool,
27}
28
29impl Default for PhpHighlighter {
30 fn default() -> Self {
31 Self { use_parser: false }
32 }
33}
34
35impl PhpHighlighter {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn with_parser() -> Self {
43 Self { use_parser: true }
44 }
45
46 fn highlight_keywords(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
47 let mut highlights = Vec::new();
48 let keywords = [
49 "abstract",
50 "and",
51 "array",
52 "as",
53 "break",
54 "callable",
55 "case",
56 "catch",
57 "class",
58 "clone",
59 "const",
60 "continue",
61 "declare",
62 "default",
63 "die",
64 "do",
65 "echo",
66 "else",
67 "elseif",
68 "empty",
69 "enddeclare",
70 "endfor",
71 "endforeach",
72 "endif",
73 "endswitch",
74 "endwhile",
75 "eval",
76 "exit",
77 "extends",
78 "final",
79 "finally",
80 "for",
81 "foreach",
82 "function",
83 "global",
84 "goto",
85 "if",
86 "implements",
87 "include",
88 "include_once",
89 "instanceof",
90 "insteadof",
91 "interface",
92 "isset",
93 "list",
94 "namespace",
95 "new",
96 "or",
97 "print",
98 "private",
99 "protected",
100 "public",
101 "require",
102 "require_once",
103 "return",
104 "static",
105 "switch",
106 "throw",
107 "trait",
108 "try",
109 "unset",
110 "use",
111 "var",
112 "while",
113 "xor",
114 "yield",
115 ];
116
117 for keyword in &keywords {
118 let mut start = 0;
119 while let Some(pos) = text[start..].find(keyword) {
120 let absolute_pos = start + pos;
121 let end_pos = absolute_pos + keyword.len();
122
123 let is_word_boundary_before = absolute_pos == 0 || !text.chars().nth(absolute_pos - 1).unwrap_or(' ').is_alphanumeric();
124 let is_word_boundary_after = end_pos >= text.len() || !text.chars().nth(end_pos).unwrap_or(' ').is_alphanumeric();
125
126 if is_word_boundary_before && is_word_boundary_after {
127 highlights.push((absolute_pos, end_pos, HighlightKind::Keyword));
128 }
129
130 start = absolute_pos + 1;
131 }
132 }
133
134 highlights
135 }
136}
137
138impl Highlighter for PhpHighlighter {
139 fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
140 let mut highlights = self.highlight_keywords(text);
141 highlights.sort_by_key(|h| h.0);
142 highlights
143 }
144}