 
   vmpIsEnabled: ->
     @editorElement.classList.contains('vim-mode-plus')
+
+  # Direct-edit related
+  # -------------------------
+  updateRealFile: ->
+    # action = require './ui-action/update-real-file'
+    # new action(this).run()
+    return unless @provider.supportDirectEdit
+    return unless @isModified()
+
+    if settings.get('confirmOnUpdateRealFile')
+      unless atom.confirm(message: 'Update real file?', buttons: ['Update', 'Cancel']) is 0
+        return
+
+    return unless @ensureNarrowEditorIsValidState()
+
+    changes = []
+    lines = @editor.buffer.getLines()
+    for line, row in lines when @isNormalItem(item = @items[row])
+      if item._lineHeader?
+        line = line[item._lineHeader.length...] # Strip lineHeader
+      if line isnt item.text
+        changes.push({newText: line, item})
+
+    return unless changes.length
+
+    unless @provider.boundToEditor
+      {success, message} = @ensureNoModifiedFileForChanges(changes)
+      unless success
+        atom.notifications.addWarning(message, dismissable: true)
+        return
+
+    {success, message} = @ensureNoConflictForChanges(changes)
+    unless success
+      atom.notifications.addWarning(message, dismissable: true)
+      return
+
+    @provider.updateRealFile(changes)
+    @setModifiedState(false)
+
+  ensureNarrowEditorIsValidState: ->
+    unless @editorLastRow is @editor.getLastBufferRow()
+      return false
+
+    # Ensure all item have valid line header
+    if @provider.showLineHeader
+      for line, row in @editor.buffer.getLines() when @isNormalItem(item = @items[row])
+        return false unless line.startsWith(item._lineHeader)
+
+    true
+
+  getModifiedFilePathsInChanges: (changes) ->
+    _.uniq(changes.map ({item}) -> item.filePath).filter (filePath) ->
+      atom.project.isPathModified(filePath)
+
+  ensureNoModifiedFileForChanges: (changes) ->
+    message = ''
+    modifiedFilePaths = @getModifiedFilePathsInChanges(changes)
+    success = modifiedFilePaths.length is 0
+    unless success
+      modifiedFilePathsAsString = modifiedFilePaths.map((filePath) -> " - `#{filePath}`").join("\n")
+      message = """
+        Cancelled `update-real-file`.
+        You are trying to update file which have **unsaved modification**.
+        But `narrow:#{@provider.getDashName()}` can not detect unsaved change.
+        To use `update-real-file`, you need to save these files.
+
+        #{modifiedFilePathsAsString}
+        """
+
+    return {success, message}
+
+  ensureNoConflictForChanges: (changes) ->
+    message = []
+    conflictChanges = @detectConflictForChanges(changes)
+    success = _.isEmpty(conflictChanges)
+    unless success
+      message.push """
+        Cancelled `update-real-file`.
+        Detected **conflicting change to same line**.
+        """
+      for filePath, changesInFile of conflictChanges
+        message.push("- #{filePath}")
+        for {newText, item} in changesInFile
+          message.push("  - #{item.point.translate([1, 1]).toString()}, #{newText}")
+
+    return {success, message: message.join("\n")}
+
+  detectConflictForChanges: (changes) ->
+    conflictChanges = {}
+    changesByFilePath =  _.groupBy(changes, ({item}) -> item.filePath)
+    for filePath, changesInFile of changesByFilePath
+      changesByRow = _.groupBy(changesInFile, ({item}) -> item.point.row)
+      for row, changesInRow of changesByRow
+        newTexts = _.pluck(changesInRow, 'newText')
+        if _.uniq(newTexts).length > 1
+          conflictChanges[filePath] ?= []
+          conflictChanges[filePath].push(changesInRow...)
+    conflictChanges

    @disposables.add new Disposable =>
      @constructor.unregister(this)

  toggleProtected: ->
    @setProtected(not @protected)

  setProtected: (@protected) ->
    @itemIndicator.redraw()
    @updateProviderPanel({@protected})

  setReadOnly: (readOnly) ->
    @readOnly = readOnly
    if @readOnly
      @editorElement.component?.setInputEnabled(false)
      @editorElement.classList.add('read-only')
      @vmpActivateNormalMode() if @vmpIsInsertMode()
    else
      @editorElement.component.setInputEnabled(true)
      @editorElement.classList.remove('read-only')
      @vmpActivateInsertMode() if @vmpIsNormalMode()

  observeStopChangingActivePaneItem: ->
    needToSync = (item) =>

      'narrow-ui:toggle-search-whole-word': => @toggleSearchWholeWord()
      'narrow-ui:toggle-search-ignore-case': => @toggleSearchIgnoreCase()

  withIgnoreCursorMove: (fn) ->
    @ignoreCursorMove = true
    fn()
    @ignoreCursorMove = false

  withIgnoreChange: (fn) ->
    @ignoreChange = true
    fn()
    @ignoreChange = false

  isModified: ->
    @modifiedState

  setModifiedState: (state) ->
    if state isnt @modifiedState
      # HACK: overwrite TextBuffer:isModified to return static state.
      # This state is used for tabs package to show modified icon on tab.
      @modifiedState = state
      @editor.buffer.isModified = -> state
      @editor.buffer.emitModifiedStatusChanged(state)

  toggleSearchWholeWord: ->
    @provider.toggleSearchWholeWord()
    @refresh(force: true)
