RevisionHistoryView.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * Copyright (C) 2012 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. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @constructor
  32. * @extends {WebInspector.View}
  33. */
  34. WebInspector.RevisionHistoryView = function()
  35. {
  36. WebInspector.View.call(this);
  37. this.registerRequiredCSS("revisionHistory.css");
  38. this.element.addStyleClass("revision-history-drawer");
  39. this.element.addStyleClass("fill");
  40. this.element.addStyleClass("outline-disclosure");
  41. this._uiSourceCodeItems = new Map();
  42. var olElement = this.element.createChild("ol");
  43. this._treeOutline = new TreeOutline(olElement);
  44. /**
  45. * @param {WebInspector.UISourceCode} uiSourceCode
  46. */
  47. function populateRevisions(uiSourceCode)
  48. {
  49. if (uiSourceCode.history.length)
  50. this._createUISourceCodeItem(uiSourceCode);
  51. }
  52. WebInspector.workspace.uiSourceCodes().forEach(populateRevisions.bind(this));
  53. WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, this._revisionAdded, this);
  54. WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
  55. WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.ProjectWillReset, this._projectWillReset, this);
  56. this._statusElement = document.createElement("span");
  57. this._statusElement.textContent = WebInspector.UIString("Local modifications");
  58. }
  59. /**
  60. * @param {WebInspector.UISourceCode} uiSourceCode
  61. */
  62. WebInspector.RevisionHistoryView.showHistory = function(uiSourceCode)
  63. {
  64. if (!WebInspector.RevisionHistoryView._view)
  65. WebInspector.RevisionHistoryView._view = new WebInspector.RevisionHistoryView();
  66. var view = WebInspector.RevisionHistoryView._view;
  67. WebInspector.showViewInDrawer(view._statusElement, view);
  68. view._revealUISourceCode(uiSourceCode);
  69. }
  70. WebInspector.RevisionHistoryView.prototype = {
  71. /**
  72. * @param {WebInspector.UISourceCode} uiSourceCode
  73. */
  74. _createUISourceCodeItem: function(uiSourceCode)
  75. {
  76. var uiSourceCodeItem = new TreeElement(uiSourceCode.displayName(), null, true);
  77. uiSourceCodeItem.selectable = false;
  78. // Insert in sorted order
  79. for (var i = 0; i < this._treeOutline.children.length; ++i) {
  80. if (this._treeOutline.children[i].title.localeCompare(uiSourceCode.displayName()) > 0) {
  81. this._treeOutline.insertChild(uiSourceCodeItem, i);
  82. break;
  83. }
  84. }
  85. if (i === this._treeOutline.children.length)
  86. this._treeOutline.appendChild(uiSourceCodeItem);
  87. this._uiSourceCodeItems.put(uiSourceCode, uiSourceCodeItem);
  88. var revisionCount = uiSourceCode.history.length;
  89. for (var i = revisionCount - 1; i >= 0; --i) {
  90. var revision = uiSourceCode.history[i];
  91. var historyItem = new WebInspector.RevisionHistoryTreeElement(revision, uiSourceCode.history[i - 1], i !== revisionCount - 1);
  92. uiSourceCodeItem.appendChild(historyItem);
  93. }
  94. var linkItem = new TreeElement("", null, false);
  95. linkItem.selectable = false;
  96. uiSourceCodeItem.appendChild(linkItem);
  97. var revertToOriginal = linkItem.listItemElement.createChild("span", "revision-history-link revision-history-link-row");
  98. revertToOriginal.textContent = WebInspector.UIString("apply original content");
  99. revertToOriginal.addEventListener("click", uiSourceCode.revertToOriginal.bind(uiSourceCode));
  100. var clearHistoryElement = uiSourceCodeItem.listItemElement.createChild("span", "revision-history-link");
  101. clearHistoryElement.textContent = WebInspector.UIString("revert");
  102. clearHistoryElement.addEventListener("click", this._clearHistory.bind(this, uiSourceCode));
  103. return uiSourceCodeItem;
  104. },
  105. /**
  106. * @param {WebInspector.UISourceCode} uiSourceCode
  107. */
  108. _clearHistory: function(uiSourceCode)
  109. {
  110. uiSourceCode.revertAndClearHistory(this._removeUISourceCode.bind(this));
  111. },
  112. _revisionAdded: function(event)
  113. {
  114. var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data.uiSourceCode);
  115. var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
  116. if (!uiSourceCodeItem) {
  117. uiSourceCodeItem = this._createUISourceCodeItem(uiSourceCode);
  118. return;
  119. }
  120. var historyLength = uiSourceCode.history.length;
  121. var historyItem = new WebInspector.RevisionHistoryTreeElement(uiSourceCode.history[historyLength - 1], uiSourceCode.history[historyLength - 2], false);
  122. if (uiSourceCodeItem.children.length)
  123. uiSourceCodeItem.children[0].allowRevert();
  124. uiSourceCodeItem.insertChild(historyItem, 0);
  125. },
  126. /**
  127. * @param {WebInspector.UISourceCode} uiSourceCode
  128. */
  129. _revealUISourceCode: function(uiSourceCode)
  130. {
  131. var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
  132. if (uiSourceCodeItem) {
  133. uiSourceCodeItem.reveal();
  134. uiSourceCodeItem.expand();
  135. }
  136. },
  137. _uiSourceCodeRemoved: function(event)
  138. {
  139. var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.data);
  140. this._removeUISourceCode(uiSourceCode);
  141. },
  142. /**
  143. * @param {WebInspector.UISourceCode} uiSourceCode
  144. */
  145. _removeUISourceCode: function(uiSourceCode)
  146. {
  147. var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
  148. if (!uiSourceCodeItem)
  149. return;
  150. this._treeOutline.removeChild(uiSourceCodeItem);
  151. this._uiSourceCodeItems.remove(uiSourceCode);
  152. },
  153. _projectWillReset: function(event)
  154. {
  155. var project = event.data;
  156. project.uiSourceCodes().forEach(this._removeUISourceCode.bind(this));
  157. },
  158. __proto__: WebInspector.View.prototype
  159. }
  160. /**
  161. * @constructor
  162. * @extends {TreeElement}
  163. * @param {WebInspector.Revision} revision
  164. * @param {WebInspector.Revision} baseRevision
  165. * @param {boolean} allowRevert
  166. */
  167. WebInspector.RevisionHistoryTreeElement = function(revision, baseRevision, allowRevert)
  168. {
  169. TreeElement.call(this, revision.timestamp.toLocaleTimeString(), null, true);
  170. this.selectable = false;
  171. this._revision = revision;
  172. this._baseRevision = baseRevision;
  173. this._revertElement = document.createElement("span");
  174. this._revertElement.className = "revision-history-link";
  175. this._revertElement.textContent = WebInspector.UIString("apply revision content");
  176. this._revertElement.addEventListener("click", this._revision.revertToThis.bind(this._revision), false);
  177. if (!allowRevert)
  178. this._revertElement.addStyleClass("hidden");
  179. }
  180. WebInspector.RevisionHistoryTreeElement.prototype = {
  181. onattach: function()
  182. {
  183. this.listItemElement.addStyleClass("revision-history-revision");
  184. },
  185. onexpand: function()
  186. {
  187. this.listItemElement.appendChild(this._revertElement);
  188. if (this._wasExpandedOnce)
  189. return;
  190. this._wasExpandedOnce = true;
  191. this.childrenListElement.addStyleClass("source-code");
  192. if (this._baseRevision)
  193. this._baseRevision.requestContent(step1.bind(this));
  194. else
  195. this._revision.uiSourceCode.requestOriginalContent(step1.bind(this));
  196. function step1(baseContent)
  197. {
  198. this._revision.requestContent(step2.bind(this, baseContent));
  199. }
  200. function step2(baseContent, newContent)
  201. {
  202. var baseLines = difflib.stringAsLines(baseContent);
  203. var newLines = difflib.stringAsLines(newContent);
  204. var sm = new difflib.SequenceMatcher(baseLines, newLines);
  205. var opcodes = sm.get_opcodes();
  206. var lastWasSeparator = false;
  207. for (var idx = 0; idx < opcodes.length; idx++) {
  208. var code = opcodes[idx];
  209. var change = code[0];
  210. var b = code[1];
  211. var be = code[2];
  212. var n = code[3];
  213. var ne = code[4];
  214. var rowCount = Math.max(be - b, ne - n);
  215. var topRows = [];
  216. var bottomRows = [];
  217. for (var i = 0; i < rowCount; i++) {
  218. if (change === "delete" || (change === "replace" && b < be)) {
  219. var lineNumber = b++;
  220. this._createLine(lineNumber, null, baseLines[lineNumber], "removed");
  221. lastWasSeparator = false;
  222. }
  223. if (change === "insert" || (change === "replace" && n < ne)) {
  224. var lineNumber = n++;
  225. this._createLine(null, lineNumber, newLines[lineNumber], "added");
  226. lastWasSeparator = false;
  227. }
  228. if (change === "equal") {
  229. b++;
  230. n++;
  231. if (!lastWasSeparator)
  232. this._createLine(null, null, " \u2026", "separator");
  233. lastWasSeparator = true;
  234. }
  235. }
  236. }
  237. }
  238. },
  239. oncollapse: function()
  240. {
  241. this._revertElement.remove();
  242. },
  243. /**
  244. * @param {?number} baseLineNumber
  245. * @param {?number} newLineNumber
  246. * @param {string} lineContent
  247. * @param {string} changeType
  248. */
  249. _createLine: function(baseLineNumber, newLineNumber, lineContent, changeType)
  250. {
  251. var child = new TreeElement("", null, false);
  252. child.selectable = false;
  253. this.appendChild(child);
  254. var lineElement = document.createElement("span");
  255. function appendLineNumber(lineNumber)
  256. {
  257. var numberString = lineNumber !== null ? numberToStringWithSpacesPadding(lineNumber + 1, 4) : " ";
  258. var lineNumberSpan = document.createElement("span");
  259. lineNumberSpan.addStyleClass("webkit-line-number");
  260. lineNumberSpan.textContent = numberString;
  261. child.listItemElement.appendChild(lineNumberSpan);
  262. }
  263. appendLineNumber(baseLineNumber);
  264. appendLineNumber(newLineNumber);
  265. var contentSpan = document.createElement("span");
  266. contentSpan.textContent = lineContent;
  267. child.listItemElement.appendChild(contentSpan);
  268. child.listItemElement.addStyleClass("revision-history-line");
  269. child.listItemElement.addStyleClass("revision-history-line-" + changeType);
  270. },
  271. allowRevert: function()
  272. {
  273. this._revertElement.removeStyleClass("hidden");
  274. },
  275. __proto__: TreeElement.prototype
  276. }