[WIP / Prototype] Use TextMate themes for syntax highlighting (#848)

* Add vscode-textmate

* Add syntax highlighting strategy plus JavaScript textmate theme

* Move to syntaxes folder

* Start parsing tokens from buffer updates

* Hard wire some scopes to Vim functions as a proof-of-concept

* Add syntax highlighting store

* Factor out to use epic

* Get end-to-end working (although everything gets scoped as a function!

* Update PLAN

* Fix compilation issues after merge

* Update Plan, initialize token colors

* Tweak keyword back to purple, test out identifiers

* Move settings to configuration, instead of being hardcoded

* Fix first round of lint issues

* Use textMateGrammar setting for loading language

* Update PLAN

* Fix remaining lint errors

* Add 'notifyViewportChanged' handler

* Remove no-op log

* Hook up SYNTAX_UPDATE_BUFFER_VIEWPORT action, and filter reconciler to only grab visible rows

* Add some smarts so that the Buffer can less aggressively update highlights, if the highlight is already set

* Update PLAN

* Batch calls to Neovim for adding and clearing highlights

* Performance improvements for parsing tokens

* Get working end-to-end

* Get working end-to-end with incremental updates

* Start cleaning up code

* Start cleaning up / remove unused code

* Fix lint issues

* Tweak throttled values

* Update PLAN.md

* Fix lint issues

* Add line limit

* Add debug scopes element, start tweaking scopes

* Fix lint issues

* Update PLAN

* Update PLAN

* Fix tests

* Update to use selector for getting the relevant range

* Wire up insert mode actions

* Fix some remaining bugs with synchronizing highlights

* Update configuration for additional tokens, update PLAN

* Additional bug fixes for syntax highligher

* Fix logging

* Remove line for getting syntax store

* Update PLAN

* Remove some unused code

* Add additional logging for notifyViewportChanged

* Change from info -> verbose

* Fix lint issue

* Update GrammarLoader to support querying based on extension

* Update loader to different grammars based on file extensions

* Remove PLAN, fix lint issue in GrammarLoader
This commit is contained in:
Bryan Phelps 2017-11-26 08:08:12 -08:00 committed by GitHub
parent a3da2c9876
commit 4dd43453b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 28653 additions and 54 deletions

View File

@ -1,2 +0,0 @@
- How does this work with typing predictions? Seems like there may be artifacts that weren't present before
- Any regressions?

View File

@ -0,0 +1,77 @@
/**
* BufferHighlights.ts
*
* Helpers to manage buffer highlight state
*/
import * as SyntaxHighlighting from "./../Services/SyntaxHighlighting"
import { NeovimInstance } from "./../neovim"
// Line number to highlight src id, for clearing
export type HighlightSourceId = number
export interface BufferHighlightState { [key: number]: HighlightSourceId }
export interface IBufferHighlightsUpdater {
setHighlightsForLine(line: number, highlights: SyntaxHighlighting.HighlightInfo[]): void
clearHighlightsForLine(line: number): void
}
// Helper class to efficiently update
// buffer highlights in a batch.
export class BufferHighlightsUpdater implements IBufferHighlightsUpdater {
private _newSrcId: number
private _calls: any[] = []
private _newState: BufferHighlightState
constructor(
private _bufferId: number,
private _neovimInstance: NeovimInstance,
private _previousState: BufferHighlightState,
) {}
public async start(): Promise<void> {
this._newState = {
...this._previousState,
}
this._newSrcId = await this._neovimInstance.request<number>("nvim_buf_add_highlight", [this._bufferId, 0, "", 0, 0, 0])
}
public setHighlightsForLine(line: number, highlights: SyntaxHighlighting.HighlightInfo[]): void {
this.clearHighlightsForLine(line)
if (!highlights || !highlights.length) {
return
}
const addHighlightCalls = highlights.map((hl) => {
return ["nvim_buf_add_highlight", [this._bufferId, this._newSrcId, hl.highlightGroup,
hl.range.start.line, hl.range.start.character, hl.range.end.character]]
})
this._newState[line] = this._newSrcId
this._calls = this._calls.concat(addHighlightCalls)
}
public clearHighlightsForLine(line: number): void {
const previousLine = this._previousState[line]
if (!previousLine) {
return
}
const oldSrcId = this._previousState[line]
this._newState[line] = null
this._calls.push(["nvim_buf_clear_highlight", [this._bufferId, oldSrcId, line, line + 1]])
}
public async apply(): Promise<BufferHighlightState> {
if (this._calls.length > 0) {
await this._neovimInstance.request<void>("nvim_call_atomic", [this._calls])
}
return this._newState
}
}

View File

@ -17,6 +17,11 @@ import * as Oni from "oni-api"
import { EventContext, NeovimInstance } from "./../neovim"
import { languageManager, sortTextEdits } from "./../Services/Language"
import { PromiseQueue } from "./../Services/Language/PromiseQueue"
import * as SyntaxHighlighting from "./../Services/SyntaxHighlighting"
import { BufferHighlightState, BufferHighlightsUpdater, IBufferHighlightsUpdater } from "./BufferHighlights"
import * as Constants from "./../Constants"
import * as Log from "./../Log"
@ -34,6 +39,9 @@ export class Buffer implements Oni.Buffer {
private _bufferLines: string[] = null
private _lastBufferLineVersion: number = -1
private _promiseQueue = new PromiseQueue()
private _previousHighlightState: BufferHighlightState = {}
public get filePath(): string {
return this._filePath
}
@ -124,6 +132,27 @@ export class Buffer implements Oni.Buffer {
.toPromise()
}
public async getOrCreateHighlightGroup(highlight: SyntaxHighlighting.IHighlight | string): Promise<SyntaxHighlighting.HighlightGroupId> {
if (typeof highlight === "string") {
return highlight
} else {
// TODO: needed for theming integration!
return null
}
}
public async updateHighlights(updateFunction: (highlightsUpdater: IBufferHighlightsUpdater) => void): Promise<void> {
this._promiseQueue.enqueuePromise(async () => {
const bufferId = parseInt(this._id, 10)
const bufferUpdater = new BufferHighlightsUpdater(bufferId, this._neovimInstance, this._previousHighlightState)
await bufferUpdater.start()
updateFunction(bufferUpdater)
this._previousHighlightState = await bufferUpdater.apply()
})
}
public async setLines(start: number, end: number, lines: string[]): Promise<void> {
// Clear buffer lines, so that if we make subsequent edits, we are always getting the freshest line
// TODO: Speed this up by updating the `_bufferLines` cache instead

View File

@ -28,6 +28,7 @@ import { registerBuiltInCommands } from "./../Services/Commands"
import { configuration, IConfigurationValues } from "./../Services/Configuration"
import { Errors } from "./../Services/Errors"
import { addInsertModeLanguageFunctionality, addNormalModeLanguageFunctionality } from "./../Services/Language"
import { ISyntaxHighlighter, NullSyntaxHighlighter, SyntaxHighlighter } from "./../Services/SyntaxHighlighting"
import { TypingPredictionManager } from "./../Services/TypingPredictionManager"
import { WindowTitle } from "./../Services/WindowTitle"
import { workspace } from "./../Services/Workspace"
@ -76,6 +77,7 @@ export class NeovimEditor implements IEditor {
private _lastBufferId: string = null
private _typingPredictionManager: TypingPredictionManager = new TypingPredictionManager()
private _syntaxHighlighter: ISyntaxHighlighter
public get mode(): string {
return this._currentMode
@ -115,6 +117,10 @@ export class NeovimEditor implements IEditor {
return this._neovimInstance
}
public get syntaxHighlighter(): ISyntaxHighlighter {
return this._syntaxHighlighter
}
constructor(
private _config = configuration,
) {
@ -218,11 +224,16 @@ export class NeovimEditor implements IEditor {
bufferUpdates$.subscribe((bufferUpdate) => {
this._onBufferChangedEvent.dispatch(bufferUpdate)
UI.Actions.bufferUpdate(parseInt(bufferUpdate.buffer.id, 10), bufferUpdate.buffer.modified, bufferUpdate.buffer.lineCount)
this._syntaxHighlighter.notifyBufferUpdate(bufferUpdate)
})
addInsertModeLanguageFunctionality(this._cursorMovedI$, this._modeChanged$)
addNormalModeLanguageFunctionality(bufferUpdates$, this._cursorMoved$, this._modeChanged$)
const textMateHighlightingEnabled = this._config.getValue("experimental.editor.textMateHighlighting.enabled")
this._syntaxHighlighter = textMateHighlightingEnabled ? new SyntaxHighlighter() : new NullSyntaxHighlighter()
this._render()
const browserWindow = remote.getCurrentWindow()
@ -277,6 +288,11 @@ export class NeovimEditor implements IEditor {
}
public dispose(): void {
if (this._syntaxHighlighter) {
this._syntaxHighlighter.dispose()
this._syntaxHighlighter = null
}
// TODO: Implement full disposal logic
this._popupMenu.dispose()
this._popupMenu = null
@ -353,11 +369,21 @@ export class NeovimEditor implements IEditor {
UI.Actions.setMode(newMode)
this._currentMode = newMode
if (newMode === "insert") {
this._syntaxHighlighter.notifyStartInsertMode(this.activeBuffer)
} else {
this._syntaxHighlighter.notifyEndInsertMode(this.activeBuffer)
}
}
private _onVimEvent(eventName: string, evt: EventContext): void {
UI.Actions.setWindowCursor(evt.windowNumber, evt.line - 1, evt.column - 1)
// Convert to 0-based positions
this._syntaxHighlighter.notifyViewportChanged(evt.bufferNumber.toString(), evt.windowTopLine - 1, evt.windowBottomLine - 1)
const lastBuffer = this.activeBuffer
const buf = this._bufferManager.updateBufferFromEvent(evt)

View File

@ -9,7 +9,6 @@ import * as React from "react"
import { Mouse } from "./../Input/Mouse"
import { NeovimInstance } from "./../neovim"
import { NeovimScreen } from "./../Screen"
// import * as UI from "./../UI/index"
import { TypingPredictionManager } from "./../Services/TypingPredictionManager"

View File

@ -0,0 +1,68 @@
import * as Constants from "./Constants"
import * as Log from "./Log"
// IPeriodicJob implements the interface for a long-running job
// that would be expensive to run synchronously, so it is
// spread across multiple asynchronous iterations.
export interface IPeriodicJob {
// Execute should return `true` if the job is complete,
// false otherwise
execute(): boolean
}
export class PeriodicJobManager {
private _currentScheduledJob: number = null
private _pendingJobs: IPeriodicJob[] = []
public startJob(job: IPeriodicJob) {
this._pendingJobs.push(job)
Log.verbose("[PeriodicJobManager]::startJob - " + this._pendingJobs.length + " total jobs.")
this._scheduleJobs()
}
private _scheduleJobs(): void {
if (this._currentScheduledJob) {
return
}
if (this._pendingJobs.length === 0) {
Log.verbose("[PeriodicJobManager]::_scheduleJobs - no jobs pending.")
}
this._currentScheduledJob = window.setTimeout(() => {
const completed = this._executePendingJobs()
window.clearTimeout(this._currentScheduledJob)
this._currentScheduledJob = null
if (!completed) {
this._scheduleJobs()
}
}, Constants.Delay.INSTANT)
Log.verbose("[PeriodicJobManager]::_scheduleJobs - " + this._currentScheduledJob)
}
private _executePendingJobs(): boolean {
const completedJobs: IPeriodicJob[] = []
this._pendingJobs.forEach((job) => {
const completed = job.execute()
if (completed) {
completedJobs.push(job)
}
})
// Remove completed jobs
this._pendingJobs = this._pendingJobs.filter((job) => completedJobs.indexOf(job) === -1)
if (this._pendingJobs.length === 0) {
Log.verbose("[PeriodicJobManager] All jobs complete.")
} else {
Log.verbose("[PeriodicJobManager] " + this._pendingJobs.length + " jobs remaining.")
}
// Return true if all jobs were completed, false otherwise
return this._pendingJobs.length === 0
}
}

View File

@ -22,8 +22,7 @@ export class PluginManager extends EventEmitter {
return this._plugins
}
constructor() {
super()
public startPlugins(): Oni.Plugin.Api {
this._rootPluginPaths.push(corePluginsRoot)
@ -33,9 +32,7 @@ export class PluginManager extends EventEmitter {
}
this._rootPluginPaths.push(path.join(this._config.getUserFolder(), "plugins"))
}
public startPlugins(): Oni.Plugin.Api {
const allPluginPaths = this._getAllPluginPaths()
this._plugins = allPluginPaths.map((pluginRootDirectory) => this._createPlugin(pluginRootDirectory))

View File

@ -31,7 +31,7 @@ export class Configuration implements Oni.Configuration {
return this._onConfigurationChangedEvent
}
constructor() {
public start(): void {
Performance.mark("Config.load.start")
this.applyConfig()

View File

@ -39,6 +39,9 @@ const BaseConfiguration: IConfigurationValues = {
{ "open": "(", "close": ")" },
],
"experimental.editor.textMateHighlighting.enabled": false,
"experimental.editor.textMateHighlighting.maxLines": 2000,
"experimental.editor.typingPrediction": false,
"experimental.neovim.transport": "stdio",
@ -92,6 +95,32 @@ const BaseConfiguration: IConfigurationValues = {
"editor.cursorColumn": false,
"editor.cursorColumnOpacity": 0.1,
"editor.tokenColors": [{
scope: "variable.object",
settings: "Identifier",
}, {
scope: "variable.other.constant",
settings: "Constant",
}, {
scope: "variable.language",
settings: "Identifier",
}, {
scope: "variable.parameter",
settings: "Identifier",
}, {
scope: "variable.other",
settings: "Identifier",
}, {
scope: "support.function",
settings: "Function",
}, {
scope: "entity.name",
settings: "Function",
}, {
scope: "entity.other",
settings: "Constant",
}],
"environment.additionalPaths": [],
"language.go.languageServer.command": "go-langserver",
@ -101,25 +130,37 @@ const BaseConfiguration: IConfigurationValues = {
"language.css.languageServer.command": cssLanguageServerPath,
"language.css.languageServer.arguments": ["--stdio"],
"language.css.textMateGrammar": path.join(__dirname, "extensions", "css", "syntaxes", "css.tmLanguage.json"),
"language.less.languageServer.command": cssLanguageServerPath,
"language.less.languageServer.arguments": ["--stdio"],
"language.less.textMateGrammar": path.join(__dirname, "extensions", "less", "syntaxes", "less.tmLanguage.json"),
"language.scss.languageServer.command": cssLanguageServerPath,
"language.scss.languageServer.arguments": ["--stdio"],
"language.scss.textMateGrammar": path.join(__dirname, "extensions", "scss", "syntaxes", "scss.json"),
"language.reason.languageServer.command": ocamlLanguageServerPath,
"language.reason.languageServer.arguments": ["--stdio"],
"language.reason.languageServer.rootFiles": [".merlin", "bsconfig.json"],
"language.reason.languageServer.configuration": ocamlAndReasonConfiguration,
"language.reason.textMateGrammar": path.join(__dirname, "extensions", "reason", "syntaxes", "reason.json"),
"language.ocaml.languageServer.command": ocamlLanguageServerPath,
"language.ocaml.languageServer.arguments": ["--stdio"],
"language.ocaml.languageServer.configuration": ocamlAndReasonConfiguration,
"language.typescript.completionTriggerCharacters": [".", "/", "\\"],
"language.typescript.textMateGrammar": {
".ts": path.join(__dirname, "extensions", "typescript", "syntaxes", "TypeScript.tmLanguage.json"),
".tsx": path.join(__dirname, "extensions", "typescript", "syntaxes", "TypeScriptReact.tmLanguage.json"),
},
"language.javascript.completionTriggerCharacters": [".", "/", "\\"],
"language.javascript.textMateGrammar": {
".js": path.join(__dirname, "extensions", "javascript", "syntaxes", "JavaScript.tmLanguage.json"),
".jsx": path.join(__dirname, "extensions", "javascript", "syntaxes", "JavaScriptReact.tmLanguage.json"),
},
"menu.caseSensitive": "smart",

View File

@ -8,6 +8,13 @@
import * as Oni from "oni-api"
import { IHighlight } from "./../SyntaxHighlighting"
export interface ITokenColorsSetting {
scope: string
settings: IHighlight | string
}
export interface IConfigurationValues {
"activate": (oni: Oni.Plugin.Api) => void
@ -31,9 +38,15 @@ export interface IConfigurationValues {
"debug.fakeLag.neovimInput": number | null
// Experimental feature flags
// - autoClosingPairs
"experimental.autoClosingPairs.enabled": boolean
"experimental.autoClosingPairs.default": any
// - textMateHighlighting
"experimental.editor.textMateHighlighting.enabled": boolean
// If a file has more lines than this value, syntax highlighting will be disabled
"experimental.editor.textMateHighlighting.maxLines": number
// The transport to use for Neovim
// Valid values are "stdio" and "pipe"
"experimental.neovim.transport": string
@ -113,6 +126,9 @@ export interface IConfigurationValues {
// If true (default), the buffer scroll bar will be visible
"editor.scrollBar.visible": boolean
// Allow overriding token colors for specific textmate scopes
"editor.tokenColors": ITokenColorsSetting[]
// Additional paths to include when launching sub-process from Oni
// (and available in terminal integration, later)
"environment.additionalPaths": string[]

View File

@ -94,6 +94,10 @@ export const renderQuickInfo = (hover: types.Hover, errors: types.Diagnostic[])
const elements = [...errorElements, ...quickInfoElements ]
if (configuration.getValue("experimental.editor.textMateHighlighting.debugScopes")) {
elements.push(getDebugScopesElement())
}
return <div className="quickinfo-container enable-mouse">
<div className="quickinfo">
<div className="container horizontal center">
@ -105,6 +109,31 @@ export const renderQuickInfo = (hover: types.Hover, errors: types.Diagnostic[])
</div>
}
const getDebugScopesElement = (): JSX.Element => {
const editor: any = editorManager.activeEditor
if (!editor || !editor.syntaxHighlighter) {
return null
}
const cursor = editorManager.activeEditor.activeBuffer.cursor
const scopeInfo = editor.syntaxHighlighter.getHighlightTokenAt(editorManager.activeEditor.activeBuffer.id, {
line: cursor.line,
character: cursor.column,
})
if (!scopeInfo || !scopeInfo.scopes) {
return null
}
const items = scopeInfo.scopes.map((si: string) => <li>{si}</li>)
return <div className="documentation">
<div>DEBUG: TextMate Scopes:</div>
<ul>
{items}
</ul>
</div>
}
const getErrorElements = (errors: types.Diagnostic[], style: any): JSX.Element[] => {
if (!errors || !errors.length) {
return Selectors.EmptyArray

View File

@ -0,0 +1,14 @@
import * as types from "vscode-languageserver-types"
export interface IHighlight {
foregroundColor: string
backgroundColor: string
bold: boolean
italic: boolean
}
export type HighlightGroupId = string
export interface HighlightInfo { range: types.Range, highlightGroup: HighlightGroupId }

View File

@ -0,0 +1,51 @@
import { IGrammar, Registry } from "vscode-textmate"
import { configuration } from "./../Configuration"
export interface IGrammarLoader {
getGrammarForLanguage(language: string, extension: string): Promise<IGrammar>
}
export interface ExtensionToGrammarMap { [extension: string]: string }
export const getPathForLanguage = (language: string, extension: string): string => {
const grammar: string | ExtensionToGrammarMap = configuration.getValue("language." + language + ".textMateGrammar")
if (typeof grammar === "string") {
return grammar
} else {
return grammar[extension] || null
}
}
export class GrammarLoader implements IGrammarLoader {
private _grammarCache: { [language: string]: IGrammar } = {}
constructor(
private _registry: Registry = new Registry(),
) {}
public async getGrammarForLanguage(language: string, extension: string): Promise<IGrammar> {
if (!language) {
return null
}
if (this._grammarCache[language]) {
return this._grammarCache[language]
}
const filePath = getPathForLanguage(language, extension)
if (!filePath) {
return null
}
const grammar = this._registry.loadGrammarFromPathSync(filePath)
this._grammarCache[language] = grammar
return grammar
}
}

View File

@ -0,0 +1,144 @@
/**
* SyntaxHighlighting.ts
*
* Handles enhanced syntax highlighting
*/
import * as throttle from "lodash/throttle"
import { configuration, Configuration } from "./../Configuration"
import { editorManager } from "./../EditorManager"
import { HighlightGroupId, HighlightInfo } from "./Definitions"
import { ISyntaxHighlightLineInfo, ISyntaxHighlightState, ISyntaxHighlightTokenInfo } from "./SyntaxHighlightingStore"
import * as Selectors from "./SyntaxHighlightSelectors"
import { Store, Unsubscribe } from "redux"
import * as Log from "./../../Log"
// SyntaxHighlightReconciler
//
// Essentially a renderer / reconciler, that will push
// highlight calls to the active buffer based on the active
// window and viewport
export class SyntaxHighlightReconciler {
private _unsubscribe: Unsubscribe
private _previousState: { [line: number]: ISyntaxHighlightLineInfo} = {}
constructor(
private _store: Store<ISyntaxHighlightState>,
private _configuration: Configuration = configuration,
) {
this._unsubscribe = this._store.subscribe(throttle(() => {
Log.info("[SyntaxHighlightReconciler] Got state update.")
const state = this._store.getState()
const activeBuffer: any = editorManager.activeEditor.activeBuffer
if (!activeBuffer) {
return
}
const bufferId = activeBuffer.id
const currentHighlightState = state.bufferToHighlights[bufferId]
if (currentHighlightState && currentHighlightState.lines) {
const lineNumbers = Object.keys(currentHighlightState.lines)
const relevantRange = Selectors.getRelevantRange(state, bufferId)
const filteredLines = lineNumbers.filter((line) => {
const lineNumber = parseInt(line, 10)
// Ignore lines that are not in current view
if (lineNumber < relevantRange.top
|| lineNumber > relevantRange.bottom) {
return false
}
const latestLine = currentHighlightState.lines[line]
// If dirty (haven't processed tokens yet) - skip
if (latestLine.dirty) {
return false
}
// Or lines that haven't been updated
return this._previousState[line] !== currentHighlightState.lines[line]
})
const tokens = filteredLines.map((li) => {
const line = currentHighlightState.lines[li]
const highlights = this._mapTokensToHighlights(line.tokens)
return {
line: parseInt(li, 10),
highlights,
}
})
filteredLines.forEach((li) => {
this._previousState[li] = currentHighlightState.lines[li]
})
if (tokens.length > 0) {
Log.info("[SyntaxHighlightReconciler] Applying changes to " + tokens.length + " lines.")
activeBuffer.updateHighlights((highlightUpdater: any) => {
tokens.forEach((token) => {
const line = token.line
const highlights = token.highlights
if (Log.isDebugLoggingEnabled()) {
Log.debug("[SyntaxHighlightingReconciler] Updating tokens for line: " + token.line + " | " + JSON.stringify(highlights))
}
highlightUpdater.setHighlightsForLine(line, highlights)
})
})
}
}
}, 100))
}
public dispose(): void {
if (this._unsubscribe) {
this._unsubscribe()
this._unsubscribe = null
}
}
private _mapTokensToHighlights(tokens: ISyntaxHighlightTokenInfo[]): HighlightInfo[] {
const mapTokenToHighlight = (token: ISyntaxHighlightTokenInfo) => ({
highlightGroup: this._getHighlightGroupFromScope(token.scopes),
range: token.range,
})
return tokens.map(mapTokenToHighlight)
.filter((t) => !!t.highlightGroup)
}
private _getHighlightGroupFromScope(scopes: string[]): HighlightGroupId {
const configurationColors = this._configuration.getValue("editor.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.settings
}
}
return null
}
}

View File

@ -0,0 +1,30 @@
// SyntaxHighlightingSelectors.ts
//
// Reducers for handling state changes from ISyntaxHighlightActions
import { ISyntaxHighlightState } from "./SyntaxHighlightingStore"
export interface SyntaxHighlightRange { top: number, bottom: number }
export const NullRange: SyntaxHighlightRange = { top: -1, bottom: -1 }
export const getRelevantRange = (state: ISyntaxHighlightState, bufferId: number | string): SyntaxHighlightRange => {
if (!state.bufferToHighlights[bufferId]) {
return NullRange
}
const buffer = state.bufferToHighlights[bufferId]
if (!state.isInsertMode) {
return {
top: buffer.topVisibleLine,
bottom: buffer.bottomVisibleLine,
}
} else {
return {
top: buffer.activeInsertModeLine,
bottom: buffer.activeInsertModeLine,
}
}
}

View File

@ -0,0 +1,159 @@
/**
* SyntaxHighlighting.ts
*
* Handles enhanced syntax highlighting
*/
import * as os from "os"
import * as path from "path"
import * as types from "vscode-languageserver-types"
import * as Oni from "oni-api"
import { IDisposable } from "oni-types"
import { Store } from "redux"
import { createSyntaxHighlightStore, ISyntaxHighlightState, ISyntaxHighlightTokenInfo } from "./SyntaxHighlightingStore"
import { SyntaxHighlightReconciler } from "./SyntaxHighlightReconciler"
import * as Log from "./../../Log"
import * as Utility from "./../../Utility"
export interface ISyntaxHighlighter extends IDisposable {
notifyBufferUpdate(evt: Oni.EditorBufferChangedEventArgs): Promise<void>
notifyViewportChanged(bufferId: string, topLineInView: number, bottomLineInView: number): void
notifyStartInsertMode(buffer: Oni.Buffer): void
notifyEndInsertMode(buffer: Oni.Buffer): void
getHighlightTokenAt(bufferId: string, position: types.Position): ISyntaxHighlightTokenInfo
}
export class SyntaxHighlighter implements ISyntaxHighlighter {
private _store: Store<ISyntaxHighlightState>
private _reconciler: SyntaxHighlightReconciler
constructor() {
this._store = createSyntaxHighlightStore()
this._reconciler = new SyntaxHighlightReconciler(this._store)
}
public notifyViewportChanged(bufferId: string, topLineInView: number, bottomLineInView: number): void {
Log.verbose("[SyntaxHighlighting.notifyViewportChanged] - bufferId: " + bufferId + " topLineInView: " + topLineInView + " bottomLineInView: " + bottomLineInView)
const state = this._store.getState()
const previousBufferState = state.bufferToHighlights[bufferId]
if (previousBufferState && topLineInView === previousBufferState.topVisibleLine && bottomLineInView === previousBufferState.bottomVisibleLine) {
return
}
this._store.dispatch({
type: "SYNTAX_UPDATE_BUFFER_VIEWPORT",
bufferId,
topVisibleLine: topLineInView,
bottomVisibleLine: bottomLineInView,
})
}
public notifyStartInsertMode(buffer: Oni.Buffer): void {
this._store.dispatch({
type: "START_INSERT_MODE",
bufferId: buffer.id,
})
}
public async notifyEndInsertMode(buffer: any): Promise<void> {
const lines = await buffer.getLines(0, buffer.lineCount, false)
// const currentState = this._store.getState()
// Send a full refresh of the lines
this._store.dispatch({
type: "END_INSERT_MODE",
bufferId: buffer.id,
})
this._store.dispatch({
type: "SYNTAX_UPDATE_BUFFER",
extension: path.extname(buffer.filePath),
language: buffer.language,
bufferId: buffer.id,
lines,
})
}
public async notifyBufferUpdate(evt: Oni.EditorBufferChangedEventArgs): Promise<void> {
const firstChange = evt.contentChanges[0]
if (!firstChange.range && !firstChange.rangeLength) {
const lines = firstChange.text.split(os.EOL)
this._store.dispatch({
type: "SYNTAX_UPDATE_BUFFER",
extension: path.extname(evt.buffer.filePath),
language: evt.buffer.language,
bufferId: evt.buffer.id,
lines,
})
} else {
// Incremental update
this._store.dispatch({
type: "SYNTAX_UPDATE_BUFFER_LINE",
bufferId: evt.buffer.id,
lineNumber: firstChange.range.start.line,
line: firstChange.text,
})
}
}
public getHighlightTokenAt(bufferId: string, position: types.Position): ISyntaxHighlightTokenInfo {
const state = this._store.getState()
const buffer = state.bufferToHighlights[bufferId]
if (!buffer) {
return null
}
const line = buffer.lines[position.line]
if (!line) {
return null
}
return line.tokens.find((r) => Utility.isInRange(position.line, position.character, r.range))
}
public dispose(): void {
if (this._reconciler) {
this._reconciler.dispose()
this._reconciler = null
}
}
}
export class NullSyntaxHighlighter implements ISyntaxHighlighter {
public notifyBufferUpdate(evt: Oni.EditorBufferChangedEventArgs): Promise<void> {
return Promise.resolve(null)
}
public getHighlightTokenAt(bufferId: string, position: types.Position): ISyntaxHighlightTokenInfo {
return null
}
public notifyViewportChanged(bufferId: string, topLineInView: number, bottomLineInView: number): void {
// tslint: disable-line
}
public notifyStartInsertMode(buffer: Oni.Buffer): void {
// tslint: disable-line
}
public notifyEndInsertMode(buffer: Oni.Buffer): void {
// tslint: disable-line
}
public dispose(): void { } // tslint:disable-line
}

View File

@ -0,0 +1,109 @@
/**
* SyntaxHighlightingPeridiocJob.ts
*
* Periodic (asynchronous) job to process syntax highlights
*/
import { Store } from "redux"
import * as types from "vscode-languageserver-types"
import { IGrammar } from "vscode-textmate"
import * as SyntaxHighlighting from "./SyntaxHighlightingStore"
import * as Selectors from "./SyntaxHighlightSelectors"
import { IPeriodicJob } from "./../../PeriodicJobs"
import * as Log from "./../../Log"
export const SYNTAX_JOB_BUDGET = 10 // Budget in milliseconds - time to allow the job to run for
export class SyntaxHighlightingPeriodicJob implements IPeriodicJob {
constructor(
private _store: Store<SyntaxHighlighting.ISyntaxHighlightState>,
private _bufferId: string,
private _grammar: IGrammar,
private _topLine: number,
private _bottomLine: number,
) {
}
public execute(): boolean {
const start = new Date().getTime()
// If the window has changed, we should bail
const currentWindow = Selectors.getRelevantRange(this._store.getState(), this._bufferId)
if (currentWindow.top !== this._topLine || currentWindow.bottom !== this._bottomLine) {
Log.verbose("[SyntaxHighlightingPeriodicJob.execute] Completing without doing work, as window size has changed.")
return true
}
while (true) {
const current = new Date().getTime()
if (current - start > SYNTAX_JOB_BUDGET) {
Log.verbose("[SyntaxHighlightingPeriodicJob.execute] Pending due to exceeding budget.")
return false
}
const currentState = this._store.getState()
const bufferState = currentState.bufferToHighlights[this._bufferId]
if (!bufferState) {
return true
}
const anyDirty = this._tokenizeFirstDirtyLine(bufferState)
if (!anyDirty) {
return true
}
}
}
private _tokenizeFirstDirtyLine(state: SyntaxHighlighting.IBufferSyntaxHighlightState): boolean {
let index = this._topLine
while (index <= this._bottomLine) {
const line = state.lines[index]
if (!line) {
break
}
if (!line.dirty) {
index++
continue
}
const previousStack = index === 0 ? null : state.lines[index - 1].ruleStack
const tokenizeResult = this._grammar.tokenizeLine(line.line, previousStack)
const tokens = tokenizeResult.tokens.map((t: any) => ({
range: types.Range.create(index, t.startIndex, index, t.endIndex),
scopes: t.scopes,
}))
const ruleStack = tokenizeResult.ruleStack
this._store.dispatch({
type: "SYNTAX_UPDATE_TOKENS_FOR_LINE",
bufferId: state.bufferId,
lineNumber: index,
tokens,
ruleStack,
})
return true
}
return false
}
}

View File

@ -0,0 +1,179 @@
// SyntaxHighlightingReducer.ts
//
// Reducers for handling state changes from ISyntaxHighlightActions
import { IBufferSyntaxHighlightState, ISyntaxHighlightAction, ISyntaxHighlightState, SyntaxHighlightLines } from "./SyntaxHighlightingStore"
import { Reducer } from "redux"
export const reducer: Reducer<ISyntaxHighlightState> = (
state: ISyntaxHighlightState = {
isInsertMode: false,
bufferToHighlights: {},
},
action: ISyntaxHighlightAction,
) => {
let newState = state
switch (action.type) {
case "START_INSERT_MODE":
newState = {
...state,
isInsertMode: true,
}
break
case "END_INSERT_MODE":
newState = {
...state,
isInsertMode: false, // If we're getting a full buffer update, assume we're not in insert mode
}
break
}
return {
...newState,
bufferToHighlights: bufferToHighlightsReducer(state.bufferToHighlights, action),
}
}
export const bufferToHighlightsReducer: Reducer<{ [bufferId: string]: IBufferSyntaxHighlightState }> = (
state: { [bufferId: string]: IBufferSyntaxHighlightState } = {},
action: ISyntaxHighlightAction,
) => {
return {
...state,
[action.bufferId]: bufferReducer(state[action.bufferId], action),
}
}
export const bufferReducer: Reducer<IBufferSyntaxHighlightState> = (
state: IBufferSyntaxHighlightState = {
bufferId: null,
extension: null,
language: null,
topVisibleLine: -1,
bottomVisibleLine: -1,
activeInsertModeLine: -1,
lines: {},
},
action: ISyntaxHighlightAction,
) => {
switch (action.type) {
case "START_INSERT_MODE":
return {
...state,
activeInsertModeLine: -1,
}
case "SYNTAX_UPDATE_BUFFER":
return {
...state,
bufferId: action.bufferId,
language: action.language,
extension: action.extension,
lines: linesReducer(state.lines, action),
}
case "SYNTAX_UPDATE_BUFFER_LINE":
return {
...state,
activeInsertModeLine: action.lineNumber,
lines: linesReducer(state.lines, action),
}
case "SYNTAX_UPDATE_BUFFER_VIEWPORT":
return {
...state,
topVisibleLine: action.topVisibleLine,
bottomVisibleLine: action.bottomVisibleLine,
}
case "SYNTAX_UPDATE_TOKENS_FOR_LINE":
return {
...state,
lines: linesReducer(state.lines, action),
}
default:
return state
}
}
export const linesReducer: Reducer<SyntaxHighlightLines> = (
state: SyntaxHighlightLines = {},
action: ISyntaxHighlightAction,
) => {
switch (action.type) {
case "SYNTAX_UPDATE_TOKENS_FOR_LINE":
{
const newState = {
...state,
}
const originalLine = newState[action.lineNumber]
// If the ruleStack changed, we need to invalidate the next line
const shouldDirtyNextLine = originalLine && originalLine.ruleStack && !originalLine.ruleStack.equals(action.ruleStack)
newState[action.lineNumber] = {
...originalLine,
dirty: false,
tokens: action.tokens,
ruleStack: action.ruleStack,
}
const nextLine = newState[action.lineNumber + 1]
if (shouldDirtyNextLine && nextLine) {
newState[action.lineNumber + 1] = {
...nextLine,
dirty: true,
}
}
return newState
}
case "SYNTAX_UPDATE_BUFFER_LINE":
{
const newState = {
...state,
}
// Set 'dirty' flag for updated line to true
const oldLine = newState[action.lineNumber]
newState[action.lineNumber] = {
tokens: [],
ruleStack: null,
...oldLine,
line: action.line,
dirty: true,
}
return newState
}
case "SYNTAX_UPDATE_BUFFER":
const updatedBufferState: SyntaxHighlightLines = {
...state,
}
for (let i = 0; i < action.lines.length; i++) {
const oldLine = updatedBufferState[i]
const newLine = action.lines[i]
if (oldLine && oldLine.line === newLine) {
continue
}
updatedBufferState[i] = {
tokens: [],
ruleStack: null,
...oldLine,
line: newLine,
dirty: true,
}
}
return updatedBufferState
}
return state
}

View File

@ -0,0 +1,151 @@
/**
* SyntaxHighlighting.ts
*
* Handles enhanced syntax highlighting
*/
import * as types from "vscode-languageserver-types"
import { StackElement } from "vscode-textmate"
import * as Log from "./../../Log"
import * as PeriodicJobs from "./../../PeriodicJobs"
import { configuration } from "./../Configuration"
import { GrammarLoader } from "./GrammarLoader"
import { SyntaxHighlightingPeriodicJob } from "./SyntaxHighlightingPeriodicJob"
import * as Selectors from "./SyntaxHighlightSelectors"
const syntaxHighlightingJobs = new PeriodicJobs.PeriodicJobManager()
export interface ISyntaxHighlightTokenInfo {
scopes: string[]
range: types.Range
}
export interface ISyntaxHighlightLineInfo {
line: string
ruleStack: StackElement
tokens: ISyntaxHighlightTokenInfo[]
dirty: boolean,
}
export interface SyntaxHighlightLines {[key: number]: ISyntaxHighlightLineInfo}
export interface IBufferSyntaxHighlightState {
bufferId: string
language: string
extension: string
// This doesn't work quite right if we have a buffer open in a separate window...
topVisibleLine: number
bottomVisibleLine: number
// When in insert mode, we'll just syntax highlight that line
// Upon leaving insert mode, we'll refresh the whole view
activeInsertModeLine: number
lines: SyntaxHighlightLines
}
export interface ISyntaxHighlightState {
isInsertMode: boolean
bufferToHighlights: {
[bufferId: string]: IBufferSyntaxHighlightState,
}
}
export type ISyntaxHighlightAction = {
type: "SYNTAX_UPDATE_BUFFER",
language: string,
extension: string,
bufferId: string,
lines: string[],
} | {
type: "SYNTAX_UPDATE_BUFFER_LINE",
bufferId: string,
lineNumber: number,
line: string,
} | {
type: "SYNTAX_UPDATE_TOKENS_FOR_LINE",
bufferId: string,
lineNumber: number,
tokens: ISyntaxHighlightTokenInfo[],
ruleStack: StackElement,
} | {
type: "SYNTAX_UPDATE_BUFFER_VIEWPORT",
bufferId: string,
topVisibleLine: number,
bottomVisibleLine: number,
} | {
type: "START_INSERT_MODE",
bufferId: string,
} | {
type: "END_INSERT_MODE",
bufferId: string,
}
import { applyMiddleware, createStore, Store } from "redux"
import { reducer } from "./SyntaxHighlightingReducer"
const grammarLoader = new GrammarLoader()
const updateTokenMiddleware = (store: any) => (next: any) => (action: any) => {
const result: ISyntaxHighlightAction = next(action)
if (action.type === "SYNTAX_UPDATE_BUFFER"
|| action.type === "SYNTAX_UPDATE_BUFFER_LINE"
|| action.type === "SYNTAX_UPDATE_BUFFER_VIEWPORT") {
const state = store.getState()
const bufferId = action.bufferId
const language = state.bufferToHighlights[bufferId].language
const extension = state.bufferToHighlights[bufferId].extension
if (!language || !extension) {
return result
}
grammarLoader.getGrammarForLanguage(language, extension)
.then((grammar) => {
if (!grammar) {
return
}
const buffer = state.bufferToHighlights[bufferId]
if (Object.keys(buffer.lines).length >= configuration.getValue("experimental.editor.textMateHighlighting.maxLines")) {
Log.info("[SyntaxHighlighting - fullBufferUpdateEpic]: Not applying syntax highlighting as the maxLines limit was exceeded")
return
}
const relevantRange = Selectors.getRelevantRange(state, bufferId)
syntaxHighlightingJobs.startJob(new SyntaxHighlightingPeriodicJob(
store as any,
action.bufferId,
grammar,
relevantRange.top,
relevantRange.bottom,
))
})
}
return result
}
const logger = (store: any) => (next: any) => (action: any) => {
// console.log("dispatching action: " + action.type + "|" + JSON.stringify(action))
return next(action)
}
export const createSyntaxHighlightStore = (): Store<ISyntaxHighlightState> => {
const syntaxHighlightStore: Store<ISyntaxHighlightState> = createStore(reducer,
applyMiddleware(updateTokenMiddleware, logger) as any,
)
return syntaxHighlightStore
}

View File

@ -0,0 +1,4 @@
export * from "./Definitions"
export * from "./SyntaxHighlighting"
export * from "./SyntaxHighlightingReducer"
export * from "./SyntaxHighlightingStore"

View File

@ -2,46 +2,8 @@ import * as os from "os"
import * as React from "react"
import * as Colors from "./../Colors"
import { CursorPositioner } from "./CursorPositioner"
require("./QuickInfo.less") // tslint:disable-line no-var-requires
export interface IQuickInfoProps {
visible: boolean
elements: JSX.Element[]
backgroundColor: string
foregroundColor: string
}
export class QuickInfo extends React.PureComponent<IQuickInfoProps, {}> {
public render(): null | JSX.Element {
if (!this.props.elements || !this.props.elements.length) {
return null
}
const borderColorString = Colors.getBorderColor(this.props.backgroundColor, this.props.foregroundColor)
const quickInfoStyle: React.CSSProperties = {
"opacity": this.props.visible ? 1 : 0,
backgroundColor: this.props.backgroundColor,
border: `1px solid ${borderColorString}`,
color: this.props.foregroundColor,
}
return <CursorPositioner beakColor={borderColorString}>
<div key={"quickinfo-container"} className="quickinfo-container enable-mouse">
<div key={"quickInfo"} style={quickInfoStyle} className="quickinfo">
{this.props.elements}
</div>
</div>
</CursorPositioner>
}
}
export interface ITextProps {
text: string
}

View File

@ -68,9 +68,16 @@ const start = (args: string[]) => {
browserWindow.setFullScreen(configuration.getValue("editor.fullScreenOnStart"))
}
configuration.start()
configChange(configuration.getValues()) // initialize values
configuration.onConfigurationChanged.subscribe(configChange)
performance.mark("NeovimInstance.Plugins.Start")
const api = pluginManager.startPlugins()
performance.mark("NeovimInstance.Plugins.End")
configuration.activate(api)
UI.init(pluginManager, parsedArgs._)
ipcRenderer.on("execute-command", (_evt: any, command: string) => {
@ -79,12 +86,6 @@ const start = (args: string[]) => {
createLanguageClientsFromConfiguration(configuration.getValues())
performance.mark("NeovimInstance.Plugins.Start")
const api = pluginManager.startPlugins()
performance.mark("NeovimInstance.Plugins.End")
configuration.activate(api)
AutoClosingPairs.activate(configuration, editorManager, inputManager, languageManager)
checkForUpdates()

View File

@ -0,0 +1,87 @@
import * as assert from "assert"
import * as SyntaxHighlighting from "./../../../src/Services/SyntaxHighlighting"
describe("SyntaxHighlightingReducer", () => {
describe("linesReducer", () => {
describe("SYNTAX_UPDATE_BUFFER", () => {
it("sets buffer lines if they don't exist yet", () => {
const originalState: SyntaxHighlighting.SyntaxHighlightLines = {}
const updateBufferAction: SyntaxHighlighting.ISyntaxHighlightAction = {
type: "SYNTAX_UPDATE_BUFFER",
extension: "ts",
language: "any",
bufferId: "1",
lines: [
"line1",
"line2",
],
}
const newState = SyntaxHighlighting.linesReducer(originalState, updateBufferAction)
assert.deepEqual(newState["0"], {
line: "line1",
dirty: true,
ruleStack: null,
tokens: [],
})
assert.deepEqual(newState["1"], {
line: "line2",
dirty: true,
ruleStack: null,
tokens: [],
})
})
it("only sets changed lines to dirty", () => {
const originalState: SyntaxHighlighting.SyntaxHighlightLines = {
"0": {
ruleStack: null,
tokens: [],
line: "line1",
dirty: false,
},
"1": {
ruleStack: null,
tokens: [],
line: "line2",
dirty: false,
},
}
const updateBufferAction: SyntaxHighlighting.ISyntaxHighlightAction = {
type: "SYNTAX_UPDATE_BUFFER",
extension: "ts",
language: "any",
bufferId: "1",
lines: [
"line1",
"line2_update",
],
}
const newState = SyntaxHighlighting.linesReducer(originalState, updateBufferAction)
assert.deepEqual(newState["0"], {
ruleStack: null,
tokens: [],
line: "line1",
dirty: false,
})
assert.deepEqual(newState["1"], {
ruleStack: null,
tokens: [],
line: "line2_update",
dirty: true,
})
})
})
})
})

View File

@ -7,6 +7,7 @@ module.exports = {
],
target: "electron-renderer",
externals: {
"vscode-textmate": "require('vscode-textmate')",
"vscode-languageserver-types": "require('vscode-languageserver-types')",
"keyboard-layout": "require('keyboard-layout')",
"gifshot": "require('gifshot')"

6
extensions/README.md Normal file
View File

@ -0,0 +1,6 @@
# NOTE TO CONTRIBUTORS
- The majority of these syntaxes (and configurations) are brought from the VSCode repository: https://github.com/Microsoft/vscode
Exceptions:
- reason - brought from the `vscode-reasonml` repository: https://github.com/reasonml-editor/vscode-reasonml

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,550 @@
{
"information_for_contributors": [
"This file has been converted from https://github.com/atom/language-less/blob/master/grammars/less.cson",
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/atom/language-less/commit/b5b3438916bd617fe2b61e090b438c3e27034fc1",
"name": "Less",
"scopeName": "source.css.less",
"fileTypes": [
"less",
"less.erb",
"rc",
"gtkrc",
"gtkrc-2.0",
"themerc"
],
"patterns": [
{
"include": "#strings"
},
{
"captures": {
"1": {
"name": "entity.other.attribute-name.class.mixin.css"
}
},
"match": "(\\.[_a-zA-Z][a-zA-Z0-9_-]*(?=\\())"
},
{
"captures": {
"1": {
"name": "entity.other.attribute-name.class.css"
},
"2": {
"name": "punctuation.definition.entity.css"
},
"4": {
"name": "variable.other.interpolation.less"
}
},
"match": "((\\.)([_a-zA-Z]|(@{[a-zA-Z0-9_-]+}))[a-zA-Z0-9_-]*)"
},
{
"captures": {
"0": {
"name": "entity.other.attribute-name.parent-selector.css"
},
"1": {
"name": "punctuation.definition.entity.css"
}
},
"match": "(&)[a-zA-Z0-9_-]*"
},
{
"begin": "(format|local|url|attr|counter|counters)\\s*(\\()",
"beginCaptures": {
"1": {
"name": "support.function.misc.css"
},
"2": {
"name": "punctuation.section.function.css"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "punctuation.section.function.css"
}
},
"patterns": [
{
"begin": "'",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.css"
}
},
"end": "'",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.css"
}
},
"name": "string.quoted.single.css",
"patterns": [
{
"match": "\\\\.",
"name": "constant.character.escape.css"
}
]
},
{
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.css"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.css"
}
},
"name": "string.quoted.double.css",
"patterns": [
{
"match": "\\\\(\\d{1,6}|.)",
"name": "constant.character.escape.css"
}
]
},
{
"match": "[^'\") \\t]+",
"name": "variable.parameter.misc.css"
}
]
},
{
"match": "(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\\b(?!.*?(?<!@){)",
"name": "constant.other.rgb-value.css"
},
{
"captures": {
"1": {
"name": "entity.other.attribute-name.id"
},
"2": {
"name": "punctuation.definition.entity.css"
},
"4": {
"name": "variable.other.interpolation.less"
}
},
"match": "((#)([_a-zA-Z]|(@{[a-zA-Z0-9_-]+}))[a-zA-Z0-9_-]*)",
"name": "meta.selector.css"
},
{
"begin": "/\\*",
"beginCaptures": {
"0": {
"name": "punctuation.definition.comment.begin.css"
}
},
"end": "\\*/",
"endCaptures": {
"0": {
"name": "punctuation.definition.comment.end.css"
}
},
"name": "comment.block.css"
},
{
"include": "source.css#numeric-values"
},
{
"captures": {
"1": {
"name": "punctuation.definition.begin.entity.css"
},
"2": {
"name": "entity.other.attribute-name.attribute.css"
},
"3": {
"name": "punctuation.separator.operator.css"
},
"4": {
"name": "string.unquoted.attribute-value.css"
},
"5": {
"name": "string.quoted.double.attribute-value.css"
},
"6": {
"name": "punctuation.definition.string.begin.css"
},
"7": {
"name": "punctuation.definition.string.end.css"
},
"8": {
"name": "punctuation.definition.end.entity.css"
}
},
"match": "(?i)(\\[)\\s*(-?[_a-z\\\\[[:^ascii:]]][_a-z0-9\\-\\\\[[:^ascii:]]]*)(?:\\s*([~|^$*]?=)\\s*(?:(-?[_a-z\\\\[[:^ascii:]]][_a-z0-9\\-\\\\[[:^ascii:]]]*)|((?>(['\"])(?:[^\\\\]|\\\\.)*?(\\6)))))?\\s*(\\])",
"name": "meta.attribute-selector.css"
},
{
"begin": "((@)import\\b)",
"beginCaptures": {
"1": {
"name": "keyword.control.at-rule.import.less"
},
"2": {
"name": "punctuation.definition.keyword.less"
}
},
"end": ";",
"endCaptures": {
"0": {
"name": "punctuation.terminator.rule.css"
}
},
"name": "meta.at-rule.import.css",
"patterns": [
{
"match": "(?<=\\(|,|\\s)\\b(reference|optional|once|multiple|less|inline)\\b(?=\\)|,)",
"name": "keyword.control.import.option.less"
},
{
"include": "#brace_round"
},
{
"include": "source.css#commas"
},
{
"include": "#strings"
}
]
},
{
"captures": {
"1": {
"name": "keyword.control.at-rule.fontface.css"
},
"2": {
"name": "punctuation.definition.keyword.css"
}
},
"match": "^\\s*((@)font-face\\b)",
"name": "meta.at-rule.fontface.css"
},
{
"captures": {
"1": {
"name": "keyword.control.at-rule.media.css"
},
"2": {
"name": "punctuation.definition.keyword.css"
}
},
"match": "^\\s*((@)media\\b)",
"name": "meta.at-rule.media.css"
},
{
"include": "source.css#media-features"
},
{
"match": "\\b(tv|tty|screen|projection|print|handheld|embossed|braille|aural|all)\\b",
"name": "support.constant.media-type.media.css"
},
{
"match": "\\b(portrait|landscape)\\b",
"name": "support.constant.property-value.media-property.media.css"
},
{
"captures": {
"1": {
"name": "support.function.less"
}
},
"match": "(\\.[a-zA-Z0-9_-]+)\\s*(;|\\()"
},
{
"begin": "(^[ \\t]+)?(?=//)",
"beginCaptures": {
"1": {
"name": "punctuation.whitespace.comment.leading.less"
}
},
"end": "(?!\\G)",
"patterns": [
{
"begin": "//",
"beginCaptures": {
"0": {
"name": "punctuation.definition.comment.less"
}
},
"end": "\\n",
"name": "comment.line.double-slash.less"
}
]
},
{
"match": "(@|\\-\\-)[\\w-]+(?=\\s*)",
"name": "variable.other.less",
"captures": {
"1": {
"name": "punctuation.definition.variable.less"
}
}
},
{
"include": "#variable_interpolation"
},
{
"begin": "{",
"beginCaptures": {
"0": {
"name": "punctuation.section.property-list.begin.bracket.curly.css"
}
},
"end": "}",
"endCaptures": {
"0": {
"name": "punctuation.section.property-list.end.bracket.curly.css"
}
},
"name": "meta.property-list.css",
"patterns": [
{
"include": "source.css#pseudo-elements"
},
{
"include": "source.css#pseudo-classes"
},
{
"include": "source.css#tag-names"
},
{
"include": "source.css#commas"
},
{
"include": "#variable_interpolation"
},
{
"include": "source.css#property-names"
},
{
"include": "#property_values"
},
{
"include": "$self"
}
]
},
{
"match": "\\!\\s*important",
"name": "keyword.other.important.css"
},
{
"match": "\\*|\\/|\\-|\\+|~|=|<=|>=|<|>",
"name": "keyword.operator.less"
},
{
"match": "\\b(not|and|when)\\b",
"name": "keyword.control.logical.operator.less"
},
{
"include": "source.css#tag-names"
},
{
"match": "(?<![\\w-])[a-z][\\w&&[^A-Z]]*+-[\\w-&&[^A-Z]]+",
"name": "entity.name.tag.custom.css"
},
{
"include": "source.css#pseudo-elements"
},
{
"include": "source.css#pseudo-classes"
},
{
"captures": {
"1": {
"name": "punctuation.section.property-list.begin.css"
},
"2": {
"name": "punctuation.section.property-list.end.css"
}
},
"match": "(\\{)(\\})",
"name": "meta.brace.curly.css"
},
{
"match": "\\{|\\}",
"name": "meta.brace.curly.css"
},
{
"include": "#brace_round"
},
{
"match": "\\[|\\]",
"name": "meta.brace.square.less"
},
{
"match": ";",
"name": "punctuation.terminator.rule.css"
},
{
"match": ":",
"name": "punctuation.separator.key-value.css"
},
{
"match": "\\btrue\\b",
"name": "constant.language.boolean.less"
},
{
"match": "\\bdefault\\b",
"name": "support.function.default.less"
},
{
"match": "\\b(isurl|isstring|isnumber|iskeyword|iscolor)\\b",
"name": "support.function.type-checking.less"
},
{
"match": "\\b(isunit|ispixel|ispercentage|isem)\\b",
"name": "support.function.unit-checking.less"
},
{
"include": "source.css#property-keywords"
},
{
"include": "source.css#color-keywords"
},
{
"include": "source.css#commas"
},
{
"include": "#less_builtin_functions"
},
{
"include": "source.css#functions"
}
],
"repository": {
"variable_interpolation": {
"match": "@{[a-zA-Z0-9_-]+}",
"name": "variable.other.interpolation.less"
},
"strings": {
"patterns": [
{
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.css"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.css"
}
},
"name": "string.quoted.double.css",
"patterns": [
{
"match": "\\\\([0-9A-Fa-f]{1,6}|.)",
"name": "constant.character.escape.css"
},
{
"include": "#variable_interpolation"
}
]
},
{
"begin": "'",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.css"
}
},
"end": "'",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.css"
}
},
"name": "string.quoted.single.css",
"patterns": [
{
"match": "\\\\([0-9A-Fa-f]{1,6}|.)",
"name": "constant.character.escape.css"
},
{
"include": "#variable_interpolation"
}
]
}
]
},
"brace_round": {
"match": "\\(|\\)",
"name": "meta.brace.round.css"
},
"property_values": {
"begin": "(?<!&)(:)\\s*(?!(\\s*{))(?!.*(?<!@){)",
"beginCaptures": {
"1": {
"name": "punctuation.separator.key-value.css"
}
},
"end": "\\s*(;)|\\s*(?=})",
"endCaptures": {
"1": {
"name": "punctuation.terminator.rule.css"
}
},
"contentName": "meta.property-value.css",
"patterns": [
{
"begin": "url(\\()",
"beginCaptures": {
"1": {
"name": "meta.brace.round.css"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "meta.brace.round.css"
}
},
"name": "support.function.any-method.builtin.url.css",
"patterns": [
{
"include": "#strings"
},
{
"match": "(\\b|\\.{0,2}/)[^)]*\\b",
"name": "string.url.css"
}
]
},
{
"include": "source.css#property-keywords"
},
{
"include": "source.css#color-keywords"
},
{
"include": "source.css#commas"
},
{
"include": "#less_builtin_functions"
},
{
"include": "source.css#functions"
},
{
"include": "$self"
}
]
},
"less_builtin_functions": {
"match": "\\b(abs|acos|alpha|argb|asin|atan|average|blue|calc|ceil|color|contrast|convert|convert|cos|darken|data-uri|desaturate|difference|e|escape|exclusion|extract|fade|fadein|fadeout|floor|format|green|greyscale|hardlight|hsl|hsla|hsv|hsva|hsvhue|hsvsaturation|hsvvalue|hue|length|lighten|lightness|luma|max|min|mix|mod|multiply|negation|overlay|percentage|pi|pow|red|replace|round|saturate|saturation|screen|sin|softlight|spin|sqrt|tan|unit)\\b",
"name": "support.function.any-method.builtin.less"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -121,7 +121,8 @@
"typescript": "2.6.1",
"vscode-jsonrpc": "3.5.0",
"vscode-languageserver": "3.5.0",
"vscode-languageserver-types": "3.5.0"
"vscode-languageserver-types": "3.5.0",
"vscode-textmate": "3.2.0"
},
"devDependencies": {
"@types/classnames": "0.0.32",
@ -174,6 +175,7 @@
"react-redux": "5.0.6",
"react-transition-group": "2.2.1",
"redux": "3.7.2",
"redux-observable": "^0.17.0",
"redux-thunk": "2.2.0",
"reselect": "3.0.1",
"rxjs": "5.5.0",

View File

@ -147,7 +147,7 @@ call s:h("Conditional", { "fg": s:purple }) " if, then, else, endif, switch, etc
call s:h("Repeat", { "fg": s:purple }) " for, do, while, etc.
call s:h("Label", { "fg": s:purple }) " case, default, etc.
call s:h("Operator", { "fg": s:purple }) " sizeof", "+", "*", etc.
call s:h("Keyword", { "fg": s:red }) " any other keyword
call s:h("Keyword", { "fg": s:purple }) " any other keyword
call s:h("Exception", { "fg": s:purple }) " try, catch, throw
call s:h("PreProc", { "fg": s:yellow }) " generic Preprocessor
call s:h("Include", { "fg": s:blue }) " preprocessor #include

View File

@ -2357,6 +2357,10 @@ fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
fast-plist@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8"
fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
@ -4104,6 +4108,12 @@ oni-types@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/oni-types/-/oni-types-0.0.4.tgz#97bc435565d5f4c3bdc50bd1700fd24dd5b6704b"
oniguruma@^6.0.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-6.2.1.tgz#a50ee69642844ad1d252685aab187171b06ece04"
dependencies:
nan "^2.0.9"
opencollective@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/opencollective/-/opencollective-1.0.3.tgz#aee6372bc28144583690c3ca8daecfc120dd0ef1"
@ -5015,6 +5025,10 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^0.4.2"
redux-observable@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/redux-observable/-/redux-observable-0.17.0.tgz#23e29e3f3c39204b7ed6a14b67a29e317c03106b"
redux-batched-subscribe@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/redux-batched-subscribe/-/redux-batched-subscribe-0.1.6.tgz#de928602708df7198b4d0c98c7119df993780d59"
@ -6223,6 +6237,13 @@ vscode-nls@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da"
vscode-textmate@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-3.2.0.tgz#87e5ab1ed30463291a73fe28b37a58590a7777dc"
dependencies:
fast-plist "^0.1.2"
oniguruma "^6.0.1"
vscode-uri@1.0.1, vscode-uri@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.1.tgz#11a86befeac3c4aa3ec08623651a3c81a6d0bbc8"