9ScopeStack::ScopeStack(ScopeStack* parent_,
const ScopeName& scopeName_)
10 : parent(parent_), scopeName(scopeName_) {
13ScopeStack::~ScopeStack() {
17ScopeStack* ScopeStack::push(ScopeStack* path,
const std::vector<ScopeName>& scopeNames) {
18 for (
const auto& name : scopeNames) {
19 path =
new ScopeStack(path, name);
24ScopeStack* ScopeStack::from(
const std::vector<ScopeName>& segments) {
25 ScopeStack* result =
nullptr;
26 for (
const auto& segment : segments) {
27 result =
new ScopeStack(result, segment);
32ScopeStack* ScopeStack::push(
const ScopeName& scopeName_) {
33 return new ScopeStack(
this, scopeName_);
36std::vector<ScopeName> ScopeStack::getSegments()
const {
37 std::vector<ScopeName> result;
38 const ScopeStack* item =
this;
40 result.push_back(item->scopeName);
43 std::reverse(result.begin(), result.end());
47std::string ScopeStack::toString()
const {
48 std::vector<ScopeName> segments = getSegments();
50 for (
size_t i = 0; i < segments.size(); i++) {
51 if (i > 0) result +=
" ";
52 result += segments[i];
57bool ScopeStack::extends(
const ScopeStack* other)
const {
61 if (parent ==
nullptr) {
64 return parent->extends(other);
67std::vector<ScopeName>* ScopeStack::getExtensionIfDefined(
const ScopeStack* base)
const {
68 std::vector<ScopeName>* result =
new std::vector<ScopeName>();
69 const ScopeStack* item =
this;
70 while (item && item != base) {
71 result->push_back(item->scopeName);
75 std::reverse(result->begin(), result->end());
84StyleAttributes::StyleAttributes(
int fontStyle_,
int foregroundId_,
int backgroundId_)
85 : fontStyle(fontStyle_), foregroundId(foregroundId_), backgroundId(backgroundId_) {
90ParsedThemeRule::ParsedThemeRule(
const ScopeName& scope_,
91 std::vector<ScopeName>* parentScopes_,
94 const std::string* foreground_,
95 const std::string* background_)
96 : scope(scope_), parentScopes(parentScopes_), index(index_), fontStyle(fontStyle_),
97 foreground(foreground_ ? new std::string(*foreground_) : nullptr),
98 background(background_ ? new std::string(*background_) : nullptr) {
101ParsedThemeRule::~ParsedThemeRule() {
109std::vector<ParsedThemeRule*> parseTheme(
const IRawTheme* source) {
110 std::vector<ParsedThemeRule*> result;
112 if (!source || source->settings.empty()) {
116 for (
size_t i = 0; i < source->settings.size(); i++) {
117 const IRawThemeSetting* entry = source->settings[i];
119 std::vector<std::string> scopes;
120 if (entry->scope !=
nullptr && !entry->scope->empty()) {
121 scopes = *entry->scope;
122 }
else if (!entry->scopeString.empty()) {
123 std::string scopeStr = entry->scopeString;
126 size_t start = scopeStr.find_first_not_of(
',');
127 size_t end = scopeStr.find_last_not_of(
',');
128 if (start != std::string::npos && end != std::string::npos) {
129 scopeStr = scopeStr.substr(start, end - start + 1);
134 while ((pos = scopeStr.find(
',')) != std::string::npos) {
135 scopes.push_back(scopeStr.substr(0, pos));
136 scopeStr.erase(0, pos + 1);
138 if (!scopeStr.empty()) {
139 scopes.push_back(scopeStr);
142 scopes.push_back(
"");
145 int fontStyle =
static_cast<int>(FontStyle::NotSet);
146 if (entry->settings.fontStyle !=
nullptr) {
147 fontStyle =
static_cast<int>(FontStyle::None);
149 std::string fontStyleStr = *entry->settings.fontStyle;
150 std::istringstream iss(fontStyleStr);
152 while (iss >> segment) {
153 if (segment ==
"italic") {
154 fontStyle |=
static_cast<int>(FontStyle::Italic);
155 }
else if (segment ==
"bold") {
156 fontStyle |=
static_cast<int>(FontStyle::Bold);
157 }
else if (segment ==
"underline") {
158 fontStyle |=
static_cast<int>(FontStyle::Underline);
159 }
else if (segment ==
"strikethrough") {
160 fontStyle |=
static_cast<int>(FontStyle::Strikethrough);
165 std::string* foreground =
nullptr;
166 if (entry->settings.foreground !=
nullptr && isValidHexColor(*entry->settings.foreground)) {
167 foreground =
new std::string(*entry->settings.foreground);
170 std::string* background =
nullptr;
171 if (entry->settings.background !=
nullptr && isValidHexColor(*entry->settings.background)) {
172 background =
new std::string(*entry->settings.background);
175 for (
const auto& scopeStr : scopes) {
176 std::string trimmedScope = scopeStr;
178 trimmedScope.erase(0, trimmedScope.find_first_not_of(
" \t\n\r"));
179 trimmedScope.erase(trimmedScope.find_last_not_of(
" \t\n\r") + 1);
182 std::vector<std::string> segments;
183 std::istringstream iss(trimmedScope);
185 while (iss >> segment) {
186 segments.push_back(segment);
189 std::string scope = segments.empty() ?
"" : segments.back();
190 std::vector<ScopeName>* parentScopes =
nullptr;
191 if (segments.size() > 1) {
192 parentScopes =
new std::vector<ScopeName>(segments.begin(), segments.end() - 1);
193 std::reverse(parentScopes->begin(), parentScopes->end());
196 result.push_back(
new ParsedThemeRule(
215std::string fontStyleToString(
int fontStyle) {
216 if (fontStyle ==
static_cast<int>(FontStyle::NotSet)) {
221 if (fontStyle &
static_cast<int>(FontStyle::Italic)) {
224 if (fontStyle &
static_cast<int>(FontStyle::Bold)) {
227 if (fontStyle &
static_cast<int>(FontStyle::Underline)) {
228 style +=
"underline ";
230 if (fontStyle &
static_cast<int>(FontStyle::Strikethrough)) {
231 style +=
"strikethrough ";
237 style = style.substr(0, style.length() - 1);
244ColorMap::ColorMap(
const std::vector<std::string>* colorMap)
247 if (colorMap !=
nullptr && !colorMap->empty()) {
249 for (
size_t i = 0; i < colorMap->size(); i++) {
250 std::string upperColor = (*colorMap)[i];
251 std::transform(upperColor.begin(), upperColor.end(), upperColor.begin(), ::toupper);
252 _color2id[upperColor] = i;
253 _id2color.push_back((*colorMap)[i]);
255 _lastColorId = colorMap->size() - 1;
261int ColorMap::getId(
const std::string* color) {
262 if (color ==
nullptr) {
266 std::string upperColor = *color;
267 std::transform(upperColor.begin(), upperColor.end(), upperColor.begin(), ::toupper);
269 auto it = _color2id.find(upperColor);
270 if (it != _color2id.end()) {
280 int value = ++_lastColorId;
281 _color2id[upperColor] = value;
282 if (_id2color.size() <=
static_cast<size_t>(value)) {
283 _id2color.resize(value + 1);
285 _id2color[value] = *color;
289std::vector<std::string> ColorMap::getColorMap()
const {
295ThemeTrieElementRule::ThemeTrieElementRule(
int scopeDepth_,
296 const std::vector<ScopeName>* parentScopes_,
300 : scopeDepth(scopeDepth_),
301 parentScopes(parentScopes_ ? *parentScopes_ : std::vector<
ScopeName>()),
302 fontStyle(fontStyle_),
303 foreground(foreground_),
304 background(background_) {
307ThemeTrieElementRule* ThemeTrieElementRule::clone()
const {
308 return new ThemeTrieElementRule(scopeDepth, &parentScopes, fontStyle, foreground, background);
311std::vector<ThemeTrieElementRule*> ThemeTrieElementRule::cloneArr(
const std::vector<ThemeTrieElementRule*>& arr) {
312 std::vector<ThemeTrieElementRule*> result;
313 for (
auto* rule : arr) {
314 result.push_back(rule->clone());
319void ThemeTrieElementRule::acceptOverwrite(
int scopeDepth_,
int fontStyle_,
int foreground_,
int background_) {
320 if (scopeDepth_ > this->scopeDepth) {
321 this->scopeDepth = scopeDepth_;
324 if (fontStyle_ !=
static_cast<int>(FontStyle::NotSet)) {
325 this->fontStyle = fontStyle_;
327 if (foreground_ != 0) {
328 this->foreground = foreground_;
330 if (background_ != 0) {
331 this->background = background_;
337ThemeTrieElement::ThemeTrieElement(ThemeTrieElementRule* mainRule,
338 const std::vector<ThemeTrieElementRule*>& rulesWithParentScopes)
339 : _mainRule(mainRule), _rulesWithParentScopes(rulesWithParentScopes) {
342ThemeTrieElement::~ThemeTrieElement() {
344 for (
auto* rule : _rulesWithParentScopes) {
347 for (
auto& pair : _children) {
352std::vector<ThemeTrieElementRule*> ThemeTrieElement::match(
const ScopeName& scope) {
354 std::vector<ThemeTrieElementRule*> result;
358 result.push_back(_mainRule);
362 result.insert(result.end(), _rulesWithParentScopes.begin(), _rulesWithParentScopes.end());
370 size_t dotIndex = scope.find(
'.');
371 std::string head = (dotIndex == std::string::npos) ? scope : scope.substr(0, dotIndex);
372 std::string tail = (dotIndex == std::string::npos) ?
"" : scope.substr(dotIndex + 1);
375 auto it = _children.find(head);
376 if (it != _children.end()) {
377 auto childMatches = it->second->match(tail);
378 result.insert(result.end(), childMatches.begin(), childMatches.end());
384void ThemeTrieElement::insert(
int scopeDepth,
385 const std::string& scope,
386 const std::vector<ScopeName>* parentScopes,
392 if (parentScopes ==
nullptr || parentScopes->empty()) {
393 _mainRule->acceptOverwrite(scopeDepth, fontStyle, foreground, background);
397 for (
auto* rule : _rulesWithParentScopes) {
398 if (rule->parentScopes == *parentScopes) {
399 rule->acceptOverwrite(scopeDepth, fontStyle, foreground, background);
405 _rulesWithParentScopes.push_back(
406 new ThemeTrieElementRule(scopeDepth, parentScopes, fontStyle, foreground, background)
414 size_t dotIndex = scope.find(
'.');
418 if (dotIndex == std::string::npos) {
422 head = scope.substr(0, dotIndex);
423 tail = scope.substr(dotIndex + 1);
427 ThemeTrieElement* child =
nullptr;
428 auto it = _children.find(head);
429 if (it != _children.end()) {
434 child =
new ThemeTrieElement(
435 new ThemeTrieElementRule(0,
nullptr,
static_cast<int>(FontStyle::NotSet), 0, 0),
436 std::vector<ThemeTrieElementRule*>()
438 _children[head] = child;
441 child->insert(scopeDepth + 1, tail, parentScopes, fontStyle, foreground, background);
446bool _matchesScope(
const ScopeName& scopeName,
const ScopeName& scopePattern) {
447 return scopePattern == scopeName ||
448 (scopeName.length() > scopePattern.length() &&
449 scopeName.substr(0, scopePattern.length()) == scopePattern &&
450 scopeName[scopePattern.length()] ==
'.');
453bool _scopePathMatchesParentScopes(ScopeStack* scopePath,
const std::vector<ScopeName>& parentScopes) {
454 if (parentScopes.empty()) {
458 for (
size_t index = 0; index < parentScopes.size(); index++) {
459 std::string scopePattern = parentScopes[index];
460 bool scopeMustMatch =
false;
463 if (scopePattern ==
">") {
464 if (index == parentScopes.size() - 1) {
467 scopePattern = parentScopes[++index];
468 scopeMustMatch =
true;
472 if (_matchesScope(scopePath->scopeName, scopePattern)) {
475 if (scopeMustMatch) {
478 scopePath = scopePath->parent;
484 scopePath = scopePath->parent;
492Theme* resolveParsedThemeRules(std::vector<ParsedThemeRule*>& parsedThemeRules,
493 const std::vector<std::string>* colorMap) {
495 std::sort(parsedThemeRules.begin(), parsedThemeRules.end(),
496 [](
const ParsedThemeRule* a,
const ParsedThemeRule* b) {
497 int r = strcmp_custom(a->scope, b->scope);
498 if (r != 0) return r < 0;
499 r = strArrCmp(a->parentScopes, b->parentScopes);
500 if (r != 0) return r < 0;
501 return a->index < b->index;
505 int defaultFontStyle =
static_cast<int>(FontStyle::None);
506 std::string defaultForeground =
"#000000";
507 std::string defaultBackground =
"#ffffff";
509 while (!parsedThemeRules.empty() && parsedThemeRules[0]->scope.empty()) {
510 ParsedThemeRule* incomingDefaults = parsedThemeRules[0];
511 parsedThemeRules.erase(parsedThemeRules.begin());
513 if (incomingDefaults->fontStyle !=
static_cast<int>(FontStyle::NotSet)) {
514 defaultFontStyle = incomingDefaults->fontStyle;
516 if (incomingDefaults->foreground !=
nullptr) {
517 defaultForeground = *incomingDefaults->foreground;
519 if (incomingDefaults->background !=
nullptr) {
520 defaultBackground = *incomingDefaults->background;
523 delete incomingDefaults;
526 ColorMap* colorMapObj =
new ColorMap(colorMap);
527 StyleAttributes* defaults =
new StyleAttributes(
529 colorMapObj->getId(&defaultForeground),
530 colorMapObj->getId(&defaultBackground)
533 ThemeTrieElement* root =
new ThemeTrieElement(
534 new ThemeTrieElementRule(0,
nullptr,
static_cast<int>(FontStyle::NotSet), 0, 0),
535 std::vector<ThemeTrieElementRule*>()
538 for (
auto* rule : parsedThemeRules) {
539 root->insert(0, rule->scope, rule->parentScopes,
541 colorMapObj->getId(rule->foreground),
542 colorMapObj->getId(rule->background));
545 return new Theme(colorMapObj, defaults, root);
550Theme::Theme(ColorMap* colorMap, StyleAttributes* defaults, ThemeTrieElement* root)
551 : _colorMap(colorMap), _defaults(defaults), _root(root) {
553 _cachedMatchRoot =
new CachedFn<ScopeName, std::vector<ThemeTrieElementRule*>>(
555 return this->_root->match(scopeName);
564 delete _cachedMatchRoot;
567Theme* Theme::createFromRawTheme(
const IRawTheme* source,
const std::vector<std::string>* colorMap) {
568 return createFromParsedTheme(parseTheme(source), colorMap);
571Theme* Theme::createFromParsedTheme(
const std::vector<ParsedThemeRule*>& source,
572 const std::vector<std::string>* colorMap) {
573 std::vector<ParsedThemeRule*> mutableSource = source;
574 return resolveParsedThemeRules(mutableSource, colorMap);
577std::vector<std::string> Theme::getColorMap()
const {
578 return _colorMap->getColorMap();
581StyleAttributes* Theme::getDefaults()
const {
585StyleAttributes* Theme::match(ScopeStack* scopePath) {
586 if (scopePath ==
nullptr) {
590 const ScopeName& scopeName = scopePath->scopeName;
591 std::vector<ThemeTrieElementRule*> matchingTrieElements = _cachedMatchRoot->get(scopeName);
594 ThemeTrieElementRule* effectiveRule =
nullptr;
598 for (
auto* rule : matchingTrieElements) {
599 if (_scopePathMatchesParentScopes(scopePath->parent, rule->parentScopes)) {
600 effectiveRule = rule;
605 if (!effectiveRule) {
609 return new StyleAttributes(
610 effectiveRule->fontStyle,
611 effectiveRule->foreground,
612 effectiveRule->background
std::string ScopeName
Semantic name identifying a scope (e.g., "source.javascript", "comment.line")