DiffView.coffee 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. define [
  2. "ace/ace"
  3. "ace/mode/latex"
  4. "ace/range"
  5. "moment"
  6. "libs/backbone"
  7. ], (Ace, LatexMode, Range, moment)->
  8. DiffView = Backbone.View.extend
  9. initialize: () ->
  10. @model.on "change:diff", () => @render()
  11. @render()
  12. render: ->
  13. diff = @model.get("diff")
  14. return unless diff?
  15. @createAceEditor()
  16. @aceEditor.setValue(@getPlainDiffContent())
  17. @aceEditor.clearSelection()
  18. @$ace = $(@aceEditor.renderer.container).find(".ace_scroller")
  19. @insertMarkers()
  20. @insertNameTag()
  21. @insertMoreChangeLabels()
  22. @scrollToFirstChange()
  23. return @
  24. destroy: () ->
  25. @$editor?.remove()
  26. createAceEditor: () ->
  27. @$el.empty()
  28. @$editor = $("<div/>")
  29. @$el.append(@$editor)
  30. @aceEditor = Ace.edit(@$editor[0])
  31. @aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
  32. @aceEditor.setReadOnly true
  33. @aceEditor.setShowPrintMargin(false)
  34. session = @aceEditor.getSession()
  35. session.setMode(new LatexMode.Mode())
  36. session.setUseWrapMode(true)
  37. @aceEditor.on "mousemove", (e) =>
  38. position = @aceEditor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
  39. e.position = position
  40. @updateVisibleNames(e)
  41. session.on "changeScrollTop", (e) =>
  42. @updateMoreChangeLabels()
  43. getPlainDiffContent: () ->
  44. content = ""
  45. for entry in @model.get("diff") or []
  46. content += entry.u or entry.i or entry.d or ""
  47. return content
  48. insertMarkers: () ->
  49. row = 0
  50. column = 0
  51. @entries = []
  52. for entry, i in @model.get("diff") or []
  53. content = entry.u or entry.i or entry.d
  54. content ||= ""
  55. lines = content.split("\n")
  56. startRow = row
  57. startColumn = column
  58. if lines.length > 1
  59. endRow = startRow + lines.length - 1
  60. endColumn = lines[lines.length - 1].length
  61. else
  62. endRow = startRow
  63. endColumn = startColumn + lines[0].length
  64. row = endRow
  65. column = endColumn
  66. range = new Range.Range(
  67. startRow, startColumn, endRow, endColumn
  68. )
  69. entry.range = range
  70. @addMarker(range, entry)
  71. if entry.i? or entry.d?
  72. @entries.push entry
  73. addMarker: (range, entry) ->
  74. session = @aceEditor.getSession()
  75. markerBackLayer = @aceEditor.renderer.$markerBack
  76. markerFrontLayer = @aceEditor.renderer.$markerFront
  77. lineHeight = @aceEditor.renderer.lineHeight
  78. if entry.i? or entry.d?
  79. hue = entry.meta.user.hue()
  80. if entry.i?
  81. @_addMarkerWithCustomStyle session, markerBackLayer, range, "inserted-change-background", false, """
  82. background-color : hsl(#{hue}, 70%, 85%);
  83. """
  84. if entry.d?
  85. @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-background", false, """
  86. background-color : hsl(#{hue}, 70%, 95%);
  87. """
  88. @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-foreground", true, """
  89. height: #{Math.round(lineHeight/2) - 1}px;
  90. border-bottom: 2px solid hsl(#{hue}, 70%, 40%);
  91. """
  92. _addMarkerWithCustomStyle: (session, markerLayer, range, klass, foreground, style) ->
  93. session.addMarker range, klass, (html, range, left, top, config) ->
  94. if range.isMultiLine()
  95. markerLayer.drawTextMarker(html, range, klass, config, style)
  96. else
  97. markerLayer.drawSingleLineMarker(html, range, "#{klass} ace_start", config, 0, style)
  98. , foreground
  99. insertNameTag: () ->
  100. @$nameTagEl = $("<div class='change-name-marker'></div>")
  101. @$nameTagEl.css({
  102. position: "absolute"
  103. })
  104. @$nameTagEl.hide()
  105. @$ace.append(@$nameTagEl)
  106. insertMoreChangeLabels: () ->
  107. @$changesBefore = $("<div class='changes-before'><span></span> <i class='icon-arrow-up'></div>")
  108. @$changesAfter = $("<div class='changes-after'><span></span> <i class='icon-arrow-down'></div>")
  109. @$ace.append(@$changesBefore)
  110. @$ace.append(@$changesAfter)
  111. @updateMoreChangeLabels()
  112. scrollToFirstChange: () ->
  113. if @entries? and @entries[0]?
  114. row = @entries[0].range.start.row
  115. @aceEditor.gotoLine(0)
  116. @aceEditor.gotoLine(row, 0, true)
  117. _drawNameTag: (entry, position) ->
  118. @$nameTagEl.show()
  119. if entry.i?
  120. text = "Added by #{entry.meta.user.name()}"
  121. else if entry.d?
  122. text = "Deleted by #{entry.meta.user.name()}"
  123. date = moment(parseInt(entry.meta.end_ts, 10)).format("Do MMM YYYY, h:mm a")
  124. text += " on #{date}"
  125. @$nameTagEl.text(text)
  126. position = @aceEditor.renderer.textToScreenCoordinates(position.row, position.column)
  127. offset = @$ace.offset()
  128. position.pageX = position.pageX - offset.left
  129. position.pageY = position.pageY - offset.top
  130. height = @$ace.height()
  131. hue = entry.meta.user.hue()
  132. css = {
  133. "background-color" : "hsl(#{hue}, 70%, 90%)";
  134. }
  135. if position.pageX + @$nameTagEl.width() < @$ace.width()
  136. css["left"] = position.pageX
  137. css["right"] = "auto"
  138. else
  139. css["right"] = 0
  140. css["left"] = "auto"
  141. if position.pageY > 2 * @$nameTagEl.height()
  142. css["bottom"] = height - position.pageY
  143. css["top"] = "auto"
  144. else
  145. css["top"] = position.pageY + @aceEditor.renderer.lineHeight
  146. css["bottom"] = "auto"
  147. @$nameTagEl.css css
  148. _hideNameTag: () ->
  149. @$nameTagEl.hide()
  150. updateVisibleNames: (e) ->
  151. visibleName = false
  152. for entry in @entries or []
  153. if entry.range.contains(e.position.row, e.position.column)
  154. @_drawNameTag(entry, e.position)
  155. visibleName = true
  156. break
  157. if !visibleName
  158. @_hideNameTag()
  159. updateMoreChangeLabels: () ->
  160. return if !@$changesBefore or !@$changesAfter
  161. firstRow = @aceEditor.renderer.getFirstFullyVisibleRow()
  162. lastRow = @aceEditor.renderer.getLastFullyVisibleRow()
  163. changesBefore = 0
  164. changesAfter = 0
  165. for entry in @entries or []
  166. if entry.range.start.row < firstRow
  167. changesBefore += 1
  168. if entry.range.end.row > lastRow
  169. changesAfter += 1
  170. if changesBefore > 0
  171. @$changesBefore.find("span").text("#{changesBefore} more change#{if changesBefore > 1 then "s" else ""} above")
  172. @$changesBefore.show()
  173. else
  174. @$changesBefore.hide()
  175. if changesAfter > 0
  176. @$changesAfter.find("span").text("#{changesAfter} more change#{if changesAfter > 1 then "s" else ""} below")
  177. @$changesAfter.show()
  178. else
  179. @$changesAfter.hide()
  180. resize: () ->
  181. @aceEditor.resize()
  182. return DiffView