Merge remote-tracking branch 'origin/master' into ns-fix-keymap-ctrl-issue

This commit is contained in:
Nathan Sobo
2016-10-24 13:55:33 -06:00
60 changed files with 3581 additions and 2174 deletions

View File

@@ -1 +1 @@
2.7.6
2.7.12

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.12.5"
"atom-package-manager": "1.13.0"
}
}

View File

@@ -28,7 +28,7 @@ while getopts ":wtfvh-:" opt; do
REDIRECT_STDERR=1
EXPECT_OUTPUT=1
;;
foreground|test)
foreground|benchmark|benchmark-test|test)
EXPECT_OUTPUT=1
;;
esac

View File

@@ -0,0 +1,73 @@
/** @babel */
import Chart from 'chart.js'
import glob from 'glob'
import fs from 'fs-plus'
import path from 'path'
export default async function ({test, benchmarkPaths}) {
document.body.style.backgroundColor = '#ffffff'
document.body.style.overflow = 'auto'
let paths = []
for (const benchmarkPath of benchmarkPaths) {
if (fs.isDirectorySync(benchmarkPath)) {
paths = paths.concat(glob.sync(path.join(benchmarkPath, '**', '*.bench.js')))
} else {
paths.push(benchmarkPath)
}
}
while (paths.length > 0) {
const benchmark = require(paths.shift())({test})
let results
if (benchmark instanceof Promise) {
results = await benchmark
} else {
results = benchmark
}
const dataByBenchmarkName = {}
for (const {name, duration, x} of results) {
dataByBenchmarkName[name] = dataByBenchmarkName[name] || {points: []}
dataByBenchmarkName[name].points.push({x, y: duration})
}
const benchmarkContainer = document.createElement('div')
document.body.appendChild(benchmarkContainer)
for (const key in dataByBenchmarkName) {
const data = dataByBenchmarkName[key]
if (data.points.length > 1) {
const canvas = document.createElement('canvas')
benchmarkContainer.appendChild(canvas)
const chart = new Chart(canvas, {
type: 'line',
data: {
datasets: [{label: key, fill: false, data: data.points}]
},
options: {
showLines: false,
scales: {xAxes: [{type: 'linear', position: 'bottom'}]}
}
})
const textualOutput = `${key}:\n\n` + data.points.map((p) => `${p.x}\t${p.y}`).join('\n')
console.log(textualOutput)
} else {
const title = document.createElement('h2')
title.textContent = key
benchmarkContainer.appendChild(title)
const duration = document.createElement('p')
duration.textContent = `${data.points[0].y}ms`
benchmarkContainer.appendChild(duration)
const textualOutput = `${key}: ${data.points[0].y}`
console.log(textualOutput)
}
global.atom.reset()
}
}
return 0
}

View File

@@ -0,0 +1,90 @@
/** @babel */
import {TextEditor, TextBuffer} from 'atom'
const MIN_SIZE_IN_KB = 0 * 1024
const MAX_SIZE_IN_KB = 10 * 1024
const SIZE_STEP_IN_KB = 1024
const LINE_TEXT = 'Lorem ipsum dolor sit amet\n'
const TEXT = LINE_TEXT.repeat(Math.ceil(MAX_SIZE_IN_KB * 1024 / LINE_TEXT.length))
export default async function ({test}) {
const data = []
const workspaceElement = atom.views.getView(atom.workspace)
document.body.appendChild(workspaceElement)
atom.packages.loadPackages()
await atom.packages.activate()
for (let pane of atom.workspace.getPanes()) {
pane.destroy()
}
for (let sizeInKB = MIN_SIZE_IN_KB; sizeInKB < MAX_SIZE_IN_KB; sizeInKB += SIZE_STEP_IN_KB) {
const text = TEXT.slice(0, sizeInKB * 1024)
console.log(text.length / 1024)
let t0 = window.performance.now()
const buffer = new TextBuffer(text)
const editor = new TextEditor({buffer, largeFileMode: true})
atom.workspace.getActivePane().activateItem(editor)
let t1 = window.performance.now()
data.push({
name: 'Opening a large file',
x: sizeInKB,
duration: t1 - t0
})
const tickDurations = []
for (let i = 0; i < 20; i++) {
await timeout(50)
t0 = window.performance.now()
await timeout(0)
t1 = window.performance.now()
tickDurations[i] = t1 - t0
}
data.push({
name: 'Max time event loop was blocked after opening a large file',
x: sizeInKB,
duration: Math.max(...tickDurations)
})
t0 = window.performance.now()
editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({
top: 100,
left: 30
}))
t1 = window.performance.now()
data.push({
name: 'Clicking the editor after opening a large file',
x: sizeInKB,
duration: t1 - t0
})
t0 = window.performance.now()
editor.element.setScrollTop(editor.element.getScrollTop() + 100)
t1 = window.performance.now()
data.push({
name: 'Scrolling down after opening a large file',
x: sizeInKB,
duration: t1 - t0
})
editor.destroy()
buffer.destroy()
await timeout(10000)
}
workspaceElement.remove()
return data
}
function timeout (duration) {
return new Promise((resolve) => setTimeout(resolve, duration))
}

View File

@@ -26,7 +26,7 @@ atom-text-editor {
// background-color: hsl(180, 24%, 12%);
}
// To style other content in the text editor's shadow DOM, use the ::shadow expression
atom-text-editor::shadow .cursor {
// style UI elements inside atom-text-editor
atom-text-editor .cursor {
// border-color: red;
}

View File

@@ -147,6 +147,7 @@
{ label: 'Open In Dev Mode…', command: 'application:open-dev' }
{ label: 'Reload Window', command: 'window:reload' }
{ label: 'Run Package Specs', command: 'window:run-package-specs' }
{ label: 'Run Benchmarks', command: 'window:run-benchmarks' }
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
]
}

View File

