diff --git a/apm/package.json b/apm/package.json index 31162dcafb8..73f860586d6 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.12.5" + "atom-package-manager": "1.13.0" } } diff --git a/dot-atom/styles.less b/dot-atom/styles.less index a321469e7a7..e4fad4f3119 100644 --- a/dot-atom/styles.less +++ b/dot-atom/styles.less @@ -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; } diff --git a/package.json b/package.json index 46f7d6e5f66..f4ab1375c33 100644 --- a/package.json +++ b/package.json @@ -47,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", @@ -67,18 +69,18 @@ "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.0", + "solarized-light-syntax": "1.1.0", "about": "1.7.0", "archive-view": "0.62.0", "autocomplete-atom-api": "0.10.0", @@ -87,18 +89,18 @@ "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.1", + "find-and-replace": "0.203.0", "fuzzy-finder": "1.4.0", - "git-diff": "1.1.0", + "git-diff": "1.2.0", "go-to-line": "0.31.0", "grammar-selector": "0.48.2", "image-view": "0.60.0", @@ -106,16 +108,16 @@ "keybinding-resolver": "0.35.0", "line-ending-selector": "0.5.0", "link": "0.31.2", - "markdown-preview": "0.158.8", + "markdown-preview": "0.159.0", "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", @@ -123,7 +125,7 @@ "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", diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index ef46b09c550..1600da9d78e 100644 --- a/spec/atom-reporter.coffee +++ b/spec/atom-reporter.coffee @@ -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 = """ -
- #{marked(deprecation.message)} -
- """ - - for stack in deprecation.getStacks() - fullStack = stack.map ({functionName, location}) -> - if functionName is '' - " 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) -> diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index 483a7767568..2172267db28 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -70,7 +70,7 @@ describe "LinesYardstick", -> font-size: 12px; font-family: monospace; } - .function { + .syntax--function { font-size: 16px } """ @@ -144,7 +144,7 @@ describe "LinesYardstick", -> font-size: 12px; font-family: monospace; } - .function { + .syntax--function { font-size: 16px } """ diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 7dbd6a4e5da..44d8b4460b1 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -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') diff --git a/spec/style-manager-spec.coffee b/spec/style-manager-spec.coffee deleted file mode 100644 index d0a1cfe13f4..00000000000 --- a/spec/style-manager-spec.coffee +++ /dev/null @@ -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}" - ] diff --git a/spec/style-manager-spec.js b/spec/style-manager-spec.js new file mode 100644 index 00000000000..898e855877c --- /dev/null +++ b/spec/style-manager-spec.js @@ -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}' + ]) + }) + }) + }) +}) diff --git a/spec/styles-element-spec.coffee b/spec/styles-element-spec.coffee index b1a57938c4e..0889b0d43ad 100644 --- a/spec/styles-element-spec.coffee +++ b/spec/styles-element-spec.coffee @@ -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' diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 7de1c6ccd7e..4478df5322d 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -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('let' + invisibles.eol + '') + expect(component.lineNodeForScreenRow(0).innerHTML).toBe('let' + invisibles.eol + '') }) 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('CE') + expect(component.lineNodeForScreenRow(10).innerHTML).toBe('CE') editor.setTabLength(3) runAnimationFrames() - expect(component.lineNodeForScreenRow(10).innerHTML).toBe('CE') + expect(component.lineNodeForScreenRow(10).innerHTML).toBe('CE') editor.setTabLength(1) runAnimationFrames() - expect(component.lineNodeForScreenRow(10).innerHTML).toBe('CE') + expect(component.lineNodeForScreenRow(10).innerHTML).toBe('CE') editor.setTextInBufferRange([[9, 0], [9, Infinity]], ' ') editor.setTextInBufferRange([[11, 0], [11, Infinity]], ' ') runAnimationFrames() - expect(component.lineNodeForScreenRow(10).innerHTML).toBe('CE') + expect(component.lineNodeForScreenRow(10).innerHTML).toBe('CE') }) 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 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/) }) diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee index 22b92164767..7ed4a106feb 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -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", -> diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index f4760ece1b8..8af2f9abd44 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -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([]) - - it "inserts block decorations before the line if not specified otherwise", -> + 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 "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] - - 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) + waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 2], [6, 2]]) + runs -> + expectValues stateForHighlightInTile(presenter, highlight, 2), {needsFlash: false} + expectValues stateForHighlightInTile(presenter, highlight, 4), {needsFlash: false} - presenter.setScrollTop(100) - presenter.setMouseWheelScreenRow(0) + describe ".offScreenBlockDecorations", -> + stateForOffScreenBlockDecoration = (presenter, decoration) -> + getState(presenter).content.offScreenBlockDecorations[decoration.id] - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 0 - isVisible: true - } - - advanceClock(presenter.stoppedScrollingDelay) + 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) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBe(blockDecoration4) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) + presenter.setBlockDecorationDimensions(blockDecoration4, 0, 20) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBeUndefined() - it "invalidates block decorations that intersect a change in the buffer", -> + 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) + + blockDecoration4.destroy() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBeUndefined() + + 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) - - 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 - } + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3) 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() + 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) - 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) + 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) - - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + 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}) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() presenter.setContentFrameWidth(100) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() presenter.setWindowSize(100, 200) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) 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", -> - 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 - } - - 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 - } - - presenter.invalidateBlockDecorationDimensions(blockDecoration1) - presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) - presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10) - - 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 - } - - blockDecoration1.destroy() - - 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(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] = [] diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index c1c7141c366..1890b0ef798 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -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", -> diff --git a/spec/tokenized-buffer-iterator-spec.js b/spec/tokenized-buffer-iterator-spec.js index f6764401c4b..cc703bbec5f 100644 --- a/spec/tokenized-buffer-iterator-spec.js +++ b/spec/tokenized-buffer-iterator-spec.js @@ -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([]) }) }) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 6558d42b478..eff79ec95a4 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -590,21 +590,21 @@ describe "TokenizedBuffer", -> 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 @@ -617,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", -> @@ -671,5 +671,5 @@ describe "TokenizedBuffer", -> 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'] diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee deleted file mode 100644 index 48bbf77f3fe..00000000000 --- a/src/block-decorations-component.coffee +++ /dev/null @@ -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 diff --git a/src/deprecated-syntax-selectors.js b/src/deprecated-syntax-selectors.js new file mode 100644 index 00000000000..4f8b9cefc15 --- /dev/null +++ b/src/deprecated-syntax-selectors.js @@ -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' +]) diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index a6e85b7e5b3..e5c1db60ee8 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -97,24 +97,23 @@ class HighlightsComponent flashHighlightNodeIfRequested: (id, newHighlightState) -> oldHighlightState = @oldState[id] - return unless newHighlightState.flashCount > oldHighlightState.flashCount - - highlightNode = @highlightNodesById[id] - - 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) - - if oldHighlightState.flashClass? - removeFlashClass() - requestAnimationFrame(addFlashClass) - else - addFlashClass() + if newHighlightState.needsFlash and oldHighlightState.flashCount isnt newHighlightState.flashCount + highlightNode = @highlightNodesById[id] + + 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) + + if oldHighlightState.flashClass? + removeFlashClass() + requestAnimationFrame(addFlashClass) + else + addFlashClass() - oldHighlightState.flashCount = newHighlightState.flashCount + oldHighlightState.flashCount = newHighlightState.flashCount diff --git a/src/lines-component.coffee b/src/lines-component.coffee index fb9b28b0398..6c92711794b 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -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) diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee deleted file mode 100644 index c1cb2ba64bf..00000000000 --- a/src/lines-tile-component.coffee +++ /dev/null @@ -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() diff --git a/src/lines-tile-component.js b/src/lines-tile-component.js new file mode 100644 index 00000000000..202e1670870 --- /dev/null +++ b/src/lines-tile-component.js @@ -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() + } + } +} diff --git a/src/off-screen-block-decorations-component.js b/src/off-screen-block-decorations-component.js new file mode 100644 index 00000000000..0460c854e04 --- /dev/null +++ b/src/off-screen-block-decorations-component.js @@ -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) + } + } +} diff --git a/src/style-manager.coffee b/src/style-manager.coffee deleted file mode 100644 index 8f932d22995..00000000000 --- a/src/style-manager.coffee +++ /dev/null @@ -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 `` 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') diff --git a/src/style-manager.js b/src/style-manager.js new file mode 100644 index 00000000000..b273f449bf3 --- /dev/null +++ b/src/style-manager.js @@ -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 `` 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} +} diff --git a/src/styles-element.coffee b/src/styles-element.coffee index d1e6bf3d9c3..2c53300c218 100644 --- a/src/styles-element.coffee +++ b/src/styles-element.coffee @@ -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 diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index d2647d767d5..cccd2f4c86f 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -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()) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 27a29bed657..4a7d1598dc8 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -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, ' ') diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index bbefc91c04b..22268af183a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -35,7 +35,10 @@ class TextEditorPresenter @observedBlockDecorations = new Set() @invalidatedDimensionsByBlockDecoration = new Set() @invalidateAllBlockDecorationsDimensions = false + @precedingBlockDecorationsByScreenRowAndId = {} + @followingBlockDecorationsByScreenRowAndId = {} @screenRowsToMeasure = [] + @flashCountsByDecorationId = {} @transferMeasurementsToModel() @transferMeasurementsFromModel() @observeModel() @@ -197,7 +200,7 @@ class TextEditorPresenter highlights: {} overlays: {} cursors: {} - blockDecorations: {} + offScreenBlockDecorations: {} gutters: [] # Shared state that is copied into ``@state.gutters`. @sharedGutterStyles = {} @@ -414,16 +417,14 @@ class TextEditorPresenter 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 @@ -432,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) @@ -1063,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 = {} @@ -1184,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) @@ -1193,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 @@ -1299,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 @@ -1336,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) @@ -1350,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 diff --git a/src/tiled-component.coffee b/src/tiled-component.coffee index 2e8dc7149f6..37de27e9b71 100644 --- a/src/tiled-component.coffee +++ b/src/tiled-component.coffee @@ -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()) diff --git a/src/tokenized-buffer-iterator.coffee b/src/tokenized-buffer-iterator.coffee deleted file mode 100644 index 23b72d5a91f..00000000000 --- a/src/tokenized-buffer-iterator.coffee +++ /dev/null @@ -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 diff --git a/src/tokenized-buffer-iterator.js b/src/tokenized-buffer-iterator.js new file mode 100644 index 00000000000..29d2fdf8694 --- /dev/null +++ b/src/tokenized-buffer-iterator.js @@ -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 + } + } +} diff --git a/static/cursors.less b/static/cursors.less index b2807217e10..0b54c6ea1c5 100644 --- a/static/cursors.less +++ b/static/cursors.less @@ -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(); } } diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 2082dc715b6..178f62d1d71 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -5,28 +5,14 @@ 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; + .editor--private, .editor-contents--private { + height: 100%; + width: 100%; + } - .editor-contents { + .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; +} diff --git a/static/text-editor-shadow.less b/static/text-editor-shadow.less deleted file mode 100644 index a3d44f56813..00000000000 --- a/static/text-editor-shadow.less +++ /dev/null @@ -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; -}