DiffView.coffee 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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. @bindToScrollEvents()
  23. @scrollToFirstChange()
  24. return @
  25. destroy: () ->
  26. @$editor?.remove()
  27. createAceEditor: () ->
  28. @$el.empty()
  29. @$editor = $("<div/>")
  30. @$el.append(@$editor)
  31. @aceEditor = Ace.edit(@$editor[0])
  32. @aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
  33. @aceEditor.setReadOnly true
  34. @aceEditor.setShowPrintMargin(false)
  35. session = @aceEditor.getSession()
  36. session.setMode(new LatexMode.Mode())
  37. session.setUseWrapMode(true)
  38. @aceEditor.on "mousemove", (e) =>
  39. position = @aceEditor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
  40. e.position = position
  41. @updateVisibleNames(e)
  42. bindToScrollEvents: () ->
  43. @aceEditor.getSession().on "changeScrollTop", (e) =>
  44. @updateMoreChangeLabels()
  45. getPlainDiffContent: () ->
  46. content = ""
  47. for entry in @model.get("diff") or []
  48. content += entry.u or entry.i or entry.d or ""
  49. return content
  50. insertMarkers: () ->
  51. row = 0
  52. column = 0
  53. @entries = []
  54. for entry, i in @model.get("diff") or []
  55. content = entry.u or entry.i or entry.d
  56. content ||= ""
  57. lines = content.split("\n")
  58. startRow = row
  59. startColumn = column
  60. if lines.length > 1
  61. endRow = startRow + lines.length - 1
  62. endColumn = lines[lines.length - 1].length
  63. else
  64. endRow = startRow
  65. endColumn = startColumn + lines[0].length
  66. row = endRow
  67. column = endColumn
  68. range = new Range.Range(
  69. startRow, startColumn, endRow, endColumn
  70. )
  71. entry.range = range
  72. @addMarker(range, entry)
  73. if entry.i? or entry.d?
  74. @entries.push entry
  75. addMarker: (range, entry) ->
  76. session = @aceEditor.getSession()
  77. markerBackLayer = @aceEditor.renderer.$markerBack
  78. markerFrontLayer = @aceEditor.renderer.$markerFront
  79. lineHeight = @aceEditor.renderer.lineHeight
  80. dark = @_isDarkTheme()
  81. if entry.i? or entry.d?
  82. hue = entry.meta.user.hue()
  83. if entry.i?
  84. if dark
  85. style = "background-color : hsl(#{hue}, 100%, 28%);"
  86. else
  87. style = "background-color : hsl(#{hue}, 70%, 85%);"
  88. @_addMarkerWithCustomStyle session, markerBackLayer, range, "inserted-change-background", false, style
  89. if entry.d?
  90. if dark
  91. bgStyle = "background-color: hsl(#{hue}, 100%, 20%);"
  92. fgStyle = "border-bottom: 2px solid hsl(#{hue}, 100%, 60%);"
  93. else
  94. bgStyle = "background-color: hsl(#{hue}, 70%, 95%);"
  95. fgStyle = "border-bottom: 2px solid hsl(#{hue}, 70%, 40%);"
  96. fgStyle += "; height: #{Math.round(lineHeight/2) - 1}px;"
  97. @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-background", false, bgStyle
  98. @_addMarkerWithCustomStyle session, markerBackLayer, range, "deleted-change-foreground", true, fgStyle
  99. _addMarkerWithCustomStyle: (session, markerLayer, range, klass, foreground, style) ->
  100. session.addMarker range, klass, (html, range, left, top, config) ->
  101. if range.isMultiLine()
  102. markerLayer.drawTextMarker(html, range, klass, config, style)
  103. else
  104. markerLayer.drawSingleLineMarker(html, range, "#{klass} ace_start", config, 0, style)
  105. , foreground
  106. _isDarkTheme: () ->
  107. rgb = $(".ace_editor").css("background-color");
  108. [m, r, g, b] = rgb.match(/rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)/)
  109. r = parseInt(r, 10)
  110. g = parseInt(g, 10)
  111. b = parseInt(b, 10)
  112. return r + g + b < 3 * 128
  113. insertNameTag: () ->
  114. @$nameTagEl = $("<div class='change-name-marker'></div>")
  115. @$nameTagEl.css({
  116. position: "absolute"
  117. })
  118. @$nameTagEl.hide()
  119. @$ace.append(@$nameTagEl)
  120. insertMoreChangeLabels: () ->
  121. @$changesBefore = $("<a class='changes-before' href='#'><span></span> <i class='icon-arrow-up'></a>")
  122. @$changesAfter = $("<a class='changes-after' href='#'><span></span> <i class='icon-arrow-down'></a>")
  123. @$ace.append(@$changesBefore)
  124. @$ace.append(@$changesAfter)
  125. @$changesBefore.on "click", () =>
  126. @gotoLastHiddenChangeBefore()
  127. @$changesAfter.on "click", () =>
  128. @gotoFirstHiddenChangeAfter()
  129. @updateMoreChangeLabels()
  130. scrollToFirstChange: () ->
  131. @aceEditor.scrollToLine(0)
  132. setTimeout () =>
  133. if @entries? and @entries[0]?
  134. row = @entries[0].range.start.row
  135. @aceEditor.scrollToLine(row, true, false)
  136. , 10
  137. _drawNameTag: (entry, position) ->
  138. @$nameTagEl.show()
  139. if entry.i?
  140. text = "Added by #{entry.meta.user.name()}"
  141. else if entry.d?
  142. text = "Deleted by #{entry.meta.user.name()}"
  143. date = moment(parseInt(entry.meta.end_ts, 10)).format("Do MMM YYYY, h:mm a")
  144. text += " on #{date}"
  145. @$nameTagEl.text(text)
  146. position = @aceEditor.renderer.textToScreenCoordinates(position.row, position.column)
  147. offset = @$ace.offset()
  148. position.pageX = position.pageX - offset.left
  149. position.pageY = position.pageY - offset.top
  150. height = @$ace.height()
  151. hue = entry.meta.user.hue()
  152. if @_isDarkTheme()
  153. css = { "background-color" : "hsl(#{hue}, 100%, 20%)"; }
  154. else
  155. css = { "background-color" : "hsl(#{hue}, 70%, 90%)"; }
  156. if position.pageX + @$nameTagEl.width() < @$ace.width()
  157. css["left"] = position.pageX
  158. css["right"] = "auto"
  159. else
  160. css["right"] = 0
  161. css["left"] = "auto"
  162. if position.pageY > 2 * @$nameTagEl.height()
  163. css["bottom"] = height - position.pageY
  164. css["top"] = "auto"
  165. else
  166. css["top"] = position.pageY + @aceEditor.renderer.lineHeight
  167. css["bottom"] = "auto"
  168. @$nameTagEl.css css
  169. _hideNameTag: () ->
  170. @$nameTagEl?.hide()
  171. updateVisibleNames: (e) ->
  172. visibleName = false
  173. for entry in @entries or []
  174. if entry.range.contains(e.position.row, e.position.column)
  175. @_drawNameTag(entry, e.position)
  176. visibleName = true
  177. break
  178. if !visibleName
  179. @_hideNameTag()
  180. updateMoreChangeLabels: () ->
  181. return if !@$changesBefore or !@$changesAfter
  182. setTimeout () =>
  183. firstRow = @aceEditor.getFirstVisibleRow()
  184. lastRow = @aceEditor.getLastVisibleRow()
  185. changesBefore = 0
  186. changesAfter = 0
  187. @lastHiddenChangeBefore = null
  188. @firstHiddenChangeAfter = null
  189. for entry in @entries or []
  190. if entry.range.start.row < firstRow
  191. changesBefore += 1
  192. @lastHiddenChangeBefore = entry
  193. if entry.range.end.row > lastRow
  194. changesAfter += 1
  195. @firstHiddenChangeAfter ||= entry
  196. if changesBefore > 0
  197. @$changesBefore.find("span").text("#{changesBefore} more change#{if changesBefore > 1 then "s" else ""} above")
  198. @$changesBefore.show()
  199. else
  200. @$changesBefore.hide()
  201. if changesAfter > 0
  202. @$changesAfter.find("span").text("#{changesAfter} more change#{if changesAfter > 1 then "s" else ""} below")
  203. @$changesAfter.show()
  204. else
  205. @$changesAfter.hide()
  206. , 100
  207. gotoLastHiddenChangeBefore: () ->
  208. return if !@lastHiddenChangeBefore
  209. @aceEditor.scrollToLine(@lastHiddenChangeBefore.range.start.row, true, false)
  210. gotoFirstHiddenChangeAfter: () ->
  211. return if !@firstHiddenChangeAfter
  212. @aceEditor.scrollToLine(@firstHiddenChangeAfter.range.end.row, true, false)
  213. resize: () ->
  214. @aceEditor.resize()
  215. return DiffView