TextMateLib 1.0
Modern C++ implementation of the TextMate syntax highlighting engine
Loading...
Searching...
No Matches
syntax_highlighter.cpp
1#include "syntax_highlighter.h"
2#include <chrono>
3#include <algorithm>
4
5namespace tml {
6
7// ============================================================================
8// Utility Functions
9// ============================================================================
10
11uint64_t SyntaxHighlighter::getCurrentTimeMs() {
12 auto now = std::chrono::high_resolution_clock::now();
13 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
14 now.time_since_epoch()
15 );
16 return static_cast<uint64_t>(ms.count());
17}
18
19// ============================================================================
20// SyntaxHighlighter - Lifecycle
21// ============================================================================
22
23SyntaxHighlighter::SyntaxHighlighter(
24 std::shared_ptr<IGrammar> gram,
25 Theme* thm,
26 bool enableCache
27)
28 : grammar(gram),
29 theme(thm),
30 session(nullptr),
31 cache(nullptr),
32 createdAtMs(getCurrentTimeMs()),
33 lastUpdateMs(createdAtMs) {
34
35 if (!grammar) {
36 throw std::invalid_argument("Grammar cannot be null");
37 }
38 if (!theme) {
39 throw std::invalid_argument("Theme cannot be null");
40 }
41
42 // Create session with the grammar
43 // Session handles incremental tokenization with state management
44 uint64_t sessionId = SessionManager::createSession(grammar);
45 session = SessionManager::getSession(sessionId);
46
47 if (!session) {
48 throw std::runtime_error("Failed to create session");
49 }
50
51 // Initialize cache if enabled (C++11 compatible)
52 if (enableCache) {
53 cache.reset(new HighlighterCache());
54 }
55}
56
57SyntaxHighlighter::~SyntaxHighlighter() {
58 // Session is managed by SessionManager with reference counting
59 // Just release our reference
60 if (session) {
61 SessionManager::releaseSession(session->getSessionId());
62 }
63}
64
65// ============================================================================
66// SyntaxHighlighter - Document Management
67// ============================================================================
68
69void SyntaxHighlighter::setDocument(const std::vector<std::string>& lines) {
70 if (!session) {
71 throw std::runtime_error("Session is null");
72 }
73
74 session->setLines(lines);
75 lastUpdateMs = getCurrentTimeMs();
76
77 // Clear cache when entire document changes
78 if (cache) {
79 cache->clear();
80 }
81}
82
83void SyntaxHighlighter::editLine(int lineIndex, const std::string& newContent) {
84 if (!session) {
85 throw std::runtime_error("Session is null");
86 }
87
88 if (lineIndex < 0 || lineIndex >= session->getLineCount()) {
89 throw std::out_of_range("Line index out of range");
90 }
91
92 // Replace single line using edit operation
93 std::vector<std::string> newLines = {newContent};
94 session->edit(newLines, lineIndex, 1);
95 lastUpdateMs = getCurrentTimeMs();
96
97 // Invalidate cache for affected line and potentially following lines
98 if (cache) {
99 cache->invalidateLine(lineIndex);
100 }
101}
102
103void SyntaxHighlighter::insertLines(int startIndex, const std::vector<std::string>& lines) {
104 if (!session) {
105 throw std::runtime_error("Session is null");
106 }
107
108 if (startIndex < 0 || startIndex > session->getLineCount()) {
109 throw std::out_of_range("Insert index out of range");
110 }
111
112 session->add(lines, startIndex);
113 lastUpdateMs = getCurrentTimeMs();
114
115 // Invalidate cache from insertion point onward
116 if (cache) {
117 cache->invalidateRange(startIndex, session->getLineCount() - 1);
118 }
119}
120
121void SyntaxHighlighter::removeLines(int startIndex, int count) {
122 if (!session) {
123 throw std::runtime_error("Session is null");
124 }
125
126 if (startIndex < 0 || count < 0 || startIndex + count > session->getLineCount()) {
127 throw std::out_of_range("Remove range out of range");
128 }
129
130 session->remove(startIndex, count);
131 lastUpdateMs = getCurrentTimeMs();
132
133 // Invalidate cache from removal point onward
134 if (cache) {
135 cache->invalidateRange(startIndex, std::max(0, session->getLineCount() - 1));
136 }
137}
138
139int SyntaxHighlighter::getLineCount() const {
140 if (!session) {
141 return 0;
142 }
143 return session->getLineCount();
144}
145
146// ============================================================================
147// SyntaxHighlighter - Token to Highlighting Conversion
148// ============================================================================
149
150HighlightedToken SyntaxHighlighter::tokenToHighlighted(const IToken& token) {
151 HighlightedToken highlighted;
152 highlighted.startIndex = token.startIndex;
153 highlighted.endIndex = token.endIndex;
154 highlighted.scopes = token.scopes;
155
156 // Build scope stack for theme matching
157 ScopeStack* scopeStack = buildScopeStack(token.scopes);
158
159 if (scopeStack && theme) {
160 // Match scope against theme to get styling
161 StyleAttributes* styleAttrs = theme->match(scopeStack);
162
163 if (styleAttrs) {
164 // Get color map from theme
165 auto colorMap = theme->getColorMap();
166
167 // Resolve foreground color
168 if (styleAttrs->foregroundId >= 0 && styleAttrs->foregroundId < static_cast<int>(colorMap.size())) {
169 highlighted.foregroundColor = colorMap[styleAttrs->foregroundId];
170 }
171
172 // Resolve background color
173 if (styleAttrs->backgroundId >= 0 && styleAttrs->backgroundId < static_cast<int>(colorMap.size())) {
174 highlighted.backgroundColor = colorMap[styleAttrs->backgroundId];
175 }
176
177 // Apply font style
178 highlighted.fontStyle = styleAttrs->fontStyle;
179 }
180 }
181
182 // Clean up scope stack
183 if (scopeStack) {
184 delete scopeStack;
185 }
186
187 // Build debug info
188 if (!token.scopes.empty()) {
189 highlighted.debugInfo = token.scopes[0];
190 for (size_t i = 1; i < token.scopes.size(); ++i) {
191 highlighted.debugInfo += " " + token.scopes[i];
192 }
193 }
194
195 return highlighted;
196}
197
198ScopeStack* SyntaxHighlighter::buildScopeStack(const std::vector<std::string>& scopes) {
199 if (scopes.empty()) {
200 return nullptr;
201 }
202
203 // Build scope stack from scope names
204 // ScopeStack is a linked-list like structure where each node represents a scope
205 ScopeStack* result = ScopeStack::from(scopes);
206 return result;
207}
208
209// ============================================================================
210// SyntaxHighlighter - Querying Highlighted Content
211// ============================================================================
212
213HighlightedLine SyntaxHighlighter::getHighlightedLine(int lineIndex) {
214 if (!session) {
215 throw std::runtime_error("Session is null");
216 }
217
218 if (lineIndex < 0 || lineIndex >= session->getLineCount()) {
219 throw std::out_of_range("Line index out of range");
220 }
221
222 // Check cache first
223 uint64_t version = lastUpdateMs;
224 if (cache) {
225 HighlightedLine* cached = cache->getCachedLine(lineIndex, version);
226 if (cached) {
227 return *cached;
228 }
229 }
230
231 HighlightedLine result;
232 result.lineIndex = lineIndex;
233 result.version = version;
234
235 // Get raw line content and tokens from session
236 const SessionLine* sessionLine = session->getLine(lineIndex);
237 if (!sessionLine) {
238 result.isComplete = false;
239 return result;
240 }
241
242 result.content = sessionLine->content;
243 result.isComplete = sessionLine->cached;
244
245 // Convert each raw token to highlighted token
246 result.tokens.reserve(sessionLine->tokens.size());
247 for (const auto& token : sessionLine->tokens) {
248 result.tokens.push_back(tokenToHighlighted(token));
249 }
250
251 // Cache the result
252 if (cache) {
253 cache->cacheLine(result, version);
254 }
255
256 return result;
257}
258
259std::vector<HighlightedLine> SyntaxHighlighter::getHighlightedRange(
260 int startIndex,
261 int endIndex
262) {
263 if (!session) {
264 throw std::runtime_error("Session is null");
265 }
266
267 int lineCount = session->getLineCount();
268 if (startIndex < 0 || endIndex >= lineCount || startIndex > endIndex) {
269 throw std::out_of_range("Range out of bounds");
270 }
271
272 std::vector<HighlightedLine> results;
273 results.reserve(endIndex - startIndex + 1);
274
275 for (int i = startIndex; i <= endIndex; ++i) {
276 results.push_back(getHighlightedLine(i));
277 }
278
279 return results;
280}
281
282std::vector<IToken> SyntaxHighlighter::getLineTokens(int lineIndex) {
283 if (!session) {
284 throw std::runtime_error("Session is null");
285 }
286
287 if (lineIndex < 0 || lineIndex >= session->getLineCount()) {
288 throw std::out_of_range("Line index out of range");
289 }
290
291 const std::vector<IToken>* tokens = session->getLineTokens(lineIndex);
292 if (!tokens) {
293 return std::vector<IToken>();
294 }
295
296 return *tokens;
297}
298
299// ============================================================================
300// SyntaxHighlighter - Theme Management
301// ============================================================================
302
303void SyntaxHighlighter::setTheme(Theme* newTheme) {
304 if (!newTheme) {
305 throw std::invalid_argument("Theme cannot be null");
306 }
307
308 theme = newTheme;
309 lastUpdateMs = getCurrentTimeMs();
310
311 // Clear cache since all colors/styles are now different
312 if (cache) {
313 cache->clear();
314 }
315}
316
317Theme* SyntaxHighlighter::getTheme() const {
318 return theme;
319}
320
321// ============================================================================
322// SyntaxHighlighter - Cache Management
323// ============================================================================
324
325void SyntaxHighlighter::clearCache() {
326 if (cache) {
327 cache->clear();
328 }
329}
330
331void SyntaxHighlighter::invalidateCacheRange(int startIndex, int endIndex) {
332 if (!cache) {
333 return;
334 }
335
336 if (startIndex < 0 || endIndex < 0 || startIndex > endIndex) {
337 throw std::invalid_argument("Invalid cache range");
338 }
339
340 cache->invalidateRange(startIndex, endIndex);
341}
342
343// ============================================================================
344// SyntaxHighlighter - Debugging & Monitoring
345// ============================================================================
346
347SyntaxHighlightingMetadata SyntaxHighlighter::getMetadata() const {
348 SyntaxHighlightingMetadata metadata;
349
350 if (session) {
351 SessionMetadata sessionMeta = session->getMetadata();
352 metadata.sessionId = sessionMeta.createdAtMs; // Using timestamp as unique ID
353 metadata.lineCount = sessionMeta.lineCount;
354 metadata.cachedLineCount = sessionMeta.cachedLineCount;
355 }
356
357 if (theme) {
358 auto colorMap = theme->getColorMap();
359 metadata.themeColorCount = static_cast<int>(colorMap.size());
360 }
361
362 metadata.lastUpdateMs = lastUpdateMs;
363
364 if (cache) {
365 metadata.cachedLineCount = cache->getCachedLineCount();
366 }
367
368 return metadata;
369}
370
371SessionImpl* SyntaxHighlighter::getSession() const {
372 return session.get();
373}
374
375// ============================================================================
376// HighlighterCache Implementation
377// ============================================================================
378
379HighlightedLine* HighlighterCache::getCachedLine(int lineIndex, uint64_t version) {
380 auto it = lineCache.find(lineIndex);
381 if (it != lineCache.end() && it->second.version == version) {
382 return &it->second.line;
383 }
384 return nullptr;
385}
386
387void HighlighterCache::cacheLine(const HighlightedLine& line, uint64_t version) {
388 lineCache[line.lineIndex] = {line, version};
389}
390
391void HighlighterCache::invalidateLine(int lineIndex) {
392 lineCache.erase(lineIndex);
393}
394
395void HighlighterCache::invalidateRange(int startIndex, int endIndex) {
396 auto it = lineCache.begin();
397 while (it != lineCache.end()) {
398 if (it->first >= startIndex && it->first <= endIndex) {
399 it = lineCache.erase(it);
400 } else {
401 ++it;
402 }
403 }
404}
405
406void HighlighterCache::clear() {
407 lineCache.clear();
408}
409
410size_t HighlighterCache::getCachedLineCount() const {
411 return lineCache.size();
412}
413
414} // namespace tml