1313 */
1414package org .httprpc .sierra .tools .previewer ;
1515
16- import org . fife . ui . autocomplete . AutoCompletion ;
17- import org . fife . ui . autocomplete . CompletionProvider ;
18- import org . fife . ui . rsyntaxtextarea . RSyntaxTextArea ;
19- import org . fife . ui . rsyntaxtextarea . SyntaxConstants ;
20- import org . httprpc . sierra . Outlet ;
21- import org . httprpc . sierra . UILoader ;
22- import org . httprpc . sierra . tools . previewer . engine . RenderingEngine ;
23- import org . httprpc . sierra . tools . previewer . model . RenderError ;
24- import org . httprpc . sierra . tools . previewer . model . RenderResult ;
25-
16+ import java . awt . BorderLayout ;
17+ import java . awt . Toolkit ;
18+ import java . awt . event . KeyEvent ;
19+ import java . io . File ;
20+ import java . io . IOException ;
21+ import java . nio . file . Files ;
22+ import java . nio . file . Path ;
23+ import java . nio . file . StandardOpenOption ;
24+ import java . util . concurrent . ExecutionException ;
25+ import java . util . function . Consumer ;
2626import javax .swing .ImageIcon ;
2727import javax .swing .JFileChooser ;
2828import javax .swing .JFrame ;
3434import javax .swing .JPanel ;
3535import javax .swing .JScrollPane ;
3636import javax .swing .JSplitPane ;
37+ import javax .swing .KeyStroke ;
3738import javax .swing .SwingWorker ;
3839import javax .swing .Timer ;
40+ import javax .swing .UIManager ;
3941import javax .swing .event .DocumentEvent ;
4042import javax .swing .event .DocumentListener ;
4143import javax .swing .filechooser .FileNameExtensionFilter ;
42- import java .awt .BorderLayout ;
43- import java .io .File ;
44- import java .io .IOException ;
45- import java .nio .file .Files ;
46- import java .nio .file .Path ;
47- import java .nio .file .StandardOpenOption ;
48- import java .util .concurrent .ExecutionException ;
49- import java .util .function .Consumer ;
44+ import org .fife .rsta .ui .search .FindDialog ;
45+ import org .fife .rsta .ui .search .ReplaceDialog ;
46+ import org .fife .rsta .ui .search .SearchEvent ;
47+ import org .fife .rsta .ui .search .SearchListener ;
48+ import org .fife .ui .autocomplete .AutoCompletion ;
49+ import org .fife .ui .autocomplete .CompletionProvider ;
50+ import org .fife .ui .rsyntaxtextarea .RSyntaxTextArea ;
51+ import org .fife .ui .rsyntaxtextarea .SyntaxConstants ;
52+ import org .fife .ui .rtextarea .SearchContext ;
53+ import org .fife .ui .rtextarea .SearchEngine ;
54+ import org .fife .ui .rtextarea .SearchResult ;
55+ import org .httprpc .sierra .Outlet ;
56+ import org .httprpc .sierra .UILoader ;
57+ import org .httprpc .sierra .tools .previewer .engine .RenderingEngine ;
58+ import org .httprpc .sierra .tools .previewer .model .RenderError ;
59+ import org .httprpc .sierra .tools .previewer .model .RenderResult ;
5060
5161/**
5262 * The main application window for the Sierra UI Previewer. UI is defined in
5363 * MainFrame.xml and loaded by UILoader. This class contains the wiring and
5464 * business logic.
5565 */
56- public class MainFrame extends JFrame {
66+ public class MainFrame extends JFrame implements SearchListener {
5767 // --- Subsystems ---
5868 private final RenderingEngine renderingEngine ;
5969 private final RecentFilesManager recentFilesManager ; // NEW: Manager instance
@@ -69,6 +79,9 @@ public class MainFrame extends JFrame {
6979 private @ Outlet JMenuBar menuBar = null ;
7080 private @ Outlet JMenuItem openItem = null ;
7181 private @ Outlet JMenuItem saveItem = null ;
82+ private @ Outlet JMenu searchMenu = null ;
83+ private @ Outlet JMenuItem findItem = null ;
84+ private @ Outlet JMenuItem replaceItem = null ;
7285 private @ Outlet JMenu recentMenu = null ;
7386 private @ Outlet JMenuItem exitItem = null ;
7487 private @ Outlet JMenuItem aboutItem = null ;
@@ -79,6 +92,8 @@ public class MainFrame extends JFrame {
7992 private @ Outlet JLabel filePathLabel = null ; // The <label> for the file path
8093
8194 // --- Manually Created Components ---
95+ private FindDialog findDialog = null ;
96+ private ReplaceDialog replaceDialog = null ;
8297 private RSyntaxTextArea editorPane = null ;
8398
8499 public MainFrame () {
@@ -97,12 +112,7 @@ public MainFrame() {
97112 fileChooser .setAcceptAllFileFilterUsed (false );
98113
99114 setupMenuBar ();
100-
101115 setupCustomEditor ();
102-
103- // 5. Set layout for previewPanel
104- // previewPanel.setLayout(new BorderLayout());
105-
106116 debounceTimer = setupDebounceTimer ();
107117
108118 // Wire editor events
@@ -128,8 +138,7 @@ public void changedUpdate(DocumentEvent e) {
128138 var iconURL = getClass ().getResource ("/sierra.png" );
129139 var icon = new ImageIcon (iconURL ).getImage ();
130140 setIconImage (icon );
131-
132- // 8. Trigger an initial render
141+
133142 triggerRender ();
134143 }
135144
@@ -206,10 +215,33 @@ private CompletionProvider createCompletionProvider() {
206215
207216 // --- Editor Setup ---
208217 /**
209- * Creates the custom RSyntaxTextArea and adds it to the <scroll-pane>
210- * placeholder that Sierra injected.
218+ * Creates the custom RSyntaxTextArea/associated functionality and adds it
219+ * to the placeholder that Sierra injected.
211220 */
212221 private void setupCustomEditor () {
222+ findDialog = new FindDialog (this , this );
223+ replaceDialog = new ReplaceDialog (this , this );
224+
225+ // This ties the properties of the two dialogs together (match case,
226+ // regex, etc.).
227+ SearchContext context = findDialog .getSearchContext ();
228+ replaceDialog .setSearchContext (context );
229+
230+ int acceleratorKey = Toolkit .getDefaultToolkit ().getMenuShortcutKeyMaskEx ();
231+ findItem .setAccelerator (KeyStroke .getKeyStroke (KeyEvent .VK_F , acceleratorKey ));
232+ findItem .addActionListener ((e ) -> {
233+ if (replaceDialog .isVisible ()) {
234+ replaceDialog .setVisible (false );
235+ }
236+ findDialog .setVisible (true );
237+ });
238+ replaceItem .addActionListener ((e ) -> {
239+ if (findDialog .isVisible ()) {
240+ findDialog .setVisible (false );
241+ }
242+ replaceDialog .setVisible (true );
243+ });
244+ replaceItem .setAccelerator (KeyStroke .getKeyStroke (KeyEvent .VK_R , acceleratorKey ));
213245 editorPane = new RSyntaxTextArea (25 , 80 );
214246 editorPane .setSyntaxEditingStyle (SyntaxConstants .SYNTAX_STYLE_XML );
215247 editorPane .setCodeFoldingEnabled (true );
@@ -230,7 +262,71 @@ private void setupCustomEditor() {
230262
231263 editorScrollPane .setViewportView (editorPane );
232264 }
265+
266+ // -- Extra search/replace functionality
267+
268+ @ Override
269+ public String getSelectedText () {
270+ return editorPane .getSelectedText ();
271+ }
272+
273+ /**
274+ * Listens for events from our search dialogs and actually does the dirty
275+ * work.
276+ */
277+ @ Override
278+ public void searchEvent (SearchEvent e ) {
279+
280+ SearchEvent .Type type = e .getType ();
281+ SearchContext context = e .getSearchContext ();
282+ SearchResult result = null ;
283+
284+ switch (type ) {
285+ case MARK_ALL :
286+ result = SearchEngine .markAll (editorPane , context );
287+ break ;
288+ case FIND :
289+ result = SearchEngine .find (editorPane , context );
290+ if (!result .wasFound () || result .isWrapped ()) {
291+ UIManager .getLookAndFeel ().provideErrorFeedback (editorPane );
292+ }
293+ break ;
294+ case REPLACE :
295+ result = SearchEngine .replace (editorPane , context );
296+ if (!result .wasFound () || result .isWrapped ()) {
297+ UIManager .getLookAndFeel ().provideErrorFeedback (editorPane );
298+ }
299+ break ;
300+ case REPLACE_ALL :
301+ result = SearchEngine .replaceAll (editorPane , context );
302+ JOptionPane .showMessageDialog (null , result .getCount ()
303+ + " occurrences replaced." );
304+ break ;
305+ default :
306+ statusBar .setText ("Unknown search event" );
307+ break ;
308+ }
309+
310+ if (result == null ){
311+ return ;
312+ }
313+
314+ String text ;
315+ if (result .wasFound ()) {
316+ text = "Text found; occurrences marked: " + result .getMarkedCount ();
317+ } else if (type == SearchEvent .Type .MARK_ALL ) {
318+ if (result .getMarkedCount () > 0 ) {
319+ text = "Occurrences marked: " + result .getMarkedCount ();
320+ } else {
321+ text = "" ;
322+ }
323+ } else {
324+ text = "Text not found" ;
325+ }
326+ statusBar .setText (text );
233327
328+ }
329+
234330 // --- Rendering/Control Logic ---
235331 /**
236332 * Implements the debounce mechanism.
0 commit comments