Path: blob/master/elisp/emacs-for-python/rope-dist/rope/base/history.py
1440 views
from rope.base import exceptions, change, taskhandle123class History(object):4"""A class that holds project history"""56def __init__(self, project, maxundos=None):7self.project = project8self._undo_list = []9self._redo_list = []10self._maxundos = maxundos11self._load_history()12self.project.data_files.add_write_hook(self.write)13self.current_change = None1415def _load_history(self):16if self.save:17result = self.project.data_files.read_data(18'history', compress=self.compress, import_=True)19if result is not None:20to_change = change.DataToChange(self.project)21for data in result[0]:22self._undo_list.append(to_change(data))23for data in result[1]:24self._redo_list.append(to_change(data))2526def do(self, changes, task_handle=taskhandle.NullTaskHandle()):27"""Perform the change and add it to the `self.undo_list`2829Note that uninteresting changes (changes to ignored files)30will not be appended to `self.undo_list`.3132"""33try:34self.current_change = changes35changes.do(change.create_job_set(task_handle, changes))36finally:37self.current_change = None38if self._is_change_interesting(changes):39self.undo_list.append(changes)40self._remove_extra_items()41del self.redo_list[:]4243def _remove_extra_items(self):44if len(self.undo_list) > self.max_undos:45del self.undo_list[0:len(self.undo_list) - self.max_undos]4647def _is_change_interesting(self, changes):48for resource in changes.get_changed_resources():49if not self.project.is_ignored(resource):50return True51return False5253def undo(self, change=None, drop=False,54task_handle=taskhandle.NullTaskHandle()):55"""Redo done changes from the history5657When `change` is `None`, the last done change will be undone.58If change is not `None` it should be an item from59`self.undo_list`; this change and all changes that depend on60it will be undone. In both cases the list of undone changes61will be returned.6263If `drop` is `True`, the undone change will not be appended to64the redo list.6566"""67if not self._undo_list:68raise exceptions.HistoryError('Undo list is empty')69if change is None:70change = self.undo_list[-1]71dependencies = self._find_dependencies(self.undo_list, change)72self._move_front(self.undo_list, dependencies)73self._perform_undos(len(dependencies), task_handle)74result = self.redo_list[-len(dependencies):]75if drop:76del self.redo_list[-len(dependencies):]77return result7879def redo(self, change=None, task_handle=taskhandle.NullTaskHandle()):80"""Redo undone changes from the history8182When `change` is `None`, the last undone change will be83redone. If change is not `None` it should be an item from84`self.redo_list`; this change and all changes that depend on85it will be redone. In both cases the list of redone changes86will be returned.8788"""89if not self.redo_list:90raise exceptions.HistoryError('Redo list is empty')91if change is None:92change = self.redo_list[-1]93dependencies = self._find_dependencies(self.redo_list, change)94self._move_front(self.redo_list, dependencies)95self._perform_redos(len(dependencies), task_handle)96return self.undo_list[-len(dependencies):]9798def _move_front(self, change_list, changes):99for change in changes:100change_list.remove(change)101change_list.append(change)102103def _find_dependencies(self, change_list, change):104index = change_list.index(change)105return _FindChangeDependencies(change_list[index:])()106107def _perform_undos(self, count, task_handle):108for i in range(count):109self.current_change = self.undo_list[-1]110try:111job_set = change.create_job_set(task_handle,112self.current_change)113self.current_change.undo(job_set)114finally:115self.current_change = None116self.redo_list.append(self.undo_list.pop())117118def _perform_redos(self, count, task_handle):119for i in range(count):120self.current_change = self.redo_list[-1]121try:122job_set = change.create_job_set(task_handle,123self.current_change)124self.current_change.do(job_set)125finally:126self.current_change = None127self.undo_list.append(self.redo_list.pop())128129def contents_before_current_change(self, file):130if self.current_change is None:131return None132result = self._search_for_change_contents([self.current_change], file)133if result is not None:134return result135if file.exists() and not file.is_folder():136return file.read()137else:138return None139140def _search_for_change_contents(self, change_list, file):141for change_ in reversed(change_list):142if isinstance(change_, change.ChangeSet):143result = self._search_for_change_contents(change_.changes,144file)145if result is not None:146return result147if isinstance(change_, change.ChangeContents) and \148change_.resource == file:149return change_.old_contents150151def write(self):152if self.save:153data = []154to_data = change.ChangeToData()155self._remove_extra_items()156data.append([to_data(change_) for change_ in self.undo_list])157data.append([to_data(change_) for change_ in self.redo_list])158self.project.data_files.write_data('history', data,159compress=self.compress)160161def get_file_undo_list(self, resource):162result = []163for change in self.undo_list:164if resource in change.get_changed_resources():165result.append(change)166return result167168def __str__(self):169return 'History holds %s changes in memory' % \170(len(self.undo_list) + len(self.redo_list))171172undo_list = property(lambda self: self._undo_list)173redo_list = property(lambda self: self._redo_list)174175@property176def tobe_undone(self):177"""The last done change if available, `None` otherwise"""178if self.undo_list:179return self.undo_list[-1]180181@property182def tobe_redone(self):183"""The last undone change if available, `None` otherwise"""184if self.redo_list:185return self.redo_list[-1]186187@property188def max_undos(self):189if self._maxundos is None:190return self.project.prefs.get('max_history_items', 100)191else:192return self._maxundos193194@property195def save(self):196return self.project.prefs.get('save_history', False)197198@property199def compress(self):200return self.project.prefs.get('compress_history', False)201202def clear(self):203"""Forget all undo and redo information"""204del self.undo_list[:]205del self.redo_list[:]206207208class _FindChangeDependencies(object):209210def __init__(self, change_list):211self.change = change_list[0]212self.change_list = change_list213self.changed_resources = set(self.change.get_changed_resources())214215def __call__(self):216result = [self.change]217for change in self.change_list[1:]:218if self._depends_on(change, result):219result.append(change)220self.changed_resources.update(change.get_changed_resources())221return result222223def _depends_on(self, changes, result):224for resource in changes.get_changed_resources():225if resource is None:226continue227if resource in self.changed_resources:228return True229for changed in self.changed_resources:230if resource.is_folder() and resource.contains(changed):231return True232if changed.is_folder() and changed.contains(resource):233return True234return False235236237