find.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. # find.py - Find and Replace
  2. import win32con, win32api
  3. import win32ui
  4. from pywin.mfc import dialog
  5. import afxres
  6. from pywin.framework import scriptutils
  7. FOUND_NOTHING=0
  8. FOUND_NORMAL=1
  9. FOUND_LOOPED_BACK=2
  10. FOUND_NEXT_FILE=3
  11. class SearchParams:
  12. def __init__(self, other=None):
  13. if other is None:
  14. self.__dict__['findText'] = ""
  15. self.__dict__['replaceText'] = ""
  16. self.__dict__['matchCase'] = 0
  17. self.__dict__['matchWords'] = 0
  18. self.__dict__['acrossFiles'] = 0
  19. self.__dict__['remember'] = 1
  20. self.__dict__['sel'] = (-1,-1)
  21. self.__dict__['keepDialogOpen']=0
  22. else:
  23. self.__dict__.update(other.__dict__)
  24. # Helper so we cant misspell attributes :-)
  25. def __setattr__(self, attr, val):
  26. if not hasattr(self, attr):
  27. raise AttributeError(attr)
  28. self.__dict__[attr]=val
  29. curDialog = None
  30. lastSearch = defaultSearch = SearchParams()
  31. searchHistory = []
  32. def ShowFindDialog():
  33. _ShowDialog(FindDialog)
  34. def ShowReplaceDialog():
  35. _ShowDialog(ReplaceDialog)
  36. def _ShowDialog(dlgClass):
  37. global curDialog
  38. if curDialog is not None:
  39. if curDialog.__class__ != dlgClass:
  40. curDialog.DestroyWindow()
  41. curDialog = None
  42. else:
  43. curDialog.SetFocus()
  44. if curDialog is None:
  45. curDialog = dlgClass()
  46. curDialog.CreateWindow()
  47. def FindNext():
  48. params = SearchParams(lastSearch)
  49. params.sel = (-1,-1)
  50. if not params.findText:
  51. ShowFindDialog()
  52. else:
  53. return _FindIt(None, params)
  54. def _GetControl(control=None):
  55. if control is None:
  56. control = scriptutils.GetActiveEditControl()
  57. return control
  58. def _FindIt(control, searchParams):
  59. global lastSearch, defaultSearch
  60. control = _GetControl(control)
  61. if control is None: return FOUND_NOTHING
  62. # Move to the next char, so we find the next one.
  63. flags = 0
  64. if searchParams.matchWords: flags = flags | win32con.FR_WHOLEWORD
  65. if searchParams.matchCase: flags = flags | win32con.FR_MATCHCASE
  66. if searchParams.sel == (-1,-1):
  67. sel = control.GetSel()
  68. # If the position is the same as we found last time,
  69. # then we assume it is a "FindNext"
  70. if sel==lastSearch.sel:
  71. sel = sel[0]+1, sel[0]+1
  72. else:
  73. sel = searchParams.sel
  74. if sel[0]==sel[1]: sel=sel[0], control.GetTextLength()
  75. rc = FOUND_NOTHING
  76. # (Old edit control will fail here!)
  77. posFind, foundSel = control.FindText(flags, sel, searchParams.findText)
  78. lastSearch = SearchParams(searchParams)
  79. if posFind >= 0:
  80. rc = FOUND_NORMAL
  81. lineno = control.LineFromChar(posFind)
  82. control.SCIEnsureVisible(lineno)
  83. control.SetSel(foundSel)
  84. control.SetFocus()
  85. win32ui.SetStatusText(win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE))
  86. if rc == FOUND_NOTHING and lastSearch.acrossFiles:
  87. # Loop around all documents. First find this document.
  88. try:
  89. try:
  90. doc = control.GetDocument()
  91. except AttributeError:
  92. try:
  93. doc = control.GetParent().GetDocument()
  94. except AttributeError:
  95. print("Cant find a document for the control!")
  96. doc = None
  97. if doc is not None:
  98. template = doc.GetDocTemplate()
  99. alldocs = template.GetDocumentList()
  100. mypos = lookpos = alldocs.index(doc)
  101. while 1:
  102. lookpos = (lookpos+1) % len(alldocs)
  103. if lookpos == mypos:
  104. break
  105. view = alldocs[lookpos].GetFirstView()
  106. posFind, foundSel = view.FindText(flags, (0, view.GetTextLength()), searchParams.findText)
  107. if posFind >= 0:
  108. nChars = foundSel[1]-foundSel[0]
  109. lineNo = view.LineFromChar(posFind) # zero based.
  110. lineStart = view.LineIndex(lineNo)
  111. colNo = posFind - lineStart # zero based.
  112. scriptutils.JumpToDocument(alldocs[lookpos].GetPathName(), lineNo+1, colNo+1, nChars)
  113. rc = FOUND_NEXT_FILE
  114. break
  115. except win32ui.error:
  116. pass
  117. if rc == FOUND_NOTHING:
  118. # Loop around this control - attempt to find from the start of the control.
  119. posFind, foundSel = control.FindText(flags, (0, sel[0]-1), searchParams.findText)
  120. if posFind >= 0:
  121. control.SCIEnsureVisible(control.LineFromChar(foundSel[0]))
  122. control.SetSel(foundSel)
  123. control.SetFocus()
  124. win32ui.SetStatusText("Not found! Searching from the top of the file.")
  125. rc = FOUND_LOOPED_BACK
  126. else:
  127. lastSearch.sel=-1,-1
  128. win32ui.SetStatusText("Can not find '%s'" % searchParams.findText )
  129. if rc != FOUND_NOTHING:
  130. lastSearch.sel = foundSel
  131. if lastSearch.remember:
  132. defaultSearch = lastSearch
  133. # track search history
  134. try:
  135. ix = searchHistory.index(searchParams.findText)
  136. except ValueError:
  137. if len(searchHistory) > 50:
  138. searchHistory[50:] = []
  139. else:
  140. del searchHistory[ix]
  141. searchHistory.insert(0, searchParams.findText)
  142. return rc
  143. def _ReplaceIt(control):
  144. control = _GetControl(control)
  145. statusText = "Can not find '%s'." % lastSearch.findText
  146. rc = FOUND_NOTHING
  147. if control is not None and lastSearch.sel != (-1,-1):
  148. control.ReplaceSel(lastSearch.replaceText)
  149. rc = FindNext()
  150. if rc !=FOUND_NOTHING:
  151. statusText = win32ui.LoadString(afxres.AFX_IDS_IDLEMESSAGE)
  152. win32ui.SetStatusText(statusText)
  153. return rc
  154. class FindReplaceDialog(dialog.Dialog):
  155. def __init__(self):
  156. dialog.Dialog.__init__(self,self._GetDialogTemplate())
  157. self.HookCommand(self.OnFindNext, 109)
  158. def OnInitDialog(self):
  159. self.editFindText = self.GetDlgItem(102)
  160. self.butMatchWords = self.GetDlgItem(105)
  161. self.butMatchCase = self.GetDlgItem(107)
  162. self.butKeepDialogOpen = self.GetDlgItem(115)
  163. self.butAcrossFiles = self.GetDlgItem(116)
  164. self.butRemember = self.GetDlgItem(117)
  165. self.editFindText.SetWindowText(defaultSearch.findText)
  166. control = _GetControl()
  167. # _GetControl only gets normal MDI windows; if the interactive
  168. # window is docked and no document open, we get None.
  169. if control:
  170. # If we have a selection, default to that.
  171. sel = control.GetSelText()
  172. if (len(sel) != 0):
  173. self.editFindText.SetWindowText(sel)
  174. if (defaultSearch.remember):
  175. defaultSearch.findText = sel
  176. for hist in searchHistory:
  177. self.editFindText.AddString(hist)
  178. if hasattr(self.editFindText, 'SetEditSel'):
  179. self.editFindText.SetEditSel(0, -2)
  180. else:
  181. self.editFindText.SetSel(0, -2)
  182. self.editFindText.SetFocus()
  183. self.butMatchWords.SetCheck(defaultSearch.matchWords)
  184. self.butMatchCase.SetCheck(defaultSearch.matchCase)
  185. self.butKeepDialogOpen.SetCheck(defaultSearch.keepDialogOpen)
  186. self.butAcrossFiles.SetCheck(defaultSearch.acrossFiles)
  187. self.butRemember.SetCheck(defaultSearch.remember)
  188. return dialog.Dialog.OnInitDialog(self)
  189. def OnDestroy(self, msg):
  190. global curDialog
  191. curDialog = None
  192. return dialog.Dialog.OnDestroy(self, msg)
  193. def DoFindNext(self):
  194. params = SearchParams()
  195. params.findText = self.editFindText.GetWindowText()
  196. params.matchCase = self.butMatchCase.GetCheck()
  197. params.matchWords = self.butMatchWords.GetCheck()
  198. params.acrossFiles = self.butAcrossFiles.GetCheck()
  199. params.remember = self.butRemember.GetCheck()
  200. return _FindIt(None, params)
  201. def OnFindNext(self, id, code):
  202. if not self.editFindText.GetWindowText():
  203. win32api.MessageBeep()
  204. return
  205. if self.DoFindNext() != FOUND_NOTHING:
  206. if not self.butKeepDialogOpen.GetCheck():
  207. self.DestroyWindow()
  208. class FindDialog(FindReplaceDialog):
  209. def _GetDialogTemplate(self):
  210. style = win32con.DS_MODALFRAME | win32con.WS_POPUP | win32con.WS_VISIBLE | win32con.WS_CAPTION | win32con.WS_SYSMENU | win32con.DS_SETFONT
  211. visible = win32con.WS_CHILD | win32con.WS_VISIBLE
  212. dt = [
  213. ["Find", (0, 2, 240, 75), style, None, (8, "MS Sans Serif")],
  214. ["Static", "Fi&nd What:", 101, (5, 8, 40, 10), visible],
  215. ["ComboBox", "", 102, (50, 7, 120, 120), visible | win32con.WS_BORDER | win32con.WS_TABSTOP |
  216. win32con.WS_VSCROLL |win32con.CBS_DROPDOWN |win32con.CBS_AUTOHSCROLL],
  217. ["Button", "Match &whole word only", 105, (5, 23, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  218. ["Button", "Match &case", 107, (5, 33, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  219. ["Button", "Keep &dialog open", 115, (5, 43, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  220. ["Button", "Across &open files", 116, (5, 52, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  221. ["Button", "&Remember as default search", 117, (5, 61, 150, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  222. ["Button", "&Find Next", 109, (185, 5, 50, 14), visible | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP],
  223. ["Button", "Cancel", win32con.IDCANCEL, (185, 23, 50, 14), visible | win32con.WS_TABSTOP],
  224. ]
  225. return dt
  226. class ReplaceDialog(FindReplaceDialog):
  227. def _GetDialogTemplate(self):
  228. style = win32con.DS_MODALFRAME | win32con.WS_POPUP | win32con.WS_VISIBLE | win32con.WS_CAPTION | win32con.WS_SYSMENU | win32con.DS_SETFONT
  229. visible = win32con.WS_CHILD | win32con.WS_VISIBLE
  230. dt = [
  231. ["Replace", (0, 2, 240, 95), style, 0, (8, "MS Sans Serif")],
  232. ["Static", "Fi&nd What:", 101, (5, 8, 40, 10), visible],
  233. ["ComboBox", "", 102, (60, 7, 110, 120), visible | win32con.WS_BORDER | win32con.WS_TABSTOP |
  234. win32con.WS_VSCROLL |win32con.CBS_DROPDOWN |win32con.CBS_AUTOHSCROLL],
  235. ["Static", "Re&place with:", 103, (5, 25, 50, 10), visible],
  236. ["ComboBox", "", 104, (60, 24, 110, 120), visible | win32con.WS_BORDER | win32con.WS_TABSTOP |
  237. win32con.WS_VSCROLL |win32con.CBS_DROPDOWN |win32con.CBS_AUTOHSCROLL],
  238. ["Button", "Match &whole word only", 105, (5, 42, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  239. ["Button", "Match &case", 107, (5, 52, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  240. ["Button", "Keep &dialog open", 115, (5, 62, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  241. ["Button", "Across &open files", 116, (5, 72, 100, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  242. ["Button", "&Remember as default search", 117, (5, 81, 150, 10), visible | win32con.BS_AUTOCHECKBOX | win32con.WS_TABSTOP],
  243. ["Button", "&Find Next", 109, (185, 5, 50, 14), visible | win32con.BS_DEFPUSHBUTTON | win32con.WS_TABSTOP],
  244. ["Button", "&Replace", 110, (185, 23, 50, 14), visible | win32con.WS_TABSTOP],
  245. ["Button", "Replace &All", 111, (185, 41, 50, 14), visible | win32con.WS_TABSTOP],
  246. ["Button", "Cancel", win32con.IDCANCEL, (185, 59, 50, 14), visible | win32con.WS_TABSTOP],
  247. ]
  248. return dt
  249. def OnInitDialog(self):
  250. rc = FindReplaceDialog.OnInitDialog(self)
  251. self.HookCommand(self.OnReplace, 110)
  252. self.HookCommand(self.OnReplaceAll, 111)
  253. self.HookMessage(self.OnActivate, win32con.WM_ACTIVATE)
  254. self.editReplaceText = self.GetDlgItem(104)
  255. self.editReplaceText.SetWindowText(lastSearch.replaceText)
  256. if hasattr(self.editReplaceText, 'SetEditSel'):
  257. self.editReplaceText.SetEditSel(0, -2)
  258. else:
  259. self.editReplaceText.SetSel(0, -2)
  260. self.butReplace = self.GetDlgItem(110)
  261. self.butReplaceAll = self.GetDlgItem(111)
  262. self.CheckButtonStates()
  263. return rc
  264. def CheckButtonStates(self):
  265. # We can do a "Replace" or "Replace All" if the current selection
  266. # is the same as the search text.
  267. ft = self.editFindText.GetWindowText()
  268. control = _GetControl()
  269. # bCanReplace = len(ft)>0 and control.GetSelText() == ft
  270. bCanReplace = control is not None and lastSearch.sel == control.GetSel()
  271. self.butReplace.EnableWindow(bCanReplace)
  272. # self.butReplaceAll.EnableWindow(bCanReplace)
  273. def OnActivate(self, msg):
  274. wparam = msg[2]
  275. fActive = win32api.LOWORD(wparam)
  276. if fActive != win32con.WA_INACTIVE:
  277. self.CheckButtonStates()
  278. def OnFindNext(self, id, code):
  279. self.DoFindNext()
  280. self.CheckButtonStates()
  281. def OnReplace(self, id, code):
  282. lastSearch.replaceText = self.editReplaceText.GetWindowText()
  283. _ReplaceIt(None)
  284. def OnReplaceAll(self, id, code):
  285. control = _GetControl(None)
  286. if control is not None:
  287. control.SetSel(0)
  288. num = 0
  289. if self.DoFindNext() == FOUND_NORMAL:
  290. num = 1
  291. lastSearch.replaceText = self.editReplaceText.GetWindowText()
  292. while _ReplaceIt(control) == FOUND_NORMAL:
  293. num = num + 1
  294. win32ui.SetStatusText("Replaced %d occurrences" % num)
  295. if num > 0 and not self.butKeepDialogOpen.GetCheck():
  296. self.DestroyWindow()
  297. if __name__=='__main__':
  298. ShowFindDialog()