/*
* 06/17/2012
*
* ParameritizedCompletionContext.java - Manages the state of parameterized
* completion-related UI components during code completion.
*
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.autocomplete;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.Highlighter.Highlight;
import javax.swing.text.Highlighter.HighlightPainter;
import org.fife.ui.autocomplete.ParameterizedCompletion.Parameter;
import org.fife.ui.autocomplete.ParameterizedCompletionInsertionInfo.ReplacementCopy;
import org.fife.ui.rsyntaxtextarea.DocumentRange;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.ChangeableHighlightPainter;
/**
* Manages UI and state specific to parameterized completions - the parameter
* description tool tip, the parameter completion choices list, the actual
* highlights in the editor, etc. This component installs new key bindings
* when appropriate to allow the user to cycle through the parameters of the
* completion, and optionally cycle through completion choices for those
* parameters.
*
* @author Robert Futrell
* @version 1.0
*/
class ParameterizedCompletionContext {
/**
* The parent window.
*/
private Window parentWindow;
/**
* The parent AutoCompletion instance.
*/
private AutoCompletion ac;
/**
* The completion being described.
*/
private ParameterizedCompletion pc;
/**
* Whether parameterized completion assistance is active.
*/
private boolean active;
/**
* A tool tip displaying the currently edited parameter name and type.
*/
private ParameterizedCompletionDescriptionToolTip tip;
/**
* The painter to paint borders around the variables.
*/
private Highlighter.HighlightPainter p;
private Highlighter.HighlightPainter endingP;
private Highlighter.HighlightPainter paramCopyP;
/**
* The tags for the highlights around parameters.
*/
private List<Object> tags;
private List<ParamCopyInfo> paramCopyInfos;
private transient boolean ignoringDocumentEvents;
/**
* Listens for events in the text component while this window is visible.
*/
private Listener listener;
/**
* The minimum offset into the document that the caret can move to
* before this tool tip disappears.
*/
private int minPos;
/**
* The maximum offset into the document that the caret can move to
* before this tool tip disappears.
*/
private Position maxPos; // Moves with text inserted.
private Position defaultEndOffs;
/**
* The currently "selected" parameter in the displayed text.
*/
private int lastSelectedParam;
/**
* A small popup window giving likely choices for parameterized completions.
*/
private ParameterizedCompletionChoicesWindow paramChoicesWindow;
/**
* The text before the caret for the current parameter. If
* {@link #paramChoicesWindow} is non-<code>null</code>, this is used to
* determine what parameter choices to actually show.
*/
private String paramPrefix;
private Object oldTabKey;
private Action oldTabAction;
private Object oldShiftTabKey;
private Action oldShiftTabAction;
private Object oldUpKey;
private Action oldUpAction;
private Object oldDownKey;
private Action oldDownAction;
private Object oldEnterKey;
private Action oldEnterAction;
private Object oldEscapeKey;
private Action oldEscapeAction;
private Object oldClosingKey;
private Action oldClosingAction;
private static final String IM_KEY_TAB = "ParamCompKey.Tab";
private static final String IM_KEY_SHIFT_TAB = "ParamCompKey.ShiftTab";
private static final String IM_KEY_UP = "ParamCompKey.Up";
private static final String IM_KEY_DOWN = "ParamCompKey.Down";
private static final String IM_KEY_ESCAPE = "ParamCompKey.Escape";
private static final String IM_KEY_ENTER = "ParamCompKey.Enter";
private static final String IM_KEY_CLOSING = "ParamCompKey.Closing";
/**
* Constructor.
*/
public ParameterizedCompletionContext(Window owner,
AutoCompletion ac, ParameterizedCompletion pc) {
this.parentWindow = owner;
this.ac = ac;
this.pc = pc;
listener = new Listener();
AutoCompletionStyleContext sc = AutoCompletion.getStyleContext();
p = new OutlineHighlightPainter(sc.getParameterOutlineColor());
endingP = new OutlineHighlightPainter(
sc.getParameterizedCompletionCursorPositionColor());
paramCopyP = new ChangeableHighlightPainter(sc.getParameterCopyColor());
tags = new ArrayList<Object>(1); // Usually small
paramCopyInfos = new ArrayList<ParamCopyInfo>(1);
}
/**
* Activates parameter completion support.
*
* @see #deactivate()
*/
public void activate() {
if (active) {
return;
}
active = true;
JTextComponent tc = ac.getTextComponent();
lastSelectedParam = -1;
if (pc.getShowParameterToolTip()) {
tip = new ParameterizedCompletionDescriptionToolTip(
parentWindow, this, ac, pc);
try {
int dot = tc.getCaretPosition();
Rectangle r = tc.modelToView(dot);
Point p = new Point(r.x, r.y);
SwingUtilities.convertPointToScreen(p, tc);
r.x = p.x;
r.y = p.y;
tip.setLocationRelativeTo(r);
tip.setVisible(true);
} catch (BadLocationException ble) { // Should never happen
UIManager.getLookAndFeel().provideErrorFeedback(tc);
ble.printStackTrace();
tip = null;
}
}
listener.install(tc);
// First time through, we'll need to create this window.
if (paramChoicesWindow==null) {
paramChoicesWindow = createParamChoicesWindow();
}
lastSelectedParam = getCurrentParameterIndex();
prepareParamChoicesWindow();
paramChoicesWindow.setVisible(true);
}
/**
* Creates the completion window offering suggestions for parameters.
*
* @return The window.
*/
private ParameterizedCompletionChoicesWindow createParamChoicesWindow() {
ParameterizedCompletionChoicesWindow pcw =
new ParameterizedCompletionChoicesWindow(parentWindow,
ac, this);
pcw.initialize(pc);
return pcw;
}
/**
* Hides any popup windows and terminates parameterized completion
* assistance.
*
* @see #activate()
*/
public void deactivate() {
if (!active) {
return;
}
active = false;
listener.uninstall();
if (tip!=null) {
tip.setVisible(false);
}
if (paramChoicesWindow!=null) {
paramChoicesWindow.setVisible(false);
}
}
/**
* Returns the text inserted for the parameter containing the specified
* offset.
*
* @param offs The offset into the document.
* @return The text of the parameter containing the offset, or
* <code>null</code> if the offset is not in a parameter.
*/
public String getArgumentText(int offs) {
List<Highlight> paramHighlights = getParameterHighlights();
if (paramHighlights==null || paramHighlights.size()==0) {
return null;
}
for (Highlight h : paramHighlights) {
if (offs>=h.getStartOffset() && offs<=h.getEndOffset()) {
int start = h.getStartOffset() + 1;
int len = h.getEndOffset() - start;
JTextComponent tc = ac.getTextComponent();
Document doc = tc.getDocument();
try {
return doc.getText(start, len);
} catch (BadLocationException ble) {
UIManager.getLookAndFeel().provideErrorFeedback(tc);
ble.printStackTrace();
return null;
}
}
}
return null;
}
/**
* Returns the highlight of the current parameter.
*
* @return The current parameter's highlight, or <code>null</code> if
* the caret is not in a parameter's bounds.
* @see #getCurrentParameterStartOffset()
*/
private Highlight getCurrentParameterHighlight() {
JTextComponent tc = ac.getTextComponent();
int dot = tc.getCaretPosition();
if (dot>0) {
dot--; // Workaround for Java Highlight issues
}
List<Highlight> paramHighlights = getParameterHighlights();
for (Highlight h : paramHighlights) {
if (dot>=h.getStartOffset() && dot<h.getEndOffset()) {
return h;
}
}
return null;
}
private int getCurrentParameterIndex() {
JTextComponent tc = ac.getTextComponent();
int dot = tc.getCaretPosition();
if (dot>0) {
dot--; // Workaround for Java Highlight issues
}
List<Highlight> paramHighlights = getParameterHighlights();
for (int i=0; i<paramHighlights.size(); i++) {
Highlight h = paramHighlights.get(i);
if (dot>=h.getStartOffset() && dot<h.getEndOffset()) {
return i;
}
}
return -1;
}
/**
* Returns the starting offset of the current parameter.
*
* @return The current parameter's starting offset, or <code>-1</code> if
* the caret is not in a parameter's bounds.
* @see #getCurrentParameterHighlight()
*/
private int getCurrentParameterStartOffset() {
Highlight h = getCurrentParameterHighlight();
return h!=null ? h.getStartOffset()+1 : -1;
}
/**
* Returns the highlight from a list that comes "first" in a list. Even
* though most parameter highlights are ordered, sometimes they aren't
* (e.g. the "cursor" parameter in a template completion is always last,
* even though it can be anywhere in the template).
*
* @param highlights The list of highlights. Assumed to be non-empty.
* @return The highlight that comes first in the document.
* @see #getLastHighlight(List)
*/
private static final int getFirstHighlight(List<Highlight> highlights) {
int first = -1;
Highlight firstH = null;
for (int i=0; i<highlights.size(); i++) {
Highlight h = highlights.get(i);
if (firstH==null || h.getStartOffset()<firstH.getStartOffset()) {
firstH = h;
first = i;
}
}
return first;
}
/**
* Returns the highlight from a list that comes "last" in that list. Even
* though most parameter highlights are ordered, sometimes they aren't
* (e.g. the "cursor" parameter in a template completion is always last,
* even though it can be anywhere in the template.
*
* @param highlights The list of highlights. Assumed to be non-empty.
* @return The highlight that comes last in the document.
* @see #getFirstHighlight(List)
*/
private static final int getLastHighlight(List<Highlight> highlights) {
int last = -1;
Highlight lastH = null;
for (int i=highlights.size()-1; i>=0; i--) {
Highlight h = highlights.get(i);
if (lastH==null || h.getStartOffset()>lastH.getStartOffset()) {
lastH = h;
last = i;
}
}
return last;
}
public List<Highlight> getParameterHighlights() {
List<Highlight> paramHighlights = new ArrayList<Highlight>(2);
JTextComponent tc = ac.getTextComponent();
Highlight[] highlights = tc.getHighlighter().getHighlights();
for (int i=0; i<highlights.length; i++) {
HighlightPainter painter = highlights[i].getPainter();
if (painter==p || painter==endingP) {
paramHighlights.add(highlights[i]);
}
}
return paramHighlights;
}
/**
* Inserts the choice selected in the parameter choices window.
*
* @return Whether the choice was inserted. This will be <code>false</code>
* if the window is not visible, or no choice is selected.
*/
boolean insertSelectedChoice() {
if (paramChoicesWindow!=null && paramChoicesWindow.isVisible()) {
String choice = paramChoicesWindow.getSelectedChoice();
if (choice!=null) {
JTextComponent tc = ac.getTextComponent();
Highlight h = getCurrentParameterHighlight();
if (h!=null) {
// "+1" is a workaround for Java Highlight issues.
tc.setSelectionStart(h.getStartOffset()+1);
tc.setSelectionEnd(h.getEndOffset());
tc.replaceSelection(choice);
moveToNextParam();
}
else {
UIManager.getLookAndFeel().provideErrorFeedback(tc);
}
return true;
}
}
return false;
}
/**
* Installs key bindings on the text component that facilitate the user
* editing this completion's parameters.
*
* @see #uninstallKeyBindings()
*/
private void installKeyBindings() {
if (AutoCompletion.getDebug()) {
System.out.println("CompletionContext: Installing keybindings");
}
JTextComponent tc = ac.getTextComponent();
InputMap im = tc.getInputMap();
ActionMap am = tc.getActionMap();
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
oldTabKey = im.get(ks);
im.put(ks, IM_KEY_TAB);
oldTabAction = am.get(IM_KEY_TAB);
am.put(IM_KEY_TAB, new NextParamAction());
ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK);
oldShiftTabKey = im.get(ks);
im.put(ks, IM_KEY_SHIFT_TAB);
oldShiftTabAction = am.get(IM_KEY_SHIFT_TAB);
am.put(IM_KEY_SHIFT_TAB, new PrevParamAction());
ks = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
oldUpKey = im.get(ks);
im.put(ks, IM_KEY_UP);
oldUpAction = am.get(IM_KEY_UP);
am.put(IM_KEY_UP, new NextChoiceAction(-1, oldUpAction));
ks = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
oldDownKey = im.get(ks);
im.put(ks, IM_KEY_DOWN);
oldDownAction = am.get(IM_KEY_DOWN);
am.put(IM_KEY_DOWN, new NextChoiceAction(1, oldDownAction));
ks = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
oldEnterKey = im.get(ks);
im.put(ks, IM_KEY_ENTER);
oldEnterAction = am.get(IM_KEY_ENTER);
am.put(IM_KEY_ENTER, new GotoEndAction());
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 HideAction());
char end = pc.getProvider().getParameterListEnd();
ks = KeyStroke.getKeyStroke(end);
oldClosingKey = im.get(ks);
im.put(ks, IM_KEY_CLOSING);
oldClosingAction = am.get(IM_KEY_CLOSING);
am.put(IM_KEY_CLOSING, new ClosingAction());
}
/**
* Moves to and selects the next parameter.
*
* @see #moveToPreviousParam()
*/
private void moveToNextParam() {
JTextComponent tc = ac.getTextComponent();
int dot = tc.getCaretPosition();
int tagCount = tags.size();
if (tagCount==0) {
tc.setCaretPosition(maxPos.getOffset());
deactivate();
}
Highlight currentNext = null;
int pos = -1;
List<Highlight> highlights = getParameterHighlights();
for (int i=0; i<highlights.size(); i++) {
Highlight hl = highlights.get(i);
// Check "< dot", not "<= dot" as OutlineHighlightPainter paints
// starting at one char AFTER the highlight starts, to work around
// Java issue. Thanks to Matthew Adereth!
if (currentNext==null || currentNext.getStartOffset()</*=*/dot ||
(hl.getStartOffset()>dot &&
hl.getStartOffset()<=currentNext.getStartOffset())) {
currentNext = hl;
pos = i;
}
}
// No params after caret - go to first one
if (currentNext.getStartOffset()+1<=dot) {
int nextIndex = getFirstHighlight(highlights);
currentNext = highlights.get(nextIndex);
pos = 0;
}
// "+1" is a workaround for Java Highlight issues.
tc.setSelectionStart(currentNext.getStartOffset()+1);
tc.setSelectionEnd(currentNext.getEndOffset());
updateToolTipText(pos);
}
/**
* Moves to and selects the previous parameter.
*
* @see #moveToNextParam()
*/
private void moveToPreviousParam() {
JTextComponent tc = ac.getTextComponent();
int tagCount = tags.size();
if (tagCount==0) { // Should never happen
tc.setCaretPosition(maxPos.getOffset());
deactivate();
}
int dot = tc.getCaretPosition();
int selStart = tc.getSelectionStart()-1; // Workaround for Java Highlight issues.
Highlight currentPrev = null;
int pos = 0;
List<Highlight> highlights = getParameterHighlights();
for (int i=0; i<highlights.size(); i++) {
Highlight h = highlights.get(i);
if (currentPrev==null || currentPrev.getStartOffset()>=dot ||
(h.getStartOffset()<selStart &&
(h.getStartOffset()>currentPrev.getStartOffset() ||
pos==lastSelectedParam))) {
currentPrev = h;
pos = i;
}
}
// Loop back from param 0 to last param.
int firstIndex = getFirstHighlight(highlights);
//if (pos==0 && lastSelectedParam==0 && highlights.size()>1) {
if (pos==firstIndex && lastSelectedParam==firstIndex && highlights.size()>1) {
pos = getLastHighlight(highlights);
currentPrev = highlights.get(pos);
// "+1" is a workaround for Java Highlight issues.
tc.setSelectionStart(currentPrev.getStartOffset()+1);
tc.setSelectionEnd(currentPrev.getEndOffset());
updateToolTipText(pos);
}
else if (currentPrev!=null && dot>currentPrev.getStartOffset()) {
// "+1" is a workaround for Java Highlight issues.
tc.setSelectionStart(currentPrev.getStartOffset()+1);
tc.setSelectionEnd(currentPrev.getEndOffset());
updateToolTipText(pos);
}
else {
tc.setCaretPosition(maxPos.getOffset());
deactivate();
}
}
private void possiblyUpdateParamCopies(Document doc) {
int index = getCurrentParameterIndex();
// FunctionCompletions add an extra param at end of inserted text
if (index>-1 && index<pc.getParamCount()) {
// Typing in an "end parameter" => stop parameter assistance.
Parameter param = pc.getParam(index);
if (param.isEndParam()) {
deactivate();
return;
}
// Get the current value of the current parameter.
List<Highlight> paramHighlights = getParameterHighlights();
Highlight h = paramHighlights.get(index);
int start = h.getStartOffset() + 1; // param offsets are offset (!) by 1
int len = h.getEndOffset() - start;
String replacement = null;
try {
replacement = doc.getText(start, len);
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
// Replace any param copies tracking this parameter with the
// value of this parameter.
for (ParamCopyInfo pci : paramCopyInfos) {
if (pci.paramName.equals(param.getName())) {
pci.h = replaceHighlightedText(doc, pci.h, replacement);
}
}
}
else { // Probably the "end parameter" for FunctionCompletions.
deactivate();
}
}
/**
* Updates the optional window listing likely completion choices,
*/
private void prepareParamChoicesWindow() {
// If this window was set to null, the user pressed Escape to hide it
if (paramChoicesWindow!=null) {
int offs = getCurrentParameterStartOffset();
if (offs==-1) {
paramChoicesWindow.setVisible(false);
return;
}
JTextComponent tc = ac.getTextComponent();
try {
Rectangle r = tc.modelToView(offs);
Point p = new Point(r.x, r.y);
SwingUtilities.convertPointToScreen(p, tc);
r.x = p.x;
r.y = p.y;
paramChoicesWindow.setLocationRelativeTo(r);
} catch (BadLocationException ble) { // Should never happen
UIManager.getLookAndFeel().provideErrorFeedback(tc);
ble.printStackTrace();
}
// Toggles visibility, if necessary.
paramChoicesWindow.setParameter(lastSelectedParam, paramPrefix);
}
}
/**
* Removes the bounding boxes around parameters.
*/
private void removeParameterHighlights() {
JTextComponent tc = ac.getTextComponent();
Highlighter h = tc.getHighlighter();
for (int i=0; i<tags.size(); i++) {
h.removeHighlight(tags.get(i));
}
tags.clear();
for (ParamCopyInfo pci : paramCopyInfos) {
h.removeHighlight(pci.h);
}
paramCopyInfos.clear();
}
/**
* Replaces highlighted text with new text. Takes special care so that
* the highlight stays just around the newly-highlighted text, since
* Swing's <code>Highlight</code> classes are funny about insertions at
* their start offsets.
*
* @param doc The document.
* @param h The highlight whose text to change.
* @param replacement The new text to be in the highlight.
* @return The replacement highlight for <code>h</code>.
*/
private Highlight replaceHighlightedText(Document doc, Highlight h,
String replacement) {
try {
int start = h.getStartOffset();
int len = h.getEndOffset() - start;
Highlighter highlighter = ac.getTextComponent().getHighlighter();
highlighter.removeHighlight(h);
if (doc instanceof AbstractDocument) {
((AbstractDocument)doc).replace(start, len, replacement, null);
}
else {
doc.remove(start, len);
doc.insertString(start, replacement, null);
}
int newEnd = start + replacement.length();
h = (Highlight)highlighter.addHighlight(start, newEnd, paramCopyP);
return h;
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
return null;
}
/**
* Removes the key bindings we installed.
*
* @see #installKeyBindings()
*/
private void uninstallKeyBindings() {
if (AutoCompletion.getDebug()) {
System.out.println("CompletionContext Uninstalling keybindings");
}
JTextComponent tc = ac.getTextComponent();
InputMap im = tc.getInputMap();
ActionMap am = tc.getActionMap();
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
im.put(ks, oldTabKey);
am.put(IM_KEY_TAB, oldTabAction);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK);
im.put(ks, oldShiftTabKey);
am.put(IM_KEY_SHIFT_TAB, oldShiftTabAction);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
im.put(ks, oldUpKey);
am.put(IM_KEY_UP, oldUpAction);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
im.put(ks, oldDownKey);
am.put(IM_KEY_DOWN, oldDownAction);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
im.put(ks, oldEnterKey);
am.put(IM_KEY_ENTER, oldEnterAction);
ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
im.put(ks, oldEscapeKey);
am.put(IM_KEY_ESCAPE, oldEscapeAction);
char end = pc.getProvider().getParameterListEnd();
ks = KeyStroke.getKeyStroke(end);
im.put(ks, oldClosingKey);
am.put(IM_KEY_CLOSING, oldClosingAction);
}
/**
* Updates the text in the tool tip to have the current parameter
* displayed in bold. The "current parameter" is determined from the
* current caret position.
*
* @return The "prefix" of text in the caret's parameter before the caret.
*/
private String updateToolTipText() {
JTextComponent tc = ac.getTextComponent();
int dot = tc.getSelectionStart();
int mark = tc.getSelectionEnd();
int index = -1;
String paramPrefix = null;
List<Highlight> paramHighlights = getParameterHighlights();
for (int i=0; i<paramHighlights.size(); i++) {
Highlight h = paramHighlights.get(i);
// "+1" because of param hack - see OutlineHighlightPainter
int start = h.getStartOffset()+1;
if (dot>=start && dot<=h.getEndOffset()) {
try {
// All text selected => offer all suggestions, otherwise
// use prefix before selection
if (dot!=start || mark!=h.getEndOffset()) {
paramPrefix = tc.getText(start, dot-start);
}
} catch (BadLocationException ble) {
ble.printStackTrace();
}
index = i;
break;
}
}
updateToolTipText(index);
return paramPrefix;
}
private void updateToolTipText(int selectedParam) {
if (selectedParam!=lastSelectedParam) {
if (tip!=null) {
tip.updateText(selectedParam);
}
this.lastSelectedParam = selectedParam;
}
}
/**
* Updates the <code>LookAndFeel</code> of all popup windows this context
* manages.
*/
public void updateUI() {
if (tip!=null) {
tip.updateUI();
}
if (paramChoicesWindow!=null) {
paramChoicesWindow.updateUI();
}
}
/**
* Called when the user presses Enter while entering parameters.
*/
private class GotoEndAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
// If the param choices window is visible and something is chosen,
// replace the parameter with it and move to the next one.
if (paramChoicesWindow!=null && paramChoicesWindow.isVisible()) {
if (insertSelectedChoice()) {
return;
}
}
// Otherwise, just move to the end.
deactivate();
JTextComponent tc = ac.getTextComponent();
int dot = tc.getCaretPosition();
if (dot!=defaultEndOffs.getOffset()) {
tc.setCaretPosition(defaultEndOffs.getOffset());
}
else {
// oldEnterAction isn't what we're looking for (wrong key)
Action a = getDefaultEnterAction(tc);
if (a!=null) {
a.actionPerformed(e);
}
else {
tc.replaceSelection("\n");
}
}
}
private Action getDefaultEnterAction(JTextComponent tc) {
ActionMap am = tc.getActionMap();
return am.get(DefaultEditorKit.insertBreakAction);
}
}
/**
* Called when the user types the character marking the closing of the
* parameter list, such as '<code>)</code>'.
*/
private class ClosingAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
JTextComponent tc = ac.getTextComponent();
int dot = tc.getCaretPosition();
char end = pc.getProvider().getParameterListEnd();
// Are they at or past the end of the parameters?
if (dot>=maxPos.getOffset()-2) { // ">=" for overwrite mode
// Try to decide if we're closing a paren that is a part
// of the (last) arg being typed.
String text = getArgumentText(dot);
if (text!=null) {
char start = pc.getProvider().getParameterListStart();
int startCount = getCount(text, start);
int endCount = getCount(text, end);
if (startCount>endCount) { // Just closing a paren
tc.replaceSelection(Character.toString(end));
return;
}
}
//tc.setCaretPosition(maxPos.getOffset());
tc.setCaretPosition(Math.min(tc.getCaretPosition()+1,
tc.getDocument().getLength()));
deactivate();
}
// If not (in the middle of parameters), just insert the paren.
else {
tc.replaceSelection(Character.toString(end));
}
}
public int getCount(String text, char ch) {
int count = 0;
int old = 0;
int pos = 0;
while ((pos=text.indexOf(ch, old))>-1) {
count++;
old = pos + 1;
}
return count;
}
}
/**
* Action performed when the user hits the escape key.
*/
private class HideAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
// On first escape press, if the param choices window is visible,
// just remove it, but keep ability to tab through params. If
// param choices window isn't visible, or second escape press,
// exit tabbing through params entirely.
if (paramChoicesWindow!=null && paramChoicesWindow.isVisible()) {
paramChoicesWindow.setVisible(false);
paramChoicesWindow = null;
}
else {
deactivate();
}
}
}
/**
* Listens for various events in the text component while this tool tip
* is visible.
*/
private class Listener implements FocusListener, CaretListener,
DocumentListener {
private boolean markOccurrencesEnabled;
/**
* Called when the text component's caret moves.
*
* @param e The event.
*/
public void caretUpdate(CaretEvent e) {
if (maxPos==null) { // Sanity check
deactivate();
return;
}
int dot = e.getDot();
if (dot<minPos || dot>maxPos.getOffset()) {
deactivate();
return;
}
paramPrefix = updateToolTipText();
if (active) {
prepareParamChoicesWindow();
}
}
public void changedUpdate(DocumentEvent e) {
}
/**
* Called when the text component gains focus.
*
* @param e The event.
*/
public void focusGained(FocusEvent e) {
// Do nothing
}
/**
* Called when the text component loses focus.
*
* @param e The event.
*/
public void focusLost(FocusEvent e) {
deactivate();
}
private void handleDocumentEvent(final DocumentEvent e) {
if (!ignoringDocumentEvents) {
ignoringDocumentEvents = true;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
possiblyUpdateParamCopies(e.getDocument());
ignoringDocumentEvents = false;
}
});
}
}
public void insertUpdate(DocumentEvent e) {
handleDocumentEvent(e);
}
/**
* Installs this listener onto a text component.
*
* @param tc The text component to install onto.
* @see #uninstall()
*/
public void install(JTextComponent tc) {
boolean replaceTabs = false;
if (tc instanceof RSyntaxTextArea) {
RSyntaxTextArea textArea = (RSyntaxTextArea)tc;
markOccurrencesEnabled = textArea.getMarkOccurrences();
textArea.setMarkOccurrences(false);
replaceTabs = textArea.getTabsEmulated();
}
Highlighter h = tc.getHighlighter();
try {
// Insert the parameter text
ParameterizedCompletionInsertionInfo info =
pc.getInsertionInfo(tc, replaceTabs);
tc.replaceSelection(info.getTextToInsert());
// Add highlights around the parameters.
final int replacementCount = info.getReplacementCount();
for (int i=0; i<replacementCount; i++) {
DocumentRange dr = info.getReplacementLocation(i);
HighlightPainter painter = i<replacementCount-1 ? p : endingP;
// "-1" is a workaround for Java Highlight issues.
tags.add(h.addHighlight(
dr.getStartOffset()-1, dr.getEndOffset(), painter));
}
for (int i=0; i<info.getReplacementCopyCount(); i++) {
ReplacementCopy rc = info.getReplacementCopy(i);
paramCopyInfos.add(new ParamCopyInfo(rc.getId(),
(Highlight)h.addHighlight(rc.getStart(), rc.getEnd(),
paramCopyP)));
}
// Go back and start at the first parameter.
tc.setCaretPosition(info.getSelectionStart());
if (info.hasSelection()) {
tc.moveCaretPosition(info.getSelectionEnd());
}
minPos = info.getMinOffset();
maxPos = info.getMaxOffset();
try {
Document doc = tc.getDocument();
if (maxPos.getOffset()==0) {
// Positions at offset 0 don't track document changes,
// so we must manually do this here. This is not a
// common occurrence.
maxPos = doc.createPosition(
info.getTextToInsert().length());
}
defaultEndOffs = doc.createPosition(
info.getDefaultEndOffs());
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
// Listen for document events AFTER we insert
tc.getDocument().addDocumentListener(this);
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
// Add listeners to the text component, AFTER text insertion.
tc.addCaretListener(this);
tc.addFocusListener(this);
installKeyBindings();
}
public void removeUpdate(DocumentEvent e) {
handleDocumentEvent(e);
}
/**
* Uninstalls this listener from the current text component.
*/
public void uninstall() {
JTextComponent tc = ac.getTextComponent();
tc.removeCaretListener(this);
tc.removeFocusListener(this);
tc.getDocument().removeDocumentListener(this);
uninstallKeyBindings();
if (markOccurrencesEnabled) {
((RSyntaxTextArea)tc).setMarkOccurrences(markOccurrencesEnabled);
}
// Remove WeakReferences in javax.swing.text.
maxPos = null;
minPos = -1;
removeParameterHighlights();
}
}
/**
* Action performed when the user presses the up or down arrow keys and
* the parameter completion choices popup is visible.
*/
private class NextChoiceAction extends AbstractAction {
private Action oldAction;
private int amount;
public NextChoiceAction(int amount, Action oldAction) {
this.amount = amount;
this.oldAction = oldAction;
}
public void actionPerformed(ActionEvent e) {
if (paramChoicesWindow!=null && paramChoicesWindow.isVisible()) {
paramChoicesWindow.incSelection(amount);
}
else if (oldAction!=null) {
oldAction.actionPerformed(e);
}
else {
deactivate();
}
}
}
/**
* Action performed when the user hits the tab key.
*/
private class NextParamAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
moveToNextParam();
}
}
private static class ParamCopyInfo {
private String paramName;
private Highlight h;
public ParamCopyInfo(String paramName, Highlight h) {
this.paramName = paramName;
this.h = h;
}
}
/**
* Action performed when the user hits shift+tab.
*/
private class PrevParamAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
moveToPreviousParam();
}
}
}