search.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. """Search dialog for Find, Find Again, and Find Selection
  2. functionality.
  3. Inherits from SearchDialogBase for GUI and uses searchengine
  4. to prepare search pattern.
  5. """
  6. from tkinter import TclError
  7. from idlelib import searchengine
  8. from idlelib.searchbase import SearchDialogBase
  9. def _setup(text):
  10. """Return the new or existing singleton SearchDialog instance.
  11. The singleton dialog saves user entries and preferences
  12. across instances.
  13. Args:
  14. text: Text widget containing the text to be searched.
  15. """
  16. root = text._root()
  17. engine = searchengine.get(root)
  18. if not hasattr(engine, "_searchdialog"):
  19. engine._searchdialog = SearchDialog(root, engine)
  20. return engine._searchdialog
  21. def find(text):
  22. """Open the search dialog.
  23. Module-level function to access the singleton SearchDialog
  24. instance and open the dialog. If text is selected, it is
  25. used as the search phrase; otherwise, the previous entry
  26. is used. No search is done with this command.
  27. """
  28. pat = text.get("sel.first", "sel.last")
  29. return _setup(text).open(text, pat) # Open is inherited from SDBase.
  30. def find_again(text):
  31. """Repeat the search for the last pattern and preferences.
  32. Module-level function to access the singleton SearchDialog
  33. instance to search again using the user entries and preferences
  34. from the last dialog. If there was no prior search, open the
  35. search dialog; otherwise, perform the search without showing the
  36. dialog.
  37. """
  38. return _setup(text).find_again(text)
  39. def find_selection(text):
  40. """Search for the selected pattern in the text.
  41. Module-level function to access the singleton SearchDialog
  42. instance to search using the selected text. With a text
  43. selection, perform the search without displaying the dialog.
  44. Without a selection, use the prior entry as the search phrase
  45. and don't display the dialog. If there has been no prior
  46. search, open the search dialog.
  47. """
  48. return _setup(text).find_selection(text)
  49. class SearchDialog(SearchDialogBase):
  50. "Dialog for finding a pattern in text."
  51. def create_widgets(self):
  52. "Create the base search dialog and add a button for Find Next."
  53. SearchDialogBase.create_widgets(self)
  54. # TODO - why is this here and not in a create_command_buttons?
  55. self.make_button("Find Next", self.default_command, isdef=True)
  56. def default_command(self, event=None):
  57. "Handle the Find Next button as the default command."
  58. if not self.engine.getprog():
  59. return
  60. self.find_again(self.text)
  61. def find_again(self, text):
  62. """Repeat the last search.
  63. If no search was previously run, open a new search dialog. In
  64. this case, no search is done.
  65. If a search was previously run, the search dialog won't be
  66. shown and the options from the previous search (including the
  67. search pattern) will be used to find the next occurrence
  68. of the pattern. Next is relative based on direction.
  69. Position the window to display the located occurrence in the
  70. text.
  71. Return True if the search was successful and False otherwise.
  72. """
  73. if not self.engine.getpat():
  74. self.open(text)
  75. return False
  76. if not self.engine.getprog():
  77. return False
  78. res = self.engine.search_text(text)
  79. if res:
  80. line, m = res
  81. i, j = m.span()
  82. first = "%d.%d" % (line, i)
  83. last = "%d.%d" % (line, j)
  84. try:
  85. selfirst = text.index("sel.first")
  86. sellast = text.index("sel.last")
  87. if selfirst == first and sellast == last:
  88. self.bell()
  89. return False
  90. except TclError:
  91. pass
  92. text.tag_remove("sel", "1.0", "end")
  93. text.tag_add("sel", first, last)
  94. text.mark_set("insert", self.engine.isback() and first or last)
  95. text.see("insert")
  96. return True
  97. else:
  98. self.bell()
  99. return False
  100. def find_selection(self, text):
  101. """Search for selected text with previous dialog preferences.
  102. Instead of using the same pattern for searching (as Find
  103. Again does), this first resets the pattern to the currently
  104. selected text. If the selected text isn't changed, then use
  105. the prior search phrase.
  106. """
  107. pat = text.get("sel.first", "sel.last")
  108. if pat:
  109. self.engine.setcookedpat(pat)
  110. return self.find_again(text)
  111. def _search_dialog(parent): # htest #
  112. "Display search test box."
  113. from tkinter import Toplevel, Text
  114. from tkinter.ttk import Frame, Button
  115. top = Toplevel(parent)
  116. top.title("Test SearchDialog")
  117. x, y = map(int, parent.geometry().split('+')[1:])
  118. top.geometry("+%d+%d" % (x, y + 175))
  119. frame = Frame(top)
  120. frame.pack()
  121. text = Text(frame, inactiveselectbackground='gray')
  122. text.pack()
  123. text.insert("insert","This is a sample string.\n"*5)
  124. def show_find():
  125. text.tag_add('sel', '1.0', 'end')
  126. _setup(text).open(text)
  127. text.tag_remove('sel', '1.0', 'end')
  128. button = Button(frame, text="Search (selection ignored)", command=show_find)
  129. button.pack()
  130. if __name__ == '__main__':
  131. from unittest import main
  132. main('idlelib.idle_test.test_search', verbosity=2, exit=False)
  133. from idlelib.idle_test.htest import run
  134. run(_search_dialog)