TextMateLib 1.0
Modern C++ implementation of the TextMate syntax highlighting engine
Loading...
Searching...
No Matches
c_api.cpp
1#include "c_api.h"
2#include "main.h"
3#include "parseRawGrammar.h"
4#include "parseRawTheme.h"
5#include "theme.h"
6#include "utf16_utils.h"
7#include <string>
8#include <cstring>
9#include <memory>
10#include <fstream>
11#include <sstream>
12#include <cctype>
13#include <rapidjson/document.h>
14#include <rapidjson/error/en.h>
15
16using namespace tml;
17using namespace rapidjson;
18
19// Helper function to read file contents
20static std::string readFileContents(const char* filepath) {
21 std::ifstream file(filepath);
22 if (!file.is_open()) {
23 return "";
24 }
25 std::stringstream buffer;
26 buffer << file.rdbuf();
27 return buffer.str();
28}
29
30// Helper function to convert std::string to C string (caller must free)
31static char* stringToCString(const std::string& str) {
32 char* cstr = new char[str.length() + 1];
33 std::strcpy(cstr, str.c_str());
34 return cstr;
35}
36
37// ============================================================================
38// Theme Helper Functions
39// ============================================================================
40
41// Convert hex color string (#RRGGBB or #RRGGBBAA) to uint32_t (0xRRGGBBAA)
42static uint32_t hexColorToUint32(const std::string& hexColor) {
43 if (hexColor.empty() || hexColor[0] != '#') {
44 return 0;
45 }
46
47 std::string hex = hexColor.substr(1);
48
49 // Handle 6-char (#RRGGBB) - add full opacity
50 if (hex.length() == 6) {
51 hex += "FF";
52 }
53 // Handle 8-char (#RRGGBBAA) - convert to RGBA format
54 else if (hex.length() != 8) {
55 return 0;
56 }
57
58 try {
59 uint32_t value = std::stoul(hex, nullptr, 16);
60 // Convert from #RRGGBBAA to 0xRRGGBBAA
61 return value;
62 } catch (...) {
63 return 0;
64 }
65}
66
67// Convert font style string to flags
68static int32_t fontStyleStringToFlags(const std::string& fontStyle) {
69 int32_t flags = TEXTMATE_FONT_STYLE_NONE;
70
71 if (fontStyle.find("italic") != std::string::npos) {
73 }
74 if (fontStyle.find("bold") != std::string::npos) {
76 }
77 if (fontStyle.find("underline") != std::string::npos) {
79 }
80
81 return flags;
82}
83
84// Helper to free ScopeStack linked list created by ScopeStack::from()
85static void freeScopeStack(ScopeStack* stack) {
86 while (stack) {
87 ScopeStack* parent = stack->parent;
88 delete stack;
89 stack = parent;
90 }
91}
92
93// Parse space-separated scope path into vector of scope names
94static std::vector<ScopeName> parseScopePath(const char* scopePath) {
95 std::vector<ScopeName> scopes;
96 if (!scopePath || !scopePath[0]) {
97 return scopes;
98 }
99
100 std::istringstream iss(scopePath);
101 std::string scope;
102 while (iss >> scope) {
103 scopes.push_back(scope);
104 }
105 return scopes;
106}
107
108// Match all scopes in the stack against the theme and merge results
109// This iterates from outermost to innermost scope, with inner scopes overwriting outer ones
110static StyleAttributes* matchAllScopes(Theme* theme, const std::vector<ScopeName>& scopes) {
111 if (scopes.empty()) {
112 return nullptr;
113 }
114
115 StyleAttributes* merged = nullptr;
116
117 // Iterate through all scopes from outermost to innermost
118 // Build a progressively deeper scope stack for each match
119 for (size_t i = 0; i < scopes.size(); i++) {
120 // Create a scope stack up to this scope
121 ScopeStack* scopeStack = nullptr;
122 for (size_t j = 0; j <= i; j++) {
123 scopeStack = new ScopeStack(scopeStack, scopes[j]);
124 }
125
126 // Match against theme
127 StyleAttributes* attrs = theme->match(scopeStack);
128
129 // Clean up scope stack
130 freeScopeStack(scopeStack);
131
132 // Merge results
133 if (attrs) {
134 if (!merged) {
135 merged = attrs;
136 } else {
137 // Inner scope attributes override outer ones (if set)
138 if (attrs->fontStyle != static_cast<int>(FontStyle::NotSet)) {
139 merged->fontStyle = attrs->fontStyle;
140 }
141 if (attrs->foregroundId != 0) {
142 merged->foregroundId = attrs->foregroundId;
143 }
144 if (attrs->backgroundId != 0) {
145 merged->backgroundId = attrs->backgroundId;
146 }
147 delete attrs;
148 }
149 }
150 }
151
152 return merged;
153}
154
155// Parse JSON theme and create Theme object
156// Use the shared parseJSONTheme from parseRawTheme.h
157// (Implementation moved to parseRawTheme.cpp to avoid duplication)
158
159// ============================================================================
160// Theme C API Implementation
161// ============================================================================
162
164 if (!themePath) {
165 return nullptr;
166 }
167
168 try {
169 std::string content = readFileContents(themePath);
170 if (content.empty()) {
171 return nullptr;
172 }
173
174 Theme* theme = parseJSONTheme(content);
175 if (!theme) {
176 return nullptr;
177 }
178
179 StyleAttributes* defaults = theme->getDefaults();
180 if (!defaults) {
181 delete theme;
182 return nullptr;
183 }
184
185 auto managed = new ManagedTheme(theme, defaults);
186 return static_cast<TextMateTheme>(managed);
187 } catch (...) {
188 return nullptr;
189 }
190}
191
193 if (!jsonContent) {
194 return nullptr;
195 }
196
197 try {
198 Theme* theme = parseJSONTheme(jsonContent);
199 if (!theme) {
200 return nullptr;
201 }
202
203 StyleAttributes* defaults = theme->getDefaults();
204 if (!defaults) {
205 delete theme;
206 return nullptr;
207 }
208
209 auto managed = new ManagedTheme(theme, defaults);
210 return static_cast<TextMateTheme>(managed);
211 } catch (...) {
212 return nullptr;
213 }
214}
215
217 TextMateTheme theme,
218 const char* scopePath,
219 uint32_t defaultColor
220) {
221 if (!theme) {
222 return defaultColor;
223 }
224
225 try {
226 auto managed = static_cast<ManagedTheme*>(theme);
227 auto colorMap = managed->theme->getColorMap();
228
229 // If no scope path provided, return default foreground
230 if (!scopePath || !scopePath[0]) {
231 if (managed->defaults) {
232 int fgId = managed->defaults->foregroundId;
233 if (fgId > 0 && fgId < static_cast<int>(colorMap.size())) {
234 return hexColorToUint32(colorMap[fgId]);
235 }
236 }
237 return defaultColor;
238 }
239
240 // Parse scope path
241 std::vector<ScopeName> scopes = parseScopePath(scopePath);
242 if (scopes.empty()) {
243 if (managed->defaults) {
244 int fgId = managed->defaults->foregroundId;
245 if (fgId > 0 && fgId < static_cast<int>(colorMap.size())) {
246 return hexColorToUint32(colorMap[fgId]);
247 }
248 }
249 return defaultColor;
250 }
251
252 // Match all scopes against theme and merge results
253 StyleAttributes* attrs = matchAllScopes(managed->theme, scopes);
254
255 // Get color from matched attributes
256 if (attrs) {
257 int fgId = attrs->foregroundId;
258 delete attrs;
259
260 if (fgId > 0 && fgId < static_cast<int>(colorMap.size())) {
261 return hexColorToUint32(colorMap[fgId]);
262 }
263 }
264
265 // Fall back to default foreground
266 if (managed->defaults) {
267 int fgId = managed->defaults->foregroundId;
268 if (fgId > 0 && fgId < static_cast<int>(colorMap.size())) {
269 return hexColorToUint32(colorMap[fgId]);
270 }
271 }
272
273 return defaultColor;
274 } catch (...) {
275 return defaultColor;
276 }
277}
278
280 TextMateTheme theme,
281 const char* scopePath,
282 uint32_t defaultColor
283) {
284 if (!theme) {
285 return defaultColor;
286 }
287
288 try {
289 auto managed = static_cast<ManagedTheme*>(theme);
290 auto colorMap = managed->theme->getColorMap();
291
292 // If no scope path provided, return default background
293 if (!scopePath || !scopePath[0]) {
294 if (managed->defaults) {
295 int bgId = managed->defaults->backgroundId;
296 if (bgId > 0 && bgId < static_cast<int>(colorMap.size())) {
297 return hexColorToUint32(colorMap[bgId]);
298 }
299 }
300 return defaultColor;
301 }
302
303 // Parse scope path
304 std::vector<ScopeName> scopes = parseScopePath(scopePath);
305 if (scopes.empty()) {
306 if (managed->defaults) {
307 int bgId = managed->defaults->backgroundId;
308 if (bgId > 0 && bgId < static_cast<int>(colorMap.size())) {
309 return hexColorToUint32(colorMap[bgId]);
310 }
311 }
312 return defaultColor;
313 }
314
315 // Match all scopes against theme and merge results
316 StyleAttributes* attrs = matchAllScopes(managed->theme, scopes);
317
318 // Get color from matched attributes
319 if (attrs) {
320 int bgId = attrs->backgroundId;
321 delete attrs;
322
323 if (bgId > 0 && bgId < static_cast<int>(colorMap.size())) {
324 return hexColorToUint32(colorMap[bgId]);
325 }
326 }
327
328 // Fall back to default background
329 if (managed->defaults) {
330 int bgId = managed->defaults->backgroundId;
331 if (bgId > 0 && bgId < static_cast<int>(colorMap.size())) {
332 return hexColorToUint32(colorMap[bgId]);
333 }
334 }
335
336 return defaultColor;
337 } catch (...) {
338 return defaultColor;
339 }
340}
341
343 TextMateTheme theme,
344 const char* scopePath,
345 int32_t defaultStyle
346) {
347 if (!theme) {
348 return defaultStyle;
349 }
350
351 try {
352 auto managed = static_cast<ManagedTheme*>(theme);
353
354 // If no scope path provided, return default font style
355 if (!scopePath || !scopePath[0]) {
356 if (managed->defaults) {
357 return managed->defaults->fontStyle;
358 }
359 return defaultStyle;
360 }
361
362 // Parse scope path
363 std::vector<ScopeName> scopes = parseScopePath(scopePath);
364 if (scopes.empty()) {
365 if (managed->defaults) {
366 return managed->defaults->fontStyle;
367 }
368 return defaultStyle;
369 }
370
371 // Match all scopes against theme and merge results
372 StyleAttributes* attrs = matchAllScopes(managed->theme, scopes);
373
374 // Get font style from matched attributes
375 if (attrs) {
376 int fontStyle = attrs->fontStyle;
377 delete attrs;
378
379 // FontStyle::NotSet is -1, only return if explicitly set
380 if (fontStyle >= 0) {
381 return fontStyle;
382 }
383 }
384
385 // Fall back to default font style
386 if (managed->defaults) {
387 return managed->defaults->fontStyle;
388 }
389
390 return defaultStyle;
391 } catch (...) {
392 return defaultStyle;
393 }
394}
395
397 if (!theme) {
398 return 0xFFFFFFFF; // White
399 }
400
401 try {
402 auto managed = static_cast<ManagedTheme*>(theme);
403 if (managed->defaults) {
404 auto colorMap = managed->theme->getColorMap();
405 int fgId = managed->defaults->foregroundId;
406
407 if (fgId > 0 && fgId < static_cast<int>(colorMap.size())) {
408 return hexColorToUint32(colorMap[fgId]);
409 }
410 }
411 return 0xFFFFFFFF; // White fallback
412 } catch (...) {
413 return 0xFFFFFFFF;
414 }
415}
416
418 if (!theme) {
419 return 0x000000FF; // Black
420 }
421
422 try {
423 auto managed = static_cast<ManagedTheme*>(theme);
424 if (managed->defaults) {
425 auto colorMap = managed->theme->getColorMap();
426 int bgId = managed->defaults->backgroundId;
427
428 if (bgId > 0 && bgId < static_cast<int>(colorMap.size())) {
429 return hexColorToUint32(colorMap[bgId]);
430 }
431 }
432 return 0x000000FF; // Black fallback
433 } catch (...) {
434 return 0x000000FF;
435 }
436}
437
439 if (theme) {
440 auto managed = static_cast<ManagedTheme*>(theme);
441 delete managed;
442 }
443}
444
445// Initialize Oniguruma library
447 try {
448 IOnigLib* onigLib = new DefaultOnigLib();
449 return static_cast<TextMateOnigLib>(onigLib);
450 } catch (...) {
451 return nullptr;
452 }
453}
454
455// Helper class to manage registry with internal grammar storage
456class ManagedRegistry {
457public:
458 Registry* registry;
459 std::map<std::string, IRawGrammar*> preloadedGrammars;
460 std::map<std::string, std::vector<std::string>> injections;
461
462 ManagedRegistry(IOnigLib* onigLib) {
463 RegistryOptions options;
464 options.onigLib = onigLib;
465
466 // Set up loadGrammar callback to return from preloaded grammars
467 options.loadGrammar = [this](const ScopeName& scopeName) -> IRawGrammar* {
468 auto it = preloadedGrammars.find(scopeName);
469 if (it != preloadedGrammars.end()) {
470 return it->second;
471 }
472 return nullptr;
473 };
474
475 // Set up getInjections callback to return configured injections
476 options.getInjections = [this](const ScopeName& scopeName) -> std::vector<ScopeName> {
477 auto it = injections.find(scopeName);
478 if (it != injections.end()) {
479 return it->second;
480 }
481 return std::vector<ScopeName>();
482 };
483
484 registry = new Registry(options);
485 }
486
487 ~ManagedRegistry() {
488 if (registry) {
489 delete registry;
490 }
491 // Note: Don't delete preloaded grammars as they're owned by the registry now
492 }
493};
494
495// Create registry with Oniguruma library
497 try {
498 ManagedRegistry* managed = new ManagedRegistry(static_cast<IOnigLib*>(onigLib));
499 return static_cast<TextMateRegistry>(managed);
500 } catch (...) {
501 return nullptr;
502 }
503}
504
505// Dispose registry
507 if (registry) {
508 ManagedRegistry* managed = static_cast<ManagedRegistry*>(registry);
509 delete managed;
510 }
511}
512
513// Add grammar to registry from JSON file (does not return Grammar, just registers it)
515 TextMateRegistry registry,
516 const char* grammarPath
517) {
518 if (!registry || !grammarPath) {
519 return 0;
520 }
521
522 try {
523 std::string content = readFileContents(grammarPath);
524 if (content.empty()) {
525 return 0;
526 }
527
528 std::string pathStr = grammarPath;
529 IRawGrammar* rawGrammar = parseRawGrammar(content, &pathStr);
530 if (!rawGrammar) {
531 return 0;
532 }
533
534 ManagedRegistry* managed = static_cast<ManagedRegistry*>(registry);
535 managed->preloadedGrammars[rawGrammar->scopeName] = rawGrammar;
536
537 return 1; // Success
538 } catch (...) {
539 return 0;
540 }
541}
542
543// Add grammar to registry from JSON string (does not return Grammar, just registers it)
545 TextMateRegistry registry,
546 const char* jsonContent
547) {
548 if (!registry || !jsonContent) {
549 return 0;
550 }
551
552 try {
553 IRawGrammar* rawGrammar = parseRawGrammar(jsonContent, nullptr);
554 if (!rawGrammar) {
555 return 0;
556 }
557
558 ManagedRegistry* managed = static_cast<ManagedRegistry*>(registry);
559 managed->preloadedGrammars[rawGrammar->scopeName] = rawGrammar;
560
561 return 1; // Success
562 } catch (...) {
563 return 0;
564 }
565}
566
567// Set grammar injections for a scope (call before loading the grammar)
569 TextMateRegistry registry,
570 const char* scopeName,
571 const char** injections,
572 int32_t injectionCount
573) {
574 if (!registry || !scopeName || !injections) {
575 return;
576 }
577
578 try {
579 ManagedRegistry* managed = static_cast<ManagedRegistry*>(registry);
580 std::vector<std::string> injectionsList;
581 for (int32_t i = 0; i < injectionCount; i++) {
582 if (injections[i]) {
583 injectionsList.push_back(injections[i]);
584 }
585 }
586 managed->injections[scopeName] = injectionsList;
587 } catch (...) {
588 // Ignore errors
589 }
590}
591
592// Set the color theme on the registry (enables themed tokenizeLine2 output)
594 TextMateRegistry registry,
595 const char* themeJsonContent
596) {
597 if (!registry || !themeJsonContent) {
598 return 0;
599 }
600
601 try {
602 ManagedRegistry* managed = static_cast<ManagedRegistry*>(registry);
603 IRawTheme* rawTheme = parseRawTheme(themeJsonContent);
604 if (!rawTheme) return 0;
605 managed->registry->setTheme(rawTheme);
606 return 1;
607 } catch (...) {
608 return 0;
609 }
610}
611
612// Get the color map from the registry (maps color IDs to hex strings)
614 if (!registry) {
615 return nullptr;
616 }
617
618 try {
619 ManagedRegistry* managed = static_cast<ManagedRegistry*>(registry);
620 std::vector<std::string> colors = managed->registry->getColorMap();
621
622 // Own the struct via RAII so a mid-construction throw (e.g. std::bad_alloc) frees
623 // what was already allocated instead of leaking it. textmate_free_color_map is a
624 // valid deleter for a partial struct: it null-checks the array, and the array is
625 // value-initialized so any not-yet-filled entries are null.
626 std::unique_ptr<TextMateColorMap, decltype(&textmate_free_color_map)> result(
628 result->colorCount = static_cast<int32_t>(colors.size());
629 result->colors = new char*[colors.size()](); // value-initialized to nullptr
630
631 for (size_t i = 0; i < colors.size(); i++) {
632 result->colors[i] = stringToCString(colors[i]);
633 }
634
635 return result.release();
636 } catch (...) {
637 return nullptr;
638 }
639}
640
641// Load grammar by scope name (after grammars have been added to registry)
643 TextMateRegistry registry,
644 const char* scopeName
645) {
646 if (!registry || !scopeName) {
647 return nullptr;
648 }
649
650 try {
651 ManagedRegistry* managed = static_cast<ManagedRegistry*>(registry);
652 Grammar* grammar = managed->registry->loadGrammar(scopeName);
653 return static_cast<TextMateGrammar>(grammar);
654 } catch (...) {
655 return nullptr;
656 }
657}
658
659// Get INITIAL state
663
664// Tokenize a line of text
666 TextMateGrammar grammar,
667 const char* lineText,
668 TextMateStateStack prevState
669) {
670 if (!grammar || !lineText) {
671 return nullptr;
672 }
673
674 try {
675 Grammar* gram = static_cast<Grammar*>(grammar);
676 StateStack* state = static_cast<StateStack*>(prevState);
677
678 ITokenizeLineResult result = gram->tokenizeLine(lineText, state);
679
680 // Own the struct via RAII so a mid-construction throw frees the partial
681 // allocation instead of leaking it. textmate_free_tokenize_result is a valid
682 // deleter for a partial struct: the tokens array and each scopes array are
683 // value-initialized, so not-yet-filled slots are null (a no-op to free).
684 std::unique_ptr<TextMateTokenizeResult, decltype(&textmate_free_tokenize_result)> cResult(
686 cResult->tokenCount = static_cast<int32_t>(result.tokens.size());
687 cResult->tokens = new TextMateToken[result.tokens.size()](); // value-initialized
688 cResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
689 cResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
690
691 // Convert tokens
692 for (size_t i = 0; i < result.tokens.size(); i++) {
693 const IToken& token = result.tokens[i];
694 cResult->tokens[i].startIndex = token.startIndex;
695 cResult->tokens[i].endIndex = token.endIndex;
696 cResult->tokens[i].scopeDepth = static_cast<int32_t>(token.scopes.size());
697
698 // Allocate scope strings (value-initialized so a throw mid-fill stays freeable)
699 cResult->tokens[i].scopes = new char*[token.scopes.size()]();
700 for (size_t j = 0; j < token.scopes.size(); j++) {
701 cResult->tokens[i].scopes[j] = stringToCString(token.scopes[j]);
702 }
703 }
704
705 return cResult.release();
706 } catch (...) {
707 return nullptr;
708 }
709}
710
711// Tokenize a line of text with encoded tokens
713 TextMateGrammar grammar,
714 const char* lineText,
715 TextMateStateStack prevState
716) {
717 if (!grammar || !lineText) {
718 return nullptr;
719 }
720
721 try {
722 Grammar* gram = static_cast<Grammar*>(grammar);
723 StateStack* state = static_cast<StateStack*>(prevState);
724
725 ITokenizeLineResult2 result = gram->tokenizeLine2(lineText, state);
726
727 // Own the struct via RAII so a mid-construction throw frees the partial
728 // allocation instead of leaking it (textmate_free_tokenize_result2 is null-safe).
729 std::unique_ptr<TextMateTokenizeResult2, decltype(&textmate_free_tokenize_result2)> cResult(
731 cResult->tokenCount = static_cast<int32_t>(result.tokens.size());
732 cResult->tokens = new uint32_t[result.tokens.size()];
733 cResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
734 cResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
735
736 // Copy tokens
737 for (size_t i = 0; i < result.tokens.size(); i++) {
738 cResult->tokens[i] = result.tokens[i];
739 }
740
741 return cResult.release();
742 } catch (...) {
743 return nullptr;
744 }
745}
746
747// Free tokenize result
749 if (result) {
750 if (result->tokens) {
751 for (int i = 0; i < result->tokenCount; i++) {
752 if (result->tokens[i].scopes) {
753 for (int j = 0; j < result->tokens[i].scopeDepth; j++) {
754 delete[] result->tokens[i].scopes[j];
755 }
756 delete[] result->tokens[i].scopes;
757 }
758 }
759 delete[] result->tokens;
760 }
761 delete result;
762 }
763}
764
765// Free tokenize result2
767 if (result) {
768 if (result->tokens) {
769 delete[] result->tokens;
770 }
771 delete result;
772 }
773}
774
775// Batch tokenize multiple lines (Phase 2 optimization)
777 TextMateGrammar grammar,
778 const char** lines,
779 int32_t lineCount,
780 TextMateStateStack initialState
781) {
782 if (!grammar || !lines || lineCount <= 0) {
783 return nullptr;
784 }
785
786 try {
787 Grammar* g = static_cast<Grammar*>(grammar);
788 StateStack* state = static_cast<StateStack*>(initialState);
789
790 // Own the batch via RAII so a mid-construction throw frees every partial
791 // allocation. textmate_free_tokenize_lines_result is a valid deleter for a
792 // partial batch: it null-checks lineResults, and the array is value-initialized
793 // so not-yet-filled slots are null (a no-op to free).
796 batchResult->lineCount = lineCount;
797 batchResult->lineResults = new TextMateTokenizeResult*[lineCount](); // value-initialized to nullptr
798
799 // Tokenize each line, propagating state
800 for (int32_t i = 0; i < lineCount; i++) {
801 std::string lineText(lines[i]);
802 auto result = g->tokenizeLine(lineText, state);
803
804 // Update state for next line
805 state = result.ruleStack;
806
807 // Allocate result for this line
808 std::unique_ptr<TextMateTokenizeResult, decltype(&textmate_free_tokenize_result)> lineResult(
810 lineResult->tokenCount = static_cast<int32_t>(result.tokens.size());
811 lineResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
812 lineResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
813
814 // Allocate and populate tokens (value-initialized so a throw mid-fill stays freeable)
815 lineResult->tokens = new TextMateToken[result.tokens.size()]();
816 for (size_t j = 0; j < result.tokens.size(); j++) {
817 const auto& token = result.tokens[j];
818 lineResult->tokens[j].startIndex = token.startIndex;
819 lineResult->tokens[j].endIndex = token.endIndex;
820 lineResult->tokens[j].scopeDepth = static_cast<int32_t>(token.scopes.size());
821
822 // Allocate scope array (value-initialized so a throw mid-fill stays freeable)
823 lineResult->tokens[j].scopes = new char*[token.scopes.size()]();
824 for (size_t k = 0; k < token.scopes.size(); k++) {
825 lineResult->tokens[j].scopes[k] = stringToCString(token.scopes[k]);
826 }
827 }
828
829 batchResult->lineResults[i] = lineResult.release();
830 }
831
832 return batchResult.release();
833 } catch (...) {
834 return nullptr;
835 }
836}
837
838// Free batch tokenize result
840 if (result) {
841 if (result->lineResults) {
842 // Free each line result
843 for (int32_t i = 0; i < result->lineCount; i++) {
845 }
846 delete[] result->lineResults;
847 }
848 delete result;
849 }
850}
851
852// Batch tokenize multiple lines with encoded tokens
854 TextMateGrammar grammar,
855 const char** lines,
856 int32_t lineCount,
857 TextMateStateStack initialState
858) {
859 if (!grammar || !lines || lineCount <= 0) {
860 return nullptr;
861 }
862
863 try {
864 Grammar* g = static_cast<Grammar*>(grammar);
865 StateStack* state = static_cast<StateStack*>(initialState);
866
867 // Own the batch via RAII so a mid-construction throw frees every partial
868 // allocation. textmate_free_tokenize_lines_result2 is a valid deleter for a
869 // partial batch: it null-checks lineResults, and the array is value-initialized
870 // so not-yet-filled slots are null (a no-op to free).
873 batchResult->lineCount = lineCount;
874 batchResult->lineResults = new TextMateTokenizeResult2*[lineCount](); // value-initialized to nullptr
875
876 for (int32_t i = 0; i < lineCount; i++) {
877 std::string lineText(lines[i]);
878 auto result = g->tokenizeLine2(lineText, state);
879
880 state = result.ruleStack;
881
882 std::unique_ptr<TextMateTokenizeResult2, decltype(&textmate_free_tokenize_result2)> lineResult(
884 lineResult->tokenCount = static_cast<int32_t>(result.tokens.size());
885 lineResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
886 lineResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
887
888 lineResult->tokens = new uint32_t[result.tokens.size()];
889 for (size_t j = 0; j < result.tokens.size(); j++) {
890 lineResult->tokens[j] = result.tokens[j];
891 }
892
893 batchResult->lineResults[i] = lineResult.release();
894 }
895
896 return batchResult.release();
897 } catch (...) {
898 return nullptr;
899 }
900}
901
902// Free batch encoded tokenize result
904 if (result) {
905 if (result->lineResults) {
906 for (int32_t i = 0; i < result->lineCount; i++) {
908 }
909 delete[] result->lineResults;
910 }
911 delete result;
912 }
913}
914
915// Free color map
917 if (colorMap) {
918 if (colorMap->colors) {
919 for (int32_t i = 0; i < colorMap->colorCount; i++) {
920 delete[] colorMap->colors[i];
921 }
922 delete[] colorMap->colors;
923 }
924 delete colorMap;
925 }
926}
927
928// ============================================================================
929// UTF-16 Tokenization API
930// ============================================================================
931
932// Tokenize a line of text with UTF-16 code unit indices
934 TextMateGrammar grammar,
935 const char* lineText,
936 TextMateStateStack prevState
937) {
938 if (!grammar || !lineText) {
939 return nullptr;
940 }
941
942 try {
943 Grammar* gram = static_cast<Grammar*>(grammar);
944 StateStack* state = static_cast<StateStack*>(prevState);
945
946 ITokenizeLineResult result = gram->tokenizeLine(lineText, state);
947
948 // Build byte-offset to UTF-16 index map
949 auto map = tml::buildByteToUtf16Map(lineText, std::strlen(lineText));
950
951 // Own the struct via RAII so a mid-construction throw frees the partial
952 // allocation instead of leaking it. textmate_free_tokenize_result is a valid
953 // deleter for a partial struct: the tokens array and each scopes array are
954 // value-initialized, so not-yet-filled slots are null (a no-op to free).
955 std::unique_ptr<TextMateTokenizeResult, decltype(&textmate_free_tokenize_result)> cResult(
957 cResult->tokenCount = static_cast<int32_t>(result.tokens.size());
958 cResult->tokens = new TextMateToken[result.tokens.size()](); // value-initialized
959 cResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
960 cResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
961
962 // Convert tokens with UTF-16 indices
963 // Note: the tokenizer may internally append '\n', so token indices
964 // can exceed strlen(lineText). Use mapByteToUtf16 for safe lookup.
965 for (size_t i = 0; i < result.tokens.size(); i++) {
966 const IToken& token = result.tokens[i];
967 cResult->tokens[i].startIndex = tml::mapByteToUtf16(map, token.startIndex);
968 cResult->tokens[i].endIndex = tml::mapByteToUtf16(map, token.endIndex);
969 cResult->tokens[i].scopeDepth = static_cast<int32_t>(token.scopes.size());
970
971 // Allocate scope strings (value-initialized so a throw mid-fill stays freeable)
972 cResult->tokens[i].scopes = new char*[token.scopes.size()]();
973 for (size_t j = 0; j < token.scopes.size(); j++) {
974 cResult->tokens[i].scopes[j] = stringToCString(token.scopes[j]);
975 }
976 }
977
978 return cResult.release();
979 } catch (...) {
980 return nullptr;
981 }
982}
983
984// Tokenize a line of text with encoded tokens and UTF-16 indices
986 TextMateGrammar grammar,
987 const char* lineText,
988 TextMateStateStack prevState
989) {
990 if (!grammar || !lineText) {
991 return nullptr;
992 }
993
994 try {
995 Grammar* gram = static_cast<Grammar*>(grammar);
996 StateStack* state = static_cast<StateStack*>(prevState);
997
998 ITokenizeLineResult2 result = gram->tokenizeLine2(lineText, state);
999
1000 // Build byte-offset to UTF-16 index map
1001 auto map = tml::buildByteToUtf16Map(lineText, std::strlen(lineText));
1002
1003 // Own the struct via RAII so a mid-construction throw frees the partial
1004 // allocation instead of leaking it (textmate_free_tokenize_result2 is null-safe).
1005 std::unique_ptr<TextMateTokenizeResult2, decltype(&textmate_free_tokenize_result2)> cResult(
1007 cResult->tokenCount = static_cast<int32_t>(result.tokens.size());
1008 cResult->tokens = new uint32_t[result.tokens.size()];
1009 cResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
1010 cResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
1011
1012 // Copy tokens, converting start offsets from UTF-8 byte to UTF-16
1013 // Encoded tokens are pairs: [startIndex, metadata, startIndex, metadata, ...]
1014 for (size_t i = 0; i < result.tokens.size(); i++) {
1015 if (i % 2 == 0) {
1016 // Even indices are start offsets
1017 cResult->tokens[i] = tml::mapByteToUtf16(map, result.tokens[i]);
1018 } else {
1019 // Odd indices are metadata — pass through
1020 cResult->tokens[i] = result.tokens[i];
1021 }
1022 }
1023
1024 return cResult.release();
1025 } catch (...) {
1026 return nullptr;
1027 }
1028}
1029
1030// Batch tokenize multiple lines with UTF-16 indices
1032 TextMateGrammar grammar,
1033 const char** lines,
1034 int32_t lineCount,
1035 TextMateStateStack initialState
1036) {
1037 if (!grammar || !lines || lineCount <= 0) {
1038 return nullptr;
1039 }
1040
1041 try {
1042 Grammar* g = static_cast<Grammar*>(grammar);
1043 StateStack* state = static_cast<StateStack*>(initialState);
1044
1045 // Own the batch via RAII so a mid-construction throw frees every partial
1046 // allocation. textmate_free_tokenize_lines_result is a valid deleter for a
1047 // partial batch: it null-checks lineResults, and the array is value-initialized
1048 // so not-yet-filled slots are null (a no-op to free).
1051 batchResult->lineCount = lineCount;
1052 batchResult->lineResults = new TextMateTokenizeResult*[lineCount](); // value-initialized to nullptr
1053
1054 // Tokenize each line, propagating state
1055 for (int32_t i = 0; i < lineCount; i++) {
1056 std::string lineText(lines[i]);
1057 auto result = g->tokenizeLine(lineText, state);
1058
1059 // Update state for next line
1060 state = result.ruleStack;
1061
1062 // Build byte-offset to UTF-16 index map for this line
1063 auto map = tml::buildByteToUtf16Map(lineText.c_str(), lineText.size());
1064
1065 // Allocate result for this line
1066 std::unique_ptr<TextMateTokenizeResult, decltype(&textmate_free_tokenize_result)> lineResult(
1068 lineResult->tokenCount = static_cast<int32_t>(result.tokens.size());
1069 lineResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
1070 lineResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
1071
1072 // Allocate and populate tokens with UTF-16 indices (value-initialized so a throw mid-fill stays freeable)
1073 lineResult->tokens = new TextMateToken[result.tokens.size()]();
1074 for (size_t j = 0; j < result.tokens.size(); j++) {
1075 const auto& token = result.tokens[j];
1076 lineResult->tokens[j].startIndex = tml::mapByteToUtf16(map, token.startIndex);
1077 lineResult->tokens[j].endIndex = tml::mapByteToUtf16(map, token.endIndex);
1078 lineResult->tokens[j].scopeDepth = static_cast<int32_t>(token.scopes.size());
1079
1080 // Allocate scope array (value-initialized so a throw mid-fill stays freeable)
1081 lineResult->tokens[j].scopes = new char*[token.scopes.size()]();
1082 for (size_t k = 0; k < token.scopes.size(); k++) {
1083 lineResult->tokens[j].scopes[k] = stringToCString(token.scopes[k]);
1084 }
1085 }
1086
1087 batchResult->lineResults[i] = lineResult.release();
1088 }
1089
1090 return batchResult.release();
1091 } catch (...) {
1092 return nullptr;
1093 }
1094}
1095
1096// Batch tokenize multiple lines with encoded tokens and UTF-16 indices
1098 TextMateGrammar grammar,
1099 const char** lines,
1100 int32_t lineCount,
1101 TextMateStateStack initialState
1102) {
1103 if (!grammar || !lines || lineCount <= 0) {
1104 return nullptr;
1105 }
1106
1107 try {
1108 Grammar* g = static_cast<Grammar*>(grammar);
1109 StateStack* state = static_cast<StateStack*>(initialState);
1110
1111 // Own the batch via RAII so a mid-construction throw frees every partial
1112 // allocation. textmate_free_tokenize_lines_result2 is a valid deleter for a
1113 // partial batch: it null-checks lineResults, and the array is value-initialized
1114 // so not-yet-filled slots are null (a no-op to free).
1117 batchResult->lineCount = lineCount;
1118 batchResult->lineResults = new TextMateTokenizeResult2*[lineCount](); // value-initialized to nullptr
1119
1120 for (int32_t i = 0; i < lineCount; i++) {
1121 std::string lineText(lines[i]);
1122 auto result = g->tokenizeLine2(lineText, state);
1123
1124 state = result.ruleStack;
1125
1126 auto map = tml::buildByteToUtf16Map(lineText.c_str(), lineText.size());
1127
1128 std::unique_ptr<TextMateTokenizeResult2, decltype(&textmate_free_tokenize_result2)> lineResult(
1130 lineResult->tokenCount = static_cast<int32_t>(result.tokens.size());
1131 lineResult->stoppedEarly = result.stoppedEarly ? 1 : 0;
1132 lineResult->ruleStack = static_cast<TextMateStateStack>(result.ruleStack);
1133
1134 lineResult->tokens = new uint32_t[result.tokens.size()];
1135 for (size_t j = 0; j < result.tokens.size(); j++) {
1136 if (j % 2 == 0) {
1137 lineResult->tokens[j] = tml::mapByteToUtf16(map, result.tokens[j]);
1138 } else {
1139 lineResult->tokens[j] = result.tokens[j];
1140 }
1141 }
1142
1143 batchResult->lineResults[i] = lineResult.release();
1144 }
1145
1146 return batchResult.release();
1147 } catch (...) {
1148 return nullptr;
1149 }
1150}
1151
1152// Get scope name from grammar
1154 if (!grammar) {
1155 return nullptr;
1156 }
1157
1158 try {
1159 Grammar* gram = static_cast<Grammar*>(grammar);
1160 static thread_local std::string scopeName;
1161 scopeName = gram->getScopeName();
1162 return scopeName.c_str();
1163 } catch (...) {
1164 return nullptr;
1165 }
1166}
1167
1168// Dispose Oniguruma library
1170 if (onigLib) {
1171 IOnigLib* lib = static_cast<IOnigLib*>(onigLib);
1172 delete lib;
1173 }
1174}
C language API for TextMateLib.
Helper class to manage theme resources and provide C API implementation.
Definition theme.h:209
Abstract interface representing the parsing state at the end of a line.
Definition types.h:55
const StateStack * INITIAL
Initial parsing state for the first line of a document.
Definition main.cpp:6
std::string ScopeName
Semantic name identifying a scope (e.g., "source.javascript", "comment.line")
Definition types.h:20
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 * TextMateRegistry
Handle to the grammar registry managing loaded grammars and themes.
Definition c_api.h:47
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
int textmate_registry_set_theme(TextMateRegistry registry, const char *themeJsonContent)
Set the color theme on the registry.
Definition c_api.cpp:593
int textmate_registry_add_grammar_from_json(TextMateRegistry registry, const char *jsonContent)
Register a grammar from a JSON string.
Definition c_api.cpp:544
TextMateOnigLib textmate_oniglib_create()
Initialize the Oniguruma regular expression library.
Definition c_api.cpp:446
void textmate_registry_dispose(TextMateRegistry registry)
Free a registry and all its resources.
Definition c_api.cpp:506
int textmate_registry_add_grammar_from_file(TextMateRegistry registry, const char *grammarPath)
Register a grammar from a JSON file.
Definition c_api.cpp:514
TextMateRegistry textmate_registry_create(TextMateOnigLib onigLib)
Create a new grammar registry.
Definition c_api.cpp:496
TextMateColorMap * textmate_registry_get_color_map(TextMateRegistry registry)
Get the color map from the registry after setting a theme.
Definition c_api.cpp:613
TextMateGrammar textmate_registry_load_grammar(TextMateRegistry registry, const char *scopeName)
Load a grammar by scope name.
Definition c_api.cpp:642
void textmate_registry_set_injections(TextMateRegistry registry, const char *scopeName, const char **injections, int32_t injectionCount)
Set grammar injection rules for a scope.
Definition c_api.cpp:568
uint32_t textmate_theme_get_default_background(TextMateTheme theme)
Get the default/fallback background color for the entire theme.
Definition c_api.cpp:417
uint32_t textmate_theme_get_background(TextMateTheme theme, const char *scopePath, uint32_t defaultColor)
Get the background color for a scope path.
Definition c_api.cpp:279
void textmate_theme_dispose(TextMateTheme theme)
Free a theme object and release resources.
Definition c_api.cpp:438
uint32_t textmate_theme_get_foreground(TextMateTheme theme, const char *scopePath, uint32_t defaultColor)
Get the foreground color for a scope path.
Definition c_api.cpp:216
int32_t textmate_theme_get_font_style(TextMateTheme theme, const char *scopePath, int32_t defaultStyle)
Get the font style flags for a scope path.
Definition c_api.cpp:342
#define TEXTMATE_FONT_STYLE_NONE
Font style flag constants for textmate_theme_get_font_style()
Definition c_api.h:179
uint32_t textmate_theme_get_default_foreground(TextMateTheme theme)
Get the default/fallback foreground color for the entire theme.
Definition c_api.cpp:396
#define TEXTMATE_FONT_STYLE_BOLD
Bold text.
Definition c_api.h:181
#define TEXTMATE_FONT_STYLE_UNDERLINE
Underlined text.
Definition c_api.h:182
#define TEXTMATE_FONT_STYLE_ITALIC
Italic text.
Definition c_api.h:180
TextMateTheme textmate_theme_load_from_json(const char *jsonContent)
Load a theme from a JSON string.
Definition c_api.cpp:192
TextMateTheme textmate_theme_load_from_file(const char *themePath)
Load a theme from a JSON file.
Definition c_api.cpp:163
void textmate_free_tokenize_result(TextMateTokenizeResult *result)
Free a line tokenization result.
Definition c_api.cpp:748
const char * textmate_grammar_get_scope_name(TextMateGrammar grammar)
Get the scope name (language identifier) of a grammar.
Definition c_api.cpp:1153
TextMateTokenizeMultiLinesResult * textmate_tokenize_lines(TextMateGrammar grammar, const char **lines, int32_t lineCount, TextMateStateStack initialState)
Tokenize multiple lines in a single call.
Definition c_api.cpp:776
void textmate_oniglib_dispose(TextMateOnigLib onigLib)
Free the Oniguruma library.
Definition c_api.cpp:1169
TextMateStateStack textmate_get_initial_state()
Get the initial parsing state.
Definition c_api.cpp:660
TextMateTokenizeResult2 * textmate_tokenize_line2(TextMateGrammar grammar, const char *lineText, TextMateStateStack prevState)
Tokenize a single line of text with encoded tokens (more efficient)
Definition c_api.cpp:712
void textmate_free_tokenize_lines_result2(TextMateTokenizeMultiLinesResult2 *result)
Free a batch encoded tokenization result.
Definition c_api.cpp:903
void textmate_free_tokenize_lines_result(TextMateTokenizeMultiLinesResult *result)
Free a batch tokenization result.
Definition c_api.cpp:839
TextMateTokenizeResult * textmate_tokenize_line(TextMateGrammar grammar, const char *lineText, TextMateStateStack prevState)
Tokenize a single line of text with decoded scopes.
Definition c_api.cpp:665
void textmate_free_color_map(TextMateColorMap *colorMap)
Free a color map.
Definition c_api.cpp:916
void textmate_free_tokenize_result2(TextMateTokenizeResult2 *result)
Free an encoded line tokenization result.
Definition c_api.cpp:766
TextMateTokenizeMultiLinesResult2 * textmate_tokenize_lines2(TextMateGrammar grammar, const char **lines, int32_t lineCount, TextMateStateStack initialState)
Tokenize multiple lines with encoded tokens in a single call.
Definition c_api.cpp:853
TextMateTokenizeMultiLinesResult2 * textmate_tokenize_lines2_utf16(TextMateGrammar grammar, const char **lines, int32_t lineCount, TextMateStateStack initialState)
Tokenize multiple lines with encoded tokens, returning UTF-16 indices.
Definition c_api.cpp:1097
TextMateTokenizeMultiLinesResult * textmate_tokenize_lines_utf16(TextMateGrammar grammar, const char **lines, int32_t lineCount, TextMateStateStack initialState)
Tokenize multiple lines in a single call, returning UTF-16 indices.
Definition c_api.cpp:1031
TextMateTokenizeResult2 * textmate_tokenize_line2_utf16(TextMateGrammar grammar, const char *lineText, TextMateStateStack prevState)
Tokenize a single line with encoded tokens, returning UTF-16 indices.
Definition c_api.cpp:985
TextMateTokenizeResult * textmate_tokenize_line_utf16(TextMateGrammar grammar, const char *lineText, TextMateStateStack prevState)
Tokenize a single line with decoded scopes, returning UTF-16 indices.
Definition c_api.cpp:933
Theme color map returned by textmate_registry_get_color_map()
Definition c_api.h:124
char ** colors
Array of hex color strings (e.g., "#RRGGBB" or "#RRGGBBAA")
Definition c_api.h:125
int32_t colorCount
Number of colors in the array.
Definition c_api.h:126
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 batch tokenizing multiple lines with encoded tokens.
Definition c_api.h:112
TextMateTokenizeResult2 ** lineResults
Array of encoded results, one per line.
Definition c_api.h:113
int32_t lineCount
Number of lines tokenized.
Definition c_api.h:114
Result from batch tokenizing multiple lines.
Definition c_api.h:100
int32_t lineCount
Number of lines tokenized.
Definition c_api.h:102
TextMateTokenizeResult ** lineResults
Array of results, one per line.
Definition c_api.h:101
Result from tokenizing a single line with encoded tokens.
Definition c_api.h:86
int32_t tokenCount
Number of tokens in the array.
Definition c_api.h:88
uint32_t * tokens
Array of encoded tokens.
Definition c_api.h:87
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