TextMateLib 1.0
Modern C++ implementation of the TextMate syntax highlighting engine
Loading...
Searching...
No Matches
session.cpp
1#include "session.h"
2#include <algorithm>
3#include <chrono>
4#include <cstring>
5#include <vector>
6#include <stdint.h>
7
8namespace tml {
9
10// ============================================================================
11// SessionLine Implementation
12// ============================================================================
13
14SessionLine::~SessionLine() {
15 if (state != nullptr) {
16 // State stacks are managed by the grammar/tokenizer
17 // They should be released through the proper mechanism
18 state = nullptr;
19 }
20}
21
22// ============================================================================
23// TextMateSession Implementation
24// ============================================================================
25
26SessionImpl::SessionImpl(uint64_t id, std::shared_ptr<IGrammar> gram)
27 : sessionId(id),
28 grammar(gram),
29 referenceCount(1),
30 nextVersion(1) {
31 auto now = std::chrono::system_clock::now();
32 createdAtMs = std::chrono::duration_cast<std::chrono::milliseconds>(
33 now.time_since_epoch()
34 ).count();
35 lastAccessMs = createdAtMs;
36}
37
38SessionImpl::~SessionImpl() {
39 clearCache();
40}
41
42void SessionImpl::retain() {
43 referenceCount++;
44}
45
46void SessionImpl::release() {
47 if (referenceCount > 0) {
48 referenceCount--;
49 }
50}
51
52void SessionImpl::updateAccessTime() {
53 auto now = std::chrono::system_clock::now();
54 lastAccessMs = std::chrono::duration_cast<std::chrono::milliseconds>(
55 now.time_since_epoch()
56 ).count();
57}
58
59bool SessionImpl::isExpired(uint64_t currentTimeMs, uint64_t maxAgeMs) const {
60 return (currentTimeMs - createdAtMs) > maxAgeMs;
61}
62
63bool SessionImpl::isStateEqual(StateStack* state1, StateStack* state2) const {
64 if (state1 == state2) {
65 return true;
66 }
67 if (state1 == nullptr || state2 == nullptr) {
68 return false;
69 }
70 // Compare state stacks by converting to string representation
71 // This is a simple comparison; more sophisticated comparison may be needed
72 return std::string(reinterpret_cast<char*>(state1)) ==
73 std::string(reinterpret_cast<char*>(state2));
74}
75
76void SessionImpl::invalidateFrom(int32_t startIndex) {
77 if (startIndex < 0 || startIndex >= static_cast<int32_t>(lines.size())) {
78 return;
79 }
80
81 for (int32_t i = startIndex; i < static_cast<int32_t>(lines.size()); i++) {
82 lines[i].cached = false;
83 lines[i].tokens.clear();
84 if (lines[i].state != nullptr) {
85 lines[i].state = nullptr;
86 }
87 lines[i].version = nextVersion;
88 }
89 nextVersion++;
90}
91
92int32_t SessionImpl::setLines(const std::vector<std::string>& newLines) {
93 updateAccessTime();
94
95 // Clear existing lines
96 clearCache();
97 lines.clear();
98
99 // Initialize new lines
100 lines.resize(newLines.size());
101 for (size_t i = 0; i < newLines.size(); i++) {
102 lines[i].content = newLines[i];
103 lines[i].cached = false;
104 lines[i].state = nullptr;
105 lines[i].version = nextVersion;
106 }
107
108 // Tokenize all lines with initial state
109 retokenizeLines(0, static_cast<int32_t>(lines.size()) - 1);
110
111 return 0;
112}
113
114void SessionImpl::retokenizeLines(int32_t startIndex, int32_t endIndex) {
115 if (!grammar || startIndex < 0 || startIndex > endIndex) {
116 return;
117 }
118
119 endIndex = std::min(endIndex, static_cast<int32_t>(lines.size()) - 1);
120
121 // Get initial state for first line to retokenize
122 StateStack* state = nullptr;
123 if (startIndex > 0 && lines[startIndex - 1].cached && lines[startIndex - 1].state) {
124 state = lines[startIndex - 1].state;
125 }
126
127 // Record every node created during this retokenize so the unreachable ones can be
128 // reclaimed below. Save/restore the previous active arena to stay re-entrant.
129 StackNodeArena* prevArena = tmlGetActiveArena();
130 tmlSetActiveArena(&stateArena);
131
132 // Retokenize with early stopping when state stabilizes
133 for (int32_t i = startIndex; i <= endIndex; i++) {
134 ITokenizeLineResult result = grammar->tokenizeLine(lines[i].content, state);
135
136 lines[i].tokens = result.tokens;
137 lines[i].state = result.ruleStack;
138 lines[i].cached = true;
139 lines[i].version = nextVersion;
140
141 // Check if we should stop cascading (state has stabilized)
142 if (i < static_cast<int32_t>(lines.size()) - 1) {
143 if (lines[i + 1].cached &&
144 isStateEqual(result.ruleStack, lines[i + 1].state)) {
145 // State matches expected, can stop here
146 break;
147 }
148 }
149
150 state = result.ruleStack;
151 }
152
153 tmlSetActiveArena(prevArena);
154
155 // Free everything no longer reachable from a currently-cached line state. The cached
156 // states are the only roots the editor can still observe; intra-line garbage and states
157 // orphaned by this edit are not among them and get reclaimed.
158 std::vector<StateStackImpl*> roots;
159 roots.reserve(lines.size());
160 for (const auto& line : lines) {
161 if (line.cached && line.state) {
162 roots.push_back(reinterpret_cast<StateStackImpl*>(line.state));
163 }
164 }
165 stateArena.sweepKeeping(roots);
166}
167
168int32_t SessionImpl::edit(
169 const std::vector<std::string>& newLines,
170 int32_t startIndex,
171 int32_t replaceCount
172) {
173 updateAccessTime();
174
175 // Validate parameters
176 if (startIndex < 0 || startIndex > static_cast<int32_t>(lines.size())) {
177 return 1; // Error
178 }
179
180 int32_t endIndex = startIndex + replaceCount - 1;
181 if (endIndex >= static_cast<int32_t>(lines.size())) {
182 endIndex = static_cast<int32_t>(lines.size()) - 1;
183 }
184
185 // Replace lines in the buffer
186 if (replaceCount > 0 && startIndex < static_cast<int32_t>(lines.size())) {
187 for (int32_t i = 0; i < static_cast<int32_t>(newLines.size()); i++) {
188 int32_t targetIdx = startIndex + i;
189 if (targetIdx < static_cast<int32_t>(lines.size())) {
190 lines[targetIdx].content = newLines[i];
191 }
192 }
193 }
194
195 // Invalidate cache from start
196 invalidateFrom(startIndex);
197
198 // Retokenize starting from the edited line
199 if (startIndex < static_cast<int32_t>(lines.size())) {
200 retokenizeLines(startIndex, static_cast<int32_t>(lines.size()) - 1);
201 }
202
203 return 0;
204}
205
206int32_t SessionImpl::add(
207 const std::vector<std::string>& newLines,
208 int32_t insertIndex
209) {
210 updateAccessTime();
211
212 // Validate parameters
213 if (insertIndex < 0 || insertIndex > static_cast<int32_t>(lines.size())) {
214 return 1; // Error
215 }
216
217 // Insert new lines at the specified position
218 lines.insert(
219 lines.begin() + insertIndex,
220 SessionLine()
221 );
222
223 for (size_t i = 0; i < newLines.size(); i++) {
224 int32_t targetIdx = insertIndex + i;
225 if (targetIdx < static_cast<int32_t>(lines.size())) {
226 lines[targetIdx].content = newLines[i];
227 lines[targetIdx].cached = false;
228 lines[targetIdx].state = nullptr;
229 }
230 }
231
232 // Invalidate cache from insertion point
233 invalidateFrom(insertIndex);
234
235 // Retokenize from insertion point
236 if (insertIndex < static_cast<int32_t>(lines.size())) {
237 retokenizeLines(insertIndex, static_cast<int32_t>(lines.size()) - 1);
238 }
239
240 return 0;
241}
242
243int32_t SessionImpl::remove(
244 int32_t startIndex,
245 int32_t removeCount
246) {
247 updateAccessTime();
248
249 // Validate parameters
250 if (startIndex < 0 || startIndex >= static_cast<int32_t>(lines.size()) ||
251 removeCount <= 0) {
252 return 1; // Error
253 }
254
255 int32_t endIndex = std::min(
256 static_cast<int32_t>(lines.size()),
257 startIndex + removeCount
258 );
259
260 // Remove lines from buffer
261 lines.erase(lines.begin() + startIndex, lines.begin() + endIndex);
262
263 // Invalidate cache from removal point
264 if (startIndex < static_cast<int32_t>(lines.size())) {
265 invalidateFrom(startIndex);
266
267 // Retokenize from removal point
268 retokenizeLines(startIndex, static_cast<int32_t>(lines.size()) - 1);
269 }
270
271 return 0;
272}
273
274const SessionLine* SessionImpl::getLine(int32_t lineIndex) const {
275 if (lineIndex < 0 || lineIndex >= static_cast<int32_t>(lines.size())) {
276 return nullptr;
277 }
278 return &lines[lineIndex];
279}
280
281const std::vector<IToken>* SessionImpl::getLineTokens(int32_t lineIndex) const {
282 const SessionLine* line = getLine(lineIndex);
283 if (line == nullptr || !line->cached) {
284 return nullptr;
285 }
286 return &line->tokens;
287}
288
289StateStack* SessionImpl::getLineState(int32_t lineIndex) const {
290 const SessionLine* line = getLine(lineIndex);
291 if (line == nullptr || !line->cached) {
292 return nullptr;
293 }
294 return line->state;
295}
296
297void SessionImpl::getTokensRange(
298 int32_t startIndex,
299 int32_t endIndex,
300 std::vector<SessionLine>& results
301) const {
302 results.clear();
303
304 if (startIndex < 0 || endIndex >= static_cast<int32_t>(lines.size()) ||
305 startIndex > endIndex) {
306 return;
307 }
308
309 for (int32_t i = startIndex; i <= endIndex; i++) {
310 if (i < static_cast<int32_t>(lines.size())) {
311 results.push_back(lines[i]);
312 }
313 }
314}
315
316void SessionImpl::invalidateRange(int32_t startIndex, int32_t endIndex) {
317 updateAccessTime();
318
319 if (startIndex < 0 || startIndex >= static_cast<int32_t>(lines.size())) {
320 return;
321 }
322
323 if (endIndex < 0 || endIndex >= static_cast<int32_t>(lines.size())) {
324 endIndex = static_cast<int32_t>(lines.size()) - 1;
325 }
326
327 for (int32_t i = startIndex; i <= endIndex; i++) {
328 lines[i].cached = false;
329 lines[i].tokens.clear();
330 if (lines[i].state != nullptr) {
331 lines[i].state = nullptr;
332 }
333 lines[i].version = nextVersion;
334 }
335 nextVersion++;
336
337 // Retokenize the range
338 if (startIndex < static_cast<int32_t>(lines.size())) {
339 retokenizeLines(startIndex, endIndex);
340 }
341}
342
343void SessionImpl::clearCache() {
344 for (auto& line : lines) {
345 line.cached = false;
346 line.tokens.clear();
347 if (line.state != nullptr) {
348 line.state = nullptr;
349 }
350 }
351 // Every cached state was just dropped, so the entire node graph is now unreachable.
352 stateArena.clear();
353 nextVersion++;
354}
355
356uint64_t SessionImpl::calculateMemoryUsage() const {
357 uint64_t usage = sizeof(SessionImpl);
358
359 // Account for lines vector
360 usage += lines.capacity() * sizeof(SessionLine);
361
362 // Account for token data
363 for (const auto& line : lines) {
364 usage += line.content.capacity();
365 usage += line.tokens.capacity() * sizeof(IToken);
366 for (const auto& token : line.tokens) {
367 for (const auto& scope : token.scopes) {
368 usage += scope.capacity();
369 }
370 }
371 }
372
373 return usage;
374}
375
376int32_t SessionImpl::countCachedLines() const {
377 int32_t count = 0;
378 for (const auto& line : lines) {
379 if (line.cached) {
380 count++;
381 }
382 }
383 return count;
384}
385
386SessionMetadata SessionImpl::getMetadata() const {
387 SessionMetadata metadata;
388 metadata.createdAtMs = createdAtMs;
389 metadata.referenceCount = referenceCount;
390 metadata.lineCount = static_cast<int32_t>(lines.size());
391 metadata.cachedLineCount = countCachedLines();
392 metadata.memoryUsageBytes = calculateMemoryUsage();
393 return metadata;
394}
395
396// ============================================================================
397// SessionManager Implementation
398// ============================================================================
399
400std::map<uint64_t, std::shared_ptr<SessionImpl>> SessionManager::sessions;
401uint64_t SessionManager::nextSessionId = 1;
402uint32_t SessionManager::operationCount = 0;
403
404uint64_t SessionManager::createSession(std::shared_ptr<IGrammar> grammar) {
405 if (!grammar) {
406 return 0;
407 }
408
409 uint64_t sessionId = nextSessionId++;
410 auto session = std::make_shared<SessionImpl>(sessionId, grammar);
411 sessions[sessionId] = session;
412
413 // Periodic cleanup
414 triggerPeriodicCleanup(60000); // 60 seconds
415
416 return sessionId;
417}
418
419std::shared_ptr<SessionImpl> SessionManager::getSession(uint64_t sessionId) {
420 auto it = sessions.find(sessionId);
421 if (it != sessions.end()) {
422 return it->second;
423 }
424 return nullptr;
425}
426
427void SessionManager::retainSession(uint64_t sessionId) {
428 auto session = getSession(sessionId);
429 if (session) {
430 session->retain();
431 }
432}
433
434void SessionManager::releaseSession(uint64_t sessionId) {
435 auto session = getSession(sessionId);
436 if (session) {
437 session->release();
438 }
439}
440
441void SessionManager::disposeSession(uint64_t sessionId) {
442 auto it = sessions.find(sessionId);
443 if (it != sessions.end()) {
444 it->second->release();
445 if (it->second->getRefCount() == 0) {
446 sessions.erase(it);
447 }
448 }
449}
450
451void SessionManager::cleanupExpired(int32_t maxAgeMs) {
452 auto now = std::chrono::system_clock::now();
453 uint64_t currentTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(
454 now.time_since_epoch()
455 ).count();
456
457 for (auto it = sessions.begin(); it != sessions.end();) {
458 if (it->second->isExpired(currentTimeMs, maxAgeMs)) {
459 it = sessions.erase(it);
460 } else {
461 ++it;
462 }
463 }
464}
465
466void SessionManager::triggerPeriodicCleanup(int32_t maxAgeMs) {
467 operationCount++;
468 if (operationCount % CLEANUP_INTERVAL == 0) {
469 cleanupExpired(maxAgeMs);
470 }
471}
472
473size_t SessionManager::getSessionCount() {
474 return sessions.size();
475}
476
477} // namespace tml
478
479// ============================================================================
480// C API Wrapper Layer (extern "C")
481// ============================================================================
482
483// Opaque types from c_api.h (not defining TextMateSession yet)
484typedef void* TextMateGrammar;
485typedef void* TextMateStateStack;
486typedef void* TextMateOnigLib;
487typedef void* TextMateTheme;
488
489// Token structure
490typedef struct {
491 int32_t startIndex;
492 int32_t endIndex;
493 int32_t scopeDepth;
494 char** scopes;
496
497// Tokenize result structure
498typedef struct {
499 TextMateToken* tokens;
500 int32_t tokenCount;
501 TextMateStateStack ruleStack;
502 int32_t stoppedEarly;
504
505// Session opaque handle
506typedef uint64_t TextMateSession;
507
508// Session line representation
509typedef struct {
510 TextMateToken* tokens;
511 int32_t tokenCount;
512 TextMateStateStack state;
513 uint64_t version;
514} TextMateSessionLine;
515
516// Session lines result
517typedef struct {
518 TextMateSessionLine* lines;
519 int32_t lineCount;
520} TextMateSessionLinesResult;
521
522// Session metadata
523typedef struct {
524 uint64_t createdAtMs;
525 uint32_t referenceCount;
526 int32_t lineCount;
527 int32_t cachedLineCount;
528 uint64_t memoryUsageBytes;
529} TextMateSessionMetadata;
530
531extern "C" {
532
533using namespace tml;
534
535// ============================================================================
536// Session Lifecycle (from session_c_api.h)
537// ============================================================================
538
539TextMateSession textmate_session_create(TextMateGrammar grammar) {
540 if (!grammar) {
541 return 0;
542 }
543
544 // Grammar is a void* that we need to cast to IGrammar*
545 auto grammarPtr = static_cast<IGrammar*>(grammar);
546 auto grammarSharedPtr = std::shared_ptr<IGrammar>(grammarPtr, [](IGrammar*) {
547 // Don't delete - ownership remains with the registry
548 });
549
550 return SessionManager::createSession(grammarSharedPtr);
551}
552
553void textmate_session_retain(TextMateSession session) {
554 if (session == 0) return;
555 SessionManager::retainSession(session);
556}
557
558void textmate_session_release(TextMateSession session) {
559 if (session == 0) return;
560 SessionManager::releaseSession(session);
561}
562
563void textmate_session_dispose(TextMateSession session) {
564 if (session == 0) return;
565 SessionManager::disposeSession(session);
566}
567
568// ============================================================================
569// Session State Management
570// ============================================================================
571
572int textmate_session_set_lines(
573 TextMateSession session,
574 const char** lines,
575 int32_t lineCount
576) {
577 if (session == 0 || !lines || lineCount < 0) {
578 return 1;
579 }
580
581 auto sessionPtr = SessionManager::getSession(session);
582 if (!sessionPtr) {
583 return 1;
584 }
585
586 std::vector<std::string> lineVec;
587 for (int32_t i = 0; i < lineCount; i++) {
588 lineVec.push_back(lines[i] ? std::string(lines[i]) : std::string(""));
589 }
590
591 return sessionPtr->setLines(lineVec);
592}
593
594int32_t textmate_session_get_line_count(TextMateSession session) {
595 if (session == 0) {
596 return 0;
597 }
598
599 auto sessionPtr = SessionManager::getSession(session);
600 if (!sessionPtr) {
601 return 0;
602 }
603
604 return sessionPtr->getLineCount();
605}
606
607// ============================================================================
608// Incremental Tokenization Operations
609// ============================================================================
610
611int textmate_session_edit(
612 TextMateSession session,
613 const char** lines,
614 int32_t lineCount,
615 int32_t startIndex,
616 int32_t replaceCount
617) {
618 if (session == 0 || !lines || lineCount < 0) {
619 return 1;
620 }
621
622 auto sessionPtr = SessionManager::getSession(session);
623 if (!sessionPtr) {
624 return 1;
625 }
626
627 std::vector<std::string> lineVec;
628 for (int32_t i = 0; i < lineCount; i++) {
629 lineVec.push_back(lines[i] ? std::string(lines[i]) : std::string(""));
630 }
631
632 return sessionPtr->edit(lineVec, startIndex, replaceCount);
633}
634
635int textmate_session_add(
636 TextMateSession session,
637 const char** lines,
638 int32_t lineCount,
639 int32_t insertIndex
640) {
641 if (session == 0 || !lines || lineCount < 0) {
642 return 1;
643 }
644
645 auto sessionPtr = SessionManager::getSession(session);
646 if (!sessionPtr) {
647 return 1;
648 }
649
650 std::vector<std::string> lineVec;
651 for (int32_t i = 0; i < lineCount; i++) {
652 lineVec.push_back(lines[i] ? std::string(lines[i]) : std::string(""));
653 }
654
655 return sessionPtr->add(lineVec, insertIndex);
656}
657
658int textmate_session_remove(
659 TextMateSession session,
660 int32_t startIndex,
661 int32_t removeCount
662) {
663 if (session == 0) {
664 return 1;
665 }
666
667 auto sessionPtr = SessionManager::getSession(session);
668 if (!sessionPtr) {
669 return 1;
670 }
671
672 return sessionPtr->remove(startIndex, removeCount);
673}
674
675// ============================================================================
676// Query Operations
677// ============================================================================
678
679TextMateTokenizeResult* textmate_session_get_line_tokens(
680 TextMateSession session,
681 int32_t lineIndex
682) {
683 if (session == 0) {
684 return nullptr;
685 }
686
687 auto sessionPtr = SessionManager::getSession(session);
688 if (!sessionPtr) {
689 return nullptr;
690 }
691
692 const auto* tokens = sessionPtr->getLineTokens(lineIndex);
693 if (!tokens) {
694 return nullptr;
695 }
696
697 // Allocate result structure
698 auto* result = new TextMateTokenizeResult();
699 result->tokenCount = static_cast<int32_t>(tokens->size());
700
701 if (result->tokenCount > 0) {
702 result->tokens = new TextMateToken[result->tokenCount];
703
704 for (int32_t i = 0; i < result->tokenCount; i++) {
705 const auto& token = (*tokens)[i];
706 result->tokens[i].startIndex = token.startIndex;
707 result->tokens[i].endIndex = token.endIndex;
708 result->tokens[i].scopeDepth = static_cast<int32_t>(token.scopes.size());
709
710 // Allocate scope array
711 result->tokens[i].scopes = new char*[token.scopes.size()];
712 for (size_t j = 0; j < token.scopes.size(); j++) {
713 size_t scopeLen = token.scopes[j].length();
714 result->tokens[i].scopes[j] = new char[scopeLen + 1];
715 std::strcpy(result->tokens[i].scopes[j], token.scopes[j].c_str());
716 }
717 }
718 } else {
719 result->tokens = nullptr;
720 }
721
722 result->ruleStack = sessionPtr->getLineState(lineIndex);
723 result->stoppedEarly = 0;
724
725 return result;
726}
727
728TextMateStateStack textmate_session_get_line_state(
729 TextMateSession session,
730 int32_t lineIndex
731) {
732 if (session == 0) {
733 return nullptr;
734 }
735
736 auto sessionPtr = SessionManager::getSession(session);
737 if (!sessionPtr) {
738 return nullptr;
739 }
740
741 return sessionPtr->getLineState(lineIndex);
742}
743
744TextMateSessionLinesResult* textmate_session_get_tokens_range(
745 TextMateSession session,
746 int32_t startIndex,
747 int32_t endIndex
748) {
749 if (session == 0) {
750 return nullptr;
751 }
752
753 auto sessionPtr = SessionManager::getSession(session);
754 if (!sessionPtr) {
755 return nullptr;
756 }
757
758 auto* result = new TextMateSessionLinesResult();
759 std::vector<SessionLine> lines;
760 sessionPtr->getTokensRange(startIndex, endIndex, lines);
761
762 result->lineCount = static_cast<int32_t>(lines.size());
763
764 if (result->lineCount > 0) {
765 result->lines = new TextMateSessionLine[result->lineCount];
766
767 for (int32_t i = 0; i < result->lineCount; i++) {
768 const auto& line = lines[i];
769 result->lines[i].tokenCount = static_cast<int32_t>(line.tokens.size());
770 result->lines[i].state = line.state;
771 result->lines[i].version = line.version;
772
773 if (result->lines[i].tokenCount > 0) {
774 result->lines[i].tokens = new TextMateToken[result->lines[i].tokenCount];
775
776 for (int32_t j = 0; j < result->lines[i].tokenCount; j++) {
777 const auto& token = line.tokens[j];
778 result->lines[i].tokens[j].startIndex = token.startIndex;
779 result->lines[i].tokens[j].endIndex = token.endIndex;
780 result->lines[i].tokens[j].scopeDepth = static_cast<int32_t>(token.scopes.size());
781
782 result->lines[i].tokens[j].scopes = new char*[token.scopes.size()];
783 for (size_t k = 0; k < token.scopes.size(); k++) {
784 size_t scopeLen = token.scopes[k].length();
785 result->lines[i].tokens[j].scopes[k] = new char[scopeLen + 1];
786 std::strcpy(result->lines[i].tokens[j].scopes[k], token.scopes[k].c_str());
787 }
788 }
789 } else {
790 result->lines[i].tokens = nullptr;
791 }
792 }
793 } else {
794 result->lines = nullptr;
795 }
796
797 return result;
798}
799
800void textmate_session_free_tokens_result(
802) {
803 if (!result) {
804 return;
805 }
806
807 if (result->tokens) {
808 for (int32_t i = 0; i < result->tokenCount; i++) {
809 if (result->tokens[i].scopes) {
810 for (int j = 0; j < result->tokens[i].scopeDepth; j++) {
811 delete[] result->tokens[i].scopes[j];
812 }
813 delete[] result->tokens[i].scopes;
814 }
815 }
816 delete[] result->tokens;
817 }
818
819 delete result;
820}
821
822void textmate_session_free_lines_result(
823 TextMateSessionLinesResult* result
824) {
825 if (!result) {
826 return;
827 }
828
829 if (result->lines) {
830 for (int32_t i = 0; i < result->lineCount; i++) {
831 if (result->lines[i].tokens) {
832 for (int32_t j = 0; j < result->lines[i].tokenCount; j++) {
833 if (result->lines[i].tokens[j].scopes) {
834 for (int k = 0; k < result->lines[i].tokens[j].scopeDepth; k++) {
835 delete[] result->lines[i].tokens[j].scopes[k];
836 }
837 delete[] result->lines[i].tokens[j].scopes;
838 }
839 }
840 delete[] result->lines[i].tokens;
841 }
842 }
843 delete[] result->lines;
844 }
845
846 delete result;
847}
848
849// ============================================================================
850// Maintenance Operations
851// ============================================================================
852
853void textmate_session_invalidate_range(
854 TextMateSession session,
855 int32_t startIndex,
856 int32_t endIndex
857) {
858 if (session == 0) {
859 return;
860 }
861
862 auto sessionPtr = SessionManager::getSession(session);
863 if (!sessionPtr) {
864 return;
865 }
866
867 sessionPtr->invalidateRange(startIndex, endIndex);
868}
869
870void textmate_session_clear_cache(TextMateSession session) {
871 if (session == 0) {
872 return;
873 }
874
875 auto sessionPtr = SessionManager::getSession(session);
876 if (!sessionPtr) {
877 return;
878 }
879
880 sessionPtr->clearCache();
881}
882
883void textmate_session_cleanup_expired(int32_t maxAgeMs) {
884 SessionManager::cleanupExpired(maxAgeMs);
885}
886
887TextMateSessionMetadata textmate_session_get_metadata(
888 TextMateSession session
889) {
890 TextMateSessionMetadata metadata = {0, 0, 0, 0, 0};
891
892 if (session == 0) {
893 return metadata;
894 }
895
896 auto sessionPtr = SessionManager::getSession(session);
897 if (!sessionPtr) {
898 return metadata;
899 }
900
901 auto cppMetadata = sessionPtr->getMetadata();
902 metadata.createdAtMs = cppMetadata.createdAtMs;
903 metadata.referenceCount = cppMetadata.referenceCount;
904 metadata.lineCount = cppMetadata.lineCount;
905 metadata.cachedLineCount = cppMetadata.cachedLineCount;
906 metadata.memoryUsageBytes = cppMetadata.memoryUsageBytes;
907
908 return metadata;
909}
910
911} // extern "C"
Abstract interface representing the parsing state at the end of a line.
Definition types.h:55
void * TextMateGrammar
Handle to a grammar definition for a specific language.
Definition c_api.h:38
void * TextMateTheme
Handle to a theme object containing color schemes.
Definition c_api.h:35
void * TextMateStateStack
Handle to a parsing state stack (immutable, used for incremental tokenization)
Definition c_api.h:41
void * TextMateOnigLib
Handle to the Oniguruma regex library instance.
Definition c_api.h:44
Represents a single token in tokenized text.
Definition c_api.h:58
int32_t startIndex
Start position in the line (0-based)
Definition c_api.h:59
char ** scopes
Array of scope strings (e.g., "keyword.control", "string.quoted.double")
Definition c_api.h:62
int32_t scopeDepth
Number of scopes in the scope hierarchy.
Definition c_api.h:61
Result from tokenizing a single line with decoded tokens.
Definition c_api.h:72
TextMateToken * tokens
Array of tokens found in this line.
Definition c_api.h:73
int32_t tokenCount
Number of tokens in the array.
Definition c_api.h:74