AdvancedSearchController.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. /*
  2. * Copyright (C) 2011 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above
  12. * copyright notice, this list of conditions and the following disclaimer
  13. * in the documentation and/or other materials provided with the
  14. * distribution.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
  17. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  18. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  19. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
  20. * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  21. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  22. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  26. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. */
  28. /**
  29. * @constructor
  30. */
  31. WebInspector.AdvancedSearchController = function()
  32. {
  33. this._shortcut = WebInspector.AdvancedSearchController.createShortcut();
  34. this._searchId = 0;
  35. WebInspector.settings.advancedSearchConfig = WebInspector.settings.createSetting("advancedSearchConfig", new WebInspector.SearchConfig("", true, false));
  36. WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
  37. }
  38. /**
  39. * @return {!WebInspector.KeyboardShortcut.Descriptor}
  40. */
  41. WebInspector.AdvancedSearchController.createShortcut = function()
  42. {
  43. if (WebInspector.isMac())
  44. return WebInspector.KeyboardShortcut.makeDescriptor("f", WebInspector.KeyboardShortcut.Modifiers.Meta | WebInspector.KeyboardShortcut.Modifiers.Alt);
  45. else
  46. return WebInspector.KeyboardShortcut.makeDescriptor("f", WebInspector.KeyboardShortcut.Modifiers.Ctrl | WebInspector.KeyboardShortcut.Modifiers.Shift);
  47. }
  48. WebInspector.AdvancedSearchController.prototype = {
  49. /**
  50. * @param {KeyboardEvent} event
  51. * @return {boolean}
  52. */
  53. handleShortcut: function(event)
  54. {
  55. if (WebInspector.KeyboardShortcut.makeKeyFromEvent(event) === this._shortcut.key) {
  56. if (!this._searchView || !this._searchView.isShowing() || this._searchView._search !== document.activeElement) {
  57. WebInspector.showPanel("scripts");
  58. this.show();
  59. } else
  60. this.close();
  61. event.consume(true);
  62. return true;
  63. }
  64. return false;
  65. },
  66. _frameNavigated: function()
  67. {
  68. this.resetSearch();
  69. },
  70. /**
  71. * @param {WebInspector.SearchScope} searchScope
  72. */
  73. registerSearchScope: function(searchScope)
  74. {
  75. // FIXME: implement multiple search scopes.
  76. this._searchScope = searchScope;
  77. },
  78. show: function()
  79. {
  80. if (!this._searchView)
  81. this._searchView = new WebInspector.SearchView(this);
  82. this._searchView.syncToSelection();
  83. if (this._searchView.isShowing())
  84. this._searchView.focus();
  85. else
  86. WebInspector.showViewInDrawer(this._searchView._searchPanelElement, this._searchView, this.stopSearch.bind(this));
  87. this.startIndexing();
  88. },
  89. close: function()
  90. {
  91. this.stopSearch();
  92. WebInspector.closeViewInDrawer();
  93. },
  94. /**
  95. * @param {boolean} finished
  96. */
  97. _onIndexingFinished: function(finished)
  98. {
  99. delete this._isIndexing;
  100. this._searchView.indexingFinished(finished);
  101. if (!finished)
  102. delete this._pendingSearchConfig;
  103. if (!this._pendingSearchConfig)
  104. return;
  105. var searchConfig = this._pendingSearchConfig
  106. delete this._pendingSearchConfig;
  107. this._innerStartSearch(searchConfig);
  108. },
  109. startIndexing: function()
  110. {
  111. this._isIndexing = true;
  112. // FIXME: this._currentSearchScope should be initialized based on searchConfig
  113. this._currentSearchScope = this._searchScope;
  114. if (this._progressIndicator)
  115. this._progressIndicator.done();
  116. this._progressIndicator = new WebInspector.ProgressIndicator();
  117. this._searchView.indexingStarted(this._progressIndicator);
  118. this._currentSearchScope.performIndexing(this._progressIndicator, this._onIndexingFinished.bind(this));
  119. },
  120. /**
  121. * @param {number} searchId
  122. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  123. */
  124. _onSearchResult: function(searchId, searchResult)
  125. {
  126. if (searchId !== this._searchId)
  127. return;
  128. this._searchView.addSearchResult(searchResult);
  129. if (!searchResult.searchMatches.length)
  130. return;
  131. if (!this._searchResultsPane)
  132. this._searchResultsPane = this._currentSearchScope.createSearchResultsPane(this._searchConfig);
  133. this._searchView.resultsPane = this._searchResultsPane;
  134. this._searchResultsPane.addSearchResult(searchResult);
  135. },
  136. /**
  137. * @param {number} searchId
  138. * @param {boolean} finished
  139. */
  140. _onSearchFinished: function(searchId, finished)
  141. {
  142. if (searchId !== this._searchId)
  143. return;
  144. if (!this._searchResultsPane)
  145. this._searchView.nothingFound();
  146. this._searchView.searchFinished(finished);
  147. delete this._searchConfig;
  148. },
  149. /**
  150. * @param {WebInspector.SearchConfig} searchConfig
  151. */
  152. startSearch: function(searchConfig)
  153. {
  154. this.resetSearch();
  155. ++this._searchId;
  156. if (!this._isIndexing)
  157. this.startIndexing();
  158. this._pendingSearchConfig = searchConfig;
  159. },
  160. /**
  161. * @param {WebInspector.SearchConfig} searchConfig
  162. */
  163. _innerStartSearch: function(searchConfig)
  164. {
  165. this._searchConfig = searchConfig;
  166. // FIXME: this._currentSearchScope should be initialized based on searchConfig
  167. this._currentSearchScope = this._searchScope;
  168. if (this._progressIndicator)
  169. this._progressIndicator.done();
  170. this._progressIndicator = new WebInspector.ProgressIndicator();
  171. var totalSearchResultsCount = this._currentSearchScope.performSearch(searchConfig, this._progressIndicator, this._onSearchResult.bind(this, this._searchId), this._onSearchFinished.bind(this, this._searchId));
  172. this._searchView.searchStarted(this._progressIndicator);
  173. },
  174. resetSearch: function()
  175. {
  176. this.stopSearch();
  177. if (this._searchResultsPane) {
  178. this._searchView.resetResults();
  179. delete this._searchResultsPane;
  180. }
  181. },
  182. stopSearch: function()
  183. {
  184. if (this._progressIndicator)
  185. this._progressIndicator.cancel();
  186. if (this._currentSearchScope)
  187. this._currentSearchScope.stopSearch();
  188. delete this._searchConfig;
  189. }
  190. }
  191. /**
  192. * @constructor
  193. * @extends {WebInspector.View}
  194. * @param {WebInspector.AdvancedSearchController} controller
  195. */
  196. WebInspector.SearchView = function(controller)
  197. {
  198. WebInspector.View.call(this);
  199. this._controller = controller;
  200. this.element.className = "search-view";
  201. this._searchPanelElement = document.createElement("span");
  202. this._searchPanelElement.className = "search-drawer-header";
  203. this._searchPanelElement.addEventListener("keydown", this._onKeyDown.bind(this), false);
  204. this._searchResultsElement = this.element.createChild("div");
  205. this._searchResultsElement.className = "search-results";
  206. this._searchLabel = this._searchPanelElement.createChild("span");
  207. this._searchLabel.textContent = WebInspector.UIString("Search sources");
  208. this._search = this._searchPanelElement.createChild("input");
  209. this._search.setAttribute("type", "search");
  210. this._search.addStyleClass("search-config-search");
  211. this._search.setAttribute("results", "0");
  212. this._search.setAttribute("size", 30);
  213. this._ignoreCaseLabel = this._searchPanelElement.createChild("label");
  214. this._ignoreCaseLabel.addStyleClass("search-config-label");
  215. this._ignoreCaseCheckbox = this._ignoreCaseLabel.createChild("input");
  216. this._ignoreCaseCheckbox.setAttribute("type", "checkbox");
  217. this._ignoreCaseCheckbox.addStyleClass("search-config-checkbox");
  218. this._ignoreCaseLabel.appendChild(document.createTextNode(WebInspector.UIString("Ignore case")));
  219. this._regexLabel = this._searchPanelElement.createChild("label");
  220. this._regexLabel.addStyleClass("search-config-label");
  221. this._regexCheckbox = this._regexLabel.createChild("input");
  222. this._regexCheckbox.setAttribute("type", "checkbox");
  223. this._regexCheckbox.addStyleClass("search-config-checkbox");
  224. this._regexLabel.appendChild(document.createTextNode(WebInspector.UIString("Regular expression")));
  225. this._searchStatusBarElement = document.createElement("div");
  226. this._searchStatusBarElement.className = "search-status-bar-item";
  227. this._searchMessageElement = this._searchStatusBarElement.createChild("div");
  228. this._searchMessageElement.className = "search-status-bar-message";
  229. this._searchResultsMessageElement = document.createElement("span");
  230. this._searchResultsMessageElement.className = "search-results-status-bar-message";
  231. this._load();
  232. }
  233. // Number of recent search queries to store.
  234. WebInspector.SearchView.maxQueriesCount = 20;
  235. WebInspector.SearchView.prototype = {
  236. /**
  237. * @return {Array.<Element>}
  238. */
  239. get statusBarItems()
  240. {
  241. return [this._searchStatusBarElement, this._searchResultsMessageElement];
  242. },
  243. /**
  244. * @return {WebInspector.SearchConfig}
  245. */
  246. get searchConfig()
  247. {
  248. return new WebInspector.SearchConfig(this._search.value, this._ignoreCaseCheckbox.checked, this._regexCheckbox.checked);
  249. },
  250. syncToSelection: function()
  251. {
  252. var selection = window.getSelection();
  253. if (selection.rangeCount) {
  254. var queryCandidate = selection.toString().replace(/\r?\n.*/, "");
  255. if (queryCandidate)
  256. this._search.value = queryCandidate;
  257. }
  258. },
  259. /**
  260. * @type {WebInspector.SearchResultsPane}
  261. */
  262. set resultsPane(resultsPane)
  263. {
  264. this.resetResults();
  265. this._searchResultsElement.appendChild(resultsPane.element);
  266. },
  267. /**
  268. * @param {WebInspector.ProgressIndicator} progressIndicator
  269. */
  270. searchStarted: function(progressIndicator)
  271. {
  272. this.resetResults();
  273. this._resetCounters();
  274. this._searchMessageElement.textContent = WebInspector.UIString("Searching...");
  275. progressIndicator.show(this._searchStatusBarElement);
  276. this._updateSearchResultsMessage();
  277. if (!this._searchingView)
  278. this._searchingView = new WebInspector.EmptyView(WebInspector.UIString("Searching..."));
  279. this._searchingView.show(this._searchResultsElement);
  280. },
  281. /**
  282. * @param {WebInspector.ProgressIndicator} progressIndicator
  283. */
  284. indexingStarted: function(progressIndicator)
  285. {
  286. this._searchMessageElement.textContent = WebInspector.UIString("Indexing...");
  287. progressIndicator.show(this._searchStatusBarElement);
  288. },
  289. /**
  290. * @param {boolean} finished
  291. */
  292. indexingFinished: function(finished)
  293. {
  294. this._searchMessageElement.textContent = finished ? "" : WebInspector.UIString("Indexing interrupted.");
  295. },
  296. _updateSearchResultsMessage: function()
  297. {
  298. if (this._searchMatchesCount && this._searchResultsCount)
  299. this._searchResultsMessageElement.textContent = WebInspector.UIString("Found %d matches in %d files.", this._searchMatchesCount, this._nonEmptySearchResultsCount);
  300. else
  301. this._searchResultsMessageElement.textContent = "";
  302. },
  303. resetResults: function()
  304. {
  305. if (this._searchingView)
  306. this._searchingView.detach();
  307. if (this._notFoundView)
  308. this._notFoundView.detach();
  309. this._searchResultsElement.removeChildren();
  310. },
  311. _resetCounters: function()
  312. {
  313. this._searchMatchesCount = 0;
  314. this._searchResultsCount = 0;
  315. this._nonEmptySearchResultsCount = 0;
  316. },
  317. nothingFound: function()
  318. {
  319. this.resetResults();
  320. if (!this._notFoundView)
  321. this._notFoundView = new WebInspector.EmptyView(WebInspector.UIString("No matches found."));
  322. this._notFoundView.show(this._searchResultsElement);
  323. this._searchResultsMessageElement.textContent = WebInspector.UIString("No matches found.");
  324. },
  325. /**
  326. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  327. */
  328. addSearchResult: function(searchResult)
  329. {
  330. this._searchMatchesCount += searchResult.searchMatches.length;
  331. this._searchResultsCount++;
  332. if (searchResult.searchMatches.length)
  333. this._nonEmptySearchResultsCount++;
  334. this._updateSearchResultsMessage();
  335. },
  336. /**
  337. * @param {boolean} finished
  338. */
  339. searchFinished: function(finished)
  340. {
  341. this._searchMessageElement.textContent = finished ? WebInspector.UIString("Search finished.") : WebInspector.UIString("Search interrupted.");
  342. },
  343. focus: function()
  344. {
  345. WebInspector.setCurrentFocusElement(this._search);
  346. this._search.select();
  347. },
  348. wasShown: function()
  349. {
  350. this.focus();
  351. },
  352. willHide: function()
  353. {
  354. this._controller.stopSearch();
  355. },
  356. /**
  357. * @param {Event} event
  358. */
  359. _onKeyDown: function(event)
  360. {
  361. switch (event.keyCode) {
  362. case WebInspector.KeyboardShortcut.Keys.Enter.code:
  363. this._onAction();
  364. break;
  365. case WebInspector.KeyboardShortcut.Keys.Esc.code:
  366. this._controller.close();
  367. event.consume(true);
  368. break;
  369. }
  370. },
  371. _save: function()
  372. {
  373. var searchConfig = new WebInspector.SearchConfig(this.searchConfig.query, this.searchConfig.ignoreCase, this.searchConfig.isRegex);
  374. WebInspector.settings.advancedSearchConfig.set(searchConfig);
  375. },
  376. _load: function()
  377. {
  378. var searchConfig = WebInspector.settings.advancedSearchConfig.get();
  379. this._search.value = searchConfig.query;
  380. this._ignoreCaseCheckbox.checked = searchConfig.ignoreCase;
  381. this._regexCheckbox.checked = searchConfig.isRegex;
  382. },
  383. _onAction: function()
  384. {
  385. if (!this.searchConfig.query || !this.searchConfig.query.length)
  386. return;
  387. this._save();
  388. this._controller.startSearch(this.searchConfig);
  389. },
  390. __proto__: WebInspector.View.prototype
  391. }
  392. /**
  393. * @constructor
  394. * @param {string} query
  395. * @param {boolean} ignoreCase
  396. * @param {boolean} isRegex
  397. */
  398. WebInspector.SearchConfig = function(query, ignoreCase, isRegex)
  399. {
  400. this.query = query;
  401. this.ignoreCase = ignoreCase;
  402. this.isRegex = isRegex;
  403. }
  404. /**
  405. * @interface
  406. */
  407. WebInspector.SearchScope = function()
  408. {
  409. }
  410. WebInspector.SearchScope.prototype = {
  411. /**
  412. * @param {WebInspector.SearchConfig} searchConfig
  413. * @param {WebInspector.Progress} progress
  414. * @param {function(WebInspector.FileBasedSearchResultsPane.SearchResult)} searchResultCallback
  415. * @param {function(boolean)} searchFinishedCallback
  416. */
  417. performSearch: function(searchConfig, progress, searchResultCallback, searchFinishedCallback) { },
  418. stopSearch: function() { },
  419. /**
  420. * @param {WebInspector.SearchConfig} searchConfig
  421. * @return {WebInspector.SearchResultsPane}
  422. */
  423. createSearchResultsPane: function(searchConfig) { }
  424. }
  425. /**
  426. * @constructor
  427. * @param {number} offset
  428. * @param {number} length
  429. */
  430. WebInspector.SearchResult = function(offset, length)
  431. {
  432. this.offset = offset;
  433. this.length = length;
  434. }
  435. /**
  436. * @constructor
  437. * @param {WebInspector.SearchConfig} searchConfig
  438. */
  439. WebInspector.SearchResultsPane = function(searchConfig)
  440. {
  441. this._searchConfig = searchConfig;
  442. this.element = document.createElement("div");
  443. }
  444. WebInspector.SearchResultsPane.prototype = {
  445. /**
  446. * @return {WebInspector.SearchConfig}
  447. */
  448. get searchConfig()
  449. {
  450. return this._searchConfig;
  451. },
  452. /**
  453. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  454. */
  455. addSearchResult: function(searchResult) { }
  456. }
  457. /**
  458. * @constructor
  459. * @extends {WebInspector.SearchResultsPane}
  460. * @param {WebInspector.SearchConfig} searchConfig
  461. */
  462. WebInspector.FileBasedSearchResultsPane = function(searchConfig)
  463. {
  464. WebInspector.SearchResultsPane.call(this, searchConfig);
  465. this._searchResults = [];
  466. this.element.id ="search-results-pane-file-based";
  467. this._treeOutlineElement = document.createElement("ol");
  468. this._treeOutlineElement.className = "search-results-outline-disclosure";
  469. this.element.appendChild(this._treeOutlineElement);
  470. this._treeOutline = new TreeOutline(this._treeOutlineElement);
  471. this._matchesExpandedCount = 0;
  472. }
  473. WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount = 20;
  474. WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce = 20;
  475. WebInspector.FileBasedSearchResultsPane.prototype = {
  476. /**
  477. * @param {WebInspector.UISourceCode} uiSourceCode
  478. * @param {number} lineNumber
  479. * @param {number} columnNumber
  480. * @return {Element}
  481. */
  482. _createAnchor: function(uiSourceCode, lineNumber, columnNumber)
  483. {
  484. var anchor = document.createElement("a");
  485. anchor.preferredPanel = "scripts";
  486. anchor.href = sanitizeHref(uiSourceCode.originURL());
  487. anchor.uiSourceCode = uiSourceCode;
  488. anchor.lineNumber = lineNumber;
  489. return anchor;
  490. },
  491. /**
  492. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  493. */
  494. addSearchResult: function(searchResult)
  495. {
  496. this._searchResults.push(searchResult);
  497. var uiSourceCode = searchResult.uiSourceCode;
  498. if (!uiSourceCode)
  499. return;
  500. var searchMatches = searchResult.searchMatches;
  501. var fileTreeElement = this._addFileTreeElement(uiSourceCode.fullDisplayName(), searchMatches.length, this._searchResults.length - 1);
  502. },
  503. /**
  504. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  505. * @param {TreeElement} fileTreeElement
  506. */
  507. _fileTreeElementExpanded: function(searchResult, fileTreeElement)
  508. {
  509. if (fileTreeElement._initialized)
  510. return;
  511. var toIndex = Math.min(searchResult.searchMatches.length, WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce);
  512. if (toIndex < searchResult.searchMatches.length) {
  513. this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex - 1);
  514. this._appendShowMoreMatchesElement(fileTreeElement, searchResult, toIndex - 1);
  515. } else
  516. this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex);
  517. fileTreeElement._initialized = true;
  518. },
  519. /**
  520. * @param {TreeElement} fileTreeElement
  521. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  522. * @param {number} fromIndex
  523. * @param {number} toIndex
  524. */
  525. _appendSearchMatches: function(fileTreeElement, searchResult, fromIndex, toIndex)
  526. {
  527. var uiSourceCode = searchResult.uiSourceCode;
  528. var searchMatches = searchResult.searchMatches;
  529. var regex = createSearchRegex(this._searchConfig.query, !this._searchConfig.ignoreCase, this._searchConfig.isRegex);
  530. for (var i = fromIndex; i < toIndex; ++i) {
  531. var lineNumber = searchMatches[i].lineNumber;
  532. var lineContent = searchMatches[i].lineContent;
  533. var matchRanges = this._regexMatchRanges(lineContent, regex);
  534. var anchor = this._createAnchor(uiSourceCode, lineNumber, matchRanges[0].offset);
  535. var numberString = numberToStringWithSpacesPadding(lineNumber + 1, 4);
  536. var lineNumberSpan = document.createElement("span");
  537. lineNumberSpan.addStyleClass("search-match-line-number");
  538. lineNumberSpan.textContent = numberString;
  539. anchor.appendChild(lineNumberSpan);
  540. var contentSpan = this._createContentSpan(lineContent, matchRanges);
  541. anchor.appendChild(contentSpan);
  542. var searchMatchElement = new TreeElement("", null, false);
  543. searchMatchElement.selectable = false;
  544. fileTreeElement.appendChild(searchMatchElement);
  545. searchMatchElement.listItemElement.className = "search-match source-code";
  546. searchMatchElement.listItemElement.appendChild(anchor);
  547. }
  548. },
  549. /**
  550. * @param {TreeElement} fileTreeElement
  551. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  552. * @param {number} startMatchIndex
  553. */
  554. _appendShowMoreMatchesElement: function(fileTreeElement, searchResult, startMatchIndex)
  555. {
  556. var matchesLeftCount = searchResult.searchMatches.length - startMatchIndex;
  557. var showMoreMatchesText = WebInspector.UIString("Show all matches (%d more).", matchesLeftCount);
  558. var showMoreMatchesElement = new TreeElement(showMoreMatchesText, null, false);
  559. fileTreeElement.appendChild(showMoreMatchesElement);
  560. showMoreMatchesElement.listItemElement.addStyleClass("show-more-matches");
  561. showMoreMatchesElement.onselect = this._showMoreMatchesElementSelected.bind(this, searchResult, startMatchIndex, showMoreMatchesElement);
  562. },
  563. /**
  564. * @param {WebInspector.FileBasedSearchResultsPane.SearchResult} searchResult
  565. * @param {number} startMatchIndex
  566. * @param {TreeElement} showMoreMatchesElement
  567. */
  568. _showMoreMatchesElementSelected: function(searchResult, startMatchIndex, showMoreMatchesElement)
  569. {
  570. var fileTreeElement = showMoreMatchesElement.parent;
  571. fileTreeElement.removeChild(showMoreMatchesElement);
  572. this._appendSearchMatches(fileTreeElement, searchResult, startMatchIndex, searchResult.searchMatches.length);
  573. },
  574. /**
  575. * @param {string} fileName
  576. * @param {number} searchMatchesCount
  577. * @param {number} searchResultIndex
  578. */
  579. _addFileTreeElement: function(fileName, searchMatchesCount, searchResultIndex)
  580. {
  581. var fileTreeElement = new TreeElement("", null, true);
  582. fileTreeElement.toggleOnClick = true;
  583. fileTreeElement.selectable = false;
  584. this._treeOutline.appendChild(fileTreeElement);
  585. fileTreeElement.listItemElement.addStyleClass("search-result");
  586. var fileNameSpan = document.createElement("span");
  587. fileNameSpan.className = "search-result-file-name";
  588. fileNameSpan.textContent = fileName;
  589. fileTreeElement.listItemElement.appendChild(fileNameSpan);
  590. var matchesCountSpan = document.createElement("span");
  591. matchesCountSpan.className = "search-result-matches-count";
  592. if (searchMatchesCount === 1)
  593. matchesCountSpan.textContent = WebInspector.UIString("(%d match)", searchMatchesCount);
  594. else
  595. matchesCountSpan.textContent = WebInspector.UIString("(%d matches)", searchMatchesCount);
  596. fileTreeElement.listItemElement.appendChild(matchesCountSpan);
  597. var searchResult = this._searchResults[searchResultIndex];
  598. fileTreeElement.onexpand = this._fileTreeElementExpanded.bind(this, searchResult, fileTreeElement);
  599. // Expand until at least certain amount of matches is expanded.
  600. if (this._matchesExpandedCount < WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount)
  601. fileTreeElement.expand();
  602. this._matchesExpandedCount += searchResult.searchMatches.length;
  603. return fileTreeElement;
  604. },
  605. /**
  606. * @param {string} lineContent
  607. * @param {RegExp} regex
  608. * @return {Array.<WebInspector.SearchResult>}
  609. */
  610. _regexMatchRanges: function(lineContent, regex)
  611. {
  612. regex.lastIndex = 0;
  613. var match;
  614. var offset = 0;
  615. var matchRanges = [];
  616. while ((regex.lastIndex < lineContent.length) && (match = regex.exec(lineContent)))
  617. matchRanges.push(new WebInspector.SearchResult(match.index, match[0].length));
  618. return matchRanges;
  619. },
  620. /**
  621. * @param {string} lineContent
  622. * @param {Array.<WebInspector.SearchResult>} matchRanges
  623. */
  624. _createContentSpan: function(lineContent, matchRanges)
  625. {
  626. var contentSpan = document.createElement("span");
  627. contentSpan.className = "search-match-content";
  628. contentSpan.textContent = lineContent;
  629. WebInspector.highlightRangesWithStyleClass(contentSpan, matchRanges, "highlighted-match");
  630. return contentSpan;
  631. },
  632. __proto__: WebInspector.SearchResultsPane.prototype
  633. }
  634. /**
  635. * @constructor
  636. * @param {WebInspector.UISourceCode} uiSourceCode
  637. * @param {Array.<Object>} searchMatches
  638. */
  639. WebInspector.FileBasedSearchResultsPane.SearchResult = function(uiSourceCode, searchMatches) {
  640. this.uiSourceCode = uiSourceCode;
  641. this.searchMatches = searchMatches;
  642. }
  643. /**
  644. * @type {WebInspector.AdvancedSearchController}
  645. */
  646. WebInspector.advancedSearchController = null;