Show More
@@ -0,0 +1,490 b'' | |||
|
1 | /*!*************************************************** | |
|
2 | * mark.js v6.1.0 | |
|
3 | * https://github.com/julmot/mark.js | |
|
4 | * Copyright (c) 2014–2016, Julian Motz | |
|
5 | * Released under the MIT license https://git.io/vwTVl | |
|
6 | *****************************************************/ | |
|
7 | ||
|
8 | "use strict"; | |
|
9 | ||
|
10 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
|
11 | ||
|
12 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | |
|
13 | ||
|
14 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | |
|
15 | ||
|
16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
|
17 | ||
|
18 | (function (factory, window, document) { | |
|
19 | if (typeof define === "function" && define.amd) { | |
|
20 | define(["jquery"], function (jQuery) { | |
|
21 | return factory(window, document, jQuery); | |
|
22 | }); | |
|
23 | } else if ((typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object") { | |
|
24 | factory(window, document, require("jquery")); | |
|
25 | } else { | |
|
26 | factory(window, document, jQuery); | |
|
27 | } | |
|
28 | })(function (window, document, $) { | |
|
29 | var Mark = function () { | |
|
30 | function Mark(ctx) { | |
|
31 | _classCallCheck(this, Mark); | |
|
32 | ||
|
33 | this.ctx = ctx; | |
|
34 | } | |
|
35 | ||
|
36 | _createClass(Mark, [{ | |
|
37 | key: "log", | |
|
38 | value: function log(msg) { | |
|
39 | var level = arguments.length <= 1 || arguments[1] === undefined ? "debug" : arguments[1]; | |
|
40 | ||
|
41 | var log = this.opt.log; | |
|
42 | if (!this.opt.debug) { | |
|
43 | return; | |
|
44 | } | |
|
45 | if ((typeof log === "undefined" ? "undefined" : _typeof(log)) === "object" && typeof log[level] === "function") { | |
|
46 | log[level]("mark.js: " + msg); | |
|
47 | } | |
|
48 | } | |
|
49 | }, { | |
|
50 | key: "escapeStr", | |
|
51 | value: function escapeStr(str) { | |
|
52 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | |
|
53 | } | |
|
54 | }, { | |
|
55 | key: "createRegExp", | |
|
56 | value: function createRegExp(str) { | |
|
57 | str = this.escapeStr(str); | |
|
58 | if (Object.keys(this.opt.synonyms).length) { | |
|
59 | str = this.createSynonymsRegExp(str); | |
|
60 | } | |
|
61 | if (this.opt.diacritics) { | |
|
62 | str = this.createDiacriticsRegExp(str); | |
|
63 | } | |
|
64 | str = this.createAccuracyRegExp(str); | |
|
65 | return str; | |
|
66 | } | |
|
67 | }, { | |
|
68 | key: "createSynonymsRegExp", | |
|
69 | value: function createSynonymsRegExp(str) { | |
|
70 | var syn = this.opt.synonyms; | |
|
71 | for (var index in syn) { | |
|
72 | if (syn.hasOwnProperty(index)) { | |
|
73 | var value = syn[index], | |
|
74 | k1 = this.escapeStr(index), | |
|
75 | k2 = this.escapeStr(value); | |
|
76 | str = str.replace(new RegExp("(" + k1 + "|" + k2 + ")", "gmi"), "(" + k1 + "|" + k2 + ")"); | |
|
77 | } | |
|
78 | } | |
|
79 | return str; | |
|
80 | } | |
|
81 | }, { | |
|
82 | key: "createDiacriticsRegExp", | |
|
83 | value: function createDiacriticsRegExp(str) { | |
|
84 | var dct = ["aÀÁÂÃÄÅàáâãäåĀāąĄ", "cÇçćĆčČ", "dđĐďĎ", "eÈÉÊËèéêëěĚĒēęĘ", "iÌÍÎÏìíîïĪī", "lłŁ", "nÑñňŇńŃ", "oÒÓÔÕÕÖØòóôõöøŌō", "rřŘ", "sŠšśŚ", "tťŤ", "uÙÚÛÜùúûüůŮŪū", "yŸÿýÝ", "zŽžżŻźŹ"]; | |
|
85 | var handled = []; | |
|
86 | str.split("").forEach(function (ch) { | |
|
87 | dct.every(function (dct) { | |
|
88 | if (dct.indexOf(ch) !== -1) { | |
|
89 | if (handled.indexOf(dct) > -1) { | |
|
90 | return false; | |
|
91 | } | |
|
92 | ||
|
93 | str = str.replace(new RegExp("[" + dct + "]", "gmi"), "[" + dct + "]"); | |
|
94 | handled.push(dct); | |
|
95 | } | |
|
96 | return true; | |
|
97 | }); | |
|
98 | }); | |
|
99 | return str; | |
|
100 | } | |
|
101 | }, { | |
|
102 | key: "createAccuracyRegExp", | |
|
103 | value: function createAccuracyRegExp(str) { | |
|
104 | switch (this.opt.accuracy) { | |
|
105 | case "partially": | |
|
106 | return "()(" + str + ")"; | |
|
107 | case "complementary": | |
|
108 | return "()(\\S*" + str + "\\S*)"; | |
|
109 | case "exactly": | |
|
110 | return "(^|\\s)(" + str + ")(?=\\s|$)"; | |
|
111 | } | |
|
112 | } | |
|
113 | }, { | |
|
114 | key: "getSeparatedKeywords", | |
|
115 | value: function getSeparatedKeywords(sv) { | |
|
116 | var _this = this; | |
|
117 | ||
|
118 | var stack = []; | |
|
119 | sv.forEach(function (kw) { | |
|
120 | if (!_this.opt.separateWordSearch) { | |
|
121 | if (kw.trim()) { | |
|
122 | stack.push(kw); | |
|
123 | } | |
|
124 | } else { | |
|
125 | kw.split(" ").forEach(function (kwSplitted) { | |
|
126 | if (kwSplitted.trim()) { | |
|
127 | stack.push(kwSplitted); | |
|
128 | } | |
|
129 | }); | |
|
130 | } | |
|
131 | }); | |
|
132 | return { | |
|
133 | "keywords": stack, | |
|
134 | "length": stack.length | |
|
135 | }; | |
|
136 | } | |
|
137 | }, { | |
|
138 | key: "getElements", | |
|
139 | value: function getElements() { | |
|
140 | var ctx = void 0, | |
|
141 | stack = []; | |
|
142 | if (typeof this.ctx === "undefined") { | |
|
143 | ctx = []; | |
|
144 | } else if (this.ctx instanceof HTMLElement) { | |
|
145 | ctx = [this.ctx]; | |
|
146 | } else if (Array.isArray(this.ctx)) { | |
|
147 | ctx = this.ctx; | |
|
148 | } else { | |
|
149 | ctx = Array.prototype.slice.call(this.ctx); | |
|
150 | } | |
|
151 | ctx.forEach(function (ctx) { | |
|
152 | stack.push(ctx); | |
|
153 | var childs = ctx.querySelectorAll("*"); | |
|
154 | if (childs.length) { | |
|
155 | stack = stack.concat(Array.prototype.slice.call(childs)); | |
|
156 | } | |
|
157 | }); | |
|
158 | if (!ctx.length) { | |
|
159 | this.log("Empty context", "warn"); | |
|
160 | } | |
|
161 | return { | |
|
162 | "elements": stack, | |
|
163 | "length": stack.length | |
|
164 | }; | |
|
165 | } | |
|
166 | }, { | |
|
167 | key: "matches", | |
|
168 | value: function matches(el, selector) { | |
|
169 | return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector); | |
|
170 | } | |
|
171 | }, { | |
|
172 | key: "matchesFilter", | |
|
173 | value: function matchesFilter(el, exclM) { | |
|
174 | var _this2 = this; | |
|
175 | ||
|
176 | var remain = true; | |
|
177 | var fltr = this.opt.filter.concat(["script", "style", "title"]); | |
|
178 | if (!this.opt.iframes) { | |
|
179 | fltr = fltr.concat(["iframe"]); | |
|
180 | } | |
|
181 | if (exclM) { | |
|
182 | fltr = fltr.concat(["*[data-markjs='true']"]); | |
|
183 | } | |
|
184 | fltr.every(function (filter) { | |
|
185 | if (_this2.matches(el, filter)) { | |
|
186 | return remain = false; | |
|
187 | } | |
|
188 | return true; | |
|
189 | }); | |
|
190 | return !remain; | |
|
191 | } | |
|
192 | }, { | |
|
193 | key: "onIframeReady", | |
|
194 | value: function onIframeReady(ifr, successFn, errorFn) { | |
|
195 | try { | |
|
196 | (function () { | |
|
197 | var ifrWin = ifr.contentWindow, | |
|
198 | bl = "about:blank", | |
|
199 | compl = "complete"; | |
|
200 | var callCallback = function callCallback() { | |
|
201 | try { | |
|
202 | if (ifrWin.document === null) { | |
|
203 | throw new Error("iframe inaccessible"); | |
|
204 | } | |
|
205 | successFn(ifrWin.document); | |
|
206 | } catch (e) { | |
|
207 | errorFn(); | |
|
208 | } | |
|
209 | }; | |
|
210 | var isBlank = function isBlank() { | |
|
211 | var src = ifr.getAttribute("src").trim(), | |
|
212 | href = ifrWin.location.href; | |
|
213 | return href === bl && src !== bl && src; | |
|
214 | }; | |
|
215 | var observeOnload = function observeOnload() { | |
|
216 | var listener = function listener() { | |
|
217 | try { | |
|
218 | if (!isBlank()) { | |
|
219 | ifr.removeEventListener("load", listener); | |
|
220 | callCallback(); | |
|
221 | } | |
|
222 | } catch (e) { | |
|
223 | errorFn(); | |
|
224 | } | |
|
225 | }; | |
|
226 | ifr.addEventListener("load", listener); | |
|
227 | }; | |
|
228 | if (ifrWin.document.readyState === compl) { | |
|
229 | if (isBlank()) { | |
|
230 | observeOnload(); | |
|
231 | } else { | |
|
232 | callCallback(); | |
|
233 | } | |
|
234 | } else { | |
|
235 | observeOnload(); | |
|
236 | } | |
|
237 | })(); | |
|
238 | } catch (e) { | |
|
239 | errorFn(); | |
|
240 | } | |
|
241 | } | |
|
242 | }, { | |
|
243 | key: "forEachElementInIframe", | |
|
244 | value: function forEachElementInIframe(ifr, cb) { | |
|
245 | var _this3 = this; | |
|
246 | ||
|
247 | var end = arguments.length <= 2 || arguments[2] === undefined ? function () {} : arguments[2]; | |
|
248 | ||
|
249 | var open = 0; | |
|
250 | var checkEnd = function checkEnd() { | |
|
251 | if (--open < 1) { | |
|
252 | end(); | |
|
253 | } | |
|
254 | }; | |
|
255 | this.onIframeReady(ifr, function (con) { | |
|
256 | var stack = Array.prototype.slice.call(con.querySelectorAll("*")); | |
|
257 | if ((open = stack.length) === 0) { | |
|
258 | checkEnd(); | |
|
259 | } | |
|
260 | stack.forEach(function (el) { | |
|
261 | if (el.tagName.toLowerCase() === "iframe") { | |
|
262 | (function () { | |
|
263 | var j = 0; | |
|
264 | _this3.forEachElementInIframe(el, function (iel, len) { | |
|
265 | cb(iel, len); | |
|
266 | if (len - 1 === j) { | |
|
267 | checkEnd(); | |
|
268 | } | |
|
269 | j++; | |
|
270 | }, checkEnd); | |
|
271 | })(); | |
|
272 | } else { | |
|
273 | cb(el, stack.length); | |
|
274 | checkEnd(); | |
|
275 | } | |
|
276 | }); | |
|
277 | }, function () { | |
|
278 | var src = ifr.getAttribute("src"); | |
|
279 | _this3.log("iframe '" + src + "' could not be accessed", "warn"); | |
|
280 | checkEnd(); | |
|
281 | }); | |
|
282 | } | |
|
283 | }, { | |
|
284 | key: "forEachElement", | |
|
285 | value: function forEachElement(cb) { | |
|
286 | var _this4 = this; | |
|
287 | ||
|
288 | var end = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; | |
|
289 | var exclM = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; | |
|
290 | ||
|
291 | var _getElements = this.getElements(); | |
|
292 | ||
|
293 | var stack = _getElements.elements; | |
|
294 | var open = _getElements.length; | |
|
295 | ||
|
296 | var checkEnd = function checkEnd() { | |
|
297 | if (--open === 0) { | |
|
298 | end(); | |
|
299 | } | |
|
300 | }; | |
|
301 | checkEnd(++open); | |
|
302 | stack.forEach(function (el) { | |
|
303 | if (!_this4.matchesFilter(el, exclM)) { | |
|
304 | if (el.tagName.toLowerCase() === "iframe") { | |
|
305 | _this4.forEachElementInIframe(el, function (iel) { | |
|
306 | if (!_this4.matchesFilter(iel, exclM)) { | |
|
307 | cb(iel); | |
|
308 | } | |
|
309 | }, checkEnd); | |
|
310 | return; | |
|
311 | } else { | |
|
312 | cb(el); | |
|
313 | } | |
|
314 | } | |
|
315 | checkEnd(); | |
|
316 | }); | |
|
317 | } | |
|
318 | }, { | |
|
319 | key: "forEachNode", | |
|
320 | value: function forEachNode(cb) { | |
|
321 | var end = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1]; | |
|
322 | ||
|
323 | this.forEachElement(function (n) { | |
|
324 | for (n = n.firstChild; n; n = n.nextSibling) { | |
|
325 | if (n.nodeType === 3 && n.textContent.trim()) { | |
|
326 | cb(n); | |
|
327 | } | |
|
328 | } | |
|
329 | }, end); | |
|
330 | } | |
|
331 | }, { | |
|
332 | key: "wrapMatches", | |
|
333 | value: function wrapMatches(node, regex, custom, cb) { | |
|
334 | var hEl = !this.opt.element ? "mark" : this.opt.element, | |
|
335 | index = custom ? 0 : 2; | |
|
336 | var match = void 0; | |
|
337 | while ((match = regex.exec(node.textContent)) !== null) { | |
|
338 | var pos = match.index; | |
|
339 | if (!custom) { | |
|
340 | pos += match[index - 1].length; | |
|
341 | } | |
|
342 | var startNode = node.splitText(pos); | |
|
343 | ||
|
344 | node = startNode.splitText(match[index].length); | |
|
345 | if (startNode.parentNode !== null) { | |
|
346 | var repl = document.createElement(hEl); | |
|
347 | repl.setAttribute("data-markjs", "true"); | |
|
348 | if (this.opt.className) { | |
|
349 | repl.setAttribute("class", this.opt.className); | |
|
350 | } | |
|
351 | repl.textContent = match[index]; | |
|
352 | startNode.parentNode.replaceChild(repl, startNode); | |
|
353 | cb(repl); | |
|
354 | } | |
|
355 | regex.lastIndex = 0; | |
|
356 | } | |
|
357 | } | |
|
358 | }, { | |
|
359 | key: "unwrapMatches", | |
|
360 | value: function unwrapMatches(node) { | |
|
361 | var parent = node.parentNode; | |
|
362 | var docFrag = document.createDocumentFragment(); | |
|
363 | while (node.firstChild) { | |
|
364 | docFrag.appendChild(node.removeChild(node.firstChild)); | |
|
365 | } | |
|
366 | parent.replaceChild(docFrag, node); | |
|
367 | parent.normalize(); | |
|
368 | } | |
|
369 | }, { | |
|
370 | key: "markRegExp", | |
|
371 | value: function markRegExp(regexp, opt) { | |
|
372 | var _this5 = this; | |
|
373 | ||
|
374 | this.opt = opt; | |
|
375 | this.log("Searching with expression \"" + regexp + "\""); | |
|
376 | var found = false; | |
|
377 | var eachCb = function eachCb(element) { | |
|
378 | found = true; | |
|
379 | _this5.opt.each(element); | |
|
380 | }; | |
|
381 | this.forEachNode(function (node) { | |
|
382 | _this5.wrapMatches(node, regexp, true, eachCb); | |
|
383 | }, function () { | |
|
384 | if (!found) { | |
|
385 | _this5.opt.noMatch(regexp); | |
|
386 | } | |
|
387 | _this5.opt.complete(); | |
|
388 | _this5.opt.done(); | |
|
389 | }); | |
|
390 | } | |
|
391 | }, { | |
|
392 | key: "mark", | |
|
393 | value: function mark(sv, opt) { | |
|
394 | var _this6 = this; | |
|
395 | ||
|
396 | this.opt = opt; | |
|
397 | sv = typeof sv === "string" ? [sv] : sv; | |
|
398 | ||
|
399 | var _getSeparatedKeywords = this.getSeparatedKeywords(sv); | |
|
400 | ||
|
401 | var kwArr = _getSeparatedKeywords.keywords; | |
|
402 | var kwArrLen = _getSeparatedKeywords.length; | |
|
403 | ||
|
404 | if (kwArrLen === 0) { | |
|
405 | this.opt.complete(); | |
|
406 | this.opt.done(); | |
|
407 | } | |
|
408 | kwArr.forEach(function (kw) { | |
|
409 | var regex = new RegExp(_this6.createRegExp(kw), "gmi"), | |
|
410 | found = false; | |
|
411 | var eachCb = function eachCb(element) { | |
|
412 | found = true; | |
|
413 | _this6.opt.each(element); | |
|
414 | }; | |
|
415 | _this6.log("Searching with expression \"" + regex + "\""); | |
|
416 | _this6.forEachNode(function (node) { | |
|
417 | _this6.wrapMatches(node, regex, false, eachCb); | |
|
418 | }, function () { | |
|
419 | if (!found) { | |
|
420 | _this6.opt.noMatch(kw); | |
|
421 | } | |
|
422 | if (kwArr[kwArrLen - 1] === kw) { | |
|
423 | _this6.opt.complete(); | |
|
424 | _this6.opt.done(); | |
|
425 | } | |
|
426 | }); | |
|
427 | }); | |
|
428 | } | |
|
429 | }, { | |
|
430 | key: "unmark", | |
|
431 | value: function unmark(opt) { | |
|
432 | var _this7 = this; | |
|
433 | ||
|
434 | this.opt = opt; | |
|
435 | var sel = this.opt.element ? this.opt.element : "*"; | |
|
436 | sel += "[data-markjs]"; | |
|
437 | if (this.opt.className) { | |
|
438 | sel += "." + this.opt.className; | |
|
439 | } | |
|
440 | this.log("Removal selector \"" + sel + "\""); | |
|
441 | this.forEachElement(function (el) { | |
|
442 | if (_this7.matches(el, sel)) { | |
|
443 | _this7.unwrapMatches(el); | |
|
444 | } | |
|
445 | }, function () { | |
|
446 | _this7.opt.complete(); | |
|
447 | _this7.opt.done(); | |
|
448 | }, false); | |
|
449 | } | |
|
450 | }, { | |
|
451 | key: "opt", | |
|
452 | set: function set(val) { | |
|
453 | this._opt = _extends({}, { | |
|
454 | "element": "", | |
|
455 | "className": "", | |
|
456 | "filter": [], | |
|
457 | "iframes": false, | |
|
458 | "separateWordSearch": true, | |
|
459 | "diacritics": true, | |
|
460 | "synonyms": {}, | |
|
461 | "accuracy": "partially", | |
|
462 | "each": function each() {}, | |
|
463 | "noMatch": function noMatch() {}, | |
|
464 | "done": function done() {}, | |
|
465 | "complete": function complete() {}, | |
|
466 | "debug": false, | |
|
467 | "log": window.console | |
|
468 | }, val); | |
|
469 | }, | |
|
470 | get: function get() { | |
|
471 | return this._opt; | |
|
472 | } | |
|
473 | }]); | |
|
474 | ||
|
475 | return Mark; | |
|
476 | }(); | |
|
477 | ||
|
478 | $.fn.mark = function (sv, opt) { | |
|
479 | new Mark(this).mark(sv, opt); | |
|
480 | return this; | |
|
481 | }; | |
|
482 | $.fn.markRegExp = function (regexp, opt) { | |
|
483 | new Mark(this).markRegExp(regexp, opt); | |
|
484 | return this; | |
|
485 | }; | |
|
486 | $.fn.unmark = function (opt) { | |
|
487 | new Mark(this).unmark(opt); | |
|
488 | return this; | |
|
489 | }; | |
|
490 | }, window, document); |
@@ -18,6 +18,7 b' module.exports = function(grunt) {' | |||
|
18 | 18 | '<%= dirs.js.src %>/bootstrap.js', |
|
19 | 19 | '<%= dirs.js.src %>/mousetrap.js', |
|
20 | 20 | '<%= dirs.js.src %>/moment.js', |
|
21 | '<%= dirs.js.src %>/moment.js', | |
|
21 | 22 | '<%= dirs.js.src %>/appenlight-client-0.4.1.min.js', |
|
22 | 23 | |
|
23 | 24 | // Plugins |
@@ -27,6 +28,7 b' module.exports = function(grunt) {' | |||
|
27 | 28 | '<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js', |
|
28 | 29 | '<%= dirs.js.src %>/plugins/jquery.autocomplete.js', |
|
29 | 30 | '<%= dirs.js.src %>/plugins/jquery.debounce.js', |
|
31 | '<%= dirs.js.src %>/plugins/jquery.mark.js', | |
|
30 | 32 | '<%= dirs.js.src %>/plugins/jquery.timeago.js', |
|
31 | 33 | '<%= dirs.js.src %>/plugins/jquery.timeago-extension.js', |
|
32 | 34 |
@@ -79,7 +79,8 b' class SearchController(BaseRepoControlle' | |||
|
79 | 79 | |
|
80 | 80 | try: |
|
81 | 81 | search_result = searcher.search( |
|
82 |
search_query, search_type, c.perm_user, repo_name |
|
|
82 | search_query, search_type, c.perm_user, repo_name, | |
|
83 | requested_page, page_limit) | |
|
83 | 84 | |
|
84 | 85 | formatted_results = Page( |
|
85 | 86 | search_result['results'], page=requested_page, |
@@ -36,11 +36,14 b' import urlparse' | |||
|
36 | 36 | import time |
|
37 | 37 | import string |
|
38 | 38 | import hashlib |
|
39 | import pygments | |
|
39 | 40 | |
|
40 | 41 | from datetime import datetime |
|
41 | 42 | from functools import partial |
|
42 | 43 | from pygments.formatters.html import HtmlFormatter |
|
43 | 44 | from pygments import highlight as code_highlight |
|
45 | from pygments.lexers import ( | |
|
46 | get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype) | |
|
44 | 47 | from pylons import url |
|
45 | 48 | from pylons.i18n.translation import _, ungettext |
|
46 | 49 | from pyramid.threadlocal import get_current_request |
@@ -307,6 +310,176 b' class CodeHtmlFormatter(HtmlFormatter):' | |||
|
307 | 310 | yield 0, '</td></tr></table>' |
|
308 | 311 | |
|
309 | 312 | |
|
313 | class SearchContentCodeHtmlFormatter(CodeHtmlFormatter): | |
|
314 | def __init__(self, **kw): | |
|
315 | # only show these line numbers if set | |
|
316 | self.only_lines = kw.pop('only_line_numbers', []) | |
|
317 | self.query_terms = kw.pop('query_terms', []) | |
|
318 | self.max_lines = kw.pop('max_lines', 5) | |
|
319 | self.line_context = kw.pop('line_context', 3) | |
|
320 | self.url = kw.pop('url', None) | |
|
321 | ||
|
322 | super(CodeHtmlFormatter, self).__init__(**kw) | |
|
323 | ||
|
324 | def _wrap_code(self, source): | |
|
325 | for cnt, it in enumerate(source): | |
|
326 | i, t = it | |
|
327 | t = '<pre>%s</pre>' % t | |
|
328 | yield i, t | |
|
329 | ||
|
330 | def _wrap_tablelinenos(self, inner): | |
|
331 | yield 0, '<table class="code-highlight %stable">' % self.cssclass | |
|
332 | ||
|
333 | last_shown_line_number = 0 | |
|
334 | current_line_number = 1 | |
|
335 | ||
|
336 | for t, line in inner: | |
|
337 | if not t: | |
|
338 | yield t, line | |
|
339 | continue | |
|
340 | ||
|
341 | if current_line_number in self.only_lines: | |
|
342 | if last_shown_line_number + 1 != current_line_number: | |
|
343 | yield 0, '<tr>' | |
|
344 | yield 0, '<td class="line">...</td>' | |
|
345 | yield 0, '<td id="hlcode" class="code"></td>' | |
|
346 | yield 0, '</tr>' | |
|
347 | ||
|
348 | yield 0, '<tr>' | |
|
349 | if self.url: | |
|
350 | yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % ( | |
|
351 | self.url, current_line_number, current_line_number) | |
|
352 | else: | |
|
353 | yield 0, '<td class="line"><a href="">%i</a></td>' % ( | |
|
354 | current_line_number) | |
|
355 | yield 0, '<td id="hlcode" class="code">' + line + '</td>' | |
|
356 | yield 0, '</tr>' | |
|
357 | ||
|
358 | last_shown_line_number = current_line_number | |
|
359 | ||
|
360 | current_line_number += 1 | |
|
361 | ||
|
362 | ||
|
363 | yield 0, '</table>' | |
|
364 | ||
|
365 | ||
|
366 | def extract_phrases(text_query): | |
|
367 | """ | |
|
368 | Extracts phrases from search term string making sure phrases | |
|
369 | contained in double quotes are kept together - and discarding empty values | |
|
370 | or fully whitespace values eg. | |
|
371 | ||
|
372 | 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more'] | |
|
373 | ||
|
374 | """ | |
|
375 | ||
|
376 | in_phrase = False | |
|
377 | buf = '' | |
|
378 | phrases = [] | |
|
379 | for char in text_query: | |
|
380 | if in_phrase: | |
|
381 | if char == '"': # end phrase | |
|
382 | phrases.append(buf) | |
|
383 | buf = '' | |
|
384 | in_phrase = False | |
|
385 | continue | |
|
386 | else: | |
|
387 | buf += char | |
|
388 | continue | |
|
389 | else: | |
|
390 | if char == '"': # start phrase | |
|
391 | in_phrase = True | |
|
392 | phrases.append(buf) | |
|
393 | buf = '' | |
|
394 | continue | |
|
395 | elif char == ' ': | |
|
396 | phrases.append(buf) | |
|
397 | buf = '' | |
|
398 | continue | |
|
399 | else: | |
|
400 | buf += char | |
|
401 | ||
|
402 | phrases.append(buf) | |
|
403 | phrases = [phrase.strip() for phrase in phrases if phrase.strip()] | |
|
404 | return phrases | |
|
405 | ||
|
406 | ||
|
407 | def get_matching_offsets(text, phrases): | |
|
408 | """ | |
|
409 | Returns a list of string offsets in `text` that the list of `terms` match | |
|
410 | ||
|
411 | >>> get_matching_offsets('some text here', ['some', 'here']) | |
|
412 | [(0, 4), (10, 14)] | |
|
413 | ||
|
414 | """ | |
|
415 | offsets = [] | |
|
416 | for phrase in phrases: | |
|
417 | for match in re.finditer(phrase, text): | |
|
418 | offsets.append((match.start(), match.end())) | |
|
419 | ||
|
420 | return offsets | |
|
421 | ||
|
422 | ||
|
423 | def normalize_text_for_matching(x): | |
|
424 | """ | |
|
425 | Replaces all non alnum characters to spaces and lower cases the string, | |
|
426 | useful for comparing two text strings without punctuation | |
|
427 | """ | |
|
428 | return re.sub(r'[^\w]', ' ', x.lower()) | |
|
429 | ||
|
430 | ||
|
431 | def get_matching_line_offsets(lines, terms): | |
|
432 | """ Return a set of `lines` indices (starting from 1) matching a | |
|
433 | text search query, along with `context` lines above/below matching lines | |
|
434 | ||
|
435 | :param lines: list of strings representing lines | |
|
436 | :param terms: search term string to match in lines eg. 'some text' | |
|
437 | :param context: number of lines above/below a matching line to add to result | |
|
438 | :param max_lines: cut off for lines of interest | |
|
439 | eg. | |
|
440 | ||
|
441 | >>> get_matching_line_offsets(''' | |
|
442 | words words words | |
|
443 | words words words | |
|
444 | some text some | |
|
445 | words words words | |
|
446 | words words words | |
|
447 | text here what | |
|
448 | ''', 'text', context=1) | |
|
449 | {3: [(5, 9)], 6: [(0, 4)]] | |
|
450 | """ | |
|
451 | matching_lines = {} | |
|
452 | phrases = [normalize_text_for_matching(phrase) | |
|
453 | for phrase in extract_phrases(terms)] | |
|
454 | ||
|
455 | for line_index, line in enumerate(lines, start=1): | |
|
456 | match_offsets = get_matching_offsets( | |
|
457 | normalize_text_for_matching(line), phrases) | |
|
458 | if match_offsets: | |
|
459 | matching_lines[line_index] = match_offsets | |
|
460 | ||
|
461 | return matching_lines | |
|
462 | ||
|
463 | def get_lexer_safe(mimetype=None, filepath=None): | |
|
464 | """ | |
|
465 | Tries to return a relevant pygments lexer using mimetype/filepath name, | |
|
466 | defaulting to plain text if none could be found | |
|
467 | """ | |
|
468 | lexer = None | |
|
469 | try: | |
|
470 | if mimetype: | |
|
471 | lexer = get_lexer_for_mimetype(mimetype) | |
|
472 | if not lexer: | |
|
473 | lexer = get_lexer_for_filename(path) | |
|
474 | except pygments.util.ClassNotFound: | |
|
475 | pass | |
|
476 | ||
|
477 | if not lexer: | |
|
478 | lexer = get_lexer_by_name('text') | |
|
479 | ||
|
480 | return lexer | |
|
481 | ||
|
482 | ||
|
310 | 483 | def pygmentize(filenode, **kwargs): |
|
311 | 484 | """ |
|
312 | 485 | pygmentize function using pygments |
@@ -90,7 +90,8 b' class Search(BaseSearch):' | |||
|
90 | 90 | if self.searcher: |
|
91 | 91 | self.searcher.close() |
|
92 | 92 | |
|
93 |
def search(self, query, document_type, search_user, repo_name=None |
|
|
93 | def search(self, query, document_type, search_user, repo_name=None, | |
|
94 | requested_page=1, page_limit=10): | |
|
94 | 95 | log.debug(u'QUERY: %s on %s', query, document_type) |
|
95 | 96 | result = { |
|
96 | 97 | 'results': [], |
@@ -514,6 +514,26 b' div.search-code-body {' | |||
|
514 | 514 | .match { background-color: #faffa6;} |
|
515 | 515 | .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; } |
|
516 | 516 | } |
|
517 | .code-highlighttable { | |
|
518 | border-collapse: collapse; | |
|
519 | ||
|
520 | tr:hover { | |
|
521 | background: #fafafa; | |
|
522 | } | |
|
523 | td.code { | |
|
524 | padding-left: 10px; | |
|
525 | } | |
|
526 | td.line { | |
|
527 | border-right: 1px solid #ccc !important; | |
|
528 | padding-right: 10px; | |
|
529 | text-align: right; | |
|
530 | font-family: "Lucida Console",Monaco,monospace; | |
|
531 | span { | |
|
532 | white-space: pre-wrap; | |
|
533 | color: #666666; | |
|
534 | } | |
|
535 | } | |
|
536 | } | |
|
517 | 537 | } |
|
518 | 538 | |
|
519 | 539 | div.annotatediv { margin-left: 2px; margin-right: 4px; } |
@@ -33,7 +33,7 b'' | |||
|
33 | 33 | </div> |
|
34 | 34 | </td> |
|
35 | 35 | <td data-commit-id="${h.md5_safe(entry['repository'])+entry['commit_id']}" id="c-${h.md5_safe(entry['repository'])+entry['commit_id']}" class="message td-description open"> |
|
36 |
%if entry |
|
|
36 | %if entry.get('message_hl'): | |
|
37 | 37 | ${h.literal(entry['message_hl'])} |
|
38 | 38 | %else: |
|
39 | 39 | ${h.urlify_commit_message(entry['message'], entry['repository'])} |
@@ -1,3 +1,40 b'' | |||
|
1 | <%def name="highlight_text_file(terms, text, url, line_context=3, | |
|
2 | max_lines=10, | |
|
3 | mimetype=None, filepath=None)"> | |
|
4 | <% | |
|
5 | lines = text.split('\n') | |
|
6 | lines_of_interest = set() | |
|
7 | matching_lines = h.get_matching_line_offsets(lines, terms) | |
|
8 | shown_matching_lines = 0 | |
|
9 | ||
|
10 | for line_number in matching_lines: | |
|
11 | if len(lines_of_interest) < max_lines: | |
|
12 | lines_of_interest |= set(range( | |
|
13 | max(line_number - line_context, 0), | |
|
14 | min(line_number + line_context, len(lines)))) | |
|
15 | shown_matching_lines += 1 | |
|
16 | ||
|
17 | %> | |
|
18 | ${h.code_highlight( | |
|
19 | text, | |
|
20 | h.get_lexer_safe( | |
|
21 | mimetype=mimetype, | |
|
22 | filepath=filepath, | |
|
23 | ), | |
|
24 | h.SearchContentCodeHtmlFormatter( | |
|
25 | linenos=True, | |
|
26 | cssclass="code-highlight", | |
|
27 | url=url, | |
|
28 | query_terms=terms, | |
|
29 | only_line_numbers=lines_of_interest | |
|
30 | ))|n} | |
|
31 | %if len(matching_lines) > shown_matching_lines: | |
|
32 | <a href="${url}"> | |
|
33 | ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')} | |
|
34 | </p> | |
|
35 | %endif | |
|
36 | </%def> | |
|
37 | ||
|
1 | 38 | <div class="search-results"> |
|
2 | 39 | %for entry in c.formatted_results: |
|
3 | 40 | ## search results are additionally filtered, and this check is just a safe gate |
@@ -38,7 +75,9 b'' | |||
|
38 | 75 | </div> |
|
39 | 76 | </div> |
|
40 | 77 | <div class="code-body search-code-body"> |
|
41 | <pre>${h.literal(entry['content_short_hl'])}</pre> | |
|
78 | ${highlight_text_file(c.cur_query, entry['content'], | |
|
79 | url=h.url('files_home',repo_name=entry['repository'],revision=entry.get('commit_id', 'tip'),f_path=entry['f_path']), | |
|
80 | mimetype=entry.get('mimetype'), filepath=entry.get('path'))} | |
|
42 | 81 | </div> |
|
43 | 82 | </div> |
|
44 | 83 | % endif |
@@ -49,3 +88,14 b'' | |||
|
49 | 88 | ${c.formatted_results.pager('$link_previous ~2~ $link_next')} |
|
50 | 89 | </div> |
|
51 | 90 | %endif |
|
91 | ||
|
92 | %if c.cur_query: | |
|
93 | <script type="text/javascript"> | |
|
94 | $(function(){ | |
|
95 | $(".code").mark( | |
|
96 | '${' '.join(h.normalize_text_for_matching(c.cur_query).split())}', | |
|
97 | {"className": 'match', | |
|
98 | }); | |
|
99 | }) | |
|
100 | </script> | |
|
101 | %endif No newline at end of file |
@@ -155,3 +155,42 b' def test_get_visual_attr(pylonsapp):' | |||
|
155 | 155 | def test_chop_at(test_text, inclusive, expected_text): |
|
156 | 156 | assert helpers.chop_at_smart( |
|
157 | 157 | test_text, '\n', inclusive, '...') == expected_text |
|
158 | ||
|
159 | ||
|
160 | @pytest.mark.parametrize('test_text, expected_output', [ | |
|
161 | ('some text', ['some', 'text']), | |
|
162 | ('some text', ['some', 'text']), | |
|
163 | ('some text "with a phrase"', ['some', 'text', 'with a phrase']), | |
|
164 | ('"a phrase" "another phrase"', ['a phrase', 'another phrase']), | |
|
165 | ('"justphrase"', ['justphrase']), | |
|
166 | ('""', []), | |
|
167 | ('', []), | |
|
168 | (' ', []), | |
|
169 | ('" "', []), | |
|
170 | ]) | |
|
171 | def test_extract_phrases(test_text, expected_output): | |
|
172 | assert helpers.extract_phrases(test_text) == expected_output | |
|
173 | ||
|
174 | ||
|
175 | @pytest.mark.parametrize('test_text, text_phrases, expected_output', [ | |
|
176 | ('some text here', ['some', 'here'], [(0, 4), (10, 14)]), | |
|
177 | ('here here there', ['here'], [(0, 4), (5, 9), (11, 15)]), | |
|
178 | ('irrelevant', ['not found'], []), | |
|
179 | ('irrelevant', ['not found'], []), | |
|
180 | ]) | |
|
181 | def test_get_matching_offsets(test_text, text_phrases, expected_output): | |
|
182 | assert helpers.get_matching_offsets( | |
|
183 | test_text, text_phrases) == expected_output | |
|
184 | ||
|
185 | def test_normalize_text_for_matching(): | |
|
186 | assert helpers.normalize_text_for_matching( | |
|
187 | 'OJjfe)*#$*@)$JF*)3r2f80h') == 'ojjfe jf 3r2f80h' | |
|
188 | ||
|
189 | def test_get_matching_line_offsets(): | |
|
190 | assert helpers.get_matching_line_offsets([ | |
|
191 | 'words words words', | |
|
192 | 'words words words', | |
|
193 | 'some text some', | |
|
194 | 'words words words', | |
|
195 | 'words words words', | |
|
196 | 'text here what'], 'text') == {3: [(5, 9)], 6: [(0, 4)]} No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now