Merge pull request #3943 from atom/ns-text-editor-shadow-dom

Render text editor contents inside shadow DOM
This commit is contained in:
Nathan Sobo 2014-11-05 09:42:07 -07:00
commit 2c83c3fe17
31 changed files with 667 additions and 474 deletions

View File

@ -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'

View File

@ -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",

View File

@ -0,0 +1 @@
a { color: red }

View File

@ -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", ->

View File

@ -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", ->

View File

@ -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()

View File

@ -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)

View File

@ -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;}"

View File

@ -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')

View File

@ -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

View File

@ -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'])

View File

@ -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

View File

@ -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.

View File

@ -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')

View File

@ -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()

View File

@ -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',

View File

@ -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?

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -25,6 +25,7 @@ class StyleManager
addStyleSheet: (source, params) ->
sourcePath = params?.sourcePath
context = params?.context
group = params?.group
if sourcePath? and styleElement = @styleElementsBySourcePath[sourcePath]

View File

@ -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

View File

@ -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'

View File

@ -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, ' ')

View File

@ -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 = $("<div class='underlayer'></div>").appendTo(this)
@overlayer = $("<div class='overlayer'></div>").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()

View File

@ -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}\"]")

View File

@ -21,7 +21,7 @@
@import "popover-list";
@import "messages";
@import "markdown";
@import "editor";
@import "text-editor-light";
@import "select-list";
@import "syntax";
@import "utilities";

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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;
}