littlebee / git-time-machine

Atom package that allows you to travel back in commit history
MIT License
1.12k stars 44 forks source link

Uncaught TypeError: Cannot read property 'handleCursorChange' of null #107

Open SpeedrunnerG55 opened 7 years ago

SpeedrunnerG55 commented 7 years ago

[Enter steps to reproduce:]

  1. ...
  2. ...

Atom: 1.15.0 x64 Electron: 1.3.13 OS: Ubuntu 16.04.2 Thrown From: git-time-machine package 1.5.9

Stack Trace

Uncaught TypeError: Cannot read property 'handleCursorChange' of null

At /home/speed/.atom/packages/git-time-machine/node_modules/split-diff/lib/split-diff.coffee:166

TypeError: Cannot read property 'handleCursorChange' of null
    at /packages/split-diff/lib/split-diff.coffee:166:18
    at Function.module.exports.Emitter.simpleDispatch (/app.asar/node_modules/event-kit/lib/emitter.js:25:14)
    at Emitter.module.exports.Emitter.emit (/app.asar/node_modules/event-kit/lib/emitter.js:129:28)
    at TextEditor.module.exports.TextEditor.cursorMoved (/app.asar/src/text-editor.js:2126:27)
    at Selection.module.exports.Selection.markerDidChange (/app.asar/src/selection.js:921:21)
    at /app.asar/src/selection.js:41:24
    at Function.module.exports.Emitter.simpleDispatch (/app.asar/node_modules/event-kit/lib/emitter.js:25:14)
    at Emitter.module.exports.Emitter.emit (/app.asar/node_modules/event-kit/lib/emitter.js:129:28)
    at DisplayMarker.module.exports.DisplayMarker.notifyObservers (/app.asar/node_modules/text-buffer/lib/display-marker.js:248:27)
    at /app.asar/node_modules/text-buffer/lib/display-marker.js:53:26
    at Function.module.exports.Emitter.simpleDispatch (/app.asar/node_modules/event-kit/lib/emitter.js:25:14)
    at Emitter.module.exports.Emitter.emit (/app.asar/node_modules/event-kit/lib/emitter.js:129:28)
    at Marker.module.exports.Marker.emitChangeEvent (/app.asar/node_modules/text-buffer/lib/marker.js:421:20)
    at Marker.module.exports.Marker.update (/app.asar/node_modules/text-buffer/lib/marker.js:362:12)
    at Marker.module.exports.Marker.setHeadPosition (/app.asar/node_modules/text-buffer/lib/marker.js:144:19)
    at DisplayMarker.module.exports.DisplayMarker.setHeadBufferPosition (/app.asar/node_modules/text-buffer/lib/display-marker.js:146:32)
    at DisplayMarker.module.exports.DisplayMarker.setHeadScreenPosition (/app.asar/node_modules/text-buffer/lib/display-marker.js:154:19)
    at /app.asar/src/cursor.js:72:31
    at Cursor.module.exports.Cursor.changePosition (/app.asar/src/cursor.js:684:7)
    at Cursor.module.exports.Cursor.setScreenPosition (/app.asar/src/cursor.js:70:19)
    at /app.asar/src/text-editor.js:1897:23
    at TextEditor.module.exports.TextEditor.moveCursors (/app.asar/src/text-editor.js:2120:9)
    at TextEditor.module.exports.TextEditor.setCursorScreenPosition (/app.asar/src/text-editor.js:1896:19)
    at TextEditorComponent.module.exports.TextEditorComponent.onMouseDown (/app.asar/src/text-editor-component.js:721:25)
    at /app.asar/src/text-editor-component.js:3:59)

Commands

     -2:22.1.0 tree-view:show (atom-workspace.workspace.scrollbars-visible-always.theme-base16-tomorrow-dark-theme.theme-one-dark-ui)
  2x -1:40.8.0 git-time-machine:toggle (input.hidden-input)
     -0:34.5.0 intentions:highlight (input.hidden-input)
     -0:34.3.0 core:save (input.hidden-input)
     -0:27.9.0 split-diff:toggle (input.hidden-input)

Non-Core Packages

