diff --git a/keymaps/base.cson b/keymaps/base.cson index 11cbe5977..f187cf2ed 100644 --- a/keymaps/base.cson +++ b/keymaps/base.cson @@ -15,6 +15,9 @@ 'shift-tab': 'editor:outdent-selected-rows' 'ctrl-K': 'editor:delete-line' +'.select-list atom-text-editor.mini': + 'enter': 'core:confirm' + '.tool-panel.panel-left, .tool-panel.panel-right': 'escape': 'tool-panel:unfocus' diff --git a/package.json b/package.json index 154a53ca1..bd82372db 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "solarized-dark-syntax": "0.22.0", "solarized-light-syntax": "0.12.0", "archive-view": "0.37.0", - "autocomplete": "0.32.0", + "autocomplete": "0.33.0", "autoflow": "0.18.0", "autosave": "0.18.0", "background-tips": "0.17.0", @@ -96,7 +96,7 @@ "markdown-preview": "0.107.0", "metrics": "0.36.0", "open-on-github": "0.30.0", - "package-generator": "0.31.0", + "package-generator": "0.32.0", "release-notes": "0.36.0", "settings-view": "0.154.0", "snippets": "0.56.0", diff --git a/spec/fixtures/packages/package-with-stylesheets/stylesheets/4.test-context.css b/spec/fixtures/packages/package-with-stylesheets/stylesheets/4.test-context.css new file mode 100644 index 000000000..2cf54c986 --- /dev/null +++ b/spec/fixtures/packages/package-with-stylesheets/stylesheets/4.test-context.css @@ -0,0 +1 @@ +a { color: red } diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 2cacbfb1d..d460591b7 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -48,9 +48,10 @@ describe "PackageManager", -> describe "when called multiple times", -> it "it only calls activate on the package once", -> spyOn(Package.prototype, 'activateNow').andCallThrough() - atom.packages.activatePackage('package-with-index') - atom.packages.activatePackage('package-with-index') - + waitsForPromise -> + atom.packages.activatePackage('package-with-index') + waitsForPromise -> + atom.packages.activatePackage('package-with-index') waitsForPromise -> atom.packages.activatePackage('package-with-index') @@ -182,8 +183,10 @@ describe "PackageManager", -> pack.mainModule.someNumber = 77 atom.packages.deactivatePackage("package-with-serialization") spyOn(pack.mainModule, 'activate').andCallThrough() - atom.packages.activatePackage("package-with-serialization") - expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) + waitsForPromise -> + atom.packages.activatePackage("package-with-serialization") + runs -> + expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77}) it "logs warning instead of throwing an exception if the package fails to load", -> atom.config.set("core.disabledPackages", []) @@ -202,11 +205,13 @@ describe "PackageManager", -> expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element2[0])).toHaveLength 0 expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element3[0])).toHaveLength 0 - atom.packages.activatePackage("package-with-keymaps") + waitsForPromise -> + atom.packages.activatePackage("package-with-keymaps") - expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])[0].command).toBe "test-1" - expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element2[0])[0].command).toBe "test-2" - expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element3[0])).toHaveLength 0 + runs -> + expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])[0].command).toBe "test-1" + expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element2[0])[0].command).toBe "test-2" + expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element3[0])).toHaveLength 0 describe "when the metadata contains a 'keymaps' manifest", -> it "loads only the keymaps specified by the manifest, in the specified order", -> @@ -215,11 +220,13 @@ describe "PackageManager", -> expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])).toHaveLength 0 - atom.packages.activatePackage("package-with-keymaps-manifest") + waitsForPromise -> + atom.packages.activatePackage("package-with-keymaps-manifest") - expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])[0].command).toBe 'keymap-1' - expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-n', target:element1[0])[0].command).toBe 'keymap-2' - expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-y', target:element3[0])).toHaveLength 0 + runs -> + expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])[0].command).toBe 'keymap-1' + expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-n', target:element1[0])[0].command).toBe 'keymap-2' + expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-y', target:element3[0])).toHaveLength 0 describe "menu loading", -> beforeEach -> @@ -232,14 +239,16 @@ describe "PackageManager", -> expect(atom.contextMenu.templateForElement(element)).toEqual [] - atom.packages.activatePackage("package-with-menus") + waitsForPromise -> + atom.packages.activatePackage("package-with-menus") - expect(atom.menu.template.length).toBe 2 - expect(atom.menu.template[0].label).toBe "Second to Last" - expect(atom.menu.template[1].label).toBe "Last" - expect(atom.contextMenu.templateForElement(element)[0].label).toBe "Menu item 1" - expect(atom.contextMenu.templateForElement(element)[1].label).toBe "Menu item 2" - expect(atom.contextMenu.templateForElement(element)[2].label).toBe "Menu item 3" + runs -> + expect(atom.menu.template.length).toBe 2 + expect(atom.menu.template[0].label).toBe "Second to Last" + expect(atom.menu.template[1].label).toBe "Last" + expect(atom.contextMenu.templateForElement(element)[0].label).toBe "Menu item 1" + expect(atom.contextMenu.templateForElement(element)[1].label).toBe "Menu item 2" + expect(atom.contextMenu.templateForElement(element)[2].label).toBe "Menu item 3" describe "when the metadata contains a 'menus' manifest", -> it "loads only the menus specified by the manifest, in the specified order", -> @@ -247,13 +256,15 @@ describe "PackageManager", -> expect(atom.contextMenu.templateForElement(element)).toEqual [] - atom.packages.activatePackage("package-with-menus-manifest") + waitsForPromise -> + atom.packages.activatePackage("package-with-menus-manifest") - expect(atom.menu.template[0].label).toBe "Second to Last" - expect(atom.menu.template[1].label).toBe "Last" - expect(atom.contextMenu.templateForElement(element)[0].label).toBe "Menu item 2" - expect(atom.contextMenu.templateForElement(element)[1].label).toBe "Menu item 1" - expect(atom.contextMenu.templateForElement(element)[2]).toBeUndefined() + runs -> + expect(atom.menu.template[0].label).toBe "Second to Last" + expect(atom.menu.template[1].label).toBe "Last" + expect(atom.contextMenu.templateForElement(element)[0].label).toBe "Menu item 2" + expect(atom.contextMenu.templateForElement(element)[1].label).toBe "Menu item 1" + expect(atom.contextMenu.templateForElement(element)[2]).toBeUndefined() describe "stylesheet loading", -> describe "when the metadata contains a 'stylesheets' manifest", -> @@ -270,12 +281,14 @@ describe "PackageManager", -> expect(atom.themes.stylesheetElementForId(two)).toBeNull() expect(atom.themes.stylesheetElementForId(three)).toBeNull() - atom.packages.activatePackage("package-with-stylesheets-manifest") + waitsForPromise -> + atom.packages.activatePackage("package-with-stylesheets-manifest") - expect(atom.themes.stylesheetElementForId(one)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(two)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(three)).toBeNull() - expect($('#jasmine-content').css('font-size')).toBe '1px' + runs -> + expect(atom.themes.stylesheetElementForId(one)).not.toBeNull() + expect(atom.themes.stylesheetElementForId(two)).not.toBeNull() + expect(atom.themes.stylesheetElementForId(three)).toBeNull() + expect($('#jasmine-content').css('font-size')).toBe '1px' describe "when the metadata does not contain a 'stylesheets' manifest", -> it "loads all stylesheets from the stylesheets directory", -> @@ -292,11 +305,22 @@ describe "PackageManager", -> expect(atom.themes.stylesheetElementForId(two)).toBeNull() expect(atom.themes.stylesheetElementForId(three)).toBeNull() + waitsForPromise -> + atom.packages.activatePackage("package-with-stylesheets") + + runs -> + expect(atom.themes.stylesheetElementForId(one)).not.toBeNull() + expect(atom.themes.stylesheetElementForId(two)).not.toBeNull() + expect(atom.themes.stylesheetElementForId(three)).not.toBeNull() + expect($('#jasmine-content').css('font-size')).toBe '3px' + + it "assigns the stylesheet's context based on the filename", -> + waitsForPromise -> atom.packages.activatePackage("package-with-stylesheets") - expect(atom.themes.stylesheetElementForId(one)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(two)).not.toBeNull() - expect(atom.themes.stylesheetElementForId(three)).not.toBeNull() - expect($('#jasmine-content').css('font-size')).toBe '3px' + + runs -> + element = atom.styles.getStyleElements().find (element) -> element.context is 'test-context' + expect(element).toBeDefined() describe "grammar loading", -> it "loads the package's grammars", -> diff --git a/spec/package-spec.coffee b/spec/package-spec.coffee index 89b8225c0..970d9b64f 100644 --- a/spec/package-spec.coffee +++ b/spec/package-spec.coffee @@ -92,7 +92,7 @@ describe "Package", -> it "reloads without readding to the stylesheets list", -> expect(theme.getStylesheetPaths().length).toBe 3 - theme.reloadStylesheet(theme.getStylesheetPaths()[0]) + theme.reloadStylesheets() expect(theme.getStylesheetPaths().length).toBe 3 describe "events", -> diff --git a/spec/select-list-view-spec.coffee b/spec/select-list-view-spec.coffee index b59f76faa..db6a1b8c8 100644 --- a/spec/select-list-view-spec.coffee +++ b/spec/select-list-view-spec.coffee @@ -184,7 +184,7 @@ describe "SelectListView", -> describe "when the mini editor loses focus", -> it "triggers the cancelled hook and detaches the select list", -> spyOn(selectList, 'detach') - filterEditorView.hiddenInput.trigger 'focusout' + filterEditorView.trigger 'blur' expect(selectList.cancelled).toHaveBeenCalled() expect(selectList.detach).toHaveBeenCalled() diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 6f84c2eab..9eef239ee 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -106,6 +106,7 @@ beforeEach -> config.set "editor.autoIndent", false config.set "core.disabledPackages", ["package-that-throws-an-exception", "package-with-broken-package-json", "package-with-broken-keymap"] + config.set "editor.useShadowDOM", true config.load.reset() config.save.reset() @@ -219,7 +220,7 @@ addCustomMatchers = (spec) -> @message = -> return "Expected element '" + @actual + "' or its descendants" + notText + " to have focus." element = @actual element = element.get(0) if element.jquery - element.webkitMatchesSelector(":focus") or element.querySelector(":focus") + element is document.activeElement or element.contains(document.activeElement) toShow: -> notText = if @isNot then " not" else "" @@ -338,12 +339,8 @@ window.setEditorWidthInChars = (editorView, widthInChars, charWidth=editorView.c $(window).trigger 'resize' # update width of editor view's on-screen lines window.setEditorHeightInLines = (editorView, heightInLines, lineHeight=editorView.lineHeight) -> - if editorView.hasClass('react') - editorView.height(editorView.getEditor().getLineHeightInPixels() * heightInLines) - editorView.component?.measureHeightAndWidth() - else - editorView.height(lineHeight * heightInLines + editorView.renderedLines.position().top) - $(window).trigger 'resize' # update editor view's on-screen lines + editorView.height(editorView.getEditor().getLineHeightInPixels() * heightInLines) + editorView.component?.measureHeightAndWidth() $.fn.resultOfTrigger = (type) -> event = $.Event(type) diff --git a/spec/styles-element-spec.coffee b/spec/styles-element-spec.coffee index 0cfb1a185..7b1fa1873 100644 --- a/spec/styles-element-spec.coffee +++ b/spec/styles-element-spec.coffee @@ -52,3 +52,24 @@ describe "StylesElement", -> expect(element.children.length).toBe initialChildCount + 1 expect(element.children[initialChildCount].textContent).toBe "a {color: blue;}" expect(updatedStyleElements).toEqual [element.children[initialChildCount]] + + it "only includes style elements matching the 'context' attribute", -> + initialChildCount = element.children.length + + atom.styles.addStyleSheet("a {color: red;}", context: 'test-context') + atom.styles.addStyleSheet("a {color: green;}") + + expect(element.children.length).toBe initialChildCount + 1 + expect(element.children[initialChildCount].textContent).toBe "a {color: green;}" + + element.setAttribute('context', 'test-context') + + expect(element.children.length).toBe 1 + expect(element.children[0].textContent).toBe "a {color: red;}" + + atom.styles.addStyleSheet("a {color: blue;}", context: 'test-context') + atom.styles.addStyleSheet("a {color: yellow;}") + + expect(element.children.length).toBe 2 + expect(element.children[0].textContent).toBe "a {color: red;}" + expect(element.children[1].textContent).toBe "a {color: blue;}" diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 251a54111..811681648 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -721,11 +721,11 @@ describe "TextEditorComponent", -> editor.setCursorScreenPosition([0, 16]) nextAnimationFrame() - atom.themes.applyStylesheet 'test', """ + atom.styles.addStyleSheet """ .function.js { font-weight: bold; } - """ + """, context: 'atom-text-editor' nextAnimationFrame() # update based on new measurements cursor = componentNode.querySelector('.cursor') @@ -1537,8 +1537,9 @@ describe "TextEditorComponent", -> it "transfers focus to the hidden input", -> expect(document.activeElement).toBe document.body - componentNode.focus() - expect(document.activeElement).toBe inputNode + wrapperNode.focus() + expect(document.activeElement).toBe wrapperNode + expect(wrapperNode.shadowRoot.activeElement).toBe inputNode it "adds the 'is-focused' class to the editor when the hidden input is focused", -> expect(document.activeElement).toBe document.body @@ -1667,12 +1668,13 @@ describe "TextEditorComponent", -> component.measureHeightAndWidth() nextAnimationFrame() - atom.themes.applyStylesheet "test", """ + atom.styles.addStyleSheet """ ::-webkit-scrollbar { width: 8px; height: 8px; } - """ + """, context: 'atom-text-editor' + nextAnimationFrame() scrollbarCornerNode = componentNode.querySelector('.scrollbar-corner') diff --git a/spec/text-editor-element-spec.coffee b/spec/text-editor-element-spec.coffee index ae213bc01..6de999618 100644 --- a/spec/text-editor-element-spec.coffee +++ b/spec/text-editor-element-spec.coffee @@ -19,18 +19,43 @@ describe "TextEditorElement", -> element = jasmineContent.firstChild expect(element.getModel().getPlaceholderText()).toBe 'testing' - describe "::focus()", -> - it "transfers focus to the hidden text area and does not emit 'focusout' or 'blur' events", -> - element = new TextEditorElement - jasmineContent.appendChild(element) + describe "focus and blur handling", -> + describe "when the editor.useShadowDOM config option is true", -> + it "proxies focus/blur events to/from the hidden input inside the shadow root", -> + atom.config.set('editor.useShadowDOM', true) - focusoutCalled = false - element.addEventListener 'focusout', -> focusoutCalled = true - blurCalled = false - element.addEventListener 'blur', -> blurCalled = true + element = new TextEditorElement + jasmineContent.appendChild(element) - element.focus() - expect(focusoutCalled).toBe false - expect(blurCalled).toBe false - expect(element.hasFocus()).toBe true - expect(element.querySelector('input')).toBe document.activeElement + blurCalled = false + element.addEventListener 'blur', -> blurCalled = true + + 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') + + document.body.focus() + expect(blurCalled).toBe true + + describe "when the editor.useShadowDOM config option is false", -> + afterEach -> + document.head.querySelector('atom-styles[context="atom-text-editor"]').remove() + + it "proxies focus/blur events to/from the hidden input", -> + atom.config.set('editor.useShadowDOM', false) + + element = new TextEditorElement + jasmineContent.appendChild(element) + + blurCalled = false + element.addEventListener 'blur', -> blurCalled = true + + element.focus() + expect(blurCalled).toBe false + expect(element.hasFocus()).toBe true + expect(document.activeElement).toBe element.querySelector('input') + + document.body.focus() + expect(blurCalled).toBe true diff --git a/spec/theme-manager-spec.coffee b/spec/theme-manager-spec.coffee index b03984534..334a0c9df 100644 --- a/spec/theme-manager-spec.coffee +++ b/spec/theme-manager-spec.coffee @@ -85,7 +85,16 @@ describe "ThemeManager", -> runs -> reloadHandler.reset() expect($('style.theme')).toHaveLength 0 - atom.config.set('core.themes', ['atom-dark-syntax']) + atom.config.set('core.themes', ['atom-dark-ui']) + + waitsFor -> + reloadHandler.callCount == 1 + + runs -> + reloadHandler.reset() + expect($('style[group=theme]')).toHaveLength 1 + expect($('style[group=theme]:eq(0)').attr('source-path')).toMatch /atom-dark-ui/ + atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-ui']) waitsFor -> reloadHandler.callCount == 1 @@ -93,17 +102,8 @@ describe "ThemeManager", -> runs -> reloadHandler.reset() expect($('style[group=theme]')).toHaveLength 2 - expect($('style[group=theme]:eq(1)').attr('source-path')).toMatch /atom-dark-syntax/ - atom.config.set('core.themes', ['atom-light-syntax', 'atom-dark-syntax']) - - waitsFor -> - reloadHandler.callCount == 1 - - runs -> - reloadHandler.reset() - expect($('style[group=theme]')).toHaveLength 2 - expect($('style[group=theme]:eq(0)').attr('source-path')).toMatch /atom-dark-syntax/ - expect($('style[group=theme]:eq(1)').attr('source-path')).toMatch /atom-light-syntax/ + expect($('style[group=theme]:eq(0)').attr('source-path')).toMatch /atom-dark-ui/ + expect($('style[group=theme]:eq(1)').attr('source-path')).toMatch /atom-light-ui/ atom.config.set('core.themes', []) waitsFor -> @@ -111,7 +111,7 @@ describe "ThemeManager", -> runs -> reloadHandler.reset() - expect($('style[group=theme]')).toHaveLength 2 + expect($('style[group=theme]')).toHaveLength 1 # atom-dark-ui has an directory path, the syntax one doesn't atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui']) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 85bb5e194..afb1af4ce 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -109,6 +109,11 @@ module.exports = type: 'boolean' default: true description: 'Disabling will improve editor font rendering but reduce scrolling performance.' + useShadowDOM: + type: 'boolean' + default: false + title: 'Use Shadow DOM' + description: 'Enable to test out themes and packages with the new shadow DOM before it ships by default.' confirmCheckoutHeadRevision: type: 'boolean' default: true diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index c6cf62490..798d81ac3 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -16,12 +16,12 @@ GutterComponent = React.createClass measuredWidth: null render: -> - {scrollHeight, scrollViewHeight, onMouseDown, backgroundColor, gutterBackgroundColor} = @props + {scrollHeight, scrollViewHeight, backgroundColor, gutterBackgroundColor} = @props if gutterBackgroundColor isnt 'rbga(0, 0, 0, 0)' backgroundColor = gutterBackgroundColor - div className: 'gutter', onClick: @onClick, onMouseDown: @onMouseDown, + div className: 'gutter', div className: 'line-numbers', ref: 'lineNumbers', style: height: Math.max(scrollHeight, scrollViewHeight) WebkitTransform: @getTransform() @@ -45,6 +45,10 @@ GutterComponent = React.createClass @appendDummyLineNumber() @updateLineNumbers() if @props.performedInitialMeasurement + node = @getDOMNode() + node.addEventListener 'click', @onClick + node.addEventListener 'mousedown', @onMouseDown + # Only update the gutter if the visible row range has changed or if a # non-zero-delta change to the screen lines has occurred within the current # visible row range. diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index dd0749ffd..7020a93e4 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -21,5 +21,11 @@ HighlightsComponent = React.createClass highlightComponents + componentDidMount: -> + if atom.config.get('editor.useShadowDOM') + insertionPoint = document.createElement('content') + insertionPoint.setAttribute('select', '.underlayer') + @getDOMNode().appendChild(insertionPoint) + shouldComponentUpdate: (newProps) -> not isEqualForProperties(newProps, @props, 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth', 'scopedCharacterWidthsChangeCount') diff --git a/src/input-component.coffee b/src/input-component.coffee index 230c4be37..776be6c14 100644 --- a/src/input-component.coffee +++ b/src/input-component.coffee @@ -7,16 +7,17 @@ InputComponent = React.createClass displayName: 'InputComponent' render: -> - {className, style, onFocus, onBlur} = @props + {className, style} = @props - input {className, style, onFocus, onBlur, 'data-react-skip-selection-restoration': true} + input {className, style, 'data-react-skip-selection-restoration': true} getInitialState: -> {lastChar: ''} componentDidMount: -> - @getDOMNode().addEventListener 'paste', @onPaste - @getDOMNode().addEventListener 'compositionupdate', @onCompositionUpdate + node = @getDOMNode() + node.addEventListener 'paste', @onPaste + node.addEventListener 'compositionupdate', @onCompositionUpdate # Don't let text accumulate in the input forever, but avoid excessive reflows componentDidUpdate: -> @@ -34,11 +35,5 @@ InputComponent = React.createClass onPaste: (e) -> e.preventDefault() - onFocus: -> - @props.onFocus?() - - onBlur: -> - @props.onBlur?() - focus: -> @getDOMNode().focus() diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 865b4b19a..f94926c80 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -57,6 +57,12 @@ LinesComponent = React.createClass @lineIdsByScreenRow = {} @renderedDecorationsByLineId = {} + componentDidMount: -> + if atom.config.get('editor.useShadowDOM') + insertionPoint = document.createElement('content') + insertionPoint.setAttribute('select', '.overlayer') + @getDOMNode().appendChild(insertionPoint) + shouldComponentUpdate: (newProps) -> return true unless isEqualForProperties(newProps, @props, 'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth', diff --git a/src/package.coffee b/src/package.coffee index 76024a3c3..79485da69 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -50,6 +50,7 @@ class Package keymaps: null menus: null stylesheets: null + stylesheetDisposables: null grammars: null scopedProperties: null mainModulePath: null @@ -175,9 +176,16 @@ class Package activateStylesheets: -> return if @stylesheetsActivated - type = @getStylesheetType() - for [stylesheetPath, content] in @stylesheets - atom.themes.applyStylesheet(stylesheetPath, content, type) + + group = @getStylesheetType() + @stylesheetDisposables = new CompositeDisposable + for [sourcePath, source] in @stylesheets + if match = path.basename(sourcePath).match(/[^.]*\.([^.]*)\./) + context = match[1] + else if @metadata.theme is 'syntax' + context = 'atom-text-editor' + + @stylesheetDisposables.add(atom.styles.addStyleSheet(source, {sourcePath, group, context})) @stylesheetsActivated = true activateResources: -> @@ -320,7 +328,7 @@ class Package deactivateResources: -> grammar.deactivate() for grammar in @grammars scopedProperties.deactivate() for scopedProperties in @scopedProperties - atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets + @stylesheetDisposables?.dispose() @activationDisposables?.dispose() @stylesheetsActivated = false @grammarsActivated = false @@ -329,11 +337,10 @@ class Package reloadStylesheets: -> oldSheets = _.clone(@stylesheets) @loadStylesheets() - atom.themes.removeStylesheet(stylesheetPath) for [stylesheetPath] in oldSheets - @reloadStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets - - reloadStylesheet: (stylesheetPath, content) -> - atom.themes.applyStylesheet(stylesheetPath, content, @getStylesheetType()) + @stylesheetDisposables.dispose() + @stylesheetDisposables = new CompositeDisposable + @stylesheetsActivated = false + @activateStylesheets() requireMainModule: -> return @mainModule if @mainModule? diff --git a/src/pane-element.coffee b/src/pane-element.coffee index e44eaea6f..2c3708523 100644 --- a/src/pane-element.coffee +++ b/src/pane-element.coffee @@ -28,9 +28,17 @@ class PaneElement extends HTMLElement @itemViews.setAttribute 'class', 'item-views' subscribeToDOMEvents: -> - @addEventListener 'focusin', => @model.focus() - @addEventListener 'focusout', => @model.blur() - @addEventListener 'focus', => @getActiveView()?.focus() + handleFocus = (event) => + @model.focus() + if event.target is this and view = @getActiveView() + view.focus() + event.stopPropagation() + + handleBlur = (event) => + @model.blur() unless @contains(event.relatedTarget) + + @addEventListener 'focus', handleFocus, true + @addEventListener 'blur', handleBlur, true createSpacePenShim: -> @__spacePenView = new PaneView(this) diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index 1e23d686c..9290835db 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -23,7 +23,7 @@ ScrollbarComponent = React.createClass style.right = verticalScrollbarWidth if scrollableInOppositeDirection style.height = horizontalScrollbarHeight - div {className, style, @onScroll}, + div {className, style}, switch orientation when 'vertical' div className: 'scrollbar-content', style: {height: scrollHeight} @@ -36,6 +36,11 @@ ScrollbarComponent = React.createClass unless orientation is 'vertical' or orientation is 'horizontal' throw new Error("Must specify an orientation property of 'vertical' or 'horizontal'") + @getDOMNode().addEventListener 'scroll', @onScroll + + componentWillUnmount: -> + @getDOMNode().removeEventListener 'scroll', @onScroll + shouldComponentUpdate: (newProps) -> return true if newProps.visible isnt @props.visible diff --git a/src/select-list-view.coffee b/src/select-list-view.coffee index 1fdc60e4a..dc494cb00 100644 --- a/src/select-list-view.coffee +++ b/src/select-list-view.coffee @@ -57,7 +57,7 @@ class SelectListView extends View initialize: -> @filterEditorView.getEditor().getBuffer().onDidChange => @schedulePopulateList() - @filterEditorView.hiddenInput.on 'focusout', => + @filterEditorView.on 'blur', => @cancel() unless @cancelling # This prevents the focusout event from firing on the filter editor view @@ -254,7 +254,7 @@ class SelectListView extends View # Extended: Store the currently focused element. This element will be given # back focus when {::cancel} is called. storeFocusedElement: -> - @previouslyFocusedElement = $(':focus') + @previouslyFocusedElement = $(document.activeElement) ### Section: Private diff --git a/src/space-pen-extensions.coffee b/src/space-pen-extensions.coffee index 7eda24bbc..353d71a9d 100644 --- a/src/space-pen-extensions.coffee +++ b/src/space-pen-extensions.coffee @@ -81,6 +81,18 @@ jQuery.event.remove = (elem, types, originalHandler, selector, mappedTypes) -> handler = HandlersByOriginalHandler.get(originalHandler) ? originalHandler JQueryEventRemove(elem, types, handler, selector, mappedTypes, RemoveEventListener if atom?.commands?) +JQueryContains = jQuery.contains + +jQuery.contains = (a, b) -> + shadowRoot = null + currentNode = b + while currentNode + if currentNode instanceof ShadowRoot and a.contains(currentNode.host) + return true + currentNode = currentNode.parentNode + + JQueryContains.call(this, a, b) + tooltipDefaults = delay: show: 1000 diff --git a/src/style-manager.coffee b/src/style-manager.coffee index 9f6b912fd..7b9a786c7 100644 --- a/src/style-manager.coffee +++ b/src/style-manager.coffee @@ -25,6 +25,7 @@ class StyleManager addStyleSheet: (source, params) -> sourcePath = params?.sourcePath + context = params?.context group = params?.group if sourcePath? and styleElement = @styleElementsBySourcePath[sourcePath] diff --git a/src/styles-element.coffee b/src/styles-element.coffee index 069c3f8c7..8510fe594 100644 --- a/src/styles-element.coffee +++ b/src/styles-element.coffee @@ -1,8 +1,8 @@ {Emitter, CompositeDisposable} = require 'event-kit' class StylesElement extends HTMLElement - createdCallback: -> - @emitter = new Emitter + subscriptions: null + context: null onDidAddStyleElement: (callback) -> @emitter.on 'did-add-style-element', callback @@ -13,15 +13,42 @@ class StylesElement extends HTMLElement onDidUpdateStyleElement: (callback) -> @emitter.on 'did-update-style-element', callback - attachedCallback: -> - @subscriptions = new CompositeDisposable + createdCallback: -> + @emitter = new Emitter @styleElementClonesByOriginalElement = new WeakMap + + attachedCallback: -> + @initialize() + + detachedCallback: -> + @subscriptions.dispose() + @subscriptions = null + + attributeChangedCallback: (attrName, oldVal, newVal) -> + @contextChanged() if attrName is 'context' + + initialize: -> + return if @subscriptions? + + @subscriptions = new CompositeDisposable + @context = @getAttribute('context') ? undefined + @subscriptions.add atom.styles.observeStyleElements(@styleElementAdded.bind(this)) @subscriptions.add atom.styles.onDidRemoveStyleElement(@styleElementRemoved.bind(this)) @subscriptions.add atom.styles.onDidUpdateStyleElement(@styleElementUpdated.bind(this)) + contextChanged: -> + return unless @subscriptions? + + @styleElementRemoved(child) for child in Array::slice.call(@children) + @context = @getAttribute('context') + @styleElementAdded(styleElement) for styleElement in atom.styles.getStyleElements() + styleElementAdded: (styleElement) -> + return unless styleElement.context is @context + styleElementClone = styleElement.cloneNode(true) + styleElementClone.context = styleElement.context @styleElementClonesByOriginalElement.set(styleElement, styleElementClone) group = styleElement.getAttribute('group') @@ -35,16 +62,17 @@ class StylesElement extends HTMLElement @emitter.emit 'did-add-style-element', styleElementClone styleElementRemoved: (styleElement) -> - styleElementClone = @styleElementClonesByOriginalElement.get(styleElement) + return unless styleElement.context is @context + + styleElementClone = @styleElementClonesByOriginalElement.get(styleElement) ? styleElement styleElementClone.remove() @emitter.emit 'did-remove-style-element', styleElementClone styleElementUpdated: (styleElement) -> + return unless styleElement.context is @context + styleElementClone = @styleElementClonesByOriginalElement.get(styleElement) styleElementClone.textContent = styleElement.textContent @emitter.emit 'did-update-style-element', styleElementClone - detachedCallback: -> - @subscriptions.dispose() - 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 6dc4700d0..a57f2762d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -93,7 +93,7 @@ TextEditorComponent = React.createClass className += ' is-focused' if focused className += ' has-selection' if hasSelection - div {className, style, tabIndex: -1}, + div {className, style}, if @shouldRenderGutter() GutterComponent { ref: 'gutter', onMouseDown: @onGutterMouseDown, lineDecorations, @@ -102,13 +102,11 @@ TextEditorComponent = React.createClass @useHardwareAcceleration, @performedInitialMeasurement, @backgroundColor, @gutterBackgroundColor } - div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown, + div ref: 'scrollView', className: 'scroll-view', InputComponent ref: 'input' className: 'hidden-input' style: hiddenInputStyle - onFocus: @onInputFocused - onBlur: @onInputBlurred LinesComponent { ref: 'lines', @@ -175,14 +173,14 @@ TextEditorComponent = React.createClass @setScrollSensitivity(atom.config.get('editor.scrollSensitivity')) componentDidMount: -> - {editor} = @props + {editor, stylesElement} = @props @observeEditor() @listenForDOMEvents() - @subscribe atom.themes.onDidAddStylesheet @onStylesheetsChanged - @subscribe atom.themes.onDidUpdateStylesheet @onStylesheetsChanged - @subscribe atom.themes.onDidRemoveStylesheet @onStylesheetsChanged + @subscribe stylesElement.onDidAddStyleElement @onStylesheetsChanged + @subscribe stylesElement.onDidUpdateStyleElement @onStylesheetsChanged + @subscribe stylesElement.onDidRemoveStyleElement @onStylesheetsChanged unless atom.themes.isInitialLoadComplete() @subscribe atom.themes.onDidReloadAll @onStylesheetsChanged @subscribe scrollbarStyle.changes, @refreshScrollbars @@ -193,9 +191,9 @@ TextEditorComponent = React.createClass @checkForVisibilityChange() componentWillUnmount: -> - {editor, parentView} = @props + {editor, hostElement} = @props - parentView.__spacePenView.trigger 'editor:will-be-removed', [parentView.__spacePenView] + hostElement.__spacePenView.trigger 'editor:will-be-removed', [hostElement.__spacePenView] @unsubscribe() @scopedConfigSubscriptions.dispose() window.removeEventListener 'resize', @requestHeightAndWidthMeasurement @@ -215,9 +213,9 @@ TextEditorComponent = React.createClass if @props.editor.isAlive() @updateParentViewFocusedClassIfNeeded(prevState) @updateParentViewMiniClassIfNeeded(prevState) - @props.parentView.__spacePenView.trigger 'cursor:moved' if cursorMoved - @props.parentView.__spacePenView.trigger 'selection:changed' if selectionChanged - @props.parentView.__spacePenView.trigger 'editor:display-updated' + @props.hostElement.__spacePenView.trigger 'cursor:moved' if cursorMoved + @props.hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged + @props.hostElement.__spacePenView.trigger 'editor:display-updated' becameVisible: -> @updatesPaused = true @@ -261,7 +259,7 @@ TextEditorComponent = React.createClass @forceUpdate() getTopmostDOMNode: -> - @props.parentView + @props.hostElement getRenderedRowRange: -> {editor, lineOverdrawMargin} = @props @@ -378,8 +376,8 @@ TextEditorComponent = React.createClass listenForDOMEvents: -> node = @getDOMNode() node.addEventListener 'mousewheel', @onMouseWheel - node.addEventListener 'focus', @onFocus # For some reason, React's built in focus events seem to bubble node.addEventListener 'textInput', @onTextInput + @refs.scrollView.getDOMNode().addEventListener 'mousedown', @onMouseDown scrollViewNode = @refs.scrollView.getDOMNode() scrollViewNode.addEventListener 'scroll', @onScrollViewScroll @@ -415,6 +413,9 @@ TextEditorComponent = React.createClass observeConfig: -> @subscribe atom.config.observe 'editor.useHardwareAcceleration', @setUseHardwareAcceleration + @subscribe atom.config.onDidChange 'editor.fontSize', @sampleFontStyling + @subscribe atom.config.onDidChange 'editor.fontFamily', @sampleFontStyling + @subscribe atom.config.onDidChange 'editor.lineHeight', @sampleFontStyling onGrammarChanged: -> {editor} = @props @@ -428,8 +429,14 @@ TextEditorComponent = React.createClass subscriptions.add atom.config.observe scopeDescriptor, 'editor.showLineNumbers', @setShowLineNumbers subscriptions.add atom.config.observe scopeDescriptor, 'editor.scrollSensitivity', @setScrollSensitivity - onFocus: -> - @refs.input.focus() if @isMounted() + focused: -> + if @isMounted() + @setState(focused: true) + @refs.input.focus() + + blurred: -> + if @isMounted() + @setState(focused: false) onTextInput: (event) -> event.stopPropagation() @@ -452,12 +459,6 @@ TextEditorComponent = React.createClass inputNode.value = event.data if editor.insertText(event.data) - onInputFocused: -> - @setState(focused: true) - - onInputBlurred: -> - @setState(focused: false) - onVerticalScroll: (scrollTop) -> {editor} = @props @@ -621,11 +622,11 @@ TextEditorComponent = React.createClass else editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]], preserveFolds: true) - onStylesheetsChanged: (stylesheet) -> + onStylesheetsChanged: (styleElement) -> return unless @performedInitialMeasurement return unless atom.themes.isInitialLoadComplete() - @refreshScrollbars() if not stylesheet? or @containsScrollbarSelector(stylesheet) + @refreshScrollbars() if not styleElement.sheet? or @containsScrollbarSelector(styleElement.sheet) @sampleFontStyling() @sampleBackgroundColors() @remeasureCharacterWidths() @@ -771,10 +772,10 @@ TextEditorComponent = React.createClass measureHeightAndWidth: -> return unless @isMounted() - {editor, parentView} = @props + {editor, hostElement} = @props scrollViewNode = @refs.scrollView.getDOMNode() - {position} = getComputedStyle(parentView) - {height} = parentView.style + {position} = getComputedStyle(hostElement) + {height} = hostElement.style if position is 'absolute' or height if @autoHeight @@ -806,9 +807,9 @@ TextEditorComponent = React.createClass @remeasureCharacterWidths() sampleBackgroundColors: (suppressUpdate) -> - {parentView} = @props + {hostElement} = @props {showLineNumbers} = @state - {backgroundColor} = getComputedStyle(parentView) + {backgroundColor} = getComputedStyle(hostElement) if backgroundColor isnt @backgroundColor @backgroundColor = backgroundColor @@ -907,10 +908,10 @@ TextEditorComponent = React.createClass lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow) screenRowForNode: (node) -> - while node isnt document + while node? if screenRow = node.dataset.screenRow return parseInt(screenRow) - node = node.parentNode + node = node.parentElement null getFontSize: -> @@ -977,11 +978,13 @@ TextEditorComponent = React.createClass updateParentViewFocusedClassIfNeeded: (prevState) -> if prevState.focused isnt @state.focused - @props.parentView.classList.toggle('is-focused', @state.focused) + @props.hostElement.classList.toggle('is-focused', @state.focused) + @props.rootElement.classList.toggle('is-focused', @state.focused) updateParentViewMiniClassIfNeeded: (prevProps) -> if prevProps.mini isnt @props.mini - @props.parentView.classList.toggle('mini', @props.mini) + @props.hostElement.classList.toggle('mini', @props.mini) + @props.rootElement.classList.toggle('mini', @props.mini) runScrollBenchmark: -> unless process.env.NODE_ENV is 'production' diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 8416bb285..4f6f9c2d2 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -6,6 +6,8 @@ TextEditor = require './text-editor' TextEditorComponent = require './text-editor-component' TextEditorView = null +GlobalStylesElement = null + class TextEditorElement extends HTMLElement model: null componentDescriptor: null @@ -18,13 +20,34 @@ class TextEditorElement extends HTMLElement @initializeContent() @createSpacePenShim() @addEventListener 'focus', @focused.bind(this) - @addEventListener 'focusout', @focusedOut.bind(this) @addEventListener 'blur', @blurred.bind(this) initializeContent: (attributes) -> - @classList.add('editor', 'react', 'editor-colors') + @classList.add('editor') @setAttribute('tabindex', -1) + if atom.config.get('editor.useShadowDOM') + @createShadowRoot() + + @stylesElement = document.createElement('atom-styles') + @stylesElement.setAttribute('context', 'atom-text-editor') + @stylesElement.initialize() + + @rootElement = document.createElement('div') + @rootElement.classList.add('editor', 'editor-colors') + + @shadowRoot.appendChild(@stylesElement) + @shadowRoot.appendChild(@rootElement) + else + unless GlobalStylesElement? + GlobalStylesElement = document.createElement('atom-styles') + GlobalStylesElement.setAttribute('context', 'atom-text-editor') + GlobalStylesElement.initialize() + document.head.appendChild(GlobalStylesElement) + + @stylesElement = GlobalStylesElement + @rootElement = this + createSpacePenShim: -> TextEditorView ?= require './text-editor-view' @__spacePenView = new TextEditorView(this) @@ -64,12 +87,19 @@ class TextEditorElement extends HTMLElement mountComponent: -> @componentDescriptor ?= TextEditorComponent( - parentView: this + hostElement: this + rootElement: @rootElement + stylesElement: @stylesElement editor: @model mini: @model.mini lineOverdrawMargin: @lineOverdrawMargin ) - @component = React.renderComponent(@componentDescriptor, this) + @component = React.renderComponent(@componentDescriptor, @rootElement) + + unless atom.config.get('editor.useShadowDOM') + inputNode = @component.refs.input.getDOMNode() + inputNode.addEventListener 'focus', @focused.bind(this) + inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false)) unmountComponent: -> return unless @component?.isMounted() @@ -79,15 +109,17 @@ class TextEditorElement extends HTMLElement focused: -> if @component? - @component.onFocus() + @component.focused() else @focusOnAttach = true - focusedOut: (event) -> - event.stopImmediatePropagation() if @contains(event.relatedTarget) - blurred: (event) -> - event.stopImmediatePropagation() if @contains(event.relatedTarget) + unless atom.config.get('editor.useShadowDOM') + if event.relatedTarget is @component?.refs.input?.getDOMNode() + event.stopImmediatePropagation() + return + + @component?.blurred() addGrammarScopeAttribute: -> grammarScope = @model.getGrammar()?.scopeName?.replace(/\./g, ' ') diff --git a/src/text-editor-view.coffee b/src/text-editor-view.coffee index 2e7365622..4fbe487d6 100644 --- a/src/text-editor-view.coffee +++ b/src/text-editor-view.coffee @@ -73,13 +73,26 @@ class TextEditorView extends View setModel: (@model) -> @editor = @model - @scrollView = @find('.scroll-view') - @underlayer = @find('.highlights').addClass('underlayer') - @overlayer = @find('.lines').addClass('overlayer') - @hiddenInput = @.find('.hidden-input') + @root = $(@element.rootElement) + + @scrollView = @root.find('.scroll-view') + + + if atom.config.get('editor.useShadowDOM') + @underlayer = $("
").appendTo(this) + @overlayer = $("
").appendTo(this) + else + @underlayer = @find('.highlights').addClass('underlayer') + @overlayer = @find('.lines').addClass('overlayer') + + @hiddenInput = @root.find('.hidden-input') + + @hiddenInput.on = (args...) => + args[0] = 'blur' if args[0] is 'focusout' + $::on.apply(this, args) @subscribe atom.config.observe 'editor.showLineNumbers', => - @gutter = @find('.gutter') + @gutter = @root.find('.gutter') @gutter.removeClassFromAllLines = (klass) => deprecate('Use decorations instead: http://blog.atom.io/2014/07/24/decorations.html') @@ -95,6 +108,13 @@ class TextEditorView extends View lines.addClass(klass) lines.length > 0 + find: -> + shadowResult = @root.find.apply(@root, arguments) + if shadowResult.length > 0 + shadowResult + else + super + # Public: Get the underlying editor model for this view. # # Returns an {TextEditor} @@ -107,7 +127,7 @@ class TextEditorView extends View Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0] Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1] Object.defineProperty @::, 'active', get: -> @is(@getPaneView()?.activeView) - Object.defineProperty @::, 'isFocused', get: -> @component?.state.focused + Object.defineProperty @::, 'isFocused', get: -> document.activeElement is @element or document.activeElement is @element.component?.refs.input.getDOMNode() Object.defineProperty @::, 'mini', get: -> @component?.props.mini Object.defineProperty @::, 'component', get: -> @element?.component @@ -164,7 +184,7 @@ class TextEditorView extends View appendToLinesView: (view) -> view.css('position', 'absolute') view.css('z-index', 1) - @find('.lines').prepend(view) + @overlayer.append(view) unmountComponent: -> React.unmountComponentAtNode(@element) if @component.isMounted() diff --git a/src/theme-manager.coffee b/src/theme-manager.coffee index e87028ae3..a0293f2cf 100644 --- a/src/theme-manager.coffee +++ b/src/theme-manager.coffee @@ -249,6 +249,9 @@ class ThemeManager if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less']) @requireStylesheet(nativeStylesheetPath) + textEditorStylesPath = path.join(@resourcePath, 'static', 'text-editor-shadow.less') + atom.styles.addStyleSheet(@loadLessStylesheet(textEditorStylesPath), sourcePath: textEditorStylesPath, context: 'atom-text-editor') + stylesheetElementForId: (id) -> document.head.querySelector("atom-styles style[source-path=\"#{id}\"]") diff --git a/static/atom.less b/static/atom.less index e2b252ad3..24231ceb6 100644 --- a/static/atom.less +++ b/static/atom.less @@ -21,7 +21,7 @@ @import "popover-list"; @import "messages"; @import "markdown"; -@import "editor"; +@import "text-editor-light"; @import "select-list"; @import "syntax"; @import "utilities"; diff --git a/static/editor.less b/static/editor.less deleted file mode 100644 index cc8e1be65..000000000 --- a/static/editor.less +++ /dev/null @@ -1,310 +0,0 @@ -@import "ui-variables"; -@import "octicon-utf-codes"; -@import "octicon-mixins"; - -atom-text-editor.react { - .editor-contents { - width: 100%; - } - - .underlayer { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: -2; - } - - .lines { - min-width: 100%; - } - - .cursor { - z-index: 4; - pointer-events: none; - } - - .editor-contents.is-focused .cursor { - visibility: visible; - } - - .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; - - .scrollbar-content { - height: 15px; - } - } - - .vertical-scrollbar { - overflow-x: hidden; - } - - .scrollbar-corner { - position: absolute; - overflow: auto; - bottom: 0; - right: 0; - } - - .scroll-view { - overflow: hidden; - z-index: 0; - } - - .scroll-view-content { - position: relative; - width: 100%; - } - - .gutter { - .line-number { - white-space: nowrap; - padding-left: .5em; - - .icon-right { - padding: 0 .4em; - &:before { - text-align: center; - } - } - } - } -} - -atom-text-editor.mini { - font-size: @input-font-size; - line-height: @component-line-height; - max-height: @component-line-height + 2; // +2 for borders - - .placeholder-text { - position: absolute; - color: @text-color-subtle; - } -} - -atom-text-editor { - z-index: 0; - font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier; - line-height: 1.3; -} - -atom-text-editor, .editor-contents { - overflow: hidden; - cursor: text; - display: -webkit-flex; - -webkit-user-select: none; - position: relative; -} - -atom-text-editor .gutter .line-number.cursor-line { - opacity: 1; -} - -atom-text-editor .gutter { - overflow: hidden; - text-align: right; - cursor: default; - min-width: 1em; - box-sizing: border-box; -} - -atom-text-editor .gutter .line-number { - padding-left: .5em; - opacity: 0.6; -} - -atom-text-editor .gutter .line-numbers { - position: relative; -} - -atom-text-editor .gutter .line-number.folded.cursor-line { - opacity: 1; -} - -atom-text-editor .gutter .line-number .icon-right { - .octicon(chevron-down, 0.8em); - display: inline-block; - visibility: hidden; - padding-left: .1em; - padding-right: .5em; - opacity: .6; -} - -atom-text-editor .gutter:hover .line-number.foldable .icon-right { - visibility: visible; - - &:before { - content: @chevron-down; - } - - &:hover { - opacity: 1; - } -} - -atom-text-editor .gutter, atom-text-editor .gutter:hover { - .line-number.folded .icon-right { - .octicon(chevron-right, 0.8em); - visibility: visible; - - &:before { // chevron-right renders too far right compared to chevron-down - position: relative; - left: -.1em; - content: @chevron-right; - } - } -} - -atom-text-editor .fold-marker { - cursor: default; -} - -atom-text-editor .fold-marker:after { - .icon(0.8em, inline); - content: @ellipsis; - padding-left: 0.2em; -} - -atom-text-editor .line.cursor-line .fold-marker:after { - opacity: 1; -} - -atom-text-editor.is-blurred .line.cursor-line { - background: rgba(0, 0, 0, 0); -} - -atom-text-editor .invisible-character { - font-weight: normal !important; - font-style: normal !important; -} - -atom-text-editor .indent-guide { - display: inline-block; - box-shadow: inset 1px 0; -} - -atom-text-editor .vertical-scrollbar, -atom-text-editor .horizontal-scrollbar { - cursor: default; -} - -atom-text-editor .vertical-scrollbar { - position: absolute; - top: 0; - right: 0; - bottom: 0; - - width: 15px; - overflow-y: auto; - z-index: 3; -} - -atom-text-editor .scroll-view { - overflow-x: auto; - overflow-y: hidden; - -webkit-flex: 1; - min-width: 0; - position: relative; -} - -atom-text-editor.soft-wrap .scroll-view { - overflow-x: hidden; -} - -atom-text-editor .underlayer { - z-index: 0; - position: absolute; - min-height: 100%; -} - -atom-text-editor .lines { - position: relative; - z-index: 1; -} - -atom-text-editor .overlayer { - z-index: 2; - position: absolute; -} - -atom-text-editor .line { - white-space: pre; -} - -atom-text-editor .line span { - vertical-align: top; -} - -atom-text-editor .cursor { - position: absolute; - border-left: 1px solid; -} - -atom-text-editor .cursor, -atom-text-editor.is-focused .cursor.blink-off { - visibility: hidden; -} - -atom-text-editor.is-focused .cursor { - visibility: visible; -} - -.cursor.hidden-cursor { - display: none; -} - -atom-text-editor .hidden-input { - padding: 0; - border: 0; - position: absolute; - z-index: -1; - top: 0; - left: 0; - opacity: 0; - width: 1px; -} - -atom-text-editor .highlight { - background: none; - padding: 0; -} - -atom-text-editor .highlight .region, -atom-text-editor .selection .region { - position: absolute; - pointer-events: none; - z-index: -1; -} - -atom-text-editor.mini:not(.react) { - height: auto; - line-height: 25px; - - .cursor { - width: 2px; - line-height: 20px; - margin-top: 2px; - } - - .gutter { - display: none; - } - - .scroll-view { - overflow: hidden; - } -} diff --git a/static/text-editor-light.less b/static/text-editor-light.less new file mode 100644 index 000000000..7021c4978 --- /dev/null +++ b/static/text-editor-light.less @@ -0,0 +1,13 @@ +@import "ui-variables"; + +atom-text-editor { + display: block; + font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier; + line-height: 1.3; +} + +atom-text-editor.mini { + font-size: @input-font-size; + line-height: @component-line-height; + max-height: @component-line-height + 2; // +2 for borders +} diff --git a/static/text-editor-shadow.less b/static/text-editor-shadow.less new file mode 100644 index 000000000..8187bdf93 --- /dev/null +++ b/static/text-editor-shadow.less @@ -0,0 +1,282 @@ +@import "ui-variables"; +@import "octicon-utf-codes"; +@import "octicon-mixins"; + +.editor, .editor-contents { + height: 100%; + width: 100%; +} + +.underlayer { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: -2; +} + +.lines { + min-width: 100%; +} + +.cursor { + z-index: 4; + pointer-events: none; + box-sizing: border-box; +} + +.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; + + .scrollbar-content { + height: 15px; + } +} + +.vertical-scrollbar { + overflow-x: hidden; +} + +.scrollbar-corner { + position: absolute; + overflow: auto; + bottom: 0; + right: 0; +} + +.scroll-view { + overflow: hidden; + z-index: 0; +} + +.scroll-view-content { + position: relative; + width: 100%; +} + +.gutter { + .line-number { + white-space: nowrap; + padding-left: .5em; + + .icon-right { + padding: 0 .4em; + &:before { + text-align: center; + } + } + } +} + +.placeholder-text { + position: absolute; + color: @text-color-subtle; +} + +.editor { + z-index: 0; +} + +.editor, .editor-contents { + overflow: hidden; + cursor: text; + display: -webkit-flex; + -webkit-user-select: none; + position: relative; +} + +.gutter .line-number.cursor-line { + opacity: 1; +} + +.gutter { + overflow: hidden; + text-align: right; + cursor: default; + min-width: 1em; + box-sizing: border-box; +} + +.gutter .line-number { + padding-left: .5em; + opacity: 0.6; +} + +.gutter .line-numbers { + position: relative; +} + +.gutter .line-number.folded.cursor-line { + opacity: 1; +} + +.gutter .line-number .icon-right { + .octicon(chevron-down, 0.8em); + display: inline-block; + visibility: hidden; + padding-left: .1em; + padding-right: .5em; + opacity: .6; +} + +.gutter:hover .line-number.foldable .icon-right { + visibility: visible; + + &:before { + content: @chevron-down; + } + + &:hover { + opacity: 1; + } +} + +.gutter, .gutter:hover { + .line-number.folded .icon-right { + .octicon(chevron-right, 0.8em); + visibility: visible; + + &:before { // chevron-right renders too far right compared to chevron-down + position: relative; + left: -.1em; + content: @chevron-right; + } + } +} + +.fold-marker { + cursor: default; +} + +.fold-marker:after { + .icon(0.8em, inline); + content: @ellipsis; + padding-left: 0.2em; +} + +.line.cursor-line .fold-marker:after { + opacity: 1; +} + +atom-text-editor::shadow.is-blurred .line.cursor-line { + background: rgba(0, 0, 0, 0); +} + +.invisible-character { + font-weight: normal !important; + font-style: normal !important; +} + +.indent-guide { + display: inline-block; + box-shadow: inset 1px 0; +} + +.vertical-scrollbar, +.horizontal-scrollbar { + cursor: default; +} + +.vertical-scrollbar { + position: absolute; + top: 0; + right: 0; + bottom: 0; + + width: 15px; + overflow-y: auto; + z-index: 3; +} + +.scroll-view { + overflow-x: auto; + overflow-y: hidden; + -webkit-flex: 1; + min-width: 0; + position: relative; +} + +atom-text-editor::shadow.soft-wrap .scroll-view { + overflow-x: hidden; +} + +.underlayer { + z-index: 0; + position: absolute; + min-height: 100%; +} + +.lines { + position: relative; + z-index: 1; +} + +.overlayer { + z-index: 2; + position: absolute; +} + +.line { + white-space: pre; +} + +.line span { + vertical-align: top; +} + +.cursor { + position: absolute; + border-left: 1px solid; +} + +.cursor { + visibility: hidden; +} + +.is-focused .cursor { + visibility: visible; +} + +.is-focused .cursor.blink-off { + visibility: hidden; +} + +.cursor.hidden-cursor { + display: none; +} + +.hidden-input { + padding: 0; + border: 0; + position: absolute; + z-index: -1; + top: 0; + left: 0; + opacity: 0; + width: 1px; +} + +.highlight { + background: none; + padding: 0; +} + +.highlight .region, +.selection .region { + position: absolute; + pointer-events: none; + z-index: -1; +}