@@ -15,11 +15,12 @@
"electronVersion": "1.3.6",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "7.0.6-beta0",
"atom-keymap": "7.0.7",
"atom-ui": "0.4.1",
"babel-core": "5.8.38",
"cached-run-in-this-context": "0.4.1",
"chai": "3.5.0",
"chart.js": "^2.3.0",
"clear-cut": "^2.0.1",
"coffee-script": "1.11.1",
"color": "^0.7.3",
@@ -32,6 +33,7 @@
"fstream": "0.1.24",
"fuzzaldrin": "^2.1",
"git-utils": "^4.1.2",
"glob": "^7.1.1",
"grim": "1.5.0",
"jasmine-json": "~0.0",
"jasmine-tagged": "^1.1.4",
@@ -45,6 +47,8 @@
"nslog": "^3",
"oniguruma": "6.1.0",
"pathwatcher": "~6.5",
"postcss": "5.2.4",
"postcss-selector-parser": "2.2.1",
"property-accessors": "^1.1.3",
"random-words": "0.0.1",
"resolve": "^1.1.6",
@@ -58,62 +62,62 @@
"sinon": "1.17.4",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "9.3.0",
"text-buffer": "9.4.0",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"winreg": "^1.2.1",
"yargs": "^3.23.0"
},
"packageDependencies": {
"atom-dark-syntax": "0.27.0",
"atom-dark-ui": "0.52.0",
"atom-light-syntax": "0.28.0",
"atom-light-ui": "0.45.0",
"base16-tomorrow-dark-theme": "1.3.0",
"base16-tomorrow-light-theme": "1.3.0",
"one-dark-ui": "1.6.2",
"one-light-ui": "1.6.2",
"one-dark-syntax": "1.5.0",
"one-light-syntax": "1.5.0",
"solarized-dark-syntax": "1.0.5",
"solarized-light-syntax": "1.0.5",
"atom-dark-syntax": "0.28.0",
"atom-dark-ui": "0.53.0",
"atom-light-syntax": "0.29.0",
"atom-light-ui": "0.46.0",
"base16-tomorrow-dark-theme": "1.4.0",
"base16-tomorrow-light-theme": "1.4.0",
"one-dark-ui": "1.7.0",
"one-light-ui": "1.7.0",
"one-dark-syntax": "1.6.0",
"one-light-syntax": "1.6.0",
"solarized-dark-syntax": "1.1.1",
"solarized-light-syntax": "1.1.1",
"about": "1.7.0",
"archive-view": "0.62.0",
"autocomplete-atom-api": "0.10.0",
"autocomplete-css": "0.13.1",
"autocomplete-css": "0.14.1",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.31.4",
"autocomplete-plus": "2.33.1",
"autocomplete-snippets": "1.11.0",
"autoflow": "0.27.0",
"autosave": "0.23.1",
"autosave": "0.23.2",
"background-tips": "0.26.1",
"bookmarks": "0.42.0",
"bookmarks": "0.43.1",
"bracket-matcher": "0.82.2",
"command-palette": "0.39.0",
"deprecation-cop": "0.54.1",
"command-palette": "0.39.1",
"deprecation-cop": "0.55.1",
"dev-live-reload": "0.47.0",
"encoding-selector": "0.22.0",
"exception-reporting": "0.40.0",
"find-and-replace": "0.202.0",
"find-and-replace": "0.203.0",
"fuzzy-finder": "1.4.0",
"git-diff": "1.1.0",
"go-to-line": "0.31.0",
"git-diff": "1.2.0",
"go-to-line": "0.31.1",
"grammar-selector": "0.48.2",
"image-view": "0.60.0",
"incompatible-packages": "0.26.1",
"keybinding-resolver": "0.35.0",
"line-ending-selector": "0.5.0",
"link": "0.31.2",
"markdown-preview": "0.158.8",
"markdown-preview": "0.159.1",
"metrics": "1.0.0",
"notifications": "0.65.1",
"open-on-github": "1.2.1",
"package-generator": "1.0.1",
"settings-view": "0.243.1",
"package-generator": "1.0.2",
"settings-view": "0.244.0",
"snippets": "1.0.3",
"spell-check": "0.68.4",
"spell-check": "0.68.5",
"status-bar": "1.6.0",
"styleguide": "0.47.2",
"styleguide": "0.47.3",
"symbols-view": "0.113.1",
"tabs": "0.103.0",
"timecop": "0.33.2",
@@ -121,10 +125,10 @@
"update-package-dependencies": "0.10.0",
"welcome": "0.35.1",
"whitespace": "0.35.0",
"wrap-guide": "0.38.2",
"wrap-guide": "0.39.0",
"language-c": "0.54.0",
"language-clojure": "0.22.1",
"language-coffee-script": "0.48.0",
"language-coffee-script": "0.48.1",
"language-csharp": "0.12.1",
"language-css": "0.40.1",
"language-gfm": "0.88.0",
@@ -161,8 +165,6 @@
"test": "node script/test"
},
"standard": {
"ignore": [],
"parser": "babel-eslint",
"globals": [
"atom",
"afterEach",

View File

@@ -5,14 +5,16 @@ SET WAIT=
SET PSARGS=%*
FOR %%a IN (%*) DO (
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--benchmark" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--benchmark-test" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES
IF /I "%%a"=="-w" (
SET EXPECT_OUTPUT=YES
SET WAIT=YES

View File

@@ -12,6 +12,7 @@ const includePathInPackagedApp = require('./include-path-in-packaged-app')
module.exports = function () {
console.log(`Copying assets to ${CONFIG.intermediateAppPath}`);
let srcPaths = [
path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'),
path.join(CONFIG.repositoryRootPath, 'dot-atom'),
path.join(CONFIG.repositoryRootPath, 'exports'),
path.join(CONFIG.repositoryRootPath, 'node_modules'),

View File

@@ -25,6 +25,7 @@ module.exports = function () {
function getPathsToTranspile () {
let paths = []
paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js')))
paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js')))
paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js')))
for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) {

View File

@@ -4,7 +4,6 @@
"dependencies": {
"async": "2.0.1",
"babel-core": "5.8.38",
"babel-eslint": "6.1.2",
"coffeelint": "1.15.7",
"colors": "1.1.2",
"csslint": "1.0.2",
@@ -24,7 +23,7 @@
"runas": "3.1.1",
"season": "5.3.0",
"semver": "5.3.0",
"standard": "6.0.0",
"standard": "8.4.0",
"sync-request": "3.0.1",
"tello": "1.0.5",
"webdriverio": "2.4.5",

View File

@@ -83,9 +83,19 @@ for (let packageName in CONFIG.appMetadata.packageDependencies) {
})
}
function runBenchmarkTests (callback) {
const benchmarksPath = path.join(CONFIG.repositoryRootPath, 'benchmarks')
const testArguments = ['--benchmark-test', benchmarksPath]
console.log('Executing benchmark tests'.bold.green)
const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit'})
cp.on('error', error => { callback(error) })
cp.on('close', exitCode => { callback(null, exitCode) })
}
let testSuitesToRun
if (process.platform === 'darwin') {
testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests].concat(packageTestSuites)
testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites)
} else {
testSuitesToRun = [runCoreMainProcessTests]
}

View File

@@ -97,7 +97,7 @@ class AtomReporter
if @failedCount is 1
@message.textContent = "#{@failedCount} failure"
else
@message.textConent = "#{@failedCount} failures"
@message.textContent = "#{@failedCount} failures"
reportSuiteResults: (suite) ->
@@ -110,42 +110,6 @@ class AtomReporter
reportSpecStarting: (spec) ->
@specStarted(spec)
addDeprecations: (spec) ->
deprecations = grim.getDeprecations()
@deprecationCount += deprecations.length
@deprecations.style.display = '' if @deprecationCount > 0
if @deprecationCount is 1
@deprecationStatus.textContent = "1 deprecation"
else
@deprecationStatus.textContent = "#{@deprecationCount} deprecations"
for deprecation in deprecations
@deprecationList.appendChild(@buildDeprecationElement(spec, deprecation))
grim.clearDeprecations()
buildDeprecationElement: (spec, deprecation) ->
div = document.createElement('div')
div.className = 'padded'
div.innerHTML = """
<div class="result-message fail deprecation-message">
#{marked(deprecation.message)}
</div>
"""
for stack in deprecation.getStacks()
fullStack = stack.map ({functionName, location}) ->
if functionName is '<unknown>'
" at #{location}"
else
" at #{functionName} (#{location})"
pre = document.createElement('pre')
pre.className = 'stack-trace padded'
pre.textContent = formatStackTrace(spec, deprecation.message, fullStack.join('\n'))
div.appendChild(pre)
div
handleEvents: ->
listen document, 'click', '.spec-toggle', (event) ->
specFailures = event.currentTarget.parentElement.querySelector('.spec-failures')
@@ -273,7 +237,6 @@ class AtomReporter
specView = new SpecResultView(spec)
specView.attach()
@failedCount++
@addDeprecations(spec)
class SuiteResultView
constructor: (@suite) ->

View File

@@ -40,11 +40,13 @@ describe "LinesYardstick", ->
mockLineNodesProvider =
lineNodesById: {}
lineIdForScreenRow: (screenRow) ->
editor.screenLineForScreenRow(screenRow).id
editor.screenLineForScreenRow(screenRow)?.id
lineNodeForScreenRow: (screenRow) ->
@lineNodesById[@lineIdForScreenRow(screenRow)] ?= buildLineNode(screenRow)
if id = @lineIdForScreenRow(screenRow)
@lineNodesById[id] ?= buildLineNode(screenRow)
textNodesForScreenRow: (screenRow) ->
lineNode = @lineNodeForScreenRow(screenRow)
@@ -68,7 +70,7 @@ describe "LinesYardstick", ->
font-size: 12px;
font-family: monospace;
}
.function {
.syntax--function {
font-size: 16px
}
"""
@@ -142,7 +144,7 @@ describe "LinesYardstick", ->
font-size: 12px;
font-family: monospace;
}
.function {
.syntax--function {
font-size: 16px
}
"""

View File

@@ -105,10 +105,10 @@ beforeEach ->
addCustomMatchers(this)
afterEach ->
ensureNoDeprecatedFunctionCalls()
ensureNoDeprecatedStylesheets()
atom.reset()
document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent
warnIfLeakingPathSubscriptions()
waits(0) # yield to ui thread to make screen update more frequently
@@ -118,8 +118,9 @@ warnIfLeakingPathSubscriptions = ->
console.error("WARNING: Leaking subscriptions for paths: " + watchedPaths.join(", "))
pathwatcher.closeAllWatchers()
ensureNoDeprecatedFunctionsCalled = ->
deprecations = Grim.getDeprecations()
ensureNoDeprecatedFunctionCalls = ->
deprecations = _.clone(Grim.getDeprecations())
Grim.clearDeprecations()
if deprecations.length > 0
originalPrepareStackTrace = Error.prepareStackTrace
Error.prepareStackTrace = (error, stack) ->
@@ -136,9 +137,19 @@ ensureNoDeprecatedFunctionsCalled = ->
error = new Error("Deprecated function(s) #{deprecations.map(({originName}) -> originName).join ', '}) were called.")
error.stack
Error.prepareStackTrace = originalPrepareStackTrace
throw error
ensureNoDeprecatedStylesheets = ->
deprecations = _.clone(atom.styles.getDeprecations())
atom.styles.clearDeprecations()
for sourcePath, deprecation of deprecations
title =
if sourcePath isnt 'undefined'
"Deprecated stylesheet at '#{sourcePath}':"
else
"Deprecated stylesheet:"
throw new Error("#{title}\n#{deprecation.message}")
emitObject = jasmine.StringPrettyPrinter.prototype.emitObject
jasmine.StringPrettyPrinter.prototype.emitObject = (obj) ->
if obj.inspect
@@ -154,12 +165,15 @@ jasmine.attachToDOM = (element) ->
jasmineContent = document.querySelector('#jasmine-content')
jasmineContent.appendChild(element) unless jasmineContent.contains(element)
deprecationsSnapshot = null
grimDeprecationsSnapshot = null
stylesDeprecationsSnapshot = null
jasmine.snapshotDeprecations = ->
deprecationsSnapshot = _.clone(Grim.deprecations)
grimDeprecationsSnapshot = _.clone(Grim.deprecations)
stylesDeprecationsSnapshot = _.clone(atom.styles.deprecationsBySourcePath)
jasmine.restoreDeprecationsSnapshot = ->
Grim.deprecations = deprecationsSnapshot
Grim.deprecations = grimDeprecationsSnapshot
atom.styles.deprecationsBySourcePath = stylesDeprecationsSnapshot
jasmine.useRealClock = ->
jasmine.unspy(window, 'setTimeout')

View File

@@ -1,68 +0,0 @@
StyleManager = require '../src/style-manager'
describe "StyleManager", ->
[manager, addEvents, removeEvents, updateEvents] = []
beforeEach ->
manager = new StyleManager(configDirPath: atom.getConfigDirPath())
addEvents = []
removeEvents = []
updateEvents = []
manager.onDidAddStyleElement (event) -> addEvents.push(event)
manager.onDidRemoveStyleElement (event) -> removeEvents.push(event)
manager.onDidUpdateStyleElement (event) -> updateEvents.push(event)
describe "::addStyleSheet(source, params)", ->
it "adds a stylesheet based on the given source and returns a disposable allowing it to be removed", ->
disposable = manager.addStyleSheet("a {color: red;}")
expect(addEvents.length).toBe 1
expect(addEvents[0].textContent).toBe "a {color: red;}"
styleElements = manager.getStyleElements()
expect(styleElements.length).toBe 1
expect(styleElements[0].textContent).toBe "a {color: red;}"
disposable.dispose()
expect(removeEvents.length).toBe 1
expect(removeEvents[0].textContent).toBe "a {color: red;}"
expect(manager.getStyleElements().length).toBe 0
describe "when a sourcePath parameter is specified", ->
it "ensures a maximum of one style element for the given source path, updating a previous if it exists", ->
disposable1 = manager.addStyleSheet("a {color: red;}", sourcePath: '/foo/bar')
expect(addEvents.length).toBe 1
expect(addEvents[0].getAttribute('source-path')).toBe '/foo/bar'
disposable2 = manager.addStyleSheet("a {color: blue;}", sourcePath: '/foo/bar')
expect(addEvents.length).toBe 1
expect(updateEvents.length).toBe 1
expect(updateEvents[0].getAttribute('source-path')).toBe '/foo/bar'
expect(updateEvents[0].textContent).toBe "a {color: blue;}"
disposable2.dispose()
addEvents = []
manager.addStyleSheet("a {color: yellow;}", sourcePath: '/foo/bar')
expect(addEvents.length).toBe 1
expect(addEvents[0].getAttribute('source-path')).toBe '/foo/bar'
expect(addEvents[0].textContent).toBe "a {color: yellow;}"
describe "when a priority parameter is specified", ->
it "inserts the style sheet based on the priority", ->
manager.addStyleSheet("a {color: red}", priority: 1)
manager.addStyleSheet("a {color: blue}", priority: 0)
manager.addStyleSheet("a {color: green}", priority: 2)
manager.addStyleSheet("a {color: yellow}", priority: 1)
expect(manager.getStyleElements().map (elt) -> elt.textContent).toEqual [
"a {color: blue}"
"a {color: red}"
"a {color: yellow}"
"a {color: green}"
]

137
spec/style-manager-spec.js Normal file
View File

@@ -0,0 +1,137 @@
const StyleManager = require('../src/style-manager')
describe('StyleManager', () => {
let [styleManager, addEvents, removeEvents, updateEvents] = []
beforeEach(() => {
styleManager = new StyleManager({configDirPath: atom.getConfigDirPath()})
addEvents = []
removeEvents = []
updateEvents = []
styleManager.onDidAddStyleElement((event) => { addEvents.push(event) })
styleManager.onDidRemoveStyleElement((event) => { removeEvents.push(event) })
styleManager.onDidUpdateStyleElement((event) => { updateEvents.push(event) })
})
describe('::addStyleSheet(source, params)', () => {
it('adds a style sheet based on the given source and returns a disposable allowing it to be removed', () => {
const disposable = styleManager.addStyleSheet('a {color: red}')
expect(addEvents.length).toBe(1)
expect(addEvents[0].textContent).toBe('a {color: red}')
const styleElements = styleManager.getStyleElements()
expect(styleElements.length).toBe(1)
expect(styleElements[0].textContent).toBe('a {color: red}')
disposable.dispose()
expect(removeEvents.length).toBe(1)
expect(removeEvents[0].textContent).toBe('a {color: red}')
expect(styleManager.getStyleElements().length).toBe(0)
})
describe('atom-text-editor shadow DOM selectors upgrades', () => {
beforeEach(() => {
// attach styles element to the DOM to parse CSS rules
styleManager.onDidAddStyleElement((styleElement) => { jasmine.attachToDOM(styleElement) })
})
it('removes the ::shadow pseudo-element from atom-text-editor selectors', () => {
styleManager.addStyleSheet(`
atom-text-editor::shadow .class-1, atom-text-editor::shadow .class-2 { color: red }
atom-text-editor::shadow > .class-3 { color: yellow }
atom-text-editor .class-4 { color: blue }
another-element::shadow .class-5 { color: white }
atom-text-editor[data-grammar*=\"js\"]::shadow .class-6 { color: green; }
atom-text-editor[mini].is-focused::shadow .class-7 { color: green; }
`)
expect(Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map((r) => r.selectorText)).toEqual([
'atom-text-editor .class-1, atom-text-editor .class-2',
'atom-text-editor > .class-3',
'atom-text-editor .class-4',
'another-element::shadow .class-5',
'atom-text-editor[data-grammar*=\"js\"] .class-6',
'atom-text-editor[mini].is-focused .class-7'
])
})
describe('when a selector targets the atom-text-editor shadow DOM', () => {
it('prepends "--syntax" to class selectors matching a grammar scope name and not already starting with "syntax--"', () => {
styleManager.addStyleSheet(`
.class-1 { color: red }
.source > .js, .source.coffee { color: green }
.syntax--source { color: gray }
#id-1 { color: blue }
`, {context: 'atom-text-editor'})
expect(Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map((r) => r.selectorText)).toEqual([
'.class-1',
'.syntax--source > .syntax--js, .syntax--source.syntax--coffee',
'.syntax--source',
'#id-1'
])
styleManager.addStyleSheet(`
.source > .js, .source.coffee { color: green }
atom-text-editor::shadow .source > .js { color: yellow }
atom-text-editor[mini].is-focused::shadow .source > .js { color: gray }
atom-text-editor .source > .js { color: red }
`)
expect(Array.from(styleManager.getStyleElements()[1].sheet.cssRules).map((r) => r.selectorText)).toEqual([
'.source > .js, .source.coffee',
'atom-text-editor .syntax--source > .syntax--js',
'atom-text-editor[mini].is-focused .syntax--source > .syntax--js',
'atom-text-editor .source > .js'
])
})
})
it('replaces ":host" with "atom-text-editor" only when the context of a style sheet is "atom-text-editor"', () => {
styleManager.addStyleSheet(':host .class-1, :host .class-2 { color: red; }')
expect(Array.from(styleManager.getStyleElements()[0].sheet.cssRules).map((r) => r.selectorText)).toEqual([
':host .class-1, :host .class-2'
])
styleManager.addStyleSheet(':host .class-1, :host .class-2 { color: red; }', {context: 'atom-text-editor'})
expect(Array.from(styleManager.getStyleElements()[1].sheet.cssRules).map((r) => r.selectorText)).toEqual([
'atom-text-editor .class-1, atom-text-editor .class-2'
])
})
it('does not throw exceptions on rules with no selectors', () => {
styleManager.addStyleSheet('@media screen {font-size: 10px}', {context: 'atom-text-editor'})
})
})
describe('when a sourcePath parameter is specified', () => {
it('ensures a maximum of one style element for the given source path, updating a previous if it exists', () => {
const disposable1 = styleManager.addStyleSheet('a {color: red}', {sourcePath: '/foo/bar'})
expect(addEvents.length).toBe(1)
expect(addEvents[0].getAttribute('source-path')).toBe('/foo/bar')
const disposable2 = styleManager.addStyleSheet('a {color: blue}', {sourcePath: '/foo/bar'})
expect(addEvents.length).toBe(1)
expect(updateEvents.length).toBe(1)
expect(updateEvents[0].getAttribute('source-path')).toBe('/foo/bar')
expect(updateEvents[0].textContent).toBe('a {color: blue}')
disposable2.dispose()
addEvents = []
styleManager.addStyleSheet('a {color: yellow}', {sourcePath: '/foo/bar'})
expect(addEvents.length).toBe(1)
expect(addEvents[0].getAttribute('source-path')).toBe('/foo/bar')
expect(addEvents[0].textContent).toBe('a {color: yellow}')
})
})
describe('when a priority parameter is specified', () => {
it('inserts the style sheet based on the priority', () => {
styleManager.addStyleSheet('a {color: red}', {priority: 1})
styleManager.addStyleSheet('a {color: blue}', {priority: 0})
styleManager.addStyleSheet('a {color: green}', {priority: 2})
styleManager.addStyleSheet('a {color: yellow}', {priority: 1})
expect(styleManager.getStyleElements().map((elt) => elt.textContent)).toEqual([
'a {color: blue}',
'a {color: red}',
'a {color: yellow}',
'a {color: green}'
])
})
})
})
})

View File

@@ -77,39 +77,3 @@ describe "StylesElement", ->
expect(element.children.length).toBe 2
expect(element.children[0].textContent).toBe "a {color: red;}"
expect(element.children[1].textContent).toBe "a {color: blue;}"
describe "atom-text-editor shadow DOM selector upgrades", ->
beforeEach ->
element.setAttribute('context', 'atom-text-editor')
spyOn(console, 'warn')
it "upgrades selectors containing .editor-colors", ->
atom.styles.addStyleSheet(".editor-colors {background: black;}", context: 'atom-text-editor')
expect(element.firstChild.sheet.cssRules[0].selectorText).toBe ':host'
it "upgrades selectors containing .editor", ->
atom.styles.addStyleSheet """
.editor {background: black;}
.editor.mini {background: black;}
.editor:focus {background: black;}
""", context: 'atom-text-editor'
expect(element.firstChild.sheet.cssRules[0].selectorText).toBe ':host'
expect(element.firstChild.sheet.cssRules[1].selectorText).toBe ':host(.mini)'
expect(element.firstChild.sheet.cssRules[2].selectorText).toBe ':host(:focus)'
it "defers selector upgrade until the element is attached", ->
element = new StylesElement
element.initialize(atom.styles)
element.setAttribute('context', 'atom-text-editor')
atom.styles.addStyleSheet ".editor {background: black;}", context: 'atom-text-editor'
expect(element.firstChild.sheet).toBeNull()
document.querySelector('#jasmine-content').appendChild(element)
expect(element.firstChild.sheet.cssRules[0].selectorText).toBe ':host'
it "does not throw exceptions on rules with no selectors", ->
atom.styles.addStyleSheet """
@media screen {font-size: 10px;}
""", context: 'atom-text-editor'

View File

@@ -509,7 +509,7 @@ describe('TextEditorComponent', function () {
it('displays newlines as their own token outside of the other tokens\' scopeDescriptor', function () {
editor.setText('let\n')
runAnimationFrames()
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="source js"><span class="storage type var js">let</span><span class="invisible-character eol">' + invisibles.eol + '</span></span>')
expect(component.lineNodeForScreenRow(0).innerHTML).toBe('<span class="syntax--source syntax--js"><span class="syntax--storage syntax--type syntax--var syntax--js">let</span><span class="invisible-character eol">' + invisibles.eol + '</span></span>')
})
it('displays trailing carriage returns using a visible, non-empty value', function () {
@@ -544,20 +544,20 @@ describe('TextEditorComponent', function () {
normalizeLineEndings: false
})
runAnimationFrames()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="syntax--source syntax--js"><span class="invisible-character eol indent-guide">CE</span></span>')
editor.setTabLength(3)
runAnimationFrames()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="syntax--source syntax--js"><span class="invisible-character eol indent-guide">CE</span></span>')
editor.setTabLength(1)
runAnimationFrames()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="syntax--source syntax--js"><span class="invisible-character eol indent-guide">CE</span></span>')
editor.setTextInBufferRange([[9, 0], [9, Infinity]], ' ')
editor.setTextInBufferRange([[11, 0], [11, Infinity]], ' ')
runAnimationFrames()
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="source js"><span class="invisible-character eol indent-guide">CE</span></span>')
expect(component.lineNodeForScreenRow(10).innerHTML).toBe('<span class="syntax--source syntax--js"><span class="invisible-character eol indent-guide">CE</span></span>')
})
describe('when soft wrapping is enabled', function () {
@@ -1252,7 +1252,7 @@ describe('TextEditorComponent', function () {
let cursor = componentNode.querySelector('.cursor')
let cursorRect = cursor.getBoundingClientRect()
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.storage.type.function.js').firstChild
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.syntax--storage.syntax--type.syntax--function.syntax--js').firstChild
let range = document.createRange()
range.setStart(cursorLocationTextNode, 0)
range.setEnd(cursorLocationTextNode, 1)
@@ -1269,7 +1269,7 @@ describe('TextEditorComponent', function () {
let cursor = componentNode.querySelector('.cursor')
let cursorRect = cursor.getBoundingClientRect()
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.source.js').childNodes[2]
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.syntax--source.syntax--js').childNodes[2]
let range = document.createRange(cursorLocationTextNode)
range.setStart(cursorLocationTextNode, 0)
range.setEnd(cursorLocationTextNode, 1)
@@ -1294,14 +1294,14 @@ describe('TextEditorComponent', function () {
editor.setCursorScreenPosition([0, 16])
runAnimationFrames(true)
atom.styles.addStyleSheet('.function.js {\n font-weight: bold;\n}', {
atom.styles.addStyleSheet('.syntax--function.syntax--js {\n font-weight: bold;\n}', {
context: 'atom-text-editor'
})
runAnimationFrames(true)
let cursor = componentNode.querySelector('.cursor')
let cursorRect = cursor.getBoundingClientRect()
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.storage.type.function.js').firstChild
let cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.syntax--storage.syntax--type.syntax--function.syntax--js').firstChild
let range = document.createRange()
range.setStart(cursorLocationTextNode, 0)
range.setEnd(cursorLocationTextNode, 1)
@@ -1772,32 +1772,32 @@ describe('TextEditorComponent', function () {
let [item3, blockDecoration3] = createBlockDecorationBeforeScreenRow(4, {className: "decoration-3"})
let [item4, blockDecoration4] = createBlockDecorationBeforeScreenRow(7, {className: "decoration-4"})
let [item5, blockDecoration5] = createBlockDecorationAfterScreenRow(7, {className: "decoration-5"})
let [item6, blockDecoration6] = createBlockDecorationAfterScreenRow(12, {className: "decoration-6"})
atom.styles.addStyleSheet(
`atom-text-editor .decoration-1 { width: 30px; height: 80px; }
atom-text-editor .decoration-2 { width: 30px; height: 40px; }
atom-text-editor .decoration-3 { width: 30px; height: 100px; }
atom-text-editor .decoration-4 { width: 30px; height: 120px; }
atom-text-editor .decoration-5 { width: 30px; height: 42px; }`,
atom-text-editor .decoration-5 { width: 30px; height: 42px; }
atom-text-editor .decoration-6 { width: 30px; height: 22px; }`,
{context: 'atom-text-editor'}
)
runAnimationFrames()
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
expect(verticalScrollbarNode.scrollHeight).toBe(editor.getScreenLineCount() * editor.getLineHeightInPixels() + 80 + 40 + 100 + 120 + 42 + 22)
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 80 + 40 + "px")
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + "px")
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBe(item1)
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-6")).toBeNull()
expect(item1.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 0)
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 2 + 80)
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 4 + 80 + 40)
@@ -1805,24 +1805,21 @@ describe('TextEditorComponent', function () {
editor.setCursorScreenPosition([0, 0])
editor.insertNewline()
blockDecoration1.destroy()
runAnimationFrames()
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
expect(verticalScrollbarNode.scrollHeight).toBe(editor.getScreenLineCount() * editor.getLineHeightInPixels() + 40 + 100 + 120 + 42 + 22)
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 40 + "px")
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-6")).toBeNull()
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40)
@@ -1833,52 +1830,71 @@ describe('TextEditorComponent', function () {
runAnimationFrames() // causes the DOM to update and to retrieve new styles
runAnimationFrames() // applies the changes
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
expect(verticalScrollbarNode.scrollHeight).toBe(editor.getScreenLineCount() * editor.getLineHeightInPixels() + 60 + 100 + 120 + 42 + 22)
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 60 + "px")
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-6")).toBeNull()
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60)
item2.style.height = "20px"
wrapperNode.invalidateBlockDecorationDimensions(blockDecoration2)
runAnimationFrames()
runAnimationFrames()
runAnimationFrames() // causes the DOM to update and to retrieve new styles
runAnimationFrames() // applies the changes
expect(component.getDomNode().querySelectorAll(".line").length).toBe(9)
expect(verticalScrollbarNode.scrollHeight).toBe(editor.getScreenLineCount() * editor.getLineHeightInPixels() + 20 + 100 + 120 + 42 + 22)
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 20 + "px")
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBe(item4)
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBe(item5)
expect(component.getTopmostDOMNode().querySelector(".decoration-6")).toBeNull()
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 20)
expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100)
expect(item5.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100 + 120 + lineHeightInPixels)
item6.style.height = "33px"
wrapperNode.invalidateBlockDecorationDimensions(blockDecoration6)
runAnimationFrames() // causes the DOM to update and to retrieve new styles
runAnimationFrames() // applies the changes
expect(component.getDomNode().querySelectorAll(".line").length).toBe(9)
expect(verticalScrollbarNode.scrollHeight).toBe(editor.getScreenLineCount() * editor.getLineHeightInPixels() + 20 + 100 + 120 + 42 + 33)
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 20 + "px")
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBe(item4)
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBe(item5)
expect(component.getTopmostDOMNode().querySelector(".decoration-6")).toBeNull()
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 20)
expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100)
expect(item5.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100 + 120 + lineHeightInPixels)
})
it("correctly sets screen rows on <content> elements, both initially and when decorations move", function () {
it("correctly sets screen rows on block decoration and ruler nodes, both initially and when decorations move", function () {
let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
atom.styles.addStyleSheet(
'atom-text-editor .decoration-1 { width: 30px; height: 80px; }',
@@ -1886,42 +1902,37 @@ describe('TextEditorComponent', function () {
)
runAnimationFrames()
let tileNode, contentElements
tileNode = component.tileNodesForLines()[0]
contentElements = tileNode.querySelectorAll("content")
expect(contentElements.length).toBe(1)
expect(contentElements[0].dataset.screenRow).toBe("0")
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
const line0 = component.lineNodeForScreenRow(0)
expect(item.previousSibling.dataset.screenRow).toBe("0")
expect(item.dataset.screenRow).toBe("0")
expect(item.nextSibling.dataset.screenRow).toBe("0")
expect(line0.previousSibling).toBe(item.nextSibling)
editor.setCursorBufferPosition([0, 0])
editor.insertNewline()
runAnimationFrames()
const line1 = component.lineNodeForScreenRow(1)
expect(item.previousSibling.dataset.screenRow).toBe("1")
expect(item.dataset.screenRow).toBe("1")
expect(item.nextSibling.dataset.screenRow).toBe("1")
expect(line1.previousSibling).toBe(item.nextSibling)
tileNode = component.tileNodesForLines()[0]
contentElements = tileNode.querySelectorAll("content")
expect(contentElements.length).toBe(1)
expect(contentElements[0].dataset.screenRow).toBe("1")
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
blockDecoration.getMarker().setHeadBufferPosition([2, 0])
editor.setCursorBufferPosition([0, 0])
editor.insertNewline()
runAnimationFrames()
const line2 = component.lineNodeForScreenRow(2)
expect(item.previousSibling.dataset.screenRow).toBe("2")
expect(item.dataset.screenRow).toBe("2")
expect(item.nextSibling.dataset.screenRow).toBe("2")
expect(line2.previousSibling).toBe(item.nextSibling)
tileNode = component.tileNodesForLines()[0]
contentElements = tileNode.querySelectorAll("content")
expect(contentElements.length).toBe(1)
expect(contentElements[0].dataset.screenRow).toBe("2")
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
blockDecoration.getMarker().setHeadBufferPosition([4, 0])
runAnimationFrames()
const line4 = component.lineNodeForScreenRow(4)
expect(item.previousSibling.dataset.screenRow).toBe("4")
expect(item.dataset.screenRow).toBe("4")
expect(item.nextSibling.dataset.screenRow).toBe("4")
expect(line4.previousSibling).toBe(item.nextSibling)
})
it('measures block decorations taking into account both top and bottom margins of the element and its children', function () {
@@ -1946,6 +1957,18 @@ describe('TextEditorComponent', function () {
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
})
it('allows the same block decoration item to be moved from one tile to another in the same animation frame', function () {
let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(5, {className: "decoration-1"})
runAnimationFrames()
expect(component.tileNodesForLines()[0].querySelector('.decoration-1')).toBeNull()
expect(component.tileNodesForLines()[1].querySelector('.decoration-1')).toBe(item)
blockDecoration.getMarker().setHeadBufferPosition([0, 0])
runAnimationFrames()
expect(component.tileNodesForLines()[0].querySelector('.decoration-1')).toBe(item)
expect(component.tileNodesForLines()[1].querySelector('.decoration-1')).toBeNull()
})
})
describe('highlight decoration rendering', function () {
@@ -2873,6 +2896,7 @@ describe('TextEditorComponent', function () {
editor.foldBufferRange([[4, 6], [4, 10]])
editor.foldBufferRange([[4, 15], [4, 20]])
runAnimationFrames()
let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker')
expect(foldMarkers.length).toBe(2)
expect(editor.isFoldedAtBufferRow(4)).toBe(true)
@@ -3409,8 +3433,7 @@ describe('TextEditorComponent', function () {
it('transfers focus to the hidden input', function () {
expect(document.activeElement).toBe(document.body)
wrapperNode.focus()
expect(document.activeElement).toBe(wrapperNode)
expect(wrapperNode.shadowRoot.activeElement).toBe(inputNode)
expect(document.activeElement).toBe(inputNode)
})
it('adds the "is-focused" class to the editor when the hidden input is focused', function () {
@@ -4294,7 +4317,7 @@ describe('TextEditorComponent', function () {
atom.config.set('editor.fontFamily', 'sans-serif')
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
atom.themes.applyStylesheet('test', '.function.js {\n font-weight: bold;\n}')
atom.themes.applyStylesheet('test', '.syntax--function.syntax--js {\n font-weight: bold;\n}')
wrapperNode.style.display = ''
component.checkForVisibilityChange()
editor.setCursorBufferPosition([0, Infinity])
@@ -4429,7 +4452,7 @@ describe('TextEditorComponent', function () {
jasmine.attachToDOM(element)
expect(element.offsetHeight).toBe(200)
expect(element.shadowRoot.querySelector('.editor-contents--private').offsetHeight).toBe(200)
expect(element.querySelector('.editor-contents--private').offsetHeight).toBe(200)
expect(Grim.deprecate.callCount).toBe(1)
expect(Grim.deprecate.argsForCall[0][0]).toMatch(/inline style/)
})
@@ -4452,7 +4475,7 @@ describe('TextEditorComponent', function () {
element.component.measureDimensions()
expect(element.offsetHeight).toBe(200)
expect(element.shadowRoot.querySelector('.editor-contents--private').offsetHeight).toBe(200)
expect(element.querySelector('.editor-contents--private').offsetHeight).toBe(200)
expect(Grim.deprecate.callCount).toBe(1)
expect(Grim.deprecate.argsForCall[0][0]).toMatch(/absolute/)
})

View File

@@ -2,8 +2,6 @@ TextEditor = require '../src/text-editor'
TextEditorElement = require '../src/text-editor-element'
{Disposable} = require 'event-kit'
# The rest of text-editor-component-spec will be moved to this file when React
# is eliminated. This covers only concerns related to the wrapper element for now
describe "TextEditorElement", ->
jasmineContent = null
@@ -59,11 +57,11 @@ describe "TextEditorElement", ->
jasmine.attachToDOM(element)
initialCount = element.shadowRoot.querySelectorAll('.line-number').length
initialCount = element.querySelectorAll('.line-number').length
element.remove()
jasmine.attachToDOM(element)
expect(element.shadowRoot.querySelectorAll('.line-number').length).toBe initialCount
expect(element.querySelectorAll('.line-number').length).toBe initialCount
it "does not render duplicate decorations in custom gutters", ->
editor = new TextEditor
@@ -74,14 +72,14 @@ describe "TextEditorElement", ->
element = atom.views.getView(editor)
jasmine.attachToDOM(element)
initialDecorationCount = element.shadowRoot.querySelectorAll('.decoration').length
initialDecorationCount = element.querySelectorAll('.decoration').length
element.remove()
jasmine.attachToDOM(element)
expect(element.shadowRoot.querySelectorAll('.decoration').length).toBe initialDecorationCount
expect(element.querySelectorAll('.decoration').length).toBe initialDecorationCount
describe "focus and blur handling", ->
it "proxies focus/blur events to/from the hidden input inside the shadow root", ->
it "proxies focus/blur events to/from the hidden input", ->
element = new TextEditorElement
jasmineContent.appendChild(element)
@@ -91,12 +89,28 @@ describe "TextEditorElement", ->
element.focus()
expect(blurCalled).toBe false
expect(element.hasFocus()).toBe true
expect(document.activeElement).toBe element
expect(element.shadowRoot.activeElement).toBe element.shadowRoot.querySelector('input')
expect(document.activeElement).toBe element.querySelector('input')
document.body.focus()
expect(blurCalled).toBe true
it "doesn't trigger a blur event on the editor element when focusing an already focused editor element", ->
blurCalled = false
element = new TextEditorElement
element.addEventListener 'blur', -> blurCalled = true
jasmineContent.appendChild(element)
expect(document.activeElement).toBe(document.body)
expect(blurCalled).toBe(false)
element.focus()
expect(document.activeElement).toBe(element.querySelector('input'))
expect(blurCalled).toBe(false)
element.focus()
expect(document.activeElement).toBe(element.querySelector('input'))
expect(blurCalled).toBe(false)
describe "when focused while a parent node is being attached to the DOM", ->
class ElementThatFocusesChild extends HTMLDivElement
attachedCallback: ->
@@ -111,7 +125,7 @@ describe "TextEditorElement", ->
parentElement = document.createElement("element-that-focuses-child")
parentElement.appendChild(element)
jasmineContent.appendChild(parentElement)
expect(element.shadowRoot.activeElement).toBe element.shadowRoot.querySelector('input')
expect(document.activeElement).toBe element.querySelector('input')
describe "when the themes finish loading", ->
[themeReloadCallback, initialThemeLoadComplete, element] = []
@@ -143,7 +157,7 @@ describe "TextEditorElement", ->
initialThemeLoadComplete = true
themeReloadCallback()
verticalScrollbarNode = element.shadowRoot.querySelector(".vertical-scrollbar")
verticalScrollbarNode = element.querySelector(".vertical-scrollbar")
scrollbarWidth = verticalScrollbarNode.offsetWidth - verticalScrollbarNode.clientWidth
expect(scrollbarWidth).toEqual(8)
@@ -181,13 +195,13 @@ describe "TextEditorElement", ->
element.getModel().setText("hello")
expect(window.requestAnimationFrame).toHaveBeenCalled()
expect(element.shadowRoot.textContent).toContain "hello"
expect(element.textContent).toContain "hello"
window.requestAnimationFrame.reset()
element.setUpdatedSynchronously(true)
element.getModel().setText("goodbye")
expect(window.requestAnimationFrame).not.toHaveBeenCalled()
expect(element.shadowRoot.textContent).toContain "goodbye"
expect(element.textContent).toContain "goodbye"
describe "::getDefaultCharacterWidth", ->
it "returns null before the element is attached", ->

View File

@@ -481,7 +481,7 @@ describe "TextEditorPresenter", ->
expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'meta.method-call.js', 'support.function.js'], 'p', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['syntax--source.syntax--js', 'syntax--meta.syntax--method-call.syntax--js', 'syntax--support.syntax--function.syntax--js'], 'p', 20)
presenter.measurementsChanged()
expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
@@ -769,7 +769,7 @@ describe "TextEditorPresenter", ->
expect(getState(presenter).hiddenInput.width).toBe 15
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['syntax--source.syntax--js', 'syntax--storage.syntax--type.syntax--var.syntax--js'], 'r', 20)
presenter.measurementsChanged()
expect(getState(presenter).hiddenInput.width).toBe 20
@@ -926,7 +926,7 @@ describe "TextEditorPresenter", ->
expect(getState(presenter).content.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'meta.method-call.js', 'support.function.js'], 'p', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['syntax--source.syntax--js', 'syntax--meta.syntax--method-call.syntax--js', 'syntax--support.syntax--function.syntax--js'], 'p', 20)
presenter.measurementsChanged()
expect(getState(presenter).content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
@@ -1280,7 +1280,16 @@ describe "TextEditorPresenter", ->
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 0).tagCodes).openTags).toContain('invisible-character eol')
expect(tagsForCodes(presenter, lineStateForScreenRow(presenter, 1).tagCodes).openTags).toContain('invisible-character eol')
describe ".blockDecorations", ->
describe ".{preceding,following}BlockDecorations", ->
stateForBlockDecorations = (blockDecorations) ->
state = {}
for blockDecoration in blockDecorations
state[blockDecoration.id] = {
decoration: blockDecoration,
screenRow: blockDecoration.getMarker().getHeadScreenPosition().row
}
state
it "contains all block decorations that are present before/after a line, both initially and when decorations change", ->
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
presenter = buildPresenter()
@@ -1292,32 +1301,32 @@ describe "TextEditorPresenter", ->
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
runs ->
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([blockDecoration2])
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([blockDecoration3])
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([blockDecoration4])
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration1]))
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration2]))
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration3]))
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration4]))
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual({})
waitsForStateToUpdate presenter, ->
blockDecoration1.getMarker().setHeadBufferPosition([1, 0])
@@ -1326,32 +1335,32 @@ describe "TextEditorPresenter", ->
blockDecoration4.getMarker().setHeadBufferPosition([8, 0])
runs ->
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([blockDecoration1])
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([blockDecoration4])
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([blockDecoration2, blockDecoration3])
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration1]))
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration4]))
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration2, blockDecoration3]))
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual({})
waitsForStateToUpdate presenter, ->
blockDecoration4.destroy()
@@ -1359,71 +1368,85 @@ describe "TextEditorPresenter", ->
blockDecoration1.getMarker().setHeadBufferPosition([0, 0])
runs ->
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([blockDecoration2])
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration1]))
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration2]))
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual({})
waitsForStateToUpdate presenter, ->
editor.setCursorBufferPosition([0, 0])
editor.insertNewline()
runs ->
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([blockDecoration1])
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([blockDecoration2])
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration1]))
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration2]))
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual({})
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual({})
it "inserts block decorations before the line if not specified otherwise", ->
it "contains block decorations located in ::mouseWheelScreenRow even if they are off screen", ->
blockDecoration = addBlockDecorationBeforeScreenRow(0)
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200)
lineId = presenter.displayLayer.getScreenLines(0, 1)[0].id
expect(getState(presenter).content.tiles[0].lines[lineId].precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration]))
presenter.setMouseWheelScreenRow(0)
expectStateUpdate presenter, -> presenter.setScrollTop(4)
expect(getState(presenter).content.tiles[0].lines[lineId].precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration]))
advanceClock(presenter.stoppedScrollingDelay)
expect(getState(presenter).content.tiles[0]).toBeUndefined()
it "inserts block decorations before the line unless otherwise specified", ->
blockDecoration = editor.decorateMarker(editor.markScreenPosition([4, 0]), {type: "block"})
presenter = buildPresenter()
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual [blockDecoration]
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual []
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual stateForBlockDecorations([blockDecoration])
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual {}
describe ".decorationClasses", ->
it "adds decoration classes to the relevant line state objects, both initially and when decorations change", ->
@@ -1741,12 +1764,12 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(explicitHeight: 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'v', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['syntax--source.syntax--js', 'syntax--storage.syntax--type.syntax--var.syntax--js'], 'v', 20)
presenter.measurementsChanged()
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10}
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['syntax--source.syntax--js', 'syntax--storage.syntax--type.syntax--var.syntax--js'], 'r', 20)
presenter.measurementsChanged()
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
@@ -2093,7 +2116,7 @@ describe "TextEditorPresenter", ->
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
}
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20)
presenter.getLinesYardstick().setScopedCharacterWidth(['syntax--source.syntax--js', 'syntax--keyword.syntax--control.syntax--js'], 'i', 20)
presenter.measurementsChanged()
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 4 * 10, width: 20 + 10, height: 10}]
@@ -2193,11 +2216,13 @@ describe "TextEditorPresenter", ->
highlight.flash('b', 500)
runs ->
expectValues stateForHighlightInTile(presenter, highlight, 2), {
needsFlash: true
flashClass: 'b'
flashDuration: 500
flashCount: 1
}
expectValues stateForHighlightInTile(presenter, highlight, 4), {
needsFlash: true
flashClass: 'b'
flashDuration: 500
flashCount: 1
@@ -2206,232 +2231,129 @@ describe "TextEditorPresenter", ->
waitsForStateToUpdate presenter, -> highlight.flash('c', 600)
runs ->
expectValues stateForHighlightInTile(presenter, highlight, 2), {
needsFlash: true
flashClass: 'c'
flashDuration: 600
flashCount: 2
}
expectValues stateForHighlightInTile(presenter, highlight, 4), {
needsFlash: true
flashClass: 'c'
flashDuration: 600
flashCount: 2
}
describe ".blockDecorations", ->
stateForBlockDecoration = (presenter, decoration) ->
getState(presenter).content.blockDecorations[decoration.id]
waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 2], [6, 2]])
runs ->
expectValues stateForHighlightInTile(presenter, highlight, 2), {needsFlash: false}
expectValues stateForHighlightInTile(presenter, highlight, 4), {needsFlash: false}
it "contains state for measured block decorations that are not visible when they are on ::mouseWheelScreenRow", ->
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0, stoppedScrollingDelay: 200)
getState(presenter) # flush pending state
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 0)
describe ".offScreenBlockDecorations", ->
stateForOffScreenBlockDecoration = (presenter, decoration) ->
getState(presenter).content.offScreenBlockDecorations[decoration.id]
presenter.setScrollTop(100)
presenter.setMouseWheelScreenRow(0)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 0
isVisible: true
}
advanceClock(presenter.stoppedScrollingDelay)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
it "invalidates block decorations that intersect a change in the buffer", ->
blockDecoration1 = addBlockDecorationBeforeScreenRow(9)
blockDecoration2 = addBlockDecorationBeforeScreenRow(10)
blockDecoration3 = addBlockDecorationBeforeScreenRow(11)
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 9
isVisible: false
}
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
decoration: blockDecoration2
screenRow: 10
isVisible: false
}
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
decoration: blockDecoration3
screenRow: 11
isVisible: false
}
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
editor.setSelectedScreenRange([[10, 0], [12, 0]])
editor.delete()
presenter.setScrollTop(0) # deleting the buffer causes the editor to autoscroll
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
decoration: blockDecoration2
screenRow: 10
isVisible: false
}
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
decoration: blockDecoration3
screenRow: 10
isVisible: false
}
it "invalidates all block decorations when content frame width, window size or bounding client rect change", ->
blockDecoration1 = addBlockDecorationBeforeScreenRow(11)
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 11
isVisible: false
}
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
presenter.setBoundingClientRect({top: 0, left: 0, width: 50, height: 30})
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 11
isVisible: false
}
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
presenter.setContentFrameWidth(100)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 11
isVisible: false
}
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
presenter.setWindowSize(100, 200)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 11
isVisible: false
}
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
it "contains state for on-screen and unmeasured block decorations, both initially and when they are updated or destroyed", ->
it "contains state for off-screen unmeasured block decorations, both initially and when they are updated or destroyed", ->
item = {}
blockDecoration1 = addBlockDecorationBeforeScreenRow(0, item)
blockDecoration2 = addBlockDecorationBeforeScreenRow(4, item)
blockDecoration3 = addBlockDecorationBeforeScreenRow(4, item)
blockDecoration4 = addBlockDecorationBeforeScreenRow(10, item)
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 0
isVisible: true
}
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
decoration: blockDecoration2
screenRow: 4
isVisible: true
}
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
decoration: blockDecoration3
screenRow: 4
isVisible: true
}
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
decoration: blockDecoration4
screenRow: 10
isVisible: false
}
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBe(blockDecoration4)
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 20)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 0
isVisible: true
}
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
decoration: blockDecoration2
screenRow: 4
isVisible: false
}
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
decoration: blockDecoration3
screenRow: 4
isVisible: false
}
expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined()
blockDecoration3.getMarker().setHeadScreenPosition([5, 0])
presenter.setScrollTop(90)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
decoration: blockDecoration2
screenRow: 4
isVisible: false
}
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
decoration: blockDecoration3
screenRow: 5
isVisible: false
}
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
decoration: blockDecoration4
screenRow: 10
isVisible: true
}
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBeUndefined()
presenter.invalidateBlockDecorationDimensions(blockDecoration1)
presenter.invalidateBlockDecorationDimensions(blockDecoration4)
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBe(blockDecoration4)
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
decoration: blockDecoration1
screenRow: 0
isVisible: false
}
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
decoration: blockDecoration4
screenRow: 10
isVisible: true
}
blockDecoration4.destroy()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBeUndefined()
blockDecoration1.destroy()
it "contains state for off-screen block decorations that intersect a buffer change", ->
blockDecoration1 = addBlockDecorationBeforeScreenRow(9)
blockDecoration2 = addBlockDecorationBeforeScreenRow(10)
blockDecoration3 = addBlockDecorationBeforeScreenRow(11)
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3)
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
decoration: blockDecoration4
screenRow: 10
isVisible: true
}
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
editor.setSelectedScreenRange([[10, 0], [12, 0]])
editor.delete()
presenter.setScrollTop(0) # deleting the buffer causes the editor to autoscroll
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3)
it "contains state for all off-screen block decorations when content frame width, window size or bounding client rect change", ->
blockDecoration1 = addBlockDecorationBeforeScreenRow(10)
blockDecoration2 = addBlockDecorationBeforeScreenRow(11)
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2)
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
presenter.setBoundingClientRect({top: 0, left: 0, width: 50, height: 30})
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2)
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
presenter.setContentFrameWidth(100)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2)
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
presenter.setWindowSize(100, 200)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2)
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20)
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
it "doesn't throw an error when setting the dimensions for a destroyed decoration", ->
blockDecoration = addBlockDecorationBeforeScreenRow(0)
presenter = buildPresenter()
blockDecoration.destroy()
presenter.setBlockDecorationDimensions(blockDecoration, 30, 30)
expect(getState(presenter).content.blockDecorations).toEqual({})
expect(getState(presenter).content.offScreenBlockDecorations).toEqual({})
describe ".overlays", ->
[item] = []

View File

@@ -198,13 +198,13 @@ describe('TextEditorRegistry', function () {
registry.maintainConfig(editor2)
await initialPackageActivation
expect(editor.getRootScopeDescriptor().getScopesArray()).toEqual(['text.plain'])
expect(editor.getRootScopeDescriptor().getScopesArray()).toEqual(['text.plain.null-grammar'])
expect(editor2.getRootScopeDescriptor().getScopesArray()).toEqual(['source.js'])
expect(editor.getEncoding()).toBe('utf8')
expect(editor2.getEncoding()).toBe('utf8')
atom.config.set('core.fileEncoding', 'utf16le', {scopeSelector: '.text.plain'})
atom.config.set('core.fileEncoding', 'utf16le', {scopeSelector: '.text.plain.null-grammar'})
atom.config.set('core.fileEncoding', 'utf16be', {scopeSelector: '.source.js'})
expect(editor.getEncoding()).toBe('utf16le')

View File

@@ -5321,8 +5321,8 @@ describe "TextEditor", ->
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual [
{text: '//', scopes: ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']},
{text: ' http://github.com', scopes: ['source.js', 'comment.line.double-slash.js']}
{text: '//', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--punctuation.syntax--definition.syntax--comment.syntax--js']},
{text: ' http://github.com', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']}
]
waitsForPromise ->
@@ -5331,9 +5331,9 @@ describe "TextEditor", ->
runs ->
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual [
{text: '//', scopes: ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']},
{text: ' ', scopes: ['source.js', 'comment.line.double-slash.js']}
{text: 'http://github.com', scopes: ['source.js', 'comment.line.double-slash.js', 'markup.underline.link.http.hyperlink']}
{text: '//', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--punctuation.syntax--definition.syntax--comment.syntax--js']},
{text: ' ', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']}
{text: 'http://github.com', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--markup.syntax--underline.syntax--link.syntax--http.syntax--hyperlink']}
]
describe "when the grammar is updated", ->
@@ -5346,8 +5346,8 @@ describe "TextEditor", ->
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual [
{text: '//', scopes: ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']},
{text: ' SELECT * FROM OCTOCATS', scopes: ['source.js', 'comment.line.double-slash.js']}
{text: '//', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--punctuation.syntax--definition.syntax--comment.syntax--js']},
{text: ' SELECT * FROM OCTOCATS', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']}
]
waitsForPromise ->
@@ -5356,8 +5356,8 @@ describe "TextEditor", ->
runs ->
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual [
{text: '//', scopes: ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']},
{text: ' SELECT * FROM OCTOCATS', scopes: ['source.js', 'comment.line.double-slash.js']}
{text: '//', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--punctuation.syntax--definition.syntax--comment.syntax--js']},
{text: ' SELECT * FROM OCTOCATS', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']}
]
waitsForPromise ->
@@ -5366,14 +5366,14 @@ describe "TextEditor", ->
runs ->
tokens = editor.tokensForScreenRow(0)
expect(tokens).toEqual [
{text: '//', scopes: ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js']},
{text: ' ', scopes: ['source.js', 'comment.line.double-slash.js']},
{text: 'SELECT', scopes: ['source.js', 'comment.line.double-slash.js', 'keyword.other.DML.sql']},
{text: ' ', scopes: ['source.js', 'comment.line.double-slash.js']},
{text: '*', scopes: ['source.js', 'comment.line.double-slash.js', 'keyword.operator.star.sql']},
{text: ' ', scopes: ['source.js', 'comment.line.double-slash.js']},
{text: 'FROM', scopes: ['source.js', 'comment.line.double-slash.js', 'keyword.other.DML.sql']},
{text: ' OCTOCATS', scopes: ['source.js', 'comment.line.double-slash.js']}
{text: '//', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--punctuation.syntax--definition.syntax--comment.syntax--js']},
{text: ' ', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']},
{text: 'SELECT', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--keyword.syntax--other.syntax--DML.syntax--sql']},
{text: ' ', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']},
{text: '*', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--keyword.syntax--operator.syntax--star.syntax--sql']},
{text: ' ', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']},
{text: 'FROM', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js', 'syntax--keyword.syntax--other.syntax--DML.syntax--sql']},
{text: ' OCTOCATS', scopes: ['syntax--source.syntax--js', 'syntax--comment.syntax--line.syntax--double-slash.syntax--js']}
]
describe ".normalizeTabsInBufferRange()", ->
@@ -5808,20 +5808,20 @@ describe "TextEditor", ->
editor.update({showIndentGuide: false})
expect(editor.tokensForScreenRow(0)).toEqual [
{text: ' ', scopes: ['source.js', 'leading-whitespace']},
{text: 'foo', scopes: ['source.js']}
{text: ' ', scopes: ['syntax--source.syntax--js', 'leading-whitespace']},
{text: 'foo', scopes: ['syntax--source.syntax--js']}
]
editor.update({showIndentGuide: true})
expect(editor.tokensForScreenRow(0)).toEqual [
{text: ' ', scopes: ['source.js', 'leading-whitespace indent-guide']},
{text: 'foo', scopes: ['source.js']}
{text: ' ', scopes: ['syntax--source.syntax--js', 'leading-whitespace indent-guide']},
{text: 'foo', scopes: ['syntax--source.syntax--js']}
]
editor.setMini(true)
expect(editor.tokensForScreenRow(0)).toEqual [
{text: ' ', scopes: ['source.js', 'leading-whitespace']},
{text: 'foo', scopes: ['source.js']}
{text: ' ', scopes: ['syntax--source.syntax--js', 'leading-whitespace']},
{text: 'foo', scopes: ['syntax--source.syntax--js']}
]
describe "when the editor is constructed with the grammar option set", ->

View File

@@ -29,7 +29,7 @@ describe "TokenIterator", ->
})
tokenizedBuffer.setGrammar(grammar)
tokenIterator = tokenizedBuffer.tokenizedLineForRow(1).getTokenIterator()
tokenIterator = tokenizedBuffer.tokenizedLines[1].getTokenIterator()
tokenIterator.next()
expect(tokenIterator.getBufferStart()).toBe 0

View File

@@ -35,40 +35,40 @@ describe('TokenizedBufferIterator', () => {
expect(iterator.seek(Point(0, 0))).toEqual([])
expect(iterator.getPosition()).toEqual(Point(0, 0))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['syntax--foo'])
iterator.moveToSuccessor()
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['bar'])
expect(iterator.getCloseTags()).toEqual(['syntax--foo'])
expect(iterator.getOpenTags()).toEqual(['syntax--bar'])
expect(iterator.seek(Point(0, 1))).toEqual(['baz'])
expect(iterator.seek(Point(0, 1))).toEqual(['syntax--baz'])
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['bar'])
expect(iterator.getOpenTags()).toEqual(['syntax--bar'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual(['bar', 'baz'])
expect(iterator.getOpenTags()).toEqual(['baz'])
expect(iterator.getCloseTags()).toEqual(['syntax--bar', 'syntax--baz'])
expect(iterator.getOpenTags()).toEqual(['syntax--baz'])
expect(iterator.seek(Point(0, 3))).toEqual(['baz'])
expect(iterator.seek(Point(0, 3))).toEqual(['syntax--baz'])
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['bar'])
expect(iterator.getOpenTags()).toEqual(['syntax--bar'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual(['bar', 'baz'])
expect(iterator.getOpenTags()).toEqual(['baz'])
expect(iterator.getCloseTags()).toEqual(['syntax--bar', 'syntax--baz'])
expect(iterator.getOpenTags()).toEqual(['syntax--baz'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 7))
expect(iterator.getCloseTags()).toEqual(['baz'])
expect(iterator.getOpenTags()).toEqual(['bar'])
expect(iterator.getCloseTags()).toEqual(['syntax--baz'])
expect(iterator.getOpenTags()).toEqual(['syntax--bar'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 7))
expect(iterator.getCloseTags()).toEqual(['bar'])
expect(iterator.getCloseTags()).toEqual(['syntax--bar'])
expect(iterator.getOpenTags()).toEqual([])
iterator.moveToSuccessor()
@@ -76,14 +76,14 @@ describe('TokenizedBufferIterator', () => {
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual([])
expect(iterator.seek(Point(0, 5))).toEqual(['baz'])
expect(iterator.seek(Point(0, 5))).toEqual(['syntax--baz'])
expect(iterator.getPosition()).toEqual(Point(0, 7))
expect(iterator.getCloseTags()).toEqual(['baz'])
expect(iterator.getOpenTags()).toEqual(['bar'])
expect(iterator.getCloseTags()).toEqual(['syntax--baz'])
expect(iterator.getOpenTags()).toEqual(['syntax--bar'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 7))
expect(iterator.getCloseTags()).toEqual(['bar'])
expect(iterator.getCloseTags()).toEqual(['syntax--bar'])
expect(iterator.getOpenTags()).toEqual([])
})
})
@@ -111,15 +111,15 @@ describe('TokenizedBufferIterator', () => {
iterator.seek(Point(0, 0))
expect(iterator.getPosition()).toEqual(Point(0, 0))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['syntax--foo'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 0))
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['foo'])
expect(iterator.getCloseTags()).toEqual(['syntax--foo'])
expect(iterator.getOpenTags()).toEqual(['syntax--foo'])
iterator.moveToSuccessor()
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getCloseTags()).toEqual(['syntax--foo'])
expect(iterator.getOpenTags()).toEqual([])
})
@@ -163,26 +163,26 @@ describe('TokenizedBufferIterator', () => {
iterator.seek(Point(0, 0))
expect(iterator.getPosition()).toEqual(Point(0, 0))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['syntax--foo'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['qux'])
expect(iterator.getCloseTags()).toEqual(['syntax--foo'])
expect(iterator.getOpenTags()).toEqual(['syntax--qux'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.getCloseTags()).toEqual(['qux'])
expect(iterator.getCloseTags()).toEqual(['syntax--qux'])
expect(iterator.getOpenTags()).toEqual([])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(1, 0))
expect(iterator.getCloseTags()).toEqual([])
expect(iterator.getOpenTags()).toEqual(['foo'])
expect(iterator.getOpenTags()).toEqual(['syntax--foo'])
iterator.moveToSuccessor()
expect(iterator.getPosition()).toEqual(Point(2, 0))
expect(iterator.getCloseTags()).toEqual(['foo'])
expect(iterator.getCloseTags()).toEqual(['syntax--foo'])
expect(iterator.getOpenTags()).toEqual([])
})
})

View File

@@ -1,3 +1,4 @@
NullGrammar = require '../src/null-grammar'
TokenizedBuffer = require '../src/tokenized-buffer'
{Point} = TextBuffer = require 'text-buffer'
_ = require 'underscore-plus'
@@ -32,15 +33,8 @@ describe "TokenizedBuffer", ->
atom.packages.activatePackage('language-coffee-script')
it "deserializes it searching among the buffers in the current project", ->
tokenizedBufferA = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBufferB = TokenizedBuffer.deserialize(
JSON.parse(JSON.stringify(tokenizedBufferA.serialize())),
atom
)
tokenizedBufferA = new TokenizedBuffer({buffer, tabLength: 2})
tokenizedBufferB = TokenizedBuffer.deserialize(JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), atom)
expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer)
describe "when the underlying buffer has no path", ->
@@ -48,25 +42,14 @@ describe "TokenizedBuffer", ->
buffer = atom.project.bufferForPathSync(null)
it "deserializes it searching among the buffers in the current project", ->
tokenizedBufferA = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBufferB = TokenizedBuffer.deserialize(
JSON.parse(JSON.stringify(tokenizedBufferA.serialize())),
atom
)
tokenizedBufferA = new TokenizedBuffer({buffer, tabLength: 2})
tokenizedBufferB = TokenizedBuffer.deserialize(JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), atom)
expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer)
describe "when the buffer is destroyed", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2})
startTokenizing(tokenizedBuffer)
it "stops tokenization", ->
@@ -78,11 +61,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains soft-tabs", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2})
startTokenizing(tokenizedBuffer)
afterEach ->
@@ -90,32 +69,29 @@ describe "TokenizedBuffer", ->
buffer.release()
describe "on construction", ->
it "initially creates un-tokenized screen lines, then tokenizes lines chunk at a time in the background", ->
line0 = tokenizedBuffer.tokenizedLineForRow(0)
expect(line0.tokens).toEqual([value: line0.text, scopes: ['source.js']])
it "tokenizes lines chunk at a time in the background", ->
line0 = tokenizedBuffer.tokenizedLines[0]
expect(line0).toBeUndefined()
line11 = tokenizedBuffer.tokenizedLineForRow(11)
expect(line11.tokens).toEqual([value: " return sort(Array.apply(this, arguments));", scopes: ['source.js']])
# background tokenization has not begun
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack).toBeUndefined()
line11 = tokenizedBuffer.tokenizedLines[11]
expect(line11).toBeUndefined()
# tokenize chunk 1
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.tokenizedLines[0].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[4].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[5]).toBeUndefined()
# tokenize chunk 2
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(9).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.tokenizedLines[5].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[9].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[10]).toBeUndefined()
# tokenize last chunk
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(12).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[10].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[12].ruleStack?).toBeTruthy()
describe "when the buffer is partially tokenized", ->
beforeEach ->
@@ -152,8 +128,8 @@ describe "TokenizedBuffer", ->
it "does not attempt to tokenize the lines in the change, and preserves the existing invalid row", ->
expect(tokenizedBuffer.firstInvalidRow()).toBe 5
buffer.setTextInRange([[6, 0], [7, 0]], "\n\n\n")
expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.tokenizedLineForRow(7).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.tokenizedLines[6]).toBeUndefined()
expect(tokenizedBuffer.tokenizedLines[7]).toBeUndefined()
expect(tokenizedBuffer.firstInvalidRow()).toBe 5
describe "when the buffer is fully tokenized", ->
@@ -165,101 +141,101 @@ describe "TokenizedBuffer", ->
it "updates tokens to reflect the change", ->
buffer.setTextInRange([[0, 0], [2, 0]], "foo()\n7\n")
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js'])
expect(tokenizedBuffer.tokenizedLines[0].tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js'])
expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js'])
# line 2 is unchanged
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLines[2].tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
buffer.insert([5, 30], '/* */')
buffer.insert([2, 0], '/*')
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js']
expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js']
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
it "resumes highlighting with the state of the previous line", ->
buffer.insert([0, 0], '/*')
buffer.insert([5, 0], '*/')
buffer.insert([1, 0], 'var ')
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[1].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
describe "when lines are both updated and removed", ->
it "updates tokens to reflect the change", ->
buffer.setTextInRange([[1, 0], [3, 0]], "foo()")
# previous line 0 remains
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.type.var.js'])
expect(tokenizedBuffer.tokenizedLines[0].tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.type.var.js'])
# previous line 3 should be combined with input to form line 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLines[1].tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
# lines below deleted regions should be shifted upward
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js'])
expect(tokenizedBuffer.tokenizedLines[2].tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLines[3].tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(tokenizedBuffer.tokenizedLines[4].tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js'])
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
buffer.insert([5, 30], '/* */')
buffer.setTextInRange([[2, 0], [3, 0]], '/*')
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js']
expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js']
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
describe "when lines are both updated and inserted", ->
it "updates tokens to reflect the change", ->
buffer.setTextInRange([[1, 0], [2, 0]], "foo()\nbar()\nbaz()\nquux()")
# previous line 0 remains
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.type.var.js'])
expect(tokenizedBuffer.tokenizedLines[0].tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.type.var.js'])
# 3 new lines inserted
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0]).toEqual(value: 'bar', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0]).toEqual(value: 'baz', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLines[2].tokens[0]).toEqual(value: 'bar', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLines[3].tokens[0]).toEqual(value: 'baz', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
# previous line 2 is joined with quux() on line 4
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0]).toEqual(value: 'quux', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
expect(tokenizedBuffer.tokenizedLines[4].tokens[0]).toEqual(value: 'quux', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'])
expect(tokenizedBuffer.tokenizedLines[4].tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])
# previous line 3 is pushed down to become line 5
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
expect(tokenizedBuffer.tokenizedLines[5].tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js'])
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
buffer.insert([5, 30], '/* */')
buffer.insert([2, 0], '/*\nabcde\nabcder')
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js']
expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js']
advanceClock() # tokenize invalidated lines in background
expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(6).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(7).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(8).tokens[0].scopes).not.toBe ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[6].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[7].tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLines[8].tokens[0].scopes).not.toBe ['source.js', 'comment.block.js']
describe "when there is an insertion that is larger than the chunk size", ->
it "tokenizes the initial chunk synchronously, then tokenizes the remaining lines in the background", ->
commentBlock = _.multiplyString("// a comment\n", tokenizedBuffer.chunkSize + 2)
buffer.insert([0, 0], commentBlock)
expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy()
expect(tokenizedBuffer.tokenizedLines[0].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[4].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[5]).toBeUndefined()
advanceClock()
expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[5].ruleStack?).toBeTruthy()
expect(tokenizedBuffer.tokenizedLines[6].ruleStack?).toBeTruthy()
it "does not break out soft tabs across a scope boundary", ->
waitsForPromise ->
@@ -284,11 +260,7 @@ describe "TokenizedBuffer", ->
runs ->
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.coffee'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.coffee'), tabLength: 2})
startTokenizing(tokenizedBuffer)
afterEach ->
@@ -352,7 +324,6 @@ describe "TokenizedBuffer", ->
expect(tokenizedHandler.callCount).toBe(1)
it "retokenizes the buffer", ->
waitsForPromise ->
atom.packages.activatePackage('language-ruby-on-rails')
@@ -362,14 +333,10 @@ describe "TokenizedBuffer", ->
runs ->
buffer = atom.project.bufferForPathSync()
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar('test.erb'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.selectGrammar('test.erb'), tabLength: 2})
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.tokenizedLineForRow(0)
{tokens} = tokenizedBuffer.tokenizedLines[0]
expect(tokens[0]).toEqual value: "<div class='name'>", scopes: ["text.html.ruby"]
waitsForPromise ->
@@ -377,7 +344,7 @@ describe "TokenizedBuffer", ->
runs ->
fullyTokenize(tokenizedBuffer)
{tokens} = tokenizedBuffer.tokenizedLineForRow(0)
{tokens} = tokenizedBuffer.tokenizedLines[0]
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby", "meta.tag.block.any.html", "punctuation.definition.tag.begin.html"]
describe ".tokenForPosition(position)", ->
@@ -387,11 +354,7 @@ describe "TokenizedBuffer", ->
it "returns the correct token (regression)", ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2})
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([1, 0]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1, 1]).scopes).toEqual ["source.js"]
@@ -400,16 +363,12 @@ describe "TokenizedBuffer", ->
describe ".bufferRangeForScopeAtPosition(selector, position)", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2})
fullyTokenize(tokenizedBuffer)
describe "when the selector does not match the token at the position", ->
it "returns a falsy value", ->
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeFalsy()
expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeUndefined()
describe "when the selector matches a single token at the position", ->
it "returns the range covered by the token", ->
@@ -423,11 +382,7 @@ describe "TokenizedBuffer", ->
describe ".indentLevelForRow(row)", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2})
fullyTokenize(tokenizedBuffer)
describe "when the line is non-empty", ->
@@ -469,7 +424,7 @@ describe "TokenizedBuffer", ->
buffer.insert([12, 0], ' ')
expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(14)).not.toBeDefined()
expect(tokenizedBuffer.tokenizedLines[14]).not.toBeDefined()
it "updates the indentLevel of empty lines surrounding a change that inserts lines", ->
buffer.insert([7, 0], '\n\n')
@@ -503,11 +458,7 @@ describe "TokenizedBuffer", ->
buffer = atom.project.bufferForPathSync('sample.js')
buffer.insert [10, 0], " // multi-line\n // comment\n // block\n"
buffer.insert [0, 0], "// multi-line\n// comment\n// block\n"
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js'))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2})
fullyTokenize(tokenizedBuffer)
it "includes the first line of multi-line comments", ->
@@ -570,61 +521,90 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
describe "::tokenizedLineForRow(row)", ->
it "returns the tokenized line for a row, or a placeholder line if it hasn't been tokenized yet", ->
buffer = atom.project.bufferForPathSync('sample.js')
grammar = atom.grammars.grammarForScopeName('source.js')
tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2})
line0 = buffer.lineForRow(0)
jsScopeStartId = grammar.startIdForScope(grammar.scopeName)
jsScopeEndId = grammar.endIdForScope(grammar.scopeName)
startTokenizing(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined()
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0)
expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([jsScopeStartId, line0.length, jsScopeEndId])
advanceClock(1)
expect(tokenizedBuffer.tokenizedLines[0]).not.toBeUndefined()
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0)
expect(tokenizedBuffer.tokenizedLineForRow(0).tags).not.toEqual([jsScopeStartId, line0.length, jsScopeEndId])
nullScopeStartId = NullGrammar.startIdForScope(NullGrammar.scopeName)
nullScopeEndId = NullGrammar.endIdForScope(NullGrammar.scopeName)
tokenizedBuffer.setGrammar(NullGrammar)
startTokenizing(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined()
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0)
expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([nullScopeStartId, line0.length, nullScopeEndId])
advanceClock(1)
expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0)
expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([nullScopeStartId, line0.length, nullScopeEndId])
it "returns undefined if the requested row is outside the buffer range", ->
buffer = atom.project.bufferForPathSync('sample.js')
grammar = atom.grammars.grammarForScopeName('source.js')
tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2})
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenizedLineForRow(999)).toBeUndefined()
describe "when the buffer is configured with the null grammar", ->
it "uses the placeholder tokens and does not actually tokenize using the grammar", ->
spyOn(atom.grammars.nullGrammar, 'tokenizeLine').andCallThrough()
it "does not actually tokenize using the grammar", ->
spyOn(NullGrammar, 'tokenizeLine').andCallThrough()
buffer = atom.project.bufferForPathSync('sample.will-use-the-null-grammar')
buffer.setText('a\nb\nc')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer = new TokenizedBuffer({buffer, tabLength: 2})
tokenizeCallback = jasmine.createSpy('onDidTokenize')
tokenizedBuffer.onDidTokenize(tokenizeCallback)
expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined()
expect(tokenizedBuffer.tokenizedLines[1]).toBeUndefined()
expect(tokenizedBuffer.tokenizedLines[2]).toBeUndefined()
expect(tokenizeCallback.callCount).toBe(0)
expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled()
fullyTokenize(tokenizedBuffer)
expect(tokenizeCallback.callCount).toBe 1
expect(atom.grammars.nullGrammar.tokenizeLine.callCount).toBe 0
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens.length).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0].value).toBe 'a'
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens.length).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].value).toBe 'b'
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens.length).toBe 1
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].value).toBe 'c'
expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined()
expect(tokenizedBuffer.tokenizedLines[1]).toBeUndefined()
expect(tokenizedBuffer.tokenizedLines[2]).toBeUndefined()
expect(tokenizeCallback.callCount).toBe(0)
expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled()
describe "text decoration layer API", ->
describe "iterator", ->
it "iterates over the syntactic scope boundaries", ->
buffer = new TextBuffer(text: "var foo = 1 /*\nhello*/var bar = 2\n")
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".js"))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName("source.js"), tabLength: 2})
fullyTokenize(tokenizedBuffer)
iterator = tokenizedBuffer.buildIterator()
iterator.seek(Point(0, 0))
expectedBoundaries = [
{position: Point(0, 0), closeTags: [], openTags: ["source.js", "storage.type.var.js"]}
{position: Point(0, 3), closeTags: ["storage.type.var.js"], openTags: []}
{position: Point(0, 8), closeTags: [], openTags: ["keyword.operator.assignment.js"]}
{position: Point(0, 9), closeTags: ["keyword.operator.assignment.js"], openTags: []}
{position: Point(0, 10), closeTags: [], openTags: ["constant.numeric.decimal.js"]}
{position: Point(0, 11), closeTags: ["constant.numeric.decimal.js"], openTags: []}
{position: Point(0, 12), closeTags: [], openTags: ["comment.block.js", "punctuation.definition.comment.js"]}
{position: Point(0, 14), closeTags: ["punctuation.definition.comment.js"], openTags: []}
{position: Point(1, 5), closeTags: [], openTags: ["punctuation.definition.comment.js"]}
{position: Point(1, 7), closeTags: ["punctuation.definition.comment.js", "comment.block.js"], openTags: ["storage.type.var.js"]}
{position: Point(1, 10), closeTags: ["storage.type.var.js"], openTags: []}
{position: Point(1, 15), closeTags: [], openTags: ["keyword.operator.assignment.js"]}
{position: Point(1, 16), closeTags: ["keyword.operator.assignment.js"], openTags: []}
{position: Point(1, 17), closeTags: [], openTags: ["constant.numeric.decimal.js"]}
{position: Point(1, 18), closeTags: ["constant.numeric.decimal.js"], openTags: []}
{position: Point(0, 0), closeTags: [], openTags: ["syntax--source.syntax--js", "syntax--storage.syntax--type.syntax--var.syntax--js"]}
{position: Point(0, 3), closeTags: ["syntax--storage.syntax--type.syntax--var.syntax--js"], openTags: []}
{position: Point(0, 8), closeTags: [], openTags: ["syntax--keyword.syntax--operator.syntax--assignment.syntax--js"]}
{position: Point(0, 9), closeTags: ["syntax--keyword.syntax--operator.syntax--assignment.syntax--js"], openTags: []}
{position: Point(0, 10), closeTags: [], openTags: ["syntax--constant.syntax--numeric.syntax--decimal.syntax--js"]}
{position: Point(0, 11), closeTags: ["syntax--constant.syntax--numeric.syntax--decimal.syntax--js"], openTags: []}
{position: Point(0, 12), closeTags: [], openTags: ["syntax--comment.syntax--block.syntax--js", "syntax--punctuation.syntax--definition.syntax--comment.syntax--js"]}
{position: Point(0, 14), closeTags: ["syntax--punctuation.syntax--definition.syntax--comment.syntax--js"], openTags: []}
{position: Point(1, 5), closeTags: [], openTags: ["syntax--punctuation.syntax--definition.syntax--comment.syntax--js"]}
{position: Point(1, 7), closeTags: ["syntax--punctuation.syntax--definition.syntax--comment.syntax--js", "syntax--comment.syntax--block.syntax--js"], openTags: ["syntax--storage.syntax--type.syntax--var.syntax--js"]}
{position: Point(1, 10), closeTags: ["syntax--storage.syntax--type.syntax--var.syntax--js"], openTags: []}
{position: Point(1, 15), closeTags: [], openTags: ["syntax--keyword.syntax--operator.syntax--assignment.syntax--js"]}
{position: Point(1, 16), closeTags: ["syntax--keyword.syntax--operator.syntax--assignment.syntax--js"], openTags: []}
{position: Point(1, 17), closeTags: [], openTags: ["syntax--constant.syntax--numeric.syntax--decimal.syntax--js"]}
{position: Point(1, 18), closeTags: ["syntax--constant.syntax--numeric.syntax--decimal.syntax--js"], openTags: []}
]
loop
@@ -637,16 +617,16 @@ describe "TokenizedBuffer", ->
expect(boundary).toEqual(expectedBoundaries.shift())
break unless iterator.moveToSuccessor()
expect(iterator.seek(Point(0, 1))).toEqual(["source.js", "storage.type.var.js"])
expect(iterator.seek(Point(0, 1))).toEqual(["syntax--source.syntax--js", "syntax--storage.syntax--type.syntax--var.syntax--js"])
expect(iterator.getPosition()).toEqual(Point(0, 3))
expect(iterator.seek(Point(0, 8))).toEqual(["source.js"])
expect(iterator.seek(Point(0, 8))).toEqual(["syntax--source.syntax--js"])
expect(iterator.getPosition()).toEqual(Point(0, 8))
expect(iterator.seek(Point(1, 0))).toEqual(["source.js", "comment.block.js"])
expect(iterator.seek(Point(1, 0))).toEqual(["syntax--source.syntax--js", "syntax--comment.syntax--block.syntax--js"])
expect(iterator.getPosition()).toEqual(Point(1, 0))
expect(iterator.seek(Point(1, 18))).toEqual(["source.js", "constant.numeric.decimal.js"])
expect(iterator.seek(Point(1, 18))).toEqual(["syntax--source.syntax--js", "syntax--constant.syntax--numeric.syntax--decimal.syntax--js"])
expect(iterator.getPosition()).toEqual(Point(1, 18))
expect(iterator.seek(Point(2, 0))).toEqual(["source.js"])
expect(iterator.seek(Point(2, 0))).toEqual(["syntax--source.syntax--js"])
iterator.moveToSuccessor() # ensure we don't infinitely loop (regression test)
it "does not report columns beyond the length of the line", ->
@@ -655,11 +635,7 @@ describe "TokenizedBuffer", ->
runs ->
buffer = new TextBuffer(text: "# hello\n# world")
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".coffee"))
tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName("source.coffee"), tabLength: 2})
fullyTokenize(tokenizedBuffer)
iterator = tokenizedBuffer.buildIterator()
@@ -688,16 +664,12 @@ describe "TokenizedBuffer", ->
})
buffer = new TextBuffer(text: 'start x\nend x\nx')
tokenizedBuffer = new TokenizedBuffer({
buffer, grammarRegistry: atom.grammars, packageManager: atom.packages,
assert: atom.assert, tabLength: 2,
})
tokenizedBuffer.setGrammar(grammar)
tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2})
fullyTokenize(tokenizedBuffer)
iterator = tokenizedBuffer.buildIterator()
iterator.seek(Point(1, 0))
expect(iterator.getPosition()).toEqual([1, 0])
expect(iterator.getCloseTags()).toEqual ['blue.broken']
expect(iterator.getOpenTags()).toEqual ['yellow.broken']
expect(iterator.getCloseTags()).toEqual ['syntax--blue.syntax--broken']
expect(iterator.getOpenTags()).toEqual ['syntax--yellow.syntax--broken']

View File

@@ -1,86 +0,0 @@
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
clone
module.exports =
class BlockDecorationsComponent
constructor: (@container, @views, @presenter, @domElementPool) ->
@newState = null
@oldState = null
@blockDecorationNodesById = {}
@domNode = @domElementPool.buildElement("content")
@domNode.setAttribute("select", ".atom--invisible-block-decoration")
@domNode.style.visibility = "hidden"
getDomNode: ->
@domNode
updateSync: (state) ->
@newState = state.content
@oldState ?= {blockDecorations: {}, width: 0}
if @newState.width isnt @oldState.width
@domNode.style.width = @newState.width + "px"
@oldState.width = @newState.width
for id of @oldState.blockDecorations
unless @newState.blockDecorations.hasOwnProperty(id)
blockDecorationNode = @blockDecorationNodesById[id]
blockDecorationNode.previousSibling.remove()
blockDecorationNode.nextSibling.remove()
blockDecorationNode.remove()
delete @blockDecorationNodesById[id]
delete @oldState.blockDecorations[id]
for id of @newState.blockDecorations
if @oldState.blockDecorations.hasOwnProperty(id)
@updateBlockDecorationNode(id)
else
@oldState.blockDecorations[id] = {}
@createAndAppendBlockDecorationNode(id)
measureBlockDecorations: ->
for decorationId, blockDecorationNode of @blockDecorationNodesById
decoration = @newState.blockDecorations[decorationId].decoration
topRuler = blockDecorationNode.previousSibling
bottomRuler = blockDecorationNode.nextSibling
width = blockDecorationNode.offsetWidth
height = bottomRuler.offsetTop - topRuler.offsetTop
@presenter.setBlockDecorationDimensions(decoration, width, height)
createAndAppendBlockDecorationNode: (id) ->
blockDecorationState = @newState.blockDecorations[id]
blockDecorationClass = "atom--block-decoration-#{id}"
topRuler = document.createElement("div")
blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item)
bottomRuler = document.createElement("div")
topRuler.classList.add(blockDecorationClass)
blockDecorationNode.classList.add(blockDecorationClass)
bottomRuler.classList.add(blockDecorationClass)
@container.appendChild(topRuler)
@container.appendChild(blockDecorationNode)
@container.appendChild(bottomRuler)
@blockDecorationNodesById[id] = blockDecorationNode
@updateBlockDecorationNode(id)
updateBlockDecorationNode: (id) ->
newBlockDecorationState = @newState.blockDecorations[id]
oldBlockDecorationState = @oldState.blockDecorations[id]
blockDecorationNode = @blockDecorationNodesById[id]
if newBlockDecorationState.isVisible
blockDecorationNode.previousSibling.classList.remove("atom--invisible-block-decoration")
blockDecorationNode.classList.remove("atom--invisible-block-decoration")
blockDecorationNode.nextSibling.classList.remove("atom--invisible-block-decoration")
else
blockDecorationNode.previousSibling.classList.add("atom--invisible-block-decoration")
blockDecorationNode.classList.add("atom--invisible-block-decoration")
blockDecorationNode.nextSibling.classList.add("atom--invisible-block-decoration")
if oldBlockDecorationState.screenRow isnt newBlockDecorationState.screenRow
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
oldBlockDecorationState.screenRow = newBlockDecorationState.screenRow

View File

@@ -1,68 +1,88 @@
path = require 'path'
fs = require 'fs-plus'
/** @babel */
# This is loaded by atom.coffee. See https://atom.io/docs/api/latest/Config for
# more information about config schemas.
module.exports =
core:
type: 'object'
properties:
ignoredNames:
type: 'array'
default: [".git", ".hg", ".svn", ".DS_Store", "._*", "Thumbs.db"]
items:
import path from 'path'
import fs from 'fs-plus'
// This is loaded by atom-environment.coffee. See
// https://atom.io/docs/api/latest/Config for more information about config
// schemas.
const configSchema = {
core: {
type: 'object',
properties: {
ignoredNames: {
type: 'array',
default: ['.git', '.hg', '.svn', '.DS_Store', '._*', 'Thumbs.db'],
items: {
type: 'string'
},
description: 'List of [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29). Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.'
excludeVcsIgnoredPaths:
type: 'boolean'
default: true
title: 'Exclude VCS Ignored Paths'
},
excludeVcsIgnoredPaths: {
type: 'boolean',
default: true,
title: 'Exclude VCS Ignored Paths',
description: 'Files and directories ignored by the current project\'s VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders.'
followSymlinks:
type: 'boolean'
default: true
},
followSymlinks: {
type: 'boolean',
default: true,
description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.'
disabledPackages:
type: 'array'
default: []
items:
},
disabledPackages: {
type: 'array',
default: [],
items: {
type: 'string'
},
description: 'List of names of installed packages which are not loaded at startup.'
customFileTypes:
type: 'object'
default: {}
description: 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)'
additionalProperties:
type: 'array'
items:
},
customFileTypes: {
type: 'object',
default: {},
description: 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)',
additionalProperties: {
type: 'array',
items: {
type: 'string'
themes:
type: 'array'
default: ['one-dark-ui', 'one-dark-syntax']
items:
}
}
},
themes: {
type: 'array',
default: ['one-dark-ui', 'one-dark-syntax'],
items: {
type: 'string'
},
description: 'Names of UI and syntax themes which will be used when Atom starts.'
projectHome:
type: 'string'
default: path.join(fs.getHomeDirectory(), 'github')
},
projectHome: {
type: 'string',
default: path.join(fs.getHomeDirectory(), 'github'),
description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.'
audioBeep:
type: 'boolean'
default: true
},
audioBeep: {
type: 'boolean',
default: true,
description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.'
destroyEmptyPanes:
type: 'boolean'
default: true
title: 'Remove Empty Panes'
},
destroyEmptyPanes: {
type: 'boolean',
default: true,
title: 'Remove Empty Panes',
description: 'When the last tab of a pane is closed, remove that pane as well.'
closeEmptyWindows:
type: 'boolean'
default: true
},
closeEmptyWindows: {
type: 'boolean',
default: true,
description: 'When a window with no open tabs or panes is given the \'Close Tab\' command, close that window.'
fileEncoding:
description: 'Default character set encoding to use when reading and writing files.'
type: 'string'
default: 'utf8'
},
fileEncoding: {
description: 'Default character set encoding to use when reading and writing files.',
type: 'string',
default: 'utf8',
enum: [
'cp437',
'eucjp',
@@ -100,179 +120,237 @@ module.exports =
'windows1258',
'windows866'
]
openEmptyEditorOnStart:
description: 'Automatically open an empty editor on startup.'
type: 'boolean'
},
openEmptyEditorOnStart: {
description: 'Automatically open an empty editor on startup.',
type: 'boolean',
default: true
automaticallyUpdate:
description: 'Automatically update Atom when a new release is available.'
type: 'boolean'
},
automaticallyUpdate: {
description: 'Automatically update Atom when a new release is available.',
type: 'boolean',
default: true
allowPendingPaneItems:
description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.'
type: 'boolean'
},
allowPendingPaneItems: {
description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.',
type: 'boolean',
default: true
telemetryConsent:
description: 'Allow usage statistics and exception reports to be sent to the Atom team to help improve the product.'
title: 'Send Telemetry to the Atom Team'
type: 'string'
default: 'undecided'
},
telemetryConsent: {
description: 'Allow usage statistics and exception reports to be sent to the Atom team to help improve the product.',
title: 'Send Telemetry to the Atom Team',
type: 'string',
default: 'undecided',
enum: [
{value: 'limited', description: 'Allow limited anonymous usage stats, exception and crash reporting'}
{value: 'no', description: 'Do not send any telemetry data'}
{value: 'undecided', description: 'Undecided (Atom will ask again next time it is launched)'}
{
value: 'limited',
description: 'Allow limited anonymous usage stats, exception and crash reporting'
},
{
value: 'no',
description: 'Do not send any telemetry data'
},
{
value: 'undecided',
description: 'Undecided (Atom will ask again next time it is launched)'
}
]
warnOnLargeFileLimit:
description: 'Warn before opening files larger than this number of megabytes.'
type: 'number'
},
warnOnLargeFileLimit: {
description: 'Warn before opening files larger than this number of megabytes.',
type: 'number',
default: 20
editor:
type: 'object'
properties:
# These settings are used in scoped fashion only. No defaults.
commentStart:
}
}
},
editor: {
type: 'object',
// These settings are used in scoped fashion only. No defaults.
properties: {
commentStart: {
type: ['string', 'null']
commentEnd:
},
commentEnd: {
type: ['string', 'null']
increaseIndentPattern:
},
increaseIndentPattern: {
type: ['string', 'null']
decreaseIndentPattern:
},
decreaseIndentPattern: {
type: ['string', 'null']
foldEndPattern:
},
foldEndPattern: {
type: ['string', 'null']
# These can be used as globals or scoped, thus defaults.
fontFamily:
type: 'string'
default: ''
},
// These can be used as globals or scoped, thus defaults.
fontFamily: {
type: 'string',
default: '',
description: 'The name of the font family used for editor text.'
fontSize:
type: 'integer'
default: 14
minimum: 1
maximum: 100
},
fontSize: {
type: 'integer',
default: 14,
minimum: 1,
maximum: 100,
description: 'Height in pixels of editor text.'
lineHeight:
type: ['string', 'number']
default: 1.5
},
lineHeight: {
type: ['string', 'number'],
default: 1.5,
description: 'Height of editor lines, as a multiplier of font size.'
showInvisibles:
type: 'boolean'
default: false
},
showInvisibles: {
type: 'boolean',
default: false,
description: 'Render placeholders for invisible characters, such as tabs, spaces and newlines.'
showIndentGuide:
type: 'boolean'
default: false
},
showIndentGuide: {
type: 'boolean',
default: false,
description: 'Show indentation indicators in the editor.'
showLineNumbers:
type: 'boolean'
default: true
},
showLineNumbers: {
type: 'boolean',
default: true,
description: 'Show line numbers in the editor\'s gutter.'
atomicSoftTabs:
type: 'boolean'
default: true
},
atomicSoftTabs: {
type: 'boolean',
default: true,
description: 'Skip over tab-length runs of leading whitespace when moving the cursor.'
autoIndent:
type: 'boolean'
default: true
},
autoIndent: {
type: 'boolean',
default: true,
description: 'Automatically indent the cursor when inserting a newline.'
autoIndentOnPaste:
type: 'boolean'
default: true
},
autoIndentOnPaste: {
type: 'boolean',
default: true,
description: 'Automatically indent pasted text based on the indentation of the previous line.'
nonWordCharacters:
type: 'string'
default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…"
},
nonWordCharacters: {
type: 'string',
default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…",
description: 'A string of non-word characters to define word boundaries.'
preferredLineLength:
type: 'integer'
default: 80
minimum: 1
},
preferredLineLength: {
type: 'integer',
default: 80,
minimum: 1,
description: 'Identifies the length of a line which is used when wrapping text with the `Soft Wrap At Preferred Line Length` setting enabled, in number of characters.'
tabLength:
type: 'integer'
default: 2
minimum: 1
},
tabLength: {
type: 'integer',
default: 2,
minimum: 1,
description: 'Number of spaces used to represent a tab.'
softWrap:
type: 'boolean'
default: false
},
softWrap: {
type: 'boolean',
default: false,
description: 'Wraps lines that exceed the width of the window. When `Soft Wrap At Preferred Line Length` is set, it will wrap to the number of characters defined by the `Preferred Line Length` setting.'
softTabs:
type: 'boolean'
default: true
},
softTabs: {
type: 'boolean',
default: true,
description: 'If the `Tab Type` config setting is set to "auto" and autodetection of tab type from buffer content fails, then this config setting determines whether a soft tab or a hard tab will be inserted when the Tab key is pressed.'
tabType:
type: 'string'
default: 'auto'
enum: ['auto', 'soft', 'hard']
},
tabType: {
type: 'string',
default: 'auto',
enum: ['auto', 'soft', 'hard'],
description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.'
softWrapAtPreferredLineLength:
type: 'boolean'
default: false
},
softWrapAtPreferredLineLength: {
type: 'boolean',
default: false,
description: 'Instead of wrapping lines to the window\'s width, wrap lines to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when the soft wrap config setting is enabled globally or for the current language. **Note:** If you want to hide the wrap guide (the vertical line) you can disable the `wrap-guide` package.'
softWrapHangingIndent:
type: 'integer'
default: 0
minimum: 0
},
softWrapHangingIndent: {
type: 'integer',
default: 0,
minimum: 0,
description: 'When soft wrap is enabled, defines length of additional indentation applied to wrapped lines, in number of characters.'
scrollSensitivity:
type: 'integer'
default: 40
minimum: 10
maximum: 200
},
scrollSensitivity: {
type: 'integer',
default: 40,
minimum: 10,
maximum: 200,
description: 'Determines how fast the editor scrolls when using a mouse or trackpad.'
scrollPastEnd:
type: 'boolean'
default: false
},
scrollPastEnd: {
type: 'boolean',
default: false,
description: 'Allow the editor to be scrolled past the end of the last line.'
undoGroupingInterval:
type: 'integer'
default: 300
minimum: 0
},
undoGroupingInterval: {
type: 'integer',
default: 300,
minimum: 0,
description: 'Time interval in milliseconds within which text editing operations will be grouped together in the undo history.'
confirmCheckoutHeadRevision:
type: 'boolean'
default: true
title: 'Confirm Checkout HEAD Revision'
},
confirmCheckoutHeadRevision: {
type: 'boolean',
default: true,
title: 'Confirm Checkout HEAD Revision',
description: 'Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit.'
invisibles:
type: 'object'
description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).'
properties:
eol:
type: ['boolean', 'string']
default: '\u00ac'
maximumLength: 1
},
invisibles: {
type: 'object',
description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).',
properties: {
eol: {
type: ['boolean', 'string'],
default: '¬',
maximumLength: 1,
description: 'Character used to render newline characters (\\n) when the `Show Invisibles` setting is enabled. '
space:
type: ['boolean', 'string']
default: '\u00b7'
maximumLength: 1
},
space: {
type: ['boolean', 'string'],
default: '·',
maximumLength: 1,
description: 'Character used to render leading and trailing space characters when the `Show Invisibles` setting is enabled.'
tab:
type: ['boolean', 'string']
default: '\u00bb'
maximumLength: 1
},
tab: {
type: ['boolean', 'string'],
default: '»',
maximumLength: 1,
description: 'Character used to render hard tab characters (\\t) when the `Show Invisibles` setting is enabled.'
cr:
type: ['boolean', 'string']
default: '\u00a4'
maximumLength: 1
},
cr: {
type: ['boolean', 'string'],
default: '¤',
maximumLength: 1,
description: 'Character used to render carriage return characters (for Microsoft-style line endings) when the `Show Invisibles` setting is enabled.'
zoomFontWhenCtrlScrolling:
type: 'boolean'
default: process.platform isnt 'darwin'
}
}
},
zoomFontWhenCtrlScrolling: {
type: 'boolean',
default: process.platform !== 'darwin',
description: 'Change the editor font size when pressing the Ctrl key and scrolling the mouse up/down.'
}
}
}
}
if process.platform in ['win32', 'linux']
module.exports.core.properties.autoHideMenuBar =
type: 'boolean'
default: false
if (['win32', 'linux'].includes(process.platform)) {
configSchema.core.properties.autoHideMenuBar = {
type: 'boolean',
default: false,
description: 'Automatically hide the menu bar and toggle it by pressing Alt. This is only supported on Windows & Linux.'
}
}
if process.platform is 'darwin'
module.exports.core.properties.useCustomTitleBar =
type: 'boolean'
default: false
if (process.platform === 'darwin') {
configSchema.core.properties.useCustomTitleBar = {
type: 'boolean',
default: false,
description: 'Use custom, theme-aware title bar.<br>Note: This currently does not include a proxy icon.<br>This setting will require a relaunch of Atom to take effect.'
}
}
export default configSchema

View File

@@ -0,0 +1,964 @@
module.exports = new Set([
'AFDKO', 'AFKDO', 'ASS', 'AVX', 'AVX2', 'AVX512', 'AVX512BW', 'AVX512DQ',
'Alignment', 'Alpha', 'AlphaLevel', 'Angle', 'Animation', 'AnimationGroup',
'ArchaeologyDigSiteFrame', 'Arrow__', 'AtLilyPond', 'AttrBaseType',
'AttrSetVal__', 'BackColour', 'Banner', 'Bold', 'Bonlang', 'BorderStyle',
'Browser', 'Button', 'C99', 'CALCULATE', 'CharacterSet', 'ChatScript',
'Chatscript', 'CheckButton', 'ClipboardFormat', 'ClipboardType',
'Clipboard__', 'CodePage', 'Codepages__', 'Collisions', 'ColorSelect',
'ColourActual', 'ColourLogical', 'ColourReal', 'ColourScheme', 'ColourSize',
'Column', 'Comment', 'ConfCachePolicy', 'ControlPoint', 'Cooldown', 'DBE',
'DDL', 'DML', 'DSC', 'Database__', 'DdcMode', 'Dialogue',
'DiscussionFilterType', 'DiscussionStatus', 'DisplaySchemes',
'Document-Structuring-Comment', 'DressUpModel', 'Edit', 'EditBox', 'Effect',
'Encoding', 'End', 'ExternalLinkBehaviour', 'ExternalLinkDirection', 'F16c',
'FMA', 'FilterType', 'Font', 'FontInstance', 'FontString', 'Fontname',
'Fonts__', 'Fontsize', 'Format', 'Frame', 'GameTooltip', 'GroupList', 'HLE',
'HeaderEvent', 'HistoryType', 'HttpVerb', 'II', 'IO', 'Icon', 'IconID',
'InPlaceBox__', 'InPlaceEditEvent', 'Info', 'Italic', 'JSXEndTagStart',
'JSXStartTagEnd', 'KNC', 'KeyModifier', 'Kotlin', 'LUW', 'Language', 'Layer',
'LayeredRegion', 'LdapItemList', 'LineSpacing', 'LinkFilter', 'LinkLimit',
'ListView', 'Locales__', 'Lock', 'LoginPolicy', 'MA_End__', 'MA_StdCombo__',
'MA_StdItem__', 'MA_StdMenu__', 'MISSING', 'Mapping', 'MarginL', 'MarginR',
'MarginV', 'Marked', 'MessageFrame', 'Minimap', 'MovieFrame', 'Name',
'Outline', 'OutlineColour', 'ParentedObject', 'Path', 'Permission', 'PlayRes',
'PlayerModel', 'PrimaryColour', 'Proof', 'QuestPOIFrame', 'RTM',
'RecentModule__', 'Regexp', 'Region', 'Rotation', 'SCADABasic', 'SSA',
'Scale', 'ScaleX', 'ScaleY', 'ScaledBorderAndShadow', 'ScenarioPOIFrame',
'ScriptObject', 'Script__', 'Scroll', 'ScrollEvent', 'ScrollFrame',
'ScrollSide', 'ScrollingMessageFrame', 'SecondaryColour', 'Sensitivity',
'Shadow', 'SimpleHTML', 'Slider', 'Spacing', 'Start', 'StatusBar', 'Stream',
'StrikeOut', 'Style', 'TIS', 'TODO', 'TabardModel', 'Text', 'Texture',
'Timer', 'ToolType', 'Translation', 'TreeView', 'TriggerStatus', 'UIObject',
'Underline', 'UserClass', 'UserList', 'UserNotifyList', 'VisibleRegion',
'Vplus', 'WrapStyle', 'XHPEndTagStart', 'XHPStartTagEnd', 'ZipType',
'__package-name__', '_c', '_function', 'a', 'a10networks', 'aaa', 'abaqus',
'abbrev', 'abbreviated', 'abbreviation', 'abcnotation', 'abl', 'abnf', 'abp',
'absolute', 'abstract', 'academic', 'access', 'access-control',
'access-qualifiers', 'accessed', 'accessor', 'account', 'accumulator', 'ace',
'ace3', 'acl', 'acos', 'act', 'action', 'action-map', 'actionhandler',
'actionpack', 'actions', 'actionscript', 'activerecord', 'activesupport',
'actual', 'acute-accent', 'ada', 'add', 'adddon', 'added', 'addition',
'additional-character', 'additive', 'addon', 'address', 'address-of',
'address-space', 'addrfam', 'adjustment', 'admonition', 'adr', 'adverb',
'adx', 'ael', 'aem', 'aerospace', 'aes', 'aes_functions', 'aesni',
'aexLightGreen', 'af', 'afii', 'aflex', 'after', 'after-expression', 'agc',
'agda', 'agentspeak', 'aggregate', 'aggregation', 'ahk', 'ai-connection',
'ai-player', 'ai-wheeled-vehicle', 'aif', 'alabel', 'alarms', 'alda', 'alert',
'algebraic-type', 'alias', 'aliases', 'align', 'align-attribute', 'alignment',
'alignment-cue-setting', 'alignment-mode', 'all', 'all-once', 'all-solutions',
'allocate', 'alloy', 'alloyglobals', 'alloyxml', 'alog', 'alpha',
'alphabeticalllt', 'alphabeticallyge', 'alphabeticallygt', 'alphabeticallyle',
'alt', 'alter', 'alternate-wysiwyg-string', 'alternates', 'alternation',
'alternatives', 'am', 'ambient-audio-manager', 'ambient-reflectivity', 'amd',
'amd3DNow', 'amdnops', 'ameter', 'amount', 'amp', 'ampersand', 'ampl',
'ampscript', 'an', 'analysis', 'analytics', 'anb', 'anchor', 'and', 'andop',
'angelscript', 'angle', 'angle-brackets', 'angular', 'animation', 'annot',
'annotated', 'annotation', 'annotation-arguments', 'anon', 'anonymous',
'another', 'ansi', 'ansi-c', 'ansi-colored', 'ansi-escape-code',
'ansi-formatted', 'ansi2', 'ansible', 'answer', 'antialiasing', 'antl',
'antlr', 'antlr4', 'anubis', 'any', 'any-method', 'anyclass', 'aolserver',
'apa', 'apache', 'apache-config', 'apc', 'apdl', 'apex', 'api',
'api-notation', 'apiary', 'apib', 'apl', 'apostrophe', 'appcache',
'applescript', 'application', 'application-name', 'application-process',
'approx-equal', 'aql', 'aqua', 'ar', 'arbitrary-radix',
'arbitrary-repetition', 'arbitrary-repitition', 'arch', 'arch_specification',
'architecture', 'archive', 'archives', 'arduino', 'area-code', 'arendelle',
'argcount', 'args', 'argument', 'argument-label', 'argument-separator',
'argument-seperator', 'argument-type', 'arguments', 'arith', 'arithmetic',
'arithmetical', 'arithmeticcql', 'ark', 'arm', 'arma', 'armaConfig',
'arnoldc', 'arp', 'arpop', 'arr', 'array', 'array-expression',
'array-literal', 'arrays', 'arrow', 'articulation', 'artihmetic', 'arvo',
'aryop', 'as', 'as4', 'ascii', 'asciidoc', 'asdoc', 'ash', 'ashx', 'asl',
'asm', 'asm-instruction', 'asm-type-prefix', 'asn', 'asp', 'asp-core-2',
'aspx', 'ass', 'assembly', 'assert', 'assertion', 'assigment', 'assign',
'assign-class', 'assigned', 'assigned-class', 'assigned-value', 'assignee',
'assignement', 'assignment', 'assignmentforge-config', 'associate',
'association', 'associativity', 'assocs', 'asterisk', 'async', 'at-marker',
'at-root', 'at-rule', 'at-sign', 'atmark', 'atml3', 'atoemp', 'atom',
'atom-term-processing', 'atomic', 'atomscript', 'att', 'attachment', 'attr',
'attribute', 'attribute-entry', 'attribute-expression', 'attribute-key-value',
'attribute-list', 'attribute-lookup', 'attribute-name', 'attribute-reference',
'attribute-selector', 'attribute-value', 'attribute-values',
'attribute-with-value', 'attribute_list', 'attribute_value',
'attribute_value2', 'attributelist', 'attributes', 'attrset',
'attrset-or-function', 'audio', 'audio-file', 'auditor', 'augmented', 'auth',
'auth_basic', 'author', 'author-names', 'authorization', 'auto', 'auto-event',
'autoconf', 'autoindex', 'autoit', 'automake', 'automatic', 'autotools',
'autovar', 'aux', 'auxiliary', 'avdl', 'avra', 'avrasm', 'avrdisasm', 'avs',
'avx', 'avx2', 'avx512', 'awk', 'axes_group', 'axis', 'axl', 'b',
'b-spline-patch', 'babel', 'back', 'back-from', 'back-reference',
'back-slash', 'backend', 'background', 'backreference', 'backslash',
'backslash-bar', 'backslash-g', 'backspace', 'backtick', 'bad-ampersand',
'bad-angle-bracket', 'bad-assignment', 'bad-comments-or-CDATA', 'bad-escape',
'bad-octal', 'bad-var', 'bang', 'banner', 'bar', 'bareword', 'barline',
'base', 'base-11', 'base-12', 'base-13', 'base-14', 'base-15', 'base-16',
'base-17', 'base-18', 'base-19', 'base-20', 'base-21', 'base-22', 'base-23',
'base-24', 'base-25', 'base-26', 'base-27', 'base-28', 'base-29', 'base-3',
'base-30', 'base-31', 'base-32', 'base-33', 'base-34', 'base-35', 'base-36',
'base-4', 'base-5', 'base-6', 'base-7', 'base-9', 'base-call', 'base-integer',
'base64', 'base85', 'base_pound_number_pound', 'basetype', 'basic',
'basic-arithmetic', 'basic-type', 'basic_functions', 'basicblock',
'basis-matrix', 'bat', 'batch', 'batchfile', 'battlesim', 'bb', 'bbcode',
'bcmath', 'be', 'beam', 'beamer', 'beancount', 'before', 'begin',
'begin-document', 'begin-emphasis', 'begin-end', 'begin-end-group',
'begin-literal', 'begin-symbolic', 'begintimeblock', 'behaviour', 'bem',
'between-tag-pair', 'bevel', 'bezier-patch', 'bfeac', 'bff', 'bg', 'bg-black',
'bg-blue', 'bg-cyan', 'bg-green', 'bg-normal', 'bg-purple', 'bg-red',
'bg-white', 'bg-yellow', 'bhtml', 'bhv', 'bibitem', 'bibliography-anchor',
'biblioref', 'bibpaper', 'bibtex', 'bif', 'big-arrow', 'big-arrow-left',
'bigdecimal', 'bigint', 'biicode', 'biiconf', 'bin', 'binOp', 'binary',
'binary-arithmetic', 'bind', 'binder', 'binding', 'binding-prefix',
'bindings', 'binop', 'bioinformatics', 'biosphere', 'bird-track', 'bis',
'bison', 'bit', 'bit-and-byte', 'bit-range', 'bit-wise', 'bitarray', 'bitop',
'bits-mov', 'bitvector', 'bitwise', 'black', 'blade', 'blanks', 'blaze',
'blenc', 'blend', 'blending', 'blendtype', 'blendu', 'blendv', 'blip',
'block', 'block-attribute', 'block-dartdoc', 'block-data', 'block-level',
'blockid', 'blockname', 'blockquote', 'blocktitle', 'blue', 'blueprint',
'bluespec', 'blur', 'bm', 'bmi', 'bmi1', 'bmi2', 'bnd', 'bnf', 'body',
'body-statement', 'bold', 'bold-italic-text', 'bold-text', 'bolt', 'bond',
'bonlang', 'boo', 'boogie', 'bool', 'boolean', 'boolean-test', 'boost',
'boot', 'bord', 'border', 'botml', 'bottom', 'boundary', 'bounded', 'bounds',
'bow', 'box', 'bpl', 'bpr', 'bqparam', 'brace', 'braced', 'braces', 'bracket',
'bracketed', 'brackets', 'brainfuck', 'branch', 'branch-point', 'break',
'breakpoint', 'breakpoints', 'breaks', 'bridle', 'brightscript', 'bro',
'broken', 'browser', 'browsers', 'bs', 'bsl', 'btw', 'buffered', 'buffers',
'bugzilla-number', 'build', 'buildin', 'buildout', 'built-in',
'built-in-variable', 'built-ins', 'builtin', 'builtin-comparison', 'builtins',
'bullet', 'bullet-point', 'bump', 'bump-multiplier', 'bundle', 'but',
'button', 'buttons', 'by', 'by-name', 'by-number', 'byref', 'byte',
'bytearray', 'bz2', 'bzl', 'c', 'c-style', 'c0', 'c1', 'c2hs', 'ca', 'cabal',
'cabal-keyword', 'cache', 'cache-management', 'cacheability-control', 'cake',
'calc', 'calca', 'calendar', 'call', 'callable', 'callback', 'caller',
'calling', 'callmethod', 'callout', 'callparent', 'camera', 'camlp4',
'camlp4-stream', 'canonicalized-program-name', 'canopen', 'capability',
'capnp', 'cappuccino', 'caps', 'caption', 'capture', 'capturename',
'cardinal-curve', 'cardinal-patch', 'cascade', 'case', 'case-block',
'case-body', 'case-class', 'case-clause', 'case-clause-body',
'case-expression', 'case-modifier', 'case-pattern', 'case-statement',
'case-terminator', 'case-value', 'cassius', 'cast', 'catch',
'catch-exception', 'catcode', 'categories', 'categort', 'category', 'cba',
'cbmbasic', 'cbot', 'cbs', 'cc', 'cc65', 'ccml', 'cdata', 'cdef', 'cdtor',
'ceiling', 'cell', 'cellcontents', 'cellwall', 'ceq', 'ces', 'cet', 'cexpr',
'cextern', 'ceylon', 'ceylondoc', 'cf', 'cfdg', 'cfengine', 'cfg', 'cfml',
'cfscript', 'cfunction', 'cg', 'cgi', 'cgx', 'chain', 'chained', 'chaining',
'chainname', 'changed', 'changelogs', 'changes', 'channel', 'chapel',
'chapter', 'char', 'characater', 'character', 'character-class',
'character-data-not-allowed-here', 'character-literal',
'character-literal-too-long', 'character-not-allowed-here', 'character-range',
'character-reference', 'character-token', 'character_not_allowed',
'character_not_allowed_here', 'characters', 'chars', 'chars-and-bytes-io',
'charset', 'check', 'check-identifier', 'checkboxes', 'checker', 'chef',
'chem', 'chemical', 'children', 'choice', 'choicescript', 'chord', 'chorus',
'chuck', 'chunk', 'ciexyz', 'circle', 'circle-jot', 'cirru', 'cisco',
'cisco-ios-config', 'citation', 'cite', 'citrine', 'cjam', 'cjson', 'clamp',
'clamping', 'class', 'class-constraint', 'class-constraints',
'class-declaration', 'class-definition', 'class-fns', 'class-instance',
'class-list', 'class-struct-block', 'class-type', 'class-type-definition',
'classcode', 'classes', 'classic', 'classicalb', 'classmethods', 'classobj',
'classtree', 'clause', 'clause-head-body', 'clauses', 'clear',
'clear-argument', 'cleared', 'clflushopt', 'click', 'client', 'client-server',
'clip', 'clipboard', 'clips', 'clmul', 'clock', 'clojure', 'cloned', 'close',
'closed', 'closing', 'closing-text', 'closure', 'clothes-body', 'cm', 'cmake',
'cmb', 'cmd', 'cnet', 'cns', 'cobject', 'cocoa', 'cocor', 'cod4mp', 'code',
'code-example', 'codeblock', 'codepoint', 'codimension', 'codstr', 'coffee',
'coffeescript', 'coffeescript-preview', 'coil', 'collection', 'collision',
'colon', 'colons', 'color', 'color-adjustment', 'coloring', 'colour',
'colour-correction', 'colour-interpolation', 'colour-name', 'colour-scheme',
'colspan', 'column', 'column-divider', 'column-specials', 'com',
'combinators', 'comboboxes', 'comma', 'comma-bar', 'comma-parenthesis',
'command', 'command-name', 'command-synopsis', 'commandline', 'commands',
'comment', 'comment-ish', 'comment-italic', 'commented-out', 'commit-command',
'commit-message', 'commodity', 'common', 'commonform', 'communications',
'community', 'commute', 'comnd', 'compare', 'compareOp', 'comparison',
'compile', 'compile-only', 'compiled', 'compiled-papyrus', 'compiler',
'compiler-directive', 'compiletime', 'compiling-and-loading', 'complement',
'complete', 'completed', 'complex', 'component', 'component-separator',
'component_instantiation', 'compositor', 'compound', 'compound-assignment',
'compress', 'computer', 'computercraft', 'concat', 'concatenated-arguments',
'concatenation', 'concatenator', 'concatination', 'concealed', 'concise',
'concrete', 'condition', 'conditional', 'conditional-directive',
'conditional-short', 'conditionals', 'conditions', 'conf', 'config',
'configuration', 'configure', 'confluence', 'conftype', 'conjunction',
'conky', 'connect', 'connection-state', 'connectivity', 'connstate', 'cons',
'consecutive-tags', 'considering', 'console', 'const', 'const-data',
'constant', 'constants', 'constrained', 'constraint', 'constraints',
'construct', 'constructor', 'constructor-list', 'constructs', 'consult',
'contacts', 'container', 'containers-raycast', 'contains', 'content',
'content-detective', 'contentSupplying', 'contentitem', 'context',
'context-free', 'context-signature', 'continuation', 'continuations',
'continue', 'continued', 'continuum', 'contol', 'contract', 'contracts',
'contrl', 'control', 'control-char', 'control-handlers', 'control-management',
'control-systems', 'control-transfer', 'controller', 'controlline',
'controls', 'contstant', 'conventional', 'conversion', 'convert-type',
'cookie', 'cool', 'coord1', 'coord2', 'coord3', 'coordinates', 'copy',
'copying', 'coq', 'core', 'core-parse', 'coreutils', 'correct', 'cos',
'counter', 'counters', 'cover', 'cplkg', 'cplusplus', 'cpm', 'cpp',
'cpp-include', 'cpp-type', 'cpp_type', 'cpu12', 'cql', 'cram', 'crc32',
'create', 'creation', 'critic', 'crl', 'crontab', 'crypto', 'crystal', 'cs',
'csharp', 'cshtml', 'csi', 'csjs', 'csound', 'csound-document',
'csound-score', 'cspm', 'css', 'csv', 'csx', 'ct', 'ctkey', 'ctor', 'ctxvar',
'ctxvarbracket', 'ctype', 'cubic-bezier', 'cucumber', 'cuda',
'cue-identifier', 'cue-timings', 'cuesheet', 'cup', 'cupsym', 'curl',
'curley', 'curly', 'currency', 'current', 'current-escape-char',
'curve', 'curve-2d', 'curve-fitting', 'curve-reference', 'curve-technique',
'custom', 'customevent', 'cut', 'cve-number', 'cvs', 'cw', 'cxx', 'cy-GB',
'cyan', 'cyc', 'cycle', 'cypher', 'cyrix', 'cython', 'd', 'da', 'daml',
'dana', 'danger', 'danmakufu', 'dark_aqua', 'dark_blue', 'dark_gray',
'dark_green', 'dark_purple', 'dark_red', 'dart', 'dartdoc', 'dash', 'dasm',
'data', 'data-acquisition', 'data-extension', 'data-integrity', 'data-item',
'data-step', 'data-transfer', 'database', 'database-name', 'datablock',
'datablocks', 'datafeed', 'datatype', 'datatypes', 'date', 'date-time',
'datetime', 'dav', 'day', 'dayofmonth', 'dayofweek', 'db', 'dba', 'dbx', 'dc',
'dcon', 'dd', 'ddp', 'de', 'dealii', 'deallocate', 'deb-control', 'debian',
'debris', 'debug', 'debug-specification', 'debugger', 'debugging',
'debugging-comment', 'dec', 'decal', 'decimal', 'decimal-arithmetic',
'decision', 'decl', 'declaration', 'declaration-expr', 'declaration-prod',
'declarations', 'declarator', 'declaratyion', 'declare', 'decode',
'decoration', 'decorator', 'decreasing', 'decrement', 'def', 'default',
'define', 'define-colour', 'defined', 'definedness', 'definingobj',
'definition', 'definitions', 'defintions', 'deflate', 'delay', 'delegated',
'delete', 'deleted', 'deletion', 'delimeter', 'delimited', 'delimiter',
'delimiter-too-long', 'delimiters', 'dense', 'deprecated', 'depricated',
'dereference', 'derived-type', 'deriving', 'desc', 'describe', 'description',
'descriptors', 'design', 'desktop', 'destination', 'destructor',
'destructured', 'determ', 'developer', 'device', 'device-io', 'dformat', 'dg',
'dhcp', 'diagnostic', 'dialogue', 'diamond', 'dict', 'dictionary',
'dictionaryname', 'diff', 'difference', 'different', 'diffuse-reflectivity',
'digdag', 'digit-width', 'dim', 'dimension', 'dip', 'dir', 'dir-target',
'dircolors', 'direct', 'direction', 'directive', 'directive-option',
'directives', 'directory', 'dirjs', 'dirtyblue', 'dirtygreen', 'disable',
'disable-markdown', 'disable-todo', 'discarded', 'discusson', 'disjunction',
'disk', 'disk-folder-file', 'dism', 'displacement', 'display', 'dissolve',
'dissolve-interpolation', 'distribution', 'diverging-function', 'divert',
'divide', 'divider', 'django', 'dl', 'dlv', 'dm', 'dmf', 'dml', 'do',
'dobody', 'doc', 'doc-comment', 'docRoot', 'dockerfile', 'dockerignore',
'doconce', 'docstring', 'doctest', 'doctree-option', 'doctype', 'document',
'documentation', 'documentroot', 'does', 'dogescript', 'doki', 'dollar',
'dollar-quote', 'dollar_variable', 'dom', 'domain', 'dontcollect', 'doors',
'dop', 'dot', 'dot-access', 'dotenv', 'dotfiles', 'dothandout', 'dotnet',
'dotnote', 'dots', 'dotted', 'dotted-circle', 'dotted-del', 'dotted-greater',
'dotted-tack-up', 'double', 'double-arrow', 'double-colon', 'double-dash',
'double-dash-not-allowed', 'double-dot', 'double-number-sign',
'double-percentage', 'double-qoute', 'double-quote', 'double-quoted',
'double-quoted-string', 'double-semicolon', 'double-slash', 'doublequote',
'doubleslash', 'dougle', 'down', 'download', 'downwards', 'doxyfile',
'doxygen', 'dragdrop', 'drawing', 'drive', 'droiuby', 'drop', 'drop-shadow',
'droplevel', 'drummode', 'drupal', 'dsl', 'dsv', 'dt', 'dtl', 'due', 'dummy',
'dummy-variable', 'dump', 'duration', 'dust', 'dust_Conditional',
'dust_end_section_tag', 'dust_filter', 'dust_partial',
'dust_partial_not_self_closing', 'dust_ref', 'dust_ref_name',
'dust_section_context', 'dust_section_name', 'dust_section_params',
'dust_self_closing_section_tag', 'dust_special', 'dust_start_section_tag',
'dustjs', 'dut', 'dwscript', 'dxl', 'dylan', 'dynamic', 'dyndoc', 'dyon', 'e',
'e3globals', 'each', 'eachin', 'earl-grey', 'ebnf', 'ebuild', 'echo',
'eclass', 'ecmascript', 'eco', 'ecr', 'ect', 'ect2', 'ect3', 'ect4', 'edasm',
'edge', 'edit-manager', 'editfields', 'editors', 'ee', 'eex', 'effect',
'effectgroup', 'effective_routine_body', 'effects', 'eiffel', 'eight', 'eio',
'eiz', 'ejectors', 'el', 'elasticsearch', 'elasticsearch2', 'element',
'elements', 'elemnt', 'elif', 'elipse', 'elision', 'elixir', 'ellipsis',
'elm', 'elmx', 'else', 'else-condition', 'else-if', 'elseif',
'elseif-condition', 'elsewhere', 'eltype', 'elvis', 'em', 'email', 'embed',
'embed-diversion', 'embedded', 'embedded-c', 'embedded-ruby', 'embedded2',
'embeded', 'ember', 'emberscript', 'emblem', 'embperl', 'emissive-colour',
'eml', 'emlist', 'emoji', 'emojicode', 'emp', 'emph', 'emphasis', 'empty',
'empty-dictionary', 'empty-list', 'empty-parenthesis', 'empty-start',
'empty-string', 'empty-tag', 'empty-tuple', 'empty-typing-pair', 'empty_gif',
'emptyelement', 'en', 'en-Scouse', 'en-au', 'en-lol', 'en-old', 'en-pirate',
'enable', 'enc', 'enchant', 'enclose', 'encode', 'encoding', 'encryption',
'end', 'end-block-data', 'end-definition', 'end-document', 'end-enum',
'end-footnote', 'end-of-line', 'end-statement', 'end-value', 'endassociate',
'endcode', 'enddo', 'endfile', 'endforall', 'endfunction', 'endian',
'endianness', 'endif', 'endinfo', 'ending', 'ending-space', 'endinterface',
'endlocaltable', 'endmodule', 'endobject', 'endobjecttable', 'endparamtable',
'endprogram', 'endproperty', 'endpropertygroup', 'endpropertygrouptable',
'endpropertytable', 'endselect', 'endstate', 'endstatetable', 'endstruct',
'endstructtable', 'endsubmodule', 'endsubroutine', 'endtimeblock', 'endtype',
'enduserflagsref', 'endvariable', 'endvariabletable', 'endwhere', 'engine',
'enterprise', 'entity', 'entity-creation-and-abolishing',
'entity_instantiation', 'entry', 'entry-definition', 'entry-key',
'entry-type', 'entrypoint', 'enum', 'enum-block', 'enum-declaration',
'enumeration', 'enumerator', 'enumerator-specification', 'env', 'environment',
'environment-variable', 'eo', 'eof', 'epatch', 'eq', 'eqn', 'eqnarray',
'equal', 'equal-or-greater', 'equal-or-less', 'equalexpr', 'equality',
'equals', 'equals-sign', 'equation', 'equation-label', 'erb', 'ereg',
'erlang', 'error', 'error-control', 'errorfunc', 'errorstop', 'es', 'es6',
'es6import', 'esc', 'escape', 'escape-char', 'escape-code', 'escape-sequence',
'escape-unicode', 'escaped', 'escapes', 'escript', 'eso-lua', 'eso-txt',
'essence', 'et', 'eth', 'ethaddr', 'etml', 'etpl', 'eudoc', 'euler',
'euphoria', 'european', 'evaled', 'evaluable', 'evaluation', 'even-tab',
'event', 'event-call', 'event-handler', 'event-handling', 'event-schedulling',
'eventType', 'eventb', 'eventend', 'events', 'evnd', 'exactly', 'example',
'exampleText', 'examples', 'exceeding-sections', 'excel-link', 'exception',
'exceptions', 'exclaimation-point', 'exclamation', 'exec', 'exec-command',
'execution-context', 'exif', 'existential', 'exit', 'exp', 'expand-register',
'expanded', 'expansion', 'expected-array-separator',
'expected-dictionary-separator', 'expected-extends', 'expected-implements',
'expected-range-separator', 'experimental', 'expires', 'expl3', 'explosion',
'exponent', 'exponential', 'export', 'exports', 'expr', 'expression',
'expression-separator', 'expression-seperator', 'expressions',
'expressions-and-types', 'exprwrap', 'ext', 'extempore', 'extend', 'extended',
'extends', 'extension', 'extension-specification', 'extensions', 'extern',
'extern-block', 'external', 'external-call', 'external-signature', 'extersk',
'extglob', 'extra', 'extra-characters', 'extra-equals-sign', 'extracted',
'extras', 'extrassk', 'exxample', 'eztpl', 'f', 'f5networks', 'fa', 'face',
'fact', 'factor', 'factorial', 'fadeawayheight', 'fadeawaywidth', 'fail',
'fakeroot', 'fallback', 'fallout4', 'false', 'fandoc', 'fann', 'fantom',
'fastcgi', 'fbaccidental', 'fbfigure', 'fbgroupclose', 'fbgroupopen', 'fbp',
'fctn', 'fe', 'feature', 'features', 'feedrate', 'fenced', 'fftwfn', 'fhem',
'fi', 'field', 'field-assignment', 'field-completions', 'field-id',
'field-level-comment', 'field-name', 'field-tag', 'fields', 'figbassmode',
'figure', 'figuregroup', 'filder-design-hdl-coder', 'file', 'file-i-o',
'file-io', 'file-name', 'file-object', 'file-path', 'fileinfo', 'filename',
'filepath', 'filetest', 'filter', 'filter-pipe', 'filteredtranscludeblock',
'filters', 'final', 'final-procedure', 'finally', 'financial',
'financial-derivatives', 'find', 'find-in-files', 'find-m', 'finder',
'finish', 'finn', 'fire', 'firebug', 'first', 'first-class', 'first-line',
'fish', 'fitnesse', 'five', 'fix_this_later', 'fixed', 'fixed-income',
'fixed-point', 'fixme', 'fl', 'flag', 'flag-control', 'flags', 'flash',
'flatbuffers', 'flex-config', 'fload', 'float', 'float-exponent', 'float_exp',
'floating-point', 'floating_point', 'floor', 'flow', 'flow-control',
'flowcontrol', 'flows', 'flowtype', 'flush', 'fma', 'fma4', 'fmod', 'fn',
'fold', 'folder', 'folder-actions', 'following', 'font',
'font-cache', 'font-face', 'font-name', 'font-size', 'fontface', 'fontforge',
'foobar', 'footer', 'footnote', 'for', 'for-in-loop', 'for-loop',
'for-quantity', 'forall', 'force', 'foreach', 'foreign', 'forever',
'forge-config', 'forin', 'form', 'form-feed', 'formal', 'format',
'format-register', 'format-verb', 'formatted', 'formatter', 'formatting',
'forth', 'fortran', 'forward', 'foundation', 'fountain', 'four',
'fourd-command', 'fourd-constant', 'fourd-constant-hex',
'fourd-constant-number', 'fourd-constant-string', 'fourd-control-begin',
'fourd-control-end', 'fourd-declaration', 'fourd-declaration-array',
'fourd-local-variable', 'fourd-parameter', 'fourd-table', 'fourd-tag',
'fourd-variable', 'fpm', 'fpu', 'fpu_x87', 'fr', 'fragment', 'frame',
'frames', 'frametitle', 'framexml', 'free', 'free-form', 'freebasic',
'freefem', 'freespace2', 'from', 'from-file', 'front-matter', 'fs', 'fs2',
'fsc', 'fsgsbase', 'fsharp', 'fsi', 'fsl', 'fsm', 'fsp', 'fsx', 'fth', 'ftl',
'ftl20n', 'full-line', 'full-stop', 'fun', 'funarg', 'func-tag', 'func_call',
'funchand', 'function', 'function-arity', 'function-attribute',
'function-call', 'function-definition', 'function-literal',
'function-parameter', 'function-recursive', 'function-return',
'function-type', 'functionDeclaration', 'functionDefinition',
'function_definition', 'function_prototype', 'functional_test', 'functionend',
'functions', 'functionstart', 'fundimental', 'funk', 'funtion-definition',
'fus', 'future', 'futures', 'fuzzy-logic', 'fx', 'fx-foliage-replicator',
'fx-light', 'fx-shape-replicator', 'fx-sun-light', 'g', 'g-code', 'ga',
'gain', 'galaxy', 'gallery', 'game-base', 'game-connection', 'game-server',
'gamebusk', 'gamescript', 'gams', 'gams-lst', 'gap', 'garch', 'gather',
'gcode', 'gdb', 'gdscript', 'gdx', 'ge', 'geant4-macro', 'geck',
'geck-keyword', 'general', 'general-purpose', 'generate', 'generator',
'generic', 'generic-config', 'generic-spec', 'generic-type', 'generic_list',
'genericcall', 'generics', 'genetic-algorithms', 'geo', 'geometric',
'geometry', 'geometry-adjustment', 'get', 'getproperty', 'getsec', 'getset',
'getter', 'gettext', 'getword', 'gfm', 'gfm-todotxt', 'gfx', 'gh-number',
'gherkin', 'gisdk', 'git', 'git-attributes', 'git-commit', 'git-config',
'git-rebase', 'gitignore', 'given', 'gj', 'gl', 'glob', 'global',
'global-functions', 'globals', 'globalsection', 'glsl', 'glue',
'glyph_class_name', 'glyphname-value', 'gml', 'gmp', 'gmsh', 'gmx', 'gn',
'gnu', 'gnuplot', 'go', 'goal', 'goatee', 'godmode', 'gohtml', 'gold', 'golo',
'google', 'gosub', 'gotemplate', 'goto', 'goto-label', 'gpd', 'gpd_note',
'gpp', 'grace', 'grade-down', 'grade-up', 'gradient', 'gradle', 'grails',
'grammar', 'grammar-rule', 'grammar_production', 'grap', 'grapahql', 'graph',
'graphics', 'graphql', 'grave-accent', 'gray', 'greater', 'greater-equal',
'greater-or-equal', 'greek', 'greek-letter', 'green', 'gremlin', 'grey',
'grg', 'grid-table', 'gridlists', 'grog', 'groovy', 'groovy-properties',
'group', 'group-level-comment', 'group-name', 'group-number',
'group-reference', 'group-title', 'group1', 'group10', 'group11', 'group2',
'group3', 'group4', 'group5', 'group6', 'group7', 'group8', 'group9',
'groupend', 'groupflag', 'grouping-statement', 'groupname', 'groupstart',
'growl', 'grr', 'gs', 'gsc', 'gsp', 'gt', 'guard', 'guards', 'gui',
'gui-bitmap-ctrl', 'gui-button-base-ctrl', 'gui-canvas', 'gui-control',
'gui-filter-ctrl', 'gui-frameset-ctrl', 'gui-menu-bar',
'gui-message-vector-ctrl', 'gui-ml-text-ctrl', 'gui-popup-menu-ctrl',
'gui-scroll-ctrl', 'gui-slider-ctrl', 'gui-text-ctrl', 'gui-text-edit-ctrl',
'gui-text-list-ctrl', 'guid', 'guillemot', 'guis', 'gzip', 'gzip_static', 'h',
'h1', 'hack', 'hackfragment', 'haddock', 'hairpin', 'ham', 'haml', 'hamlbars',
'hamlc', 'hamlet', 'hamlpy', 'handlebar', 'handlebars', 'handler',
'hanging-paragraph', 'haproxy-config', 'harbou', 'harbour', 'hard-break',
'hardlinebreaks', 'hash', 'hash-tick', 'hashbang', 'hashicorp', 'hashkey',
'haskell', 'haxe', 'hbs', 'hcl', 'hdl', 'hdr', 'he', 'header',
'header-continuation', 'header-value', 'headername', 'headers', 'heading',
'heading-0', 'heading-1', 'heading-2', 'heading-3', 'heading-4', 'heading-5',
'heading-6', 'height', 'helen', 'help', 'helper', 'helpers', 'heredoc',
'heredoc-token', 'herestring', 'heritage', 'hex', 'hex-ascii', 'hex-byte',
'hex-literal', 'hex-old', 'hex-string', 'hex-value', 'hex8', 'hexadecimal',
'hexidecimal', 'hexprefix', 'hg-commit', 'hgignore', 'hi', 'hidden', 'hide',
'high-minus', 'highlight-end', 'highlight-group',
'highlight-start', 'hint', 'history', 'hive', 'hive-name', 'hjson', 'hl7',
'hlsl', 'hn', 'hoa', 'hoc', 'hocharacter', 'hocomment', 'hocon', 'hoconstant',
'hocontinuation', 'hocontrol', 'hombrew-formula', 'homebrew', 'homematic',
'hook', 'hoon', 'horizontal-blending', 'horizontal-packed-arithmetic',
'horizontal-rule', 'hostname', 'hosts', 'hour', 'hours', 'hps', 'hql', 'hr',
'hrm', 'hs', 'hsc2hs', 'ht', 'htaccess', 'htl', 'html', 'html_entity',
'htmlbars', 'http', 'hu', 'hungary', 'hxml', 'hy', 'hydrant', 'hydrogen',
'hyperbolic', 'hyperlink', 'hyphen', 'hyphenation', 'hyphenation-char', 'i',
'i-beam', 'i18n', 'iRev', 'ice', 'icinga2', 'icmc', 'icmptype', 'icmpv6type',
'icmpxtype', 'iconv', 'id', 'id-type', 'id-with-protocol', 'idd', 'ideal',
'identical', 'identifer', 'identified', 'identifier', 'identifier-type',
'identifiers-and-DTDs', 'identity', 'idf', 'idl', 'idris', 'ieee', 'if',
'if-block', 'if-branch', 'if-condition', 'if-else', 'if-then', 'ifacespec',
'ifdef', 'ifname', 'ifndef', 'ignore', 'ignore-eol', 'ignore-errors',
'ignorebii', 'ignored', 'ignored-binding', 'ignoring', 'iisfunc', 'ijk',
'ilasm', 'illagal', 'illeagal', 'illegal', 'illumination-model', 'image',
'image-acquisition', 'image-alignment', 'image-option', 'image-processing',
'images', 'imap', 'imba', 'imfchan', 'img', 'immediate',
'immediately-evaluated', 'immutable', 'impex', 'implementation',
'implementation-defined-hooks', 'implemented', 'implements', 'implicit',
'import', 'import-all', 'importall', 'important', 'in', 'in-block',
'in-module', 'in-out', 'inappropriate', 'include', 'include-statement',
'includefile', 'incomplete', 'incomplete-variable-assignment', 'inconsistent',
'increment', 'increment-decrement', 'indent', 'indented',
'indented-paragraph', 'indepimage', 'index', 'index-seperator', 'indexed',
'indexer', 'indexes', 'indicator', 'indices', 'indirect', 'indirection',
'individual-enum-definition', 'individual-rpc-call', 'inet', 'inetprototype',
'inferred', 'infes', 'infinity', 'infix', 'info', 'inform', 'inform6',
'inform7', 'infotype', 'ingore-eol', 'inherit', 'inheritDoc', 'inheritance',
'inherited', 'inherited-class', 'inherited-struct', 'inherits', 'ini', 'init',
'initial-lowercase', 'initial-uppercase', 'initial-value', 'initialization',
'initialize', 'initializer-list', 'ink', 'inline', 'inline-data',
'inlineConditionalBranchSeparator', 'inlineConditionalClause',
'inlineConditionalEnd', 'inlineConditionalStart', 'inlineLogicEnd',
'inlineLogicStart', 'inlineSequenceEnd', 'inlineSequenceSeparator',
'inlineSequenceStart', 'inlineSequenceTypeChar', 'inlineblock', 'inlinecode',
'inlinecomment', 'inlinetag', 'inner', 'inner-class', 'inno', 'ino', 'inout',
'input', 'inquire', 'inserted', 'insertion', 'insertion-and-extraction',
'inside', 'install', 'instance', 'instancemethods', 'instanceof', 'instances',
'instantiation', 'instruction', 'instruction-pointer', 'instructions',
'instrument', 'instrument-block', 'instrument-control',
'instrument-declaration', 'int', 'int32', 'int64', 'integer', 'integer-float',
'intel', 'intel-hex', 'intent', 'intepreted', 'interaction', 'interbase',
'interface', 'interface-block', 'interface-or-protocol', 'interfaces',
'interior-instance', 'interiors', 'interlink', 'internal', 'internet',
'interpolate-argument', 'interpolate-string', 'interpolate-variable',
'interpolated', 'interpolation', 'interrupt', 'intersection', 'interval',
'intervalOrList', 'intl', 'intrinsic', 'intuicio4', 'invalid',
'invalid-character', 'invalid-character-escape', 'invalid-inequality',
'invalid-quote', 'invalid-variable-name', 'invariant', 'invocation', 'invoke',
'invokee', 'io', 'ior', 'iota', 'ip', 'ip-port', 'ip6', 'ipkg', 'ipsec',
'ipv4', 'ipv6', 'ipynb', 'irct', 'irule', 'is', 'isa', 'isc', 'iscexport',
'isclass', 'isml', 'issue', 'it', 'italic', 'italic-text', 'item',
'item-access', 'itemlevel', 'items', 'iteration', 'itunes', 'ivar', 'ja',
'jack', 'jade', 'jakefile', 'jasmin', 'java', 'java-properties', 'java-props',
'javadoc', 'javascript', 'jbeam', 'jekyll', 'jflex', 'jibo-rule', 'jinja',
'jison', 'jisonlex', 'jmp', 'joint', 'joker', 'jolie', 'jot', 'journaling',
'jpl', 'jq', 'jquery', 'js', 'js-label', 'jsdoc', 'jsduck', 'jsim', 'json',
'json5', 'jsoniq', 'jsonnet', 'jsont', 'jsp', 'jsx', 'julia', 'julius',
'jump', 'juniper', 'juniper-junos-config', 'junit-test-report', 'junos',
'juttle', 'jv', 'jxa', 'k', 'kag', 'kagex', 'kb', 'kbd', 'kconfig',
'kerboscript', 'kernel', 'kevs', 'kevscript', 'kewyword', 'key',
'key-assignment', 'key-letter', 'key-pair', 'key-path', 'key-value',
'keyboard', 'keyframe', 'keyframes', 'keygroup', 'keyname', 'keyspace',
'keyspace-name', 'keyvalue', 'keyword', 'keyword-parameter', 'keyword1',
'keyword2', 'keyword3', 'keyword4', 'keyword5', 'keyword6', 'keyword7',
'keyword8', 'keyword_arrays', 'keyword_objects', 'keyword_roots',
'keyword_string', 'keywords', 'keywork', 'kickstart', 'kind', 'kmd', 'kn',
'knitr', 'knockout', 'knot', 'ko', 'ko-virtual', 'kos', 'kotlin', 'krl',
'ksp-cfg', 'kspcfg', 'kurumin', 'kv', 'kxi', 'kxigauge', 'l', 'l20n',
'l4proto', 'label', 'label-expression', 'labeled', 'labeled-parameter',
'labelled-thing', 'lagda', 'lambda', 'lambda-function', 'lammps', 'langref',
'language', 'language-range', 'languagebabel', 'langversion', 'largesk',
'lasso', 'last', 'last-paren-match', 'latex', 'latex2', 'latino', 'latte',
'launch', 'layout', 'layoutbii', 'lbsearch', 'lc', 'lc-3', 'lcb', 'ldap',
'ldif', 'le', 'leader-char', 'leading', 'leading-space', 'leading-tabs',
'leaf', 'lean', 'ledger', 'left', 'left-margin', 'leftshift', 'lefttoright',
'legacy', 'legacy-setting', 'lemon', 'len', 'length', 'leopard', 'less',
'less-equal', 'less-or-equal', 'let', 'letter', 'level', 'level-of-detail',
'level1', 'level2', 'level3', 'level4', 'level5', 'level6', 'levels', 'lex',
'lexc', 'lexical', 'lf-in-string', 'lhs', 'li', 'lib', 'libfile', 'library',
'libs', 'libxml', 'lid', 'lifetime', 'ligature', 'light', 'light_purple',
'lighting', 'lightning', 'lilypond', 'lilypond-drummode',
'lilypond-figbassmode', 'lilypond-figuregroup', 'lilypond-internals',
'lilypond-lyricsmode', 'lilypond-markupmode', 'lilypond-notedrum',
'lilypond-notemode', 'lilypond-notemode-explicit', 'lilypond-notenames',
'lilypond-schememode', 'limit_zone', 'line-block', 'line-break',
'line-continuation', 'line-cue-setting', 'line-statement',
'line-too-long', 'linebreak', 'linenumber', 'link', 'link-label',
'link-text', 'link-url', 'linkage', 'linkage-type', 'linkedin',
'linkedsockets', 'linkplain', 'linkplain-label', 'linq', 'linuxcncgcode',
'liquid', 'liquidhaskell', 'liquidsoap', 'lisp', 'lisp-repl', 'list',
'list-done', 'list-separator', 'list-style-type', 'list-today', 'list_item',
'listing', 'listnum', 'listvalues', 'litaco', 'litcoffee', 'literal',
'literal-string', 'literate', 'litword', 'livecodescript', 'livescript',
'livescriptscript', 'll', 'llvm', 'load-constants', 'load-hint', 'loader',
'local', 'local-variables', 'localhost', 'localizable', 'localized',
'localname', 'locals', 'localtable', 'location', 'lock', 'log', 'log-debug',
'log-error', 'log-failed', 'log-info', 'log-patch', 'log-success',
'log-verbose', 'log-warning', 'logarithm', 'logging', 'logic', 'logicBegin',
'logical', 'logical-expression', 'logicblox', 'logicode', 'logo', 'logstash',
'logtalk', 'lol', 'long', 'look-ahead', 'look-behind', 'lookahead',
'lookaround', 'lookbehind', 'loop', 'loop-control', 'low-high', 'lowercase',
'lowercase_character_not_allowed_here', 'lozenge', 'lparen', 'lsg', 'lsl',
'lst', 'lst-cpu12', 'lstdo', 'lt', 'lt-gt', 'lterat', 'lu', 'lua', 'lucee',
'lucius', 'lury', 'lv', 'lyricsmode', 'm', 'm4', 'm4sh', 'm65816', 'm68k',
'mac-classic', 'mac-fsaa', 'machine', 'machineclause', 'macro', 'macro-usage',
'macro11', 'macrocallblock', 'macrocallinline', 'madoko', 'magenta', 'magic',
'magik', 'mail', 'mailer', 'mailto', 'main', 'makefile', 'makefile2', 'mako',
'mamba', 'man', 'mantissa', 'manualmelisma', 'map', 'map-library', 'map-name',
'mapfile', 'mapkey', 'mapping', 'mapping-type', 'maprange', 'marasm',
'margin', 'marginpar', 'mark', 'mark-input', 'markdown', 'marker', 'marko',
'marko-attribute', 'marko-tag', 'markup', 'markupmode', 'mas2j', 'mask',
'mason', 'mat', 'mata', 'match', 'match-bind', 'match-branch',
'match-condition', 'match-definition', 'match-exception', 'match-option',
'match-pattern', 'material', 'material-library', 'material-name', 'math',
'math-symbol', 'math_complex', 'math_real', 'mathematic', 'mathematica',
'mathematical', 'mathematical-symbols', 'mathematics', 'mathjax', 'mathml',
'matlab', 'matrix', 'maude', 'maven', 'max', 'max-angle', 'max-distance',
'max-length', 'maxscript', 'maybe', 'mb', 'mbstring', 'mc', 'mcc', 'mccolor',
'mch', 'mcn', 'mcode', 'mcq', 'mcr', 'mcrypt', 'mcs', 'md', 'mdash', 'mdoc',
'mdx', 'me', 'measure', 'media', 'media-feature', 'media-property',
'media-type', 'mediawiki', 'mei', 'mel', 'memaddress', 'member',
'member-function-attribute', 'member-of', 'membership', 'memcache',
'memcached', 'memoir', 'memoir-alltt', 'memoir-fbox', 'memoir-verbatim',
'memory', 'memory-management', 'memory-protection', 'memos', 'menhir',
'mention', 'menu', 'mercury', 'merge-group', 'merge-key', 'merlin',
'mesgTrigger', 'mesgType', 'message', 'message-declaration',
'message-forwarding-handler', 'message-sending', 'message-vector', 'messages',
'meta', 'meta-conditional', 'meta-data', 'meta-file', 'meta-info',
'metaclass', 'metacommand', 'metadata', 'metakey', 'metamodel', 'metapost',
'metascript', 'meteor', 'method', 'method-call', 'method-definition',
'method-modification', 'method-mofification', 'method-parameter',
'method-parameters', 'method-restriction', 'methodcalls', 'methods',
'metrics', 'mhash', 'microsites', 'microsoft-dynamics', 'middle',
'midi_processing', 'migration', 'mime', 'min', 'minelua', 'minetweaker',
'minitemplate', 'minitest', 'minus', 'minute', 'mips', 'mirah', 'misc',
'miscellaneous', 'mismatched', 'missing', 'missing-asterisk',
'missing-inheritance', 'missing-parameters', 'missing-section-begin',
'missingend', 'mission-area', 'mixin', 'mixin-name', 'mjml', 'ml', 'mlab',
'mls', 'mm', 'mml', 'mmx', 'mmx_instructions', 'mn', 'mnemonic',
'mobile-messaging', 'mochi', 'mod', 'mod-r', 'mod_perl', 'mod_perl_1',
'modblock', 'modbus', 'mode', 'model', 'model-based-calibration',
'model-predictive-control', 'modelica', 'modelicascript', 'modeline',
'models', 'modern', 'modified', 'modifier', 'modifiers', 'modify',
'modify-range', 'modifytime', 'modl', 'modr', 'modula-2', 'module',
'module-alias', 'module-binding', 'module-definition', 'module-expression',
'module-function', 'module-reference', 'module-rename', 'module-sum',
'module-type', 'module-type-definition', 'modules', 'modulo', 'modx',
'mojolicious', 'mojom', 'moment', 'mond', 'money', 'mongo', 'mongodb',
'monicelli', 'monitor', 'monkberry', 'monkey', 'monospace', 'monospaced',
'monte', 'month', 'moon', 'moonscript', 'moos', 'moose', 'moosecpp', 'motion',
'mouse', 'mov', 'movement', 'movie', 'movie-file', 'mozu', 'mpw', 'mpx',
'mqsc', 'ms', 'mscgen', 'mscript', 'msg', 'msgctxt', 'msgenny', 'msgid',
'msgstr', 'mson', 'mson-block', 'mss', 'mta', 'mtl', 'mucow', 'mult', 'multi',
'multi-line', 'multi-symbol', 'multi-threading', 'multiclet', 'multids-file',
'multiline', 'multiline-cell', 'multiline-text-reference',
'multiline-tiddler-title', 'multimethod', 'multipart', 'multiplication',
'multiplicative', 'multiply', 'multiverse', 'mumps', 'mundosk', 'music',
'must_be', 'mustache', 'mut', 'mutable', 'mutator', 'mx', 'mxml', 'mydsl1',
'mylanguage', 'mysql', 'mysqli', 'mysqlnd-memcache', 'mysqlnd-ms',
'mysqlnd-qc', 'mysqlnd-uh', 'mzn', 'nabla', 'nagios', 'name', 'name-list',
'name-of-parameter', 'named', 'named-char', 'named-key', 'named-tuple',
'nameless-typed', 'namelist', 'names', 'namespace', 'namespace-block',
'namespace-definition', 'namespace-language', 'namespace-prefix',
'namespace-reference', 'namespace-statement', 'namespaces', 'nan', 'nand',
'nant', 'nant-build', 'narration', 'nas', 'nasal', 'nasl', 'nasm', 'nastran',
'nat', 'native', 'nativeint', 'natural', 'navigation', 'nbtkey', 'ncf', 'ncl',
'ndash', 'ne', 'nearley', 'neg-ratio', 'negatable', 'negate', 'negated',
'negation', 'negative', 'negative-look-ahead', 'negative-look-behind',
'negativity', 'nesc', 'nessuskb', 'nested', 'nested_braces',
'nested_brackets', 'nested_ltgt', 'nested_parens', 'nesty', 'net',
'net-object', 'netbios', 'network', 'network-value', 'networking',
'neural-network', 'new', 'new-line', 'new-object', 'newline',
'newline-spacing', 'newlinetext', 'newlisp', 'newobject', 'nez', 'nft',
'ngdoc', 'nginx', 'nickname', 'nil', 'nim', 'nine', 'ninja', 'ninjaforce',
'nit', 'nitro', 'nix', 'nl', 'nlf', 'nm', 'nm7', 'no', 'no-capture',
'no-completions', 'no-content', 'no-default', 'no-indent',
'no-leading-digits', 'no-trailing-digits', 'no-validate-params', 'node',
'nogc', 'noindent', 'nokia-sros-config', 'non', 'non-capturing',
'non-immediate', 'non-null-typehinted', 'non-standard', 'non-terminal',
'nondir-target', 'none', 'none-parameter', 'nonlocal', 'nonterminal', 'noon',
'noop', 'nop', 'noparams', 'nor', 'normal', 'normal_numeric',
'normal_objects', 'normal_text', 'normalised', 'not', 'not-a-number',
'not-equal', 'not-identical', 'notation', 'note', 'notechord', 'notemode',
'notequal', 'notequalexpr', 'notes', 'notidentical', 'notification', 'nowdoc',
'noweb', 'nrtdrv', 'nsapi', 'nscript', 'nse', 'nsis', 'nsl', 'ntriples',
'nul', 'null', 'nullify', 'nullological', 'nulltype', 'num', 'number',
'number-sign', 'number-sign-equals', 'numbered', 'numberic', 'numbers',
'numbersign', 'numeric', 'numeric_std', 'numerical', 'nunjucks', 'nut',
'nvatom', 'nxc', 'o', 'obj', 'objaggregation', 'objc', 'objcpp', 'objdump',
'object', 'object-comments', 'object-definition', 'object-level-comment',
'object-name', 'objects', 'objectset', 'objecttable', 'objectvalues', 'objj',
'obsolete', 'ocaml', 'ocamllex', 'occam', 'oci8', 'ocmal', 'oct', 'octal',
'octave', 'octave-change', 'octave-shift', 'octet', 'octo', 'octobercms',
'octothorpe', 'odd-tab', 'odedsl', 'ods', 'of', 'off', 'offset', 'ofx',
'ogre', 'ok', 'ol', 'old', 'old-style', 'omap', 'omitted', 'on-background',
'on-error', 'once', 'one', 'one-sixth-em', 'one-twelfth-em', 'oniguruma',
'oniguruma-comment', 'only', 'only-in', 'onoff', 'ooc', 'oot', 'op-domain',
'op-range', 'opa', 'opaque', 'opc', 'opcache', 'opcode',
'opcode-argument-types', 'opcode-declaration', 'opcode-definition',
'opcode-details', 'open', 'open-gl', 'openal', 'openbinding', 'opencl',
'opendss', 'opening', 'opening-text', 'openmp', 'openssl', 'opentype',
'operand', 'operands', 'operation', 'operator', 'operator2', 'operator3',
'operators', 'opmask', 'opmaskregs', 'optical-density', 'optimization',
'option', 'option-description', 'option-toggle', 'optional',
'optional-parameter', 'optional-parameter-assignment', 'optionals',
'optionname', 'options', 'optiontype', 'or', 'oracle', 'orbbasic', 'orcam',
'orchestra', 'order', 'ordered', 'ordered-block', 'ordinal', 'organized',
'orgtype', 'origin', 'osiris', 'other', 'other-inherited-class',
'other_buildins', 'other_keywords', 'others', 'otherwise',
'otherwise-expression', 'out', 'outer', 'output', 'overload', 'override',
'owner', 'ownership', 'oz', 'p', 'p4', 'p5', 'p8', 'pa', 'package',
'package-definition', 'package_body', 'packages', 'packed',
'packed-arithmetic', 'packed-blending', 'packed-comparison',
'packed-conversion', 'packed-floating-point', 'packed-integer', 'packed-math',
'packed-mov', 'packed-other', 'packed-shift', 'packed-shuffle', 'packed-test',
'padlock', 'page', 'page-props', 'pagebreak', 'pair', 'pair-programming',
'paket', 'pandoc', 'papyrus', 'papyrus-assembly', 'paragraph', 'parallel',
'param', 'param-list', 'paramater', 'paramerised-type', 'parameter',
'parameter-entity', 'parameter-space', 'parameterless', 'parameters',
'paramless', 'params', 'paramtable', 'paramter', 'paren', 'paren-group',
'parens', 'parent', 'parent-reference', 'parent-selector',
'parent-selector-suffix', 'parenthases', 'parentheses', 'parenthesis',
'parenthetical', 'parenthetical_list', 'parenthetical_pair', 'parfor',
'parfor-quantity', 'parse', 'parsed', 'parser', 'parser-function',
'parser-token', 'parser3', 'part', 'partial', 'particle', 'pascal', 'pass',
'pass-through', 'passive', 'passthrough', 'password', 'password-hash',
'patch', 'path', 'path-camera', 'path-pattern', 'pathoperation', 'paths',
'pathspec', 'patientId', 'pattern', 'pattern-argument', 'pattern-binding',
'pattern-definition', 'pattern-match', 'pattern-offset', 'patterns', 'pause',
'payee', 'payload', 'pbo', 'pbtxt', 'pcdata', 'pcntl', 'pdd', 'pddl', 'ped',
'pegcoffee', 'pegjs', 'pending', 'percentage', 'percentage-sign',
'percussionnote', 'period', 'perl', 'perl-section', 'perl6', 'perl6fe',
'perlfe', 'perlt6e', 'perm', 'permutations', 'personalization', 'pervasive',
'pf', 'pflotran', 'pfm', 'pfx', 'pgn', 'pgsql', 'phone', 'phone-number',
'phonix', 'php', 'php-code-in-comment', 'php_apache', 'php_dom', 'php_ftp',
'php_imap', 'php_mssql', 'php_odbc', 'php_pcre', 'php_spl', 'php_zip',
'phpdoc', 'phrasemodifiers', 'phraslur', 'physical-zone', 'physics', 'pi',
'pic', 'pick', 'pickup', 'picture', 'pig', 'pillar', 'pipe', 'pipe-sign',
'pipeline', 'piratesk', 'pitch', 'pixie', 'pkgbuild', 'pl', 'placeholder',
'placeholder-parts', 'plain', 'plainsimple-emphasize', 'plainsimple-heading',
'plainsimple-number', 'plantuml', 'player', 'playerversion', 'pld_modeling',
'please-build', 'please-build-defs', 'plist', 'plsql', 'plugin', 'plus',
'plztarget', 'pmc', 'pml', 'pmlPhysics-arrangecharacter',
'pmlPhysics-emphasisequote', 'pmlPhysics-graphic', 'pmlPhysics-header',
'pmlPhysics-htmlencoded', 'pmlPhysics-links', 'pmlPhysics-listtable',
'pmlPhysics-physicalquantity', 'pmlPhysics-relationships',
'pmlPhysics-slides', 'pmlPhysics-slidestacks', 'pmlPhysics-speech',
'pmlPhysics-structure', 'pnt', 'po', 'pod', 'poe', 'pogoscript', 'point',
'point-size', 'pointer', 'pointer-arith', 'pointer-following', 'points',
'polarcoord', 'policiesbii', 'policy', 'polydelim', 'polygonal', 'polymer',
'polymorphic', 'polymorphic-variant', 'polynomial-degree', 'polysep', 'pony',
'port', 'port_list', 'pos-ratio', 'position-cue-setting', 'positional',
'positive', 'posix', 'posix-reserved', 'post-match', 'postblit', 'postcss',
'postfix', 'postpone', 'postscript', 'potigol', 'potion', 'pound',
'pound-sign', 'povray', 'power', 'power_set', 'powershell', 'pp', 'ppc',
'ppcasm', 'ppd', 'praat', 'pragma', 'pragma-all-once', 'pragma-mark',
'pragma-message', 'pragma-newline-spacing', 'pragma-newline-spacing-value',
'pragma-once', 'pragma-stg', 'pragma-stg-value', 'pre', 'pre-defined',
'pre-match', 'preamble', 'prec', 'precedence', 'precipitation', 'precision',
'precision-point', 'pred', 'predefined', 'predicate', 'prefetch',
'prefetchwt', 'prefix', 'prefixed-uri', 'prefixes', 'preinst', 'prelude',
'prepare', 'prepocessor', 'preposition', 'prepositional', 'preprocessor',
'prerequisites', 'preset', 'preview', 'previous', 'prg', 'primary',
'primitive', 'primitive-datatypes', 'primitive-field', 'print',
'print-argument', 'priority', 'prism', 'private', 'privileged', 'pro',
'probe', 'proc', 'procedure', 'procedure_definition', 'procedure_prototype',
'process', 'process-id', 'process-substitution', 'processes', 'processing',
'proctitle', 'production', 'profile', 'profiling', 'program', 'program-block',
'program-name', 'progressbars', 'proguard', 'project', 'projectile', 'prolog',
'prolog-flags', 'prologue', 'promoted', 'prompt', 'prompt-prefix', 'prop',
'properties', 'properties_literal', 'property', 'property-flag',
'property-list', 'property-name', 'property-value',
'property-with-attributes', 'propertydef', 'propertyend', 'propertygroup',
'propertygrouptable', 'propertyset', 'propertytable', 'proposition',
'protection', 'protections', 'proto', 'protobuf', 'protobufs', 'protocol',
'protocol-specification', 'prototype', 'provision', 'proxy', 'psci', 'pseudo',
'pseudo-class', 'pseudo-element', 'pseudo-method', 'pseudo-mnemonic',
'pseudo-variable', 'pshdl', 'pspell', 'psql', 'pt', 'ptc-config',
'ptc-config-modelcheck', 'pthread', 'ptr', 'ptx', 'public', 'pug',
'punchcard', 'punctual', 'punctuation', 'punctutation', 'puncuation',
'puncutation', 'puntuation', 'puppet', 'purebasic', 'purescript', 'pweave',
'pwisa', 'pwn', 'py2pml', 'pyj', 'pyjade', 'pymol', 'pyresttest', 'python',
'python-function', 'q', 'q-brace', 'q-bracket', 'q-ltgt', 'q-paren', 'qa',
'qm', 'qml', 'qos', 'qoute', 'qq', 'qq-brace', 'qq-bracket', 'qq-ltgt',
'qq-paren', 'qry', 'qtpro', 'quad', 'quad-arrow-down', 'quad-arrow-left',
'quad-arrow-right', 'quad-arrow-up', 'quad-backslash', 'quad-caret-down',
'quad-caret-up', 'quad-circle', 'quad-colon', 'quad-del-down', 'quad-del-up',
'quad-diamond', 'quad-divide', 'quad-equal', 'quad-jot', 'quad-less',
'quad-not-equal', 'quad-question', 'quad-quote', 'quad-slash', 'quadrigraph',
'qual', 'qualified', 'qualifier', 'quality', 'quant', 'quantifier',
'quantifiers', 'quartz', 'quasi', 'quasiquote', 'quasiquotes', 'query',
'query-dsl', 'question', 'questionmark', 'quicel', 'quicktemplate',
'quicktime-file', 'quotation', 'quote', 'quoted', 'quoted-identifier',
'quoted-object', 'quoted-or-unquoted', 'quotes', 'qx', 'qx-brace',
'qx-bracket', 'qx-ltgt', 'qx-paren', 'r', 'r3', 'rabl', 'racket', 'radar',
'radar-area', 'radiobuttons', 'radix', 'rails', 'rainmeter', 'raml', 'random',
'random_number', 'randomsk', 'range', 'range-2', 'rank', 'rant', 'rapid',
'rarity', 'ratio', 'rational-form', 'raw', 'raw-regex', 'raxe', 'rb', 'rd',
'rdfs-type', 'rdrand', 'rdseed', 'react', 'read', 'readline', 'readonly',
'readwrite', 'real', 'realip', 'rebeca', 'rebol', 'rec', 'receive',
'receive-channel', 'recipe', 'recipient-subscriber-list', 'recode', 'record',
'record-field', 'record-usage', 'recordfield', 'recutils', 'red',
'redbook-audio', 'redirect', 'redirection', 'redprl', 'redundancy', 'ref',
'refer', 'reference', 'referer', 'refinement', 'reflection', 'reg', 'regex',
'regexname', 'regexp', 'regexp-option', 'region-anchor-setting',
'region-cue-setting', 'region-identifier-setting', 'region-lines-setting',
'region-scroll-setting', 'region-viewport-anchor-setting',
'region-width-setting', 'register', 'register-64', 'registers', 'regular',
'reiny', 'reject', 'rejecttype', 'rel', 'related', 'relation', 'relational',
'relations', 'relationship', 'relationship-name', 'relationship-pattern',
'relationship-pattern-end', 'relationship-pattern-start', 'relationship-type',
'relationship-type-or', 'relationship-type-ored', 'relationship-type-start',
'relative', 'rem', 'reminder', 'remote', 'removed', 'rename', 'renamed-from',
'renamed-to', 'renaming', 'render', 'renpy', 'reocrd', 'reparator', 'repeat',
'repl-prompt', 'replace', 'replaceXXX', 'replaced', 'replacement', 'reply',
'repo', 'reporter', 'reporting', 'repository', 'request', 'request-type',
'require', 'required', 'requiredness', 'requirement', 'requirements',
'rescue', 'reserved', 'reset', 'resolution', 'resource', 'resource-manager',
'response', 'response-type', 'rest', 'rest-args', 'rester', 'restriced',
'restructuredtext', 'result', 'result-separator', 'results', 'retro',
'return', 'return-type', 'return-value', 'returns', 'rev', 'reverse',
'reversed', 'review', 'rewrite', 'rewrite-condition', 'rewrite-operator',
'rewrite-pattern', 'rewrite-substitution', 'rewrite-test', 'rewritecond',
'rewriterule', 'rf', 'rfc', 'rgb', 'rgb-percentage', 'rgb-value', 'rhap',
'rho', 'rhs', 'rhtml', 'richtext', 'rid', 'right', 'ring', 'riot',
'rivescript', 'rjs', 'rl', 'rmarkdown', 'rnc', 'rng', 'ro', 'roboconf',
'robot', 'robotc', 'robust-control', 'rockerfile', 'roff', 'role',
'rollout-control', 'root', 'rotate', 'rotate-first', 'rotate-last', 'round',
'round-brackets', 'router', 'routeros', 'routes', 'routine', 'row', 'row2',
'rowspan', 'roxygen', 'rparent', 'rpc', 'rpc-definition', 'rpe', 'rpm-spec',
'rpmspec', 'rpt', 'rq', 'rrd', 'rsl', 'rspec', 'rtemplate', 'ru', 'ruby',
'rubymotion', 'rule', 'rule-identifier', 'rule-name', 'rule-pattern',
'rule-tag', 'ruleDefinition', 'rules', 'run', 'rune', 'runoff', 'runtime',
'rust', 'rviz', 'rx', 's', 'safe-call', 'safe-navigation', 'safe-trap',
'safer', 'safety', 'sage', 'salesforce', 'salt', 'sampler',
'sampler-comparison', 'samplerarg', 'sampling', 'sas', 'sass',
'sass-script-maps', 'satcom', 'satisfies', 'sblock', 'scad', 'scala',
'scaladoc', 'scalar', 'scale', 'scam', 'scan', 'scenario', 'scenario_outline',
'scene', 'scene-object', 'scheduled', 'schelp', 'schem', 'schema', 'scheme',
'schememode', 'scientific', 'scilab', 'sck', 'scl', 'scope', 'scope-name',
'scope-resolution', 'scoping', 'score', 'screen', 'scribble', 'script',
'script-flag', 'script-metadata', 'script-object', 'script-tag', 'scripting',
'scriptlet', 'scriptlocal', 'scriptname', 'scriptname-declaration', 'scripts',
'scroll', 'scrollbars', 'scrollpanes', 'scss', 'scumm', 'sdbl', 'sdl', 'sdo',
'sealed', 'search', 'seawolf', 'second', 'secondary', 'section',
'section-attribute', 'sectionname', 'sections', 'see', 'segment',
'segment-registers', 'segment-resolution', 'select', 'select-block',
'selector', 'self', 'self-binding', 'self-close', 'sem', 'semantic',
'semanticmodel', 'semi-colon', 'semicolon', 'semicoron', 'semireserved',
'send-channel', 'sender', 'senum', 'sep', 'separator', 'separatory',
'sepatator', 'seperator', 'sequence', 'sequences', 'serial', 'serpent',
'server', 'service', 'service-declaration', 'service-rpc', 'services',
'session', 'set', 'set-colour', 'set-size', 'set-variable', 'setbagmix',
'setname', 'setproperty', 'sets', 'setter', 'setting', 'settings', 'settype',
'setword', 'seven', 'severity', 'sexpr', 'sfd', 'sfst', 'sgml', 'sgx1',
'sgx2', 'sha', 'sha256', 'sha512', 'sha_functions', 'shad', 'shade',
'shaderlab', 'shadow-object', 'shape', 'shape-base', 'shape-base-data',
'shared', 'shared-static', 'sharp', 'sharpequal', 'sharpge', 'sharpgt',
'sharple', 'sharplt', 'sharpness', 'shebang', 'shell', 'shell-function',
'shell-session', 'shift', 'shift-and-rotate', 'shift-left', 'shift-right',
'shine', 'shinescript', 'shipflow', 'shmop', 'short', 'shortcut', 'shortcuts',
'shorthand', 'shorthandpropertyname', 'show', 'show-argument',
'shuffle-and-unpack', 'shutdown', 'shy', 'sidebar', 'sifu', 'sigdec', 'sigil',
'sign-line', 'signal', 'signal-processing', 'signature', 'signed',
'signed-int', 'signedness', 'signifier', 'silent', 'sim-group', 'sim-object',
'sim-set', 'simd', 'simd-horizontal', 'simd-integer', 'simple',
'simple-delimiter', 'simple-divider', 'simple-element', 'simple_delimiter',
'simplexml', 'simplez', 'simulate', 'since', 'singe', 'single', 'single-line',
'single-quote', 'single-quoted', 'single_quote', 'singlequote', 'singleton',
'singleword', 'sites', 'six', 'size', 'size-cue-setting', 'sized_integer',
'sizeof', 'sjs', 'sjson', 'sk', 'skaction', 'skdragon', 'skeeland',
'skellett', 'sketchplugin', 'skevolved', 'skew', 'skill', 'skipped',
'skmorkaz', 'skquery', 'skrambled', 'skrayfall', 'skript', 'skrpg', 'sksharp',
'skstuff', 'skutilities', 'skvoice', 'sky', 'skyrim', 'sl', 'slash',
'slash-bar', 'slash-option', 'slash-sign', 'slashes', 'sleet', 'slice',
'slim', 'slm', 'sln', 'slot', 'slugignore', 'sma', 'smali', 'smalltalk',
'smarty', 'smb', 'smbinternal', 'smilebasic', 'sml', 'smoothing-group',
'smpte', 'smtlib', 'smx', 'snakeskin', 'snapshot', 'snlog', 'snmp', 'so',
'soap', 'social', 'socketgroup', 'sockets', 'soft', 'solidity', 'solve',
'soma', 'somearg', 'something', 'soql', 'sort', 'sorting', 'souce', 'sound',
'sound_processing', 'sound_synthesys', 'source', 'source-constant', 'soy',
'sp', 'space', 'space-after-command', 'spacebars', 'spaces', 'sparql',
'spath', 'spec', 'special', 'special-attributes', 'special-character',
'special-curve', 'special-functions', 'special-hook', 'special-keyword',
'special-method', 'special-point', 'special-token-sequence', 'special-tokens',
'special-type', 'specification', 'specifier', 'spectral-curve',
'specular-exponent', 'specular-reflectivity', 'sphinx', 'sphinx-domain',
'spice', 'spider', 'spindlespeed', 'splat', 'spline', 'splunk', 'splunk-conf',
'splus', 'spn', 'spread', 'spread-line', 'spreadmap', 'sprite', 'sproto',
'sproutcore', 'sqf', 'sql', 'sqlbuiltin', 'sqlite', 'sqlsrv', 'sqr', 'sqsp',
'squad', 'square', 'squart', 'squirrel', 'sr-Cyrl', 'sr-Latn', 'src',
'srltext', 'sros', 'srt', 'srv', 'ss', 'ssa', 'sse', 'sse2', 'sse2_simd',
'sse3', 'sse4', 'sse4_simd', 'sse5', 'sse_avx', 'sse_simd', 'ssh-config',
'ssi', 'ssl', 'ssn', 'sstemplate', 'st', 'stable', 'stack', 'stack-effect',
'stackframe', 'stage', 'stan', 'standard', 'standard-key', 'standard-links',
'standard-suite', 'standardadditions', 'standoc', 'star', 'starline', 'start',
'start-block', 'start-condition', 'start-symbol', 'start-value',
'starting-function-params', 'starting-functions', 'starting-functions-point',
'startshape', 'stata', 'statamic', 'state', 'state-flag', 'state-management',
'stateend', 'stategrouparg', 'stategroupval', 'statement',
'statement-separator', 'states', 'statestart', 'statetable', 'static',
'static-assert', 'static-classes', 'static-if', 'static-shape',
'staticimages', 'statistics', 'stats', 'std', 'stdWrap', 'std_logic',
'std_logic_1164', 'stderr-write-file', 'stdint', 'stdlib', 'stdlibcall',
'stdplugin', 'stem', 'step', 'step-size', 'steps', 'stg', 'stile-shoe-left',
'stile-shoe-up', 'stile-tilde', 'stitch', 'stk', 'stmt', 'stochastic', 'stop',
'stopping', 'storage', 'story', 'stp', 'straight-quote', 'stray',
'stray-comment-end', 'stream', 'stream-selection-and-control', 'streamsfuncs',
'streem', 'strict', 'strictness', 'strike', 'strikethrough', 'string',
'string-constant', 'string-format', 'string-interpolation',
'string-long-quote', 'string-long-single-quote', 'string-single-quote',
'stringchar', 'stringize', 'strings', 'strong', 'struc', 'struct',
'struct-union-block', 'structdef', 'structend', 'structs', 'structstart',
'structtable', 'structure', 'stuff', 'stupid-goddamn-hack', 'style',
'styleblock', 'styles', 'stylus', 'sub', 'sub-pattern', 'subchord', 'subckt',
'subcmd', 'subexp', 'subexpression', 'subkey', 'subkeys', 'subl', 'submodule',
'subnet', 'subnet6', 'subpattern', 'subprogram', 'subroutine', 'subscript',
'subsection', 'subsections', 'subset', 'subshell', 'subsort', 'substituted',
'substitution', 'substitution-definition', 'subtitle', 'subtlegradient',
'subtlegray', 'subtract', 'subtraction', 'subtype', 'suffix', 'sugarml',
'sugarss', 'sugly', 'sugly-comparison-operators', 'sugly-control-keywords',
'sugly-declare-function', 'sugly-delcare-operator', 'sugly-delcare-variable',
'sugly-else-in-invalid-position', 'sugly-encode-clause',
'sugly-function-groups', 'sugly-function-recursion',
'sugly-function-variables', 'sugly-general-functions',
'sugly-general-operators', 'sugly-generic-classes', 'sugly-generic-types',
'sugly-global-function', 'sugly-int-constants', 'sugly-invoke-function',
'sugly-json-clause', 'sugly-language-constants', 'sugly-math-clause',
'sugly-math-constants', 'sugly-multiple-parameter-function',
'sugly-number-constants', 'sugly-operator-operands', 'sugly-print-clause',
'sugly-single-parameter-function', 'sugly-subject-or-predicate',
'sugly-type-function', 'sugly-uri-clause', 'summary', 'super', 'superclass',
'supercollider', 'superscript', 'superset', 'supervisor', 'supervisord',
'supplemental', 'supplimental', 'support', 'suppress-image-or-category',
'suppressed', 'surface', 'surface-technique', 'sv', 'svg', 'svm', 'svn',
'swift', 'swig', 'switch', 'switch-block', 'switch-expression',
'switch-statement', 'switchEnd', 'switchStart', 'swizzle', 'sybase',
'syllableseparator', 'symbol', 'symbol-definition', 'symbol-type', 'symbolic',
'symbolic-math', 'symbols', 'symmetry', 'sync-match', 'sync-mode',
'sync-mode-location', 'synchronization', 'synchronize', 'synchronized',
'synergy', 'synopsis', 'syntax', 'syntax-case', 'syntax-cluster',
'syntax-conceal', 'syntax-error', 'syntax-include', 'syntax-item',
'syntax-keywords', 'syntax-match', 'syntax-option', 'syntax-region',
'syntax-rule', 'syntax-spellcheck', 'syntax-sync', 'sys-types', 'sysj',
'syslink', 'syslog-ng', 'system', 'system-events', 'system-identification',
'system-table-pointer', 'systemreference', 'sytem-events', 't',
't3datastructure', 't4', 't5', 't7', 'ta', 'tab', 'table', 'table-name',
'tablename', 'tabpanels', 'tabs', 'tabular', 'tacacs', 'tack-down', 'tack-up',
'taco', 'tads3', 'tag', 'tag-string', 'tag-value', 'tagbraces', 'tagdef',
'tagged', 'tagger_script', 'taglib', 'tagname', 'tagnamedjango', 'tags',
'taint', 'take', 'target', 'targetobj', 'targetprop', 'task', 'tasks',
'tbdfile', 'tbl', 'tbody', 'tcl', 'tcoffee', 'tcp-object', 'td', 'tdl', 'tea',
'team', 'telegram', 'tell', 'telnet', 'temp', 'template', 'template-call',
'template-parameter', 'templatetag', 'tempo', 'temporal', 'term',
'term-comparison', 'term-creation-and-decomposition', 'term-io',
'term-testing', 'term-unification', 'terminal', 'terminate', 'termination',
'terminator', 'terms', 'ternary', 'ternary-if', 'terra', 'terraform',
'terrain-block', 'test', 'testcase', 'testing', 'tests', 'testsuite', 'testx',
'tex', 'texres', 'texshop', 'text', 'text-reference', 'text-suite', 'textbf',
'textcolor', 'textile', 'textio', 'textit', 'textlabels', 'textmate',
'texttt', 'textual', 'texture', 'texture-map', 'texture-option', 'tfoot',
'th', 'thead', 'then', 'therefore', 'thin', 'thing1', 'third', 'this',
'thorn', 'thread', 'three', 'thrift', 'throughput', 'throw', 'throwables',
'throws', 'tick', 'ticket-num', 'ticket-psa', 'tid-file', 'tidal',
'tidalcycles', 'tiddler', 'tiddler-field', 'tiddler-fields', 'tidy', 'tier',
'tieslur', 'tikz', 'tilde', 'time', 'timeblock', 'timehrap', 'timeout',
'timer', 'times', 'timesig', 'timespan', 'timespec', 'timestamp', 'timing',
'titanium', 'title', 'title-page', 'title-text', 'titled-paragraph', 'tjs',
'tl', 'tla', 'tlh', 'tmpl', 'tmsim', 'tmux', 'tnote', 'tnsaudit', 'to',
'to-file', 'to-type', 'toc', 'toc-list', 'todo', 'todo_extra', 'todotxt',
'token', 'token-def', 'token-paste', 'token-type', 'tokenised', 'tokenizer',
'toml', 'too-many-tildes', 'tool', 'toolbox', 'tooltip', 'top', 'top-level',
'top_level', 'topas', 'topic', 'topic-decoration', 'topic-title', 'tornado',
'torque', 'torquescript', 'tosca', 'total-config', 'totaljs', 'tpye', 'tr',
'trace', 'trace-argument', 'trace-object', 'traceback', 'tracing',
'track_processing', 'trader', 'tradersk', 'trail', 'trailing',
'trailing-array-separator', 'trailing-dictionary-separator', 'trailing-match',
'trait', 'traits', 'traits-keyword', 'transaction',
'transcendental', 'transcludeblock', 'transcludeinline', 'transclusion',
'transform', 'transformation', 'transient', 'transition',
'transitionable-property-value', 'translation', 'transmission-filter',
'transparency', 'transparent-line', 'transpose', 'transposed-func',
'transposed-matrix', 'transposed-parens', 'transposed-variable', 'trap',
'tree', 'treetop', 'trenni', 'trigEvent_', 'trigLevelMod_', 'trigLevel_',
'trigger', 'trigger-words', 'triggermodifier', 'trigonometry',
'trimming-loop', 'triple', 'triple-dash', 'triple-slash', 'triple-star',
'true', 'truncate', 'truncation', 'truthgreen', 'try', 'try-catch',
'trycatch', 'ts', 'tsql', 'tss', 'tst', 'tsv', 'tsx', 'tt', 'ttcn3',
'ttlextension', 'ttpmacro', 'tts', 'tubaina', 'tubaina2', 'tul', 'tup',
'tuple', 'turbulence', 'turing', 'turquoise', 'turtle', 'tutch', 'tvml',
'tw5', 'twig', 'twigil', 'twiki', 'two', 'txl', 'txt', 'txt2tags', 'type',
'type-annotation', 'type-cast', 'type-cheat', 'type-checking',
'type-constrained', 'type-constraint', 'type-declaration', 'type-def',
'type-definition', 'type-definition-group', 'type-definitions',
'type-descriptor', 'type-of', 'type-or', 'type-parameter', 'type-parameters',
'type-signature', 'type-spec', 'type-specialization', 'type-specifiers',
'type_2', 'type_trait', 'typeabbrev', 'typeclass', 'typed', 'typed-hole',
'typedblock', 'typedcoffeescript', 'typedecl', 'typedef', 'typeexp',
'typehint', 'typehinted', 'typeid', 'typename', 'types', 'typesbii',
'typescriptish', 'typographic-quotes', 'typoscript', 'typoscript2', 'u',
'u-degree', 'u-end', 'u-offset', 'u-resolution', 'u-scale', 'u-segments',
'u-size', 'u-start', 'u-value', 'uc', 'ucicfg', 'ucicmd', 'udaf', 'udf',
'udl', 'udp', 'udtf', 'ui', 'ui-block', 'ui-group', 'ui-state', 'ui-subgroup',
'uintptr', 'ujm', 'uk', 'ul', 'umbaska', 'unOp', 'unary', 'unbuffered',
'unchecked', 'uncleared', 'unclosed', 'unclosed-string', 'unconstrained',
'undef', 'undefined', 'underbar-circle', 'underbar-diamond', 'underbar-iota',
'underbar-jot', 'underbar-quote', 'underbar-semicolon', 'underline',
'underline-text', 'underlined', 'underscore', 'undocumented',
'unescaped-quote', 'unexpected', 'unexpected-characters',
'unexpected-extends', 'unexpected-extends-character', 'unfiled',
'unformatted', 'unicode', 'unicode-16-bit', 'unicode-32-bit',
'unicode-escape', 'unicode-raw', 'unicode-raw-regex', 'unified', 'unify',
'unimplemented', 'unimportant', 'union', 'union-declaration', 'unique-id',
'unit', 'unit-checking', 'unit-test', 'unit_test', 'unittest', 'unity',
'unityscript', 'universal-match', 'unix', 'unknown', 'unknown-escape',
'unknown-method', 'unknown-property-name', 'unknown-rune', 'unlabeled',
'unless', 'unnecessary', 'unnumbered', 'uno', 'unoconfig', 'unop', 'unoproj',
'unordered', 'unordered-block', 'unosln', 'unpack', 'unpacking', 'unparsed',
'unqualified', 'unquoted', 'unrecognized', 'unrecognized-character',
'unrecognized-character-escape', 'unrecognized-string-escape', 'unsafe',
'unsigned', 'unsigned-int', 'unsized_integer', 'unsupplied', 'until',
'untitled', 'untyped', 'unused', 'uopz', 'update', 'uppercase', 'upstream',
'upwards', 'ur', 'uri', 'url', 'usable', 'usage', 'use', 'use-as', 'use-map',
'use-material', 'usebean', 'usecase', 'usecase-block', 'user', 'user-defined',
'user-defined-property', 'user-defined-type', 'user-interaction',
'userflagsref', 'userid', 'username', 'users', 'using',
'using-namespace-declaration', 'using_animtree', 'util', 'utilities',
'utility', 'utxt', 'uv-resolution', 'uvu', 'uvw', 'ux', 'uxc', 'uxl', 'uz',
'v', 'v-degree', 'v-end', 'v-offset', 'v-resolution', 'v-scale', 'v-segments',
'v-size', 'v-start', 'v-value', 'val', 'vala', 'valgrind', 'valid',
'valid-ampersand', 'valid-bracket', 'valign', 'value', 'value-pair',
'value-signature', 'value-size', 'value-type', 'valuepair', 'vamos', 'vamp',
'vane-down', 'vane-left', 'vane-right', 'vane-up', 'var',
'var-single-variable', 'var1', 'var2', 'variable', 'variable-access',
'variable-assignment', 'variable-declaration', 'variable-definition',
'variable-modifier', 'variable-parameter', 'variable-reference',
'variable-usage', 'variables', 'variabletable', 'variant',
'variant-definition', 'varname', 'varnish', 'vars', 'vb', 'vbnet', 'vbs',
'vc', 'vcard', 'vcd', 'vcl', 'vcs', 'vector', 'vector-load', 'vectors',
'vehicle', 'velocity', 'vendor-prefix', 'verb', 'verbatim', 'verdict',
'verilog', 'version', 'version-number', 'version-specification', 'vertex',
'vertex-reference', 'vertical-blending', 'vertical-span',
'vertical-text-cue-setting', 'vex', 'vhdl', 'vhost', 'vi', 'via',
'video-texturing', 'video_processing', 'view', 'viewhelpers', 'vimAugroupKey',
'vimBehaveModel', 'vimFTCmd', 'vimFTOption', 'vimFuncKey', 'vimGroupSpecial',
'vimHiAttrib', 'vimHiClear', 'vimMapModKey', 'vimPattern', 'vimSynCase',
'vimSynType', 'vimSyncC', 'vimSyncLinecont', 'vimSyncMatch', 'vimSyncNone',
'vimSyncRegion', 'vimUserAttrbCmplt', 'vimUserAttrbKey', 'vimUserCommand',
'viml', 'virtual', 'virtual-host', 'virtual-reality', 'visibility',
'visualforce', 'visualization', 'vlanhdr', 'vle', 'vmap', 'vmx', 'voice',
'void', 'volatile', 'volt', 'volume', 'vpath', 'vplus', 'vrf', 'vtt', 'vue',
'vue-jade', 'vue-stylus', 'w-offset', 'w-scale', 'w-value',
'w3c-extended-color-name', 'w3c-non-standard-color-name',
'w3c-standard-color-name', 'wait', 'waitress', 'waitress-config',
'waitress-rb', 'warn', 'warning', 'warnings', 'wast', 'water', 'watson-todo',
'wavefront', 'wavelet', 'wddx', 'wdiff', 'weapon', 'weave', 'weaveBracket',
'weaveBullet', 'webidl', 'webspeed', 'webvtt', 'weekday', 'weirdland', 'wf',
'wh', 'whatever', 'wheeled-vehicle', 'when', 'where', 'while',
'while-condition', 'while-loop', 'whiskey', 'white', 'whitespace', 'widget',
'width', 'wiki', 'wiki-link', 'wildcard', 'wildsk', 'win', 'window',
'window-classes', 'windows', 'winered', 'with', 'with-arg', 'with-args',
'with-arguments', 'with-params', 'with-prefix', 'with-side-effects',
'with-suffix', 'with-terminator', 'with-value', 'with_colon', 'without-args',
'without-arguments', 'wla-dx', 'word', 'word-op', 'wordnet', 'wordpress',
'words', 'workitem', 'world', 'wow', 'wp', 'write', 'wrong',
'wrong-access-type', 'wrong-division', 'wrong-division-assignment', 'ws',
'www', 'wxml', 'wysiwyg-string', 'x10', 'x86', 'x86_64', 'x86asm', 'xacro',
'xbase', 'xchg', 'xhp', 'xhprof', 'xikij', 'xml', 'xml-attr', 'xmlrpc',
'xmlwriter', 'xop', 'xor', 'xparse', 'xq', 'xquery', 'xref', 'xsave',
'xsd-all', 'xsd_nillable', 'xsd_optional', 'xsl', 'xslt', 'xsse3_simd', 'xst',
'xtend', 'xtoy', 'xtpl', 'xu', 'xvc', 'xve', 'xyzw', 'y', 'y1', 'y2', 'yabb',
'yaml', 'yaml-ext', 'yang', 'yara', 'yate', 'yaws', 'year', 'yellow', 'yield',
'ykk', 'yorick', 'you-forgot-semicolon', 'z', 'z80', 'zap', 'zapper', 'zep',
'zepon', 'zepto', 'zero', 'zero-width-marker', 'zero-width-print', 'zeroop',
'zh-CN', 'zh-TW', 'zig', 'zilde', 'zlib', 'zoomfilter', 'zzz'
])

View File

@@ -97,24 +97,23 @@ class HighlightsComponent
flashHighlightNodeIfRequested: (id, newHighlightState) ->
oldHighlightState = @oldState[id]
return unless newHighlightState.flashCount > oldHighlightState.flashCount
if newHighlightState.needsFlash and oldHighlightState.flashCount isnt newHighlightState.flashCount
highlightNode = @highlightNodesById[id]
highlightNode = @highlightNodesById[id]
addFlashClass = =>
highlightNode.classList.add(newHighlightState.flashClass)
oldHighlightState.flashClass = newHighlightState.flashClass
@flashTimeoutId = setTimeout(removeFlashClass, newHighlightState.flashDuration)
addFlashClass = =>
highlightNode.classList.add(newHighlightState.flashClass)
oldHighlightState.flashClass = newHighlightState.flashClass
@flashTimeoutId = setTimeout(removeFlashClass, newHighlightState.flashDuration)
removeFlashClass = =>
highlightNode.classList.remove(oldHighlightState.flashClass)
oldHighlightState.flashClass = null
clearTimeout(@flashTimeoutId)
removeFlashClass = =>
highlightNode.classList.remove(oldHighlightState.flashClass)
oldHighlightState.flashClass = null
clearTimeout(@flashTimeoutId)
if oldHighlightState.flashClass?
removeFlashClass()
requestAnimationFrame(addFlashClass)
else
addFlashClass()
if oldHighlightState.flashClass?
removeFlashClass()
requestAnimationFrame(addFlashClass)
else
addFlashClass()
oldHighlightState.flashCount = newHighlightState.flashCount
oldHighlightState.flashCount = newHighlightState.flashCount

View File

@@ -0,0 +1,111 @@
/** @babel */
import {remote} from 'electron'
import path from 'path'
import ipcHelpers from './ipc-helpers'
import util from 'util'
export default async function () {
const {getWindowLoadSettings} = require('./window-load-settings-helpers')
const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings()
try {
const Clipboard = require('../src/clipboard')
const ApplicationDelegate = require('../src/application-delegate')
const AtomEnvironment = require('../src/atom-environment')
const TextEditor = require('../src/text-editor')
const exportsPath = path.join(resourcePath, 'exports')
require('module').globalPaths.push(exportsPath) // Add 'exports' to module search path.
process.env.NODE_PATH = exportsPath // Set NODE_PATH env variable since tasks may need it.
document.title = 'Benchmarks'
// Allow `document.title` to be assigned in benchmarks without actually changing the window title.
let documentTitle = null
Object.defineProperty(document, 'title', {
get () { return documentTitle },
set (title) { documentTitle = title }
})
window.addEventListener('keydown', (event) => {
// Reload: cmd-r / ctrl-r
if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) {
ipcHelpers.call('window-method', 'reload')
}
// Toggle Dev Tools: cmd-alt-i (Mac) / ctrl-shift-i (Linux/Windows)
if (event.keyCode === 73) {
const isDarwin = process.platform === 'darwin'
if ((isDarwin && event.metaKey && event.altKey) || (!isDarwin && event.ctrlKey && event.shiftKey)) {
ipcHelpers.call('window-method', 'toggleDevTools')
}
}
// Close: cmd-w / ctrl-w
if ((event.metaKey || event.ctrlKey) && event.keyCode === 87) {
ipcHelpers.call('window-method', 'close')
}
// Copy: cmd-c / ctrl-c
if ((event.metaKey || event.ctrlKey) && event.keyCode === 67) {
ipcHelpers.call('window-method', 'copy')
}
}, true)
const clipboard = new Clipboard()
TextEditor.setClipboard(clipboard)
const applicationDelegate = new ApplicationDelegate()
global.atom = new AtomEnvironment({
applicationDelegate,
window,
document,
clipboard,
configDirPath: process.env.ATOM_HOME,
enablePersistence: false
})
// Prevent benchmarks from modifying application menus
global.atom.menu.sendToBrowserProcess = function () { }
if (headless) {
Object.defineProperties(process, {
stdout: { value: remote.process.stdout },
stderr: { value: remote.process.stderr }
})
console.log = function (...args) {
const formatted = util.format(...args)
process.stdout.write(formatted + '\n')
}
console.warn = function (...args) {
const formatted = util.format(...args)
process.stderr.write(formatted + '\n')
}
console.error = function (...args) {
const formatted = util.format(...args)
process.stderr.write(formatted + '\n')
}
} else {
remote.getCurrentWindow().show()
}
const benchmarkRunner = require('../benchmarks/benchmark-runner')
const statusCode = await benchmarkRunner({test, benchmarkPaths})
if (headless) {
exitWithStatusCode(statusCode)
}
} catch (error) {
if (headless) {
console.error(error.stack || error)
exitWithStatusCode(1)
} else {
ipcHelpers.call('window-method', 'openDevTools')
throw error
}
}
}
function exitWithStatusCode (statusCode) {
remote.app.emit('will-quit')
remote.process.exit(statusCode)
}

View File

@@ -148,19 +148,19 @@ class LanguageMode
rowRange
rowRangeForCommentAtBufferRow: (bufferRow) ->
return unless @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
return unless @editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment()
startRow = bufferRow
endRow = bufferRow
if bufferRow > 0
for currentRow in [bufferRow-1..0] by -1
break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
break unless @editor.tokenizedBuffer.tokenizedLines[currentRow]?.isComment()
startRow = currentRow
if bufferRow < @buffer.getLastRow()
for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1
break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
break unless @editor.tokenizedBuffer.tokenizedLines[currentRow]?.isComment()
endRow = currentRow
return [startRow, endRow] if startRow isnt endRow
@@ -189,7 +189,7 @@ class LanguageMode
# row is a comment.
isLineCommentedAtBufferRow: (bufferRow) ->
return false unless 0 <= bufferRow <= @editor.getLastBufferRow()
@editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
@editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment()
# Find a row range for a 'paragraph' around specified bufferRow. A paragraph
# is a block of text bounded by and empty line or a block of text that is not
@@ -246,10 +246,7 @@ class LanguageMode
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
suggestedIndentForLineAtBufferRow: (bufferRow, line, options) ->
if @editor.largeFileMode or @editor.tokenizedBuffer.grammar is NullGrammar
tokenizedLine = @editor.tokenizedBuffer.buildPlaceholderTokenizedLineForRowWithText(bufferRow, line)
else
tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line)
tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line)
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
suggestedIndentForTokenizedLineAtBufferRow: (bufferRow, line, tokenizedLine, options) ->

View File

@@ -19,7 +19,7 @@ module.exports =
class LinesComponent extends TiledComponent
placeholderTextDiv: null
constructor: ({@presenter, @domElementPool, @assert}) ->
constructor: ({@views, @presenter, @domElementPool, @assert}) ->
@domNode = document.createElement('div')
@domNode.classList.add('lines')
@tilesNode = document.createElement("div")
@@ -32,10 +32,6 @@ class LinesComponent extends TiledComponent
@cursorsComponent = new CursorsComponent
@domNode.appendChild(@cursorsComponent.getDomNode())
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', '.overlayer')
@domNode.appendChild(insertionPoint)
getDomNode: ->
@domNode
@@ -61,9 +57,17 @@ class LinesComponent extends TiledComponent
@domNode.appendChild(@placeholderTextDiv)
@oldState.placeholderText = @newState.placeholderText
# Removing and updating block decorations needs to be done in two different
# steps, so that the same decoration node can be moved from one tile to
# another in the same animation frame.
for component in @getComponents()
component.removeDeletedBlockDecorations()
for component in @getComponents()
component.updateBlockDecorations()
@cursorsComponent.updateSync(state)
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool, @assert})
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool, @assert, @views})
buildEmptyState: ->
{tiles: {}}
@@ -87,6 +91,11 @@ class LinesComponent extends TiledComponent
@presenter.setLineHeight(lineHeightInPixels)
@presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
measureBlockDecorations: ->
for component in @getComponents()
component.measureBlockDecorations()
return
lineIdForScreenRow: (screenRow) ->
tile = @presenter.tileForRow(screenRow)
@getComponentForTile(tile)?.lineIdForScreenRow(screenRow)

View File

@@ -1,288 +0,0 @@
HighlightsComponent = require './highlights-component'
ZERO_WIDTH_NBSP = '\ufeff'
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
clone
module.exports =
class LinesTileComponent
constructor: ({@presenter, @id, @domElementPool, @assert}) ->
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@textNodesByLineId = {}
@insertionPointsBeforeLineById = {}
@insertionPointsAfterLineById = {}
@domNode = @domElementPool.buildElement("div")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@highlightsComponent = new HighlightsComponent(@domElementPool)
@domNode.appendChild(@highlightsComponent.getDomNode())
destroy: ->
@domElementPool.freeElementAndDescendants(@domNode)
getDomNode: ->
@domNode
updateSync: (state) ->
@newState = state
unless @oldState
@oldState = {tiles: {}}
@oldState.tiles[@id] = {lines: {}}
@newTileState = @newState.tiles[@id]
@oldTileState = @oldState.tiles[@id]
if @newState.backgroundColor isnt @oldState.backgroundColor
@domNode.style.backgroundColor = @newState.backgroundColor
@oldState.backgroundColor = @newState.backgroundColor
if @newTileState.zIndex isnt @oldTileState.zIndex
@domNode.style.zIndex = @newTileState.zIndex
@oldTileState.zIndex = @newTileState.zIndex
if @newTileState.display isnt @oldTileState.display
@domNode.style.display = @newTileState.display
@oldTileState.display = @newTileState.display
if @newTileState.height isnt @oldTileState.height
@domNode.style.height = @newTileState.height + 'px'
@oldTileState.height = @newTileState.height
if @newState.width isnt @oldState.width
@domNode.style.width = @newState.width + 'px'
@oldTileState.width = @newTileState.width
if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left
@domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)"
@oldTileState.top = @newTileState.top
@oldTileState.left = @newTileState.left
@updateLineNodes()
@highlightsComponent.updateSync(@newTileState)
removeLineNodes: ->
@removeLineNode(id) for id of @oldTileState.lines
return
removeLineNode: (id) ->
@domElementPool.freeElementAndDescendants(@lineNodesByLineId[id])
@removeBlockDecorationInsertionPointBeforeLine(id)
@removeBlockDecorationInsertionPointAfterLine(id)
delete @lineNodesByLineId[id]
delete @textNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
delete @oldTileState.lines[id]
updateLineNodes: ->
for id of @oldTileState.lines
unless @newTileState.lines.hasOwnProperty(id)
@removeLineNode(id)
newLineIds = null
newLineNodes = null
for id, lineState of @newTileState.lines
if @oldTileState.lines.hasOwnProperty(id)
@updateLineNode(id)
else
newLineIds ?= []
newLineNodes ?= []
newLineIds.push(id)
newLineNodes.push(@buildLineNode(id))
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldTileState.lines[id] = cloneObject(lineState)
return unless newLineIds?
for id, i in newLineIds
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
if nextNode = @findNodeNextTo(lineNode)
@domNode.insertBefore(lineNode, nextNode)
else
@domNode.appendChild(lineNode)
@insertBlockDecorationInsertionPointBeforeLine(id)
@insertBlockDecorationInsertionPointAfterLine(id)
removeBlockDecorationInsertionPointBeforeLine: (id) ->
if insertionPoint = @insertionPointsBeforeLineById[id]
@domElementPool.freeElementAndDescendants(insertionPoint)
delete @insertionPointsBeforeLineById[id]
insertBlockDecorationInsertionPointBeforeLine: (id) ->
{hasPrecedingBlockDecorations, screenRow} = @newTileState.lines[id]
if hasPrecedingBlockDecorations
lineNode = @lineNodesByLineId[id]
insertionPoint = @domElementPool.buildElement("content")
@domNode.insertBefore(insertionPoint, lineNode)
@insertionPointsBeforeLineById[id] = insertionPoint
insertionPoint.dataset.screenRow = screenRow
@updateBlockDecorationInsertionPointBeforeLine(id)
updateBlockDecorationInsertionPointBeforeLine: (id) ->
oldLineState = @oldTileState.lines[id]
newLineState = @newTileState.lines[id]
insertionPoint = @insertionPointsBeforeLineById[id]
return unless insertionPoint?
if newLineState.screenRow isnt oldLineState.screenRow
insertionPoint.dataset.screenRow = newLineState.screenRow
precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations.map((d) -> ".atom--block-decoration-#{d.id}").join(',')
if precedingBlockDecorationsSelector isnt oldLineState.precedingBlockDecorationsSelector
insertionPoint.setAttribute("select", precedingBlockDecorationsSelector)
oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector
removeBlockDecorationInsertionPointAfterLine: (id) ->
if insertionPoint = @insertionPointsAfterLineById[id]
@domElementPool.freeElementAndDescendants(insertionPoint)
delete @insertionPointsAfterLineById[id]
insertBlockDecorationInsertionPointAfterLine: (id) ->
{hasFollowingBlockDecorations, screenRow} = @newTileState.lines[id]
if hasFollowingBlockDecorations
lineNode = @lineNodesByLineId[id]
insertionPoint = @domElementPool.buildElement("content")
@domNode.insertBefore(insertionPoint, lineNode.nextSibling)
@insertionPointsAfterLineById[id] = insertionPoint
insertionPoint.dataset.screenRow = screenRow
@updateBlockDecorationInsertionPointAfterLine(id)
updateBlockDecorationInsertionPointAfterLine: (id) ->
oldLineState = @oldTileState.lines[id]
newLineState = @newTileState.lines[id]
insertionPoint = @insertionPointsAfterLineById[id]
return unless insertionPoint?
if newLineState.screenRow isnt oldLineState.screenRow
insertionPoint.dataset.screenRow = newLineState.screenRow
followingBlockDecorationsSelector = newLineState.followingBlockDecorations.map((d) -> ".atom--block-decoration-#{d.id}").join(',')
if followingBlockDecorationsSelector isnt oldLineState.followingBlockDecorationsSelector
insertionPoint.setAttribute("select", followingBlockDecorationsSelector)
oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector
findNodeNextTo: (node) ->
for nextNode, index in @domNode.children
continue if index is 0 # skips highlights node
return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode)
return
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
buildLineNode: (id) ->
{lineText, tagCodes, screenRow, decorationClasses} = @newTileState.lines[id]
lineNode = @domElementPool.buildElement("div", "line")
lineNode.dataset.screenRow = screenRow
if decorationClasses?
for decorationClass in decorationClasses
lineNode.classList.add(decorationClass)
textNodes = []
startIndex = 0
openScopeNode = lineNode
for tagCode in tagCodes when tagCode isnt 0
if @presenter.isCloseTagCode(tagCode)
openScopeNode = openScopeNode.parentElement
else if @presenter.isOpenTagCode(tagCode)
scope = @presenter.tagForCode(tagCode)
newScopeNode = @domElementPool.buildElement("span", scope.replace(/\.+/g, ' '))
openScopeNode.appendChild(newScopeNode)
openScopeNode = newScopeNode
else
textNode = @domElementPool.buildText(lineText.substr(startIndex, tagCode))
startIndex += tagCode
openScopeNode.appendChild(textNode)
textNodes.push(textNode)
if startIndex is 0
textNode = @domElementPool.buildText(' ')
lineNode.appendChild(textNode)
textNodes.push(textNode)
if lineText.endsWith(@presenter.displayLayer.foldCharacter)
# Insert a zero-width non-breaking whitespace, so that
# LinesYardstick can take the fold-marker::after pseudo-element
# into account during measurements when such marker is the last
# character on the line.
textNode = @domElementPool.buildText(ZERO_WIDTH_NBSP)
lineNode.appendChild(textNode)
textNodes.push(textNode)
@textNodesByLineId[id] = textNodes
lineNode
updateLineNode: (id) ->
oldLineState = @oldTileState.lines[id]
newLineState = @newTileState.lines[id]
lineNode = @lineNodesByLineId[id]
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses
if oldDecorationClasses?
for decorationClass in oldDecorationClasses
unless newDecorationClasses? and decorationClass in newDecorationClasses
lineNode.classList.remove(decorationClass)
if newDecorationClasses?
for decorationClass in newDecorationClasses
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
lineNode.classList.add(decorationClass)
oldLineState.decorationClasses = newLineState.decorationClasses
if not oldLineState.hasPrecedingBlockDecorations and newLineState.hasPrecedingBlockDecorations
@insertBlockDecorationInsertionPointBeforeLine(id)
else if oldLineState.hasPrecedingBlockDecorations and not newLineState.hasPrecedingBlockDecorations
@removeBlockDecorationInsertionPointBeforeLine(id)
if not oldLineState.hasFollowingBlockDecorations and newLineState.hasFollowingBlockDecorations
@insertBlockDecorationInsertionPointAfterLine(id)
else if oldLineState.hasFollowingBlockDecorations and not newLineState.hasFollowingBlockDecorations
@removeBlockDecorationInsertionPointAfterLine(id)
if newLineState.screenRow isnt oldLineState.screenRow
lineNode.dataset.screenRow = newLineState.screenRow
@lineIdsByScreenRow[newLineState.screenRow] = id
@screenRowsByLineId[id] = newLineState.screenRow
@updateBlockDecorationInsertionPointBeforeLine(id)
@updateBlockDecorationInsertionPointAfterLine(id)
oldLineState.screenRow = newLineState.screenRow
oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations
oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
lineNodeForLineId: (lineId) ->
@lineNodesByLineId[lineId]
textNodesForLineId: (lineId) ->
@textNodesByLineId[lineId].slice()
lineIdForScreenRow: (screenRow) ->
@lineIdsByScreenRow[screenRow]
textNodesForScreenRow: (screenRow) ->
@textNodesByLineId[@lineIdsByScreenRow[screenRow]]?.slice()

401
src/lines-tile-component.js Normal file
View File

@@ -0,0 +1,401 @@
const HighlightsComponent = require('./highlights-component')
const ZERO_WIDTH_NBSP = '\ufeff'
module.exports = class LinesTileComponent {
constructor ({presenter, id, domElementPool, assert, views}) {
this.id = id
this.presenter = presenter
this.views = views
this.domElementPool = domElementPool
this.assert = assert
this.lineNodesByLineId = {}
this.screenRowsByLineId = {}
this.lineIdsByScreenRow = {}
this.textNodesByLineId = {}
this.blockDecorationNodesByLineIdAndDecorationId = {}
this.domNode = this.domElementPool.buildElement('div')
this.domNode.style.position = 'absolute'
this.domNode.style.display = 'block'
this.highlightsComponent = new HighlightsComponent(this.domElementPool)
this.domNode.appendChild(this.highlightsComponent.getDomNode())
}
destroy () {
this.removeLineNodes()
this.domElementPool.freeElementAndDescendants(this.domNode)
}
getDomNode () {
return this.domNode
}
updateSync (state) {
this.newState = state
if (this.oldState == null) {
this.oldState = {tiles: {}}
this.oldState.tiles[this.id] = {lines: {}}
}
this.newTileState = this.newState.tiles[this.id]
this.oldTileState = this.oldState.tiles[this.id]
if (this.newState.backgroundColor !== this.oldState.backgroundColor) {
this.domNode.style.backgroundColor = this.newState.backgroundColor
this.oldState.backgroundColor = this.newState.backgroundColor
}
if (this.newTileState.zIndex !== this.oldTileState.zIndex) {
this.domNode.style.zIndex = this.newTileState.zIndex
this.oldTileState.zIndex = this.newTileState.zIndex
}
if (this.newTileState.display !== this.oldTileState.display) {
this.domNode.style.display = this.newTileState.display
this.oldTileState.display = this.newTileState.display
}
if (this.newTileState.height !== this.oldTileState.height) {
this.domNode.style.height = this.newTileState.height + 'px'
this.oldTileState.height = this.newTileState.height
}
if (this.newState.width !== this.oldState.width) {
this.domNode.style.width = this.newState.width + 'px'
this.oldState.width = this.newState.width
}
if (this.newTileState.top !== this.oldTileState.top || this.newTileState.left !== this.oldTileState.left) {
this.domNode.style.transform = `translate3d(${this.newTileState.left}px, ${this.newTileState.top}px, 0px)`
this.oldTileState.top = this.newTileState.top
this.oldTileState.left = this.newTileState.left
}
this.updateLineNodes()
this.highlightsComponent.updateSync(this.newTileState)
}
removeLineNodes () {
for (const id of Object.keys(this.oldTileState.lines)) {
this.removeLineNode(id)
}
}
removeLineNode (lineId) {
this.domElementPool.freeElementAndDescendants(this.lineNodesByLineId[lineId])
for (const decorationId of Object.keys(this.oldTileState.lines[lineId].precedingBlockDecorations)) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
topRulerNode.remove()
blockDecorationNode.remove()
bottomRulerNode.remove()
}
for (const decorationId of Object.keys(this.oldTileState.lines[lineId].followingBlockDecorations)) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
topRulerNode.remove()
blockDecorationNode.remove()
bottomRulerNode.remove()
}
delete this.blockDecorationNodesByLineIdAndDecorationId[lineId]
delete this.lineNodesByLineId[lineId]
delete this.textNodesByLineId[lineId]
delete this.lineIdsByScreenRow[this.screenRowsByLineId[lineId]]
delete this.screenRowsByLineId[lineId]
delete this.oldTileState.lines[lineId]
}
updateLineNodes () {
for (const id of Object.keys(this.oldTileState.lines)) {
if (!this.newTileState.lines.hasOwnProperty(id)) {
this.removeLineNode(id)
}
}
const newLineIds = []
const newLineNodes = []
for (const id of Object.keys(this.newTileState.lines)) {
const lineState = this.newTileState.lines[id]
if (this.oldTileState.lines.hasOwnProperty(id)) {
this.updateLineNode(id)
} else {
newLineIds.push(id)
newLineNodes.push(this.buildLineNode(id))
this.screenRowsByLineId[id] = lineState.screenRow
this.lineIdsByScreenRow[lineState.screenRow] = id
this.oldTileState.lines[id] = Object.assign({}, lineState)
// Avoid assigning state for block decorations, because we need to
// process it later when updating the DOM.
this.oldTileState.lines[id].precedingBlockDecorations = {}
this.oldTileState.lines[id].followingBlockDecorations = {}
}
}
while (newLineIds.length > 0) {
const id = newLineIds.shift()
const lineNode = newLineNodes.shift()
this.lineNodesByLineId[id] = lineNode
const nextNode = this.findNodeNextTo(lineNode)
if (nextNode == null) {
this.domNode.appendChild(lineNode)
} else {
this.domNode.insertBefore(lineNode, nextNode)
}
}
}
findNodeNextTo (node) {
let i = 1 // skip highlights node
while (i < this.domNode.children.length) {
const nextNode = this.domNode.children[i]
if (this.screenRowForNode(node) < this.screenRowForNode(nextNode)) {
return nextNode
}
i++
}
return null
}
screenRowForNode (node) {
return parseInt(node.dataset.screenRow)
}
buildLineNode (id) {
const {lineText, tagCodes, screenRow, decorationClasses} = this.newTileState.lines[id]
const lineNode = this.domElementPool.buildElement('div', 'line')
lineNode.dataset.screenRow = screenRow
if (decorationClasses != null) {
for (const decorationClass of decorationClasses) {
lineNode.classList.add(decorationClass)
}
}
const textNodes = []
let startIndex = 0
let openScopeNode = lineNode
for (const tagCode of tagCodes) {
if (tagCode !== 0) {
if (this.presenter.isCloseTagCode(tagCode)) {
openScopeNode = openScopeNode.parentElement
} else if (this.presenter.isOpenTagCode(tagCode)) {
const scope = this.presenter.tagForCode(tagCode)
const newScopeNode = this.domElementPool.buildElement('span', scope.replace(/\.+/g, ' '))
openScopeNode.appendChild(newScopeNode)
openScopeNode = newScopeNode
} else {
const textNode = this.domElementPool.buildText(lineText.substr(startIndex, tagCode))
startIndex += tagCode
openScopeNode.appendChild(textNode)
textNodes.push(textNode)
}
}
}
if (startIndex === 0) {
const textNode = this.domElementPool.buildText(' ')
lineNode.appendChild(textNode)
textNodes.push(textNode)
}
if (lineText.endsWith(this.presenter.displayLayer.foldCharacter)) {
// Insert a zero-width non-breaking whitespace, so that LinesYardstick can
// take the fold-marker::after pseudo-element into account during
// measurements when such marker is the last character on the line.
const textNode = this.domElementPool.buildText(ZERO_WIDTH_NBSP)
lineNode.appendChild(textNode)
textNodes.push(textNode)
}
this.textNodesByLineId[id] = textNodes
return lineNode
}
updateLineNode (id) {
const oldLineState = this.oldTileState.lines[id]
const newLineState = this.newTileState.lines[id]
const lineNode = this.lineNodesByLineId[id]
const newDecorationClasses = newLineState.decorationClasses
const oldDecorationClasses = oldLineState.decorationClasses
if (oldDecorationClasses != null) {
for (const decorationClass of oldDecorationClasses) {
if (newDecorationClasses == null || !newDecorationClasses.includes(decorationClass)) {
lineNode.classList.remove(decorationClass)
}
}
}
if (newDecorationClasses != null) {
for (const decorationClass of newDecorationClasses) {
if (oldDecorationClasses == null || !oldDecorationClasses.includes(decorationClass)) {
lineNode.classList.add(decorationClass)
}
}
}
oldLineState.decorationClasses = newLineState.decorationClasses
if (newLineState.screenRow !== oldLineState.screenRow) {
lineNode.dataset.screenRow = newLineState.screenRow
this.lineIdsByScreenRow[newLineState.screenRow] = id
this.screenRowsByLineId[id] = newLineState.screenRow
}
oldLineState.screenRow = newLineState.screenRow
}
removeDeletedBlockDecorations () {
for (const lineId of Object.keys(this.newTileState.lines)) {
const oldLineState = this.oldTileState.lines[lineId]
const newLineState = this.newTileState.lines[lineId]
for (const decorationId of Object.keys(oldLineState.precedingBlockDecorations)) {
if (!newLineState.precedingBlockDecorations.hasOwnProperty(decorationId)) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
topRulerNode.remove()
blockDecorationNode.remove()
bottomRulerNode.remove()
delete this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
delete oldLineState.precedingBlockDecorations[decorationId]
}
}
for (const decorationId of Object.keys(oldLineState.followingBlockDecorations)) {
if (!newLineState.followingBlockDecorations.hasOwnProperty(decorationId)) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
topRulerNode.remove()
blockDecorationNode.remove()
bottomRulerNode.remove()
delete this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
delete oldLineState.followingBlockDecorations[decorationId]
}
}
}
}
updateBlockDecorations () {
for (const lineId of Object.keys(this.newTileState.lines)) {
const oldLineState = this.oldTileState.lines[lineId]
const newLineState = this.newTileState.lines[lineId]
const lineNode = this.lineNodesByLineId[lineId]
if (!this.blockDecorationNodesByLineIdAndDecorationId.hasOwnProperty(lineId)) {
this.blockDecorationNodesByLineIdAndDecorationId[lineId] = {}
}
for (const decorationId of Object.keys(newLineState.precedingBlockDecorations)) {
const oldBlockDecorationState = oldLineState.precedingBlockDecorations[decorationId]
const newBlockDecorationState = newLineState.precedingBlockDecorations[decorationId]
if (oldBlockDecorationState != null) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
if (oldBlockDecorationState.screenRow !== newBlockDecorationState.screenRow) {
topRulerNode.remove()
blockDecorationNode.remove()
bottomRulerNode.remove()
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(topRulerNode, lineNode)
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(blockDecorationNode, lineNode)
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(bottomRulerNode, lineNode)
}
} else {
const topRulerNode = document.createElement('div')
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(topRulerNode, lineNode)
const blockDecorationNode = this.views.getView(newBlockDecorationState.decoration.getProperties().item)
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(blockDecorationNode, lineNode)
const bottomRulerNode = document.createElement('div')
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(bottomRulerNode, lineNode)
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId] =
{topRulerNode, blockDecorationNode, bottomRulerNode}
}
oldLineState.precedingBlockDecorations[decorationId] = Object.assign({}, newBlockDecorationState)
}
for (const decorationId of Object.keys(newLineState.followingBlockDecorations)) {
const oldBlockDecorationState = oldLineState.followingBlockDecorations[decorationId]
const newBlockDecorationState = newLineState.followingBlockDecorations[decorationId]
if (oldBlockDecorationState != null) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
if (oldBlockDecorationState.screenRow !== newBlockDecorationState.screenRow) {
topRulerNode.remove()
blockDecorationNode.remove()
bottomRulerNode.remove()
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(bottomRulerNode, lineNode.nextSibling)
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(blockDecorationNode, lineNode.nextSibling)
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(topRulerNode, lineNode.nextSibling)
}
} else {
const bottomRulerNode = document.createElement('div')
bottomRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(bottomRulerNode, lineNode.nextSibling)
const blockDecorationNode = this.views.getView(newBlockDecorationState.decoration.getProperties().item)
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(blockDecorationNode, lineNode.nextSibling)
const topRulerNode = document.createElement('div')
topRulerNode.dataset.screenRow = newBlockDecorationState.screenRow
this.domNode.insertBefore(topRulerNode, lineNode.nextSibling)
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId] =
{topRulerNode, blockDecorationNode, bottomRulerNode}
}
oldLineState.followingBlockDecorations[decorationId] = Object.assign({}, newBlockDecorationState)
}
}
}
measureBlockDecorations () {
for (const lineId of Object.keys(this.newTileState.lines)) {
const newLineState = this.newTileState.lines[lineId]
for (const decorationId of Object.keys(newLineState.precedingBlockDecorations)) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
const width = blockDecorationNode.offsetWidth
const height = bottomRulerNode.offsetTop - topRulerNode.offsetTop
const {decoration} = newLineState.precedingBlockDecorations[decorationId]
this.presenter.setBlockDecorationDimensions(decoration, width, height)
}
for (const decorationId of Object.keys(newLineState.followingBlockDecorations)) {
const {topRulerNode, blockDecorationNode, bottomRulerNode} =
this.blockDecorationNodesByLineIdAndDecorationId[lineId][decorationId]
const width = blockDecorationNode.offsetWidth
const height = bottomRulerNode.offsetTop - topRulerNode.offsetTop
const {decoration} = newLineState.followingBlockDecorations[decorationId]
this.presenter.setBlockDecorationDimensions(decoration, width, height)
}
}
}
lineNodeForScreenRow (screenRow) {
return this.lineNodesByLineId[this.lineIdsByScreenRow[screenRow]]
}
lineNodeForLineId (lineId) {
return this.lineNodesByLineId[lineId]
}
textNodesForLineId (lineId) {
return this.textNodesByLineId[lineId].slice()
}
lineIdForScreenRow (screenRow) {
return this.lineIdsByScreenRow[screenRow]
}
textNodesForScreenRow (screenRow) {
const textNodes = this.textNodesByLineId[this.lineIdsByScreenRow[screenRow]]
if (textNodes == null) {
return null
} else {
return textNodes.slice()
}
}
}

View File

@@ -13,19 +13,21 @@ class LinesYardstick
measuredRowForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
row = Math.floor(targetTop / @model.getLineHeightInPixels())
row if 0 <= row <= @model.getLastScreenRow()
row if 0 <= row
screenPositionForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
targetLeft = pixelPosition.left
row = @lineTopIndex.rowForPixelPosition(targetTop)
targetLeft = 0 if targetTop < 0 or targetLeft < 0
targetLeft = Infinity if row > @model.getLastScreenRow()
row = Math.min(row, @model.getLastScreenRow())
row = Math.max(0, row)
row = Math.max(0, @lineTopIndex.rowForPixelPosition(targetTop))
lineNode = @lineNodesProvider.lineNodeForScreenRow(row)
return Point(row, 0) unless lineNode
unless lineNode
lastScreenRow = @model.getLastScreenRow()
if row > lastScreenRow
return Point(lastScreenRow, @model.lineLengthForScreenRow(lastScreenRow))
else
return Point(row, 0)
targetLeft = pixelPosition.left
targetLeft = 0 if targetTop < 0 or targetLeft < 0
textNodes = @lineNodesProvider.textNodesForScreenRow(row)
lineOffset = lineNode.getBoundingClientRect().left

View File

@@ -42,7 +42,7 @@ class AtomApplication
# take a few seconds to trigger 'error' event, it could be a bug of node
# or atom-shell, before it's fixed we check the existence of socketPath to
# speedup startup.
if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test
if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test or options.benchmark or options.benchmarkTest
new AtomApplication(options).initialize(options)
return
@@ -64,7 +64,7 @@ class AtomApplication
constructor: (options) ->
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @setPortable, @userDataDir} = options
@socketPath = null if options.test
@socketPath = null if options.test or options.benchmark or options.benchmarkTest
@pidsToOpenWindows = {}
@windows = []
@@ -86,7 +86,9 @@ class AtomApplication
@config.onDidChange 'core.useCustomTitleBar', @promptForRestart.bind(this)
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config)
@autoUpdateManager = new AutoUpdateManager(
@version, options.test or options.benchmark or options.benchmarkTest, @resourcePath, @config
)
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
@@ -103,23 +105,41 @@ class AtomApplication
Promise.all(windowsClosePromises).then(=> @disposable.dispose())
launch: (options) ->
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test or options.benchmark or options.benchmarkTest
@openWithOptions(options)
else
@loadState(options) or @openPath(options)
openWithOptions: ({initialPaths, pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env}) ->
openWithOptions: (options) ->
{
initialPaths, pathsToOpen, executedFrom, urlsToOpen, benchmark,
benchmarkTest, test, pidToKillWhenClosed, devMode, safeMode, newWindow,
logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env
} = options
app.focus()
if test
@runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout, env})
@runTests({
headless: true, devMode, @resourcePath, executedFrom, pathsToOpen,
logFile, timeout, env
})
else if benchmark or benchmarkTest
@runBenchmarks({headless: true, test: benchmarkTest, @resourcePath, executedFrom, pathsToOpen, timeout, env})
else if pathsToOpen.length > 0
@openPaths({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env})
@openPaths({
initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow,
devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env
})
else if urlsToOpen.length > 0
@openUrl({urlToOpen, devMode, safeMode, env}) for urlToOpen in urlsToOpen
for urlToOpen in urlsToOpen
@openUrl({urlToOpen, devMode, safeMode, env})
else
# Always open a editor window if this is the first instance of Atom.
@openPath({initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env})
@openPath({
initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup,
clearWindowState, addToLastWindow, env
})
# Public: Removes the {AtomWindow} from the global window list.
removeWindow: (window) ->
@@ -280,6 +300,9 @@ class AtomApplication
@disposable.add ipcHelpers.on ipcMain, 'run-package-specs', (event, packageSpecPath) =>
@runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false})
@disposable.add ipcHelpers.on ipcMain, 'run-benchmarks', (event, benchmarksPath) =>
@runBenchmarks({resourcePath: @devResourcePath, pathsToOpen: [benchmarksPath], headless: false, test: false})
@disposable.add ipcHelpers.on ipcMain, 'command', (event, command) =>
@emit(command)
@@ -651,6 +674,29 @@ class AtomApplication
safeMode ?= false
new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode, env})
runBenchmarks: ({headless, test, resourcePath, executedFrom, pathsToOpen, env}) ->
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath
try
windowInitializationScript = require.resolve(path.resolve(@devResourcePath, 'src', 'initialize-benchmark-window'))
catch error
windowInitializationScript = require.resolve(path.resolve(__dirname, '..', '..', 'src', 'initialize-benchmark-window'))
benchmarkPaths = []
if pathsToOpen?
for pathToOpen in pathsToOpen
benchmarkPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen)))
if benchmarkPaths.length is 0
process.stderr.write 'Error: Specify at least one benchmark path.\n\n'
process.exit(1)
devMode = true
isSpec = true
safeMode = false
new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, test, isSpec, devMode, benchmarkPaths, safeMode, env})
resolveTestRunnerPath: (testPath) ->
FindParentDir ?= require 'find-parent-dir'

View File

@@ -19,7 +19,7 @@ if (args.resourcePath) {
const stableResourcePath = path.dirname(path.dirname(__dirname))
const defaultRepositoryPath = path.join(electron.app.getPath('home'), 'github', 'atom')
if (args.dev || args.test) {
if (args.dev || args.test || args.benchmark || args.benchmarkTest) {
if (process.env.ATOM_DEV_RESOURCE_PATH) {
resourcePath = process.env.ATOM_DEV_RESOURCE_PATH
} else if (fs.statSyncNoException(defaultRepositoryPath)) {

View File

@@ -45,6 +45,8 @@ module.exports = function parseCommandLine (processArgs) {
'portable',
'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.'
)
options.boolean('benchmark').describe('benchmark', 'Open a new window that runs the specified benchmarks.')
options.boolean('benchmark-test').describe('benchmark--test', 'Run a faster version of the benchmarks in headless mode.')
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.')
options.string('timeout').describe(
@@ -78,6 +80,8 @@ module.exports = function parseCommandLine (processArgs) {
const addToLastWindow = args['add']
const safeMode = args['safe']
const pathsToOpen = args._
const benchmark = args['benchmark']
const benchmarkTest = args['benchmark-test']
const test = args['test']
const mainProcess = args['main-process']
const timeout = args['timeout']
@@ -132,10 +136,29 @@ module.exports = function parseCommandLine (processArgs) {
devResourcePath = normalizeDriveLetterName(devResourcePath)
return {
resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath,
userDataDir, profileStartup, timeout, setPortable, clearWindowState,
addToLastWindow, mainProcess, env: process.env
resourcePath,
devResourcePath,
pathsToOpen,
urlsToOpen,
executedFrom,
test,
version,
pidToKillWhenClosed,
devMode,
safeMode,
newWindow,
logFile,
socketPath,
userDataDir,
profileStartup,
timeout,
setPortable,
clearWindowState,
addToLastWindow,
mainProcess,
benchmark,
benchmarkTest,
env: process.env
}
}

View File

@@ -57,7 +57,7 @@ module.exports = function start (resourcePath, startTime) {
if (args.userDataDir != null) {
app.setPath('userData', args.userDataDir)
} else if (args.test) {
} else if (args.test || args.benchmark || args.benchmarkTest) {
app.setPath('userData', temp.mkdirSync('atom-test-data'))
}

View File

@@ -45,7 +45,7 @@ class NativeCompileCache {
Module.prototype._compile = function (content, filename) {
let moduleSelf = this
// remove shebang
content = content.replace(/^\#\!.*/, '')
content = content.replace(/^#!.*/, '')
function require (path) {
return moduleSelf.require(path)
}

View File

@@ -2,12 +2,39 @@
import {Disposable} from 'event-kit'
export default Object.freeze({
export default {
name: 'Null Grammar',
scopeName: 'text.plain',
scopeName: 'text.plain.null-grammar',
scopeForId (id) {
if (id === -1 || id === -2) {
return this.scopeName
} else {
return null
}
},
startIdForScope (scopeName) {
if (scopeName === this.scopeName) {
return -1
} else {
return null
}
},
endIdForScope (scopeName) {
if (scopeName === this.scopeName) {
return -2
} else {
return null
}
},
tokenizeLine (text) {
return {
tags: [this.startIdForScope(this.scopeName), text.length, this.endIdForScope(this.scopeName)],
ruleStack: null
}
},
onDidUpdate (callback) {
return new Disposable(noop)
}
})
}
function noop () {}

View File

@@ -0,0 +1,62 @@
module.exports = class OffScreenBlockDecorationsComponent {
constructor ({presenter, views}) {
this.presenter = presenter
this.views = views
this.newState = {offScreenBlockDecorations: {}, width: 0}
this.oldState = {offScreenBlockDecorations: {}, width: 0}
this.domNode = document.createElement('div')
this.domNode.style.visibility = 'hidden'
this.domNode.style.position = 'absolute'
this.blockDecorationNodesById = {}
}
getDomNode () {
return this.domNode
}
updateSync (state) {
this.newState = state.content
if (this.newState.width !== this.oldState.width) {
this.domNode.style.width = `${this.newState.width}px`
this.oldState.width = this.newState.width
}
for (const id of Object.keys(this.oldState.offScreenBlockDecorations)) {
if (!this.newState.offScreenBlockDecorations.hasOwnProperty(id)) {
const {topRuler, blockDecoration, bottomRuler} = this.blockDecorationNodesById[id]
topRuler.remove()
blockDecoration.remove()
bottomRuler.remove()
delete this.blockDecorationNodesById[id]
delete this.oldState.offScreenBlockDecorations[id]
}
}
for (const id of Object.keys(this.newState.offScreenBlockDecorations)) {
const decoration = this.newState.offScreenBlockDecorations[id]
if (!this.oldState.offScreenBlockDecorations.hasOwnProperty(id)) {
const topRuler = document.createElement('div')
this.domNode.appendChild(topRuler)
const blockDecoration = this.views.getView(decoration.getProperties().item)
this.domNode.appendChild(blockDecoration)
const bottomRuler = document.createElement('div')
this.domNode.appendChild(bottomRuler)
this.blockDecorationNodesById[id] = {topRuler, blockDecoration, bottomRuler}
}
this.oldState.offScreenBlockDecorations[id] = decoration
}
}
measureBlockDecorations () {
for (const id of Object.keys(this.blockDecorationNodesById)) {
const {topRuler, blockDecoration, bottomRuler} = this.blockDecorationNodesById[id]
const width = blockDecoration.offsetWidth
const height = bottomRuler.offsetTop - topRuler.offsetTop
const decoration = this.newState.offScreenBlockDecorations[id]
this.presenter.setBlockDecorationDimensions(decoration, width, height)
}
}
}

View File

@@ -54,6 +54,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage
'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet')
'application:open-license': -> @getModel().openLicense()
'window:run-package-specs': -> @runPackageSpecs()
'window:run-benchmarks': -> @runBenchmarks()
'window:focus-next-pane': -> @getModel().activateNextPane()
'window:focus-previous-pane': -> @getModel().activatePreviousPane()
'window:focus-pane-above': -> @focusPaneViewAbove()

View File

@@ -1,177 +0,0 @@
fs = require 'fs-plus'
path = require 'path'
{Emitter, Disposable} = require 'event-kit'
StylesElement = require './styles-element'
# Extended: A singleton instance of this class available via `atom.styles`,
# which you can use to globally query and observe the set of active style
# sheets. The `StyleManager` doesn't add any style elements to the DOM on its
# own, but is instead subscribed to by individual `<atom-styles>` elements,
# which clone and attach style elements in different contexts.
module.exports =
class StyleManager
constructor: ({@configDirPath}) ->
@emitter = new Emitter
@styleElements = []
@styleElementsBySourcePath = {}
###
Section: Event Subscription
###
# Extended: Invoke `callback` for all current and future style elements.
#
# * `callback` {Function} that is called with style elements.
# * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property
# will be null because this element isn't attached to the DOM. If you want
# to attach this element to the DOM, be sure to clone it first by calling
# `.cloneNode(true)` on it. The style element will also have the following
# non-standard properties:
# * `sourcePath` A {String} containing the path from which the style
# element was loaded.
# * `context` A {String} indicating the target context of the style
# element.
#
# Returns a {Disposable} on which `.dispose()` can be called to cancel the
# subscription.
observeStyleElements: (callback) ->
callback(styleElement) for styleElement in @getStyleElements()
@onDidAddStyleElement(callback)
# Extended: Invoke `callback` when a style element is added.
#
# * `callback` {Function} that is called with style elements.
# * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property
# will be null because this element isn't attached to the DOM. If you want
# to attach this element to the DOM, be sure to clone it first by calling
# `.cloneNode(true)` on it. The style element will also have the following
# non-standard properties:
# * `sourcePath` A {String} containing the path from which the style
# element was loaded.
# * `context` A {String} indicating the target context of the style
# element.
#
# Returns a {Disposable} on which `.dispose()` can be called to cancel the
# subscription.
onDidAddStyleElement: (callback) ->
@emitter.on 'did-add-style-element', callback
# Extended: Invoke `callback` when a style element is removed.
#
# * `callback` {Function} that is called with style elements.
# * `styleElement` An `HTMLStyleElement` instance.
#
# Returns a {Disposable} on which `.dispose()` can be called to cancel the
# subscription.
onDidRemoveStyleElement: (callback) ->
@emitter.on 'did-remove-style-element', callback
# Extended: Invoke `callback` when an existing style element is updated.
#
# * `callback` {Function} that is called with style elements.
# * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property
# will be null because this element isn't attached to the DOM. The style
# element will also have the following non-standard properties:
# * `sourcePath` A {String} containing the path from which the style
# element was loaded.
# * `context` A {String} indicating the target context of the style
# element.
#
# Returns a {Disposable} on which `.dispose()` can be called to cancel the
# subscription.
onDidUpdateStyleElement: (callback) ->
@emitter.on 'did-update-style-element', callback
###
Section: Reading Style Elements
###
# Extended: Get all loaded style elements.
getStyleElements: ->
@styleElements.slice()
addStyleSheet: (source, params) ->
sourcePath = params?.sourcePath
context = params?.context
priority = params?.priority
if sourcePath? and styleElement = @styleElementsBySourcePath[sourcePath]
updated = true
else
styleElement = document.createElement('style')
if sourcePath?
styleElement.sourcePath = sourcePath
styleElement.setAttribute('source-path', sourcePath)
if context?
styleElement.context = context
styleElement.setAttribute('context', context)
if priority?
styleElement.priority = priority
styleElement.setAttribute('priority', priority)
styleElement.textContent = source
if updated
@emitter.emit 'did-update-style-element', styleElement
else
@addStyleElement(styleElement)
new Disposable => @removeStyleElement(styleElement)
addStyleElement: (styleElement) ->
{sourcePath, priority} = styleElement
if priority?
for existingElement, index in @styleElements
if existingElement.priority > priority
insertIndex = index
break
insertIndex ?= @styleElements.length
@styleElements.splice(insertIndex, 0, styleElement)
@styleElementsBySourcePath[sourcePath] ?= styleElement if sourcePath?
@emitter.emit 'did-add-style-element', styleElement
removeStyleElement: (styleElement) ->
index = @styleElements.indexOf(styleElement)
unless index is -1
@styleElements.splice(index, 1)
delete @styleElementsBySourcePath[styleElement.sourcePath] if styleElement.sourcePath?
@emitter.emit 'did-remove-style-element', styleElement
getSnapshot: ->
@styleElements.slice()
restoreSnapshot: (styleElementsToRestore) ->
for styleElement in @getStyleElements()
@removeStyleElement(styleElement) unless styleElement in styleElementsToRestore
existingStyleElements = @getStyleElements()
for styleElement in styleElementsToRestore
@addStyleElement(styleElement) unless styleElement in existingStyleElements
return
buildStylesElement: ->
stylesElement = new StylesElement
stylesElement.initialize(this)
stylesElement
###
Section: Paths
###
# Extended: Get the path of the user style sheet in `~/.atom`.
#
# Returns a {String}.
getUserStyleSheetPath: ->
return "" unless @configDirPath?
stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less'])
if fs.isFileSync(stylesheetPath)
stylesheetPath
else
path.join(@configDirPath, 'styles.less')

307
src/style-manager.js Normal file
View File

@@ -0,0 +1,307 @@
const {Emitter, Disposable} = require('event-kit')
const crypto = require('crypto')
const fs = require('fs-plus')
const path = require('path')
const postcss = require('postcss')
const selectorParser = require('postcss-selector-parser')
const StylesElement = require('./styles-element')
const DEPRECATED_SYNTAX_SELECTORS = require('./deprecated-syntax-selectors')
// Extended: A singleton instance of this class available via `atom.styles`,
// which you can use to globally query and observe the set of active style
// sheets. The `StyleManager` doesn't add any style elements to the DOM on its
// own, but is instead subscribed to by individual `<atom-styles>` elements,
// which clone and attach style elements in different contexts.
module.exports = class StyleManager {
constructor ({configDirPath}) {
this.configDirPath = configDirPath
if (this.configDirPath != null) {
this.cacheDirPath = path.join(this.configDirPath, 'compile-cache', 'style-manager')
}
this.emitter = new Emitter()
this.styleElements = []
this.styleElementsBySourcePath = {}
this.deprecationsBySourcePath = {}
}
/*
Section: Event Subscription
*/
// Extended: Invoke `callback` for all current and future style elements.
//
// * `callback` {Function} that is called with style elements.
// * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property
// will be null because this element isn't attached to the DOM. If you want
// to attach this element to the DOM, be sure to clone it first by calling
// `.cloneNode(true)` on it. The style element will also have the following
// non-standard properties:
// * `sourcePath` A {String} containing the path from which the style
// element was loaded.
// * `context` A {String} indicating the target context of the style
// element.
//
// Returns a {Disposable} on which `.dispose()` can be called to cancel the
// subscription.
observeStyleElements (callback) {
for (let styleElement of this.getStyleElements()) {
callback(styleElement)
}
return this.onDidAddStyleElement(callback)
}
// Extended: Invoke `callback` when a style element is added.
//
// * `callback` {Function} that is called with style elements.
// * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property
// will be null because this element isn't attached to the DOM. If you want
// to attach this element to the DOM, be sure to clone it first by calling
// `.cloneNode(true)` on it. The style element will also have the following
// non-standard properties:
// * `sourcePath` A {String} containing the path from which the style
// element was loaded.
// * `context` A {String} indicating the target context of the style
// element.
//
// Returns a {Disposable} on which `.dispose()` can be called to cancel the
// subscription.
onDidAddStyleElement (callback) {
return this.emitter.on('did-add-style-element', callback)
}
// Extended: Invoke `callback` when a style element is removed.
//
// * `callback` {Function} that is called with style elements.
// * `styleElement` An `HTMLStyleElement` instance.
//
// Returns a {Disposable} on which `.dispose()` can be called to cancel the
// subscription.
onDidRemoveStyleElement (callback) {
return this.emitter.on('did-remove-style-element', callback)
}
// Extended: Invoke `callback` when an existing style element is updated.
//
// * `callback` {Function} that is called with style elements.
// * `styleElement` An `HTMLStyleElement` instance. The `.sheet` property
// will be null because this element isn't attached to the DOM. The style
// element will also have the following non-standard properties:
// * `sourcePath` A {String} containing the path from which the style
// element was loaded.
// * `context` A {String} indicating the target context of the style
// element.
//
// Returns a {Disposable} on which `.dispose()` can be called to cancel the
// subscription.
onDidUpdateStyleElement (callback) {
return this.emitter.on('did-update-style-element', callback)
}
onDidUpdateDeprecations (callback) {
return this.emitter.on('did-update-deprecations', callback)
}
/*
Section: Reading Style Elements
*/
// Extended: Get all loaded style elements.
getStyleElements () {
return this.styleElements.slice()
}
addStyleSheet (source, params = {}) {
let styleElement
let updated
if (params.sourcePath != null && this.styleElementsBySourcePath[params.sourcePath] != null) {
updated = true
styleElement = this.styleElementsBySourcePath[params.sourcePath]
} else {
updated = false
styleElement = document.createElement('style')
if (params.sourcePath != null) {
styleElement.sourcePath = params.sourcePath
styleElement.setAttribute('source-path', params.sourcePath)
}
if (params.context != null) {
styleElement.context = params.context
styleElement.setAttribute('context', params.context)
}
if (params.priority != null) {
styleElement.priority = params.priority
styleElement.setAttribute('priority', params.priority)
}
}
let transformed
if (this.cacheDirPath != null) {
const hash = crypto.createHash('sha1')
if (params.context != null) {
hash.update(params.context)
}
hash.update(source)
const cacheFilePath = path.join(this.cacheDirPath, hash.digest('hex'))
try {
transformed = JSON.parse(fs.readFileSync(cacheFilePath))
} catch (e) {
transformed = transformDeprecatedShadowDOMSelectors(source, params.context)
fs.writeFileSync(cacheFilePath, JSON.stringify(transformed))
}
} else {
transformed = transformDeprecatedShadowDOMSelectors(source, params.context)
}
styleElement.textContent = transformed.source
if (transformed.deprecationMessage) {
this.deprecationsBySourcePath[params.sourcePath] = {message: transformed.deprecationMessage}
this.emitter.emit('did-update-deprecations')
}
if (updated) {
this.emitter.emit('did-update-style-element', styleElement)
} else {
this.addStyleElement(styleElement)
}
return new Disposable(() => { this.removeStyleElement(styleElement) })
}
addStyleElement (styleElement) {
let insertIndex = this.styleElements.length
if (styleElement.priority != null) {
for (let [index, existingElement] of this.styleElements.entries()) {
if (existingElement.priority > styleElement.priority) {
insertIndex = index
break
}
}
}
this.styleElements.splice(insertIndex, 0, styleElement)
if (styleElement.sourcePath != null && this.styleElementsBySourcePath[styleElement.sourcePath] == null) {
this.styleElementsBySourcePath[styleElement.sourcePath] = styleElement
}
this.emitter.emit('did-add-style-element', styleElement)
}
removeStyleElement (styleElement) {
const index = this.styleElements.indexOf(styleElement)
if (index !== -1) {
this.styleElements.splice(index, 1)
if (styleElement.sourcePath != null) {
delete this.styleElementsBySourcePath[styleElement.sourcePath]
}
this.emitter.emit('did-remove-style-element', styleElement)
}
}
getDeprecations () {
return this.deprecationsBySourcePath
}
clearDeprecations () {
this.deprecationsBySourcePath = {}
}
getSnapshot () {
return this.styleElements.slice()
}
restoreSnapshot (styleElementsToRestore) {
for (let styleElement of this.getStyleElements()) {
if (!styleElementsToRestore.includes(styleElement)) {
this.removeStyleElement(styleElement)
}
}
const existingStyleElements = this.getStyleElements()
for (let styleElement of styleElementsToRestore) {
if (!existingStyleElements.includes(styleElement)) {
this.addStyleElement(styleElement)
}
}
}
buildStylesElement () {
var stylesElement = new StylesElement()
stylesElement.initialize(this)
return stylesElement
}
/*
Section: Paths
*/
// Extended: Get the path of the user style sheet in `~/.atom`.
//
// Returns a {String}.
getUserStyleSheetPath () {
if (this.configDirPath == null) {
return ''
} else {
const stylesheetPath = fs.resolve(path.join(this.configDirPath, 'styles'), ['css', 'less'])
if (fs.isFileSync(stylesheetPath)) {
return stylesheetPath
} else {
return path.join(this.configDirPath, 'styles.less')
}
}
}
}
function transformDeprecatedShadowDOMSelectors (css, context) {
const transformedSelectors = []
const transformedSource = postcss.parse(css)
transformedSource.walkRules((rule) => {
const transformedSelector = selectorParser((selectors) => {
selectors.each((selector) => {
const firstNode = selector.nodes[0]
if (context === 'atom-text-editor' && firstNode.type === 'pseudo' && firstNode.value === ':host') {
const atomTextEditorElementNode = selectorParser.tag({value: 'atom-text-editor'})
firstNode.replaceWith(atomTextEditorElementNode)
}
let previousNodeIsAtomTextEditor = false
let targetsAtomTextEditorShadow = context === 'atom-text-editor'
let previousNode
selector.each((node) => {
if (targetsAtomTextEditorShadow && node.type === 'class') {
if (DEPRECATED_SYNTAX_SELECTORS.has(node.value)) {
node.value = `syntax--${node.value}`
}
} else {
if (previousNodeIsAtomTextEditor && node.type === 'pseudo' && node.value === '::shadow') {
selector.removeChild(node)
targetsAtomTextEditorShadow = true
}
}
previousNode = node
if (node.type === 'combinator') {
previousNodeIsAtomTextEditor = false
} else if (previousNode.type === 'tag' && previousNode.value === 'atom-text-editor') {
previousNodeIsAtomTextEditor = true
}
})
})
}).process(rule.selector, {lossless: true}).result
if (transformedSelector !== rule.selector) {
transformedSelectors.push({before: rule.selector, after: transformedSelector})
rule.selector = transformedSelector
}
})
let deprecationMessage
if (transformedSelectors.length > 0) {
deprecationMessage = 'Starting from Atom v1.13.0, the contents of `atom-text-editor` elements '
deprecationMessage += 'are no longer encapsulated within a shadow DOM boundary. '
deprecationMessage += 'This means you should stop using `:host` and `::shadow` '
deprecationMessage += 'pseudo-selectors, and prepend all your syntax selectors with `syntax--`. '
deprecationMessage += 'To prevent breakage with existing style sheets, Atom will automatically '
deprecationMessage += 'upgrade the following selectors:\n\n'
deprecationMessage += transformedSelectors
.map((selector) => `* \`${selector.before}\` => \`${selector.after}\``)
.join('\n\n') + '\n\n'
deprecationMessage += 'Automatic translation of selectors will be removed in a few release cycles to minimize startup time. '
deprecationMessage += 'Please, make sure to upgrade the above selectors as soon as possible.'
}
return {source: transformedSource.toString(), deprecationMessage}
}

View File

@@ -19,10 +19,6 @@ class StylesElement extends HTMLElement
@styleElementClonesByOriginalElement = new WeakMap
attachedCallback: ->
if @context is 'atom-text-editor'
for styleElement in @children
@upgradeDeprecatedSelectors(styleElement)
@context = @getAttribute('context') ? undefined
detachedCallback: ->
@@ -64,10 +60,6 @@ class StylesElement extends HTMLElement
break
@insertBefore(styleElementClone, insertBefore)
if @context is 'atom-text-editor'
@upgradeDeprecatedSelectors(styleElementClone)
@emitter.emit 'did-add-style-element', styleElementClone
styleElementRemoved: (styleElement) ->
@@ -87,31 +79,4 @@ class StylesElement extends HTMLElement
styleElementMatchesContext: (styleElement) ->
not @context? or styleElement.context is @context
upgradeDeprecatedSelectors: (styleElement) ->
return unless styleElement.sheet?
upgradedSelectors = []
for rule in styleElement.sheet.cssRules
continue unless rule.selectorText?
continue if /\:host/.test(rule.selectorText)
inputSelector = rule.selectorText
outputSelector = rule.selectorText
.replace(/\.editor-colors($|[ >])/g, ':host$1')
.replace(/\.editor([:.][^ ,>]+)/g, ':host($1)')
.replace(/\.editor($|[ ,>])/g, ':host$1')
unless inputSelector is outputSelector
rule.selectorText = outputSelector
upgradedSelectors.push({inputSelector, outputSelector})
if upgradedSelectors.length > 0
warning = "Upgraded the following syntax theme selectors in `#{styleElement.sourcePath}` for shadow DOM compatibility:\n\n"
for {inputSelector, outputSelector} in upgradedSelectors
warning += "`#{inputSelector}` => `#{outputSelector}`\n"
warning += "\nSee the upgrade guide for information on removing this warning."
console.warn(warning)
module.exports = StylesElement = document.registerElement 'atom-styles', prototype: StylesElement.prototype

View File

@@ -8,12 +8,12 @@ TextEditorPresenter = require './text-editor-presenter'
GutterContainerComponent = require './gutter-container-component'
InputComponent = require './input-component'
LinesComponent = require './lines-component'
OffScreenBlockDecorationsComponent = require './off-screen-block-decorations-component'
ScrollbarComponent = require './scrollbar-component'
ScrollbarCornerComponent = require './scrollbar-corner-component'
OverlayManager = require './overlay-manager'
DOMElementPool = require './dom-element-pool'
LinesYardstick = require './lines-yardstick'
BlockDecorationsComponent = require './block-decorations-component'
LineTopIndex = require 'line-top-index'
module.exports =
@@ -42,7 +42,7 @@ class TextEditorComponent
@assert domNode?, "TextEditorComponent::domNode was set to null."
@domNodeValue = domNode
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, tileSize, @views, @themes, @assert}) ->
constructor: ({@editor, @hostElement, tileSize, @views, @themes, @styles, @assert}) ->
@tileSize = tileSize if tileSize?
@disposables = new CompositeDisposable
@@ -64,11 +64,7 @@ class TextEditorComponent
@domNode = document.createElement('div')
@domNode.classList.add('editor-contents--private')
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@domNode.appendChild(insertionPoint)
@overlayManager = new OverlayManager(@presenter, @hostElement, @views)
@blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool)
@overlayManager = new OverlayManager(@presenter, @domNode, @views)
@scrollViewNode = document.createElement('div')
@scrollViewNode.classList.add('scroll-view')
@@ -77,11 +73,11 @@ class TextEditorComponent
@hiddenInputComponent = new InputComponent
@scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
@linesComponent = new LinesComponent({@presenter, @hostElement, @domElementPool, @assert, @grammars})
@linesComponent = new LinesComponent({@presenter, @domElementPool, @assert, @grammars, @views})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
if @blockDecorationsComponent?
@linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode())
@offScreenBlockDecorationsComponent = new OffScreenBlockDecorationsComponent({@presenter, @views})
@scrollViewNode.appendChild(@offScreenBlockDecorationsComponent.getDomNode())
@linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex)
@presenter.setLinesYardstick(@linesYardstick)
@@ -98,9 +94,9 @@ class TextEditorComponent
@observeEditor()
@listenForDOMEvents()
@disposables.add @stylesElement.onDidAddStyleElement @onStylesheetsChanged
@disposables.add @stylesElement.onDidUpdateStyleElement @onStylesheetsChanged
@disposables.add @stylesElement.onDidRemoveStyleElement @onStylesheetsChanged
@disposables.add @styles.onDidAddStyleElement @onStylesheetsChanged
@disposables.add @styles.onDidUpdateStyleElement @onStylesheetsChanged
@disposables.add @styles.onDidRemoveStyleElement @onStylesheetsChanged
unless @themes.isInitialLoadComplete()
@disposables.add @themes.onDidChangeActiveThemes @onAllThemesLoaded
@disposables.add scrollbarStyle.onDidChangePreferredScrollbarStyle @refreshScrollbars
@@ -165,8 +161,8 @@ class TextEditorComponent
@gutterContainerComponent = null
@hiddenInputComponent.updateSync(@newState)
@offScreenBlockDecorationsComponent.updateSync(@newState)
@linesComponent.updateSync(@newState)
@blockDecorationsComponent?.updateSync(@newState)
@horizontalScrollbarComponent.updateSync(@newState)
@verticalScrollbarComponent.updateSync(@newState)
@scrollbarCornerComponent.updateSync(@newState)
@@ -186,7 +182,8 @@ class TextEditorComponent
readAfterUpdateSync: =>
@overlayManager?.measureOverlays()
@blockDecorationsComponent?.measureBlockDecorations() if @isVisible()
@linesComponent.measureBlockDecorations()
@offScreenBlockDecorationsComponent.measureBlockDecorations()
mountGutterContainerComponent: ->
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views})
@@ -969,9 +966,7 @@ class TextEditorComponent
updateParentViewFocusedClassIfNeeded: ->
if @oldState.focused isnt @newState.focused
@hostElement.classList.toggle('is-focused', @newState.focused)
@rootElement.classList.toggle('is-focused', @newState.focused)
@oldState.focused = @newState.focused
updateParentViewMiniClass: ->
@hostElement.classList.toggle('mini', @editor.isMini())
@rootElement.classList.toggle('mini', @editor.isMini())

View File

@@ -1,9 +1,7 @@
Grim = require 'grim'
{Emitter, CompositeDisposable} = require 'event-kit'
TextBuffer = require 'text-buffer'
TextEditorComponent = require './text-editor-component'
StylesElement = require './styles-element'
ShadowStyleSheet = null
class TextEditorElement extends HTMLElement
model: null
@@ -14,6 +12,7 @@ class TextEditorElement extends HTMLElement
focusOnAttach: false
hasTiledRendering: true
logicalDisplayBuffer: true
lightDOM: true
createdCallback: ->
# Use globals when the following instance variables aren't set.
@@ -33,22 +32,18 @@ class TextEditorElement extends HTMLElement
@setAttribute('tabindex', -1)
initializeContent: (attributes) ->
unless ShadowStyleSheet?
ShadowStyleSheet = document.createElement('style')
ShadowStyleSheet.textContent = @themes.loadLessStylesheet(require.resolve('../static/text-editor-shadow.less'))
@createShadowRoot()
@shadowRoot.appendChild(ShadowStyleSheet.cloneNode(true))
@stylesElement = new StylesElement
@stylesElement.initialize(@styles)
@stylesElement.setAttribute('context', 'atom-text-editor')
Object.defineProperty(this, 'shadowRoot', {
get: =>
Grim.deprecate("""
The contents of `atom-text-editor` elements are no longer encapsulated
within a shadow DOM boundary. Please, stop using `shadowRoot` and access
the editor contents directly instead.
""")
this
})
@rootElement = document.createElement('div')
@rootElement.classList.add('editor--private')
@shadowRoot.appendChild(@stylesElement)
@shadowRoot.appendChild(@rootElement)
@appendChild(@rootElement)
attachedCallback: ->
@buildModel() unless @getModel()?
@@ -56,7 +51,7 @@ class TextEditorElement extends HTMLElement
@mountComponent() unless @component?
@listenForComponentEvents()
@component.checkForVisibilityChange()
if this is document.activeElement
if @hasFocus()
@focused()
@emitter.emit("did-attach")
@@ -116,18 +111,18 @@ class TextEditorElement extends HTMLElement
mountComponent: ->
@component = new TextEditorComponent(
hostElement: this
rootElement: @rootElement
stylesElement: @stylesElement
editor: @model
tileSize: @tileSize
views: @views
themes: @themes
styles: @styles
workspace: @workspace
assert: @assert
)
@rootElement.appendChild(@component.getDomNode())
@shadowRoot.addEventListener('blur', @shadowRootBlurred.bind(this), true)
inputNode = @component.hiddenInputComponent.getDomNode()
inputNode.addEventListener 'focus', @focused.bind(this)
inputNode.addEventListener 'blur', @inputNodeBlurred.bind(this)
unmountComponent: ->
if @component?
@@ -135,21 +130,18 @@ class TextEditorElement extends HTMLElement
@component.getDomNode().remove()
@component = null
focused: ->
focused: (event) ->
@component?.focused()
blurred: (event) ->
if event.relatedTarget is @component?.hiddenInputComponent.getDomNode()
event.stopImmediatePropagation()
return
@component?.blurred()
# Work around what seems to be a bug in Chromium. Focus can be stolen from the
# hidden input when clicking on the gutter and transferred to the
# already-focused host element. The host element never gets a 'focus' event
# however, which leaves us in a limbo state where the text editor element is
# focused but the hidden input isn't focused. This always refocuses the hidden
# input if a blur event occurs in the shadow DOM that is transferring focus
# back to the host element.
shadowRootBlurred: (event) ->
@component.focused() if event.relatedTarget is this
inputNodeBlurred: (event) ->
if event.relatedTarget isnt this
@dispatchEvent(new FocusEvent('blur', bubbles: false))
addGrammarScopeAttribute: ->
@dataset.grammar = @model.getGrammar()?.scopeName?.replace(/\./g, ' ')

View File

@@ -35,7 +35,10 @@ class TextEditorPresenter
@observedBlockDecorations = new Set()
@invalidatedDimensionsByBlockDecoration = new Set()
@invalidateAllBlockDecorationsDimensions = false
@precedingBlockDecorationsByScreenRowAndId = {}
@followingBlockDecorationsByScreenRowAndId = {}
@screenRowsToMeasure = []
@flashCountsByDecorationId = {}
@transferMeasurementsToModel()
@transferMeasurementsFromModel()
@observeModel()
@@ -138,6 +141,11 @@ class TextEditorPresenter
@shouldUpdateDecorations = true
observeModel: ->
@disposables.add @model.displayLayer.onDidReset =>
@spliceBlockDecorationsInRange(0, Infinity, Infinity)
@shouldUpdateDecorations = true
@emitDidUpdateState()
@disposables.add @model.displayLayer.onDidChangeSync (changes) =>
for change in changes
startRow = change.start.row
@@ -192,7 +200,7 @@ class TextEditorPresenter
highlights: {}
overlays: {}
cursors: {}
blockDecorations: {}
offScreenBlockDecorations: {}
gutters: []
# Shared state that is copied into ``@state.gutters`.
@sharedGutterStyles = {}
@@ -292,24 +300,21 @@ class TextEditorPresenter
tileForRow: (row) ->
row - (row % @tileSize)
constrainRow: (row) ->
Math.max(0, Math.min(row, @model.getScreenLineCount()))
getStartTileRow: ->
@constrainRow(@tileForRow(@startRow ? 0))
@tileForRow(@startRow ? 0)
getEndTileRow: ->
@constrainRow(@tileForRow(@endRow ? 0))
@tileForRow(@endRow ? 0)
isValidScreenRow: (screenRow) ->
screenRow >= 0 and screenRow < @model.getScreenLineCount()
screenRow >= 0 and screenRow < @model.getApproximateScreenLineCount()
getScreenRowsToRender: ->
startRow = @getStartTileRow()
endRow = @constrainRow(@getEndTileRow() + @tileSize)
endRow = @getEndTileRow() + @tileSize
screenRows = [startRow...endRow]
longestScreenRow = @model.getLongestScreenRow()
longestScreenRow = @model.getApproximateLongestScreenRow()
if longestScreenRow?
screenRows.push(longestScreenRow)
if @screenRowsToMeasure?
@@ -355,7 +360,7 @@ class TextEditorPresenter
zIndex = 0
for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize
tileEndRow = @constrainRow(tileStartRow + @tileSize)
tileEndRow = tileStartRow + @tileSize
rowsWithinTile = []
while screenRowIndex >= 0
@@ -390,7 +395,7 @@ class TextEditorPresenter
visibleTiles[tileStartRow] = true
zIndex++
if @mouseWheelScreenRow? and 0 <= @mouseWheelScreenRow < @model.getScreenLineCount()
if @mouseWheelScreenRow? and 0 <= @mouseWheelScreenRow < @model.getApproximateScreenLineCount()
mouseWheelTile = @tileForRow(@mouseWheelScreenRow)
unless visibleTiles[mouseWheelTile]?
@@ -409,20 +414,17 @@ class TextEditorPresenter
visibleLineIds = {}
for screenRow in screenRows
line = @linesByScreenRow.get(screenRow)
unless line?
throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}")
continue unless line?
visibleLineIds[line.id] = true
precedingBlockDecorations = @precedingBlockDecorationsByScreenRow[screenRow] ? []
followingBlockDecorations = @followingBlockDecorationsByScreenRow[screenRow] ? []
precedingBlockDecorations = @precedingBlockDecorationsByScreenRowAndId[screenRow] ? {}
followingBlockDecorations = @followingBlockDecorationsByScreenRowAndId[screenRow] ? {}
if tileState.lines.hasOwnProperty(line.id)
lineState = tileState.lines[line.id]
lineState.screenRow = screenRow
lineState.decorationClasses = @lineDecorationClassesForRow(screenRow)
lineState.precedingBlockDecorations = precedingBlockDecorations
lineState.followingBlockDecorations = followingBlockDecorations
lineState.hasPrecedingBlockDecorations = precedingBlockDecorations.length > 0
lineState.hasFollowingBlockDecorations = followingBlockDecorations.length > 0
else
tileState.lines[line.id] =
screenRow: screenRow
@@ -431,8 +433,6 @@ class TextEditorPresenter
decorationClasses: @lineDecorationClassesForRow(screenRow)
precedingBlockDecorations: precedingBlockDecorations
followingBlockDecorations: followingBlockDecorations
hasPrecedingBlockDecorations: precedingBlockDecorations.length > 0
hasFollowingBlockDecorations: followingBlockDecorations.length > 0
for id, line of tileState.lines
delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
@@ -598,7 +598,9 @@ class TextEditorPresenter
visibleLineNumberIds = {}
for screenRow in screenRows when @isRowRendered(screenRow)
lineId = @linesByScreenRow.get(screenRow).id
line = @linesByScreenRow.get(screenRow)
continue unless line?
lineId = line.id
{bufferRow, softWrappedAtStart: softWrapped} = @displayLayer.softWrapDescriptorForScreenRow(screenRow)
foldable = not softWrapped and @model.isFoldableAtBufferRow(bufferRow)
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
@@ -625,7 +627,7 @@ class TextEditorPresenter
return unless @scrollTop? and @lineHeight? and @height?
@endRow = Math.min(
@model.getScreenLineCount(),
@model.getApproximateScreenLineCount(),
@lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight - 1) + 1
)
@@ -659,7 +661,7 @@ class TextEditorPresenter
updateVerticalDimensions: ->
if @lineHeight?
oldContentHeight = @contentHeight
@contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getScreenLineCount()))
@contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getApproximateScreenLineCount()))
if @contentHeight isnt oldContentHeight
@updateHeight()
@@ -669,7 +671,7 @@ class TextEditorPresenter
updateHorizontalDimensions: ->
if @baseCharacterWidth?
oldContentWidth = @contentWidth
rightmostPosition = @model.getRightmostScreenPosition()
rightmostPosition = @model.getApproximateRightmostScreenPosition()
@contentWidth = @pixelPositionForScreenPosition(rightmostPosition).left
@contentWidth += @scrollLeft
@contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width
@@ -1060,41 +1062,42 @@ class TextEditorPresenter
@decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1)
updateBlockDecorations: ->
@blockDecorationsToRenderById = {}
@precedingBlockDecorationsByScreenRow = {}
@followingBlockDecorationsByScreenRow = {}
visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1)
if @invalidateAllBlockDecorationsDimensions
for decoration in @model.getDecorations(type: 'block')
@invalidatedDimensionsByBlockDecoration.add(decoration)
@invalidateAllBlockDecorationsDimensions = false
for markerId, decorations of visibleDecorationsByMarkerId
visibleDecorationsById = {}
visibleDecorationsByScreenRowAndId = {}
for markerId, decorations of @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1)
for decoration in decorations when decoration.isType('block')
@updateBlockDecorationState(decoration, true)
screenRow = decoration.getMarker().getHeadScreenPosition().row
if decoration.getProperties().position is "after"
@followingBlockDecorationsByScreenRowAndId[screenRow] ?= {}
@followingBlockDecorationsByScreenRowAndId[screenRow][decoration.id] = {screenRow, decoration}
else
@precedingBlockDecorationsByScreenRowAndId[screenRow] ?= {}
@precedingBlockDecorationsByScreenRowAndId[screenRow][decoration.id] = {screenRow, decoration}
visibleDecorationsById[decoration.id] = true
visibleDecorationsByScreenRowAndId[screenRow] ?= {}
visibleDecorationsByScreenRowAndId[screenRow][decoration.id] = true
for screenRow, blockDecorations of @precedingBlockDecorationsByScreenRowAndId
if Number(screenRow) isnt @mouseWheelScreenRow
for id, blockDecoration of blockDecorations
unless visibleDecorationsByScreenRowAndId[screenRow]?[id]
delete @precedingBlockDecorationsByScreenRowAndId[screenRow][id]
for screenRow, blockDecorations of @followingBlockDecorationsByScreenRowAndId
if Number(screenRow) isnt @mouseWheelScreenRow
for id, blockDecoration of blockDecorations
unless visibleDecorationsByScreenRowAndId[screenRow]?[id]
delete @followingBlockDecorationsByScreenRowAndId[screenRow][id]
@state.content.offScreenBlockDecorations = {}
@invalidatedDimensionsByBlockDecoration.forEach (decoration) =>
@updateBlockDecorationState(decoration, false)
for decorationId, decorationState of @state.content.blockDecorations
continue if @blockDecorationsToRenderById[decorationId]
continue if decorationState.screenRow is @mouseWheelScreenRow
delete @state.content.blockDecorations[decorationId]
updateBlockDecorationState: (decoration, isVisible) ->
return if @blockDecorationsToRenderById[decoration.getId()]
screenRow = decoration.getMarker().getHeadScreenPosition().row
if decoration.getProperties().position is "after"
@followingBlockDecorationsByScreenRow[screenRow] ?= []
@followingBlockDecorationsByScreenRow[screenRow].push(decoration)
else
@precedingBlockDecorationsByScreenRow[screenRow] ?= []
@precedingBlockDecorationsByScreenRow[screenRow].push(decoration)
@state.content.blockDecorations[decoration.getId()] = {decoration, screenRow, isVisible}
@blockDecorationsToRenderById[decoration.getId()] = true
unless visibleDecorationsById[decoration.id]
@state.content.offScreenBlockDecorations[decoration.id] = decoration
updateLineDecorations: ->
@lineDecorationsByScreenRow = {}
@@ -1181,6 +1184,9 @@ class TextEditorPresenter
startTile = @tileForRow(screenRange.start.row)
endTile = @tileForRow(screenRange.end.row)
needsFlash = properties.flashCount? and @flashCountsByDecorationId[decorationId] isnt properties.flashCount
if needsFlash
@flashCountsByDecorationId[decorationId] = properties.flashCount
for tileStartRow in [startTile..endTile] by @tileSize
rangeWithinTile = @intersectRangeWithTile(screenRange, tileStartRow)
@@ -1190,6 +1196,7 @@ class TextEditorPresenter
tileState = @state.content.tiles[tileStartRow] ?= {highlights: {}}
highlightState = tileState.highlights[decorationId] ?= {}
highlightState.needsFlash = needsFlash
highlightState.flashCount = properties.flashCount
highlightState.flashClass = properties.flashClass
highlightState.flashDuration = properties.flashDuration
@@ -1296,7 +1303,7 @@ class TextEditorPresenter
setBlockDecorationDimensions: (decoration, width, height) ->
return unless @observedBlockDecorations.has(decoration)
@lineTopIndex.resizeBlock(decoration.getId(), height)
@lineTopIndex.resizeBlock(decoration.id, height)
@invalidatedDimensionsByBlockDecoration.delete(decoration)
@shouldUpdateDecorations = true
@@ -1333,7 +1340,7 @@ class TextEditorPresenter
@didDestroyBlockDecoration(decoration)
isAfter = decoration.getProperties().position is "after"
@lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0, isAfter)
@lineTopIndex.insertBlock(decoration.id, decoration.getMarker().getHeadScreenPosition().row, 0, isAfter)
@observedBlockDecorations.add(decoration)
@invalidateBlockDecorationDimensions(decoration)
@@ -1347,14 +1354,14 @@ class TextEditorPresenter
# change.
return if markerEvent.textChanged
@lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row)
@lineTopIndex.moveBlock(decoration.id, decoration.getMarker().getHeadScreenPosition().row)
@shouldUpdateDecorations = true
@emitDidUpdateState()
didDestroyBlockDecoration: (decoration) ->
return unless @observedBlockDecorations.has(decoration)
@lineTopIndex.removeBlock(decoration.getId())
@lineTopIndex.removeBlock(decoration.id)
@observedBlockDecorations.delete(decoration)
@invalidatedDimensionsByBlockDecoration.delete(decoration)
@shouldUpdateDecorations = true
@@ -1530,7 +1537,7 @@ class TextEditorPresenter
[@startRow, @endRow]
isRowRendered: (row) ->
@getStartTileRow() <= row < @constrainRow(@getEndTileRow() + @tileSize)
@getStartTileRow() <= row < @getEndTileRow() + @tileSize
isOpenTagCode: (tagCode) ->
@displayLayer.isOpenTagCode(tagCode)

