A semantic search engine for source code https://bitshift.benkurtovic.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

235 lines
11 KiB

  1. /*
  2. * @file Manages all query entry, `index.html` server querying, and results
  3. * diplay.
  4. */
  5. FINISH_TYPING_INTERVAL = 650;
  6. searchBar = $("form#search-bar input[type='text']")[0];
  7. resultsDiv = $("div#results")[0];
  8. var typingTimer, lastValue;
  9. //Obtained by parsing python file with pygments
  10. var codeExample = '<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40</pre></div></td><td class="code"><div class="hll"><pre><span class="sd">&quot;&quot;&quot;</span>\n<span class="sd">Module to contain all the project&#39;s Flask server plumbing.</span>\n<span class="sd">&quot;&quot;&quot;</span>\n\n<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>\n<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">render_template</span><span class="p">,</span> <span class="n">session</span>\n\n<span class="kn">from</span> <span class="nn">bitshift</span> <span class="kn">import</span> <span class="n">assets</span>\n<span class="c"># from bitshift.database import Database</span>\n<span class="c"># from bitshift.query import parse_query</span>\n\n<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>\n<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_object</span><span class="p">(</span><span class="s">&quot;bitshift.config&quot;</span><span class="p">)</span>\n\n<span class="n">app_env</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">jinja_env</span>\n<span class="n">app_env</span><span class="o">.</span><span class="n">line_statement_prefix</span> <span class="o">=</span> <span class="s">&quot;=&quot;</span>\n<span class="n">app_env</span><span class="o">.</span><span class="n">globals</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">assets</span><span class="o">=</span><span class="n">assets</span><span class="p">)</span>\n\n<span class="c"># database = Database()</span>\n\n<span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/&quot;</span><span class="p">)</span>\n<span class="k">def</span> <span class="nf">index</span><span class="p">():</span>\n <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&quot;index.html&quot;</span><span class="p">)</span>\n\n<span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/search/&lt;query&gt;&quot;</span><span class="p">)</span>\n<span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">query</span><span class="p">):</span>\n <span class="c"># tree = parse_query(query)</span>\n <span class="c"># database.search(tree)</span>\n <span class="k">pass</span>\n\n<span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/about&quot;</span><span class="p">)</span>\n<span class="k">def</span> <span class="nf">about</span><span class="p">():</span>\n <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&quot;about.html&quot;</span><span class="p">)</span>\n\n<span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/developers&quot;</span><span class="p">)</span>\n<span class="k">def</span> <span class="nf">developers</span><span class="p">():</span>\n <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&quot;developers.html&quot;</span><span class="p">)</span>\n\n<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span>\n <span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">debug</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>\n</pre></div>\n</td></tr></table>'
  11. searchBar.onkeyup = typingTimer;
  12. var testCodelet = {
  13. 'code_url': 'https://github.com/earwig/bitshift/blob/develop/app.py',
  14. 'filename': 'app.py',
  15. 'language': 'Python',
  16. 'date_created': 'May 10, 2014',
  17. 'date_modified': '2 days ago',
  18. 'symbols': {
  19. 'vars': [
  20. ['app', [ [12, -1, 12, -1] ],
  21. [ [13, -1, 13, -1],
  22. [15, -1, 15, -1],
  23. [16, -1, 16, -1],
  24. [17, -1, 17, -1] ]]
  25. ]
  26. },
  27. 'authors': ['sevko', 'earwig'],
  28. 'html_code': codeExample
  29. };
  30. // Enable infinite scrolling down the results page.
  31. $(window).scroll(function() {
  32. var searchField = $("div#search-field");
  33. if($(window).scrollTop() + $(window).height() == $(document).height() && searchField.hasClass('partly-visible')){
  34. loadMoreResults();
  35. }
  36. });
  37. // Enable capturing the `enter` key.
  38. $("form#search-bar").submit(function(event){
  39. event.preventDefault();
  40. return false;
  41. });
  42. /*
  43. * Clear the existing timer and set a new one the the user types text into the
  44. * search bar.
  45. */
  46. function typingTimer(event){
  47. clearTimeout(typingTimer);
  48. var enterKeyCode = 13;
  49. if(event.keyCode != enterKeyCode){
  50. if(lastValue != searchBar.value)
  51. typingTimer = setTimeout(finishedTyping, FINISH_TYPING_INTERVAL);
  52. }
  53. else {
  54. event.preventDefault();
  55. finishedTyping();
  56. return false;
  57. }
  58. };
  59. /*
  60. * Callback which queries the server whenver the user stops typing.
  61. *
  62. * Whenever the user doesn't type for a `FINISH_TYPING_INTERVAL` after having
  63. * entered new text in the search bar, send the current query request to the
  64. * server.
  65. */
  66. function finishedTyping(){
  67. lastValue = searchBar.value;
  68. var searchField = $("div#search-field");
  69. clearResults();
  70. if(searchBar.value){
  71. if(!searchField.hasClass("partly-visible"))
  72. searchField.addClass("partly-visible");
  73. populateResults();
  74. }
  75. else
  76. searchField.removeClass("partly-visible");
  77. }
  78. /*
  79. * Removes any child elements of `div#results`.
  80. */
  81. function clearResults(){
  82. while(resultsDiv.firstChild)
  83. resultsDiv.removeChild(resultsDiv.firstChild);
  84. }
  85. /*
  86. * Query the server with the current search string, and populate `div#results`
  87. * with its response.
  88. */
  89. function populateResults(){
  90. var results = queryServer();
  91. for(var result = 0; result < results.length; result++){
  92. var newDiv = results[result];
  93. resultsDiv.appendChild(newDiv);
  94. setTimeout(
  95. (function(divReference){
  96. return function(){
  97. divReference.classList.add("cascade");
  98. };
  99. }(newDiv)), result * 20);
  100. }
  101. }
  102. /*
  103. * Create a result element based upon a codelet instance.
  104. *
  105. * @return {Element} The result element.
  106. */
  107. function createResult(codelet) {
  108. //Level 1
  109. var newDiv = document.createElement("div"),
  110. table = document.createElement("table"),
  111. row = document.createElement("tr");
  112. //Level 2
  113. var displayInfo = document.createElement("div"),
  114. sidebar = document.createElement("td"),
  115. codeElt = document.createElement("td"),
  116. hiddenInfo = document.createElement("td");
  117. //Level 3
  118. var title = document.createElement("span"),
  119. site = document.createElement("span"),
  120. dateModified = document.createElement("span"),
  121. language = document.createElement("span"),
  122. dateCreated = document.createElement("span"),
  123. matches = document.createElement("div"),
  124. authors = document.createElement("div");
  125. //Classes and ID's
  126. newDiv.classList.add('result');
  127. displayInfo.id = 'display-info';
  128. sidebar.id = 'sidebar';
  129. codeElt.id = 'code';
  130. hiddenInfo.id = 'hidden-info';
  131. title.id = 'title';
  132. site.id = 'site';
  133. dateModified.id = 'date-modified';
  134. language.id = 'language';
  135. dateCreated.id = 'date-created';
  136. matches.id = 'matches';
  137. authors.id = 'authors';
  138. //Add the bulk of the html
  139. var hostUrl = codelet.code_url.match(/htt(p|ps):\/\/\w+\.\w+/)[0],
  140. hostName = hostUrl.split('://')[1].split('.')[0];
  141. title.innerHTML = 'File <a href="' + codelet.code_url + '">'
  142. + codelet.filename + '</a>';
  143. site.innerHTML = 'on <a href="' + hostUrl + '">' + hostName +'</a>';
  144. dateModified.innerHTML = 'Last modified ' + codelet.date_modified;
  145. // Needs to be changed from int to string on the server
  146. language.innerHTML = codelet.language;
  147. dateCreated.innerHTML = 'Created ' + codelet.date_created;
  148. matches.innerHTML = 'Symbol matches: ';
  149. $.each(Object.keys(codelet.symbols), function(i, t) {
  150. $.each(codelet.symbols[t], function(i, s) {
  151. matches.innerHTML += '<span>' + s[0] + '</span>';
  152. });
  153. });
  154. authors.innerHTML = 'Authors: ';
  155. $.each(codelet.authors, function(i, a) {
  156. authors.innerHTML += '<span>' + a + '; </span>';
  157. });
  158. sidebar.innerHTML = '';
  159. // Needs to be processed on the server
  160. codeElt.innerHTML = '<div id=tablecontainer>' + codelet.html_code + '</div>';
  161. //Finish and append elements to parent elements
  162. row.appendChild(sidebar);
  163. row.appendChild(codeElt);
  164. row.appendChild(hiddenInfo);
  165. table.appendChild(row);
  166. displayInfo.appendChild(title);
  167. displayInfo.appendChild(site);
  168. displayInfo.appendChild(dateModified);
  169. hiddenInfo.appendChild(dateCreated);
  170. hiddenInfo.appendChild(language);
  171. hiddenInfo.appendChild(matches);
  172. hiddenInfo.appendChild(authors);
  173. newDiv.appendChild(displayInfo);
  174. newDiv.appendChild(table);
  175. return newDiv;
  176. }
  177. /*
  178. * AJAX the current query string to the server, and return its response.
  179. *
  180. * @return {Array} The server's response in the form of `div.result` DOM
  181. * elements, to fill `div#results`.
  182. */
  183. function queryServer(){
  184. var resultDivs = []
  185. for(var result = 0; result < 20; result++){
  186. var newDiv = createResult(testCodelet);
  187. resultDivs.push(newDiv);
  188. }
  189. return resultDivs;
  190. }
  191. /*
  192. * Adds more results to `div#results`.
  193. */
  194. function loadMoreResults(){
  195. results = queryServer();
  196. for(var result = 0; result < results.length; result++){
  197. var newDiv = results[result];
  198. resultsDiv.appendChild(newDiv);
  199. setTimeout(
  200. (function(divReference){
  201. return function(){
  202. divReference.classList.add("cascade");
  203. };
  204. }(newDiv)), result * 20);
  205. }
  206. }