Files
atom/src/text-editor-element.js
2017-05-05 09:30:08 +02:00

347 lines
9.4 KiB
JavaScript

const {Emitter, Range} = require('atom')
const Grim = require('grim')
const TextEditorComponent = require('./text-editor-component')
const dedent = require('dedent')
class TextEditorElement extends HTMLElement {
initialize (component) {
this.component = component
return this
}
get shadowRoot () {
Grim.deprecate(dedent`
The contents of \`atom-text-editor\` elements are no longer encapsulated
within a shadow DOM boundary. Please, stop using \`shadowRoot\` and access
the editor contents directly instead.
`)
return this
}
get rootElement () {
Grim.deprecate(dedent`
The contents of \`atom-text-editor\` elements are no longer encapsulated
within a shadow DOM boundary. Please, stop using \`rootElement\` and access
the editor contents directly instead.
`)
return this
}
createdCallback () {
this.emitter = new Emitter()
this.initialText = this.textContent
this.tabIndex = -1
this.addEventListener('focus', (event) => this.getComponent().didFocus(event))
this.addEventListener('blur', (event) => this.getComponent().didBlur(event))
}
attachedCallback () {
this.getComponent().didAttach()
this.emitter.emit('did-attach')
}
detachedCallback () {
this.emitter.emit('did-detach')
this.getComponent().didDetach()
}
attributeChangedCallback (name, oldValue, newValue) {
if (this.component) {
switch (name) {
case 'mini':
this.getModel().update({mini: newValue != null})
break
case 'placeholder-text':
this.getModel().update({placeholderText: newValue})
break
case 'gutter-hidden':
this.getModel().update({lineNumberGutterVisible: newValue == null})
break
}
}
}
// Extended: Get a promise that resolves the next time the element's DOM
// is updated in any way.
//
// This can be useful when you've made a change to the model and need to
// be sure this change has been flushed to the DOM.
//
// Returns a {Promise}.
getNextUpdatePromise () {
return this.getComponent().getNextUpdatePromise()
}
getModel () {
return this.getComponent().props.model
}
setModel (model) {
this.getComponent().update({model})
this.updateModelFromAttributes()
}
updateModelFromAttributes () {
const props = {mini: this.hasAttribute('mini')}
if (this.hasAttribute('placeholder-text')) props.placeholderText = this.getAttribute('placeholder-text')
if (this.hasAttribute('gutter-hidden')) props.lineNumberGutterVisible = false
this.getModel().update(props)
if (this.initialText) this.getModel().setText(this.initialText)
}
onDidAttach (callback) {
return this.emitter.on('did-attach', callback)
}
onDidDetach (callback) {
return this.emitter.on('did-detach', callback)
}
measureDimensions () {
this.getComponent().measureDimensions()
}
setWidth (width) {
this.style.width = this.getComponent().getGutterContainerWidth() + width + 'px'
}
getWidth () {
return this.getComponent().getScrollContainerWidth()
}
setHeight (height) {
this.style.height = height + 'px'
}
getHeight () {
return this.getComponent().getScrollContainerHeight()
}
onDidChangeScrollLeft (callback) {
return this.emitter.on('did-change-scroll-left', callback)
}
onDidChangeScrollTop (callback) {
return this.emitter.on('did-change-scroll-top', callback)
}
// Deprecated: get the width of an `x` character displayed in this element.
//
// Returns a {Number} of pixels.
getDefaultCharacterWidth () {
return this.getComponent().getBaseCharacterWidth()
}
// Extended: get the width of an `x` character displayed in this element.
//
// Returns a {Number} of pixels.
getBaseCharacterWidth () {
return this.getComponent().getBaseCharacterWidth()
}
getMaxScrollTop () {
return this.getComponent().getMaxScrollTop()
}
getScrollHeight () {
return this.getComponent().getScrollHeight()
}
getScrollWidth () {
return this.getComponent().getScrollWidth()
}
getVerticalScrollbarWidth () {
return this.getComponent().getVerticalScrollbarWidth()
}
getHorizontalScrollbarHeight () {
return this.getComponent().getHorizontalScrollbarHeight()
}
getScrollTop () {
return this.getComponent().getScrollTop()
}
setScrollTop (scrollTop) {
const component = this.getComponent()
component.setScrollTop(scrollTop)
component.scheduleUpdate()
}
getScrollBottom () {
return this.getComponent().getScrollBottom()
}
setScrollBottom (scrollBottom) {
return this.getComponent().setScrollBottom(scrollBottom)
}
getScrollLeft () {
return this.getComponent().getScrollLeft()
}
setScrollLeft (scrollLeft) {
const component = this.getComponent()
component.setScrollLeft(scrollLeft)
component.scheduleUpdate()
}
getScrollRight () {
return this.getComponent().getScrollRight()
}
setScrollRight (scrollRight) {
return this.getComponent().setScrollRight(scrollRight)
}
// Essential: Scrolls the editor to the top.
scrollToTop () {
this.setScrollTop(0)
}
// Essential: Scrolls the editor to the bottom.
scrollToBottom () {
this.setScrollTop(Infinity)
}
hasFocus () {
return this.getComponent().focused
}
// Extended: Converts a buffer position to a pixel position.
//
// * `bufferPosition` A {Point}-like object that represents a buffer position.
//
// Be aware that calling this method with a column that does not translate
// to column 0 on screen could cause a synchronous DOM update in order to
// measure the requested horizontal pixel position if it isn't already
// cached.
//
// Returns an {Object} with two values: `top` and `left`, representing the
// pixel position.
pixelPositionForBufferPosition (bufferPosition) {
const screenPosition = this.getModel().screenPositionForBufferPosition(bufferPosition)
return this.getComponent().pixelPositionForScreenPosition(screenPosition)
}
// Extended: Converts a screen position to a pixel position.
//
// * `screenPosition` A {Point}-like object that represents a buffer position.
//
// Be aware that calling this method with a non-zero column value could
// cause a synchronous DOM update in order to measure the requested
// horizontal pixel position if it isn't already cached.
//
// Returns an {Object} with two values: `top` and `left`, representing the
// pixel position.
pixelPositionForScreenPosition (screenPosition) {
screenPosition = this.getModel().clipScreenPosition(screenPosition)
return this.getComponent().pixelPositionForScreenPosition(screenPosition)
}
screenPositionForPixelPosition (pixelPosition) {
return this.getComponent().screenPositionForPixelPosition(pixelPosition)
}
pixelRectForScreenRange (range) {
range = Range.fromObject(range)
const start = this.pixelPositionForScreenPosition(range.start)
const end = this.pixelPositionForScreenPosition(range.end)
const lineHeight = this.getComponent().getLineHeight()
return {
top: start.top,
left: start.left,
height: end.top + lineHeight - start.top,
width: end.left - start.left
}
}
pixelRangeForScreenRange (range) {
range = Range.fromObject(range)
return {
start: this.pixelPositionForScreenPosition(range.start),
end: this.pixelPositionForScreenPosition(range.end)
}
}
getComponent () {
if (!this.component) {
this.component = new TextEditorComponent({
element: this,
mini: this.hasAttribute('mini'),
updatedSynchronously: this.updatedSynchronously
})
this.updateModelFromAttributes()
}
return this.component
}
setUpdatedSynchronously (updatedSynchronously) {
this.updatedSynchronously = updatedSynchronously
if (this.component) this.component.updatedSynchronously = updatedSynchronously
return updatedSynchronously
}
isUpdatedSynchronously () {
return this.component ? this.component.updatedSynchronously : this.updatedSynchronously
}
// Experimental: Invalidate the passed block {Decoration}'s dimensions,
// forcing them to be recalculated and the surrounding content to be adjusted
// on the next animation frame.
//
// * {blockDecoration} A {Decoration} representing the block decoration you
// want to update the dimensions of.
invalidateBlockDecorationDimensions () {
this.getComponent().invalidateBlockDecorationDimensions(...arguments)
}
setFirstVisibleScreenRow (row) {
this.getModel().setFirstVisibleScreenRow(row)
}
getFirstVisibleScreenRow () {
return this.getModel().getFirstVisibleScreenRow()
}
getLastVisibleScreenRow () {
return this.getModel().getLastVisibleScreenRow()
}
getVisibleRowRange () {
return this.getModel().getVisibleRowRange()
}
intersectsVisibleRowRange (startRow, endRow) {
return !(
endRow <= this.getFirstVisibleScreenRow() ||
this.getLastVisibleScreenRow() <= startRow
)
}
selectionIntersectsVisibleRowRange (selection) {
const {start, end} = selection.getScreenRange()
return this.intersectsVisibleRowRange(start.row, end.row + 1)
}
setFirstVisibleScreenColumn (column) {
return this.getModel().setFirstVisibleScreenColumn(column)
}
getFirstVisibleScreenColumn () {
return this.getModel().getFirstVisibleScreenColumn()
}
}
module.exports =
document.registerElement('atom-text-editor', {
prototype: TextEditorElement.prototype
})