1class StringDimension { 2 constructor(logical=0, physical=0) { 3 this.logical = logical; // [...string].length 4 this.physical = physical; // string.length 5 } 6 static plus(x, y) { 7 return new StringDimension( 8 x.logical + y.logical, 9 x.physical + y.physical 10 ); 11 } 12 static addChar(x, char) { 13 return new StringDimension( 14 x.logical + 1, 15 x.physical + char.length 16 ); 17 } 18} 19 20function* charIterator(string, end=true) { 21 let index = new StringDimension(); 22 for (let char of string) { 23 yield [index, char]; 24 index = StringDimension.addChar(index, char); 25 } 26 if (end) 27 yield [index, '']; 28} 29 30 31class SymbolManager { 32 constructor() { 33 this.trie = new Map(); 34 this.dict = new Map(); 35 } 36 take(symbol) { 37 if (!this.dict.has(symbol)) { 38 this.trieAdd(symbol); 39 this.dict.set(symbol, { 40 opening: false, 41 closing: false, 42 alone: false, 43 view: undefined 44 }) 45 } 46 return this.dict.get(symbol); 47 } 48 setView(symbol, view) { 49 this.take(symbol).view = view; 50 } 51 trieAdd(symbol) { 52 let trie = this.trie; 53 for (let char of symbol) 54 trie = trie.get(char) || trie.set(char, new Map()).get(char); 55 trie.set('END', symbol); 56 } 57} 58 59class MarkManager { 60 constructor() { 61 this.dict = new Map(); 62 this.refresh(); 63 } 64 refresh() { 65 this.list = []; 66 this.amount = new Map(); 67 } 68 take(symbol, mark='-UNDEFINED') { 69 if (!this.dict.has(symbol)) 70 this.dict.set(symbol, { 71 mark: mark, 72 closedBy: [], 73 consume: true 74 }); 75 return this.dict.get(symbol); 76 } 77 getUnduplicatedList() { 78 return Array.from(this.amount.keys(), symbol => this.dict.get(symbol).mark); 79 } 80 open(symbol) { 81 let amount = this.amount.get(symbol) || 0; 82 this.amount.set(symbol, amount + 1); 83 this.list.push(symbol); 84 } 85 close(symbol) { 86 let notYetClosedMarks = []; 87 let closedMarks = []; 88 while (this.list.length) { 89 let opening = this.list.pop(); 90 if (!this.take(opening).closedBy.includes(symbol)) { 91 notYetClosedMarks.unshift(opening); 92 continue; 93 } 94 this.amountDec(opening); 95 closedMarks.push(opening); 96 if (this.take(opening).consume) 97 break; 98 } 99 this.list = this.list.concat(notYetClosedMarks); 100 return closedMarks; 101 } 102 amountDec(symbol) { 103 let amount = this.amount.get(symbol) || 1; 104 this.amount.set(symbol, amount - 1); 105 if (amount === 1) 106 this.amount.delete(symbol); 107 } 108} 109 110class ContextManager { 111 constructor() { 112 this.Symbol = new SymbolManager(); 113 this.Mark = new MarkManager(); 114 } 115 setSymbolView(symbol, view) { 116 this.Symbol.setView(symbol, view); 117 } 118 addMarkOnly(mark, symbol) { 119 this.Symbol.take(symbol).alone = true; 120 this.Mark.take(symbol, mark); 121 } 122 addMarkBetween(mark, opening, closing=opening, consume=true) { 123 this.Symbol.take(opening).opening = true; 124 this.Symbol.take(closing).closing = true; 125 this.Mark.take(opening, mark).closedBy.push(closing); 126 if (!consume) 127 this.Mark.take(opening).consume = false; 128 } 129 addMarkAfter(mark, leading, closedBySpace=false) { 130 this.addMarkBetween(mark, leading, '\n', false); 131 if (closedBySpace) 132 this.addMarkBetween(mark, leading, ' ', false); 133 } 134} 135 136 137function mkTextObj(rawContent, start, symbolId) { 138 return { 139 isSymbol: symbolId !== undefined, 140 symbolId: symbolId, 141 raw: rawContent, 142 view: rawContent, 143 start: start 144 }; 145} 146function newBranch(trie, start) { 147 return { 148 currNode: trie, 149 achieved: undefined, 150 start: start, 151 currText: '', 152 currTextLength: new StringDimension(), 153 inactive: false 154 }; 155}
156function walkBranches(branchList, char) { 157 let nextBranchList = []; 158 for (let branch of branchList) { 159 if (!branch.currNode.has(char)) 160 branch.inactive = true; 161 if (branch.inactive) { 162 if (branch.achieved !== undefined) 163 nextBranchList.push(branch); 164 continue; 165 } 166 nextBranchList.push(branch); 167 branch.currNode = branch.currNode.get(char); 168 branch.currText += char; 169 branch.currTextLength = StringDimension.addChar(branch.currTextLength, char); 170 if (branch.currNode.has('END')) { 171 branch.achieved = { 172 symbolId: branch.currNode.get('END'), 173 text: branch.currText, 174 textLength: branch.currTextLength 175 }; 176 break; // we don't need the branches in the rest of the list 177 } 178 } 179 return nextBranchList; 180} 181function* checkBranches(branchList, buffer) { 182 while (branchList.length && branchList[0].inactive) { 183 let branch = branchList.shift(); 184 let prefixLength = branch.start.logical - buffer.start.logical; 185 if (prefixLength > 0) { 186 let text = buffer.chars.splice(0, prefixLength).join(''); 187 yield mkTextObj(text, buffer.start); 188 } 189 buffer.chars.splice(0, branch.achieved.textLength.logical); 190 buffer.start = StringDimension.plus(branch.start, branch.achieved.textLength); 191 yield mkTextObj(branch.achieved.text, branch.start, branch.achieved.symbolId); 192 } 193 return [branchList, buffer]; 194} 195function* textObjGenerator(symbolManager, wholeContent) { 196 let branchList = []; 197 let buffer = { 198 chars: [], 199 start: new StringDimension(), 200 }; 201 for (let [index, char] of charIterator(wholeContent)) { 202 buffer.chars.push(char); 203 if (symbolManager.trie.has(char)) 204 branchList.push(newBranch(symbolManager.trie, index)); 205 branchList = walkBranches(branchList, char); 206 [branchList, buffer] = yield* checkBranches(branchList, buffer); 207 } 208 let remainder = buffer.chars.join(''); 209 if (remainder.length > 0) 210 yield mkTextObj(remainder, buffer.start); 211} 212 213 214function mkTextMark(textObj, markList) { 215 textObj.markList = markList; 216 textObj.startOffset = textObj.start.physical; 217 return textObj; 218} 219function* textMarkGenerator(ctxManager, wholeContent) { 220 ctxManager.Mark.refresh(); 221 for (let textObj of textObjGenerator(ctxManager.Symbol, wholeContent)) { 222 if (!textObj.isSymbol) { 223 yield mkTextMark(textObj, ctxManager.Mark.getUnduplicatedList()); 224 continue; 225 } 226 let markList = []; 227 let symbol = textObj.symbolId; 228 let symbolData = ctxManager.Symbol.take(symbol); 229 if (symbolData.view) 230 textObj.view = symbolData.view; 231 if (symbolData.alone) 232 markList.push(`${ctxManager.Mark.take(symbol).mark}`); 233 if (symbolData.closing) { 234 for (let opening of ctxManager.Mark.close(symbol)) 235 markList.push(`${ctxManager.Mark.take(opening).mark}-end`); 236 } 237 let symbolUsed = markList.length > 0; 238 markList = markList.concat(ctxManager.Mark.getUnduplicatedList()); 239 if (!symbolUsed && symbolData.opening) { 240 ctxManager.Mark.open(symbol); 241 markList.push(`${ctxManager.Mark.take(symbol).mark}-start`); 242 } 243 yield mkTextMark(textObj, markList); 244 } 245}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.