Give Task an RPC-style interaction with its Worker

This commit makes all interactions between Task and Worker look
like method calls. The worker now has a global `callTaskMethod`
function that it can use to call methods on the Task object. And the
Task can use `callWorkerMethod` to call methods on a global `handler`
object in the worker. The worker's initial `handler` actually contains
the `start` method, which the Task initially calls to kick things off.
Then the global `handler` gets replaced with whatever `handlerPath`
is specified by the Task. The worker then calls `workerStarted` on its
parent Task object.

This commit also gets rid of the `onProgress` method with the reply
semantics, favoring a more explicit interaction. When `snippetsLoaded`
finishes adding the snippet data, we call `loadNextPackageSnippets`
explicitly rather than returning a reply message.
This commit is contained in:
Nathan Sobo
2013-01-24 15:39:26 -07:00
committed by Kevin Sawicki
parent c628a88409
commit 0726987896
7 changed files with 103 additions and 91 deletions

View File

@@ -4,7 +4,7 @@ PEG = require 'pegjs'
_ = require 'underscore'
SnippetExpansion = require './src/snippet-expansion'
Snippet = require './src/snippet'
SnippetsTask = require './src/snippets-task'
SnippetsTask = require './src/load-snippets-task'
module.exports =
class Snippets extends AtomPackage

View File

@@ -1,6 +1,6 @@
Snippets = require 'snippets'
Snippet = require 'snippets/src/snippet'
SnippetsTask = require 'snippets/src/snippets-task'
LoadSnippetsTask = require 'snippets/src/load-snippets-task'
RootView = require 'root-view'
Buffer = require 'buffer'
Editor = require 'editor'
@@ -11,7 +11,7 @@ describe "Snippets extension", ->
[buffer, editor] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/sample.js'))
spyOn(SnippetsTask.prototype, 'start')
spyOn(LoadSnippetsTask.prototype, 'start')
atom.loadPackage("snippets")
editor = rootView.getActiveEditor()
buffer = editor.getBuffer()
@@ -214,7 +214,7 @@ describe "Snippets extension", ->
describe "snippet loading", ->
it "loads non-hidden snippet files from all atom packages with snippets directories, logging a warning if a file can't be parsed", ->
spyOn(console, 'warn').andCallThrough()
jasmine.unspy(SnippetsTask.prototype, 'start')
jasmine.unspy(LoadSnippetsTask.prototype, 'start')
snippets.loaded = false
snippets.loadAll()
@@ -228,7 +228,7 @@ describe "Snippets extension", ->
expect(console.warn.calls.length).toBeGreaterThan 0
it "loads snippets from all TextMate packages with snippets", ->
jasmine.unspy(SnippetsTask.prototype, 'start')
jasmine.unspy(LoadSnippetsTask.prototype, 'start')
snippets.loaded = false
snippets.loadAll()

View File

@@ -1,15 +1,13 @@
fs = require 'fs'
module.exports =
loadTextmateSnippets: ({path}) ->
fs = require 'fs'
loadTextmateSnippets: (path) ->
snippetsDirPath = fs.join(path, 'Snippets')
snippets = fs.list(snippetsDirPath).map (snippetPath) ->
fs.readPlist(snippetPath)
self.postMessage
type: 'loadSnippets'
snippets: snippets
@snippetsLoaded(snippets)
loadAtomSnippets: ({path}) ->
fs = require 'fs'
loadAtomSnippets: (path) ->
snippetsDirPath = fs.join(path, 'snippets')
snippets = []
for snippetsPath in fs.list(snippetsDirPath)
@@ -18,6 +16,6 @@ module.exports =
snippets.push(fs.readObject(snippetsPath))
catch e
console.warn "Error reading snippets file '#{snippetsPath}'"
self.postMessage
type: 'loadSnippets'
snippets: snippets
@snippetsLoaded(snippets)
snippetsLoaded: (snippets) -> callTaskMethod('snippetsLoaded', snippets)

View File

