From a2397f20a009f61d970327a8cea0702740c52d89 Mon Sep 17 00:00:00 2001 From: kzoli Date: Tue, 28 Mar 2017 14:15:32 +0200 Subject: [PATCH 1/3] Enhanced completion proposals in Parameter completion --- .../autocomplete/AutoCompleteDescWindow.java | 10 +++- .../autocomplete/AutoCompletePopupWindow.java | 14 ++++- .../fife/ui/autocomplete/AutoCompletion.java | 2 + .../ParameterizedCompletionChoicesWindow.java | 24 ++++++-- .../ParameterizedCompletionContext.java | 44 ++++++++++++--- .../java/org/fife/ui/autocomplete/Util.java | 55 ++++++++++++++++++- 6 files changed, 131 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/fife/ui/autocomplete/AutoCompleteDescWindow.java b/src/main/java/org/fife/ui/autocomplete/AutoCompleteDescWindow.java index f873898..d449226 100644 --- a/src/main/java/org/fife/ui/autocomplete/AutoCompleteDescWindow.java +++ b/src/main/java/org/fife/ui/autocomplete/AutoCompleteDescWindow.java @@ -205,8 +205,14 @@ public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) { } - - /** + @Override + public void dispose() + { + ac = null; + super.dispose(); + } + + /** * Sets the currently displayed description and updates the history. * * @param historyItem The item to add to the history. diff --git a/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java b/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java index b7b9911..fdc84b8 100644 --- a/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java +++ b/src/main/java/org/fife/ui/autocomplete/AutoCompletePopupWindow.java @@ -187,8 +187,18 @@ public AutoCompletePopupWindow(Window parent, final AutoCompletion ac) { } - - @Override + @Override + public void dispose() + { + ac = null; + if (descWindow != null) { + descWindow.dispose(); + descWindow = null; + } + super.dispose(); + } + + @Override public void caretUpdate(CaretEvent e) { if (isVisible()) { // Should always be true int line = ac.getLineOfCaret(); diff --git a/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java b/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java index 4f8adc6..76b5d0d 100644 --- a/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java +++ b/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java @@ -1220,6 +1220,8 @@ public void uninstall() { textComponent = null; popupWindowListener.uninstall(popupWindow); + // release system resources + if (popupWindow != null) popupWindow.dispose(); popupWindow = null; } diff --git a/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java b/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java index 0e08c32..91c34c5 100644 --- a/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java +++ b/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java @@ -116,8 +116,14 @@ public void mouseClicked(MouseEvent e) { } - - /** + @Override + public void dispose() + { + ac = null; + super.dispose(); + } + + /** * Returns the selected value. * * @return The selected value, or null if nothing is @@ -133,14 +139,21 @@ public String getSelectedChoice() { * Changes the selected index. * * @param amount The amount by which to change the selected index. + * @param cyclic true if the selection should go around, false, if it should stop at first or last item */ - public void incSelection(int amount) { + public void incSelection(int amount, boolean cyclic) { int selection = list.getSelectedIndex(); selection += amount; - if (selection<0) { + if (selection < 0 && cyclic) { // Account for nothing selected yet selection = model.getSize()-1;//+= model.getSize(); } + else if (selection < 0) { + selection = 0; + } + else if (selection >= model.getSize() && !cyclic) { + selection = model.getSize() - 1; + } else { selection %= model.getSize(); } @@ -225,6 +238,7 @@ public void setParameter(int param, String prefix) { model.clear(); List temp = new ArrayList(); + List prefixParts = Util.getTextParts(prefix); if (choicesListList!=null && param>=0 && param getTextParts(String text) { + List textParts = new ArrayList(); + if (text == null || "".equals(text)) return textParts; + StringBuilder sb = new StringBuilder(); + for (int i = 0;i < text.length();i++) { + if (Character.isUpperCase(text.charAt(i))) { + if (sb.length() > 0) { + textParts.add(sb.toString()); + sb = new StringBuilder(); + } + } + sb.append(text.charAt(i)); + } + if (sb.length() > 0) textParts.add(sb.toString()); + + return textParts; + } + + /** + * Compares two list of string if they share the same Uppercase segments. + * + * @param enteredText the text Parts for the entered text + * @param toBeMatched the text Parts for method, field, classname, etc. + * @return true if they match, false otherwise + */ + public static boolean matchTextParts(List enteredText, List toBeMatched) { + if (toBeMatched.size() < enteredText.size()) return false; + for (int i = 0;i < enteredText.size();i++) { + if (!toBeMatched.get(i).startsWith(enteredText.get(i))) { + return false; + } + } + return true; + } + + /** + * Compares two string if they share the same Uppercase segments. This method will + * create the string list for both parameter to be matched + * + * @param enteredText + * @param toBeMatched + * @return + */ + public static boolean matchTextParts(String enteredText, String toBeMatched) { + return matchTextParts(getTextParts(enteredText), getTextParts(toBeMatched)); + } } \ No newline at end of file From 0ac8a29f9186943b21d438601655861198626db0 Mon Sep 17 00:00:00 2001 From: kzoli Date: Thu, 30 Mar 2017 16:28:50 +0200 Subject: [PATCH 2/3] Enhanced completion proposals in Parameter completion --- .../fife/ui/autocomplete/AutoCompletion.java | 237 +++++++++++++++++- ...meterizedCompletionDescriptionToolTip.java | 76 +++--- 2 files changed, 281 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java b/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java index 76b5d0d..37b4369 100644 --- a/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java +++ b/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java @@ -8,9 +8,13 @@ */ package org.fife.ui.autocomplete; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import sun.management.snmp.jvminstr.JvmThreadInstanceEntryImpl; + import java.awt.*; import java.awt.event.*; import java.beans.*; +import java.util.ArrayList; import java.util.List; import javax.swing.*; import javax.swing.Timer; @@ -145,6 +149,11 @@ public class AutoCompletion { */ private KeyStroke trigger; + /** + * The keystroke to show method parameter tooltip + */ + private KeyStroke paramTrigger; + /** * The previous key in the text component's InputMap for the * trigger key. @@ -157,7 +166,26 @@ public class AutoCompletion { */ private Action oldTriggerAction; - /** + /** + * The previous key in the text component's InputMap for the + * paramTrigger key. + */ + private Object oldParamTriggerKey; + + /** + * The action previously assigned to {@link #paramTrigger}, so we can reset it if + * the user disables auto-completion. + */ + private Action oldParamTriggerAction; + + /** + * List of tooltip windows in case user presses Ctrl-P. If multiple ParameterizedCompletion is returned from the + * provider, we show multiple tool windows. This holds reference, so we can remove it. + */ + private List parameterTooltipWindows = new ArrayList(); + + private ParameterTooltipListener parameterTooltipListener; + /** * The previous key in the text component's InputMap for the * parameter completion trigger key. */ @@ -222,6 +250,11 @@ public class AutoCompletion { */ private static final String PARAM_TRIGGER_KEY = "AutoComplete"; + /** + * The key used in the input map for the AutoComplete action. + */ + private static final String SHOW_PARAM_TRIGGER_KEY = "AutoCompleteParams"; + /** * Key used in the input map for the parameter completion action. */ @@ -252,6 +285,7 @@ public AutoCompletion(CompletionProvider provider) { setCompletionProvider(provider); setTriggerKey(getDefaultTriggerKey()); + setParamTrigger(getDefaultParamTriggerKey()); setAutoCompleteEnabled(true); setAutoCompleteSingleChoices(true); setAutoActivationEnabled(false); @@ -369,6 +403,10 @@ public static KeyStroke getDefaultTriggerKey() { return KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, mask); } + public static KeyStroke getDefaultParamTriggerKey() { + int mask = InputEvent.CTRL_MASK; + return KeyStroke.getKeyStroke(KeyEvent.VK_P, mask); + } /** * Returns the handler to use when an external URL is clicked in the @@ -545,6 +583,7 @@ protected boolean hidePopupWindow() { return true; } } + hideParameterTooltips(); return false; } @@ -629,6 +668,7 @@ public void install(JTextComponent c) { this.textComponent = c; installTriggerKey(getTriggerKey()); + installParamTriggerKey(getParamTrigger()); // Install the function completion key, if there is one. // NOTE: We cannot do this if the start char is ' ' (e.g. just a space @@ -678,6 +718,69 @@ private void installTriggerKey(KeyStroke ks) { } + /** + * Installs a "trigger key" action onto the current text component. + * + * @param ks The keystroke that should trigger the action. + * @see #uninstallTriggerKey() + */ + private void installParamTriggerKey(KeyStroke ks) { + InputMap im = textComponent.getInputMap(); + oldParamTriggerKey = im.get(ks); + im.put(ks, SHOW_PARAM_TRIGGER_KEY); + ActionMap am = textComponent.getActionMap(); + oldParamTriggerAction = am.get(SHOW_PARAM_TRIGGER_KEY); + am.put(SHOW_PARAM_TRIGGER_KEY, showParameterTooltip()); + } + + private void hideParameterTooltips() { + if (parameterTooltipWindows.size() > 0) { + for (ParameterizedCompletionDescriptionToolTip tip : parameterTooltipWindows) { + tip.setVisible(false); + } + parameterTooltipWindows.clear(); + } + if (parameterTooltipListener != null) { + parameterTooltipListener.uninstall(textComponent); + } + } + + private Action showParameterTooltip() { + return new AbstractAction() + { + @Override + public void actionPerformed(ActionEvent e) + { + hideParameterTooltips(); + if (parameterTooltipListener == null) { + parameterTooltipListener = new ParameterTooltipListener(); + } + parameterTooltipListener.install(textComponent); + List completions = provider.getParameterizedCompletions(textComponent); + + if (completions != null && completions.size() > 0) { + for (int i = 0;i < completions.size();i++) { + ParameterizedCompletion pc = completions.get(i); + ParameterizedCompletionDescriptionToolTip tip = new ParameterizedCompletionDescriptionToolTip(parentWindow, new ParameterizedCompletionContext(parentWindow, AutoCompletion.this, pc), AutoCompletion.this, pc); + try { + int dot = textComponent.getCaretPosition(); + Rectangle r = textComponent.modelToView(dot); + Point p = new Point(r.x, r.y); + SwingUtilities.convertPointToScreen(p, textComponent); + r.x = p.x; + r.y = p.y - (24 * (completions.size() - (i + 1))); + tip.setLocationRelativeTo(r); + tip.setVisible(true); + parameterTooltipWindows.add(tip); + } + catch (Exception ex) { + } + } + } + } + }; + } + /** * Creates and returns the action to call when the user presses the * auto-completion trigger key (e.g. ctrl+space). This is a hook for @@ -785,6 +888,8 @@ protected int refreshPopupWindow() { return getLineOfCaret(); } + hideParameterTooltips(); + // If the popup is currently visible, and they type a space (or any // character that resets the completion list to "all completions"), // the popup window should be hidden instead of being reset to show @@ -1224,6 +1329,8 @@ public void uninstall() { if (popupWindow != null) popupWindow.dispose(); popupWindow = null; + hideParameterTooltips(); + } } @@ -1533,5 +1640,131 @@ public void removeFrom(JTextComponent tc) { } - + public KeyStroke getParamTrigger() + { + return paramTrigger; + } + + public void setParamTrigger(KeyStroke paramTrigger) + { + this.paramTrigger = paramTrigger; + } + + /** + * Listener used to handle focus/caret update and ESC key if the Ctrl-P parameter hint tooltip is visible + */ + private class ParameterTooltipListener implements FocusListener, CaretListener { + private int minPos; + private int maxPos; + private Object oldEscapeKey; + private Action oldEscapeAction; + private static final String IM_KEY_ESCAPE = "ParamTooltipEscape"; + + public void install(JTextComponent textComponent) { + if (textComponent instanceof RSyntaxTextArea) { + RSyntaxTextArea t = (RSyntaxTextArea) textComponent; + + // set default to the whole line + minPos = t.getLineStartOffsetOfCurrentLine(); + maxPos = t.getLineEndOffsetOfCurrentLine(); + + // determine the first ( and the last ) within the method call and set it as boundaries of Parameter + // tooltip + int parenCounter = 0; + Document doc = textComponent.getDocument(); + + Element root = doc.getDefaultRootElement(); + int index = root.getElementIndex(textComponent.getCaretPosition()); + Element elem = root.getElement(index); + int start = elem.getStartOffset(); + int len = elem.getEndOffset() - start; + Segment seg = new Segment(new char[len], 0, 0); + try { + doc.getText(start, len, seg); + + // go from current caret position backwards, till we find an opening ( without a closing ) + int i = textComponent.getCaretPosition() - elem.getStartOffset(); + while (i >= 0) { + if (seg.charAt(i) == ')') parenCounter++; + else if (seg.charAt(i) == '(' && parenCounter > 0) parenCounter--; + else if (seg.charAt(i) == '(' && parenCounter == 0) { + minPos = i + 1 + elem.getStartOffset(); + break; + } + i--; + } + + // now go from current caret position towards the end of the line trying to find a closing ) without opening ( + i = textComponent.getCaretPosition() - elem.getStartOffset(); + while (i < seg.length()) { + if (seg.charAt(i) == '(') parenCounter++; + else if (seg.charAt(i) == ')' && parenCounter > 0) parenCounter--; + else if (seg.charAt(i) == ')' && parenCounter == 0) { + maxPos = i + 1 + elem.getStartOffset(); + break; + } + i++; + } + } + catch (Exception ble) { + ble.printStackTrace(); + } + } + textComponent.addCaretListener(this); + textComponent.addFocusListener(this); + + installKeyBindings(textComponent); + } + + public void uninstall(JTextComponent textComponent) { + textComponent.removeCaretListener(this); + textComponent.removeFocusListener(this); + + uninstallKeyBindings(textComponent); + } + + private void installKeyBindings(JTextComponent textComponent) { + InputMap im = textComponent.getInputMap(); + ActionMap am = textComponent.getActionMap(); + + KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + oldEscapeKey = im.get(ks); + im.put(ks, IM_KEY_ESCAPE); + oldEscapeAction = am.get(IM_KEY_ESCAPE); + am.put(IM_KEY_ESCAPE, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + hideParameterTooltips(); + } + }); + } + + private void uninstallKeyBindings(JTextComponent textComponent) { + InputMap im = textComponent.getInputMap(); + ActionMap am = textComponent.getActionMap(); + + KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + im.put(ks, oldEscapeKey); + am.put(IM_KEY_ESCAPE, oldEscapeAction); + } + + @Override + public void caretUpdate(CaretEvent e) { + int dot = e.getDot(); + if (dot < minPos || dot >= maxPos) { + hideParameterTooltips(); + return; + } + } + + @Override + public void focusGained(FocusEvent e) { + // do nothing + } + + @Override + public void focusLost(FocusEvent e) { + hideParameterTooltips(); + } + } } \ No newline at end of file diff --git a/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java b/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java index 47d17d4..f45e73c 100644 --- a/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java +++ b/src/main/java/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java @@ -46,6 +46,8 @@ class ParameterizedCompletionDescriptionToolTip { */ private ParameterizedCompletion pc; + private Window owner; + /** * Constructor. @@ -58,38 +60,44 @@ public ParameterizedCompletionDescriptionToolTip(Window owner, ParameterizedCompletionContext context, AutoCompletion ac, ParameterizedCompletion pc) { - tooltip = new JWindow(owner); - this.pc = pc; - - descLabel = new JLabel(); - descLabel.setBorder(BorderFactory.createCompoundBorder( - TipUtil.getToolTipBorder(), - BorderFactory.createEmptyBorder(2, 5, 2, 5))); - descLabel.setOpaque(true); - descLabel.setBackground(TipUtil.getToolTipBackground()); - // It appears that if a JLabel is set as a content pane directly, when - // using the JDK's opacity API's, it won't paint its background, even - // if label.setOpaque(true) is called. You have to have a container - // underneath it for it to paint its background. Thus, we embed our - // label in a parent JPanel to handle this case. - //tooltip.setContentPane(descLabel); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(descLabel); - tooltip.setContentPane(panel); - - // Give apps a chance to decorate us with drop shadows, etc. - PopupWindowDecorator decorator = PopupWindowDecorator.get(); - if (decorator!=null) { - decorator.decorate(tooltip); - } - - updateText(0); - - tooltip.setFocusableWindowState(false); - + this.owner = owner; + initTooltipWindow(); } + /** + * Initializes the tooltip window + */ + private void initTooltipWindow() { + tooltip = new JWindow(owner); + + descLabel = new JLabel(); + descLabel.setBorder(BorderFactory.createCompoundBorder( + TipUtil.getToolTipBorder(), + BorderFactory.createEmptyBorder(2, 5, 2, 5))); + descLabel.setOpaque(true); + descLabel.setBackground(TipUtil.getToolTipBackground()); + // It appears that if a JLabel is set as a content pane directly, when + // using the JDK's opacity API's, it won't paint its background, even + // if label.setOpaque(true) is called. You have to have a container + // underneath it for it to paint its background. Thus, we embed our + // label in a parent JPanel to handle this case. + //tooltip.setContentPane(descLabel); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(descLabel); + tooltip.setContentPane(panel); + + // Give apps a chance to decorate us with drop shadows, etc. + PopupWindowDecorator decorator = PopupWindowDecorator.get(); + if (decorator!=null) { + decorator.decorate(tooltip); + } + + updateText(0); + + tooltip.setFocusableWindowState(false); + } + /** * Returns whether this tool tip is visible. @@ -98,7 +106,7 @@ public ParameterizedCompletionDescriptionToolTip(Window owner, * @see #setVisible(boolean) */ public boolean isVisible() { - return tooltip.isVisible(); + return tooltip == null ? false : tooltip.isVisible(); } @@ -108,6 +116,7 @@ public boolean isVisible() { * @param r The visual position of the caret (in screen coordinates). */ public void setLocationRelativeTo(Rectangle r) { + if (tooltip == null) initTooltipWindow(); // Multi-monitor support - make sure the completion window (and // description window, if applicable) both fit in the same window in @@ -145,7 +154,13 @@ else if (x+tooltip.getWidth()>screenBounds.x+screenBounds.width) { // completion * @see #isVisible() */ public void setVisible(boolean visible) { + if (tooltip == null) initTooltipWindow(); tooltip.setVisible(visible); + // if not visible, dispose window resources + if (!visible) { + tooltip.dispose(); + tooltip = null; + } } @@ -157,6 +172,7 @@ public void setVisible(boolean visible) { * @return Whether the text needed to be updated. */ public boolean updateText(int selectedParam) { + if (tooltip == null) initTooltipWindow(); StringBuilder sb = new StringBuilder(""); int paramCount = pc.getParamCount(); From 66ef86e3897e8a13a28f2d6b2df60381158110c2 Mon Sep 17 00:00:00 2001 From: kzoli Date: Wed, 5 Apr 2017 09:31:32 +0200 Subject: [PATCH 3/3] Enhanced completion proposals in Parameter completion --- src/main/java/org/fife/ui/autocomplete/AutoCompletion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java b/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java index 37b4369..db64b76 100644 --- a/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java +++ b/src/main/java/org/fife/ui/autocomplete/AutoCompletion.java @@ -1323,7 +1323,6 @@ public void uninstall() { UIManager.removePropertyChangeListener(lafListener); - textComponent = null; popupWindowListener.uninstall(popupWindow); // release system resources if (popupWindow != null) popupWindow.dispose(); @@ -1331,6 +1330,7 @@ public void uninstall() { hideParameterTooltips(); + textComponent = null; } }