mirror of https://github.com/onivim/oni.git
Feature/reapply syntax highlighting onColorscheme autocmd (#2565)
* initial effort to get syntax highlighting to reapply on colorscheme reload * remove lint check for whitespace inside template literals * neaten reducer reset tidy up comment in bufferhighlight so it appears in documentation dropdown * Refactor token colors to pass token scopes as an array of strings update affected tests * fix imports in token colors * pass in only visible tokens to the neovim token synchronizer * fix lint errors * fix another lint error * revert token destructuring in reconciler * notify redraw when tokencolors change in neovim editor * update redux * [wip] get token subset in synchronizeTokenColors * make cache access more readable * remove subset functionality from token synchronizer * convert highlight cache to map so it can be cleared and reused easily * do not split tokens on space char as this changes the way tokens are identified * revert package json and yarn lock * move private fields to avoid lint errors * add onColorsChanged to neovim instance mock * fix comments for token formatter function in token colors
This commit is contained in:
parent
27cef2a0dc
commit
65d0e20455
|
@ -16,8 +16,13 @@ export interface IBufferHighlightsUpdater {
|
|||
clearHighlightsForLine(line: number): void
|
||||
}
|
||||
|
||||
// Helper class to efficiently update
|
||||
// buffer highlights in a batch.
|
||||
/**
|
||||
* Helper class to efficiently update
|
||||
* buffer highlights in a batch
|
||||
*
|
||||
* @name BufferHighlightsUpdater
|
||||
* @class
|
||||
*/
|
||||
export class BufferHighlightsUpdater implements IBufferHighlightsUpdater {
|
||||
private _calls: any[] = []
|
||||
|
||||
|
|
|
@ -315,19 +315,15 @@ export class NeovimEditor extends Editor implements Oni.Editor {
|
|||
this._actions.setColors(updatedColors)
|
||||
}
|
||||
|
||||
this._colors.onColorsChanged.subscribe(() => onColorsChanged())
|
||||
this._colors.onColorsChanged.subscribe(onColorsChanged)
|
||||
onColorsChanged()
|
||||
|
||||
const onTokenColorschanged = () => {
|
||||
if (this._neovimInstance.isInitialized) {
|
||||
this._neovimInstance.tokenColorSynchronizer.synchronizeTokenColors(
|
||||
this._tokenColors.tokenColors,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.trackDisposable(
|
||||
this._tokenColors.onTokenColorsChanged.subscribe(() => onTokenColorschanged()),
|
||||
this._tokenColors.onTokenColorsChanged.subscribe(() => {
|
||||
if (this._neovimInstance.isInitialized) {
|
||||
this._syntaxHighlighter.notifyColorschemeRedraw(`${this.activeBuffer.id}`)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
// Overlays
|
||||
|
@ -1292,6 +1288,8 @@ export class NeovimEditor extends Editor implements Oni.Editor {
|
|||
|
||||
private async _onColorsChanged(): Promise<void> {
|
||||
const newColorScheme = await this._neovimInstance.eval<string>("g:colors_name")
|
||||
const { bufferNumber } = await this._neovimInstance.getContext()
|
||||
this._syntaxHighlighter.notifyColorschemeRedraw(`${bufferNumber}`)
|
||||
|
||||
// In error cases, the neovim API layer returns an array
|
||||
if (typeof newColorScheme !== "string") {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ISyntaxHighlightTokenInfo } from "./SyntaxHighlightingStore"
|
|||
export interface ISyntaxHighlighter extends IDisposable {
|
||||
notifyBufferUpdate(evt: Oni.EditorBufferChangedEventArgs): Promise<void>
|
||||
notifyViewportChanged(bufferId: string, topLineInView: number, bottomLineInView: number): void
|
||||
notifyColorschemeRedraw(id: string): void
|
||||
|
||||
getHighlightTokenAt(bufferId: string, position: types.Position): ISyntaxHighlightTokenInfo
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* Handles enhanced syntax highlighting
|
||||
*/
|
||||
|
||||
import { Buffer, Editor } from "oni-api"
|
||||
import * as Log from "oni-core-logging"
|
||||
|
||||
import { prettyPrint } from "./../../Utility"
|
||||
import { TokenColor, TokenColors } from "./../TokenColors"
|
||||
|
||||
import { NeovimEditor } from "./../../Editor/NeovimEditor"
|
||||
|
||||
import { HighlightInfo } from "./Definitions"
|
||||
import {
|
||||
ISyntaxHighlightLineInfo,
|
||||
|
@ -18,21 +18,37 @@ import {
|
|||
} from "./SyntaxHighlightingStore"
|
||||
import { TokenScorer } from "./TokenScorer"
|
||||
|
||||
import { IBufferHighlightsUpdater } from "../../Editor/BufferHighlights"
|
||||
import * as Selectors from "./SyntaxHighlightSelectors"
|
||||
|
||||
// SyntaxHighlightReconciler
|
||||
//
|
||||
// Essentially a renderer / reconciler, that will push
|
||||
// highlight calls to the active buffer based on the active
|
||||
// window and viewport
|
||||
interface IBufferWithSyntaxHighlighter extends Buffer {
|
||||
updateHighlights?: (
|
||||
tokenColors: TokenColor[],
|
||||
highlightCallback: (args: IBufferHighlightsUpdater) => void,
|
||||
) => void
|
||||
}
|
||||
|
||||
export interface IEditorWithSyntaxHighlighter extends Editor {
|
||||
activeBuffer: IBufferWithSyntaxHighlighter
|
||||
}
|
||||
|
||||
/**
|
||||
* SyntaxHighlightReconciler
|
||||
*
|
||||
* Essentially a renderer / reconciler, that will push
|
||||
* highlight calls to the active buffer based on the active
|
||||
* window and viewport
|
||||
* @name SyntaxHighlightReconciler
|
||||
* @class
|
||||
*/
|
||||
export class SyntaxHighlightReconciler {
|
||||
private _previousState: { [line: number]: ISyntaxHighlightLineInfo } = {}
|
||||
private _tokenScorer = new TokenScorer()
|
||||
|
||||
constructor(private _editor: NeovimEditor, private _tokenColors: TokenColors) {}
|
||||
constructor(private _editor: IEditorWithSyntaxHighlighter, private _tokenColors: TokenColors) {}
|
||||
|
||||
public update(state: ISyntaxHighlightState) {
|
||||
const activeBuffer: any = this._editor.activeBuffer
|
||||
const { activeBuffer } = this._editor
|
||||
|
||||
if (!activeBuffer) {
|
||||
return
|
||||
|
@ -77,6 +93,15 @@ export class SyntaxHighlightReconciler {
|
|||
}
|
||||
})
|
||||
|
||||
// Get only the token colors that apply to the visible section of the buffer
|
||||
const visibleTokens = tokens.reduce((accumulator, { highlights }) => {
|
||||
if (highlights) {
|
||||
const tokenColors = highlights.map(({ tokenColor }) => tokenColor)
|
||||
accumulator.push(...tokenColors)
|
||||
}
|
||||
return accumulator
|
||||
}, [])
|
||||
|
||||
filteredLines.forEach(line => {
|
||||
const lineNumber = parseInt(line, 10)
|
||||
this._previousState[line] = Selectors.getLineFromBuffer(
|
||||
|
@ -87,26 +112,22 @@ export class SyntaxHighlightReconciler {
|
|||
|
||||
if (tokens.length) {
|
||||
Log.verbose(
|
||||
"[SyntaxHighlightReconciler] Applying changes to " + tokens.length + " lines.",
|
||||
`[SyntaxHighlightReconciler] Applying changes to ${tokens.length} lines.`,
|
||||
)
|
||||
activeBuffer.updateHighlights(
|
||||
this._tokenColors.tokenColors,
|
||||
(highlightUpdater: any) => {
|
||||
tokens.forEach(token => {
|
||||
const { line, highlights } = token
|
||||
if (Log.isDebugLoggingEnabled()) {
|
||||
Log.debug(
|
||||
"[SyntaxHighlightingReconciler] Updating tokens for line: " +
|
||||
token.line +
|
||||
" | " +
|
||||
JSON.stringify(highlights),
|
||||
)
|
||||
}
|
||||
activeBuffer.updateHighlights(visibleTokens, highlightUpdater => {
|
||||
tokens.forEach(token => {
|
||||
const { line, highlights } = token
|
||||
if (Log.isDebugLoggingEnabled()) {
|
||||
Log.debug(
|
||||
`[SyntaxHighlightingReconciler] Updating tokens for line: ${line} | ${prettyPrint(
|
||||
highlights,
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
|
||||
highlightUpdater.setHighlightsForLine(line, highlights)
|
||||
})
|
||||
},
|
||||
)
|
||||
highlightUpdater.setHighlightsForLine(line, highlights)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,8 +142,10 @@ export class SyntaxHighlightReconciler {
|
|||
}
|
||||
|
||||
private _getHighlightGroupFromScope(scopes: string[]): TokenColor {
|
||||
const configurationColors = this._tokenColors.tokenColors
|
||||
const highestRanked = this._tokenScorer.rankTokenScopes(scopes, configurationColors)
|
||||
const highestRanked = this._tokenScorer.rankTokenScopes(
|
||||
scopes,
|
||||
this._tokenColors.tokenColors,
|
||||
)
|
||||
return highestRanked
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,10 @@ import {
|
|||
} from "./SyntaxHighlightingStore"
|
||||
|
||||
import { ISyntaxHighlighter } from "./ISyntaxHighlighter"
|
||||
import { SyntaxHighlightReconciler } from "./SyntaxHighlightReconciler"
|
||||
import {
|
||||
IEditorWithSyntaxHighlighter,
|
||||
SyntaxHighlightReconciler,
|
||||
} from "./SyntaxHighlightReconciler"
|
||||
import { getLineFromBuffer } from "./SyntaxHighlightSelectors"
|
||||
|
||||
import * as Utility from "./../../Utility"
|
||||
|
@ -45,7 +48,10 @@ export class SyntaxHighlighter implements ISyntaxHighlighter {
|
|||
constructor(private _editor: NeovimEditor, private _tokenColors: TokenColors) {
|
||||
this._store = createSyntaxHighlightStore()
|
||||
|
||||
this._reconciler = new SyntaxHighlightReconciler(this._editor, this._tokenColors)
|
||||
this._reconciler = new SyntaxHighlightReconciler(
|
||||
this._editor as IEditorWithSyntaxHighlighter,
|
||||
this._tokenColors,
|
||||
)
|
||||
this._unsubscribe = this._store.subscribe(() => {
|
||||
const state = this._store.getState()
|
||||
this._reconciler.update(state)
|
||||
|
@ -62,12 +68,10 @@ export class SyntaxHighlighter implements ISyntaxHighlighter {
|
|||
bottomLineInView: number,
|
||||
): void {
|
||||
Log.verbose(
|
||||
"[SyntaxHighlighting.notifyViewportChanged] - bufferId: " +
|
||||
bufferId +
|
||||
" topLineInView: " +
|
||||
topLineInView +
|
||||
" bottomLineInView: " +
|
||||
bottomLineInView,
|
||||
`[SyntaxHighlighting.notifyViewportChanged] -
|
||||
bufferId: ${bufferId}
|
||||
topLineInView: ${topLineInView}
|
||||
bottomLineInView: ${bottomLineInView}`,
|
||||
)
|
||||
|
||||
const state = this._store.getState()
|
||||
|
@ -89,6 +93,10 @@ export class SyntaxHighlighter implements ISyntaxHighlighter {
|
|||
})
|
||||
}
|
||||
|
||||
public async notifyColorschemeRedraw(bufferId: string) {
|
||||
this._store.dispatch({ type: "SYNTAX_RESET_BUFFER", bufferId })
|
||||
}
|
||||
|
||||
public async notifyBufferUpdate(evt: Oni.EditorBufferChangedEventArgs): Promise<void> {
|
||||
const firstChange = evt.contentChanges[0]
|
||||
if (!firstChange.range && !firstChange.rangeLength) {
|
||||
|
@ -157,6 +165,10 @@ export class NullSyntaxHighlighter implements ISyntaxHighlighter {
|
|||
return null
|
||||
}
|
||||
|
||||
public notifyColorschemeRedraw(id: string): void {
|
||||
return null
|
||||
}
|
||||
|
||||
public notifyViewportChanged(
|
||||
bufferId: string,
|
||||
topLineInView: number,
|
||||
|
|
|
@ -51,6 +51,11 @@ export const bufferReducer: Reducer<IBufferSyntaxHighlightState> = (
|
|||
action: ISyntaxHighlightAction,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case "SYNTAX_RESET_BUFFER":
|
||||
return {
|
||||
...state,
|
||||
lines: linesReducer(state.lines, action),
|
||||
}
|
||||
case "SYNTAX_UPDATE_BUFFER":
|
||||
return {
|
||||
...state,
|
||||
|
@ -125,6 +130,21 @@ export const linesReducer: Reducer<SyntaxHighlightLines> = (
|
|||
}
|
||||
return newState
|
||||
}
|
||||
case "SYNTAX_RESET_BUFFER":
|
||||
const resetState = Object.entries(state).reduce<SyntaxHighlightLines>(
|
||||
(newResetState, [lineNumber, line]) => {
|
||||
newResetState[lineNumber] = {
|
||||
tokens: [],
|
||||
ruleStack: null,
|
||||
...line,
|
||||
dirty: true,
|
||||
}
|
||||
return newResetState
|
||||
},
|
||||
{},
|
||||
)
|
||||
return resetState
|
||||
|
||||
case "SYNTAX_UPDATE_BUFFER":
|
||||
const updatedBufferState: SyntaxHighlightLines = {
|
||||
...state,
|
||||
|
|
|
@ -73,6 +73,10 @@ export const DefaultSyntaxHighlightState: ISyntaxHighlightState = {
|
|||
}
|
||||
|
||||
export type ISyntaxHighlightAction =
|
||||
| {
|
||||
type: "SYNTAX_RESET_BUFFER"
|
||||
bufferId: string
|
||||
}
|
||||
| {
|
||||
type: "SYNTAX_UPDATE_BUFFER"
|
||||
language: string
|
||||
|
@ -150,14 +154,14 @@ const updateBufferLineMiddleware = (store: any) => (next: any) => (action: any)
|
|||
action.lineNumber === 0 ? null : buffer.lines[action.lineNumber - 1].ruleStack
|
||||
const tokenizeResult = grammar.tokenizeLine(action.line, previousRuleStack)
|
||||
|
||||
const tokens = tokenizeResult.tokens.map((t: any) => ({
|
||||
const tokens = tokenizeResult.tokens.map(token => ({
|
||||
range: types.Range.create(
|
||||
action.lineNumber,
|
||||
t.startIndex,
|
||||
token.startIndex,
|
||||
action.lineNumber,
|
||||
t.endIndex,
|
||||
token.endIndex,
|
||||
),
|
||||
scopes: t.scopes,
|
||||
scopes: token.scopes,
|
||||
}))
|
||||
|
||||
const updateInsertLineAction: ISyntaxHighlightAction = {
|
||||
|
@ -180,7 +184,11 @@ const updateBufferLineMiddleware = (store: any) => (next: any) => (action: any)
|
|||
const updateTokenMiddleware = (store: any) => (next: any) => (action: any) => {
|
||||
const result: ISyntaxHighlightAction = next(action)
|
||||
|
||||
if (action.type === "SYNTAX_UPDATE_BUFFER" || action.type === "SYNTAX_UPDATE_BUFFER_VIEWPORT") {
|
||||
if (
|
||||
action.type === "SYNTAX_UPDATE_BUFFER" ||
|
||||
action.type === "SYNTAX_UPDATE_BUFFER_VIEWPORT" ||
|
||||
action.type === "SYNTAX_RESET_BUFFER"
|
||||
) {
|
||||
const state: ISyntaxHighlightState = store.getState()
|
||||
const bufferId = action.bufferId
|
||||
|
||||
|
@ -212,7 +220,7 @@ const updateTokenMiddleware = (store: any) => (next: any) => (action: any) => {
|
|||
|
||||
syntaxHighlightingJobs.startJob(
|
||||
new SyntaxHighlightingPeriodicJob(
|
||||
store as any,
|
||||
store,
|
||||
action.bufferId,
|
||||
grammar,
|
||||
relevantRange.top,
|
||||
|
|
|
@ -119,7 +119,7 @@ export class TokenScorer {
|
|||
if (parts.length < 2) {
|
||||
return null
|
||||
}
|
||||
const matchingToken = theme.find(color => color.scope === scope)
|
||||
const matchingToken = theme.find(color => color.scope.includes(scope))
|
||||
if (matchingToken) {
|
||||
return matchingToken
|
||||
}
|
||||
|
|
|
@ -190,8 +190,8 @@ class TokenThemeProvider extends React.Component<IProps, IState> {
|
|||
|
||||
public generateTokens({ defaultMap = defaultsToMap, defaultTokens }: IGenerateTokenArgs) {
|
||||
const newTokens = Object.keys(defaultMap).reduce((acc, defaultTokenName) => {
|
||||
const defaultToken = this.props.tokenColors.find(
|
||||
token => token.scope === defaultTokenName,
|
||||
const defaultToken = this.props.tokenColors.find(token =>
|
||||
token.scope.includes(defaultTokenName),
|
||||
)
|
||||
if (defaultToken) {
|
||||
const tokens = defaultMap[defaultTokenName].map(name =>
|
||||
|
|
|
@ -8,8 +8,11 @@
|
|||
|
||||
import { Event, IDisposable, IEvent } from "oni-types"
|
||||
|
||||
import { Configuration, IConfigurationValues } from "./Configuration"
|
||||
import { ThemeManager } from "./Themes"
|
||||
|
||||
export interface TokenColor {
|
||||
scope: string
|
||||
scope: string[]
|
||||
settings: TokenColorStyle
|
||||
// private field for determining where a token came from
|
||||
_source?: string
|
||||
|
@ -18,6 +21,7 @@ export interface TokenColor {
|
|||
export interface ThemeToken {
|
||||
scope: string | string[]
|
||||
settings: TokenColorStyle
|
||||
_source?: string
|
||||
}
|
||||
|
||||
export interface TokenColorStyle {
|
||||
|
@ -27,9 +31,6 @@ export interface TokenColorStyle {
|
|||
fontStyle: "bold" | "italic" | "bold italic"
|
||||
}
|
||||
|
||||
import { Configuration, IConfigurationValues } from "./Configuration"
|
||||
import { ThemeManager } from "./Themes"
|
||||
|
||||
export class TokenColors implements IDisposable {
|
||||
private _subscriptions: IDisposable[] = []
|
||||
private _tokenColors: TokenColor[] = []
|
||||
|
@ -71,38 +72,49 @@ 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 {
|
||||
activeTheme: {
|
||||
colors: { "editor.tokenColors": tokenColorsFromTheme = [] },
|
||||
colors: { "editor.tokenColors": themeTokens = [] },
|
||||
},
|
||||
} = this._themeManager
|
||||
|
||||
const themeTokens = this._flattenThemeTokens(tokenColorsFromTheme)
|
||||
const userColors = this._configuration.getValue("editor.tokenColors")
|
||||
|
||||
this._tokenColors = this._mergeTokenColors({
|
||||
const combinedColors = this._mergeTokenColors({
|
||||
user: userColors,
|
||||
theme: themeTokens,
|
||||
defaults: this._defaultTokenColors,
|
||||
})
|
||||
|
||||
this._tokenColors = this._convertThemeTokenScopes(combinedColors)
|
||||
|
||||
this._onTokenColorsChangedEvent.dispatch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme tokens can pass in token scopes as a string or an array
|
||||
* this converts all token scopes passed in to an array of strings
|
||||
*
|
||||
* @name convertThemeTokenScopes
|
||||
* @function
|
||||
* @param {ThemeToken[]} tokens
|
||||
* @returns {TokenColor[]}
|
||||
*/
|
||||
private _convertThemeTokenScopes(tokens: ThemeToken[]) {
|
||||
// TODO: figure out how space separated token scopes should be handled
|
||||
// token.scope.split(" ") -> convert "meta.var string.quoted" -> ["meta.var", "string.quoted"]
|
||||
// this however breaks prioritisation of tokens
|
||||
return tokens.map(token => {
|
||||
const scope = !token.scope
|
||||
? []
|
||||
: Array.isArray(token.scope)
|
||||
? token.scope
|
||||
: [token.scope]
|
||||
return { ...token, scope }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge different token source whilst unifying settings
|
||||
* each source is passed by name so that later the priority
|
||||
|
@ -111,34 +123,32 @@ export class TokenColors implements IDisposable {
|
|||
* user source
|
||||
*/
|
||||
private _mergeTokenColors(tokens: {
|
||||
user: TokenColor[]
|
||||
user: ThemeToken[]
|
||||
defaults: TokenColor[]
|
||||
theme: TokenColor[]
|
||||
theme: ThemeToken[]
|
||||
}) {
|
||||
return Object.keys(tokens).reduce(
|
||||
(output, key) => {
|
||||
const tokenColors: TokenColor[] = tokens[key]
|
||||
return tokenColors.reduce((mergedTokens, currentToken) => {
|
||||
return Object.entries(tokens).reduce<ThemeToken[]>(
|
||||
(output, [_source, tokenColors]) =>
|
||||
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,
|
||||
_source,
|
||||
})
|
||||
}
|
||||
return existingToken
|
||||
})
|
||||
}
|
||||
return [...mergedTokens, { ...currentToken, _source: key }]
|
||||
}, output)
|
||||
},
|
||||
[] as TokenColor[],
|
||||
return [...mergedTokens, { ...currentToken, _source }]
|
||||
}, output),
|
||||
[],
|
||||
)
|
||||
}
|
||||
|
||||
private _mergeSettings(prev: TokenColor, next: TokenColor) {
|
||||
private _mergeSettings(prev: ThemeToken, next: ThemeToken) {
|
||||
const priority = {
|
||||
user: 2,
|
||||
theme: 1,
|
||||
|
|
|
@ -311,3 +311,8 @@ export function get<T>(obj: T, ...paths: string[]): string {
|
|||
.split(".")
|
||||
.reduce((a, b) => (a && a[b] ? a[b] : null), obj)
|
||||
}
|
||||
|
||||
export function prettyPrint<T extends object>(item: T, spacing = 2) {
|
||||
// tslint:disable-next-line
|
||||
console.log(JSON.stringify(item, null, spacing))
|
||||
}
|
||||
|
|
|
@ -524,8 +524,8 @@ export class NeovimInstance extends EventEmitter implements INeovimInstance {
|
|||
const settings = vimHighlightToTokenColorStyle(currentValue)
|
||||
const newScopeNames: string[] = VimHighlightToDefaultScope[highlightGroupName] || []
|
||||
|
||||
const newScopes = newScopeNames.map((scope): TokenColor => ({
|
||||
scope,
|
||||
const newScopes = newScopeNames.map(scope => ({
|
||||
scope: [scope],
|
||||
settings,
|
||||
}))
|
||||
|
||||
|
|
|
@ -28,42 +28,41 @@ const getGuiStringFromTokenColor = ({ settings: { fontStyle } }: TokenColor): st
|
|||
}
|
||||
|
||||
export class NeovimTokenColorSynchronizer {
|
||||
private _currentIndex: number = 0
|
||||
private _currentIndex = 0
|
||||
private _tokenScopeSelectorToHighlightName: { [key: string]: string } = {}
|
||||
private _highlightNameToHighlightValue: { [key: string]: string } = {}
|
||||
private _highlightNameToHighlightValue = new Map<string, string>()
|
||||
|
||||
constructor(private _neovimInstance: NeovimInstance) {}
|
||||
constructor(private _neovimInstance: NeovimInstance) {
|
||||
this._neovimInstance.onColorsChanged.subscribe(this._resetHighlightCache)
|
||||
}
|
||||
|
||||
// This method creates highlight groups for any token colors that haven't been set yet
|
||||
public async synchronizeTokenColors(tokenColors: TokenColor[]): Promise<void> {
|
||||
const highlightsToAdd = tokenColors.map(tokenColor => {
|
||||
public async synchronizeTokenColors(tokenColors: TokenColor[]) {
|
||||
const highlightsToAdd = tokenColors.reduce<string[]>((newHighlights, tokenColor) => {
|
||||
const highlightName = this._getOrCreateHighlightGroup(tokenColor)
|
||||
const highlightFromScope = this._convertTokenStyleToHighlightInfo(tokenColor)
|
||||
|
||||
const currentHighlight = this._highlightNameToHighlightValue[highlightName]
|
||||
const currentHighlight = this._highlightNameToHighlightValue.get(highlightName)
|
||||
|
||||
if (currentHighlight === highlightFromScope) {
|
||||
return null
|
||||
} else {
|
||||
this._highlightNameToHighlightValue[highlightName] = highlightFromScope
|
||||
return highlightFromScope
|
||||
return newHighlights
|
||||
}
|
||||
})
|
||||
this._highlightNameToHighlightValue.set(highlightName, highlightFromScope)
|
||||
return [...newHighlights, highlightFromScope]
|
||||
}, [])
|
||||
|
||||
const filteredHighlights = highlightsToAdd.filter(hl => !!hl)
|
||||
|
||||
const atomicCalls = filteredHighlights.map(hlCommand => ["nvim_command", [hlCommand]])
|
||||
const atomicCalls = highlightsToAdd.map(hlCommand => ["nvim_command", [hlCommand]])
|
||||
|
||||
if (!atomicCalls.length) {
|
||||
return
|
||||
}
|
||||
|
||||
Log.info(
|
||||
"[NeovimTokenColorSynchronizer::synchronizeTokenColors] Setting " +
|
||||
atomicCalls.length +
|
||||
" highlights",
|
||||
`[NeovimTokenColorSynchronizer::synchronizeTokenColors] Setting ${
|
||||
atomicCalls.length
|
||||
} highlights`,
|
||||
)
|
||||
await this._neovimInstance.request("nvim_call_atomic", [atomicCalls])
|
||||
this._neovimInstance.request("nvim_call_atomic", [atomicCalls])
|
||||
Log.info(
|
||||
"[NeovimTokenColorSynchronizer::synchronizeTokenColors] Highlights set successfully",
|
||||
)
|
||||
|
@ -77,6 +76,10 @@ export class NeovimTokenColorSynchronizer {
|
|||
return this._getOrCreateHighlightGroup(tokenColor)
|
||||
}
|
||||
|
||||
private _resetHighlightCache = () => {
|
||||
this._highlightNameToHighlightValue.clear()
|
||||
}
|
||||
|
||||
private _convertTokenStyleToHighlightInfo(tokenColor: TokenColor): string {
|
||||
const name = this._getOrCreateHighlightGroup(tokenColor)
|
||||
const foregroundColor = Color(tokenColor.settings.foreground).hex()
|
||||
|
@ -86,9 +89,9 @@ export class NeovimTokenColorSynchronizer {
|
|||
}
|
||||
|
||||
private _getOrCreateHighlightGroup(tokenColor: TokenColor): string {
|
||||
const existingGroup = this._tokenScopeSelectorToHighlightName[
|
||||
this._getKeyFromTokenColor(tokenColor)
|
||||
]
|
||||
const tokenKey = this._getKeyFromTokenColor(tokenColor)
|
||||
const existingGroup = this._tokenScopeSelectorToHighlightName[tokenKey]
|
||||
|
||||
if (existingGroup) {
|
||||
return existingGroup
|
||||
} else {
|
||||
|
@ -98,21 +101,20 @@ export class NeovimTokenColorSynchronizer {
|
|||
"[NeovimTokenColorSynchronizer::_getOrCreateHighlightGroup] Creating new highlight group - " +
|
||||
newHighlightGroupName,
|
||||
)
|
||||
this._tokenScopeSelectorToHighlightName[
|
||||
this._getKeyFromTokenColor(tokenColor)
|
||||
] = newHighlightGroupName
|
||||
this._tokenScopeSelectorToHighlightName[tokenKey] = newHighlightGroupName
|
||||
return newHighlightGroupName
|
||||
}
|
||||
}
|
||||
|
||||
private _getKeyFromTokenColor(tokenColor: TokenColor): string {
|
||||
const {
|
||||
settings: { background, foreground, fontStyle },
|
||||
settings: { background = "none", foreground = "none", fontStyle = "none" },
|
||||
} = tokenColor
|
||||
const separator = `__`
|
||||
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}`
|
||||
const bold = `bold-${fontStyle ? fontStyle.includes("bold") : false}`
|
||||
const italic = `italic-${fontStyle ? fontStyle.includes("italic") : false}`
|
||||
return [tokenColor.scope, bg, fg, bold, italic].join(separator)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@ export class MockNeovimInstance {
|
|||
private _requests: NeovimRequest[] = []
|
||||
private _pendingPromises: Array<Utility.ICompletablePromise<any>> = []
|
||||
|
||||
public get onColorsChanged() {
|
||||
return {
|
||||
subscribe: (fn: (args?: any) => any) => fn(),
|
||||
}
|
||||
}
|
||||
|
||||
public request(requestName: string, args: any[]) {
|
||||
this._requests.push({ requestName, args })
|
||||
const promise = Utility.createCompletablePromise()
|
||||
|
|
|
@ -25,7 +25,7 @@ describe("SyntaxHighlightReconciler", () => {
|
|||
|
||||
mockTokenColors = new Mocks.MockTokenColors([
|
||||
{
|
||||
scope: "scope.test",
|
||||
scope: ["scope.test"],
|
||||
settings: {
|
||||
background: COLOR_BLACK,
|
||||
foreground: COLOR_WHITE,
|
||||
|
@ -90,7 +90,7 @@ describe("SyntaxHighlightReconciler", () => {
|
|||
{
|
||||
range: types.Range.create(0, 0, 0, 5),
|
||||
tokenColor: {
|
||||
scope: "scope.test",
|
||||
scope: ["scope.test"],
|
||||
settings: {
|
||||
background: COLOR_BLACK,
|
||||
foreground: COLOR_WHITE,
|
||||
|
@ -151,7 +151,7 @@ describe("SyntaxHighlightReconciler", () => {
|
|||
{
|
||||
range: types.Range.create(0, 0, 0, 5),
|
||||
tokenColor: {
|
||||
scope: "scope.test",
|
||||
scope: ["scope.test"],
|
||||
settings: {
|
||||
background: COLOR_BLACK,
|
||||
foreground: COLOR_WHITE,
|
||||
|
|
|
@ -9,7 +9,7 @@ import { TokenColor } from "./../../src/Services/TokenColors"
|
|||
|
||||
import { MockNeovimInstance } from "./../Mocks/neovim"
|
||||
|
||||
const createTokenColor = (scope: string): TokenColor => ({
|
||||
const createTokenColor = (...scope: string[]): TokenColor => ({
|
||||
scope,
|
||||
settings: {
|
||||
foreground: "#FFFFFF",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"check-format"
|
||||
],
|
||||
"whitespace": false,
|
||||
"no-trailing-whitespace": [true, "ignore-template-strings"],
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue