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;
128 for (int32_t i = startIndex; i <= endIndex; i++) {
129 ITokenizeLineResult result = grammar->tokenizeLine(lines[i].content, state);
131 lines[i].tokens = result.tokens;
132 lines[i].state = result.ruleStack;
133 lines[i].cached =
true;
134 lines[i].version = nextVersion;
137 if (i <
static_cast<int32_t
>(lines.size()) - 1) {
138 if (lines[i + 1].cached &&
139 isStateEqual(result.ruleStack, lines[i + 1].state)) {
145 state = result.ruleStack;
149int32_t SessionImpl::edit(
150 const std::vector<std::string>& newLines,
157 if (startIndex < 0 || startIndex >
static_cast<int32_t
>(lines.size())) {
161 int32_t endIndex = startIndex + replaceCount - 1;
162 if (endIndex >=
static_cast<int32_t
>(lines.size())) {
163 endIndex =
static_cast<int32_t
>(lines.size()) - 1;
167 if (replaceCount > 0 && startIndex <
static_cast<int32_t
>(lines.size())) {
168 for (int32_t i = 0; i < static_cast<int32_t>(newLines.size()); i++) {
169 int32_t targetIdx = startIndex + i;
170 if (targetIdx <
static_cast<int32_t
>(lines.size())) {
171 lines[targetIdx].content = newLines[i];
177 invalidateFrom(startIndex);
180 if (startIndex <
static_cast<int32_t
>(lines.size())) {
181 retokenizeLines(startIndex,
static_cast<int32_t
>(lines.size()) - 1);
187int32_t SessionImpl::add(
188 const std::vector<std::string>& newLines,
194 if (insertIndex < 0 || insertIndex >
static_cast<int32_t
>(lines.size())) {
200 lines.begin() + insertIndex,
204 for (
size_t i = 0; i < newLines.size(); i++) {
205 int32_t targetIdx = insertIndex + i;
206 if (targetIdx <
static_cast<int32_t
>(lines.size())) {
207 lines[targetIdx].content = newLines[i];
208 lines[targetIdx].cached =
false;
209 lines[targetIdx].state =
nullptr;
214 invalidateFrom(insertIndex);
217 if (insertIndex <
static_cast<int32_t
>(lines.size())) {
218 retokenizeLines(insertIndex,
static_cast<int32_t
>(lines.size()) - 1);
224int32_t SessionImpl::remove(
231 if (startIndex < 0 || startIndex >=
static_cast<int32_t
>(lines.size()) ||
236 int32_t endIndex = std::min(
237 static_cast<int32_t
>(lines.size()),
238 startIndex + removeCount
242 lines.erase(lines.begin() + startIndex, lines.begin() + endIndex);
245 if (startIndex <
static_cast<int32_t
>(lines.size())) {
246 invalidateFrom(startIndex);
249 retokenizeLines(startIndex,
static_cast<int32_t
>(lines.size()) - 1);
255const SessionLine* SessionImpl::getLine(int32_t lineIndex)
const {
256 if (lineIndex < 0 || lineIndex >=
static_cast<int32_t
>(lines.size())) {
259 return &lines[lineIndex];
262const std::vector<IToken>* SessionImpl::getLineTokens(int32_t lineIndex)
const {
263 const SessionLine* line = getLine(lineIndex);
264 if (line ==
nullptr || !line->cached) {
267 return &line->tokens;
270StateStack* SessionImpl::getLineState(int32_t lineIndex)
const {
271 const SessionLine* line = getLine(lineIndex);
272 if (line ==
nullptr || !line->cached) {
278void SessionImpl::getTokensRange(
281 std::vector<SessionLine>& results
285 if (startIndex < 0 || endIndex >=
static_cast<int32_t
>(lines.size()) ||
286 startIndex > endIndex) {
290 for (int32_t i = startIndex; i <= endIndex; i++) {
291 if (i <
static_cast<int32_t
>(lines.size())) {
292 results.push_back(lines[i]);
297void SessionImpl::invalidateRange(int32_t startIndex, int32_t endIndex) {
300 if (startIndex < 0 || startIndex >=
static_cast<int32_t
>(lines.size())) {
304 if (endIndex < 0 || endIndex >=
static_cast<int32_t
>(lines.size())) {
305 endIndex =
static_cast<int32_t
>(lines.size()) - 1;
308 for (int32_t i = startIndex; i <= endIndex; i++) {
309 lines[i].cached =
false;
310 lines[i].tokens.clear();
311 if (lines[i].state !=
nullptr) {
312 lines[i].state =
nullptr;
314 lines[i].version = nextVersion;
319 if (startIndex <
static_cast<int32_t
>(lines.size())) {
320 retokenizeLines(startIndex, endIndex);
324void SessionImpl::clearCache() {
325 for (
auto& line : lines) {
328 if (line.state !=
nullptr) {
329 line.state =
nullptr;
335uint64_t SessionImpl::calculateMemoryUsage()
const {
336 uint64_t usage =
sizeof(SessionImpl);
339 usage += lines.capacity() *
sizeof(SessionLine);
342 for (
const auto& line : lines) {
343 usage += line.content.capacity();
344 usage += line.tokens.capacity() *
sizeof(IToken);
345 for (
const auto& token : line.tokens) {
346 for (
const auto& scope : token.scopes) {
347 usage += scope.capacity();
355int32_t SessionImpl::countCachedLines()
const {
357 for (
const auto& line : lines) {
365SessionMetadata SessionImpl::getMetadata()
const {
366 SessionMetadata metadata;
367 metadata.createdAtMs = createdAtMs;
368 metadata.referenceCount = referenceCount;
369 metadata.lineCount =
static_cast<int32_t
>(lines.size());
370 metadata.cachedLineCount = countCachedLines();
371 metadata.memoryUsageBytes = calculateMemoryUsage();
379std::map<uint64_t, std::shared_ptr<SessionImpl>> SessionManager::sessions;
380uint64_t SessionManager::nextSessionId = 1;
381uint32_t SessionManager::operationCount = 0;
383uint64_t SessionManager::createSession(std::shared_ptr<IGrammar> grammar) {
388 uint64_t sessionId = nextSessionId++;
389 auto session = std::make_shared<SessionImpl>(sessionId, grammar);
390 sessions[sessionId] = session;
393 triggerPeriodicCleanup(60000);
398std::shared_ptr<SessionImpl> SessionManager::getSession(uint64_t sessionId) {
399 auto it = sessions.find(sessionId);
400 if (it != sessions.end()) {
406void SessionManager::retainSession(uint64_t sessionId) {
407 auto session = getSession(sessionId);
413void SessionManager::releaseSession(uint64_t sessionId) {
414 auto session = getSession(sessionId);
420void SessionManager::disposeSession(uint64_t sessionId) {
421 auto it = sessions.find(sessionId);
422 if (it != sessions.end()) {
423 it->second->release();
424 if (it->second->getRefCount() == 0) {
430void SessionManager::cleanupExpired(int32_t maxAgeMs) {
431 auto now = std::chrono::system_clock::now();
432 uint64_t currentTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(
433 now.time_since_epoch()
436 for (
auto it = sessions.begin(); it != sessions.end();) {
437 if (it->second->isExpired(currentTimeMs, maxAgeMs)) {
438 it = sessions.erase(it);
445void SessionManager::triggerPeriodicCleanup(int32_t maxAgeMs) {
447 if (operationCount % CLEANUP_INTERVAL == 0) {
448 cleanupExpired(maxAgeMs);
452size_t SessionManager::getSessionCount() {
453 return sessions.size();
481 int32_t stoppedEarly;
485typedef uint64_t TextMateSession;
493} TextMateSessionLine;
497 TextMateSessionLine* lines;
499} TextMateSessionLinesResult;
503 uint64_t createdAtMs;
504 uint32_t referenceCount;
506 int32_t cachedLineCount;
507 uint64_t memoryUsageBytes;
508} TextMateSessionMetadata;
524 auto grammarPtr =
static_cast<IGrammar*
>(grammar);
525 auto grammarSharedPtr = std::shared_ptr<IGrammar>(grammarPtr, [](IGrammar*) {
529 return SessionManager::createSession(grammarSharedPtr);
532void textmate_session_retain(TextMateSession session) {
533 if (session == 0)
return;
534 SessionManager::retainSession(session);
537void textmate_session_release(TextMateSession session) {
538 if (session == 0)
return;
539 SessionManager::releaseSession(session);
542void textmate_session_dispose(TextMateSession session) {
543 if (session == 0)
return;
544 SessionManager::disposeSession(session);
551int textmate_session_set_lines(
552 TextMateSession session,
556 if (session == 0 || !lines || lineCount < 0) {
560 auto sessionPtr = SessionManager::getSession(session);
565 std::vector<std::string> lineVec;
566 for (int32_t i = 0; i < lineCount; i++) {
567 lineVec.push_back(lines[i] ? std::string(lines[i]) : std::string(
""));
570 return sessionPtr->setLines(lineVec);
573int32_t textmate_session_get_line_count(TextMateSession session) {
578 auto sessionPtr = SessionManager::getSession(session);
583 return sessionPtr->getLineCount();
590int textmate_session_edit(
591 TextMateSession session,
597 if (session == 0 || !lines || lineCount < 0) {
601 auto sessionPtr = SessionManager::getSession(session);
606 std::vector<std::string> lineVec;
607 for (int32_t i = 0; i < lineCount; i++) {
608 lineVec.push_back(lines[i] ? std::string(lines[i]) : std::string(
""));
611 return sessionPtr->edit(lineVec, startIndex, replaceCount);
614int textmate_session_add(
615 TextMateSession session,
620 if (session == 0 || !lines || lineCount < 0) {
624 auto sessionPtr = SessionManager::getSession(session);
629 std::vector<std::string> lineVec;
630 for (int32_t i = 0; i < lineCount; i++) {
631 lineVec.push_back(lines[i] ? std::string(lines[i]) : std::string(
""));
634 return sessionPtr->add(lineVec, insertIndex);
637int textmate_session_remove(
638 TextMateSession session,
646 auto sessionPtr = SessionManager::getSession(session);
651 return sessionPtr->remove(startIndex, removeCount);
659 TextMateSession session,
666 auto sessionPtr = SessionManager::getSession(session);
671 const auto* tokens = sessionPtr->getLineTokens(lineIndex);
678 result->tokenCount =
static_cast<int32_t
>(tokens->size());
680 if (result->tokenCount > 0) {
683 for (int32_t i = 0; i < result->tokenCount; i++) {
684 const auto& token = (*tokens)[i];
685 result->tokens[i].
startIndex = token.startIndex;
686 result->tokens[i].endIndex = token.endIndex;
687 result->tokens[i].scopeDepth =
static_cast<int32_t
>(token.scopes.size());
690 result->tokens[i].scopes =
new char*[token.scopes.size()];
691 for (
size_t j = 0; j < token.scopes.size(); j++) {
692 size_t scopeLen = token.scopes[j].length();
693 result->tokens[i].scopes[j] =
new char[scopeLen + 1];
694 std::strcpy(result->tokens[i].scopes[j], token.scopes[j].c_str());
698 result->tokens =
nullptr;
701 result->ruleStack = sessionPtr->getLineState(lineIndex);
702 result->stoppedEarly = 0;
708 TextMateSession session,
715 auto sessionPtr = SessionManager::getSession(session);
720 return sessionPtr->getLineState(lineIndex);
723TextMateSessionLinesResult* textmate_session_get_tokens_range(
724 TextMateSession session,
732 auto sessionPtr = SessionManager::getSession(session);
737 auto* result =
new TextMateSessionLinesResult();
738 std::vector<SessionLine> lines;
739 sessionPtr->getTokensRange(startIndex, endIndex, lines);
741 result->lineCount =
static_cast<int32_t
>(lines.size());
743 if (result->lineCount > 0) {
744 result->lines =
new TextMateSessionLine[result->lineCount];
746 for (int32_t i = 0; i < result->lineCount; i++) {
747 const auto& line = lines[i];
748 result->lines[i].tokenCount =
static_cast<int32_t
>(line.tokens.size());
749 result->lines[i].state = line.state;
750 result->lines[i].version = line.version;
752 if (result->lines[i].tokenCount > 0) {
753 result->lines[i].tokens =
new TextMateToken[result->lines[i].tokenCount];
755 for (int32_t j = 0; j < result->lines[i].tokenCount; j++) {
756 const auto& token = line.tokens[j];
757 result->lines[i].tokens[j].
startIndex = token.startIndex;
758 result->lines[i].tokens[j].endIndex = token.endIndex;
759 result->lines[i].tokens[j].scopeDepth =
static_cast<int32_t
>(token.scopes.size());
761 result->lines[i].tokens[j].scopes =
new char*[token.scopes.size()];
762 for (
size_t k = 0; k < token.scopes.size(); k++) {
763 size_t scopeLen = token.scopes[k].length();
764 result->lines[i].tokens[j].scopes[k] =
new char[scopeLen + 1];
765 std::strcpy(result->lines[i].tokens[j].scopes[k], token.scopes[k].c_str());
769 result->lines[i].tokens =
nullptr;
773 result->lines =
nullptr;
779void textmate_session_free_tokens_result(
787 for (int32_t i = 0; i < result->
tokenCount; i++) {
801void textmate_session_free_lines_result(
802 TextMateSessionLinesResult* result
809 for (int32_t i = 0; i < result->lineCount; i++) {
810 if (result->lines[i].tokens) {
811 for (int32_t j = 0; j < result->lines[i].tokenCount; j++) {
812 if (result->lines[i].tokens[j].scopes) {
813 for (
int k = 0; k < result->lines[i].tokens[j].scopeDepth; k++) {
814 delete[] result->lines[i].tokens[j].scopes[k];
816 delete[] result->lines[i].tokens[j].scopes;
819 delete[] result->lines[i].tokens;
822 delete[] result->lines;
832void textmate_session_invalidate_range(
833 TextMateSession session,
841 auto sessionPtr = SessionManager::getSession(session);
846 sessionPtr->invalidateRange(startIndex, endIndex);
849void textmate_session_clear_cache(TextMateSession session) {
854 auto sessionPtr = SessionManager::getSession(session);
859 sessionPtr->clearCache();
862void textmate_session_cleanup_expired(int32_t maxAgeMs) {
863 SessionManager::cleanupExpired(maxAgeMs);
866TextMateSessionMetadata textmate_session_get_metadata(
867 TextMateSession session
869 TextMateSessionMetadata metadata = {0, 0, 0, 0, 0};
875 auto sessionPtr = SessionManager::getSession(session);
880 auto cppMetadata = sessionPtr->getMetadata();
881 metadata.createdAtMs = cppMetadata.createdAtMs;
882 metadata.referenceCount = cppMetadata.referenceCount;
883 metadata.lineCount = cppMetadata.lineCount;
884 metadata.cachedLineCount = cppMetadata.cachedLineCount;
885 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.