/** * <copyright> * </copyright> * * */ package org.reuseware.air.language.abstractsyntax.resource.as.ui; /** * A CodeCompletionHelper can be used to derive completion proposals for partial * documents. It runs the parser generated by EMFText in a special mode (i.e., the * rememberExpectedElements mode). Based on the elements that are expected by the * parser for different regions in the document, valid proposals are computed. */ public class AsCodeCompletionHelper { private org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsAttributeValueProvider attributeValueProvider = new org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsAttributeValueProvider(); /** * Computes a set of proposals for the given document assuming the cursor is at * 'cursorOffset'. The proposals are derived using the meta information, i.e., the * generated language plug-in. * * @param originalResource * @param content the documents content * @param cursorOffset * * @return */ public org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal[] computeCompletionProposals(org.reuseware.air.language.abstractsyntax.resource.as.IAsTextResource originalResource, String content, int cursorOffset) { org.eclipse.emf.ecore.resource.ResourceSet resourceSet = new org.eclipse.emf.ecore.resource.impl.ResourceSetImpl(); // the shadow resource needs the same URI because reference resolvers may use the // URI to resolve external references org.reuseware.air.language.abstractsyntax.resource.as.IAsTextResource resource = (org.reuseware.air.language.abstractsyntax.resource.as.IAsTextResource) resourceSet.createResource(originalResource.getURI()); java.io.ByteArrayInputStream inputStream = new java.io.ByteArrayInputStream(content.getBytes()); org.reuseware.air.language.abstractsyntax.resource.as.IAsMetaInformation metaInformation = resource.getMetaInformation(); org.reuseware.air.language.abstractsyntax.resource.as.IAsTextParser parser = metaInformation.createParser(inputStream, null); org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[] expectedElements = parseToExpectedElements(parser, resource); if (expectedElements == null) { return new org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal[0]; } if (expectedElements.length == 0) { return new org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal[0]; } java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedAfterCursor = java.util.Arrays.asList(getElementsExpectedAt(expectedElements, cursorOffset)); java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedBeforeCursor = java.util.Arrays.asList(getElementsExpectedAt(expectedElements, cursorOffset - 1)); setPrefixes(expectedAfterCursor, content, cursorOffset); setPrefixes(expectedBeforeCursor, content, cursorOffset); // first we derive all possible proposals from the set of elements that are // expected at the cursor position java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> allProposals = new java.util.LinkedHashSet<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal>(); java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> rightProposals = deriveProposals(expectedAfterCursor, content, resource, cursorOffset); java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> leftProposals = deriveProposals(expectedBeforeCursor, content, resource, cursorOffset - 1); // second, the set of left proposals (i.e., the ones before the cursor) is checked // for emptiness. if the set is empty, the right proposals (i.e., the ones after // the cursor are removed, because it does not make sense to propose them until // the element before the cursor was completed allProposals.addAll(leftProposals); if (leftProposals.isEmpty()) { allProposals.addAll(rightProposals); } // third, the proposals are sorted according to their relevance proposals that // matched the prefix are preferred over ones that did not afterward proposals are // sorted alphabetically final java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> sortedProposals = new java.util.ArrayList<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal>(allProposals); java.util.Collections.sort(sortedProposals); return sortedProposals.toArray(new org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal[sortedProposals.size()]); } public org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[] parseToExpectedElements(org.reuseware.air.language.abstractsyntax.resource.as.IAsTextParser parser, org.reuseware.air.language.abstractsyntax.resource.as.IAsTextResource resource) { final java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedElements = parser.parseToExpectedElements(null, resource); if (expectedElements == null) { return new org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[0]; } removeDuplicateEntries(expectedElements); removeInvalidEntriesAtEnd(expectedElements); return expectedElements.toArray(new org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[expectedElements.size()]); } private void removeDuplicateEntries(java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedElements) { for (int i = 0; i < expectedElements.size() - 1; i++) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal elementAtIndex = expectedElements.get(i); for (int j = i + 1; j < expectedElements.size();) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal elementAtNext = expectedElements.get(j); if (elementAtIndex.equals(elementAtNext) && elementAtIndex.getStartExcludingHiddenTokens() == elementAtNext.getStartExcludingHiddenTokens()) { expectedElements.remove(j); } else { j++; } } } } private void removeInvalidEntriesAtEnd(java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedElements) { for (int i = 0; i < expectedElements.size() - 1;) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal elementAtIndex = expectedElements.get(i); org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal elementAtNext = expectedElements.get(i + 1); if (elementAtIndex.getStartExcludingHiddenTokens() == elementAtNext.getStartExcludingHiddenTokens() && shouldRemove(elementAtIndex.getFollowSetID(), elementAtNext.getFollowSetID())) { expectedElements.remove(i + 1); } else { i++; } } } public boolean shouldRemove(int followSetID1, int followSetID2) { return followSetID1 != followSetID2; } private String findPrefix(java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedElements, org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal expectedAtCursor, String content, int cursorOffset) { if (cursorOffset < 0) { return ""; } int end = 0; for (org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal expectedElement : expectedElements) { if (expectedElement == expectedAtCursor) { final int start = expectedElement.getStartExcludingHiddenTokens(); if (start >= 0 && start < Integer.MAX_VALUE) { end = start; } break; } } end = Math.min(end, cursorOffset); final String prefix = content.substring(end, Math.min(content.length(), cursorOffset)); return prefix; } private java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> deriveProposals(java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedElements, String content, org.reuseware.air.language.abstractsyntax.resource.as.IAsTextResource resource, int cursorOffset) { java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> resultSet = new java.util.LinkedHashSet<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal>(); for (org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal expectedElement : expectedElements) { resultSet.addAll(deriveProposals(expectedElement, content, resource, cursorOffset)); } return resultSet; } private java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> deriveProposals(org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal expectedTerminal, String content, org.reuseware.air.language.abstractsyntax.resource.as.IAsTextResource resource, int cursorOffset) { org.reuseware.air.language.abstractsyntax.resource.as.IAsMetaInformation metaInformation = resource.getMetaInformation(); org.reuseware.air.language.abstractsyntax.resource.as.IAsLocationMap locationMap = resource.getLocationMap(); org.reuseware.air.language.abstractsyntax.resource.as.IAsExpectedElement expectedElement = (org.reuseware.air.language.abstractsyntax.resource.as.IAsExpectedElement) expectedTerminal.getTerminal(); if (expectedElement instanceof org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedCsString) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedCsString csString = (org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedCsString) expectedElement; return deriveProposal(csString, content, expectedTerminal.getPrefix(), cursorOffset); } else if (expectedElement instanceof org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedStructuralFeature) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedStructuralFeature expectedFeature = (org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedStructuralFeature) expectedElement; org.eclipse.emf.ecore.EStructuralFeature feature = expectedFeature.getFeature(); org.eclipse.emf.ecore.EClassifier featureType = feature.getEType(); java.util.List<org.eclipse.emf.ecore.EObject> elementsAtCursor = locationMap.getElementsAt(cursorOffset); org.eclipse.emf.ecore.EObject container = null; // we need to skip the proxy elements at the cursor, because they are not the // container for the reference we try to complete for (int i = 0; i < elementsAtCursor.size(); i++) { container = elementsAtCursor.get(i); if (!container.eIsProxy()) { break; } } // if no container can be found, the cursor is probably at the end of the // document. we need to create artificial containers. if (container == null) { boolean attachedArtificialContainer = false; org.eclipse.emf.ecore.EClass containerClass = expectedTerminal.getTerminal().getRuleMetaclass(); org.eclipse.emf.ecore.EStructuralFeature[] containmentTrace = expectedTerminal.getContainmentTrace(); java.util.List<org.eclipse.emf.ecore.EObject> contentList = null; for (org.eclipse.emf.ecore.EStructuralFeature eStructuralFeature : containmentTrace) { if (attachedArtificialContainer) { break; } org.eclipse.emf.ecore.EClass neededClass = eStructuralFeature.getEContainingClass(); // fill the content list during the first iteration of the loop if (contentList == null) { contentList = new java.util.ArrayList<org.eclipse.emf.ecore.EObject>(); java.util.Iterator<org.eclipse.emf.ecore.EObject> allContents = resource.getAllContents(); while (allContents.hasNext()) { org.eclipse.emf.ecore.EObject next = allContents.next(); contentList.add(next); } } // find object to attach artificial container to for (int i = contentList.size() - 1; i >= 0; i--) { org.eclipse.emf.ecore.EObject object = contentList.get(i); if (neededClass.isInstance(object)) { org.eclipse.emf.ecore.EObject newContainer = containerClass.getEPackage().getEFactoryInstance().create(containerClass); if (eStructuralFeature.getEType().isInstance(newContainer)) { org.reuseware.air.language.abstractsyntax.resource.as.util.AsEObjectUtil.setFeature(object, eStructuralFeature, newContainer, false); container = newContainer; attachedArtificialContainer = true; } } } } } if (feature instanceof org.eclipse.emf.ecore.EReference) { org.eclipse.emf.ecore.EReference reference = (org.eclipse.emf.ecore.EReference) feature; if (featureType instanceof org.eclipse.emf.ecore.EClass) { if (reference.isContainment()) { // the FOLLOW set should contain only non-containment references assert false; } else { return handleNCReference(metaInformation, container, reference, expectedTerminal.getPrefix(), expectedFeature.getTokenName()); } } } else if (feature instanceof org.eclipse.emf.ecore.EAttribute) { org.eclipse.emf.ecore.EAttribute attribute = (org.eclipse.emf.ecore.EAttribute) feature; if (featureType instanceof org.eclipse.emf.ecore.EEnum) { org.eclipse.emf.ecore.EEnum enumType = (org.eclipse.emf.ecore.EEnum) featureType; return handleEnumAttribute(metaInformation, expectedFeature, enumType, expectedTerminal.getPrefix(), container); } else { // handle EAttributes (derive default value depending on the type of the // attribute, figure out token resolver, and call deResolve()) return handleAttribute(metaInformation, expectedFeature, container, attribute, expectedTerminal.getPrefix()); } } else { // there should be no other subclass of EStructuralFeature assert false; } } else { // there should be no other class implementing IExpectedElement assert false; } return java.util.Collections.emptyList(); } private java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> handleEnumAttribute(org.reuseware.air.language.abstractsyntax.resource.as.IAsMetaInformation metaInformation, org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedStructuralFeature expectedFeature, org.eclipse.emf.ecore.EEnum enumType, String prefix, org.eclipse.emf.ecore.EObject container) { java.util.Collection<org.eclipse.emf.ecore.EEnumLiteral> enumLiterals = enumType.getELiterals(); java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> result = new java.util.LinkedHashSet<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal>(); for (org.eclipse.emf.ecore.EEnumLiteral literal : enumLiterals) { String unResolvedLiteral = literal.getLiteral(); // use token resolver to get de-resolved value of the literal org.reuseware.air.language.abstractsyntax.resource.as.IAsTokenResolverFactory tokenResolverFactory = metaInformation.getTokenResolverFactory(); org.reuseware.air.language.abstractsyntax.resource.as.IAsTokenResolver tokenResolver = tokenResolverFactory.createTokenResolver(expectedFeature.getTokenName()); String resolvedLiteral = tokenResolver.deResolve(unResolvedLiteral, expectedFeature.getFeature(), container); if (matches(resolvedLiteral, prefix)) { result.add(new org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal(resolvedLiteral, prefix, !"".equals(prefix), true)); } } return result; } private java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> handleNCReference(org.reuseware.air.language.abstractsyntax.resource.as.IAsMetaInformation metaInformation, org.eclipse.emf.ecore.EObject container, org.eclipse.emf.ecore.EReference reference, java.lang.String prefix, java.lang.String tokenName) { // proposals for non-containment references are derived by calling the reference // resolver switch in fuzzy mode. org.reuseware.air.language.abstractsyntax.resource.as.IAsReferenceResolverSwitch resolverSwitch = metaInformation.getReferenceResolverSwitch(); org.reuseware.air.language.abstractsyntax.resource.as.IAsTokenResolverFactory tokenResolverFactory = metaInformation.getTokenResolverFactory(); org.reuseware.air.language.abstractsyntax.resource.as.IAsReferenceResolveResult<org.eclipse.emf.ecore.EObject> result = new org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsReferenceResolveResult<org.eclipse.emf.ecore.EObject>(true); resolverSwitch.resolveFuzzy(prefix, container, reference, 0, result); java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.IAsReferenceMapping<org.eclipse.emf.ecore.EObject>> mappings = result.getMappings(); if (mappings != null) { java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> resultSet = new java.util.LinkedHashSet<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal>(); for (org.reuseware.air.language.abstractsyntax.resource.as.IAsReferenceMapping<org.eclipse.emf.ecore.EObject> mapping : mappings) { org.eclipse.swt.graphics.Image image = null; if (mapping instanceof org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsElementMapping<?>) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsElementMapping<?> elementMapping = (org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsElementMapping<?>) mapping; java.lang.Object target = elementMapping.getTargetElement(); // de-resolve reference to obtain correct identifier org.reuseware.air.language.abstractsyntax.resource.as.IAsTokenResolver tokenResolver = tokenResolverFactory.createTokenResolver(tokenName); final String identifier = tokenResolver.deResolve(elementMapping.getIdentifier(), reference, container); if (target instanceof org.eclipse.emf.ecore.EObject) { image = getImage((org.eclipse.emf.ecore.EObject) target); } // check the prefix. return only matching references if (matches(identifier, prefix)) { resultSet.add(new org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal(identifier, prefix, true, true, image)); } } } return resultSet; } return java.util.Collections.emptyList(); } private java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> handleAttribute(org.reuseware.air.language.abstractsyntax.resource.as.IAsMetaInformation metaInformation, org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedStructuralFeature expectedFeature, org.eclipse.emf.ecore.EObject container, org.eclipse.emf.ecore.EAttribute attribute, java.lang.String prefix) { java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> resultSet = new java.util.LinkedHashSet<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal>(); java.lang.Object[] defaultValues = attributeValueProvider.getDefaultValues(attribute); if (defaultValues != null) { for (Object defaultValue : defaultValues) { if (defaultValue != null) { org.reuseware.air.language.abstractsyntax.resource.as.IAsTokenResolverFactory tokenResolverFactory = metaInformation.getTokenResolverFactory(); String tokenName = expectedFeature.getTokenName(); if (tokenName != null) { org.reuseware.air.language.abstractsyntax.resource.as.IAsTokenResolver tokenResolver = tokenResolverFactory.createTokenResolver(tokenName); if (tokenResolver != null) { String defaultValueAsString = tokenResolver.deResolve(defaultValue, attribute, container); if (matches(defaultValueAsString, prefix)) { resultSet.add(new org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal(defaultValueAsString, prefix, !"".equals(prefix), true)); } } } } } } return resultSet; } private java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> deriveProposal(org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedCsString csString, String content, String prefix, int cursorOffset) { String proposal = csString.getValue(); java.util.Collection<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal> result = new java.util.LinkedHashSet<org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal>(); if (matches(proposal, prefix)) { result.add(new org.reuseware.air.language.abstractsyntax.resource.as.ui.AsCompletionProposal(proposal, prefix, !"".equals(prefix), false)); } return result; } /** * Calculates the prefix for each given expected element. The prefix depends on * the current document content, the cursor position, and the position where the * element is expected. */ private void setPrefixes(java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedElements, String content, int cursorOffset) { if (cursorOffset < 0) { return; } for (org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal expectedElement : expectedElements) { java.lang.String prefix = findPrefix(expectedElements, expectedElement, content, cursorOffset); expectedElement.setPrefix(prefix); } } public org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[] getElementsExpectedAt(org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[] allExpectedElements, int cursorOffset) { java.util.List<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal> expectedAtCursor = new java.util.ArrayList<org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal>(); for (int i = 0; i < allExpectedElements.length; i++) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal expectedElement = allExpectedElements[i]; int startIncludingHidden = expectedElement.getStartIncludingHiddenTokens(); int end = getEnd(allExpectedElements, i); if (cursorOffset >= startIncludingHidden && cursorOffset <= end) { expectedAtCursor.add(expectedElement); } } return expectedAtCursor.toArray(new org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[expectedAtCursor.size()]); } private int getEnd(org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal[] allExpectedElements, int indexInList) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal elementAtIndex = allExpectedElements[indexInList]; int startIncludingHidden = elementAtIndex.getStartIncludingHiddenTokens(); int startExcludingHidden = elementAtIndex.getStartExcludingHiddenTokens(); for (int i = indexInList + 1; i < allExpectedElements.length; i++) { org.reuseware.air.language.abstractsyntax.resource.as.mopp.AsExpectedTerminal elementAtI = allExpectedElements[i]; int startIncludingHiddenForI = elementAtI.getStartIncludingHiddenTokens(); int startExcludingHiddenForI = elementAtI.getStartExcludingHiddenTokens(); if (startIncludingHidden != startIncludingHiddenForI || startExcludingHidden != startExcludingHiddenForI) { return startIncludingHiddenForI - 1; } } return Integer.MAX_VALUE; } private boolean matches(java.lang.String proposal, java.lang.String prefix) { return (proposal.toLowerCase().startsWith(prefix.toLowerCase()) || org.reuseware.air.language.abstractsyntax.resource.as.util.AsStringUtil.matchCamelCase(prefix, proposal) != null) && !proposal.equals(prefix); } public org.eclipse.swt.graphics.Image getImage(org.eclipse.emf.ecore.EObject element) { if (!org.eclipse.core.runtime.Platform.isRunning()) { return null; } org.eclipse.emf.edit.provider.ComposedAdapterFactory adapterFactory = new org.eclipse.emf.edit.provider.ComposedAdapterFactory(org.eclipse.emf.edit.provider.ComposedAdapterFactory.Descriptor.Registry.INSTANCE); adapterFactory.addAdapterFactory(new org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory()); adapterFactory.addAdapterFactory(new org.eclipse.emf.ecore.provider.EcoreItemProviderAdapterFactory()); adapterFactory.addAdapterFactory(new org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory()); org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider labelProvider = new org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider(adapterFactory); return labelProvider.getImage(element); } }