text: from rope.base import exceptions, change, taskhandle
  
  
  class History(object):
      """A class that holds project history"""
  
      def __init__(self, project, maxundos=None):
          self.project = project
          self._undo_list = []
          self._redo_list = []
          self._maxundos = maxundos
          self._load_history()
          self.project.data_files.add_write_hook(self.write)
          self.current_change = None
  
      def _load_history(self):
          if self.save:
              result = self.project.data_files.read_data(
                  "history", compress=self.compress, import_=True
              )
              if result is not None:
                  to_change = change.DataToChange(self.project)
                  for data in result[0]:
                      self._undo_list.append(to_change(data))
                  for data in result[1]:
                      self._redo_list.append(to_change(data))
  
      def do(self, changes, task_handle=taskhandle.NullTaskHandle()):
          """Perform the change and add it to the `self.undo_list`
  
          Note that uninteresting changes (changes to ignored files)
          will not be appended to `self.undo_list`.
  
          """
          try:
              self.current_change = changes
              changes.do(change.create_job_set(task_handle, changes))
          finally:
              self.current_change = None
          if self._is_change_interesting(changes):
              self.undo_list.append(changes)
              self._remove_extra_items()
          del self.redo_list[:]
  
      def _remove_extra_items(self):
          if len(self.undo_list) > self.max_undos:
              del self.undo_list[0 : len(self.undo_list) - self.max_undos]
  
      def _is_change_interesting(self, changes):
          for resource in changes.get_changed_resources():
              if not self.project.is_ignored(resource):
                  return True
          return False
  
      def undo(self, change=None, drop=False, task_handle=taskhandle.NullTaskHandle()):
          """Redo done changes from the history
  
          When `change` is `None`, the last done change will be undone.
          If change is not `None` it should be an item from
          `self.undo_list`; this change and all changes that depend on
          it will be undone.  In both cases the list of undone changes
          will be returned.
  
          If `drop` is `True`, the undone change will not be appended to
          the redo list.
  
          """
          if not self._undo_list:
              raise exceptions.HistoryError("Undo list is empty")
          if change is None:
              change = self.undo_list[-1]
          dependencies = self._find_dependencies(self.undo_list, change)
          self._move_front(self.undo_list, dependencies)
          self._perform_undos(len(dependencies), task_handle)
          result = self.redo_list[-len(dependencies) :]
          if drop:
              del self.redo_list[-len(dependencies) :]
          return result
  
      def redo(self, change=None, task_handle=taskhandle.NullTaskHandle()):
          """Redo undone changes from the history
  
          When `change` is `None`, the last undone change will be
          redone.  If change is not `None` it should be an item from
          `self.redo_list`; this change and all changes that depend on
          it will be redone.  In both cases the list of redone changes
          will be returned.
  
          """
          if not self.redo_list:
              raise exceptions.HistoryError("Redo list is empty")
          if change is None:
              change = self.redo_list[-1]
          dependencies = self._find_dependencies(self.redo_list, change)
          self._move_front(self.redo_list, dependencies)
          self._perform_redos(len(dependencies), task_handle)
          return self.undo_list[-len(dependencies) :]
  
      def _move_front(self, change_list, changes):
          for change in changes:
              change_list.remove(change)
              change_list.append(change)
  
      def _find_dependencies(self, change_list, change):
          index = change_list.index(change)
          return _FindChangeDependencies(change_list[index:])()
  
      def _perform_undos(self, count, task_handle):
          for i in range(count):
              self.current_change = self.undo_list[-1]
              try:
                  job_set = change.create_job_set(task_handle, self.current_change)
                  self.current_change.undo(job_set)
              finally:
                  self.current_change = None
              self.redo_list.append(self.undo_list.pop())
  
      def _perform_redos(self, count, task_handle):
          for i in range(count):
              self.current_change = self.redo_list[-1]
              try:
                  job_set = change.create_job_set(task_handle, self.current_change)
                  self.current_change.do(job_set)
              finally:
                  self.current_change = None
              self.undo_list.append(self.redo_list.pop())
  
      def contents_before_current_change(self, file):
          if self.current_change is None:
              return None
          result = self._search_for_change_contents([self.current_change], file)
          if result is not None:
              return result
          if file.exists() and not file.is_folder():
              return file.read()
          else:
              return None
  
      def _search_for_change_contents(self, change_list, file):
          for change_ in reversed(change_list):
              if isinstance(change_, change.ChangeSet):
                  result = self._search_for_change_contents(change_.changes, file)
                  if result is not None:
                      return result
              if isinstance(change_, change.ChangeContents) and change_.resource == file:
                  return change_.old_contents
  
      def write(self):
          if self.save:
              data = []
              to_data = change.ChangeToData()
              self._remove_extra_items()
              data.append([to_data(change_) for change_ in self.undo_list])
              data.append([to_data(change_) for change_ in self.redo_list])
              self.project.data_files.write_data("history", data, compress=self.compress)
  
      def get_file_undo_list(self, resource):
          result = []
          for change in self.undo_list:
              if resource in change.get_changed_resources():
                  result.append(change)
          return result
  
      def __str__(self):
          return "History holds %s changes in memory" % (
              len(self.undo_list) + len(self.redo_list)
          )
  
      undo_list = property(lambda self: self._undo_list)
      redo_list = property(lambda self: self._redo_list)
  
      @property
      def tobe_undone(self):
          """The last done change if available, `None` otherwise"""
          if self.undo_list:
              return self.undo_list[-1]
  
      @property
      def tobe_redone(self):
          """The last undone change if available, `None` otherwise"""
          if self.redo_list:
              return self.redo_list[-1]
  
      @property
      def max_undos(self):
          if self._maxundos is None:
              return self.project.prefs.get("max_history_items", 100)
          else:
              return self._maxundos
  
      @property
      def save(self):
          return self.project.prefs.get("save_history", False)
  
      @property
      def compress(self):
          return self.project.prefs.get("compress_history", False)
  
      def clear(self):
          """Forget all undo and redo information"""
          del self.undo_list[:]
          del self.redo_list[:]
  
  
  class _FindChangeDependencies(object):
      def __init__(self, change_list):
          self.change = change_list[0]
          self.change_list = change_list
          self.changed_resources = set(self.change.get_changed_resources())
  
      def __call__(self):
          result = [self.change]
          for change in self.change_list[1:]:
              if self._depends_on(change, result):
                  result.append(change)
                  self.changed_resources.update(change.get_changed_resources())
          return result
  
      def _depends_on(self, changes, result):
          for resource in changes.get_changed_resources():
              if resource is None:
                  continue
              if resource in self.changed_resources:
                  return True
              for changed in self.changed_resources:
                  if resource.is_folder() and resource.contains(changed):
                      return True
                  if changed.is_folder() and changed.contains(resource):
                      return True
          return False
  