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