mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 15:37:46 +03:00
Merge master into jr-fix-observe-text-editor-methods
This commit is contained in:
commit
4fecbaf4f5
@ -31,7 +31,7 @@
|
||||
"event-kit": "^2.3.0",
|
||||
"find-parent-dir": "^0.3.0",
|
||||
"first-mate": "7.0.7",
|
||||
"fs-plus": "^3.0.0",
|
||||
"fs-plus": "^3.0.1",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^2.1",
|
||||
"git-utils": "5.0.0",
|
||||
@ -85,7 +85,7 @@
|
||||
"solarized-dark-syntax": "1.1.2",
|
||||
"solarized-light-syntax": "1.1.2",
|
||||
"about": "1.7.6",
|
||||
"archive-view": "0.63.2",
|
||||
"archive-view": "0.63.3",
|
||||
"autocomplete-atom-api": "0.10.1",
|
||||
"autocomplete-css": "0.16.2",
|
||||
"autocomplete-html": "0.8.0",
|
||||
@ -104,7 +104,7 @@
|
||||
"exception-reporting": "0.41.4",
|
||||
"find-and-replace": "0.208.3",
|
||||
"fuzzy-finder": "1.5.8",
|
||||
"github": "0.3.0",
|
||||
"github": "0.3.2",
|
||||
"git-diff": "1.3.6",
|
||||
"go-to-line": "0.32.1",
|
||||
"grammar-selector": "0.49.4",
|
||||
@ -121,7 +121,7 @@
|
||||
"settings-view": "0.250.0",
|
||||
"snippets": "1.1.4",
|
||||
"spell-check": "0.71.4",
|
||||
"status-bar": "1.8.8",
|
||||
"status-bar": "1.8.10",
|
||||
"styleguide": "0.49.6",
|
||||
"symbols-view": "0.116.0",
|
||||
"tabs": "0.106.0",
|
||||
|
@ -27,6 +27,11 @@ document.registerElement('text-editor-component-test-element', {
|
||||
describe('TextEditorComponent', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.useRealClock()
|
||||
|
||||
// Force scrollbars to be visible regardless of local system configuration
|
||||
const scrollbarStyle = document.createElement('style')
|
||||
scrollbarStyle.textContent = '::-webkit-scrollbar { -webkit-appearance: none }'
|
||||
jasmine.attachToDOM(scrollbarStyle)
|
||||
})
|
||||
|
||||
describe('rendering', () => {
|
||||
@ -591,11 +596,32 @@ describe('TextEditorComponent', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports the isLineNumberGutterVisible parameter', () => {
|
||||
it('does not render the line number gutter at all if the isLineNumberGutterVisible parameter is false', () => {
|
||||
const {component, element, editor} = buildComponent({lineNumberGutterVisible: false})
|
||||
expect(element.querySelector('.line-number')).toBe(null)
|
||||
})
|
||||
|
||||
it('does not render the line numbers but still renders the line number gutter if showLineNumbers is false', async () => {
|
||||
function checkScrollContainerLeft (component) {
|
||||
const {scrollContainer, gutterContainer} = component.refs
|
||||
expect(scrollContainer.getBoundingClientRect().left).toBe(Math.round(gutterContainer.element.getBoundingClientRect().right))
|
||||
}
|
||||
|
||||
const {component, element, editor} = buildComponent({showLineNumbers: false})
|
||||
expect(Array.from(element.querySelectorAll('.line-number')).every((e) => e.textContent === '')).toBe(true)
|
||||
checkScrollContainerLeft(component)
|
||||
|
||||
await editor.update({showLineNumbers: true})
|
||||
expect(Array.from(element.querySelectorAll('.line-number')).map((e) => e.textContent)).toEqual([
|
||||
'00', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13'
|
||||
])
|
||||
checkScrollContainerLeft(component)
|
||||
|
||||
await editor.update({showLineNumbers: false})
|
||||
expect(Array.from(element.querySelectorAll('.line-number')).every((e) => e.textContent === '')).toBe(true)
|
||||
checkScrollContainerLeft(component)
|
||||
})
|
||||
|
||||
it('supports the placeholderText parameter', () => {
|
||||
const placeholderText = 'Placeholder Test'
|
||||
const {element} = buildComponent({placeholderText, text: ''})
|
||||
@ -3396,7 +3422,7 @@ function buildEditor (params = {}) {
|
||||
const buffer = new TextBuffer({text})
|
||||
const editorParams = {buffer}
|
||||
if (params.height != null) params.autoHeight = false
|
||||
for (const paramName of ['mini', 'autoHeight', 'autoWidth', 'lineNumberGutterVisible', 'placeholderText', 'softWrapped']) {
|
||||
for (const paramName of ['mini', 'autoHeight', 'autoWidth', 'lineNumberGutterVisible', 'showLineNumbers', 'placeholderText', 'softWrapped']) {
|
||||
if (params[paramName] != null) editorParams[paramName] = params[paramName]
|
||||
}
|
||||
return new TextEditor(editorParams)
|
||||
|
@ -447,6 +447,7 @@ class Pane
|
||||
# Public: Make the given item *active*, causing it to be displayed by
|
||||
# the pane's view.
|
||||
#
|
||||
# * `item` The item to activate
|
||||
# * `options` (optional) {Object}
|
||||
# * `pending` (optional) {Boolean} indicating that the item should be added
|
||||
# in a pending state if it does not yet exist in the pane. Existing pending
|
||||
|
@ -86,6 +86,7 @@ class TextEditorComponent {
|
||||
(this.props.cursorBlinkResumeDelay || CURSOR_BLINK_RESUME_DELAY)
|
||||
)
|
||||
this.lineTopIndex = new LineTopIndex()
|
||||
this.lineNodesPool = new NodePool()
|
||||
this.updateScheduled = false
|
||||
this.suppressUpdates = false
|
||||
this.hasInitialMeasurements = false
|
||||
@ -116,6 +117,7 @@ class TextEditorComponent {
|
||||
this.lineNodesByScreenLineId = new Map()
|
||||
this.textNodesByScreenLineId = new Map()
|
||||
this.overlayComponents = new Set()
|
||||
this.overlayDimensionsByElement = new WeakMap()
|
||||
this.shouldRenderDummyScrollbars = true
|
||||
this.remeasureScrollbars = false
|
||||
this.pendingAutoscroll = null
|
||||
@ -134,6 +136,7 @@ class TextEditorComponent {
|
||||
this.idsByTileStartRow = new Map()
|
||||
this.nextTileId = 0
|
||||
this.renderedTileStartRows = []
|
||||
this.showLineNumbers = this.props.model.doesShowLineNumbers()
|
||||
this.lineNumbersToRender = {
|
||||
maxDigits: 2,
|
||||
bufferRows: [],
|
||||
@ -481,6 +484,7 @@ class TextEditorComponent {
|
||||
guttersToRender: this.guttersToRender,
|
||||
decorationsToRender: this.decorationsToRender,
|
||||
isLineNumberGutterVisible: this.props.model.isLineNumberGutterVisible(),
|
||||
showLineNumbers: this.showLineNumbers,
|
||||
lineNumbersToRender: this.lineNumbersToRender,
|
||||
didMeasureVisibleBlockDecoration: this.didMeasureVisibleBlockDecoration
|
||||
})
|
||||
@ -582,6 +586,7 @@ class TextEditorComponent {
|
||||
blockDecorations: this.decorationsToRender.blocks.get(tileStartRow),
|
||||
highlightDecorations: this.decorationsToRender.highlights.get(tileStartRow),
|
||||
displayLayer,
|
||||
nodePool: this.lineNodesPool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
})
|
||||
@ -595,6 +600,7 @@ class TextEditorComponent {
|
||||
screenLine,
|
||||
screenRow,
|
||||
displayLayer,
|
||||
nodePool: this.lineNodesPool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
}))
|
||||
@ -754,6 +760,7 @@ class TextEditorComponent {
|
||||
{
|
||||
key: overlayProps.element,
|
||||
overlayComponents: this.overlayComponents,
|
||||
measuredDimensions: this.overlayDimensionsByElement.get(overlayProps.element),
|
||||
didResize: () => { this.updateSync() }
|
||||
},
|
||||
overlayProps
|
||||
@ -816,6 +823,10 @@ class TextEditorComponent {
|
||||
queryLineNumbersToRender () {
|
||||
const {model} = this.props
|
||||
if (!model.isLineNumberGutterVisible()) return
|
||||
if (this.showLineNumbers !== model.doesShowLineNumbers()) {
|
||||
this.remeasureGutterDimensions = true
|
||||
this.showLineNumbers = model.doesShowLineNumbers()
|
||||
}
|
||||
|
||||
this.queryMaxLineNumberDigits()
|
||||
|
||||
@ -1300,15 +1311,16 @@ class TextEditorComponent {
|
||||
const {row, column} = screenPosition
|
||||
let wrapperTop = contentClientRect.top + this.pixelPositionAfterBlocksForRow(row) + this.getLineHeight()
|
||||
let wrapperLeft = contentClientRect.left + this.pixelLeftForRowAndColumn(row, column)
|
||||
const clientRect = element.getBoundingClientRect()
|
||||
this.overlayDimensionsByElement.set(element, clientRect)
|
||||
|
||||
if (avoidOverflow !== false) {
|
||||
const computedStyle = window.getComputedStyle(element)
|
||||
const elementHeight = element.offsetHeight
|
||||
const elementTop = wrapperTop + parseInt(computedStyle.marginTop)
|
||||
const elementBottom = elementTop + elementHeight
|
||||
const flippedElementTop = wrapperTop - this.getLineHeight() - elementHeight - parseInt(computedStyle.marginBottom)
|
||||
const elementBottom = elementTop + clientRect.height
|
||||
const flippedElementTop = wrapperTop - this.getLineHeight() - clientRect.height - parseInt(computedStyle.marginBottom)
|
||||
const elementLeft = wrapperLeft + parseInt(computedStyle.marginLeft)
|
||||
const elementRight = elementLeft + element.offsetWidth
|
||||
const elementRight = elementLeft + clientRect.width
|
||||
|
||||
if (elementBottom > windowInnerHeight && flippedElementTop >= 0) {
|
||||
wrapperTop -= (elementTop - flippedElementTop)
|
||||
@ -1320,8 +1332,8 @@ class TextEditorComponent {
|
||||
}
|
||||
}
|
||||
|
||||
decoration.pixelTop = wrapperTop
|
||||
decoration.pixelLeft = wrapperLeft
|
||||
decoration.pixelTop = Math.round(wrapperTop)
|
||||
decoration.pixelLeft = Math.round(wrapperLeft)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1410,24 +1422,8 @@ class TextEditorComponent {
|
||||
this.scheduleUpdate()
|
||||
}
|
||||
|
||||
// Transfer focus to the hidden input, but first ensure the input is in the
|
||||
// visible part of the scrolled content to avoid the browser trying to
|
||||
// auto-scroll to the form-field.
|
||||
const {hiddenInput} = this.refs.cursorsAndInput.refs
|
||||
hiddenInput.style.top = this.getScrollTop() + 'px'
|
||||
hiddenInput.style.left = this.getScrollLeft() + 'px'
|
||||
|
||||
hiddenInput.focus()
|
||||
|
||||
// Restore the previous position of the field now that it is already focused
|
||||
// and won't cause unwanted scrolling.
|
||||
if (this.hiddenInputPosition) {
|
||||
hiddenInput.style.top = this.hiddenInputPosition.pixelTop + 'px'
|
||||
hiddenInput.style.left = this.hiddenInputPosition.pixelLeft + 'px'
|
||||
} else {
|
||||
hiddenInput.style.top = 0
|
||||
hiddenInput.style.left = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Called by TextEditorElement so that this function is always the first
|
||||
@ -1450,6 +1446,12 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
didFocusHiddenInput () {
|
||||
// Focusing the hidden input when it is off-screen causes the browser to
|
||||
// scroll it into view. Since we use synthetic scrolling this behavior
|
||||
// causes all the lines to disappear so we counteract it by always setting
|
||||
// the scroll position to 0.
|
||||
this.refs.scrollContainer.scrollTop = 0
|
||||
this.refs.scrollContainer.scrollLeft = 0
|
||||
if (!this.focused) {
|
||||
this.focused = true
|
||||
this.startCursorBlinking()
|
||||
@ -2899,7 +2901,7 @@ class GutterContainerComponent {
|
||||
|
||||
renderLineNumberGutter (gutter) {
|
||||
const {
|
||||
rootComponent, isLineNumberGutterVisible, hasInitialMeasurements, lineNumbersToRender,
|
||||
rootComponent, isLineNumberGutterVisible, showLineNumbers, hasInitialMeasurements, lineNumbersToRender,
|
||||
renderedStartRow, renderedEndRow, rowsPerTile, decorationsToRender, didMeasureVisibleBlockDecoration,
|
||||
scrollHeight, lineNumberGutterWidth, lineHeight
|
||||
} = this.props
|
||||
@ -2925,13 +2927,15 @@ class GutterContainerComponent {
|
||||
didMeasureVisibleBlockDecoration: didMeasureVisibleBlockDecoration,
|
||||
height: scrollHeight,
|
||||
width: lineNumberGutterWidth,
|
||||
lineHeight: lineHeight
|
||||
lineHeight: lineHeight,
|
||||
showLineNumbers
|
||||
})
|
||||
} else {
|
||||
return $(LineNumberGutterComponent, {
|
||||
ref: 'lineNumberGutter',
|
||||
element: gutter.getElement(),
|
||||
maxDigits: lineNumbersToRender.maxDigits
|
||||
maxDigits: lineNumbersToRender.maxDigits,
|
||||
showLineNumbers
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -2943,6 +2947,7 @@ class LineNumberGutterComponent {
|
||||
this.element = this.props.element
|
||||
this.virtualNode = $.div(null)
|
||||
this.virtualNode.domNode = this.element
|
||||
this.nodePool = new NodePool()
|
||||
etch.updateSync(this)
|
||||
}
|
||||
|
||||
@ -2955,7 +2960,7 @@ class LineNumberGutterComponent {
|
||||
|
||||
render () {
|
||||
const {
|
||||
rootComponent, height, width, lineHeight, startRow, endRow, rowsPerTile,
|
||||
rootComponent, showLineNumbers, height, width, lineHeight, startRow, endRow, rowsPerTile,
|
||||
maxDigits, keys, bufferRows, softWrappedFlags, foldableFlags, decorations
|
||||
} = this.props
|
||||
|
||||
@ -2980,25 +2985,27 @@ class LineNumberGutterComponent {
|
||||
const decorationsForRow = decorations[row - startRow]
|
||||
if (decorationsForRow) className = className + ' ' + decorationsForRow
|
||||
|
||||
let number = softWrapped ? '•' : bufferRow + 1
|
||||
number = NBSP_CHARACTER.repeat(maxDigits - number.length) + number
|
||||
let number = null
|
||||
if (showLineNumbers) {
|
||||
number = softWrapped ? '•' : bufferRow + 1
|
||||
number = NBSP_CHARACTER.repeat(maxDigits - number.length) + number
|
||||
}
|
||||
|
||||
const lineNumberProps = {
|
||||
key,
|
||||
className,
|
||||
style: {width: width + 'px'},
|
||||
dataset: {bufferRow}
|
||||
width,
|
||||
bufferRow,
|
||||
number,
|
||||
nodePool: this.nodePool
|
||||
}
|
||||
const currentRowTop = rootComponent.pixelPositionAfterBlocksForRow(row)
|
||||
const previousRowBottom = rootComponent.pixelPositionAfterBlocksForRow(row - 1) + lineHeight
|
||||
if (currentRowTop > previousRowBottom) {
|
||||
lineNumberProps.style.marginTop = (currentRowTop - previousRowBottom) + 'px'
|
||||
lineNumberProps.marginTop = currentRowTop - previousRowBottom
|
||||
}
|
||||
|
||||
tileChildren[row - tileStartRow] = $.div(lineNumberProps,
|
||||
number,
|
||||
$.div({className: 'icon-right'})
|
||||
)
|
||||
tileChildren[row - tileStartRow] = $(LineNumberComponent, lineNumberProps)
|
||||
}
|
||||
|
||||
const tileTop = rootComponent.pixelPositionBeforeBlocksForRow(tileStartRow)
|
||||
@ -3024,7 +3031,7 @@ class LineNumberGutterComponent {
|
||||
|
||||
return $.div(
|
||||
{
|
||||
className: 'gutter line-bufferRows',
|
||||
className: 'gutter line-numbers',
|
||||
attributes: {'gutter-name': 'line-number'},
|
||||
style: {position: 'relative', height: height + 'px'},
|
||||
on: {
|
||||
@ -3032,7 +3039,7 @@ class LineNumberGutterComponent {
|
||||
}
|
||||
},
|
||||
$.div({key: 'placeholder', className: 'line-number dummy', style: {visibility: 'hidden'}},
|
||||
'0'.repeat(maxDigits),
|
||||
showLineNumbers ? '0'.repeat(maxDigits) : null,
|
||||
$.div({className: 'icon-right'})
|
||||
),
|
||||
children
|
||||
@ -3042,6 +3049,7 @@ class LineNumberGutterComponent {
|
||||
shouldUpdate (newProps) {
|
||||
const oldProps = this.props
|
||||
|
||||
if (oldProps.showLineNumbers !== newProps.showLineNumbers) return true
|
||||
if (oldProps.height !== newProps.height) return true
|
||||
if (oldProps.width !== newProps.width) return true
|
||||
if (oldProps.lineHeight !== newProps.lineHeight) return true
|
||||
@ -3099,6 +3107,49 @@ class LineNumberGutterComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class LineNumberComponent {
|
||||
constructor (props) {
|
||||
const {className, width, marginTop, bufferRow, number, nodePool} = props
|
||||
this.props = props
|
||||
const style = {width: width + 'px'}
|
||||
if (marginTop != null) style.marginTop = marginTop + 'px'
|
||||
this.element = nodePool.getElement('DIV', className, style)
|
||||
this.element.dataset.bufferRow = bufferRow
|
||||
if (number) this.element.appendChild(nodePool.getTextNode(number))
|
||||
this.element.appendChild(nodePool.getElement('DIV', 'icon-right', null))
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.element.remove()
|
||||
this.props.nodePool.release(this.element)
|
||||
}
|
||||
|
||||
update (props) {
|
||||
const {nodePool, className, width, marginTop, number} = props
|
||||
|
||||
if (this.props.className !== className) this.element.className = className
|
||||
if (this.props.width !== width) this.element.style.width = width + 'px'
|
||||
if (this.props.marginTop !== marginTop) {
|
||||
if (marginTop != null) {
|
||||
this.element.style.marginTop = marginTop + 'px'
|
||||
} else {
|
||||
this.element.style.marginTop = ''
|
||||
}
|
||||
}
|
||||
if (this.props.number !== number) {
|
||||
if (number) {
|
||||
this.element.insertBefore(nodePool.getTextNode(number), this.element.firstChild)
|
||||
} else {
|
||||
const numberNode = this.element.firstChild
|
||||
numberNode.remove()
|
||||
nodePool.release(numberNode)
|
||||
}
|
||||
}
|
||||
|
||||
this.props = props
|
||||
}
|
||||
}
|
||||
|
||||
class CustomGutterComponent {
|
||||
constructor (props) {
|
||||
this.props = props
|
||||
@ -3362,7 +3413,7 @@ class LinesTileComponent {
|
||||
createLines () {
|
||||
const {
|
||||
tileStartRow, screenLines, lineDecorations, textDecorations,
|
||||
displayLayer, lineNodesByScreenLineId, textNodesByScreenLineId
|
||||
nodePool, displayLayer, lineNodesByScreenLineId, textNodesByScreenLineId
|
||||
} = this.props
|
||||
|
||||
this.lineComponents = []
|
||||
@ -3373,6 +3424,7 @@ class LinesTileComponent {
|
||||
lineDecoration: lineDecorations[i],
|
||||
textDecorations: textDecorations[i],
|
||||
displayLayer,
|
||||
nodePool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
})
|
||||
@ -3384,7 +3436,7 @@ class LinesTileComponent {
|
||||
updateLines (oldProps, newProps) {
|
||||
var {
|
||||
screenLines, tileStartRow, lineDecorations, textDecorations,
|
||||
displayLayer, lineNodesByScreenLineId, textNodesByScreenLineId
|
||||
nodePool, displayLayer, lineNodesByScreenLineId, textNodesByScreenLineId
|
||||
} = newProps
|
||||
|
||||
var oldScreenLines = oldProps.screenLines
|
||||
@ -3406,6 +3458,7 @@ class LinesTileComponent {
|
||||
lineDecoration: lineDecorations[newScreenLineIndex],
|
||||
textDecorations: textDecorations[newScreenLineIndex],
|
||||
displayLayer,
|
||||
nodePool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
})
|
||||
@ -3442,6 +3495,7 @@ class LinesTileComponent {
|
||||
lineDecoration: lineDecorations[newScreenLineIndex],
|
||||
textDecorations: textDecorations[newScreenLineIndex],
|
||||
displayLayer,
|
||||
nodePool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
})
|
||||
@ -3468,6 +3522,7 @@ class LinesTileComponent {
|
||||
lineDecoration: lineDecorations[newScreenLineIndex],
|
||||
textDecorations: textDecorations[newScreenLineIndex],
|
||||
displayLayer,
|
||||
nodePool,
|
||||
lineNodesByScreenLineId,
|
||||
textNodesByScreenLineId
|
||||
})
|
||||
@ -3611,10 +3666,9 @@ class LinesTileComponent {
|
||||
|
||||
class LineComponent {
|
||||
constructor (props) {
|
||||
const {screenRow, screenLine, lineNodesByScreenLineId} = props
|
||||
const {nodePool, screenRow, screenLine, lineNodesByScreenLineId} = props
|
||||
this.props = props
|
||||
this.element = document.createElement('div')
|
||||
this.element.className = this.buildClassName()
|
||||
this.element = nodePool.getElement('DIV', this.buildClassName(), null)
|
||||
this.element.dataset.screenRow = screenRow
|
||||
lineNodesByScreenLineId.set(screenLine.id, this.element)
|
||||
this.appendContents()
|
||||
@ -3639,23 +3693,24 @@ class LineComponent {
|
||||
}
|
||||
|
||||
destroy () {
|
||||
const {lineNodesByScreenLineId, textNodesByScreenLineId, screenLine} = this.props
|
||||
const {nodePool, lineNodesByScreenLineId, textNodesByScreenLineId, screenLine} = this.props
|
||||
if (lineNodesByScreenLineId.get(screenLine.id) === this.element) {
|
||||
lineNodesByScreenLineId.delete(screenLine.id)
|
||||
textNodesByScreenLineId.delete(screenLine.id)
|
||||
}
|
||||
|
||||
this.element.remove()
|
||||
nodePool.release(this.element)
|
||||
}
|
||||
|
||||
appendContents () {
|
||||
const {displayLayer, screenLine, textDecorations, textNodesByScreenLineId} = this.props
|
||||
const {displayLayer, nodePool, screenLine, textDecorations, textNodesByScreenLineId} = this.props
|
||||
|
||||
const textNodes = []
|
||||
textNodesByScreenLineId.set(screenLine.id, textNodes)
|
||||
|
||||
const {lineText, tags} = screenLine
|
||||
let openScopeNode = document.createElement('span')
|
||||
let openScopeNode = nodePool.getElement('SPAN', null, null)
|
||||
this.element.appendChild(openScopeNode)
|
||||
|
||||
let decorationIndex = 0
|
||||
@ -3676,8 +3731,7 @@ class LineComponent {
|
||||
if (displayLayer.isCloseTag(tag)) {
|
||||
openScopeNode = openScopeNode.parentElement
|
||||
} else if (displayLayer.isOpenTag(tag)) {
|
||||
const newScopeNode = document.createElement('span')
|
||||
newScopeNode.className = displayLayer.classNameForTag(tag)
|
||||
const newScopeNode = nodePool.getElement('SPAN', displayLayer.classNameForTag(tag), null)
|
||||
openScopeNode.appendChild(newScopeNode)
|
||||
openScopeNode = newScopeNode
|
||||
} else {
|
||||
@ -3701,7 +3755,7 @@ class LineComponent {
|
||||
}
|
||||
|
||||
if (column === 0) {
|
||||
const textNode = document.createTextNode(' ')
|
||||
const textNode = nodePool.getTextNode(' ')
|
||||
this.element.appendChild(textNode)
|
||||
textNodes.push(textNode)
|
||||
}
|
||||
@ -3710,22 +3764,22 @@ class LineComponent {
|
||||
// Insert a zero-width non-breaking whitespace, so that LinesYardstick can
|
||||
// take the fold-marker::after pseudo-element into account during
|
||||
// measurements when such marker is the last character on the line.
|
||||
const textNode = document.createTextNode(ZERO_WIDTH_NBSP_CHARACTER)
|
||||
const textNode = nodePool.getTextNode(ZERO_WIDTH_NBSP_CHARACTER)
|
||||
this.element.appendChild(textNode)
|
||||
textNodes.push(textNode)
|
||||
}
|
||||
}
|
||||
|
||||
appendTextNode (textNodes, openScopeNode, text, activeClassName, activeStyle) {
|
||||
const {nodePool} = this.props
|
||||
|
||||
if (activeClassName || activeStyle) {
|
||||
const decorationNode = document.createElement('span')
|
||||
if (activeClassName) decorationNode.className = activeClassName
|
||||
if (activeStyle) Object.assign(decorationNode.style, activeStyle)
|
||||
const decorationNode = nodePool.getElement('SPAN', activeClassName, activeStyle)
|
||||
openScopeNode.appendChild(decorationNode)
|
||||
openScopeNode = decorationNode
|
||||
}
|
||||
|
||||
const textNode = document.createTextNode(text)
|
||||
const textNode = nodePool.getTextNode(text)
|
||||
openScopeNode.appendChild(textNode)
|
||||
textNodes.push(textNode)
|
||||
}
|
||||
@ -3855,10 +3909,13 @@ class OverlayComponent {
|
||||
// Synchronous DOM updates in response to resize events might trigger a
|
||||
// "loop limit exceeded" error. We disconnect the observer before
|
||||
// potentially mutating the DOM, and then reconnect it on the next tick.
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this.resizeObserver.disconnect()
|
||||
this.props.didResize()
|
||||
process.nextTick(() => { this.resizeObserver.observe(this.element) })
|
||||
this.resizeObserver = new ResizeObserver((entries) => {
|
||||
const {contentRect} = entries[0]
|
||||
if (contentRect.width !== this.props.measuredDimensions.width || contentRect.height !== this.props.measuredDimensions.height) {
|
||||
this.resizeObserver.disconnect()
|
||||
this.props.didResize()
|
||||
process.nextTick(() => { this.resizeObserver.observe(this.element) })
|
||||
}
|
||||
})
|
||||
this.didAttach()
|
||||
this.props.overlayComponents.add(this)
|
||||
@ -3966,3 +4023,83 @@ function debounce (fn, wait) {
|
||||
if (!timeout) timeout = setTimeout(later, wait)
|
||||
}
|
||||
}
|
||||
|
||||
class NodePool {
|
||||
constructor () {
|
||||
this.elementsByType = {}
|
||||
this.textNodes = []
|
||||
this.stylesByNode = new WeakMap()
|
||||
}
|
||||
|
||||
getElement (type, className, style) {
|
||||
var element
|
||||
var elementsByDepth = this.elementsByType[type]
|
||||
if (elementsByDepth) {
|
||||
while (elementsByDepth.length > 0) {
|
||||
var elements = elementsByDepth[elementsByDepth.length - 1]
|
||||
if (elements && elements.length > 0) {
|
||||
element = elements.pop()
|
||||
if (elements.length === 0) elementsByDepth.pop()
|
||||
break
|
||||
} else {
|
||||
elementsByDepth.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (element) {
|
||||
element.className = className
|
||||
var existingStyle = this.stylesByNode.get(element)
|
||||
if (existingStyle) {
|
||||
for (var key in existingStyle) {
|
||||
if (!style || !style[key]) element.style[key] = ''
|
||||
}
|
||||
}
|
||||
if (style) Object.assign(element.style, style)
|
||||
this.stylesByNode.set(element, style)
|
||||
|
||||
while (element.firstChild) element.firstChild.remove()
|
||||
return element
|
||||
} else {
|
||||
var newElement = document.createElement(type)
|
||||
if (className) newElement.className = className
|
||||
if (style) Object.assign(newElement.style, style)
|
||||
this.stylesByNode.set(newElement, style)
|
||||
return newElement
|
||||
}
|
||||
}
|
||||
|
||||
getTextNode (text) {
|
||||
if (this.textNodes.length > 0) {
|
||||
var node = this.textNodes.pop()
|
||||
node.textContent = text
|
||||
return node
|
||||
} else {
|
||||
return document.createTextNode(text)
|
||||
}
|
||||
}
|
||||
|
||||
release (node, depth = 0) {
|
||||
var {nodeName} = node
|
||||
if (nodeName === '#text') {
|
||||
this.textNodes.push(node)
|
||||
} else {
|
||||
var elementsByDepth = this.elementsByType[nodeName]
|
||||
if (!elementsByDepth) {
|
||||
elementsByDepth = []
|
||||
this.elementsByType[nodeName] = elementsByDepth
|
||||
}
|
||||
|
||||
var elements = elementsByDepth[depth]
|
||||
if (!elements) {
|
||||
elements = []
|
||||
elementsByDepth[depth] = elements
|
||||
}
|
||||
|
||||
elements.push(node)
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
this.release(node.childNodes[i], depth + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,6 @@ class TextEditor extends Model
|
||||
registered: false
|
||||
atomicSoftTabs: true
|
||||
invisibles: null
|
||||
showLineNumbers: true
|
||||
scrollSensitivity: 40
|
||||
|
||||
Object.defineProperty @prototype, "element",
|
||||
@ -156,7 +155,7 @@ class TextEditor extends Model
|
||||
{
|
||||
@softTabs, @initialScrollTopRow, @initialScrollLeftColumn, initialLine, initialColumn, tabLength,
|
||||
@softWrapped, @decorationManager, @selectionsMarkerLayer, @buffer, suppressCursorCreation,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, @largeFileMode,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, @showLineNumbers, @largeFileMode,
|
||||
@assert, grammar, @showInvisibles, @autoHeight, @autoWidth, @scrollPastEnd, @editorWidthInChars,
|
||||
@tokenizedBuffer, @displayLayer, @invisibles, @showIndentGuide,
|
||||
@softWrapped, @softWrapAtPreferredLineLength, @preferredLineLength,
|
||||
@ -184,6 +183,7 @@ class TextEditor extends Model
|
||||
@softWrapped ?= false
|
||||
@softWrapAtPreferredLineLength ?= false
|
||||
@preferredLineLength ?= 80
|
||||
@showLineNumbers ?= true
|
||||
|
||||
@buffer ?= new TextBuffer({shouldDestroyOnFileDelete: ->
|
||||
atom.config.get('core.closeDeletedFileTabs')})
|
||||
@ -357,6 +357,7 @@ class TextEditor extends Model
|
||||
when 'showLineNumbers'
|
||||
if value isnt @showLineNumbers
|
||||
@showLineNumbers = value
|
||||
@component?.scheduleUpdate()
|
||||
|
||||
when 'showInvisibles'
|
||||
if value isnt @showInvisibles
|
||||
|
BIN
static/atomicons.woff
Executable file
BIN
static/atomicons.woff
Executable file
Binary file not shown.
@ -11,6 +11,31 @@
|
||||
// Find: @([a-z-]+): "[^"]+";
|
||||
// Replace: .make-icon(\1);
|
||||
|
||||
|
||||
// Atomicons (private)
|
||||
|
||||
.make-icon(type-array);
|
||||
.make-icon(type-boolean);
|
||||
.make-icon(type-class);
|
||||
.make-icon(type-constant);
|
||||
.make-icon(type-constructor);
|
||||
.make-icon(type-enum);
|
||||
.make-icon(type-field);
|
||||
.make-icon(type-file);
|
||||
.make-icon(type-function);
|
||||
.make-icon(type-interface);
|
||||
.make-icon(type-method);
|
||||
.make-icon(type-module);
|
||||
.make-icon(type-namespace);
|
||||
.make-icon(type-number);
|
||||
.make-icon(type-package);
|
||||
.make-icon(type-property);
|
||||
.make-icon(type-string);
|
||||
.make-icon(type-variable);
|
||||
|
||||
|
||||
// Octicons
|
||||
|
||||
.make-icon(alert);
|
||||
.make-icon(alignment-align);
|
||||
.make-icon(alignment-aligned-to);
|
||||
|
@ -2,11 +2,12 @@
|
||||
@import "octicon-mixins";
|
||||
|
||||
//
|
||||
// Octicon font
|
||||
// Icon fonts
|
||||
// --------------------------------------------------
|
||||
|
||||
@font-face { .octicon-font-legacy(); } // keep for backwards compatibility
|
||||
@font-face { .octicon-font(); }
|
||||
@font-face { .atomicon-font(); } // Private
|
||||
|
||||
|
||||
//
|
||||
|
@ -46,3 +46,10 @@
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.atomicon-font() {
|
||||
font-family: 'Octicons Regular';
|
||||
src: url('atomicons.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
@ -1,3 +1,28 @@
|
||||
|
||||
// Atomicons (private)
|
||||
|
||||
@type-array: "\e900";
|
||||
@type-boolean: "\e901";
|
||||
@type-class: "\e902";
|
||||
@type-constant: "\e903";
|
||||
@type-constructor: "\e904";
|
||||
@type-enum: "\e905";
|
||||
@type-field: "\e906";
|
||||
@type-file: "\e907";
|
||||
@type-function: "\e908";
|
||||
@type-interface: "\e909";
|
||||
@type-method: "\e90a";
|
||||
@type-module: "\e90b";
|
||||
@type-namespace: "\e90c";
|
||||
@type-number: "\e90d";
|
||||
@type-package: "\e90e";
|
||||
@type-property: "\e90f";
|
||||
@type-string: "\e910";
|
||||
@type-variable: "\e911";
|
||||
|
||||
|
||||
// Octicons
|
||||
|
||||
@alert: "\f02d";
|
||||
@alignment-align: "\f08a";
|
||||
@alignment-aligned-to: "\f08e";
|
||||
|
Loading…
Reference in New Issue
Block a user