history.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from rope.base import exceptions, change, taskhandle
  2. class History(object):
  3. """A class that holds project history"""
  4. def __init__(self, project, maxundos=None):
  5. self.project = project
  6. self._undo_list = []
  7. self._redo_list = []
  8. self._maxundos = maxundos
  9. self._load_history()
  10. self.project.data_files.add_write_hook(self.write)
  11. self.current_change = None
  12. def _load_history(self):
  13. if self.save:
  14. result = self.project.data_files.read_data(
  15. 'history', compress=self.compress, import_=True)
  16. if result is not None:
  17. to_change = change.DataToChange(self.project)
  18. for data in result[0]:
  19. self._undo_list.append(to_change(data))
  20. for data in result[1]:
  21. self._redo_list.append(to_change(data))
  22. def do(self, changes, task_handle=taskhandle.NullTaskHandle()):
  23. """Perform the change and add it to the `self.undo_list`
  24. Note that uninteresting changes (changes to ignored files)
  25. will not be appended to `self.undo_list`.
  26. """
  27. try:
  28. self.current_change = changes
  29. changes.do(change.create_job_set(task_handle, changes))
  30. finally:
  31. self.current_change = None
  32. if self._is_change_interesting(changes):
  33. self.undo_list.append(changes)
  34. self._remove_extra_items()
  35. del self.redo_list[:]
  36. def _remove_extra_items(self):
  37. if len(self.undo_list) > self.max_undos:
  38. del self.undo_list[0:len(self.undo_list) - self.max_undos]
  39. def _is_change_interesting(self, changes):
  40. for resource in changes.get_changed_resources():
  41. if not self.project.is_ignored(resource):
  42. return True
  43. return False
  44. def undo(self, change=None, drop=False,
  45. task_handle=taskhandle.NullTaskHandle()):
  46. """Redo done changes from the history
  47. When `change` is `None`, the last done change will be undone.
  48. If change is not `None` it should be an item from
  49. `self.undo_list`; this change and all changes that depend on
  50. it will be undone. In both cases the list of undone changes
  51. will be returned.
  52. If `drop` is `True`, the undone change will not be appended to
  53. the redo list.
  54. """
  55. if not self._undo_list:
  56. raise exceptions.HistoryError('Undo list is empty')
  57. if change is None:
  58. change = self.undo_list[-1]
  59. dependencies = self._find_dependencies(self.undo_list, change)
  60. self._move_front(self.undo_list, dependencies)
  61. self._perform_undos(len(dependencies), task_handle)
  62. result = self.redo_list[-len(dependencies):]
  63. if drop:
  64. del self.redo_list[-len(dependencies):]
  65. return result
  66. def redo(self, change=None, task_handle=taskhandle.NullTaskHandle()):
  67. """Redo undone changes from the history
  68. When `change` is `None`, the last undone change will be
  69. redone. If change is not `None` it should be an item from
  70. `self.redo_list`; this change and all changes that depend on
  71. it will be redone. In both cases the list of redone changes
  72. will be returned.
  73. """
  74. if not self.redo_list:
  75. raise exceptions.HistoryError('Redo list is empty')
  76. if change is None:
  77. change = self.redo_list[-1]
  78. dependencies = self._find_dependencies(self.redo_list, change)
  79. self._move_front(self.redo_list, dependencies)
  80. self._perform_redos(len(dependencies), task_handle)
  81. return self.undo_list[-len(dependencies):]
  82. def _move_front(self, change_list, changes):
  83. for change in changes:
  84. change_list.remove(change)
  85. change_list.append(change)
  86. def _find_dependencies(self, change_list, change):
  87. index = change_list.index(change)
  88. return _FindChangeDependencies(change_list[index:])()
  89. def _perform_undos(self, count, task_handle):
  90. for i in range(count):
  91. self.current_change = self.undo_list[-1]
  92. try:
  93. job_set = change.create_job_set(task_handle,
  94. self.current_change)
  95. self.current_change.undo(job_set)
  96. finally:
  97. self.current_change = None
  98. self.redo_list.append(self.undo_list.pop())
  99. def _perform_redos(self, count, task_handle):
  100. for i in range(count):
  101. self.current_change = self.redo_list[-1]
  102. try:
  103. job_set = change.create_job_set(task_handle,
  104. self.current_change)
  105. self.current_change.do(job_set)
  106. finally:
  107. self.current_change = None
  108. self.undo_list.append(self.redo_list.pop())
  109. def contents_before_current_change(self, file):
  110. if self.current_change is None:
  111. return None
  112. result = self._search_for_change_contents([self.current_change], file)
  113. if result is not None:
  114. return result
  115. if file.exists() and not file.is_folder():
  116. return file.read()
  117. else:
  118. return None
  119. def _search_for_change_contents(self, change_list, file):
  120. for change_ in reversed(change_list):
  121. if isinstance(change_, change.ChangeSet):
  122. result = self._search_for_change_contents(change_.changes,
  123. file)
  124. if result is not None:
  125. return result
  126. if isinstance(change_, change.ChangeContents) and \
  127. change_.resource == file:
  128. return change_.old_contents
  129. def write(self):
  130. if self.save:
  131. data = []
  132. to_data = change.ChangeToData()
  133. self._remove_extra_items()
  134. data.append([to_data(change_) for change_ in self.undo_list])
  135. data.append([to_data(change_) for change_ in self.redo_list])
  136. self.project.data_files.write_data('history', data,
  137. compress=self.compress)
  138. def get_file_undo_list(self, resource):
  139. result = []
  140. for change in self.undo_list:
  141. if resource in change.get_changed_resources():
  142. result.append(change)
  143. return result
  144. def __str__(self):
  145. return 'History holds %s changes in memory' % \
  146. (len(self.undo_list) + len(self.redo_list))
  147. undo_list = property(lambda self: self._undo_list)
  148. redo_list = property(lambda self: self._redo_list)
  149. @property
  150. def tobe_undone(self):
  151. """The last done change if available, `None` otherwise"""
  152. if self.undo_list:
  153. return self.undo_list[-1]
  154. @property
  155. def tobe_redone(self):
  156. """The last undone change if available, `None` otherwise"""
  157. if self.redo_list:
  158. return self.redo_list[-1]
  159. @property
  160. def max_undos(self):
  161. if self._maxundos is None:
  162. return self.project.prefs.get('max_history_items', 100)
  163. else:
  164. return self._maxundos
  165. @property
  166. def save(self):
  167. return self.project.prefs.get('save_history', False)
  168. @property
  169. def compress(self):
  170. return self.project.prefs.get('compress_history', False)
  171. def clear(self):
  172. """Forget all undo and redo information"""
  173. del self.undo_list[:]
  174. del self.redo_list[:]
  175. class _FindChangeDependencies(object):
  176. def __init__(self, change_list):
  177. self.change = change_list[0]
  178. self.change_list = change_list
  179. self.changed_resources = set(self.change.get_changed_resources())
  180. def __call__(self):
  181. result = [self.change]
  182. for change in self.change_list[1:]:
  183. if self._depends_on(change, result):
  184. result.append(change)
  185. self.changed_resources.update(change.get_changed_resources())
  186. return result
  187. def _depends_on(self, changes, result):
  188. for resource in changes.get_changed_resources():
  189. if resource is None:
  190. continue
  191. if resource in self.changed_resources:
  192. return True
  193. for changed in self.changed_resources:
  194. if resource.is_folder() and resource.contains(changed):
  195. return True
  196. if changed.is_folder() and changed.contains(resource):
  197. return True
  198. return False