Make TokenizedBuffer a telepath.Model subclass

There's a bunch of improvised code to make this work right now because
of the circularity of this refactoring. It will stabilize over time.
This commit is contained in:
Nathan Sobo 2013-12-07 02:25:47 -08:00
parent b61654b52f
commit a4d2b4d21a
9 changed files with 75 additions and 82 deletions

View File

@ -2389,17 +2389,16 @@ describe "Editor", ->
describe "when a better-matched grammar is added to syntax", ->
it "switches to the better-matched grammar and re-tokenizes the buffer", ->
editor.destroy()
jsGrammar = atom.syntax.selectGrammar('a.js')
atom.syntax.removeGrammar(jsGrammar)
editor = atom.project.openSync('sample.js', autoIndent: false)
expect(editor.getGrammar()).toBe atom.syntax.nullGrammar
expect(editor.lineForScreenRow(0).tokens.length).toBe 1
editor2 = atom.project.openSync('sample.js', autoIndent: false)
expect(editor2.getGrammar()).toBe atom.syntax.nullGrammar
expect(editor2.lineForScreenRow(0).tokens.length).toBe 1
atom.syntax.addGrammar(jsGrammar)
expect(editor.getGrammar()).toBe jsGrammar
expect(editor.lineForScreenRow(0).tokens.length).toBeGreaterThan 1
expect(editor2.getGrammar()).toBe jsGrammar
expect(editor2.lineForScreenRow(0).tokens.length).toBeGreaterThan 1
describe "auto-indent", ->
copyText = (text, {startColumn}={}) ->

View File

@ -698,18 +698,16 @@ describe "Pane", ->
expect(newPane.items.length).toBe pane.items.length - 1
it "focuses the pane after attach only if had focus when serialized", ->
reloadContainer = ->
containerState = container.serialize()
container.remove()
container = atom.deserializers.deserialize(containerState)
pane = container.getRoot()
container.attachToDom()
container.attachToDom()
pane.focus()
reloadContainer()
expect(pane).toMatchSelector(':has(:focus)')
container2 = atom.deserializers.deserialize(container.serialize())
pane2 = container2.getRoot()
container2.attachToDom()
expect(pane2).toMatchSelector(':has(:focus)')
$(document.activeElement).blur()
reloadContainer()
expect(pane).not.toMatchSelector(':has(:focus)')
container3 = atom.deserializers.deserialize(container.serialize())
pane3 = container3.getRoot()
container3.attachToDom()
expect(pane3).not.toMatchSelector(':has(:focus)')

View File

