diff --git a/package.json b/package.json index d96e10b4e..6510f3e0c 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,15 @@ "ctags": "0.3.0", "oniguruma": "0.8.0", "mkdirp": "0.3.5", - "git-utils": "0.12.0", + "git-utils": "0.13.0", "underscore": "1.4.4", "d3": "3.0.8", "coffee-cache": "0.1.0", "pegjs": "0.7.0", "async": "0.2.6", "nak": "0.2.12", - "spellchecker": "0.2.0", - "pathwatcher": "0.1.5", + "spellchecker": "0.3.0", + "pathwatcher": "0.2.0", "plist": "git://github.com/nathansobo/node-plist.git", "space-pen": "git://github.com/nathansobo/space-pen.git" }, diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index c7d21989e..868226f5f 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -2552,3 +2552,14 @@ describe "Editor", -> runs -> expect(editor.renderedLines.find('.line').text()).toBe 'hidden changes' + + describe "editor:scroll-to-cursor", -> + it "scrolls to and centers the editor on the cursor's position", -> + editor.attachToDom(heightInLines: 3) + editor.setCursorBufferPosition([1, 2]) + editor.scrollToBottom() + expect(editor.getFirstVisibleScreenRow()).not.toBe 0 + expect(editor.getLastVisibleScreenRow()).not.toBe 2 + editor.trigger('editor:scroll-to-cursor') + expect(editor.getFirstVisibleScreenRow()).toBe 0 + expect(editor.getLastVisibleScreenRow()).toBe 2 diff --git a/src/app/editor.coffee b/src/app/editor.coffee index dff8f920e..7b4c9fb55 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -177,6 +177,7 @@ class Editor extends View 'editor:toggle-indent-guide': => config.set('editor.showIndentGuide', !config.get('editor.showIndentGuide')) 'editor:save-debug-snapshot': @saveDebugSnapshot 'editor:toggle-line-numbers': => config.set('editor.showLineNumbers', !config.get('editor.showLineNumbers')) + 'editor:scroll-to-cursor': @scrollToCursorPosition documentation = {} for name, method of editorBindings @@ -768,6 +769,9 @@ class Editor extends View scrollToBottom: -> @scrollBottom(@screenLineCount() * @lineHeight) + scrollToCursorPosition: -> + @scrollToBufferPosition(@getCursorBufferPosition(), center: true) + # Public: Scrolls the editor to the given buffer position. # # bufferPosition - An object that represents a buffer position. It can be either diff --git a/src/app/git.coffee b/src/app/git.coffee index 175746237..02cafde74 100644 --- a/src/app/git.coffee +++ b/src/app/git.coffee @@ -60,7 +60,7 @@ class Git # Public: Reread the index to update any values that have changed since the last time the index was read. refreshIndex: -> @getRepo().refreshIndex() - + # Public: Retrieves the path of the repository. # # Returns a {String}. @@ -219,7 +219,7 @@ class Git # path - The {String} path to check # # Returns a {Number} representing the status. - getDirectoryStatus: (directoryPath) -> + getDirectoryStatus: (directoryPath) -> directoryPath = "#{directoryPath}/" directoryStatus = 0 for path, status of @statuses @@ -234,5 +234,8 @@ class Git getAheadBehindCounts: -> @getRepo().getAheadBehindCount() + getLineDiffs: (path, text) -> + @getRepo().getLineDiffs(@relativize(path), text) + _.extend Git.prototype, Subscriber _.extend Git.prototype, EventEmitter diff --git a/src/app/gutter.coffee b/src/app/gutter.coffee index da97e6e89..76bf22cad 100644 --- a/src/app/gutter.coffee +++ b/src/app/gutter.coffee @@ -76,7 +76,7 @@ class Gutter extends View rowValue = (row + 1).toString() classes = ['line-number'] classes.push('fold') if editor.isFoldedAtBufferRow(row) - @div class: classes.join(' '), => + @div lineNumber: row, class: classes.join(' '), => rowValuePadding = _.multiplyString(' ', maxDigits - rowValue.length) @raw("#{rowValuePadding}#{rowValue}") diff --git a/src/app/keymaps/editor.cson b/src/app/keymaps/editor.cson index 07568fc0b..f1af1b842 100644 --- a/src/app/keymaps/editor.cson +++ b/src/app/keymaps/editor.cson @@ -27,6 +27,7 @@ 'ctrl-meta-down': 'editor:move-line-down' 'meta-D': 'editor:duplicate-line' 'ctrl-J': 'editor:join-line' + 'meta-<': 'editor:scroll-to-cursor' '.editor.mini': 'enter': 'core:confirm', diff --git a/src/packages/git-diff/lib/git-diff-view.coffee b/src/packages/git-diff/lib/git-diff-view.coffee new file mode 100644 index 000000000..bff48f146 --- /dev/null +++ b/src/packages/git-diff/lib/git-diff-view.coffee @@ -0,0 +1,66 @@ +_ = require 'underscore' + +module.exports = +class GitDiffView + diffs: null + editor: null + + constructor: (@editor) -> + @gutter = @editor.gutter + @diffs = {} + + @editor.on 'editor:path-changed', => @subscribeToBuffer() + @editor.on 'editor:display-updated', => @renderDiffs() + git.on 'statuses-changed', => + @diffs = {} + @scheduleDiffs() + git.on 'status-changed', (path) => + delete @diffs[path] + @scheduleDiffs() if path is @editor.getPath() + + @subscribeToBuffer() + + subscribeToBuffer: -> + if @buffer? + @removeDiffs() + delete @diffs[@buffer.getPath()] if @buffer.destroyed + @buffer.off '.git-diff' + @buffer = null + + if @buffer = @editor.getBuffer() + @scheduleDiffs() unless @diffs[@buffer.getPath()]? + @buffer.on 'contents-modified.git-diff', => + @generateDiffs() + @renderDiffs() + + scheduleDiffs: -> + _.nextTick => + @generateDiffs() + @renderDiffs() + + generateDiffs: -> + if path = @buffer.getPath() + @diffs[path] = git?.getLineDiffs(path, @buffer.getText()) + + removeDiffs: -> + if @gutter.hasGitLineDiffs + @gutter.find('.line-number').removeClass('git-line-added git-line-modified git-line-removed') + @gutter.hasGitLineDiffs = false + + renderDiffs: -> + return unless @gutter.isVisible() + + @removeDiffs() + + hunks = @diffs[@editor.getPath()] ? [] + linesHighlighted = 0 + for hunk in hunks + if hunk.oldLines is 0 and hunk.newLines > 0 + for row in [hunk.newStart...hunk.newStart + hunk.newLines] + linesHighlighted += @gutter.find(".line-number[lineNumber=#{row - 1}]").addClass('git-line-added').length + else if hunk.newLines is 0 and hunk.oldLines > 0 + linesHighlighted += @gutter.find(".line-number[lineNumber=#{hunk.newStart - 1}]").addClass('git-line-removed').length + else + for row in [hunk.newStart...hunk.newStart + hunk.newLines] + linesHighlighted += @gutter.find(".line-number[lineNumber=#{row - 1}]").addClass('git-line-modified').length + @gutter.hasGitLineDiffs = linesHighlighted > 0 diff --git a/src/packages/git-diff/lib/git-diff.coffee b/src/packages/git-diff/lib/git-diff.coffee new file mode 100644 index 000000000..127eb4b81 --- /dev/null +++ b/src/packages/git-diff/lib/git-diff.coffee @@ -0,0 +1,12 @@ +GitDiffView = require './git-diff-view' + +module.exports = + configDefaults: + enabled: false + + activate: -> + return unless git? + return unless config.get('git-diff.enabled') + + rootView.eachEditor (editor) => + new GitDiffView(editor) if git? and editor.getPane()? diff --git a/src/packages/git-diff/package.cson b/src/packages/git-diff/package.cson new file mode 100644 index 000000000..8fe8b9080 --- /dev/null +++ b/src/packages/git-diff/package.cson @@ -0,0 +1 @@ +'main': 'lib/git-diff' diff --git a/src/packages/git-diff/spec/git-diff-spec.coffee b/src/packages/git-diff/spec/git-diff-spec.coffee new file mode 100644 index 000000000..a800ac37a --- /dev/null +++ b/src/packages/git-diff/spec/git-diff-spec.coffee @@ -0,0 +1,63 @@ +RootView = require 'root-view' +_ = require 'underscore' + +describe "GitDiff package", -> + editor = null + + beforeEach -> + config.set('git-diff.enabled', true) + window.rootView = new RootView + rootView.attachToDom() + rootView.open('sample.js') + atom.activatePackage('git-diff') + editor = rootView.getActiveView() + + describe "when the editor has modified lines", -> + it "highlights the modified lines", -> + expect(editor.find('.git-line-modified').length).toBe 0 + editor.insertText('a') + advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editor.find('.git-line-modified').length).toBe 1 + expect(editor.find('.git-line-modified').attr('lineNumber')).toBe '0' + + describe "when the editor has added lines", -> + it "highlights the added lines", -> + expect(editor.find('.git-line-added').length).toBe 0 + editor.moveCursorToEndOfLine() + editor.insertNewline() + editor.insertText('a') + advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editor.find('.git-line-added').length).toBe 1 + expect(editor.find('.git-line-added').attr('lineNumber')).toBe '1' + + describe "when the editor has removed lines", -> + it "highlights the line preceeding the deleted lines", -> + expect(editor.find('.git-line-added').length).toBe 0 + editor.setCursorBufferPosition([5]) + editor.deleteLine() + advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editor.find('.git-line-removed').length).toBe 1 + expect(editor.find('.git-line-removed').attr('lineNumber')).toBe '4' + + describe "when a modified line is restored to the HEAD version contents", -> + it "removes the diff highlight", -> + expect(editor.find('.git-line-modified').length).toBe 0 + editor.insertText('a') + advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editor.find('.git-line-modified').length).toBe 1 + editor.backspace() + advanceClock(editor.getBuffer().stoppedChangingDelay) + expect(editor.find('.git-line-modified').length).toBe 0 + + describe "when a modified file is opened", -> + it "highlights the changed lines", -> + path = project.resolve('sample.txt') + buffer = project.buildBuffer(path) + buffer.setText("Some different text.") + rootView.open('sample.txt') + nextTick = false + _.nextTick -> nextTick = true + waitsFor -> nextTick + runs -> + expect(editor.find('.git-line-modified').length).toBe 1 + expect(editor.find('.git-line-modified').attr('lineNumber')).toBe '0' diff --git a/src/packages/git-diff/stylesheets/git-diff.less b/src/packages/git-diff/stylesheets/git-diff.less new file mode 100644 index 000000000..a04bed75d --- /dev/null +++ b/src/packages/git-diff/stylesheets/git-diff.less @@ -0,0 +1,11 @@ +.git-line-modified { + color: #f78a46; +} + +.git-line-added { + color: #5293d8; +} + +.git-line-removed { + color: #c41e3a; +}