From e5436974eb451eb269b1359e5e6bed2111df506c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Mar 2013 16:46:08 -0600 Subject: [PATCH] Replace snippet loader task w/ async loading --- package.json | 3 +- .../snippets/lib/load-snippets-handler.coffee | 56 ------------ .../snippets/lib/load-snippets-task.coffee | 34 ------- src/packages/snippets/lib/snippets.coffee | 91 ++++++++++++++++--- .../snippets/spec/snippets-spec.coffee | 36 ++------ src/stdlib/cson.coffee | 22 +++-- src/stdlib/fs-utils.coffee | 13 +++ 7 files changed, 118 insertions(+), 137 deletions(-) delete mode 100644 src/packages/snippets/lib/load-snippets-handler.coffee delete mode 100644 src/packages/snippets/lib/load-snippets-task.coffee diff --git a/package.json b/package.json index 9a9140974..caa23521f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "underscore": "1.4.4", "d3": "3.0.8", "coffee-cache": "0.1.0", - "pegjs": "0.7.0" + "pegjs": "0.7.0", + "async": "0.2.6" }, "scripts": { diff --git a/src/packages/snippets/lib/load-snippets-handler.coffee b/src/packages/snippets/lib/load-snippets-handler.coffee deleted file mode 100644 index 6d65a46c4..000000000 --- a/src/packages/snippets/lib/load-snippets-handler.coffee +++ /dev/null @@ -1,56 +0,0 @@ -fs = require 'fs-utils' -TextMatePackage = require 'text-mate-package' -SnippetBodyParser = require './snippet-body-parser' -CSON = require 'cson' - -module.exports = - snippetsLoaded: (snippets) -> - for snippet in snippets - for selector, snippetsByName of snippet - for name, attributes of snippetsByName - attributes.bodyTree = SnippetBodyParser.parse(attributes.body) - callTaskMethod('snippetsLoaded', snippets) - - loadTextMateSnippets: (path) -> - snippetsDirPath = fs.join(path, 'Snippets') - snippets = [] - - for snippetsPath in fs.list(snippetsDirPath) - logWarning = -> - console.warn "Error reading TextMate snippets file '#{snippetsPath}'" - - continue if fs.base(snippetsPath).indexOf('.') is 0 - try - if CSON.isObjectPath(snippetsPath) and object = CSON.readObject(snippetsPath) - snippets.push(object) - else if object = fs.readPlist(snippetsPath) - snippets.push(object) - else - logWarning() - catch e - logWarning() - - @snippetsLoaded(@translateTextmateSnippets(snippets)) - - loadAtomSnippets: (path) -> - snippetsDirPath = fs.join(path, 'snippets') - snippets = [] - for snippetsPath in fs.list(snippetsDirPath) - continue if fs.base(snippetsPath).indexOf('.') is 0 - try - snippets.push(CSON.readObject(snippetsPath)) - catch e - console.warn "Error reading snippets file '#{snippetsPath}'" - @snippetsLoaded(snippets) - - 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/packages/snippets/lib/load-snippets-task.coffee b/src/packages/snippets/lib/load-snippets-task.coffee deleted file mode 100644 index 98b73b470..000000000 --- a/src/packages/snippets/lib/load-snippets-task.coffee +++ /dev/null @@ -1,34 +0,0 @@ -Task = require 'task' -TextMatePackage = require 'text-mate-package' - -module.exports = -class LoadSnippetsTask extends Task - constructor: (@snippets) -> - super('snippets/lib/load-snippets-handler') - @packages = atom.getLoadedPackages() - @packages.push(path: config.configDirPath) - - started: -> - @loadNextPackageSnippets() - - loadNextPackageSnippets: -> - unless @packages.length - @done() - @snippets.loaded = true - return - - @packageBeingLoaded = @packages.shift() - if @packageBeingLoaded instanceof TextMatePackage - @loadTextMateSnippets(@packageBeingLoaded.path) - else - @loadAtomSnippets(@packageBeingLoaded.path) - - loadAtomSnippets: (path) -> - @callWorkerMethod('loadAtomSnippets', path) - - loadTextMateSnippets: (path) -> - @callWorkerMethod('loadTextMateSnippets', path) - - snippetsLoaded: (snippets) -> - @snippets.add(snippet) for snippet in snippets - @loadNextPackageSnippets() diff --git a/src/packages/snippets/lib/snippets.coffee b/src/packages/snippets/lib/snippets.coffee index a76497497..3cd25e164 100644 --- a/src/packages/snippets/lib/snippets.coffee +++ b/src/packages/snippets/lib/snippets.coffee @@ -1,10 +1,12 @@ AtomPackage = require 'atom-package' -fs = require 'fs-utils' +fs = require 'fs' +fsUtils = require 'fs-utils' _ = require 'underscore' SnippetExpansion = require './snippet-expansion' Snippet = require './snippet' -LoadSnippetsTask = require './load-snippets-task' +TextMatePackage = require 'text-mate-package' CSON = require 'cson' +async = require 'async' module.exports = snippetsByExtension: {} @@ -20,19 +22,82 @@ module.exports = @loadSnippetsTask?.abort() loadAll: -> - @loadSnippetsTask = new LoadSnippetsTask(this) - @loadSnippetsTask.start() + packages = atom.getLoadedPackages() + packages.push(path: config.configDirPath) + async.eachSeries packages, @loadSnippetsFromPackage.bind(this), @doneLoading.bind(this) - loadDirectory: (snippetsDirPath) -> - for snippetsPath in fs.list(snippetsDirPath) when fs.base(snippetsPath).indexOf('.') isnt 0 - snippets.loadFile(snippetsPath) + doneLoading: -> + @loaded = true - loadFile: (snippetsPath) -> - try - snippets = CSON.readObject(snippetsPath) - catch e - console.warn "Error reading snippets file '#{snippetsPath}'" - @add(snippets) + loadSnippetsFromPackage: (pack, done) -> + if pack instanceof TextMatePackage + @loadTextMateSnippets(pack.path, done) + else + @loadAtomSnippets(pack.path, done) + + loadAtomSnippets: (path, done) -> + snippetsDirPath = fsUtils.join(path, 'snippets') + + loadSnippetFile = (filename, done) => + return done() if filename.indexOf('.') is 0 + filepath = fsUtils.join(snippetsDirPath, filename) + CSON.readObjectAsync filepath, (err, object) => + if err + console.warn "Error reading snippets file '#{filepath}': #{err.stack}" + else + @add(object) + done() + + fs.readdir snippetsDirPath, (err, paths) -> + async.each(paths, loadSnippetFile, done) + + loadTextMateSnippets: (path, done) -> + snippetsDirPath = fsUtils.join(path, 'Snippets') + + loadSnippetFile = (filename, done) => + return done() if filename.indexOf('.') is 0 + + filepath = fsUtils.join(snippetsDirPath, filename) + + logError = (err) -> + console.warn "Error reading snippets file '#{filepath}': #{err.stack ? err}" + + try + readObject = + if CSON.isObjectPath(filepath) + CSON.readObjectAsync.bind(CSON) + else + fsUtils.readPlistAsync.bind(fsUtils) + + readObject filepath, (err, object) => + try + if err + logError(err) + else + @add(@translateTextmateSnippet(object)) + catch err + logError(err) + finally + done() + catch err + logError(err) + done() + + return done() unless fsUtils.isDirectory(snippetsDirPath) + fs.readdir snippetsDirPath, (err, paths) -> + if err + console.warn err + return done() + async.each(paths, loadSnippetFile, done) + + translateTextmateSnippet: ({ scope, name, content, tabTrigger }) -> + scope = TextMatePackage.cssSelectorFromScopeSelector(scope) if scope + scope ?= '*' + snippetsByScope = {} + snippetsByName = {} + snippetsByScope[scope] = snippetsByName + snippetsByName[name] = { prefix: tabTrigger, body: content } + snippetsByScope add: (snippetsBySelector) -> for selector, snippetsByName of snippetsBySelector diff --git a/src/packages/snippets/spec/snippets-spec.coffee b/src/packages/snippets/spec/snippets-spec.coffee index 9c6c323ac..7fbb6871b 100644 --- a/src/packages/snippets/spec/snippets-spec.coffee +++ b/src/packages/snippets/spec/snippets-spec.coffee @@ -1,5 +1,4 @@ Snippet = require 'snippets/lib/snippet' -LoadSnippetsTask = require 'snippets/lib/load-snippets-task' RootView = require 'root-view' Buffer = require 'text-buffer' Editor = require 'editor' @@ -12,13 +11,12 @@ describe "Snippets extension", -> beforeEach -> window.rootView = new RootView rootView.open('sample.js') - spyOn(LoadSnippetsTask.prototype, 'start') packageWithSnippets = window.loadPackage("package-with-snippets") - spyOn(atom, "getLoadedPackages").andCallFake -> window.textMatePackages.concat([packageWithSnippets]) + spyOn(require("snippets/lib/snippets"), 'loadAll') window.loadPackage("snippets") editor = rootView.getActiveView() @@ -239,12 +237,13 @@ describe "Snippets extension", -> describe "snippet loading", -> beforeEach -> - jasmine.unspy(LoadSnippetsTask.prototype, 'start') - spyOn(LoadSnippetsTask.prototype, 'loadAtomSnippets').andCallFake -> @snippetsLoaded({}) - spyOn(LoadSnippetsTask.prototype, 'loadTextMateSnippets').andCallFake -> @snippetsLoaded({}) + jasmine.unspy(window, "setTimeout") + jasmine.unspy(snippets, 'loadAll') + spyOn(snippets, 'loadAtomSnippets').andCallFake (path, done) -> done() + spyOn(snippets, 'loadTextMateSnippets').andCallFake (path, done) -> done() it "loads non-hidden snippet files from all atom packages with snippets directories, logging a warning if a file can't be parsed", -> - jasmine.unspy(LoadSnippetsTask.prototype, 'loadAtomSnippets') + jasmine.unspy(snippets, 'loadAtomSnippets') spyOn(console, 'warn') snippets.loaded = false snippets.loadAll() @@ -259,7 +258,7 @@ describe "Snippets extension", -> expect(console.warn.calls.length).toBe 1 it "loads snippets from all TextMate packages with snippets", -> - jasmine.unspy(LoadSnippetsTask.prototype, 'loadTextMateSnippets') + jasmine.unspy(snippets, 'loadTextMateSnippets') spyOn(console, 'warn') snippets.loaded = false snippets.loadAll() @@ -281,28 +280,11 @@ describe "Snippets extension", -> expect(console.warn).toHaveBeenCalled() expect(console.warn.calls.length).toBe 1 - it "terminates the worker when loading completes", -> - jasmine.unspy(LoadSnippetsTask.prototype, 'loadAtomSnippets') - spyOn(console, "warn") - spyOn(Worker.prototype, 'terminate').andCallThrough() + it "loads CSON snippets from TextMate packages", -> + jasmine.unspy(snippets, 'loadTextMateSnippets') snippets.loaded = false snippets.loadAll() - waitsFor "all snippets to load", 5000, -> snippets.loaded - - runs -> - expect(console.warn).toHaveBeenCalled() - expect(console.warn.argsForCall[0]).toMatch /Error reading snippets file '.*?\/spec\/fixtures\/packages\/package-with-snippets\/snippets\/junk-file'/ - expect(Worker.prototype.terminate).toHaveBeenCalled() - expect(Worker.prototype.terminate.calls.length).toBe 1 - - it "loads CSON snippets from TextMate packages", -> - jasmine.unspy(LoadSnippetsTask.prototype, 'loadTextMateSnippets') - snippets.loaded = false - task = new LoadSnippetsTask(snippets) - task.packages = [Package.build(project.resolve('packages/package-with-a-cson-grammar.tmbundle'))] - task.start() - waitsFor "CSON snippets to load", 5000, -> snippets.loaded runs -> diff --git a/src/stdlib/cson.coffee b/src/stdlib/cson.coffee index f6ba3258d..1d105dc6a 100644 --- a/src/stdlib/cson.coffee +++ b/src/stdlib/cson.coffee @@ -1,25 +1,35 @@ _ = require 'underscore' -fs = require 'fs-utils' +fs = require 'fs' +fsUtils = require 'fs-utils' module.exports = isObjectPath: (path) -> - extension = fs.extension(path) + extension = fsUtils.extension(path) extension is '.cson' or extension is '.json' readObject: (path) -> - contents = fs.read(path) - if fs.extension(path) is '.cson' + @parseObject(path, fsUtils.read(path)) + + readObjectAsync: (path, done) -> + fs.readFile path, 'utf8', (err, contents) => + return done(err) if err? + try done(null, @parseObject(path, contents)) + catch err + done(err) + + parseObject: (path, contents) -> + if fsUtils.extension(path) is '.cson' CoffeeScript = require 'coffee-script' CoffeeScript.eval(contents, bare: true) else JSON.parse(contents) writeObject: (path, object) -> - if fs.extension(path) is '.cson' + if fsUtils.extension(path) is '.cson' content = @stringify(object) else content = JSON.stringify(object, undefined, 2) - fs.write(path, "#{content}\n") + fsUtils.write(path, "#{content}\n") stringifyIndent: (level=0) -> _.multiplyString(' ', Math.max(level, 0)) diff --git a/src/stdlib/fs-utils.coffee b/src/stdlib/fs-utils.coffee index d024ae4df..51fd3cc78 100644 --- a/src/stdlib/fs-utils.coffee +++ b/src/stdlib/fs-utils.coffee @@ -273,3 +273,16 @@ module.exports = throw new Error(e) if e object = data[0] object + + readPlistAsync: (path, done) -> + plist = require 'plist' + fs.readFile path, 'utf8', (err, contents) -> + return done(err) if err + [parseErr, object] = [] + # plist has an async api, but it isn't actually synchronous + # and it doesn't ever call our callback if there's invalid input + plist.parseString contents, (err, data) -> + parseErr = err + object = data[0] unless err + parseErr = "Could not parse plist at path: '#{path}'" unless object + done(parseErr, object)