[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/webroot/rsrc/js/application/diffusion/ -> DiffusionLocateFileSource.js (source)

   1  /**
   2   * @provides javelin-diffusion-locate-file-source
   3   * @requires javelin-install
   4   *           javelin-dom
   5   *           javelin-typeahead-preloaded-source
   6   *           javelin-util
   7   * @javelin
   8   */
   9  
  10  JX.install('DiffusionLocateFileSource', {
  11  
  12    extend: 'TypeaheadPreloadedSource',
  13  
  14    construct: function(uri) {
  15      JX.TypeaheadPreloadedSource.call(this, uri);
  16      this.cache = {};
  17    },
  18  
  19    members: {
  20      tree: null,
  21      limit: 20,
  22      cache: null,
  23  
  24      ondata: function(results) {
  25        this.tree = results.tree;
  26        this.setReady(true);
  27      },
  28  
  29  
  30      /**
  31       * Match a query and show results in the typeahead.
  32       */
  33      matchResults: function(value, partial) {
  34        // For now, just pretend spaces don't exist.
  35        var search = value.toLowerCase();
  36        search = search.replace(' ', '');
  37  
  38        var paths = this.findResults(search);
  39  
  40        var nodes = [];
  41        for (var ii = 0; ii < paths.length; ii++) {
  42          var path = paths[ii];
  43          var name = [];
  44          name.push(path.path.substr(0, path.pos));
  45          name.push(
  46            JX.$N('strong', {}, path.path.substr(path.pos, path.score)));
  47  
  48          var pos = path.score;
  49          var lower = path.path.toLowerCase();
  50          for (var jj = path.pos + path.score; jj < path.path.length; jj++) {
  51            if (lower.charAt(jj) == search.charAt(pos)) {
  52              pos++;
  53              name.push(JX.$N('strong', {}, path.path.charAt(jj)));
  54              if (pos == search.length) {
  55                break;
  56              }
  57            } else {
  58              name.push(path.path.charAt(jj));
  59            }
  60          }
  61  
  62          if (jj < path.path.length - 1 ) {
  63            name.push(path.path.substr(jj + 1));
  64          }
  65  
  66          var attr = {
  67            className: 'visual-only phui-icon-view phui-font-fa fa-file'
  68          };
  69          var icon = JX.$N('span', attr, '');
  70  
  71          nodes.push(
  72            JX.$N(
  73              'a',
  74              {
  75                sigil: 'typeahead-result',
  76                className: 'jx-result diffusion-locate-file',
  77                ref: path.path
  78              },
  79              [icon, name]));
  80        }
  81  
  82        this.invoke('resultsready', nodes, value);
  83        if (!partial) {
  84          this.invoke('complete');
  85        }
  86      },
  87  
  88  
  89      /**
  90       * Find the results matching a query.
  91       */
  92      findResults: function(search) {
  93        if (!search.length) {
  94          return [];
  95        }
  96  
  97        // We know that the results for "abc" are always a subset of the results
  98        // for "a" and "ab" -- and there's a good chance we already computed
  99        // those result sets. Find the longest cached result which is a prefix
 100        // of the search query.
 101        var best = 0;
 102        var start = this.tree;
 103        for (var k in this.cache) {
 104          if ((k.length <= search.length) &&
 105              (k.length > best) &&
 106              (search.substr(0, k.length) == k)) {
 107            best = k.length;
 108            start = this.cache[k];
 109          }
 110        }
 111  
 112        var matches;
 113        if (start === null) {
 114          matches = null;
 115        } else {
 116          matches = this.matchTree(start, search, 0);
 117        }
 118  
 119        // Save this tree in cache; throw the cache away after a few minutes.
 120        if (!(search in this.cache)) {
 121          this.cache[search] = matches;
 122          setTimeout(
 123            JX.bind(this, function() { delete this.cache[search]; }),
 124            1000 * 60 * 5);
 125        }
 126  
 127        if (!matches) {
 128          return [];
 129        }
 130  
 131        var paths = [];
 132        this.buildPaths(matches, paths, '', search, []);
 133  
 134        paths.sort(
 135          function(u, v) {
 136            if (u.score != v.score) {
 137              return (v.score - u.score);
 138            }
 139  
 140            if (u.pos != v.pos) {
 141              return (u.pos - v.pos);
 142            }
 143  
 144            return ((u.path > v.path) ? 1 : -1);
 145          });
 146  
 147        var num =  Math.min(paths.length, this.limit);
 148        var results = [];
 149        for (var ii = 0; ii < num; ii++) {
 150          results.push(paths[ii]);
 151        }
 152  
 153        return results;
 154      },
 155  
 156  
 157      /**
 158       * Select the subtree that matches a query.
 159       */
 160      matchTree: function(tree, value, pos) {
 161        var matches = null;
 162        for (var k in tree) {
 163          var p = pos;
 164  
 165          if (p != value.length) {
 166            p = this.matchString(k, value, pos);
 167          }
 168  
 169          var result;
 170          if (p == value.length) {
 171            result = tree[k];
 172          } else {
 173            if (tree == 1) {
 174              continue;
 175            } else {
 176              result = this.matchTree(tree[k], value, p);
 177              if (!result) {
 178                continue;
 179              }
 180            }
 181          }
 182  
 183          if (!matches) {
 184            matches = {};
 185          }
 186          matches[k] = result;
 187        }
 188  
 189        return matches;
 190      },
 191  
 192  
 193      /**
 194       * Look for the needle in a string, returning how much of it was found.
 195       */
 196      matchString: function(haystack, needle, pos) {
 197        var str = haystack.toLowerCase();
 198        var len = str.length;
 199        for (var ii = 0; ii < len; ii++) {
 200          if (str.charAt(ii) == needle.charAt(pos)) {
 201            pos++;
 202            if (pos == needle.length) {
 203              break;
 204            }
 205          }
 206        }
 207        return pos;
 208      },
 209  
 210  
 211      /**
 212       * Flatten a tree into paths.
 213       */
 214      buildPaths: function(matches, paths, prefix, search) {
 215        var first = search.charAt(0);
 216  
 217        for (var k in matches) {
 218          if (matches[k] == 1) {
 219            var path = prefix + k;
 220            var lower = path.toLowerCase();
 221  
 222            var best = 0;
 223            var pos = 0;
 224            for (var jj = 0; jj < lower.length; jj++) {
 225              if (lower.charAt(jj) != first) {
 226                continue;
 227              }
 228  
 229              var score = this.scoreMatch(lower, jj, search);
 230              if (score == -1) {
 231                break;
 232              }
 233  
 234              if (score > best) {
 235                best = score;
 236                pos = jj;
 237                if (best == search.length) {
 238                  break;
 239                }
 240              }
 241            }
 242  
 243            paths.push({
 244              path: path,
 245              score: best,
 246              pos: pos
 247            });
 248  
 249          } else {
 250            this.buildPaths(matches[k], paths, prefix + k, search);
 251          }
 252        }
 253      },
 254  
 255  
 256      /**
 257       * Score a matching string by finding the longest prefix of the search
 258       * query it contains continguously.
 259       */
 260      scoreMatch: function(haystack, haypos, search) {
 261        var pos = 0;
 262        for (var ii = haypos; ii < haystack.length; ii++) {
 263          if (haystack.charAt(ii) == search.charAt(pos)) {
 264            pos++;
 265            if (pos == search.length) {
 266              return pos;
 267            }
 268          } else {
 269            ii++;
 270            break;
 271          }
 272        }
 273  
 274        var rem = pos;
 275        for (/* keep going */; ii < haystack.length; ii++) {
 276          if (haystack.charAt(ii) == search.charAt(rem)) {
 277            rem++;
 278            if (rem == search.length) {
 279              return pos;
 280            }
 281          }
 282        }
 283  
 284        return -1;
 285      }
 286  
 287    }
 288  });


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1