diff --git a/README.md b/README.md index e5d6dba..dd67610 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Show a horizontal line between the brackets? Enabled by default >`"bracket-pair-colorizer-2.scopeLineRelativePosition"` Disable this to show the vertical line in column 0 ![Scope Line](images/no-relative.png "Gutter Brackets Example") - + >`"bracket-pair-colorizer-2.scopeLineCSS"` Choose a border style to highlight the active scope. Use `{color}` to match the existing bracket color @@ -97,6 +97,28 @@ Choose a border style to highlight the active scope. Use `{color}` to match the >`"bracket-pair-colorizer-2.excludedLanguages"` Exclude a language from being colorized +>`"bracket-pair-colorizer-2.htmlStyleTagsLanguages"` +Languages which should colorize HTML-style tags e.g. `
` matches to `
`. This will not affect the behavior of other types of brackets +![HTML-Style Tags](images/htmlTags.png "HTML-Style Tags Example") + +```json +"bracket-pair-colorizer-2.htmlStyleTagsLanguages": [ + "html", + "xml" +] +``` +Exclude a language from being colorized + +>`"bracket-pair-colorizer-2.htmlIgnoredTags"` +Tags which do not have a corresponding close and therefore should be ignored by the HTML colorization e.g. `
` + +```json +"bracket-pair-colorizer-2.htmlIgnoredTags": [ + "br", + "hr" +] +``` + ### Commands These commands will expand/undo the cursor selection to the next scope diff --git a/images/htmlTags.png b/images/htmlTags.png new file mode 100644 index 0000000..2269958 Binary files /dev/null and b/images/htmlTags.png differ diff --git a/package.json b/package.json index 14d0351..453621e 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,24 @@ "default": [], "description": "Don't colorize files of these languages", "scope": "window" + }, + "bracket-pair-colorizer-2.htmlStyleTagsLanguages": { + "type": "array", + "default": [ + "html", + "xml" + ], + "description": "Languages which should colorize HTML-style tags e.g.
matches to
. This will not affect the behavior of other types of brackets", + "scope": "window" + }, + "bracket-pair-colorizer-2.htmlIgnoredTags": { + "type": "array", + "default": [ + "br", + "hr" + ], + "description": "Tags which do not have a corresponding close therefore should be ignored by the HTML colorization e.g.
", + "scope": "window" } } } diff --git a/src/bracketUtil.ts b/src/bracketUtil.ts index 7bc1573..de35d98 100644 --- a/src/bracketUtil.ts +++ b/src/bracketUtil.ts @@ -13,7 +13,7 @@ export function getRegexForBrackets(input: ISimpleInternalBracket[]): RegExp { return createBracketOrRegExp(pieces); } -function createBracketOrRegExp(pieces: string[]): RegExp { +export function createBracketOrRegExp(pieces: string[]): RegExp { const regexStr = `(${pieces.map(prepareBracketForRegExp).join(")|(")})`; return createRegExp(regexStr, true, { global: true }); } diff --git a/src/documentDecoration.ts b/src/documentDecoration.ts index 8b45db6..0b5eb6e 100644 --- a/src/documentDecoration.ts +++ b/src/documentDecoration.ts @@ -8,6 +8,9 @@ import Settings from "./settings"; import TextLine from "./textLine"; import { ignoreBracketsInToken, LineTokens } from "./vscodeFiles"; import { TextDocumentContentChangeEvent } from "vscode"; +import { createBracketOrRegExp } from "./bracketUtil"; + +type Match = { content: string, index: number }; export default class DocumentDecoration { public readonly settings: Settings; @@ -320,7 +323,9 @@ export default class DocumentDecoration { const tokens = tokenized.tokens; const lineTokens = new LineTokens(tokens, newText); - const matches = new Array<{ content: string, index: number }>(); + const matches = new Array(); + const htmlMatches = { open: new Array(), close: new Array(), ignore: new Array() }; + const count = lineTokens.getCount(); for (let i = 0; i < count; i++) { const tokenType = lineTokens.getStandardTokenType(i); @@ -330,24 +335,68 @@ export default class DocumentDecoration { const currentTokenText = newText.substring(searchStartOffset, searchEndOffset); - let result: RegExpExecArray | null; - // tslint:disable-next-line:no-conditional-assignment - while ((result = this.languageConfig.regex.exec(currentTokenText)) !== null) { - matches.push({ content: result[0], index: result.index + searchStartOffset }); + this.pushMatches(currentTokenText, this.languageConfig.regex, searchStartOffset, matches); + if (this.languageConfig.colorHtmlStyleTags) { + this.pushMatches( + currentTokenText, createBracketOrRegExp([""]), searchStartOffset, htmlMatches.close + ); + this.pushMatches( + currentTokenText, createBracketOrRegExp(["<"]), searchStartOffset, htmlMatches.open + ); + this.pushMatches( + currentTokenText, + createBracketOrRegExp( + this.settings.htmlIgnoredTags.map((value: string) => { return "<" + value }) + ), searchStartOffset, htmlMatches.ignore + ); } } } - const newLine = new TextLine(tokenized.ruleStack, previousLineState, index); + // Filter out the overlap between the open tags and the other tags + // (also protects against a potential <> in languageConfig) + const htmlCloseIgnoreIndexes = matches.concat(htmlMatches.close, htmlMatches.ignore) + .map((value: { index: number }) => { return value.index }); + htmlMatches.open = htmlMatches.open.filter(function (value: { index: number }) { + return htmlCloseIgnoreIndexes.indexOf(value.index) == -1 + }); + + // Collate all matches + let tokensToAdd = new Array<{ content: string, index: number, key: number, open: boolean }>(); for (const match of matches) { const lookup = this.languageConfig.bracketToId.get(match.content); if (lookup) { - newLine.AddToken(match.content, match.index, lookup.key, lookup.open); + tokensToAdd.push({ content: match.content, index: match.index, key: lookup.key, open: lookup.open }); } } + tokensToAdd = tokensToAdd.concat( + htmlMatches.open.map((match: Match) => { + return { content: match.content, index: match.index, key: this.languageConfig.htmlKey, open: true } + }), + htmlMatches.close.map((match: Match) => { + return { content: match.content, index: match.index, key: this.languageConfig.htmlKey, open: false } + }) + ); + + // Force index order so that the bracket stack is created correctly + tokensToAdd.sort((a, b) => a.index - b.index); + + const newLine = new TextLine(tokenized.ruleStack, previousLineState, index); + for (const token of tokensToAdd) { + newLine.AddToken(token.content, token.index, token.key, token.open); + } + return newLine; } + private pushMatches(text: string, regex: RegExp, indexOffset: number, matches: Array) { + let result: RegExpExecArray | null; + // tslint:disable-next-line:no-conditional-assignment + while ((result = regex.exec(text)) !== null) { + matches.push({ content: result[0], index: result.index + indexOffset }); + } + } + private setOverLineDecoration( bracket: Bracket, event: vscode.TextEditorSelectionChangeEvent, diff --git a/src/languageConfig.ts b/src/languageConfig.ts index b481c8a..22a4418 100644 --- a/src/languageConfig.ts +++ b/src/languageConfig.ts @@ -4,10 +4,23 @@ export default class LanguageConfig { public readonly grammar: IGrammar; public readonly regex: RegExp; public readonly bracketToId: Map; + // Key to be used for html brackets + public readonly htmlKey: number; + public readonly colorHtmlStyleTags: boolean; - constructor(grammar: IGrammar, regex: RegExp, bracketToId: Map) { + constructor( + grammar: IGrammar, regex: RegExp, bracketToId: Map, + colorHtmlStyleTags: boolean + ) { this.grammar = grammar; this.regex = regex; this.bracketToId = bracketToId; + let htmlKey = -1; + if (colorHtmlStyleTags) { + bracketToId.forEach((value: { key: number }) => { if (value.key > htmlKey) { htmlKey = value.key } }); + htmlKey++; + } + this.htmlKey = htmlKey; + this.colorHtmlStyleTags = colorHtmlStyleTags; } } diff --git a/src/multipleIndexes.ts b/src/multipleIndexes.ts index 340bed9..cc0846a 100644 --- a/src/multipleIndexes.ts +++ b/src/multipleIndexes.ts @@ -32,6 +32,10 @@ export default class MultipleBracketGroups implements IBracketManager { this.allLinesOpenBracketStack[value.key] = []; this.previousOpenBracketColorIndexes[value.key] = 0; } + if (languageConfig.colorHtmlStyleTags) { + this.allLinesOpenBracketStack[languageConfig.htmlKey] = []; + this.previousOpenBracketColorIndexes[languageConfig.htmlKey] = 0; + } } } diff --git a/src/settings.ts b/src/settings.ts index c853f35..10649f4 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -5,7 +5,7 @@ import TextMateLoader from "./textMateLoader"; import { ThemeColor } from "vscode"; export default class Settings { - public readonly TextMateLoader = new TextMateLoader(); + public readonly TextMateLoader: TextMateLoader; public readonly bracketDecorations: Map; public readonly colorMode: ColorMode; public readonly contextualParsing: boolean; @@ -22,6 +22,7 @@ export default class Settings { public readonly colors: string[]; public readonly unmatchedScopeColor: string; public readonly excludedLanguages: Set; + public readonly htmlIgnoredTags: string[]; public isDisposed = false; private readonly gutterIcons: GutterIconManager; private readonly activeBracketCSSElements: string[][]; @@ -129,6 +130,7 @@ export default class Settings { } this.colors = configuration.get("colors") as string[]; + if (!Array.isArray(this.colors)) { throw new Error("colors is not an array"); } @@ -142,6 +144,20 @@ export default class Settings { } this.excludedLanguages = new Set(excludedLanguages); + + this.htmlIgnoredTags = configuration.get("htmlIgnoredTags") as string[]; + + if (!Array.isArray(this.htmlIgnoredTags)) { + throw new Error("htmlIgnoredTags is not an array"); + } + + let htmlStyleTagsLanguages = configuration.get("htmlStyleTagsLanguages") as string[]; + + if (!Array.isArray(htmlStyleTagsLanguages)) { + throw new Error("htmlStyleTagsLanguages is not an array"); + } + + this.TextMateLoader = new TextMateLoader(htmlStyleTagsLanguages); } public dispose() { diff --git a/src/textMateLoader.ts b/src/textMateLoader.ts index 04a1bdf..29d0168 100644 --- a/src/textMateLoader.ts +++ b/src/textMateLoader.ts @@ -15,10 +15,12 @@ export class TextMateLoader { private readonly vsctm: any; private readonly oniguruma: any; private readonly languageConfigs = new Map(); - constructor() { + private readonly htmlStyleTagsLanguages: string[]; + constructor(htmlStyleTagsLanguages: string[] = []) { this.initializeGrammars(); this.vsctm = this.loadTextMate(); this.oniguruma = this.loadOniguruma(); + this.htmlStyleTagsLanguages = htmlStyleTagsLanguages; } public tryGetLanguageConfig(languageID: string) { @@ -105,7 +107,10 @@ export class TextMateLoader { } const regex = getRegexForBrackets(mappedBrackets); - this.languageConfigs.set(languageID, new LanguageConfig(grammar, regex, bracketToId)); + const colorHtmlStyleTags = this.htmlStyleTagsLanguages.indexOf(languageID) > -1; + this.languageConfigs.set( + languageID, new LanguageConfig(grammar, regex, bracketToId, colorHtmlStyleTags) + ); } } return grammar;