ReplaceDialog.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. from Tkinter import *
  2. from idlelib import SearchEngine
  3. from idlelib.SearchDialogBase import SearchDialogBase
  4. import re
  5. def replace(text):
  6. root = text._root()
  7. engine = SearchEngine.get(root)
  8. if not hasattr(engine, "_replacedialog"):
  9. engine._replacedialog = ReplaceDialog(root, engine)
  10. dialog = engine._replacedialog
  11. dialog.open(text)
  12. class ReplaceDialog(SearchDialogBase):
  13. title = "Replace Dialog"
  14. icon = "Replace"
  15. def __init__(self, root, engine):
  16. SearchDialogBase.__init__(self, root, engine)
  17. self.replvar = StringVar(root)
  18. def open(self, text):
  19. SearchDialogBase.open(self, text)
  20. try:
  21. first = text.index("sel.first")
  22. except TclError:
  23. first = None
  24. try:
  25. last = text.index("sel.last")
  26. except TclError:
  27. last = None
  28. first = first or text.index("insert")
  29. last = last or first
  30. self.show_hit(first, last)
  31. self.ok = 1
  32. def create_entries(self):
  33. SearchDialogBase.create_entries(self)
  34. self.replent = self.make_entry("Replace with:", self.replvar)[0]
  35. def create_command_buttons(self):
  36. SearchDialogBase.create_command_buttons(self)
  37. self.make_button("Find", self.find_it)
  38. self.make_button("Replace", self.replace_it)
  39. self.make_button("Replace+Find", self.default_command, 1)
  40. self.make_button("Replace All", self.replace_all)
  41. def find_it(self, event=None):
  42. self.do_find(0)
  43. def replace_it(self, event=None):
  44. if self.do_find(self.ok):
  45. self.do_replace()
  46. def default_command(self, event=None):
  47. if self.do_find(self.ok):
  48. if self.do_replace(): # Only find next match if replace succeeded.
  49. # A bad re can cause a it to fail.
  50. self.do_find(0)
  51. def _replace_expand(self, m, repl):
  52. """ Helper function for expanding a regular expression
  53. in the replace field, if needed. """
  54. if self.engine.isre():
  55. try:
  56. new = m.expand(repl)
  57. except re.error:
  58. self.engine.report_error(repl, 'Invalid Replace Expression')
  59. new = None
  60. else:
  61. new = repl
  62. return new
  63. def replace_all(self, event=None):
  64. prog = self.engine.getprog()
  65. if not prog:
  66. return
  67. repl = self.replvar.get()
  68. text = self.text
  69. res = self.engine.search_text(text, prog)
  70. if not res:
  71. text.bell()
  72. return
  73. text.tag_remove("sel", "1.0", "end")
  74. text.tag_remove("hit", "1.0", "end")
  75. line = res[0]
  76. col = res[1].start()
  77. if self.engine.iswrap():
  78. line = 1
  79. col = 0
  80. ok = 1
  81. first = last = None
  82. # XXX ought to replace circular instead of top-to-bottom when wrapping
  83. text.undo_block_start()
  84. while 1:
  85. res = self.engine.search_forward(text, prog, line, col, 0, ok)
  86. if not res:
  87. break
  88. line, m = res
  89. chars = text.get("%d.0" % line, "%d.0" % (line+1))
  90. orig = m.group()
  91. new = self._replace_expand(m, repl)
  92. if new is None:
  93. break
  94. i, j = m.span()
  95. first = "%d.%d" % (line, i)
  96. last = "%d.%d" % (line, j)
  97. if new == orig:
  98. text.mark_set("insert", last)
  99. else:
  100. text.mark_set("insert", first)
  101. if first != last:
  102. text.delete(first, last)
  103. if new:
  104. text.insert(first, new)
  105. col = i + len(new)
  106. ok = 0
  107. text.undo_block_stop()
  108. if first and last:
  109. self.show_hit(first, last)
  110. self.close()
  111. def do_find(self, ok=0):
  112. if not self.engine.getprog():
  113. return False
  114. text = self.text
  115. res = self.engine.search_text(text, None, ok)
  116. if not res:
  117. text.bell()
  118. return False
  119. line, m = res
  120. i, j = m.span()
  121. first = "%d.%d" % (line, i)
  122. last = "%d.%d" % (line, j)
  123. self.show_hit(first, last)
  124. self.ok = 1
  125. return True
  126. def do_replace(self):
  127. prog = self.engine.getprog()
  128. if not prog:
  129. return False
  130. text = self.text
  131. try:
  132. first = pos = text.index("sel.first")
  133. last = text.index("sel.last")
  134. except TclError:
  135. pos = None
  136. if not pos:
  137. first = last = pos = text.index("insert")
  138. line, col = SearchEngine.get_line_col(pos)
  139. chars = text.get("%d.0" % line, "%d.0" % (line+1))
  140. m = prog.match(chars, col)
  141. if not prog:
  142. return False
  143. new = self._replace_expand(m, self.replvar.get())
  144. if new is None:
  145. return False
  146. text.mark_set("insert", first)
  147. text.undo_block_start()
  148. if m.group():
  149. text.delete(first, last)
  150. if new:
  151. text.insert(first, new)
  152. text.undo_block_stop()
  153. self.show_hit(first, text.index("insert"))
  154. self.ok = 0
  155. return True
  156. def show_hit(self, first, last):
  157. text = self.text
  158. text.mark_set("insert", first)
  159. text.tag_remove("sel", "1.0", "end")
  160. text.tag_add("sel", first, last)
  161. text.tag_remove("hit", "1.0", "end")
  162. if first == last:
  163. text.tag_add("hit", first)
  164. else:
  165. text.tag_add("hit", first, last)
  166. text.see("insert")
  167. text.update_idletasks()
  168. def close(self, event=None):
  169. SearchDialogBase.close(self, event)
  170. self.text.tag_remove("hit", "1.0", "end")
  171. def _replace_dialog(parent):
  172. root = Tk()
  173. root.title("Test ReplaceDialog")
  174. width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
  175. root.geometry("+%d+%d"%(x, y + 150))
  176. # mock undo delegator methods
  177. def undo_block_start():
  178. pass
  179. def undo_block_stop():
  180. pass
  181. text = Text(root)
  182. text.undo_block_start = undo_block_start
  183. text.undo_block_stop = undo_block_stop
  184. text.pack()
  185. text.insert("insert","This is a sample string.\n"*10)
  186. def show_replace():
  187. text.tag_add(SEL, "1.0", END)
  188. replace(text)
  189. text.tag_remove(SEL, "1.0", END)
  190. button = Button(root, text="Replace", command=show_replace)
  191. button.pack()
  192. if __name__ == '__main__':
  193. from idlelib.idle_test.htest import run
  194. run(_replace_dialog)