Skip to content

Commit 8f85d2a

Browse files
committed
Merge pull request #23 from dmnd/instant-display
Instant blame display, remove key throttling
2 parents 3971405 + 3507129 commit 8f85d2a

File tree

4 files changed

+114
-87
lines changed

4 files changed

+114
-87
lines changed
Lines changed: 21 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,38 @@
1-
const _ = require('underscore');
21
const $ = require('atom').$;
32
const React = require('atom').React;
43
const BlameListView = require('../views/blame-list-view');
5-
const errorController = require('../controllers/errorController');
64

7-
const TOGGLE_DEBOUNCE_TIME = 600;
85

96
/**
10-
* Getter for the currently focused editor.
7+
* Display or hide a BlameListView for the active editor.
118
*
12-
* @return {JQuery} - The currently focused editor element.
13-
*/
14-
function getFocusedEditorView () {
15-
var activePane = atom.workspaceView.getActivePaneView();
16-
return activePane.find('.editor.is-focused').view();
17-
}
18-
19-
/**
20-
* If active editor is not currently blaming funs blame command for given
21-
* file and blamer and inserts a BlameView for the file. If blame data is already
22-
* shown it removes that element.
9+
* If the active editor does not have an existing BlameListView, one will be
10+
* mounted.
2311
*
24-
* @param {String} filePath - path to the file to blame
25-
* @param {Blamer} projectBlamer - a fully initialized Blamer for the current project
12+
* @param {Blamer} projectBlamer - a Blamer for the current project
2613
*/
27-
function toggleBlame(filePath, projectBlamer) {
28-
var focusedEditor = getFocusedEditorView();
29-
if (focusedEditor.blaming) {
30-
// we're already blaming this container, so unmount
31-
focusedEditor.blaming = false;
32-
var mountPoint = focusedEditor.find('.git-blame-mount');
33-
React.unmountComponentAtNode(mountPoint[0]);
34-
mountPoint.remove();
14+
function toggleBlame(projectBlamer) {
15+
var editorView = atom.workspaceView.getActiveView();
16+
var editor = editorView.getEditor();
17+
18+
if (!editorView.blameView) {
19+
// insert the BlameListView after the gutter div
20+
var mountPoint = $('<div>', {'class': 'git-blame-mount'});
21+
editorView.find('.gutter').after(mountPoint);
22+
23+
editorView.blameView = React.renderComponent(new BlameListView({
24+
projectBlamer: projectBlamer,
25+
filePath: editor.getPath(),
26+
lineCount: editor.getLineCount(),
27+
scrollbar: editorView.find('.vertical-scrollbar')
28+
}), mountPoint[0]);
3529
} else {
36-
// blame the given file + show view on success
37-
projectBlamer.blame(filePath, function(err, blameData) {
38-
if (err) {
39-
errorController.handleError(err);
40-
} else {
41-
insertBlameView(blameData, focusedEditor);
42-
}
43-
});
30+
editorView.blameView.toggle();
4431
}
4532
}
4633

47-
/**
48-
* Debounced version of toggleBlame that will only allow toggleBlame function
49-
* to be executed 700ms after the last execution. Executes immediately the first
50-
* time its called.
51-
*/
52-
var debouncedToggleBlame = _.debounce(toggleBlame, TOGGLE_DEBOUNCE_TIME, true);
53-
54-
/**
55-
* Inserts a BlameView rendered from input blameData into its proper
56-
* spot within the focusedEditor.
57-
*
58-
* @param {Array|Object} blameData - array of data for a blame of each line output
59-
* of blameFormatter
60-
* @param {JQuery} focusedEditor - the currently focused editor element in which
61-
* the BlameView should be inserted
62-
*/
63-
function insertBlameView(blameData, focusedEditor) {
64-
// insert the BlameListView after the gutter div
65-
var mountPoint = $('<div>', {'class': 'git-blame-mount'});
66-
focusedEditor.find('.gutter').after(mountPoint);
67-
68-
React.renderComponent(new BlameListView({
69-
annotations: blameData,
70-
scrollbar: focusedEditor.find('.vertical-scrollbar')
71-
}), mountPoint[0]);
72-
73-
focusedEditor.blaming = true;
74-
}
7534

7635
// EXPORTS
7736
module.exports = {
78-
toggleBlame: debouncedToggleBlame
37+
toggleBlame: toggleBlame
7938
};

lib/git-blame.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ function activate() {
1010
initializeContext();
1111

1212
// git-blame:blame
13-
atom.workspaceView.command('git-blame:toggle', function() {
14-
return toggleBlame();
15-
});
16-
17-
return;
13+
atom.workspaceView.command('git-blame:toggle', toggleBlame);
1814
}
1915

2016
function initializeContext() {
@@ -35,11 +31,7 @@ function toggleBlame() {
3531
if (!projectBlamer) {
3632
return;
3733
}
38-
39-
var editor = atom.workspace.activePaneItem;
40-
var filePath = editor.getPath();
41-
42-
BlameViewController.toggleBlame(filePath, projectBlamer);
34+
BlameViewController.toggleBlame(projectBlamer);
4335
}
4436

4537
// EXPORTS

lib/views/blame-line-view.coffee

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
11
{$, React, Reactionary} = require 'atom'
2+
RP = React.PropTypes
23
{div, span, a} = Reactionary
34
RemoteRevision = require '../util/RemoteRevision'
45

56
HASH_LENGTH = 7 # github uses this length
7+
BLANK_HASH = '-'.repeat(HASH_LENGTH)
8+
9+
10+
renderLoading = ->
11+
div className: 'blame-line loading',
12+
span className: 'hash', BLANK_HASH
13+
span className: 'date', '1337-01-01'
14+
span className: 'committer', 'Loading'
15+
616

7-
module.exports =
817
BlameLineComponent = React.createClass
18+
propTypes:
19+
date: RP.string.isRequired
20+
hash: RP.string.isRequired
21+
url: RP.string.isRequired
22+
committer: RP.string.isRequired
23+
backgroundClass: RP.string
24+
noCommit: RP.bool
25+
926
render: ->
1027
if @props.noCommit
1128
div className: 'blame-line no-commit text-subtle',
12-
span className: 'hash', '-'.repeat(HASH_LENGTH)
29+
span className: 'hash', BLANK_HASH
1330
span className: 'date', @props.date
1431
span className: 'committer', 'Nobody'
1532
else
@@ -33,3 +50,5 @@ BlameLineComponent = React.createClass
3350

3451
shouldComponentUpdate: ->
3552
false
53+
54+
module.exports = {BlameLineComponent, renderLoading}

lib/views/blame-list-view.coffee

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
{React, Reactionary, $} = require 'atom'
22
{div, span, a} = Reactionary
3-
_ = require('underscore')
4-
BlameLineComponent = require './blame-line-view'
3+
RP = React.PropTypes
4+
_ = require 'underscore'
5+
{BlameLineComponent, renderLoading} = require './blame-line-view'
56

67

78
BlameListLinesComponent = React.createClass
9+
propTypes:
10+
annotations: RP.arrayOf(RP.object)
11+
loading: RP.bool.isRequired
12+
filePath: RP.string.isRequired
13+
lineCount: RP.number.isRequired
14+
15+
renderLoading: ->
16+
lines = [0...@props.lineCount].map renderLoading
17+
div null, lines
18+
819
# makes background color alternate by commit
920
_addAlternatingBackgroundColor: (lines) ->
1021
bgClass = null
@@ -22,42 +33,66 @@ BlameListLinesComponent = React.createClass
2233
lastHash = line.hash
2334
lines
2435

25-
render: ->
36+
renderLoaded: ->
2637
# clone so it can be modified
2738
lines = _.clone @props.annotations
2839

2940
# add url to open diff
3041
filePath = atom.workspace.activePaneItem.getPath()
3142
remoteUrl = atom.project.getRepo()?.getOriginUrl(filePath)
3243
l['url'] = remoteUrl for l in lines
33-
3444
@_addAlternatingBackgroundColor lines
35-
3645
div null, lines.map BlameLineComponent
3746

38-
shouldComponentUpdate: ->
39-
false
47+
render: ->
48+
if @props.loading
49+
@renderLoading()
50+
else
51+
@renderLoaded()
52+
53+
shouldComponentUpdate: ({loading}) ->
54+
loading isnt @props.loading
4055

4156

4257
BlameListView = React.createClass
58+
propTypes:
59+
projectBlamer: RP.object.isRequired
60+
filePath: RP.string.isRequired
61+
lineCount: RP.number.isRequired
62+
scrollbar: RP.object.isRequired
63+
4364
getInitialState: ->
4465
{
4566
# TODO: get this from the parent component somehow?
4667
scrollTop: @props.scrollbar.scrollTop()
4768
# TODO: be intelligent about persisting this so it doesn't reset
4869
width: 210
70+
loading: true
71+
visible: true
4972
}
5073

5174
render: ->
52-
div
53-
className: 'git-blame'
54-
style: width: @state.width,
55-
div className: 'git-blame-resize-handle', onMouseDown: @resizeStarted
56-
div className: 'git-blame-scroller',
75+
display = if @state.visible then 'inline-block' else 'none'
76+
77+
body = if @state.error
78+
div "Sorry, an error occurred." # TODO: make this better
79+
else
80+
div
81+
className: 'git-blame-scroller'
5782
div
5883
className: 'blame-lines'
5984
style: WebkitTransform: @getTransform()
60-
BlameListLinesComponent annotations: @props.annotations
85+
BlameListLinesComponent
86+
annotations: @state.annotations
87+
loading: @state.loading
88+
filePath: @props.filePath
89+
lineCount: @props.lineCount
90+
91+
div
92+
className: 'git-blame'
93+
style: width: @state.width, display: display
94+
div className: 'git-blame-resize-handle', onMouseDown: @resizeStarted
95+
body
6196

6297
getTransform: ->
6398
{scrollTop} = @state
@@ -69,6 +104,28 @@ BlameListView = React.createClass
69104
else
70105
"translate(0px, #{-scrollTop}px)"
71106

107+
componentWillMount: ->
108+
# kick off async request for blame data
109+
@loadBlame true
110+
111+
loadBlame: (force) ->
112+
return if @state.loading and not force
113+
114+
@setState loading: true
115+
@props.projectBlamer.blame @props.filePath, (err, data) =>
116+
if err
117+
@setState
118+
loading: false
119+
error: true
120+
else
121+
@setState
122+
loading: false
123+
error: false
124+
annotations: data
125+
126+
toggle: ->
127+
@setState visible: !@state.visible
128+
72129
componentDidMount: ->
73130
# Bind to scroll event on vertical-scrollbar to sync up scroll position of
74131
# blame gutter.

0 commit comments

Comments
 (0)