--- /dev/null
+var chokidar = require('chokidar')
+var mm = require('minimatch')
+
+var helper = require('./helper')
+var log = require('./logger').create('watcher')
+
+var DIR_SEP = require('path').sep
+
+// Get parent folder, that be watched (does not contain any special globbing character)
+var baseDirFromPattern = function (pattern) {
+ return pattern
+ .replace(/[\/\\][^\/\\]*\*.*$/, '') // remove parts with *
+ .replace(/[\/\\][^\/\\]*[\!\+]\(.*$/, '') // remove parts with !(...) and +(...)
+ .replace(/[\/\\][^\/\\]*\)\?.*$/, '') || DIR_SEP // remove parts with (...)?
+}
+
+var watchPatterns = function (patterns, watcher) {
+ // filter only unique non url patterns paths
+ var pathsToWatch = []
+ var uniqueMap = {}
+ var path
+
+ patterns.forEach(function (pattern) {
+ path = baseDirFromPattern(pattern)
+ if (!uniqueMap[path]) {
+ uniqueMap[path] = true
+ pathsToWatch.push(path)
+ }
+ })
+
+ // watch only common parents, no sub paths
+ pathsToWatch.forEach(function (path) {
+ if (!pathsToWatch.some(function (p) {
+ return p !== path && path.substr(0, p.length + 1) === p + DIR_SEP
+ })) {
+ watcher.add(path)
+ log.debug('Watching "%s"', path)
+ }
+ })
+}
+
+// Function to test if a path should be ignored by chokidar.
+var createIgnore = function (patterns, excludes) {
+ return function (path, stat) {
+ if (!stat || stat.isDirectory()) {
+ return false
+ }
+
+ // Check if the path matches any of the watched patterns.
+ if (!patterns.some(function (pattern) {
+ return mm(path, pattern, {dot: true})
+ })) {
+ return true
+ }
+
+ // Check if the path matches any of the exclude patterns.
+ if (excludes.some(function (pattern) {
+ return mm(path, pattern, {dot: true})
+ })) {
+ return true
+ }
+
+ return false
+ }
+}
+
+var onlyWatchedTrue = function (pattern) {
+ return pattern.watched
+}
+
+var getWatchedPatterns = function (patternObjects) {
+ return patternObjects.filter(onlyWatchedTrue).map(function (patternObject) {
+ return patternObject.pattern
+ })
+}
+
+exports.watch = function (patterns, excludes, fileList, usePolling, emitter) {
+ var watchedPatterns = getWatchedPatterns(patterns)
+ var options = {
+ usePolling: usePolling,
+ ignorePermissionErrors: true,
+ ignoreInitial: true,
+ ignored: createIgnore(watchedPatterns, excludes)
+ }
+ var chokidarWatcher = new chokidar.FSWatcher(options)
+
+ watchPatterns(watchedPatterns, chokidarWatcher)
+
+ var bind = function (fn) {
+ return function (path) {
+ return fn.call(fileList, helper.normalizeWinPath(path))
+ }
+ }
+
+ // register events
+ chokidarWatcher.on('add', bind(fileList.addFile))
+ .on('change', bind(fileList.changeFile))
+ .on('unlink', bind(fileList.removeFile))
+ // If we don't subscribe; unhandled errors from Chokidar will bring Karma down
+ // (see GH Issue #959)
+ .on('error', function (e) {
+ log.debug(e)
+ })
+
+ emitter.on('exit', function (done) {
+ chokidarWatcher.close()
+ done()
+ })
+
+ return chokidarWatcher
+}
+
+exports.watch.$inject = ['config.files', 'config.exclude', 'fileList', 'config.usePolling',
+ 'emitter']