Bug 499538 - Don't show existing JSON keys in popup completion

The commit also fixes bug 499537.

Change-Id: Ib0ff582c96c07f46e6b3a788836923da8cf8bc39
Signed-off-by: Snjezana Peco <snjeza.peco@gmail.com>
diff --git a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/contentassist/AbstractJSONCompletionProposalComputer.java b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/contentassist/AbstractJSONCompletionProposalComputer.java
index e12e9be..616c6ac 100644
--- a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/contentassist/AbstractJSONCompletionProposalComputer.java
+++ b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/contentassist/AbstractJSONCompletionProposalComputer.java
@@ -23,6 +23,7 @@
 import org.eclipse.jface.text.contentassist.ICompletionProposal;
 import org.eclipse.wst.json.core.document.IJSONNode;
 import org.eclipse.wst.json.core.document.IJSONPair;
+import org.eclipse.wst.json.core.document.IJSONValue;
 import org.eclipse.wst.json.core.regions.JSONRegionContexts;
 import org.eclipse.wst.json.ui.internal.JSONUIMessages;
 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
@@ -37,8 +38,12 @@
 public abstract class AbstractJSONCompletionProposalComputer implements
 		ICompletionProposalComputer {
 
-	private static final String BLANK = ""; //$NON-NLS-1$
+	private static final String BLANK = " "; //$NON-NLS-1$
+	private static final String EMPTY = ""; //$NON-NLS-1$
 	private static final String COLON = ":"; //$NON-NLS-1$
+	protected static final String QUOTE = "\""; //$NON-NLS-1$
+	protected static final String TRUE = "true"; //$NON-NLS-1$
+	protected static final String FALSE = "false"; //$NON-NLS-1$
 	private String fErrorMessage;
 	private ITextViewer fTextViewer;
 
@@ -78,17 +83,16 @@
 		if (completionRegion != null && completionRegion.getType() == JSONRegionContexts.JSON_OBJECT_CLOSE && documentPosition > 0) {
 			completionRegion = getCompletionRegion(documentPosition, node);
 		}
-		String matchString = BLANK; //$NON-NLS-1$
+		String matchString = EMPTY;
 		if (completionRegion != null) {
 			if (isPairValue(context, node)) {
 				try {
-					String nodeText = node.getStructuredDocument().get(node.getStartOffset(), node.getEndOffset() - node.getStartOffset());
+					String nodeText = getNodeText(node);
 					int colonIndex  = nodeText.indexOf(COLON);
-					if (colonIndex >= 0) {
-						String str = nodeText.substring(colonIndex);
-						str = str.replaceAll(",", BLANK).trim();
-						str = str.replaceAll(COLON, BLANK).trim();
-						str = str.trim();
+					int offset = documentPosition - node.getStartOffset();
+					if (colonIndex >= 0 && offset >= 0) {
+						String str = nodeText.substring(colonIndex+1, offset);
+						str = str.replaceAll(",", BLANK); //$NON-NLS-1$
 						matchString = str;
 					}
 				} catch (BadLocationException e) {
@@ -105,7 +109,7 @@
 		if (contentAssistRequest == null) {
 			contentAssistRequest = new ContentAssistRequest(
 					(IJSONNode) treeNode, node != null ? node.getParentNode() : null, sdRegion,
-					completionRegion, documentPosition, 0, BLANK);
+					completionRegion, documentPosition, 0, EMPTY);
 			setErrorMessage(JSONUIMessages.Content_Assist_not_availab_UI_);
 		}
 
@@ -160,7 +164,7 @@
 		int documentPosition = context.getInvocationOffset();
 
 		ContentAssistRequest contentAssistRequest = null;
-		String regionType = completionRegion!= null ? completionRegion.getType() : BLANK;
+		String regionType = completionRegion!= null ? completionRegion.getType() : EMPTY;
 		IStructuredDocumentRegion sdRegion = getStructuredDocumentRegion(documentPosition);
 
 		// Handle the most common and best supported cases
@@ -220,6 +224,40 @@
 			begin = sdRegion.getStartOffset(completionRegion);
 		}
 
+		if (isPairValue(context, nodeAtOffset)) {
+			IJSONPair pair = (IJSONPair) nodeAtOffset;
+			IJSONValue value = pair.getValue();
+			if (value != null) {
+				try {
+					begin = value.getStartOffset();
+					String valueText = getNodeText(value);
+					valueText = valueText.trim();
+					replaceLength = valueText.length();
+					if (valueText.startsWith(QUOTE)) {
+						begin = begin + 1;
+						replaceLength = replaceLength - 1;
+					}
+					if (valueText.endsWith(QUOTE)) {
+						replaceLength = replaceLength - 1;
+					}
+				} catch (BadLocationException e) {
+					// ignore
+				}
+			}
+		} else if (nodeAtOffset instanceof IJSONPair) {
+			IJSONPair pair = (IJSONPair) nodeAtOffset;
+				try {
+					begin = pair.getStartOffset();
+					String text = getNodeText(pair);
+					text = text.trim();
+					replaceLength = pair.getName().length();
+					if (text.startsWith(QUOTE)) {
+						begin = begin + 1;
+					}
+				} catch (BadLocationException e) {
+					// ignore
+				}
+		}
 		contentAssistRequest = new ContentAssistRequest(nodeAtOffset,
 				node.getParentNode(), sdRegion, completionRegion, begin,
 				replaceLength, matchString);
@@ -392,18 +430,18 @@
 	private String getMatchString(IStructuredDocumentRegion parent,
 			ITextRegion aRegion, int offset) {
 		if (aRegion == null) {
-			return BLANK;
+			return EMPTY;
 		}
 		String regionType = aRegion.getType();
 		if (regionType != JSONRegionContexts.JSON_OBJECT_KEY) {
-			return BLANK;
+			return EMPTY;
 		}
 		if ((parent.getText(aRegion).length() > 0)
 				&& (parent.getStartOffset(aRegion) < offset)) {
 			return parent.getText(aRegion).substring(0,
 					offset - parent.getStartOffset(aRegion));
 		}
-		return BLANK;
+		return EMPTY;
 	}
 
 	/**
@@ -438,7 +476,7 @@
 		}
 		int documentPosition = context.getInvocationOffset();
 		try {
-			String nodeText = node.getStructuredDocument().get(node.getStartOffset(), node.getEndOffset() - node.getStartOffset());
+			String nodeText = getNodeText(node);
 			int colonIndex  = nodeText.indexOf(COLON); //$NON-NLS-1$
 			if (colonIndex >= 0) {
 				return documentPosition > node.getStartOffset() + colonIndex;
@@ -449,6 +487,10 @@
 		return false;
 	}
 
+	private String getNodeText(IJSONNode node) throws BadLocationException {
+		return node.getStructuredDocument().get(node.getStartOffset(), node.getEndOffset() - node.getStartOffset());
+	}
+
 	/**
 	 * <p>
 	 * helpful utility method for determining if one string starts with another
diff --git a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/contentassist/JSONCompletionProposalComputer.java b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/contentassist/JSONCompletionProposalComputer.java
index 166f338..06f3345 100644
--- a/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/contentassist/JSONCompletionProposalComputer.java
+++ b/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/contentassist/JSONCompletionProposalComputer.java
@@ -11,6 +11,8 @@
 package org.eclipse.wst.json.ui.internal.contentassist;

 

 import java.io.IOException;

+import java.util.HashSet;

+import java.util.Set;

 

 import org.eclipse.json.jsonpath.IJSONPath;

 import org.eclipse.json.jsonpath.JSONPath;

@@ -37,10 +39,6 @@
 public class JSONCompletionProposalComputer extends

 		AbstractJSONCompletionProposalComputer {

 

-	private static final String QUOTE = "\""; //$NON-NLS-1$

-	private static final String TRUE = "true"; //$NON-NLS-1$

-	private static final String FALSE = "false"; //$NON-NLS-1$

-

 	@Override

 	public void sessionStarted() {

 		// default is to do nothing

@@ -98,24 +96,30 @@
 					boolean isValue = isPairValue(context, node);

 					if (thisProperty != null && isValue) {

 						if (thisProperty.getFirstType() == JSONSchemaType.Boolean) {

-							boolean showProperty = beginsWith(FALSE, matchString.trim())

-									|| beginsWith(TRUE, matchString.trim());

-							if (showProperty) {

-								addStringProposal(contentAssistRequest, TRUE, false);

+							if (beginsWith(FALSE, matchString.trim())) {

 								addStringProposal(contentAssistRequest, FALSE, false);

 							}

+							if (beginsWith(TRUE, matchString.trim())) {

+								addStringProposal(contentAssistRequest, TRUE, false);

+							}

 							return;

 						}

 						if (thisProperty.getFirstType() == JSONSchemaType.String) {

 							if (thisProperty.getEnumList() != null && thisProperty.getEnumList().size() > 0) {

 								for (String prop : thisProperty.getEnumList()) {

-									addStringProposal(contentAssistRequest, prop,

-											!(region.getType() == JSONRegionContexts.JSON_VALUE_STRING));

+									boolean showProperty = beginsWith(prop, matchString.trim());

+									if (showProperty) {

+										addStringProposal(contentAssistRequest, prop,

+												!(region.getType() == JSONRegionContexts.JSON_VALUE_STRING));

+									}

 								}

 							} else {

 								if (thisProperty.getDefaultValue() != null) {

-									addStringProposal(contentAssistRequest, thisProperty.getDefaultValue(),

-											!(region.getType() == JSONRegionContexts.JSON_VALUE_STRING));

+									boolean showProperty = beginsWith(thisProperty.getDefaultValue(), matchString.trim());

+									if (showProperty) {

+										addStringProposal(contentAssistRequest, thisProperty.getDefaultValue(),

+												!(region.getType() == JSONRegionContexts.JSON_VALUE_STRING));

+									}

 								}

 							}

 							return;

@@ -131,14 +135,30 @@
 				}

 				IJSONSchemaProperty parentProperty = schemaDocument

 						.getProperty(path);

+				Set<String> existing = new HashSet<String>();

+				boolean addComma = false;

+				if (node instanceof IJSONObject) {

+					addExisting(existing, node);

+					addComma = addComma(context, node);

+				} else if (node instanceof IJSONPair && node.getParentNode() instanceof IJSONObject) {

+					addExisting(existing, node.getParentNode());

+				}

 				if (parentProperty != null) {

 					for (IJSONSchemaProperty property : parentProperty

 							.getPropertyValues()) {

-						boolean showProperty = beginsWith(property.getName(),

+						boolean showProperty = !existing.contains(property.getName()) && beginsWith(property.getName(),

 								matchString.trim());

 						if (showProperty) {

-							String replacementString = ContentAssistHelper

+							String replacementString;

+							if (node instanceof IJSONPair) {

+								replacementString = property.getName();

+							} else {

+								replacementString = ContentAssistHelper

 									.getRequiredName(node, property);

+								if (addComma) {

+									replacementString = replacementString + ",";

+								}

+							}

 							String additionalProposalInfo = property

 									.getDescription();

 							Image icon = JSONEditorPluginImageHelper

@@ -164,6 +184,32 @@
 		}

 	}

 

+	private boolean addComma(CompletionProposalInvocationContext context, IJSONNode node) {

+		IJSONNode child = node.getFirstChild();

+		int documentPosition = context.getInvocationOffset();

+		while (child != null) {

+			if (documentPosition > child.getStartOffset()) {

+				child = child.getNextSibling();

+			} else {

+				break;

+			}

+		}

+		return child != null;

+	}

+

+	private void addExisting(Set<String> existing, IJSONNode node) {

+		IJSONNode child = node.getFirstChild();

+		while (child != null) {

+			if (child instanceof IJSONPair) {

+				String name = ((IJSONPair) child).getName();

+				if (name != null && !name.isEmpty()) {

+					existing.add(name);

+				}

+			}

+			child = child.getNextSibling();

+		}

+	}

+

 	private void addStringProposal(ContentAssistRequest contentAssistRequest, String replacementString, boolean addQuote) {

 		String additionalProposalInfo = null;

 		Image icon = null;

@@ -178,7 +224,7 @@
 		JSONKeyCompletionProposal proposal = new JSONKeyCompletionProposal(

 				replacementString,

 				contentAssistRequest

-						.getReplacementBeginPosition() - matchString.length(),

+						.getReplacementBeginPosition(),

 				contentAssistRequest.getReplacementLength(),

 				replacementString.length() - 2, icon,

 				displayString, null,