14SessionLine::~SessionLine() {
15 if (state !=
nullptr) {
26SessionImpl::SessionImpl(uint64_t
id, std::shared_ptr<IGrammar> gram)
31 auto now = std::chrono::system_clock::now();
32 createdAtMs = std::chrono::duration_cast<std::chrono::milliseconds>(
33 now.time_since_epoch()
35 lastAccessMs = createdAtMs;
38SessionImpl::~SessionImpl() {
42void SessionImpl::retain() {
46void SessionImpl::release() {
47 if (referenceCount > 0) {
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()
59bool SessionImpl::isExpired(uint64_t currentTimeMs, uint64_t maxAgeMs)
const {
60 return (currentTimeMs - createdAtMs) > maxAgeMs;
64 if (state1 == state2) {
67 if (state1 ==
nullptr || state2 ==
nullptr) {
72 return std::string(
reinterpret_cast<char*
>(state1)) ==
73 std::string(
reinterpret_cast<char*
>(state2));
76void SessionImpl::invalidateFrom(int32_t startIndex) {
77 if (startIndex < 0 || startIndex >=
static_cast<int32_t
>(lines.size())) {
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;
87 lines[i].version = nextVersion;
92int32_t SessionImpl::setLines(
const std::vector<std::string>& newLines) {
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;
109 retokenizeLines(0,
static_cast<int32_t
>(lines.size()) - 1);
114void SessionImpl::retokenizeLines(int32_t startIndex, int32_t endIndex) {
115 if (!grammar || startIndex < 0 || startIndex > endIndex) {
119 endIndex = std::min(endIndex,
static_cast<int32_t
>(lines.size()) - 1);
123 if (startIndex > 0 && lines[startIndex - 1].cached && lines[startIndex - 1].state) {
124 state = lines[startIndex - 1].state;
129 StackNodeArena* prevArena = tmlGetActiveArena();
130 tmlSetActiveArena(&stateArena);
133 for (int32_t i = startIndex; i <= endIndex; i++) {
134 ITokenizeLineResult result = grammar->tokenizeLine(lines[i].content, state);
136 lines[i].tokens = result.tokens;
137 lines[i].state = result.ruleStack;
138 lines[i].cached =
true;
139 lines[i].version = nextVersion;
142 if (i <
static_cast<int32_t
>(lines.size()) - 1) {
143 if (lines[i + 1].cached &&
144 isStateEqual(result.ruleStack, lines[i + 1].state)) {
150 state = result.ruleStack;
153 tmlSetActiveArena(prevArena);
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));
165 stateArena.sweepKeeping(roots);
168int32_t SessionImpl::edit(
169 const std::vector<std::string>& newLines,
176 if (startIndex < 0 || startIndex >
static_cast<int32_t
>(lines.size())) {
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;
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];
196 invalidateFrom(startIndex);
199 if (startIndex <
static_cast<int32_t
>(lines.size())) {
200 retokenizeLines(startIndex,
static_cast<int32_t
>(lines.size()) - 1);
206int32_t SessionImpl::add(
207 const std::vector<std::string>& newLines,
213 if (insertIndex < 0 || insertIndex >
static_cast<int32_t
>(lines.size())) {
219 lines.begin() + insertIndex,
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;
233 invalidateFrom(insertIndex);
236 if (insertIndex <
static_cast<int32_t
>(lines.size())) {
237 retokenizeLines(insertIndex,
static_cast<int32_t
>(lines.size()) - 1);
243int32_t SessionImpl::remove(
250 if (startIndex < 0 || startIndex >=
static_cast<int32_t
>(lines.size()) ||
255 int32_t endIndex = std::min(
256 static_cast<int32_t
>(lines.size()),
257 startIndex + removeCount
261 lines.erase(lines.begin() + startIndex, lines.begin() + endIndex);
264 if (startIndex <
static_cast<int32_t
>(lines.size())) {
265 invalidateFrom(startIndex);
268 retokenizeLines(startIndex,
static_cast<int32_t
>(lines.size()) - 1);
274const SessionLine* SessionImpl::getLine(int32_t lineIndex)
const {
275 if (lineIndex < 0 || lineIndex >=
static_cast<int32_t
>(lines.size())) {
278 return &lines[lineIndex];
281const std::vector<IToken>* SessionImpl::getLineTokens(int32_t lineIndex)
const {
282 const SessionLine* line = getLine(lineIndex);
283 if (line ==
nullptr || !line->cached) {
286 return &line->tokens;
289StateStack* SessionImpl::getLineState(int32_t lineIndex)
const {
290 const SessionLine* line = getLine(lineIndex);
291 if (line ==
nullptr || !line->cached) {
297void SessionImpl::getTokensRange(
300 std::vector<SessionLine>& results
304 if (startIndex < 0 || endIndex >=
static_cast<int32_t
>(lines.size()) ||
305 startIndex > endIndex) {
309 for (int32_t i = startIndex; i <= endIndex; i++) {
310 if (i <
static_cast<int32_t
>(lines.size())) {
311 results.push_back(lines[i]);
316void SessionImpl::invalidateRange(int32_t startIndex, int32_t endIndex) {
319 if (startIndex < 0 || startIndex >=
static_cast<int32_t
>(lines.size())) {
323 if (endIndex < 0 || endIndex >=
static_cast<int32_t
>(lines.size())) {
324 endIndex =
static_cast<int32_t
>(lines.size()) - 1;
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;
333 lines[i].version = nextVersion;
338 if (startIndex <
static_cast<int32_t
>(lines.size())) {
339 retokenizeLines(startIndex, endIndex);
343void SessionImpl::clearCache() {
344 for (
auto& line : lines) {
347 if (line.state !=
nullptr) {
348 line.state =
nullptr;
356uint64_t SessionImpl::calculateMemoryUsage()
const {
357 uint64_t usage =
sizeof(SessionImpl);
360 usage += lines.capacity() *
sizeof(SessionLine);
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();
376int32_t SessionImpl::countCachedLines()
const {
378 for (
const auto& line : lines) {
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();
400std::map<uint64_t, std::shared_ptr<SessionImpl>> SessionManager::sessions;
401uint64_t SessionManager::nextSessionId = 1;
402uint32_t SessionManager::operationCount = 0;
404uint64_t SessionManager::createSession(std::shared_ptr<IGrammar> grammar) {
409 uint64_t sessionId = nextSessionId++;
410 auto session = std::make_shared<SessionImpl>(sessionId, grammar);
411 sessions[sessionId] = session;
414 triggerPeriodicCleanup(60000);
419std::shared_ptr<SessionImpl> SessionManager::getSession(uint64_t sessionId) {
420 auto it = sessions.find(sessionId);
421 if (it != sessions.end()) {
427void SessionManager::retainSession(uint64_t sessionId) {
428 auto session = getSession(sessionId);
434void SessionManager::releaseSession(uint64_t sessionId) {
435 auto session = getSession(sessionId);
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) {
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()
457 for (
auto it = sessions.begin(); it != sessions.end();) {
458 if (it->second->isExpired(currentTimeMs, maxAgeMs)) {
459 it = sessions.erase(it);
466void SessionManager::triggerPeriodicCleanup(int32_t maxAgeMs) {
468 if (operationCount % CLEANUP_INTERVAL == 0) {
469 cleanupExpired(maxAgeMs);
473size_t SessionManager::getSessionCount() {
474 return sessions.size();
502 int32_t stoppedEarly;
506typedef uint64_t TextMateSession;
514} TextMateSessionLine;
518 TextMateSessionLine* lines;
520} TextMateSessionLinesResult;
524 uint64_t createdAtMs;
525 uint32_t referenceCount;
527 int32_t cachedLineCount;
528 uint64_t memoryUsageBytes;
529} TextMateSessionMetadata;
545 auto grammarPtr =
static_cast<IGrammar*
>(grammar);
546 auto grammarSharedPtr = std::shared_ptr<IGrammar>(grammarPtr, [](IGrammar*) {
550 return SessionManager::createSession(grammarSharedPtr);
553void textmate_session_retain(TextMateSession session) {
554 if (session == 0)
return;
555 SessionManager::retainSession(session);
558void textmate_session_release(TextMateSession session) {
559 if (session == 0)
return;
560 SessionManager::releaseSession(session);
563void textmate_session_dispose(TextMateSession session) {
564 if (session == 0)
return;
565 SessionManager::disposeSession(session);
572int textmate_session_set_lines(
573 TextMateSession session,
577 if (session == 0 || !lines || lineCount < 0) {
581 auto sessionPtr = SessionManager::getSession(session);
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(
""));
591 return sessionPtr->setLines(lineVec);
594int32_t textmate_session_get_line_count(TextMateSession session) {
599 auto sessionPtr = SessionManager::getSession(session);
604 return sessionPtr->getLineCount();
611int textmate_session_edit(
612 TextMateSession session,
618 if (session == 0 || !lines || lineCount < 0) {
622 auto sessionPtr = SessionManager::getSession(session);
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(
""));
632 return sessionPtr->edit(lineVec, startIndex, replaceCount);
635int textmate_session_add(
636 TextMateSession session,
641 if (session == 0 || !lines || lineCount < 0) {
645 auto sessionPtr = SessionManager::getSession(session);
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(
""));
655 return sessionPtr->add(lineVec, insertIndex);
658int textmate_session_remove(
659 TextMateSession session,
667 auto sessionPtr = SessionManager::getSession(session);
672 return sessionPtr->remove(startIndex, removeCount);
680 TextMateSession session,
687 auto sessionPtr = SessionManager::getSession(session);
692 const auto* tokens = sessionPtr->getLineTokens(lineIndex);
699 result->tokenCount =
static_cast<int32_t
>(tokens->size());
701 if (result->tokenCount > 0) {
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());
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());
719 result->tokens =
nullptr;
722 result->ruleStack = sessionPtr->getLineState(lineIndex);
723 result->stoppedEarly = 0;
729 TextMateSession session,
736 auto sessionPtr = SessionManager::getSession(session);
741 return sessionPtr->getLineState(lineIndex);
744TextMateSessionLinesResult* textmate_session_get_tokens_range(
745 TextMateSession session,
753 auto sessionPtr = SessionManager::getSession(session);
758 auto* result =
new TextMateSessionLinesResult();
759 std::vector<SessionLine> lines;
760 sessionPtr->getTokensRange(startIndex, endIndex, lines);
762 result->lineCount =
static_cast<int32_t
>(lines.size());
764 if (result->lineCount > 0) {
765 result->lines =
new TextMateSessionLine[result->lineCount];
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;
773 if (result->lines[i].tokenCount > 0) {
774 result->lines[i].tokens =
new TextMateToken[result->lines[i].tokenCount];
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());
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());
790 result->lines[i].tokens =
nullptr;
794 result->lines =
nullptr;
800void textmate_session_free_tokens_result(
808 for (int32_t i = 0; i < result->
tokenCount; i++) {
822void textmate_session_free_lines_result(
823 TextMateSessionLinesResult* result
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];
837 delete[] result->lines[i].tokens[j].scopes;
840 delete[] result->lines[i].tokens;
843 delete[] result->lines;
853void textmate_session_invalidate_range(
854 TextMateSession session,
862 auto sessionPtr = SessionManager::getSession(session);
867 sessionPtr->invalidateRange(startIndex, endIndex);
870void textmate_session_clear_cache(TextMateSession session) {
875 auto sessionPtr = SessionManager::getSession(session);
880 sessionPtr->clearCache();
883void textmate_session_cleanup_expired(int32_t maxAgeMs) {
884 SessionManager::cleanupExpired(maxAgeMs);
887TextMateSessionMetadata textmate_session_get_metadata(
888 TextMateSession session
890 TextMateSessionMetadata metadata = {0, 0, 0, 0, 0};
896 auto sessionPtr = SessionManager::getSession(session);
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;
Abstract interface representing the parsing state at the end of a line.
void * TextMateGrammar
Handle to a grammar definition for a specific language.
void * TextMateTheme
Handle to a theme object containing color schemes.
void * TextMateStateStack
Handle to a parsing state stack (immutable, used for incremental tokenization)
void * TextMateOnigLib
Handle to the Oniguruma regex library instance.
Represents a single token in tokenized text.
int32_t startIndex
Start position in the line (0-based)
char ** scopes
Array of scope strings (e.g., "keyword.control", "string.quoted.double")
int32_t scopeDepth
Number of scopes in the scope hierarchy.
Result from tokenizing a single line with decoded tokens.
TextMateToken * tokens
Array of tokens found in this line.
int32_t tokenCount
Number of tokens in the array.