Skip to content

Line Wrap + Code Fold support #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/controllers/blameViewController.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ function toggleBlame(projectBlamer) {
}
}


// EXPORTS
module.exports = {
toggleBlame: toggleBlame
Expand Down
62 changes: 62 additions & 0 deletions lib/views/blame-list-lines-component.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{React, Reactionary, $} = require 'atom'
{div} = Reactionary
RP = React.PropTypes
_ = require 'underscore'
{BlameLineComponent, renderLoading} = require './blame-line-view'

# React Component representing a list of git-blame lines. Contained
# within the BlameListView when necessary.
#
BlameListLinesComponent = React.createClass
propTypes:
annotations: RP.arrayOf(RP.object)
loading: RP.bool.isRequired
dirty: RP.bool.isRequired
initialLineCount: RP.number.isRequired
remoteRevision: RP.object.isRequired

# Renders the loading gutter. Should be shown while blame command
# line async process is happening, i.e. when @props.loading is true
renderLoading: ->
lines = [0...@props.initialLineCount].map renderLoading
div null, lines

# Helper that makes background color of BlameLineComponent
# alternate by commit
_addAlternatingBackgroundColor: (lines) ->
bgClass = null
lastHash = null
for line in lines
bgClass = if line.noCommit
''
else if line.hash is lastHash
bgClass
else if bgClass is 'line-bg-lighter'
'line-bg-darker'
else
'line-bg-lighter'
line['backgroundClass'] = bgClass
lastHash = line.hash
lines

# Renders list of BlameLineComponents to show the user useful git-blame data
renderLoaded: ->
# clone so it can be modified
lines = _.clone @props.annotations

# add url to open diff
l.remoteRevision = @props.remoteRevision for l in lines
@_addAlternatingBackgroundColor lines
div null, lines.map BlameLineComponent

render: ->
if @props.loading
@renderLoading()
else
@renderLoaded()

# =============
# Exports
# =============

module.exports = BlameListLinesComponent
132 changes: 69 additions & 63 deletions lib/views/blame-list-view.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,20 @@
{div} = Reactionary
RP = React.PropTypes
_ = require 'underscore'
{BlameLineComponent, renderLoading} = require './blame-line-view'


BlameListLinesComponent = React.createClass
propTypes:
annotations: RP.arrayOf(RP.object)
loading: RP.bool.isRequired
dirty: RP.bool.isRequired
initialLineCount: RP.number.isRequired
remoteRevision: RP.object.isRequired

renderLoading: ->
lines = [0...@props.initialLineCount].map renderLoading
div null, lines

# makes background color alternate by commit
_addAlternatingBackgroundColor: (lines) ->
bgClass = null
lastHash = null
for line in lines
bgClass = if line.noCommit
''
else if line.hash is lastHash
bgClass
else if bgClass is 'line-bg-lighter'
'line-bg-darker'
else
'line-bg-lighter'
line['backgroundClass'] = bgClass
lastHash = line.hash
lines

renderLoaded: ->
# clone so it can be modified
lines = _.clone @props.annotations

# add url to open diff
l.remoteRevision = @props.remoteRevision for l in lines
@_addAlternatingBackgroundColor lines
div null, lines.map BlameLineComponent

render: ->
if @props.loading
@renderLoading()
else
@renderLoaded()

shouldComponentUpdate: ({loading, dirty}) ->
finishedInitialLoad = @props.loading and not loading and not @props.dirty
finishedEdit = @props.dirty and not dirty
finishedInitialLoad or finishedEdit
BlameListLinesComponent = require './blame-list-lines-component'

# Main container component for the git-blame gutter. Handles bound
# Editor events to keep the blame gutter in sync.
#
BlameListView = React.createClass
propTypes:
projectBlamer: RP.object.isRequired
remoteRevision: RP.object.isRequired
editorView: RP.object.isRequired

# an array of event binding Disposable objects used for unbinding
eventBindingDisposables: []

getInitialState: ->
{
# TODO: get this from the parent component somehow?
Expand All @@ -79,6 +35,7 @@ BlameListView = React.createClass

render: ->
display = if @state.visible then 'inline-block' else 'none'
preparedAnnotations = @prepareAnnotationsForCurrentEditorState @state.annotations

body = if @state.error
div "Sorry, an error occurred." # TODO: make this better
Expand All @@ -89,10 +46,10 @@ BlameListView = React.createClass
className: 'blame-lines'
style: WebkitTransform: @getTransform()
BlameListLinesComponent
annotations: @state.annotations
annotations: preparedAnnotations
loading: @state.loading
dirty: @state.dirty
initialLineCount: @editor().getLineCount()
initialLineCount: preparedAnnotations.length
remoteRevision: @props.remoteRevision
div
className: 'git-blame'
Expand All @@ -110,12 +67,6 @@ BlameListView = React.createClass
else
"translate(0px, #{-scrollTop}px)"

componentWillMount: ->
# kick off async request for blame data
@loadBlame()
@editor().on 'contents-modified', @contentsModified
@editor().buffer.on 'saved', @saved

loadBlame: ->
@setState loading: true
@props.projectBlamer.blame @editor().getPath(), (err, data) =>
Expand All @@ -131,14 +82,46 @@ BlameListView = React.createClass
dirty: false
annotations: data

# Modifies the blame data for the current editor state, taking
# folds into account.
# TODO: Handle soft wraps here as well
prepareAnnotationsForCurrentEditorState: (annotations) ->
return [] unless annotations

filteredLineData = []
highestScreenRowSeen = 0
e = @editor()

# loop through the blame data and filter out the blame line rows
# for lines that are not visible on the screen due to folded code
# TODO: Handle soft wraps here.
for lineData, index in annotations
screenRow = e.screenPositionForBufferPosition([index, 0]).row
if screenRow == index or screenRow > highestScreenRowSeen
filteredLineData.push lineData
highestScreenRowSeen = screenRow

return filteredLineData

# bound callback for Editor 'contents-modified' event
contentsModified: ->
return unless @isMounted()
@setState dirty: true unless @state.dirty

# bound callback for Editor.buffer 'saved' event
saved: ->
return unless @isMounted()
@loadBlame() if @state.visible and @state.dirty

# bound callback for Editor 'screen-lines-changed' event. This happens quite
# often while editing, so we calla debounced method to force a re-render with
# current data. This is the only way to know when code is folded / unfolded,
# but its also called whenever new lines are added / while editing.
onScreenLinesChanged: (e) ->
return unless @isMounted()
@forceUpdate()
# @matchScrollPosition()

toggle: ->
if @state.visible
@setState visible: false
Expand All @@ -151,16 +134,35 @@ BlameListView = React.createClass
# blame gutter.
@scrollbar().on 'scroll', @matchScrollPosition

componentWillMount: ->
# kick off async request for blame data
@loadBlame()

# bind to published events
@eventBindingDisposables.push @editor().onDidStopChanging(@contentsModified)
@eventBindingDisposables.push @editor().buffer.onDidSave(@saved)

# bind to internal events
@editor().on 'screen-lines-changed', @onScreenLinesChanged

componentWillUnmount: ->
# unbind published events
for disposable in @eventBindingDisposables
disposable.dispose()

# unbind internal events
@editor().off 'screen-lines-changed', @onScreenLinesChanged
@scrollbar().off 'scroll', @matchScrollPosition
@editor().off 'contents-modified', @contentsModified
@editor().buffer.off 'saved', @saved

# Makes the view arguments scroll position match the target elements scroll
# position
# Matches scroll position of the BlameListView with the scroll bar. Bit
# of a hack since blame scrolls separately from the buffer right now
matchScrollPosition: ->
@setState scrollTop: @scrollbar().scrollTop()

# ==========
# Resize
# ==========

resizeStarted: ({pageX}) ->
@setState dragging: true, initialPageX: pageX, initialWidth: @state.width
$(document).on 'mousemove', @onResizeMouseMove
Expand All @@ -181,4 +183,8 @@ BlameListView = React.createClass
e.stopPropagation()
e.preventDefault()

# ==========
# Exports
# ==========

module.exports = BlameListView