From 9ab730b3d03c775412f8c09fb374bedfcfededed Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 24 Jan 2013 08:36:53 -0800 Subject: [PATCH] Load snippets in a web worker A single web worker is now used to load snippets one bundle at a time. --- src/packages/snippets/index.coffee | 9 ++-- .../snippets/spec/snippets-spec.coffee | 42 ++++++++++-------- .../snippets/src/package-extensions.coffee | 25 ----------- .../snippets/src/snippets-reader.coffee | 43 +++++++++++++++++++ .../snippets/src/snippets-task.coffee | 42 ++++++++++++++++++ src/stdlib/fs.coffee | 1 - src/stdlib/require.coffee | 26 ++++++++--- src/stdlib/task.coffee | 26 +++++++++++ 8 files changed, 159 insertions(+), 55 deletions(-) delete mode 100644 src/packages/snippets/src/package-extensions.coffee create mode 100644 src/packages/snippets/src/snippets-reader.coffee create mode 100644 src/packages/snippets/src/snippets-task.coffee create mode 100644 src/stdlib/task.coffee diff --git a/src/packages/snippets/index.coffee b/src/packages/snippets/index.coffee index 4f93ede37..e7e01c99a 100644 --- a/src/packages/snippets/index.coffee +++ b/src/packages/snippets/index.coffee @@ -4,14 +4,14 @@ PEG = require 'pegjs' _ = require 'underscore' SnippetExpansion = require './src/snippet-expansion' Snippet = require './src/snippet' -require './src/package-extensions' +SnippetsTask = require './src/snippets-task' module.exports = class Snippets extends AtomPackage snippetsByExtension: {} parser: PEG.buildParser(fs.read(require.resolve 'snippets/snippets.pegjs'), trackLineAndColumn: true) - userSnippetsDir: fs.join(config.configDirPath, 'snippets') + loaded: false activate: (@rootView) -> window.snippets = this @@ -19,10 +19,7 @@ class Snippets extends AtomPackage @rootView.on 'editor:attached', (e, editor) => @enableSnippetsInEditor(editor) loadAll: -> - for pack in atom.getPackages() - pack.loadSnippets() - - @loadDirectory(@userSnippetsDir) if fs.exists(@userSnippetsDir) + new SnippetsTask(this).start() loadDirectory: (snippetsDirPath) -> for snippetsPath in fs.list(snippetsDirPath) when fs.base(snippetsPath).indexOf('.') isnt 0 diff --git a/src/packages/snippets/spec/snippets-spec.coffee b/src/packages/snippets/spec/snippets-spec.coffee index 942b7b4ea..712de9ae5 100644 --- a/src/packages/snippets/spec/snippets-spec.coffee +++ b/src/packages/snippets/spec/snippets-spec.coffee @@ -1,5 +1,6 @@ Snippets = require 'snippets' Snippet = require 'snippets/src/snippet' +SnippetsTask = require 'snippets/src/snippets-task' RootView = require 'root-view' Buffer = require 'buffer' Editor = require 'editor' @@ -12,8 +13,7 @@ describe "Snippets extension", -> [buffer, editor] = [] beforeEach -> rootView = new RootView(require.resolve('fixtures/sample.js')) - spyOn(AtomPackage.prototype, 'loadSnippets') - spyOn(TextMatePackage.prototype, 'loadSnippets') + spyOn(SnippetsTask.prototype, 'start') atom.loadPackage("snippets") editor = rootView.getActiveEditor() buffer = editor.getBuffer() @@ -216,28 +216,36 @@ 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(AtomPackage.prototype, 'loadSnippets') + jasmine.unspy(SnippetsTask.prototype, 'start') + snippets.loaded = false snippets.loadAll() - expect(syntax.getProperty(['.test'], 'snippets.test')?.constructor).toBe Snippet + waitsFor((-> snippets.loaded), "Waiting for all snippets to load", 5000) - # warn about junk-file, but don't even try to parse a hidden file - expect(console.warn).toHaveBeenCalled() - expect(console.warn.calls.length).toBeGreaterThan 0 + runs -> + expect(syntax.getProperty(['.test'], 'snippets.test')?.constructor).toBe Snippet + + # warn about junk-file, but don't even try to parse a hidden file + expect(console.warn).toHaveBeenCalled() + expect(console.warn.calls.length).toBeGreaterThan 0 it "loads snippets from all TextMate packages with snippets", -> - jasmine.unspy(TextMatePackage.prototype, 'loadSnippets') + jasmine.unspy(SnippetsTask.prototype, 'start') + snippets.loaded = false snippets.loadAll() - snippet = syntax.getProperty(['.source.js'], 'snippets.fun') - expect(snippet.constructor).toBe Snippet - expect(snippet.prefix).toBe 'fun' - expect(snippet.name).toBe 'Function' - expect(snippet.body).toBe """ - function function_name (argument) { - \t// body... - } - """ + waitsFor((-> snippets.loaded), "Waiting for all snippets to load", 5000) + + runs -> + snippet = syntax.getProperty(['.source.js'], 'snippets.fun') + expect(snippet.constructor).toBe Snippet + expect(snippet.prefix).toBe 'fun' + expect(snippet.name).toBe 'Function' + expect(snippet.body).toBe """ + function function_name (argument) { + \t// body... + } + """ describe "Snippets parser", -> it "breaks a snippet body into lines, with each line containing tab stops at the appropriate position", -> diff --git a/src/packages/snippets/src/package-extensions.coffee b/src/packages/snippets/src/package-extensions.coffee deleted file mode 100644 index 342d20ecd..000000000 --- a/src/packages/snippets/src/package-extensions.coffee +++ /dev/null @@ -1,25 +0,0 @@ -AtomPackage = require 'atom-package' -TextMatePackage = require 'text-mate-package' -fs = require 'fs' - -AtomPackage.prototype.loadSnippets = -> - snippetsDirPath = fs.join(@path, 'snippets') - snippets.loadDirectory(snippetsDirPath) if fs.exists(snippetsDirPath) - -TextMatePackage.prototype.loadSnippets = -> - snippetsDirPath = fs.join(@path, 'Snippets') - if fs.exists(snippetsDirPath) - tmSnippets = fs.list(snippetsDirPath).map (snippetPath) -> fs.readPlist(snippetPath) - snippets.add(@translateSnippets(tmSnippets)) - -TextMatePackage.prototype.translateSnippets = (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 diff --git a/src/packages/snippets/src/snippets-reader.coffee b/src/packages/snippets/src/snippets-reader.coffee new file mode 100644 index 000000000..b5469c0fd --- /dev/null +++ b/src/packages/snippets/src/snippets-reader.coffee @@ -0,0 +1,43 @@ +eval("window = {};") +eval("console = {};") +console.warn = -> + self.postMessage + type: 'warn' + details: arguments +console.log = -> + self.postMessage + type: 'warn' + details: arguments +eval("attachEvent = function(){};") + +self.addEventListener 'message', (event) -> + switch event.data.type + when 'start' + window.resourcePath = event.data.resourcePath + importScripts(event.data.requirePath) + self.postMessage(type:'started') + else + self[event.data.type](event.data) + +self.loadTextmateSnippets = ({path}) -> + fs = require 'fs' + snippetsDirPath = fs.join(path, 'Snippets') + snippets = fs.list(snippetsDirPath).map (snippetPath) -> + fs.readPlist(snippetPath) + self.postMessage + type: 'loadSnippets' + snippets: snippets + +self.loadAtomSnippets = ({path}) -> + fs = require 'fs' + snippetsDirPath = fs.join(path, 'snippets') + snippets = [] + for snippetsPath in fs.list(snippetsDirPath) + continue if fs.base(snippetsPath).indexOf('.') is 0 + try + snippets.push(fs.readObject(snippetsPath)) + catch e + console.warn "Error reading snippets file '#{snippetsPath}'" + self.postMessage + type: 'loadSnippets' + snippets: snippets diff --git a/src/packages/snippets/src/snippets-task.coffee b/src/packages/snippets/src/snippets-task.coffee new file mode 100644 index 000000000..a9658042c --- /dev/null +++ b/src/packages/snippets/src/snippets-task.coffee @@ -0,0 +1,42 @@ +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 diff --git a/src/stdlib/fs.coffee b/src/stdlib/fs.coffee index 39ae82334..a9eb755da 100644 --- a/src/stdlib/fs.coffee +++ b/src/stdlib/fs.coffee @@ -2,7 +2,6 @@ # http://ringojs.org/api/v0.8/fs/ _ = require 'underscore' -$ = require 'jquery' module.exports = # Make the given path absolute by resolving it against the diff --git a/src/stdlib/require.coffee b/src/stdlib/require.coffee index 1076bcc49..c68e54725 100644 --- a/src/stdlib/require.coffee +++ b/src/stdlib/require.coffee @@ -30,12 +30,12 @@ require = (path, cb) -> if __moduleExists file if not __modules.loaded[file.toLowerCase()]? - console.warn "Circular require: #{__filename} required #{file}" + console.warn "Circular require: #{window.__filename} required #{file}" return __modules[file] else if __modules.loaded[file.toLowerCase()] console.warn "Multiple requires (different cases) for #{file}" - [ previousFilename, window.__filename ] = [ __filename, file ] + [ previousFilename, window.__filename ] = [ window.__filename, file ] __modules[file] = {} # Fix for circular references __modules[file] = (exts[ext] or (file) -> __read file) file window.__filename = previousFilename @@ -43,10 +43,10 @@ require = (path, cb) -> define = (cb) -> __defines.push -> - exports = __modules[__filename] or {} + exports = __modules[window.__filename] or {} module = exports: exports cb.call exports, require, exports, module - __modules.loaded[__filename.toLowerCase()] = true + __modules.loaded[window.__filename.toLowerCase()] = true module.exports or exports exts = @@ -57,6 +57,19 @@ exts = coffee: (file) -> exts.js(file, __coffeeCache(file)) +getPath = (path) -> + path = resolve(path) + parts = path.split '.' + return path unless parts[parts.length - 1] is 'coffee' + + tmpPath = "/tmp/atom-compiled-scripts" + cachePath = [tmpPath, $native.md5ForPath(path)].join("/") + if not __exists(cachePath) + {CoffeeScript} = require 'coffee-script' + compiled = CoffeeScript.compile(__read(path), filename: path) + $native.write(cachePath, compiled) + cachePath + resolve = (name, {verifyExistence}={}) -> verifyExistence ?= true file = name @@ -65,11 +78,11 @@ resolve = (name, {verifyExistence}={}) -> file = parts[parts.length-1] if file[0..1] is './' - prefix = __filename.split('/')[0..-2].join '/' + prefix = window.__filename.split('/')[0..-2].join '/' file = file.replace './', "#{prefix}/" if file[0..2] is '../' - prefix = __filename.split('/')[0..-3].join '/' + prefix = window.__filename.split('/')[0..-3].join '/' file = file.replace '../', "#{prefix}/" if file[0] isnt '/' @@ -157,6 +170,7 @@ this.nakedLoad = nakedLoad this.define = define this.require.paths = paths +this.require.getPath = getPath this.require.exts = exts this.require.resolve = resolve diff --git a/src/stdlib/task.coffee b/src/stdlib/task.coffee new file mode 100644 index 000000000..8abea562a --- /dev/null +++ b/src/stdlib/task.coffee @@ -0,0 +1,26 @@ +module.exports = +class Task + + constructor: (path) -> + @path = require.getPath(path) + + onProgress: (event) -> + + start: -> + worker = new Worker(@path) + worker.onmessage = (event) => + switch event.data.type + when 'warn' + console.warn(event.data.details...) + return + when 'log' + console.log(event.data.details...) + return + + reply = @onProgress(event) + worker.postMessage(reply) if reply + + worker.postMessage + type: 'start' + resourcePath: window.resourcePath + requirePath: require.getPath('require')