View File

@@ -2,7 +2,7 @@ _ = require 'underscore-plus'
path = require 'path'
fs = require 'fs-plus'
Grim = require 'grim'
{CompositeDisposable, Emitter} = require 'event-kit'
{CompositeDisposable, Disposable, Emitter} = require 'event-kit'
{Point, Range} = TextBuffer = require 'text-buffer'
LanguageMode = require './language-mode'
DecorationManager = require './decoration-manager'
@@ -184,6 +184,10 @@ class TextEditor extends Model
else
@displayLayer = @buffer.addDisplayLayer(displayLayerParams)
@backgroundWorkHandle = requestIdleCallback(@doBackgroundWork)
@disposables.add new Disposable =>
cancelIdleCallback(@backgroundWorkHandle) if @backgroundWorkHandle?
@displayLayer.setTextDecorationLayer(@tokenizedBuffer)
@defaultMarkerLayer = @displayLayer.addMarkerLayer()
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true)
@@ -210,6 +214,13 @@ class TextEditor extends Model
priority: 0
visible: lineNumberGutterVisible
doBackgroundWork: (deadline) =>
if @displayLayer.doBackgroundWork(deadline)
@presenter?.updateVerticalDimensions()
@backgroundWorkHandle = requestIdleCallback(@doBackgroundWork)
else
@backgroundWorkHandle = null
update: (params) ->
displayLayerParams = {}
@@ -394,6 +405,9 @@ class TextEditor extends Model
@disposables.add @displayLayer.onDidChangeSync (e) =>
@mergeIntersectingSelections()
@emitter.emit 'did-change', e
@disposables.add @displayLayer.onDidReset =>
@mergeIntersectingSelections()
@emitter.emit 'did-change', {}
destroyed: ->
@disposables.dispose()
@@ -910,6 +924,8 @@ class TextEditor extends Model
# editor. This accounts for folds.
getScreenLineCount: -> @displayLayer.getScreenLineCount()
getApproximateScreenLineCount: -> @displayLayer.getApproximateScreenLineCount()
# Essential: Returns a {Number} representing the last zero-indexed buffer row
# number of the editor.
getLastBufferRow: -> @buffer.getLastRow()
@@ -956,7 +972,6 @@ class TextEditor extends Model
tokens
screenLineForScreenRow: (screenRow) ->
return if screenRow < 0 or screenRow > @getLastScreenRow()
@displayLayer.getScreenLines(screenRow, screenRow + 1)[0]
bufferRowForScreenRow: (screenRow) ->
@@ -974,10 +989,14 @@ class TextEditor extends Model
getRightmostScreenPosition: -> @displayLayer.getRightmostScreenPosition()
getApproximateRightmostScreenPosition: -> @displayLayer.getApproximateRightmostScreenPosition()
getMaxScreenLineLength: -> @getRightmostScreenPosition().column
getLongestScreenRow: -> @getRightmostScreenPosition().row
getApproximateLongestScreenRow: -> @getApproximateRightmostScreenPosition().row
lineLengthForScreenRow: (screenRow) -> @displayLayer.lineLengthForScreenRow(screenRow)
# Returns the range for the given buffer row.
@@ -2868,7 +2887,7 @@ class TextEditor extends Model
# whitespace.
usesSoftTabs: ->
for bufferRow in [0..@buffer.getLastRow()]
continue if @tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
continue if @tokenizedBuffer.tokenizedLines[bufferRow]?.isComment()
line = @buffer.lineForRow(bufferRow)
return true if line[0] is ' '

