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..db64b76 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
@@ -1218,10 +1323,14 @@ public void uninstall() {
UIManager.removePropertyChangeListener(lafListener);
- textComponent = null;
popupWindowListener.uninstall(popupWindow);
+ // release system resources
+ if (popupWindow != null) popupWindow.dispose();
popupWindow = null;
+ hideParameterTooltips();
+
+ textComponent = null;
}
}
@@ -1531,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/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 && paramscreenBounds.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();
diff --git a/src/main/java/org/fife/ui/autocomplete/Util.java b/src/main/java/org/fife/ui/autocomplete/Util.java
index c563e17..d0a8e90 100644
--- a/src/main/java/org/fife/ui/autocomplete/Util.java
+++ b/src/main/java/org/fife/ui/autocomplete/Util.java
@@ -16,6 +16,8 @@
import java.lang.reflect.Method;
import java.net.URI;
import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.regex.Pattern;
import javax.swing.JLabel;
@@ -319,5 +321,56 @@ public static String stripHtml(String text) {
}
-
+ /**
+ * Return text parts list for given text split at uppercase characters
+ *
+ * @param text
+ * @return
+ */
+ public static List 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