PageRenderTime 27ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/knipknap-SpiffGtkWidgets-59a713f/src/SpiffGtkWidgets/TextEditor/Features/ListIndent.py

#
Python | 287 lines | 199 code | 44 blank | 44 comment | 38 complexity | 93a654ed7c282c97dda1e3e7fe4fd1dd MD5 | raw file
Possible License(s): AGPL-3.0
  1. # -*- coding: UTF-8 -*-
  2. # Copyright (C) 2010 Samuel Abels
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License
  6. # version 3 as published by the Free Software Foundation.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. import pango, re
  16. from Feature import Feature
  17. bullet_point_re = re.compile(r'^(?:\d+\.|[\*\-])$')
  18. class ListIndent(Feature):
  19. """
  20. This class implements an auto-indent feature for the text buffer.
  21. """
  22. def __init__(self, buffer):
  23. """
  24. Constructor.
  25. buffer -- the associated TextBuffer
  26. """
  27. Feature.__init__(self, buffer)
  28. self.bullet_point = u'รข&#x20AC;?'
  29. self.lock_signals = None
  30. self.start_tag = buffer.create_tag('list-start',
  31. #foreground = 'lightblue',
  32. left_margin = 30,
  33. pixels_above_lines = 12)
  34. self.bullet_tag = buffer.create_tag('list-bullet',
  35. #background = 'orange',
  36. left_margin = 30)
  37. self.list_tag = buffer.create_tag('list',
  38. #underline = pango.UNDERLINE_SINGLE,
  39. left_margin = 30,
  40. pixels_above_lines = 3)
  41. buffer.connect_after('insert-text', self._on_buffer_insert_text_after)
  42. buffer.connect('delete-range', self._on_buffer_delete_range)
  43. buffer.connect('mark-set', self._on_buffer_mark_set)
  44. def _on_buffer_mark_set(self, buffer, iter, mark):
  45. if mark.get_name() != 'insert':
  46. return
  47. if not iter.has_tag(self.bullet_tag):
  48. return
  49. if self.lock_signals:
  50. return
  51. self.lock_signals = True
  52. next = iter.copy()
  53. next.forward_char()
  54. if next.has_tag(self.bullet_tag):
  55. iter.forward_to_tag_toggle(self.bullet_tag)
  56. else:
  57. iter.backward_to_tag_toggle(self.bullet_tag)
  58. iter.backward_char()
  59. buffer.place_cursor(iter)
  60. self.lock_signals = False
  61. def _to_list_start(self, iter):
  62. self._to_list_item_start(iter)
  63. while not iter.is_start():
  64. next = iter.copy()
  65. next.backward_line()
  66. if not self._at_bullet_point(next):
  67. break
  68. iter = next
  69. def _to_list_end(self, iter):
  70. while True:
  71. self._to_list_item_end(iter)
  72. iter.forward_char()
  73. if iter.is_end():
  74. return
  75. if not self._at_bullet_point(iter):
  76. iter.backward_char()
  77. return
  78. def _to_list_item_start(self, iter):
  79. iter.set_line(iter.get_line())
  80. def _to_list_item_end(self, iter):
  81. iter.forward_line()
  82. if iter.is_end():
  83. return
  84. iter.backward_char()
  85. def _at_bullet_point(self, iter):
  86. end = iter.copy()
  87. end.forward_find_char(lambda x, d: x == ' ')
  88. if end.is_end() or end.get_line() != iter.get_line():
  89. return False
  90. text = self.buffer.get_text(iter, end)
  91. if text == self.bullet_point or bullet_point_re.match(text):
  92. return True
  93. return False
  94. def _insert_inside_list(self, buffer, start, end):
  95. #print "INSERT INSIDE"
  96. insert_start = buffer.get_iter_at_offset(start)
  97. insert_end = buffer.get_iter_at_offset(end)
  98. text = buffer.get_text(insert_start, insert_end)
  99. item_start = insert_start.copy()
  100. self._to_list_item_start(item_start)
  101. list_end = insert_end.copy()
  102. self._to_list_end(list_end)
  103. bullet_end = item_start.copy()
  104. bullet_end.forward_to_tag_toggle(self.bullet_tag)
  105. bullet_point = buffer.get_text(item_start, bullet_end)
  106. #print "LIST", repr(buffer.get_text(item_start, list_end))
  107. #print "BULLET", repr(bullet_point)
  108. #print "TEXT", repr(text)
  109. buffer.apply_tag(self.list_tag, item_start, list_end)
  110. if text in ('\r', '\n'):
  111. next_char = list_end.copy()
  112. next_char.forward_char()
  113. buffer.remove_tag(self.list_tag, list_end, next_char)
  114. buffer.insert_with_tags(insert_end,
  115. bullet_point,
  116. self.bullet_tag, self.list_tag)
  117. def _insert_outside_list(self, buffer, insert_start_off, insert_end_off):
  118. # Move back to the beginning of the line in which text was inserted.
  119. #print "INSERT OUTSIDE"
  120. iter = buffer.get_iter_at_offset(insert_start_off)
  121. iter.set_line(iter.get_line())
  122. if not self._at_bullet_point(iter):
  123. return
  124. while iter.get_offset() < insert_end_off:
  125. if not self._at_bullet_point(iter):
  126. iter.forward_line()
  127. continue
  128. start_off = iter.get_offset()
  129. # Replace the item by a bullet point.
  130. start = iter.copy()
  131. next_char = iter.copy()
  132. next_char.forward_find_char(lambda x, d: x == ' ')
  133. buffer.delete(start, next_char)
  134. buffer.insert(start, self.bullet_point)
  135. # Mark the bullet point.
  136. start = buffer.get_iter_at_offset(start_off)
  137. end_off = start_off + len(self.bullet_point) + 1
  138. end = buffer.get_iter_at_offset(end_off)
  139. buffer.apply_tag(self.bullet_tag, start, end)
  140. iter = buffer.get_iter_at_offset(start_off)
  141. iter.forward_line()
  142. # Mark the list start.
  143. start = buffer.get_iter_at_offset(insert_start_off)
  144. self._to_list_start(start)
  145. end = start.copy()
  146. end.forward_to_tag_toggle(self.bullet_tag)
  147. buffer.apply_tag(self.start_tag, start, end)
  148. buffer.apply_tag(self.bullet_tag, start, end)
  149. # Mark the entire list.
  150. self._to_list_end(end)
  151. buffer.apply_tag(self.list_tag, start, end)
  152. def _on_buffer_insert_text_after(self, buffer, iter, text, length):
  153. if self.lock_signals:
  154. return
  155. self.lock_signals = True
  156. end = iter.get_offset()
  157. start = end - len(unicode(text))
  158. previous = buffer.get_iter_at_offset(start - 1)
  159. if previous.has_tag(self.list_tag):
  160. self._insert_inside_list(buffer, start, end)
  161. else:
  162. self._insert_outside_list(buffer, start, end)
  163. self.lock_signals = False
  164. def _delete_inside_list(self, start, end):
  165. #print "DELETE INSIDE"
  166. if not start.has_tag(self.list_tag) and not self._at_bullet_point(end):
  167. #print "start has no list tag"
  168. next_item_start = end.copy()
  169. self._to_list_item_end(next_item_start)
  170. next_item_start.forward_line()
  171. self.buffer.remove_tag(self.list_tag, end, next_item_start)
  172. if self._at_bullet_point(next_item_start):
  173. bullet_end = next_item_start.copy()
  174. bullet_end.forward_to_tag_toggle(self.bullet_tag)
  175. self.buffer.apply_tag(self.start_tag,
  176. next_item_start,
  177. bullet_end)
  178. return
  179. if start.has_tag(self.start_tag):
  180. #print "instart"
  181. # Delete the old start tag.
  182. start.backward_to_tag_toggle(self.start_tag)
  183. # The text of the first item is now no longer a list.
  184. item_end = end.copy()
  185. self._to_list_item_end(item_end)
  186. item_end.forward_char()
  187. self.buffer.remove_tag(self.list_tag, start, item_end)
  188. # The start of the next item (if any) is now the start
  189. # of the list.
  190. if not item_end.has_tag(self.bullet_tag):
  191. return
  192. bullet_end = item_end.copy()
  193. bullet_end.forward_to_tag_toggle(self.bullet_tag)
  194. self.buffer.apply_tag(self.start_tag, item_end, bullet_end)
  195. return
  196. if start.has_tag(self.bullet_tag):
  197. #print "start in bullet"
  198. start.backward_to_tag_toggle(self.bullet_tag)
  199. prev_char = start.copy()
  200. prev_char.backward_char()
  201. next_item = end.copy()
  202. self._to_list_item_end(next_item)
  203. next_item.forward_char()
  204. self.buffer.remove_tag(self.list_tag, prev_char, next_item)
  205. if next_item.has_tag(self.bullet_tag):
  206. bullet_end = next_item.copy()
  207. bullet_end.forward_to_tag_toggle(self.bullet_tag)
  208. self.buffer.apply_tag(self.start_tag, next_item, bullet_end)
  209. if end.has_tag(self.bullet_tag):
  210. #print "end in bullet"
  211. end.forward_to_tag_toggle(self.bullet_tag)
  212. end_line = end.copy()
  213. self._to_list_item_start(end_line)
  214. if end_line.has_tag(self.bullet_tag):
  215. return
  216. next = end.copy()
  217. self._to_list_item_end(next)
  218. if not next.is_end():
  219. next.backward_char()
  220. self.buffer.apply_tag(self.list_tag, end, next)
  221. def _delete_outside_list(self, start, end):
  222. next = start.copy()
  223. next.forward_char()
  224. text = self.buffer.get_text(start, next)
  225. if text not in ('\r', '\n'):
  226. return
  227. previous = start.copy()
  228. previous.backward_char()
  229. if not previous.has_tag(self.list_tag):
  230. return
  231. item_end = end.copy()
  232. self._to_list_item_end(item_end)
  233. self.buffer.apply_tag(self.list_tag, end, item_end)
  234. def _on_buffer_delete_range(self, buffer, start, end):
  235. if self.lock_signals:
  236. return
  237. self.lock_signals = True
  238. if start.has_tag(self.list_tag) or end.has_tag(self.list_tag):
  239. self._delete_inside_list(start, end)
  240. else:
  241. self._delete_outside_list(start, end)
  242. self.lock_signals = False