2525import javax .swing .text .BadLocationException ;
2626import javax .swing .text .JTextComponent ;
2727
28- /**
29- * Popup menu for auto-completing text in a document
30- */
28+ /** Popup menu for auto-completing text in a document */
3129public class AutocompletePopup extends JPopupMenu {
3230
3331 private static final Set <Integer > CURSOR_MOVEMENT_PASSTHROUGH_KEYS =
@@ -42,7 +40,6 @@ public class AutocompletePopup extends JPopupMenu {
4240 private final AutocompleteContext autocompleteContext ;
4341
4442 /**
45- *
4643 * @param parent text component where completion will be performed
4744 * @param position position to perform completion at
4845 * @param inputSoFar text
@@ -57,7 +54,8 @@ public AutocompletePopup(
5754 @ NotNull Consumer <String > onCompletion ) {
5855 super ();
5956 autocompleteContext = new AutocompleteContext (inputSoFar , suggestions );
60- int maxVisibleRows = Math .min (autocompleteContext .unfilteredSuggestionsSize (), 10 ); // Limit to 10 rows max
57+ int maxVisibleRows =
58+ Math .min (autocompleteContext .unfilteredSuggestionsSize (), 10 ); // Limit to 10 rows max
6159 updateSuggestionList ();
6260 suggestionList .setSelectionMode (ListSelectionModel .SINGLE_SELECTION );
6361 suggestionList .setVisibleRowCount (maxVisibleRows );
@@ -67,12 +65,7 @@ public AutocompletePopup(
6765 new MouseAdapter () {
6866 @ Override
6967 public void mouseClicked (MouseEvent e ) {
70- int index = suggestionList .locationToIndex (e .getPoint ());
71- if (index >= 0 ) {
72- String selected = autocompleteContext .getCompletion (index );
73- onCompletion .accept (selected );
74- setVisible (false );
75- }
68+ executeCompletion (suggestionList .locationToIndex (e .getPoint ()), onCompletion );
7669 }
7770 });
7871
@@ -92,76 +85,13 @@ public void mouseMoved(MouseEvent e) {
9285 new KeyAdapter () {
9386 @ Override
9487 public void keyPressed (KeyEvent e ) {
95- if (e .getKeyCode () == KeyEvent .VK_ESCAPE ) {
96- setVisible (false );
97- e .consume ();
98- return ;
99- }
100- // Update auto-complete context and pass on to parent. Hide if nothing could be
101- // deleted from the auto-complete context.
102- if (e .getKeyCode () == KeyEvent .VK_BACK_SPACE ) {
103- if (autocompleteContext .canDelete ()) {
104- autocompleteContext .deleteInput ();
105- updateSuggestionList ();
106- } else {
107- setVisible (false );
108- }
109-
110- parent .dispatchEvent (e );
111- return ;
112- }
113- // ENTER triggers the current selected completion
114- if (e .getKeyCode () == KeyEvent .VK_ENTER ) {
115- int index = suggestionList .getSelectedIndex ();
116- if (index >= 0 ) {
117- String selected = autocompleteContext .getCompletion (index );
118- onCompletion .accept (selected );
119- setVisible (false );
120- e .consume ();
121- return ;
122- }
123- }
124- // UP/DOWN keys navigate selection; custom handling here allows wrapping between
125- // start/end of list
126- if (e .getKeyCode () == KeyEvent .VK_UP || e .getKeyCode () == KeyEvent .VK_DOWN ) {
127- int index = suggestionList .getSelectedIndex ();
128- if (index == -1 ) {
129- index = 0 ;
130- } else {
131- index += (e .getKeyCode () == KeyEvent .VK_UP ) ? -1 : 1 ;
132- }
133- int numSuggestions = suggestionList .getModel ().getSize ();
134- if (index < 0 ) {
135- index = numSuggestions - 1 ;
136- } else if (index >= numSuggestions ) {
137- index = 0 ;
138- }
139- suggestionList .setSelectedIndex (index );
140- suggestionList .ensureIndexIsVisible (index );
141- e .consume ();
142- return ;
143- }
144- // Unambiguous cursor movement keys are passed through to parent after closing
145- if (CURSOR_MOVEMENT_PASSTHROUGH_KEYS .contains (e .getKeyCode ())) {
146- setVisible (false );
147- parent .dispatchEvent (e );
148- return ;
149- }
88+ handleKeyPressed (e , parent , onCompletion );
15089 }
15190
15291 @ Override
15392 public void keyTyped (KeyEvent e ) {
15493 char typedChar = e .getKeyChar ();
155- if (Character .isDefined (typedChar ) && !Character .isISOControl (typedChar )) {
156- autocompleteContext .appendInput (typedChar );
157- }
158- if (autocompleteContext .filteredSuggestions ().isEmpty ()) {
159- // Close the popup if no suggestions match
160- setVisible (false );
161- }
162- updateSuggestionList ();
163- // enter the chars in the document
164- parent .dispatchEvent (e );
94+ handleKeyTyped (e , typedChar , parent );
16595 }
16696 });
16797
@@ -196,6 +126,92 @@ public void keyTyped(KeyEvent e) {
196126 }
197127 }
198128
129+ private void handleKeyPressed (
130+ @ NotNull KeyEvent e , @ NotNull JTextComponent parent , @ NotNull Consumer <String > onCompletion ) {
131+ if (e .getKeyCode () == KeyEvent .VK_BACK_SPACE ) {
132+ handleBackspace (e , parent );
133+ return ;
134+ }
135+ // ENTER triggers the current selected completion
136+ if (e .getKeyCode () == KeyEvent .VK_ENTER ) {
137+ executeCompletion (suggestionList .getSelectedIndex (), onCompletion );
138+ e .consume ();
139+ return ;
140+ }
141+ if (e .getKeyCode () == KeyEvent .VK_UP || e .getKeyCode () == KeyEvent .VK_DOWN ) {
142+ handleUpDownKeys (e );
143+ return ;
144+ }
145+ // Unambiguous cursor movement keys are passed through to parent after closing
146+ if (CURSOR_MOVEMENT_PASSTHROUGH_KEYS .contains (e .getKeyCode ())) {
147+ setVisible (false );
148+ parent .dispatchEvent (e );
149+ return ;
150+ }
151+ parent .dispatchEvent (e );
152+ }
153+
154+ /**
155+ * When the user types, pass the typing through to the parent and update the suggestion list. If
156+ * no more suggestions are available, close this popup.
157+ */
158+ private void handleKeyTyped (KeyEvent e , char typedChar , @ NotNull JTextComponent parent ) {
159+ if (Character .isDefined (typedChar ) && !Character .isISOControl (typedChar )) {
160+ autocompleteContext .appendInput (typedChar );
161+ }
162+ if (autocompleteContext .filteredSuggestions ().isEmpty ()) {
163+ // Close the popup if no suggestions match
164+ setVisible (false );
165+ }
166+ updateSuggestionList ();
167+ // enter the chars in the document
168+ parent .dispatchEvent (e );
169+ }
170+
171+ /**
172+ * Update auto-complete context and pass on to parent. Hide if nothing could be deleted from the
173+ * auto-complete context.
174+ */
175+ private void handleBackspace (KeyEvent e , @ NotNull JTextComponent parent ) {
176+ if (autocompleteContext .canDelete ()) {
177+ autocompleteContext .deleteInput ();
178+ updateSuggestionList ();
179+ } else {
180+ setVisible (false );
181+ }
182+
183+ parent .dispatchEvent (e );
184+ }
185+
186+ private void executeCompletion (int suggestionIndex , @ NotNull Consumer <String > onCompletion ) {
187+ if (suggestionIndex >= 0 ) {
188+ String selected = autocompleteContext .getCompletion (suggestionIndex );
189+ onCompletion .accept (selected );
190+ setVisible (false );
191+ }
192+ }
193+
194+ /**
195+ * UP/DOWN keys navigate selection; custom handling here allows wrapping between start/end of list
196+ */
197+ private void handleUpDownKeys (KeyEvent e ) {
198+ int index = suggestionList .getSelectedIndex ();
199+ if (index == -1 ) {
200+ index = 0 ;
201+ } else {
202+ index += (e .getKeyCode () == KeyEvent .VK_UP ) ? -1 : 1 ;
203+ }
204+ int numSuggestions = suggestionList .getModel ().getSize ();
205+ if (index < 0 ) {
206+ index = numSuggestions - 1 ;
207+ } else if (index >= numSuggestions ) {
208+ index = 0 ;
209+ }
210+ suggestionList .setSelectedIndex (index );
211+ suggestionList .ensureIndexIsVisible (index );
212+ e .consume ();
213+ }
214+
199215 private void updateSuggestionList () {
200216 suggestionList .setListData (new Vector <>(autocompleteContext .filteredSuggestions ()));
201217 suggestionList .setSelectedIndex (0 );
0 commit comments