// Generated by CoffeeScript 1.12.7
(function() {
  var BookmarkCompleter, DomainCompleter, HistoryCache, HistoryCompleter, MultiCompleter, RankingUtils, RegexpCache, SearchEngineCompleter, Suggestion, TabCompleter, root,
    hasProp = {}.hasOwnProperty,
    slice = [].slice;

  Suggestion = (function() {
    Suggestion.prototype.showRelevancy = false;

    function Suggestion(options) {
      this.options = options;
      this.queryTerms = null;
      this.type = null;
      this.url = null;
      this.relevancyFunction = null;
      this.title = "";
      this.relevancyData = null;
      this.autoSelect = false;
      this.highlightTerms = true;
      this.insertText = null;
      this.deDuplicate = true;
      extend(this, this.options);
    }

    Suggestion.prototype.computeRelevancy = function() {
      return this.relevancy != null ? this.relevancy : this.relevancy = this.relevancyFunction(this);
    };

    Suggestion.prototype.generateHtml = function(request) {
      var insertTextClass, insertTextIndicator, relevancyHtml;
      if (this.html) {
        return this.html;
      }
      relevancyHtml = this.showRelevancy ? "<span class='relevancy'>" + (this.computeRelevancy()) + "</span>" : "";
      insertTextClass = this.insertText ? "vomnibarInsertText" : "vomnibarNoInsertText";
      insertTextIndicator = "&#8618;";
      if (this.insertText && request.isCustomSearch) {
        this.title = this.insertText;
      }
      return this.html = request.isCustomSearch ? "<div class=\"vimiumReset vomnibarTopHalf\">\n   <span class=\"vimiumReset vomnibarSource " + insertTextClass + "\">" + insertTextIndicator + "</span><span class=\"vimiumReset vomnibarSource\">" + this.type + "</span>\n   <span class=\"vimiumReset vomnibarTitle\">" + (this.highlightQueryTerms(Utils.escapeHtml(this.title))) + "</span>\n   " + relevancyHtml + "\n </div>" : "<div class=\"vimiumReset vomnibarTopHalf\">\n   <span class=\"vimiumReset vomnibarSource " + insertTextClass + "\">" + insertTextIndicator + "</span><span class=\"vimiumReset vomnibarSource\">" + this.type + "</span>\n   <span class=\"vimiumReset vomnibarTitle\">" + (this.highlightQueryTerms(Utils.escapeHtml(this.title))) + "</span>\n </div>\n <div class=\"vimiumReset vomnibarBottomHalf\">\n  <span class=\"vimiumReset vomnibarSource vomnibarNoInsertText\">" + insertTextIndicator + "</span><span class=\"vimiumReset vomnibarUrl\">" + (this.highlightUrlTerms(Utils.escapeHtml(this.shortenUrl()))) + "</span>\n  " + relevancyHtml + "\n</div>";
    };

    Suggestion.prototype.getUrlRoot = function(url) {
      var a;
      a = document.createElement('a');
      a.href = url;
      return a.protocol + "//" + a.hostname;
    };

    Suggestion.prototype.getHostname = function(url) {
      var a;
      a = document.createElement('a');
      a.href = url;
      return a.hostname;
    };

    Suggestion.prototype.stripTrailingSlash = function(url) {
      if (url[url.length - 1] === "/") {
        url = url.substring(url, url.length - 1);
      }
      return url;
    };

    Suggestion.prototype.pushMatchingRanges = function(string, term, ranges) {
      var index, j, matchedText, ref, results1, splits, textPosition, unmatchedText;
      textPosition = 0;
      splits = string.split(RegexpCache.get(term, "(", ")"));
      results1 = [];
      for (index = j = 0, ref = splits.length - 2; j <= ref; index = j += 2) {
        unmatchedText = splits[index];
        matchedText = splits[index + 1];
        textPosition += unmatchedText.length;
        ranges.push([textPosition, textPosition + matchedText.length]);
        results1.push(textPosition += matchedText.length);
      }
      return results1;
    };

    Suggestion.prototype.highlightQueryTerms = function(string) {
      var end, escapedTerms, j, k, len, len1, ranges, ref, start, term;
      if (!this.highlightTerms) {
        return string;
      }
      ranges = [];
      escapedTerms = this.queryTerms.map(function(term) {
        return Utils.escapeHtml(term);
      });
      for (j = 0, len = escapedTerms.length; j < len; j++) {
        term = escapedTerms[j];
        this.pushMatchingRanges(string, term, ranges);
      }
      if (ranges.length === 0) {
        return string;
      }
      ranges = this.mergeRanges(ranges.sort(function(a, b) {
        return a[0] - b[0];
      }));
      ranges = ranges.sort(function(a, b) {
        return b[0] - a[0];
      });
      for (k = 0, len1 = ranges.length; k < len1; k++) {
        ref = ranges[k], start = ref[0], end = ref[1];
        string = string.substring(0, start) + ("<span class='vomnibarMatch'>" + (string.substring(start, end)) + "</span>") + string.substring(end);
      }
      return string;
    };

    Suggestion.prototype.highlightUrlTerms = function(string) {
      if (this.highlightTermsExcludeUrl) {
        return string;
      } else {
        return this.highlightQueryTerms(string);
      }
    };

    Suggestion.prototype.mergeRanges = function(ranges) {
      var mergedRanges, previous;
      previous = ranges.shift();
      mergedRanges = [previous];
      ranges.forEach(function(range) {
        if (previous[1] >= range[0]) {
          return previous[1] = Math.max(range[1], previous[1]);
        } else {
          mergedRanges.push(range);
          return previous = range;
        }
      });
      return mergedRanges;
    };

    Suggestion.prototype.shortenUrl = function() {
      var filter, j, k, len, len1, ref, ref1, replace, replacements, url;
      if (this.shortUrl != null) {
        return this.shortUrl;
      }
      url = (Utils.decodeURIByParts(this.url) || this.url).toLowerCase();
      ref = this.stripPatterns;
      for (j = 0, len = ref.length; j < len; j++) {
        ref1 = ref[j], filter = ref1[0], replacements = ref1[1];
        if (new RegExp(filter).test(url)) {
          for (k = 0, len1 = replacements.length; k < len1; k++) {
            replace = replacements[k];
            url = url.replace(replace, "");
          }
        }
      }
      return this.shortUrl = url;
    };

    Suggestion.prototype.stripPatterns = [
      [
        "^https?://www\\.google\\.(com|ca|com\\.au|co\\.uk|ie)/.*[&?]q=", "ei gws_rd url ved usg sa usg sig2 bih biw cd aqs ie sourceid es_sm".split(/\s+/).map(function(param) {
          return new RegExp("\&" + param + "=[^&]+");
        })
      ], ["^https?://www\\.google\\.(com|ca|com\\.au|co\\.uk|ie)/maps/place/.*/@", [new RegExp("/@.*")]], [
        '.', ["^https?://", "\\W+$"].map(function(re) {
          return new RegExp(re);
        })
      ]
    ];

    Suggestion.boostRelevancyScore = function(factor, score) {
      return score + (score < 0.5 ? score * factor : (1.0 - score) * factor);
    };

    return Suggestion;

  })();

  BookmarkCompleter = (function() {
    function BookmarkCompleter() {}

    BookmarkCompleter.prototype.folderSeparator = "/";

    BookmarkCompleter.prototype.currentSearch = null;

    BookmarkCompleter.prototype.bookmarks = null;

    BookmarkCompleter.prototype.filter = function(arg, onComplete1) {
      this.queryTerms = arg.queryTerms;
      this.onComplete = onComplete1;
      this.currentSearch = {
        queryTerms: this.queryTerms,
        onComplete: this.onComplete
      };
      if (this.bookmarks) {
        return this.performSearch();
      }
    };

    BookmarkCompleter.prototype.onBookmarksLoaded = function() {
      if (this.currentSearch) {
        return this.performSearch();
      }
    };

    BookmarkCompleter.prototype.performSearch = function() {
      var onComplete, results, suggestions, usePathAndTitle;
      usePathAndTitle = this.currentSearch.queryTerms.reduce(((function(_this) {
        return function(prev, term) {
          return prev || term.indexOf(_this.folderSeparator) === 0;
        };
      })(this)), false);
      results = this.currentSearch.queryTerms.length > 0 ? this.bookmarks.filter((function(_this) {
        return function(bookmark) {
          var ref, suggestionTitle, suggestionUrl;
          suggestionTitle = usePathAndTitle ? bookmark.pathAndTitle : bookmark.title;
          if (bookmark.hasJavascriptPrefix == null) {
            bookmark.hasJavascriptPrefix = Utils.hasJavascriptPrefix(bookmark.url);
          }
          if (bookmark.hasJavascriptPrefix) {
            if (bookmark.shortUrl == null) {
              bookmark.shortUrl = "javascript:...";
            }
          }
          suggestionUrl = (ref = bookmark.shortUrl) != null ? ref : bookmark.url;
          return RankingUtils.matches(_this.currentSearch.queryTerms, suggestionUrl, suggestionTitle);
        };
      })(this)) : [];
      suggestions = results.map((function(_this) {
        return function(bookmark) {
          return new Suggestion({
            queryTerms: _this.currentSearch.queryTerms,
            type: "bookmark",
            url: bookmark.url,
            title: usePathAndTitle ? bookmark.pathAndTitle : bookmark.title,
            relevancyFunction: _this.computeRelevancy,
            shortUrl: bookmark.shortUrl,
            deDuplicate: bookmark.shortUrl == null
          });
        };
      })(this));
      onComplete = this.currentSearch.onComplete;
      this.currentSearch = null;
      return onComplete(suggestions);
    };

    BookmarkCompleter.prototype.refresh = function() {
      this.bookmarks = null;
      return chrome.bookmarks.getTree((function(_this) {
        return function(bookmarks) {
          _this.bookmarks = _this.traverseBookmarks(bookmarks).filter(function(bookmark) {
            return bookmark.url != null;
          });
          return _this.onBookmarksLoaded();
        };
      })(this));
    };

    BookmarkCompleter.prototype.ignoreTopLevel = {
      'Other Bookmarks': true,
      'Mobile Bookmarks': true,
      'Bookmarks Bar': true
    };

    BookmarkCompleter.prototype.traverseBookmarks = function(bookmarks) {
      var results;
      results = [];
      bookmarks.forEach((function(_this) {
        return function(folder) {
          return _this.traverseBookmarksRecursive(folder, results);
        };
      })(this));
      return results;
    };

    BookmarkCompleter.prototype.traverseBookmarksRecursive = function(bookmark, results, parent) {
      if (parent == null) {
        parent = {
          pathAndTitle: ""
        };
      }
      bookmark.pathAndTitle = bookmark.title && !(parent.pathAndTitle === "" && this.ignoreTopLevel[bookmark.title]) ? parent.pathAndTitle + this.folderSeparator + bookmark.title : parent.pathAndTitle;
      results.push(bookmark);
      if (bookmark.children) {
        return bookmark.children.forEach((function(_this) {
          return function(child) {
            return _this.traverseBookmarksRecursive(child, results, bookmark);
          };
        })(this));
      }
    };

    BookmarkCompleter.prototype.computeRelevancy = function(suggestion) {
      var ref;
      return RankingUtils.wordRelevancy(suggestion.queryTerms, (ref = suggestion.shortUrl) != null ? ref : suggestion.url, suggestion.title);
    };

    return BookmarkCompleter;

  })();

  HistoryCompleter = (function() {
    function HistoryCompleter() {}

    HistoryCompleter.prototype.filter = function(arg, onComplete) {
      var queryTerms, seenTabToOpenCompletionList;
      queryTerms = arg.queryTerms, seenTabToOpenCompletionList = arg.seenTabToOpenCompletionList;
      if (queryTerms.length === 0 && !seenTabToOpenCompletionList) {
        onComplete([]);
        return Utils.nextTick(function() {
          return HistoryCache.use(function() {});
        });
      } else {
        return HistoryCache.use((function(_this) {
          return function(history) {
            var results;
            results = 0 < queryTerms.length ? history.filter(function(entry) {
              return RankingUtils.matches(queryTerms, entry.url, entry.title);
            }) : history;
            return onComplete(results.map(function(entry) {
              return new Suggestion({
                queryTerms: queryTerms,
                type: "history",
                url: entry.url,
                title: entry.title,
                relevancyFunction: _this.computeRelevancy,
                relevancyData: entry
              });
            }));
          };
        })(this));
      }
    };

    HistoryCompleter.prototype.computeRelevancy = function(suggestion) {
      var historyEntry, recencyScore, wordRelevancy;
      historyEntry = suggestion.relevancyData;
      recencyScore = RankingUtils.recencyScore(historyEntry.lastVisitTime);
      if (suggestion.queryTerms.length === 0) {
        return recencyScore;
      }
      wordRelevancy = RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.url, suggestion.title);
      return (wordRelevancy + Math.max(recencyScore, wordRelevancy)) / 2;
    };

    return HistoryCompleter;

  })();

  DomainCompleter = (function() {
    function DomainCompleter() {}

    DomainCompleter.prototype.domains = null;

    DomainCompleter.prototype.filter = function(arg, onComplete) {
      var query, queryTerms;
      queryTerms = arg.queryTerms, query = arg.query;
      if (queryTerms.length === 0 || /\S\s/.test(query)) {
        return onComplete([]);
      }
      if (this.domains) {
        return this.performSearch(queryTerms, onComplete);
      } else {
        return this.populateDomains((function(_this) {
          return function() {
            return _this.performSearch(queryTerms, onComplete);
          };
        })(this));
      }
    };

    DomainCompleter.prototype.performSearch = function(queryTerms, onComplete) {
      var domain, domains, query, ref, ref1;
      query = queryTerms[0];
      domains = (function() {
        var ref, results1;
        ref = this.domains;
        results1 = [];
        for (domain in ref) {
          if (!hasProp.call(ref, domain)) continue;
          if (0 <= domain.indexOf(query)) {
            results1.push(domain);
          }
        }
        return results1;
      }).call(this);
      domains = this.sortDomainsByRelevancy(queryTerms, domains);
      return onComplete([
        new Suggestion({
          queryTerms: queryTerms,
          type: "domain",
          url: (ref = (ref1 = domains[0]) != null ? ref1[0] : void 0) != null ? ref : "",
          relevancy: 2.0
        })
      ].filter(function(s) {
        return 0 < s.url.length;
      }));
    };

    DomainCompleter.prototype.sortDomainsByRelevancy = function(queryTerms, domainCandidates) {
      var domain, recencyScore, results, score, wordRelevancy;
      results = (function() {
        var j, len, results1;
        results1 = [];
        for (j = 0, len = domainCandidates.length; j < len; j++) {
          domain = domainCandidates[j];
          recencyScore = RankingUtils.recencyScore(this.domains[domain].entry.lastVisitTime || 0);
          wordRelevancy = RankingUtils.wordRelevancy(queryTerms, domain, null);
          score = (wordRelevancy + Math.max(recencyScore, wordRelevancy)) / 2;
          results1.push([domain, score]);
        }
        return results1;
      }).call(this);
      results.sort(function(a, b) {
        return b[1] - a[1];
      });
      return results;
    };

    DomainCompleter.prototype.populateDomains = function(onComplete) {
      return HistoryCache.use((function(_this) {
        return function(history) {
          _this.domains = {};
          history.forEach(function(entry) {
            return _this.onPageVisited(entry);
          });
          chrome.history.onVisited.addListener(_this.onPageVisited.bind(_this));
          chrome.history.onVisitRemoved.addListener(_this.onVisitRemoved.bind(_this));
          return onComplete();
        };
      })(this));
    };

    DomainCompleter.prototype.onPageVisited = function(newPage) {
      var base, domain, slot;
      domain = this.parseDomainAndScheme(newPage.url);
      if (domain) {
        slot = (base = this.domains)[domain] || (base[domain] = {
          entry: newPage,
          referenceCount: 0
        });
        if (slot.entry.lastVisitTime < newPage.lastVisitTime) {
          slot.entry = newPage;
        }
        return slot.referenceCount += 1;
      }
    };

    DomainCompleter.prototype.onVisitRemoved = function(toRemove) {
      if (toRemove.allHistory) {
        return this.domains = {};
      } else {
        return toRemove.urls.forEach((function(_this) {
          return function(url) {
            var domain;
            domain = _this.parseDomainAndScheme(url);
            if (domain && _this.domains[domain] && (_this.domains[domain].referenceCount -= 1) === 0) {
              return delete _this.domains[domain];
            }
          };
        })(this));
      }
    };

    DomainCompleter.prototype.parseDomainAndScheme = function(url) {
      return Utils.hasFullUrlPrefix(url) && !Utils.hasChromePrefix(url) && url.split("/", 3).join("/");
    };

    return DomainCompleter;

  })();

  TabCompleter = (function() {
    function TabCompleter() {}

    TabCompleter.prototype.filter = function(arg, onComplete) {
      var queryTerms;
      queryTerms = arg.queryTerms;
      return chrome.tabs.query({}, (function(_this) {
        return function(tabs) {
          var results, suggestions;
          results = tabs.filter(function(tab) {
            return RankingUtils.matches(queryTerms, tab.url, tab.title);
          });
          suggestions = results.map(function(tab) {
            return new Suggestion({
              queryTerms: queryTerms,
              type: "tab",
              url: tab.url,
              title: tab.title,
              relevancyFunction: _this.computeRelevancy,
              tabId: tab.id,
              deDuplicate: false
            });
          });
          return onComplete(suggestions);
        };
      })(this));
    };

    TabCompleter.prototype.computeRelevancy = function(suggestion) {
      if (suggestion.queryTerms.length) {
        return RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.url, suggestion.title);
      } else {
        return BgUtils.tabRecency.recencyScore(suggestion.tabId);
      }
    };

    return TabCompleter;

  })();

  SearchEngineCompleter = (function() {
    function SearchEngineCompleter() {}

    SearchEngineCompleter.debug = false;

    SearchEngineCompleter.prototype.previousSuggestions = null;

    SearchEngineCompleter.prototype.cancel = function() {
      return CompletionSearch.cancel();
    };

    SearchEngineCompleter.prototype.preprocessRequest = function(request) {
      return SearchEngines.use((function(_this) {
        return function(engines) {
          var key, keyword, query, queryTerms;
          queryTerms = request.queryTerms, query = request.query;
          for (key in engines) {
            if (!hasProp.call(engines, key)) continue;
            extend(request, {
              searchEngines: engines,
              keywords: key
            });
          }
          keyword = queryTerms[0];
          if (keyword && engines[keyword] && (1 < queryTerms.length || /\S\s/.test(query))) {
            return extend(request, {
              queryTerms: queryTerms.slice(1),
              keyword: keyword,
              engine: engines[keyword],
              isCustomSearch: true
            });
          }
        };
      })(this));
    };

    SearchEngineCompleter.prototype.refresh = function(port) {
      this.previousSuggestions = {};
      return SearchEngines.refreshAndUse(Settings.get("searchEngines"), function(engines) {
        var key;
        return port.postMessage({
          handler: "keywords",
          keywords: (function() {
            var results1;
            results1 = [];
            for (key in engines) {
              if (!hasProp.call(engines, key)) continue;
              results1.push(key);
            }
            return results1;
          })()
        });
      });
    };

    SearchEngineCompleter.prototype.filter = function(request, onComplete) {
      var _, base, cachedSuggestions, description, engine, filter, haveCompletionEngine, keyword, mkSuggestion, previousSuggestions, primarySuggestion, query, queryTerms, searchUrl, suggestion, suggestions;
      queryTerms = request.queryTerms, query = request.query, engine = request.engine;
      if (!engine) {
        return onComplete([]);
      }
      keyword = engine.keyword, searchUrl = engine.searchUrl, description = engine.description;
      extend(request, searchUrl, {
        customSearchMode: true
      });
      if ((base = this.previousSuggestions)[searchUrl] == null) {
        base[searchUrl] = [];
      }
      haveCompletionEngine = CompletionSearch.haveCompletionEngine(searchUrl);
      filter = function(suggestions) {
        var j, len, results1, suggestion, terms;
        results1 = [];
        for (j = 0, len = suggestions.length; j < len; j++) {
          suggestion = suggestions[j];
          if (suggestion.isSearchSuggestion || suggestion.isCustomSearch) {
            results1.push(suggestion);
          } else {
            terms = Utils.extractQuery(searchUrl, suggestion.url);
            if (!(terms && RankingUtils.matches(queryTerms, terms))) {
              continue;
            }
            suggestion.url = Utils.createSearchUrl(terms, searchUrl);
            results1.push(suggestion);
          }
        }
        return results1;
      };
      previousSuggestions = (function() {
        var ref, results1;
        if (queryTerms.length === 0) {
          return [];
        } else {
          ref = this.previousSuggestions[searchUrl];
          results1 = [];
          for (_ in ref) {
            if (!hasProp.call(ref, _)) continue;
            suggestion = ref[_];
            if (!RankingUtils.matches(queryTerms, suggestion.title)) {
              continue;
            }
            extend(suggestion, {
              relevancy: null,
              html: null,
              queryTerms: queryTerms
            });
            suggestion.relevancy = null;
            results1.push(suggestion);
          }
          return results1;
        }
      }).call(this);
      primarySuggestion = new Suggestion({
        queryTerms: queryTerms,
        type: description,
        url: Utils.createSearchUrl(queryTerms, searchUrl),
        title: queryTerms.join(" "),
        searchUrl: searchUrl,
        relevancy: 2.0,
        autoSelect: true,
        highlightTerms: false,
        isSearchSuggestion: true,
        isPrimarySuggestion: true
      });
      if (queryTerms.length === 0) {
        return onComplete([primarySuggestion], {
          filter: filter
        });
      }
      mkSuggestion = (function(_this) {
        return function() {
          var count;
          count = 0;
          return function(suggestion) {
            var url;
            url = Utils.createSearchUrl(suggestion, searchUrl);
            return _this.previousSuggestions[searchUrl][url] = new Suggestion({
              queryTerms: queryTerms,
              type: description,
              url: url,
              title: suggestion,
              searchUrl: searchUrl,
              insertText: suggestion,
              highlightTerms: false,
              highlightTermsExcludeUrl: true,
              isCustomSearch: true,
              relevancy: ++count === 1 ? 1.0 : null,
              relevancyFunction: _this.computeRelevancy
            });
          };
        };
      })(this)();
      cachedSuggestions = haveCompletionEngine ? CompletionSearch.complete(searchUrl, queryTerms) : null;
      suggestions = previousSuggestions;
      suggestions.push(primarySuggestion);
      if (queryTerms.length === 0 || (cachedSuggestions != null) || !haveCompletionEngine) {
        if (cachedSuggestions != null) {
          suggestions.push.apply(suggestions, cachedSuggestions.map(mkSuggestion));
        }
        return onComplete(suggestions, {
          filter: filter,
          continuation: null
        });
      } else {
        return onComplete(suggestions, {
          filter: filter,
          continuation: (function(_this) {
            return function(onComplete) {
              return CompletionSearch.complete(searchUrl, queryTerms, function(suggestions) {
                if (suggestions == null) {
                  suggestions = [];
                }
                if (SearchEngineCompleter.debug) {
                  console.log("fetched suggestions:", suggestions.length, query);
                }
                return onComplete(suggestions.map(mkSuggestion));
              });
            };
          })(this)
        });
      }
    };

    SearchEngineCompleter.prototype.computeRelevancy = function(arg) {
      var queryTerms, relevancyData, title;
      relevancyData = arg.relevancyData, queryTerms = arg.queryTerms, title = arg.title;
      return Suggestion.boostRelevancyScore(0.5, 0.7 * RankingUtils.wordRelevancy(queryTerms, title, title));
    };

    SearchEngineCompleter.prototype.postProcessSuggestions = function(request, suggestions) {
      var _, engine, engines, j, len, results1, suggestion;
      if (!request.searchEngines) {
        return;
      }
      engines = (function() {
        var ref, results1;
        ref = request.searchEngines;
        results1 = [];
        for (_ in ref) {
          if (!hasProp.call(ref, _)) continue;
          engine = ref[_];
          results1.push(engine);
        }
        return results1;
      })();
      engines.sort(function(a, b) {
        return b.searchUrl.length - a.searchUrl.length;
      });
      engines.push({
        keyword: null,
        description: "search history",
        searchUrl: Settings.get("searchUrl")
      });
      results1 = [];
      for (j = 0, len = suggestions.length; j < len; j++) {
        suggestion = suggestions[j];
        if (!(suggestion.isSearchSuggestion || suggestion.insertText)) {
          results1.push((function() {
            var k, len1, results2;
            results2 = [];
            for (k = 0, len1 = engines.length; k < len1; k++) {
              engine = engines[k];
              if (suggestion.insertText = Utils.extractQuery(engine.searchUrl, suggestion.url)) {
                suggestion.customSearchMode = engine.keyword;
                suggestion.title || (suggestion.title = suggestion.insertText);
                break;
              } else {
                results2.push(void 0);
              }
            }
            return results2;
          })());
        } else {
          results1.push(void 0);
        }
      }
      return results1;
    };

    return SearchEngineCompleter;

  })();

  MultiCompleter = (function() {
    MultiCompleter.prototype.maxResults = 10;

    MultiCompleter.prototype.filterInProgress = false;

    MultiCompleter.prototype.mostRecentQuery = null;

    function MultiCompleter(completers) {
      this.completers = completers;
    }

    MultiCompleter.prototype.refresh = function(port) {
      var completer, j, len, ref, results1;
      ref = this.completers;
      results1 = [];
      for (j = 0, len = ref.length; j < len; j++) {
        completer = ref[j];
        results1.push(typeof completer.refresh === "function" ? completer.refresh(port) : void 0);
      }
      return results1;
    };

    MultiCompleter.prototype.cancel = function(port) {
      var completer, j, len, ref, results1;
      ref = this.completers;
      results1 = [];
      for (j = 0, len = ref.length; j < len; j++) {
        completer = ref[j];
        results1.push(typeof completer.cancel === "function" ? completer.cancel(port) : void 0);
      }
      return results1;
    };

    MultiCompleter.prototype.filter = function(request, onComplete) {
      var completer, continuations, filters, j, jobs, len, queryTerms, ref, ref1, ref2, suggestions;
      if (this.filterInProgress) {
        return this.mostRecentQuery = arguments;
      }
      ref = this.completers;
      for (j = 0, len = ref.length; j < len; j++) {
        completer = ref[j];
        if (typeof completer.preprocessRequest === "function") {
          completer.preprocessRequest(request);
        }
      }
      RegexpCache.clear();
      queryTerms = request.queryTerms;
      ref1 = [null, true], this.mostRecentQuery = ref1[0], this.filterInProgress = ref1[1];
      ref2 = [[], [], []], suggestions = ref2[0], continuations = ref2[1], filters = ref2[2];
      jobs = new JobRunner(this.completers.map(function(completer) {
        return function(callback) {
          return completer.filter(request, function(newSuggestions, arg) {
            var continuation, filter, ref3;
            if (newSuggestions == null) {
              newSuggestions = [];
            }
            ref3 = arg != null ? arg : {}, continuation = ref3.continuation, filter = ref3.filter;
            suggestions.push.apply(suggestions, newSuggestions);
            if (continuation != null) {
              continuations.push(continuation);
            }
            if (filter != null) {
              filters.push(filter);
            }
            return callback();
          });
        };
      }));
      return jobs.onReady((function(_this) {
        return function() {
          var filter, k, len1, shouldRunContinuations;
          for (k = 0, len1 = filters.length; k < len1; k++) {
            filter = filters[k];
            suggestions = filter(suggestions);
          }
          shouldRunContinuations = 0 < continuations.length && (_this.mostRecentQuery == null);
          if (!(suggestions.length === 0 && shouldRunContinuations)) {
            suggestions = _this.prepareSuggestions(request, queryTerms, suggestions);
            onComplete({
              results: suggestions
            });
          }
          if (shouldRunContinuations) {
            jobs = new JobRunner(continuations.map(function(continuation) {
              return function(callback) {
                return continuation(function(newSuggestions) {
                  suggestions.push.apply(suggestions, newSuggestions);
                  return callback();
                });
              };
            }));
            jobs.onReady(function() {
              var l, len2;
              for (l = 0, len2 = filters.length; l < len2; l++) {
                filter = filters[l];
                suggestions = filter(suggestions);
              }
              suggestions = _this.prepareSuggestions(request, queryTerms, suggestions);
              return onComplete({
                results: suggestions
              });
            });
          }
          _this.filterInProgress = false;
          if (_this.mostRecentQuery) {
            return _this.filter.apply(_this, _this.mostRecentQuery);
          }
        };
      })(this));
    };

    MultiCompleter.prototype.prepareSuggestions = function(request, queryTerms, suggestions) {
      var completer, count, j, k, l, len, len1, len2, ref, seenUrls, suggestion, url;
      for (j = 0, len = suggestions.length; j < len; j++) {
        suggestion = suggestions[j];
        suggestion.computeRelevancy(queryTerms);
      }
      suggestions.sort(function(a, b) {
        return b.relevancy - a.relevancy;
      });
      count = 0;
      seenUrls = {};
      suggestions = (function() {
        var k, len1, results1;
        results1 = [];
        for (k = 0, len1 = suggestions.length; k < len1; k++) {
          suggestion = suggestions[k];
          url = suggestion.shortenUrl();
          if (suggestion.deDuplicate && seenUrls[url]) {
            continue;
          }
          if (count++ === this.maxResults) {
            break;
          }
          results1.push(seenUrls[url] = suggestion);
        }
        return results1;
      }).call(this);
      ref = this.completers;
      for (k = 0, len1 = ref.length; k < len1; k++) {
        completer = ref[k];
        if (typeof completer.postProcessSuggestions === "function") {
          completer.postProcessSuggestions(request, suggestions);
        }
      }
      for (l = 0, len2 = suggestions.length; l < len2; l++) {
        suggestion = suggestions[l];
        suggestion.generateHtml(request);
      }
      return suggestions;
    };

    return MultiCompleter;

  })();

  RankingUtils = {
    matches: function() {
      var j, k, len, len1, matchedTerm, queryTerms, regexp, term, thing, things;
      queryTerms = arguments[0], things = 2 <= arguments.length ? slice.call(arguments, 1) : [];
      for (j = 0, len = queryTerms.length; j < len; j++) {
        term = queryTerms[j];
        regexp = RegexpCache.get(term);
        matchedTerm = false;
        for (k = 0, len1 = things.length; k < len1; k++) {
          thing = things[k];
          matchedTerm || (matchedTerm = thing.match(regexp));
        }
        if (!matchedTerm) {
          return false;
        }
      }
      return true;
    },
    matchWeights: {
      matchAnywhere: 1,
      matchStartOfWord: 1,
      matchWholeWord: 1,
      maximumScore: 3,
      recencyCalibrator: 2.0 / 3.0
    },
    scoreTerm: function(term, string) {
      var count, nonMatching, score;
      score = 0;
      count = 0;
      nonMatching = string.split(RegexpCache.get(term));
      if (nonMatching.length > 1) {
        score = RankingUtils.matchWeights.matchAnywhere;
        count = nonMatching.reduce((function(p, c) {
          return p - c.length;
        }), string.length);
        if (RegexpCache.get(term, "\\b").test(string)) {
          score += RankingUtils.matchWeights.matchStartOfWord;
          if (RegexpCache.get(term, "\\b", "\\b").test(string)) {
            score += RankingUtils.matchWeights.matchWholeWord;
          }
        }
      }
      return [score, count < string.length ? count : string.length];
    },
    wordRelevancy: function(queryTerms, url, title) {
      var c, j, len, maximumPossibleScore, ref, ref1, s, term, titleCount, titleScore, urlCount, urlScore;
      urlScore = titleScore = 0.0;
      urlCount = titleCount = 0;
      for (j = 0, len = queryTerms.length; j < len; j++) {
        term = queryTerms[j];
        ref = RankingUtils.scoreTerm(term, url), s = ref[0], c = ref[1];
        urlScore += s;
        urlCount += c;
        if (title) {
          ref1 = RankingUtils.scoreTerm(term, title), s = ref1[0], c = ref1[1];
          titleScore += s;
          titleCount += c;
        }
      }
      maximumPossibleScore = RankingUtils.matchWeights.maximumScore * queryTerms.length;
      urlScore /= maximumPossibleScore;
      urlScore *= RankingUtils.normalizeDifference(urlCount, url.length);
      if (title) {
        titleScore /= maximumPossibleScore;
        titleScore *= RankingUtils.normalizeDifference(titleCount, title.length);
      } else {
        titleScore = urlScore;
      }
      if (urlScore < titleScore) {
        urlScore = titleScore;
      }
      return (urlScore + titleScore) / 2;
    },
    recencyScore: function(lastAccessedTime) {
      var recency, recencyDifference, recencyScore;
      this.oneMonthAgo || (this.oneMonthAgo = 1000 * 60 * 60 * 24 * 30);
      recency = Date.now() - lastAccessedTime;
      recencyDifference = Math.max(0, this.oneMonthAgo - recency) / this.oneMonthAgo;
      recencyScore = recencyDifference * recencyDifference * recencyDifference;
      return recencyScore *= RankingUtils.matchWeights.recencyCalibrator;
    },
    normalizeDifference: function(a, b) {
      var max;
      max = Math.max(a, b);
      return (max - Math.abs(a - b)) / max;
    }
  };

  RegexpCache = {
    init: function() {
      this.initialized = true;
      return this.clear();
    },
    clear: function() {
      return this.cache = {};
    },
    get: function(string, prefix, suffix) {
      var base, regexpString;
      if (prefix == null) {
        prefix = "";
      }
      if (suffix == null) {
        suffix = "";
      }
      if (!this.initialized) {
        this.init();
      }
      regexpString = Utils.escapeRegexSpecialCharacters(string);
      if (prefix) {
        regexpString = prefix + regexpString;
      }
      if (suffix) {
        regexpString = regexpString + suffix;
      }
      return (base = this.cache)[regexpString] || (base[regexpString] = new RegExp(regexpString, (Utils.hasUpperCase(string) ? "" : "i")));
    }
  };

  HistoryCache = {
    size: 20000,
    history: null,
    reset: function() {
      this.history = null;
      return this.callbacks = null;
    },
    use: function(callback) {
      if (this.history != null) {
        return callback(this.history);
      } else {
        return this.fetchHistory(callback);
      }
    },
    fetchHistory: function(callback) {
      if (this.callbacks) {
        return this.callbacks.push(callback);
      }
      this.callbacks = [callback];
      return chrome.history.search({
        text: "",
        maxResults: this.size,
        startTime: 0
      }, (function(_this) {
        return function(history) {
          var j, len, ref;
          history.map(function(entry) {
            return entry.title != null ? entry.title : entry.title = "";
          });
          history.sort(_this.compareHistoryByUrl);
          _this.history = history;
          chrome.history.onVisited.addListener(_this.onPageVisited.bind(_this));
          chrome.history.onVisitRemoved.addListener(_this.onVisitRemoved.bind(_this));
          ref = _this.callbacks;
          for (j = 0, len = ref.length; j < len; j++) {
            callback = ref[j];
            callback(_this.history);
          }
          return _this.callbacks = null;
        };
      })(this));
    },
    compareHistoryByUrl: function(a, b) {
      if (a.url === b.url) {
        return 0;
      }
      if (a.url > b.url) {
        return 1;
      }
      return -1;
    },
    onPageVisited: function(newPage) {
      var i, pageWasFound, ref;
      if (newPage.title == null) {
        newPage.title = "";
      }
      i = HistoryCache.binarySearch(newPage, this.history, this.compareHistoryByUrl);
      pageWasFound = ((ref = this.history[i]) != null ? ref.url : void 0) === newPage.url;
      if (pageWasFound) {
        return this.history[i] = newPage;
      } else {
        return this.history.splice(i, 0, newPage);
      }
    },
    onVisitRemoved: function(toRemove) {
      if (toRemove.allHistory) {
        return this.history = [];
      } else {
        return toRemove.urls.forEach((function(_this) {
          return function(url) {
            var i;
            i = HistoryCache.binarySearch({
              url: url
            }, _this.history, _this.compareHistoryByUrl);
            if (i < _this.history.length && _this.history[i].url === url) {
              return _this.history.splice(i, 1);
            }
          };
        })(this));
      }
    }
  };

  HistoryCache.binarySearch = function(targetElement, array, compareFunction) {
    var compareResult, element, high, low, middle;
    high = array.length - 1;
    low = 0;
    while (low <= high) {
      middle = Math.floor((low + high) / 2);
      element = array[middle];
      compareResult = compareFunction(element, targetElement);
      if (compareResult > 0) {
        high = middle - 1;
      } else if (compareResult < 0) {
        low = middle + 1;
      } else {
        return middle;
      }
    }
    if (compareFunction(element, targetElement) < 0) {
      return middle + 1;
    } else {
      return middle;
    }
  };

  root = typeof exports !== "undefined" && exports !== null ? exports : window;

  root.Suggestion = Suggestion;

  root.BookmarkCompleter = BookmarkCompleter;

  root.MultiCompleter = MultiCompleter;

  root.HistoryCompleter = HistoryCompleter;

  root.DomainCompleter = DomainCompleter;

  root.TabCompleter = TabCompleter;

  root.SearchEngineCompleter = SearchEngineCompleter;

  root.HistoryCache = HistoryCache;

  root.RankingUtils = RankingUtils;

  root.RegexpCache = RegexpCache;

}).call(this);
