-
-
Notifications
You must be signed in to change notification settings - Fork 25
/
liquidmetal.js
135 lines (117 loc) · 4.09 KB
/
liquidmetal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* LiquidMetal, version: 1.3.0 (2014-08-19)
*
* A mimetic poly-alloy of Quicksilver's scoring algorithm, essentially
* LiquidMetal.
*
* For usage and examples, visit:
* http://github.com/rmm5t/liquidmetal
*
* Licensed under the MIT:
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright (c) 2009-2017, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
*/
(function(global) {
var SCORE_NO_MATCH = 0.0;
var SCORE_MATCH = 1.0;
var SCORE_TRAILING = 0.8;
var SCORE_TRAILING_BUT_STARTED = 0.9;
var SCORE_BUFFER = 0.85;
var WORD_SEPARATORS = " \t_-";
var LiquidMetal = {
lastScore: null,
lastScoreArray: null,
score: function(string, abbrev) {
// short circuits
if (abbrev.length === 0) return SCORE_TRAILING;
if (abbrev.length > string.length) return SCORE_NO_MATCH;
// match & score all
var allScores = [];
var search = string.toLowerCase();
abbrev = abbrev.toLowerCase();
this._scoreAll(string, search, abbrev, -1, 0, [], allScores);
// complete miss
if (allScores.length == 0) return 0;
// sum per-character scores into overall scores,
// selecting the maximum score
var maxScore = 0.0, maxArray = [];
for (var i = 0; i < allScores.length; i++) {
var scores = allScores[i];
var scoreSum = 0.0;
for (var j = 0; j < string.length; j++) { scoreSum += scores[j]; }
if (scoreSum > maxScore) {
maxScore = scoreSum;
maxArray = scores;
}
}
// normalize max score by string length
// s. t. the perfect match score = 1
maxScore /= string.length;
// record maximum score & score array, return
this.lastScore = maxScore;
this.lastScoreArray = maxArray;
return maxScore;
},
_scoreAll: function(string, search, abbrev, searchIndex, abbrIndex, scores, allScores) {
// save completed match scores at end of search
if (abbrIndex == abbrev.length) {
// add trailing score for the remainder of the match
var started = (search.charAt(0) == abbrev.charAt(0));
var trailScore = started ? SCORE_TRAILING_BUT_STARTED : SCORE_TRAILING;
fillArray(scores, trailScore, scores.length, string.length);
// save score clone (since reference is persisted in scores)
allScores.push(scores.slice(0));
return;
}
// consume current char to match
var c = abbrev.charAt(abbrIndex);
abbrIndex++;
// cancel match if a character is missing
var index = search.indexOf(c, searchIndex);
if (index == -1) return;
// match all instances of the abbreviaton char
var scoreIndex = searchIndex; // score section to update
while ((index = search.indexOf(c, searchIndex+1)) != -1) {
// score this match according to context
if (isNewWord(string, index)) {
scores[index-1] = 1;
fillArray(scores, SCORE_BUFFER, scoreIndex+1, index-1);
}
else if (isUpperCase(string, index)) {
fillArray(scores, SCORE_BUFFER, scoreIndex+1, index);
}
else {
fillArray(scores, SCORE_NO_MATCH, scoreIndex+1, index);
}
scores[index] = SCORE_MATCH;
// consume matched string and continue search
searchIndex = index;
this._scoreAll(string, search, abbrev, searchIndex, abbrIndex, scores, allScores);
}
}
};
function isUpperCase(string, index) {
var c = string.charAt(index);
return ("A" <= c && c <= "Z");
}
function isNewWord(string, index) {
var c = string.charAt(index-1);
return (WORD_SEPARATORS.indexOf(c) != -1);
}
function fillArray(array, value, from, to) {
for (var i = from; i < to; i++) { array[i] = value; }
return array;
}
// Export as AMD...
if (typeof define === 'function' && define.amd) {
define(function () { return LiquidMetal; });
}
// ...or as a node module
else if (typeof module !== 'undefined' && module.exports) {
module.exports = LiquidMetal;
}
else {
global.LiquidMetal = LiquidMetal;
}
})(typeof window !== 'undefined' ? window : this);