View File

@@ -1,10 +1,3 @@
{values} = require 'underscore-plus'
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
clone
module.exports =
class TiledComponent
updateSync: (state) ->
@@ -41,7 +34,7 @@ class TiledComponent
component = @componentsByTileId[tileRow] = @buildComponentForTile(tileRow)
@getTilesNode().appendChild(component.getDomNode())
@oldState.tiles[tileRow] = cloneObject(tileState)
@oldState.tiles[tileRow] = Object.assign({}, tileState)
component.updateSync(@newState)
@@ -50,5 +43,9 @@ class TiledComponent
getComponentForTile: (tileRow) ->
@componentsByTileId[tileRow]
getComponents: ->
for _, component of @componentsByTileId
component
getTiles: ->
values(@componentsByTileId).map (component) -> component.getDomNode()
@getComponents().map((component) -> component.getDomNode())

View File

@@ -1,126 +0,0 @@
{Point} = require 'text-buffer'
module.exports =
class TokenizedBufferIterator
constructor: (@tokenizedBuffer) ->
@openTags = null
@closeTags = null
@containingTags = null
seek: (position) ->
@openTags = []
@closeTags = []
@tagIndex = null
currentLine = @tokenizedBuffer.tokenizedLineForRow(position.row)
@currentTags = currentLine.tags
@currentLineOpenTags = currentLine.openScopes
@currentLineLength = currentLine.text.length
@containingTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.scopeForId(id)
currentColumn = 0
for tag, index in @currentTags
if tag >= 0
if currentColumn >= position.column
@tagIndex = index
break
else
currentColumn += tag
@containingTags.pop() while @closeTags.shift()
@containingTags.push(openTag) while openTag = @openTags.shift()
else
scopeName = @tokenizedBuffer.grammar.scopeForId(tag)
if tag % 2 is 0 # close tag
if @openTags.length > 0
if currentColumn >= position.column
@tagIndex = index
break
else
@containingTags.pop() while @closeTags.shift()
@containingTags.push(openTag) while openTag = @openTags.shift()
@closeTags.push(scopeName)
else # open tag
@openTags.push(scopeName)
@tagIndex ?= @currentTags.length
@position = Point(position.row, Math.min(@currentLineLength, currentColumn))
@containingTags.slice()
moveToSuccessor: ->
@containingTags.pop() for tag in @closeTags
@containingTags.push(tag) for tag in @openTags
@openTags = []
@closeTags = []
loop
if @tagIndex is @currentTags.length
if @isAtTagBoundary()
break
else
if @shouldMoveToNextLine
@moveToNextLine()
@openTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.scopeForId(id)
@shouldMoveToNextLine = false
else if @nextLineHasMismatchedContainingTags()
@closeTags = @containingTags.slice().reverse()
@containingTags = []
@shouldMoveToNextLine = true
else
return false unless @moveToNextLine()
else
tag = @currentTags[@tagIndex]
if tag >= 0
if @isAtTagBoundary()
break
else
@position = Point(@position.row, Math.min(@currentLineLength, @position.column + @currentTags[@tagIndex]))
else
scopeName = @tokenizedBuffer.grammar.scopeForId(tag)
if tag % 2 is 0
if @openTags.length > 0
break
else
@closeTags.push(scopeName)
else
@openTags.push(scopeName)
@tagIndex++
true
getPosition: ->
@position
getCloseTags: ->
@closeTags.slice()
getOpenTags: ->
@openTags.slice()
###
Section: Private Methods
###
nextLineHasMismatchedContainingTags: ->
if line = @tokenizedBuffer.tokenizedLineForRow(@position.row + 1)
return true if line.openScopes.length isnt @containingTags.length
for i in [0...@containingTags.length] by 1
if @containingTags[i] isnt @tokenizedBuffer.grammar.scopeForId(line.openScopes[i])
return true
false
else
false
moveToNextLine: ->
@position = Point(@position.row + 1, 0)
if tokenizedLine = @tokenizedBuffer.tokenizedLineForRow(@position.row)
@currentTags = tokenizedLine.tags
@currentLineLength = tokenizedLine.text.length
@currentLineOpenTags = tokenizedLine.openScopes
@tagIndex = 0
true
else
false
isAtTagBoundary: ->
@closeTags.length > 0 or @openTags.length > 0

