1+ # --- START OF FILE editor_frame.py ---
2+
13import wx
24import os
35import theme
46from menu_mixin import SettingsMenuMixin
57
6- # --- NEW: Dialogs for Find and Replace functionality ---
78class FindDialog (wx .Dialog ):
89 def __init__ (self , parent , title ):
910 super (FindDialog , self ).__init__ (parent , title = title )
1011 self .parent = parent
1112 panel = wx .Panel (self )
1213 vbox = wx .BoxSizer (wx .VERTICAL )
13-
14- self .find_text = wx .TextCtrl (panel )
14+ self .find_text = wx .TextCtrl (panel , style = wx .TE_PROCESS_ENTER )
1515 vbox .Add (wx .StaticText (panel , label = "Find what:" ), 0 , wx .ALL , 5 )
1616 vbox .Add (self .find_text , 0 , wx .EXPAND | wx .ALL , 5 )
1717
18+ # --- NEW: Direction controls ---
19+ self .direction_radio = wx .RadioBox (panel , label = "Direction" , choices = ["Down" , "Up" ])
20+ vbox .Add (self .direction_radio , 0 , wx .EXPAND | wx .ALL , 5 )
21+
1822 self .case_checkbox = wx .CheckBox (panel , label = "Match case" )
1923 vbox .Add (self .case_checkbox , 0 , wx .ALL , 5 )
2024
21- # --- FIX: Add a horizontal sizer for buttons ---
2225 hbox = wx .BoxSizer (wx .HORIZONTAL )
23- find_next_btn = wx .Button (panel , label = "Find Next" )
24- close_btn = wx .Button (panel , label = "Close" , id = wx .ID_CANCEL ) # Use standard Cancel ID
26+ find_next_btn = wx .Button (panel , label = "Find & Next" )
27+ close_btn = wx .Button (panel , label = "& Close" , id = wx .ID_CANCEL )
2528 hbox .Add (find_next_btn )
2629 hbox .Add (close_btn , 0 , wx .LEFT , 5 )
2730 vbox .Add (hbox , 0 , wx .ALIGN_CENTER | wx .ALL , 5 )
2831
2932 panel .SetSizer (vbox )
3033 find_next_btn .Bind (wx .EVT_BUTTON , self .on_find_next )
34+ self .find_text .Bind (wx .EVT_TEXT_ENTER , self .on_find_next )
3135 self .find_text .SetFocus ()
3236
3337 def on_find_next (self , event ):
34- self .parent .do_find (self .find_text .GetValue (), self .case_checkbox .IsChecked ())
38+ direction = self .direction_radio .GetStringSelection ().lower ()
39+ self .parent .do_find (self .find_text .GetValue (), self .case_checkbox .IsChecked (), direction )
3540
3641class ReplaceDialog (wx .Dialog ):
3742 def __init__ (self , parent , title ):
@@ -41,44 +46,52 @@ def __init__(self, parent, title):
4146 vbox = wx .BoxSizer (wx .VERTICAL )
4247
4348 vbox .Add (wx .StaticText (panel , label = "Find what:" ), 0 , wx .ALL , 5 )
44- self .find_text = wx .TextCtrl (panel )
49+ self .find_text = wx .TextCtrl (panel , style = wx . TE_PROCESS_ENTER )
4550 vbox .Add (self .find_text , 0 , wx .EXPAND | wx .ALL , 5 )
4651
4752 vbox .Add (wx .StaticText (panel , label = "Replace with:" ), 0 , wx .ALL , 5 )
48- self .replace_text = wx .TextCtrl (panel )
53+ self .replace_text = wx .TextCtrl (panel , style = wx . TE_PROCESS_ENTER )
4954 vbox .Add (self .replace_text , 0 , wx .EXPAND | wx .ALL , 5 )
5055
56+ # --- NEW: Direction controls ---
57+ self .direction_radio = wx .RadioBox (panel , label = "Direction" , choices = ["Down" , "Up" ])
58+ vbox .Add (self .direction_radio , 0 , wx .EXPAND | wx .ALL , 5 )
59+
5160 self .case_checkbox = wx .CheckBox (panel , label = "Match case" )
5261 vbox .Add (self .case_checkbox , 0 , wx .ALL , 5 )
5362
5463 hbox = wx .BoxSizer (wx .HORIZONTAL )
55- find_next_btn = wx .Button (panel , label = "Find Next" )
56- replace_btn = wx .Button (panel , label = "Replace" )
57- replace_all_btn = wx .Button (panel , label = "Replace All" )
58- close_btn = wx .Button (panel , label = "Close" , id = wx .ID_CANCEL ) # Use standard Cancel ID
64+ find_next_btn = wx .Button (panel , label = "Find & Next" )
65+ replace_btn = wx .Button (panel , label = "& Replace" )
66+ replace_all_btn = wx .Button (panel , label = "Replace & All" )
67+ close_btn = wx .Button (panel , label = "& Close" , id = wx .ID_CANCEL )
5968 hbox .Add (find_next_btn )
6069 hbox .Add (replace_btn , 0 , wx .LEFT , 5 )
6170 hbox .Add (replace_all_btn , 0 , wx .LEFT , 5 )
62- hbox .Add (close_btn , 0 , wx .LEFT , 15 ) # Add extra space
71+ hbox .Add (close_btn , 0 , wx .LEFT , 15 )
6372 vbox .Add (hbox , 0 , wx .EXPAND | wx .ALL , 5 )
6473
6574 panel .SetSizer (vbox )
6675 find_next_btn .Bind (wx .EVT_BUTTON , self .on_find_next )
6776 replace_btn .Bind (wx .EVT_BUTTON , self .on_replace )
6877 replace_all_btn .Bind (wx .EVT_BUTTON , self .on_replace_all )
78+ self .find_text .Bind (wx .EVT_TEXT_ENTER , self .on_find_next )
79+ self .replace_text .Bind (wx .EVT_TEXT_ENTER , self .on_find_next )
6980 self .find_text .SetFocus ()
7081
7182 def on_find_next (self , event ):
72- self .parent .do_find (self .find_text .GetValue (), self .case_checkbox .IsChecked ())
83+ direction = self .direction_radio .GetStringSelection ().lower ()
84+ self .parent .do_find (self .find_text .GetValue (), self .case_checkbox .IsChecked (), direction )
7385
7486 def on_replace (self , event ):
75- self .parent .do_replace (self .find_text .GetValue (), self .replace_text .GetValue (), self .case_checkbox .IsChecked ())
87+ direction = self .direction_radio .GetStringSelection ().lower ()
88+ self .parent .do_replace (self .find_text .GetValue (), self .replace_text .GetValue (), self .case_checkbox .IsChecked (), direction )
7689
7790 def on_replace_all (self , event ):
7891 self .parent .do_replace_all (self .find_text .GetValue (), self .replace_text .GetValue (), self .case_checkbox .IsChecked ())
7992
80-
8193class EditorFrame (wx .Frame , SettingsMenuMixin ):
94+ # ... (__init__ is updated for new menu items) ...
8295 def __init__ (self , parent , title , local_path , remote_path , sftp_client ):
8396 super (EditorFrame , self ).__init__ (parent , title = title , size = (800 , 600 ))
8497 self .local_path , self .remote_path , self .sftp = local_path , remote_path , sftp_client
@@ -100,6 +113,7 @@ def __init__(self, parent, title, local_path, remote_path, sftp_client):
100113 paste_item = edit_menu .Append (wx .ID_PASTE , "&Paste\t Ctrl+V" )
101114 search_menu = wx .Menu ()
102115 find_item = search_menu .Append (wx .ID_FIND , "&Find\t Ctrl+F" )
116+ find_next_item = search_menu .Append (wx .ID_ANY , "Find &Next\t F3" ) # NEW
103117 replace_item = search_menu .Append (wx .ID_REPLACE , "Find and &Replace\t Ctrl+H" )
104118 goto_item = search_menu .Append (wx .ID_ANY , "&Go To Line\t Ctrl+G" )
105119 menu_bar .Append (file_menu , "&File" )
@@ -117,6 +131,7 @@ def __init__(self, parent, title, local_path, remote_path, sftp_client):
117131 self .Bind (wx .EVT_MENU , lambda e : self .text_ctrl .Copy (), copy_item )
118132 self .Bind (wx .EVT_MENU , lambda e : self .text_ctrl .Paste (), paste_item )
119133 self .Bind (wx .EVT_MENU , self .on_find , find_item )
134+ self .Bind (wx .EVT_MENU , lambda e : self .do_find (self .last_search_string , "matchcase" in self .last_search_flags , "down" ), find_next_item )
120135 self .Bind (wx .EVT_MENU , self .on_replace , replace_item )
121136 self .Bind (wx .EVT_MENU , self .on_go_to_line , goto_item )
122137 self .Bind (wx .EVT_CLOSE , self .on_close )
@@ -125,37 +140,51 @@ def __init__(self, parent, title, local_path, remote_path, sftp_client):
125140 self .load_file_content ()
126141 self .Show ()
127142
128- def on_find (self , event ):
129- with FindDialog (self , "Find" ) as dlg :
130- dlg .ShowModal ()
131- def on_replace (self , event ):
132- with ReplaceDialog (self , "Find and Replace" ) as dlg :
133- dlg .ShowModal ()
134- def do_find (self , find_string , match_case ):
143+ # --- UPDATED: `do_find` now handles direction ---
144+ def do_find (self , find_string , match_case , direction = "down" ):
145+ if not find_string : return
135146 self .last_search_string = find_string
136- flags = wx .FR_DOWN
137- if match_case : flags |= wx .FR_MATCHCASE
138- self .last_search_flags = flags
147+
139148 content = self .text_ctrl .GetValue ()
149+ search_content = content if match_case else content .lower ()
150+ search_string = find_string if match_case else find_string .lower ()
151+
140152 start = self .text_ctrl .GetInsertionPoint ()
141- if not match_case :
142- content = content .lower ()
143- find_string = find_string .lower ()
144- pos = content .find (find_string , start )
145- if pos == - 1 : pos = content .find (find_string , 0 )
153+
154+ pos = - 1
155+ if direction == "down" :
156+ pos = search_content .find (search_string , start )
157+ if pos == - 1 : # Wrap search
158+ pos = search_content .find (search_string , 0 )
159+ else : # direction == "up"
160+ pos = search_content .rfind (search_string , 0 , start )
161+ if pos == - 1 : # Wrap search
162+ pos = search_content .rfind (search_string )
163+
146164 if pos != - 1 :
147165 self .text_ctrl .SetSelection (pos , pos + len (find_string ))
148166 self .text_ctrl .SetFocus ()
149167 else :
150168 wx .MessageBox (f"'{ find_string } ' not found." , "Find" , wx .ICON_INFORMATION )
151- def do_replace (self , find_string , replace_string , match_case ):
169+
170+ # --- UPDATED: `do_replace` now passes direction ---
171+ def do_replace (self , find_string , replace_string , match_case , direction = "down" ):
152172 start , end = self .text_ctrl .GetSelection ()
153173 selected_text = self .text_ctrl .GetStringSelection ()
174+
154175 compare_text = selected_text if match_case else selected_text .lower ()
155176 compare_find = find_string if match_case else find_string .lower ()
177+
156178 if compare_text == compare_find :
157179 self .text_ctrl .Replace (start , end , replace_string )
158- self .do_find (find_string , match_case )
180+
181+ self .do_find (find_string , match_case , direction )
182+
183+ # ... (rest of file is unchanged) ...
184+ def on_find (self , event ):
185+ with FindDialog (self , "Find" ) as dlg : dlg .ShowModal ()
186+ def on_replace (self , event ):
187+ with ReplaceDialog (self , "Find and Replace" ) as dlg : dlg .ShowModal ()
159188 def do_replace_all (self , find_string , replace_string , match_case ):
160189 content = self .text_ctrl .GetValue ()
161190 if match_case :
0 commit comments