atom-clock 0.1.6 
auto-indent 0.5.0 
busy-signal 1.3.0 
file-icons 2.1.1 
git-log 0.4.1 
git-plus 7.3.3 
git-time-machine 1.5.9 
gpp-compiler 3.0.7 
intentions 1.1.2 
linter 2.1.2 
linter-gcc 0.7.1 
linter-ui-default 1.2.1 
linter-write-good 0.8.0 
minimap 4.26.8 
minimap-bookmarks 0.4.2 
minimap-cursorline 0.2.0 
minimap-find-and-replace 4.5.2 
minimap-git-diff 4.3.1 
minimap-highlight-selected 4.5.0 
minimap-linter 2.0.0 
minimap-selection 4.4.0 
minimap-split-diff 0.3.7 
split-diff 1.3.0 
tree-view-git-status 1.3.1 
wakatime 6.0.13 
aplocher commented 7 years ago

Getting this too, whenever I load an item from the time machine timeline (app.ts - TypeScript file from angular js v1 project, if it matters)

export-mike commented 7 years ago

I manually edited my

{CompositeDisposable, Directory, File} = require 'atom'
DiffView = require './diff-view'
LoadingView = require './ui/loading-view'
FooterView = require './ui/footer-view'
SyncScroll = require './sync-scroll'
configSchema = require './config-schema'
path = require 'path'