@@ -0,0 +1,42 @@
Task = require 'Task'
TextMatePackage = require 'text-mate-package'
module.exports =
class LoadSnippetsTask extends Task
constructor: (@snippets) ->
super('snippets/src/load-snippets-handler')
@packages = atom.getPackages()
@packages.push(path: config.configDirPath)
started: ->
@loadNextPackageSnippets()
loadNextPackageSnippets: ->
unless @packages.length
@snippets.loaded = true
return
@packageBeingLoaded = @packages.shift()
if @packageBeingLoaded instanceof TextMatePackage
method = 'loadTextmateSnippets'
else
method = 'loadAtomSnippets'
@callWorkerMethod(method, @packageBeingLoaded.path)
snippetsLoaded: (snippets) ->
if @packageBeingLoaded instanceof TextMatePackage
snippets = @translateTextmateSnippets(snippets)
@snippets.add(snippet) for snippet in snippets
@loadNextPackageSnippets()
translateTextmateSnippets: (tmSnippets) ->
atomSnippets = {}
for { scope, name, content, tabTrigger } in tmSnippets
if scope
scope = TextMatePackage.cssSelectorFromScopeSelector(scope)
else
scope = '*'
snippetsForScope = (atomSnippets[scope] ?= {})
snippetsForScope[name] = { prefix: tabTrigger, body: content }
[atomSnippets]

View File

@@ -1,42 +0,0 @@
Task = require 'task'
TextMatePackage = require 'text-mate-package'
module.exports =
class SnippetsTask extends Task
constructor: (@snippets) ->
super('snippets/src/snippets-reader')
@packages = atom.getPackages()
@packages.push(path: config.configDirPath)
onProgress: (event) =>
if event.data.type is 'loadSnippets'
rawSnippets = event.data.snippets
if @package instanceof TextMatePackage
@snippets.add(@translateTextmateSnippets(rawSnippets))
else
@snippets.add(snippet) for snippet in rawSnippets
@package = @packages.shift()
if not @package?
@snippets.loaded = true
return
if @package instanceof TextMatePackage
eventType = 'loadTextmateSnippets'
else
eventType = 'loadAtomSnippets'
{ type: eventType, path: @package.path }
translateTextmateSnippets: (tmSnippets) ->
atomSnippets = {}
for { scope, name, content, tabTrigger } in tmSnippets
if scope
scope = TextMatePackage.cssSelectorFromScopeSelector(scope)
else
scope = '*'
snippetsForScope = (atomSnippets[scope] ?= {})
snippetsForScope[name] = { prefix: tabTrigger, body: content }
atomSnippets

View File

@@ -1,21 +1,29 @@
# This file is loaded within Task's worker thread. It will attempt to invoke
# any message with a 'method' and 'args' key on the global `handler` object. The
# initial `handler` object contains the `start` method, which is called by the
# task itself to relay information from the window thread and bootstrap the
# worker's environment. The `start` method then replaces the handler with an
# object required from the given `handlerPath`.
self.window = {}
self.attachEvent = ->
self.console =
warn: ->
self.postMessage
type: 'warn'
details: arguments
log: ->
self.postMessage
type: 'log'
details: arguments
warn: -> callTaskMethod 'warn', arguments...
log: -> callTaskMethod 'log', arguments...
error: -> callTaskMethod 'error', arguments...
self.addEventListener 'message', (event) ->
switch event.data.type
when 'start'
window.resourcePath = event.data.resourcePath
importScripts(event.data.requirePath)
self.task = require(event.data.taskPath)
self.postMessage(type:'started')
else
self.task[event.data.type](event.data)
# `callTaskMethod` can be used to invoke method's on the parent `Task` object
# back in the window thread.
self.callTaskMethod = (method, args...) ->
postMessage(method: method, args: args)
# The worker's initial handler replaces itself when `start` is invoked
self.handler =
start: ({resourcePath, requirePath, handlerPath}) ->
window.resourcePath = resourcePath
importScripts(requirePath)
self.handler = require(handlerPath)
callTaskMethod 'started'
self.addEventListener 'message', ({data}) ->
handler[data.method]?(data.args...) if data.method

View File

@@ -1,26 +1,32 @@
module.exports =
class Task
constructor: (@path) ->
onProgress: (event) ->
start: ->
worker = new Worker(require.getPath('task-shell'))
worker.onmessage = (event) =>
switch event.data.type
when 'warn'
console.warn(event.data.details...)
return
when 'log'
console.log(event.data.details...)
return
@worker = new Worker(require.getPath('task-shell'))
@worker.onmessage = ({data}) =>
if data.method and this[data.method]
this[data.method](data.args...)
else
@onMessage(data)
@startWorker()
reply = @onProgress(event)
worker.postMessage(reply) if reply
log: -> console.log(arguments...)
warn: -> console.warn(arguments...)
error: -> console.error(arguments...)
worker.postMessage
type: 'start'
startWorker: ->
@callWorkerMethod 'start'
resourcePath: window.resourcePath
requirePath: require.getPath('require')
taskPath: @path
handlerPath: @path
started: ->
onMessage: (message) ->
callWorkerMethod: (method, args...) ->
@postMessage({method, args})
postMessage: (data) ->
@worker.postMessage(data)