text: '''
  Provides both a trailing spaces highlighter and a deletion command.
  
  See README.md for details.
  
  @author: Jean-Denis Vauguet <jd@vauguet.fr>, Oktay Acikalin <ok@ryotic.de>
  @license: MIT (http://www.opensource.org/licenses/mit-license.php)
  @since: 2011-02-25
  '''
  
  import sublime
  import sublime_plugin
  import difflib
  import codecs
  
  DEFAULT_MAX_FILE_SIZE = 1048576
  DEFAULT_IS_ENABLED = True
  DEFAULT_MODIFIED_LINES_ONLY = False
  
  # Global settings object and flags.
  # Flags duplicate some of the (core) JSON settings, in case the settings file has
  # been corrupted or is empty (ST2 really dislikes that!)
  ts_settings_filename = "trailing_spaces.sublime-settings"
  ts_settings = None
  trailing_spaces_live_matching = DEFAULT_IS_ENABLED
  trim_modified_lines_only = DEFAULT_MODIFIED_LINES_ONLY
  startup_queue = []
  on_disk = None
  
  
  # Private: Loads settings and sets whether the plugin (live matching) is enabled.
  #
  # Returns nothing.
  def plugin_loaded():
      global ts_settings_filename, ts_settings, trailing_spaces_live_matching
      global current_highlighting_scope, trim_modified_lines_only, startup_queue
      global DEFAULT_COLOR_SCOPE_NAME, on_disk
  
      ts_settings = sublime.load_settings(ts_settings_filename)
      trailing_spaces_live_matching = bool(ts_settings.get("trailing_spaces_enabled",
                                           DEFAULT_IS_ENABLED))
      current_highlighting_scope = ts_settings.get("trailing_spaces_highlight_color",
                                                   "invalid")
      DEFAULT_COLOR_SCOPE_NAME = current_highlighting_scope
      trim_modified_lines_only = bool(ts_settings.get("trailing_spaces_modified_lines_only",
                                                      DEFAULT_MODIFIED_LINES_ONLY))
  
      if trailing_spaces_live_matching:
          for view in startup_queue:
              match_trailing_spaces(view)
      else:
          current_highlighting_scope = ""
          if ts_settings.get("trailing_spaces_highlight_color") != current_highlighting_scope:
              persist_settings()
  
  
  # Private: Updates user's settings with in-memory values.
  #
  # Allows for persistent settings from the menu.
  #
  # Returns nothing.
  def persist_settings():
      sublime.save_settings(ts_settings_filename)
  
  
  # Private: Determine if the view is a "Find results" view.
  #
  # view - the view, you know
  #
  # Returns True or False.
  def is_find_results(view):
      return view.settings().get('syntax') and "Find Results" in view.settings().get('syntax')
  
  
  # Private: Get the regions matching trailing spaces.
  #
  # As the core regexp matches lines, the regions are, well, "per lines".
  #
  # view - the view, you know
  #
  # Returns both the list of regions which map to trailing spaces and the list of
  # regions which are to be highlighted, as a list [matched, highlightable].
  def find_trailing_spaces(view):
      sel = view.sel()[0]
      line = view.line(sel.b)
      include_empty_lines = bool(ts_settings.get("trailing_spaces_include_empty_lines",
                                                 DEFAULT_IS_ENABLED))
      include_current_line = bool(ts_settings.get("trailing_spaces_include_current_line",
                                                  DEFAULT_IS_ENABLED))
      regexp = ts_settings.get("trailing_spaces_regexp") + "$"
      no_empty_lines_regexp = "(?<=\S)%s$" % regexp
  
      offending_lines = view.find_all(regexp if include_empty_lines else no_empty_lines_regexp)
  
      if include_current_line:
          return [offending_lines, offending_lines]
      else:
          current_offender = view.find(regexp if include_empty_lines else no_empty_lines_regexp, line.a)
          removal = False if current_offender == None else line.intersects(current_offender)
          highlightable = [i for i in offending_lines if i != current_offender] if removal else offending_lines
          return [offending_lines, highlightable]
  
  
  # Private: Find the fraking trailing spaces in the view and flags them as such!
  #
  # It will refresh highlighted regions as well. Does not execute if the
  # document's size exceeds the file_max_size setting, or if the fired in a view
  # which is not a legacy document (helper/build views and so on).
  #
  # view - the view, you know
  #
  # Returns nothing.
  def match_trailing_spaces(view):
      if ts_settings is None:
          startup_queue.append(view)
          return
  
      # Silently pass if file is too big.
      if max_size_exceeded(view):
          return
  
      if not is_find_results(view):
          (matched, highlightable) = find_trailing_spaces(view)
          add_trailing_spaces_regions(view, matched)
          highlight_trailing_spaces_regions(view, highlightable)
  
  
  # Private: Checks whether the document is bigger than the max_size setting.
  #
  # view - the view, you know
  #
  # Returns True or False.
  def max_size_exceeded(view):
      return view.size() > ts_settings.get('trailing_spaces_file_max_size',
                                           DEFAULT_MAX_FILE_SIZE)
  
  
  # Private: Marks specified regions as trailing spaces.
  #
  # view - the view, you know
  # regions - regions qualified as trailing spaces
  #
  # Returns nothing.
  def add_trailing_spaces_regions(view, regions):
      view.erase_regions('TrailingSpacesMatchedRegions')
      view.add_regions('TrailingSpacesMatchedRegions',
                       regions,
                       "",
                       "",
                       sublime.HIDE_ON_MINIMAP)
  
  
  # Private: Highlights specified regions as trailing spaces.
  #
  # It will use the scope enforced by the state of the toggable highlighting.
  #
  # view - the view, you know
  # regions - regions qualified as trailing spaces
  #
  # Returns nothing.
  def highlight_trailing_spaces_regions(view, regions):
      view.erase_regions("TrailingSpacesHighlightedRegions")
      view.add_regions('TrailingSpacesHighlightedRegions',
                       regions,
                       current_highlighting_scope or "",
                       "",
                       sublime.HIDE_ON_MINIMAP)
  
  
  # Private: Toggles highlighting of all trailing spaces in the view.
  #
  # It has no effect is the plugin is disabled.
  #
  # view - the view, you know
  #
  # Returns True (highlighting was turned on) or False (turned off).
  def toggle_highlighting(view):
      global current_highlighting_scope
  
      # If the scope is that of an invisible, there is nothing to toggle.
      if DEFAULT_COLOR_SCOPE_NAME == "":
          return "disabled!"
  
      # If performing live, highlighted trailing regions must be updated
      # internally.
      if not trailing_spaces_live_matching:
          (matched, highlightable) = find_trailing_spaces(view)
          highlight_trailing_spaces_regions(view, highlightable)
  
      scope = DEFAULT_COLOR_SCOPE_NAME if current_highlighting_scope == "" else ""
      current_highlighting_scope = scope
      highlight_trailing_spaces_regions(view, view.get_regions('TrailingSpacesHighlightedRegions'))
      return "off" if current_highlighting_scope == "" else "on"
  
  
  # Clear all the highlighted regions in all views.
  #
  # FIXME: this is not used! Delete?
  #
  # window - the window, you know
  #
  # Returns nothing.
  def clear_trailing_spaces_highlight(window):
      for view in window.views():
          view.erase_regions('TrailingSpacesMatchedRegions')
  
  
  # Find edited lines since last save, as line numbers, based on diff.
  #
  # It uses a Differ object to compute the diff between the file as red on the
  # disk, and the current buffer (which may differ from the disk's state). See
  # http://docs.python.org/2/library/difflib.html for details about diff codes.
  #
  # It relies on a full diff, so it may be expensive computation for very large
  # files (diff generation + looping through all lines).
  #
  # old - a buffer of lines, as in "old version"
  # new - a buffer of lines, as in "new version"
  #
  # Returns the list of edited line numbers.
  def modified_lines_as_numbers(old, new):
      d = difflib.Differ()
      diffs = d.compare(old, new)
  
      # Pretty Naive Algorithm (tm):
      # - split off the "Differ code", to check whether:
      #   - the line is in either in both files or just b: increment the line number
      #   - the line is only in b: it qualifies as an edited line!
      # Starting from -1 as ST2 is internally 0-based for lines.
      lineNum = -1
      edited_lines = []
      for line in diffs:
          code = line[:2]
          # those lines with "? " are not real! watch out!
          if code in ("  ", "+ "):
              lineNum += 1
          if code == "+ ":
              edited_lines.append(lineNum)
  
      return False if not edited_lines else edited_lines
  
  
  # Private: Find the dirty lines.
  #
  # view - the view, you know
  #
  # Returns the list of regions matching dirty lines.
  def get_modified_lines(view):
      try:
          on_disk
          on_buffer = view.substr(sublime.Region(0, view.size())).splitlines()
      except UnicodeDecodeError:
          sublime.status_message("File format incompatible with this feature (UTF-8 files only)")
          return
  
      lines = []
      line_numbers = modified_lines_as_numbers(on_disk, on_buffer)
      if line_numbers:
          lines = [view.full_line(view.text_point(number,0)) for number in line_numbers]
      return lines
  
  
  # Private: Finds the trailing spaces regions to be deleted.
  #
  # It abides by the user settings: while in mode "Only Modified Lines", it returns
  # the subset of trailing spaces regions which are within dirty lines; otherwise, it
  # returns all trailing spaces regions for the document.
  #
  # view - the view, you know
  #
  # Returns a list of regions to be deleted.
  def find_regions_to_delete(view):
      # If the plugin has been running in the background, regions have been matched.
      # Otherwise, we must find trailing spaces right now!
      if trailing_spaces_live_matching:
          regions = view.get_regions('TrailingSpacesMatchedRegions')
      else:
          (regions, highlightable) = find_trailing_spaces(view)
  
      # Filtering is required in case triming is restricted to dirty regions only.
      if trim_modified_lines_only:
          modified_lines = get_modified_lines(view)
  
          # If there are no dirty lines, don't do nothing.
          if not modified_lines:
              return
  
          # Super-private: filters trailing spaces regions to dirty lines only.
          #
          # As one cannot perform a smart find_all within arbitrary boundaries, we must do some
          # extra work:
          # - we want to loop through the modified lines set, not the whole trailing regions
          # - but we need a way to match modified lines with trailings to those very regions
          #
          # Hence the reversed dict on regions: keys are the text_point of the begining of
          # each region, values are the region's actual boundaries. As a Region is unhashable,
          # trailing regions are being recreated later on from those two values.
          #
          # We loop then loop through the modified lines: for each line, we get its begining
          # text_point, and check whether it matches a line with trailing spaces in the
          # reversed dict. If so, this is a match (a modified line with trailing spaces), so
          # we can re-create and store a Region for the relevant trailing spaces boundaries.
          #
          # Returns the filtered list of trailing spaces regions for the modified lines set.
          def only_those_with_trailing_spaces():
              regions_by_begin = {}
              matches = []
              for region in regions:
                  begin = view.line(region).begin()
                  regions_by_begin[begin] = (region.begin(), region.end())
  
              for line in modified_lines:
                  text_point = line.begin()
                  if text_point in regions_by_begin:
                      matches.append(sublime.Region(regions_by_begin[text_point][0], regions_by_begin[text_point][1]))
  
              return matches
  
          regions = only_those_with_trailing_spaces()
  
      return regions
  
  # Private: Deletes the trailing spaces regions.
  #
  # view - the view, you know
  # edit - the Edit object spawned by the deletion command
  #
  # Returns the number of deleted regions.
  def delete_trailing_regions(view, edit):
      regions = find_regions_to_delete(view)
  
      if regions:
          # Trick: reversing the regions takes care of the growing offset while
          # deleting the successive regions.
          regions.reverse()
          for r in regions:
              view.erase(edit, r)
          return len(regions)
      else:
          return 0
  
  
  # Public: Toggles the highlighting on or off.
  class ToggleTrailingSpacesCommand(sublime_plugin.WindowCommand):
      def run(self):
          view = self.window.active_view()
          if max_size_exceeded(view):
              sublime.status_message("File is too big, trailing spaces handling disabled.")
              return
  
          state = toggle_highlighting(view)
          ts_settings.set("trailing_spaces_highlight_color", current_highlighting_scope)
          persist_settings()
          sublime.status_message('Highlighting of trailing spaces is %s' % state)
  
      def is_checked(self):
          return current_highlighting_scope != ""
  
  
  # Public: Toggles "Modified Lines Only" mode on or off.
  class ToggleTrailingSpacesModifiedLinesOnlyCommand(sublime_plugin.WindowCommand):
      def run(self):
          global trim_modified_lines_only
  
          was_on = ts_settings.get("trailing_spaces_modified_lines_only")
          ts_settings.set("trailing_spaces_modified_lines_only", not was_on)
          persist_settings()
  
          # TODO: use ts_settings.add_on_change() when it lands in ST3
          trim_modified_lines_only = ts_settings.get('trailing_spaces_modified_lines_only')
          message = "Let's trim trailing spaces everywhere" if was_on \
                    else "Let's trim trailing spaces only on modified lines"
          sublime.status_message(message)
  
      def is_checked(self):
          return ts_settings.get("trailing_spaces_modified_lines_only")
  
  
  # Public: Matches and highlights trailing spaces on key events, according to the
  # current settings.
  class TrailingSpacesListener(sublime_plugin.EventListener):
      def on_modified(self, view):
          if trailing_spaces_live_matching:
              match_trailing_spaces(view)
  
      def on_activated(self, view):
          if trailing_spaces_live_matching:
              match_trailing_spaces(view)
  
      def on_selection_modified(self, view):
          if trailing_spaces_live_matching:
              match_trailing_spaces(view)
  
      def on_activated(self, view):
          self.freeze_last_version(view)
          if trailing_spaces_live_matching:
              match_trailing_spaces(view)
  
      def on_pre_save(self, view):
          self.freeze_last_version(view)
          if ts_settings.get("trailing_spaces_trim_on_save"):
              view.run_command("delete_trailing_spaces")
  
      # Toggling messes with what is red from the disk, and it breaks the diff
      # used when modified_lines_only is true. Honestly, I don't know why (yet).
      # Anyway, let's cache the persisted version of the document's buffer for
      # later use on specific event, so that we always have a decent version of
      # "what's on the disk" to work with.
      def freeze_last_version(self, view):
          global on_disk
  
          file_name = view.file_name()
          # For some reasons, the on_activated hook gets fired on a ghost document
          # from time to time.
          if file_name:
              on_disk = codecs.open(file_name, "r", "utf-8").read().splitlines()
  
  
  # Public: Deletes the trailing spaces.
  class DeleteTrailingSpacesCommand(sublime_plugin.TextCommand):
      def run(self, edit):
          if max_size_exceeded(self.view):
              sublime.status_message("File is too big, trailing spaces handling disabled.")
              return
  
          deleted = delete_trailing_regions(self.view, edit)
  
          if deleted:
              if ts_settings.get("trailing_spaces_save_after_trim") \
              and not ts_settings.get("trailing_spaces_trim_on_save"):
                  sublime.set_timeout(lambda: self.save(self.view), 10)
  
              msg_parts = {"nbRegions": deleted,
                           "plural":    's' if deleted > 1 else ''}
              message = "Deleted %(nbRegions)s trailing spaces region%(plural)s" % msg_parts
          else:
              message = "No trailing spaces to delete!"
  
          sublime.status_message(message)
  
      def save(self, view):
          if view.file_name() is None:
              view.run_command('prompt_save_as')
          else:
              view.run_command('save')
  
  
  # ST3 features a plugin_loaded hook which is called when ST's API is ready.
  #
  # We must therefore call our init callback manually on ST2. It must be the last
  # thing in this plugin (thanks, beloved contributors!).
  if not int(sublime.version()) > 3000:
      plugin_loaded()
  