<commit_before>_ = require 'underscore-plus'
path = require 'path'
Serializable = require 'serializable'
{Model} = require 'theorist'
{Point, Range} = require 'text-buffer'
LanguageMode = require './language-mode'
DisplayBuffer = require './display-buffer'
Cursor = require './cursor'
Selection = require './selection'
TextMateScopeSelector = require('first-mate').ScopeSelector

# Public: The core model of Atom.
#
# An {Editor} represents a unique view of each document, with its own
# {Cursor}s and scroll position.
#
# For instance if a user creates a split, Atom creates a second {Editor}
# but both {Editor}s interact with the same buffer underlying buffer. So
# if you type in either buffer it immediately appears in both but if you scroll
# in one it doesn't scroll the other.
#
# Almost all packages will interact primiarily with this class as it provides
# access to objects you'll most commonly interact with. To access it you'll
# want to register a callback on {WorkspaceView} which will be fired once for every
# existing {Editor} as well as any future {Editor}s.
#
# ## Example
# ```coffeescript
#   atom.workspaceView.eachEditorView (editorView) ->
#     editorView.insertText('Hello World')
# ```
module.exports =
class Editor extends Model
  Serializable.includeInto(this)
  atom.deserializers.add(this)

  @properties
    scrollTop: 0
    scrollLeft: 0

  deserializing: false
  callDisplayBufferCreatedHook: false
  registerEditor: false
  buffer: null
  languageMode: null
  cursors: null
  selections: null
  suppressSelectionMerging: false

  constructor: ({@softTabs, initialLine, tabLength, softWrap, @displayBuffer, buffer, registerEditor, suppressCursorCreation}) ->
    super

    @cursors = []
    @selections = []

    @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrap})
    @buffer = @displayBuffer.buffer
    @softTabs = @buffer.usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true

    for marker in @findMarkers(@getSelectionMarkerAttributes())
      marker.setAttributes(preserveFolds: true)
      @addSelection(marker)

    @subscribeToBuffer()
    @subscribeToDisplayBuffer()

    if @getCursors().length is 0 and not suppressCursorCreation
      if initialLine
        position = [initialLine, 0]
      else
        position = [0, 0]
      @addCursorAtBufferPosition(position)

    @languageMode = new LanguageMode(this, @buffer.getExtension())

    @subscribe @$scrollTop, 'value', (scrollTop) => @emit 'scroll-top-changed', scrollTop
    @subscribe @$scrollLeft, 'value', (scrollLeft) => @emit 'scroll-left-changed', scrollLeft

    atom.project.addEditor(this) if registerEditor

  serializeParams: ->
    id: @id
    softTabs: @softTabs
    scrollTop: @scrollTop
    scrollLeft: @scrollLeft
    displayBuffer: @displayBuffer.serialize()

  deserializeParams: (params) ->
    params.displayBuffer = DisplayBuffer.deserialize(params.displayBuffer)
    params.registerEditor = true
    params

  # Private:
  subscribeToBuffer: ->
    @buffer.retain()
    @subscribe @buffer, "path-changed", =>
      unless atom.project.getPath()?
        atom.project.setPath(path.dirname(@getPath()))
      @emit "title-changed"
      @emit "path-changed"
    @subscribe @buffer, "contents-modified", => @emit "contents-modified"
    @subscribe @buffer, "contents-conflicted", => @emit "contents-conflicted"
    @subscribe @buffer, "modified-status-changed", => @emit "modified-status-changed"
    @subscribe @buffer, "destroyed", => @destroy()
    @preserveCursorPositionOnBufferReload()

  # Private:
  subscribeToDisplayBuffer: ->
    @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
    @subscribe @displayBuffer, "changed", (e) => @emit 'screen-lines-changed', e
    @subscribe @displayBuffer, "markers-updated", => @mergeIntersectingSelections()
    @subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
    @subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...

  # Private:
  getViewClass: ->
    require './editor-view'

  # Private:
  destroyed: ->
    @unsubscribe()
    selection.destroy() for selection in @getSelections()
    @buffer.release()
    @displayBuffer.destroy()
    @languageMode.destroy()
    atom.project?.removeEditor(this)

  # Private: Creates an {Editor} with the same initial state
  copy: ->
    tabLength = @getTabLength()
    displayBuffer = @displayBuffer.copy()
    softTabs = @getSoftTabs()
    newEditor = new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true})
    newEditor.setScrollTop(@getScrollTop())
    newEditor.setScrollLeft(@getScrollLeft())
    for marker in @findMarkers(editorId: @id)
      marker.copy(editorId: newEditor.id, preserveFolds: true)
    atom.project.addEditor(newEditor)
    newEditor

  # Public: Retrieves the filename of the open file.
  #
  # This is `'untitled'` if the file is new and not saved to the disk.
  #
  # Returns a {String}.
  getTitle: ->
    if sessionPath = @getPath()
      path.basename(sessionPath)
    else
      'untitled'

  # Public: Retrieves the filename and path of the open file.
  #
  # It has the follows the following format, `<filename> - <directory>`. If the
  # file is brand new, the title is `untitled`.
  #
  # Returns a {String}.
  getLongTitle: ->
    if sessionPath = @getPath()
      fileName = path.basename(sessionPath)
      directory = path.basename(path.dirname(sessionPath))
      "#{fileName} - #{directory}"
    else
      'untitled'

  # Public: Compares two `Editor`s to determine equality.
  #
  # Equality is based on the condition that:
  #
  # * the two {TextBuffer}s are the same
  # * the two `scrollTop` and `scrollLeft` property are the same
  # * the two {Cursor} screen positions are the same
  #
  # Returns a {Boolean}.
  isEqual: (other) ->
    return false unless other instanceof Editor
    @buffer.getPath() == other.buffer.getPath() and
      @getScrollTop() == other.getScrollTop() and
      @getScrollLeft() == other.getScrollLeft() and
      @getCursorScreenPosition().isEqual(other.getCursorScreenPosition())

  # Public: Controls visiblity based on the given Boolean.
  setVisible: (visible) -> @displayBuffer.setVisible(visible)

  # Deprecated: Use the ::scrollTop property directly
  setScrollTop: (@scrollTop) -> @scrollTop

  # Deprecated: Use the ::scrollTop property directly
  getScrollTop: -> @scrollTop

  # Deprecated: Use the ::scrollLeft property directly
  setScrollLeft: (@scrollLeft) -> @scrollLeft

  # Deprecated: Use the ::scrollLeft property directly
  getScrollLeft: -> @scrollLeft<commit_msg>Avoid exceptions when a live editor is compared with a destroyed one<commit_after>_ = require 'underscore-plus'
