[autocomplete] 87/143: Starting work on TemplateCompletions - allows completions for parameterized boilerplate code - for-loops, etc.

Benjamin Mesing ben at alioth.debian.org
Sat Oct 19 12:53:23 UTC 2013


This is an automated email from the git hooks/post-receive script.

ben pushed a commit to branch master
in repository autocomplete.

commit ff7db970571a3b5f306efd94bea5092198da5aeb
Author: bobbylight <robert at fifesoft.com>
Date:   Sun Jun 17 17:33:03 2012 +0000

    Starting work on TemplateCompletions - allows completions for parameterized boilerplate code - for-loops, etc.
---
 .../fife/ui/autocomplete/FunctionCompletion.java   |   15 +-
 .../ui/autocomplete/ParameterizedCompletion.java   |   37 +-
 .../ParameterizedCompletionChoicesWindow.java      |    4 +
 .../ParameterizedCompletionDescriptionToolTip.java |  376 ++++++++++++++++----
 .../ParameterizedCompletionInsertionInfo.java      |   67 +++-
 src/org/fife/ui/autocomplete/SizeGrip.java         |    3 -
 .../fife/ui/autocomplete/TemplateCompletion.java   |  213 ++++++++---
 src/org/fife/ui/autocomplete/TemplatePiece.java    |   63 ++++
 8 files changed, 647 insertions(+), 131 deletions(-)

diff --git a/src/org/fife/ui/autocomplete/FunctionCompletion.java b/src/org/fife/ui/autocomplete/FunctionCompletion.java
index 9f9b13e..427d146 100644
--- a/src/org/fife/ui/autocomplete/FunctionCompletion.java
+++ b/src/org/fife/ui/autocomplete/FunctionCompletion.java
@@ -147,7 +147,8 @@ public class FunctionCompletion extends VariableCompletion
 
 
 	public ParameterizedCompletionInsertionInfo getInsertionInfo(
-			JTextComponent tc, boolean addParamStartList) {
+			JTextComponent tc, boolean addParamStartList,
+			boolean replaceTabsWithSpaces) {
 
 		ParameterizedCompletionInsertionInfo info =
 			new ParameterizedCompletionInsertionInfo();
@@ -163,8 +164,10 @@ public class FunctionCompletion extends VariableCompletion
 		// this tool tip.
 		int minPos = dot;
 		Position maxPos = null;
+		int defaultEndOffs = -1;
 		try {
-			maxPos = tc.getDocument().createPosition(dot-sb.length());
+			maxPos = tc.getDocument().createPosition(dot-sb.length()+1);
+			defaultEndOffs = dot-sb.length();
 		} catch (BadLocationException ble) {
 			ble.printStackTrace(); // Never happens
 		}
@@ -192,7 +195,13 @@ public class FunctionCompletion extends VariableCompletion
 			}
 		}
 		sb.append(getProvider().getParameterListEnd());
-
+		int endOffs = dot + sb.length();
+		if (addParamStartList) {
+			endOffs -= 1;//getProvider().getParameterListStart().length();
+		}
+		info.addReplacementLocation(endOffs, endOffs); // offset after function
+		info.setDefaultEndOffs(endOffs);
+		
 		int selectionEnd = paramCount>0 ? (dot+firstParamLen) : dot;
 		info.setInitialSelection(dot, selectionEnd);
 		info.setTextToInsert(sb.toString());
