mirror of https://github.com/onivim/oni.git
[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:
parent
a3da2c9876
commit
4dd43453b0
2
PLAN.md
2
PLAN.md
|
@ -1,2 +0,0 @@
|
|||
- How does this work with typing predictions? Seems like there may be artifacts that weren't present before
|
||||
- Any regressions?
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ export class Configuration implements Oni.Configuration {
|
|||
return this._onConfigurationChangedEvent
|
||||
}
|
||||
|
||||
constructor() {
|
||||
public start(): void {
|
||||
Performance.mark("Config.load.start")
|
||||
|
||||
this.applyConfig()
|
||||
|
|
|
@ -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",
|
||||
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./Definitions"
|
||||
export * from "./SyntaxHighlighting"
|
||||
export * from "./SyntaxHighlightingReducer"
|
||||
export * from "./SyntaxHighlightingStore"
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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')"
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
21
yarn.lock
21
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue