From 56a6510b25e526cd20ac0fcc4d7b8b235d47a1e8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 22 Sep 2016 14:06:38 +0200 Subject: [PATCH 01/88] Start on removing shadow DOM --- spec/text-editor-component-spec.js | 7 +- spec/text-editor-element-spec.coffee | 19 ++- src/lines-component.coffee | 4 - src/text-editor-component.coffee | 21 +-- src/text-editor-element.coffee | 37 ++--- static/text-editor-light.less | 2 + static/text-editor-shadow.less | 201 --------------------------- 7 files changed, 32 insertions(+), 259 deletions(-) delete mode 100644 static/text-editor-shadow.less diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index afbb3a50e..cc7b2bc0a 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -3408,8 +3408,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 () { @@ -4428,7 +4427,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').offsetHeight).toBe(200) expect(Grim.deprecate.callCount).toBe(1) expect(Grim.deprecate.argsForCall[0][0]).toMatch(/inline style/) }) @@ -4451,7 +4450,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').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 951b5ea36..f11baaf58 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -58,11 +58,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 = atom.workspace.buildTextEditor() @@ -73,11 +73,11 @@ 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", -> @@ -90,8 +90,7 @@ 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 @@ -110,7 +109,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] = [] @@ -142,7 +141,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) @@ -180,13 +179,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/src/lines-component.coffee b/src/lines-component.coffee index fb9b28b03..0b19a0d43 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -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 diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index d2647d767..596adb7c5 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -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 @@ -62,12 +62,8 @@ class TextEditorComponent @domElementPool = new DOMElementPool @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) + @domNode.classList.add('editor-contents') + @overlayManager = new OverlayManager(@presenter, @domNode, @views) @blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool) @scrollViewNode = document.createElement('div') @@ -80,8 +76,7 @@ class TextEditorComponent @linesComponent = new LinesComponent({@presenter, @hostElement, @domElementPool, @assert, @grammars}) @scrollViewNode.appendChild(@linesComponent.getDomNode()) - if @blockDecorationsComponent? - @linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode()) + @linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode()) @linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex) @presenter.setLinesYardstick(@linesYardstick) @@ -98,9 +93,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 @@ -969,9 +964,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 27a29bed6..fe45b99b9 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -1,9 +1,6 @@ {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 @@ -33,22 +30,7 @@ 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') - - @rootElement = document.createElement('div') - @rootElement.classList.add('editor--private') - - @shadowRoot.appendChild(@stylesElement) - @shadowRoot.appendChild(@rootElement) + @rootElement = this attachedCallback: -> @buildModel() unless @getModel()? @@ -116,18 +98,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) + @appendChild(@component.getDomNode()) + inputNode = @component.hiddenInputComponent.getDomNode() + inputNode.addEventListener 'focus', @focused.bind(this) + inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false)) unmountComponent: -> if @component? @@ -139,6 +121,9 @@ class TextEditorElement extends HTMLElement @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 @@ -148,8 +133,8 @@ class TextEditorElement extends HTMLElement # 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 + # shadowRootBlurred: (event) -> + # @component.focused() if event.relatedTarget is this addGrammarScopeAttribute: -> @dataset.grammar = @model.getGrammar()?.scopeName?.replace(/\./g, ' ') diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 2082dc715..acc7b4472 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -25,6 +25,7 @@ atom-text-editor { display: flex; .editor-contents { + height: 100%; width: 100%; overflow: hidden; cursor: text; @@ -35,6 +36,7 @@ atom-text-editor { .gutter { overflow: hidden; + z-index: 0; text-align: right; cursor: default; min-width: 1em; diff --git a/static/text-editor-shadow.less b/static/text-editor-shadow.less deleted file mode 100644 index a3d44f568..000000000 --- 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; -} From b71b412ede557cc69a19edcdd132a72570d1d34b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 22 Sep 2016 18:17:23 +0200 Subject: [PATCH 02/88] Transform deprecated shadow DOM selectors --- package.json | 1 + spec/styles-element-spec.coffee | 80 +++++++++++++------ spec/text-editor-component-spec.js | 123 +++++++++++++++-------------- src/lines-tile-component.coffee | 6 +- src/styles-element.coffee | 66 ++++++++++------ src/text-editor-component.coffee | 2 +- static/text-editor-light.less | 9 +-- 7 files changed, 171 insertions(+), 116 deletions(-) diff --git a/package.json b/package.json index a35f008e5..63b00ee8f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "nslog": "^3", "oniguruma": "6.1.0", "pathwatcher": "~6.5", + "postcss-selector-parser": "^2.2.1", "property-accessors": "^1.1.3", "random-words": "0.0.1", "resolve": "^1.1.6", diff --git a/spec/styles-element-spec.coffee b/spec/styles-element-spec.coffee index b1a57938c..b29979524 100644 --- a/spec/styles-element-spec.coffee +++ b/spec/styles-element-spec.coffee @@ -80,34 +80,66 @@ describe "StylesElement", -> 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 "removes the ::shadow pseudo-element from atom-text-editor selectors", -> + atom.styles.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; } + """) + expect(Array.from(element.lastChild.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' + ]) + expect(console.warn).toHaveBeenCalled() - it "upgrades selectors containing .editor", -> - atom.styles.addStyleSheet """ - .editor {background: black;} - .editor.mini {background: black;} - .editor:focus {background: black;} - """, context: 'atom-text-editor' + describe "when the context of a style sheet is 'atom-text-editor'", -> + it "prepends `--syntax` to selectors not contained in atom-text-editor or matching a spatial decoration", -> + atom.styles.addStyleSheet(""" + .class-1 { color: red; } + .class-2 > .class-3, .class-4.class-5 { color: green; } + .class-6 atom-text-editor .class-7 { color: yellow; } + atom-text-editor .class-8, .class-9 { color: blue; } + atom-text-editor .indent-guide, atom-text-editor .leading-whitespace { background: white; } + .syntax--class-10 { color: gray; } + :host .class-11 { color: purple; } + #id-1 { color: gray; } + """, {context: 'atom-text-editor'}) + expect(Array.from(element.lastChild.sheet.cssRules).map((r) -> r.selectorText)).toEqual([ + '.syntax--class-1', + '.syntax--class-2 > .syntax--class-3, .syntax--class-4.syntax--class-5', + '.class-6 atom-text-editor .class-7', + 'atom-text-editor .class-8, .syntax--class-9', + 'atom-text-editor .syntax--indent-guide, atom-text-editor .syntax--leading-whitespace', + '.syntax--class-10', + 'atom-text-editor .class-11', + '#id-1' + ]) + expect(console.warn).toHaveBeenCalled() - 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' + describe "when the context of a style sheet is not 'atom-text-editor'", -> + it "never prepends class names with `--syntax`", -> + atom.styles.addStyleSheet(""" + .class-1 { color: red; } + .class-2 > .class-3, .class-4.class-5 { color: green; } + .class-6 atom-text-editor .class-7 { color: yellow; } + atom-text-editor .class-8, .class-9 { color: blue; } + atom-text-editor .indent-guide, atom-text-editor .leading-whitespace { background: white; } + #id-1 { color: gray; } + """) + expect(Array.from(element.lastChild.sheet.cssRules).map((r) -> r.selectorText)).toEqual([ + '.class-1' + '.class-2 > .class-3, .class-4.class-5' + '.class-6 atom-text-editor .class-7' + 'atom-text-editor .class-8, .class-9' + 'atom-text-editor .indent-guide, atom-text-editor .leading-whitespace' + '#id-1' + ]) + expect(console.warn).not.toHaveBeenCalled() it "does not throw exceptions on rules with no selectors", -> atom.styles.addStyleSheet """ diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index cc7b2bc0a..5feb5761c 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -405,49 +405,49 @@ describe('TextEditorComponent', function () { } }) - it('applies .leading-whitespace for lines with leading spaces and/or tabs', function () { + it('applies .syntax--leading-whitespace for lines with leading spaces and/or tabs', function () { editor.setText(' a') runAnimationFrames() let leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(false) editor.setText('\ta') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(false) }) - it('applies .trailing-whitespace for lines with trailing spaces and/or tabs', function () { + it('applies .syntax--trailing-whitespace for lines with trailing spaces and/or tabs', function () { editor.setText(' ') runAnimationFrames() let leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) editor.setText('\t') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) editor.setText('a ') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) editor.setText('a\t') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) }) it('keeps rebuilding lines when continuous reflow is on', function () { @@ -501,14 +501,14 @@ describe('TextEditorComponent', function () { expect(component.lineNodeForScreenRow(0).textContent).toBe('' + invisibles.space + 'a line with tabs' + invisibles.tab + 'and spaces' + invisibles.space + invisibles.eol) let leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('invisible-character')).toBe(true) - expect(leafNodes[leafNodes.length - 1].classList.contains('invisible-character')).toBe(true) + expect(leafNodes[0].classList.contains('syntax--invisible-character')).toBe(true) + expect(leafNodes[leafNodes.length - 1].classList.contains('syntax--invisible-character')).toBe(true) }) 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 () { @@ -543,20 +543,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 () { @@ -583,30 +583,30 @@ describe('TextEditorComponent', function () { runAnimationFrames() }) - it('adds an "indent-guide" class to spans comprising the leading whitespace', function () { + it('adds an "syntax--indent-guide" class to spans comprising the leading whitespace', function () { let line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1)) expect(line1LeafNodes[0].textContent).toBe(' ') - expect(line1LeafNodes[0].classList.contains('indent-guide')).toBe(true) - expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe(false) + expect(line1LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line1LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(false) let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) - expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(false) + expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(false) }) - it('renders leading whitespace spans with the "indent-guide" class for empty lines', function () { + it('renders leading whitespace spans with the "syntax--indent-guide" class for empty lines', function () { editor.getBuffer().insert([1, Infinity], '\n') runAnimationFrames() let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(2) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) }) it('renders indent guides correctly on lines containing only whitespace', function () { @@ -616,11 +616,11 @@ describe('TextEditorComponent', function () { let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(3) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) expect(line2LeafNodes[2].textContent).toBe(' ') - expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(true) }) it('renders indent guides correctly on lines containing only whitespace when invisibles are enabled', function () { @@ -638,11 +638,11 @@ describe('TextEditorComponent', function () { let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(4) expect(line2LeafNodes[0].textContent).toBe('--') - expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe('--') - expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) expect(line2LeafNodes[2].textContent).toBe('--') - expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(true) expect(line2LeafNodes[3].textContent).toBe('x') }) @@ -653,9 +653,9 @@ describe('TextEditorComponent', function () { let line0LeafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(line0LeafNodes[0].textContent).toBe(' ') - expect(line0LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line0LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) expect(line0LeafNodes[1].textContent).toBe(' ') - expect(line0LeafNodes[1].classList.contains('indent-guide')).toBe(false) + expect(line0LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(false) }) it('updates the indent guides on empty lines preceding an indentation change', function () { @@ -667,9 +667,9 @@ describe('TextEditorComponent', function () { let line12LeafNodes = getLeafNodes(component.lineNodeForScreenRow(12)) expect(line12LeafNodes[0].textContent).toBe(' ') - expect(line12LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line12LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) expect(line12LeafNodes[1].textContent).toBe(' ') - expect(line12LeafNodes[1].classList.contains('indent-guide')).toBe(true) + expect(line12LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) }) it('updates the indent guides on empty lines following an indentation change', function () { @@ -682,9 +682,9 @@ describe('TextEditorComponent', function () { let line13LeafNodes = getLeafNodes(component.lineNodeForScreenRow(13)) expect(line13LeafNodes[0].textContent).toBe(' ') - expect(line13LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line13LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) expect(line13LeafNodes[1].textContent).toBe(' ') - expect(line13LeafNodes[1].classList.contains('indent-guide')).toBe(true) + expect(line13LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) }) }) @@ -701,11 +701,11 @@ describe('TextEditorComponent', function () { let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(3) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(false) + expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(false) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(false) + expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(false) expect(line2LeafNodes[2].textContent).toBe(' ') - expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(false) + expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(false) }) }) @@ -720,19 +720,19 @@ describe('TextEditorComponent', function () { describe('when there is a fold', function () { it('renders a fold marker on the folded line', function () { let foldedLineNode = component.lineNodeForScreenRow(4) - expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy() + expect(foldedLineNode.querySelector('.syntax--fold-marker')).toBeFalsy() editor.foldBufferRow(4) runAnimationFrames() foldedLineNode = component.lineNodeForScreenRow(4) - expect(foldedLineNode.querySelector('.fold-marker')).toBeTruthy() + expect(foldedLineNode.querySelector('.syntax--fold-marker')).toBeTruthy() editor.unfoldBufferRow(4) runAnimationFrames() foldedLineNode = component.lineNodeForScreenRow(4) - expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy() + expect(foldedLineNode.querySelector('.syntax--fold-marker')).toBeFalsy() }) }) }) @@ -1251,7 +1251,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) @@ -1268,7 +1268,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) @@ -1284,7 +1284,7 @@ describe('TextEditorComponent', function () { runAnimationFrames() let cursorRect = componentNode.querySelector('.cursor').getBoundingClientRect() - let foldMarkerRect = componentNode.querySelector('.fold-marker').getBoundingClientRect() + let foldMarkerRect = componentNode.querySelector('.syntax--fold-marker').getBoundingClientRect() expect(cursorRect.left).toBeCloseTo(foldMarkerRect.right, 0) }) @@ -1293,14 +1293,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) @@ -2872,19 +2872,20 @@ describe('TextEditorComponent', function () { editor.foldBufferRange([[4, 6], [4, 10]]) editor.foldBufferRange([[4, 15], [4, 20]]) runAnimationFrames() - let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') + debugger + let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') expect(foldMarkers.length).toBe(2) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 6]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 15]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') expect(foldMarkers.length).toBe(0) expect(editor.isFoldedAtBufferRow(4)).toBe(false) }) @@ -2894,25 +2895,25 @@ describe('TextEditorComponent', function () { editor.foldBufferRange([[4, 4], [4, 5]]) editor.foldBufferRange([[4, 4], [4, 20]]) runAnimationFrames() - let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') + let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 4]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 4]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 10]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') expect(foldMarkers.length).toBe(0) expect(editor.isFoldedAtBufferRow(4)).toBe(false) }) @@ -4292,7 +4293,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]) diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index c1cb2ba64..c7e3bf367 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -202,8 +202,8 @@ class LinesTileComponent if @presenter.isCloseTagCode(tagCode) openScopeNode = openScopeNode.parentElement else if @presenter.isOpenTagCode(tagCode) - scope = @presenter.tagForCode(tagCode) - newScopeNode = @domElementPool.buildElement("span", scope.replace(/\.+/g, ' ')) + scopes = @presenter.tagForCode(tagCode).replace(/\s+/g, '.').split('.').map((scope) -> "syntax--#{scope}") + newScopeNode = @domElementPool.buildElement("span", scopes.join(' ')) openScopeNode.appendChild(newScopeNode) openScopeNode = newScopeNode else @@ -219,7 +219,7 @@ class LinesTileComponent if lineText.endsWith(@presenter.displayLayer.foldCharacter) # Insert a zero-width non-breaking whitespace, so that - # LinesYardstick can take the fold-marker::after pseudo-element + # LinesYardstick can take the syntax--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) diff --git a/src/styles-element.coffee b/src/styles-element.coffee index d1e6bf3d9..e82775c92 100644 --- a/src/styles-element.coffee +++ b/src/styles-element.coffee @@ -1,4 +1,9 @@ {Emitter, CompositeDisposable} = require 'event-kit' +selectorProcessor = require 'postcss-selector-parser' +SPATIAL_DECORATIONS = new Set([ + 'invisible-character', 'hard-tab', 'leading-whitespace', + 'trailing-whitespace', 'eol', 'indent-guide', 'fold-marker' +]) class StylesElement extends HTMLElement subscriptions: null @@ -19,9 +24,8 @@ class StylesElement extends HTMLElement @styleElementClonesByOriginalElement = new WeakMap attachedCallback: -> - if @context is 'atom-text-editor' - for styleElement in @children - @upgradeDeprecatedSelectors(styleElement) + for styleElement in @children + @upgradeDeprecatedSelectors(styleElement) @context = @getAttribute('context') ? undefined @@ -64,10 +68,7 @@ class StylesElement extends HTMLElement break @insertBefore(styleElementClone, insertBefore) - - if @context is 'atom-text-editor' - @upgradeDeprecatedSelectors(styleElementClone) - + @upgradeDeprecatedSelectors(styleElementClone) @emitter.emit 'did-add-style-element', styleElementClone styleElementRemoved: (styleElement) -> @@ -90,28 +91,49 @@ class StylesElement extends HTMLElement upgradeDeprecatedSelectors: (styleElement) -> return unless styleElement.sheet? + transformDeprecatedShadowSelectors = (selectors) -> + selectors.each (selector) -> + isSyntaxSelector = not selector.some((node) -> + (node.type is 'tag' and node.value is 'atom-text-editor') or + (node.type is 'class' and node.value is 'region') or + (node.type is 'class' and node.value is 'wrap-guide') or + (node.type is 'class' and /spell-check/.test(node.value)) + ) + previousNode = null + selector.each (node) -> + isShadowPseudoClass = node.type is 'pseudo' and node.value is '::shadow' + isHostPseudoClass = node.type is 'pseudo' and node.value is ':host' + if isHostPseudoClass and not previousNode? + newNode = selectorProcessor.tag({value: 'atom-text-editor'}) + node.replaceWith(newNode) + previousNode = newNode + else if isShadowPseudoClass and previousNode?.type is 'tag' and previousNode?.value is 'atom-text-editor' + selector.removeChild(node) + else + if styleElement.context is 'atom-text-editor' and node.type is 'class' + if (isSyntaxSelector and not node.value.startsWith('syntax--')) or SPATIAL_DECORATIONS.has(node.value) + node.value = 'syntax--' + node.value + previousNode = node + upgradedSelectors = [] - - for rule in styleElement.sheet.cssRules - continue unless rule.selectorText? - continue if /\:host/.test(rule.selectorText) - + for rule in styleElement.sheet.cssRules when 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 + outputSelector = selectorProcessor(transformDeprecatedShadowSelectors).process(outputSelector).result + if inputSelector isnt 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" + upgradedSelectorsText = upgradedSelectors.map(({inputSelector, outputSelector}) -> "`#{inputSelector}` => `#{outputSelector}`").join('\n') + console.warn(""" + Shadow DOM for `atom-text-editor` elements has been removed. This means + should stop using :host and ::shadow pseudo-selectors, and prepend all + your syntax selectors with `syntax--`. To prevent breakage with existing + stylesheets, we have automatically upgraded the following selectors in + `#{styleElement.sourcePath}`: - warning += "\nSee the upgrade guide for information on removing this warning." - console.warn(warning) + #{upgradedSelectorsText} + """) 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 596adb7c5..e01096ed6 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -538,7 +538,7 @@ class TextEditorComponent screenPosition = @screenPositionForMouseEvent(event) - if event.target?.classList.contains('fold-marker') + if event.target?.classList.contains('syntax--fold-marker') bufferPosition = @editor.bufferPositionForScreenPosition(screenPosition) @editor.destroyFoldsIntersectingBufferRange([bufferPosition, bufferPosition]) return diff --git a/static/text-editor-light.less b/static/text-editor-light.less index acc7b4472..1f16ec71e 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -20,7 +20,6 @@ atom-overlay { z-index: 4; } -// TODO: Remove the following styles when the editor shadow DOM can no longer be disabled atom-text-editor { display: flex; @@ -123,12 +122,12 @@ atom-text-editor { .line { white-space: pre; - &.cursor-line .fold-marker::after { + &.cursor-line .syntax--fold-marker::after { opacity: 1; } } - .fold-marker { + .syntax--fold-marker { cursor: default; &::after { @@ -144,12 +143,12 @@ atom-text-editor { color: @text-color-subtle; } - .invisible-character { + .syntax--invisible-character { font-weight: normal !important; font-style: normal !important; } - .indent-guide { + .syntax--indent-guide { display: inline-block; box-shadow: inset 1px 0; } From 853130f58182b6a06b905c80d42bb2a3c50c09cd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 27 Sep 2016 07:37:12 -0700 Subject: [PATCH 03/88] Rewrite TokenizedBufferIterator in js --- src/tokenized-buffer-iterator.coffee | 126 -------------------- src/tokenized-buffer-iterator.js | 165 +++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 126 deletions(-) delete mode 100644 src/tokenized-buffer-iterator.coffee create mode 100644 src/tokenized-buffer-iterator.js diff --git a/src/tokenized-buffer-iterator.coffee b/src/tokenized-buffer-iterator.coffee deleted file mode 100644 index 23b72d5a9..000000000 --- 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 000000000..9c1f4977a --- /dev/null +++ b/src/tokenized-buffer-iterator.js @@ -0,0 +1,165 @@ +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.tokenizedBuffer.grammar.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.tokenizedBuffer.grammar.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) { + 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.tokenizedBuffer.grammar.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.tokenizedBuffer.grammar.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.tokenizedBuffer.grammar.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 + } +} From 2d553fae129950944ad1abdfdef4d2f3150dd584 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Sep 2016 09:53:47 -0700 Subject: [PATCH 04/88] Return scopes prepended with `--syntax` from `TokenizedBufferIterator` --- spec/tokenized-buffer-iterator-spec.js | 56 +++++++++++++------------- src/tokenized-buffer-iterator.js | 15 ++++--- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/spec/tokenized-buffer-iterator-spec.js b/spec/tokenized-buffer-iterator-spec.js index f6764401c..cc703bbec 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/src/tokenized-buffer-iterator.js b/src/tokenized-buffer-iterator.js index 9c1f4977a..32f6ed76a 100644 --- a/src/tokenized-buffer-iterator.js +++ b/src/tokenized-buffer-iterator.js @@ -17,7 +17,7 @@ module.exports = class TokenizedBufferIterator { this.currentTags = currentLine.tags this.currentLineOpenTags = currentLine.openScopes this.currentLineLength = currentLine.text.length - this.containingTags = this.currentLineOpenTags.map(id => this.tokenizedBuffer.grammar.scopeForId(id)) + this.containingTags = this.currentLineOpenTags.map(id => this.scopeForId(id)) let currentColumn = 0 for (let [index, tag] of this.currentTags.entries()) { @@ -37,7 +37,7 @@ module.exports = class TokenizedBufferIterator { } } } else { - const scopeName = this.tokenizedBuffer.grammar.scopeForId(tag) + const scopeName = this.scopeForId(tag) if (tag % 2 === 0) { if (this.openTags.length > 0) { if (currentColumn >= position.column) { @@ -83,7 +83,7 @@ module.exports = class TokenizedBufferIterator { break } else if (this.shouldMoveToNextLine) { this.moveToNextLine() - this.openTags = this.currentLineOpenTags.map(id => this.tokenizedBuffer.grammar.scopeForId(id)) + this.openTags = this.currentLineOpenTags.map(id => this.scopeForId(id)) this.shouldMoveToNextLine = false } else if (this.nextLineHasMismatchedContainingTags()) { this.closeTags = this.containingTags.slice().reverse() @@ -104,7 +104,7 @@ module.exports = class TokenizedBufferIterator { )) } } else { - const scopeName = this.tokenizedBuffer.grammar.scopeForId(tag) + const scopeName = this.scopeForId(tag) if (tag % 2 === 0) { if (this.openTags.length > 0) { break @@ -140,7 +140,7 @@ module.exports = class TokenizedBufferIterator { } else { return ( this.containingTags.length !== line.openScopes.length || - this.containingTags.some((tag, i) => tag !== this.tokenizedBuffer.grammar.scopeForId(line.openScopes[i])) + this.containingTags.some((tag, i) => tag !== this.scopeForId(line.openScopes[i])) ) } } @@ -162,4 +162,9 @@ module.exports = class TokenizedBufferIterator { isAtTagBoundary() { return this.closeTags.length > 0 || this.openTags.length > 0 } + + scopeForId (id) { + const scope = this.tokenizedBuffer.grammar.scopeForId(id).replace(/\./g, '.syntax--') + return `syntax--${scope}` + } } From 93512ba174d8ec442526dcc4894c7c690231129c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Oct 2016 16:09:27 +0200 Subject: [PATCH 05/88] Add deprecated syntax selectors --- src/deprecated-syntax-selectors.js | 646 +++++++++++++++++++++++++++++ 1 file changed, 646 insertions(+) create mode 100644 src/deprecated-syntax-selectors.js diff --git a/src/deprecated-syntax-selectors.js b/src/deprecated-syntax-selectors.js new file mode 100644 index 000000000..6d3184dc0 --- /dev/null +++ b/src/deprecated-syntax-selectors.js @@ -0,0 +1,646 @@ +module.exports = [ + 'AFDKO', 'AVX', 'AVX2', 'AVX512', 'AVX512BW', 'AVX512DQ', 'Alpha', + 'Animation', 'ArchaeologyDigSiteFrame', 'Arrow__', 'AtLilyPond', + 'AttrBaseType', 'AttrSetVal__', 'Button', 'C99', 'CharacterSet', 'Chatscript', + 'CheckButton', 'ClipboardType', 'Clipboard__', 'Codepages__', 'ColourActual', + 'ColourReal', 'ColourSize', 'ConfCachePolicy', 'ControlPoint', 'DBE', 'DDL', + 'DML', 'Database__', 'DdcMode', 'DiscussionFilterType', 'DiscussionStatus', + 'DisplaySchemes', 'Document-Structuring-Comment', 'Edit', + 'ExternalLinkBehaviour', 'ExternalLinkDirection', 'F16c', 'FMA', 'Font', + 'FontInstance', 'FontString', 'Fonts__', 'Frame', 'GameTooltip', 'GroupList', + 'HLE', 'HeaderEvent', 'HistoryType', 'HttpVerb', 'II', 'IO', 'Icon', 'IconID', + 'InPlaceBox__', 'InPlaceEditEvent', 'Info', 'JSXEndTagStart', + 'JSXStartTagEnd', 'KNC', 'Kotlin', 'LUW', 'Language', 'LdapItemList', + 'LinkFilter', 'LinkLimit', 'Locales__', 'Lock', 'LoginPolicy', 'MA_End__', + 'MA_StdCombo__', 'MA_StdItem__', 'MA_StdMenu__', 'MISSING', 'MessageFrame', + 'Minimap', 'Path', 'PlayerModel', 'Proof', 'RTM', 'RecentModule__', 'Regexp', + 'SCADABasic', 'Script__', 'ScrollEvent', 'ScrollSide', + 'ScrollingMessageFrame', 'Sensitivity', 'Slider', 'Stream', 'Style', + 'TM_COMMENT_END_2', 'TM_COMMENT_START', 'TODO', 'ToolType', 'Translation', + 'TriggerStatus', 'UIObject', 'UserClass', 'UserList', 'UserNotifyList', + 'VisibleRegion', 'ZipType', 'a', 'a10networks', 'aaa', 'abaqus', 'abbrev', + 'abbreviated', 'abcnotation', 'abl', 'abp', 'abstract', 'academic', 'access', + 'access-control', 'access-qualifiers', 'accessed', 'accessibility', + 'accessible', 'accessor', 'account', 'ace', 'acl', 'act', 'action', + 'actionpack', 'actions', 'actionscript', 'actionscript-file-encoding', + 'activerecord', 'activesupport', 'ada', 'add', 'addition', + 'additional-character', 'addon', 'address', 'address-of', 'addrfam', + 'adjustment', 'adverb', 'adx', 'aerospace', 'aes', 'aes_functions', 'aesni', + 'aexLightGreen', 'af', 'after-expression', 'agda', 'aggregate', 'aggregation', + 'ahk', 'alabel', 'alda', 'alert', 'alias', 'aliases', 'align', 'alignment', + 'all', 'all-once', 'all-solutions', 'allocatable', 'allocate', 'alloy', + 'alloyglobals', 'alloyxml', 'alog', 'alpha', 'alphabeticalllt', + 'alphabeticallyge', 'alphabeticallygt', 'alphabeticallyle', 'alt', 'alter', + 'alternate-wysiwyg-string', 'alternates', 'alternatives', 'amd', 'amd3DNow', + 'amdnops', 'ameter', 'amount', 'ampl', 'ampscript', 'analytics', 'anb', + 'anchor', 'and', 'angelscript', 'angle', 'angle-brackets', 'angular', + 'annotate', 'annotation', 'annotation-arguments', 'anon', 'anonymous', 'ansi', + 'ansi-c', 'ansible', 'answer', 'antl', 'antlr', 'antlr4', 'anubis', 'any', + 'any-method', 'aolserver', 'apache', 'apache-config', 'apc', 'apdl', 'apex', + 'api', 'api-notation', 'apiary', 'apib', 'apl', 'apostrophe', 'applescript', + 'application', 'application-name', 'application-process', 'approx-equal', + 'aqua', 'ar', 'arbitrary-radix', 'arbitrary-repetition', + 'arbitrary-repitition', 'architecture', 'arduino', 'arendelle', 'args', + 'argument', 'argument-label', 'argument-separator', 'argument-seperator', + 'arguments', 'arith', 'arithmetic', 'arithmetical', 'arithmeticcql', 'ark', + 'arm', 'armaConfig', 'arnoldc', 'arp', 'arpop', 'arr', 'array', + 'array-expression', 'array-item', 'arrays', 'arrow', 'articulation', + 'artihmetic', 'as', 'as3', 'asciidoc', 'asdoc', 'ash', 'ashx', 'asl', 'asm', + 'asm-instruction', 'asm-type-prefix', 'asn', 'asp', 'asp-core-2', 'aspx', + 'ass', 'assembly', 'assert', 'assertion', 'assigment', 'assign', 'assigned', + 'assigned-class', 'assigned-value', 'assignee', 'assignment', 'associate', + 'association', 'associativity', 'assocs', 'asterisk', 'async', 'asynchronous', + 'at-rule', 'at-sign', 'atml3', 'atoemp', 'atom', 'atomic', 'att', + 'attachment', 'attr', 'attribute', 'attribute-key-value', 'attribute-list', + 'attribute-lookup', 'attribute-name', 'attribute-selector', + 'attribute-specification', 'attribute-value', 'attribute-values', + 'attribute-with-value', 'attribute_list', 'attribute_value2', 'attributelist', + 'attributes', 'attrset', 'audio-file', 'auditor', 'augmented', 'auth', + 'auth_basic', 'author', 'authorization', 'auto', 'autoconf', 'autoindex', + 'autoit', 'automake', 'automatic', 'autotools', 'avdl', 'avrasm', 'avrdisasm', + 'avs', 'avx', 'avx2', 'awk', 'axis', 'b', 'babel', 'back', 'back-from', + 'back-reference', 'back-slash', 'backend', 'background', 'backlash', + 'backreference', 'backslash', 'backspace', 'backtick', 'bad-ampersand', + 'bad-angle-bracket', 'bad-comments-or-CDATA', 'bad-escape', 'bang', 'banner', + 'bar', 'bareword', 'barline', 'base-11', 'base-13', 'base-15', 'base-17', + 'base-19', 'base-21', 'base-23', 'base-25', 'base-27', 'base-29', 'base-3', + 'base-31', 'base-33', 'base-35', 'base-5', 'base-7', 'base-9', 'base-integer', + 'base64', 'base85', 'base_pound_number_pound', 'basetype', 'basic', + 'basic-arithmetic', 'basic_functions', 'bat', 'batch', 'batchfile', + 'battlesim', 'bb', 'bbcode', 'bcmath', 'be', 'beam', 'beamer', 'beancount', + 'begin', 'begin-document', 'begin-end-group', 'behaviour', 'bell', 'bem', + 'between-tag-pair', 'bff', 'bg-black', 'bg-blue', 'bg-cyan', 'bg-green', + 'bg-normal', 'bg-purple', 'bg-red', 'bg-white', 'bg-yellow', 'bhtml', 'bhv', + 'bibtex', 'bif', 'big-arrow', 'bigdecimal', 'bigint', 'biicode', 'biiconf', + 'bin', 'binOp', 'binary', 'binding', 'bindings', 'bioinformatics', + 'biosphere', 'bison', 'bit', 'bit-and-byte', 'bit-wise', 'bitarray', + 'bits-mov', 'bitwise', 'black', 'blade', 'blaze', 'blenc', 'blend', + 'blending', 'block', 'block-dartdoc', 'block-data', 'block-directive', + 'block-level', 'block-params', 'blockid', 'blockname', 'blockquote', + 'blocktitle', 'blue', 'blueprint', 'bm', 'bmi1', 'bmi2', 'bnd', 'bnf', 'body', + 'bold', 'bolt', 'boo', 'boogie', 'bool', 'boolean', 'boolean-test', 'boot', + 'border', 'bottom', 'bounded', 'bounds', 'bow', 'box', 'bpl', 'brace', + 'braced', 'braces', 'bracket', 'bracketed', 'brackets', 'brainfuck', 'branch', + 'break', 'breakpoint', 'bridle', 'brightscript', 'bro', 'broken', 'browser', + 'bsl', 'buffered', 'buffers', 'bugzilla-number', 'build', 'buildin', + 'built-in', 'built-ins', 'builtin', 'bulk', 'bullet', 'bullet-point', + 'bundle', 'but', 'buttons', 'by-name', 'by-number', 'byref', 'byte', 'bz2', + 'c', 'c-style', 'c0', 'c1', 'c2hs', 'c99', 'ca', 'cabal-keyword', 'cache', + 'cache-management', 'cacheability-control', 'cake', 'calc', 'calca', + 'calendar', 'call', 'callmethod', 'callout', 'camlp4', 'camlp4-stream', + 'capability', 'capnp', 'cappuccino', 'caps', 'caption', 'capture', 'cascade', + 'case', 'case-block', 'case-body', 'case-statement', 'case-terminator', + 'case_control', 'cassius', 'cast', 'catch', 'catch-exception', 'catcode', + 'categort', 'category', 'cbot', 'cc65', 'cdata', 'cdef', 'cell', 'cellwall', + 'ceq', 'ces', 'cexpr', 'cf', 'cfdg', 'cfengine', 'cfg', 'cfml', 'cfunction', + 'cg', 'cgx', 'chain', 'chainname', 'changed', 'changelogs', 'changes', + 'channel', 'chapel', 'chapter', 'char', 'character', 'character-class', + 'character-data-not-allowed-here', 'character-literal-too-long', + 'character-not-allowed-here', 'character_not_allowed_here', 'characters', + 'chars', 'charset', 'check', 'children', 'chord', 'chorus', 'chuck', 'chunk', + 'cirru', 'cisco', 'citation', 'cite', 'citrine', 'cjam', 'cjson', 'class', + 'class-constraint', 'class-constraints', 'class-declaration', 'class-fns', + 'class-instance', 'class-struct-block', 'class-type', 'class-type-definition', + 'classes', 'classicalb', 'classname', 'classobj', 'clause', + 'clause-head-body', 'clauses', 'clear', 'cleared', 'clflushopt', 'click', + 'client', 'clip', 'clipboard', 'clips', 'clmul', 'clock', 'clojure', 'close', + 'closed', 'closing', 'closure', 'cm', 'cmake', 'cmb', 'cmd', 'cobject', + 'cocoa', 'cocor', 'cod4mp', 'code', 'code-example', 'codeblock', 'codetag', + 'codimension', 'codstr', 'coffee', 'coffeescript', 'coil', 'collection', + 'colon', 'colons', 'color', 'colour', 'colspan', 'column', 'column-specials', + 'com', 'combinators', 'comboboxes', 'comma', 'comma-parenthesis', 'command', + 'commandline', 'comment', 'comment-italic', 'commentblock', 'commented-out', + 'commentinline', 'commit-command', 'common', 'common-lisp', 'commonform', + 'communications', 'community', 'compare', 'compareOp', 'comparison', + 'compatibility-version', 'compile', 'compile-only', 'compiled', + 'compiled-papyrus', 'compiler', 'complement', 'complete_tag', 'complex', + 'component', 'component_instantiation', 'compositor', 'compound', + 'compound-assignment', 'compress', 'computercraft', 'concatenator', + 'concrete', 'condition', 'conditional', 'conditional-directive', + 'conditionals', 'conditions', 'conf', 'config', 'configuration', 'confluence', + 'conftype', 'conjunction', 'conky', 'connstate', 'cons', + 'consider_option_subnode', 'consider_options_subnode', 'considering', + 'console', 'const', 'const-data', 'constant', 'constants', 'constrained', + 'constraint', 'constraints', 'construct', 'constructor', 'constructs', + 'consult', 'container', 'contains', 'content', 'content-detective', + 'contentSupplying', 'context', 'context-free', 'context-signature', + 'contigous', 'continuation', 'continuations', 'continue', 'continuum', + 'contol', 'contract', 'contracts', 'control', 'control-management', + 'control-systems', 'control-transfer', 'controller', 'conventional', + 'conversion', 'convert-type', 'cookie', 'cool', 'coord1', 'coord2', + 'coordinates', 'coq', 'core', 'core-parse', 'core-rule', 'cos', 'counter', + 'counters', 'cover', 'cplkg', 'cplusplus', 'cpp', 'cpp-type', 'cpp_type', + 'cpu12', 'cql', 'cram', 'crc32', 'create', 'creation', 'critic', 'crl', + 'crontab', 'crypto', 'crystal', 'cs', 'csharp', 'cshtml', 'csi', 'csound', + 'csound-document', 'csound-score', 'cspm', 'css', 'csv', 'csx', 'ct', 'ctkey', + 'ctxvar', 'ctxvarbracket', 'ctype', 'cubic-bezier', 'cucumber', 'cuda', + 'cuesheet', 'cup', 'cupsym', 'curl', 'curley', 'curly', 'currency', + 'curve-fitting', 'custom', 'custom-media', 'cut', 'cve-number', 'cvs', + 'cy-GB', 'cyan', 'cycle', 'cypher', 'cyrix', 'cython', 'd', 'daml', 'dana', + 'danger', 'dark_aqua', 'dark_blue', 'dark_gray', 'dark_green', 'dark_purple', + 'dark_red', 'dart', 'dartdoc', 'dash', 'dasm', 'data', 'data-acquisition', + 'data-index', 'data-integrity', 'data-step', 'data-transfer', 'database', + 'database-name', 'datablock', 'datafeed', 'datatype', 'datatypes', 'date', + 'date-time', 'datetime', 'day', 'db', 'dba', 'dbx', 'dc', 'dcon', 'dd', 'de', + 'dealii', 'deallocate', 'deb-control', 'debug', 'debug-password', 'debugger', + 'dec', 'decimal', 'decision', 'decl', 'declaration', 'declaration-prod', + 'declarator', 'declare', 'decoration', 'decorator', 'decrement', 'def', + 'default', 'default-frame-rate', 'default-script-limits', 'default-size', + 'defaults-css-files', 'defaults-css-url', 'deferred', 'define', 'definedness', + 'definition', 'definitions', 'defintions', 'deflate', 'delete', 'deleted', + 'deletion', 'delimited', 'delimiter', 'delimiters', 'dense', 'deprecated', + 'dereference', 'derived', 'derived-type', 'desc', 'describe', 'description', + 'design', 'desktop', 'destination', 'destructor', 'destructured', 'developer', + 'device', 'diagnostic', 'dialogue', 'dict', 'dictionary', 'dictionaryname', + 'diff', 'difference', 'different', 'dimension', 'dip', 'dir', 'dircolors', + 'direct', 'direction', 'directive', 'directives', 'directory', 'dirtyblue', + 'dirtygreen', 'disable', 'disable-markdown', 'discarded', 'disjunction', + 'disk', 'disk-folder-file', 'divider', 'django', 'dl', 'dlv', 'dm', 'dml', + 'dnl', 'do', 'dobody', 'doc', 'doc-comment', 'docRoot', 'dockerfile', + 'doconce', 'docstring', 'doctype', 'document', 'documentation', + 'documentroot', 'doki', 'dollar', 'dollar_variable', 'dom', 'domain', + 'dontcollect', 'doors', 'dot', 'dot-access', 'dotenv', 'dots', 'dotted', + 'double', 'double-arrow', 'double-colon', 'double-dash', 'double-dot', + 'double-number-sign', 'double-percentage', 'double-quote', 'double-quoted', + 'double-slash', 'doublequote', 'doubleslash', 'dougle', 'doxyfile', 'doxygen', + 'drive', 'droiuby', 'drop', 'drop-shadow', 'droplevel', 'drummode', 'drupal', + 'dsl', 'dt', 'dtl', 'dummy', 'dummy-variable', 'dump', 'duration', 'dust', + 'dust_Conditional', 'dust_filter', 'dust_partial_not_self_closing', + 'dust_section_context', 'dust_self_closing_section_tag', + 'dust_start_section_tag', 'dustjs', 'dwscript', 'dxl', 'dylan', 'dyndoc', + 'dyon', 'e', 'each', 'eachin', 'earl-grey', 'ebuild', 'echo', 'ecmascript', + 'eco', 'ecr', 'ect', 'ect2', 'ect3', 'ect4', 'edasm', 'edge', 'ee', + 'eel-expression', 'eex', 'effect', 'effectgroup', 'eiffel', 'eight', 'eio', + 'el_expression', 'elasticsearch', 'elasticsearch2', 'element', 'elemental', + 'elements', 'elemnt', 'elif', 'elision', 'elixir', 'ellipsis', 'elm', 'elmx', + 'else', 'elseif', 'elsewhere', 'eltype', 'elvis', 'em', 'email', 'embed', + 'embedded', 'embedded-c', 'embedded-ruby', 'embedded2', 'embeded', 'ember', + 'emberscript', 'emblem', 'emoji', 'emojicode', 'emph', 'emphasis', 'empty', + 'empty-dictionary', 'empty-list', 'empty-string', 'empty-tag', 'empty-tuple', + 'empty-typing-pair', 'empty_gif', 'emptyelement', 'en', 'en-au', 'en-old', + 'enable', 'enc', 'enchant', 'end', 'end-definition', 'end-document', + 'end-of-line', 'end-statement', 'endassociate', 'enddo', 'endfile', + 'endforall', 'endfunction', 'endif', 'ending', 'endinterface', 'endmodule', + 'endprogram', 'endselect', 'endsubmodule', 'endsubroutine', 'endtype', + 'endwhere', 'engine', 'entity', 'entity_instantiation', 'entitytype', 'entry', + 'entry-definition', 'entry-key', 'entry-type', 'entrypoint', 'enum', + 'enumeration', 'enumerator', 'enumerator-specification', 'env', 'environment', + 'environment-variable', 'eof', 'epatch', 'eq', 'equal', 'equalexpr', 'equals', + 'equals-sign', 'equation', 'erb', 'ereg', 'erlang', 'error', 'error-control', + 'errorfunc', 'errors', 'errorstop', 'es', 'es6', 'es6import', 'escape', + 'escape-code', 'escape-sequence', 'escaped', 'escaped-content', 'escapes', + 'escript', 'eso-lua', 'eso-txt', 'essence', 'eth', 'ethaddr', 'etpl', + 'european', 'evaled', 'evaluation', 'even-tab', 'event', 'event-call', + 'event-handler', 'eventType', 'eventb', 'eventend', 'events', 'exactly', + 'example', 'exampleText', 'examples', 'excel-link', 'exception', 'exceptions', + 'exclamation', 'exec', 'execution-context', 'exif', 'existential', 'exit', + 'exp', 'expected-array-separator', 'expected-dictionary-separator', + 'expected-extends', 'expected-implements', 'expected-range-separator', + 'expires', 'explicit', 'exponential', 'export', 'expr', 'expression', + 'expression-seperator', 'expressions', 'expressions-and-types', 'ext', + 'extempore', 'extend', 'extended', 'extends', 'extension', + 'extension-specification', 'extensions', 'extern', 'external', 'externs', + 'extersk', 'extglob', 'extra', 'extra-characters', 'extra-equals-sign', + 'extracted', 'extrassk', 'f5networks', 'fa', 'fact', 'factor', 'fail', + 'fakeroot', 'fallback', 'false', 'fandoc', 'fann', 'fantom', 'fastcgi', + 'fbaccidental', 'fbgroupclose', 'fbgroupopen', 'fbp', 'feature', 'features', + 'fenced', 'fhem', 'field', 'field-name', 'field-tag', 'fields', 'figuregroup', + 'filder-design-hdl-coder', 'file', 'file-name', 'file-path', 'fileinfo', + 'filename', 'filter', 'filter-pipe', 'filteredtranscludeblock', 'filters', + 'final', 'final-procedure', 'financial', 'financial-derivatives', 'find-m', + 'finder', 'finish', 'finn', 'firebug', 'first', 'first-class', 'first-line', + 'fish', 'fix_this_later', 'fixed', 'fixed-income', 'fixed-point', 'fixme', + 'fl', 'flag', 'flags', 'flash', 'flash-type', 'flash9', 'flatbuffers', + 'flex-config', 'float', 'float-exponent', 'floating-point', 'floating_point', + 'flow', 'flowtype', 'flush', 'fma', 'fn', 'folder-actions', 'following', + 'font', 'font-name', 'font-size', 'fontface', 'fonts', 'footer', 'footnote', + 'for', 'for-loop', 'for-quantity', 'forall', 'foreach', 'foreign', + 'forge-config', 'forin', 'form', 'format', 'format-verb', 'formatted', + 'formfeed', 'forth', 'fortran', 'forward', 'foundation', 'fountain', 'four', + 'fourd-command', 'fourd-comment', 'fourd-constant', 'fourd-constant-hex', + 'fourd-constant-number', 'fourd-constant-string', 'fourd-control-begin', + 'fourd-control-end', 'fourd-declaration', 'fourd-declaration-array', + 'fourd-dollar', 'fourd-local-variable', 'fourd-parameter', 'fourd-script', + 'fourd-table', 'fourd-tag', 'fourd-variable', 'fpm', 'fpu', 'fpu_x87', 'fr', + 'fragment', 'frame', 'frames', 'frametitle', 'framexml', 'freebasic', + 'freefem', 'from', 'from-file', 'front-matter', 'fsgsbase', 'fsharp', 'fsl', + 'fsm', 'ftl', 'fuck', 'full-line', 'fun', 'func', 'func-tag', 'funchand', + 'function', 'function-call', 'function-definition', 'function-parameter', + 'function-parameters', 'function-recursive', 'function-return', + 'function-type', 'function-with-body', 'functionDeclarationArgument', + 'function_definition', 'function_prototype', 'functioncall', 'functionend', + 'functions', 'fundimental', 'funk', 'funtion-definition', 'fus', + 'fuzzy-logic', 'fx', 'g', 'galaxy', 'gallery', 'gamebusk', 'gamescript', + 'gams', 'gams-lst', 'gap', 'garch', 'gather', 'gcode', 'gdb', 'gdscript', + 'gdx', 'ge', 'geck-keyword', 'general', 'general-purpose', 'generate', + 'generator', 'generic', 'generic-spec', 'generic-type', 'generic_list', + 'genericcall', 'genetic-algorithms', 'geo', 'geom', 'geometric', + 'geometry-adjustment', 'get', 'getproperty', 'getset', 'getter', 'gettext', + 'getword', 'gfm', 'gfm-todotxt', 'gfx', 'gh-number', 'gherkin', 'gisdk', + 'git', 'git-attributes', 'git-commit', 'git-config', 'git-rebase', + 'gitignore', 'given', 'gj', 'global', 'globalsection', 'glsl', + 'glyph_class_name', 'glyphname-value', 'gml', 'gmp', 'gms', 'gmsh', 'gmx', + 'gn', 'gnuplot', 'go', 'goatee', 'godmode', 'gohtml', 'gold', 'golo', + 'google', 'gotemplate', 'goto', 'goto-label', 'gradle', 'grails', 'grammar', + 'grammar-action', 'grammar-rule', 'grammar_production', 'grapahql', 'graph', + 'graphics', 'graphql', 'gray', 'greater', 'greek', 'green', 'gridlists', + 'grog', 'groovy', 'groovy-properties', 'group', 'group-title', 'growl', 'gs', + 'gsc', 'gsp', 'gt', 'guard', 'gui', 'guid', 'guillemot', 'gzip', + 'gzip_static', 'h', 'h1', 'hack', 'haddock', 'hairpin', 'ham', 'haml', + 'hamlbars', 'hamlet', 'hamlpy', 'handlebars', 'handler', 'haproxy-config', + 'harbou', 'harbour', 'hardlinebreaks', 'hash', 'hash-tick', 'hashicorp', + 'hashkey', 'haskell', 'haxe', 'haxedoc', 'hbs', 'hcl', 'hdl', 'hdr', 'he', + 'head', 'header', 'header-value', 'headers', 'heading', 'heading-1', + 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'height', + 'helen', 'helpers', 'heredoc', 'heredoc-token', 'herestring', 'heritage', + 'hex', 'hex-byte', 'hex-literal', 'hex-string', 'hex-value', 'hex8', + 'hexadecimal', 'hexidecimal', 'hexprefix', 'hidden', 'hide', 'highlight', + 'hive', 'hjson', 'hl7', 'hlsl', 'hn', 'hoa', 'hoc', 'hocomment', 'hocon', + 'hocontinuation', 'hocontrol', 'hombrew-formula', 'homematic', 'hook', + 'hostname', 'hosts', 'hour', 'hours', 'hps', 'hql', 'hr', 'hs', 'hsc2hs', + 'htaccess', 'html', 'html_entity', 'htmlbars', 'http', 'hu', 'hxinst', 'hy', + 'hyperlink', 'hyphen', 'hyphenation', 'i18n', 'iRev', 'ice', 'icinga2', + 'icmpv6type', 'iconv', 'id', 'identical', 'identifier', + 'identifiers-and-DTDs', 'idl', 'idris', 'ieee', 'if', 'if-block', 'if-branch', + 'if-condition', 'ifdef', 'ifndef', 'ignore', 'ignore-eol', 'ignorebii', + 'ignored', 'iisfunc', 'ilasm', 'illeagal', 'illegal', 'image', + 'image-acquisition', 'image-processing', 'imaginary', 'imba', 'immediate', + 'immutable', 'impex', 'implementation', 'implemented', 'implements', + 'implicit', 'import', 'import-all', 'importall', 'important', 'impure', 'in', + 'in-block', 'inappropriate', 'include', 'include-libraries', + 'include-resource-bundles', 'incode', 'incomplete', + 'incomplete-variable-assignment', 'inconsistent', 'increment', + 'increment-decrement', 'indepimage', 'index', 'index-seperator', 'indexed', + 'indexer', 'indexes', 'indices', 'inet', 'inetprototype', 'inferred', 'infes', + 'infinity', 'infix', 'info', 'inform', 'inform6', 'inform7', 'inherit', + 'inheritDoc', 'inheritance', 'inherited', 'inherited-class', 'ini', 'init', + 'initialization', 'initializer-list', 'inline', 'inline-data', + 'inline-expression', 'inlineblock', 'inner', 'inner-class', 'inno', 'inout', + 'input', 'inquire', 'inserted', 'insertion-and-extraction', 'inside', + 'install', 'instance', 'instantiation', 'instruction', 'instructions', + 'instrument', 'instrument-control', 'int', 'int64', 'integer', + 'integer-float', 'intel', 'intel-hex', 'intent', 'intepreted', 'interbase', + 'interface', 'interface-or-protocol', 'interfaces', 'internal', + 'internalsubset', 'internet', 'interpolated', 'interpolation', 'interrupt', + 'intersection', 'intl', 'intrinsic', 'intuicio4', 'invalid', + 'invalid-inequality', 'invalid-quote', 'invalid-variable-name', 'invocation', + 'invoke', 'io', 'ip', 'ip-port', 'ipkg', 'ipv4', 'ipv6', 'ipynb', 'irct', + 'is', 'isa', 'isc', 'iscexport', 'isclass', 'isml', 'issue', 'italic', 'item', + 'item-access', 'itemlevel', 'items', 'iteration', 'itunes', 'ivar', 'ja', + 'jack', 'jade', 'java', 'java-properties', 'java-props', 'javadoc', + 'javascript', 'jbeam', 'jekyll', 'jflex', 'jibo-rule', 'jinja', 'jison', + 'jisonlex', 'jmp', 'joker', 'jolie', 'jpl', 'jq', 'jquery', 'js', 'jsdoc', + 'jsduck', 'jsim', 'json', 'json5', 'jsoniq', 'jsonnet', 'jsont', 'jsp', 'jsx', + 'julia', 'julius', 'jump', 'juniper', 'junit-test-report', 'junos', 'juttle', + 'kag', 'kagex', 'kbd', 'keep-as3-metadata', 'kerboscript', 'kernel', + 'kevscript', 'kewyword', 'key', 'key-assignment', 'key-pair', 'key-path', + 'key-value', 'keyframe', 'keyframes', 'keygroup', 'keyvalue', 'keyword', + 'keyword_arrays', 'keyword_objects', 'keyword_roots', 'keyword_string', + 'keywords', 'kickstart', 'kind', 'kmd', 'kn', 'knitr', 'kos', 'kotlin', 'krl', + 'kspcfg', 'kurumin', 'kv', 'l20n', 'label', 'labeled', 'labeled-parameter', + 'lambda', 'langauge', 'language', 'languagebabel', 'languages', 'langversion', + 'largesk', 'lasso', 'last', 'latex', 'latex2', 'latino', 'latte', 'layout', + 'layoutbii', 'lbsearch', 'lc', 'lcb', 'ldap', 'ldif', 'le', 'leading', + 'leading-tabs', 'lean', 'left', 'left-margin', 'leftshift', 'legacy', 'lemon', + 'length', 'leopard', 'less', 'less-equal', 'let', 'letter', 'level', 'levels', + 'lex', 'lhs', 'li', 'lib', 'library', 'libxml', 'lifetime', 'ligature', + 'light_purple', 'lilypond', 'lilypond-figuregroup', 'lilypond-internals', + 'lilypond-lyricsmode', 'lilypond-notemode', 'lilypond-schememode', + 'limit_zone', 'line', 'line-break', 'line-continuation', + 'line-continuation-operator', 'line-number', 'linebreak', 'linefeed', + 'linenumber', 'link', 'link-label', 'link-report', 'link-text', 'link-url', + 'linkage', 'linkedsockets', 'linkplain', 'linkplain-label', 'linq', + 'linuxcncgcode', 'liquid', 'lisp', 'list', 'list-directive', 'list-separator', + 'list_item', 'listnum', 'listvalues', 'litcoffee', 'literal', + 'literal-string', 'literate', 'litword', 'livecodescript', 'livescript', + 'livescriptscript', 'll', 'llvm', 'load-constants', 'load-externs', + 'load-hint', 'loader', 'local', 'local-variables', 'locale-element', + 'localized', 'localized-description', 'localized-title', 'localname', + 'locals', 'location', 'lock', 'log', 'log-debug', 'log-error', 'log-info', + 'log-patch', 'log-success', 'log-verbose', 'log-warning', 'logging', 'logic', + 'logical', 'logical-expression', 'logicblox', 'logo', 'logstash', 'logtalk', + 'lol', 'long', 'look-ahead', 'look-behind', 'lookahead', 'lookaround', + 'lookbehind', 'loop', 'loop-control', 'lp', 'lparen', 'lsg', 'lsl', + 'lst-cpu12', 'lstdo', 'lt', 'lt-gt', 'lua', 'lucee', 'luceecomment', + 'luceescript', 'lucius', 'lury', 'lv', 'lyricsmode', 'm', 'm4', 'm4sh', + 'm65816', 'm68k', 'mac-classic', 'machine', 'macro', 'macro-usage', 'macro11', + 'madoko', 'magenta', 'magic', 'magik', 'mail', 'mailer', 'main', 'makefile', + 'mako', 'mamba', 'manager-class', 'map', 'mapfile', 'mapkey', 'mapping', + 'maprange', 'marasm', 'margin', 'marginpar', 'mark', 'markdown', 'marker', + 'marko', 'marko-attribute', 'marko-tag', 'markup', 'markupmode', 'mask', + 'mason', 'mat', 'mata', 'match', 'match-condition', 'match-definition', + 'match-option', 'match-pattern', 'material', 'math', 'math_complex', + 'math_real', 'mathematic', 'mathematical', 'mathematical-symbols', + 'mathematics', 'matlab', 'matrix', 'maude', 'max-cached-fonts', + 'max-execution-time', 'maxscript', 'maybe', 'mb', 'mbstring', 'mc', 'mcc', + 'mccolor', 'mch', 'mcn', 'mcode', 'mcr', 'mcrypt', 'mcs', 'md', 'media', + 'media-feature', 'mediawiki', 'mei', 'mel', 'memaddress', 'member', + 'member-function-attribute', 'membership', 'memcache', 'memcached', 'memoir', + 'memory-management', 'menhir', 'mention', 'mercury', 'merlin', 'message', + 'message-forwarding-handler', 'messages', 'meta', 'meta-data', 'meta-info', + 'metadata', 'metakey', 'metascript', 'meteor', 'method', 'method-call', + 'method-definition', 'method-mofification', 'method-parameter', + 'method-parameters', 'method-restriction', 'methods', 'mhash', 'microsites', + 'middle', 'migration', 'mime', 'min', 'minelua', 'minetweaker', + 'minitemplate', 'minus', 'mips', 'mirah', 'misc', 'miscellaneous', + 'mismatched', 'missing', 'missing-asterisk', 'missing-inheritance', + 'missing-parameters', 'missing-section-begin', 'missingend', 'mixin', 'mjml', + 'ml', 'mlab', 'mls', 'mm', 'mml', 'mmx', 'mmx_instructions', 'mnemonic', + 'mochi', 'mod', 'mod_perl', 'modblock', 'model', 'model-based-calibration', + 'model-predictive-control', 'modelica', 'modelicascript', 'modern', + 'modifier', 'modl', 'modr', 'mods', 'modula-2', 'module', 'module-alias', + 'module-binding', 'module-definition', 'module-expression', + 'module-reference', 'module-rename', 'module-sum', 'module-type', 'modules', + 'modulo', 'mojolicious', 'mojom', 'mond', 'money', 'mongo', 'mongodb', + 'monicelli', 'monitor', 'monkberry', 'monkey', 'monospace', 'monte', 'month', + 'moon', 'moos', 'moose', 'moosecpp', 'mouse', 'mov', 'mozu', 'mpw', 'mpx', + 'mscgen', 'mscript', 'msg', 'msgctxt', 'msgenny', 'msgstr', 'mson', + 'mson-block', 'mss', 'mta', 'mucow', 'multi', 'multi-line', 'multi-threading', + 'multids-file', 'multiline', 'multiplication', 'multiplicative', 'multiply', + 'multiverse', 'mumps', 'mundosk', 'mustache', 'mut', 'mutable', 'mutator', + 'mx', 'mxml', 'mydsl1', 'mysql', 'mysqli', 'mysqlnd-memcache', 'mysqlnd-ms', + 'mysqlnd-qc', 'mysqlnd-uh', 'mzn', 'nabla', 'nagios', 'name', 'name-list', + 'name-of-parameter', 'named', 'named-tuple', 'nameless-typed', 'namelist', + 'namespace', 'namespace-block', 'namespace-definition', 'namespace-reference', + 'namespaces', 'nan', 'nant', 'narration', 'nas', 'nastran', 'nat', 'native', + 'nativeint', 'natural', 'navigation', 'ncf', 'ncl', 'ndash', 'nearley', + 'negate', 'negated', 'negation', 'negative', 'negative-look-ahead', + 'negative-look-behind', 'neko', 'nesc', 'nested', 'nested_braces', + 'nested_ltgt', 'nesty', 'net', 'netbios', 'network', 'neural-network', 'new', + 'new-object', 'newline', 'newline-spacing', 'newlinetext', 'newlisp', 'nez', + 'nft', 'ngdoc', 'nginx', 'nickname', 'nil', 'nim', 'ninja', 'ninjaforce', + 'nit', 'nitro', 'nix', 'nl', 'nlf', 'nm', 'nm7', 'no-capture', + 'no-completions', 'no-content', 'no-default', 'no-indent', + 'no-trailing-digits', 'no-validate-params', 'nocapture', 'node', 'nogc', + 'noindent', 'non', 'non-capturing', 'non-immediate', 'non-intrinsic', + 'non-null-typehinted', 'non-overridable', 'non-standard', 'non-terminal', + 'non_recursive', 'none', 'none-parameter', 'nonlocal', 'nonterminal', 'noon', + 'nop', 'nopass', 'normal', 'normal_numeric', 'normal_objects', 'normal_text', + 'not', 'not-a-number', 'notation', 'note', 'notechord', 'notemode', + 'notequal', 'notequalexpr', 'notidentical', 'notification', 'nowdoc', 'noweb', + 'nrtdrv', 'nsapi', 'nscript', 'nse', 'nsis', 'nsl', 'ntriples', 'nul', 'null', + 'nullify', 'nullological', 'nulltype', 'num', 'number', 'number-sign', + 'numbered', 'numberic', 'numbersign', 'numeric', 'numeric_std', 'nunjucks', + 'nut', 'nvatom', 'nxc', 'objc', 'objcpp', 'objdump', 'object', + 'object-comments', 'object-definition', 'objj', 'obsolete', 'ocaml', + 'ocamllex', 'occam', 'occurrences', 'oci8', 'ocmal', 'oct', 'octal', 'octave', + 'octo', 'octobercms', 'octothorpe', 'odd-tab', 'odedsl', 'ods', 'offset', + 'ofx', 'ogre', 'ok', 'ol', 'old', 'old-style', 'omap', 'omitted', 'on', + 'on-background', 'on-error', 'one', 'oniguruma', 'only', 'only-in', 'onoff', + 'ooc', 'op-domain', 'opa', 'opaque', 'opc', 'opcache', 'opcode', 'open', + 'opencl', 'opendss', 'opening', 'openmp', 'openssl', 'opentype', 'operand', + 'operands', 'operation', 'operator', 'operator2', 'operators', 'opmaskregs', + 'optimization', 'optimize', 'option', 'option-toggle', 'optional', + 'optional-parameter', 'optional-parameter-assignment', 'optionals', + 'optionname', 'options', 'or', 'oracle', 'orcam', 'orchestra', 'order', + 'ordered', 'ordered-block', 'orgtype', 'origin', 'other', 'others', + 'otherwise', 'out', 'outer', 'output', 'overload', 'override', 'overtype', + 'owner', 'oz', 'p5', 'p8', 'pa', 'package', 'package-definition', + 'package_body', 'packed', 'packed-arithmetic', 'packed-blending', + 'packed-comparison', 'packed-floating-point', 'packed-integer', 'packed-math', + 'packed-mov', 'packed-other', 'packed-shift', 'packed-shuffle', 'packed-test', + 'page', 'page-props', 'pair', 'pandoc', 'papyrus', 'papyrus-assembly', + 'paragraph', 'param', 'param-list', 'paramater', 'parameter', + 'parameter-entity', 'parameterless', 'parameters', 'paramless', 'params', + 'paren', 'paren-group', 'parens', 'parent', 'parent-reference', 'parenthases', + 'parentheses', 'parenthesis', 'parenthesized', 'parenthetical_list', + 'parenthetical_pair', 'parfor-quantity', 'parse', 'parser-token', 'parser3', + 'particle', 'pascal', 'pass', 'pass-through', 'passthrough', 'password', + 'password-hash', 'patch', 'path', 'path-pattern', 'pattern', + 'pattern-definition', 'pause', 'payee', 'pbtxt', 'pcntl', 'pdd', 'pddl', + 'peg', 'pegcoffee', 'pegjs', 'pending', 'percentage', 'percussionnote', + 'period', 'perl', 'perl-section', 'perl6', 'perl6fe', 'perlfe', 'perlt6e', + 'perm', 'permutations', 'personalization', 'pf', 'pfm', 'pfx', 'pgn', 'pgsql', + 'phone-number', 'php', 'php_apache', 'php_dom', 'php_ftp', 'php_imap', + 'php_mssql', 'php_odbc', 'php_pcre', 'php_spl', 'php_zip', 'phpdoc', + 'phrasemodifiers', 'phraslur', 'physics', 'pi', 'pic', 'pick', 'pig', + 'pillar', 'pink', 'pipe', 'pipeline', 'piratesk', 'placeholder', + 'placeholder-selector', 'plain', 'plainsimple-heading', 'plainsimple-number', + 'plantuml', 'playerversion', 'plist', 'plsql', 'plugin', 'plus', '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', 'pointer', 'pointer-arith', 'policiesbii', 'polydelim', + 'polymorphic', 'polymorphic-variant', 'polysep', 'pony', 'port', 'positional', + 'positive', 'posix', 'posix-reserved', 'post', 'post-match', 'postblit', + 'postcss', 'postfix', 'postpone', 'postscript', 'potigol', 'potion', 'pound', + 'power', 'powershell', 'pp', 'ppd', 'praat', 'pragma', 'pragma-all-once', + 'pragma-mark', 'pragma-message', 'pragma-newline-spacing-value', + 'pragma-stg-value', 'pre', 'pre-defined', 'preamble', 'prec', 'precedence', + 'pred', 'predefined', 'predicate', 'prefetchwt', 'prefix', 'prefixed-uri', + 'prefixes', 'preinst', 'prelude', 'prepare', 'prepocessor', 'preposition', + 'prepositional', 'preprocessor', 'prerequisites', 'previous', 'primitive', + 'primitive-datatypes', 'primitive-field', 'print', 'priority', 'prism', + 'private', 'privileged', 'pro', 'probe', 'proc', 'procedure', + 'procedure_definition', 'procedure_prototype', 'process', + 'process-substitution', 'processes', 'processing', 'proctitle', 'profile', + 'program', 'program-block', 'progressbars', 'proguard', 'project', 'prolog', + 'prologue', 'promoted', 'prompt', 'prompt-prefix', 'prop', 'properties', + 'properties_literal', 'property', 'property-list', 'property-name', + 'property-value', 'propertyend', 'proposition', 'protected', 'protection', + 'proto', 'protobuf', 'protobufs', 'protocol', 'protocol-list', 'prototype', + 'proxy', 'psci', 'pseudo', 'pseudo-class', 'pseudo-element', 'pseudo-method', + 'pseudo-mnemonic', 'pseudo-variable', 'pshdl', 'pspell', 'psql', 'pt', + 'ptc-config', 'pthread', 'public', 'pug', 'punchcard', 'punctual', + 'punctuation', 'punctutation', 'puncuation', 'puppet', 'pure', 'purebasic', + 'purescript', 'pweave', 'py2pml', 'pymol', 'pyresttest', 'python', + 'python-function', 'q', 'q-bracket', 'q-paren', 'qa', 'qml', 'qoute', 'qq', + 'qq-bracket', 'qq-paren', 'qry', 'qtpro', 'quad', 'qual', 'qualifier', + 'quality', 'quant', 'quantifier', 'quantifiers', 'quartz', 'quasi', + 'quasiquote', 'quasiquotes', 'query', 'query-dsl', 'question', 'questionmark', + 'quicktime-file', 'quotation', 'quote', 'quoted', 'quoted-expression', + 'quoted-identifier', 'quoted-object', 'quotes', 'qx', 'r', 'rabl', 'racket', + 'radix', 'rails', 'rainmeter', 'raml', 'randomsk', 'range', 'rant', 'rapid', + 'rarity', 'ratio', 'raw', 'raw-regex', 'rd', 'rdfs-type', 'rdrand', 'rdseed', + 'read', 'readline', 'readwrite', 'real', 'realip', 'rebeca', 'rebol', 'rec', + 'receive-channel', 'recipient-subscriber-list', 'recode', 'record', + 'record-field', 'record-usage', 'recordfield', 'recursive', 'recutils', 'red', + 'redirect', 'redirection', 'ref', 'reference', 'referer', 'refinement', + 'reflection', 'reg', 'regex', 'regexname', 'regexp', 'regexp-option', + 'region', 'register', 'register-64', 'registers', 'reiny', 'reject', + 'relation', 'relational', 'relations', 'relationship-pattern', + 'relationship-pattern-end', 'relationship-pattern-start', + 'relationship-type-or', 'relationship-type-start', 'rem', 'reminder', + 'remoting', 'rename', 'renaming', 'render', 'reparator', 'repeat', 'replace', + 'replaceXXX', 'replacement', 'reply', 'repo', 'reporter', 'repository', + 'request', 'request-type', 'require', 'required', 'requirements', 'rescue', + 'reserved', 'reset', 'resource', 'resource-bundle-list', 'response', + 'response-type', 'rest', 'rester', 'restriced', 'restructuredtext', 'result', + 'result-separator', 'results', 'return', 'return-type', 'return-value', + 'returns', 'rev', 'reversed', 'review', 'rewrite', 'rf', 'rfc', + 'rgb-percentage', 'rgb-value', 'rhap', 'rhs', 'rhtml', 'right', 'riot', + 'rivescript', 'rl', 'rmarkdown', 'rnc', 'roboconf', 'robot', 'robotc', + 'robust-control', 'role', 'root', 'round', 'round-brackets', 'routeros', + 'routine', 'row', 'row2', 'rowspan', 'roxygen', 'rparent', 'rpc', 'rpm-spec', + 'rpmspec', 'rpt', 'rq', 'rrd', 'rsl', 'rsl-url', 'rspec', 'rtemplate', 'ru', + 'ruby', 'rubymotion', 'rule', 'ruleDefinition', 'rules', 'run', 'rune', + 'runtime', 'runtime-shared-library-path', 'rust', 'safe-call', + 'safe-navigation', 'safe-trap', 'safer', 'safety', 'sage', 'sampler', + 'sampler-comparison', 'samplerarg', 'sampling', 'sas', 'sass', + 'sass-script-maps', 'satcom', 'save', 'scad', 'scala', 'scaladoc', 'scalar', + 'scam', 'scenario', 'scenario_outline', 'scene', 'schem', 'scheme', + 'schememode', 'scilab', 'sck', 'scope', 'scope-name', 'scope-resolution', + 'scribble', 'script', 'script-metadata', 'script-tag', 'scriptblock', + 'scripting', 'scriptlet', 'scriptlocal', 'scriptname-declaration', + 'scrollbars', 'scss', 'sdbl', 'sdl', 'sdo', 'search', 'seawolf', 'second', + 'section', 'section-item', 'sectionname', 'sections', 'see', 'select', + 'select-block', 'selectionset', 'selector', 'self', 'self-binding', 'sem', + 'semantic', 'semi-colon', 'semicolon', 'semicoron', 'semireserved', + 'send-channel', 'senum', 'separator', 'sepatator', 'seperator', 'sequence', + 'sequences', 'serpent', 'server', 'service', 'service-rpc', 'services', + 'session', 'set', 'set_node', 'setname', 'setproperty', 'setter', 'setting', + 'settings', 'settype', 'setword', 'severity', 'sexpr', 'sfst', 'sgml', 'sha', + 'sha256', 'sha512', 'sha_functions', 'shade', 'shaderlab', 'shared', + 'shared-static', 'sharpequal', 'sharpge', 'sharpgt', 'sharple', 'sharplt', + 'shebang', 'shell', 'shell-session', 'shift', 'shift-and-rotate', + 'shift-right', 'shipflow', 'shmop', 'short', 'shortcut', 'shortcuts', + 'shorthandpropertyname', 'show', 'show-actionscript-warnings', + 'show-argument', 'show-binding-warnings', + 'show-shadowed-device-font-warnings', 'show-unused-type-selector-warnings', + 'shutdown', 'sigil', 'sign-line', 'signal', 'signal-processing', 'signature', + 'simd', 'simd-integer', 'simple', 'simple-element', 'simple_delimiter', + 'simplexml', 'simplez', 'since', 'singe', 'single', 'single-line', + 'single-quote', 'single-quoted', 'single_quote', 'singleton', 'six', 'size', + 'sized_integer', 'sizeof', 'sjs', 'sjson', 'skaction', 'skdragon', 'skeeland', + 'sketchplugin', 'skew', 'skip', 'skmorkaz', 'skquery', 'skrambled', + 'skrayfall', 'skript', 'skrpg', 'sksharp', 'skstuff', 'skutilities', + 'skvoice', 'sl', 'slash', 'slash-option', 'slashes', 'slashstar', 'sleet', + 'slice', 'slim', 'slm', 'sln', 'slot', 'slot_subnode', 'slugignore', 'sma', + 'smali', 'smalltalk', 'smarty', 'sml', 'smpte', 'smx', 'snlog', 'snmp', + 'soap', 'socketgroup', 'sockets', 'somearg', 'something', 'soql', 'sort', + 'souce', 'source', 'source-constant', 'source-path', 'soy', 'sp', 'space', + 'space-after-command', 'spacebars', 'spaces', 'sparql', 'spath', 'spec', + 'special', 'special-attributes', 'special-functions', 'special-method', + 'special-tokens', 'specification', 'sphinx', 'spice', 'spider', 'splat', + 'spline', 'splunk', 'splus', 'spn', 'spread', 'spreadmap', 'sproto', + 'sproutcore', 'sqf', 'sql', 'sqlite', 'sqlsrv', 'sqsp', 'square', 'squart', + 'squirrel', 'sr-Latn', 'src', 'srltext', 'ss', 'sse', 'sse2', 'sse2_simd', + 'sse3', 'sse4_simd', 'sse_simd', 'ssh-config', 'ssi', 'ssl', 'st', 'stable', + 'stack', 'stack-effect', 'stackframe', 'stan', 'standard', 'standard-links', + 'standard-suite', 'standardadditions', 'standoc', 'star', 'start', + 'start-block', 'start-condition', 'start-symbol', 'starting-functions-point', + 'startshape', 'statamic', 'state', 'state-management', 'stateend', + 'stategrouparg', 'stategroupval', 'statement', 'statement-separator', + 'states', 'statestart', 'static', 'static-assert', 'static-if', 'static-rsls', + 'staticimages', 'statistics', 'stats', 'std', 'std_logic', 'stdint', + 'stdlibcall', 'step', 'steps', 'stg', 'stk', 'stmt', 'stop', 'stopping', + 'storage', 'stp', 'stray-comment-end', 'stream', 'streamsfuncs', 'streem', + 'strict', 'strike', 'strikethrough', 'string', 'string-constant', + 'string-format', 'string-interpolation', 'string-long-single-quote', + 'strings', 'strong', 'struct', 'struct-union-block', 'structdef', 'structs', + 'structure', 'stuff', 'style', 'styleblock', 'styles', 'stylus', 'sub', + 'sub-pattern', 'subckt', 'subcmd', 'subexp', 'subexpression', 'subkey', + 'subkeys', 'subl', 'submodule', 'subroutine', 'subscript', 'subsection', + 'subsections', 'subshell', 'subsort', 'substitute', 'substitution', + 'subtitle', 'subtlegradient', 'subtraction', 'suffix', 'sugly', + 'sugly-control-keywords', 'sugly-delcare-operator', 'sugly-delcare-variable', + 'sugly-encode-clause', 'sugly-function-groups', 'sugly-function-recursion', + 'sugly-general-operators', 'sugly-generic-types', '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-print-clause', 'sugly-subject-or-predicate', 'sugly-type-function', + 'sugly-uri-clause', 'super', 'superclass', 'supercollider', 'superscript', + 'supertype', 'supervisor', 'supplemental', 'supplimental', 'support', + 'supports', 'suppressed', 'svg', 'svm', 'svn', 'swift', 'swig', 'switch', + 'switch-block', 'switch-expression', 'switch-statement', 'switchStart', + 'swizzle', 'sybase', 'symbol', 'symbol-type', 'symbolic', 'symbolic-math', + 'symbols', 'symmetry', 'synchronization', 'synchronize', 'synchronized', + 'synergy', 'syntax', 'sys-types', 'syslog-ng', 'system', 'system-events', + 'system-identification', 'systemreference', 'sytem-events', 't4', 't5', 't7', + 'ta', 'tab', 'table', 'table-name', 'tablename', 'tabs', 'tabular', 'tacacs', + 'taco', 'tads3', 'tag', 'tag-not-recognized', 'tag-string', 'tag-value', + 'tagbraces', 'tagdef', 'tagged', 'tagger_script', 'taglib', 'tagnamedjango', + 'tags', 'taint', 'target', 'task', 'tasks', 'tbdfile', 'tbody', 'tcl', + 'tcoffee', 'td', 'tdl', 'tea', 'telegram', 'tell', 'temp', 'template', + 'templatetag', 'temporal', 'term', 'terminal', 'termination', 'terminator', + 'terms_node', 'ternary', 'ternary-if', 'terra', 'terraform', 'testcase', + 'testing', 'tests', 'testsuite', 'tex', 'texshop', 'text', 'text-reference', + 'text-suite', 'textbf', 'textile', 'textio', 'textit', 'texttt', 'th', + 'thead', 'theme', 'then', 'therefore', 'thin', 'third', 'this', 'thrift', + 'throw', 'throwables', 'throws', 'tick', 'ticket-num', 'ticket-psa', + 'tid-file', 'tidal', 'tidalcycles', 'tiddler', 'tiddler-field', 'tidy', + 'tieslur', 'time', 'times', 'timespan', 'timestamp', 'timing', 'titanium', + 'title', 'title-text', 'tjs', 'tl', 'tla', 'tmpl', 'tmsim', 'tmux', 'to', + 'to-file', 'toc', 'toc-list', 'todo', 'todo_extra', 'todo_node', 'todotxt', + 'token', 'token-def', 'token-paste', 'token-type', 'tokenizer', 'toml', + 'toolbox', 'tools', 'tooltip', 'top', 'top-level', 'topic', 'tornado', + 'torque', 'torquescript', 'tosca', 'total-config', 'totaljs', 'tpye', 'tr', + 'trace', 'trace-argument', 'traceback', 'trader', 'trail', 'trailing', + 'trailing-match', 'trailing-whitespace', 'trait', 'traits', 'transaction', + 'transcendental', 'transcludeblock', 'transcludeinline', 'transclusion', + 'transitionable-property-value', 'transpose', 'transposed-matrix', + 'transposed-parens', 'transposed-variable', 'trap', 'treetop', 'trenni', + 'trigEvent_', 'trigLevel_', 'trigger', 'triple', 'triple-slash', 'true', + 'truncate', 'truncation', 'try', 'trycatch', 'ts', 'tsql', 'tss', 'tsv', + 'tsx', 'tt', 'ttpmacro', 'tts', 'tubaina2', 'tup', 'tuple', 'turtle', 'tutch', + 'tvml', 'tw5', 'twig', 'twigil', 'twiki', 'two', 'txt', 'txt2tags', 'type', + 'type-cast', 'type-constrained', 'type-constraint', 'type-declaration', + 'type-def', 'type-definition', 'type-definition-group', 'type-of', 'type-or', + 'type-parameters', 'type-signature', 'type_params', 'type_trait', + 'typeabbrev', 'typeclass', 'typedblock', 'typedcoffeescript', 'typedef', + 'typehint', 'typehinted', 'typeid', 'typename', 'types', 'typesbii', + 'typescriptish', 'typoscript', 'uc', 'ucicfg', 'ucicmd', 'udaf', 'udf', 'udl', + 'udtf', 'ui', 'ui-block', 'uintptr', 'ujm', 'uk', 'ul', 'unOp', 'unary', + 'unbuffered', 'uncleared', 'unclosed', 'unclosed-string', 'undef', + 'undefined', 'underline', 'underscore', 'undocumented', 'unescaped-quote', + 'unexpected', 'unexpected-extends', 'unexpected-extends-character', + 'unexpected-text', 'unformatted', 'unicode', 'unicode-16-bit', + 'unicode-escape', 'unicode-raw', 'unicode-raw-regex', 'unify', 'union', + 'unit', 'unit-test', 'unit_test', 'unittest', 'unity', 'universal-match', + 'unknown', 'unknown-escape', 'unknown-rune', 'unlabeled', 'unnumbered', 'uno', + 'unoconfig', 'unop', 'unoproj', 'unordered', 'unordered-block', 'unosln', + 'unpack', 'unpacking', 'unquoted', 'unrecognized', 'unrecognized-character', + 'unrecognized-character-escape', 'unrecognized-string-escape', 'unsafe', + 'unsupplied', 'until', 'untitled', 'untyped', 'uopz', 'update', 'upstream', + 'uri', 'url', 'usable', 'usage', 'use', 'use-as', 'usebean', 'usecase', + 'user', 'user-defined', 'user-defined-property', 'user-defined-type', + 'user-interaction', 'userid', 'username', 'using', + 'using-namespace-declaration', 'using_animtree', 'utility', 'uvu', 'ux', + 'uxc', 'uz', 'val', 'vala', 'valgrind', 'valid', 'valid-ampersand', 'valign', + 'value', 'value-pair', 'value-size', 'value-type', 'valuepair', 'vamos', + 'var', 'var-single-variable', 'var1', 'var2', 'variable', 'variable-access', + 'variable-declaration', 'variable-modifier', 'variable-parameter', + 'variable-reference', 'variable-usage', 'variables', 'variant-definition', + 'varname', 'varnish', 'vb', 'vbnet', 'vbs', 'vc', 'vcl', 'vector', + 'vector-load', 'vectors', 'velocity', 'vendor-prefix', 'verbatim', + 'verbose-stacktraces', 'verdict', 'verilog', 'version', 'vertical-tab', 'vex', + 'vhdl', 'via', 'view', 'viewhelpers', 'vim', 'viml', 'virtual', + 'virtual-reality', 'visibility', 'visualforce', 'vmap', 'voice', 'void', + 'volatile', 'volt', 'vpath', 'vplus', 'vue', 'w3c-extended-color-name', + 'w3c-standard-color-name', 'wait', 'waitress-rb', 'warn', + 'warn-array-tostring-changes', 'warn-assignment-within-conditional', + 'warn-bad-array-cast', 'warn-bad-bool-assignment', 'warn-bad-date-cast', + 'warn-bad-es3-type-method', 'warn-bad-es3-type-prop', + 'warn-bad-nan-comparison', 'warn-bad-null-assignment', + 'warn-bad-null-comparison', 'warn-bad-undefined-comparison', + 'warn-boolean-constructor-with-no-args', 'warn-changes-in-resolve', + 'warn-class-is-sealed', 'warn-const-not-initialized', + 'warn-constructor-returns-value', 'warn-deprecated-event-handler-error', + 'warn-deprecated-function-error', 'warn-deprecated-property-error', + 'warn-duplicate-argument-names', 'warn-duplicate-variable-def', + 'warn-for-var-in-changes', 'warn-import-hides-class', + 'warn-instance-of-changes', 'warn-internal-error', 'warn-level-not-supported', + 'warn-missing-namespace-decl', 'warn-negative-uint-literal', + 'warn-no-constructor', 'warn-no-explicit-super-call-in-constructor', + 'warn-no-type-decl', 'warn-number-from-string-changes', + 'warn-scoping-change-in-this', 'warn-slow-text-field-addition', + 'warn-unlikely-function-value', 'warn-xml-class-has-changed', 'warning', + 'warnings', 'wast', 'wavelet', 'wddx', 'weave', 'webidl', 'webspeed', + 'weekday', 'weirdland', 'wh', 'whatever', 'when', 'where', 'while', + 'while-condition', 'while-loop', 'whiskey', 'white', 'whitespace', 'widget', + 'width', 'wiki', 'wiki-link', 'wildcard', 'wildsk', 'win', 'window-classes', + 'windows', 'with', 'with-arg', 'with-args', 'with-arguments', 'with-params', + 'with-side-effects', 'without-arguments', 'without-attributes', 'wla-dx', + 'word', 'word-op', 'words', 'world', 'wow', 'wp', 'write', + 'wrong-access-type', 'wrong-division-assignment', 'ws', 'x10', 'x86', + 'x86_64', 'x86asm', 'xbase', 'xchg', 'xflags-mark', 'xhprof', 'xhtml', + 'xikij', 'xml', 'xmlrpc', 'xmlwriter', 'xop', 'xor', 'xq', 'xquery', 'xref', + 'xsave', 'xsd_nillable', 'xsd_optional', 'xsl', 'xsse3_simd', 'xst', 'xtend', + 'xtoy', 'xtpl', 'xu', 'xvc', 'xve', 'yaml', 'yaml-ext', 'yang', 'yara', + 'yard', 'yate', 'year', 'yellow', 'yield', 'yorick', 'you-forgot-semicolon', + 'z80', 'zap', 'zapper', 'zep', 'zepon', 'zepto', 'zero', 'zh-CN', 'zig', + 'zip', 'zlib', 'zoomfilter' +] From ad396937198f2e848c2e7d51b6b4dd5da2b291ae Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Oct 2016 17:35:34 +0200 Subject: [PATCH 06/88] :art: --- src/tokenized-buffer-iterator.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tokenized-buffer-iterator.js b/src/tokenized-buffer-iterator.js index 32f6ed76a..3e6822670 100644 --- a/src/tokenized-buffer-iterator.js +++ b/src/tokenized-buffer-iterator.js @@ -1,14 +1,14 @@ const {Point} = require('text-buffer') module.exports = class TokenizedBufferIterator { - constructor(tokenizedBuffer) { + constructor (tokenizedBuffer) { this.tokenizedBuffer = tokenizedBuffer this.openTags = null this.closeTags = null this.containingTags = null } - seek(position) { + seek (position) { this.openTags = [] this.closeTags = [] this.tagIndex = null @@ -17,7 +17,7 @@ module.exports = class TokenizedBufferIterator { this.currentTags = currentLine.tags this.currentLineOpenTags = currentLine.openScopes this.currentLineLength = currentLine.text.length - this.containingTags = this.currentLineOpenTags.map(id => this.scopeForId(id)) + this.containingTags = this.currentLineOpenTags.map((id) => this.scopeForId(id)) let currentColumn = 0 for (let [index, tag] of this.currentTags.entries()) { @@ -68,8 +68,8 @@ module.exports = class TokenizedBufferIterator { return this.containingTags.slice() } - moveToSuccessor() { - for (let tag of this.closeTags) { + moveToSuccessor () { + for (let tag of this.closeTags) { // eslint-disable-line no-unused-vars this.containingTags.pop() } for (let tag of this.openTags) { @@ -83,7 +83,7 @@ module.exports = class TokenizedBufferIterator { break } else if (this.shouldMoveToNextLine) { this.moveToNextLine() - this.openTags = this.currentLineOpenTags.map(id => this.scopeForId(id)) + this.openTags = this.currentLineOpenTags.map((id) => this.scopeForId(id)) this.shouldMoveToNextLine = false } else if (this.nextLineHasMismatchedContainingTags()) { this.closeTags = this.containingTags.slice().reverse() @@ -121,19 +121,19 @@ module.exports = class TokenizedBufferIterator { return true } - getPosition() { + getPosition () { return this.position } - getCloseTags() { + getCloseTags () { return this.closeTags.slice() } - getOpenTags() { + getOpenTags () { return this.openTags.slice() } - nextLineHasMismatchedContainingTags() { + nextLineHasMismatchedContainingTags () { const line = this.tokenizedBuffer.tokenizedLineForRow(this.position.row + 1) if (line == null) { return false @@ -145,7 +145,7 @@ module.exports = class TokenizedBufferIterator { } } - moveToNextLine() { + moveToNextLine () { this.position = Point(this.position.row + 1, 0) const tokenizedLine = this.tokenizedBuffer.tokenizedLineForRow(this.position.row) if (tokenizedLine == null) { @@ -159,7 +159,7 @@ module.exports = class TokenizedBufferIterator { } } - isAtTagBoundary() { + isAtTagBoundary () { return this.closeTags.length > 0 || this.openTags.length > 0 } From 712e91b03ccea59f53049007f5fc58f7893f47fe Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Oct 2016 17:35:44 +0200 Subject: [PATCH 07/88] Convert StyleManager to JavaScript --- src/style-manager.coffee | 177 --------------------------------- src/style-manager.js | 207 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 177 deletions(-) delete mode 100644 src/style-manager.coffee create mode 100644 src/style-manager.js diff --git a/src/style-manager.coffee b/src/style-manager.coffee deleted file mode 100644 index 8f932d229..000000000 --- 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 000000000..ae06f9060 --- /dev/null +++ b/src/style-manager.js @@ -0,0 +1,207 @@ +const fs = require('fs-plus') +const path = require('path') +const {Emitter, Disposable} = require('event-kit') +const 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}) { + this.configDirPath = configDirPath + this.emitter = new Emitter() + this.styleElements = [] + this.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) { + 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) + } + + /* + 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) + } + } + + styleElement.textContent = source + 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) + } + } + + 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') + } + } + } +} From 7174b54139b50e73d2bfb358fa95b063b9f009da Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 3 Oct 2016 18:11:03 +0200 Subject: [PATCH 08/88] Convert style-manager-spec to JavaScript --- spec/style-manager-spec.coffee | 68 ---------------------------------- spec/style-manager-spec.js | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 68 deletions(-) delete mode 100644 spec/style-manager-spec.coffee create mode 100644 spec/style-manager-spec.js diff --git a/spec/style-manager-spec.coffee b/spec/style-manager-spec.coffee deleted file mode 100644 index d0a1cfe13..000000000 --- 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 000000000..19451ffea --- /dev/null +++ b/spec/style-manager-spec.js @@ -0,0 +1,66 @@ +const StyleManager = require('../src/style-manager') + +describe('StyleManager', () => { + let [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', () => { + const disposable = manager.addStyleSheet('a {color: red}') + expect(addEvents.length).toBe(1) + expect(addEvents[0].textContent).toBe('a {color: red}') + const 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', () => { + const disposable1 = manager.addStyleSheet('a {color: red}', {sourcePath: '/foo/bar'}) + expect(addEvents.length).toBe(1) + expect(addEvents[0].getAttribute('source-path')).toBe('/foo/bar') + + const 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}' + ]) + }) + }) + }) +}) From 92a3c2f4b2595aca434965c42038271a5f3d35e8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 4 Oct 2016 10:14:11 +0200 Subject: [PATCH 09/88] Use a more accurate list for deprecated-syntax-selectors.js --- src/deprecated-syntax-selectors.js | 1576 +++++++++++++++++----------- 1 file changed, 947 insertions(+), 629 deletions(-) diff --git a/src/deprecated-syntax-selectors.js b/src/deprecated-syntax-selectors.js index 6d3184dc0..8b85956ca 100644 --- a/src/deprecated-syntax-selectors.js +++ b/src/deprecated-syntax-selectors.js @@ -1,646 +1,964 @@ -module.exports = [ - 'AFDKO', 'AVX', 'AVX2', 'AVX512', 'AVX512BW', 'AVX512DQ', 'Alpha', - 'Animation', 'ArchaeologyDigSiteFrame', 'Arrow__', 'AtLilyPond', - 'AttrBaseType', 'AttrSetVal__', 'Button', 'C99', 'CharacterSet', 'Chatscript', - 'CheckButton', 'ClipboardType', 'Clipboard__', 'Codepages__', 'ColourActual', - 'ColourReal', 'ColourSize', 'ConfCachePolicy', 'ControlPoint', 'DBE', 'DDL', - 'DML', 'Database__', 'DdcMode', 'DiscussionFilterType', 'DiscussionStatus', - 'DisplaySchemes', 'Document-Structuring-Comment', 'Edit', - 'ExternalLinkBehaviour', 'ExternalLinkDirection', 'F16c', 'FMA', 'Font', - 'FontInstance', 'FontString', 'Fonts__', 'Frame', 'GameTooltip', 'GroupList', - 'HLE', 'HeaderEvent', 'HistoryType', 'HttpVerb', 'II', 'IO', 'Icon', 'IconID', - 'InPlaceBox__', 'InPlaceEditEvent', 'Info', 'JSXEndTagStart', - 'JSXStartTagEnd', 'KNC', 'Kotlin', 'LUW', 'Language', 'LdapItemList', - 'LinkFilter', 'LinkLimit', 'Locales__', 'Lock', 'LoginPolicy', 'MA_End__', - 'MA_StdCombo__', 'MA_StdItem__', 'MA_StdMenu__', 'MISSING', 'MessageFrame', - 'Minimap', 'Path', 'PlayerModel', 'Proof', 'RTM', 'RecentModule__', 'Regexp', - 'SCADABasic', 'Script__', 'ScrollEvent', 'ScrollSide', - 'ScrollingMessageFrame', 'Sensitivity', 'Slider', 'Stream', 'Style', - 'TM_COMMENT_END_2', 'TM_COMMENT_START', 'TODO', 'ToolType', 'Translation', - 'TriggerStatus', 'UIObject', 'UserClass', 'UserList', 'UserNotifyList', - 'VisibleRegion', 'ZipType', 'a', 'a10networks', 'aaa', 'abaqus', 'abbrev', - 'abbreviated', 'abcnotation', 'abl', 'abp', 'abstract', 'academic', 'access', - 'access-control', 'access-qualifiers', 'accessed', 'accessibility', - 'accessible', 'accessor', 'account', 'ace', 'acl', 'act', 'action', - 'actionpack', 'actions', 'actionscript', 'actionscript-file-encoding', - 'activerecord', 'activesupport', 'ada', 'add', 'addition', - 'additional-character', 'addon', 'address', 'address-of', 'addrfam', - 'adjustment', 'adverb', 'adx', 'aerospace', 'aes', 'aes_functions', 'aesni', - 'aexLightGreen', 'af', 'after-expression', 'agda', 'aggregate', 'aggregation', - 'ahk', 'alabel', 'alda', 'alert', 'alias', 'aliases', 'align', 'alignment', - 'all', 'all-once', 'all-solutions', 'allocatable', 'allocate', 'alloy', - 'alloyglobals', 'alloyxml', 'alog', 'alpha', 'alphabeticalllt', - 'alphabeticallyge', 'alphabeticallygt', 'alphabeticallyle', 'alt', 'alter', - 'alternate-wysiwyg-string', 'alternates', 'alternatives', 'amd', 'amd3DNow', - 'amdnops', 'ameter', 'amount', 'ampl', 'ampscript', 'analytics', 'anb', - 'anchor', 'and', 'angelscript', 'angle', 'angle-brackets', 'angular', - 'annotate', 'annotation', 'annotation-arguments', 'anon', 'anonymous', 'ansi', - 'ansi-c', 'ansible', 'answer', 'antl', 'antlr', 'antlr4', 'anubis', 'any', - 'any-method', 'aolserver', 'apache', 'apache-config', 'apc', 'apdl', 'apex', - 'api', 'api-notation', 'apiary', 'apib', 'apl', 'apostrophe', 'applescript', - 'application', 'application-name', 'application-process', 'approx-equal', - 'aqua', 'ar', 'arbitrary-radix', 'arbitrary-repetition', - 'arbitrary-repitition', 'architecture', 'arduino', 'arendelle', 'args', - 'argument', 'argument-label', 'argument-separator', 'argument-seperator', - 'arguments', 'arith', 'arithmetic', 'arithmetical', 'arithmeticcql', 'ark', - 'arm', 'armaConfig', 'arnoldc', 'arp', 'arpop', 'arr', 'array', - 'array-expression', 'array-item', 'arrays', 'arrow', 'articulation', - 'artihmetic', 'as', 'as3', 'asciidoc', 'asdoc', 'ash', 'ashx', 'asl', 'asm', - 'asm-instruction', 'asm-type-prefix', 'asn', 'asp', 'asp-core-2', 'aspx', - 'ass', 'assembly', 'assert', 'assertion', 'assigment', 'assign', 'assigned', - 'assigned-class', 'assigned-value', 'assignee', 'assignment', 'associate', - 'association', 'associativity', 'assocs', 'asterisk', 'async', 'asynchronous', - 'at-rule', 'at-sign', 'atml3', 'atoemp', 'atom', 'atomic', 'att', - 'attachment', 'attr', 'attribute', 'attribute-key-value', 'attribute-list', - 'attribute-lookup', 'attribute-name', 'attribute-selector', - 'attribute-specification', 'attribute-value', 'attribute-values', - 'attribute-with-value', 'attribute_list', 'attribute_value2', 'attributelist', - 'attributes', 'attrset', 'audio-file', 'auditor', 'augmented', 'auth', - 'auth_basic', 'author', 'authorization', 'auto', 'autoconf', 'autoindex', - 'autoit', 'automake', 'automatic', 'autotools', 'avdl', 'avrasm', 'avrdisasm', - 'avs', 'avx', 'avx2', 'awk', 'axis', 'b', 'babel', 'back', 'back-from', - 'back-reference', 'back-slash', 'backend', 'background', 'backlash', - 'backreference', 'backslash', 'backspace', 'backtick', 'bad-ampersand', - 'bad-angle-bracket', 'bad-comments-or-CDATA', 'bad-escape', 'bang', 'banner', - 'bar', 'bareword', 'barline', 'base-11', 'base-13', 'base-15', 'base-17', - 'base-19', 'base-21', 'base-23', 'base-25', 'base-27', 'base-29', 'base-3', - 'base-31', 'base-33', 'base-35', 'base-5', 'base-7', 'base-9', 'base-integer', +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_functions', 'bat', 'batch', 'batchfile', - 'battlesim', 'bb', 'bbcode', 'bcmath', 'be', 'beam', 'beamer', 'beancount', - 'begin', 'begin-document', 'begin-end-group', 'behaviour', 'bell', 'bem', - 'between-tag-pair', 'bff', 'bg-black', 'bg-blue', 'bg-cyan', 'bg-green', - 'bg-normal', 'bg-purple', 'bg-red', 'bg-white', 'bg-yellow', 'bhtml', 'bhv', - 'bibtex', 'bif', 'big-arrow', 'bigdecimal', 'bigint', 'biicode', 'biiconf', - 'bin', 'binOp', 'binary', 'binding', 'bindings', 'bioinformatics', - 'biosphere', 'bison', 'bit', 'bit-and-byte', 'bit-wise', 'bitarray', - 'bits-mov', 'bitwise', 'black', 'blade', 'blaze', 'blenc', 'blend', - 'blending', 'block', 'block-dartdoc', 'block-data', 'block-directive', - 'block-level', 'block-params', 'blockid', 'blockname', 'blockquote', - 'blocktitle', 'blue', 'blueprint', 'bm', 'bmi1', 'bmi2', 'bnd', 'bnf', 'body', - 'bold', 'bolt', 'boo', 'boogie', 'bool', 'boolean', 'boolean-test', 'boot', - 'border', 'bottom', 'bounded', 'bounds', 'bow', 'box', 'bpl', 'brace', - 'braced', 'braces', 'bracket', 'bracketed', 'brackets', 'brainfuck', 'branch', - 'break', 'breakpoint', 'bridle', 'brightscript', 'bro', 'broken', 'browser', - 'bsl', 'buffered', 'buffers', 'bugzilla-number', 'build', 'buildin', - 'built-in', 'built-ins', 'builtin', 'bulk', 'bullet', 'bullet-point', - 'bundle', 'but', 'buttons', 'by-name', 'by-number', 'byref', 'byte', 'bz2', - 'c', 'c-style', 'c0', 'c1', 'c2hs', 'c99', 'ca', 'cabal-keyword', 'cache', - 'cache-management', 'cacheability-control', 'cake', 'calc', 'calca', - 'calendar', 'call', 'callmethod', 'callout', 'camlp4', 'camlp4-stream', - 'capability', 'capnp', 'cappuccino', 'caps', 'caption', 'capture', 'cascade', - 'case', 'case-block', 'case-body', 'case-statement', 'case-terminator', - 'case_control', 'cassius', 'cast', 'catch', 'catch-exception', 'catcode', - 'categort', 'category', 'cbot', 'cc65', 'cdata', 'cdef', 'cell', 'cellwall', - 'ceq', 'ces', 'cexpr', 'cf', 'cfdg', 'cfengine', 'cfg', 'cfml', 'cfunction', - 'cg', 'cgx', 'chain', 'chainname', 'changed', 'changelogs', 'changes', - 'channel', 'chapel', 'chapter', 'char', 'character', 'character-class', - 'character-data-not-allowed-here', 'character-literal-too-long', - 'character-not-allowed-here', 'character_not_allowed_here', 'characters', - 'chars', 'charset', 'check', 'children', 'chord', 'chorus', 'chuck', 'chunk', - 'cirru', 'cisco', 'citation', 'cite', 'citrine', 'cjam', 'cjson', 'class', - 'class-constraint', 'class-constraints', 'class-declaration', 'class-fns', - 'class-instance', 'class-struct-block', 'class-type', 'class-type-definition', - 'classes', 'classicalb', 'classname', 'classobj', 'clause', - 'clause-head-body', 'clauses', 'clear', 'cleared', 'clflushopt', 'click', - 'client', 'clip', 'clipboard', 'clips', 'clmul', 'clock', 'clojure', 'close', - 'closed', 'closing', 'closure', 'cm', 'cmake', 'cmb', 'cmd', 'cobject', - 'cocoa', 'cocor', 'cod4mp', 'code', 'code-example', 'codeblock', 'codetag', - 'codimension', 'codstr', 'coffee', 'coffeescript', 'coil', 'collection', - 'colon', 'colons', 'color', 'colour', 'colspan', 'column', 'column-specials', - 'com', 'combinators', 'comboboxes', 'comma', 'comma-parenthesis', 'command', - 'commandline', 'comment', 'comment-italic', 'commentblock', 'commented-out', - 'commentinline', 'commit-command', 'common', 'common-lisp', 'commonform', - 'communications', 'community', 'compare', 'compareOp', 'comparison', - 'compatibility-version', 'compile', 'compile-only', 'compiled', - 'compiled-papyrus', 'compiler', 'complement', 'complete_tag', 'complex', - 'component', 'component_instantiation', 'compositor', 'compound', - 'compound-assignment', 'compress', 'computercraft', 'concatenator', + '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', - 'conditionals', 'conditions', 'conf', 'config', 'configuration', 'confluence', - 'conftype', 'conjunction', 'conky', 'connstate', 'cons', - 'consider_option_subnode', 'consider_options_subnode', 'considering', - 'console', 'const', 'const-data', 'constant', 'constants', 'constrained', - 'constraint', 'constraints', 'construct', 'constructor', 'constructs', - 'consult', 'container', 'contains', 'content', 'content-detective', - 'contentSupplying', 'context', 'context-free', 'context-signature', - 'contigous', 'continuation', 'continuations', 'continue', 'continuum', - 'contol', 'contract', 'contracts', 'control', 'control-management', - 'control-systems', 'control-transfer', 'controller', 'conventional', - 'conversion', 'convert-type', 'cookie', 'cool', 'coord1', 'coord2', - 'coordinates', 'coq', 'core', 'core-parse', 'core-rule', 'cos', 'counter', - 'counters', 'cover', 'cplkg', 'cplusplus', 'cpp', 'cpp-type', 'cpp_type', - 'cpu12', 'cql', 'cram', 'crc32', 'create', 'creation', 'critic', 'crl', - 'crontab', 'crypto', 'crystal', 'cs', 'csharp', 'cshtml', 'csi', 'csound', - 'csound-document', 'csound-score', 'cspm', 'css', 'csv', 'csx', 'ct', 'ctkey', - 'ctxvar', 'ctxvarbracket', 'ctype', 'cubic-bezier', 'cucumber', 'cuda', - 'cuesheet', 'cup', 'cupsym', 'curl', 'curley', 'curly', 'currency', - 'curve-fitting', 'custom', 'custom-media', 'cut', 'cve-number', 'cvs', - 'cy-GB', 'cyan', 'cycle', 'cypher', 'cyrix', 'cython', 'd', 'daml', 'dana', - 'danger', 'dark_aqua', 'dark_blue', 'dark_gray', 'dark_green', 'dark_purple', - 'dark_red', 'dart', 'dartdoc', 'dash', 'dasm', 'data', 'data-acquisition', - 'data-index', 'data-integrity', 'data-step', 'data-transfer', 'database', - 'database-name', 'datablock', 'datafeed', 'datatype', 'datatypes', 'date', - 'date-time', 'datetime', 'day', 'db', 'dba', 'dbx', 'dc', 'dcon', 'dd', 'de', - 'dealii', 'deallocate', 'deb-control', 'debug', 'debug-password', 'debugger', - 'dec', 'decimal', 'decision', 'decl', 'declaration', 'declaration-prod', - 'declarator', 'declare', 'decoration', 'decorator', 'decrement', 'def', - 'default', 'default-frame-rate', 'default-script-limits', 'default-size', - 'defaults-css-files', 'defaults-css-url', 'deferred', 'define', 'definedness', - 'definition', 'definitions', 'defintions', 'deflate', 'delete', 'deleted', - 'deletion', 'delimited', 'delimiter', 'delimiters', 'dense', 'deprecated', - 'dereference', 'derived', 'derived-type', 'desc', 'describe', 'description', - 'design', 'desktop', 'destination', 'destructor', 'destructured', 'developer', - 'device', 'diagnostic', 'dialogue', 'dict', 'dictionary', 'dictionaryname', - 'diff', 'difference', 'different', 'dimension', 'dip', 'dir', 'dircolors', - 'direct', 'direction', 'directive', 'directives', 'directory', 'dirtyblue', - 'dirtygreen', 'disable', 'disable-markdown', 'discarded', 'disjunction', - 'disk', 'disk-folder-file', 'divider', 'django', 'dl', 'dlv', 'dm', 'dml', - 'dnl', 'do', 'dobody', 'doc', 'doc-comment', 'docRoot', 'dockerfile', - 'doconce', 'docstring', 'doctype', 'document', 'documentation', - 'documentroot', 'doki', 'dollar', 'dollar_variable', 'dom', 'domain', - 'dontcollect', 'doors', 'dot', 'dot-access', 'dotenv', 'dots', 'dotted', - 'double', 'double-arrow', 'double-colon', 'double-dash', 'double-dot', - 'double-number-sign', 'double-percentage', 'double-quote', 'double-quoted', - 'double-slash', 'doublequote', 'doubleslash', 'dougle', 'doxyfile', 'doxygen', - 'drive', 'droiuby', 'drop', 'drop-shadow', 'droplevel', 'drummode', 'drupal', - 'dsl', 'dt', 'dtl', 'dummy', 'dummy-variable', 'dump', 'duration', 'dust', - 'dust_Conditional', 'dust_filter', 'dust_partial_not_self_closing', - 'dust_section_context', 'dust_self_closing_section_tag', - 'dust_start_section_tag', 'dustjs', 'dwscript', 'dxl', 'dylan', 'dyndoc', - 'dyon', 'e', 'each', 'eachin', 'earl-grey', 'ebuild', 'echo', 'ecmascript', - 'eco', 'ecr', 'ect', 'ect2', 'ect3', 'ect4', 'edasm', 'edge', 'ee', - 'eel-expression', 'eex', 'effect', 'effectgroup', 'eiffel', 'eight', 'eio', - 'el_expression', 'elasticsearch', 'elasticsearch2', 'element', 'elemental', - 'elements', 'elemnt', 'elif', 'elision', 'elixir', 'ellipsis', 'elm', 'elmx', - 'else', 'elseif', 'elsewhere', 'eltype', 'elvis', 'em', 'email', 'embed', - 'embedded', 'embedded-c', 'embedded-ruby', 'embedded2', 'embeded', 'ember', - 'emberscript', 'emblem', 'emoji', 'emojicode', 'emph', 'emphasis', 'empty', - 'empty-dictionary', 'empty-list', 'empty-string', 'empty-tag', 'empty-tuple', - 'empty-typing-pair', 'empty_gif', 'emptyelement', 'en', 'en-au', 'en-old', - 'enable', 'enc', 'enchant', 'end', 'end-definition', 'end-document', - 'end-of-line', 'end-statement', 'endassociate', 'enddo', 'endfile', - 'endforall', 'endfunction', 'endif', 'ending', 'endinterface', 'endmodule', - 'endprogram', 'endselect', 'endsubmodule', 'endsubroutine', 'endtype', - 'endwhere', 'engine', 'entity', 'entity_instantiation', 'entitytype', 'entry', - 'entry-definition', 'entry-key', 'entry-type', 'entrypoint', 'enum', + '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', 'eof', 'epatch', 'eq', 'equal', 'equalexpr', 'equals', - 'equals-sign', 'equation', 'erb', 'ereg', 'erlang', 'error', 'error-control', - 'errorfunc', 'errors', 'errorstop', 'es', 'es6', 'es6import', 'escape', - 'escape-code', 'escape-sequence', 'escaped', 'escaped-content', 'escapes', - 'escript', 'eso-lua', 'eso-txt', 'essence', 'eth', 'ethaddr', 'etpl', - 'european', 'evaled', 'evaluation', 'even-tab', 'event', 'event-call', - 'event-handler', 'eventType', 'eventb', 'eventend', 'events', 'exactly', - 'example', 'exampleText', 'examples', 'excel-link', 'exception', 'exceptions', - 'exclamation', 'exec', 'execution-context', 'exif', 'existential', 'exit', - 'exp', 'expected-array-separator', 'expected-dictionary-separator', - 'expected-extends', 'expected-implements', 'expected-range-separator', - 'expires', 'explicit', 'exponential', 'export', 'expr', 'expression', - 'expression-seperator', 'expressions', 'expressions-and-types', 'ext', - 'extempore', 'extend', 'extended', 'extends', 'extension', - 'extension-specification', 'extensions', 'extern', 'external', 'externs', - 'extersk', 'extglob', 'extra', 'extra-characters', 'extra-equals-sign', - 'extracted', 'extrassk', 'f5networks', 'fa', 'fact', 'factor', 'fail', - 'fakeroot', 'fallback', 'false', 'fandoc', 'fann', 'fantom', 'fastcgi', - 'fbaccidental', 'fbgroupclose', 'fbgroupopen', 'fbp', 'feature', 'features', - 'fenced', 'fhem', 'field', 'field-name', 'field-tag', 'fields', 'figuregroup', - 'filder-design-hdl-coder', 'file', 'file-name', 'file-path', 'fileinfo', - 'filename', 'filter', 'filter-pipe', 'filteredtranscludeblock', 'filters', - 'final', 'final-procedure', 'financial', 'financial-derivatives', 'find-m', - 'finder', 'finish', 'finn', 'firebug', 'first', 'first-class', 'first-line', - 'fish', 'fix_this_later', 'fixed', 'fixed-income', 'fixed-point', 'fixme', - 'fl', 'flag', 'flags', 'flash', 'flash-type', 'flash9', 'flatbuffers', - 'flex-config', 'float', 'float-exponent', 'floating-point', 'floating_point', - 'flow', 'flowtype', 'flush', 'fma', 'fn', 'folder-actions', 'following', - 'font', 'font-name', 'font-size', 'fontface', 'fonts', 'footer', 'footnote', - 'for', 'for-loop', 'for-quantity', 'forall', 'foreach', 'foreign', - 'forge-config', 'forin', 'form', 'format', 'format-verb', 'formatted', - 'formfeed', 'forth', 'fortran', 'forward', 'foundation', 'fountain', 'four', - 'fourd-command', 'fourd-comment', 'fourd-constant', 'fourd-constant-hex', + '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', 'folded', '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-dollar', 'fourd-local-variable', 'fourd-parameter', 'fourd-script', - 'fourd-table', 'fourd-tag', 'fourd-variable', 'fpm', 'fpu', 'fpu_x87', 'fr', - 'fragment', 'frame', 'frames', 'frametitle', 'framexml', 'freebasic', - 'freefem', 'from', 'from-file', 'front-matter', 'fsgsbase', 'fsharp', 'fsl', - 'fsm', 'ftl', 'fuck', 'full-line', 'fun', 'func', 'func-tag', 'funchand', - 'function', 'function-call', 'function-definition', 'function-parameter', - 'function-parameters', 'function-recursive', 'function-return', - 'function-type', 'function-with-body', 'functionDeclarationArgument', - 'function_definition', 'function_prototype', 'functioncall', 'functionend', - 'functions', 'fundimental', 'funk', 'funtion-definition', 'fus', - 'fuzzy-logic', 'fx', 'g', 'galaxy', 'gallery', 'gamebusk', 'gamescript', - 'gams', 'gams-lst', 'gap', 'garch', 'gather', 'gcode', 'gdb', 'gdscript', - 'gdx', 'ge', 'geck-keyword', 'general', 'general-purpose', 'generate', - 'generator', 'generic', 'generic-spec', 'generic-type', 'generic_list', - 'genericcall', 'genetic-algorithms', 'geo', 'geom', 'geometric', - 'geometry-adjustment', 'get', 'getproperty', 'getset', 'getter', 'gettext', - 'getword', 'gfm', 'gfm-todotxt', 'gfx', 'gh-number', 'gherkin', 'gisdk', - 'git', 'git-attributes', 'git-commit', 'git-config', 'git-rebase', - 'gitignore', 'given', 'gj', 'global', 'globalsection', 'glsl', - 'glyph_class_name', 'glyphname-value', 'gml', 'gmp', 'gms', 'gmsh', 'gmx', - 'gn', 'gnuplot', 'go', 'goatee', 'godmode', 'gohtml', 'gold', 'golo', - 'google', 'gotemplate', 'goto', 'goto-label', 'gradle', 'grails', 'grammar', - 'grammar-action', 'grammar-rule', 'grammar_production', 'grapahql', 'graph', - 'graphics', 'graphql', 'gray', 'greater', 'greek', 'green', 'gridlists', - 'grog', 'groovy', 'groovy-properties', 'group', 'group-title', 'growl', 'gs', - 'gsc', 'gsp', 'gt', 'guard', 'gui', 'guid', 'guillemot', 'gzip', - 'gzip_static', 'h', 'h1', 'hack', 'haddock', 'hairpin', 'ham', 'haml', - 'hamlbars', 'hamlet', 'hamlpy', 'handlebars', 'handler', 'haproxy-config', - 'harbou', 'harbour', 'hardlinebreaks', 'hash', 'hash-tick', 'hashicorp', - 'hashkey', 'haskell', 'haxe', 'haxedoc', 'hbs', 'hcl', 'hdl', 'hdr', 'he', - 'head', 'header', 'header-value', 'headers', 'heading', 'heading-1', - 'heading-2', 'heading-3', 'heading-4', 'heading-5', 'heading-6', 'height', - 'helen', 'helpers', 'heredoc', 'heredoc-token', 'herestring', 'heritage', - 'hex', 'hex-byte', 'hex-literal', 'hex-string', 'hex-value', 'hex8', - 'hexadecimal', 'hexidecimal', 'hexprefix', 'hidden', 'hide', 'highlight', - 'hive', 'hjson', 'hl7', 'hlsl', 'hn', 'hoa', 'hoc', 'hocomment', 'hocon', - 'hocontinuation', 'hocontrol', 'hombrew-formula', 'homematic', 'hook', - 'hostname', 'hosts', 'hour', 'hours', 'hps', 'hql', 'hr', 'hs', 'hsc2hs', - 'htaccess', 'html', 'html_entity', 'htmlbars', 'http', 'hu', 'hxinst', 'hy', - 'hyperlink', 'hyphen', 'hyphenation', 'i18n', 'iRev', 'ice', 'icinga2', - 'icmpv6type', 'iconv', 'id', 'identical', 'identifier', - 'identifiers-and-DTDs', 'idl', 'idris', 'ieee', 'if', 'if-block', 'if-branch', - 'if-condition', 'ifdef', 'ifndef', 'ignore', 'ignore-eol', 'ignorebii', - 'ignored', 'iisfunc', 'ilasm', 'illeagal', 'illegal', 'image', - 'image-acquisition', 'image-processing', 'imaginary', 'imba', 'immediate', - 'immutable', 'impex', 'implementation', 'implemented', 'implements', - 'implicit', 'import', 'import-all', 'importall', 'important', 'impure', 'in', - 'in-block', 'inappropriate', 'include', 'include-libraries', - 'include-resource-bundles', 'incode', 'incomplete', - 'incomplete-variable-assignment', 'inconsistent', 'increment', - 'increment-decrement', 'indepimage', 'index', 'index-seperator', 'indexed', - 'indexer', 'indexes', 'indices', 'inet', 'inetprototype', 'inferred', 'infes', - 'infinity', 'infix', 'info', 'inform', 'inform6', 'inform7', 'inherit', - 'inheritDoc', 'inheritance', 'inherited', 'inherited-class', 'ini', 'init', - 'initialization', 'initializer-list', 'inline', 'inline-data', - 'inline-expression', 'inlineblock', 'inner', 'inner-class', 'inno', 'inout', - 'input', 'inquire', 'inserted', 'insertion-and-extraction', 'inside', - 'install', 'instance', 'instantiation', 'instruction', 'instructions', - 'instrument', 'instrument-control', 'int', 'int64', 'integer', - 'integer-float', 'intel', 'intel-hex', 'intent', 'intepreted', 'interbase', - 'interface', 'interface-or-protocol', 'interfaces', 'internal', - 'internalsubset', 'internet', 'interpolated', 'interpolation', 'interrupt', - 'intersection', 'intl', 'intrinsic', 'intuicio4', 'invalid', - 'invalid-inequality', 'invalid-quote', 'invalid-variable-name', 'invocation', - 'invoke', 'io', 'ip', 'ip-port', 'ipkg', 'ipv4', 'ipv6', 'ipynb', 'irct', - 'is', 'isa', 'isc', 'iscexport', 'isclass', 'isml', 'issue', 'italic', 'item', + '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', 'java', 'java-properties', 'java-props', 'javadoc', - 'javascript', 'jbeam', 'jekyll', 'jflex', 'jibo-rule', 'jinja', 'jison', - 'jisonlex', 'jmp', 'joker', 'jolie', 'jpl', 'jq', 'jquery', 'js', 'jsdoc', - 'jsduck', 'jsim', 'json', 'json5', 'jsoniq', 'jsonnet', 'jsont', 'jsp', 'jsx', - 'julia', 'julius', 'jump', 'juniper', 'junit-test-report', 'junos', 'juttle', - 'kag', 'kagex', 'kbd', 'keep-as3-metadata', 'kerboscript', 'kernel', - 'kevscript', 'kewyword', 'key', 'key-assignment', 'key-pair', 'key-path', - 'key-value', 'keyframe', 'keyframes', 'keygroup', 'keyvalue', 'keyword', - 'keyword_arrays', 'keyword_objects', 'keyword_roots', 'keyword_string', - 'keywords', 'kickstart', 'kind', 'kmd', 'kn', 'knitr', 'kos', 'kotlin', 'krl', - 'kspcfg', 'kurumin', 'kv', 'l20n', 'label', 'labeled', 'labeled-parameter', - 'lambda', 'langauge', 'language', 'languagebabel', 'languages', 'langversion', - 'largesk', 'lasso', 'last', 'latex', 'latex2', 'latino', 'latte', 'layout', - 'layoutbii', 'lbsearch', 'lc', 'lcb', 'ldap', 'ldif', 'le', 'leading', - 'leading-tabs', 'lean', 'left', 'left-margin', 'leftshift', 'legacy', 'lemon', - 'length', 'leopard', 'less', 'less-equal', 'let', 'letter', 'level', 'levels', - 'lex', 'lhs', 'li', 'lib', 'library', 'libxml', 'lifetime', 'ligature', - 'light_purple', 'lilypond', 'lilypond-figuregroup', 'lilypond-internals', - 'lilypond-lyricsmode', 'lilypond-notemode', 'lilypond-schememode', - 'limit_zone', 'line', 'line-break', 'line-continuation', - 'line-continuation-operator', 'line-number', 'linebreak', 'linefeed', - 'linenumber', 'link', 'link-label', 'link-report', 'link-text', 'link-url', - 'linkage', 'linkedsockets', 'linkplain', 'linkplain-label', 'linq', - 'linuxcncgcode', 'liquid', 'lisp', 'list', 'list-directive', 'list-separator', - 'list_item', 'listnum', 'listvalues', 'litcoffee', 'literal', + '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-externs', - 'load-hint', 'loader', 'local', 'local-variables', 'locale-element', - 'localized', 'localized-description', 'localized-title', 'localname', - 'locals', 'location', 'lock', 'log', 'log-debug', 'log-error', 'log-info', - 'log-patch', 'log-success', 'log-verbose', 'log-warning', 'logging', 'logic', - 'logical', 'logical-expression', 'logicblox', 'logo', 'logstash', 'logtalk', - 'lol', 'long', 'look-ahead', 'look-behind', 'lookahead', 'lookaround', - 'lookbehind', 'loop', 'loop-control', 'lp', 'lparen', 'lsg', 'lsl', - 'lst-cpu12', 'lstdo', 'lt', 'lt-gt', 'lua', 'lucee', 'luceecomment', - 'luceescript', 'lucius', 'lury', 'lv', 'lyricsmode', 'm', 'm4', 'm4sh', - 'm65816', 'm68k', 'mac-classic', 'machine', 'macro', 'macro-usage', 'macro11', - 'madoko', 'magenta', 'magic', 'magik', 'mail', 'mailer', 'main', 'makefile', - 'mako', 'mamba', 'manager-class', 'map', 'mapfile', 'mapkey', 'mapping', - 'maprange', 'marasm', 'margin', 'marginpar', 'mark', 'markdown', 'marker', - 'marko', 'marko-attribute', 'marko-tag', 'markup', 'markupmode', 'mask', - 'mason', 'mat', 'mata', 'match', 'match-condition', 'match-definition', - 'match-option', 'match-pattern', 'material', 'math', 'math_complex', - 'math_real', 'mathematic', 'mathematical', 'mathematical-symbols', - 'mathematics', 'matlab', 'matrix', 'maude', 'max-cached-fonts', - 'max-execution-time', 'maxscript', 'maybe', 'mb', 'mbstring', 'mc', 'mcc', - 'mccolor', 'mch', 'mcn', 'mcode', 'mcr', 'mcrypt', 'mcs', 'md', 'media', - 'media-feature', 'mediawiki', 'mei', 'mel', 'memaddress', 'member', - 'member-function-attribute', 'membership', 'memcache', 'memcached', 'memoir', - 'memory-management', 'menhir', 'mention', 'mercury', 'merlin', 'message', - 'message-forwarding-handler', 'messages', 'meta', 'meta-data', 'meta-info', - 'metadata', 'metakey', 'metascript', 'meteor', 'method', 'method-call', - 'method-definition', 'method-mofification', 'method-parameter', - 'method-parameters', 'method-restriction', 'methods', 'mhash', 'microsites', - 'middle', 'migration', 'mime', 'min', 'minelua', 'minetweaker', - 'minitemplate', 'minus', 'mips', 'mirah', 'misc', 'miscellaneous', - 'mismatched', 'missing', 'missing-asterisk', 'missing-inheritance', - 'missing-parameters', 'missing-section-begin', 'missingend', 'mixin', 'mjml', - 'ml', 'mlab', 'mls', 'mm', 'mml', 'mmx', 'mmx_instructions', 'mnemonic', - 'mochi', 'mod', 'mod_perl', 'modblock', 'model', 'model-based-calibration', - 'model-predictive-control', 'modelica', 'modelicascript', 'modern', - 'modifier', 'modl', 'modr', 'mods', 'modula-2', 'module', 'module-alias', - 'module-binding', 'module-definition', 'module-expression', - 'module-reference', 'module-rename', 'module-sum', 'module-type', 'modules', - 'modulo', 'mojolicious', 'mojom', 'mond', 'money', 'mongo', 'mongodb', - 'monicelli', 'monitor', 'monkberry', 'monkey', 'monospace', 'monte', 'month', - 'moon', 'moos', 'moose', 'moosecpp', 'mouse', 'mov', 'mozu', 'mpw', 'mpx', - 'mscgen', 'mscript', 'msg', 'msgctxt', 'msgenny', 'msgstr', 'mson', - 'mson-block', 'mss', 'mta', 'mucow', 'multi', 'multi-line', 'multi-threading', - 'multids-file', 'multiline', 'multiplication', 'multiplicative', 'multiply', - 'multiverse', 'mumps', 'mundosk', 'mustache', 'mut', 'mutable', 'mutator', - 'mx', 'mxml', 'mydsl1', 'mysql', 'mysqli', 'mysqlnd-memcache', 'mysqlnd-ms', + '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-tuple', 'nameless-typed', 'namelist', - 'namespace', 'namespace-block', 'namespace-definition', 'namespace-reference', - 'namespaces', 'nan', 'nant', 'narration', 'nas', 'nastran', 'nat', 'native', - 'nativeint', 'natural', 'navigation', 'ncf', 'ncl', 'ndash', 'nearley', - 'negate', 'negated', 'negation', 'negative', 'negative-look-ahead', - 'negative-look-behind', 'neko', 'nesc', 'nested', 'nested_braces', - 'nested_ltgt', 'nesty', 'net', 'netbios', 'network', 'neural-network', 'new', - 'new-object', 'newline', 'newline-spacing', 'newlinetext', 'newlisp', 'nez', - 'nft', 'ngdoc', 'nginx', 'nickname', 'nil', 'nim', 'ninja', 'ninjaforce', - 'nit', 'nitro', 'nix', 'nl', 'nlf', 'nm', 'nm7', 'no-capture', + '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-trailing-digits', 'no-validate-params', 'nocapture', 'node', 'nogc', - 'noindent', 'non', 'non-capturing', 'non-immediate', 'non-intrinsic', - 'non-null-typehinted', 'non-overridable', 'non-standard', 'non-terminal', - 'non_recursive', 'none', 'none-parameter', 'nonlocal', 'nonterminal', 'noon', - 'nop', 'nopass', 'normal', 'normal_numeric', 'normal_objects', 'normal_text', - 'not', 'not-a-number', 'notation', 'note', 'notechord', 'notemode', - 'notequal', 'notequalexpr', 'notidentical', 'notification', 'nowdoc', 'noweb', - 'nrtdrv', 'nsapi', 'nscript', 'nse', 'nsis', 'nsl', 'ntriples', 'nul', 'null', - 'nullify', 'nullological', 'nulltype', 'num', 'number', 'number-sign', - 'numbered', 'numberic', 'numbersign', 'numeric', 'numeric_std', 'nunjucks', - 'nut', 'nvatom', 'nxc', 'objc', 'objcpp', 'objdump', 'object', - 'object-comments', 'object-definition', 'objj', 'obsolete', 'ocaml', - 'ocamllex', 'occam', 'occurrences', 'oci8', 'ocmal', 'oct', 'octal', 'octave', - 'octo', 'octobercms', 'octothorpe', 'odd-tab', 'odedsl', 'ods', 'offset', - 'ofx', 'ogre', 'ok', 'ol', 'old', 'old-style', 'omap', 'omitted', 'on', - 'on-background', 'on-error', 'one', 'oniguruma', 'only', 'only-in', 'onoff', - 'ooc', 'op-domain', 'opa', 'opaque', 'opc', 'opcache', 'opcode', 'open', - 'opencl', 'opendss', 'opening', 'openmp', 'openssl', 'opentype', 'operand', - 'operands', 'operation', 'operator', 'operator2', 'operators', 'opmaskregs', - 'optimization', 'optimize', 'option', 'option-toggle', 'optional', + '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', 'or', 'oracle', 'orcam', 'orchestra', 'order', - 'ordered', 'ordered-block', 'orgtype', 'origin', 'other', 'others', - 'otherwise', 'out', 'outer', 'output', 'overload', 'override', 'overtype', - 'owner', 'oz', 'p5', 'p8', 'pa', 'package', 'package-definition', - 'package_body', 'packed', 'packed-arithmetic', 'packed-blending', - 'packed-comparison', 'packed-floating-point', 'packed-integer', 'packed-math', + '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', - 'page', 'page-props', 'pair', 'pandoc', 'papyrus', 'papyrus-assembly', - 'paragraph', 'param', 'param-list', 'paramater', 'parameter', - 'parameter-entity', 'parameterless', 'parameters', 'paramless', 'params', - 'paren', 'paren-group', 'parens', 'parent', 'parent-reference', 'parenthases', - 'parentheses', 'parenthesis', 'parenthesized', 'parenthetical_list', - 'parenthetical_pair', 'parfor-quantity', 'parse', 'parser-token', 'parser3', - 'particle', 'pascal', 'pass', 'pass-through', 'passthrough', 'password', - 'password-hash', 'patch', 'path', 'path-pattern', 'pattern', - 'pattern-definition', 'pause', 'payee', 'pbtxt', 'pcntl', 'pdd', 'pddl', - 'peg', 'pegcoffee', 'pegjs', 'pending', 'percentage', 'percussionnote', - 'period', 'perl', 'perl-section', 'perl6', 'perl6fe', 'perlfe', 'perlt6e', - 'perm', 'permutations', 'personalization', 'pf', 'pfm', 'pfx', 'pgn', 'pgsql', - 'phone-number', 'php', 'php_apache', 'php_dom', 'php_ftp', 'php_imap', - 'php_mssql', 'php_odbc', 'php_pcre', 'php_spl', 'php_zip', 'phpdoc', - 'phrasemodifiers', 'phraslur', 'physics', 'pi', 'pic', 'pick', 'pig', - 'pillar', 'pink', 'pipe', 'pipeline', 'piratesk', 'placeholder', - 'placeholder-selector', 'plain', 'plainsimple-heading', 'plainsimple-number', - 'plantuml', 'playerversion', 'plist', 'plsql', 'plugin', 'plus', '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', 'pointer', 'pointer-arith', 'policiesbii', 'polydelim', - 'polymorphic', 'polymorphic-variant', 'polysep', 'pony', 'port', 'positional', - 'positive', 'posix', 'posix-reserved', 'post', 'post-match', 'postblit', - 'postcss', 'postfix', 'postpone', 'postscript', 'potigol', 'potion', 'pound', - 'power', 'powershell', 'pp', 'ppd', 'praat', 'pragma', 'pragma-all-once', - 'pragma-mark', 'pragma-message', 'pragma-newline-spacing-value', - 'pragma-stg-value', 'pre', 'pre-defined', 'preamble', 'prec', 'precedence', - 'pred', 'predefined', 'predicate', 'prefetchwt', 'prefix', 'prefixed-uri', - 'prefixes', 'preinst', 'prelude', 'prepare', 'prepocessor', 'preposition', - 'prepositional', 'preprocessor', 'prerequisites', 'previous', 'primitive', - 'primitive-datatypes', 'primitive-field', 'print', 'priority', 'prism', - 'private', 'privileged', 'pro', 'probe', 'proc', 'procedure', - 'procedure_definition', 'procedure_prototype', 'process', - 'process-substitution', 'processes', 'processing', 'proctitle', 'profile', - 'program', 'program-block', 'progressbars', 'proguard', 'project', 'prolog', - 'prologue', 'promoted', 'prompt', 'prompt-prefix', 'prop', 'properties', - 'properties_literal', 'property', 'property-list', 'property-name', - 'property-value', 'propertyend', 'proposition', 'protected', 'protection', - 'proto', 'protobuf', 'protobufs', 'protocol', 'protocol-list', 'prototype', - 'proxy', 'psci', 'pseudo', 'pseudo-class', 'pseudo-element', 'pseudo-method', - 'pseudo-mnemonic', 'pseudo-variable', 'pshdl', 'pspell', 'psql', 'pt', - 'ptc-config', 'pthread', 'public', 'pug', 'punchcard', 'punctual', - 'punctuation', 'punctutation', 'puncuation', 'puppet', 'pure', 'purebasic', - 'purescript', 'pweave', 'py2pml', 'pymol', 'pyresttest', 'python', - 'python-function', 'q', 'q-bracket', 'q-paren', 'qa', 'qml', 'qoute', 'qq', - 'qq-bracket', 'qq-paren', 'qry', 'qtpro', 'quad', 'qual', 'qualifier', - 'quality', 'quant', 'quantifier', 'quantifiers', 'quartz', 'quasi', - 'quasiquote', 'quasiquotes', 'query', 'query-dsl', 'question', 'questionmark', - 'quicktime-file', 'quotation', 'quote', 'quoted', 'quoted-expression', - 'quoted-identifier', 'quoted-object', 'quotes', 'qx', 'r', 'rabl', 'racket', - 'radix', 'rails', 'rainmeter', 'raml', 'randomsk', 'range', 'rant', 'rapid', - 'rarity', 'ratio', 'raw', 'raw-regex', 'rd', 'rdfs-type', 'rdrand', 'rdseed', - 'read', 'readline', 'readwrite', 'real', 'realip', 'rebeca', 'rebol', 'rec', - 'receive-channel', 'recipient-subscriber-list', 'recode', 'record', - 'record-field', 'record-usage', 'recordfield', 'recursive', 'recutils', 'red', - 'redirect', 'redirection', 'ref', 'reference', 'referer', 'refinement', - 'reflection', 'reg', 'regex', 'regexname', 'regexp', 'regexp-option', - 'region', 'register', 'register-64', 'registers', 'reiny', 'reject', - 'relation', 'relational', 'relations', 'relationship-pattern', - 'relationship-pattern-end', 'relationship-pattern-start', - 'relationship-type-or', 'relationship-type-start', 'rem', 'reminder', - 'remoting', 'rename', 'renaming', 'render', 'reparator', 'repeat', 'replace', - 'replaceXXX', 'replacement', 'reply', 'repo', 'reporter', 'repository', - 'request', 'request-type', 'require', 'required', 'requirements', 'rescue', - 'reserved', 'reset', 'resource', 'resource-bundle-list', 'response', - 'response-type', 'rest', 'rester', 'restriced', 'restructuredtext', 'result', - 'result-separator', 'results', 'return', 'return-type', 'return-value', - 'returns', 'rev', 'reversed', 'review', 'rewrite', 'rf', 'rfc', - 'rgb-percentage', 'rgb-value', 'rhap', 'rhs', 'rhtml', 'right', 'riot', - 'rivescript', 'rl', 'rmarkdown', 'rnc', 'roboconf', 'robot', 'robotc', - 'robust-control', 'role', 'root', 'round', 'round-brackets', 'routeros', - 'routine', 'row', 'row2', 'rowspan', 'roxygen', 'rparent', 'rpc', 'rpm-spec', - 'rpmspec', 'rpt', 'rq', 'rrd', 'rsl', 'rsl-url', 'rspec', 'rtemplate', 'ru', - 'ruby', 'rubymotion', 'rule', 'ruleDefinition', 'rules', 'run', 'rune', - 'runtime', 'runtime-shared-library-path', 'rust', 'safe-call', - 'safe-navigation', 'safe-trap', 'safer', 'safety', 'sage', 'sampler', + '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', 'save', 'scad', 'scala', 'scaladoc', 'scalar', - 'scam', 'scenario', 'scenario_outline', 'scene', 'schem', 'scheme', - 'schememode', 'scilab', 'sck', 'scope', 'scope-name', 'scope-resolution', - 'scribble', 'script', 'script-metadata', 'script-tag', 'scriptblock', - 'scripting', 'scriptlet', 'scriptlocal', 'scriptname-declaration', - 'scrollbars', 'scss', 'sdbl', 'sdl', 'sdo', 'search', 'seawolf', 'second', - 'section', 'section-item', 'sectionname', 'sections', 'see', 'select', - 'select-block', 'selectionset', 'selector', 'self', 'self-binding', 'sem', - 'semantic', 'semi-colon', 'semicolon', 'semicoron', 'semireserved', - 'send-channel', 'senum', 'separator', 'sepatator', 'seperator', 'sequence', - 'sequences', 'serpent', 'server', 'service', 'service-rpc', 'services', - 'session', 'set', 'set_node', 'setname', 'setproperty', 'setter', 'setting', - 'settings', 'settype', 'setword', 'severity', 'sexpr', 'sfst', 'sgml', 'sha', - 'sha256', 'sha512', 'sha_functions', 'shade', 'shaderlab', 'shared', - 'shared-static', 'sharpequal', 'sharpge', 'sharpgt', 'sharple', 'sharplt', - 'shebang', 'shell', 'shell-session', 'shift', 'shift-and-rotate', - 'shift-right', 'shipflow', 'shmop', 'short', 'shortcut', 'shortcuts', - 'shorthandpropertyname', 'show', 'show-actionscript-warnings', - 'show-argument', 'show-binding-warnings', - 'show-shadowed-device-font-warnings', 'show-unused-type-selector-warnings', - 'shutdown', 'sigil', 'sign-line', 'signal', 'signal-processing', 'signature', - 'simd', 'simd-integer', 'simple', 'simple-element', 'simple_delimiter', - 'simplexml', 'simplez', 'since', 'singe', 'single', 'single-line', - 'single-quote', 'single-quoted', 'single_quote', 'singleton', 'six', 'size', - 'sized_integer', 'sizeof', 'sjs', 'sjson', 'skaction', 'skdragon', 'skeeland', - 'sketchplugin', 'skew', 'skip', 'skmorkaz', 'skquery', 'skrambled', - 'skrayfall', 'skript', 'skrpg', 'sksharp', 'skstuff', 'skutilities', - 'skvoice', 'sl', 'slash', 'slash-option', 'slashes', 'slashstar', 'sleet', - 'slice', 'slim', 'slm', 'sln', 'slot', 'slot_subnode', 'slugignore', 'sma', - 'smali', 'smalltalk', 'smarty', 'sml', 'smpte', 'smx', 'snlog', 'snmp', - 'soap', 'socketgroup', 'sockets', 'somearg', 'something', 'soql', 'sort', - 'souce', 'source', 'source-constant', 'source-path', 'soy', 'sp', 'space', - 'space-after-command', 'spacebars', 'spaces', 'sparql', 'spath', 'spec', - 'special', 'special-attributes', 'special-functions', 'special-method', - 'special-tokens', 'specification', 'sphinx', 'spice', 'spider', 'splat', - 'spline', 'splunk', 'splus', 'spn', 'spread', 'spreadmap', 'sproto', - 'sproutcore', 'sqf', 'sql', 'sqlite', 'sqlsrv', 'sqsp', 'square', 'squart', - 'squirrel', 'sr-Latn', 'src', 'srltext', 'ss', 'sse', 'sse2', 'sse2_simd', - 'sse3', 'sse4_simd', 'sse_simd', 'ssh-config', 'ssi', 'ssl', 'st', 'stable', - 'stack', 'stack-effect', 'stackframe', 'stan', 'standard', 'standard-links', - 'standard-suite', 'standardadditions', 'standoc', 'star', 'start', - 'start-block', 'start-condition', 'start-symbol', 'starting-functions-point', - 'startshape', 'statamic', 'state', 'state-management', 'stateend', - 'stategrouparg', 'stategroupval', 'statement', 'statement-separator', - 'states', 'statestart', 'static', 'static-assert', 'static-if', 'static-rsls', - 'staticimages', 'statistics', 'stats', 'std', 'std_logic', 'stdint', - 'stdlibcall', 'step', 'steps', 'stg', 'stk', 'stmt', 'stop', 'stopping', - 'storage', 'stp', 'stray-comment-end', 'stream', 'streamsfuncs', 'streem', - 'strict', 'strike', 'strikethrough', 'string', 'string-constant', - 'string-format', 'string-interpolation', 'string-long-single-quote', - 'strings', 'strong', 'struct', 'struct-union-block', 'structdef', 'structs', - 'structure', 'stuff', 'style', 'styleblock', 'styles', 'stylus', 'sub', - 'sub-pattern', 'subckt', 'subcmd', 'subexp', 'subexpression', 'subkey', - 'subkeys', 'subl', 'submodule', 'subroutine', 'subscript', 'subsection', - 'subsections', 'subshell', 'subsort', 'substitute', 'substitution', - 'subtitle', 'subtlegradient', 'subtraction', 'suffix', 'sugly', - 'sugly-control-keywords', 'sugly-delcare-operator', 'sugly-delcare-variable', - 'sugly-encode-clause', 'sugly-function-groups', 'sugly-function-recursion', - 'sugly-general-operators', 'sugly-generic-types', '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-print-clause', 'sugly-subject-or-predicate', 'sugly-type-function', - 'sugly-uri-clause', 'super', 'superclass', 'supercollider', 'superscript', - 'supertype', 'supervisor', 'supplemental', 'supplimental', 'support', - 'supports', 'suppressed', 'svg', 'svm', 'svn', 'swift', 'swig', 'switch', - 'switch-block', 'switch-expression', 'switch-statement', 'switchStart', - 'swizzle', 'sybase', 'symbol', 'symbol-type', 'symbolic', 'symbolic-math', - 'symbols', 'symmetry', 'synchronization', 'synchronize', 'synchronized', - 'synergy', 'syntax', 'sys-types', 'syslog-ng', 'system', 'system-events', - 'system-identification', 'systemreference', 'sytem-events', 't4', 't5', 't7', - 'ta', 'tab', 'table', 'table-name', 'tablename', 'tabs', 'tabular', 'tacacs', - 'taco', 'tads3', 'tag', 'tag-not-recognized', 'tag-string', 'tag-value', - 'tagbraces', 'tagdef', 'tagged', 'tagger_script', 'taglib', 'tagnamedjango', - 'tags', 'taint', 'target', 'task', 'tasks', 'tbdfile', 'tbody', 'tcl', - 'tcoffee', 'td', 'tdl', 'tea', 'telegram', 'tell', 'temp', 'template', - 'templatetag', 'temporal', 'term', 'terminal', 'termination', 'terminator', - 'terms_node', 'ternary', 'ternary-if', 'terra', 'terraform', 'testcase', - 'testing', 'tests', 'testsuite', 'tex', 'texshop', 'text', 'text-reference', - 'text-suite', 'textbf', 'textile', 'textio', 'textit', 'texttt', 'th', - 'thead', 'theme', 'then', 'therefore', 'thin', 'third', 'this', 'thrift', - 'throw', 'throwables', 'throws', 'tick', 'ticket-num', 'ticket-psa', - 'tid-file', 'tidal', 'tidalcycles', 'tiddler', 'tiddler-field', 'tidy', - 'tieslur', 'time', 'times', 'timespan', 'timestamp', 'timing', 'titanium', - 'title', 'title-text', 'tjs', 'tl', 'tla', 'tmpl', 'tmsim', 'tmux', 'to', - 'to-file', 'toc', 'toc-list', 'todo', 'todo_extra', 'todo_node', 'todotxt', - 'token', 'token-def', 'token-paste', 'token-type', 'tokenizer', 'toml', - 'toolbox', 'tools', 'tooltip', 'top', 'top-level', 'topic', 'tornado', + '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', 'traceback', 'trader', 'trail', 'trailing', - 'trailing-match', 'trailing-whitespace', 'trait', 'traits', 'transaction', + 'trace', 'trace-argument', 'trace-object', 'traceback', 'tracing', + 'track_processing', 'trader', 'tradersk', 'trail', 'trailing', + 'trailing-array-separator', 'trailing-dictionary-separator', 'trailing-match', + 'trailing-whitespace', 'trait', 'traits', 'traits-keyword', 'transaction', 'transcendental', 'transcludeblock', 'transcludeinline', 'transclusion', - 'transitionable-property-value', 'transpose', 'transposed-matrix', - 'transposed-parens', 'transposed-variable', 'trap', 'treetop', 'trenni', - 'trigEvent_', 'trigLevel_', 'trigger', 'triple', 'triple-slash', 'true', - 'truncate', 'truncation', 'try', 'trycatch', 'ts', 'tsql', 'tss', 'tsv', - 'tsx', 'tt', 'ttpmacro', 'tts', 'tubaina2', 'tup', 'tuple', 'turtle', 'tutch', - 'tvml', 'tw5', 'twig', 'twigil', 'twiki', 'two', 'txt', 'txt2tags', 'type', - 'type-cast', 'type-constrained', 'type-constraint', 'type-declaration', - 'type-def', 'type-definition', 'type-definition-group', 'type-of', 'type-or', - 'type-parameters', 'type-signature', 'type_params', 'type_trait', - 'typeabbrev', 'typeclass', 'typedblock', 'typedcoffeescript', 'typedef', + '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', 'typoscript', 'uc', 'ucicfg', 'ucicmd', 'udaf', 'udf', 'udl', - 'udtf', 'ui', 'ui-block', 'uintptr', 'ujm', 'uk', 'ul', 'unOp', 'unary', - 'unbuffered', 'uncleared', 'unclosed', 'unclosed-string', 'undef', - 'undefined', 'underline', 'underscore', 'undocumented', 'unescaped-quote', - 'unexpected', 'unexpected-extends', 'unexpected-extends-character', - 'unexpected-text', 'unformatted', 'unicode', 'unicode-16-bit', - 'unicode-escape', 'unicode-raw', 'unicode-raw-regex', 'unify', 'union', - 'unit', 'unit-test', 'unit_test', 'unittest', 'unity', 'universal-match', - 'unknown', 'unknown-escape', 'unknown-rune', 'unlabeled', 'unnumbered', 'uno', - 'unoconfig', 'unop', 'unoproj', 'unordered', 'unordered-block', 'unosln', - 'unpack', 'unpacking', 'unquoted', 'unrecognized', 'unrecognized-character', + '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', - 'unsupplied', 'until', 'untitled', 'untyped', 'uopz', 'update', 'upstream', - 'uri', 'url', 'usable', 'usage', 'use', 'use-as', 'usebean', 'usecase', - 'user', 'user-defined', 'user-defined-property', 'user-defined-type', - 'user-interaction', 'userid', 'username', 'using', - 'using-namespace-declaration', 'using_animtree', 'utility', 'uvu', 'ux', - 'uxc', 'uz', 'val', 'vala', 'valgrind', 'valid', 'valid-ampersand', 'valign', - 'value', 'value-pair', 'value-size', 'value-type', 'valuepair', 'vamos', - 'var', 'var-single-variable', 'var1', 'var2', 'variable', 'variable-access', - 'variable-declaration', 'variable-modifier', 'variable-parameter', - 'variable-reference', 'variable-usage', 'variables', 'variant-definition', - 'varname', 'varnish', 'vb', 'vbnet', 'vbs', 'vc', 'vcl', 'vector', - 'vector-load', 'vectors', 'velocity', 'vendor-prefix', 'verbatim', - 'verbose-stacktraces', 'verdict', 'verilog', 'version', 'vertical-tab', 'vex', - 'vhdl', 'via', 'view', 'viewhelpers', 'vim', 'viml', 'virtual', - 'virtual-reality', 'visibility', 'visualforce', 'vmap', 'voice', 'void', - 'volatile', 'volt', 'vpath', 'vplus', 'vue', 'w3c-extended-color-name', - 'w3c-standard-color-name', 'wait', 'waitress-rb', 'warn', - 'warn-array-tostring-changes', 'warn-assignment-within-conditional', - 'warn-bad-array-cast', 'warn-bad-bool-assignment', 'warn-bad-date-cast', - 'warn-bad-es3-type-method', 'warn-bad-es3-type-prop', - 'warn-bad-nan-comparison', 'warn-bad-null-assignment', - 'warn-bad-null-comparison', 'warn-bad-undefined-comparison', - 'warn-boolean-constructor-with-no-args', 'warn-changes-in-resolve', - 'warn-class-is-sealed', 'warn-const-not-initialized', - 'warn-constructor-returns-value', 'warn-deprecated-event-handler-error', - 'warn-deprecated-function-error', 'warn-deprecated-property-error', - 'warn-duplicate-argument-names', 'warn-duplicate-variable-def', - 'warn-for-var-in-changes', 'warn-import-hides-class', - 'warn-instance-of-changes', 'warn-internal-error', 'warn-level-not-supported', - 'warn-missing-namespace-decl', 'warn-negative-uint-literal', - 'warn-no-constructor', 'warn-no-explicit-super-call-in-constructor', - 'warn-no-type-decl', 'warn-number-from-string-changes', - 'warn-scoping-change-in-this', 'warn-slow-text-field-addition', - 'warn-unlikely-function-value', 'warn-xml-class-has-changed', 'warning', - 'warnings', 'wast', 'wavelet', 'wddx', 'weave', 'webidl', 'webspeed', - 'weekday', 'weirdland', 'wh', 'whatever', 'when', 'where', 'while', + '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-classes', - 'windows', 'with', 'with-arg', 'with-args', 'with-arguments', 'with-params', - 'with-side-effects', 'without-arguments', 'without-attributes', 'wla-dx', - 'word', 'word-op', 'words', 'world', 'wow', 'wp', 'write', - 'wrong-access-type', 'wrong-division-assignment', 'ws', 'x10', 'x86', - 'x86_64', 'x86asm', 'xbase', 'xchg', 'xflags-mark', 'xhprof', 'xhtml', - 'xikij', 'xml', 'xmlrpc', 'xmlwriter', 'xop', 'xor', 'xq', 'xquery', 'xref', - 'xsave', 'xsd_nillable', 'xsd_optional', 'xsl', 'xsse3_simd', 'xst', 'xtend', - 'xtoy', 'xtpl', 'xu', 'xvc', 'xve', 'yaml', 'yaml-ext', 'yang', 'yara', - 'yard', 'yate', 'year', 'yellow', 'yield', 'yorick', 'you-forgot-semicolon', - 'z80', 'zap', 'zapper', 'zep', 'zepon', 'zepto', 'zero', 'zh-CN', 'zig', - 'zip', 'zlib', 'zoomfilter' -] + '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' +]) From 80fc448b8d45b417bd0a8a6ca64ddbd61f50cde7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 4 Oct 2016 11:22:41 +0200 Subject: [PATCH 10/88] Transform deprecated shadow DOM selectors in StyleManager --- package.json | 3 +- spec/style-manager-spec.js | 100 ++++++++++++++++++++++++----- spec/styles-element-spec.coffee | 68 -------------------- src/deprecated-syntax-selectors.js | 2 +- src/lines-tile-component.coffee | 6 +- src/style-manager.js | 41 +++++++++++- src/styles-element.coffee | 57 ---------------- static/text-editor-light.less | 8 +-- 8 files changed, 133 insertions(+), 152 deletions(-) diff --git a/package.json b/package.json index 63b00ee8f..3a33472f3 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "nslog": "^3", "oniguruma": "6.1.0", "pathwatcher": "~6.5", - "postcss-selector-parser": "^2.2.1", + "postcss": "5.2.4", + "postcss-selector-parser": "2.2.1", "property-accessors": "^1.1.3", "random-words": "0.0.1", "resolve": "^1.1.6", diff --git a/spec/style-manager-spec.js b/spec/style-manager-spec.js index 19451ffea..65fc24e9a 100644 --- a/spec/style-manager-spec.js +++ b/spec/style-manager-spec.js @@ -1,39 +1,107 @@ const StyleManager = require('../src/style-manager') describe('StyleManager', () => { - let [manager, addEvents, removeEvents, updateEvents] = [] + let [styleManager, addEvents, removeEvents, updateEvents] = [] beforeEach(() => { - manager = new StyleManager({configDirPath: atom.getConfigDirPath()}) + styleManager = 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) }) + 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 stylesheet based on the given source and returns a disposable allowing it to be removed', () => { - const disposable = manager.addStyleSheet('a {color: red}') + const disposable = styleManager.addStyleSheet('a {color: red}') expect(addEvents.length).toBe(1) expect(addEvents[0].textContent).toBe('a {color: red}') - const styleElements = manager.getStyleElements() + 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(manager.getStyleElements().length).toBe(0) + 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 } + `) + 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' + ]) + } + ) + + 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 .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 .source > .js' + ]) + }) + }) + + it('replaces ":host" with "atom-text-editor" only when the context of a stylesheet 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' + ]) + global.debug = true + styleManager.addStyleSheet(':host .class-1, :host .class-2 { color: red; }', {context: 'atom-text-editor'}) + global.debug = false + 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 = manager.addStyleSheet('a {color: red}', {sourcePath: '/foo/bar'}) + 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 = manager.addStyleSheet('a {color: blue}', {sourcePath: '/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') @@ -41,7 +109,7 @@ describe('StyleManager', () => { disposable2.dispose() addEvents = [] - manager.addStyleSheet('a {color: yellow}', {sourcePath: '/foo/bar'}) + 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}') @@ -50,11 +118,11 @@ describe('StyleManager', () => { 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([ + 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}', diff --git a/spec/styles-element-spec.coffee b/spec/styles-element-spec.coffee index b29979524..0889b0d43 100644 --- a/spec/styles-element-spec.coffee +++ b/spec/styles-element-spec.coffee @@ -77,71 +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 -> - spyOn(console, 'warn') - - it "removes the ::shadow pseudo-element from atom-text-editor selectors", -> - atom.styles.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; } - """) - expect(Array.from(element.lastChild.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' - ]) - expect(console.warn).toHaveBeenCalled() - - describe "when the context of a style sheet is 'atom-text-editor'", -> - it "prepends `--syntax` to selectors not contained in atom-text-editor or matching a spatial decoration", -> - atom.styles.addStyleSheet(""" - .class-1 { color: red; } - .class-2 > .class-3, .class-4.class-5 { color: green; } - .class-6 atom-text-editor .class-7 { color: yellow; } - atom-text-editor .class-8, .class-9 { color: blue; } - atom-text-editor .indent-guide, atom-text-editor .leading-whitespace { background: white; } - .syntax--class-10 { color: gray; } - :host .class-11 { color: purple; } - #id-1 { color: gray; } - """, {context: 'atom-text-editor'}) - expect(Array.from(element.lastChild.sheet.cssRules).map((r) -> r.selectorText)).toEqual([ - '.syntax--class-1', - '.syntax--class-2 > .syntax--class-3, .syntax--class-4.syntax--class-5', - '.class-6 atom-text-editor .class-7', - 'atom-text-editor .class-8, .syntax--class-9', - 'atom-text-editor .syntax--indent-guide, atom-text-editor .syntax--leading-whitespace', - '.syntax--class-10', - 'atom-text-editor .class-11', - '#id-1' - ]) - expect(console.warn).toHaveBeenCalled() - - describe "when the context of a style sheet is not 'atom-text-editor'", -> - it "never prepends class names with `--syntax`", -> - atom.styles.addStyleSheet(""" - .class-1 { color: red; } - .class-2 > .class-3, .class-4.class-5 { color: green; } - .class-6 atom-text-editor .class-7 { color: yellow; } - atom-text-editor .class-8, .class-9 { color: blue; } - atom-text-editor .indent-guide, atom-text-editor .leading-whitespace { background: white; } - #id-1 { color: gray; } - """) - expect(Array.from(element.lastChild.sheet.cssRules).map((r) -> r.selectorText)).toEqual([ - '.class-1' - '.class-2 > .class-3, .class-4.class-5' - '.class-6 atom-text-editor .class-7' - 'atom-text-editor .class-8, .class-9' - 'atom-text-editor .indent-guide, atom-text-editor .leading-whitespace' - '#id-1' - ]) - expect(console.warn).not.toHaveBeenCalled() - - 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/src/deprecated-syntax-selectors.js b/src/deprecated-syntax-selectors.js index 8b85956ca..8b798bd4c 100644 --- a/src/deprecated-syntax-selectors.js +++ b/src/deprecated-syntax-selectors.js @@ -863,7 +863,7 @@ module.exports = new Set([ 'trace', 'trace-argument', 'trace-object', 'traceback', 'tracing', 'track_processing', 'trader', 'tradersk', 'trail', 'trailing', 'trailing-array-separator', 'trailing-dictionary-separator', 'trailing-match', - 'trailing-whitespace', 'trait', 'traits', 'traits-keyword', 'transaction', + 'trait', 'traits', 'traits-keyword', 'transaction', 'transcendental', 'transcludeblock', 'transcludeinline', 'transclusion', 'transform', 'transformation', 'transient', 'transition', 'transitionable-property-value', 'translation', 'transmission-filter', diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index c7e3bf367..c1cb2ba64 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -202,8 +202,8 @@ class LinesTileComponent if @presenter.isCloseTagCode(tagCode) openScopeNode = openScopeNode.parentElement else if @presenter.isOpenTagCode(tagCode) - scopes = @presenter.tagForCode(tagCode).replace(/\s+/g, '.').split('.').map((scope) -> "syntax--#{scope}") - newScopeNode = @domElementPool.buildElement("span", scopes.join(' ')) + scope = @presenter.tagForCode(tagCode) + newScopeNode = @domElementPool.buildElement("span", scope.replace(/\.+/g, ' ')) openScopeNode.appendChild(newScopeNode) openScopeNode = newScopeNode else @@ -219,7 +219,7 @@ class LinesTileComponent if lineText.endsWith(@presenter.displayLayer.foldCharacter) # Insert a zero-width non-breaking whitespace, so that - # LinesYardstick can take the syntax--fold-marker::after pseudo-element + # 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) diff --git a/src/style-manager.js b/src/style-manager.js index ae06f9060..a294d1d15 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -1,7 +1,10 @@ +const {Emitter, Disposable} = require('event-kit') const fs = require('fs-plus') const path = require('path') -const {Emitter, Disposable} = require('event-kit') +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 @@ -122,7 +125,7 @@ module.exports = class StyleManager { } } - styleElement.textContent = source + styleElement.textContent = transformDeprecatedShadowDOMSelectors(source, params.context) if (updated) { this.emitter.emit('did-update-style-element', styleElement) } else { @@ -205,3 +208,37 @@ module.exports = class StyleManager { } } } + +function transformDeprecatedShadowDOMSelectors (css, context) { + const root = postcss.parse(css) + root.walkRules((rule) => { + rule.selector = 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 targetsAtomTextEditorShadow = context === 'atom-text-editor' + let previousNode + selector.each((node) => { + if (targetsAtomTextEditorShadow && node.type === 'class') { + if (DEPRECATED_SYNTAX_SELECTORS.has(node.value) && !node.value.startsWith('syntax--')) { + node.value = `syntax--${node.value}` + } + } else if (previousNode) { + const currentNodeIsShadowPseudoClass = node.type === 'pseudo' && node.value === '::shadow' + const previousNodeIsAtomTextEditor = previousNode.type === 'tag' && previousNode.value === 'atom-text-editor' + if (previousNodeIsAtomTextEditor && currentNodeIsShadowPseudoClass) { + selector.removeChild(node) + targetsAtomTextEditorShadow = true + } + } + previousNode = node + }) + }) + }).process(rule.selector).result + }) + return root.toString() +} diff --git a/src/styles-element.coffee b/src/styles-element.coffee index e82775c92..2c53300c2 100644 --- a/src/styles-element.coffee +++ b/src/styles-element.coffee @@ -1,9 +1,4 @@ {Emitter, CompositeDisposable} = require 'event-kit' -selectorProcessor = require 'postcss-selector-parser' -SPATIAL_DECORATIONS = new Set([ - 'invisible-character', 'hard-tab', 'leading-whitespace', - 'trailing-whitespace', 'eol', 'indent-guide', 'fold-marker' -]) class StylesElement extends HTMLElement subscriptions: null @@ -24,9 +19,6 @@ class StylesElement extends HTMLElement @styleElementClonesByOriginalElement = new WeakMap attachedCallback: -> - for styleElement in @children - @upgradeDeprecatedSelectors(styleElement) - @context = @getAttribute('context') ? undefined detachedCallback: -> @@ -68,7 +60,6 @@ class StylesElement extends HTMLElement break @insertBefore(styleElementClone, insertBefore) - @upgradeDeprecatedSelectors(styleElementClone) @emitter.emit 'did-add-style-element', styleElementClone styleElementRemoved: (styleElement) -> @@ -88,52 +79,4 @@ class StylesElement extends HTMLElement styleElementMatchesContext: (styleElement) -> not @context? or styleElement.context is @context - upgradeDeprecatedSelectors: (styleElement) -> - return unless styleElement.sheet? - - transformDeprecatedShadowSelectors = (selectors) -> - selectors.each (selector) -> - isSyntaxSelector = not selector.some((node) -> - (node.type is 'tag' and node.value is 'atom-text-editor') or - (node.type is 'class' and node.value is 'region') or - (node.type is 'class' and node.value is 'wrap-guide') or - (node.type is 'class' and /spell-check/.test(node.value)) - ) - previousNode = null - selector.each (node) -> - isShadowPseudoClass = node.type is 'pseudo' and node.value is '::shadow' - isHostPseudoClass = node.type is 'pseudo' and node.value is ':host' - if isHostPseudoClass and not previousNode? - newNode = selectorProcessor.tag({value: 'atom-text-editor'}) - node.replaceWith(newNode) - previousNode = newNode - else if isShadowPseudoClass and previousNode?.type is 'tag' and previousNode?.value is 'atom-text-editor' - selector.removeChild(node) - else - if styleElement.context is 'atom-text-editor' and node.type is 'class' - if (isSyntaxSelector and not node.value.startsWith('syntax--')) or SPATIAL_DECORATIONS.has(node.value) - node.value = 'syntax--' + node.value - previousNode = node - - upgradedSelectors = [] - for rule in styleElement.sheet.cssRules when rule.selectorText? - inputSelector = rule.selectorText - outputSelector = rule.selectorText - outputSelector = selectorProcessor(transformDeprecatedShadowSelectors).process(outputSelector).result - if inputSelector isnt outputSelector - rule.selectorText = outputSelector - upgradedSelectors.push({inputSelector, outputSelector}) - - if upgradedSelectors.length > 0 - upgradedSelectorsText = upgradedSelectors.map(({inputSelector, outputSelector}) -> "`#{inputSelector}` => `#{outputSelector}`").join('\n') - console.warn(""" - Shadow DOM for `atom-text-editor` elements has been removed. This means - should stop using :host and ::shadow pseudo-selectors, and prepend all - your syntax selectors with `syntax--`. To prevent breakage with existing - stylesheets, we have automatically upgraded the following selectors in - `#{styleElement.sourcePath}`: - - #{upgradedSelectorsText} - """) - module.exports = StylesElement = document.registerElement 'atom-styles', prototype: StylesElement.prototype diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 1f16ec71e..193749d51 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -122,12 +122,12 @@ atom-text-editor { .line { white-space: pre; - &.cursor-line .syntax--fold-marker::after { + &.cursor-line .fold-marker::after { opacity: 1; } } - .syntax--fold-marker { + .fold-marker { cursor: default; &::after { @@ -143,12 +143,12 @@ atom-text-editor { color: @text-color-subtle; } - .syntax--invisible-character { + .invisible-character { font-weight: normal !important; font-style: normal !important; } - .syntax--indent-guide { + .indent-guide { display: inline-block; box-shadow: inset 1px 0; } From 895dbdfce223870dff361e645022cf0d5172a6fa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 4 Oct 2016 11:58:51 +0200 Subject: [PATCH 11/88] Add StyleManager.prototype.{onDidUpdateDeprecations,getDeprecations} --- src/style-manager.js | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/style-manager.js b/src/style-manager.js index a294d1d15..0b139514c 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -17,6 +17,7 @@ module.exports = class StyleManager { this.emitter = new Emitter() this.styleElements = [] this.styleElementsBySourcePath = {} + this.deprecationsBySourcePath = {} } /* @@ -93,6 +94,10 @@ module.exports = class StyleManager { return this.emitter.on('did-update-style-element', callback) } + onDidUpdateDeprecations (callback) { + return this.emitter.on('did-update-deprecations', callback) + } + /* Section: Reading Style Elements */ @@ -125,7 +130,12 @@ module.exports = class StyleManager { } } - styleElement.textContent = transformDeprecatedShadowDOMSelectors(source, params.context) + const 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 { @@ -163,6 +173,14 @@ module.exports = class StyleManager { } } + getDeprecations () { + return this.deprecationsBySourcePath + } + + clearDeprecations () { + this.deprecationsBySourcePath = {} + } + getSnapshot () { return this.styleElements.slice() } @@ -210,9 +228,10 @@ module.exports = class StyleManager { } function transformDeprecatedShadowDOMSelectors (css, context) { - const root = postcss.parse(css) - root.walkRules((rule) => { - rule.selector = selectorParser((selectors) => { + 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') { @@ -238,7 +257,23 @@ function transformDeprecatedShadowDOMSelectors (css, context) { previousNode = node }) }) - }).process(rule.selector).result + }).process(rule.selector, {lossless: true}).result + if (transformedSelector !== rule.selector) { + transformedSelectors.push({before: rule.selector, after: transformedSelector}) + rule.selector = transformedSelector + } }) - return root.toString() + let deprecationMessage + if (transformedSelectors.length > 0) { + deprecationMessage = 'Shadow DOM support for \`atom-text-editor\` elements has been deprecated. ' + 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 stylesheets, Atom will automatically ' + deprecationMessage += 'upgrade the following selectors:\n\n' + deprecationMessage += transformedSelectors + .map((selector) => `* \`${selector.before}\` => \`${selector.after}\``) + .join('\n\n') + '\n\n' + deprecationMessage += 'Please, make sure to upgrade these selectors as soon as possible.' + } + return {source: transformedSource.toString(), deprecationMessage} } From f8a89ed99aed763a869d3a1a65e554811a7e0cf1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 4 Oct 2016 18:06:09 +0200 Subject: [PATCH 12/88] Add backward compatible classes to TextEditorComponent's root node --- src/text-editor-component.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index e01096ed6..60033dc88 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -63,6 +63,11 @@ class TextEditorComponent @domElementPool = new DOMElementPool @domNode = document.createElement('div') @domNode.classList.add('editor-contents') + # TODO: Remove these backwards compatible classes once everyone has + # transitioned to non-shadow DOM selectors. + @domNode.classList.add('editor-contents--private') + @domNode.classList.add('editor--private') + @overlayManager = new OverlayManager(@presenter, @domNode, @views) @blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool) From 0f6e018804096dd1b0148e8df30477e41c831ba4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 4 Oct 2016 18:26:37 +0200 Subject: [PATCH 13/88] Throw an error if there is any deprecation in a spec Previously this logic lived in atom-reporter, but it seems more reasonable to throw errors in spec-helper instead, so that the test suite fails in CI as well whenever a deprecated method or stylesheet is used. --- spec/atom-reporter.coffee | 39 +-------------------------------------- spec/spec-helper.coffee | 30 ++++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 46 deletions(-) diff --git a/spec/atom-reporter.coffee b/spec/atom-reporter.coffee index ef46b09c5..1600da9d7 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/spec-helper.coffee b/spec/spec-helper.coffee index 7dbd6a4e5..44d8b4460 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') From 8280fa95406249d5079ec53ab53a1adbaa4bfedc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 5 Oct 2016 12:42:49 +0200 Subject: [PATCH 14/88] Rewrite LinesTileComponent in JavaScript --- src/lines-tile-component.coffee | 288 -------------------------- src/lines-tile-component.js | 348 ++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+), 288 deletions(-) delete mode 100644 src/lines-tile-component.coffee create mode 100644 src/lines-tile-component.js diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee deleted file mode 100644 index c1cb2ba64..000000000 --- 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 000000000..0e98600ed --- /dev/null +++ b/src/lines-tile-component.js @@ -0,0 +1,348 @@ +const HighlightsComponent = require('./highlights-component') +const ZERO_WIDTH_NBSP = '\ufeff' + +module.exports = class LinesTileComponent { + constructor ({presenter, id, domElementPool, assert}) { + this.presenter = presenter + this.id = id + this.domElementPool = domElementPool + this.assert = assert + this.measuredLines = new Set() + this.lineNodesByLineId = {} + this.screenRowsByLineId = {} + this.lineIdsByScreenRow = {} + this.textNodesByLineId = {} + this.insertionPointsBeforeLineById = {} + this.insertionPointsAfterLineById = {} + 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.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.oldTileState.width = this.newTileState.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 (id) { + this.domElementPool.freeElementAndDescendants(this.lineNodesByLineId[id]) + this.removeBlockDecorationInsertionPointBeforeLine(id) + this.removeBlockDecorationInsertionPointAfterLine(id) + delete this.lineNodesByLineId[id] + delete this.textNodesByLineId[id] + delete this.lineIdsByScreenRow[this.screenRowsByLineId[id]] + delete this.screenRowsByLineId[id] + delete this.oldTileState.lines[id] + } + + 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) + } + } + + 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) + } + this.insertBlockDecorationInsertionPointBeforeLine(id) + this.insertBlockDecorationInsertionPointAfterLine(id) + } + } + + removeBlockDecorationInsertionPointBeforeLine (id) { + const insertionPoint = this.insertionPointsBeforeLineById[id] + if (insertionPoint != null) { + this.domElementPool.freeElementAndDescendants(insertionPoint) + delete this.insertionPointsBeforeLineById[id] + } + } + + insertBlockDecorationInsertionPointBeforeLine (id) { + const {hasPrecedingBlockDecorations, screenRow} = this.newTileState.lines[id] + if (hasPrecedingBlockDecorations) { + const lineNode = this.lineNodesByLineId[id] + const insertionPoint = this.domElementPool.buildElement('content') + this.domNode.insertBefore(insertionPoint, lineNode) + this.insertionPointsBeforeLineById[id] = insertionPoint + insertionPoint.dataset.screenRow = screenRow + this.updateBlockDecorationInsertionPointBeforeLine(id) + } + } + + updateBlockDecorationInsertionPointBeforeLine (id) { + const oldLineState = this.oldTileState.lines[id] + const newLineState = this.newTileState.lines[id] + const insertionPoint = this.insertionPointsBeforeLineById[id] + if (insertionPoint != null) { + if (newLineState.screenRow !== oldLineState.screenRow) { + insertionPoint.dataset.screenRow = newLineState.screenRow + } + + const precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations + .map((d) => `.atom--block-decoration-${d.id}`) + .join(',') + if (precedingBlockDecorationsSelector !== oldLineState.precedingBlockDecorationsSelector) { + insertionPoint.setAttribute('select', precedingBlockDecorationsSelector) + oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector + } + } + } + + removeBlockDecorationInsertionPointAfterLine (id) { + const insertionPoint = this.insertionPointsAfterLineById[id] + if (insertionPoint != null) { + this.domElementPool.freeElementAndDescendants(insertionPoint) + delete this.insertionPointsAfterLineById[id] + } + } + + insertBlockDecorationInsertionPointAfterLine (id) { + const {hasFollowingBlockDecorations, screenRow} = this.newTileState.lines[id] + if (hasFollowingBlockDecorations) { + const lineNode = this.lineNodesByLineId[id] + const insertionPoint = this.domElementPool.buildElement('content') + this.domNode.insertBefore(insertionPoint, lineNode.nextSibling) + this.insertionPointsAfterLineById[id] = insertionPoint + insertionPoint.dataset.screenRow = screenRow + this.updateBlockDecorationInsertionPointAfterLine(id) + } + } + + updateBlockDecorationInsertionPointAfterLine (id) { + const oldLineState = this.oldTileState.lines[id] + const newLineState = this.newTileState.lines[id] + const insertionPoint = this.insertionPointsAfterLineById[id] + + if (insertionPoint != null) { + if (newLineState.screenRow !== oldLineState.screenRow) { + insertionPoint.dataset.screenRow = newLineState.screenRow + } + + const followingBlockDecorationsSelector = newLineState.followingBlockDecorations + .map((d) => `.atom--block-decoration-${d.id}`) + .join(',') + if (followingBlockDecorationsSelector !== oldLineState.followingBlockDecorationsSelector) { + insertionPoint.setAttribute('select', followingBlockDecorationsSelector) + oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector + } + } + } + + 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)) { + 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 (!oldLineState.hasPrecedingBlockDecorations && newLineState.hasPrecedingBlockDecorations) { + this.insertBlockDecorationInsertionPointBeforeLine(id) + } else if (oldLineState.hasPrecedingBlockDecorations && !newLineState.hasPrecedingBlockDecorations) { + this.removeBlockDecorationInsertionPointBeforeLine(id) + } + + if (!oldLineState.hasFollowingBlockDecorations && newLineState.hasFollowingBlockDecorations) { + this.insertBlockDecorationInsertionPointAfterLine(id) + } else if (oldLineState.hasFollowingBlockDecorations && !newLineState.hasFollowingBlockDecorations) { + this.removeBlockDecorationInsertionPointAfterLine(id) + } + + if (newLineState.screenRow !== oldLineState.screenRow) { + lineNode.dataset.screenRow = newLineState.screenRow + this.lineIdsByScreenRow[newLineState.screenRow] = id + this.screenRowsByLineId[id] = newLineState.screenRow + } + + this.updateBlockDecorationInsertionPointBeforeLine(id) + this.updateBlockDecorationInsertionPointAfterLine(id) + oldLineState.screenRow = newLineState.screenRow + oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations + oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations + } + + 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() + } + } +} From 3147a2ce3dda8030e6540ea70916a6bd3accd359 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 5 Oct 2016 16:31:58 +0200 Subject: [PATCH 15/88] Fix wrong variable name causing style.width to be constantly re-assigned --- src/lines-tile-component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lines-tile-component.js b/src/lines-tile-component.js index 0e98600ed..1ac94bc42 100644 --- a/src/lines-tile-component.js +++ b/src/lines-tile-component.js @@ -61,7 +61,7 @@ module.exports = class LinesTileComponent { if (this.newState.width !== this.oldState.width) { this.domNode.style.width = this.newState.width + 'px' - this.oldTileState.width = this.newTileState.width + this.oldState.width = this.newState.width } if (this.newTileState.top !== this.oldTileState.top || this.newTileState.left !== this.oldTileState.left) { From 35e45ef47218f14efe96254f70b1e31c5660e7a6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 10:35:16 +0200 Subject: [PATCH 16/88] Improve selector deprecation message Signed-off-by: Nathan Sobo --- src/style-manager.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/style-manager.js b/src/style-manager.js index 0b139514c..60c74c59a 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -265,15 +265,16 @@ function transformDeprecatedShadowDOMSelectors (css, context) { }) let deprecationMessage if (transformedSelectors.length > 0) { - deprecationMessage = 'Shadow DOM support for \`atom-text-editor\` elements has been deprecated. ' + deprecationMessage = 'The contents of `atom-text-editor` elements 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 stylesheets, Atom will automatically ' + 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 += 'Please, make sure to upgrade these selectors as soon as possible.' + 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} } From 9c5bddaa69d4ef60ac1dcfc2d5ec5f5b99aaaf65 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 10:26:09 +0200 Subject: [PATCH 17/88] Reimplement block decorations without the shadow DOM --- spec/text-editor-component-spec.js | 235 ++++---- spec/text-editor-presenter-spec.coffee | 527 ++++++++---------- src/block-decorations-component.coffee | 86 --- src/lines-component.coffee | 16 +- src/lines-tile-component.js | 273 +++++---- src/off-screen-block-decorations-component.js | 61 ++ src/text-editor-component.coffee | 15 +- src/text-editor-presenter.coffee | 75 ++- src/tiled-component.coffee | 15 +- 9 files changed, 637 insertions(+), 666 deletions(-) delete mode 100644 src/block-decorations-component.coffee create mode 100644 src/off-screen-block-decorations-component.js diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 5feb5761c..323ecf69e 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -405,49 +405,49 @@ describe('TextEditorComponent', function () { } }) - it('applies .syntax--leading-whitespace for lines with leading spaces and/or tabs', function () { + it('applies .leading-whitespace for lines with leading spaces and/or tabs', function () { editor.setText(' a') runAnimationFrames() let leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(false) editor.setText('\ta') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(false) }) - it('applies .syntax--trailing-whitespace for lines with trailing spaces and/or tabs', function () { + it('applies .trailing-whitespace for lines with trailing spaces and/or tabs', function () { editor.setText(' ') runAnimationFrames() let leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) editor.setText('\t') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) editor.setText('a ') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) editor.setText('a\t') runAnimationFrames() leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('syntax--trailing-whitespace')).toBe(true) - expect(leafNodes[0].classList.contains('syntax--leading-whitespace')).toBe(false) + expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe(true) + expect(leafNodes[0].classList.contains('leading-whitespace')).toBe(false) }) it('keeps rebuilding lines when continuous reflow is on', function () { @@ -501,14 +501,14 @@ describe('TextEditorComponent', function () { expect(component.lineNodeForScreenRow(0).textContent).toBe('' + invisibles.space + 'a line with tabs' + invisibles.tab + 'and spaces' + invisibles.space + invisibles.eol) let leafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) - expect(leafNodes[0].classList.contains('syntax--invisible-character')).toBe(true) - expect(leafNodes[leafNodes.length - 1].classList.contains('syntax--invisible-character')).toBe(true) + expect(leafNodes[0].classList.contains('invisible-character')).toBe(true) + expect(leafNodes[leafNodes.length - 1].classList.contains('invisible-character')).toBe(true) }) 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 () { @@ -543,20 +543,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 () { @@ -583,30 +583,30 @@ describe('TextEditorComponent', function () { runAnimationFrames() }) - it('adds an "syntax--indent-guide" class to spans comprising the leading whitespace', function () { + it('adds an "indent-guide" class to spans comprising the leading whitespace', function () { let line1LeafNodes = getLeafNodes(component.lineNodeForScreenRow(1)) expect(line1LeafNodes[0].textContent).toBe(' ') - expect(line1LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) - expect(line1LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(false) + expect(line1LeafNodes[0].classList.contains('indent-guide')).toBe(true) + expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe(false) let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) - expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(false) + expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) + expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(false) }) - it('renders leading whitespace spans with the "syntax--indent-guide" class for empty lines', function () { + it('renders leading whitespace spans with the "indent-guide" class for empty lines', function () { editor.getBuffer().insert([1, Infinity], '\n') runAnimationFrames() let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(2) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) }) it('renders indent guides correctly on lines containing only whitespace', function () { @@ -616,11 +616,11 @@ describe('TextEditorComponent', function () { let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(3) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) expect(line2LeafNodes[2].textContent).toBe(' ') - expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(true) }) it('renders indent guides correctly on lines containing only whitespace when invisibles are enabled', function () { @@ -638,11 +638,11 @@ describe('TextEditorComponent', function () { let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(4) expect(line2LeafNodes[0].textContent).toBe('--') - expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(true) expect(line2LeafNodes[1].textContent).toBe('--') - expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(true) expect(line2LeafNodes[2].textContent).toBe('--') - expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(true) + expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(true) expect(line2LeafNodes[3].textContent).toBe('x') }) @@ -653,9 +653,9 @@ describe('TextEditorComponent', function () { let line0LeafNodes = getLeafNodes(component.lineNodeForScreenRow(0)) expect(line0LeafNodes[0].textContent).toBe(' ') - expect(line0LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line0LeafNodes[0].classList.contains('indent-guide')).toBe(true) expect(line0LeafNodes[1].textContent).toBe(' ') - expect(line0LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(false) + expect(line0LeafNodes[1].classList.contains('indent-guide')).toBe(false) }) it('updates the indent guides on empty lines preceding an indentation change', function () { @@ -667,9 +667,9 @@ describe('TextEditorComponent', function () { let line12LeafNodes = getLeafNodes(component.lineNodeForScreenRow(12)) expect(line12LeafNodes[0].textContent).toBe(' ') - expect(line12LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line12LeafNodes[0].classList.contains('indent-guide')).toBe(true) expect(line12LeafNodes[1].textContent).toBe(' ') - expect(line12LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) + expect(line12LeafNodes[1].classList.contains('indent-guide')).toBe(true) }) it('updates the indent guides on empty lines following an indentation change', function () { @@ -682,9 +682,9 @@ describe('TextEditorComponent', function () { let line13LeafNodes = getLeafNodes(component.lineNodeForScreenRow(13)) expect(line13LeafNodes[0].textContent).toBe(' ') - expect(line13LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(true) + expect(line13LeafNodes[0].classList.contains('indent-guide')).toBe(true) expect(line13LeafNodes[1].textContent).toBe(' ') - expect(line13LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(true) + expect(line13LeafNodes[1].classList.contains('indent-guide')).toBe(true) }) }) @@ -701,11 +701,11 @@ describe('TextEditorComponent', function () { let line2LeafNodes = getLeafNodes(component.lineNodeForScreenRow(2)) expect(line2LeafNodes.length).toBe(3) expect(line2LeafNodes[0].textContent).toBe(' ') - expect(line2LeafNodes[0].classList.contains('syntax--indent-guide')).toBe(false) + expect(line2LeafNodes[0].classList.contains('indent-guide')).toBe(false) expect(line2LeafNodes[1].textContent).toBe(' ') - expect(line2LeafNodes[1].classList.contains('syntax--indent-guide')).toBe(false) + expect(line2LeafNodes[1].classList.contains('indent-guide')).toBe(false) expect(line2LeafNodes[2].textContent).toBe(' ') - expect(line2LeafNodes[2].classList.contains('syntax--indent-guide')).toBe(false) + expect(line2LeafNodes[2].classList.contains('indent-guide')).toBe(false) }) }) @@ -720,19 +720,19 @@ describe('TextEditorComponent', function () { describe('when there is a fold', function () { it('renders a fold marker on the folded line', function () { let foldedLineNode = component.lineNodeForScreenRow(4) - expect(foldedLineNode.querySelector('.syntax--fold-marker')).toBeFalsy() + expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy() editor.foldBufferRow(4) runAnimationFrames() foldedLineNode = component.lineNodeForScreenRow(4) - expect(foldedLineNode.querySelector('.syntax--fold-marker')).toBeTruthy() + expect(foldedLineNode.querySelector('.fold-marker')).toBeTruthy() editor.unfoldBufferRow(4) runAnimationFrames() foldedLineNode = component.lineNodeForScreenRow(4) - expect(foldedLineNode.querySelector('.syntax--fold-marker')).toBeFalsy() + expect(foldedLineNode.querySelector('.fold-marker')).toBeFalsy() }) }) }) @@ -1284,7 +1284,7 @@ describe('TextEditorComponent', function () { runAnimationFrames() let cursorRect = componentNode.querySelector('.cursor').getBoundingClientRect() - let foldMarkerRect = componentNode.querySelector('.syntax--fold-marker').getBoundingClientRect() + let foldMarkerRect = componentNode.querySelector('.fold-marker').getBoundingClientRect() expect(cursorRect.left).toBeCloseTo(foldMarkerRect.right, 0) }) @@ -1771,32 +1771,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) @@ -1804,24 +1804,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) @@ -1832,52 +1829,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; }', @@ -1885,42 +1901,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 () { @@ -1945,6 +1956,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 () { @@ -2872,20 +2895,20 @@ describe('TextEditorComponent', function () { editor.foldBufferRange([[4, 6], [4, 10]]) editor.foldBufferRange([[4, 15], [4, 20]]) runAnimationFrames() - debugger - let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') + + let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') expect(foldMarkers.length).toBe(2) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 6]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 15]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') expect(foldMarkers.length).toBe(0) expect(editor.isFoldedAtBufferRow(4)).toBe(false) }) @@ -2895,25 +2918,25 @@ describe('TextEditorComponent', function () { editor.foldBufferRange([[4, 4], [4, 5]]) editor.foldBufferRange([[4, 4], [4, 20]]) runAnimationFrames() - let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') + let foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 4]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 4]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') expect(foldMarkers.length).toBe(1) expect(editor.isFoldedAtBufferRow(4)).toBe(true) clickElementAtPosition(foldMarkers[0], [4, 10]) runAnimationFrames() - foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.syntax--fold-marker') + foldMarkers = component.lineNodeForScreenRow(4).querySelectorAll('.fold-marker') expect(foldMarkers.length).toBe(0) expect(editor.isFoldedAtBufferRow(4)).toBe(false) }) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 8cc5e5086..cbcebee25 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -479,7 +479,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 @@ -766,7 +766,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 @@ -922,7 +922,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 @@ -1274,7 +1274,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() @@ -1286,32 +1295,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]) @@ -1320,32 +1329,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() @@ -1353,71 +1362,85 @@ describe "TextEditorPresenter", -> blockDecoration1.getMarker().setHeadBufferPosition([0, 0]) runs -> - expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1]) - expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([blockDecoration2]) - expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration1])) + expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration2])) + expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual({}) waitsForStateToUpdate presenter, -> editor.setCursorBufferPosition([0, 0]) editor.insertNewline() runs -> - expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([blockDecoration1]) - expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([blockDecoration2]) - expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration1])) + expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration2])) + expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual({}) + expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual({}) - it "inserts block decorations before the line if not specified otherwise", -> + it "contains block decorations located in ::mouseWheelScreenRow even if they are off screen", -> + blockDecoration = addBlockDecorationBeforeScreenRow(0) + presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200) + lineId = presenter.displayLayer.getScreenLines(0, 1)[0].id + + expect(getState(presenter).content.tiles[0].lines[lineId].precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration])) + + presenter.setMouseWheelScreenRow(0) + expectStateUpdate presenter, -> presenter.setScrollTop(4) + expect(getState(presenter).content.tiles[0].lines[lineId].precedingBlockDecorations).toEqual(stateForBlockDecorations([blockDecoration])) + + advanceClock(presenter.stoppedScrollingDelay) + expect(getState(presenter).content.tiles[0]).toBeUndefined() + + it "inserts block decorations before the line unless otherwise specified", -> blockDecoration = editor.decorateMarker(editor.markScreenPosition([4, 0]), {type: "block"}) presenter = buildPresenter() - expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual [blockDecoration] - expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual stateForBlockDecorations([blockDecoration]) + expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual {} describe ".decorationClasses", -> it "adds decoration classes to the relevant line state objects, both initially and when decorations change", -> @@ -1734,12 +1757,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} @@ -2085,7 +2108,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}] @@ -2208,222 +2231,112 @@ describe "TextEditorPresenter", -> flashCount: 2 } - describe ".blockDecorations", -> - stateForBlockDecoration = (presenter, decoration) -> - getState(presenter).content.blockDecorations[decoration.id] + describe ".offScreenBlockDecorations", -> + stateForOffScreenBlockDecoration = (presenter, decoration) -> + getState(presenter).content.offScreenBlockDecorations[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) - - presenter.setScrollTop(100) - presenter.setMouseWheelScreenRow(0) - - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 0 - isVisible: true - } - - advanceClock(presenter.stoppedScrollingDelay) - - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - - it "invalidates block decorations that intersect a change in the buffer", -> - blockDecoration1 = addBlockDecorationBeforeScreenRow(9) - blockDecoration2 = addBlockDecorationBeforeScreenRow(10) - blockDecoration3 = addBlockDecorationBeforeScreenRow(11) - presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) - - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 9 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration2), { - decoration: blockDecoration2 - screenRow: 10 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 11 - isVisible: false - } - - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) - presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) - presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined() - expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined() - - editor.setSelectedScreenRange([[10, 0], [12, 0]]) - editor.delete() - presenter.setScrollTop(0) # deleting the buffer causes the editor to autoscroll - - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - expectValues stateForBlockDecoration(presenter, blockDecoration2), { - decoration: blockDecoration2 - screenRow: 10 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 10 - isVisible: false - } - - it "invalidates all block decorations when content frame width, window size or bounding client rect change", -> - blockDecoration1 = addBlockDecorationBeforeScreenRow(11) - presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) - - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } - - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - - presenter.setBoundingClientRect({top: 0, left: 0, width: 50, height: 30}) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } - - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - - presenter.setContentFrameWidth(100) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } - - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - - presenter.setWindowSize(100, 200) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 11 - isVisible: false - } - - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - - it "contains state for on-screen and unmeasured block decorations, both initially and when they are updated or destroyed", -> + it "contains state for off-screen unmeasured block decorations, both initially and when they are updated or destroyed", -> item = {} blockDecoration1 = addBlockDecorationBeforeScreenRow(0, item) blockDecoration2 = addBlockDecorationBeforeScreenRow(4, item) blockDecoration3 = addBlockDecorationBeforeScreenRow(4, item) blockDecoration4 = addBlockDecorationBeforeScreenRow(10, item) presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) - - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 0 - isVisible: true - } - expectValues stateForBlockDecoration(presenter, blockDecoration2), { - decoration: blockDecoration2 - screenRow: 4 - isVisible: true - } - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 4 - isVisible: true - } - expectValues stateForBlockDecoration(presenter, blockDecoration4), { - decoration: blockDecoration4 - screenRow: 10 - isVisible: false - } + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBe(blockDecoration4) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) presenter.setBlockDecorationDimensions(blockDecoration4, 0, 20) - - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 0 - isVisible: true - } - expectValues stateForBlockDecoration(presenter, blockDecoration2), { - decoration: blockDecoration2 - screenRow: 4 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 4 - isVisible: false - } - expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() - - blockDecoration3.getMarker().setHeadScreenPosition([5, 0]) - presenter.setScrollTop(90) - - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - expectValues stateForBlockDecoration(presenter, blockDecoration2), { - decoration: blockDecoration2 - screenRow: 4 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 5 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration4), { - decoration: blockDecoration4 - screenRow: 10 - isVisible: true - } + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBeUndefined() presenter.invalidateBlockDecorationDimensions(blockDecoration1) + presenter.invalidateBlockDecorationDimensions(blockDecoration4) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBe(blockDecoration4) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 0 - isVisible: false - } - expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined() - expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined() - expectValues stateForBlockDecoration(presenter, blockDecoration4), { - decoration: blockDecoration4 - screenRow: 10 - isVisible: true - } + blockDecoration4.destroy() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration4)).toBeUndefined() - blockDecoration1.destroy() + it "contains state for off-screen block decorations that intersect a buffer change", -> + blockDecoration1 = addBlockDecorationBeforeScreenRow(9) + blockDecoration2 = addBlockDecorationBeforeScreenRow(10) + blockDecoration3 = addBlockDecorationBeforeScreenRow(11) + presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3) - expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined() - expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined() - expectValues stateForBlockDecoration(presenter, blockDecoration4), { - decoration: blockDecoration4 - screenRow: 10 - isVisible: true - } + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) + presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBeUndefined() + + editor.setSelectedScreenRange([[10, 0], [12, 0]]) + editor.delete() + presenter.setScrollTop(0) # deleting the buffer causes the editor to autoscroll + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration3)).toBe(blockDecoration3) + + it "contains state for all off-screen block decorations when content frame width, window size or bounding client rect change", -> + blockDecoration1 = addBlockDecorationBeforeScreenRow(10) + blockDecoration2 = addBlockDecorationBeforeScreenRow(11) + presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + + presenter.setBoundingClientRect({top: 0, left: 0, width: 50, height: 30}) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + + presenter.setContentFrameWidth(100) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + + presenter.setWindowSize(100, 200) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBe(blockDecoration1) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBe(blockDecoration2) + + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expect(stateForOffScreenBlockDecoration(presenter, blockDecoration2)).toBeUndefined() it "doesn't throw an error when setting the dimensions for a destroyed decoration", -> blockDecoration = addBlockDecorationBeforeScreenRow(0) presenter = buildPresenter() - blockDecoration.destroy() presenter.setBlockDecorationDimensions(blockDecoration, 30, 30) - - expect(getState(presenter).content.blockDecorations).toEqual({}) + expect(getState(presenter).content.offScreenBlockDecorations).toEqual({}) describe ".overlays", -> [item] = [] diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee deleted file mode 100644 index 48bbf77f3..000000000 --- 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/lines-component.coffee b/src/lines-component.coffee index 0b19a0d43..d25b87b6b 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") @@ -57,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: {}} @@ -83,6 +91,10 @@ class LinesComponent extends TiledComponent @presenter.setLineHeight(lineHeightInPixels) @presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) + measureBlockDecorations: -> + for component in @getComponents() + component.measureBlockDecorations() + lineIdForScreenRow: (screenRow) -> tile = @presenter.tileForRow(screenRow) @getComponentForTile(tile)?.lineIdForScreenRow(screenRow) diff --git a/src/lines-tile-component.js b/src/lines-tile-component.js index 1ac94bc42..a05762fc5 100644 --- a/src/lines-tile-component.js +++ b/src/lines-tile-component.js @@ -2,18 +2,17 @@ const HighlightsComponent = require('./highlights-component') const ZERO_WIDTH_NBSP = '\ufeff' module.exports = class LinesTileComponent { - constructor ({presenter, id, domElementPool, assert}) { - this.presenter = presenter + constructor ({presenter, id, domElementPool, assert, views}) { this.id = id + this.presenter = presenter + this.views = views this.domElementPool = domElementPool this.assert = assert - this.measuredLines = new Set() this.lineNodesByLineId = {} this.screenRowsByLineId = {} this.lineIdsByScreenRow = {} this.textNodesByLineId = {} - this.insertionPointsBeforeLineById = {} - this.insertionPointsAfterLineById = {} + this.blockDecorationNodesByLineIdAndDecorationId = {} this.domNode = this.domElementPool.buildElement('div') this.domNode.style.position = 'absolute' this.domNode.style.display = 'block' @@ -22,6 +21,7 @@ module.exports = class LinesTileComponent { } destroy () { + this.removeLineNodes() this.domElementPool.freeElementAndDescendants(this.domNode) } @@ -80,15 +80,29 @@ module.exports = class LinesTileComponent { } } - removeLineNode (id) { - this.domElementPool.freeElementAndDescendants(this.lineNodesByLineId[id]) - this.removeBlockDecorationInsertionPointBeforeLine(id) - this.removeBlockDecorationInsertionPointAfterLine(id) - delete this.lineNodesByLineId[id] - delete this.textNodesByLineId[id] - delete this.lineIdsByScreenRow[this.screenRowsByLineId[id]] - delete this.screenRowsByLineId[id] - delete this.oldTileState.lines[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 () { @@ -110,6 +124,10 @@ module.exports = class LinesTileComponent { 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 = {} } } @@ -123,87 +141,6 @@ module.exports = class LinesTileComponent { } else { this.domNode.insertBefore(lineNode, nextNode) } - this.insertBlockDecorationInsertionPointBeforeLine(id) - this.insertBlockDecorationInsertionPointAfterLine(id) - } - } - - removeBlockDecorationInsertionPointBeforeLine (id) { - const insertionPoint = this.insertionPointsBeforeLineById[id] - if (insertionPoint != null) { - this.domElementPool.freeElementAndDescendants(insertionPoint) - delete this.insertionPointsBeforeLineById[id] - } - } - - insertBlockDecorationInsertionPointBeforeLine (id) { - const {hasPrecedingBlockDecorations, screenRow} = this.newTileState.lines[id] - if (hasPrecedingBlockDecorations) { - const lineNode = this.lineNodesByLineId[id] - const insertionPoint = this.domElementPool.buildElement('content') - this.domNode.insertBefore(insertionPoint, lineNode) - this.insertionPointsBeforeLineById[id] = insertionPoint - insertionPoint.dataset.screenRow = screenRow - this.updateBlockDecorationInsertionPointBeforeLine(id) - } - } - - updateBlockDecorationInsertionPointBeforeLine (id) { - const oldLineState = this.oldTileState.lines[id] - const newLineState = this.newTileState.lines[id] - const insertionPoint = this.insertionPointsBeforeLineById[id] - if (insertionPoint != null) { - if (newLineState.screenRow !== oldLineState.screenRow) { - insertionPoint.dataset.screenRow = newLineState.screenRow - } - - const precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations - .map((d) => `.atom--block-decoration-${d.id}`) - .join(',') - if (precedingBlockDecorationsSelector !== oldLineState.precedingBlockDecorationsSelector) { - insertionPoint.setAttribute('select', precedingBlockDecorationsSelector) - oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector - } - } - } - - removeBlockDecorationInsertionPointAfterLine (id) { - const insertionPoint = this.insertionPointsAfterLineById[id] - if (insertionPoint != null) { - this.domElementPool.freeElementAndDescendants(insertionPoint) - delete this.insertionPointsAfterLineById[id] - } - } - - insertBlockDecorationInsertionPointAfterLine (id) { - const {hasFollowingBlockDecorations, screenRow} = this.newTileState.lines[id] - if (hasFollowingBlockDecorations) { - const lineNode = this.lineNodesByLineId[id] - const insertionPoint = this.domElementPool.buildElement('content') - this.domNode.insertBefore(insertionPoint, lineNode.nextSibling) - this.insertionPointsAfterLineById[id] = insertionPoint - insertionPoint.dataset.screenRow = screenRow - this.updateBlockDecorationInsertionPointAfterLine(id) - } - } - - updateBlockDecorationInsertionPointAfterLine (id) { - const oldLineState = this.oldTileState.lines[id] - const newLineState = this.newTileState.lines[id] - const insertionPoint = this.insertionPointsAfterLineById[id] - - if (insertionPoint != null) { - if (newLineState.screenRow !== oldLineState.screenRow) { - insertionPoint.dataset.screenRow = newLineState.screenRow - } - - const followingBlockDecorationsSelector = newLineState.followingBlockDecorations - .map((d) => `.atom--block-decoration-${d.id}`) - .join(',') - if (followingBlockDecorationsSelector !== oldLineState.followingBlockDecorationsSelector) { - insertionPoint.setAttribute('select', followingBlockDecorationsSelector) - oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector - } } } @@ -296,29 +233,143 @@ module.exports = class LinesTileComponent { oldLineState.decorationClasses = newLineState.decorationClasses - if (!oldLineState.hasPrecedingBlockDecorations && newLineState.hasPrecedingBlockDecorations) { - this.insertBlockDecorationInsertionPointBeforeLine(id) - } else if (oldLineState.hasPrecedingBlockDecorations && !newLineState.hasPrecedingBlockDecorations) { - this.removeBlockDecorationInsertionPointBeforeLine(id) - } - - if (!oldLineState.hasFollowingBlockDecorations && newLineState.hasFollowingBlockDecorations) { - this.insertBlockDecorationInsertionPointAfterLine(id) - } else if (oldLineState.hasFollowingBlockDecorations && !newLineState.hasFollowingBlockDecorations) { - this.removeBlockDecorationInsertionPointAfterLine(id) - } - if (newLineState.screenRow !== oldLineState.screenRow) { lineNode.dataset.screenRow = newLineState.screenRow this.lineIdsByScreenRow[newLineState.screenRow] = id this.screenRowsByLineId[id] = newLineState.screenRow } - this.updateBlockDecorationInsertionPointBeforeLine(id) - this.updateBlockDecorationInsertionPointAfterLine(id) oldLineState.screenRow = newLineState.screenRow - oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations - oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations + } + + removeDeletedBlockDecorations () { + 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] + 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) { diff --git a/src/off-screen-block-decorations-component.js b/src/off-screen-block-decorations-component.js new file mode 100644 index 000000000..d651211be --- /dev/null +++ b/src/off-screen-block-decorations-component.js @@ -0,0 +1,61 @@ +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.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/text-editor-component.coffee b/src/text-editor-component.coffee index 60033dc88..a3fac1e81 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 = @@ -69,7 +69,6 @@ class TextEditorComponent @domNode.classList.add('editor--private') @overlayManager = new OverlayManager(@presenter, @domNode, @views) - @blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool) @scrollViewNode = document.createElement('div') @scrollViewNode.classList.add('scroll-view') @@ -78,10 +77,11 @@ class TextEditorComponent @hiddenInputComponent = new InputComponent @scrollViewNode.appendChild(@hiddenInputComponent.getDomNode()) - @linesComponent = new LinesComponent({@presenter, @hostElement, @domElementPool, @assert, @grammars}) + @linesComponent = new LinesComponent({@presenter, @hostElement, @domElementPool, @assert, @grammars, @views}) @scrollViewNode.appendChild(@linesComponent.getDomNode()) - @linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode()) + @offScreenBlockDecorationsComponent = new OffScreenBlockDecorationsComponent({@presenter, @views}) + @scrollViewNode.appendChild(@offScreenBlockDecorationsComponent.getDomNode()) @linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex) @presenter.setLinesYardstick(@linesYardstick) @@ -165,8 +165,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 +186,8 @@ class TextEditorComponent readAfterUpdateSync: => @overlayManager?.measureOverlays() - @blockDecorationsComponent?.measureBlockDecorations() if @isVisible() + @linesComponent.measureBlockDecorations() + @offScreenBlockDecorationsComponent.measureBlockDecorations() mountGutterContainerComponent: -> @gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views}) @@ -543,7 +544,7 @@ class TextEditorComponent screenPosition = @screenPositionForMouseEvent(event) - if event.target?.classList.contains('syntax--fold-marker') + if event.target?.classList.contains('fold-marker') bufferPosition = @editor.bufferPositionForScreenPosition(screenPosition) @editor.destroyFoldsIntersectingBufferRange([bufferPosition, bufferPosition]) return diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 90de3fd91..3a5078f26 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -34,6 +34,8 @@ class TextEditorPresenter @observedBlockDecorations = new Set() @invalidatedDimensionsByBlockDecoration = new Set() @invalidateAllBlockDecorationsDimensions = false + @precedingBlockDecorationsByScreenRowAndId = {} + @followingBlockDecorationsByScreenRowAndId = {} @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -191,7 +193,7 @@ class TextEditorPresenter highlights: {} overlays: {} cursors: {} - blockDecorations: {} + offScreenBlockDecorations: {} gutters: [] # Shared state that is copied into ``@state.gutters`. @sharedGutterStyles = {} @@ -412,16 +414,14 @@ class TextEditorPresenter throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}") 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 @@ -430,8 +430,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) @@ -1059,41 +1057,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 = {} @@ -1295,7 +1294,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 @@ -1332,7 +1331,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) @@ -1346,14 +1345,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 2e8dc7149..37de27e9b 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()) From ae942b87baecc5f0e121fbc91dcfef6c81dfd651 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 11:50:42 +0200 Subject: [PATCH 18/88] Put back missing comment that was accidentally deleted with decaf --- src/lines-tile-component.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lines-tile-component.js b/src/lines-tile-component.js index a05762fc5..6053cea19 100644 --- a/src/lines-tile-component.js +++ b/src/lines-tile-component.js @@ -199,6 +199,9 @@ module.exports = class LinesTileComponent { } 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) From e94ea55d900961fc87ac2012fbc1f12d66238f30 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 12:36:17 +0200 Subject: [PATCH 19/88] :art: --- src/lines-component.coffee | 1 + src/tiled-component.coffee | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index d25b87b6b..6c9271179 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -94,6 +94,7 @@ class LinesComponent extends TiledComponent measureBlockDecorations: -> for component in @getComponents() component.measureBlockDecorations() + return lineIdForScreenRow: (screenRow) -> tile = @presenter.tileForRow(screenRow) diff --git a/src/tiled-component.coffee b/src/tiled-component.coffee index 37de27e9b..d44800b2e 100644 --- a/src/tiled-component.coffee +++ b/src/tiled-component.coffee @@ -44,8 +44,7 @@ class TiledComponent @componentsByTileId[tileRow] getComponents: -> - for _, component of @componentsByTileId - component + Object.values(@componentsByTileId) getTiles: -> @getComponents().map((component) -> component.getDomNode()) From 91df848b8ae276871f45fa6055ae963725a85463 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 13:30:58 +0200 Subject: [PATCH 20/88] Keep a backward compatible DOM structure for `atom-text-editor` contents --- spec/text-editor-component-spec.js | 4 +- src/off-screen-block-decorations-component.js | 1 + src/text-editor-component.coffee | 4 -- src/text-editor-element.coffee | 15 +++++++- static/text-editor-light.less | 37 +++++++++---------- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 323ecf69e..95740b7f5 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -4451,7 +4451,7 @@ describe('TextEditorComponent', function () { jasmine.attachToDOM(element) expect(element.offsetHeight).toBe(200) - expect(element.querySelector('.editor-contents').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/) }) @@ -4474,7 +4474,7 @@ describe('TextEditorComponent', function () { element.component.measureDimensions() expect(element.offsetHeight).toBe(200) - expect(element.querySelector('.editor-contents').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/src/off-screen-block-decorations-component.js b/src/off-screen-block-decorations-component.js index d651211be..0460c854e 100644 --- a/src/off-screen-block-decorations-component.js +++ b/src/off-screen-block-decorations-component.js @@ -6,6 +6,7 @@ module.exports = class OffScreenBlockDecorationsComponent { this.oldState = {offScreenBlockDecorations: {}, width: 0} this.domNode = document.createElement('div') this.domNode.style.visibility = 'hidden' + this.domNode.style.position = 'absolute' this.blockDecorationNodesById = {} } diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index a3fac1e81..1cd6db916 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -62,11 +62,7 @@ class TextEditorComponent @domElementPool = new DOMElementPool @domNode = document.createElement('div') - @domNode.classList.add('editor-contents') - # TODO: Remove these backwards compatible classes once everyone has - # transitioned to non-shadow DOM selectors. @domNode.classList.add('editor-contents--private') - @domNode.classList.add('editor--private') @overlayManager = new OverlayManager(@presenter, @domNode, @views) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index fe45b99b9..1770f7bac 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -30,7 +30,18 @@ class TextEditorElement extends HTMLElement @setAttribute('tabindex', -1) initializeContent: (attributes) -> - @rootElement = this + 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') + @appendChild(@rootElement) attachedCallback: -> @buildModel() unless @getModel()? @@ -106,7 +117,7 @@ class TextEditorElement extends HTMLElement workspace: @workspace assert: @assert ) - @appendChild(@component.getDomNode()) + @rootElement.appendChild(@component.getDomNode()) inputNode = @component.hiddenInputComponent.getDomNode() inputNode.addEventListener 'focus', @focused.bind(this) inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false)) diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 193749d51..178f62d1d 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; -} - -atom-text-editor { - display: flex; - - .editor-contents { + .editor--private, .editor-contents--private { height: 100%; width: 100%; - overflow: hidden; + } + + .editor-contents--private { + width: 100%; cursor: text; display: flex; -webkit-user-select: none; @@ -218,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; +} From abef1f25f9101183c7b73eaaf22ea872dd815c3a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 15:03:06 +0200 Subject: [PATCH 21/88] Exclude `folded` from deprecated syntax selectors --- src/deprecated-syntax-selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deprecated-syntax-selectors.js b/src/deprecated-syntax-selectors.js index 8b798bd4c..4f8b9cefc 100644 --- a/src/deprecated-syntax-selectors.js +++ b/src/deprecated-syntax-selectors.js @@ -319,7 +319,7 @@ module.exports = new Set([ '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', 'folded', 'folder', 'folder-actions', 'following', 'font', + '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', From 51e186b65618c5f7f566d4672611e2378b898421 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 15:51:38 +0200 Subject: [PATCH 22/88] Delete leftover ::shadow pseudo-selectors --- dot-atom/styles.less | 4 ++-- static/cursors.less | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dot-atom/styles.less b/dot-atom/styles.less index a321469e7..e4fad4f31 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/static/cursors.less b/static/cursors.less index b2807217e..0b54c6ea1 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(); } } From 1091b0eb60f489e2c70f8a0876b215c07664f581 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 16:43:44 +0200 Subject: [PATCH 23/88] Fix decorations flashing more than once When, after flashing a decoration, the decorated range moved, Atom was showing an additional flash, even if the previous one had already been consumed. This bug originated in `HighlightsComponent`, where we maintained state about a certain highlight's flash count. The problem with this approach, however, is that highlight objects in the component are very volatile, and we could even have more than one for a single decoration (i.e. when such decoration spans multiple tiles). To fix this, we'll now maintain some additional state in `TextEditorPresenter`, which will set a `needsFlash` attribute on the highlight state objects, thereby preventing `HighlightsComponent` from showing the flash animation more than once when the decorated range changes. --- spec/text-editor-presenter-spec.coffee | 9 +++++++ src/highlights-component.coffee | 33 +++++++++++++------------- src/text-editor-presenter.coffee | 5 ++++ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index cbcebee25..3229fd3b4 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2208,11 +2208,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 @@ -2221,16 +2223,23 @@ 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 } + waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 2], [6, 2]]) + runs -> + expectValues stateForHighlightInTile(presenter, highlight, 2), {needsFlash: false} + expectValues stateForHighlightInTile(presenter, highlight, 4), {needsFlash: false} + describe ".offScreenBlockDecorations", -> stateForOffScreenBlockDecoration = (presenter, decoration) -> getState(presenter).content.offScreenBlockDecorations[decoration.id] diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index a6e85b7e5..e5c1db60e 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 + if newHighlightState.needsFlash and oldHighlightState.flashCount isnt newHighlightState.flashCount + highlightNode = @highlightNodesById[id] - highlightNode = @highlightNodesById[id] + addFlashClass = => + highlightNode.classList.add(newHighlightState.flashClass) + oldHighlightState.flashClass = newHighlightState.flashClass + @flashTimeoutId = setTimeout(removeFlashClass, newHighlightState.flashDuration) - addFlashClass = => - highlightNode.classList.add(newHighlightState.flashClass) - oldHighlightState.flashClass = newHighlightState.flashClass - @flashTimeoutId = setTimeout(removeFlashClass, newHighlightState.flashDuration) + removeFlashClass = => + highlightNode.classList.remove(oldHighlightState.flashClass) + oldHighlightState.flashClass = null + clearTimeout(@flashTimeoutId) - removeFlashClass = => - highlightNode.classList.remove(oldHighlightState.flashClass) - oldHighlightState.flashClass = null - clearTimeout(@flashTimeoutId) + if oldHighlightState.flashClass? + removeFlashClass() + requestAnimationFrame(addFlashClass) + else + addFlashClass() - if oldHighlightState.flashClass? - removeFlashClass() - requestAnimationFrame(addFlashClass) - else - addFlashClass() - - oldHighlightState.flashCount = newHighlightState.flashCount + oldHighlightState.flashCount = newHighlightState.flashCount diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 3a5078f26..b87a958f6 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -37,6 +37,7 @@ class TextEditorPresenter @precedingBlockDecorationsByScreenRowAndId = {} @followingBlockDecorationsByScreenRowAndId = {} @screenRowsToMeasure = [] + @flashCountsByDecorationId = {} @transferMeasurementsToModel() @transferMeasurementsFromModel() @observeModel() @@ -1179,6 +1180,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) @@ -1188,6 +1192,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 From 4db895c731e688b57c822e15a2913fe7a3f89406 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 19:18:04 +0200 Subject: [PATCH 24/88] Transform also more complex `atom-text-editor` selectors --- spec/style-manager-spec.js | 33 +++++++++++++++++++-------------- src/style-manager.js | 15 ++++++++++----- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/spec/style-manager-spec.js b/spec/style-manager-spec.js index 65fc24e9a..fdec350af 100644 --- a/spec/style-manager-spec.js +++ b/spec/style-manager-spec.js @@ -34,20 +34,23 @@ describe('StyleManager', () => { }) 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 } - `) - 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' - ]) - } - ) + 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--"', () => { @@ -67,11 +70,13 @@ describe('StyleManager', () => { 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' ]) }) diff --git a/src/style-manager.js b/src/style-manager.js index 60c74c59a..27b680ad0 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -239,22 +239,27 @@ function transformDeprecatedShadowDOMSelectors (css, context) { 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.startsWith('syntax--')) { + if (DEPRECATED_SYNTAX_SELECTORS.has(node.value)) { node.value = `syntax--${node.value}` } - } else if (previousNode) { - const currentNodeIsShadowPseudoClass = node.type === 'pseudo' && node.value === '::shadow' - const previousNodeIsAtomTextEditor = previousNode.type === 'tag' && previousNode.value === 'atom-text-editor' - if (previousNodeIsAtomTextEditor && currentNodeIsShadowPseudoClass) { + } 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 From 07d56b23f0911b743e4c3606fd1de44172d72c58 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 6 Oct 2016 20:06:37 +0200 Subject: [PATCH 25/88] Cache style sheet deprecated selectors transformations in StyleManager --- spec/style-manager-spec.js | 6 ++---- src/style-manager.js | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/spec/style-manager-spec.js b/spec/style-manager-spec.js index fdec350af..898e85587 100644 --- a/spec/style-manager-spec.js +++ b/spec/style-manager-spec.js @@ -14,7 +14,7 @@ describe('StyleManager', () => { }) describe('::addStyleSheet(source, params)', () => { - it('adds a stylesheet based on the given source and returns a disposable allowing it to be removed', () => { + 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}') @@ -82,14 +82,12 @@ describe('StyleManager', () => { }) }) - it('replaces ":host" with "atom-text-editor" only when the context of a stylesheet is "atom-text-editor"', () => { + 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' ]) - global.debug = true styleManager.addStyleSheet(':host .class-1, :host .class-2 { color: red; }', {context: 'atom-text-editor'}) - global.debug = false expect(Array.from(styleManager.getStyleElements()[1].sheet.cssRules).map((r) => r.selectorText)).toEqual([ 'atom-text-editor .class-1, atom-text-editor .class-2' ]) diff --git a/src/style-manager.js b/src/style-manager.js index 27b680ad0..d5937c058 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -1,4 +1,5 @@ const {Emitter, Disposable} = require('event-kit') +const crypto = require('crypto') const fs = require('fs-plus') const path = require('path') const postcss = require('postcss') @@ -14,6 +15,7 @@ const DEPRECATED_SYNTAX_SELECTORS = require('./deprecated-syntax-selectors') module.exports = class StyleManager { constructor ({configDirPath}) { this.configDirPath = configDirPath + this.cacheDirPath = path.join(this.configDirPath, 'compile-cache', 'style-manager') this.emitter = new Emitter() this.styleElements = [] this.styleElementsBySourcePath = {} @@ -130,7 +132,20 @@ module.exports = class StyleManager { } } - const transformed = transformDeprecatedShadowDOMSelectors(source, params.context) + 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')) + let transformed + try { + transformed = JSON.parse(fs.readFileSync(cacheFilePath)) + } catch (e) { + transformed = transformDeprecatedShadowDOMSelectors(source, params.context) + fs.writeFileSync(cacheFilePath, JSON.stringify(transformed)) + } + styleElement.textContent = transformed.source if (transformed.deprecationMessage) { this.deprecationsBySourcePath[params.sourcePath] = {message: transformed.deprecationMessage} From 901b0b17a31629e097687d516dd220451462f463 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Oct 2016 10:54:07 +0200 Subject: [PATCH 26/88] Fix failing tests --- spec/lines-yardstick-spec.coffee | 4 +-- spec/text-editor-element-spec.coffee | 2 -- spec/text-editor-spec.coffee | 46 ++++++++++++++-------------- spec/tokenized-buffer-spec.coffee | 44 +++++++++++++------------- src/style-manager.js | 28 ++++++++++------- src/text-editor-component.coffee | 2 +- src/text-editor-element.coffee | 1 + src/tiled-component.coffee | 3 +- src/tokenized-buffer-iterator.js | 8 +++-- 9 files changed, 74 insertions(+), 64 deletions(-) diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index ed50451e7..79693a8e6 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -68,7 +68,7 @@ describe "LinesYardstick", -> font-size: 12px; font-family: monospace; } - .function { + .syntax--function { font-size: 16px } """ @@ -142,7 +142,7 @@ describe "LinesYardstick", -> font-size: 12px; font-family: monospace; } - .function { + .syntax--function { font-size: 16px } """ diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee index f11baaf58..d0cfc199e 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -1,8 +1,6 @@ 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 diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 2c4ba084f..bae66a054 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -5322,8 +5322,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 -> @@ -5332,9 +5332,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", -> @@ -5347,8 +5347,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 -> @@ -5357,8 +5357,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 -> @@ -5367,14 +5367,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()", -> @@ -5809,20 +5809,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-spec.coffee b/spec/tokenized-buffer-spec.coffee index ad9fa0ee7..f1d2f2c0f 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -610,21 +610,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 @@ -637,16 +637,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", -> @@ -699,5 +699,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/style-manager.js b/src/style-manager.js index d5937c058..9f3385482 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -15,7 +15,9 @@ const DEPRECATED_SYNTAX_SELECTORS = require('./deprecated-syntax-selectors') module.exports = class StyleManager { constructor ({configDirPath}) { this.configDirPath = configDirPath - this.cacheDirPath = path.join(this.configDirPath, 'compile-cache', 'style-manager') + if (this.configDirPath != null) { + this.cacheDirPath = path.join(this.configDirPath, 'compile-cache', 'style-manager') + } this.emitter = new Emitter() this.styleElements = [] this.styleElementsBySourcePath = {} @@ -132,18 +134,22 @@ module.exports = class StyleManager { } } - 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')) let transformed - try { - transformed = JSON.parse(fs.readFileSync(cacheFilePath)) - } catch (e) { + 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) - fs.writeFileSync(cacheFilePath, JSON.stringify(transformed)) } styleElement.textContent = transformed.source diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 1cd6db916..cccd2f4c8 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -73,7 +73,7 @@ class TextEditorComponent @hiddenInputComponent = new InputComponent @scrollViewNode.appendChild(@hiddenInputComponent.getDomNode()) - @linesComponent = new LinesComponent({@presenter, @hostElement, @domElementPool, @assert, @grammars, @views}) + @linesComponent = new LinesComponent({@presenter, @domElementPool, @assert, @grammars, @views}) @scrollViewNode.appendChild(@linesComponent.getDomNode()) @offScreenBlockDecorationsComponent = new OffScreenBlockDecorationsComponent({@presenter, @views}) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 1770f7bac..230813cb5 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -1,3 +1,4 @@ +Grim = require 'grim' {Emitter, CompositeDisposable} = require 'event-kit' TextBuffer = require 'text-buffer' TextEditorComponent = require './text-editor-component' diff --git a/src/tiled-component.coffee b/src/tiled-component.coffee index d44800b2e..37de27e9b 100644 --- a/src/tiled-component.coffee +++ b/src/tiled-component.coffee @@ -44,7 +44,8 @@ class TiledComponent @componentsByTileId[tileRow] getComponents: -> - Object.values(@componentsByTileId) + for _, component of @componentsByTileId + component getTiles: -> @getComponents().map((component) -> component.getDomNode()) diff --git a/src/tokenized-buffer-iterator.js b/src/tokenized-buffer-iterator.js index 3e6822670..29d2fdf86 100644 --- a/src/tokenized-buffer-iterator.js +++ b/src/tokenized-buffer-iterator.js @@ -164,7 +164,11 @@ module.exports = class TokenizedBufferIterator { } scopeForId (id) { - const scope = this.tokenizedBuffer.grammar.scopeForId(id).replace(/\./g, '.syntax--') - return `syntax--${scope}` + const scope = this.tokenizedBuffer.grammar.scopeForId(id) + if (scope) { + return `syntax--${scope.replace(/\./g, '.syntax--')}` + } else { + return null + } } } From d48c1e96d04ff1a6b875a890e9d22276d727ed09 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Oct 2016 15:42:21 +0200 Subject: [PATCH 27/88] Tweak wording in TextEditorElement spec --- spec/text-editor-element-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee index d0cfc199e..67255a1dc 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -78,7 +78,7 @@ describe "TextEditorElement", -> 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) From e4274e57ed62e7a524446606c7d09a923b53f92e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 7 Oct 2016 16:10:06 +0200 Subject: [PATCH 28/88] Include Atom version in deprecation message --- src/style-manager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/style-manager.js b/src/style-manager.js index 9f3385482..4175278c3 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -291,7 +291,8 @@ function transformDeprecatedShadowDOMSelectors (css, context) { }) let deprecationMessage if (transformedSelectors.length > 0) { - deprecationMessage = 'The contents of `atom-text-editor` elements are no longer encapsulated within a shadow DOM boundary. ' + 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 ' From 1e3443e6c1a6a0fb94d04f340a1c0357c10b3183 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 6 Oct 2016 16:52:21 -0700 Subject: [PATCH 29/88] Avoid forcing computation of all screen lines when opening a file --- package.json | 2 +- src/lines-yardstick.coffee | 19 ++++++++-------- src/text-editor-presenter.coffee | 37 +++++++++++++++++--------------- src/text-editor.coffee | 13 +++++++++-- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index cce3707a0..ab9b078e0 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "9.3.0", + "text-buffer": "9.3.1-1", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 551733a55..563cd5923 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -13,19 +13,20 @@ class LinesYardstick measuredRowForPixelPosition: (pixelPosition) -> targetTop = pixelPosition.top row = Math.floor(targetTop / @model.getLineHeightInPixels()) - row if 0 <= row <= @model.getLastScreenRow() + row if 0 <= row screenPositionForPixelPosition: (pixelPosition) -> targetTop = pixelPosition.top - targetLeft = pixelPosition.left - row = @lineTopIndex.rowForPixelPosition(targetTop) - targetLeft = 0 if targetTop < 0 or targetLeft < 0 - targetLeft = Infinity if row > @model.getLastScreenRow() - row = Math.min(row, @model.getLastScreenRow()) - row = Math.max(0, row) - + row = Math.max(0, @lineTopIndex.rowForPixelPosition(targetTop)) lineNode = @lineNodesProvider.lineNodeForScreenRow(row) - return Point(row, 0) unless lineNode + unless lineNode + if row > @model.getLastScreenRow() + return Point(@model.getLastScreenRow(), Infinity) + else + return Point(row, 0) + + targetLeft = pixelPosition.left + targetLeft = 0 if targetTop < 0 or targetLeft < 0 textNodes = @lineNodesProvider.textNodesForScreenRow(row) lineOffset = lineNode.getBoundingClientRect().left diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 90de3fd91..580229b7a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -137,6 +137,11 @@ class TextEditorPresenter @shouldUpdateDecorations = true observeModel: -> + @disposables.add @model.displayLayer.onDidReset => + @spliceBlockDecorationsInRange(0, Infinity, Infinity) + @shouldUpdateDecorations = true + @emitDidUpdateState() + @disposables.add @model.displayLayer.onDidChangeSync (changes) => for change in changes startRow = change.start.row @@ -291,24 +296,21 @@ class TextEditorPresenter tileForRow: (row) -> row - (row % @tileSize) - constrainRow: (row) -> - Math.max(0, Math.min(row, @model.getScreenLineCount())) - getStartTileRow: -> - @constrainRow(@tileForRow(@startRow ? 0)) + @tileForRow(@startRow ? 0) getEndTileRow: -> - @constrainRow(@tileForRow(@endRow ? 0)) + @tileForRow(@endRow ? 0) isValidScreenRow: (screenRow) -> - screenRow >= 0 and screenRow < @model.getScreenLineCount() + screenRow >= 0 and screenRow < @model.getApproximateScreenLineCount() getScreenRowsToRender: -> startRow = @getStartTileRow() - endRow = @constrainRow(@getEndTileRow() + @tileSize) + endRow = @getEndTileRow() + @tileSize screenRows = [startRow...endRow] - longestScreenRow = @model.getLongestScreenRow() + longestScreenRow = @model.getApproximateLongestScreenRow() if longestScreenRow? screenRows.push(longestScreenRow) if @screenRowsToMeasure? @@ -354,7 +356,7 @@ class TextEditorPresenter zIndex = 0 for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize - tileEndRow = @constrainRow(tileStartRow + @tileSize) + tileEndRow = tileStartRow + @tileSize rowsWithinTile = [] while screenRowIndex >= 0 @@ -389,7 +391,7 @@ class TextEditorPresenter visibleTiles[tileStartRow] = true zIndex++ - if @mouseWheelScreenRow? and 0 <= @mouseWheelScreenRow < @model.getScreenLineCount() + if @mouseWheelScreenRow? and 0 <= @mouseWheelScreenRow < @model.getApproximateScreenLineCount() mouseWheelTile = @tileForRow(@mouseWheelScreenRow) unless visibleTiles[mouseWheelTile]? @@ -408,8 +410,7 @@ class TextEditorPresenter visibleLineIds = {} for screenRow in screenRows line = @linesByScreenRow.get(screenRow) - unless line? - throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}") + continue unless line? visibleLineIds[line.id] = true precedingBlockDecorations = @precedingBlockDecorationsByScreenRow[screenRow] ? [] @@ -597,7 +598,9 @@ class TextEditorPresenter visibleLineNumberIds = {} for screenRow in screenRows when @isRowRendered(screenRow) - lineId = @linesByScreenRow.get(screenRow).id + line = @linesByScreenRow.get(screenRow) + continue unless line? + lineId = line.id {bufferRow, softWrappedAtStart: softWrapped} = @displayLayer.softWrapDescriptorForScreenRow(screenRow) foldable = not softWrapped and @model.isFoldableAtBufferRow(bufferRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) @@ -624,7 +627,7 @@ class TextEditorPresenter return unless @scrollTop? and @lineHeight? and @height? @endRow = Math.min( - @model.getScreenLineCount(), + @model.getApproximateScreenLineCount(), @lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight - 1) + 1 ) @@ -658,7 +661,7 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getScreenLineCount())) + @contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getApproximateScreenLineCount())) if @contentHeight isnt oldContentHeight @updateHeight() @@ -668,7 +671,7 @@ class TextEditorPresenter updateHorizontalDimensions: -> if @baseCharacterWidth? oldContentWidth = @contentWidth - rightmostPosition = @model.getRightmostScreenPosition() + rightmostPosition = @model.getApproximateRightmostScreenPosition() @contentWidth = @pixelPositionForScreenPosition(rightmostPosition).left @contentWidth += @scrollLeft @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width @@ -1529,7 +1532,7 @@ class TextEditorPresenter [@startRow, @endRow] isRowRendered: (row) -> - @getStartTileRow() <= row < @constrainRow(@getEndTileRow() + @tileSize) + @getStartTileRow() <= row < @getEndTileRow() + @tileSize isOpenTagCode: (tagCode) -> @displayLayer.isOpenTagCode(tagCode) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 034b9e8f0..ce08d01a8 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -391,6 +391,9 @@ class TextEditor extends Model @disposables.add @displayLayer.onDidChangeSync (e) => @mergeIntersectingSelections() @emitter.emit 'did-change', e + @disposables.add @displayLayer.onDidReset => + @mergeIntersectingSelections() + @emitter.emit 'did-change', {} destroyed: -> @disposables.dispose() @@ -907,6 +910,8 @@ class TextEditor extends Model # editor. This accounts for folds. getScreenLineCount: -> @displayLayer.getScreenLineCount() + getApproximateScreenLineCount: -> @displayLayer.getApproximateScreenLineCount() + # Essential: Returns a {Number} representing the last zero-indexed buffer row # number of the editor. getLastBufferRow: -> @buffer.getLastRow() @@ -953,8 +958,8 @@ class TextEditor extends Model tokens screenLineForScreenRow: (screenRow) -> - return if screenRow < 0 or screenRow > @getLastScreenRow() - @displayLayer.getScreenLines(screenRow, screenRow + 1)[0] + result = @displayLayer.getScreenLines(screenRow, screenRow + 1) + result[0] if result bufferRowForScreenRow: (screenRow) -> @displayLayer.translateScreenPosition(Point(screenRow, 0)).row @@ -971,10 +976,14 @@ class TextEditor extends Model getRightmostScreenPosition: -> @displayLayer.getRightmostScreenPosition() + getApproximateRightmostScreenPosition: -> @displayLayer.getApproximateRightmostScreenPosition() + getMaxScreenLineLength: -> @getRightmostScreenPosition().column getLongestScreenRow: -> @getRightmostScreenPosition().row + getApproximateLongestScreenRow: -> @getApproximateRightmostScreenPosition().row + lineLengthForScreenRow: (screenRow) -> @displayLayer.lineLengthForScreenRow(screenRow) # Returns the range for the given buffer row. From e9c932f90d32e3304d04b6fef7b726c752b6141f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 11 Oct 2016 22:15:08 -0700 Subject: [PATCH 30/88] Start work towards doing display layer computations in idle callbacks --- package.json | 2 +- src/text-editor.coffee | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ab9b078e0..51b3508a8 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "9.3.1-1", + "text-buffer": "9.3.1-3", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", diff --git a/src/text-editor.coffee b/src/text-editor.coffee index ce08d01a8..5445643d9 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2,7 +2,7 @@ _ = require 'underscore-plus' path = require 'path' fs = require 'fs-plus' Grim = require 'grim' -{CompositeDisposable, Emitter} = require 'event-kit' +{CompositeDisposable, Disposable, Emitter} = require 'event-kit' {Point, Range} = TextBuffer = require 'text-buffer' LanguageMode = require './language-mode' DecorationManager = require './decoration-manager' @@ -181,6 +181,10 @@ class TextEditor extends Model else @displayLayer = @buffer.addDisplayLayer(displayLayerParams) + @backgroundWorkHandle = requestIdleCallback(@doBackgroundWork) + @disposables.add new Disposable => + cancelIdleCallback(@backgroundWorkHandle) if @backgroundWorkHandle? + @displayLayer.setTextDecorationLayer(@tokenizedBuffer) @defaultMarkerLayer = @displayLayer.addMarkerLayer() @selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true, persistent: true) @@ -207,6 +211,13 @@ class TextEditor extends Model priority: 0 visible: lineNumberGutterVisible + doBackgroundWork: (deadline) => + if @displayLayer.doBackgroundWork(deadline) + @presenter?.updateVerticalDimensions() + @backgroundWorkHandle = requestIdleCallback(@doBackgroundWork) + else + @backgroundWorkHandle = null + update: (params) -> displayLayerParams = {} From d20372a35f00b7cce786627011ae96772f0a08b8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 10 Oct 2016 20:59:39 -0700 Subject: [PATCH 31/88] Start on removing placeholder lines in TokenizedBuffer --- spec/tokenized-buffer-spec.coffee | 24 +++++------- src/text-editor.coffee | 2 +- src/tokenized-buffer-iterator.coffee | 18 ++++++--- src/tokenized-buffer.coffee | 58 +++++++++------------------- 4 files changed, 41 insertions(+), 61 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index ad9fa0ee7..5a1eabe3d 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -2,7 +2,7 @@ TokenizedBuffer = require '../src/tokenized-buffer' {Point} = TextBuffer = require 'text-buffer' _ = require 'underscore-plus' -describe "TokenizedBuffer", -> +fdescribe "TokenizedBuffer", -> [tokenizedBuffer, buffer] = [] beforeEach -> @@ -90,27 +90,24 @@ describe "TokenizedBuffer", -> buffer.release() describe "on construction", -> - it "initially creates un-tokenized screen lines, then tokenizes lines chunk at a time in the background", -> + it "tokenizes lines chunk at a time in the background", -> line0 = tokenizedBuffer.tokenizedLineForRow(0) - expect(line0.tokens).toEqual([value: line0.text, scopes: ['source.js']]) + expect(line0).toBe(undefined) line11 = tokenizedBuffer.tokenizedLineForRow(11) - expect(line11.tokens).toEqual([value: " return sort(Array.apply(this, arguments));", scopes: ['source.js']]) - - # background tokenization has not begun - expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack).toBeUndefined() + expect(line11).toBe(undefined) # tokenize chunk 1 advanceClock() expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy() expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLineForRow(5)).toBe(undefined) # tokenize chunk 2 advanceClock() expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy() expect(tokenizedBuffer.tokenizedLineForRow(9).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLineForRow(10)).toBe(undefined) # tokenize last chunk advanceClock() @@ -588,12 +585,9 @@ describe "TokenizedBuffer", -> expect(tokenizeCallback.callCount).toBe 1 expect(atom.grammars.nullGrammar.tokenizeLine.callCount).toBe 0 - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens.length).toBe 1 - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0].value).toBe 'a' - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens.length).toBe 1 - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].value).toBe 'b' - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens.length).toBe 1 - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].value).toBe 'c' + expect(tokenizedBuffer.tokenizedLineForRow(0)).toBe null + expect(tokenizedBuffer.tokenizedLineForRow(1)).toBe null + expect(tokenizedBuffer.tokenizedLineForRow(2)).toBe null describe "text decoration layer API", -> describe "iterator", -> diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 02cce3daf..ca1f1a938 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2868,7 +2868,7 @@ class TextEditor extends Model # whitespace. usesSoftTabs: -> for bufferRow in [0..@buffer.getLastRow()] - continue if @tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() + continue if @tokenizedBuffer.tokenizedLineForRow(bufferRow)?.isComment() line = @buffer.lineForRow(bufferRow) return true if line[0] is ' ' diff --git a/src/tokenized-buffer-iterator.coffee b/src/tokenized-buffer-iterator.coffee index 23b72d5a9..79217af5b 100644 --- a/src/tokenized-buffer-iterator.coffee +++ b/src/tokenized-buffer-iterator.coffee @@ -1,5 +1,7 @@ {Point} = require 'text-buffer' +EMPTY = Object.freeze([]) + module.exports = class TokenizedBufferIterator constructor: (@tokenizedBuffer) -> @@ -12,11 +14,17 @@ class TokenizedBufferIterator @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) + if currentLine = @tokenizedBuffer.tokenizedLineForRow(position.row) + @currentTags = currentLine.tags + @currentLineOpenTags = currentLine.openScopes + @currentLineLength = currentLine.text.length + @containingTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.scopeForId(id) + else + @currentTags = EMPTY + @currentLineOpenTags = EMPTY + @currentLineLength = @tokenizedBuffer.buffer.lineLengthForRow(position.row) + @containingTags = [] + currentColumn = 0 for tag, index in @currentTags diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 80358f23d..d34e9ce68 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -211,18 +211,7 @@ class TokenizedBuffer extends Model # Returns a {Boolean} indicating whether the given buffer row starts # a a foldable row range due to the code's indentation patterns. isFoldableCodeAtRow: (row) -> - # Investigating an exception that's occurring here due to the line being - # undefined. This should paper over the problem but we want to figure out - # what is happening: tokenizedLine = @tokenizedLineForRow(row) - @assert tokenizedLine?, "TokenizedLine is undefined", (error) => - error.metadata = { - row: row - rowCount: @tokenizedLines.length - tokenizedBufferChangeCount: @changeCount - bufferChangeCount: @buffer.changeCount - } - return false unless tokenizedLine? return false if @buffer.isRowBlank(row) or tokenizedLine.isComment() @@ -236,21 +225,21 @@ class TokenizedBuffer extends Model nextRow = row + 1 return false if nextRow > @buffer.getLastRow() - (row is 0 or not @tokenizedLineForRow(previousRow).isComment()) and - @tokenizedLineForRow(row).isComment() and - @tokenizedLineForRow(nextRow).isComment() + (not @tokenizedLineForRow(previousRow)?.isComment()) and + @tokenizedLineForRow(row)?.isComment() and + @tokenizedLineForRow(nextRow)?.isComment() buildTokenizedLinesForRows: (startRow, endRow, startingStack, startingopenScopes) -> ruleStack = startingStack openScopes = startingopenScopes stopTokenizingAt = startRow + @chunkSize - tokenizedLines = for row in [startRow..endRow] + tokenizedLines = for row in [startRow..endRow] by 1 if (ruleStack or row is 0) and row < stopTokenizingAt tokenizedLine = @buildTokenizedLineForRow(row, ruleStack, openScopes) ruleStack = tokenizedLine.ruleStack openScopes = @scopesFromTags(openScopes, tokenizedLine.tags) else - tokenizedLine = @buildPlaceholderTokenizedLineForRow(row, openScopes) + tokenizedLine = null tokenizedLine if endRow >= stopTokenizingAt @@ -260,19 +249,7 @@ class TokenizedBuffer extends Model tokenizedLines buildPlaceholderTokenizedLinesForRows: (startRow, endRow) -> - @buildPlaceholderTokenizedLineForRow(row) for row in [startRow..endRow] by 1 - - buildPlaceholderTokenizedLineForRow: (row) -> - @buildPlaceholderTokenizedLineForRowWithText(row, @buffer.lineForRow(row)) - - buildPlaceholderTokenizedLineForRowWithText: (row, text) -> - if @grammar isnt NullGrammar - openScopes = [@grammar.startIdForScope(@grammar.scopeName)] - else - openScopes = [] - tags = [text.length] - lineEnding = @buffer.lineEndingForRow(row) - new TokenizedLine({openScopes, text, tags, lineEnding, @tokenIterator}) + null for row in [startRow..endRow] by 1 buildTokenizedLineForRow: (row, ruleStack, openScopes) -> @buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack, openScopes) @@ -283,8 +260,7 @@ class TokenizedBuffer extends Model new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator}) tokenizedLineForRow: (bufferRow) -> - if 0 <= bufferRow < @tokenizedLines.length - @tokenizedLines[bufferRow] ?= @buildPlaceholderTokenizedLineForRow(bufferRow) + @tokenizedLines[bufferRow] tokenizedLinesForRows: (startRow, endRow) -> for row in [startRow..endRow] by 1 @@ -366,16 +342,18 @@ class TokenizedBuffer extends Model scopeDescriptorForPosition: (position) -> {row, column} = @buffer.clipPosition(Point.fromObject(position)) - iterator = @tokenizedLineForRow(row).getTokenIterator() - while iterator.next() - if iterator.getBufferEnd() > column - scopes = iterator.getScopes() - break + if iterator = @tokenizedLineForRow(row)?.getTokenIterator() + while iterator.next() + if iterator.getBufferEnd() > column + scopes = iterator.getScopes() + break - # rebuild scope of last token if we iterated off the end - unless scopes? - scopes = iterator.getScopes() - scopes.push(iterator.getScopeEnds().reverse()...) + # rebuild scope of last token if we iterated off the end + unless scopes? + scopes = iterator.getScopes() + scopes.push(iterator.getScopeEnds().reverse()...) + else + scopes = [] new ScopeDescriptor({scopes}) From 66510ae545d63949df4d123aec6bf25b6f6509d4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 11 Oct 2016 12:48:17 +0200 Subject: [PATCH 32/88] Handle null grammars consistently, building placeholder lines on-demand Previously we were treating the local `NullGrammar` differently from `atom.grammars.nullGrammar`. These two grammars are conceptually the same, as the former was created to support editors creation without a grammar registry. To keep backwards-compatibility, we also build placeholder lines on-demand when calling `TokenizedBuffer.prototype.tokenizedLineForRow`. This ensure that packages relying on the internals of `TokenizedBuffer` to retrieve syntactic boundaries won't break when the null grammar is used or large file mode is on. --- spec/text-editor-registry-spec.js | 4 ++-- spec/tokenized-buffer-spec.coffee | 14 ++++++------- src/language-mode.coffee | 16 +++++++-------- src/null-grammar.js | 29 ++++++++++++++++++++++++++- src/tokenized-buffer-iterator.coffee | 17 +++++----------- src/tokenized-buffer.coffee | 30 +++++++++++++++++++--------- 6 files changed, 71 insertions(+), 39 deletions(-) diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js index 86bb71a6f..51027e63c 100644 --- a/spec/text-editor-registry-spec.js +++ b/spec/text-editor-registry-spec.js @@ -198,13 +198,13 @@ describe('TextEditorRegistry', function () { registry.maintainConfig(editor2) await initialPackageActivation - expect(editor.getRootScopeDescriptor().getScopesArray()).toEqual(['text.plain']) + expect(editor.getRootScopeDescriptor().getScopesArray()).toEqual(['text.plain.null-grammar']) expect(editor2.getRootScopeDescriptor().getScopesArray()).toEqual(['source.js']) expect(editor.getEncoding()).toBe('utf8') expect(editor2.getEncoding()).toBe('utf8') - atom.config.set('core.fileEncoding', 'utf16le', {scopeSelector: '.text.plain'}) + atom.config.set('core.fileEncoding', 'utf16le', {scopeSelector: '.text.plain.null-grammar'}) atom.config.set('core.fileEncoding', 'utf16be', {scopeSelector: '.source.js'}) expect(editor.getEncoding()).toBe('utf16le') diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 5a1eabe3d..cbbaba9d5 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -2,7 +2,7 @@ TokenizedBuffer = require '../src/tokenized-buffer' {Point} = TextBuffer = require 'text-buffer' _ = require 'underscore-plus' -fdescribe "TokenizedBuffer", -> +describe "TokenizedBuffer", -> [tokenizedBuffer, buffer] = [] beforeEach -> @@ -149,8 +149,8 @@ fdescribe "TokenizedBuffer", -> it "does not attempt to tokenize the lines in the change, and preserves the existing invalid row", -> expect(tokenizedBuffer.firstInvalidRow()).toBe 5 buffer.setTextInRange([[6, 0], [7, 0]], "\n\n\n") - expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeFalsy() - expect(tokenizedBuffer.tokenizedLineForRow(7).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLineForRow(6)).toBeFalsy() + expect(tokenizedBuffer.tokenizedLineForRow(7)).toBeFalsy() expect(tokenizedBuffer.firstInvalidRow()).toBe 5 describe "when the buffer is fully tokenized", -> @@ -252,7 +252,7 @@ fdescribe "TokenizedBuffer", -> buffer.insert([0, 0], commentBlock) expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy() expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeFalsy() + expect(tokenizedBuffer.tokenizedLineForRow(5)).toBeFalsy() advanceClock() expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy() @@ -585,9 +585,9 @@ fdescribe "TokenizedBuffer", -> expect(tokenizeCallback.callCount).toBe 1 expect(atom.grammars.nullGrammar.tokenizeLine.callCount).toBe 0 - expect(tokenizedBuffer.tokenizedLineForRow(0)).toBe null - expect(tokenizedBuffer.tokenizedLineForRow(1)).toBe null - expect(tokenizedBuffer.tokenizedLineForRow(2)).toBe null + expect(tokenizedBuffer.tokenizedLineForRow(0)).toBeFalsy() + expect(tokenizedBuffer.tokenizedLineForRow(1)).toBeFalsy() + expect(tokenizedBuffer.tokenizedLineForRow(2)).toBeFalsy() describe "text decoration layer API", -> describe "iterator", -> diff --git a/src/language-mode.coffee b/src/language-mode.coffee index ad038d7db..20d54ae28 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -148,19 +148,19 @@ class LanguageMode rowRange rowRangeForCommentAtBufferRow: (bufferRow) -> - return unless @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() + return unless @editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment() startRow = bufferRow endRow = bufferRow if bufferRow > 0 for currentRow in [bufferRow-1..0] by -1 - break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() + break unless @editor.tokenizedBuffer.tokenizedLines[currentRow]?.isComment() startRow = currentRow if bufferRow < @buffer.getLastRow() for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1 - break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment() + break unless @editor.tokenizedBuffer.tokenizedLines[currentRow]?.isComment() endRow = currentRow return [startRow, endRow] if startRow isnt endRow @@ -189,7 +189,7 @@ class LanguageMode # row is a comment. isLineCommentedAtBufferRow: (bufferRow) -> return false unless 0 <= bufferRow <= @editor.getLastBufferRow() - @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment() + @editor.tokenizedBuffer.tokenizedLines[bufferRow]?.isComment() # Find a row range for a 'paragraph' around specified bufferRow. A paragraph # is a block of text bounded by and empty line or a block of text that is not @@ -246,10 +246,10 @@ class LanguageMode @suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options) suggestedIndentForLineAtBufferRow: (bufferRow, line, options) -> - if @editor.largeFileMode or @editor.tokenizedBuffer.grammar is NullGrammar - tokenizedLine = @editor.tokenizedBuffer.buildPlaceholderTokenizedLineForRowWithText(bufferRow, line) - else - tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line) + tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line) + iterator = tokenizedLine.getTokenIterator() + iterator.next() + scopeDescriptor = new ScopeDescriptor(scopes: iterator.getScopes()) @suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options) suggestedIndentForTokenizedLineAtBufferRow: (bufferRow, line, tokenizedLine, options) -> diff --git a/src/null-grammar.js b/src/null-grammar.js index 0ca3f83f1..01841346e 100644 --- a/src/null-grammar.js +++ b/src/null-grammar.js @@ -4,7 +4,34 @@ import {Disposable} from 'event-kit' export default Object.freeze({ name: 'Null Grammar', - scopeName: 'text.plain', + scopeName: 'text.plain.null-grammar', + scopeForId (id) { + if (id === -1 || id === -2) { + return this.scopeName + } else { + return null + } + }, + startIdForScope (scopeName) { + if (scopeName === this.scopeName) { + return -1 + } else { + return null + } + }, + endIdForScope (scopeName) { + if (scopeName === this.scopeName) { + return -2 + } else { + return null + } + }, + tokenizeLine (text) { + return { + tags: [this.startIdForScope(this.scopeName), text.length, this.endIdForScope(this.scopeName)], + ruleStack: null + } + }, onDidUpdate (callback) { return new Disposable(noop) } diff --git a/src/tokenized-buffer-iterator.coffee b/src/tokenized-buffer-iterator.coffee index 79217af5b..90e29fdfe 100644 --- a/src/tokenized-buffer-iterator.coffee +++ b/src/tokenized-buffer-iterator.coffee @@ -1,7 +1,5 @@ {Point} = require 'text-buffer' -EMPTY = Object.freeze([]) - module.exports = class TokenizedBufferIterator constructor: (@tokenizedBuffer) -> @@ -14,16 +12,11 @@ class TokenizedBufferIterator @closeTags = [] @tagIndex = null - if currentLine = @tokenizedBuffer.tokenizedLineForRow(position.row) - @currentTags = currentLine.tags - @currentLineOpenTags = currentLine.openScopes - @currentLineLength = currentLine.text.length - @containingTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.scopeForId(id) - else - @currentTags = EMPTY - @currentLineOpenTags = EMPTY - @currentLineLength = @tokenizedBuffer.buffer.lineLengthForRow(position.row) - @containingTags = [] + currentLine = @tokenizedBuffer.tokenizedLineForRow(position.row) + @currentLineLength = currentLine.text.length + @currentLineOpenTags = currentLine.openScopes + @currentTags = currentLine.tags + @containingTags = @currentLineOpenTags.map (id) => @tokenizedBuffer.grammar.scopeForId(id) currentColumn = 0 diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index d34e9ce68..23b73dfb1 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -36,7 +36,6 @@ class TokenizedBuffer extends Model @tokenIterator = new TokenIterator(this) @disposables.add @buffer.registerTextDecorationLayer(this) - @rootScopeDescriptor = new ScopeDescriptor(scopes: ['text.plain']) @setGrammar(grammar ? NullGrammar) @@ -118,7 +117,8 @@ class TokenizedBuffer extends Model tokenizeNextChunk: -> # Short circuit null grammar which can just use the placeholder tokens - if (@grammar.name is 'Null Grammar') and @firstInvalidRow()? + if @grammar.name is 'Null Grammar' and @firstInvalidRow()? + @tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, @buffer.getLastRow()) @invalidRows = [] @markTokenizationComplete() return @@ -192,7 +192,7 @@ class TokenizedBuffer extends Model @updateInvalidRows(start, end, delta) previousEndStack = @stackForRow(end) # used in spill detection below - if @largeFileMode or @grammar is NullGrammar + if @largeFileMode or @grammar.name is 'Null Grammar' newTokenizedLines = @buildPlaceholderTokenizedLinesForRows(start, end + delta) else newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1), @openScopesForRow(start)) @@ -234,12 +234,12 @@ class TokenizedBuffer extends Model openScopes = startingopenScopes stopTokenizingAt = startRow + @chunkSize tokenizedLines = for row in [startRow..endRow] by 1 - if (ruleStack or row is 0) and row < stopTokenizingAt + if row < stopTokenizingAt tokenizedLine = @buildTokenizedLineForRow(row, ruleStack, openScopes) ruleStack = tokenizedLine.ruleStack openScopes = @scopesFromTags(openScopes, tokenizedLine.tags) else - tokenizedLine = null + tokenizedLine = undefined tokenizedLine if endRow >= stopTokenizingAt @@ -249,7 +249,7 @@ class TokenizedBuffer extends Model tokenizedLines buildPlaceholderTokenizedLinesForRows: (startRow, endRow) -> - null for row in [startRow..endRow] by 1 + new Array(endRow - startRow + 1) buildTokenizedLineForRow: (row, ruleStack, openScopes) -> @buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack, openScopes) @@ -260,7 +260,20 @@ class TokenizedBuffer extends Model new TokenizedLine({openScopes, text, tags, ruleStack, lineEnding, @tokenIterator}) tokenizedLineForRow: (bufferRow) -> - @tokenizedLines[bufferRow] + if 0 <= bufferRow <= @buffer.getLastRow() + if tokenizedLine = @tokenizedLines[bufferRow] + tokenizedLine + else + text = @buffer.lineForRow(bufferRow) + lineEnding = @buffer.lineEndingForRow(bufferRow) + tags = [ + @grammar.startIdForScope(@grammar.scopeName), + text.length, + @grammar.endIdForScope(@grammar.scopeName) + ] + @tokenizedLines[bufferRow] = new TokenizedLine({openScopes: [], text, tags, lineEnding, @tokenIterator}) + else + null tokenizedLinesForRows: (startRow, endRow) -> for row in [startRow..endRow] by 1 @@ -270,8 +283,7 @@ class TokenizedBuffer extends Model @tokenizedLines[bufferRow]?.ruleStack openScopesForRow: (bufferRow) -> - if bufferRow > 0 - precedingLine = @tokenizedLineForRow(bufferRow - 1) + if precedingLine = @tokenizedLineForRow(bufferRow - 1) @scopesFromTags(precedingLine.openScopes, precedingLine.tags) else [] From 00f4c7b282827345096077c9bb6eeb5a0997b1c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 08:50:25 +0200 Subject: [PATCH 33/88] Use `TokenizedBuffer.prototype.tokenizedLineForRow` conservatively Since this method will now construct a placeholder line, we want to use it only where necessary to keep memory footprint to a minimum. --- spec/token-iterator-spec.coffee | 2 +- spec/tokenized-buffer-spec.coffee | 114 +++++++++++++++--------------- src/text-editor.coffee | 2 +- src/tokenized-buffer.coffee | 36 +++++----- 4 files changed, 79 insertions(+), 75 deletions(-) diff --git a/spec/token-iterator-spec.coffee b/spec/token-iterator-spec.coffee index f876d30d1..6ae01cd30 100644 --- a/spec/token-iterator-spec.coffee +++ b/spec/token-iterator-spec.coffee @@ -29,7 +29,7 @@ describe "TokenIterator", -> }) tokenizedBuffer.setGrammar(grammar) - tokenIterator = tokenizedBuffer.tokenizedLineForRow(1).getTokenIterator() + tokenIterator = tokenizedBuffer.tokenizedLines[1].getTokenIterator() tokenIterator.next() expect(tokenIterator.getBufferStart()).toBe 0 diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index cbbaba9d5..a21db2fe3 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -91,28 +91,28 @@ describe "TokenizedBuffer", -> describe "on construction", -> it "tokenizes lines chunk at a time in the background", -> - line0 = tokenizedBuffer.tokenizedLineForRow(0) + line0 = tokenizedBuffer.tokenizedLines[0] expect(line0).toBe(undefined) - line11 = tokenizedBuffer.tokenizedLineForRow(11) + line11 = tokenizedBuffer.tokenizedLines[11] expect(line11).toBe(undefined) # tokenize chunk 1 advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(5)).toBe(undefined) + expect(tokenizedBuffer.tokenizedLines[0].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[4].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[5]).toBe(undefined) # tokenize chunk 2 advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(9).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(10)).toBe(undefined) + expect(tokenizedBuffer.tokenizedLines[5].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[9].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[10]).toBe(undefined) # tokenize last chunk advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(10).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(12).ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[10].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[12].ruleStack?).toBeTruthy() describe "when the buffer is partially tokenized", -> beforeEach -> @@ -149,8 +149,8 @@ describe "TokenizedBuffer", -> it "does not attempt to tokenize the lines in the change, and preserves the existing invalid row", -> expect(tokenizedBuffer.firstInvalidRow()).toBe 5 buffer.setTextInRange([[6, 0], [7, 0]], "\n\n\n") - expect(tokenizedBuffer.tokenizedLineForRow(6)).toBeFalsy() - expect(tokenizedBuffer.tokenizedLineForRow(7)).toBeFalsy() + expect(tokenizedBuffer.tokenizedLines[6]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[7]).toBeUndefined() expect(tokenizedBuffer.firstInvalidRow()).toBe 5 describe "when the buffer is fully tokenized", -> @@ -162,101 +162,101 @@ describe "TokenizedBuffer", -> it "updates tokens to reflect the change", -> buffer.setTextInRange([[0, 0], [2, 0]], "foo()\n7\n") - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js']) - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js']) + expect(tokenizedBuffer.tokenizedLines[0].tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.begin.bracket.round.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js']) # line 2 is unchanged - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) + expect(tokenizedBuffer.tokenizedLines[2].tokens[1]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> buffer.insert([5, 30], '/* */') buffer.insert([2, 0], '/*') - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js'] advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] it "resumes highlighting with the state of the previous line", -> buffer.insert([0, 0], '/*') buffer.insert([5, 0], '*/') buffer.insert([1, 0], 'var ') - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[1].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] describe "when lines are both updated and removed", -> it "updates tokens to reflect the change", -> buffer.setTextInRange([[1, 0], [3, 0]], "foo()") # previous line 0 remains - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.type.var.js']) + expect(tokenizedBuffer.tokenizedLines[0].tokens[0]).toEqual(value: 'var', scopes: ['source.js', 'storage.type.var.js']) # previous line 3 should be combined with input to form line 1 - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[6]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) # lines below deleted regions should be shifted upward - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js']) - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js']) + expect(tokenizedBuffer.tokenizedLines[2].tokens[1]).toEqual(value: 'while', scopes: ['source.js', 'keyword.control.js']) + expect(tokenizedBuffer.tokenizedLines[3].tokens[1]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) + expect(tokenizedBuffer.tokenizedLines[4].tokens[1]).toEqual(value: '<', scopes: ['source.js', 'keyword.operator.comparison.js']) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> buffer.insert([5, 30], '/* */') buffer.setTextInRange([[2, 0], [3, 0]], '/*') - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js'] + expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js'] advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] describe "when lines are both updated and inserted", -> it "updates tokens to reflect the change", -> buffer.setTextInRange([[1, 0], [2, 0]], "foo()\nbar()\nbaz()\nquux()") # previous line 0 remains - expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.type.var.js']) + expect(tokenizedBuffer.tokenizedLines[0].tokens[0]).toEqual( value: 'var', scopes: ['source.js', 'storage.type.var.js']) # 3 new lines inserted - expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0]).toEqual(value: 'bar', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0]).toEqual(value: 'baz', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[1].tokens[0]).toEqual(value: 'foo', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[2].tokens[0]).toEqual(value: 'bar', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[3].tokens[0]).toEqual(value: 'baz', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) # previous line 2 is joined with quux() on line 4 - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0]).toEqual(value: 'quux', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) + expect(tokenizedBuffer.tokenizedLines[4].tokens[0]).toEqual(value: 'quux', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js']) + expect(tokenizedBuffer.tokenizedLines[4].tokens[4]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js']) # previous line 3 is pushed down to become line 5 - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) + expect(tokenizedBuffer.tokenizedLines[5].tokens[3]).toEqual(value: '=', scopes: ['source.js', 'keyword.operator.assignment.js']) describe "when the change invalidates the tokenization of subsequent lines", -> it "schedules the invalidated lines to be tokenized in the background", -> buffer.insert([5, 30], '/* */') buffer.insert([2, 0], '/*\nabcde\nabcder') - expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] - expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js'] + expect(tokenizedBuffer.tokenizedLines[2].tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js'] + expect(tokenizedBuffer.tokenizedLines[3].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[4].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js'] advanceClock() # tokenize invalidated lines in background - expect(tokenizedBuffer.tokenizedLineForRow(5).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(6).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(7).tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] - expect(tokenizedBuffer.tokenizedLineForRow(8).tokens[0].scopes).not.toBe ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[5].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[6].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[7].tokens[0].scopes).toEqual ['source.js', 'comment.block.js'] + expect(tokenizedBuffer.tokenizedLines[8].tokens[0].scopes).not.toBe ['source.js', 'comment.block.js'] describe "when there is an insertion that is larger than the chunk size", -> it "tokenizes the initial chunk synchronously, then tokenizes the remaining lines in the background", -> commentBlock = _.multiplyString("// a comment\n", tokenizedBuffer.chunkSize + 2) buffer.insert([0, 0], commentBlock) - expect(tokenizedBuffer.tokenizedLineForRow(0).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(4).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(5)).toBeFalsy() + expect(tokenizedBuffer.tokenizedLines[0].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[4].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[5]).toBeUndefined() advanceClock() - expect(tokenizedBuffer.tokenizedLineForRow(5).ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLineForRow(6).ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[5].ruleStack?).toBeTruthy() + expect(tokenizedBuffer.tokenizedLines[6].ruleStack?).toBeTruthy() it "does not break out soft tabs across a scope boundary", -> waitsForPromise -> @@ -366,7 +366,7 @@ describe "TokenizedBuffer", -> tokenizedBuffer.setGrammar(atom.grammars.selectGrammar('test.erb')) fullyTokenize(tokenizedBuffer) - {tokens} = tokenizedBuffer.tokenizedLineForRow(0) + {tokens} = tokenizedBuffer.tokenizedLines[0] expect(tokens[0]).toEqual value: "
", scopes: ["text.html.ruby"] waitsForPromise -> @@ -374,7 +374,7 @@ describe "TokenizedBuffer", -> runs -> fullyTokenize(tokenizedBuffer) - {tokens} = tokenizedBuffer.tokenizedLineForRow(0) + {tokens} = tokenizedBuffer.tokenizedLines[0] expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby", "meta.tag.block.any.html", "punctuation.definition.tag.begin.html"] describe ".tokenForPosition(position)", -> @@ -406,7 +406,7 @@ describe "TokenizedBuffer", -> describe "when the selector does not match the token at the position", -> it "returns a falsy value", -> - expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeFalsy() + expect(tokenizedBuffer.bufferRangeForScopeAtPosition('.bogus', [0, 1])).toBeUndefined() describe "when the selector matches a single token at the position", -> it "returns the range covered by the token", -> @@ -466,7 +466,7 @@ describe "TokenizedBuffer", -> buffer.insert([12, 0], ' ') expect(tokenizedBuffer.indentLevelForRow(13)).toBe 2 - expect(tokenizedBuffer.tokenizedLineForRow(14)).not.toBeDefined() + expect(tokenizedBuffer.tokenizedLines[14]).not.toBeDefined() it "updates the indentLevel of empty lines surrounding a change that inserts lines", -> buffer.insert([7, 0], '\n\n') @@ -585,9 +585,9 @@ describe "TokenizedBuffer", -> expect(tokenizeCallback.callCount).toBe 1 expect(atom.grammars.nullGrammar.tokenizeLine.callCount).toBe 0 - expect(tokenizedBuffer.tokenizedLineForRow(0)).toBeFalsy() - expect(tokenizedBuffer.tokenizedLineForRow(1)).toBeFalsy() - expect(tokenizedBuffer.tokenizedLineForRow(2)).toBeFalsy() + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[1]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[2]).toBeUndefined() describe "text decoration layer API", -> describe "iterator", -> diff --git a/src/text-editor.coffee b/src/text-editor.coffee index ca1f1a938..50b2e6f96 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2868,7 +2868,7 @@ class TextEditor extends Model # whitespace. usesSoftTabs: -> for bufferRow in [0..@buffer.getLastRow()] - continue if @tokenizedBuffer.tokenizedLineForRow(bufferRow)?.isComment() + continue if @tokenizedBuffer.tokenizedLines[bufferRow]?.isComment() line = @buffer.lineForRow(bufferRow) return true if line[0] is ' ' diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 23b73dfb1..ff2d72019 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -211,30 +211,34 @@ class TokenizedBuffer extends Model # Returns a {Boolean} indicating whether the given buffer row starts # a a foldable row range due to the code's indentation patterns. isFoldableCodeAtRow: (row) -> - tokenizedLine = @tokenizedLineForRow(row) - return false unless tokenizedLine? - - return false if @buffer.isRowBlank(row) or tokenizedLine.isComment() - nextRow = @buffer.nextNonBlankRow(row) - return false unless nextRow? - - @indentLevelForRow(nextRow) > @indentLevelForRow(row) + if 0 <= row <= @buffer.getLastRow() + nextRow = @buffer.nextNonBlankRow(row) + tokenizedLine = @tokenizedLines[row] + if @buffer.isRowBlank(row) or tokenizedLine?.isComment() or not nextRow? + false + else + @indentLevelForRow(nextRow) > @indentLevelForRow(row) + else + false isFoldableCommentAtRow: (row) -> previousRow = row - 1 nextRow = row + 1 - return false if nextRow > @buffer.getLastRow() - - (not @tokenizedLineForRow(previousRow)?.isComment()) and - @tokenizedLineForRow(row)?.isComment() and - @tokenizedLineForRow(nextRow)?.isComment() + if nextRow > @buffer.getLastRow() + false + else + Boolean( + not (@tokenizedLines[previousRow]?.isComment()) and + @tokenizedLines[row]?.isComment() and + @tokenizedLines[nextRow]?.isComment() + ) buildTokenizedLinesForRows: (startRow, endRow, startingStack, startingopenScopes) -> ruleStack = startingStack openScopes = startingopenScopes stopTokenizingAt = startRow + @chunkSize tokenizedLines = for row in [startRow..endRow] by 1 - if row < stopTokenizingAt + if (not @firstInvalidRow()? or row < @firstInvalidRow()) and row < stopTokenizingAt tokenizedLine = @buildTokenizedLineForRow(row, ruleStack, openScopes) ruleStack = tokenizedLine.ruleStack openScopes = @scopesFromTags(openScopes, tokenizedLine.tags) @@ -283,7 +287,7 @@ class TokenizedBuffer extends Model @tokenizedLines[bufferRow]?.ruleStack openScopesForRow: (bufferRow) -> - if precedingLine = @tokenizedLineForRow(bufferRow - 1) + if precedingLine = @tokenizedLines[bufferRow - 1] @scopesFromTags(precedingLine.openScopes, precedingLine.tags) else [] @@ -438,7 +442,7 @@ class TokenizedBuffer extends Model logLines: (start=0, end=@buffer.getLastRow()) -> for row in [start..end] - line = @tokenizedLineForRow(row).text + line = @tokenizedLines[row].text console.log row, line, line.length return From d3882c165f2d54dbdb60ed036ddf2bd01f78f885 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 12:04:54 +0200 Subject: [PATCH 34/88] :art: --- spec/tokenized-buffer-spec.coffee | 22 +++++++++++++--------- src/null-grammar.js | 4 ++-- src/tokenized-buffer.coffee | 19 ++++++++++--------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index a21db2fe3..16fc4849c 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -1,3 +1,4 @@ +NullGrammar = require '../src/null-grammar' TokenizedBuffer = require '../src/tokenized-buffer' {Point} = TextBuffer = require 'text-buffer' _ = require 'underscore-plus' @@ -568,26 +569,29 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false describe "when the buffer is configured with the null grammar", -> - it "uses the placeholder tokens and does not actually tokenize using the grammar", -> - spyOn(atom.grammars.nullGrammar, 'tokenizeLine').andCallThrough() + it "does not actually tokenize using the grammar", -> + spyOn(NullGrammar, 'tokenizeLine').andCallThrough() buffer = atom.project.bufferForPathSync('sample.will-use-the-null-grammar') buffer.setText('a\nb\nc') - tokenizedBuffer = new TokenizedBuffer({ buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, + assert: atom.assert, tabLength: 2 }) tokenizeCallback = jasmine.createSpy('onDidTokenize') tokenizedBuffer.onDidTokenize(tokenizeCallback) - fullyTokenize(tokenizedBuffer) - - expect(tokenizeCallback.callCount).toBe 1 - expect(atom.grammars.nullGrammar.tokenizeLine.callCount).toBe 0 - expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() expect(tokenizedBuffer.tokenizedLines[1]).toBeUndefined() expect(tokenizedBuffer.tokenizedLines[2]).toBeUndefined() + expect(tokenizeCallback.callCount).toBe(0) + expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled() + + fullyTokenize(tokenizedBuffer) + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[1]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLines[2]).toBeUndefined() + expect(tokenizeCallback.callCount).toBe(0) + expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled() describe "text decoration layer API", -> describe "iterator", -> diff --git a/src/null-grammar.js b/src/null-grammar.js index 01841346e..fe9c3889e 100644 --- a/src/null-grammar.js +++ b/src/null-grammar.js @@ -2,7 +2,7 @@ import {Disposable} from 'event-kit' -export default Object.freeze({ +export default { name: 'Null Grammar', scopeName: 'text.plain.null-grammar', scopeForId (id) { @@ -35,6 +35,6 @@ export default Object.freeze({ onDidUpdate (callback) { return new Disposable(noop) } -}) +} function noop () {} diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index ff2d72019..20e79d28a 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -94,11 +94,13 @@ class TokenizedBuffer extends Model false retokenizeLines: -> - lastRow = @buffer.getLastRow() @fullyTokenized = false - @tokenizedLines = new Array(lastRow + 1) + @tokenizedLines = new Array(@buffer.getLineCount()) @invalidRows = [] - @invalidateRow(0) + if @largeFileMode or @grammar.name is 'Null Grammar' + @markTokenizationComplete() + else + @invalidateRow(0) setVisible: (@visible) -> @tokenizeInBackground() if @visible @@ -167,11 +169,10 @@ class TokenizedBuffer extends Model return invalidateRow: (row) -> - return if @largeFileMode - - @invalidRows.push(row) - @invalidRows.sort (a, b) -> a - b - @tokenizeInBackground() + if @grammar.name isnt 'Null Grammar' and not @largeFileMode + @invalidRows.push(row) + @invalidRows.sort (a, b) -> a - b + @tokenizeInBackground() updateInvalidRows: (start, end, delta) -> @invalidRows = @invalidRows.map (row) -> @@ -238,7 +239,7 @@ class TokenizedBuffer extends Model openScopes = startingopenScopes stopTokenizingAt = startRow + @chunkSize tokenizedLines = for row in [startRow..endRow] by 1 - if (not @firstInvalidRow()? or row < @firstInvalidRow()) and row < stopTokenizingAt + if (ruleStack or row is 0) and row < stopTokenizingAt tokenizedLine = @buildTokenizedLineForRow(row, ruleStack, openScopes) ruleStack = tokenizedLine.ruleStack openScopes = @scopesFromTags(openScopes, tokenizedLine.tags) From e317d7d3259a7e2da3b9e4168ed868079c481fee Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 12:10:24 +0200 Subject: [PATCH 35/88] Clean up tests --- spec/tokenized-buffer-spec.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 16fc4849c..e8470d1c1 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -93,22 +93,22 @@ describe "TokenizedBuffer", -> describe "on construction", -> it "tokenizes lines chunk at a time in the background", -> line0 = tokenizedBuffer.tokenizedLines[0] - expect(line0).toBe(undefined) + expect(line0).toBeUndefined() line11 = tokenizedBuffer.tokenizedLines[11] - expect(line11).toBe(undefined) + expect(line11).toBeUndefined() # tokenize chunk 1 advanceClock() expect(tokenizedBuffer.tokenizedLines[0].ruleStack?).toBeTruthy() expect(tokenizedBuffer.tokenizedLines[4].ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLines[5]).toBe(undefined) + expect(tokenizedBuffer.tokenizedLines[5]).toBeUndefined() # tokenize chunk 2 advanceClock() expect(tokenizedBuffer.tokenizedLines[5].ruleStack?).toBeTruthy() expect(tokenizedBuffer.tokenizedLines[9].ruleStack?).toBeTruthy() - expect(tokenizedBuffer.tokenizedLines[10]).toBe(undefined) + expect(tokenizedBuffer.tokenizedLines[10]).toBeUndefined() # tokenize last chunk advanceClock() From ea80483cbe6e97a668f720f42f9538267bc52d7f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 12:16:17 +0200 Subject: [PATCH 36/88] Delete `TokenizedBuffer.prototype.buildPlaceholderTokenizedLinesForRows` --- src/tokenized-buffer.coffee | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 20e79d28a..ba40af58a 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -120,7 +120,7 @@ class TokenizedBuffer extends Model tokenizeNextChunk: -> # Short circuit null grammar which can just use the placeholder tokens if @grammar.name is 'Null Grammar' and @firstInvalidRow()? - @tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, @buffer.getLastRow()) + @tokenizedLines = new Array(@buffer.getLineCount()) @invalidRows = [] @markTokenizationComplete() return @@ -194,7 +194,8 @@ class TokenizedBuffer extends Model @updateInvalidRows(start, end, delta) previousEndStack = @stackForRow(end) # used in spill detection below if @largeFileMode or @grammar.name is 'Null Grammar' - newTokenizedLines = @buildPlaceholderTokenizedLinesForRows(start, end + delta) + lineCount = ((end + delta) - start) + 1 + newTokenizedLines = new Array(lineCount) else newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1), @openScopesForRow(start)) _.spliceWithArray(@tokenizedLines, start, end - start + 1, newTokenizedLines) @@ -253,9 +254,6 @@ class TokenizedBuffer extends Model tokenizedLines - buildPlaceholderTokenizedLinesForRows: (startRow, endRow) -> - new Array(endRow - startRow + 1) - buildTokenizedLineForRow: (row, ruleStack, openScopes) -> @buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack, openScopes) From 633e68f4d5a7518b75b61a97e2ca449e779e584d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 12:18:58 +0200 Subject: [PATCH 37/88] Remove null guard in `scopeDescriptorForPosition` --- src/tokenized-buffer.coffee | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index ba40af58a..6de370cd5 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -357,18 +357,16 @@ class TokenizedBuffer extends Model scopeDescriptorForPosition: (position) -> {row, column} = @buffer.clipPosition(Point.fromObject(position)) - if iterator = @tokenizedLineForRow(row)?.getTokenIterator() - while iterator.next() - if iterator.getBufferEnd() > column - scopes = iterator.getScopes() - break - - # rebuild scope of last token if we iterated off the end - unless scopes? + iterator = @tokenizedLineForRow(row).getTokenIterator() + while iterator.next() + if iterator.getBufferEnd() > column scopes = iterator.getScopes() - scopes.push(iterator.getScopeEnds().reverse()...) - else - scopes = [] + break + + # rebuild scope of last token if we iterated off the end + unless scopes? + scopes = iterator.getScopes() + scopes.push(iterator.getScopeEnds().reverse()...) new ScopeDescriptor({scopes}) From 2246072ac93b8e797d7f5bcc06faddfa1f3c41f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 12:20:32 +0200 Subject: [PATCH 38/88] Restore line order in `TokenizedBufferIterator.prototype.seek` --- src/tokenized-buffer-iterator.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tokenized-buffer-iterator.coffee b/src/tokenized-buffer-iterator.coffee index 90e29fdfe..23b72d5a9 100644 --- a/src/tokenized-buffer-iterator.coffee +++ b/src/tokenized-buffer-iterator.coffee @@ -13,11 +13,10 @@ class TokenizedBufferIterator @tagIndex = null currentLine = @tokenizedBuffer.tokenizedLineForRow(position.row) - @currentLineLength = currentLine.text.length - @currentLineOpenTags = currentLine.openScopes @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 From d393cba75de055f618db76e8df50afa915cb2f8d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 12:28:30 +0200 Subject: [PATCH 39/88] Simplify on-demand placeholder line creation and add test coverage --- spec/tokenized-buffer-spec.coffee | 36 +++++++++++++++++++++++++++++++ src/language-mode.coffee | 3 --- src/tokenized-buffer.coffee | 8 +------ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index e8470d1c1..f2b487e85 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -568,6 +568,42 @@ describe "TokenizedBuffer", -> expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false + describe "::tokenizedLineForRow(row)", -> + it "returns the tokenized line for a row, or a placeholder line if it hasn't been tokenized yet", -> + buffer = atom.project.bufferForPathSync('sample.js') + grammar = atom.grammars.grammarForScopeName('source.js') + tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2}) + line0 = buffer.lineForRow(0) + + jsScopeStartId = grammar.startIdForScope(grammar.scopeName) + jsScopeEndId = grammar.endIdForScope(grammar.scopeName) + startTokenizing(tokenizedBuffer) + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([jsScopeStartId, line0.length, jsScopeEndId]) + advanceClock(1) + expect(tokenizedBuffer.tokenizedLines[0]).not.toBeUndefined() + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).not.toEqual([jsScopeStartId, line0.length, jsScopeEndId]) + + nullScopeStartId = NullGrammar.startIdForScope(NullGrammar.scopeName) + nullScopeEndId = NullGrammar.endIdForScope(NullGrammar.scopeName) + tokenizedBuffer.setGrammar(NullGrammar) + startTokenizing(tokenizedBuffer) + expect(tokenizedBuffer.tokenizedLines[0]).toBeUndefined() + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([nullScopeStartId, line0.length, nullScopeEndId]) + advanceClock(1) + expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe(line0) + expect(tokenizedBuffer.tokenizedLineForRow(0).tags).toEqual([nullScopeStartId, line0.length, nullScopeEndId]) + + it "returns undefined if the requested row is outside the buffer range", -> + buffer = atom.project.bufferForPathSync('sample.js') + grammar = atom.grammars.grammarForScopeName('source.js') + tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2}) + fullyTokenize(tokenizedBuffer) + expect(tokenizedBuffer.tokenizedLineForRow(999)).toBeUndefined() + describe "when the buffer is configured with the null grammar", -> it "does not actually tokenize using the grammar", -> spyOn(NullGrammar, 'tokenizeLine').andCallThrough() diff --git a/src/language-mode.coffee b/src/language-mode.coffee index 20d54ae28..bb9f339c4 100644 --- a/src/language-mode.coffee +++ b/src/language-mode.coffee @@ -247,9 +247,6 @@ class LanguageMode suggestedIndentForLineAtBufferRow: (bufferRow, line, options) -> tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line) - iterator = tokenizedLine.getTokenIterator() - iterator.next() - scopeDescriptor = new ScopeDescriptor(scopes: iterator.getScopes()) @suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options) suggestedIndentForTokenizedLineAtBufferRow: (bufferRow, line, tokenizedLine, options) -> diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 6de370cd5..11a15c575 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -269,14 +269,8 @@ class TokenizedBuffer extends Model else text = @buffer.lineForRow(bufferRow) lineEnding = @buffer.lineEndingForRow(bufferRow) - tags = [ - @grammar.startIdForScope(@grammar.scopeName), - text.length, - @grammar.endIdForScope(@grammar.scopeName) - ] + tags = [@grammar.startIdForScope(@grammar.scopeName), text.length, @grammar.endIdForScope(@grammar.scopeName)] @tokenizedLines[bufferRow] = new TokenizedLine({openScopes: [], text, tags, lineEnding, @tokenIterator}) - else - null tokenizedLinesForRows: (startRow, endRow) -> for row in [startRow..endRow] by 1 From 503f31ea6cf9b9e3b7c0ac615d7255e58a8809d6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 13:11:34 +0200 Subject: [PATCH 40/88] Delete unnecessary dependencies in `TokenizedBuffer` specs --- spec/tokenized-buffer-spec.coffee | 94 ++++++------------------------- 1 file changed, 16 insertions(+), 78 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index f2b487e85..6558d42b4 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -33,15 +33,8 @@ describe "TokenizedBuffer", -> atom.packages.activatePackage('language-coffee-script') it "deserializes it searching among the buffers in the current project", -> - tokenizedBufferA = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBufferB = TokenizedBuffer.deserialize( - JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), - atom - ) - + tokenizedBufferA = new TokenizedBuffer({buffer, tabLength: 2}) + tokenizedBufferB = TokenizedBuffer.deserialize(JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), atom) expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) describe "when the underlying buffer has no path", -> @@ -49,25 +42,14 @@ describe "TokenizedBuffer", -> buffer = atom.project.bufferForPathSync(null) it "deserializes it searching among the buffers in the current project", -> - tokenizedBufferA = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBufferB = TokenizedBuffer.deserialize( - JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), - atom - ) - + tokenizedBufferA = new TokenizedBuffer({buffer, tabLength: 2}) + tokenizedBufferB = TokenizedBuffer.deserialize(JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), atom) expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) describe "when the buffer is destroyed", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) startTokenizing(tokenizedBuffer) it "stops tokenization", -> @@ -79,11 +61,7 @@ describe "TokenizedBuffer", -> describe "when the buffer contains soft-tabs", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) startTokenizing(tokenizedBuffer) afterEach -> @@ -282,11 +260,7 @@ describe "TokenizedBuffer", -> runs -> buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.coffee')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.coffee'), tabLength: 2}) startTokenizing(tokenizedBuffer) afterEach -> @@ -350,7 +324,6 @@ describe "TokenizedBuffer", -> expect(tokenizedHandler.callCount).toBe(1) it "retokenizes the buffer", -> - waitsForPromise -> atom.packages.activatePackage('language-ruby-on-rails') @@ -360,11 +333,7 @@ describe "TokenizedBuffer", -> runs -> buffer = atom.project.bufferForPathSync() buffer.setText "
<%= User.find(2).full_name %>
" - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.selectGrammar('test.erb')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.selectGrammar('test.erb'), tabLength: 2}) fullyTokenize(tokenizedBuffer) {tokens} = tokenizedBuffer.tokenizedLines[0] @@ -385,11 +354,7 @@ describe "TokenizedBuffer", -> it "returns the correct token (regression)", -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) expect(tokenizedBuffer.tokenForPosition([1, 0]).scopes).toEqual ["source.js"] expect(tokenizedBuffer.tokenForPosition([1, 1]).scopes).toEqual ["source.js"] @@ -398,11 +363,7 @@ describe "TokenizedBuffer", -> describe ".bufferRangeForScopeAtPosition(selector, position)", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) describe "when the selector does not match the token at the position", -> @@ -421,11 +382,7 @@ describe "TokenizedBuffer", -> describe ".indentLevelForRow(row)", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) describe "when the line is non-empty", -> @@ -501,11 +458,7 @@ describe "TokenizedBuffer", -> buffer = atom.project.bufferForPathSync('sample.js') buffer.insert [10, 0], " // multi-line\n // comment\n // block\n" buffer.insert [0, 0], "// multi-line\n// comment\n// block\n" - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.grammarForScopeName('source.js')) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName('source.js'), tabLength: 2}) fullyTokenize(tokenizedBuffer) it "includes the first line of multi-line comments", -> @@ -609,10 +562,7 @@ describe "TokenizedBuffer", -> spyOn(NullGrammar, 'tokenizeLine').andCallThrough() buffer = atom.project.bufferForPathSync('sample.will-use-the-null-grammar') buffer.setText('a\nb\nc') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2 - }) + tokenizedBuffer = new TokenizedBuffer({buffer, tabLength: 2}) tokenizeCallback = jasmine.createSpy('onDidTokenize') tokenizedBuffer.onDidTokenize(tokenizeCallback) @@ -633,11 +583,7 @@ describe "TokenizedBuffer", -> describe "iterator", -> it "iterates over the syntactic scope boundaries", -> buffer = new TextBuffer(text: "var foo = 1 /*\nhello*/var bar = 2\n") - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".js")) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName("source.js"), tabLength: 2}) fullyTokenize(tokenizedBuffer) iterator = tokenizedBuffer.buildIterator() @@ -689,11 +635,7 @@ describe "TokenizedBuffer", -> runs -> buffer = new TextBuffer(text: "# hello\n# world") - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(atom.grammars.selectGrammar(".coffee")) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar: atom.grammars.grammarForScopeName("source.coffee"), tabLength: 2}) fullyTokenize(tokenizedBuffer) iterator = tokenizedBuffer.buildIterator() @@ -722,11 +664,7 @@ describe "TokenizedBuffer", -> }) buffer = new TextBuffer(text: 'start x\nend x\nx') - tokenizedBuffer = new TokenizedBuffer({ - buffer, grammarRegistry: atom.grammars, packageManager: atom.packages, - assert: atom.assert, tabLength: 2, - }) - tokenizedBuffer.setGrammar(grammar) + tokenizedBuffer = new TokenizedBuffer({buffer, grammar, tabLength: 2}) fullyTokenize(tokenizedBuffer) iterator = tokenizedBuffer.buildIterator() From 1f210adad1e4bd7bf9483181578c020b35e831d6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 12 Oct 2016 18:41:56 +0200 Subject: [PATCH 41/88] Delete unused conditional in `tokenizeNextChunk` and `invalidateRow` Previously, for null grammar and large file mode, we were short circuiting the tokenization of the next chunk or the invalidation of a row. However, that is unnecessary because there is no additional chunk to process with null grammar or in large file mode. --- src/tokenized-buffer.coffee | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index 11a15c575..ce56e0388 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -103,7 +103,8 @@ class TokenizedBuffer extends Model @invalidateRow(0) setVisible: (@visible) -> - @tokenizeInBackground() if @visible + if @visible and @grammar.name isnt 'Null Grammar' and not @largeFileMode + @tokenizeInBackground() getTabLength: -> @tabLength @@ -118,13 +119,6 @@ class TokenizedBuffer extends Model @tokenizeNextChunk() if @isAlive() and @buffer.isAlive() tokenizeNextChunk: -> - # Short circuit null grammar which can just use the placeholder tokens - if @grammar.name is 'Null Grammar' and @firstInvalidRow()? - @tokenizedLines = new Array(@buffer.getLineCount()) - @invalidRows = [] - @markTokenizationComplete() - return - rowsRemaining = @chunkSize while @firstInvalidRow()? and rowsRemaining > 0 @@ -169,10 +163,9 @@ class TokenizedBuffer extends Model return invalidateRow: (row) -> - if @grammar.name isnt 'Null Grammar' and not @largeFileMode - @invalidRows.push(row) - @invalidRows.sort (a, b) -> a - b - @tokenizeInBackground() + @invalidRows.push(row) + @invalidRows.sort (a, b) -> a - b + @tokenizeInBackground() updateInvalidRows: (start, end, delta) -> @invalidRows = @invalidRows.map (row) -> @@ -190,19 +183,19 @@ class TokenizedBuffer extends Model start = oldRange.start.row end = oldRange.end.row delta = newRange.end.row - oldRange.end.row + oldLineCount = oldRange.end.row - oldRange.start.row + 1 + newLineCount = newRange.end.row - newRange.start.row + 1 @updateInvalidRows(start, end, delta) previousEndStack = @stackForRow(end) # used in spill detection below if @largeFileMode or @grammar.name is 'Null Grammar' - lineCount = ((end + delta) - start) + 1 - newTokenizedLines = new Array(lineCount) + _.spliceWithArray(@tokenizedLines, start, oldLineCount, new Array(newLineCount)) else newTokenizedLines = @buildTokenizedLinesForRows(start, end + delta, @stackForRow(start - 1), @openScopesForRow(start)) - _.spliceWithArray(@tokenizedLines, start, end - start + 1, newTokenizedLines) - - newEndStack = @stackForRow(end + delta) - if newEndStack and not _.isEqual(newEndStack, previousEndStack) - @invalidateRow(end + delta + 1) + _.spliceWithArray(@tokenizedLines, start, oldLineCount, newTokenizedLines) + newEndStack = @stackForRow(end + delta) + if newEndStack and not _.isEqual(newEndStack, previousEndStack) + @invalidateRow(end + delta + 1) isFoldableAtRow: (row) -> if @largeFileMode From c2363010f8cb9d9b5d35c52053f6de7f469b9615 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Oct 2016 11:38:36 -0700 Subject: [PATCH 42/88] Map out-of-range pixel positions to valid columns --- spec/lines-yardstick-spec.coffee | 6 ++++-- src/lines-yardstick.coffee | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index ed50451e7..483a77675 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -40,11 +40,13 @@ describe "LinesYardstick", -> mockLineNodesProvider = lineNodesById: {} + lineIdForScreenRow: (screenRow) -> - editor.screenLineForScreenRow(screenRow).id + editor.screenLineForScreenRow(screenRow)?.id lineNodeForScreenRow: (screenRow) -> - @lineNodesById[@lineIdForScreenRow(screenRow)] ?= buildLineNode(screenRow) + if id = @lineIdForScreenRow(screenRow) + @lineNodesById[id] ?= buildLineNode(screenRow) textNodesForScreenRow: (screenRow) -> lineNode = @lineNodeForScreenRow(screenRow) diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 563cd5923..d4979865c 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -20,8 +20,9 @@ class LinesYardstick row = Math.max(0, @lineTopIndex.rowForPixelPosition(targetTop)) lineNode = @lineNodesProvider.lineNodeForScreenRow(row) unless lineNode - if row > @model.getLastScreenRow() - return Point(@model.getLastScreenRow(), Infinity) + lastScreenRow = @model.getLastScreenRow() + if row > lastScreenRow + return Point(lastScreenRow, @model.lineLengthForScreenRow(lastScreenRow)) else return Point(row, 0) From 8d6bc5be4849980c0779ab4de4d87fb3cb4da13e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Oct 2016 11:39:16 -0700 Subject: [PATCH 43/88] Remove unnecessary guard in screenLineForScreenRow --- src/text-editor.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 5445643d9..886a66592 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -969,8 +969,7 @@ class TextEditor extends Model tokens screenLineForScreenRow: (screenRow) -> - result = @displayLayer.getScreenLines(screenRow, screenRow + 1) - result[0] if result + @displayLayer.getScreenLines(screenRow, screenRow + 1)[0] bufferRowForScreenRow: (screenRow) -> @displayLayer.translateScreenPosition(Point(screenRow, 0)).row From beaab4eb2438edf801b84c25344b9f2402e1e8f2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 Oct 2016 13:28:42 -0700 Subject: [PATCH 44/88] :arrow_up: text-buffer (prerelease) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cce7cdcbe..90a3b29ad 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "9.3.1-3", + "text-buffer": "9.3.1-4", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", From fa90851e14226daabf1d60610e9addb96a9fddd1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 13 Oct 2016 15:57:40 +0200 Subject: [PATCH 45/88] Implement `atom --benchmark` --- benchmarks/benchmark-runner.js | 60 +++++++++++++++++ package.json | 2 + src/initialize-benchmark-window.js | 86 ++++++++++++++++++++++++ src/main-process/atom-application.coffee | 55 ++++++++++++--- src/main-process/main.js | 2 +- src/main-process/parse-command-line.js | 4 +- src/main-process/start.js | 2 +- 7 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 benchmarks/benchmark-runner.js create mode 100644 src/initialize-benchmark-window.js diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js new file mode 100644 index 000000000..a3a4a3f4e --- /dev/null +++ b/benchmarks/benchmark-runner.js @@ -0,0 +1,60 @@ +/** @babel */ + +import Chart from 'chart.js' +import glob from 'glob' +import fs from 'fs-plus' +import path from 'path' + +export default async function (benchmarkPaths) { + let paths = [] + for (const benchmarkPath of benchmarkPaths) { + if (fs.isDirectorySync(benchmarkPath)) { + paths = paths.concat(glob.sync(path.join(benchmarkPath, '**', '*.bench.js'))) + } else { + paths.push(benchmarkPath) + } + } + + document.body.style.backgroundColor = '#ffffff' + while (paths.length > 0) { + const benchmark = require(paths.shift())() + let results + if (benchmark instanceof Promise) { + results = await benchmark + } else { + results = benchmark + } + + const dataByBenchmarkName = {} + for (const {name, duration, x} of results) { + dataByBenchmarkName[name] = dataByBenchmarkName[name] || {points: []} + dataByBenchmarkName[name].points.push({x, y: duration}) + } + + const benchmarkContainer = document.createElement('div') + document.body.appendChild(benchmarkContainer) + for (const key in dataByBenchmarkName) { + const data = dataByBenchmarkName[key] + if (data.points.length > 1) { + const canvas = document.createElement('canvas') + benchmarkContainer.appendChild(canvas) + const chart = new Chart(canvas, { + type: 'line', + data: { + labels: data.points.map((p) => p.x), + datasets: [{label: key, data: data.points}] + } + }) + } else { + const title = document.createElement('h2') + title.textContent = key + benchmarkContainer.appendChild(title) + const duration = document.createElement('p') + duration.textContent = `${data.points[0].y}ms` + benchmarkContainer.appendChild(duration) + } + + global.atom.reset() + } + } +} diff --git a/package.json b/package.json index 108db5904..6b1357157 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "babel-core": "5.8.38", "cached-run-in-this-context": "0.4.1", "chai": "3.5.0", + "chart.js": "^2.3.0", "clear-cut": "^2.0.1", "coffee-script": "1.11.1", "color": "^0.7.3", @@ -32,6 +33,7 @@ "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "^4.1.2", + "glob": "^7.1.1", "grim": "1.5.0", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js new file mode 100644 index 000000000..493caab15 --- /dev/null +++ b/src/initialize-benchmark-window.js @@ -0,0 +1,86 @@ +/** @babel */ + +import {ipcRenderer, remote} from 'electron' +import path from 'path' +import ipcHelpers from './ipc-helpers' + +export default function () { + const {getWindowLoadSettings} = require('./window-load-settings-helpers') + const {headless, resourcePath, benchmarkPaths} = getWindowLoadSettings() + try { + const Clipboard = require('../src/clipboard') + const ApplicationDelegate = require('../src/application-delegate') + const AtomEnvironment = require('../src/atom-environment') + const TextEditor = require('../src/text-editor') + + const exportsPath = path.join(resourcePath, 'exports') + require('module').globalPaths.push(exportsPath) // Add 'exports' to module search path. + process.env.NODE_PATH = exportsPath // Set NODE_PATH env variable since tasks may need it. + + document.title = 'Benchmarks' + window.addEventListener('keydown', (event) => { + // Quit: cmd-q / ctrl-q + if ((event.metaKey || event.ctrlKey) && event.keyCode === 81) { + ipcRenderer.send('command', 'application:quit') + } + + // Reload: cmd-r / ctrl-r + if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) { + ipcHelpers.call('window-method', 'reload') + } + + // Toggle Dev Tools: cmd-alt-i (Mac) / ctrl-shift-i (Linux/Windows) + if (event.keyCode === 73) { + const isDarwin = process.platform === 'darwin' + if ((isDarwin && event.metaKey && event.altKey) || (!isDarwin && event.ctrlKey && event.shiftKey)) { + ipcHelpers.call('window-method', 'toggleDevTools') + } + } + + // Close: cmd-w / ctrl-w + if ((event.metaKey || event.ctrlKey) && event.keyCode === 87) { + ipcHelpers.call('window-method', 'close') + } + + // Copy: cmd-c / ctrl-c + if ((event.metaKey || event.ctrlKey) && event.keyCode === 67) { + ipcHelpers.call('window-method', 'copy') + } + }, true) + + const clipboard = new Clipboard() + TextEditor.setClipboard(clipboard) + + const applicationDelegate = new ApplicationDelegate() + global.atom = new AtomEnvironment({ + applicationDelegate, window, document, clipboard, + configDirPath: process.env.ATOM_HOME, enablePersistence: false + }) + + // Prevent benchmarks from modifying application menus + global.atom.menu.sendToBrowserProcess = function () { } + + if (!headless) { + remote.getCurrentWindow().show() + } + + const benchmarkRunner = require('../benchmarks/benchmark-runner') + return benchmarkRunner(benchmarkPaths).then((code) => { + if (headless) { + exitWithStatusCode(code) + } + }) + } catch (e) { + if (headless) { + console.error(error.stack || error) + exitWithStatusCode(1) + } else { + throw e + } + } +} + +function exitWithStatusCode (code) { + remote.app.emit('will-quit') + remote.process.exit(status) +} diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index 21f138eed..b8caa23ba 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -42,7 +42,7 @@ class AtomApplication # take a few seconds to trigger 'error' event, it could be a bug of node # or atom-shell, before it's fixed we check the existence of socketPath to # speedup startup. - if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test + if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test or options.benchmark new AtomApplication(options).initialize(options) return @@ -86,7 +86,7 @@ class AtomApplication @config.onDidChange 'core.useCustomTitleBar', @promptForRestart.bind(this) - @autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config) + @autoUpdateManager = new AutoUpdateManager(@version, options.test or options.benchmark, @resourcePath, @config) @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) @@ -103,23 +103,39 @@ class AtomApplication Promise.all(windowsClosePromises).then(=> @disposable.dispose()) launch: (options) -> - if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test + if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test or options.benchmark @openWithOptions(options) else @loadState(options) or @openPath(options) - openWithOptions: ({initialPaths, pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env}) -> + openWithOptions: (options) -> + {initialPaths, pathsToOpen, executedFrom, urlsToOpen, benchmark, test, + pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, + timeout, clearWindowState, addToLastWindow, env} = options + app.focus() if test - @runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout, env}) + @runTests({ + headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, + logFile, timeout, env + }) + else if benchmark + @runBenchmarks({headless: false, @resourcePath, executedFrom, pathsToOpen, timeout, env}) else if pathsToOpen.length > 0 - @openPaths({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env}) + @openPaths({ + initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, + devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env + }) else if urlsToOpen.length > 0 - @openUrl({urlToOpen, devMode, safeMode, env}) for urlToOpen in urlsToOpen + for urlToOpen in urlsToOpen + @openUrl({urlToOpen, devMode, safeMode, env}) else # Always open a editor window if this is the first instance of Atom. - @openPath({initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow, env}) + @openPath({ + initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, + clearWindowState, addToLastWindow, env + }) # Public: Removes the {AtomWindow} from the global window list. removeWindow: (window) -> @@ -651,6 +667,29 @@ class AtomApplication safeMode ?= false new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode, env}) + runBenchmarks: ({headless, resourcePath, executedFrom, pathsToOpen, env}) -> + if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath) + resourcePath = @resourcePath + + try + windowInitializationScript = require.resolve(path.resolve(@devResourcePath, 'src', 'initialize-benchmark-window')) + catch error + windowInitializationScript = require.resolve(path.resolve(__dirname, '..', '..', 'src', 'initialize-benchmark-window')) + + benchmarkPaths = [] + if pathsToOpen? + for pathToOpen in pathsToOpen + benchmarkPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen))) + + if benchmarkPaths.length is 0 + process.stderr.write 'Error: Specify at least one benchmark path.\n\n' + process.exit(1) + + devMode = true + isSpec = true + safeMode = false + new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, benchmarkPaths, safeMode, env}) + resolveTestRunnerPath: (testPath) -> FindParentDir ?= require 'find-parent-dir' diff --git a/src/main-process/main.js b/src/main-process/main.js index 28871b661..5823b2e27 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -19,7 +19,7 @@ if (args.resourcePath) { const stableResourcePath = path.dirname(path.dirname(__dirname)) const defaultRepositoryPath = path.join(electron.app.getPath('home'), 'github', 'atom') - if (args.dev || args.test) { + if (args.dev || args.test || args.benchmark) { if (process.env.ATOM_DEV_RESOURCE_PATH) { resourcePath = process.env.ATOM_DEV_RESOURCE_PATH } else if (fs.statSyncNoException(defaultRepositoryPath)) { diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js index 07b2b49f8..db6991a1e 100644 --- a/src/main-process/parse-command-line.js +++ b/src/main-process/parse-command-line.js @@ -45,6 +45,7 @@ module.exports = function parseCommandLine (processArgs) { 'portable', 'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.' ) + options.boolean('benchmark').describe('benchmark', 'Run the specified benchmarks.') options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.') options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.') options.string('timeout').describe( @@ -78,6 +79,7 @@ module.exports = function parseCommandLine (processArgs) { const addToLastWindow = args['add'] const safeMode = args['safe'] const pathsToOpen = args._ + const benchmark = args['benchmark'] const test = args['test'] const mainProcess = args['main-process'] const timeout = args['timeout'] @@ -135,7 +137,7 @@ module.exports = function parseCommandLine (processArgs) { resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath, userDataDir, profileStartup, timeout, setPortable, clearWindowState, - addToLastWindow, mainProcess, env: process.env + addToLastWindow, mainProcess, benchmark, env: process.env } } diff --git a/src/main-process/start.js b/src/main-process/start.js index 125f3ed54..bae9a9927 100644 --- a/src/main-process/start.js +++ b/src/main-process/start.js @@ -57,7 +57,7 @@ module.exports = function start (resourcePath, startTime) { if (args.userDataDir != null) { app.setPath('userData', args.userDataDir) - } else if (args.test) { + } else if (args.test || args.benchmark) { app.setPath('userData', temp.mkdirSync('atom-test-data')) } From 40d9bc7cb88262970936c3187de71767f33b72d8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 13 Oct 2016 16:11:56 +0200 Subject: [PATCH 46/88] Don't fill area under lines and make the window scrollable --- benchmarks/benchmark-runner.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index a3a4a3f4e..cdef57787 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -6,6 +6,9 @@ import fs from 'fs-plus' import path from 'path' export default async function (benchmarkPaths) { + document.body.style.backgroundColor = '#ffffff' + document.body.style.overflow = 'auto' + let paths = [] for (const benchmarkPath of benchmarkPaths) { if (fs.isDirectorySync(benchmarkPath)) { @@ -15,7 +18,6 @@ export default async function (benchmarkPaths) { } } - document.body.style.backgroundColor = '#ffffff' while (paths.length > 0) { const benchmark = require(paths.shift())() let results @@ -42,7 +44,7 @@ export default async function (benchmarkPaths) { type: 'line', data: { labels: data.points.map((p) => p.x), - datasets: [{label: key, data: data.points}] + datasets: [{label: key, fill: false, data: data.points}] } }) } else { From bb0a0cdb54eb2cd924c86c9eaaddfc060d5c91af Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 13 Oct 2016 16:14:55 +0200 Subject: [PATCH 47/88] Add a `TextEditor` benchmark for large files construction --- ...xt-editor-large-file-construction.bench.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 benchmarks/text-editor-large-file-construction.bench.js diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js new file mode 100644 index 000000000..27847b675 --- /dev/null +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -0,0 +1,25 @@ +/** @babel */ + +import fs from 'fs' +import temp from 'temp' +import {TextEditor, TextBuffer} from 'atom' + +export default function () { + const data = [] + const maxLineCount = 10000 + const step = 500 + const lineText = 'Lorem ipsum dolor sit amet\n' + const sampleText = lineText.repeat(maxLineCount) + for (let lineCount = 0; lineCount <= maxLineCount; lineCount += step) { + const text = sampleText.slice(0, lineText.length * lineCount) + const buffer = new TextBuffer(text) + const t0 = window.performance.now() + const editor = new TextEditor({buffer, largeFileMode: true}) + document.body.appendChild(editor.element) + const t1 = window.performance.now() + data.push({name: 'Opening and rendering a TextEditor', x: lineCount, duration: t1 - t0}) + editor.element.remove() + editor.destroy() + } + return data +} From 42992032da507f5460b4aeee684316b5a5af45f3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 13 Oct 2016 16:43:26 +0200 Subject: [PATCH 48/88] Add textual output to benchmark runner --- benchmarks/benchmark-runner.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index cdef57787..3ec6ade48 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -47,6 +47,9 @@ export default async function (benchmarkPaths) { datasets: [{label: key, fill: false, data: data.points}] } }) + + const textualOutput = `${key}:\n` + data.points.map((p) => ` (${p.x}, ${p.y})`).join('\n') + console.log(textualOutput) } else { const title = document.createElement('h2') title.textContent = key @@ -54,6 +57,9 @@ export default async function (benchmarkPaths) { const duration = document.createElement('p') duration.textContent = `${data.points[0].y}ms` benchmarkContainer.appendChild(duration) + + const textualOutput = `${key}: ${data.points[0].y}` + console.log(textualOutput) } global.atom.reset() From 1bdd79d7197235dc1c0e79cc4535ab5a3be66651 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 13 Oct 2016 17:09:54 +0200 Subject: [PATCH 49/88] Implement `atom --benchmark-test` to ensure benchmarks are valid on CI --- atom.sh | 2 +- benchmarks/benchmark-runner.js | 4 +-- ...xt-editor-large-file-construction.bench.js | 6 ++--- resources/win/atom.cmd | 18 +++++++------ src/initialize-benchmark-window.js | 26 ++++++++++++++++--- src/main-process/atom-application.coffee | 24 ++++++++++------- src/main-process/main.js | 2 +- src/main-process/parse-command-line.js | 6 +++-- src/main-process/start.js | 2 +- 9 files changed, 59 insertions(+), 31 deletions(-) diff --git a/atom.sh b/atom.sh index 0655e343e..a8c30fa19 100755 --- a/atom.sh +++ b/atom.sh @@ -28,7 +28,7 @@ while getopts ":wtfvh-:" opt; do REDIRECT_STDERR=1 EXPECT_OUTPUT=1 ;; - foreground|test) + foreground|benchmark|benchmark-test|test) EXPECT_OUTPUT=1 ;; esac diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index 3ec6ade48..b218f8423 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -5,7 +5,7 @@ import glob from 'glob' import fs from 'fs-plus' import path from 'path' -export default async function (benchmarkPaths) { +export default async function ({test, benchmarkPaths}) { document.body.style.backgroundColor = '#ffffff' document.body.style.overflow = 'auto' @@ -19,7 +19,7 @@ export default async function (benchmarkPaths) { } while (paths.length > 0) { - const benchmark = require(paths.shift())() + const benchmark = require(paths.shift())({test}) let results if (benchmark instanceof Promise) { results = await benchmark diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js index 27847b675..fced34de0 100644 --- a/benchmarks/text-editor-large-file-construction.bench.js +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -4,10 +4,10 @@ import fs from 'fs' import temp from 'temp' import {TextEditor, TextBuffer} from 'atom' -export default function () { +export default function ({test}) { const data = [] - const maxLineCount = 10000 - const step = 500 + const maxLineCount = test ? 5 : 10000 + const step = test ? 1 : 500 const lineText = 'Lorem ipsum dolor sit amet\n' const sampleText = lineText.repeat(maxLineCount) for (let lineCount = 0; lineCount <= maxLineCount; lineCount += step) { diff --git a/resources/win/atom.cmd b/resources/win/atom.cmd index 73c4ddb01..43ec8ebe3 100644 --- a/resources/win/atom.cmd +++ b/resources/win/atom.cmd @@ -5,14 +5,16 @@ SET WAIT= SET PSARGS=%* FOR %%a IN (%*) DO ( - IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES - IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-f" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--foreground" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-h" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--help" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-t" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--test" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--benchmark" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--benchmark-test" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="-v" SET EXPECT_OUTPUT=YES + IF /I "%%a"=="--version" SET EXPECT_OUTPUT=YES IF /I "%%a"=="-w" ( SET EXPECT_OUTPUT=YES SET WAIT=YES diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index 493caab15..a315703b7 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -3,10 +3,11 @@ import {ipcRenderer, remote} from 'electron' import path from 'path' import ipcHelpers from './ipc-helpers' +import util from 'util' export default function () { const {getWindowLoadSettings} = require('./window-load-settings-helpers') - const {headless, resourcePath, benchmarkPaths} = getWindowLoadSettings() + const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings() try { const Clipboard = require('../src/clipboard') const ApplicationDelegate = require('../src/application-delegate') @@ -58,14 +59,33 @@ export default function () { }) // Prevent benchmarks from modifying application menus + global.atom.menu.update() global.atom.menu.sendToBrowserProcess = function () { } - if (!headless) { + if (headless) { + Object.defineProperties(process, { + stdout: { value: remote.process.stdout }, + stderr: { value: remote.process.stderr } + }) + + console.log = function (...args) { + const formatted = util.format(...args) + process.stdout.write(formatted + "\n") + } + console.warn = function (...args) { + const formatted = util.format(...args) + process.stderr.write(formatted + "\n") + } + console.error = function (...args) { + const formatted = util.format(...args) + process.stderr.write(formatted + "\n") + } + } else { remote.getCurrentWindow().show() } const benchmarkRunner = require('../benchmarks/benchmark-runner') - return benchmarkRunner(benchmarkPaths).then((code) => { + return benchmarkRunner({test, benchmarkPaths}).then((code) => { if (headless) { exitWithStatusCode(code) } diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index b8caa23ba..cd57e6314 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -42,7 +42,7 @@ class AtomApplication # take a few seconds to trigger 'error' event, it could be a bug of node # or atom-shell, before it's fixed we check the existence of socketPath to # speedup startup. - if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test or options.benchmark + if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test or options.benchmark or options.benchmarkTest new AtomApplication(options).initialize(options) return @@ -86,7 +86,9 @@ class AtomApplication @config.onDidChange 'core.useCustomTitleBar', @promptForRestart.bind(this) - @autoUpdateManager = new AutoUpdateManager(@version, options.test or options.benchmark, @resourcePath, @config) + @autoUpdateManager = new AutoUpdateManager( + @version, options.test or options.benchmark or options.benchmarkTest, @resourcePath, @config + ) @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) @@ -103,15 +105,17 @@ class AtomApplication Promise.all(windowsClosePromises).then(=> @disposable.dispose()) launch: (options) -> - if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test or options.benchmark + if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test or options.benchmark or options.benchmarkTest @openWithOptions(options) else @loadState(options) or @openPath(options) openWithOptions: (options) -> - {initialPaths, pathsToOpen, executedFrom, urlsToOpen, benchmark, test, - pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, - timeout, clearWindowState, addToLastWindow, env} = options + { + initialPaths, pathsToOpen, executedFrom, urlsToOpen, benchmark, + benchmarkTest, test, pidToKillWhenClosed, devMode, safeMode, newWindow, + logFile, profileStartup, timeout, clearWindowState, addToLastWindow, env + } = options app.focus() @@ -120,8 +124,8 @@ class AtomApplication headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout, env }) - else if benchmark - @runBenchmarks({headless: false, @resourcePath, executedFrom, pathsToOpen, timeout, env}) + else if benchmark or benchmarkTest + @runBenchmarks({headless: true, test: benchmarkTest, @resourcePath, executedFrom, pathsToOpen, timeout, env}) else if pathsToOpen.length > 0 @openPaths({ initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, @@ -667,7 +671,7 @@ class AtomApplication safeMode ?= false new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode, env}) - runBenchmarks: ({headless, resourcePath, executedFrom, pathsToOpen, env}) -> + runBenchmarks: ({headless, test, resourcePath, executedFrom, pathsToOpen, env}) -> if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath) resourcePath = @resourcePath @@ -688,7 +692,7 @@ class AtomApplication devMode = true isSpec = true safeMode = false - new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, benchmarkPaths, safeMode, env}) + new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, test, isSpec, devMode, benchmarkPaths, safeMode, env}) resolveTestRunnerPath: (testPath) -> FindParentDir ?= require 'find-parent-dir' diff --git a/src/main-process/main.js b/src/main-process/main.js index 5823b2e27..7ccd1a6c3 100644 --- a/src/main-process/main.js +++ b/src/main-process/main.js @@ -19,7 +19,7 @@ if (args.resourcePath) { const stableResourcePath = path.dirname(path.dirname(__dirname)) const defaultRepositoryPath = path.join(electron.app.getPath('home'), 'github', 'atom') - if (args.dev || args.test || args.benchmark) { + if (args.dev || args.test || args.benchmark || args.benchmarkTest) { if (process.env.ATOM_DEV_RESOURCE_PATH) { resourcePath = process.env.ATOM_DEV_RESOURCE_PATH } else if (fs.statSyncNoException(defaultRepositoryPath)) { diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js index db6991a1e..2f10ae3bb 100644 --- a/src/main-process/parse-command-line.js +++ b/src/main-process/parse-command-line.js @@ -45,7 +45,8 @@ module.exports = function parseCommandLine (processArgs) { 'portable', 'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.' ) - options.boolean('benchmark').describe('benchmark', 'Run the specified benchmarks.') + options.boolean('benchmark').describe('benchmark', 'Open a new window that runs the specified benchmarks.') + options.boolean('benchmark-test').describe('benchmark--test', 'Run a faster version of the benchmarks in headless mode.') options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.') options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.') options.string('timeout').describe( @@ -80,6 +81,7 @@ module.exports = function parseCommandLine (processArgs) { const safeMode = args['safe'] const pathsToOpen = args._ const benchmark = args['benchmark'] + const benchmarkTest = args['benchmark-test'] const test = args['test'] const mainProcess = args['main-process'] const timeout = args['timeout'] @@ -137,7 +139,7 @@ module.exports = function parseCommandLine (processArgs) { resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath, userDataDir, profileStartup, timeout, setPortable, clearWindowState, - addToLastWindow, mainProcess, benchmark, env: process.env + addToLastWindow, mainProcess, benchmark, benchmarkTest, env: process.env } } diff --git a/src/main-process/start.js b/src/main-process/start.js index bae9a9927..84ae9b8c2 100644 --- a/src/main-process/start.js +++ b/src/main-process/start.js @@ -57,7 +57,7 @@ module.exports = function start (resourcePath, startTime) { if (args.userDataDir != null) { app.setPath('userData', args.userDataDir) - } else if (args.test || args.benchmark) { + } else if (args.test || args.benchmark || args.benchmarkTest) { app.setPath('userData', temp.mkdirSync('atom-test-data')) } From 5211b49f7de35e15c7006b63de8585e773407049 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 13 Oct 2016 19:52:02 +0200 Subject: [PATCH 50/88] Add a "Run Benchmarks" command to plot benchmark results --- menus/darwin.cson | 1 + src/initialize-benchmark-window.js | 6 ------ src/main-process/atom-application.coffee | 3 +++ src/register-default-commands.coffee | 1 + src/workspace-element.coffee | 9 +++++++++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/menus/darwin.cson b/menus/darwin.cson index ccbb5f7a6..b967220c0 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -147,6 +147,7 @@ { label: 'Open In Dev Mode…', command: 'application:open-dev' } { label: 'Reload Window', command: 'window:reload' } { label: 'Run Package Specs', command: 'window:run-package-specs' } + { label: 'Run Benchmarks', command: 'window:run-benchmarks' } { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index a315703b7..580dbb2fa 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -20,11 +20,6 @@ export default function () { document.title = 'Benchmarks' window.addEventListener('keydown', (event) => { - // Quit: cmd-q / ctrl-q - if ((event.metaKey || event.ctrlKey) && event.keyCode === 81) { - ipcRenderer.send('command', 'application:quit') - } - // Reload: cmd-r / ctrl-r if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) { ipcHelpers.call('window-method', 'reload') @@ -59,7 +54,6 @@ export default function () { }) // Prevent benchmarks from modifying application menus - global.atom.menu.update() global.atom.menu.sendToBrowserProcess = function () { } if (headless) { diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index cd57e6314..47d336957 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -300,6 +300,9 @@ class AtomApplication @disposable.add ipcHelpers.on ipcMain, 'run-package-specs', (event, packageSpecPath) => @runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false}) + @disposable.add ipcHelpers.on ipcMain, 'run-benchmarks', (event, benchmarksPath) => + @runBenchmarks({resourcePath: @devResourcePath, pathsToOpen: [benchmarksPath], headless: false, test: false}) + @disposable.add ipcHelpers.on ipcMain, 'command', (event, command) => @emit(command) diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 3ba50d497..8196d9237 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -54,6 +54,7 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage 'application:open-your-stylesheet': -> ipcRenderer.send('command', 'application:open-your-stylesheet') 'application:open-license': -> @getModel().openLicense() 'window:run-package-specs': -> @runPackageSpecs() + 'window:run-benchmarks': -> @runBenchmarks() 'window:focus-next-pane': -> @getModel().activateNextPane() 'window:focus-previous-pane': -> @getModel().activatePreviousPane() 'window:focus-pane-above': -> @focusPaneViewAbove() diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee index ab8e39532..be0af81ed 100644 --- a/src/workspace-element.coffee +++ b/src/workspace-element.coffee @@ -142,4 +142,13 @@ class WorkspaceElement extends HTMLElement ipcRenderer.send('run-package-specs', specPath) + runBenchmarks: -> + if activePath = @workspace.getActivePaneItem()?.getPath?() + [projectPath] = @project.relativizePath(activePath) + else + [projectPath] = @project.getPaths() + + if projectPath + ipcRenderer.send('run-benchmarks', path.join(projectPath, 'benchmarks')) + module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype From 80189b48263fc9868aa04452ef95d54371a8c49b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 10:47:00 +0200 Subject: [PATCH 51/88] Catch benchmarks errors and exit with `statusCode = 1` --- benchmarks/benchmark-runner.js | 2 ++ src/initialize-benchmark-window.js | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index b218f8423..0fdb47549 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -65,4 +65,6 @@ export default async function ({test, benchmarkPaths}) { global.atom.reset() } } + + return 0 } diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index 580dbb2fa..ccaced194 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -5,7 +5,7 @@ import path from 'path' import ipcHelpers from './ipc-helpers' import util from 'util' -export default function () { +export default async function () { const {getWindowLoadSettings} = require('./window-load-settings-helpers') const {test, headless, resourcePath, benchmarkPaths} = getWindowLoadSettings() try { @@ -19,6 +19,13 @@ export default function () { process.env.NODE_PATH = exportsPath // Set NODE_PATH env variable since tasks may need it. document.title = 'Benchmarks' + // Allow `document.title` to be assigned in benchmarks without actually changing the window title. + let documentTitle = null + Object.defineProperty(document, 'title', { + get () { return documentTitle }, + set (title) { documentTitle = title } + }) + window.addEventListener('keydown', (event) => { // Reload: cmd-r / ctrl-r if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) { @@ -79,22 +86,22 @@ export default function () { } const benchmarkRunner = require('../benchmarks/benchmark-runner') - return benchmarkRunner({test, benchmarkPaths}).then((code) => { - if (headless) { - exitWithStatusCode(code) - } - }) - } catch (e) { + const statusCode = await benchmarkRunner({test, benchmarkPaths}) + if (headless) { + exitWithStatusCode(statusCode) + } + } catch (error) { if (headless) { console.error(error.stack || error) exitWithStatusCode(1) } else { + ipcHelpers.call('window-method', 'openDevTools') throw e } } } -function exitWithStatusCode (code) { +function exitWithStatusCode (statusCode) { remote.app.emit('will-quit') - remote.process.exit(status) + remote.process.exit(statusCode) } From a0c7c63e92fd97754d313ef086fc4c4ef68c16a5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 11:01:35 +0200 Subject: [PATCH 52/88] Run benchmarks tests during `script/test` on macOS --- script/test | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/script/test b/script/test index ccb5dce86..38568207f 100755 --- a/script/test +++ b/script/test @@ -83,9 +83,19 @@ for (let packageName in CONFIG.appMetadata.packageDependencies) { }) } +function runBenchmarkTests (callback) { + const benchmarksPath = path.join(CONFIG.repositoryRootPath, 'benchmarks') + const testArguments = ['--benchmark-test', benchmarksPath] + + console.log('Executing benchmark tests'.bold.green) + const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit'}) + cp.on('error', error => { callback(error) }) + cp.on('close', exitCode => { callback(null, exitCode) }) +} + let testSuitesToRun if (process.platform === 'darwin') { - testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests].concat(packageTestSuites) + testSuitesToRun = [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites) } else { testSuitesToRun = [runCoreMainProcessTests] } From 1d1a6bf1a7da2e34a8b14c8c39a328026cfef0b4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 11:18:01 +0200 Subject: [PATCH 53/88] Make textual output CSV friendly --- benchmarks/benchmark-runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index 0fdb47549..9f23851a1 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -48,7 +48,7 @@ export default async function ({test, benchmarkPaths}) { } }) - const textualOutput = `${key}:\n` + data.points.map((p) => ` (${p.x}, ${p.y})`).join('\n') + const textualOutput = `${key}:\n` + data.points.map((p) => ` ${p.x};${p.y}`).join('\n') console.log(textualOutput) } else { const title = document.createElement('h2') From 6c65f88bc30da6358d476e9b5a011da79592470e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 11:44:53 +0200 Subject: [PATCH 54/88] Copy and transpile benchmarks/benchmark-runner.js --- script/lib/copy-assets.js | 1 + script/lib/transpile-babel-paths.js | 1 + 2 files changed, 2 insertions(+) diff --git a/script/lib/copy-assets.js b/script/lib/copy-assets.js index 3c33ad13a..28357ee9a 100644 --- a/script/lib/copy-assets.js +++ b/script/lib/copy-assets.js @@ -12,6 +12,7 @@ const includePathInPackagedApp = require('./include-path-in-packaged-app') module.exports = function () { console.log(`Copying assets to ${CONFIG.intermediateAppPath}`); let srcPaths = [ + path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'), path.join(CONFIG.repositoryRootPath, 'dot-atom'), path.join(CONFIG.repositoryRootPath, 'exports'), path.join(CONFIG.repositoryRootPath, 'node_modules'), diff --git a/script/lib/transpile-babel-paths.js b/script/lib/transpile-babel-paths.js index 0a987e124..3c440a4bd 100644 --- a/script/lib/transpile-babel-paths.js +++ b/script/lib/transpile-babel-paths.js @@ -25,6 +25,7 @@ module.exports = function () { function getPathsToTranspile () { let paths = [] + paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'benchmarks', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'exports', '**', '*.js'))) paths = paths.concat(glob.sync(path.join(CONFIG.intermediateAppPath, 'src', '**', '*.js'))) for (let packageName of Object.keys(CONFIG.appMetadata.packageDependencies)) { From a36e9883d8c0862f4f642ac73d425e1e0b564d16 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 11:51:57 +0200 Subject: [PATCH 55/88] :art: --- benchmarks/benchmark-runner.js | 7 +++++-- benchmarks/text-editor-large-file-construction.bench.js | 9 ++++++--- src/initialize-benchmark-window.js | 2 +- src/main-process/atom-application.coffee | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/benchmarks/benchmark-runner.js b/benchmarks/benchmark-runner.js index 9f23851a1..30b23ffbf 100644 --- a/benchmarks/benchmark-runner.js +++ b/benchmarks/benchmark-runner.js @@ -43,12 +43,15 @@ export default async function ({test, benchmarkPaths}) { const chart = new Chart(canvas, { type: 'line', data: { - labels: data.points.map((p) => p.x), datasets: [{label: key, fill: false, data: data.points}] + }, + options: { + showLines: false, + scales: {xAxes: [{type: 'linear', position: 'bottom'}]} } }) - const textualOutput = `${key}:\n` + data.points.map((p) => ` ${p.x};${p.y}`).join('\n') + const textualOutput = `${key}:\n\n` + data.points.map((p) => `${p.x}\t${p.y}`).join('\n') console.log(textualOutput) } else { const title = document.createElement('h2') diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js index fced34de0..f37255ca4 100644 --- a/benchmarks/text-editor-large-file-construction.bench.js +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -6,8 +6,8 @@ import {TextEditor, TextBuffer} from 'atom' export default function ({test}) { const data = [] - const maxLineCount = test ? 5 : 10000 - const step = test ? 1 : 500 + const maxLineCount = test ? 5 : 5000 + const step = test ? 1 : 125 const lineText = 'Lorem ipsum dolor sit amet\n' const sampleText = lineText.repeat(maxLineCount) for (let lineCount = 0; lineCount <= maxLineCount; lineCount += step) { @@ -17,7 +17,10 @@ export default function ({test}) { const editor = new TextEditor({buffer, largeFileMode: true}) document.body.appendChild(editor.element) const t1 = window.performance.now() - data.push({name: 'Opening and rendering a TextEditor', x: lineCount, duration: t1 - t0}) + data.push({ + name: 'Opening and rendering a TextEditor in large file mode', + x: lineCount, duration: t1 - t0 + }) editor.element.remove() editor.destroy() } diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index ccaced194..55bda75fa 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -96,7 +96,7 @@ export default async function () { exitWithStatusCode(1) } else { ipcHelpers.call('window-method', 'openDevTools') - throw e + throw error } } } diff --git a/src/main-process/atom-application.coffee b/src/main-process/atom-application.coffee index 47d336957..aaceebffe 100644 --- a/src/main-process/atom-application.coffee +++ b/src/main-process/atom-application.coffee @@ -64,7 +64,7 @@ class AtomApplication constructor: (options) -> {@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, @logFile, @setPortable, @userDataDir} = options - @socketPath = null if options.test + @socketPath = null if options.test or options.benchmark or options.benchmarkTest @pidsToOpenWindows = {} @windows = [] From c54b330d68e718d695b697efd97f5c07be85334e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 13:54:10 +0200 Subject: [PATCH 56/88] Update eslint and fix new `script/lint` errors --- package.json | 2 -- script/package.json | 3 +-- src/initialize-benchmark-window.js | 16 +++++++++------ src/main-process/parse-command-line.js | 27 ++++++++++++++++++++++---- src/native-compile-cache.js | 2 +- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 6b1357157..6361f561c 100644 --- a/package.json +++ b/package.json @@ -163,8 +163,6 @@ "test": "node script/test" }, "standard": { - "ignore": [], - "parser": "babel-eslint", "globals": [ "atom", "afterEach", diff --git a/script/package.json b/script/package.json index 0ddc54928..1f4cf782f 100644 --- a/script/package.json +++ b/script/package.json @@ -4,7 +4,6 @@ "dependencies": { "async": "2.0.1", "babel-core": "5.8.38", - "babel-eslint": "6.1.2", "coffeelint": "1.15.7", "colors": "1.1.2", "csslint": "1.0.2", @@ -24,7 +23,7 @@ "runas": "3.1.1", "season": "5.3.0", "semver": "5.3.0", - "standard": "6.0.0", + "standard": "8.4.0", "sync-request": "3.0.1", "tello": "1.0.5", "webdriverio": "2.4.5", diff --git a/src/initialize-benchmark-window.js b/src/initialize-benchmark-window.js index 55bda75fa..e4be4420b 100644 --- a/src/initialize-benchmark-window.js +++ b/src/initialize-benchmark-window.js @@ -1,6 +1,6 @@ /** @babel */ -import {ipcRenderer, remote} from 'electron' +import {remote} from 'electron' import path from 'path' import ipcHelpers from './ipc-helpers' import util from 'util' @@ -56,8 +56,12 @@ export default async function () { const applicationDelegate = new ApplicationDelegate() global.atom = new AtomEnvironment({ - applicationDelegate, window, document, clipboard, - configDirPath: process.env.ATOM_HOME, enablePersistence: false + applicationDelegate, + window, + document, + clipboard, + configDirPath: process.env.ATOM_HOME, + enablePersistence: false }) // Prevent benchmarks from modifying application menus @@ -71,15 +75,15 @@ export default async function () { console.log = function (...args) { const formatted = util.format(...args) - process.stdout.write(formatted + "\n") + process.stdout.write(formatted + '\n') } console.warn = function (...args) { const formatted = util.format(...args) - process.stderr.write(formatted + "\n") + process.stderr.write(formatted + '\n') } console.error = function (...args) { const formatted = util.format(...args) - process.stderr.write(formatted + "\n") + process.stderr.write(formatted + '\n') } } else { remote.getCurrentWindow().show() diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js index 2f10ae3bb..68a18fa30 100644 --- a/src/main-process/parse-command-line.js +++ b/src/main-process/parse-command-line.js @@ -136,10 +136,29 @@ module.exports = function parseCommandLine (processArgs) { devResourcePath = normalizeDriveLetterName(devResourcePath) return { - resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test, - version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath, - userDataDir, profileStartup, timeout, setPortable, clearWindowState, - addToLastWindow, mainProcess, benchmark, benchmarkTest, env: process.env + resourcePath, + devResourcePath, + pathsToOpen, + urlsToOpen, + executedFrom, + test, + version, + pidToKillWhenClosed, + devMode, + safeMode, + newWindow, + logFile, + socketPath, + userDataDir, + profileStartup, + timeout, + setPortable, + clearWindowState, + addToLastWindow, + mainProcess, + benchmark, + benchmarkTest, + env: process.env } } diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index 50fa71fc3..a9857fc0c 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -45,7 +45,7 @@ class NativeCompileCache { Module.prototype._compile = function (content, filename) { let moduleSelf = this // remove shebang - content = content.replace(/^\#\!.*/, '') + content = content.replace(/^#!.*/, '') function require (path) { return moduleSelf.require(path) } From 00a20698b69a1c5a562e3a7d0776bd2c91fa1714 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 15:56:26 +0200 Subject: [PATCH 57/88] Make large file construction benchmark easier --- ...xt-editor-large-file-construction.bench.js | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js index f37255ca4..0e92973f4 100644 --- a/benchmarks/text-editor-large-file-construction.bench.js +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -5,24 +5,15 @@ import temp from 'temp' import {TextEditor, TextBuffer} from 'atom' export default function ({test}) { - const data = [] - const maxLineCount = test ? 5 : 5000 - const step = test ? 1 : 125 - const lineText = 'Lorem ipsum dolor sit amet\n' - const sampleText = lineText.repeat(maxLineCount) - for (let lineCount = 0; lineCount <= maxLineCount; lineCount += step) { - const text = sampleText.slice(0, lineText.length * lineCount) - const buffer = new TextBuffer(text) - const t0 = window.performance.now() - const editor = new TextEditor({buffer, largeFileMode: true}) - document.body.appendChild(editor.element) - const t1 = window.performance.now() - data.push({ - name: 'Opening and rendering a TextEditor in large file mode', - x: lineCount, duration: t1 - t0 - }) - editor.element.remove() - editor.destroy() - } - return data + const text = 'Lorem ipsum dolor sit amet\n'.repeat(test ? 10 : 500000) + const t0 = window.performance.now() + const buffer = new TextBuffer(text) + const editor = new TextEditor({buffer, largeFileMode: true}) + editor.element.style.height = "600px" + document.body.appendChild(editor.element) + const t1 = window.performance.now() + editor.element.remove() + editor.destroy() + + return [{name: 'Opening and rendering a large file', duration: t1 - t0}] } From 4852b5f42b526b74b2fa2dd7d47c272a74ebc56e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 Oct 2016 16:34:20 +0200 Subject: [PATCH 58/88] :arrow_up: autocomplete-plus --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 108db5904..f368ff2c9 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.13.1", "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.31.4", + "autocomplete-plus": "2.32.0", "autocomplete-snippets": "1.11.0", "autoflow": "0.27.0", "autosave": "0.23.1", From a2d3cf9c719d525acf3bc77e57721e91c4df779f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 14 Oct 2016 09:53:43 -0700 Subject: [PATCH 59/88] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 90a3b29ad..2590727f1 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "sinon": "1.17.4", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "9.3.1-4", + "text-buffer": "9.4.0", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "winreg": "^1.2.1", From 608121d9494eb4b1b26eeaa8c06099ca5ee238fa Mon Sep 17 00:00:00 2001 From: Thomas Johansen Date: Fri, 14 Oct 2016 21:21:52 +0200 Subject: [PATCH 60/88] :arrow_up: python@2.7.12 --- .python-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.python-version b/.python-version index 49cdd668e..4712731cc 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -2.7.6 +2.7.12 From 17c72d51a6be83dd457d127ec007684dfe2a4004 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 14 Oct 2016 13:23:54 -0700 Subject: [PATCH 61/88] Benchmark opening files of various sizes --- ...xt-editor-large-file-construction.bench.js | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js index 0e92973f4..748da71aa 100644 --- a/benchmarks/text-editor-large-file-construction.bench.js +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -1,19 +1,61 @@ /** @babel */ -import fs from 'fs' -import temp from 'temp' import {TextEditor, TextBuffer} from 'atom' -export default function ({test}) { - const text = 'Lorem ipsum dolor sit amet\n'.repeat(test ? 10 : 500000) - const t0 = window.performance.now() - const buffer = new TextBuffer(text) - const editor = new TextEditor({buffer, largeFileMode: true}) - editor.element.style.height = "600px" - document.body.appendChild(editor.element) - const t1 = window.performance.now() - editor.element.remove() - editor.destroy() +const MAX_SIZE_IN_KB = 10 * 1024 +const SIZE_STEP_IN_KB = 1024 +const LINE_TEXT = 'Lorem ipsum dolor sit amet\n' +const CLICK_COUNT = 2 +const TEXT = LINE_TEXT.repeat(Math.ceil(MAX_SIZE_IN_KB * 1024 / LINE_TEXT.length)) - return [{name: 'Opening and rendering a large file', duration: t1 - t0}] +export default async function ({test}) { + const data = [] + + for (let sizeInKB = 0; sizeInKB < MAX_SIZE_IN_KB; sizeInKB += SIZE_STEP_IN_KB) { + const text = TEXT.slice(0, sizeInKB * 1024) + console.log(text.length / 1024) + + const t0 = window.performance.now() + const buffer = new TextBuffer(text) + const editor = new TextEditor({buffer, largeFileMode: true}) + editor.element.style.height = "600px" + document.body.appendChild(editor.element) + const t1 = window.performance.now() + + data.push({ + name: 'Opening and rendering a large file', + x: sizeInKB, + duration: t1 - t0 + }) + + for (let i = 0; i < CLICK_COUNT; i++) { + const t2 = window.performance.now() + editor.setCursorScreenPosition( + editor.element.screenPositionForPixelPosition({ + top: i * 20, + left: 0 + }) + ) + const t3 = window.performance.now() + + data.push({ + name: 'Clicking somewhere onscreen after opening a large file', + x: sizeInKB, + duration: t3 - t2 + }) + + await timeout(100) + } + + editor.element.remove() + editor.destroy() + buffer.destroy() + await timeout(5000) + } + + return data +} + +function timeout (duration) { + return new Promise((resolve) => setTimeout(resolve, duration)) } From 42949dbed6e6917a0760a4231b4709b88ea1870a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Oct 2016 14:29:47 -0600 Subject: [PATCH 62/88] :arrow_up: atom-keymap --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f151bbd7..210016fd6 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "electronVersion": "1.3.6", "dependencies": { "async": "0.2.6", - "atom-keymap": "7.0.3", + "atom-keymap": "7.0.4", "atom-ui": "0.4.1", "babel-core": "5.8.38", "cached-run-in-this-context": "0.4.1", From 465d8dade10fef9f9a6368d1fb63a27c02321955 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 14 Oct 2016 14:51:00 -0700 Subject: [PATCH 63/88] Improve measurements of responsiveness in large file benchmark Signed-off-by: Nathan Sobo --- ...xt-editor-large-file-construction.bench.js | 83 +++++++++++++------ 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js index 748da71aa..cb6225efa 100644 --- a/benchmarks/text-editor-large-file-construction.bench.js +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -2,57 +2,86 @@ import {TextEditor, TextBuffer} from 'atom' -const MAX_SIZE_IN_KB = 10 * 1024 +const MIN_SIZE_IN_KB = 0 * 1024 +const MAX_SIZE_IN_KB = 6 * 1024 const SIZE_STEP_IN_KB = 1024 const LINE_TEXT = 'Lorem ipsum dolor sit amet\n' -const CLICK_COUNT = 2 const TEXT = LINE_TEXT.repeat(Math.ceil(MAX_SIZE_IN_KB * 1024 / LINE_TEXT.length)) export default async function ({test}) { const data = [] - for (let sizeInKB = 0; sizeInKB < MAX_SIZE_IN_KB; sizeInKB += SIZE_STEP_IN_KB) { + const workspaceElement = atom.views.getView(atom.workspace) + document.body.appendChild(workspaceElement) + + atom.packages.loadPackages() + await atom.packages.activate() + + for (let pane of atom.workspace.getPanes()) { + pane.destroy() + } + + for (let sizeInKB = MIN_SIZE_IN_KB; sizeInKB < MAX_SIZE_IN_KB; sizeInKB += SIZE_STEP_IN_KB) { const text = TEXT.slice(0, sizeInKB * 1024) console.log(text.length / 1024) - const t0 = window.performance.now() + let t0 = window.performance.now() const buffer = new TextBuffer(text) const editor = new TextEditor({buffer, largeFileMode: true}) - editor.element.style.height = "600px" - document.body.appendChild(editor.element) - const t1 = window.performance.now() + atom.workspace.getActivePane().activateItem(editor) + let t1 = window.performance.now() data.push({ - name: 'Opening and rendering a large file', + name: 'Opening a large file', x: sizeInKB, duration: t1 - t0 }) - for (let i = 0; i < CLICK_COUNT; i++) { - const t2 = window.performance.now() - editor.setCursorScreenPosition( - editor.element.screenPositionForPixelPosition({ - top: i * 20, - left: 0 - }) - ) - const t3 = window.performance.now() - - data.push({ - name: 'Clicking somewhere onscreen after opening a large file', - x: sizeInKB, - duration: t3 - t2 - }) - - await timeout(100) + const tickDurations = [] + for (let i = 0; i < 20; i++) { + await timeout(50) + t0 = window.performance.now() + await timeout(0) + t1 = window.performance.now() + tickDurations[i] = t1 - t0 } - editor.element.remove() + data.push({ + name: 'Max time event loop was blocked after opening a large file', + x: sizeInKB, + duration: Math.max(...tickDurations) + }) + + t0 = window.performance.now() + editor.setCursorScreenPosition(editor.element.screenPositionForPixelPosition({ + top: 100, + left: 30 + })) + t1 = window.performance.now() + + data.push({ + name: 'Clicking the editor after opening a large file', + x: sizeInKB, + duration: t1 - t0 + }) + + t0 = window.performance.now() + editor.setFirstVisibleScreenRow(editor.getLastVisibleScreenRow() + 10) + t1 = window.performance.now() + + data.push({ + name: 'Scrolling down after opening a large file', + x: sizeInKB, + duration: t1 - t0 + }) + editor.destroy() buffer.destroy() - await timeout(5000) + await timeout(10000) } + workspaceElement.remove() + return data } From 537570f829b3d026c12a0eff75fba0cfaf572459 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 14 Oct 2016 15:27:47 -0700 Subject: [PATCH 64/88] Simulate scrolling more realistically in large file benchmark --- benchmarks/text-editor-large-file-construction.bench.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/text-editor-large-file-construction.bench.js b/benchmarks/text-editor-large-file-construction.bench.js index cb6225efa..694729724 100644 --- a/benchmarks/text-editor-large-file-construction.bench.js +++ b/benchmarks/text-editor-large-file-construction.bench.js @@ -3,7 +3,7 @@ import {TextEditor, TextBuffer} from 'atom' const MIN_SIZE_IN_KB = 0 * 1024 -const MAX_SIZE_IN_KB = 6 * 1024 +const MAX_SIZE_IN_KB = 10 * 1024 const SIZE_STEP_IN_KB = 1024 const LINE_TEXT = 'Lorem ipsum dolor sit amet\n' const TEXT = LINE_TEXT.repeat(Math.ceil(MAX_SIZE_IN_KB * 1024 / LINE_TEXT.length)) @@ -66,7 +66,7 @@ export default async function ({test}) { }) t0 = window.performance.now() - editor.setFirstVisibleScreenRow(editor.getLastVisibleScreenRow() + 10) + editor.element.setScrollTop(editor.element.getScrollTop() + 100) t1 = window.performance.now() data.push({ From b05cf3007bc0e9986d8c4cb013b2f51387fd13e6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 14 Oct 2016 15:59:39 -0700 Subject: [PATCH 65/88] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 75e04614e..5352507df 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "dev-live-reload": "0.47.0", "encoding-selector": "0.22.0", "exception-reporting": "0.40.0", - "find-and-replace": "0.202.0", + "find-and-replace": "0.202.1", "fuzzy-finder": "1.4.0", "git-diff": "1.1.0", "go-to-line": "0.31.0", From d16532e847f0f3a20e634b12dff8d626aab153d7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 14 Oct 2016 21:47:23 -0600 Subject: [PATCH 66/88] :arrow_up: atom-keymap Closes #12980 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5352507df..07186c114 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "electronVersion": "1.3.6", "dependencies": { "async": "0.2.6", - "atom-keymap": "7.0.4", + "atom-keymap": "7.0.5", "atom-ui": "0.4.1", "babel-core": "5.8.38", "cached-run-in-this-context": "0.4.1", From 17557fe7d6dc6a1c1b4509404a208c5b3f859a49 Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Sat, 15 Oct 2016 14:01:58 -0700 Subject: [PATCH 67/88] Convert ConfigSchema to JS --- ...{config-schema.coffee => config-schema.js} | 510 +++++++++++------- 1 file changed, 318 insertions(+), 192 deletions(-) rename src/{config-schema.coffee => config-schema.js} (54%) diff --git a/src/config-schema.coffee b/src/config-schema.js similarity index 54% rename from src/config-schema.coffee rename to src/config-schema.js index ec0b20ca3..113051920 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.js @@ -1,68 +1,103 @@ -path = require 'path' -fs = require 'fs-plus' +/** @babel */ -# This is loaded by atom.coffee. See https://atom.io/docs/api/latest/Config for -# more information about config schemas. -module.exports = - core: - type: 'object' - properties: - ignoredNames: - type: 'array' - default: [".git", ".hg", ".svn", ".DS_Store", "._*", "Thumbs.db"] - items: +import path from 'path' +import fs from 'fs-plus' + +const configSchema = { + core: { + type: 'object', + + properties: { + ignoredNames: { + type: 'array', + default: ['.git', '.hg', '.svn', '.DS_Store', '._*', 'Thumbs.db'], + + items: { type: 'string' + }, + description: 'List of [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29). Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.' - excludeVcsIgnoredPaths: - type: 'boolean' - default: true - title: 'Exclude VCS Ignored Paths' + }, + + excludeVcsIgnoredPaths: { + type: 'boolean', + default: true, + title: 'Exclude VCS Ignored Paths', description: 'Files and directories ignored by the current project\'s VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders.' - followSymlinks: - type: 'boolean' - default: true + }, + + followSymlinks: { + type: 'boolean', + default: true, description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.' - disabledPackages: - type: 'array' - default: [] - items: + }, + + disabledPackages: { + type: 'array', + default: [], + + items: { type: 'string' + }, + description: 'List of names of installed packages which are not loaded at startup.' - customFileTypes: - type: 'object' - default: {} - description: 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)' - additionalProperties: - type: 'array' - items: + }, + + customFileTypes: { + type: 'object', + default: {}, + description: "Associates scope names (e.g. `\"source.js\"`) with arrays of file extensions and file names (e.g. `[\"Somefile\", \".js2\"]`)", + + additionalProperties: { + type: 'array', + + items: { type: 'string' - themes: - type: 'array' - default: ['one-dark-ui', 'one-dark-syntax'] - items: + } + } + }, + + themes: { + type: 'array', + default: ['one-dark-ui', 'one-dark-syntax'], + + items: { type: 'string' + }, + description: 'Names of UI and syntax themes which will be used when Atom starts.' - projectHome: - type: 'string' - default: path.join(fs.getHomeDirectory(), 'github') + }, + + projectHome: { + type: 'string', + default: path.join(fs.getHomeDirectory(), 'github'), description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' - audioBeep: - type: 'boolean' - default: true + }, + + audioBeep: { + type: 'boolean', + default: true, description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.' - destroyEmptyPanes: - type: 'boolean' - default: true - title: 'Remove Empty Panes' + }, + + destroyEmptyPanes: { + type: 'boolean', + default: true, + title: 'Remove Empty Panes', description: 'When the last tab of a pane is closed, remove that pane as well.' - closeEmptyWindows: - type: 'boolean' - default: true + }, + + closeEmptyWindows: { + type: 'boolean', + default: true, description: 'When a window with no open tabs or panes is given the \'Close Tab\' command, close that window.' - fileEncoding: - description: 'Default character set encoding to use when reading and writing files.' - type: 'string' - default: 'utf8' + }, + + fileEncoding: { + description: 'Default character set encoding to use when reading and writing files.', + type: 'string', + default: 'utf8', + enum: [ 'cp437', 'eucjp', @@ -100,179 +135,270 @@ module.exports = 'windows1258', 'windows866' ] - openEmptyEditorOnStart: - description: 'Automatically open an empty editor on startup.' - type: 'boolean' + }, + + openEmptyEditorOnStart: { + description: 'Automatically open an empty editor on startup.', + type: 'boolean', default: true - automaticallyUpdate: - description: 'Automatically update Atom when a new release is available.' - type: 'boolean' + }, + + automaticallyUpdate: { + description: 'Automatically update Atom when a new release is available.', + type: 'boolean', default: true - allowPendingPaneItems: - description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.' - type: 'boolean' + }, + + allowPendingPaneItems: { + description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.', + type: 'boolean', default: true - telemetryConsent: - description: 'Allow usage statistics and exception reports to be sent to the Atom team to help improve the product.' - title: 'Send Telemetry to the Atom Team' - type: 'string' - default: 'undecided' - enum: [ - {value: 'limited', description: 'Allow limited anonymous usage stats, exception and crash reporting'} - {value: 'no', description: 'Do not send any telemetry data'} - {value: 'undecided', description: 'Undecided (Atom will ask again next time it is launched)'} - ] - warnOnLargeFileLimit: - description: 'Warn before opening files larger than this number of megabytes.' - type: 'number' + }, + + telemetryConsent: { + description: 'Allow usage statistics and exception reports to be sent to the Atom team to help improve the product.', + title: 'Send Telemetry to the Atom Team', + type: 'string', + default: 'undecided', + + enum: [{ + value: 'limited', + description: 'Allow limited anonymous usage stats, exception and crash reporting' + }, { + value: 'no', + description: 'Do not send any telemetry data' + }, { + value: 'undecided', + description: 'Undecided (Atom will ask again next time it is launched)' + }] + }, + + warnOnLargeFileLimit: { + description: "Warn before opening files larger than this number of megabytes.", + type: "number", default: 20 - editor: - type: 'object' - properties: - # These settings are used in scoped fashion only. No defaults. - commentStart: - type: ['string', 'null'] - commentEnd: - type: ['string', 'null'] - increaseIndentPattern: - type: ['string', 'null'] - decreaseIndentPattern: - type: ['string', 'null'] - foldEndPattern: - type: ['string', 'null'] + } + } + }, - # These can be used as globals or scoped, thus defaults. - fontFamily: - type: 'string' - default: '' + editor: { + type: 'object', + + properties: { + commentStart: { + type: ['string', 'null'] + }, + + commentEnd: { + type: ['string', 'null'] + }, + + increaseIndentPattern: { + type: ['string', 'null'] + }, + + decreaseIndentPattern: { + type: ['string', 'null'] + }, + + foldEndPattern: { + type: ['string', 'null'] + }, + + fontFamily: { + type: 'string', + default: '', description: 'The name of the font family used for editor text.' - fontSize: - type: 'integer' - default: 14 - minimum: 1 - maximum: 100 + }, + + fontSize: { + type: 'integer', + default: 14, + minimum: 1, + maximum: 100, description: 'Height in pixels of editor text.' - lineHeight: - type: ['string', 'number'] - default: 1.5 + }, + + lineHeight: { + type: ['string', 'number'], + default: 1.5, description: 'Height of editor lines, as a multiplier of font size.' - showInvisibles: - type: 'boolean' - default: false + }, + + showInvisibles: { + type: 'boolean', + default: false, description: 'Render placeholders for invisible characters, such as tabs, spaces and newlines.' - showIndentGuide: - type: 'boolean' - default: false + }, + + showIndentGuide: { + type: 'boolean', + default: false, description: 'Show indentation indicators in the editor.' - showLineNumbers: - type: 'boolean' - default: true + }, + + showLineNumbers: { + type: 'boolean', + default: true, description: 'Show line numbers in the editor\'s gutter.' - atomicSoftTabs: - type: 'boolean' - default: true + }, + + atomicSoftTabs: { + type: 'boolean', + default: true, description: 'Skip over tab-length runs of leading whitespace when moving the cursor.' - autoIndent: - type: 'boolean' - default: true + }, + + autoIndent: { + type: 'boolean', + default: true, description: 'Automatically indent the cursor when inserting a newline.' - autoIndentOnPaste: - type: 'boolean' - default: true + }, + + autoIndentOnPaste: { + type: 'boolean', + default: true, description: 'Automatically indent pasted text based on the indentation of the previous line.' - nonWordCharacters: - type: 'string' - default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…" + }, + + nonWordCharacters: { + type: 'string', + default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…", description: 'A string of non-word characters to define word boundaries.' - preferredLineLength: - type: 'integer' - default: 80 - minimum: 1 + }, + + preferredLineLength: { + type: 'integer', + default: 80, + minimum: 1, description: 'Identifies the length of a line which is used when wrapping text with the `Soft Wrap At Preferred Line Length` setting enabled, in number of characters.' - tabLength: - type: 'integer' - default: 2 - minimum: 1 + }, + + tabLength: { + type: 'integer', + default: 2, + minimum: 1, description: 'Number of spaces used to represent a tab.' - softWrap: - type: 'boolean' - default: false + }, + + softWrap: { + type: 'boolean', + default: false, description: 'Wraps lines that exceed the width of the window. When `Soft Wrap At Preferred Line Length` is set, it will wrap to the number of characters defined by the `Preferred Line Length` setting.' - softTabs: - type: 'boolean' - default: true + }, + + softTabs: { + type: 'boolean', + default: true, description: 'If the `Tab Type` config setting is set to "auto" and autodetection of tab type from buffer content fails, then this config setting determines whether a soft tab or a hard tab will be inserted when the Tab key is pressed.' - tabType: - type: 'string' - default: 'auto' - enum: ['auto', 'soft', 'hard'] + }, + + tabType: { + type: 'string', + default: 'auto', + enum: ['auto', 'soft', 'hard'], description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.' - softWrapAtPreferredLineLength: - type: 'boolean' - default: false + }, + + softWrapAtPreferredLineLength: { + type: 'boolean', + default: false, description: 'Instead of wrapping lines to the window\'s width, wrap lines to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when the soft wrap config setting is enabled globally or for the current language. **Note:** If you want to hide the wrap guide (the vertical line) you can disable the `wrap-guide` package.' - softWrapHangingIndent: - type: 'integer' - default: 0 - minimum: 0 + }, + + softWrapHangingIndent: { + type: 'integer', + default: 0, + minimum: 0, description: 'When soft wrap is enabled, defines length of additional indentation applied to wrapped lines, in number of characters.' - scrollSensitivity: - type: 'integer' - default: 40 - minimum: 10 - maximum: 200 + }, + + scrollSensitivity: { + type: 'integer', + default: 40, + minimum: 10, + maximum: 200, description: 'Determines how fast the editor scrolls when using a mouse or trackpad.' - scrollPastEnd: - type: 'boolean' - default: false + }, + + scrollPastEnd: { + type: 'boolean', + default: false, description: 'Allow the editor to be scrolled past the end of the last line.' - undoGroupingInterval: - type: 'integer' - default: 300 - minimum: 0 + }, + + undoGroupingInterval: { + type: 'integer', + default: 300, + minimum: 0, description: 'Time interval in milliseconds within which text editing operations will be grouped together in the undo history.' - confirmCheckoutHeadRevision: - type: 'boolean' - default: true - title: 'Confirm Checkout HEAD Revision' + }, + + confirmCheckoutHeadRevision: { + type: 'boolean', + default: true, + title: 'Confirm Checkout HEAD Revision', description: 'Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit.' - invisibles: - type: 'object' - description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).' - properties: - eol: - type: ['boolean', 'string'] - default: '\u00ac' - maximumLength: 1 + }, + + invisibles: { + type: 'object', + description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).', + + properties: { + eol: { + type: ['boolean', 'string'], + default: '¬', + maximumLength: 1, description: 'Character used to render newline characters (\\n) when the `Show Invisibles` setting is enabled. ' - space: - type: ['boolean', 'string'] - default: '\u00b7' - maximumLength: 1 + }, + + space: { + type: ['boolean', 'string'], + default: '·', + maximumLength: 1, description: 'Character used to render leading and trailing space characters when the `Show Invisibles` setting is enabled.' - tab: - type: ['boolean', 'string'] - default: '\u00bb' - maximumLength: 1 + }, + + tab: { + type: ['boolean', 'string'], + default: '»', + maximumLength: 1, description: 'Character used to render hard tab characters (\\t) when the `Show Invisibles` setting is enabled.' - cr: - type: ['boolean', 'string'] - default: '\u00a4' - maximumLength: 1 + }, + + cr: { + type: ['boolean', 'string'], + default: '¤', + maximumLength: 1, description: 'Character used to render carriage return characters (for Microsoft-style line endings) when the `Show Invisibles` setting is enabled.' - zoomFontWhenCtrlScrolling: - type: 'boolean' - default: process.platform isnt 'darwin' + } + } + }, + + zoomFontWhenCtrlScrolling: { + type: 'boolean', + default: process.platform !== 'darwin', description: 'Change the editor font size when pressing the Ctrl key and scrolling the mouse up/down.' + } + } + } +}; -if process.platform in ['win32', 'linux'] - module.exports.core.properties.autoHideMenuBar = - type: 'boolean' - default: false +if (['win32', 'linux'].includes(process.platform)) { + configSchema.core.properties.autoHideMenuBar = { + type: 'boolean', + default: false, description: 'Automatically hide the menu bar and toggle it by pressing Alt. This is only supported on Windows & Linux.' + }; +} -if process.platform is 'darwin' - module.exports.core.properties.useCustomTitleBar = - type: 'boolean' - default: false +if (process.platform === 'darwin') { + configSchema.core.properties.useCustomTitleBar = { + type: 'boolean', + default: false, description: 'Use custom, theme-aware title bar.
Note: This currently does not include a proxy icon.
This setting will require a relaunch of Atom to take effect.' + }; +} + +export default configSchema From 164fec6322f7fd0942cce670d2516eed808048d7 Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Sat, 15 Oct 2016 15:56:05 -0700 Subject: [PATCH 68/88] Add missing comments --- src/config-schema.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/config-schema.js b/src/config-schema.js index 113051920..f73217225 100644 --- a/src/config-schema.js +++ b/src/config-schema.js @@ -3,6 +3,9 @@ import path from 'path' import fs from 'fs-plus' +// This is loaded by atom-environment.coffee. See +// https://atom.io/docs/api/latest/Config for more information about config +// schemas. const configSchema = { core: { type: 'object', @@ -183,7 +186,7 @@ const configSchema = { editor: { type: 'object', - + // These settings are used in scoped fashion only. No defaults. properties: { commentStart: { type: ['string', 'null'] @@ -205,6 +208,7 @@ const configSchema = { type: ['string', 'null'] }, + // These can be used as globals or scoped, thus defaults. fontFamily: { type: 'string', default: '', From a460383e583e98857dd96230d93a8f10f2b3e748 Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Sat, 15 Oct 2016 16:00:12 -0700 Subject: [PATCH 69/88] :art: Fix weird auto-generated spacing --- src/config-schema.js | 80 ++++++++------------------------------------ 1 file changed, 14 insertions(+), 66 deletions(-) diff --git a/src/config-schema.js b/src/config-schema.js index f73217225..e6dc6b0b1 100644 --- a/src/config-schema.js +++ b/src/config-schema.js @@ -9,32 +9,26 @@ import fs from 'fs-plus' const configSchema = { core: { type: 'object', - properties: { ignoredNames: { type: 'array', default: ['.git', '.hg', '.svn', '.DS_Store', '._*', 'Thumbs.db'], - items: { type: 'string' }, - description: 'List of [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29). Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.' }, - excludeVcsIgnoredPaths: { type: 'boolean', default: true, title: 'Exclude VCS Ignored Paths', description: 'Files and directories ignored by the current project\'s VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders.' }, - followSymlinks: { type: 'boolean', default: true, description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.' }, - disabledPackages: { type: 'array', default: [], @@ -45,62 +39,50 @@ const configSchema = { description: 'List of names of installed packages which are not loaded at startup.' }, - customFileTypes: { type: 'object', default: {}, description: "Associates scope names (e.g. `\"source.js\"`) with arrays of file extensions and file names (e.g. `[\"Somefile\", \".js2\"]`)", - additionalProperties: { type: 'array', - items: { type: 'string' } } }, - themes: { type: 'array', default: ['one-dark-ui', 'one-dark-syntax'], - items: { type: 'string' }, - description: 'Names of UI and syntax themes which will be used when Atom starts.' }, - projectHome: { type: 'string', default: path.join(fs.getHomeDirectory(), 'github'), description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.' }, - audioBeep: { type: 'boolean', default: true, description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.' }, - destroyEmptyPanes: { type: 'boolean', default: true, title: 'Remove Empty Panes', description: 'When the last tab of a pane is closed, remove that pane as well.' }, - closeEmptyWindows: { type: 'boolean', default: true, description: 'When a window with no open tabs or panes is given the \'Close Tab\' command, close that window.' }, - fileEncoding: { description: 'Default character set encoding to use when reading and writing files.', type: 'string', default: 'utf8', - enum: [ 'cp437', 'eucjp', @@ -139,43 +121,41 @@ const configSchema = { 'windows866' ] }, - openEmptyEditorOnStart: { description: 'Automatically open an empty editor on startup.', type: 'boolean', default: true }, - automaticallyUpdate: { description: 'Automatically update Atom when a new release is available.', type: 'boolean', default: true }, - allowPendingPaneItems: { description: 'Allow items to be previewed without adding them to a pane permanently, such as when single clicking files in the tree view.', type: 'boolean', default: true }, - telemetryConsent: { description: 'Allow usage statistics and exception reports to be sent to the Atom team to help improve the product.', title: 'Send Telemetry to the Atom Team', type: 'string', default: 'undecided', - - enum: [{ - value: 'limited', - description: 'Allow limited anonymous usage stats, exception and crash reporting' - }, { - value: 'no', - description: 'Do not send any telemetry data' - }, { - value: 'undecided', - description: 'Undecided (Atom will ask again next time it is launched)' - }] + enum: [ + { + value: 'limited', + description: 'Allow limited anonymous usage stats, exception and crash reporting' + }, + { + value: 'no', + description: 'Do not send any telemetry data' + }, + { + value: 'undecided', + description: 'Undecided (Atom will ask again next time it is launched)' + } + ] }, - warnOnLargeFileLimit: { description: "Warn before opening files larger than this number of megabytes.", type: "number", @@ -183,7 +163,6 @@ const configSchema = { } } }, - editor: { type: 'object', // These settings are used in scoped fashion only. No defaults. @@ -191,30 +170,24 @@ const configSchema = { commentStart: { type: ['string', 'null'] }, - commentEnd: { type: ['string', 'null'] }, - increaseIndentPattern: { type: ['string', 'null'] }, - decreaseIndentPattern: { type: ['string', 'null'] }, - foldEndPattern: { type: ['string', 'null'] }, - // These can be used as globals or scoped, thus defaults. fontFamily: { type: 'string', default: '', description: 'The name of the font family used for editor text.' }, - fontSize: { type: 'integer', default: 14, @@ -222,101 +195,85 @@ const configSchema = { maximum: 100, description: 'Height in pixels of editor text.' }, - lineHeight: { type: ['string', 'number'], default: 1.5, description: 'Height of editor lines, as a multiplier of font size.' }, - showInvisibles: { type: 'boolean', default: false, description: 'Render placeholders for invisible characters, such as tabs, spaces and newlines.' }, - showIndentGuide: { type: 'boolean', default: false, description: 'Show indentation indicators in the editor.' }, - showLineNumbers: { type: 'boolean', default: true, description: 'Show line numbers in the editor\'s gutter.' }, - atomicSoftTabs: { type: 'boolean', default: true, description: 'Skip over tab-length runs of leading whitespace when moving the cursor.' }, - autoIndent: { type: 'boolean', default: true, description: 'Automatically indent the cursor when inserting a newline.' }, - autoIndentOnPaste: { type: 'boolean', default: true, description: 'Automatically indent pasted text based on the indentation of the previous line.' }, - nonWordCharacters: { type: 'string', default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…", description: 'A string of non-word characters to define word boundaries.' }, - preferredLineLength: { type: 'integer', default: 80, minimum: 1, description: 'Identifies the length of a line which is used when wrapping text with the `Soft Wrap At Preferred Line Length` setting enabled, in number of characters.' }, - tabLength: { type: 'integer', default: 2, minimum: 1, description: 'Number of spaces used to represent a tab.' }, - softWrap: { type: 'boolean', default: false, description: 'Wraps lines that exceed the width of the window. When `Soft Wrap At Preferred Line Length` is set, it will wrap to the number of characters defined by the `Preferred Line Length` setting.' }, - softTabs: { type: 'boolean', default: true, description: 'If the `Tab Type` config setting is set to "auto" and autodetection of tab type from buffer content fails, then this config setting determines whether a soft tab or a hard tab will be inserted when the Tab key is pressed.' }, - tabType: { type: 'string', default: 'auto', enum: ['auto', 'soft', 'hard'], description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.' }, - softWrapAtPreferredLineLength: { type: 'boolean', default: false, description: 'Instead of wrapping lines to the window\'s width, wrap lines to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when the soft wrap config setting is enabled globally or for the current language. **Note:** If you want to hide the wrap guide (the vertical line) you can disable the `wrap-guide` package.' }, - softWrapHangingIndent: { type: 'integer', default: 0, minimum: 0, description: 'When soft wrap is enabled, defines length of additional indentation applied to wrapped lines, in number of characters.' }, - scrollSensitivity: { type: 'integer', default: 40, @@ -324,31 +281,26 @@ const configSchema = { maximum: 200, description: 'Determines how fast the editor scrolls when using a mouse or trackpad.' }, - scrollPastEnd: { type: 'boolean', default: false, description: 'Allow the editor to be scrolled past the end of the last line.' }, - undoGroupingInterval: { type: 'integer', default: 300, minimum: 0, description: 'Time interval in milliseconds within which text editing operations will be grouped together in the undo history.' }, - confirmCheckoutHeadRevision: { type: 'boolean', default: true, title: 'Confirm Checkout HEAD Revision', description: 'Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit.' }, - invisibles: { type: 'object', description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).', - properties: { eol: { type: ['boolean', 'string'], @@ -356,21 +308,18 @@ const configSchema = { maximumLength: 1, description: 'Character used to render newline characters (\\n) when the `Show Invisibles` setting is enabled. ' }, - space: { type: ['boolean', 'string'], default: '·', maximumLength: 1, description: 'Character used to render leading and trailing space characters when the `Show Invisibles` setting is enabled.' }, - tab: { type: ['boolean', 'string'], default: '»', maximumLength: 1, description: 'Character used to render hard tab characters (\\t) when the `Show Invisibles` setting is enabled.' }, - cr: { type: ['boolean', 'string'], default: '¤', @@ -379,7 +328,6 @@ const configSchema = { } } }, - zoomFontWhenCtrlScrolling: { type: 'boolean', default: process.platform !== 'darwin', From d124248db0a4d500a4ce6e52f86550a18af45580 Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Sun, 16 Oct 2016 09:28:33 -0700 Subject: [PATCH 70/88] :shirt: Fix linter errors --- src/config-schema.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config-schema.js b/src/config-schema.js index e6dc6b0b1..694e132db 100644 --- a/src/config-schema.js +++ b/src/config-schema.js @@ -42,7 +42,7 @@ const configSchema = { customFileTypes: { type: 'object', default: {}, - description: "Associates scope names (e.g. `\"source.js\"`) with arrays of file extensions and file names (e.g. `[\"Somefile\", \".js2\"]`)", + description: 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)', additionalProperties: { type: 'array', items: { @@ -157,8 +157,8 @@ const configSchema = { ] }, warnOnLargeFileLimit: { - description: "Warn before opening files larger than this number of megabytes.", - type: "number", + description: 'Warn before opening files larger than this number of megabytes.', + type: 'number', default: 20 } } @@ -335,14 +335,14 @@ const configSchema = { } } } -}; +} if (['win32', 'linux'].includes(process.platform)) { configSchema.core.properties.autoHideMenuBar = { type: 'boolean', default: false, description: 'Automatically hide the menu bar and toggle it by pressing Alt. This is only supported on Windows & Linux.' - }; + } } if (process.platform === 'darwin') { @@ -350,7 +350,7 @@ if (process.platform === 'darwin') { type: 'boolean', default: false, description: 'Use custom, theme-aware title bar.
Note: This currently does not include a proxy icon.
This setting will require a relaunch of Atom to take effect.' - }; + } } export default configSchema From be82d40e34bbe0b48e18e645b09670251e35e0a0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 12:23:43 +0200 Subject: [PATCH 71/88] :arrow_up: Upgrade core packages to suppress deprecations --- package.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 982d394d0..03e3e4c04 100644 --- a/package.json +++ b/package.json @@ -69,38 +69,38 @@ "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", "autocomplete-css": "0.13.1", "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.32.0", + "autocomplete-plus": "2.33.0", "autocomplete-snippets": "1.11.0", "autoflow": "0.27.0", "autosave": "0.23.1", "background-tips": "0.26.1", - "bookmarks": "0.42.0", + "bookmarks": "0.43.0", "bracket-matcher": "0.82.2", "command-palette": "0.39.0", - "deprecation-cop": "0.54.1", + "deprecation-cop": "0.55.0", "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", @@ -108,12 +108,12 @@ "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", + "settings-view": "0.244.0", "snippets": "1.0.3", "spell-check": "0.68.4", "status-bar": "1.6.0", @@ -125,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", From a6999e4a823a674010f97fc86771a565b9292758 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 14:28:50 +0200 Subject: [PATCH 72/88] Fix lint errors --- src/lines-tile-component.js | 1 - src/style-manager.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lines-tile-component.js b/src/lines-tile-component.js index 6053cea19..202e16708 100644 --- a/src/lines-tile-component.js +++ b/src/lines-tile-component.js @@ -249,7 +249,6 @@ module.exports = class LinesTileComponent { 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] for (const decorationId of Object.keys(oldLineState.precedingBlockDecorations)) { if (!newLineState.precedingBlockDecorations.hasOwnProperty(decorationId)) { const {topRulerNode, blockDecorationNode, bottomRulerNode} = diff --git a/src/style-manager.js b/src/style-manager.js index 4175278c3..b273f449b 100644 --- a/src/style-manager.js +++ b/src/style-manager.js @@ -293,14 +293,14 @@ function transformDeprecatedShadowDOMSelectors (css, context) { 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 += '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 += '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} From 883bb15276b44bce8329bf31b84cf6c92ee52afb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 14:32:03 +0200 Subject: [PATCH 73/88] Don't trigger a blur event when focusing the same editor twice --- spec/text-editor-element-spec.coffee | 17 +++++++++++++++++ src/text-editor-element.coffee | 20 +++++++------------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee index 446206fe5..7ed4a106f 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -94,6 +94,23 @@ describe "TextEditorElement", -> 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: -> diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 230813cb5..99d8ed818 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -50,7 +50,7 @@ class TextEditorElement extends HTMLElement @mountComponent() unless @component? @listenForComponentEvents() @component.checkForVisibilityChange() - if this is document.activeElement + if @hasFocus() @focused() @emitter.emit("did-attach") @@ -121,7 +121,7 @@ class TextEditorElement extends HTMLElement @rootElement.appendChild(@component.getDomNode()) inputNode = @component.hiddenInputComponent.getDomNode() inputNode.addEventListener 'focus', @focused.bind(this) - inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false)) + inputNode.addEventListener 'blur', @inputNodeBlurred.bind(this) unmountComponent: -> if @component? @@ -129,24 +129,18 @@ class TextEditorElement extends HTMLElement @component.getDomNode().remove() @component = null - focused: -> + focused: (event) -> @component?.focused() blurred: (event) -> - if event.relatedTarget is @component.hiddenInputComponent.getDomNode() + 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, ' ') From c6832d5b0e77a05e712e901a9387fa54daeb55d0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 14:32:42 +0200 Subject: [PATCH 74/88] Add TextEditorElement.lightDOM to make package specs backward compatible --- src/text-editor-element.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 99d8ed818..4a7d1598d 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -12,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. From fd7f5aea63605f366943e884adbd80cb2507a7c0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 14:50:15 +0200 Subject: [PATCH 75/88] Fix tests --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 03e3e4c04..9c62e60f6 100644 --- a/package.json +++ b/package.json @@ -89,12 +89,12 @@ "autocomplete-plus": "2.33.0", "autocomplete-snippets": "1.11.0", "autoflow": "0.27.0", - "autosave": "0.23.1", + "autosave": "0.23.2", "background-tips": "0.26.1", - "bookmarks": "0.43.0", + "bookmarks": "0.43.1", "bracket-matcher": "0.82.2", "command-palette": "0.39.0", - "deprecation-cop": "0.55.0", + "deprecation-cop": "0.55.1", "dev-live-reload": "0.47.0", "encoding-selector": "0.22.0", "exception-reporting": "0.40.0", @@ -115,9 +115,9 @@ "package-generator": "1.0.1", "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", From 1466bcaf337a8942c4b88b3286b67a8e95897570 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 16:57:33 +0200 Subject: [PATCH 76/88] :arrow_up: apm --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 31162dcaf..73f860586 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" } } From 7a128767a186b4d26c7113ef30ab82bca14dd681 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 16:58:11 +0200 Subject: [PATCH 77/88] :arrow_up: package-generator --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c62e60f6..5d4ef3726 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "metrics": "1.0.0", "notifications": "0.65.1", "open-on-github": "1.2.1", - "package-generator": "1.0.1", + "package-generator": "1.0.2", "settings-view": "0.244.0", "snippets": "1.0.3", "spell-check": "0.68.5", From dffaa6fda9bf2e504e75bfcbeb3df0e5ca1cec04 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 17 Oct 2016 16:59:24 +0200 Subject: [PATCH 78/88] :arrow_up: command-palette --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d4ef3726..2b7a64f97 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "background-tips": "0.26.1", "bookmarks": "0.43.1", "bracket-matcher": "0.82.2", - "command-palette": "0.39.0", + "command-palette": "0.39.1", "deprecation-cop": "0.55.1", "dev-live-reload": "0.47.0", "encoding-selector": "0.22.0", From 4a8606ff31da3485ec24dcde970121cffb7b8aa8 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 17 Oct 2016 15:16:12 -0400 Subject: [PATCH 79/88] :arrow_up: autocomplete-css@0.14.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07186c114..9465699c3 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "about": "1.7.0", "archive-view": "0.62.0", "autocomplete-atom-api": "0.10.0", - "autocomplete-css": "0.13.1", + "autocomplete-css": "0.14.0", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.32.0", "autocomplete-snippets": "1.11.0", From 3cef308d97169c9daad66b605e4977a6f5b5e1bb Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 17 Oct 2016 17:03:04 -0400 Subject: [PATCH 80/88] :arrow_down: autocomplete-css@0.13.1 Until specs are fixed. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9465699c3..07186c114 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "about": "1.7.0", "archive-view": "0.62.0", "autocomplete-atom-api": "0.10.0", - "autocomplete-css": "0.14.0", + "autocomplete-css": "0.13.1", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.32.0", "autocomplete-snippets": "1.11.0", From f0fcc3aaeedb34f6b3fed126fe2cdab07f406150 Mon Sep 17 00:00:00 2001 From: Joe Fitzgerald Date: Mon, 17 Oct 2016 16:22:18 -0600 Subject: [PATCH 81/88] :arrow_up: autocomplete-plus --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07186c114..ad8f759bd 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "autocomplete-atom-api": "0.10.0", "autocomplete-css": "0.13.1", "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.32.0", + "autocomplete-plus": "2.33.1", "autocomplete-snippets": "1.11.0", "autoflow": "0.27.0", "autosave": "0.23.1", From 1076a13ebc47faa54f38ee9a95255ec6efd558e8 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 17 Oct 2016 20:41:10 -0400 Subject: [PATCH 82/88] :arrow_up: autocomplete-css@0.14.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad8f759bd..46f7d6e5f 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "about": "1.7.0", "archive-view": "0.62.0", "autocomplete-atom-api": "0.10.0", - "autocomplete-css": "0.13.1", + "autocomplete-css": "0.14.1", "autocomplete-html": "0.7.2", "autocomplete-plus": "2.33.1", "autocomplete-snippets": "1.11.0", From 5db40baa8a01df7e52ea7d8f59b841b8bb42482f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 18 Oct 2016 11:41:40 -0600 Subject: [PATCH 83/88] :arrow_up: atom-keymap Fixes #12958 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4ab1375c..33129ddea 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "electronVersion": "1.3.6", "dependencies": { "async": "0.2.6", - "atom-keymap": "7.0.5", + "atom-keymap": "7.0.6", "atom-ui": "0.4.1", "babel-core": "5.8.38", "cached-run-in-this-context": "0.4.1", From 639facbd364ea9367acfcf76a7a73bb85a85bab9 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 19 Oct 2016 10:42:27 +0900 Subject: [PATCH 84/88] :arrow_up: markdown-preview@v0.159.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33129ddea..8558a836d 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "keybinding-resolver": "0.35.0", "line-ending-selector": "0.5.0", "link": "0.31.2", - "markdown-preview": "0.159.0", + "markdown-preview": "0.159.1", "metrics": "1.0.0", "notifications": "0.65.1", "open-on-github": "1.2.1", From 602af52c52de02b5059065a673a64d3cf4ad4197 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 19 Oct 2016 13:17:39 +0900 Subject: [PATCH 85/88] :arrow_up: solarized-dark/light-syntax@v1.1.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8558a836d..06daba2ec 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,8 @@ "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", + "solarized-dark-syntax": "1.1.1", + "solarized-light-syntax": "1.1.1", "about": "1.7.0", "archive-view": "0.62.0", "autocomplete-atom-api": "0.10.0", From 07a017039f29cb7f0fed5a19eaaca782ae5531c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20N=C3=A9h=C3=A9mie?= Date: Wed, 19 Oct 2016 12:11:12 +0200 Subject: [PATCH 86/88] :bug: Use flex display in text editor instead of block --- static/text-editor-light.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/text-editor-light.less b/static/text-editor-light.less index 178f62d1d..5f159ce94 100644 --- a/static/text-editor-light.less +++ b/static/text-editor-light.less @@ -3,7 +3,7 @@ @import "octicon-mixins"; atom-text-editor { - display: block; + display: flex; font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; .editor--private, .editor-contents--private { From 0e2f4caece271546d1ce89ccad9021fb98a59a96 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Fri, 21 Oct 2016 10:05:27 -0400 Subject: [PATCH 87/88] :arrow_up: language-coffee-script@0.48.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06daba2ec..56247200d 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "wrap-guide": "0.39.0", "language-c": "0.54.0", "language-clojure": "0.22.1", - "language-coffee-script": "0.48.0", + "language-coffee-script": "0.48.1", "language-csharp": "0.12.1", "language-css": "0.40.1", "language-gfm": "0.88.0", From 574e69c63fbf18284786266c6da2b2ed680e2152 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 21 Oct 2016 22:12:20 -0700 Subject: [PATCH 88/88] :arrow_up: go-to-line@0.31.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 56247200d..7ce36478c 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "find-and-replace": "0.203.0", "fuzzy-finder": "1.4.0", "git-diff": "1.2.0", - "go-to-line": "0.31.0", + "go-to-line": "0.31.1", "grammar-selector": "0.48.2", "image-view": "0.60.0", "incompatible-packages": "0.26.1",