From b71b412ede557cc69a19edcdd132a72570d1d34b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 22 Sep 2016 18:17:23 +0200 Subject: [PATCH] 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; }