1 module dinu.filter; 2 3 import 4 core.thread, 5 core.atomic, 6 std.conv, 7 std.parallelism, 8 std.string, 9 std.path, 10 std.stdio, 11 std.algorithm, 12 std.range, 13 std.uni, 14 std.datetime, 15 dinu.fuzzyMatch; 16 17 18 shared struct Match(T) { 19 long score; 20 immutable(int)[] positions; 21 immutable(T)[] data; 22 } 23 24 25 shared class Queue(T) { 26 27 private T[] queue; 28 29 synchronized void opOpAssign(string op)(T object) if(op == "~") { 30 queue ~= object; 31 } 32 33 synchronized size_t length(){ 34 return queue.length; 35 } 36 37 synchronized T pop(){ 38 assert(queue.length, "Please check queue length, is 0"); 39 auto first = queue[0]; 40 queue = queue[1..$]; 41 return first; 42 } 43 44 synchronized shared(T[]) consume(){ 45 auto copy = queue; 46 queue = []; 47 return copy; 48 } 49 50 } 51 52 53 class FuzzyFilter(T) { 54 55 protected immutable(Match!T)[] matches; 56 57 this(shared bool delegate(immutable T) filterFunc){ 58 addQueue = new shared Queue!(immutable T); 59 narrowQueue = new shared Queue!string; 60 this.filterFunc = filterFunc; 61 auto worker = task({ 62 filterLoop; 63 }); 64 worker.executeInNewThread; 65 } 66 67 void reset(string filter=""){ 68 filter = filter.expandTilde; 69 restart = true; 70 synchronized(this){ 71 narrowQueue.consume; 72 this.filter = filter; 73 } 74 } 75 76 void narrow(string text){ 77 narrowQueue ~= text; 78 } 79 80 immutable(Match!T)[] res(){ 81 return matches; 82 } 83 84 void add(immutable T p){ 85 synchronized(this) 86 choices ~= p; 87 addQueue ~= p; 88 } 89 90 void set(immutable T[] choices){ 91 synchronized(this) 92 this.choices = choices; 93 reset; 94 } 95 96 void remove(immutable T[] remove){ 97 if(!remove.length) 98 return; 99 synchronized(this) 100 choices = choices.filter!(a => !remove.canFind(a)).array; 101 reset; 102 } 103 104 void stop(){ 105 running = false; 106 } 107 108 protected { 109 110 shared immutable(T)[] choices; 111 shared Queue!(immutable T) addQueue; 112 shared Queue!string narrowQueue; 113 shared string filter; 114 115 shared bool restart; 116 117 shared bool delegate(immutable T) filterFunc; 118 119 shared bool running = true; 120 121 122 void match(immutable T p){ 123 if(!filterFunc(p)) 124 return; 125 Match!T match; 126 if(p.filterText == filter){ 127 match.score = 9999999; 128 match.positions = filter.length.iota.array.to!(int[]).idup; 129 }else{ 130 auto res = p.filterText.compareFuzzy(p.prepFilter(filter)); 131 match.score = res[0]; 132 match.positions = res[1..$].idup; 133 } 134 if(match.score > 0){ 135 if(!filter.startsWith(".") && (p.filterText.startsWith(".") || p.filterText.canFind("/."))){ 136 match.score = 1.min(match.score-100); 137 } 138 match.score.atomicOp!"+="(p.score); 139 match.data = [p]; 140 foreach(i, e; matches){ 141 if(restart) 142 break; 143 if(e.score <= match.score){ 144 if(e.score == match.score && e.data[0].filterText.icmp(match.data[0].filterText) < 0) 145 continue; 146 matches = matches[0..i] ~ match ~ matches[i..$]; 147 return; 148 } 149 } 150 matches ~= match; 151 } 152 } 153 154 void intReset(string filter){ 155 this.filter = filter; 156 matches = []; 157 foreach(m; choices){ 158 match(m); 159 if(restart) 160 break; 161 } 162 } 163 164 void intNarrow(string filter){ 165 this.filter = filter; 166 auto cpy = matches; 167 matches = []; 168 foreach(m; cpy){ 169 match(m.data[0]); 170 if(restart) 171 break; 172 } 173 } 174 175 void filterLoop(){ 176 try{ 177 while(running){ 178 if(narrowQueue.length){ 179 filter ~= narrowQueue.consume.join(""); 180 intNarrow(filter); 181 }else if(addQueue.length){ 182 foreach(m; addQueue.consume) 183 match(m); 184 }else if(restart){ 185 restart = false; 186 intReset(filter); 187 }else{ 188 Thread.sleep(5.msecs); 189 } 190 } 191 }catch(Throwable t) 192 writeln(t); 193 } 194 195 } 196 197 }