@ -18,22 +18,14 @@ describe "TokenizedBuffer", ->
advanceClock() while tokenizedBuffer.firstInvalidRow()?
changeHandler?.reset()
describe "@deserialize(state)", ->
it "constructs a tokenized buffer with the same buffer and tabLength setting", ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer1 = new TokenizedBuffer(buffer: buffer, tabLength: 4)
tokenizedBuffer2 = atom.deserializers.deserialize(tokenizedBuffer1.serialize())
expect(tokenizedBuffer2.buffer).toBe tokenizedBuffer1.buffer
expect(tokenizedBuffer2.getTabLength()).toBe tokenizedBuffer1.getTabLength()
describe "when the buffer is destroyed", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
startTokenizing(tokenizedBuffer)
it "stops tokenization", ->
tokenizedBuffer.destroy()
tokenizedBuffer.state.destroy()
spyOn(tokenizedBuffer, 'tokenizeNextChunk')
advanceClock()
expect(tokenizedBuffer.tokenizeNextChunk).not.toHaveBeenCalled()
@ -41,7 +33,7 @@ describe "TokenizedBuffer", ->
describe "when the buffer contains soft-tabs", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
startTokenizing(tokenizedBuffer)
tokenizedBuffer.on "changed", changeHandler = jasmine.createSpy('changeHandler')
@ -321,7 +313,7 @@ describe "TokenizedBuffer", ->
beforeEach ->
atom.packages.activatePackage('language-coffee-script', sync: true)
buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
startTokenizing(tokenizedBuffer)
afterEach ->
@ -355,7 +347,7 @@ describe "TokenizedBuffer", ->
'abc\uD835\uDF97def'
//\uD835\uDF97xyz
"""
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
fullyTokenize(tokenizedBuffer)
afterEach ->
@ -392,7 +384,7 @@ describe "TokenizedBuffer", ->
buffer = atom.project.bufferForPathSync()
buffer.setText "<div class='name'><%= User.find(2).full_name %></div>"
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
tokenizedBuffer.setGrammar(atom.syntax.selectGrammar('test.erb'))
fullyTokenize(tokenizedBuffer)
@ -411,7 +403,7 @@ describe "TokenizedBuffer", ->
it "returns the correct token (regression)", ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
fullyTokenize(tokenizedBuffer)
expect(tokenizedBuffer.tokenForPosition([1,0]).scopes).toEqual ["source.js"]
expect(tokenizedBuffer.tokenForPosition([1,1]).scopes).toEqual ["source.js"]
@ -420,7 +412,7 @@ describe "TokenizedBuffer", ->
describe ".bufferRangeForScopeAtPosition(selector, position)", ->
beforeEach ->
buffer = atom.project.bufferForPathSync('sample.js')
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer = atom.create(new TokenizedBuffer({buffer}))
fullyTokenize(tokenizedBuffer)
describe "when the selector does not match the token at the position", ->

View File

@ -80,6 +80,11 @@ class Atom
setBodyPlatformClass: ->
document.body.classList.add("platform-#{process.platform}")
# Public: Create a new telepath model. This won't be needed when Atom is itself
# a telepath model.
create: (model) ->
@site.createDocument(model)
# Public: Get the current window
getCurrentWindow: ->
remote.getCurrentWindow()
@ -420,7 +425,7 @@ class Atom
serializedWindowState = @loadSerializedWindowState()
doc = Document.deserialize(serializedWindowState) if serializedWindowState?
doc ?= Document.create()
doc.registerModelClasses(require('./text-buffer'), require('./project'))
doc.registerModelClasses(require('./text-buffer'), require('./project'), require('./tokenized-buffer'))
# TODO: Remove this when everything is using telepath models
if @site?
@site.setRootDocument(doc)

View File

@ -27,17 +27,18 @@ class DisplayBuffer
if optionsOrState instanceof telepath.Document
@state = optionsOrState
@id = @state.get('id')
@tokenizedBuffer = atom.deserializers.deserialize(@state.get('tokenizedBuffer'))
@tokenizedBuffer = @state.get('tokenizedBuffer')
@tokenizedBuffer.created()
@buffer = @tokenizedBuffer.buffer
else
{@buffer, softWrap, editorWidthInChars} = optionsOrState
{@buffer, softWrap, editorWidthInChars, tabLength} = optionsOrState
@id = guid.create().toString()
@tokenizedBuffer = new TokenizedBuffer(optionsOrState)
@tokenizedBuffer = new TokenizedBuffer({tabLength, @buffer, project: atom.project})
@state = atom.site.createDocument
deserializer: @constructor.name
version: @constructor.version
id: @id
tokenizedBuffer: @tokenizedBuffer.getState()
tokenizedBuffer: @tokenizedBuffer
softWrap: softWrap ? atom.config.get('editor.softWrap') ? false
editorWidthInChars: editorWidthInChars
@ -62,6 +63,7 @@ class DisplayBuffer
@updateWrappedScreenLines() if @getSoftWrap()
serialize: -> @state.clone()
getState: -> @state
copy: ->
@ -654,6 +656,7 @@ class DisplayBuffer
softWraps = 0
while wrapScreenColumn = @findWrapColumn(tokenizedLine.text)
[wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(wrapScreenColumn)
newScreenLines.push(wrappedLine)
softWraps++
newScreenLines.push(tokenizedLine)

View File

@ -106,7 +106,7 @@ class EditorView extends View
@edit(editor)
else if @mini
@edit(new Editor
buffer: TextBuffer.createAsRoot()
buffer: atom.create(new TextBuffer)
softWrap: false
tabLength: 2
softTabs: true

View File

@ -34,7 +34,7 @@ class Project extends telepath.Model
# Private: Called by telepath.
created: ->
for buffer in @buffers.getValues()
buffer.once 'destroyed', (buffer) => @removeBuffer(buffer)
buffer.once 'destroyed', (buffer) => @removeBuffer(buffer) if @isAlive()
@openers = []
@editors = []
@ -64,7 +64,7 @@ class Project extends telepath.Model
# Private:
destroyed: ->
editor.destroy() for editor in @getEditors()
buffer.release() for buffer in @getBuffers()
buffer.destroy() for buffer in @getBuffers()
@destroyRepo()
# Private:
@ -239,7 +239,7 @@ class Project extends telepath.Model
# Private:
addBufferAtIndex: (buffer, index, options={}) ->
buffer = @buffers.insert(index, buffer)
buffer.once 'destroyed', => @removeBuffer(buffer)
buffer.once 'destroyed', => @removeBuffer(buffer) if @isAlive()
@emit 'buffer-created', buffer
buffer

View File

@ -81,12 +81,12 @@ class TextBuffer extends telepath.Model
@emit 'changed', bufferChangeEvent
@scheduleModifiedEvents()
destroy: ->
unless @destroyed
destroyed: ->
unless @alreadyDestroyed
@cancelStoppedChangingTimeout()
@file?.off()
@unsubscribe()
@destroyed = true
@alreadyDestroyed = true
@emit 'destroyed', this
isRetained: -> @refcount > 0

View File

@ -1,16 +1,16 @@
_ = require 'underscore-plus'
TokenizedLine = require './tokenized-line'
{Emitter, Subscriber} = require 'emissary'
Token = require './token'
telepath = require 'telepath'
{Point, Range} = telepath
{Model, Point, Range} = require 'telepath'
### Internal ###
module.exports =
class TokenizedBuffer
Emitter.includeInto(this)
Subscriber.includeInto(this)
class TokenizedBuffer extends Model
@properties
bufferPath: null
tabLength: -> atom.config.get('editor.tabLength') ? 2
project: null
grammar: null
currentGrammarScore: null
@ -20,24 +20,19 @@ class TokenizedBuffer
invalidRows: null
visible: false
@acceptsDocuments: true
atom.deserializers.add(this)
constructor: ->
super
@deserializing = @state?
@deserialize: (state) ->
new this(state)
created: ->
if @deserializing
@deserializing = false
return this
constructor: (optionsOrState) ->
if optionsOrState instanceof telepath.Document
@state = optionsOrState
# TODO: This needs to be made async, but should wait until the new Telepath changes land
@buffer = atom.project.bufferForPathSync(optionsOrState.get('bufferPath'))
if @buffer? and @buffer.isAlive()
@bufferPath = @buffer.getPath()
else
{ @buffer, tabLength } = optionsOrState
@state = atom.site.createDocument
deserializer: @constructor.name
bufferPath: @buffer.getPath()
tabLength: tabLength ? atom.config.get('editor.tabLength') ? 2
@buffer = @project.bufferForPathSync(@bufferPath)
@subscribe atom.syntax, 'grammar-added grammar-updated', (grammar) =>
if grammar.injectionSelector?
@ -48,12 +43,22 @@ class TokenizedBuffer
@on 'grammar-changed grammar-updated', => @resetTokenizedLines()
@subscribe @buffer, "changed", (e) => @handleBufferChange(e)
@subscribe @buffer, "path-changed", => @state.set('bufferPath', @buffer.getPath())
@subscribe @buffer, "path-changed", => @bufferPath = @buffer.getPath()
@subscribe @$tabLength.changes.onValue (tabLength) =>
lastRow = @buffer.getLastRow()
@tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, lastRow)
@invalidateRow(0)
@emit "changed", { start: 0, end: lastRow, delta: 0 }
@reloadGrammar()
serialize: -> @state.clone()
getState: -> @state
# TODO: Remove when everything is a telepath model
destroy: ->
@destroyed()
destroyed: ->
@unsubscribe()
setGrammar: (grammar, score) ->
return if grammar is @grammar
@ -87,24 +92,19 @@ class TokenizedBuffer
#
# Returns a {Number}.
getTabLength: ->
@state.get('tabLength')
@tabLength
# Specifies the tab length.
#
# tabLength - A {Number} that defines the new tab length.
setTabLength: (tabLength) ->
@state.set('tabLength', tabLength)
lastRow = @buffer.getLastRow()
@tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, lastRow)
@invalidateRow(0)
@emit "changed", { start: 0, end: lastRow, delta: 0 }
setTabLength: (@tabLength) ->
tokenizeInBackground: ->
return if not @visible or @pendingChunk or @destroyed
return if not @visible or @pendingChunk or not @isAlive()
@pendingChunk = true
_.defer =>
@pendingChunk = false
@tokenizeNextChunk() unless @destroyed
@tokenizeNextChunk() if @isAlive() and @buffer.isAlive()
tokenizeNextChunk: ->
rowsRemaining = @chunkSize
@ -248,10 +248,6 @@ class TokenizedBuffer
endColumn = tokenizedLine.bufferColumnForToken(lastToken) + lastToken.bufferDelta
new Range([position.row, startColumn], [position.row, endColumn])
destroy: ->
@unsubscribe()
@destroyed = true
iterateTokensInBufferRange: (bufferRange, iterator) ->
bufferRange = Range.fromObject(bufferRange)
{ start, end } = bufferRange