View File

@@ -0,0 +1,174 @@
const {Point} = require('text-buffer')
module.exports = class TokenizedBufferIterator {
constructor (tokenizedBuffer) {
this.tokenizedBuffer = tokenizedBuffer
this.openTags = null
this.closeTags = null
this.containingTags = null
}
seek (position) {
this.openTags = []
this.closeTags = []
this.tagIndex = null
const currentLine = this.tokenizedBuffer.tokenizedLineForRow(position.row)
this.currentTags = currentLine.tags
this.currentLineOpenTags = currentLine.openScopes
this.currentLineLength = currentLine.text.length
this.containingTags = this.currentLineOpenTags.map((id) => this.scopeForId(id))
let currentColumn = 0
for (let [index, tag] of this.currentTags.entries()) {
if (tag >= 0) {
if (currentColumn >= position.column) {
this.tagIndex = index
break
} else {
currentColumn += tag
while (this.closeTags.length > 0) {
this.closeTags.shift()
this.containingTags.pop()
}
while (this.openTags.length > 0) {
const openTag = this.openTags.shift()
this.containingTags.push(openTag)
}
}
} else {
const scopeName = this.scopeForId(tag)
if (tag % 2 === 0) {
if (this.openTags.length > 0) {
if (currentColumn >= position.column) {
this.tagIndex = index
break
} else {
while (this.closeTags.length > 0) {
this.closeTags.shift()
this.containingTags.pop()
}
while (this.openTags.length > 0) {
const openTag = this.openTags.shift()
this.containingTags.push(openTag)
}
}
}
this.closeTags.push(scopeName)
} else {
this.openTags.push(scopeName)
}
}
}
if (this.tagIndex == null) {
this.tagIndex = this.currentTags.length
}
this.position = Point(position.row, Math.min(this.currentLineLength, currentColumn))
return this.containingTags.slice()
}
moveToSuccessor () {
for (let tag of this.closeTags) { // eslint-disable-line no-unused-vars
this.containingTags.pop()
}
for (let tag of this.openTags) {
this.containingTags.push(tag)
}
this.openTags = []
this.closeTags = []
while (true) {
if (this.tagIndex === this.currentTags.length) {
if (this.isAtTagBoundary()) {
break
} else if (this.shouldMoveToNextLine) {
this.moveToNextLine()
this.openTags = this.currentLineOpenTags.map((id) => this.scopeForId(id))
this.shouldMoveToNextLine = false
} else if (this.nextLineHasMismatchedContainingTags()) {
this.closeTags = this.containingTags.slice().reverse()
this.containingTags = []
this.shouldMoveToNextLine = true
} else if (!this.moveToNextLine()) {
return false
}
} else {
const tag = this.currentTags[this.tagIndex]
if (tag >= 0) {
if (this.isAtTagBoundary()) {
break
} else {
this.position = Point(this.position.row, Math.min(
this.currentLineLength,
this.position.column + this.currentTags[this.tagIndex]
))
}
} else {
const scopeName = this.scopeForId(tag)
if (tag % 2 === 0) {
if (this.openTags.length > 0) {
break
} else {
this.closeTags.push(scopeName)
}
} else {
this.openTags.push(scopeName)
}
}
this.tagIndex++
}
}
return true
}
getPosition () {
return this.position
}
getCloseTags () {
return this.closeTags.slice()
}
getOpenTags () {
return this.openTags.slice()
}
nextLineHasMismatchedContainingTags () {
const line = this.tokenizedBuffer.tokenizedLineForRow(this.position.row + 1)
if (line == null) {
return false
} else {
return (
this.containingTags.length !== line.openScopes.length ||
this.containingTags.some((tag, i) => tag !== this.scopeForId(line.openScopes[i]))
)
}
}
moveToNextLine () {
this.position = Point(this.position.row + 1, 0)
const tokenizedLine = this.tokenizedBuffer.tokenizedLineForRow(this.position.row)
if (tokenizedLine == null) {
return false
} else {
this.currentTags = tokenizedLine.tags
this.currentLineLength = tokenizedLine.text.length
this.currentLineOpenTags = tokenizedLine.openScopes
this.tagIndex = 0
return true
}
}
isAtTagBoundary () {
return this.closeTags.length > 0 || this.openTags.length > 0
}
scopeForId (id) {
const scope = this.tokenizedBuffer.grammar.scopeForId(id)
if (scope) {
return `syntax--${scope.replace(/\./g, '.syntax--')}`
} else {
return null
}
}
}

