logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

replace.py (10089B)


  1. """Replace dialog for IDLE. Inherits SearchDialogBase for GUI.
  2. Uses idlelib.searchengine.SearchEngine for search capability.
  3. Defines various replace related functions like replace, replace all,
  4. and replace+find.
  5. """
  6. import re
  7. from tkinter import StringVar, TclError
  8. from idlelib.searchbase import SearchDialogBase
  9. from idlelib import searchengine
  10. def replace(text, insert_tags=None):
  11. """Create or reuse a singleton ReplaceDialog instance.
  12. The singleton dialog saves user entries and preferences
  13. across instances.
  14. Args:
  15. text: Text widget containing the text to be searched.
  16. """
  17. root = text._root()
  18. engine = searchengine.get(root)
  19. if not hasattr(engine, "_replacedialog"):
  20. engine._replacedialog = ReplaceDialog(root, engine)
  21. dialog = engine._replacedialog
  22. dialog.open(text, insert_tags=insert_tags)
  23. class ReplaceDialog(SearchDialogBase):
  24. "Dialog for finding and replacing a pattern in text."
  25. title = "Replace Dialog"
  26. icon = "Replace"
  27. def __init__(self, root, engine):
  28. """Create search dialog for finding and replacing text.
  29. Uses SearchDialogBase as the basis for the GUI and a
  30. searchengine instance to prepare the search.
  31. Attributes:
  32. replvar: StringVar containing 'Replace with:' value.
  33. replent: Entry widget for replvar. Created in
  34. create_entries().
  35. ok: Boolean used in searchengine.search_text to indicate
  36. whether the search includes the selection.
  37. """
  38. super().__init__(root, engine)
  39. self.replvar = StringVar(root)
  40. self.insert_tags = None
  41. def open(self, text, insert_tags=None):
  42. """Make dialog visible on top of others and ready to use.
  43. Also, highlight the currently selected text and set the
  44. search to include the current selection (self.ok).
  45. Args:
  46. text: Text widget being searched.
  47. """
  48. SearchDialogBase.open(self, text)
  49. try:
  50. first = text.index("sel.first")
  51. except TclError:
  52. first = None
  53. try:
  54. last = text.index("sel.last")
  55. except TclError:
  56. last = None
  57. first = first or text.index("insert")
  58. last = last or first
  59. self.show_hit(first, last)
  60. self.ok = True
  61. self.insert_tags = insert_tags
  62. def create_entries(self):
  63. "Create base and additional label and text entry widgets."
  64. SearchDialogBase.create_entries(self)
  65. self.replent = self.make_entry("Replace with:", self.replvar)[0]
  66. def create_command_buttons(self):
  67. """Create base and additional command buttons.
  68. The additional buttons are for Find, Replace,
  69. Replace+Find, and Replace All.
  70. """
  71. SearchDialogBase.create_command_buttons(self)
  72. self.make_button("Find", self.find_it)
  73. self.make_button("Replace", self.replace_it)
  74. self.make_button("Replace+Find", self.default_command, isdef=True)
  75. self.make_button("Replace All", self.replace_all)
  76. def find_it(self, event=None):
  77. "Handle the Find button."
  78. self.do_find(False)
  79. def replace_it(self, event=None):
  80. """Handle the Replace button.
  81. If the find is successful, then perform replace.
  82. """
  83. if self.do_find(self.ok):
  84. self.do_replace()
  85. def default_command(self, event=None):
  86. """Handle the Replace+Find button as the default command.
  87. First performs a replace and then, if the replace was
  88. successful, a find next.
  89. """
  90. if self.do_find(self.ok):
  91. if self.do_replace(): # Only find next match if replace succeeded.
  92. # A bad re can cause it to fail.
  93. self.do_find(False)
  94. def _replace_expand(self, m, repl):
  95. "Expand replacement text if regular expression."
  96. if self.engine.isre():
  97. try:
  98. new = m.expand(repl)
  99. except re.error:
  100. self.engine.report_error(repl, 'Invalid Replace Expression')
  101. new = None
  102. else:
  103. new = repl
  104. return new
  105. def replace_all(self, event=None):
  106. """Handle the Replace All button.
  107. Search text for occurrences of the Find value and replace
  108. each of them. The 'wrap around' value controls the start
  109. point for searching. If wrap isn't set, then the searching
  110. starts at the first occurrence after the current selection;
  111. if wrap is set, the replacement starts at the first line.
  112. The replacement is always done top-to-bottom in the text.
  113. """
  114. prog = self.engine.getprog()
  115. if not prog:
  116. return
  117. repl = self.replvar.get()
  118. text = self.text
  119. res = self.engine.search_text(text, prog)
  120. if not res:
  121. self.bell()
  122. return
  123. text.tag_remove("sel", "1.0", "end")
  124. text.tag_remove("hit", "1.0", "end")
  125. line = res[0]
  126. col = res[1].start()
  127. if self.engine.iswrap():
  128. line = 1
  129. col = 0
  130. ok = True
  131. first = last = None
  132. # XXX ought to replace circular instead of top-to-bottom when wrapping
  133. text.undo_block_start()
  134. while True:
  135. res = self.engine.search_forward(text, prog, line, col,
  136. wrap=False, ok=ok)
  137. if not res:
  138. break
  139. line, m = res
  140. chars = text.get("%d.0" % line, "%d.0" % (line+1))
  141. orig = m.group()
  142. new = self._replace_expand(m, repl)
  143. if new is None:
  144. break
  145. i, j = m.span()
  146. first = "%d.%d" % (line, i)
  147. last = "%d.%d" % (line, j)
  148. if new == orig:
  149. text.mark_set("insert", last)
  150. else:
  151. text.mark_set("insert", first)
  152. if first != last:
  153. text.delete(first, last)
  154. if new:
  155. text.insert(first, new, self.insert_tags)
  156. col = i + len(new)
  157. ok = False
  158. text.undo_block_stop()
  159. if first and last:
  160. self.show_hit(first, last)
  161. self.close()
  162. def do_find(self, ok=False):
  163. """Search for and highlight next occurrence of pattern in text.
  164. No text replacement is done with this option.
  165. """
  166. if not self.engine.getprog():
  167. return False
  168. text = self.text
  169. res = self.engine.search_text(text, None, ok)
  170. if not res:
  171. self.bell()
  172. return False
  173. line, m = res
  174. i, j = m.span()
  175. first = "%d.%d" % (line, i)
  176. last = "%d.%d" % (line, j)
  177. self.show_hit(first, last)
  178. self.ok = True
  179. return True
  180. def do_replace(self):
  181. "Replace search pattern in text with replacement value."
  182. prog = self.engine.getprog()
  183. if not prog:
  184. return False
  185. text = self.text
  186. try:
  187. first = pos = text.index("sel.first")
  188. last = text.index("sel.last")
  189. except TclError:
  190. pos = None
  191. if not pos:
  192. first = last = pos = text.index("insert")
  193. line, col = searchengine.get_line_col(pos)
  194. chars = text.get("%d.0" % line, "%d.0" % (line+1))
  195. m = prog.match(chars, col)
  196. if not prog:
  197. return False
  198. new = self._replace_expand(m, self.replvar.get())
  199. if new is None:
  200. return False
  201. text.mark_set("insert", first)
  202. text.undo_block_start()
  203. if m.group():
  204. text.delete(first, last)
  205. if new:
  206. text.insert(first, new, self.insert_tags)
  207. text.undo_block_stop()
  208. self.show_hit(first, text.index("insert"))
  209. self.ok = False
  210. return True
  211. def show_hit(self, first, last):
  212. """Highlight text between first and last indices.
  213. Text is highlighted via the 'hit' tag and the marked
  214. section is brought into view.
  215. The colors from the 'hit' tag aren't currently shown
  216. when the text is displayed. This is due to the 'sel'
  217. tag being added first, so the colors in the 'sel'
  218. config are seen instead of the colors for 'hit'.
  219. """
  220. text = self.text
  221. text.mark_set("insert", first)
  222. text.tag_remove("sel", "1.0", "end")
  223. text.tag_add("sel", first, last)
  224. text.tag_remove("hit", "1.0", "end")
  225. if first == last:
  226. text.tag_add("hit", first)
  227. else:
  228. text.tag_add("hit", first, last)
  229. text.see("insert")
  230. text.update_idletasks()
  231. def close(self, event=None):
  232. "Close the dialog and remove hit tags."
  233. SearchDialogBase.close(self, event)
  234. self.text.tag_remove("hit", "1.0", "end")
  235. self.insert_tags = None
  236. def _replace_dialog(parent): # htest #
  237. from tkinter import Toplevel, Text, END, SEL
  238. from tkinter.ttk import Frame, Button
  239. top = Toplevel(parent)
  240. top.title("Test ReplaceDialog")
  241. x, y = map(int, parent.geometry().split('+')[1:])
  242. top.geometry("+%d+%d" % (x, y + 175))
  243. # mock undo delegator methods
  244. def undo_block_start():
  245. pass
  246. def undo_block_stop():
  247. pass
  248. frame = Frame(top)
  249. frame.pack()
  250. text = Text(frame, inactiveselectbackground='gray')
  251. text.undo_block_start = undo_block_start
  252. text.undo_block_stop = undo_block_stop
  253. text.pack()
  254. text.insert("insert","This is a sample sTring\nPlus MORE.")
  255. text.focus_set()
  256. def show_replace():
  257. text.tag_add(SEL, "1.0", END)
  258. replace(text)
  259. text.tag_remove(SEL, "1.0", END)
  260. button = Button(frame, text="Replace", command=show_replace)
  261. button.pack()
  262. if __name__ == '__main__':
  263. from unittest import main
  264. main('idlelib.idle_test.test_replace', verbosity=2, exit=False)
  265. from idlelib.idle_test.htest import run
  266. run(_replace_dialog)