2
// LaneStringMatcher.cs
5
// Lluis Sanchez Gual <lluis@novell.com>
7
// Copyright (c) 2010 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
using System.Collections.Generic;
30
namespace MonoDevelop.Core.Text
32
class LaneStringMatcher: StringMatcher
34
readonly string filter;
35
readonly string filterLowerCase;
36
readonly List<MatchLane> matchLanes;
38
public LaneStringMatcher (string filter)
40
matchLanes = new List<MatchLane> ();
42
this.filterLowerCase = filter != null ? filter.ToLowerInvariant () : "";
45
public override bool CalcMatchRank (string name, out int matchRank)
47
if (filterLowerCase.Length == 0) {
48
matchRank = int.MinValue;
52
MatchLane lane = MatchString (name, out totalWords);
54
matchRank = filterLowerCase.Length - name.Length;
55
matchRank -= lane.Positions [0];
57
matchRank += (lane.WordStartsMatched - totalWords) * 100;
59
// Favor matches where all parts are word starts
60
if (lane.WordStartsMatched == lane.Index + 1)
63
// Favor matches with less splits. That is, 'abc def' is better than 'ab c def'.
64
int baseRank = (filter.Length - lane.Index - 1) * 5000;
66
// First matching letter close to the begining is better
67
// The more matched letters the better
68
matchRank = baseRank - (lane.Positions [0] + (name.Length - filterLowerCase.Length));
70
matchRank += lane.ExactCaseMatches * 10;
72
// rank up matches which start with a filter substring
73
if (lane.Positions [0] == 0)
74
matchRank += lane.Lengths[0] * 50;
78
matchRank = int.MinValue;
82
public override bool IsMatch (string name)
85
return filterLowerCase.Length == 0 || MatchString (name, out totalWords) != null;
89
/// Gets the match indices.
92
/// The indices in the text which are matched by our filter.
94
/// <param name='text'>
95
/// The text to match.
97
public override int[] GetMatch (string text)
99
if (filterLowerCase.Length == 0)
101
if (string.IsNullOrEmpty (text))
104
var lane = MatchString (text, out totalWords);
108
for (int i = 0; i <= lane.Index; i++) {
109
cnt += lane.Lengths[i];
111
int[] result = new int [cnt];
113
for (int i = 0; i <= lane.Index; i++) {
114
int p = lane.Positions[i];
115
for (int j = 0 ; j < lane.Lengths[i]; j++) {
122
MatchLane MatchString (string text, out int totalWords)
126
if (text == null || text.Length < filterLowerCase.Length)
131
string textLowerCase = text.ToLowerInvariant ();
133
bool lastWasSeparator = false;
134
int firstMatchPos = -1;
136
int tlen = text.Length;
137
int flen = filterLowerCase.Length;
138
for (int n=0; n<tlen && j < flen; n++) {
139
char ctLower = textLowerCase[n];
140
char cfLower = filterLowerCase [j];
141
bool wordStart = (ctLower != text[n]) || n == 0 || lastWasSeparator;
144
if (ctLower == cfLower && !(cfLower != filter[j] && ctLower == text[n])) {
145
bool exactMatch = filter[j] == text[n];
147
if (firstMatchPos == -1)
150
MatchLane lane = CreateLane (MatchMode.Substring, n);
152
lane.ExactCaseMatches++;
156
lastWasSeparator = IsSeparator (ctLower);
167
int tn = firstMatchPos;
168
char filterStartLower = filterLowerCase[0];
169
bool filterStartIsUpper = filterStartLower != filter[0];
171
while (tn < text.Length) {
173
char ctLower = textLowerCase [tn];
174
bool ctIsUpper = ct != ctLower;
175
bool wordStart = ctIsUpper || tn == 0 || lastWasSeparator;
180
// Keep the lane count in a var because new lanes don't have to be updated
181
// until the next iteration
182
int laneCount = matchLanes != null ? matchLanes.Count : 0;
184
if (ctLower == filterStartLower && !(filterStartIsUpper && !ctIsUpper)) {
185
MatchLane lane = CreateLane (MatchMode.Substring, tn);
186
if (filterStartIsUpper == ctIsUpper)
187
lane.ExactCaseMatches++;
188
matchLanes.Add (lane);
189
if (filterLowerCase.Length == 1)
190
return matchLanes[0];
191
if (ctIsUpper || lastWasSeparator)
192
matchLanes.Add (CreateLane (MatchMode.Acronym, tn));
195
for (int n=0; n<laneCount; n++) {
196
MatchLane lane = matchLanes [n];
199
char cfLower = filterLowerCase [lane.MatchIndex];
200
bool cfIsUpper = cfLower != filter [lane.MatchIndex];
201
bool match = ctLower == cfLower && !(cfIsUpper && !ctIsUpper);
202
bool exactMatch = match && (cfIsUpper == ctIsUpper);
203
bool wordStartMatch = match && wordStart;
205
if (lane.MatchMode == MatchMode.Substring) {
206
if (wordStartMatch && !LaneExists (MatchMode.Acronym, lane.MatchIndex + 1)) {
207
// Possible acronym match after a substring. Start a new lane.
208
MatchLane newLane = CloneLane (lane);
209
newLane.MatchMode = MatchMode.Acronym;
210
newLane.WordStartsMatched++;
212
newLane.Positions [newLane.Index] = tn;
213
newLane.Lengths [newLane.Index] = 1;
214
newLane.MatchIndex++;
216
newLane.ExactCaseMatches++;
217
matchLanes.Add (newLane);
220
if (!LaneExists (MatchMode.Acronym, lane.MatchIndex)) {
221
// Maybe it is a false substring start, so add a new lane to keep
222
// track of the old lane
223
MatchLane newLane = CloneLane (lane);
224
newLane.MatchMode = MatchMode.Acronym;
225
matchLanes.Add (newLane);
227
newLane.ExactCaseMatches++;
230
// Update the current lane
231
lane.Lengths [lane.Index]++;
234
if (lane.Lengths [lane.Index] > 1)
235
lane.MatchMode = MatchMode.Acronym;
237
matchLanes [n] = null; // Kill the lane
240
else if (lane.MatchMode == MatchMode.Acronym) {
241
if (match && lane.Positions [lane.Index] == tn - 1 && !LaneExists (MatchMode.Substring, lane.MatchIndex + 1)) {
242
// Possible substring match after an acronim. Start a new lane.
243
MatchLane newLane = CloneLane (lane);
244
newLane.MatchMode = MatchMode.Substring;
245
newLane.Lengths [newLane.Index]++;
246
newLane.MatchIndex++;
248
newLane.ExactCaseMatches++;
249
matchLanes.Add (newLane);
250
if (newLane.MatchIndex == filterLowerCase.Length)
253
if ((wordStartMatch || (match && char.IsPunctuation (cfLower))) && !LaneExists (MatchMode.Acronym, lane.MatchIndex + 1)) {
254
// Maybe it is a false acronym start, so add a new lane to keep
255
// track of the old lane
256
MatchLane newLane = CloneLane (lane);
257
matchLanes.Add (newLane);
259
// Update the current lane
261
lane.Positions [lane.Index] = tn;
262
lane.Lengths [lane.Index] = 1;
265
lane.WordStartsMatched++;
267
newLane.ExactCaseMatches++;
270
if (lane.MatchIndex == filterLowerCase.Length)
273
lastWasSeparator = IsSeparator (ct);
279
bool LaneExists (MatchMode mode, int matchIndex)
281
if (matchLanes == null)
283
for (int n=0; n<matchLanes.Count; n++) {
284
MatchLane lane = matchLanes [n];
285
if (lane != null && lane.MatchMode == mode && lane.MatchIndex == matchIndex)
291
bool IsSeparator (char ct)
293
return (ct == '.' || ct == '_' || ct == '-' || ct == ' ' || ct == '/' || ct == '\\');
296
void ResetLanePool ()
301
MatchLane GetPoolLane ()
303
if (lanePoolIndex < lanePool.Count)
304
return lanePool [lanePoolIndex++];
306
MatchLane lane = new MatchLane (filterLowerCase.Length * 2);
312
MatchLane CreateLane (MatchMode mode, int pos)
314
MatchLane lane = GetPoolLane ();
315
lane.Initialize (mode, pos);
316
if (mode == MatchMode.Acronym)
317
lane.WordStartsMatched = 1;
321
MatchLane CloneLane (MatchLane other)
323
MatchLane lane = GetPoolLane ();
324
lane.Initialize (other);
328
int lanePoolIndex = 0;
329
List<MatchLane> lanePool = new List<MatchLane> ();
338
public int[] Positions;
339
public int[] Lengths;
340
public MatchMode MatchMode;
342
public int MatchIndex;
343
public int ExactCaseMatches;
344
public int WordStartsMatched;
346
public MatchLane (int maxlen)
348
Positions = new int [maxlen];
349
Lengths = new int [maxlen];
352
public void Initialize (MatchMode mode, int pos)
359
ExactCaseMatches = 0;
360
WordStartsMatched = 0;
363
public void Initialize (MatchLane other)
365
for (int n=0; n<=other.Index; n++) {
366
Positions [n] = other.Positions [n];
367
Lengths [n] = other.Lengths [n];
369
MatchMode = other.MatchMode;
370
MatchIndex = other.MatchIndex;
372
ExactCaseMatches = other.ExactCaseMatches;
373
WordStartsMatched = other.WordStartsMatched;