module.exports = SplitDiff =
  diffView: null
  config: configSchema
  subscriptions: null
  editorSubscriptions: null
  isEnabled: false
  wasEditor1Created: false
  wasEditor2Created: false
  hasGitRepo: false
  process: null

  activate: (state) ->
    window.splitDiffResolves = []

    @subscriptions = new CompositeDisposable()
    @subscriptions.add atom.commands.add 'atom-workspace, .tree-view .selected, .tab.texteditor',
      'split-diff:enable': (e) =>
        @diffPanes(e)
        e.stopPropagation()
      'split-diff:next-diff': =>
        if @isEnabled
          @nextDiff()
        else
          @diffPanes()
      'split-diff:prev-diff': =>
        if @isEnabled
          @prevDiff()
        else
          @diffPanes()
      'split-diff:copy-to-right': =>
        if @isEnabled
          @copyToRight()
      'split-diff:copy-to-left': =>
        if @isEnabled
          @copyToLeft()
      'split-diff:disable': => @disable()
      'split-diff:ignore-whitespace': => @toggleIgnoreWhitespace()
      'split-diff:toggle': => @toggle()

  deactivate: ->
    @disable()
    @subscriptions.dispose()

  # called by "toggle" command
  # toggles split diff
  toggle: () ->
    if @isEnabled
      @disable()
    else
      @diffPanes()

  # called by "Disable" command
  # removes diff and sync scroll, disposes of subscriptions
  disable: () ->
    @isEnabled = false

    # remove listeners
    if @editorSubscriptions?
      @editorSubscriptions.dispose()
      @editorSubscriptions = null

    if @diffView?
      if @wasEditor1Created
        @diffView.cleanUpEditor(1)
      if @wasEditor2Created
        @diffView.cleanUpEditor(2)
      @diffView.destroy()
      @diffView = null

    # remove views
    if @footerView?
      @footerView.destroy()
      @footerView = null
    if @loadingView?
      @loadingView.destroy()
      @loadingView = null

    if @syncScroll?
      @syncScroll.dispose()
      @syncScroll = null

    # reset all variables
    @wasEditor1Created = false
    @wasEditor2Created = false
    @hasGitRepo = false

    # auto hide tree view while diffing #82
    if @_getConfig('hideTreeView')
      atom.commands.dispatch(atom.views.getView(atom.workspace), 'tree-view:show')

  # called by "toggle ignore whitespace" command
  # toggles ignoring whitespace and refreshes the diff
  toggleIgnoreWhitespace: ->
    isWhitespaceIgnored = @_getConfig('ignoreWhitespace')
    @_setConfig('ignoreWhitespace', !isWhitespaceIgnored)
    @footerView?.setIgnoreWhitespace(!isWhitespaceIgnored)

  # called by "Move to next diff" command
  nextDiff: ->
    if @diffView?
      selectedIndex = @diffView.nextDiff()
      @footerView?.showSelectionCount( selectedIndex + 1 )

  # called by "Move to previous diff" command
  prevDiff: ->
    if @diffView?
      selectedIndex = @diffView.prevDiff()
      @footerView?.showSelectionCount( selectedIndex + 1 )

  # called by "Copy to right" command
  copyToRight: ->
    if @diffView?
      @diffView.copyToRight()
      @footerView?.hideSelectionCount()

  # called by "Copy to left" command
  copyToLeft: ->
    if @diffView?
      @diffView.copyToLeft()
      @footerView?.hideSelectionCount()

  # called by the commands enable/toggle to do initial diff
  # sets up subscriptions for auto diff and disabling when a pane is destroyed
  # event is an optional argument of a file path to diff with current
  diffPanes: (event) ->
    # in case enable was called again
    @disable()

    @editorSubscriptions = new CompositeDisposable()

    if event?.currentTarget.classList.contains('tab')
      filePath = event.currentTarget.path
      editorsPromise = @_getEditorsForDiffWithActive(filePath)
    else if event?.currentTarget.classList.contains('list-item') && event?.currentTarget.classList.contains('file')
      filePath = event.currentTarget.getPath()
      editorsPromise = @_getEditorsForDiffWithActive(filePath)
    else
      editorsPromise = @_getEditorsForQuickDiff()

    editorsPromise.then ((editors) ->
      if editors == null
        return
      @_setupVisibleEditors(editors.editor1, editors.editor2)
      @diffView = new DiffView(editors)

      # add listeners
      @editorSubscriptions.add editors.editor1.onDidStopChanging =>
        @updateDiff(editors)
      @editorSubscriptions.add editors.editor2.onDidStopChanging =>
        @updateDiff(editors)
      @editorSubscriptions.add editors.editor1.onDidDestroy =>
        @disable()
      @editorSubscriptions.add editors.editor2.onDidDestroy =>
        @disable()
      @editorSubscriptions.add atom.config.onDidChange 'split-diff', () =>
        @updateDiff(editors)
      @editorSubscriptions.add editors.editor1.onDidChangeCursorPosition (event) =>
        if @diffView.handleCursorChange
          @diffView.handleCursorChange(event.cursor, event.oldBufferPosition, event.newBufferPosition)
      @editorSubscriptions.add editors.editor2.onDidChangeCursorPosition (event) =>
        if @diffView.handleCursorChange
          @diffView.handleCursorChange(event.cursor, event.oldBufferPosition, event.newBufferPosition)
      @editorSubscriptions.add editors.editor1.onDidAddCursor (cursor) =>
        if @diffView.handleCursorChange
          @diffView.handleCursorChange(cursor, -1, cursor.getBufferPosition())
      @editorSubscriptions.add editors.editor2.onDidAddCursor (cursor) =>
        if @diffView.handleCursorChange
          @diffView.handleCursorChange(cursor, -1, cursor.getBufferPosition())

      # add the bottom UI panel
      if !@footerView?
        @footerView = new FooterView(@_getConfig('ignoreWhitespace'))
        @footerView.createPanel()
      @footerView.show()

      # update diff if there is no git repo (no onchange fired)
      if !@hasGitRepo
        @updateDiff(editors)

      # add application menu items
      @editorSubscriptions.add atom.menu.add [
        {
          'label': 'Packages'
          'submenu': [
            'label': 'Split Diff'
            'submenu': [
              { 'label': 'Ignore Whitespace', 'command': 'split-diff:ignore-whitespace' }
              { 'label': 'Move to Next Diff', 'command': 'split-diff:next-diff' }
              { 'label': 'Move to Previous Diff', 'command': 'split-diff:prev-diff' }
              { 'label': 'Copy to Right', 'command': 'split-diff:copy-to-right'}
              { 'label': 'Copy to Left', 'command': 'split-diff:copy-to-left'}
            ]
          ]
        }
      ]
      @editorSubscriptions.add atom.contextMenu.add {
        'atom-text-editor': [{
          'label': 'Split Diff',
          'submenu': [
            { 'label': 'Ignore Whitespace', 'command': 'split-diff:ignore-whitespace' }
            { 'label': 'Move to Next Diff', 'command': 'split-diff:next-diff' }
            { 'label': 'Move to Previous Diff', 'command': 'split-diff:prev-diff' }
            { 'label': 'Copy to Right', 'command': 'split-diff:copy-to-right'}
            { 'label': 'Copy to Left', 'command': 'split-diff:copy-to-left'}
          ]
        }]
      }
      ).bind(this) # make sure the scope is correct

  # called by both diffPanes and the editor subscription to update the diff
  updateDiff: (editors) ->
    @isEnabled = true

    # auto hide tree view while diffing #82
    if @_getConfig('hideTreeView') && document.querySelector('.tree-view')
      atom.commands.dispatch(atom.views.getView(atom.workspace), 'tree-view:toggle')

    # if there is a diff being computed in the background, cancel it
    if @process?
      @process.kill()
      @process = null

    isWhitespaceIgnored = @_getConfig('ignoreWhitespace')
    editorPaths = @_createTempFiles(editors)

    # create the loading view if it doesn't exist yet
    if !@loadingView?
      @loadingView = new LoadingView()
      @loadingView.createModal()
    @loadingView.show()

    # --- kick off background process to compute diff ---
    {BufferedNodeProcess} = require 'atom'
    command = path.resolve __dirname, "./compute-diff.js"
    args = [editorPaths.editor1Path, editorPaths.editor2Path, isWhitespaceIgnored]
    theOutput = ''
    stdout = (output) =>
      theOutput = output
      computedDiff = JSON.parse(output)
      @process.kill()
      @process = null
      @loadingView?.hide()
      @_resumeUpdateDiff(editors, computedDiff)
    stderr = (err) =>
      theOutput = err
    exit = (code) =>
      @loadingView?.hide()

      if code != 0
        console.log('BufferedNodeProcess code was ' + code)
        console.log(theOutput)
    @process = new BufferedNodeProcess({command, args, stdout, stderr, exit})
    # --- kick off background process to compute diff ---

  # resumes after the compute diff process returns
  _resumeUpdateDiff: (editors, computedDiff) ->
    return unless @diffView?

    @diffView.clearDiff()
    if @syncScroll?
      @syncScroll.dispose()
      @syncScroll = null

    leftHighlightType = 'added'
    rightHighlightType = 'removed'
    if @_getConfig('leftEditorColor') == 'red'
      leftHighlightType = 'removed'
    if @_getConfig('rightEditorColor') == 'green'
      rightHighlightType = 'added'
    @diffView.displayDiff(computedDiff, leftHighlightType, rightHighlightType, @_getConfig('diffWords'), @_getConfig('ignoreWhitespace'))

    while window.splitDiffResolves?.length
      window.splitDiffResolves.pop()(@diffView.getMarkerLayers())

    @footerView?.setNumDifferences(@diffView.getNumDifferences())

    scrollSyncType = @_getConfig('scrollSyncType')
    if scrollSyncType == 'Vertical + Horizontal'
      @syncScroll = new SyncScroll(editors.editor1, editors.editor2, true)
      @syncScroll.syncPositions()
    else if scrollSyncType == 'Vertical'
      @syncScroll = new SyncScroll(editors.editor1, editors.editor2, false)
      @syncScroll.syncPositions()

  # Gets the first two visible editors found or creates them as needed.
  # Returns a Promise which yields a value of {editor1: TextEditor, editor2: TextEditor}
  _getEditorsForQuickDiff: () ->
    editor1 = null
    editor2 = null

    # try to find the first two editors
    panes = atom.workspace.getPanes()
    for p in panes
      activeItem = p.getActiveItem()
      if atom.workspace.isTextEditor(activeItem)
        if editor1 == null
          editor1 = activeItem
        else if editor2 == null
          editor2 = activeItem
          break

    # auto open editor panes so we have two to diff with
    if editor1 == null
      editor1 = atom.workspace.buildTextEditor()
      @wasEditor1Created = true
      # add first editor to the first pane
      panes[0].addItem(editor1)
      panes[0].activateItem(editor1)
    if editor2 == null
      editor2 = atom.workspace.buildTextEditor()
      @wasEditor2Created = true
      editor2.setGrammar(editor1.getGrammar())
      rightPaneIndex = panes.indexOf(atom.workspace.paneForItem(editor1)) + 1
      if panes[rightPaneIndex]
        # add second editor to existing pane to the right of first editor
        panes[rightPaneIndex].addItem(editor2)
        panes[rightPaneIndex].activateItem(editor2)
      else
        # no existing pane so split right
        atom.workspace.paneForItem(editor1).splitRight({items: [editor2]})

    return Promise.resolve({editor1: editor1, editor2: editor2})

  # Gets the active editor and opens the specified file to the right of it
  # Returns a Promise which yields a value of {editor1: TextEditor, editor2: TextEditor}
  _getEditorsForDiffWithActive: (filePath) ->
    activeEditor = atom.workspace.getActiveTextEditor()
    if activeEditor?
      editor1 = activeEditor
      @wasEditor2Created = true
      panes = atom.workspace.getPanes()
      # get index of pane following active editor pane
      rightPaneIndex = panes.indexOf(atom.workspace.paneForItem(editor1)) + 1
      # pane is created if there is not one to the right of the active editor
      rightPane = panes[rightPaneIndex] || atom.workspace.paneForItem(editor1).splitRight()
      if editor1.getPath() == filePath
        # if diffing with itself, set filePath to null so an empty editor is
        # opened, which will cause a git diff
        filePath = null
      editor2Promise = atom.workspace.openURIInPane(filePath, rightPane)

      return editor2Promise.then (editor2) ->
        return {editor1: editor1, editor2: editor2}
    else
      noActiveEditorMsg = 'No active file found! (Try focusing a text editor)'
      atom.notifications.addWarning('Split Diff', {detail: noActiveEditorMsg, dismissable: false, icon: 'diff'})
      return Promise.resolve(null)

    return Promise.resolve(null)

  _setupVisibleEditors: (editor1, editor2) ->
    BufferExtender = require './buffer-extender'
    buffer1LineEnding = (new BufferExtender(editor1.getBuffer())).getLineEnding()

    if @wasEditor2Created
      # want to scroll a newly created editor to the first editor's position
      atom.views.getView(editor1).focus()
      # set the preferred line ending before inserting text #39
      if buffer1LineEnding == '\n' || buffer1LineEnding == '\r\n'
        @editorSubscriptions.add editor2.onWillInsertText () ->
          editor2.getBuffer().setPreferredLineEnding(buffer1LineEnding)

    @_setupGitRepo(editor1, editor2)

    # unfold all lines so diffs properly align
    editor1.unfoldAll()
    editor2.unfoldAll()

    shouldNotify = !@_getConfig('muteNotifications')
    softWrapMsg = 'Warning: Soft wrap enabled! (Line diffs may not align)'
    if editor1.isSoftWrapped() && shouldNotify
      atom.notifications.addWarning('Split Diff', {detail: softWrapMsg, dismissable: false, icon: 'diff'})
    else if editor2.isSoftWrapped() && shouldNotify
      atom.notifications.addWarning('Split Diff', {detail: softWrapMsg, dismissable: false, icon: 'diff'})

    buffer2LineEnding = (new BufferExtender(editor2.getBuffer())).getLineEnding()
    if buffer2LineEnding != '' && (buffer1LineEnding != buffer2LineEnding) && editor1.getLineCount() != 1 && editor2.getLineCount() != 1 && shouldNotify
      # pop warning if the line endings differ and we haven't done anything about it
      lineEndingMsg = 'Warning: Line endings differ!'
      atom.notifications.addWarning('Split Diff', {detail: lineEndingMsg, dismissable: false, icon: 'diff'})

  _setupGitRepo: (editor1, editor2) ->
    editor1Path = editor1.getPath()
    # only show git changes if the right editor is empty
    if editor1Path? && (editor2.getLineCount() == 1 && editor2.lineTextForBufferRow(0) == '')
      for directory, i in atom.project.getDirectories()
        if editor1Path is directory.getPath() or directory.contains(editor1Path)
          projectRepo = atom.project.getRepositories()[i]
          if projectRepo? && projectRepo.repo?
            relativeEditor1Path = projectRepo.relativize(editor1Path)
            gitHeadText = projectRepo.repo.getHeadBlob(relativeEditor1Path)
            if gitHeadText?
              editor2.selectAll()
              editor2.insertText(gitHeadText)
              @hasGitRepo = true
              break

  # creates temp files so the compute diff process can get the text easily
  _createTempFiles: (editors) ->
    editor1Path = ''
    editor2Path = ''
    tempFolderPath = atom.getConfigDirPath() + '/split-diff'

    editor1Path = tempFolderPath + '/split-diff 1'
    editor1TempFile = new File(editor1Path)
    editor1TempFile.writeSync(editors.editor1.getText())

    editor2Path = tempFolderPath + '/split-diff 2'
    editor2TempFile = new File(editor2Path)
    editor2TempFile.writeSync(editors.editor2.getText())

    editorPaths =
      editor1Path: editor1Path
      editor2Path: editor2Path

    return editorPaths

  _getConfig: (config) ->
    atom.config.get("split-diff.#{config}")

  _setConfig: (config, value) ->
    atom.config.set("split-diff.#{config}", value)

  # --- SERVICE API ---
  getMarkerLayers: () ->
    new Promise (resolve, reject) ->
      window.splitDiffResolves.push(resolve)

  provideSplitDiff: ->
    getMarkerLayers: @getMarkerLayers

if @diffView.handleCursorChange

is what I added in a few placed