mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
379 lines
11 KiB
CoffeeScript
379 lines
11 KiB
CoffeeScript
{View, $} = require 'space-pen'
|
|
React = require 'react-atom-fork'
|
|
{defaults} = require 'underscore-plus'
|
|
TextBuffer = require 'text-buffer'
|
|
Editor = require './editor'
|
|
EditorComponent = require './editor-component'
|
|
{deprecate} = require 'grim'
|
|
|
|
# Public: Represents the entire visual pane in Atom.
|
|
#
|
|
# The EditorView manages the {Editor}, which manages the file buffers.
|
|
#
|
|
# ## Requiring in packages
|
|
#
|
|
# ```coffee
|
|
# {EditorView} = require 'atom'
|
|
#
|
|
# miniEditorView = new EditorView(mini: true)
|
|
# ```
|
|
#
|
|
# ## Iterating over the open editor views
|
|
#
|
|
# ```coffee
|
|
# for editorView in atom.workspaceView.getEditorViews()
|
|
# console.log(editorView.getModel().getPath())
|
|
# ```
|
|
#
|
|
# ## Subscribing to every current and future editor
|
|
#
|
|
# ```coffee
|
|
# atom.workspace.eachEditorView (editorView) ->
|
|
# console.log(editorView.getModel().getPath())
|
|
# ```
|
|
module.exports =
|
|
class EditorView extends View
|
|
@configDefaults:
|
|
fontFamily: ''
|
|
fontSize: 16
|
|
lineHeight: 1.3
|
|
showInvisibles: false
|
|
showIndentGuide: false
|
|
showLineNumbers: true
|
|
autoIndent: true
|
|
normalizeIndentOnPaste: true
|
|
nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
|
preferredLineLength: 80
|
|
tabLength: 2
|
|
softWrap: false
|
|
softTabs: true
|
|
softWrapAtPreferredLineLength: false
|
|
scrollSensitivity: 40
|
|
useHardwareAcceleration: true
|
|
|
|
@content: (params) ->
|
|
attributes = params.attributes ? {}
|
|
attributes.class = 'editor react editor-colors'
|
|
attributes.tabIndex = -1
|
|
@div attributes
|
|
|
|
focusOnAttach: false
|
|
|
|
# The constructor for setting up an `EditorView` instance.
|
|
#
|
|
# editorOrParams - Either an {Editor}, or an object with one property, `mini`.
|
|
# If `mini` is `true`, a "miniature" `Editor` is constructed.
|
|
# Typically, this is ideal for scenarios where you need an Atom editor,
|
|
# but without all the chrome, like scrollbars, gutter, _e.t.c._.
|
|
#
|
|
constructor: (editorOrParams, props) ->
|
|
super
|
|
|
|
if editorOrParams instanceof Editor
|
|
@editor = editorOrParams
|
|
else
|
|
{@editor, @mini, placeholderText} = editorOrParams
|
|
props ?= {}
|
|
props.mini = @mini
|
|
props.placeholderText = placeholderText
|
|
@editor ?= new Editor
|
|
buffer: new TextBuffer
|
|
softWrap: false
|
|
tabLength: 2
|
|
softTabs: true
|
|
mini: mini
|
|
|
|
props = defaults({@editor, parentView: this}, props)
|
|
@component = React.renderComponent(EditorComponent(props), @element)
|
|
|
|
node = @component.getDOMNode()
|
|
|
|
@scrollView = $(node).find('.scroll-view')
|
|
@underlayer = $(node).find('.highlights').addClass('underlayer')
|
|
@overlayer = $(node).find('.lines').addClass('overlayer')
|
|
@hiddenInput = $(node).find('.hidden-input')
|
|
|
|
# FIXME: there should be a better way to deal with the gutter element
|
|
@subscribe atom.config.observe 'editor.showLineNumbers', =>
|
|
@gutter = $(node).find('.gutter')
|
|
|
|
@gutter.removeClassFromAllLines = (klass) =>
|
|
@gutter.find('.line-number').removeClass(klass)
|
|
|
|
@gutter.getLineNumberElement = (bufferRow) =>
|
|
@gutter.find("[data-buffer-row='#{bufferRow}']")
|
|
|
|
@gutter.addClassToLine = (bufferRow, klass) =>
|
|
lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
|
|
lines.addClass(klass)
|
|
lines.length > 0
|
|
|
|
@on 'focus', =>
|
|
if @component?
|
|
@component.onFocus()
|
|
else
|
|
@focusOnAttach = true
|
|
|
|
getEditor: ->
|
|
deprecate("Use EditorView::getModel instead")
|
|
@editor
|
|
|
|
# Public: Get the underlying editor model for this view.
|
|
#
|
|
# Returns an {Editor}.
|
|
getModel: -> @editor
|
|
|
|
Object.defineProperty @::, 'lineHeight', get: -> @editor.getLineHeightInPixels()
|
|
Object.defineProperty @::, 'charWidth', get: -> @editor.getDefaultCharWidth()
|
|
Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0]
|
|
Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1]
|
|
Object.defineProperty @::, 'active', get: -> @is(@getPane()?.activeView)
|
|
Object.defineProperty @::, 'isFocused', get: -> @component?.state.focused
|
|
Object.defineProperty @::, 'mini', get: -> @component?.props.mini
|
|
|
|
afterAttach: (onDom) ->
|
|
return unless onDom
|
|
return if @attached
|
|
@attached = true
|
|
@component.pollDOM()
|
|
@focus() if @focusOnAttach
|
|
|
|
@addGrammarScopeAttribute()
|
|
@subscribe @editor, 'grammar-changed', =>
|
|
@addGrammarScopeAttribute()
|
|
|
|
@trigger 'editor:attached', [this]
|
|
|
|
addGrammarScopeAttribute: ->
|
|
grammarScope = @editor.getGrammar()?.scopeName?.replace(/\./g, ' ')
|
|
@attr('data-grammar', grammarScope)
|
|
|
|
scrollTop: (scrollTop) ->
|
|
if scrollTop?
|
|
@editor.setScrollTop(scrollTop)
|
|
else
|
|
@editor.getScrollTop()
|
|
|
|
scrollLeft: (scrollLeft) ->
|
|
if scrollLeft?
|
|
@editor.setScrollLeft(scrollLeft)
|
|
else
|
|
@editor.getScrollLeft()
|
|
|
|
# Public: Scrolls the editor to the bottom.
|
|
scrollToBottom: ->
|
|
@editor.setScrollBottom(Infinity)
|
|
|
|
# Public: Scrolls the editor to the given screen position.
|
|
#
|
|
# screenPosition - An object that represents a buffer position. It can be either
|
|
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
|
# options - A hash matching the options available to {::scrollToScreenPosition}
|
|
scrollToScreenPosition: (screenPosition, options) ->
|
|
@editor.scrollToScreenPosition(screenPosition, options)
|
|
|
|
# Public: Scrolls the editor to the given buffer position.
|
|
#
|
|
# bufferPosition - An object that represents a buffer position. It can be either
|
|
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
|
# options - A hash matching the options available to {::scrollToBufferPosition}
|
|
scrollToBufferPosition: (bufferPosition, options) ->
|
|
@editor.scrollToBufferPosition(bufferPosition, options)
|
|
|
|
scrollToCursorPosition: ->
|
|
@editor.scrollToCursorPosition()
|
|
|
|
scrollToPixelPosition: (pixelPosition) ->
|
|
screenPosition = screenPositionForPixelPosition(pixelPosition)
|
|
@editor.scrollToScreenPosition(screenPosition)
|
|
|
|
# Public: Converts a buffer position to a pixel position.
|
|
#
|
|
# position - An object that represents a buffer position. It can be either
|
|
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
|
#
|
|
# Returns an object with two values: `top` and `left`, representing the pixel positions.
|
|
pixelPositionForBufferPosition: (bufferPosition) ->
|
|
@editor.pixelPositionForBufferPosition(bufferPosition)
|
|
|
|
# Public: Converts a screen position to a pixel position.
|
|
#
|
|
# position - An object that represents a screen position. It can be either
|
|
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
|
|
#
|
|
# Returns an object with two values: `top` and `left`, representing the pixel positions.
|
|
pixelPositionForScreenPosition: (screenPosition) ->
|
|
@editor.pixelPositionForScreenPosition(screenPosition)
|
|
|
|
appendToLinesView: (view) ->
|
|
view.css('position', 'absolute')
|
|
view.css('z-index', 1)
|
|
@find('.lines').prepend(view)
|
|
|
|
beforeRemove: ->
|
|
return unless @attached
|
|
@attached = false
|
|
React.unmountComponentAtNode(@element) if @component.isMounted()
|
|
@trigger 'editor:detached', this
|
|
|
|
# Public: Split the editor view left.
|
|
splitLeft: ->
|
|
pane = @getPane()
|
|
pane?.splitLeft(pane?.copyActiveItem()).activeView
|
|
|
|
# Public: Split the editor view right.
|
|
splitRight: ->
|
|
pane = @getPane()
|
|
pane?.splitRight(pane?.copyActiveItem()).activeView
|
|
|
|
# Public: Split the editor view up.
|
|
splitUp: ->
|
|
pane = @getPane()
|
|
pane?.splitUp(pane?.copyActiveItem()).activeView
|
|
|
|
# Public: Split the editor view down.
|
|
splitDown: ->
|
|
pane = @getPane()
|
|
pane?.splitDown(pane?.copyActiveItem()).activeView
|
|
|
|
# Public: Get this view's pane.
|
|
#
|
|
# Returns a {Pane}.
|
|
getPane: ->
|
|
@parent('.item-views').parents('.pane').view()
|
|
|
|
hide: ->
|
|
super
|
|
@pollComponentDOM()
|
|
|
|
show: ->
|
|
super
|
|
@pollComponentDOM()
|
|
|
|
pollComponentDOM: ->
|
|
return unless @component?
|
|
valueToRestore = @component.performSyncUpdates
|
|
@component.performSyncUpdates = true
|
|
@component.pollDOM()
|
|
@component.performSyncUpdates = valueToRestore
|
|
|
|
pageDown: ->
|
|
deprecate('Use editorView.getModel().pageDown()')
|
|
@editor.pageDown()
|
|
|
|
pageUp: ->
|
|
deprecate('Use editorView.getModel().pageUp()')
|
|
@editor.pageUp()
|
|
|
|
# Public: Retrieves the number of the row that is visible and currently at the
|
|
# top of the editor.
|
|
#
|
|
# Returns a {Number}.
|
|
getFirstVisibleScreenRow: ->
|
|
@editor.getVisibleRowRange()[0]
|
|
|
|
# Public: Retrieves the number of the row that is visible and currently at the
|
|
# bottom of the editor.
|
|
#
|
|
# Returns a {Number}.
|
|
getLastVisibleScreenRow: ->
|
|
@editor.getVisibleRowRange()[1]
|
|
|
|
# Public: Gets the font family for the editor.
|
|
#
|
|
# Returns a {String} identifying the CSS `font-family`.
|
|
getFontFamily: ->
|
|
@component?.getFontFamily()
|
|
|
|
# Public: Sets the font family for the editor.
|
|
#
|
|
# fontFamily - A {String} identifying the CSS `font-family`.
|
|
setFontFamily: (fontFamily) ->
|
|
@component?.setFontFamily(fontFamily)
|
|
|
|
# Public: Retrieves the font size for the editor.
|
|
#
|
|
# Returns a {Number} indicating the font size in pixels.
|
|
getFontSize: ->
|
|
@component?.getFontSize()
|
|
|
|
# Public: Sets the font size for the editor.
|
|
#
|
|
# fontSize - A {Number} indicating the font size in pixels.
|
|
setFontSize: (fontSize) ->
|
|
@component?.setFontSize(fontSize)
|
|
|
|
setWidthInChars: (widthInChars) ->
|
|
@component.getDOMNode().style.width = (@editor.getDefaultCharWidth() * widthInChars) + 'px'
|
|
|
|
# Public: Sets the line height of the editor.
|
|
#
|
|
# Calling this method has no effect when called on a mini editor.
|
|
#
|
|
# lineHeight - A {Number} without a unit suffix identifying the CSS
|
|
# `line-height`.
|
|
setLineHeight: (lineHeight) ->
|
|
@component.setLineHeight(lineHeight)
|
|
|
|
# Public: Sets whether you want to show the indentation guides.
|
|
#
|
|
# showIndentGuide - A {Boolean} you can set to `true` if you want to see the
|
|
# indentation guides.
|
|
setShowIndentGuide: (showIndentGuide) ->
|
|
@component.setShowIndentGuide(showIndentGuide)
|
|
|
|
# Public: Enables/disables soft wrap on the editor.
|
|
#
|
|
# softWrap - A {Boolean} which, if `true`, enables soft wrap
|
|
setSoftWrap: (softWrap) ->
|
|
@editor.setSoftWrap(softWrap)
|
|
|
|
# Public: Set whether invisible characters are shown.
|
|
#
|
|
# showInvisibles - A {Boolean} which, if `true`, show invisible characters.
|
|
setShowInvisibles: (showInvisibles) ->
|
|
@component.setShowInvisibles(showInvisibles)
|
|
|
|
toggleSoftWrap: ->
|
|
@editor.toggleSoftWrap()
|
|
|
|
toggleSoftTabs: ->
|
|
@editor.toggleSoftTabs()
|
|
|
|
getText: ->
|
|
@editor.getText()
|
|
|
|
setText: (text) ->
|
|
@editor.setText(text)
|
|
|
|
insertText: (text) ->
|
|
@editor.insertText(text)
|
|
|
|
isInputEnabled: ->
|
|
@component.isInputEnabled()
|
|
|
|
setInputEnabled: (inputEnabled) ->
|
|
@component.setInputEnabled(inputEnabled)
|
|
|
|
requestDisplayUpdate: -> # No-op shim for find-and-replace
|
|
|
|
updateDisplay: -> # No-op shim for package specs
|
|
|
|
resetDisplay: -> # No-op shim for package specs
|
|
|
|
redraw: -> # No-op shim
|
|
|
|
# Public: Set the text to appear in the editor when it is empty.
|
|
#
|
|
# This only affects mini editors.
|
|
#
|
|
# placeholderText - A {String} of text to display when empty.
|
|
setPlaceholderText: (placeholderText) ->
|
|
if @component?
|
|
@component.setProps({placeholderText})
|
|
else
|
|
@props.placeholderText = placeholderText
|
|
|
|
lineElementForScreenRow: (screenRow) ->
|
|
$(@component.lineNodeForScreenRow(screenRow))
|