Accomodate for name/contentName containing multiple scopes

This commit is contained in:
Alex Dima 2016-11-17 15:16:40 -08:00
Родитель 103f4cb88c
Коммит 8e4fb9a57d
3 изменённых файлов: 284 добавлений и 218 удалений

Просмотреть файл

@ -98,7 +98,7 @@ export function collectIncludedScopes(result: IScopeNameSet, grammar: IRawGramma
delete result[grammar.scopeName];
}
interface Injection {
export interface Injection {
readonly matcher: Matcher<StackElement>;
readonly priorityMatch: boolean;
readonly ruleId: number;
@ -118,7 +118,7 @@ function collectInjections(result: Injection[], selector: string, rule: IRawRule
}
function nameMatcher(identifers: string[], stackElements: StackElement) {
let scopes = stackElements.generateScopes();
let scopes = stackElements.contentNameScopesList.generateScopes();
var lastIndex = 0;
return identifers.every(identifier => {
for (var i = lastIndex; i < scopes.length; i++) {
@ -162,14 +162,14 @@ class ScopeMetadataProvider {
private readonly _themeProvider: IThemeProvider;
private readonly _cache: { [scopeName: string]: ScopeMetadata; };
private readonly _rootMetaData: ScopeMetadata;
private readonly _defaultMetaData: ScopeMetadata;
private readonly _embeddedLanguages: IEmbeddedLanguagesMap;
private readonly _embeddedLanguagesRegex: RegExp;
constructor(initialLanguage: number, themeProvider: IThemeProvider, embeddedLanguages: IEmbeddedLanguagesMap) {
this._themeProvider = themeProvider;
this._cache = Object.create(null);
this._rootMetaData = new ScopeMetadata(
this._defaultMetaData = new ScopeMetadata(
'',
initialLanguage,
StandardTokenType.Other,
@ -206,8 +206,8 @@ class ScopeMetadataProvider {
}
}
public getRootMetadata(): ScopeMetadata {
return this._rootMetaData;
public getDefaultMetadata(): ScopeMetadata {
return this._defaultMetaData;
}
/**
@ -283,7 +283,7 @@ class ScopeMetadataProvider {
}
}
class Grammar implements IGrammar, IRuleFactoryHelper {
export class Grammar implements IGrammar, IRuleFactoryHelper {
private _rootId: number;
private _lastRuleId: number;
@ -390,15 +390,17 @@ class Grammar implements IGrammar, IRuleFactoryHelper {
let isFirstLine: boolean;
if (!prevState) {
isFirstLine = true;
let rootMetadata = this._scopeMetadataProvider.getRootMetadata();
let themeData = rootMetadata.themeData[0];
let _rootMetadata = StackElementMetadata.set(0, rootMetadata.languageId, rootMetadata.tokenType, themeData.fontStyle, themeData.foreground, themeData.background);
let rawDefaultMetadata = this._scopeMetadataProvider.getDefaultMetadata();
let defaultTheme = rawDefaultMetadata.themeData[0];
let defaultMetadata = StackElementMetadata.set(0, rawDefaultMetadata.languageId, rawDefaultMetadata.tokenType, defaultTheme.fontStyle, defaultTheme.foreground, defaultTheme.background);
let scopeName = this.getRule(this._rootId).getName(null, null);
let scopeNameMetadata = this._scopeMetadataProvider.getMetadataForScope(scopeName);
let _scopeNameMetadata = StackElementMetadata.merge(_rootMetadata, scopeNameMetadata, null);
let rootScopeName = this.getRule(this._rootId).getName(null, null);
let rawRootMetadata = this._scopeMetadataProvider.getMetadataForScope(rootScopeName);
let rootMetadata = ScopeListElement.mergeMetadata(defaultMetadata, null, rawRootMetadata);
prevState = new StackElement(null, this._rootId, -1, null, scopeName, _scopeNameMetadata, null, _scopeNameMetadata);
let scopeList = new ScopeListElement(null, rootScopeName, rootMetadata);
prevState = new StackElement(null, this._rootId, -1, null, scopeList, scopeList);
} else {
isFirstLine = false;
prevState.reset();
@ -462,19 +464,24 @@ function handleCaptures(grammar: Grammar, lineText: OnigString, isFirstLine: boo
// pop captures while needed
while (localStack.length > 0 && localStack[localStack.length - 1].endPos <= captureIndex.start) {
// pop!
lineTokens.produce(stack, localStack[localStack.length - 1].endPos, localStack);
lineTokens.produceFromScopes(localStack[localStack.length - 1].scopes, localStack[localStack.length - 1].endPos);
localStack.pop();
}
lineTokens.produce(stack, captureIndex.start, localStack);
if (localStack.length > 0) {
lineTokens.produceFromScopes(localStack[localStack.length - 1].scopes, captureIndex.start);
} else {
lineTokens.produce(stack, captureIndex.start);
}
if (captureRule.retokenizeCapturedWithRuleId) {
// the capture requires additional matching
let scopeName = captureRule.getName(getString(lineText), captureIndices);
let scopeNameMetadata = grammar.getMetadataForScope(scopeName);
let nameScopesList = stack.contentNameScopesList.push(grammar, scopeName);
let contentName = captureRule.getContentName(getString(lineText), captureIndices);
let contentNameMetadata = grammar.getMetadataForScope(contentName);
let stackClone = stack.push(captureRule.retokenizeCapturedWithRuleId, captureIndex.start, null, scopeName, scopeNameMetadata, contentName, contentNameMetadata);
let contentNameScopesList = nameScopesList.push(grammar, contentName);
let stackClone = stack.push(captureRule.retokenizeCapturedWithRuleId, captureIndex.start, null, nameScopesList, contentNameScopesList);
_tokenizeString(grammar,
createOnigString(
getString(lineText).substring(0, captureIndex.end)
@ -487,16 +494,15 @@ function handleCaptures(grammar: Grammar, lineText: OnigString, isFirstLine: boo
let captureRuleScopeName = captureRule.getName(getString(lineText), captureIndices);
if (captureRuleScopeName !== null) {
// push
let captureRuleScopeNameMetadata = grammar.getMetadataForScope(captureRuleScopeName);
let base = localStack.length === 0 ? stack.contentMetadata : localStack[localStack.length - 1].scopeMetadata;
let _captureRuleScopeNameMetadata = StackElementMetadata.merge(base, captureRuleScopeNameMetadata, new ScopeListProvider(stack, localStack, null));
localStack.push(new LocalStackElement(captureRuleScopeName, _captureRuleScopeNameMetadata, captureIndex.end));
let base = localStack.length > 0 ? localStack[localStack.length - 1].scopes : stack.contentNameScopesList;
let captureRuleScopesList = base.push(grammar, captureRuleScopeName);
localStack.push(new LocalStackElement(captureRuleScopesList, captureIndex.end));
}
}
while (localStack.length > 0) {
// pop!
lineTokens.produce(stack, localStack[localStack.length - 1].endPos, localStack);
lineTokens.produceFromScopes(localStack[localStack.length - 1].scopes, localStack[localStack.length - 1].endPos);
localStack.pop();
}
}
@ -729,7 +735,7 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
}
lineTokens.produce(stack, captureIndices[0].start);
stack = stack.withContentName(null, null);
stack = stack.setContentNameScopesList(stack.nameScopesList);
handleCaptures(grammar, lineText, isFirstLine, stack, lineTokens, poppedRule.endCaptures, captureIndices);
lineTokens.produce(stack, captureIndices[0].end);
@ -758,8 +764,8 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
let beforePush = stack;
// push it on the stack rule
let scopeName = _rule.getName(getString(lineText), captureIndices);
let scopeNameMetadata = grammar.getMetadataForScope(scopeName);
stack = stack.push(matchedRuleId, linePos, null, scopeName, scopeNameMetadata, null, null);
let nameScopesList = stack.contentNameScopesList.push(grammar, scopeName);
stack = stack.push(matchedRuleId, linePos, null, nameScopesList, nameScopesList);
if (_rule instanceof BeginEndRule) {
let pushedRule = <BeginEndRule>_rule;
@ -772,11 +778,11 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
anchorPosition = captureIndices[0].end;
let contentName = pushedRule.getContentName(getString(lineText), captureIndices);
let contentNameMetadata = grammar.getMetadataForScope(contentName);
stack = stack.withContentName(contentName, contentNameMetadata);
let contentNameScopesList = nameScopesList.push(grammar, contentName);
stack = stack.setContentNameScopesList(contentNameScopesList);
if (pushedRule.endHasBackReferences) {
stack = stack.withEndRule(pushedRule.getEndWithResolvedBackReferences(getString(lineText), captureIndices));
stack = stack.setEndRule(pushedRule.getEndWithResolvedBackReferences(getString(lineText), captureIndices));
}
if (!hasAdvanced && beforePush.hasSameRuleAs(stack)) {
@ -797,11 +803,11 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
lineTokens.produce(stack, captureIndices[0].end);
anchorPosition = captureIndices[0].end;
let contentName = pushedRule.getContentName(getString(lineText), captureIndices);
let contentNameMetadata = grammar.getMetadataForScope(contentName);
stack = stack.withContentName(contentName, contentNameMetadata);
let contentNameScopesList = nameScopesList.push(grammar, contentName);
stack = stack.setContentNameScopesList(contentNameScopesList);
if (pushedRule.whileHasBackReferences) {
stack = stack.withEndRule(pushedRule.getWhileWithResolvedBackReferences(getString(lineText), captureIndices));
stack = stack.setEndRule(pushedRule.getWhileWithResolvedBackReferences(getString(lineText), captureIndices));
}
if (!hasAdvanced && beforePush.hasSameRuleAs(stack)) {
@ -923,8 +929,82 @@ export class StackElementMetadata {
| (_background << MetadataConsts.BACKGROUND_OFFSET)
) >>> 0;
}
}
public static merge(metadata: number, source: ScopeMetadata, scopeList: ScopeListProvider): number {
export class ScopeListElement {
_scopeListElementBrand: void;
public readonly parent: ScopeListElement;
public readonly scope: string;
public readonly metadata: number;
constructor(parent: ScopeListElement, scope: string, metadata: number) {
this.parent = parent;
this.scope = scope;
this.metadata = metadata;
}
private static _equals(a: ScopeListElement, b: ScopeListElement): boolean {
do {
if (a === b) {
return true;
}
if (a.scope !== b.scope || a.metadata !== b.metadata) {
return false;
}
// Go to previous pair
a = a.parent;
b = b.parent;
if (!a && !b) {
// End of list reached for both
return true;
}
if (!a || !b) {
// End of list reached only for one
return false;
}
} while (true);
}
public equals(other: ScopeListElement): boolean {
return ScopeListElement._equals(this, other);
}
private static _matchesScope(scope: string, selector: string, selectorWithDot: string): boolean {
return (selector === scope || scope.substring(0, selectorWithDot.length) === selectorWithDot);
}
private static _matches(target: ScopeListElement, parentScopes: string[]): boolean {
if (parentScopes === null) {
return true;
}
let len = parentScopes.length;
let index = 0;
let selector = parentScopes[index];
let selectorWithDot = selector + '.';
while (target) {
if (this._matchesScope(target.scope, selector, selectorWithDot)) {
index++;
if (index === len) {
return true;
}
selector = parentScopes[index];
selectorWithDot = selector + '.';
}
target = target.parent;
}
return false;
}
public static mergeMetadata(metadata: number, scopesList: ScopeListElement, source: ScopeMetadata): number {
if (source === null) {
return metadata;
}
@ -933,18 +1013,12 @@ export class StackElementMetadata {
let foreground = 0;
let background = 0;
if (source.themeData === null || source.themeData.length === 0) {
// No themeing...
} else if (source.themeData.length === 1 && source.themeData[0].parentScopes === null) {
let themeData = source.themeData[0];
fontStyle = themeData.fontStyle;
foreground = themeData.foreground;
background = themeData.background;
} else {
// find the first themeData that matches
if (source.themeData !== null) {
// Find the first themeData that matches
for (let i = 0, len = source.themeData.length; i < len; i++) {
let themeData = source.themeData[i];
if (scopeList && scopeList.matches(themeData.parentScopes)) {
if (this._matches(scopesList, themeData.parentScopes)) {
fontStyle = themeData.fontStyle;
foreground = themeData.foreground;
background = themeData.background;
@ -953,65 +1027,133 @@ export class StackElementMetadata {
}
}
return this.set(metadata, source.languageId, source.tokenType, fontStyle, foreground, background);
return StackElementMetadata.set(metadata, source.languageId, source.tokenType, fontStyle, foreground, background);
}
private static _push(target: ScopeListElement, grammar: Grammar, scopes: string[]): ScopeListElement {
for (let i = 0, len = scopes.length; i < len; i++) {
let scope = scopes[i];
let rawMetadata = grammar.getMetadataForScope(scope);
let metadata = ScopeListElement.mergeMetadata(target.metadata, target, rawMetadata);
target = new ScopeListElement(target, scope, metadata);
}
return target;
}
public push(grammar: Grammar, scope: string): ScopeListElement {
if (scope === null) {
return this;
}
if (scope.indexOf(' ') >= 0) {
// there are multiple scopes to push
return ScopeListElement._push(this, grammar, scope.split(/ /g));
}
// there is a single scope to push
return ScopeListElement._push(this, grammar, [scope]);
}
private static _generateScopes(scopesList: ScopeListElement): string[] {
let result: string[] = [], resultLen = 0;
while (scopesList) {
result[resultLen++] = scopesList.scope;
scopesList = scopesList.parent;
}
result.reverse();
return result;
}
public generateScopes(): string[] {
return ScopeListElement._generateScopes(this);
}
}
/**
* **IMPORTANT** - Immutable!
* Represents a "pushed" state on the stack (as a linked list element).
*/
export class StackElement implements StackElementDef {
public _stackElementBrand: void;
_stackElementBrand: void;
/**
* The position on the current line where this state was pushed.
* This is relevant only while tokenizing a line, to detect endless loops.
* Its value is meaningless across lines.
*/
private _enterPos: number;
public readonly depth: number;
/**
* The previous state on the stack (or null for the root state).
*/
public readonly parent: StackElement;
/**
* The depth of the stack.
*/
public readonly depth: number;
/**
* The state (rule) that this element represents.
*/
public readonly ruleId: number;
/**
* The "pop" (end) condition for this state in case that it was dynamically generated through captured text.
*/
public readonly endRule: string;
public readonly scopeName: string;
public readonly scopeMetadata: number;
public readonly contentName: string;
public readonly contentMetadata: number;
/**
* The list of scopes containing the "name" for this state.
*/
public readonly nameScopesList: ScopeListElement;
/**
* The list of scopes containing the "contentName" (besides "name") for this state.
* This list **must** contain as an element `scopeName`.
*/
public readonly contentNameScopesList: ScopeListElement;
constructor(parent: StackElement, ruleId: number, enterPos: number, endRule: string, scopeName: string, scopeMetadata: number, contentName: string, contentMetadata: number) {
constructor(parent: StackElement, ruleId: number, enterPos: number, endRule: string, nameScopesList: ScopeListElement, contentNameScopesList: ScopeListElement) {
this.parent = parent;
this.depth = (this.parent ? this.parent.depth + 1 : 1);
this.ruleId = ruleId;
this._enterPos = enterPos;
this.endRule = endRule;
this.scopeName = scopeName;
this.scopeMetadata = scopeMetadata;
this.contentName = contentName;
this.contentMetadata = contentMetadata;
this.nameScopesList = nameScopesList;
this.contentNameScopesList = contentNameScopesList;
}
private static _equals(a: StackElement, b: StackElement): boolean {
/**
* A structural equals check. Does not take into account `scopes`.
*/
private static _structuralEquals(a: StackElement, b: StackElement): boolean {
do {
if (
a.depth !== b.depth
|| a.ruleId !== b.ruleId
|| a.endRule !== b.endRule
|| a.scopeName !== b.scopeName
|| a.contentName !== b.contentName
) {
if (a === b) {
return true;
}
if (a.depth !== b.depth || a.ruleId !== b.ruleId || a.endRule !== b.endRule) {
return false;
}
// Go to previous pair
a = a.parent;
b = b.parent;
if (!a && !b) {
// End of list reached for both
return true;
}
if (!a || !b) {
// End of list reached only for one
return false;
}
} while (true);
}
private static _equals(a: StackElement, b: StackElement): boolean {
if (!this._structuralEquals(a, b)) {
return false;
}
return a.contentNameScopesList.equals(b.contentNameScopesList);
}
public equals(other: StackElement): boolean {
return StackElement._equals(this, other);
}
@ -1038,14 +1180,8 @@ export class StackElement implements StackElementDef {
return this;
}
public push(ruleId: number, enterPos: number, endRule: string, scopeName: string, scopeNameMetadata: ScopeMetadata, contentName: string, contentNameMetadata: ScopeMetadata): StackElement {
let _scopeNameMetadata = StackElementMetadata.merge(this.contentMetadata, scopeNameMetadata, new ScopeListProvider(this, null, null));
return this._push(ruleId, enterPos, endRule, scopeName, _scopeNameMetadata, contentName, contentNameMetadata);
}
private _push(ruleId: number, enterPos: number, endRule: string, scopeName: string, scopeMetadata: number, contentName: string, contentNameMetadata: ScopeMetadata): StackElement {
let _contentNameMetadata = StackElementMetadata.merge(scopeMetadata, contentNameMetadata, new ScopeListProvider(this, null, scopeName));
return new StackElement(this, ruleId, enterPos, endRule, scopeName, scopeMetadata, contentName, _contentNameMetadata);
public push(ruleId: number, enterPos: number, endRule: string, nameScopesList: ScopeListElement, contentNameScopesList: ScopeListElement): StackElement {
return new StackElement(this, ruleId, enterPos, endRule, nameScopesList, contentNameScopesList);
}
public getEnterPos(): number {
@ -1061,7 +1197,7 @@ export class StackElement implements StackElementDef {
outIndex = this.parent._writeString(res, outIndex);
}
res[outIndex++] = `(${this.ruleId}, ${this.scopeName}, ${this.contentName})`;
res[outIndex++] = `(${this.ruleId}, TODO-${this.nameScopesList}, TODO-${this.contentNameScopesList})`;
return outIndex;
}
@ -1072,43 +1208,18 @@ export class StackElement implements StackElementDef {
return '[' + r.join(',') + ']';
}
public withContentName(contentName: string, contentNameMetadata: ScopeMetadata): StackElement {
if (this.contentName === contentName) {
public setContentNameScopesList(contentNameScopesList: ScopeListElement): StackElement {
if (this.contentNameScopesList === contentNameScopesList) {
return this;
}
return this.parent._push(this.ruleId, this._enterPos, this.endRule, this.scopeName, this.scopeMetadata, contentName, contentNameMetadata);
return this.parent.push(this.ruleId, this._enterPos, this.endRule, this.nameScopesList, contentNameScopesList);
}
public withEndRule(endRule: string): StackElement {
public setEndRule(endRule: string): StackElement {
if (this.endRule === endRule) {
return this;
}
return new StackElement(this.parent, this.ruleId, this._enterPos, endRule, this.scopeName, this.scopeMetadata, this.contentName, this.contentMetadata);
}
private _writeScopes(scopes: string[], outIndex: number): number {
if (this.parent) {
outIndex = this.parent._writeScopes(scopes, outIndex);
}
if (this.scopeName) {
scopes[outIndex++] = this.scopeName;
}
if (this.contentName) {
scopes[outIndex++] = this.contentName;
}
return outIndex;
}
/**
* Token scopes
*/
public generateScopes(): string[] {
let result: string[] = [];
this._writeScopes(result, 0);
return result;
return new StackElement(this.parent, this.ruleId, this._enterPos, endRule, this.nameScopesList, this.contentNameScopesList);
}
public hasSameRuleAs(other: StackElement): boolean {
@ -1117,13 +1228,11 @@ export class StackElement implements StackElementDef {
}
export class LocalStackElement {
public readonly scopeName: string;
public readonly scopeMetadata: number;
public readonly scopes: ScopeListElement;
public readonly endPos: number;
constructor(scopeName: string, scopeMetadata: number, endPos: number) {
this.scopeName = scopeName;
this.scopeMetadata = scopeMetadata;
constructor(scopes: ScopeListElement, endPos: number) {
this.scopes = scopes;
this.endPos = endPos;
}
}
@ -1159,19 +1268,17 @@ class LineTokens {
this._lastTokenEndIndex = 0;
}
public produce(stack: StackElement, endIndex: number, extraScopes?: LocalStackElement[]): void {
public produce(stack: StackElement, endIndex: number): void {
this.produceFromScopes(stack.contentNameScopesList, endIndex);
}
public produceFromScopes(scopesList: ScopeListElement, endIndex: number): void {
if (this._lastTokenEndIndex >= endIndex) {
return;
}
if (this._emitBinaryTokens) {
let metadata: number;
if (extraScopes && extraScopes.length > 0) {
metadata = extraScopes[extraScopes.length - 1].scopeMetadata;
} else {
metadata = stack.contentMetadata;
}
let metadata = scopesList.metadata;
if (this._binaryTokens.length > 0 && this._binaryTokens[this._binaryTokens.length - 1] === metadata) {
// no need to push a token with the same metadata
this._lastTokenEndIndex = endIndex;
@ -1185,14 +1292,7 @@ class LineTokens {
return;
}
let scopes = stack.generateScopes();
let outIndex = scopes.length;
if (extraScopes) {
for (let i = 0; i < extraScopes.length; i++) {
scopes[outIndex++] = extraScopes[i].scopeName;
}
}
let scopes = scopesList.generateScopes();
if (IN_DEBUG_MODE) {
console.log(' token: |' + this._lineText.substring(this._lastTokenEndIndex, endIndex).replace(/\n$/, '\\n') + '|');
@ -1219,7 +1319,7 @@ class LineTokens {
if (this._tokens.length === 0) {
this._lastTokenEndIndex = -1;
this.produce(stack, lineLength, null);
this.produce(stack, lineLength);
this._tokens[this._tokens.length - 1].startIndex = 0;
}
@ -1235,87 +1335,10 @@ class LineTokens {
if (this._binaryTokens.length === 0) {
this._lastTokenEndIndex = -1;
this.produce(stack, lineLength, null);
this.produce(stack, lineLength);
this._binaryTokens[this._binaryTokens.length - 2] = 0;
}
return this._binaryTokens;
}
}
export class ScopeListProvider {
private _stack: StackElement;
private _localStack: LocalStackElement[];
private _extraScope: string;
constructor(stack: StackElement, localStack: LocalStackElement[], extraScope: string) {
this._stack = stack;
this._localStack = localStack;
this._extraScope = extraScope;
}
private static _matches(scope: string, selector: string, selectorPrefix: string): boolean {
return (
scope !== null
&& (selector === scope || scope.substring(0, selectorPrefix.length) === selectorPrefix)
);
}
public matches(parentScopes:string[]): boolean {
if (parentScopes === null) {
return true;
}
let len = parentScopes.length;
let index = 0;
let selector = parentScopes[index];
let selectorPrefix = selector + '.';
if (ScopeListProvider._matches(this._extraScope, selector, selectorPrefix)) {
index++;
if (index === len) {
return true;
}
selector = parentScopes[index];
selectorPrefix = selector + '.';
}
if (this._localStack) {
for (let j = this._localStack.length - 1; j >= 0; j--) {
if (ScopeListProvider._matches(this._localStack[j].scopeName, selector, selectorPrefix)) {
index++;
if (index === len) {
return true;
}
selector = parentScopes[index];
selectorPrefix = selector + '.';
}
}
}
let stack = this._stack;
while (stack) {
if (ScopeListProvider._matches(stack.contentName, selector, selectorPrefix)) {
index++;
if (index === len) {
return true;
}
selector = parentScopes[index];
selectorPrefix = selector + '.';
}
if (ScopeListProvider._matches(stack.scopeName, selector, selectorPrefix)) {
index++;
if (index === len) {
return true;
}
selector = parentScopes[index];
selectorPrefix = selector + '.';
}
stack = stack.parent;
}
return false;
}
}

Просмотреть файл

@ -15,6 +15,7 @@ import {
parseTheme, ParsedThemeRule,
FontStyle, ColorMap
} from '../theme';
import * as plist from 'fast-plist';
const THEMES_TEST_PATH = path.join(__dirname, '../../test-cases/themes');
@ -23,6 +24,7 @@ const THEMES_TEST_PATH = path.join(__dirname, '../../test-cases/themes');
interface ILanguageRegistration {
id: string;
extensions: string[];
filenames: string[];
}
interface IGrammarRegistration {
@ -80,7 +82,27 @@ class Resolver implements RegistryOptions {
}
}
throw new Error('Could not findLanguageByExtension for ' + fileExtension);
return null;
}
public findLanguageByFilename(filename: string): string {
for (let i = 0; i < this._languages.length; i++) {
let language = this._languages[i];
if (!language.filenames) {
continue;
}
for (let j = 0; j < language.filenames.length; j++) {
let lFilename = language.filenames[j];
if (filename === lFilename) {
return language.id;
}
}
}
return null;
}
public findGrammarByLanguage(language: string): IGrammarRegistration {
@ -125,12 +147,22 @@ function explainThemeScope(theme: IRawTheme, scope: string): IRawThemeSetting[]
continue;
}
for (let j = 0, lenJ = selectors.length; j < lenJ; j++) {
let selector = selectors[j];
let rawSelector = selectors[j];
let selector: string;
let lastSpaceIndex = rawSelector.lastIndexOf(' ');
if (lastSpaceIndex >= 0) {
selector = rawSelector.substr(lastSpaceIndex + 1);
} else {
selector = rawSelector;
}
let selectorPrefix = selector + '.';
if (selector === scope || scope.substring(0, selectorPrefix.length) === selectorPrefix) {
// match!
result[resultLen++] = setting;
// break the loop
j = lenJ;
}
}
@ -238,13 +270,14 @@ function assertTokenization(theme: IRawTheme, colorMap: string[], fileContents:
let expectedContent = expected[j].content;
let expectedColor = expected[j].color;
if (actualColor !== expectedColor) {
fail('at ' + actualContent + ', color mismatch: ' + actualColor + ', ' + expectedColor);
let contentIsInvisible = /^\s+$/.test(expectedContent);
if (!contentIsInvisible && actualColor !== expectedColor) {
fail(`at ${actualContent} (${i}-${j}), color mismatch: ${actualColor}, ${expectedColor}`);
break;
}
if (actualContent.substr(0, expectedContent.length) !== expectedContent) {
fail('at ' + actualContent + ', content mismatch: ' + actualContent + ', ' + expectedContent);
fail(`at ${actualContent} (${i}-${j}), content mismatch: ${actualContent}, ${expectedContent}`);
break;
}
@ -275,7 +308,10 @@ function assertThemeTokenization(themeName:string, theme: IRawTheme, resolver: R
// Determine the language
let testFileExtension = path.extname(testFile);
let language = resolver.findLanguageByExtension(testFileExtension);
let language = resolver.findLanguageByExtension(testFileExtension) || resolver.findLanguageByFilename(testFile);
if (!language) {
throw new Error('Could not determine language for ' + testFile);
}
let grammar = resolver.findGrammarByLanguage(language);
let embeddedLanguages: { [scopeName: string]: number; } = Object.create(null);
@ -307,16 +343,21 @@ function assertThemeTokenization(themeName:string, theme: IRawTheme, resolver: R
(<any>dark_plus).settings = dark_vs.settings.concat(dark_plus.settings);
let hc_black: IRawTheme = JSON.parse(fs.readFileSync(path.join(THEMES_TEST_PATH, 'hc_black.json')).toString());
let abyss: IRawTheme = plist.parse(fs.readFileSync(path.join(THEMES_TEST_PATH, 'Abyss.tmTheme')).toString());
let monokai: IRawTheme = plist.parse(fs.readFileSync(path.join(THEMES_TEST_PATH, 'Monokai.tmTheme')).toString());
// Load all language/grammar metadata
let _grammars: IGrammarRegistration[] = JSON.parse(fs.readFileSync(path.join(THEMES_TEST_PATH, 'grammars.json')).toString('utf8'));
let _languages: ILanguageRegistration[] = JSON.parse(fs.readFileSync(path.join(THEMES_TEST_PATH, 'languages.json')).toString('utf8'));
let resolver = new Resolver(_grammars, _languages);
assertThemeTokenization('light_vs', light_vs, resolver);
assertThemeTokenization('light_plus', light_plus, resolver);
assertThemeTokenization('dark_vs', dark_vs, resolver);
assertThemeTokenization('dark_plus', dark_plus, resolver);
assertThemeTokenization('hc_black', hc_black, resolver);
// assertThemeTokenization('abyss', abyss, resolver);
// assertThemeTokenization('light_vs', light_vs, resolver);
// assertThemeTokenization('light_plus', light_plus, resolver);
// assertThemeTokenization('dark_vs', dark_vs, resolver);
// assertThemeTokenization('dark_plus', dark_plus, resolver);
// assertThemeTokenization('hc_black', hc_black, resolver);
// assertThemeTokenization('monokai', monokai, resolver);
})();
describe('Theme matching', () => {
@ -416,8 +457,8 @@ describe('Theme matching', () => {
assertMatch('bar', [
new ThemeTrieElementRule(null, FontStyle.NotSet, _NOT_SET, _C),
new ThemeTrieElementRule(['selector', 'source.css'], FontStyle.Bold, _NOT_SET, _C)
new ThemeTrieElementRule(null, FontStyle.NotSet, _NOT_SET, _C),
]);
});
});

Просмотреть файл

@ -440,7 +440,8 @@
"value": "html",
"scopes": [
"text.jade",
"meta.tag.other entity.name.tag.jade"
"meta.tag.other",
"entity.name.tag.jade"
]
}
]
@ -458,7 +459,8 @@
"value": "head",
"scopes": [
"text.jade",
"meta.tag.other entity.name.tag.jade"
"meta.tag.other",
"entity.name.tag.jade"
]
}
]