| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- 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
|