diff --git a/src/org/fife/ui/autocomplete/ParameterizedCompletion.java b/src/org/fife/ui/autocomplete/ParameterizedCompletion.java
index 1d995f0..acd674f 100644
--- a/src/org/fife/ui/autocomplete/ParameterizedCompletion.java
+++ b/src/org/fife/ui/autocomplete/ParameterizedCompletion.java
@@ -50,7 +50,8 @@ public interface ParameterizedCompletion extends Completion {
 
 
 	public ParameterizedCompletionInsertionInfo getInsertionInfo(
-			JTextComponent tc, boolean addParamStartList);
+			JTextComponent tc, boolean addParamStartList,
+			boolean replaceTabsWithSpaces);
 
 
 	/**
@@ -64,6 +65,7 @@ public interface ParameterizedCompletion extends Completion {
 		private String name;
 		private Object type;
 		private String desc;
+		private boolean isEndParam;
 
 		/**
 		 * Constructor.
@@ -77,8 +79,30 @@ public interface ParameterizedCompletion extends Completion {
 		 * @param name The name of the parameter.
 		 */
 		public Parameter(Object type, String name) {
+			this(type, name, false);
+		}
+
+		/**
+		 * Constructor.
+		 *
+		 * @param type The type of this parameter.  This may be
+		 *        <code>null</code> for languages without specific types,
+		 *        dynamic typing, etc.  Usually you'll pass a String for this
+		 *        value, but you may pass any object representing a type in
+		 *        your language, as long as its <code>toString()</code> method
+		 *        returns a string representation of the type.
+		 * @param name The name of the parameter.
+		 * @param endParam Whether this parameter is an "ending parameter;"
+		 *        that is, whether this parameter is at a logical "ending
+		 *        point" in the completion text.  If the user types in a
+		 *        parameter that is an ending point, parameter completion mode
+		 *        terminates.  Set this to <code>true</code> for a trailing
+		 *        parameter after a function call's closing ')', for example.
+		 */
+		public Parameter(Object type, String name, boolean endParam) {
 			this.name = name;
 			this.type = type;
+			this.isEndParam = endParam;
 		}
 
 		public String getDescription() {
@@ -107,6 +131,17 @@ public interface ParameterizedCompletion extends Completion {
 			return type;
 		}
 
+		/**
+		 * @return Whether this parameter is an "ending parameter;"
+		 *         that is, whether this parameter is at a logical "ending
+		 *         point" in the completion text.  If the user types in a
+		 *         parameter that is an ending point, parameter completion mode
+		 *         terminates.
+		 */
+		public boolean isEndParam() {
+			return isEndParam;
+		}
+
 		public void setDescription(String desc) {
 			this.desc = desc;
 		}
diff --git a/src/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java b/src/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java
index cd553cf..31f21e8 100644
--- a/src/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java
+++ b/src/org/fife/ui/autocomplete/ParameterizedCompletionChoicesWindow.java
@@ -280,6 +280,10 @@ public class ParameterizedCompletionChoicesWindow extends JWindow {
 
 		}
 
+		else {
+			setVisible(false);
+		}
+
 	}
 
 
diff --git a/src/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java b/src/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java
index 677c2f7..47a1e48 100644
--- a/src/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java
+++ b/src/org/fife/ui/autocomplete/ParameterizedCompletionDescriptionToolTip.java
@@ -20,6 +20,7 @@ import java.awt.event.FocusListener;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import javax.swing.AbstractAction;
 import javax.swing.Action;
@@ -34,6 +35,9 @@ 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.Document;
 import javax.swing.text.Highlighter;
@@ -41,7 +45,13 @@ import javax.swing.text.JTextComponent;
 import javax.swing.text.Position;
 import javax.swing.text.Highlighter.Highlight;
 
+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.PopupWindowDecorator;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
+import org.fife.ui.rtextarea.ChangeableHighlightPainter;
 
 
 /**
@@ -63,11 +73,17 @@ class ParameterizedCompletionDescriptionToolTip {
 	 */
 	Highlighter.HighlightPainter p;
 
+	Highlighter.HighlightPainter paramCopyP;
+
 	/**
 	 * The tags for the highlights around parameters.
 	 */
 	List tags;
 
+	private List paramCopyInfos;
+
+	private transient boolean ignoringDocumentEvents;
+
 	/**
 	 * The parent AutoCompletion instance.
 	 */