path = require 'path'
Serializable = require 'serializable'
{Model} = require 'theorist'
{Point, Range} = require 'text-buffer'
LanguageMode = require './language-mode'
DisplayBuffer = require './display-buffer'
Cursor = require './cursor'
Selection = require './selection'
TextMateScopeSelector = require('first-mate').ScopeSelector

# Public: The core model of Atom.
#
# An {Editor} represents a unique view of each document, with its own
# {Cursor}s and scroll position.
#
# For instance if a user creates a split, Atom creates a second {Editor}
# but both {Editor}s interact with the same buffer underlying buffer. So
# if you type in either buffer it immediately appears in both but if you scroll
# in one it doesn't scroll the other.
#
# Almost all packages will interact primiarily with this class as it provides
# access to objects you'll most commonly interact with. To access it you'll
# want to register a callback on {WorkspaceView} which will be fired once for every
# existing {Editor} as well as any future {Editor}s.
#
# ## Example
# ```coffeescript
#   atom.workspaceView.eachEditorView (editorView) ->
#     editorView.insertText('Hello World')
# ```
module.exports =
class Editor extends Model
  Serializable.includeInto(this)
  atom.deserializers.add(this)

  @properties
    scrollTop: 0
    scrollLeft: 0

  deserializing: false
  callDisplayBufferCreatedHook: false
  registerEditor: false
  buffer: null
  languageMode: null
  cursors: null
  selections: null
  suppressSelectionMerging: false

  constructor: ({@softTabs, initialLine, tabLength, softWrap, @displayBuffer, buffer, registerEditor, suppressCursorCreation}) ->
    super

    @cursors = []
    @selections = []

    @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrap})
    @buffer = @displayBuffer.buffer
    @softTabs = @buffer.usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true

    for marker in @findMarkers(@getSelectionMarkerAttributes())
      marker.setAttributes(preserveFolds: true)
      @addSelection(marker)

    @subscribeToBuffer()
    @subscribeToDisplayBuffer()

    if @getCursors().length is 0 and not suppressCursorCreation
      if initialLine
        position = [initialLine, 0]
      else
        position = [0, 0]
      @addCursorAtBufferPosition(position)

    @languageMode = new LanguageMode(this, @buffer.getExtension())

    @subscribe @$scrollTop, 'value', (scrollTop) => @emit 'scroll-top-changed', scrollTop
    @subscribe @$scrollLeft, 'value', (scrollLeft) => @emit 'scroll-left-changed', scrollLeft

    atom.project.addEditor(this) if registerEditor

  serializeParams: ->
    id: @id
    softTabs: @softTabs
    scrollTop: @scrollTop
    scrollLeft: @scrollLeft
    displayBuffer: @displayBuffer.serialize()

  deserializeParams: (params) ->
    params.displayBuffer = DisplayBuffer.deserialize(params.displayBuffer)
    params.registerEditor = true
    params

  # Private:
  subscribeToBuffer: ->
    @buffer.retain()
    @subscribe @buffer, "path-changed", =>
      unless atom.project.getPath()?
        atom.project.setPath(path.dirname(@getPath()))
      @emit "title-changed"
      @emit "path-changed"
    @subscribe @buffer, "contents-modified", => @emit "contents-modified"
    @subscribe @buffer, "contents-conflicted", => @emit "contents-conflicted"
    @subscribe @buffer, "modified-status-changed", => @emit "modified-status-changed"
    @subscribe @buffer, "destroyed", => @destroy()
    @preserveCursorPositionOnBufferReload()

  # Private:
  subscribeToDisplayBuffer: ->
    @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
    @subscribe @displayBuffer, "changed", (e) => @emit 'screen-lines-changed', e
    @subscribe @displayBuffer, "markers-updated", => @mergeIntersectingSelections()
    @subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
    @subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...

  # Private:
  getViewClass: ->
    require './editor-view'

  # Private:
  destroyed: ->
    @unsubscribe()
    selection.destroy() for selection in @getSelections()
    @buffer.release()
    @displayBuffer.destroy()
    @languageMode.destroy()
    atom.project?.removeEditor(this)

  # Private: Creates an {Editor} with the same initial state
  copy: ->
    tabLength = @getTabLength()
    displayBuffer = @displayBuffer.copy()
    softTabs = @getSoftTabs()
    newEditor = new Editor({@buffer, displayBuffer, tabLength, softTabs, suppressCursorCreation: true})
    newEditor.setScrollTop(@getScrollTop())
    newEditor.setScrollLeft(@getScrollLeft())
    for marker in @findMarkers(editorId: @id)
      marker.copy(editorId: newEditor.id, preserveFolds: true)
    atom.project.addEditor(newEditor)
    newEditor

  # Public: Retrieves the filename of the open file.
  #
  # This is `'untitled'` if the file is new and not saved to the disk.
  #
  # Returns a {String}.
  getTitle: ->
    if sessionPath = @getPath()
      path.basename(sessionPath)
    else
      'untitled'

  # Public: Retrieves the filename and path of the open file.
  #
  # It has the follows the following format, `<filename> - <directory>`. If the
  # file is brand new, the title is `untitled`.
  #
  # Returns a {String}.
  getLongTitle: ->
    if sessionPath = @getPath()
      fileName = path.basename(sessionPath)
      directory = path.basename(path.dirname(sessionPath))
      "#{fileName} - #{directory}"
    else
      'untitled'

  # Public: Compares two `Editor`s to determine equality.
  #
  # Equality is based on the condition that:
  #
  # * the two {TextBuffer}s are the same
  # * the two `scrollTop` and `scrollLeft` property are the same
  # * the two {Cursor} screen positions are the same
  #
  # Returns a {Boolean}.
  isEqual: (other) ->
    return false unless other instanceof Editor
    @isAlive() == other.isAlive() and
      @buffer.getPath() == other.buffer.getPath() and
      @getScrollTop() == other.getScrollTop() and
      @getScrollLeft() == other.getScrollLeft() and
      @getCursorScreenPosition().isEqual(other.getCursorScreenPosition())

  # Public: Controls visiblity based on the given Boolean.
  setVisible: (visible) -> @displayBuffer.setVisible(visible)

  # Deprecated: Use the ::scrollTop property directly
  setScrollTop: (@scrollTop) -> @scrollTop

  # Deprecated: Use the ::scrollTop property directly
  getScrollTop: -> @scrollTop

  # Deprecated: Use the ::scrollLeft property directly
  setScrollLeft: (@scrollLeft) -> @scrollLeft

  # Deprecated: Use the ::scrollLeft property directly
  getScrollLeft: -> @scrollLeft
{'fraction_non_alphanumeric': 0.1166154537959979, 'fraction_numerical': 0.0008993479727197782, 'mean_word_length': 3.9846843481509153, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1410239', 'n_tokens_mistral': 3904, 'n_tokens_neox': 3833, 'n_words': 1133}