View File

@@ -36,7 +36,6 @@ class TokenizedBuffer extends Model
@tokenIterator = new TokenIterator(this)
@disposables.add @buffer.registerTextDecorationLayer(this)
@rootScopeDescriptor = new ScopeDescriptor(scopes: ['text.plain'])
@setGrammar(grammar ? NullGrammar)
@@ -95,14 +94,17 @@ class TokenizedBuffer extends Model
false
retokenizeLines: ->
lastRow = @buffer.getLastRow()
@fullyTokenized = false
@tokenizedLines = new Array(lastRow + 1)
@tokenizedLines = new Array(@buffer.getLineCount())
@invalidRows = []
@invalidateRow(0)
if @largeFileMode or @grammar.name is 'Null Grammar'
@markTokenizationComplete()
else
@invalidateRow(0)
setVisible: (@visible) ->
@tokenizeInBackground() if @visible
if @visible and @grammar.name isnt 'Null Grammar' and not @largeFileMode
@tokenizeInBackground()
getTabLength: -> @tabLength
@@ -117,12 +119,6 @@ class TokenizedBuffer extends Model
@tokenizeNextChunk() if @isAlive() and @buffer.isAlive()
tokenizeNextChunk: ->
# Short circuit null grammar which can just use the placeholder tokens
if (@grammar.name is 'Null Grammar') and @firstInvalidRow()?
@invalidRows = []
@markTokenizationComplete()
return
rowsRemaining = @chunkSize
while @firstInvalidRow()? and rowsRemaining > 0
@@ -167,8 +163,6 @@ class TokenizedBuffer extends Model
return
invalidateRow: (row) ->
return if @largeFileMode
@invalidRows.push(row)
@invalidRows.sort (a, b) -> a - b
@tokenizeInBackground()
@@ -189,18 +183,19 @@ class TokenizedBuffer extends Model
start = oldRange.start.row
end = oldRange.end.row
delta = newRange.end.row - oldRange.end.row
oldLineCount = oldRange.end.row - oldRange.start.row + 1
newLineCount = newRange.end.row - newRange.start.row + 1
@updateInvalidRows(start, end, delta)
previousEndStack = @stackForRow(end) # used in spill detection below
if @largeFileMode or @grammar is NullGrammar
newTokenizedLines = @buildPlaceholderTokenizedLinesForRows(start, end + delta)
if @largeFileMode or @grammar.name is 'Null Grammar'
_.spliceWithArray(@tokenizedLines, start, oldLineCount, new Array(newLineCount))
else
newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1), @openScopesForRow(start))
_.spliceWithArray(@tokenizedLines, start, end - start + 1, newTokenizedLines)
newEndStack = @stackForRow(end + delta)
if newEndStack and not _.isEqual(newEndStack, previousEndStack)
@invalidateRow(end + delta + 1)
_.spliceWithArray(@tokenizedLines, start, oldLineCount, newTokenizedLines)
newEndStack = @stackForRow(end + delta)
if newEndStack and not _.isEqual(newEndStack, previousEndStack)
@invalidateRow(end + delta + 1)
isFoldableAtRow: (row) ->
if @largeFileMode
@@ -211,46 +206,39 @@ class TokenizedBuffer extends Model
# Returns a {Boolean} indicating whether the given buffer row starts
# a a foldable row range due to the code's indentation patterns.
isFoldableCodeAtRow: (row) ->
# Investigating an exception that's occurring here due to the line being
# undefined. This should paper over the problem but we want to figure out
# what is happening:
tokenizedLine = @tokenizedLineForRow(row)
@assert tokenizedLine?, "TokenizedLine is undefined", (error) =>
error.metadata = {
row: row
rowCount: @tokenizedLines.length
tokenizedBufferChangeCount: @changeCount
bufferChangeCount: @buffer.changeCount
}
return false unless tokenizedLine?
return false if @buffer.isRowBlank(row) or tokenizedLine.isComment()
nextRow = @buffer.nextNonBlankRow(row)
return false unless nextRow?
@indentLevelForRow(nextRow) > @indentLevelForRow(row)
if 0 <= row <= @buffer.getLastRow()
nextRow = @buffer.nextNonBlankRow(row)
tokenizedLine = @tokenizedLines[row]
if @buffer.isRowBlank(row) or tokenizedLine?.isComment() or not nextRow?
false
else
@indentLevelForRow(nextRow) > @indentLevelForRow(row)
else
false
isFoldableCommentAtRow: (row) ->
previousRow = row - 1
nextRow = row + 1
return false if nextRow > @buffer.getLastRow()
(row is 0 or not @tokenizedLineForRow(previousRow).isComment()) and
@tokenizedLineForRow(row).isComment() and
@tokenizedLineForRow(nextRow).isComment()
if nextRow > @buffer.getLastRow()
false
else
Boolean(
not (@tokenizedLines[previousRow]?.isComment()) and
@tokenizedLines[row]?.isComment() and
@tokenizedLines[nextRow]?.isComment()
)
buildTokenizedLinesForRows: (startRow, endRow, startingStack, startingopenScopes) ->
ruleStack = startingStack
openScopes = startingopenScopes
stopTokenizingAt = startRow + @chunkSize
tokenizedLines = for row in [startRow..endRow]
tokenizedLines = for row in [startRow..endRow] by 1
if (ruleStack or row is 0) and row < stopTokenizingAt
tokenizedLine = @buildTokenizedLineForRow(row, ruleStack, openScopes)
ruleStack = tokenizedLine.ruleStack
openScopes = @scopesFromTags(openScopes, tokenizedLine.tags)
else
tokenizedLine = @buildPlaceholderTokenizedLineForRow(row, openScopes)
tokenizedLine = undefined
tokenizedLine
if endRow >= stopTokenizingAt
@@ -259,21 +247,6 @@ class TokenizedBuffer extends Model
tokenizedLines
buildPlaceholderTokenizedLinesForRows: (startRow, endRow) ->
@buildPlaceholderTokenizedLineForRow(row) for row in [startRow..endRow] by 1
buildPlaceholderTokenizedLineForRow: (row) ->
@buildPlaceholderTokenizedLineForRowWithText(row, @buffer.lineForRow(row))
buildPlaceholderTokenizedLineForRowWithText: (row, text) ->
if @grammar isnt NullGrammar
openScopes = [@grammar.startIdForScope(@grammar.scopeName)]
else
openScopes = []
tags = [text.length]
lineEnding = @buffer.lineEndingForRow(row)
new TokenizedLine({openScopes, text, tags, lineEnding, @tokenIterator})
buildTokenizedLineForRow: (row, ruleStack, openScopes) ->
@buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack, openScopes)
@@ -283,8 +256,14 @@ class TokenizedBuffer extends Model
new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator})
tokenizedLineForRow: (bufferRow) ->
if 0 <= bufferRow < @tokenizedLines.length
@tokenizedLines[bufferRow] ?= @buildPlaceholderTokenizedLineForRow(bufferRow)
if 0 <= bufferRow <= @buffer.getLastRow()
if tokenizedLine = @tokenizedLines[bufferRow]
tokenizedLine
else
text = @buffer.lineForRow(bufferRow)
lineEnding = @buffer.lineEndingForRow(bufferRow)
tags = [@grammar.startIdForScope(@grammar.scopeName), text.length, @grammar.endIdForScope(@grammar.scopeName)]
@tokenizedLines[bufferRow] = new TokenizedLine({openScopes: [], text, tags, lineEnding, @tokenIterator})
tokenizedLinesForRows: (startRow, endRow) ->
for row in [startRow..endRow] by 1
@@ -294,8 +273,7 @@ class TokenizedBuffer extends Model
@tokenizedLines[bufferRow]?.ruleStack
openScopesForRow: (bufferRow) ->
if bufferRow > 0
precedingLine = @tokenizedLineForRow(bufferRow - 1)
if precedingLine = @tokenizedLines[bufferRow - 1]
@scopesFromTags(precedingLine.openScopes, precedingLine.tags)
else
[]
@@ -448,7 +426,7 @@ class TokenizedBuffer extends Model
logLines: (start=0, end=@buffer.getLastRow()) ->
for row in [start..end]
line = @tokenizedLineForRow(row).text
line = @tokenizedLines[row].text
console.log row, line, line.length
return