@@ -100,6 +116,8 @@ class ParameterizedCompletionDescriptionToolTip {
 	 */
 	private Position maxPos; // Moves with text inserted.
 
+	private Position defaultEndOffs;
+
 	/**
 	 * A small popup window giving likely choices for parameterized completions.
 	 */
@@ -158,7 +176,7 @@ class ParameterizedCompletionDescriptionToolTip {
 
 		descLabel = new JLabel();
 		descLabel.setBorder(BorderFactory.createCompoundBorder(
-					BorderFactory.createLineBorder(Color.BLACK),
+					TipUtil.getToolTipBorder(),
 					BorderFactory.createEmptyBorder(2, 5, 2, 5)));
 		descLabel.setOpaque(true);
 		descLabel.setBackground(TipUtil.getToolTipBackground());
@@ -185,7 +203,9 @@ class ParameterizedCompletionDescriptionToolTip {
 		listener = new Listener();
 
 		p = new OutlineHighlightPainter(Color.GRAY);
+		paramCopyP = new ChangeableHighlightPainter(new Color(255, 224, 224));
 		tags = new ArrayList(1); // Usually small
+		paramCopyInfos = new ArrayList(1);
 
 		paramChoicesWindow = createParamChoicesWindow();
 
@@ -206,6 +226,31 @@ class ParameterizedCompletionDescriptionToolTip {
 	}
 
 
+	public String getArgumentText(int offs) {
+		List paramHighlights = getParameterHighlights();
+		if (paramHighlights==null || paramHighlights.size()==0) {
+			return null;
+		}
+		for (int i=0; i<paramHighlights.size(); i++) {
+			Highlight h = (Highlight)paramHighlights.get(i);
+			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.
 	 *
@@ -234,6 +279,27 @@ class ParameterizedCompletionDescriptionToolTip {
 	}
 
 
+	private int getCurrentParameterIndex() {
+
+		JTextComponent tc = ac.getTextComponent();
+		int dot = tc.getCaretPosition();
+		if (dot>0) {
+			dot--; // Workaround for Java Highlight issues
+		}
+
+		List paramHighlights = getParameterHighlights();
+		for (int i=0; i<paramHighlights.size(); i++) {
+			Highlight h = (Highlight)paramHighlights.get(i);
+			if (dot>=h.getStartOffset() && dot<h.getEndOffset()) {
+				return i;
+			}
+		}
+
+		return -1;
+
+	}
+
+
 	/**
 	 * Returns the starting offset of the current parameter.
 	 *
@@ -247,6 +313,54 @@ class ParameterizedCompletionDescriptionToolTip {
 	}
 
 
+	/**
+	 * 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 highlights) {
+		int first = -1;
+		Highlight firstH = null;
+		for (int i=0; i<highlights.size(); i++) {
+			Highlight h = (Highlight)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 highlights) {
+		int last = -1;
+		Highlight lastH = null;
+		for (int i=highlights.size()-1; i>=0; i--) {
+			Highlight h = (Highlight)highlights.get(i);
+			if (lastH==null || h.getStartOffset()>lastH.getStartOffset()) {
+				lastH = h;
+				last = i;
+			}
+		}
+		return last;
+	}
+
+
 	private List getParameterHighlights() {
 		List paramHighlights = new ArrayList(1);
 		JTextComponent tc = ac.getTextComponent();
@@ -360,7 +474,6 @@ class ParameterizedCompletionDescriptionToolTip {
 
 		JTextComponent tc = ac.getTextComponent();
 		int dot = tc.getCaretPosition();
-
 		int tagCount = tags.size();
 		if (tagCount==0) {
 			tc.setCaretPosition(maxPos.getOffset());
@@ -383,17 +496,18 @@ class ParameterizedCompletionDescriptionToolTip {
 			}
 		}
 
-		if (currentNext!=null && dot<=currentNext.getStartOffset()) {
-			 // "+1" is a workaround for Java Highlight issues.
-			tc.setSelectionStart(currentNext.getStartOffset()+1);
-			tc.setSelectionEnd(currentNext.getEndOffset());
-			updateText(pos);
-		}
-		else {
-			tc.setCaretPosition(maxPos.getOffset());
-			setVisible(false, false);
+		// No params after caret - go to first one
+		if (currentNext.getStartOffset()+1<=dot) {
+			int nextIndex = getFirstHighlight(highlights);
+			currentNext = (Highlight)highlights.get(nextIndex);
+			pos = 0;
 		}
 
+		// "+1" is a workaround for Java Highlight issues.
+		tc.setSelectionStart(currentNext.getStartOffset()+1);
+		tc.setSelectionEnd(currentNext.getEndOffset());
+		updateText(pos);
+
 	}
 
 
@@ -422,15 +536,18 @@ class ParameterizedCompletionDescriptionToolTip {
 			Highlight h = (Highlight)highlights.get(i);
 			if (currentPrev==null || currentPrev.getStartOffset()>=dot ||
 					(h.getStartOffset()<selStart &&
-					h.getStartOffset()>currentPrev.getStartOffset())) {
+					(h.getStartOffset()>currentPrev.getStartOffset() ||
+							pos==lastSelectedParam))) {
 				currentPrev = h;
 				pos = i;
 			}
 		}
 
 		// Loop back from param 0 to last param.
-		if (pos==0 && lastSelectedParam==0 && highlights.size()>1) {
-			pos = highlights.size() - 1;
+		int firstIndex = getFirstHighlight(highlights);
+		//if (pos==0 && lastSelectedParam==0 && highlights.size()>1) {
+		if (pos==firstIndex && lastSelectedParam==firstIndex && highlights.size()>1) {
+			pos = getLastHighlight(highlights);
 			currentPrev = (Highlight)highlights.get(pos);
 			 // "+1" is a workaround for Java Highlight issues.
 			tc.setSelectionStart(currentPrev.getStartOffset()+1);
@@ -451,6 +568,49 @@ class ParameterizedCompletionDescriptionToolTip {
 	}
 
 
+	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()) {
+				setVisible(false, false);
+				return;
+			}
+
+			// Get the current value of the current parameter.
+			List paramHighlights = getParameterHighlights();
+			Highlight h = (Highlight)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 (Iterator i=paramCopyInfos.iterator(); i.hasNext(); ) {
+				ParamCopyInfo pci = (ParamCopyInfo)i.next();
+				if (pci.paramName.equals(param.getName())) {
+					pci.h = replaceHighlightedText(doc, pci.h, replacement);
+				}
+			}
+
+		}
+
+		else { // Probably the "end parameter" for FunctionCompletions.
+			setVisible(false, false);
+		}
+
+	}
+
+
 	/**
 	 * Updates the optional window listing likely completion choices,
 	 */
@@ -496,6 +656,52 @@ class ParameterizedCompletionDescriptionToolTip {
 			h.removeHighlight(tags.get(i));
 		}
 		tags.clear();
+		for (int i=0; i<paramCopyInfos.size(); i++) {
+			ParamCopyInfo pci = (ParamCopyInfo)paramCopyInfos.get(i);
+			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;
+
 	}
 
 
@@ -693,28 +899,7 @@ class ParameterizedCompletionDescriptionToolTip {
 			// (such as type parameters in Java).  We need to take care to
 			// escape these.
 			String temp = pc.getParam(i).toString();
-			int lt = temp.indexOf('<');
-			if (lt>-1) {
-				sb.append(temp.substring(0, lt));
-				sb.append("<");
-				for (int j=lt+1; j<temp.length(); j++) {
-					char ch = temp.charAt(j);
-					switch (ch) {
-						case '<':
-							sb.append("<");
-							break;
-						case '>':
-							sb.append(">");
-							break;
-						default:
-							sb.append(ch);
-							break;
-					}
-				}
-			}
-			else {
-				sb.append(temp);
-			}
+			sb.append(RSyntaxUtilities.escapeForHtml(temp, "<br>", false));
 
 			if (i==selectedParam) {
 				sb.append("</b>");
@@ -775,7 +960,7 @@ class ParameterizedCompletionDescriptionToolTip {
 
 			// Otherwise, just move to the end.
 			JTextComponent tc = ac.getTextComponent();
-			tc.setCaretPosition(maxPos.getOffset());
+			tc.setCaretPosition(defaultEndOffs.getOffset());
 			setVisible(false, false);
 
 		}
@@ -799,9 +984,9 @@ class ParameterizedCompletionDescriptionToolTip {
 			char end = pc.getProvider().getParameterListEnd();
 
 			// Are they at or past the end of the parameters?
-			if (dot>=maxPos.getOffset()-1) { // ">=" for overwrite mode
+			if (dot>=maxPos.getOffset()-2) { // ">=" for overwrite mode
 
-				if (dot==maxPos.getOffset()) { // Happens in overwrite mode
+				if (dot==maxPos.getOffset()-1) { // Happens in overwrite mode
 					tc.replaceSelection(Character.toString(end));
 				}
 
@@ -818,7 +1003,8 @@ class ParameterizedCompletionDescriptionToolTip {
 							return;
 						}
 					}
-					tc.setCaretPosition(maxPos.getOffset());
+					//tc.setCaretPosition(maxPos.getOffset());
+					tc.setCaretPosition(tc.getCaretPosition()+1);
 				}
 
 				setVisible(false, false);
@@ -832,30 +1018,6 @@ class ParameterizedCompletionDescriptionToolTip {
 
 		}
 
-		public String getArgumentText(int offs) {
-			List paramHighlights = getParameterHighlights();
-			if (paramHighlights==null || paramHighlights.size()==0) {
-				return null;
-			}
-			for (int i=0; i<paramHighlights.size(); i++) {
-				Highlight h = (Highlight)paramHighlights.get(i);
-				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;
-		}
-
 		public int getCount(String text, char ch) {
 			int count = 0;
 			int old = 0;
@@ -903,7 +1065,10 @@ class ParameterizedCompletionDescriptionToolTip {
 	 * @author Robert Futrell
 	 * @version 1.0
 	 */
-	private class Listener implements FocusListener, CaretListener {
+	private class Listener implements FocusListener, CaretListener,
+							DocumentListener {
+
+		private boolean markOccurrencesEnabled;
 
 		/**
 		 * Called when the text component's caret moves.
@@ -917,6 +1082,7 @@ class ParameterizedCompletionDescriptionToolTip {
 			}
 			int dot = e.getDot();
 			if (dot<minPos || dot>=maxPos.getOffset()) {
+				System.err.println(">>>>> dot==" + dot + ", " + minPos + ", " + maxPos);
 				setVisible(false, false);
 				return;
 			}
@@ -927,6 +1093,10 @@ class ParameterizedCompletionDescriptionToolTip {
 		}
 
 
+		public void changedUpdate(DocumentEvent e) {
+		}
+
+
 		/**
 		 * Called when the text component gains focus.
 		 *
@@ -947,6 +1117,24 @@ class ParameterizedCompletionDescriptionToolTip {
 		}
 
 
+		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.
 		 *
@@ -963,20 +1151,34 @@ class ParameterizedCompletionDescriptionToolTip {
 			tc.addFocusListener(this);
 			installKeyBindings();
 
+			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, addParamStartList);
-
-				// Insert the parameter text and add highlights around the
-				// parameters.
+					pc.getInsertionInfo(tc, addParamStartList, replaceTabs);
 				tc.replaceSelection(info.getTextToInsert());
+
+				// Add highlights around the parameters.
 				for (int i=0; i<info.getReplacementCount(); i++) {
-					Point pt = info.getReplacementLocation(i);
+					DocumentRange dr = info.getReplacementLocation(i);
 					 // "-1" is a workaround for Java Highlight issues.
-					tags.add(h.addHighlight(pt.x-1, pt.y, p));
+					tags.add(h.addHighlight(dr.getStartOffset()-1, dr.getEndOffset(), p));
+				}
+				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.
@@ -987,6 +1189,15 @@ class ParameterizedCompletionDescriptionToolTip {
 
 				minPos = info.getMinOffset();
 				maxPos = info.getMaxOffset();
+				try {
+					defaultEndOffs = tc.getDocument().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
@@ -995,6 +1206,11 @@ class ParameterizedCompletionDescriptionToolTip {
 		}
 
 
+		public void removeUpdate(DocumentEvent e) {
+			handleDocumentEvent(e);
+		}
+
+
 		/**
 		 * Uninstalls this listener from the current text component.
 		 * 
@@ -1004,8 +1220,13 @@ class ParameterizedCompletionDescriptionToolTip {
 			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;
@@ -1064,6 +1285,19 @@ class ParameterizedCompletionDescriptionToolTip {
 	}
 
 
+	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.
 	 *
diff --git a/src/org/fife/ui/autocomplete/ParameterizedCompletionInsertionInfo.java b/src/org/fife/ui/autocomplete/ParameterizedCompletionInsertionInfo.java
index b82430d..f7e3a94 100644
--- a/src/org/fife/ui/autocomplete/ParameterizedCompletionInsertionInfo.java
+++ b/src/org/fife/ui/autocomplete/ParameterizedCompletionInsertionInfo.java
@@ -9,11 +9,12 @@
  */
 package org.fife.ui.autocomplete;
 
-import java.awt.Point;
 import java.util.ArrayList;
 import java.util.List;
 import javax.swing.text.Position;
 
+import org.fife.ui.rsyntaxtextarea.DocumentRange;
+
 
 /**
  * Describes a parameterized completion - what's being inserted, where the
@@ -26,13 +27,24 @@ class ParameterizedCompletionInsertionInfo {
 
 	private int minOffs;
 	private Position maxOffs;
+	private int defaultEnd;
 	private int selStart;
 	private int selEnd;
 	private String textToInsert;
 	private List replacementLocations;
+	private List replacementCopies;
 
 
 	public ParameterizedCompletionInsertionInfo() {
+		defaultEnd = -1;
+	}
+
+
+	public void addReplacementCopy(String id, int start, int end) {
+		if (replacementCopies==null) {
+			replacementCopies = new ArrayList(1);
+		}
+		replacementCopies.add(new ReplacementCopy(id, start, end));
 	}
 
 
@@ -49,7 +61,12 @@ class ParameterizedCompletionInsertionInfo {
 		if (replacementLocations==null) {
 			replacementLocations = new ArrayList(1);
 		}
-		replacementLocations.add(new Point(start, end));
+		replacementLocations.add(new DocumentRange(start, end));
+	}
+
+
+	public int getDefaultEndOffs() {
+		return defaultEnd>-1 ? defaultEnd : getMaxOffset().getOffset();
 	}
 
 
@@ -77,6 +94,11 @@ class ParameterizedCompletionInsertionInfo {
 	}
 
 
+	public int getReplacementCopyCount() {
+		return replacementCopies==null ? 0 : replacementCopies.size();
+	}
+
+
 	/**
 	 * Returns the number of replacements in the completion.
 	 *
@@ -87,6 +109,11 @@ class ParameterizedCompletionInsertionInfo {
 	}
 
 
+	public ReplacementCopy getReplacementCopy(int index) {
+		return (ReplacementCopy)replacementCopies.get(index);
+	}
+
+
 	/**
 	 * Returns the starting- and ending-offsets of the replacement regions
 	 * in the completion.
@@ -95,8 +122,8 @@ class ParameterizedCompletionInsertionInfo {
 	 * @return The range in the document of that replacement region.
 	 * @see #getReplacementCount()
 	 */
-	public Point getReplacementLocation(int index) {
-		return (Point)replacementLocations.get(index);
+	public DocumentRange getReplacementLocation(int index) {
+		return (DocumentRange)replacementLocations.get(index);
 	}
 
 
@@ -178,6 +205,11 @@ class ParameterizedCompletionInsertionInfo {
 	}
 
 
+	public void setDefaultEndOffs(int end) {
+		defaultEnd = end;
+	}
+
+
 	/**
 	 * Sets the text to insert for the completion.
 	 *
@@ -189,4 +221,31 @@ class ParameterizedCompletionInsertionInfo {
 	}
 
 
+	public static class ReplacementCopy {
+
+		private String id;
+		private int start;
+		private int end;
+
+		public ReplacementCopy(String id, int start, int end) {
+			this.id = id;
+			this.start = start;
+			this.end = end;
+		}
+
+		public int getEnd() {
+			return end;
+		}
+
+		public String getId() {
+			return id;
+		}
+
+		public int getStart() {
+			return start;
+		}
+
+	}
+
+
 }
\ No newline at end of file
diff --git a/src/org/fife/ui/autocomplete/SizeGrip.java b/src/org/fife/ui/autocomplete/SizeGrip.java
index e698cc8..3f90b84 100644
--- a/src/org/fife/ui/autocomplete/SizeGrip.java
+++ b/src/org/fife/ui/autocomplete/SizeGrip.java
@@ -193,9 +193,6 @@ class SizeGrip extends JPanel {
 	/**
 	 * Listens for mouse events on this panel and resizes the parent window
 	 * appropriately.
-	 *
-	 * @author Robert Futrell
-	 * @version 1.0
 	 */
 	/*
 	 * NOTE: We use SwingUtilities.convertPointToScreen() instead of just using
diff --git a/src/org/fife/ui/autocomplete/TemplateCompletion.java b/src/org/fife/ui/autocomplete/TemplateCompletion.java
index 19a085c..6e5efad 100644
--- a/src/org/fife/ui/autocomplete/TemplateCompletion.java
+++ b/src/org/fife/ui/autocomplete/TemplateCompletion.java
@@ -15,8 +15,11 @@ import java.util.List;
 import javax.swing.text.BadLocationException;
 import javax.swing.text.Document;
 import javax.swing.text.JTextComponent;
+import javax.swing.text.PlainDocument;
 import javax.swing.text.Position;
 
+import org.fife.ui.autocomplete.TemplatePiece.Param;
+import org.fife.ui.autocomplete.TemplatePiece.ParamCopy;
 import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
 
 
@@ -25,7 +28,26 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
  * can tab through and fill in.  This completion type is useful for inserting
  * common boilerplate code, such as for-loops.<p>
  *
- * This class is a work in progress and currently should not be used.
+ * The format of a template is similar to those in Eclipse.  The following
+ * example would be the format for a for-loop template:
+ * 
+ * <pre>
+ * for (int ${i} = 0; ${i} < ${array}.length; ${i}++) {
+ *    ${cursor}
+ * }
+ * </pre>
+ *
+ * In the above example, the first <code>${i}</code> is a parameter for the
+ * user to type into; all the other <code>${i}</code> instances are
+ * automatically changed to what the user types in the first one.  The parameter
+ * named <code>${cursor}</code> is the "ending position" of the template.  It's
+ * where the caret moves after it cycles through all other parameters.  If the
+ * user types into it, template mode terminates.  If more than one
+ * <code>${cursor}</code> parameter is specified, behavior is undefined.<p>
+ * 
+ * Leading whitespace is automatically added to lines if the template spans
+ * more than one line, and if used with a text component using a
+ * <code>PlainDocument</code>, tabs will be converted to spaces if requested.
  *
  * @author Robert Futrell
  * @version 1.0
@@ -35,7 +57,7 @@ public class TemplateCompletion extends AbstractCompletion
 
 	private List pieces;
 
-	private String replacementText;
+	private String inputText;
 
 	private String definitionString;
 
@@ -45,43 +67,35 @@ public class TemplateCompletion extends AbstractCompletion
 	private List params;
 
 
-
-	public TemplateCompletion(CompletionProvider provider, String replacementText,
-			String definitionString) {
+	public TemplateCompletion(CompletionProvider provider,
+				String inputText, String definitionString, String template) {
 		super(provider);
-		this.replacementText = replacementText;
+		this.inputText = inputText;
 		this.definitionString = definitionString;
 		pieces = new ArrayList(3);
 		params = new ArrayList(3);
+		parse(template);
 	}
 
 
-	public void addTemplatePiece(TemplatePiece piece) {
+	private void addTemplatePiece(TemplatePiece piece) {
 		pieces.add(piece);
-		if (piece.isParam) {
+		if (piece instanceof Param && !"cursor".equals(piece.getText())) {
 			final String type = null; // TODO
-			Parameter param = new Parameter(type, piece.text);
+			Parameter param = new Parameter(type, piece.getText());
 			params.add(param);
 		}
 	}
 
 
+	public String getInputText() {
+		return inputText;
+	}
+
+
 	private String getPieceText(int index, String leadingWS) {
-		String text = null;
 		TemplatePiece piece = (TemplatePiece)pieces.get(index);
-		if (piece.id!=null) {
-			final String id = piece.id;
-			for (int i=0; i<pieces.size(); i++) {
-				piece = (TemplatePiece)pieces.get(i);
-				if (id.equals(piece.id)) {
-					text = piece.text;
-					break;
-				}
-			}
-		}
-		else {
-			text = piece.text;
-		}
+		String text = piece.getText();
 		if (text.indexOf('\n')>-1) {
 			text = text.replaceAll("\n", "\n" + leadingWS);
 		}
@@ -90,13 +104,13 @@ public class TemplateCompletion extends AbstractCompletion
 
 
 	/**
-	 * For template completions, the "replacement text" is really just the
-	 * first piece of the template, not the entire thing.  You should add
-	 * template pieces so that the rest of the template is dynamically
-	 * generated.
+	 * Returns <code>null</code>; template completions insert all of their
+	 * text via <code>getInsertionInfo()</code>.
+	 *
+	 * @return <code>null</code> always.
 	 */
 	public String getReplacementText() {
-		return replacementText;
+		return null;
 	}
 
 
@@ -112,19 +126,20 @@ public class TemplateCompletion extends AbstractCompletion
 
 
 	public ParameterizedCompletionInsertionInfo getInsertionInfo(
-			JTextComponent tc, boolean addParamStartList) {
+			JTextComponent tc, boolean addParamStartList,
+			boolean replaceTabsWithSpaces) {
 
 		ParameterizedCompletionInsertionInfo info =
 			new ParameterizedCompletionInsertionInfo();
 
 		StringBuffer sb = new StringBuffer();
 		int dot = tc.getCaretPosition();
-		int paramCount = getParamCount();
 
 		// Get the range in which the caret can move before we hide
 		// this tool tip.
 		int minPos = dot;
 		Position maxPos = null;
+		int defaultEndOffs = -1;
 		try {
 			maxPos = tc.getDocument().createPosition(dot);
 		} catch (BadLocationException ble) {
@@ -132,6 +147,8 @@ public class TemplateCompletion extends AbstractCompletion
 		}
 		info.setCaretRange(minPos, maxPos);
 		int firstParamLen = 0;
+		int selStart = dot; // Default value
+		int selEnd = selStart;
 
 		Document doc = tc.getDocument();
 		String leadingWS = null;
@@ -148,19 +165,38 @@ public class TemplateCompletion extends AbstractCompletion
 		for (int i=0; i<pieces.size(); i++) {
 			TemplatePiece piece = (TemplatePiece)pieces.get(i);
 			String text = getPieceText(i, leadingWS);
-			sb.append(text);
-			int end = start + text.length();
-			if (piece.isParam) {
-				info.addReplacementLocation(start, end);
-				if (firstParamLen==0) {
-					firstParamLen = text.length();
+			if (piece instanceof Param && "cursor".equals(text)) {
+				if (replaceTabsWithSpaces) {
+					start = possiblyReplaceTabsWithSpaces(sb, tc, start);
 				}
+				defaultEndOffs = start;
+			}
+			else {
+				int end = start + text.length();
+				sb.append(text);
+				if (piece instanceof Param) {
+					info.addReplacementLocation(start, end);
+					if (firstParamLen==0) {
+						firstParamLen = text.length();
+						selStart = start;
+						selEnd = selStart + firstParamLen;
+					}
+				}
+				else if (piece instanceof ParamCopy) {
+					info.addReplacementCopy(piece.getText(), start, end);
+				}
+				start = end;
 			}
-			start = end;
 		}
-		int selectionEnd = paramCount>0 ? (dot+firstParamLen) : dot;
-		info.setInitialSelection(dot, selectionEnd);
+
+		info.setInitialSelection(selStart, selEnd);
+		if (defaultEndOffs>-1) {
+			// Keep this location "after" all others when tabbing
+			info.addReplacementLocation(defaultEndOffs, defaultEndOffs);
+		}
+		info.setDefaultEndOffs(defaultEndOffs);
 		info.setTextToInsert(sb.toString());
+
 		return info;
 
 	}
@@ -182,23 +218,102 @@ public class TemplateCompletion extends AbstractCompletion
 	}
 
 
-	public String toString() {
-		return getDefinitionString();
+	/**
+	 * Returns whether a parameter is already defined with a specific name.
+	 *
+	 * @param name The name.
+	 * @return Whether a parameter is defined with that name.
+	 */
+	private boolean isParamDefined(String name) {
+		for (int i=0; i<getParamCount(); i++) {
+			Parameter param = getParam(i);
+			if (name.equals(param.getName())) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+
+	/**
+	 * Parses a template string into logical pieces used by this class.
+	 *
+	 * @param template The template to parse.
+	 */
+	private void parse(String template) {
+
+		int offs = 0;
+		int lastOffs = 0;
+
+		while ((offs=template.indexOf('$', lastOffs))>-1 && offs<template.length()-1) {
+
+			char next = template.charAt(offs+1);
+			switch (next) {
+				case '$': // "$$" => escaped single dollar sign
+					addTemplatePiece(new TemplatePiece.Text(
+							template.substring(lastOffs, offs+1)));
+					lastOffs = offs += 2;
+					break;
+				case '{': // "${...}" => variable
+					int closingCurly = template.indexOf('}', offs+2);
+					if (closingCurly>-1) {
+						addTemplatePiece(new TemplatePiece.Text(
+								template.substring(lastOffs, offs)));
+						String varName = template.substring(offs+2, closingCurly);
+						if (!"cursor".equals(varName) && isParamDefined(varName)) {
+							addTemplatePiece(new TemplatePiece.ParamCopy(varName));
+						}
+						else {
+							addTemplatePiece(new TemplatePiece.Param(varName));
+						}
+						lastOffs = offs = closingCurly + 1;
+					}
+					break;
+			}
+
+		}
+
+		if (lastOffs<template.length()) {
+			String text = template.substring(lastOffs);
+			addTemplatePiece(new TemplatePiece.Text(text));
+		}
+
 	}
 
 
-	public static class TemplatePiece {
+	private int possiblyReplaceTabsWithSpaces(StringBuffer sb, JTextComponent tc,
+											int start) {
 
-		private String id;
-		private String text;
-		private boolean isParam;
+		int size = 4;
+		Document doc = tc.getDocument();
+		if (doc != null) {
+			Integer i = (Integer) doc.getProperty(PlainDocument.tabSizeAttribute);
+			if (i != null) {
+				size = i.intValue();
+			}
+		}
+		String tab = "";
+		for (int i=0; i<size; i++) {
+			tab += " ";
+		}
 
-		public TemplatePiece(String id, String text, boolean isParam) {
-			this.id = id;
-			this.text = text;
-			this.isParam = isParam;
+		int lastNewline = sb.lastIndexOf("\n");
+		int lineOffs = 0;
+		for (int j=lastNewline+1; j<sb.length(); j++) {
+			if (sb.charAt(j)=='\t') {
+				int count = size - (lineOffs%size);
+				sb.replace(j, j+1, tab.substring(0, count));
+				start += count - 1;
+			}
 		}
 
+		return start;
+
+	}
+
+
+	public String toString() {
+		return getDefinitionString();
 	}
 
 
diff --git a/src/org/fife/ui/autocomplete/TemplatePiece.java b/src/org/fife/ui/autocomplete/TemplatePiece.java
new file mode 100644
index 0000000..df69343
--- /dev/null
+++ b/src/org/fife/ui/autocomplete/TemplatePiece.java
@@ -0,0 +1,63 @@
+package org.fife.ui.autocomplete;
+
+
+/**
+ * A piece of a <code>TemplateCompletion</code>.  You add instances of this
+ * class to template completions to define them.
+ *
+ * @author Robert Futrell
+ * @version 1.0
+ * @see TemplateCompletion
+ */
+interface TemplatePiece {
+
+
+	String getText();
+
+
+	public class Text implements TemplatePiece {
+
+		private String text;
+
+		public Text(String text) {
+			this.text = text;
+		}
+
+		public String getText() {
+			return text;
+		}
+
+	}
+
+
+	public class Param implements TemplatePiece {
+
+		String text;
+
+		public Param(String text) {
+			this.text = text;
+		}
+
+		public String getText() {
+			return text;
+		}
+
+	}
+
+
+	public class ParamCopy implements TemplatePiece {
+
+		private String text;
+
+		public ParamCopy(String text) {
+			this.text = text;
+		}
+
+		public String getText() {
+			return text;
+		}
+
+	}
+
+
+}
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/autocomplete.git



More information about the pkg-java-commits mailing list