Feature/change token format (#1705)

* add check to return string for classname construction
if only passed single word
also add more token types for reasonml

* add more default tokens

* add css types and increase token colors

* add vscode style token colors for onedark
add functionality to parse the token colors format

* switch oni token style to match vscode

* remove convoluted ternary in flattenTheme

* tweak get key from token color to match updated token style

* import deepmerge avoid type issues via require.default

* fix bad merge in common.ts

* comment out console.log

* fix lint errors

* merge default and theme tokens

* add types for deepmerge and ts-ignore the incorrect package

* fix package json reversions

* remove deepmerge create custom strategy for merging tokens

* fix lint errors

* update tests and fix object.entries (not available in test)

* fix scope fetching in syntax reconciler and make variables more readable

* fix atomic calls length check in synchronizeTokenColors

* Stop inclusion of banned tokens in the syntax highlight reconciler

* rank token scopes by priority and depth

* separate out tokenRanking into a tokenSelector class -> single responsibility principle

* refactor getMatchingToken to use recursive method

* add break clause to function

* fix lint errors

* change test to use foeground instead of foregroundColor

* fix incorrect set fontStyle in vim highlights for italics
prettify comments into jsDoc blocks

* fix comment for TokenScorer

* add initial test for token theme provider
be explicit about passing in the token colors and default to the service

* fix passing in of the token colors as props explicitly
this makes the component (TokenThemeProvider) more testable

* refactor construct className following testing
add passing jest-styled-components test

* refactor constructClassname further to be more fault tolerant
update jest and use test.each for classname testing

* fix failing test re. checking fontsyle for bold and italic

* further tweak to create Classname to further simplify its workings
make get Css Rule should actually return a css rule and not occasionally a boolean

* fix type annotations for getCssRule and ensure str always returned

* add tokenScorer tests and improve copy for themeprovider tests
This commit is contained in:
Akin 2018-09-09 22:51:04 +01:00 committed by GitHub
parent 663b877202
commit 5238600911
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1262 additions and 316 deletions

View File

@ -22,9 +22,12 @@ export const scopesToString = (scope: string[]) => {
if (scope) {
return scope
.map(s => {
const lastStop = s.lastIndexOf(".")
const remainder = s.substring(0, lastStop)
return remainder.replace(/\./g, "-")
if (s.includes(".")) {
const lastStop = s.lastIndexOf(".")
const remainder = s.substring(0, lastStop)
return remainder.replace(/\./g, "-")
}
return s
})
.filter(value => !!value)
.join(" ")

View File

@ -16,6 +16,7 @@ import {
ISyntaxHighlightState,
ISyntaxHighlightTokenInfo,
} from "./SyntaxHighlightingStore"
import { TokenScorer } from "./TokenScorer"
import * as Selectors from "./SyntaxHighlightSelectors"
@ -26,6 +27,7 @@ import * as Selectors from "./SyntaxHighlightSelectors"
// window and viewport
export class SyntaxHighlightReconciler {
private _previousState: { [line: number]: ISyntaxHighlightLineInfo } = {}
private _tokenScorer = new TokenScorer()
constructor(private _editor: NeovimEditor, private _tokenColors: TokenColors) {}
@ -64,26 +66,26 @@ export class SyntaxHighlightReconciler {
return this._previousState[line] !== latestLine
})
const tokens = filteredLines.map(li => {
const lineNumber = parseInt(li, 10)
const tokens = filteredLines.map(currentLine => {
const lineNumber = parseInt(currentLine, 10)
const line = Selectors.getLineFromBuffer(currentHighlightState, lineNumber)
const highlights = this._mapTokensToHighlights(line.tokens)
return {
line: parseInt(li, 10),
line: parseInt(currentLine, 10),
highlights,
}
})
filteredLines.forEach(li => {
const lineNumber = parseInt(li, 10)
this._previousState[li] = Selectors.getLineFromBuffer(
filteredLines.forEach(line => {
const lineNumber = parseInt(line, 10)
this._previousState[line] = Selectors.getLineFromBuffer(
currentHighlightState,
lineNumber,
)
})
if (tokens.length > 0) {
if (tokens.length) {
Log.verbose(
"[SyntaxHighlightReconciler] Applying changes to " + tokens.length + " lines.",
)
@ -91,9 +93,7 @@ export class SyntaxHighlightReconciler {
this._tokenColors.tokenColors,
(highlightUpdater: any) => {
tokens.forEach(token => {
const line = token.line
const highlights = token.highlights
const { line, highlights } = token
if (Log.isDebugLoggingEnabled()) {
Log.debug(
"[SyntaxHighlightingReconciler] Updating tokens for line: " +
@ -122,16 +122,7 @@ export class SyntaxHighlightReconciler {
private _getHighlightGroupFromScope(scopes: string[]): TokenColor {
const configurationColors = this._tokenColors.tokenColors
for (const scope of scopes) {
const matchingRule = configurationColors.find((c: any) => scope.indexOf(c.scope) === 0)
if (matchingRule) {
// TODO: Convert to highlight group id
return matchingRule
}
}
return null
const highestRanked = this._tokenScorer.rankTokenScopes(scopes, configurationColors)
return highestRanked
}
}

View File

@ -0,0 +1,129 @@
import { TokenColor } from "./../TokenColors"
interface TokenRanking {
depth: number
highestRankedToken: TokenColor
}
/**
* Determines the correct token to render for a particular item
* in a line based on textmate highlighting rules
* @name TokenScorer
* @class
*/
export class TokenScorer {
/**
* meta tokens are not intended for syntax highlighting but for other types of plugins
* source is a token that All items are given effectively giving it no value from the
* point of view of syntax highlighting as it distinguishes nothing
*
* see: https://www.sublimetext.com/docs/3/scope_naming.html
*/
private _BANNED_TOKENS = ["meta", "source"]
private readonly _SCOPE_PRIORITIES = {
support: 1,
}
/**
* rankTokenScopes
* If more than one scope selector matches the current scope then they are ranked
* according to how good a match they each are. The winner is the scope selector
* which (in order of precedence):
* 1. Match the element deepest down in the scope e.g.
* string wins over source.php when the scope is source.php string.quoted.
* 2. Match most of the deepest element e.g. string.quoted wins over string.
* 3. Rules 1 and 2 applied again to the scope selector when removing the deepest element
* (in the case of a tie), e.g. text source string wins over source string.
*
* Reference: https://macromates.com/manual/en/scope_selectors
*
* @name rankTokenScopes
* @function
* @param {string[]} scopes
* @param {TokenColor[]} themeColors
* @returns {TokenColor}
*/
public rankTokenScopes(scopes: string[], themeColors: TokenColor[]): TokenColor {
const initialRanking: TokenRanking = { highestRankedToken: null, depth: null }
const { highestRankedToken } = scopes.reduce((highestSoFar, scope) => {
if (this._isBannedScope(scope)) {
return highestSoFar
}
const matchingToken = this._getMatchingToken(scope, themeColors)
if (!matchingToken) {
return highestSoFar
}
const depth = scope.split(".").length
if (depth === highestSoFar.depth) {
const highestPrecedence = this._determinePrecedence(
matchingToken,
highestSoFar.highestRankedToken,
)
return { highestRankedToken: highestPrecedence, depth }
}
if (depth > highestSoFar.depth) {
return { highestRankedToken: matchingToken, depth }
}
return highestSoFar
}, initialRanking)
return highestRankedToken || null
}
private _isBannedScope = (scope: string) => {
return this._BANNED_TOKENS.some(token => scope.includes(token))
}
private _getPriority = (token: TokenColor) => {
const priorities = Object.keys(this._SCOPE_PRIORITIES)
return priorities.reduce(
(acc, priority) =>
token.scope.includes(priority) && this._SCOPE_PRIORITIES[priority] < acc.priority
? { priority: this._SCOPE_PRIORITIES[priority], token }
: acc,
{ priority: 0, token },
)
}
/**
* Assign each token a priority based on `SCOPE_PRIORITIES` and then
* sort by priority take the first aka the highest priority one
*
* @name _determinePrecedence
* @function
* @param {TokenColor[]} ...tokens
* @returns {TokenColor}
*/
private _determinePrecedence(...tokens: TokenColor[]): TokenColor {
const [{ token }] = tokens
.map(this._getPriority)
.sort((prev, next) => next.priority - prev.priority)
return token
}
/**
* if the lowest scope level doesn't match then we go up one level
* i.e. constant.numeric.special -> constant.numeric
* and search the theme colors for a match
*
* @name _getMatchingToken
* @function
* @param {string} scope
* @param {TokenColor[]} theme
* @returns {TokenColor}
*/
private _getMatchingToken(scope: string, theme: TokenColor[]): TokenColor {
const parts = scope.split(".")
if (parts.length < 2) {
return null
}
const matchingToken = theme.find(color => color.scope === scope)
if (matchingToken) {
return matchingToken
}
const currentScope = parts.slice(0, parts.length - 1).join(".")
return this._getMatchingToken(currentScope, theme)
}
}

View File

@ -1,44 +1,8 @@
import * as React from "react"
import { css, ThemeProvider, withTheme } from "styled-components"
import { getInstance as TokenColorsInstance, TokenColor } from "./../../Services/TokenColors"
import { IThemeColors } from "./../../UI/components/common"
/**
* Provides a check that a token exists and has valid values
* if not it returns nothing or in the case of the foregroundColor it returns a default
* @returns {string | undefined}
*/
const cssToken = (theme: INewTheme, token: string) => (property: string) => {
try {
const details = theme["editor.tokenColors.hoverTokens"][token]
return details[property]
} catch (e) {
if (property === "foregroundColor") {
return theme["toolTip.foreground"]
}
}
}
/**
* Construct Class name is a function which takes a token
* and returns another function which takes the theme as an argument
* with which it creates a css class based on the token name and returns this as
* a string
* @returns {fn(theme) => string}
*/
const constructClassName = (token: string) => (theme: INewTheme) => {
const notPunctuation = !token.includes("punctuation")
const tokenAsClass = token.replace(/[.]/g, "-")
const tokenStyle = cssToken(theme, token)
const cssClass = `
.${tokenAsClass} {
color: ${tokenStyle("foregroundColor") || ""};
${tokenStyle("bold") && notPunctuation ? "font-weight: bold" : ""};
${tokenStyle("italic") ? "font-style: italic" : ""};
}
`
return cssClass
}
import { TokenColor, TokenColorStyle } from "./../../Services/TokenColors"
import { Css, IThemeColors } from "./../../UI/components/common"
/**
* A object representing a key of default oni tokens and an array of tokens
@ -46,6 +10,7 @@ const constructClassName = (token: string) => (theme: INewTheme) => {
*/
const defaultsToMap = {
"variable.parameter": [
"support",
"support.variable",
"support.variable.property.dom",
"support.variable.dom",
@ -56,24 +21,36 @@ const defaultsToMap = {
"support.variable.property",
"variable.language",
"variable.language.this",
"variable.function",
"variable.parameter",
"variable.object",
"variable",
"meta.object.type",
"meta.object",
"variable.other.readwrite",
"variable.other.readwrite.alias",
"constant.numeric",
"constant.language",
"constant.numeric.integer",
"constant.character.escape",
],
"support.function": [
"invalid",
"function",
"support.function",
"entity.name",
"entity.name.section",
"entity.name.type",
"entity.name.tag",
"entity.name.type.alias",
"entity.name.type.class",
"entity.name.function",
"entity.name.type.enum",
"entity.name.type.interface",
"entity.name.type.module",
"entity.other.attribute.name",
"entity.other.inherited-class",
"entity.other.attribute.name",
"punctuation.accessor",
"punctuation.separator.continuation",
"punctuation.separator.comma",
@ -81,9 +58,11 @@ const defaultsToMap = {
"punctuation.terminator",
],
"variable.other.constant": [
"constant",
"constant.language",
"variable.other",
"entity.other",
"keyword",
"keyword.package",
"keyword.var",
"keyword.const",
@ -95,6 +74,7 @@ const defaultsToMap = {
"keyword.operator.expression.void",
"keyword.control.import",
"storage.type",
"storage.modifier",
"storage.type.type",
"storage.type.class",
"storage.type.enum",
@ -119,28 +99,16 @@ const defaultsToMap = {
"string.quoted.double",
"string.quoted.single",
"string.quoted.triple",
"string",
"string.other",
],
}
const symbols = Object.values(defaultsToMap)
.reduce((acc, a) => [...acc, ...a], [])
.map(constructClassName)
type TokenFunc = (theme: INewTheme) => string
const flattenedSymbols = (theme: INewTheme, fns: TokenFunc[]) => fns.map(fn => fn(theme)).join("\n")
const styles = css`
${p => flattenedSymbols(p.theme, symbols)};
`
export interface INewTheme extends IThemeColors {
"editor.tokenColors.hoverTokens": {
[token: string]: {
foregroundColor: string
backgroundColor: string
italic: string
bold: string
}
[token: string]: TokenColorStyle
}
}
@ -148,15 +116,21 @@ interface IDefaultMap {
[defaultTokens: string]: string[]
}
interface RenderProps {
theme: INewTheme
styles: Css
}
interface IProps {
render: (s: { theme: INewTheme; styles: any }) => React.ReactElement<any>
render: (s: RenderProps) => React.ReactElement<RenderProps> | React.ReactNode
theme: INewTheme
defaultMap?: IDefaultMap
tokenColors?: TokenColor[]
}
interface IState {
theme: INewTheme
styles: string
styles: Css
}
interface IGenerateTokenArgs {
@ -164,6 +138,8 @@ interface IGenerateTokenArgs {
defaultTokens: TokenColor[]
}
type Style = "bold" | "italic" | "foreground" | "background"
/**
* **TokenThemeProvider** is a Render Prop
* It is designed to be used to give UI components access to a
@ -178,45 +154,54 @@ interface IGenerateTokenArgs {
class TokenThemeProvider extends React.Component<IProps, IState> {
public state: IState = {
styles: null,
theme: null,
theme: this.props.theme,
}
private tokenColors = TokenColorsInstance().tokenColors
private enhancedTokens: TokenColor[]
public flattenedDefaults = Object.values(defaultsToMap).reduce((acc, a) => [...acc, ...a], [])
public componentDidMount() {
const editorTokens = this.createThemeFromTokens()
this.setState({ theme: { ...this.props.theme, ...editorTokens } })
const themeTokenNames = this.convertTokenNamesToClasses(this.props.tokenColors)
const tokensToHightlight = [...themeTokenNames, ...this.flattenedDefaults]
const styles = this.constructStyles(tokensToHightlight)
const editorTokens = this.createThemeFromTokens(this.props.tokenColors)
const theme = { ...this.props.theme, ...editorTokens }
this.setState({ theme, styles })
}
public createThemeFromTokens() {
if (!this.enhancedTokens) {
this.enhancedTokens = this.generateTokens({ defaultTokens: this.tokenColors })
}
const tokenColorsMap = this.enhancedTokens.reduce((theme, token) => {
return {
...theme,
[token.scope]: {
...token.settings,
},
}
}, {})
public createThemeFromTokens(tokens: TokenColor[]) {
const combinedThemeAndDefaultTokens = this.generateTokens({
defaultTokens: this.props.tokenColors,
})
const tokenColorsMap = combinedThemeAndDefaultTokens.reduce(
(theme, token) => {
return {
...theme,
[token.scope]: {
...token.settings,
},
}
},
{} as { [key: string]: TokenColorStyle },
)
return { "editor.tokenColors.hoverTokens": tokenColorsMap }
}
public generateTokens({ defaultMap = defaultsToMap, defaultTokens }: IGenerateTokenArgs) {
const newTokens = Object.keys(defaultMap).reduce((acc, key) => {
const defaultToken = this.tokenColors.find(token => token.scope === key)
const newTokens = Object.keys(defaultMap).reduce((acc, defaultTokenName) => {
const defaultToken = this.props.tokenColors.find(
token => token.scope === defaultTokenName,
)
if (defaultToken) {
const tokens = defaultMap[key].map(name =>
const tokens = defaultMap[defaultTokenName].map(name =>
this.generateSingleToken(name, defaultToken),
)
return [...acc, ...tokens]
}
return acc
}, [])
return [...newTokens, ...this.tokenColors]
return [...newTokens, ...this.props.tokenColors]
}
public generateSingleToken(name: string, { settings }: TokenColor) {
@ -226,8 +211,88 @@ class TokenThemeProvider extends React.Component<IProps, IState> {
}
}
/**
* Provides a check that a token exists and has valid values
* if not it returns nothing or in the case of the foregroundColor it returns a default
* @returns {string}
*/
public getCssRule = (
hoverTokens: INewTheme["editor.tokenColors.hoverTokens"],
token: string,
style: Style,
) => {
const details = hoverTokens[token]
if (!details) {
return ""
}
const italicOrBold = details.fontStyle && details.fontStyle.includes(style)
switch (style) {
case "italic":
return italicOrBold ? "font-style: italic" : ""
case "bold":
return italicOrBold ? "font-weight: bold" : ""
case "foreground":
default:
return details[style] ? `color: ${details[style]}` : ""
}
}
/**
* Construct Class is a function which takes a token
* and returns another function which takes the theme as an argument
* with which it creates a css class based on the token name and returns this as a string
* @returns {fn(theme) => string}
*/
public constructClassName = (token: string) => (theme: INewTheme) => {
const tokenAsClass = token.replace(/[.]/g, "-")
const hoverTokens = theme["editor.tokenColors.hoverTokens"]
if (!hoverTokens || !(token in hoverTokens)) {
return ""
}
const foreground = this.getCssRule(hoverTokens, token, "foreground")
const italics = this.getCssRule(hoverTokens, token, "italic")
const bold = this.getCssRule(hoverTokens, token, "bold")
const hasContent = foreground || italics || bold
if (!hasContent) {
return ""
}
const cssClass = `
.${tokenAsClass} {
${bold};
${italics};
${foreground};
}
`
return cssClass
}
public convertTokenNamesToClasses = (tokenArray: TokenColor[]) => {
const arrayOfArrays = tokenArray.map(token => token.scope)
const names = [].concat(...arrayOfArrays)
return names
}
public constructStyles = (tokensToMap: string[] = this.flattenedDefaults) => {
const symbols = tokensToMap.map(this.constructClassName)
const flattenSymbols = (theme: INewTheme, fns: TokenFunc[]) =>
fns.map(fn => fn(theme)).join("\n")
const styles = css`
${p => flattenSymbols(p.theme, symbols)};
`
return styles
}
public render() {
const { theme } = this.state
const { theme, styles } = this.state
return (
theme && (
<ThemeProvider theme={theme}>{this.props.render({ theme, styles })}</ThemeProvider>

View File

@ -12,7 +12,7 @@ import { PluginManager } from "./../../Plugins/PluginManager"
import { Configuration, configuration, GenericConfigurationValues } from "./../Configuration"
import * as PersistentSettings from "./../Configuration/PersistentSettings"
import { TokenColor } from "./../TokenColors"
import { ThemeToken, TokenColor } from "./../TokenColors"
import { IThemeLoader, PluginThemeLoader } from "./ThemeLoader"
export interface IThemeColors {
@ -87,7 +87,7 @@ export interface IThemeColors {
"fileExplorer.cursor.background": string
"fileExplorer.cursor.foreground": string
"editor.tokenColors": TokenColor[]
"editor.tokenColors": ThemeToken[]
// LATER:
// - Notifications?

View File

@ -11,14 +11,20 @@ import { Event, IDisposable, IEvent } from "oni-types"
export interface TokenColor {
scope: string
settings: TokenColorStyle
// private field for determining where a token came from
_source?: string
}
export interface ThemeToken {
scope: string | string[]
settings: TokenColorStyle
}
export interface TokenColorStyle {
foregroundColor: string
backgroundColor: string
foreground: string
background: string
bold: boolean
italic: boolean
fontStyle: "bold" | "italic" | "bold italic"
}
import { Configuration, IConfigurationValues } from "./Configuration"
@ -65,19 +71,97 @@ export class TokenColors implements IDisposable {
this._subscriptions = []
}
private _flattenThemeTokens = (themeTokens: ThemeToken[] = []) => {
const multidimensionalTokens = themeTokens.map(token => {
if (Array.isArray(token.scope)) {
return token.scope.map(s => ({
scope: s,
settings: token.settings,
}))
}
return token
})
return [].concat(...multidimensionalTokens).filter(t => !!t.scope)
}
private _updateTokenColors(): void {
const tokenColorsFromTheme = this._themeManager.activeTheme
? this._themeManager.activeTheme.tokenColors
: []
const {
activeTheme: {
colors: { "editor.tokenColors": tokenColorsFromTheme = [] },
},
} = this._themeManager
const themeTokens = this._flattenThemeTokens(tokenColorsFromTheme)
const userColors = this._configuration.getValue("editor.tokenColors")
this._tokenColors = [
...(userColors || []),
...(tokenColorsFromTheme || []),
...this._defaultTokenColors,
]
this._tokenColors = this._mergeTokenColors({
user: userColors,
theme: themeTokens,
defaults: this._defaultTokenColors,
})
this._onTokenColorsChangedEvent.dispatch()
}
/**
* Merge different token source whilst unifying settings
* each source is passed by name so that later the priority
* for merging can be used e.g. if user source has a
* a higher priority then conflicting settings can prefer the
* user source
*/
private _mergeTokenColors(tokens: {
user: TokenColor[]
defaults: TokenColor[]
theme: TokenColor[]
}) {
return Object.keys(tokens).reduce(
(output, key) => {
const tokenColors: TokenColor[] = tokens[key]
return tokenColors.reduce((mergedTokens, currentToken) => {
const duplicateToken = mergedTokens.find(t => currentToken.scope === t.scope)
if (duplicateToken) {
return mergedTokens.map(existingToken => {
if (existingToken.scope === duplicateToken.scope) {
return this._mergeSettings(existingToken, {
...currentToken,
_source: key,
})
}
return existingToken
})
}
return [...mergedTokens, { ...currentToken, _source: key }]
}, output)
},
[] as TokenColor[],
)
}
private _mergeSettings(prev: TokenColor, next: TokenColor) {
const priority = {
user: 2,
theme: 1,
defaults: 0,
}
if (priority[next._source] > priority[prev._source]) {
return {
...next,
settings: {
...prev.settings,
...next.settings,
},
}
}
return {
...prev,
settings: {
...next.settings,
...prev.settings,
},
}
}
}
let _tokenColors: TokenColors

View File

@ -1,7 +1,7 @@
import * as os from "os"
import * as React from "react"
import styled, { boxShadowInset, css, fontSizeSmall, withProps } from "./common"
import styled, { boxShadowInset, css, Css, fontSizeSmall, withProps } from "./common"
export const QuickInfoWrapper = styled.div`
user-select: none;
@ -78,7 +78,7 @@ const childStyles = css`
`
interface DocProps {
tokenStyles?: any
tokenStyles?: Css
}
export const Documentation = withProps<DocProps>(styled.div)`
${fontSizeSmall};
@ -107,8 +107,9 @@ export const Documentation = withProps<DocProps>(styled.div)`
interface TitleProps {
padding?: string
tokenStyles?: any
tokenStyles?: Css
}
export const Title = withProps<TitleProps>(styled.div)`
padding: ${p => p.padding || "0.7rem"};
overflow: hidden;
@ -143,7 +144,7 @@ export const QuickInfoContainer = withProps<{ hasDocs: boolean }>(styled.div)`
`
export interface ITextProps {
tokenStyles?: any
tokenStyles?: Css
padding?: string
text?: string
html?: {

View File

@ -3,6 +3,7 @@ import * as React from "react"
import { QuickInfoContainer, QuickInfoDocumentation, QuickInfoTitle } from "./QuickInfo"
import TokenThemeProvider from "./../../Services/SyntaxHighlighting/TokenThemeProvider"
import { getInstance as TokenColorsInstance } from "./../../Services/TokenColors"
interface IQuickInfoProps {
titleAndContents: ITitleAndContents
@ -20,6 +21,7 @@ interface ITitleAndContents {
class QuickInfoHoverContainer extends React.Component<IQuickInfoProps> {
public render() {
const { tokenColors } = TokenColorsInstance()
const { titleAndContents, isVisible } = this.props
const hasTitle = !!(titleAndContents && titleAndContents.title.__html)
const hasDocs =
@ -33,12 +35,13 @@ class QuickInfoHoverContainer extends React.Component<IQuickInfoProps> {
return (
isVisible && (
<TokenThemeProvider
render={({ theme, styles }) => (
tokenColors={tokenColors}
render={({ styles }) => (
<QuickInfoContainer hasDocs={hasDocs}>
<QuickInfoTitle
padding={hasDocs ? "0.5rem" : null}
html={titleAndContents.title}
tokenStyles={styles}
padding={hasDocs && "0.5rem"}
html={titleAndContents.title}
/>
{titleAndContents.description && (
<QuickInfoDocumentation

View File

@ -1,6 +1,11 @@
import * as Color from "color"
import * as styledComponents from "styled-components"
import { ThemedStyledComponentsModule, ThemeProps } from "styled-components" // tslint:disable-line no-duplicate-imports
import {
FlattenInterpolation,
InterpolationValue,
ThemedStyledComponentsModule,
ThemeProps,
} from "styled-components" // tslint:disable-line no-duplicate-imports
import { IThemeColors } from "../../Services/Themes/ThemeManager"
export const bufferScrollBarSize = "7px"
@ -16,9 +21,7 @@ const {
IThemeColors
>
export type Css =
| styledComponents.InterpolationValue[]
| Array<styledComponents.FlattenInterpolation<ThemeProps<IThemeColors>>>
export type Css = InterpolationValue[] | Array<FlattenInterpolation<ThemeProps<IThemeColors>>>
type FlexDirection = "flex-start" | "flex-end" | "center" | "space-between"

View File

@ -13,12 +13,14 @@ import { TokenColor } from "./../Services/TokenColors"
import { NeovimInstance } from "./NeovimInstance"
const getGuiStringFromTokenColor = (color: TokenColor): string => {
if (color.settings.bold && color.settings.italic) {
const getGuiStringFromTokenColor = ({ settings: { fontStyle } }: TokenColor): string => {
if (!fontStyle) {
return "gui=none"
} else if (fontStyle.includes("bold italic")) {
return "gui=bold,italic"
} else if (color.settings.bold) {
} else if (fontStyle === "bold") {
return "gui=bold"
} else if (color.settings.italic) {
} else if (fontStyle === "italic") {
return "gui=italic"
} else {
return "gui=none"
@ -50,11 +52,9 @@ export class NeovimTokenColorSynchronizer {
const filteredHighlights = highlightsToAdd.filter(hl => !!hl)
const atomicCalls = filteredHighlights.map(hlCommand => {
return ["nvim_command", [hlCommand]]
})
const atomicCalls = filteredHighlights.map(hlCommand => ["nvim_command", [hlCommand]])
if (atomicCalls.length === 0) {
if (!atomicCalls.length) {
return
}
@ -79,10 +79,10 @@ export class NeovimTokenColorSynchronizer {
private _convertTokenStyleToHighlightInfo(tokenColor: TokenColor): string {
const name = this._getOrCreateHighlightGroup(tokenColor)
const foregroundColor = Color(tokenColor.settings.foregroundColor).hex()
const backgroundColor = Color(tokenColor.settings.backgroundColor).hex()
const foregroundColor = Color(tokenColor.settings.foreground).hex()
const backgroundColor = Color(tokenColor.settings.background).hex()
const gui = getGuiStringFromTokenColor(tokenColor)
return `:hi ${name} guifg=${foregroundColor} guibg=${backgroundColor} ${gui}`
return `:hi! ${name} guifg=${foregroundColor} guibg=${backgroundColor} ${gui}`
}
private _getOrCreateHighlightGroup(tokenColor: TokenColor): string {
@ -106,8 +106,13 @@ export class NeovimTokenColorSynchronizer {
}
private _getKeyFromTokenColor(tokenColor: TokenColor): string {
return `${tokenColor.scope}_${tokenColor.settings.backgroundColor}_${
tokenColor.settings.foregroundColor
}_${tokenColor.settings.bold}_${tokenColor.settings.italic}`
const {
settings: { background, foreground, fontStyle },
} = tokenColor
const bg = `background-${background}`
const fg = `foreground-${foreground}`
const bold = `bold-${fontStyle && fontStyle.includes("bold")}`
const italic = `italic-${fontStyle && fontStyle.includes("italic")}`
return `${tokenColor.scope}_${bg}_${fg}_${bold}_${italic}`
}
}

View File

@ -15,12 +15,25 @@ export interface IVimHighlight {
italic: boolean
}
const setFontStyle = (highlight: IVimHighlight) => {
switch (true) {
case highlight.bold && !highlight.italic:
return "bold"
case !highlight.bold && highlight.italic:
return "italic"
case highlight.bold && highlight.italic:
return "bold italic"
case !highlight.bold && !highlight.italic:
default:
return null
}
}
export const vimHighlightToTokenColorStyle = (highlight: IVimHighlight): TokenColorStyle => {
return {
foregroundColor: Color(highlight.foreground).hex(),
backgroundColor: Color(highlight.background).hex(),
bold: highlight.bold,
italic: highlight.italic,
foreground: Color(highlight.foreground).hex(),
background: Color(highlight.background).hex(),
fontStyle: setFontStyle(highlight),
}
}

View File

@ -9,6 +9,11 @@ describe("Markdown Conversion Functions", () => {
assert.ok(className === "token-scope")
})
it("Scopes to string fn should correctly convert single words to a className", () => {
const className = markdown.scopesToString(["source"])
assert.ok(className === "source")
})
it("Scopes to string Should return null if passed an falsy", () => {
const nullArg = markdown.scopesToString(null)
assert.ok(!nullArg)

View File

@ -27,10 +27,9 @@ describe("SyntaxHighlightReconciler", () => {
{
scope: "scope.test",
settings: {
backgroundColor: COLOR_BLACK,
foregroundColor: COLOR_WHITE,
bold: true,
italic: true,
background: COLOR_BLACK,
foreground: COLOR_WHITE,
fontStyle: "bold italic",
},
},
])
@ -93,10 +92,9 @@ describe("SyntaxHighlightReconciler", () => {
tokenColor: {
scope: "scope.test",
settings: {
backgroundColor: COLOR_BLACK,
foregroundColor: COLOR_WHITE,
italic: true,
bold: true,
background: COLOR_BLACK,
foreground: COLOR_WHITE,
fontStyle: "bold italic",
},
},
},
@ -155,10 +153,9 @@ describe("SyntaxHighlightReconciler", () => {
tokenColor: {
scope: "scope.test",
settings: {
backgroundColor: COLOR_BLACK,
foregroundColor: COLOR_WHITE,
italic: true,
bold: true,
background: COLOR_BLACK,
foreground: COLOR_WHITE,
fontStyle: "bold italic",
},
},
},

View File

@ -16,7 +16,9 @@ describe("TokenColors", () => {
let themeManager: ThemeManager
beforeEach(() => {
mockConfiguration = new MockConfiguration()
mockConfiguration = new MockConfiguration({
"editor.tokenColors": [{ scope: "comment", fontStyle: "bold" }],
})
themeLoader = new MockThemeLoader()
themeManager = new ThemeManager(themeLoader)
themeLoader.addTheme("testTheme", DefaultTheme)

View File

@ -12,10 +12,9 @@ import { MockNeovimInstance } from "./../Mocks/neovim"
const createTokenColor = (scope: string): TokenColor => ({
scope,
settings: {
foregroundColor: "#FFFFFF",
backgroundColor: "#FF0000",
bold: false,
italic: false,
foreground: "#FFFFFF",
background: "#FF0000",
fontStyle: null,
},
})

View File

@ -45,6 +45,387 @@
"highlight.mode.operator.foreground": "#282c34",
"highlight.mode.visual.background": "#56b6c2",
"highlight.mode.visual.foreground": "#282c34"
"highlight.mode.visual.foreground": "#282c34",
"editor.tokenColors": [
{
"settings": {
"background": "#282c34",
"foreground": "#abb2bf",
"findHighlight": "#FFE792",
"findHighlightForeground": "#000000",
"selectionBorder": "#222218",
"activeGuide": "#9D550FB0",
"bracketsForeground": "#F8F8F2A5",
"bracketsOptions": "underline",
"bracketContentsForeground": "#F8F8F2A5",
"bracketContentsOptions": "underline",
"tagsOptions": "stippled_underline"
}
},
{
"name": "Comment",
"scope": "comment",
"settings": {
"foreground": "#5c6370",
"fontStyle": "italic"
}
},
{
"name": "Comment",
"scope": "html.doctype",
"settings": {
"foreground": "#5c6370",
"fontStyle": "italic"
}
},
{
"name": "String",
"scope": "string",
"settings": {
"foreground": "#98c379"
}
},
{
"name": "Embeded String Begin and End",
"scope": ["string.embedded.begin", "string.embedded.end"],
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "Embeded String",
"scope": "string.embedded",
"settings": {
"foreground": "#56b6c2"
}
},
{
"name": "Number",
"scope": "constant.numeric",
"settings": {
"foreground": "#d19a66"
}
},
{
"name": "Built-in constant",
"scope": "constant.language",
"settings": {
"foreground": "#d19a66"
}
},
{
"name": "User-defined constant",
"scope": ["constant.character", "constant.other"],
"settings": {
"foreground": "#d19a66"
}
},
{
"name": "Language Variable",
"scope": "variable.language",
"settings": {
"foreground": "#c678dd"
}
},
{
"name": "Variable",
"scope": ["variable.readwrite", "variable.block", "variable.support"],
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "Variable",
"scope": "variable.readwrite.var-single-variable.js",
"settings": {
"foreground": "#d19a66"
}
},
{
"name": "Variable",
"scope": "variable.readwrite.js",
"settings": {
"foreground": "#abb2bf"
}
},
{
"name": "Keyword",
"scope": [
"keyword",
"keyword.operator.logical",
"keyword.operator.constructor",
"keyword.operator.new"
],
"settings": {
"foreground": "#c678dd"
}
},
{
"name": "Keyword Operator",
"scope": "keyword.operator",
"settings": {
"foreground": "#61afef"
}
},
{
"name": "Storage",
"scope": "storage",
"settings": {
"fontStyle": "",
"foreground": "#c678dd"
}
},
{
"name": "Storage type",
"scope": "storage.type.function",
"settings": {
"foreground": "#c678dd"
}
},
{
"name": "Class name",
"scope": ["entity.name.class", "entity.name.module", "entity.name.type"],
"settings": {
"foreground": "#e5c07b"
}
},
{
"name": "Inherited class",
"scope": "entity.other.inherited-class",
"settings": {
"foreground": "#98c379"
}
},
{
"name": "Tag name",
"scope": "entity.name.tag",
"settings": {
"fontStyle": "",
"foreground": "#e06c75"
}
},
{
"name": "Tag attribute",
"scope": "entity.other.attribute-name",
"settings": {
"fontStyle": "",
"foreground": "#d19a66"
}
},
{
"name": "Function name",
"scope": "entity.name.function",
"settings": {
"fontStyle": "",
"foreground": "#61afef"
}
},
{
"name": "Function argument",
"scope": "variable.parameter",
"settings": {
"fontStyle": "italic",
"foreground": "#e06c75"
}
},
{
"name": "Function call",
"scope": "entity.name.function-call",
"settings": {
"fontStyle": "",
"foreground": "#abb2bf"
}
},
{
"name": "Builtin Functions",
"scope": ["function.support.builtin", "function.support.core"],
"settings": {
"fontStyle": "",
"foreground": "#56b6c2"
}
},
{
"name": "Library function",
"scope": "support.function",
"settings": {
"foreground": "#56b6c2"
}
},
{
"name": "Library constant",
"scope": "support.constant",
"settings": {
"fontStyle": "",
"foreground": "#e5c07b"
}
},
{
"name": "Library class/type",
"scope": "support.class",
"settings": {
"foreground": "#e5c07b"
}
},
{
"name": "Library type",
"scope": "support.type",
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "Json Property",
"scope": "support.dictionary.json",
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "StyleSheet Property name",
"scope": [
"support.type.property-name.css",
"support.type.property-name.scss",
"support.type.property-name.less",
"support.type.property-name.sass"
],
"settings": {
"foreground": "#abb2bf"
}
},
{
"name": "StyleSheet Property value",
"scope": [
"support.constant.css",
"support.constant.scss",
"support.constant.less",
"support.constant.sass"
],
"settings": {
"foreground": "#56b6c2"
}
},
{
"name": "StyleSheet Variable",
"scope": ["variable.css", "variable.scss", "variable.less", "variable.sass"],
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "StyleSheet Variable String",
"scope": [
"variable.css.string",
"variable.scss.string",
"variable.less.string",
"variable.sass.string"
],
"settings": {
"foreground": "#98c379"
}
},
{
"name": "StyleSheet Unit",
"scope": ["unit.css", "unit.scss", "unit.less", "unit.sass"],
"settings": {
"foreground": "#d19a66"
}
},
{
"name": "StyleSheet Function",
"scope": ["function.css", "function.scss", "function.less", "function.sass"],
"settings": {
"foreground": "#56b6c2"
}
},
{
"name": "Other variable",
"scope": [
"variable.other.property",
"variable.other.object",
"variable.other.block"
],
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "Pug/Jade Import",
"scope": ["jade.import.variable", "pug.import.variable"],
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "Invalid",
"scope": "invalid",
"settings": {
"background": "#c678dd",
"fontStyle": "",
"foreground": "#F8F8F0"
}
},
{
"name": "Invalid deprecated",
"scope": "invalid.deprecated",
"settings": {
"background": "#56b6c2",
"foreground": "#F8F8F0"
}
},
{
"name": "JSON String",
"scope": "structure.dictionary.property-name.json",
"settings": {
"foreground": "#e06c75"
}
},
{
"name": "Link",
"scope": "string.detected-link",
"settings": {
"foreground": "#c678dd"
}
},
{
"name": "diff.header",
"scope": ["meta.diff", "meta.diff.header"],
"settings": {
"foreground": "#75715E"
}
},
{
"name": "diff.deleted",
"scope": "markup.deleted",
"settings": {
"foreground": "#c678dd"
}
},
{
"name": "diff.inserted",
"scope": "markup.inserted",
"settings": {
"foreground": "#e5c07b"
}
},
{
"name": "diff.changed",
"scope": "markup.changed",
"settings": {
"foreground": "#98c379"
}
},
{
"scope": "constant.numeric.line-number.find-in-files - match",
"settings": {
"foreground": "#56b6c2A0"
}
},
{
"scope": "entity.name.filename.find-in-files",
"settings": {
"foreground": "#98c379"
}
}
]
}
}

View File

@ -900,7 +900,7 @@
"@types/enzyme": "^3.1.8",
"@types/fs-extra": "^5.0.2",
"@types/highlight.js": "^9.12.2",
"@types/jest": "^22.1.3",
"@types/jest": "^23.3.1",
"@types/jsdom": "11.0.0",
"@types/json5": "^0.0.29",
"@types/lodash": "4.14.38",
@ -956,7 +956,8 @@
"innosetup-compiler": "5.5.9",
"istanbul-api": "^1.2.1",
"istanbul-lib-coverage": "^1.1.1",
"jest": "^23.2.0",
"jest": "^23.5.0",
"jest-styled-components": "^6.1.1",
"jsdom": "11.0.0",
"less": "2.7.1",
"less-loader": "^4.1.0",

View File

@ -45,7 +45,7 @@ export const settings = {
{
scope: "variable.other",
settings: {
foregroundColor: "#00FF00",
foreground: "#00FF00",
},
},
],

View File

@ -0,0 +1,106 @@
import { TokenScorer } from "./../browser/src/Services/SyntaxHighlighting/TokenScorer"
import { TokenColor } from "./../browser/src/Services/TokenColors"
describe("TokenScorer Tests", () => {
const ranker = new TokenScorer()
const theme: TokenColor[] = [
{
scope: "entity",
settings: {
foreground: "blue",
background: "red",
fontStyle: "italic",
},
},
{
scope: "entity.name",
settings: {
foreground: "blue",
background: "red",
fontStyle: "italic",
},
},
{
scope: "entity.name.interface",
settings: {
foreground: "blue",
background: "red",
fontStyle: "italic",
},
},
{
scope: "entity.name.interface.golang",
settings: {
foreground: "blue",
background: "red",
fontStyle: "italic",
},
},
]
it("should correctly rank tokens based on how deep in the syntax tree they are", () => {
const scopes = ["entity.name", "entity.name.interface", "entity.name.interface.golang"]
const highest = ranker.rankTokenScopes(scopes, theme)
expect(highest).toEqual({
scope: "entity.name.interface.golang",
settings: {
foreground: "blue",
background: "red",
fontStyle: "italic",
},
})
})
it("should only return a token that is included in the theme aka one that can be highlighted", () => {
const scopes = [
"entity.name",
"entity.name.interface",
"entity.name.interface.golang",
"item.class.component.element.object",
]
const highest = ranker.rankTokenScopes(scopes, theme)
expect(highest.scope).toBe("entity.name.interface.golang")
})
it('should correctly prioritise fields with higher scope priorities like "support" ', () => {
const scopes = [
"entity.name",
"entity.name.interface",
"entity.name.interface.golang",
"support.class.component.element",
]
const supportTheme: TokenColor[] = [
...theme,
{
scope: "support.class.component.element",
settings: {
foreground: "blue",
background: "red",
fontStyle: "italic",
},
},
]
const highest = ranker.rankTokenScopes(scopes, supportTheme)
expect(highest).toEqual({
scope: "support.class.component.element",
settings: {
foreground: "blue",
background: "red",
fontStyle: "italic",
},
})
})
it("should return null if none of the scopes it is passed have any matches", () => {
const highest = ranker.rankTokenScopes(["testing.failure"], theme)
expect(highest).toBeFalsy()
})
it("should ignore banned scopes like meta", () => {
const highest = ranker.rankTokenScopes(
["meta.long.token.name", "source.other.long.name"],
theme,
)
expect(highest).toBeFalsy()
})
})

View File

@ -0,0 +1,74 @@
import * as React from "react"
import { mount } from "enzyme"
import "jest-styled-components"
import TokenThemeProvider from "./../browser/src/Services/SyntaxHighlighting/TokenThemeProvider"
import { TokenColor } from "./../browser/src/Services/TokenColors"
import styled, { css } from "./../browser/src/UI/components/common"
const tokenColors: TokenColor[] = [
{
scope: "string.quoted",
settings: {
foreground: "green",
background: "blue",
fontStyle: "bold italic",
},
},
{
scope: "entity.name.struct",
settings: {
foreground: "rebeccapurple",
background: "orange",
fontStyle: "italic",
},
},
]
const TestComponent = styled<{ tokenStyles: any }, "div">("div")`
${p => p.tokenStyles};
`
describe("<TokenThemeProvider />", () => {
const theme = {
"editor.background": "black",
"editor.foreground": "white",
"menu.background": "green",
"menu.foreground": "grey",
}
const component = (
<TokenThemeProvider
theme={theme}
tokenColors={tokenColors}
render={props => (
<TestComponent tokenStyles={props.styles}>
<span className="string-quoted">test text</span>
</TestComponent>
)}
/>
)
it("should render without crashing", () => {
const wrapper = mount(component)
expect(wrapper.length).toBe(1)
})
test.each`
className | cssRule | result
${".string-quoted"} | ${"color"} | ${"green"}
${".string-quoted"} | ${"font-style"} | ${"italic"}
${".string-quoted"} | ${"font-weight"}| ${"bold"}
${".entity-name-struct"} | ${"color"} | ${"rebeccapurple"}
${".entity-name-struct"} | ${"font-style"} | ${"italic"}
${".entity-name-struct"} | ${"font-weight"}| ${undefined}
`(
"the TokenThemeProvider returns a nested class rule with a style of $cssRule with a value of $result for $className",
({ cssRule, result, className }) => {
const wrapper = mount(component)
expect(wrapper.find(TestComponent)).toHaveStyleRule(cssRule, result, {
modifier: className,
})
},
)
})

386
yarn.lock
View File

@ -120,9 +120,9 @@
version "9.12.3"
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.3.tgz#b672cfaac25cbbc634a0fd92c515f66faa18dbca"
"@types/jest@^22.1.3":
version "22.1.3"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.1.3.tgz#25da391935e6fac537551456f077ce03144ec168"
"@types/jest@^23.3.1":
version "23.3.1"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf"
"@types/jsdom@11.0.0":
version "11.0.0"
@ -751,6 +751,10 @@ atob@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.0.tgz#ab2b150e51d7b122b9efc8d7340c06b6c41076bc"
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
atob@~1.1.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773"
@ -1018,6 +1022,13 @@ babel-jest@^23.2.0:
babel-plugin-istanbul "^4.1.6"
babel-preset-jest "^23.2.0"
babel-jest@^23.4.2:
version "23.4.2"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.4.2.tgz#f276de67798a5d68f2d6e87ff518c2f6e1609877"
dependencies:
babel-plugin-istanbul "^4.1.6"
babel-preset-jest "^23.2.0"
babel-messages@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
@ -2911,6 +2922,15 @@ css@^2.0.0:
source-map-resolve "^0.3.0"
urix "^0.1.0"
css@^2.2.1:
version "2.2.4"
resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
dependencies:
inherits "^2.0.3"
source-map "^0.6.1"
source-map-resolve "^0.5.2"
urix "^0.1.0"
cssesc@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
@ -3925,17 +3945,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
expect@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/expect/-/expect-23.2.0.tgz#53a7e135e36fe27e75867b1178ff08aaacc2b0dd"
dependencies:
ansi-styles "^3.2.0"
jest-diff "^23.2.0"
jest-get-type "^22.1.0"
jest-matcher-utils "^23.2.0"
jest-message-util "^23.2.0"
jest-regex-util "^23.0.0"
expect@^23.3.0:
version "23.3.0"
resolved "https://registry.yarnpkg.com/expect/-/expect-23.3.0.tgz#ecb051adcbdc40ac4db576c16067f12fdb13cc61"
@ -3947,6 +3956,17 @@ expect@^23.3.0:
jest-message-util "^23.3.0"
jest-regex-util "^23.3.0"
expect@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/expect/-/expect-23.5.0.tgz#18999a0eef8f8acf99023fde766d9c323c2562ed"
dependencies:
ansi-styles "^3.2.0"
jest-diff "^23.5.0"
jest-get-type "^22.1.0"
jest-matcher-utils "^23.5.0"
jest-message-util "^23.4.0"
jest-regex-util "^23.3.0"
express@^4.16.2:
version "4.16.3"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
@ -5215,6 +5235,12 @@ invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.2:
dependencies:
loose-envify "^1.0.0"
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
loose-envify "^1.0.0"
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
@ -5734,15 +5760,15 @@ isurl@^1.0.0-alpha5:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
jest-changed-files@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.2.0.tgz#a145a6e4b66d0129fc7c99cee134dc937a643d9c"
jest-changed-files@^23.4.2:
version "23.4.2"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83"
dependencies:
throat "^4.0.0"
jest-cli@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.2.0.tgz#3b543a3da5145dd8937931017282379fc696c45b"
jest-cli@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.5.0.tgz#d316b8e34a38a610a1efc4f0403d8ef8a55e4492"
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.1"
@ -5755,22 +5781,22 @@ jest-cli@^23.2.0:
istanbul-lib-coverage "^1.2.0"
istanbul-lib-instrument "^1.10.1"
istanbul-lib-source-maps "^1.2.4"
jest-changed-files "^23.2.0"
jest-config "^23.2.0"
jest-environment-jsdom "^23.2.0"
jest-changed-files "^23.4.2"
jest-config "^23.5.0"
jest-environment-jsdom "^23.4.0"
jest-get-type "^22.1.0"
jest-haste-map "^23.2.0"
jest-message-util "^23.2.0"
jest-regex-util "^23.0.0"
jest-resolve-dependencies "^23.2.0"
jest-runner "^23.2.0"
jest-runtime "^23.2.0"
jest-snapshot "^23.2.0"
jest-util "^23.2.0"
jest-validate "^23.2.0"
jest-watcher "^23.2.0"
jest-haste-map "^23.5.0"
jest-message-util "^23.4.0"
jest-regex-util "^23.3.0"
jest-resolve-dependencies "^23.5.0"
jest-runner "^23.5.0"
jest-runtime "^23.5.0"
jest-snapshot "^23.5.0"
jest-util "^23.4.0"
jest-validate "^23.5.0"
jest-watcher "^23.4.0"
jest-worker "^23.2.0"
micromatch "^3.1.10"
micromatch "^2.3.11"
node-notifier "^5.2.1"
prompts "^0.1.9"
realpath-native "^1.0.0"
@ -5799,23 +5825,24 @@ jest-config@^23.0.0:
jest-validate "^23.3.0"
pretty-format "^23.2.0"
jest-config@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.2.0.tgz#d2fb556fd5a2a19c39eb56d139dcca5dad2a1c88"
jest-config@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.5.0.tgz#3770fba03f7507ee15f3b8867c742e48f31a9773"
dependencies:
babel-core "^6.0.0"
babel-jest "^23.2.0"
babel-jest "^23.4.2"
chalk "^2.0.1"
glob "^7.1.1"
jest-environment-jsdom "^23.2.0"
jest-environment-node "^23.2.0"
jest-environment-jsdom "^23.4.0"
jest-environment-node "^23.4.0"
jest-get-type "^22.1.0"
jest-jasmine2 "^23.2.0"
jest-regex-util "^23.0.0"
jest-resolve "^23.2.0"
jest-util "^23.2.0"
jest-validate "^23.2.0"
pretty-format "^23.2.0"
jest-jasmine2 "^23.5.0"
jest-regex-util "^23.3.0"
jest-resolve "^23.5.0"
jest-util "^23.4.0"
jest-validate "^23.5.0"
micromatch "^2.3.11"
pretty-format "^23.5.0"
jest-diff@^23.2.0:
version "23.2.0"
@ -5826,6 +5853,15 @@ jest-diff@^23.2.0:
jest-get-type "^22.1.0"
pretty-format "^23.2.0"
jest-diff@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.5.0.tgz#250651a433dd0050290a07642946cc9baaf06fba"
dependencies:
chalk "^2.0.1"
diff "^3.2.0"
jest-get-type "^22.1.0"
pretty-format "^23.5.0"
jest-docblock@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7"
@ -5839,13 +5875,12 @@ jest-each@^23.2.0:
chalk "^2.0.1"
pretty-format "^23.2.0"
jest-environment-jsdom@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.2.0.tgz#3634603a08a975b0ca8a658320f56a54a8e04558"
jest-each@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.5.0.tgz#77f7e2afe6132a80954b920006e78239862b10ba"
dependencies:
jest-mock "^23.2.0"
jest-util "^23.2.0"
jsdom "^11.5.1"
chalk "^2.0.1"
pretty-format "^23.5.0"
jest-environment-jsdom@^23.3.0:
version "23.3.0"
@ -5855,12 +5890,13 @@ jest-environment-jsdom@^23.3.0:
jest-util "^23.3.0"
jsdom "^11.5.1"
jest-environment-node@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.2.0.tgz#b6fe41372e382093bb6f3d9bdf6c1c4ec0a50f18"
jest-environment-jsdom@^23.4.0:
version "23.4.0"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023"
dependencies:
jest-mock "^23.2.0"
jest-util "^23.2.0"
jest-util "^23.4.0"
jsdom "^11.5.1"
jest-environment-node@^23.3.0:
version "23.3.0"
@ -5869,38 +5905,30 @@ jest-environment-node@^23.3.0:
jest-mock "^23.2.0"
jest-util "^23.3.0"
jest-environment-node@^23.4.0:
version "23.4.0"
resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10"
dependencies:
jest-mock "^23.2.0"
jest-util "^23.4.0"
jest-get-type@^22.1.0:
version "22.1.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.1.0.tgz#4e90af298ed6181edc85d2da500dbd2753e0d5a9"
jest-haste-map@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.2.0.tgz#d10cbac007c695948c8ef1821a2b2ed2d4f2d4d8"
jest-haste-map@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.5.0.tgz#d4ca618188bd38caa6cb20349ce6610e194a8065"
dependencies:
fb-watchman "^2.0.0"
graceful-fs "^4.1.11"
invariant "^2.2.4"
jest-docblock "^23.2.0"
jest-serializer "^23.0.1"
jest-worker "^23.2.0"
micromatch "^3.1.10"
micromatch "^2.3.11"
sane "^2.0.0"
jest-jasmine2@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.2.0.tgz#aa670cdb1e4d5f8ec774c94dda5e105fe33d8bb4"
dependencies:
chalk "^2.0.1"
co "^4.6.0"
expect "^23.2.0"
is-generator-fn "^1.0.0"
jest-diff "^23.2.0"
jest-each "^23.2.0"
jest-matcher-utils "^23.2.0"
jest-message-util "^23.2.0"
jest-snapshot "^23.2.0"
jest-util "^23.2.0"
pretty-format "^23.2.0"
jest-jasmine2@^23.3.0:
version "23.3.0"
resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.3.0.tgz#a8706baac23c8a130d5aa8ef5464a9d49096d1b5"
@ -5917,11 +5945,28 @@ jest-jasmine2@^23.3.0:
jest-util "^23.3.0"
pretty-format "^23.2.0"
jest-leak-detector@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.2.0.tgz#c289d961dc638f14357d4ef96e0431ecc1aa377d"
jest-jasmine2@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.5.0.tgz#05fe7f1788e650eeb5a03929e6461ea2e9f3db53"
dependencies:
pretty-format "^23.2.0"
babel-traverse "^6.0.0"
chalk "^2.0.1"
co "^4.6.0"
expect "^23.5.0"
is-generator-fn "^1.0.0"
jest-diff "^23.5.0"
jest-each "^23.5.0"
jest-matcher-utils "^23.5.0"
jest-message-util "^23.4.0"
jest-snapshot "^23.5.0"
jest-util "^23.4.0"
pretty-format "^23.5.0"
jest-leak-detector@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.5.0.tgz#14ac2a785bd625160a2ea968fd5d98b7dcea3e64"
dependencies:
pretty-format "^23.5.0"
jest-matcher-utils@^23.2.0:
version "23.2.0"
@ -5931,15 +5976,13 @@ jest-matcher-utils@^23.2.0:
jest-get-type "^22.1.0"
pretty-format "^23.2.0"
jest-message-util@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.2.0.tgz#591e8148fff69cf89b0414809c721756ebefe744"
jest-matcher-utils@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.5.0.tgz#0e2ea67744cab78c9ab15011c4d888bdd3e49e2a"
dependencies:
"@babel/code-frame" "^7.0.0-beta.35"
chalk "^2.0.1"
micromatch "^3.1.10"
slash "^1.0.0"
stack-utils "^1.0.1"
jest-get-type "^22.1.0"
pretty-format "^23.5.0"
jest-message-util@^23.3.0:
version "23.3.0"
@ -5951,24 +5994,30 @@ jest-message-util@^23.3.0:
slash "^1.0.0"
stack-utils "^1.0.1"
jest-message-util@^23.4.0:
version "23.4.0"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f"
dependencies:
"@babel/code-frame" "^7.0.0-beta.35"
chalk "^2.0.1"
micromatch "^2.3.11"
slash "^1.0.0"
stack-utils "^1.0.1"
jest-mock@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134"
jest-regex-util@^23.0.0:
version "23.0.0"
resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.0.0.tgz#dd5c1fde0c46f4371314cf10f7a751a23f4e8f76"
jest-regex-util@^23.3.0:
version "23.3.0"
resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5"
jest-resolve-dependencies@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.2.0.tgz#6df8d5709c6406639cd07f54bff074e01b5c0458"
jest-resolve-dependencies@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.5.0.tgz#10c4d135beb9d2256de1fedc7094916c3ad74af7"
dependencies:
jest-regex-util "^23.0.0"
jest-snapshot "^23.2.0"
jest-regex-util "^23.3.0"
jest-snapshot "^23.5.0"
jest-resolve@^23.2.0:
version "23.2.0"
@ -5978,27 +6027,35 @@ jest-resolve@^23.2.0:
chalk "^2.0.1"
realpath-native "^1.0.0"
jest-runner@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.2.0.tgz#0d91967ea82f72b0c705910926086d2055ce75af"
jest-resolve@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.5.0.tgz#3b8e7f67e84598f0caf63d1530bd8534a189d0e6"
dependencies:
browser-resolve "^1.11.3"
chalk "^2.0.1"
realpath-native "^1.0.0"
jest-runner@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.5.0.tgz#570f7a044da91648b5bb9b6baacdd511076c71d7"
dependencies:
exit "^0.1.2"
graceful-fs "^4.1.11"
jest-config "^23.2.0"
jest-config "^23.5.0"
jest-docblock "^23.2.0"
jest-haste-map "^23.2.0"
jest-jasmine2 "^23.2.0"
jest-leak-detector "^23.2.0"
jest-message-util "^23.2.0"
jest-runtime "^23.2.0"
jest-util "^23.2.0"
jest-haste-map "^23.5.0"
jest-jasmine2 "^23.5.0"
jest-leak-detector "^23.5.0"
jest-message-util "^23.4.0"
jest-runtime "^23.5.0"
jest-util "^23.4.0"
jest-worker "^23.2.0"
source-map-support "^0.5.6"
throat "^4.0.0"
jest-runtime@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.2.0.tgz#62dcb01766a1c4c64696dc090209e76ce1aadcbc"
jest-runtime@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.5.0.tgz#eb503525a196dc32f2f9974e3482d26bdf7b63ce"
dependencies:
babel-core "^6.0.0"
babel-plugin-istanbul "^4.1.6"
@ -6007,15 +6064,15 @@ jest-runtime@^23.2.0:
exit "^0.1.2"
fast-json-stable-stringify "^2.0.0"
graceful-fs "^4.1.11"
jest-config "^23.2.0"
jest-haste-map "^23.2.0"
jest-message-util "^23.2.0"
jest-regex-util "^23.0.0"
jest-resolve "^23.2.0"
jest-snapshot "^23.2.0"
jest-util "^23.2.0"
jest-validate "^23.2.0"
micromatch "^3.1.10"
jest-config "^23.5.0"
jest-haste-map "^23.5.0"
jest-message-util "^23.4.0"
jest-regex-util "^23.3.0"
jest-resolve "^23.5.0"
jest-snapshot "^23.5.0"
jest-util "^23.4.0"
jest-validate "^23.5.0"
micromatch "^2.3.11"
realpath-native "^1.0.0"
slash "^1.0.0"
strip-bom "3.0.0"
@ -6026,17 +6083,6 @@ jest-serializer@^23.0.1:
version "23.0.1"
resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165"
jest-snapshot@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.2.0.tgz#c7a3d017177bbad60c8a595869cf90a8782e6a7e"
dependencies:
chalk "^2.0.1"
jest-diff "^23.2.0"
jest-matcher-utils "^23.2.0"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
pretty-format "^23.2.0"
jest-snapshot@^23.3.0:
version "23.3.0"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.3.0.tgz#fc4e9f81e45432d10507e27f50bce60f44d81424"
@ -6053,18 +6099,26 @@ jest-snapshot@^23.3.0:
pretty-format "^23.2.0"
semver "^5.5.0"
jest-util@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.2.0.tgz#62b770757696d96e094a04b8f1c373ca50a5ab2e"
jest-snapshot@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.5.0.tgz#cc368ebd8513e1175e2a7277f37a801b7358ae79"
dependencies:
callsites "^2.0.0"
babel-types "^6.0.0"
chalk "^2.0.1"
graceful-fs "^4.1.11"
is-ci "^1.0.10"
jest-message-util "^23.2.0"
jest-diff "^23.5.0"
jest-matcher-utils "^23.5.0"
jest-message-util "^23.4.0"
jest-resolve "^23.5.0"
mkdirp "^0.5.1"
slash "^1.0.0"
source-map "^0.6.0"
natural-compare "^1.4.0"
pretty-format "^23.5.0"
semver "^5.5.0"
jest-styled-components@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-6.1.1.tgz#2b9b6e3ded212b43ea71df72e82d55b856e9d1ed"
dependencies:
css "^2.2.1"
jest-util@^23.3.0:
version "23.3.0"
@ -6079,14 +6133,18 @@ jest-util@^23.3.0:
slash "^1.0.0"
source-map "^0.6.0"
jest-validate@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.2.0.tgz#67c8b909e11af1701765238894c67ac3291b195e"
jest-util@^23.4.0:
version "23.4.0"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561"
dependencies:
callsites "^2.0.0"
chalk "^2.0.1"
jest-get-type "^22.1.0"
leven "^2.1.0"
pretty-format "^23.2.0"
graceful-fs "^4.1.11"
is-ci "^1.0.10"
jest-message-util "^23.4.0"
mkdirp "^0.5.1"
slash "^1.0.0"
source-map "^0.6.0"
jest-validate@^23.3.0:
version "23.3.0"
@ -6097,9 +6155,18 @@ jest-validate@^23.3.0:
leven "^2.1.0"
pretty-format "^23.2.0"
jest-watcher@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.2.0.tgz#678e852896e919e9d9a0eb4b8baf1ae279620ea9"
jest-validate@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.5.0.tgz#f5df8f761cf43155e1b2e21d6e9de8a2852d0231"
dependencies:
chalk "^2.0.1"
jest-get-type "^22.1.0"
leven "^2.1.0"
pretty-format "^23.5.0"
jest-watcher@^23.4.0:
version "23.4.0"
resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c"
dependencies:
ansi-escapes "^3.0.0"
chalk "^2.0.1"
@ -6111,12 +6178,12 @@ jest-worker@^23.2.0:
dependencies:
merge-stream "^1.0.1"
jest@^23.2.0:
version "23.2.0"
resolved "https://registry.yarnpkg.com/jest/-/jest-23.2.0.tgz#828bf31a096d45dcf06824d1ea03013af7bcfc20"
jest@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/jest/-/jest-23.5.0.tgz#80de353d156ea5ea4a7332f7962ac79135fbc62e"
dependencies:
import-local "^1.0.0"
jest-cli "^23.2.0"
jest-cli "^23.5.0"
jmespath@0.15.0:
version "0.15.0"
@ -8327,6 +8394,13 @@ pretty-format@^23.2.0:
ansi-regex "^3.0.0"
ansi-styles "^3.2.0"
pretty-format@^23.5.0:
version "23.5.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.5.0.tgz#0f9601ad9da70fe690a269cd3efca732c210687c"
dependencies:
ansi-regex "^3.0.0"
ansi-styles "^3.2.0"
pretty-quick@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-1.5.0.tgz#304853ece7f8cb56bec74ba3ccd978037e7f2117"
@ -9649,6 +9723,16 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-resolve@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
dependencies:
atob "^2.1.1"
decode-uri-component "^0.2.0"
resolve-url "^0.2.1"
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.4.15:
version "0.4.18"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"