From 241a60fe8a522a0b39586ec5ec93c67a77f2898d Mon Sep 17 00:00:00 2001 From: mazab Date: Tue, 23 Jun 2015 15:51:48 +0200 Subject: [PATCH] Bug 438549 Add mechanism for parameter guessing. Change-Id: Ib348e401932a9119185dbab8ecacaf80fd3e17ff Signed-off-by: mazab --- .../AbstractContentAssistTest.java | 104 ++++- .../CPPParameterGuessingTests.java | 141 +++++++ .../CParameterGuessingTests.java | 79 ++++ .../text/contentassist2/CompletionTests.java | 13 +- .../ContentAssist2TestSuite.java | 5 +- .../DOMCompletionProposalComputer.java | 127 +++++- .../FunctionCompletionProposal.java | 136 ++++++ .../text/contentassist/ParameterGuesser.java | 390 ++++++++++++++++++ .../ParameterGuessingProposal.java | 368 +++++++++++++++++ .../PositionBasedCompletionProposal.java | 169 ++++++++ 10 files changed, 1519 insertions(+), 13 deletions(-) create mode 100644 core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CPPParameterGuessingTests.java create mode 100644 core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CParameterGuessingTests.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/FunctionCompletionProposal.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuesser.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuessingProposal.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/PositionBasedCompletionProposal.java diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/AbstractContentAssistTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/AbstractContentAssistTest.java index 99b9b83cbcb..e6c78ba6d80 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/AbstractContentAssistTest.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/AbstractContentAssistTest.java @@ -12,12 +12,15 @@ * Markus Schorn (Wind River Systems) * Thomas Corbat (IFS) * Sergey Prigogin (Google) + * Mohamed Azab (Mentor Graphics) *******************************************************************************/ package org.eclipse.cdt.ui.tests.text.contentassist2; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -49,6 +52,7 @@ import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTNameBase; import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal; import org.eclipse.cdt.internal.ui.text.contentassist.CContentAssistProcessor; import org.eclipse.cdt.internal.ui.text.contentassist.ContentAssistPreference; +import org.eclipse.cdt.internal.ui.text.contentassist.ParameterGuessingProposal; import org.eclipse.cdt.internal.ui.text.contentassist.RelevanceConstants; public abstract class AbstractContentAssistTest extends BaseUITestCase { @@ -56,6 +60,18 @@ public abstract class AbstractContentAssistTest extends BaseUITestCase { ID, DISPLAY, REPLACEMENT, CONTEXT, INFORMATION } + private class ContentAssistResult { + long startTime; + long endTime; + Object[] results; + + public ContentAssistResult(long startTime, long endTime, Object[] results) { + this.startTime = startTime; + this.endTime = endTime; + this.results = results; + } + } + protected ICProject fCProject; private IFile fCFile; protected ITextEditor fEditor; @@ -107,8 +123,9 @@ public abstract class AbstractContentAssistTest extends BaseUITestCase { return CUIPlugin.getDefault().getPreferenceStore(); } - protected void assertContentAssistResults(int offset, int length, String[] expected, - boolean isCompletion, boolean isTemplate, boolean filterResults, CompareType compareType) throws Exception { + private ContentAssistResult invokeContentAssist(int offset, int length, + boolean isCompletion, boolean isTemplate, boolean filterResults) + throws Exception { if (CTestPlugin.getDefault().isDebugging()) { System.out.println("\n\n\n\n\nTesting " + this.getClass().getName()); } @@ -138,12 +155,20 @@ public abstract class AbstractContentAssistTest extends BaseUITestCase { results= filterResults(results, isCode); } } - String[] resultStrings= toStringArray(results, compareType); + return new ContentAssistResult(startTime, endTime, results); + } + + protected void assertContentAssistResults(int offset, int length, + String[] expected, boolean isCompletion, boolean isTemplate, + boolean filterResults, CompareType compareType) throws Exception { + ContentAssistResult r = invokeContentAssist(offset, length, isCompletion, isTemplate, filterResults); + + String[] resultStrings= toStringArray(r.results, compareType); Arrays.sort(expected); Arrays.sort(resultStrings); if (CTestPlugin.getDefault().isDebugging()) { - System.out.println("Time: " + (endTime - startTime) + " ms"); + System.out.println("Time: " + (r.endTime - r.startTime) + " ms"); for (String proposal : resultStrings) { System.out.println("Result: " + proposal); } @@ -177,6 +202,77 @@ public abstract class AbstractContentAssistTest extends BaseUITestCase { } } + protected void assertContentAssistResults(int offset, int length, Map expected, + boolean isCompletion, boolean isTemplate, boolean filterResults, CompareType compareType) + throws Exception { + ContentAssistResult r = invokeContentAssist(offset, length, isCompletion, isTemplate, filterResults); + Map resultMap = toMap(r.results, compareType); + + if (CTestPlugin.getDefault().isDebugging()) { + System.out.println("Time : " + (r.endTime - r.startTime) + " ms"); + for (String proposal : resultMap.keySet()) { + System.out.println("Result: " + proposal); + String[][] result = resultMap.get(proposal); + for (String[] row : result) { + for (String s : row) { + System.out.print(s + " "); + } + System.out.println(); + } + } + System.out.println(); + } + + for (String proposal : expected.keySet()) { + String[][] result = resultMap.get(proposal); + assertNotNull(result); + String[][] expectedGuesses = expected.get(proposal); + String exp = ""; + String guess = ""; + int minLength = expectedGuesses.length < result.length ? expectedGuesses.length : result.length; + for (int i = 0; i < minLength; i++) { + String[] tmp = expectedGuesses[i]; + Arrays.sort(tmp); + exp += toString(tmp) + "\n"; + tmp = result[i]; + Arrays.sort(tmp); + guess += toString(tmp) + "\n"; + } + assertEquals(exp, guess); + } + } + + private Map toMap(Object[] results, + CompareType compareType) { + Map resultsMap = new HashMap<>(); + for (Object result : results) { + switch (compareType) { + case REPLACEMENT: + if (result instanceof ParameterGuessingProposal) { + ParameterGuessingProposal proposal = (ParameterGuessingProposal) result; + String pName = proposal.getReplacementString(); + ICompletionProposal[][] pProposals = proposal + .getParametersGuesses(); + String[][] p; + if (pProposals != null) { + p = new String[pProposals.length][]; + for (int i = 0; i < pProposals.length; i++) { + p[i] = new String[pProposals[i].length]; + for (int j = 0; j < pProposals[i].length; j++) { + p[i][j] = pProposals[i][j].getDisplayString(); + } + } + } else { + p = new String[0][]; + } + resultsMap.put(pName, p); + } + break; + } + } + return resultsMap; + } + protected void assertContentAssistResults(int offset, int length, String[] expected, boolean isCompletion, boolean isTemplate, CompareType compareType) throws Exception { assertContentAssistResults(offset, length, expected, isCompletion, isTemplate, true, compareType); } diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CPPParameterGuessingTests.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CPPParameterGuessingTests.java new file mode 100644 index 00000000000..f6f8883afa6 --- /dev/null +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CPPParameterGuessingTests.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2015 Mentor Graphics Corporation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mohamed Azab (Mentor Graphics) - Initial implementation. + *******************************************************************************/ +package org.eclipse.cdt.ui.tests.text.contentassist2; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.Test; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; + +import org.eclipse.cdt.core.testplugin.util.BaseTestCase; + +public class CPPParameterGuessingTests extends AbstractContentAssistTest { + private static final String HEADER_FILE_NAME = "PGTest.h"; + private static final String SOURCE_FILE_NAME = "PGTest.cpp"; + + // {PGTest.h} + // class aClass { + // public: + // int aField; + // void aMethod(char c); + // void aMethod(char c, int x); + // }; + // + // class bClass : aClass { + // }; + // + // void overload(int x, aClass a); + // void overload(int x, aClass* aPtr); + // int piab(aClass a, bClass b); + // templatevoid tFunc(T x, T y); + + public CPPParameterGuessingTests(String name) { + super(name, true); + } + + public static Test suite() { + return BaseTestCase.suite(CPPParameterGuessingTests.class, "_"); + } + + @Override + protected IFile setUpProjectContent(IProject project) throws Exception { + String headerContent = readTaggedComment(HEADER_FILE_NAME); + StringBuilder sourceContent = getContentsForTest(1)[0]; + sourceContent.insert(0, "#include \"" + HEADER_FILE_NAME + "\"\n"); + assertNotNull(createFile(project, HEADER_FILE_NAME, headerContent)); + return createFile(project, SOURCE_FILE_NAME, sourceContent.toString()); + } + + protected void assertParametersGuesses(Map expected) + throws Exception { + assertContentAssistResults(getBuffer().length() - 1, 0, expected, true, + false, false, CompareType.REPLACEMENT); + } + + // void foo(){ + // aClass* aTypePtr; + // bClass bTypeObj; + // piab( + public void testIndirectTypes() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("piab(a, b)", new String[][] { { "*aTypePtr", "bTypeObj" }, + { "bTypeObj" } }); + assertParametersGuesses(resultsMap); + } + + // void foo(){ + // int intVal; + // aClass aTypeObj; + // overload( + public void testOverloadedFunction() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("overload(x, a)", new String[][] { { "intVal" }, { "aTypeObj" } }); + resultsMap.put("overload(x, aPtr)", + new String[][] { { "intVal" }, { "&aTypeObj" } }); + assertParametersGuesses(resultsMap); + } + + // void foo(){ + // aClass aTypeObj; + // tFunc ( + public void testTemplateFunction() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("tFunc (x, y)", new String[][] { { "x" }, + { "y" } }); + assertParametersGuesses(resultsMap); + } + + // struct container { + // aClass* aTypePtr; + // }; + // + // void foo(){ + // char charX, charY, charZ; + // container containerObj; + // containerObj.aTypePtr = new aClass(); + // containerObj.aTypePtr-> + public void testOverloadedMethod() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("aMethod(c)", new String[][] { { "charX", "charY", "charZ" } }); + resultsMap.put("aMethod(c, x)", new String[][] { { "charX", "charY", "charZ" }, + { "charX", "charY", "charZ" } }); + assertParametersGuesses(resultsMap); + } + + // void testParameterNameMatching(int lngName, int shrt); + // + // void foo() { + // int lng; + // int shrtNameMatch; + // testParameter + public void testParameterNameMatching() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("testParameterNameMatching(lngName, shrt)", new String[][] { + { "lng", "shrtNameMatch" }, { "lng", "shrtNameMatch" } }); + assertParametersGuesses(resultsMap); + } + + // class cClass : bClass { + // public: + // cClass(int inCall) { + // char charX, charY; + // aClass:: + public void testInsideConstructor() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("aMethod(c)", new String[][] { { "charX", "charY", "inCall" } }); + resultsMap.put("aMethod(c, x)", new String[][] { + { "charX", "charY"}, { "inCall" } }); + assertParametersGuesses(resultsMap); + } +} diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CParameterGuessingTests.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CParameterGuessingTests.java new file mode 100644 index 00000000000..5c9b94d6345 --- /dev/null +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CParameterGuessingTests.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2015 Mentor Graphics Corporation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Mohamed Azab (Mentor Graphics) - Initial implementation. + *******************************************************************************/ +package org.eclipse.cdt.ui.tests.text.contentassist2; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.Test; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; + +import org.eclipse.cdt.core.testplugin.util.BaseTestCase; + +public class CParameterGuessingTests extends AbstractContentAssistTest { + private static final String HEADER_FILE_NAME = "PGTest_C.h"; + private static final String SOURCE_FILE_NAME = "PGTest_C.c"; + + // {PGTest_C.h} + // typedef struct aStruct { + // int a; + // int b; + // } aStruct; + // + // void ov1(int x, aStruct a); + // void ov2(int x, aStruct* aPtr); + // int funWith2ATypeObjectParams(aStruct a, aStruct b); + + public CParameterGuessingTests(String name) { + super(name, false); + } + + public static Test suite() { + return BaseTestCase.suite(CParameterGuessingTests.class, "_"); + } + + @Override + protected IFile setUpProjectContent(IProject project) throws Exception { + String headerContent = readTaggedComment(HEADER_FILE_NAME); + StringBuilder sourceContent = getContentsForTest(1)[0]; + sourceContent.insert(0, "#include \"" + HEADER_FILE_NAME + "\"\n"); + assertNotNull(createFile(project, HEADER_FILE_NAME, headerContent)); + return createFile(project, SOURCE_FILE_NAME, sourceContent.toString()); + } + + protected void assertParametersGuesses(Map expected) throws Exception { + assertContentAssistResults(getBuffer().length() - 1, 0, expected, true, + false, false, CompareType.REPLACEMENT); + } + + // void foo(){ + // aStruct* axPtr; + // aStruct ax; + // funWith2ATypeObjectParams( + public void testIndirectTypes() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("funWith2ATypeObjectParams(a, b)", new String[][] { { "ax", "*axPtr" }, + { "ax", "*axPtr" } }); + assertParametersGuesses(resultsMap); + } + + // void foo(){ + // aStruct ax; + // ov + public void testMultipleFunctions() throws Exception { + Map resultsMap = new HashMap<>(); + resultsMap.put("ov1(x, a)", new String[][] { { "x" }, { "ax" } }); + resultsMap.put("ov2(x, aPtr)", new String[][] { { "x" }, { "&ax" } }); + assertParametersGuesses(resultsMap); + } +} diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests.java index 85bcbc33ecd..a3325c6fcdd 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2014 Wind River Systems, Inc. and others. + * Copyright (c) 2006, 2015 Wind River Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -15,6 +15,7 @@ * Nathan Ridge * Thomas Corbat (IFS) * Michael Woski + * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. *******************************************************************************/ package org.eclipse.cdt.ui.tests.text.contentassist2; @@ -855,7 +856,7 @@ public class CompletionTests extends AbstractContentAssistTest { // Printer::/*cursor*/ public void testPrivateStaticMember_109480() throws Exception { // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=109480 - final String[] expected= { "InitPrinter()", "port" }; + final String[] expected= { "InitPrinter(port)", "port" }; assertCompletionResults(fCursorOffset, expected, REPLACEMENT); } @@ -1261,7 +1262,7 @@ public class CompletionTests extends AbstractContentAssistTest { // } //}; public void testContentAssistInDeferredClassInstance_194592() throws Exception { - final String[] expected= { "add()" }; + final String[] expected= { "add(tOther)" }; assertCompletionResults(fCursorOffset, expected, REPLACEMENT); } @@ -1428,7 +1429,7 @@ public class CompletionTests extends AbstractContentAssistTest { // } // using N::f/*cursor*/ public void testUsingDeclaration_379631() throws Exception { - final String[] expected= { "foo;" }; + final String[] expected= { "foo()" }; assertCompletionResults(fCursorOffset, expected, REPLACEMENT); } @@ -1575,7 +1576,7 @@ public class CompletionTests extends AbstractContentAssistTest { // } // using N::fo/*cursor*/; public void testUsingCompletionWithFollowingSemicolon() throws Exception { - final String[] expected = { "foo" }; + final String[] expected = { "foo()" }; assertContentAssistResults(fCursorOffset, expected, true, REPLACEMENT); final String[] expectedInformation = { "null" }; assertContentAssistResults(fCursorOffset, expectedInformation, true, CONTEXT); @@ -1613,7 +1614,7 @@ public class CompletionTests extends AbstractContentAssistTest { setDisplayDefaultArguments(true); final String[] expectedDisplay = { "default_argument(int i = 23) : void" }; assertContentAssistResults(fCursorOffset, expectedDisplay, true, DISPLAY); - final String[] expectedReplacement = { "default_argument()" }; + final String[] expectedReplacement = { "default_argument(i)" }; assertContentAssistResults(fCursorOffset, expectedReplacement, true, REPLACEMENT); } diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/ContentAssist2TestSuite.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/ContentAssist2TestSuite.java index 7a064666c2d..354cb986d25 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/ContentAssist2TestSuite.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/ContentAssist2TestSuite.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006, 2007 Siemens AG and others. + * Copyright (c) 2006, 2015 Siemens AG and others. * All rights reserved. This content and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -9,6 +9,7 @@ * Norbert Ploett - Initial implementation * Bryan Wilkinson (QNX) * Andrew Ferguson (Symbian) + * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. *******************************************************************************/ package org.eclipse.cdt.ui.tests.text.contentassist2; @@ -71,6 +72,8 @@ public class ContentAssist2TestSuite extends TestSuite { addTest(CompletionTests.suite()); addTest(CompletionTests_PlainC.suite()); addTest(ParameterHintTests.suite()); + addTest(CPPParameterGuessingTests.suite()); + addTest(CParameterGuessingTests.suite()); addTest(ShowCamelCasePreferenceTest.suite()); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java index 5c7323936f6..dc6367d8407 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/DOMCompletionProposalComputer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2014 QNX Software Systems and others. + * Copyright (c) 2007, 2015 QNX Software Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -13,12 +13,15 @@ * Jens Elmenthaler - http://bugs.eclipse.org/173458 (camel case completion) * Nathan Ridge * Thomas Corbat (IFS) + * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. *******************************************************************************/ package org.eclipse.cdt.internal.ui.text.contentassist; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; @@ -107,17 +110,22 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer private static final String TEMPLATE_PARAMETER_PATTERN = "template<{0}> class"; //$NON-NLS-1$; private static final String TYPENAME = "typename"; //$NON-NLS-1$; private static final String ELLIPSIS = "..."; //$NON-NLS-1$; + private String fPrefix; + private ArrayList fAvailableElements; /** * Default constructor is required (executable extension). */ public DOMCompletionProposalComputer() { + fPrefix = ""; //$NON-NLS-1$ } @Override protected List computeCompletionProposals( CContentAssistInvocationContext context, IASTCompletionNode completionNode, String prefix) { + fPrefix = prefix; + initializeDefinedElements(context); List proposals = new ArrayList(); if (inPreprocessorDirective(context)) { @@ -587,7 +595,122 @@ public class DOMCompletionProposalComputer extends ParsingBasedProposalComputer proposal.setContextInformation(info); } - proposals.add(proposal); + /* + * The ParameterGuessingProposal will be active if the content assist is invoked before typing + * any parameters. Otherwise, the normal Parameter Hint Proposal will be added. + */ + if (isBeforeParameters(context)) { + proposals.add(ParameterGuessingProposal.createProposal(context, fAvailableElements, proposal, function, fPrefix)); + } else { + proposals.add(proposal); + } + } + + /** + * Returns true if the invocation is at the function name or before typing any parameters + */ + private boolean isBeforeParameters(CContentAssistInvocationContext context) { + /* + * Invocation offset and parse offset are the same if content assist is invoked while in the function + * name (i.e. before the '('). After that, the parse offset will indicate the end of the name part. If + * the diff. between them is zero, then we're still inside the function name part. + */ + int relativeOffset = context.getInvocationOffset() - context.getParseOffset(); + if (relativeOffset == 0) + return true; + int startOffset = context.getParseOffset(); + String completePrefix = context.getDocument().get().substring(startOffset, + context.getInvocationOffset()); + int lastChar = getLastNonWhitespaceChar(completePrefix); + if (lastChar != -1 && completePrefix.charAt(lastChar) == '(') + return true; + return false; + } + + private static int getLastNonWhitespaceChar(String str) { + char[] chars = str.toCharArray(); + for (int i = chars.length - 1; i >= 0; i--) { + if (!Character.isWhitespace(chars[i])) + return i; + } + return -1; + } + + /** + * Initializes the list of defined elements at the start of the current statement. + */ + private void initializeDefinedElements(CContentAssistInvocationContext context) { + /* + * Get all defined elements before the start of the statement. + * ex1: int a = foo( + * ^ --> We don't want 'a' as a suggestion. + * ex2: char* foo(int a, int b) {return NULL;} + * void bar(char* name) {} + * ... + * bar( foo( + * ^ --> If this offset is used, the only defined name will be "bar(char*)". + */ + int startOffset = getStatementStartOffset(context.getDocument(), + context.getParseOffset() - fPrefix.length()); + Map elementsMap = new HashMap<>(); + IASTCompletionNode node = null; + // Create a content assist context that points to the start of the statement. + CContentAssistInvocationContext newContext = new CContentAssistInvocationContext( + context.getViewer(), startOffset, context.getEditor(), true, false); + try { + node = newContext.getCompletionNode(); + if (node != null) { + IASTTranslationUnit tu = node.getTranslationUnit(); + IASTName[] names = node.getNames(); + for (IASTName name : names) { + IASTCompletionContext astContext = name.getCompletionContext(); + if (astContext != null) { + IBinding[] bindings = astContext.findBindings(name, true); + if (bindings != null) { + AccessContext accessibilityContext = new AccessContext(name); + for (IBinding binding : bindings) { + // Consider only variables that are part of the current translation unit and fields. + if (binding instanceof IVariable + && accessibilityContext.isAccessible(binding) + && !elementsMap.containsKey(binding.getName())) { + if (binding instanceof ICPPField) { + elementsMap.put(binding.getName(), binding); + } else { + IASTName[] declarations = tu.getDeclarationsInAST(binding); + if (declarations.length != 0) + elementsMap.put(binding.getName(), binding); + } + } + } + } + } + } + } + } finally { + fAvailableElements = new ArrayList<>(elementsMap.values()); + newContext.dispose(); + } + } + + /** + * Returns the position after last semicolon or opening or closing brace before the given offset. + */ + private static int getStatementStartOffset(IDocument doc, int offset) { + if (offset != 0) { + String docPart; + try { + docPart = doc.get(0, offset); + int index = docPart.lastIndexOf(';'); + int tmpIndex = docPart.lastIndexOf('{'); + index = (index < tmpIndex) ? tmpIndex : index; + tmpIndex = docPart.lastIndexOf('}'); + index = (index < tmpIndex) ? tmpIndex : index; + return index + 1; + } catch (BadLocationException e) { + CUIPlugin.log(e); + } + } + return offset; } private boolean skipDefaultedParameter(IParameter param) { diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/FunctionCompletionProposal.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/FunctionCompletionProposal.java new file mode 100644 index 00000000000..5c69c51dd02 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/FunctionCompletionProposal.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2005, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Tom Eicher - [content assist] prefix complete casted method proposals - https://bugs.eclipse.org/bugs/show_bug.cgi?id=247547 + * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.text.contentassist; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.link.ILinkedModeListener; +import org.eclipse.jface.text.link.LinkedModeModel; +import org.eclipse.jface.text.link.LinkedModeUI; +import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags; +import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy; +import org.eclipse.jface.text.link.LinkedPosition; +import org.eclipse.jface.text.link.LinkedPositionGroup; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; + +import org.eclipse.cdt.core.dom.ast.IFunction; +import org.eclipse.cdt.core.dom.ast.IParameter; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.cdt.ui.CUIPlugin; + +/** + * This is a modified version of org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal + * + * This class adds a linked mode function compilation proposal with exit policy. + */ +public class FunctionCompletionProposal extends CCompletionProposal { + private boolean fHasParametersComputed; + private boolean fHasParameters; + protected IParameter[] fFunctionParameters; + protected int fInvocationOffset; + protected int fParseOffset; + protected ITranslationUnit fTranslationUnit; + protected IDocument fDocument; + + public FunctionCompletionProposal(String replacementString, int replacementOffset, int replacementLength, + Image image, String displayString, String idString, int relevance, ITextViewer viewer, + IFunction function, int invocationOffset, int parseOffset, ITranslationUnit translationUnit, IDocument document) { + super(replacementString, replacementOffset, replacementLength, image, displayString, idString, + relevance, viewer); + fFunctionParameters = function.getParameters(); + fInvocationOffset = invocationOffset; + fParseOffset = parseOffset; + fTranslationUnit = translationUnit; + fDocument = document; + } + + @Override + public void apply(IDocument document, char trigger, int offset) { + if (trigger == ' ' || trigger == '(') + trigger = '\0'; + super.apply(document, trigger, offset); + if (hasParameters()) { + setUpLinkedMode(document, ')'); + } else if (getReplacementString().endsWith(";")) { //$NON-NLS-1$ + setUpLinkedMode(document, ';'); + } + } + + /** + * Returns {@code true} if the method has any parameters, {@code true} if it has no parameters + */ + protected final boolean hasParameters() { + if (!fHasParametersComputed) { + fHasParametersComputed = true; + fHasParameters = computeHasParameters(); + } + return fHasParameters; + } + + private boolean computeHasParameters() { + return (fFunctionParameters != null && fFunctionParameters.length != 0); + } + + protected static class ExitPolicy implements IExitPolicy { + final char fExitCharacter; + + public ExitPolicy(char exitCharacter) { + fExitCharacter = exitCharacter; + } + + @Override + public ExitFlags doExit(LinkedModeModel environment, VerifyEvent event, int offset, int length) { + if (event.character == fExitCharacter) { + if (environment.anyPositionContains(offset)) { + return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false); + } else { + return new ExitFlags(ILinkedModeListener.UPDATE_CARET, true); + } + } + + switch (event.character) { + case ';': + return new ExitFlags(ILinkedModeListener.NONE, true); + default: + return null; + } + } + } + + protected void setUpLinkedMode(IDocument document, char closingCharacter) { + if (fTextViewer != null) { + int exit = getReplacementOffset() + getReplacementString().length(); + try { + LinkedPositionGroup group = new LinkedPositionGroup(); + group.addPosition(new LinkedPosition(document, fInvocationOffset, 0, + LinkedPositionGroup.NO_STOP)); + + LinkedModeModel model = new LinkedModeModel(); + model.addGroup(group); + model.forceInstall(); + + LinkedModeUI ui = new EditorLinkedModeUI(model, fTextViewer); + ui.setSimpleMode(true); + ui.setExitPolicy(new ExitPolicy(closingCharacter)); + ui.setExitPosition(fTextViewer, exit, 0, Integer.MAX_VALUE); + ui.setCyclingMode(LinkedModeUI.CYCLE_NEVER); + ui.enter(); + } catch (BadLocationException x) { + CUIPlugin.log(x); + } + } + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuesser.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuesser.java new file mode 100644 index 00000000000..828c38769c4 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuesser.java @@ -0,0 +1,390 @@ +/******************************************************************************* + * Copyright (c) 2000, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.text.contentassist; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.swt.graphics.Image; + +import org.eclipse.cdt.core.dom.ast.DOMException; +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.ICompositeType; +import org.eclipse.cdt.core.dom.ast.IEnumeration; +import org.eclipse.cdt.core.dom.ast.IEnumerator; +import org.eclipse.cdt.core.dom.ast.IFunction; +import org.eclipse.cdt.core.dom.ast.IPointerType; +import org.eclipse.cdt.core.dom.ast.IType; +import org.eclipse.cdt.core.dom.ast.ITypedef; +import org.eclipse.cdt.core.dom.ast.IVariable; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPBase; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPBinding; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassSpecialization; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassTemplate; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPField; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionTemplate; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPMember; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPUsingDeclaration; +import org.eclipse.cdt.core.model.CModelException; +import org.eclipse.cdt.core.model.IField; +import org.eclipse.cdt.core.parser.ast.ASTAccessVisibility; +import org.eclipse.cdt.core.parser.util.CharArrayUtils; +import org.eclipse.cdt.ui.CUIPlugin; + +import org.eclipse.cdt.internal.ui.viewsupport.CElementImageProvider; + +/** + * This class is based on org.eclipse.jdt.internal.ui.text.java.ParameterGuesser + * + * This class produces a logically-ordered list of applicable variables for later use as parameter guessing + * proposals for a function parameter. + */ +public class ParameterGuesser { + private final Set fAlreadyMatchedNames = new HashSet<>(); + + /** + * Variable type. Used to choose the best guess based on scope (LOCAL is preferred over FIELD, + * which is preferred over GLOBAL). + */ + static enum VariableType { + LOCAL(0), + FIELD(1), + GLOBAL(3); // Give the global variables a lower priority. + + private final int priority; + + private VariableType(int priority) { + this.priority = priority; + } + + public int getPriority() { + return priority; + } + } + + private final static class Variable { + public final String name; + public final VariableType variableType; + public final int positionScore; + + public int totalScore; + + public final char[] triggerChars; + public final ImageDescriptor descriptor; + + public boolean alreadyMatched; + + public Variable(String name, VariableType variableType, int positionScore, + char[] triggerChars, ImageDescriptor descriptor) { + this.name = name; + this.variableType = variableType; + this.positionScore = positionScore; + this.triggerChars = triggerChars; + this.descriptor = descriptor; + } + } + + private Collection evaluateVisibleMatches(IType expectedType, List suggestions) + throws CModelException { + Set res = new HashSet<>(); + int size = suggestions.size(); + for (int i = 0; i < size; i++) { + Variable variable = createVariable(suggestions.get(i), expectedType, i); + if (variable != null) { + if (fAlreadyMatchedNames.contains(variable.name)) { + variable.alreadyMatched = true; + } + res.add(variable); + } + } + return res; + } + + private boolean isAnonymousBinding(IBinding binding) { + char[] name = binding.getNameCharArray(); + return name.length == 0 || name[0] == '{'; + } + + protected IType getType(IBinding binding) { + if (!isAnonymousBinding(binding) && binding instanceof IVariable) + return ((IVariable) binding).getType(); + return null; + } + + private Variable createVariable(IBinding element, IType enclosingType, int positionScore) + throws CModelException { + IType elementType = getType(element); + String elementName = element.getName(); + if (elementType != null + && (elementType.toString().equals(enclosingType.toString()) + || elementType.isSameType(enclosingType) + || isParent(elementType, enclosingType) + || isReferenceTo(enclosingType, elementType) + || isReferenceTo(elementType, enclosingType))) { + VariableType variableType = VariableType.GLOBAL; + if (element instanceof ICPPField) { + variableType = VariableType.FIELD; + + } else if (element instanceof IVariable) { + try { + if (element instanceof ICPPBinding && ((ICPPBinding) element).isGloballyQualified()) { + variableType = VariableType.GLOBAL; + } else { + variableType = VariableType.LOCAL; + } + } catch (DOMException e) { + } + } + + // Handle reference case + if (isReferenceTo(enclosingType, elementType)) + elementName = "&" + elementName; //$NON-NLS-1$ + else if (isReferenceTo(elementType, enclosingType)) + elementName = "*" + elementName; //$NON-NLS-1$ + return new Variable(elementName, variableType, positionScore, CharArrayUtils.EMPTY_CHAR_ARRAY, + getImageDescriptor(element)); + } + return null; + } + + private boolean isReferenceTo(IType ref, IType val) { + if (ref instanceof IPointerType) { + IType ptr = ((IPointerType) ref).getType(); + if (ptr.toString().equals(val.toString()) || ptr.isSameType(val)) + return true; + } + return false; + } + + /** + * Returns true, if the parent type is a direct/indirect parent of the child type + */ + private boolean isParent(IType child, IType parent) { + if (child != null && parent != null + && child instanceof ICPPClassType && !(child instanceof ICPPClassSpecialization) + && parent instanceof ICPPClassType && !(parent instanceof ICPPClassSpecialization)) { + ICPPBase[] bases = ((ICPPClassType) child).getBases(); + for (ICPPBase base : bases) { + IType tmpType = base.getBaseClassType(); + if (tmpType.toString().equals(parent.toString()) || tmpType.isSameType(parent) + || isParent(tmpType, parent)) + return true; + } + } + + return false; + } + + private ImageDescriptor getImageDescriptor(IBinding binding) { + ImageDescriptor imageDescriptor = null; + + if (binding instanceof ITypedef) { + imageDescriptor = CElementImageProvider.getTypedefImageDescriptor(); + } else if (binding instanceof ICompositeType) { + if (((ICompositeType) binding).getKey() == ICPPClassType.k_class + || binding instanceof ICPPClassTemplate) + imageDescriptor = CElementImageProvider.getClassImageDescriptor(); + else if (((ICompositeType) binding).getKey() == ICompositeType.k_struct) + imageDescriptor = CElementImageProvider.getStructImageDescriptor(); + else if (((ICompositeType) binding).getKey() == ICompositeType.k_union) + imageDescriptor = CElementImageProvider.getUnionImageDescriptor(); + } else if (binding instanceof ICPPMethod) { + switch (((ICPPMethod) binding).getVisibility()) { + case ICPPMember.v_private: + imageDescriptor = CElementImageProvider.getMethodImageDescriptor(ASTAccessVisibility.PRIVATE); + break; + case ICPPMember.v_protected: + imageDescriptor = + CElementImageProvider.getMethodImageDescriptor(ASTAccessVisibility.PROTECTED); + break; + default: + imageDescriptor = CElementImageProvider.getMethodImageDescriptor(ASTAccessVisibility.PUBLIC); + break; + } + } else if (binding instanceof IFunction) { + imageDescriptor = CElementImageProvider.getFunctionImageDescriptor(); + } else if (binding instanceof ICPPField) { + switch (((ICPPField) binding).getVisibility()) { + case ICPPMember.v_private: + imageDescriptor = CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PRIVATE); + break; + case ICPPMember.v_protected: + imageDescriptor = + CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PROTECTED); + break; + default: + imageDescriptor = CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PUBLIC); + break; + } + } else if (binding instanceof IField) { + imageDescriptor = CElementImageProvider.getFieldImageDescriptor(ASTAccessVisibility.PUBLIC); + } else if (binding instanceof IVariable) { + imageDescriptor = CElementImageProvider.getVariableImageDescriptor(); + } else if (binding instanceof IEnumeration) { + imageDescriptor = CElementImageProvider.getEnumerationImageDescriptor(); + } else if (binding instanceof IEnumerator) { + imageDescriptor = CElementImageProvider.getEnumeratorImageDescriptor(); + } else if (binding instanceof ICPPNamespace) { + imageDescriptor = CElementImageProvider.getNamespaceImageDescriptor(); + } else if (binding instanceof ICPPFunctionTemplate) { + imageDescriptor = CElementImageProvider.getFunctionImageDescriptor(); + } else if (binding instanceof ICPPUsingDeclaration) { + IBinding[] delegates = ((ICPPUsingDeclaration) binding).getDelegates(); + if (delegates.length > 0) + return getImageDescriptor(delegates[0]); + } + return imageDescriptor; + } + + /** + * Returns the matches for the type and name argument, ordered by match quality. + * + * @param expectedType the qualified type of the parameter we are trying to match + * @param paramName the name of the parameter (used to find similarly named matches) + * @param pos the position + * @param suggestions the suggestions or null + * @param isLastParameter true iff this proposal is for the last parameter of a method + * @return returns the name of the best match, or null if no match found + */ + public ICompletionProposal[] parameterProposals(IType expectedType, String paramName, Position pos, + List suggestions, boolean isLastParameter) + throws CModelException { + List typeMatches = new ArrayList<>(evaluateVisibleMatches(expectedType, suggestions)); + orderMatches(typeMatches, paramName); + + ICompletionProposal[] ret = new ICompletionProposal[typeMatches.size()]; + int i = 0; + int replacementLength = 0; + for (Variable v : typeMatches) { + if (i == 0) { + fAlreadyMatchedNames.add(v.name); + replacementLength = v.name.length(); + } + + String displayString = v.name; + + final char[] triggers; + if (isLastParameter) { + triggers = v.triggerChars; + } else { + triggers = new char[v.triggerChars.length + 1]; + System.arraycopy(v.triggerChars, 0, triggers, 0, v.triggerChars.length); + triggers[triggers.length - 1] = ','; + } + ret[i++] = new PositionBasedCompletionProposal(v.name, pos, replacementLength, + getImage(v.descriptor), displayString, null, null, triggers); + } + return ret; + } + + private static class MatchComparator implements Comparator { + @Override + public int compare(Variable one, Variable two) { + return two.totalScore - one.totalScore; + } + } + + /** + * Determines the best match of all possible type matches. The input into this method is all possible + * completions that match the type of the argument. The purpose of this method is to choose among them + * based on the following simple rules: + * + * 1) Local Variables > Instance/Class Variables > Inherited Instance/Class Variables + * + * 2) A longer case insensitive substring match will prevail + * + * 3) Variables that have not been used already during this completion will prevail over those that have + * already been used (this avoids the same String/int/char from being passed in for multiple arguments) + * + * 4) A better source position score will prevail (the declaration point of the variable, or + * "how close to the point of completion?" + * + * @param typeMatches + * the list of type matches + * @param paramName + * the parameter name + */ + private static void orderMatches(List typeMatches, String paramName) { + if (typeMatches != null) { + calculateVariablesScores(paramName, typeMatches); + Collections.sort(typeMatches, new MatchComparator()); + } + } + + /** + * Set the replacement scores of the variables with respect to the parameter name. + */ + private static void calculateVariablesScores(String parameterName, List variables) { + for (Variable v : variables) { + v.totalScore = score(v, parameterName); + } + } + + /** + * The four order criteria as described below - put already used into bit 10, all others into bits + * 0-9, 11-20, 21-30; 31 is sign - always 0 + * + * @param v the variable + * @param parameterName the name of the parameter to be replaced. + * @return the score for v + */ + private static int score(Variable v, String parameterName) { + int variableScore = 100 - v.variableType.getPriority(); // since these are increasing with distance + int subStringScore = getContainedString(v.name, parameterName).length(); + // Substring scores under 60% are not considered. + // This prevents marginal matches like a - ba and false - isBool that will + // destroy the sort order. + int shorter = Math.min(v.name.length(), parameterName.length()); + if (subStringScore < 0.6 * shorter) + subStringScore = 0; + + int positionScore = v.positionScore; + int matchedScore = v.alreadyMatched ? 0 : 1; + + int score = variableScore << 21 | subStringScore << 11 | matchedScore << 10 | positionScore; + + return score; + } + + /** + * Returns the shorter string if it is a substring of the longer string, or an empty + * string otherwise. + * @param first the first string + * @param second the second string + */ + private static String getContainedString(String first, String second) { + // Now only considering the case where shorter string is part of longer string. + // TODO: Use a more efficient technique to get the common string (i.e. suffix tree). + String shorterStr = first.length() < second.length() ? first : second; + String longerStr = first == shorterStr ? second : first; + if (longerStr.contains(shorterStr)) { + return shorterStr; + } else { + return ""; //$NON-NLS-1$ + } + } + + private static Image getImage(ImageDescriptor descriptor) { + return descriptor == null ? null : CUIPlugin.getImageDescriptorRegistry().get(descriptor); + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuessingProposal.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuessingProposal.java new file mode 100644 index 00000000000..b4d94a9e238 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/ParameterGuessingProposal.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 2000, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew McCullough - initial API and implementation + * IBM Corporation - general improvement and bug fixes, partial reimplementation + * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.text.contentassist; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IPositionUpdater; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.link.ILinkedModeListener; +import org.eclipse.jface.text.link.InclusivePositionUpdater; +import org.eclipse.jface.text.link.LinkedModeModel; +import org.eclipse.jface.text.link.LinkedModeUI; +import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags; +import org.eclipse.jface.text.link.LinkedPosition; +import org.eclipse.jface.text.link.LinkedPositionGroup; +import org.eclipse.jface.text.link.ProposalPosition; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; + +import org.eclipse.cdt.core.dom.ast.IBinding; +import org.eclipse.cdt.core.dom.ast.IFunction; +import org.eclipse.cdt.core.dom.ast.IParameter; +import org.eclipse.cdt.core.dom.ast.IType; +import org.eclipse.cdt.core.model.ITranslationUnit; +import org.eclipse.cdt.ui.CUIPlugin; + +import org.eclipse.cdt.internal.ui.editor.CEditor; +import org.eclipse.cdt.internal.ui.editor.EditorHighlightingSynchronizer; + +/** + * This class is based on org.eclipse.jdt.internal.ui.text.java.ParameterGuessingProposal + * + * Extents the basic Function Completion Proposal to add a linked mode for each of the function parameters + * with a list of suggestions for each parameter. + */ +public class ParameterGuessingProposal extends FunctionCompletionProposal { + private ICompletionProposal[][] fChoices; // initialized by guessParameters() + private Position[] fPositions; // initialized by guessParameters() + private IRegion fSelectedRegion; // initialized by apply() + private IPositionUpdater fUpdater; + private String fFullPrefix; // The string from the start of the statement to the invocation offset. + private CEditor fCEditor; + private char[][] fParametersNames; + private IType[] fParametersTypes; + private ArrayList fAssignableElements; + + public static ParameterGuessingProposal createProposal(CContentAssistInvocationContext context, + ArrayList availableElements, CCompletionProposal proposal, IFunction function, + String prefix) { + String replacement = getParametersList(function); + String fullPrefix = function.getName() + "("; //$NON-NLS-1$ + int replacementOffset = proposal.getReplacementOffset(); + int replacementLength = 0; + int invocationOffset = context.getInvocationOffset(); + int parseOffset = context.getParseOffset(); + IDocument document = context.getDocument(); + /* + * Adjust the replacement offset, the replacement string and the replacement length for the case of + * invoking after '('. - The replacement offset will be calculated to point to the start of the + * function call statement, as in that case the proposal.getReplacementOffset() doesn't point to that. + * - The replacement string will contain the in-editor prefix instead of the function name only, to + * handle the case of C++ function templates. - The length will be updated after changing the + * replacement string. + */ + if (isInsideBracket(invocationOffset, parseOffset)) { + replacementOffset = parseOffset - prefix.length(); + try { + fullPrefix = document.get(replacementOffset, invocationOffset - replacementOffset); + replacement = fullPrefix + replacement + ")"; //$NON-NLS-1$ + } catch (BadLocationException e1) { + } + try { + // remove ')' from the replacement string if it is auto appended. + if (document.getChar(invocationOffset) == ')') + replacement = replacement.substring(0, replacement.length() - 1); + } catch (BadLocationException e) { + } + } else { + replacement = fullPrefix + replacement + ")"; //$NON-NLS-1$ + replacementOffset = proposal.getReplacementOffset(); + } + replacementLength = replacement.length(); + ParameterGuessingProposal ret = new ParameterGuessingProposal(replacement, replacementOffset, + replacementLength, proposal.getImage(), proposal.getDisplayString(), proposal.getIdString(), + proposal.getRelevance(), context.getViewer(), function, invocationOffset, parseOffset, + context.getTranslationUnit(), document); + ret.setContextInformation(proposal.getContextInformation()); + ret.fFullPrefix = fullPrefix; + ret.fCEditor = getCEditor(context.getEditor()); + /* + * Get all defined elements before the start of the statement. ex: int a = foo( ^ --> We don't want + * 'a' as a suggestion. ex2: char* foo(int a, int b) {return NULL;} void bar(char* name){} ... + * bar(foo( ^ --> If this offset is used, the only defined name will be "bar(char*)". + */ + ret.fAssignableElements = availableElements; + return ret; + } + + /** + * Returns a comma-separated list of parameters + */ + private static String getParametersList(IFunction method) { + StringBuilder params = new StringBuilder(); + for (IParameter param : method.getParameters()) { + if (params.length() != 0) + params.append(", "); //$NON-NLS-1$ + params.append(param.getName()); + } + return params.toString(); + } + + public ParameterGuessingProposal(String replacementString, int replacementOffset, int replacementLength, + Image image, String displayString, String idString, int relevance, ITextViewer viewer, + IFunction function, int invocationOffset, int parseOffset, ITranslationUnit tu, IDocument document) { + super(replacementString, replacementOffset, replacementLength, image, displayString, idString, + relevance, viewer, function, invocationOffset, parseOffset, tu, document); + } + + /** + * Checks if the invocation of content assist was after open bracket. + */ + private static boolean isInsideBracket(int invocationOffset, int parseOffset) { + return invocationOffset - parseOffset != 0; + } + + /** + * {@inheritDoc} + */ + @Override + public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) { + if (isInsideBracket(fInvocationOffset, fParseOffset)) { + try { + return fDocument.get(getReplacementOffset(), fInvocationOffset - getReplacementOffset()); + } catch (BadLocationException e) { + } + } + return super.getPrefixCompletionText(document, completionOffset); + } + + @Override + public void apply(final IDocument document, char trigger, int offset) { + super.apply(document, trigger, offset); + + // Initialize necessary fields + fParametersNames = getFunctionParametersNames(fFunctionParameters); + fParametersTypes = getFunctionParametersTypes(fFunctionParameters); + + try { + guessParameters(); + } catch (Exception e) { + CUIPlugin.log(e); + return; + } + + int baseOffset = getReplacementOffset(); + String replacement = getReplacementString(); + try { + if (fPositions != null && fTextViewer != null) { + LinkedModeModel model = new LinkedModeModel(); + + for (int i = 0; i < fPositions.length; i++) { + LinkedPositionGroup group = new LinkedPositionGroup(); + int positionOffset = fPositions[i].getOffset(); + int positionLength = fPositions[i].getLength(); + + if (fChoices[i].length == 0) { + group.addPosition(new LinkedPosition(document, positionOffset, positionLength, + LinkedPositionGroup.NO_STOP)); + } else { + ensurePositionCategoryInstalled(document, model); + document.addPosition(getCategory(), fPositions[i]); + group.addPosition(new ProposalPosition(document, positionOffset, positionLength, + LinkedPositionGroup.NO_STOP, fChoices[i])); + } + model.addGroup(group); + } + + model.forceInstall(); + if (fCEditor != null) { + model.addLinkingListener(new EditorHighlightingSynchronizer(fCEditor)); + } + + LinkedModeUI ui = new EditorLinkedModeUI(model, fTextViewer); + ui.setExitPosition(fTextViewer, baseOffset + replacement.length(), 0, Integer.MAX_VALUE); + // exit character can be either ')' or ';' + final char exitChar = replacement.charAt(replacement.length() - 1); + ui.setExitPolicy(new ExitPolicy(exitChar) { + @Override + public ExitFlags doExit(LinkedModeModel model2, VerifyEvent event, int offset2, int length) { + if (event.character == ',') { + for (int i = 0; i < fPositions.length - 1; i++) { // not for the last one + Position position = fPositions[i]; + if (position.offset <= offset2 + && offset2 + length <= position.offset + position.length) { + event.character = '\t'; + event.keyCode = SWT.TAB; + return null; + } + } + } else if (event.character == ')' && exitChar != ')') { + // exit from link mode when user is in the last ')' position. + Position position = fPositions[fPositions.length - 1]; + if (position.offset <= offset2 + && offset2 + length <= position.offset + position.length) { + return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false); + } + } + return super.doExit(model2, event, offset2, length); + } + }); + ui.setCyclingMode(LinkedModeUI.CYCLE_WHEN_NO_PARENT); + ui.setDoContextInfo(true); + ui.enter(); + fSelectedRegion = ui.getSelectedRegion(); + } else { + fSelectedRegion = new Region(baseOffset + replacement.length(), 0); + } + } catch (Exception e) { + ensurePositionCategoryRemoved(document); + CUIPlugin.log(e); + } + } + + @Override + public Point getSelection(IDocument document) { + if (fSelectedRegion == null) + return new Point(getReplacementOffset(), 0); + + return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); + } + + private void guessParameters() throws Exception { + int count = fParametersNames.length; + fPositions = new Position[count]; + fChoices = new ICompletionProposal[count][]; + + ParameterGuesser guesser = new ParameterGuesser(); + + for (int i = 0; i < count; i++) { + String paramName = new String(fParametersNames[i]); + Position position = new Position(0, 0); + + boolean isLastParameter = i == count - 1; + ArrayList allProposals = new ArrayList<>(); + ICompletionProposal[] argumentProposals = guesser.parameterProposals(fParametersTypes[i], + paramName, position, fAssignableElements, isLastParameter); + allProposals.addAll(Arrays.asList(argumentProposals)); + fPositions[i] = position; + fChoices[i] = argumentProposals; + } + updateProposalsPossitions(); + } + + private void updateProposalsPossitions() throws Exception { + StringBuffer buffer = new StringBuffer(); + buffer.append(fFullPrefix); + setCursorPosition(buffer.length()); + + int count = fParametersNames.length; + int replacementOffset = getReplacementOffset(); + + for (int i = 0; i < count; i++) { + if (i != 0) + buffer.append(", "); //$NON-NLS-1$ + + String argument = new String(fParametersNames[i]); + + Position position = fPositions[i]; + position.setOffset(replacementOffset + buffer.length()); + position.setLength(argument.length()); + + buffer.append(argument); + } + } + + private static IType[] getFunctionParametersTypes(IParameter[] functionParameters) { + IType[] ret = new IType[functionParameters.length]; + for (int i = 0; i < functionParameters.length; i++) { + ret[i] = functionParameters[i].getType(); + } + return ret; + } + + private static char[][] getFunctionParametersNames(IParameter[] functionParameters) { + char[][] parameterNames = new char[functionParameters.length][]; + for (int i = 0; i < functionParameters.length; i++) { + parameterNames[i] = functionParameters[i].getNameCharArray(); + } + return parameterNames; + } + + private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { + if (!document.containsPositionCategory(getCategory())) { + document.addPositionCategory(getCategory()); + fUpdater = new InclusivePositionUpdater(getCategory()); + document.addPositionUpdater(fUpdater); + + model.addLinkingListener(new ILinkedModeListener() { + @Override + public void left(LinkedModeModel environment, int flags) { + ensurePositionCategoryRemoved(document); + } + + @Override + public void suspend(LinkedModeModel environment) { + } + + @Override + public void resume(LinkedModeModel environment, int flags) { + } + }); + } + } + + private void ensurePositionCategoryRemoved(IDocument document) { + if (document.containsPositionCategory(getCategory())) { + try { + document.removePositionCategory(getCategory()); + } catch (BadPositionCategoryException e) { + // ignore + } + document.removePositionUpdater(fUpdater); + } + } + + private String getCategory() { + return "ParameterGuessingProposal_" + toString(); //$NON-NLS-1$ + } + + /** + * Returns the c editor, or null if it cannot be determined. + */ + private static CEditor getCEditor(IEditorPart editorPart) { + if (editorPart instanceof CEditor) { + return (CEditor) editorPart; + } + return null; + } + + /** + * Returns the guesses for each parameter + */ + public ICompletionProposal[][] getParametersGuesses() { + return fChoices; + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/PositionBasedCompletionProposal.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/PositionBasedCompletionProposal.java new file mode 100644 index 00000000000..bc809cc195f --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/contentassist/PositionBasedCompletionProposal.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2000, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Mohamed Azab (Mentor Graphics) - Bug 438549. Add mechanism for parameter guessing. + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.text.contentassist; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +/** + * An enhanced implementation of the ICompletionProposal interface implementing all the extension interfaces. + * It uses a position to track its replacement offset and length. The position must be set up externally. + */ +public class PositionBasedCompletionProposal implements ICompletionProposal, ICompletionProposalExtension, ICompletionProposalExtension2 { + /** The string to be displayed in the completion proposal popup */ + private String fDisplayString; + /** The replacement string */ + private String fReplacementString; + /** The replacement position. */ + private Position fReplacementPosition; + /** The cursor position after this proposal has been applied */ + private int fCursorPosition; + /** The image to be displayed in the completion proposal popup */ + private Image fImage; + /** The context information of this proposal */ + private IContextInformation fContextInformation; + /** The additional info of this proposal */ + private String fAdditionalProposalInfo; + /** The trigger characters */ + private char[] fTriggerCharacters; + + /** + * Creates a new completion proposal based on the provided information. The replacement string is + * considered being the display string too. All remaining fields are set to null. + * + * @param replacementString the actual string to be inserted into the document + * @param replacementPosition the position of the text to be replaced + * @param cursorPosition the position of the cursor following the insert relative to replacementOffset + */ + public PositionBasedCompletionProposal(String replacementString, Position replacementPosition, int cursorPosition) { + this(replacementString, replacementPosition, cursorPosition, null, null, null, null, null); + } + + /** + * Creates a new completion proposal. All fields are initialized based on the provided information. + * + * @param replacementString the actual string to be inserted into the document + * @param replacementPosition the position of the text to be replaced + * @param cursorPosition the position of the cursor following the insert relative to replacementOffset + * @param image the image to display for this proposal + * @param displayString the string to be displayed for the proposal + * @param contextInformation the context information associated with this proposal + * @param additionalProposalInfo the additional information associated with this proposal + * @param triggers the trigger characters + */ + public PositionBasedCompletionProposal(String replacementString, Position replacementPosition, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo, char[] triggers) { + Assert.isNotNull(replacementString); + Assert.isTrue(replacementPosition != null); + + fReplacementString= replacementString; + fReplacementPosition= replacementPosition; + fCursorPosition= cursorPosition; + fImage= image; + fDisplayString= displayString; + fContextInformation= contextInformation; + fAdditionalProposalInfo= additionalProposalInfo; + fTriggerCharacters= triggers; + } + + @Override + public void apply(IDocument document) { + try { + document.replace(fReplacementPosition.getOffset(), fReplacementPosition.getLength(), fReplacementString); + } catch (BadLocationException x) { + // ignore + } + } + + @Override + public Point getSelection(IDocument document) { + return new Point(fReplacementPosition.getOffset() + fCursorPosition, 0); + } + + @Override + public IContextInformation getContextInformation() { + return fContextInformation; + } + + @Override + public Image getImage() { + return fImage; + } + + @Override + public String getDisplayString() { + if (fDisplayString != null) + return fDisplayString; + return fReplacementString; + } + + @Override + public String getAdditionalProposalInfo() { + return fAdditionalProposalInfo; + } + + @Override + public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { + apply(viewer.getDocument()); + } + + @Override + public void selected(ITextViewer viewer, boolean smartToggle) { + } + + @Override + public void unselected(ITextViewer viewer) { + } + + @Override + public boolean validate(IDocument document, int offset, DocumentEvent event) { + try { + String content= document.get(fReplacementPosition.getOffset(), offset - fReplacementPosition.getOffset()); + if (fReplacementString.startsWith(content)) + return true; + } catch (BadLocationException e) { + // ignore concurrently modified document + } + return false; + } + + @Override + public void apply(IDocument document, char trigger, int offset) { + // not called any more + } + + @Override + public boolean isValidFor(IDocument document, int offset) { + // not called any more + return false; + } + + @Override + public char[] getTriggerCharacters() { + return fTriggerCharacters; + } + + @Override + public int getContextInformationPosition() { + return fReplacementPosition.getOffset(); + } +} +