-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Quality of Life features to assume role policies. Added Find and Replace, Undo & Redo, and line wrapping for the Session Policy text field. #48
Merged
Merged
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
46051ec
Added Undo/Redo functionality
JAEKts 75ba2d6
Added Find/Replace to Session Policy Editor for Assume Role Profiles
JAEKts aa72da7
Addressed review comments
JAEKts 7f81aae
Addressed review comments pt2
JAEKts 07af75d
Addressed review comments pt2.5 - Made small ammendment to p2
JAEKts dfd2660
Addressed additional review comments
JAEKts 4238b22
Clean up and standardizing text component handlers
jakekarnes42 66c011a
Finalizing bug fixes
JAEKts File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
208 changes: 208 additions & 0 deletions
208
src/main/java/com/netspi/awssigner/controller/RegexHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
package com.netspi.awssigner.controller; | ||
|
||
import javax.swing.*; | ||
import javax.swing.text.*; | ||
import java.awt.*; | ||
import java.util.*; | ||
import java.util.regex.*; | ||
import static com.netspi.awssigner.log.LogWriter.logError; | ||
|
||
/** | ||
* Handles regex-based search, highlighting, and replacement for a JTextArea. | ||
*/ | ||
public class RegexHandler { | ||
|
||
private final JTextArea textArea; | ||
private final Highlighter highlighter; | ||
private final java.util.List<int[]> matchPositions; | ||
private int currentMatchIndex = -1; | ||
private String currentRegex; // Store the last used regex pattern | ||
|
||
public RegexHandler(JTextArea textArea) { | ||
this.textArea = textArea; | ||
this.highlighter = textArea.getHighlighter(); | ||
this.matchPositions = new ArrayList<>(); | ||
this.currentRegex = null; | ||
} | ||
|
||
public void findAndHighlightNext(String regex) throws PatternSyntaxException { | ||
if (currentRegex == null || !currentRegex.equals(regex)) { | ||
// Perform a new search if the regex changes | ||
findAllMatches(regex); | ||
currentMatchIndex = 0; // Start with the first match | ||
} else if (!matchPositions.isEmpty()) { | ||
// Cycle to the next match if there are matches | ||
currentMatchIndex = (currentMatchIndex + 1) % matchPositions.size(); | ||
} | ||
|
||
if (!matchPositions.isEmpty()) { | ||
updateHighlights(); // Highlight matches and the current match | ||
} | ||
} | ||
|
||
/** | ||
* Finds all matches and stores their positions. | ||
* | ||
* @param regex The regex pattern to search for | ||
* @throws PatternSyntaxException if the regex is invalid | ||
*/ | ||
private void findAllMatches(String regex) throws PatternSyntaxException { | ||
clearHighlights(); | ||
matchPositions.clear(); | ||
|
||
currentRegex = regex; // Store the current regex | ||
String content = textArea.getText(); | ||
Pattern pattern = Pattern.compile(regex); | ||
Matcher matcher = pattern.matcher(content); | ||
|
||
while (matcher.find()) { | ||
int start = matcher.start(); | ||
int end = matcher.end(); | ||
matchPositions.add(new int[]{start, end}); | ||
} | ||
|
||
currentMatchIndex = matchPositions.isEmpty() ? -1 : 0; // Reset match index | ||
} | ||
|
||
/** | ||
* Replaces the current highlighted match with the given replacement. | ||
* | ||
* @param replacement The text to replace the current match | ||
*/ | ||
public void replaceCurrentMatch(String replacement) { | ||
if (!hasCurrentMatch()) { | ||
logError("No current match to replace."); | ||
return; | ||
} | ||
|
||
try { | ||
String content = textArea.getText(); | ||
int[] currentMatch = matchPositions.get(currentMatchIndex); | ||
int start = currentMatch[0]; | ||
int end = currentMatch[1]; | ||
|
||
// Replace the current match using a quoted replacement | ||
String updatedContent = content.substring(0, start) | ||
+ Matcher.quoteReplacement(replacement) | ||
+ content.substring(end); | ||
textArea.setText(updatedContent); | ||
|
||
// Adjust subsequent match positions | ||
int adjustment = replacement.length() - (end - start); | ||
matchPositions.remove(currentMatchIndex); | ||
|
||
for (int i = currentMatchIndex; i < matchPositions.size(); i++) { | ||
matchPositions.get(i)[0] += adjustment; | ||
matchPositions.get(i)[1] += adjustment; | ||
} | ||
|
||
// Highlight remaining matches | ||
highlightAllMatches(); | ||
|
||
// Highlight the next match if any | ||
if (!matchPositions.isEmpty()) { | ||
currentMatchIndex = currentMatchIndex % matchPositions.size(); | ||
highlightCurrentMatch(); | ||
} else { | ||
currentMatchIndex = -1; // Reset if no matches remain | ||
} | ||
} catch (Exception e) { | ||
logError("Error replacing current match: " + e.getMessage()); | ||
} | ||
} | ||
|
||
/** | ||
* Highlights all matches in yellow, except the current match. | ||
*/ | ||
private void highlightAllMatches() { | ||
clearHighlights(); // Clear existing highlights | ||
try { | ||
for (int i = 0; i < matchPositions.size(); i++) { | ||
if (i != currentMatchIndex) { // Skip the current match | ||
int[] match = matchPositions.get(i); | ||
highlighter.addHighlight(match[0], match[1], new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW)); | ||
} | ||
} | ||
} catch (BadLocationException e) { | ||
logError("Error highlighting matches: " + e.getMessage()); | ||
} | ||
} | ||
|
||
/** | ||
* Highlights the current match in orange. | ||
*/ | ||
private void highlightCurrentMatch() { | ||
if (!hasCurrentMatch()) { | ||
return; | ||
} | ||
|
||
try { | ||
int[] currentMatch = matchPositions.get(currentMatchIndex); | ||
highlighter.addHighlight(currentMatch[0], currentMatch[1], new DefaultHighlighter.DefaultHighlightPainter(Color.ORANGE)); | ||
textArea.setCaretPosition(currentMatch[0]); // Move caret to the current match | ||
} catch (BadLocationException e) { | ||
logError("Error highlighting current match: " + e.getMessage()); | ||
} | ||
} | ||
|
||
/** | ||
* Highlights all matches and the current match. | ||
*/ | ||
private void updateHighlights() { | ||
highlightAllMatches(); // Highlight all matches in yellow, except the current one | ||
highlightCurrentMatch(); // Highlight the current match in orange | ||
} | ||
|
||
/** | ||
* Replaces all matches with the given replacement. | ||
* | ||
* @param replacement The text to replace all matches | ||
*/ | ||
public void replaceAllMatches(String replacement) { | ||
if (matchPositions.isEmpty() || currentRegex == null) { | ||
logError("No matches to replace."); | ||
return; | ||
} | ||
|
||
try { | ||
String content = textArea.getText(); | ||
// Use quoted replacement for safety | ||
String updatedContent = content.replaceAll(currentRegex, Matcher.quoteReplacement(replacement)); | ||
textArea.setText(updatedContent); | ||
|
||
clearHighlights(); | ||
matchPositions.clear(); | ||
} catch (Exception e) { | ||
logError("Error replacing all matches: " + e.getMessage()); | ||
} | ||
} | ||
|
||
/** | ||
* Clears all highlights in the text area. | ||
*/ | ||
private void clearHighlights() { | ||
try { | ||
highlighter.removeAllHighlights(); | ||
} catch (Exception e) { | ||
logError("Error clearing highlights: " + e.getMessage()); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if a current match is highlighted. | ||
* | ||
* @return True if a current match exists, otherwise false | ||
*/ | ||
public boolean hasCurrentMatch() { | ||
return currentMatchIndex >= 0 && currentMatchIndex < matchPositions.size(); | ||
} | ||
|
||
/** | ||
* Gets the total number of matches found. | ||
* | ||
* @return The count of matches | ||
*/ | ||
public int getMatchCount() { | ||
return matchPositions.size(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
src/main/java/com/netspi/awssigner/controller/UndoRedoManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.netspi.awssigner.controller; | ||
|
||
import javax.swing.*; | ||
import javax.swing.text.JTextComponent; | ||
import javax.swing.undo.UndoManager; | ||
import java.awt.Toolkit; | ||
import java.awt.event.InputEvent; | ||
import java.awt.event.KeyEvent; | ||
import java.awt.event.ActionEvent; | ||
|
||
public class UndoRedoManager { | ||
|
||
public static void addUndoRedoFunctionality(JTextComponent textComponent) { | ||
final UndoManager undoManager = new UndoManager(); | ||
|
||
// Add UndoableEditListener to the document | ||
textComponent.getDocument().addUndoableEditListener(e -> undoManager.addEdit(e.getEdit())); | ||
|
||
// Get the platform-specific menu shortcut key mask | ||
int menuShortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); | ||
|
||
// Undo key stroke | ||
KeyStroke undoKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z, menuShortcutKeyMask); | ||
|
||
// Redo key strokes | ||
KeyStroke redoKeyStroke1 = KeyStroke.getKeyStroke(KeyEvent.VK_Z, menuShortcutKeyMask | InputEvent.SHIFT_DOWN_MASK); | ||
KeyStroke redoKeyStroke2 = KeyStroke.getKeyStroke(KeyEvent.VK_Z, menuShortcutKeyMask | InputEvent.SHIFT_DOWN_MASK); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like these 2 keystrokes are the same. Maybe we wanted:
That should make the 2 redo hotkeys: CTRL+Y and CTRL+SHIFT+Z (or using Command on Mac). I think that should match people's expectations. |
||
|
||
// Bind the undo action | ||
textComponent.getInputMap().put(undoKeyStroke, "Undo"); | ||
textComponent.getActionMap().put("Undo", new AbstractAction() { | ||
@Override | ||
public void actionPerformed(ActionEvent e) { | ||
if (undoManager.canUndo()) { | ||
undoManager.undo(); | ||
} | ||
} | ||
}); | ||
|
||
// Bind the redo actions | ||
textComponent.getInputMap().put(redoKeyStroke1, "Redo"); | ||
textComponent.getInputMap().put(redoKeyStroke2, "Redo"); | ||
textComponent.getActionMap().put("Redo", new AbstractAction() { | ||
@Override | ||
public void actionPerformed(ActionEvent e) { | ||
if (undoManager.canRedo()) { | ||
undoManager.redo(); | ||
} | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we made the right choice by reverting most of the changes in this file, but we're back to the case where a new DocumentListener is added each time the text field gains focus. I think this might still work, but I don't know if we'd run into issues like before where someone clicks in and out of the field over and over.
I think there are two routes for this class. If we want to keep the API signature the same, we could do this:
The above should work but the use of a Set for tracking feels like a bit of overkill. The advantage is that it's "drop-in" for the current class and wouldn't require other changes. The alternative would be to a larger change to the class like so:
I think this is a bit better because now the class is both a FocusListener and DocumentListener. We've already renamed the class to TextComponentChangeListener to reflect this. Because we're changing that class, we'd need to rename the file and update how it's used in AWSSignerController too: