mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 07:28:08 +03:00
Refactor DisplayBuffer to DecorationManager
This commit moves all the remaining concerns not related to decorations out of `DisplayBuffer` and into `TextEditor`. This means the `DisplayBuffer` is now free to be renamed to `DecorationManager`.
This commit is contained in:
parent
0cf0d6f587
commit
bef7539e34
85
spec/decoration-manager-spec.coffee
Normal file
85
spec/decoration-manager-spec.coffee
Normal file
@ -0,0 +1,85 @@
|
||||
DecorationManager = require '../src/decoration-manager'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
describe "DecorationManager", ->
|
||||
[decorationManager, buffer, defaultMarkerLayer] = []
|
||||
|
||||
beforeEach ->
|
||||
buffer = atom.project.bufferForPathSync('sample.js')
|
||||
displayLayer = buffer.addDisplayLayer()
|
||||
defaultMarkerLayer = displayLayer.addMarkerLayer()
|
||||
decorationManager = new DecorationManager(displayLayer, defaultMarkerLayer)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
afterEach ->
|
||||
decorationManager.destroy()
|
||||
buffer.release()
|
||||
|
||||
describe "decorations", ->
|
||||
[marker, decoration, decorationProperties] = []
|
||||
beforeEach ->
|
||||
marker = defaultMarkerLayer.markBufferRange([[2, 13], [3, 15]])
|
||||
decorationProperties = {type: 'line-number', class: 'one'}
|
||||
decoration = decorationManager.decorateMarker(marker, decorationProperties)
|
||||
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
expect(decoration).toBeDefined()
|
||||
expect(decoration.getProperties()).toBe decorationProperties
|
||||
expect(decorationManager.decorationForId(decoration.id)).toBe decoration
|
||||
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id][0]).toBe decoration
|
||||
|
||||
decoration.destroy()
|
||||
expect(decorationManager.decorationsForScreenRowRange(2, 3)[marker.id]).not.toBeDefined()
|
||||
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "will not fail if the decoration is removed twice", ->
|
||||
decoration.destroy()
|
||||
decoration.destroy()
|
||||
expect(decorationManager.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "does not allow destroyed markers to be decorated", ->
|
||||
marker.destroy()
|
||||
expect(->
|
||||
decorationManager.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')})
|
||||
).toThrow("Cannot decorate a destroyed marker")
|
||||
expect(decorationManager.getOverlayDecorations()).toEqual []
|
||||
|
||||
describe "when a decoration is updated via Decoration::update()", ->
|
||||
it "emits an 'updated' event containing the new and old params", ->
|
||||
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
|
||||
decoration.setProperties type: 'line-number', class: 'two'
|
||||
|
||||
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
|
||||
expect(oldProperties).toEqual decorationProperties
|
||||
expect(newProperties).toEqual {type: 'line-number', gutterName: 'line-number', class: 'two'}
|
||||
|
||||
describe "::getDecorations(properties)", ->
|
||||
it "returns decorations matching the given optional properties", ->
|
||||
expect(decorationManager.getDecorations()).toEqual [decoration]
|
||||
expect(decorationManager.getDecorations(class: 'two').length).toEqual 0
|
||||
expect(decorationManager.getDecorations(class: 'one').length).toEqual 1
|
||||
|
||||
describe "::decorateMarker", ->
|
||||
describe "when decorating gutters", ->
|
||||
[marker] = []
|
||||
|
||||
beforeEach ->
|
||||
marker = defaultMarkerLayer.markBufferRange([[1, 0], [1, 0]])
|
||||
|
||||
it "creates a decoration that is both of 'line-number' and 'gutter' type when called with the 'line-number' type", ->
|
||||
decorationProperties = {type: 'line-number', class: 'one'}
|
||||
decoration = decorationManager.decorateMarker(marker, decorationProperties)
|
||||
expect(decoration.isType('line-number')).toBe true
|
||||
expect(decoration.isType('gutter')).toBe true
|
||||
expect(decoration.getProperties().gutterName).toBe 'line-number'
|
||||
expect(decoration.getProperties().class).toBe 'one'
|
||||
|
||||
it "creates a decoration that is only of 'gutter' type if called with the 'gutter' type and a 'gutterName'", ->
|
||||
decorationProperties = {type: 'gutter', gutterName: 'test-gutter', class: 'one'}
|
||||
decoration = decorationManager.decorateMarker(marker, decorationProperties)
|
||||
expect(decoration.isType('gutter')).toBe true
|
||||
expect(decoration.isType('line-number')).toBe false
|
||||
expect(decoration.getProperties().gutterName).toBe 'test-gutter'
|
||||
expect(decoration.getProperties().class).toBe 'one'
|
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ describe "TextEditor", ->
|
||||
buffer = new TextBuffer
|
||||
editor = atom.workspace.buildTextEditor({buffer})
|
||||
editor.setEditorWidthInChars(80)
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
steps = []
|
||||
|
||||
times 30, ->
|
||||
|
@ -1477,7 +1477,7 @@ describe('TextEditorComponent', function () {
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
let marker2 = editor.displayBuffer.markBufferRange([[9, 0], [9, 0]])
|
||||
let marker2 = editor.markBufferRange([[9, 0], [9, 0]])
|
||||
editor.decorateMarker(marker2, {
|
||||
type: ['line-number', 'line'],
|
||||
'class': 'b'
|
||||
@ -1889,7 +1889,7 @@ describe('TextEditorComponent', function () {
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
marker = editor.displayBuffer.markBufferRange([[9, 2], [9, 4]], {
|
||||
marker = editor.markBufferRange([[9, 2], [9, 4]], {
|
||||
invalidate: 'inside'
|
||||
})
|
||||
editor.decorateMarker(marker, {
|
||||
@ -2084,7 +2084,7 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
describe('when the marker is empty', function () {
|
||||
it('renders an overlay decoration when added and removes the overlay when the decoration is destroyed', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[2, 13], [2, 13]], {
|
||||
let marker = editor.markBufferRange([[2, 13], [2, 13]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@ -2106,7 +2106,7 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
|
||||
it('renders the overlay element with the CSS class specified by the decoration', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[2, 13], [2, 13]], {
|
||||
let marker = editor.markBufferRange([[2, 13], [2, 13]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@ -2127,7 +2127,7 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
describe('when the marker is not empty', function () {
|
||||
it('renders at the head of the marker by default', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[2, 5], [2, 10]], {
|
||||
let marker = editor.markBufferRange([[2, 5], [2, 10]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@ -2173,7 +2173,7 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
|
||||
it('slides horizontally left when near the right edge on #win32 and #darwin', async function () {
|
||||
let marker = editor.displayBuffer.markBufferRange([[0, 26], [0, 26]], {
|
||||
let marker = editor.markBufferRange([[0, 26], [0, 26]], {
|
||||
invalidate: 'never'
|
||||
})
|
||||
let decoration = editor.decorateMarker(marker, {
|
||||
@ -4934,7 +4934,7 @@ describe('TextEditorComponent', function () {
|
||||
|
||||
function lineNumberForBufferRowHasClass (bufferRow, klass) {
|
||||
let screenRow
|
||||
screenRow = editor.displayBuffer.screenRowForBufferRow(bufferRow)
|
||||
screenRow = editor.screenRowForBufferRow(bufferRow)
|
||||
return component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
}
|
||||
|
||||
|
@ -5710,6 +5710,19 @@ describe "TextEditor", ->
|
||||
expect(editor.getFirstVisibleScreenRow()).toEqual 89
|
||||
expect(editor.getVisibleRowRange()).toEqual [89, 99]
|
||||
|
||||
describe "::scrollToScreenPosition(position, [options])", ->
|
||||
it "triggers ::onDidRequestAutoscroll with the logical coordinates along with the options", ->
|
||||
scrollSpy = jasmine.createSpy("::onDidRequestAutoscroll")
|
||||
editor.onDidRequestAutoscroll(scrollSpy)
|
||||
|
||||
editor.scrollToScreenPosition([8, 20])
|
||||
editor.scrollToScreenPosition([8, 20], center: true)
|
||||
editor.scrollToScreenPosition([8, 20], center: false, reversed: true)
|
||||
|
||||
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {})
|
||||
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true})
|
||||
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true})
|
||||
|
||||
describe '.get/setPlaceholderText()', ->
|
||||
it 'can be created with placeholderText', ->
|
||||
newEditor = atom.workspace.buildTextEditor(
|
||||
|
@ -419,7 +419,7 @@ describe "TokenizedBuffer", ->
|
||||
atom.workspace.open('sample.js').then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
tokenizedBuffer.onDidTokenize tokenizedHandler
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
expect(tokenizedHandler.callCount).toBe(1)
|
||||
@ -432,7 +432,7 @@ describe "TokenizedBuffer", ->
|
||||
atom.workspace.open('sample.js').then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
|
||||
tokenizedBuffer.onDidTokenize tokenizedHandler
|
||||
@ -450,7 +450,7 @@ describe "TokenizedBuffer", ->
|
||||
atom.workspace.open('coffee.coffee').then (o) -> editor = o
|
||||
|
||||
runs ->
|
||||
tokenizedBuffer = editor.displayBuffer.tokenizedBuffer
|
||||
tokenizedBuffer = editor.tokenizedBuffer
|
||||
tokenizedBuffer.onDidTokenize tokenizedHandler
|
||||
fullyTokenize(tokenizedBuffer)
|
||||
tokenizedHandler.reset()
|
||||
|
@ -428,7 +428,7 @@ describe "Workspace", ->
|
||||
workspace.open('sample.js').then (e) -> editor = e
|
||||
|
||||
runs ->
|
||||
expect(editor.displayBuffer.largeFileMode).toBe true
|
||||
expect(editor.largeFileMode).toBe true
|
||||
|
||||
describe "when the file is over 20MB", ->
|
||||
it "prompts the user to make sure they want to open a file this big", ->
|
||||
@ -453,7 +453,7 @@ describe "Workspace", ->
|
||||
|
||||
runs ->
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(editor.displayBuffer.largeFileMode).toBe true
|
||||
expect(editor.largeFileMode).toBe true
|
||||
|
||||
describe "when passed a path that matches a custom opener", ->
|
||||
it "returns the resource returned by the custom opener", ->
|
||||
|
180
src/decoration-manager.coffee
Normal file
180
src/decoration-manager.coffee
Normal file
@ -0,0 +1,180 @@
|
||||
{Emitter} = require 'event-kit'
|
||||
Model = require './model'
|
||||
Decoration = require './decoration'
|
||||
LayerDecoration = require './layer-decoration'
|
||||
|
||||
module.exports =
|
||||
class DecorationManager extends Model
|
||||
didUpdateDecorationsEventScheduled: false
|
||||
updatedSynchronously: false
|
||||
|
||||
constructor: (@displayLayer, @defaultMarkerLayer) ->
|
||||
super
|
||||
|
||||
@emitter = new Emitter
|
||||
@decorationsById = {}
|
||||
@decorationsByMarkerId = {}
|
||||
@overlayDecorationsById = {}
|
||||
@layerDecorationsByMarkerLayerId = {}
|
||||
@decorationCountsByLayerId = {}
|
||||
@layerUpdateDisposablesByLayerId = {}
|
||||
|
||||
observeDecorations: (callback) ->
|
||||
callback(decoration) for decoration in @getDecorations()
|
||||
@onDidAddDecoration(callback)
|
||||
|
||||
onDidAddDecoration: (callback) ->
|
||||
@emitter.on 'did-add-decoration', callback
|
||||
|
||||
onDidRemoveDecoration: (callback) ->
|
||||
@emitter.on 'did-remove-decoration', callback
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@emitter.on 'did-update-decorations', callback
|
||||
|
||||
setUpdatedSynchronously: (@updatedSynchronously) ->
|
||||
|
||||
decorationForId: (id) ->
|
||||
@decorationsById[id]
|
||||
|
||||
getDecorations: (propertyFilter) ->
|
||||
allDecorations = []
|
||||
for markerId, decorations of @decorationsByMarkerId
|
||||
allDecorations.push(decorations...) if decorations?
|
||||
if propertyFilter?
|
||||
allDecorations = allDecorations.filter (decoration) ->
|
||||
for key, value of propertyFilter
|
||||
return false unless decoration.properties[key] is value
|
||||
true
|
||||
allDecorations
|
||||
|
||||
getLineDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line')
|
||||
|
||||
getLineNumberDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line-number')
|
||||
|
||||
getHighlightDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('highlight')
|
||||
|
||||
getOverlayDecorations: (propertyFilter) ->
|
||||
result = []
|
||||
for id, decoration of @overlayDecorationsById
|
||||
result.push(decoration)
|
||||
if propertyFilter?
|
||||
result.filter (decoration) ->
|
||||
for key, value of propertyFilter
|
||||
return false unless decoration.properties[key] is value
|
||||
true
|
||||
else
|
||||
result
|
||||
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsByMarkerId = {}
|
||||
for marker in @defaultMarkerLayer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
decorationsByMarkerId[marker.id] = decorations
|
||||
decorationsByMarkerId
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsState = {}
|
||||
|
||||
for layerId of @decorationCountsByLayerId
|
||||
layer = @displayLayer.getMarkerLayer(layerId)
|
||||
|
||||
for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow]) when marker.isValid()
|
||||
screenRange = marker.getScreenRange()
|
||||
rangeIsReversed = marker.isReversed()
|
||||
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
for decoration in decorations
|
||||
decorationsState[decoration.id] = {
|
||||
properties: decoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
if layerDecorations = @layerDecorationsByMarkerLayerId[layerId]
|
||||
for layerDecoration in layerDecorations
|
||||
decorationsState["#{layerDecoration.id}-#{marker.id}"] = {
|
||||
properties: layerDecoration.overridePropertiesByMarkerId[marker.id] ? layerDecoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
decorationsState
|
||||
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
|
||||
marker = @displayLayer.getMarkerLayer(marker.layer.id).getMarker(marker.id)
|
||||
decoration = new Decoration(marker, this, decorationParams)
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
@decorationsByMarkerId[marker.id].push(decoration)
|
||||
@overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
|
||||
@decorationsById[decoration.id] = decoration
|
||||
@observeDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-add-decoration', decoration
|
||||
decoration
|
||||
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
decoration = new LayerDecoration(markerLayer, this, decorationParams)
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
|
||||
@observeDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
decoration
|
||||
|
||||
decorationsForMarkerId: (markerId) ->
|
||||
@decorationsByMarkerId[markerId]
|
||||
|
||||
scheduleUpdateDecorationsEvent: ->
|
||||
if @updatedSynchronously
|
||||
@emitter.emit 'did-update-decorations'
|
||||
return
|
||||
|
||||
unless @didUpdateDecorationsEventScheduled
|
||||
@didUpdateDecorationsEventScheduled = true
|
||||
process.nextTick =>
|
||||
@didUpdateDecorationsEventScheduled = false
|
||||
@emitter.emit 'did-update-decorations'
|
||||
|
||||
decorationDidChangeType: (decoration) ->
|
||||
if decoration.isType('overlay')
|
||||
@overlayDecorationsById[decoration.id] = decoration
|
||||
else
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
@unobserveDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
didDestroyLayerDecoration: (decoration) ->
|
||||
{markerLayer} = decoration
|
||||
return unless decorations = @layerDecorationsByMarkerLayerId[markerLayer.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @layerDecorationsByMarkerLayerId[markerLayer.id] if decorations.length is 0
|
||||
@unobserveDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
observeDecoratedLayer: (layer) ->
|
||||
@decorationCountsByLayerId[layer.id] ?= 0
|
||||
if ++@decorationCountsByLayerId[layer.id] is 1
|
||||
@layerUpdateDisposablesByLayerId[layer.id] = layer.onDidUpdate(@scheduleUpdateDecorationsEvent.bind(this))
|
||||
|
||||
unobserveDecoratedLayer: (layer) ->
|
||||
if --@decorationCountsByLayerId[layer.id] is 0
|
||||
@layerUpdateDisposablesByLayerId[layer.id].dispose()
|
||||
delete @decorationCountsByLayerId[layer.id]
|
||||
delete @layerUpdateDisposablesByLayerId[layer.id]
|
@ -62,7 +62,7 @@ class Decoration
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: (@marker, @displayBuffer, properties) ->
|
||||
constructor: (@marker, @decorationManager, properties) ->
|
||||
@emitter = new Emitter
|
||||
@id = nextId()
|
||||
@setProperties properties
|
||||
@ -78,7 +78,7 @@ class Decoration
|
||||
@markerDestroyDisposable.dispose()
|
||||
@markerDestroyDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyDecoration(this)
|
||||
@decorationManager.didDestroyDecoration(this)
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
@ -149,8 +149,8 @@ class Decoration
|
||||
oldProperties = @properties
|
||||
@properties = translateDecorationParamsOldToNew(newProperties)
|
||||
if newProperties.type?
|
||||
@displayBuffer.decorationDidChangeType(this)
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.decorationDidChangeType(this)
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
|
||||
|
||||
###
|
||||
@ -175,5 +175,5 @@ class Decoration
|
||||
@properties.flashCount++
|
||||
@properties.flashClass = klass
|
||||
@properties.flashDuration = duration
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-flash'
|
||||
|
@ -1,750 +0,0 @@
|
||||
_ = require 'underscore-plus'
|
||||
{CompositeDisposable, Emitter} = require 'event-kit'
|
||||
{Point, Range} = require 'text-buffer'
|
||||
TokenizedBuffer = require './tokenized-buffer'
|
||||
RowMap = require './row-map'
|
||||
Model = require './model'
|
||||
Token = require './token'
|
||||
Decoration = require './decoration'
|
||||
LayerDecoration = require './layer-decoration'
|
||||
{isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isWrapBoundary} = require './text-utils'
|
||||
|
||||
ZERO_WIDTH_NBSP = '\ufeff'
|
||||
|
||||
class BufferToScreenConversionError extends Error
|
||||
constructor: (@message, @metadata) ->
|
||||
super
|
||||
Error.captureStackTrace(this, BufferToScreenConversionError)
|
||||
|
||||
module.exports =
|
||||
class DisplayBuffer extends Model
|
||||
verticalScrollMargin: 2
|
||||
horizontalScrollMargin: 6
|
||||
changeCount: 0
|
||||
softWrapped: null
|
||||
editorWidthInChars: null
|
||||
lineHeightInPixels: null
|
||||
defaultCharWidth: null
|
||||
height: null
|
||||
width: null
|
||||
didUpdateDecorationsEventScheduled: false
|
||||
updatedSynchronously: false
|
||||
|
||||
@deserialize: (state, atomEnvironment) ->
|
||||
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer, atomEnvironment)
|
||||
state.displayLayer = state.tokenizedBuffer.buffer.getDisplayLayer(state.displayLayerId)
|
||||
state.config = atomEnvironment.config
|
||||
state.assert = atomEnvironment.assert
|
||||
state.grammarRegistry = atomEnvironment.grammars
|
||||
state.packageManager = atomEnvironment.packages
|
||||
new this(state)
|
||||
|
||||
constructor: (params={}) ->
|
||||
super
|
||||
|
||||
{
|
||||
tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, @ignoreInvisibles,
|
||||
@largeFileMode, @config, @assert, @grammarRegistry, @packageManager, @displayLayer
|
||||
} = params
|
||||
|
||||
@emitter = new Emitter
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@tokenizedBuffer ?= new TokenizedBuffer({
|
||||
tabLength, buffer, @largeFileMode, @config,
|
||||
@grammarRegistry, @packageManager, @assert
|
||||
})
|
||||
@buffer = @tokenizedBuffer.buffer
|
||||
@displayLayer ?= @buffer.addDisplayLayer()
|
||||
@displayLayer.setTextDecorationLayer(@tokenizedBuffer)
|
||||
@charWidthsByScope = {}
|
||||
@defaultMarkerLayer = @displayLayer.addMarkerLayer()
|
||||
@decorationsById = {}
|
||||
@decorationsByMarkerId = {}
|
||||
@overlayDecorationsById = {}
|
||||
@layerDecorationsByMarkerLayerId = {}
|
||||
@decorationCountsByLayerId = {}
|
||||
@layerUpdateDisposablesByLayerId = {}
|
||||
|
||||
@disposables.add @tokenizedBuffer.observeGrammar @subscribeToScopedConfigSettings
|
||||
@disposables.add @buffer.onDidCreateMarker @didCreateDefaultLayerMarker
|
||||
|
||||
subscribeToScopedConfigSettings: =>
|
||||
@scopedConfigSubscriptions?.dispose()
|
||||
@scopedConfigSubscriptions = subscriptions = new CompositeDisposable
|
||||
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
subscriptions.add @config.onDidChange 'editor.tabLength', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.invisibles', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.showInvisibles', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.showIndentGuide', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.softWrap', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.softWrapHangingIndent', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.softWrapAtPreferredLineLength', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.preferredLineLength', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
|
||||
@resetDisplayLayer()
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'DisplayBuffer'
|
||||
id: @id
|
||||
softWrapped: @isSoftWrapped()
|
||||
editorWidthInChars: @editorWidthInChars
|
||||
tokenizedBuffer: @tokenizedBuffer.serialize()
|
||||
largeFileMode: @largeFileMode
|
||||
displayLayerId: @displayLayer.id
|
||||
|
||||
copy: ->
|
||||
new DisplayBuffer({
|
||||
@buffer, tabLength: @getTabLength(), @largeFileMode, @config, @assert,
|
||||
@grammarRegistry, @packageManager, displayLayer: @buffer.copyDisplayLayer(@displayLayer.id)
|
||||
})
|
||||
|
||||
resetDisplayLayer: ->
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
invisibles =
|
||||
if @config.get('editor.showInvisibles', scope: scopeDescriptor) and not @ignoreInvisibles
|
||||
@config.get('editor.invisibles', scope: scopeDescriptor)
|
||||
else
|
||||
{}
|
||||
|
||||
softWrapColumn =
|
||||
if @isSoftWrapped()
|
||||
if @config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
|
||||
@config.get('editor.preferredLineLength', scope: scopeDescriptor)
|
||||
else
|
||||
@getEditorWidthInChars()
|
||||
else
|
||||
Infinity
|
||||
|
||||
@displayLayer.reset({
|
||||
invisibles: invisibles
|
||||
softWrapColumn: softWrapColumn
|
||||
showIndentGuides: @config.get('editor.showIndentGuide', scope: scopeDescriptor)
|
||||
tabLength: @getTabLength(),
|
||||
ratioForCharacter: @ratioForCharacter.bind(this),
|
||||
isWrapBoundary: isWrapBoundary,
|
||||
foldCharacter: ZERO_WIDTH_NBSP
|
||||
})
|
||||
|
||||
onDidChangeSoftWrapped: (callback) ->
|
||||
@emitter.on 'did-change-soft-wrapped', callback
|
||||
|
||||
onDidChangeGrammar: (callback) ->
|
||||
@tokenizedBuffer.onDidChangeGrammar(callback)
|
||||
|
||||
onDidTokenize: (callback) ->
|
||||
@tokenizedBuffer.onDidTokenize(callback)
|
||||
|
||||
onDidChange: (callback) ->
|
||||
@emitter.on 'did-change', callback
|
||||
|
||||
onDidChangeCharacterWidths: (callback) ->
|
||||
@emitter.on 'did-change-character-widths', callback
|
||||
|
||||
onDidRequestAutoscroll: (callback) ->
|
||||
@emitter.on 'did-request-autoscroll', callback
|
||||
|
||||
observeDecorations: (callback) ->
|
||||
callback(decoration) for decoration in @getDecorations()
|
||||
@onDidAddDecoration(callback)
|
||||
|
||||
onDidAddDecoration: (callback) ->
|
||||
@emitter.on 'did-add-decoration', callback
|
||||
|
||||
onDidRemoveDecoration: (callback) ->
|
||||
@emitter.on 'did-remove-decoration', callback
|
||||
|
||||
onDidCreateMarker: (callback) ->
|
||||
@emitter.on 'did-create-marker', callback
|
||||
|
||||
onDidUpdateMarkers: (callback) ->
|
||||
@emitter.on 'did-update-markers', callback
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@emitter.on 'did-update-decorations', callback
|
||||
|
||||
# Sets the visibility of the tokenized buffer.
|
||||
#
|
||||
# visible - A {Boolean} indicating of the tokenized buffer is shown
|
||||
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
|
||||
|
||||
setUpdatedSynchronously: (@updatedSynchronously) ->
|
||||
|
||||
getVerticalScrollMargin: ->
|
||||
maxScrollMargin = Math.floor(((@getHeight() / @getLineHeightInPixels()) - 1) / 2)
|
||||
Math.min(@verticalScrollMargin, maxScrollMargin)
|
||||
|
||||
setVerticalScrollMargin: (@verticalScrollMargin) -> @verticalScrollMargin
|
||||
|
||||
getHorizontalScrollMargin: -> Math.min(@horizontalScrollMargin, Math.floor(((@getWidth() / @getDefaultCharWidth()) - 1) / 2))
|
||||
setHorizontalScrollMargin: (@horizontalScrollMargin) -> @horizontalScrollMargin
|
||||
|
||||
getHeight: ->
|
||||
@height
|
||||
|
||||
setHeight: (@height) ->
|
||||
@height
|
||||
|
||||
getWidth: ->
|
||||
@width
|
||||
|
||||
setWidth: (newWidth) ->
|
||||
oldWidth = @width
|
||||
@width = newWidth
|
||||
@resetDisplayLayer() if newWidth isnt oldWidth and @isSoftWrapped()
|
||||
@width
|
||||
|
||||
getLineHeightInPixels: -> @lineHeightInPixels
|
||||
setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels
|
||||
|
||||
ratioForCharacter: (character) ->
|
||||
if isKoreanCharacter(character)
|
||||
@getKoreanCharWidth() / @getDefaultCharWidth()
|
||||
else if isHalfWidthCharacter(character)
|
||||
@getHalfWidthCharWidth() / @getDefaultCharWidth()
|
||||
else if isDoubleWidthCharacter(character)
|
||||
@getDoubleWidthCharWidth() / @getDefaultCharWidth()
|
||||
else
|
||||
1
|
||||
|
||||
getKoreanCharWidth: -> @koreanCharWidth
|
||||
|
||||
getHalfWidthCharWidth: -> @halfWidthCharWidth
|
||||
|
||||
getDoubleWidthCharWidth: -> @doubleWidthCharWidth
|
||||
|
||||
getDefaultCharWidth: -> @defaultCharWidth
|
||||
|
||||
setDefaultCharWidth: (defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
|
||||
doubleWidthCharWidth ?= defaultCharWidth
|
||||
halfWidthCharWidth ?= defaultCharWidth
|
||||
koreanCharWidth ?= defaultCharWidth
|
||||
if defaultCharWidth isnt @defaultCharWidth or doubleWidthCharWidth isnt @doubleWidthCharWidth and halfWidthCharWidth isnt @halfWidthCharWidth and koreanCharWidth isnt @koreanCharWidth
|
||||
@defaultCharWidth = defaultCharWidth
|
||||
@doubleWidthCharWidth = doubleWidthCharWidth
|
||||
@halfWidthCharWidth = halfWidthCharWidth
|
||||
@koreanCharWidth = koreanCharWidth
|
||||
@resetDisplayLayer() if @isSoftWrapped() and @getEditorWidthInChars()?
|
||||
defaultCharWidth
|
||||
|
||||
getCursorWidth: -> 1
|
||||
|
||||
scrollToScreenRange: (screenRange, options = {}) ->
|
||||
scrollEvent = {screenRange, options}
|
||||
@emitter.emit "did-request-autoscroll", scrollEvent
|
||||
|
||||
scrollToScreenPosition: (screenPosition, options) ->
|
||||
@scrollToScreenRange(new Range(screenPosition, screenPosition), options)
|
||||
|
||||
scrollToBufferPosition: (bufferPosition, options) ->
|
||||
@scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition), options)
|
||||
|
||||
# Retrieves the current tab length.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getTabLength: ->
|
||||
if @tabLength?
|
||||
@tabLength
|
||||
else
|
||||
@config.get('editor.tabLength', scope: @getRootScopeDescriptor())
|
||||
|
||||
# Specifies the tab length.
|
||||
#
|
||||
# tabLength - A {Number} that defines the new tab length.
|
||||
setTabLength: (tabLength) ->
|
||||
return if tabLength is @tabLength
|
||||
|
||||
@tabLength = tabLength
|
||||
@tokenizedBuffer.setTabLength(@tabLength)
|
||||
@resetDisplayLayer()
|
||||
|
||||
setIgnoreInvisibles: (ignoreInvisibles) ->
|
||||
return if ignoreInvisibles is @ignoreInvisibles
|
||||
|
||||
@ignoreInvisibles = ignoreInvisibles
|
||||
@resetDisplayLayer()
|
||||
|
||||
setSoftWrapped: (softWrapped) ->
|
||||
if softWrapped isnt @softWrapped
|
||||
@softWrapped = softWrapped
|
||||
@resetDisplayLayer()
|
||||
softWrapped = @isSoftWrapped()
|
||||
@emitter.emit 'did-change-soft-wrapped', softWrapped
|
||||
softWrapped
|
||||
else
|
||||
@isSoftWrapped()
|
||||
|
||||
isSoftWrapped: ->
|
||||
if @largeFileMode
|
||||
false
|
||||
else
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
@softWrapped ? @config.get('editor.softWrap', scope: scopeDescriptor) ? false
|
||||
|
||||
# Set the number of characters that fit horizontally in the editor.
|
||||
#
|
||||
# editorWidthInChars - A {Number} of characters.
|
||||
setEditorWidthInChars: (editorWidthInChars) ->
|
||||
if editorWidthInChars > 0
|
||||
previousWidthInChars = @editorWidthInChars
|
||||
@editorWidthInChars = editorWidthInChars
|
||||
if editorWidthInChars isnt previousWidthInChars and @isSoftWrapped()
|
||||
@resetDisplayLayer()
|
||||
|
||||
# Returns the editor width in characters for soft wrap.
|
||||
getEditorWidthInChars: ->
|
||||
width = @getWidth()
|
||||
if width? and @defaultCharWidth > 0
|
||||
Math.max(0, Math.floor(width / @defaultCharWidth))
|
||||
else
|
||||
@editorWidthInChars
|
||||
|
||||
indentLevelForLine: (line) ->
|
||||
@tokenizedBuffer.indentLevelForLine(line)
|
||||
|
||||
# Given starting and ending screen rows, this returns an array of the
|
||||
# buffer rows corresponding to every screen row in the range
|
||||
#
|
||||
# startScreenRow - The screen row {Number} to start at
|
||||
# endScreenRow - The screen row {Number} to end at (default: the last screen row)
|
||||
#
|
||||
# Returns an {Array} of buffer rows as {Numbers}s.
|
||||
bufferRowsForScreenRows: (startScreenRow, endScreenRow) ->
|
||||
for screenRow in [startScreenRow..endScreenRow]
|
||||
@bufferRowForScreenRow(screenRow)
|
||||
|
||||
# Creates a new fold between two row numbers.
|
||||
#
|
||||
# startRow - The row {Number} to start folding at
|
||||
# endRow - The row {Number} to end the fold
|
||||
#
|
||||
# Returns the new {Fold}.
|
||||
foldBufferRowRange: (startRow, endRow) ->
|
||||
@displayLayer.foldBufferRange(Range(Point(startRow, Infinity), Point(endRow, Infinity)))
|
||||
|
||||
isFoldedAtBufferRow: (bufferRow) ->
|
||||
@displayLayer.foldsIntersectingBufferRange(Range(Point(bufferRow, 0), Point(bufferRow, Infinity))).length > 0
|
||||
|
||||
isFoldedAtScreenRow: (screenRow) ->
|
||||
@isFoldedAtBufferRow(@bufferRowForScreenRow(screenRow))
|
||||
|
||||
isFoldableAtBufferRow: (row) ->
|
||||
@tokenizedBuffer.isFoldableAtRow(row)
|
||||
|
||||
# Removes any folds found that contain the given buffer row.
|
||||
#
|
||||
# bufferRow - The buffer row {Number} to check against
|
||||
unfoldBufferRow: (bufferRow) ->
|
||||
@displayLayer.destroyFoldsIntersectingBufferRange(Range(Point(bufferRow, 0), Point(bufferRow, Infinity)))
|
||||
|
||||
# Given a buffer row, this converts it into a screen row.
|
||||
#
|
||||
# bufferRow - A {Number} representing a buffer row
|
||||
#
|
||||
# Returns a {Number}.
|
||||
screenRowForBufferRow: (bufferRow) ->
|
||||
if @largeFileMode
|
||||
bufferRow
|
||||
else
|
||||
@displayLayer.translateScreenPosition(Point(screenRow, 0)).row
|
||||
|
||||
lastScreenRowForBufferRow: (bufferRow) ->
|
||||
if @largeFileMode
|
||||
bufferRow
|
||||
else
|
||||
@displayLayer.translateBufferPosition(Point(bufferRow, 0), clip: 'forward').row
|
||||
|
||||
# Given a screen row, this converts it into a buffer row.
|
||||
#
|
||||
# screenRow - A {Number} representing a screen row
|
||||
#
|
||||
# Returns a {Number}.
|
||||
bufferRowForScreenRow: (screenRow) ->
|
||||
@displayLayer.translateScreenPosition(Point(screenRow, 0)).row
|
||||
|
||||
# Given a buffer range, this converts it into a screen position.
|
||||
#
|
||||
# bufferRange - The {Range} to convert
|
||||
#
|
||||
# Returns a {Range}.
|
||||
screenRangeForBufferRange: (bufferRange, options) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
start = @screenPositionForBufferPosition(bufferRange.start, options)
|
||||
end = @screenPositionForBufferPosition(bufferRange.end, options)
|
||||
new Range(start, end)
|
||||
|
||||
# Given a screen range, this converts it into a buffer position.
|
||||
#
|
||||
# screenRange - The {Range} to convert
|
||||
#
|
||||
# Returns a {Range}.
|
||||
bufferRangeForScreenRange: (screenRange) ->
|
||||
screenRange = Range.fromObject(screenRange)
|
||||
start = @bufferPositionForScreenPosition(screenRange.start)
|
||||
end = @bufferPositionForScreenPosition(screenRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
# Gets the number of screen lines.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLineCount: ->
|
||||
@displayLayer.getScreenLineCount()
|
||||
|
||||
# Gets the number of the last screen line.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getLastRow: ->
|
||||
@getLineCount() - 1
|
||||
|
||||
# Given a buffer position, this converts it into a screen 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 of options with the following keys:
|
||||
# wrapBeyondNewlines:
|
||||
# wrapAtSoftNewlines:
|
||||
#
|
||||
# Returns a {Point}.
|
||||
screenPositionForBufferPosition: (bufferPosition, options) ->
|
||||
throw new Error("This TextEditor has been destroyed") if @isDestroyed()
|
||||
|
||||
@displayLayer.translateBufferPosition(bufferPosition, options)
|
||||
|
||||
# Given a buffer position, this converts it into a 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 of options with the following keys:
|
||||
# wrapBeyondNewlines:
|
||||
# wrapAtSoftNewlines:
|
||||
#
|
||||
# Returns a {Point}.
|
||||
bufferPositionForScreenPosition: (screenPosition, options) ->
|
||||
return @displayLayer.translateScreenPosition(screenPosition, options)
|
||||
|
||||
# Retrieves the grammar's token scopeDescriptor for a buffer position.
|
||||
#
|
||||
# bufferPosition - A {Point} in the {TextBuffer}
|
||||
#
|
||||
# Returns a {ScopeDescriptor}.
|
||||
scopeDescriptorForBufferPosition: (bufferPosition) ->
|
||||
@tokenizedBuffer.scopeDescriptorForPosition(bufferPosition)
|
||||
|
||||
bufferRangeForScopeAtPosition: (selector, position) ->
|
||||
@tokenizedBuffer.bufferRangeForScopeAtPosition(selector, position)
|
||||
|
||||
# Retrieves the grammar's token for a buffer position.
|
||||
#
|
||||
# bufferPosition - A {Point} in the {TextBuffer}.
|
||||
#
|
||||
# Returns a {Token}.
|
||||
tokenForBufferPosition: (bufferPosition) ->
|
||||
@tokenizedBuffer.tokenForPosition(bufferPosition)
|
||||
|
||||
# Get the grammar for this buffer.
|
||||
#
|
||||
# Returns the current {Grammar} or the {NullGrammar}.
|
||||
getGrammar: ->
|
||||
@tokenizedBuffer.grammar
|
||||
|
||||
# Sets the grammar for the buffer.
|
||||
#
|
||||
# grammar - Sets the new grammar rules
|
||||
setGrammar: (grammar) ->
|
||||
@tokenizedBuffer.setGrammar(grammar)
|
||||
|
||||
# Reloads the current grammar.
|
||||
reloadGrammar: ->
|
||||
@tokenizedBuffer.reloadGrammar()
|
||||
|
||||
# Given a position, this clips it to a real position.
|
||||
#
|
||||
# For example, if `position`'s row exceeds the row count of the buffer,
|
||||
# or if its column goes beyond a line's length, this "sanitizes" the value
|
||||
# to a real position.
|
||||
#
|
||||
# position - The {Point} to clip
|
||||
# options - A hash with the following values:
|
||||
# wrapBeyondNewlines: if `true`, continues wrapping past newlines
|
||||
# wrapAtSoftNewlines: if `true`, continues wrapping past soft newlines
|
||||
# skipSoftWrapIndentation: if `true`, skips soft wrap indentation without wrapping to the previous line
|
||||
# screenLine: if `true`, indicates that you're using a line number, not a row number
|
||||
#
|
||||
# Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
|
||||
clipScreenPosition: (screenPosition, options={}) ->
|
||||
@displayLayer.clipScreenPosition(screenPosition, options)
|
||||
|
||||
# Clip the start and end of the given range to valid positions on screen.
|
||||
# See {::clipScreenPosition} for more information.
|
||||
#
|
||||
# * `range` The {Range} to clip.
|
||||
# * `options` (optional) See {::clipScreenPosition} `options`.
|
||||
# Returns a {Range}.
|
||||
clipScreenRange: (range, options) ->
|
||||
start = @clipScreenPosition(range.start, options)
|
||||
end = @clipScreenPosition(range.end, options)
|
||||
|
||||
new Range(start, end)
|
||||
|
||||
# Calculates a {Range} representing the start of the {TextBuffer} until the end.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
rangeForAllLines: ->
|
||||
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
|
||||
|
||||
decorationForId: (id) ->
|
||||
@decorationsById[id]
|
||||
|
||||
getDecorations: (propertyFilter) ->
|
||||
allDecorations = []
|
||||
for markerId, decorations of @decorationsByMarkerId
|
||||
allDecorations.push(decorations...) if decorations?
|
||||
if propertyFilter?
|
||||
allDecorations = allDecorations.filter (decoration) ->
|
||||
for key, value of propertyFilter
|
||||
return false unless decoration.properties[key] is value
|
||||
true
|
||||
allDecorations
|
||||
|
||||
getLineDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line')
|
||||
|
||||
getLineNumberDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('line-number')
|
||||
|
||||
getHighlightDecorations: (propertyFilter) ->
|
||||
@getDecorations(propertyFilter).filter (decoration) -> decoration.isType('highlight')
|
||||
|
||||
getOverlayDecorations: (propertyFilter) ->
|
||||
result = []
|
||||
for id, decoration of @overlayDecorationsById
|
||||
result.push(decoration)
|
||||
if propertyFilter?
|
||||
result.filter (decoration) ->
|
||||
for key, value of propertyFilter
|
||||
return false unless decoration.properties[key] is value
|
||||
true
|
||||
else
|
||||
result
|
||||
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsByMarkerId = {}
|
||||
for marker in @findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow])
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
decorationsByMarkerId[marker.id] = decorations
|
||||
decorationsByMarkerId
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsState = {}
|
||||
|
||||
for layerId of @decorationCountsByLayerId
|
||||
layer = @getMarkerLayer(layerId)
|
||||
|
||||
for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow]) when marker.isValid()
|
||||
screenRange = marker.getScreenRange()
|
||||
rangeIsReversed = marker.isReversed()
|
||||
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
for decoration in decorations
|
||||
decorationsState[decoration.id] = {
|
||||
properties: decoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
if layerDecorations = @layerDecorationsByMarkerLayerId[layerId]
|
||||
for layerDecoration in layerDecorations
|
||||
decorationsState["#{layerDecoration.id}-#{marker.id}"] = {
|
||||
properties: layerDecoration.overridePropertiesByMarkerId[marker.id] ? layerDecoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
decorationsState
|
||||
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed()
|
||||
marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id)
|
||||
decoration = new Decoration(marker, this, decorationParams)
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
@decorationsByMarkerId[marker.id].push(decoration)
|
||||
@overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
|
||||
@decorationsById[decoration.id] = decoration
|
||||
@observeDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-add-decoration', decoration
|
||||
decoration
|
||||
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
decoration = new LayerDecoration(markerLayer, this, decorationParams)
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
|
||||
@observeDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
decoration
|
||||
|
||||
decorationsForMarkerId: (markerId) ->
|
||||
@decorationsByMarkerId[markerId]
|
||||
|
||||
# Retrieves a {DisplayMarker} based on its id.
|
||||
#
|
||||
# id - A {Number} representing a marker id
|
||||
#
|
||||
# Returns the {DisplayMarker} (if it exists).
|
||||
getMarker: (id) ->
|
||||
@defaultMarkerLayer.getMarker(id)
|
||||
|
||||
# Retrieves the active markers in the buffer.
|
||||
#
|
||||
# Returns an {Array} of existing {DisplayMarker}s.
|
||||
getMarkers: ->
|
||||
@defaultMarkerLayer.getMarkers()
|
||||
|
||||
getMarkerCount: ->
|
||||
@buffer.getMarkerCount()
|
||||
|
||||
# Public: Constructs a new marker at the given screen range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {DisplayMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenRange: (screenRange, options) ->
|
||||
@defaultMarkerLayer.markScreenRange(screenRange, options)
|
||||
|
||||
# Public: Constructs a new marker at the given buffer range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {DisplayMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferRange: (bufferRange, options) ->
|
||||
@defaultMarkerLayer.markBufferRange(bufferRange, options)
|
||||
|
||||
# Public: Constructs a new marker at the given screen position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {DisplayMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenPosition: (screenPosition, options) ->
|
||||
@defaultMarkerLayer.markScreenPosition(screenPosition, options)
|
||||
|
||||
# Public: Constructs a new marker at the given buffer position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {DisplayMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferPosition: (bufferPosition, options) ->
|
||||
@defaultMarkerLayer.markBufferPosition(bufferPosition, options)
|
||||
|
||||
# Finds the first marker satisfying the given attributes
|
||||
#
|
||||
# Refer to {DisplayBuffer::findMarkers} for details.
|
||||
#
|
||||
# Returns a {DisplayMarker} or null
|
||||
findMarker: (params) ->
|
||||
@defaultMarkerLayer.findMarkers(params)[0]
|
||||
|
||||
# Public: Find all markers satisfying a set of parameters.
|
||||
#
|
||||
# params - An {Object} containing parameters that all returned markers must
|
||||
# satisfy. Unreserved keys will be compared against the markers' custom
|
||||
# properties. There are also the following reserved keys with special
|
||||
# meaning for the query:
|
||||
# :startBufferRow - A {Number}. Only returns markers starting at this row in
|
||||
# buffer coordinates.
|
||||
# :endBufferRow - A {Number}. Only returns markers ending at this row in
|
||||
# buffer coordinates.
|
||||
# :containsBufferRange - A {Range} or range-compatible {Array}. Only returns
|
||||
# markers containing this range in buffer coordinates.
|
||||
# :containsBufferPosition - A {Point} or point-compatible {Array}. Only
|
||||
# returns markers containing this position in buffer coordinates.
|
||||
# :containedInBufferRange - A {Range} or range-compatible {Array}. Only
|
||||
# returns markers contained within this range.
|
||||
#
|
||||
# Returns an {Array} of {DisplayMarker}s
|
||||
findMarkers: (params) ->
|
||||
@defaultMarkerLayer.findMarkers(params)
|
||||
|
||||
addMarkerLayer: (options) ->
|
||||
@displayLayer.addMarkerLayer(options)
|
||||
|
||||
getMarkerLayer: (id) ->
|
||||
@displayLayer.getMarkerLayer(id)
|
||||
|
||||
getDefaultMarkerLayer: -> @defaultMarkerLayer
|
||||
|
||||
refreshMarkerScreenPositions: ->
|
||||
@defaultMarkerLayer.refreshMarkerScreenPositions()
|
||||
layer.refreshMarkerScreenPositions() for id, layer of @customMarkerLayersById
|
||||
return
|
||||
|
||||
destroyed: ->
|
||||
@displayLayer.destroy()
|
||||
@defaultMarkerLayer.destroy()
|
||||
@scopedConfigSubscriptions.dispose()
|
||||
@disposables.dispose()
|
||||
@tokenizedBuffer.destroy()
|
||||
|
||||
getRootScopeDescriptor: ->
|
||||
@tokenizedBuffer.rootScopeDescriptor
|
||||
|
||||
didCreateDefaultLayerMarker: (textBufferMarker) =>
|
||||
if marker = @getMarker(textBufferMarker.id)
|
||||
# The marker might have been removed in some other handler called before
|
||||
# this one. Only emit when the marker still exists.
|
||||
@emitter.emit 'did-create-marker', marker
|
||||
|
||||
scheduleUpdateDecorationsEvent: ->
|
||||
if @updatedSynchronously
|
||||
@emitter.emit 'did-update-decorations'
|
||||
return
|
||||
|
||||
unless @didUpdateDecorationsEventScheduled
|
||||
@didUpdateDecorationsEventScheduled = true
|
||||
process.nextTick =>
|
||||
@didUpdateDecorationsEventScheduled = false
|
||||
@emitter.emit 'did-update-decorations'
|
||||
|
||||
decorationDidChangeType: (decoration) ->
|
||||
if decoration.isType('overlay')
|
||||
@overlayDecorationsById[decoration.id] = decoration
|
||||
else
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
@unobserveDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
didDestroyLayerDecoration: (decoration) ->
|
||||
{markerLayer} = decoration
|
||||
return unless decorations = @layerDecorationsByMarkerLayerId[markerLayer.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @layerDecorationsByMarkerLayerId[markerLayer.id] if decorations.length is 0
|
||||
@unobserveDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
observeDecoratedLayer: (layer) ->
|
||||
@decorationCountsByLayerId[layer.id] ?= 0
|
||||
if ++@decorationCountsByLayerId[layer.id] is 1
|
||||
@layerUpdateDisposablesByLayerId[layer.id] = layer.onDidUpdate(@scheduleUpdateDecorationsEvent.bind(this))
|
||||
|
||||
unobserveDecoratedLayer: (layer) ->
|
||||
if --@decorationCountsByLayerId[layer.id] is 0
|
||||
@layerUpdateDisposablesByLayerId[layer.id].dispose()
|
||||
delete @decorationCountsByLayerId[layer.id]
|
||||
delete @layerUpdateDisposablesByLayerId[layer.id]
|
@ -131,7 +131,7 @@ class LanguageMode
|
||||
for currentRow in [bufferRow..0] by -1
|
||||
[startRow, endRow] = @rowRangeForFoldAtBufferRow(currentRow) ? []
|
||||
continue unless startRow? and startRow <= bufferRow <= endRow
|
||||
unless @editor.displayBuffer.isFoldedAtBufferRow(startRow)
|
||||
unless @editor.isFoldedAtBufferRow(startRow)
|
||||
return @editor.foldBufferRowRange(startRow, endRow)
|
||||
|
||||
# Find the row range for a fold at a given bufferRow. Will handle comments
|
||||
@ -146,19 +146,19 @@ class LanguageMode
|
||||
rowRange
|
||||
|
||||
rowRangeForCommentAtBufferRow: (bufferRow) ->
|
||||
return unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
return unless @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
|
||||
startRow = bufferRow
|
||||
endRow = bufferRow
|
||||
|
||||
if bufferRow > 0
|
||||
for currentRow in [bufferRow-1..0] by -1
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
startRow = currentRow
|
||||
|
||||
if bufferRow < @buffer.getLastRow()
|
||||
for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
break unless @editor.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
endRow = currentRow
|
||||
|
||||
return [startRow, endRow] if startRow isnt endRow
|
||||
@ -181,13 +181,13 @@ class LanguageMode
|
||||
[bufferRow, foldEndRow]
|
||||
|
||||
isFoldableAtBufferRow: (bufferRow) ->
|
||||
@editor.displayBuffer.tokenizedBuffer.isFoldableAtRow(bufferRow)
|
||||
@editor.tokenizedBuffer.isFoldableAtRow(bufferRow)
|
||||
|
||||
# Returns a {Boolean} indicating whether the line at the given buffer
|
||||
# row is a comment.
|
||||
isLineCommentedAtBufferRow: (bufferRow) ->
|
||||
return false unless 0 <= bufferRow <= @editor.getLastBufferRow()
|
||||
@editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
@editor.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
|
||||
# Find a row range for a 'paragraph' around specified bufferRow. A paragraph
|
||||
# is a block of text bounded by and empty line or a block of text that is not
|
||||
@ -240,11 +240,11 @@ class LanguageMode
|
||||
# Returns a {Number}.
|
||||
suggestedIndentForBufferRow: (bufferRow, options) ->
|
||||
line = @buffer.lineForRow(bufferRow)
|
||||
tokenizedLine = @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow)
|
||||
tokenizedLine = @editor.tokenizedBuffer.tokenizedLineForRow(bufferRow)
|
||||
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
|
||||
|
||||
suggestedIndentForLineAtBufferRow: (bufferRow, line, options) ->
|
||||
tokenizedLine = @editor.displayBuffer.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line)
|
||||
tokenizedLine = @editor.tokenizedBuffer.buildTokenizedLineForRowWithText(bufferRow, line)
|
||||
@suggestedIndentForTokenizedLineAtBufferRow(bufferRow, line, tokenizedLine, options)
|
||||
|
||||
suggestedIndentForTokenizedLineAtBufferRow: (bufferRow, line, tokenizedLine, options) ->
|
||||
|
@ -7,7 +7,7 @@ nextId = -> idCounter++
|
||||
# layer. Created via {TextEditor::decorateMarkerLayer}.
|
||||
module.exports =
|
||||
class LayerDecoration
|
||||
constructor: (@markerLayer, @displayBuffer, @properties) ->
|
||||
constructor: (@markerLayer, @decorationManager, @properties) ->
|
||||
@id = nextId()
|
||||
@destroyed = false
|
||||
@markerLayerDestroyedDisposable = @markerLayer.onDidDestroy => @destroy()
|
||||
@ -19,7 +19,7 @@ class LayerDecoration
|
||||
@markerLayerDestroyedDisposable.dispose()
|
||||
@markerLayerDestroyedDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyLayerDecoration(this)
|
||||
@decorationManager.didDestroyLayerDecoration(this)
|
||||
|
||||
# Essential: Determine whether this decoration is destroyed.
|
||||
#
|
||||
@ -44,7 +44,7 @@ class LayerDecoration
|
||||
setProperties: (newProperties) ->
|
||||
return if @destroyed
|
||||
@properties = newProperties
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
|
||||
# Essential: Override the decoration properties for a specific marker.
|
||||
#
|
||||
@ -58,4 +58,4 @@ class LayerDecoration
|
||||
@overridePropertiesByMarkerId[marker.id] = properties
|
||||
else
|
||||
delete @overridePropertiesByMarkerId[marker.id]
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@decorationManager.scheduleUpdateDecorationsEvent()
|
||||
|
@ -1,9 +1,9 @@
|
||||
module.exports =
|
||||
class MarkerObservationWindow
|
||||
constructor: (@displayBuffer, @bufferWindow) ->
|
||||
constructor: (@decorationManager, @bufferWindow) ->
|
||||
|
||||
setScreenRange: (range) ->
|
||||
@bufferWindow.setRange(@displayBuffer.bufferRangeForScreenRange(range))
|
||||
@bufferWindow.setRange(@decorationManager.bufferRangeForScreenRange(range))
|
||||
|
||||
setBufferRange: (range) ->
|
||||
@bufferWindow.setRange(range)
|
||||
|
@ -4,7 +4,8 @@ Grim = require 'grim'
|
||||
{CompositeDisposable, Emitter} = require 'event-kit'
|
||||
{Point, Range} = TextBuffer = require 'text-buffer'
|
||||
LanguageMode = require './language-mode'
|
||||
DisplayBuffer = require './display-buffer'
|
||||
DecorationManager = require './decoration-manager'
|
||||
TokenizedBuffer = require './tokenized-buffer'
|
||||
Cursor = require './cursor'
|
||||
Model = require './model'
|
||||
Selection = require './selection'
|
||||
@ -12,6 +13,9 @@ TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
{Directory} = require "pathwatcher"
|
||||
GutterContainer = require './gutter-container'
|
||||
TextEditorElement = require './text-editor-element'
|
||||
{isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isWrapBoundary} = require './text-utils'
|
||||
|
||||
ZERO_WIDTH_NBSP = '\ufeff'
|
||||
|
||||
# Essential: This class represents all essential editing state for a single
|
||||
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
|
||||
@ -63,21 +67,30 @@ class TextEditor extends Model
|
||||
selectionFlashDuration: 500
|
||||
gutterContainer: null
|
||||
editorElement: null
|
||||
verticalScrollMargin: 2
|
||||
horizontalScrollMargin: 6
|
||||
softWrapped: null
|
||||
editorWidthInChars: null
|
||||
lineHeightInPixels: null
|
||||
defaultCharWidth: null
|
||||
height: null
|
||||
width: null
|
||||
|
||||
Object.defineProperty @prototype, "element",
|
||||
get: -> @getElement()
|
||||
|
||||
@deserialize: (state, atomEnvironment) ->
|
||||
try
|
||||
displayBuffer = DisplayBuffer.deserialize(state.displayBuffer, atomEnvironment)
|
||||
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer, atomEnvironment)
|
||||
catch error
|
||||
if error.syscall is 'read'
|
||||
return # Error reading the file, don't deserialize an editor for it
|
||||
else
|
||||
throw error
|
||||
|
||||
state.displayBuffer = displayBuffer
|
||||
state.selectionsMarkerLayer = displayBuffer.getMarkerLayer(state.selectionsMarkerLayerId)
|
||||
state.buffer = state.tokenizedBuffer.buffer
|
||||
state.displayLayer = state.buffer.getDisplayLayer(state.displayLayerId)
|
||||
state.selectionsMarkerLayer = state.displayLayer.getMarkerLayer(state.selectionsMarkerLayerId)
|
||||
state.config = atomEnvironment.config
|
||||
state.notificationManager = atomEnvironment.notifications
|
||||
state.packageManager = atomEnvironment.packages
|
||||
@ -97,13 +110,19 @@ class TextEditor extends Model
|
||||
super
|
||||
|
||||
{
|
||||
@softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, tabLength,
|
||||
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
|
||||
@softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, @tabLength,
|
||||
@softWrapped, @decorationManager, @selectionsMarkerLayer, @buffer, suppressCursorCreation,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, @largeFileMode, @config,
|
||||
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
|
||||
@project, @assert, @applicationDelegate, grammar, showInvisibles, @autoHeight, @scrollPastEnd
|
||||
@project, @assert, @applicationDelegate, grammar, @showInvisibles, @autoHeight, @scrollPastEnd
|
||||
} = params
|
||||
|
||||
{
|
||||
tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, @ignoreInvisibles,
|
||||
@largeFileMode, @config, @assert, @grammarRegistry, @packageManager, @displayLayer
|
||||
} = params
|
||||
|
||||
|
||||
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
|
||||
throw new Error("Must pass a notificationManager parameter when constructing TextEditors") unless @notificationManager?
|
||||
throw new Error("Must pass a packageManager parameter when constructing TextEditors") unless @packageManager?
|
||||
@ -124,17 +143,23 @@ class TextEditor extends Model
|
||||
@scrollPastEnd ?= true
|
||||
@hasTerminatedPendingState = false
|
||||
|
||||
showInvisibles ?= true
|
||||
@showInvisibles ?= true
|
||||
|
||||
buffer ?= new TextBuffer
|
||||
@displayBuffer ?= new DisplayBuffer({
|
||||
buffer, tabLength, softWrapped, ignoreInvisibles: @mini or not showInvisibles, largeFileMode,
|
||||
@config, @assert, @grammarRegistry, @packageManager
|
||||
@buffer ?= new TextBuffer
|
||||
@tokenizedBuffer ?= new TokenizedBuffer({
|
||||
tabLength, @buffer, @largeFileMode, @config,
|
||||
@grammarRegistry, @packageManager, @assert
|
||||
})
|
||||
{@buffer, @displayLayer} = @displayBuffer
|
||||
@displayLayer ?= @buffer.addDisplayLayer()
|
||||
@displayLayer.setTextDecorationLayer(@tokenizedBuffer)
|
||||
@defaultMarkerLayer = @displayLayer.addMarkerLayer()
|
||||
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true)
|
||||
|
||||
@decorationManager = new DecorationManager(@displayLayer, @defaultMarkerLayer)
|
||||
|
||||
@decorateMarkerLayer(@displayLayer.foldsMarkerLayer, {type: 'line-number', class: 'folded'})
|
||||
|
||||
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true)
|
||||
@disposables.add @tokenizedBuffer.observeGrammar @subscribeToScopedConfigSettings
|
||||
|
||||
for marker in @selectionsMarkerLayer.getMarkers()
|
||||
marker.setProperties(preserveFolds: true)
|
||||
@ -142,7 +167,7 @@ class TextEditor extends Model
|
||||
|
||||
@subscribeToTabTypeConfig()
|
||||
@subscribeToBuffer()
|
||||
@subscribeToDisplayBuffer()
|
||||
@subscribeToDisplayLayer()
|
||||
|
||||
if @cursors.length is 0 and not suppressCursorCreation
|
||||
initialLine = Math.max(parseInt(initialLine) or 0, 0)
|
||||
@ -168,8 +193,12 @@ class TextEditor extends Model
|
||||
softTabs: @softTabs
|
||||
firstVisibleScreenRow: @getFirstVisibleScreenRow()
|
||||
firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
|
||||
displayBuffer: @displayBuffer.serialize()
|
||||
selectionsMarkerLayerId: @selectionsMarkerLayer.id
|
||||
softWrapped: @isSoftWrapped()
|
||||
editorWidthInChars: @editorWidthInChars
|
||||
tokenizedBuffer: @tokenizedBuffer.serialize()
|
||||
largeFileMode: @largeFileMode
|
||||
displayLayerId: @displayLayer.id
|
||||
registered: atom.textEditors.editors.has this
|
||||
|
||||
subscribeToBuffer: ->
|
||||
@ -194,10 +223,26 @@ class TextEditor extends Model
|
||||
onDidTerminatePendingState: (callback) ->
|
||||
@emitter.on 'did-terminate-pending-state', callback
|
||||
|
||||
subscribeToDisplayBuffer: ->
|
||||
subscribeToScopedConfigSettings: =>
|
||||
@scopedConfigSubscriptions?.dispose()
|
||||
@scopedConfigSubscriptions = subscriptions = new CompositeDisposable
|
||||
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
subscriptions.add @config.onDidChange 'editor.tabLength', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.invisibles', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.showInvisibles', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.showIndentGuide', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.softWrap', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.softWrapHangingIndent', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.softWrapAtPreferredLineLength', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
subscriptions.add @config.onDidChange 'editor.preferredLineLength', scope: scopeDescriptor, @resetDisplayLayer.bind(this)
|
||||
|
||||
@resetDisplayLayer()
|
||||
|
||||
subscribeToDisplayLayer: ->
|
||||
@disposables.add @selectionsMarkerLayer.onDidCreateMarker @addSelection.bind(this)
|
||||
@disposables.add @displayBuffer.onDidChangeGrammar @handleGrammarChange.bind(this)
|
||||
@disposables.add @displayBuffer.onDidTokenize @handleTokenization.bind(this)
|
||||
@disposables.add @tokenizedBuffer.onDidChangeGrammar @handleGrammarChange.bind(this)
|
||||
@disposables.add @tokenizedBuffer.onDidTokenize @handleTokenization.bind(this)
|
||||
@disposables.add @displayLayer.onDidChangeSync (e) =>
|
||||
@mergeIntersectingSelections()
|
||||
@emitter.emit 'did-change', e
|
||||
@ -207,13 +252,27 @@ class TextEditor extends Model
|
||||
@tabTypeSubscription = @config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), =>
|
||||
@softTabs = @shouldUseSoftTabs(defaultValue: @softTabs)
|
||||
|
||||
resetDisplayLayer: ->
|
||||
@displayLayer.reset({
|
||||
invisibles: @getInvisibles(),
|
||||
softWrapColumn: @getSoftWrapColumn(),
|
||||
showIndentGuides: @config.get('editor.showIndentGuide', scope: @getRootScopeDescriptor()),
|
||||
tabLength: @getTabLength(),
|
||||
ratioForCharacter: @ratioForCharacter.bind(this),
|
||||
isWrapBoundary: isWrapBoundary,
|
||||
foldCharacter: ZERO_WIDTH_NBSP
|
||||
})
|
||||
|
||||
destroyed: ->
|
||||
@disposables.dispose()
|
||||
@displayLayer.destroy()
|
||||
@scopedConfigSubscriptions.dispose()
|
||||
@disposables.dispose()
|
||||
@tokenizedBuffer.destroy()
|
||||
@tabTypeSubscription.dispose()
|
||||
selection.destroy() for selection in @selections.slice()
|
||||
@selectionsMarkerLayer.destroy()
|
||||
@buffer.release()
|
||||
@displayBuffer.destroy()
|
||||
@languageMode.destroy()
|
||||
@gutterContainer.destroy()
|
||||
@emitter.emit 'did-destroy'
|
||||
@ -297,7 +356,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidChangeSoftWrapped: (callback) ->
|
||||
@displayBuffer.onDidChangeSoftWrapped(callback)
|
||||
@emitter.on 'did-change-soft-wrapped', callback
|
||||
|
||||
# Extended: Calls your `callback` when the buffer's encoding has changed.
|
||||
#
|
||||
@ -451,7 +510,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observeDecorations: (callback) ->
|
||||
@displayBuffer.observeDecorations(callback)
|
||||
@decorationManager.observeDecorations(callback)
|
||||
|
||||
# Extended: Calls your `callback` when a {Decoration} is added to the editor.
|
||||
#
|
||||
@ -460,7 +519,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidAddDecoration: (callback) ->
|
||||
@displayBuffer.onDidAddDecoration(callback)
|
||||
@decorationManager.onDidAddDecoration(callback)
|
||||
|
||||
# Extended: Calls your `callback` when a {Decoration} is removed from the editor.
|
||||
#
|
||||
@ -469,7 +528,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidRemoveDecoration: (callback) ->
|
||||
@displayBuffer.onDidRemoveDecoration(callback)
|
||||
@decorationManager.onDidRemoveDecoration(callback)
|
||||
|
||||
# Extended: Calls your `callback` when the placeholder text is changed.
|
||||
#
|
||||
@ -480,9 +539,6 @@ class TextEditor extends Model
|
||||
onDidChangePlaceholderText: (callback) ->
|
||||
@emitter.on 'did-change-placeholder-text', callback
|
||||
|
||||
onDidChangeCharacterWidths: (callback) ->
|
||||
@displayBuffer.onDidChangeCharacterWidths(callback)
|
||||
|
||||
onDidChangeFirstVisibleScreenRow: (callback, fromView) ->
|
||||
@emitter.on 'did-change-first-visible-screen-row', callback
|
||||
|
||||
@ -497,17 +553,14 @@ class TextEditor extends Model
|
||||
@viewRegistry.getView(this).onDidChangeScrollLeft(callback)
|
||||
|
||||
onDidRequestAutoscroll: (callback) ->
|
||||
@displayBuffer.onDidRequestAutoscroll(callback)
|
||||
@emitter.on 'did-request-autoscroll', callback
|
||||
|
||||
# TODO Remove once the tabs package no longer uses .on subscriptions
|
||||
onDidChangeIcon: (callback) ->
|
||||
@emitter.on 'did-change-icon', callback
|
||||
|
||||
onDidUpdateMarkers: (callback) ->
|
||||
@displayBuffer.onDidUpdateMarkers(callback)
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@displayBuffer.onDidUpdateDecorations(callback)
|
||||
@decorationManager.onDidUpdateDecorations(callback)
|
||||
|
||||
# Essential: Retrieves the current {TextBuffer}.
|
||||
getBuffer: -> @buffer
|
||||
@ -517,31 +570,31 @@ class TextEditor extends Model
|
||||
|
||||
# Create an {TextEditor} with its initial state based on this object
|
||||
copy: ->
|
||||
displayBuffer = @displayBuffer.copy()
|
||||
selectionsMarkerLayer = displayBuffer.getMarkerLayer(@buffer.getMarkerLayer(@selectionsMarkerLayer.id).copy().id)
|
||||
selectionsMarkerLayer = @getMarkerLayer(@buffer.getMarkerLayer(@selectionsMarkerLayer.id).copy().id)
|
||||
softTabs = @getSoftTabs()
|
||||
newEditor = new TextEditor({
|
||||
@buffer, displayBuffer, selectionsMarkerLayer, @tabLength, softTabs,
|
||||
@buffer, selectionsMarkerLayer, @tabLength, softTabs,
|
||||
suppressCursorCreation: true, @config, @notificationManager, @packageManager,
|
||||
@firstVisibleScreenRow, @firstVisibleScreenColumn,
|
||||
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
|
||||
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate,
|
||||
displayLayer: @buffer.copyDisplayLayer(@displayLayer.id)
|
||||
})
|
||||
newEditor
|
||||
|
||||
# Controls visibility based on the given {Boolean}.
|
||||
setVisible: (visible) -> @displayBuffer.setVisible(visible)
|
||||
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
|
||||
|
||||
setMini: (mini) ->
|
||||
if mini isnt @mini
|
||||
@mini = mini
|
||||
@displayBuffer.setIgnoreInvisibles(@mini)
|
||||
@setIgnoreInvisibles(@mini)
|
||||
@emitter.emit 'did-change-mini', @mini
|
||||
@mini
|
||||
|
||||
isMini: -> @mini
|
||||
|
||||
setUpdatedSynchronously: (updatedSynchronously) ->
|
||||
@displayBuffer.setUpdatedSynchronously(updatedSynchronously)
|
||||
@decorationManager.setUpdatedSynchronously(updatedSynchronously)
|
||||
|
||||
onDidChangeMini: (callback) ->
|
||||
@emitter.on 'did-change-mini', callback
|
||||
@ -594,12 +647,19 @@ class TextEditor extends Model
|
||||
# * `editorWidthInChars` A {Number} representing the width of the
|
||||
# {TextEditorElement} in characters.
|
||||
setEditorWidthInChars: (editorWidthInChars) ->
|
||||
@displayBuffer.setEditorWidthInChars(editorWidthInChars)
|
||||
if editorWidthInChars > 0
|
||||
previousWidthInChars = @editorWidthInChars
|
||||
@editorWidthInChars = editorWidthInChars
|
||||
if editorWidthInChars isnt previousWidthInChars and @isSoftWrapped()
|
||||
@resetDisplayLayer()
|
||||
|
||||
# Returns the editor width in characters.
|
||||
getEditorWidthInChars: ->
|
||||
@displayBuffer.getEditorWidthInChars()
|
||||
|
||||
width = @getWidth()
|
||||
if width? and @defaultCharWidth > 0
|
||||
Math.max(0, Math.floor(width / @defaultCharWidth))
|
||||
else
|
||||
@editorWidthInChars
|
||||
|
||||
###
|
||||
Section: File Details
|
||||
@ -788,16 +848,21 @@ class TextEditor extends Model
|
||||
return if screenRow < 0 or screenRow > @getLastScreenRow()
|
||||
@displayLayer.getScreenLines(screenRow, screenRow + 1)[0]
|
||||
|
||||
bufferRowForScreenRow: (row) -> @displayLayer.translateScreenPosition(Point(row, 0)).row
|
||||
bufferRowForScreenRow: (screenRow) ->
|
||||
@displayLayer.translateScreenPosition(Point(screenRow, 0)).row
|
||||
|
||||
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
|
||||
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
|
||||
bufferRowsForScreenRows: (startScreenRow, endScreenRow) ->
|
||||
for screenRow in [startScreenRow..endScreenRow]
|
||||
@bufferRowForScreenRow(screenRow)
|
||||
|
||||
screenRowForBufferRow: (row) -> @displayLayer.translateBufferPosition(Point(row, 0)).row
|
||||
screenRowForBufferRow: (row) ->
|
||||
if @largeFileMode
|
||||
bufferRow
|
||||
else
|
||||
@displayLayer.translateScreenPosition(Point(screenRow, 0)).row
|
||||
|
||||
getRightmostScreenPosition: -> @displayLayer.getRightmostScreenPosition()
|
||||
|
||||
# {Delegates to: DisplayBuffer.getMaxLineLength}
|
||||
getMaxScreenLineLength: -> @getRightmostScreenPosition().column
|
||||
|
||||
getLongestScreenRow: -> @getRightmostScreenPosition().row
|
||||
@ -1342,7 +1407,10 @@ class TextEditor extends Model
|
||||
# * `options` (optional) An options hash for {::clipScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
screenPositionForBufferPosition: (bufferPosition, options) -> @displayLayer.translateBufferPosition(bufferPosition, options)
|
||||
screenPositionForBufferPosition: (bufferPosition, options) ->
|
||||
throw new Error("This TextEditor has been destroyed") if @isDestroyed()
|
||||
|
||||
@displayLayer.translateBufferPosition(bufferPosition, options)
|
||||
|
||||
# Essential: Convert a position in screen-coordinates to buffer-coordinates.
|
||||
#
|
||||
@ -1352,21 +1420,30 @@ class TextEditor extends Model
|
||||
# * `options` (optional) An options hash for {::clipScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
bufferPositionForScreenPosition: (screenPosition, options) -> @displayLayer.translateScreenPosition(screenPosition, options)
|
||||
bufferPositionForScreenPosition: (screenPosition, options) ->
|
||||
@displayLayer.translateScreenPosition(screenPosition, options)
|
||||
|
||||
# Essential: Convert a range in buffer-coordinates to screen-coordinates.
|
||||
#
|
||||
# * `bufferRange` {Range} in buffer coordinates to translate into screen coordinates.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
screenRangeForBufferRange: (bufferRange) -> @displayLayer.translateBufferRange(bufferRange)
|
||||
screenRangeForBufferRange: (bufferRange, options) ->
|
||||
bufferRange = Range.fromObject(bufferRange)
|
||||
start = @screenPositionForBufferPosition(bufferRange.start, options)
|
||||
end = @screenPositionForBufferPosition(bufferRange.end, options)
|
||||
new Range(start, end)
|
||||
|
||||
# Essential: Convert a range in screen-coordinates to buffer-coordinates.
|
||||
#
|
||||
# * `screenRange` {Range} in screen coordinates to translate into buffer coordinates.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
bufferRangeForScreenRange: (screenRange) -> @displayLayer.translateScreenRange(screenRange)
|
||||
bufferRangeForScreenRange: (screenRange) ->
|
||||
screenRange = Range.fromObject(screenRange)
|
||||
start = @bufferPositionForScreenPosition(screenRange.start)
|
||||
end = @bufferPositionForScreenPosition(screenRange.end)
|
||||
new Range(start, end)
|
||||
|
||||
# Extended: Clip the given {Point} to a valid position in the buffer.
|
||||
#
|
||||
@ -1510,7 +1587,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Decoration} object
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
@displayBuffer.decorateMarker(marker, decorationParams)
|
||||
@decorationManager.decorateMarker(marker, decorationParams)
|
||||
|
||||
# Essential: *Experimental:* Add a decoration to every marker in the given
|
||||
# marker layer. Can be used to decorate a large number of markers without
|
||||
@ -1524,7 +1601,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {LayerDecoration}.
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
@displayBuffer.decorateMarkerLayer(markerLayer, decorationParams)
|
||||
@decorationManager.decorateMarkerLayer(markerLayer, decorationParams)
|
||||
|
||||
# Deprecated: Get all the decorations within a screen row range on the default
|
||||
# layer.
|
||||
@ -1538,10 +1615,10 @@ class TextEditor extends Model
|
||||
# params objects attached to the marker.
|
||||
# Returns an empty object when no decorations are found
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow)
|
||||
@decorationManager.decorationsForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsStateForScreenRowRange(startScreenRow, endScreenRow)
|
||||
@decorationManager.decorationsStateForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
# Extended: Get all decorations.
|
||||
#
|
||||
@ -1550,7 +1627,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns an {Array} of {Decoration}s.
|
||||
getDecorations: (propertyFilter) ->
|
||||
@displayBuffer.getDecorations(propertyFilter)
|
||||
@decorationManager.getDecorations(propertyFilter)
|
||||
|
||||
# Extended: Get all decorations of type 'line'.
|
||||
#
|
||||
@ -1559,7 +1636,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns an {Array} of {Decoration}s.
|
||||
getLineDecorations: (propertyFilter) ->
|
||||
@displayBuffer.getLineDecorations(propertyFilter)
|
||||
@decorationManager.getLineDecorations(propertyFilter)
|
||||
|
||||
# Extended: Get all decorations of type 'line-number'.
|
||||
#
|
||||
@ -1568,7 +1645,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns an {Array} of {Decoration}s.
|
||||
getLineNumberDecorations: (propertyFilter) ->
|
||||
@displayBuffer.getLineNumberDecorations(propertyFilter)
|
||||
@decorationManager.getLineNumberDecorations(propertyFilter)
|
||||
|
||||
# Extended: Get all decorations of type 'highlight'.
|
||||
#
|
||||
@ -1577,7 +1654,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns an {Array} of {Decoration}s.
|
||||
getHighlightDecorations: (propertyFilter) ->
|
||||
@displayBuffer.getHighlightDecorations(propertyFilter)
|
||||
@decorationManager.getHighlightDecorations(propertyFilter)
|
||||
|
||||
# Extended: Get all decorations of type 'overlay'.
|
||||
#
|
||||
@ -1586,13 +1663,13 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns an {Array} of {Decoration}s.
|
||||
getOverlayDecorations: (propertyFilter) ->
|
||||
@displayBuffer.getOverlayDecorations(propertyFilter)
|
||||
@decorationManager.getOverlayDecorations(propertyFilter)
|
||||
|
||||
decorationForId: (id) ->
|
||||
@displayBuffer.decorationForId(id)
|
||||
@decorationManager.decorationForId(id)
|
||||
|
||||
decorationsForMarkerId: (id) ->
|
||||
@displayBuffer.decorationsForMarkerId(id)
|
||||
@decorationManager.decorationsForMarkerId(id)
|
||||
|
||||
###
|
||||
Section: Markers
|
||||
@ -1630,8 +1707,8 @@ class TextEditor extends Model
|
||||
# start or start at the marker's end. This is the most fragile strategy.
|
||||
#
|
||||
# Returns a {DisplayMarker}.
|
||||
markBufferRange: (args...) ->
|
||||
@displayBuffer.markBufferRange(args...)
|
||||
markBufferRange: (bufferRange, options) ->
|
||||
@defaultMarkerLayer.markBufferRange(bufferRange, options)
|
||||
|
||||
# Essential: Create a marker on the default marker layer with the given range
|
||||
# in screen coordinates. This marker will maintain its logical location as the
|
||||
@ -1665,8 +1742,8 @@ class TextEditor extends Model
|
||||
# start or start at the marker's end. This is the most fragile strategy.
|
||||
#
|
||||
# Returns a {DisplayMarker}.
|
||||
markScreenRange: (args...) ->
|
||||
@displayBuffer.markScreenRange(args...)
|
||||
markScreenRange: (screenRange, options) ->
|
||||
@defaultMarkerLayer.markScreenRange(screenRange, options)
|
||||
|
||||
# Essential: Mark the given position in buffer coordinates on the default
|
||||
# marker layer.
|
||||
@ -1675,8 +1752,8 @@ class TextEditor extends Model
|
||||
# * `options` (optional) See {TextBuffer::markRange}.
|
||||
#
|
||||
# Returns a {DisplayMarker}.
|
||||
markBufferPosition: (args...) ->
|
||||
@displayBuffer.markBufferPosition(args...)
|
||||
markBufferPosition: (bufferPosition, options) ->
|
||||
@defaultMarkerLayer.markBufferPosition(bufferPosition, options)
|
||||
|
||||
# Essential: Mark the given position in screen coordinates on the default
|
||||
# marker layer.
|
||||
@ -1685,8 +1762,8 @@ class TextEditor extends Model
|
||||
# * `options` (optional) See {TextBuffer::markRange}.
|
||||
#
|
||||
# Returns a {DisplayMarker}.
|
||||
markScreenPosition: (args...) ->
|
||||
@displayBuffer.markScreenPosition(args...)
|
||||
markScreenPosition: (screenPosition, options) ->
|
||||
@defaultMarkerLayer.markScreenPosition(screenPosition, options)
|
||||
|
||||
# Essential: Find all {DisplayMarker}s on the default marker layer that
|
||||
# match the given properties.
|
||||
@ -1708,20 +1785,22 @@ class TextEditor extends Model
|
||||
# in range-compatible {Array} in buffer coordinates.
|
||||
# * `containsBufferPosition` Only include markers containing this {Point}
|
||||
# or {Array} of `[row, column]` in buffer coordinates.
|
||||
findMarkers: (properties) ->
|
||||
@displayBuffer.findMarkers(properties)
|
||||
#
|
||||
# Returns an {Array} of {DisplayMarker}s
|
||||
findMarkers: (params) ->
|
||||
@defaultMarkerLayer.findMarkers(params)
|
||||
|
||||
# Extended: Get the {DisplayMarker} on the default layer for the given
|
||||
# marker id.
|
||||
#
|
||||
# * `id` {Number} id of the marker
|
||||
getMarker: (id) ->
|
||||
@displayBuffer.getMarker(id)
|
||||
@defaultMarkerLayer.getMarker(id)
|
||||
|
||||
# Extended: Get all {DisplayMarker}s on the default marker layer. Consider
|
||||
# using {::findMarkers}
|
||||
getMarkers: ->
|
||||
@displayBuffer.getMarkers()
|
||||
@defaultMarkerLayer.getMarkers()
|
||||
|
||||
# Extended: Get the number of markers in the default marker layer.
|
||||
#
|
||||
@ -1742,7 +1821,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {DisplayMarkerLayer}.
|
||||
addMarkerLayer: (options) ->
|
||||
@displayBuffer.addMarkerLayer(options)
|
||||
@displayLayer.addMarkerLayer(options)
|
||||
|
||||
# Public: *Experimental:* Get a {DisplayMarkerLayer} by id.
|
||||
#
|
||||
@ -1753,7 +1832,7 @@ class TextEditor extends Model
|
||||
# Returns a {MarkerLayer} or `undefined` if no layer exists with the given
|
||||
# id.
|
||||
getMarkerLayer: (id) ->
|
||||
@displayBuffer.getMarkerLayer(id)
|
||||
@displayLayer.getMarkerLayer(id)
|
||||
|
||||
# Public: *Experimental:* Get the default {DisplayMarkerLayer}.
|
||||
#
|
||||
@ -1764,7 +1843,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {DisplayMarkerLayer}.
|
||||
getDefaultMarkerLayer: ->
|
||||
@displayBuffer.getDefaultMarkerLayer()
|
||||
@defaultMarkerLayer
|
||||
|
||||
###
|
||||
Section: Cursors
|
||||
@ -2576,14 +2655,36 @@ class TextEditor extends Model
|
||||
# Essential: Get the on-screen length of tab characters.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getTabLength: -> @displayBuffer.getTabLength()
|
||||
getTabLength: ->
|
||||
if @tabLength?
|
||||
@tabLength
|
||||
else
|
||||
@config.get('editor.tabLength', scope: @getRootScopeDescriptor())
|
||||
|
||||
# Essential: Set the on-screen length of tab characters. Setting this to a
|
||||
# {Number} This will override the `editor.tabLength` setting.
|
||||
#
|
||||
# * `tabLength` {Number} length of a single tab. Setting to `null` will
|
||||
# fallback to using the `editor.tabLength` config setting
|
||||
setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength)
|
||||
setTabLength: (tabLength) ->
|
||||
return if tabLength is @tabLength
|
||||
|
||||
@tabLength = tabLength
|
||||
@tokenizedBuffer.setTabLength(@tabLength)
|
||||
@resetDisplayLayer()
|
||||
|
||||
setIgnoreInvisibles: (ignoreInvisibles) ->
|
||||
return if ignoreInvisibles is @ignoreInvisibles
|
||||
|
||||
@ignoreInvisibles = ignoreInvisibles
|
||||
@resetDisplayLayer()
|
||||
|
||||
getInvisibles: ->
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
if @config.get('editor.showInvisibles', scope: scopeDescriptor) and not @ignoreInvisibles and @showInvisibles
|
||||
@config.get('editor.invisibles', scope: scopeDescriptor)
|
||||
else
|
||||
{}
|
||||
|
||||
# Extended: Determine if the buffer uses hard or soft tabs.
|
||||
#
|
||||
@ -2594,7 +2695,7 @@ class TextEditor extends Model
|
||||
# whitespace.
|
||||
usesSoftTabs: ->
|
||||
for bufferRow in [0..@buffer.getLastRow()]
|
||||
continue if @displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
continue if @tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
|
||||
|
||||
line = @buffer.lineForRow(bufferRow)
|
||||
return true if line[0] is ' '
|
||||
@ -2637,14 +2738,27 @@ class TextEditor extends Model
|
||||
# Essential: Determine whether lines in this editor are soft-wrapped.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isSoftWrapped: (softWrapped) -> @displayBuffer.isSoftWrapped()
|
||||
isSoftWrapped: ->
|
||||
if @largeFileMode
|
||||
false
|
||||
else
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
@softWrapped ? @config.get('editor.softWrap', scope: scopeDescriptor) ? false
|
||||
|
||||
# Essential: Enable or disable soft wrapping for this editor.
|
||||
#
|
||||
# * `softWrapped` A {Boolean}
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
setSoftWrapped: (softWrapped) -> @displayBuffer.setSoftWrapped(softWrapped)
|
||||
setSoftWrapped: (softWrapped) ->
|
||||
if softWrapped isnt @softWrapped
|
||||
@softWrapped = softWrapped
|
||||
@resetDisplayLayer()
|
||||
softWrapped = @isSoftWrapped()
|
||||
@emitter.emit 'did-change-soft-wrapped', softWrapped
|
||||
softWrapped
|
||||
else
|
||||
@isSoftWrapped()
|
||||
|
||||
# Essential: Toggle soft wrapping for this editor
|
||||
#
|
||||
@ -2652,7 +2766,15 @@ class TextEditor extends Model
|
||||
toggleSoftWrapped: -> @setSoftWrapped(not @isSoftWrapped())
|
||||
|
||||
# Essential: Gets the column at which column will soft wrap
|
||||
getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()
|
||||
getSoftWrapColumn: ->
|
||||
scopeDescriptor = @getRootScopeDescriptor()
|
||||
if @isSoftWrapped()
|
||||
if @config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
|
||||
@config.get('editor.preferredLineLength', scope: scopeDescriptor)
|
||||
else
|
||||
@getEditorWidthInChars()
|
||||
else
|
||||
Infinity
|
||||
|
||||
###
|
||||
Section: Indentation
|
||||
@ -2710,7 +2832,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Number}.
|
||||
indentLevelForLine: (line) ->
|
||||
@displayBuffer.indentLevelForLine(line)
|
||||
@tokenizedBuffer.indentLevelForLine(line)
|
||||
|
||||
# Extended: Indent rows intersecting selections based on the grammar's suggested
|
||||
# indent level.
|
||||
@ -2738,7 +2860,7 @@ class TextEditor extends Model
|
||||
|
||||
# Essential: Get the current {Grammar} of this editor.
|
||||
getGrammar: ->
|
||||
@displayBuffer.getGrammar()
|
||||
@tokenizedBuffer.grammar
|
||||
|
||||
# Essential: Set the current {Grammar} of this editor.
|
||||
#
|
||||
@ -2747,11 +2869,11 @@ class TextEditor extends Model
|
||||
#
|
||||
# * `grammar` {Grammar}
|
||||
setGrammar: (grammar) ->
|
||||
@displayBuffer.setGrammar(grammar)
|
||||
@tokenizedBuffer.setGrammar(grammar)
|
||||
|
||||
# Reload the grammar based on the file name.
|
||||
reloadGrammar: ->
|
||||
@displayBuffer.reloadGrammar()
|
||||
@tokenizedBuffer.reloadGrammar()
|
||||
|
||||
###
|
||||
Section: Managing Syntax Scopes
|
||||
@ -2761,7 +2883,7 @@ class TextEditor extends Model
|
||||
# e.g. `['.source.ruby']`, or `['.source.coffee']`. You can use this with
|
||||
# {Config::get} to get language specific config values.
|
||||
getRootScopeDescriptor: ->
|
||||
@displayBuffer.getRootScopeDescriptor()
|
||||
@tokenizedBuffer.rootScopeDescriptor
|
||||
|
||||
# Essential: Get the syntactic scopeDescriptor for the given position in buffer
|
||||
# coordinates. Useful with {Config::get}.
|
||||
@ -2774,7 +2896,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {ScopeDescriptor}.
|
||||
scopeDescriptorForBufferPosition: (bufferPosition) ->
|
||||
@displayBuffer.scopeDescriptorForBufferPosition(bufferPosition)
|
||||
@tokenizedBuffer.scopeDescriptorForPosition(bufferPosition)
|
||||
|
||||
# Extended: Get the range in buffer coordinates of all tokens surrounding the
|
||||
# cursor that match the given scope selector.
|
||||
@ -2786,7 +2908,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Range}.
|
||||
bufferRangeForScopeAtCursor: (scopeSelector) ->
|
||||
@displayBuffer.bufferRangeForScopeAtPosition(scopeSelector, @getCursorBufferPosition())
|
||||
@tokenizedBuffer.bufferRangeForScopeAtPosition(scopeSelector, @getCursorBufferPosition())
|
||||
|
||||
# Extended: Determine if the given row is entirely a comment
|
||||
isBufferRowCommented: (bufferRow) ->
|
||||
@ -2802,8 +2924,8 @@ class TextEditor extends Model
|
||||
|
||||
@notificationManager.addInfo(content, dismissable: true)
|
||||
|
||||
# {Delegates to: DisplayBuffer.tokenForBufferPosition}
|
||||
tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
|
||||
tokenForBufferPosition: (bufferPosition) ->
|
||||
@tokenizedBuffer.tokenForPosition(bufferPosition)
|
||||
|
||||
###
|
||||
Section: Clipboard Operations
|
||||
@ -2935,7 +3057,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# * `bufferRow` A {Number}
|
||||
unfoldBufferRow: (bufferRow) ->
|
||||
@displayBuffer.unfoldBufferRow(bufferRow)
|
||||
@displayLayer.destroyFoldsIntersectingBufferRange(Range(Point(bufferRow, 0), Point(bufferRow, Infinity)))
|
||||
|
||||
# Extended: For each selection, fold the rows it intersects.
|
||||
foldSelectedLines: ->
|
||||
@ -2965,7 +3087,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isFoldableAtBufferRow: (bufferRow) ->
|
||||
@displayBuffer.isFoldableAtBufferRow(bufferRow)
|
||||
@tokenizedBuffer.isFoldableAtRow(bufferRow)
|
||||
|
||||
# Extended: Determine whether the given row in screen coordinates is foldable.
|
||||
#
|
||||
@ -2997,7 +3119,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isFoldedAtBufferRow: (bufferRow) ->
|
||||
@displayBuffer.isFoldedAtBufferRow(bufferRow)
|
||||
@displayLayer.foldsIntersectingBufferRange(Range(Point(bufferRow, 0), Point(bufferRow, Infinity))).length > 0
|
||||
|
||||
# Extended: Determine whether the given row in screen coordinates is folded.
|
||||
#
|
||||
@ -3005,10 +3127,16 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isFoldedAtScreenRow: (screenRow) ->
|
||||
@displayBuffer.isFoldedAtScreenRow(screenRow)
|
||||
@isFoldedAtBufferRow(@bufferRowForScreenRow(screenRow))
|
||||
|
||||
# Creates a new fold between two row numbers.
|
||||
#
|
||||
# startRow - The row {Number} to start folding at
|
||||
# endRow - The row {Number} to end the fold
|
||||
#
|
||||
# Returns the new {Fold}.
|
||||
foldBufferRowRange: (startRow, endRow) ->
|
||||
@displayBuffer.foldBufferRowRange(startRow, endRow)
|
||||
@foldBufferRange(Range(Point(startRow, Infinity), Point(endRow, Infinity)))
|
||||
|
||||
foldBufferRange: (range) ->
|
||||
@displayLayer.foldBufferRange(range)
|
||||
@ -3066,7 +3194,7 @@ class TextEditor extends Model
|
||||
# * `options` (optional) {Object}
|
||||
# * `center` Center the editor around the position if possible. (default: false)
|
||||
scrollToBufferPosition: (bufferPosition, options) ->
|
||||
@displayBuffer.scrollToBufferPosition(bufferPosition, options)
|
||||
@scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition), options)
|
||||
|
||||
# Essential: Scrolls the editor to the given screen position.
|
||||
#
|
||||
@ -3075,7 +3203,7 @@ class TextEditor extends Model
|
||||
# * `options` (optional) {Object}
|
||||
# * `center` Center the editor around the position if possible. (default: false)
|
||||
scrollToScreenPosition: (screenPosition, options) ->
|
||||
@displayBuffer.scrollToScreenPosition(screenPosition, options)
|
||||
@scrollToScreenRange(new Range(screenPosition, screenPosition), options)
|
||||
|
||||
scrollToTop: ->
|
||||
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
|
||||
@ -3087,7 +3215,9 @@ class TextEditor extends Model
|
||||
|
||||
@viewRegistry.getView(this).scrollToBottom()
|
||||
|
||||
scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options)
|
||||
scrollToScreenRange: (screenRange, options = {}) ->
|
||||
scrollEvent = {screenRange, options}
|
||||
@emitter.emit "did-request-autoscroll", scrollEvent
|
||||
|
||||
getHorizontalScrollbarHeight: ->
|
||||
Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.")
|
||||
@ -3173,48 +3303,69 @@ class TextEditor extends Model
|
||||
getSelectionMarkerAttributes: ->
|
||||
{type: 'selection', invalidate: 'never'}
|
||||
|
||||
getVerticalScrollMargin: -> @displayBuffer.getVerticalScrollMargin()
|
||||
setVerticalScrollMargin: (verticalScrollMargin) -> @displayBuffer.setVerticalScrollMargin(verticalScrollMargin)
|
||||
getVerticalScrollMargin: ->
|
||||
maxScrollMargin = Math.floor(((@getHeight() / @getLineHeightInPixels()) - 1) / 2)
|
||||
Math.min(@verticalScrollMargin, maxScrollMargin)
|
||||
|
||||
getHorizontalScrollMargin: -> @displayBuffer.getHorizontalScrollMargin()
|
||||
setHorizontalScrollMargin: (horizontalScrollMargin) -> @displayBuffer.setHorizontalScrollMargin(horizontalScrollMargin)
|
||||
setVerticalScrollMargin: (@verticalScrollMargin) -> @verticalScrollMargin
|
||||
|
||||
getLineHeightInPixels: -> @displayBuffer.getLineHeightInPixels()
|
||||
setLineHeightInPixels: (lineHeightInPixels) -> @displayBuffer.setLineHeightInPixels(lineHeightInPixels)
|
||||
getHorizontalScrollMargin: -> Math.min(@horizontalScrollMargin, Math.floor(((@getWidth() / @getDefaultCharWidth()) - 1) / 2))
|
||||
setHorizontalScrollMargin: (@horizontalScrollMargin) -> @horizontalScrollMargin
|
||||
|
||||
getKoreanCharWidth: -> @displayBuffer.getKoreanCharWidth()
|
||||
getLineHeightInPixels: -> @lineHeightInPixels
|
||||
setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels
|
||||
|
||||
getHalfWidthCharWidth: -> @displayBuffer.getHalfWidthCharWidth()
|
||||
getKoreanCharWidth: -> @koreanCharWidth
|
||||
getHalfWidthCharWidth: -> @halfWidthCharWidth
|
||||
getDoubleWidthCharWidth: -> @doubleWidthCharWidth
|
||||
getDefaultCharWidth: -> @defaultCharWidth
|
||||
|
||||
getDoubleWidthCharWidth: -> @displayBuffer.getDoubleWidthCharWidth()
|
||||
ratioForCharacter: (character) ->
|
||||
if isKoreanCharacter(character)
|
||||
@getKoreanCharWidth() / @getDefaultCharWidth()
|
||||
else if isHalfWidthCharacter(character)
|
||||
@getHalfWidthCharWidth() / @getDefaultCharWidth()
|
||||
else if isDoubleWidthCharacter(character)
|
||||
@getDoubleWidthCharWidth() / @getDefaultCharWidth()
|
||||
else
|
||||
1
|
||||
|
||||
getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth()
|
||||
setDefaultCharWidth: (defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
|
||||
@displayBuffer.setDefaultCharWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
|
||||
doubleWidthCharWidth ?= defaultCharWidth
|
||||
halfWidthCharWidth ?= defaultCharWidth
|
||||
koreanCharWidth ?= defaultCharWidth
|
||||
if defaultCharWidth isnt @defaultCharWidth or doubleWidthCharWidth isnt @doubleWidthCharWidth and halfWidthCharWidth isnt @halfWidthCharWidth and koreanCharWidth isnt @koreanCharWidth
|
||||
@defaultCharWidth = defaultCharWidth
|
||||
@doubleWidthCharWidth = doubleWidthCharWidth
|
||||
@halfWidthCharWidth = halfWidthCharWidth
|
||||
@koreanCharWidth = koreanCharWidth
|
||||
@resetDisplayLayer() if @isSoftWrapped() and @getEditorWidthInChars()?
|
||||
defaultCharWidth
|
||||
|
||||
setHeight: (height, reentrant=false) ->
|
||||
if reentrant
|
||||
@displayBuffer.setHeight(height)
|
||||
@height = height
|
||||
else
|
||||
Grim.deprecate("This is now a view method. Call TextEditorElement::setHeight instead.")
|
||||
@viewRegistry.getView(this).setHeight(height)
|
||||
|
||||
getHeight: ->
|
||||
Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.")
|
||||
@displayBuffer.getHeight()
|
||||
|
||||
getClientHeight: -> @displayBuffer.getClientHeight()
|
||||
@height
|
||||
|
||||
setWidth: (width, reentrant=false) ->
|
||||
if reentrant
|
||||
@displayBuffer.setWidth(width)
|
||||
oldWidth = @width
|
||||
@width = width
|
||||
@resetDisplayLayer() if width isnt oldWidth and @isSoftWrapped()
|
||||
@width
|
||||
else
|
||||
Grim.deprecate("This is now a view method. Call TextEditorElement::setWidth instead.")
|
||||
@viewRegistry.getView(this).setWidth(width)
|
||||
|
||||
getWidth: ->
|
||||
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
|
||||
@displayBuffer.getWidth()
|
||||
@width
|
||||
|
||||
# Experimental: Scroll the editor such that the given screen row is at the
|
||||
# top of the visible area.
|
||||
@ -3222,10 +3373,8 @@ class TextEditor extends Model
|
||||
unless fromView
|
||||
maxScreenRow = @getScreenLineCount() - 1
|
||||
unless @config.get('editor.scrollPastEnd') and @scrollPastEnd
|
||||
height = @displayBuffer.getHeight()
|
||||
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
|
||||
if height? and lineHeightInPixels?
|
||||
maxScreenRow -= Math.floor(height / lineHeightInPixels)
|
||||
if @height? and @lineHeightInPixels?
|
||||
maxScreenRow -= Math.floor(@height / @lineHeightInPixels)
|
||||
screenRow = Math.max(Math.min(screenRow, maxScreenRow), 0)
|
||||
|
||||
unless screenRow is @firstVisibleScreenRow
|
||||
@ -3235,10 +3384,8 @@ class TextEditor extends Model
|
||||
getFirstVisibleScreenRow: -> @firstVisibleScreenRow
|
||||
|
||||
getLastVisibleScreenRow: ->
|
||||
height = @displayBuffer.getHeight()
|
||||
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
|
||||
if height? and lineHeightInPixels?
|
||||
Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getScreenLineCount() - 1)
|
||||
if @height? and @lineHeightInPixels?
|
||||
Math.min(@firstVisibleScreenRow + Math.floor(@height / @lineHeightInPixels), @getScreenLineCount() - 1)
|
||||
else
|
||||
null
|
||||
|
||||
@ -3333,8 +3480,6 @@ class TextEditor extends Model
|
||||
inspect: ->
|
||||
"<TextEditor #{@id}>"
|
||||
|
||||
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
|
||||
|
||||
emitWillInsertTextEvent: (text) ->
|
||||
result = true
|
||||
cancel = -> result = false
|
||||
|
Loading…
Reference in New Issue
Block a user