View File

@@ -142,4 +142,13 @@ class WorkspaceElement extends HTMLElement
ipcRenderer.send('run-package-specs', specPath)
runBenchmarks: ->
if activePath = @workspace.getActivePaneItem()?.getPath?()
[projectPath] = @project.relativizePath(activePath)
else
[projectPath] = @project.getPaths()
if projectPath
ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks'))
module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype

View File

@@ -13,14 +13,14 @@
// Editors
& when ( lightness(@syntax-background-color) < 50% ) {
.platform-darwin atom-text-editor:not([mini])::shadow .editor-contents--private {
.platform-darwin atom-text-editor:not([mini]) .editor-contents--private {
.cursor-white();
}
}
// Mini Editors
& when ( lightness(@input-background-color) < 50% ) {
.platform-darwin atom-text-editor[mini]::shadow .editor-contents--private {
.platform-darwin atom-text-editor[mini] .editor-contents--private {
.cursor-white();
}
}

View File

@@ -2,31 +2,17 @@
@import "octicon-utf-codes";
@import "octicon-mixins";
atom-text-editor {
display: block;
font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
}
atom-text-editor[mini] {
font-size: @input-font-size;
line-height: @component-line-height;
max-height: @component-line-height + 2; // +2 for borders
overflow: auto;
}
atom-overlay {
position: fixed;
display: block;
z-index: 4;
}
// TODO: Remove the following styles when the editor shadow DOM can no longer be disabled
atom-text-editor {
display: flex;
font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
.editor-contents {
.editor--private, .editor-contents--private {
height: 100%;
width: 100%;
}
.editor-contents--private {
width: 100%;
overflow: hidden;
cursor: text;
display: flex;
-webkit-user-select: none;
@@ -35,6 +21,7 @@ atom-text-editor {
.gutter {
overflow: hidden;
z-index: 0;
text-align: right;
cursor: default;
min-width: 1em;
@@ -217,3 +204,16 @@ atom-text-editor {
right: 0;
}
}
atom-text-editor[mini] {
font-size: @input-font-size;
line-height: @component-line-height;
max-height: @component-line-height + 2; // +2 for borders
overflow: auto;
}
atom-overlay {
position: fixed;
display: block;
z-index: 4;
}

View File

@@ -1,201 +0,0 @@
@import "ui-variables";
@import "octicon-utf-codes";
@import "octicon-mixins";
.editor--private, .editor-contents--private {
height: 100%;
width: 100%;
}
.editor-contents--private {
width: 100%;
cursor: text;
display: flex;
-webkit-user-select: none;
position: relative;
}
.gutter {
overflow: hidden;
z-index: 0;
text-align: right;
cursor: default;
min-width: 1em;
box-sizing: border-box;
}
.line-numbers {
position: relative;
}
.line-number {
position: relative;
white-space: nowrap;
padding-left: .5em;
opacity: 0.6;
&.cursor-line {
opacity: 1;
}
.icon-right {
.octicon(chevron-down, 0.8em);
display: inline-block;
visibility: hidden;
opacity: .6;
padding: 0 .4em;
&::before {
text-align: center;
}
}
}
.gutter:hover {
.line-number.foldable .icon-right {
visibility: visible;
&:hover {
opacity: 1;
}
}
}
.gutter, .gutter:hover {
.line-number.folded .icon-right {
.octicon(chevron-right, 0.8em);
visibility: visible;
&::before {
position: relative;
left: -.1em;
}
}
}
.scroll-view {
position: relative;
z-index: 0;
overflow: hidden;
flex: 1;
min-width: 0;
min-height: 0;
}
.highlight {
background: none;
padding: 0;
}
.highlight .region {
position: absolute;
pointer-events: none;
z-index: -1;
}
.lines {
min-width: 100%;
position: relative;
z-index: 1;
}
.line {
white-space: pre;
&.cursor-line .fold-marker::after {
opacity: 1;
}
}
.fold-marker {
cursor: default;
&::after {
.icon(0.8em, inline);
content: @ellipsis;
padding-left: 0.2em;
}
}
.placeholder-text {
position: absolute;
color: @text-color-subtle;
}
.invisible-character {
font-weight: normal !important;
font-style: normal !important;
}
.indent-guide {
display: inline-block;
box-shadow: inset 1px 0;
}
.hidden-input {
padding: 0;
border: 0;
position: absolute;
z-index: -1;
top: 0;
left: 0;
opacity: 0;
width: 1px;
}
.cursor {
z-index: 4;
pointer-events: none;
box-sizing: border-box;
position: absolute;
border-left: 1px solid;
opacity: 0;
}
&.is-focused .cursor {
opacity: 1;
}
.cursors.blink-off .cursor {
opacity: 0;
}
.horizontal-scrollbar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 15px;
overflow-x: auto;
overflow-y: hidden;
z-index: 3;
cursor: default;
.scrollbar-content {
height: 15px;
}
}
.vertical-scrollbar {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 15px;
overflow-x: hidden;
overflow-y: auto;
z-index: 3;
cursor: default;
}
.scrollbar-corner {
position: absolute;
overflow: auto;
bottom: 0;
right: 0;
}