blob: 6b0c458b07884b2cb337e6aa608e932107b189f8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2014 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
*******************************************************************************/
package org.eclipse.wst.html.ui.internal.text.correction;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension2;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.wst.html.core.internal.HTMLCoreMessages;
import org.eclipse.wst.html.core.internal.HTMLCorePlugin;
import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames;
import org.eclipse.wst.html.core.internal.validate.StringMatcher;
import org.eclipse.wst.html.ui.internal.HTMLUIMessages;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
import org.eclipse.wst.sse.ui.internal.reconcile.TemporaryAnnotation;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
public class HTMLAttributeValidationQuickFixProcessor implements IQuickAssistProcessor {
private StringMatcher UNDEFINED_ATTRIBUTE_NAME_MATCHER;
private IPreferencesService fPreferenceService;
public HTMLAttributeValidationQuickFixProcessor() {
String templ = HTMLCoreMessages.Undefined_attribute_name___ERROR_;
templ = templ.replaceAll("\\{[0-9]*\\}", "\\*"); //$NON-NLS-1$
UNDEFINED_ATTRIBUTE_NAME_MATCHER = new StringMatcher(templ);
fPreferenceService = Platform.getPreferencesService();
}
/*
* @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#getErrorMessage()
*/
public String getErrorMessage() {
return null;
}
/*
* @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canFix(org.eclipse.jface.text.source.Annotation)
*/
public boolean canFix(Annotation annotation) {
boolean result = false;
String text = null;
if (annotation instanceof TemporaryAnnotation) {
TemporaryAnnotation tempAnnotation = (TemporaryAnnotation) annotation;
int problemID = tempAnnotation.getProblemID();
text = tempAnnotation.getText();
if (problemID == 0 && text != null)
result = true;
} else if (annotation instanceof MarkerAnnotation) {
MarkerAnnotation markerAnnotation = (MarkerAnnotation) annotation;
text = markerAnnotation.getText();
IMarker marker = markerAnnotation.getMarker();
IResource resource = marker == null ? null : marker.getResource();
if (resource != null && resource.exists() && resource.isAccessible() && text != null) {
result = true;
}
}
result = (result && UNDEFINED_ATTRIBUTE_NAME_MATCHER.match(text));
return result;
}
/*
* @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#canAssist(org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext)
*/
public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
return true;
}
/*
* @see org.eclipse.jface.text.quickassist.IQuickAssistProcessor#computeQuickAssistProposals(org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext)
*/
public ICompletionProposal[] computeQuickAssistProposals(
IQuickAssistInvocationContext invocationContext) {
ISourceViewer viewer = invocationContext.getSourceViewer();
int documentOffset = invocationContext.getOffset();
int length = viewer != null ? viewer.getSelectedRange().y : 0;
IAnnotationModel model = viewer.getAnnotationModel();
if (model == null)
return null;
List proposals = new ArrayList();
if (model instanceof IAnnotationModelExtension2) {
Iterator iter = ((IAnnotationModelExtension2) model).getAnnotationIterator(documentOffset, length, true, true);
while (iter.hasNext()) {
Annotation anno = (Annotation) iter.next();
if (canFix(anno)) {
int offset = -1;
if (anno instanceof TemporaryAnnotation) {
offset = ((TemporaryAnnotation)anno).getPosition().getOffset();
} else if (anno instanceof MarkerAnnotation) {
offset = ((MarkerAnnotation)anno).getMarker().getAttribute(IMarker.CHAR_START, -1);
}
if (offset == -1)
continue;
IDOMNode node = (IDOMNode) ContentAssistUtils.getNodeAt(viewer, offset);
if (!(node instanceof Element))
continue;
Object adapter = (node instanceof IAdaptable ? ((IAdaptable)node).getAdapter(IResource.class) : null);
IProject project = (adapter instanceof IResource ? ((IResource)adapter).getProject() : null);
IScopeContext[] fLookupOrder = new IScopeContext[] {new InstanceScope(), new DefaultScope()};
if (project != null) {
ProjectScope projectScope = new ProjectScope(project);
if(projectScope.getNode(getPreferenceNodeQualifier())
.getBoolean(getProjectSettingsKey(), false))
fLookupOrder = new IScopeContext[] {projectScope, new InstanceScope(), new DefaultScope()};
}
boolean ignore = fPreferenceService.getBoolean(
getPreferenceNodeQualifier(), HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES,
HTMLCorePreferenceNames.IGNORE_ATTRIBUTE_NAMES_DEFAULT, fLookupOrder);
String ignoreList = fPreferenceService.getString(
getPreferenceNodeQualifier(), HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE,
HTMLCorePreferenceNames.ATTRIBUTE_NAMES_TO_IGNORE_DEFAULT, fLookupOrder);
Set result = new HashSet();
if (ignoreList.trim().length() > 0) {
String[] names = ignoreList.split(","); //$NON-NLS-1$
for (int i = 0; names != null && i < names.length; i++) {
String name = names[i] == null ? null : names[i].trim();
if (name != null && name.length() > 0)
result.add(name.toLowerCase());
}
}
String name = getAttributeName(node, offset);
if (name == null) continue;
// If ignore == false. then show a quick fix anyway (due to allow to turn 'ignore' option on)
if (!ignore || shouldShowQuickFix(result, name.toLowerCase())) {
IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal(
name.toLowerCase(), offset, NLS.bind(HTMLUIMessages.DoNotValidateAttribute, name),
HTMLUIMessages.DoNotValidateAttributeAddInfo, node);
if (!proposals.contains(p))
proposals.add(p);
}
int dashIndex = name.indexOf('-');
while (dashIndex != -1) {
StringBuffer namePattern = new StringBuffer(name.substring(0, dashIndex + 1)).append('*');
// Do not continue creating proposals for the rest of patterns if
// a more common pattern is already created
if (ignore && result.contains(namePattern.toString().toLowerCase()))
break;
IgnoreAttributeNameCompletionProposal p = new IgnoreAttributeNameCompletionProposal(
namePattern.toString().toLowerCase(), offset, NLS.bind(HTMLUIMessages.DoNotValidateAllAttributes, namePattern.toString()),
HTMLUIMessages.DoNotValidateAllAttributesAddInfo, node);
if (!proposals.contains(p))
proposals.add(p);
dashIndex = name.indexOf('-', dashIndex + 1);
}
}
}
}
if (proposals.isEmpty())
return null;
return (ICompletionProposal[]) proposals.toArray(new ICompletionProposal[proposals.size()]);
}
private String getAttributeName(IDOMNode node, int offset) {
IStructuredDocumentRegion startStructuredDocumentRegion = node == null ? null : node.getStartStructuredDocumentRegion();
// Only a tag start can have attributes
if ((startStructuredDocumentRegion != null) && startStructuredDocumentRegion.containsOffset(offset)) {
NamedNodeMap attributes = node.getAttributes();
if (attributes == null) return null;
for (int i = 0; i < attributes.getLength(); i++) {
IDOMAttr attr = (IDOMAttr)attributes.item(i);
if (attr.getNameRegionStartOffset() <= offset && attr.getNameRegionEndOffset() >= offset) {
return attr.getName();
}
}
}
return null;
}
private boolean shouldShowQuickFix(Set lcIgnoredPatterns, String attrName) {
// Check the attribute name absence in ignore list
String [] lcPatterns = (String[])lcIgnoredPatterns.toArray(new String[0]);
for (int i = 0; i < lcPatterns.length; i++) {
StringMatcher strMatcher = new StringMatcher(lcPatterns[i]);
if (strMatcher.match(attrName.toLowerCase())) {
return false; // The attribute name is already ignored, no need to show a quickfix
}
}
// The attribute name is not ignored yet, need to show a quickfix
return true;
}
private String getPreferenceNodeQualifier() {
return HTMLCorePlugin.getDefault().getBundle().getSymbolicName();
}
private String getProjectSettingsKey() {
return HTMLCorePreferenceNames.USE_PROJECT_SETTINGS;
}
}